今日得以继续蓝桥杯国赛备赛之旅:
有道是 “不知何事萦怀抱,醒也无聊,醉也无聊,梦也何曾到谢桥。”
那我们该如何 让这位诗人纳兰 “再听乐府曲 ,畅解相思苦”呢?
那就建立起串口通信吧!
我将从SCT15F2K61S2单片机串口通信基础出发,逐步缓解此等繁琐愁杂。
附上工程文件下载传送门:
https://download.csdn.net/download/qq_64257614/87800670?spm=1001.2014.3001.5503
本篇文章除了学习基础,还主要研究学习以下问题:
1.在蓝桥杯单片机实训平台上串口通信实现printf()函数
2.蓝桥杯单片机串口通信发送变量乱码问题
3.接收中断数据处理的实现
4.字符串与变量拼接到一起,变成新的字符串。
目录
一、串口通信知识基础:
二、串口通信基础程序设计:
总设计方针:
首先生成我们要用的串口初始化程序:
其次就是编写发送与接受函数:
中断服务函数:(这个要先定义俩变量):
三、串口通信的一些处理操作:
输出的处理:
1.重定义改造printf函数:
2.连续发送单个字节:
3. sprintf 函数可以格式化输出:
输入的处理:
4.ASCLL码与发送变量值的处理:
5.字符串与变量的拼接:
6.中断接收字符串:
三、实践拓展延伸:
实验要求:
程序设计:
#include "usart_main.h"
#include "Public.h"
#include "uart.h"
一、串口通信知识基础:
串口按位(bit)发送和接收字节的通信方式。
IAP15F2k61s2单片机内部有俩个采用UART工作方式的全双工串行通信接口,
全双工就是俩个单工的结合,A与B俩个机能同时收发数据。
此外半双工,单工就不做介绍,大家可以百度,这个了解就可以了。
每个串行口的组成:
2个数据缓冲器:(SBUF) 串口1的地址是99H,串口2的地址是9BH。
1个移位寄存器:
1个串行控制器: (SCON)
1个波特率发生器:
串口1和串口2:
串口1对应硬件部分是TxD和RxD
串口1可以在:
【P30/RXD,P31/TXD】、【P36/RXD_2,P37/TXD_2】、【P1^6/RXD_3,P17/TXD_3】
之间切换。
(1)串口1可以使用定时器1、定时器2为波特率发生器
(2)定时器2默认为16位自动重装载模式
(3)定时器2的时间常数保存在T2H、T2L
串口2对应硬件部分是TxD2和RxD2
串口2可以在:
【P10/TXD2,P11/RXD2】、【P46/RXD2_2,P47/TXD2_2】
之间切换。
二、串口通信基础程序设计:
总设计方针:
步骤1~4不需要我们自己背记,可用软件生成,下面会介绍。
步骤5~7需要加在软件生成的函数里面。
1.设定定时器1工作方式(TMOD寄存器)
2.计算波特率,如果用的是定时器1作为波特率发生器,那就赋值给TH1,TL1
3.设置AUXR寄存器
4.设置SCON寄存器
5.打开定时器1
6.使能串口中断 ES
7.开总中断EA
串口通信中需要用到的寄存器SCON——串行通信控制寄存器(一般设置为0x50)
首先生成我们要用的串口初始化程序:
这里不需要我们背寄存器了,直接用STC_isp软件生成:
如图设定好要用的串口1、频率、波特率等
这个初始化代码没有需要改动的地方,但后续有添加语句,
这个void UartInit(void)函数就是串口1的初始化函数了,
一般在主函数while(1)之前调用一下就行了。
添加语句后就是这样:
void UartInit(void) //9600bps@11.0592MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器时钟1T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TL1 = 0xE0; //设置定时初始值
TH1 = 0xFE; //设置定时初始值
ET1 = 0; //禁止定时器%d中断
TR1 = 1; //定时器1开始计时
REN = 1; //允许串口接受
ES = 1; //开启串口中断
EA = 1; //开启总中断
}
其次就是编写发送与接受函数:
发送一个字节函数:
//发送一个字节
void SendData(unsigned char dat)
{
SBUF=dat; //把要发送的数据存入写SBUF
while(TI == 0); //等待发送标志位置1
TI = 0;
}
发送字符串函数:
void Uart_Sendstring(unsigned char *pucStr)
{
while(*pucStr!='\0')
{
SBUF=*pucStr;
while(TI==0); //等待串口发送完成
TI=0;
//因为TI在串口发送后,硬件自动置1,
//我们需要重新发送数据的话,就需要在置1后,软件清零,
pucStr++;
}
}
中断服务函数:(这个要先定义俩变量):
这样写可以接收上位机数据进行分析
u8 R_sign; //接收缓冲区内容完成标志
u8 R_dat; //用于接收缓冲区内容
//串口1中断服务函数
void Uart_1_serv() interrupt 4
{
if(RI) //查询串口接收程序,如果接收标志位为1,说明有数据接收完毕
{
RI = 0; //清除接收标志位
R_dat = SBUF; //将接收缓冲区内容转至R_dat变量中
R_sign = 1;
}
}
三、串口通信的一些处理操作:
输出的处理:
1.重定义改造printf函数:
printf()函数的使用需要注意引用头文件 stdio.h
然后需要重定向putchar()函数
这样以后就能用printf()函数向串口打印数据了
这类思想常见于嵌入式的程序设计,在我的另一篇文章有所研究:
使用printf()函数时该注意,会占用大量资源
嵌入式硬件中Printf函数的原理_NULL指向我的博客-CSDN博客
void send_date(unsigned char date){
SBUF = date;
while(!TI);
TI = 0;
}
char putchar (char ch){
send_date(ch);
return ch;
}
printf()用法举例:
unsigned char test=128;
printf("Welcome to stc15f2k60s2\n");
printf("%d",test);
2.连续发送单个字节:
void send_string(unsigned char *date, unsigned char len){
while(len--)
{
send_date(*date);
date ++;
}
}
我们有时要输出变量,用这种方式就要把变量转换成字符型:
cent + '0' ;
3. sprintf 函数可以格式化输出:
sprintf(string,"num:%d",(int)cent);
send_string(string,strlen(string));
输入的处理:
通过将接收到的数据保持到一个数组当中,
就可以之后在循环中对接收到的数据进行判断。
此段代码是接收中断函数的处理更改
if(RI==1){
str[rev] = SBUF;
RI = 0;
rev++;
}
else{
TI = 0;
}
if(str[rev-1] == '\n'){
if(str[0] == 'S' && str[1] == 'T')
{
printf("STOK\r\n");
}
rev = 0;
for(i = 0; i<8;i++){
str[i] = '\0';
}
4.ASCLL码与发送变量值的处理:
单片机与上位机之间的通信出现问题。例如我如下方式发送一个变量test:
会发现接收不是我们要的效果
u8 test=1;
printf("%c",test+'0');
SendData(test+'0');
总所周知,ASCII码是由八位二进制小数进行一映射的,从0-127的数字分别对应了不同字符:
造成这样的结果在于上位机给我们发送的是ASCII码,
并且上位机接收显示的也是ASCII码,而不是数值大小。
也就是说,我们给上位机发 “1”,上位机实际接收到的是0000_0001(B),
对应ASCII为SOH(SOH(start of headline) 标题开始),
因此上位机并不显示1,而是显示SOH,
要想上位机显示1,应该给它发送49(u8)才对。
同理,上位机发送“1“,我们实际接收到的为49(u8)
因此应该减去48才为我们需要得到的数字1。
在单片机中,我们的数字一律是实际的数值,我们定义变量时,
定义为u8,u6,就是代表无符号的整数,单片机串口发送也是发送这些数值。
而在上位机中,则是发ASCII码,显示ASCII码。
以下为ASCLL码对照表:
我们如果把发送的数据改成:
uint8_t Data[10]={'1','2','3','4'};
那么这样上位机就可以完美接收到1234。
单引号代表取这个符号的ASCII值,因此比较字符串,
可以使用Data[x]=='A',这样的操作进行比较。
5.字符串与变量的拼接:
可以使用C语言中的字符串拼接函数strcat()
来将字符串和整数变量拼接在一起。
下面是一个示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "Hello";
int num = 123;
char str2[10];
// 将整数转换为字符串格式
sprintf(str2, "%d", num);
// 使用strcat()函数将两个字符串拼接在一起
strcat(str1, str2);
printf("%s", str1);
return 0;
}
输出结果为:Hello123
。
在代码中,我们首先定义了一个字符串str1
和一个整数num
。
然后,我们使用sprintf()
函数将整数转换为字符串格式并将其保存在另一个字符串str2
中。
最后,我们使用strcat()
函数将str1
和str2
拼接在一起,并将结果打印到控制台上。
6.中断接收字符串:
像这样编写即可:
//中断接收字符串,根据选择接收字符还是字符串
unsigned char i;
unsigned char string[20];
void Usart_interrupt() interrupt 4 //用与接收字符串
{
if (RI) {
RI = 0;
string[i] = SBUF;
if (string[i] == '\n') //回车为接收结束标志
{
i = 0;
} else {
i++;
}
}
}
三、实践拓展延伸:
实验要求:
/*
本实验练习串口通信:主要练习以下内容:
1.串口接受命令,串口中断并执行
接收到上位机传来0xff后,单片机发送'Hello'
接收到上位机传来0xfe后,单片机发送'World'
接收到上位机传来0xfd后,单片机发送'Hello World'
关于接收到上位机其他数据,单片机都返回发送'Wrong Order'
2.串口发送字符,数字。
用Uart_Sendstring()函数做一些1中指定的发送字符串的任务
3.配置Printf函数,
开机用Printf函数向上位机发送'Welcome to stc15f2k60s2\n'
开机用Printf函数向上位机发送 变量test的值,test=1
*/
/*
本实验有以下注意点:printf函数需要重定向
*/
程序设计:
具体设计没有特别难的地方,注意发送变量ASCLL码规则即可。
#include "usart_main.h"
#include "usart_main.h"
void main()
{
u8 test=1;
// u16 test2=1234;
cls_buzz_led(); //关闭外设
UartInit(); //初始化串口
printf("Welcome to stc15f2k60s2\n");
printf("%c",test+'0');
// printf("\n"); //发送换行测试
SendData(test+'0');
// SendData('\n'); //发送换行测试
// SendData(test+'0'); //发送换行测试
while(1)
{
if(R_sign==1) //如果有数据接收完毕
{
R_sign=0;
switch(R_dat)
{
case 0xff:Uart_Sendstring("Hello"); break;
case 0xfe:Uart_Sendstring("World"); break;
case 0xfd:Uart_Sendstring("Hello World"); break;
default :Uart_Sendstring("Wrong Order");break;
}
}
}
}
#ifndef _usart_main_h_
#define _usart_main_h_
#include "stc15f2k60s2.h"
#include "Public.h"
#include "stdio.h"
#include "uart.h"
#endif
#include "Public.h"
#include "Public.h"
void inint_74HC573(u8 select) //74HC573片选函数(简记:0x53f0)
{
switch(select)
{
case 4: P2 = (P2 & 0X1F) | 0X80; break;//开启 Y4 LED端口 装填
case 5: P2 = (P2 & 0X1F) | 0XA0; break;//开启 Y5 ULN2003驱动端口 装填
case 6: P2 = (P2 & 0X1F) | 0XC0; break;//开启 Y6 数码管位选端口 装填
case 7: P2 = (P2 & 0X1F) | 0XE0; break;//开启 Y7 数码管段选端口 装填
}
}
void cls_buzz_led()
{
inint_74HC573(5);
buzz = 0;
inint_74HC573(4);
P0 = 0xff;
}
#ifndef _Public_h_
#define _Public_h_
#include "stc15f2k60s2.h"
typedef unsigned int u16;
typedef unsigned char u8;
sbit buzz=P0^6; //蜂鸣器
void inint_74HC573(u8 select); //74HC573片选函数(简记:0x53f0)
void delay_xms(u8 xms); //传入参数 参数为几就 延时几毫秒
void cls_buzz_led();
#endif
#include "uart.h"
#include "uart.h"
u8 R_sign; //接收缓冲区内容完成标志
u8 R_dat; //用于接收缓冲区内容
void UartInit(void) //9600bps@11.0592MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器时钟1T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TL1 = 0xE0; //设置定时初始值
TH1 = 0xFE; //设置定时初始值
ET1 = 0; //禁止定时器%d中断
TR1 = 1; //定时器1开始计时
REN = 1; //允许串口接受
ES = 1; //开启串口中断
EA = 1; //开启总中断
}
//发送字符串函数
void Uart_Sendstring(unsigned char *pucStr)
{
while(*pucStr!='\0')
{
SBUF=*pucStr;
while(TI==0); //等待串口发送完成
TI=0;
//因为TI在串口发送后,硬件自动置1,
//我们需要重新发送数据的话,就需要在置1后,软件清零,
pucStr++;
}
}
//发送一个字节
void SendData(unsigned char dat)
{
SBUF=dat; //把要发送的数据存入写SBUF
while(TI == 0); //等待发送标志位置1
TI = 0;
}
/*
//发送字符串,以'\0'形式结尾
void SendString(unsigned char *s)
{
while (*s!='\0') //检查是否到结尾
{
SendData(*s++); //地址递增进行逐个发送
}
}
*/
void send_date(unsigned char date){
SBUF = date;
while(!TI);
TI = 0;
}
char putchar (char ch){
send_date(ch);
return ch;
}
//串口1中断服务函数
void Uart_1_serv() interrupt 4
{
if(RI) //查询串口接收程序,如果接收标志位为1,说明有数据接收完毕
{
RI = 0; //清除接收标志位
R_dat = SBUF; //将接收缓冲区内容转至R_dat变量中
R_sign = 1;
}
}
#ifndef _uart_h_
#define _uart_h_
#include "stc15f2k60s2.h"
#include "Public.h"
#include "stdio.h"
extern u8 R_sign;
extern u8 R_dat;
void Uart_Sendstring(unsigned char *pucStr);
void UartInit(void); //9600bps@11.0592MHz
void Uart_1_serv();
void SendData(unsigned char dat);
#endif