当前位置: 首页 > news >正文

速通串口通信

  • 串口通信可以说是蓝桥杯单片机考察的最难的一个模块了,如果你是参加第十六届蓝桥杯的选手,可以在比赛前花上一点时间学习以下串口通信的函数实现,以防省赛突然蹦出串口通信来,机会是留给有准备的人的。
  • 直接点击下方链接跳转到串口初始化函数开始学习即可,前面的寄存器不学对比赛没有影响。
    串口初始化

一、串行口控制寄存器SCON

在这里插入图片描述

1.RI

RI的功能简单来说就是接收中断请求标志位:数据接收完成时由硬件置为1,需手动清零

2.TI

TI的功能是发送中断请求标志位:数据发送完成时由硬件置为1,需手动清零

3.SM0、SM1

对于SM0、SM1只需要知道当SM0、SM1=01时,串口工作在方式一(8位UART,波特率可变)。

4.REN

允许/禁止串行中断接收控制位,REN置1时允许接收中断,置0时则反之。

5.其余位

其余位与串口中断没有直接关系,统一置为0即可。

综上所述,对于SCON的配置为:SCON = 0x50;

二、电源控制器PCON

在这里插入图片描述
只需知道SMOD置0即可,其他不用管。

三、数据缓存器SBUF

如果读者有学过计算机组成原理这门课,那肯定对SBUF寄存器不陌生吧,在此只需知道SBUF为数据的临时中转站即可,无需过多了解。
在这里插入图片描述

  • 串行发送时,CPU向SBUF写入数据,此时99H表示发送缓存SBUF。
  • 串行接收时:CPU从SBUF读取数据,此时99H表示接收缓存SBUF。
  • 数据发送:把数据扔进SBUF后,内核会自动将数据发送,内容发送完毕,TI标志位置1。
  • 数据接收:内核从串口接收到一个完整数据后,会将RI标志置1,用户用SBUF读取即可。

四、中断允许寄存器IE

在这里插入图片描述
只需知道EA、ES的功能即可。

五、串口初始化函数

如果你还没理解前面的寄存器功能是没有关系的,速成只需要会使用ISP对串口进行配置即可,配置如下图所示:
在这里插入图片描述

定时器时钟配置1T还是12T目测没有太大影响,最好设置12T模式

再将复制后的代码进行以下处理:

void Uart1_Init(void)	//9600bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x01;		//串口1选择定时器2为波特率发生器
	AUXR |= 0x04;		//定时器时钟1T模式
	T2L = 0xC7;			//设置定时初始值
	T2H = 0xFE;			//设置定时初始值
	AUXR |= 0x10;		//定时器2开始计时
	ES = 1;
	EA = 1;
}

六、字符串发送函数

1.方法一:不引用头文件(不推荐)

//单片机回复上位机(单个字节)
void SendByte(unsigned char dat)
{
	SBUF = dat;
	while(!TI);//死循环,等待字节发送
	TI = 0;//字节发送完成后TI会置1,计时清0
}

//单片机回复上位机(字符串)
//写法一:如果你刚开始学习C语言,写法一比较好理解
void SendString(unsigned char *dat, unsigned char i)
{
	unsigned char j;
    for (j = 0; j < i; j++) // 遍历数组中的所有元素
    {
        SendByte(dat[j]); // 发送当前字节
    }
}
//写法二
void SendString(unsigned char *dat)
{
	while(*dat != '\0')//当发送数组未到达终止符时
		SendByte(*dat++);//发送该字节,发送完成后数组往后移位
}

2.方法二:引用头文件

stdio.h头文件中,已经声明了putcharsprintf这两个函数。
在这里插入图片描述
putchar函数需要用户自己定义,在底层函数中格式如下:

extern char putchar(char ch)
{
	SBUF = ch; // 将ch写入SBUF,发出数据
	while (TI == 0);// 等待发送完成
	TI = 0; // 清除发送完成标志
	return ch;
}

该函数是标准库函数 printf 的底层实现依赖。在嵌入式系统中,printf 会调用 putchar 将字符逐个发送到串口。
所以你要单片机回复上位机某个字符串时,只需按照以下方式编写代码即可。

