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.开启GPIOARCC->APB2ENR |= (0x01<<2);GPIOA->CRH &= ~(0xF << 4);//先清0GPIOA->CRH |= (0xB << 4);//在配置模式1011GPIOA->CRH &= ~(0xF << 8);//先清0GPIOA->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=72MUART2--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;//发送使能TEUSART1->CR1 |= (0x01<<3);//接收使能REUSART1->CR1 |= (0x01<<2);//开启接收中断RXNEIEUSART1->CR1 |= (0x01<<5);//开启空闲中断IDLEIEUSART1->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 PA10GPIO_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.配置USARTUSART_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. 启动USART1USART_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;// 接收中断,RXNEif(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;
}