stm32 yazılım güncelleme

STM32 Uzaktan Gömülü Yazılım Güncelleme


Merhabalar, bu içeriği bootloader serisinin son yazısı olarak düşünebiliriz. Şayet direkt olarak bu yazıyla karşılaştıysanız ancak konu kümülatif bir şekilde ilerlediği için, şuradan diğer içerikleri incelemeniz sizin için faydalı olacaktır. Daha önce Duru isminde bir non-blocking, kullanımı rahat ve farklı MCU ailelerinden üyelerine port edilmesi mümkün ve rahat olan bir framework çalışmamı yayınlamıştım. Bugün ki uygulamamız yine o yapı üzerinde çalışacaktır.

Ne yapacağımızı daha rahat anlamak adına aşağıda bulunan görseli oluşturdum. Görsel Modemin (UC20 QUECTEL) ayağa kaldırılması gibi süreçleri içermiyor. Periyodik olarak backend tarafında yeni bir yazılım olup olmadığını sorguladığımız kısım özelinden başlıyor.

Belirlediğim formata göre sunucu üzerinde dosya şu şekilde barınmakta.

 can_%u.%u.%u.bin 
      |  | +---- bugfix-id
      |  + ----- minor-id
      + -------- major-id

Dosyanın hazırlanma sürecinde derleyici üzerinde yapılması gerekenlerden daha önce bahsetmiştim. Şimdi ise dosyanın içeriğinden bahsedeyim. ‘projeadı.bin’ dosyası oluştuktan sonra bu dosyayı Hex Editor programı üzerinden açarak dosyanın CRC32’sini buradan hesaplayarak ekliyorum ve ardından yayına verilecek versiyon damgasını 4 byte şeklinde ekliyorum.

Şimdi yazılım aşamasında Duru için gerekli yapıyı hazırlayalım, bunun için öncelikle ilgili callbackleri hazırlayalım. Ayrıca ilgili AT komutlarının kullandığınız modeme göre değişiklik gösterebileceğini unutmayınız.

/* İlgili sunucuya modem üzerinden ftp kanalı açılır */
uint8_t ftp_connect(void* self)
{	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
	
	char cr = {0x0D};
	
	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPOPEN=\"%s\",%i%c","ftpservername", 21, cr);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Sunucu üzerinde bulunan dosyalar listelenir, ilgili dosyanın olması durumunda yeni versiyon olup olmadığı flash üzerinde saklanılan adresten sorgulanır */
uint8_t parse_list_file (void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "CONNECT\r\n");
		
		if (ret != NULL)
		{
			ret = ret + sizeof("CONNECT\r\n");
			
			int i = 0;
			
			memset(gsm_uart.update_file_name, 0, sizeof(gsm_uart.update_file_name));
			
			while (*ret != '\r')
			{
					gsm_uart.update_file_name[i] = *ret++;
					i++;
			}

			ret = strstr(gsm_uart.update_file_name, "can_");
			
			if (ret != NULL)
			{
				ret = ret + sizeof("can");
				
				sscanf(ret, "%u.%u.%u", &gsm_uart.major, &gsm_uart.minor, &gsm_uart.bugfix);
				
				uint32_t current_version = *(uint32_t* )VERSION_ADDR;
				
				if (current_version == 0xFFFFFFFF)
				{
					is_any_new_firmware = true;
				}
				
				else
				{
					uint8_t major = current_version >> 24;
					uint8_t minor = current_version >> 16;
					uint8_t bug_fix = current_version >> 8;			
					
					char cv[10] = {0};
				
					snprintf(cv, sizeof(current_version), "%u.%u.%u", major, minor, bug_fix);
				
					int ret_version = comp_version(cv, ret);
					
					if (ret < 0)
					{
						is_any_new_firmware = true;
					}
					
					//Yeni update degil ise direkt olarak çik.
					else
					{
						struct command_machine* cm = (struct command_machine*)self;
						cm -> command_index = cm -> command_index + 3;
					}
				}
			}
		}	
	}
		
	return 0;
}
/* Dosya Modemin RAM bölgesine indirilir */
uint8_t ftp_get_file(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;

	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPGET=\"%s\",\"RAM:can.bin\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Dosyanın boyutu sunucudan istenilir */
uint8_t ftp_get_size(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
		
	char cmd[60] = { 0 };	
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPSIZE=\"%s\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
extern bool is_any_new_firmware;
extern float file_size;

/* Dosyanın boyutu ve dosya sorgulanır */
uint8_t ftp_file_size_cb(void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "OK\r\n\r\n");
		
		if (ret != NULL)
		{
			if (strstr(ret, "627") || strstr(ret, "550"))
			{
				is_any_new_firmware = false;
			}
			
			else 
			{
				ret = strstr(ret, "0,");
				
				if (ret)
				{
					ret = ret + 2;
					
					file_size = atof(ret);
					
					is_any_new_firmware = (file_size > 0) ? true : false;
				}
			}
		}
	}
	
	return 0;
}
/* Versiyonlar karşılaştırılır */
static int comp_version (const char* cv_version1, const char* version2 ) {
    unsigned major1 = 0, minor1 = 0, bugfix1 = 0;
	
    unsigned major2 = 0, minor2 = 0, bugfix2 = 0;
	
    sscanf(cv_version1, "%u.%u.%u", &major1, &minor1, &bugfix1);
	
    sscanf(version2, "%u.%u.%u", &major2, &minor2, &bugfix2);
	
    if (major1 < major2) return -1;
    if (major1 > major2) return 1;
    if (minor1 < minor2) return -1;
    if (minor1 > minor2) return 1;
    if (bugfix1 < bugfix2) return -1;
    if (bugfix1 > bugfix2) return 1;
    return 0;
}
/* Eğer herhangi bir güncelleme yok ise sorgulama durdulur */
int reset (void* self)
{
	if (self != NULL)
	{		
		struct command_machine* cm = (struct command_machine*)self;
		
		cm -> command_index = cm -> command_index + 3;
		
		//machine_stop_current(self);
	}
	
	return 0;	
}

