17.1关于 RS232

RS232是美国电子工业协会(Electronic Industries Association,EIA)于1962年发布的串行通信接口标准,其中RS为英文“Recomend Standard”的缩写,中文翻译为“推荐标准”,232为标识号。该标准对串行通信的物理接口及逻辑电平都做了规定,其输出的电平称为RS232电平。

早期常见于个人计算机上,是计算机通讯接口之一。现如今在个人计算机上已比较少见,但在一些工控设备依旧比较多。

最简单的RS232通信由三条数据线组成,即TXD、RXD和GND。RS232采用负逻辑电平,即-15V ~ -3V代表逻辑“1”,+3V ~ +15V代表逻辑“0”。这里的电平,是TxD线(或者RxD线)相对于GND的电压。

RS232最长传输距离为15米,通常采用DB9接口,有公母之分,该接口如图 17.1.1 所示。
在这里插入图片描述
DB9有9个针脚,通常RS232只需要TXD、RXD和GND即可,引脚定义如图 17.1.1 所示。
在这里插入图片描述

17.2硬件设计

STM32F103系列有5个串口(3个USART,2个UART),在本开发板中,UART4/5没有复用为串口,而是另作它用,因此只有USART1、2、3,共3个串口能使用。

在本开发板上,需要用到串口的有:调试串口、RS232公、RS232母、RS485、对外串口模块接口,总计五处。因此,设计了一个模拟开关电路,如图 17.2.1 所示,通过拨码开关(J11,图 3.3.1 中编号11)实现切换。该电路中,J11为两位拨码开关,U9~U12为二选一模拟开关。以U9为例,通过控制6脚“Select”高低电平,使U9的4脚“A”分别接通3脚“B0”或1脚“B1”,从而实现USAR2_TX分别用于L_USAR2_TX(RS232公头的TXD)和H_L_USART2_TX(RS485的DI)。

图 17.2.1 原理图上的表格,标注了设置方法和对应串口的用途。如果J11的1脚拨为ON,USART2用作RS485,反之为RS232公头;如果J11的2脚拨为ON,USART2用作开发板模块扩展接口(J23),反之为RS232母头。

在这里插入图片描述
因此,如果要使用RS232的公头和母头,需要将开发板蓝色拨码(J11)的两位,都设置为OFF,即往下拨。

图 17.2.2 为RS232电路,U15为UART转RS232电平芯片,自动将UART信号转为RS232信号,通过J13(DB9公头)、J14(DB9公头)引出。
在这里插入图片描述
为了方便实验,开发板设计了一公一母RS232,需要使用RS232公对母线将其连接。根据内部线的连接情况,RS232公对母线又分两种,如图 17.2.3 所示,直通线通常用于延长,交叉线通常用于连接。对于串口,需要公头的TXD发送数据,母头的RXD接收数据,因此需要交叉线,本开发配件也是交叉线。
在这里插入图片描述

17.3软件设计

17.3.1 软件设计思路

实验目的:本实验通过调试串口,控制RS232互传数据,让读者了解如何使用RS232。

  1. 初始化USART1、2、3:设置波特率,收发选择,有效数据位等;
  2. 将所使用的串口引脚初始化:USART使能、GPIO端口时钟使能、GPIO引脚设置为USART复用;
  3. RS232采用中断方式发送,编写中断回调函数;
  4. 主函数编写控制逻辑:使用MobaXterm向调试串口发送“A/a”,公头发送一次数据给母头,成功则绿灯亮,失败则红灯亮;向调试串口发送“B/b”,母头发送一次数据给公头,成功则蓝灯亮,失败则红灯亮;向调试串口发送“C/c”,自动间隔一秒,公母互发一次数据,成功则绿/蓝灯闪烁,失败则红灯亮;向调试串口发送“C/c”,停止自动发送,红灯亮;

本实验配套代码位于“5_程序源码\9_通信—RS232\”。

17.3.2软件设计讲解

  1. GPIO 引脚选择与串口选择
    本实验会用到三个串口,USART1用于调试、USART2用于RS232公头、USART3用于RS232母头,在代码框架上,将每个串口都单独放在“.c”文件里,方便修改裁剪,这里头文件也对应三个。

代码段 17.3.1 调试串口 USART1 相关宏定义(driver_usart1.h)

