nt检查是什么意思| 七月十四号是什么星座| 生物科学是什么专业| 85年属什么| 高反是什么意思| 白头发多是什么原因| 戒指戴左手食指是什么意思| 过年为什么要吃饺子| 尿是褐色的是什么原因| pc是什么缩写| 清明上河图什么季节| 什么的跑步| 1月1日是什么节| 四个火读什么| 鼾症是什么病| 跖疣是什么东西| 气血两虚吃什么补最快| 身体缺钾有什么症状| 腰椎疼痛挂什么科| 无所不用其极是什么意思| 角化型脚气用什么药膏| 金牛属于什么象星座| 血糖低什么症状| 奉天为什么改名沈阳| 大腿两侧疼痛什么原因| 不孕为什么要查胰岛素| 肝主什么| 什么是有意义的东西| 月经不来是什么原因导致的| 一条什么| 五月十六日是什么星座| av是什么意思| 门牙旁边的牙齿叫什么| 阳寿是什么意思| 脑炎是什么病严重吗| 华法林是什么药| 吉和页念什么| 高校新生是什么意思| 什么的气味| 重庆东站什么时候通车| 维生素d3吃多了有什么副作用| 肝囊肿吃什么食物好| Fine什么意思中文| 9月24号是什么星座| 红枣有什么功效和作用| 海关是什么意思| 胃气上逆吃什么中成药| 咏字五行属什么| 有趣是什么意思| 大义是什么意思| 东面墙适合挂什么画| 甲状腺查什么| c罗为什么不结婚| 郫县豆瓣酱能做什么菜| 人到无求品自高什么意思| 言重了是什么意思| 区教育局局长是什么级别| 失业是什么意思| 朝朝暮暮是什么意思| 福祸相依什么意思| 什么的饭菜| 请问今晚买什么生肖| 氨水对人体有什么危害| 扶阳是什么意思| 阴茎越来越小是什么原因| pp材质是什么| lof什么意思| l1椎体在什么位置| 拔完牙不能吃什么| 武汉有什么好玩的地方| 做梦车丢了有什么预兆| 早晨起床口苦是什么原因| 皮草是什么意思| 5月24日什么星座| 什么好赚钱| 温水煮青蛙是什么意思| 文曲星下凡是什么意思| 什么病不能吃西兰花| 粽子叶是什么植物的叶子| 艾滋病通过什么途径传播| cu是什么| 月经流的是什么血| 马不停蹄是什么生肖| 晚上喝酸奶有什么好处和坏处| 女性支原体感染有什么症状| 打黄体酮针有什么副作用| 死海为什么叫死海| 智齿有什么作用| 判决书什么时候生效| 泌尿外科主要检查什么| 甲状腺看什么门诊| 壮阳吃什么| 牛黄是什么| 胸口中间疼挂什么科| 海带是什么植物| 盆腔积液什么意思| 日斤念什么字| 洗衣机不排水是什么原因| 喝酒喝吐了用什么缓解| 脚没力气是什么原因| 深沉是什么意思| 乾字五行属什么| 离殇是什么意思| 朋友圈为什么发不出去| 囊肿吃什么药| 工匠精神是什么| 手指甲月牙代表什么| 甲状腺结节是什么引起的| 咽喉炎吃什么好| 牙齿经常出血是什么原因| 经辐照是什么意思| 孕妇白细胞高是什么原因| 什么的寒风| 太妃是皇上的什么人| 为什么一躺下就头晕目眩| 什么样的人容易得抑郁症| 吃什么清肺养肺| 25年是什么婚| 小孩脸上长痣是什么原因引起的| 八百里加急是什么意思| 甲亢有什么症状| 十八层地狱分别叫什么| 负罪感什么意思| 酒糟鼻买什么药膏去红| 9.25是什么星座| 浮瓜沉李什么意思| thr是什么氨基酸| 吃什么会放屁| 澳大利亚属于什么洲| 扁导体发炎吃什么药| 幼儿反复发烧是什么原因| 什么时候人流| 糖链抗原是什么意思| 美国人的祖先是什么人| 莲蓬什么季节成熟| 针眼是什么原因引起的| 葛根粉有什么效果| 肚子疼吐了是什么原因| 令瓦念什么| 常喝黑苦荞茶有什么好处| 梦到结婚是什么预兆| 交媾是什么意思| 什么叫肿瘤| 电压高是什么原因造成| 额头窄适合什么发型| 严重失眠挂什么科| 梦见生了个女儿是什么意思| 非那根又叫什么| 吃醋对身体有什么好处| 女人在什么时候最容易怀孕| 新疆有什么民族| 脚心烧是什么原因| 肺结节吃什么药能散结| 粉刺长什么样图片| 执迷不悟是什么生肖| 什么叫湿热| 脑供血不足吃什么药| 肺结节是什么病| 日出东方下一句是什么| 10月25号是什么星座| 防腐剂是什么| tl是什么意思| 羽毛球拍u是什么意思| 养成系是什么意思| 什么症状吃柏子养心丸| fk是什么意思| 和什么细什么的成语| 甲状腺肿物是什么意思| 知了的学名叫什么| 极差是什么| 心梗挂什么科| 输卵管发炎有什么症状表现| 奶头痛是什么原因| 吃芒果有什么好处| 八段锦什么时候练最好| 巨门是什么意思| 胆囊炎是什么病| 喝什么降尿酸| 霍山黄芽属于什么茶| 药敏试验是什么意思| 蚂蚁上树什么意思| 血脂粘稠有什么症状| dos是什么| 乙醇对人体有什么伤害| 鲥鱼是什么鱼| 喝酒胃出血吃什么药| 牛奶什么时候喝最好| 下作是什么意思| 早熟是什么意思| 得了阴虱用什么药能除根| n2是什么| 1月28日什么星座| 始于初见止于终老是什么意思| 大宝贝是什么意思| 一个九一个鸟念什么| 什么是便血| 清热利湿是什么意思| 内膜薄吃什么增长最快| 低血糖什么不能吃| 打飞机是什么意思| 湿疹长什么样子| 双签是什么意思| 梦到怀孕生孩子是什么意思| 脱发是什么病| 低血压是什么原因| 媞是什么意思| 尿素高不能吃什么| acr是什么意思| 女性失眠吃什么药最好| 莞尔一笑什么意思| 孕妇喝咖啡有什么危害| 什么是白矮星| 非诚勿扰什么意思| 月经病是什么意思啊| 温州什么最出名| 汗颜是什么意思| 扬长而去是什么意思| 十滴水泡脚有什么好处| 口臭吃什么药最有效| 拔智齿第二天可以吃什么| 游坦之练的什么武功| 两融是什么意思| 88.88红包代表什么意思| 人为什么要喝酒| 分贝是什么意思| 长命的动物是什么生肖| 一什么树干| 盐酸左氧氟沙星片治什么病| 咖啡soe是什么意思| 18k是什么意思| 冠字五行属什么| 去香港需要办理什么证件| 女性性高潮是什么感觉| 煮花生放什么调料好吃| 出汗有什么好处| 脸浮肿是什么原因| 低血糖是什么原因| 鱼用什么游泳| 小孩咳嗽喝什么药| 新生儿黄疸高有什么风险| 葡萄又什么又什么| 为什么奢侈品都是pvc| 尿黄起泡是什么原因| 啤酒花是什么| 来来来喝完这杯还有三杯是什么歌| 补钾用什么药| 714什么星座| 胆囊肿是什么病严重吗| 刺身是什么| 世界上最贵的东西是什么| 什么人不适合戴翡翠| 21.75是什么意思| 蜂蜜的主要成分是什么| 胆管结石用什么药能把它除掉| 养阴生津是什么意思| 弱碱性水是什么水| 尿沉渣检查什么| 头发定型用什么好| 结婚10周年是什么婚| c3c4补体是什么意思| 曹操为什么要杀华佗| 尿素氮高吃什么药| ol是什么意思| 原则上是什么意思| 百度
发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
打印
[STM32]

