一、通信的基本概念
- 按照数据传输的方式分串行通信和并行通信。
- 按照通信的数据同步方式分异步通信和同步通信。
- 按照数据的传输方向分单工通信、半双工通信和全双工通信。
串行通信
使用一条数据线,数据在这条数据线上一位一位的依次进行传输。其速度慢,但使用的传输线条较少,适用于远距离的数据传送。
并行通信
并行通信是数据字节的各位通过多条数据线同时传送的通信方式,通常有8位,16位,32位,位数取决于数据的多少,例如:传输的数据是8位,用的数据线为8根。其传送速度快,控制简单,抗干扰能力较差,但是占用的传输线条较多,比较使用于近距离通信,传输成本较高。
异步通信
- 通信的发送和接收设备使用各自的时钟控制数据的发送和接收过程,为了使双方的收发协调,要求发送方和接收设备的时钟尽可能保持一致。以字符为单位进行传输的,发送的字符之间的时间间隔可以是任意的,但是每一个字符的各个位是以固定的时间进行传输的。异步通信不要求收发双方时钟的严格一致,传输效率不高。
- 数据通常是8位数据,在8位数据最前面增加了一个起始位,最后一位是校验位,后面增加了一个停止位,有时校验位可以去掉。一帧字符帧最少是10位,8个数据位,一个起始位,一个停止位。如果有校验位就是11位。
同步通信
同步通信要求建立发送方时钟对接收方时钟进行直接控制,可保证发送方和接收方的时钟完全同步。发送频率和接收方的接收频率要同步。由一个统一的时钟控制发送端的发送。其传输成本较高。
单工通信
数据只允许向一个方向进行传送,即数据发送设备只能发送数据,数据接收设备只能接收数据。
半双工通信
- 数据允许向两个方向进行传输,但是需要分时进行。传送数据的过程于接收数据的过程不可以同时进行。即进行通信的两个设备都要同时具备接收数据和传输数据的功能,但是同一时刻只能有一个设备进行传输,另一个进行接收数据。
- RS485属于半双工通信。
全双工通信
- 数据允许向两个方向进行传输,并且发送数据的过程和接收数据的过程可以同时进行,同样的进行通信的两个设备都要同时具备接收数据和传输数据的功能。
- RS232属于全双工通信。
通信速率
- 衡量通信性能的一个非常重要的参数就是通信速率,通常以比特率(Bitrate) 来表示。比特率是每秒钟传输二进制代码的位数,单位是:位/秒(bps)。
- 例:每秒传输240个字符,每个字符格式包含10位,8个有效位,1个起始位,1个停止位,没秒传输的位数:240×10=2400bps
- 波特率表示每秒钟传输多少个码元,通信中常用时间间隔相同的符号来表示一个二进制的数字,这样的符号称为码元。
- 如常见的通信传输中,用0V表示数字0,5V或3.3V表示数字1,1个码元就可以有两种状态0和1,所以1个码元等于一个二进制比特位,因为一个位可以是0,可以是1,两种状态。此时波特率大小就等于比特率。
- 如果在通信传输过程中,有0V,2V,4V,6V,分别表示二进制00,01,10,11,那么码元就等于4种状态,也就是两个二进制比特位,所以码元数是二进制比特率的一半,此时波特率是比特率的二倍。
- 常见通信一个码元就表示两种状态,通常所说的波特率就是比特率。
二、串口通信接口标准
- 串口通信指外设和计算机之间通过数据信号线和地线能够按位进行数据传输的一种通信方式,属于串行通信。
- 串口是一种接口标准,没有规定协议。
- 串口通信常见接口标准:RS232C,RS232,RS485等,常用的是RS232和RS485。RS232是从RS232C进行改进,原理是一样的。
RS232C
- RS232C接口规定了使用了25针的连接器,简称DB25,9针的简称DB9,大多数使用的是DB9。
- 串口通信中,常用引脚是TXD,RXD,SGND。串行通信当中要求收发双方两个设备必须要共地,所以需要使用GND。
- RS232C的逻辑电平,和我们的TTL(单片机系统)逻辑状态正好相反,是不一样的
- 1:-3V ~ -15V
- 0:3V - 15V
- 所以进行232通信时,不能直接将TTL的电平和232的电平直接连接,所以需要一个电平转化芯片(MAX3232),芯片要求3.3V。
- 串口线发送和接收要进行交叉连接,发送连接到接收,而且要保证发送方和接收方进行共地(GND要相连)。
三、通信协议
- RS232 的通信协议比较简单,通常遵循 96-N-8-1 格式。
- 96:通信的波特率,为9600
- N:无校验位
- 8:数据传输位数,为8位,不要和字符帧搞混
- 1:停止位,为1位
四、USART
USART简介
- USART 即通用同步异步收发器,它能够灵活地与外部设备进行全双工数据交换,满足外部设备对工业标准 NRZ 异步串行数据格式的要求。UART 即通用异步 收发器,它是在 USART 基础上裁剪掉了同步通信功能,同步和异步主要看其时钟是否需要对外提供。
- 在使用串口的时候,我们使用的都是异步通信。
- STM32F103ZET6芯片有3个USART和2个UART,都具有串口通信功能。USART支持同步单向通信、半双工单向通信和全双工通信,通常使用全双工通信。
USART结构框图
1号框:功能引脚
- TX:数据发送输出引脚
- RX:数据接收输入引脚
- SCLK:时钟引脚
- SW_RX:数据接收引脚,只用于单线和智能卡模式。SW_RX内部引脚,对外没有这个引脚
- nRTS:请求发送引脚,n代表低电平有效。如果使能RTS流控制,当串口接收器准备号接收新数据的时候,就会将nRTS引脚拉低,变成低电平,当接收寄存器已满时,nRTS引脚被拉高,引脚仅适用于硬件流控制。
- nCTS:清除发送引脚,也是应用于硬件流控制的引脚,n代表低电平有效,如果使能了CTS流控制,发送器在发送下一帧数据之前,会检测CTS引脚,如果检测的是低电平,表示可以发送数据,如果检测到高电平,那么就在发送完当前数据帧之后,停止发送。
- SCLK:发送器时钟输出引脚,如果将USART配置为同步通信,时钟就通过SCLK引脚输出,控制接收设备的时钟。
2号框:数据寄存器功能框图
- 数据寄存器(DR)只有低9位有效,第9位数据是否有效要取决于控制寄存器的M位来进行设置。当M位设置为0,表示数据位是8位字长,当M位设置为1时,表示数据字长是9位
- 使用串口通信的时候,通常都是8位数据字长。
- 发送寄存器:当进行发送操作的时候,它会往TDR寄存器当中写入数据,数据是专门存储到TDR(发送数据寄存器)当中。
- 当我们进行读取数据的时候,会从RDR(接收数据寄存器)当中传输到DR寄存器当中,它是自动完成的。
- TDR和RDR都是介于系统总线和移位寄存器当中的。
- 串行通信是一位一位进行传输的,发送的时候把TDR寄存器的内容转移到发送移位寄存器当中,把移位寄存器的数据通过TX一位一位的发送出去。
- 接收数据的时候,把接收到的每一位数据从RX中接收存储到接收移位寄存器当中,然后再转移到RDR接收数据寄存器当中,如果我们要读取数据,就直接读取数据寄存器DR,他自动会将RDR的数据转移到DR数据寄存器当中,完成读取。
- USART还支持DMA传输,可以实现高速数据传输。
3号框:控制器单元
- UE:用来使能USART的功能的,使用USART要使能这个外设。
- M:控制发送和接收数据字长
- 发送器:发送8位或9位的数据,发送使能位TE置1,发送移位寄存器的数据就会在TX脚进行一位一位的传输。如果是同步通信,相应的SCLK时钟也会有脉冲输出控制接收设备的时钟。
- 接收器:RE控制接收的,要对RE置1,使能USART的接收,使接收器在RX先上搜索接收起始位,然后把数据存放在接受移位寄存器当中,之后把数据自动转移到接收数据寄存器当中,并把状态寄存器的RXNE位自动置1,如果RXNEIE置1的话,就会控制到中断,可以产生中断
4号框:波特率生成模块
波特率计算公式:
$TX/RX=\frac{fck}{16×USARTDIV}$
TX/RX:波特率 fck:时钟频率 USARTDIV:存放在波特率寄存器当中的无符号定点数,它有整数部分和小数部分,库函数开发有相应的配置函数,不需要自己计算。
- 串口1挂接在APB2总线,时钟72M。其他串口挂接在APB1总线,时钟36M。
- 波特率给发送器和接收器提供时钟
五、USART串口通信配置步骤
USART 相关库函数在 stm32f10x_usart.c 和 stm32f10x_usart.h 文件 中。
- 使能串口时钟及 GPIO 端口时钟
- USART1使用的PA9(TXD:发送引脚)和PA10(RXD:接收引脚)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
- USART1使用的PA9(TXD:发送引脚)和PA10(RXD:接收引脚)
- GPIO 端口模式设置,设置串口对应的引脚为复用功能
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // 输出引脚要设置推挽输出模式 GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; // 输入引脚要浮空输入模式,在串口引脚接收数据的时候,完全依靠外部输入 GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA,&GPIO_InitStructure);
- 初始化串口参数,包含波特率、字长、奇偶校验等参数
// 参数1:指定哪个串口;参数2:结构体指针变量 void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct); typedef struct { uint32_t USART_BaudRate; //波特率 // 数据帧字长,可以设置为8位或者9位 // 如果没有使能奇偶校验,一般使用8位,使能奇偶校验,一般设为9位 uint16_t USART_WordLength; //字长 // 停止位:可选0.5,1,1.5,2 uint16_t USART_StopBits; //停止位 // 可选奇校验,偶校验,无校验和其他校验 uint16_t USART_Parity; //校验位 // 模式可选接收模式,发送模式,逻辑或运算选择两种模式(可接收和发送) uint16_t USART_Mode; //USART 模式 uint16_t USART_HardwareFlowControl; //硬件流控制 } USART_InitTypeDef;
- 使能串口
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
- 设置串口中断类型并使能
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalStat e NewState); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断 USART_ITConfig(USART1,USART_IT_TC, ENABLE); // 发送数据完成产生中断
- 设置串口中断优先级,使能串口中断通道,对NVIC的配置
- 编写串口中断服务函数
USART1_IRQHandler // USART接收数据 uint16_t USART_ReceiveData(USART_TypeDef* USARTx); // 发送数据 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
六、硬件设计
自动下载运行电路
- CH340C:将USB和串口进行互转
- USART1 TX(PA9): 串口1,发送引脚
- USART1 RX(PA10):串口1,接收引脚
- 使用P4端子,将USB转串口模块作为单独独立的模块,方便调试
- 只给STM32进行程序下载或串口通信的时候,要将12引脚短接,34引脚短接
- TXD连接到PA10接收引脚,RXD连接到PA9发送引脚,串口通信要进行交叉连接
- USB还可以进行供电,只是供电的话充电宝也可以进行供电,下载程序就要接入PC端了
RS232电路
- SP3232:RS232电平和TTL电平进行转换
- P6端子:35连接,46连接。使用串口2
- 13,14引脚连接在RS232的母头,也就是DB9的母头
- 使用RS232母头的时候,要将P6端子35连接,46连接,进行短接
- 串口2和RS485是共用引脚,所以在使用RS232母头的时候,RS485就不能使用。使用RS485,母头就不能使用,此时要将P6端子24连接,13连接RS485串口就和串口2连接。
- 开发板上还有一个公头,连接在串口3,串口3连接在P10端子,和WiFi、蓝牙接口共用一个串口3,串口3接在PB10和PB11上的,使用串口3,也就是RS232公头的时候,要将P10端子的46,35短接。使用WiFi的话,要将24,13短接。
七、软件设计
目的
本章所要实现的功能是:STM32F1 通过 USART1 实现与 PC 机对话,STM32F1 的 USART1 收到 PC 机发来的数据后原封不动的返回给 PC 机显示。同时使用 DS0指示灯不断闪烁提示系统正常运行。程序框架如下:
- 初始化 USART1,并使能串口接收中断等
- 编写 USART1 中断函数
- 编写主函数
USART.c
#include "USART.h"
// 串口1接收缓存
u8 USART1_RX_BUF[USART1_REC_LEN];
// 接收状态;识别接收的数据长度,以及是否接收到0x0D(回车)和0x0A(换行),接收到这两个数据认为传输完成
// 第15位:接收完成标志;
// 14:接收到0x0D,通过14位是否为1判断是否接收到0x0D,如果后面是0A,就接收完成,将15位置1
// 0-13位:接收的有效数据字节
u16 USART1_RX_STA = 0;
void USART1_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
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);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置波特率
USART_InitStructure.USART_BaudRate = bound;
// 模式配置,配置成全双工模式,可发送可接收
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_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStructure);
// 使能串口
USART_Cmd(USART1, ENABLE);
USART_ClearFlag(USART1, USART_FLAG_TC);
// 设置串口中断类型,参数2:设置接收非空中断,开启接收中断
/*
USART_IT_RXNE是STM32串口通信中的一个中断类型,称为“接收缓冲区非空中断”。
当USART接收到数据并将其存储在接收数据寄存器(DR)中时,该中断会被触发
。具体来说,每当接收到一个字节的数据时,USART_IT_RXNE中断就会被激活。
*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 设置串口中断优先级,使能串口中断通道
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// 利用数组和自定义协议进行收发,中断服务函数的目的是将接收到的数据保存到数组当中,然后在主函数进行发送
void USART1_IRQHandler(void)
{
// 定义变量用来保存类型
u8 r = 0;
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
// 接收数据,一个字节一个字节进行接收
r = USART_ReceiveData(USART1);
// 0x8000:1000 0000 0000 0000,判断是否接收完成
if((USART1_RX_STA & 0x8000) == 0) // 等于0表示没有接收完成
{
// 0x4000:0100 0000 0000 0000,用来判断是否接收0x0D,0A
if(USART1_RX_STA & 0x4000) // 接收到0x0D
{
// 如果0x0D后面不是0x0A,就认为接收错误
if(r != 0x0A) USART1_RX_STA = 0;
// 如果后面接收到的是0x0A,就认为接收完成
else USART1_RX_STA |= 0x8000;
}
else // 没接收到
{
// 如果接收到0x0D,就将14位置1
if(r == 0x0d) USART1_RX_STA |= 0x4000; // 将USART1_RX_STA的14位置1
else
{
// 刚开始USART1_RX_STA是0,& 0x3fff后还是0,会将r的值赋给USART1_RX_BUF[0]
// 随后USART1_RX_STA++,下一个数组就是USART1_RX_BUF[]
USART1_RX_BUF[USART1_RX_STA & 0x3fff] = r;
USART1_RX_STA ++;
// 如果还没有接收到0x0d,就认为接受错误,将USART1_RX_STA清0,等到下一次接收
if(USART1_RX_STA > (USART1_REC_LEN - 1)) USART1_RX_STA = 0;
}
}
}
}
}
USART.h
#ifndef __USART_H
#define __USART_H
#include "system.h"
void USART1_Init(u32 bound);
void USART1_IRQHandler(void);
// 定义宏:接收数据长度,定义宏方便修改数组的大小
#define USART1_REC_LEN 200
// 将数组作为全局变量
extern u8 USART1_RX_BUF[USART1_REC_LEN];
// 声明的时候不需要赋初值
extern u16 USART1_RX_STA;
#endif
main.c
#include "LED.h"
#include "system.h"
#include "SysTick.h"
#include "USART.h"
int main()
{
u8 i = 0;
u16 len = 0;
u16 t = 0;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
USART1_Init(115200);
while(1)
{
// 接收完成USART1_RX_STA最高位是1,USART1_RX_STA & 0x8000为真
if(USART1_RX_STA & 0x8000)
{
len = USART1_RX_STA & 0x3fff;
for(t = 0; t < len; t ++)
{
// 参数1:串口;参数2:数据
USART_SendData(USART1, USART1_RX_BUF[t]);
// 判断是否发送完成,参数1:串口;参数2:状态(选择发送完成)
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET);
}
// 清0,下一次进入到服务中断就又可以接收了
USART1_RX_STA = 0;
}
i ++;
if(i % 20 == 0) LED1 = !LED1;
delay_ms(10);
}
}