一.数据通信的基础概念
<1>按数据通信方式分类
- 串行通信:数据逐位按顺序依次传输;
- 并行通信:数据各位通过多条线同时传输。
特点 | 传输速率 | 抗干扰能力 | 通信距离 | IO资源占用 | 成本 |
串行通信 | 较低 | 较强 | 较长 | 较少 | 较低 |
并行通信 | 较高 | 较弱 | 较短 | 较多 | 较高 |
<2>按数据传输方向分类
- 单工通信:数据只能沿一个方向传输;
- 半双工通信:数据可以沿两个方向传输,但需要分时进行;
- 全双工通信:数据可以同时进行双向传输。
<3>按数据同步方向分类
- 同步通信:共用同一时钟信号;
- 异步通信:没有时钟信号,通过在数据信号中加入起始位和停止位等一些同步信号。
<4>波特率
1.比特率
每秒钟传送的比特数,单位bit/s.
2.波特率
每秒钟传送的码元数,单位Baud.
码元:信号经过调制之后并且进行了编码。
3.波特率和波特率的关系
比特率=波特率*log2M,M表示每个码元承载的信息量;
二进系统中,波特率数值上等于比特率。
4.常见的串行通信接口
通信接口 | 接口引脚 | 数据同步方式 | 数据传输方向 |
UART (通用异步收发器) | TXD:发送端 RXD:接收端 | 异步通信 | 全双工 |
1-wire | DQ:发送/接收端 | 异步通信 | 半双工 |
IIC | SCL:同步时钟 SDA:数据输入/输出端 | 同步信号 | 半双工 |
SPI | SCK:同步时钟 MISO:主机输入,从机输出 MOSI:主机输出,从机输入 CS:片选信号 | 同步通信 | 全双工 |
二.串口(RS-232)
<1>串口
1.串口的概念
串行通信接口:指按位发送和接收的接口。如:RS-232/422/485等。
2.RS-232接口(DB9)
数据线:
- TXD(pin3):串口数据输出;
- RXD(pin2):串口数据输入。
握手(同步信号):
- RTS(pin7):请求发送;
- CTS(pin8):清除发送;
- DSR(pin6):数据发送就绪;
- DCD(pin1):数据载波检测;
- DTR(pin4):数据终端就绪。
地线:
GND(pin5):信号地,与两根数据线可组成异步通信。
其他:
RI(pin9):振铃指示。
<2>RS-232电平与CMOS/TTL电平对比
1.RS-232电平
逻辑1:-15V ~ -3V
逻辑0:+3V ~ +15V
2.CMOS电平(3.3V)--- STM32
逻辑1:3.3V
逻辑0:0V
3.TTL电平(5V) --- 51
逻辑1:5V
逻辑0:0V
注意:CMOS/TTL电平不能与RS-232电平直接交换信息。
<3>设备间的RS-232通信示意图
电平转换芯片:MAX3232、SP3232等
注意:两个设备之间的TXD和RXD,必须交叉连接,方可正常通信。
<4>STM32串口与电脑USB口通信示意图
PC还需要安装CH340 USB虚拟串口驱动。
注意:两个设备之间的TXD和RXD,必须交叉连接,方可正常通信。
<5>RS-232异步通信协议
由于是异步通信,所以SCLK线并不使用
- 启动位:必须占1个位长,保持逻辑0电平;
- 有效数据位:可选5、6、7、8、9个位长。LSB(最低有效位)在前,MSB在后;
- 校验位:可选占1个位长,也可以没有该位;
- 停止位:必须有,可选占0.5、1、1.5、2个位长,保持逻辑1电平。
三.STM32的USART
<1>USART简介
Universal synchronous asynchronous receiver transmitter,通用同步异步收发器;
Universal asynchronous receiver transmitter,通用异步收发器。
USART/UART都可以与外部设备进行全双工异步通信;
USART,我们常用的也是异步通信。
<2>USART主要特征
1.全双工异步通信;
2.单线半双工通信;
3.单独的发送器和接收器使能位;
4.可配置使用DMA的多缓冲器通信;
5.多个带标志的中断源。
<3>USART框图
<4>设置USART/UART波特率(F1)
1.计算公式
波特率计算公式:baud = f(ck) / 16 * USARTDIV;
其中f(ck)是串口的时钟,如:USART1的时钟是PCLK2,其他串口都是PCLK1;
USARTDIV = DIV_Mantissa + (DIV_Fraction / 16)。
2.波特比率寄存器(BRR)
把USARTDIV的整数部分写入[15:4],USARTDIV的小数部分写入[3:0]。
fraction加上0.5是进行四舍五入。
<5>USART寄存器简介(F1)
设置好控制和波特率寄存器后,往该寄存器(DR)写入数据即可发送,接收数据则读该寄存器。
四.HAL库外设初始化MSP回调机制
五.HAL库中断回调机制
六.USART/UART异步通信配置步骤
- 配置串口工作参数:HAL_UART_Init()
- 串口底层初始化:HAL_UART_MspInit() --- 配置GPIO、NVIC、CLOCK等
- 开启串口异步接收中断:HAL_UART_Receive_IT()
- 设置优先级、使能中断:HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()
- 编写中断服务函数:USARTx_IRQHandler()、UARTx_IRQHandler()
- 串口数据发送:USART_DR、HAL_UART_Transmit()
HAL库相关函数:
七.IO引脚的复用功能
<1>复用的概念
- 通用:IO端口的输入或输出是由GPIO外设控制;
- 复用:IO端口的输入或输出是由其它非GPIO外设控制。
<2>F1的IO引脚复用
- 可查数据手册引脚定义来看各IO支持什么复用功能;
- 同一时间IO只能用作一种复用功能,否则会发生冲突;
- 遇到IO复用功能冲突,可考虑重映射功能。
<3>F4/F7/H7的IO引脚复用
为了解决F1系列存在的IO复用功能冲突问题,F4往后的系列都加入了复用器。
复用器特点:
- 每个IO引脚都有一个复用器;
- 复用器采用16路复用功能输入(AF0到AF15);
- 复用器一次仅允许一个外设的复用功能(AF)连接到IO引脚;
- 通过GPIOx_AFRL和GPIOx_AFRH寄存器进行配置。
注意:复位完成后,所有IO都会连接到系统的复用功能0(AF0)。
八.通过串口接收或者发送一个字符
uart.c
#include "uart.h"
/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((USART_UX->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART_UX->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
/******************************************************************************************/
uint8_t g_rx_buffer[1];/*HAL库使用到的串口接受数据缓冲区*/
uint8_t g_usart1_rx_flag = 0;/*串口接收到数据标志*/
UART_HandleTypeDef g_uart1_hadle;/*UART句柄*/
/*uart初始化*/
void usart_init(uint32_t baudrate)
{
g_uart1_hadle.Instance = USART1;
g_uart1_hadle.Init.BaudRate = baudrate;
g_uart1_hadle.Init.WordLength = UART_WORDLENGTH_8B;
g_uart1_hadle.Init.StopBits = UART_STOPBITS_1;
g_uart1_hadle.Init.Parity = UART_PARITY_NONE;
g_uart1_hadle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
g_uart1_hadle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&g_uart1_hadle);
HAL_UART_Receive_IT(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1);
}
/*串口MSP回调函数*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if(huart->Instance == USART1) /*如果是串口1,进行串口1 MSP初始化*/
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_9;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_10;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA,&gpio_init_struct);
HAL_NVIC_SetPriority(USART1_IRQn,3,3);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/*(1)使能USART1和对应IO时钟,(2)初始化IO,(3)使能USART1中断,设置优先级*/
}
}
/*串口1中断服务函数*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_hadle);
HAL_UART_Receive_IT(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1);
}
/*串口数据接收完成回调函数*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
g_usart1_rx_flag = 1;/*如果用到多个串口,要先判断串口寄存器基地址*/
}
uart.h
#ifndef _UART_H
#define _UART_H
#include "./SYSTEM/sys/sys.h"
#include "stdio.h"
/******************************************************************************************/
/* 引脚 和 串口 定义
* 默认是针对USART1的.
* 注意: 通过修改这几个宏定义,可以支持USART1~UART5任意一个串口.
*/
#define USART_TX_GPIO_PORT GPIOA
#define USART_TX_GPIO_PIN GPIO_PIN_9
#define USART_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define USART_RX_GPIO_PORT GPIOA
#define USART_RX_GPIO_PIN GPIO_PIN_10
#define USART_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define USART_UX USART1
#define USART_UX_IRQn USART1_IRQn
#define USART_UX_IRQHandler USART1_IRQHandler
#define USART_UX_CLK_ENABLE() do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0) /* USART1 时钟使能 */
/******************************************************************************************/
extern uint8_t g_rx_buffer[1];/*HAL库使用到的串口接受数据缓冲区*/
extern uint8_t g_usart1_rx_flag;/*串口接收到数据标志*/
extern UART_HandleTypeDef g_uart1_hadle;/*UART句柄*/
void usart_init(uint32_t bound); /* 串口初始化函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "uart.h"
#include "./SYSTEM/delay/delay.h"
void led_init(void); /* LED初始化函数声明 */
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 波特率设置为115200 */
printf("请输入一个字符:\r\n\r\n");
while(1)
{
if(g_usart1_rx_flag == 1)
{
printf("您输入的字符为:\r\n");
HAL_UART_Transmit(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1,1000);
while(__HAL_UART_GET_FLAG(&g_uart1_hadle,UART_FLAG_TC) != 1)
printf("\r\n");
g_usart1_rx_flag = 0;
}else
{
delay_ms(10);
}
}
}
九.通过串口接收或者发送一个字符串
uart.c
#include "uart.h"
/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((USART_UX->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART_UX->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
/******************************************************************************************/
/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t g_usart_rx_buf[USART_REC_LEN];
/* 接收状态
* bit15, 接收完成标志
* bit14, 接收到0x0d
* bit13~0, 接收到的有效字节数目
*/
uint16_t g_usart_rx_sta = 0;
uint8_t g_rx_buffer[RXBUFFERSIZE]; /* HAL库使用的串口接收缓冲 */
UART_HandleTypeDef g_uart1_hadle;/*UART句柄*/
/*uart初始化*/
void usart_init(uint32_t baudrate)
{
g_uart1_hadle.Instance = USART1;
g_uart1_hadle.Init.BaudRate = baudrate;
g_uart1_hadle.Init.WordLength = UART_WORDLENGTH_8B;
g_uart1_hadle.Init.StopBits = UART_STOPBITS_1;
g_uart1_hadle.Init.Parity = UART_PARITY_NONE;
g_uart1_hadle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
g_uart1_hadle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&g_uart1_hadle);
HAL_UART_Receive_IT(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1);
}
/*串口MSP回调函数*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if(huart->Instance == USART1) /*如果是串口1,进行串口1 MSP初始化*/
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_9;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_10;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA,&gpio_init_struct);
HAL_NVIC_SetPriority(USART1_IRQn,3,3);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/*(1)使能USART1和对应IO时钟,(2)初始化IO,(3)使能USART1中断,设置优先级*/
}
}
/**
* @brief 串口1中断服务函数
* @param 无
* @retval 无
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_hadle); /* 调用HAL库中断处理公用函数 */
}
/**
* @brief 串口数据接收回调函数
数据处理在这里进行
* @param huart:串口句柄
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART_UX) /* 如果是串口1 */
{
if ((g_usart_rx_sta & 0x8000) == 0) /* 接收未完成 */
{
if (g_usart_rx_sta & 0x4000) /* 接收到了0x0d(即回车键) */
{
if (g_rx_buffer[0] != 0x0a) /* 接收到的不是0x0a(即不是换行键) */
{
g_usart_rx_sta = 0; /* 接收错误,重新开始 */
}
else /* 接收到的是0x0a(即换行键) */
{
g_usart_rx_sta |= 0x8000; /* 接收完成了 */
}
}
else /* 还没收到0X0d(即回车键) */
{
if (g_rx_buffer[0] == 0x0d)
g_usart_rx_sta |= 0x4000;
else
{
g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0];
g_usart_rx_sta++;
if (g_usart_rx_sta > (USART_REC_LEN - 1))
{
g_usart_rx_sta = 0; /* 接收数据错误,重新开始接收 */
}
}
}
}
HAL_UART_Receive_IT(&g_uart1_hadle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}
}
uart.h
#ifndef _UART_H
#define _UART_H
#include "./SYSTEM/sys/sys.h"
#include "stdio.h"
/******************************************************************************************/
/* 引脚 和 串口 定义
* 默认是针对USART1的.
* 注意: 通过修改这几个宏定义,可以支持USART1~UART5任意一个串口.
*/
#define USART_TX_GPIO_PORT GPIOA
#define USART_TX_GPIO_PIN GPIO_PIN_9
#define USART_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define USART_RX_GPIO_PORT GPIOA
#define USART_RX_GPIO_PIN GPIO_PIN_10
#define USART_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define USART_UX USART1
#define USART_UX_IRQn USART1_IRQn
#define USART_UX_IRQHandler USART1_IRQHandler
#define USART_UX_CLK_ENABLE() do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0) /* USART1 时钟使能 */
/******************************************************************************************/
#define USART_REC_LEN 200 /* 定义最大接收字节数 200 */
#define USART_EN_RX 1 /* 使能(1)/禁止(0)串口1接收 */
#define RXBUFFERSIZE 1 /* 缓存大小 */
extern UART_HandleTypeDef g_uart1_hadle; /* HAL UART句柄 */
extern uint8_t g_usart_rx_buf[USART_REC_LEN]; /* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t g_usart_rx_sta; /* 接收状态标记 */
extern uint8_t g_rx_buffer[RXBUFFERSIZE]; /* HAL库USART接收Buffer */
void usart_init(uint32_t bound); /* 串口初始化函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "uart.h"
#include "./SYSTEM/delay/delay.h"
void led_init(void); /* LED初始化函数声明 */
int main(void)
{
uint8_t len;
uint16_t times = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 波特率设置为115200 */
printf("请输入一个字符:\r\n\r\n");
while(1)
{
if (g_usart_rx_sta & 0x8000) /* 接收到了数据? */
{
len = g_usart_rx_sta & 0x3fff; /* 得到此次接收到的数据长度 */
printf("\r\n您发送的消息为:\r\n");
HAL_UART_Transmit(&g_uart1_hadle,(uint8_t*)g_usart_rx_buf, len, 1000); /* 发送接收到的数据 */
while(__HAL_UART_GET_FLAG(&g_uart1_hadle,UART_FLAG_TC) != SET); /* 等待发送结束 */
printf("\r\n\r\n"); /* 插入换行 */
g_usart_rx_sta = 0;
}
else
{
times++;
if (times % 5000 == 0)
{
printf("\r\n 串口实验 r\n");
}
if (times % 200 == 0) printf("请输入数据,以回车键结束\r\n");
delay_ms(10);
}
}
}