STC8H1K08_介绍与应用(2)

STC8H1K08_介绍与应用(2)

image-20260702201627265

image-20260702201812184

STC8H 系列单片机具有4个全双工异步串行通信模块。每个串行口由2个数据缓冲器、一个移位寄存器、一个串行控制寄存器和一个波特率发生器等组成。每个串行口的数据缓冲器由2个互相独立的接收、发送缓冲器构成,可以同时发送和接收数据。

STC8 系列单片机的串口 1有4种工作方式,其中两种方式的波特率是可变的,另两种是固定的,以供不同应用场合选用。串口 2/串口 3/串口 4都只有两种工作模式,这两种方式的波特率都是可变的。用户可用软件设置不同的波特率和选择不同的工作方式。主机可通过查询或中断方式对接收/发送进行程序处理,使用十分灵活。

串口 1、串口 2、串口 3、串口 4的通讯口均可以通过功能管脚的切换功能切换到多组端口,从而可以将一个通讯口分时复用为多个通讯口。

image-20260702203200508

串口 1_控制寄存器(SCON)

image-20260702203355470

SM0/FE:当PCON寄存器中的SMOD0位为1时,该位为帧错误检测标志位。当UART在接收过程中检测到一个无效停止位时,通过UART接收器将该位置1,必须由软件清零。当PCON寄存器中的SMOD0位为0时,该位和SM1一起指定串口1的通信工作模式,如下表所示:

image-20260702203830686

SM2:允许模式 2或模式 3多机通信控制位。当串口1使用模式 2 或模式 3 时,如果 SM2 位为 1 且 REN 位为 1,则接收机处于地址帧筛选状态。此时可以利用接收到的第 9 位(即 RB8)来筛选地址帧,若 RB8 = 1,说明该帧是地址帧,地址信息可以进入 SBUF,并使 RI 为1,进而在中断服务程序中再进行地址号比较;若 RB8 = 0,说明该位不是地址帧,应丢掉且保持 RI = 0。在模式 2或模式 3中,如果 SM 2位为 0 且 REN 位为 1,接收机处于地址帧选被禁止状态,不论收到的 RB8 为 0 或 1,均可使接收到的信息进入 SBUF,并使 RI = 1,此时 RB8 通常为校验位。模式 1和模式 0 为非多机通信方式,在这两种方式时,SM2 应设置为 0.

REN:允许/禁止串口接收控制位。

  • 0:禁止串口接收数据
  • 1:允许串口接收数据

TB8:当串口 1 使用模式 2 或模式 3 时,TB8 为要发送的第 9 位数据,按需要由软件置位或清 0。在模式 0 和模式 1 中,该位不用。
RB8:当串口 1 使用模式 2 或模式 3 时,RB8 为接收到的第 9 位数据,一般用作校验位或者地址帧/数据帧标志位。在模式 0 和模式 1 中,该位不用。
TI:串口 1 发送中断请求标志位。在模式 0 中,当串口发送数据第 8 位结束时,由硬件自动将 TI 置 1,向主机请求中断,响应中断后 TI 必须用软件清零。在其他模式中,则在停止位开始发送时由硬件自动将 TI 置 1,向 CPU 发请求中断,响应中断后 TI 必须用软件清零。
RI:串口 1接收中断请求标志位。在模式 0中,当串口接收第 8 位数据结束时,由硬件自动将 RI 置 1,向主机请求中断,响应中断后 RI 必须用软件清零。在其他模式中,串行接收到停止位的中间时刻由硬件自动将 RI 置 1,向 CPU 发中断申请,响应中断后 RI 必须由软件清零。
综上,配置串口 1使用模式 1(可变波特率8位数据方式)时,只需要设置SM0、SM1、REN,TI和RI则是在收发响应中断后软件清零。

串口1数据寄存器SBUF

串口1的数据读写都是对同个寄存器SBUF进行操作

image-20260702205745862

