SPI通讯的数据交互及图片显示

这篇具有很好参考价值的文章主要介绍了SPI通讯的数据交互及图片显示。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

这个项目耗时三个月,前两个月攻克技术难关,后一个月进行功能联调,也是我很长时间没有更新的原因。一个项目从初期的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内核的支持包
freertos spi通信,stm32,嵌入式,通信,单片机,stm32
freertos spi通信,stm32,嵌入式,通信,单片机,stm32

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一直为低电平。

freertos spi通信,stm32,嵌入式,通信,单片机,stm32
freertos spi通信,stm32,嵌入式,通信,单片机,stm32
freertos spi通信,stm32,嵌入式,通信,单片机,stm32
freertos spi通信,stm32,嵌入式,通信,单片机,stm32
freertos spi通信,stm32,嵌入式,通信,单片机,stm32
安卓测试工具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,这对于稳定性和准确性要求还是很高的,所以协议的制定必须考虑多种情况,降低出错率,增加容错机制。

freertos spi通信,stm32,嵌入式,通信,单片机,stm32

freertos spi通信,stm32,嵌入式,通信,单片机,stm32

七、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,用于区分当前包和下一包的帧类型。
freertos spi通信,stm32,嵌入式,通信,单片机,stm32

freertos spi通信,stm32,嵌入式,通信,单片机,stm32
freertos spi通信,stm32,嵌入式,通信,单片机,stm32

      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
处于运行状态,当内核不需要继续运行,就可以选择进入后面的三种低功耗模式降低功耗,这三种模式中,电源消耗不同、唤醒时间不同、唤醒源不同,用户需要根据应用需求,选择最佳的低功耗模式。三种低功耗的模式说明如下图:

freertos spi通信,stm32,嵌入式,通信,单片机,stm32

2、功耗现状

测试了副屏功耗,整体较高,具体功耗指标如下,需要进行优化。

  1. 固件全部擦除。副屏驱动板 暗电流 = 0.52mA。
  2. 使用旧固件(2022.11.17):显示MCU自己存储的图标,不通过XR2刷新。
    不带屏 = 21.8mA;带屏亮度低(R37 = 51R) = 34.9mA;带屏最大亮度 = 65.6mA。
  3. 使用新固件(2023.01.18):主板XR2经MCU给副屏刷动画。
    屏不亮 = 30mA;(屏占约7mA)
    低亮度下(R37 = 51R) 刷动画 = 40mA;刷完动画 显示图标 = 35mA;
    最大亮度下 刷动画 = 65.6mA;刷完动画 显示图标 = 60mA。
  4. 拆掉SPI FLASH,拆掉副屏,板子功耗0.47ma,SPI FLASH应该有50ua的功耗
  5. 拆掉SPI FLASH,拆掉副屏,拆掉MCU,板子功耗0.07ma
3、 优化方向
  1. 副屏暗电流0.52mA,查询硬件电路,优化暗电流,将0.52mA降到最低。
  2. 固件(带副屏)一跑起来就有30mA,查询固件中不需要的功能 及 IO口配置,降低不必要的耗电。
  3. 擦除MCU固件,带副屏/不带副屏,都是1mA左右。需要排查有固件时,MCU与DDIC和TP IC的通信是否有功耗。
  4. 有固件,带副屏(背光不亮,屏显示黑色)与不带副屏,功耗相差(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);
  }
}

##更新版本总结
##优化方向文章来源地址https://www.toymoban.com/news/detail-723508.html

