OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 3965|回复: 4

STM32CubeMX6.0 + HAL + LittleVGL7.6 等学习[最全附工程源码]

[复制链接]

14

主题

40

帖子

0

精华

初级会员

Rank: 2

积分
153
金钱
153
注册时间
2017-6-27
在线时间
48 小时
发表于 2020-11-5 19:45:31 | 显示全部楼层 |阅读模式
本帖最后由 1061700625 于 2020-11-5 19:46 编辑

markdown复制过来格式不大好,可以看这个:
https://blog.csdn.net/sxf1061700625/article/details/109015919


[size=0.9]
[url=]STM32CubeMX + HAL[/url][url=]前言[/url][url=]紧急避坑[/url][url=]USART[/url][url=]freertos+fatfs+sdio[/url][url=]一些说明[/url][url=]Cube基本使用[/url][url=]HAL库函数[/url][url=]中断回调函数[/url][url=]外设对应时钟[/url][url=]配置示例[/url][url=]小编有话说[/url][url=]USART[/url][url=]RTC[/url][url=]SDIO + FATFS[/url][url=]SDRAM[/url][url=]LTDC + DMA2D[/url][url=]FreeRTOS[/url][url=]TouchGFX显示[/url][url=]LittleVGL[/url][url=]显示图片[/url][url=]C数组形式[/url][url=]canvas画图[/url][url=]文件系统[/url][url=]显示中文[/url][url=]待补充...[/url]

STM32CubeMX + HAL前言
我的CSDN博客:小锋学长生活大爆炸
紧急避坑USART
问题1: 打印正常,但是加入接收中断后,开始出bug,最后锁定接收中断挂掉了。原因: HAL库的串口接收发送函数有bug,就是收发同时进行的时候,会出现锁死的现象。解决: 需要注释掉    HAL_UART_Receive_IT 和 HAL_UART_Transmit_IT 中的 __HAL_LOCK(huart) 函数。或者不要在接收里面,每接收到一个字符就printf一下。
问题2:  在接收中断中使用HAL_UART_Receive_IT()函数,会导致CR1的RXNEIE 置0,最后一直处于错误状态,无法进行接收。解决:  注释掉    HAL_UART_Receive_IT 中的 HAL_LOCK(huart) 函数

freertos+fatfs+sdio
问题:没有加freertos时候,sd卡读写正常;加上freertos时候,mout成功,但read等其他操作返回错误3 not ready
解决: sdio和sddma的中断优先级要小于freertos的最小优先级
一些说明
使用STM32CubeMX代码生成工具,不用关注底层配置的细节,真舒服。
使用教程:
虽然Cube+HAL很舒服,但新手不建议用。最好还是先去学一下标准库怎么用,有个大致概念后,再来学这一套。
自动化的东西虽好,但一旦出了问题,解决起来也是挺头疼的。


Cube基本使用
  • 新建工程
  • 选择芯片
  • Pinout&Configuration,选择RCC(HSE:Crystal/Ceramic Resonator)、SYS(Debug:Serial Wiire)
  • Clock Configuration,配置时钟树

  • Project Manager,配置工程输出项

  • Pinout&Configuration,选择功能(若是选GPIO相关,可以直接在Pinout view选择;若是其他功能,可以在左边Categories打开,会自动配置引脚)、设置Parameter Settings/NVIC等

  • GENERATE CODE,生成工程,用KEIL打开编辑



HAL库函数
  • 函数形式:均以HAL_开头
  • 寻找过程:在驱动文件stm32f4xx_hal_XXX.c或其.h文件中找函数定义,一般在靠后位置
  • 其他说明:

    • HAL库并没有把所有的操作都封装成凼数。
    • 对于底层的寄存器操作(如读取捕获/比较寄存器),还有修改外设的某个配置参数(如改变输入捕获的极性),HAL库会使用宏定义来实现。而且会用__HAL_作为这类宏定义的前缀
    • 获取某个参数,宏定义中一般会有_GET;而设置某个参数的,宏定义中就会有_SET。
    • 在开发过程中,如果遇到寄存器级别或者更小范围的操作时,可以到该外设的头文件中查找,一般都能找到相应的宏定义。
    • HAL库函数第一个参数一般都是句柄(一个包含了当前对象绝大部分状态的结构体),虽然增加了开销,但是用起来便捷了非常多。




中断回调函数
  • 函数形式:HAL_XXX_XXXCallback()。
  • 寻找过程:中断文件stm32f4xx_it.c - > 中断函数XXX_IRQHandler(void) -> HAL库中断函数HAL_XXX_IRQHandler(GPIO_PIN_13) -> 回调函数HAL_XXX_XXXCallback()



