STM32的串口(RS485)数据收发

news2024/9/22 23:26:11

一、前言

我们的单片机串口一般常用RS232、RS485、TTL这几种通讯方式,日常调试可能RS232、TTL比较多,真正和其它厂家数据交互的时候,还是RS485用的比较多,因为它是差分信号等电气属性,所以比较稳定,传输距离长,常与modbus协议同用交互数据,今天这篇文章就讲一下如何利用RS485进行数据收发。

二、RS485特性

RS485(或EIA-485、过去称为RS-485)是隶属于OSI模型物理层的电气特性规定为2线、半双工、平衡传输线多点通信的标准。这是由电信行业协会(TIA)及电子工业联盟(EIA)联合发布的标准。RS485的主要特性和应用如下:

  1. 电气特性

    • RS-485采用差分信号负逻辑,逻辑“0”以两线间的电压差为+(2~6)V表示;逻辑“1”以两线间的电压差为-(2~6)V表示。
    • 接口信号电平比RS-232-C降低了,就不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL电路连接。
  2. 通信性能

    • RS-485的数据最高传输速率为10Mbps。
    • RS-485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
    • RS-485接口的最大传输距离标准值为4000英尺(约1219米),实际上可达3000米。但传输速率与传输距离成反比,在较低的传输速率下才能达到最大的通信距离
    • RS-485总线一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或更多节点。
  3. 应用

    • RS485广泛应用于工业环境中,特别是在要求通信距离为几十米到上千米时,如工矿、交通运输等企业的称重计量设备的数据采集。
    • RS485因其传输距离远、抗干扰能力强、支持多节点和接线简单等特点,成为首选的串行接口。
  4. 半双工特性

    • RS485是一种半双工-异步-串行通信总线,意味着两者通信时,同一时刻只能由其中一方发送,另一方只能接收,不可以同时收发。

三、软件设计

我们一般看到的RS485串口数据接收的简单案例都是直接接收缓存区一直增加或者以回车键为结束的案例,但是这样没有办法正常解析接收到的数据并根据数据进行相应处理。其实,我们可以加一个定时器,因为串口数据一句话是连续发送的,只要从接收到第一个字节开始启动定时器,规定时间内没有收到下一个字节,即定时器倒计时结束,我们就可以认为本次数据接收完成了接收到完整的一条数据以后,我们对其进行解析,发送的话,直接调用函数发送就可以了,只不过发送之前需要控制 RS485_RE脚切换成发送模式

四、代码实现

1.初始化串口

//初始化IO 串口7
//bound:波特率
void RS485_Init(u32 bound)
{
    //GPIO端口设置
	GPIO_InitTypeDef GPIO_Initure;
	
	//PCF8574_Init();                         //初始化PCF8574,用于控制RE脚
	
	__HAL_RCC_GPIOF_CLK_ENABLE();			//使能GPIOA时钟
	__HAL_RCC_UART7_CLK_ENABLE();			//使能UART7时钟
	
	GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7; //PF6,7
	GPIO_Initure.Mode=GPIO_MODE_AF_PP;		//复用推挽输出
	GPIO_Initure.Pull=GPIO_PULLUP;			//上拉
	GPIO_Initure.Speed=GPIO_SPEED_HIGH;		//高速
	GPIO_Initure.Alternate=GPIO_AF8_UART7;	//复用为UART7
	HAL_GPIO_Init(GPIOF,&GPIO_Initure);	   	//初始化PF6,7
	
	GPIO_Initure.Pin=GPIO_PIN_8; //PF8
	GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;		//推挽输出
	GPIO_Initure.Pull=GPIO_PULLDOWN;			//下拉
	GPIO_Initure.Speed=GPIO_SPEED_HIGH;		//高速
	//GPIO_Initure.Alternate=GPIO_AF8_UART7;	//复用为UART7
	HAL_GPIO_Init(GPIOF,&GPIO_Initure);	   	//初始化PF8
    
    //USART 初始化设置
	UART7_RS485Handler.Instance=UART7;			        //UART7
	UART7_RS485Handler.Init.BaudRate=bound;		        //波特率
	UART7_RS485Handler.Init.WordLength=UART_WORDLENGTH_8B;	//字长为8位数据格式
	UART7_RS485Handler.Init.StopBits=UART_STOPBITS_1;		//一个停止位
	UART7_RS485Handler.Init.Parity=UART_PARITY_NONE;		//无奇偶校验位
	UART7_RS485Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE;	//无硬件流控
	UART7_RS485Handler.Init.Mode=UART_MODE_TX_RX;		    //收发模式
	HAL_UART_Init(&UART7_RS485Handler);			        //HAL_UART_Init()会使能UART7
    
  __HAL_UART_DISABLE_IT(&UART7_RS485Handler,UART_IT_TC);
#if EN_UART7_RX
	__HAL_UART_ENABLE_IT(&UART7_RS485Handler,UART_IT_RXNE);//开启接收中断
	HAL_NVIC_EnableIRQ(UART7_IRQn);				        //使能USART1中断
	HAL_NVIC_SetPriority(UART7_IRQn,3,3);			        //抢占优先级3,子优先级3
#endif	
  RS485_TX_Set(0);                                        //设置为接收模式	
	TIM3_Init(500-1,9000-1);       //定时器3初始化,定时器时钟为90M,分频系数为9000-1,
                                    //所以定时器3的频率为90M/9000=10K,自动重装载为500-1,那么定时器周期就是50ms
}

