草庐IT

STM32CubeIDE开发(二十二), stm32的RS485/232串口通信开发要点

py_free-物联智能 2023-04-18 原文

目录

一、stm32串口通信

        1.1 硬件流控

         1.2 软件流控

        1.3 串口通信参数

二、新建RS485通信工程

        2.1 项目实现背景信息

        2.2 项目配置

        2.3 代码实现

   三、RS485驱动调用及测试

        3.1 接口调用

       3.2 编译及下载及测试


一、stm32串口通信

         stm32串口通信一般是指通过UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器传输数据,UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,其在应用程序开发过程中使用频率最高的数据总线。而MCU的UART 通信实际上是基于GPIO引脚电平信号实现的,因此,在预制UART外设不够时,也会可以采用GPIO引脚自行模拟UART串口通信。

        1.1 硬件流控

        UART 串口的特点是将数据一位一位地顺序传送,只要 2 根传输线就可以实现双向通信,一根线发送数据的同时用另一根线接收数据。但是,数据在两个串口之间进行通讯的时候常常会出现丢失数据的现象,主要原因是数据处理及数据接收效率不一致引起的冲突,为了解决这种问题,引入了流控概念。流控的概念源于 RS232 这个标准,用来解决这个速度匹配的问题,通过新增传输线来控制数据传输数据线的流向、流速等,这就是简单的三线串口的通讯方式,如下图:

         在两条数据线基础上,增加两根控制线,一根叫 CTS(Clear To Send 为输入信号,一根叫 RTS(Require To Send 为输出信号),一个是接收控制,一个是发送控制。可以看到,数据线方向与流控线数据方向相反,因为流控的主要是协调接收数据和处理数据一致性,所以需要让发送端等待,接收端发出来的信号叫 RTS 信号,发送端检测管脚叫 CTS。

        数据接收与 RTS 信号标记如下图,接收端缓存没数据时,RTS是低电平状态,发送端可以发送数据;当接收端接收到数据后,RTS切换为高电平,就是告诉发送端,数据还没有被拿走,请发送端等待;RTS 信号在数据没有被读取之前都是保持在高电平状态,一旦数据被 DMA 或者 CPU 从 DR 寄存器读取之后,RTS 就释放高电平,变为低电平,这时候发送端又可以发送数据的了。另外,如果 USART 是FIFO 模式,即缓冲模式开启的时候,在 FIFO 满的时候才会去拉高 RTS 信号。

         相应的,发送数据和CTS信号类似,发送端在发送数据时,要实时监测 CTS 的电平状态,如果发现是高电平,就不会再发送新的数据,直到 CTS 检测发现已经没有高电平信号才会再次发送。

        我们在cubeIDE中,对应上述的就是勾选RS232流控制支持,就可以选择CTS/RTS引脚配置。

         如上面所述,由设想的两根数据线传输多出两根流控制线,这无形中增加硬件成本,因此,RS485出现后,RS232的CTS/RT合二为一DE ,STM32 上有一个 DE 管脚和 RS485 的接收器芯片直接相连,控制数据的收发,主要就是数据的方向的控制。因为 RS485 是一个半双工的通讯模式,它的数据收的时候就不能发,发的时候不能收。

         在cubeMX中,需要支持DE引脚开始,就需要勾选RS485流控制功能

         1.2 软件流控

        上述的增加流控制线的做法都是属于硬件流控的实现,在实际项目开发过程,我们很多时候为了方便以及节省引脚,往往采用软流控。软件流控是以特殊的字符来代表从机已经不能再接收新的数据了,基本的流程就是从机在接收数据很多的时候或主动给发送端发送一个特殊字符,当发送端接收到这个特殊字符后就不能再发送数据了。

  软件流控很方便,不需要增加新的硬件,还是以前的TX、RX两根数据线实现,但是使用了软件流控,它本身的字符也是数据,这个数据只不过是说在软件里把它设置了一个特殊的含义。如果它是一个全双工的通讯,在给另一个串口发送数据的时候如果也包含了这样一个特殊字符,对方就会误以为我让它不要再发送数据了,会有一定的概率出现错误,而硬件流控就不需要考虑这方面,只需要使用 CTS 和 RTS,所有的数据都是由硬件来操作的,因此硬件流控在稳定性上更可靠。

       1.3 串口通信参数

         UART 串口通信有几个重要的参数,分别是起始位、波特率、数据位、停止位和奇偶检验位(流控,按需),对于两个使用 UART 串口通信的端口,这些参数必须匹配,否则通信将无法正常完成。

        起始位:表示数据传输的开始,默认电平逻辑为 “0” 。

        波特率:串口通信时的速率,它用单位时间内传输的二进制代码的有效位(bit)数来表示,其单位为每秒比特数 bit/s(bps)。常见的波特率值有 4800、9600、14400、38400、115200等,数值越大数据传输的越快,波特率为 115200 表示每秒钟传输 115200 位数据。

        数据位:可能值有 5、6、7、8、9,表示传输这几个 bit 位数据。一般取值为 8,因为一个 ASCII 字符值为 8 位。

        停止位: 表示一帧数据的结束。默认电平逻辑为 “1”。

        奇偶校验位:用于接收方对接收到的数据进行校验,校验 “1” 的位数为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性,使用时不需要此位也可以。

二、新建RS485通信工程

        2.1 项目实现背景信息

        本文采用的STM32L496VGTX-ali开发板,该开发板预留了扩展接口支持USART4通信,本文将USART4与USB转RS485(CH340)连接,进而与电脑通信。

         实物图:

         扩展接口原理框图如下:

         另外本人开发板标注背后扩展口标注的是PA0、PA1,查看其他描述,确定就是PA0、PA1引脚。

      现在基于本专栏前面已近实现的串口lpusart通信和及lcd亮屏工程为基础创建新工程,并移植了相关代码。

   cubeIDE开发, stm32调试信息串口通信输出显示_py_free的博客-CSDN博客

   cubeIDE开发, stm32的OLED点亮及字符显示设计(基于SPI通信)_py_free的博客-CSDN博客

        现将在该工程基础上,实现串口Usart4和lpusart通信,并lcd屏打印其发送数据;Usart4发送'A'/'B'可以点亮及熄灭LED0。

        2.2 项目配置

        双击(.ioc)打开cubeMX配置界面,开启USART4串口功能,配置串口参数

        并开启usart4的中断支持

         点击保存生成输出代码。

        2.3 代码实现

        在ICore目录下,新建rs485文件夹,并在该文件目录下,创建rs485.h和rs485.c源文件,其实现代码如下:

        rs485.h

#ifndef RS485_RS485_H_
#define RS485_RS485_H_

#include "stm32l4xx_hal.h" //HAL库文件声明

extern UART_HandleTypeDef huart4;//声明USART4的HAL库结构体
void RS485_printf (char *fmt, ...);  //RS485发送

#endif /* RS485_RS485_H_ */

        rs485.c

#include "../usart/usart.h"
#include "main.h"
#include <stdarg.h>
/*
RS485总线通信,使用UART4,这是RS485专用的printf函数
*/
void RS485_printf (char *fmt, ...)
{
    char buff[USART4_REC_LEN+1];  //用于存放转换后的数据 [长度]
    uint16_t i=0;
    va_list arg_ptr;
    va_start(arg_ptr,fmt);
    vsnprintf(buff, USART4_REC_LEN+1,fmt,arg_ptr);//数据转换
    i=strlen(buff);//得出数据长度
    if(strlen(buff)>USART4_REC_LEN)i=USART4_REC_LEN;//如果长度大于最大值,则长度等于最大值(多出部分忽略)
    HAL_UART_Transmit(&huart4,(uint8_t *)buff,i,0Xffff);//串口发送函数(串口号,内容,数量,溢出时间)
    va_end(arg_ptr);
}
//所有USART串口的中断回调函数HAL_UART_RxCpltCallback,统一存放在【USART.C】文件中。

        调整usart.h和usart.c文件,重新实现串口USART4的中断回调函数

        usart.h

#ifndef _USART_H_
#define _USART_H_

#include "stm32l4xx_hal.h" //HAL库文件声明
#include <string.h>//用于字符串处理的库
#include "../print/print.h"//用于printf函数串口重映射

extern UART_HandleTypeDef hlpuart1;//声明LPUSART的HAL库结构体
extern UART_HandleTypeDef huart4;//声明USART4的HAL库结构体

#define HLPUSART_REC_LEN  256//定义LPUSART最大接收字节数
#define USART4_REC_LEN 256

extern uint8_t  HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
extern uint16_t HLPUSART_RX_STA;//接收状态标记
extern uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存

extern uint8_t USART4_RX_BUF[USART4_REC_LEN];
extern uint16_t USART4_RX_STA;
extern uint8_t USART4_NewData;

void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart);//串口中断回调函数声明