外设对应时钟
  • 随便进入一个外设初始化函数,如MX_GPIO_Init()
  • 随便进入一个时钟使能函数,如__HAL_RCC_GPIOC_CLK_ENABLE()
  • 随便进入一个RCC宏定义,如RCC_AHB1ENR_GPIOCEN
  • 或者直接进入stm32f429xx.h文件
  • 里面有所有外设与时钟对应关系,如RCC_AHB1ENR_DMA1EN

配置示例小编有话说
  • 例子源码:
  • 如果配置过程中,参数不知道怎么设置,可以去标准库例程(如野火、正点原子)中看对应的参数是什么
  • Cube软件只是帮你配置了底层,一些初始化代码还是需要自己手动加的,如SDRAM充电初始化、读写函数等
  • 以下内容都是基于“野火F429IGT6挑战者V2开发板”,其他板子按照原理图改改引脚都能用的



USART
源码链接:
详细教程网上挺多,配置也简单,只要勾选一下USARTx,再开一下中断就行。
在Keil就比较要注意了。
由于每次接收完,程序内部自动把接收中断关了,所以每次要手动打开。
总的来说,加这几部分:
  • main函数中,while之前:

// 使能串口中断接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)&DataTemp_UART1, 1);
  • 任意位置添加printf重定向函数

#include "stdio.h"
int fputc(int ch, FILE *f){
     HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0XFF);
     return ch;
}
  • 任意位置添加中断回调函数

#define UART1BuffLen 200
extern uint8_t DataBuff_UART1[UART1BuffLen];
extern uint32_t DataTemp_UART1;
extern uint16_t DataSTA_UART1;
​
uint32_t DataTemp_UART1;
uint8_t DataBuff_UART1[UART1BuffLen];
uint16_t DataSTA_UART1;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
   if(huart->Instance == USART1){
     if(DataSTA_UART1 < UART1BuffLen){
         if(DataTemp_UART1 == 0x0A && DataSTA_UART1>0 && DataBuff_UART1[DataSTA_UART1-1]==0X0D){
             printf("USART: %s\r\n", DataBuff_UART1);
             DataSTA_UART1 = 0;
         }
         else{
             if(DataSTA_UART1 == 0){
                 memset(DataBuff_UART1, 0, sizeof(DataBuff_UART1));
             }
             DataBuff_UART1[DataSTA_UART1++] = DataTemp_UART1;
         }
     }
     // 使能串口中断接收
     HAL_UART_Receive_IT(&huart1, (uint8_t*)&DataTemp_UART1, 1);
   }
}

RTC
RTC_DateTypeDef sDate;
RTC_TimeTypeDef sTime;  
uint8_t second_tmp = 0;
&#8203;
&#8203;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // 读取时间
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); // 读取日期
if(second_tmp != sTime.Seconds) { // 读取秒
     second_tmp = sTime.Seconds;
     printf("20%d%d-%d%d-%d%d\r\n",
            sDate.Year/10%10, sDate.Year%10,
            sDate.Month/10%10, sDate.Month%10,
            sDate.Date/10%10, sDate.Date%10);
     printf("%d%d:%d%d:%d%d\r\n",
            sTime.Hours/10%10, sTime.Hours%10,
            sTime.Minutes/10%10, sTime.Minutes%10,
            sTime.Seconds/10%10, sTime.Seconds%10);
}

SDIO + FATFS
  • 选择SDIO功能,Pinout&Clock Configuration,Connectivity -> SDIO -> Mode: SD 4bit Wide bus -> 勾选NVIC

  • 配置SDIO时钟,Clock Configuration,SDIO模块输入要求为48MHz,系统提示可以自动设置时钟问题,选择Yes。SDIO时钟分频系数CLKDIV,计算公式为SDIO_CK=48MHz/(CLKDIV+2)也可手动修改时钟配置。如果遇到读写问题,可以试着调整到24MHz

  • 启用文件系统中间件Pinout&Clock Configuration,Middleware -> FATFS,模式选择SD卡,配置文件系统:
    如果要支持中文文件名,则配置CODE_PAGE为Simplified Chinese
    如果要支持长文件名,则要使能USE_LEN

  • 继续上面界面,Advanced Settings勾选Use dma template

  • 设置DMA传输,Pinout&Clock Configuration,Connectivity -> SDIO-> DMA Settings

  • 配置NVIC,Pinout&Clock Configuration,System Core -> NVIC。

