5.串口的输入输出

目录

通信分类

常见的通信和通信接口

常见的通信分类

        串行和并行区分 

        单端和差分

        单工和双工 

        同步和异步 

常见通信和分类

串口的讲解

串口在电子领域的作用

串口下载是如何实现的

串口的分类

串口的不同电平和调试

        TTL 电平 

        232 电平 

        485 电平 

串口通信的物理层

串口通信的协议层

串口重要的参数

串口传输数据所需时间的计算

STM32 中串口的讲解

STM32 中串口的基本结构

STM32 中串口发送

STM32 中串口的接收

STM32 中串口波特率的设置

STM32 中串口的中断

STM32 中串口的个数和引脚

串口初始化的配置

代码


通信分类 

常见的通信和通信接口

        常见的通信:串口,485,iic, spi, can(汽车电子),wifi,蓝牙,4G,NB,lora 

        常见的通信接口: 单总线 串口 485 IIC SPI CAN 

常见的通信分类

        串行和并行区分 

                串行:数据只能 1bit 1bit 的发送/接收 --传输会慢 

                并行:数据可以支持多个 bit 同时发送/接收 --传输数据快

                区分:几根数据线 发送或者接收数据只有一根线 那就是串行 

        单端和差分

                单端:一个数据线的高低区别 0 1 --串口 UART

                差分:两个线电压差区别 0 1 -- 485 通信,CAN 通信 抗干扰能力强 

                区分:一根线确定数据 0 还是 1 叫做单端,两根线电平差确定数据 0 还是 1 叫做差分 

        单工和双工 

                单工: -- 收音机 

                设备只能发送(接收),只能做一件事

                双工:设备能够发送也能接收

                半双工:发送和接收都可以进行,但不可以同时进行 -- 485 

                全双工:发送和接收不相互影响,可以同时进行 --串口,SPI 

                区分:只能发送或者接收,叫做单工;半双工,可以发送,也可以接收,但是不能同时发送和接收;全双工,可以发送也可以接收,并能够同时进行 

        同步和异步 

                同步: -- 有时钟线 

                设备在通信时,用的是同一个时钟,时钟节奏由主机操作 

                异步: -- 无时钟线 

                设备在通信时,没有时钟线 

                区分:有时钟线就是同步通信,没有时钟线就是异步 

常见通信和分类

串口的讲解

串口在电子领域的作用

        UART:调试 -- printf 

        短距离有线通信:两个设备通过串口进行通信 

        设备之间的无线通信:4G、LORA、NB、蓝牙等,单片机通过串口实现无线通信 

串口下载是如何实现的

        FlyMCU 下载 -- 电脑 – USB -- CH340 -- USART1 -- STM32 

        CH340: 实现 USB 电平转换成 TTL 电平 

        一键下载电路,可以实现自动调节 Boot0 的电平 

串口的分类

        串口的通信方式:串行全双工单端,UART 和 USART 都是串口 

        UART:有线串行单端全双工异步通信 

        USART:有线串行单端全双工同步通信 

        S 指的是时钟 

        但是实际应用中,一般都是使用 UART 

串口的不同电平和调试

        TTL 232 485 本质上都是串口,只是电平规范不一样 

        TTL 电平 

                TTL 电平: 单片机的串口引脚 都是 TTL 电平 

如果电脑接收 TTL 电平数据,需要将 TTL 电平转换成 USB 电平,常见通过 CH340 实现。

        232 电平 

                232 电平:TTL+232 芯片 转化成 232 电平

        485 电平 

                485 电平:TTL+485 芯片 转化成 485 电平

注意

        如果两个设备通信:必须电平一致 

        TTL 接口: TX RX GND 

        232 接口: TX RX GND

        485 接口: A B 

串口通信的物理层

        T:发送 transmission 

        R:接收 Receive 

        交叉连接 

