CC2640开发记录1_在TI-RTOS框架下实现LED+BUTTON功能_任务之间消息传递

时间:2024-03-25 17:26:21

一 需要准备的

① 仔细阅读《SWRU393_CC2640_BLE_Software_Developer's_Guide》官方文档

②《ble_cc26xx_2_01_00_44423》蓝牙协议栈

③  IAR+XDS100V3调试器

④ 功能比较全的开发板

 

二 目的

在TI-RTOS的框架下,实现LED与BUTTON的交互功能,以此来熟悉TASK的编写方法,方便于后期创建功能更加丰富的应用程序。

LED是设备的简单输出设备,高级形式就是LCD屏幕,或者网络设备输出报文。

BUTTON是简单的输入设备,高级形式可以升级为触摸屏或者网络设备输入报文。

 

很显然,BUTTON一般是由外部中断来实现的,TI-RTOS严格管理了外部的中断事件源。

datasheet中指明

CC2640开发记录1_在TI-RTOS框架下实现LED+BUTTON功能_任务之间消息传递

RTOS根据优先级分了四个层级,HWI>SWI>TASK>IDLE

HWI由硬件事件来触发,且优先级极高,由芯片内部决定最高的优先级。

因此,可知配置外部中断的中断事件是由HWI机制管理的。

 

TI-RTOS中的消息机制

如果我在app中创建了一个处理按键的TASK,并且认为HWI也是一个TASK并且HWI管理了外部中断源,简单来说,只有HWI的TASK可以知道键有没有被按下,那么如何通知app中处理按键的TASK呢?

datasheet中指明,TI-RTOS采用queue和其他很多机制来实现消息的传递

CC2640开发记录1_在TI-RTOS框架下实现LED+BUTTON功能_任务之间消息传递

 

显然的是 TASKA 与 TASKB共享了一个消息队列,TASKB与TASKA相互独立,也就是TASKA与TASKB的执行顺序互不相干,没有说TASKB一定在TASKA之后执行。

思考一下,在不知道TASKA有没有在我之前执行,不知道其在什么时候执行,但是我作为TASKB需要得知他的工作结果,该如何处理这个问题?

这边给出的方法是等待,TASKB长时间处于BLOCK(阻塞状态)程序不运行,当TASKA收到紧要的消息,如,得知了外部的按键被按下了,这时候会做第一件事,post信号量,唤醒TASKB,然后就是将这个紧要的消息推入与TASKB共享的消息队列,然后TASKA的任务就结束了。TASKB被唤醒,检查队列是否为空,推出消息开始处理!

如datasheet图

CC2640开发记录1_在TI-RTOS框架下实现LED+BUTTON功能_任务之间消息传递

 

总共五个按键硬件连接,见定义

#define Board_KEY_SELECT IOID_11

#define Board_KEY_UP IOID_19

#define Board_KEY_DOWN IOID_12

#define Board_KEY_LEFT IOID_15

#define Board_KEY_RIGHT IOID_18

 

创建board_key.c

#include <stdbool.h>

#include <ti/sysbios/knl/Clock.h>

#include <ti/sysbios/family/arm/m3/Hwi.h>

#include <ti/sysbios/knl/Semaphore.h>

#include <ti/sysbios/knl/Queue.h>

 

#include <ti/drivers/pin/PINCC26XX.h>

#include <ICall.h>

#include <inc/hw_ints.h>

#include "bcomdef.h"

#include "PIN.h"

#include "util.h"

#include "board_key.h"

 

// Value of keys Pressed

static uint8_t keysPressed;

 

// Key debounce clock

static Clock_Struct keyChangeClock;

 

// Pointer to application callback

keysPressedCB_t appKeyChangeHandler = NULL;

 

// Memory for the GPIO module to construct a Hwi

Hwi_Struct callbackHwiKeys;

 

//call back function

static void Board_keyChangeHandler(UArg a0);

static void Board_keyCallback(PIN_Handle hPin, PIN_Id pinId);


 

PIN_Config keyPinsCfg[] =

{

Board_KEY_SELECT | PIN_GPIO_OUTPUT_DIS | PIN_INPUT_EN | PIN_PULLUP,

Board_KEY_UP | PIN_GPIO_OUTPUT_DIS | PIN_INPUT_EN | PIN_PULLUP,

Board_KEY_DOWN | PIN_GPIO_OUTPUT_DIS | PIN_INPUT_EN | PIN_PULLUP,

Board_KEY_LEFT | PIN_GPIO_OUTPUT_DIS | PIN_INPUT_EN | PIN_PULLUP,

Board_KEY_RIGHT | PIN_GPIO_OUTPUT_DIS | PIN_INPUT_EN | PIN_PULLUP,

PIN_TERMINATE

};

 

