目录
[1.波特率(Baud Rate)](#1.波特率(Baud Rate))
[3.SBUF寄存器(Serial Buffer)](#3.SBUF寄存器(Serial Buffer))
前言
关于蓝桥杯比赛时会提供的资料前几篇都有提到,这里就不在赘述了,只放一个下载链接:
单片机资源数据包_2023
除了基础部分的按键、LED灯,数码管扫描,还有温度传感器,AD/DA转化,EEPROM存储器,RTC之外,还有三个模块考试的时候可能会考,分别是超声波,NE555和串口。近几年的题也是越来越难,这三个模块也逐渐出现在了省赛的舞台上(当然如果进国赛了,这几个模块就都可能考了)。提升篇主要针对这三个模块进行介绍。
由于这三个模块比赛时不会提供底层代码,所以许多都需要咱们自己来完成,所以不同人写的代码,差异性可能会更大。此外这些代码会涉及到单片机运行的底层知识,关于单片机基础部分的内容,提升篇也会尽可能介绍一部分(当然如果你不会也没关系,文章会教你如何用stc生成或者查数据手册,就算不知道原理,小背一背也是能自己实现的)
一、串口收发数据的介绍
串口收发数据简单点说就是单片机给电脑发数据,电脑给单片机发数据,平时下载程序就是电脑在给单片机发数据。我们在收发数据之前,首先要调整好COM以及波特率。COM口我们可以在设备管理器------端口处查看,一般情况下stc---isp都能正确扫描到COM口。
本篇文章关于串口的部分会着重告诉大家如何去找代码,而非自己去写这串代码,因为这串代码虽然考试时不会给,但是isp里面提供的有现成的代码,至少底层代码不用自己写了,可以直接去移植。这里先介绍一些串口有关的基础知识(当然比赛时用不到),当然也不用记,第二章会告诉大家如何获取与修改现成的代码。
1.波特率(Baud Rate)
波特率是表示每秒传输的位数,通常用波特(bps)来表示。在串口通信中,发送方和接收方必须配置相同的波特率才能正确地进行数据传输。STC15F2K单片机的串口模块可以通过设定计数器的值来实现不同的波特率,也就是说在使用串口时,需要一个定时器/计时器作为波特率发生器。
2.帧格式
帧格式指的是如何将数据转换为连续的位流进行传输。通常包括起始位(Start Bit)、数据位(Data Bits)、校验位(Parity Bit)和停止位(Stop Bit)。起始位指示数据的传输开始,停止位指示数据的传输结束,数据位是实际要传输的数据,而校验位用于检验数据传输的正确性。常见的帧格式包括8N1(8个数据位,无校验位,1个停止位)和8E1(8个数据位,偶校验,1个停止位)。
3.SBUF寄存器(Serial Buffer)
SBUF寄存器是STC15F2K单片机中用于串口通信的特殊寄存器。
对于发送数据,在发送之前,将要发送的数据写入SBUF寄存器。当发送完成后,SBUF寄存器会自动清空等待下一次写入。对于接收数据,接收到的数据会存放在SBUF寄存器中,然后可以通过读取SBUF寄存器来获取接收到的数据。
数据传输:对于发送数据,可以通过将要发送的数据写入SBUF寄存器,然后通过串口模块发送出去。发送数据时,需要检查串口发送完成标志位(TI)是否被置位,以避免数据的丢失。对于接收数据,可以通过读取SBUF寄存器来获取接收到的数据。接收数据时,需要检查串口接收完成标志位(RI)是否被置位,以判断是否有新的数据到达。
写成代码的话,unsigned char da;da=SBUF就是读数据,SBUF=da就是写数据了,当然,这样说太理想了,实际上还需要一些其他处理。SBUF其实是两个寄存器,只是名字一样而已,在发送和接受时虽然都是SBUF,但是对应的寄存器其实是不相同的。
4.中断处理
在接收数据时,可以使用串口接收中断来实现异步接收数据的功能,以提高系统的实时性和效率。串口中断跟定时器中断一样,不过定时器中断是每隔一定时间进一次中断,而串口中断是每次接收到数据之后,就会进一次中断。
二、如何从stc-isp获取串口收发数据的代码
1.代码的获取
stc-isp有一个"范例程序"的功能,在菜单中选择stc15系列单片机,就能找到许多范例程序,这里我们只看串口收发数据的,这里以定时器1模式0(16为自动重载)作为波特率发生器,其他定时器作为波特率发生器的代码也都一样。另外,比赛时一定要合理安排单片机资源。
下面是stc-isp复制过来的代码,这个代码是带有main函数的,我们后续还要对其进行修改:
cpp
/*---------------------------------------------------------------------*/
/* --- STC MCU Limited ------------------------------------------------*/
/* --- STC15F4K60S4 系列 定时器1用作串口1的波特率发生器举例------------*/
/* --- Mobile: (86)13922805190 ----------------------------------------*/
/* --- Fax: 86-0513-55012956,55012947,55012969 ------------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966-------------------------*/
/* --- Web: www.STCMCU.com --------------------------------------------*/
/* --- Web: www.GXWMCU.com --------------------------------------------*/
/* 如果要在程序中使用此代码,请在程序中注明使用了STC的资料及程序 */
/* 如果要在文章中应用此代码,请在文章中注明使用了STC的资料及程序 */
/*---------------------------------------------------------------------*/
//本示例在Keil开发环境下请选择Intel的8058芯片型号进行编译
//若无特别说明,工作频率一般为11.0592MHz
#include "reg51.h"
#include "intrins.h"
typedef unsigned char BYTE;
typedef unsigned int WORD;
#define FOSC 11059200L //系统频率
#define BAUD 115200 //串口波特率
#define NONE_PARITY 0 //无校验
#define ODD_PARITY 1 //奇校验
#define EVEN_PARITY 2 //偶校验
#define MARK_PARITY 3 //标记校验
#define SPACE_PARITY 4 //空白校验
#define PARITYBIT NONE_PARITY //定义校验位
sfr P0M1 = 0x93;
sfr P0M0 = 0x94;
sfr P1M1 = 0x91;
sfr P1M0 = 0x92;
sfr P2M1 = 0x95;
sfr P2M0 = 0x96;
sfr P3M1 = 0xb1;
sfr P3M0 = 0xb2;
sfr P4M1 = 0xb3;
sfr P4M0 = 0xb4;
sfr P5M1 = 0xC9;
sfr P5M0 = 0xCA;
sfr P6M1 = 0xCB;
sfr P6M0 = 0xCC;
sfr P7M1 = 0xE1;
sfr P7M0 = 0xE2;
sfr AUXR = 0x8e; //辅助寄存器
sfr P_SW1 = 0xA2; //外设功能切换寄存器1
#define S1_S0 0x40 //P_SW1.6
#define S1_S1 0x80 //P_SW1.7
sbit P22 = P2^2;
bit busy;
void SendData(BYTE dat);
void SendString(char *s);
void main()
{
P0M0 = 0x00;
P0M1 = 0x00;
P1M0 = 0x00;
P1M1 = 0x00;
P2M0 = 0x00;
P2M1 = 0x00;
P3M0 = 0x00;
P3M1 = 0x00;
P4M0 = 0x00;
P4M1 = 0x00;
P5M0 = 0x00;
P5M1 = 0x00;
P6M0 = 0x00;
P6M1 = 0x00;
P7M0 = 0x00;
P7M1 = 0x00;
ACC = P_SW1;
ACC &= ~(S1_S0 | S1_S1); //S1_S0=0 S1_S1=0
P_SW1 = ACC; //(P3.0/RxD, P3.1/TxD)
// ACC = P_SW1;
// ACC &= ~(S1_S0 | S1_S1); //S1_S0=1 S1_S1=0
// ACC |= S1_S0; //(P3.6/RxD_2, P3.7/TxD_2)
// P_SW1 = ACC;
//
// ACC = P_SW1;
// ACC &= ~(S1_S0 | S1_S1); //S1_S0=0 S1_S1=1
// ACC |= S1_S1; //(P1.6/RxD_3, P1.7/TxD_3)
// P_SW1 = ACC;
#if (PARITYBIT == NONE_PARITY)
SCON = 0x50; //8位可变波特率
#elif (PARITYBIT == ODD_PARITY) || (PARITYBIT == EVEN_PARITY) || (PARITYBIT == MARK_PARITY)
SCON = 0xda; //9位可变波特率,校验位初始为1
#elif (PARITYBIT == SPACE_PARITY)
SCON = 0xd2; //9位可变波特率,校验位初始为0
#endif
AUXR = 0x40; //定时器1为1T模式
TMOD = 0x00; //定时器1为模式0(16位自动重载)
TL1 = (65536 - (FOSC/4/BAUD)); //设置波特率重装值
TH1 = (65536 - (FOSC/4/BAUD))>>8;
TR1 = 1; //定时器1开始启动
ES = 1; //使能串口中断
EA = 1;
SendString("STC15F2K60S2\r\nUart Test !\r\n");
while(1);
}
/*----------------------------
UART 中断服务程序
-----------------------------*/
void Uart() interrupt 4
{
if (RI)
{
RI = 0; //清除RI位
P0 = SBUF; //P0显示串口数据
P22 = RB8; //P2.2显示校验位
}
if (TI)
{
TI = 0; //清除TI位
busy = 0; //清忙标志
}
}
/*----------------------------
发送串口数据
----------------------------*/
void SendData(BYTE dat)
{
while (busy); //等待前面的数据发送完成
ACC = dat; //获取校验位P (PSW.0)
if (P) //根据P来设置校验位
{
#if (PARITYBIT == ODD_PARITY)
TB8 = 0; //设置校验位为0
#elif (PARITYBIT == EVEN_PARITY)
TB8 = 1; //设置校验位为1
#endif
}
else
{
#if (PARITYBIT == ODD_PARITY)
TB8 = 1; //设置校验位为1
#elif (PARITYBIT == EVEN_PARITY)
TB8 = 0; //设置校验位为0
#endif
}
busy = 1;
SBUF = ACC; //写数据到UART数据寄存器
}
/*----------------------------
发送字符串
----------------------------*/
void SendString(char *s)
{
while (*s) //检测字符串结束标志
{
SendData(*s++); //发送当前字符
}
}
2.代码的修改
可以看到这串代码引用的头文件是#include "reg51.h",而我们之前引用的都是stc15.h,其实,我们可以理解为15单片机是51单片机的哥哥,是向下兼容51的,而stc15.h其实是包含有reg51.h的所有内容,并且stc15.h里还有一些51单片机没有,但是15单片机有的东西(不知道上述内容对不对,反正大概是这样的),所以我们需要对代码进行修改,大致分四步走:
1)第一步,修改头文件
删除头文件reg51.h并添加头文件stc15.h
2)第二步,删除重复定义
删除所有sfr以及sbit开头的定义,因为这些定义都是51没有但是15有的,所以引用51的头文件之后,15的东西需要单独定义出来,而引用15的头文件之后,这些都在头文件定义过了,就不用再定义了,具体删除的代码如下:
#include "reg51.h"
sfr P0M1 = 0x93;
sfr P0M0 = 0x94;
sfr P1M1 = 0x91;
sfr P1M0 = 0x92;
sfr P2M1 = 0x95;
sfr P2M0 = 0x96;
sfr P3M1 = 0xb1;
sfr P3M0 = 0xb2;
sfr P4M1 = 0xb3;
sfr P4M0 = 0xb4;
sfr P5M1 = 0xC9;
sfr P5M0 = 0xCA;
sfr P6M1 = 0xCB;
sfr P6M0 = 0xCC;
sfr P7M1 = 0xE1;
sfr P7M0 = 0xE2;
sfr AUXR = 0x8e; //辅助寄存器
sbit P22 = P2^2;
3)第三步,删除I/0口配置
I/0口配置在main函数内,while(1)之前,这些代码都是配置引脚为准双向的(或者叫初始化为准双向),对于stc15f2k单片机这些是不必要的,因为默认就是准双向,但是对于某些单片机,引脚必须先初始化之后才可以使用。需要删除的代码如下:
P0M0 = 0x00;
P0M1 = 0x00;
P1M0 = 0x00;
P1M1 = 0x00;
P2M0 = 0x00;
P2M1 = 0x00;
P3M0 = 0x00;
P3M1 = 0x00;
P4M0 = 0x00;
P4M1 = 0x00;
P5M0 = 0x00;
P5M1 = 0x00;
P6M0 = 0x00;
P6M1 = 0x00;
P7M0 = 0x00;
P7M1 = 0x00;
其实,我们也可以在stc-isp内找到这些引脚的配置代码,可以根据自己的需要生成:
4)第四步,修改波特率并测试
代码上已经定义了波特率,我们直接修改其数值即可,改为9600
在stc-isp的串口助手选择正确的串口,并调整波特率为9600,然后就可以打开串口了。默认的代码只有上电之后发送一串数据,所以可以重启一下开发板,就可以看到发送的数据了!
3.代码的使用
经过2代码的修改之后,剩下的代码已经十分的干净了,剩下的代码中在main函数的while(1)之前完成了定时器的初始化。此外代码还写好了两个发送数据的代码,我们可以直接拿来用。对于接收数据,UART中断服务函数内可以自行对数据处理:
/*----------------------------
UART 中断服务程序
-----------------------------*/
void Uart() interrupt 4
{
if (RI)
{
RI = 0; //清除RI位
P0 = SBUF; //P0显示串口数据
P22 = RB8; //P2.2显示校验位
}
if (TI)
{
TI = 0; //清除TI位
busy = 0; //清忙标志
}
}
从P0=SBUF那一行那里开始,就是我们可以修改的地方。比如我们可以用一个自己定义的全局变量来记录SBUF的值,再放到其他地方处理。
但是实际上,在真正的比赛中,我们还需要考虑的内容还有许多,这里只是一种最简单的情况。
此外呢,其实也不难发现,刚才的几个过程也并非必要(因为我一个字都不改时,这串代码就可以正常运行),有的人真的把15单片机当51使用也不是不可以。
三、代码演示
刚才已经把代码介绍与修改好了,这里演示一个修改好之后完整的代码,实现以下功能:
1.上电发送数据"STC15F2K60S2\r\nUart Test !\r\n"
2.LED灯显示接收到的数据:
main.c
cpp
/*---------------------------------------------------------------------*/
/* --- STC MCU Limited ------------------------------------------------*/
/* --- STC15F4K60S4 系列 定时器1用作串口1的波特率发生器举例------------*/
/* --- Mobile: (86)13922805190 ----------------------------------------*/
/* --- Fax: 86-0513-55012956,55012947,55012969 ------------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966-------------------------*/
/* --- Web: www.STCMCU.com --------------------------------------------*/
/* --- Web: www.GXWMCU.com --------------------------------------------*/
/* 如果要在程序中使用此代码,请在程序中注明使用了STC的资料及程序 */
/* 如果要在文章中应用此代码,请在文章中注明使用了STC的资料及程序 */
/*---------------------------------------------------------------------*/
//本示例在Keil开发环境下请选择Intel的8058芯片型号进行编译
//若无特别说明,工作频率一般为11.0592MHz
#include <stc15.h>
#include "intrins.h"
typedef unsigned char BYTE;
typedef unsigned int WORD;
#define FOSC 11059200L //系统频率
#define BAUD 9600 //串口波特率
#define NONE_PARITY 0 //无校验
#define ODD_PARITY 1 //奇校验
#define EVEN_PARITY 2 //偶校验
#define MARK_PARITY 3 //标记校验
#define SPACE_PARITY 4 //空白校验
#define PARITYBIT NONE_PARITY //定义校验位
#define S1_S0 0x40 //P_SW1.6
#define S1_S1 0x80 //P_SW1.7
bit busy;
void SendData(BYTE dat);
void SendString(char *s);
void main()
{
ACC = P_SW1;
ACC &= ~(S1_S0 | S1_S1); //S1_S0=0 S1_S1=0
P_SW1 = ACC; //(P3.0/RxD, P3.1/TxD)
#if (PARITYBIT == NONE_PARITY)
SCON = 0x50; //8位可变波特率
#elif (PARITYBIT == ODD_PARITY) || (PARITYBIT == EVEN_PARITY) || (PARITYBIT == MARK_PARITY)
SCON = 0xda; //9位可变波特率,校验位初始为1
#elif (PARITYBIT == SPACE_PARITY)
SCON = 0xd2; //9位可变波特率,校验位初始为0
#endif
AUXR = 0x40; //定时器1为1T模式
TMOD = 0x00; //定时器1为模式0(16位自动重载)
TL1 = (65536 - (FOSC/4/BAUD)); //设置波特率重装值
TH1 = (65536 - (FOSC/4/BAUD))>>8;
TR1 = 1; //定时器1开始启动
ES = 1; //使能串口中断
EA = 1;
//不能连续发送!(直接放在while(1)里)可以在发送之间加一个100ms延时,否则容易接收不到,发送不出去
SendString("STC15F2K60S2\r\nUart Test !\r\n");
while(1);
}
/*----------------------------
UART 中断服务程序
-----------------------------*/
void Uart() interrupt 4
{
if (RI)
{
RI = 0; //清除RI位
P0 = SBUF; //P0显示串口数据
P2|=0x80;P2&=0x9F;P2&=0x1F;//打开LED灯
}
if (TI)
{
TI = 0; //清除TI位
busy = 0; //清忙标志
}
}
/*----------------------------
发送串口数据
----------------------------*/
void SendData(BYTE dat)
{
while (busy); //等待前面的数据发送完成
ACC = dat; //获取校验位P (PSW.0)
if (P) //根据P来设置校验位
{
#if (PARITYBIT == ODD_PARITY)
TB8 = 0; //设置校验位为0
#elif (PARITYBIT == EVEN_PARITY)
TB8 = 1; //设置校验位为1
#endif
}
else
{
#if (PARITYBIT == ODD_PARITY)
TB8 = 1; //设置校验位为1
#elif (PARITYBIT == EVEN_PARITY)
TB8 = 0; //设置校验位为0
#endif
}
busy = 1;
SBUF = ACC; //写数据到UART数据寄存器
}
/*----------------------------
发送字符串
----------------------------*/
void SendString(char *s)
{
while (*s) //检测字符串结束标志
{
SendData(*s++); //发送当前字符
}
}