signed

QiShunwang

“诚信为本、客户至上”

C8051F320 串口使用

2020/12/29 1:30:23   来源:

C8051F320 串口使用

  • 一、UART0 介绍
    • 1、UART0
    • 2、增强的波特率发生器
    • 3、工作方式
      • ①、8 位 UART
      • ②、9 位 UART
    • 4、多机通信
  • 二、UART0 寄存器说明
    • 1、SCON0: UART0 控制寄存器
    • 2、SBUF0:UART0 串行数据缓冲寄存器
  • 三、定时器1
    • 1、定时器 1 介绍
    • 2、方式 2
    • 3、相关寄存器
      • ①、TCON:定时器控制寄存器
      • ②、TMOD:定时器方式寄存器
      • ③、CKCON:时钟控制寄存器
      • ④、TH1、TL1
  • 四、中断
    • 1、中断系统
    • 2、中断向量表
  • 五、示例代码
    • 1、main.c
    • 2、uart.c
    • 3、printf.c
    • 4、效果图



一、UART0 介绍

1、UART0

        UART0 是一个异步、全双工串口,它提供标准 8051 串行口的方式 1 和方式 3。UART0具有增强的波特率发生器电路,有多个时钟源可用于产生标准波特率。接收数据缓冲机制允许 UART0 在软件尚未读取前一个数据字节的情况下开始接收第二个输入数据字节。

        UART0 有两个相关的特殊功能寄存器:串行控制寄存器(SCON0)和串行数据缓冲器(SBUF0)。用同一个 SBUF0 地址可以访问发送寄存器和接收寄存器。写 SBUF0 时自动访问发送寄存器;读 SBUF0 时自动访问接收寄存器,不可能从发送数据寄存器中读数据

        如果 UART0 中断被允许,则每次发送完成(SCON0 中的 TI0 位被置‘1’)或接收到数据字节(SCON0 中的 RI0 位被置‘1’)时将产生中断。当 CPU 转向中断服务程序时硬件不清除 UART0 中断标志。中断标志必须用软件清除,这就允许软件查询 UART0 中断的原因(发送完成或接收完成)。

UART0 原理框图
在这里插入图片描述

2、增强的波特率发生器

        UART0 波特率由定时器 1 工作在 8 位自动重装载方式产生。发送(TX)时钟由 TL1 产生;接收(RX)时钟由 TL1 的拷贝寄存器(图 17.2 中的 RX 定时器)产生,该寄存器不能被用户访问。TX 和 RX 定时器的溢出信号经过二分频后用于产生 TX 和 RX 波特率。当定时器 1 被允许时,RX 定时器运行并使用与定时器 1 相同的重载值(TH1)。在检测到 RX 引脚上的起始条件时 RX 定时器被强制重载,这允许在检测到起始位时立即开始接收过程,而与 TX 定时器的状态无关。

UART0 波特率逻辑
在这里插入图片描述

        定时器 1 应被配置为方式 2,即 8 位自动重装载方式。定时器 1 的重载值应设置为使其溢出频率为所期望的波特率频率的两倍。注意,定时器 1 的时钟可以在 6 个时钟源中选择:SYSCLK、SYSCLK/4、SYSCLK/12、SYSCLK/48、外部振荡器时钟/8 和外部输入 T1。对于任何给定的定时器 1 时钟源,UART0 的波特率由以下方程决定:

在这里插入图片描述

        其中T1CLK是定时器 1 的时钟频率,T1H是定时器 1 的高字节(重载值)。
        注意,当外部振荡器驱动定时器 1 时,内部振荡器仍可产生系统时钟。

3、工作方式

        UART0 提供标准的异步、全双工通信,其工作方式(8 位或 9 位)通过 S0MODE 位(SCON0.7)来选择。典型的 UART 连接方式如下图所示。

在这里插入图片描述