/*********************
* 引脚宏定义
**********************/
#define DEBUG_USART USART1
#define DEBUG_USART_RX_PIN GPIO_PIN_10
#define DEBUG_USART_TX_PIN GPIO_PIN_9
#define DEBUG_USART_PORT GPIOA
#define DEBUG_USART_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
#define DEBUG_USART_CLK_EN() __HAL_RCC_USART1_CLK_ENABLE()
#define DEBUG_USART_CLK_DIS() __HAL_RCC_USART1_CLK_DISABLE()
#define DEBUG_USART_IRQn USART1_IRQn

代码段 17.3.2 RS232 公头 USART2 相关宏定义(driver_usart2.h)

/*********************
* 引脚宏定义
**********************/
#define RS232_MALE USART2
#define RS232_MALE_RX_PIN GPIO_PIN_3
#define RS232_MALE_TX_PIN GPIO_PIN_2
#define RS232_MALE_PORT GPIOA
#define RS232_MALE_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
/*********************
* 函数宏定义
**********************/
#define RS232_MALE_IRQn USART2_IRQn
#define RS232_MALE_IRQHandler USART2_IRQHandler
#define RS232_MALE_CLK_ENABLE() __HAL_RCC_USART2_CLK_ENABLE()
#define RS232_MALE_CLK_DISABLE() __HAL_RCC_USART2_CLK_DISABLE()
/*********************
* 全局变量声明
**********************/
extern UART_HandleTypeDef husart2;
extern volatile int8_t male_tx_finish;
extern volatile int8_t male_rx_finish;

代码段 17.3.3 RS232 母头 USART3 相关宏定义(driver_usart3.h)

/*********************
* 引脚宏定义
**********************/
#define RS232_FEMALE USART3
#define RS232_FEMALE_RX_PIN GPIO_PIN_11
#define RS232_FEMALE_TX_PIN GPIO_PIN_10
#define RS232_FEMALE_PORT GPIOB
#define RS232_FEMALE_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
/*********************
* 函数宏定义
**********************/
#define RS232_FEMALE_IRQn USART3_IRQn
#define RS232_FEMALE_IRQHandler USART3_IRQHandler
#define RS232_FEMALE_CLK_ENABLE() __HAL_RCC_USART3_CLK_ENABLE()
#define RS232_FEMALE_CLK_DISABLE() __HAL_RCC_USART3_CLK_DISABLE()
/*********************
* 全局变量声明
**********************/
extern UART_HandleTypeDef husart3;
extern volatile int8_t female_tx_finish;
extern volatile int8_t female_rx_finish;

分别定义了三个串口、对应GPIO、时钟使能,方便代码复用。

  1. 初始化USART
    USART初始化包含两部分:协议部分和硬件部分。
    协议部分放在各自“.c”文件里,硬件部分都是调用“HAL_UART_Init()”,单独创建一个“.c”文件处理。
/*
* 定义全局变量
*/
UART_HandleTypeDef husart1;
/*
* 函数名:void DEBUG_USART_Init(uint32_t baudrate)
* 输入参数:baudrate-串口波特率
* 输出参数:无
* 返回值:无
* 函数作用:初始化 USART 的波特率,收发选择,有效数据位等
*/
void DEBUG_USART_Init(uint32_t baudrate)
{
husart1.Instance = DEBUG_USART; // 选择 USART1
husart1.Init.BaudRate = baudrate; // 配置波特率
husart1.Init.WordLength = USART_WORDLENGTH_8B; // 配置数据有效位为 8bit
husart1.Init.StopBits = USART_STOPBITS_1; // 配置一位停止位
husart1.Init.Parity = USART_PARITY_NONE; // 不设校验位
husart1.Init.Mode = USART_MODE_TX_RX; // 可收可发
husart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
// 使用库函数初始化 USART3 的参数
if (HAL_UART_Init(&husart1) != HAL_OK)
{
Error_Handler(); } }

调试串口协议的初始化解析参考上一章介绍,不再赘述。
代码段 17.3.5 USART2 初始化(driver_usart2.c)

