Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
Bootloader
ota在线升级
基于rt-fota的stm32l431-bootloader实战学习
发布于 2020-05-19 15:39:40 浏览:4518
订阅该版
[tocm] ## RT-FOTA项目简介 由于RTT官方推出的bootloader名字叫RT-OTA,因此为了蹭点RTT的流量,我这个bootloader名字就叫RT-FOTA。 ## RT-FOTA的需求分析 开发基于ROTS的bootloader软件,网上很多牛人会说bootloader最好是裸机程序编写,就像u-boot一样稳定和可靠。 但我个人认为目前32位单片机资源丰富,RT-Thread的稳定和可靠性不言而喻,加之RTT的组件丰富,后续功能拓展方便(比如加入网络和USB功能)。 因此我使用RT-Thread的阉割版本rtt-nano实现。兼容RTT官方的rbl文件:使用RTT官方的打包软件生成下载文件,可以实现加密压缩功能。 由于个人水平问题,只能做到尽可能的兼容,比如RBL文件里面的HASH_CODE我就不知道怎么计算出来的。 ## 移植方便 由于RT-FOTA基于RT-Thread开发,因此只要你的平台被RT-Thread支持,就可以很方便的移植到。 ## RT-FOTA主要的功能 * 支持RTT官方的RBL打包软件,使用方式也一致。目前支持包括CRC32、AES256、quicklz和fastlz功能; * 支持命令行模式(FINSH组件)和出厂固件恢复; * 支持FLASH分区(FAL组件); * 支持功能扩展(RTT组件); * 其他功能可自行方便扩展; ## RT-FOTA项目地址 https://gitee.com/spunky_973/rt-fota
查看更多
18
个回答
默认排序
按发布时间排序
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
我公司有个项目使用的是STM32L431单片机,可惜不知道什么原因,这款芯片使用官网的网页配置生成的bootloader烧录文件不能使用,故借此机会可以移植一下王总的rt-fota。 rt-fota源项目使用的是STM32F407单片机,移植的虽然是 nano 版本, 但是添加了完整版特有的 device 框架和finsh组件,这样的优点是很方便扩展更多的软件包, 但是相应的也增加了nano的尺寸,这对于大flash的单片机来说没影响。 我把rt-fota源项目移植到我STM32F429的开发板,然后进行重新配置,使用AC6编译,并开最大优化,编译后所占尺寸为 [attach]15454[/attach] 这样的尺寸,对于我只有256Kflash的STM32L431来说,还是有点太大了, 故此有了这篇重新移植rt-fota的文章 MDK配置: [attach]15455[/attach]
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
在移植重新移植rt-fota之前,先说一下如何简单移植rt-fota项目到自己的板子上的方法: 第一步:下载rt-fota,使用mdk打开项目,根据自己单片机的型号重新选择 [attach]15456[/attach] 第二步:使用cubmx生成自己板子的驱动,替换红框的两个文件 [attach]15457[/attach] 第三步:替换配置 [attach]15458[/attach] 第四步:配置rtconfig.h与你板子有关的信息 第五步:如果你的板子不是M4内核,还需要替换HAL库 最后:如果你使用的也是AC6编译器,编译会有个错误,更改一下cpuport.c代码: ```c #elif defined(__CLANG_ARM) int __rt_ffs(int value) { __asm volatile( "CMP r0, #0x00 \n" "BEQ exit \n" "RBIT r0, r0 \n" "CLZ r0, r0 \n" "ADDS r0, r0, #0x01 \n" "exit: \n" : "=r"(value) : "r"(value) ); return value; } #elif defined(__IAR_SYSTEMS_ICC__) int __rt_ffs(int value) { if (value == 0) return value; asm("RBIT %0, %1" : "=r"(value) : "r"(value)); asm("CLZ %0, %1" : "=r"(value) : "r"(value)); asm("ADDS %0, %1, #0x01" : "=r"(value) : "r"(value)); return value; } ``` 这样就可以愉快的体验rt-fota了~ 如果不在乎代码尺寸,直接用rthread完整版,体验更佳
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
接下来开始正式重新移植rt-fota到STM32L431的板子上,对rt-fota进行瘦身。首先使用cubmx生成STM32L431的工程。RT-Thread 操作系统重定义 HardFault_Handler、PendSV_Handler、SysTick_Handler 中断函数,为了避免重复定义的问题,在生成工程之前,需要在中断配置中,代码生成的选项中,取消选择三个中断函数(对应注释选项是 Hard fault interrupt, Pendable request, Time base :System tick timer),最后点击生成代码,具体操作如下图中步骤 [attach]15462[/attach] MDK需要先获取 RT-Thread Nano pack 安装包并进行安装。[http://download.rt-thread.org/download/mdk/](RT-Thread Nano 离线安装包下载),下载结束后双击文件进行安装。 RT-Thread Nano pack安装完成后,勾选 kernel和shell [attach]15460[/attach] [attach]15463[/attach] 然后还需要移植finsh,移植官方提供的移植示例代码:[https://www.rt-thread.org/document/site/tutorial/nano/finsh-port/an0045-finsh-port/#_5](中断示例) finsh移植完成后,测试如果能正常打印和接收命令,进行下一步移植软件包
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
移植SFUD软件包。SFUD全称Serial Flash Universal Driver,是一款开源的串行 SPI Flash 通用驱动库,由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计。 SFUD的特点在于:[list] [*]支持 SPI/QSPI 接口 [*]面向对象设计(同时支持多个 Flash 对象) [*]可灵活裁剪、扩展性强 [*]支持 4 字节地址 [/list]项目地址:[https://github.com/armink/SFUD](https://github.com/armink/SFUD) 移植思路:在移植过程中主要参考两个资料:项目的readme文档和demo工程。对于这些开源项目,其实移植起来也就两步:[list] [*]① 添加源码到工程中; [/list][attach]15466[/attach] [list] [*]② 实现需要的接口即可; [/list]实现SFUD移植接口: ① 底层SPI/QSPI读写接口: ``` static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf, size_t read_size) { sfud_err result = SFUD_SUCCESS; spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data; /** * add your spi write and read code */ RT_ASSERT(spi); HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_RESET); if(write_size && read_size) { if(HAL_SPI_Transmit(spi_dev->spix, (uint8_t *)write_buf, write_size, 1000)!=HAL_OK) { result = SFUD_ERR_WRITE; } /* For simplicity reasons, this example is just waiting till the end of the transfer, but application may perform other tasks while transfer operation is ongoing. */ while (HAL_SPI_GetState(spi_dev->spix) != HAL_SPI_STATE_READY); if(HAL_SPI_Receive(spi_dev->spix, (uint8_t *)read_buf, read_size, 1000)!=HAL_OK) { result = SFUD_ERR_READ; } }else if(write_size) { if(HAL_SPI_Transmit(spi_dev->spix, (uint8_t *)write_buf, write_size, 1000)!=HAL_OK) { result = SFUD_ERR_WRITE; } }else { if(HAL_SPI_Receive(spi_dev->spix, (uint8_t *)read_buf, read_size, 1000)!=HAL_OK) { result = SFUD_ERR_READ; } } /* For simplicity reasons, this example is just waiting till the end of the transfer, but application may perform other tasks while transfer operation is ongoing. */ while (HAL_SPI_GetState(spi_dev->spix) != HAL_SPI_STATE_READY); HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_SET); return result; } ``` ② 如果使用的是QSPI通信方式,还需要实现快速读取数据的接口: ``` static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format, uint8_t *read_buf, size_t read_size) ``` ③ SFUD底层使用的SPI/QSPI接口和SPI设备对象初始化接口: ``` sfud_err sfud_spi_port_init(sfud_flash *flash) { sfud_err result = SFUD_SUCCESS; /** * add your port spi bus and device object initialize code like this: * 1. rcc initialize * 2. gpio initialize * 3. spi device initialize * 4. flash->spi and flash->retry item initialize * flash->spi.wr = spi_write_read; //Required * flash->spi.qspi_read = qspi_read; //Required when QSPI mode enable * flash->spi.lock = spi_lock; * flash->spi.unlock = spi_unlock; * flash->spi.user_data = &spix; * flash->retry.delay = null; * flash->retry.times = 10000; //Required */ rt_mutex_init(&lock, "sfud_lock", RT_IPC_FLAG_FIFO); MX_SPI_Init(); #if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F0) \ || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32G0) SET_BIT(hspi2.Instance->CR2, SPI_RXFIFO_THRESHOLD_HF); #endif switch (flash->index) { case SFUD_W25QXX_DEVICE_INDEX: { /* 同步 Flash 移植所需的接口及数据 */ flash->spi.wr = spi_write_read; flash->spi.lock = spi_lock; flash->spi.unlock = spi_unlock; flash->spi.user_data = &user_spi; /* about 100 microsecond delay */ flash->retry.delay = retry_delay_100us; /* adout 60 seconds timeout */ flash->retry.times = 60 * 10000; break; } } return result; } ``` 其他: ``` static void spi_lock(const sfud_spi *spi) { sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data); struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data); RT_ASSERT(spi); RT_ASSERT(sfud_dev); RT_ASSERT(rtt_dev); rt_mutex_take(&lock, RT_WAITING_FOREVER); } static void spi_unlock(const sfud_spi *spi) { sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data); struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data); RT_ASSERT(spi); RT_ASSERT(sfud_dev); RT_ASSERT(rtt_dev); rt_mutex_release(&lock); } static void retry_delay_100us(void) { /* 100 microsecond delay */ rt_thread_delay((RT_TICK_PER_SECOND * 1 + 9999) / 10000); } ```
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
添加SFUD读写测试命令 ``` static void sf(uint8_t argc, char **argv) { #define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ') #define HEXDUMP_WIDTH 16 #define CMD_PROBE_INDEX 0 #define CMD_READ_INDEX 1 #define CMD_WRITE_INDEX 2 #define CMD_ERASE_INDEX 3 #define CMD_RW_STATUS_INDEX 4 #define CMD_BENCH_INDEX 5 sfud_err result = SFUD_SUCCESS; static const sfud_flash *sfud_dev = NULL; size_t i = 0, j = 0; const char* sf_help_info[] = { [CMD_PROBE_INDEX] = "sf probe - probe and init SPI flash", [CMD_READ_INDEX] = "sf read addr size - read 'size' bytes starting at 'addr'", [CMD_WRITE_INDEX] = "sf write addr data1 ... dataN - write some bytes 'data' to flash starting at 'addr'", [CMD_ERASE_INDEX] = "sf erase addr size - erase 'size' bytes starting at 'addr'", [CMD_RW_STATUS_INDEX] = "sf status [
] - read or write '1:volatile|0:non-volatile' 'status'", [CMD_BENCH_INDEX] = "sf bench - full chip benchmark. DANGER: It will erase full chip!", }; if (argc < 2) { rt_kprintf("Usage:\n"); for (i = 0; i < sizeof(sf_help_info) / sizeof(char*); i++) { rt_kprintf("%s\n", sf_help_info[i]); } rt_kprintf("\n"); } else { const char *operator = argv[1]; uint32_t addr, size; if (!strcmp(operator, "probe")) { if (argc < 2) { rt_kprintf("Usage: %s.\n", sf_help_info[CMD_PROBE_INDEX]); } else { sfud_dev = sfud_get_device(SFUD_W25QXX_DEVICE_INDEX);; if (sfud_dev->chip.capacity < 1024 * 1024) { rt_kprintf("%d KB %s is current selected device.\n", sfud_dev->chip.capacity / 1024, sfud_dev->name); } else { rt_kprintf("%d MB %s is current selected device.\n", sfud_dev->chip.capacity / 1024 / 1024, sfud_dev->name); } } } else { if (!sfud_dev) { rt_kprintf("No flash device selected. Please run 'sf probe'.\n"); return; } if (!rt_strcmp(operator, "read")) { if (argc < 4) { rt_kprintf("Usage: %s.\n", sf_help_info[CMD_READ_INDEX]); return; } else { addr = strtol(argv[2], NULL, 0); size = strtol(argv[3], NULL, 0); uint8_t *data = rt_malloc(size); if (data) { result = sfud_read(sfud_dev, addr, size, data); if (result == SFUD_SUCCESS) { rt_kprintf("Read the %s flash data success. Start from 0x%08X, size is %ld. The data is:\n", sfud_dev->name, addr, size); rt_kprintf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); for (i = 0; i < size; i += HEXDUMP_WIDTH) { rt_kprintf("[%08X] ", addr + i); /* dump hex */ for (j = 0; j < HEXDUMP_WIDTH; j++) { if (i + j < size) { rt_kprintf("%02X ", data[i + j]); } else { rt_kprintf(" "); } } /* dump char for hex */ for (j = 0; j < HEXDUMP_WIDTH; j++) { if (i + j < size) { rt_kprintf("%c", __is_print(data[i + j]) ? data[i + j] : '.'); } } rt_kprintf("\n"); } rt_kprintf("\n"); } rt_free(data); } else { rt_kprintf("Low memory!\n"); } } } else if (!rt_strcmp(operator, "write")) { if (argc < 4) { rt_kprintf("Usage: %s.\n", sf_help_info[CMD_WRITE_INDEX]); return; } else { addr = strtol(argv[2], NULL, 0); size = argc - 3; uint8_t *data = rt_malloc(size); if (data) { for (i = 0; i < size; i++) { data[i] = strtol(argv[3 + i], NULL, 0); } result = sfud_write(sfud_dev, addr, size, data); if (result == SFUD_SUCCESS) { rt_kprintf("Write the %s flash data success. Start from 0x%08X, size is %ld.\n", sfud_dev->name, addr, size); rt_kprintf("Write data: "); for (i = 0; i < size; i++) { rt_kprintf("%d ", data[i]); } rt_kprintf(".\n"); } rt_free(data); } else { rt_kprintf("Low memory!\n"); } } } else if (!rt_strcmp(operator, "erase")) { if (argc < 4) { rt_kprintf("Usage: %s.\n", sf_help_info[CMD_ERASE_INDEX]); return; } else { addr = strtol(argv[2], NULL, 0); size = strtol(argv[3], NULL, 0); result = sfud_erase(sfud_dev, addr, size); if (result == SFUD_SUCCESS) { rt_kprintf("Erase the %s flash data success. Start from 0x%08X, size is %ld.\n", sfud_dev->name, addr, size); } } } else if (!rt_strcmp(operator, "status")) { if (argc < 3) { uint8_t status; result = sfud_read_status(sfud_dev, &status); if (result == SFUD_SUCCESS) { rt_kprintf("The %s flash status register current value is 0x%02X.\n", sfud_dev->name, status); } } else if (argc == 4) { bool is_volatile = strtol(argv[2], NULL, 0); uint8_t status = strtol(argv[3], NULL, 0); result = sfud_write_status(sfud_dev, is_volatile, status); if (result == SFUD_SUCCESS) { rt_kprintf("Write the %s flash status register to 0x%02X success.\n", sfud_dev->name, status); } } else { rt_kprintf("Usage: %s.\n", sf_help_info[CMD_RW_STATUS_INDEX]); return; } } else if (!rt_strcmp(operator, "bench")) { if ((argc > 2 && rt_strcmp(argv[2], "yes")) || argc < 3) { rt_kprintf("DANGER: It will erase full chip! Please run 'sf bench yes'.\n"); return; } /* full chip benchmark test */ addr = 0; size = sfud_dev->chip.capacity; uint32_t start_time, time_cast; size_t write_size = SFUD_WRITE_MAX_PAGE_SIZE, read_size = SFUD_WRITE_MAX_PAGE_SIZE; uint8_t *write_data = rt_malloc(write_size), *read_data = rt_malloc(read_size); if (write_data && read_data) { rt_memset(write_data, 0x55, write_size); /* benchmark testing */ rt_kprintf("Erasing the %s %ld bytes data, waiting...\n", sfud_dev->name, size); start_time = rt_tick_get(); result = sfud_erase(sfud_dev, addr, size); if (result == SFUD_SUCCESS) { time_cast = rt_tick_get() - start_time; rt_kprintf("Erase benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND, time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000)); } else { rt_kprintf("Erase benchmark has an error. Error code: %d.\n", result); } /* write test */ rt_kprintf("Writing the %s %ld bytes data, waiting...\n", sfud_dev->name, size); start_time = rt_tick_get(); for (i = 0; i < size; i += write_size) { result = sfud_write(sfud_dev, addr + i, write_size, write_data); if (result != SFUD_SUCCESS) { rt_kprintf("Writing %s failed, already wr for %lu bytes, write %d each time\n", sfud_dev->name, i, write_size); break; } } if (result == SFUD_SUCCESS) { time_cast = rt_tick_get() - start_time; rt_kprintf("Write benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND, time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000)); } else { rt_kprintf("Write benchmark has an error. Error code: %d.\n", result); } /* read test */ rt_kprintf("Reading the %s %ld bytes data, waiting...\n", sfud_dev->name, size); start_time = rt_tick_get(); for (i = 0; i < size; i += read_size) { if (i + read_size <= size) { result = sfud_read(sfud_dev, addr + i, read_size, read_data); } else { result = sfud_read(sfud_dev, addr + i, size - i, read_data); } /* data check */ if (memcmp(write_data, read_data, read_size)) { rt_kprintf("Data check ERROR! Please check you flash by other command.\n"); result = SFUD_ERR_READ; } if (result != SFUD_SUCCESS) { rt_kprintf("Read %s failed, already rd for %lu bytes, read %d each time\n", sfud_dev->name, i, read_size); break; } } if (result == SFUD_SUCCESS) { time_cast = rt_tick_get() - start_time; rt_kprintf("Read benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND, time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000)); } else { rt_kprintf("Read benchmark has an error. Error code: %d.\n", result); } } else { rt_kprintf("Low memory!\n"); } rt_free(write_data); rt_free(read_data); } else { rt_kprintf("Usage:\n"); for (i = 0; i < sizeof(sf_help_info) / sizeof(char*); i++) { rt_kprintf("%s\n", sf_help_info[i]); } rt_kprintf("\n"); return; } if (result != SFUD_SUCCESS) { rt_kprintf("This flash operate has an error. Error code: %d.\n", result); } } } } MSH_CMD_EXPORT(sf, SPI Flash operate.); ``` [attach]15471[/attach]
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
使用SFUD初始化SFUD的API如下,该函数会初始化 Flash 设备表中的全部设备: sfud_err sfud_init(void); 在QSPI模式下,SFUD 对于 QSPI 模式的支持仅限于快速读命令,通过该函数可以配置 Flash 所使用的 QSPI 总线的实际支持的数据线最大宽度,例如:1 线(默认值,即传统的 SPI 模式)、2 线、4 线: sfud_err sfud_qspi_fast_read_enable(sfud_flash *flash, uint8_t data_line_width); 所以,在main函数中编写如下初始化函数: /* SFUD初始化 */if(sfud_init() != SFUD_SUCCESS){ rt_kprintf("SFUD init fail.\r\n");} 编译、下载之后,可以在串口终端中看到SFUD打印的日志: 楼上最后的两幅图
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
对了,STM32L431的SPI速率不能配置为最高的40K,实测有读写错误,配置为20k没有出现过读写错误
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
移植FAL软件包。 FAL (Flash Abstraction Layer) Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及 分区操作的 API (框架图如下所示),并具有以下特性:[list] [*]支持静态可配置的分区表,并可关联多个 Flash 设备; [*]分区表支持 自动装载 。避免在多固件项目,分区表被多次定义的问题; [*]代码精简,对操作系统 无依赖 ,可运行于裸机平台,比如对资源有一定要求的 Bootloader; [*]统一的操作接口。保证了文件系统、OTA、NVM(例如:[https://github.com/armink-rtt-pkgs/EasyFlash](EasyFlash)) 等对 Flash 有一定依赖的组件,底层 Flash 驱动的可重用性; [*]自带基于 Finsh/MSH 的测试命令,可以通过 Shell 按字节寻址的方式操作(读写擦) Flash 或分区,方便开发者进行调试、测试; [/list]项目地址:[https://github.com/RT-Thread-packages/fal](https://github.com/RT-Thread-packages/fal)移植FAL: 首先把FAL添加到工程[attach]15472[/attach]接口移植相关的示例代码及文档在软件包的samples文件夹下:作者已经为我们提供了fal_flash_sfud_port.c,fal_flash_stm32f2_port.c,fal_cfg.h三个文件,我们只需要对这三个文件进行修改就可以了。修改fal_flash_sfud_port.c:使用上一步已经移植好的sfud的接口sfud_get_device()进行对SPIFLASH的控制[attach]15473[/attach]修改fal_flash_stm32f2_port.c内部flash的操作接口可以直接使用RTT的官方库,添加STM32L431的flash驱动即可,所以不使用fal_flash_stm32f2_port.c[attach]15474[/attach]把drv_flash_l4.c中#include "drv_config.h"替换为#include
[attach]15475[/attach]修改fal_cfg.h:[attach]15476[/attach] 使用FAL软件包初始化FAL的API如下:int fal_init(void);串口打印信息如下:[attach]15477[/attach]
Aladdin-Wang
认证专家
2020-05-19
这家伙很懒,什么也没写!
到此,如果不使用加密和压缩功能的话,rt-fota所需要的依赖环境已经添加完毕,这是rt-fota所能达到的最小尺寸,如果不使用系统和HAL库的话,还可以继续优化代码尺寸
会飞的胖子
2020-05-19
这家伙很懒,什么也没写!
我基于nano重新修改了rt-fota的部分实现,AC6使用OZ编译,26KB大小占用
撰写答案
登录
注册新账号
关注者
1
被浏览
4.5k
关于作者
Aladdin-Wang
这家伙很懒,什么也没写!
提问
5
回答
39
被采纳
7
关注TA
发私信
相关问题
1
Linux下通过USBTinyISP为Arduino开发板烧?写Bootloader
2
请教修改NVIC后RTT调度函数失效的问题[已解决 bootloader中打开了不必要的中断]
3
进入bootloader的方式探讨
4
求助:IAP里的APP使用的RTT,跳转后出错。[已解决]
5
有没有人在STM32F103上用UART IAP跑过RT-Thread?
6
想做网口的IAP远程升级,不知可不可行
7
IAP问题
8
[已解决]请教基于RTT的IAP程序切换到应用程序不成功的问题(基于STM32F4)?
9
stm32f4xx-----IAP移植APP程序需要注意的地方
10
在调试IAP网络升级遇到跳转之后bootloader程序网络不通
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
【24嵌入式设计大赛】基于RT-Thread星火一号的智慧家居系统
2
RT-Thread EtherKit开源以太网硬件正式发布
3
如何在master上的BSP中添加配置yml文件
4
使用百度AI助手辅助编写一个rt-thread下的ONVIF设备发现功能的功能代码
5
RT-Thread 发布 EtherKit开源以太网硬件!
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
MicroPython
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
16
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
6
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
RTT_逍遥
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部