PIN_State keyPins;

PIN_Handle hKeyPins;

 

/*

* brief 按键的初始化函数

* PIN_open(&keyPins, keyPinsCfg) 是为了创建PIN列表和PIN操作相关的动态内存空间,返回PIN_Handle型的数据,

* keyPinsCfg是使用到的PIN列表,是一个U32型的数据,格式按规则仿造,第一个参数是一个软件的ID,不与实际的硬件IO口相对应

*

* PIN_registerIntCb(hKeyPins, Board_keyCallback)是用来注册PIN相关HWI事件发生后需要回调的函数指针,见PIN.h的说明

* * Registers a callback function (see #PIN_IntCb for details) for the client

* * identified by handle that will be called from HWI context upon an interrupt

* * event on one or more of the allocated pins that have interrupts enabled

*

* 因此,使用PIN_open返回的handle数据hKeyPins,并且为其指定相应的回调函数 Board_keyCallback

*

* PIN_setConfig(hKeyPins, PIN_BM_IRQ, Board_KEY_SELECT | PIN_IRQ_BOTHEDGES);实际来配置硬件GPIO的中断类型,配置为双边中断

*

* Util_constructClock(&keyChangeClock, Board_keyChangeHandler,

* KEY_DEBOUNCE_TIMEOUT, 0, false, 0); 用来创建一个定时器,实现消抖功能

*

*

* appKeyChangeHandler = CB; 注册消抖机制完成后执行的最终按键处理函数指针

*

* pra @ CB : 最终执行的按键处理函数指针

* return : NULL

*/

void board_key_init(keysPressedCB_t CB){

//分配操作用的handle空间

hKeyPins = PIN_open(&keyPins, keyPinsCfg);

 

//注册PIN相关的HWI事件发生后回调的函数

PIN_registerIntCb(hKeyPins, Board_keyCallback);

 

//配置实际GPIO的中断模式

PIN_setConfig(hKeyPins, PIN_BM_IRQ, Board_KEY_SELECT | PIN_IRQ_BOTHEDGES);

PIN_setConfig(hKeyPins, PIN_BM_IRQ, Board_KEY_UP | PIN_IRQ_BOTHEDGES);

PIN_setConfig(hKeyPins, PIN_BM_IRQ, Board_KEY_DOWN | PIN_IRQ_BOTHEDGES);

PIN_setConfig(hKeyPins, PIN_BM_IRQ, Board_KEY_LEFT | PIN_IRQ_BOTHEDGES);

PIN_setConfig(hKeyPins, PIN_BM_IRQ, Board_KEY_RIGHT | PIN_IRQ_BOTHEDGES);

 

//构建一个消抖用的定时器

Util_constructClock(&keyChangeClock, Board_keyChangeHandler,

KEY_DEBOUNCE_TIMEOUT, 0, false, 0);

 

//传入消抖后实际执行的最终处理函数指针

appKeyChangeHandler = CB;

}

 

/*

* brief PIN相关的HWI事件发生后回调的函数实体

* 使用PIN_getInputValue函数来判定是哪一个按键被按下,并且写入全局变量keysPressed中以记录。

* 判断完成后,打开定时器,等待KEY_DEBOUNCE_TIMEOUT时间之后,会自动执行Board_keyChangeHandler任务,这里实际上就是做了一个消抖处理

* Util_startClock(&keyChangeClock); 这里keyChangeClock是一个定时器实体,工作在one-shot模式,见datasheet CLOCK用法

*

* @PIN_Handle hPin, PIN_Id pinId,由系统调用者提供传参的格式

*

* return : NULL

*/

static void Board_keyCallback(PIN_Handle hPin, PIN_Id pinId){

keysPressed = 0;

 

if ( PIN_getInputValue(Board_KEY_SELECT) == 0 )

{

keysPressed |= KEY_SELECT;

}

 

if ( PIN_getInputValue(Board_KEY_UP) == 0 )

{

keysPressed |= KEY_UP;

}

 

if ( PIN_getInputValue(Board_KEY_DOWN) == 0 )

{

keysPressed |= KEY_DOWN;

}

 

if ( PIN_getInputValue(Board_KEY_LEFT) == 0 )

{

keysPressed |= KEY_LEFT;

}

 

if ( PIN_getInputValue(Board_KEY_RIGHT) == 0 )

{

keysPressed |= KEY_RIGHT;

}

 

Util_startClock(&keyChangeClock);

}

 

