0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于STM32的串口环形队列IAP调试

jf_pJlTbmA9 来源:jf_pJlTbmA9 作者:jf_pJlTbmA9 2023-09-18 15:33 次阅读

IAP很常见了,我这里主要是记录一下我所使用的方法,调试也花了两天时间。我所用的型号是STM32F103C8T6,这个片子估计是目前性价比最高的了,所以平时也都是用的这个。这个IC有64KFlash和20K的RAM,也有小道说有后置隐藏的64K,也就是说其实是有128K,我一直也没有测试,有空测测,有大神这样说,估计是可以的。这里重点记录一下我写的IAP思路和代码以及细节和遇到坑的地方。先大体的概述一下,最后贴上我认为重点的代码。

在概述之前先要解决一个问题,那就是sram空间和flash空间的问题,sram只有20K,flash有64k。

解决的办法有很多:

1)最常见的就是自己写上位机软件,通过分包发送,期间还可以加入加密算法,校验等等。

2)使用环形队列,简单点说就是个环形数组,一边接收上位机数据,一边往flash里面写。

这里条件限制就采用第二种方法。所以即使是分给A和B的25K空间的flash空间,sram只有20K也是不能一次接收完所有的bin数据的,这里我只开辟了一个1K的BUF,使用尾插法写入,我的测试应用程序都在5-6K,用这样的方法可以在9600波特率下测试稳定,也试过57600的勉强可以的,115200就不行了。

环形队列代码如下:

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
};
 
 
//初始化环形缓冲区
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;//头指向数组首地址
    p->tail = p->buf;//尾指向数组首地址
 
    return 1;
}
 
//删除一个环形缓冲区
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);//总的减去已经写入的空间
}
 
 
//  p--要读的环形链表
//  buf--读出的数据
//  count--读的个数
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));	//比较能读的个数
        memcpy(buf, p->head, copySz);		//读出数据
        p->head += copySz;					//头指针加上读取的个数
        return copySz;						//返回读取的个数
    }
    else //头大于等于了尾
    {
        if (len < Get_Capacity(p)-(p->head - p->buf))//读的个数小于头上面的数据量
        {
            copySz = len;//读出的个数
            memcpy(buf, p->head, copySz);
            p->head += copySz;
            return copySz;
        }
        else//读的个数大于头上面的数据量
        {
            copySz = Get_Capacity(p) - (p->head - p->buf);//先读出来头上面的数据
            memcpy(buf, p->head, copySz);
            p->head = p->buf;//头指针指向数组的首地址
            //还要读的个数
            copySz += Read(p,(char*)buf+copySz, len-copySz);//接着读剩余要读的个数
            return copySz;
        }
    }
}
//  p--要写的环形链表
//  buf--写出的数据
//  len--写的个数
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)//个数小于等于尾上面剩余的空间
        {
            memcpy(p->tail, buf, len);//拷贝数据到环形数组
            p->tail += len;//尾指针加上数据个数
            if (p->tail == p->buf+Get_Capacity(p))//正好写到最后
            {
                p->tail = p->buf;//尾指向数组的首地址
            }
            return len;//返回写入的数据个数
        }
        else
        {
            memcpy(p->tail, buf, tailAvailSz);	//填入尾上面剩余的空间
            p->tail = p->buf;					//尾指针指向数组首地址
            //剩余空间                   剩余数据的首地址       剩余数据的个数
            return tailAvailSz + Write(p, (char*)buf+tailAvailSz, len-tailAvailSz);//接着写剩余的数据
        }
    }
    else //头大于尾
    {
        memcpy(p->tail, buf, len);
        p->tail += len;
        return len;
    }
}
 
 
/*********************************************END OF FILE********************************************/

头文件

#ifndef __FY_LOOPLIST_H
#define __FY_LOOPLIST_H
 
