当前位置:   article > 正文

STM32学习----RS232串口通讯_stm32 rs232

stm32 rs232

一、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步都自动生成好了,只需要在第五步调用发送函数就可以

以下是自动生成的配置代码

  1. #include "usart.h"
  2. //声明一个UART句柄结构体,应为使用串口1 ,结构体命名为huart1
  3. UART_HandleTypeDef huart1;
  4. //配置串口波特率、数据长度、停止位、校验位等
  5. //调用HAL_UART_Init函数,再调用HAL_UART_MspInit函数,
  6. //完成UART管脚和时钟的配置,以及串口初始化
  7. void MX_USART1_UART_Init(void)
  8. {
  9. huart1.Instance = USART1;
  10. huart1.Init.BaudRate = 115200;
  11. huart1.Init.WordLength = UART_WORDLENGTH_8B;
  12. huart1.Init.StopBits = UART_STOPBITS_1;
  13. huart1.Init.Parity = UART_PARITY_NONE;
  14. huart1.Init.Mode = UART_MODE_TX_RX;
  15. huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  16. huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  17. if (HAL_UART_Init(&huart1) != HAL_OK)
  18. {
  19. Error_Handler();
  20. }
  21. }
  22. //配置UART管脚位置和时钟
  23. void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
  24. {
  25. GPIO_InitTypeDef GPIO_InitStruct = {0};
  26. if(uartHandle->Instance==USART1)
  27. {
  28. __HAL_RCC_USART1_CLK_ENABLE();
  29. __HAL_RCC_GPIOA_CLK_ENABLE();
  30. //串口1使用GPIOA的,PIN9和PIN10
  31. GPIO_InitStruct.Pin = GPIO_PIN_9;
  32. GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  33. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  34. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  35. GPIO_InitStruct.Pin = GPIO_PIN_10;
  36. GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  37. GPIO_InitStruct.Pull = GPIO_NOPULL;
  38. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  39. /* 串口中断配置,轮询模式可以不用配置中断*/
  40. HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
  41. HAL_NVIC_EnableIRQ(USART1_IRQn);
  42. }
  43. }
  44. //相当于还原,还原到配置之前的状态
  45. void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
  46. {
  47. if(uartHandle->Instance==USART1)
  48. {
  49. __HAL_RCC_USART1_CLK_DISABLE();
  50. HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
  51. HAL_NVIC_DisableIRQ(USART1_IRQn);
  52. }
  53. }

  在main文件中去完成串口数据发送

        串口发送使用函数

  1. //huart:用哪个串口发送
  2. //pData:被发送的数据指针
  3. //Size:发送的数据个数
  4. //Timeout:超时时间
  5. HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

        串口接收使用函数

  1. //huart:用哪个串口接收
  2. //pData:接收的数据存放位置
  3. //Size:接收的数据个数
  4. //Timeout:超时时间
  5. 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个数据发出去;

还要把接收到的数据清空,等待下一次接收新数据。

  1. #include "main.h"
  2. #include "usart.h"
  3. #include "gpio.h"
  4. //将要发送的数据
  5. uint8_t Tx_Buffer[6] = {0x5A, 0x01,0x02,0x03,0x04,0xA5};
  6. //接收的数据存放位置
  7. uint8_t Rx_Buffer[5] = {0};
  8. void SystemClock_Config(void);
  9. int main(void)
  10. {
  11. HAL_Init();
  12. /* Configure the system clock */
  13. SystemClock_Config();
  14. MX_GPIO_Init();
  15. MX_USART1_UART_Init();
  16. while (1)
  17. {
  18. //开始接收数据
  19. if(HAL_UART_Receive(&huart1, Rx_Buffer, 5, 10))
  20. {
  21. //如果收到的第一个数据是AA,第五个数据是BB
  22. //就把要发送的数据发出去
  23. if((Rx_Buffer[0]==0xAA)&&(Rx_Buffer[4]==0xBB))
  24. {
  25. HAL_UART_Transmit(&huart1, Tx_Buffer, 6, 10);
  26. //也可以在收到数据的时候点个灯
  27. //当然,也可以根据其他的数据来做该做的事
  28. HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
  29. }
  30. //把接收到的数据清空,等待下一次接收
  31. for(uint8_t i=0; i<5;i++)
  32. {
  33. Rx_Buffer[i] = 0;
  34. }
  35. }
  36. }
  37. }

