51单片机-串口程序代码

一、先上代码

usart.c

cpp 复制代码
/*
============================================================
  89C51 串口通信功能函数库
  适合初学者阅读 ------ 每一句都有注释
  作者:示例教学代码
  日期:2024
============================================================

【用法示例 - 请先看这里!】

  #include <reg51.h>

  void main(void)
  {
      // 第一步:初始化串口,设置波特率为 9600
      UART_Init();

      // 第二步:发送一个字节(例如发送字符 'A',ASCII = 0x41)
      UART_SendByte('A');

      // 第三步:发送一个字符串
      UART_SendString("Hello, 89C51!\r\n");

      // 第四步:开启总中断,开始接收数据(初始化时已开启,这里提示一下)
      // 当串口收到数据时,会自动调用中断服务函数 UART_ISR()
      // 接收到的数据存放在 rx_buffer[] 缓冲区中
      // 可以用 rx_count 知道当前缓冲区里有几个字节

      // 第五步:主循环中检查缓冲区是否有数据
      while (1)
      {
          if (rx_count > 0)             // 如果缓冲区里有数据
          {
              unsigned char ch;
              EA = 0;                   // 关总中断,防止读取时被打断
              ch = rx_buffer[0];        // 取出第一个字节
              // 把缓冲区往前移一位(简单处理方式)
              unsigned char i;
              for (i = 0; i < rx_count - 1; i++)
                  rx_buffer[i] = rx_buffer[i + 1];
              rx_count--;               // 计数减一
              EA = 1;                   // 重新开总中断

              UART_SendByte(ch);        // 把收到的字节原样发回去(回显)
          }
      }
  }

============================================================
*/

#include <usart.h>      /* 包含89C51寄存器定义头文件,里面定义了TMOD、SCON等寄存器 */

/* -------------------------------------------------------
   宏定义:波特率相关常数
   -------------------------------------------------------
   晶振频率:89C51 最常见的是 11.0592 MHz
   为什么选 11.0592?因为用它可以精确产生标准波特率,误差为 0%!
   如果用 12 MHz,波特率会有误差,通信容易出错。
   ------------------------------------------------------- */
#define FOSC        11059200UL  /* 晶振频率,单位 Hz,UL 表示无符号长整型 */
#define BAUD_RATE   9600        /* 目标波特率,常用值:9600、19200、38400 bps */

/*
   波特率计算方法(重点!):
   ------------------------------------------------
   89C51 串口工作在方式1时,使用定时器1产生波特率。
   定时器1 工作在方式2(8位自动重装),溢出时产生时钟。

   公式:
       TH1 = 256 - (FOSC / 12 / 32 / BAUD_RATE)

   拆解说明:
   - FOSC / 12:定时器的时钟频率 = 晶振 ÷ 12(每个机器周期=12个振荡周期)
                例:11059200 / 12 = 921600 Hz
   - / 32:    串口方式1每位需要采样32次
                例:921600 / 32 = 28800 Hz
   - / BAUD_RATE:计算每个波特需要溢出几次
                例:28800 / 9600 = 3
   - 256 - 结果:得到定时器初值(方式2下,TH1存的是重装值)
                例:256 - 3 = 253 = 0xFD

   所以 TH1 = 0xFD 时,波特率恰好是 9600 bps(晶振11.0592MHz)

   如果 SMOD = 1(电源控制寄存器PCON的最高位),波特率加倍:
       TH1 = 256 - (FOSC / 12 / 16 / BAUD_RATE)
   ------------------------------------------------
*/
#define TH1_VALUE   (256 - (FOSC / 12 / 32 / BAUD_RATE))
/* 上面这行:用宏自动计算 TH1 的值,这样修改 BAUD_RATE 就能自动更新 */

/* -------------------------------------------------------
   接收缓冲区定义
   ------------------------------------------------------- */
#define RX_BUF_SIZE  10         /* 缓冲区大小:最多存32个字节,可根据需要修改 */

unsigned char rx_buffer[RX_BUF_SIZE];  /* 接收数据缓冲区数组 */
unsigned char rx_count = 0;            /* 当前缓冲区中已存数据的字节数,初始为0 */


/* ============================================================
   函数:UART_Init
   功能:初始化89C51串口
   参数:无
   返回:无
   ============================================================ */
