stm32之iap实现应用(基于串口,上位机,详细源码)

2023-11-19

开发环境:Window 7
开发工具:Keil uVision4
硬件:stm32f103c8t6

篇幅略长,前面文字很多,主要是希望能让小白们理解,后面就是实现步骤,包括实现的代码。

在研发调试的时候我们一般用烧录器下载代码,对于stm32f103c8t6来说,还可以用串口下载,步骤如下:

1.PC端下载一个上位机Flash Loader Demo
2.芯片的串口引脚Tx、Rx(PA.9、PA.10)通过USB>TTL连接到电脑上
3.将芯片的boot0引脚接高电平、boot1引脚接低电平。这是为了让芯片上电的时候从系统存储区启动,原厂的isp程序保存在那里,地址是0x1FFF 000 ~ 0x1FFF 77FF。系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动程序,它负责实现串口、USB以及CAN等isp烧录功能。
4.打开上位机,配置如图,波特率可多选,这是因为上位机在发送握手0x7F时,芯片接收到0x7F后,就能将波特率算出来,然后给自身串口初始化,跟上位机设置一样的波特率。接着给芯片上电,上位机选择bin文件,下载到芯片里面后,将boot0和boot1引脚接低,重新上电,就能运行刚下载的程序了。

在这里插入图片描述
这样就可以在没有烧录器的情况下下载程序了,当然如果要进行调试的话,还是需要烧录器。

除了上面这两种给芯片下载新程序的方法,还可以在芯片运行中给自身flash存储器写入新程序。这就是iap(In application Programing在应用编程)。做一个产品,研发时一般都是在PC端借助烧录器升级,但到客户手里一般是用U盘升级,只要把U盘插入到机器中,就能给自身升级。其中,在插入U盘后,芯片检测到需要升级,就会跳转到iap程序段里面去,然后读取U盘里面的程序,再将U盘的程序文件拷贝到自身的flash里,拷贝完成之后,跳转到新的程序中运行。下面是通过串口给自身升级的iap案例,将实现的过程代码详细说明。

stc32f103c8t6内部有一个64k的flash存储器,用于储存代码,在电脑上编译好的程序,通过烧录器把它烧录到内部flash中。Flash里面的内容掉电不会丢失,烧录完,芯片重新上电,就可以从内部flash中加载代码(起始地址一般是0x0800 0000)。

内部falsh除了用烧录器读写外,还可以在芯片运行时,对自身的内部flash进行读写。如果flash储存了程序后还有剩余的空间,那么可以把它用来保存程序运行时产生需要掉电保存的数据;也可以在芯片运行时将另一个编译后的二进制程序文件写到剩余的flash,然后进行跳转到新的程序上面运行。这也是iap的实现原理。

1、先介绍怎么利用stm库对flash进行操作

所有flash操作相关的函数接口在stm32f10x_flash.h里面。读flash里面的数据直接根据地址读出来就行了。往写flash里面写数据,需要解锁,擦除,写入数据,上锁;擦除后存储单元都变成1,因为储存单元不能由0变1,所以在写入之前一定要先擦除,不然会写入失败。
操作代码如下:

#define address 0x08006000   //写入的flash地址
#define value 0x55aa55aa     //将要写的数据
void flash_test(void)
{
	uint32_t *pdata=address;

	Printf(“data=%d”,*pdata);   //先将原来的数据打印出来
    FLASH_Unlock();    //解锁
	FLASH_ErasePage(address);   
	//擦除,擦除只能按页擦,擦除address地址所在的页,不同的芯片一页的大小不一样,对于stc32f103c8t6来说,一页就是1024字节,也就是1k。

	FLASH_ProgramWord(address,value);   
	/*将data写入address地址里面,除了写入uint32_t类型,还可以写uint16_t类型的数据,对于一份很长的代码来说,只能这样一个个的写进去flash,对应接口如下:
	FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
	FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);*/

	FLASH_Lock();		//上锁,保护数据		
	Status=FLASH_WaitForLastOperation(0xFFFFFF);//等待烧写结束,参数是等待的超时时间
	if(Status==FLASH_COMPLETE){
	//写入成功
	}
	Printf(“data=%d”,*pdata);//打印确认是否写入成功
}

要注意,address的地址不能指向自身的代码区域,因为修改了自身的程序会造成不可预测的效果,所以要指向自身程序后面的空余区域,一般来说从0x08000000+加上程序的大小,后面的就是空余区域。下图是对于芯片stc32f103c8t6的工程配置:
在这里插入图片描述

2、流程图不太会写,简单地将过程描述一遍,iap策略如下:

在这里插入图片描述
对于升级的方式,可以选择以下几种,如USART,IIC,CAN,USB,以太网接口甚至是无线射频通道,将程序文件发送到iap。

存储区划分:

Bootloader工程:0x800 0000-0x800 2BFF (11k)
升级标志:0x800 2C00-0x800 2FFF (1K)
App工程:0x800 3000 -0x801 0000 (52k)

芯片上电首先是进入bootloader工程的,所以把bootloader放在前面。升级标志可能会有人问为啥要1k这么大,一个字节不行了吗?首先,bootloader和app都可能会对升级标志的值进行修改或读取,所以不能保存在RAM,只能保存在ROM,那么对ROM的数据进行修改就是flash读写操作,上面提了要先擦除,而且擦除是按页擦,一页就是1k,所以就算标志位不需要1k这么大,只用其一个字节,那剩下的也不能用到其他地方,因为它随时会被擦除。

