一、简介
通信:将一个设备的数据传送到另一个设备,扩展硬件系统
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发
USART:引脚TX、RX 全双工 同步/异步时钟 单端电平 点对点设备
全双工:通信双方能够同时进行双向通信
串口:串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的相互通信
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块相互通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。
二、硬件电路连接
简单双向串口通信有两根通信线(发送TX、接收RX)
TX、RX要交叉连接
当只需要单向数据传输时,可以只接一根通信线
当电平标准不一致时,需要加电平转换芯片
三、电平标准
电平标准是数据1(高电平)和数据0(低电平)的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V或+5V表示1,0V表示0;
RS232电平:-3~-15V或+5V表示1,+3~+15V表示0;
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
四、串口参数及时序
波特率:串口通信的速率。 二进制调制:波特率=比特率
起始位:标志一个数据帧的开始,固定为低电平。串口空闲状态为高电平,使用固定低电平的起始位产生下降沿,来判断数据开始发送
数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行。发送一个字节,低位先行
校验位:用于数据验证,根据数据位计算得来。
停止位:用于数据帧间隔,固定位高电平。停止位将电平拉高,这样有新的数据的时候,起始位的低电平才会产生下降沿。
五、USART简介(STM32)
1、USART(Universal Synchronous/Asynchronous Receiver/ Transmitter)通用同步/异步收发器
2、USART是STM32内部集成的硬件外设,可根据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送,也可以自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。
3、自带波特率发生器,最高达4.5Mbits/s
4、可配置数据位长度(8/9)、停止位长度(0、1、1.5、2)
5、可选校验位(无校验、奇校验、偶校验)
6、支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
STM32F103C8T6拥有三个串口(串口1、串口2、串口3)它们对应的引脚如下:
这是通过STM32CubeMX软件进行的查询,该软件主要用于hal库的快速开发,也可以通过查询手册来查找引脚功能。当标准库学习打好一定基础后,可以学习hal库,hal库的来快速进行功能配置。
六、标准库串口配置流程
1、使能时钟(USART时钟及其GPIO时钟)
2、初始化GPIO引脚(引脚、模式、速度)
3、配置USART参数(波特率、数据位、停止位、校验位、传输和接收模式等)
4、使能USART外设
5、配置中断(可以不使用中断触发串口接收,但是不使用中断可能会导致接收数据不全)
6、发送数据函数
7、接收数据函数
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h> //通过这个头文件,可以在函数中处理不定数量的参数,比如printf()函数
uint8_t Serial_RxData; //用于存储从串口接收到的单个字节数据
uint8_t Serial_RxFlag; //接收标志位,用于指示是否有新数据接收到
void Serial_Init(void) //串口初始化函数
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //打开串口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //打开GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure; //GPIO引脚配置 结构体定义
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //设置USART1 TX引脚 PA9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置速度
GPIO_Init(GPIOA, &GPIO_InitStructure); //应用配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入上拉模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1 RX 引脚 PA10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度,可以不设置
GPIO_Init(GPIOA, &GPIO_InitStructure); //应用配置
USART_InitTypeDef USART_InitStructure; //串口配置结构体定义
USART_InitStructure.USART_BaudRate = 9600; //波特率 9600
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //串口发送和串口接收
USART_InitStructure.USART_Parity = USART_Parity_No; //无校验位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1个停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8个数据位
USART_Init(USART1, &USART_InitStructure); //应用配置
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能接收中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //USART1 中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //响应优先级
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE); //使能USART1(即开启串口)
}
//串口数据发送函数
void Serial_SendByte(uint8_t Byte) //发送一个字节数据,并等待发送缓存区为空
{
USART_SendData(USART1, Byte); //发送单字节数据
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
}
void Serial_SendArray(uint8_t *Array, uint16_t Length) //发送一个字节数组,逐个字节发送
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]); //逐个字节发送数组中的数据
}
}
void Serial_SendString(char *String) //发送一个字符串,以空字符“\0”为结束标志
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++) //如果数据不为\0则继续发送 0A
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y) //计算X的Y次幂,用于后续的数字转换
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length) // 将一个整数按位转换为字符并逐个发送,以显示数值。
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
}
}
//fputc: 标准库函数printf重定向到串口输出。这样,printf输出的字符将通过串口发送
int fputc(int ch, FILE *f) //重定向 printf 函数输出
{
Serial_SendByte(ch); //将字符发送到串口
return ch;
}
/*
char *format: 第一个参数是一个字符串指针,它定义了输出的格式。类似于 printf 的格式字符串,可以包含格式说明符,如 %d、%s 等。
...: 三个点表示这是一个可变参数函数,可以接受任意数量的参数。
*/
void Serial_Printf(char *format, ...) //变参格式化输出函数
{
char String[100]; //定义字符串缓冲区
va_list arg; //va_list 是一个类型,用于访问可变参数列表
va_start(arg, format); //初始化 va_list
/*va_start 是一个宏,用于初始化 va_list 变量,使其指向可变参数的第一个参数。
format 是最后一个固定参数,用于告诉 va_start 从哪里开始处理可变参数。*/
vsprintf(String, format, arg);
/*
vsprintf 是标准C库函数,用于将格式化的输出写入 String 缓冲区中。
format 是格式字符串,arg 是初始化后的 va_list,它包含了所有可变参数的值。
这一步实现了将传入的可变参数按照指定的格式组合成一个字符串。
*/
va_end(arg);//va_end 宏用于清理 va_list 变量。这是一个好的实践,可以避免潜在的错误
Serial_SendString(String); //通过串口发送字符串
}
//串口接收标志函数
uint8_t Serial_GetRxFlag(void) //检查是否有新数据接收,如果有则清除标志并返回1。
{
if (Serial_RxFlag == 1)
{
Serial_RxFlag = 0; //清除标志位
return 1; //返回1,表示有新数据
}
return 0; //返回0,表示没有新数据
}
uint8_t Serial_GetRxData(void) //返回接收到的字节数据
{
return Serial_RxData; //返回接收到的数据
}
void USART1_IRQHandler(void) //串口1中断服务函数
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //检查接收中断标志
{
Serial_RxData = USART_ReceiveData(USART1); //读取接收到的数据
Serial_RxFlag = 1; //设置接收标志位
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除中断标志
}
}