リモートマイクロコントローラーのファームウェアアップデート

以前の記事の1つで、Cortex M0 +コアを備えたSAM D21マイクロコントローラー上のUSBブートローダーについて書きました。 フラッシュドライブを使用してファームウェアを更新することは非常に便利ですが、すべての場合に適しているわけではありません。 たとえば、デバイスへのアクセスが制限されているか問題がある場合、デバイスとの通信がリモートで確立されます。 このような場合、ブートローダーの開発は特別な注意を払って処理する必要があります。 そうしないと、エラーが発生した場合、「ブリック」を受け取る可能性が高くなり、自分の頭に膨大な数の問題が発生します。 そのような届きにくいデバイスの例は、7階の建物の正面にぶら下がっているスマートな建築用ランプの制御ボードです。



問題の声明



この記事の基礎となったプロジェクトでは、リモートデバイスとの通信は、 G3 PLC (電力線通信)規格に準拠した電力線を介して実行されます。 一見、通信方法は重要ではありません。 マイクロコントローラの場合、依然としてUART / I2C / SPI交換になります。 ただし、たとえば、この場合、これにより、ファームウェアのオンライン更新が不可能という形で制限が課せられます(ブートローダーがサーバーとの接続を確立して新しいファームウェアを受信する場合)。 PLCモデムを使用するのは難しい作業であり、ブートローダーセクションのサイズが十分ではありません。

したがって、次の要件が形成されました。







USBブートローダーに関する記事は、SAM D20 / D21ファミリーのメモリの整理に関する簡単な理論を提供するものであり、ここでは複製しません。 ATSAMD20G16は 、ターゲットマイクロコントローラーとして使用されます。 プロジェクトは、 ASF (Atmel Software Framework)を使用してIARで組み立てられます。



作業アルゴリズム



デバイスは常にブートローダーで起動し、EEPROMのコマンドに応じて、ファームウェアが更新されるか、現在のファームウェアのcrcの正当性が単純にチェックされます。 ブートローダーの最後に、エラーがない場合、アプリケーションへの移行が実行されます。

#define APP_START_ADDRESS 0x00004000 // brief Function for starting application // This function will configure the WDT module and enable it. The LED is // kept toggling till WDT reset occurs. static void start_application(void) { // Pointer to the Application Section void (*application_code_entry)(void); // Rebase the Stack Pointer __set_MSP(*(uint32_t *) APP_START_ADDRESS); // Rebase the vector table base address SCB->VTOR = ((uint32_t) APP_START_ADDRESS & SCB_VTOR_TBLOFF_Msk); // Load the Reset Handler address of the application application_code_entry = (void (*)(void))(unsigned *)(*(unsigned *)(APP_START_ADDRESS + 4)); // Jump to user Reset Handler in the application application_code_entry(); }
      
      





ブートローダーの一般的なアルゴリズムを次の図に示します。



メインコードが破損した場合の誤動作を防ぐため、マイクロコントローラが起動するたびにファームウェアのCRCチェックが実行されます。 計算されたCRCが計算されたCRCと一致しない場合の対処方法は、特定のアプリケーションごとに個別に決定されます。 赤色のLEDを際限なく点滅させることができ、バックアップファームウェアを埋めることができます(それを保存する場所を提供する必要があります)。

アプリケーションの一般的なアルゴリズムを下の図に示します。 ここでは、ファームウェアの更新に直接関連するもののみを紹介します。





アプリケーションのプロジェクト設定



アプリケーションでプロジェクトに計画されたものを実装するには、以下を実行する必要があります。



これはプロジェクト設定で行われます。



CRCカウント




これらの設定は多項式0x1021に対応し、コードを使用してカウントするときにバイトをスワップすることを忘れないでください。また、CRCは充填されたコードだけでなくメモリ全体でIARを使用して計算されるという事実も忘れないでください。 アプリケーションメモリ全体の内容でCRCを計算した後、最後にチェックサムにさらに2バイトを追加して結果を逆にする必要があります( EWARM_DevelopmentGuide.ENUを参照)。



