USART串口通信
通信接口
- 通信的目的: 将一个设备的数据传送到另一个设备, 扩展硬件系统
- 通信协议: 制定通信的规则, 通信双方按照协议规则进行数据收发
名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
---|---|---|---|---|---|
USART | TX、RX | 全双工 | 异步 | 单端 | 点对点 |
I2C | SCL、SDA | 半双工 | 同步 | 单端 | 多设备 |
SPI | SCLK、MOSI、MISO、CS | 全双工 | 同步 | 单端 | 多设备 |
CAN | CAN_H、CAN_L | 半双工 | 异步 | 差分 | 多设备 |
USB | DP、DM | 半双工 | 异步 | 差分 | 点对点 |
USART有同步和异步两种通讯方式, 但同步只用作特殊功能, 一般只使用异步通讯
串口通信
- 串口是一种应用十分广泛的通讯接口, 串口成本低, 容易使用、通信线路简单, 可实现两个设备的互相通信
- 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信, 极大地扩展了单片机的应用范围, 增强了单片机系统的硬件实力
通信方式
串行通信与并行通信
- 串行通信: 数据字节一位位地依次传送的通信方式, 串行通信的速度慢, 但用的传输线条数少, 成本低,适用于远距离的数据传送
- 并行通信: 数据字节的各位同事传送的通信方式, 优点是数据传送速度快, 缺点是占用的传输线条数多, 适用于近距离通信, 远距离通信的成本较高
串行异步通信和串行同步通信
异步通信:
一次通信传送一个字符帧, 发送的字符之间的时间间隔可以是任意的, 优点是通信设备简单、价格低廉, 但因为具有起始位和停止位, 传输效率较低
同步通信:
进行通信前先建立同步, 发送频率和接受方的接受频率要同步。在发送信息时, 将多个字符加上同步字符组成一个信息帧, 有一个统一的时钟控制发送端的发送, 接收端识别到同步字符后, 就认为开始一个信息帧, 此后位数作为实际传输信息处理。优点: 传输速度较快, 可用于点对多点 缺点: 需要使用专用的时钟控制线实现同步, 对于长距离通信成本较高, 通信速率也会降低。一般用于同一PCB上芯片级之间的通信
串口参数及时序(串行异步通信)
- 波特率: 串口通信的速率
- 起始位: 标志一个数据帧的开始, 固定为低电平
- 数据位: 数据帧的有效载荷, 1为高电平, 0为低电平, 低位先行
- 校验位: 用于数据验证, 根据数据位计算得来
- 停止位: 用于数据帧间隔, 固定为高电平
硬件电路
- 简单双向串口通信有两根通信线(发送端TX和接受端RX)
- TX与RX需要交叉连接
- 当只需单向的数据传输时, 可以只接一根通信线
- 当电平标准不一致时, 需要加电平转换芯片
电平标准
- 电平标准是数据1和数据0的表达方式, 是传输线缆中人为规定的电压与数据的对应关系, 串口常用的电平标准有如下三种:
- TTL电平: +3.3V或+5V表示1, 0V 表示0
- RS232电平: -3~-15V表示1, +3~+15V表示0
- RS485电平: 两线压差+2~+6V表示1, -2~-6V表示0(差分信号)
差分信号的干扰小, 一般TTL电平和RS232电平传输距离只有十几米, 而RS485电平的传输范围可达数千米
串口时序
USART简介
- USART(Universal Synchronous/Asynchronous Receiver/Transmitter) 通用同步/异步收发器
- USART是STM32内部集成的硬件外设, 可根据数据寄存器的一个字节数据自动生成数据帧时序, 从TX引脚发送出去, 也可自动接收RX引脚的数据帧时序, 拼接为一个字节数据, 存放在数据寄存器里
- 自带波特率发生器, 最高可达4.5Mbits/s
- 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
- 可选校验位(无校验/奇校验/偶校验)
- 支持同步模式、硬件流控制、DMA、智能卡、IrDA、Lin
- STM32F108C8T6 USART资源: USART1、USART2、USART3
USART框图
USART基本结构
数据帧
- 字长包含校验位 一般情况下: 9位字长(设置奇偶校验) 8位字长(无校验)
起始位侦测
- 在一位的时间里进行16次的采样
- 3、5、7/ 8、9、10次进行采样 且要求每3位至少应该有两个0(只有两个0会在状态寄存器里置NE, 噪声标志位)
数据采样
- 8、9、10次采样每3位也至少应该有两个0(只有两个0会在状态寄存器里置NE, 噪声标志位)
波特率发生器
- 发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
- 计算公式: 波特率 = fPCLK2/1 /(16 * DIV)
案例: 上位机控制LED亮灭
说明:
按键输入端为PA1 采用EXTI中断
PA0输出控制LED1, PA2输出控制LED2
LED2可通过按键控制
LED1通过上位机下达指令控制
利用Timer定时器每隔十秒进入一次中断, 获取LED1和LED2的亮灭状态
硬件接线
PA0接LED1, PA1接按键,PA2接LED2,PA9为单片机的Tx发送端接CH3400的Rx端,PA10为单片机的Rx接收断接CH3400的Tx端
初始化USART
/*串口通信初始化函数 抢占优先级0 响应优先级0*/void Serial_Init(void){// RCC使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置GPIO// TX 发送端GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// RX 接收断GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置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;// 8位USART_Init(USART1, &USART_InitStructure);// 打开USART中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 接收寄存器非空(正在接收)// 配置NVICNVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel= USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;NVIC_Init(&NVIC_InitStructure);// 启动USARTUSART_Cmd(USART1, ENABLE);}
主要代码
Serial.c
#include "stm32f10x.h"char Down_String[] = "AABBCCDD01000000\r\n";// 保存从下位机获取的数据char Header[] = "AABBCCDD00";// 协议头+数据来源char Up_String[100];// 保存从上位机传送来的数据uint8_t idx;//下标 uint8_t Decode_Flag;/*串口通信初始化函数 抢占优先级0 响应优先级0*/void Serial_Init(void){// RCC使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置GPIO// TX 发送端GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// RX 接收断GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置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;// 8位USART_Init(USART1, &USART_InitStructure);// 打开USART中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 接收寄存器非空(正在接收)// 配置NVICNVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel= USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;NVIC_Init(&NVIC_InitStructure);// 启动USARTUSART_Cmd(USART1, ENABLE);}/*发送单个数据*/void Send_Byte(uint16_t Byte){USART_SendData(USART1, Byte);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);// 等待发送完毕}/*发送字符串*/void Serial_SendString(char * String){uint8_t i;for(i=0; String[i] != '\0'; ++i){Send_Byte(String[i]);}}/*向上位机传送监控的信息*/void Transimit_Message(void){Serial_SendString((char*)Down_String);}/*获取LED信息 LED为低电平驱动*/void Get_LEDInfo(void){// LED1 if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==1){ // LED1灭Down_String[11] = '0';}else{Down_String[11] = '1';}// LED2if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)==1){ // LED2灭Down_String[13] = '0';}else{Down_String[13] = '1';}}/*下位机解码上位机传送的信息*/uint8_t Decode_Info(char * String){for(uint8_t i=0; i < 8; ++i){if(String[i] != Header[i]){return 0;}}// 判断是否有保留位for(uint8_t i=12; i <= 15; ++i){if(String[i] != '0') return 0;}// 控制LED1亮灭if(String[10] == '0' && String[11] == '1'){// 开灯GPIO_ResetBits(GPIOA, GPIO_Pin_0);}else if(String[10] == '0' && String[11] == '0'){// 关灯GPIO_SetBits(GPIOA, GPIO_Pin_0);}else return 0;return 1;}/*根据反馈解码状态打印信息*/void Decode_Info_Print(void){if(Decode_Flag == 0){// 解码失败, 请用正确的协议进行通讯Serial_SendString("Decoded failed!\r\nPlease ensure the communication protocol is correct!\r\n");}else{// 解码成功!LED1状态转换成功Serial_SendString("Decoded successful!\r\n");}Decode_Flag = 0;}/*中断函数*/void USART1_IRQHandler(void){if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)// 接受寄存器不空{uint8_t Serial_RxData;Serial_RxData = USART_ReceiveData(USART1);// 接受数据Up_String[idx++] = (char)Serial_RxData;// 清除中断标志USART_ClearITPendingBit(USART1, USART_IT_RXNE);}}
Serial.h
#ifndef __SERIAL_H#define __SERIAL_H#include "stm32f10x.h"void Serial_Init(void);void Send_Byte(uint16_t Byte);void Serial_SendString(char * String);void Transimit_Message(void);void Get_LEDInfo(void);uint8_t Decode_Info(char * String);void Decode_Info_Print(void);extern char Down_String[];extern char Header[];extern uint8_t idx;extern uint8_t Decode_Flag;extern char Up_String[100];#endif
LED.c
#include "stm32f10x.h"/*初始化LED1*/void LED1_Init(void){// RCC使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//初始化GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_SetBits(GPIOA, GPIO_Pin_0);// 设置高电平}/*关灯 低电平驱动*/void LED1_Off(void){GPIO_SetBits(GPIOA, GPIO_Pin_0);}/*开灯*/void LED1_On(void){GPIO_ResetBits(GPIOA, GPIO_Pin_0);}/*翻转灯泡状态*/void LED1_Turn(void){if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==1){LED1_On();}else{LED1_Off();}}void LED2_Init(void){// RCC使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//初始化GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_SetBits(GPIOA, GPIO_Pin_2);// 设置高电平}/*关灯 低电平驱动*/void LED2_Off(void){GPIO_SetBits(GPIOA, GPIO_Pin_2);}/*开灯*/void LED2_On(void){GPIO_ResetBits(GPIOA, GPIO_Pin_2);}/*翻转灯泡状态*/void LED2_Turn(void){if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)==1){LED2_On();}else{LED2_Off();}}
key.c
#include "stm32f10x.h"#include "LED.h"/*初始化按键 PA1 抢占优先级1 响应优先级1*/void Key_Init(void){// RCC使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);// 初始化GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;// 上拉输入GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; // PA1GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置AFIOGPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);// 配置中断EXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure.EXTI_Line=EXTI_Line1;EXTI_InitStructure.EXTI_LineCmd=ENABLE;EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;EXTI_Init(&EXTI_InitStructure);//配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);}/*按键中断函数*/void EXTI1_IRQHandler(void){if(EXTI_GetITStatus(EXTI_Line1) == SET){if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)==0){LED2_Turn();}// 清除中断标志EXTI_ClearITPendingBit(EXTI_Line1);}}
Timer.c
#include "stm32f10x.h"#include "Serial.h"/*初始化定时器 抢占优先级0 响应优先级1 */void Timer_Init(void){// RCC开启时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);// 选择时钟源TIM_InternalClockConfig(TIM2);// 内部时钟// 配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;// 内部时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;// 计数模式TIM_TimeBaseInitStructure.TIM_Period=50000-1;// 加载到自动重装寄存器的值TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;// 预分频的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;// 重复计数器的值 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//TIM_ClearFlag(TIM2, TIM_FLAG_Update);// 清除标志// 使能更新中断TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);// 配置NVICNVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;// 中断通道NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;// 抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;// 响应优先级NVIC_Init(&NVIC_InitStructure);// 启动定时器TIM_Cmd(TIM2, ENABLE);return;}/*10秒钟进入一次中断 获取系统信息*/void TIM2_IRQHandler(void){// 判断标志位if (TIM_GetITStatus(TIM2, TIM_IT_Update)==SET){// 获取LED信息Get_LEDInfo();// 将信息发送给上位机Transimit_Message();// 清除标志位TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}}
main.c
#include "stm32f10x.h" #include "delay.h"#include "LED.h"#include "key.h"#include "Timer.h"// 定时器 10s 获取一次信息#include "Serial.h"// 串口相关程序// 上位机控制LED亮灭int main(void){LED1_Init();LED2_Init();Key_Init();Timer_Init();Serial_Init();while(1){// 接收到一个完整协议的数据 进行解析if(idx >= 16){// 解码Decode_Flag = Decode_Info((char*)Up_String);// 打印解码信息Decode_Info_Print();idx = 0;}}}
效果展示
视频链接
【单片机作业: USART通信案例上位机控制LED灯亮灭】
参考资料
嵌入式单片机STM32原理及应用
【STM32入门教程-2023持续更新中】