转载著名出处。
一、目标:
在使用CH552串口的过程中遇到了一些问题,分享一下心得。CH552芯片带有两路UART:UART0和UART1。这两个串口都是标准51类型的串口,网上有一大堆相关的教程,不过没有一篇文章系统的讲了使用中可能遇到的一些问题。
二、测试环境:
使用CH552评估板进行的测试。因为CH552不支持在线调试,所以程序监控过程使用printf串口打印来实现。默认由串口0的TX(P3.1引脚)输出,波特率57600.
①建立示例工程:
先去官网下载例程包CH554EVT.ZIP.
解压后双击打开工程模板
项目栏中添加UART1文件夹中的C文件,CH554例程这里都是project下添加C文件,H文件都在C中include。(CH559的工程默认直接在main.c中把所有的C、H都include了)
尝试编译,正确输出编译结果,并且在工程文件夹中生成了HEX文件。
恭喜,接下来我们可以开始撸代码了。
②硬件连接
用CH340USB转串口模块和串口对应引脚对接。UART1的默认RX-P1.6,TX-P1.7。测试不同串口的时候把串口线替换过去就可以了。当然别忘记接地线。
CH552串口引脚定义:
串口号 | TX | RX |
---|---|---|
UART0 | P3.1 | P3.0 |
UART1 | P1.7 | P1.6 |
这样子的模块,购物网站上也很便宜,几块钱就能买一个(工程师必备良品)。 | ||
![]() |
三、代码分析:
①初始化
串口0外设初始化,其实这个函数的目的是配合printf来使能的,可以直接当作串口初始化函数。
UART0
/*******************************************************************************
* Function Name : mInitSTDIO()
* Description : CH554串口0初始化,默认使用T1作UART0的波特率发生器,也可以使用T2
作为波特率发生器
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void mInitSTDIO( )
{
UINT32 x;
UINT8 x2;
SM0 = 0;
SM1 = 1;
SM2 = 0; //串口0使用模式1
//使用Timer1作为波特率发生器
RCLK = 0; //UART0接收时钟
TCLK = 0; //UART0发送时钟
PCON |= SMOD;
x = 10 * FREQ_SYS / UART0_BUAD / 16; //如果更改主频,注意x的值不要溢出
x2 = x % 10;
x /= 10;
if ( x2 >= 5 ) x ++; //四舍五入
TMOD = TMOD & ~ bT1_GATE & ~ bT1_CT & ~ MASK_T1_MOD | bT1_M1; //0X20,Timer1作为8位自动重载定时器
T2MOD = T2MOD | bTMR_CLK | bT1_CLK; //Timer1时钟选择
TH1 = 0-x; //12MHz晶振,buad/12为实际需设置波特率
TR1 = 1; //启动定时器1
TI = 1;
REN = 1; //串口0接收使能
}
UART1
/*******************************************************************************
* Function Name : UART1Setup()
* Description : CH554串口1初始化
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void UART1Init( )
{
U1SM0 = 0; //UART1选择8位数据位
U1SMOD = 1; //快速模式
U1REN = 1; //使能接收
SBAUD1 = 0 - FREQ_SYS/16/UART1_BUAD;
U1TI = 0;
}
因为UART0默认是printf的输出串口,所以初始化比较特殊。
相比UART1,UART0的初始化函数多了 TI =1;
TI
是串口发送完成标志,为什么需要在初始化中置1呢?这个就要涉及到printf的实现。
②printf
printf是标准C实现的,库会回调putchar()
函数,这个函数的实现在我们的工程中是看不到的,函数实体存放于KEIL安装目录下:
我们可以通过修改putchar()
函数来实现一些自定义功能,最常见的就是将打印口变成串口1。下面我们来看一下函数具体实现:
/*
* putchar (mini version): outputs charcter only
*/
char putchar (char c) {
while (!TI);
TI = 0;
return (SBUF = c);
}
很简单,逻辑就是:等待发送完成标志置1,清掉标志,才能发送下一个数据。
所以这个就解释了初始化的时候为什么要将TI置1,不置1的话printf输出第一个字符的时候怎么办!所以putchar()
函数也能够被用来发送单个字节数据。(非主流)
③发送
/*******************************************************************************
* Function Name : CH554UART1SendByte(UINT8 SendDat)
* Description : CH554UART1发送一个字节
* Input : UINT8 SendDat;要发送的数据
* Output : None
* Return : None
*******************************************************************************/
void CH554UART1SendByte(UINT8 SendDat)
{
SBUF1 = SendDat; //查询发送,中断方式可不用下面2条语句,但发送前需TI=0
while(U1TI ==0);
U1TI = 0;
}
看函数的实现,可以发现正好逻辑和putchar()
反了过来:先发送数据,等待数据发送完成,清标志,退出。这样子有几个优点:
1、初始化时发送完成标志清除,更符合惯性思维
2、连续调用这个函数的时候不会导致数据在内部BUFF中被覆盖,造成丢数据。
3、如果要使用串口的中断,不至于一打开中断使能就立马跳转到中断向量。(所以如果使用默认的串口打印功能,是不能打开串口0中断使能的,不然要么打印不出来卡住,要么直接因为产生中断,恰好又没写中断函数实体,导致程序飞掉)。
缺点也就是while等待的形式CPU效率不高,尤其是波特率比较低的时候。
④接收
同样,我们可以借鉴一下C标准库实现的接收,代码存放位置和前面提到的putchar()
在同一文件夹:
函数
char _getkey () {
char c;
while (!RI);
c = SBUF;
RI = 0;
return (c);
}
这个函数很好理解,死等RI标志置位,指示有接收到一个数据。读取数据、清除标志。这个函数在不注重CPU效率的情况下用来做单字节接收很合适。
串口在实际场景下,建议都采用中断的方式做接收,能够保证最快的处理速度,从而不导致数据丢失。
代码如下:
UINT8 dat;
UINT8 flag=0;
void UART0Interrupt( void ) interrupt INT_NO_UART0 //CH554/552串口0中断号4 //串口1中断服务程序,使用寄存器组1
{
if(RI)
{
dat = SBUF;
RI = 0;
flag = 1;
}
}
void main( )
{
CfgFsys( ); //CH554时钟选择配置
mDelaymS(20);
mInitSTDIO( ); //串口0调试端口初始化
TI=0;
EA=1;
while(1)
{
if(flag){
CH554UART0SendByte(dat);
flag = 0;
}
//====CPU干别的事====
}
}
代码中为什么要把TI置0和主循环中串口0发送函数的构成,相信阅读了介绍发送部分的代码应该很好理解。在使用中断的时候要注意中断函数尽可能的简单,这样进出中断函数才快,串口是异步的,可能会出现CPU还在中断里面,但是下一个数据就来了,这种情况下就会导致数据丢失了。
总结:
51单片机的标准串口使用其实还是比较简单的,总共就没几个寄存器,数据的工程师应该都能背出来了吧。串口作为最常用的外设,很多人对于底层实现还是一知半解,希望这个文章能有一些帮助吧。
常见问题:
1、串口接收丢数据:查询法,就是主循环跑的太慢。中断法就是中断效率太低。
2、串口0打印不出来、数据发不出来:printf默认实现方式和默认串口发送方式对于TI的处理不同
3、波特率不对:串口波特率发生的计数器是256位的,有些分频理论计算误差是允许的,但是计数值超出了256,所以要切换更高的分频,让计数器初值处于0~256范围内。
4、数据不对:有很多人会直接把TTL电平的TX、RX和RS232、RS485直接对接…best wishes…
5、想到再说