MODBUS开发实战记录 、STM32 hal库通讯控制 485是什么、STM32开发MODBUS 开发485传感器--实战项目 第0、第一章串口485协议通讯

第零章-STM32工程模板

0.1-新建工程在这里插入图片描述

调试器
在这里插入图片描述
晶振
在这里插入图片描述
在这里插入图片描述
设置PC13 输出
在这里插入图片描述英文保存路径
在这里插入图片描述
在这里插入图片描述
生成代码
在这里插入图片描述

0.2-点灯

在这里插入图片描述
编译-烧录-测试

0.3-工程模板

添加四个文件
在这里插入图片描述
内容如下
CallBack.c

//此文件主要放自己重新实现的回调函数

/* Includes ------------------------------------------------------------------*/
#include "MyApp.h"
/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/


/* Public variables-----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/      



/********************************************************
  End Of File
********************************************************/

MyApp.h

#ifndef  __MyApp_H_
#define  __MyApp_H_
//这个文件包含所有使用的.h 文件
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"


#endif
/********************************************************
  End Of File
********************************************************/

Public.c

//这个文件主要放公共文件
/* Includes ------------------------------------------------------------------*/
#include "MyApp.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/

/* Public variables-----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/      


/********************************************************
  End Of File
********************************************************/

Public.h

#ifndef __PUBLIC_H_
#define __PUBLIC_H_
//放公共变量
#include "MyApp.h"

/* Public define-------------------------------------------------------------*/

/* extern variables-----------------------------------------------------------*/


#endif
/********************************************************
  End Of File
********************************************************/

编译一下、0错误无警告

第一章-485相关

1.1-串口外设初始化和接收

串口一用于打印调试:波特率:115200。

串口二连接485电平转换芯片,用于modbus通信

串口一设置截图:
在这里插入图片描述
串口二设置截图:

发送:使用DMA+TC中断

接收:使用DMA+空闲中断

这里的优先级应该调整到最高!!!
在这里插入图片描述
开启的中断

有部分DMA中断软件无法关闭可以自己写代码关闭

https://shequ.stmicroelectronics.cn/thread-615792-1-1.html

在main之前开启

在这里插入图片描述

  __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);//使能串口2的空闲中断
  HAL_UART_Receive_DMA(&huart2,UART2.pRec_Buffer,UART2_Rec_LENGTH);//串口2开启DMA接收

新建UART.h、在其中创建结构体类型

#ifndef __UART_H_
#define __UART_H_
#include "MyApp.h"


typedef struct
{

	uint8_t* pSend_Buffer;//发送缓存指针
	uint8_t* pRec_Buffer;//接收缓存指针
	
	void (*SendArray)(uint8_t*,uint16_t);//串口发送数组函数
	void (*SendString)(uint8_t*);//发送字符串函数
	
	void (*RS485_Set_SendMode)(void);//设置485为发送模式
	void (*RS485_Set_RecMode)(void);//设置485接收模式

} UART_t;




/* extern variables-----------------------------------------------------------*/

/* extern function prototypes-------------------------------------------------*/


#endif
/********************************************************
  End Of File
********************************************************/

创建UART2.h文件、

#ifndef __UART2_H__
#define __UART2_H__

/* define ------------------------------------------------------------------*/
#include "MyApp.h"
//#include "UART.h"


#define UART2_Send_LENGTH	20	//发送缓冲长度
#define UART2_Rec_LENGTH	20	//接收缓冲长度

/* extern variables-----------------------------------------------------------*/

extern UART_t UART2;					//声明结构体类型变量
void  UART2_VarInit(void);				//初始化



#endif


/********************************************************
  End Of File
********************************************************/


新建UART2.c 、

/* Includes ------------------------------------------------------------------*/
#include "MyApp.h"
/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/

static uint8_t ucSend_Buffer[UART2_Send_LENGTH] = {0x00};//发送缓冲区
static uint8_t ucRec_Buffer [UART2_Rec_LENGTH] = {0x00};//接收缓冲区

/* Private function prototypes------------------------------------------------*/

static void SendArray(uint8_t*,uint16_t);	//串口发送数组
static void SendString(uint8_t*);			//串口发送字符串

static void RS485_Set_SendMode(void);		//设置485为发送模式
static void RS485_Set_RecMode(void);		//设置485为接收模式

/* Public variables-----------------------------------------------------------*/
UART_t UART2 = {0};							//定义并初始化结构类型变量
/*******************
*  @brief  赋值结构体变量
*  @param  
*  @return  
*
*******************/
void UART2_VarInit(void)
{
	UART2.pSend_Buffer = ucSend_Buffer;
	UART2.pRec_Buffer = ucRec_Buffer;
	
	UART2.SendArray = SendArray;
	UART2.SendString = SendString;

	UART2.RS485_Set_RecMode = RS485_Set_RecMode;
	UART2.RS485_Set_SendMode = RS485_Set_SendMode;
	
}
/*******************
*  @brief  串口发送数组
*  @param  
*  @return  
*
*******************/

