牛骨文教育服务平台(让学习变的简单)

-基于TI CC254X OSAL的分析

当工具链配置完成后,SourceInsight向你展示一份源码工程,不借助百度和开发文档,能否在一两个小时内理解源码的组成框架和接口,进行快速开发?

上一篇《如何快速理解一个全新的嵌入式操作系统》我们已经分析了如何快速理解OSAL的任务调度和任务间通信(其实OSAL只是酷似多任务操作系统的单任务系统),再理解好OASL的消息产生和处理过程,我们就能够进行快速开发了。

>一、消息的来源

嵌入式系统的消息包括两种,一是系统消息,包括低电、热插拔等,由系统进程去处理;二是用户消息,包括Timer、按键、串口、绘图等消息,由各应用进程进行处理。对于TI CC254x的OSAL,我们理解好Timer、key、UART就已足够。

>二、HAL

OSAL向用户提供HAL硬件抽象层,对CC254x的所支持的硬件模块进行了封装抽象,如timer、key、UART、LED、LCD、flash、ADC等等,并向用户提供硬件模块的操作接口。

CC254x是低功耗蓝牙集成芯片,用户的产品和电路设计一般都会参考官方的典型电路,而OSAL为官方针对典型电路所设计的系统库和接口,用户的代码编程就只需要按照其提供的HAL接口进行调用就可以实现功能,而不需要通过GPIO级别的驱动编程来实现模块的功能。

针对CC254x的编程是应用编程,关注的是HAL接口,可以认为是基于硬件功能的API接口,而且电路的外设的功能也相对固定,如LED、LCD、UART所使用的pin脚都是相对固定的,因此调用简单的HAL层接口即可实现功能;而驱动编程则是针对SOC片上资源来进行开发,需要根据SOC的datasheet明确的物理地址资源进行访问和控制,并向用户提供API接口。从两者的区别可以理解HAL硬件抽象层的含义。当然,透传理解SOC datasheet也有助于HAL接口编程。

>三、Timer

对于Timer定时器接口,一般是设置定时器、编写定时器时间到达时的回调函数。而定时器模块的初始化函数一般由系统初始化完成。

OSAL对于Timer的HAL层代码对用户并不透明,我们可以理解Timer的HAL层是设置Timer模块的硬件相关操作,并且实现了Timer中断时的服务处理过程。OSAL的Timer的封装接口实际上是在HAL层的基础进行再次封装。其主要提供的接口如下:

uint8osal_start_timerEx( uint8 taskID, uint16 event_id, uint32 timeout_value )

taskID标识哪个目标任务来处理这个定时到达消息,即当定时完成时,定时器中断服务器函数会往这个目标任务的taskEvents[taskID]写入event_id这个消息;event_id可以由用户自定义;timeout_value是定时时间,1毫秒为单位。

可见这个定时接口并没有设置定时完成时的回调函数,而是在定时完成时向这个目标任务发送一个事件。而在目标任务的执行过程中要检测这个事件并执行相应的处理。

>四、UART

       UARTHAL层代码对用户是透明的,对于用户编程来说,最重要的就是串口的初始化(波特率)、串口输入、串口输出。

1.串口初始化

voidNPI_InitTransport( npiCBack_t npiCBack )

串口的初始化并没有带taskId这个参数,可见其是一个全局的系统级的接口。串口的使用一般是使用中断串口输入,非中断直接串口输出。该函数里面设置波特率是115200.

npiCBack是串口HAL提供给用户的一个回调函数,即在串口中断时会调用该回调函数。而串口中断有以下几种事件:

一般我们都会在该回调函数中实现串口接收,如实现串口透传模式。回调接口声明如下:

typedefvoid (*npiCBack_t) ( uint8 port, uint8 event )

       port是UART0或者UART1,而event即是串口中断事件。

2.串口输入

uint16NPI_ReadTransport( uint8 *buf, uint16 len )

3.串口输出

uint16NPI_WriteTransport( uint8 *buf, uint16 len )

从这些接口来看其前缀是NPI,真实的意义是Network Processor Interface (NPI),表示所谓的网络传输层。其实只是更高一层的数据输入输出罢了。NPI的底层可以是UART、SPI或者USB等等。我们这里默认是使用UART。

>五、按键消息来源和处理

1. 代码理解前的思考

1)按键消息按理是跟应用相关的,因此其必然是跟某个taskId绑定。在这种简单的嵌入式系统中,一般是由一个称为UI的任务来统一处理按键的消息。

2)按照上一篇文章和上一节Timer的分析,OSAL的设计是将事件event_id发往目标task,即设置taskEvents[tasked]。我们可以想象在按键中断(或者按键轮询)时检测到按键会往目标task发一个按键事件。但是,我们再细想,发一个KEY事件够了吗?很明显taskEvents的元素才是16bit,每个bit表示一个事件,最多只能代表16种事件,就算这16事件都用来表示不同的按键,也显得不够。因为系统可能有更多的按键啊,如果这样设计扩展性就太差了。事实上,它只是发了一个KEY_CHANGE事件,而键值是以MSG消息的形式发到系统的消息队列,而该消息也会带上目标taskId的标识。

3)以上两点是OSAL的KEY处理机制。对于用户快速开发,则需要知晓如何增加一个按键,或者改变一个按键对应的GPIO;处理按键的过程在哪里实现?

带着以上问题,我们从头到尾跟踪一次KEY处理的过程。OSAL对KEY的处理机制有点绕,但封装得挺有意思的。

对Key处理机制真正的理解过程应该是倒序的,即从按键的处理一步一步往前推,在现场教学时,对着代码反跟踪能够更加体现本文的方法论。为了表述更加有条理性,这里就从头到尾正序阐述。

2. 初始化

       1)main->HalDriverInit->HalKeyInit

2)main->InitBoard( OB_READY )

OnboardKeyIntEnable= HAL_KEY_INTERRUPT_ENABLE;

HalKeyConfig(OnboardKeyIntEnable, OnBoard_KeyCallback);

相关的代码在hal_keys.c和hal_keys.h,若要增加按键或者修改按键设置即修改这里。

OnBoard_KeyCallback是按键中断的回调函数。我们在下一步再展开其实现过程,现在跟踪在哪里会调用这个回调。我们从中断的源头开始跟踪。

3. 中断的执行过程

4.HAL层任务的处理过程

pHalKeyProcessFunction即是之前在HalKeyConfig接口中设置的OnBoard_KeyCallback,继续跟踪这个函数的实现:

这个 registeredKeysTaskID是什么,就是处理按键消息的任务Id。在哪里被初始化呢?

5.按键处理任务的初始化

main-> osal_init_system-> osalInitTasks-> SimpleBLEPeripheral_Init

-> RegisterForKeys( simpleBLEPeripheral_TaskID )

即在这个函数里面将simpleBLEPeripheral_TaskID赋值给registeredKeysTaskID,即SimpleBLEPeripheral对应的任务来处理这个消息。

6.按键的处理

用户添加的按键处理即在simpleBLEPeripheral_HandleKeys函数中。

请一步步地印证第一点,代码理解前的思考。

节日快乐!

更多原创嵌入式Linux,IOT、蓝牙wifi开发技术分享请关注微信公众号:嵌入式企鹅圈