//环形缓冲区数据结构
typedef struct {
    unsigned int   capacity;	//空间大小
    unsigned char  *head; 	//头
    unsigned char  *tail; 	//尾
    unsigned char  *buf; 	//数组的首地址
} _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个部分,第一部分是BootLoader,第二部分是程序A(APP1),第三部分是程序B(APP2),第四部分是用来存储一些变量和标记的。下面是空间的分配情况。BootLoader程序可以用来更新程序A,而程序A又更新程序B,程序B可以更新程序A。最开始的时候想的是程序A、B都带更新了干嘛还多此一举,其实这个Bootloader还是需要的。如果之后程序A、B和FLAG三部分,假设一种情况,在程序B中更新程序A中遇到问题,复位后直接成砖,因为程序A在其实地址,上电直接运行程序A,而程序A现在出问题了,那就没招了。所以加上BootLoader情况下,不管怎么样BootLoader的程序是不会错的,因为更新不会更新BootLoader,计时更新出错了,还可以进入BootLoader重新更新应用程序。我见也有另外一种设计方法的,就是应用程序只有一个程序A,把程序B区域的flash当作缓存用,重启的时候判断B区域有没有更新程序,有的话就把B拷贝到A,然后擦除B,我感觉这样其实也一样,反正不管怎么样这部分空间是必须要预留出来的。

pYYBAGEAt9SAXm_fAABKqTd1KAw663.png

这里在keil中配置的只有起始地址和大小,并没有结束地址,我这里也就不详细计算了。总体就是这样的。

2、Bootloader部分

BootLoader的任务有两个,一是在串口中断接收BIN的数据和主循环内判断以及更新APP1的程序,二是在在程序开始的时候判断有没有可用的用户程序进而跳转到用户程序(程序A或者程序B)。

简单介绍下执行流程:

系统上电首先肯定是执行BootLoader程序的,因为它的起始地址就是0x08000000,首先是初始化,然后判断按键是否手动升级程序,按键按下了就把FLAG部分的APP标记写成0xFFFF(这里用的宏定义方式),再执行执行App_Check(),否则就直接执行App_Check()。

App_Check函数是来判断程序A和程序B的,最开始BootLoader是用swd方式下载的,下载的时候全片擦除,所以会执行主循环的Update_Check函数。此时串口打印出“等待接收APP1的BIN”,这个时候发送APP1的BIN过去,等接受完了,会写在FLAG区域写个0xAAAA,代表程序A写入了,下次启动可以执行程序A。

主要代码部分

#include "fy_includes.h"
 
