欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-09 15:22:22
...

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

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

  1. MCU采用STM32F103C8T6
  2. 实现功能,主机分别对从机实现读和写的操作
  3. 主机要用到一个外部中断实现发数据的操作

一、配置从机

1.1、配置系统实现定时1MS的功能

初始化系统时钟为72MHZ

/******************************************************************************
  * @brief  选择外部时钟或者内部时钟并进行倍频
  * @param  
	RCC_PLLSource:PLL时钟源 :
						可以选择:RCC_PLLSource_HSI_Div2、RCC_PLLSource_HSE_Div2、RCC_PLLSource_HSE_Div1
	PLLMUL:PLL输入时钟的倍频系数 
	          范围:RCC_CFGR_PLLMULL2~16
						PLL时钟根据时钟和倍频来确定,选择内部时钟最高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;		   //配频到64MHZ
				RCC_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 source
	RCC_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_IRQHandler        TIM3_IRQHandler
#define MODBUS_TIM_Period            (1000-1)
#define MODBUS_TIM_Prescaler         (72-1)
/******************************************************************************
  * @brief  MODBUS_TIM_Config:TIM3初始化
  * @param  
  * @retval 
 ******************************************************************************/
void MODBUS_TIM_Config(void)
{
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  MODBUS_TIM_APBxClock_FUN(MODBUS_TIM_CLK, ENABLE);      //开启定时器时钟,即内部时钟CK_INT=72M
  TIM_TimeBaseStructure.TIM_Period=MODBUS_TIM_Period;        //自动重装载寄存器周的值(计数值)
  // 累计TIM_Period 个频率后产生一个更新或者中断
  // 时钟预分频数为71,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M
  TIM_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);                              // 使能计数器
 }
/******************************************************************************
  * @brief  ALL_NVIC_Init:配置各个中断优先级
  * @param  
  * @retval 
 ******************************************************************************/


void ALL_NVIC_Init(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);             // 设置中断组为1	
	NVIC_InitStructure.NVIC_IRQChannel = MODBUS_TIM_IRQ ;       // 设置中断来源
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   // 设置主优先级为 1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;          // 设置抢占优先级为3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

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

/******************************************************************************
  * @brief  MODBUS_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,内部设置为64MHZ
    MODBUS_TIM_Config();
     ALL_NVIC_Init();
}

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

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

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

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

// 串口2-USART2
#define MODBUS_USART                            USART2
#define MODBUS_USART_CLK                        RCC_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_APBxClkCmd            RCC_APB2PeriphClockCmd

#define MODBUS_USART_TX_GPIO_PORT               GPIOA
#define MODBUS_USART_TX_GPIO_PIN                GPIO_Pin_2
#define MODBUS_USART_RX_GPIO_PORT               GPIOA
#define MODBUS_USART_RX_GPIO_PIN                GPIO_Pin_3
// USART GPIO 中断
#define MODBUS_USART_IRQ                        USART2_IRQn
#define MODBUS_USART_IRQHandler                 USART2_IRQHandler
/******************************************************************************
* @brief  MODBUS_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);	                                     // 使能串口
}
/******************************************************************************
  * @brief  ALL_NVIC_Init:配置各个中断优先级
  * @param  
  * @retval 
 ******************************************************************************/
void ALL_NVIC_Init(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);             // 设置中断组为1

	NVIC_InitStructure.NVIC_IRQChannel = MODBUS_USART_IRQ ;       // 设置中断来源
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;    // 设置主优先级为 1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;           // 设置抢占优先级为0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	
}

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