#endif /* _USART_H_ */

        usart.c

#include "usart.h"

uint8_t  HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
/*
 * bit15:接收到回车(0x0d)时设置HLPUSART_RX_STA|=0x8000;
 * bit14:接收溢出标志,数据超出缓存长度时,设置HLPUSART_RX_STA|=0x4000;
 * bit13:预留
 * bit12:预留
 * bit11~0:接收到的有效字节数目(0~4095)
 */
uint16_t HLPUSART_RX_STA=0;接收状态标记//bit15:接收完成标志,bit14:接收到回车(0x0d),bit13~0:接收到的有效字节数目
uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存

uint8_t USART4_RX_BUF[USART4_REC_LEN];
uint16_t USART4_RX_STA=0;
uint8_t USART4_NewData;

void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart)//串口中断回调函数
{
	if(huart ==&hlpuart1)//判断中断来源(串口1:USB转串口)
    {
		if(HLPUSART_NewData==0x0d){//回车标记
     	  HLPUSART_RX_STA|=0x8000;//标记接到回车
		}else{
			if((HLPUSART_RX_STA&0X0FFF)<HLPUSART_REC_LEN){
				HLPUSART_RX_BUF[HLPUSART_RX_STA&0X0FFF]=HLPUSART_NewData; //将收到的数据放入数组
				HLPUSART_RX_STA++;  //数据长度计数加1
			}else{
				HLPUSART_RX_STA|=0x4000;//数据超出缓存长度,标记溢出
			}
        }
       HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData,1); //再开启接收中断
    }
	if(huart ==&huart4)//判断中断来源(串口4:USB转串口)
    {
//		printf("%c",USART4_NewData);
		if(USART4_NewData==0x0d){//回车标记
			USART4_RX_STA|=0x8000;//标记接到回车
		}else{
			if((USART4_RX_STA&0X0FFF)<USART4_REC_LEN){
				USART4_RX_BUF[USART4_RX_STA&0X0FFF]=USART4_NewData; //将收到的数据放入数组
				USART4_RX_STA++;  //数据长度计数加1
			}else{
				USART4_RX_STA|=0x4000;//数据超出缓存长度,标记溢出
			}
        }
       HAL_UART_Receive_IT(&huart4,(uint8_t *)&USART4_NewData,1); //再开启接收中断
    }
}

   三、RS485驱动调用及测试

        3.1 接口调用

        在main.c文件中加入rs485.h头文件支持