八宝山殡仪馆开放VR体验“生死跨越”

[复制链接]
5206|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

[i=s] 本帖最后由 begseeder 于 2025-5-27 08:19 编辑 [/i]

[i=s] 本帖最后由 begseeder 于 2025-5-22 15:44 编辑 [/i]

#申请原创# @21小跑堂

前言

在研发阶段需要更新程序时,直接使用调试器进行烧录即可,但是如果想要对一个封装好的产品进行程序升级时,一般都是没有引出烧录接口的,此时只有拆机一途。如果只有一两个需要更新,那么拆也就拆了,但有上百个呢,此时非bootloader不可。本文主旨是在小容量的32单片机上进行开发,比如stm32f103芯片,这样的主控芯片资源比较紧张,但是如果有条件上bootloader,建议一定要做的,功能不需要复杂,只需实现最基本的全量更新就行。

在这基础上,我更想实现的是一插件式的工具,包括boot流程、交互协议、数据流以及flash驱动这4个相关的插件,这个插件机制的实现是基于表驱动的方式,因为表就是策略,怎么驱动表就是机制,一般来说机制是固定的,策略是可替换的,这样就达到了插件的效果,同时满足同一机制的事物可以有多个,这样就把整个bootloader细分出几个具有同样机制的流程结构,通过级联的方式联接部分,每个机制的策略可以进行方便的更换,更大的提高了应用的灵活性。当面对多种应用场景时,这样的插件式设计将极大提高代码的复用能力,方便替换、新增以及删除,从而改变程序的行为,并且更为重要的是这种改变没有影响到程序的主体框架。比如一个项目中通过CAN总线进行更新,而另一个项目使用485,这时候只需要更换相应的插件就可实现功能的调整。

