这个项目耗时三个月,前两个月攻克技术难关,后一个月进行功能联调,也是我很长时间没有更新的原因。一个项目从初期的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 true
typedef 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 2016
uint8_t month; // 0-11
uint8_t day; // 0-30
uint8_t second; // 0-59
uint8_t minute; // 0-59
uint8_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)
#endif
void 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.1
1、主机留意 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 -p
spitest -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 bytes
for(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 interrupt
JumpToApplication = (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:
副屏切换到唤醒状态,触控失效.文章来源:https://www.toymoban.com/news/detail-723508.html
#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);
}
}
##更新版本总结
##优化方向文章来源地址https://www.toymoban.com/news/detail-723508.html
到了这里,关于SPI通讯的数据交互及图片显示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!