欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > 速通串口通信

速通串口通信

2025/2/28 15:30:42 来源:https://blog.csdn.net/2301_79437046/article/details/145883908  浏览:    关键词:速通串口通信
  • 串口通信可以说是蓝桥杯单片机考察的最难的一个模块了,如果你是参加第十六届蓝桥杯的选手,可以在比赛前花上一点时间学习以下串口通信的函数实现,以防省赛突然蹦出串口通信来,机会是留给有准备的人的。
  • 直接点击下方链接跳转到串口初始化函数开始学习即可,前面的寄存器不学对比赛没有影响。
    串口初始化

一、串行口控制寄存器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;}
}

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词