/* USER CODE BEGIN Includes */
#include "../../ICore/key/key.h"
#include "../../ICore/led/led.h"
#include "../../ICore/print/print.h"
#include "../../ICore/usart/usart.h"
#include "../../ICore/rs485/rs485.h"
#include "../../ICore/oled/oled.h"
/* USER CODE END Includes */

        在主函数初始化中调整加入usart4初始化设定

  /* USER CODE BEGIN 2 */
  ResetPrintInit(&hlpuart1);
  HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
  HLPUSART_RX_STA = 0;
  HAL_UART_Receive_IT(&huart4,(uint8_t *)&USART4_NewData,1); //开启串口4接收中断
  USART4_RX_STA = 0;
  //
  OLED_init();
  //设置OLED蓝色背景显示
  BSP_LCD_Clear_DMA(LCD_DISP_BLUE);
  printf("OLED_Clear_DMA\r\n");
  /* USER CODE END 2 */

        在主函数循环体中,修改代码实现对usart4接收数据处理

  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始
		  RS485_printf("%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
		  OLED_printf(10,10,"%.*s",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
		  HLPUSART_RX_STA=0;//接收错误,重新开始
		  HAL_Delay(100);//等待
	  }
	  if(USART4_RX_STA&0xC000){//溢出或换行,重新开始
		  if(1==(USART4_RX_STA&0x0FFF))
		  {
			  switch (USART4_RX_BUF[0]){
			  case 'A':
				  set_led0_val(1);
				  break;
			  case 'B':
				  set_led0_val(0);
				  break;
			  default:
				  break;
			  }
		  }
  		  printf("%.*s\r\n",USART4_RX_STA&0X0FFF, USART4_RX_BUF);
  		  OLED_printf(10,42,"%.*s",USART4_RX_STA&0X0FFF, USART4_RX_BUF);
  		  USART4_RX_STA=0;//接收错误,重新开始
  		  HAL_Delay(100);//等待
  	  }
    /* USER CODE END WHILE */

       3.2 编译及下载及测试

         LED0在发送'A'字符是关闭(LED灯是低位有效)

有关STM32CubeIDE开发(二十二), stm32的RS485/232串口通信开发要点的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  3. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  4. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  5. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  9. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  10. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

随机推荐