微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 无线和射频 > 射频无线通信设计 > CC2530串口中断调试经验小结

CC2530串口中断调试经验小结

时间:10-02 整理:3721RD 点击:

CC2530串口中断调试经验小结


CC2530是Zigbee模块常用的微控制器芯片,由于项目对网络吞吐量要求较高,最终去掉了Zigbee繁重的协议栈,自己写了简单的广播通信程序。主节点需要接收其他从节点的数据并通过串口上传给上位机。从节点较多的时候上传给上位机的数据非常多。简单一算,原来代码的小半时间都用来等待串口数据发送完成了。所以打算使用串口发送中断来完成串口数据的发送过程。
首先给出原来的串口收发代码,这也是大部分单片机串口程序的思想:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
// 全局变量  
char serial_rxbuf[128];       // 串口接收缓冲区  
char serial_txbuf[128];       // 串口发送缓冲区  
int  serial_rxpos = 0;  
int  serial_rxlen = 0;  
void uart0_init(void)  
{  
    PERCFG = 0x00;              // UART0 选择位置0 TX@P0.3 RX@P0.2  
    P0SEL |= 0x0C;              // P0.3 P0.2选择外设功能  
    U0CSR |= 0xC0;              // UART模式 接收器使能  
    U0GCR |= 11;                // 查表获得 U0GCR 和 U0BAUD  
    U0BAUD = 216;               // 115200  
    UTX0IF = 1;  
    URX0IE = 1;                 // 使能接收中断 IEN0@BIT2  
}  
#pragma vector=URX0_VECTOR  
__interrupt void UART0_ISR(void)  
{  
    URX0IF = 0;                                   // 清除接收中断标志  
    serial_rxbuf[serial_rxpos] = U0DBUF;          // 填充缓冲区  
    serial_rxpos++;  
    serial_rxlen++;  
}  
void uart0_sendbuf(char *pbuf , int len)  
{  
  memcpy(serial_txbuf, pbuf, len);      // 复制数据到缓冲区  
  pbuf = serial_txbuf;  
    for( int i = 0 ; i < len ; i++)  
    {  
        while(!UTX0IF);             // 等待发送完成  
        UTX0IF = 0;             // 清楚发送中断标志  
        U0DBUF = *pbuf;             // 写入发送字节  
        pbuf++;  
    }  
}  
上面的程序中,串口数据接收在中断中进行,发送数据时每次都需要等待发送完成后再写入下一个字节。本程序波特率为115200,这意味发送15个字节就需要大约1ms,而1ms时间内,可能就有两个从节点广播了数据,主节点需要接收这两个包。虽然RF数据接收是在中断中进行的,但是接收下来的数据处理却在主函数中,而主函数被串口发送循环阻塞。最终导致先收到的RF数据被覆盖。怎么才能让单片机向串口自动发送一段数据呢?好在CC2530中还有一个串口发送中断。下面是发送中断的代码:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
char serial_rxbuf[SERIAL_BUF_SIZE];       // 串口接收缓冲区  
char serial_txbuf[SERIAL_BUF_SIZE];       // 串口发送缓冲区  
volatile int serial_rxpos = 0;            // 串口接收数据位置  
volatile int serial_rxlen = 0;            // 串口接收数据长度  
volatile int serial_txpos = 0;            // 串口发送数据位置  
volatile int serial_txend = 0;            // 串口发送数据  
void uart0_init(void)  
{  
    PERCFG = 0x00;              // UART0 选择位置0 TX@P0.3 RX@P0.2  
    P0SEL |= 0x0C;              // P0.3 P0.2选择外设功能  
    U0CSR |= 0xC0;              // UART模式 接收器使能  
    U0GCR |= 11;                // 查表获得 U0GCR 和 U0BAUD  
    U0BAUD = 216;               // 115200  
    UTX0IF = 0;  
    URX0IE = 1;                 // 使能接收中断 IEN0@BIT2  
    IEN2 |= 0x04;               // 使能发送中断  
}  
void uart0_sendbuf(char *pbuf , int len)  
{  
  int left;  
  int end = serial_txend;  
  if (len <= 0) return;  
  // 计算剩余空间  
  if (serial_txpos <= end)  
  {  
    left = serial_txpos - end + SERIAL_BUF_SIZE;  
  } else {  
    left = serial_txpos - end;  
  }  
  if (len < left)                               // 如果空间不够将直接退出  
  {  
    int len1 = SERIAL_BUF_SIZE-end;  
    if (len <= len1) {  
      memcpy(serial_txbuf+end, pbuf, len);         // 复制数据到尾部  
    } else {  
      memcpy(serial_txbuf+end, pbuf, len1);        // 复制部分数据到尾部  
      memcpy(serial_txbuf, pbuf+len1, len-len1);   // 复制另一部分数据到头部  
    }  
    if (serial_txpos == end) {  
      U0DBUF = serial_txbuf[end];                 // 如果缓冲区为空,写入待发送的第一个字节到串口寄存器中,否则数据将不会被发送  
    }  
    // 更新缓冲区尾部位置  
    if (end+len >= SERIAL_BUF_SIZE) serial_txend = end+len-SERIAL_BUF_SIZE;  
    else serial_txend = end+len;  
  }  
}  
#pragma vector=URX0_VECTOR  
__interrupt void URX0_ISR(void)  
{  
    int pos;  
    URX0IF = 0;   
    pos = serial_rxpos;  
    if (pos >= SERIAL_BUF_SIZE) {  
      is_serial_receive = 1;  
    } else {  
      serial_rxbuf[pos] = U0DBUF;  
      serial_rxpos = pos+1;  
      serial_rxlen++;  
    }  
}  
#pragma vector=UTX0_VECTOR  
__interrupt void UTX0_ISR(void)  
{  
    int pos;  
    UTX0IF = 0;                                 // 清除接收中断标志  
    pos = serial_txpos;  
    pos++;  
    if (pos >= SERIAL_BUF_SIZE)   
      pos -= SERIAL_BUF_SIZE;  
    if (pos == serial_txend) {  
      serial_txpos = pos;                  // 缓冲区为空,发送结束  
    } else {  
      U0DBUF = serial_txbuf[pos];          // 写入下一个发送字节  
      serial_txpos = pos;  
    }  
}  
串口接收没变,主要添加了一个串口发送中断,该中断会在每次发送完成后置位串口发送中断标志寄存器UTX0IF,并触发中断。中断程序中判断发送字节是否为最后一个字节,若不是,则将继续写入下一个发送字节。uart0_sendbuf函数只是将发送数据写入到发送缓冲区中,实际的发送过程主要由串口发送中断完成。
用这个串口收发代码写了个回显的程序,使用串口助手进行测试,串口助手大约每20ms发送50个字节,但是大约几分钟后就收不到任何数据。然后检查代码,首先发现一个问题:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
if (serial_txpos == end) {  
  U0DBUF = serial_txbuf[end];       // 如果缓冲区为空,写入待发送的第一个字节到串口寄存器中,否则数据将不会被发送  
}  
// 更新缓冲区尾部位置  
if (end+len >= SERIAL_BUF_SIZE) serial_txend = end+len-SERIAL_BUF_SIZE;  
else serial_txend = end+len;  
这里先判断发送缓冲区是否为空,然后写入发送数据。但是如果在判断时正在发送最后一个字节,由于最后一个字节没有发完,不会写串口寄存器。在更新缓冲区尾部位置之前,发送完成了,进入中断函数后认为缓冲区为空,也不会再写串口寄存器了。然后就不会发送新写入的数据。更不幸的是,假如缓冲区已经被写满了,后面再调用uart0_sendbuf函数将不做任何处理直接退出,结果就是永远都发不了数据。分析到此后,觉得差不多了,就把条件判断语句和更新缓冲区尾部位置的代码换了下,即:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
    // 更新缓冲区尾部位置  
    if (end+len >= SERIAL_BUF_SIZE) serial_txend = end+len-SERIAL_BUF_SIZE;  
    else serial_txend = end+len;  
    if (serial_txpos == end) {  
      U0DBUF = serial_txbuf[end];    // 如果缓冲区为空,写入待发送的第一个字节到串口寄存器中,否则数据将不会被发送  
    }  