技术要点

表驱动设计

网上关于表驱动的介绍有很多,并且实现起来也是各式各样,但是总体而言使用这种方法的核心还是抽象与辨识,这要求开发者必须对要实现的应用有一个全面的了解和认知,这样才能从无序中识别出有序的主体部分,而这部分必然成为程序的框架部分,是静态的,固定的,而其余的依托框架存在的零散部分,属于易变的,可替换的。要做到以上辨识和分离的操作,还真的需要不少功底。其实只要涉及到某种范式的使用,都或多或少的有一些瓶颈,我现在也正在学习这种方式,它的难点在于怎么构造这些表,表的结构该怎么设计,可以认为这个表就是一个结构化的解空间,所谓解空间就是对表进行输入的所有响应。所以一个好的表,必然有一个可以覆盖可能输入情况的所有响应,当然除了解空间的确定外,求解的方法,或者说把输入变换到解空间的方法也是不可缺少的,这些都极大考验开发者的能力和水平。相比于我之前写代码的思路,那都是直截了当的,平心而论,从任一个局部来看这种代码,完全是心中所思所想的一一映射,也就是完全把我们的思考过程转化为代码,这样的代码应该是很符合逻辑的不是吗。但是从局部上升的系统的整体,就会发现局部的片面和松散,这样的代码的每个部分都会符合原本的预期,但也仅此而已,一把钥匙只能配一把锁,但唯有铁丝才不挑锁。

表驱动与状态机结合

上面只介绍了表驱动的概念,现在介绍一种应用场景,那就是实现状态机。我们都知道在程序中的状态机就是通过定义一组有限状态和其转移的规则来控制系统行为的模型,一般有4个组成部分:状态事件转换动作,那么要想基于表驱动实现状态机,就需要把这4个部分添加到表结构中,如下所示:

typedef struct{
  State CurState;
  Event CurEvent;
  void (*Action)(void);
  State NextState;
}TransitionItem_t;

这样基本的结构就是表中每一项的内容,而一个表中有多少项,就看开发者自己怎么理解问题和解析问题的了。举个例子,现在有一个按键和一个LED灯,要实现单击时,LED灯常亮,在常亮过程中长按时,LED闪烁,在闪烁过程中单击,LED常灭。在遇到这种应用时,不要直接开始写代码,无论多简单,先分析一下,画个状态图:

pic1.png

然后根据状态图,定义状态表:

pic2.png

上面这个状态表就可以完全设计到我们的代码中,如下所示:

TransitionItem_t FsmTable[] = {
   {LED_OFF,KEY_CLICK,LightUp,LED_ON},
   {LED_ON,KEY_LONG,Toggle,LED_TOGGLE},
   {LED_TOGGLE,KEY_CLICK,LightOff,LED_OFF}
}