串口通信的协议层

        位协议:以 bit 为单位,每位都有信息 

        字协议:以 byte 为单位,每个字节代表的有信息 

        串口的数据协议: 

        起始位 数据位 校验位 停止位

        校验:奇校验、偶校验、无校验

        数据位中 1 的个数+奇偶校验位 1 的个数 -- 奇数/偶数

        校验位   1 -- 奇校验郑  0 -- 偶校验 

        校验是为了接收方检测接收的数据是否准确 

        波特率:1s 发送多少个位 bits/s --传输快慢 双方设备 必须一致 

串口重要的参数

        波特率 数据位 停止位 校验方式

        波特率一致,数据位的个数一致,校验方式一致,停止位个数一致 

        9600 波特率 8 个数据位 无校验 1 个停止位 (简写:9600 8 N 1) 

串口传输数据所需时间的计算

        已知:9600 波特率(bits/s) 8 个数据位 1 个停止位 无校验

        每传输 1 个字节,起始位(1 位)+数据位(8 位)+停止位(1 位)=共 10 位 

        波特率=9600 波特率(bits/s),那么传输 1 位时间 t=1/9600(s/bit) 

        传输 1 个字节时间=传输 10 位时间=10*t=10*(1/9600)=1/960s=1ms 

        已知:115200 波特率(bits/s) 8 个数据位 1 个停止位 奇校验

        每传输 1 个字节,起始位(1 位)+数据位(8 位)+停止位(1 位)+校验位(1 位)=共 11 位 

        波特率=115200 波特率(bits/s),那么传输 1 位时间 t=1/115200(s/bit) 

        传输 1 个字节时间=传输 11 位时间=11*t=11*(1/115200)

STM32 中串口的讲解

STM32 中串口的基本结构

STM32 中串口发送

STM32 中串口的接收

STM32 中串口波特率的设置

        常见的波特率 9600 115200 

        UART1:72M 

        UART2--5:36M 

STM32 中串口的中断

        接收数据就绪可读:接收到数据了 

        检测到空闲线路:一帧数据接收完成了 

        事件标志:TC TXNE IDLE

STM32 中串口的个数和引脚

        数据手册,引脚定义

        一共五个串口
        USART1_TX -- PA9
        USART1_RX -- PA10
        USART2_TX -- PA2
        USART2_RX -- PA3
        USART3_TX -- PB10
        USART3_RX -- PB11
        USART4_TX -- PC10
        USART4_RX -- PC11
        USART4_CK -- PC12
        USART5_RX -- PD2

        一般使用默认复用功能,如果使用的是重定义的功能,需要开启 AFIO 的时钟 

串口初始化的配置

需要配置,单片机内部的串口外设需要借助 GPIO 口和外界通信.

代码

#include "UART1.h"
#include "main.h"
#include "stdio.h"
#include "led.h"
#include "relay.h"
#include "stdio.h"
#include "string.h"

uint8_t U1_R_Buff[100];//接收缓冲区
uint8_t U1_R_Length = 0;//接收到的长度
uint8_t U1_R_Idle = 0;//接收完成标志位,1完成,0未完成

