硬件开发1-51单片机3-串口

UART:通用异步收发器

  • 包含 2 个串口:1 个用于 ISP 下载程序,1 个用于与主机通信
  • 主要信号线:RXD (接收信号线)、TXD (发送信号线)
  • 通信特性:全双工、串行、异步

一、通信方式

1、单工

  • 发送方与接收方是固定的
    • 数据只能 A是发 B是收(固定的)
  • 数据传输通过一根信号线实现
  • 数据传输的方向是固定的单向的

2、半双工(如 I2C)

  • 通信的双方既可以作为发送方,也可以作为接收方
  • 数据传输通过一根信号线实现
  • 数据传输的方向可以是双向,但同一时刻的传输方向呈现单一性
  • 典型例子 I2C
    • A 给 B 发的时候,B 不能给 A 发,B 只能去接收数据

3、全双工(如 UART)

  • 通信的双方既可以作为发送方,也可以作为接收方
  • 数据传输通过两根信号线实现(TXD RXD)
  • 数据的传输在任意时刻都是双向的
  • 例(URT)

二、数据传输的顺序

UART 传输遵循 LSB 优先原则(低位先行),先发低位数据

数据位置

三、数据传输的形式

1、串行

通过一根信号线传输,按先后次序逐个 bit 发送

  • 概念:通过一根信号线传输数据,按照先后次序逐个bit逐个bit去发送数据

  • 优缺点:

    • 传输速率慢
    • 硬件成本低,实现简单
    • 传输距离远,抗干扰性好
      • (例RS485 差分)(1km工业级)

2、并行

通过多根信号线同时传输多个 bit

  • 概念:通过多根信号线同时去传输数据

  • 优缺点:

    • 传输速度快
    • 硬件成本高,实现复杂
    • 传输距离近,抗干扰弱
      • 理论传输距离30m,距离增加容易并行线路间信号偏移,易受电磁波干扰,会造成数据受干扰

3、串口通信时序图:

四、串行传输和并行传输的区别

特性 串行(如 UART) 并行
传输速率 较慢(单线逐个 bit 传输) 较快(多线同时传输)
硬件成本 低,实现简单 高,实现复杂
传输距离 远,抗干扰性好(如 RS485 差分) 近(通常 30 米内),抗干扰性差
干扰问题 较少 距离增加后易产生信号偏移和电磁干扰

五、串口通信时序

1、空闲状态为高电平

2、高电平变为低电平(发送低电平信号)代表起始位,准备开始通信

3、发送数据(通常 8bit,遵循 LSB 低位先行原则)

4、发送 1bit 校验位(奇偶校验)

5、发送 1bit 停止位,代表本次通信结束

六、奇偶校验

1、局限性

无法检测偶数个 bit 出错

2、奇校验

校验位为 '1',数据位中 '1' 的个数加上校验位 '1' 的总数为奇数时,校验通过

3、偶校验

校验位为 '0',数据位中 '1' 的个数加上校验位 '0' 的总数为偶数时,校验通过

七、串口通信参数

1、波特率

bps(bit per second),每秒传输的 bit 数量

常见值:2400、4800、9600、115200

2、数据位

通常为 8bit

3、停止位

通常为 1bit

4、校验位

None(N):无校验

Even(E):偶校验

Odd(O):奇校验

典型参数组合:

9600, 8, N, 1

2400, 8, E, 1

115200, 8, O, 1

八、同步通信与异步通信

通信方式 时钟线 同步方式
I2C 有(SCL) 同步
SPI 有(SCLK) 同步
UART 异步

1、同步通信

双方通过共享时钟线约定通信频率,同步发送 / 接收数据

2、异步通信

无共享时钟线,通过设置相同波特率实现同步(如 UART 发送和接收端均设置为 2400bps)

九、串口寄存器配置

串口的波特率发生器依赖于定时器 1(Timer1)

1、SCON/PCON 串口控制寄存器

PCON 寄存器 bit6 置 0:通过 SCON 寄存器中 SM0 和 SM1 指定串口工作方式

SCON 寄存器 bit6 和 bit7 清 0

SCON 寄存器 SM1(bit6)置 1,SM0(bit7)清 0:串口工作在 8 位 UART 模式

SCON 寄存器 REN(bit4)置 1:允许串口接收数据

SCON 寄存器 TI(bit1):串口 8 位数据发送完毕后硬件自动置 1,需软件清 0(查询用)

SCON 寄存器 RI(bit0):串口 8 位数据接收完毕后硬件自动置 1,需软件清 0(查询用)

PCON 寄存器 SMOD(bit7)置 1:波特率加倍

2、定时器寄存器配置

TMOD(定时器模式选择寄存器):

高四位清 0(针对定时器 1)

bit5 置 1,bit4 清 0:定时器 1 工作在 8 位自动重装载模式

写入定时器初值到 TL1 和 TH1

TCON 寄存器 bit6 置 1:允许定时器 1 开始计数

3、中断寄存器配置

IE(中断控制寄存器):

bit7(EA)置 1:CPU 允许响应所有中断

bit3(ET1)置 1:允许定时器 1 产生中断

十、练习

