
基于STM32H743的掌上多功能百宝箱
简介
基于LVGL开发人机交互界面,配套的2.8寸触摸屏。同时使用OV2640摄像头、ATGM336H定位、SHT40温湿度、BH1750环境光传感器等各个传感器对应的配套功能。
简介:基于LVGL开发人机交互界面,配套的2.8寸触摸屏。同时使用OV2640摄像头、ATGM336H定位、SHT40温湿度、BH1750环境光传感器等各个传感器对应的配套功能。开源协议
:GPL 3.0
(未经作者授权,禁止转载)描述
1、作品名称
基于STM32H743的掌上多功能百宝箱
2、项目介绍
前几天参加了STM32的线下研讨会,领略到STM32在AI应用和GUI加速上的魅力。STM32H743是STM32系列的新款高性能MCU,目前在开源广场上关于H7系列的项目寥寥,且大部分都是开发板。为了能使H743的性能得到充分利用,本项目拟基于LVGL开发人机交互界面,配套的2.8寸触摸屏可以省去按键操作的麻烦,提升用户体验。同时,H743的GPIO接口非常多,可以挂接很多外设,本项目将使用OV2640摄像头、ATGM336H定位模块、SHT40温湿度传感器、BH1750环境光传感器等,做各个传感器对应的配套功能。
由于本项目外设多、基于触摸屏可扩展开发相当多的内容,且其仅为手掌大小(比2.8寸屏幕应该略大一圈),因而得名"掌上多功能百宝箱"。用户可基于本项目开源的软件代码,继续修改新增新的功能。
3、硬件模块介绍
3.1 STM32H743ZGT6 主控芯片
STM32H743ZGT6 是 STM32H7 系列中的高性能 MCU,采用 ARM Cortex-M7 内核,主频高达 480 MHz,内置 2MB 的 FLASH 和 1MB 的 RAM。这使得它在处理复杂任务时具备出色的性能,特别适合需要快速响应的应用场景。其丰富的外设接口使得外设的连接和控制更加灵活。
另外,设计上支持DFU下载,可以用STM32CubeProgrammer实现USB直连电脑下载程序。
3.2 扩展SDRAM
本项目中使用了 256Mbit 的 MT48LC16M16A SDRAM(相当于 32MB),其主要用于存储较大的临时数据和运行时的动态数组。由于 STM32H743 内部 RAM 容量有限,外部 SDRAM 的加入大大扩展了系统的可用内存,在显示较高分辨率的图形界面或处理摄像头图像时,SDRAM 提供了充足的缓存空间,为未来开发更复杂的功能提供了硬件基础。
3.3 扩展FLASH
项目中配备了 256Mbit 的 MT25QL256ABA1EW9-0SIT QSPI FLASH,用于存储系统文件和用户数据。QSPI 接口具有较快的读写速度,能够满足在加载大型资源时的需求。预留的 MT29F2G01ABAGDWB 作为备用 FLASH ,将来可做进一步扩展。
3.4 外置EEPROM
JSM24C02 是一个 2Kbit 的 EEPROM,作为本项目的设置参数存储单元,主要用于保存用户的偏好设置、系统配置和重要的运行参数等。相比于 FLASH,EEPROM 的读写寿命更长,且支持小数据的多次写入操作,因此非常适合用于频繁更新但数据量较小的场景。在断电的情况下,EEPROM 能确保配置数据的持久保存,从而避免因电源故障或重启导致的数据丢失。
3.5 ATGM336H 定位模块
ATGM336H 是一款高精度的 GPS 定位模块,能够快速获取当前位置并提供精确的地理坐标信息。在本项目中,定位模块主要用于实现路径追踪、运动轨迹记录等功能。结合触摸屏界面,用户可以查看实时定位信息。ATGM336H 支持GPS和北斗卫星定位,能在复杂的环境中也能获得稳定的定位信号。
3.6 OV2640 摄像头模块
OV2640 是一款具备 200 万像素的图像传感器,支持多种分辨率下的拍照和视频功能。在本项目中,OV2640 摄像头将用于捕获图像,并与 STM32 官方推出的 AI 库结合,开发手势识别等人工智能应用。
3.7 SHT40 温湿度传感器
SHT40 是一款高精度的温湿度传感器,其具备良好的稳定性和快速响应能力,能够精确检测环境的温度和湿度变化。在本项目中,SHT40 主要用于监测设备周围的环境状况,确保用户能实时掌握温湿度数据,并基于此开发相关的环境监控功能。
3.8 BH1750 环境光传感器
BH1750 是一款数字环境光传感器,用于检测周围环境的光线强度。在本项目中,BH1750 主要用于根据环境光线变化自动调整触摸屏的亮度,从而提升视觉体验,尤其是在不同光线条件下,屏幕亮度的自动调整可以有效减轻眼睛疲劳。
3.9 2.8英寸触摸屏
触摸屏用的是下面这一款,驱动IC为ILI9341,电阻式触摸。
http://e.tb.cn/h.gyKlKdaJeWtXDig?tk=mSZ33MEX7nv
若使用电容触摸屏,需要更改显示屏驱动源码。
4 项目进度
时间 | 具体内容 |
---|---|
2024.9.6 | 项目创建构思 |
9.9 | 原理图绘制完毕 |
9.11 | PCB绘制完毕 |
9.18 | 开始软件、UI开发 |
9.20 | 定位模块相关功能开发完毕 |
9.23 | 基于LVGL的主要界面开发完毕 |
9.25 | 加入DMA、缓冲实现全局60Hz流畅刷新 |
9.26 | 外壳设计完毕 |
9.30 | SDRAM驱动开发完毕 |
10.1 | QSPI Flash驱动开发完毕 |
10.6 | FatFS移植完毕 |
10.9 | LVGL文件浏览器Beta开发完毕 |
10.10 | 工程V1.0发布 |
10.17 | 已将源码开源于GitHub |
2025.5.20 | 将Gui_Guider源码开源 |
5 软件部分
目前,本项目仍有在开发的功能,附件中的.hex文件不一定是最新的。强烈建议您通过文末的GitHub仓库,clone代码到本地,然后在Keil中编译、下载!
5.1 LVGL性能报告
得益于H7系列自带的缓存以及超大RAM,可以肆无忌惮地增加缓冲数组的大小以及开启缓冲,以下是LVGL性能测试表:
组合 | 帧数 | CPU |
---|---|---|
单缓冲10*320+屏幕驱动自带画点函数 | 43FPS | 97%CPU |
单缓冲10*320+DMA+DCache OFF | 103FPS | 89%CPU |
单缓冲10*320+DMA+DCache ON | 214FPS | 87%CPU |
双缓冲20*320+DMA+DCache ON | 440FPS | 83%CPU |
双缓冲100*320+DMA+DCache ON | 997FPS | 57%CPU |
双缓冲全屏BUFFER240*320+DMA+DCache ON | 1152FPS | 62%CPU |
综合上述性能测试的表现,为了保持全程60FPS,又不牺牲太多性能和资源占用,因此,选用了双缓冲 100*320+DMA+DCache ON
下面是LVGL接口文件lv_port_disp关于缓冲这段的代码:
static lv_disp_draw_buf_t draw_buf_dsc_2;
static lv_color_t buf_2_1[MY_DISP_HOR_RES * 100]; /*A buffer for 10 rows*/
static lv_color_t buf_2_2[MY_DISP_HOR_RES * 100]; /*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 * 100); /*Initialize the display buffer*/
5.2 GPS模块的相关开发
在界面显示上面,专门开发了一个界面用于显示GPS相关信息。
在串口接收完消息后,调用解码GPS模块传来的GNRMC数据:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
if (0x0D == UART1_temp[0])
UART1_temp[0] = 0x20; // 防止\r在窗口中导致某行显示不了
if (0x0A == UART1_temp[0])
{
UART1_Rx_flg = 1;
}
u1_data[UART1_Rx_cnt++] = UART1_temp[0];
if (UART1_Rx_cnt >= MAX_REC_LENGTH)
{
UART1_Rx_cnt = 0;
memset(u1_data, '\0', strlen(u1_data));
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)UART1_temp, REC_LENGTH);
}
if (huart->Instance == UART5) // 专门接收GPS的数据 只要$GNRMC这一行的数据
{
if (UART5_temp[0] == '$')
{
UART5_Rx_cnt = 0;
}
if (0x0D == UART5_temp[0])
{
UART5_temp[0] = 0x20;
}
U5_Rec_temp[UART5_Rx_cnt++] = UART5_temp[0];
if (U5_Rec_temp[0] == '$' && U5_Rec_temp[4] == 'M' && U5_Rec_temp[5] == 'C' && UART5_temp[0] == 0x0A)
{
// 该行接收结束
UART5_Rx_flg = 1;
if (strlen(u5_data) + strlen(U5_Rec_temp) > 1000)
memset(u5_data, '\0', strlen(u5_data));
strcpy(u5_data, U5_Rec_temp);
memset(U5_Rec_temp, '\0', strlen(U5_Rec_temp));
UART5_Rx_cnt = 0;
Uart_Rece_Pares();
}
HAL_UART_Receive_IT(&huart5, (uint8_t *)UART5_temp, REC_LENGTH);
}
}
void Uart_Rece_Pares(void) // 串口接收内容解析函数
{
// 注意变量类型
char *point = 0;
char *nextPoint = 0;
for (uint8_t i = 0; i < 8; i ++)
{
if (i == 0)
{
if ((point = strstr(u5_data,",")) == NULL)
{
return ;
}
}
else
{
point ++;
if ((nextPoint = strstr(point,",")) != NULL)
{
// 存储信息
switch (i)
{
case 1: // UTC时间
memcpy(&gps.UTCTime,point,nextPoint - point);
break;
case 2: // 数据有效标识
memcpy(&gps.UsefulFlag,point,nextPoint - point);
break;
case 3: // 纬度
memcpy(&gps.latitude,point,nextPoint - point);
break;
case 4: // 纬度方向
memcpy(&gps.N_S,point,nextPoint - point);
break;
case 5: // 经度
memcpy(&gps.longitude,point,nextPoint - point);
break;
case 6: // 经度方向
memcpy(&gps.E_W,point,nextPoint - point);
break;
case 7:
memcpy(&gps.speed,point,nextPoint - point);
break;
}
point = nextPoint;
}
}
}
//处理数�??
float latitude = 0; // 存储纬度信息
uint16_t temp1 = 0;
float longitude = 0; // 存储经度信息
uint16_t temp2 = 0;
latitude = strtod((const char*)gps.latitude,NULL); // 字符串转换成浮点数
longitude = strtod((const char*)gps.longitude,NULL); // 字符串转换成浮点数
// 纬度信息处理
// 五位纬度信息
if ((latitude - 10000.0f) >= 0)
{
temp1 = (((uint16_t)latitude / 10000) % 10) * 100 + (((uint16_t)latitude / 1000) % 10) * 10 + ((uint16_t)latitude / 100) % 10;
latitude = latitude - (float)temp1 * 100;
latitude = (float)temp1 + latitude / 60;
}
else // 四位纬度信息
{
temp1 = (((uint16_t)latitude / 1000) % 10) * 10 + ((uint16_t)latitude / 100) % 10;
latitude = latitude - (float)temp1 * 100;
latitude = (float)temp1 + latitude / 60;
}
// 经度信息处理
// 五位经度信息
if ((longitude - 10000.0f) >= 0)
{
temp2 = (((uint16_t)longitude / 10000) % 10) * 100 + (((uint16_t)longitude / 1000) % 10) * 10 + ((uint16_t)longitude / 100) % 10;
longitude = longitude - (float)temp2 * 100;
longitude = (float)temp2 + longitude / 60;
}
else // 四位经度信息
{
temp2 = (((uint16_t)longitude / 1000) % 10) * 10 + ((uint16_t)longitude / 100) % 10;
longitude = longitude - (float)temp2 * 100;
longitude = (float)temp2 + longitude / 60;
}
gps.UTCTime_int = ((uint32_t)strtod((const char*)gps.UTCTime,NULL) +80000)%240000;
gps.latitude_d = latitude;
gps.longitude_d = longitude;
}
然后显示于表格中
void update_gps_info(lv_timer_t * timer){
#if !LV_USE_GUIDER_SIMULATOR
HAL_UART_Receive_IT(&huart5,(uint8_t *)UART5_temp,REC_LENGTH);
char t0[20],t1[20],t2[20],t3[20],t4[20];
//检查状态码
if(UART5_Rx_flg==1) {
UART5_Rx_flg = 0;
if(gps.UsefulFlag=='V')
strcpy(t0,"Wait...");
else if(gps.UsefulFlag=='A')
strcpy(t0,"OK");
lv_table_set_cell_value(guider_ui.app_screen_table_gps,0,1,t0);//状态
sprintf(t1,"%.5f",gps.latitude_d);
if(gps.N_S=='N')
strcat(t1," N");
else if(gps.N_S=='S')
strcat(t1," S");
lv_table_set_cell_value(guider_ui.app_screen_table_gps,1,1,t1);//纬度
sprintf(t2,"%.5f",gps.longitude_d);
if(gps.E_W=='E')
strcat(t2," E");
else if(gps.E_W=='W')
strcat(t2," W");
lv_table_set_cell_value(guider_ui.app_screen_table_gps,2,1,t2);//经度
sprintf(t3,"%02d:%02d:%02d",gps.UTCTime_int/10000,gps.UTCTime_int/100%100,gps.UTCTime_int%100);
lv_table_set_cell_value(guider_ui.app_screen_table_gps,3,1,t3);//UTC
gps.speed_d = strtod((const char*)gps.speed,NULL);
sprintf(t4,"%.2f",gps.speed_d*1.852);
strcat(t4," km/h");
lv_table_set_cell_value(guider_ui.app_screen_table_gps,4,1,t4);//速度 km/h
}
#endif
}
5.3 踩坑记录表
以下是开发软件过程中踩坑的汇总表,每次解决小问题时都有记录。如有朋友在调试时出现问题,不妨查看此表:
- 进systeminit后立即hardfault。解决:用cube programmer进行full chip erase然后下载程序,可能之前程序有错误。
- 屏幕显示白屏:解决:开启cortex m7里面的相关选项,见https://blog.csdn.net/weixin_66689383/article/details/132046062
- 在CUBEMX中配置需要RCC的power supply设为PWR_LDO,否则芯片跑不起来!!
- DMA开启后屏幕花屏。原因:开启了cortex_m7中的MPU的Cache,导致了数据不一致。解决:关闭DCache(会降低性能)或者在DMA中断开启前后加上SCB_CleanInvalidateDCache();
- 下载程序后程序不运行。原因:加入了printf但没有任何重定向,删掉即可
- 下载程序时显示no algorithm found for。 解决:https://blog.csdn.net/qq_22433397/article/details/122223508
- SDRAM写数据后再读取出错,解决:调整SDRAM时钟周期,目前测试不报错的为 2 8 6 6 2 2 6 另外注意SDRAM配置不要出错 BANK1 9BITS 13BITS 3CLK DISABLE 2CLK ENABLE 0CLK
- 移植FATFS遇到在f_mount出现内存管理错误。原因:访问了禁止访问的内存。解决:在CUBEMX中把FATFS的设备从2改成1就行。
- FATFS文件系统创建后重新挂载失败。原因:sizeof()运算符的坑,写入时用了sizeof(buff),sizeof(buff)读到是4,但实际上要写4096。在建立MBR时,0x55 0xAA代表MBR的结束,在0x1FE~0x1FF上,自然就不被写入flash,也就找不到引导了。
- 在移植FATFS到主分支之后,出现了FATFS打不开文件的情况,在测试分支正常。解决:开启了cortex_m7中的MPU的Cache,导致了数据不一致问题。解决:在MPU设置里专门把0X24000000后面512KB区域设为Cacheable disabled
- 加入FATFS之后屏幕刷新变卡了。原因:开启了上面的MPU 0x24000000的区域后,需要Cacheable disabled,关闭之后就变卡了。解决:在底层QSPI Read 和 Write函数开头加入SCB_CleanInvalidateDCache(),用以清理DCache缓存保证数据一致性
- FATFS读取文件大小错误。原因:在printf时误把 unsigned int 占位符写成了%llu,实际应为%u
- LVGL文件系统在读取文件return时进hardfault。原因:在fs_open return了一个局部变量的地址,应将改变量作为全局变量
6 参考 致谢
项目硬件设计过程中参考如下工程:
https://oshwhub.com/lixidixi/stm32h743iit6-development-board
https://oshwhub.com/upoaktreesup/stm32h735igt6-dev
定位模块参考:
https://oshwhub.com/bai-yy/stm32-hu-atgm336h-zeng-ge-yi-biao_copy_copy
在开发软件过程中,在论坛 https://www.armbbs.cn/ 获益良多,感谢硬汉大佬的H7系列开源代码以及热心帮助!
特别感谢嘉立创的星火计划提供耗材支持!
7 外观
代码开源地址
Keil工程源码:https://github.com/vrxiaojie/STM32H743ZGT6
Gui_Guider源码:
https://github.com/vrxiaojie/STM32H743ZGT6/releases/tag/Gui_Guider_V1.0
设计图

BOM


评论