历史上的今天
今天是:2025年05月27日(星期二)
2019年05月27日 | 基于STM32的串口环形队列IAP调试心得
2019-05-27 来源:eefocus
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,我感觉这样其实也一样,反正不管怎么样这部分空间是必须要预留出来的。

这里在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;
史海拾趣
|
用c#实现windowmobile5的蓝牙打印功能,强人给个思路,谢谢 具体情况是这样的。要求用VS2005的C#编写一个window mobile5上的软件,要求实现这样的功能,用蓝牙连接一台同样有蓝牙功能的打印机,并打印该系统上指定的文件或内容。打印机是CMP-10BT http://www.citizen-systems.co.jp/english/printer/tps/c ...… 查看全部问答> |
|
LIBRARY IEEE;USE IEEE.STD_LOGIC_1164.ALL;USE IEEE.STD_LOGIC_ARITH.ALL;USE IEEE.STD_LOGIC_UNSIGNED.ALL; --LIBRARY ALTERA;--USE ALTERA.MAXPLUS2.ALL; LIBRARY LPM;USE LPM.LPM_COMPONENTS.ALL; ENTITY Uart_Tx ISGENERIC(Tx_D_Width : PO ...… 查看全部问答> |