/*
晶振使用的是16M 其他频率在system_stm32f10x.c中修改
使用printf需要在fy_includes.h修改串口重定向为#define PRINTF_USART USART1 
*/
 
 
/*
Bootloader程序
完成三个任务
步骤1.检查是否有程序更新,如果有就擦写flash进行更新,如果没有进入步骤2
步骤2.判断app1有没有可执行程序,如果有就执行,如果没有进入步骤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)
{
	//获取程序标号
	STMFLASH_Read(FLASH_PARAM_ADDR, temp16,1);
 
	if(temp16 == FLAG_APP1)//执行程序A
	{
		if(((*(vu32*)(FLASH_APP1_ADDR+4)) 0xFF000000)==0x08000000)//可执行?
		{
			printf(" 执行程序A...rn");			
			IAP_RunApp(FLASH_APP1_ADDR);
		}
		else
		{
			printf(" 程序A不可执行,擦除APP1程序所在空间...rn");
			for(u8 i=10;i<35;i++)
			{
				STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
			}
			printf(" 程序A所在空间擦除完成... rn");
			printf(" 将执行程序B... rn");
			if(((*(vu32*)(FLASH_APP2_ADDR+4)) 0xFF000000)==0x08000000)//可执行?
			{
				printf(" 执行程序B...rn");				
				IAP_RunApp(FLASH_APP2_ADDR);
			}
			else
			{
				printf(" 程序B不可执行,擦除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)//执行程序B
	{
		if(((*(vu32*)(FLASH_APP2_ADDR+4)) 0xFF000000)==0x08000000)//可执行?
		{
			printf(" 执行程序B...rn");			
			IAP_RunApp(FLASH_APP2_ADDR);
		}
		else
		{
			printf(" 程序B不可执行,擦除APP2程序所在空间... rn");
			for(u8 i=35;i<60;i++)
			{
				STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
			}
			printf(" 程序B所在空间擦除完成... rn");
			printf(" 将执行程序A... rn");
			if(((*(vu32*)(FLASH_APP1_ADDR+4)) 0xFF000000)==0x08000000)//可执行?
			{
				printf(" 执行程序A...rn");				
				IAP_RunApp(FLASH_APP1_ADDR);
			}
			else
			{
				printf(" 程序A不可执行,擦除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);//读取两个数据
		 
		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();//软件复位
			}
			else
			{
				printf(" rn 接收BIN文件完成,长度为 %d rn",applen);
				
				temp16 = FLAG_APP1;
				STMFLASH_Write(FLASH_PARAM_ADDR, temp16,1);//写入标记
				temp16 = (u16)(applen>>16);
				STMFLASH_Write(FLASH_PARAM_ADDR+2, temp16,1);
				temp16 = (u16)(applen);
				STMFLASH_Write(FLASH_PARAM_ADDR+4, temp16,1);
				
				SoftReset();//软件复位
			}
		}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时钟
    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);//关闭串口空闲中断
 
	printf(" this is bootloader!rnrn");
	if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == SET)
	{
		Delay_ms(100);
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == SET)//开机按下keyup进行更新
		{
			printf(" 主动更新,");
			temp16 = FLAG_NONE;
			STMFLASH_Write(FLASH_PARAM_ADDR, temp16,1);
		}
		else
		{
			
		}
	}
 
	App_Check();
	
	printf(" 执行BootLoader程序... rn");
	_list.Create( list1,rxbuf,sizeof(rxbuf));
 
	write_addr = FLASH_APP1_ADDR;
	
    while(1)
    {		
		Update_Check();
    }
}
 
//USART1串口中断函数
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部分

这两个都是用户程序,这两个程序都带有更新程序功能,我这里用作测试的A和B程序大体都差不多,不同的地方就是程序A接收的BIN用来更新程序B,程序B接收的BIN用来更新A,还有就是中断向量表便宜不同以及打印输出不同。

应用程序部分没什么说的,程序A和B很类似,这里贴上A的代码

#include "fy_includes.h"
 
/*
晶振使用的是16M 其他频率在system_stm32f10x.c中修改
使用printf需要在fy_includes.h修改串口重定向为#define PRINTF_USART USART1 
*/
 
 
/*
APP1程序
完成两个任务
1.执行本身的app任务,同时监听程序更新,监听到停止本身的任务进入到状态2
2.等待接收完成,完成后复位重启
*/
 
#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)//监听到有更新程序
	{
		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);//读取两个数据
				 
				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();//软件复位
					}
					
					printf(" rn 接收BIN文件完成,长度为 %d rn",applen);
					
					temp16 = FLAG_APP2;
					STMFLASH_Write(FLASH_PARAM_ADDR, temp16,1);//写入标记
					temp16 = (u16)(applen>>16);
					STMFLASH_Write(FLASH_PARAM_ADDR+2, temp16,1);
					temp16 = (u16)(applen);
					STMFLASH_Write(FLASH_PARAM_ADDR+4, temp16,1);
					
					printf(" 系统将重启....rn");
					SoftReset();//软件复位
				}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(" 正在运行APP1 rn");
		Led_Tog();
	}
}
 
int main(void)
{
	SCB->VTOR = FLASH_APP1_ADDR; 	 
 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	 //开启AFIO时钟
    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串口中断函数
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);//关闭串口空闲中断
		}
		else
		{
			Usart1_SendBuf(rxbuf,rxlen);
		}
		rxlen=0;
    }
	
}

这里如果要移植需要注意的就是向量表的偏移以及更新擦写的区域。

4、剩余的4Kflash空间部分

这里其实只是用来存储2个变量,一个是程序运行标记,一个是接收到的程序长度,程序标记还有点把子用,程序长度其实要不要都无所谓。

5、遇到的坑

最值得一说的就是更新部分,最开始程序没有加入擦除flash,遇到的情况就是下载完BootLoader后发送app1没问题,在app1中更新App2也没问题,然后app2再更新app1就出问题了。直观的结果就是循环队列溢出,原因就是app2在更新app1前没有去擦除app1所在的flash,所以在写的时候就要去擦除,这样就写的很慢,然而串口接收是不停的收,所以就是写不过来。

审核编辑:彭菁

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • STM32
    +关注

    关注

    2265

    文章

    10870

    浏览量

    354682
  • 串口
    +关注

    关注

    14

    文章

    1543

    浏览量

    76157
  • IAP
    IAP
    +关注

    关注

    2

    文章

    163

    浏览量

    24245
  • 代码
    +关注

    关注

    30

    文章

    4741

    浏览量

    68319