那么现在表结构有了,或者说解空间有了,怎么求解呢,最简单的就是查表,对比两个元素,当前状态以及发生的事件,在这个例子中就是 (LED_OFF,KEY_CLICK)(LED_ON,KEY_LONG)以及 (LED_TOGGLE,KEY_CLICK),(有没有点像多元离散函数,可以结合着加深对比理解), 只要符合这三个定义好的组合之一,那么必然发生动作,如果不属于三个之一,那么什么反应都没有,也不影响现有状态,驱动代码如下所示:

void RunFsm(TransitionItem_t *fsm,int fsm_size){
  /* 判断是否为空指针 */
  if(fsm){
    for(int i = 0;i<fsm_size;i++){
    /* 条件对比 */
    if(fsm[i].CurState == GlobalState && fsm[i].CurEvent == GlobalEvent){
      /* 执行动作 */
      fsm[i].Action();
      /* 执行状态转移 */
      GlobalState = fsm[i].NextState;
        }
    }
  }
}

Bootloader简介

Bootloader是嵌入式系统中一段特殊的引导程序,用于固件加载或更新等功能,在芯片发生复位时首先会进入Bootloader进行引导,决定是否更新或跳转应用程序。在这里使用的是小容量的单片机,所以Bootloader程序尽量精简,这样可以给应用程序让出更多的空间,一般简单的应用下我们会进行分区,分为Bootloader区和App区,如下图所示:

draft.png

可以看到大致上最简单的分区方案就是这样,其中app有效标志放在了Bootloader区末尾,这个标志就是Bootloader进行引导的条件。对于固件升级来说,实际上是通过Bootloader对App区域进行擦写动作,数据来源于外部,内容就是App工程编译出来的bin文件,如果在没有其他额外Flash的支持下,进行升级的风险还是蛮大的,所以需要有一定的安全校验措施。 总的来说,要实现一个基本的Bootloader,流程还是比较简单的,大致如下图所示:

draft_bootflow.png

可以看到,上图的流程中显示了两种复位情况,即上电复位和软件复位,其中软件复位是从应用程序中进行的复位,是专门为固件升级设定的,表示存在更新请求,当然可以附加更多的信息,同时软件复位有一个特点,就是不会改变RAM的数据;不管哪种复位,只要存在更新请求,就会第一时间把App有效标志位清除,这样如果在更新过程中发生任何问题,标志位都是无效的,避免发生错误跳转的问题,只有完成全部的更新流程,包括校验等操作后才会标记有效;同时,如果是正常的上电复位,或者更新失败后,都会停留在Bootloader中,等待超时,判断有效标志,决定是否跳转。

综合实践

现在经过上面的介绍,已经大致对涉及的技术要求有了一定的了解,那么接下来就是对插件式的Bootloader进行设计了。

有一个很核心的概念,就是策略与机制分离,对应到我们的设计中,机制就是Bootloader功能,策略就是实现功能的方式。乍一看,策略还比较好理解,机制要怎么理解呢,可以认为是一种容器、框架或模型,甚至就是一套固化规则,策略千千万,但都必须映射到机制的规则域中才能在机制的世界中存在。

当然每个人有每个人的理解,不同的理解也产生不同的代码,现在介绍一下我对Bootloader机制的理解,先上图大致描述一下:

draft_mechanism.png

从上图中可以看到有4个主要的元素,也可以看作节点,包括通信流程、协议交互流程、Boot流程以及Flash驱动,可以认为要实现一个最基础的Bootloader必须要有这4个元素,而这些也是机制所固有的成分,所有策略都是作用在这4个元素之上的,其中为什么Flash与其他3个不一样,是因为在实现中,Flash读写都是一次性的,并且由Boot流程直接控制;而流程的控制就可以使用状态机来实现,本质上来说,状态机是一个独立的机制,而现在通过表驱动方法,增强了这一机制的通用性和灵活性,只需要更换状态表,就可以实现策略的改变。