然而,测试结果没有改变,只能继续看代码。发现下面这段代码存在问题:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
// 计算剩余空间  
if (serial_txpos <= end)  
{  
  left = serial_txpos - end + SERIAL_BUF_SIZE;  
} else {  
  left = serial_txpos - end;  
}  
由于serial_txpos变量使用了volatile关键词修饰,因此每次取值都将从存储器中取。而如果在判断条件之后进入了中断,改变了serial_txpos的值后,left可能被设置为SERIAL_BUF_SIZE+1;因为缓冲区空的条件(serial_txpos==serial_txend)和缓冲器满的条件刚好相差了1,后续逻辑将完全错误。修改方法也很简单:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
// 计算剩余空间  
int pos = serial_txpos;  
if (pos <= end)  
{  
  left = pos - end + SERIAL_BUF_SIZE;  
} else {  
  left = pos - end;  
}  
这时总该没问题了吧,然后结果虽然有所改善,但是运行十到二十分钟后收到的数据还是乱了,最后过了好久又收不到数据了(具体多久我也不清楚,晚上跑的程序,第二天起来发现接收停止了)。无论如何,还是存在问题的,不过本人水平有限,实在查不出来了。无奈之下只能使出杀手锏——关中断。代码如下:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
void uart0_sendbuf(char *pbuf , int len)  
{  
  int left;  
  int pos;  
  int end = serial_txend;  
  if (len <= 0) return;  
  // serial_txpos may be changed in UART0_ISR  
  pos = serial_txpos;  
  if (pos <= end)                               // 如果空间不够将直接退出  
  {  
    left = pos - end + SERIAL_BUF_SIZE;  
  } else {  
    left = pos - end;  
  }  
  if (len < left)  
  {  
    IEN2 &= ~0x04;                    // 暂时关闭串口发送中断  
    int len1 = SERIAL_BUF_SIZE-end;  
    if (len <= len1) {  
      memcpy(serial_txbuf+end, pbuf, len);        // 复制数据到尾部  
    } else {  
      memcpy(serial_txbuf+end, pbuf, len1);       // 复制部分数据到尾部  
      memcpy(serial_txbuf, pbuf+len1, len-len1);  // 复制另一部分数据到头部  
    }  
    if (serial_txpos == end) {  
      U0DBUF = serial_txbuf[end];   // 如果缓冲区为空,写入待发送的第一个字节到串口寄存器中,否则数据将不会被发送  
    }  
    // 更新缓冲区尾部位置  
    if (end+len >= SERIAL_BUF_SIZE) serial_txend = end+len-SERIAL_BUF_SIZE;  
    else serial_txend = end+len;  
    IEN2 |= 0x04;               // 开启串口发送中断  
  }  
}  
这时测试终于没问题了,无论快速发送数据(发送缓冲区长期满)还是低速发送数据(发送缓冲区长期空)都能连续跑好几个小时完全正常。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top