SBUF:串口 1 数据接收/发送缓冲区。SBUF 实际是 2 个缓冲器,读缓冲器和写缓冲器,两个操作分别对应两个不同的寄存器,1个是只写寄存器(写缓冲器),1个是只读寄存器(读缓冲器)。对SBUF进行读操作,实际是读取串口接收缓冲区,对 SBUF 进行写操作则是触发串口开始发送数据。

串口 1_模式 1工作方式

当软件设置 SCON 的 SM0、SM1 为“01”时,串行口 1 则以模式 1 进行工作。此模式为 8 位 UART格式,一帧信息为 10 位:1 位起始位,8 位数据位(低位在先)和 1 位停止位。波特率可变,即可根据需要进行设置波特率。TxD 为数据发送口,RxD 为数据接收口,串行口全双工接受/发送。

模式 1 的发送过程

串行通信模式发送时,数据由串行发送端 TxD 输出。当主机执行一条写 SBUF的指令就启动串行通信的发送,写“SBUF”信号还把“1”装入发送移位寄存器的第 9 位,并通知 TX控制单元开始发送。移位寄存器将数据不断右移送 TxD 端口发送,在数据的左边不断移入“0”作补充。当数据的最高位移到移位寄存器的输出位置,紧跟其后的是第 9 位“1”,在它的左边各位全为“0”,这个状态条件,使 TX 控制单元作最后一次移位输出,然后使允许发送信号“SEND”失效,完成一帧信息的发送,并置位中断请求位 TI,即 TI=1,向主机请求中断处理。

模式 1 的接收过程

当软件置位接收允许标志位 REN,即 REN=1 时,接收器便对 RxD 端口的信号
进行检测,当检测到 RxD 端口发送从“1”→“0”的下降沿跳变时就启动接收器准备接收数据,并立即复位波特率发生器的接收计数器,将 1FFH 装入移位寄存器。接收的数据从接收移位寄存器的右边移入,已装入的 1FFH 向左边移出,当起始位"0"移到移位寄存器的最左边时,使 RX 控制器作最后一次移位,完成一帧的接收。若同时满足以下两个条件:

  1. RI=0;
  2. SM2=0 或接收到的停止位为 1。

则接收到的数据有效,实现装载入 SBUF,停止位进入 RB8,RI 标志位被置 1,向主机请求中断,若上述两条件不能同时满足,则接收到的数据作废并丢失,无论条件满足与否,接收器重又检测 RxD 端口上的"1"→"0"的跳变,继续下一帧的接收。接收有效,在响应中断后,RI 标志位必须由软件清 0。通常情况下,串行通信工作于模式 1 时,SM2 设置为"0"。

image-20260702210711002

串口1波特率计算方式

串口 1 的波特率是可变的,其波特率可由定时器 1 或者定时器 2 产生。当定时器采用 1T 模式时(12倍速),相应的波特率的速度也会相应提高 12 倍。
串口 1 模式 1 的波特率计算公式如下表所示:(SYSclk 为系统工作频率)

image-20260702210802903

下面为常用频率与常用波特率所对应定时器的重载值:

image-20260702210835288

如果对于计算方式比较头疼的话,可以用万能的STC-ISP工具帮忙,如图写入配置参数,直接生成串口初始化代码:

image-20260702211004502

串口注意事项

关于串口中断请求有如下问题需要注意:(串口 1、串口 2、串口 3、串口 4 均类似,下面以串口 1为例进行说明)
8 位数据模式时,发送完成约 1/3 个停止位后产生 TI 中断请求,如下图所示:

image-20260702211137006

8 位数据模式时,接收完成一半个停止位后产生 RI 中断请求,如下图所示:

image-20260702211204741

// 引入STC8H系列单片机寄存器头文件,定义所有IO、串口、定时器寄存器
#include "stc8h.h"
// 引入51单片机内置函数库,本例暂时没用到,保留标准写法
#include "intrins.h"
// 引入标准输入输出库,用来支持printf打印
#include <stdio.h>

