在调试STM32F407的串口Modbus通讯之前,也使用过Modbus通讯,只不过都是在PLC或则昆仑通态的触摸屏上使用直接调用现成的库里面的模块,驱动就可以,相对于STM32来,使用PLC库里面的模块和触摸屏驱动都是初始化后配参数就可以了,但是用32写的时候比较麻烦了一些。由于STM32没有RS485通讯端口只能用UART端口转成RS485加了一个485芯片,在调试Modbus通信按照plc和触摸屏的经验慢慢琢磨也是挺有意思的。当最后你都调通的时候心里那种成就感是对于一个理工男来说是非常爽的!不啰嗦了下面介绍Modbus通信流程以及功能码:

  1、数据目的:读输入继电器    1区      功能码:02        通信格式: 设备地址     功能码    起始地址     读数据位个数     CRC校验

                              例:       01             02         00 00           00 01                  B9 CA

例 :01 02 00 00 00 01 B9 CA

                           返回数据:01 02 01 01 60 48    //返回01表示继电器1状态

                                01 02 01 00 A1 88    //返回00表示继电器0状态

  2、数据目的:读输出继电器    0区      功能码:01        通信格式: 设备地址     功能码    起始地址     读数据位个数     CRC校验

                                例:       01             01         00 00           00 01                   FD CA

例:01 01 00 00 00 01 FD CA

                           返回数据:01 01 01 01 90 48    //返回01表示继电器1状态

                               01 01 01 00 51 88    //返回00表示继电器0状态

  3、数据目的:写输出继电器    0区      功能码:05        通信格式: 设备地址     功能码    起始地址       数据                CRC校验

                                例:       01             05         00 00           00 00                 CD CA

例:01 05 00 00 00 00 CD CA    //写继电器0状态

例:01 05 00 00 FF FFCC 7A    //写继电器1状态

  4、数据目的:读3区16位寄存器数据   3区      功能码:04        通信格式: 设备地址     功能码    起始地址    数据位数         CRC校验

                                     例:      01              04         00 01           00 00              60 0A

       例:01 04 00 01 00 01 60 0A    //写继电器0状态

                                       返回数据:01 04 02 00 05 79 33    //返回02带边数据位数 00 05代表返回的数据

  5、数据目的:读4区16位寄存器数据   4区      功能码:03        通信格式: 设备地址     功能码    起始地址    数据位数         CRC校验

                                     例:    01              03         00 01           00 00              D5 CA

例:01 03 00 01 00 01 D5 CA    //写继电器0状态

                                              返回数据:01 03 02 00 05 78 47    //返回02带边数据位数 00 05代表返回的数据

  6、数据目的:写4区16位寄存器数据   4区      功能码:06 /10        通信格式: 设备地址     功能码    起始地址       数据            CRC校验

                                              例:     01              06         00 01           00 00              9C 6B

      例:01 06 00 01 0E 03 9C 6B   //写00 01数据0E 03

啰嗦了半天功能码,接下来写一下Modbus的在STM32中的流程:

  1、发送需要读取从站的地址以及功能码

  2、发送完成后开始200ms定时中断等待从站数据是否返回(定时器中断优先级低于串口中断)若无数据返回重复1步骤

  3、有数据返回开启2ms中断,接收间隔超过2ms代表数据接收完成。

  4、解析数据,返回数据;

 extern u8 RS485_BUFF[];