①、8 位 UART

        8 位 UART 方式,每个数据字节共使用 10 位:一个起始位、8 个数据位(LSB 在先)和一个停止位。数据从 TX0 引脚发送,在 RX0 引脚接收。在接收时,8 个数据位存入 SBUF0,停止位进入 RB80(SCON0.2)。

        当软件向 SBUF0 寄存器写入一个字节时开始数据发送。在发送结束时(停止位开始)发送中断标志 TI0(SCON0.1)被置‘1’。在接收允许位 REN0(SCON0.4)被置‘1’后,数据接收可以在任何时刻开始。收到停止位后,如果满足下述条件则数据字节将被装入到接收寄存器 SBUF0:RI0 必须为逻辑‘0’;如果 MCE0 为逻辑‘1’,则停止位必须为‘1’。在发生接收数据溢出的情况下,先接收到的 8 位数据被锁存到 SBUF0,而后面的溢出数据被丢弃。

        如果这些条件满足,则 8 位数据被存入 SBUF0,停止位被存入 RB80,RI0 标志被置位。如果这些条件不满足,则不装入 SBUF0 和 RB80,RI0 标志也不会被置‘1’。如果中断被允许, 在 TI0 或 RI0 置位时将产生一个中断。

8 位 UART 时序图
在这里插入图片描述

②、9 位 UART

        在 9 位 UART 方式,每个数据字节共使用 11 位:一个起始位、8 个数据位(LSB 在先)、一个可编程的第九位和一个停止位。第九发送数据位由 TB80(SCON0.3)中的值决定,由用户软件赋值。它可以被赋值为 PSW 中的奇偶位 P(用于错误检测),或用于多处理器通信。在接收时,第九数据位进入 RB80(SCON0.2),停止位被忽略。

        当执行一条向 SBUF0 寄存器写一个数据字节的指令时开始数据发送。在发送结束时(停止位开始)发送中断标志 TI0 被置‘1’。在接收允许位 REN0 被置‘1’后,数据接收可以在任何时刻开始。收到停止位后如果满足下述条件则数据字节将被装入到接收寄存器 SBUF0:RI0 为逻辑‘0’;如果 MCE0 为逻辑‘1’,则第九位必须为逻辑‘1’(当 MCE0 为逻辑‘0’时,第九位数据的状态并不重要)。如果这些条件满足,则 8 位数据被存入 SBUF0,第九位被存入 RB80,RI0 标志被置位。如果这些条件不满足,则不装入 SBUF0 和 RB80,RI0 标志也不会被置‘1’。如果中断被允许,在 TI0 或 RI0 置位时将产生一个中断。

9 位 UART 时序图
在这里插入图片描述

4、多机通信

        9 位UART方式通过使用第9 数据位可以支持一个主处理器与一个或多个从处理器之间的多机通信。当主机要发送数据给一个或多个从机时,它先发送一个用于选择目标的地址字节。地址字节与数据字节的区别是:地址字节的第 9 位为逻辑‘1’;数据字节的第 9 位总是设置为逻辑‘0’。

        如果从机的 MCE0 位(SCON.5)被置‘1’,则只有当 UART 接收到的第九位为逻辑‘1’ (RB80 = 1)并收到有效的停止位后 UART 才会产生中断。在UART 的中断处理程序中,软件将接收到的地址与从机自身的 8 位地址进行比较。如果地址匹配,从机将清除它的 MCE0位以允许后面接收数据字节时产生中断。未被寻址的从机仍保持其 MCE0 位为‘1’,在收到后续的数据字节时不产生中断,从而忽略收到的数据。一旦接收完整个消息,被寻址的从机将它的 MCE0 位重新置‘1’以忽略所有的数据传输,直到它收到下一个地址字节。

        可以将多个地址分配给一个从机,或将一个地址分配给多个从机从而允许同时向多个从机“广播”发送。主机可以被配置为接收所有的传输数据,或通过实现某种协议使主/从角色能临时变换以允许原来的主机和从机之间进行半双工通信。

UART 多机方式连接图
在这里插入图片描述

二、UART0 寄存器说明

1、SCON0: UART0 控制寄存器

R/WRR/WR/WR/WR/WR/WR/W复位值
S0MODE-RI0MCE0REN0TB80RB80TI001000000
位7位6位5位4位3位2位1位0(可位寻址)SFR地址:0x98

位 7: S0MODE:串行口工作方式选择位
           该位选择 UART0 的工作方式。
           0:方式 0:波特率可编程的 8 位 UART。
           1:方式 1:波特率可编程的 9 位 UART。

位 6: 未使用。读 = 1b。写 = 忽略。

