目录
一.开发环境
文章中实现的功能是单片机作为从机与modbuspoll通讯,并通过modbuspoll写入对应寄存器的值改变RGB灯的颜色和亮灭
1.硬件开发环境
主控芯片STM32F103ZET6
RS485收发芯片MAX3485CSA
硬件电路
2.软件开发环境
STM32Cube+moudbus poll+kail
如果不熟悉moudbus poll可以参考以下文章
Modbus调试软件--ModbusPoll、ModbusSlave使用详解
二.STM32CubMX相关配置
1、GPIO配置
如上图所示MAX3485CSA的控制引脚连接到单片机的PC2引脚,RE 接收器输出使能控制。当RE 接低电平时,接收器输出使能,RO 输出有效;当RE 接高电平时,接收器输出禁能,RO 为高阻态;DE 驱动器输出使能控制。DE 接高电平时驱动器输出有效,DE 为低电平时输出为高阻态;RE 接高电平且 DE 接低电平时,器件进入低功耗关断模式
2、RCC配置
3、SYS配置
4、USART2配置
5、NVIC配置
6、生成工程
三.代码部分
1、创建和编写moudbus.c和moudbus.h文件
1.moudbus.h
#ifndef __MODBUS_H
#define __MODBUS_H
#include "main.h"
#define RS485_DE_RE_GPIO_Port GPIOC
#define RS485_DE_RE_Pin GPIO_PIN_2
typedef struct /* 声明一个变量,用于管理变量 */
{
uint8_t address; /* 从机基地址 */
uint8_t send[100]; /* 发送缓冲区 */
uint8_t receive[100]; /* 接收缓冲区 */
} modbus_TypeDef;
extern modbus_TypeDef Modbus; /* 声明modbus的结构体 */
void Modbus_processing(void);
void Function_03(void);
void Function_06(void);
void Modbus_Init(void);
uint16_t Modbus_CRC16(uint8_t *buffer, uint16_t buffer_length);
#endif
2.moudbus.c
#include "modbus.h"
#include "usart.h"
//#include "gpio.h"
modbus_TypeDef Modbus; //定义结构体
/* 寄存器数据 */
uint16_t Reg[8];
/*初始化*/
void Modbus_Init(void)
{
Modbus.address = 0x01; /* 地址位 */
}
void Modbus_processing(void)
{
uint16_t crc,rccrc;
if (xUSART2.receiveNUM < 8) return;
crc = Modbus_CRC16(&Modbus.receive[0],xUSART2.receiveNUM-2); //获取到接收的数据进行计算
rccrc = (Modbus.receive[xUSART2.receiveNUM-2]<<8) + Modbus.receive[xUSART2.receiveNUM-1];
if(crc == rccrc) //校验码相同才可以进入
{
if(Modbus.receive[0] == Modbus.address) //判断基地址
{
switch(Modbus.receive[1]) //判断功能码
{
case 3: Function_03(); break;
case 6: Function_06(); break;
case 16: break;
}
}
else if(Modbus.receive[0] == 0)
{
}
}
}
/* 功能码03:主机读取从机 */
void Function_03(void)
{
uint16_t Regadd, Reglen,crc;
uint8_t i,j;
i = 0;
Regadd = (Modbus.receive[2]<<8)+Modbus.receive[3];
Reglen = (Modbus.receive[4]<<8)+Modbus.receive[5];
//开始打包数据包
Modbus.send[i++] = Modbus.address;
Modbus.send[i++] = 0x03;
Modbus.send[i++] = Reglen*2;
for(j=0;j<Reglen;j++)
{
Modbus.send[i++] = Reg[Regadd+j]>>8;
Modbus.send[i++] = Reg[Regadd+j]%256;
}
crc = Modbus_CRC16(Modbus.send,i);
Modbus.send[i++] = crc>>8;
Modbus.send[i++] = crc%256;
HAL_GPIO_WritePin(RS485_DE_RE_GPIO_Port, RS485_DE_RE_Pin, GPIO_PIN_SET);//使能485芯片
HAL_UART_Transmit_DMA (&huart2,(uint8_t*)Modbus.send,i);
}
/* 功能码06:主机写入从机 */
void Function_06(void)
{
uint16_t Regadd,crc,val;
uint16_t i;
i = 0;
Regadd = (Modbus.receive[2]<<8)+Modbus.receive[3];
val = (Modbus.receive[4]<<8)+Modbus.receive[5];
Reg[Regadd] = val;
//开始打包回应包
Modbus.send[i++] = Modbus.address;
Modbus.send[i++] = 0x06;
Modbus.send[i++] = Regadd>>8;
Modbus.send[i++] = Regadd%256;
Modbus.send[i++] = val>>8;
Modbus.send[i++] = val%256;
crc = Modbus_CRC16(Modbus.send,i);
Modbus.send[i++] = crc>>8;
Modbus.send[i++] = crc%256;
HAL_GPIO_WritePin(RS485_DE_RE_GPIO_Port, RS485_DE_RE_Pin, GPIO_PIN_SET);
HAL_UART_Transmit_DMA (&huart2,(uint8_t*)Modbus.send,i);
}
/* CRC校验 参数1:数组指针;参数2;长度 */
uint16_t Modbus_CRC16(uint8_t *buffer, uint16_t buffer_length)
{
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < buffer_length; i++)
{
crc ^= (uint16_t)buffer[i];
for (uint8_t j = 0; j < 8; j++)
{
if (crc & 0x0001)
crc = (crc >> 1) ^ 0xA001;
else
crc = crc >> 1;
}
}
crc = (crc >> 8) | (crc << 8);
return crc;
}
2、新增USART.C代码
#include "usart.h"
/* USER CODE BEGIN 0 */
#include <stdio.h> //添加头文件
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
#pragma import(__use_no_semihosting) //添加打印重定向函数
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0x02);
return ch;
}
/* USER CODE END 1 */
1、编写main.c和main.h代码
1.main.h新增部分
/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */
typedef struct /* 声明一个变量,用于管理变量 */
{
uint16_t receiveNUM; /* 接收字节数;在中段回调函数里被自动赋值;8>0即为接收到的第一帧 */
uint8_t receiveData[512]; /* 接受到的数据 */
uint8_t BuffTemp[512]; /* 接收缓存;接收到一帧的时候就会清零并(一帧)复制到ReceivedData[ ] */
} xUSARTx_TypeDef;
/* 声明串口结构体 */
extern xUSARTx_TypeDef xUSART2; /* 声明串口1的结构体 */
/* USER CODE END ET */
/* Exported constants --------------------------------------------------------*/
1.main.c新增部分
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "modbus.h" //新增头文件
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PTD */
extern uint16_t Reg[8]; //声明变量
/* USER CODE END PTD */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
xUSARTx_TypeDef xUSART2; /* 定义串口2的结构体 */
/* USER CODE END 0 */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
Modbus_Init(); //添加初始化
Modbus_processing();
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, xUSART2.BuffTemp, sizeof(xUSART2.BuffTemp)); // 开启DMA空闲中断
/* USER CODE END 2 */
while (1)
{
//在while循环里添加测试代码,以控制RGB灯为例,低电平点亮
if (Reg[0] & 0x0001) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}
else{ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);}
if (Reg[1] & 0x0002) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);}
else{ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);}
if (Reg[2] & 0x0003) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);}
else{ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);}
if (Reg[3] & 0x0004) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1 | GPIO_PIN_0| GPIO_PIN_5, GPIO_PIN_RESET);}
else{ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1 | GPIO_PIN_0| GPIO_PIN_5, GPIO_PIN_SET);}
if (Reg[4] & 0x0005) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1 | GPIO_PIN_0, GPIO_PIN_RESET);}
else{ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1 | GPIO_PIN_0, GPIO_PIN_SET);}
if (Reg[5] & 0x0006) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1 | GPIO_PIN_5, GPIO_PIN_RESET);}
else{ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1 | GPIO_PIN_5, GPIO_PIN_SET);}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart == &huart2) // 判断串口
{
__HAL_UNLOCK(huart); // 解锁串口状态
xUSART2.receiveNUM = Size; // 把接收字节数,存入结构体xUSART1.ReceiveNum
memset(Modbus.receive, 0, sizeof(Modbus.receive)); // 清除Modbus.receive前一帧接收到的数据
memset(xUSART2.receiveData, 0, sizeof(xUSART2.receiveData)); // 清除xUSART1.ReceiveData前一帧接收到的数据
memcpy(xUSART2.receiveData, xUSART2.BuffTemp, Size); // 把新数据,从临时缓存中,复制到xUSART1.ReceiveData[]
memcpy(Modbus.receive, xUSART2.BuffTemp, Size);// 把新数据,从临时缓存中,复制到Modbus.receive
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, xUSART2.BuffTemp, sizeof(xUSART2.BuffTemp)); // 再次开启DMA空闲中断; 每当接收完指定长度,或者产生空闲中断时,就会来到这个
Modbus_processing(); // 立即处理接收到的请求
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
HAL_GPIO_WritePin(RS485_DE_RE_GPIO_Port, RS485_DE_RE_Pin, GPIO_PIN_RESET);
}
}
/* USER CODE END 4 */
四.下载验证
通过USB转485转换器连接到电脑,并打开moudbuspoll软件,选择对应com口并设置对应波特率
1、读取测试
因为代上面代码中uint16_t Reg[8]是空的所以寄存器0-7的值都为0
1、写入测试
写入Reg[0]的值为0x0001
写入Reg[1]的值为0x0002
写入Reg[2]的值为0x0003
RGB灯点亮正常
查看对应寄存器的值