void UART1_Config(void)
{
#if (USB_STD_LIB==0)

	//1.开启GPIOA
	RCC->APB2ENR |= (0x01<<2);
	
	GPIOA->CRH &= ~(0xF << 4);//先清0
	GPIOA->CRH |= (0xB << 4);//在配置模式1011
	
	GPIOA->CRH &= ~(0xF << 8);//先清0
	GPIOA->CRH |= (0x4 << 8);//在置0100
	//开串口1的时钟
	RCC->APB2ENR |= (0x01<<14);
	USART1->CR1 |= (0x1 << 13);
	USART1->CR1 &= ~(0x1 << 12);
	USART1->CR2 &= ~(0x3 << 12);
	/*USARTDIV = fck / (波特率 * 16)
	UART1接APB2总线72M,所以fck=72M
	UART2--UART5接APB1总线36M,所以fck=36M
	假如想要9600波特率
	USARTDIV = fck / (波特率 * 16)=72000000/(9600*16)=468.75
	小数部分=0.75*16=12=0x0C
	整数部分=468=0x1D4
	整数和小数拼接,写入寄存器的结果=0x1D4C
	*/
	USART1->BRR = 0x1D4C;
	//发送使能TE
	USART1->CR1 |= (0x01<<3);
	//接收使能RE
	USART1->CR1 |= (0x01<<2);
	//开启接收中断RXNEIE
	USART1->CR1 |= (0x01<<5);
	//开启空闲中断IDLEIE
	USART1->CR1 |= (0x01<<4);
	NVIC_SetPriority(USART1_IRQn, 9);//抢占3次级0
	//允许NVIC层面的中断
	NVIC_EnableIRQ(USART1_IRQn);
#elif (USB_STD_LIB==1)
	//1.开A端口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	//2.定义结构体 xxx需要传递结构体地址,PA9 PA10
	GPIO_InitTypeDef GPIO_InitStruct = {0};//3.给结构体赋值
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//代配置引脚
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//引脚速率
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;//代配置引脚
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStruct);//4.调用xxx_Init函数,将参数写入寄存器中
	//3.开USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	//4.配置USART
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制  ---- 自动发送和接收数据
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//开启使能发送和接收
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验方式,奇偶校验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位长度
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据长度
	USART_Init(USART1, &USART_InitStructure);
	// 5. 启动USART1
	USART_Cmd(USART1, ENABLE);
	// 6. 配置USART中断,开启中断必须写中断服务函数,下方开启了俩中断,在中断中必须处理这两个
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//接收中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//空闲中断
	// 7. 配置NVIC中断
	NVIC_InitTypeDef   NVIC_InitStructure = {0};
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//次级优先级
	NVIC_Init(&NVIC_InitStructure);
	
#endif
}

//单字节发送
/*TC和TXE的区别
TXE == 1 表示发送数据寄存器空,数据移位寄存器不确定
TC == 1 表示发送数据寄存器空,数据移位寄存器空
*/
/*
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
*/
void UART1_SendByte(uint8_t data)
{
#if (USB_STD_LIB==0)
	while((USART1->SR & (0x01 << 6)) == 0)//0上次没发完,1发送完成
	{}
	USART1->DR = data;//发送完成,发送新数据
#elif (USB_STD_LIB==1)
	//非中断中使用不挂IT的函数
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)//RESET上次没发完,SET发送完成
	{}
	USART_SendData(USART1, data);//发送完成,发送新数据
//	(1)软件序列清除该位(先读USART_SR,然后写入USART_DR)。   (2)写0清0
//	USART_ClearFlag(USART1,USART_FLAG_TC);
		
#endif
}
//数组发送
void UART1_SendBuff(uint8_t *Buff, uint16_t Length)
{
	for(uint16_t i = 0; i < Length; i++)
	{
		UART1_SendByte(*Buff++);
	}
}
//字符串发送
void UART1_SendStr(uint8_t *Str)
{
	while(*Str != '\0')
	{
		UART1_SendByte(*Str++);
	}
}
/*
实现printf,fputc函数的重写
1.必须串口初始化
2.必须勾选魔法棒 Use MicroLIB
3.换行使用\r\n
4.printf只能和某一个串口一块使用,一般是调试口
*/
int fputc(int ch, FILE *f)
{
	UART1_SendByte(ch);
	return ch;
}

//阻塞接收,不断检测,很少使用
uint8_t UART1_RecByte(void)
{
	
#if (USB_STD_LIB==0)
	uint8_t temp = 0;
	while((USART1->SR & (0x01 << 5)) == 0)//检测RXNE
	{//0未接收到
		
	}
	//1接收到
	temp = USART1->DR;
	return temp;
#elif (USB_STD_LIB==1)
	uint8_t temp = 0;
	while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)//检测RXNE
	{//RESET未接收到
		
	}
	//1接收到
	temp = USART_ReceiveData(USART1);
