CC2540/CC2541蓝牙4.0BLE协议栈开发(连载)
温馨提示:本连载帖子图形文字密切结合,每一步操作都有截图,建议大家注册登录本论坛,这样方能看到图片。
低功耗蓝牙(BluetoothLow Energy),简称BLE。蓝牙低能耗无线技术利用许多智能手段最大限度地降低功耗。
蓝牙2.1+EDR/3.0+HS版本(通常指“标准蓝牙技术”)与蓝牙低能耗(BLE)技术有许多共同点:它们都是低成本、短距离、可互操作的鲁棒性无线技术,工作在免许可的2.4GHz ISM射频频段。
不过它们之间有一个重要区别:蓝牙低能耗技术从一开始就设计为超低功耗(ULP)无线技术,而标准蓝牙技术主要是能够构成“低功耗的”无线连接。
标准蓝牙技术是一种“面向连接”的无线技术,具有固定的连接时间间隔,因此是移动电话连接无线耳机等高活动连接的理想之选。相反,蓝牙低能耗技术采用可变连接时间间隔,这个间隔根据具体应用可以设置为几毫秒到几秒不等。另外,因为BLE技术采用非常快速的连接方式,因此平时可以处于“非连接”状态(节省能源),此时链路两端相互间只是知晓对方,只有在必要时才开启链路,然后在尽可能短的时间内关闭链路。
BLE技术的工作模式非常适合用于从微型无线传感器(每半秒交换一次数据)或使用完全异步通信的遥控器等其它外设传送数据。这些设备发送的数据量非常少(通常几个字节),而且发送次数也很少(例如每秒几次到每分钟一次,甚至更少)。
蓝牙低能耗架构共有两种芯片构成:单模芯片和双模芯片。蓝牙单模器件是蓝牙规范中新出现的一种只支持蓝牙低能耗技术的芯片——是专门针对ULP操作优化的技术的一部分。蓝牙单模芯片可以和其它单模芯片及双模芯片通信,此时后者需要使用自身架构中的蓝牙低能耗技术部分进行收发数据。双模芯片也能与标准蓝牙技术及使用传统蓝牙架构的其它双模芯片通信。
双模芯片可以在目前使用标准蓝牙芯片的任何场合使用。这样安装有双模芯片的手机、PC、个人导航设备(PND)或其它应用就可以和市场上已经在用的所有传统标准蓝牙设备以及所有未来的蓝牙低能耗设备通信。然而,由于这些设备要求执行标准蓝牙和蓝牙低能耗任务,因此双模芯片针对ULP操作的优化程度没有像单模芯片那么高。
单模芯片可以用单节钮扣电池(如3V、220mAh的CR2032)工作很长时间(几个月甚至几年)。相反,标准蓝牙技术(和蓝牙低能耗双模器件)通常要求使用至少两节AAA电池(电量是钮扣电池的10至12倍,可以容忍高得多的峰值电流),并且更多情况下最多只能工作几天或几周的时间(取决于具体应用)。注意,也有一些高度专业化的标准蓝牙设备,它们可以使用容量比AAA电池低的电池工作。
蓝牙4.0已经走向了商用,在最新款的Xperia Z、Galaxy S3、S4、Note2、SurfaceRT、iPhone 5、iPhone 4S、魅族MX3、Moto Droid Razr、HTC One X、小米手机2、The New iPad、iPad 4、 MacBook Air、Macbook Pro,Nokia Lumia系列以及台商ACER AS3951系列/Getway NV57系列,ASUS UX21/31三星NOTE系列上都已应用了蓝牙4.0技术。
接下来我们从环境的搭建到蓝牙4.0协议栈的开发来深入学习蓝牙4.0的开发过程。
本课程所有程序和文档资料均可在以下网盘下载:
http://pan.baidu.com/share/link?shareid=3562495290&uk=3996269986#dir/path=%2F%E6%9C%B1%E5%85%86%E7%A5%BAForARM%2F%E9%A6%92%E5%A4%B4%E7%A7%91%E6%8A%80%E8%93%9D%E7%89%994.0%E7%B3%BB%E5%88%97
目录导航:
第一节 BLE开发环境的搭建
第二节 BLE快速体验
第三节 创建IAR工程-点亮LED
第四节 控制LED
第五节 LCD12864显示
第六节 独立按键之查询方式
第七节 独立按键之中断方式
第八节 CC254x内部温度传感器温度采集
第九节 五向按键
第十节 蜂鸣器
第十一节 串口通信
第十二节 Flash的读写
第十三节 BLE协议栈简介
第十四节 OSAL工作原理
第十五节 BLE蓝牙4.0协议栈启动分析
第十六节 协议栈LED实验
第十六节 协议栈LCD显示
第十七节 协议栈UART实验
第十八节 协议栈五向按键
第十九节 协议栈Flash数据存储
第二十节 DHT11温湿度传感器
第二十一节 蓝牙协议栈之从机通讯
第二十二节 蓝牙协议栈主从一体之主机通讯
第二十三节 OAD空中升级
第二十四节 SBL串口升级
第二十五节 UBL-USB升级
第二十六节 MT-iBeacon基站使用iPhone空中升级
第二十七节 MT-iBeacon基站在PC端实现OAD空中升级
第二十八节 MT-iBeacon基站关于LightBlue软件的使用
第二十九节 如何使用MT-USBDongle的透传功能
第一节 BLE开发环境的搭建
1.1 硬件准备
要进行BLE的开发,首先我们需要一个硬件环境。
(1) MT254xBoard开发板(最好有两块,方便进行数据收发实验);
(2) USBDongle-BLE抓包工具(多个固件,一个硬件多种用途),协议开发时辅助我们分析数据包;
(3) 开发必备CC-Debug,用于下载和调试程序;
在后期的学习中,这些工具我们都会使用到!
1.2 BLE协议栈的安装
我们使用的是最新版本的协议栈BLE-CC254x-1.4.0,首先在配套的资料文件夹中的tools文件夹下找到BLE-CC254x-1.4.0.exe文件。
我们提供了一个安装包和一个免安装的源码,根据我的开发经验,建议使用安装包安装到C盘,直接使用免安装源码在后期的开发中会遇到一些莫名其妙的问题。下面开始安装协议栈,安装方式很简单,和安装软件一样,直接下一步到底即可。
开始安装:
同意安装:
选择C盘:
按照上述步骤,直到完成安装即可。在安装的最后阶段,默认的会安装Btool。
安装BTool:
安装完成:
至此,说明我们已经成功安装了协议栈。完成后将会出现说明文件。
在说明文件中我们可以看到,这个版本的协议栈需要使用IAR for 8051 8.20.2版本的软件。
注:如果使用的是Win8以上的系统建议使用IAR for 8051 8.30.2版本的软件,安装方式和8.20.2是一样的。
下面我们就开始安装这个版本的软件。
1.3 IAR安装
在配套的文件目录下找到如下文件。
安装IAR:
点击是开始安装Dongle驱动。
这样IAR的安装就完成了。先开启软件来体验一下安装成果吧!
1.4 安装烧写软件
解压后可以看到如下文件:
安装此软件即可,安装步骤:
直接下一步到最终即可。
至此,我们目前需要用到的开发软件就安装完成了,来个全家福吧!
支持一下!
第二节 BLE快速体验
经过前面的安装,我们的开发环境已经搭建好了,现在我们先来体验一下BLE,给自己点动力,comeon!使用SmartRFFlash Programmer烧写从机固件:CC2540_SmartRF_SimpleBLEPeripheral.hex,烧写方法见SmartRF Flash Programmer的使用章节。
协议栈默认自带了一些已经编译好的文件,可以直接烧写,具体路径如下图:
从机固件路径:
读取设备的IEEE地址:
烧写完成后,如果你有支持Ble的手机或平板就可以搜索到设备了,或者使用本公司开发的USBDongle(抓包固件或HostTestRelease固件)也可以搜索到设备,具体的使用可以阅读相应的产品使用手册,我这里用andriod平板搜索:
通过MAC地址可以知道我们的设备已经在正常的广播了,我这里使用本公司开发的andriod端软件TruthBlue可以正常搜索到我们的设备。如果用户手上有支持BLE的设备并且系统在andriod4.3以上也可以安装我们的这个软件。
连接上设备后如图,这里我们不要求大家能够看懂这些,这里仅仅是为了体验,后面的章节中我们会详细的讲述这些知识。
表示 很期待更多的介绍,支持!
第三节 创建IAR工程-点亮LED
经过前面的准备工作,这章开始我们开始正式的开发过程。万事开头难,针对MT254xboard开发板的详细介绍参见《MT254xBoard-V1.0-硬件手册.pdf》,在这里我不做详述。
这个教程是为有一定51基础和C基础的人准备的,如果读者这方面还欠缺,请找相关方面的书籍恶补一下。CC2540的本质就是一个8051的单片机,所以我们裸机开发就可以作为一个51单片机来开发,裸机开发的目的是为了让大家熟悉整个硬件以及开发环境,这并不是我们的最终目的,但这是一个必须的过程,为后面开发协议栈奠定基础。
打开我们前面安装的IAR软件,创建一个新的工程:
因为我们使用的CC2540是增强型51单片机,这里我们创建一个空的8051工程,具体配置选项如图:
选择目录保存工程:
我们这里创建一个最简单的例程,点亮一个LED,这个例程就像我们学习每种编程语言是都是先来个Hello World!。虽然简单,但是能够让我们最快的掌握一个开发环境的使用。
新的工程为空工程,没有任何文件,我们这里新建一个文件并且保存为C文件。
添加文件到工程:
保存WorkSpace,在IAR中每个工程都必须要有一个Workspace,而且一个Workspace中可以有多个工程,所以这里我也必须要保存一个Workspace,点击file->save Workspace As就会弹出如下对话框,这里和保存文件一样需要对这个WorkSpace命名,我们这里一样取名LED。
接下来我们需要对工程进行一些配置,使它适应我们的CPU。在工程处右击,进入配置界面。
CPU配置:
这里我们第一个要做的就是选择我们的CPU,我们使用的是TI公司生产的CC2540F256,所以这里选择CC2540F256。配置好CPU后,我们还需要配置编译输出的文件格式,选择到Linker选项,配置如下图:
debug选项:
选项配置:
经过这些配置后,我们可以开始编码了,下面开始编写我们的第一个代码,功能是点亮2个LED,开发板上有两个LED灯,分别对应P1.0和P1.1。
代码如下,可能觉得都是注释,这里我还是建议大家有一个好的编码风格,在开发大项目时就能够看到它的优势。
/****************************************************************************** 版权所有 (C), 2013-2020, 深圳市馒头科技有限公司 ****************************************************************************** 文 件 名 : LED.c 版 本 号 : V1.0 作 者 : 朱兆祺 生成日期 : 2014年6月6日 功能描述 : 点亮一个LED 函数列表 : main 修改历史 : 1.日 期 : 2014年6月6日 作 者 : 朱兆祺 修改内容 : 创建文件 ******************************************************************************/ /*----------------------------------------------* * 包含头文件 * *----------------------------------------------*/ #include <ioCC2540.h> #include "delay.h" /***************************************************************************** 函 数 名 : main 功能描述 : 主函数,C程序入口 输入参数 : void 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月6日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ int main(void) { P1SEL &= ~0X03; // 将P1.1、0设置为IO功能 P1DIR |= 0X03; // 设置P1.1、0为输出功能 while(1) { P1 = (P1 & 0XFC) | 0X01; // 设置P1.0输出高电平 } return 0; }
编写好代码后,就可以编译下载到开发板上了。点击图中所示图标全速运行。
根据原理图,P1.0对应的是LED2,这里我们能够看到LED2处于点亮的状态。
根据CC254X的数据手册,我们可以很快知道P1SEL是设置IO功能,P1DIR是设置输入输出。至于为什么程序是这么写,我们来看下,CC254X芯片的P1口一共有8个IO口,那就是说刚刚好由两位十六进制进行控制:1111 1111(FF),这里仅仅是LED1和LED2,也就是P1.1和P1.0两个IO口,为了不影响其他引脚的使用,我们这里巧妙使用与或控制其功能。比如:P1 = (P1 & 0XFC) | 0X01; P1与上1111 1100,这样不影响其他引脚的基础上,清除了P1.0和P1.1的输出,再或上0X01,这样将P1.0设置为高电平,根据原理图,高电平是点亮LED2.
第四节 控制LED
上一节点亮了单个LED灯,我们这堂课接着控制LED灯。这堂课我们要完成的是LED闪烁10次,蜂鸣器响1s钟。这里我们先使用延时函数进行。
我们的程序一定要做到结构清晰,可移植性强,阅读性高。程序设计不仅仅是实现了功能,如果那样的代码,那只有你自己可以看懂,是一手垃圾。真正的漂亮代码具有阅读性高、可移植性强、代码规范性好。
delay.h:
/****************************************************************************** 版权所有 (C), 2013-2020, 深圳市馒头科技有限公司 ****************************************************************************** 文 件 名 : delay.h 版 本 号 : V1.0 作 者 : 朱兆祺 生成日期 : 2014年06月07日 功能描述 : 主函数 函数列表 : 修改历史 : 1.日 期 : 2014年06月07日 作 者 : 朱兆祺 修改内容 : 创建文件 ******************************************************************************/ #ifndef __DELAY_H__ #define __DELAY_H__ /***************************************************************************** 函 数 名 : Delay1ms 功能描述 : 延时函数 输入参数 : unsigned int uiDelay:延时1ms的数量 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月7日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ extern void Delay1ms(unsigned int uiDelay); #endif /* end file */
延时函数的执行程序delay.c:
/****************************************************************************** 版权所有 (C), 2013-2020, 深圳市馒头科技有限公司 ****************************************************************************** 文 件 名 : delay.c 版 本 号 : V1.0 作 者 : 朱兆祺 生成日期 : 2014年06月07日 功能描述 : 主函数 函数列表 : 修改历史 : 1.日 期 : 2014年06月07日 作 者 : 朱兆祺 修改内容 : 创建文件 ******************************************************************************/ /* 包含delay延时的头文件 */ #include "delay.h" /***************************************************************************** 函 数 名 : Delay1ms 功能描述 : 延时函数 输入参数 : unsigned int uiDelay:延时1ms的数量 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月7日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ void Delay1ms(unsigned int uiDelay) { unsigned int i; for ( ; uiDelay > 0; uiDelay--) { /* 大约延时1ms */ for (i = 0; i < 320; i++); } } /* end file */
主函数其实也很简单:
/****************************************************************************** 版权所有 (C), 2013-2020, 深圳市馒头科技有限公司 ****************************************************************************** 文 件 名 : main.c 版 本 号 : V1.0 作 者 : 朱兆祺 生成日期 : 2014年06月06日 功能描述 : 主函数 函数列表 : 修改历史 : 1.日 期 : 2014年06月06日 作 者 : 朱兆祺 修改内容 : 创建文件 ******************************************************************************/ /* 包含CC254X的头文件 */ #include <ioCC2540.h> #include "delay.h" /***************************************************************************** 函 数 名 : main 功能描述 : 主函数 输入参数 : 无 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月6日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ int main(void) { /* 控制LED灯闪烁 */ unsigned char i; /* 驱动无源蜂鸣器 */ unsigned int j; /* 将P1.0、P1.1设置为IO口 */ P1SEL &= ~0x03; /* 将P1.0、P1.1设置为IO口的输出 */ P1DIR |= 0x03; /* 将P2.0设置为IO口 */ P2SEL &= ~0x01; /* 将P2.0设置为IO口输出 */ P2DIR |= 0x01; /* 主循环 */ while(1) { /* LED1,LED2闪烁10次 */ for (i = 0; i < 10; i++) { /* P1.0----LED2,P1.1----LED1 */ /* P1.0,P1.1输出高电平,即点亮LED2,LED1 */ /* FC : 1111 1100*/ P1 = (P1 & 0xFC) | 0x03; Delay1ms(1000); /* P1.0,P1.1输出低电平,即熄灭LED2,LED1 */ /* FC : 1111 1100*/ P1 = (P1 & 0xFC) & (~0x03); Delay1ms(1000); } /* 给出500HZ的方波驱动 */ for(j = 0; j < 1000; j++) { /* P2.0----蜂鸣器 */ P2 = (P2 & 0xFE) & (~0x01); Delay1ms(1); P2 = (P2 & 0xFE) | 0x01; Delay1ms(1); } } } /* end file */
这里需要注意的是,MT254X蓝牙4.0开发板使用的无源蜂鸣器,那么我们需要产生一个方波来驱动。如这代码:
/* 给出500HZ的方波驱动 */ for(j = 0; j < 1000; j++) { /* P2.0----蜂鸣器 */ P2 = (P2 & 0xFE) & (~0x01); Delay1ms(1); P2 = (P2 & 0xFE) | 0x01; Delay1ms(1); }
如果是有缘蜂鸣器,则没有那么麻烦,直接给出低电平驱动。为什么是低电平,我们看下原理图:
使用的PNP三极管,并且使用续流二极管保护蜂鸣器。
控制LED视频下载地址:
http://pan.baidu.com/share/link?shareid=3562495290&uk=3996269986#dir/path=%2F%E6%9C%B1%E5%85%86%E7%A5%BAForARM%2F%E9%A6%92%E5%A4%B4%E7%A7%91%E6%8A%80%E8%93%9D%E7%89%994.0%E7%B3%BB%E5%88%97%2F%E8%93%9D%E7%89%994.0%E8%AF%BE%E7%A8%8B
第五节 LCD12864显示
上一节我们成功控制了LED和蜂鸣器,这一节我们马不停蹄接着LCD12864的控制。关于LCD12864的手册可以在网盘下载:
http://pan.baidu.com/share/link?shareid=3562495290&uk=3996269986#dir/path=%2F%E6%9C%B1%E5%85%86%E7%A5%BAForARM%2F%E9%A6%92%E5%A4%B4%E7%A7%91%E6%8A%80%E8%93%9D%E7%89%994.0%E7%B3%BB%E5%88%97%2FMT254xBoard%E8%93%9D%E7%89%99%E5%BC%80%E5%8F%91%E6%9D%BF
为了系统能够稳定的工作,首先我们将系统时钟切换到32M的外部晶振,为了自由配置所需要的时钟,主要借助于CLKCONCMD.OSC选择系统主时钟,而借助于CLKCONCMD.OSC32K则用于选择芯片32K时钟源!而低功耗模式设置时,需要借助于SLEEPCMD寄存器,在《CC253x-CC2540-41Applications User's Guide.pdf》中并没有说明SLEEPCMD第二位功能,如下所示:
但是参考cc2430芯片的说明书可以发现,对应的SLEEP寄存器则有说明,如下所示,这个是TI有意隐藏芯片细节,当SLEEPCMD.OSC_PD为0时,32MHz晶振与16MHz RC振荡器都会起振:
对于SLEEPSTA寄存器中BIT6/BIT5说明在cc2530说明书中也并没有说明,可以参考cc2430说明书中内容,其中第6位XOSC_STB表明外部高速32M晶振是否上电并稳定起振,当稳定时该位为1;同样对于第5位HFRC_STB则表明内部16MHz高速RC振荡器是否起振,并是否稳定,当16MHz RC振荡器稳定时该位为1。
void SysStartXOSC(void) { SLEEPCMD &= ~0x04; // 启动所有晶振 while (!(SLEEPSTA & 0x40)); // 等待晶振稳定 CLKCONCMD = (CLKCONCMD & 0x80) | 0x49; // 使用16M晶振作为主时钟 while ((CLKCONSTA & ~0x80) != 0x49 ); // 等待主时钟切换到16M晶振 CLKCONCMD = (CLKCONCMD & ~0x80) ; // 使用外部32K晶振作为休眠时钟 while ( (CLKCONSTA & 0x80) != 0 ); // 等待睡眠时钟切换到外部32K晶振 CLKCONCMD = (CLKCONCMD & 0x80) ; // 使用32M晶振作为主时钟 while ( (CLKCONSTA & ~0x80) != 0 ); // 等待主时钟切换到32M晶振 SLEEPCMD |= 0x04; // 关闭未使用的晶振 }
按照上述方式配置后,我们就可以工作在外部的32M晶振上了,配置好系统时钟和SPI后,剩下的工作只需要按照液晶屏的说明书发送相应的指令就可以将液晶屏驱动起来了,具体的驱动代码详见下一堂课程。这里使用的是ASCII的点阵表,所以只能显示英文,如果需要显示中文,就需要中文字库的支持了。
LCD12864的驱动程序:
/****************************************************************************** 版权所有 (C), 2013-2020, 深圳市馒头科技有限公司 ****************************************************************************** 文 件 名 : Lcd12864.c 版 本 号 : V1.0 作 者 : 朱兆祺 生成日期 : 2014年6月18日 功能描述 : LCD12864驱动 //control P0.1 - LCD_MODE P1.2 - LCD_CS //spi P1.5 - CLK P1.6 - MOSI 函数列表 : 修改历史 : 1.日 期 : 2014年6月18日 作 者 : 朱兆祺 修改内容 : 创建文件 ******************************************************************************/ /*----------------------------------------------* * 包含头文件 * *----------------------------------------------*/ #include <ioCC2540.h> #include "Lcd12864.h" #include "common.h" /*----------------------------------------------* * 宏定义 * *----------------------------------------------*/ /* LCD lines */ #define LCD12864_MAX_LINE 64 #define LCD12864_MAX_ROW 128 #define HAL_LCD_FONT_LINES 8 #define HAL_LCD_FONT_ROWS 6 /* LCD Max Chars and Buffer */ #define HAL_LCD_MAX_LINES (LCD12864_MAX_LINE/HAL_LCD_FONT_LINES) // 6*8点阵最大行数 #define HAL_LCD_MAX_CHARS (LCD12864_MAX_ROW/HAL_LCD_FONT_ROWS) // 6*8点阵最大列数 /* LCD Control lines */ #define HAL_LCD_RS_PORT 0 #define HAL_LCD_RS_PIN 1 #define HAL_LCD_CS_PORT 1 #define HAL_LCD_CS_PIN 2 /* LCD SPI lines */ #define HAL_LCD_CLK_PORT 1 #define HAL_LCD_CLK_PIN 5 #define HAL_LCD_MOSI_PORT 1 #define HAL_LCD_MOSI_PIN 6 // 12864 命令 #define LCD_CMD_DISPLAY_ON 0xAF #define LCD_CMD_DISPLAY_OFF 0xAE #define LCD_CMD_BEGIN_LINE 0x40 #define LCD_CMD_PAGE_LINE 0xB0 #define LCD_CMD_ROW_HIG 0x10 #define LCD_CMD_ROW_LOW 0x00 #define LCD_CMD_READ_STATE 0x00 #define LCD_CMD_ROW_ADDR_NORMAL 0xA0 // 从左到右 #define LCD_CMD_ROW_ADDR_REVERSE 0xA1 // 从右到左 #define LCD_CMD_DISPLAY_NORMAL 0xA6 #define LCD_CMD_DISPLAY_REVERSE 0xA7 #define LCD_CMD_DISPLAY_POINT_ALL 0xA5 #define LCD_CMD_DISPLAY_POINT_NORMAL 0xA4 #define LCD_CMD_BIAS_SET 0xA2 // 0XA2:BIAS=1/9 (常用) 0XA3:BIAS=1/7 #define LCD_CMD_SOFT_RESET 0xE2 #define LCD_CMD_LINE_NORMAL 0xC0 // 从上到下 #define LCD_CMD_LINE_REVERSE 0xC8 // 从下到上 #define LCD_CMD_POWER_ONE 0x2C #define LCD_CMD_POWER_TWO 0x2E #define LCD_CMD_POWER_THREE 0x2F #define LCD_CMD_CONTRAST_ONE_LEVEL 0x22 // 0x20-0x27 #define LCD_CMD_CONTRAST_TWO_CMD 0x81 // 0x00-0x3F #define LCD_CMD_STATIC_PICTURE_ON 0xAD /* SPI interface control */ #define LCD_SPI_BEGIN() HAL_CONFIG_IO_OUTPUT(HAL_LCD_CS_PORT, HAL_LCD_CS_PIN, 0); /* chip select */ #define LCD_SPI_END() \ { \ asm("NOP"); \ asm("NOP"); \ asm("NOP"); \ asm("NOP"); \ HAL_CONFIG_IO_OUTPUT(HAL_LCD_CS_PORT, HAL_LCD_CS_PIN, 1); /* chip select */ \ } /* clear the received and transmit byte status, write tx data to buffer, wait till transmit done */ #define LCD_SPI_TX(x) { U1CSR &= ~(BV(2) | BV(1)); U1DBUF = x; while( !(U1CSR & BV(1)) ); } /* Control macros */ #define LCD_DO_WRITE() HAL_CONFIG_IO_OUTPUT(HAL_LCD_RS_PORT, HAL_LCD_RS_PIN, 1); #define LCD_DO_CONTROL() HAL_CONFIG_IO_OUTPUT(HAL_LCD_RS_PORT, HAL_LCD_RS_PIN, 0); /*全体ASCII 列表:5x7点阵库*/ const static uint8 aucAsciiTable5x7[][5]={ 0x00,0x00,0x00,0x00,0x00,//space 0x00,0x00,0x4f,0x00,0x00,//! 0x00,0x07,0x00,0x07,0x00,//" 0x14,0x7f,0x14,0x7f,0x14,//# 0x24,0x2a,0x7f,0x2a,0x12,//$ 0x23,0x13,0x08,0x64,0x62,//% 0x36,0x49,0x55,0x22,0x50,//& 0x00,0x05,0x07,0x00,0x00,//] 0x00,0x1c,0x22,0x41,0x00,//( 0x00,0x41,0x22,0x1c,0x00,//) 0x14,0x08,0x3e,0x08,0x14,//* 0x08,0x08,0x3e,0x08,0x08,//+ 0x00,0x50,0x30,0x00,0x00,//, 0x08,0x08,0x08,0x08,0x08,//- 0x00,0x60,0x60,0x00,0x00,//. 0x20,0x10,0x08,0x04,0x02,/// 0x3e,0x51,0x49,0x45,0x3e,//0 0x00,0x42,0x7f,0x40,0x00,//1 0x42,0x61,0x51,0x49,0x46,//2 0x21,0x41,0x45,0x4b,0x31,//3 0x18,0x14,0x12,0x7f,0x10,//4 0x27,0x45,0x45,0x45,0x39,//5 0x3c,0x4a,0x49,0x49,0x30,//6 0x01,0x71,0x09,0x05,0x03,//7 0x36,0x49,0x49,0x49,0x36,//8 0x06,0x49,0x49,0x29,0x1e,//9 0x00,0x36,0x36,0x00,0x00,//: 0x00,0x56,0x36,0x00,0x00,//; 0x08,0x14,0x22,0x41,0x00,//< 0x14,0x14,0x14,0x14,0x14,//= 0x00,0x41,0x22,0x14,0x08,//> 0x02,0x01,0x51,0x09,0x06,//? 0x32,0x49,0x79,0x41,0x3e,//@ 0x7e,0x11,0x11,0x11,0x7e,//A 0x7f,0x49,0x49,0x49,0x36,//B 0x3e,0x41,0x41,0x41,0x22,//C 0x7f,0x41,0x41,0x22,0x1c,//D 0x7f,0x49,0x49,0x49,0x41,//E 0x7f,0x09,0x09,0x09,0x01,//F 0x3e,0x41,0x49,0x49,0x7a,//G 0x7f,0x08,0x08,0x08,0x7f,//H 0x00,0x41,0x7f,0x41,0x00,//I 0x20,0x40,0x41,0x3f,0x01,//J 0x7f,0x08,0x14,0x22,0x41,//K 0x7f,0x40,0x40,0x40,0x40,//L 0x7f,0x02,0x0c,0x02,0x7f,//M 0x7f,0x04,0x08,0x10,0x7f,//N 0x3e,0x41,0x41,0x41,0x3e,//O 0x7f,0x09,0x09,0x09,0x06,//P 0x3e,0x41,0x51,0x21,0x5e,//Q 0x7f,0x09,0x19,0x29,0x46,//R 0x46,0x49,0x49,0x49,0x31,//S 0x01,0x01,0x7f,0x01,0x01,//T 0x3f,0x40,0x40,0x40,0x3f,//U 0x1f,0x20,0x40,0x20,0x1f,//V 0x3f,0x40,0x38,0x40,0x3f,//W 0x63,0x14,0x08,0x14,0x63,//X 0x07,0x08,0x70,0x08,0x07,//Y 0x61,0x51,0x49,0x45,0x43,//Z 0x00,0x7f,0x41,0x41,0x00,//[ 0x02,0x04,0x08,0x10,0x20,// 斜杠 0x00,0x41,0x41,0x7f,0x00,//] 0x04,0x02,0x01,0x02,0x04,//^ 0x40,0x40,0x40,0x40,0x40,//_ 0x01,0x02,0x04,0x00,0x00,//` 0x20,0x54,0x54,0x54,0x78,//a 0x7f,0x48,0x48,0x48,0x30,//b 0x38,0x44,0x44,0x44,0x44,//c 0x30,0x48,0x48,0x48,0x7f,//d 0x38,0x54,0x54,0x54,0x58,//e 0x00,0x08,0x7e,0x09,0x02,//f 0x48,0x54,0x54,0x54,0x3c,//g 0x7f,0x08,0x08,0x08,0x70,//h 0x00,0x00,0x7a,0x00,0x00,//i 0x20,0x40,0x40,0x3d,0x00,//j 0x7f,0x20,0x28,0x44,0x00,//k 0x00,0x41,0x7f,0x40,0x00,//l 0x7c,0x04,0x38,0x04,0x7c,//m 0x7c,0x08,0x04,0x04,0x78,//n 0x38,0x44,0x44,0x44,0x38,//o 0x7c,0x14,0x14,0x14,0x08,//p 0x08,0x14,0x14,0x14,0x7c,//q 0x7c,0x08,0x04,0x04,0x08,//r 0x48,0x54,0x54,0x54,0x24,//s 0x04,0x04,0x3f,0x44,0x24,//t 0x3c,0x40,0x40,0x40,0x3c,//u 0x1c,0x20,0x40,0x20,0x1c,//v 0x3c,0x40,0x30,0x40,0x3c,//w 0x44,0x28,0x10,0x28,0x44,//x 0x04,0x48,0x30,0x08,0x04,//y 0x44,0x64,0x54,0x4c,0x44,//z 0x08,0x36,0x41,0x41,0x00,//{ 0x00,0x00,0x77,0x00,0x00,//| 0x00,0x41,0x41,0x36,0x08,//} 0x04,0x02,0x02,0x02,0x01,//~ }; const uint8 asciiTableSize = sizeof( aucAsciiTable5x7 ) / sizeof( aucAsciiTable5x7[0]); /***************************************************************************** 函 数 名 : LCD12864_Cmd 功能描述 : 发送控制命令 输入参数 : uint8 cmd 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年5月28日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ static void LCD12864_Cmd(uint8 cmd) { LCD_SPI_BEGIN(); LCD_DO_CONTROL(); LCD_SPI_TX(cmd); LCD_SPI_END(); } /***************************************************************************** 函 数 名 : LCD12864_Dat 功能描述 : 发送数据 输入参数 : uint8 data 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年5月28日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ static void LCD12864_Dat(uint8 data) { LCD_SPI_BEGIN(); LCD_DO_WRITE(); LCD_SPI_TX(data); LCD_SPI_END(); } void LCD12864_Init(void) { PERCFG |= 0x02; // 设置UART alt2 为 SPI // 配置引脚为SPI功能 HAL_CONFIG_IO_PERIPHERAL(HAL_LCD_CLK_PORT, HAL_LCD_CLK_PIN); HAL_CONFIG_IO_PERIPHERAL(HAL_LCD_MOSI_PORT, HAL_LCD_MOSI_PIN); /* Configure SPI */ U1UCR = 0x80; // 清除原来的数据 U1CSR = 0x00; // SPI 主机模式 // 高位在前,第一个上升沿发送数据,波特率为2M U1GCR = HAL_SPI_TRANSFER_MSB_FIRST | HAL_SPI_CLOCK_PHA_0 | HAL_SPI_CLOCK_POL_LO | 0x0F; U1BAUD = 0xFF; // CS RS 配置为输出 HAL_CONFIG_IO_OUTPUT(HAL_LCD_RS_PORT, HAL_LCD_RS_PIN, 1); HAL_CONFIG_IO_OUTPUT(HAL_LCD_CS_PORT, HAL_LCD_CS_PIN, 1); SoftWaitUs(15000); // 15 ms LCD12864_Cmd(LCD_CMD_SOFT_RESET); //软复位 SoftWaitUs(15000); // 15 ms LCD12864_Cmd(LCD_CMD_POWER_ONE); //升压步聚1 SoftWaitUs(15); // 15 us LCD12864_Cmd(LCD_CMD_POWER_TWO); //升压步聚2 SoftWaitUs(15); // 15 us LCD12864_Cmd(LCD_CMD_POWER_THREE); //升压步聚3 SoftWaitUs(150); // 15 us LCD12864_Cmd(LCD_CMD_CONTRAST_ONE_LEVEL); //粗调对比度,可设置范围0x20~0x27 LCD12864_Cmd(LCD_CMD_CONTRAST_TWO_CMD); //微调对比度 LCD12864_Cmd(0x3a); //0x1a,微调对比度的值,可设置范围0x00~0x3f LCD12864_Cmd(LCD_CMD_BIAS_SET); // 1/9偏压比(bias) LCD12864_Cmd(LCD_CMD_LINE_NORMAL); //行扫描顺序:从上到下 LCD12864_Cmd(LCD_CMD_ROW_ADDR_REVERSE); //列扫描顺序:从左到右 LCD12864_Cmd(LCD_CMD_BEGIN_LINE); //起始行:第一行开始 LCD12864_Cmd(LCD_CMD_DISPLAY_ON); //打开显示 LCD12864_Cmd(LCD_CMD_DISPLAY_POINT_NORMAL); LCD12864_Cmd(LCD_CMD_DISPLAY_NORMAL); //设置为正显模式 SoftWaitUs(150); // 150 us } /***************************************************************************** 函 数 名 : LCD12864_SetAddr 功能描述 : 设置起始地址 输入参数 : uint8 line uint8 col 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月1日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ static void LCD12864_SetAddr(uint8 line, uint8 col) { uint8 ucLine, ucRow; //line += 5; col += 4; if((line >= LCD12864_MAX_LINE) || (col >= LCD12864_MAX_ROW)) { return; } ucLine = LCD_CMD_PAGE_LINE | (line&0x0f); LCD12864_Cmd(ucLine); SoftWaitUs(15); ucRow = LCD_CMD_ROW_HIG | (col>>4); LCD12864_Cmd(ucRow); SoftWaitUs(15); // 15 us ucRow = LCD_CMD_ROW_LOW | (col&0x0f); LCD12864_Cmd(ucRow); SoftWaitUs(15); // 15 us } /***************************************************************************** 函 数 名 : LCD12864_Dis5X8 功能描述 : 将一个字符用5*8的点阵显示 输入参数 : char ch 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月1日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ static void LCD12864_Dis5X8(char ch) { uint8 ucCnt; if((ch >= 0x20)&&(ch < 0x7f)) { uint8 ucChar = ch - 0x20; for(ucCnt=0; ucCnt<5; ucCnt++) { LCD12864_Dat( aucAsciiTable5x7[ucChar][ucCnt]); } //LCD12864_Dat(0x00); } else if(ch==0x00) //不需要显示,清空指定位置 { for(ucCnt=0; ucCnt<5; ucCnt++) { LCD12864_Dat(0x00); } } LCD12864_Dat(0x00); } /***************************************************************************** 函 数 名 : LCD12864_Clear 功能描述 : 清屏 输入参数 : void 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月1日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ void LCD12864_Clear(void) { uint8 ucLine, ucRow; for(ucLine=0; ucLine<LCD12864_MAX_LINE; ucLine++) { LCD12864_SetAddr(ucLine, 0); for(ucRow=0; ucRow<LCD12864_MAX_ROW; ucRow++) { LCD12864_Dat(0x00); } } } /***************************************************************************** 函 数 名 : LCD12864_DisChar 功能描述 : 在指定位置显示一个字符 输入参数 : uint8 line uint8 col char ch 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月1日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ void LCD12864_DisChar(uint8 line, uint8 col, char ch) { if (( line < HAL_LCD_MAX_LINES)&&(col < HAL_LCD_MAX_CHARS)) { LCD12864_SetAddr(line, col*HAL_LCD_FONT_ROWS); LCD12864_Dis5X8(ch); } } /***************************************************************************** 函 数 名 : LCD12864_DisStr 功能描述 : 将字符串显示到指定行 输入参数 : uint8 line 显示的行 0~7 char* pStr 显示的字符串首地址 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月2日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ void LCD12864_DisStr(uint8 line, char* pStr) { uint8 ucCnt = 0; for ( ucCnt = 0 ; ucCnt < HAL_LCD_MAX_CHARS; ucCnt++ ) { if ( '\0' == *pStr ) { break; } LCD12864_DisChar( line, ucCnt, pStr[ucCnt]); } for ( ; ucCnt < HAL_LCD_MAX_CHARS; ucCnt++ ) { LCD12864_DisChar( line, ucCnt, 0); } } /*----------------------------------------------* * end of file * *----------------------------------------------*/
主程序:
/***************************************************************************** 函 数 名 : main 功能描述 : 主函数 输入参数 : 无 输出参数 : 无 返 回 值 : 修改历史 : 1.日 期 : 2014年6月6日 作 者 : 朱兆祺 修改内容 : 创建 *****************************************************************************/ int main(void) { /* 启动外部晶振 */ SysStartXOSC(); /* LCD12864的初始化 */ LCD12864_Init(); /* 清屏 */ LCD12864_Clear(); while(1) { /* 显示字符 */ LCD12864_DisStr(3, "ShenZhenShiManTouKeJi"); } return 0; }
这样我们就点亮的LCD12864屏幕:
很给力,支持一下
第六节 独立按键之查询方式
在MT254xboard上有一个独立按键KEY1,如图 ,独立按键和复位键在整个班子的左上角。按键通过P0.0口和CPU连接,在没有按键时为高电平,按下后为低电平。下面我们通过LCD来显示独立按键的状态。
其对应的原理图如下:
我们先用查询的方式读取按键的状态。因为按键接入在P0.0口,所以我们读取P0.0口的电平即可知道按键的状态。
uint8 KeyValue(void) // 读取按键状态 { if((P0&0X01) == 0X00 ) // 按下为低电平 { return KEY_DOWN; } else { return KEY_UP; } }
这里我们在while循环中不断的读取按键状态,并且判断是否改变,如果改变则改变LCD的显示。
int main(void) { uint8 OldKeyValue = 0; uint8 NewKeyValue = 0; SysStartXOSC(); LCD12864_Init(); LCD12864_DisStr(1, " Key Test"); // 按键初始化 P0SEL &= ~0X01; // 设置为 IO功能 P0DIR &= ~0X01; // 设置为输入功能 while(1) { NewKeyValue = KeyValue(); // 读取按键状态 if(OldKeyValue != NewKeyValue) // 按键状态改变 { OldKeyValue = NewKeyValue; // 保存当前按键状态 if(OldKeyValue == KEY_DOWN) { LCD12864_DisStr(3, " Key Down "); } else { LCD12864_DisStr(3, " Key Up "); } } } return 0; }
运行程序,效果如图所示:
第七节 独立按键之中断方式
复制Key工程,重命名为KeyInterrupt。刚刚我们用查询的方式读取按键的状态。但是这种方式在实际的工程中没有实际的应用价值,下面我们采用外部中断的方式来读取按键的状态,每当按键按下时就会触发一次外部中断。为了P0.0口能够触发中断,我们需要进行如下配置:
P0IEN |= 0X01; // P00 设置为中断方式 PICTL &=~ 0X01; // 下降沿触发 IEN1 |= 0X20; // 允许P0口中断 P0IFG = 0x00; // 清除中断标志位 EA = 1; // 开总中断
然后就需要编写中断服务函数了。这里注意一点,在IAR中的中断函数有点特殊,格式为:
#pragma vector = 中断向量 __interrupt 函数
所以我们的中断函数为:
#pragma vector = P0INT_VECTOR __interrupt void P0_ISR(void) { if(0x01&P0IFG) { NewKeyValue = KEY_DOWN; // 记录按键按下 } P0IFG = 0; //清中断标志 P0IF = 0; //清中断标志 }
在中断中我们记录按键按下,等待应用程序处理。而在主函数中我们需要处理按键按下事件,主函数中我们对按键计数并且通过LCD显示。
int main(void) { char LCDBuf[21]={0}; // 显存 int KeyCnt = 0; SysStartXOSC(); LCD12864_Init(); LCD12864_DisStr(1, " Key Test"); P0SEL &= ~0X01; // 设置为IO功能 P0DIR &= ~0X01; // 设置为输入功能 P0IEN |= 0X01; // P0.0 设置为中断方式 PICTL |= 0X01; // 下降沿触发 IEN1 |= 0X20; // 允许P0口中断 P0IFG = 0x00; // 清除中断标志位 EA = 1; // 开总中断 sprintf(LCDBuf, " Key Count : %d", KeyCnt++); // 按键计数 LCD12864_DisStr(3, LCDBuf); while(1) { if(KEY_DOWN == NewKeyValue) // 按键按下 { SoftWaitUs(25000); // 延时防抖 if((P0&0X01) == 0X00) // 再次确认按键是否按下 { sprintf(LCDBuf, " Key Count : %d", KeyCnt++); // 按键计数 LCD12864_DisStr(3, LCDBuf); } else { NewKeyValue = KEY_UP; // 按键松开 } } } return 0; }
每按一次按键计数加1,效果如图所示:
第八节 CC254x内部温度传感器温度采集
CC254x内部有一个温度传感器,我们这节使用这个传感器来采集芯片的温度,此传感器精度不高。不适合用于实际的工程中,这里只为演示AD采样。要使用内部的温度采集我们需要使用AD采样,所以我们需要先来了解CC254x的AD功能。在后续课程有对ADC的详细说明。
ADC结构图如下所示:
ADC控制寄存器1如下图所示:
我们使用手动触发的方式进行AD采样,所以STSEL = 11B,最低两位始终为1,最终ADCCON1=0x33。
ADC控制寄存器3如图所示:
ADC参考电压使用内部电压,采用12位精度采集。采集温度通道。所以ADCCON3= 0x3E。这里注意一点,ADCCON2和ADCCON3的配置是一样的,我们这里用ADCCON3来配置。
uint16 ADC_Read (uint8 channel) { int16 reading = 0; uint8 adcChannel = 0x01<<channel; int16 Result = 0; if (channel <= 7) // 通道0-7需要通过P0.0-P0.7输入 { ADCCFG |= adcChannel; } uint8 i=0; do{ ADCCON3 = channel | 0x20; // 12位精度,启动转换 while (!(ADCCON1 & 0x80)); // 等待转换完成 // 读取采样结果 reading = (int16)(ADCL); reading |= (int16)(ADCH << 8); reading >>= 4; // 丢弃低位 Result += reading; // 累加 }while(i++ < 10); // 连续采样10次 if (channel <= 7) { ADCCFG &= (adcChannel ^ 0xFF); } return (Result/10); }
在读取温度值前,我们还需要使能温度传感器。
int main(void) { float temp=0; char LCDBuf[21] = {0}; SysStartXOSC(); // 启动外部晶振 LCD12864_Init(); // LCD初始化 // 打开温度传感器 TR0 = 0x01; ATEST = 0x01; while(1) { temp = (ADC_Read(TEMP_ADC_CHANNEL) - 1340) /10.0; sprintf(LCDBuf, " temp : %0.1f", temp); // LCD12864_DisStr(3, LCDBuf); SoftWaitUs(100000); } return 0; }
采集的温度显示在LCD上,可以看到温度在跳动,这是由于AD的误差太大导致的,这里只做一个简单的实验,如果需要工程应用,建议外接温度传感器。把手放在芯片上可以看到温度在上升。温度采集结果如下图所示:
第九节 五向按键
五向按键,也就是我们平常所见的摇杆内部构造,五向按键有上下左右和中间五个按键值,MT254xboard上的五向按键检测电路由馒头科技自主设计,而不是Ti的设计,采用一个外部中断和一个AD检测口来完成按键的检测。
由原理图可知当我们按下不同的键值时在JOY_CHK将会产生一个上升沿,并且在JOY_AD口有不同的电压。我们只需要在JOY_CHK的外部中断中读取JOY_AD的电压即可识别不同的按键。
外部中断和AD采用在前面已经讲过了,这里只需要拿来用就可以了。JOY_CHK连接在P0.7脚,JOY_AD连接在P0.6脚。我们将按键值显示在LCD上。
int main(void) { uint8 KeyValue = 0; SysStartXOSC(); LCD12864_Init(); LCD12864_DisStr(1, " JoyStick Test"); P0INP |= 0X40; // P0.6 三态 P0SEL &= ~0X80; // 设置为IO功能 P0DIR &= ~0X80; // 设置为输入功能 P0IEN |= 0X80; // P0.7 设置为中断方式 PICTL &= ~0X80; // 上升沿触发 IEN1 |= 0X20; // 允许P0口中断 P0IFG = 0x00; // 清除中断标志位 EA = 1; // 开总中断 while(1) { if(KeyStat) // 按键按下 { KeyValue = GetKeyValue(); switch ( KeyValue ) { case KEY_UP : sprintf(LCDBuf, "\tUP"); break; case KEY_DOWN : sprintf(LCDBuf, "\tDown"); break; case KEY_LEFT : sprintf(LCDBuf, "\tLeft"); break; case KEY_CENTER : sprintf(LCDBuf, "\tCenter"); break; case KEY_RIGHT : sprintf(LCDBuf, "\tRight"); break; default: break; } KeyStat =0; LCD12864_DisStr(3, LCDBuf); } } return 0; }
按键的检测通过电压来区分。
uint8 GetKeyValue(void) { uint16 adc; uint8 ksave0 = 0; adc = ADC_Read (JOY_AD_CHANNEL); if ((adc >= 800) && (adc <= 1100)) { ksave0 = KEY_RIGHT; } else if ((adc >= 1200) && (adc <= 2000)) { ksave0 = KEY_CENTER; } else if ((adc >= 2050) && (adc <= 2150)) { ksave0 = KEY_UP; } else if ((adc >= 2200) && (adc <= 2230)) { ksave0 = KEY_LEFT; } else if ((adc >= 2240) && (adc <= 2500)) { ksave0 = KEY_DOWN; } return ksave0; }
使用五向按键效果如下所示:
第十节 蜂鸣器
蜂鸣器是一种常用的报警设备,常用的蜂鸣器有无源和有源两种类型,无源蜂鸣器需要用一定频率的方波驱动,从而发出不同频率的声音。而有源蜂鸣器只需要通电就会发出固定频率的声音,MT254xboard开发板上的蜂鸣器用的是无源蜂鸣器,因此我们需要用一定频率的方波来驱动。
硬件驱动方面,我们这里使用了PNP三极管来驱动蜂鸣器,BUZZ引脚为芯片的P2.0。对照IO复用表可知,此IO可以作为定时器4的匹配通道1输出。所以我们需要把定时器配置为PWM匹配输出模式:
PERCFG |= (0x01<<4); // 选择定时器4匹配功能中的第2种IO口 P2DIR |= 0x01; // p2.0 输出 P2SEL |= 0x01; // p2.0 复用功能 T4CTL &= ~0x10; // Stop timer 3 (if it was running) T4CTL |= 0x04; // Clear timer 3 T4CTL &= ~0x08; // Disable Timer 3 overflow interrupts T4CTL |= 0x03; // Timer 3 mode = 3 - Up/Down T4CCTL0 &= ~0x40; // Disable channel 0 interrupts T4CCTL0 |= 0x04; // Ch0 mode = compare T4CCTL0 |= 0x10; // Ch0 output compare mode = toggle on compare
这里仅仅是配置为匹配输出,具体输出什么样的波形还需要我们再通过计算得出。
void Buzzer_Start(uint16 frequency) { P2SEL |= 0x01; // p2.0 复用功能 uint8 prescaler = 0; // Get current Timer tick divisor setting uint8 tickSpdDiv = (CLKCONSTA & 0x38)>>3; // Check if frequency too low if (frequency < (244 >> tickSpdDiv)){ // 244 Hz = 32MHz / 256 (8bit counter) / 4 (up/down counter and toggle on compare) / 128 (max timer prescaler) Buzzer_Stop(); // A lower tick speed will lower this number accordingly. } // Calculate nr of ticks required to achieve target frequency uint32 ticks = (8000000/frequency) >> tickSpdDiv; // 8000000 = 32M / 4; // Fit this into an 8bit counter using the timer prescaler while ((ticks & 0xFFFFFF00) != 0) { ticks >>= 1; prescaler += 32; } // Update registers T4CTL &= ~0xE0; T4CTL |= prescaler; T4CC0 = (uint8)ticks; // Start timer T4CTL |= 0x10; }
这个函数是通过传入参数的形式,使P2.0口发出指定频率的方波。
void Buzzer_Stop(void) { T4CTL &= ~0x10; // Stop timer 3 P2SEL &= ~0x01; P2_0 = 1; }
这个函数是使蜂鸣器停止,主要有三个动作,停止定时器,将P2.0配置为IO功能并且输出高电平,因为我们使用的是PNP三极管。
我们在按键的程序上加上蜂鸣器的控制,当按下按键时,蜂鸣器响。松开后停止响。
int main(void) { char LCDBuf[21]={0}; // 显存 int KeyCnt = 0; SysStartXOSC(); LCD12864_Init(); LCD12864_DisStr(1, " Buzzer Test"); Buzzer_Init(); P0SEL &= ~0X01; // 设置为IO功能 P0DIR &= ~0X01; // 设置为输入功能 P0IEN |= 0X01; // P0.0 设置为中断方式 PICTL |= 0X01; // 下降沿触发 IEN1 |= 0X20; // 允许P0口中断 P0IFG = 0x00; // 清除中断标志位 EA = 1; // 开总中断 sprintf(LCDBuf, " Key Count : %d", KeyCnt++); // 按键计数 LCD12864_DisStr(3, LCDBuf); while(1) { if(KEY_DOWN == NewKeyValue) // 按键按下 { SoftWaitUs(25000); // 延时防抖 if((P0&0X01) == 0X00) // 再次确认按键是否按下 { sprintf(LCDBuf, " Key Count : %d", KeyCnt++); // 按键计数 LCD12864_DisStr(3, " Buzzer Start"); Buzzer_Start(2000); } else { NewKeyValue = KEY_UP; // 按键松开 Buzzer_Stop(); LCD12864_DisStr(3, " Buzzer Stop"); } } } return 0; }
按下按键后可以看到LCD显示Buzzer Start,听到蜂鸣器响,如果你有示波器,还能测到P2.0口有一个2KHz的方波。
第十一节 串口通信
在软件开发过程中调试是一个很关键的过程,而调试用的最多的手段就是打印Log,嵌入式平台很少有显示设备,所以我们需要将信息通过串口打印到PC端。
MT254xboard上已经通过RS232芯片将UART0连接到DB9,我们只需要将DB9连接到电脑即可,UART0 对应的外部设备 IO 引脚关系为:P0_2------RX,P0_3------TX。
我们需要将这两个IO配置为复用功能,CC2540的USART可以配置为SPI模式或者异步UART模式,这里我们需要配置为异步UART模式。
首先配置IO为UART模式:
PERCFG &= ~0x01; // 配置UART为位置 1 P0SEL = 0x3c; // P0_2,P0_3,P0_4,P0_5用作串口功能 P2DIR &= ~0XC0; // P0 优先作为UART0
配置UART0寄存器,将UART0配置为8N1模式,波特率为115200。
U0CSR |= 0x80; // UART 方式 U0GCR |= 11; // U0GCR与U0BAUD配合 U0BAUD |= 216; // 波特率设为115200 UTX0IF = 0; // 清除中断标志 U0CSR |= 0X40; // 允许接收 IEN0 |= 0x84; // 开总中断,接收中断
这里采用中断方式来接收串口数据,并在中断中回调应用层的接收处理函数。
#pragma vector = URX0_VECTOR __interrupt void UART0_ISR(void) { uint8 ch; URX0IF = 0; // 清中断标志 ch = U0DBUF; if ( NULL != RecvCb ) // 调用回调函数 { RecvCb(ch); } }
为了测试串口的通讯功能,这里我们通过串口接收命令的方式来控制LED的亮灭和蜂鸣器的响和停止,并且显示当前的状态。根据串口输出提示,发送对应字符可以实现相应功能,并且显示状态。
第十二节 Flash的读写
嵌入式系统中需要存储数据,而片内的Flash资源很匮乏,所以我们经常需要使用SpiFlash来存储数据,MT254xboard中板载了一个512Kbyte的Flash,下面我们来驱动此Flash。上一小节中我们用SPI的方式驱动了LCD12864,这节我们继续用SPI来驱动板载的Flash,《GD25Q40.pdf》详细的说明了如何驱动这片Flash,在此不做累述,我们复制LCD12864工程,重命名为SpiFlash,在此工程中添加GD25Q40的两个驱动文件。
下面我们来检测这个Flash,检测的方法为,全部写入0xAA,然后再读出,对比是否为0xAA,如果是,那Flash是没有问题的,否则Flash可能已经有坏块。具体的代码见例程,这个过程所需要的时间取决于我们需要检测的区域大小,如果完全检测,则可能需要几分钟的时间。
int main(void) { SysStartXOSC(); LCD12864_Init(); // LCD初始化 GD25Q40_Init(); // Flash初始化 LCD12864_DisStr(0, "Flash Check...."); sprintf(LCDBuf, "Flash ID :%04X", GD25Q40_ReadID()); // 读取器件ID LCD12864_DisStr(1, LCDBuf); GD25Q40_EraseChip(); // 擦除整片Flash 大约需要10S LCD12864_DisStr(2, "Erase Chip Complete"); uint32 iCnt = 0; // 全部写入0xAA const uint8 Write = 0xAA; for(iCnt=0; iCnt < CHECK_ADDR_RANGE; iCnt++) { GD25Q40_Write(&Write, iCnt, 1); // 写入0xAA } // 读取Flash内部的值,与写入的值对比 uint8 Read; for(iCnt=0; iCnt < CHECK_ADDR_RANGE; iCnt++) { GD25Q40_Read(&Read, iCnt, 1); if(Read != Write) { LCD12864_DisStr(3, "Flash Error"); break; } } // 写入的值与读出的值完全一样 if(iCnt >= CHECK_ADDR_RANGE) { LCD12864_DisStr(3, "Flash Check Success"); } GD25Q40_EraseChip(); // 再次擦除 while(1); return 0; }
MT254X蓝牙4.0开发板Flash效果:
通常只能达到11bit !
第十三节 BLE协议栈简介
TI的协议栈分为两部分:控制器和主机。对于4.0以前的蓝牙,这两部分是分开的。所有profile和应用都建构在GAP或GATT之上。根据这张图,我们从底层开始介绍。TI的这款CC2540器件可以单芯片实现BLE蓝牙协议栈结构图的所有组件,包括应用程序。
1.1.1 PHY层
1Mbps自适应跳频GFSK(高斯频移键控),运行在免证的2.4GHz。
1.1.2 LL层
LL层为RF控制器,控制设备处于准备(standby)、广播、监听/扫描(scan)、初始化、连接,这五种状态中一种。五种状态切换描述为:未连接时,设备广播信息,另外一个设备一直监听或按需扫描,两个设备连接初始化,设备连接上了。发起聊天的设备为主设备,接受聊天的设备为从设备,同一次聊天只能有一个意见领袖,即主设备和从设备不能切换。
1.1.3 HCI层
HCI层为接口层,向上为主机提供软件应用程序接口(API),对外为外部硬件控制接口,可以通过串口、SPI、USB来实现设备控制。
1.1.4 L2CAP层
L2CAP层提供数据封装服务,允许逻辑上的点对点通讯。
1.1.5 SM层
SM层提供配对和密匙分发,实现安全连接和数据交换。
1.1.6 ATT层
ATT层负责数据检索,允许设备向另外一个设备展示一块特定的数据称之为属性,在ATT环境中,展示属性的设备称之为服务器,与它配对的设备称之为客户端。链路层的主机从机和这里的服务器、客服端是两种概念,主设备既可以是服务器