中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

視頻鏈接生成競(jìng)價(jià)推廣和seo的區(qū)別

視頻鏈接生成,競(jìng)價(jià)推廣和seo的區(qū)別,搭建網(wǎng)站 軟件,安徽工程建設(shè)造價(jià)信息網(wǎng)站STM32 IAP應(yīng)用開發(fā)--bootloader升級(jí)程序 Chapter1 STM32 IAP應(yīng)用開發(fā)——通過串口/RS485實(shí)現(xiàn)固件升級(jí)(方式2)前言什么是IAP?什么是BootLoader? 方案介紹:1)bootloader部分:2)APP部分…

STM32 IAP應(yīng)用開發(fā)--bootloader升級(jí)程序

  • Chapter1 STM32 IAP應(yīng)用開發(fā)——通過串口/RS485實(shí)現(xiàn)固件升級(jí)(方式2)
    • 前言
      • 什么是IAP?
      • 什么是BootLoader?
    • 方案介紹:
      • 1)bootloader部分:
      • 2)APP部分:
    • 3 程序編寫
      • 3.1 BootLoader部分
      • 3.2 APP的制作
    • 4 修改工程中的內(nèi)存配置
      • 4.1 Bootloader工程內(nèi)存配置
      • 4.2 APP工程內(nèi)存配置
    • 5 燒錄相關(guān)配置
      • 5.1 BootLoader部分
      • 5.2 APP部分
    • 6 運(yùn)行測(cè)試
    • 結(jié)束語(yǔ)
  • Chapter2 STM32F1 IAP在線升級(jí)功能實(shí)現(xiàn)(使用串口)及心得
    • APP程序
  • Chapter3 STM32學(xué)習(xí)筆記之簡(jiǎn)易Bootloader串口升級(jí)設(shè)計(jì)
    • 概念簡(jiǎn)介
    • 復(fù)位執(zhí)行流程
    • 實(shí)現(xiàn)要點(diǎn)
      • Bootloader程序?qū)崿F(xiàn)
      • App程序?qū)崿F(xiàn)
  • Chapter4 STM32 IAP應(yīng)用開發(fā)——自制BootLoader
    • 1 環(huán)境搭建
    • 2 BootLoader工作原理以及常見分區(qū)介紹
    • 3 BootLoader的制作
    • 4 燒錄下載配置
    • 5 運(yùn)行測(cè)試
    • 結(jié)束語(yǔ)


Chapter1 STM32 IAP應(yīng)用開發(fā)——通過串口/RS485實(shí)現(xiàn)固件升級(jí)(方式2)

原文鏈接:https://blog.csdn.net/ShenZhen_zixian/article/details/129424077

前言

什么是IAP?

IAP(In-Application Programming) 指MCU可以在系統(tǒng)中獲取新代碼并對(duì)自己重新編程,即可用程序來改變程序。在應(yīng)用編程(IAP)是用戶的應(yīng)用代碼對(duì)片內(nèi)Flash存儲(chǔ)器進(jìn)行擦除/編程的方法。這種方式的典型應(yīng)用就是用一小段代碼來實(shí)現(xiàn)程序的下載,實(shí)際上單片機(jī)的ISP功能就是通過IAP技術(shù)來實(shí)現(xiàn)的,即片子在出廠前就已經(jīng)有一段小的boot程序在里面,片子上電后,開始運(yùn)行這段程序,當(dāng)檢測(cè)到上位機(jī)有下載要求時(shí),便和上位機(jī)通信,然后下載數(shù)據(jù)到數(shù)據(jù)存儲(chǔ)區(qū),從而實(shí)現(xiàn)固件升級(jí)。

什么是BootLoader?

百度百科:在嵌入式操作系統(tǒng)中,BootLoader是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行??梢猿跏蓟布O(shè)備、建立內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個(gè)合適狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境。在嵌入式系統(tǒng)中,通常并沒有像BIOS那樣的固件程序(注,有的嵌入式CPU也會(huì)內(nèi)嵌一段短小的啟動(dòng)程序),因此整個(gè)系統(tǒng)的加載啟動(dòng)任務(wù)就完全由BootLoader來完成。

實(shí)際上,BootLoader不僅僅在操作系統(tǒng)上使用,在一些內(nèi)存小,功能應(yīng)用較為簡(jiǎn)單的單片機(jī)設(shè)備上面也可以通過BootLoader來完成固件升級(jí)。

我之前也有發(fā)過一些關(guān)于STM32遠(yuǎn)程升級(jí)的文章,實(shí)現(xiàn)的方式有很多種,感興趣的同學(xué)可以去看一下。
STM32固件升級(jí)系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

那么這一期我來介紹一下如何自己制作一個(gè)BootLoader程序,并且通過串口或者RS485實(shí)現(xiàn)固件升級(jí)。

1 環(huán)境搭建
關(guān)于STM32以及Keil的環(huán)境這里就不具體介紹了,網(wǎng)上教程也很多,不懂的同學(xué)自行查閱資料。

2 功能描述
在做bootloader之前一定要先想好升級(jí)的途徑和方式,這樣才好規(guī)劃分區(qū)以及制作bootloader。
關(guān)于bootloader詳細(xì)的講解,可以看下我之前發(fā)的博客:
STM32 IAP應(yīng)用開發(fā)——自制BootLoader

分區(qū)介紹:
我用的是STM32F407,內(nèi)存是512K的(想用內(nèi)存更小的MCU也是可以的,改下各個(gè)分區(qū)的內(nèi)存分配就行了)。
注:F4系列的MCU不像F1那樣,內(nèi)存扇區(qū)都很大(最少也是16K),而且同一塊扇區(qū)只能一起擦除,所以就沒辦法分的那么細(xì)了。詳細(xì)的內(nèi)存分布可以參考下面的兩個(gè)圖。
STM32F4x扇區(qū)分布圖如下:
在這里插入圖片描述
STM32F1x扇區(qū)分布圖如下:
在這里插入圖片描述

那么我這里呢,就用一個(gè)512k的內(nèi)存,分成3個(gè)區(qū)域,來實(shí)現(xiàn)一個(gè)升級(jí)的功能。
分區(qū)表如下:
在這里插入圖片描述
在這里插入圖片描述

方案介紹:

1)bootloader部分:

開始運(yùn)行后先等待5s,在這個(gè)時(shí)間內(nèi)如果收到串口2或者RS485的升級(jí)命令就進(jìn)入升級(jí)模式,如果超時(shí)則跳轉(zhuǎn)到用戶程序(APP)。
在升級(jí)模式,可以通過串口2或者RS485傳輸要升級(jí)的固件,傳輸?shù)臄?shù)據(jù)協(xié)議我這里圖方便就直接用Ymodem了,不知道Ymodem協(xié)議的可以先自行查閱一下資料。
在這里插入圖片描述

2)APP部分:

APP部分修改一下中斷向量表地址即可,其他的隨便你做什么應(yīng)用。
另外,我在分區(qū)的時(shí)候留了一塊settimg區(qū),在實(shí)際的應(yīng)該中如果有需要記錄一些掉電后還能保存的數(shù)據(jù),那么這塊區(qū)域就可以用得上了。

3 程序編寫

3.1 BootLoader部分

不管用的是什么MCU,要實(shí)現(xiàn)固件升級(jí)都離不開BootLoader,BootLoader是一個(gè)統(tǒng)稱,它其實(shí)只是一段引導(dǎo)程序,在MCU啟動(dòng)的時(shí)候會(huì)先運(yùn)行這段代碼,判斷是否需要升級(jí),如果不需要升級(jí)就跳轉(zhuǎn)到APP分區(qū)運(yùn)行用戶代碼,如果需要升級(jí)則先通過一些硬件接口接收和搬運(yùn)要升級(jí)的新固件,然后再跳轉(zhuǎn)到APP分區(qū)運(yùn)行新固件,從而實(shí)現(xiàn)固件升級(jí)。
BootLoader的制作需要根據(jù)實(shí)際的需求來做,不同的運(yùn)行方式或者升級(jí)方式在做法上都是有區(qū)別的,包括BootLoader所需要的內(nèi)存空間也不盡相同。
不過不管是用什么方式,Bootloader都應(yīng)該盡可能做的更小更簡(jiǎn)潔,這樣的話內(nèi)存的開銷就更小,對(duì)于內(nèi)存較小的MCU來說壓力就沒那么大了。

注:我這里是基于正點(diǎn)原子的工程模板改的,增加了自己的功能。

示例代碼如下:
Bootloader分區(qū)定義:

#define FLASH_SECTOR_SIZE           1024
#define FLASH_SECTOR_NUM            512    // 512K
#define FLASH_START_ADDR            ((uint32_t)0x8000000)
#define FLASH_END_ADDR              ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))//flash sector addr
#define ADDR_FLASH_SECTOR_0         ((uint32_t)0x08000000) 	//sector0 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_1         ((uint32_t)0x08004000) 	//sector1 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_2         ((uint32_t)0x08008000) 	//sector2 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3         ((uint32_t)0x0800C000) 	//sector3 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4         ((uint32_t)0x08010000) 	//sector4 addr, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5         ((uint32_t)0x08020000) 	//sector5 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6         ((uint32_t)0x08040000) 	//sector6 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_7         ((uint32_t)0x08060000) 	//sector7 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_8         ((uint32_t)0x08080000) 	//sector8 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_9         ((uint32_t)0x080A0000) 	//sector9 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_10        ((uint32_t)0x080C0000) 	//sector10 addr,128 Kbytes  
#define ADDR_FLASH_SECTOR_11        ((uint32_t)0x080E0000) 	//sector11 addr,128 Kbytes  #define BOOT_SECTOR_ADDR            0x08000000     // BOOT sector start addres
#define BOOT_SECTOR_SIZE            0x4000         // BOOT sector size    
#define SETTING_SECTOR_ADDR         0x08004000     // SETTING sector start addres  
#define SETTING_SECTOR_SIZE         0x4000         // SETTING sector size     
#define APP_SECTOR_ADDR             0x08008000     // APP sector start address  
#define APP_SECTOR_SIZE             0x78000        // APP sector size    #define BOOT_ERASE_SECTORS_NUM      1  // 16k
#define SETTING_ERASE_SECTORS_NUM   1  // 16k
#define APP_ERASE_SECTORS_NUM       6  // 16k + 16k + 64k + 128k + 128k + 128k

main函數(shù):

#include "bootloader.h"
#include "usart.h"
#include "rs485.h"
#include "delay.h"
#include "ymodem.h"#define WAIT_TIMEOUT   5void print_boot_message(void)
{uart_log("---------- Enter BootLoader ----------\r\n");uart_log("\r\n");uart_log("======== flash pration table =========\r\n");uart_log("| name     | offset     | size       |\r\n");uart_log("--------------------------------------\r\n");uart_log("| boot     | 0x%08X | 0x%08X |\r\n", BOOT_SECTOR_ADDR, BOOT_SECTOR_SIZE);uart_log("| setting  | 0x%08X | 0x%08X |\r\n", SETTING_SECTOR_ADDR, SETTING_SECTOR_SIZE);uart_log("| app      | 0x%08X | 0x%08X |\r\n", APP_SECTOR_ADDR, APP_SECTOR_SIZE);uart_log("======================================\r\n");
}void print_wait_message(void)
{uart_log("------- Please enter parameter -------\r\n");uart_log("[1].Start program\r\n");uart_log("[2].Update program\r\n");uart_log("--------------------------------------\r\n");
}int main() 
{process_status process;uint16_t timerout = 0;delay_init(168);uart_init(115200);ymodem_init();print_boot_message();print_wait_message();while (1) {process = get_ymodem_status();switch (process) {case WAIT_START_PROGRAM:uart_log("wait start app...(%ds)\r\n", WAIT_TIMEOUT - timerout);delay_ms(1000);timerout ++;if(timerout >= WAIT_TIMEOUT){set_ymodem_status(START_PROGRAM);}break;case START_PROGRAM:uart_log("start app...\r\n");delay_ms(50);if (!jump_app(APP_SECTOR_ADDR)) {uart_log("start app failed: app no program\r\n");delay_ms(1000);}break;case UPDATE_PROGRAM:ymodem_c();uart_log("update app program...\r\n");delay_ms(1000);break;case UPDATE_SUCCESS:uart_log("update success\r\n");uart_log("system reboot...\r\n");delay_ms(1000);system_reboot();break;default:break;}}
}

Ymodem協(xié)議處理:

#define YMODEM_SOH		0x01
#define YMODEM_STX		0x02
#define YMODEM_EOT		0x04
#define YMODEM_ACK		0x06
#define YMODEM_NAK		0x15
#define YMODEM_CA		0x18
#define YMODEM_C		0x43#define MAX_QUEUE_SIZE  1200typedef void (*ymodem_callback)(process_status);typedef struct 
{process_status process;uint8_t status;uint8_t id;uint32_t addr;uint8_t sectors_size;ymodem_callback cb;
} ymodem_t;//順序循環(huán)隊(duì)列的結(jié)構(gòu)體定義如下:
typedef struct
{uint8_t queue[MAX_QUEUE_SIZE];int rear;  //隊(duì)尾指針int front;  //隊(duì)頭指針int count;  //計(jì)數(shù)器
} seq_queue_t; typedef struct 
{uint8_t data[1200];uint16_t len;
} download_buf_t;void ymodem_ack(void) 
{uint8_t buf[3];buf[0] = YMODEM_ACK;buf[1] = 0x0D;buf[2] = 0x0A;RS485_Send_Data(buf, 3);
}void ymodem_nack(void) 
{uint8_t buf[3];buf[0] = YMODEM_NAK;buf[1] = 0x0D;buf[2] = 0x0A;RS485_Send_Data(buf, 3);
}void ymodem_c(void) 
{uint8_t buf[3];buf[0] = YMODEM_C;buf[1] = 0x0D;buf[2] = 0x0A;RS485_Send_Data(buf, 3);
}void set_ymodem_status(process_status process) 
{ymodem.process = process;
}process_status get_ymodem_status(void) 
{process_status process = ymodem.process;return process;
}void ymodem_start(ymodem_callback cb) 
{if (ymodem.status == 0) {ymodem.cb = cb;}
}void ymodem_recv(download_buf_t *p) 
{uint8_t type = p->data[0];switch (ymodem.status) {case 0:if (type == YMODEM_SOH) {ymodem.process = BUSY;ymodem.addr = APP_SECTOR_ADDR;uart_log("erase flash: 0x%08X\r\n", APP_SECTOR_ADDR);mcu_flash_erase(ymodem.addr, APP_ERASE_SECTORS_NUM);uart_log("erase flash success\r\n");ymodem_ack();ymodem_c();ymodem.status++;}else if (type == '1') {uart_log("start program now\r\n");ymodem.process = START_PROGRAM;}else if (type == '2') {uart_log("enter update mode\r\n");ymodem.process = UPDATE_PROGRAM;}break;case 1:if (type == YMODEM_SOH || type == YMODEM_STX) {if (type == YMODEM_SOH) {mcu_flash_write(ymodem.addr, &p->data[3], 128);ymodem.addr += 128;}else {mcu_flash_write(ymodem.addr, &p->data[3], 1024);ymodem.addr += 1024;}ymodem_ack();}else if (type == YMODEM_EOT) {ymodem_nack();ymodem.status++;}else {ymodem.status = 0;}break;case 2:if (type == YMODEM_EOT) {ymodem_ack();ymodem_c();ymodem.status++;}break;case 3:if (type == YMODEM_SOH) {ymodem_ack();ymodem.status = 0;ymodem.process = UPDATE_SUCCESS;}}p->len = 0;
}void ymodem_init(void)
{RS485_Init(115200);timer_init();queue_initiate(&rx_queue);
}

關(guān)于bootloader詳細(xì)的講解,可以看下我之前發(fā)的博客:
STM32 IAP應(yīng)用開發(fā)——自制BootLoader
完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

3.2 APP的制作

APP部分根據(jù)自己實(shí)際的功能來做,只要記得修改中斷向量表地址即可。地址的值等于你APP區(qū)的起始地址。

示例代碼如下:
main函數(shù):

#include "main.h"
#include "usart.h"
#include "delay.h"#define APP_VERSION          "V100"
#define NVIC_VTOR_MASK       0x3FFFFF80
#define APP_PART_ADDR        0x08008000void ota_app_vtor_reconfig(void)
{/* Set the Vector Table base location by user application firmware definition */SCB->VTOR = APP_PART_ADDR & NVIC_VTOR_MASK;
}void led_init(void)
{         GPIO_InitTypeDef  GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(GPIOF, &GPIO_InitStructure);GPIO_SetBits(GPIOF, GPIO_Pin_9);
}void print_boot_message(void)
{uart_log("======================================\r\n");uart_log("-------------- Enter APP -------------\r\n");uart_log ("app version is: %s\r\n", APP_VERSION);uart_log("======================================\r\n");
}int main(void)
{ota_app_vtor_reconfig();delay_init(168);uart_init(115200);print_boot_message();led_init();uart_log ("app init success\r\n");while (1){GPIO_SetBits(GPIOF, GPIO_Pin_9);delay_ms(1000);GPIO_ResetBits(GPIOF, GPIO_Pin_9);delay_ms(1000);}
}

完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

4 修改工程中的內(nèi)存配置

因?yàn)槲覀儗?duì)stm32的內(nèi)存進(jìn)行了分區(qū),不同的代碼要存放在不同的區(qū)域,因此,我們?cè)诰幾g工程之前需要先定義好各自的區(qū)域,以免出現(xiàn)內(nèi)存越界。

4.1 Bootloader工程內(nèi)存配置

Bootloader的起始地址不需要改,按flash默認(rèn)地址即可,size需要改成實(shí)際分區(qū)大小。
在這里插入圖片描述

4.2 APP工程內(nèi)存配置

APP的起始地址和size都需要根據(jù)實(shí)際的分區(qū)來改。
在這里插入圖片描述

5 燒錄相關(guān)配置

我們的Bootloader做好以后需要燒錄到MCU里面,可以直接用Keil uVison來下載,也可以用J-Flash或者其他,這個(gè)都沒關(guān)系,但是要注意內(nèi)存的分配,要把固件燒到對(duì)應(yīng)的內(nèi)存地址上。

5.1 BootLoader部分

1)使用Keil uVision下載
如果是用keil下載的話,需要注意flash的配置,具體如下:
在這里插入圖片描述
2)使用其他下載工具
如果是用J-Flash或者STlink的工具燒錄的話注意燒錄的起始地址是0x08000000就好了。

5.2 APP部分

1)使用Keil uVision下載
跟BootLoader一樣,我們按照前面分配好的空間配置APP的參數(shù)即可。
在這里插入圖片描述
2)使用其他下載工具
如果是用J-Flash或者STlink的工具燒錄的話注意燒錄的起始地址是0x08008000就好了。

6 運(yùn)行測(cè)試

用串口助手查看運(yùn)行l(wèi)og(我這里用的是XShell,用其他的也是可以的)。