初始化PF6,7为串口7的RX和TX,bound(波特率)咱们传入的是9600,一般RS485通讯用9600比较多,调试的串口用115200较多,然后就是一些基本的串口配置,最后初始化定时器3。

2.切换接发模式

//RS485模式控制.
//en:0,接收;1,发送.
void RS485_TX_Set(u8 en)
{
	if(en==0){
		 HAL_GPIO_WritePin(GPIOF, GPIO_PIN_8, GPIO_PIN_RESET);
	}else{
	HAL_GPIO_WritePin(GPIOF, GPIO_PIN_8, GPIO_PIN_SET);
	}
	
}

PF8引脚在拉的时候RS485是接收模式,PF8引脚在拉的时候RS485是发送模式。

3.初始化定时器

void TIM3_Init(u16 arr,u16 psc)
{  
    TIM3_Handler.Instance=TIM3;                          //通用定时器3
    TIM3_Handler.Init.Prescaler=psc;                     //分频系数
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM3_Handler.Init.Period=arr;                        //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
    HAL_TIM_Base_Init(&TIM3_Handler);
}

定时器时钟为90M,分频系数为9000-1,所以定时器3的频率为90M/9000=10K,自动重装载为500-1,那么定时器周期就是50ms,也就是50ms内RS485不传回下一个字节,即认为传输结束。

4.串口和定时器中断

//接收缓存区 	
u8 RS485_RX_BUF[256];  	//接收缓冲,最大64个字节.
//接收到的数据长度
u8 RS485_RX_CNT=0;
u8 RS485RX_FLAG=0;//如果RS485接收到了数据 标志位变为1  

void UART7_IRQHandler(void)
{
    u8 res;	  
    if((__HAL_UART_GET_FLAG(&UART7_RS485Handler,UART_FLAG_RXNE)!=RESET))  //接收中断
	{
			 HAL_TIM_Base_Stop(&TIM3_Handler);
       HAL_UART_Receive(&UART7_RS485Handler,&res,1,100);
		  if(RS485_RX_CNT<255)
		  {
		   	RS485_RX_BUF[RS485_RX_CNT]=res;		//记录接收到的值
			  RS485_RX_CNT++;						//接收数据增加1 
		  }
			 HAL_TIM_Base_Start_IT(&TIM3_Handler); //开定时器   
	} 
}

//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM3_Handler);
}


//回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
				HAL_TIM_Base_Stop(&TIM3_Handler);
				__HAL_TIM_CLEAR_IT(&TIM3_Handler, TIM_IT_UPDATE);
				RS485RX_FLAG=1;
    }
}

串口中断和定时器中断是最重要的,这里我们一起讲,这里的逻辑就是:串口接收到每一个字节,都先关闭定时器,然后接收完了再启动定时器,这样的话,只要定时器倒计时结束之前停止运行,就不会进中断,直到所有数据接收完成以后,没数据再进来,进入定时器的中断,此时,先结束定时器运行,然后清除中断标志位(不清除的话可能会多进入一次定时器中断),最后将接收标志位置1即为接收完成。