static void SendArray(uint8_t* pArray,uint16_t Len)
{



}
/*******************
*  @brief  发送字符串
*  @param  
*  @return  
*
*******************/
static void SendString(uint8_t* pString)
{
	HAL_UART_Transmit(&huart2,pString, strlen((const char *)pString), 0xFF);

}

/*******************
*  @brief  设置485为发送模式
*  @param  
*  @return  
*
*******************/
static void RS485_Set_SendMode(void)
{
	

}
/*******************
*  @brief  设置485接收模式
*  @param  
*  @return  
*
*******************/
static void RS485_Set_RecMode(void)
{


}

/********************************************************
  End Of File
********************************************************/

在stm32f1xx_if.c的串口全局中断里面检测串口空闲中断标志位、调用串口空闲回调函数

在这里插入图片描述

  //判断串口空闲中断标志位
  if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET)
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除串口空闲中断标志位
		HAL_UART_IdleCallBack(&huart2);		//调用串口空闲回调函数
	}

我们在CallBack.c 创建串口空闲回调函数

在这里插入图片描述

/*******************
*  @brief  自己实现的串口空闲回调函数
*  @param  UART_HandleTypeDef * huart 处理的串口
*  @return  
*
*******************/
void HAL_UART_IdleCallBack(UART_HandleTypeDef * huart)
{
	if(huart->Instance == huart2.Instance)
	{
		//这里解析->解析时候可以关闭DMA接收
		HAL_UART_DMAStop(&huart2);//这是关闭DM接收
		HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
		UART2.SendString(UART2.pRec_Buffer);		
		HAL_UART_Receive_DMA(&huart2,UART2.pRec_Buffer,UART2_Rec_LENGTH);//开启DMA接收
	}
}

我们在CallBack.h

#ifndef __CallBack_H__
#define __CallBack_H__
/* Includes ------------------------------------------------------------------*/
#include "MyApp.h"

/* extern variables-----------------------------------------------------------*/

/* extern function prototypes-------------------------------------------------*/
//串口空闲中断的回调函数
extern void HAL_UART_IdleCallBack(UART_HandleTypeDef * huart);

#endif
/********************************************************
  End Of File
********************************************************/

然后不要忘记添加初始化代码

在这里插入图片描述

  UART2_VarInit();//初始化参数

现象 发送字符串(要有回车换号结束),然后会返回字符串、

在这里插入图片描述

1.2-串口外设发送

我们先用LED表示485设置引脚标志串口处于发送或者接收状态

在这里插入图片描述

/*******************
*  @brief  串口发送数组
*  @param  
*  @return  
*
*******************/

static void SendArray(uint8_t* pArray,uint16_t Len)
{
	UART2.RS485_Set_SendMode();//设置485芯片为发送模式
	HAL_UART_Transmit_DMA(&huart2, pArray, Len);//使用DMA发送
}
/*******************
*  @brief  发送字符串
*  @param  
*  @return  
*
*******************/
static void SendString(uint8_t* pString)
{
	UART2.RS485_Set_SendMode();//设置485芯片为发送模式
	HAL_UART_Transmit(&huart2,pString, strlen((const char *)pString), 0xFF);

}

/*******************
*  @brief  设置485为发送模式
*  @param  
*  @return  
*
*******************/
static void RS485_Set_SendMode(void)
{
	HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);
	HAL_Delay(1);
}
/*******************
*  @brief  设置485接收模式
*  @param  
*  @return  
*
*******************/
static void RS485_Set_RecMode(void)
{
	HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);
	HAL_Delay(1);
}

/********************************************************
  End Of File
********************************************************/

在TC中断中添加设置485为接收模式的函数

因为在发送完之后要马上设置485芯片为接收模式

这是弱定义的函数

在这里插入图片描述

我们在CallBack.c中重新实现

在这里插入图片描述