Gelelim ilgili komutları ve komut pencerelerini hazırlamaya,

struct command ftp_open = {"FTP", "QFTPOPEN: 0,0\r\n", NULL, NULL, NULL, 2000, &ftp_connect };

struct command ftp_set_current_dir = {"AT+QFTPCWD=\"/\"\r\n", "QFTPCWD: 0,0", NULL, NULL, NULL, 3000, NULL };

struct command ftp_get_list_file_names = {"AT+QFTPNLST=\"/\",\"COM:\"\r\n", "+QFTPNLST:", NULL, NULL, &parse_list_file, 3000, NULL, reset};

struct command ftp_get_file_size = {"get_size", "QFTPSIZE:", NULL, NULL, &ftp_file_size_cb, 3000, &ftp_get_size };

struct command ftp_get_file_to_ram = {"get_file", "QFTPGET:", NULL, NULL, NULL, 3000, &ftp_get_file };

struct command ftp_close = {"AT+QFTPCLOSE\r\n", "+QFTPCLOSE: 0,0", NULL, NULL, NULL, 1500, NULL };

struct command ftp_deactivate_pdp = {"AT+QIDEACT=1\r\n", "OK\r\n", NULL, NULL, NULL, 4000, NULL };
const struct command* ftp_command_list_table[] = 
{
	&ftp_open,
	&ftp_set_current_dir,
	&ftp_get_list_file_names,
	&ftp_get_file_size,
	&ftp_get_file_to_ram,
	&ftp_close,
	&ftp_deactivate_pdp,	
};