/*
* 定义全局变量
*/
UART_HandleTypeDef husart2;
volatile int8_t male_tx_finish = 0;
volatile int8_t male_rx_finish = 0;
/*
* 函数名:void RS232_MALE_Init(uint32_t baudrate)
* 输入参数:baudrate-串口波特率
* 输出参数:无
* 返回值:无
* * 函数作用:初始化 USART 的波特率,收发选择,有效数据位等
*/
void RS232_MALE_Init(uint32_t baudrate)
{
husart2.Instance = RS232_MALE; // 选择 USART2
husart2.Init.BaudRate = baudrate; // 配置波特率
husart2.Init.WordLength = USART_WORDLENGTH_8B; // 配置数据有效位为 8bit
husart2.Init.StopBits = USART_STOPBITS_1; // 配置一位停止位
husart2.Init.Parity = USART_PARITY_NONE; // 不设校验位
husart2.Init.Mode = USART_MODE_TX_RX; // 可收可发
husart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
// 使用库函数初始化 USART3 的参数
if (HAL_UART_Init(&husart2) != HAL_OK)
{
Error_Handler(); } }

代码段 17.3.6 USART3 初始化(driver_usart3.c)

/*
* 定义全局变量
*/
UART_HandleTypeDef husart3;
volatile int8_t female_tx_finish = 0;
volatile int8_t female_rx_finish = 0;
/*
* 函数名:void RS232_FEMALE_Init(uint32_t baudrate)
* 输入参数:baudrate-串口波特率
* 输出参数:无
* 返回值:无
* 函数作用:初始化 USART 的波特率,收发选择,有效数据位等
*/
void RS232_FEMALE_Init(uint32_t baudrate)
{
husart3.Instance = RS232_FEMALE; // 选择 USART3
husart3.Init.BaudRate = baudrate; // 配置波特率
husart3.Init.WordLength = USART_WORDLENGTH_8B; // 配置数据有效位为 8bit
husart3.Init.StopBits = USART_STOPBITS_1; // 配置一位停止位
husart3.Init.Parity = USART_PARITY_NONE; // 不设校验位
husart3.Init.Mode = USART_MODE_TX_RX; // 可收可发
husart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
// 使用库函数初始化 USART3 的参数
if (HAL_UART_Init(&husart3) != HAL_OK)
{
Error_Handler(); } }

RS232的本质还是串口,串口的初始化和之前的基本一样。RS232通常遵循“96-N-8-1”格式,96指波特率9600,N指无校验,8指8bits数据位,1指1bit停止位。

串口协议初始化完后,都调用“HAL_UART_Init()”进行设置,在“HAL_UART_Init()”调用
“HAL_UART_MspInit()”初始化串口硬件部分。

代码段 17.3.7 USART MSP 初始化(driver_msp_usart.c)

