IAPによるSTM32ファームウェアアップグレードの分析と例

ほとんどのMCUは、IAPを介してオンチップフラッシュを読み書きして、ファームウェアのアップグレードを実現できます。

これは主にSTM32がIAPアップグレードを実装する方法です。

 

異なるカーネルのstm32モードはわずかに異なる場合があります。まず、STM32F103C8T6を例にとり、F1コアのIAPプロセスについて説明します。

1.オンチップFLASH読み取りおよび書き込み

IAPを実現するには、まずオンチップFLASHの読み取りと書き込みを実現する必要があります。

1.プログラム領域を消去し、ライブラリ関数FLASH_ErasePageを呼び出してページごとに消去します

int FlashErase(uint32_t addr)
{
	uint8_t retry_time;
  uint8_t i;
	retry_time = 200;
	
  FLASH_Unlock();
  for(i=0; i<55; i++)
  {
		FLASH_ErasePage((uint32_t)addr);
		while((FLASH->SR & FLASH_FLAG_EOP) == 0)
		{
			delay_ms(1);
			retry_time--;
			if(retry_time == 0)
			{
				return 1;
			}		
		}
		//标记清零
		FLASH->SR |= FLASH_FLAG_EOP;
    addr += 0x400;
		delay_ms(1);
  }
  FLASH_Lock();
	return 0;
}

2.オンチップフラッシュを読む

指定されたFLASHアドレスを読み取るための直接ポインタは問題ありません

#define PARA_START_ADDR1     0x0800f800 	//参数首地址


uint8_t *src;
src = (uint8_t *)PARA_START_ADDR1;
printf("flash data = %x\r\n",*(src));

バイトのストレージ順序には特別な注意を払う必要があります。これはデフォルトではリトルエンディアンである必要があります。たとえば、2バイトの変数が0xAA55に格納されている場合、*(src)は0x55を取得し、*(src + 1)は0xAAを取得します。

FALSH読み取りを使用して、作成されたプログラムが正しいかどうかを確認したり、保存されたパラメーターを確認したりできます。

 

3.オンチップFLASHを書き込む

//保存配置参数
void Save_Para(uint32_t addr)
{
        uint8_t *s;
        uint16_t i,data,len
    	uint8_t retry_time;

        s = (uint8_t *)&System_Para;    //要写入的数据地址

		FLASH_Unlock();
		FLASH_ErasePage(addr);
		retry_time = 200;
		while((FLASH->SR & FLASH_FLAG_EOP) == 0)
		{
			delay_ms(1);
			retry_time--;
			if(retry_time == 0)
			{
				break;
			}		
		}	
		//标记清零
		FLASH->SR |= FLASH_FLAG_EOP;	
		
		for(i=0; (i+1)<=len; i+=2)
		{
			data = *(uint16_t *)(s+i);
			FLASH_ProgramHalfWord(addr+i,data);
			retry_time = 10;
			while((FLASH->SR & FLASH_FLAG_EOP) == 0)
			{
				delay_ms(1);
				retry_time--;
				if(retry_time == 0)
				{
					break;
				}		
			}		
			//标记清零
			FLASH->SR |= FLASH_FLAG_EOP;		
		}
		if(i == len)
		{
			data = 0xff00+*(s+i);//ff00
			FLASH_ProgramHalfWord(addr+i,data);
			retry_time = 10;
			while((FLASH->SR & FLASH_FLAG_EOP) == 0)
			{
				delay_ms(1);
				retry_time--;
				if(retry_time == 0)
				{
					break;
				}		
			}		
			//标记清零
			FLASH->SR |= FLASH_FLAG_EOP;		
		}
		//标记清零
		FLASH->SR |= FLASH_FLAG_EOP;
		FLASH_Lock();
}

オンチップフラッシュを書き込む前に消去する必要があります。一度に少なくとも1ページを消去します。書き込み時には、ワードまたはハーフワード(4バイトまたは2バイト)を一度に書き込むことができます。

 

2つ目は、プログラム間をジャンプする

オンチップFLASHの読み取りと書き込みの方法を知っていると、IAPの実装を開始できます

1.まず、プログラムのアドレスを確認する必要があります

STM32汎用フラッシュの開始アドレスは0x08000000です。

IAPを実現するには、ブートプログラム(boot)とメインプログラム(app)が必要なので、2つのプロジェクトをビルドする必要があり、これら2つのプロジェクトのプログラムアドレスは異なります。

たとえば、STM32F103C8T6のプログラムスペースは64KBであり、スタートアッププログラム用に4KBを予約します。

その後:

スタートアッププログラムの開始アドレスは0x08000000、プログラムスペースは0x1000、
メインプログラムの開始アドレスは0x08001000、プログラムスペースは0xF000です。

このパラメータは、プロジェクトでここに設定されます

 

場合によっては、オフチップフラッシュを使用して端末を格納できます。プログラムは最初にオフチップで書き込まれ、次にアップグレードプロセス中に読み込まれて、オンチッププログラムを更新します。これにはオフチップメモリ​​が必要です。もちろん、オン-チップストレージも使用できます。

たとえば、F103C8T6の場合、スタートアッププログラムとして0x08000000から0x08001000までの4KBを使用します。

0x08001000から0x08008000まで、28KBがメインプログラムです

0x08008000から0x0800FFFFまでの32KBは、プログラムおよびパラメーターのバックアップ領域として使用されます。

このように、IAPはオフチップメモリ​​なしで実行できますが、プログラムスペースの半分が無駄になり、STM32オンチップFLASHの読み取りと書き込みの寿命はそれほど長くありません。

 

2.スタートアッププログラムがジャンプするように設定されており、メインプログラムにジャンプするコードをスタートアッププログラムに追加する必要があります

#define PGM_START_ADDR  0x08001000   //程序起始地址

void Check_And_Jump(void)
{
	//跳转到主程序
	/* Test if user code is programmed starting from address "ApplicationAddress" */
	if (((*(__IO uint32_t*)PGM_START_ADDR) & 0x2FFE0000 ) == 0x20000000)
	{
		/* Jump to user application */
			JumpAddress = *(__IO uint32_t*) (PGM_START_ADDR + 4);
			Jump_To_Main = (pFunction) JumpAddress;
			/* Initialize user application's Stack Pointer */
			__set_MSP(*(__IO uint32_t*) PGM_START_ADDR);
			Jump_To_Main();
	}
}

3.メインプログラム起動時の割り込みベクタオフセットを設定し、MAIN関数の先頭に追加します。オフセットは起動プログラム0x1000の長さです。

int main(void)
{
	NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x1000);   		//中断向量转移

このようにして、バーンインしてプログラムとメインプログラムをそれぞれ起動します。デバイスの電源がオンになると、最初にスタートアッププログラムに入り、次にメインプログラムにジャンプします。

 

3、ファームウェアのアップグレード

オンチップのFLASHの読み取りと書き込み、およびプログラム間のジャンプを組み合わせることで、オンチップのFALSHアップグレードを実現できます。

1.まず、メインプログラムのBINファイルが必要であり、この項目を設定する必要があります

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o D:\stm32Work\bin\wifi.bin  D:\STM32F1\WifiBoard\OBJ\LED.axf

最初のパラメーターはコンパイラーの場所、次に書き込み先の場所、最後にプロジェクトが配置されているaxfファイルの場所です。

この項目を構成した後、メインプログラムプロジェクトで[再構築]をクリックすると、上記で指定したパスにメインプログラムのbinファイルがあります。

2.BINファイルを転送します

メインプログラムのBINファイルは、特定の要件に応じて、ローカルまたはネットワーク経由で送信できます。簡単なテストとして、TTLシリアルポートを使用して送信することを検討してください。いくつかの一般的なシリアルポートデバッグツールを使用して、ファイルをHEX形式で直接転送したり、ホストコンピューターを作成していくつかのシリアル通信ルールを指定したりできます。

(1)一般的に、RAMが限られているため、1回の読み取りと書き込み用に小さいバッファを設定し、ファイルを複数回転送することをお勧めします。

(2)書き込み後、実際に書き込みに失敗しているので、もう一度読み取って正しいかどうかを確認することをお勧めします。

//写入256字节
FlashWriteMultiData((pack_index)*256+start_pos, PACK_LEN, (uint8_t *)RS232_RX_BUF+4);

//将写入的字节再读回来,检查校验和
FlashReadData((pack_index)*256+start_pos,PACK_LEN,checkBuf);

(3)単一パケットおよび全体パケットのデータの整合性を検証するための検証の追加を検討する必要があります。アップグレードに問題が発生すると、システム全体を復元することはできず、再プログラムすることしかできません。したがって、プログラムの作成で発生する可能性のあるエラーを回避するように注意する必要があります。

3.起動手順に戻ります

メインプログラムは、シリアルポートを介してデータを受信し、オンチップFALSHの後半に書き込み、再起動してスタートアッププログラムに戻ります。

//保存升级状态
SystemPara.IAP_Status = 0x0F;
SavePara(PARA_START_ADDR1);
//重启
delay_ms(500);
NVIC_SystemReset();