/******************************************************************************
  * @brief  Usart_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);   // 等待发送数据寄存器为空 
}

配置串口接收中断函数

/******************************************************************************
  * @brief  MODBUS_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,内部设置为64MHZ
	MODBUS_USART_Config();
    ALL_NVIC_Init();

}

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

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

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 int    timout;         //MODbus的数据断续时间	
 unsigned char   recount;        //MODbus端口已经收到的数据个数
 unsigned char   timrun;         //MODbus定时器是否计时的标志
 unsigned char   reflag;         //收到一帧数据的标志
 unsigned char   Sendbuf[100];   //MODbus发送缓冲区	
}MODBUS;

配置串口接收函数

/******************************************************************************
  * @brief  MODBUS_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;   	//启动定时
			  }
  }
}

配置定时器函数

/******************************************************************************
  * @brief  MODBUS_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);//清除中断标志位
		}
}

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

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

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中包含下列函数,函数不一一讲解,可以细看,其中处理函数不发送错误码,实际中功能码够了

/******************************************************************************
  * @brief  Modbud_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;j<Reglen;j++)
	{
	  modbus.Sendbuf[i++]=Reg[Regadd+j]/256;    //发送读取数据字节数的高位 
		modbus.Sendbuf[i++]=Reg[Regadd+j]%256;  //发送读取数据字节数的低位 
	}
	crc=crc16(modbus.Sendbuf,i);                //CRC校验
	modbus.Sendbuf[i++]=crc/256;                //发送CRC的值高位
	modbus.Sendbuf[i++]=crc%256;                //发送CRC的值低位
	
	for(j=0;j<i;j++)                            //通过串口逐个发送
   Usart_SendByte( MODBUS_USART,modbus.Sendbuf[j]);
}
/******************************************************************************
  * @brief  Modbud_fun6:6号功能码处理,写寄存器
  * @param  
  * @retval 
 ******************************************************************************/

void Modbud_fun6()                             
{
  unsigned int Regadd;
	unsigned int val;
	unsigned int i,crc,j;
	i=0;
  Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];  //得到要修改的地址 
	val=modbus.rcbuf[4]*256+modbus.rcbuf[5];   //修改后的值
	Reg[Regadd]=val;                           //修改本设备相应的寄存器
	//以下为回应主机
	modbus.Sendbuf[i++]=modbus.myadd;          //发送本设备地址
  modbus.Sendbuf[i++]=0x06;                    //发送功能码 
  modbus.Sendbuf[i++]=Regadd/256;              //发送修改地址高位
	modbus.Sendbuf[i++]=Regadd%256;            //发送修改地址低位
	modbus.Sendbuf[i++]=val/256;               //发送修改的值高位
	modbus.Sendbuf[i++]=val%256;               //发送修改的值低位
	crc=crc16(modbus.Sendbuf,i);               //校验地址、功能码、地址、数据
	modbus.Sendbuf[i++]=crc/256;               //发送CRC的值高位
	modbus.Sendbuf[i++]=crc%256;               //发送CRC的值低位
	
	for(j=0;j<i;j++)                             //通过串口逐个发送
   Usart_SendByte( MODBUS_USART,modbus.Sendbuf[j]);
}
/******************************************************************************
  * @brief  Mosbus_Event:MODBUS处理数据程序
  * @param  
  * @retval 
 ******************************************************************************/

void Mosbus_Event(void)
{
	unsigned int crc;
	unsigned int rccrc;
	unsigned char i=0;
  if(modbus.reflag==0)      //没有收到MODbus的数据包
	{
	  return ;                //没有收到处理指令,继续等待下一条数据
	}
	while(modbus.rcbuf[i++]!=modbus.myadd);
		
	crc= crc16(&modbus.rcbuf[0], modbus.recount-2);                             //计算校验码
  rccrc=modbus.rcbuf[modbus.recount-2]*256 + modbus.rcbuf[modbus.recount-1];  //收到的校验码
  if(crc ==  rccrc)                                                           //数据包符合CRC校验规则
	{ 
	  if(modbus.rcbuf[0] == modbus.myadd)         //确认数据包是否是发给本设备的 
		{
		  switch(modbus.rcbuf[1])                   //分析功能码
			{
			  case 0:     break;
			  case 1:     break;
		    case 2:     break;
		    case 3:     Modbud_fun3();    break;   //3号功能码处理
		    case 4:     break;
		    case 5:     break;
		    case 6:     Modbud_fun6();     break;  //6号功能码处理
	      case 7:     break;			
        //....				
			}
		}
		else if(modbus.rcbuf[0] == 0)             //广播地址,不处理
		{
		}
	}                                          //数据包不符合CRC校验规则
	modbus.recount=0;                          //清除缓存计数
  modbus.reflag=0;	                         //重新开始执行处理函数
}