CRC検証コード:



 void TestChecksum() { unsigned short calc = 0; unsigned char zeros[2] = {0, 0}; /* Run the checksum algorithm */ calc = slow_crc16(0, (unsigned char *) ChecksumStart,(ChecksumEnd - ChecksumStart+1)); /* Rotate out the answer */ calc = slow_crc16(calc, zeros, 2); /* Test the checksum */ if (calc != checksum) { abort(); /* Failure */ } } unsigned short slow_crc16(unsigned short sum, unsigned char *p, unsigned int len) { while (len--) { int i; unsigned char byte = *(p++); for (i = 0; i < 8; ++i) { unsigned long oSum = sum; sum <<= 1; if (byte & 0x80) sum |= 1; if (oSum & 0x8000) sum ^= 0x1021; byte <<= 1; } } return sum; }
      
      







アプリケーションのコードの場所の設定


メモリの始まりと割り込みベクトルのテーブルのアドレスを示します。







コード



作業には、次のASF / MKモジュールが必要です。



すべてのモジュールの初期化と構成に加えて、作業のロジックを確認する必要があります。

完全なmain.cコード
#include <asf.h>

#include "twi_driver.h"

#include "MCP7941x.h"

#include "at45db041d.h"

#include "init.h"

#include "utils.h"

// ------------------------------------------------ -----------------------------

// ---------------グローバル変数------------------------------- ----------

// ---------周辺インスタンスの構造------------------------------------ -

extern struct spi_module spi_master_instance;

extern struct spi_slave_instスレーブ;

extern struct tc_module tc_instance_tc2;

extern struct i2c_master_module i2c_master_instance;



// ------------------------周辺機器----------------------- ---------------------

// ------------------------------ i2c ----------------- ---------------------------

unsigned char twi_out_data [9];

unsigned char twi_in_data [8];

// ---------------------------- SPI ------------------- ---------------------------

extern unsigned char spi_in_data [ext_flash_page_size];

extern unsigned char spi_out_data [ext_flash_page_size];

unsigned char temp_buffer_write [ext_flash_page_size];

符号なしlong rtc_cnt;

// -------------------サイクルのカウンター-------------------------- -------------

符号なしint i、m;

unsigned char led_cnt;

// ------------------------ファームウェアで動作--------------------- -------------

unsigned int page_addr;

unsigned int CRC;

unsigned int CRC_read;

unsigned char zero [2] = {0,0};

unsigned int last_page_length;

unsigned int page_num;

unsigned int byte_amount;

unsigned int last_page_number;

const uint8_t flash_data [256];

// ------------------------------------------------ -----------------------------

// ------------------- init ---------------------------- ------------------------



void MC_init(void)

{

struct nvm_config config;



system_init();

SystemClock_Init();

configure_tc2();

configure_pins();

configure_spi_master();

configure_i2c_master();

nvm_get_config_defaults(&config);

nvm_set_config(&config);

}



void init_variables(void)

{

// ------------------------------ i2c ----------------- ---------------------------

clear_twi_in_buffer();

clear_twi_out_buffer();

// ---------------------------- SPI ------------------- ---------------------------

for(i = 0; i <ext_flash_page_size; i ++)temp_buffer_write [i] = 0;

rtc_cnt = 0;

// -------------------サイクルのカウンター-------------------------- -------------

led_cnt = 0;

// ------------------------その他----------------------- ------------------------

last_page_number = 0;

}



void external_init(void)

{

符号なしchar temp;

// ----------------外部eeprom ---------------------

//メモリが書き込み保護されていないことを確認します

clear_twi_in_buffer();

twi_read_bytes(twi_in_data、1、EEPROM_ADR、0xff);

if(twi_in_data [0]!= 0)

{

clear_twi_out_buffer();

twi_out_data [0] = 0xff;

twi_out_data [1] = 0;

twi_write_bytes(twi_out_data、2、EEPROM_ADR、EEPROM_OWN_METERS_1_STATUS);

}

// ----------------フラッシュ-------------------------------

//外部フラッシュのページサイズを確認します

temp = flash_wait(spi_delay);

// 264の場合、256を設定

if((temp&0x01)!= 0x01)set_page_size_256();

temp = 0;

}



//データをフラッシュにプログラミングするための簡単な関数

//この関数は、データがFlashページサイズより大きいかどうかを確認します。

//大きい場合、分割してページ単位で書き込みます。

//プログラミングするFlashページのパラメーターアドレス