// 定义系统IRC频率 11.0592MHz,单位HZ
#define FOSC 11059200UL
// 目标串口波特率115200
#define BAUD 115200UL
// 计算定时器2初值,作为串口波特率发生器
// T2不分频,分频系数4,公式:65536 - 系统时钟/(4*波特率)
#define BRT_VAL (65536UL - FOSC / (4UL * BAUD))

// bit型变量,只有0/1两种状态,标记串口是否正在发送数据
bit busy;  
// 接收缓冲区写指针:中断收到数据存在buffer哪里
unsigned char wptr; 
// 接收缓冲区读指针:主循环从buffer哪里读出数据
unsigned char rptr; 
// 16字节接收环形缓冲区,缓存串口收到的数据
unsigned char buffer[16];

/**********************************************************************
* 函数名:UartIsr
* 功能:串口1中断服务函数,中断号4(51单片机固定串口1中断编号)
* 触发时机:1.发送完1字节数据 2.收到1字节数据
**********************************************************************/
void UartIsr() interrupt 4 
{
	// 判断是不是【发送完成】触发的中断(T1发送中断标志位)
	if (TI)
	{
		TI = 0;				             // 软件手动清零发送中断标志,硬件不会自动清0
		busy = 0;			             // 发送忙标志清零,代表当前字节发送完毕,可以发下一个
	}
	// 判断是不是【收的数据】触发的中断(RI接收中断标志位)
	if (RI)
	{
		RI = 0;                          // 软件手动清零接收中断标志
		buffer[wptr++] = SBUF;		     // SBUF是串口数据寄存器,读取收到的字节存入缓冲区
									     // 写指针往后挪一位,准备存下一个数据
		wptr &= 0x0f;                    // 缓冲区只有16格(0~15),超过15自动回到0,环形缓存
	}
}

/**********************************************************************
* 函数名:UartInit
* 功能:串口1初始化,配置模式、波特率、缓存指针初始值
**********************************************************************/
void UartInit(void)
{
	SCON = 0x50;            // 串口控制寄存器配置
							// SM0=0,SM1=1 → 模式1:8位异步串口
							// REN=1 → 开启单片机硬件接收功能,能接收外部发来的数据
	T2L = BRT_VAL;          // 定时器2低8位装载初值
	T2H = BRT_VAL >> 8;     // 定时器2高8位装载初值
	AUXR |= 0x15;           // 辅助寄存器配置定时器2
							// S1T2= 1:串口1使用定时器2产生波特率
							// T2R = 1:启动定时器2开始计数
							// T2x12=1:定时器2直接使用系统晶振,不分频,波特率更精准
	wptr = 0x00;			// 缓存写指针初始化为0,从缓冲区第0格开始存
	rptr = 0x00;            // 缓存读指针初始化为0,从缓冲区第0格开始读
	busy = 0;               // 初始状态:串口空闲,没有在发送数据
}

/**********************************************************************
* 函数名:UartSend
* 输入参数:dat 需要发送的1字节数据
* 功能:阻塞发送单个字节,等上一字节发完再发下一个,防止乱码
**********************************************************************/
void UartSend(unsigned char dat)
{
	while (busy);            // 循环等待,直到串口发送空闲
	busy = 1;				 // 标记串口现在正在发送,禁止新数据插入
	SBUF = dat;				 // 把数据写入串口寄存器,硬件自动向外发送
}

/**********************************************************************
* 函数名:UartSendStr
* 输入参数:p 字符串首地址
* 功能:循环发送一整串字符串,直到字符串结束符'\0'为止
**********************************************************************/
void UartSendStr(char *p)
{
	// 判断当前字符不是结束符就持续发送
	while (*p != '\0')
	{
		UartSend(*p++);   // 发送当前字符
						  // 指针后移,读取下一个字符
	}
}

