历史上的今天
返回首页

历史上的今天

今天是:2025年08月20日(星期三)

正在发生

2019年08月20日 | stm32之iap实现应用(基于串口,上位机,详细源码)

2019-08-20 来源:eefocus

开发环境:Window 7

开发工具:Keil uVision4

硬件:stc32f103c8t6


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

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


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,那么上位机配置就要跟其保持一致。


创建


#ifndef __USART1_INIT_H__

#define __USART1_INIT_H__


#include "stm32f10x.h"

#include


void USART1_Configuration(void);//打印输出串口初始化

void sengdata(unsigned char data);

unsigned char waitdata(void); 


#endif


创建


#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);

}

}

}


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


创建


#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__ 


创建


#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来写给上位机的应答的。


创建


#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__


创建


#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        cs ^= data[i];

    return cs;

}


void Getcommand(void) //AN2606 page10

unsigned char i;

sengdata(cmd_count);

sengdata(bootloaderversion);

for(i=0;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

推荐阅读

史海拾趣

ETRI公司的发展小趣事

作为一家具有全球视野的研究机构,ETRI也积极拓展海外合作。他们与金陵华软投资集团(CSC)共同签署了战略合作备忘录,双方将在新技术对接、投资服务、资源共享、信息交流等方面实现合作。这一合作不仅有助于ETRI拓展海外市场,也将为双方带来更多的发展机遇。

DPA Components International公司的发展小趣事

DPA Components International公司非常重视企业文化的建设。公司倡导“以人为本、追求卓越”的企业文化,注重员工的培训和发展。公司为员工提供丰富的培训资源和职业发展机会,鼓励员工不断创新和进取。同时,DPA还建立了完善的激励机制和福利制度,让员工感受到公司的关怀和温暖。在这种积极向上的企业文化氛围中,员工的工作积极性和创造力得到了充分激发,为公司的持续发展提供了强大动力。

Amperite Co公司的发展小趣事

DPA Components International公司非常重视企业文化的建设。公司倡导“以人为本、追求卓越”的企业文化,注重员工的培训和发展。公司为员工提供丰富的培训资源和职业发展机会,鼓励员工不断创新和进取。同时,DPA还建立了完善的激励机制和福利制度,让员工感受到公司的关怀和温暖。在这种积极向上的企业文化氛围中,员工的工作积极性和创造力得到了充分激发,为公司的持续发展提供了强大动力。

CYAN公司的发展小趣事

随着5G技术的兴起,CYAN敏锐地捕捉到了市场的变化。公司投入大量研发资源,成功开发出一款基于5G技术的高性能网络路由器。这款路由器不仅具有超高的数据传输速度和稳定性,还具备智能管理和安全保护功能,满足了市场对高性能网络设备的需求。

Bogen Communications Inc公司的发展小趣事

在电子行业的激烈竞争中,Bogen Communications Inc公司凭借一项革命性的技术创新,迅速崭露头角。该公司研发出一种新型的音频处理芯片,极大地提升了通信设备的音质和性能。这一创新不仅赢得了市场的广泛认可,还为公司带来了大量的订单和合作伙伴。随着技术的不断完善和市场的扩大,Bogen Communications Inc逐渐成为了音频通信领域的领导者。

A/D Electronics Inc公司的发展小趣事

A/D Electronics Inc在创立初期,以其卓越的技术研发团队在模拟到数字转换器(ADC)领域取得了重大突破。公司研发出一款高精度、低噪声的ADC芯片,这一创新产品迅速在市场中获得认可,为公司的初步发展奠定了坚实基础。随着技术的不断迭代,A/D Electronics Inc陆续推出了一系列高性能的电子产品,满足了市场对于高效、稳定电子元件的日益增长需求。

问答坊 | AI 解惑

有关基准源的问题

有关基准源的问题 我在电路中要用到1.25V和2.5V这两个基准电压                                       &nbs ...…

查看全部问答>

模电、数电教程,有需要的进来

本帖最后由 paulhyde 于 2014-9-15 08:55 编辑  …

查看全部问答>

基于LPC2478的嵌入式智能胰岛素泵的设计

 概述   当前全球范围内,每10秒钟就有一个糖尿病患者因并发症死亡,在同一个10秒钟内,又会新增两例糖尿病患者;受糖尿病影响的人口总数约为2.46亿,预计在未来20年内糖尿病患者的数量会上升至3.8亿。糖尿病是一种终身代谢性疾病,若能得到有效 ...…

查看全部问答>

杭州 WinCE Driver本月需要2名!及其他岗位

大家好,我是猎头Kevin,现在急聘2位wince和windows平台的驱动开发,base杭州,本科3~4年,月薪8~10K,注大型企业如果技术精湛年薪20万以下都可以谈。有没有人选帮忙推荐啊?我的联系方式是yaochen21@msn.com手机15857144628,急聘中,所以发这里, ...…

查看全部问答>

如何改变WINCe系统的分辨率

如何改变WINCe系统的分辨率,是否可以通过注册表来修改呢?发现弹出窗口太大了!…

查看全部问答>

cetk 测试camera时挂死

cetk测试camera的时候,还没有到驱动就挂死了,真是奇怪。困扰了好久,请各位支支招,非常感谢。 DSHOW API: ShellProc(SPM_LOAD_DLL, ...) called                      ...…

查看全部问答>

DM9000读写问题

在对DM9000芯片读ID时,ID读不对,读出的数据是相同的,都是第一个读到的内容,VIDL  0x46,整个就成了0x46464646。不知道是什么原因,请大家指点下,排除硬件问题…

查看全部问答>

WinCE6.0关于总线访问

各位大侠好:     小弟有一事征询大家意见。     最近在WinCE6.0上开发程序,涉及到总线访问的事情,就是需要去读写一个固定的物理地址。问是否要单独写一个 物理地址访问的驱动啊, 在驱动里面MmMapIoSpace后,再去读写物 ...…

查看全部问答>

一个弱弱的问题,想学习关于单片机

本人计算机毕业2年,现在从事.net的WEB开发,但是自己越干越没有意思,想学学单片机,请问如何下手呢,在学校里面学过一些简单的单片机的知识和汇编语言,请问实际工作中,单片机方面是主要从事什么样的工作?是不是要学习单片机,自己必须买单片机 ...…

查看全部问答>

工作三年,换工作,何去何从。。。

说一下背景:          本人09年小二本毕业,毕业后在上海工作一年,后来深圳,算起来总共工作时间为三年,三年说长不长,说短也不短,由于这两三年一直工作在小公司,主要工作就是单片机的底层开发,还有就是arm7, ...…

查看全部问答>