到了这里,关于SPI通讯的数据交互及图片显示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SPI通信协议&SPI通信外设

    目录 SPI 介绍 硬件电路 移位示意图  软件SPI SPI时序基本单元  SPI时序​编辑 W25Q64  硬件电路 W25Q64框图 Flash操作注意事项 指令集  硬件SPI SPI框图  主模式全双工连续传输 非连续传输  推挽输出 ,高低电平都有很强的驱动能力,使得上升沿和下降沿都很迅速。   当ss为高电

    2024年02月10日
    浏览(47)
  • 学习笔记之FPGA的SPI通讯

          FPGA由于 FPGA 是基于 SRAM 结构的,程序掉电后会丢失,所以需要一个外置 Flash 保存程序, FPGA 每次上电后去读取 Flash 中的配置程序,在 ALINX 开发板中,很多使用的是 SPI 接口的 nor flash,这种 flash 只需要 4 根 IO。FPGA 的配置 flash 是特殊的 IO,上电时工作,FPGA 要使用这些

    2024年04月17日
    浏览(28)
  • STM32—SPI详解入门(使用SPI通讯读写W25Q128模块)

    目录 一、SPI是什么 二、SPI物理架构 三、SPI工作原理 四、SPI工作模式 五、SPI相关寄存器介绍 六、SPI用到的结构体与函数 1.结构体 2.函数 七、W25Q128芯片 1.W25Q128介绍 2.W25Q128存储架构 3.W25Q128常用指令 4.W25Q128状态寄存器 5.W25Q128常见操作流程 八、实验(使用SPI通讯读写W25Q128模块

    2024年02月14日
    浏览(50)
  • SPI通信协议及基于Arduino的SPI通信例程

    一、SPI通信协议介绍 SPI通信协议(Serial Peripheral Interface)是一种同步串行通信协议,由Motorola公司在1980年代初开发。SPI协议常用于单片机、嵌入式系统和外围设备之间的通信。 SPI协议使用四根线进行通信:时钟线(SCLK)、数据输入线(MOSI)、数据输出线(MISO)和从机选择

    2024年02月07日
    浏览(39)
  • 【SPI】STM32 SPI 双机通信,SPI从机模式使用

    最近要用到STM32的SPI从机模式,从其他板子读SPI数据过来,踩了2天坑,记录一下过程。 (因为hal库提供了三种函数,我调试的过程是阻塞、中断、DMA依次来调试学习的, 这份代码为使用DMA方式的代码。 ) 软件 :keil5、STM32CubeMX 硬件 :两块STM32F103C8T6最小系统 实现功能 :两

    2024年02月17日
    浏览(51)
  • stm32 AD7606 芯片驱动 hal库 spi通讯

             这两天用来个ad7606 的芯片,结果硬件出来个问题,花了不少时间看这个芯片手册,干脆分享一下。         OS0 OS1 OS2         这个三个引脚用于配置芯片的采样频率,只要不设置为111即可正常采样; CONVSTA CONVSTB 这两个引脚用于启动芯片采集转换,默认高电平,

    2024年04月26日
    浏览(38)
  • 【STM32学习】——SPI通信协议&SPI时序&W25Q64存储芯片&软件SPI读写

    目录 前言 一、SPI通信协议 1.概述​ 2.硬件电路  3.移位示意图 二、SPI时序 1.时序基本单元 2.完整时序波形 三、W25Q64存储芯片 1.芯片简介  2.硬件电路引脚定义  3.芯片框图 4.Flash操作注意事项 四、软件SPI读写W25Q64 五、SPI通信外设 总结 声明:学习笔记来自江科大自化协B站教

    2024年02月09日
    浏览(58)
  • SPI通信协议

    本文重点介绍的是SPI通信协议,内容是来自野火视频教程,本人只是做一些简单的记录和总结。 `

    2024年02月06日
    浏览(38)
  • STM32——SPI通信

    SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线 四根通信线: SCK(Serial Clock)【CLK或SCL或CK】、 MOSI(Master Output Slave Input)【DO(Data Output)】、 MISO(Master Input Slave Output)【DI(Data Input)】、 SS(Slave Select)【CS或NSS】 同步,全双工 支持总线挂载多设备

    2024年02月11日
    浏览(36)
  • SPI通信说明

    本文详细介绍SPI的通信原理和特点,主要用途为学习。 SPI是一种高速、全双工、同步通信总线,所以可以在同一时间发送和接收数据,SPI没有定义速度限制,通常能达到甚至超过10M/bps。 SPI是有主从机设备的,主机只允许有一个,从机可以有多个。 SPI通信时需要用到四根线,

    2024年02月15日
    浏览(7)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包