BLE协议栈学习2——OSAL

OSAL简介

BLE 协议栈包含了 BLE 协议所规定的基本功能,这些功能是以函数的形式实现的,为了便于管理
这些函数集,BLE 协议栈内加入了实时操作系统(并非真正意义上的操作系统),称为 OSAL(操作系统抽象层,OperatingSystemAbstractionLayer)。
OSAL 与标准的操作系统还是有一定区别的,OSAL 实现了类似操作系统的某些功能,例如,任务切换、提供了内存管理功能等,但 OSAL 并不能称为真正意义上的操作系统。

  • OSAL消息队列
    在进行消息队列的解释之前,需要区分一下消息和事件的区别。
    事件:是驱动任务去执行某些操作的条件,当系统中产生了一个事件,OSAL 将这个事件传递给相应的任务后,任务才能执行一个相应的操作,例如调用具体的某个功能函数。通常某些事件发生时,又伴随着一些附加信息的产生,例如:主机 GATT 接收到数据后,会产生GATT_MSG_EVENT 消息,但是任务的事件处理函数在处理这个事件的时候,还需要得到所收到的数据。
    因此消息的作用就是将事件和完成这个任务所需要的数据进行一个打包封装,将消息发送到消息队列,然后在事件处理函数中就可以使用 osal_msg_receive,从消息队列中得到该消息。如下代码可以从消息队列中得到一个消息。

    pMsg=osal_msg_receive(simpleBLETaskId))
    

    OSAL 维护了一个消息队列,每一个消息都会被放到这个消息队列中去,当任务接收到事件后,可以从消息队列中获取属于自己的消息,然后调用消息处理函数进行相应的处理即可。

  • OSAL应用编程接口
    OSAL 提供了丰富的编程接口 API 函数,我们只需要了解 API 如何调用就可以使用OSAL进行开发。
    OSAL一共提供了8个方面的API:

    • 消息管理
      Osal_msg_allocate()为消息分配缓存空间,分配之后,可以填充消息,然后经过 osal_msg_sned()将消息发送出去,然后任务函数中通过 osal_msg_reveive()函数接收属于自己的消息,并处理,最后调用osal_msg_deallocate()函数销毁由 Osal_msg_allocate()分配的内存空间。
    • 任务同步
      当我们需要立刻启动一个事件时,调用 osal_set_event(),调用后立即产生一个事件,等到 osal 下次循环时,就会判断到这个事件,然后调用相应的任务函数处理该事件。
      如果想过一段时间在启动一个事件,或者说,我需要延时 5 秒然后查询一下 gpio 的状态,这时就需要 osal_start_timerEx 来创建一个定时器事件,当设定的时间到达后,就会产生某个事件。加入刚才已经创建了一个定时器事件,但是中途改变主意了,不需要去轮询 gpio 状态,就可以调用osal_stop_timerEx()来停止已经启动的定时器。
    • 时间管理
    • 中断管理
    • 任务管理
    • 内存管理
      单片机的内存资源非常有限,CC2540的内部只有8K的RAM,因此OSAL提供了一种“静态内存池”的技术。
      简单地讲系统运行的时候开辟了一个非常大的数组,然后提供一些接口给用户,当用户需要使用内存的时候,调用分配 api 取得一些内存,等到用户使用完之后,不在需要这个内存的时候,在调用销户内存的 api,释放先前占用的内存,这样最大程度复用了有限的内存空间。
      osal_mem_alloc()用来分配内存,用完之后,通过调用 osal_mem_free 释放内存。
    • 电源管理
    • 非易失闪存管理
      我们在实际项目开发中,肯定会有一些数据需要保证掉电后不丢失,对于常规操作而言就是外挂一个EEPROM设备连接到单片机进行参数存储。但是对于CC2540而言,OSAL提供了内部 flash 的存储管理接口。通过调用这些接口,就可以掉电保存参数,我们只需要先对这些参数数据创建一个唯一的 item 标识,用来区分其他的已经保存的数据的 item标识,然后调用 osal_nv_write()函数写入参数,等到需要的时候可以调用 osal_nv_read()来读取保存的值即可。

OSAL基础实验

在学习BLE协议栈的过程中,OSAL可以称得上能够贯穿始终,后续的BLE操作都是在OSAL的框架下进行的,因此学会使用OSAL至关重要,接下来就是OSAL的几个基础实验,在这些实验中并不包含BLE的部分。