Ve yeni bir güncelleme var ise artık dosyamızı sd karta yazdırabiliriz.

		if (is_any_new_firmware  && get_command_window_status(&command_machine_t, "FTP"))
		{
			// Herhangi bir yarım kalma durumları için dosya var olup olmaması sınanır.
			if ((fr = f_open(&file, "can.bin\0", FA_READ)) == FR_NO_FILE)
			{
					//Nothing.
			}
			
			else
			{
				f_close(&file);
				
				f_unlink("can.bin\0");
			}
			
			temp = file_size / 1000;
			
			temp_remainig = (file_size) - (temp * 1000);
		
			uart_tx_platform_specific((uint8_t *)"AT+QFOPEN=\"RAM:can.bin\",2\r\n", strlen("AT+QFOPEN=\"RAM:can.bin\",2\r\n"));
		
			HAL_Delay(500);
			
			while(temp-- > -1)
			{		
				int data_size = temp > -1 ? 1000 : temp_remainig;
			
				char temp_buf[75] = { 0 };
				
				snprintf(temp_buf, sizeof(temp_buf), "AT+QFREAD=3000,%d\r\n", 1000);
				
				clear_gsm_uart(&gsm_uart);	
				
				uart_tx_platform_specific((uint8_t *)temp_buf, strlen(temp_buf));
				
				HAL_Delay(1500);
			
				if (strlen(gsm_uart.buffer) > 0)
				{						
					char* ret = NULL;
					
					ret = strstr(gsm_uart.buffer, "CONNECT");
					
					if (ret != NULL)
					{
						ret = ret + sizeof("CONNECT");
						
						while (*ret++ != '\n');
						
						if ((fr = f_open(&file,(char *)"can.bin\0", FA_OPEN_APPEND | FA_WRITE)) == FR_OK)
						{												
							UINT bw;
							
							f_write(&file, ret, data_size, &bw);  
							
							fr = f_close(&file);	
						}						
					}
				}
			}
			
			NVIC_SystemReset();
		}

Aynı MCU içerisinde daha önce yazmış olduğum STM32 SD KART ÜZERİNDEN BOOT ETMEK içeriğinde bulunan yazılım her reset sonrasında çalıştırılmakta.

Eğer modeminiz destekliyorsa, ilgili dosyayı modemin RAM bölgesine indirilmesini sağlamak gerçekten güzel bir kolaylık sağlamakta. Buna ek olarak ilgili SSL sertifikalarını kullanarak rahatlıkla FTPS işlemini de yapabiliyorsunuz.

Sözün özü; Duru kütüphanesini kullanarak güncelleme yapmak bu kadar basit 🙂

Herkese iyi çalışmalar dilerim.

STM32 SD KART ÜZERİNDEN BOOT ETMEK


Merhabalar, daha önce burada ve burada bulunan yazılarımı okumadıysanız, bakmanızda fayda olabilir. Daha önce o başlıklarda bahsetmiş olduğum kısımlara tekrar tekrar değinmeyeceğim için kopukluklar yaşanabilir.

Bu yazıda bahsetmeyi çok istediğim ama bahsetmeyeceğim şunlar var :

  • Secure Bootloader (Bilmediğim bir konu)
  • Başlıktan da anlaşılacağı gibi sd kart üzerinden boot edeceğiz ama o dosya sd karta nasıl geldi ? (Bundan başka bir yazıda OTA ile ilgili üretmeyi planlığım içerikte bahsedeceğim babajım)

Bu yazıda bahsedeceğim ve olası çözümlerini implemente ettiğimi düşündüğüm şunlar var :

  • Dosyanın bütünlüğünü(integrity) kontrol ettiğimiz bir yapı.
  • Cihazın anlık enerji kesintilerinde yazma işlemlerinin yarım kalmasına karşın durumu handle edebilmesi. Daha önce yukarda belirttiğim linklerde bulunan st’nin kendi sistem bootloader programlarında bu bir problemdi. (Burada bir çok case olduğu için, ya şöyle bir şey olursa diye aklınıza bir şey gelirse ve o durum atlanmışsa bildirirseniz çok sevinirim. Kahve ısmarlarım bende)
  • Yazılımın versiyonun kontrolü işlemi.

Enerjin düşük ise ve 30 saniye vaktin var ise şuraya tıklayarak hayata dönmene yardımcı olabilirim.

Bildiğiniz, karşılaştığınız gibi bir cihazın kutulandıktan ya da fiziksel olarak sizden ayrıldıktan sonra yazılımın doğası gereği yapılan yeni geliştirmelere her zaman açık olması gerekebiliyor.

Uygulamanın yapmaya çalıştığı işi şu resim ile özetleyebiliriz.

ST’nin flash bölümünde birden çok ve farklı boyutlarda olmak üzere sektörler mevcut. Kullandığım mikrodenetleyici STM32F446xx serisine ait. Ve bu seri için 8 farklı sektör mevcut. Ben uygulama kodumu 128kb olarak parçalanmış 7.sektörde çalıştırırken, bootloader uygulamamı ise 0.sektörde konumlandıracağım. Bu konumlandırma işleminin KEIL IDE’si üzerinde nasıl gerçekleştirileceğini daha önce yukarda belirttiğim linklerde bahsetmiştim.

