同步操作将从 惊鸿一回车/STM32F429_CubeMX_LVGL_FreeRTOS 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
基于野火F429开发板,用STM32Cube生成代码,全面详细的教程
最终示例是通过ESP32,在F429上实时显示天气
文章目录
STM32CubeMX + HAL
前言
紧急避坑
USART
freertos+fatfs+sdio
一些说明
Cube基本使用
HAL库函数
中断回调函数
外设对应时钟
配置示例
小编有话说
USART
RTC
SDIO + FATFS
SDRAM
LTDC + DMA2D
FreeRTOS
TouchGFX显示
LittleVGL
显示图片
C数组形式
canvas画图
文件系统
显示中文
待补充...
我的CSDN博客:小锋学长生活大爆炸
问题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时候,sd卡读写正常;加上freertos时候,mout成功,但read等其他操作返回错误3 not ready
解决: sdio和sddma的中断优先级要小于freertos的最小优先级
使用STM32CubeMX
代码生成工具,不用关注底层配置的细节,真舒服。
使用教程:
https://sxf1024.lanzoui.com/b09rf2dwj 密码:bgvi
虽然Cube+HAL
很舒服,但新手不建议用。最好还是先去学一下标准库怎么用,有个大致概念后,再来学这一套。
自动化的东西虽好,但一旦出了问题,解决起来也是挺头疼的。
RCC(HSE:Crystal/Ceramic Resonator)
、SYS(Debug:Serial Wiire)
GPIO
相关,可以直接在Pinout view选择;若是其他功能,可以在左边Categories打开,会自动配置引脚)、设置Parameter Settings/NVIC
等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_AHB1ENR_GPIOCEN
stm32f429xx.h
文件RCC_AHB1ENR_DMA1EN
例子源码:
如果配置过程中,参数不知道怎么设置,可以去标准库例程(如野火、正点原子)中看对应的参数是什么
Cube软件只是帮你配置了底层,一些初始化代码还是需要自己手动加的,如SDRAM充电初始化、读写函数等
以下内容都是基于**“野火F429IGT6挑战者V2开发板”**,其他板子按照原理图改改引脚都能用的
源码链接:
https://sxf1024.lanzoui.com/b09rf535a 密码:bf5q
详细教程网上挺多,配置也简单,只要勾选一下USARTx,再开一下中断就行。
在Keil就比较要注意了。
由于每次接收完,程序内部自动把接收中断关了,所以每次要手动打开。
总的来说,加这几部分:
main
函数中,while
之前:// 使能串口中断接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)&DataTemp_UART1, 1);
#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_DateTypeDef sDate;
RTC_TimeTypeDef sTime;
uint8_t second_tmp = 0;
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);
}
Connectivity -> SDIO -> Mode: SD 4bit Wide bus -> 勾选NVIC
Yes
。SDIO时钟分频系数CLKDIV
,计算公式为SDIO_CK=48MHz/(CLKDIV+2)
也可手动修改时钟配置。如果遇到读写问题,可以试着调整到24MHz。启用文件系统中间件,Pinout&Clock Configuration,Middleware -> FATFS
,模式选择SD卡,配置文件系统:
如果要支持中文文件名,则配置CODE_PAGE
为Simplified Chinese
如果要支持长文件名,则要使能USE_LEN
Use dma template
Connectivity -> SDIO-> DMA Settings
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”)
static void BL8782_PDN_INIT(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
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);
开启FMC功能,Pinout&Configuration ,Connectivity -> FMC -> SDRAM2
SDRAM1的起始地址为0XC0000000,SDRAM2的起始地址为0XD0000000。
一般SDRAM都含有4个bank。
Configuration中的参数可从SDRAM的数据手册上找到。
各个选项的配置(只做解释,不对应上图):
Clock and chip enable
:FMC_SDCKE0 和FMC_SDCLK0对应的存储区域1 的地址范围是0xC000 0000-0xCFFF FFFF;而FMC_SDCKE1 和FMC_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
uint8_t temp[100]__attribute__((at(0xD0000000)));
for(int i=0;i<100;i++){
temp[i] = i;
}
for(int i=0;i<100;i++){
printf("%d ", temp[i]);
}
到这里只是借助Cube完成了引脚配置,还需要SDRAM初始化操作和读写函数,可从官方例程里获取,路径:
C:\Users\10617\STM32Cube\Repository\STM32Cube_FW_F4_V1.25.0\Drivers\BSP\STM32F429I-Discovery\XXX
/*****************************SDRAM使能函数******************************/
/**
* @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;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 2: Insert 100 us minimum delay */
/* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
HAL_Delay(1);
/* 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;
/* 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;
/* 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;
/* 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,源码链接:
https://sxf1024.lanzoui.com/b09rf535a 密码:bf5q
添加到工程Core
路径下,然后在KEIL中初始化操作:
(注意这个SDRAM_InitSequence();
不能加在HAL_SDRAM_MspInit()
后面!!! 因为它这里还没FMC初始化完成!!!! 加在这里是没有用的!!!)
#include "bsp_sdram.h"
MX_FMC_Init();
SDRAM_InitSequence();
SDRAM_Test();
务必在上面SDRAM配置成功后,再来搞这个!!!
详细教程看这个:https://zzttzz.gitee.io/blog/posts/7109b92c
但他给的源码还有点问题,运行处理没效果。
我提供的源码链接:
https://sxf1024.lanzoui.com/b09rf535a 密码:bf5q
注意:
我跟他的配置有点不一样,我的是:
层 1 = layer 0,层 2 = layer 1
后面要上TouchGFX,这里先加操作系统。
当FreeRTOS遇到FATFS+SDIO时,这里有挺多注意细节的!!!
针对初学者,使用STM32CubeMX配置FreeRTOS时,大部分参数默认即可
改完之后,注意:中断处理程序调用RTOS函数,请确保它们的优先级比最高的系统调用中断优先级低(数字上高),例如FreeRTOS中的LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
如使用了FreeRTOS,会要求强制使用DMA模板的Fatfs,所以打开DMA通道,开中断,以及开SDIO中断是必须的,否则后面配置FATFS无法运行。
使能SDIO中断,这里的中断优先级默认不是5
的,而FreeRTOS要求优先级从5
开始
Project Manager->Project->Linker Setting
中的最小堆栈大小,太小就会无法挂载SD卡或者读写时失败,基本上默认值都是无法正常运行的MINIMAL_STACK_SIZE
,默认值是128,使用默认值会造成f_mount直接卡死在内部,这里使用256
生成代码,使用Keil打开。RTOS默认创建了一个defaultTask()
,在freertos.c
文件中
由于SD卡初始化时有检测读写是否在task任务中,所以SD读写测试代码需要放到defaultTask()
中
其他的无需改动,运行即可!
发现它创建任务用的是osThreadNew
,对xTaskCreate
又进行了封装,省去了繁琐
网上有人说要“在TIM6中断中,加入临界段保护(或进入中断保护)”,不知真假,没试
虽然方便,但它是用C++开发的,所以不是特别友好...说白了就是看不懂,不知道怎么去改
Cube
里安装TouchGFX
包FreeRTOS
要选CMSIS V1
版本!!!TouchGFX
包,并配置如下C:\Users\10617\STM32Cube\Repository\Packs\STMicroelectronics\X-CUBE-TOUCHGFX\4.15.0\Utilities\PC_Software\TouchGFXDesigner\TouchGFX-4.14.0.msi
TouchGFX
文件夹,打开其中的.touchgfx
文件,绘制界面#include "app_touchgfx.h"
// 开启LCD
LCD_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
奈何不会C++,只能另谋出路,LittltVGL设计的界面似乎还挺好看的,而且用C编写,兼容C++,更新很活跃。EMWIN风格类似window XP ,littlevGL风格类似android 。移植很简单(并没有多简单)。
官网github:https://github.com/lvgl/lvgl
官方文档:https://docs.lvgl.io/latest/en/html
官方推荐方法学习路线:
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();
// 开启LCD
LCD_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);
}
当然,我的源码链接(只有显示部分,触摸目前没用到):
https://sxf1024.lanzoui.com/b09rf535a 密码:bf5q
注意!!!:
一定要先执行初始化lv_init(); lv_port_disp_init();
,再做其他ui操作,不然会死活不显示出来!!!
附,我的一些理解:
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文件链接:https://littlevgl.com/image-to-c-array
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);
// 声明一个buff
static lv_color_t buffer[LV_CANVAS_BUF_SIZE_TRUE_COLOR(48, 48)];
// 创建canvas
lv_obj_t* canvas = lv_canvas_create(lv_scr_act(), NULL);
// 关联canvas与buff
lv_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);
}
}
下载以下链接中的几个文件:https://github.com/lvgl/lv_fs_if
将下载的.c/.h添加到工程中
添加这些行到lv_conf.h
:
/*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
中提供的函数完成操作
初始化图像,需要以下回调:
使用示例:
// 挂载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);
教程看这篇:http://www.lfly.xyz/forum.php?mod=viewthread&tid=21
LvglFontTool V0.4:http://www.lfly.xyz/forum.php?mod=viewthread&tid=11&extra=page%3D1
注意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);
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。