/*

* brief 最终的按键处理函数,运行init函数外部传入的处理函数指针

*

* pra @ UArg a0 按照回调函数指针参数要求写的,用来向外部传递键值

*

* return : NULL

*/

static void Board_keyChangeHandler(UArg a0)

{

if (appKeyChangeHandler != NULL)

{

// Notify the application

(*appKeyChangeHandler)(keysPressed);

}

}

创建board_key.h

 

#ifndef __BOARD_KEY__

#define __BOARD_KEY__

 

#include "stdint.h"

 

//KEY HARDWARE PIN INDEX

#define Board_KEY_SELECT IOID_11

#define Board_KEY_UP IOID_19

#define Board_KEY_DOWN IOID_12

#define Board_KEY_LEFT IOID_15

#define Board_KEY_RIGHT IOID_18


 

//KEY SOFTWARE INDEX

#define KEY_SELECT 0x0001

#define KEY_UP 0x0002

#define KEY_DOWN 0x0004

#define KEY_LEFT 0x0008

#define KEY_RIGHT 0x0010

// Debounce timeout in milliseconds

#define KEY_DEBOUNCE_TIMEOUT 200


 

typedef void (*keysPressedCB_t)(uint8 keysPressed);

 

extern void board_key_init(keysPressedCB_t CB);

 

#endif

可见 PIN_registerIntCb(hKeyPins, Board_keyCallback);函数将app与底层的HWI联结起来了。于是我们可以通过外部的函数来得知是否由外部中断进入。

 

到目前为止都仅仅是进行到硬件和软件之间的联结代码,也就是外部硬件事件来了,软件可以发现。没有用到TI-RTOS的TASK传消息机制。

目前做的消息传递是 H->S1 接下来就是 S1->S2->S3....

H ->S1 ->S2 (H 硬件消息 S 软件消息 ->消息传递)

 

在主函数中我们定义了如下函数作为指针传入board_key_init

board_key_init(SimpleBLEPeripheral_keyChangeHandler);

 

/*********************************************************************

* @fn SimpleBLEPeripheral_keyChangeHandler

*

* @brief Key event handler function

*

* @param keys - keypressed

*

* @return none

*/

void SimpleBLEPeripheral_keyChangeHandler(uint8 keys)

{

SimpleBLEPeripheral_enqueueMsg(SBP_KEY_CHANGE_EVT, keys);

}

在主函数中很明显创建了一个TASK共享的消息队列,并且定义了enqueue函数推入消息,这个enqueue消息有两个参数,第一个参数是用来分别消息的类型,是消息的唯一标志,第二个参数可用来传递一些其他信息。例如本例子中的键值。很显然在其他应用中会传递很大的数据,所以这里可以用ICALL来实现传大值。按键任务不需要,但使用不影响。

 

消息唯一标志已经定义了

// Internal Events for RTOS application

#define SBP_STATE_CHANGE_EVT 0x0001

#define SBP_CHAR_CHANGE_EVT 0x0002

#define SBP_PERIODIC_EVT 0x0004

#define SBP_CONN_EVT_END_EVT 0x0008

#define SBP_BLE_CHAR_CHANGE_EVT 0x0010

#define SBP_KEY_CHANGE_EVT 0x0020

前文说到,TI-RTOS TASK之间采用共享消息队列的方法来完成消息互传。

在这个应用的环境下,笔者认为HWI对应的一系列任务就是TASKA,他推入了有按键按下的消息进入队列

SimpleBLEPeripheral_enqueueMsg(SBP_KEY_CHANGE_EVT, keys);

而TASKB就是SimpleBLEPeripheral_task,这个TASK是一个最小的可以实现BLE从机角色的任务。换言之这个任务执行,则运行此代码的设备能实现BLE数据通讯的功能,是CC2640最基本的功能,这里的代码TI官方写好了,而且还包括主机Central 等其他角色相关代码。这里笔者在从机角色的基础上进行修改。

在TASKB的运行函数里有这样的几句

// Waits for a signal to the semaphore associated with the calling thread.

// Note that the semaphore associated with a thread is signaled when a

// message is queued to the message receive queue of the thread or when

// ICall_signal() function is called onto the semaphore.

ICall_Errno errno = ICall_wait(ICALL_TIMEOUT_FOREVER);