注意,SDIO中断优先级必须高于DMA2 stream3和DMA2 stream6的中断优先级
紧急避坑!!!如果没有用freertos,那中断优先级设置没啥关系。但如果用了freertos,那SDIO的优先级必须要注意跟freertos区分开来,不能高过他!不然就是mout正常,read等其他操作都返回错误3 not ready。其实当你开启freertos,然后点击NVIC时候,cube会提醒你,要注意函数的中断优先级和freertos优先级的关系。(如果中断处理程序调用RTOS函数,请确保其抢占优先级低于最高的SysCall中断优先级。如FreeRTOS中的“LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY”)
  • 堆栈设置,Progect Manager -> Project -> Linker Settings,加大堆栈大小(注意:由于刚才设置长文件名动态缓存存储在堆中,故需要增大堆大小,如果不修改则程序运行时堆会生成溢出,程序进入硬件错误中断(HardFault),死循环)。

  • 注意这里要找一个闲置的GPIO用作SD卡的检测脚,并拉低。不然open时候可能会一直失败

  • 生成工程,GENERATE CODE ,用KEIL打开

  • 注意,如果是野火的F429开发板,还需要禁用WIFI引脚才行!!!

static void BL8782_PDN_INIT(void)
{
   /*定义一个GPIO_InitTypeDef类型的结构体*/
   GPIO_InitTypeDef GPIO_InitStructure;
&#8203;
   RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOB, ENABLE);                              
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;   
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;   
   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
   GPIO_Init(GPIOB, &GPIO_InitStructure);   
   
   GPIO_ResetBits(GPIOB,GPIO_Pin_13);  //禁用WiFi模块
}
  • 测试一下

UINT bw;
retSD = f_mount(&SDFatFS, SDPath, 0);
if(retSD != FR_OK) {
     printf("Mount Error :%d\r\n", retSD);
}
retSD = f_open(&SDFile, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);
if(retSD != FR_OK){
     printf("Open Error :%d\r\n", retSD);
}
retSD = f_write(&SDFile, "abcde", 5, &bw);
if(retSD != FR_OK){
     printf("Write Error :%d\r\n", retSD);
}
f_close(&SDFile);
retSD = f_open(&SDFile, "0:/test.txt", FA_READ);
char buff[10] = {0};
retSD = f_read(&SDFile, buff, 5, &bw);
if(retSD == FR_OK){
     printf("%s\r\n", buff);
}
f_close(&SDFile);

SDRAM
  • 开启FMC功能,Pinout&Configuration ,Connectivity -> FMC -> SDRAM2
    SDRAM1的起始地址为0XC0000000SDRAM2的起始地址为0XD0000000
    一般SDRAM都含有4个bank。
    Configuration中的参数可从SDRAM的数据手册上找到。

各个选项的配置(只做解释,不对应上图):
  • Clock and chip enable:FMC_SDCKE0FMC_SDCLK0对应的存储区域1 的地址范围是0xC000 0000-0xCFFF FFFF;而FMC_SDCKE1FMC_SDCLK1 对应的存储区域2 的地址范围是0xD000 0000- 0xDFFF FFFF
  • Bank由硬件连接决定需要选择SDRAM bank 2
  • Column bit number表示列数,8位
  • Row bit number表示行数,12位
  • CAS latency表示CAS潜伏期,即上面说的CL,该配置需要与之后的SDRAM模式寄存器的配置相同,这里先配置为2 memory clock cycles(对于SDRAM时钟超过133MHz的,则需要配置为3 memory clock cycles)
  • Write protection 表示写保护,一般配置为Disabled
  • SDRAM common clock为SDRAM 时钟配置,可选HCLK的2分频\3分频\不使能SDCLK时钟。前面主频配置为216MHz,SDRAM common clock设置为2分频,那SDCLK时钟为108MHz,每个时钟周期为9.25ns
  • SDRAM common burst read 表示突发读,这里选择使能
  • SDRAM common read pipe delay 表示CAS潜伏期后延迟多少个时钟在进行读数据,这里选择0 HCLK clock cycle
  • Load mode register to active delay 加载模式寄存器命令和激活或刷新命令之间的延迟,按存储器时钟周期计

  • Exit self-refresh delay从发出自刷新命令到发出激活命令之间的延迟,按存储器时钟周期数计查数据手册知道其最小值为70ns,由于我们每个时钟周期为9.25ns,所以设为8 (70÷9.25,向上取整)

  • SDRAM common row cycle delay刷新命令和激活命令之间的延迟,以及两个相邻刷新命令之间的延迟, 以存储器时钟周期数表示
    查数据手册知道其最小值为63ns,由于我们每个时钟周期为9.25ns,所以设为7 (63÷9.25,向上取整)

  • Write recovery time写命令和预充电命令之间的延迟,按存储器时钟周期数计

  • SDRAM common row precharge delay预充电命令与其它命令之间的延迟,按存储器时钟周期数计
    查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以设为2 (15÷9.25,向上取整)

  • Row to column delay激活命令与读/写命令之间的延迟,按存储器时钟周期数计
    查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以这里本应该设为2 (15÷9.25,向上取整)
    但要注意,时序必须满足以下式子:
    TWR ≥ TRAS - TRCD
    TWR ≥ TRC - TRCD - TRP
    其中:TWR = Write recovery time = 2
    TRAS = Self refresh time = 5
    TRC = SDRAM common row cycle delay = 7
    TRP = SDRAM common row precharge delay = 2
    TRCD = Row to column delay
    所以这里Row to column delay应该取3

  • 生成代码,GENERATE CODE, 用KEIL打开

