一、串口模块简介
(一)通信基本介绍
1、串行通信与并行通信
按数据传送的方式,通讯可分为串行通讯与并行通讯,串行通讯是指设备之间通过少量数据信号线(一般是8根以下),地线以及控制信号线,按数据位形式一位一位地传输数据的通讯方式。而并行通讯一般是指使用8、16、32 及 64根或更多的数据线进行传输的通讯方式,它们的通讯传输对比说明见下图,并行通讯就像多个车道的公路,可以同时传输多个数据位的数据,而串行通讯,而串行通讯就像单个车道的公路,同一时刻只能传输一个数据位的数据。
串并行通信对比图
串行通讯与并行通讯的特性:
特性 | 串行 | 并行 |
---|---|---|
传输距离 | 较远 | 较近 |
抗干扰能力 | 较强 | 较弱 |
传输速率 | 较慢 | 较快 |
传输成本 | 较低 | 较高 |
- 传输距离:要根据实际情况区分,但一般来说串行传输通讯协议距离比并行通讯协议传输距离要远
- 抗干扰能力:串行传输由于数据线少,同时可以使用双绞线,差分传输等减少干扰的传输方式,抗干扰能力更强。并行传输因为数据线多,数据线会产生较多干扰,所以抗干扰能力差
- 传输速率:在传输时钟频率较低的情况下,并行传输的传输速率是高于串行传输的,此时并行传输的数据线数量多,占据着优势。但是传输时钟频率高的情况下,串行传输的传输速率是高于并行传输,因为此时,由于传输时钟频率较高,并行传输会存在多数据通道同步的问题,多根数据线同时收发数据可能会存在不同步,某几根数据线数据已发送,另外几根数据线数据还未发送
- 传输成本:由信号线的数量来决定
2、全双工、半双工及单工通信
根据数据通讯的方向,通讯又分为全双工、半双工及单工通讯,它们主要以信道的方向来区分,它们的区别主要在于:
- 信道内的数据数据是否能够双向流通
- 信道内的数据在某一时刻能否双向流通
单工通信:数据的传输方向是单向的。任何时刻,数据只能从发送方发送给接收方。例如,广播,电视
半双工通信:数据的传输方向是双向的。但是某一时刻,设备之间仅能进行收或者发数据。例如,对讲机
全双工通信:数据的传输方向是双向的。任意时刻,设备之间可以同时收发数据。例如,电话
总结表下表所示:
通信方式 | 说明 |
---|---|
单工 |
在任何时刻都只能进行一个方向的通讯,即一个固定为发送设备,另一个 固定为接收设备
|
半双工 |
两个设备之间可以收发数据,但不能在同一时刻进行
|
全双工 |
在同一时刻,两个设备之间可以同时收发数据
|
3、同步通信和异步通信
根据通讯的数据同步方式,又分为同步和异步两种,可以根据通讯过程中是否有使用到时钟信号进行简单的区分。
在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调,同步数据,见图 20-3。通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。
同步通信:需要有时钟线,使得发送接收双方时钟信号一致,进而进行收发数据的同步。通讯双方统一约定,会在时钟信号的上升沿或下降沿进行数据线的采样。具体如下图
异步通信:不需要时钟线,但需要在发送的数据中,加入特定信号位用于同步数据。具体如下图
同步通讯与异步通讯的区别:
- 同步通讯时,由于时钟同步,通讯双方收发的数据都是连续的比特流。异步通讯时,时钟无需同步,通讯双方收发数据,可以断断续续的发送,发送方任意时长后发送下一个字节,这都是允许的
- 同步通讯的效率更高,异步通讯的效率较低
- 同步通讯硬件较为复杂,因为需要保持时钟信号的同步。异步通信硬件较为简单,因为无需同步时钟信号
- 同步通信可以实现一对多通讯。而异步通信只能实现点对点通讯
(二)串口协议和RS-232标准
1、串口通信协议
串口通信指两个或两个以上的设备使用串口按位(bit)发送和接收字节。可以在使用一根线发送数据的同时用另一根线接收数据。串口通信协议就是串口通讯时共同遵循的协议。协议的内容是每一个bit 所代表的意义。常用的串口通信协议有以下几种:
- RS-232(ANSI/EIA-232标准) 只支持 点对点, 最大距离 50英尺。最大速度为128000bit/s, 距离越远 速度越慢。 支持全双工(发送同时也可接收)。
- RS-422(EIA RS-422-AStandard),支持点对多一条平衡总线上连接最多10个接收器 将传输速率提高到10Mbps,传输距离延长到4000英尺(约1219米),所以在100kbps速率以内,传输距离最大。支持全双工(发送同时也可接收)。
- RS-485(EIA-485标准)是RS-422的改进, 支持多对多(2线连接),从10个增加到32个,可以用超过4000英尺的线进行串行通行。速率最大10Mbps。支持全双工(发送同时也可接收)。2线连接时 是半双工状态。
2、RS - 232标准
RS-232标准接口(又称EIA RS-232)是常用的串行通信接口标准之一,它是由美国电子工业协会(EIA)联合贝尔系统公司、调制解调厂家及计算机终端生产厂家于1970年共同制定,其全名是“数据终端设备( DTE)和数据通信设备(DCE)之间串行二进制数据交换接口技术标准”。(百度百科)
(三)RS232电平与TTL电平的区别
根据通讯使用的电平标准不同,串口通讯可分为 TTL标准和 RS-232标准、RS-485标准
标准名称 | 逻辑1 | 逻辑0 |
---|---|---|
TTL | +3.3V或+5V表示1 | 0V表示0 |
RS-232 | -3V~-15V表示1 | +3V~+15V表示0 |
RS-485 | 两线压差+2~+6V表示1 | -2~-6V表示0(差分信号) |
RS232与TTL电平标准下表示同一个信号
1、TTL电平标准
(1)输出 L: <0.8V ; H:>2.4V。
(2)输入 L: <1.2V ; H:>2.0V
TTL器件输出低电平要小于0.8V,高电平要大于2.4V。输入,低于1.2V就认为是0,高于2.0就认为是1。于是TTL电平的输入低电平的噪声容限就只有(0.8-0)/2=0.4V,高电平的噪声容限为(5-2.4)/2=1.3V。
2、RS232标准
在TXD和RXD数据线上:
(1)逻辑1为-3~-15V的电压
(2)逻辑0为3~15V的电压
在RTS、CTS、DSR、DTR和DCD等控制线上:
(1)信号有效(ON状态)为3~15V的电压
(2)信号无效(OFF状态)为-3~-15V的电压
这是由通信协议RS-232规定的。
RS-232:标准串口,最常用的一种串行通讯接口。有三种类型(A,B和C),它们分别采用不同的电压来表示on和off。最被广泛使用的是RS-232C,它将mark(on)比特的电压定义为-3V到-12V之间,而将space(off)的电压定义到+3V到+12V之间。传送距离最大为约15米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kΩ。所以RS-232适合本地设备之间的通信。
(四)了解"USB/TTL转232"模块(以CH340芯片模块为例)
1、CH340基本工作原理:
CH340 是一个USB 总线的转接芯片,实现USB 转串口、USB 转IrDA 红外或者USB 转打印口。为了增加串口通讯的远距离传输及抗干扰能力,RS-232标准使用-15V 表示逻辑 1, +15V 表示逻辑 0。常常会使用 MH340芯片对 USB/TTL与RS-232电平的信号进行转换。
2、CH340模块介绍
CH340是一个USB总线的转接芯片,可实现USB转串口或者USB转打印口。
3、CH340的特点
● 全速USB设备接口,兼容USBV2.0。
● 仿真标准串口,用于升级原串口外围设备,或者通过USB增加额外串口。
● 计算机端Windows操作系统下的串口应用程序完全兼容,无需修改。
● 硬件全双工串口,内置收发缓冲区,支持通讯波特率50bps~2Mbps。
● 通过外加电平转换器件,提供RS232、RS485、RS422等接口。
● CH340R芯片支持IrDA规范SIR红外线通讯,支持波特率2400bps到115200bps。
● 内置固件,软件兼容CH341,可以直接使用CH341的VCP驱动程序。
● 支持5V电源电压和3.3V电源电压。
二、标准库方式实现LED电灯与闪烁
(一)STM32F103标准库模板
STM32工程文件夹主要包含以下几个文件夹:
- Library: 放置库文件,包含驱动程序和算法等。提供运行需要的驱动程序支持。
- Listings: 放置汇编列表文件(.lst)。这些文件记录了编译过程中产生的汇编代码。
- Objects: 放置目标文件(.o文件),这些文件是编译源代码(.c文件)后的结果,包含机器码等。
- Start: 放置启动文件startup_stm32f10x_xx.s,包含系统时钟配置、堆栈设置、中断向量表等初始化代码。
- User: 用户代码文件夹,放置用户自己编写的源代码(.c文件)。
其中:
- Listings和Objects是编译过程中的中间文件。
- Startup文件负责系统初始化。
- User文件夹放用户应用程序源代码。
(二)文件配置
接下来就是将这些文件配置进入工程
首先将工程文件夹里面的文件全部导入工程,全部的.c和.h文件
接着点击魔术棒,导入文件夹路径
(三)操作STM32的GPIO口
1.使用RCC开启GPIO时钟
在点亮LED的实验中我们采用GPIOA引脚PA0~15,因此需要开启APB2的外设使能时钟,代码如下所示:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启A口的时钟
2.使用GPIO_Init函数初始化GPIO口
(1)定义结构体
结构体的类型为:GPIO_InitTypeDef
该结构体的成员有三个,分别为:GPIO_Pin、GPIO_Speed、GPIO_Mode。
GPIO_Mode为GPIO工作的模式,共八种,如下所示:
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
GPIO_Pin为我们选择的引脚,按照格式选择相应引脚即可,即为GPIO_Pin_0到GPIO_Pin_15,或者全部使用即为GPIO_Pin_All。
GPIO_Speed为输出速率,具体如下:
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
在本次实验中,上述三个结构体成员我们选择如下三种:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;//用的GPIO A的0号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
(2)使用GPIO_Init函数初始化GPIO口
GPIO_Init(GPIOA,&GPIO_InitStructure);
(四)实际应用
1、配置标准库点亮LED
题目要求:使用配置标准库的方式点亮LED。
接线图:
代码:
#include "stm32f10x.h" // Device header
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启A口的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;//用的GPIO A的0号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);//配置低电平,若只需要点亮LED灯就只执行此条命令。
while(1)
{
}
}
开发板效果:
2、配置标准库实现LED流水灯
题目要求:使用配置标准库的方式实现LED流水灯效果
接线图:
代码:
#include "stm32f10x.h" // Device header
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; //GPIO引脚,赋值为所有引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOA的初始化
/*主循环,循环体内的代码会一直循环执行*/
while (1)
{
/*使用GPIO_Write,同时设置GPIOA所有引脚的高低电平,实现LED流水灯*/
GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
Delay_ms(500); //延时100ms
GPIO_Write(GPIOA, ~0x0002); //0000 0000 0000 0010,PA1引脚为低电平,其他引脚均为高电平
Delay_ms(500); //延时100ms
GPIO_Write(GPIOA, ~0x0004); //0000 0000 0000 0100,PA2引脚为低电平,其他引脚均为高电平
Delay_ms(500); //延时100ms
GPIO_Write(GPIOA, ~0x0008); //0000 0000 0000 1000,PA3引脚为低电平,其他引脚均为高电平
Delay_ms(500); //延时100ms
GPIO_Write(GPIOA, ~0x0010); //0000 0000 0001 0000,PA4引脚为低电平,其他引脚均为高电平
Delay_ms(500); //延时100ms
GPIO_Write(GPIOA, ~0x0020); //0000 0000 0010 0000,PA5引脚为低电平,其他引脚均为高电平
Delay_ms(500); //延时100ms
GPIO_Write(GPIOA, ~0x0040); //0000 0000 0100 0000,PA6引脚为低电平,其他引脚均为高电平
Delay_ms(500); //延时100ms
GPIO_Write(GPIOA, ~0x0080); //0000 0000 1000 0000,PA7引脚为低电平,其他引脚均为高电平
Delay_ms(500); //延时100ms
}
}
开发板效果:
LED流水灯
三、串口通信实际应用
(一)串口发送HelloWorld
题目要求:STM32系统给上位机(win10)连续发送“hello windows!”。win10采用“串口助手”工具接收。
接线图:
代码:
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
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); //将PA9引脚初始化为复用推挽输出
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx; //模式,选择为发送模式
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_Init,配置USART1
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
int main(void)
{
/*模块初始化*/
Serial_Init(); //串口初始化
while (1)
{
Serial_SendString("hello world!"); //串口发送字符串
Delay_ms(500);
}
}
效果:
串口发送Hello World
(二)串口控制LED亮灭
题目要求:STM32以查询方式接收上位机(win10)串口发来的数据,如果接收到“Y”则点亮链接到stm32上的一个LED灯;接收到“N”则熄灭LED灯。
代码:
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t RxData; //定义用于接收串口数据的变量
uint8_t Serial_RxData; //定义串口接收的数据变量
uint8_t Serial_RxFlag; //定义串口接收的标志位变量
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
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); //将PA9引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入
//使用GPIOA0引脚连接LED
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);
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 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_Init,配置USART1
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
int main(void)
{
/*串口初始化*/
Serial_Init(); //串口初始化
GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);//将A6口初始化为电平
while (1)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
{
RxData=USART_ReceiveData(USART1);
if(RxData=='Y')
{GPIO_ResetBits(GPIOA,GPIO_Pin_0);}
if(RxData=='N')
{GPIO_SetBits(GPIOA,GPIO_Pin_0);}
}
}
}