总结
豆包 AI 助手文章总结

06-HAL库硬件SPI DMA驱动LCD并移植LVGL 8.3

絕刀狂花
发布: 2025-04-27 15:24:11
原创
665人浏览过

本节内容将详细介绍如何使用hal库通过硬件spi dma在cubemx中配置及相关注意事项,以及如何使用spi dma驱动lcd显示屏并移植lvgl v8.3。

源码地址:https://www.php.cn/link/d466c07ceb8f2e0704c4da35f9bd11c9

或者关注公众号,在后台回复“SPI DMA”即可获取本章节的源码。

HAL库SPI DMA在CubeMX中的配置

配置SPI DMA在CubeMX中非常简单,只需将SPI1的TX配置为DMA模式,并选择正常模式即可。请注意,不要使用循环模式,否则可能导致LVGL显示异常。

06-HAL库硬件SPI DMA驱动LCD并移植LVGL 8.3

接下来,让我们详细解析配置生成的代码,主要关注DMA的模式配置参数。

/* 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);
登录后复制

继续介绍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);
登录后复制

这些接口主要用于发送、接收,以及暂停、恢复和停止DMA传输。本次驱动LCD主要使用的是DMA发送函数,它包含三个参数:

/** 
 * @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);
登录后复制

LCD驱动函数的改造

在上一章节中,我们使用的是SPI驱动的LCD,本章节将对驱动函数进行改造,加入DMA驱动。只需将SPI发送函数改为DMA发送函数即可,以下几个函数也需要进行相应的修改:

// ST7789写函数
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);
  }
}
登录后复制

移植LVGL V8.3

本次移植的是LVGL V8.3,源码可在LVGL官方GitHub上下载。如果不想自己下载,可以通过关注公众号并在后台回复“SPI DMA”获取源码。

LVGL的移植过程不再详细描述,网上有大量教程可供参考。我们直接使用提供的源码即可。以下是几个关键的移植点:

  1. 周期调用LVGL tick接口:在定时器3的回调函数中调用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);
      }
    }
    // }
  }
}
登录后复制
  1. 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);
}
登录后复制

刷新函数:

/*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); // 指定填充区域
        //                                                                  // 一行一行 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); // 指定填充区域
        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);
}
登录后复制
  1. 移植后的目录结构如下:

06-HAL库硬件SPI DMA驱动LCD并移植LVGL 8.3

  1. 代码展示:我们将在LCD任务中替换上一章节展示的电子表功能,代码如下:
/** 
 * @function lcd menu thread 
 * @author:小飞哥玩嵌入式-小飞哥 
 * @TODO: LED控制线程 
 * @param: 
 * @return: NULL 
 */
static void rt_lcd_menu_entry(void *parameter)
{
  uint8_t s_cnt = 0;
  key_para_t key_para = { 0 };
  clock_time_t clock_time = { 0 };
  lcd_st7789_init();
  lcd_st7789_clear(LCD_DISP_WHITE);
  rt_thread_mdelay(2);
  lcd_st7789_fill_area(10, 10, 40, 40, LCD_DISP_BLUE);
  embedded_tim_start_init();
  // clock_time.hour = 11;
  // clock_time.minute = 50;
  // menu_main_window();
  // lcd_menu_key_init();
  lv_init(); // lvgl 系统初始化
  lv_port_disp_init(); // lvgl 显示接口初始化,放在 lv_init()的后面
  lv_port_indev_init(); // lvgl 输入接口初始化,放在 lv_init()的后面
  // lv_example_btn_1();
  // lv_example_led_1();
  // lv_example_calendar_1();
  lv_demo_stress();
  for (;;) {
    //    s_cnt++;
    //    {
    //      if (s_cnt > 2)
    //        s_cnt = 0;
    //      clock_time.second++;
    //    }
    //    if (clock_time.second > 59)
    //    {
    //      clock_time.second = 0;
    //      clock_time.minute++;
    //      if (clock_time.minute > 59)
    //      {
    //        clock_time.minute = 0;
    //        clock_time.hour++;
    //        if (clock_time.hour > 12)
    //        {
    //          clock_time.hour = 1;
    //        }
    //      }
    //    }
    //    lcd_menu_keyvalue_get(&key_para, 20);
    //    lcd_menu_handler(&key_para);
    //    menu_clock_run(&clock_time);
    lv_task_handler();
    rt_thread_mdelay(5);
  }
}
登录后复制

实现效果不再展示,大家可以拿起手里的开发板,动手实践一下。

以上就是06-HAL库硬件SPI DMA驱动LCD并移植LVGL 8.3的详细内容,更多请关注php中文网其它相关文章!

驱动精灵
驱动精灵

驱动精灵基于驱动之家十余年的专业数据积累,驱动支持度高,已经为数亿用户解决了各种电脑驱动问题、系统故障,是目前有效的驱动软件,有需要的小伙伴快来保存下载体验吧!

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
豆包 AI 助手文章总结
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号