在调试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. POJ 2287 田忌赛马

    Tian Ji -- The Horse Racing Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 17699   Acc ...

  2. web.xml的配置过程中也需要注意顺序问题

    配置WEB.XML的配置文件过程中发现: 直接红叉,鼠标放在红叉出信息如下: cvc-complex-type.2.4.a: Invalid content was found starting wi ...

  3. 使用HttpURLConnection方式访问接口

    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import ...

  4. 使用WebClient下载文件到本地目录

    利用WebClient实现下载文件 调用 string url = "https://timgsa.baidu.com/timg?image&quality=80&size= ...

  5. Elasticsearch 搜索API

    章节 Elasticsearch 基本概念 Elasticsearch 安装 Elasticsearch 使用集群 Elasticsearch 健康检查 Elasticsearch 列出索引 Elas ...

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

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

  7. 【剑指Offer】面试题12. 矩阵中的路径

    题目 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左.右.上.下移动一格.如果一条路径经过了矩阵的某一格,那么该路径 ...

  8. python-模块安装

    首先到这个网址https://www.lfd.uci.edu/~gohlke/pythonlibs/ 找到自己想要用的模块,然后下载下来, 回到桌面找到文件所在位置进入cmd中, pip instal ...

  9. POJ 3663:Costume Party

    Costume Party Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 12607   Accepted: 4977 De ...

  10. Java虚拟机之内存模型

    一.java并发基础 在并发编程中存在两个关键问题①线程之间如何通信 ②线程之间如何同步. 通信 通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. ...