5.接收完成后处理

void led_task(void *pdata)
{
	OS_ERR err;
	while(1)
	{
		if(RS485RX_FLAG==1){
			RS485RX_FLAG=0;
			RS485_Have_Data();
			OSTimeDlyHMSM(0,0,0,20,OS_OPT_TIME_HMSM_STRICT,&err); //延时2s
		}
 	}
}

void RS485_Have_Data(){
	RS485_Send_Data(RS485_RX_BUF,RS485_RX_CNT);
	 RS485_RX_CNT=0;
}

这里是写了一个任务,只要判断RS485RX_FLAG=1以后,就开始对接收到的数据进行处理,我们要先将RS485RX_FLAG赋值0,避免多次运行,然后调用处理函数,此函数功能就是将接收到的数据原封不动返回去就算成功了,一般正常项目中我们都会对接收数据解析然后返回对应的数据,最后处理完了以后,别忘了将RS485_RX_CNT赋值0,确保下次再来了新数据还是从接收缓存区的第一位开始赋值的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1938551.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab演示三维坐标系旋转

function showTwo3DCoordinateSystemsWithAngleDifference() clear all close all % 第一个三维坐标系 origin1 [0 0 0]; x_axis1 [1 0 0]; y_axis1 [0 1 0]; z_axis1 [0 0 1];% 绕 x 轴旋转 30 度的旋转矩阵 theta_x 30 * pi / 180; rotation_matrix_x [1 0 0; 0 cos(th…

SpringBoot使用本地缓存——Caffeine

SpringBoot使用本地缓存——Caffeine 缓存&#xff0c;想必大家都用过&#xff0c;将常用的数据存储在缓存上能在一定程度上提升数据存取的速度。这正是局部性原理的应用。之前用的缓存大多是分布式的&#xff0c;比如Redis。使用Redis作为缓存虽然是大多数系统的选择&#xf…

基于重要抽样的主动学习不平衡分类方法ALIS

这篇论文讨论了数据分布不平衡对分类器性能造成的影响,并提出了一种新的有效解决方案 - 主动学习框架ALIS。 1、数据分布不平衡会影响分类器的学习性能。现有的方法主要集中在过采样少数类或欠采样多数类,但往往只采用单一的采样技术,无法有效解决严重的类别不平衡问题。 2、论…

【Datawhale AI 夏令营】CV图像竞赛——Deepfake攻防

【Datawhale AI 夏令营】CV图像竞赛——Deepfake攻防 从零入门CV图像竞赛(Deepfake攻防) 是 Datawhale 2024 年 AI 夏令营第二期 的学习活动&#xff08;“CV图像”方向&#xff09;&#xff0c;基于蚂蚁集团举办的“外滩大会-全球Deepfake攻防挑战赛”开展的实践学习 ​ 这几天…

Mysql深入讲解(索引、事务、锁机制)

一、MySQL索引 1、何为索引&#xff1f; MySQL中的索引是一种数据结构&#xff0c;用于加快对数据库表中数据的查询速度【查询速度提升】。它类似于书本目录&#xff0c;使得用户可以根据特定字段快速定位到所需的数据行&#xff0c;而无需扫描整个表。 2、索引分类 Hash索…

C 语言回调函数

回调函数的概念 您的理解是正确的。pFunCallBack 是一种函数指针类型&#xff0c;它定义了函数的签名&#xff08;即函数的参数类型和返回类型&#xff09;。当我们说 pFunCallBack pFun&#xff0c;我们是在声明一个变量 pFun&#xff0c;其类型是 pFunCallBack —— 即一个函…

【D3.js in Action 3 精译_018】2.4 向选择集添加元素

当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可视化最佳实践&#xff08;下&#xff09;1.4 本章小结 第二章…

RNN循环递归网络讲解与不掉包python实现

1.算法简介 参考论文&#xff1a;Elman J L. Finding structure in time[J]. Cognitive science, 1990, 14(2): 179-211.&#xff0c;谷歌被引次数超16000! 说到循环递归结构就不得不提到其鼻祖RNN网络。首先我们先对RNN有个初步的概念&#xff1a;想象一下&#xff0c;你正在…

[紧急!!!]20240719全球Windows10/11蓝屏问题,CrowdStrike导致的错误解决方案

文章目录 前言一、CrowdStrike是什么&#xff1f;二、PC解决方式&#xff08;网路上大神的方式&#xff0c;虚拟机测试过&#xff09;1.Windows PC 上 CrowdStrike BSOD 问题的官方解决方法&#xff1a;2.阻止CrowdStrick启动-命令行法3.阻止CrowdStrick启动-注册表法 三、AWS …

基于Matlab的数据可视化

基于Matlab的数据可视化 一、二维图形的绘制&#xff08;一&#xff09;基本图形函数&#xff08;1&#xff09;plot函数&#xff08;2&#xff09;fplot函数&#xff08;3&#xff09;其他坐标系的二维曲线 &#xff08;二&#xff09;图形属性设置&#xff08;1&#xff09;线…

对某次应急响应中webshell的分析

文章前言 在之前处理一起应急事件时发现攻击者在WEB应用目录下上传了webshell&#xff0c;但是webshell似乎使用了某种加密混淆手法&#xff0c;无法直观的看到其中的木马连接密码&#xff0c;而客户非要让我们连接webshell来证实此文件为后门文件且可执行和利用(也是很恼火&a…

数据结构与算法04二叉树|二叉排序树|AVL树

目录 一、二叉树(binary tree) 1、二叉树常见术语 2、二叉树常用的操作 2.1、初始化&#xff1a;与链表十分相似&#xff0c;先创建节点&#xff0c;然后构造引用/指针关系. 2.2、插入和删除操作 3、常见二叉树类型 3.1、满二叉树 3.2、完全二叉树&#xff08;complete b…

跳跃游戏Ⅱ - vector

55. 跳跃游戏 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool canJump(vector<int>& nums) {int n nums.size();int reach 0;for(int i 0; i < n; i){if(i > reach){return false;}reach max(inums[i], reach);}return true;} }; …

SpringBoot3 + Vue3 学习 Day 2

登入接口 和 获取用户详细信息的开发 学习视频登入接口的开发1、登入主逻辑2、登入认证jwt 介绍生成 JWT① 导入依赖② 编写代码③ 验证JWT 登入认证接口的实现① 导入 工具类② controller 类实现③ 存在的问题及优化① 编写拦截器② 注册拦截器③ 其他接口直接提供服务 获取用…

JVM(day4)类加载机制

类加载过程 加载 通过一个类的全限定名来获取定义此类的二进制字节流。 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 在内存中生成一个代表这个类的java.lang.Class对象&#xff0c;作为方法区这个类的各种数据的访问入口。 验证 文件格式验证 元数…

LeetCode做题记录(第二天)647. 回文子串

题目&#xff1a; 647. 回文子串 标签&#xff1a;双指针 字符串 动态规划 题目信息&#xff1a; 思路一&#xff1a;暴力实现 我们直接for套for分割成一个个子串再判断&#xff0c;如果子串是回文子串&#xff0c;就1&#xff0c;最后得出结果 代码实现&#xff1a; cl…

C语言实例-约瑟夫生者死者小游戏

问题&#xff1a; 30个人在一条船上&#xff0c;超载&#xff0c;需要15人下船。于是人们排成一队&#xff0c;排队的位置即为他们的编号。报数&#xff0c;从1开始&#xff0c;数到9的人下船&#xff0c;如此循环&#xff0c;直到船上仅剩15人为止&#xff0c;问都有哪些编号…

Missing script:‘dev‘

场景&#xff1a; npm run dev 原因&#xff1a;没有安装依赖&#xff0c;可用镜像安装&#xff08;详见下图ReadMe 蓝色字体&#xff09;&#xff0c;没安装依赖可从package-lock.json文件是否存在看出&#xff0c;存在则有依赖 解决&#xff1a;

KMP算法(算法篇)

算法之KMP算法 KMP算法 概念&#xff1a; KMP算法是用于解决字符串匹配的问题的算法&#xff0c;也就是有一个文本串和一个模式串&#xff0c;求解这个模式串是否在文本串中出现或者匹配。相对于暴力求解&#xff0c;KMP算法使用了前缀表来进行匹配&#xff0c;充分利用了之…

【Vue3】从零开始编写项目

【Vue3】从零开始编写项目 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的…