//	USART_GetFlagStatus(USART1,USART_FLAG_RXNE);
	return temp;
		
#endif
}

/*
进入中断接收一个数据
如果对方连续发送5个字节的数据,会触发6次中断,其中5次接收中断,1次空闲中断
*/
/*
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
放断点时,接收中断中一班不放断点,如果放了会导致后面的中断接收不到。
如果可以放断点运行进去,证明接收中断没问题。
一般可以再空闲中断放断点,可以查看watch窗口查看接收到的内容对不对。
*/
void USART1_IRQHandler(void)
{
	uint8_t data = 0;
    // 接收中断,RXNE
    if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) 
    {
        data = USART_ReceiveData(USART1);  // 读取数据
        U1_R_Buff[U1_R_Length++] = data;   // 存入缓冲区
    }
    // 空闲中断(数据接收完毕)
    if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET) 
    {
        data = USART_ReceiveData(USART1);  // 清除IDLE标志
        U1_R_Idle = 1;  // 标记接收完成
        // 这里不需要清除中断标志,因为空闲中断不能通过`USART_ClearITPendingBit()`清除
    }
}
/*
通过串口助手发送下面4个字节十六进制数据,按照要求,单片机执行对应的内容
5A 02 功能码 和校验
总共 4 个字节
5A 帧头
02 剩余长度
功能码 1 个字节 01 开灯 02 关灯 03 开蜂鸣器 04 关蜂鸣器
和校验 1 个字节 前面所有字节(不包括自己)的字节和
5A 02 01 5D 开灯
5A 02 02 5E 关灯
5A 02 03 5F 开蜂鸣器
5A 02 04 60 关蜂鸣器
*/
//处理函数
void USART1_Handler(void)
{
	uint8_t data_cs = 0;
	if(U1_R_Idle == 1)
	{//1数据接收完成
		U1_R_Idle = 0;//空闲标志位清0
		//处理数据内容,处理完成后清除接收缓冲区
		data_cs = U1_R_Buff[0] + U1_R_Buff[1] + U1_R_Buff[2];  // 校验和计算
		// 校验接收到的校验字节
		if (data_cs == U1_R_Buff[3]) 
		{
			if(U1_R_Buff[0] == 0x5A && U1_R_Buff[1] == 0x02)//校验帧头和剩余帧
			{
				uint8_t function_code = U1_R_Buff[2];
				switch (function_code) 
				{
					case 0x01:  // 开灯
						LED1_ON();LED2_ON();LED3_ON();LED4_ON();
						printf("Received Data: ");
						for (int i = 0; i < U1_R_Length; i++) 
						{
							printf("%02X ", U1_R_Buff[i]);
						}
						printf("\r\nChecksum: %02X, Received checksum: %02X\r\n", data_cs, U1_R_Buff[3]);
						break;
					case 0x02:  // 关灯
						LED1_OFF();LED2_OFF();LED3_OFF();LED4_OFF();
						printf("Received Data: ");
						for (int i = 0; i < U1_R_Length; i++) 
						{
							printf("%02X ", U1_R_Buff[i]);
						}
						printf("\r\nChecksum: %02X, Received checksum: %02X\r\n", data_cs, U1_R_Buff[3]);
						break;
					case 0x03:  // 开继电器
						Relay_ON();
						break;
					case 0x04:  // 关继电器
						Relay_OFF();
						break;
					default:
						// 无效的功能码
						break;
				}
			}
			else
			{
				printf("帧头和剩余长度有错误!!!\n");
			}
		}
		else
		{
			printf("校验不通过!!!\n");
		}
		memset(U1_R_Buff, 0, sizeof(U1_R_Buff));
		U1_R_Length = 0;
	}
	//不能放在这里,因为数据还没接收完就被清除了
	//memset(U1_R_Buff, 0, sizeof(U1_R_Buff));
	//U1_R_Length = 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力做小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值