//串口处理函数
void UartProc() 
{
	//处理数据溢出代码
	if(//上位机发送数据)
		printf("HELLO!");
}

七、串口中断函数

//全局变量定义
idata unsigned char uart_rec[10];//数据缓存数组
idata unsigned char uart_rec_index; //数据缓存数组索引

void Uart1Server() interrupt 4
{
	if(RI == 1)//串口数据接收完成
	{
		uart_rec[uart_rec_index] = SBUF;//读取数据缓存区的数据
		uart_rec_index++;//移位
		RI = 0;//重新接收
	}
}

八、防止数据溢出判断

上面写的代码存在一个致命的缺点就是没有考虑数据溢出,也就是说,当数据接收数组存满后,再发送串口数据会导致溢出,所以还需要加入溢出判断。

memset函数
调用该函数需要引入头文件#include <string.h>
函数调用格式:memset(*ptr,value,size)
*ptr:要改变的数组
value:改变后的值
size:设置的字节数
例如:memset(arr,0,3);表示将数组arr的第0位到第2位的数据赋值为0
也就是说,该函数等于以下代码:

unsigned char i;
for(i = 0; i < 3; i++)
	arr[i] = 0;
/*全局变量声明区*/
idata unsigned char uart_rec[10];//数据缓存存放数组
idata unsigned char uart_rec_index;//数据缓存存放数组索引
idata unsigned char systick;//滴答计时器
idata bit uart_flag;//处理数据标志位

//Uart初始化以及发送字节函数省略

void UartProc()//串口处理数据
{
	//当未开始串口通信时,不执行该函数
	if(!uart_rec_index)
		return;
	if(systick >= 10)//当滴答计时器计时10ms时,进行一次数据处理
	{
		systick = uart_flag = 0;
		
		//串口执行操作(自行编写)

		//执行完操作后,开始清除缓存区数据
		memset(uart_rec,0,uart_rec_index);//清空缓存区
		uart_rec_index = 0;//重置索引值
		
	}
}

void Uart1_Server() interrupt 4
{
	if(RI == 1)//接收到数据时
	{
		uart_flag = 1;//有数据传入,准备处理数据
		uart_rec[uart_rec_index] = SBUF;
		uart_rec_index++;
		RI = 0;
		if(uart_rec_index > 10)
		{
			memset(uart_rec,0,10);
			uart_rec_index = 0;//防止数据溢出
		}
	}
}

void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;			//定时器时钟12T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x18;				//设置定时初始值
	TH1 = 0xFC;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
	EA = 1;
}

void Timer1_Isr(void) interrupt 3
{
	if(uart_flag)//数据接收时,滴答计时器开始计时
		systick++;
}

九、完整代码及应用

模块化编程,将UartInit()putchar()函数放进Uart.c文件中,并在Uart.h中声明,在main.c中引入头文件Uart.h

1.Uart.h

#ifndef __Uart_H__
#define __Uart_H__

void UartInit(void);
extern char putchar(char ch);

#endif

2.Uart.c

#include <STC15F2K60S2.H>

void Uart1_Init(void)	//9600bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x01;		//串口1选择定时器2为波特率发生器
	AUXR |= 0x04;		//定时器时钟1T模式
	T2L = 0xC7;			//设置定时初始值
	T2H = 0xFE;			//设置定时初始值
	AUXR |= 0x10;		//定时器2开始计时
	EA = 1;
	ES = 1;
}

extern char putchar(char ch)
{
	SBUF = ch;
	while(!TI);//等待数据发送
	TI = 0;
	return ch;
}

3.main.c

#include <STC15F2K60S2.H> // 单片机寄存器专用头文件
#include <string.h>
#include <stdio.h>
#include "Init.h"
#include "Uart.h"	  // 串口底层驱动专用头文件
#include <intrins.h>

idata unsigned char uart_rec[10];
idata unsigned char uart_rec_index;
idata unsigned char systick;

idata bit uart_flag;

