笔者最近需要用STM32F4读取插针式土壤温湿度传感器测量土壤的温度和湿度。该土壤温湿度传感器的输出信号是RS485信号,并采用Modbus RTU作为通信协议。被折磨许久,遂将主要学习过程进行记录,欢迎大家学习和批评指正。
硬件连接原理图
用STM32F4上的USART3引脚以及一个GPIO口PE4连接485转换芯片。其中PE4用与发送或接收使能,当STM32F4要发送数据的时候,控制PE4为高电平,数据通过TXD发送出去。当STM32F4要接收数据的时候,控制PE4为低电平,数据通过RXD接收回来。
485转换芯片简介
由于单片机采用TTL电平表示逻辑0和1,而该温湿度传感器采用RS485电平表示逻辑0和1,因此485转换芯片的作用是将TTL电平中的0和1转换成RS485电平中的0和1。RS485信号采用差分信号表示0和1,如下图所示:
(图片来源:江科大stm32教学视频USART串口协议)
如果A比B高+2v~+6v则表示逻辑0,B比A高+2v~+6v表示逻辑0。
有了485电平转换芯片后,我们只需用STM32F4上的串口发相应的符合Modbus RTU协议的数据包,就可以进行数据的写入和读取了。
Modbus RTU协议简介
Modbus RTU数据帧格式协议介绍可以参考一下这篇博客:
STM32+RS485+Modbus-RTU(主机模式+从机模式)-标准库/HAL库开发_stm32modbus主机-CSDN博客
简单来说就是:
主机问询帧结构:
地址码 | 功能码 | 寄存器起始地址 | 寄存器长度 | 校验码低位 | 校验码高位 |
1字节 | 1字节 | 2字节 | 2字节 | 1字节 | 1字节 |
从机应答帧结构:
地址码 | 功能码 | 有效字节数 | 数据一区 | 第二数据区 | 第N数据区 | 校验码 |
1字节 | 1字节 | 1字节 | 2字节 | 2字节 | 2字节 | 2字节 |
我们用串口一个字节一个字节地发送。在发送的时候需要注意帧与帧之间最小的时间间隔为发送3.5个字节的时间,以及在每一帧内部传输的每个字节之间的最大时间间隔为发送1.5个字节的时间,具体时间是多久由自己约定的波特率来计算。如下图所示:
图中一个字符表示串口传输一个字节的帧格式。如下图所示,由1个bit起始位,8bit数据位,1个bit奇偶校验位,1个bit停止位组成,在发送时从起始位开始。
(图片来源:ngitech.cn/mobile/news/show/3246.html)
串口配置
发送直接用串口一个字节一个字节地发送Modbus数据帧,接收采用串口+DMA的方式
先用接CH340芯片的USART1连接电脑进行测试。采用Modbus Slave软件,将电脑作为从机。
Modbus Slave使用方法见:Modbus测试工具ModbusPoll与Modbus Slave使用方法_modbuspoll中文版-CSDN博客
串口配置头文件:
#ifndef __MBUSART_H
#define __MBUSART_H
#include "stdio.h"
#include "stm32f4xx_conf.h"
#include "sys.h"
/*DMA接收数据缓存大小*/
#define UART_DMARX_SIZE 0xff
typedef struct
{
u8 buf[UART_DMARX_SIZE];
__IO u8 len;
} _serialbuf_st ; //串口数据结构
typedef struct
{
u8 addr;//从机地址
u8 start;//寄存器起始
u8 len; //接收到或待发送的寄存器数
u16 buf[UART_DMARX_SIZE/2];//寄存器数据
} _mbdata_st; //用户数据
extern _serialbuf_st serialRXbuf_st;
extern _serialbuf_st serialTXbuf_st;
void Usart1_init(u32 baud) ;
void WaitForTransmitComplete(USART_TypeDef* USARTx) ;
void myUSART_Sendbyte(USART_TypeDef* USARTx, uint16_t Data) ;
void myUSART_Sendstr(USART_TypeDef* USARTx, const char *s) ;
void myUSART_Sendarr(USART_TypeDef* USARTx, u8 a[] ,uint8_t len);
#endif
串口配置C文件
#include "Mb_usart.h"
#include "string.h"
_serialbuf_st serialRXbuf_st;
_serialbuf_st serialTXbuf_st;
/*DMA接收数据缓存*/
u8 g_uart1DmaRXBuf[UART_DMARX_SIZE];
void myUSART_Sendbyte(USART_TypeDef* USARTx, uint16_t Data)
{
while((USARTx->SR&0X40)==0);
USARTx->DR = (Data & (uint16_t)0x01FF);
}
void myUSART_Sendstr(USART_TypeDef* USARTx, const char *s)
{
while(*s != '\0')
{
myUSART_Sendbyte( USARTx, *s) ;
s++;
}
}
void myUSART_Sendarr(USART_TypeDef* USARTx, u8 a[] ,uint8_t len)
{
uint8_t i=0;
while(i < len )
{
myUSART_Sendbyte( USARTx, a[i]) ;
i++;
}
}
/*
Usart1初始化
*/
void Usart1_init(u32 baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = baud;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
DMA_DeInit(DMA2_Stream5);
while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE){}//等待DMA可配置
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(USART1->DR));//DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)g_uart1DmaRXBuf;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = UART_DMARX_SIZE;//数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 ,只要串口接收到字节就开始搬移数据,不然第二次发送数据的时候产生不了DMA请求
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
DMA_Init(DMA2_Stream5,&DMA_InitStructure);
USART_ITConfig(USART1,USART_IT_TC,DISABLE); //关闭中断
USART_ITConfig(USART1,USART_IT_RXNE,DISABLE); //当接收到1个字节,会产生USART_IT_RXNE中断,关闭
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); //开启USART_IT_IDLE中断,即空闲中断检测,空闲中断是在监测到数据接收后(即串口的RXNE位被置位)开始检测,当总线上在一个字节对应的周期内未再有新的数据接收时,触发空闲中断IDLE位被硬件置1.
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //
NVIC_Init(&NVIC_InitStructure);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
USART_Cmd(USART1, ENABLE);
DMA_Cmd(DMA2_Stream5,ENABLE);
memset( & serialRXbuf_st ,0, sizeof ( serialRXbuf_st ) ) ;//结构体清0
}
//等待发送完成
void WaitForTransmitComplete(USART_TypeDef* USARTx)
{
while((USARTx->SR&0X40)==0){};
}
/*
说明:串口中断,DMA与空闲中断处理,用于串口接收
*/
void USART1_IRQHandler(void)//接收总线空闲时进入一次中断,每接收一个字节产生一次DMA请求,NDTR计数器的值减1,temp的值则为已接收的字节数。
{
_serialbuf_st *p= &serialRXbuf_st;
__IO u8 temp = 0;
u8 i=0;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
temp = USART1->SR;//清中断
temp = USART1->DR;
DMA_Cmd(DMA2_Stream5,DISABLE);
temp = UART_DMARX_SIZE - ((uint16_t)(DMA2_Stream5->NDTR));//得到DMA传输数据的长度(字节个数)
for (i = 0;i < temp;i++)
{
p->buf[i] =g_uart1DmaRXBuf[i];//赋值到全局变量serialRXbuf_st->buf中
}
p->len = temp ;//接收的字节个数
DMA_SetCurrDataCounter(DMA2_Stream5,UART_DMARX_SIZE);//重置NDTR计数器
DMA_Cmd(DMA2_Stream5,ENABLE);
}
GPIO_SetBits(GPIOC,GPIO_Pin_4);
__nop();
}
代码注释非常详细,此处就不过多描述,请直接看代码即可。
发送0x03功能码并接收从机应答信息
头文件配置:
#ifndef __MDBS_FUNC_H
#define __MDBS_FUNC_H
#include "stdio.h"
#include "stm32f4xx_conf.h"
#include "sys.h"
#include "Mb_usart.h"
#include "crc16.h"
#include "delay.h"
//sp3485控制端口
#define Mdbus_CTRL PEout(4) // 拉高发送数据,拉低接收数据
//#define LED1 PCout(0) // DS1
#define res_OK 0
#define res_ERR1 1
#define res_ERR2 2
#define res_ERR3 3
#define res_CRCERR 4
u8 mb_recv_readHoldingReg( _mbdata_st *mbp);
void mb_sent_writeHoldingReg( const _mbdata_st mbp);
void Mdbus_CTRL_Init(void);//初始化
#endif
C文件:
#include "mdbs_func.h"
/*
说明:
接收“读保持寄存器”的结果
命令0X03
返回:
res_OK 正确
res_ERR1 其他错误
res_ERR2 地址不符
res_ERR3 无反馈
res_CRCERR CRC错误
*/
u8 mb_recv_readHoldingReg( _mbdata_st *mbp)
{
u16 temp;//存放CRC校验码
u16 crc_r;//存放接收到的CRC校验码
u8 i;
Mdbus_CTRL = 0;
//delay_ms(10);
if( serialRXbuf_st.len == 0 ) return res_ERR3;
serialRXbuf_st.len=0;
if( mbp->addr == serialRXbuf_st.buf[0] )
{
if( 0x03 == serialRXbuf_st.buf[1] )
{
for(i=0;i<serialRXbuf_st.buf[2]/2;i++)
{
mbp->buf[i]= (u16)(serialRXbuf_st.buf[i*2+3]<<8) + serialRXbuf_st.buf[i*2+4];
}
mbp->len = serialRXbuf_st.buf [2]/2;//寄存器数
temp=mc_check_crc16(serialRXbuf_st.buf, 3 + serialRXbuf_st.buf [2]);//0x03功能码发送时,校验长度6个字节
crc_r =(u16)(serialRXbuf_st.buf[serialRXbuf_st.buf[2] + 3] << 8) + serialRXbuf_st.buf[serialRXbuf_st.buf[2] + 4];
if(temp != crc_r)
{
return res_CRCERR;
}
else
{
return res_OK;
}
}
else
{
return res_ERR1;
}
}
return res_ERR2;
}
//sp3485控制输入输出使能引脚初始化
void Mdbus_CTRL_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOF时钟
//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOE, GPIO_Pin_4);//GPIOF9,F10设置高,灯灭
}
/*
发送"写保持寄存器",命令0X03
*/
void mb_sent_writeHoldingReg( const _mbdata_st mbp)
{
u8 len = mbp.len;
u16 temp;//存放CRC校验码
Mdbus_CTRL = 1;
delay_ms(10);
serialTXbuf_st.buf[0] = mbp.addr;//0x01
serialTXbuf_st.buf[1] = 0x03;//功能码
serialTXbuf_st.buf[2] = mbp.start>>8;//起始地址高八位
serialTXbuf_st.buf[3] = mbp.start;//起始地址低八位
serialTXbuf_st.buf[4] = 0x00;//00
serialTXbuf_st.buf[5] = len;//寄存器个数01
// serialTXbuf_st.buf[6] = 0xD5;//CRC
// serialTXbuf_st.buf[7] = 0xCA;
temp=mc_check_crc16(serialTXbuf_st.buf, 6);//0x03功能码发送时,校验长度6个字节
serialTXbuf_st.buf[6] = temp>>8; //CRC16低位在前,mc_check_crc16函数已自动完成调换
serialTXbuf_st.buf[7] = temp;//高
myUSART_Sendarr( USART3, serialTXbuf_st.buf , 8) ;
WaitForTransmitComplete(USART3) ; //发送完成
Mdbus_CTRL = 0;
}
CRC16校验
这里采用的是正点原子编写的CRC16校验代码,头文件声明一下函数即可
#include "crc16.h"
/CRC8&CRC16表
//CRC16校验 高位字节表
const u8 CRC16HiTable[]=
{
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
//CRC16校验低位字节表
const u8 CRC16LoTable[]=
{
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
//使用CRC16校验
//计算方式:
//buf:待校验缓冲区首地址
//len:要校验的长度
//返回值:CRC16校验值(高字节在前,低字节在后)
u16 mc_check_crc16(u8 *buf,u16 len)
{
u8 index;
u16 check16=0;
u8 crc_low=0XFF;
u8 crc_high=0XFF;
while(len--)
{
index=crc_high^(*buf++);
crc_high=crc_low^CRC16HiTable[index];
crc_low=CRC16LoTable[index];
}
check16 +=crc_high;
check16 <<=8;
check16+=crc_low;
return check16;
}
以上就是所有配置,先用Modbus Slave进行测试
主函数
int main(void)
{
u8 i=0;
//定义要发送的信息
_mbdata_st mbp_s;
_mbdata_st mbp_r;
mbp_s.addr = 0x01;
mbp_s.start = 0x01;
mbp_s.len = 0x01;
mbp_r.addr = mbp_s.addr;
mbp_r.start = mbp_s.start;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //延时初始化
// uart_init(115200); //串口初始化波特率为115200
// uart3_init(4800);
LED_GPIO_Config(); //初始化与LED连接的硬件接口
Mdbus_CTRL_Init();
Usart1_init(9600);
mb_sent_writeHoldingReg(mbp_s);//发送数据
delay_ms(20);
while(mb_recv_readHoldingReg(&mbp_r) != res_OK)//接收数据
{
delay_ms(20);
mb_sent_writeHoldingReg(mbp_s);
}
while(1)
{
delay_ms(500);
GPIO_ResetBits(GPIOC,GPIO_Pin_0); //LED0对应引脚GPIOF.9拉低,亮 等同LED0=0;
GPIO_SetBits(GPIOA,GPIO_Pin_7); //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1;
delay_ms(500);
GPIO_SetBits(GPIOC,GPIO_Pin_0); //LED0对应引脚GPIOF.0拉高,灭 等同LED0=1;
GPIO_ResetBits(GPIOA,GPIO_Pin_7); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0;
for(i = 0; i<mbp_r.len;i++)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(USART1, mbp_r.buf[i]>>8);//先传高八位
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(USART1, mbp_r.buf[i]& (uint8_t)0xFF);//后传低八位
}
}
}
Modbus Slave配置信息
程序中我们发送的数据为01 03 0001 0001 D5CA
再打开串口调试助手(注意波特率一致),可以看到读取到地址0001寄存器的值为0x0002,与Modbus Slave中设置的值一致,测试通过。
将usart1的配置改为usart3的置,即可实现对土壤温湿度传感器的读取。
总结
本文实现了03功能码的发送与接收。由于对速度要求不高,因此没有采用定时器来严格计算每次发送间隔,只采用delay函数粗略保证发送时间间隔大于3.5个字节时间,如果各位有需要可以自行添加。另外,还有一些其他功能码诸如0x06和0x10,参考0x03功能码的收发也可以同理写出来了。
代码参考博客