1. OLED显示实验

和第一部分学习CC2540作为单片机使用一样,第一件事就是先拿OLED显示个什么,毕竟后续的用户交互显示屏才是第一选择。
实现步骤:
1.任务初始化函数里调用 SimpleOsal_Init 函数向 lcd 输出信息

void SimpleOsal_Init( uint8 task_id )
{
  SimpleOsal_TaskID = task_id;

  // Register for all key events - This app will handle all key events
  RegisterForKeys( SimpleOsal_TaskID );
 
  //向lcd输出相关信息
  //第一行,显示内容:"SimpleOsal"
  HalLcdWriteString( "SimpleOsal", HAL_LCD_LINE_1 );
  //第二行,显示内容:"XL OSAL Test"
  HalLcdWriteString( "XL OSAL Test", HAL_LCD_LINE_2 );
  // Setup a delayed profile startup
  //为了方便观察实验现象,延时2s执行后面的操作
  osal_start_timerEx( SimpleOsal_TaskID, SBP_START_DEVICE_EVT, 2000 );
}

2.调用 osal_start_timerEx 函数延时启动事件,对 count 计数,同时对count进行显示。

if ( events & SBP_START_DEVICE_EVT )
  {
  	//向LCD第1行显示Count的10进制计数
	HalLcdWriteStringValue("Count(10):", count, 10, HAL_LCD_LINE_1);

  	//向LCD第2行显示Count的16进制计数
	HalLcdWriteStringValue("Count(16):", count, 16, HAL_LCD_LINE_2);
	count++;

    //为了方便观察现象,我们1秒之后再次启动该任务
    osal_start_timerEx( SimpleOsal_TaskID, SBP_START_DEVICE_EVT, 1000 );
    return ( events ^ SBP_START_DEVICE_EVT );
  }
  // Discard unknown events
  return 0;

实现效果:
编译下载完成后,OLED显示屏会先显示"SimpleOsal"和"XL OSAL Test"后,跳转到计数页面,页面上显示的是count的十进制和十六进制的形式。
在这里插入图片描述

2. ADC五向按键实验

在实际项目中,按键和显示屏的重要性如出一辙,在单片机上按键是主要的交互输入设备,本实验所要实现的就是通过可以进行五个方向按动的按键配合ADC实现按动方向绑定,在显示屏上显示当前按键拨动的方向。
实现步骤:
1.任务初始化函数里调用 SimpleOsal_Init 函数向 lcd 输出信息

void SimpleOsal_Init( uint8 task_id )
{
  SimpleOsal_TaskID = task_id;

  // Register for all key events - This app will handle all key events
  RegisterForKeys( SimpleOsal_TaskID );
 
  //向lcd输出相关信息
  //第一行,显示内容:"SimpleOsal"
  HalLcdWriteString( "SimpleOsal", HAL_LCD_LINE_1 );
  //第二行,显示内容:"XL OSAL Test"
  HalLcdWriteString( "XL OSAL Test", HAL_LCD_LINE_2 );
  // Setup a delayed profile startup
  //为了方便观察实验现象,延时2s执行后面的操作
  osal_start_timerEx( SimpleOsal_TaskID, SBP_START_DEVICE_EVT, 2000 );
}

2.调用 osal_start_timerEx 函数延时启动事件
由于按键属于系统消息,因此会在标准的 SYS_EVENT_MSG 中触发,然后通过osal_msg_receive 来接收系统消息,如果消息有效,则进入消息处理函数中去进一步判断是何种系统消息。

uint16 SimpleOsal_ProcessEvent( uint8 task_id, uint16 events )
{

  VOID task_id; // OSAL required parameter that isn't used in this function

  if ( events & SYS_EVENT_MSG )
  {
    uint8 *pMsg;

    if ( (pMsg = osal_msg_receive( SimpleOsal_TaskID )) != NULL )
    {
      //处理按键信消息
      SimpleOsal_ProcessOSALMsg( (osal_event_hdr_t *)pMsg );

      // Release the OSAL message
      VOID osal_msg_deallocate( pMsg );
    }

    // return unprocessed events
    return (events ^ SYS_EVENT_MSG);
  }

  if ( events & SBP_START_DEVICE_EVT )
  {
    //HalLcdWriteString( "Key test", HAL_LCD_LINE_3 );
    return ( events ^ SBP_START_DEVICE_EVT );
  }
  // Discard unknown events
  return 0;
}

