·
近期看了硬石电子的关于modbus rtu代码,写的很亲民,便于理解,在这里做一下笔记,方便以后查看。
一共需要移植
两个.c文件:bsp_usartx_fifo.c、modbus_slave.c
两个.h文件:bsp_usartx_fifo.h、modbus_slave.h
放置两个函数:Usart_FIFO_Init(); MODS_Poll();
开启三个宏:#define USART1_FIFO_EN 1//使能初始化串口1
__________ #define USART_SELECT_NUM 1//将modbus用在串口1
__________ #define HBAUD485 USART1_BAUD//设置串口波特率
在单片机上使用modbus rtu,肯定是通过串口UART进行通讯(485的话就是多控制一个引脚的高低电平变化),那第一步便是进行串口的初始化,modbus rtu协议帧是对时间严格把控的,那怎么能少的了定时器,紧跟着的便是定时器初始化。
(1)将Usart_FIFO_Init()放在主函数的初始化位置,
#include "stm32f10x.h"
#include "bsp_usartx_fifo.h"
#include "modbus_slave.h"
/*主函数*/
int main(void)
{
Usart_FIFO_Init();
while ( 1 )
{
MODS_Poll();
}
}
(2)进入Usart_FIFO_Init(),看看有哪些需要初始化:
void Usart_FIFO_Init(void)
{
/* 初始化串口相关的变量 */
UsartVarInit();
/* 初始化串口相关的变量 */
RS485_InitTXEN();
/* 配置NVIC,设定USART接收中断优先级 */
NVIC_Configuration_USART();
/* 初始化USART对应GPIO和USART外设 */
InitHardUsart();
/* 定时器初始化 */
bsp_InitHardTimer();
}
a.首先UsartVarInit()的作用是对串口相关的结构体变量初始化,其中需要注意的是宏定义USART1_FIFO_EN的开关,这里我使用UART1做的测试,将该宏置1来开启串口1,然后#define USART_SELECT_NUM 1//将modbus用在串口1上;这里分为两部分,为的是方便对每个串口的单独配置,可以对其他串口添加需要的应用代码。
static void UsartVarInit(void)
{
#if USART1_FIFO_EN == 1
g_tUsart1.usart = USART1; /* STM32 串口设备 */
g_tUsart1.pTxBuf = g_TxBuf1; /* 发送缓冲区指针 */
g_tUsart1.pRxBuf = g_RxBuf1; /* 接收缓冲区指针 */
g_tUsart1.usTxBufSize = USART1_TX_BUF_SIZE; /* 发送缓冲区大小 */
g_tUsart1.usRxBufSize = USART1_RX_BUF_SIZE; /* 接收缓冲区大小 */
g_tUsart1.usTxWrite = 0; /* 发送FIFO写索引 */
g_tUsart1.usTxRead = 0; /* 发送FIFO读索引 */
g_tUsart1.usRxWrite = 0; /* 接收FIFO写索引 */
g_tUsart1.usRxRead = 0; /* 接收FIFO读索引 */
g_tUsart1.usRxCount = 0; /* 接收到的新数据个数 */
g_tUsart1.usTxCount = 0; /* 待发送的数据个数 */
g_tUsart1.SendBefor = RS485_SendBefor; /* 发送数据前的回调函数 */
g_tUsart1.SendOver = RS485_SendOver; /* 发送完毕后的回调函数 */
g_tUsart1.ReciveNew = RS485_ReciveNew; /* 接收到新数据后的回调函数 */
#endif
...
}
b.其次是对串口驱动的配置,初始化了485的引脚,配置中断优先级,配置串口引脚波特率等。
RS485_InitTXEN();
NVIC_Configuration_USART();
InitHardUsart();
c.为简化文件的个数,没有建立tim.c,直接将定时器初始化放在了串口初始化的末尾处bsp_InitHardTimer()。分频系数72-1,时钟周期1us。
将MODS_Poll()放在主函数的while(1)中;
/*主函数*/
int main(void)
{
Usart_FIFO_Init();
while ( 1 )
{
MODS_Poll();
}
}
使用modbus从机的最终目的是响应来自主机的命令,主机命令都是按照功能码区别的,因此只需要编写编写对应的应用函数,添加所需要的寄存器地址及数值便可,这里以03 06 10功能码为例,分别编写读寄存器,写寄存器函数,添加寄存器的数量只需要添加case SLAVE_REG_P01…的宏定义,同时添加对应的结构体变量 uint16_t P01…,结构体变量只需要在需要的地方赋值即可。
/*
*********************************************************************************************************
* 函 数 名: MODS_ReadRegValue
* 功能说明: 读取保持寄存器的值
* 形 参: reg_addr 寄存器地址
* reg_value 存放寄存器结果
* 返 回 值: 1表示OK 0表示错误
*********************************************************************************************************
*/
static uint8_t MODS_ReadRegValue(uint16_t reg_addr, uint8_t *reg_value)
{
uint16_t value;
switch (reg_addr) /* 判断寄存器地址 */
{
case SLAVE_REG_P01:
value = g_tVar.P01;
break;
case SLAVE_REG_P02:
value = g_tVar.P02; /* 将寄存器值读出 */
break;
case SLAVE_REG_P03:
value = g_tVar.P03;
break;
default:
return 0; /* 读取寄存器超界,参数异常,返回 0 */
}
reg_value[0] = value >> 8;
reg_value[1] = value;
return 1; /* 读取成功 */
}
/*
*********************************************************************************************************
* 函 数 名: MODS_WriteRegValue
* 功能说明: 读取保持寄存器的值
* 形 参: reg_addr 寄存器地址
* reg_value 寄存器值
* 返 回 值: 1表示OK 0表示错误
*********************************************************************************************************
*/
static uint8_t MODS_WriteRegValue(uint16_t reg_addr, uint16_t reg_value)
{
switch (reg_addr) /* 判断寄存器地址 */
{
case SLAVE_REG_P01:
g_tVar.P01 = reg_value; /* 将值写入保存寄存器 */
break;
case SLAVE_REG_P02:
g_tVar.P02 = reg_value; /* 将值写入保存寄存器 */
break;
case SLAVE_REG_P03:
g_tVar.P03 = reg_value;
break;
default:
return 0; /* 参数异常,返回 0 */
}
return 1; /* 读取成功 */
}
/* 03H 读保持寄存器(内部寄存器) */
/* 06H 写保持寄存器(内部寄存器) */
/* 10H 写多个保存寄存器(内部寄存器) */
#define SLAVE_REG_P01 0x0301
#define SLAVE_REG_P02 0x0302
#define SLAVE_REG_P03 0x0303
typedef struct
{
/* 03H 06H 读写保持寄存器 */
uint16_t P01;
uint16_t P02;
uint16_t P03;
/* 04H 读取模拟量寄存器 */
uint16_t A01;
/* 01H 05H 读写单个强制线圈 */
uint16_t D01;
uint16_t D02;
uint16_t D03;
uint16_t D04;
}VAR_T;
链接:https://pan.baidu.com/s/1XTeeJrtT3iE9qAbe7pIrlQ
提取码:yja1