u8 Rcv_Len;
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern u8 Uart0_send_buff[];
extern u8 Uart0_rev_buff[];
extern vs8 Uart0_send_counter;
extern vu8 Uart0_rev_count;//com0串口接收计数器
extern vu8 Uart0_rev_comflag;
extern vu8 Crc_counter;//com0校验计数器
extern vu8 *Uart0_send_pointer ;//com0串口发送指针
extern vu8 Uart_send_flag;//从机响应超时标志 /*******************************************************************************
* 函 数 名 : RS485_Init
* 函数功能 : USART2初始化函数
* 输 入 : bound:波特率
* 输 出 : 无
*******************************************************************************/
void RS485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOA\G时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟 //串口2引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //GPIOA2复用为USART2
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //GPIOA3复用为USART2 //USART2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA2与GPIOA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA2,PA3 //PG8推挽输出,485模式控制
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOG8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOG,&GPIO_InitStructure); //初始化PG8 //USART2 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
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(USART2, &USART_InitStructure); //初始化串口2 USART_Cmd(USART2, ENABLE); //使能串口 2 USART_ClearFlag(USART2, USART_FLAG_TC); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启接受中断 //Usart2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、 RS485_TX_EN=; //默认为接收模式
}
/*******************************************************************************
* 函 数 名 : USART2_IRQHandler
* 函数功能 : USART2中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{ if(Uart_send_flag==)
{
Uart_send_flag=;//标志没有超时
TIM_Cmd(TIM2, DISABLE);
TIM_DeInit( TIM2);//复位TIM2定时器
}
if(Uart0_rev_comflag != )
{
if(Uart0_rev_count < )
{
Uart0_rev_buff[Uart0_rev_count++]= USART_ReceiveData(USART2);
TIM_DeInit( TIM2);//复位TIM2定时器
TIM2_Init(,);
}
else
{
USART_ReceiveData(USART2);//如果接收计数器大于100,放弃接收的数据,防止同总线上有其它数据长大于100的信息导致本接收缓存溢出 }
}
else
{
USART_ReceiveData(USART2);//如果接收没有处理,放弃接收的数据
}
}
if(USART_GetITStatus(USART2, USART_IT_TXE) != RESET)
{ Uart0_send_counter--;
if(Uart0_send_counter>)
{
/* Write one byte to the transmit data register */
USART_SendData(USART2, *Uart0_send_pointer++); }
else
{
delay_ms();
RS485_TX_EN=;
// s8 i ;
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
Uart_send_flag = ;// 数据发送完成
TIM_DeInit( TIM2);//复位TIM2定时器
TIM2_Init(,); } } }
extern u8 RS485_BUFF[];     //UART2  外设485数据缓存区
extern u8 mag_nav_cmd[]; //磁导航modbus指令
extern u8 inertia_cmd[]; //惯性导航modbus指令
extern u8 readrfid_cmd[]; //读取ID卡modbus指令 extern u8 Uart0_send_buff[];
extern u8 Uart0_rev_buff[];
extern vs8 Uart0_send_counter;
extern vu8 Uart0_rev_count;//com0串口接收计数器
extern vu8 Uart0_rev_comflag;
extern vu8 Crc_counter;//com0校验计数器
extern vu8 *Uart0_send_pointer ;//com0串口发送指针
extern vu8 Uart_send_flag;//从机响应超时标志
void Modbus_Function_3(uint8_t device_id,uint8_t reg_H_addr,uint8_t reg_L_addr,uint8_t red_num_H,uint8_t red_num_L);
u8 time_state;
extern u8 sent_data[];
/*******************************************************************************
* 函 数 名 : TIM2_Init
* 函数功能 : TIM2初始化函数
* 输 入 : per:重装载值
psc:分频系数
* 输 出 : 无
*******************************************************************************/
void TIM2_Init(u32 per,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //TIM初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //优先级结构体
//APB1 42M //APB1_TIM2 84M
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2时钟 TIM_TimeBaseInitStructure.TIM_Period=(per-)*; //自动装载症////
TIM_TimeBaseInitStructure.TIM_Prescaler=psc-; //分频系数////分频系数和计数值决定定时器值
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); // 初始化定时器TIM2 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启定时器中断
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清楚定时器标志位 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //定时器中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM2,ENABLE); //使能定时器
} /*******************************************************************************
* 函 数 名 : TIM2_IRQHandler
* 函数功能 : TIM2中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
u8 i; /* TIM IT DISABLE [使能TIM2溢出中断]*/
TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
/* TIM2 DISABLE counter */
TIM_Cmd(TIM2, DISABLE);
TIM_DeInit( TIM2);//复位TIM2定时器
if(Uart_send_flag==)
{
Uart_send_flag=;//说明超时
}
else
{
Uart0_rev_comflag = ;//接收完成
for(i=;i<Uart0_rev_count+;i++)
{
RS485_BUFF[i]= Uart0_rev_buff[i];
}
Crc_counter = Uart0_rev_count;//crc校验计数器赋值
Uart0_rev_count = ;//接收计数器清零
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}