4.パラメータを保存するメカニズムが必要です

アップグレードが終了し、FALSHが再起動してスタートアッププログラムに戻ると、スタートアッププログラムは現在アップグレードプロセス中であるかどうかを認識しないため、現在のプロセスが正常にオンになっているかどうかを識別するために、FALSHに少なくとも1つのフラグが必要です。 FALSHでパラメータを保存する領域を直接指定することをお勧めします。

このように、スタートアッププログラムに入るときは、まず現在の状態を判断してから、メインプログラムに直接ジャンプするか、プログラムの点滅を開始するかを決定します。

 

次のコードは、変更せずにプロジェクトから直接取得したものであり、参照にのみ使用できます。プロセス全体を再度書き直すことはありません。

起動時に、プログラムバックアップ領域に保存されているプログラムをメインプログラムの場所にフラッシュします。

uint8_t IAP_Write(void)
{
	uint16_t j;
	uint32_t data;
	uint32_t left_len,addr=0;
	static uint16_t check_sum = 0;
	static uint16_t flash_read_checknum;
	
	left_len = System_Para.Filesize-4;

	while(left_len)
	{
		if(left_len > 256)
		{
			Flash_Read_Data(addr,256,Prog_data);
			addr += 256;
			left_len = System_Para.Filesize-4-addr;
			for(j=0;j<256;j++)
			{
				check_sum += Prog_data[j];
			}
		}
		else
		{
			Flash_Read_Data(addr,left_len,Prog_data);
			
			for(j=0;j<left_len;j++)
			{
				check_sum += Prog_data[j];
			}
			left_len = 0;
		}
	}

	flash_read_checknum = Flash_Read_Byte(System_Para.Filesize-2);
	flash_read_checknum = (flash_read_checknum<<8) + Flash_Read_Byte(System_Para.Filesize-1);

	if(flash_read_checknum == check_sum)
	{
		//校验和正确
		Flash_Read_Data(0,256,Prog_data);
		if(((*(__IO uint32_t*)Prog_data) & 0x2FFE0000 ) != 0x20000000)
		{
			//程序错误,直接返回主程序
			return 10;
		}
	}
	else
	{
		//校验和错误,直接返回主程序
		return 10;
	}
	
	total_data = System_Para.Filesize;
	
	//擦除程序区,失败就返回0
	if(FlashErase((uint32_t)PGM_START_ADDR))   //清除程序空间
	{
		//FLASH烧写擦除过程中失败,不能直接返回主程序,因为主程序已经没有代码了
		return 0;
	}
	
	FLASH_Unlock();
	addr = 0;
	
	left_len = total_data;
	
	while(left_len)
	{
		if(left_len > 256)
		{
			Flash_Read_Data(addr,256,Prog_data);
			for(j=0;j<256;j+=4)
			{
				data = *((uint32_t *)(Prog_data+j));
				if(Inner_FLASH_Program(addr+PGM_START_ADDR+j,data) == 0)
				{
				  return 0;	//失败
				}
			}			
			addr += 256;
			left_len = total_data-addr;
		}
		else
		{
			Flash_Read_Data(addr,left_len,Prog_data);
			
			for(j=0;j<left_len;j+=4)
			{
				data = *((uint32_t *)(Prog_data+j));
				if(Inner_FLASH_Program(addr+PGM_START_ADDR+j,data) == 0)
				{
				  return 0;
				}
			}
			left_len = 0;			
		}
	}
	FLASH_Lock();
	return 10;
}

//片内程序读取
void Flash_Read_Data(uint32_t data_addr,uint32_t len,uint8_t *addr)
{
	uint16_t i;
	uint8_t *src;
	
	data_addr += 0x8008000;

	src = (uint8_t *)data_addr;
  for(i=0;i<len;i++)
  {
    *(addr+i) = *(src+i);
  }
}

void Data_Init(void)
{
	uint8_t *dst;
  uint8_t *src;
  uint16_t i;
	uint16_t checksum = 0;

  //读取系统参数
  dst = (uint8_t *)&System_Para;
	src = (uint8_t *)PARA_START_ADDR1;
  if((*(src) == 0x55)&&(*(src+1) == 0xAA))   //参数没溢出
  {
		for(i=0;i<sizeof(System_Para);i++)
		{
			checksum += *(src+i);
			*(dst+i) = *(src+i);
		}

		//校验正确
		if(checksum == (*(src+1023)<<8) + *(src+1022))    //校验成功
		{
			if(System_Para.IAP_Status == 0x0F)	//FTP下载完成
			{
				if((System_Para.Filesize>0x100) && (System_Para.Filesize<0x10000))
				{
					if(IAP_Write() == 0)
					{
						delay_ms(100);
						NVIC_SystemReset();
					}
				}
				System_Para.IAP_Status = 0x00;
				Save_Para(PARA_START_ADDR1);
				NVIC_SystemReset();
			}
		}
	}
	Check_And_Jump();
}