位 5: MCE0:多处理器通信允许
           该位的功能取决于串行口工作方式。
           S0MODE = 0:检查有效停止位。
                      0:停止位的逻辑电平被忽略。
                      1:只有当停止位为逻辑‘1’时 RI0 激活。
           S0MODE = 1:多处理器通信允许。
                      0:第 9 位的逻辑电平被忽略。
                      1:只有当第 9 位为逻辑‘1’时 RI0 才被置位并产生中断。

位 4: REN0:接收允许
           该位允许/禁止 UART 接收器。
           0:UART0 接收禁止。
           1:UART0 接收允许。

位 3: TB80:第 9 发送位
           该位的逻辑电平被赋值给 9 位 UART 方式的第 9 发送位。在 8 位 UART 方式中未用。跟据需要用软件置‘1’或清‘0’。

位 2: RB80:第 9 接收位
           在方式 0,则 RB80 被赋值为停止位的值。在方式 1 该位被赋值为 9 位 UART 方式中第九数据位的值。

位 1: TI0:发送中断标志
           当 UART0 发送完一个字节数据后该位被硬件置‘1’(在 8 位 UART 方式时,是在发送第 8 位后;在 9 位 UART 方式时,是在停止位开始)。当 UART0 中断被允许时,置‘1’该位将导致 CPU 转到 UART0 中断服务程序。该位必须用软件清‘0’。

位 0: RI0:接收中断标志
           当 UART0 接收到一个字节数据时该位被硬件置‘1’(在停止位后)。当 UART0中断被允许时,置‘1’该位将会使 CPU 转到 UART0 中断服务程序。该位必须用软件清‘0’。

2、SBUF0:UART0 串行数据缓冲寄存器

R/WR/WR/WR/WR/WR/WR/WR/W复位值
00000000
位7位6位5位4位3位2位1位0SFR地址:0x99

位 7-0: SBUF0.[7:0]: UART0 数据缓冲器位 7-0(MSB-LSB)
           实际上是两个寄存器:发送移位寄存器和接收锁存寄存器。当数据被写到SBUF0 时,它进入发送移位寄存器等待串行发送。向 SBUF0 写入一个字节即启动发送过程。读 SBUF0 时返回接收锁存器的内容。

三、定时器1

1、定时器 1 介绍

        C8051F32x 内部有 4 个 16 位计数器/定时器:其中两个与标准 8051 中的计数器/定时器兼容,另外两个是 16 位自动重装载定时器,可用于 ADC、SMBus、USB(帧测量)或作为通用定时器使用。这些定时器可以用于测量时间间隔,对外部事件计数或产生周期性的中断请求。定时器 0 和定时器 1 几乎完全相同,有四种工作方式。定时器 2 和定时器 3 均可作为一个 16位或两个 8 位自动重装载定时器。

在这里插入图片描述
        定时器 0 和定时器 1 有 5 个可选择的时钟源,由定时器时钟选择位(T1M-T0M)和时钟分频位(SCA1-SCA0)决定。时钟分频位定义一个分频时钟,作为定时器 0 和/或定时器 1 的时钟源。

        定时器 0 和定时器 1 可以被配置为使用分频时钟或系统时钟。定时器 2 和定时器 3 可以使用系统时钟、系统时钟/12 或外部振荡器时钟/8 作为时钟源。

        定时器 0 和定时器 1 可以工作在计数器方式。当作为计数器使用时,在为定时器所选择的输入引脚(T0 或 T1)上出现负跳变时计数器/定时器寄存器的值加 1。对事件计数的最大频率可达到系统时钟频率的四分之一。输入信号不需要是周期性的,但在一个给定电平上的保持时间至少应为两个完整的系统时钟周期,以保证该电平能够被正确采样。

        由于 UART0 波特率由定时器 1 工作在 8 位自动重装载方式产生,所以这里只阐述定时器 1 方式 2。

2、方式 2

        方式 2 将定时器 0 和定时器 1 配置为具有自动重新装入计数初值能力的 8 位计数器/定时器。TL0 保持计数值,而 TH0 保持重载值。当 TL0 中的计数值发生溢出(从全‘1’到 0x00)时,定时器溢出标志 TF0(TCON.5)被置位,TH0 中的重载值被重新装入到 TL0。如果中断被允许,在 TF0 被置位时将产生一个中断。TH0 中的重载值保持不变。为了保证第一次计数正确,必须在允许定时器之前将 TL0 初始化为所希望的计数初值。当工作于方式 2 时,定时器 1 的操作与定时器 0 完全相同

        在方式 2,定时器 1 和定时器 0 的配置和控制方法与方式 0 一样。当 GATE0(TMOD.3)为逻辑‘0’或输入信号/INT0 有效时(有效电平由 INT01CF 寄存器中的 IN0PL 为定义),置‘1’TR0 位(TCON.4)将允许定时器 0 工作。

