前言
果然还是 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 的计算公式如下:
定时器及其模式,见手册 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 中断源,中断请求位分别是 TI
和 RI
,中断允许位是 ES
。
TI
和 RI
分别是 发送和接收中断请求寄存器,在 串口模式1(可变波特率8位数据方式) 中,数据发送或接收到停止位时,硬件会将 TI
和 RI
置1,并向 CPU 发送中断请求,响应中断后必须软件清零。
UART1
中断源,见 11.3 STC8G 系列中断列表TI
和RI
寄存器,见 13.2.1 串口1控制寄存器(SCON)
Step1. 响应 TI
或 RI
的中断请求
Step2. TI
发送中断请求
SBUF
数据寄存器里的数据(1 Byte)已经通过 TX 引脚发送完成,将 TI
和 busy
复位,表示 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位自动重载模式)。
TL
和 TH
分别是 定时器1计数寄存器的 低8位和高8位。
AUXR
是 辅助寄存器1,B01000000 表示定时器1速度处于 1T模式(不分频),且串口1选择 定时器1 作为波特率发生器。
SCON
,见 13.2.1 串口1控制寄存器(SCON)TMOD
,见 12.2.2 定时器0/1模式寄存器(TMOD)TL
和TH
, 见 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
是 总中断允许控制位。
ES
和EA
,见 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;}