第二章 C#+STM32实现设备远程管理与IAP—STM32的BootLoader

整体思路

STM32复位先读取flash的固定区域,该区域存有升级相关信息,包含固件长度、下载地址、升级类型等,根据升级类型,开始升级。由于本次只用到http下载,所以升级类型是固定的。通过http下载文件后缓存在数组内,比较升级长度与与实际接受长度,一致,则说明下载成功,然后将数据写入应用区,跳转至应用区。

第一步 串口配置

uint16_t Usart3_Receive_Count;
uint8_t  Usart3_Receive_Buff[50*1024];

/******************************************UART4******************************************************/
void usart3_init(uint32_t bound)
{
	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
	
	USART_DeInit(USART3);
	
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_USART3);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_USART3);
	
	/*端口配置*/
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8 | GPIO_Pin_9; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP; 
	GPIO_Init(GPIOD, &GPIO_InitStructure);  

   //USART4 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式	
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位	
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
  USART_Init(USART3, &USART_InitStructure); //初始化串口4
  
	//Usart3 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;//串口4中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、
	
	USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启相关中断
	USART_Cmd(USART3, ENABLE);  //使能串口4
}


//串口4发送函数
void Usart3_Send_Data(void* str, uint16_t len)
{
	uint16_t i;
	uint8_t * s;	
	
	s = (uint8_t *)str;
	
	for(i = 0; i< len; i++)
	{
		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); 
		USART_SendData(USART3 ,*s++);//发送当前字符
	}
	
	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET){} 
		
}


void USART3_IRQHandler(void)                	//串口4中断服务程序
{
	uint8_t Res=0;
	
	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
	{
		Res = USART_ReceiveData(USART3);
						
		Usart3_Receive_Buff[Usart3_Receive_Count] = Res;  	  //将接收到的字符串存到缓存中

		Usart3_Receive_Count++;		
	}
} 

uint8_t usart_data_receive(void)
{
	static uint16_t Last_Count;
	
	if((Usart3_Receive_Count == Last_Count) && (Usart3_Receive_Count > 0))
	{		
		Last_Count = 0;
		
		return true;
	}
	
	Last_Count = Usart3_Receive_Count;
			
	return false;
}

bootload代码没怎么注意格式,大家忽略,具体编码格式优化会在第三章 APP应用体现,串口的话就是注意缓存大小,http可以分段下载也可以一次性下载数据,由于本次升级固件长度只有40K左右,所以选用一次性下载,那么串口缓存设置的50k。

uint8_t  Usart3_Receive_Buff[50*1024];

然后是串口接受函数,采用中断接收,因为bootload里面只有这一个中断,所以没啥问题。
串口接受完成判断就是比较常用的定时扫描接受长度,如果间隔一定时间,两次扫描串口接收数据长度不一样,则说明还在接收数据,如果两次扫面串口接收数据长度一样,则说明接收完成,排除长度为零的状况。

uint8_t usart_data_receive(void)
{
	static uint16_t Last_Count;
	
	if((Usart3_Receive_Count == Last_Count) && (Usart3_Receive_Count > 0))
	{		
		Last_Count = 0;
		
		return true; //接收完成返回true
	}
	
	Last_Count = Usart3_Receive_Count;
			
	return false;//接收未完成返回false
}

第二步 4G模块配置

4G模块主要用的是移远的EC20,具体指令含义可以去参考手册。


/*------------------------------------------------------------------------变量------------------------------------------------------------------------*/
static u8 RunStep = 0;      //运行步骤
static uint16_t TimeCount;  //时间计数

static u8 LastRunStep = 0;      //上一次运行状态
static uint16_t OverTimeCount;  //超时计时
/*-------------------------------------------------------------------------END------------------------------------------------------------------------*/

/*----------------------------------------------------------------------函数声明----------------------------------------------------------------------*/
void usart_receive_data_clear(void);

/*------------------------------------------------------------------------END------------------------------------------------------------------------*/


/*----------------------------------------------------------------------功能函数----------------------------------------------------------------------*/