2、中断收发

  1. //串口中断发送函数
  2. //huart: 要发送数据的串口
  3. //pData:要发送的数据指针
  4. //Size:要发送的数据个数,
  5. HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
  6. //串口中断接收函数
  7. //huart: 要接收数据的串口
  8. //pData:接收的数据存放位置
  9. //Size:要接收的数据个数
  10. 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、在中断回调函数中,把接收中断重新使能,因为中断执行的过程中把中断使能关闭了。

中断代码

  1. //将要发送的数据
  2. extern uint8_t Tx_Buffer[6];
  3. //接收的数据存放位置
  4. extern uint8_t Rx_Buffer[5];
  5. /**
  6. * @brief This function handles USART1 global interrupt.
  7. */
  8. void USART1_IRQHandler(void)
  9. {
  10. HAL_UART_IRQHandler(&huart1);
  11. }
  12. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  13. {
  14. if(huart->Instance == USART1)
  15. {
  16. //只要串口接收中断产生了让灯改变一下亮灭的状态
  17. HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
  18. //判断接收到的数据是否正确,本例程只判断首尾的数据
  19. if((Rx_Buffer[0]==0xAA)&&(Rx_Buffer[4]==0xBB))
  20. {
  21. //收到的数据正确,就把事先准备好的数据发出去
  22. HAL_UART_Transmit_IT(&huart1, Tx_Buffer, 5);
  23. }
  24. //把收到的数据清空,准备下一次接收
  25. for(uint8_t i=0; i<5;i++)
  26. {
  27. Rx_Buffer[i] = 0;
  28. }
  29. }
  30. //因为中断函数执行完之后,就把中断关闭了,需要重新开启中断
  31. HAL_UART_Receive_IT(&huart1, Rx_Buffer, 5);
  32. }

main文件代码

  1. #include "main.h"
  2. #include "usart.h"
  3. #include "gpio.h"
  4. //将要发送的数据
  5. uint8_t Tx_Buffer[6] = {0x5A, 0x01,0x02,0x03,0xA5};
  6. //接收的数据存放位置
  7. uint8_t Rx_Buffer[5] = {0};
  8. void SystemClock_Config(void);
  9. int main(void)
  10. {
  11. HAL_Init();
  12. /* Configure the system clock */
  13. SystemClock_Config();
  14. MX_GPIO_Init();
  15. MX_USART1_UART_Init();
  16. //使能接收中断
  17. HAL_UART_Receive_IT(&huart1, Rx_Buffer, 5);
  18. while (1)
  19. {
  20. //什么都不做
  21. }
  22. }

中断跟捕猎有点像

        你设置好陷阱,等待猎物的到来,没有猎物的时候,陷阱是不会被触发的;当陷阱被触发之后,你就可以去把里面的猎物取出来了,猎物怎么处理是你的事,是放大自然,还是大孜然;当然了,此时你的陷阱已经被破坏了,如果你还要捕猎,那就把你的陷阱重新弄好。

        这种HAL库里面弄好的中断收发函数有个不好的地方,主要在串口接收,你要收到设定数量的数据才开始触发中断,比如设置收到5个数据才执行中断,第一次PC先给单片机发送4个数据,跟设定的5个数据相比,没达到触发中断的条件,那就继续等着。第二次PC接着又发送3个数据,这时候问题出现了,单片机怎么处理,当然是在第二次的数据还没发送完,只发送一个的时候,这时触发条件成熟了(达到5个数据),单片机开始中断,去处理数据。如果PC的两次发送时间间隔很久怎么办,凉拌,什么都不办了。这肯定是不行的。就像你的陷阱是想逮住5只猎物,但是只逮住了4只,然后再也没有猎物来,你就永远不能去取你的猎物,或者后面又来了3只,一共7只了,你拿走5只,还留两只等下次凑数,那这个猎人脑子肯定是有问题。

        解决这个问题的办法也是有,猎物逮一个收一个,就是来回跑有点累。代码演示一下。

1、每次接收到一个数据就中断;数据存在Rx_Data;

2、中断回调函数里面,把Rx_Data的数据转存到Rx_Buffer数组;