メインプログラム、アップグレードプログラムをプログラムバックアップ領域に書き込みます。

void RS232_Rec_Deal(void)
{
	uint32_t i,j;
	uint8_t pack_type;
	uint16_t pack_index;
	uint8_t pack_check;
	uint8_t checksum;
	uint8_t * pos;
	uint8_t check_buf[256];
	uint8_t *addr;
	u8 dx;
	uint32_t start_pos = 0;
	

	if(RS232_RX_STA&0x8000)	//接收完成
	{
		if(RS232_RX_BUF[0] == 0xAA)	//串口升级
		{
            start_pos = 0x8008000;

			pack_type = RS232_RX_BUF[1];	//帧类型
			pack_index = ((uint16_t)RS232_RX_BUF[2]<<8)+RS232_RX_BUF[3];	//帧序号
			pack_check = RS232_RX_BUF[4+PACK_LEN];	//帧校验
			
			//算校验和
			checksum = 0;
			for(i=0;i<PACK_LEN+3;i++)
			{
				checksum += RS232_RX_BUF[1+i];
			}
			
			if(pack_type == UPDATE_START)	//启动帧
			{
				if(checksum == pack_check) //校验正确
				{
					Update_Sta.total_pack = ((uint16_t)RS232_RX_BUF[4]<<8)+RS232_RX_BUF[5];	//总包数
					Update_Sta.prog_check = ((uint16_t)RS232_RX_BUF[6]<<8)+RS232_RX_BUF[7];	//文件校验和
					memset(Update_Sta.pack_sta,0,256);
					FlashErase(start_pos);							//擦除片内
					Update_Rep(REP_STA,START_REC);
				}
				else
				{
					Update_Rep(REP_STA,DATA_ERR);
				}
			}
			else if(pack_type == UPDATE_DATA)	//数据帧
			{
				if(checksum == pack_check) //校验正确
				{
					//先写入,然后再读出来,检查是否正确
					Flash_Write_MultiData((pack_index)*256+start_pos, PACK_LEN, (uint8_t *)RS232_RX_BUF+4);
					Flash_Read_Data((pack_index)*256+start_pos,PACK_LEN,check_buf);
					for(i=0;i<PACK_LEN;i++)
					{
						if(RS232_RX_BUF[4+i] != check_buf[i])
						{
							break;
						}
					}
					if(i >= PACK_LEN)
					{
						Update_Sta.pack_sta[pack_index] = 1;
						Update_Rep(REP_REC,DATA_RECOK);	//接收成功

						if(pack_index == (Update_Sta.total_pack-1))	//最后一包
						{
							//中间漏包,则判定失败
							for(j=0;j<Update_Sta.total_pack;j++)
							{
								if(Update_Sta.pack_sta[j] == 0)
								{
									Update_Rep(REP_STA,DATA_ERR);
									RS232_RX_STA = 0;
									return;
								}
							}
							for(j=0;j<255;j++)
							{
							  if(check_buf[255-j] != 0)
								{
								  break;
								}
							}
							System_Para.Filesize = Update_Sta.total_pack*PACK_LEN-j;

							pos = (uint8_t *)start_pos;
							if(( *(pos+System_Para.Filesize-4) == ((TERMINAL_TYPE&0xff00)>>8)) \
								&&( *(pos+System_Para.Filesize-3) == (TERMINAL_TYPE&0xff)))
							{
								Update_Rep(REP_STA,UPDATA_OK);
								System_Para.IAP_Status = 0x0F;
								Save_Para(PARA_START_ADDR1);
								delay_ms(500);
								NVIC_SystemReset();
							}
							else
							{
							  Update_Rep(REP_STA,DATA_ERR);
							}
						}
					}
					else
					{
						Update_Rep(REP_REC,DATA_RECFAIL);
						Update_Rep(REP_STA,DATA_ERR);
					}
				}
				else
				{
					Update_Rep(REP_REC,DATA_RECFAIL);
				}
			}
		}
		RS232_RX_STA = 0;
	}
}	

上記のプロセスを参照すると、基本的に、オンチップFLASHを使用してIAPアップグレードを完了することができます。オフチップメモリ​​を使用する場合、方法は基本的に同じなので、これ以上は言いません。

 

おすすめ

転載: blog.csdn.net/zhangfls/article/details/111685866