This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Do multiple SPI transactions with PPI and timer

I have a serial OLED LCD on my SPI bus on my nrf52840
I want to do periodic LCD refresh by sending it's frame buffer without involving CPU.

I already know how to do SPI transaction with PPI. But this is a slightly different. 

Below are the steps involved in refreshing the frame buffer to LCD.

-clear DC pin (LCD command mode)
-clear CS pin
-send SPI transaction for row / column / frame size configuration ( 9 bytes)
-set CS pin
-set DC pin (LCD data mode)
-clear CS pin
-send SPI transaction for full frame buffer ( 8192 bytes )
-set CS pin

Can some one please explain me how can I do this with timer/compare and PPI. So far I know how to do single SPI transaction with PPI but this involves two SPI transactions and two GPIO toggle

  • Hi,

    I do not really see how this could be possible without cpu interaction. Seems to me you will have to update the spi data pointer in between the two transfers?

  • thank you for your reply. Is the issue is only with the variable SPI buffer length ? 

  • No, not only the buffer length, changing the content that is sent by SPI requires cpu input. Assuming you are refreshing the display periodically you might be able to do this inbetween the ppi events. But it would create a race condition in case updating the buffer content is mandatory. As an option you could give the cpu highest priority for updating this. But I guess there is a good reason why you would like to do this using PPI, such as running the SD or similar?

  • Hi,

    I have found a solution to this problem. Turns out we do not need to do step 1 to 4 all time time.
    We only need to do it once after LCD is initialized. (Unless you need to re initialize for some reason). 
    So we can easily do this with PPI and timer completely independent of the CPU. Below is my initialization code.

    void lcd_spi_bus_init(void)
    {
        ret_code_t err_code;
    
        nrfx_spim_config_t spi_oled_config = NRFX_SPIM_DEFAULT_CONFIG;
    
        spi_oled_config.sck_pin = NRFX_SPIM_SCK_PIN;
        spi_oled_config.mosi_pin = NRFX_SPIM_MOSI_PIN;
        spi_oled_config.ss_pin = NRFX_SPIM_CS_PIN;
        spi_oled_config.frequency = SPIM_FREQUENCY_FREQUENCY_M32;
        spi_oled_config.mode = NRF_SPIM_MODE_0;
        spi_oled_config.ss_active_high = false;
    
        err_code = nrfx_spim_init(&m_lcd_spi, &spi_oled_config, spi_lcd_handler, NULL);
        APP_ERROR_CHECK(err_code);
    }
    
    void lcdRefreshTimerHandler(nrf_timer_event_t event_type, void *p_context)
    {
        //dummy handler
    }
    
    nrf_ppi_channel_t ppi_channel_lcd_refresh_timer;
    nrf_ppi_channel_t ppi_channel_lcd_cs_raise;
    void OLED_Display_Init()
    {
        uint32_t cs_task_addr;
        uint32_t spi_task_addr;
        uint32_t spi_end_evt_addr;
        ret_code_t err_code;
    
        lcd_spi_bus_init();
        nrf_gpio_cfg(
            NRFX_SPIM_MOSI_PIN,
            NRF_GPIO_PIN_DIR_INPUT,
            NRF_GPIO_PIN_INPUT_DISCONNECT,
            NRF_GPIO_PIN_PULLDOWN,
            NRF_GPIO_PIN_H0H1,
            NRF_GPIO_PIN_NOSENSE);
    
        nrf_gpio_cfg_output(NRFX_SPIM_DC_PIN);
        nrf_gpio_cfg_output(NRFX_SPIM_RESET_PIN);
    
        clearReset();
        nrf_delay_ms(20);
        setReset();
        nrf_delay_ms(100);
    
        lcd_InitRegisters();
        memset(displaybuffer, 0x0, LCD_BUFFER_LEN);
        OLED_DrawBitMap(0, 0, gImage_spacex);
    
        // init lcd cs gpiote
        nrf_drv_gpiote_out_config_t config_lcd_cs_out = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true); 
        err_code = nrf_drv_gpiote_out_init(NRFX_SPIM_CS_PIN, &config_lcd_cs_out);
        APP_ERROR_CHECK(err_code);
    
        // allocate PPI channels for hardware
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_lcd_refresh_timer);
        APP_ERROR_CHECK(err_code);
    
        cs_task_addr = nrf_drv_gpiote_out_task_addr_get(NRFX_SPIM_CS_PIN);
        spi_task_addr = nrfx_spim_start_task_get(&m_lcd_spi);
        spi_end_evt_addr = nrfx_spim_end_event_get(&m_lcd_spi);
    
        // lower cs when timer intterupt
        uint32_t compare_evt_addr;
        compare_evt_addr = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE0);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel_lcd_refresh_timer, compare_evt_addr, cs_task_addr);
        APP_ERROR_CHECK(err_code);
    
        // fork spi transaction when cs lower task
        err_code = nrf_drv_ppi_channel_fork_assign(ppi_channel_lcd_refresh_timer, spi_task_addr);
        APP_ERROR_CHECK(err_code);
    
        // allocate cs raise ppi channel
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_lcd_cs_raise);
        APP_ERROR_CHECK(err_code);
    
        // raise cs pin when spi trsaction ends
        err_code = nrf_drv_ppi_channel_assign(ppi_channel_lcd_cs_raise, spi_end_evt_addr, cs_task_addr);
        APP_ERROR_CHECK(err_code);
    
        // set up lcd refresh spi transaction for later
        setDC();    // set DC for lcd data
        nrfx_spim_xfer_desc_t spim_lcd_xfer_desc = NRFX_SPIM_XFER_TX(displaybuffer, LCD_BUFFER_LEN); 
        err_code = nrfx_spim_xfer(&m_lcd_spi, &spim_lcd_xfer_desc, NRFX_SPIM_FLAG_HOLD_XFER);
        APP_ERROR_CHECK(err_code);
    
        // enable CS task
        nrf_drv_gpiote_out_task_enable(NRFX_SPIM_CS_PIN);
    
        err_code = nrf_drv_ppi_channel_enable(ppi_channel_lcd_cs_raise);
        APP_ERROR_CHECK(err_code);
        
        err_code = nrf_drv_ppi_channel_enable(ppi_channel_lcd_refresh_timer);
        APP_ERROR_CHECK(err_code);
    
        // set up timer for 50 ms for LCD refresh 
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
        err_code = nrf_drv_timer_init(&timer, &timer_cfg, lcdRefreshTimerHandler);
        APP_ERROR_CHECK(err_code);
    
        uint32_t time_ticks = nrf_drv_timer_ms_to_ticks(&timer, 50);
    
        nrf_drv_timer_extended_compare(&timer, (nrf_timer_cc_channel_t)0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    
        // here onwards LCD refresh is done independent of CPU
        nrf_drv_timer_enable(&timer);
    }

Related