1)開始運(yùn)行代碼
等待5s,如果不需要升級(jí)就跳轉(zhuǎn)到App區(qū),如下圖:
在這里插入圖片描述
2)發(fā)送命令1
在等待的5s內(nèi)通過串口2或者RS485發(fā)送一個(gè)’1’,直接跳轉(zhuǎn)到APP。
注:我這里為了方便調(diào)試才用的這種方式,實(shí)際上可以根據(jù)自己的需求來做。
在這里插入圖片描述
3)發(fā)送命令2,進(jìn)入升級(jí)模式
在等待的5s內(nèi)通過串口2或者RS485發(fā)送一個(gè)’2’,進(jìn)入升級(jí)模式。
注:我這里為了方便調(diào)試才用的這種方式,實(shí)際上可以根據(jù)自己的需求來做。比如用按鍵進(jìn)入,或者用其他串口,USB之類的,也可以在APP部分做這個(gè)功能。
串口調(diào)試窗口log如下圖:
在這里插入圖片描述
4)通過Ymodem傳輸新固件
調(diào)試工具我用的是XShell,實(shí)際上用其他工具也行,只要支持Ymodem方式傳輸文件即可。
在這里插入圖片描述
在這里插入圖片描述
5)升級(jí)固件
固件升級(jí)完成后自動(dòng)重啟,重新運(yùn)行Bootloader和APP。
在這里插入圖片描述
至此,整個(gè)升級(jí)流程就走完了。

結(jié)束語(yǔ)

好了,關(guān)于自制BootLoader并實(shí)現(xiàn)串口以及RS485升級(jí)固件的介紹就講到這里,本文列舉的例子其實(shí)只是升級(jí)的其中一種方式,只是提供一個(gè)思路,不是唯一的方法,實(shí)際上最好還是根據(jù)自己實(shí)際的需求來做。我之前也發(fā)給幾篇升級(jí)相關(guān)的文章,用的都是不同的方式,各有各的優(yōu)點(diǎn)和缺點(diǎn),感興趣的同學(xué)可以去看一下。

Chapter2 STM32F1 IAP在線升級(jí)功能實(shí)現(xiàn)(使用串口)及心得

原文鏈接:https://blog.csdn.net/qq_45625638/article/details/127121199

公司產(chǎn)品要求,需要做一個(gè)能遠(yuǎn)程升級(jí)程序的功能,找了很多例程,大多都是需要按鍵來完成操作的,而我需要的是通過串口發(fā)送指令來完成,于是東拼西湊最后還是用了四天的時(shí)間勉強(qiáng)做出來

整個(gè)功能需要的程序是兩個(gè)部分。一個(gè)是IAP程序,一個(gè)是APP程序。對(duì)于IAP程序和APP原理方面的內(nèi)容就不再過多贅述。直接從操作開始吧。

IAP程序
寫IAP程序之前首先得配置程序的起始地址和大小。這里根據(jù)個(gè)人情況而定,我這里單片機(jī)flash大小是512k,所以IAP程序選擇分配的大小是64k。
在這里插入圖片描述
點(diǎn)擊魔術(shù)棒,然后在選擇target,設(shè)置起始地址(start)和大小(size),IAP程序起始地址都是從0x8000000開始的,大小就是0x10000也就是64k啦。
在這里我參考了原子哥的源碼和一位大神的分享,原子的資料一搜一大堆,這里就僅貼出大佬的鏈接
stm32 IAP 程序編寫心得
有了前車之鑒,做起來也稍顯得心應(yīng)手,這里的flash操作的函數(shù)和IAP功能函數(shù)都是拿來主義了,原子IAP例程里拿來就可以用。主要工作還是針對(duì)main函數(shù),這里直接貼出源碼:

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "stmflash.h"
#include "iap.h"#define ADDR_CodeWriteFlag  0X08070000		//設(shè)置跳轉(zhuǎn)標(biāo)志位保存地址
#define ADDR_JumpToIAPFlag  0X08070001
int main(void)
{	u16 IAPFlagBuf[2];u16 RxDataCount=0;				//串口接收到的數(shù)據(jù)計(jì)數(shù)u16 RxDataLength=0;				//串口接收到的數(shù)據(jù)長(zhǎng)度u16 AppCodeLength = 0;		//接收到的app代碼長(zhǎng)度u8  RxCmdFlag = 0;				u8  AppRunFlag = 0;				//應(yīng)用程序運(yùn)行標(biāo)志u16 JumpToAPPFlag;									//跳轉(zhuǎn)至APP程序標(biāo)志位u16 JumpToIAPFlag;									//跳轉(zhuǎn)回IAP標(biāo)志位NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設(shè)置NVIC中斷分組2:2位搶占優(yōu)先級(jí),2位響應(yīng)優(yōu)先級(jí)JumpToAPPFlag = STMFLASH_ReadHalfWord(ADDR_CodeWriteFlag); //讀取APP寫入標(biāo)志位,判斷是否已有程序JumpToIAPFlag = STMFLASH_ReadHalfWord(ADDR_JumpToIAPFlag);uart_init(9600);					//串口初始化為9600delay_init();	   	 				//延時(shí)初始化 IAPFlagBuf[0] = 0x11;IAPFlagBuf[1] = 0x00;printf("提示:輸入send發(fā)送bin文件!\r\n");while(1){if(JumpToAPPFlag != 0x11)			//判斷是否已有APP程序,如果已有,跳轉(zhuǎn)至APP程序運(yùn)行{if(JumpToIAPFlag == 0x11)		//判斷是否從APP程序跳轉(zhuǎn)回來,如果是擦除已有APP程序{			JumpToIAPFlag = 0x00;		}if(USART_RX_CNT)			//如果有數(shù)據(jù)進(jìn)來{if(RxDataCount == USART_RX_CNT)				//串口沒有再收到新數(shù)據(jù){RxDataLength = USART_RX_CNT;if(RxCmdFlag == 0 && RxDataLength == 4)	//接收到IAP指令{if(USART_RX_BUF[0] == 's' && USART_RX_BUF[1] == 'e' && USART_RX_BUF[2] == 'n' && USART_RX_BUF[3] == 'd')//判斷是否為IAP指令{RxCmdFlag = 1;							//接收到更新APP代碼指令,標(biāo)志位置1RxDataLength = 0;						//清空指令長(zhǎng)度,防止影響后面計(jì)算APP代碼大小printf("準(zhǔn)備接收app程序,請(qǐng)?zhí)砑觔in文件!\r\n"); //準(zhǔn)備好接收bin文件,等待用戶添加}else{CodeUpdateFlag = 0;AppCodeLength = 0;printf("指令錯(cuò)誤!\r\n");	//未接收到IAP更新指令,其他任何串口發(fā)送數(shù)據(jù)都認(rèn)為指令錯(cuò)誤}}else if(RxCmdFlag == 1 && RxDataLength > 10)//接收APP程序{CodeUpdateFlag = 1;												//代碼更新標(biāo)志位置位,用于應(yīng)用程序代碼接收完成后寫FLASHRxCmdFlag = 0;AppCodeLength = USART_RX_CNT;printf("APP程序接收完成!\r\n");printf("程序大小:%dBytes\r\n",AppCodeLength);}else{RxDataLength = 0;printf("文件或指令錯(cuò)誤!\r\n"); //如果代碼大小不足10Bytes,認(rèn)為沒有正確添加bin文件}RxDataCount = 0;USART_RX_CNT = 0;}else {RxDataCount = USART_RX_CNT;}}delay_ms(10);											//給以串口中斷的時(shí)間,判斷是否接收完成if(CodeUpdateFlag)								//代碼更新標(biāo)志位置位{CodeUpdateFlag = 0;if(AppCodeLength){printf("程序更新中...\r\n");if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)					//判斷代碼合法性{	printf("正在下載程序!\r\n");iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,AppCodeLength);	//新代碼寫入FLASH  AppRunFlag = 1;}else {printf("程序更新失敗,請(qǐng)檢查bin文件是否正確!\r\n");printf("跳轉(zhuǎn)到原有應(yīng)用程序!\r\n");iap_load_app(FLASH_APP1_ADDR);								         //執(zhí)行FLASH APP代碼}}else {printf("沒有程序可以更新!\r\n");}								 }if(AppRunFlag)																//App運(yùn)行標(biāo)志置位{printf("開始運(yùn)行程序!\r\n");delay_ms(10);                        if(((*(vu32*)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000)		//判斷代碼合法性{	 AppRunFlag = 0;STMFLASH_Write(ADDR_CodeWriteFlag,IAPFlagBuf,2);		   //寫入APP代碼標(biāo)志//RCC_DeInit(); //關(guān)閉外設(shè)//__disable_irq();iap_load_app(FLASH_APP1_ADDR);								         //執(zhí)行FLASH APP代碼}else {printf("應(yīng)用程序錯(cuò)誤!\r\n");  }									   }}else{printf("已有一個(gè)應(yīng)用程序!\r\n");printf("開始運(yùn)行程序!\r\n");delay_ms(10);iap_load_app(FLASH_APP1_ADDR);										//執(zhí)行FLASH APP代碼}}
} 
  1. main函數(shù)中,選擇一塊不使用的flash區(qū)域來保存跳轉(zhuǎn)標(biāo)志位。
    在這里插入圖片描述

