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; // 开启串口发送中断
}
}
这时测试终于没问题了,无论快速发送数据(发送缓冲区长期满)还是低速发送数据(发送缓冲区长期空)都能连续跑好几个小时完全正常。