题目:

主机发送指令,从机解析主机发送的指令并获得功能码,根据功能码完成对外设的控制,并回复应答给主机 (功能码01:LED控制 功能码02:数码管控制 功能码03:蜂鸣器控制)

注意点:

1、发送缓冲区模式

HEX 模式:以十六进制数值(0 - 9, A - F)显示原始字节(Byte)。比如字符 A ,其 ASCII 码是 0x41 ,在 HEX 模式下就显示为 41

文本模式:将字节直接转换为 ASCII 字符(可打印字符优先)来显示数据。比如字节 ,在文本模式下会显示为字符A.

2、数码管的显示---视觉暂留

数码管一次只能显示一位数字,若要显示多位数字,就需要利用循环不断刷新来实现。具体来说,就是通过循环依次选中不同的数码管位,然后在该位上显示对应的数字,并且每次显示后稍作延迟,由于人眼存在视觉暂留效应,就会感觉所有数字是同时显示的。

具体实际应用,结合digital.c的内容和main.c的调用去理解

Digiter_show函数通过while循环对输入的数字n进行处理:每次取n的个位数字m,先清空显示(P0 = 0),然后通过select_bit(t++)选中当前要显示的位,再用select_seg(m)在该位上显示数字m,之后延迟一段时间(delay(100)),最后将n除以 10 去掉已经处理过的个位。这样循环往复,直到n变为 0,从而实现了多位数字的动态显示效果,通过不断刷新各个位来让肉眼看起来是同时显示多位数字。

3、类型转换

  • 网络传输的字节本就是 0~255 的无符号值
  • 但 C 语言的char可能会把大于 127 的值当成负数
  • 强制转换确保了我们用 "无符号视角" 去解读这些字节,和十六进制常量的比较才会正确

0xBB这个值:

  • 作为有符号 char 是 - 69
  • 作为无符号 char 是 187
  • 只有用无符号方式比较,才能正确等于十六进制的0xBB(187)

代码中的recv_buffer,会将接收到的16进制强制转换成10进制

main.c

cs 复制代码
#include <reg51.h>
#include <stdio.h>
#include <string.h>
#include "uart.h"
#include "delay.h"
#include "led.h"
#include "digital.h"
#include "time.h"

#define DEV_ADDRESS	0x01

#define HZ200 	63035	//0x01
#define HZ400 	64285	//0x02
#define HZ600 	64702	//0x03
#define HZ800 	64910	//0x04
#define HZ1000 	65035	//0x05

//解析主机发过来的指令,并返回一个功能码
int parse(void)
{
	int ret = 0;
	unsigned char sum = 0;
	int i = 0;

	if ((unsigned char)recv_buffer[0] == 0xAA && (unsigned char)recv_buffer[6] == 0xBB)
	{
		if ((unsigned char)recv_buffer[1] == 0x01)
		{
			for (i = 0; i < 5 ; i++)
			{
				sum += (unsigned char)recv_buffer[i];
			}
			if (sum == (unsigned char)recv_buffer[5])
			{
				ret = recv_buffer[2];
			}
		}
	}
	return ret;
}

//根据功能码执行主机下发的控制指令
void do_handler(unsigned int n)
{
	int i = 0;

	switch (n)
	{
		case 1:
			led_show(recv_buffer[4]);
			break;
		case 2:

			while(1)
			{
			 	Digiter_show(recv_buffer[4]); 
			}
			break;
		case 3:

			switch (recv_buffer[4])
			{
				case 1:h_z = HZ200;break;
				case 2:h_z = HZ400;break;
				case 3:h_z = HZ600;break;
			    case 4:h_z = HZ800;break;
				case 5:h_z = HZ1000;break;
				default:
				break;
			}
			Timer0_Init();
			break;

		default:
			break;
	
	}
}

//从机实现对应功能 并给主机答复
//1.将主机发的内容拷贝到要发送给的数组中,并将功能码改为0x81
//2.判断起始和停止位
//3.判断地址码是否相等,是->将前五位求和->赋值给校验码位
//4.利用数组法发送至主机
void callback(void)
{
	xdata char send_buffer[10];
	unsigned char sum = 0;
	int i = 0;


	memcpy(send_buffer,recv_buffer,7);
	send_buffer[2] |= (1 << 7);		//将功能码改成0x81
	
	if((unsigned char)send_buffer[0] == 0xAA && (unsigned char)send_buffer[6] == 0xBB)
	{
		if ((unsigned char)send_buffer == DEV_ADDRESS)
		{
			for (i = 0; i < 5; i++)
			{
				sum += send_buffer[i];
			}
			send_buffer[5] = sum;
		}
	} 
	Uart_SendBuffer(send_buffer,7);
}

int main(void)
{
	int ret = 0;
	Uart_Init();
	Led_Init();

	while (1)
	{
		if (pos != 0)
		{
			delay(0xAFF);
			ret = parse();

			if ( ret != 0)
			{
				do_handler(ret);	
			}
			if (ret != 0)
			{
				callback();
			}
			pos = 0;
		}
	}

	return 0;
}

uart.c

cs 复制代码
#include <reg51.h>

