CH552 CH554 51单片机标准串口使用教程及常见问题

转载著名出处。

一、目标:

在使用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串口引脚定义:

串口号TXRX
UART0P3.1P3.0
UART1P1.7P1.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、想到再说

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要用51单片机控制串口流水灯,需要以下步骤: 1. 首先需要准备好51单片机的开发环境,例如Keil或者SDCC等。 2. 然后需要连接好串口,将串口的TX和RX分别连接到单片机的P3.1和P3.0引脚上。 3. 接着需要写好串口通信的代码,用来接收PC端发送的指令。具体的代码可以参考如下: ```c #include <reg51.h> #include <stdio.h> #define FOSC 11059200L #define BAUD 9600 #define TIMER_1MS (65536-FOSC/12/1000) typedef unsigned char byte; typedef unsigned int word; sbit LED1 = P1^0; sbit LED2 = P1^1; void init_serial() { TMOD &= 0x0F; TMOD |= 0x20; TH1 = TL1 = TIMER_1MS / BAUD; TR1 = 1; SM0 = 0; SM1 = 1; REN = 1; } void send_byte(byte dat) { SBUF = dat; while(!TI); TI = 0; } byte receive_byte() { while(!RI); RI = 0; return SBUF; } void main() { byte ch; init_serial(); while(1) { ch = receive_byte(); if(ch == '1') { LED1 = 1; LED2 = 0; } else if(ch == '2') { LED1 = 0; LED2 = 1; } else if(ch == '3') { LED1 = 1; LED2 = 1; } else if(ch == '4') { LED1 = 0; LED2 = 0; } } } ``` 这段代码实现了串口初始化,以及接收PC端发送的指令,并根据指令控制LED灯的亮灭。 4. 接下来需要编写流水灯的代码。具体的代码可以参考如下: ```c #include <reg51.h> typedef unsigned char byte; typedef unsigned int word; sbit LED1 = P1^0; sbit LED2 = P1^1; void delay(word i) { while(i--); } void main() { byte i; while(1) { for(i=0; i<8; i++) { LED1 = i & 0x01; LED2 = (i>>1) & 0x01; delay(50000); } } } ``` 这段代码实现了一个简单的流水灯效果,LED1和LED2分别控制两个LED灯的亮灭。 5. 最后将流水灯的代码和串口通信的代码合并即可。将PC端发送的指令与流水灯的代码结合,即可实现通过串口控制流水灯的效果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值