/********************************ec20_update_https********************************
*
* Function:     连接http,并下载数据
* Description: 通过http访问输入网址,并采用GET下载数据
* Input:  @Url      需要访问的http地址
					 @UrlLen   http地址长度
					 @ByteLen  需要下载的文件数据长度
* Output:--
* Return: @SUCCESS 下载数据成功
					 @ERROR    正在连接过程
* Other:  此函数执行时间约50ms
*
********************************************************************************/
int ec20_update_https(char *Url, uint16_t UrlLen, uint16_t ByteLen)
{
	char buff[25];
	
	//通过发送指令,判断模块是否重启,
	//这里是因为有时候是从app区域复位回来的,4G模块没有重启,网络还是连接的,所以先判断一下,需不需等待模块复位完成
	if(RunStep == 0)
	{
		Usart3_Send_Data("AT+CREG?\r\n", sizeof("AT+CREG?\r\n")); 
		RunStep = 1;
	}
	else if(RunStep == 1)
	{
		TimeCount++;
		if(TimeCount > 2) 
		{
			if(strstr((char *)Usart3_Receive_Buff, "+CREG: 0,1") != NULL) /*判断网络状态状态*/
			{
				RunStep = 4;    /*模块未重启,开始连接http*/
			}
			else
			{
				RunStep = 2;    /*模块重启中,等待重启完成*/
			}
			
			TimeCount = 0;
		}	
	}
	
	//重启等待 这里相当于模块重新上电,所以要等个7、8秒模块完成注册
	//这里要注意的是我用的是移动卡,7、8秒就够了,如果用的其他卡可能时间有差异,包括网络环境对注册时间也有影响
	else if(RunStep == 2)
	{
		if(strstr((char *)Usart3_Receive_Buff, "RDY") != NULL) 
		{		
			RunStep = 3;
		}
	}
	else if(RunStep == 3) /*等待网络注册*/
	{
		TimeCount++;
		if(TimeCount > 140) /*等待约7s*/
		{
			TimeCount = 0;
			RunStep = 4;
			//RunStep = 0; //所以这里,等待时间有差异的话可以返回到第一步区查询,根据需求自己可以调整
		}
	}
	
	//设置状态
	if(RunStep == 4)
	{
		Usart3_Send_Data("AT+QHTTPCFG=\"contextid\",1\r\n", sizeof("AT+QHTTPCFG=\"contextid\",1\r\n")); 
		
		RunStep = 5;
	}
	else if(RunStep == 5)
	{	
		if(strstr((char *)Usart3_Receive_Buff, "OK") != NULL) 
		{			
			RunStep = 6;       
			//每一步完成后记得清除串口缓存,以免影响下一次指令回复的判断                
			usart_receive_data_clear();       /*清除缓存数据*/	
		}
	}
	
	//设置回应报文,是否隐藏
	else if(RunStep == 6)
	{
		Usart3_Send_Data("AT+QHTTPCFG=\"responseheader\",0\r\n", sizeof("AT+QHTTPCFG=\"responseheader\",0\r\n")); 
		
		RunStep = 7;
	}
	else if(RunStep == 7)
	{	
		if(strstr((char *)Usart3_Receive_Buff, "OK") != NULL) 
		{
			RunStep = 8;                       
			usart_receive_data_clear();       /*清除缓存数据*/	
		}
	}
	
	//设置连接url长度
	else if(RunStep == 8)
	{
		sprintf(buff, "AT+QHTTPURL=%d,80\r\n", UrlLen);
		
		Usart3_Send_Data(buff, 25); 
		
		RunStep = 9;
	}
	else if(RunStep == 9)
	{	
		if(strstr((char *)Usart3_Receive_Buff, "CONNECT") != NULL) 
		{
			RunStep = 10;                       
			usart_receive_data_clear();       /*清除缓存数据*/	
		}
	}
	
	//连接url
	else if(RunStep == 10)
	{	
		Usart3_Send_Data(Url, UrlLen); 
		
		RunStep = 11;
	}
	else if(RunStep == 11)
	{	
		if(strstr((char *)Usart3_Receive_Buff, "OK") != NULL) 
		{
			RunStep = 12;                       
			usart_receive_data_clear();       /*清除缓存数据*/	
		}
	}
	
	//GET数据
	else if(RunStep == 12)
	{	
		Usart3_Send_Data("AT+QHTTPGET=60\r\n", sizeof("AT+QHTTPGET=60\r\n")); 
		
		RunStep = 13;
	}
	else if(RunStep == 13)
	{	
		if(strstr((char *)Usart3_Receive_Buff, "QHTTPGET: 0,200") != NULL) 
		{
			RunStep = 14;                       
			usart_receive_data_clear();       /*清除缓存数据*/	
		}
	}
	
	//读取数据
	else if(RunStep == 14)
	{
		Usart3_Send_Data("AT+QHTTPREAD=60\r\n", sizeof("AT+QHTTPREAD=60\r\n")); 
		RunStep = 15;
	}
	
	else if(RunStep == 15)
	{
		if(usart_data_receive() == true)
		{
			//这里的数据包含了指令回复+固件数据,所以接收长度是大于固件长度
			//这里-9是我自己做的判断,因为指令回复是9个字节,大家可以用模块区试一下
			if(ByteLen <= (Usart3_Receive_Count - 9))
			{
			    //接收完成后关闭连接
				Usart3_Send_Data("AT+QMTDISC=0\r\n", sizeof("AT+QMTDISC=0\r\n")); 
				
				RunStep = 0;
				
				return CONNECTED;  /*返回连接成功*/
			}
		}
	}
	
	//超时判断,如果运行卡在其中一步则返回错误,应答时间超过10S
	if(LastRunStep == RunStep),要返回错误
	{
		OverTimeCount++;
		
		if(OverTimeCount > 400)
		{
			OverTimeCount = 0;
			return ERROR;
		}
	}
	else
	{
		OverTimeCount = 0;
		LastRunStep = RunStep;
	}
	
	return CONNECTING;       /*返回正在连接*/
}