xdata char recv_buffer[32];
unsigned int pos = 0;

// 串口接收服务
void uart_RecvHandler(void) interrupt 4
{
	if ((SCON & (1 << 0)) == 1)
	{
		if (pos < 32)
		{
			recv_buffer[pos++] = SBUF;
			recv_buffer[pos] = 0;
		}
		SCON &= ~(1 << 0);
	}
}

//串口初始化
void Uart_Init(void)
{
	//将scon寄存器中的bit6和bit7清0
	SCON &= ~(3 << 6);

	//串口工作模式选择:SMO:0 SM1:1 代表串口工作在8位UART模式
	SCON |= (1 << 6);

	// 允许串口接收数据
	SCON |= (1 << 4); 


	// 串口波特率加倍
	PCON &= ~(1 << 6);
	PCON |= (1 << 7);
            

	// TMOD寄存器高四位清0
	// 定时器1工作在8位自动重装模式
	TMOD &= ~(0x0F << 4);        
	TMOD |= (1 << 5);            

	// 2 ^ 8 - 2 ^ smod * focs/32/bps/12	bps:2400
	TL1 = 230;			        
	TH1 = 230;

	// 允许定时器1开始计数
	TCON |= (1 << 6);             

	// 允许CPU响应中断 + 允许串口产生中断
	IE |= (1 << 7) | (1 << 4);    
}

void Uart_SendChar(unsigned char ch)
{
	SBUF = ch;
	while ((SCON & (1 << 1)) == 0);

	SCON &= ~(1 << 1);

}

void Uart_SendStr(const char *p)
{
	while (*p)
	{
	 	Uart_SendChar(*p++);
	} 
}

void Uart_SendBuffer(const char *p,int len)
{
	while(len--)
	{
		Uart_SendChar(*p++);
	}
}

uart.h

cs 复制代码
#ifndef UART_H__
#define UART_H__

extern void Uart_Init(void);
extern void Uart_SendChar(unsigned char ch);
extern void Uart_SendStr(const char *p);
extern void Uart_SendBuffer(const char *p,int len);
extern xdata char recv_buffer[32];
extern unsigned int pos;

#endif

led.c

cs 复制代码
#include <reg51.h>

//灯 初始化
void led_init(void)
{
	P2 = 0xFF;
}

//灯 全亮
void led_allon(void)
{
	P2 = 0; 
}

//灯 全灭
void led_alloff(void)
{
	P2 = 0xFF;
}

//灯 翻转
void led_nor(void)
{
	P2 = P2 ^ 0xFF;
}

//某一位bit 亮
void led_show(unsigned int n)
{
	P2 = ~n;

}

led.h

cs 复制代码
#ifndef LED_H__
#define LED_H__


extern void led_init(void);
extern void led_allon(void);
extern void led_alloff(void);
extern void led_show(unsigned int n);
extern void led_nor(void);

#endif

time.c

cs 复制代码
#include <reg51.h>
#include "led.h"

#define HZ200 	63035
#define HZ400 	64285
#define HZ600 	64702
#define HZ800 	64910
#define HZ1000 	65035

unsigned int h_z = 0;

void Timer0_handler(void) interrupt 1
{
	TH0 = h_z >> 8;
	TL0 = h_z;
	
	P2 ^= (1 << 1);
	
}

void Timer0_Init(void)
{
	TMOD &= ~(0x0F << 0);
	TMOD |= (1 << 0);
	
	TH0 = h_z >> 8;
	TL0 = h_z;
	
	TCON |= (1 << 4);
	
	IE |= (1 << 7) | (1 << 1);

}

time.h

cs 复制代码
#ifndef TIMER_H__
#define TIMER_H__

extern unsigned int h_z;
extern void Timer0_Init(void);


#endif

delay.c

cs 复制代码
void delay(unsigned int n)
{
	while(n--);
}

delay.h

cs 复制代码
#ifndef DELAY_H__
#define DELAY_H__

extern void delay(unsigned int n);

#endif
相关推荐
殷忆枫3 小时前
基于STM32的智能家居语音控制系统设计
stm32·嵌入式硬件·智能家居
hazy1k3 小时前
8051单片机-成为点灯大师
驱动开发·嵌入式硬件·51单片机
weixin_471525785 小时前
【单片机day02】
单片机·嵌入式硬件
翻斗花园牛姥姥6 小时前
51单片机GPIO与中断全解析
单片机·嵌入式硬件
三佛科技-187366133979 小时前
辉芒微MCU需要熟悉哪些指令?这15条核心指令与入门要点必须掌握
单片机·嵌入式硬件
The️10 小时前
STM32-FreeRTOS操作系统-任务管理
stm32·单片机·嵌入式硬件·mcu
星一工作室11 小时前
STM32项目分享:基于单片机的图书馆座位监测系统
stm32·单片机·嵌入式硬件
La Pulga12 小时前
【STM32】外部中断(上)
c语言·stm32·单片机·嵌入式硬件
猫猫的小茶馆12 小时前
【C语言】汇编语言与C语言的混合编程
c语言·开发语言·stm32·单片机·嵌入式硬件·mcu·物联网