那么现在局部元素都介绍完了,该怎么在各元素之间建立关联呢,可以有消息、订阅发布等方式,但是,这些都大材小用了,通过梳理,发现完全可以使用链式通知的方式来建立关系,在上图中可以看到流向中标识的1.1、1.2等字样,用前后级来描述的话就是,由前级主动通知后级该做什么,否则后级什么都不做,当然这个前后级可以不是固定的前后级,是相对的前后级,可以想象一下环形链表,以上就是我所设计的机制内容,它的约束,或者说应用条件可以归纳以下几点:

  • 单向流控,链式触发
  • 机制结构固定,需识别或转化策略以满足结构要求

下面给出实现机制的主要代码: 状态机的结构:

/* 状态表结构定义 */
typedef struct
{
/* 状态表事件项 */
? ? uint32_t Event;
/* 状态表状态项 */
? ? uint32_t State;
/* 状态表动作项 */
? ? int (*Action)(uint32_t *event, void *arg);
/* 状态表转移项 */
? ? uint32_t NextState;

} TransitionItem_t;

/* 基类状态机定义 */
typedef struct
{
/* 状态表 */
? ? TransitionItem_t *FsmTable;
/* 当前状态 */
? ? uint32_t CurState;
/* 当前发生事件 */
? ? uint32_t CurEvent;
/* 状态表尺寸 */
? ? uint32_t FsmSize;

} BaseFsm_t;

定义了状态表的结构,以及基类状态机的结构,基类状态机提供给4个元素使用,其次是运行状态机的驱动代码:

int RunFsm(BaseFsm_t *fsm, void *arg)
{
  /* 防止空指针错误 */
? ? if (fsm->FsmTable != NULL)
? ? {
? ? ? ? for (uint32_t i = 0; i < fsm->FsmSize; i++)
? ? ? ? {
? ? ? ? /* (状态,事件)元组对比 */
? ? ? ? ? ? if (fsm->CurState == fsm->FsmTable[i].State && fsm->CurEvent == fsm->FsmTable[i].Event)
? ? ? ? ? ? {
        /* 防止空指针错误 */
? ? ? ? ? ? ? ? if (fsm->FsmTable[i].Action != NULL)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? /* 执行动作 */
? ? ? ? ? ? ? ? ? ? fsm->FsmTable[i].Action(&fsm->CurEvent, arg);
? ? ? ? ? ? ? ? }
        /* 状态转移 */
? ? ? ? ? ? ? ? fsm->CurState = fsm->FsmTable[i].NextState;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? }

? ? }
? ? else
? ? {
? ? ? ? return -1;
? ? }
? ? return 0;

}

接下来是通信流程的结构设计:

/* 通信流程的结构设计 */
typedef struct
{
? ? struct
? ? {
    /* 流程控制状态表 */
? ? ? ? BaseFsm_t *Fsm;
? ? ? ? /* 驱动器由外部提供 主要是每种通信驱动方式差异比较大,做不到通用 */
? ? ? ? void *Driver;
? ? ? ? /* 用于通知流程路径中下一节点,适用于单一路径 */
? ? ? ? void *Linkto;
? ? } PrivateArea;

? ? /* 初始化 传入状态机和驱动器 都由用户根据框架自定义 */
? ? void (*Init)(BaseFsm_t *fsm, void *driver, void *link);
    /* 获取状态机 */
? ? BaseFsm_t *(*GetFsm)(void);
    /* 获取通信驱动器 */
? ? void *(*GetDriver)(void);
    /* 获取下一节点 */
? ? void *(*GetLink)(void);

} DataStreamHandle_t;

通信节点可以认为是整个机制的触发节点,因为所有的事件流都可以由通信来引导,包括无通信造成的超时事件流;继承了上面的基类状态表,扩展了通信必要的驱动器,还有一些方法定义。

其次是交互协议流程的结构定义:

typedef struct
{
? ? struct
? ? {
    /* 流程控制状态表 */
? ? ? ? BaseFsm_t *Fsm;
? ? ? ? /* 用于通知流程路径中下一节点,适用于单一路径 */
? ? ? ? void *Linkto;
? ? ? ? /* 协议体 - 封装与解析协议中的数据或协议特征 */
? ? ? ? void *ProtoBody;
? ? } PrivateArea;
    /* 初始化 传入状态机和协议体 都由用户根据框架自定义 */
? ? void (*Init)(BaseFsm_t *fsm, void *link, void *data_body);
    /* 获取状态机 */
? ? BaseFsm_t *(*GetFsm)(void);
    /* 获取协议体 */
? ? void *(*GetProtoBody)(void);
    /* 获取下一节点 */
? ? void *(*GetLink)(void);

} ProtocalHandle_t;

同样的,继承了基类的状态表,同时扩展了一个协议体属性,这个属性的结构是自定义的,体现协议的通式,用于封装和解析数据使用。

最后就是Boot流程的结构定义:

typedef struct
{
? ? struct
? ? {
    ?/* 流程控制状态表 */
? ? ? ? BaseFsm_t *Fsm;
    /* flash驱动器 */
? ? ? ? void *FlashDriver;
    /* 用于通知流程路径中下一节点,适用于单一路径 */
? ? ? ? void *Linkto;
? ? } PrivateArea;

? ? void (*Init)(BaseFsm_t *fsm, void *link, void *driver);
? ? BaseFsm_t *(*GetFsm)(void);
? ? void *(*GetFlashDriver)(void);
? ? void *(*GetLink)(void);

} BootloaderHandle_t;

基本和上面的结构大同小异,至此整个机制的结构就定义完成了,接下来就是框架的搭建了。

首先,一般的通信接收功能都会在中断中进行,因为这样实时性最高,能及时的处理数据信息,所以我们的通信流程的触发可以结合接收中断来进行,以CAN接收中断举例:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)

{
  uint8_t i = 0;

? if (hcan->Instance == CAN1)
? {

? ? HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CANxRxHeader, CANRecvBuf);
? ? if (CANxRxHeader.DLC > 0)
? ? {
? ?  /* 作为触发条件 */
? ? ? CanMsgRecved = 1;
? ? }
? }
}

这样,通过状态机就可以控制通信的流程,同时传入通信用的驱动器,因为我们设计的时候都是采用 void *这种抽象指针,所以灵活程度很高。然后就是协议交互流程和Boot流程的使用,这些都可以放到以某一时基为周期运行的代码块中:

while (1)
?{
    ?
    /* 1ms时基 */
? ? if (TIMEBASE_HOOK(TimeBaseScope, OPT_TIMEBASE_1MS))
? ? {

? ? ? TIMEBASE_DROP(TimeBaseScope, OPT_TIMEBASE_1MS);
? ? ? /* 单路径流程,总是通过前级主动通知后级该做什么(自定义事件),同时后级继承(传入)前级的遗产(输出内容) */
    /* 放入状态机在直接接收数据的地方 */
? ? ? RunFsm(DataStreamHandle.GetFsm(), DataStreamHandle.GetDriver());
? ? ? 
? ? ? /* 协议状态机,传入数据收发接口的原生数据形式 比如can报文形式,串口形式 */
? ? ? RunFsm(ProtocolHandle.GetFsm(), DataStreamHandle.GetDriver());

? ? ? /* boot流程状态机 */
? ? ? RunFsm(BootloaderHandle.GetFsm(), ProtocolHandle.GetDataBody());
? ? }
}

因为通过链式传递消息通知,在单路径流程中,总是可以通过前级主动通知后级该做什么(自定义事件),同时后级继承(传入)前级的遗产(输出内容)。以上整个机制的框架的完成了,接下来主要就是策略的定义了,其实就是识别出自定义策略中的事件、状态以及转移,并把这些以状态表的方式呈现出来。 首先给出通信状态图:

draft_data_state.png

通信流程中定义的事件和状态宏定义,以及状态表定义:

/* 通信事件流自定义宏 >>>>>> */

/* 无事件 */
#define E_DATA_NONE 0
/* 有数据进入 */
#define E_DATA_IN (CAST_U32(0X01) << 0)
/* 数据超时 */
#define E_DATA_TIMEOUT (CAST_U32(0X01) << 1)
/* 数据接收结束 */
#define E_DATA_END (CAST_U32(0X01) << 2)
/* 数据请求协议处理 因为只有自己知道自己什么情况,当然得主动通知 */
#define E_DATA_LINK_PTO (CAST_U32(0X01) << 3)
/* 等待协议给过来消息 */
#define E_DATA_RET_PTO (CAST_U32(0X01) << 4)

