Bootloader+IAP
简要说明
本学习笔记主要包括STM32F103C8T6 下的Bootloader+IAP
Bootloader :用于更新APP的程序
IAP :在设备运行时,由Bootloader引导对自身程序擦写
OTA :无线通信将新的固件下载到设备中(Over The Air)
说明
使用的APP数据,OTA标志位,版本号存储在AT24C02
多个备份APP数据存储在W25Q64
Q:已经操作了AT24C02和W25Q64,为什么还要操作单片机上的SRAM?
AT24C02和Flash读写太慢,跟不上CPU的速度,SRAM是临时、高速的数据交换区 。
整体流程图
这里说明下没有Running APP那一环,Visio源文件找不到了
分区
A区存放APP,B区存放Bootloader程序。OTA_Flag表示是否更新APP,存放在AT24C02中
❌️ | A | B | ------>程序运行后,先进入A区;若OTA_Flag=1开始更新数据,当正在更新的A区出现异常退出,下一次上电后运行A发现程序不全,无法再进入B区进行后续更新A区的操作,并且之前的A区程序有出现问题,悲报,成砖头了 。
✔️ | B | A | ------>程序运行后,先进入B区,观测到OTA_Flag=1后对A区进行数据更新,即使异常退出,后续仍然可以进入B区对A区进行更新,更新完成后置OTA_Flag=0。
方案
🦝DMA + 空闲中断
在无需CPU帮助下,DMA 负责外设与寄存器之间数据传输
空闲中断 (IDLE)是串口通信中判断“接收完成”的经典方式
Q:如何消除空闲中断?
读一次标志位寄存器,再读一次数据寄存器,即可消除空闲中断
环形缓冲区 :数据传输 过程中,接收速度和处理速度可能不一致 ,因此需要缓冲区先保存数据,防止数据丢失 ;一维数组,确认单次接收最大量 ,防止出现越界问题
Q:如何判断Read和Write的状态问题?
预留一个空位。Read == Write,即空;(Write + 1)% N == R,即满;
通过一个count计数,W是生产者,R是消费者;每次W就count+1,每次R就count-1;0<=count<=Max_Count。
SE指针对(环形缓冲) :缓冲区是一维数组,总长度为 2048 Byte,划分为10个数据块,每个数据块200 Byte。通过两个指针 IN(生产者指针)和 OUT(消费者指针)管理缓冲区的读写操作。当 IN ≠ OUT 时,表示缓冲区中存在待处理的数据包。指针每次移动一个数据块(200字节),当指针到达缓冲区末尾时自动回卷到起始位置,实现环形缓冲机制。
❗TIP:每次接收后,都需要判断剩余空间 ,以防内存不足,及时回卷;需要记录已经存放的累加值
编程
STM32F103C8T6使用外部高速时钟HSE,8MHz;通过PLL(倍频锁相环)可以达到72MHz
串口与DMA
.h程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #define URx_SIZE 2048 #define UTx_SIZE 2048 #define URx_MAX 200 #define NUM 10 uint8_t URx_Buff[URx_SIZE];uint8_t UTx_Buff[UTx_SIZE];typedef struct { uint8_t *start; uint8_t *end; }UCB_URxBuff; typedef struct { uint16_t URxCounter; UCB_URxBuff URxDataPtr[NUM]; UCB_URxBuff *URxDataIN; UCB_URxBuff *URxDataOUT; UCB_URxBuff *URxDataEND; }UCB_CB; extern UCB_CB UCB;extern uint8_t URx_Buff[URx_SIZE];
配置串口和DMA
设置相关外设时钟
初始化GPIO(IO分区,MODE,频率,IO口)
配置串口(初始化,波特率,校验,数据位长度,停止位个数,接收发情况)
配置串口DMA
打开串口中断(IDLE为空闲中断)
调用其他功能函数
使能串口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 void RCC_USART_Init (void ) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); } void GPIO_USART_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } void NVIC_USART_Init (void ) { NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0 ; NVIC_Init(&NVIC_InitStructure); } void DMA_USART_Init (void ) { DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_BufferSize = URx_MAX + 1 ; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t )URx_Buff; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t )&USART1->DR; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_Init(DMA1_Channel5, &DMA_InitStructure); DMA_Cmd(DMA1_Channel5, ENABLE); } void USART1_Init (uint32_t band) { RCC_USART_Init(); GPIO_USART_Init(); USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = band; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_Init(USART1, &USART_InitStructure); USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); NVIC_USART_Init(); DMA_USART_Init(); UCB.URxCounter = 0 ; UCB.URxDataIN = &UCB.URxDataPtr[0 ]; UCB.URxDataOUT = &UCB.URxDataPtr[0 ]; UCB.URxDataEND = &UCB.URxDataPtr[NUM - 1 ]; UCB.URxDataIN->start = URx_Buff; USART_Cmd(USART1, ENABLE); }
串口空闲中断函数
检测空闲中断
消除空闲中断标志位 (只有读标志位寄存器和数据位寄存器才能将空闲标志位清除)
在空闲中断中读取串口DMA增添数据的长度 (DMA,通道) — counter += (总量 - 剩余空闲值)
设置缓冲区的IN指针
Disable DMA,重新配置DMA(DMA,通道,大小,地址),保证不出现完成状态,再使能DMA
要一直让DMA读取数据,直到出现空闲中断才会停止,因此不会出现完成状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 void USART1_IRQHandler (void ) { if (USART_GetFlagStatus(USART1, USART_FLAG_IDLE) != 0 ) { USART_ClearFlag(USART1, USART_FLAG_IDLE); USART_ReceiveData(USART1); UCB.URxCounter += (URx_MAX + 1 ) - DMA_GetCurrDataCounter(DMA1_Channel5); UCB.URxDataIN->end = &URx_Buff[UCB.URxCounter - 1 ]; UCB.URxDataIN++; if (UCB.URxDataIN == UCB.URxDataEND) { UCB.URxDataIN = &UCB.URxDataPtr[0 ]; } if (URx_SIZE - UCB.URxCounter >= URx_MAX) { UCB.URxDataIN->start = &URx_Buff[UCB.URxCounter]; } else { UCB.URxDataIN->start = URx_Buff; UCB.URxCounter = 0 ; } DMA_Cmd(DMA1_Channel5, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel5, URx_MAX + 1 ); DMA1_Channel5->CMAR = (uint32_t )UCB.URxDataIN->start; DMA_Cmd(DMA1_Channel5, ENABLE); } }
编写新的Printf函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void uprintf (char *format, ...) { va_list list_data; va_start(list_data, format); vsnprintf((char *)UTx_Buff, sizeof (UTx_Buff), format, list_data); va_end(list_data); uint16_t len = strlen ((const char *)UTx_Buff); for (uint16_t i = 0 ; i < len; i++) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) != 1 ); USART_SendData(USART1, UTx_Buff[i]); } while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != 1 ); }
I2C
软件I2C ,延时部分需要自己重新设计
.h程序
1 2 3 4 5 6 7 #define SCL_H GPIO_SetBits(GPIOB, GPIO_Pin_3) #define SCL_L GPIO_ResetBits(GPIOB, GPIO_Pin_3) #define SDA_H GPIO_SetBits(GPIOB, GPIO_Pin_4) #define SDA_L GPIO_ResetBits(GPIOB, GPIO_Pin_4) #define Read_SDA GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4)
Delay
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void delay_init (void ) { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); } void delay_us (uint16_t us) { SysTick_Config(SystemCoreClock / 1000000 ); while (us--) { while (!((SysTick->CTRL) & (SysTick_CTRL_COUNTFLAG))); } SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; } void delay_ms (uint16_t ms) { SysTick_Config(SystemCoreClock / 1000 ); while (ms--) { while (!((SysTick->CTRL) & (SysTick_CTRL_COUNTFLAG))); } SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; }
初始化、START和STOP信号
硬件I2C,开启时钟
配置GPIO (PB6,PB7,开漏模式)
配置SCL和SDA 两条线
起始信号 :SCL高电平,SDA从高电平变为低电平
停止信号 :SCL高电平,SDA从低电平变为高电平
数据信号 :SCL高电平,SDA保持不变;SCL低电平,SDA电平可以修改
应答信号 :SCL高电平,SDA低电平 ,应答 ,否则,非应答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 void I2C1_Init (void ) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); SCL_H; SDA_H; } void I2C_Start (void ) { SCL_H; SDA_H; delay_us(2 ); SDA_L; SCL_L; } void I2C_Stop (void ) { SCL_L; SDA_L; delay_us(2 ); SCL_H; SDA_H; }
发送一个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void I2C_SendByte (uint8_t tx) { for (int8_t i = 7 ; i >= 0 ; i--) { SCL_L; if (tx & (1 << i)) { SDA_H; } else { SDA_L; } delay_us(2 ); SCL_H; delay_us(2 ); } SCL_L; SDA_H; }
读取一个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 uint8_t I2C_ReadByte (uint8_t ack) { uint8_t rx = 0 ; for (int8_t i = 7 ; i >= 0 ; i--) { SCL_L; delay_us(2 ); SCL_H; if (Read_SDA) { rx |= (1 << i); } delay_us(2 ); } SCL_L; if (ack) { SDA_L; SCL_H; delay_us(2 ); SCL_L; SDA_H; } else { SDA_H; SCL_H; delay_us(2 ); SCL_L; } return rx; }
等待从机ACK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 uint8_t I2C_Wait_ACK (int16_t timeout) { do { timeout--; delay_us(2 ); }while (Read_SDA && timeout >= 0 ); if (timeout < 0 ) { return 1 ; } SCL_H; delay_us(2 ); if (Read_SDA != 0 ) { return 2 ; } SCL_L; delay_us(2 ); return 0 ; }
AT24C02
I2C通信 ,设备地址:1 0 1 0 E2 E1 E0 R/非W;
读R=1,0XA1;
写W=0,0XA0;
按字节写入
1 2 #define AT24C02_WADDR 0xA0 #define AT24C02_RADDR 0xA1
AT24C02共32页 ,一页8字节 ,共256字节;存在回卷问题
写入
Byte Write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 uint8_t AT24C02_WriteByte (uint8_t addr, uint8_t wdata) { I2C_Start(); I2C_SendByte(AT24C02_WADDR); if (I2C_Wait_ACK(100 ) != 0 ) return 1 ; I2C_SendByte(addr); if (I2C_Wait_ACK(100 ) != 0 ) return 2 ; I2C_SendByte(wdata); if (I2C_Wait_ACK(100 ) != 0 ) return 2 ; I2C_Stop(); return 0 ; }
Page Write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 uint8_t AT24C02_WritePage (uint8_t addr, uint8_t *wdata) { I2C_Start(); I2C_SendByte(AT24C02_WADDR); if (I2C_Wait_ACK(100 ) != 0 ) return 1 ; I2C_SendByte(addr); if (I2C_Wait_ACK(100 ) != 0 ) return 2 ; for (uint8_t i = 0 ; i < 8 ; i++) { I2C_SendByte(wdata[i]); if (I2C_Wait_ACK(100 ) != 0 ) return 3 + i; } I2C_Stop(); return 0 ; }
读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 uint8_t AT24C02_ReadData (uint8_t addr, uint8_t *rdata, uint16_t datalen) { I2C_Start(); I2C_SendByte(AT24C02_WADDR); if (I2C_Wait_ACK(100 ) != 0 ) return 1 ; I2C_SendByte(addr); if (I2C_Wait_ACK(100 ) != 0 ) return 2 ; I2C_Start(); I2C_SendByte(AT24C02_RADDR); if (I2C_Wait_ACK(100 ) != 0 ) return 3 ; for (uint8_t i = 0 ; i < datalen - 1 ; i++) { rdata[i] = I2C_ReadByte(1 ); } rdata[datalen - 1 ] = I2C_ReadByte(0 ); I2C_Stop(); return 0 ; }
SPI
外部FLASH型号为W25Q64,使用硬件SPI
配置SPI0时钟 ,相关GPIO开启
复位SPI外设
配置SPI结构体成员(主从模式,发送类型(全双工),一帧大小,硬件/软件,大端/小端(大端),工作方式(极性(上下)/相位(一二),从机决定),传输速度)
初始化SPI
使能SPI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 void SPI1_Init (void ) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitTypeDef GPIO_StructInit; GPIO_StructInit.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_StructInit.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_StructInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_StructInit); GPIO_StructInit.GPIO_Pin = GPIO_Pin_6; GPIO_StructInit.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_StructInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_StructInit); SPI_I2S_DeInit(SPI1); SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7 ; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }
数据收发
SPI_I2S_FLAG_TXE 表示发送区空了 ,SPI_SPI_FLAG_RXNE 表示接收区不为空 。切记全双工,有发就有收!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 uint16_t SPI1_ReadWrite_Byte (uint16_t tx) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != 1 ); SPI_I2S_SendData(SPI1, tx); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != 1 ); return SPI_I2S_ReceiveData(SPI1); } void SPI1_Write (uint8_t *wdata, uint16_t datalen) { uint16_t i; for (i = 0 ; i < datalen; i++) { SPI1_ReadWrite_Byte(wdata[i]); } } void SPI1_Read (uint8_t *rdata, uint16_t datalen) { uint16_t i; for (i = 0 ; i < datalen; i++) { rdata[i] = SPI1_ReadWrite_Byte(0xff ); } }
W25Q64
状态寄存器
主要看S0 — BUSY
指令表
相关程序
SPI+Flash需等待Busy状态 ,先读取状态寄存器地址,后随便写入其它(此时写入什么都不重要,主要获取寄存器中的数值),打开片选,使能Write再关闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #define CS_ENABLE GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define CS_DISABLE GPIO_SetBits(GPIOA, GPIO_Pin_4) void W25Q64_Init (void ) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_StructInit; GPIO_StructInit.GPIO_Pin = GPIO_Pin_4; GPIO_StructInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_StructInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_StructInit); CS_DISABLE; SPI1_Init(); } void W25Q64_WaitBusy (void ) { uint8_t res; do { CS_ENABLE; SPI1_ReadWrite_Byte(0x05 ); res = SPI1_ReadWrite_Byte(0xff ); CS_DISABLE; } while ((res & 0x01 ) == 0x01 ); } void W25Q64_Enable (void ) { W25Q64_WaitBusy(); CS_ENABLE; SPI1_ReadWrite_Byte(0x06 ); CS_DISABLE; }
擦除64KB
W25Q64总共8MB = 8 * 1024KB,一次擦除64KB ,可得共计8 * 1024 / 64 = 128个Block
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void W25Q64_Erase_64k (uint8_t block) { uint8_t wdata[4 ]; wdata[0 ] = 0xD8 ; wdata[1 ] = (block * 64 * 1024 ) >> 16 ; wdata[2 ] = (block * 64 * 1024 ) >> 8 ; wdata[3 ] = (block * 64 * 1024 ) >> 0 ; W25Q64_WaitBusy(); W25Q64_Enable(); CS_ENABLE; SPI1_Write(wdata, 4 ); CS_DISABLE; W25Q64_WaitBusy(); }
页写入256Byte
页地址 = page * 256 Byte,每次从页地址开始写入256 Byte,页地址总长24位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void W25Q64_PageWrite (uint8_t *wbuff, uint16_t page) { uint8_t wdata[4 ]; wdata[0 ] = 0x02 ; wdata[1 ] = (page * 256 ) >> 16 ; wdata[2 ] = (page * 256 ) >> 8 ; wdata[3 ] = (page * 256 ) >> 0 ; W25Q64_WaitBusy(); W25Q64_Enable(); CS_ENABLE; SPI1_Write(wdata, 4 ); SPI1_Write(wbuff, 256 ); CS_DISABLE; }
读取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void W25Q64_Read (uint8_t *rbuff, uint32_t addr, uint32_t datalen) { W25Q64_WaitBusy(); CS_ENABLE; SPI1_ReadWrite_Byte(0x03 ); SPI1_ReadWrite_Byte((uint16_t )(addr >> 16 )); SPI1_ReadWrite_Byte((uint16_t )(addr >> 8 )); SPI1_ReadWrite_Byte((uint16_t )(addr >> 0 )); SPI1_Read(rbuff, datalen); CS_DISABLE; }
STM32的FLASH
Flash擦除后为0xFF
擦除Flash
Flash一页1KB ,下面函数实现一次擦除num页 数据
流程 :
Flash开锁
确认地址,擦除地址数据
Flash锁定
1 2 3 4 5 6 7 8 9 10 11 12 13 void FLASH_Erase (uint16_t start, uint16_t num) { uint16_t i; FLASH_Unlock(); for (i = 0 ; i < num; i++) { FLASH_ErasePage((FLASH_SADDR + start * 1024 ) + (1024 * i)); } FLASH_Lock(); }
写入Flash
一次写入num个4字节数据 ,地址自动递增
流程 :
Flash开锁
将数据写入对应地址
Flash锁定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void FLASH_Write (uint32_t saddr, uint32_t *wdata, uint32_t wnum) { FLASH_Unlock(); while (wnum) { FLASH_ProgramWord(saddr, *wdata); wnum -= 4 ; saddr += 4 ; wdata++; } FLASH_Lock(); }
Bootloader功能实现
AB分区规划
1个扇页1KB ,A区起始位置:0x 0800 5000, 单片机RAM位置:0x20000000 ~ 0x20004FFF
STM32F103C8T6
64KB
扇页
B区
20KB
0 ~ 19
A区
44KB
20 ~ 63
注意点
问题
解答
谁将OTA_Flag打勾?
A区负责控制,标志位存放在AT24C02
什么时候OTA_flag打勾
A区下载完毕之后
OTA时,最新版本的程序文件下载到哪?
分片下载,共计256片塞入W25Q64(一页256Byte,共256页)
OTA时,最新版本的程序文件如何下载?下载多少?
服务器下发程序大小,分片下载到W25Q64
下载多少这个变量用不用保存?
需要,保存到AT24C02之中
发生OTA事件时,B区如何更新A区
从W25Q64读取数据,写入A区Flash
OTA宏定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #define FLASH_SADDR 0x08000000 #define FLASH_PAGE_SIZE 1024 #define FLASH_NUM 64 #define FLASH_B_NUM 20 #define FLASH_A_NUM FLASH_NUM - FLASH_B_NUM #define FLASH_A_SPAGE FLASH_B_NUM #define FLASH_A_SADDR FLASH_SADDR + FLASH_A_SPAGE *FLASH_PAGE_SIZE #define UPDATA_A_FLAG 0x00000001 #define IAP_XMODEM_FLAG 0x00000002 #define IAP_XMODEMD_FLAG 0x00000004 #define SET_VERSION_FLAG 0x00000008 #define CMD_5_FLAG 0x000000010 #define CMD5_XMODEM_FLAG 0x000000020 #define CMD_6_FLAG 0x000000040 #define OTA_SET_FLAG 0xAABBCCDD typedef struct { uint32_t OTA_flag; uint32_t FireLen[11 ]; uint8_t OTA_Ver[32 ]; } OTA_CB; typedef struct { uint8_t UpDataBuff[FLASH_PAGE_SIZE]; uint32_t W25Q64_BlockNM; uint32_t XmodemTimer; uint32_t XmodemNB; uint32_t XmodemCRC; } UpData; extern OTA_CB OTA;extern UpData Updata_A;#define OTA_INFO_SIZE sizeof(OTA_CB)
OTA读取标志位
1 2 3 4 5 6 7 void AT24C02_ReadOTA (void ) { memset (&OTA, 0 , OTA_INFO_SIZE); AT24C02_ReadData(0 , (uint8_t *)&OTA, OTA_INFO_SIZE); }
OTA保存关键变量到AT24C02
1 2 3 4 5 6 7 8 9 10 11 void AT24C02_WriteOTA (void ) { uint8_t *pdata = (uint8_t *)&OTA; for (uint8_t i = 0 ; i < (OTA_INFO_SIZE + 7 ) / 8 ; i++) { AT24C02_WritePage(i * 8 , pdata + i * 8 ); delay_ms(10 ); } }
引导更新OTA
(从下面向上看)
分区跳转 两大关键SP、PC 设定
Cortex-M3有R0-R12通用寄存器,R13有MSP(主堆栈指针)和PSP(进程堆栈指针)(保存现场和恢复现场的指针),R14是LR(连接寄存器,保存子函数之间跳转的返回值),R15是PC(程序计数器)
Q:MSP指针和PSP指针,分别在什么情况下使用?
MSP (Main Stack Pointer)
系统默认的栈指针。
上电复位后,CPU 自动把 MSP 当栈指针 。
通常用来处理 异常 / 中断 / 内核级任务 。
PSP (Process Stack Pointer)
需要软件设置(CONTROL 寄存器里切换)。
通常用来跑 用户线程 / 普通任务 。(FreeRTOS里的每个任务栈)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #define FLASH_A_SADDR FLASH_SADDR + FLASH_A_SPAGE * FLASH_PAGE_SIZE typedef void (*load_a) (void ) ; load_a load_A; __ASM void MSR_SP (uint32_t addr) { MSR MSP, r0; BX r14; } void LOAD_A (uint32_t addr) { if ((*(uint32_t *)addr >= 0x20000000 ) && (*(uint32_t *)addr <= 0x20004FFF )) { MSR_SP(*(uint32_t *)addr); load_A = (load_a) * (uint32_t *)(addr + 4 ); load_A(); } else { uprintf("跳转A分区失败\r\n" ); } } void BootLoader_Jump (void ) { if (BootLoader_Enter(20 ) == 0 ) { if (OTA.OTA_flag == OTA_SET_FLAG) { uprintf("OTA更新 \r\n" ); BootState |= UPDATA_A_FLAG; UpDATA_A.W25Q64_BlockNM = 0 ; } else { uprintf("跳转A分区 \r\n" ); LOAD_A(FLASH_A_SADDR); } } uprintf("进入BootLoader命令行 \r\n" ); BootLoader_Info(); } void BootLoader_Clear (void ) { USART_DeInit(USART1); GPIO_DeInit(GPIOA); GPIO_DeInit(GPIOB); }
BootLoader事件
(从上面向下看)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 uint32_t BootState; uint8_t wdata[2048 ];uint8_t rdata[2048 ];void BootLoader_Info (void ) { uprintf(" \r\n" ); uprintf("[1]擦除A区 \r\n" ); uprintf("[2]串口IAP下载A区 \r\n" ); uprintf("[3]设置OTA版本号 \r\n" ); uprintf("[4]查询OTA版本号 \r\n" ); uprintf("[5]向外部FLASH下载程序 \r\n" ); uprintf("[6]使用外部FLASH内程序 \r\n" ); uprintf("[7]重启 \r\n" ); } uint8_t BootLoader_Enter (uint8_t timeout) { uprintf("输入小写字母w,进入BootLoader命令行 \r\n" ); while (timeout--) { Delay_ms(200 ); if (U1_RX_Buff[0 ] == 'w' ) { return 1 ; } } return 0 ; } void BootLoader_Event (uint8_t *data, uint16_t datalen) { int temp, i; if (BootState == 0 ) { if ((datalen == 1 ) && (data[0 ] == '1' )) { uprintf("[1]擦除A区 \r\n" ); FLASH_Erase(FLASH_A_SPAGE, FLASH_A_NUM); Delay_ms(100 ); BootLoader_Info(); } else if ((datalen == 1 ) && (data[0 ] == '2' )) { uprintf("通过Xmodem协议,串口IAP下载A区程序,使用bin文件 \r\n" ); FLASH_Erase(FLASH_A_SPAGE, FLASH_A_NUM); BootState |= (IAP_XMODEM_FLAG | IAP_XMODEMD_FLAG); UpDATA_A.XmodemTimer = 0 ; UpDATA_A.XmodemNB = 0 ; } else if ((datalen == 1 ) && (data[0 ] == '3' )) { uprintf("设置版本号 \r\n" ); BootState |= SET_VERSION_FLAG; } else if ((datalen == 1 ) && (data[0 ] == '4' )) { uprintf("查询版本号 \r\n" ); AT24C02_ReadOTA(); uprintf("版本号:%s \r\n" , OTA.OTA_Ver); } else if ((datalen == 1 ) && (data[0 ] == '5' )) { uprintf("向外部FLASH传输程序,输入需要使用的块编号(1~9) \r\n" ); BootState |= CMD_5_FLAG; } else if ((datalen == 1 ) && (data[0 ] == '6' )) { uprintf("使用外部FLASH下载程序,输入需要使用的块编号(1~9) \r\n" ); BootState |= CMD_6_FLAG; } else if ((datalen == 1 ) && (data[0 ] == '7' )) { uprintf("重启中 \r\n" ); __set_FAULTMASK(1 ); Delay_ms(100 ); NVIC_SystemReset(); } } else if (BootState & IAP_XMODEMD_FLAG) { if ((datalen == 133 ) && (data[0 ] == 0x01 )) { BootState &= ~IAP_XMODEM_FLAG; UpDATA_A.XmodemCRC = Xmodem_CRC16(&data[3 ], 128 ); if (UpDATA_A.XmodemCRC == data[131 ] * 256 + data[132 ]) { UpDATA_A.XmodemNB++; memcpy (&UpDATA_A.UpDataBuff[((UpDATA_A.XmodemNB - 1 ) % (FLASH_PAGE_SIZE / 128 )) * 128 ], &data[3 ], 128 ); if ((UpDATA_A.XmodemNB % (FLASH_PAGE_SIZE / 128 )) == 0 ) { if (BootState & CMD5_XMODEM_FLAG) { for (i = 0 ; i < 4 ; i++) { W25Q64_PageWrite(&UpDATA_A.UpDataBuff[i * 256 ], (UpDATA_A.XmodemNB / 8 - 1 ) * 4 + i + UpDATA_A.W25Q64_BlockNM * 64 * 4 ); } } else { FLASH_Write(FLASH_A_SADDR + ((UpDATA_A.XmodemNB / (FLASH_PAGE_SIZE / 128 )) - 1 ) * FLASH_PAGE_SIZE, (uint32_t *)UpDATA_A.UpDataBuff, FLASH_PAGE_SIZE); } } uprintf("\x06" ); } else { uprintf("\x15" ); } } if ((datalen == 1 ) && (data[0 ] == 0x04 )) { uprintf("\x06" ); if ((UpDATA_A.XmodemNB % (FLASH_PAGE_SIZE / 128 )) != 0 ) { if (BootState & CMD5_XMODEM_FLAG) { for (i = 0 ; i < 4 ; i++) { W25Q64_PageWrite(&UpDATA_A.UpDataBuff[i * 256 ], (UpDATA_A.XmodemNB / 8 ) * 4 + i + UpDATA_A.W25Q64_BlockNM * 64 * 4 ); } } else { FLASH_Write(FLASH_A_SADDR + ((UpDATA_A.XmodemNB / (FLASH_PAGE_SIZE / 128 ))) * FLASH_PAGE_SIZE, (uint32_t *)UpDATA_A.UpDataBuff, (UpDATA_A.XmodemNB % (FLASH_PAGE_SIZE / 128 )) * 128 ); } } BootState &= ~IAP_XMODEMD_FLAG; if (BootState & CMD5_XMODEM_FLAG) { BootState &= ~CMD5_XMODEM_FLAG; OTA.FireLen[UpDATA_A.W25Q64_BlockNM] = UpDATA_A.XmodemNB * 128 ; AT24C02_WriteOTA(); Delay_ms(100 ); BootLoader_Info(); } else { __set_FAULTMASK(1 ); Delay_ms(100 ); NVIC_SystemReset(); } } } else if (BootState & SET_VERSION_FLAG) { if (datalen <= 32 ) { if (sscanf ((const char *)data, "VER-%d.%d.%d-%d/%d/%d-%d:%d" , &temp, &temp, &temp, &temp, &temp, &temp, &temp, &temp) == 8 ) { memset (OTA.OTA_Ver, 0 , 32 ); memcpy (OTA.OTA_Ver, data, 32 ); AT24C02_WriteOTA(); uprintf("版本号正确 \r\n" ); BootLoader_Info(); BootState &= ~SET_VERSION_FLAG; } else { uprintf("版本号格式错误 \r\n" ); } } else { uprintf("版本号长度错误 \r\n" ); } } else if (BootState & CMD_5_FLAG) { if (datalen == 1 ) { if ((data[0 ] >= 0x31 ) && (data[0 ] <= 0x39 )) { UpDATA_A.W25Q64_BlockNM = data[0 ] - 0x30 ; BootState |= (IAP_XMODEM_FLAG | IAP_XMODEMD_FLAG | CMD5_XMODEM_FLAG); UpDATA_A.XmodemTimer = 0 ; UpDATA_A.XmodemNB = 0 ; OTA.FireLen[UpDATA_A.W25Q64_BlockNM] = 0 ; W25Q64_Erase_64k(UpDATA_A.W25Q64_BlockNM); uprintf("通过Xmodem协议,向外部FLASH第%d个块传输程序,使用bin文件 \r\n" , UpDATA_A.W25Q64_BlockNM); BootState &= ~CMD_5_FLAG; } else { uprintf("编号错误 \r\n" ); } } else { uprintf("数据长度错误 \r\n" ); } } else if (BootState & CMD_6_FLAG) { if (datalen == 1 ) { if ((data[0 ] >= 0x31 ) && (data[0 ] <= 0x39 )) { UpDATA_A.W25Q64_BlockNM = data[0 ] - 0x30 ; BootState |= UPDATA_A_FLAG; BootState &= ~CMD_6_FLAG; } else { uprintf("编号错误 \r\n" ); } } else { uprintf("长度错误 \r\n" ); } } }
主程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 uint32_t BootState; uint8_t wdata[2048 ];uint8_t rdata[2048 ];extern OTA_CB OTA;extern UpData UpDATA_A;extern uint32_t BootState;int main (void ) { uint8_t i; USART1_Init(921600 ); Delay_Init(); I2C1_Init(); AT24C02_ReadOTA(); W25Q64_Init(); BootLoader_Jump(); while (1 ) { Delay_ms(10 ); if (U1CB.URxDataOUT != U1CB.URxDataIN) { BootLoader_Event(U1CB.URxDataOUT->start, U1CB.URxDataOUT->end - U1CB.URxDataOUT->start + 1 ); U1CB.URxDataOUT++; if (U1CB.URxDataOUT == U1CB.URxDataEND) { U1CB.URxDataOUT = &U1CB.URxDataPtr[0 ]; } } if (BootState & IAP_XMODEM_FLAG) { if (UpDATA_A.XmodemTimer >= 100 ) { uprintf("C" ); UpDATA_A.XmodemTimer = 0 ; } UpDATA_A.XmodemTimer++; } if (BootState & UPDATA_A_FLAG) { uprintf("长度%d字节\r\n" , OTA.FireLen[UpDATA_A.W25Q64_BlockNM]); FLASH_Erase(FLASH_A_SPAGE, FLASH_A_NUM); uprintf("A区已擦除 \r\n" ); if (OTA.FireLen[UpDATA_A.W25Q64_BlockNM] % 4 == 0 ) { for (i = 0 ; i < (OTA.FireLen[UpDATA_A.W25Q64_BlockNM] / 1024 ); i++) { W25Q64_Read(UpDATA_A.UpDataBuff, i * FLASH_PAGE_SIZE + 64 * 1024 * UpDATA_A.W25Q64_BlockNM, FLASH_PAGE_SIZE); FLASH_Write(FLASH_A_SADDR + i * FLASH_PAGE_SIZE, (uint32_t *)UpDATA_A.UpDataBuff, FLASH_PAGE_SIZE); } if (OTA.FireLen[UpDATA_A.W25Q64_BlockNM] % 1024 != 0 ) { memset (UpDATA_A.UpDataBuff, 0 , FLASH_PAGE_SIZE); W25Q64_Read(UpDATA_A.UpDataBuff, i * FLASH_PAGE_SIZE + 64 * 1024 * UpDATA_A.W25Q64_BlockNM, OTA.FireLen[UpDATA_A.W25Q64_BlockNM] % 1024 ); FLASH_Write(FLASH_A_SADDR + i * FLASH_PAGE_SIZE, (uint32_t *)UpDATA_A.UpDataBuff, OTA.FireLen[UpDATA_A.W25Q64_BlockNM] % 1024 ); } if (UpDATA_A.W25Q64_BlockNM == 0 ) { OTA.OTA_flag = 0 ; AT24C02_WriteOTA(); } uprintf(" \r\nA区更新完毕 \r\n" ); __set_FAULTMASK(1 ); Delay_ms(100 ); NVIC_SystemReset(); } else { uprintf("长度错误\r\n" ); BootState &= ~UPDATA_A_FLAG; BootLoader_Info(); } } } }
Xmodem协议
Xmodem使用SecureCRTP 软件配置连接串口通信,它相较于Ymodem的区别是具有更小的Package长度
格式
Byte1
Byte2
Byte3
Byte4 ~ Byte131
Byte132 ~ Byte133
Start of Header(SOH)
Packet Number
~(Packet Number)
Pcacket Data
CRC16 Check
指令
位
指令
说明
SOH
0x01
128字节数据包帧头
STX
0x02
1024字节数据包帧头
EOT
0x04
结束传输
ACK
0x06
正确应答
NAK
0x15
错误应答,重传数据
CAN
0x18
取消传输
CTRLZ
0x1A
数据填充
HSC
0x43
握手
例子
1 2 3 4 5 6 7 8 9 10 ‘C’ SOH | 0x01 | 0xFE | Data[0 ~127 ] | CRC16 ACK(正确应答) SOH | 0x02 | 0xFD | Data[0 ~127 ] | CRC16 NAK(错误应答) SOH | 0x02 | 0xFD | Data[0 ~127 ] | CRC16 ACK SOH | 0x03 | 0xFC | Data[0 ~127 ] | CRC16 ......
CRC16程序
STM32支持CRC32,不支持CRC16,需要自己写
多项式p(x) = x^16 + x^12 + x^5 + 1 ,借助多项式将输入的数值进行模2除法 ,在C语言中是进行异或运算 ^。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 寄存器清零 数据最右边补齐W位0 when(还有数据){ 左移寄存器1 位,读取数据的下一位到寄存器的bit 0 if (左移寄存器时出现溢出){ 寄存器 ^= poly; } } 寄存器的值就是校验值 uint16_t Xmodem_CRC16 (uint8_t *data, uint16_t datalen) { uint8_t i; uint16_t Crcinit = 0x0000 ; uint16_t Poly = 0x1021 ; while (datalen--) { Crcinit = (*data << 8 ) ^ Crcinit; for (i = 0 ; i < 8 ; i++) { if (Crcinit & 0x8000 ) Crcinit = (Crcinit << 1 ) ^ Poly; else Crcinit = (Crcinit << 1 ); } data++; } return Crcinit; }
参考
havenxie/stm32-iap-uart-boot: STM32 IAP(UART模式)的BOOT部分
【手把手教程 4G通信物联网 OTA远程升级 BootLoader程序设计】GD32F103C8T6单片机【上篇章】_哔哩哔哩_bilibili
补充(7.24)
Bootloader执行流程
上电或复位
当系统上电或复位时,处理器从一个固定的地址开始执行,这个地址称为 向量表(Vector Table) 的起始地址。
读取向量表地址(比如 Flash 起始地址)
默认情况下,ARM Cortex-M 处理器会从地址 0x08000000 (即 Flash 起始地址)读取:
0x08000000:初始 MSP(Main Stack Pointer)
0x08000004:Reset Handler 的地址 ,也就是主程序的入口点
设置 MSP
处理器将 0x08000000 处的值加载到 MSP(Main Stack Pointer),为堆栈初始化。
跳转到 Reset Handler
处理器将 0x08000004 处的值作为程序计数器 PC,开始执行实际程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 __ASM void MSR_SP (uint32_t addr) { MSR MSP, r0; BX r14; } void LOAD_A (uint32_t addr) { if ((*(uint32_t *)addr >= 0x20000000 ) && (*(uint32_t *)addr <= 0x20004FFF )) { MSR_SP(*(uint32_t *)addr); load_A = (load_a)*(uint32_t *)(addr + 4 ); load_A(); } else { uprintf("Failed to jump to Area A \r\n" ); } }
SRAM在此的意义
SRAM(Static RAM)是 MCU 的运行内存(RAM),栈、全局变量、局部变量都存放在这里。
MSP 指针一般会指向 SRAM 的顶端(例如 0x20000000),向下增长。
应用程序运行期间所有动态数据、堆栈帧等都存在于 SRAM 中。
APP程序配置
system_stm32f10x.c文件中的VECT_TAB_OFFSET,设置0x5000
配置Target
DMA如何配置RX_BUFFER + 1配合IDLE实现接收不定长数据
DMA设置 :传输计数为RX_BUFFER + 1
IDLE触发 :当接收数据长度小于设置计数时触发
长度计算 :通过剩余计数计算实际接收长度
重新配置 :每次IDLE中断后重新配置DMA