本篇博客的最终效果是实现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里面,具体的自己搜搜看
结果