/* 自定义状态宏 */

/* 空状态 */
#define S_DATA_NONE 0
/* 接收状态 */
#define S_DATA_READING (CAST_U32(0X01) << 0)
/* 数据接收完成状态 */
#define S_DATA_READ_END (CAST_U32(0X01) << 1)
/* 等待协议反馈状态 */
#define S_DATA_WAIT_PTO (CAST_U32(0X01) << 2)
/* 通信事件流自定义宏 <<<<<< */

TransitionItem_t DataStreamTable[] = {

? ? {E_DATA_IN, S_DATA_NONE, BufData, S_DATA_READING},
? ? {E_DATA_TIMEOUT, S_DATA_READING, ResetData, S_DATA_NONE},
? ? {E_DATA_END, S_DATA_READING, LinkProto, S_DATA_READ_END},
? ? {E_DATA_LINK_PTO, S_DATA_READ_END, NULL, S_DATA_WAIT_PTO},
? ? {E_DATA_RET_PTO, S_DATA_WAIT_PTO, SendResp, S_DATA_NONE}

};

其次是交互协议状态图:

draft_proto_state.png

对应的事件、状态以及状态表定义如下:

/* 协议收到通信的通知 */

#define E_PROTO_RECV_DATA (CAST_U32(0X01) << 0)

/* 协议解析完成 */

#define E_PROTO_PARSE_OK (CAST_U32(0X01) << 1)

/* Boot反馈 */

#define E_PROTO_RET_BOOT (CAST_U32(0X01) << 2)

/* 空状态 */
#define S_PROTO_NONE 0
/* 解析状态 */
#define S_PROTO_PARSE (CAST_U32(0X01) << 1)
/* 等待Boot反馈状态 */
#define S_PROTO_WAIT_BOOT (CAST_U32(0X01) << 2)
/* 状态表定义 */
TransitionItem_t ProtoTable[] = {

? ? {E_PROTO_RECV_DATA, S_PROTO_NONE, ParseData, S_PROTO_PARSE},
? ? {E_PROTO_PARSE_OK, S_PROTO_PARSE, LinkBoot, S_PROTO_WAIT_BOOT},
? ? {E_PROTO_RET_BOOT, S_PROTO_WAIT_BOOT, RetData, S_PROTO_NONE}

};

最后是Boot流程的状态图:

draft_boot_state.png

对应的事件、状态以及状态表定义如下:

/* Boot收到协议的通知 */
#define E_BOOT_RECV_PROTO (CAST_U32(0X01) << 0)
/* Boot起始命令 */
#define E_BOOT_CMD_START (CAST_U32(0X01) << 1)
/* Boot更新命令 */
#define E_BOOT_CMD_UPDATE (CAST_U32(0X01) << 2)
/* Boot结束命令 */
#define E_BOOT_CMD_END (CAST_U32(0X01) << 3)
/* Boot跳转命令 */
#define E_BOOT_CMD_JUMP (CAST_U32(0X01) << 4)

#define S_BOOT_NONE 0
/* Boot准备状态 */
#define S_BOOT_READY (CAST_U32(0X01) << 1)
/* Boot更新状态 */
#define S_BOOT_UPDATE (CAST_U32(0X01) << 2)
/* Boot结束状态 */
#define S_BOOT_END (CAST_U32(0X01) << 3)

/* 状态表定义 */
TransitionItem_t BootTable[] = {
? ? {E_BOOT_RECV_PROTO | E_BOOT_CMD_START, S_BOOT_NONE ResetBoot, S_BOOT_READY},
? ? {E_BOOT_RECV_PROTO | E_BOOT_CMD_UPDATE, S_BOOT_READY, Update, S_BOOT_UPDATE},
? ? {E_BOOT_RECV_PROTO | E_BOOT_CMD_UPDATE, S_BOOT_UPDATE, Update, S_BOOT_UPDATE},
? ? {E_BOOT_RECV_PROTO | E_BOOT_CMD_END, S_BOOT_UPDATE, Check, S_BOOT_END},
? ? {E_BOOT_RECV_PROTO | E_BOOT_CMD_JUMP, S_BOOT_END, JumpApp, S_BOOT_NONE}

};

