一、背景
编程学习的目的是为了日后在工作中可以有相应的能力,应对现实需求。在学习阶段编程的目的和工作不同,工作中编程目的是为了解决问题,而学习阶段编程的目的是掌握编程技能,培养良好的编程习惯,解决问题的需求只是载体,并非目的,和学数学时的应用题性质一样。
为编程学习构建程序框架,目的就在于此。
程序框架及配套的PC机UI操控界面已经完成,现在就尝试一下基于程序框架编写第一个应用:将前面激活硬件的三个程序纳入到框架中,作为测试程序的三个功能。
二、需求
这个应用很简单,需求为:
1、将三个测试程序的功能整合到框架程序中,可以分别执行。
2、测试功能选择通过串口命令、借助于PC界面实现。
三、尝试
3.1 构思
在程序框架设计时,预设了两个应用任务:主任务、其它任务。
主任务设计是作为应用管理的,其它任务设计是为了示意,编写具体应用时,取代之。
将第一个应用作为硬件测试应用,增加的任务命名为:测试任务,取代“其它任务”。
将前面三个激活硬件的测试程序作为测试任务的三个功能,串口接收任务收到操作命令后转发给主任务,主任务将命令转发给测试任务。同时通过数据发送任务应答,告知命令收到。
测试任务根据收到的操作命令执行,并定时反馈测试工作状态。
主任务支持读状态命令,可以返回测试任务当前的工作状态。
要修改的是相关变量的定义(用Test取代Other),以及增加的变量定义。基础任务(串口命令接收、串口数据发送、调试信息输出、看护任务)基本不变,只是涉及到Other的改为Test。
主要修改的是主任务任务,需要编写的是测试任务。
3.2 实施
3.2.1 定义操作命令
将三个测试功能定义为:
1)测试A(TestA):执行原来的激活程序1,因为可以用命令操作,增加了三个操作参数:左侧电机PWM、右侧电机PWM、运行时间。原来这些是写在程序中的常量,原来程序是两侧分别正、反转,小车原地打转,现在可以通过设置左右侧PWM,让小车原地打转或前进、后退。
2)测试B(TestA):执行原来的激活程序2,因为可以用命令操作,增加了三个操作参数:左侧电机PWM、右侧电机PWM、运行脉冲数。这个测试是先左侧运转设定脉冲数,再右侧运转设定脉冲数。
3)测试C(TestC):执行原来的激活程序3,同样支持三个参数:左侧电机PWM、右侧电机PWM、运行脉冲数。和测试B区别在于 — 两侧同时运转,一侧运转到脉冲数则停止,两侧运转的脉冲数一样。同时在运转时采集两侧的电机供电电压和电流,通过调试信息通道输出,目的是测试一下模拟量采集功能的Arduino实现。
具体操作确定后,定义操作命令(读、写内存命令不变):
A)命令3:读工作状态命令(Key = 3,Len = 0)
0x03 0x00 0x00
应答内容:
0x03 0x04 0x00 左侧电机PWM(1字节) 右侧电机PWM(1字节)剩余工作时间或脉冲数(2字节)
PWM 为 1字节有符号数,范围 -100 ~ 100,正数前进,负数倒退,0 - 停止
B)命令4:测试动作A(Key = 4,Len = 4,Val:1字节左侧 PWM,1字节右侧 PWM,2字节时间)
0x04 0x04 0x00 左侧电机PWM(1字节) 右侧电机PWM(1字节)2字节运行时间
PWM 为 1字节有符号数,范围 -100 ~ 100,正数前进,负数倒退,0 - 停止
运行时间单位:秒
应答内容:
0x04 0x04 0x00 左侧电机PWM(1字节) 右侧电机PWM(1字节) 剩余工作时间(2字节)
C) 命令5:测试动作B(Key = 5,Len = 4,Val:1字节左侧 PWM,1字节右侧 PWM,2字节脉冲数)
0x05 0x04 0x00 左侧电机PWM(1字节) 右侧电机PWM(1字节)2字节运行脉冲数
PWM 为 1字节有符号数,范围 -100 ~ 100,正数前进,负数倒退,0 - 停止
脉冲数单位:个
应答内容:
0x05 0x04 0x00 左侧电机PWM(1字节) 右侧电机PWM(1字节)剩余脉冲数(2字节)
D) 命令6:测试动作C(Key = 6,Len = 4,Val:1字节左侧 PWM,1字节右侧 PWM,2字节脉冲数)
0x06 0x04 0x00 左侧电机PWM(1字节) 右侧电机PWM(1字节)2字节运行脉冲数
PWM 为 1字节有符号数,范围 -100 ~ 100,正数前进,负数倒退,0 - 停止
脉冲数单位:个
应答内容:
0x04 0x04 0x00 左侧电机PWM(1字节) 右侧电机PWM(1字节)剩余脉冲数(2字节)
此测试只是增加了运转时工作电压和电流的输出,暂定用 Dump 通道。
提示:上述命令的返回内容一样,针对常见的设备操作命令,一般均可以将关注的工作状态作为应答内容,用统一的帧格式处理,虽说可能会冗余一点,但对应接收端的数据解析编程而言,会方便很多,也减少了出错概率。
3.2.2 定义相关数据
重新定义触发事件名(将Other 改为 Test):
定义操作命令:
定义主任务和测试任务之间的交互数据:
定义相关硬件资源(放在BoardConfig.h文件中):
3.2.3 修改主任务的命令解析
对主任务的命令解析执行部分做相应修改,支持新定义的命令:
其中,读工作状态处理函数完成将测试任务返回的工作状态数据填入发送数据区:
转发给测试任务操作命令的函数 SetOpData 完成从串口命令数据中提取命令及参数,填入主任务与测试任务的交互数据结构中:
虽说三个操作处理一样,但此处仍使用了Switch结构,主要是为了便于扩充。
3.2.4 构建测试任务
测试任务将原来三个测试程序均包含在内,故首先要完成硬件初始化,这部分就用到了程序框架设计中的初始化同步机制,将测试相关的硬件初始化放置在测试任务的初始化部分:
测试任务的核心操作是控制电机,为便于处理,增加程序可读性和可维护性,构建电机操作函数(完整函数见程序):
应用任务基本处理模式为:
1)接收主任务消息,解析执行。
2)定时唤醒,处理自身正在执行的操作。
此处,对主任务的响应如下:
根据命令启动相应的操作,如测试A(完整处理见附件):
注意:前面设计框架时提过 —— 任务内不能有长时间的“逗留”,应尽快执行完操作后进入阻塞等待状态。但测试A的操作是电机运行一段时间后停止,该如何处理?
通常处理方式有两种:
A)启用硬件定时器,时间到后执行停止电机操作,完成当前命令。
B)定时唤醒程序,执行软计时,时间到后停止电机、完成当前命令。
因本篇目的是将原来的激活程序纳入到程序框架中,看看是否可以方便的实现基于程序框架的编程模式,并非以程序实现功能为主题,故不希望加入太多额外的内容,尽量使用 RTOS 所提供的资源。所以采用第二种方式:软计时。
每个OS都有一个Tick,即操作系统周期性处理的最小时间,Free RTOS同样有,并且提供了一个钩子函数,以便应用编程可以方便的加入自己的内容。
在程序框架设计中,串口接收任务及看护任务均利用了Tick,测试任务也用Tick计时。首先在Tick的钩子函数中增加唤醒测试任务的消息:
在测试任务中响应此消息,执行软件计时操作,完成测试命令A:
这种方式可以运用到应用中的很多场景,只要是计时要求不是很高(tick通常是ms级别,本程序框架为5ms),都可以应对,包括一些可以查询方式处理的操作,如串口接收任务中的命令接收,因为有串口硬件接收缓冲,不会丢失输出,程序只是在应用层面处理,无很强的时间要求。
周期性将测试状态发送给主任务也是基于Tick的软计时处理:
测试任务处理流程已完整实现,主任务的如何响应测试任务发来的数据呢?
3.2.5 主任务接收测试任务数据
测试任务在执行操作时,定时将当前工作状态发送给主任务,以便主任务在相应读状态命令时,可以及时返回当前状态。
因为定义了相应的任务间传输数据结构,故处理很简单:
至此,第一个应用程序全部完成,上述实现顺序就是设计构思的流程。学习编程者很多会感觉开始时无处下手,其实并不难,只要梳理好以下几点即可:
1)需求:到底要实现什么?
2)现实世界的实现过程:撇开程序,假设是你自己在做,应该一步步怎么做?
3)将自己做的过程映射到程序步骤中。
4)梳理约束条件,用已有资源化解,如上述计时问题。
3.3 调试
从上面的程序截图中可以看到,程序中有许多 Println 语句,那些就是调试信息输出,因为基于RTOS编程是多任务模式,每个任务相当于一个人,各自在做自己的事情,通过交互实现协调,共同完成一个任务,通过合理的设置信息输出,就可以方便的观察到程序中一个命令的执行流程:
串口接收->帧检测->命令转发->命令接收->解析->派发->执行->返回
为了清晰的看出是哪个任务发送的信息,在每条信息开始都标注了ID:
CR:串口接收任务
TX:数据发送任务
WD:看护任务
MT:主任务
TT:测试任务
上电后的输出信息:
读内存命令的执行信息输出:
测试命令A的执行信息输出:
从上述输出信息可以清晰地看出程序的执行流程。
在调试程序过程中,可以根据需要增减相应的输出,从而观察自己程序的执行是否符合设计,查找出现问题的原因。
3.4 PC操作界面
作为辅助手段的 PC 操作界面,在每次增加功能时都要对应编写,似乎觉得有点麻烦,但如果考虑到做此事的目的就是学习编程,则这个过程所耗费的时间远比找一个可以凑合用的软件、摸索使用方法所耗费的时间值!
PC 测试程序的框架设计上考虑了便于扩展,此处只需增加一个页面的处理即可,因为主题是单片机编程,对PC程序有兴趣可以研究代码。
测试任务PC操作界面:
四、结语
这个程序只是一个基于程序框架编程的示例,重点是演示如何在框架中添加相应的具体任务?以及如何实现操作?程序的处理流程是怎样的?
其实编程并无唯一的正确方式,只要遵循基本的要求(可靠性、可读性、可扩展性)即可,关键是要自己培养一个合适的习惯,一套处理问题的方式。
此程序框架只是众多可选方式的一种。
后面从趣味性和实用性角度考虑,构思一些有趣的应用,作为素材,尝试在程序框架下编写相应的解决方案,从而为编程学习者提供应对不同需求的参考例程。
————————————
文中程序下载:
链接:https://pan.baidu.com/s/1V8TVDMe9WwCuMTEZJLW_EQ
提取码:trgy