对于单片机非OS程序来说,好的架构必须具备如下特点:代码规范优雅,结构清晰,各模块之间低耦合。个人根据多年工作经历,总结如下:编写代码前应进行结构设计,C语言是面向过程的语言,所以一般系统结构分为三层:驱动层,功能模块层,任务调用层。为了降低耦合性,函数调用规则尽可能做到上层调用下层。

驱动层
非OS驱动层一般由硬件抽象层(HAL)和驱动程序组成,是系统中不可或缺的重要部分。它的作用是为上层程序提供外部设备的操作接口,并且实现设备的驱动程序。上层程序可以不管操作的设备内部实现,只需要调用驱动的接口即可。
其中HAL只是对硬件的一个抽象,对一组API进行定义,却不提供具体的实现。HAL设计的一些要素是:
- 与硬件的密切相关性
- 与上层任务的无关性
- 接口的功能包括硬件或者系统所需硬件支持的所有功能
- 接口简单明了
例如我们想读取i2c设备上某个寄存器指定位的值,我们可以如下封装。
uint8_t RegisterGetField(uint8_t ChipAddr,
uint8_t Address,
uint8_t StartBit,
uint8_t FieldLong)
{
uint8_t ucBuffer;
i2c_read(ChipAddr, Address, &ucBuffer);
ucBuffer <<= (7 - StartBit);
ucBuffer >>= (8 - FieldLong);
return ucBuffer;
}
功能模块层
对于一个非OS系统,除了项目所需的需求模块,常用的功能模块应该包括:
- 消息模块
- 定时器模块
- 调试模块
消息模块主要用于各个任务之间参数的传递。例如串口接收到的数据包经过分析后,需要显示,保存。。。
void msg_wait_for_event(message_id_t *event,
void *data_ptr,
uint16_t *data_size);
bool_t msg_post_event(message_id_t event,
void *data_ptr,
uint16_t data_size);
定时器模块实现各种任务的轮询。例如键盘扫描,ADC采样。。。
个人喜好的定时器模块的数据结构如下:
typedef struct timer_tag {
void (*timeout_func)(void *parameter); /**< timeout function */
void *parameter; /**< timeout function's parameter */
uint32_t init_tick; /**< timer timeout tick */
uint32_t timeout_tick; /**< timeout_tick = sys_tick + init_tick */
uint8_t flag; /*bit0: TIMER_FLAG_ACTIVATED
bit1: TIMER_FLAG_PERIODIC
bit6: TIMER_FLAG_TIMEOUT
bit7: TIMER_FLAG_USED
*/
} timer_t;
任务调用层
顶层的任务调度是系统运行起来的基本功能集合,来实现整个系统的功能,任务基本分为四个部分:看门狗管理,定时任务,消息处理任务,中断任务。Main函数结构大体如下:
void main(void)
{
/*各个模块的初始化*/
init();
While(1)
{
/*喂狗*/
feed_dog();
/*各类消息汇总处理*/
Handle_message();
/*定时器任务*/
timer_task();
/*其他中断任务*/
others_task();
}
}
注:任务处理程序内部结构最好用状态转移法,这样设计的目的是模拟RTOS系统的多任务模式。
本文介绍了一种适用于单片机非操作系统程序的良好架构设计方案。该方案将系统分为驱动层、功能模块层和任务调用层三个层级,通过模块化设计降低耦合性,提高代码复用性和维护性。文章详细阐述了各层的设计原则和具体实现方法。
4596

被折叠的 条评论
为什么被折叠?



