IAP很常見了,我這里主要是記錄一下我所使用的方法,調(diào)試也花了兩天時(shí)間。我所用的型號是STM32F103C8T6,這個(gè)片子估計(jì)是目前性價(jià)比最高的了,所以平時(shí)也都是用的這個(gè)。這個(gè)IC有64KFlash和20K的RAM,也有小道說有后置隱藏的64K,也就是說其實(shí)是有128K,我一直也沒有測試,有空測測,有大神這樣說,估計(jì)是可以的。這里重點(diǎn)記錄一下我寫的IAP思路和代碼以及細(xì)節(jié)和遇到坑的地方。先大體的概述一下,最后貼上我認(rèn)為重點(diǎn)的代碼。
在概述之前先要解決一個(gè)問題,那就是sram空間和flash空間的問題,sram只有20K,flash有64k。
解決的辦法有很多:
1)最常見的就是自己寫上位機(jī)軟件,通過分包發(fā)送,期間還可以加入加密算法,校驗(yàn)等等。
2)使用環(huán)形隊(duì)列,簡單點(diǎn)說就是個(gè)環(huán)形數(shù)組,一邊接收上位機(jī)數(shù)據(jù),一邊往flash里面寫。
這里條件限制就采用第二種方法。所以即使是分給A和B的25K空間的flash空間,sram只有20K也是不能一次接收完所有的bin數(shù)據(jù)的,這里我只開辟了一個(gè)1K的BUF,使用尾插法寫入,我的測試應(yīng)用程序都在5-6K,用這樣的方法可以在9600波特率下測試穩(wěn)定,也試過57600的勉強(qiáng)可以的,115200就不行了。
環(huán)形隊(duì)列代碼如下:
C文件:
#include "fy_looplist.h"
#include "fy_includes.h"
#ifndef NULL
#define NULL 0
#endif
#ifndef min
#define min(a, b) (a)<(b)?(a):(b) //< 獲取最小值
#endif
#define DEBUG_LOOP 1
static int Create(_loopList_s* p,unsigned char *buf,unsigned int len);
static void Delete(_loopList_s* p);
static int Get_Capacity(_loopList_s *p);
static int Get_CanRead(_loopList_s *p);
static int Get_CanWrite(_loopList_s *p);
static int Read(_loopList_s *p, void *buf, unsigned int len);
static int Write(_loopList_s *p, const void *buf, unsigned int len);
struct _typdef_LoopList _list=
{
Create,
Delete,
Get_Capacity,
Get_CanRead,
Get_CanWrite,
Read,
Write
};
//初始化環(huán)形緩沖區(qū)
static int Create(_loopList_s* p,unsigned char *buf,unsigned int len)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULLn");
#endif
return 0;
}
p->capacity = len;
p->buf = buf;
p->head = p->buf;//頭指向數(shù)組首地址
p->tail = p->buf;//尾指向數(shù)組首地址
return 1;
}
//刪除一個(gè)環(huán)形緩沖區(qū)
static void Delete(_loopList_s* p)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULLn");
#endif
return;
}
p->buf = NULL;//地址賦值為空
p->head = NULL;//頭地址為空
p->tail = NULL;//尾地址尾空
p->capacity = 0;//長度為空
}
//獲取鏈表的長度
static int Get_Capacity(_loopList_s *p)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULLn");
#endif
return -1;
}
return p->capacity;
}
//返回能讀的空間
static int Get_CanRead(_loopList_s *p)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULLn");
#endif
return -1;
}
if(p->head == p->tail)//頭與尾相遇
{
return 0;
}
if(p->head < p->tail)//尾大于頭
{
return p->tail - p->head;
}
return Get_Capacity(p) - (p->head - p->tail);//頭大于尾
}
//返回能寫入的空間
static int Get_CanWrite(_loopList_s *p)
{
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULLn");
#endif
return -1;
}
return Get_Capacity(p) - Get_CanRead(p);//總的減去已經(jīng)寫入的空間
}
// p--要讀的環(huán)形鏈表
// buf--讀出的數(shù)據(jù)
// count--讀的個(gè)數(shù)
static int Read(_loopList_s *p, void *buf, unsigned int len)
{
int copySz = 0;
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: input list is NULLn");
#endif
return -1;
}
if(NULL == buf)
{
#if DEBUG_LOOP
printf("ERROR: input buf is NULLn");
#endif
return -2;
}
if(p->head < p->tail)//尾大于頭
{
copySz = min(len, Get_CanRead(p)); //比較能讀的個(gè)數(shù)
memcpy(buf, p->head, copySz); //讀出數(shù)據(jù)
p->head += copySz; //頭指針加上讀取的個(gè)數(shù)
return copySz; //返回讀取的個(gè)數(shù)
}
else //頭大于等于了尾
{
if (len < Get_Capacity(p)-(p->head - p->buf))//讀的個(gè)數(shù)小于頭上面的數(shù)據(jù)量
{
copySz = len;//讀出的個(gè)數(shù)
memcpy(buf, p->head, copySz);
p->head += copySz;
return copySz;
}
else//讀的個(gè)數(shù)大于頭上面的數(shù)據(jù)量
{
copySz = Get_Capacity(p) - (p->head - p->buf);//先讀出來頭上面的數(shù)據(jù)
memcpy(buf, p->head, copySz);
p->head = p->buf;//頭指針指向數(shù)組的首地址
//還要讀的個(gè)數(shù)
copySz += Read(p,(char*)buf+copySz, len-copySz);//接著讀剩余要讀的個(gè)數(shù)
return copySz;
}
}
}
// p--要寫的環(huán)形鏈表
// buf--寫出的數(shù)據(jù)
// len--寫的個(gè)數(shù)
static int Write(_loopList_s *p, const void *buf, unsigned int len)
{
int tailAvailSz = 0;//尾部剩余空間
if(NULL == p)
{
#if DEBUG_LOOP
printf("ERROR: list is empty n");
#endif
return -1;
}
if(NULL == buf)
{
#if DEBUG_LOOP
printf("ERROR: buf is empty n");
#endif
return -2;
}
if (len >= Get_CanWrite(p))//如果剩余的空間不夠
{
#if DEBUG_LOOP
printf("ERROR: no memory n");
#endif
return -3;
}
if (p->head <= p->tail)//頭小于等于尾
{
tailAvailSz = Get_Capacity(p) - (p->tail - p->buf); //查看尾上面剩余的空間
if (len <= tailAvailSz)//個(gè)數(shù)小于等于尾上面剩余的空間
{
memcpy(p->tail, buf, len);//拷貝數(shù)據(jù)到環(huán)形數(shù)組
p->tail += len;//尾指針加上數(shù)據(jù)個(gè)數(shù)
if (p->tail == p->buf+Get_Capacity(p))//正好寫到最后
{
p->tail = p->buf;//尾指向數(shù)組的首地址
}
return len;//返回寫入的數(shù)據(jù)個(gè)數(shù)
}
else
{
memcpy(p->tail, buf, tailAvailSz); //填入尾上面剩余的空間
p->tail = p->buf; //尾指針指向數(shù)組首地址
//剩余空間 剩余數(shù)據(jù)的首地址 剩余數(shù)據(jù)的個(gè)數(shù)
return tailAvailSz + Write(p, (char*)buf+tailAvailSz, len-tailAvailSz);//接著寫剩余的數(shù)據(jù)
}
}
else //頭大于尾
{
memcpy(p->tail, buf, len);
p->tail += len;
return len;
}
}
/*********************************************END OF FILE********************************************/
頭文件
#ifndef __FY_LOOPLIST_H
#define __FY_LOOPLIST_H
//環(huán)形緩沖區(qū)數(shù)據(jù)結(jié)構(gòu)
typedef struct {
unsigned int capacity; //空間大小
unsigned char *head; //頭
unsigned char *tail; //尾
unsigned char *buf; //數(shù)組的首地址
} _loopList_s;
struct _typdef_LoopList
{
int (*Create) (_loopList_s* p,unsigned char *buf,unsigned int len);
void (*Delete)(_loopList_s* p);
int (*Get_Capacity)(_loopList_s *p);
int (*Get_CanRead)(_loopList_s *p);
int (*Get_CanWrite)(_loopList_s *p);
int (*Read)(_loopList_s *p, void *buf, unsigned int len);
int (*Write)(_loopList_s *p, const void *buf, unsigned int len);
};
extern struct _typdef_LoopList _list;
#endif
1、整體思路
1、把64K的flash空間分成了4個(gè)部分,第一部分是BootLoader,第二部分是程序A(APP1),第三部分是程序B(APP2),第四部分是用來存儲一些變量和標(biāo)記的。下面是空間的分配情況。BootLoader程序可以用來更新程序A,而程序A又更新程序B,程序B可以更新程序A。最開始的時(shí)候想的是程序A、B都帶更新了干嘛還多此一舉,其實(shí)這個(gè)Bootloader還是需要的。如果之后程序A、B和FLAG三部分,假設(shè)一種情況,在程序B中更新程序A中遇到問題,復(fù)位后直接成磚,因?yàn)槌绦駻在其實(shí)地址,上電直接運(yùn)行程序A,而程序A現(xiàn)在出問題了,那就沒招了。所以加上BootLoader情況下,不管怎么樣BootLoader的程序是不會錯的,因?yàn)楦虏粫翨ootLoader,計(jì)時(shí)更新出錯了,還可以進(jìn)入BootLoader重新更新應(yīng)用程序。我見也有另外一種設(shè)計(jì)方法的,就是應(yīng)用程序只有一個(gè)程序A,把程序B區(qū)域的flash當(dāng)作緩存用,重啟的時(shí)候判斷B區(qū)域有沒有更新程序,有的話就把B拷貝到A,然后擦除B,我感覺這樣其實(shí)也一樣,反正不管怎么樣這部分空間是必須要預(yù)留出來的。