void UART_Init(void)
{
    /* --- 第1步:配置定时器1,用于产生波特率 --- */
    SCON = 0x50;
    TMOD &= 0x0F;   /* 先清除 TMOD 高4位(定时器1的配置位),保留低4位(定时器0)
                       0x0F = 0000 1111,与运算后 TMOD 高4位变0 */

    TMOD |= 0x20;   /* 设置定时器1 为 方式2(8位自动重装模式)
                       0x20 = 0010 0000,即 M1=1, M0=0,表示方式2
                       方式2优点:溢出后自动从TH1重装到TL1,不需要软件手动重装 */

    TH1 = TH1_VALUE;    /* 设置重装值(溢出后自动装入TL1的值)
                           根据波特率公式计算,9600bps时 = 0xFD = 253 */

    TL1 = TH1_VALUE;    /* 设置初始值,让定时器从这个值开始计数
                           第一次溢出时间和以后一样,保证第一个波特率周期正确 */

    TR1 = 1;            /* 启动定时器1,开始计数产生波特率
                           TR1 是 TCON 寄存器中的位,=1 表示运行 */

    /* --- 第2步:配置串口控制寄存器 SCON --- */

//    SCON = 0x50;    
                        /* 0x50 = 0101 0000,逐位说明:
                       位7 SM0 = 0  \
                       位6 SM1 = 1  / SM0=0,SM1=1:串口工作方式1(10位异步)
                                      方式1:1位起始位 + 8位数据 + 1位停止位
                       位5 SM2 = 0    多机通信位,单机通信置0
                       位4 REN = 1    允许接收,必须置1才能接收数据
                       位3 TB8 = 0    方式1不用,置0
                       位2 RB8 = 0    方式1不用,置0
                       位1 TI  = 0    发送中断标志,初始清0
                       位0 RI  = 0    接收中断标志,初始清0 */

    /* --- 第3步:开启中断 --- */

    ES = 1;     /* 开串口中断(Enable Serial port interrupt)
                   ES 是 IE 寄存器中的位,=1 允许串口产生中断 */

    EA = 1;     /* 开总中断(Enable All interrupts)
                   EA 必须=1,各个中断才能真正起作用
                   如果 EA=0,即使 ES=1 也不会触发中断 */
}


/* ============================================================
   函数:UART_SendByte
   功能:通过串口发送一个字节的数据
   参数:dat - 要发送的字节数据(0x00 ~ 0xFF)
   返回:无

   发送流程:把数据写入SBUF,硬件自动发送,等待TI=1表示发完
   ============================================================ */
void UART_SendByte(unsigned char dat)
{
    unsigned char t = 0;
//    TI = 0;         /* 清除发送中断标志
//                       TI=1 表示"上一次发送完成了",这里先清0,准备新的发送 */

    SBUF = dat;     /* 把数据写入串口发送缓冲寄存器 SBUF
                       写入后,硬件自动将数据转换成串行信号发送出去
                       89C51 中 SBUF 发送和接收共用地址,但物理上是两个不同的寄存器 */

    while (!TI)
    {
      t++;
      if(t>200)
        break;
    }    /* 等待发送完成
                       发送过程中 TI=0,发完一个字节后硬件自动将 TI 置1
                       while(!TI) 就是:只要 TI=0(还没发完),就一直等待
                       注意:这是"查询等待"方式,CPU会在这里空转,直到发完 */

    TI = 0;         /* 发送完成后,手动清除 TI 标志
                       因为这里没有用发送中断,所以要手动清TI
                       如果不清,下次调用此函数时可能出错 */
}


/* ============================================================
   函数:UART_SendString
   功能:通过串口发送一个字符串(以'\0'结尾的C字符串)
   参数:str - 指向字符串的指针,字符串必须以'\0'结尾
   返回:无

   示例:UART_SendString("Hello!\r\n");
         \r\n 是回车+换行,串口调试助手中会换行显示
   ============================================================ */
void UART_SendString(unsigned char *str)
{
    while (*str != '\0')    /* 循环条件:当前字符不是字符串结束符'\0'
                               *str 表示取指针str指向的字符
                               字符串"Hello"在内存中是 H,e,l,l,o,\0 */
    {
        UART_SendByte(*str);    /* 发送当前字符(调用上面的发送字节函数) */
        str++;                  /* 指针移向下一个字符
                                   str++ 使 str 指向字符串的下一个位置 */
    }
    /* 退出循环时,*str == '\0',字符串发送完毕 */
}


/* ============================================================
   中断服务函数:UART_ISR(串口中断服务程序)
   功能:当串口收到数据时自动执行,将数据存入缓冲区
   触发条件:RI=1(收到数据)或 TI=1(发送完成)都会触发串口中断

   中断号 4 = 串口中断(89C51固定分配)
   中断优先级可在 IP 寄存器中设置,这里使用默认低优先级

   注意:中断函数不能有参数,不能有返回值
   ============================================================ */