我選擇的是0x8070000開始的區(qū)域,整個(gè)flash大小是512k也就是0x8080000,所以最后我又留了64k的大小來存放,所以留給APP程序的就是0x08010000到0x08070000的地址,也是就384k大小空間,記住這個(gè)0x08010000到0x08070000,后面APP程序要考。

  1. 每次進(jìn)入main函數(shù)都要先讀這個(gè)地址的值,若等于0x11則直接跳轉(zhuǎn)到APP,因?yàn)樵谔D(zhuǎn)APP之前是要對(duì)這個(gè)地址進(jìn)行寫0x11的,所以跳轉(zhuǎn)到APP后這個(gè)地址是0x11,開機(jī)啟動(dòng)會(huì)自動(dòng)進(jìn)入APP。
    在這里插入圖片描述
    到此似乎思路就清晰了,原子通過按鍵來操作接收,下載。我這里通過指令來接收下載,其他的判斷棧頂?shù)刂返暮戏ㄐ?#xff0c;看兩遍代碼也能明白了。

APP程序

接下來就是APP程序的操作部分了。
要從IAP跳轉(zhuǎn)到APP,APP的程序也需要修改。首先就是程序的起始地址和大小。
在這里插入圖片描述
前面的IAP從0x8000000到0x8010000,所以我們的APP程序只能從0x8010000開始,而0x8070000到0x8080000又要保存跳轉(zhuǎn)標(biāo)志位,那么APP的地址就只能是0x8010000到0x8070000了,既然這樣,那就拉滿吧,所以大小設(shè)置我選擇0x60000。
然后就是輸出bin文件了,要用串口傳輸不能用hex文件,所以在USER界面
選擇如圖所示操作,方框里填的是E:\keil5\ARM\ARMCC\bin\fromelf.exe --bin -o …\output\Project.bin …\output\Project.axf 注意有空格,當(dāng)然這個(gè)也得根據(jù)自己的文件位置更改。
在這里插入圖片描述
下一步就是更改偏移地址了,這里只需要在主函數(shù)里加
SCB->VTOR=FLASH_BASE|0x10000; 即可,我這里偏移0x10000
在這里插入圖片描述
到此就能編譯生成bin文件,通過IAP程序,使用串口來完成升級(jí)了。
但是后續(xù)要升級(jí)怎么辦,我們還得從APP跳轉(zhuǎn)到IAP來。所以在APP程序中還需要接收指令來跳轉(zhuǎn)到IAP去。
在這里插入圖片描述
這里Receive_Data_Point3是接收到的字節(jié)長(zhǎng)度,我設(shè)置的跳轉(zhuǎn)指令是APPTOIAP!共9位。
然后就是跳轉(zhuǎn)前還需將存放標(biāo)志位得地址寫入0x00,前面IAP中會(huì)對(duì)該地址得值進(jìn)行判斷是否是0x11,是的話就又跳回來了。然后有大佬踩坑后,得知跳轉(zhuǎn)到IAP直接用NVIC_SystemReset(); 即可。
在這里插入圖片描述
到此就基本完成了。
第一次寫博客,也是為了記錄和學(xué)習(xí)。語(yǔ)言不通順還請(qǐng)多諒解,如有錯(cuò)誤的地方也請(qǐng)多加指正。同時(shí)有疑問的同學(xué)也可以評(píng)論留言,歡迎討論交流。

可能出現(xiàn)的問題解決方法:
在后續(xù)的測(cè)試中發(fā)現(xiàn),串口接收bin文件時(shí)還沒接收完就進(jìn)入到了寫入flash的動(dòng)作,導(dǎo)致有很大的概率程序升級(jí)失敗。分析了半天原因可能是,接收緩存在10ms內(nèi)沒收到數(shù)據(jù)就默認(rèn)接收完畢進(jìn)入寫入跳轉(zhuǎn)了,我使用的9600波特率,在將下圖中的延時(shí)增加到100ms后,沒有再出現(xiàn)問題了。
在這里插入圖片描述