/*
* 函数名:void HAL_USART_MspInit(USART_HandleTypeDef* husart)
* 输入参数:husart-USART 句柄
* 输出参数:无
* 返回值:无
* 函数作用:使能 USART1\2\3 的时钟,使能引脚时钟,并配置引脚的复用功能
*/
void HAL_UART_MspInit(UART_HandleTypeDef* husart)
{
// 定义 GPIO 结构体对象
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(husart->Instance==DEBUG_USART) {
// 使能 USART1 的时钟
DEBUG_USART_CLK_EN();
// 使能 USART1 的输入输出引脚的时钟
DEBUG_USART_GPIO_CLK_EN();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = DEBUG_USART_TX_PIN; // 选择 USART1 的 TX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 配置为复用推挽功能
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚翻转速率快
HAL_GPIO_Init(DEBUG_USART_PORT, &GPIO_InitStruct); // 初始化 TX 引脚
GPIO_InitStruct.Pin = DEBUG_USART_RX_PIN; // 选择 RX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT; // 配置为输入
HAL_GPIO_Init(DEBUG_USART_PORT, &GPIO_InitStruct); // 初始化 RX 引脚
HAL_NVIC_SetPriority(DEBUG_USART_IRQn, 1, 0); // 设置 USART3 的中断等级(0-15)(0-15)
// 规则:(0,0)最高,(0,1)次之依次由高到低排序到(15,15)
HAL_NVIC_EnableIRQ(DEBUG_USART_IRQn); // 使能 USART1 的中断
}
else if(husart->Instance==RS232_MALE) {
// 使能 USART2 的时钟
RS232_MALE_CLK_ENABLE();
// 使能 USART2 的输入输出和方向引脚的时钟
RS232_MALE_GPIO_CLK_EN();
/**USART3 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = RS232_MALE_TX_PIN; // 选择 USART2 的 TX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 配置为复用推挽功能
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚翻转速率快
HAL_GPIO_Init(RS232_MALE_PORT, &GPIO_InitStruct); // 初始化 TX 引脚
GPIO_InitStruct.Pin = RS232_MALE_RX_PIN; // 选择 RX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 配置为输入
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉
HAL_GPIO_Init(RS232_MALE_PORT, &GPIO_InitStruct); // 初始化 RX 引脚
HAL_NVIC_SetPriority(RS232_MALE_IRQn, 1, 0); // 设置 USART3 的中断等级(0-15)(0-15)
// 规则:(0,0)最高,(0,1)次之依次由高到低排序到(15,15)
HAL_NVIC_EnableIRQ(RS232_MALE_IRQn); // 使能 USART2 的中断
}
else if(husart->Instance==RS232_FEMALE) {
// 使能 USART3 的时钟
RS232_FEMALE_CLK_ENABLE();
// 使能 USART3 的输入输出引脚的时钟
RS232_FEMALE_GPIO_CLK_EN();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
GPIO_InitStruct.Pin = RS232_FEMALE_TX_PIN; // 选择 USART3 的 TX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 配置为复用推挽功能
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚翻转速率快
HAL_GPIO_Init(RS232_FEMALE_PORT, &GPIO_InitStruct); // 初始化 TX 引脚
GPIO_InitStruct.Pin = RS232_FEMALE_RX_PIN; // 选择 RX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 配置为输入
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉
HAL_GPIO_Init(RS232_FEMALE_PORT, &GPIO_InitStruct); // 初始化 RX 引脚
HAL_NVIC_SetPriority(RS232_FEMALE_IRQn, 1, 0); // 设置 USART3 的中断等级(0-15)(0-15)
// 规则:(0,0)最高,(0,1)次之依次由高到低排序到(15,15)
HAL_NVIC_EnableIRQ(RS232_FEMALE_IRQn); // 使能 USART3 的中断
} }
  • 12~36行:设置USART1的时钟、引脚、中断;
  • 37~61行:设置USART2的时钟、引脚、中断;
  • 62~86行:设置USART3的时钟、引脚、中断;

初始化完成后,便可使用HAL库提供的发送/接收函数,收发数据了,HAL提供三种收发函数:

  • HAL_UART_Receive()/HAL_UART_Transmit():串口收发数据,使用超时管理模式;
  • HAL_UART_Receive_IT()/HAL_UART_Transmit_IT():串口收发数据,使用中断模式;
  • HAL_UART_Transmit_DMA()/HAL_UART_Transmit_DMA():串口收发数据,使用DMA模式;

这里三种收发函数都可满足需求,这里调试串口使用的超时管理模式,重定向打印函数,两个RS232使用中断模式收发数据。

代码段 17.3.8 重定向打印函数(driver_usart1.c)

/*****************************************************
*function: 写字符文件函数
*param1: 输出的字符
*param2: 文件指针
*return: 输出字符的 ASCII 码
******************************************************/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&husart1, (uint8_t*)&ch, 1, 10);
return ch;
}
/*****************************************************
*function: 读字符文件函数
*param1: 文件指针
*return: 读取字符的 ASCII 码
******************************************************/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&husart1, (uint8_t*)&ch, 1, 10);
return (int)ch;
}

代码段 17.3.9 RS232 公头中断收发函数(driver_usart2.c)

/*
* 函数名:void RS232_MALE_Tx(uint8_t *pdata, uint16_t sz)
* 输入参数:pdata->指向发送数据所存储的首地址
sz->发送数据个数
* 输出参数:无
* 返回值:无
* 函数作用:USART2 的发送函数
*/
void RS232_MALE_Tx(uint8_t *pdata, uint16_t sz)
{
male_tx_finish = 0;
HAL_UART_Transmit_IT(&husart2, pdata, sz); }
/*
* 函数名:void RS232_MALE_Rx(uint8_t *pdata, uint16_t sz)
* 输入参数:pdata->指向接收数据所存储的首地址
sz->接收数据个数
* 输出参数:无
* 返回值:无
* 函数作用:USART2 的接收函数
*/
void RS232_MALE_Rx(uint8_t *pdata, uint16_t sz)
{
male_rx_finish = 0;
HAL_UART_Receive_IT(&husart2, pdata, sz); }

代码段 17.3.10 RS232 母头中断收发函数(driver_usart3.c)