/**
  * @brief  Tx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance == huart2.Instance)
	{
		UART2.RS485_Set_RecMode();
	}
}

注释掉这个

在这里插入图片描述

这里我们测试发现,发送字符串后STM32 就会卡死,那么我们如何调试SMT32卡死的具体位置那

主函数点灯方便调试

设置硬件仿真

在这里插入图片描述

开启仿真->全速运行(如果点击全速运行,没有运行需要按一下复位键

然后点击串口发送后灯就不闪了,

在这里插入图片描述

可以查看当前死在那个中断里面

在这里插入图片描述

在这里有中断号

在这里插入图片描述

这里我们就找到是串口中断调用了延时函数导致异常的

我们要把这个去掉

在这里插入图片描述

我们初始化一个其他引脚作为485芯片模式控制引脚

发送也更改为使用DMA发送

在这里插入图片描述

HAL_UART_Transmit_DMA(&huart2,pString, strlen((const char *)pString));

然后给单片机接上485模块和电脑接TTL转485模块进行通信

记录使用485芯片发送1234

这是接到A上 ,不接B ,不接GND(单片机发送数据)

在这里插入图片描述

这是接到A 也接到B上(单片机发送数据)

在这里插入图片描述

这是也接GND 和AB(单片机发送数据)

这是接B 不接A 接GND(单片机发送数据)

总结发现好像是A电平和TTL传输的一样的,然后B好像是相反的

A在空闲是高电平,B在空闲是低电平

其实原理图设置的时候可以给A加上拉电阻,给B 加下拉电阻

在这里插入图片描述

然后我们做一下、电脑发送数据到单片机

无论电脑发送单片机还是单片机发送电脑,都是A线 传输的信号和TTL中发送线TX电平一致,而B就是A的反向

在这里插入图片描述

模块暂时使用淘宝的具有自动切换功能的485转换模块

1.3-认识MODBUS协议

信号传输的物理层

485是物理层对01 信号传输方式的规定:使用差分信号传输、是一个半双工的部分485信号是有方向控制引脚的。

在这里插入图片描述

主从通信

在这里插入图片描述

MODBUS协议规定

MODBUS为了提供通用性、增加了对数据包格式的规定,规定如下

地址码:主要是从机地址,

0 : 0地址是广播指令

1-255:从机使用地址,掉电保持的

功能码:要执行的功能,协议里面规定了常用的功能码,

0x03 保持寄存器

0x04输入寄存器

0x06单个保持寄存器

0x10多个保持寄存器

这里补充什么是保持寄存器什么是输入寄存器:

保持寄存器:主要是参数存储掉电不丢失,比如地址、波特率等

输入寄存器:主要是保存一些A/D转化结果,比如传感器的温湿度数据,光照

数据区:读写寄存器的地址,或者读写的数据

校验码: 高位校验+低位校验

在这里插入图片描述

O3功能码的介绍

主要是 地址码(8bit)+功能码(8bit)+寄存器起始地址+寄存器个数(读寄存器个数)+CRC校验

在这里插入图片描述

06写单个寄存器向从机写单个数据

在这里插入图片描述

16写多个寄存器向从机写多个数据

在这里插入图片描述

1.4-测试MODBUS传感器

我们下面用电脑串口软件+485转USB模块+MODBUS传感器

完成电脑读取传感器数据和设置传感器地址等

先看手册对寄存器的描述

主要说用可读可写的保持寄存器保存模块的参数,比如地址,波特率等掉电不丢失

只读的输入寄存器保存保存的是传感器数据,比如温湿度

通过设置地址+寄存器地址就可以访问他们

!在这里插入图片描述

我们查看的例子是

如何读输入寄存器

注意校验码,主机发送的 低字节在前,高字节在后,

设备回应的 高字节在前面,低字节在后面

注意上面的校验码

http://www.ip33.com/crc.html

然后我们测试一下

在这里插入图片描述

如何写单个保存寄存器

主机发送的:写设备地址+写功能码+写寄存器高字节+写寄存器低字节+数据高字节+数据低字节+CRC16校验 高字节+CRC校验低字节

注意这个是.CRC的高字节在前,低字节在后

写之后他们给回应的

设备地址+功能码+寄存器地址高字节+寄存器地址低字节+数据位高字节+数据位低字节+CRC16校验高字节+CRC16校验低字节

在这里插入图片描述

举例设置设备地址然后读数据

1.设置新地址,然后断电重启

在这里插入图片描述

2.然后新地址读数据

在这里插入图片描述

方便后面调试,咱们还是把地址设置为01

然后断电上电,地址就是01了、可以再读一下温湿度数据、能够设置波特率了
在这里插入图片描述

1.5-编写STM32代码

STM32编写MODBUS的传感器驱动流程:

1.使用电脑串口助手+USB转485模块作为辅助,调试单片机的程序、就是电脑模拟传感器发单片机看单片机是否可以解析。

2.然后再接上传感器到单片机,

电脑模拟传感器

串口一:设置115200用于打印输出调试

串口二:设置9600用于连接modbus 传感器

在这里插入图片描述

方标调试,使用串口一实现printf

在这里插入图片描述

/**
* @brief 重定向printf (重定向fputc),
					使用时候记得勾选上魔法棒->Target->UseMicro LIB 
					可能需要在C文件加typedef struct __FILE FILE;
					包含这个文件#include "stdio.h"
* @param 
* @return 
*/
int fputc(int ch,FILE *stream)
{
	HAL_UART_Transmit(&huart1,( uint8_t *)&ch,1,0xFFFF);
	return ch;
}