STM32F407的Modbus做为主站与从站通讯的更多相关文章

  1. Modbus协议栈实现Modbus RTU多主站支持

    前面我们已经详细讲解过Modbus协议栈的开发过程,并且利用协议栈封装了Modbus RTU主站和从站,Modbus TCP服务器与客户端,Modbus ASCII主站与从站应用.但在使用过程中,我们 ...

  2. 实现Modbus ASCII多主站应用

    1.更新设计关于原来的协议栈在Modbus ASCII主站应用时所存在的局限性与Modbus RTU也是一样的,所以我们不分析它的不足,只讨论更新设计.我们将主站及其所访问的从站定义为通用的对象,而当 ...

  3. C++使用RabbitMQ类库做客户端与RabbitMQ Server通讯,生成C++可调用的rabbimq.*.dll的过程

    Step: download the latest rabbitmq-c via: https://github.com/alanxz/rabbitmq-c follow the document, ...

  4. 郑重推荐开源CANopen协议栈CANFestival(LGPL许可)!!!!!!!!

    郑重推荐开源CANopen协议栈CANFestival(LGPL许可)!!!!!!!!(这条文章已经被阅读了 次) 时间:2010/03/04 06:47am 来源:winshton [这个贴子最后由 ...

  5. 开放型Modbus/TCP 规范

    修订版 1.0,1999 年3 月29 日Andy SwalesSchneider 电气公司aswales@modicon.com目录目录............................... ...

  6. modbus串口通讯C#

    简介 公司给的一个小任务,这篇文章进行详细讲解 题目: modbus串口通讯 主要内容如下: 1.实现使用modbus通讯规约的测试软件: 2.具有通信超时功能: 3.分主站从站,并能编辑报文.生成报 ...

  7. Modbus协议和应用开发介绍

    因业务需要了解Modbus协议的使用,因此对Modbus的协议,以及相应的C#处理应用进行了解,针对协议的几种方式(RTU.ASCII.TCPIP)进行了封装,以及对Modbus的各种功能码的特点进行 ...

  8. Modbus库开发笔记之二:Modbus消息帧的生成

    前面我们已经对Modbus的基本事务作了说明,也据此设计了我们将要实现的主从站的操作流程.这其中与Modbus直接相关的就是Modbus消息帧的生成.Modbus消息帧也是实现Modbus通讯协议的根 ...

  9. Modbus库开发笔记之一:实现功能的基本设计

    Modbus作为开放式的工业通讯协议,在各种工业设备中应用极其广泛.本人也使用Modbus通讯很多年了,或者用现成的,或者针对具体应用开发,一直以来都想要开发一个比较通用的协议栈能在后续的项目中复用, ...

随机推荐

  1. P1050 螺旋矩阵

    P1050 螺旋矩阵 转跳点:

  2. Linux 下 zip 文件解压乱码如何解决

    作者:Latm Ake链接:https://www.zhihu.com/question/20523036/answer/35225920来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业 ...

  3. POJ 2155 二维线段树 经典的记录所有修改再统一遍历 单点查询

    本来是想找一个二维线段树涉及懒惰标记的,一看这个题,区间修改,单点查询,以为是懒惰标记,敲到一半发现这二维线段树就不适合懒惰标记,你更新了某段的某列,但其实其他段的相应列也要打标记,但因为区间不一样, ...

  4. python之os.exec*族用法简结

    os.exec*族主要用来代替当前进程,执行新的程序,不返回值.在UNIX上,新的执行程序加载到当前进程,与调用它的进程有相同的id. os.execl(path, arg0, arg1, ...) ...

  5. MongoDB Projection

    版权所有,未经许可,禁止转载 章节 MongoDB 入门 MongoDB 优势 MongoDB 安装 MongoDB 数据建模 MongoDB 创建数据库 MongoDB 删除数据库 MongoDB ...

  6. Origin中使用CopyPage复制图片到Word后比例失调解决办法

    Origin画图的优势很多,其图形美观易于操作.对我而言,Origin最大的优点就是与Word兼容,在Origin操作界面空白处直接使用右键CopyPage命令,然后在Word中使用粘贴命令即可插入图 ...

  7. 在登陆退出时候使用Vuex

    1.登陆的时候,在登陆模块请求接口,然后获取一个access_token,获取用户权限.保存到缓存里面. 2.退出的时候,请求退出接口,把缓存里面的access_token清除. 一旦要在登陆里面做一 ...

  8. 基础语法-for循环的嵌套

    基础语法-for循环的嵌套 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.for循环嵌套概述 说白了就是在for循环中再嵌套一层for循环. 二.for循环嵌套案例 1> ...

  9. java floor,ceil和round方法

    Math.floor():返回值是double类型的,返回的是不大于它的最大整数 举例: double x = Math.floor(5.8); System.out.println(x); //输出 ...

  10. Spring Tools 4 STS没有创建Dynamic Web Project的选项 以及 Spring Tools 4 STS New 菜单没有Spring Bean Configuration File选项

    Spring Tools 4 STS没有创建Dynamic Web Project的选项 STS4默认不带Dynamic Web Project插件. 解决方法:1.打开:Help 选择 Instal ...