下面开始说明这两部分的代码实现,其中的一些配置也要细心注意。

3、Bootloader工程

Bootloader程序开机引导app程序,在运行app程序中,若收到升级信号,则从app跳转到bootloader里,然后boorloader通过串口接收新的程序文件,对app进行升级。所以,我们还需要一个上位机将程序文件通过串口发送给bootloader,为了方便,我没自己做上位机,直接用Flash Loader Demo,这个可以网上下载。那么上位机有了,还要了解它的通讯协议, 到底数据是怎么从上位机发送过来了,bootloader该怎么接收数据。
其实我们要做的bootloader工程就是要实现原厂isp的功能,跟上位机同步,接受上位机数据。我们无法得到人家的isp代码,但是可以上st的官网下载它的isp协议。了解了它的协议就能自己写单片机端的代码了。协议下载链接。这里不对这个协议进行细说,直接说明实现的代码,下图是原厂isp所支持的命令。
在这里插入图片描述
建立bootloader工程,打开一个新的带stm32标准库的keil工程,对工程进行如下配置。
在这里插入图片描述

第一步,初始化USART1外设,这里不做波特率自适应,把波特率固定为115200,那么上位机配置就要跟其保持一致。

创建<USART.h>

#ifndef __USART1_INIT_H__
#define __USART1_INIT_H__

#include "stm32f10x.h"
#include <stdio.h>

void USART1_Configuration(void);//打印输出串口初始化
void sengdata(unsigned char data);
unsigned char waitdata(void); 

#endif

创建<USART.c>

#include "USART1.h"
#include "Queue.h"

 void USART1_Configuration(void)//打印输出串口初始化
 {
	  GPIO_InitTypeDef GPIO_InitStructure;
	  USART_InitTypeDef USART_InitStructure;
	  NVIC_InitTypeDef NVIC_InitStructure;
    
	  //配置串口1 (USART1) 时钟
	  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	  //配置串口1接收终端的优先级
	  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); 
	  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; 
	  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 
	  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 
	  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	  NVIC_Init(&NVIC_InitStructure);
	 
    //配置串口1 发送引脚(PA.09)
	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	  GPIO_Init(GPIOA, &GPIO_InitStructure);    
	  //配置串口1 接收引脚 (PA.10)
	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	  GPIO_Init(GPIOA, &GPIO_InitStructure);
	    
	  //串口1工作模式(USART1 mode)配置 
	  USART_InitStructure.USART_BaudRate = 115200;//设置波特率;
	  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(USART1, &USART_InitStructure); 
		
	  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
	  USART_Cmd(USART1, ENABLE);//使能串口
}

void sengdata(unsigned char data)
{
	USART_SendData(USART1, (unsigned char) data);
	while( USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET);	
}

extern QueueT RxQueueEntity;
unsigned char waitdata(void)  //阻塞等待一个数据到来
{
	while(1){
		if(getDataCount(&RxQueueEntity)!=0){
		      return outQueue(&RxQueueEntity);
			}
	}
}

第二步,创建接收队列,因为上位机发送过来的数据很多,芯片不能及时处理,那么就要先把数据放进队列,然后逐个拿出来处理。这样就不会丢失数据。直接复制下面代码就行,可以先不用理解。

创建<Queue.h>

#ifndef __QUEUE__H__
#define __QUEUE__H__ 
#include "core_cm3.h"

typedef struct  
{
	u16 in;
	u16 out;
	u16 cntMax;
	u8*  pBuf;
}QueueT;

/*队列的特点:先进先出,若队列满了,不能再放数据。可循环使用队列的位置*/

void QueueCreate(QueueT* thiz,u8* BufAddress,u16  BufSize); //创建一个队列,初始化结构体里面的成员
u16 getDataCount(QueueT* thiz); //获取队列里面有效的数据的大小
u16 getEmptyCount(QueueT* thiz); //获取队列里面还剩余多少空的位置
u8 inQueue(QueueT* thiz,u8 data); //将一个数据放进队列
u8 outQueue(QueueT* thiz); //从队列里面拿一个数据出来

#endif //__QUEUE__H__ 

创建<Queue.c>

#include "Queue.h"
void QueueCreate(QueueT* thiz,u8* BufAddress,u16  BufSize)
{
		thiz->in=0;
		thiz->out=0;
		thiz->cntMax=BufSize;
	    thiz->pBuf=BufAddress;
}

u16 getDataCount(QueueT* thiz)
{
	  if (thiz->in >= thiz->out){
        return (thiz->in - thiz->out);
     }else{
        return (thiz->in + thiz->cntMax - thiz->out);
     }
}

u16  getEmptyCount(QueueT* thiz)
{
	u16 dataCnt;
		 
    if (thiz->in >= thiz->out){
        dataCnt=(thiz->in - thiz->out);
    }else{
        dataCnt=(thiz->in + thiz->cntMax - thiz->out);
    }
		
	if ((dataCnt+1u) >= thiz->cntMax){
        return 0; //fifo full
    }
	return  (thiz->cntMax-dataCnt-1u);

}

