STM32F407 系列文章 -RS232通讯(六)
文章目录
前言
一般STM32F407芯片都会自带好几路UART串口,但从407芯片端口输出的串口UART信号为TTL信号,这时我们就需要232驱动芯片,也可以说是电平转换芯片,将TTL信号转换为所需要的232信号,一般市场上所卖的板子都带这一功能的,因此要实现串口RS232通讯功能,需准备STM32F407开发板一块。
一、串口(UART)
常见的UART串口有RS232、RS485、RS422、TTL等,它们的区别在于通信协议、电平标准不同,我们今天主要讲解RS232的功能特性及实现方法。
二、RS23-硬件特性
RS232 是由美国电子工业协会(EIA)制定的串行数据通信接口 标准,使用两根线(一根发送,一根接收)进行全双工通信,传输速 率较低,传输距离有限。
MCU输出的是TTL电平,需要做电平转换,这里使用TPT3232E芯片来做232电平转换,该芯片官方推荐的设计原理图如下所示。其提供两路输入和输出,输入连接在 MCU 端,输出连接在 232 总线上。其中引脚ROUT1、TIN1作为一路输入,ROUT2、TIN2作为另一路输入,接在MCU的UART上;引脚TOUT1、RIN1 作为一 路输出,TOUT2、RIN2 作为另一路输出,接在外部232上;。
图中C1推荐值为0.2uF,C2、C3、C4推荐值为0.1uF。
三、RS232-程序实现
知道了RS232的硬件特性后,更好于我们进行程序设计。具体实现方式其实和TTL是一样的,和RS485有区别的是,485有特定的IO引脚来控制其发送和接收工作模式,这点要注意,其它是一样的。具体的本专栏里面有讲到和相应的代码软件。
1.函数rs232_init()
主要完成RS232设置,包括硬件初始化、引脚时钟使能、工作模式设置、参数初始化设置。中断设置等等,该函数被main()函数调用。代码如下(示例):
/* 引脚 和 串口 定义
* 默认是针对USART1的.
* 注意: 通过修改这12个宏定义,可以支持USART1~UART7任意一个串口.
*/
#define USART_TX_GPIO_PORT GPIOA
#define USART_TX_GPIO_PIN GPIO_PIN_9
#define USART_TX_GPIO_AF GPIO_AF7_USART1
#define USART_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* 发送引脚时钟使能 */
#define USART_RX_GPIO_PORT GPIOA
#define USART_RX_GPIO_PIN GPIO_PIN_10
#define USART_RX_GPIO_AF GPIO_AF7_USART1
#define USART_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* 接收引脚时钟使能 */
#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 时钟使能 */
/**
* @brief 串口232初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @note 注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
* 这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
* @retval 无
*/
void rs232_init(uint32_t baudrate)
{
g_rs232_handle.Instance = USART_UX; /* USART1 */
g_rs232_handle.Init.BaudRate = baudrate; /* 波特率 */
g_rs232_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
g_rs232_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
g_rs232_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
g_rs232_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
g_rs232_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
g_rs232_handle.Init.OverSampling = UART_OVERSAMPLING_16; /* 过采样 */
HAL_UART_Init(&g_rs232_handle); /* HAL_UART_Init()会使能UART1 */
}
/**
* @brief UART底层初始化函数
* @param huart: UART句柄类型指针
* @note 此函数会被HAL_UART_Init()调用
* 完成时钟使能,引脚配置,中断配置
* @retval 无
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if(huart->Instance == USART_UX) /* 如果是串口1,进行串口1 MSP初始化 */
{
USART_TX_GPIO_CLK_ENABLE(); /* 发送引脚时钟使能 */
USART_RX_GPIO_CLK_ENABLE(); /* 接收引脚时钟使能 */
USART_UX_CLK_ENABLE(); /* USART1 时钟使能 */
gpio_init_struct.Pin = USART_TX_GPIO_PIN; /* TX引脚 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = USART_TX_GPIO_AF; /* 复用为USART1 */
HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct); /* 初始化发送引脚 */
gpio_init_struct.Pin = USART_RX_GPIO_PIN; /* RX引脚 */
HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct); /* 初始化接收引脚 */
__HAL_UART_DISABLE_IT(huart, UART_IT_TC);
HAL_NVIC_EnableIRQ(USART_UX_IRQn); /* 使能USART1中断通道 */
HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3); /* 抢占优先级3,子优先级3 */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART1接收中断 */
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART总线空闲中断 */
}
}
2.函数USART_UX_IRQHandler()
串口中断函数,主要完成接收外部232总线上的数据,在收到数据时,UART接收中断被触发生效,开始接收数据,直到UART空闲中断被触发,知道数据接收完毕,代码如下(示例):
static struct
{
uint8_t buf[USART_REC_LEN]; /* 帧接收缓冲 */
struct
{
uint16_t len : 15; /* 帧接收长度,sta[14:0] */
uint16_t finsh : 1; /* 帧接收完成标志,sta[15] */
}sta; /* 帧状态信息 */
}g_rs232_rx_buf = {0}; /* UART1接收帧缓冲信息结构体 */
/**
* @brief 串口232中断服务函数
* @param 无
* @retval 无
*/
void USART_UX_IRQHandler(void)
{
uint8_t res;
/* UART接收过载错误中断 */
if (__HAL_UART_GET_FLAG(&g_rs232_handle, UART_FLAG_ORE) != RESET)
{
__HAL_UART_CLEAR_OREFLAG(&g_rs232_handle); /* 清除接收过载错误中断标志*/
(void)g_rs232_handle.Instance->SR; /* 先读SR寄存器再读DR寄存器*/
(void)g_rs232_handle.Instance->DR;
}
/* UART接收数据中断 */
if ((__HAL_UART_GET_FLAG(&g_rs232_handle, UART_FLAG_RXNE) != RESET))
{
HAL_UART_Receive(&g_rs232_handle, &res, 1, 1000);
if (g_rs232_rx_buf.sta.len < USART_REC_LEN) /* 缓冲区未满 */
{
g_rs232_rx_buf.buf[g_rs232_rx_buf.sta.len] = res;/* 记录接收到的值 */
g_rs232_rx_buf.sta.len++; /* 接收数据增加1 */
}
else {
g_rs232_rx_buf.sta.len = 0; /* 覆盖之前收到的数据 */
g_rs232_rx_buf.buf[g_rs232_rx_buf.sta.len] = res;/* 将接收到的数据写入缓冲*/
g_rs232_rx_buf.sta.len++;
}
}
/* UART总线空闲中断 */
if (__HAL_UART_GET_FLAG(&g_rs232_handle, UART_FLAG_IDLE) != RESET)
{
g_rs232_rx_buf.sta.finsh = 1; /* 标记帧接收完成 */
__HAL_UART_CLEAR_IDLEFLAG(&g_rs232_handle); /* 清除UART总线空闲中断 */
}
}
3.函数rs232_send_data()
串口发数函数,主要完成串口数据成功传送到外部232总线上,代码如下(示例):
/**
* @brief RS232发送len个字节
* @param buf : 发送区首地址
* @param len : 发送的字节数(为了和本代码的接收匹配,这里建议不要超过 USART_REC_LEN 个字节)
* @retval 无
*/
uint8_t rs232_send_data(uint8_t *buf, uint8_t len)
{
return HAL_UART_Transmit(&g_rs232_handle, buf, len, 1000);
}
4.函数rs232_receive_data()
串口接收函数,主要完成将UART接收中断上接收到的数据传递出去,并返回return 接收到数据长度,代码如下(示例):
/**
* @brief RS232查询接收到的数据
* @param buf : 接收缓冲区首地址
* @param len : 接收到的数据长度
* @arg 0 , 表示没有接收到任何数据
* @arg 其他, 表示接收到的数据长度
* @retval 无
*/
uint8_t rs232_receive_data(uint8_t *buf)
{
uint8_t len = 0;
if(g_rs232_rx_buf.sta.finsh)
{
len = (uint8_t)g_rs232_rx_buf.sta.len;
for (uint8_t i=0; i<len; i++)
buf[i] = g_rs232_rx_buf.buf[i];
g_rs232_rx_buf.sta.len = 0;
g_rs232_rx_buf.sta.finsh = 0;
}
return len;
}
5.函数rs232_receive_data()
串口数据收发处理函数,该功能放在main()函数中被调用。做主机使用时,主要完成向从机发送请求指令,将数据投递到从机节点上;做从机使用时,接收来自主机的指令消息,并进行反馈;本文设置当主机使用,代码如下(示例):
/**
* @brief 232串口数据收发处理函数
* @param 无
* @retval 无
*/
void RS232_Data_Process(void)
{
static uint32_t LastDispatchTime = 0; /* 最近一次的调度时间 */
uint8_t sbuf[5] = {0};
uint8_t rbuf[14] = {0};
uint8_t rlen = 0;
/* 232发送数据处理方式 */
if(g_1msTick - LastDispatchTime > 500) /* 500ms请求一次 */
{
sbuf[0] = 0x68; /* 标识符 */
sbuf[1] = 0x04; /* 数据长度 从数据长度到校验和*/
sbuf[2] = 0x00; /* 地址码 默认00 */
sbuf[3] = 0x04; /* 命令字 */
sbuf[4] = 0x08; /* 校验和 */
uint8_t checksum = CRC_8_CHECKSUM(sbuf, 3);
if (rs232_send_data(sbuf, 5) == HAL_OK) /* 发送倾角传感器数据请求帧 */
LastDispatchTime = g_1msTick;
g_rs232_idle_flag = false;
}
/* 232接收数据处理方式 */
rlen = rs232_receive_data(rbuf);
if(rlen)
{
if(rlen != 14)
{
char *p = "232Len ERR!\r\n";
CircleArray_Push(&g_UiUCycArray, (uint8_t*)p, sizeof("232Len ERR!\r\n"));
return;
}
if(*(uint16_t*)&rbuf[0] != 0x0D68)
{
char *p = "232Head ERR!\r\n";
CircleArray_Push(&g_UiUCycArray, (uint8_t*)p, sizeof("232Head ERR!\r\n"));
return;
}
uint8_t crc = CRC_8_CHECKSUM(&rbuf[1],12);
if(crc != *(uint8_t*)&rbuf[13])
{
char *p = "232CRC ERR!\r\n";
CircleArray_Push(&g_UiUCycArray, (uint8_t*)p, sizeof("232Head ERR!\r\n"));
return;
}
/* 推送给Wife模块 */
Wife_Message msg = {0};
msg.Head = rbuf[0];
msg.SN = g_wife_msg_sn++;
msg.Time = g_1msTick;
memcpy(msg.Data, &rbuf[4], 9);
msg.MesCRC = CRC_8_CHECKSUM((uint8_t*)&msg.Head,WIFE_MSG_LEN-1);
BlockCircleArray_Push(&g_PushWifeArray, (uint8_t*)&msg);
CircleArray_Push(&FileArray, (uint8_t*)&msg, WIFE_MSG_LEN);
/* 数据计算 */
float X_Pos = (rbuf[4]&0x0f)*100 + (rbuf[5] >> 4)*10 + (rbuf[5]&0x0f) +
(rbuf[6]>>4)*0.1 + (rbuf[6]&0x0f)*0.01;
float Y_Pos = (rbuf[7]&0x0f)*100 + (rbuf[8] >> 4)*10 + (rbuf[8]&0x0f) +
(rbuf[9]>>4)*0.1 + (rbuf[9]&0x0f)*0.01;
if(rbuf[4] >> 4)
X_Pos *= -1;
if(rbuf[7] >> 4)
Y_Pos *= -1;
/* 倾角锁定 */
QJ_Lock_Out(Y_Pos);
/* 推送到LCD队列 */
UI_CycMessage m;
m.id = UI_XJD_ID;
sprintf(m.p, "%.2f", X_Pos);
BlockCircleArray_Push(&g_PushLCDArray, (uint8_t*)&m);
m.id = UI_YJD_ID;
sprintf(m.p, "%.2f", Y_Pos);
BlockCircleArray_Push(&g_PushLCDArray, (uint8_t*)&m);
g_rs232_idle_flag = true;
}
}
总结
RS232不管硬件还是软件,其实现的方式还是比较简单的,好入手,更适合初学者。下面提供的代码,基于STM32F407ZGT芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。
相应的代码链接:代码程序
标签:HAL,USART,UART,uint8,STM32,串口,GPIO,rs232,232 From: https://blog.csdn.net/weixin_47006346/article/details/144103207