/******************************usart_receive_data_clear******************************
*
* Function:   清除串口缓存数据
* Description:
* Input: --
* Output:--
* Return:--
* Other:--
*
***********************************************************************************/
void usart_receive_data_clear(void)
{
	Usart3_Receive_Count = 0;
	memset(Usart3_Receive_Buff, 0 , 512);
}

第三步 主程序



/*----------------------------------------------------------------------结构体----------------------------------------------------------------------*/
//升级的相关信息我用的结构体存的,这样比较方便
typedef struct _st_ota_
{
	uint8_t OtaType;
	
	uint32_t OtaPackageLen;
	
	char OtaVersion[5];
	
	char OtaUrl[256];
}STOta;

STOta OtaParm;
/*------------------------------------------------------------------------END------------------------------------------------------------------------*/




/*-----------------------------------------------------------------------宏定义-----------------------------------------------------------------------*/
//一定要注意此处,因为用的自带flash,存储地址一定不要放在 bootload与app的扇区,防止被擦除
//此处的flash地址要与app应用中升级信息存储地址一致,因为是在app中接收升级信息存储后,跳转到bootload后读取升级信息,所以地址要一致。
#define OTA_PARM_SAVE_ADDR  0x08040000

#define TEXT_LENTH sizeof(OtaParm)	 
	
#define SIZE TEXT_LENTH / 4 + ((TEXT_LENTH % 4) ? 1 : 0)

/*------------------------------------------------------------------------END------------------------------------------------------------------------*/


/*----------------------------------------------------------------------功能函数----------------------------------------------------------------------*/
typedef  void (*iapfun)(void);	
iapfun jump2app; 

/*------------------------------------------------------------------------END------------------------------------------------------------------------*/




/*----------------------------------------------------------------------跳转 汇编----------------------------------------------------------------------*/
__asm void MSR_MSP(u32 addr) 
{
	MSR MSP, r0 			//set Main Stack value
	BX r14
}
/*------------------------------------------------------------------------END------------------------------------------------------------------------*/
//简易延时程序,这里对时许要求不高就这么写了,忍不了的可以去用滴答定时器写一个
static void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}


