開發環境: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還有很多要完善的地方,比如加入超時機制等。希望以上能起到提供一些參考的作用。有錯誤或者疑問都可以提出來,謝謝。