3、一共收到5个数据之后,就把收到的数据发出去,数据个数计数也清零,重新开始。

  1. //串口收到的数据给Rx_Data
  2. extern uint8_t Rx_Data;
  3. //串口接收数据缓存,Rx_Data就是个二道贩子,最终数据还是放在Rx_Buffer
  4. extern uint8_t Rx_Buffer[5];
  5. //接收数据计数
  6. uint8_t Rx_Count = 0;
  7. /**
  8. * @brief This function handles USART1 global interrupt.
  9. */
  10. void USART1_IRQHandler(void)
  11. {
  12. HAL_UART_IRQHandler(&huart1);
  13. }
  14. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  15. {
  16. if(huart->Instance == USART1)
  17. {
  18. //只要串口接收中断产生了让灯改变一下亮灭的状态
  19. HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
  20. //把串口收到的数据转存在Rx_Buffer里
  21. Rx_Buffer[Rx_Count++] = Rx_Data;
  22. //把收到的数据清空,准备下一次接收
  23. Rx_Data = 0;
  24. //判断是不是收到了5个数据
  25. if(Rx_Count ==5 )
  26. {
  27. //收到了5个数据,就把收到的数据发出去
  28. HAL_UART_Transmit_IT(&huart1, Rx_Buffer, 5);
  29. //收到5个数据之后,再重新计数
  30. Rx_Count = 0;
  31. }
  32. }
  33. //因为中断函数执行完之后,就把中断关闭了,需要重新开启中断
  34. HAL_UART_Receive_IT(&huart1, &Rx_Data, 1);
  35. }

        上面每次收一个数据就中断的情况,在数据少的时候用用还可以,在大项目里面,数据多的话,最好还是别用,不然单片机就一直在中断了,不要干别的事情了。就好比你在干几个亿的项目,隔几秒钟就有人来汇报一下,你是什么心态。

        那肯定有解决方案的,为了解决大量的数据传送,不要想着轮询的方式了,轮询是最傻瓜的工作方式,在控制上想都别想,问题太多,简单项目用用还可以,比如打印信息。那可不可以等数据都发送完了,通讯线空下来的时候给我个中断,通知我来处理数据。就像你在跟客户讨论需求的时候,你不会客户提一个需求你就去干,再接等客户提下一个需求,再继续干,那人就废了,让客户一口气把需求提完,等客户安静下来,我再来处理那一堆的需求。STM32单片机就可以这样。

  1. //huart:那个串口收数据
  2. //pData:收到的数据存在哪里
  3. //Size:收多少个数据
  4. HAL_UARTEx_ReceiveToIdle_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

测试例程

1、PC给单片机发送10个数据,随便哪10个;

2、单片机通过空闲中断的方式接收;

3、接收到之后把数据拷贝出来;

4、把数据通过中断发出去

  1. //将要发送的数据
  2. extern uint8_t Tx_Buffer[6];
  3. //接收的数据存放位置
  4. extern uint8_t Rx_Buffer[20];
  5. uint8_t RX_BUFFER[20];
  6. /**
  7. * @brief This function handles USART1 global interrupt.
  8. */
  9. void USART1_IRQHandler(void)
  10. {
  11. HAL_UART_IRQHandler(&huart1);
  12. }
  13. void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
  14. {
  15. //if((huart->Instance == USART1)&&(Size==10))
  16. if(huart->Instance == USART1)
  17. {
  18. //只要串口接收中断产生了让灯改变一下亮灭的状态
  19. HAL_GPIO_TogglePin(GPIOA, LED0_Pin);
  20. //把串口收到的数据转到RX_BUFFER里
  21. memcpy(RX_BUFFER,Rx_Buffer,sizeof(Rx_Buffer) );
  22. //收到的数据正确,就把事先准备好的数据发出去
  23. HAL_UART_Transmit_IT(&huart1, RX_BUFFER, 10);
  24. //把收到的数据清空,准备下一次接收
  25. for(uint8_t i=0; i<20;i++)
  26. {
  27. Rx_Buffer[i] = 0;
  28. }
  29. }
  30. //因为中断函数执行完之后,就把中断关闭了,需要重新开启中断
  31. HAL_UARTEx_ReceiveToIdle_IT(&huart1, Rx_Buffer, 10);
  32. }

还有DMA收发数据,放在DMA专题那边说。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/982190
推荐阅读
相关标签
  

闽ICP备14008679号