【CC2530入门教程-增强版】基础技能综合实训案例(基础版)-终端源码
广东职业技术学院 欧浩源
一、关于硬件电路
关于这个综合实训案例,具体需求详见《【CC2530入门教程-增强版】基础技能综合实训案例(基础版)-题目需求》。
我自己实在“全国职业院校技能大赛--物联网技术应用赛项”的Zigbee模块上实现的。该模块的电路应该和TI公司官方评估板的推荐电路差不多,我想现在市面上很多开发板也是参考这样的电路设计,只要您使用的开发板上有LED灯、按键输入、串口输出和一路A/D转换,都可以使用我的源码实现这个综合实训,硬件电路引脚连接如下图:
二、程序设计思路
整个测控终端的代码大约有370多行,在这里不能逐一的分析和讲述。各个基础功能模块的操作和《CC2530入门教材》中的案例没有太大差异,其核心代码为主要业务逻辑的处理。这里的主函数不想前面单一功能模块案例的那么简单了,实际上它是一个中断服务与前后台处理的程序结构。既要处理本地的各种功能,又要响应远程的各种命令,不但要管理各个基本任务,而且还要进行运行状态的切换。在功能比较复杂的系统中,可能还需要引入基于状态机思路的事件调度思想或者更高级的程序框架结构。
串口数据接收是测控终端的第一个关键地方。在这里不是要接收一个字节或者一串字符,而是要完整正确接收完8个字节的一帧数据,在数据接收完成后通过一个标志F_DataRecv通知主函数一帧数据接收完成可以进行解析和执行了。命令帧的正确解析和顺利执行是测控终端运行的枢纽,这个部分没有难点,主要是根据通信规约来逐一解析。既然测控终端要采集光照数据和记录设备状态,那么ADC数据采集部分也是一个核心部分,但是没有太大的难点。在进行阈值比较,进行灯光自动开关的时候,不要忘记对照明设备状态的实时登记哦。
三、项目源代码
#include "ioCC2530.h" #define D_SEC P1_1 #define D_DAT P1_0 #define D_AUTO P1_4 #define D_LIGHT P1_3 #define K_LIGHT P1_2 #define K_ALARM P0_1 unsigned char F_Alarm = 0; unsigned char F_Clear = 0; unsigned char F_02Second = 0; unsigned char F_RecvData = 0; unsigned char F_SendInfo = 0; unsigned char ID = 0x01; unsigned char SendDat[8],RecvDat[8]; unsigned char count_t1,count_recv; /*=======================简单的延时函数========================*/ void Delay(unsigned int t) { while(t--); } /*======================端口初始化函数========================*/ void Init_Port() { P1SEL &= ~0x1b; //P1_0、P1_1、P1_3和P1_4作为通用I/O端口 P1DIR |= 0x1b; //P1_0、P1_1、P1_3和P1_4端口输出 P1SEL &= ~0x04; //P1_2作为通用I/O端口 P1DIR &= ~0x04; //P1_2端口输入 P0SEL &= ~0x02; //P0_1作为通用I/O端口 P0DIR &= ~0x02; //P0_1端口输入 D_SEC = 0; //秒闪灯关闭 D_DAT = 0; //数据灯关闭 D_AUTO = 0; //应急灯关闭 D_LIGHT = 0; //照明灯关闭 } /*=======================灯光测试函数========================*/ void Check_Light() { D_SEC = 1; Delay(60000); D_DAT = 1; Delay(60000); D_AUTO = 1; Delay(60000); D_LIGHT = 1; Delay(60000); D_SEC = 0; Delay(60000); D_DAT = 0; Delay(60000); D_AUTO = 0; Delay(60000); D_LIGHT = 0; Delay(60000); } /*========================打开照明灯=========================*/ void Open_Light() { D_LIGHT = 1; //打开照明灯 SendDat[5] |= 0x01; //设置照明灯为打开状态 } /*========================关闭照明灯=========================*/ void Close_Light() { D_LIGHT = 0; //关闭照明灯 SendDat[5] &= ~0x01; //设置照明灯为关闭状态 } /*======================手动控制照明灯=======================*/ void Control_Light() { if(K_LIGHT == 0) //判断按键的信号 { Delay(200); //去抖动 if(K_LIGHT == 0) //确认按键按下 { while(K_LIGHT == 0); //等待按键松开 if((SendDat[5]&0x01) == 0x00) //判断灯光当前状态 { Open_Light(); } else { Close_Light(); } } } } /*=======================定时器1初始化========================*/ void Init_Timer1() { T1CC0L = 0xd4; //设置最大计数值低8位 T1CC0H = 0x30; //设置最大计数值高8位 T1IE = 1; EA = 1; T1CTL = 0x0f; //分频系数是128,正计数/倒计数模式 } /*====================定时器1服务函数========================*/ #pragma vector = T1_VECTOR __interrupt void Timer1_int() { T1STAT &= ~0x20; //清除定时器1的溢出中断标志位 count_t1++; //定时器1溢出一次加1,溢出周期为0.2S F_02Second = 1; if(count_t1 == 5) //定时1秒的时间间隔到。 { D_SEC = ~D_SEC; //切换秒闪灯 count_t1 = 0; } } /*====================ADC0初始化函数========================*/ void Init_ADC0() { P0SEL |= 0x01; //P0_0端口设置为外设功能 P0DIR &= ~0x01; //P0_0端口设置为输入端口 APCFG |= 0x01; //P0_0作为模拟I/O使用 } /*====================ADC0数据读取函数=====================*/ void Read_ADC0() { ADCIF = 0; //参考电压选择AVDD5引脚,256抽取率,AIN0通道0 ADCCON3 = (0x80 | 0x10 | 0x00); while(!ADCIF); //等待A/D转换完成, SendDat[3] = ADCH; //读取ADC数据高位寄存器 SendDat[4] = ADCL; //读取ADC数据低位寄存器 } /*================判断光照度并执行应急照明===============*/ void CheckDataAndExceute() { if(SendDat[3] < 0x06) //光照阈值比较 { D_AUTO = 1; //自动点亮应急灯 SendDat[5] |= 0x02; //标志应急灯打开状态 } else{ D_AUTO = 0; //自动关闭应急灯 SendDat[5] &= ~0x02; //标志应急灯关闭状态 } } /*=====================串口0初始化函数====================*/ void Init_Uart0() { PERCFG = 0x00; //串口0的引脚映射到位置1,即P0_2和P0_3 P0SEL = 0x0C; //将P0_2和P0_3端口设置成外设功能 U0BAUD = 59; //16MHz的系统时钟产生9600BPS的波特率 U0GCR = 9; U0UCR |= 0x80; //禁止流控,8位数据,清除缓冲器 U0CSR |= 0xC0; //选择UART模式,使能接收器 UTX0IF = 0; //清除TX发送中断标志 URX0IF = 0; //清除RX接收中断标志 URX0IE = 1; //使能URAT0的接收中断 EA = 1; //使能总中断 } /*===================串口0接收中断服务函数==================*/ #pragma vector = URX0_VECTOR __interrupt void UR0_SendInt() { unsigned char dat; URX0IF = 0; //清除RX接收中断标志 dat = U0DBUF; //终端接收到一个字节数据 if(dat == 0xaf) //首先判断是不是0xaf { RecvDat[0] = dat; //如果是0xaf则放到帧头的位置 } else if(RecvDat[0] == 0xaf) { RecvDat[count_recv+1] = dat; count_recv++; //接收完数据帧剩下的7个字节 if(count_recv == 7) { count_recv = 0; //帧计数清0 F_RecvData = 1; //标志一个8位的数据帧接收完成 } } } /*====================清空接收缓存函数====================*/ void Clear_RecvData() { unsigned char i; for(i = 0; i < 8; i++) { RecvDat[i] = 0; } } /*===================解析并执行上位机命令==================*/ void ExecuteCmd() { if(RecvDat[0] == 0xaf && RecvDat[7] == 0xfa) { if(ID == RecvDat[1]) //首先判断命令帧的ID和本机ID是否一致 { switch(RecvDat[2]) //然后解析命令域 { case 0x01: //启动数据采集功能 F_SendInfo = 1; D_DAT = 1; break; case 0x02: //关闭数据采集功能 F_SendInfo = 0; D_DAT = 0; break; case 0x03: //远程打开照明灯 Open_Light(); break; case 0x04: //远程关闭照明灯 Close_Light(); break; case 0x0f: //解除现场报警 if(F_Alarm == 1) //只有在已经触发报警的情况下 { F_Clear = 1; //才会解除现场报警 } break; } } } Clear_RecvData(); //解析命令完成后清空接收数据缓存 } /*====================发送单个字节的函数===================*/ void UR0SendByte(unsigned char dat) { U0DBUF = dat; while(!UTX0IF); UTX0IF = 0; } /*=====================发送数据帧的函数====================*/ void UR0SendString(unsigned char *str, unsigned char num) { unsigned char i; for(i = 0; i < num; i++) { UR0SendByte(str[i]); } } /*====================计算校验和的函数====================*/ void CheckSum() { unsigned char i; SendDat[6] = 0; for(i = 0; i < 6; i++) SendDat[6] += SendDat[i]; } /*===================初始化发送缓存函数===================*/ void Init_SendData() { SendDat[0] = 0xaf; SendDat[1] = ID; SendDat[2] = 0xff; SendDat[3] = 0x00; SendDat[4] = 0x00; SendDat[5] = 0x00; SendDat[6] = 0x00; SendDat[7] = 0xfa; } /*===================终端上线命令帧函数===================*/ void SendSysOpenCmd() { Init_SendData(); SendDat[2] = 0x10; CheckSum(); UR0SendString(SendDat, 8); } /*===================上传终端数据的函数===================*/ void Send_Infomation() { SendDat[2] = 0x11; CheckSum(); UR0SendString(SendDat, 8); } /*====================上传报警帧的函数====================*/ void Send_Alarm() { SendDat[2] = 0x1f; CheckSum(); UR0SendString(SendDat, 8); } /*====================秒时间到服务函数====================*/ void WorkFor02Second() { Read_ADC0(); //每秒钟采样一次光照度传感器的电压 if(F_SendInfo == 1) //上传测控终端的数据及设备状态 { Send_Infomation(); } } /*=======================外部中断初始化========================*/ void Init_INTP() { P0IE = 1; //端口0中断使能 P0IEN |= 0x02; //端口P0_1外部中断使能 PICTL |= 0x01; //端口P0_0到P0_7下降沿触发 EA = 1; //使能总中断 } /*====================外部中断服务函数========================*/ #pragma vector = P0INT_VECTOR //外部中断0的向量入口 __interrupt void Int0_Sevice() { P0IFG &= ~ 0x02; //软件清除P0_1端口的标志位 P0IF = 0; //软件清除P0端口的标志位 F_Alarm = 1; //设置报警标志位 Send_Alarm(); //向上位机发送现场报警命令帧 } /*=======================报警灯光函数========================*/ void Alarm_Light() { D_SEC = 1; D_DAT = 1; D_AUTO = 1; D_LIGHT = 1; Delay(60000); Delay(60000); Delay(60000); D_SEC = 0; D_DAT = 0; D_AUTO = 0; D_LIGHT = 0; Delay(60000); Delay(60000); Delay(60000); if(F_Clear == 1) //上位机解除现场报警 { F_Clear = 0; //恢复报警安全锁 F_Alarm = 0; //解除报警,恢复正常工作状态 Check_Light(); //重新检查照明设备状态 SendSysOpenCmd(); //重新发送终端上线命令帧 T1CTL = 0x0f; //重新打开定时器 } } /*===================测控终端初始化函数====================*/ void Init_System() { Init_Port(); //初始化通用I/O端口 Init_INTP(); //初始化外部中断 Init_Timer1(); //初始化定时器1 Init_ADC0(); //初始化ADC0 Init_Uart0(); //初始化串口0 Init_SendData(); //初始化串口发送数据缓存 Clear_RecvData(); //初始化串口接收数据缓存 Check_Light(); //检测照明设备工作状态 SendSysOpenCmd(); //向上位机发送终端上线命令帧 } /*=======================主函数===========================*/ void main() { Init_System(); //测控终端初始化 while(1) { if(F_RecvData == 1) //接收到一个完整的数据帧 { ExecuteCmd(); //解析命令帧并执行功能 F_RecvData = 0; //清除命令帧接收完成标志 } if(F_Alarm == 0) //现场正常工作状态 { Control_Light(); //手动控制照明灯 CheckDataAndExceute(); //自动控制应急灯 if(F_02Second == 1) //定时0.2秒到 { WorkFor02Second(); //执行0.2秒钟应该做的事情 F_02Second = 0; //清除定时0.2秒到的标志 } } else //现场进入报警工作状态 { T1CTL = 0x00; //关闭定时器 F_02Second = 0; //清除定时标志 F_SendInfo = 0; //清除数据发送标志 Alarm_Light(); //开启现场报警灯 } } }