void UART_ISR(void) interrupt 4
{
    /* --- 处理接收中断 --- */
    if (RI == 1)                /* 判断是否是"接收完成"中断
                                   RI=1 表示串口接收到了一个完整字节
                                   (如果是TI=1则跳过,我们不处理发送中断) */
    {
        RI = 0;                 /* 立即清除接收中断标志
                                   必须用软件手动清零,否则会不断触发中断!
                                   这是初学者最容易忘记的一步 */
        
        if (rx_count < RX_BUF_SIZE)    /* 检查缓冲区是否已满
                                           如果满了就丢弃新数据,防止数组越界 */
        {
            rx_buffer[rx_count] = SBUF;     /* 从接收缓冲寄存器SBUF读出数据
                                               存入接收缓冲区的下一个空位
                                               rx_buffer[0]是第1个收到的字节
                                               rx_buffer[1]是第2个,以此类推 */
            rx_count++;                     /* 缓冲区数据计数加1 */
        }
        UART_SendByte(SBUF);     //发送收到的数据
        /* 如果 rx_count >= RX_BUF_SIZE,说明缓冲区满了,新数据被丢弃
           实际项目中可以设置一个溢出标志位来通知主程序 */
    }
    
    /* --- 处理发送中断(这里不需要处理,忽略即可)--- */
    if (TI == 1)                /* 如果是发送完成中断 */
    {
        TI = 0;                 /* 清除发送中断标志,防止反复进入中断 */
        /* 在本例中,发送使用查询方式(在SendByte中等待TI),
           所以发送中断不需要额外处理,直接清标志退出即可 */
    }
}
void rx_buffer_clear(void)
{
    rx_count = 0;
    rx_buffer[0] = '\0';
}






/* ============================================================
   【补充说明 - 常见问题】

   Q1: 为什么串口通信会乱码?
   A:  1. 检查两端波特率是否一致
       2. 检查晶振是否是 11.0592 MHz(其他晶振会有误差)
       3. 检查 TH1 值是否正确

   Q2: 发送函数卡住不动(死在while(!TI))?
   A:  检查 TR1 是否启动,检查 TI 是否被意外清零

   Q3: 接收不到数据?
   A:  1. 检查 REN=1(允许接收位)
       2. 检查 EA=1 且 ES=1(总中断和串口中断都开了)
       3. 检查 RI 是否在中断里清零了

   Q4: 缓冲区总是满?
   A:  主程序处理数据的速度太慢,增大 RX_BUF_SIZE 或加快处理

   【波特率与 TH1 对照表(晶振 11.0592 MHz,SMOD=0)】
   波特率      TH1值(十进制)  TH1值(十六进制)
   1200 bps      230              0xE6
   2400 bps      244              0xF4
   4800 bps      250              0xFA
   9600 bps      253              0xFD
   19200 bps     253 (SMOD=1)     0xFD(需要设置PCON |= 0x80)
   38400 bps     255 (SMOD=1)     0xFF(误差较大,不推荐)

============================================================ */

usart.h

cpp 复制代码
#ifndef __USART_H__
#define __USART_H__

#include <reg52.h>

void UART_Init(void);
void UART_SendByte(unsigned char dat);
void UART_SendString(char *str);
void rx_buffer_clear(void);

#endif

二、硬件连接说明

TX RX

三、代码用法

cpp 复制代码
UART_Init();



UART_SendByte('K');

UART_SendString("while\r\n");
相关推荐
JaneHan_2 小时前
STM32CubeMX+HAL+Keil5 GPIO输入 按键控制
stm32·单片机·嵌入式硬件
SariHcr1232 小时前
PG2K100千兆以太网接口速度测试
网络·嵌入式硬件·嵌入式实时数据库
平凡灵感码头2 小时前
C51 与 STM32 编程对比:从数据类型、关键字到程序结构
stm32·单片机·嵌入式硬件
LCG元2 小时前
STM32实战:基于STM32F103的HC-SR04超声波测距与OLED显示
stm32·单片机·嵌入式硬件
yoyobravery2 小时前
蓝桥杯第16届单片机
单片机·职场和发展·蓝桥杯
somi72 小时前
ARM-04-驱动-Misc ,Platform ,DTS
arm开发·单片机·嵌入式硬件·自用
never forget shyang2 小时前
CCS20.2.0使用教程
c语言·git·单片机
UTP协同自动化测试11 小时前
物联网模组测试难点 |APP指令下发+UART 响应+GPIO 电平变化,如何一次性验证?
功能测试·嵌入式硬件·物联网·模块测试
yoyobravery13 小时前
蓝桥杯第15届单片机满分
单片机·职场和发展·蓝桥杯