uint8_t temp[100]__attribute__((at(0xD0000000)));   
for(int i=0;i<100;i++){
     temp = i;
}
for(int i=0;i<100;i++){
     printf("%d  ", temp);
}
  • 到这里只是借助Cube完成了引脚配置,还需要SDRAM初始化操作和读写函数,可从官方例程里获取,路径:
    C:\Users\10617\STM32Cube\Repository\STM32Cube_FW_F4_V1.25.0\Drivers\BSP\STM32F429I-Discovery\XXX

/*****************************SDRAM使能函数******************************/
&#8203;
/**
   * @brief  对SDRAM芯片进行初始化配置
   * @param  None.
   * @retval None.
   */
static void USER_SDRAM_ENABLE(void)
{
     FMC_SDRAM_CommandTypeDef Command;
     
   __IO uint32_t tmpmrd =0;
   
   /* Step 1:  Configure a clock configuration enable command */
   Command.CommandMode             = FMC_SDRAM_CMD_CLK_ENABLE;
   Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK2;
   Command.AutoRefreshNumber       = 1;
   Command.ModeRegisterDefinition  = 0;
&#8203;
   /* Send the command */
   HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
&#8203;
   /* Step 2: Insert 100 us minimum delay */
   /* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
   HAL_Delay(1);
&#8203;
   /* Step 3: Configure a PALL (precharge all) command */
   Command.CommandMode             = FMC_SDRAM_CMD_PALL;
   Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK2;
   Command.AutoRefreshNumber       = 1;
   Command.ModeRegisterDefinition  = 0;
&#8203;
   /* Send the command */
   HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);  
   
   /* Step 4: Configure an Auto Refresh command */
   Command.CommandMode             = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
   Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK2;
   Command.AutoRefreshNumber       = 4;
   Command.ModeRegisterDefinition  = 0;
&#8203;
   /* Send the command */
   HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
   
   /* Step 5: Program the external memory mode register */
   tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2          |
                      SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL   |
                      SDRAM_MODEREG_CAS_LATENCY_3           |
                      SDRAM_MODEREG_OPERATING_MODE_STANDARD |
                      SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
   
   Command.CommandMode             = FMC_SDRAM_CMD_LOAD_MODE;
   Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK2;
   Command.AutoRefreshNumber       = 1;
   Command.ModeRegisterDefinition  = tmpmrd;
&#8203;
   /* Send the command */
   HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
   
   /* Step 6: Set the refresh rate counter */
   /* Set the device refresh rate */
   HAL_SDRAM_ProgramRefreshRate(&hsdram1, REFRESH_COUNT);
}
/*****************************使能函数结束******************************/
或者以我的野火STM32F429IGT6的版本SDRAM为8M,源码链接:
添加到工程Core路径下,然后在KEIL中初始化操作:
(注意这个SDRAM_InitSequence();不能加在HAL_SDRAM_MspInit()后面!!! 因为它这里还没FMC初始化完成!!!! 加在这里是没有用的!!!)
#include "bsp_sdram.h"MX_FMC_Init();SDRAM_InitSequence(); SDRAM_Test();

LTDC + DMA2D
务必在上面SDRAM配置成功后,再来搞这个!!!
但他给的源码还有点问题,运行处理没效果。
我提供的源码链接:
注意:
  • 我跟他的配置有点不一样,我的是:

    • Pixel Clock Plparity:Normal Input
    • DMA2D要开一下中断,等级可以不用很高。如果不开的话,有可能会传图时候卡住。
    • LCD-TFT时钟:25MHz

  • 层 1 = layer 0,层 2 = layer 1



