在调试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. C. Basketball Exercise dp

    C. Basketball Exercise time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  2. 小程序封装API

    一般我们https请求都是通过wx.request来请求,但是这种请求如果多了,页面会混乱不说,还不好管理,因此我将请求单独拎出去,方便管理,也方便后期维护. // api.js const API_ ...

  3. 云时代架构阅读笔记七——Java多线程中如何使用synchronized关键字

    关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨synchronized关键字. sy ...

  4. 云时代架构阅读笔记五——Java内存模型详解(一)

    什么是Java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的访问差异,以实现让Java程序在各种平台下都能达到一致 ...

  5. 12.Python的高级语法和用法

    # from enum import Enum # 枚举 # class VIP(Enum): # YELLOW = # YELLOW_ALIAS = # 别名 # GREEN = # BLACK = ...

  6. 034-PHP简单定义一个匿名函数

    <?php /* 简单定义一个匿名函数 */ # 把匿名函数赋值给一个变量,也叫临时函数 $demo = function ($txt) { echo $txt; }; # 调用测试下 $dem ...

  7. (20)sopel算法

    基础知识的理论,主要看这个博客:https://blog.csdn.net/github_38140310/article/details/68959931 然后代码展示: #include &quo ...

  8. ES6模块化深入 debug

    引子: 2020.2.24.最近刚写完一个vue项目.项目用到ES6的模块化 想到之前写node项目用到过commonjs模块化 就想着把所有用到过的模块化技术 总结学习一下 在看阮一峰老师的 es6 ...

  9. opencv 矩阵操作

    OpenCv矩阵操作 有很多函数有mask,代表掩码,如果某位mask是0,那么对应的src的那一位就不计算,mask要和矩阵/ROI/的大小相等 大多数函数支持ROI,如果图像ROI被设置,那么只处 ...

  10. 02-NVIDIA Jetson TX2 通过JetPack 3.1刷机完整版(踩坑版)

    未经允许,不得擅自改动和转载 文 | 阿小庆 2018-1-20 本文继第一篇文章:01-NVIDIA Jetson TX2开箱上电显示界面 TX2 出厂时,已经自带了 Ubuntu 16.04 系统 ...