static void SimpleOsal_ProcessOSALMsg( osal_event_hdr_t *pMsg )
{
  switch ( pMsg->event )
  {
	case KEY_CHANGE:
	  simpleOsal_HandleKeys( ((keyChange_t *)pMsg)->state, ((keyChange_t *)pMsg)->keys );
	  break;

  default:
    // do nothing
    break;
  }
}

3.根据传入的参数 keys 来判断是哪个按键触发

static void simpleOsal_HandleKeys( uint8 shift, uint8 keys )
{
  if ( keys & HAL_KEY_SW_1 )
  {
    HalLcdWriteString("Joys: RIGHT",HAL_LCD_LINE_2);
  }
  if ( keys & HAL_KEY_SW_2 )
  {
    HalLcdWriteString("Joys: UP",HAL_LCD_LINE_2);
  }
  if ( keys & HAL_KEY_SW_3 )
  {
    HalLcdWriteString("Joys: DOWN",HAL_LCD_LINE_2);
  }
  if ( keys & HAL_KEY_SW_4 )
  {
    HalLcdWriteString("Joys: LEFT",HAL_LCD_LINE_2);
  }
  if ( keys & HAL_KEY_SW_5 )
  {
    HalLcdWriteString("Joys: CENTER",HAL_LCD_LINE_2);
  }
}

实验效果:
显示不同方向键按下之后的信息
在这里插入图片描述

3. UART串口实验

串口在单片机开发过程中的地位也是举足轻重,毕竟不会所有的消息都通过显示屏展示,比如调试信息、数据监测等,所以此时就需要UART串口来进行开发调试的辅助了。
本实验要实现的是通过五向按键更改数值,同时将数值实时通过串口发送到电脑的串口调试助手上显示。
实现步骤:
1.初始化UART配置,并在SimpleOsal_Init中调用

void SerialApp_Init( uint8 taskID )
{
  //调用uart初始化代码
  serialAppInitTransport();
  //记录任务函数的taskID,备用
  sendMsgTo_TaskID = taskID;
}

/*uart初始化代码,配置串口的波特率、流控制等*/
void serialAppInitTransport( )
{
  halUARTCfg_t uartConfig;

  // configure UART
  uartConfig.configured           = TRUE;
  uartConfig.baudRate             = SBP_UART_BR;//波特率
  uartConfig.flowControl          = SBP_UART_FC;//流控制
  uartConfig.flowControlThreshold = SBP_UART_FC_THRESHOLD;//流控制阈值,当开启flowControl时,该设置有效
  uartConfig.rx.maxBufSize        = SBP_UART_RX_BUF_SIZE;//uart接收缓冲区大小
  uartConfig.tx.maxBufSize        = SBP_UART_TX_BUF_SIZE;//uart发送缓冲区大小
  uartConfig.idleTimeout          = SBP_UART_IDLE_TIMEOUT;
  uartConfig.intEnable            = SBP_UART_INT_ENABLE;//是否开启中断
  uartConfig.callBackFunc         = sbpSerialAppCallback;//uart接收回调函数,在该函数中读取可用uart数据

  // start UART
  // Note: Assumes no issue opening UART port.
  (void)HalUARTOpen( SBP_UART_PORT, &uartConfig );

  return;
}

2.ADC 采样到的数据通过 UART 输出到 PC

if ( events & SBP_START_DEVICE_EVT )
  {
	AdcValue = HalAdcRead(HAL_ADC_CHANNEL_4, HAL_ADC_RESOLUTION_8);
	HalLcdWriteStringValue("P0_4 ADC:", AdcValue, 10,HAL_LCD_LINE_1);
    //将采样的adc值通过UART发送出去
    SerialPrintValue("P0_4 ADC Value:",AdcValue,10);
    //换行
    SerialPrintString("\r\n");

	//500ms之后再次启动,这样实验500ms采样一次
	osal_start_timerEx( SimpleOsal_TaskID, SBP_START_DEVICE_EVT, 500 );
    return ( events ^ SBP_START_DEVICE_EVT );
  }
  // Discard unknown events
  return 0;