//プログラムするデータを含むバッファへのparamバッファポインタ

//フラッシュにプログラムされるデータのparam lenの長さ(バイト単位)



static void program_memory(uint32_tアドレス、uint8_t *バッファー、uint16_t len)

{

//長さがFlashページサイズより大きいかどうかを確認します

if(len> NVMCTRL_PAGE_SIZE)

{

uint32_t offset = 0;

while(len> NVMCTRL_PAGE_SIZE)

{

//行の最初のページかどうかを確認します

if((address&0xFF)== 0)

{

//行を消去します

nvm_erase_row(アドレス);

}

// 1ページのデータをフラッシュに書き込みます

nvm_write_buffer(アドレス、バッファー+オフセット、NVMCTRL_PAGE_SIZE);

//プログラムするアドレスをインクリメントします

アドレス+ = NVMCTRL_PAGE_SIZE;

//データを含むバッファのオフセットをインクリメントします

オフセット+ = NVMCTRL_PAGE_SIZE;

//長さをデクリメントします

len-= NVMCTRL_PAGE_SIZE;

}

//プログラムするデータが残っているかどうかを確認します

if(len> 0)

{

//フラッシュにデータを書き込みます

nvm_write_buffer(アドレス、バッファー+オフセット、len);

}

}

他に

{

//行の最初のページかどうかを確認します)

if((address&0xFF)== 0)

{

//行を消去します

nvm_erase_row(アドレス);

}

//フラッシュにデータを書き込みます

nvm_write_buffer(アドレス、バッファー、len);

}

}



//アプリケーションを起動するための簡単な関数

//この関数は、WDTモジュールを構成して有効にします。 LEDは

// WDTリセットが発生するまでトグルし続けます。

静的void start_application(void)

{

//アプリケーションセクションへのポインタ

void(* application_code_entry)(void);

//スタックポインターをリベースします

__set_MSP(*(uint32_t *)APP_START_ADDRESS);

//ベクターテーブルのベースアドレスをリベースします

SCB-> VTOR =((uint32_t)APP_START_ADDRESS&SCB_VTOR_TBLOFF_Msk);

//アプリケーションのリセットハンドラーアドレスを読み込みます

application_code_entry =(void(*)(void))(unsigned *)(*(unsigned *)(APP_START_ADDRESS + 4));

//アプリケーションのユーザーリセットハンドラーにジャンプします

application_code_entry();

}



unsigned int slow_crc16(unsigned short sum、unsigned char * p、unsigned int len)

{

while(len--)

{

int i;

unsigned char byte = *(p ++);



for(i = 0; i <8; ++ i)

{

符号なしlong osum = sum;

sum << = 1;

if(バイト&0x80)

sum | = 1;

if(osum&0x8000)

sum ^ = 0x1021;

バイト<< = 1;

}

}

戻り値;

}



void MC_reset(void)

{

tc_reset(&tc_instance_tc2);

i2c_master_reset(&i2c_master_instance);

spi_reset(&spi_master_instance);



}

// ------------------------ !!!!!! MAIN !!!!!! ----------- --------------------------

int main(void)