u8 inQueue(QueueT* thiz,u8 data)
{
	    u16   in;

     in =  thiz->in + 1u;
     if (in >= thiz->cntMax){
         in = 0;
     }

     if (in == thiz->out){ //full fifo
         return 0;
     }

     thiz->pBuf[thiz->in] = data;
     thiz->in = in;

     return 1;
}

u8  outQueue(QueueT* thiz)
{
	 u8   data;
     u16  out;
	
     if (thiz->in == thiz->out){ //empty fifo
         return 0;
     }

     out = thiz->out;
     data = thiz->pBuf[out];
     out++;
     if (out >= thiz->cntMax){
         out = 0;
     }
     thiz->out = out;

     return data;
}

第三步,建立与上位机的通讯协议,接收上位机的命令并作出相应的应答。下面代码是根据协议AN2606来写给上位机的应答的。

创建<iap.h>

#ifndef __IAP_H__
#define __IAP_H__
#include "core_cm3.h"

#define updata_flagaddr 0x8002C00  //升级标志
#define verify_flagaddr 0x800FF10    //在app里面
#define ACK   0x79        //肯定应答
#define NACK  0x1F        //否定应答

typedef struct{
	  unsigned char cmd;
	  void (*pfunction)(void);
}CommandHandleStruct;

typedef  void (*iapfun)(void);
void jump_to_app(u32 appxaddr);			

extern const CommandHandleStruct CmdHdlStr[11];

#endif //__IAP_H__

创建<iap.c>

#include "iap.h"
#include "USART1.h"

static unsigned char bootloaderversion=0x22;
static unsigned char cmd_count=11;
static unsigned char cmd[11]={0x00,0x01,0x02,0x11,0x21,0x31,0x43,0x63,0x73,0x82,0x92};

void jump_to_app(u32 appaddr)  //跳转函数
{
	  iapfun jump2app; 
	
		if(((*(vu32*)appaddr)&0x2FFE0000)==0x20000000)
		{ 
			__set_PRIMASK(1);
			__set_MSP(*(vu32*)appaddr);
			jump2app=(iapfun)*(vu32*)(appaddr+4);
			jump2app(); 
		}
}

unsigned char checksum(unsigned char *data, int len) //计算p开始len个字节的checksum,也就是计算异或
{
    int i;
    unsigned char cs;
    cs = 0;
    for ( i=0; i<len; i++ )
        cs ^= data[i];
    return cs;
}

void Getcommand(void) //AN2606 page10
{ 
	unsigned char i;
		
	sengdata(cmd_count);
	sengdata(bootloaderversion);
		
	for(i=0;i<cmd_count;i++){
		sengdata(cmd[i]);
	}
	sengdata(ACK);
}

void GetVersion(void)  //AN2606 page12
{
	  sengdata(bootloaderversion);
	  sengdata(0x01);
	  sengdata(0x01);
	  sengdata(ACK);
}

void GetID(void)  //AN2606 page14
{
	  sengdata(0x01);
	  sengdata(0x04);
	  sengdata(0x10);
		sengdata(ACK);
}
unsigned int addr=0,temp1;
unsigned int* flashdata;
void ReadMemorycommand(void)  //AN2606 page16
{

	unsigned char temp[4],recchecksum,len,tempsum=0,i;
	
	temp[0]= waitdata();
	temp[1]= waitdata();
	temp[2]= waitdata();
	temp[3]= waitdata();
	
    addr=(temp[0]<<24)|(temp[1]<<16)|(temp[2]<<8)|temp[3];
	recchecksum = waitdata();
    if(recchecksum==checksum(temp,4)&&(addr>=0x08000000)||(addr<0x08010000)){
		sengdata(ACK);
	}else{
		sengdata(NACK);
		return;
	}
	len=waitdata();
	recchecksum=waitdata();
	tempsum=~len;
	if(recchecksum==tempsum){
			sengdata(ACK);
	}else{
			sengdata(NACK);
			return;
	}
	for(i=0;i<len+1;i++){
			flashdata=(unsigned int*)(addr+i);
		    temp1=*flashdata;
			sengdata(*flashdata);
	}
}
void Gocommand(void)  //AN2606 page18
{
		unsigned int addr;
		unsigned char temp[4],recchecksum;
	
		temp[0]= waitdata();
		temp[1]= waitdata();
		temp[2]= waitdata();
		temp[3]= waitdata();
	
	  addr=(temp[0]<<24)|(temp[1]<<16)|(temp[2]<<8)|temp[3];
		recchecksum = waitdata();
	  if(recchecksum==checksum(temp,4)&&(addr>=0x08000000)&&(addr<0x08010000)){
			sengdata(ACK);
		}else{
			sengdata(NACK);
			return;
		}
		//清除升级标志
		FLASH_Unlock(); //解锁
		FLASH_ErasePage(updata_flagaddr);   
		FLASH_ProgramWord(updata_flagaddr,0x00);
		FLASH_Lock();	  //上锁
		FLASH_WaitForLastOperation(0xFFFFFF);//等待擦除结束
	  jump_to_app(addr);
}

