前言
本篇博客主要学习了解UART串口通信,了解串口基本通信方案,了解串口的时序和中断,实现最基本的发送和接收。本篇博客大部分是自己收集和整理,如有侵权请联系我删除。
本次博客开发板使用的是正点原子精英版,芯片是STM32F103ZET6,需要资料可以@我拿取。
交流群:717237739
如果觉得有用点赞关注收藏三连,多谢支持
本博客内容原创,创作不易,转载请注明
一. 认识基本的通信
1.通信接口:
目的:将一个设备的数据通过某种协议格式传输给另外一个设备,拓展硬件系统。
通信协议:指定通信规则,通信双方按照协议的规则进行数据收发
2.通信简介:
串行通信和并行通信:
计算机与外界的信息交换称为通信, 基本的通信方式有两种。
-
- 并行通信: 所传送数据的各位同时发送或接收。
-
- 串行通信: 所传送数据的各位按顺序一位一位地发送或接收。
串行通信的方式:
按照数据传送方向,串行通信可分为单工制式、半双工制式和全双工制式。
单工制式: 数据在甲机和乙机之间只允许单方向传送。两机之间只需 1 条数据线。
**半双工制式:**数据在甲机和乙机之间允许双方向传送,但只能分时复用,因而两机之间只需 1 条数据线。
全双工制式: 甲、 乙两机之间数据的发送和接收可以同时进行, 全双工形式的串行通信必须使用 2 条数据线
同步通信和异步通信:
同步通信:
接收和发送时钟严格保持同步,在通信时通常要求有同步时钟信号。
对硬件结构要求较高。由于这种方式易于进行串行外围扩展,所以目前很多型号的单片机都增加了串行同步通信接口,如目前已得到广泛应用的 I2C 串行总线和 SPI 串行接口等。
异步通信:
进行串行通信的单片机的时钟相互独立。通信双方只需按约定的帧格式来发送和接收数据,所以硬件结构比同步通信方式简单。此外,它还能利用校验位检测错误,所以这种通信方式应用较广泛。在单片机中主要是采用异步通信方式。
二 . 简单认识串口UART
1.串口通信(全双工的,异步通信)
- 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
- 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力
2.硬件电路
UART异步通信方式引脚连接方法:有供电可以不接VCC,接不接都行
- -RXD:数据输入引脚。数据接受。
- -TXD:数据发送引脚。数据发送。
- 简单双向串口通信有两根通信线(发送端TX和接收端RX) TX与RX要交叉连接 当只需单向的数据传输时,可以只接一根通信线 当电平标准不一致时,需要加电平转换芯片
3.电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
- TTL电平:+3.3V或+5V表示1,0V表示0
- RS232电平:-3~-15V表示1,+3~+15V表示0
- RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
三. 串口简介
1.基本特性简介
- 全双工异步通信。
- 小数波特率发生器系统,提供精确的波特率。
- 可配置的16倍过采样或8倍过采样,因而为速度容差与时钟容差的灵活配置提供了可能。
- 可编程的数据字长度(8位或者9位);
- 可配置的停止位长度(0.5/1/1.5/2);
- 可选校验位(无校验/奇校验/偶校验)
- 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
- 可配置的使用DMA多缓冲器通信。 单独的发送器和接收器使能位。
- 检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志 多个带标志的中断源。
- 校验控制:─ 发送校验位 ─ 对接收数据进行校验
- 四个错误检测标志:溢出错误 ─ 噪音错误 ─ 帧错误 ─ 校验错误
2.STM32串口通信过程
3.串口参数及时序
- 波特率:串口通信的速率,表示一秒传输的位数,一秒传多少个字节?9600/(1+8+1)=960
波特率计算公式:波特率/(起始位+数据位+停止位+校验位)
(两个使用串口通信的设备,它们的波特率必须设置为相同,不然无法进行通信,会乱码)
- 起始位:标志一个数据帧的开始,固定为低电平
- 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
- 校验位:用于数据验证,根据数据位计算得来
- 停止位:用于数据帧间隔,固定为高电平
这些配置的参数,其实就是一些协议的规定,基本就是按照常见的配置就行了
注意:如果使用奇偶校验,数据位不是八位而是九位了
串口时序:
传输数据时先传送字符的低位,后传送字符的高位。即低位(LSB)在前,高位(MSB)在后
注意:数据都是从低位(0)开始,然后按照时序读出对应的0/1,完成之后,正确的读法应该是从后往前,例如第一个0X55,十六进制是 01010101,从后面开始读。
四.串口配置 和 库函数详解
1.配置过程(正点原子精英板)
1)查看原理图,具体使用的功能。
2)配置串口对应的IO口
TXD(PA.9 ) RXD(PA.10)
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
3)配置串口的功能
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
4)开启IO口和串口的时钟
每次配置都要打开时钟,找到对应的总线并使能。
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
5)使能串口
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
6)串口发送数据
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
7)串口接收数据
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
这是最基础的串口使用,如果需要中断可以参考我的博客文章,这里暂时不做演示,附送中断代码。NVIC中断详解
2.串口初始化代码
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
实验现象:
3.自己编写的发送接收字节和字符串代码 (参考)
串口(发送)一个字节
/********************************************************************
*函数名 :void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data)
*功能描述 :串口发送一个字节函数
*入口参数 :USARTx,哪个串口 ; Data 发送的数据
*出口参数 :无
*函数返回: 无
*调用提示 :无
*********************************************************************/
void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data)
{
while (!USART_GetFlagStatus(USARTx,USART_FLAG_TC));//等等上一个字节发送完成
USART_SendData(USARTx,Data);
}
串口(接收)一个字节
/********************************************************************
*函数名 :uint16_t USART_ReceiveByte(USART_TypeDef* USARTx)
*功能描述 :串口接收一个字节函数
*入口参数 :USARTx ,哪个串口
*出口参数 :无
*函数返回: 返回收到的字符
*调用提示 :无
*********************************************************************/
uint16_t USART_ReceiveByte(USART_TypeDef* USARTx)
{
while (!USART_GetFlagStatus(USARTx,USART_FLAG_RXNE)); //等待收到一个字节
return USART_ReceiveData(USARTx); //返回收到的数据
}
串口发送字符串函数
/********************************************************************
*函数名 :void USART_SendStr(USART_TypeDef* USARTx, uint8_t *str)
*功能描述 :串口发送字符串函数
*入口参数 :USARTx,哪个串口 ; str 发送的数据
*出口参数 :无
*函数返回: 无
*调用提示 :无
"hello322432422342" --字符串以'\0'
*********************************************************************/
void USART_SendStr(USART_TypeDef* USARTx, uint8_t *str)
{
while(*str != '\0'){
USART_SendByte(USARTx,*str++);
}
}
串口接收字符串函数
/********************************************************************
*函数名 :uint16_t USART_ReceiveStr(USART_TypeDef* USARTx)
*功能描述 :串口接收字符串函数
*入口参数 :USARTx ,哪个串口
*出口参数 :无
*函数返回: 无
*调用提示 :无
*说明: 以收到\r\n作为结束。 即发送过来的数据必须以\r\n作为结尾。
*********************************************************************/
void USART_ReceiveStr(USART_TypeDef* USARTx)
{
u8 recLen = 0;
u8 data;
while(1)
{
data = USART_ReceiveByte(USARTx);
if(data == '\r' || data == '\n'){//收到了结束标志
break;
}
recDataBuf[recLen] = data; //收到的数据不是结束标志,就把它存到数组中
recLen++;
}
recDataBuf[recLen] = '\0';//添加结束符recDataBuf[recLen] =0;
USART_SendStr(USARTx,recDataBuf); //返回收到的数据
}
4.使用printf函数打印数据
1)包含头文件:stdio.h
2)重写fputc函数
//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
3)包含微库
总结:
串口在我们项目中是属于比较常用的,掌握程度也不难,多实践多使用对调试很有帮助,串口的拓展就是收发数据包,或者队列接收,IAP升级等等,协议还有485-modbus,接下来的博客可能会写,大家记得关注。大家如果对我的博客有疑问或者错误,可以@我修改,大家相互交流。
交流群:717237739
如果觉得有用点赞关注收藏三连,多谢支持
本博客内容原创,创作不易,转载请注明
点赞收藏关注博主,不定期分享单片机知识,互相学习交流。