İlgili dosyanın metadatası ve proje özelinde oluşturduğum fonksiyonlar şu şekilde.

#ifndef BOOT_H_INCLUDED
#define BOOT_H_INCLUDED

#include "stdint.h"

#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */

#define APP_ADDRESS (ADDR_FLASH_SECTOR_7)						 		/* Image Location */

#define FLASH_USER_START_ADDR   (APP_ADDRESS)   /* Start @ of user Flash area */

#define FLASH_USER_END_ADDR     ((uint32_t)0x0807FFFF)    /* End @ of user Flash area */
																					 
#define VERSION_ADDR ((uint32_t)(FLASH_USER_END_ADDR - 3))

typedef struct meta_data meta_data_t;
struct meta_data
{
	uint8_t crc_val[4];
	
	uint8_t version[4];
};	

void jump_to_app(void);

int erase_flash_sector(void);

#endif
#include "stm32f4xx.h"                  // Device header
#include "boot.h"

static uint8_t get_sector(uint32_t address)
{	
	uint8_t sector = 0;
  
  if((address < ADDR_FLASH_SECTOR_1) && (address >= ADDR_FLASH_SECTOR_0))
  {
    sector = FLASH_SECTOR_0;  
  }
  else if((address < ADDR_FLASH_SECTOR_2) && (address >= ADDR_FLASH_SECTOR_1))
  {
    sector = FLASH_SECTOR_1;  
  }
  else if((address < ADDR_FLASH_SECTOR_3) && (address >= ADDR_FLASH_SECTOR_2))
  {
    sector = FLASH_SECTOR_2;  
  }
  else if((address < ADDR_FLASH_SECTOR_4) && (address >= ADDR_FLASH_SECTOR_3))
  {
    sector = FLASH_SECTOR_3;  
  }
  else if((address < ADDR_FLASH_SECTOR_5) && (address >= ADDR_FLASH_SECTOR_4))
  {
    sector = FLASH_SECTOR_4;  
  }
  else if((address < ADDR_FLASH_SECTOR_6) && (address >= ADDR_FLASH_SECTOR_5))
  {
    sector = FLASH_SECTOR_5;  
  }
  else if((address < ADDR_FLASH_SECTOR_7) && (address >= ADDR_FLASH_SECTOR_6))
  {
    sector = FLASH_SECTOR_6;  
  }	
  else /* (Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11) */
  {
    sector = FLASH_SECTOR_7;
  }

  return sector;
}

void jump_to_app(void)
{
	uint32_t* image_loc_addr = (uint32_t*)APP_ADDRESS;
	
	uint32_t stack_pointer = image_loc_addr[0];
	
	uint32_t jump_addr = image_loc_addr[1];
		
	typedef void (*jump_func)(void);
	
	jump_func jump = (jump_func) jump_addr;
	
	HAL_RCC_DeInit();
	
	HAL_DeInit();
	
	SysTick -> CTRL = 0;
	SysTick -> LOAD = 0;
	SysTick -> VAL = 0;
	
	__set_MSP(stack_pointer);
	
	jump();	
	
}

int erase_flash_sector(void)
{
	FLASH_EraseInitTypeDef EraseInitStruct;
	
	int ret = 1;
	
	uint8_t sec = get_sector(FLASH_USER_START_ADDR);
	
	uint8_t number_of_sector = get_sector(FLASH_USER_END_ADDR) - sec + 1;
	
	EraseInitStruct.TypeErase = TYPEERASE_SECTORS;
	EraseInitStruct.VoltageRange = VOLTAGE_RANGE_3;
	EraseInitStruct.Sector = sec;
	EraseInitStruct.NbSectors = number_of_sector;
	
	uint32_t sector_error;
	
	if (HAL_FLASHEx_Erase(&EraseInitStruct, &sector_error) != HAL_OK)
	{
		ret = -1;
	}	
	
	return ret;
}