T0 方式 2 原理框图
在这里插入图片描述

3、相关寄存器

①、TCON:定时器控制寄存器

R/WR/WR/WR/WR/WR/WR/WR/W复位值
TF1TR1TF0TR0IE1IT1IE0IT000000000
位7位6位5位4位3位2位1位0(可位寻址)SFR地址:0x88

在这里插入图片描述
在这里插入图片描述

②、TMOD:定时器方式寄存器

R/WR/WR/WR/WR/WR/WR/WR/W复位值
GATE1C/T1T1M1T1M0GATE0C/T0T0M1T0M000000000
位7位6位5位4位3位2位1位0SFR地址:0x89

在这里插入图片描述

③、CKCON:时钟控制寄存器

R/WR/WR/WR/WR/WR/WR/WR/W复位值
T3MHT3MLT2MHT2MLT1MT0MSCA1SCA000000000
位7位6位5位4位3位2位1位0SFR地址:0x8E

位 3: T1M:定时器 1 时钟选择。
           该位选择定时器 1 的时钟源。当 C/T1 被设置为逻辑‘1’时,T1M 被忽略。
           0:定时器 1 使用由分频位(SCA1-SAC0)定义的时钟。
           1:定时器 1 使用系统时钟。

位 1-0: SCA1-SCA0:定时器 0/1 分频位
           如果定时器 0/1 被配置为使用分频时钟,则这些位控制时钟分频数。

SCA1SCA0分频时钟
00系统时钟/12
01系统时钟/4
10系统时钟/48
11外部时钟/8

           注:外部时钟 8 分频与系统时钟同步,在该方式,外部时钟频率必须小于或等于系统时钟频率。

④、TH1、TL1

在这里插入图片描述
在这里插入图片描述

四、中断

1、中断系统

        CIP-51 包含一个扩展的中断系统,支持 16 个中断源,每个中断源有两个优先级。中断源在片内外设与外部输入引脚之间的分配随器件的不同而变化。每个中断源可以在一个 SFR 中有一个或多个中断标志。当一个外设或外部源满足有效的中断条件时,相应的中断标志被置为逻辑‘1’。

        如果一个中断源被允许,则在中断标志被置位时将产生一个中断。一旦当前指令执行完,CPU 产生一个 LCALL 到预定地址,开始执行中断服务程序(ISR)。每个 ISR 必须以 RETI 指令结束,使程序回到中断前执行的那条指令的下一条指令。如果中断未被允许,中断标志将被硬件忽略,程序继续正常执行。中断标志置‘1’与否不受中断允许/禁止状态的影响。

        每个中断源都可以用一个 SFR(IE – EIE2)中的相关中断允许位来允许或禁止,但是必须首先将 EA 位(IE.7)置‘1’,以保证每个单独的中断允许位有效。不管每个中断允许位的设置如何,清‘0’EA 位将禁止所有中断。

        某些中断标志在 CPU 进入 ISR 时被自动清除,但大多数中断标志不是由硬件清除的,必须在 ISR 返回前用软件清除。如果一个中断标志在 CPU 执行完中断返回(RETI)指令后仍然保持置位状态,则会立即产生一个新的中断请求,CPU 将在执行完下一条指令后再次进入该 ISR。

        MCU 支持 16 个中断源。软件可以通过将任何一个中断标志设置为逻辑‘1’来模拟一个中断。如果中断标志被允许,系统将产生一个中断请求,CPU 将转向与该中断标志对应的 ISR地址。

2、中断向量表

