【51单片机】串口通信与单片机交互详解
单片机与串口通信
一、任务目标
1.甲、乙两单片机进行 方式3(或方式2)串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED。方式3比方式1多了1个可编程位TB8,该位一般作奇偶校验位。乙机接收到的8位二进制数据有可能出错,需进行奇偶校验,其方法是将乙机的RB8和PSW的奇偶校验位P进行比较,如果相同,接收数据;否则拒绝接收。
2.将单片机串口与笔记本电脑串口模块相连,单片机每隔2秒发送“Hello C51”,笔记本电脑用串口助手软件接收。 如果串口助手发送字符“0" 给单片机,则单片机停止发送; 如果单片机收到“1”,则继续每隔2秒发送“Hello C51”。
二、理论基础
串行口的内部结构如下:
想要利用串行口通信,发送端TXD单向导向RXD。
串行口控制寄存器SCON,字节地址98H,可位寻址,位地址为98H~9FH,即SCON的所有位都可用软件来进行位操作清“0”或置“1”。
三、实现过程
3.1 串口传送流水灯数据
3.1.1 仿真图
3.1.2 甲机代码
#include <reg51.h>
void Send(unsigned char dat);
int i,j;
sbit P_x=PSW^0;
unsigned char Tab[8] = {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f}; // 控制流水灯显示数据数组
void delay(void) // 延时函数,这里假设晶振为12MHz,大约延时200ms
{
unsigned char m, n;
for (m = 200; m > 0; m--) // 调整循环次数以达到约200ms的延时效果
{
for (n = 248; n > 0; n--); // 内层循环同样需要调整
}
}
void main(void)
{
unsigned char i;
TMOD = 0x20; // 设置定时器T1为方式2
SCON = 0x50; // 设置串口为方式1,8位可变波特率,允许接收。方式3通常用于多机通信。
TH1 = 0xfd; // 波特率设置需要根据晶振频率来确定,这里假设晶振为11.0592MHz。
TL1 = 0xfd;
TR1 = 1; // 启动定时器T1
while (1)
{
for(i = 0; i < 8; i++)
{
Send(Tab[i]);
delay(); // 延时函数应根据实际晶振频率调整以达到约200ms的效果。
}
}
}
void Send(unsigned char dat) // 发送1字节数据的函数
{
unsigned char temp;
temp = dat; // 保存原始数据以便后面计算偶校验位
TB8 = 0; // 初始设置为0,之后根据数据计算偶校验位
for(j = 0; j < 8; j++) // 计算偶校验位
{
if ((temp & 0x01) ^ TB8) // 如果当前位和TB8不同,则TB8取反
{
TB8 = ~TB8;
}
temp >>= 1; // 右移一位处理下一位
}
SBUF = dat; // 发送数据
while (!TI); // 等待发送完成
TI = 0; // 清除发送完成标志
}
3.1.3 乙机代码
#include <reg51.h>
sbit P_x=PSW^0; // P位为PSW 寄存器的第0位,即奇偶校验位
void main(void) //主函数
{
TMOD=0x20; //设置定时器T1为方式2
SCON=0xd0; //设置串口为方式3,允许接收REN=1
PCON=0x00; // SMOD=0
TH1=0xfd; //给定时器T1赋初值,波特率为9600
TL1=0xfd;
TR1=1; //接通定时器T1
REN=1; //允许接收
while(1)
{
P1=Receive(); //将接收到的数据送P1口显示
}
}
unsigned char Receive(void) //接收1字节数据的函数
{
unsigned char dat;
while(RI==0); //检测RI,RI=0,未接收完,则循环等待
;
RI=0; //已接收一帧数据,将RI清0
ACC=SBUF; //将接收缓冲器的数据存于ACC
if(RB8==P_x) //只有偶校验成功才能往下执行,接收数据
{
dat=ACC; //将接收缓冲器的数据存于dat
return dat; //将接收的数据返回
}
}
3.2 串口通信
3.2.1 仿真图
3.2.2 实现代码
#include <reg51.h>
#include <stdio.h>
#include <string.h>
char send_flag = 0;
char received_char;
// 假设波特率设置为9600,根据实际情况调整
void serial_init() {
SCON = 0x50; // 设置为模式1,8位数据,可变波特率
TMOD &= 0x0F; // 清除定时器1模式位
TMOD |= 0x20; // 设置定时器1为8位自动重装模式
TH1 = TL1 = 256 - 12; // 定时器初值,根据波特率调整
TR1 = 1; // 启动定时器1
ES = 1; // 开启串口中断
EA = 1; // 开启总中断
}
// 发送字符串函数
void serial_send_string(char *str) {
while (*str) {
while (!TI); // 等待上一次发送完成
SBUF = *str++; // 发送当前字符
TI = 0; // 清除发送完成标志
}
}
// 串口中断服务程序
void serial_ISR() interrupt 4 using 1 {
if (RI) {
RI = 0; // 清除接收标志
received_char = SBUF; // 读取接收到的字符
if (received_char == '0') {
// 如果接收到字符'0',停止发送
} else if (received_char == '1') {
// 如果接收到字符'1',继续发送
}
}
}
// 主函数
void main() {
serial_init(); // 初始化串口
send_flag = 1; // 发送标志
while (1) {
if (send_flag) {
serial_send_string("Hello C51"); // 发送字符串
// 延时2秒,可以使用定时器中断实现
}
}
}
四、实验总结
通过本次实验,我深刻体会到了理论与实践相结合的重要性。在实验过程中,我不仅加深了对相关理论知识的理解,还提高了自己的实践操作能力。同时,我也学会了如何分析和解决实验中遇到的问题,这对我的学习和工作都具有重要的意义。
以上则是我此次的实验内容,如有错漏请各位大佬多多指教!
作者:双料毒狼_s