ST ailesinin işlemcilerinde flasha yazabileceğiniz data tipleri ya da gerilim seviyesine göre yazabileceğiniz data tipleri her zaman standart olmuyor gördüğüm kadarıyla. (Daha önce tecrübe ettim). Bu yüzden kendi kullandığınız işlemcinin datasheetinde şu tabloyu kontrol etmenizde fayda var.

    if ((fr = f_mount(&fs, SDPath, 1)) != FR_OK)
	{
		goto exit;
	}	
	
	if ((fr = f_open(&file, "test.bin", FA_READ)) == FR_OK)
	{
		//4 offset for CRC
		if (f_size(&file) - 4 > (FLASH_USER_END_ADDR - FLASH_USER_START_ADDR))
		{
			f_close(&file);
			
			goto exit; 
		}
		
		uint8_t meta_data_buf[8] = { 0 };

		uint32_t crc_val = 0;
	
		unsigned int number_of_bytes_read = 0;
		
		f_lseek(&file, (f_size(&file) - sizeof(struct meta_data)));
				
		f_read(&file, meta_data_buf, sizeof(meta_data_buf), &number_of_bytes_read);
		
		meta_data_t* mdb = (meta_data_t*) meta_data_buf;		
		
		f_lseek(&file, 0);
		
		do 
		{
			unsigned int number_of_bytes_read = 0;
			
			uint8_t buffer[4] = { 0 };
			
			fr = f_read(&file, buffer, sizeof(buffer), &number_of_bytes_read);
		
			for (int i = 0; i < 4; ++i)
			{
				crc_val = get_crc32(crc_val, buffer[i]); 
			}
					
		} while (number_of_bytes_read > 0 && f_tell(&file) + sizeof(struct meta_data) + 4 <= f_size(&file) && fr == FR_OK);
		
		uint32_t match_crc = (mdb -> crc_val[0] << 24) | 
												 (mdb -> crc_val[1] << 16) | 
												 (mdb -> crc_val[2] << 8)  | 
												 (mdb -> crc_val[3]);
		
		uint32_t version =   (mdb -> version[0] << 24) | 
												 (mdb -> version[1] << 16) | 
												 (mdb -> version[2] << 8)  | 
												 (mdb -> version[3]);
		
		/* CRC is valid and version is ok */
		if (crc_val == match_crc && ((version > (*(uint32_t*)VERSION_ADDR)) || ((*(uint32_t*)VERSION_ADDR) == 0xFFFFFFFF)))
		{
			fr = f_lseek(&file, 0);
			
			HAL_FLASH_Unlock();
			
			if (erase_flash_sector() < 0)
			{
				goto exit;
			}		
			
			static unsigned int address_offset = 0;
			
			do 
			{	
				uint8_t buffer_flash[4] = { 0 };

				unsigned int number_of_bytes_read = 0;
				
				uint32_t written_data = 0;
				
				fr = f_read(&file, buffer_flash, sizeof(buffer_flash), &number_of_bytes_read);
				
				written_data = *(uint32_t*)buffer_flash;
				
				HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_ADDRESS + address_offset, written_data);
				
				if (written_data != (*(__IO uint32_t*)(APP_ADDRESS + address_offset)))
				{
					/* Whatever you want depend on your logic */
					while(1);
				}
				
				address_offset = address_offset + 4;
			
			} while (number_of_bytes_read > 0 && f_tell(&file) + sizeof(struct meta_data) + 4 <= f_size(&file) && fr == FR_OK);
						
			HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, VERSION_ADDR , version);
			
			f_close(&file);	
			
			HAL_FLASH_Lock();
			
			//TODO: Delete related file if you want
			
			goto exit;
						
		}//(crc_val == match_crc)
		
		else
		{
			goto exit;		
		}
	}
	
	else
	{
		goto exit;
	}
	
	exit:
		
		if (*(uint32_t*)APP_ADDRESS != 0xFFFFFFFF && *(uint32_t*)VERSION_ADDR != 0xFFFFFFFF)
		{
			jump_to_app();
		}
			
		else
		{
			/* Whatever you want depend on your logic */
			while(1);
			//NVIC_SystemReset();
		}

Edit : Flowchartta belirtilmemiş ama ilgili dosyanın boyutu, uygulamanın çalışacağı boyuttan yüksek olması durumunda yazma işlemi yapılmamakta.

Bu konuda Türkçe kaynak oluşturmak için bu yazıyı paylaştığımı belirtmek ister ve bu yapıyı kullanırken karşılacağınız sorunlar olur ise paylaşmanızı rica ederek, yazıyı sonlandırıyorum. İşleriniz rast gitsin hocamlar.