這里在keil中配置的只有起始地址和大小,并沒有結(jié)束地址,我這里也就不詳細(xì)計(jì)算了??傮w就是這樣的。
2、Bootloader部分
BootLoader的任務(wù)有兩個(gè),一是在串口中斷接收BIN的數(shù)據(jù)和主循環(huán)內(nèi)判斷以及更新APP1的程序,二是在在程序開始的時(shí)候判斷有沒有可用的用戶程序進(jìn)而跳轉(zhuǎn)到用戶程序(程序A或者程序B)。
簡單介紹下執(zhí)行流程:
系統(tǒng)上電首先肯定是執(zhí)行BootLoader程序的,因?yàn)樗钠鹗嫉刂肪褪?x08000000,首先是初始化,然后判斷按鍵是否手動升級程序,按鍵按下了就把FLAG部分的APP標(biāo)記寫成0xFFFF(這里用的宏定義方式),再執(zhí)行執(zhí)行App_Check(),否則就直接執(zhí)行App_Check()。
App_Check函數(shù)是來判斷程序A和程序B的,最開始BootLoader是用swd方式下載的,下載的時(shí)候全片擦除,所以會執(zhí)行主循環(huán)的Update_Check函數(shù)。此時(shí)串口打印出“等待接收APP1的BIN”,這個(gè)時(shí)候發(fā)送APP1的BIN過去,等接受完了,會寫在FLAG區(qū)域?qū)憘€(gè)0xAAAA,代表程序A寫入了,下次啟動可以執(zhí)行程序A。
主要代碼部分
#include "fy_includes.h" /* 晶振使用的是16M 其他頻率在system_stm32f10x.c中修改 使用printf需要在fy_includes.h修改串口重定向?yàn)?define PRINTF_USART USART1 */ /* Bootloader程序 完成三個(gè)任務(wù) 步驟1.檢查是否有程序更新,如果有就擦寫flash進(jìn)行更新,如果沒有進(jìn)入步驟2 步驟2.判斷app1有沒有可執(zhí)行程序,如果有就執(zhí)行,如果沒有進(jìn)入步驟3 步驟3.串口等待接收程序固件 */ #define FLAG_UPDATE_APP1 0xBBAA #define FLAG_UPDATE_APP2 0xAABB #define FLAG_APP1 0xAAAA #define FLAG_APP2 0xBBBB #define FLAG_NONE 0xFFFF _loopList_s list1; u8 rxbuf[1024]; u8 temp8[2]; u16 temp16; u32 rxlen=0; u32 applen=0; u32 write_addr; u8 overflow=0; u32 now_tick=0; u8 _cnt_10ms=0; static void App_Check(void) { //獲取程序標(biāo)號 STMFLASH_Read(FLASH_PARAM_ADDR, temp16,1); if(temp16 == FLAG_APP1)//執(zhí)行程序A { if(((*(vu32*)(FLASH_APP1_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序A...rn"); IAP_RunApp(FLASH_APP1_ADDR); } else { printf(" 程序A不可執(zhí)行,擦除APP1程序所在空間...rn"); for(u8 i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序A所在空間擦除完成... rn"); printf(" 將執(zhí)行程序B... rn"); if(((*(vu32*)(FLASH_APP2_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序B...rn"); IAP_RunApp(FLASH_APP2_ADDR); } else { printf(" 程序B不可執(zhí)行,擦除APP2程序所在空間...rn"); for(u8 i=35;i<60;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序B所在空間擦除完成...rn"); } } } if(temp16 == FLAG_APP2)//執(zhí)行程序B { if(((*(vu32*)(FLASH_APP2_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序B...rn"); IAP_RunApp(FLASH_APP2_ADDR); } else { printf(" 程序B不可執(zhí)行,擦除APP2程序所在空間... rn"); for(u8 i=35;i<60;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序B所在空間擦除完成... rn"); printf(" 將執(zhí)行程序A... rn"); if(((*(vu32*)(FLASH_APP1_ADDR+4)) 0xFF000000)==0x08000000)//可執(zhí)行? { printf(" 執(zhí)行程序A...rn"); IAP_RunApp(FLASH_APP1_ADDR); } else { printf(" 程序A不可執(zhí)行,擦除APP1程序所在空間...rn"); for(u8 i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序A所在空間擦除完成...rn"); } } } if(temp16 == FLAG_NONE) { printf(" 擦除App1程序所在空間...rn"); for(u8 i=10;i<35;i++) { STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512); } printf(" 程序A所在空間擦除完成...rn"); } } static void Update_Check(void) { if(_list.Get_CanRead( list1)>1) { _list.Read( list1, temp8,2);//讀取兩個(gè)數(shù)據(jù) temp16 = (u16)(temp8[1]<<8) | temp8[0]; STMFLASH_Write(write_addr, temp16,1); write_addr+=2; } if(GetSystick_ms() - now_tick >10)//10ms { now_tick = GetSystick_ms(); _cnt_10ms++; if(applen == rxlen rxlen)//接收完成 { if(overflow) { printf("接收溢出,無法更新,請重試 rn"); SoftReset();//軟件復(fù)位 } else { printf(" rn 接收BIN文件完成,長度為 %d rn",applen); temp16 = FLAG_APP1; STMFLASH_Write(FLASH_PARAM_ADDR, temp16,1);//寫入標(biāo)記 temp16 = (u16)(applen>>16); STMFLASH_Write(FLASH_PARAM_ADDR+2, temp16,1); temp16 = (u16)(applen); STMFLASH_Write(FLASH_PARAM_ADDR+4, temp16,1); SoftReset();//軟件復(fù)位 } }else applen = rxlen;//更新長度 } if(_cnt_10ms>=50) { _cnt_10ms=0; Led_Tog(); if(!rxlen) { printf(" 等待接收App1的BIN文件 rn"); } } } int main(void) { NVIC_SetPriorityGrouping( NVIC_PriorityGroup_2); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //開啟AFIO時(shí)鐘 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //禁止JTAG保留SWD Systick_Configuration(); Led_Configuration(); Key_Configuration(); Usart1_Configuration(9600); USART_ITConfig(USART1, USART_IT_IDLE, DISABLE);//關(guān)閉串口空閑中斷 printf(" this is bootloader!rnrn"); if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == SET) { Delay_ms(100); if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == SET)//開機(jī)按下keyup進(jìn)行更新 { printf(" 主動更新,"); temp16 = FLAG_NONE; STMFLASH_Write(FLASH_PARAM_ADDR, temp16,1); } else { } } App_Check(); printf(" 執(zhí)行BootLoader程序... rn"); _list.Create( list1,rxbuf,sizeof(rxbuf)); write_addr = FLASH_APP1_ADDR; while(1) { Update_Check(); } } //USART1串口中斷函數(shù) void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { u8 temp = USART1->DR; if(_list.Write( list1, temp,1)<=0) { overflow=1; } rxlen++; } }
其中的宏:
//FLASH起始地址 #define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址 #define FLASH_APP1_ADDR STM32_FLASH_BASE+0x2800 //偏移10K #define FLASH_APP2_ADDR STM32_FLASH_BASE+0x8c00 //偏移35K #define FLASH_PARAM_ADDR STM32_FLASH_BASE+0xF000 //偏移60K
3、程序A和程序B部分
這兩個(gè)都是用戶程序,這兩個(gè)程序都帶有更新程序功能,我這里用作測試的A和B程序大體都差不多,不同的地方就是程序A接收的BIN用來更新程序B,程序B接收的BIN用來更新A,還有就是中斷向量表便宜不同以及打印輸出不同。
應(yīng)用程序部分沒什么說的,程序A和B很類似,這里貼上A的代碼
#include "fy_includes.h"
/*
晶振使用的是16M 其他頻率在system_stm32f10x.c中修改
使用printf需要在fy_includes.h修改串口重定向?yàn)?define PRINTF_USART USART1
*/
/*
APP1程序
完成兩個(gè)任務(wù)
1.執(zhí)行本身的app任務(wù),同時(shí)監(jiān)聽程序更新,監(jiān)聽到停止本身的任務(wù)進(jìn)入到狀態(tài)2
2.等待接收完成,完成后復(fù)位重啟
*/
#define FLAG_UPDATE_APP1 0xBBAA
#define FLAG_UPDATE_APP2 0xAABB
#define FLAG_APP1 0xAAAA
#define FLAG_APP2 0xBBBB
#define FLAG_NONE 0xFFFF
_loopList_s list1;
u8 rxbuf[1024];
u8 temp8[2];
u16 temp16;
u32 rxlen=0;
u32 applen=0;
u32 write_flsh_addr;
u8 update=0;
u8 overflow=0;
u32 now_tick;
u8 _cnt_10ms=0;
static void Update_Check(void)
{
if(update)//監(jiān)聽到有更新程序
{
write_flsh_addr = FLASH_APP2_ADDR;//App1更新App2的程序
overflow=0;
rxlen=0;
_list.Create( list1,rxbuf,sizeof(rxbuf));
printf(" 擦除APP2程序所在空間...rn");
for(u8 i=35;i<60;i++)//擦除APP2所在空間程序
{
STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
}
printf(" 程序B所在空間擦除完成...rn");
while(1)
{
if(_list.Get_CanRead( list1)>1)
{
_list.Read( list1, temp8,2);//讀取兩個(gè)數(shù)據(jù)
temp16 = (u16)(temp8[1]<<8) | temp8[0];
STMFLASH_Write(write_flsh_addr, temp16,1);
write_flsh_addr+=2;
}
if(GetSystick_ms() - now_tick >10)//10ms
{
now_tick = GetSystick_ms();
_cnt_10ms++;
if(applen == rxlen rxlen)//接收完成
{
if(overflow)
{
printf(" rn 接收溢出,請重新嘗試 rn");
SoftReset();//軟件復(fù)位
}
printf(" rn 接收BIN文件完成,長度為 %d rn",applen);
temp16 = FLAG_APP2;
STMFLASH_Write(FLASH_PARAM_ADDR, temp16,1);//寫入標(biāo)記
temp16 = (u16)(applen>>16);
STMFLASH_Write(FLASH_PARAM_ADDR+2, temp16,1);
temp16 = (u16)(applen);
STMFLASH_Write(FLASH_PARAM_ADDR+4, temp16,1);
printf(" 系統(tǒng)將重啟....rn");
SoftReset();//軟件復(fù)位
}else applen = rxlen;//更新長度
}
if(_cnt_10ms>=50)
{
_cnt_10ms=0;
Led_Tog();
if(!rxlen)
{
printf(" 等待接收App2的BIN文件 rn");
}
}
}//while(1)
}
}
static void App_Task(void)
{
if(GetSystick_ms() - now_tick >500)
{
now_tick = GetSystick_ms();
printf(" 正在運(yùn)行APP1 rn");
Led_Tog();
}
}
int main(void)
{
SCB->VTOR = FLASH_APP1_ADDR;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //開啟AFIO時(shí)鐘
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //禁止JTAG保留SWD
Systick_Configuration();
Led_Configuration();
Usart1_Configuration(9600);
printf(" this is APP1!rn");
Delay_ms(500);
while(1)
{
Update_Check();
App_Task();
}
}
//USART1串口中斷函數(shù)
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
u8 temp = USART1->DR;
if(update)
{
if(_list.Write( list1, temp,1) <= 0 )
{
overflow = 1;
}
}
else
{
rxbuf[rxlen] = temp;
}
rxlen++;
}
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
u8 temp = USART1->DR;
temp = USART1->SR;
if(strstr((char *)rxbuf,"App Update") rxlen)
{
update=1;
USART_ITConfig(USART1, USART_IT_IDLE, DISABLE);//關(guān)閉串口空閑中斷
}
else
{
Usart1_SendBuf(rxbuf,rxlen);
}
rxlen=0;
}
}
這里如果要移植需要注意的就是向量表的偏移以及更新擦寫的區(qū)域。
4、剩余的4Kflash空間部分
這里其實(shí)只是用來存儲2個(gè)變量,一個(gè)是程序運(yùn)行標(biāo)記,一個(gè)是接收到的程序長度,程序標(biāo)記還有點(diǎn)把子用,程序長度其實(shí)要不要都無所謂。
5、遇到的坑
最值得一說的就是更新部分,最開始程序沒有加入擦除flash,遇到的情況就是下載完BootLoader后發(fā)送app1沒問題,在app1中更新App2也沒問題,然后app2再更新app1就出問題了。直觀的結(jié)果就是循環(huán)隊(duì)列溢出,原因就是app2在更新app1前沒有去擦除app1所在的flash,所以在寫的時(shí)候就要去擦除,這樣就寫的很慢,然而串口接收是不停的收,所以就是寫不過來。
審核編輯:彭菁
-
STM32
+關(guān)注
關(guān)注
2305文章
11118瀏覽量
370874 -
串口
+關(guān)注
關(guān)注
15文章
1604瀏覽量
81848 -
IAP
+關(guān)注
關(guān)注
2文章
165瀏覽量
25771 -
代碼
+關(guān)注
關(guān)注
30文章
4940瀏覽量
73055
發(fā)布評論請先 登錄
請問串口接受用環(huán)形隊(duì)列,發(fā)送也能用嗎?
環(huán)形隊(duì)列在串口數(shù)據(jù)接收中的使用
如何使用隊(duì)列實(shí)現(xiàn)STM32串口環(huán)形緩沖?
實(shí)現(xiàn)隊(duì)列環(huán)形緩沖的方法
基于STM32F1的環(huán)形隊(duì)列的程序資料合集免費(fèi)下載
STM32串口環(huán)形緩沖--使用隊(duì)列實(shí)現(xiàn)(開放源碼)
STM32串口數(shù)據(jù)接收 --環(huán)形緩沖區(qū)
基于STM32的串口環(huán)形隊(duì)列IAP調(diào)試心得

基于STM32的串口環(huán)形隊(duì)列IAP調(diào)試
評論