/*
* 函数名:void RS232_FEMALE_Tx(uint8_t *pdata, uint16_t sz)
* 输入参数:pdata->指向发送数据所存储的首地址
sz->发送数据个数
* 输出参数:无
* 返回值:无
* 函数作用:USART3 的发送函数
*/
void RS232_FEMALE_Tx(uint8_t *pdata, uint16_t sz)
{
female_tx_finish = 0;
HAL_UART_Transmit_IT(&husart3, pdata, sz); }
/*
* 函数名:void RS232_FEMALE_Rx(uint8_t *pdata, uint16_t sz)
* 输入参数:pdata->指向接收数据所存储的首地址
sz->接收数据个数
* 输出参数:无
* 返回值:无
* 函数作用:USART3 的接收函数
*/
void RS232_FEMALE_Rx(uint8_t *pdata, uint16_t sz)
{
female_rx_finish = 0;
HAL_UART_Receive_IT(&husart3, pdata, sz); }

后面调用“ RS232_MALE_Tx() ” / “ RS232_MALE_Rx() ”便可让 RS232 公头收发数据,“RS232_FEMALE_Tx()”/“RS232_FEMALE_Rx()”便可让RS232母头收发数据。

  1. 中断回调函数
    使用“RS232_MALE_Tx()”发送数据,“RS232_FEMALE_Rx()”接收数据,如何才能得知发送完数据,对方收到数据?
    这里先理一下USART中断流程,USART发生中断时,将自动跳到前面代码段 10.2.1 介绍的中断向量表对应位置,比如57行、58行的“USART2_IRQHandler”和“USART3_IRQHandler”。
    这里需要实现这两个中断服务函数内容,都统一调用HAL库提供的“HAL_UART_IRQHandler()”处理,这里“USART2_IRQHandler”在头文件定义的别名为“RS232_MALE_IRQHandler”。

代码段 17.3.11 USART2 中断处理函数(driver_usart2.c)

/*
* 函数名:void USART2_IRQHandler(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:USART2 的中断服务函数
*/
void RS232_MALE_IRQHandler(void) {
HAL_UART_IRQHandler(&husart2); }

代码段 17.3.12 USART3 中断处理函数(driver_usart3.c)

/*
* 函数名:void USART3_IRQHandler(void)
* 输入参数:无
* * 输出参数:无
* 返回值:无
* 函数作用:USART3 的中断服务函数
*/
void RS232_FEMALE_IRQHandler(void) {
HAL_UART_IRQHandler(&husart3); }

“ HAL_UART_IRQHandler() ”会根据传入的 句 柄 husart ,清理中断寄存器标志位,最终调用“HAL_UART_RxCpltCallback ()”或“HAL_UART_TxCpltCallback ()”函数,这两个回调函数里编写用户自己的控制逻辑。这里在回调函数里修改自定义标志位,以便后面查询是否接收成功。

代码段 17.3.13 USART 中断回调函数(driver_msp_usart.c)

