51单片机——串口通信


本篇博客的最终效果是实现51单片机用串口发送Hello World,netty监听串口读到Hello World后回发给51单片机,最终51单片机回显到LCD1602显示屏。

串口通信?

其实我压根不知道串口通信是啥,我在这方面也是小白,只知道按照标准做就可以实现通信。
在这里插入图片描述
上图示是四孔串口,应该算是全双工通信的,复杂的有9针串口,提供额外的口子可以调控发送速率等。

在这里插入图片描述

开发板原理图如下:
在这里插入图片描述

串口通信的原理?

我们知道串口通信是要设置一个波特率的,比如4800bit/s, 那么这个数据发送速率如何控制呢?
定时器那篇博客里说了12MHz的晶振,一个指令周期是1us,每过一个1us,定时器+1,加到溢出了,发出一个溢出中断,然后就去发送码元数据,这样去控制码元的发送速率。

定时器初值的计算
关于串口定时器初值计算,我没搞懂,但是他有公式,套用公式即可
公式:TL1 = 256 - fosc * (SMOD + 1) / (384 * 波特率)
我的单片机是11.0592MHz,fosc=11.0592*1000000, 晶振每s振动次数
TL1= 256 - 11059200/384/4800 = 250 = 0xFA

这里用的定时器是定时器1,8位重装,8位最大255,加到溢出值256,初始值和重装值都设置为0xFA
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值

软件计算
感觉之所以出11.0592MHz的原因可能就是用这个计算误差为0,如果是12MHz的不管怎么计算都会有误差,但是12MHz的采用12分频的话一个指令周期正好1us。
在这里插入图片描述

串口的配置

在这里插入图片描述SCON配置
在这里插入图片描述REN::接收使能,为0代表51单片机的串口不接受数据,我们需要接收置位为1
TB8,RB8:不用管,只有在方式2和方式3 9位UART下才用得到
TI:发送数据中断标志位,在发完8位数据后,由内部硬件自动置位为1,我们需要软件复位为0
RI:接收数据中断标志位,在接完8位数据后,由内部硬件自动置位为1,我们需要软件复位为0
SCON=0x50

SBUF是一字节缓冲区,发送的时候直接SBUF='A’就把A发送到串口了,接收的时候char a = SBUF就可以了

PCON配置
在这里插入图片描述
B7的SMOD=1开启倍速,我们不需要倍速,8为全0就好。
PCON=0; 为了不影响其他位一般这样写PCON &= 0x7F;

IE配置
这里配的是UART中断的配置‘
在这里插入图片描述EA=1,ES=1允许UART中断,netty从串口发来的数据,是要经过这个中断处理程序的:void UART_Routine() interrupt 4

串口模式图:
在这里插入图片描述
在这里插入图片描述

定时器的配置

之前有详细说过定时器0的配置,这里就不说了,具体查看STC89C52文档第7章

	//定时器配置
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	//上面两行等价TMOD=0x20
	TL1 = 0xFA;		//设定定时初值
	TH1 = 0xFA;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断,这里不需要定时器的中断,只需要计时
	TR1 = 1;		//启动定时器1

c源代码

Delay.c


void Delay(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}


Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

LCD1602.c

#include <REGX52.H>
#include <Delay.h>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0


/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	Delay(1);
	LCD_EN=0;
	Delay(1);
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	Delay(1);
	LCD_EN=0;
	Delay(1);
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

UART.h

#ifndef __UART_H__
#define __UART_H__

void UART_Init();
void UART_SendByte(unsigned char Byte);

#endif

UART.c

#include <REGX52.H>


//11.0592MHz
//fosc=11.0592*1000000, 晶振每s振动次数
// TL1 = 256 - fosc * (SMOD + 1) / (384 * 波特率)
// TL1= 256 - 11059200/384/4800 = 250 = 0xfa
void UART_Init()
{
	//PCON |= 0x80; //波特率倍速 smod=1,9600
	//TL1 = 0xF4;		//设定定时初值
	//TH1 = 0xF4;		//设定定时器重装值
	PCON=0;
	//PCON &= 0x7F;		//波特率不倍速 smod=0,4800
	SCON = 0x50;		//8位数据,可变波特率
	
	//定时器配置
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFA;		//设定定时初值
	TH1 = 0xFA;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	
	//串口中断配置
	EA=1;
	ES=1;
}
/**
  * @brief  串口发送一个字节数据
  * @param  Byte 要发送的一个字节数据
  * @retval 无
  */
void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);
	//到这里说明TI=1,因为硬件置为1
	//手工复位
	TI=0;
}

main.c

#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
#include "LCD1602.h"
void main()
{
	unsigned char cs[] = {0x10,11,'H','e','l','l','o',' ','W','o','r','l','d',0x16};
	char i = 0;
	LCD_Init();
	
	UART_Init();			//串口初始化
	
	Delay(3000);		//延时
	
	
	while(i<14){
		UART_SendByte(cs[i]);	//串口发送一个字节
		Delay(10);		//延时
		i++;
	}
			
	while(1){}
	
}

//下面这个中断程序是接收netty的返回数据
char col = 0;
unsigned char rcs[14];
char i = 0;
void UART_Routine() interrupt 4
{
	if(RI==1)					//如果接收标志位为1,接收到了数据
	{
		rcs[col++] = SBUF;
		if(col==14){
			LCD_ShowString(1,0,rcs);
		}
		RI=0;					//接收标志位清0
	}
}

netty源代码

netty代码有点多,我就说个大概,可以去git拉。

整体流程如下:
在这里插入图片描述
协议如下图
在这里插入图片描述单片机发送的数据如下:
unsigned char cs[] = {0x10,11,‘H’,‘e’,‘l’,‘l’,‘o’,’ ',‘W’,‘o’,‘r’,‘l’,‘d’,0x16};

第一步经过FrameDecoder
在这里插入图片描述
第二步经过PacketDecoder
在这里插入图片描述第三步经过PacketHandler
在这里插入图片描述

git码云代码,代码在com.lry.netty01.rx,其他代码不用管,另外java串口通信需要一些jar包,放到jdk里面,具体的自己搜搜看

结果

在这里插入图片描述
在这里插入图片描述

  • 10
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值