void WriteMemorycommand(void) //AN2606 page20
{
	  unsigned int Status;
	  unsigned int addr,recdata_32bit,i;
		unsigned char temp[4],recchecksum=0,len,recdata[0xFF];
	
		temp[0]= waitdata();
		temp[1]= waitdata();
		temp[2]= waitdata();
		temp[3]= waitdata();
	
	  addr=(temp[0]<<24)|(temp[1]<<16)|(temp[2]<<8)|temp[3];
		recchecksum = waitdata();
	  if(recchecksum==checksum(temp,4)&&(addr>=0x08000000)&&(addr<0x08010000)){
			sengdata(ACK);
		}else{
			sengdata(NACK);
			return;
		}
		len=waitdata();
		for(i=0;i<len+1;i++){
				recdata[i]=waitdata();
		}
		recchecksum=waitdata();
		if(recchecksum==(len^checksum(recdata,len+1))){
			//写flash
			FLASH_Unlock();    //解锁
			for(i=0;i<len+1-3;i+=4){
				recdata_32bit=recdata[i+0]|(recdata[i+1]<<8)|(recdata[i+2]<<16)|(recdata[i+3]<<24);
				FLASH_ProgramWord(addr,recdata_32bit);   
				addr+=4;
			}
			FLASH_Lock();	//上锁
			Status=FLASH_WaitForLastOperation(0xFFFFFF);//等待烧写结束
			if(Status==FLASH_COMPLETE){
			//写入成功
			}
			sengdata(ACK);
		}else{
			sengdata(NACK);
			return;
		}
}
void Erasecommand(void) //AN2606 page24
{
	  unsigned int Status;
		unsigned char pagecount,erasepagearry[50],recchecksum,i,tm;
	
		pagecount=waitdata();
	  for(i=0;i<pagecount+1;i++){
				erasepagearry[i]=waitdata();
		}
		recchecksum=waitdata();
		tm=checksum(erasepagearry,pagecount+1)^pagecount;
	  if(recchecksum==tm){
			//擦除页
			FLASH_Unlock(); //解锁
			for(i=0;i<pagecount+1;i++){
					FLASH_ErasePage(0x08000000+erasepagearry[i]*1024);   
			}
			FLASH_Lock();	  //上锁
			Status=FLASH_WaitForLastOperation(0xFFFFFF);//等待擦除结束
		
			if(Status==FLASH_COMPLETE){
				//擦除成功
			}
			sengdata(ACK);
		}else{
			sengdata(NACK);
			return;
		}
}
//下面四个暂时没用到,所以我没做实现
void WriteProtectcommand(void)
{
}
void WriteUnprotectcommand(void)
{
}
void ReadoutProtectcommand(void)
{
}
void ReadoutUnprotectcommand(void)
{
}

/*
Byte 4: 0x00 – Get command
Byte 5: 0x01 – Get Version and Read Protection Status
Byte 6: 0x02 – Get ID
Byte 7: 0x11 – Read Memory command
Byte 8: 0x21 – Go command
Byte 9: 0x31 – Write Memory command
Byte 10: 0x43 – Erase command
Byte 11: 0x63 – Write Protect command
Byte 12: 0x73 – Write Unprotect command
Byte 13: 0x82 – Readout Protect command
Byte 14: 0x92 – Readout Unprotect command
*/
//把所有应答函数的函数地址保存到数组里面,方便后续遍历定位执行命令相对应的函数
const CommandHandleStruct CmdHdlStr[11]={
    {0x00,Getcommand},
    {0x01,GetVersion},
    {0x02,GetID},
    {0x11,ReadMemorycommand},
    {0x21,Gocommand},
    {0x31,WriteMemorycommand},
    {0x43,Erasecommand},
    {0x63,WriteProtectcommand},
    {0x73,WriteUnprotectcommand},
    {0x82,ReadoutProtectcommand},
    {0x92,ReadoutUnprotectcommand} ,
};

第四步,创建main.c

#include "stm32f10x.h"
#include "USART1.h"
#include "iap.h"
#include "Queue.h"

void GPIO_Configuration(void);

#define RxbufSize  500
QueueT RxQueueEntity; //包含了队列内信息的结构体
u8 rxbuf[RxbufSize];//队列缓存

int main(void)
{	  
	  u8 ch=0,checksum,recchecksum,i;
	
	  RCC_DeInit();
	  SystemInit();
	  __set_PRIMASK(0);
	
    USART1_Configuration();//串口初始化
	GPIO_Configuration(); //将接到PC.13的lcd点亮
	QueueCreate(&RxQueueEntity,&rxbuf[0],RxbufSize); //创建串口1的接收队列

    if(*(uint16_t*)updata_flagaddr==0){ //不需要升级
			if((*(unsigned int*)verify_flagaddr)==0xaabbccdd){
				jump_to_app(0x8003000);//跳转到app
			}
	}
		
	  //等待升级
	  while(1){
		  if(getDataCount(&RxQueueEntity)!=0){
		      ch=outQueue(&RxQueueEntity);
					if(ch==0x7f){ //接收到上位机发送的同步信号7f
							sengdata(ACK);
					}else{  //接收到上位机的命令
							for(i=0;i<11;i++){
								if(CmdHdlStr[i].cmd==ch){
									  checksum=~ch;
									  recchecksum=waitdata();//接收校验位
										if(recchecksum==checksum){
											sengdata(ACK);
											CmdHdlStr[i].pfunction(); //调用相应的应答函数
											break;
										}
								}
							}
					}
			}
		}

}
void GPIO_Configuration(void) //在运行bootloader时,设置pin13的led常亮
{
  GPIO_InitTypeDef GPIO_InitStructure;
  
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC , ENABLE); 						 	 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
  GPIO_Init(GPIOC, &GPIO_InitStructure);
 GPIO_ResetBits(GPIOC,GPIO_Pin_13);
}