可以看到状态表中的事件项都是多种事件的组合,因为我们的事件宏定义是通过移位操作进行的,所以提供了组合事件的能力。

至此,Bootloader的机制和策略都制定完毕了,这里只演示了比较简单的策略功能,但因为机制提供了策略更换的能力,所以实现更复杂的功能只需要制定相应的策略状态表即可,实现这个机制也算是一个尝试,学到了很多,同时呢也接触到更高级的内容,还有待进一步深入了解,真真是无知啊,越来越感觉到知识储备不够,设计出来的东西其实价值不是很大,也就自娱自乐而已。

打赏榜单

21小跑堂 打赏了 100.00 元 2025-08-04
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论
21小跑堂 2025-5-29 16:18 回复TA
同样是单片机的bootloader,该作者使用花式表驱动操作,打造了一个插件机制,使用一根铁丝,撬开百家锁。文中技术要点阐述专业,实现步骤条理清晰,看得出来是一位优秀的工程师。 

相关帖子

沙发
flyingstar01| | 2025-5-29 17:47 | 只看该作者
学习了。
板凳
dffzh| | 2025-5-30 09:14 | 只看该作者
用得上,感谢分享!
地板
zhjb1| | 2025-5-31 10:49 | 只看该作者
学习了
5
小迷糊仙| | 2025-6-4 12:23 | 只看该作者
学习了  感谢分享
6
爱情海玩偶| | 2025-6-6 10:03 | 只看该作者
表驱动真的是一个很神奇的方式。
我之前多级菜单界面也是使用表驱动。这是我第一次接触表驱动。是重构老的代码。老的代码界面操作和跳转全在main函数里面。导致main函数几千行。各种全局变量的标志位。
第二次表驱动是前段时间搞modbus协议。处理协议的时候太多if else了。后来被推荐使用表驱动来写
7
卖芯片的小张| | 2025-6-6 10:22 | 只看该作者
有需要芯片元器件一站式配单的可以找我
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

30

帖子

0

粉丝
看近视眼挂什么科 阴道里面有个肉球是什么 平台期是什么意思 bb霜和粉底液有什么区别 弹性是什么意思
争奇斗艳什么意思 吃什么补白蛋白最快最好 减脂吃什么主食 手术后吃什么 胃底腺息肉什么意思
乙肝有抗体是什么意思 白虎关是什么意思 皮炎用什么药膏最有效 硫酸铜什么颜色 咖啡有什么作用和功效
手麻脚麻是什么病 左室舒张功能减低什么意思 曲率是什么意思 九月一日什么节日 什么样的人能镇住凶宅
醋酸氯已定是什么药hcv9jop7ns5r.cn 刚怀孕肚子有什么变化hcv9jop3ns3r.cn 一个小时尿一次是什么原因hcv8jop1ns3r.cn 蓝瘦香菇是什么意思hcv9jop2ns6r.cn 盆腔静脉石是什么意思hanqikai.com
莆田荔枝什么时候成熟hcv8jop4ns9r.cn 膨鱼鳃用什么搭配煲汤hcv8jop2ns8r.cn 梦到吵架是什么意思hcv8jop2ns5r.cn 唵是什么意思hcv8jop6ns0r.cn 看诊是什么意思hcv8jop6ns4r.cn
蜂蜜和柠檬一起喝有什么作用hcv8jop8ns1r.cn 冰瓷棉是什么面料creativexi.com 吃什么可以降低血糖hcv9jop3ns2r.cn 栀子花黄叶是什么原因hcv7jop6ns3r.cn 纪梵希属于什么档次hcv7jop6ns8r.cn
一览无余是什么意思chuanglingweilai.com 什么人容易长智齿hcv9jop1ns4r.cn 肺气肿有什么症状hcv7jop9ns4r.cn no.是什么意思hcv8jop1ns7r.cn apc是什么牌子0735v.com
百度