中断源中断向量优先级中断标志位寻址硬件清除中断允许优先级控制
复位0x0000最高N/AN/A始终允许总是最高
外部中断 0 (/INT0)0x00030IE0 (TCON.1)YYEX0 (IE.0)PX0 (IP.0)
定时器 0 溢出0x000B1TF0 (TCON.5)YYET0 (IE.1)PT0 (IP.1)
外部中断 1 (INT1)0x00132IE1 (TCON.3)YYEX1 (IE.2)PX1 (IP.2)
定时器 1 溢出0x001B3TF1 (TCON.7)YYET1 (IE.3)PT1 (IP.3)
UART00x00234 RI0 (SCON0.0) TI0 (SCON0.1)YNES0 (IE.4)PS0 (IP.4)
定时器 2 溢出0x002B5TF2H (TMR2CN.7) TF2L (TMR2CN.6)YNET2 (IE.5)PT2 (IP.5)
SPI00x00336SPIF (SPI0CN.7) WCOL (SPI0CN.6) MODF (SPI0CN.5) RXOVRN(SPI0CN.4)YNESPI0 (IE.6)PSPI0 (IP.6)
SMB00x003B7SI (SMB0CN.0)YNESMB0 (EIE1.0)PSMB0 (EIP1.0)
USB00x00438特殊NNEUSB0(EIE1.1)PUSB0 (EIP1.1)
ADC0 窗口比较0x004B9AD0WINT (ADC0CN.3)YNEWADC0 (EIE1.2)PWADC0 (EIP1.2)
ADC0 转换结束0x005310AD0INT (ADC0CN.5)YNEADC0C (EIE1.3)PADC0 (EIP1.3)
可编程计数器阵列0x005B11CF (PCA0CN.7) CCFn (PCA0CN.n)YNEPCA0 (EIE1.4)PPCA0 (EIP1.4)
比较器 00x006312CP0FIF(CPT0CN.4) CP0RIF(CPT0CN.5)NNECP0 (EIE1.5)PCP0 (EIP1.5)
比较器 10x006B13CP1FIF(CPT1CN.4) CP1RIF(CPT1CN.5)NNECP1 (EIE1.6)PCP1 (EIP1.6)
定时器 3 溢出0x007314TF3H(TMR3CN.7) TF3L(TMR3CN.6)NNET3 (EIE1.7)PT3 (EIP1.7)
VBUS 电平0x007B15N/AN/AN/AEVBUS (EIE2.0)PVBUS (EIP2.0)

五、示例代码

Keil 环境

1、main.c

#include <C8051F320.h>
#include "uart.h"	//头文件只有外部函数声明
#include "printf.h"	//头文件只有外部函数声明

#ifndef uchar
	#define uchar unsigned char
#endif
#ifndef uint
	#define uint  unsigned int
#endif

#define XTLVLD				0x80		//晶体振荡器有效标志

#define CRYSTAL_FREQUENCY	12000000L	//外部振荡器频率Hz

//外部振荡器频率控制位
#if (CRYSTAL_FREQUENCY <= 32000)
	#define XFCN 0
#elif (CRYSTAL_FREQUENCY <= 84000)
	#define XFCN 1
#elif (CRYSTAL_FREQUENCY <= 225000)
	#define XFCN 2
#elif (CRYSTAL_FREQUENCY <= 590000)
	#define XFCN 3
#elif (CRYSTAL_FREQUENCY <= 1500000)
	#define XFCN 4
#elif (CRYSTAL_FREQUENCY <= 4000000)
	#define XFCN 5
#elif (CRYSTAL_FREQUENCY <= 10000000)
	#define XFCN 6
#elif (CRYSTAL_FREQUENCY <= 30000000)
	#define XFCN 7
#else
	#error "Crystal Frequency must be less than 30MHz"
#endif


sbit led = P2 ^ 0;