最后,找到stm32f10x_it.c,加入:
将串口接收到的数据放进队列。

#include "Queue.h"
extern QueueT RxQueueEntity;

void USART1_IRQHandler(void)
{	
	u16 code,Status;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{ 	
		 code=USART_ReceiveData(USART1);
		  Status = USART_GetFlagStatus(USART1, USART_FLAG_NE|USART_FLAG_PE|USART_FLAG_NE);
		  if(Status!=RESET){//如果发生错误接忽略接收的数据
				USART_ClearFlag(USART1,Status);//把错误标志清楚
				return;
			}
		  if(getEmptyCount(&RxQueueEntity)!=0){ //判断队列是否有空的位置,若满了就丢弃
			    inQueue(&RxQueueEntity,code); //将接受到的数据放进队列
		  }
	    //printf("%c",code);    //将接受到的数据直接返回打印
	} 
	 
}

建立好的工程如下图:
在这里插入图片描述
然后进行编译下载到芯片里面去。

4、建立app工程,打开一个新的带stm32标准库的keil工程,对工程进行如下配置。

在这里插入个图片描述
第一步,初始化USART1外设,把波特率固定为115200。

创建USART1.h

#ifndef __USART1_INIT_H__
#define __USART1_INIT_H__

#include "stm32f10x.h"
#include <stdio.h>

int fputc(int ch, FILE *f);
void USART1_Configuration(void);//打印输出串口初始化

#endif

创建USART1.c

#include "USART1.h"

 void USART1_Configuration(void)//打印输出串口初始化
 {
	  GPIO_InitTypeDef GPIO_InitStructure;
	  USART_InitTypeDef USART_InitStructure;
	  NVIC_InitTypeDef NVIC_InitStructure;
    
	  //配置串口1 (USART1) 时钟
	  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	  //配置串口1接收终端的优先级
	  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); 
	  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; 
	  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 
	  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 
	  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	  NVIC_Init(&NVIC_InitStructure);
	 
    //配置串口1 发送引脚(PA.09)
	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	  GPIO_Init(GPIOA, &GPIO_InitStructure);    
	  //配置串口1 接收引脚 (PA.10)
	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	  GPIO_Init(GPIOA, &GPIO_InitStructure);
	    
	  //串口1工作模式(USART1 mode)配置 
	  USART_InitStructure.USART_BaudRate = 115200;//配置波特率;
	  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(USART1, &USART_InitStructure); 
		
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
	  USART_Cmd(USART1, ENABLE);//使能串口
}

int fputc(int ch, FILE *f) //重定向c库里面的fputc到串口,那么使用printf时就能将打印的信息从串口发送出去,在PC上同串口助手接收信息
{
	//将Printf内容发往串口
	USART_SendData(USART1, (unsigned char) ch);
	while( USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET);	
	return (ch);
}

第二步,找到stm32f10x_it.c,加入:

int updatareq=0;
void USART1_IRQHandler(void)
{	
	u16 code,Status;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{ 	
		 	code=USART_ReceiveData(USART1);
		  Status = USART_GetFlagStatus(USART1, USART_FLAG_NE|USART_FLAG_PE|USART_FLAG_NE);
		  if(Status!=RESET){//如果发生错误接忽略接收的数据
				USART_ClearFlag(USART1,Status);//把错误标志清楚
				return;
			}
	  	if(code==0x7F){ //如果接受到0x7F,那么跳转到bootloader,等待升级
				updatareq=1;
	    } 
		}
}

第三步,找到system_stm32f10x.h,修改中断列表的偏移地址:
#define VECT_TAB_OFFSET 0x3000
最后加入main.c:

#include "stm32f10x.h"
#include "GPIOLIKE51.h"
#include "USART1.h"

void GPIO_Configuration(void);
void Delay(uint32_t nCount);


static const unsigned int verifyflag __attribute__((at(0x800FF10)))= 0xaabbccdd;

#define updata_flagaddr 0x8002C00 //升级标志
#define verify_flagaddr 0x800FF10 //在app里面
typedef  void (*iapfun)(void);

extern int updatareq;
void jump_to_boot(u32 appaddr)
{
	  iapfun jump2boot; 
	
		if(((*(vu32*)appaddr)&0x2FFE0000)==0x20000000)
		{ 
			printf("jump_to_boot\n");
			__set_PRIMASK(1);
			__set_MSP(*(vu32*)appaddr);
			jump2boot=(iapfun)*(vu32*)(appaddr+4);
			jump2boot(); 
		}
}
int main(void)
{
	  RCC_DeInit();
	  SystemInit();
	  __set_PRIMASK(0);
	
	  GPIO_Configuration(); //运行app时,将接到PC.13引脚的lcd闪烁
	  USART1_Configuration();
      while (1){
			GPIO_SetBits(GPIOC,GPIO_Pin_13);
			Delay(0xfffff);
			Delay(0xfffff);	
			GPIO_ResetBits(GPIOC,GPIO_Pin_13);
			Delay(0xfffff);
			Delay(0xfffff);	
			printf("app runing \n");
			 if(updatareq){
				FLASH_Unlock(); //解锁
				FLASH_ErasePage(updata_flagaddr);   
				FLASH_ProgramWord(updata_flagaddr,0x01);
				FLASH_Lock();	  //上锁
				FLASH_WaitForLastOperation(0xFFFFFF);//等待擦除结束
				jump_to_boot(0x8000000);
			}
    }
}