在MODBUS_USART中包含下列函数

/******************************************************************************
  * @brief  MODBUS_Init:MODBUS初始化
  * @param  
  * @retval 
 ******************************************************************************/
void MODBUS_Init(void)
{
	MODBUS_TIM_Config();
	MODBUS_USART_Config();
	modbus.myadd=4;        //初始化本从设备的地址
	modbus.timrun=0;       //初始化MODbus定时器停止计时
}
/******************************************************************************
  * @brief  MODBUS_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;   	//启动定时
			  }
  }
}
/******************************************************************************
  * @brief  MODBUS_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毫秒,并开始记时
			 if(modbus.timout>=8)       //间隔时间达到了时间,假设为8T,实际3.5T即可
				{
					modbus.timrun=0;        //关闭定时器--停止定时
					modbus.reflag=1;        //收到一帧数据,开始处理数据
				}
			}
	   TIM_ClearITPendingBit(MODBUS_TIM , TIM_FLAG_Update);//清除中断标志位
		}
}

主函数

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

运行MODBUS调试助手,配置9600TB/s,地址设置为4

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

发送:04 06 00 00 00 01 48 5F

表示向0号寄存器写1

返回:04 06 00 00 00 01 48 5F

发送:04 03 00 00 00 01 84 5F

表示读取0号地址后一个寄存器的值

返回:04 03 02 00 01 B5 84

二、配置主机

2.1、配置系统实现定时1MS的功能

如上配置点击跳转

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

如上配置点击跳转

2.3、配置一个USART1和外部中断功能

USART1配置,用来查看数据,这个串口仅仅用来查看数据,不用配置接收中断

// 串口1-USART1
#define DEBUG1_USART                            USART1
#define DEBUG1_USART_CLK                        RCC_APB2Periph_USART1
#define DEBUG1_USART_APBxClkCmd                 RCC_APB2PeriphClockCmd
#define DEBUG1_USART_BAUDRATE                   9600
// USART GPIO 引脚宏定义
#define DEBUG1_USART_GPIO_CLK                   RCC_APB2Periph_GPIOA
#define DEBUG1_USART_GPIO_APBxClkCmd            RCC_APB2PeriphClockCmd
#define DEBUG1_USART_TX_GPIO_PORT               GPIOA
#define DEBUG1_USART_TX_GPIO_PIN                GPIO_Pin_9
#define DEBUG1_USART_RX_GPIO_PORT               GPIOA
#define DEBUG1_USART_RX_GPIO_PIN                GPIO_Pin_10
/******************************************************************************
* @brief  DEBUG_USART_Init:配置串口调试
  * @param  无
  * @retval 无
 ******************************************************************************/
void DEBUG_USART_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	DEBUG1_USART_GPIO_APBxClkCmd(DEBUG1_USART_GPIO_CLK, ENABLE);	  // 打开串口GPIO 的时钟	
	DEBUG1_USART_APBxClkCmd(DEBUG1_USART_CLK, ENABLE);	            // 打开串口外设的时钟	
	// 将USART1 Tx 的GPIO 配置为推挽复用模式
  GPIO_InitStructure.GPIO_Pin = DEBUG1_USART_TX_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(DEBUG1_USART_TX_GPIO_PORT, &GPIO_InitStructure);		
	// 将USART Rx 的GPIO 配置为浮空输入模式
  GPIO_InitStructure.GPIO_Pin = DEBUG1_USART_RX_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(DEBUG1_USART_RX_GPIO_PORT, &GPIO_InitStructure);	
	// 配置串口的工作参数
	USART_InitStructure.USART_BaudRate = DEBUG1_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(DEBUG1_USART, &USART_InitStructure);	                       // 完成串口的初始化配置	
	USART_ITConfig(DEBUG1_USART, USART_IT_RXNE, ENABLE);	               // 使能串口接收中断
	USART_Cmd(DEBUG1_USART, ENABLE);	                                   // 使能串口
}

配置外部中断,采用PA0按键的功能

//引脚定义
#define KEY_UP_INT_GPIO_PORT           GPIOA
#define KEY_UP_INT_GPIO_CLK           (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY_UP_INT_GPIO_PIN            GPIO_Pin_0
#define KEY_UP_INT_EXTI_PORTSOURCE     GPIO_PortSourceGPIOA
#define KEY_UP_INT_EXTI_PINSOURCE      GPIO_PinSource0

#define KEY_UP_INT_EXTI_LINE           EXTI_Line0
#define KEY_UP_INT_EXTI_IRQ            EXTI0_IRQn
#define KEY_UP_IRQHandler              EXTI0_IRQHandler
void EXTI_Key_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;

	RCC_APB2PeriphClockCmd(KEY_UP_INT_GPIO_CLK,ENABLE);    //开启按键GPIO 口的时钟
 /*--------------------------KEY1 配置---------------------*/
	GPIO_InitStructure.GPIO_Pin = KEY_UP_INT_GPIO_PIN;     // 选择按键用到的GPIO 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  // 配置为下拉输入,因为浮空输入其他的管脚对他的干扰很大 
	GPIO_Init(KEY_UP_INT_GPIO_PORT, &GPIO_InitStructure);
	GPIO_EXTILineConfig(KEY_UP_INT_EXTI_PORTSOURCE,KEY_UP_INT_EXTI_PINSOURCE);// 选择EXTI 的信号源 	
	EXTI_InitStructure.EXTI_Line = KEY_UP_INT_EXTI_LINE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;   // EXTI 为中断模式 
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿中断
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;             //使能中断 
	EXTI_Init(&EXTI_InitStructure);
}

这里需要主要的一点,采用下拉的方式,不要采用浮空输入的方式

在MODBUS_USART中配置外部中断函数

/******************************************************************************
  * @brief  MODBUS_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毫秒,并开始记时
			 if(modbus.timout>=8)       //间隔时间达到了时间,假设为8T,实际3.5T即可
				{
					modbus.timrun=0;        //关闭定时器--停止定时
					modbus.reflag=1;        //收到一帧数据,开始处理数据
				}
			}
	   TIM_ClearITPendingBit(MODBUS_TIM , TIM_FLAG_Update);//清除中断标志位
		}
}

 /******************************************************************************
  * @brief  KEY_UP_IRQHandler:外部发送数据方式
  * @param  
  * @retval 
 ******************************************************************************/
void KEY_UP_IRQHandler(void)
{

	if (EXTI_GetITStatus(KEY_UP_INT_EXTI_LINE) != RESET)   //确保是否产生了EXTI Line 中断
		{
		
	  Usart_SendByte( DEBUG1_USART,modbus.myadd);
		Usart_SendByte( DEBUG1_USART,0x03);
		Usart_SendByte( DEBUG1_USART,0x00);
		Usart_SendByte( DEBUG1_USART,0x00);
		Usart_SendByte( DEBUG1_USART,0x00);
		Usart_SendByte( DEBUG1_USART,0x02);
	  Usart_SendByte( DEBUG1_USART,0xC4);
		Usart_SendByte( DEBUG1_USART,0x5E);	
			
		Usart_SendByte( MODBUS_USART,modbus.myadd);
		Usart_SendByte( MODBUS_USART,0x03);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0x02);
	  Usart_SendByte( MODBUS_USART,0xC4);
		Usart_SendByte( MODBUS_USART,0x5E);	

		EXTI_ClearITPendingBit(KEY_UP_INT_EXTI_LINE);        //清除中断标志位
		}
}

打开串口USART2发送数据,用USART1接收数据

从机返回写指令数据处理

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

发送:04 06 00 00 00 01 48 5F

对寄存器00 00 地址写1

返回:00 01

从机返回读指令数据处理

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

发送:04 03 02 00 01 B5 84

对寄存器00 00 地址d读1

返回:00 01

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

如上配置点击跳转

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

这个地方和从机不同

/******************************************************************************
  * @brief  Modbud_fun3:3号功能码处理  ---主机要读取从机的寄存器后,将信息保存到主机指定长度的寄存器
  * @param  
  * @retval 
 ******************************************************************************/
void Modbud_fun3(void)                           
{
  unsigned int Regadd=0,i=0,j,Reglen;                                   
	Reglen=modbus.rcbuf[2];                         //得到读取的寄存器的数量
	for(i=0;i<Reglen;i++)                           //处理读取的数据保存到主机相应寄存器
	{                                               //数据从寄存器的第一个保存到指定数量
	Reg[Regadd]=modbus.rcbuf[3+i]*256;              //将数据高位保存寄存器
	Usart_SendByte( DEBUG1_USART,Reg[Regadd]/256);  //发送到另一个串口显示	
  i++;		                                        //数据增加,处理低位
    Reg[Regadd]=Reg[Regadd]+modbus.rcbuf[i+3];      //发送到另一个串口显示	
	Usart_SendByte( DEBUG1_USART,Reg[Regadd]%256);  //将数据低位保存寄存器
	Regadd++;		                               //处理完高位和低位数据,进行下一个数据
	}
}
/******************************************************************************
  * @brief  Modbud_fun6:6号功能码处理,主机向从机指定寄存器写数据,写完后,返回修改的数据
  * @param  
  * @retval 
 ******************************************************************************/
void Modbud_fun6()                             
{
  unsigned int Regadd,i=0,crc,j,val;             
	Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];        //得到要更改寄存器的地址
	Reg[Regadd]=modbus.rcbuf[4]*256+modbus.rcbuf[5];   //将从机修改的数据再保存到主机寄存器中
  Usart_SendByte( DEBUG1_USART,Reg[Regadd]/256);     //另一个串口显示修改的数据
	Usart_SendByte( DEBUG1_USART,Reg[Regadd]%256);     //另一个串口显示修改的数据
}

主函数如下

int main(void)
{ 
	SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
	MODBUS_Init();
  ALL_NVIC_Init();
	Usart_SendByte( DEBUG1_USART,0x32);
	CODE_End();
	while(1)
	Mosbus_Event();
}

三、主机向从机下发命令

3.1、主机向从机下发写命令

将主机的串口连接从机的串口,按下主机的按键,在主机的另一个串口查看是否有数据接收

按键中断配置函数

		Usart_SendByte( DEBUG1_USART,modbus.myadd);
		Usart_SendByte( DEBUG1_USART,0x06);
		Usart_SendByte( DEBUG1_USART,0x00);
		Usart_SendByte( DEBUG1_USART,0x00);
		Usart_SendByte( DEBUG1_USART,0xFF);
		Usart_SendByte( DEBUG1_USART,0xFF);
	    Usart_SendByte( DEBUG1_USART,0x88);
		Usart_SendByte( DEBUG1_USART,0x2F);			

		Usart_SendByte( MODBUS_USART,modbus.myadd);
		Usart_SendByte( MODBUS_USART,0x06);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0xFF);
		Usart_SendByte( MODBUS_USART,0xFF);
	    Usart_SendByte( MODBUS_USART,0x88);
	    Usart_SendByte( MODBUS_USART,0x2F);	

相当于主机向从机发数据:0x04 0x06 0x00 0x00 0xFF 0xFF 0x88

返回:0xFF 0xFF

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

3.2、主机向从机下发读命令

将主机的串口连接从机的串口,按下主机的按键,在主机的另一个串口查看是否有数据接收

	    Usart_SendByte( MODBUS_USART,modbus.myadd);
		Usart_SendByte( MODBUS_USART,0x03);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0x00);
		Usart_SendByte( MODBUS_USART,0x01);
	    Usart_SendByte( MODBUS_USART,0x84);
		Usart_SendByte( MODBUS_USART,0x5F);	
		
	    Usart_SendByte( DEBUG1_USART,modbus.myadd);
		Usart_SendByte( DEBUG1_USART,0x03);
		Usart_SendByte( DEBUG1_USART,0x00);
		Usart_SendByte( DEBUG1_USART,0x00);
		Usart_SendByte( DEBUG1_USART,0x00);
	 	Usart_SendByte( DEBUG1_USART,0x01);
	    Usart_SendByte( DEBUG1_USART,0x84);
		Usart_SendByte( DEBUG1_USART,0x5F);	

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

相当于主机向从机发数据:0x04 0x06 0x00 0x00 0x01 0x84 0x5F

返回:0x00 0x00

这里返回00是由于之前调试过程完3.1步后复位为原来值导致的。

相关标签: STM32学习