这个项目耗时三个月,前两个月攻克技术难关,后一个月进行功能联调,也是我很长时间没有更新的原因。一个项目从初期的evt到最终的pvt,离不开大家的合作。从前期的prd核对到最终的项目交付,耗费了我大量心血,期间遇到的问题不计其数,所以说一个好的项目能极大的锻炼开发人员各方面的能力,包括抗压能力、技术栈、沟通能力。通过这次项目我觉得开发人员在接手一个项目时,尤其是项目负责人时,最重要的不是马上去编码,而是规划,只有前期足够的文档支持,才能事倍功半。尤其是PRD需求的评估。涉及到技术方面其中要着重考虑:代码架构、涉及到的技术栈、通讯的稳定性和快速性、通讯协议的制定和容错处理等,把一个大的项目分成若干个小模块,逐个击破,最终整合、优化。其中有一段时间遇到技术难关时真的很痛苦,尤其是没有相关技术支持还得接受各方的压力,心态真的很重要,尤其对于自己陌生的技术栈,一定要有快速学习的态度和能力,坚持下去会有意想不到的收获。
副屏项目总结
1、项目背景:项目需要通过MCU作为SPI从机和安卓主机通信显示应用图标、电量信息、开关机动画、主从机交互等功能。
2、芯片:STM32U575CIU6
3、IDE: keil
4、触摸芯片:CST816T 自电容触控芯片
5、TFT LCD 驱动芯片:GC9A01A
6、SPI Flash:GD25LE64E
一、STM32U575CIU6 平台移植FreeRTOS
1、遇到的问题
a、STM32CUBEMX 不支持 该系列单片机的RTOS库
b、FreeRTOS的官方支持包没有ContexM33内核的支持包
2、解决问题:
前期一味靠移植试图解决问题,结果是浪费了很多时间效果不理想,换个思路结果很快解决问题。
参考STM32L5系列的RTOS架构,L5系列也是M33内核,通过STM32CUBEMX生成KEIL工程进行参考移植。
二、移植相关设备驱动
根据原厂提供的SDK进行移植,这部分难度不到,但是要注意代码的封层架构,将软硬件隔离,便于将来进行硬件替换,这部分将来会单独写一篇文章。
STM32U575CIU6 平台移植SFUD
STM32U575CIU6 平台移植屏幕驱动
STM32U575CIU6 平台移植触摸芯片驱动
三、STM32U575CIU6 平台的log日志系统
一个合格的嵌入式系统,log日志的重要性不言而喻,此次项目采用RTT作为log输出,通过RTC为log系统提供准确的时间戳,可以输出变量日志等级控制。
#ifndef __HAL_LOG_PUBIF_H
#define __HAL_LOG_PUBIF_H#ifdef __cplusplus
extern "C" {
#endif#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>/*全局宏定义*/
#define LOG_RTT_MODE true
#define CONFIG_APP_DEBUG_LOG truetypedef int8_t S8;
typedef int16_t S16;
typedef int32_t S32;
typedef uint8_t U8;
typedef uint16_t U16;
typedef uint32_t U32;
typedef bool BOOL;typedef struct {uint16_t year; // 16 means 2016uint8_t month; // 0-11uint8_t day; // 0-30uint8_t second; // 0-59uint8_t minute; // 0-59uint8_t hour; // 0-23
} UTCTimeStruct;//正常打印最大字符串长度
#define LOG_BUF_MAX_SIZE (512)// 16进制数打印最大缓存
#define LOG_HEXDUMP_MAX_LENGTH (256)// log队列最大长度
#define LOG_QUEUE_NUM (64)/*** @brief : 日志输出等级定义*/
#undef LEVEL_INFO
#undef LEVEL_WARNING
#undef LEVEL_ERROR#define LEVEL_CLOSE (1)
#define LEVEL_SIMPLE_FORCE (4)
#define LEVEL_FORCE (5)#define LEVEL_CLI (9)
#define LEVEL_RELEASE (10)
#define LEVEL_SIMPLE (11)
#define LEVEL_DEBUG (12)
#define LEVEL_INFO (13)
#define LEVEL_WARNING (14)
#define LEVEL_ERROR (15)
#define __LEVEL__ LEVEL_ERROR#if (BUTTON_ACTION_LOG_EN == 1)
#define LEVEL_BUTTON_ACTION (LEVEL_DEBUG)
#else
#define LEVEL_BUTTON_ACTION (LEVEL_CLOSE)
#endif#if (BLE_ORIGIN_DATA_LOG_LOG_EN == 1)
#define LEVEL_BLE_ORIGIN_DATA (LEVEL_DEBUG)
#else
#define LEVEL_BLE_ORIGIN_DATA (LEVEL_CLOSE)
#endif#if (BLE_CMD_DATA_LOG_LOG_EN == 1)
#define LEVEL_BLE_CMD_DATA (LEVEL_DEBUG)
#else
#define LEVEL_BLE_CMD_DATA (LEVEL_CLOSE)
#endif#if (ZB_ORIGIN_DATA_LOG_LOG_EN == 1)
#define LEVEL_ZB_ORIGIN_DATA (LEVEL_DEBUG)
#else
#define LEVEL_ZB_ORIGIN_DATA (LEVEL_CLOSE)
#endif/*** @brief : 宏函数,输出变量日志等级控制*/
#define LOG_PRINT(level, format, ...) \do { \if ((LEVEL_CLOSE < level) && (level <= __LEVEL__)) { \__log(level, format, ##__VA_ARGS__); \} \} while (0)/*** @brief : 宏函数,输出变量日志等级控制*/
#define LOG_PRINT_HEXDUMP(level, buf, len) \do { \if ((LEVEL_CLOSE < level) && (level <= __LEVEL__)) { \__log_hexdump(level, buf, len); \} \} while (0)#if (CONFIG_APP_DEBUG_LOG == true)
#define LOG(level, format, ...) LOG_PRINT(level, format, ##__VA_ARGS__)
#define LOG_RELEASE(level, format, ...) LOG_PRINT(level, format, ##__VA_ARGS__)
#define LOG_FACTORY(level, format, ...) LOG_PRINT(level, format, ##__VA_ARGS__)
#define LOG_HEXDUMP(level, buf, len) LOG_PRINT_HEXDUMP(level, buf, len)
#define LOG_FACTORY_HEXDUMP(level, buf, len) LOG_PRINT_HEXDUMP(level, buf, len)
#else
#define LOG(level, format, ...)
#define LOG_RELEASE(level, format, ...) \LOG_PRINT( \((LEVEL_SIMPLE == level) ? LEVEL_SIMPLE \: ((LEVEL_FORCE == level) ? LEVEL_SIMPLE_FORCE : LEVEL_RELEASE)), \format, ##__VA_ARGS__)#define LOG_FACTORY(level, format, ...) LOG_PRINT(level, format, ##__VA_ARGS__)
#define LOG_HEXDUMP(level, buf, len)
#define LOG_FACTORY_HEXDUMP(level, buf, len) LOG_PRINT_HEXDUMP(level, buf, len)
#endifvoid log_task_handle(void* pvParameters);
void __log(U8 level, const char* restrict format, ...);
void __log_hexdump(U8 level, U8* buf, U16 len);#ifdef __cplusplus
}
#endif#endif
四、STM32U575CIU6 平台的看门狗系统
看门狗容错处理也是前期必须要做的工作。
void MX_IWDG_Init(void)
{/* USER CODE BEGIN IWDG_Init 0 *//* USER CODE END IWDG_Init 0 *//* USER CODE BEGIN IWDG_Init 1 *//* USER CODE END IWDG_Init 1 */hiwdg.Instance = IWDG;hiwdg.Init.Prescaler = IWDG_PRESCALER_32;hiwdg.Init.Window = 3000-1;hiwdg.Init.Reload = 3000-1;hiwdg.Init.EWI = 0;if (HAL_IWDG_Init(&hiwdg) != HAL_OK){Error_Handler();}/* USER CODE BEGIN IWDG_Init 2 *//* USER CODE END IWDG_Init 2 */}/* USER CODE BEGIN 1 */
/**
* @brief IWDG_Feed(void)(3S之内喂一次狗)
* @param None
* @retval None
*/
void IWDG_Feed(void)
{ HAL_IWDG_Refresh(&hiwdg);
}
/* USER CO
五、STM32U575CIU6 平台SPI通讯(指令交互)
整体的通信流程还是相当复杂的,采用发送包采用双命令字格式: 无论是短包命令还是长包命令都包含两个cmd,用于区分当前包和下一包的帧类型。
#2022.11.29
HEL 库配置
/* SPI2 init function */
void MX_SPI2_Init(void)
{
hspi2.Instance = SPI2;hspi2.Init.Mode = SPI_MODE_MASTER; //MASTER 模式 hspi2.Init.Direction = SPI_DIRECTION_2LINES; //全双工hspi2.Init.DataSize = SPI_DATASIZE_8BIT; //数据大小为8bit hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟空闲状态为低电平hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; //第一个边沿采样 hspi2.Init.NSS = SPI_NSS_HARD_OUTPUT; //配置spi在master下,NSS作为SPI专用IO,由MCU自动控制片选,只能1主1从hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; //数据传输模式为MSB hspi2.Init.TIMode = SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi2.Init.CRCPolynomial = 0x0;hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;hspi2.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; //用于设置NSS引脚上的高电平或者低电平作为激活电平。hspi2.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;hspi2.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; hspi2.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;hspi2.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;hspi2.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; hspi2.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;hspi2.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;hspi2.Init.IOSwap = SPI_IO_SWAP_DISABLE; if (HAL_SPI_Init(&hspi2) != HAL_OK){ Error_Handler(); }}
hspi2.Init.NSS = SPI_NSS_SOFT; //配置spi在master下,NSS作为普通IO,由用户自己写代码控制片选,可以1主多从
hspi2.Init.NSS = SPI_NSS_HARD_OUTPUT; //配置spi在master下,NSS作为SPI专用IO,由MCU自动控制片选,只能1主1从
hspi2.Init.NSS = SPI_NSS_HARD_INPUT; //仅当配置spi在slave下,作为从机片选输入#2022.12.11、主机留意 NSS
之前从机设置hspi2.Init.NSS = SPI_NSS_HARD_INPUT;
主机没有设置,导致主机发送完一个字节不管NSS,从机需要主机拉高NSS,导致从机只能接收一个字节。
这种模式下从机设置按数据帧接收也可以,就相当于接收一个数据帧。从机更改为hspi2.Init.NSS = SPI_NSS_SOFT; //使得NSS一直为低电平,则可以接收多个字节2、主机配置 多字节之间有间隔,就是将时序分开。3、两种方案:
aa、主机设置多字节之间没有间隔,但是必须设置NSS。这样从机用DMA方式 通过判断NSS(NSS拉高--发送完毕)电平来确定主机数据是否发送完毕。
bb、主机设置多字节之间有间隔,NSS主机可以不用设置。从机可以通过定时器超时中断接收数据。4、可能有人疑问,之前一直没管NSS,SPI一样通信,那是因为从机使用了NSS软件模式,通过寄存器控制,使得NSS一直为低电平。
安卓测试工具:adb spitest,通过这个小工具就可以模拟SPI主机发送指令,可以设置通信速度,在调试从机起到了很大作用。
#2022.12.6
spitest -D /dev/spidev0.0 -s 10000000 -p \\x10\\x10\\x07\\x00\\x01\\x11\\x22\\x33\\x44\\x55\\x66 -v
spitest -D /dev/spidev0.0 -s 10000000 -p \\x10\\x50\\x07\\x00\\x01\\x11\\x22\\x33\\x44\\x55\\x66 -v
spitest 15000000 -pspitest -D /dev/spidev0.0 -s 15000000 -p \\x10\\x10\\x01\x00\\x01 -v /*错误示例 测试看门狗 缺一‘\’*/
测试流程spitest -D /dev/spidev0.0 -s 10000000 -p \\x10\\x10\\x01\\x00\\x01 -v /*主从机第一次握手*/
spitest -D /dev/spidev0.0 -s 10000000 -p \\x10\\x50\\x01\\x00\\x01 -v /*主从机握手是否成功*/
/*主机发送第一包数据*/
spitest -D /dev/spidev0.0 -s 10000000 -p \\x50\\x10\\x16\\x00\\x01\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x11\\x22\\x33\\x44\\x55\\x66\\x77\\x88\\x99\\x21\\x22\\x23 -v
spitest -D /dev/spidev0.0 -s 10000000 -p \\x10\\x50\\x01\\x00\\x01 -v /*主机查询第一个数据包是否发送成功*/
/*主机发送第二包数据*/
spitest -D /dev/spidev0.0 -s 10000000 -p \\x50\\x10\\x16\\x00\\x01\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x11\\x22\\x33\\x44\\x55\\x66\\x77\\x88\\x99\\x21\\x22\\x23 -v
spitest -D /dev/spidev0.0 -s 10000000 -p \\x10\\x10\\x01\\x00\\x01 -v /*主机查询第二个数据包是否发送成功*/spitest -D /dev/spidev0.0 -s -v一包图片:115200 字节 两包每包:57600字节
0x5a 0xa5 0x50 0x10 len_L len_H appcmd 57600 sum_L sum_H 每包总长度57609 字节 len = 57601 (包含一字节appcmd).
六、STM32U575CIU6 平台SPI通讯(传输图片数据)
一包图片的数据量是115200个字节。速率是10M,这对于稳定性和准确性要求还是很高的,所以协议的制定必须考虑多种情况,降低出错率,增加容错机制。
七、STM32U575CIU6 平台OTA升级之APP
第一次通过SPI进行OTA,之前用串口和CAN总线进行OTA升级的bin文件还比较小。这次的升级工作最小的bin文件包有600多k,而且只预留了SPI通信,虽然速度方面是其他总线不可比拟的,但同时对稳定性要求也是最高的,所以制定详细且容错机制丰富的OTA协议是非常重要的。
1、升级背景
- 副屏固件升级采用SPI通讯方式
- SPI通讯采用固定长短帧方式进行通讯
2、升级流程
2.1 通讯格式和命令
短包数据类型(总长度固定长20字节,不够20字节补0xFF)
2.1.1 发送包采用双命令字格式
无论是短包命令还是长包命令都包含两个cmd,用于区分当前包和下一包的帧类型。
else if((DF_CMD_SPI_IAP_START == g_SPI_Device.StdID)) /*IAP 跳转Boot指令*/{LOG(LEVEL_DEBUG, "IAP ID pass ");if (0 == memcmp(&g_SPI_Device.data_u8_t[0], DF_STR_SPI_IAP_START, strlen(DF_STR_SPI_IAP_START))){LOG(LEVEL_DEBUG, " System Reset to run Bootloader ! ");#if 1__disable_irq();bsp_flash_Erase_Flash(6, 1); /*0x0800C000*/vTaskDelay(10);bsp_flash_Write_Flash(IAP_FLAG_ADDR, (uint8_t *)DF_FLAG_IAP_STRING, 1);__enable_irq();vTaskDelay(20);HAL_NVIC_SystemReset();
#endif}else{LOG(LEVEL_DEBUG,"IAP Start CMD error ! ");}}
3、测试指令
spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x01\\x00\\x02 -v /*APP升级握手指令*/spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x01\\x00\\x01 -v /*APP升级握手指令*/
spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x01\\x00\\x01 -v /*主从机握手是否成功*/
spitest -D /dev/spidev0.0 -s 15000000 -p \\x40\\x40\\x07\\x00\\x01\\x41\\x53\\x74\\x61\\x72\\x74 -v /*APP升级切换boot指令*/Boot
spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x01\\x00\\x01 -v /*BOOT升级握手指令*/
spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x01\\x00\\x01 -v /*主从机握手是否成功*/
spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x07\\x00\\x01\\x41\\x53\\x74\\x61\\x72\\x74 -v /*bootstart指令*/
spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\xe0\\x01\\x00\\x01 -v /*查询*/spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x06\\x00\\x01\\x42\\x46\\x69\\x6c\\x65 -v /*OTA COPY指令*/spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x06\\x00\\x01\\x43\\x53\\x74\\x6f\\x70 -v /*Stop指令*/spitest -p reset /*Reset指令*/spitest -D /dev/spidev0.0 -s 10000000 -p \\x20\\x20\\x09\\x00\\x01\\x21\\x56\\x65\\x72\\x73\\x69\\x6f\\x6e -v /*软件版本查询指令*/spitest -D /dev/spidev0.0 -s 10000000 -p \\x40\\x40\\x06\\x00\\x01\\x44\\x43\\x6f\\x70\\x79 -v /*ota_cpoy结果查询指令*//*主机发送第一包数据*/
spitest -D /dev/spidev0.0 -s 15000000 -p \\xe0\\x40\\x21\\x00\\x01\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x01\\x02\\x03\\x04\\x05 -v
spitest -D /dev/spidev0.0 -s 15000000 -p \\x40\\xe0\\x01\\x00\\x01 -v /*主机查询第一个数据包是否发送成功*/
/*主机发送第二包数据*/
spitest -D /dev/spidev0.0 -s 15000000 -p \\xe0\\x40\\x21\\x00\\x01\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\xa\\xb\\xc\\xd\\xe\\xf\\x11\\x22\\x33\\x44\\x55\\x66\\77\\88\\x99\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28 -v
spitest -D /dev/spidev0.0 -s 15000000 -p \\x40\\xe0\\x01\\x00\\x01 -v /*主机查询第一个数据包是否发送成功*/spitest -D /dev/spidev0.0 -s 15000000 -p \\x40\\xe0\\x01\\x00\\x01 -v /*BOOT升级握手指令*/spitest -D /dev/spidev0.0 -s 15000000 -p \\xe0\\x40\\x21\\x00\\x01\\x31\\x32\\x33\\x34\\x35\\x06\\x07\\x08\\x09\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x01\\x02\\x03\\x04\\x05 -v
八、STM32U575CIU6 平台OTA升级之BOOT
最近现在调试说stm32 的iap程序时,每次跳转总是进入hardfault_handler,仔细检查跳转时的设置,前面进行了两个操作关中断
__disable_irq()和把用户代码的栈顶地址设置为栈顶指针__set_MSP(),首先用户代码的栈顶地址是正确的,看了下__disable_irq()使用的“cpsid
i”只是简单的禁止CPU去响应中断,没有真正的去屏蔽中断的触发,中断发生后,相应的寄存器会将中断标志置位,在__enable_irq()后,
由于中断标志位没请空,还会触发中断,因此禁止中断需要逐个对模块进行Disable操作。进行修改后程序正常运行。
增加BOOT容错机制,进入Bootloader 固件升级后,最长200秒执行完,否则重启.
#include "main.h"
#include "app_iap.h"
#include "protocol.h"
#include "bsp_flash.h"SemaphoreHandle_t xBinarySemaphore_iap_rec;
SemaphoreHandle_t xBinarySemaphore_iap_ota_copy;
uint8_t ready_update_buff[MCRC_IAP_LEGTH] = {0};
/**
* @brief vDisplay_Task(void)
* @param None
* @retval None
*/
void vIAP_Task(void const *argument)
{xBinarySemaphore_iap_rec = xSemaphoreCreateBinary();xBinarySemaphore_iap_ota_copy = xSemaphoreCreateBinary();volatile uint16_t flash_msg_len_index = 0;volatile uint16_t flash_write_index = 0;volatile static uint8_t flash_erase_flag = 0;volatile static uint8_t flash_write_flag = 0;volatile static uint32_t flash_addr_stage = 0;uint8_t temp_buff[10] = {0};for (;;){if (xSemaphoreTake(xBinarySemaphore_iap_rec, portMAX_DELAY) == pdTRUE){LOG("[RUN]Iap_Task_Start \r\n");flash_msg_len_index = msg_len -1;LOG("flash_msg_len_index:%d \r\n", flash_msg_len_index);memcpy(ready_update_buff, rec_iap_update_buff, flash_msg_len_index);flash_write_index = flash_msg_len_index / 16;LOG("flash_write_index:%d \r\n", flash_write_index);
#if 1__disable_irq();if (flash_erase_flag == 0){flash_erase_flag = 1;bsp_flash_Erase_Blank2_Flash(0, 120); /*0x08100000 -- 0x081F0000*/flash_addr_stage = STAGE_START_ADDR;LOG("Boot Erase Stage Page Success \r\n");}vTaskDelay(10);bsp_flash_Write_Flash(flash_addr_stage, (uint8_t *)ready_update_buff, flash_write_index);//bsp_flash_Read_Flash(flash_addr_stage, temp_buff, 4);flash_addr_stage += flash_msg_len_index;flash_write_flag++;//LOG("data:0x%x 0x%x 0x%x 0x%x\r\n", temp_buff[0], temp_buff[1], temp_buff[2], temp_buff[3]);LOG("flash_addr_stage:0x%x \r\n", flash_addr_stage);LOG("Boot Write Stage index:%d \r\n", flash_write_flag);__enable_irq();
#endif}vTaskDelay(200);}
}volatile uint8_t ota_copy_status; /*ota_copy 结果状态*/
/**
* @brief vOTA_Copy_Task(void)
* @param None
* @retval None
*/
void vOTA_Copy_Task(void const *argument)
{HAL_StatusTypeDef ota_temp = HAL_ERROR;xBinarySemaphore_iap_ota_copy = xSemaphoreCreateBinary();for (;;){if (xSemaphoreTake(xBinarySemaphore_iap_ota_copy, portMAX_DELAY) == pdTRUE){LOG("ota_copy start \r\n");ota_temp = ota_copy(STAGE_START_ADDR, APP_START_ADDR, STAGE_SIZE);if (ota_temp == HAL_OK){ota_copy_status = OTA_COPY_PASS;LOG("ota_copy success \r\n");}else{ota_copy_status = OTA_COPY_FAIL;LOG("ota_copy fail \r\n");}}vTaskDelay(200);}
}/**
* @brief ota_copy
* @param None
* @retval None
*/uint8_t ota_copy(uint32_t source_addr, uint32_t destination_addr, uint32_t len)
{HAL_StatusTypeDef status;/* 擦除APP区 */status = bsp_flash_Erase_Blank1_Flash(8, 120); /*0x08010000(page8) -- 0x08100000(page64)*/LOG("> Start erase APP flash success\r\n");/* 复制 */uint8_t tmp[1024] = {0}; //1k bytesfor(uint32_t i = 0; i < len/1024; i++){bsp_flash_Read_Flash(source_addr + i*1024, tmp, 1024);bsp_flash_Write_Flash(destination_addr + i*1024, tmp, 64);}LOG("> Start from STAGE copy code to APP success\r\n");/* 擦除Stage区 */status = bsp_flash_Erase_Blank2_Flash(0, 120); /*0x08100000 -- 0x081F0000*/LOG("> Start erase STAGE flash success\r\n");return (status);
}/**
* @brief ota_jumpApp
* @param None
* @retval None
*/
typedef void (*pFunction)(void);void ota_jumpApp (uint32_t app_addr)
{uint8_t temp_buff11[4];pFunction JumpToApplication;uint32_t JumpAddress;JumpAddress = *(__IO uint32_t *)(app_addr + 4);LOG(" APP:0x%x \r\n", app_addr);LOG(" DATA:0x%x \r\n", (*( __IO uint32_t *)app_addr));if(((*( __IO uint32_t *)app_addr) & 0x2FF00000) == 0x20000000) //检查栈顶地址是否合法.{SysTick->CTRL = 0; /*关键代码*/HAL_DeInit(); /*可选*/HAL_NVIC_DisableIRQ(SysTick_IRQn); /*可选*/HAL_NVIC_ClearPendingIRQ(SysTick_IRQn); /*可选*/LOG("APP jump start !\r\n");__disable_irq(); //disable all interruptJumpToApplication = (pFunction)JumpAddress; /*用户代码区第二个字为程序开始地址(复位地址)*/__set_MSP(*( __IO uint32_t *)app_addr); /*初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)*/JumpToApplication(); /*跳转到APP*/LOG("APP jump end !\r\n");}
}
九、STM32U575CIU6 平台实现低功耗
1、低功耗简介
按功耗由高到低排列,STM32 具有运行、睡眠、停止和待机四种工作模式。上电复位后 STM32
处于运行状态,当内核不需要继续运行,就可以选择进入后面的三种低功耗模式降低功耗,这三种模式中,电源消耗不同、唤醒时间不同、唤醒源不同,用户需要根据应用需求,选择最佳的低功耗模式。三种低功耗的模式说明如下图:
2、功耗现状
测试了副屏功耗,整体较高,具体功耗指标如下,需要进行优化。
- 固件全部擦除。副屏驱动板 暗电流 = 0.52mA。
- 使用旧固件(2022.11.17):显示MCU自己存储的图标,不通过XR2刷新。
不带屏 = 21.8mA;带屏亮度低(R37 = 51R) = 34.9mA;带屏最大亮度 = 65.6mA。 - 使用新固件(2023.01.18):主板XR2经MCU给副屏刷动画。
屏不亮 = 30mA;(屏占约7mA)
低亮度下(R37 = 51R) 刷动画 = 40mA;刷完动画 显示图标 = 35mA;
最大亮度下 刷动画 = 65.6mA;刷完动画 显示图标 = 60mA。 - 拆掉SPI FLASH,拆掉副屏,板子功耗0.47ma,SPI FLASH应该有50ua的功耗
- 拆掉SPI FLASH,拆掉副屏,拆掉MCU,板子功耗0.07ma
3、 优化方向
- 副屏暗电流0.52mA,查询硬件电路,优化暗电流,将0.52mA降到最低。
- 固件(带副屏)一跑起来就有30mA,查询固件中不需要的功能 及 IO口配置,降低不必要的耗电。
- 擦除MCU固件,带副屏/不带副屏,都是1mA左右。需要排查有固件时,MCU与DDIC和TP IC的通信是否有功耗。
- 有固件,带副屏(背光不亮,屏显示黑色)与不带副屏,功耗相差(27mA-20mA)7mA左右。硬件改版,屏的3.0V供电由MCU控制,可以优化7mA。
4、实现方式
/**
* @brief
* @param None
* @retval None
*/
void dev_enter_lowpower_mode(void)
{/*peripheral disable start*/WriteCommand(0x28);WriteCommand(0x10);HAL_ADC_MspDeInit(&hadc1);HAL_CRC_MspDeInit(&hcrc);HAL_SPI_MspDeInit(&hspi2);HAL_SPI_MspDeInit(&hspi3);HAL_OSPI_MspDeInit(&hospi1);HAL_TIM_PWM_MspDeInit(&htim2);HAL_RTC_MspDeInit(&hrtc);__HAL_RCC_GPDMA1_CLK_DISABLE();// HAL_PWREx_DisableVddA(); /*关闭ADC电源*/
// HAL_PWREx_DisableVddIO2();MX_GPIO_Low_Power();//HAL_FLASHEx_EnablePowerDown(FLASH_BANK_1);__HAL_RCC_GPIOC_CLK_DISABLE();__HAL_RCC_GPIOH_CLK_DISABLE();__HAL_RCC_GPIOA_CLK_DISABLE();__HAL_RCC_GPIOB_CLK_DISABLE();/*peripheral disable stop*/__HAL_RCC_LSI_DISABLE();__HAL_RCC_HSI_DISABLE();/* USER CODE BEGIN MspInit 1 *//* Enter the system to STOP2 mode */HAL_SuspendTick();__HAL_RCC_PWR_CLK_ENABLE();HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
}void HAL_System_SuspendTick(void)
{/* Disable SysTick Interrupt */SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
}/**
* @brief
* @param None
* @retval None
*/
void HAL_System_ResumeTick(void)
{/* Enable SysTick Interrupt */SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
}
十、STM32U575CIU6 平台实现触控功能
在移植好的触摸芯片驱动前提下,采用软件定时器实现触控功能。
aa: 在副屏待机息屏状态下,触控生效,其他条件触控无效. bb: 触摸屏幕,屏幕点亮,显示对应图片,5S后自动熄灭. cc:
副屏切换到唤醒状态,触控失效.
#include "main.h"TimerHandle_t xAutoReloadTimer;
SemaphoreHandle_t xBinarySemaphore_touch;
uint8_t screen_backlight_status = 0;
volatile uint8_t first_waken_flag = 0;static void touch_load_timer_Func(TimerHandle_t xTimer);
/**
* @brief vDisplay_Task(void)
* @param None
* @retval None
*/
void vTouch_Task(void const *argument)
{static uint8_t soft_timer_status = 0;xBinarySemaphore_touch = xSemaphoreCreateBinary();xAutoReloadTimer = xTimerCreate("AutoReload", /* 名字, 不重要 */mainAUTO_RELOAD_TIMER_PERIOD, /* 周期 */pdTRUE, /* 自动加载 */0, /* ID */touch_load_timer_Func /* 回调函数 */);for (;;){if (soft_timer_status == 1 && system_status != DF_CMD_SPI_POWER_STANDBY && system_status != DF_CMD_SPI_POWER_SHUTDOWN){/* 停止软件定时器 */soft_timer_status = 0;xTimerStop(xAutoReloadTimer, 0);LOG(LEVEL_DEBUG, "stop soft timer.");}if (xSemaphoreTake(xBinarySemaphore_touch, pdMS_TO_TICKS(100)) == pdTRUE){if (touch_flag){//first_waken_flag = 1;lcdSetBrightness(100); /*打开屏幕背光*/LOG(LEVEL_DEBUG, "Turn on screen backlight");if (xAutoReloadTimer){/* 启动软件定时器 */soft_timer_status = 1;screen_backlight_status = 1;xTimerStart(xAutoReloadTimer, 0);LOG(LEVEL_DEBUG, "start soft timer.");first_waken_flag = 1;}vTaskDelay(300);touch_flag = 0;}}}
}/**
* @brief vDisplay_Task(void)
* @param None
* @retval None
*/
static void touch_load_timer_Func(TimerHandle_t xTimer)
{if (system_status == DF_CMD_SPI_POWER_SHUTDOWN){if (screen_backlight_status){screen_backlight_status = 0;lcdSetBrightness(0); /*熄灭屏幕背光*/LOG(LEVEL_DEBUG, "Power_down_Turn off the screen backlight.");dev_enter_lowpower_mode();first_waken_flag = 0;power_down_awaken_flag = 1;}}else{if (screen_backlight_status){screen_backlight_status = 0;lcdSetBrightness(0); /*熄灭屏幕背光*/LOG(LEVEL_DEBUG, "Power_standby_Turn off the screen backlight.");}}}
十一、STM32U575CIU6 平台实现电量采集及电量显示
void vCollect_battery_Task(void const *argument) /*关机未充电量任务 任务优先级 3*/
{uint8_t power_down_display_battery_flag = 0;uint8_t battery_voltage_index = 0;uint32_t battery_voltage = 0;int32_t real_voltage = 0;float real_voltage_make_judge = 0;float real_voltage_cal = 0;float battery_voltage_sum = 0;for (;;){if (HAL_ADC_Start(&hadc1) != HAL_OK){Error_Handler();}if (HAL_ADC_PollForConversion(&hadc1, 5) != HAL_OK){Error_Handler();}battery_voltage = HAL_ADC_GetValue(&hadc1);real_voltage = __HAL_ADC_CALC_DATA_TO_VOLTAGE(hadc1.Instance, VDDA_APPLI, battery_voltage, \ADC_RESOLUTION_14B);real_voltage_cal = (real_voltage / 1000.0);//LOG(LEVEL_DEBUG, "real_voltage:%d real_voltage_cal:%.2f ",real_voltage, real_voltage_cal);battery_voltage_sum += real_voltage_cal;//LOG(LEVEL_DEBUG, "battery_voltage_sum:%.2f ", battery_voltage_sum);battery_voltage_index++;if (battery_voltage_index == 20){real_voltage_make_judge = battery_voltage_sum / 20.0 * 2.65;LOG(LEVEL_DEBUG, "real_voltage_make_judge:%.2f ", real_voltage_make_judge);battery_voltage_sum = 0;battery_voltage_index = 0;power_down_display_battery_flag = 1;}if (power_down_display_battery_flag == 1) {power_down_display_battery_flag = 0;if (real_voltage_make_judge < POWER_FIRST_GEAR) /*电量0-20%*/{lcd_display_const_picture(0, 0, PIXEL_SIZE, PIXEL_SIZE, gImage_20);}else if (real_voltage_make_judge >= POWER_FIRST_GEAR && real_voltage_make_judge < POWER_SECOND_GEAR) /*电量20-40%*/{lcd_display_const_picture(0, 0, PIXEL_SIZE, PIXEL_SIZE, gImage_40);}else if (real_voltage_make_judge >= POWER_SECOND_GEAR && real_voltage_make_judge < POWER_THIRD_GEAR) /*电量40-60%*/{lcd_display_const_picture(0, 0, PIXEL_SIZE, PIXEL_SIZE, gImage_60);}else if (real_voltage_make_judge >= POWER_THIRD_GEAR && real_voltage_make_judge < POWER_FOURTH_GEAR) /*电量60-80%*/{lcd_display_const_picture(0, 0, PIXEL_SIZE, PIXEL_SIZE, gImage_80);}else if (real_voltage_make_judge >= POWER_FOURTH_GEAR) /*电量80-100%*/{lcd_display_const_picture(0, 0, PIXEL_SIZE, PIXEL_SIZE, gImage_100);}}vTaskDelay(20);}
}
##更新版本总结
##优化方向