BLE开发笔记——串口的使用
进行软件开发,串口打印是个有效的调试方式,特别适用于没有显示设备的开发平台,所以为了方便调试必须要学会串口的配置和使用,在后期实现串口数据蓝牙转发的时候,也必须清楚串口的使用。本文就针对我自己的情况说一下串口的使用,CC2541有两个USART,使用都是类似的,根据使用管脚和串口的不同做适当的调整就好。
配置
我手头的是一个CC2541的最小系统,如下图所示,使用的串口是UART0,与IO的分配关系是:P1_5->TX,P1_4->RX。要实现串口的使用(不使用硬件流控制)需要进行IO的复用配置和针对串口的配置。
IO配置
需要将P1_4和P1_5两个IO进行复用配置,同时将UART0配置到这两个IO的位置,关于这一项配置,首先得明确IO复用的map,如下很清晰地看到UART0映射到P1_4和P1_5,是配置UART0为位置2。还需要配置相应管脚优先使用串口,那么需要配置的寄存器分别是PERCFG、P1SEL和P2DIR,关于这三个寄存器的详细信息可以看我的另一篇文章 BLE开发——CC2541寄存器整理。
配置代码:
PERCFG |= 0x01; // 配置UART0为位置2
P1SEL = 0x3c; // P1_2,P1_3,P1_4,P1_5用作外设,串口功能
P2DIR &= ~0xc0; // P1优先用作串口
对了,这里得插一句,可能你不明白怎么看寄存器赋什么值,这里以上面的P1SEL为例讲一下:
我们知道此寄存器的描述:P1.7–P1.0的功能选择(0:通用I/O 1:外设功能)。我们需要的是P1_2,P1_3,P1_4,P1_5用作外设,所以这四个管脚对应的一个Byte的2、3、4、5bit位是1,其余bit位不管,设置为0为普通IO,所以值就是0011 1100那么对应十六进制数为0x3c。
UART0配置
IO配置好以后,需要对UART0的寄存器进行配置,首先得选择串口方式是UART异步通信方式,然后通过U0GCR.BAUD_E[4:0]和U0BAUD.BAUD[7:0]结合配置波特率,波特率的计算公式如下:
$$ BaudRate = \frac{(256+BAUD \_ M)\times2^{BAUD \_ E}}{2^{28}}\times f $$
其中f是系统时钟,我设置的是32MHz外部晶振,对应32MHz时钟波特率设置配置表如下:
波特率 | UxBAUD.BAUD_M | UxBAUD.BAUD_E | 误差(%) |
---|---|---|---|
2400 | 59 | 6 | 0.14 |
4800 | 59 | 7 | 0.14 |
9600 | 59 | 8 | 0.14 |
14,400 | 216 | 8 | 0.03 |
19,200 | 59 | 9 | 0.14 |
28,800 | 216 | 9 | 0.03 |
38,400 | 59 | 10 | 0.14 |
57,600 | 216 | 10 | 0.03 |
76,800 | 59 | 11 | 0.14 |
115,200 | 216 | 11 | 0.03 |
230,400 | 216 | 12 | 0.03 |
其实还可以根据自己需求设置U0UCR对停止位、校验、起始位、位长进行配置,我没有配置使用了默认设置:8位数据位、没有校验、1个停止位。
配置代码:
U0CSR |= 0x80; // UART 方式
U0GCR |= 11; // U0GCR与U0BAUD配合
U0BAUD |= 216; // 波特率设为115200
接收中断配置
我采用接收中断的方式接收串口数据,所以还得需要配置中断,查询CC2541用户手册可以发现设置IEN0即可,寄存器详细如下:
设置第7位和低位即可,同时需要清空UART0串口接收中断标志,因为串口基本配置完毕,然后配置U0CSR允许接收,配置代码:
UTX0IF = 0; // 清除中断标志
U0CSR |= 0X40; // 允许接收
IEN0 |= 0x84; // 开总中断,接收中断
串口使用
这里采用中断的方式接收串口数据,所以必须写中断处理程序,接受搞定,那么发送数据怎么办呢?无论接收还是发送数据都是在操作U0DBUF,接收到数据后硬件会把数据放在U0DBUF,读取即可取出数据,当往U0DBUF写数据后,串口就会把数据发送出去,为了更方便使用串口,我编写了判断接收,读取数据,发送字符串、整数和浮点数的处理函数,下面分别来说一下。
串口接收中断
以下代码实现将接收到的数据发回去,将程序烧进芯片,连接串口到PC,在PC端打开串口助手(这个网上有很多,自己下载一个即可,比如OpenJumper串口助手)进行测试,PC端往串口发数据,则会看到CC2541将数据返回发送,PC端串口助手接收区便收到发送的数据。
// 中断处理
#pragma vector = URX0_VECTOR
__interrupt void UART0_ISR(void)
{
unsigned char ch;
URX0IF = 0; // 清中断标志
ch = U0DBUF;
U0DBUF = ch;
}
串口函数
为了方便使用,写了一些函数,这里只说明一下接收:我是建立了一个缓存数组,长度64,还有一个标记指针,用来记录最新数据位置,每次接收到数据就往数组里塞,读取的时候取出数据,再逐个前移数据,标记指针减1。
具体实现,看我的代码,很好理解的。
-
头文件serial.h
#ifndef __SERIAL_H__ #define __SERIAL_H__ void oscInit(void); void serialInit(void); unsigned char serialAvailable(void); unsigned char serialRead(); unsigned char serialWrite(unsigned char dataSend); void print(char *bytes, unsigned char length); void printInt(int num); void printFloat(float num); void println(char *bytes, unsigned char length); void printIntln(int num); void printFloatln(float num); #endif
-
源文件serial.c
#include "serial.h" #include <ioCC2541.h> static unsigned char serialReData[64]; static unsigned char dataPoint = 0; // 启动外部32M晶振 void oscInit(void) { SLEEPCMD &= ~0x04; // 启动所有晶振 while (!(SLEEPSTA & 0x40)); // 等待晶振稳定 CLKCONCMD = (CLKCONCMD & 0x80) | 0x49; // 使用16M晶振作为主时钟 while ((CLKCONSTA & ~0x80) != 0x49 ); // 等待主时钟切换到16M晶振 CLKCONCMD = (CLKCONCMD & ~0x80) ; // 使用外部32K晶振作为休眠时钟 while ( (CLKCONSTA & 0x80) != 0 ); // 等待睡眠时钟切换到外部32K晶振 CLKCONCMD = (CLKCONCMD & 0x80) ; // 使用32M晶振作为主时钟 while ( (CLKCONSTA & ~0x80) != 0 ); // 等待主时钟切换到32M晶振 SLEEPCMD |= 0x04; // 关闭未使用的晶振 } // 串口初始化 void serialInit(void) { // 初始化串口的IO PERCFG |= 0x01; // 配置UART0为位置2 P1SEL = 0x3c; // P1_2,P1_3,P1_4,P1_5用作串口功能 P2DIR &= ~0xc0; // P1优先用作串口 // 配置串口 U0CSR |= 0x80; // UART 方式 U0GCR |= 11; // U0GCR与U0BAUD配合 U0BAUD |= 216; // 波特率设为115200 UTX0IF = 0; // 清除中断标志 U0CSR |= 0X40; // 允许接收 IEN0 |= 0x84; // 开总中断,接收中断 } unsigned char serialAvailable(void) { return dataPoint; } // 读取一个Byte unsigned char serialRead() { unsigned char ch; ch = serialReData[0]; if(dataPoint > 0) { for(int i = 0; i < dataPoint; i++) serialReData[i] = serialReData[i+1]; dataPoint--; } else { dataPoint = 0; } return ch; } // 发送一个Byte数据 unsigned char serialWrite(unsigned char dataSend) { U0DBUF = dataSend; while((U0CSR & 0x01) == 0x01); return 1; } // 按个数发送字符串 void print(char *bytes, unsigned char length) { for(int i = 0; i < length; i++) { serialWrite(*(bytes+i)); } } // 发送整数 void printInt(int num) { if(num == 0) { serialWrite(0x30); } else { unsigned char ch; unsigned int nT; unsigned char flag = 0; if(num < 0) { serialWrite('-'); nT=-num; } else { nT = num; } for(int i = 0; i < 5; i++) { ch = nT/10000+0x30; nT %= 10000; nT *= 10; if(flag == 1) { serialWrite(ch); } else { if(ch > 0x30) { flag = 1; serialWrite(ch); } } } } } // 发送两位小数 void printFloat(float numFloat) { int numI = (int)numFloat; unsigned char factor = 100; char ch; printInt(numI); numI = (int)(numFloat*factor-numI*factor); serialWrite('.'); ch = numI / 10; serialWrite(ch+0x30); ch = numI % 10; serialWrite(ch+0x30); } // 发送字符串(带换行) void println(char *bytes, unsigned char length) { print(bytes, length); serialWrite('\n'); } // 发送整数(带换行) void printIntln(int num) { printInt(num); serialWrite('\n'); } // 发送两位小数(带换行) void printFloatln(float num) { printFloat(num); serialWrite('\n'); } // 中断处理 #pragma vector = URX0_VECTOR __interrupt void UART0_ISR(void) { unsigned char ch; URX0IF = 0; // 清中断标志 ch = U0DBUF; if(dataPoint < 64) { serialReData[dataPoint] = ch; dataPoint++; } else { for(int i = 0; i < 63; i++) { serialReData[i] = serialReData[i+1]; } dataPoint = 63; serialReData[63] = ch; } U0DBUF = ch; }