2228 字
11 分钟
STC 串口通信
2022-08-16

前言#

果然还是 STC 有意思,比 Arduino 有趣多了。😊

芯片使用的是 STC8G1K08A-8Pin,STC 配置 I/O 中有提到过,个人认为非常适合新手使用。

这次需要实现的是 STC 芯片通过串口接收数据,并将接收数据发送出去


自定义串口#

除了使用 STC 的烧录软件 STC-ISP 以外,建议从 STC 官网 获取对应系列芯片的手册。

配置串口波特率#

Step1. 确定芯片晶振的频率(系统工作频率)- FOSC

我使用的 STC8G 默认情况下的 IRC,内部时钟频率为 11.0582MHz

Step2. 确定定时器的重载值 - BRT

定时器的重载值由4个因素决定,分别是:定时器、定时器模式、定时器速度(时钟分频)和系统工作频率

我选择的参数是:定时器1,定时器模式0,1T(不分频),因此 BRT 的计算公式如下:

BRT=65536SYSclk4×BaudrateBRT = 65536 - \frac{SYSclk}{4 \times Baudrate}

定时器及其模式,见手册 12.2.7 定时器1模式0(16位自动重装载模式) 定时器速度(时钟分频),见手册 12.2.12 辅助寄存器1 (AUXR) 定时器的重载值计算,见手册 13.2.6 串口1模式1,模式1波特率计算公式

Step3. 代码实现

#define FOSC 11059200UL // unsigned long 晶振频率
#define BRT (65536 - FOSC / 115200 / 4) // 时钟重载值

串口状态、读写指针和数据缓冲#

STC8G.h 外,STC 官方还给出了4个全局变量,用以实现串口读写。

bit busy 表示是否写忙。当 busy = 0 时,表示 TX 引脚空闲(非写忙),可以写数据;当 busy = 1 时,表示 TX 引脚写忙,正在写数据。

char buffer[16] 数据缓冲区,当读取数据超过16个 Byte 时,从头开始写入。

char wptr写指针,即确定下一个接收到的字节位于缓冲区的位置。

char rptr读指针,即确定下一个应该发送的字节位于缓冲区的位置。

bit busy; // 写忙
char wptr; // 写指针
char rptr; // 读指针
char buffer[16]; // 数据缓冲区

串口中断#

请自行了解串口的工作方式!!

interrupt 4 是 STC8G 中的 UART1 中断源,中断请求位分别是 TIRI,中断允许位是 ES

TIRI 分别是 发送和接收中断请求寄存器,在 串口模式1(可变波特率8位数据方式) 中,数据发送或接收到停止位时,硬件会将 TIRI 置1,并向 CPU 发送中断请求,响应中断后必须软件清零。

UART1 中断源,见 11.3 STC8G 系列中断列表 TIRI 寄存器,见 13.2.1 串口1控制寄存器(SCON)

Step1. 响应 TIRI 的中断请求

Step2. TI 发送中断请求

SBUF 数据寄存器里的数据(1 Byte)已经通过 TX 引脚发送完成,将 TIbusy 复位,表示 TX 引脚空闲,可接收其他发送数据。

Step3. RI 接收中断请求

SBUF 数据寄存器里有通过 RX 引脚接收的数据(1 Byte),将 RI 复位,并把 SBUF 中的数据读取到数据缓冲区中。

判断下一个接收的字节是否超出缓冲区,如果超出就从头开始写入。

void UartIsr() interrupt 4
{
if (TI)
{
TI = 0; // 发送中断请求寄存器
busy = 0; // TX 空闲
}
if (RI)
{
RI = 0; // 接收中断请求寄存器
buffer[wptr++] = SBUF; // 数据寄存器
wptr &= 0x0f;
}
}

串口初始化#

SCON串口1控制寄存器,B0101000 表示串口1工作模式处于 模式1(可变波特率8位数据方式),并 允许串口接收数据

TMOD定时器0/1模式寄存器,B00000000 表示定时器1工作模式处于 模式0(16位自动重载模式)

TLTH 分别是 定时器1计数寄存器的 低8位和高8位

AUXR辅助寄存器1,B01000000 表示定时器1速度处于 1T模式(不分频),且串口1选择 定时器1 作为波特率发生器。

SCON,见 13.2.1 串口1控制寄存器(SCON) TMOD,见 12.2.2 定时器0/1模式寄存器(TMOD) TLTH, 见 12.2.11 定时器1计数寄存器(TL1,TH1) AUXR,见 12.2.12 & 13.2.4 辅助寄存器1(AUXR)

void UartInit()
{
SCON = 0x50; // 串口1控制寄存器
TMOD = 0x00; // 定时器0/1模式寄存器
TL1 = BRT; // 定时器1计数寄存器 低8位
TH1 = BRT >> 8; // 定时器1计数寄存器 高8位
TR1 = 1; // 定时器1开始计时
AUXR = 0x40; // 辅助寄存器1
wptr = 0x00;
rptr = 0x00;
busy = 0;
}

数据发送#

UartSend(char)发送单个字节

UartSendStr(char *)发送字符串

void UartSend(char dat)
{
while (busy); // TX正在发送数据,等待空闲
busy = 1;
SBUF = dat;
}
void UartSendStr(char *p)
{
while (*p)
{
UartSend(*p++);
}
}

数据接收#

串口中断 中有实现。

main() 主函数#

ES串行口1中断允许位