FreeRTOS
后面要上TouchGFX,这里先加操作系统。
当FreeRTOS遇到FATFS+SDIO时,这里有挺多注意细节的!!!
针对初学者,使用STM32CubeMX配置FreeRTOS时,大部分参数默认即可
  • 使能freertos

  • 由于freertos工作时会调用systick,为了防止其他进程干扰系统,所以当使用RTOS时,强烈建议使用HAL时基源而不是Systick。HAL时基源可以从SYS下的Pinout选项卡更改。因此更改系统时基源,这里选TIM6

        改完之后,注意:中断处理程序调用RTOS函数,请确保它们的优先级比最高的系统调用中断优先级低(数字上高),例如FreeRTOS中的LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
  • 如使用了FreeRTOS,会要求强制使用DMA模板的Fatfs,所以打开DMA通道开中断,以及开SDIO中断是必须的,否则后面配置FATFS无法运行。
    使能SDIO中断,这里的中断优先级默认不是5的,而FreeRTOS要求优先级从5开始

  • 当配置完发现无法mout SD卡,可以尝试加大CLKDIV值

  • 修改Project Manager->Project->Linker Setting中的最小堆栈大小,太小就会无法挂载SD卡或者读写时失败,基本上默认值都是无法正常运行的

  • FreeRTOS基本都是使用默认值,需要增大MINIMAL_STACK_SIZE,默认值是128,使用默认值会造成f_mount直接卡死在内部,这里使用256

  • 生成代码,使用Keil打开。RTOS默认创建了一个defaultTask(),在freertos.c文件中
  • 由于SD卡初始化时有检测读写是否在task任务中,所以SD读写测试代码需要放到defaultTask()中

  • 由于任务调度启动后就不再往下执行,所以把之前的LTDC显示测试代码也可以放到task中,或往前挪一挪

  • 其他的无需改动,运行即可!
  • 发现它创建任务用的是osThreadNew,对xTaskCreate又进行了封装,省去了繁琐
  • 网上有人说要“在TIM6中断中,加入临界段保护(或进入中断保护)”,不知真假,没试



TouchGFX显示
虽然方便,但它是用C++开发的,所以不是特别友好...说白了就是看不懂,不知道怎么去改
  • 先在Cube里安装TouchGFX包

  • 这里要注意,FreeRTOS要选CMSIS V1版本!!!

  • 开启TouchGFX包,并配置如下

  • 生成工程,但先别用keil打开!!!
  • 到目录下安装TouchGFX Designer软件,它用来绘制界面的

C:\Users\10617\STM32Cube\Repository\Packs\STMicroelectronics\X-CUBE-TOUCHGFX\4.15.0\Utilities\PC_Software\TouchGFXDesigner\TouchGFX-4.14.0.msi
  • 安装完成后,回到Cube工程目录,会发现多了一个TouchGFX文件夹,打开其中的.touchgfx文件,绘制界面

  • 然后就可以打开keil,添加以下内容

#include "app_touchgfx.h"// 开启LCDLCD_DisplayOn();LCD_SetLayerVisible(1,DISABLE);LCD_SetLayerVisible(0,ENABLE);LCD_SetTransparency(1,0);LCD_SetTransparency(0,255);LCD_SelectLayer(0);// 显示TouchGFX内容MX_TouchGFX_Process();
  • 这里要注意!由于MicroLib不支持C++,所以需要在keil里取消勾选!!!

  • 但printf函数又需要MicroLib支持,所以需要添加以下函数,否则会开机无法运行!!!

#if 1 #pragma import(__use_no_semihosting)              //标准库需要的支持函数                  struct __FILE  {          int handle;  };  FILE __stdout;        //定义_sys_exit()以避免使用半主机模式     void _sys_exit(int x)  {          x = x;  }  void _ttywrch(int ch){        ch = ch;}//重定义fputc函数  int fputc(int ch, FILE *f) {               HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0XFF);        return ch; } #endif
  • 完成以上内容后,编译、烧录即可