收藏 人收藏

    评论

    相关推荐

    请问串口接受用环形队列,发送也能用吗?

    串口接受用环形队列,发送也可以用?发送用普通的中断也可以
    发表于 05-07 07:56

    环形队列串口数据接收中的使用

    前言  书接上回,前文主要介绍了环形队列的实现原理以及C语言实现及测试过程,本文将回归到嵌入式平台的应用中,话不多说,淦,上干货!实验目的HAL库下串口的配置及使用环形
    发表于 12-06 06:27

    stm32f103的串口IAP调试过程是怎样的?

    stm32f103的串口IAP调试过程是怎样的?
    发表于 12-07 07:04

    如何使用队列实现STM32串口环形缓冲?

    串口环形缓冲的好处是什么?如何使用队列实现STM32串口环形缓冲?
    发表于 12-07 07:13

    基于stm32串口环形缓冲队列处理机制是什么

    基于stm32串口环形缓冲队列处理机制是什么
    发表于 12-08 07:06

    实现队列环形缓冲的方法

    串口队列环形缓冲区队列串口环形缓冲的好处代码实现队列
    发表于 02-21 07:11

    基于STM32F1的环形队列的程序资料合集免费下载

    本文档的主要内容详细介绍的设计基于STM32F1的环形队列的程序资料合集免费下载。
    发表于 04-12 08:00 1次下载
    基于<b class='flag-5'>STM32</b>F1的<b class='flag-5'>环形</b><b class='flag-5'>队列</b>的程序资料合集免费下载

    STM32串口环形缓冲--使用队列实现(开放源码)

    串口队列环形缓冲区队列串口环形缓冲的好处代码实现队列
    发表于 12-24 19:04 28次下载
    <b class='flag-5'>STM32</b><b class='flag-5'>串口</b><b class='flag-5'>环形</b>缓冲--使用<b class='flag-5'>队列</b>实现(开放源码)

    STM32串口数据接收 --环形缓冲区

    STM32串口数据接收 --环形缓冲区环形缓冲区简介  在单片机中串口通信是我们使用最频繁的,使用串口
    发表于 12-28 19:24 30次下载
    <b class='flag-5'>STM32</b><b class='flag-5'>串口</b>数据接收 --<b class='flag-5'>环形</b>缓冲区

    基于STM32串口环形队列IAP调试心得

    使用环形队列,简单点说就是个环形数组,一边接收上位机数据,一边往flash里面写。
    发表于 02-08 15:22 5次下载
    基于<b class='flag-5'>STM32</b>的<b class='flag-5'>串口</b><b class='flag-5'>环形</b><b class='flag-5'>队列</b><b class='flag-5'>IAP</b><b class='flag-5'>调试</b>心得

    基于STM32F103的IAP串口升级源码

    基于STM32F103的IAP串口升级源码代码,共两个工程,bl+app分享
    发表于 09-23 17:08 43次下载

    AN3965 STM32F40x和STM32F41x基于串口IAP

    AN3965 STM32F40x和STM32F41x基于串口IAP
    发表于 11-24 08:31 0次下载
    AN3965 <b class='flag-5'>STM32</b>F40x和<b class='flag-5'>STM32</b>F41x基于<b class='flag-5'>串口</b>的<b class='flag-5'>IAP</b>

    STM32进阶之串口环形缓冲区实现

    码代码的应该学数据结构都学过队列环形队列队列的一种特殊形式,应用挺广泛的。因为有太多文章关于这方面的内容,理论知识可以看别人的,下面写得挺好的:
    发表于 12-06 10:00 2928次阅读

    基于串口环形队列IAP实现

    我这里主要是记录一下我所使用的方法,调试也花了两天时间。
    的头像 发表于 04-12 09:28 686次阅读

    嵌入式环形队列与消息队列的实现原理

    嵌入式环形队列,也称为环形缓冲区或循环队列,是一种先进先出(FIFO)的数据结构,用于在固定大小的存储区域中高效地存储和访问数据。其主要特点包括固定大小的数组和两个指针(头指针和尾指针
    的头像 发表于 09-02 15:29 320次阅读