static void simpleOsal_HandleKeys( uint8 shift, uint8 keys )
{
  if ( keys & HAL_KEY_SW_1 )
  {
    HalLcdWriteString("Joystick: RIGHT",HAL_LCD_LINE_2);
    //向UART发送当前按键
    SerialPrintString("Joystick: RIGHT\r\n");
  }
  if ( keys & HAL_KEY_SW_2 )
  {
    HalLcdWriteString("Joystick: UP",HAL_LCD_LINE_2);
    SerialPrintString("Joystick: UP\r\n");
  }
  if ( keys & HAL_KEY_SW_3 )
  {
    HalLcdWriteString("Joystick: DOWN",HAL_LCD_LINE_2);
    SerialPrintString("Joystick: DOWN\r\n");
  }
  if ( keys & HAL_KEY_SW_4 )
  {
    HalLcdWriteString("Joystick: LEFT",HAL_LCD_LINE_2);
    SerialPrintString("Joystick: LEFT\r\n");
  }
  if ( keys & HAL_KEY_SW_5 )
  {
    HalLcdWriteString("Joystick: CENTER",HAL_LCD_LINE_2);
    SerialPrintString("Joystick: CENTER\r\n");
  }
  HalLedBlink(HAL_LED_2, 1,20,500 );
}

3.接收到数据后,输出到显示屏

/*uart接收回调函数*/
void sbpSerialAppCallback(uint8 port, uint8 event)
{
  uint8  pktBuffer[SBP_UART_RX_BUF_SIZE];
  // unused input parameter; PC-Lint error 715.
  (void)event;
  int i=0;
  for(i=6000;i>0;i--){
	asm("nop");
  }
  //HalLcdWriteString("Data form my UART:", HAL_LCD_LINE_4 );
  //返回可读的字节
  if ( (numBytes = Hal_UART_RxBufLen(port)) > 0 ){
  	//读取全部有效的数据,这里可以一个一个读取,以解析特定的命令
	(void)HalUARTRead (port, pktBuffer, numBytes);
        //接收到数据后,输出到LCD显示。
        HalLcdWriteString(pktBuffer,HAL_LCD_LINE_2);
  }  
}

以上代码实验了 UART 功能,但是还需要设置一下 IAR 工程,需要添加 HAL_UART=TRUE 宏定义来使能UART。
在这里插入图片描述
实验效果如下:
LCD 会显示一些内容,并且会打印 ADC 通道 4 的值以及按键的状态信息,还会打印串口发送来的数据。
在这里插入图片描述

4. SNV 内部 Flash 存储实验

我们开发板使用的 CC2540 内部有 256K 的 flash 空间,他不光可以存储程序,还可以存储用户数据,TI 针对内部 flash 提供了上层轻便的调用接口 SNV,来存储用户数据。
实现步骤:
1.任务初始化函数里调用 SimpleOsal_Init 函数向 lcd 输出信息

void SimpleOsal_Init( uint8 task_id )
{
  SimpleOsal_TaskID = task_id;

  // Register for all key events - This app will handle all key events
  RegisterForKeys( SimpleOsal_TaskID );
 
  //向lcd输出相关信息
  //第一行,显示内容:"SimpleOsal"
  HalLcdWriteString( "SimpleOsal", HAL_LCD_LINE_1 );
  //第二行,显示内容:"XL OSAL Test"
  HalLcdWriteString( "XL OSAL Test", HAL_LCD_LINE_2 );
  // Setup a delayed profile startup
  //为了方便观察实验现象,延时2s执行后面的操作
  osal_start_timerEx( SimpleOsal_TaskID, SBP_START_DEVICE_EVT, 2000 );
}

2.通过五向按键的 UP 和 Down 来完成count 计数

static void simpleOsal_HandleKeys( uint8 shift, uint8 keys )
{
  static uint8 count=0;
  if ( keys & HAL_KEY_SW_2 )//UP
  {
    count++;
    HalLcdWriteStringValue("NV Write:",count,10,HAL_LCD_LINE_1);
    osal_snv_write(0xFF,1,&count);
  }

  if ( keys & HAL_KEY_SW_3 )//DOWN
  {

    osal_snv_read(0xFF,1,&count);
    HalLcdWriteStringValue("NV Read:",count,10,HAL_LCD_LINE_2);
  }
}

实验效果:
先多次按 UP,然后再按一次 down,读取已经存入的数据,然后断电,上电后,直接按 down,读取 0xff 处的数据,这样就实现了数据的掉电存储。
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值