void Port_IO_Init()
{
	// P0.0  -  Unassigned,  Open-Drain, Digital
	// P0.1  -  Unassigned,  Open-Drain, Digital
	// P0.2  -  Skipped,     Open-Drain, Analog
	// P0.3  -  Skipped,     Open-Drain, Analog
	// P0.4  -  TX0 (UART0), Push-Pull,  Digital
	// P0.5  -  RX0 (UART0), Open-Drain, Digital
	// P0.6  -  Unassigned,  Open-Drain, Digital
	// P0.7  -  Unassigned,  Open-Drain, Digital

	// P1.0  -  Unassigned,  Open-Drain, Digital
	// P1.1  -  Unassigned,  Open-Drain, Digital
	// P1.2  -  Unassigned,  Open-Drain, Digital
	// P1.3  -  Unassigned,  Open-Drain, Digital
	// P1.4  -  Unassigned,  Open-Drain, Digital
	// P1.5  -  Unassigned,  Open-Drain, Digital
	// P1.6  -  Unassigned,  Open-Drain, Digital
	// P1.7  -  Unassigned,  Open-Drain, Digital
	// P2.0  -  Unassigned,  Push-Pull,  Digital
	// P2.1  -  Unassigned,  Open-Drain, Digital
	// P2.2  -  Unassigned,  Open-Drain, Digital
	// P2.3  -  Unassigned,  Open-Drain, Digital

	P0MDOUT  = 0x10;		//P0.4 TX上拉输出
	P0MDIN   = 0xF3;		//P0.2, P0.3 模拟引脚
	P2MDOUT  = 0x01; 		//P2.0 上拉输出
	P0SKIP   = 0x0C;		//Crossbar中跳过 P0.2,P0.3
	XBR0     = 0x01;		//启用串口引脚
	XBR1     = 0x40;		//启用交叉开关和弱上拉
}

void Oscillator_Init()
{
	int i = 0;
	OSCXCN    = 0x60 | XFCN;				//外部晶振,不分频,12MHz

	for(i = 0; i < 3000; i++);   //等待1ms初始化

	while((OSCXCN & XTLVLD) == 0);

	CLKSEL    = 0x01;		//选择系统时钟源,外部振荡器
	OSCICN    = 0x00;		//关闭内部RC振荡器
}


void Init_Device(void)
{
	Port_IO_Init();
	Oscillator_Init();
}


void main()
{
	int i, j;
	PCA0MD &= ~0x40;                    //关闭看门狗

	Init_Device();
	Uart0_Init();
	EA = 1;				//开总中断


	while(1) {
		led = 0;

		for(j = 0; j < 100; j++) {
			for(i = 0; i < 3000; i++);
		}

		led = 1;

		for(j = 0; j < 100; j++) {
			for(i = 0; i < 3000; i++);
		}

		led = 0;

		for(j = 0; j < 100; j++) {
			for(i = 0; i < 3000; i++);
		}

		led = 1;

		for(j = 0; j < 100; j++) {
			for(i = 0; i < 3000; i++);
		}

		printf("%s", "Hello, This is C8051F320 UART0!\n");
		printf("hex = %x ", 0x08);
		printf("int = %d ", 6);
		printf("character = %c ", 'A');
		printf("0b = %b \n", 0x10);
	}
}

2、uart.c

#include <C8051F320.h>
#include "uart.h"	//头文件只有外部函数声明

#ifndef uchar
	#define uchar unsigned char
#endif
#ifndef uint
	#define uint  unsigned int
#endif

#ifndef SYSCLK
	#define SYSCLK 12000000		//系统频率
#endif
#ifndef BAUDRATE
	#define BAUDRATE  115200		//串口波特率
#endif

bit busy;

//串口初始化
void Uart0_Init(void)
{
	uchar temp;
	
	SCON0 = 0x10;		//UART0 接收允许

	temp = SYSCLK / BAUDRATE / 2 / 256;
	if(temp < 1) {
		TH1 = -(SYSCLK / BAUDRATE / 2);
		CKCON &= ~0x0B;                  // T1M = 1; SCA1:0 = xx,定时器 1 使用系统时钟
		CKCON |=  0x08;
	} else if(temp < 4) {
		TH1 = -(SYSCLK / BAUDRATE / 2 / 4);
		CKCON &= ~0x0B;                  // T1M = 0; SCA1:0 = 01,定时器 1 使用由分频位(SCA1-SAC0)定义的时钟。系统时钟/4
		CKCON |=  0x01;
	} else if(temp < 12) {
		TH1 = -(SYSCLK / BAUDRATE / 2 / 12);
		CKCON &= ~0x0B;                  // T1M = 0; SCA1:0 = 00,定时器 1 使用由分频位(SCA1-SAC0)定义的时钟。系统时钟/12
	} else {
		TH1 = -(SYSCLK / BAUDRATE / 2 / 48);
		CKCON &= ~0x0B;                  // T1M = 0; SCA1:0 = 10,定时器 1 使用由分频位(SCA1-SAC0)定义的时钟。系统时钟/48
		CKCON |=  0x02;
	}

	TL1 = TH1;                          //初始化 Timer1
	TMOD &= ~0xf0;
	TMOD |=  0x20;                      //定时器 1 工作方式 2:自动重装载的 8 位计数器/定时器, TR1 =1 时定时器 1 工作
	TR1 = 1;                            //启动 Timer1

	IE |= 0x10;		//允许 UART0 中断
}

