RT-Thread學習筆記系列之OTA升級
前言
- 做一個產品,首先是需要設計後期可升級更新功能,否則沒有升級功能則每次出現問題則需要寄回更新產品固件,因此會特別麻煩,所以使用到OTA升級功能
- RT-Thread推薦使用官方提供的Bootloader,具體使用方法請參考官方教程 (https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/)
- 幾個關鍵點需要注意,我因爲粗心則折騰了挺久還成功
- 應用層的偏移地址要和製作的Bootloader的偏移地址一樣
- 應用層的中斷向量要和製作的Bootloader的偏移地址一樣
- 固件打包器的壓縮算法,加密算法等選項要和製作的Bootloader設置的一樣,固件分區名一定是app(本人因爲粗心以爲是download區,導致升級不成功)
硬件介紹和Bootloader配置
本人使用的MCU是STM32F407ZGT6,其中有用到片外SPI Flash,SD卡。引腳兼容正點原子的F407的探索者開發板。Bootloader配置如下圖
整個過程參考官方的教程還是比較順利的,具體不做多介紹。
需求與功能設計
因爲RT-Thread的OTA軟件包只有使用串口和網絡進行更新固件,如果是個人的話很少存在有個人的穩定服務器。串口升級則需要一個相對懂得操作的人才能執行。如果面對的使用對象是完全對這些不瞭解的人呢?那該咋辦呢?
SD卡存儲和拷貝文件對於日常生活中人們比較熟悉,所以將升級的固件拷貝SD卡再接入開發的設備進行升級。(本人的測試方案,後期可能會通過網絡下載等方式直接下載到SD卡或SPI Flash設備中)
功能實現過程
該教程是直接使用RT-Thread Studio開發工具進行開發的,以下是軟件包配置
board.h文件中使用以下宏定義
#define BSP_USING_SPI1
#define BSP_USING_SDIO
#define BSP_USING_ON_CHIP_FLASH
stm32f4xx_hal_conf.h文件中使用以下宏定義
#define HAL_MODULE_ENABLED
#define HAL_SD_MODULE_ENABLED
#define HAL_SPI_MODULE_ENABLED
#define HAL_FLASH_MODULE_ENABLED
使用STM32CUBEMX配置片外Flash和SD卡的接口,本人的配置如下
/**
* @brief SD MSP Initialization
* This function configures the hardware resources used in this example
* @param hsd: SD handle pointer
* @retval None
*/
void HAL_SD_MspInit(SD_HandleTypeDef* hsd)
{
GPIO_InitTypeDef GPIO_InitStruct = {
0};
if(hsd->Instance==SDIO)
{
/* USER CODE BEGIN SDIO_MspInit 0 */
/* USER CODE END SDIO_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_SDIO_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/**SDIO GPIO Configuration
PC8 ------> SDIO_D0
PC9 ------> SDIO_D1
PC10 ------> SDIO_D2
PC11 ------> SDIO_D3
PC12 ------> SDIO_CK
PD2 ------> SDIO_CMD
*/
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* USER CODE BEGIN SDIO_MspInit 1 */
/* USER CODE END SDIO_MspInit 1 */
}
}
/**
* @brief SD MSP De-Initialization
* This function freeze the hardware resources used in this example
* @param hsd: SD handle pointer
* @retval None
*/
void HAL_SD_MspDeInit(SD_HandleTypeDef* hsd)
{
if(hsd->Instance==SDIO)
{
/* USER CODE BEGIN SDIO_MspDeInit 0 */
/* USER CODE END SDIO_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SDIO_CLK_DISABLE();
/**SDIO GPIO Configuration
PC8 ------> SDIO_D0
PC9 ------> SDIO_D1
PC10 ------> SDIO_D2
PC11 ------> SDIO_D3
PC12 ------> SDIO_CK
PD2 ------> SDIO_CMD
*/
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
|GPIO_PIN_12);
HAL_GPIO_DeInit(GPIOD, GPIO_PIN_2);
/* USER CODE BEGIN SDIO_MspDeInit 1 */
/* USER CODE END SDIO_MspDeInit 1 */
}
}
/**
* @brief SPI MSP Initialization
* This function configures the hardware resources used in this example
* @param hspi: SPI handle pointer
* @retval None
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
GPIO_InitTypeDef GPIO_InitStruct = {
0};
if(hspi->Instance==SPI1)
{
/* USER CODE BEGIN SPI1_MspInit 0 */
/* USER CODE END SPI1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**SPI1 GPIO Configuration
PB3 ------> SPI1_SCK
PB4 ------> SPI1_MISO
PB5 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* SPI1 interrupt Init */
HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
/* USER CODE BEGIN SPI1_MspInit 1 */
/* USER CODE END SPI1_MspInit 1 */
}
else if(hspi->Instance==SPI2)
{
/* USER CODE BEGIN SPI2_MspInit 0 */
/* USER CODE END SPI2_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_SPI2_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**SPI2 GPIO Configuration
PC2 ------> SPI2_MISO
PC3 ------> SPI2_MOSI
PB13 ------> SPI2_SCK
*/
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN SPI2_MspInit 1 */
/* USER CODE END SPI2_MspInit 1 */
}
}
/**
* @brief SPI MSP De-Initialization
* This function freeze the hardware resources used in this example
* @param hspi: SPI handle pointer
* @retval None
*/
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* hspi)
{
if(hspi->Instance==SPI1)
{
/* USER CODE BEGIN SPI1_MspDeInit 0 */
/* USER CODE END SPI1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SPI1_CLK_DISABLE();
/**SPI1 GPIO Configuration
PB3 ------> SPI1_SCK
PB4 ------> SPI1_MISO
PB5 ------> SPI1_MOSI
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5);
/* SPI1 interrupt DeInit */
HAL_NVIC_DisableIRQ(SPI1_IRQn);
/* USER CODE BEGIN SPI1_MspDeInit 1 */
/* USER CODE END SPI1_MspDeInit 1 */
}
else if(hspi->Instance==SPI2)
{
/* USER CODE BEGIN SPI2_MspDeInit 0 */
/* USER CODE END SPI2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SPI2_CLK_DISABLE();
/**SPI2 GPIO Configuration
PC2 ------> SPI2_MISO
PC3 ------> SPI2_MOSI
PB13 ------> SPI2_SCK
*/
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_2|GPIO_PIN_3);
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_13);
/* USER CODE BEGIN SPI2_MspDeInit 1 */
/* USER CODE END SPI2_MspDeInit 1 */
}
}
分區表配置如下
#define RT_APP_PART_ADDR 0x08020000
#define NOR_FLASH_DEV_NAME "W25Q128"
/* ===================== Flash device Configuration ========================= */
extern const struct fal_flash_dev stm32_onchip_flash_16k;
extern const struct fal_flash_dev stm32_onchip_flash_64k;
extern const struct fal_flash_dev stm32_onchip_flash_128k;
extern struct fal_flash_dev nor_flash0;
#define FLASH_SIZE_GRANULARITY_16K (4 * 16 * 1024)
#define FLASH_SIZE_GRANULARITY_64K (1 * 64 * 1024)
#define FLASH_SIZE_GRANULARITY_128K (1 * 128 * 1024)
#define STM32_FLASH_START_ADRESS_16K STM32_FLASH_START_ADRESS
#define STM32_FLASH_START_ADRESS_64K (STM32_FLASH_START_ADRESS_16K + FLASH_SIZE_GRANULARITY_16K)
#define STM32_FLASH_START_ADRESS_128K (STM32_FLASH_START_ADRESS_64K + FLASH_SIZE_GRANULARITY_64K)
/* flash device table */
#define FAL_FLASH_DEV_TABLE \
{ \
&stm32_onchip_flash_16k, \
&stm32_onchip_flash_64k, \
&stm32_onchip_flash_128k, \
&nor_flash0, \
}
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "app", "onchip_flash_128k", 0, 896*1024, 0}, \
{FAL_PART_MAGIC_WORD, "download", NOR_FLASH_DEV_NAME, 0, 1024*1024, 0}, \
}
#endif /* FAL_PART_HAS_TABLE_CFG */
中斷向量偏移配置如下
static int ota_app_vtor_reconfig(void)
{
#define NVIC_VTOR_MASK 0x3FFFFF80
SCB->VTOR = RT_APP_PART_ADDR & NVIC_VTOR_MASK;
return 0;
}
INIT_BOARD_EXPORT(ota_app_vtor_reconfig);
片外SPI Flash掛載款設備(fal才能發現)如下
#include "spi_flash.h"
#include "spi_flash_sfud.h"
#include "drv_spi.h"
static int rt_hw_spi_flash_init(void)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
rt_hw_spi_device_attach("spi1", "spi10", GPIOB, GPIO_PIN_14);
if (RT_NULL == rt_sfud_flash_probe("W25Q128", "spi10"))
{
return -RT_ERROR;
};
return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);
SD卡掛載文件系統如下
#include "dfs_fs.h"
static int sd_mount(void)
{
rt_thread_mdelay(500);
if(dfs_mount("sd0", "/", "elm", 0, 0) == 0)
{
rt_kprintf("sd0 mount success\r\n");
}
else {
rt_kprintf("sd0 mount fail\r\n");
}
return RT_EOK;
}
INIT_APP_EXPORT(sd_mount);
此時編譯下載就能發現設備和文件系統了
根據上面的需求加入一下代碼則能實現從SD卡實現升級固件功能,該例子則使用finsh控制檯進行命令選擇文件升級(後期可以通過觸摸屏幕直接在板子上直接選擇文件升級)
#include <dfs_posix.h> /* 當需要使用文件操作時,需要包含這個頭文件 */
void upgrade(int argc, char **argv)
{
fal_partition_t fal_dev = RT_NULL;
int fd, buffsize, writecnt, fal_addr, len;
unsigned char *ucData = RT_NULL;
char *filename = RT_NULL;
if (argc < 2)
{
rt_kprintf("upgrade [filename]\n");
return ;
}
filename = argv[1];
rt_kprintf("start upgrade %s\n", filename);
fd = open(filename, O_RDONLY);
if(fd < 0)
{
rt_kprintf("open %s file fail\n");
return ;
}
fal_dev = fal_partition_find("download");
if(fal_dev == RT_NULL)
{
rt_kprintf("can not find download partition\n");
return ;
}
if(fal_partition_erase_all(fal_dev) < 0)
{
rt_kprintf("erase download partition all fail\n");
return ;
}
buffsize = fal_dev->len;
ucData = rt_malloc(buffsize);//try malloc max buff
if (ucData == RT_NULL)
{
ucData = rt_malloc(4096);
if(ucData == RT_NULL)
{
rt_kprintf("No memory\n");
return;
}
buffsize = 4096;
}
writecnt = 0;
fal_addr = 0;
while(writecnt < fal_dev->len)
{
len = read(fd, ucData, buffsize);
if(len < 0)break;
if(fal_partition_write(fal_dev, fal_addr, ucData, buffsize) < 0)
{
rt_kprintf("fal write download partition addr:%x size:%d fail\n");
return ;
}
writecnt += buffsize;
fal_addr += buffsize;
}
rt_kprintf("write upgrade %s file success\n", filename);
rt_kprintf("reboot system\n");
NVIC_SystemReset();
return;
}
MSH_CMD_EXPORT(upgrade, a sd card upgrade sample);
編譯下載後,將需要升級的固件拷貝到sd卡根目錄中,設備接入SD卡重啓進行驅動SD卡,輸入ls命令看看升級固件是否存在
輸入 upgrade rtthread.rbl選擇固件進行升級
升級成功,接下來則可以繼續開發其他應用功能了