void GPIO_Configuration(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC , ENABLE); 						 	 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}

void Delay(uint32_t nCount)
{
  for(; nCount != 0; nCount--);
}



最后,设置输出文件的保存路径,不更改也可以,保持默认,那么等一下就到这个文件夹下找到输出文件:
在这里插入图片描述
生成app的bin文件:
fromelf --bin --output “$Ltest.bin” “#L”
在这里插入图片描述

5.下载验证:

前面已经将boot下载到芯片里面去;现在,把usb>ttl 接口接到电脑上,打开上位机,上位机就能检测到连上串口的设备,设置波特率115200,无校验位。配置完了之后,先给芯片上电(boot0=0,boot1=0正常启动),正常来说,接到PC.13的led会常亮,因为上电运行的是boot。那么点击上位机的Next进行同步,若同步成功就会跳转到下一个界面,若是同步不成功,就会卡着,鼠标光标转圈;同步失败的话检查线路是否连接正确,COM口选择是否正确。
在这里插入图片描述
设置flash大小,然后点next继续:
在这里插入图片描述
这里选择刚才编译出来的app工程bin文件,注意红圈的配置,点击Next继续:
在这里插入图片描述
开始app下载,下载完成后会自动跳转到app程序,这时候可以看到led闪烁。
在这里插入图片描述
如果成功下载了之后,芯片重新上电,首先是进入boot,检测app完整存在,那么就会自动跳转到app。
再把芯片串口接到电脑上,打开串口助手,检测通讯正常,给芯片发送7F,如图,app收到7F就会跳转到boot,等待升级;然后把串口助手关闭,打开Flash Loader Demo上位机,给芯片升级app。其实也可以不用串口助手发送7F,下载上位机同步的时候就会给芯片发送7F,只不过在操作的时候,要点击两次Next,点击第一次的时候忽略提示就行,再点击一次。
在这里插入图片描述
到最后,终于写完了,这个iap还有很多要完善的地方,比如加入超时机制等。希望以上能起到提供一些参考的作用。有错误或者疑问都可以提出来,谢谢。

 

https://blog.csdn.net/weixin_42653531/article/details/89706374

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