EA总中断允许控制位

ESEA,见 11.4.1 中断使能寄存器(中断允许位)

void main()
{
// 准双向口
P3M0 = 0x00;
P3M1 = 0x00;
UartInit();
ES = 1; // 使能串口1中断
EA = 1; // CPU开放中断
UartSendStr("Uart Test !\r\n");
...
}

其他#

下面是测试的结果:

支持 ASCII编码的所有字符,且支持中文和中文标点符号。


完整代码#

代码部分来源于 STC-ISP 中内置的 STC8G 系列 - 定时器1(模式0)做串口1波特率发生器 - C

代码 - 点击查看详情
#include <STC8G.h>
#include <intrins.h>
#define FOSC 11059200UL
#define BRT (65536 - FOSC / 115200 / 4)
bit busy;
char wptr;
char rptr;
char buffer[16];
void UartIsr() interrupt 4
{
if (TI)
{
TI = 0;
busy = 0;
}
if (RI)
{
RI = 0;
buffer[wptr++] = SBUF;
wptr &= 0x0f;
}
}
void UartInit()
{
SCON = 0x50;
TMOD = 0x00;
TL1 = BRT;
TH1 = BRT >> 8;
TR1 = 1;
AUXR = 0x40;
wptr = 0x00;
rptr = 0x00;
busy = 0;
}
void UartSend(char dat)
{
while (busy);
busy = 1;
SBUF = dat;
}
void UartSendStr(char *p)
{
while (*p)
{
UartSend(*p++);
}
}
void delayMicroseconds(unsigned int times)
{ // @11.0592MHz
do
{
_nop_();
} while (--times);
}
void delayMilliseconds(unsigned int times)
{
do
{
delayMicroseconds(1000);
} while (--times);
}
void main()
{
P3M0 = 0x00;
P3M1 = 0x00;
P5M0 = 0x00;
P5M1 = 0x00;
UartInit();
ES = 1;
EA = 1;
delayMilliseconds(1000);
UartSendStr("Uart Test !\r\n");
while (1)
{
if (rptr != wptr)
{
UartSend(buffer[rptr++]);
rptr &= 0x0f;
}
}
}

stdio.h 标准库#

2022-08-17 看到了 C51 的标准输入输出库,就尝试了一下

配置方法#

Step1. 在代码中导入 #include <stdio.h>

Step2. STC-ISP 生成串口配置

进入 STC-ISP 的界面,调整系统频率、波特率等参数,生成对应的串口初始化代码:

Step3. 初始化发送中断请求寄存器,并使能中断源

TI = 1;
ES = 1; // 使能 UART1 中断
EA = 1; // 使能 总中断

TI = 1,必须使能 TI,否则无法正常读写。

printf & scanf#

用法一如既往。

extern int printf (const char *, …);

printf("Hello World!");
int num = 123;
printf("Hello World! %d", num);

extern int scanf (const char *, …);

int num;
scanf("%d", &num)
char c;
scanf("%c", &c)

其他#

2022-08-18 C51 的 stdio.h 库的说明书好难找啊!! 2022-09-14 找到了 C51 的官方开发手册,Cx51 User Guide

1. scanf() 读取流程

scanf() 在获取输入的同时,还会把输入内容再打印出来。

2. printf() 双精度浮点型的转换问题

使用 printf() 进行浮点数输出时,需要避免使用 double(%lf) 类型,尽量使用 float(%f) 类型,我在使用 double 类型时无法正常输出小数点。

3. stdio.h 标准库的体积

使用 stdio.h 标准库和使用自定义串口相比,实现相同功能,代码长了近 2000B,而且使用自定义串口的代码总长也就才 2600B。

2022-08-19

不推荐在使用软接口的情况下(SPI、PWM等),使用 stdio.h 标准库。

我今天测试的项目使用了软 SPI 接口,然后发现在调用 stdio.h 的情况下,无法进行正常的数据传输,我尝试在软 SPI 传输前,关闭总中断控制 EA = 0 或手动调整 UART1 的寄存器也没有办法。

后续会尝试硬 SPI 接口和 stdio.h 标准库的兼容性。

完整代码#

代码 - 点击查看详情
#include <STC8G.h>
#include <intrins.h>
#include <stdio.h>
char dat;
void UartInit();
void main()
{
// I/O 设置
P3M0 = 0x00;
P3M1 = 0x00;
delayMilliseconds(500);
// UART1 初始化
UartInit();
printf("Uart Test !\r\n");
delayMilliseconds(500);
while (1)
{
scanf("%c", &dat);
// printf("\r\n");
// printf("input: %c", dat);
}
}
void delayMicroseconds(unsigned int times)
{ // @11.0592MH
do {
_nop_();
} while (--times);
}
void delayMilliseconds(unsigned int times)
{
do {
delayMicroseconds(950);
} while (--times);
}
void UartInit(void)
{ //115200bps@11.0592MHz
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器时钟1T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TL1 = 0xE8; //设置定时初始值
TH1 = 0xFF; //设置定时初始值
ET1 = 0; //禁止定时器%d中断
TR1 = 1; //定时器1开始计时
ES = 1; // 使能 UART1 中断
EA = 1; // 使能 总中断
TI = 1;
}
STC 串口通信
https://fuwari.vercel.app/posts/嵌入式/stc/stc-串口通信/
作者
Asuwee
发布于
2022-08-16
许可协议
CC BY-NC-SA 4.0