{

unsigned int temp;

符号なしint j;

符号なしchar k;



init_variables();

MC_init();



for(i = 0; i <20; i ++)

{

LED_2_TOGGLE();

delay_ms(100);

}



system_interrupt_enable_global();

external_init();

// ------------アップグレード用のコマンドがあるかどうかを確認します-------------------------

for(i = 0; i <8; i ++)twi_in_data [i] = 0;

//外部EEPROMからアップグレードを読み取ります

twi_read_bytes(twi_in_data、4、EEPROM_ADR、EEPROM_UPGRADE);

//アップグレードコマンド

if(twi_in_data [0] == 0x55)

{

for(i = 0; i <20; i ++)

{

LED_1_TOGGLE();

delay_ms(100);

}

//ファームウェアの長さとその中のcrcの位置を決定します

//コードだけでなく、PLC経由ですべてのメモリを転送するため

//コードのみを渡す方法がわかるまで

for(page_addr = 2; page_addr <194; page_addr ++)

{

//ページを読む

Continuous_low_freq_read(spi_in_data、page_addr、0、ext_flash_page_size);

temp = 0;

for(j = 0; j <ext_flash_page_size; j ++)

{

if(spi_in_data [j] == 0xff)

{

temp ++;

}

そうでなければtemp = 0;

}

//最初の空白ページ

if(temp == ext_flash_page_size)

{

//前のページを読む

page_addr--;

Continuous_low_freq_read(spi_in_data、page_addr、0、ext_flash_page_size);

last_page_number = page_addr;

//最後からのコードで最後のページを分析します

//最後のバイトがffでない場合、これはCRCです

if(spi_in_data [ext_flash_page_size-1]!= 0xff)

{

CRC_read = spi_in_data [ext_flash_page_size-2];

CRC_read << = 8;

CRC_read | = spi_in_data [ext_flash_page_size-1];

}

//最後のバイトがffの場合、場所crcを探します

他に

{

i = ext_flash_page_size-1;

while((spi_in_data [i] == 0xff)&&(i> 0))

{

i--;

}

CRC_read = spi_in_data [i];

CRC_read << = 8;

CRC_read | = spi_in_data [i-1];

//これは最後のバイトの位置ですcrc

last_page_length = i + 1;

休憩;

}

}

}

}

// --------------現在のファームウェアのCRCを確認します-----------------------------

他に

{

kは0です。

CRC = 0;

CRC_read = 0xffff;

//収束しない場合、5回試行します

//収束しない場合は、青色になるまで赤色のLEDを点滅させます)

while((CRC!= CRC_read)&&(k <5))

{

k ++;

CRC = 0;

CRC_read = 0;

//ファームウェアの長さとその中のcrcの位置を決定します

for(page_addr = 0x4000; page_addr <0x10000; page_addr + = NVMCTRL_PAGE_SIZE)

{

//ページを読む

nvm_read_buffer(page_addr、spi_in_data、NVMCTRL_PAGE_SIZE);

temp = 0;

for(j = 0; j <64; j ++)

{

if(spi_in_data [j] == 0xff)

{

temp ++;

}

そうでなければtemp = 0;

}

//最初の空白ページ

if(temp == NVMCTRL_PAGE_SIZE)

{

//前のページを読む

page_addr- = NVMCTRL_PAGE_SIZE;

nvm_read_buffer(page_addr、spi_in_data、NVMCTRL_PAGE_SIZE);

last_page_number = page_addr;

//最後からのコードで最後のページを分析します

//最後のバイトがffでない場合、これはCRCです

if(spi_in_data [NVMCTRL_PAGE_SIZE-1]!= 0xff)

{

CRC_read = spi_in_data [NVMCTRL_PAGE_SIZE-2];

CRC_read << = 8;

CRC_read | = spi_in_data [NVMCTRL_PAGE_SIZE-1];

}

//最後のバイトがffの場合、場所crcを探します

他に

{

i = NVMCTRL_PAGE_SIZE-1;

while((spi_in_data [i] == 0xff)&&(i> 0))

{

i--;

}

CRC_read = spi_in_data [i];

CRC_read << = 8;

CRC_read | = spi_in_data [i-1];

//これは最後のバイトの位置ですcrc

last_page_length = i + 1;

休憩;

}

}

}

// crcを直接検討します

for(page_addr = 0x4000; page_addr <last_page_number + 1; page_addr + = NVMCTRL_PAGE_SIZE)

{

//ページを読む

nvm_read_buffer(page_addr、spi_in_data、NVMCTRL_PAGE_SIZE);

temp = 0;

//読み取りで隣接するバイトをスワップします

for(j = 0; j <NVMCTRL_PAGE_SIZE; j + = 2)

{

temp = spi_in_data [j];

spi_in_data [j] = spi_in_data [j + 1];

spi_in_data [j + 1] = temp;

}

if(page_addr == last_page_number)

{

//ページのcrcをCRCリンカーに個別に考慮します

CRC = slow_crc16(CRC、spi_in_data、last_page_length-2);

//およびCRCリンカの後

temp = NVMCTRL_PAGE_SIZE-last_page_length;

CRC = slow_crc16(CRC、&(spi_in_data [last_page_length])、temp);

}

//コードがページ全体にある場合

他に

{

CRC = slow_crc16(CRC、spi_in_data、NVMCTRL_PAGE_SIZE);

}

}

for(i = 0; i <NVMCTRL_PAGE_SIZE; i ++)spi_in_data [i] = 0xff;

while(page_addr <0x10000)

{

CRC = slow_crc16(CRC、spi_in_data、NVMCTRL_PAGE_SIZE);

page_addr + = NVMCTRL_PAGE_SIZE;

}

CRC = slow_crc16(CRC、ゼロ、2);

} // end o fwhile((CRC!= CRC_read)&&(k <5))

// crcが同意しなかった場合

if(CRC!= CRC_read)

{

一方(1)

{

LED_1_TOGGLE();

delay_ms(500);

}

}

// CRCが収束した場合

他に

{

MC_reset();

start_application();

}

}

CRC = 0;

//////////////////////////////////////////////////// ///////////////////////////////

// ------------------------------メインプログラム---------------- -------------------

//////////////////////////////////////////////////// ///////////////////////////////

一方(1)

{

// CRCをチェックするページを読み取ります

for(page_addr = 2; page_addr <last_page_number + 1; page_addr ++)

{

//ページを読む

Continuous_low_freq_read(spi_in_data、page_addr、0、ext_flash_page_size);

temp = 0;

//読み取りで隣接するバイトをスワップします

for(j = 0; j <ext_flash_page_size; j + = 2)

{

temp = spi_in_data [j];

spi_in_data [j] = spi_in_data [j + 1];

spi_in_data [j + 1] = temp;

}

if(page_addr == last_page_number)

{

//ページのcrcをCRCリンカーに個別に考慮します

CRC = slow_crc16(CRC、spi_in_data、last_page_length-2);

//およびCRCリンカの後

temp = ext_flash_page_size-last_page_length;

CRC = slow_crc16(CRC、&(spi_in_data [last_page_length])、temp);

}

//コードがページ全体にある場合

他に

{

CRC = slow_crc16(CRC、spi_in_data、ext_flash_page_size);

}

}

//外部フラッシュのページ番号からアドレスをバイトに変換します

page_addr << = 8;

for(i = 0; i <NVMCTRL_PAGE_SIZE; i ++)spi_in_data [i] = 0xff;

while(page_addr <0xc200)

{

CRC = slow_crc16(CRC、spi_in_data、NVMCTRL_PAGE_SIZE);

page_addr + = NVMCTRL_PAGE_SIZE;

}

CRC = slow_crc16(CRC、ゼロ、2);



// CRCが一致する場合

//もう一度読み取り、フラッシュに書き込みます

//それ以外の場合は、何らかのフラグを付けてアプリケーションに移動します

// CRCファームウェアのロールバックについては考慮していません:CRCのサイズと場所がわかりません

//ただし、デフォルトでは両方とも0

if(CRC == CRC_read)

{

page_addr = 0x4000;

for(i = 0; i <15; i ++)

{

//新しいファームウェアを消去して書き込みます

Continuous_low_freq_read(spi_in_data、i、0、NVMCTRL_PAGE_SIZE);

program_memory(page_addr、spi_in_data、256);

page_addr + = 256;

delay_ms(10);

}

twi_out_data [0] = EEPROM_FW_RESULT;

twi_out_data [1] = 0xda;

twi_write_bytes(twi_out_data、2、EEPROM_ADR、EEPROM_FW_RESULT);

}

他に

{

twi_out_data [0] = EEPROM_FW_RESULT;

twi_out_data [1] = 0xad;

twi_write_bytes(twi_out_data、2、EEPROM_ADR、EEPROM_FW_RESULT);

}

//点滅しているコマンドのみを削除します

//ファームウェアパラメータを消去しません

delay_ms(100);

twi_out_data [0] = EEPROM_UPGRADE;

twi_out_data [1] = 0xff;

twi_write_bytes(twi_out_data、2、EEPROM_ADR、EEPROM_UPGRADE);

delay_ms(100);

for(j = 2; j <2048; j ++)

{

//アドレスがビニングされる最初の2ページを除いて、フラッシュ全体を消去します

erase_page(j);

temp = flash_wait(spi_delay);

temp = 0;

}

for(i = 0; i <20; i ++)

{

LED_1_TOGGLE();

delay_ms(300);

}

MC_reset();

start_application();



} // whileの終わり(1)

} //メインの終わり




All Articles