MODBUS移植STM32,分别配置STM32做从机和主机
MODBUS移植STM32,分别配置STM32做从机和主机
近期自学了MODBUS通信协议,也从网上找了很多资料,自己也分别做了从机和主机的配置,现在进行配合操作
- MCU采用STM32F103C8T6
- 实现功能,主机分别对从机实现读和写的操作
- 主机要用到一个外部中断实现发数据的操作
一、配置从机
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();
}
运行程序是否到断点处,现象如下:
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,发送数据
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);//清除中断标志位
}
}
结合串口和定时器运行程序,用串口助手发送助手,调试程序是否进入断点
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
发送: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接收数据
从机返回写指令数据处理
发送:04 06 00 00 00 01 48 5F
对寄存器00 00 地址写1
返回:00 01
从机返回读指令数据处理
发送: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
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);
相当于主机向从机发数据:0x04 0x06 0x00 0x00 0x01 0x84 0x5F
返回:0x00 0x00
这里返回00是由于之前调试过程完3.1步后复位为原来值导致的。