/**********************************************************************
* 函数名:main
* 功能:程序入口主函数,上电后最先执行,死循环一直运行
**********************************************************************/
void main(void)
{
	// STC8H特有:开启XFR扩展寄存器访问权限,不开启无法配置定时器2、串口增强功能
	P_SW2 |= 0x80;        

	// 配置全部IO口为【准双向口】,单片机上电默认模式,输入输出通用
	P0M0 = 0x00; P0M1 = 0x00;
	P1M0 = 0x00; P1M1 = 0x00;
	P2M0 = 0x00; P2M1 = 0x00;
	P3M0 = 0x00; P3M1 = 0x00;
	P4M0 = 0x00; P4M1 = 0x00;
	P5M0 = 0x00; P5M1 = 0x00;

	UartInit();     // 调用函数,初始化串口
	ES = 1;			// ES=1:开启串口1单独中断
	EA = 1;			// EA=1:开启单片机总中断,所有中断必须打开总中断才能生效

	// 上电先打印一串提示文字,\r是回车,\n是换行
	UartSendStr("Uart Test !\r\n");
	
	// 无限死循环,单片机上电后一直在这里循环运行
	while (1)
	{
		// 读写指针不相等 = 缓冲区里存了收到的数据
		if (rptr != wptr)
		{
			// 读取缓冲区数据,通过串口发出去(回显功能)
			UartSend(buffer[rptr++]);
			// 读指针超过15归零,环形缓存
			rptr &= 0x0f;
		}
	}
}
ES = 1;			// ES=1:开启串口1单独中断
EA = 1;			// EA=1:开启单片机总中断,所有中断必须打开总中断才能生效

执行顺序说明

代码里先写 ES=1 再写 EA=1 是规范写法:

  1. 先把外设分开关配置好(提前打开串口中断允许);
  2. 最后打开全局总中断 EA,避免中途没配置完就意外触发中断导致程序错乱。

image-20260702220208569

串口重定向

与32的串口重定向函数fputc函数和fgetc函数不同,51单片机串口重定向给重写的是putchar函数,然后可以使用printf函数往串口进行打印,可在串口uart.c文件中添加以下部分:

char putchar(char c)
{
    UartSend(c);
    return c;
}

逐行翻译

  1. char putchar(char c)
    函数,接收 1 个字符 c(比如 'A'、'1'、换行符)
  2. UartSend(c);
    调用你自己写好的单字节发送函数
    UartSend 内部自带 while(busy) 等待,确保串口不冲突,不会乱码。
  3. return c;
    标准规定 putchar 必须返回发送出去的字符,固定写法,照抄就行。
// 引入STC8H系列单片机寄存器头文件,定义所有IO、串口、定时器寄存器
#include "stc8h.h"
// 引入51单片机内置函数库,_nop_延时使用
#include "intrins.h"
// 引入标准输入输出库,用来支持printf打印
#include <stdio.h>

// 定义系统IRC频率 11.0592MHz,单位HZ
#define FOSC 11059200UL
// 目标串口波特率115200
#define BAUD 115200UL
// 计算定时器2初值,作为串口波特率发生器
// T2不分频,分频系数4,公式:65536 - 系统时钟/(4*波特率)
#define BRT_VAL (65536UL - FOSC / (4UL * BAUD))
// 串口接收环形缓冲区大小
#define UART_BUF_LEN 16

// bit型变量,只有0/1两种状态,标记串口是否正在发送数据
bit busy;
// 接收缓冲区写指针:中断收到数据存在buffer哪里
unsigned char wptr;
// 接收缓冲区读指针:主循环从buffer哪里读出数据
unsigned char rptr;
// 16字节接收环形缓冲区,缓存串口收到的数据
unsigned char buffer[UART_BUF_LEN];