int main(void)
{	
	char *RetAddr;
	int TimeCount;
	int Ret;
	int ErrorCount;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	
	gpio_init(GPIOE, RCC_AHB1Periph_GPIOE, GPIO_Pin_2, GPIO_Speed_50MHz, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_UP);//led
	//先读取升级参数
	STMFLASH_Read(OTA_PARM_SAVE_ADDR, (u32*)&OtaParm, SIZE);//读取
	
	usart3_init(115200);
	//升级类型为2,则说明要升级且用的是http模式
	if(OtaParm.OtaType == 2) //判断是否要升级
	{
		while(1)
		{			
			Ret = ec20_update_https(OtaParm.OtaUrl,strlen(OtaParm.OtaUrl), OtaParm.OtaPackageLen);
			if(Ret == CONNECTED)  //http访问成功,并下载文件包
			{
				RetAddr = strstr((char *)Usart3_Receive_Buff, "CONNECT");	             //寻找升级文件起点		
				//开始往app区更新文件
				STMFLASH_Write(0x8004000, (u32 *)(RetAddr + 9), OtaParm.OtaPackageLen/4+((OtaParm.OtaPackageLen%4)?1:0)); //写入升级文件
				
				OtaParm.OtaType = 0;//升级完成后记得清除升级的标志,避免每次上电重复更新
				
				STMFLASH_Write(OTA_PARM_SAVE_ADDR, (u32 *)&OtaParm, SIZE);    //修改参数并保存
				
				break;
			}	
			else if(Ret == ERROR) //返回错误
			{
				ErrorCount++;
				
				if(ErrorCount > 3)
				{
					break; //升级失败,则跳出,多次下载失败后记得要跳回app区,避免此处卡死
				}
			}
			
			/*************升级灯闪烁**************/
			TimeCount++;
			if(TimeCount % 2 == 0)
			{
				GPIOE->ODR^=GPIO_Pin_2;  //相比app区加快闪烁频率,用于区分是在bootload还是在app
			}
			
			/***********升级时间过长+*************/
			if(TimeCount > 1200)//这里1分钟内基本可以完成升级,我的测试基本在10-20秒内
			{
				break;//升级失败,则跳出,升级时间过长,也要跳回app区
			}
			
			Delay(0x32ff00); //约50ms
		}	
	}
	//
	if(((*(vu32*)0x8004000) & 0x2FFE0000) == 0x20000000)	//检查栈顶地址是否合法.
	{ 
		jump2app = (iapfun)*(vu32*)(0x8004000 + 4);			
		MSR_MSP(*(vu32*)0x8004000);		//跳转到app区域			
		jump2app();									    
	}
}

由于用的型号为STM32F407VE,flash为512K,选择的扇区0为bootloader,扇区1开始app

#define ADDR_FLASH_SECTOR_0     ((u32)0x08000000) 	//扇区0起始地址, 16 Kbytes  //bootloader区域
#define ADDR_FLASH_SECTOR_1     ((u32)0x08004000) 	//扇区1起始地址, 16 Kbytes  //app开始区域
#define ADDR_FLASH_SECTOR_2     ((u32)0x08008000) 	//扇区2起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3     ((u32)0x0800C000) 	//扇区3起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4     ((u32)0x08010000) 	//扇区4起始地址, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5     ((u32)0x08020000) 	//扇区5起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6     ((u32)0x08040000) 	//扇区6起始地址, 128 Kbytes  //ota参数区域
#define ADDR_FLASH_SECTOR_7     ((u32)0x08060000) 	//扇区7起始地址, 128 Kbytes  //407xE的flash到这里
#define ADDR_FLASH_SECTOR_8     ((u32)0x08080000) 	//扇区8起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_9     ((u32)0x080A0000) 	//扇区9起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_10    ((u32)0x080C0000) 	//扇区10起始地址,128 Kbytes  
#define ADDR_FLASH_SECTOR_11    ((u32)0x080E0000) 	//扇区11起始地址,128 Kbytes  //407xG

源码工程在此处下载源码工程下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哆啦A不做梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值