stm32之iap实现应用(基于串口,上位机,详细源码) 的相关文章

  • STM32---串口实现在应用程序的固件更新(IAP)

    背景 xff1a 在产品发布后 xff0c 可能需要对固件进行更新或者升级 xff0c 那么在影响产品正常运行的情况下 xff0c 如果升级固件呢 xff1f 理论 xff1a 下面的所有理论部分内容参考 STM32开发指南 什么是IAP
  • 【STM32】IAP下载程序分析

    IAP下载程序分析 IAP基本原理STM32启动流程程序跳转代码实现总结 IAP基本原理 要实现STM32的IAP xff08 在应用编程 xff09 xff0c 需要分别建立bootloader和app工程 这里的bootloader程序
  • ICP、ISP、IAP、JTAG、SWD下载方式

    目录 ICP ISP IAP JTAG SWD下载方式 概述 JTAG SWD ICP ISP IAP ISP与ICP的差别 ISP和IAP的差别 ICP ISP IAP JTAG SWD下载方式 概述 JTAG和SWD是一种标准协议 xf
  • 【STM32---IAP】基于CAN总线的BootLoader上下位机设计

    IAP开发 下位机STM32 43 上位机Linux 一 准备工作二 IAP系统开发2 1 IAP简介2 2 IAP下位机开发2 2 1 刷写文件选择2 2 2 Bootloader程序框架2 2 3 Bootloader程序开发2 2 3
  • stm32之iap实现应用(基于串口,上位机,详细源码)

    开发环境 Window 7 开发工具 Keil uVision4 硬件 stm32f103c8t6 篇幅略长 前面文字很多 主要是希望能让小白们理解 后面就是实现步骤 包括实现的代码 在研发调试的时候我们一般用烧录器下载代码 对于stm32
  • 基于ARM Cortex-M0+内核的bootloader程序升级原理及代码解析

    本文主要讲述BootLoader程序升级原理及一些代码的解析 力图用通俗易懂的语言描述清楚BootLoader升级的主要关键点 BootLoader 升级原理概述 首次接触这一块时 有一个概念叫IAP 在应用编程 通俗一点讲便是通过一段已有
  • STM32 IAP Ymodem

    STM32 IAP采用Ymodem协议升级固件 公司最近软件需要通过IAP来升级所有板卡的固件 其中板卡有2块 一块主控板卡 一块子控板卡 其中 主控板卡与子控板卡之间采用RS485通信 PC与主控板卡采用RS232通信 具体框架 一 PC
  • STM32F103C8T6单片机IAP升级

    关于IAP升级的方法和原理 网上已经有很多资料了 这块就不再说了 现在就将bootloader和app配置方法整理如下 APP程序就是一个简单的LED闪烁 APP设置为从FLASH中启动 STM32F103C8T6单片机flash有64K
  • stm32串口IAP例程解析

    例程获取 可以通过访问官方网站www st com获取示例代码和应用笔记 示例代码 x cube iap usart 应用笔记 an4657 同时本文涉及的所有资料可以从此下载 链接 https pan baidu com s 19nKPc
  • IAP 程序 跳转问题

    问题 STM32F407 未加下面 清除所有中断标志位 IAP无法跳转到APP 原因 开了定时器 配置了中断 在执行时为清除中断 导致程序无法跳转 void flash load application uint32 t applicati
  • stm32 IAP APP 相互跳转实验 (keil4 jlink STM32F407ZE)

    1 实验目标 STM32 IAP学习时 希望有一个快捷的方式去实验IAP与APP之间的相互跳转 1 验证IAP跳转至APP 2 验证APP通过软件reset跳转至IAP 避免再一开始就实验完整的IAP过程 编写BootLoader 编写 A
  • IAP与APP(一):两个固件使用Keil5默认设置编译后,利用J-Flash偏移APP的地址然后合并IAP烧写,运行时出现在跳转APP时无限重启的现象

    最近做了个在线升级 并没有使用系统 芯片 STM32F103C8T6 环境 Keil 5 STM32CubeMX 5 2 1 跳转和写FLASH在网上一搜其实不少 主要注意要重定向中断向量表和跳转前要关闭所有中断 在写好IAP和APP两个固
  • ISP、IAP、DFU和bootloader

    这是嵌入式开发中常用的几个专业术语 其诞生的背景和其具体作用大概如下 在很久很久以前 那是8051单片机流行的时代 做单片机开发都需要一个专用工具 就是单片机的编程器 或者叫烧写器 说 烧 写一点不为过 当年的经典芯片AT89C51在编程时
  • STM32在应用编程(IAP)详解

    什么是IAP STM32单片机的程序烧写有多种方法 分别为 JTAG SW ISP IAP gt JTAG的方式需要专用的烧写工具 在产品布置到现场后 更新产品程序比较麻烦 gt ISP即为 在系统编程 In System Programm
  • STM32 之八 在线升级(IAP)超详细图解 及 需要注意的问题解决

    IAP 是啥 IAP In Application Programming 即在应用编程 也就是用户可以使用自己的程序对MCU的中的运行程序进行更新 而无需借助于外部烧写器 其实ST官网也给出了IAP的示例程序 感兴趣的可以直接去官网搜索
  • 快速应用RT-Thread IAP升级功能

    快速应用RT Thread IAP升级功能 参考官方文档 步骤一准备bootload APP项目设置及代码修改 下载RTT源码 修改main c 增加分区表配置文件 依据分区表的配置修改flash驱动 第一次烧录进单片机 修改分散加载文件
  • STM32速成笔记—串口IAP

    本文涉及到串口通信和Flash知识 对于这部分知识不熟悉的小伙伴可以到博主STM32速成笔记专栏查看 文章目录 一 串口IAP简介 1 1 什么是IAP 1 2 STM32下载程序 二 串口IAP有什么作用 三 启动流程 3 1 正常启动流
  • 单片机通用Bootloader框架

    通用Bootloader框架 终端控制台预览 flash分区 APP分区固件制作 设置中断向量表 设置flash起始地址 加入升级成功标识写入 生成可烧写bin文件 固件升级与上载 更新固件 上载固件 升级方式 工程下载 最近搞了Bootl
  • STM32在线升级 (IAP)

    来自QQ群 Linux 技术分享 311078264 打开链接加入QQ群 https jq qq com wv 1027 k 5Gr3bAx 此文档由elikang整理 为了文章简单直接 许多细节未能在文章中体现 如有疑问请进群讨论 STM
  • stm32之iap实现应用(基于串口,上位机,详细源码)

    开发环境 Window 7 开发工具 Keil uVision4 硬件 stm32f103c8t6 篇幅略长 前面文字很多 主要是希望能让小白们理解 后面就是实现步骤 包括实现的代码 在研发调试的时候我们一般用烧录器下载代码 对于stm32