Chapter3 STM32學(xué)習(xí)筆記之簡(jiǎn)易Bootloader串口升級(jí)設(shè)計(jì)

原文鏈接:https://blog.csdn.net/xinghuanmeiying/article/details/79573065

概念簡(jiǎn)介

在學(xué)習(xí)制作串口升級(jí) Bootloader 之前,我們先了解一下STM32的 IAP (In Application Programming)即在應(yīng)用編程,IAP是用戶自己的程序在運(yùn)行過程中對(duì) User Flash 的部分區(qū)域進(jìn)行燒寫,目的是為了在產(chǎn)品發(fā)布后可以方便地通過預(yù)留的通信口對(duì)產(chǎn)品中的固件程序進(jìn)行更新升級(jí).
設(shè)計(jì)這樣的功能需要有兩個(gè)項(xiàng)目代碼:

Bootloader 程序: 該部分代碼用于實(shí)現(xiàn)通過某種通信方式(如 USB,USART)接收程序或數(shù)據(jù),執(zhí)行對(duì)第二部分代碼的更新,通常存儲(chǔ)于 Flash 的起始地址(0x08000000);
App 程序: 該部分代碼是產(chǎn)品實(shí)現(xiàn)業(yè)務(wù)邏輯正常運(yùn)行的代碼,該部分代碼需要在存儲(chǔ)的 Flash 中進(jìn)行相對(duì)的地址偏移.

復(fù)位執(zhí)行流程

當(dāng)芯片上電或者復(fù)位后,首先是Bootloader代碼開始運(yùn)行,它作如下操作:

(1) 檢查是否需要對(duì)第二部分代碼進(jìn)行更新;
(2) 如果不需要更新則轉(zhuǎn)到(4);
(3) 執(zhí)行更新操作;
(4) 跳轉(zhuǎn)到第二部分代碼執(zhí)行.

加了 Bootloader 程序后,程序運(yùn)行流程圖:
在這里插入圖片描述

實(shí)現(xiàn)要點(diǎn)

Bootloader程序?qū)崿F(xiàn)

(1) 完成 USART 串口數(shù)據(jù)接收傳輸功能,并將接收到的數(shù)據(jù)絕對(duì)定位到 SRAM 的設(shè)定地址中,地址的設(shè)定根據(jù)實(shí)際情況而定,應(yīng)保證設(shè)定的地址大于 Bootloader 執(zhí)行需要的 RAM 的空間;
(2) 需要實(shí)現(xiàn)對(duì) STM32 Flash 讀寫操作相關(guān)的驅(qū)動(dòng),并將絕對(duì)定位 SRAM 處的固件數(shù)據(jù)寫入到 Flash 中;
(3) 完成固件數(shù)據(jù)接收和寫入 Flash 后,需要對(duì) PC 指針進(jìn)行程序跳轉(zhuǎn),跳轉(zhuǎn)完成后,即運(yùn)行固件中的程序.

注: 程序跳轉(zhuǎn)之前需要關(guān)閉所有的中斷.

App程序?qū)崿F(xiàn)

(1) 在 main 函數(shù)最開頭處設(shè)置 VTOR 寄存器,實(shí)現(xiàn)固件中斷向量表的重定向.例如:

SCB->VTOR = FLASH_BASE | 0X10000; /*中斷向量表偏移量設(shè)置*/

(2) 設(shè)置 App 程序的存儲(chǔ)偏移地址,這里以 STM32F103ZE系列的單片機(jī)為例,Flash為512k,SRAM為64k.這里設(shè)置Flash前64k用于存儲(chǔ) Bootloader 程序,后448k為固件程序空間,SRAM地址不需要設(shè)置偏移.
在這里插入圖片描述
可以用jflash查看生成的HEX文件,查看HEX起始地址是否為設(shè)置的偏移地址,如下圖所示:
在這里插入圖片描述
若程序跳轉(zhuǎn)時(shí),發(fā)生 HardFault 異常中斷,可能是設(shè)置的地址偏移量沒有生效,應(yīng)該勾選 MDK 的相關(guān)配置.
在這里插入圖片描述
(3) 將編譯生成的 .axf 文件 (要勾選生成HEX文件) 通過 MDK 安裝時(shí)自帶的 fromelf.exe 程序轉(zhuǎn)成可以通過串口升級(jí)的 bin文件.

語(yǔ)法格式為: [MDK安裝目錄/fromelf.exe] –bin -o [bin文件生成目錄] [axf文件目錄]
點(diǎn)擊編譯即可生成可以通過串口升級(jí)的 bin 文件.
在這里插入圖片描述
總結(jié): App 程序在代碼上除了需要設(shè)置中斷向量表偏移和程序存儲(chǔ)地址偏移以外,其余和不加 Bootloader 的程序代碼并無差別,難點(diǎn)在于理解 Flash 偏移地址的設(shè)置.

Chapter4 STM32 IAP應(yīng)用開發(fā)——自制BootLoader

原文鏈接:https://blog.csdn.net/ShenZhen_zixian/article/details/129064681

實(shí)際上,BootLoader不僅僅在操作系統(tǒng)上使用,在一些內(nèi)存小,功能應(yīng)用較為簡(jiǎn)單的單片機(jī)設(shè)備上面也可以通過BootLoader來完成固件升級(jí)。

我之前也有發(fā)過一些關(guān)于STM32遠(yuǎn)程升級(jí)的文章,但用的是第三方BootLoader,而且是基于操作系統(tǒng)實(shí)現(xiàn)的。BootLoader占用的內(nèi)存也比較大,而且不開源。
所以這一講我就來介紹一下如何自己制作一個(gè)簡(jiǎn)單的BootLoader程序。

1 環(huán)境搭建

關(guān)于STM32以及Keil的環(huán)境這里就不具體介紹了,網(wǎng)上教程也很多,不懂的同學(xué)自行查閱資料。

2 BootLoader工作原理以及常見分區(qū)介紹

