基于STM32F103的Modbus通讯(32做从机)
最近做了个实验,用STM32做从机接收主机发送过来的数据包(也是基于mobus协议),而后从机将一些数据发送给主机。
首先呢还是介绍下modbus协议。其实modbus协议你不需要了解太多。既然是要使用,那么你只需要明白一点,modbus协议就是在你要发送的数据的基础上,在数据前面加上一个帧头,数据后面加一个帧尾。嗯,是不是还是有点迷?举个例子吧。
所以主机会发过来一帧数据:01 03 00 6B 00 03 17 74(这个01是我假设主机的地址,这个域名的作用就是用来判断是否是主机发送过来的数据。因为通信过程可能因为各种原因而导致主机发送过来的数据异常,故而我们从机接收到数据之后会先对数据进行分析主机发过来的数据是否正常,正常从机再发送数据过去,异常则不对这帧数据进行响应即从机不发数据。17 74是根据01 03 00 6B 00 03计算出来的CRC校验值。)
当从机接收到这串数据,并且判断数据正常则发送一帧数据到主机:02 03 06 02 2B 00 00 00 64 11 8A(同样的02是我假设的这个从机的地址,需注意的是咱们假设自己的从机地址不要与主机的地址相同。)在这帧数据中帧头就是02 03 06,11 8A是根据02 03 06 02 2B 00 00 00 64计算出来的CRC 校验值也是帧尾。
(提供一个在线计算CRC校验值的网站:http://www.ip33.com/crc.html)
那么问题来了。。。程序中我们怎么去计算CRC校验值呢???这个嘛 ,下方我会贴上整个实验的例程,其中CRC.c中h函数unsigned int GetCRC16(unsigned char *ptr, unsigned char len)我们只需要调用这个函数就可以算出CRC校验值了。有兴趣的也可以去额外了解下CRC校验具体是怎么实现的。
整个实验例程如下:
main.c:
#include "stm32f10x.h"
#include "bsp_485.h"
#include "bsp_led.h"
#include "crc16.h"
/*描述:硬件:RS485接口 协议:Modbus RTU
*功能:采用DMA方式发送数据,中断方式接收数据。
*注: 接收到指令之后,判断是否是相应指令而进行DMA数据发送。
*/
/*DMA:开启DMA,DMA发送完一帧数据后产生发送完成中断,
* 在DMA发送完成中断中,开启USART接收中断(字节)
* 在USART接收中断中保存接收到的数据。
*注: 本程序额外开启了USART空闲中断,在空闲中断中将
* USART接收中断中接收到的数据发送至串口调试助手显示
* 并开启DMA请求
*/
extern uint8_t u3Temp;
extern uint16_t uart3_p;
extern uint16_t ReceivedUsart3Flag,tempU3,uart3_RXbuff[];
extern uint8_t SendU3Buff[SENDU3BUFF_SIZE];
uint16_t len,iU3;
uint8_t u2_Temp;
char *pbuf;
void SendUsart3Buff(void);
void SendU3DatatoDebug(void);
void load_U3_SendBuff(void);
int main(void)
{
load_U3_SendBuff();
LED_GPIO_Config();
USART3_Config();
while(1)
{
}
}
/*Description:调试通信程序专用,用于将接收到的数据再发送到串口调试助手显示*/
void SendU3DatatoDebug()
{
for(uart3_p= 1; uart3_p <= u3Temp; uart3_p++)
{
Usart_SendByte(USART3,uart3_RXbuff[uart3_p]);
}
uart3_p = 1;
RS485_RX_EN();
ReceivedUsart3Flag = 0;
}
/*将USART3需要发送的数据存放在SendU3Buff[]中*/
void load_U3_SendBuff()
{
uint16_t CRCtemp;
SendU3Buff[0]=0x01;//ID
SendU3Buff[1]=0x03;//功能码
SendU3Buff[2]=0x0C;//内容数据字节数
/*填充将要发送的数据(两个字节为一个寄存器的值)*/
SendU3Buff[3]=0x00; //数据1(slave地址0,网站上地址40001)
SendU3Buff[4]=0x64;
SendU3Buff[5]=0x00;//数据2(slave地址1,网站上地址40002)
SendU3Buff[6]=0x96;
SendU3Buff[7]=0x00;//数据3
SendU3Buff[8]=0xC8;
SendU3Buff[9]=0x00; //4
SendU3Buff[10]=0xFA;
SendU3Buff[11]=0x01;//5页面未添加该数据
SendU3Buff[12]=0x2B;
SendU3Buff[13]=0x01;//6
SendU3Buff[14]=0x5E;
CRCtemp = GetCRC16(SendU3Buff, 15);
/*CRC校验值*/
SendU3Buff[15] = CRCtemp;//存放CRCl
SendU3Buff[16] = (CRCtemp >> 8); //保存CRCh//SendBuff[i] = ((CRCtemp<<8)|(CRCtemp>>8)); //保存CRCh
}
485.c:
#include "bsp_485.h"
uint8_t SendU3Buff[SENDU3BUFF_SIZE];
static void NVIC_USART3_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
static void NVIC_DMA1_2_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
//static void NVIC_DMA1_2_Configuration(void)
//{
// NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// NVIC_Init(&NVIC_InitStructure);
//}
/**
* U3:TX:PB10 RX:PB11 TXRXEN:PD3
* U5:TX:PC12 RX:PD1 TXRXEN:PD0
*
*/
void USART3_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//TX、RX是时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);//EN时钟
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd(RCC_APB1Periph_USART3, ENABLE);//USART时钟
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* 设置485收发控制管脚为推挽输出Out_PP */
GPIO_InitStructure.GPIO_Pin = RS485_RE_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD , &GPIO_InitStructure);
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = 115200;
// 配置 针数据字长
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
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(USART3, &USART_InitStructure);
// 串口中断优先级配置
NVIC_USART3_Configuration();
// 使能串口接收中断
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
// 使能串口空闲中断(用于检测一帧数据接收完毕)
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
// 使能串口
USART_Cmd(USART3, ENABLE);
/*控制 485 芯片进入接收模式*/
RS485_RX_EN();//
}
void USART3_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
// 开启DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* 复位初始化 DMA 数据流 */
DMA_DeInit(DMA1_Channel2);
// 设置DMA外设地址:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
// 内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendU3Buff;
// 方向:从内存到外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
// 传输大小
DMA_InitStructure.DMA_BufferSize = SENDU3BUFF_SIZE;
// 外设地址不增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 内存地址自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据单位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
// 内存数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// DMA模式,一次或者循环模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// 优先级:中
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
// 禁止内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 配置DMA通道
DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);
NVIC_DMA1_2_Configuration();
//开启DMA通道的TC中断:传输完成中断
DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE);
// 使能DMA
DMA_Cmd (DMA1_Channel2,ENABLE);
}
/***************** 发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/***************** 发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{}
}
/***************** 发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
uint8_t temp_h, temp_l;
/* 取出高八位 */
temp_h = (ch&0XFF00)>>8;
/* 取出低八位 */
temp_l = ch&0XFF;
/* 发送高八位 */
USART_SendData(pUSARTx,temp_h);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
/* 发送低八位 */
USART_SendData(pUSARTx,temp_l);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/*
Desc:接收中断时,将接收到的所有数据用数组保存。
*/
//中断缓存串口数据
#define UART_BUFF_SIZE 1024
uint16_t uart3_p = 1;
uint16_t uart3_RXbuff[UART_BUFF_SIZE];
uint8_t u3Temp;
uint8_t tempU3;
uint8_t ReceivedUsart3Flag = 0;
uint16_t clr;
uint16_t a=0x00;
void bspU3_RS485_IRQHandler(void)
{
if (USART_GetITStatus( USART3, USART_IT_RXNE) != RESET) //收到一个字节的数据
{//保存接收到的数据
uart3_RXbuff[uart3_p] = USART_ReceiveData(USART3);
uart3_p++;
}
if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) //收到一帧的数据
{//将接收到的数据发送到串口调试助手上以便观察数据是否正确
u3Temp = uart3_p-1;
clr = USART3->SR;
clr = USART3->DR;
ReceivedUsart3Flag = 1;
RS485_TX_EN() ;
// for(uart3_p= 1; uart3_p <= u3Temp; uart3_p++)//调试通信程序专用,用于将接收到的数据再发送到串口调试助手显示
// {
// Usart_SendByte(USART3,uart3_RXbuff[uart3_p]);
// }
uart3_p = 1;
// LED1_OFF;
// LED2_OFF;
// LED3_OFF;
// LED4_OFF;
LED1_ON;
LED2_ON;
LED3_ON;
LED4_ON;
if(uart3_RXbuff[1]==0x01&&uart3_RXbuff[2]==0x03&&uart3_RXbuff[3]==0x00&&uart3_RXbuff[4]==0x00&&
uart3_RXbuff[5]==0x00&&uart3_RXbuff[6]==0x06&&uart3_RXbuff[7]==0xC5&&uart3_RXbuff[8]==0xC8)
// uart3_RXbuff[9]==0x00&&uart3_RXbuff[10]==0x01&&uart3_RXbuff[11]==0x03&&uart3_RXbuff[12]==0x9C&&
// uart3_RXbuff[13]==0x41&&uart3_RXbuff[14]==0x00&&uart3_RXbuff[15]==0x02&&uart3_RXbuff[16]==0x17&&
{
/* DMA发送使能 */
USART3_DMA_Config();
RS485_TX_EN() ;
USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);//这个放在最后
}
}
}
void bspU3_RS485_DMA_IRQHandler()
{
if(DMA_GetITStatus(DMA1_IT_TC2))
{
//清TC标志
DMA_ClearITPendingBit(DMA1_IT_GL2); //清除全部中断标志 //DMA_ClearFLAG(DMA1_FLAG_TC2); //清除全部中断标志(这种写法也可以)
while(USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); //等待USART1发送完成标志TC置1
USART_ClearFlag(USART3, USART_FLAG_TC); //清除发送完成标志
}
//关闭DMA通道
DMA_Cmd(DMA1_Channel2, DISABLE);
RS485_RX_EN();
}
CRC16.c:
#include "crc16.h"
unsigned int GetCRC16(unsigned char *ptr, unsigned char len)
{
uint16_t index;
uint8_t crcl = 0xFF; //高CRC字节
uint8_t crch = 0xFF; //低CRC字节
uint8_t TabH[] = { //CRC高位字节值表
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
} ;
u8 TabL[] = { //CRC低位字节值表
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
} ;
while (len--) //计算指定长度的CRC
{
index = crcl ^ *ptr++;
crcl = crch ^ TabH[index];
crch = TabL[index];
}
return ((crch<<8) | crcl);
}
嗯。。。我把工程打包了一份。。。链接如下。。。
https://download.csdn.net/download/qq_34471646/11240985