---基于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函数中。
请一步步地印证第一点,代码理解前的思考。
感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程