/*
* 函数名:void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
* 输入参数:husart-USART 句柄
* 输出参数:无
* 返回值:无
* 函数作用:USART 发送中断回调函数
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == RS232_MALE) {
male_tx_finish = 1; }
else if(huart->Instance == RS232_FEMALE) {
female_tx_finish = 1; } }
/*
* 函数名:void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
* 输入参数:husart-USART 句柄
* 输出参数:无
* 返回值:无
* 函数作用:USART 接收中断回调函数
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == RS232_MALE) {
male_rx_finish = 1;
}
else if(huart->Instance == RS232_FEMALE) {
female_rx_finish = 1; } }

即默认“ male_rx_finish”和“female_rx_finish”都为0,一旦收到数据,中断处理函数自动完成数据处理,然后调用自己编写的回调函数,修改“ male_rx_finish”和“female_rx_finish”为1。

  1. 主函数控制逻辑
    因为需要实现间隔1s自动互发,这里还需补充一下如何利用SysTick定时器计时。SysTick定时器默认作为“HAL_Delay()”的基准,也是就每隔1ms,就会进入SysTick中断,利用这一特性,就能实现自动发送数据。
    首先定义一个结构体,包含RS232的收发周期。

代码段 17.3.14 定义 SYSTICK 结构体(main.h)

typedef struct
{
uint32_t timeout; // 超时时间
uint32_t rs232_female_period; // RS232 母头自动发送周期
uint32_t rs232_male_period; // RS232 公头自动发送周期
}SYSTICK;

修改SysTick中断处理函数,在该函数里,对SYSTICK结构体的每个成员减1,也就是每间隔1ms,rs232_female_period减1,rs232_male_period减1。这里要注意SYSTICK结构体成员变量是uint32_t类型的,sizeof()函数计算出来的是字节数,一个uint32_t有4个字节,所以for()循环的判断要将sizeof()计算出来的数据大小左移2(除以4)得到正确的结构体成员个数。

代码段 17.3.15 SysTick 中断处理函数(stm32f1xx_it.c)

/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void) {
uint8_t i = 0;
uint32_t *pSYSTICK = (uint32_t*)&gSYSTICK;
HAL_IncTick();
for(i=0; i<(sizeof(gSYSTICK)>>2); i++) {
if(pSYSTICK[i] != 0) {
pSYSTICK[i]--;
} } }

代码段 17.3.16 主函数控制(main.c)

// 初始化 USART1,设置波特率为 115200 bps
DEBUG_USART_Init(115200);
// 初始化 USART2,设置波特率为 9600 bps
RS232_MALE_Init(9600);
// 初始化 USART3,设置波特率为 9600 bps
RS232_FEMALE_Init(9600);
// 初始化 LED
LedGpioInit();
// 在 windows 下字符串\r\n 表示回车
// 如果工程在编译下面这句中文的时候报错,请在“Option for target”->"C/C++"->"Misc Controls"添加“ --locale=english”
printf("百问科技 www.100ask.net\n\r");
printf("RS232 实验\n\r");
printf("输入指令: \n\r \
'A/a'->RS232 公头发送指令给 RS232 母头,成功绿灯亮,失败红灯亮 \n\r \
‘B/b’->RS232 母头发送指令给 RS232 公头,成功蓝灯亮,失败红灯亮 \n\r \
‘C/c’->自动间隔 1S,RS232 公母自动发送,成功绿/蓝灯亮,失败红灯亮 \n\r \
'E/e'->停止测试\n\r");
while(1) {
scanf("%c", &cmd); // 获取按键输入
if(cmd != 0) {
if(cmd=='A' || cmd=='a') // 输入按键 A/a
{
step = 1;
printf("第%2d 次:RS232 公头---->RS232 母头\n\r", cnt);
cnt++;
RS232_FEMALE_Rx((uint8_t*)&RS232_FEMALE_RxMsg, 1); // 母头等待接收
RS232_MALE_Tx((uint8_t*)&RS232_MALE_TxMsg, 1); // 公头发送数据
}
else if(cmd == 'B' || cmd=='b') // 输入按键 B/a
{
step = 2;
printf("第%2d 次:RS232 公头<----RS232 母头\n\r", cnt);
cnt++;
RS232_MALE_Rx((uint8_t*)&RS232_MALE_RxMsg, 1); // 公头等待接收
RS232_FEMALE_Tx((uint8_t*)&RS232_FEMALE_TxMsg, 1); // 母头发送数据
}
else if(cmd=='C' || cmd=='c') // 输入按键 C/c
{
step = 3;
//因为要循环发送,需要放在主循环里实现
}
else if(cmd=='E' || cmd=='e') // 输入按键 E/e
{
step = 0;
printf("停止测试\n\r"); }
cmd = 0; }
// 自动间隔 1S,RS232 公母自动发送
if (step == 3) {
if(gSYSTICK.rs232_female_period == 0) // 滴答定时器周期到了
{
gSYSTICK.rs232_female_period = 1000; // 重新装填初始值 1000ms
printf("第%2d 次:RS232 公头---->RS232 母头\n\r", cnt);
cnt++;
RS232_FEMALE_Rx((uint8_t*)&RS232_FEMALE_RxMsg, 1);
RS232_MALE_Tx((uint8_t*)&RS232_MALE_TxMsg, 1); }
if(gSYSTICK.rs232_male_period == 0) // 滴答定时器周期到了
{
gSYSTICK.rs232_male_period = 1000; // 重新装填初始值 1000ms
printf("第%2d 次:RS232 公头<----RS232 母头\n\r", cnt);
cnt++;
RS232_MALE_Rx((uint8_t*)&RS232_MALE_RxMsg, 1);
RS232_FEMALE_Tx((uint8_t*)&RS232_FEMALE_TxMsg, 1); } }
// 分别判断 RS232 公母接收结果,点亮对应 LED 灯
if(female_rx_finish && step == 1) // 判断 RS232 公头->RS232 母头是否成功
{
male_rx_finish = 0;
RLED(OFF);
GLED(ON);
BLED(OFF); }
if(male_rx_finish && step == 2) // 判断 RS232 母头->RS232 公头是否成功
{
female_rx_finish = 0;
RLED(OFF);
GLED(OFF);
BLED(ON); }
if(male_rx_finish && female_rx_finish && step == 3) // 判断 RS232 自动互传是否成功
{
RLED(OFF);
GLED(ON);
BLED(ON); }
if ((male_rx_finish == 0 && female_rx_finish == 0) || step == 0) // 失败或者退出测试
{
RLED(ON);
GLED(OFF);
BLED(OFF); } }
  • 1-6行:初始化三个串口,其中调试串口波特率为115200,两个RS232为9600;
  • 13-19行:串口打印使用说明,16-18行结尾的“\”表示接上行,即15-19为一行代码;
  • 23~56行:获取输入的值,进行相应操作:
    • 26~34行:RS232公头发送数据给RS232母头,首先记录当前状态为step=1,打印当前操作,调用
      “RS232_FEMALE_Rx()”接收数据,“RS232_MALE_Tx”发送数据;
    • 35~43 行:RS232 母头发送数据给 RS232 公头,首先记录当前状态为 step=2,打印当前操作,调用
      “RS232_MALE_Rx()”接收数据,“RS232_FEMALE_Tx”发送数据;
    • 44~48 行:RS232 公母间隔指定时间互发,因为要循环发送,需要放在主函数实现,这里只记录当前
      状态为 step=3;
    • 49~54 行:退出测试,step 状态改为 0;
  • 59~82行:间隔指定时间,RS232公母自动发送:
    • 59行:如果状态step为3,才进行自动发送,即前面输入“C/c”;
    • 61行:判断rs232_female_period是否为0,即SysTick中断里将该值减为了0,表示设置的周期到了;
    • 62行:再次装填rs232_female_period初值,1000ms即1s;
    • 65~69行:RS232公头发送数据给RS232母头,打印当前操作,调用“RS232_FEMALE_Rx()”接收数据,“RS232_MALE_Tx”发送数据;
    • 72~80行:RS232母头发送数据给RS232公头;
  • 84~115行:分别判断RS232公母接收结果,点亮对应LED灯:
    • 85行:如果RS232公头发送数据给RS232母头,发送成功,“HAL_UART_RxCpltCallback()”会
      将female_rx_finish设置为1;
    • 86行:当前step为1,只关心female_rx_finish,将male_rx_finish清0防止干扰;
    • 89~91行:只亮绿灯;
    • 94~101行:如果RS232公头发送数据给RS232母头,发送成功,蓝灯亮;
    • 103~108行:如果RS232互传成功,蓝绿灯都亮;
    • 110~115行:失败或者退出,亮红灯;

17.4 实验效果

本实验对应配套资料的“5_程序源码\9_通信—RS232\”。将调试串口连接电脑、RS232公母接口用交叉线连接,如图 17.4.1 所示。注意蓝色的拨码开关,两个拨码都要朝下(OFF)。

在这里插入图片描述
打开工程后,编译,下载。打开MobaXterm,连接好调试串口,按下开发板复位,即可看到串口打印使用说明。

在MobaXterm输入字母A,RS232公头向RS232母头发送数据,正常发送绿灯亮;此时拔掉RS232交叉线,再次输入A,红灯亮。

在MobaXterm输入字母B,RS232母头向RS232公头发送数据,正常发送蓝灯亮;此时拔掉RS232交叉线,再次输入B,红灯亮。

在MobaXterm输入字母C,RS232公母互相发送数据,正常发送蓝绿灯亮;此时拔掉RS232交叉线,红灯亮;最后输入E,退出测试。

测试串口效果如图 17.4.2 所示。
在这里插入图片描述


百问网技术论坛:
http://bbs.100ask.net/

百问网嵌入式视频官网:
https://www.100ask.net/index

百问网开发板:
淘宝:https://100ask.taobao.com/
天猫:https://weidongshan.tmall.com/

技术交流群2(鸿蒙开发/Linux/嵌入式/驱动/资料下载)
QQ群:752871361

单片机-嵌入式Linux交流群:
QQ群:536785813

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