├── LvglFontTool_V0.4.zip ├── README.md ├── STM32CubeMX + HAL库.pdf ├── esp_http_client.7z └── 最新的总工程.7z /LvglFontTool_V0.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/STM32F429_CubeMX_LVGL_FreeRTOS/12c23863dc50c4c932da9fedc44bc62f21f4c928/LvglFontTool_V0.4.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STM32F429_STM32Cube_LVGL 2 | 基于野火F429开发板,用STM32Cube生成代码,全面详细的教程 3 | 4 | 最终示例是通过ESP32,在F429上实时显示天气 5 | 6 | --- 7 | 文章目录 8 | STM32CubeMX + HAL 9 | 前言 10 | 紧急避坑 11 | USART 12 | freertos+fatfs+sdio 13 | 一些说明 14 | Cube基本使用 15 | HAL库函数 16 | 中断回调函数 17 | 外设对应时钟 18 | 配置示例 19 | 小编有话说 20 | USART 21 | RTC 22 | SDIO + FATFS 23 | SDRAM 24 | LTDC + DMA2D 25 | FreeRTOS 26 | TouchGFX显示 27 | LittleVGL 28 | 显示图片 29 | C数组形式 30 | canvas画图 31 | 文件系统 32 | 显示中文 33 | 待补充... 34 | 35 | --- 36 | 37 | # STM32CubeMX + HAL 38 | 39 | ## 前言 40 | 41 | 我的CSDN博客:[小锋学长生活大爆炸](https://blog.csdn.net/sxf1061700625) 42 | 43 | ## 紧急避坑 44 | 45 | #### USART 46 | 47 | **问题1:** 打印正常,但是加入接收中断后,开始出bug,最后锁定接收中断挂掉了。 48 | **原因:** HAL库的串口接收发送函数有bug,就是收发同时进行的时候,会出现锁死的现象。 49 | **解决:** 需要注释掉 HAL_UART_Receive_IT 和 HAL_UART_Transmit_IT 中的 **__HAL_LOCK(huart)** 函数。或者不要在接收里面,每接收到一个字符就printf一下。 50 | 51 | **问题2:** 在接收中断中使用HAL_UART_Receive_IT()函数,会导致CR1的RXNEIE 置0,最后一直处于错误状态,无法进行接收。 52 | **解决:** 注释掉 HAL_UART_Receive_IT 中的 HAL_LOCK(huart) 函数 53 | 54 | 55 | 56 | #### freertos+fatfs+sdio 57 | 58 | **问题:**没有加freertos时候,sd卡读写正常;加上freertos时候,mout成功,但read等其他操作返回错误3 not ready 59 | 60 | **解决:** sdio和sddma的中断优先级要小于freertos的最小优先级 61 | 62 | ## 一些说明 63 | 64 | 使用`STM32CubeMX`代码生成工具,不用关注底层配置的细节,真舒服。 65 | 66 | 使用教程: 67 | 68 | [https://sxf1024.lanzoui.com/b09rf2dwj 密码:bgvi](https://sxf1024.lanzoui.com/b09rf2dwj) 69 | 70 | *虽然`Cube+HAL`很舒服,但新手不建议用。最好还是先去学一下标准库怎么用,有个大致概念后,再来学这一套。* 71 | 72 | 自动化的东西虽好,但一旦出了问题,解决起来也是挺头疼的。 73 | 74 | --- 75 | 76 | ### Cube基本使用 77 | 78 | 1. 新建工程 79 | 2. 选择芯片 80 | 3. **Pinout&Configuration**,选择`RCC(HSE:Crystal/Ceramic Resonator)`、`SYS(Debug:Serial Wiire)` 81 | 4. **Clock Configuration**,配置时钟树 82 | 83 | ![image-20201012091155867](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201012091155867.png) 84 | 85 | 5. **Project Manager**,配置工程输出项 86 | 87 | ![无标题](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/%E6%97%A0%E6%A0%87%E9%A2%98.png) 88 | 89 | 6. **Pinout&Configuration**,选择功能(若是选`GPIO`相关,可以直接在**Pinout view**选择;若是其他功能,可以在左边**Categories**打开,会自动配置引脚)、设置`Parameter Settings/NVIC`等 90 | 91 | ![cube](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/cube.png) 92 | 93 | 7. **GENERATE CODE**,生成工程,用KEIL打开编辑 94 | 95 | --- 96 | 97 | ### HAL库函数 98 | 99 | - 函数形式:均以`HAL_`开头 100 | - 寻找过程:在驱动文件`stm32f4xx_hal_XXX.c`或其`.h`文件中找函数定义,一般在靠后位置 101 | - 其他说明: 102 | - `HAL`库并没有把所有的操作都封装成凼数。 103 | - 对于底层的**寄存器操作**(如读取捕获/比较寄存器),还有修改外设的某个**配置参数**(如改变输入捕获的极性),`HAL`库会使用**宏定义**来实现。而且会用`__HAL_`作为这类宏定义的**前缀**。 104 | - **获取**某个参数,宏定义中一般会有`_GET`;而**设置**某个参数的,宏定义中就会有`_SET`。 105 | - 在开发过程中,如果遇到**寄存器**级别或者**更小范围**的操作时,可以到该外设的**头文件**中查找,一般都能找到相应的宏定义。 106 | - `HAL`库函数**第一个参数**一般都是**句柄**(一个包含了当前对象绝大部分状态的结构体),虽然增加了开销,但是用起来便捷了非常多。 107 | 108 | --- 109 | 110 | ### 中断回调函数 111 | 112 | - 函数形式:`HAL_XXX_XXXCallback()`。 113 | 114 | - 寻找过程:中断文件`stm32f4xx_it.c` - > 中断函数`XXX_IRQHandler(void)` -> HAL库中断函数`HAL_XXX_IRQHandler(GPIO_PIN_13)` -> 回调函数`HAL_XXX_XXXCallback()` 115 | 116 | --- 117 | 118 | ### 外设对应时钟 119 | 120 | 1. 随便进入一个**外设初始化函数**,如`MX_GPIO_Init()` 121 | 2. 随便进入一个**时钟使能函数**,如`__HAL_RCC_GPIOC_CLK_ENABLE()` 122 | 3. 随便进入一个**RCC宏定义**,如`RCC_AHB1ENR_GPIOCEN` 123 | 4. 或者直接进入`stm32f429xx.h`文件 124 | 5. 里面有所有**外设与时钟**对应关系,如`RCC_AHB1ENR_DMA1EN` 125 | 126 | ## 配置示例 127 | 128 | ### 小编有话说 129 | 130 | - 例子源码: 131 | 132 | [https://sxf1024.lanzoui.com/b09rf535a](https://sxf1024.lanzoui.com/b09rf535a) 密码:bf5q 133 | 134 | - 如果配置过程中,参数不知道怎么设置,可以去标准库例程(如野火、正点原子)中看对应的参数是什么 135 | 136 | - Cube软件只是帮你配置了底层,一些初始化代码还是需要自己手动加的,如SDRAM充电初始化、读写函数等 137 | 138 | - 以下内容都是基于**“野火F429IGT6挑战者V2开发板”**,其他板子按照原理图改改引脚都能用的 139 | 140 | --- 141 | 142 | ### USART 143 | 144 | 源码链接: 145 | 146 | [https://sxf1024.lanzoui.com/b09rf535a](https://sxf1024.lanzoui.com/b09rf535a) 密码:bf5q 147 | 148 | 详细教程网上挺多,配置也简单,只要勾选一下USARTx,再开一下中断就行。 149 | 150 | ![image-20201014160252528](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014160252528.png) 151 | 152 | 在Keil就比较要注意了。 153 | 154 | 由于每次接收完,程序内部自动把接收中断关了,所以每次要手动打开。 155 | 156 | 总的来说,加这几部分: 157 | 158 | - `main`函数中,`while`之前: 159 | 160 | ```c 161 | // 使能串口中断接收 162 | HAL_UART_Receive_IT(&huart1, (uint8_t*)&DataTemp_UART1, 1); 163 | ``` 164 | 165 | - 任意位置添加**printf重定向函数**: 166 | 167 | ```c 168 | #include "stdio.h" 169 | int fputc(int ch, FILE *f){ 170 | HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0XFF); 171 | return ch; 172 | } 173 | ``` 174 | 175 | - 任意位置添加**中断回调函数**: 176 | 177 | ```c 178 | #define UART1BuffLen 200 179 | extern uint8_t DataBuff_UART1[UART1BuffLen]; 180 | extern uint32_t DataTemp_UART1; 181 | extern uint16_t DataSTA_UART1; 182 | 183 | uint32_t DataTemp_UART1; 184 | uint8_t DataBuff_UART1[UART1BuffLen]; 185 | uint16_t DataSTA_UART1; 186 | void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 187 | { 188 | if(huart->Instance == USART1){ 189 | if(DataSTA_UART1 < UART1BuffLen){ 190 | if(DataTemp_UART1 == 0x0A && DataSTA_UART1>0 && DataBuff_UART1[DataSTA_UART1-1]==0X0D){ 191 | printf("USART: %s\r\n", DataBuff_UART1); 192 | DataSTA_UART1 = 0; 193 | } 194 | else{ 195 | if(DataSTA_UART1 == 0){ 196 | memset(DataBuff_UART1, 0, sizeof(DataBuff_UART1)); 197 | } 198 | DataBuff_UART1[DataSTA_UART1++] = DataTemp_UART1; 199 | } 200 | } 201 | // 使能串口中断接收 202 | HAL_UART_Receive_IT(&huart1, (uint8_t*)&DataTemp_UART1, 1); 203 | } 204 | } 205 | ``` 206 | 207 | --- 208 | 209 | ### RTC 210 | 211 | ![image-20201011214845208](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201011214845208.png) 212 | 213 | image-20201011214629363 214 | 215 | ![image-20201011214744110](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201011214744110.png) 216 | 217 | ![image-20201011214905788](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201011214905788.png) 218 | 219 | ```c 220 | RTC_DateTypeDef sDate; 221 | RTC_TimeTypeDef sTime; 222 | uint8_t second_tmp = 0; 223 | 224 | 225 | HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // 读取时间 226 | HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); // 读取日期 227 | if(second_tmp != sTime.Seconds) { // 读取秒 228 | second_tmp = sTime.Seconds; 229 | printf("20%d%d-%d%d-%d%d\r\n", 230 | sDate.Year/10%10, sDate.Year%10, 231 | sDate.Month/10%10, sDate.Month%10, 232 | sDate.Date/10%10, sDate.Date%10); 233 | printf("%d%d:%d%d:%d%d\r\n", 234 | sTime.Hours/10%10, sTime.Hours%10, 235 | sTime.Minutes/10%10, sTime.Minutes%10, 236 | sTime.Seconds/10%10, sTime.Seconds%10); 237 | } 238 | ``` 239 | 240 | --- 241 | 242 | ### SDIO + FATFS 243 | 244 | 1. 选择SDIO功能,**Pinout&Clock Configuration**,`Connectivity -> SDIO -> Mode: SD 4bit Wide bus -> 勾选NVIC` 245 | 246 | ![image-20201012093115874](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201012093115874.png) 247 | 248 | 2. 配置SDIO时钟,**Clock Configuration**,SDIO模块输入要求为**48MHz**,系统提示可以自动设置时钟问题,选择`Yes`。SDIO时钟分频系数`CLKDIV`,计算公式为`SDIO_CK=48MHz/(CLKDIV+2)`也可手动修改时钟配置。如果遇到读写问题,可以试着调整到**24MHz**。 249 | 250 | image-20201012093007042 251 | 252 | 3. **启用文件系统中间件**,**Pinout&Clock Configuration**,`Middleware -> FATFS`,模式选择SD卡,配置文件系统: 253 | 254 | 如果要支持中文文件名,则配置`CODE_PAGE`为`Simplified Chinese` 255 | 256 | 如果要支持长文件名,则要使能`USE_LEN` 257 | 258 | ![image-20201012093606060](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201012093606060.png) 259 | 260 | 4. 继续上面界面,**Advanced Settings**勾选`Use dma template` 261 | 262 | ![image-20201012195528074](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201012195528074.png) 263 | 264 | 5. 设置DMA传输,**Pinout&Clock Configuration**,`Connectivity -> SDIO-> DMA Settings` 265 | 266 | ![image-20201012094328572](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201012094328572.png) 267 | 268 | 6. 配置NVIC,**Pinout&Clock Configuration**,`System Core -> NVIC`。 269 | 270 | 注意,SDIO中断优先级**必须高于**DMA2 stream3和DMA2 stream6的中断优先级 271 | 272 | **紧急避坑!!!如果没有用freertos,那中断优先级设置没啥关系。但如果用了freertos,那SDIO的优先级必须要注意跟freertos区分开来,不能高过他!不然就是mout正常,read等其他操作都返回错误3 not ready。**其实当你开启freertos,然后点击NVIC时候,cube会提醒你,要注意函数的中断优先级和freertos优先级的关系。(如果中断处理程序调用RTOS函数,请确保其抢占优先级低于最高的SysCall中断优先级。如FreeRTOS中的“LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY”) 273 | 274 | ![image-20201026111711326](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201026111711326.png) 275 | 276 | ![image-20201026111909569](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201026111909569.png) 277 | 278 | ![image-20201026102608763](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201026102608763.png) 279 | 280 | 7. 堆栈设置,**Progect Manager -> Project -> Linker Settings**,加大堆栈大小(注意:由于刚才设置长文件名动态缓存存储在堆中,故需要增大堆大小,如果不修改则程序运行时堆会生成溢出,程序进入硬件错误中断(HardFault),死循环)。 281 | 282 | ![image-20201012094934104](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201012094934104.png) 283 | 284 | 8. 注意这里要找一个闲置的GPIO用作SD卡的检测脚,并拉低。不然open时候可能会一直失败 285 | 286 | ![image-20201025194719408](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201025194719408.png) 287 | 288 | ![image-20201025194733315](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201025194733315.png) 289 | 290 | 9. 生成工程,**GENERATE CODE** ,用KEIL打开 291 | 292 | ![image-20201012095539078](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201012095539078.png) 293 | 294 | 10. 注意,如果是野火的F429开发板,还需要禁用WIFI引脚才行!!! 295 | 296 | ```c 297 | static void BL8782_PDN_INIT(void) 298 | { 299 | /*定义一个GPIO_InitTypeDef类型的结构体*/ 300 | GPIO_InitTypeDef GPIO_InitStructure; 301 | 302 | RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOB, ENABLE); 303 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; 304 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; 305 | GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 306 | GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; 307 | GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 308 | GPIO_Init(GPIOB, &GPIO_InitStructure); 309 | 310 | GPIO_ResetBits(GPIOB,GPIO_Pin_13); //禁用WiFi模块 311 | } 312 | ``` 313 | 314 | 11. 测试一下 315 | 316 | ```c 317 | UINT bw; 318 | retSD = f_mount(&SDFatFS, SDPath, 0); 319 | if(retSD != FR_OK) { 320 | printf("Mount Error :%d\r\n", retSD); 321 | } 322 | retSD = f_open(&SDFile, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE); 323 | if(retSD != FR_OK){ 324 | printf("Open Error :%d\r\n", retSD); 325 | } 326 | retSD = f_write(&SDFile, "abcde", 5, &bw); 327 | if(retSD != FR_OK){ 328 | printf("Write Error :%d\r\n", retSD); 329 | } 330 | f_close(&SDFile); 331 | retSD = f_open(&SDFile, "0:/test.txt", FA_READ); 332 | char buff[10] = {0}; 333 | retSD = f_read(&SDFile, buff, 5, &bw); 334 | if(retSD == FR_OK){ 335 | printf("%s\r\n", buff); 336 | } 337 | f_close(&SDFile); 338 | ``` 339 | 340 | --- 341 | 342 | ### SDRAM 343 | 344 | 1. 开启FMC功能,**Pinout&Configuration** ,`Connectivity -> FMC -> SDRAM2` 345 | 346 | **SDRAM1**的起始地址为**0XC0000000**,**SDRAM2**的起始地址为**0XD0000000**。 347 | 348 | 一般SDRAM都含有**4**个bank。 349 | 350 | **Configuration**中的参数可从SDRAM的**数据手册**上找到。 351 | 352 | ![image-20201014121059733](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014121059733.png) 353 | 354 | 各个选项的配置(只做解释,不对应上图): 355 | 356 | - `Clock and chip enable`:**FMC_SDCKE0** 和**FMC_SDCLK0**对应的存储区域1 的地址范围是**0xC000 0000-0xCFFF FFFF**;而**FMC_SDCKE1** 和**FMC_SDCLK1** 对应的存储区域2 的地址范围是**0xD000 0000- 0xDFFF FFFF** 357 | 358 | - `Bank`由硬件连接决定需要选择**SDRAM bank 2** 359 | - `Column bit number`表示列数,**8位** 360 | - `Row bit number`表示行数,**12位** 361 | - `CAS latency`表示CAS潜伏期,即上面说的CL,该配置需要与之后的SDRAM模式寄存器的配置相同,这里先配置为**2 memory clock cycles**(对于SDRAM时钟超过133MHz的,则需要配置为3 memory clock cycles) 362 | - `Write protection` 表示写保护,一般配置为**Disabled** 363 | - `SDRAM common clock`为SDRAM 时钟配置,可选HCLK的2分频\3分频\不使能SDCLK时钟。前面主频配置为216MHz,SDRAM common clock设置为**2分频**,那SDCLK时钟为108MHz,每个时钟周期为9.25ns 364 | - `SDRAM common burst read` 表示突发读,这里选择使能 365 | - `SDRAM common read pipe delay` 表示CAS潜伏期后延迟多少个时钟在进行读数据,这里选择**0 HCLK clock cycle** 366 | - `Load mode register to active delay` 加载模式寄存器命令和激活或刷新命令之间的延迟,按存储器时钟周期计 367 | 368 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/141338jvprpqplpi64lqj8.png) 369 | 370 | - `Exit self-refresh delay `从发出自刷新命令到发出激活命令之间的延迟,按存储器时钟周期数计查数据手册知道其最小值为70ns,由于我们每个时钟周期为9.25ns,所以设为**8** (70÷9.25,向上取整) 371 | 372 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/141338o1ocj10qicgqdjsg.png) 373 | 374 | - `SDRAM common row cycle delay`刷新命令和激活命令之间的延迟,以及两个相邻刷新命令之间的延迟, 以存储器时钟周期数表示 375 | 376 | 查数据手册知道其最小值为63ns,由于我们每个时钟周期为9.25ns,所以设为**7** (63÷9.25,向上取整) 377 | 378 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/141338m7g7k9u8tyljv66r.png) 379 | 380 | - `Write recovery time`写命令和预充电命令之间的延迟,按存储器时钟周期数计 381 | 382 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/141338xvouu4u33mk4vl0a.png) 383 | 384 | - `SDRAM common row precharge delay `预充电命令与其它命令之间的延迟,按存储器时钟周期数计 385 | 386 | 查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以设为**2** (15÷9.25,向上取整) 387 | 388 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/141338c5yokzquobourcrk.png) 389 | 390 | - `Row to column delay`激活命令与读/写命令之间的延迟,按存储器时钟周期数计 391 | 392 | 查数据手册知道其最小值为15ns,由于我们每个时钟周期为9.25ns,所以这里本应该设为**2** (15÷9.25,向上取整) 393 | 394 | 但要注意,时序必须满足以下式子: 395 | 396 | TWR ≥ TRAS - TRCD 397 | 398 | TWR ≥ TRC - TRCD - TRP 399 | 400 | 其中:TWR = **Write recovery** **time** = 2 401 | 402 | TRAS = **Self refresh time** = 5 403 | 404 | TRC = **SDRAM common row cycle delay** = 7 405 | 406 | TRP = **SDRAM common row precharge delay** = 2 407 | 408 | TRCD = **Row to column delay** 409 | 410 | 所以这里**Row to column delay**应该取3 411 | 412 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/141339pa631kk6zbz0wh6m.png) 413 | 414 | 2. 生成代码,**GENERATE CODE**, 用KEIL打开 415 | 416 | ```c 417 | uint8_t temp[100]__attribute__((at(0xD0000000))); 418 | for(int i=0;i<100;i++){ 419 | temp[i] = i; 420 | } 421 | for(int i=0;i<100;i++){ 422 | printf("%d ", temp[i]); 423 | } 424 | ``` 425 | 426 | 3. 到这里只是借助Cube完成了引脚配置,还需要SDRAM初始化操作和读写函数,可从官方例程里获取,路径: 427 | 428 | C:\Users\10617\STM32Cube\Repository\STM32Cube_FW_F4_V1.25.0\Drivers\BSP\STM32F429I-Discovery\XXX 429 | 430 | ```c 431 | /*****************************SDRAM使能函数******************************/ 432 | 433 | /** 434 | * @brief 对SDRAM芯片进行初始化配置 435 | * @param None. 436 | * @retval None. 437 | */ 438 | static void USER_SDRAM_ENABLE(void) 439 | { 440 | FMC_SDRAM_CommandTypeDef Command; 441 | 442 | __IO uint32_t tmpmrd =0; 443 | 444 | /* Step 1: Configure a clock configuration enable command */ 445 | Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; 446 | Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; 447 | Command.AutoRefreshNumber = 1; 448 | Command.ModeRegisterDefinition = 0; 449 | 450 | /* Send the command */ 451 | HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT); 452 | 453 | /* Step 2: Insert 100 us minimum delay */ 454 | /* Inserted delay is equal to 1 ms due to systick time base unit (ms) */ 455 | HAL_Delay(1); 456 | 457 | /* Step 3: Configure a PALL (precharge all) command */ 458 | Command.CommandMode = FMC_SDRAM_CMD_PALL; 459 | Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; 460 | Command.AutoRefreshNumber = 1; 461 | Command.ModeRegisterDefinition = 0; 462 | 463 | /* Send the command */ 464 | HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT); 465 | 466 | /* Step 4: Configure an Auto Refresh command */ 467 | Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; 468 | Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; 469 | Command.AutoRefreshNumber = 4; 470 | Command.ModeRegisterDefinition = 0; 471 | 472 | /* Send the command */ 473 | HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT); 474 | 475 | /* Step 5: Program the external memory mode register */ 476 | tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 | 477 | SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | 478 | SDRAM_MODEREG_CAS_LATENCY_3 | 479 | SDRAM_MODEREG_OPERATING_MODE_STANDARD | 480 | SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; 481 | 482 | Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE; 483 | Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; 484 | Command.AutoRefreshNumber = 1; 485 | Command.ModeRegisterDefinition = tmpmrd; 486 | 487 | /* Send the command */ 488 | HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT); 489 | 490 | /* Step 6: Set the refresh rate counter */ 491 | /* Set the device refresh rate */ 492 | HAL_SDRAM_ProgramRefreshRate(&hsdram1, REFRESH_COUNT); 493 | } 494 | /*****************************使能函数结束******************************/ 495 | ``` 496 | 497 | 或者以我的野火**STM32F429IGT6**的版本SDRAM为**8M**,源码链接: 498 | 499 | [https://sxf1024.lanzoui.com/b09rf535a](https://sxf1024.lanzoui.com/b09rf535a) 密码:bf5q 500 | 501 | 添加到工程`Core`路径下,然后在KEIL中初始化操作: 502 | 503 | (注意这个`SDRAM_InitSequence();`**不能**加在`HAL_SDRAM_MspInit()`后面!!! 因为它这里还没FMC初始化完成!!!! 加在这里是**没有用的**!!!) 504 | 505 | ```c 506 | #include "bsp_sdram.h" 507 | MX_FMC_Init(); 508 | SDRAM_InitSequence(); 509 | SDRAM_Test(); 510 | ``` 511 | 512 | --- 513 | 514 | ### LTDC + DMA2D 515 | 516 | **务必在上面SDRAM配置成功后,再来搞这个!!!** 517 | 518 | 详细教程看这个:https://zzttzz.gitee.io/blog/posts/7109b92c 519 | 520 | 但他给的源码还**有点问题**,运行处理没效果。 521 | 522 | 我提供的源码链接: 523 | 524 | [https://sxf1024.lanzoui.com/b09rf535a](https://sxf1024.lanzoui.com/b09rf535a) 密码:bf5q 525 | 526 | **注意:** 527 | 528 | - 我跟他的配置有点不一样,我的是: 529 | - Pixel Clock Plparity:Normal Input 530 | - DMA2D要开一下中断,等级可以不用很高。如果不开的话,有可能会传图时候卡住。 531 | - LCD-TFT时钟:25MHz 532 | 533 | - 层 1 = layer 0,层 2 = layer 1 534 | 535 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/20170821102811043) 536 | 537 | img 538 | 539 | --- 540 | 541 | ### FreeRTOS 542 | 543 | 后面要上TouchGFX,这里先加操作系统。 544 | 545 | **当FreeRTOS遇到FATFS+SDIO时,这里有挺多注意细节的!!!** 546 | 547 | 针对初学者,使用STM32CubeMX配置FreeRTOS时,大部分参数默认即可 548 | 549 | 1. 使能freertos 550 | 551 | ![image-20201014165023823](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014165023823.png) 552 | 553 | ![备注:1、](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/20191129141331476.png) 554 | 555 | 2. 由于freertos工作时会调用systick,为了防止其他进程干扰系统,所以当使用RTOS时,强烈建议使用HAL时基源而不是Systick。HAL时基源可以从SYS下的Pinout选项卡更改。因此更改系统时基源,这里选TIM6 556 | 557 | ![image-20201014165408107](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014165408107.png) 558 | 559 | ​ 改完之后,注意:中断处理程序调用RTOS函数,请确保它们的优先级比最高的系统调用中断优先级低(数字上高),例如FreeRTOS中的`LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY` 560 | 561 | 3. 如使用了FreeRTOS,会要求强制使用**DMA模板**的Fatfs,所以**打开DMA通道**,**开中断**,以及**开SDIO中断**是必须的,否则后面配置FATFS无法运行。 562 | 563 | 使能SDIO中断,这里的中断优先级默认不是`5`的,而FreeRTOS要求优先级从`5`开始 564 | 565 | ![image-20201014203641765](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014203641765.png) 566 | 567 | 4. 当配置完发现无法mout SD卡,可以尝试加大CLKDIV值 568 | 569 | ![image-20201014203506378](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014203506378.png) 570 | 571 | 5. 修改`Project Manager->Project->Linker Setting`中的**最小堆栈大小**,太小就会无法挂载SD卡或者读写时失败,基本上默认值都是无法正常运行的 572 | 573 | ![image-20201014203746258](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014203746258.png) 574 | 575 | 6. FreeRTOS基本都是使用默认值,需要增大`MINIMAL_STACK_SIZE`,默认值是128,使用默认值会造成f_mount直接卡死在内部,这里使用`256` 576 | 577 | ![image-20201014203856605](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014203856605.png) 578 | 579 | 7. 生成代码,使用Keil打开。RTOS默认创建了一个`defaultTask()`,在`freertos.c`文件中 580 | 581 | 8. 由于SD卡初始化时有检测**读写是否在task任务**中,所以SD读写测试代码需要放到`defaultTask()`中 582 | 583 | ![image-20201014204217089](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014204217089.png) 584 | 585 | 9. 由于任务调度启动后就不再往下执行,所以把之前的LTDC显示测试代码也可以放到task中,或往前挪一挪 586 | 587 | ![image-20201014204526297](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201014204526297.png) 588 | 589 | 10. 其他的无需改动,运行即可! 590 | 11. 发现它创建任务用的是`osThreadNew`,对`xTaskCreate`又进行了封装,省去了繁琐 591 | 592 | 12. 网上有人说要“**在TIM6中断中,加入临界段保护(或进入中断保护)**”,不知真假,没试 593 | 594 | --- 595 | 596 | ### TouchGFX显示 597 | 598 | 虽然方便,但它是用C++开发的,所以不是特别友好...说白了就是看不懂,不知道怎么去改 599 | 600 | 1. 先在`Cube`里安装`TouchGFX`包 601 | 602 | ![image-20201015090331929](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201015090331929.png) 603 | 604 | ![image-20201015090406926](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201015090406926.png) 605 | 606 | 2. 这里要注意,`FreeRTOS`要选`CMSIS V1`版本!!! 607 | 608 | ![image-20201015090537967](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201015090537967.png) 609 | 610 | 3. 开启`TouchGFX`包,并配置如下 611 | 612 | ![image-20201015090522472](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201015090522472.png) 613 | 614 | 3. 生成工程,但先别用keil打开!!! 615 | 4. 到目录下安装TouchGFX Designer软件,它用来绘制界面的 616 | 617 | ``` 618 | C:\Users\10617\STM32Cube\Repository\Packs\STMicroelectronics\X-CUBE-TOUCHGFX\4.15.0\Utilities\PC_Software\TouchGFXDesigner\TouchGFX-4.14.0.msi 619 | ``` 620 | 621 | 5. 安装完成后,回到Cube工程目录,会发现多了一个`TouchGFX`文件夹,打开其中的`.touchgfx`文件,绘制界面 622 | 623 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/20190723101707319.png) 624 | 625 | ![在这里插入图片描述](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/36668c8a6cc4cd0aae4872f3ee2617af.png) 626 | 627 | ![在这里插入图片描述](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/928daaa0211ca13528e4d391ffd51c2e.png) 628 | 629 | 6. 然后就可以打开keil,添加以下内容 630 | 631 | ```c 632 | #include "app_touchgfx.h" 633 | 634 | // 开启LCD 635 | LCD_DisplayOn(); 636 | LCD_SetLayerVisible(1,DISABLE); 637 | LCD_SetLayerVisible(0,ENABLE); 638 | LCD_SetTransparency(1,0); 639 | LCD_SetTransparency(0,255); 640 | LCD_SelectLayer(0); 641 | // 显示TouchGFX内容 642 | MX_TouchGFX_Process(); 643 | ``` 644 | 645 | 7. 这里要注意!由于`MicroLib`**不支持**`C++`,所以需要在keil里**取消勾选**!!! 646 | 647 | ![image-20201015084256974](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201015084256974.png) 648 | 649 | 8. 但`printf`函数又**需要**`MicroLib`支持,所以需要**添加**以下函数,**否则会开机无法运行**!!! 650 | 651 | ```c 652 | #if 1 653 | #pragma import(__use_no_semihosting) 654 | //标准库需要的支持函数 655 | struct __FILE 656 | { 657 | int handle; 658 | }; 659 | 660 | FILE __stdout; 661 | //定义_sys_exit()以避免使用半主机模式 662 | void _sys_exit(int x) 663 | { 664 | x = x; 665 | } 666 | void _ttywrch(int ch) 667 | { 668 | ch = ch; 669 | } 670 | //重定义fputc函数 671 | int fputc(int ch, FILE *f) 672 | { 673 | HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0XFF); 674 | return ch; 675 | } 676 | #endif 677 | ``` 678 | 679 | 9. 完成以上内容后,编译、烧录即可 680 | 681 | --- 682 | 683 | ### LittleVGL 684 | 685 | 奈何不会C++,只能另谋出路,LittltVGL设计的界面似乎还挺好看的,而且用C编写,兼容C++,更新很活跃。EMWIN风格类似window XP ,littlevGL风格类似android 。移植很简单(并没有多简单)。 686 | 687 | 官网github:[https://github.com/lvgl/lvgl](https://github.com/lvgl/lvgl) 688 | 689 | 官方文档:[https://docs.lvgl.io/latest/en/html](https://docs.lvgl.io/latest/en/html/) 690 | 691 | 官方推荐方法学习路线: 692 | 693 | - 点击在线演示以查看LVGL的运行情况(3分钟) 694 | - 阅读文档的介绍页面(5分钟) 695 | - 熟悉Quick overview页面的基础知识(15分钟) 696 | - 设置模拟器(10分钟) 697 | - 尝试一些例子 698 | - 移植LVGL到一个板子。请参阅移植指南或准备使用项目 699 | - 阅读概述页,更好地了解库(2-3小时) 700 | - 查看小部件的文档,了解它们的特性和用法 701 | - 如果你有问题可以去论坛 702 | - 阅读贡献指南,了解如何帮助改进LVGL(15分钟) 703 | 704 | 705 | 706 | 1、 教程可交叉参考以下这几篇,取长补短吧: 707 | 708 | - csdn移植教程:https://blog.csdn.net/t01051/article/details/108748462 709 | - 微雪移植教程:https://www.waveshare.net/study/article-964-1.html 710 | - No space解决方案:https://blog.csdn.net/qq_36075612/article/details/107671669 711 | - csdn移植教程:https://blog.csdn.net/zcy_cyril/article/details/107457371 712 | - 放SRAM/SDRAM:https://blog.csdn.net/qq_41543888/article/details/106532577 713 | 714 | 2、在Cube里开一个MTM的DMA(或者不开它,直接用DMA2D) 715 | 716 | ![image-20201015143713645](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201015143713645.png) 717 | 718 | 3、生成工程,修改`disp_flush`函数 719 | 720 | ```c 721 | /********************** 722 | * STATIC VARIABLES 723 | **********************/ 724 | static __IO uint16_t * my_fb = (__IO uint16_t*) (0xD0000000); 725 | 726 | static DMA_HandleTypeDef DmaHandle; 727 | static int32_t x1_flush; 728 | //static int32_t y1_flush; 729 | static int32_t x2_flush; 730 | static int32_t y2_fill; 731 | static int32_t y_fill_act; 732 | static const lv_color_t * buf_to_flush; 733 | static lv_disp_t *our_disp = NULL; 734 | 735 | /********************* 736 | * INCLUDES 737 | *********************/ 738 | #include "lv_port_disp.h" 739 | #include "bsp_lcd.h" 740 | #include "dma2d.h" 741 | #include "stm32f4xx_hal_dma.h" 742 | #include "dma.h" 743 | 744 | static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 745 | { 746 | // int32_t x; 747 | // int32_t y; 748 | // for(y = area->y1; y <= area->y2; y++) { 749 | // for(x = area->x1; x <= area->x2; x++) { 750 | // /* Put a pixel to the display. For example: */ 751 | // /* put_px(x, y, *color_p)*/ 752 | //// LCD_FillRect_C(area->x1, area->y1, area->x2-area->x1, area->y2-area->y1, (uint32_t)color_p); 753 | //// LCD_DrawPixel(x, y, (uint32_t)color_p->full); 754 | // LCD_FillRect_C(x, y, 1, 1, (uint32_t)color_p->full); 755 | // color_p++; 756 | // } 757 | // } 758 | 759 | int32_t x1 = area->x1; 760 | int32_t x2 = area->x2; 761 | int32_t y1 = area->y1; 762 | int32_t y2 = area->y2; 763 | /*Return if the area is out the screen*/ 764 | if(x2 < 0) return; 765 | if(y2 < 0) return; 766 | if(x1 > LV_HOR_RES_MAX - 1) return; 767 | if(y1 > LV_VER_RES_MAX - 1) return; 768 | /*Truncate the area to the screen*/ 769 | int32_t act_x1 = x1 < 0 ? 0 : x1; 770 | int32_t act_y1 = y1 < 0 ? 0 : y1; 771 | int32_t act_x2 = x2 > LV_HOR_RES_MAX - 1 ? LV_HOR_RES_MAX - 1 : x2; 772 | int32_t act_y2 = y2 > LV_VER_RES_MAX - 1 ? LV_VER_RES_MAX - 1 : y2; 773 | x1_flush = act_x1; 774 | // y1_flush = act_y1; 775 | x2_flush = act_x2; 776 | y2_fill = act_y2; 777 | y_fill_act = act_y1; 778 | buf_to_flush = color; 779 | HAL_StatusTypeDef err; 780 | uint32_t length = (x2_flush - x1_flush + 1); 781 | #if LV_COLOR_DEPTH == 24 || LV_COLOR_DEPTH == 32 782 | length *= 2; /* STM32 DMA uses 16-bit chunks so multiply by 2 for 32-bit color */ 783 | #endif 784 | 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); 785 | if(err != HAL_OK) { 786 | printf("disp_flush %d\r\n",err); 787 | while(1); /*Halt on error*/ 788 | } 789 | 790 | lv_disp_flush_ready(disp_drv); 791 | } 792 | ``` 793 | 794 | **注意!!!** 795 | 796 | 微雪这个函数**有点问题**,如果遇到显示**不正确**的时候,建议改成以下这个试试: 797 | 798 | ```c 799 | int32_t x1 = area->x1; 800 | int32_t x2 = area->x2; 801 | int32_t y1 = area->y1; 802 | int32_t y2 = area->y2; 803 | /*Return if the area is out the screen*/ 804 | if(x2 < 0) return; 805 | if(y2 < 0) return; 806 | if(x1 > LV_HOR_RES_MAX - 1) return; 807 | if(y1 > LV_VER_RES_MAX - 1) return; 808 | /*Truncate the area to the screen*/ 809 | int32_t act_x1 = x1 < 0 ? 0 : x1; 810 | int32_t act_y1 = y1 < 0 ? 0 : y1; 811 | int32_t act_x2 = x2 > LV_HOR_RES_MAX - 1 ? LV_HOR_RES_MAX - 1 : x2; 812 | int32_t act_y2 = y2 > LV_VER_RES_MAX - 1 ? LV_VER_RES_MAX - 1 : y2; 813 | 814 | 815 | for(int32_t y = act_y1; y <= act_y2; y++) { 816 | for(int32_t x = act_x1; x <= act_x2; x++) { 817 | /* Put a pixel to the display. For example: */ 818 | /* put_px(x, y, *color_p)*/ 819 | my_fb[y*LV_HOR_RES_MAX+x] = (uint32_t)color_p->full; 820 | color_p++; 821 | } 822 | } 823 | ``` 824 | 825 | 4、按着上面教程,把littlvgl的显存地址改为SDRAM的 826 | 827 | 5、由于用作时基的TIM6的中断时间是`100ms`,所以我们可以新开一个定时器如TIM7,设置它的中断时间为`1~5ms`。但好像是需要手动启动定时器的: 828 | 829 | ```c 830 | HAL_TIM_Base_Start_IT(&htim7); 831 | ``` 832 | 833 | 6、keil测试: 834 | 835 | ```c 836 | lv_init(); 837 | lv_port_disp_init(); 838 | 839 | // 开启LCD 840 | LCD_DisplayOn(); 841 | LCD_SetLayerVisible(1,DISABLE); 842 | LCD_SetLayerVisible(0,ENABLE); 843 | LCD_SetTransparency(1,0); 844 | LCD_SetTransparency(0,255); 845 | LCD_SelectLayer(0); 846 | LCD_Clear(LCD_COLOR_BLUE); 847 | 848 | /*Create a Label on the currently active screen*/ 849 | lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL); 850 | /*Modify the Label's text*/ 851 | lv_label_set_text(label1, "Hello world!"); 852 | /* Align the Label to the center 853 | * NULL means align on parent (which is the screen now) 854 | * 0, 0 at the end means an x, y offset after alignment*/ 855 | lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0); 856 | 857 | #include "bsp_lcd.h" 858 | static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 859 | { 860 | int32_t x; 861 | int32_t y; 862 | for(y = area->y1; y <= area->y2; y++) { 863 | for(x = area->x1; x <= area->x2; x++) { 864 | LCD_DrawPixel(x, y, (uint32_t)color_p->full); 865 | color_p++; 866 | } 867 | } 868 | lv_disp_flush_ready(disp_drv); 869 | } 870 | ``` 871 | 872 | 当然,我的源码链接(只有显示部分,触摸目前没用到): 873 | 874 | [https://sxf1024.lanzoui.com/b09rf535a](https://sxf1024.lanzoui.com/b09rf535a) 密码:bf5q 875 | 876 | **注意!!!**: 877 | 一定要**先执行**初始化`lv_init(); lv_port_disp_init();`,再做其他ui操作,不然会死活不显示出来!!! 878 | 879 | 附,我的一些理解: 880 | 881 | - display是buff区,screen是一整个界面,界面里可以放控件和窗口win,窗口里还能放控件,一个控件可能有多个part 882 | - 创建`screen`:`lv_obj_t* screen1 = lv_obj_create(NULL, NULL);` 883 | - 创建`window`:`lv_obj_t* win = lv_win_create(screen1, NULL);` 884 | - 创建`label`:`lv_obj_t * label1 = lv_label_create(win, NULL);` 885 | - `label`中设置text`:`lv_label_set_text(label1, "Hello world!");` 886 | - 居中:`lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);` 887 | - `screen`切换:`lv_scr_load(screen1);` 888 | - 添加`style`: 889 | 890 | ```c 891 | static lv_style_t loading_style; 892 | lv_obj_t* loading_label = lv_label_create(lv_scr_act(), NULL); 893 | lv_label_set_text(loading_label, "Loading..."); 894 | lv_obj_align(loading_label, NULL, LV_ALIGN_CENTER, 0, 0); 895 | lv_style_init(&loading_style); 896 | lv_style_set_text_color(&loading_style, LV_STATE_DEFAULT, LV_COLOR_BLUE); 897 | lv_style_set_text_font(&loading_style,LV_STATE_DEFAULT, &lv_font_montserrat_24); 898 | lv_obj_add_style(loading_label, LV_OBJ_PART_MAIN, &loading_style); 899 | ``` 900 | 901 | #### 显示图片 902 | 903 | ##### C数组形式 904 | 905 | 图片转C文件链接:https://littlevgl.com/image-to-c-array 906 | 907 | ![img](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/112926lqax24r2s926xzu9.png) 908 | 909 | - 首先我们需要将文件加入到我们的工程树中 910 | - 然后在需要的地方声明一下就可以了,可以用下面两种方式: 911 | 912 | ```c 913 | extern const lv_img_t my_image_name; 914 | LV_IMG_DECLARE(my_image_name); 915 | ``` 916 | 917 | - 我们再来看看转换出来的图片文件的一些信息,包括宽度,高度,大小等。就在我们的刚下载的文件最后就可以看到一个结构体,如下: 918 | 919 | ```c 920 | const lv_img_dsc_t WaveShare_LOGO = { 921 | .header.always_zero = 0, 922 | .header.w = 287, 923 | .header.h = 81, 924 | .data_size = 11728, 925 | .header.cf = LV_IMG_CF_INDEXED_4BIT, 926 | .data = WaveShare_LOGO_map, 927 | }; 928 | ``` 929 | 930 | - 使用示例: 931 | 932 | ```c 933 | // 先声明一下外部图片结构体 934 | LV_IMG_DECLARE(WaveShare_LOGO) 935 | // 创建一个图片 936 | lv_obj_t * img1 = lv_img_create(lv_scr_act(), NULL); 937 | // 将数组内容放入 938 | lv_img_set_src(img1, &WaveShare_LOGO); 939 | // 图片在屏幕居中 940 | lv_obj_align(img1, NULL, LV_ALIGN_CENTER, 0, -20); 941 | ``` 942 | 943 | #### canvas画图 944 | 945 | ```c 946 | // 声明一个buff 947 | static lv_color_t buffer[LV_CANVAS_BUF_SIZE_TRUE_COLOR(48, 48)]; 948 | // 创建canvas 949 | lv_obj_t* canvas = lv_canvas_create(lv_scr_act(), NULL); 950 | // 关联canvas与buff 951 | lv_canvas_set_buffer(canvas, buffer, 48, 48, LV_IMG_CF_TRUE_COLOR); 952 | // 背景涂色 953 | lv_canvas_fill_bg(canvas, LV_COLOR_BLUE, LV_OPA_50); 954 | // 在画布上画点 955 | lv_color_t c0; 956 | c0.full = 0; 957 | uint32_t x; 958 | uint32_t y; 959 | for( y = 10; y < 30; y++) { 960 | for( x = 5; x < 20; x++) { 961 | // 这里的x,y都是相对父元素而言 962 | lv_canvas_set_px(canvas, x, y, c0); 963 | } 964 | } 965 | ``` 966 | 967 | #### 文件系统 968 | 969 | 1. 下载以下链接中的几个文件:https://github.com/lvgl/lv_fs_if 970 | 971 | 2. 将下载的.c/.h添加到工程中 972 | 3. 添加这些行到`lv_conf.h`: 973 | 974 | ```c 975 | /*File system interface*/ 976 | #define LV_USE_FS_IF 1 977 | #if LV_USE_FS_IF 978 | # define LV_FS_IF_FATFS '\0' // ‘S’ 979 | # define LV_FS_IF_PC '\0' 980 | #endif /*LV_USE_FS_IF*/ 981 | ``` 982 | 983 | 4. 通过将`'\0'`更改为要用于该驱动器的字母来启用所需的接口。如`'S'`表示FATFS的SD卡 984 | 5. 调用`lv_fs_if_init()`来注册启用的接口 985 | 6. 使用`lv_fs_fatfs.c`中提供的函数完成操作 986 | 7. 初始化图像,需要以下回调: 987 | - open 988 | - close 989 | - read 990 | - seek 991 | - tell 992 | 993 | 8. 使用示例: 994 | 995 | ```c 996 | // 挂载SD卡 997 | retSD = f_mount(&SDFatFS, SDPath, 0); 998 | // 文件系统初始化 999 | lv_fs_if_init(); 1000 | // 创建一个图像 1001 | lv_obj_t *icon = lv_img_create(lv_scr_act(), NULL); 1002 | // SD卡文件绑定到图像 1003 | lv_img_set_src(icon, "S:0.bin"); 1004 | // 居中显示 1005 | lv_obj_align(icon, NULL, LV_ALIGN_CENTER, 0, 0); 1006 | ``` 1007 | 1008 | #### 显示中文 1009 | 1010 | 教程看这篇:http://www.lfly.xyz/forum.php?mod=viewthread&tid=21 1011 | 1012 | LvglFontTool V0.4:http://www.lfly.xyz/forum.php?mod=viewthread&tid=11&extra=page%3D1 1013 | 1014 | **注意keil工程必须是UTF8编码!** 1015 | 1016 | image-20201026133629769 1017 | 1018 | ![image-20201026133655619](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201026133655619.png) 1019 | 1020 | 这时,串口输出中文可能是乱码。没关系,lvgl显示正常就行。或者用`SwitchToGbk`函数将utf8转成Unicode,这样串口就是正常的了。该函数可以到工程目录的`User/SXF`下找: 1021 | 1022 | ![image-20201026220214722](https://gitee.com/songxf1024/MarkdownPicBed/raw/master/Typora/img/image-20201026220214722.png) 1023 | 1024 | 生成的myFont.c下,有个函数需要替换一下(读SD卡方式): 1025 | 1026 | ```c 1027 | static uint8_t *__user_font_getdata(int offset, int size){ 1028 | //如字模保存在SPI FLASH, SPIFLASH_Read(__g_font_buf,offset,size); 1029 | //如字模已加载到SDRAM,直接返回偏移地址即可如:return (uint8_t*)(sdram_fontddr+offset); 1030 | uint32_t br; 1031 | if( f_open(&SDFile, (const TCHAR*)"0:/myFont.bin", FA_READ) != FR_OK ) 1032 | { 1033 | printf("myFont.bin open failed\r\n"); 1034 | } 1035 | else 1036 | { 1037 | if( f_lseek(&SDFile, (FSIZE_t)offset) != FR_OK ) 1038 | { 1039 | printf("myFont.bin lseek failed\r\n"); 1040 | } 1041 | if( f_read(&SDFile, __g_font_buf, (UINT)size, (UINT*)&br) != FR_OK ) 1042 | { 1043 | printf("myFont.bin lseek failed\r\n"); 1044 | } 1045 | 1046 | // printf("offset:%d\t size:%d\t __g_font_buf:%s\r\n", offset, size, __g_font_buf); 1047 | f_close(&SDFile); 1048 | } 1049 | return __g_font_buf; 1050 | } 1051 | ``` 1052 | 1053 | 调用示例: 1054 | 1055 | ```c 1056 | static lv_style_t date_style; 1057 | lv_style_init(&date_style); 1058 | lv_obj_t* date_label2 = lv_label_create(lv_scr_act(), NULL); 1059 | lv_label_set_text(date_label2, "123y呀"); 1060 | lv_style_set_text_color(&date_style, LV_STATE_DEFAULT, LV_COLOR_RED); 1061 | lv_style_set_text_font(&date_style, LV_STATE_DEFAULT, &myFont); 1062 | lv_obj_add_style(date_label2, LV_OBJ_PART_MAIN, &date_style); 1063 | lv_obj_align(date_label2, NULL, LV_ALIGN_IN_TOP_LEFT, 10, 10); 1064 | lv_obj_set_pos(date_label2, 10, 10); 1065 | ``` 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | --- 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | ### 待补充... 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | -------------------------------------------------------------------------------- /STM32CubeMX + HAL库.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/STM32F429_CubeMX_LVGL_FreeRTOS/12c23863dc50c4c932da9fedc44bc62f21f4c928/STM32CubeMX + HAL库.pdf -------------------------------------------------------------------------------- /esp_http_client.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/STM32F429_CubeMX_LVGL_FreeRTOS/12c23863dc50c4c932da9fedc44bc62f21f4c928/esp_http_client.7z -------------------------------------------------------------------------------- /最新的总工程.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/STM32F429_CubeMX_LVGL_FreeRTOS/12c23863dc50c4c932da9fedc44bc62f21f4c928/最新的总工程.7z --------------------------------------------------------------------------------