-基于TI CC254X OSAL的分析 当工具链配置完成后,SourceInsight向你展示一份源码工程,不借助百度和开发文档,能否在一两个小时内理解源码的组成框架和接口,进行快速开发? 上一篇《[如何快速理解一个全新的嵌入式操作系统](http://blog.csdn.net/yueqian_scut/article/details/48781937)》我们已经分析了如何快速理解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提供给用户的一个回调函数,即在串口中断时会调用该回调函数。而串口中断有以下几种事件: ![](https://box.kancloud.cn/2016-01-12_5694d4a2aefa5.jpg) 一般我们都会在该回调函数中实现串口接收,如实现串口透传模式。回调接口声明如下: 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 ![](https://box.kancloud.cn/2016-01-12_5694d4a2bdf87.jpg) 2)main->InitBoard( OB_READY ) OnboardKeyIntEnable= HAL_KEY_INTERRUPT_ENABLE; HalKeyConfig(OnboardKeyIntEnable, OnBoard_KeyCallback); ![](https://box.kancloud.cn/2016-01-12_5694d4a2d36e1.jpg) 相关的代码在hal_keys.c和hal_keys.h,若要增加按键或者修改按键设置即修改这里。 OnBoard_KeyCallback是按键中断的回调函数。我们在下一步再展开其实现过程,现在跟踪在哪里会调用这个回调。我们从中断的源头开始跟踪。 **3. 中断的执行过程** ![](https://box.kancloud.cn/2016-01-12_5694d4a2e440b.jpg) **4.HAL层任务的处理过程** ![](https://box.kancloud.cn/2016-01-12_5694d4a302f16.jpg) pHalKeyProcessFunction即是之前在HalKeyConfig接口中设置的OnBoard_KeyCallback,继续跟踪这个函数的实现: ![](https://box.kancloud.cn/2016-01-12_5694d4a31aba4.jpg) 这个 registeredKeysTaskID是什么,就是处理按键消息的任务Id。在哪里被初始化呢? **5.按键处理任务的初始化** main-> osal_init_system-> osalInitTasks-> SimpleBLEPeripheral_Init -> RegisterForKeys( simpleBLEPeripheral_TaskID ) 即在这个函数里面将simpleBLEPeripheral_TaskID赋值给registeredKeysTaskID,即SimpleBLEPeripheral对应的任务来处理这个消息。 **6.按键的处理** ![](https://box.kancloud.cn/2016-01-12_5694d4a3313be.jpg) 用户添加的按键处理即在simpleBLEPeripheral_HandleKeys函数中。 请一步步地印证第一点,代码理解前的思考。 节日快乐! 更多原创嵌入式Linux,IOT、蓝牙wifi开发技术分享请关注微信公众号:嵌入式企鹅圈 ![](https://box.kancloud.cn/2016-01-12_5694d4a143a64.jpg)