Skip to content

Ssd1309 display with async DMA only fills the first block of the screen, then hangs #939

@Patrickfen

Description

@Patrickfen

I'm working on a small project combining an SSD1309 display driven by a SAMD21G18A mcu. I'm hoping to use embassy in order to display graphics until a user presses a button, but I'm having some strange issues with async:

bind_interrupts!(struct Irqs {
    EIC => eic::InterruptHandler;
    SERCOM0 => sercom::i2c::InterruptHandler<sercom::Sercom0>;
    DMAC => dmac::InterruptHandler;
});

#[embassy_executor::main]
async fn main(_spawn: Spawner) {
    // Setup
    let mut peripherals = pac::Peripherals::take().unwrap();
    let _core = pac::CorePeripherals::take().unwrap();
    let mut clocks = clock::GenericClockController::with_internal_8mhz(
        peripherals.gclk,
        &mut peripherals.pm,
        &mut peripherals.sysctrl,
        &mut peripherals.nvmctrl,
    );
    let pins = gpio::Pins::new(peripherals.port);

    // Initialize DMA Controller
    let mut dmac =
        dmac::DmaController::init(peripherals.dmac, &mut peripherals.pm).into_future(Irqs);
    let channels: dmac::FutureChannels = dmac.split();
    let channel: dmac::Channel<dmac::Ch0, dmac::ReadyFuture> =
        channels.0.init(dmac::PriorityLevel::Lvl0);

    // I2C
    let gclk0 = clocks.gclk0();
    let sercom0_clock = &clocks.sercom0_core(&gclk0).unwrap();
    let pads = sercom::i2c::Pads::new(
        pins.pa08.into_alternate::<gpio::C>(), // A4
        pins.pa09.into_alternate::<gpio::C>(), // A5
    );
    let i2c = sercom::i2c::Config::new(
        &peripherals.pm,
        peripherals.sercom0,
        pads,
        sercom0_clock.freq(),
    )
    .baud(400.kHz())
    .enable()
    .into_future(Irqs)
    .with_dma_channel(channel); // -- works if removed --

    // Display
    let di: I2CInterface<_> = display_interface_i2c::I2CInterface::new(i2c, 0x3C, 0x40);
    let mut display: GraphicsMode<_, _> =
        oled_async::Builder::new(oled_async::displays::ssd1309::Ssd1309_128_64 {})
            .connect(di)
            .into();
    display.init().await.unwrap();
    display.clear();
    display.fill_solid(&display.bounding_box(), On).unwrap();
    display.flush().await.unwrap();

    loop {}
}

I can see the display spring to life and display a block of white, however it doesn't fill the entire screen, and adding panics to the code shows me that it never reaches beyond display.flush().await.unwrap();. Interestingly, as soon as I remove .with_dma_channel(channel); from the i2c config, everything does work, though slow enough to see blocks of the screen update in sequence (exactly what I hoped to prevent by using DMA). Could this be an issue with the DMA implementation in atsamd hal not waking up the executor? From what I saw in the existing examples there doesn't seem to be any additional setup or interrupts that need to be configured to get DMA working.

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions