MODBUS移植STM32,分别配置STM32做从机和主机

关注
MODBUS移植STM32,分别配置STM32做从机和主机www.shan-machinery.comMODBUS移植STM32,分别配置STM32做从机和主机

近期自学了MODBUS通信协议,也从网上找了很多资料,自己也分别做了从机和主机的配置,现在进行配合操作

MCU采用STM32F103C8T6实现功能,主机分别对从机实现读和写的操作主机要用到一个外部中断实现发数据的操作一、配置从机1.1、配置系统实现定时1MS的功能

初始化系统时钟为72MHZ

/******************************************************************************* @brief选择外部时钟或者内部时钟并进行倍频* @paramRCC_PLLSource:PLL时钟源 :可以选择:RCC_PLLSource_HSI_Div2、RCC_PLLSource_HSE_Div2、RCC_PLLSource_HSE_Div1PLLMUL:PLL输入时钟的倍频系数 范围:RCC_CFGR_PLLMULL2~16PLL时钟根据时钟和倍频来确定,选择内部时钟最高64M* @retval ******************************************************************************/void SysClock_Configuration(uint32_t RCC_PLLSource, uint32_t PLLMUL){__IO uint32_t HSEStatus = 0;RCC_ClocksTypeDef get_rcc_clock; RCC_DeInit(); // Resets the RCC clock configuration to the default reset state. if(RCC_PLLSource_HSI_Div2 != RCC_PLLSource) //选择外部时钟{RCC_HSEConfig(RCC_HSE_ON); //打开外部时钟if(RCC_WaitForHSEStartUp() == SUCCESS)//等待外部时钟开启{HSEStatus = 1;}else{ //外部时钟打开失败RCC_PLLSource = RCC_PLLSource_HSI_Div2;//自动选择内部时钟PLLMUL = RCC_CFGR_PLLMULL16; //配频到64MHZRCC_HSEConfig(RCC_HSE_OFF);//关闭外部时钟RCC_HSICmd(ENABLE);//打开内部时钟}}else{ //内部时钟 RCC_PLLSource = RCC_PLLSource_HSI_Div2; //自动选择内部时钟 PLLMUL = RCC_CFGR_PLLMULL16;//倍频到64MHZ RCC_HSEConfig(RCC_HSE_OFF); //关闭外部时钟 RCC_HSICmd(ENABLE); //打开内部时钟}RCC_HCLKConfig(RCC_SYSCLK_Div1); //HCLK(AHB)时钟为系统时钟1分频RCC_PCLK1Config(RCC_HCLK_Div2);//PCLK(APB1)时钟为HCLK时钟2分频 RCC_PCLK2Config(RCC_HCLK_Div1);//PCLK(APB2)时钟为HCLK时钟1分频 //0-24MHz时,取FLASH_Latency_0;//24-48MHz,取FLASH_Latency_1;//48-72MHz时,取FLASH_Latency_2。FLASH_SetLatency(FLASH_Latency_2);//不用到可以不配置FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); RCC_PLLConfig(RCC_PLLSource, PLLMUL); //PLL时钟配置,时钟源 * PLLMUL RCC_PLLCmd(ENABLE); //开启PLL时钟,并等待PLL时钟准备好while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//选择PLL时钟为系统时钟 while(RCC_GetSYSCLKSource() != 0x08);//Wait till PLL is used as system clock sourceRCC_ClockSecuritySystemCmd(ENABLE); //打开时钟安全系统RCC_GetClocksFreq(&get_rcc_clock); //仿真的时候就可以在结构体get_rcc_clock中看见各个外设的时钟了}

配置TIM3时钟

NVIC包含函数

#include “USART.h”#include “TIMER.h”

// 使用TIM3,对MODBUS协议定时#define MODBUS_TIM TIM3 #define MODBUS_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd#define MODBUS_TIM_CLK RCC_APB1Periph_TIM3#define MODBUS_TIM_IRQ TIM3_IRQn#define MODBUS_TIM_IRQHandlerTIM3_IRQHandler#define MODBUS_TIM_Period(1000-1)#define MODBUS_TIM_Prescaler (72-1)/******************************************************************************* @briefMODBUS_TIM_Config:TIM3初始化* @param* @retval ******************************************************************************/void MODBUS_TIM_Config(void){TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;MODBUS_TIM_APBxClock_FUN(MODBUS_TIM_CLK, ENABLE);//开启定时器时钟,即内部时钟CK_INT=72MTIM_TimeBaseStructure.TIM_Period=MODBUS_TIM_Period;//自动重装载寄存器周的值(计数值)// 累计TIM_Period 个频率后产生一个更新或者中断// 时钟预分频数为71,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1MTIM_TimeBaseStructure.TIM_Prescaler= MODBUS_TIM_Prescaler;TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频因子 ,基本定时器没有,不用管TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置TIM_TimeBaseStructure.TIM_RepetitionCounter=0;// 重复计数器的值,基本定时器没有,不用管TIM_TimeBaseInit(MODBUS_TIM,&TIM_TimeBaseStructure);// 初始化定时器TIM_ClearFlag(MODBUS_TIM,TIM_FLAG_Update);// 清除计数器中断标志位TIM_ITConfig(MODBUS_TIM,TIM_IT_Update,ENABLE);// 开启计数器中断TIM_Cmd(MODBUS_TIM, ENABLE);// 使能计数器 }/******************************************************************************* @briefALL_NVIC_Init:配置各个中断优先级* @param* @retval ******************************************************************************/void ALL_NVIC_Init(void){NVIC_InitTypeDef NVIC_InitStructure;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 设置中断组为1NVIC_InitStructure.NVIC_IRQChannel = MODBUS_TIM_IRQ ; // 设置中断来源NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 设置主优先级为 1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;// 设置抢占优先级为3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}

配置中断函数定时中断函数放到了MODBUS_USART.c中

/******************************************************************************* @briefMODBUS_TIM_IRQHandler:MODBUS定时器中断函数* @param* @retval ******************************************************************************/void MODBUS_TIM_IRQHandler (void) //定时器中断函数{if ( TIM_GetITStatus( MODBUS_TIM, TIM_IT_Update) != RESET ){ TIM_ClearITPendingBit(MODBUS_TIM , TIM_FLAG_Update);//清除中断标志位}}

主函数

int main(void){ SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZMODBUS_TIM_Config(); ALL_NVIC_Init();}

运行程序是否到断点处,现象如下:

在这里插入图片描述

1.2、配置系统实现串口接收中断的功能

使用USART2:PA2和PA3,配置串口GPIO口

// 串口2-USART2#define MODBUS_USARTUSART2#define MODBUS_USART_CLKRCC_APB1Periph_USART2#define MODBUS_USART_APBxClkCmd RCC_APB1PeriphClockCmd#define MODBUS_USART_BAUDRATE 9600// USART GPIO 引脚宏定义#define MODBUS_USART_GPIO_CLK RCC_APB2Periph_GPIOA#define MODBUS_USART_GPIO_APBxClkCmdRCC_APB2PeriphClockCmd#define MODBUS_USART_TX_GPIO_PORT GPIOA#define MODBUS_USART_TX_GPIO_PINGPIO_Pin_2#define MODBUS_USART_RX_GPIO_PORT GPIOA#define MODBUS_USART_RX_GPIO_PINGPIO_Pin_3// USART GPIO 中断#define MODBUS_USART_IRQUSART2_IRQn#define MODBUS_USART_IRQHandler USART2_IRQHandler/******************************************************************************* @briefMODBUS_USART_Config:MODBUS配置串口模式* @param无* @retval 无 ******************************************************************************/void MODBUS_USART_Config(void){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;MODBUS_USART_GPIO_APBxClkCmd(MODBUS_USART_GPIO_CLK, ENABLE);// 打开串口GPIO 的时钟MODBUS_USART_APBxClkCmd(MODBUS_USART_CLK, ENABLE);// 打开串口外设的时钟// 将USART1 Tx 的GPIO 配置为推挽复用模式GPIO_InitStructure.GPIO_Pin = MODBUS_USART_TX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(MODBUS_USART_TX_GPIO_PORT, &GPIO_InitStructure);// 将USART Rx 的GPIO 配置为浮空输入模式GPIO_InitStructure.GPIO_Pin = MODBUS_USART_RX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(MODBUS_USART_RX_GPIO_PORT, &GPIO_InitStructure);// 配置串口的工作参数USART_InitStructure.USART_BaudRate = MODBUS_USART_BAUDRATE;// 配置波特率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(MODBUS_USART, &USART_InitStructure); // 完成串口的初始化配置USART_ITConfig(MODBUS_USART, USART_IT_RXNE, ENABLE); // 使能串口接收中断USART_Cmd(MODBUS_USART, ENABLE); // 使能串口}/******************************************************************************* @briefALL_NVIC_Init:配置各个中断优先级* @param* @retval ******************************************************************************/void ALL_NVIC_Init(void){NVIC_InitTypeDef NVIC_InitStructure;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 设置中断组为1NVIC_InitStructure.NVIC_IRQChannel = MODBUS_USART_IRQ ; // 设置中断来源NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;// 设置主优先级为 1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 设置抢占优先级为0NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}

配置串口发送字符函数,为之后发数据做准备

/******************************************************************************* @briefUsart_SendByte:发送一个字符 * @param* @retval ******************************************************************************/void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch){USART_SendData(pUSARTx,ch);// 发送一个字节数据到USART while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); // 等待发送数据寄存器为空 }

配置串口接收中断函数

/******************************************************************************* @briefMODBUS_USART_IRQHandler:MODBUS串口中断函数* @param* @retval ******************************************************************************/ void MODBUS_USART_IRQHandler(void){uint8_t ucTemp;if (USART_GetITStatus(MODBUS_USART,USART_IT_RXNE)!=RESET)//判断是否有数据接收{} }

主函数

int main(void){ SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZMODBUS_USART_Config();ALL_NVIC_Init();}

用串口助手配置为9600BT/s,发送数据

1.3、配置系统实现收到数据后延时3.5T的功能

在配置延时3.5T功能同时,配置MODBUS结构体用来保存相关数据,建立MODBUS.c和MODBUS.h

嵌套关系

#include “stm32f10x.h”#include “modbusCRC.h”#include “USART.h”

typedef struct{ unsigned char myadd;//本设备的地址 unsigned char rcbuf[100]; //MODBUS接收缓冲区 unsigned inttimout; //MODbus的数据断续时间 unsigned char recount;//MODbus端口已经收到的数据个数 unsigned char timrun; //MODbus定时器是否计时的标志 unsigned char reflag; //收到一帧数据的标志 unsigned char Sendbuf[100]; //MODbus发送缓冲区}MODBUS;

配置串口接收函数

/******************************************************************************* @briefMODBUS_USART_IRQHandler:MODBUS串口中断函数* @param* @retval ******************************************************************************/ void MODBUS_USART_IRQHandler(void){uint8_t ucTemp;if (USART_GetITStatus(MODBUS_USART,USART_IT_RXNE)!=RESET)//判断是否有数据接收{ucTemp = USART_ReceiveData( MODBUS_USART ); //将接收的一个字节保存modbus.rcbuf[modbus.recount++]=ucTemp; //保存到MODBUS的接收缓存区modbus.timout=0;//串口接收数据的过程中,定时器不计时if(modbus.recount==1) //收到主机发来的一帧数据的第一字节{modbus.timrun=1; //启动定时}}}

配置定时器函数

/******************************************************************************* @briefMODBUS_TIM_IRQHandler:MODBUS定时器中断函数* @param* @retval ******************************************************************************/void MODBUS_TIM_IRQHandler (void) //定时器中断函数{if ( TIM_GetITStatus( MODBUS_TIM, TIM_IT_Update) != RESET ){if(modbus.timrun!=0)//串口发送数据是否结束,结束就让定时器定时{ modbus.timout++; //定时器定时1毫秒,并开始记时 TIM_ClearITPendingBit(MODBUS_TIM , TIM_FLAG_Update);//清除中断标志位}}

结合串口和定时器运行程序,用串口助手发送助手,调试程序是否进入断点

1.4、配置系统处理数据的功能

配置到这里我们基本将MODBUS的时序配置好,但是数据未曾处理,接下来我们对数据处理,并用MODBUS调试助手验证

其中MODBUS包含

#include “modbusCRC.h”#include “USART.h”

其中MODBUS_USART包含

#include “USART.h”#include “MODBUS.h”#include “TIMER.h”

在MODBUS中定义寄存器

unsigned int Reg[]={0x0000, //本设备寄存器中的值 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A,};

在MODBUS中包含下列函数,函数不一一讲解,可以细看,其中处理函数不发送错误码,实际中功能码够了

/******************************************************************************* @briefModbud_fun3:3号功能码处理---主机要读取本从机的寄存器* @param* @retval ******************************************************************************/void Modbud_fun3(void) {u16 Regadd;u16 Reglen;u16 byte;u16 i,j;u16 crc;Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];//得到要读取的寄存器的首地址Reglen=modbus.rcbuf[4]*256+modbus.rcbuf[5];//得到要读取的寄存器的数量i=0;modbus.Sendbuf[i++]=modbus.myadd; //发送本设备地址modbus.Sendbuf[i++]=0x03; //发送功能码byte=Reglen*2;//要返回的数据字节数//modbus.Sendbuf[i++]=byte/256;modbus.Sendbuf[i++]=byte%256; //发送要返回的数据字节数 for(j=0;jhttps://www.shan-machinery.com