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,如有侵权,请联系删除。

posted @ 2022-04-15 20:50  yassine  阅读(437)  评论(0编辑  收藏  举报