根据手册,笔者推断上述代码一定包含一个pending,取观察是否有POST出现,而TASKA在推入消息后,一定给出了一个POST,也许是在HWI的代码里实现的,因为我只是注册了回调函数,实际上系统内置的代码我并没有编写,有可能是已经固化的官方写的程序已经写好了这些,我们不得而知,只能猜测。

CC2640开发记录1_在TI-RTOS框架下实现LED+BUTTON功能_任务之间消息传递这里显然的说了 application wakes up 。

 

接下来在任务的死循环中找到了如下代码

 

while (!Queue_empty(appMsgQueue))

{

sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue);

if (pMsg)

{

// Process message.

SimpleBLEPeripheral_processAppMsg(pMsg);

 

// Free the space from the message.

ICall_free(pMsg);

}

}

发现此代码一直在检查appMsgQueue队列是否为空,如果空,那么就会用dequeue把报文推出来使用,从而进到我们自己定义的事件报文处理函数SimpleBLEPeripheral_processAppMsg(pMsg);。

/*********************************************************************

* @fn SimpleBLEPeripheral_processAppMsg

*

* @brief Process an incoming callback from a profile.

*

* @param pMsg - message to process

*

* @return None.

*/

static void SimpleBLEPeripheral_processAppMsg(sbpEvt_t *pMsg)

{

switch (pMsg->hdr.event)

{

case SBP_STATE_CHANGE_EVT:

SimpleBLEPeripheral_processStateChangeEvt((gaprole_States_t)pMsg->

hdr.state);

break;

 

case SBP_CHAR_CHANGE_EVT:

SimpleBLEPeripheral_processCharValueChangeEvt(pMsg->hdr.state);

break;

 

#ifdef QUNJIEBLE_CODE

case SBP_BLE_CHAR_CHANGE_EVT:

BLE_CharValueChangeEvt(pMsg->hdr.state);

case SBP_KEY_CHANGE_EVT:

SimpleBLEPeripheral_handleKeys(0, pMsg->hdr.state);

#endif

 

default:

// Do nothing.

break;

}

}

这里很显然,不仅仅TASKA可以触发TASKB的消息处理函数,还有很多其他的TASK也可以,是由各自TASK推入的消息类型来决定的,这里看出来pMsg->hdr.event存放着消息的类型,我们根据类型找到了SBP_KEY_CHANGE_EVT,并且执行了最终的执行函数SimpleBLEPeripheral_handleKeys(0, pMsg->hdr.state);

 

static void SimpleBLEPeripheral_handleKeys(uint8_t shift, uint8_t keys){

if(keys == KEY_LEFT){

LED_TURN_OFF();

}

 

if(keys == KEY_RIGHT){

LED_TURN_ON();

}

}

在这里我们操作,检测到左键关灯,右键开灯。

 

灯的代码很简单,配置方向,开灯高电平,关灯低电平

static PIN_State ledPins;

static PIN_Handle hledPins = NULL;

#define LED_PORT IOID_25

PIN_Config ledPinsCfg[] =

{

LED_PORT | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,

PIN_TERMINATE

};

 

void board_led_init(void){

hledPins = PIN_open(&ledPins, ledPinsCfg);

}

void LED_TURN_OFF(void){

PIN_setOutputValue(hledPins, LED_PORT, 0);

}

void LED_TURN_ON(void){

PIN_setOutputValue(hledPins, LED_PORT, 1);

}

测试结果

按下按键LEFT,开灯,否则,关灯。

 

小结:

LED任务很简单,直接在上下文中进行,即在有需要的时候顺序执行即可。

BUTTON任务复杂,牵扯到了HWI TASK的消息传递,消息的传递过程包含两个类型

1 TASK 与 TASK之间的消息传递,非上下文传递,两个任务执行没有顺序关系。->

2 上下文消息传递,笔者认为,函数调用即上下文传递,函数调用本质上就是顺序执行的,有先后关系。->

HWI->Board_keyCallback->Board_keyChangeHandler->SimpleBLEPeripheral_keyChangeHandler

->SimpleBLEPeripheral_processAppMsg->SimpleBLEPeripheral_handleKeys

粉色函数扮演TASKA

绿色函数扮演TASKB 通过QUEUE共享消息 

 

笔者将要在CC2640上开发很多功能,所以需要深入的了解TI-RTOS的结构机理,目前在努力的阅读datasheet,希望能把更多的设备运行在CC2640 上,这篇文章记录了笔者完成按键功能和led功能的内容。