CC2530串口通信中如何接收上位机的一串字符串
这几天苦于学习zigbee,也才是刚入门.在掌握8051的基础下,首先得调通2530的串口,不然怎么对得起这芯片呢?
说真的,学习zstack真的是让人崩溃的一件事情,当你对着资料连续看了两天也找不到下手的地方的时候,真的是有一种欲哭无声,有气生不出来的感觉.
我是想实现这样一种功能:
zigbee节点通过串口连接GPRS DTU向服务器定时(1min)发送请求,请求一个json字符串,这个字符串中包含了一个配肥料的信息.zigbee节点通过json中的参数配置肥料,完成后向服务器发送confirm请求,完成一个订单. 然后,又开始间隔向服务器请求别的订单.
是的,这样一个程序,我本打算用串口小程序先模拟出来,但是遇到一个问题:串口发送请求后,服务器响应json给zigbee节点,节点总会无缘无故抽风,要么收不到数据,要么收的数据乱码,要么收到的数据解析的时候没有结果,发送null给服务器.
就这样的问题,我调试了N久,基础代码调不通,就闷头回来学习zstack,学习一两个小时,代码看懂了就是无从下手,又返回去看基础代码.....
来来回回,我的生命就这样一点点被消耗...
然后我又重新设计程序流程图,重新写代码,,,,,,,又来来回回,调试了几遍,还是没有结果.最后一遍,我静下心来,一点一点地思考,一点一点地修改.终于调试通了.
事后,我总结了一下自己的问题:
1.基础代码还没完全摸透,zigbee硬件的特性还没有完全熟悉,就去摸zstack,肯定行不通,而且zstack这玩意儿就算你是天才也不是一两天就摸得透的.
2.串口通信过程中,串口发送接收的特性,串口中断函数的执行方式等,如果这两个特性摸不透,根本看不懂别人写的串口代码的逻辑.
我分析了以下串口通信的原理:
首先是设备上电,初始化串口,将设备和电脑通过串口相连接(电脑先模拟GPRS,调试).
电脑发送abcde给串口.
串口的特性是按位按序列接收数据.比如a的序列是11000110(比喻),串口首先按位接收这个字符,收到后将这个序列存到U0DBUF(用串口0)中,即缓冲区,然后产生中断,执行中断函数,中断执行完毕后,将URX0IF中断寄存器设置为0,关闭中断,然后返回执行main.这个时候又会接收第二个b字符,又产生中断,直到接收数据完成.才不产生其它中断了.
中断函数大概是这样的:
/**********************/
/***串口中断函数 ***/
#pragma vector = URX0_VECTOR
__interrupt void UART0_ISR(void)
{
URX0IF = 0;
recFlag = U0DBUF;
//从U0DBUF获取数据
RecData[recIndex++] = U0DBUF;
}
其中RecData是数组,作为数据缓冲区,recIndex是数组索引,都是全局变量,没产生一次中断,就会把中断的一个byte存到数组中,最后就可以产生一个完整的字符串.
最后,要学会串口,还需要弄明白两个函数:
1.串口初始化函数
2.串口发送字符串的函数
初始化函数:
/****************/
/***初始化串口 **/
/****************/
void initUART0(void)
{
CLKCONCMD &= ~0x40; //设置时钟源,32MHz晶振
while(CLKCONSTA & 0x40); //等待晶振稳定工作
CLKCONCMD &= ~0x47; //设置系统主时钟频率 ,32MHz
PERCFG = 0x00; //位置1 P0口
P0SEL = 0x0c; //P0用于串口
P2DIR &= ~0XC0; //P0优先作为UART0
U0CSR |= 0x80; //串口设置为UART方式
U0GCR |= 8;
U0BAUD |= 59; //波特率设为9600
UTX0IF = 1; //UART0 TX中断标志初始置位1
URX0IF = 0;
U0CSR |= 0X40; //允许接收
IEN0 |= 0x84; //开总中断,接收中断
}
/****************/
/***初始化LED **/
/****************/
void initLED(void)
{
//P1控制led
P1DIR = 0x02;
LED = 0; //点亮LED;
}
/****************/
/***延时函数ms **/
/****************/
void Delay(uint n)
{
int i,j;
for(i=0; i<n; i++)
{
for(j=0; j<560; j++)
;
}
}
初始化串口的大致顺序是:1首先设置外设功能,比如上述设置P0的P02 P03为外设功能,并通过P2设置P02 P03优先作为UART0,然后通过U0CSR寄存器设置串口为异步模式UART.然后设置波特率,收发中断标志,以及收发允许等.最后一项为开总中断,因为设置了串口中断,不开中断将不会产生任何串口收发的中断.以上对应的寄存器在cc2530芯片手册中都有详细的描述.我就不赘述了.
发送字符串函数:
/****************/
/***发送字符串 **/
/****************/
void Send_To_Server(uchar *data,uint len)
{
int j;
for(j=0;j<len;j++)
{
U0DBUF = *data++;
while(UTX0IF == 0);
UTX0IF = 0;
}
}
发送数据也是一个字符一个字符地发送,将数据写入U0DBUF,数据将会自动被发送到串口中去.这里注意一下,有一个循环,循环是等UTX0IF为1才继续执行,因为如果UTX0IF为1,说明产生了发送字符的中断函数,即字符确保被发送出去了.然后关中断,继续发送其它的.这里还需要注意一下U0DBUF是一个双向缓冲器,既可以用于发送也可以用于接收.因此,发送和接收必须要保证原子性,即发送一个字符串的时候不能同时产生接收的动作.因此发送之前,务必使用寄存器保证这时不在接收字符,等发送完毕后,在开启接收功能.
最后附上我的基础程序:
# include <iocc2530.h>
# include <string.h>
#define uint unsigned int
#define uchar unsigned char
//灯亮的端口
#define LED P1_1 //p11控制D2亮和灭
//声明需要用到的全局变量等
uchar token[17] = "machinetoken=123"; //令牌
uchar and[2] = "&"; //http参数连接字符
uchar oid_front[9] = "orderid="; //http参数订单号前缀
uchar rs_front[8] = "result="; //http参数结果前缀
uchar txd[50];//即将发送给服务器的数据
uchar txFlag = 1;//发送标志,是否发送请求
uchar RecData[110];
uint recIndex = 0;
uchar recFlag = 0;//中断函数从服务器拿到数据时,这个应该不为0
/***这些参数是oid,ferta,fertb,fertc等参数,应该为数字,是从服务器发送过来的json字符串中解析出来的*/
uchar oid[5]; //总订单数不可能超过10000吧?
uchar ferta[4];
uchar fertb[4];
uchar fertc[4];
/***这参数是终端设备处理完成后生成的结果号*/
uchar rsid[2];
void Delay(uint n); //延时函数
void initUART0(void); //初始化串口
void initLED(void); //初始化灯
void Send_To_Server(uchar *data,uint len);
void resolveOid(uchar *str);
void main(void)
{
//首先延时2s
//Delay(2000);
//首先初始化串口,在初始化LED
initUART0();
initLED();
//初始化完成进入循环
while(1)
{
Delay(100);
if(recFlag == '}')
{
//首先接收数据获取订单号,ferta,fertb,fertc等四个数据,此处只获取一个数据即可
resolveOid(RecData);
//处理数据
int i=0;
for(;i<10;i++)
{
LED = !LED;
Delay(500);
}
//.......
rsid[0] = '1';
//.......
//生成处理结果字符串
strcat(txd,token);
strcat(txd,and);
strcat(txd,oid_front);
strcat(txd,oid);
strcat(txd,and);
strcat(txd,rs_front);
strcat(txd,rsid);
strcat(txd,and);
if(recIndex > 20)
{
//响应服务器
U0CSR &= ~0x40; //禁止接收
Delay(3000);
Send_To_Server((char *)txd,50);
Delay(3000);
U0CSR |= 0x40; //允许接收
//清除字符串
}
memset(txd,0,50);
//进入发送状态
//清除recFlag状态
recFlag = 0;
//一次请求和回复完成后,需要重置接收数据的缓存.
recFlag = 0;
memset(RecData,0,110);
recIndex = 0;
}
else
{
Delay(2000); //消除时延
if(recFlag == 0)
{
U0CSR &= ~0x40;
Send_To_Server((char *)token,17);
U0CSR |= 0x40;
}
memset(txd,0,50);
//清除recFlag状态
recFlag = 0;
//一次请求和回复完成后,需要重置接收数据的缓存.
recFlag = 0;
memset(RecData,0,110);
recIndex = 0;
}
Delay(10);
}
}
/**从服务器数据中解析出oid****/
void resolveOid(uchar *str)
{
uchar tmp;
int serverIndex = 10;//解析json字符串的索引位置
int index = 0;//字符串索引
for(; serverIndex<15; serverIndex++) //获取订单号
{
tmp = str[serverIndex];
if(tmp <= '9' && tmp >= '0')
{
oid[index++] = tmp;
}
}
}
/****************/
/***初始化串口 **/
/****************/
void initUART0(void)
{
CLKCONCMD &= ~0x40; //设置时钟源,32MHz晶振
while(CLKCONSTA & 0x40); //等待晶振稳定工作
CLKCONCMD &= ~0x47; //设置系统主时钟频率 ,32MHz
PERCFG = 0x00; //位置1 P0口
P0SEL = 0x0c; //P0用于串口
P2DIR &= ~0XC0; //P0优先作为UART0
U0CSR |= 0x80; //串口设置为UART方式
U0GCR |= 8;
U0BAUD |= 59; //波特率设为9600
UTX0IF = 1; //UART0 TX中断标志初始置位1
URX0IF = 0;
U0CSR |= 0X40; //允许接收
IEN0 |= 0x84; //开总中断,接收中断
}
/****************/
/***初始化LED **/
/****************/
void initLED(void)
{
//P1控制led
P1DIR = 0x02;
LED = 0; //点亮LED;
}
/****************/
/***延时函数ms **/
/****************/
void Delay(uint n)
{
int i,j;
for(i=0; i<n; i++)
{
for(j=0; j<560; j++)
;
}
}
/****************/
/***发送字符串 **/
/****************/
void Send_To_Server(uchar *data,uint len)
{
int j;
for(j=0;j<len;j++)
{
U0DBUF = *data++;
while(UTX0IF == 0);
UTX0IF = 0;
}
}
/**********************/
/***串口中断函数 ***/
#pragma vector = URX0_VECTOR
__interrupt void UART0_ISR(void)
{
URX0IF = 0;
recFlag = U0DBUF;
//从U0DBUF获取数据
RecData[recIndex++] = U0DBUF;
}
非常简单,大概就是不断轮询发送数据给上位机.
一旦上位机发送数据给下位机
下位机便处理接收到的数据,在把处理完毕的数据发送给上位机,然后又开始新的轮询.
数据实例:
zb发送:
machinetoken=123
server response:
{"orderId":60,"orderNumber":12233331,"creTime":"2017-11-13","fertA":2,"fertB":3,"fertC":4,"crePersonId":3}
zb处理后发送:
machinetoken=123&orderid=60&result=1
server response:
{"result":"true"}
在上述过程中,如果上位机回应给zb的数据是{"result":"false"},则zb节点不做任何回应,开始新一轮的轮询.轮询的间隔时间是可以控制的.
最后说说我做这个干嘛:
用手机发送一个配置肥料的订单到服务器,zb从服务器获取订单,然后配置肥料.
当然这是一个简单的demo,并不是真正的项目,目的是用于验证项目的可行性,为毕业设计打好基础.
转载于:https://my.oschina.net/qkmc/blog/1575260
本文转自 link,如有侵权,请联系删除。