勾选

在这里插入图片描述

在myAPP.h添加

#include "stdio.h"

硬件连接:

单片机串口一>USB转TTL->电脑USB口

单片机串口二->TTL转485->485转USB->电脑USB口

然后我们的程序是

在这里插入图片描述

float  Temp = 0;
uint8_t Humidty = 0;//定义温度、湿度
uint8_t Modbus_SendArray[] = {0x01,0x04,0x00,0x00,0x00,0x02,0x71,0xCB};//要发送的数据
	HAL_Delay(1000);
	HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
	printf("串口一测试发送数据:%.1f %d",Temp,Humidty);
	//串口发送读取温湿度
	UART2.SendArray(Modbus_SendArray,8);

在这里插入图片描述

上面说明单片机发送16进制指令没有问题,下面测试单片机接收16进制,然后再发回来

解析函数内容先写把接收的数据都返回回去

在这里插入图片描述

#ifndef  __MODBUS_H__
#define __MODBUS_H__

#include "MyApp.h"
//声明一下变量
extern uint8_t ucSend_Buffer[UART2_Send_LENGTH];//发送缓冲区
extern uint8_t ucRec_Buffer [UART2_Rec_LENGTH] ;//接收缓冲区
 
void MODBUS_ProtocolAnalysis(UART_t * UART);



#endif

#include "MyApp.h"
/*******************
*  @brief  MODBUS协议解析
*  @param  某个串口的的结构体变量地址
*  @return  无
*
*******************/
void MODBUS_ProtocolAnalysis(UART_t * UART)
{
    UART2.SendArray(ucRec_Buffer,sizeof(ucRec_Buffer));

}

然后把协议解析函数在空闲中断调用

在这里插入图片描述

发送什么就会收到,使用串口二的串口助手查看

在这里插入图片描述

然后我们编写 MODBUS解析函数的内容了

1.先判断地址对不。

2.然后进行CRC校验,校验把高字节在前和低字节在前的情况都写了

3.然后根据位置 把温度高位字节左移八位,和低位相加

void MODBUS_ProtocolAnalysis(UART_t * UART)
{

	//UART2.SendArray(UART->pRec_Buffer,sizeof(UART->pRec_Buffer));
	
	//UART2.SendArray(ucRec_Buffer,sizeof(ucRec_Buffer));
	//停止串口二DMA接收
	HAL_UART_DMAStop(&huart2);
	//接收后的流程:地址对,CRC校验,校验对就看功能码,然后根据位读数据
	
	if(*(UART->pRec_Buffer+0) != TEMP_HUM_ADDR)
	{
		return ;
	}
	else if(*(UART->pRec_Buffer+0) == TEMP_HUM_ADDR)//地址正确
	{
		//计算CRC
		CRC_16.CRC_Value = CRC_16.CRC_Check(UART->pRec_Buffer,7);//根据接收到数据的前6个字节计算CRC值
		CRC_16.CRC_H = (CRC_16.CRC_Value >> 8);                  	//计算出高位和低位CRC
		CRC_16.CRC_L = CRC_16.CRC_Value;
		
		//校验CRC值
		if( ((*(UART->pRec_Buffer+7) == CRC_16.CRC_L) && (*(UART->pRec_Buffer+8) == CRC_16.CRC_H)) //CRC 高位在前或者低位在前都可以
														||
		 ((*(UART-> pRec_Buffer+7) == CRC_16.CRC_H) && (*(UART->pRec_Buffer+8) == CRC_16.CRC_L)))	
		 {
			Temp = ((*(UART-> pRec_Buffer+3) << 8) + *(UART-> pRec_Buffer+4))*0.1;//计算温度
			Humidty = ((*(UART-> pRec_Buffer+5) << 8) + *(UART-> pRec_Buffer+6))*0.1;//计算湿度
		 }
	}
	
	//清缓存
	for(uint8_t i=0;i<UART2_Rec_LENGTH;i++)
	{
		*(UART->pRec_Buffer+i) = 0x00;
	}
}

1.6-连接传感器测试

然后不要忘记出函数发送查询指令

在这里插入图片描述

float  Temp = 0;
uint8_t Humidty = 0;//定义温度、湿度
uint8_t Modbus_SendArray[] = {0x01,0x04,0x00,0x00,0x00,0x02,0x71,0xCB};//要发送的数据
while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

	HAL_Delay(2000);
	HAL_Delay(2000);
	HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
	printf("串口一测试发送数据:%.1f %d",Temp,Humidty);
	//串口发送读取温湿度
	UART2.SendArray(Modbus_SendArray,8);//单片机发送读传感器命令
	
  }

然后接线

单片机串口二->TTL转485模块->485传感器

单片机串口一->TTL转USB->电脑

在这里插入图片描述

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值