/**********************************************************************
* 函数名:UartIsr
* 功能:串口1中断服务函数,中断号4(51单片机固定串口1中断编号)
* 触发时机:1.发送完1字节数据 2.收到1字节数据
**********************************************************************/
void UartIsr() interrupt 4
{
    // 判断是不是【发送完成】触发的中断(T1发送中断标志位)
    if (TI)
    {
        TI = 0;                            // 软件手动清零发送中断标志,硬件不会自动清0
        busy = 0;                          // 发送忙标志清零,代表当前字节发送完毕,可以发下一个
    }
    // 判断是不是【收的数据】触发的中断(RI接收中断标志位)
    if (RI)
    {
        RI = 0;                            // 软件手动清零接收中断标志
        // 缓冲区未满才存入,防止覆盖未读取数据
        if (((wptr + 1) & 0x0F) != rptr)
        {
            buffer[wptr++] = SBUF;         // SBUF是串口数据寄存器,读取收到的字节存入缓冲区
            wptr &= 0x0f;                  // 环形缓存指针回卷
        }
    }
}

/**********************************************************************
* 函数名:UartInit
* 功能:串口1初始化,模式1、T2波特率发生器、开启接收
**********************************************************************/
void UartInit(void)
{
    SCON = 0x50;            // SM0=0,SM1=1 8位异步串口; REN=1 硬件接收使能
    T2L = BRT_VAL;          // 定时器2低8位装载初值
    T2H = BRT_VAL >> 8;     // 定时器2高8位装载初值
    AUXR |= 0x15;           // S1T2=1(串口1使用T2)、T2R=1(开启T2)、T2x12=1(T2不分频)

    wptr = 0x00;            // 缓存指针初始化
    rptr = 0x00;
    busy = 0;               // 发送空闲标志
}

/**********************************************************************
* 函数名:UartSend
* 输入参数:dat 需要发送的1字节数据
* 功能:阻塞发送单个字节,使用busy互斥,兼容printf
**********************************************************************/
void UartSend(unsigned char dat)
{
    while (busy);            // 等待上一字节发送完成
    busy = 1;                // 标记发送占用
    SBUF = dat;              // 写入发送寄存器
}

/**********************************************************************
* 函数名:UartSendStr
* 输入参数:p 字符串首地址
* 功能:循环发送字符串直到'\0'结束
**********************************************************************/
void UartSendStr(char *p)
{
    while (*p != '\0')
    {
        UartSend(*p++);
    }
}

/**********************************************************************
* 函数名:putchar
* 功能:重定向printf输出到串口1,统一使用UartSend互斥逻辑
**********************************************************************/
char putchar(char c)
{
    UartSend(c);
    return c;
}

/**********************************************************************
* 函数名:Delay_ms
* 功能:@24.000MHz下毫秒级延时
**********************************************************************/
void Delay_ms(unsigned int ms)
{
    unsigned int t;
    unsigned char i, j;
    for (t = 0; t < ms; t++)
    {
        _nop_();
        i = 32;
        j = 40;
        do
        {
            while (--j);
        } while (--i);
    }
}

/**********************************************************************
* 函数名:main
* 功能:程序入口,上电初始化,循环回显接收数据+周期打印日志
**********************************************************************/
void main(void)
{
    // STC8H特有:开启XFR扩展寄存器访问权限,否则T2/AUXR无法配置
    P_SW2 |= 0x80;

    // 全部IO配置为准双向口(上电默认)
    P0M0 = 0x00; P0M1 = 0x00;
    P1M0 = 0x00; P1M1 = 0x00;
    P2M0 = 0x00; P2M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
    P4M0 = 0x00; P4M1 = 0x00;
    P5M0 = 0x00; P5M1 = 0x00;

    UartInit();     // 串口仅初始化一次
    ES = 1;         // 串口1中断使能
    EA = 1;         // 全局总中断打开

    // 上电打印启动信息
    UartSendStr("Uart Test Start!\r\n");

    // 无限主循环
    while (1)
    {
        // 缓冲区存在未读取数据:回显收到的字符
        if (rptr != wptr)
        {
            UartSend(buffer[rptr++]);
            rptr &= 0x0f;
        }

        // 每500ms打印一次Hello World
        printf("Hello World\r\n");
        Delay_ms(500);
    }
}
posted @ 2026-07-02 22:26  Q&25  阅读(23)  评论(0)    收藏  举报