STM32F4作Modbus RTU协议主机完成0x03功能码的收发

笔者最近需要用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功能码的收发也可以同理写出来了。

代码参考博客

STM32-modbus rtu 之主机程序_stm32 modbus主机程序-CSDN博客

  • 11
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值