CC2530串口中断
2014-04-26 20:00
204 查看
CC2530是Zigbee模块常用的微控制器芯片,由于项目对网络吞吐量要求较高,最终去掉了Zigbee繁重的协议栈,自己写了简单的广播通信程序。主节点需要接收其他从节点的数据并通过串口上传给上位机。从节点较多的时候上传给上位机的数据非常多。简单一算,原来代码的小半时间都用来等待串口数据发送完成了。所以打算使用串口发送中断来完成串口数据的发送过程。
首先给出原来的串口收发代码,这也是大部分单片机串口程序的思想:
上面的程序中,串口数据接收在中断中进行,发送数据时每次都需要等待发送完成后再写入下一个字节。本程序波特率为115200,这意味发送15个字节就需要大约1ms,而1ms时间内,可能就有两个从节点广播了数据,主节点需要接收这两个包。虽然RF数据接收是在中断中进行的,但是接收下来的数据处理却在主函数中,而主函数被串口发送循环阻塞。最终导致先收到的RF数据被覆盖。怎么才能让单片机向串口自动发送一段数据呢?好在CC2530中还有一个串口发送中断。下面是发送中断的代码:
用这个串口收发代码写了个回显的程序,使用串口助手进行测试,串口助手大约每20ms发送50个字节,但是大约几分钟后就收不到任何数据。然后检查代码,首先发现一个问题:
这里先判断发送缓冲区是否为空,然后写入发送数据。但是如果在判断时正在发送最后一个字节,由于最后一个字节没有发完,不会写串口寄存器。在更新缓冲区尾部位置之前,发送完成了,进入中断函数后认为缓冲区为空,也不会再写串口寄存器了。然后就不会发送新写入的数据。更不幸的是,假如缓冲区已经被写满了,后面再调用uart0_sendbuf函数将不做任何处理直接退出,结果就是永远都发不了数据。分析到此后,觉得差不多了,就把条件判断语句和更新缓冲区尾部位置的代码换了下,即:
然而,测试结果没有改变,只能继续看代码。发现下面这段代码存在问题:
这时测试终于没问题了,无论快速发送数据(发送缓冲区长期满)还是低速发送数据(发送缓冲区长期空)都能连续跑好几个小时完全正常。
首先给出原来的串口收发代码,这也是大部分单片机串口程序的思想:
// 全局变量 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中还有一个串口发送中断。下面是发送中断的代码:
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个字节,但是大约几分钟后就收不到任何数据。然后检查代码,首先发现一个问题:
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函数将不做任何处理直接退出,结果就是永远都发不了数据。分析到此后,觉得差不多了,就把条件判断语句和更新缓冲区尾部位置的代码换了下,即:
// 更新缓冲区尾部位置 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]; // 如果缓冲区为空,写入待发送的第一个字节到串口寄存器中,否则数据将不会被发送 }
然而,测试结果没有改变,只能继续看代码。发现下面这段代码存在问题:
// 计算剩余空间 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,后续逻辑将完全错误。修改方法也很简单:
// 计算剩余空间 int pos = serial_txpos; if (pos <= end) { left = pos - end + SERIAL_BUF_SIZE; } else { left = pos - end; }这时总该没问题了吧,然后结果虽然有所改善,但是运行十到二十分钟后收到的数据还是乱了,最后过了好久又收不到数据了(具体多久我也不清楚,晚上跑的程序,第二天起来发现接收停止了)。无论如何,还是存在问题的,不过本人水平有限,实在查不出来了。无奈之下只能使出杀手锏——关中断。代码如下:
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; // 开启串口发送中断 } }
这时测试终于没问题了,无论快速发送数据(发送缓冲区长期满)还是低速发送数据(发送缓冲区长期空)都能连续跑好几个小时完全正常。
相关文章推荐
- cc2530入门 与串口中断处理
- CC2530 输入输出配置、中断配置、时钟、串口配置
- CC2530实现M3650B-HA主动模式读卡(串口中断接收)
- cc2530在ZSTACK中如何开启双串口模式(UART1中断方式)
- CC2530串口
- PIC32MX450 串口4中断
- STM32串口中断接收一个完整的数据帧
- STM32串口中断卡死主循环问题分析
- STM32利用串口空闲中断在串口DMA配置下接收变长数据
- 用C语言中断方式实现串口的读写
- s3c2440串口接收中断(OK2440III)
- wince串口线程、中断等相关学习(作者:wogoyixikexie@gliet)
- ZIGBEE(CC2530,ZSTACK)串口波特率自适应
- 【51单片机学习过程记录】16 中断之 串口中断的应用2(中断方式)
- CC2530定时器3通道1输入捕获中断
- 初识stm32-----串口1的中断收发
- STM8S处理串口中断注意
- CC2530串口接收总结
- 串口使用中断发送字符串,并在LCD显示内容
- 使用Msp430的串口中断接收一包数据