不管用的是什么MCU,要實(shí)現(xiàn)固件升級(jí)都離不開BootLoader,BootLoader是一個(gè)統(tǒng)稱,它其實(shí)只是一段引導(dǎo)程序,在MCU啟動(dòng)的時(shí)候會(huì)先運(yùn)行這段代碼,判斷是否需要升級(jí),如果不需要升級(jí)就跳轉(zhuǎn)到APP分區(qū)運(yùn)行用戶代碼,如果需要升級(jí)則先通過一些硬件接口接收和搬運(yùn)要升級(jí)的新固件,然后再跳轉(zhuǎn)到APP分區(qū)運(yùn)行新固件,從而實(shí)現(xiàn)固件升級(jí)。
在這里插入圖片描述

常見分區(qū)方式介紹:
1.Application
沒有加入Bootloader之前,我們單片機(jī)內(nèi)部的flash就是一整塊的,所有的應(yīng)用代碼都放在這。
在這里插入圖片描述
2.Bootloader + Application
在原有的flash區(qū)域里面劃分出兩個(gè)區(qū)域,Bootloader和Application,這種分區(qū)方式的好處在于既可以實(shí)現(xiàn)升級(jí)功能,App區(qū)又可以分到較大的空間,缺點(diǎn)是沒有存放新固件的區(qū)域,需要從外部導(dǎo)入進(jìn)來,而且一旦傳輸?shù)倪^程被異常打斷,那么原有的App代碼也無法正常運(yùn)行了,也就是傳說中的“變磚”。
在這里插入圖片描述
3.Bootloader + Application + Download
這種分區(qū)方式是比較萬能的一種,優(yōu)點(diǎn)是新固件是先存放到Download區(qū)的,哪怕搬運(yùn)的過程中出現(xiàn)異常中斷的情況,也不會(huì)“變磚”,缺點(diǎn)是需要單獨(dú)劃分一塊內(nèi)存跟APP區(qū)差不多的區(qū)域用來存放新固件,變相的減少了APP區(qū)的空間,對(duì)于內(nèi)存較小的單片機(jī)來說壓力就比較大了。
在這里插入圖片描述
4.Bootloader + Application1 + Application2
這種方式可以同時(shí)存在兩套App,優(yōu)點(diǎn)在于升級(jí)了新固件以后,還保留了原來的舊版固件,必要的時(shí)候還可以進(jìn)行版本的回退。
在這里插入圖片描述

5.Bootloader + Setting + Application + Download
這種方式跟第3種基本一樣,只是增加了一個(gè)區(qū)域用來存放升級(jí)相關(guān)的一些參數(shù)以及用戶的一些配置。
在這里插入圖片描述

3 BootLoader的制作

BootLoader的制作需要根據(jù)實(shí)際的需求來做,不同的運(yùn)行方式或者升級(jí)方式在做法上都是有區(qū)別的,包括BootLoader所需要的內(nèi)存空間也不盡相同。
不過不管是用什么方式,Bootloader都應(yīng)該盡可能做的更小更簡(jiǎn)潔,這樣的話內(nèi)存的開銷就更小,對(duì)于內(nèi)存較小的MCU來說壓力就沒那么大了。

我下面要做的這個(gè)bootloader是上面講的常見分區(qū)方式里面的第5種。
分區(qū)介紹:
我用的是STM32F103,內(nèi)存是128K的(想用內(nèi)存更小的MCU也是可以的,改下各個(gè)分區(qū)的內(nèi)存分配就行了)。

分區(qū)表如下:
在這里插入圖片描述
功能描述:
運(yùn)行bootloader的時(shí)候先從setting里面讀一些參數(shù),確定是否需要升級(jí),如果需要,則把download分區(qū)的固件搬運(yùn)到app分區(qū),如果不需要升級(jí)則直接跳轉(zhuǎn)到app分區(qū).
至于新固件的下載傳輸過程,我放到App里面去處理了,這跟我的項(xiàng)目實(shí)際需求有關(guān)系,App部分這里就先不往下拓展了,后面我會(huì)專門寫一篇博客來介紹。

各個(gè)功能模塊的具體講解:
1、分區(qū)定義
先把各個(gè)分區(qū)的內(nèi)存地址以及大小定義好,方便后面使用。

#define FLASH_SECTOR_SIZE       1024
#define FLASH_SECTOR_NUM        128    // 128K
#define FLASH_START_ADDR        ((uint32_t)0x8000000)
#define FLASH_END_ADDR          ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))#define BOOT_SECTOR_ADDR        0x08000000     // BOOT sector start address 
#define BOOT_SECTOR_SIZE        0x3000         // BOOT sector size
#define SETTING_SECTOR_ADDR     0x08003000     // SETTING sector start address 
#define SETTING_SECTOR_SIZE     0x1000         // SETTING sector size
#define APP_SECTOR_ADDR         0x08004000     // APP sector start address  
#define APP_SECTOR_SIZE         0xE000         // APP sector size
#define DOWNLOAD_SECTOR_ADDR    0x08012000     // Download sector start address
#define DOWNLOAD_SECTOR_SIZE    0xE000         // Download sector size   

2、程序跳轉(zhuǎn)
Bootloader作為引導(dǎo)程序,最重要的工作之一就是通過內(nèi)存跳轉(zhuǎn)進(jìn)入用戶程序,下面這段代碼可以跳轉(zhuǎn)到任何一個(gè)內(nèi)存地址。

uint8_t jump_app(uint32_t app_addr) 
{uint32_t jump_addr;jump_callback cb;if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000) {  jump_addr = *(__IO uint32_t*) (app_addr + 4);  cb = (jump_callback)jump_addr;  __set_MSP(*(__IO uint32_t*)app_addr);  cb();return 1;} return 0;
}

3、處理函數(shù)
從setting區(qū)里面讀取process狀態(tài)值,然后進(jìn)行對(duì)應(yīng)的處理,如果需要升級(jí)則把download區(qū)的固件搬運(yùn)到app區(qū),然后再運(yùn)行新APP,如果不需要升級(jí)則直接跳轉(zhuǎn)到APP。