随机推荐

  • Spark常用参数解释

    Spark的默认配置文件位于堡垒机上的这个位置 SPARK CONF DIR spark defaults conf 用户可以自行查看和理解 需要注意的是 默认值优先级最低 用户如果提交任务时或者代码里明确指定配置 则以用户配置为先 用户再
  • python stats_python statsmodel的使用

    1 Pandas Python Data Analysis Library 或 pandas 是基于NumPy 的一种工具 相当于这是Python官方自己的一套库 statsmodel是基于Pandas开发的一套库 用于一些描述统计 统计模
  • MySQL 数据库备份(包含存储过程) 和 还原数据库

    备份数据库 使用命令 mysqldump u用户名 p密码 R 数据库名字 gt t sql sql R 表示 备份数据库时 同时也备份存储过程 还原数据库 运用了一个比较 笨 的方法 在MySQL里面手动新建一个数据库 然后把t sql
  • 部署stable diffusion时踩过的坑

    一个月前开始接触AI绘画 几天前开始学习stable diffusion 由于对自身电脑配置的信心不大 因此开始的时候使用的google免费的15G云盘空间进行云部署 但是15G内存对于想要生成更多的图片的人来说不是很够的 因为在使用过程中
  • 同事都在偷偷用的Python接单平台竟然是这8个!!轻松让你月入上w!

    一 Python爬虫学到怎么样可以接单 1 基础简单回顾 想要上手爬虫 基本知识和工具的熟练使用是必须要具备的 首先Python的一些语言基础肯定要有 爬虫大部分是用python写的 基本的语法 数据结构 函数等要熟练 比如 List di
  • vscode: Downloading package ‘C/C++ language components (Linux / x86_64)‘ Failed.

    使用vscode远程连接docker容器 进入容器后报错 Updating C C dependencies Downloading package C C language components Linux x86 64 Failed R
  • extern const static

    1 基本解释 extern可以置于变量或者函数前 以标示变量或者函数的定义在别的文件中 提示编译器遇到此变量和函数时在其他模块中寻找其定义 此外extern也可用来进行链接指定 也就是说extern有两个作用 第一个 当它与 C 一起连用时
  • 2021-07-29

    git和GitHub的搭配使用1 作为一个小白 一直觉得GitHub是个程序员大佬玩转的东西 最近在发现了宝贝教程https www bilibili com video BV1db4y1d79C spm id from 333 788 b
  • Excalidraw本地化部署

    1 Excalidraw介绍 Excalidraw是一个开源 小巧易用的手写风格的框图画板软件 excalidraw官网地址 https excalidraw com 2 Excalidraw本地化安装 git方式 2 1安装部署 在ter
  • shell case 分支选择

    转自 http hlee iteye com blog 577628 case和select结构在技术上说并不是循环 因为它们并不对可执行代码块进行迭代 但是和循环相似的是 它们也依靠在代码块顶部或底部的条件判断来决定程序的分支 在代码块中
  • STM32单片机的IIC硬件编程---查询等待方式

    IIC器件是一种介于高速和低速之间的嵌入式外围设备 其实总体来说 它的速度算是比较慢的 通常情况下 速度慢的器件意味着更多的等待 这对于精益求精的嵌入式工程师来说 简直就是一个恶梦 低速器件的存取数据实在是太浪费资源 如何面对这种低速设备
  • 【日常】DBeaver中sql连接,局域网状态下

    投身外包大军 项目组使用内网 不能自己下软件 不能自己带U盘 上头的很 使用的DBeaver进行数据库的使用 碰到了sql连接的问题 记录一下 不得不说这个软件的图标有点可爱 一个小河狸 内网连接 不能联网 所以一般配套会给一个sql co
  • 父进程等待子进程终止 wait, WIFEXITED, WEXITSTATUS

    wait 的函数原型是 include
  • jqGrid 编辑完数据后能返回到当前位置的方法

    jqGrid 是一个js的jquery组件 虽然不轻便 但功能还是蛮强大的 也比较方便使用 在数据加载后 经常需要对其中的记录进行编辑 修改完后再返回时需要看到修改后的数据 一般采取重新加载的方法reloadGrid 但问题是列表中的数据因
  • STM8自学入门方向

    我还是我 今年计划自学学习STM8和汇编基础 STM8花了半个月 学的一点皮毛 对芯片有一定的了解了 学完后 发现可以拿到的资源远远没有32多 学习了内部大部分常用资源的应用 IO操作 定时器 IO中断 RS232 IIC 后面会发布我的总
  • 大数据时代下的个人知识管理

    前言 说到个人知识管理 在之前通过网络查询了一些资料 定义看起来让人蠢蠢欲动 作用是能快速找到自己收藏的文档 每个人或多或少都必须的有一些文件管理的习惯 管理就是一种习惯 利用专业的软件可以更容易的养成个人知识管理的习惯 当不小心清空了自己
  • c++双向列表释放_Python 列表List常见操作和错误总结

    一 列表的输入 即从控制台读取输入 然后创建列表 1 一维列表创建常见的方法有 当然 可以进一步简化成下面这样 其中第二句 在列表里用到了列表解析式 这是非常Pythonic的写法 酷炫 2 二维列表的输入和创建 二维列表复杂一些 可以以矩
  • Quartz-Spring[一]之MethodInvokingJobDetailFactoryBean配置任务

    Spring中使用Quartz的3种方法 MethodInvokingJobDetailFactoryBean implements Job extends QuartzJobBean 动态启动 暂定 添加 删除定时功能 可传参数 Quar
  • React之state、hooks性能分析

    目录 一 state 1 为什么使用setState 2 setState异步更新 3 如何获取异步的结果 4 setState一定是异步吗 5 源码分析 6 数据的合并 7 多个state的合并 二 为什么需要Hook 三 Class组件
  • stm32之iap实现应用(基于串口,上位机,详细源码)

    开发环境 Window 7 开发工具 Keil uVision4 硬件 stm32f103c8t6 篇幅略长 前面文字很多 主要是希望能让小白们理解 后面就是实现步骤 包括实现的代码 在研发调试的时候我们一般用烧录器下载代码 对于stm32