距离上一次发,五个月了……
贴代码了,Uart是最常用的串行通信方式了,每个单片机一定都会自带至少一个uart用于通信,CC2530也不例外。当然CC2530的是Usart,即通用同步异步串行接收发送器,即可以使用异步Uart模式,也可以使用同步SPI模式。
代码及解析
#include <ioCC2530.h>
#include "string.h"
/****************************
该示例简单的使用了CC2530的Usart模块。
上电后会自动的通过Uart发送一段文字。
在按下KET1时会发送出数字“1”。
在串口助手中给CC2530发送数字“0”,板载的LED1会改变状态。
****************************/
#define LED1 P1_0 //定义LED1为P10口控制
#define KEY1 P0_1 //定义KEY1为P01口控制
#define uchar unsigned char
#define uint unsigned int
/*本地函数*/
void initUART(void);
void initLED(void);
void sendmsg(char *);
void delay(int n);
char temp=0;
int main(void)
{
/*初始化LED*/
initLED();
/*初始化Uart*/
initUART();
sendmsg("新冠新冠快离开!\r\n\r\n");
while(1)
{
/*Uart发送相关代码段*/
if(KEY1 == 0) //如果有按键按下
{
delay(40); //延时以消抖
if(KEY1 == 0)
{
sendmsg("1\r\n"); //检测到按键1按下后发送数字1
}
}
/*Uart接受相关代码段*/
if(0 != temp) //如果Uart接收到数据
{
if(temp == 0x30) //如果接受到数字0
{
LED1 = !LED1; //改变LED1状态
}
temp = 0; //清除temp
}
}
}
#pragma vector = URX0_VECTOR
__interrupt void UART0_ISR(void)
{
URX0IF = 0; //清除中断标志位
temp = U0DBUF; //将接收缓冲区的8位数据存入temp中
}
void initLED(void)
{
P1DIR |= 0x03; //P1_0/P1_1定义为输出
LED1 = 1; //LED1灯初始化为关
}
void initUART(void)
{
P0SEL |= 0x0C; //将端口0_4/0_5设置为外设功能
U0GCR |= 11;
U0BAUD |= 216; //设置波特率为57600
U0CSR |= 0XC0; //将USART设置为Uart功能,并使能Usart接收器
URX0IF = 0; //清除接收中断标志位
EA = 1; //开启总中断开关,以能够使用系统的中断功能
URX0IE = 1; //Usart0中断功能使能
}
/*延时函数*/
void delay(int n)
{
int i,j;
for(i=0;i<n;i++)
for(j=0;j<1000;j++);
}
/*Uart发送函数*/
void sendmsg(char *data)
{
while(1)
{
if('\0' == *data) //一旦检测到字符串结束,就退出,一个字符串总是以'\0'作为结尾
{
break;
}
U0DBUF = *data++; //否则就将当前字符传入发送缓冲区中
while(UTX0IF == 0); //等待发送完成,发送完成后UTX0IF会被系统自动置1
UTX0IF = 0; //清除发送完成状态
}
}
另一个版本
当然还有一个代码比较多的版本,也是我当时实验的时候检查的版本。用了DMA和FSM,记录一下😑,注释我懒得打了😬。
#include <ioCC2530.h>
#include "string.h"
/****************************
该示例使用了Usart、DMA,通过DMA将Uart接收到的数据直接放入数组中,不需要CPU的干预。
在上电后会发送一段文字。
定义了一个数据包结构,一共五位,帧头为0x0F,0x0D,然后是包长度0x34,然后是数据位,
0x30关灯,0x31开灯,0x37切换灯状态,最后为校验位,前四个字节的异或和。
以16进制不换行发送该五位数据后,CC2530会使用根据校验和的结果继续判断或输出校验出错,
校验通过后FSM校验前三个字节,成功后会对灯状态进行切换,否则输出错误信息。
****************************/
#define LED1 P1_0
#define LED2 P1_1
#define KEY1 P0_1
#define uchar unsigned char
#define uint unsigned int
#pragma bitfields=reversed //转换为小端模式
typedef struct
{
uchar SRCADDRH; // 源地址高8位
uchar SRCADDRL; // 源地址低8位
uchar DESTADDRH; // 目标地址高8位
uchar DESTADDRL; // 目标地址低8位
uchar VLEN :3;// 长度域模式选择
uchar LENH :5;// 传输长度高字节
uchar LENL :8;// 传输长度低字节
uchar WORDSIZE :1;// 字节或字传输
uchar TMODE :2;// 传输模式选择
uchar TRIG :5;// 触发事件选择
uchar SRCINC :2;// 源地址增量 :-1/0/1/2
uchar DESTINC :2;// 目的地址增量 :-1/0/1/2
uchar IRQMASK :1;// 中断屏蔽
uchar M8 :1;// 7或8bit传输长度,仅在字节传输模式下适用
uchar PRIORITY :2;// 优先级
}DMA_DESC;
#pragma bitfields = default //转回大端模式
DMA_DESC uartdma; //DMA配置参数 结构体变量
void initUART(void);
void initLED(void);
void sendmsg(char *);
void receivemsg(void);
void delay(int n);
uint FSM(uchar *buff);
void changeled(uchar data);
uint xor(uchar *data);
void init_DMA();
uint count = 0;
char temp=0;
uchar buff[6]; // 需添加多一个字节作为结束符 否则出错
//起始字段定义为0x0F 0x0D 包长度为4个字节 命令码:1代表开灯 0代表关灯 7代表切换灯状态 FCS为前四个字节异或和
#define STATE1 0x00
#define STATE2 0x02
#define STATE3 0x04
#define STATE4 0x06
#define STATE5 0x08
int main(void)
{
initLED();
init_DMA();
initUART();
sendmsg("新冠新冠快离开!\r\n\r\n");
while(1)
{
DMAARM |= 0x01; //令DMA通道0进入工作状态
DMAIRQ |= 0x00; //清除DMA传送完成标志
delay(500);
if(buff[4] != 0) //如果buff中已经有五位数据
{
buff[5]='\0';
sendmsg("接收数据包如下:");
sendmsg(buff);
sendmsg("\r\n");
if(xor(buff) == 1)
{
if(FSM(buff) == 1)
{
sendmsg("接收数据包有效\r\n");
}
else
{
sendmsg("接收数据包无效\r\n");
}
}
else
{
sendmsg("FCS出错");
}
memset(buff,0,6);
}
}
}
#pragma vector = URX0_VECTOR
__interrupt void UART0_ISR(void)
{
URX0IF = 0;
temp = U0DBUF;
}
void initLED(void)
{
P1DIR |= 0x03;
LED1 = 1;
LED2 = 1;
}
void initUART(void)//9600
{
P0SEL |= 0x0C;
U0GCR |= 11;
U0BAUD |= 216;
U0CSR |= 0XC0;
UTX0IF = 0;
URX0IF = 0;
EA = 1;
URX0IE = 1;
}
void delay(int n)//11us
{
int i,j;
for(i=0;i<n;i++)
for(j=0;j<1000;j++);
}
void sendmsg(char *data)
{
while(1)
{
if(*data=='\0')
break;
U0DBUF = *data++;
while(UTX0IF == 0);
UTX0IF = 0;
}
}
uint FSM(uchar *buff)
{
uchar state = STATE1;
uchar data;
uint flag = 0;
while(1)
{
data = *buff++;
switch(state)
{
case STATE1:
if(data == 0x0F)
{
state = STATE2;
}
else
flag = 1;
break;
case STATE2:
if(data == 0x0D)
{
state = STATE3;
}
else
flag = 1;
break;
case STATE3:
if(data == 0x34)
{
state = STATE4;
}
else
flag = 1;
break;
case STATE4:
changeled(data);
state = STATE5;
break;
default:
break;
}
if(state == STATE5)
{
return 1;
}
if(flag == 1)
{
break;
}
}
return 0;
}
void changeled(uchar data)
{
if(data == 0x30)
LED1 = 1;
if(data == 0x31)
LED1 = 0;
if(data == 0x37)
LED1 = !LED1;
}
/*校验*/
uint xor(uchar *data)
{
uchar temp = 0;
/*取异或和*/
for(int i=0;i<4;i++)
{
temp ^= *data++;
}
sendmsg("FCS:"); //打印异或和查看
sendmsg(&temp);
sendmsg("\r\n");
// sendmsg(&(*data));
if(temp == *data)
return 1;
else
return 0;
}
void init_DMA()
{
/*配置源地址*/
uartdma.SRCADDRH=(uchar)((uint)&X_U0DBUF>>8);
uartdma.SRCADDRL=(uchar)((uint)&X_U0DBUF&0x00ff);
/*配置目的地址*/
uartdma.DESTADDRH=(uchar)((uint)&buff >> 8);
uartdma.DESTADDRL=(uchar)((uint)&buff&0x00FF);
/*选择LEN作为传送长度*/
uartdma.VLEN=0x00; //选择LEN作为传送长度
/*设置传输长度*/
uartdma.LENH=0;
uartdma.LENL=5;
uartdma.WORDSIZE=0x00; //选择字节(byte)传送
uartdma.TMODE=0x00; //字节模式
uartdma.TRIG=14; //串口接收中断触发
uartdma.SRCINC=0x00; //源地址增量为0
uartdma.DESTINC=0x01; //目的地址增量为1
uartdma.IRQMASK=0; //清除DMA中断
uartdma.M8=0x00; //选择8位长的字节来传送数据
uartdma.PRIORITY=0x02; //传输优先级为高
/*将配置结构体的首地址赋予相关SFR*/
DMA0CFGH=(uchar)((uint)&uartdma >> 8);
DMA0CFGL=(uchar)((uint)&uartdma & 0x00ff);
asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
}
- 了解一下CC2530的Usart。
- 72行,首先将Usart0对应的端口设置成外设端口,Usart即是一个外设,设置成外设端口Usart才能够通过Uart将数据发送和接受;
- 73-74行,设置Uart的波特率,只要将下图中的数据一一对应写进寄存器里就行,非常简单。但是要注意,我手里的CC2530默认的系统时钟是16M的,下图是32M的波特率寄存器对应值,这里记得要小心,如果你的单片机系统时钟是32M,那么就可以直接对着表里写寄存器,如果是16M的,像我的一样,那么下图中波特率所对应的寄存器的值写进去了,实际的波特率会减半。比如选择115200波特率,对应U0GCR为11,U0BAUD为216,这样写进去之后,16M系统时钟下,实际上的波特率为57600,设置错误就会导致乱码,务必小心。
- 75行,将Usart设置为Uart模式,且使能接收。
- 77-79行,把中断相关的寄存器打开,包括总中断和Uart0的接受中断。78-79行当然也可以直接IEN0 |= 0x84;
其他没啥好说的了,大概效果就是这样,上电发送一次,按键会发送1,回复0会让LED取反状态。
小结
Uart是真的很常用了,这段时间一直在实习,其实在公司虽然也会用到CAN、SPI、IIC这种协议,但是Uart依旧是最常用,最方便,最简单的一个。了解好Uart还是很重要的。上面那个复杂点的用到的DMA,放下次单独说吧,DMA还是很牛的。