電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 235|回復: 0
收起左側

06-HAL庫硬件SPI DMA驅動LCD并移植LVGL 8.3

[復制鏈接]

170

主題

170

帖子

1241

積分

三級會員

Rank: 3Rank: 3

積分
1241
跳轉到指定樓層
樓主
發(fā)表于 2024-6-12 08:00:00 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式

關注、星標公眾號,直達精彩內(nèi)容
1、本節(jié)內(nèi)容介紹
  • 1.1、HAL庫硬件SPI DMA在cubemx中的配置及注意事項;
  • 1.2、HAL庫SPI DMA詳解與結構介紹;
  • 1.3、使用SPI DMA驅動LCD顯示屏并移植LVGL V8.3源碼地址:https://gitee.com/MR_Wyf/hal-cubemx-rt-thread/tree/hal_rttNano_st7789_menu/
    或者關注公眾號,后臺回復“SPI DMA”,獲取本章節(jié)源碼
    2、HAL庫SPI DMA在CUBEMX中的配置2.1、配置界面配置非常簡單,只需要選擇SPI1的TX配置為DMA模式即可,選擇正常模式即可,不需要循環(huán)模式,否則LVGL可能會顯示異常

    2.2、SPI DMA代碼詳解先來看上面配置生成的代碼,主要就是DMA的模式配置參數(shù)
        /* SPI1 DMA Init */
        /* SPI1_TX Init */
        hdma_spi1_tx.Instance = DMA1_Channel3;
        hdma_spi1_tx.Init.Request = DMA_REQUEST_1;
        hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
        hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_spi1_tx.Init.Mode = DMA_NORMAL;
        hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
        if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
        {
          Error_Handler();
        }
        __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi1_tx);
    繼續(xù)來看下SPI DMA的接口:
    HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
    HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
    HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                                  uint16_t Size);
    HAL_StatusTypeDef HAL_SPI_DMAPause(SPI_HandleTypeDef *hspi);
    HAL_StatusTypeDef HAL_SPI_DMAResume(SPI_HandleTypeDef *hspi);
    HAL_StatusTypeDef HAL_SPI_DMAStop(SPI_HandleTypeDef *hspi);
    主要就是發(fā)送、接收,以及接收暫停、接受恢復、接收停止功能函數(shù),本次驅動LCD主要用到的是DMA發(fā)送函數(shù),主要有3個參數(shù)
    /**
      * @brief  Transmit an amount of data in non-blocking mode with DMA.
      * @param  hspi pointer to a SPI_HandleTypeDef structure that contains
      *               the configuration information for SPI module.
      * @param  pData pointer to data buffer
      * @param  Size amount of data to be sent
      * @retval HAL status
      */
    HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
    2.3 LCD驅動函數(shù)改造上一章節(jié)中我們使用的是SPI驅動的LCD,并沒有加入DMA,本章節(jié)咱們對驅動函數(shù)進行改造,加入DMA驅動,只需要把我們的SPI發(fā)送函數(shù)改為DMA發(fā)送函數(shù)即可,以下幾個函數(shù)同理改動
    // ST7789寫函數(shù)
    static HAL_StatusTypeDef lcd_st7789_write(int is_cmd, uint8_t data)
    {
    uint8_t pData[2] = { 0 };
    assert_param(NULL != hspi_lcd);
    pData[0] = data;
    if (is_cmd)
      HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET);
    else
      HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET);
    // return HAL_SPI_Transmit(hspi_lcd, pData, 1, HAL_MAX_DELAY);
      return HAL_SPI_Transmit_DMA(hspi_lcd, pData, 1);
    }
    /********************************************************************
    *
    *       LcdWriteReg
    *
    * Function description:
    *   Sets display register
    */
    void lcd_st7789_write_reg(uint8_t Data)
    {
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET);
    // HAL_SPI_Transmit(&hspi1, &Data, 1, 10);
    HAL_SPI_Transmit_DMA(&hspi1, &Data, 1);
    }
    /********************************************************************
    *
    *       LcdWriteData
    *
    * Function description:
    *   Writes a value to a display register
    */
    void lcd_st7789_write_data(uint8_t Data)
    {
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET);
    HAL_SPI_Transmit_DMA(&hspi1, &Data, 1);
    //HAL_SPI_Transmit(&hspi1, &Data, 1, 10);
    }
    /********************************************************************
    *
    *       lcd_st7789_write_data_multiple
    *
    * Function description:
    *   Writes multiple values to a display register.
    */
    extern uint8_t g_spi_dma_tc;
    void lcd_st7789_write_data_multiple(uint8_t *pData, int NumItems)
    {
    if (g_spi_dma_tc) {
      g_spi_dma_tc = 0;
      HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET);
      //HAL_SPI_Transmit(&hspi1, pData, NumItems, 10);
      HAL_SPI_Transmit_DMA(&hspi1, pData, NumItems);
    }
    }
    3、移植LVGL V8.3本次移植的是lvgl V8.3,源碼在lvgl官方github上就可以下載到,想下載的兄弟小手動一動,不想下載的也沒關系,小飛哥會把源碼開源,直接拿過去就行了
    至于LVGL的移植,就不再贅述了,相信網(wǎng)上有成堆的教程,小飛哥也不再廢話浪費大家伙時間了,直接下載源碼即可
    主要強調(diào)幾個移植的點:
  • 1、周期調(diào)用lvgl tick接口,提供lvgl“心跳”在定時器3回調(diào)函數(shù)中調(diào)用lv_tick_inc(1),為LVGL提供心跳,周期10ms足夠
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
      /**timer for lvgl,period 1ms*/
      if (htim->Instance == TIM3)
      {
        lv_tick_inc(1);
      }
      if (htim->Instance == TIM15)
      {
        // if(RT_EOK==rt_sem_take(sem_uart_rec,RT_WAITING_NO))
        // {
        if (embedded_get_uart_rec_flag())
        {
          /*100ms*/
          if (embedded_get_uart_timeout_cnt() > 9)
          {
            //      lv_tick_inc(1);
            embedded_set_uart_rec_flag(RT_FALSE);
            rt_sem_release(sem_uart_timeout);
          }
        }
        // }
      }
    }
  • 2、lvgl初始化配置,使用“雙緩存”void lv_port_disp_init(void)
    {
        /*-------------------------
         * Initialize your display
         * -----------------------*/
        disp_init();
        /*-----------------------------
         * Create a buffer for drawing
         *----------------------------*/
        /**
         * LVGL requires a buffer where it internally draws the widgets.
         * Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
         * The buffer has to be greater than 1 display row
         *
         * There are 3 buffering configurations:
         * 1. Create ONE buffer:
         *      LVGL will draw the display's content here and writes it to your display
         *
         * 2. Create TWO buffer:
         *      LVGL will draw the display's content to a buffer and writes it your display.
         *      You should use DMA to write the buffer's content to the display.
         *      It will enable LVGL to draw the next part of the screen to the other buffer while
         *      the data is being sent form the first buffer. It makes rendering and flushing parallel.
         *
         * 3. Double buffering
         *      Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
         *      This way LVGL will always provide the whole rendered screen in `flush_cb`
         *      and you only need to change the frame buffer's address.
         */
        /* Example for 1) */
        static lv_disp_draw_buf_t draw_buf_dsc_1;
        static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                             /*A buffer for 10 rows*/
        lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
        /* Example for 2) */
        static lv_disp_draw_buf_t draw_buf_dsc_2;
        static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                                /*A buffer for 10 rows*/
        static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];                                /*An other buffer for 10 rows*/
        lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
        /* Example for 3) also set disp_drv.full_refresh = 1 below*/
        static lv_disp_draw_buf_t draw_buf_dsc_3;
        static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/
        static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/
        lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,
                              MY_DISP_VER_RES * MY_DISP_VER_RES); /*Initialize the display buffer*/
        /*-----------------------------------
         * Register the display in LVGL
         *----------------------------------*/
        static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
        lv_disp_drv_init(&disp_drv);   /*Basic initialization*/
        /*Set up the functions to access to your display*/
        /*Set the resolution of the display*/
        disp_drv.hor_res = MY_DISP_HOR_RES;
        disp_drv.ver_res = MY_DISP_VER_RES;
        /*Used to copy the buffer's content to the display*/
        disp_drv.flush_cb = disp_flush;
        /*Set a display buffer*/
        disp_drv.draw_buf = &draw_buf_dsc_2;
        disp_drv_p = &disp_drv;
        /*Required for Example 3)*/
        // disp_drv.full_refresh = 1;
        /* Fill a memory array with a color if you have GPU.
         * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
         * But if you have a different GPU you can use with this callback.*/
        // disp_drv.gpu_fill_cb = gpu_fill;
        /*Finally register the driver*/
        lv_disp_drv_register(&disp_drv);
    }
    刷新函數(shù):
    /*Flush the content of the internal buffer the specific area on the display
    *You can use DMA or any hardware acceleration to do this operation in the background but
    *'lv_disp_flush_ready()' has to be called when finished.*/
    static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
    {
        if (disp_flush_enabled)
        {
            // /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
            //        int32_t x;
            //        int32_t y;
            //        for (y = area->y1; y y2; y++)
            //        {
            //            for (x = area->x1; x x2; x++)
            //            {
            //                /*Put a pixel to the display. For example:*/
            //                /*put_px(x, y, *color_p)*/
            //                lcd_st7789_write_pixel(x, y, color_p->full);
            //                color_p++;
            //            }
            //        }
            // int32_t y;
            // lcd_st7789_set_addr_win(area->x1, area->y1, area->x2, area->y2); // 指定填充區(qū)域
            //                                                                  // 一行一行 DMA
            // for (y = area->y1; y y2; y++)
            // {
            //     lcd_st7789_write_data_multiple((uint8_t *)color_p, (uint16_t)(area->x2 - area->x1 + 1) * 2);
            //     color_p += (area->x2 - area->x1 + 1);
            // }
            unsigned int size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2;
            lcd_st7789_set_addr_win(area->x1, area->y1, area->x2, area->y2); // 指定填充區(qū)域
            lcd_st7789_write_data_multiple((uint8_t *)color_p, size);
        }
        /*IMPORTANT!!!
         *Inform the graphics library that you are ready with the flushing*/
        //    lv_disp_flush_ready(disp_drv);
    }
  • 3、移植后的目錄如下
  • 回復

    使用道具 舉報

    發(fā)表回復

    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規(guī)則

    關閉

    站長推薦上一條 /1 下一條


    聯(lián)系客服 關注微信 下載APP 返回頂部 返回列表