版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、本系列教程将结合TI推出的CC254x SoC 系列,讲解从环境的搭建到蓝牙4.0协议栈的开发来深入学习蓝牙4.0的开发过程。教程共分为六部分,本文为第四部分:第四部分知识点:第十六节 协议栈LED实验第十七节 协议栈LCD显示第十八节 协议栈UART实验第十九节 协议栈五向按键第二十节 协议栈Flash数据存储有关TI 的CC254x芯片介绍,可点击下面链接查看:主流蓝牙BLE控制芯片详解(1):TI CC2540同系列资料推荐:由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(1)由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(2)由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(3)有关本文
2、的工具下载,大家可以到以下这个地址:朱兆祺ForARM第十六节 协议栈LED实验TI的协议栈中在HAL层已经有了LED的驱动,我们只需要针对我们的开发板进行配置即可,我们的开发板有两个LED,分别对应P1.0和P1.1。这个在裸机开发的时候已经介绍了。为了保持协议栈原有的代码不变,我们在BLE-CC254x-1.4.0Componentshaltarget目录下新建一个文件夹,使它适应我们的开发板。打开LED实验工程LEDExample,选择MT254xboard,并且在工程配置中要定义HAL_LED=TRUE,下载到开发板运行,可以看到两个LED同时在闪烁。那我们的实现代码在哪里呢?其实在协
3、议栈中实现这个很简单,在启动事件中我们调用了一个HalLedSet函数,并且设置了两个LED同时闪烁。就是这么简单,协议栈已经把其它事情做好了,只需要我们调用设置函数即可。设置的模式总共有5种。#define HAL_LED_MODE_OFF 0x00 / 关闭LED#define HAL_LED_MODE_ON 0x01 / 打开LED#define HAL_LED_MODE_BLINK 0x02 / 闪烁一次#define HAL_LED_MODE_FLASH 0x04 / 不断的闪烁,最多255次#define HAL_LED_MODE_TOGGLE 0x08 / 翻转LED状态为了适应
4、不同的需求,我们可能需要更改LED的输出引脚,如图板级配置在hal_board_cfg.h文件中。这里我们的开发板只有两个LED,所以我们在这里根据开发板的实际情况修改相应的IO口。第十七节 协议栈LCD显示实验打开LCD12864的实验工程,一样的在工程配置中打开LCD,选择MT254xboard然后直接编译下载,我们可以看到LCD上已经有显示了。这些显示来自哪里呢?在初始化函数中可以看到图中的函数调用,这里是将字符串显示到LCD的第一行。在事件回调函数中可以看到这里将本机地址显示到第二行,将字符串Initialized显示到第三行,但是为什么我们在第三行没有看到这行字符串呢?而显示的字符串
5、是Advertising ,这是因为系统启动后运行非常快,在我们还没反应过来的时候已经进入了广播状态,并且将原来的字符串覆盖了,所以我们最后只能看到Advertising 了。HalLcdWriteString是将第一个参数指向的字符串显示到第二个参数指定第几行中,例如我们需要在第5行显示系统启动信息,我们可以在启动事件中,添加如下代码。这里我们来介绍一下Lcd驱动的实现,在Hal_lcd.h文件中申明了以下函数,这些函数的功能都有英文注释,这里我就不再累述了。/* Initialize LCD Service*/extern void HalLcdInit(void);/* Write a
6、string to the LCD*/extern void HalLcdWriteString ( char *str, uint8 option);/* Write a value to the LCD*/extern void HalLcdWriteValue ( uint32 value, const uint8 radix, uint8 option);/* Write a value to the LCD*/extern void HalLcdWriteScreen( char *line1, char *line2 );/* Write a string followed by
7、a value to the LCD*/extern void HalLcdWriteStringValue( char *title, uint16 value, uint8 format, uint8 line );/* Write a string followed by 2 values to the LCD*/extern void HalLcdWriteStringValueValue( char *title, uint16 value1, uint8 format1, uint16 value2, uint8 format2, uint8 line );/* Write a p
8、ercentage bar to the LCD*/extern void HalLcdDisplayPercentBar( char *title, uint8 value );协议栈中很多地方都调用了这些函数,我们如果要使我们的硬件能够兼容协议栈,被协议栈使用,就需要实现这些函数的定义,当然,为了适应我们的开发板,我已经实现了这些函数,实现都在hal_lcd.c中。第十八节 协议栈UART实验协议栈中已经用了串口的驱动,我们要做的只是对串口进行初始化,然后就可以进行串口数据的收发了。用使用串口,第一步,需要打开使能串口功能,通过配置工程来实现,这里注意,我们现在不使用USB的CDC类来实现
9、串口,所以HAL_UART_USB=FALSE。HAL_UART=TRUEHAL_UART_USB=FALSE要使用串口必须先初始化相应的串口,那该如何初始化呢?在Hal_uart.h文件中我们可以看到如下函数。uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config);这个函数就是用来初始化串口的,这个函数有两个参数,第一个指定串口号,第二个是串口的配置参数。我们来看看这个结构体的定义:typedef structbool configured; / 配置与否uint8 baudRate; / 波特率bool flowControl; / 流控制u
10、int16 flowControlThreshold;uint8 idleTimeout; / 空闲时间halUARTBufControl_t rx; / 接收halUARTBufControl_t tx; / 发送bool intEnable; / 中断使能uint32 rxChRvdTime; / 接收数据时间halUARTCBack_t callBackFunc; / 回调函数halUARTCfg_t;这个结构体成员很多,但是我们在使用串口的时候并不需要使用所有的成员。void Serial_Init(void)halUARTCfg_t SerialCfg = 0;SerialCfg.b
11、audRate = HAL_UART_BR_115200; / 波特率SerialCfg.flowControl = HAL_UART_FLOW_OFF; / 流控制SerialCfg.callBackFunc = SerialCb; / 回调函数SerialCEnable = TRUE;SerialCfg.configured = TRUE;HalLcdWriteString( “Open Uart0”, HAL_LCD_LINE_5 ); / 在第5行显示启动信息HalUARTOpen(HAL_UART_PORT_0, &SerialCfg);HalUARTWrite(HAL_
12、UART_PORT_0, “Hello MT254xBoardrn”, osal_strlen(“Hello MT254xBoardrn”);在串口回调函数中我们只做一件事,将串口接收到的数据显示到LCD中并且原样的从串口输出。回调函数的实现如下:static void SerialCb( uint8 port, uint8 events )uint8 RxBuf64=0;if(events & HAL_UART_TX_EMPTY)|( events & HAL_UART_TX_FULL ) / 发送区满或者空return;uint16 usRxBufLen = Hal_UART_RxBufL
13、en(HAL_UART_PORT_0); / 读取接收据量usRxBufLen = MIN(64,usRxBufLen);uint16 readLen = HalUARTRead(HAL_UART_PORT_0, RxBuf, usRxBufLen);HalUARTWrite(HAL_UART_PORT_0, RxBuf, usRxBufLen);实验现象,从实验现象中可以看到,一开始在串口中输出了一个标志字符串,然后我们通过串口发送了0123456789,然后数据原样的从串口输出了,这和我们预期的结果是一样的。但是我们发现LCD上的显示和我们预期的不一样,LCD上只显示了6789,前面的数据
14、并没有显示,这是怎么一回事呢?进行单步调试可以发现,我们发送一次数据,回调函数被回调了两次,第一次回调只接受到了012345,第二次回调接收到了6789,而在LCD上的显示第二次覆盖了第一次的显示,所以我们会看到这种现象,解决的办法,我们需要定义一个数据帧的时间间隔,当接收数据的间隔超过了此间隔就认为接收结束。下面我们改写接收处理,我们在接收到数据后开启定时器,定时5ms这样,当接收间隔大于5ms后,我们就可以在定时事件中处理串口接收到的数据。static void SerialCb( uint8 port, uint8 events )if(events & HAL_UART_TX_EMPT
15、Y)|( events & HAL_UART_TX_FULL ) / 发送区满或者空return;uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0); / 读取接收据量if(usRxBufLen)usRxBufLen = MIN(128,usRxBufLen);uint16 readLen = HalUARTRead(HAL_UART_PORT_0, &SerialRxBufRxIndex, usRxBufLen); / 读取数据到缓冲区RxIndex += readLen;readLen %= 128;osal_start_timer
16、Ex(simpleBLEPeripheral_TaskID, UART_EVENT, 5); / 启动定时器事件处理代码:if ( events & UART_EVENT )HalLcdWriteString( (char*)SerialRxBuf, HAL_LCD_LINE_6 ); / 在第5行显示启动信息HalUARTWrite(HAL_UART_PORT_0, SerialRxBuf, osal_strlen(SerialRxBuf);osal_memset(SerialRxBuf, 0, 128);return (events UART_EVENT);经过这样的处理后,可以发现我们刚
17、刚的问题已经解决了。到这里串口已经可以正常使用了,为了更加方便的使用串口,我在这里添加一个函数实现标准C中printf,这样更有利于我们输出。int SerialPrintf(const char*fmt, 。)uint32 ulLen;va_list ap;char *pBuf = (char*)osal_mem_alloc(PRINT_BUF_LEN); / 开辟缓冲区va_start(ap, fmt);ulLen = vsprintf(pBuf, fmt, ap); / 用虚拟打印函数实现va_end(ap);HalUARTWrite(HAL_UART_PORT_0, (uint8*)p
18、Buf, ulLen); / 从串口0输出osal_mem_free(pBuf); / 释放内存空间return ulLen;我们可以像使用C标准中的printf来使用这个函数,例如我们将LCD的输出全部导向串口的输出,在HalLcdWriteString的实现中添加串口输出代码,如下图:重新编译并且烧录后可以看到LCD的输出和串口的输出是一样的了。第十九节 协议栈五向按键和前面几个一样,按键的驱动在协议栈中也已经有了,我们只需要做一些小的修改,使它适应我们的开发板即可。1. 修改工程配置,使能按键功能。2. 在我们的工程中要使用按键功能,仅仅打开配置选项是不够的。因为协议栈代码默认只有MIN
19、IDK开发板才有按键。从这里可以看到(类似的地方有很多),如果要使能按键功能还需要定义CC2540_MINIDK,但是阅读整个协议栈你会发现,定义 CC2540_MINIDK后还会打开其它的功能,而那些功能并不是我们想要的,所以在这里我们使用另外一种方法来实现。我们定义我们的开发板也能使用按键功能,所以在工程配置中添加MT254xboard=TRUE,然后在按键功能有宏开关的地方加入这个条件。具体位置参见代码。按下相应的按键后可以看到串口输出相应的按键值。五向按键的工作原理在裸机开发的时候已经讲过了,在协议栈中已经有相应的驱动代码了,无需我们编写,只需要按照实际情况改写即可。例如我们的开发板每
20、个按键对应的电压值和原来的值并不一样,所以我们这里改写了每个按键值的电压范围。uint8 halGetJoyKeyInput(void)/* The joystick control is encoded as an analog voltage.* Read the JOY_LEVEL analog value and map it to joy movement.*/uint16 adc;uint8 ksave0 = 0;uint8 ksave1;/* Keep on reading the ADC until two consecutive key decisions are the s
21、ame. */doksave1 = ksave0; /* save previouse key reading */adc = HalAdcRead (HAL_KEY_JOY_CHN, HAL_ADC_RESOLUTION_10);if (adc = 2) & (adc = 95) / 85 rightksave0 |= HAL_KEY_RIGHT;else if (adc = 96) & (adc = 110) / 101 centksave0 |= HAL_KEY_CENTER;else if (adc = 111) & (adc = 140) / 127 upksave0 |= HAL_
22、KEY_UP;else if (adc = 141) & (adc = 200) / 170 leftksave0 |= HAL_KEY_LEFT;else if (adc = 201) & (adc = 300) / 257 downksave0 |= HAL_KEY_DOWN; while (ksave0 != ksave1);return ksave0;第二十节 协议栈Flash数据存储CC254x自带了256K Flash,这256K的储存空间不仅可以储存代码,也可以储存用户的数据,协议栈自带了SNV管理代码,我们只需要学会使用即可。SNV的使用只有两个函数,分别是读函数osal_sn
23、v_read和写函数osal_snv_write,在SNV的储存中,储存的每个数据都有一个唯一的ID,SNV也正是利用这个ID来管理储存在Flash中的数据,在BLE的协议栈中,蓝牙自身数据储存用了一部分ID,我们储存的数据ID不可使用这些ID,在bcomdef.h中有这些ID的定义。下面我们往SNV中存入串口接收到的数据,然后开发板断电重启后读取出这串字符串并通过串口发送出去,来演示SNV的断电保存。首先我们定义一个我们储存数据的ID,注意不能和已经有的定义冲突。#define BLE_NVID_USER_CFG_START 0x80 /! Start of the USER Configu
24、ration NV IDs#define BLE_NVID_USER_CFG_END 0x89 /! End of the USER Configuration NV IDs我们在启动事件中读取SNV中0x80的值并通过串口输出读取结果,如果读取成功,则会将读取结果打印到PC端,如果读取失败,则会提示读取失败。在串口接收事件中将接收到的数据存入SNV中,并且也进行相应的提示。将工程编译下载后,可以看到现象如下:第一次上电可以看到,提示读取数据失败了,说明第一次运行时是没有存储数据的,接下来我们通过串口发送字符串 MT254xboard SNV Test字符串。可以看到成功的将我们发送过去的字符
25、存入了SNV中,那是否成功存入呢?我们将开发板断电后重启,看看第二次上电是否能够读取出我们存入的数据。重启后可以发现我们成功的读取出了第一次存入的数据,说明我们成功的将数据存入了SNV中。第十五节 BLE蓝牙4.0协议栈启动分析TI的这款CC2540/CC2541器件可以单芯片实现BLE蓝牙协议栈结构图的所有组件,包括应用程序。从这章开始我们来剖析协议栈源码,我们选用 SimpleBLEPeripheral工程开刀,这是一个从机的例程,基本的工作是对外广播,等待主机来连接,读写展示的属性。首先打开工程文件,打开后可以看到整个工程的结构。我们按照系统的启动顺序来一步一步走,我们都知道在C代码中,
26、一般启动的首个函数为main,这个函数在 SimpleBLEPeripheral_Main.c中,打开文件,可以看到这个文件只有一个main函数和一个函数的申明,我们暂时不理会那个申明的函数,先看main都做了些什么工作:Int main(void)/* Initialize hardware */HAL_BOARD_INIT(); / 硬件初始化/ Initialize board I/OInitBoard( OB_COLD ); / 板级初始化/* Initialze the HAL driver */HalDriverInit(); / Hal驱动初始化/* Initialize NV
27、system */osal_snv_init(); / Flash存储SNV初始化/* Initialize LL */* Initialize the operating system */osal_init_system(); / OSAL初始化/* Enable interrupts */HAL_ENABLE_INTERRUPTS(); / 使能总中断/ Final board initializationInitBoard( OB_READY ); / 板级初始化#if defined ( POWER_SAVING )osal_pwrmgr_device( PWRMGR_BATTERY
28、 ); / 低功耗管理#endif/* Start OSAL */osal_start_system(); / No Return from here 启动OSALreturn 0;通过代码我们可以看到,系统启动的过程,主要是做了一些初始化,如果开启了低功耗,则还需要开启低功耗管理。我们先不去理会初始化做了什么,但是我们知道在main函数的最后启动了OSAL,那么我们就进去看看OSAL是如何运作的。在IAR中如果需要跳转到某个函数或变量的定义,可以在此函数名中右击然后选择Go To Definition就可以调到相应的定义。void osal_start_system( void )#if !
29、defined ( ZBIT ) & !defined ( UBIT )for(;) / Forever Loop#endifosal_run_system();这里看到我们进入了一个死循环,并且一直调用osal_run_system(),那我们再进入此函数。void osal_run_system( void )uint8 idx = 0;#ifndef HAL_BOARD_CC2538osalTimeUpdate(); / 定时器更新#endifHal_ProcessPoll(); / Hal层信息处理do if (tasksEventsidx) / Task is highest pri
30、ority that is ready.break; while (+idx tasksCnt); / 检查每个人任务是否有事件if (idx tasksCnt) / 有事件发生uint16 events;halIntState_t intState;HAL_ENTER_CRITICAL_SECTION(intState); / 进入临界区events = tasksEventsidx;tasksEventsidx = 0; / Clear the Events for this task. 清除事件标志HAL_EXIT_CRITICAL_SECTION(intState); / 退出临界区a
31、ctiveTaskID = idx;events = (tasksArridx)( idx, events ); / 执行事件处理函数activeTaskID = TASK_NO_TASK;HAL_ENTER_CRITICAL_SECTION(intState); / 进入临界区tasksEventsidx |= events; / Add back unprocessed events to the current task.HAL_EXIT_CRITICAL_SECTION(intState); / 退出临界区#if defined( POWER_SAVING ) / 没有事件发生,并且开
32、启了低功耗模式else / Complete pass through all task events with no activity? / 系统进入低功耗模式osal_pwrmgr_powerconserve(); / Put the processor/system into sleep#endif/* Yield in case cooperative scheduling is being used. */#if defined (configUSE_PREEMPTION) & (configUSE_PREEMPTION = 0)osal_task_yield();#endif在这里
33、可以看到这个OSAL的核心,整个OSAL通过检测每个任务是否有事件发生,如果有则执行相应的任务,处理相应的事件。如果没有事件需要处理并且开启了低功耗模式,则系统就会进入低功耗模式。这里有一个很关键的地方,OSAL是如何知道哪个事件需要哪个任务来处理呢?events = (tasksArridx)( idx, events ); / 执行事件处理函数我们看这里有一个很关键的数组tasksArr,很显然,这是一个函数指针数组,我们看看它的定义。const pTaskEventHandlerFn tasksArr =LL_ProcessEvent, / task 0Hal_ProcessEvent,
34、 / task 1HCI_ProcessEvent, / task 2#if defined ( OSAL_CBTIMER_NUM_TASKS )OSAL_CBTIMER_PROCESS_EVENT( osal_CbTimerProcessEvent ), / task 3#endifL2CAP_ProcessEvent, / task 4GAP_ProcessEvent, / task 5GATT_ProcessEvent, / task 6SM_ProcessEvent, / task 7GAPRole_ProcessEvent, / task 8GAPBondMgr_ProcessEve
35、nt, / task 9GATTServApp_ProcessEvent, / task 10SimpleBLEPeripheral_ProcessEvent / task 11;可以看到在这个数组的定义中,每个成员都是任务的执行函数,按照任务的优先级排序,并且在osalInitTasks中初始化的时候,我们可以看到每个任务都有一个对应的初始化函数,并且传递了一个taskID,此ID从0开始自增,这里有一点非常重要,初始化的顺序和任务数组的定义顺序是一样的,这就保证了我们给任务发生消息或事件时能够准确的传递到相应的任务处理函数。void osalInitTasks( void )uint8 t
36、askID = 0;tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt);/* LL Task */LL_Init( taskID+ );/* Hal Task */Hal_Init( taskID+ );/* HCI Task */HCI_Init( taskID+ );#if defined ( OSAL_CBTIMER_NUM_TASKS )/* Callback Timer Tasks */osal_CbTimerInit( taskID );taskID += OSAL_CBTIMER_NUM_TASKS;#endif/* L2CAP Task */L2CAP_Init( taskID+ );/* GAP Task */GAP_Init( taskID+ );/* GATT Task */GATT_Init( taskID+ );/* SM Task */SM_Init(
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024-2025学年度机械设备制造修理人员全真模拟模拟题1套附答案详解
- 2024-2025学年大连汽车职业技术学院单招《语文》真题附参考答案详解(综合题)
- 2024-2025学年度电工通关题库及完整答案详解一套
- 2024-2025学年医学检验(士)过关检测试卷附参考答案详解(综合题)
- 2024-2025学年咨询工程师通关考试题库【模拟题】附答案详解
- 2024-2025学年度护士资格证综合提升测试卷附参考答案详解(综合题)
- 鼻中隔偏曲的物理治疗护理
- 2024-2025学年医师定期考核练习题带答案详解(考试直接用)
- 2024-2025学年化验员考前冲刺练习题及答案详解【全优】
- 就项目合作事宜的确认函6篇范本
- 2026天津宏达投资控股有限公司及所属企业招聘工作人员16人备考题库带答案详解(完整版)
- 学校饮用水污染事件应急报告与管理制度
- 洁净灯具施工方案(3篇)
- 政治试题-汕头市2025-2026学年度普通高中毕业班教学质量监测(含解析)
- 2026-2030中国一次性餐盒行业深度调研及投资前景预测研究报告
- 2026年春苏教版新教材小学科学二年级下册(全册)教学设计(附教材目录P97)
- 注塑岗位安全培训课件
- 2026年考试题库北汽集团高管知识水平测试
- 三项管理制度及生产安全事故应急救援预案
- 核电防异物管理指南(核心版)
- 2026年国家电网招聘之电网计算机考试题库500道含完整答案(历年真题)
评论
0/150
提交评论