process = get_boot_state();
switch (process) 
{case START_PROGRAM:printf("start app...\r\n");delay_ms(50);if (!jump_app(APP_SECTOR_ADDR)) {printf("no program\r\n");delay_ms(1000);}printf("start app failed\r\n");break;case UPDATE_PROGRAM:printf("update app program...\r\n");app_addr = APP_SECTOR_ADDR;down_addr = DOWNLOAD_SECTOR_ADDR;printf("app addr: 0x%08X \r\n", app_addr);printf("down addr: 0x%08X \r\n", down_addr);printf("erase mcu flash...\r\n");mcu_flash_erase(app_addr, APP_ERASE_SECTORS);  printf("mcu flash erase success\r\n");printf("write mcu flash...\r\n");// memset(down_buf, 0, sizeof(down_buf));for (i = 0; i < APP_ERASE_SECTORS * 8; i++){mcu_flash_read(down_addr, &down_buf[0], 128);delay_ms(5);mcu_flash_write(app_addr, &down_buf[0], 128);delay_ms(5);down_addr += 128;app_addr += 128;}printf("mcu flash write success\r\n");set_boot_state(UPDATE_SUCCESS);break;case UPDATE_SUCCESS:printf("update success\r\n");boot_state = UPDATE_SUCCESS_STATE;write_setting_boot_state(boot_state);set_boot_state(START_PROGRAM);break;default:break;
}

完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87462312

4 燒錄下載配置

我們的Bootloader做好以后需要燒錄到MCU里面,可以直接用Keil uVison來下載,也可以用J-Flash或者其他,這個(gè)都沒關(guān)系,但是要注意內(nèi)存的分配,要把固件燒到對(duì)應(yīng)的內(nèi)存地址上。
我這里做出來的bootloader bin只有8K,不過為了方便后續(xù)在這部分增加新功能,我實(shí)際分配了12K的空間,地址區(qū)間是0x08000000-0x08003000。

如果是用keil下載的話,需要注意flash的配置,具體如下:
在這里插入圖片描述

在這里插入圖片描述
如果是用J-Flash或者STlink的工具燒錄的話注意燒錄的起始地址是0x08000000就好了。

5 運(yùn)行測(cè)試

注:這里我沒講解App部分代碼,你們只看Bootloader部分的log就好了,不影響的,想看APP部分可以看我另外一篇文章,或者下載完整的代碼實(shí)際跑一下也行。APP部分講解:STM32 IAP應(yīng)用開發(fā)——通過USB實(shí)現(xiàn)固件升級(jí)

運(yùn)行結(jié)果:
不需要升級(jí)時(shí)直接跳轉(zhuǎn)到App區(qū),如下圖:
在這里插入圖片描述
需要升級(jí)時(shí)先從download區(qū)搬運(yùn)新固件到app區(qū),然后再跳轉(zhuǎn)到App區(qū),如下圖:
在這里插入圖片描述

結(jié)束語(yǔ)

好了,關(guān)于自制BootLoader的介紹就講到這里,本文只是提供一個(gè)思路,不是唯一的方法,關(guān)鍵還是看你自己實(shí)際的需求。
還有App那部分這里沒詳細(xì)講,我單獨(dú)寫了一篇文章,鏈接在下方,合到一起看就比較清晰了?;蛘吣阋部梢韵螺d完整的源碼自己去跑一下,下面的源碼我把BootLoader和APP都上傳了。

APP部分講解:STM32 IAP應(yīng)用開發(fā)——通過USB實(shí)現(xiàn)固件升級(jí)
完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87462312

如果你有什么問題或者有更好的方法,歡迎在評(píng)論區(qū)留言。

更多相關(guān)文章:
STM32固件升級(jí)系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

http://www.risenshineclean.com/news/7200.html

相關(guān)文章:

  • 如何作做網(wǎng)站百度一下進(jìn)入首頁(yè)
  • 公司網(wǎng)站 開源如何進(jìn)行網(wǎng)絡(luò)營(yíng)銷推廣
  • 做速賣通的素材有哪些網(wǎng)站做百度推廣的公司電話號(hào)碼
  • 代做網(wǎng)站作業(yè)廣告推廣平臺(tái)網(wǎng)站
  • 備案的域名可以做盜版電影網(wǎng)站嗎廣州各區(qū)正在進(jìn)一步優(yōu)化以下措施
  • 建設(shè)銀行網(wǎng)站維修圖片做營(yíng)銷策劃的公司
  • 甘孜州住房和城鄉(xiāng)規(guī)劃建設(shè)局網(wǎng)站外包公司為什么沒人去
  • 廣州企業(yè)網(wǎng)站建設(shè)公司bt磁力搜索神器
  • 做我的狗哪個(gè)網(wǎng)站可以看seo優(yōu)化技術(shù)是什么
  • 最新軟件發(fā)布平臺(tái)seo搜索引擎優(yōu)化課程總結(jié)
  • 西安正規(guī)網(wǎng)站建設(shè)報(bào)價(jià)重慶seo服務(wù)
  • 網(wǎng)站制作鄭州網(wǎng)站制作網(wǎng)站制作的重要性及步驟詳解
  • 企業(yè)如何建公司網(wǎng)站網(wǎng)站登錄入口
  • 可信網(wǎng)站辦理大數(shù)據(jù)精準(zhǔn)營(yíng)銷
  • 重慶免費(fèi)網(wǎng)站制作寧波免費(fèi)seo排名優(yōu)化
  • 網(wǎng)站添加谷歌地圖商城小程序開發(fā)哪家好
  • 網(wǎng)頁(yè)傳奇手游排行榜前十名吉林關(guān)鍵詞優(yōu)化的方法
  • 企業(yè)商務(wù)網(wǎng)站建設(shè)論文保定百度推廣聯(lián)系電話
  • 做視頻網(wǎng)站怎么掙錢上海關(guān)鍵詞優(yōu)化推薦
  • 網(wǎng)站建設(shè)河南優(yōu)化合作平臺(tái)
  • jquery 案例網(wǎng)站騰訊與中國(guó)聯(lián)通
  • 長(zhǎng)沙長(zhǎng)沙h5網(wǎng)站建設(shè)百度網(wǎng)頁(yè)鏈接
  • 學(xué)做美食網(wǎng)站哪個(gè)好文案短句干凈治愈
  • wordpress 換域名插件關(guān)鍵詞排名優(yōu)化江蘇的團(tuán)隊(duì)
  • asp網(wǎng)站圖片輪播代碼衡陽(yáng)seo優(yōu)化推薦
  • python做網(wǎng)站 jsp網(wǎng)站企業(yè)網(wǎng)站推廣方案
  • 手機(jī)上怎么建網(wǎng)站怎么推廣軟件
  • 企業(yè)建設(shè)網(wǎng)站專業(yè)服務(wù)百度推廣怎么弄
  • 響應(yīng)式網(wǎng)站適合用什么框架做龍網(wǎng)網(wǎng)絡(luò)推廣軟件
  • 廈門維品網(wǎng)站建設(shè)手機(jī)優(yōu)化軟件哪個(gè)好用