//发送串口数据
void SendData(unsigned char dat)
{
	while(busy);

	busy = 1;
	SBUF0 = dat;                 //写数据到UART数据寄存器
}

//发送字符串
/*void SendString(char *s)
{
	while(*s) {                 //检测字符串结束标志
		SendData(*s++);         //发送当前字符
	}
}*/

//中断服务程序
void uart0() interrupt 4
{
	unsigned char recv;

	if(RI0 == 1) {
		RI0 = 0;                 //清除RI0位
		recv = SBUF0;
	}

	if(TI0 == 1) {
		TI0 = 0;                 //清除TI0位
		busy = 0;               //清忙标志
	}
}

3、printf.c

#include <C8051F320.h>
#include <stdarg.h>
#include "uart.h"	//头文件只有外部函数声明
#include "printf.h"	//头文件只有外部函数声明

#ifndef uchar
	#define uchar unsigned char
#endif
#ifndef uint
	#define uint  unsigned int
#endif

//数字转字符串
void vNum2String(uint u16Number, uchar u8Base)
{
	char i, buf[33];
	char *p = buf + 33;
	uint c, n, len;

	*--p = '\0';
	len = 0;

	do {
		n = u16Number / u8Base;
		c = u16Number - (n * u8Base);

		if(c < 10) {
			*--p = '0' + c;
		} else {
			*--p = 'A' + (c - 10);
		}

		len++;
		u16Number /= u8Base;
	} while(u16Number != 0);

	if(u8Base == 2) {
		if(len < 8) {
			len = 8 - len;

			for(i = 0; i < len; i++) {
				*--p = '0';
			}
		}
	} else if(u8Base == 16) {
		*--p = '0';
	} else {

	}

	while(*p) {
		SendData(*p++);
	}

	return;
}

//printf函数
void printf(const char *fmt, ...)
{
	char *bp = (char *)fmt;		//参数列表变量之前的所有字符串
	va_list ap;					//申请参数列表变量
	char c;
	char *p;
	uint i;

	va_start(ap, fmt);			//申明最后一个传递给函数的已知的固定参数

	while((c = *bp++)) {
		if(c != '%') {
			if(c == '\n') {
				SendData('\r');
				SendData('\n');
			} else {
				SendData(c);
			}

			continue;
		}

		switch((c = *bp++)) {

			/* %d - 整数 */
			case 'd':
				//读取固定参数 fmt 后的 参数
				//参数范围在0~255,使用va_arg(ap, uchar),参数大于255,使用va_arg(ap, uint)
				vNum2String(va_arg(ap, uchar), 10);
				break;

			/* %x - 16进制 */
			case 'x':
				SendData('0');
				SendData('x');
				//读取固定参数 fmt 后的 参数
				//参数范围在0~255,使用va_arg(ap, uchar),参数大于255,使用va_arg(ap, uint)
				vNum2String(va_arg(ap, uchar), 16);
				break;

			/* %b - 2进制 */
			case 'b':
				SendData('0');
				SendData('b');
				//读取固定参数 fmt 后的 参数
				//参数范围在0~255,使用va_arg(ap, uchar),参数大于255,使用va_arg(ap, uint)
				vNum2String(va_arg(ap, uchar), 2);
				break;

			/* %c - 单个字符 */
			case 'c':
				SendData(va_arg(ap, char));
				break;

			case 'i':
				i = va_arg(ap, uint);

				if(i < 0) {
					SendData('-');
					vNum2String((~i) + 1, 10);
				} else {
					vNum2String(i, 10);
				}

				break;

			/* %s - 字符串 */
			case 's':
				p = va_arg(ap, char *);

				do {
					SendData(*p++);
				} while(*p);

				break;

			/* %% - 显示% */
			case '%':
				SendData('%');
				break;

			/* % 无法处理的参数 */
			default:
				SendData('?');
				break;

		}
	}

	return;
}

4、效果图

在这里插入图片描述