一、RS232相关概念
RS ==Recommend Standard ==推荐标准;
232==标识号,第232号;
时间:1962年
地点:美国
人物:美国电子工业协会 == Electronic Industries Association ==(美国)电子工业协会
事件:发布了一个串行通信的物理接口结合逻辑电平的规范文件,就是这个232号文件。
现在关于串口通讯的叫法太多,什么RS232通讯、串口通讯、DB9通讯、UART通讯等等,其实这些称呼都跟这个额232号文件有些关系,随便怎么叫吧,理解就行。
串行通讯,从字面意思理解就行,就是把数据串成一串发出去,比如一个字节8位,高位先发出去,那么发送顺序就是bit7--bit6--bit5--bit4--bit3--bit2--bit1--bit0。
就某种单片机来说,比如STM32F103,它有好几个UART口,俗称串口,但是它的引脚高电平是3.3V,低电平是0V,正好满足TTL的电平标准,2.4V--5V表示逻辑1,0V--0.4V表示逻辑0。
但是电压低了传输距离就比较短,为了传输远点,就把TTL电平转换成RS232电平,通过某种电平转换芯片,比如MAX232。RS232电平逻辑是-3V到-15V表示逻辑1,3V到15V表示逻辑0。这相当于把电平扩大,但是也只能传输十几米。
人们为了传输更远,就用上了差分传输,比如RS422和RS485,用两根线的电压差来表示逻辑1和逻辑0,两根线的压差为2V至6V表示逻辑1,两线的压差为-2V至-6V表示逻辑0,这样就能传输1000米以上。
数据在一根线上传输,那什么时候是开始,什么时候是结束,每位数据的宽度是多少、数据有没有传输错误。那就需要约定一下,不是谁都像孙悟空一样有觉悟,头上敲三下就是让他凌晨3点过来,还是要讲清楚点好。
线顶一个空闲的状态,就是没传输数据的时候,传输线的电平逻辑是1,用单片机TTL的电平标准就是3.3V,高电平;
开始:发出1位逻辑0电平
结束:发出1位逻辑1电平
数据宽度:要定义每一位的宽度是多少,不然你发两位1我却认为是1位1,怎么办,发送和接收的双发要统一度量衡,才不会有误解。这个数据宽度就是用波特率的约定。
数据有没有传错:那就把收到的数据大家数一数,算一算,我给你发100块钱,我还告诉你是100张一块的,那你收到之后,要数一数,是不是100张,是不是100块,都对了,那就表示是我给你的。
传输一个字节数据的示意图
STM32F103单片机USART内部结构图
这个图太大了,对新人实在是不友好,对老人也不友好,还是需要好好的梳理一下,总共就4块
1、串口引脚:发送和接收数据的外部引脚;
2、波特率发生器:产生波特率,就是为了控制数据每个bit的宽度;
3、发送与接收的控制单元:控制数据怎么发送,怎么接收;
4、发送与接收数据寄存器:单片机的发送的数据在内部怎么产生,接收的数据怎么吸收;
串口数据收发:
1、轮询收发
发送数据
1、声明一个UART_HandleTypeDef结构
2、配置波特率、字符长度、停止位、校验位等
3、UART 管脚配置:配置管脚位置和时钟
4、UART初始化
5、开始发送数据
用STM32CubeMX生成代码,前面4步都自动生成好了,只需要在第五步调用发送函数就可以
以下是自动生成的配置代码
#include "usart.h"
//声明一个UART句柄结构体,应为使用串口1 ,结构体命名为huart1
UART_HandleTypeDef huart1;
//配置串口波特率、数据长度、停止位、校验位等
//调用HAL_UART_Init函数,再调用HAL_UART_MspInit函数,
//完成UART管脚和时钟的配置,以及串口初始化
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
//配置UART管脚位置和时钟
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
//串口1使用GPIOA的,PIN9和PIN10
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 串口中断配置,轮询模式可以不用配置中断*/
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
//相当于还原,还原到配置之前的状态
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
__HAL_RCC_USART1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
HAL_NVIC_DisableIRQ(USART1_IRQn);
}
}
在main文件中去完成串口数据发送
串口发送使用函数
//huart:用哪个串口发送
//pData:被发送的数据指针
//Size:发送的数据个数
//Timeout:超时时间
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
串口接收使用函数
//huart:用哪个串口接收
//pData:接收的数据存放位置
//Size:接收的数据个数
//Timeout:超时时间
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
收发实验放在一起比较好,本实验是单片机与PC之间进行数据传输:
PC向单片机发送5个数据 0xAA、 0x11 、0x22 、0x33 、0xBB
单片机每次接收5个数据,并判断第一个数据是不是AA,最后一个数据是不是BB;
如果判断正确,就把事先准备好的6个数据发出去;
还要把接收到的数据清空,等待下一次接收新数据。
#include "main.h"
#include "usart.h"
#include "gpio.h"
//将要发送的数据
uint8_t Tx_Buffer[6] = {0x5A, 0x01,0x02,0x03,0x04,0xA5};
//接收的数据存放位置
uint8_t Rx_Buffer[5] = {0};
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
while (1)
{
//开始接收数据
if(HAL_UART_Receive(&huart1, Rx_Buffer, 5, 10))
{
//如果收到的第一个数据是AA,第五个数据是BB
//就把要发送的数据发出去
if((Rx_Buffer[0]==0xAA)&&(Rx_Buffer[4]==0xBB))
{
HAL_UART_Transmit(&huart1, Tx_Buffer, 6, 10);
//也可以在收到数据的时候点个灯
//当然,也可以根据其他的数据来做该做的事
HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
}
//把接收到的数据清空,等待下一次接收
for(uint8_t i=0; i<5;i++)
{
Rx_Buffer[i] = 0;
}
}
}
}
2、中断收发
//串口中断发送函数
//huart: 要发送数据的串口
//pData:要发送的数据指针
//Size:要发送的数据个数,
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
//串口中断接收函数
//huart: 要接收数据的串口
//pData:接收的数据存放位置
//Size:要接收的数据个数
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
针对发送中断函数,把设置的Size个数据发送完之后,才执行中断,就可以执行回调函数了,一般数据发送完,我们也不用做什么事情,回调函数也懒得写,有需求的话就去写吧。
针对接收中断函数,也是接收到Size个数据之后才产生中断,,然后执行回调函数,可以在回调函数中做一些操作,比如判断数据的对错,以及收到数据要做的事情,一般是比较简短的事情,太复杂的事情就不要再回调函数里面做了,可以在回调函数中做个标记,然后再大循环中处理。
以下例程是中断收发数据
1、单片机设置接收中断使能,
2、等待PC发送5个数据0xAA、 0x11 、0x22 、0x33 、0xBB
3、收到5个数据后,串口接收中断触发
4、在接收回调函数中判断第一个数据和最后一个数据是否正确;
5、正确的话就用中断的方式把事先准备好的数据发给PC(本例程是准备6个数据);
6、把前面接收的数据清空,等待下一次接收(当然,接收的数据你想怎么处理就怎么处理)
7、在中断回调函数中,把接收中断重新使能,因为中断执行的过程中把中断使能关闭了。
中断代码
//将要发送的数据
extern uint8_t Tx_Buffer[6];
//接收的数据存放位置
extern uint8_t Rx_Buffer[5];
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
//只要串口接收中断产生了让灯改变一下亮灭的状态
HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
//判断接收到的数据是否正确,本例程只判断首尾的数据
if((Rx_Buffer[0]==0xAA)&&(Rx_Buffer[4]==0xBB))
{
//收到的数据正确,就把事先准备好的数据发出去
HAL_UART_Transmit_IT(&huart1, Tx_Buffer, 5);
}
//把收到的数据清空,准备下一次接收
for(uint8_t i=0; i<5;i++)
{
Rx_Buffer[i] = 0;
}
}
//因为中断函数执行完之后,就把中断关闭了,需要重新开启中断
HAL_UART_Receive_IT(&huart1, Rx_Buffer, 5);
}
main文件代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
//将要发送的数据
uint8_t Tx_Buffer[6] = {0x5A, 0x01,0x02,0x03,0xA5};
//接收的数据存放位置
uint8_t Rx_Buffer[5] = {0};
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
//使能接收中断
HAL_UART_Receive_IT(&huart1, Rx_Buffer, 5);
while (1)
{
//什么都不做
}
}
中断跟捕猎有点像
你设置好陷阱,等待猎物的到来,没有猎物的时候,陷阱是不会被触发的;当陷阱被触发之后,你就可以去把里面的猎物取出来了,猎物怎么处理是你的事,是放大自然,还是大孜然;当然了,此时你的陷阱已经被破坏了,如果你还要捕猎,那就把你的陷阱重新弄好。
这种HAL库里面弄好的中断收发函数有个不好的地方,主要在串口接收,你要收到设定数量的数据才开始触发中断,比如设置收到5个数据才执行中断,第一次PC先给单片机发送4个数据,跟设定的5个数据相比,没达到触发中断的条件,那就继续等着。第二次PC接着又发送3个数据,这时候问题出现了,单片机怎么处理,当然是在第二次的数据还没发送完,只发送一个的时候,这时触发条件成熟了(达到5个数据),单片机开始中断,去处理数据。如果PC的两次发送时间间隔很久怎么办,凉拌,什么都不办了。这肯定是不行的。就像你的陷阱是想逮住5只猎物,但是只逮住了4只,然后再也没有猎物来,你就永远不能去取你的猎物,或者后面又来了3只,一共7只了,你拿走5只,还留两只等下次凑数,那这个猎人脑子肯定是有问题。
解决这个问题的办法也是有,猎物逮一个收一个,就是来回跑有点累。代码演示一下。
1、每次接收到一个数据就中断;数据存在Rx_Data;
2、中断回调函数里面,把Rx_Data的数据转存到Rx_Buffer数组;
3、一共收到5个数据之后,就把收到的数据发出去,数据个数计数也清零,重新开始。
//串口收到的数据给Rx_Data
extern uint8_t Rx_Data;
//串口接收数据缓存,Rx_Data就是个二道贩子,最终数据还是放在Rx_Buffer
extern uint8_t Rx_Buffer[5];
//接收数据计数
uint8_t Rx_Count = 0;
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
//只要串口接收中断产生了让灯改变一下亮灭的状态
HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
//把串口收到的数据转存在Rx_Buffer里
Rx_Buffer[Rx_Count++] = Rx_Data;
//把收到的数据清空,准备下一次接收
Rx_Data = 0;
//判断是不是收到了5个数据
if(Rx_Count ==5 )
{
//收到了5个数据,就把收到的数据发出去
HAL_UART_Transmit_IT(&huart1, Rx_Buffer, 5);
//收到5个数据之后,再重新计数
Rx_Count = 0;
}
}
//因为中断函数执行完之后,就把中断关闭了,需要重新开启中断
HAL_UART_Receive_IT(&huart1, &Rx_Data, 1);
}
上面每次收一个数据就中断的情况,在数据少的时候用用还可以,在大项目里面,数据多的话,最好还是别用,不然单片机就一直在中断了,不要干别的事情了。就好比你在干几个亿的项目,隔几秒钟就有人来汇报一下,你是什么心态。
那肯定有解决方案的,为了解决大量的数据传送,不要想着轮询的方式了,轮询是最傻瓜的工作方式,在控制上想都别想,问题太多,简单项目用用还可以,比如打印信息。那可不可以等数据都发送完了,通讯线空下来的时候给我个中断,通知我来处理数据。就像你在跟客户讨论需求的时候,你不会客户提一个需求你就去干,再接等客户提下一个需求,再继续干,那人就废了,让客户一口气把需求提完,等客户安静下来,我再来处理那一堆的需求。STM32单片机就可以这样。
//huart:那个串口收数据
//pData:收到的数据存在哪里
//Size:收多少个数据
HAL_UARTEx_ReceiveToIdle_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
测试例程
1、PC给单片机发送10个数据,随便哪10个;
2、单片机通过空闲中断的方式接收;
3、接收到之后把数据拷贝出来;
4、把数据通过中断发出去文章来源:https://www.toymoban.com/news/detail-457985.html
//将要发送的数据
extern uint8_t Tx_Buffer[6];
//接收的数据存放位置
extern uint8_t Rx_Buffer[20];
uint8_t RX_BUFFER[20];
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
//if((huart->Instance == USART1)&&(Size==10))
if(huart->Instance == USART1)
{
//只要串口接收中断产生了让灯改变一下亮灭的状态
HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
//把串口收到的数据转到RX_BUFFER里
memcpy(RX_BUFFER,Rx_Buffer,sizeof(Rx_Buffer) );
//收到的数据正确,就把事先准备好的数据发出去
HAL_UART_Transmit_IT(&huart1, RX_BUFFER, 10);
//把收到的数据清空,准备下一次接收
for(uint8_t i=0; i<20;i++)
{
Rx_Buffer[i] = 0;
}
}
//因为中断函数执行完之后,就把中断关闭了,需要重新开启中断
HAL_UARTEx_ReceiveToIdle_IT(&huart1, Rx_Buffer, 10);
}
还有DMA收发数据,放在DMA专题那边说。文章来源地址https://www.toymoban.com/news/detail-457985.html
到了这里,关于STM32学习----RS232串口通讯的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!