void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;			//定时器时钟12T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x18;				//设置定时初始值
	TH1 = 0xFC;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
}

void Timer1_Isr(void) interrupt 3
{
	if(uart_flag)
		systick++;
}


void UartProc()
{
	if(!uart_rec_index)
		return;
	if(systick >= 10)
	{
		systick = uart_flag = 0;

		//逻辑函数
		
		//清除数据
		memset(uart_rec,0,uart_rec_index);
		uart_rec_index = 0;
	}
}

void UartServer() interrupt 4
{
	if(RI == 1)
	{
		uart_flag = 1;
		uart_rec[uart_rec_index] = SBUF;
		uart_rec_index++;
		RI = 0;
		if(uart_rec_index > 10)
		{
			memset(uart_rec,0,10);
			uart_rec_index = 0;//防止数据溢出
		}
	}
}

void main()
{
	SystemInit();
	Timer1_Init();
	Uart1_Init();
	while(1)
	{
		UartProc();
	}
}

4.应用

应用1:上位机发送指定内容时单片机才回复

上位机向串口发送字符串ok,单片机收到后向上位机回复HELLO
只需在串口数据处理函数更改即可

void UartProc()
{
	if(!uart_rec_index)
		return;
	if(systick >= 10)
	{
		systick = uart_flag = 0;

		//逻辑函数
		if(uart_rec[0] == 'o' && uart_rec[1] == 'k')
		{
			printf("HELLO");
		}
		//清除数据
		memset(uart_rec,0,uart_rec_index);
		uart_rec_index = 0;
	}
}

串口现象:
在这里插入图片描述

应用2:单片机回复完成后换行

上位机向串口发送字符串ok,单片机收到后向上位机回复HELLO,并自动换行回复WORLD!
在末尾加上换行符\n即可

void UartProc()
{
	if(!uart_rec_index)
		return;
	if(systick >= 10)
	{
		systick = uart_flag = 0;

		//逻辑函数
		if(uart_rec[0] == 'o' && uart_rec[1] == 'k')
		{
			printf("HELLO\n");
			printf("WORLD!\n");
		}
		//清除数据
		memset(uart_rec,0,uart_rec_index);
		uart_rec_index = 0;
	}
}

串口现象:
在这里插入图片描述

相关文章:

  • 【Qt】详细介绍如何在Visual Studio Code中编译、运行Qt项目
  • Spring 核心技术解析【纯干货版】- XIV:Spring 消息模块 Spring-Jms 模块精讲
  • SEATA 2.2.0使用K8S部署,使用自定义配置文件
  • 2004-2024年光刻机系统及性能研究领域国内外发展历史、差距、研究难点热点、进展突破及下一个十年研究热点方向2025.2.27
  • 【fnOS飞牛云NAS本地部署DeepSeek-R1结合内网穿透远程访问告别服务器繁忙】
  • Java 调试模式下 Redisson 看门狗失效
  • 【Day50 LeetCode】图论问题 Ⅷ
  • fody引用c++的dll合并后提示找不到
  • 【MySQL】(1) 数据库基础
  • jQuery UI API 文档
  • Spring Boot 整合 MyBatis 与 PostgreSQL 实战指南
  • kafka-web管理工具cmak
  • GPT 与BERT的异同
  • pta天梯L1-009 N个数求和
  • 第一章:觉醒
  • 【GPU机器数据传输】Linux系统中跨机器大规模数据传输,rsync | bbcp | scp | sftp 对比。
  • Python 字典与集合:从入门到精通的全面解析
  • win10下安装wireshark的问题
  • C#中使用System.Net库实现自动发送邮件功能
  • 汽车v型推力杆总成三维5自由度性能及疲劳测试系统
  • 毕业设计可以做哪些简单网站/网页设计与制作代码
  • wordpress前台登陆界面/优化快速排名公司
  • 纪检监察网站建设/发布信息的免费平台
  • 贵阳网站建设是什么/百度点击优化
  • 网站建设的总结/友情链接模板
  • 深圳画册设计印刷/网站seo主要是做什么的