LittleVGL
奈何不会C++,只能另谋出路,LittltVGL设计的界面似乎还挺好看的,而且用C编写,兼容C++,更新很活跃。EMWIN风格类似window XP ,littlevGL风格类似android 。移植很简单(并没有多简单)。
官方推荐方法学习路线:
  • 点击在线演示以查看LVGL的运行情况(3分钟)
  • 阅读文档的介绍页面(5分钟)
  • 熟悉Quick overview页面的基础知识(15分钟)
  • 设置模拟器(10分钟)
  • 尝试一些例子
  • 移植LVGL到一个板子。请参阅移植指南或准备使用项目
  • 阅读概述页,更好地了解库(2-3小时)
  • 查看小部件的文档,了解它们的特性和用法
  • 如果你有问题可以去论坛
  • 阅读贡献指南,了解如何帮助改进LVGL(15分钟)


1、 教程可交叉参考以下这几篇,取长补短吧:
2、在Cube里开一个MTM的DMA(或者不开它,直接用DMA2D)
3、生成工程,修改disp_flush函数
/********************** *  STATIC VARIABLES **********************/static __IO uint16_t * my_fb = (__IO uint16_t*) (0xD0000000);static DMA_HandleTypeDef  DmaHandle;static int32_t            x1_flush;//static int32_t            y1_flush;static int32_t            x2_flush;static int32_t            y2_fill;static int32_t            y_fill_act;static const lv_color_t * buf_to_flush;static lv_disp_t *our_disp = NULL;/********************* *      INCLUDES *********************/#include "lv_port_disp.h"#include "bsp_lcd.h"#include "dma2d.h"#include "stm32f4xx_hal_dma.h"#include "dma.h"static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){//    int32_t x;//    int32_t y;//    for(y = area->y1; y <= area->y2; y++) {//        for(x = area->x1; x <= area->x2; x++) {//            /* Put a pixel to the display. For example: *///            /* put_px(x, y, *color_p)*/////                                                LCD_FillRect_C(area->x1, area->y1, area->x2-area->x1, area->y2-area->y1, (uint32_t)color_p);////                                                LCD_DrawPixel(x, y, (uint32_t)color_p->full);//                                                LCD_FillRect_C(x, y, 1, 1, (uint32_t)color_p->full);//            color_p++;//        }//    }    int32_t x1 = area->x1;    int32_t x2 = area->x2;    int32_t y1 = area->y1;    int32_t y2 = area->y2;    /*Return if the area is out the screen*/    if(x2 < 0) return;    if(y2 < 0) return;    if(x1 > LV_HOR_RES_MAX - 1) return;    if(y1 > LV_VER_RES_MAX - 1) return;    /*Truncate the area to the screen*/    int32_t act_x1 = x1 < 0 ? 0 : x1;    int32_t act_y1 = y1 < 0 ? 0 : y1;    int32_t act_x2 = x2 > LV_HOR_RES_MAX - 1 ? LV_HOR_RES_MAX - 1 : x2;    int32_t act_y2 = y2 > LV_VER_RES_MAX - 1 ? LV_VER_RES_MAX - 1 : y2;    x1_flush = act_x1;    //                y1_flush = act_y1;    x2_flush = act_x2;    y2_fill = act_y2;    y_fill_act = act_y1;    buf_to_flush = color;    HAL_StatusTypeDef err;    uint32_t length = (x2_flush - x1_flush + 1);    #if LV_COLOR_DEPTH == 24 || LV_COLOR_DEPTH == 32    length *= 2; /* STM32 DMA uses 16-bit chunks so multiply by 2 for 32-bit color */    #endif    err = HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,(uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * LV_HOR_RES_MAX + x1_flush], length);    if(err != HAL_OK) {        printf("disp_flush %d\r\n",err);        while(1);        /*Halt on error*/    }    lv_disp_flush_ready(disp_drv);}
注意!!!
微雪这个函数有点问题,如果遇到显示不正确的时候,建议改成以下这个试试:
int32_t x1 = area->x1;int32_t x2 = area->x2;int32_t y1 = area->y1;int32_t y2 = area->y2;/*Return if the area is out the screen*/if(x2 < 0) return;if(y2 < 0) return;if(x1 > LV_HOR_RES_MAX - 1) return;if(y1 > LV_VER_RES_MAX - 1) return;/*Truncate the area to the screen*/int32_t act_x1 = x1 < 0 ? 0 : x1;int32_t act_y1 = y1 < 0 ? 0 : y1;int32_t act_x2 = x2 > LV_HOR_RES_MAX - 1 ? LV_HOR_RES_MAX - 1 : x2;int32_t act_y2 = y2 > LV_VER_RES_MAX - 1 ? LV_VER_RES_MAX - 1 : y2;for(int32_t y = act_y1; y <= act_y2; y++) {    for(int32_t x = act_x1; x <= act_x2; x++) {        /* Put a pixel to the display. For example: */        /* put_px(x, y, *color_p)*/        my_fb[y*LV_HOR_RES_MAX+x] = (uint32_t)color_p->full;        color_p++;    }}
4、按着上面教程,把littlvgl的显存地址改为SDRAM的
5、由于用作时基的TIM6的中断时间是100ms,所以我们可以新开一个定时器如TIM7,设置它的中断时间为1~5ms。但好像是需要手动启动定时器的:
HAL_TIM_Base_Start_IT(&htim7);
6、keil测试:
lv_init();lv_port_disp_init();// 开启LCDLCD_DisplayOn();LCD_SetLayerVisible(1,DISABLE);LCD_SetLayerVisible(0,ENABLE);LCD_SetTransparency(1,0);LCD_SetTransparency(0,255);LCD_SelectLayer(0);LCD_Clear(LCD_COLOR_BLUE);/*Create a Label on the currently active screen*/lv_obj_t * label1 =  lv_label_create(lv_scr_act(), NULL);/*Modify the Label's text*/lv_label_set_text(label1, "Hello world!");/* Align the Label to the center         * NULL means align on parent (which is the screen now)         * 0, 0 at the end means an x, y offset after alignment*/lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);#include "bsp_lcd.h"static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){  int32_t x;  int32_t y;    for(y = area->y1; y <= area->y2; y++) {      for(x = area->x1; x <= area->x2; x++) {          LCD_DrawPixel(x, y, (uint32_t)color_p->full);          color_p++;      }            }    lv_disp_flush_ready(disp_drv);}
当然,我的源码链接(只有显示部分,触摸目前没用到):
注意!!!:一定要先执行初始化lv_init(); lv_port_disp_init();,再做其他ui操作,不然会死活不显示出来!!!
附,我的一些理解:
  • display是buff区,screen是一整个界面,界面里可以放控件和窗口win,窗口里还能放控件,一个控件可能有多个part
  • 创建screen:lv_obj_t* screen1 = lv_obj_create(NULL, NULL);
  • 创建window:lv_obj_t* win = lv_win_create(screen1, NULL);
  • 创建label:lv_obj_t * label1 =  lv_label_create(win, NULL);
  • label中设置text:lv_label_set_text(label1, "Hello world!");`
  • 居中:lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);
  • screen切换:lv_scr_load(screen1);
  • 添加style:

static lv_style_t loading_style;lv_obj_t* loading_label = lv_label_create(lv_scr_act(), NULL);lv_label_set_text(loading_label, "Loading...");lv_obj_align(loading_label, NULL, LV_ALIGN_CENTER, 0, 0);lv_style_init(&loading_style);lv_style_set_text_color(&loading_style, LV_STATE_DEFAULT, LV_COLOR_BLUE);lv_style_set_text_font(&loading_style,LV_STATE_DEFAULT, &lv_font_montserrat_24);lv_obj_add_style(loading_label, LV_OBJ_PART_MAIN, &loading_style);显示图片C数组形式
  • 首先我们需要将文件加入到我们的工程树中
  • 然后在需要的地方声明一下就可以了,可以用下面两种方式:

extern const lv_img_t my_image_name; LV_IMG_DECLARE(my_image_name);
  • 我们再来看看转换出来的图片文件的一些信息,包括宽度,高度,大小等。就在我们的刚下载的文件最后就可以看到一个结构体,如下:

const lv_img_dsc_t WaveShare_LOGO = {  .header.always_zero = 0,  .header.w = 287,  .header.h = 81,  .data_size = 11728,  .header.cf = LV_IMG_CF_INDEXED_4BIT,  .data = WaveShare_LOGO_map,};
  • 使用示例:

// 先声明一下外部图片结构体LV_IMG_DECLARE(WaveShare_LOGO)// 创建一个图片lv_obj_t * img1 = lv_img_create(lv_scr_act(), NULL);// 将数组内容放入lv_img_set_src(img1, &WaveShare_LOGO);// 图片在屏幕居中lv_obj_align(img1, NULL, LV_ALIGN_CENTER, 0, -20);canvas画图// 声明一个buffstatic lv_color_t buffer[LV_CANVAS_BUF_SIZE_TRUE_COLOR(48, 48)];// 创建canvaslv_obj_t* canvas = lv_canvas_create(lv_scr_act(), NULL);// 关联canvas与bufflv_canvas_set_buffer(canvas, buffer, 48, 48, LV_IMG_CF_TRUE_COLOR);// 背景涂色lv_canvas_fill_bg(canvas, LV_COLOR_BLUE, LV_OPA_50);// 在画布上画点lv_color_t c0;c0.full = 0;uint32_t x;uint32_t y;for( y = 10; y < 30; y++) {    for( x = 5; x < 20; x++) {        // 这里的x,y都是相对父元素而言        lv_canvas_set_px(canvas, x, y, c0);    }}文件系统/*File system interface*/#define LV_USE_FS_IF        1#if LV_USE_FS_IF#  define LV_FS_IF_FATFS    '\0'     // ‘S’#  define LV_FS_IF_PC       '\0'#endif  /*LV_USE_FS_IF*/
  • 通过将'\0'更改为要用于该驱动器的字母来启用所需的接口。如'S'表示FATFS的SD卡
  • 调用lv_fs_if_init()来注册启用的接口
  • 使用lv_fs_fatfs.c中提供的函数完成操作
  • 初始化图像,需要以下回调:

    • open
    • close
    • read
    • seek
    • tell

  • 使用示例:

// 挂载SD卡retSD = f_mount(&SDFatFS, SDPath, 0);// 文件系统初始化lv_fs_if_init();// 创建一个图像lv_obj_t *icon = lv_img_create(lv_scr_act(), NULL);// SD卡文件绑定到图像lv_img_set_src(icon, "S:0.bin");// 居中显示lv_obj_align(icon, NULL, LV_ALIGN_CENTER, 0, 0);显示中文
注意keil工程必须是UTF8编码!
这时,串口输出中文可能是乱码。没关系,lvgl显示正常就行。或者用SwitchToGbk函数将utf8转成Unicode,这样串口就是正常的了。该函数可以到工程目录的User/SXF下找:
生成的myFont.c下,有个函数需要替换一下(读SD卡方式):
static uint8_t *__user_font_getdata(int offset, int size){//如字模保存在SPI FLASH, SPIFLASH_Read(__g_font_buf,offset,size);//如字模已加载到SDRAM,直接返回偏移地址即可如:return (uint8_t*)(sdram_fontddr+offset);    uint32_t br;    if( f_open(&SDFile, (const TCHAR*)"0:/myFont.bin", FA_READ) != FR_OK )    {        printf("myFont.bin open failed\r\n");    }    else    {        if( f_lseek(&SDFile, (FSIZE_t)offset) != FR_OK )        {            printf("myFont.bin lseek failed\r\n");        }        if( f_read(&SDFile, __g_font_buf, (UINT)size, (UINT*)&br) != FR_OK )        {            printf("myFont.bin lseek failed\r\n");        }        //    printf("offset:%d\t size:%d\t __g_font_buf:%s\r\n", offset, size, __g_font_buf);        f_close(&SDFile);    }    return __g_font_buf;}
调用示例:
static lv_style_t date_style;lv_style_init(&date_style);lv_obj_t* date_label2 = lv_label_create(lv_scr_act(), NULL);lv_label_set_text(date_label2, "123y呀");lv_style_set_text_color(&date_style, LV_STATE_DEFAULT, LV_COLOR_RED);lv_style_set_text_font(&date_style, LV_STATE_DEFAULT, &myFont);lv_obj_add_style(date_label2, LV_OBJ_PART_MAIN, &date_style);lv_obj_align(date_label2, NULL, LV_ALIGN_IN_TOP_LEFT, 10, 10);lv_obj_set_pos(date_label2, 10, 10);










待补充...






正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

0

主题

10

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
288
金钱
288
注册时间
2020-9-1
在线时间
36 小时
发表于 2020-11-10 13:27:10 | 显示全部楼层
楼主写得好详细,我顶
回复 支持 反对

使用道具 举报

0

主题

3

帖子

0

精华

新手上路

积分
42
金钱
42
注册时间
2019-6-3
在线时间
12 小时
发表于 2020-12-8 14:43:09 | 显示全部楼层
优化程序,看到这个,楼主厉害1
回复 支持 反对

使用道具 举报

0

主题

20

帖子

0

精华

初级会员

Rank: 2

积分
166
金钱
166
注册时间
2019-8-12
在线时间
46 小时
发表于 2021-1-15 14:26:11 | 显示全部楼层
摸摸大佬
回复 支持 反对

使用道具 举报

2

主题

70

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
366
金钱
366
注册时间
2019-1-16
在线时间
101 小时
发表于 2021-1-17 21:09:00 | 显示全部楼层
膜拜大佬   !!!!!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-10 10:20

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表