- 软件:Keil uvision5
- 单片机:STM32F103C8T6
- 调试:XCOM V2.6、Modbus调试精灵
- 串口通信:波特率:9600 数据位:8 校验位:无 停止位:1
- modbus:RTU模式下03功能码、06功能码实现
- 附件:STM32单片机实现modbus通讯协议-单片机文档类资源-CSDN下载
(1)modbus通讯协议是由Modicon公司在1979年开发的,应用于工业现场控制的总线协议。
(2)modbus协议采用主从模式,由一个主机对多个从机进行通信,最多支持247个从机。
(3)modbus数据传输模式:ASCII和RTU两种模式。
1)ASCII模式:
① 有开始标志和结束标志,数据帧以英文冒号(0x3A)开始,以回车和换号(0x0D and 0x0A )符号结束;一个数据帧的各字节的传输时间间隔可以大于1s。
② 允许的传输的字符集为十六进制的0~9和A~F;每个8位的字节被拆分成两个ASCII字符进行发送,比如十六进制数0xAF ,会被分解成ASCII字符“A”和“F”进行发送,发送的字符量比RTU增加一倍。
2)RTU模式:
① 没有规定的开始和结束标记,规定每两个字节之间发送或者接收的时间间隔不能超过3.5倍字符传输时间。如果两个字符时间间隔超过了3.5倍的字符传输时间,就认为一帧数据已经接收,新的一帧数据传输开始。
② 每个字节可以传输两个十六进制字符,比如十六进制数0xAF,直接以十六进制0xAF进行发送
3)modbus数据帧格式:ModBus协议帧格式(RTU、ASCII、TCP/IP)_匠在江湖的博客-CSDN博客_modbus协议帧格式
① modbus数据帧校验方式:CRC循环冗余校验(RTU模式)和LRC纵向冗余校验(ASCII模式)
② modbus从机地址:主机通过唯一的从机地址识别从机,主机与从机通信时通过不同的功能码实现不同的功能
③ modbus功能码:MODBUS协议整理——功能码简述 - Endv - 博客园
STM32F103 | 产品 | STM32/STM8 | MCU单片机 | 意法半导体STM | 意法半导体STM | STM32/STM8微控制器 | MCU单片机

void modbusUsart_Init(void) {
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // USART2时钟使能
// USART1_TX A2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART1_RX A3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 串口参数设置
USART_InitStructure.USART_BaudRate = 9600; // 波特率 // 波特率:9600bps,9600bit/s => 1/9600bps = 104us/bit
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字长:8
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位:1
USART_InitStructure.USART_Parity = USART_Parity_No; // 奇偶校验位:0
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件数据流控制:无
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 串口模式:收发模式
// 串口设置
USART_DeInit(USART2); // 串口复位
USART_Init(USART2, &USART_InitStructure); // 串口参数初始化
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); // 串口中断开启
USART_Cmd(USART2, ENABLE); // 串口使能
USART_ClearFlag(USART2,USART_FLAG_TC); // 清除标志位
// NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStructure);
}
void modbusUsart_Transmit(u8 data) { // modbusUsart发送一个字节
USART_SendData(USART2, data); // 发送数据
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); // 串口状态读取
USART_ClearFlag(USART2,USART_FLAG_TC); // 清除标志位
}
void USART2_IRQHandler() { // USART2中断,MODBUS字节接收中断
u8 receiveBuffer;
receiveBuffer=USART_ReceiveData(USART2); // 读串口数据
switch(receiveBuffer) {
case 1: modbusUsart_Transmit(1); break; // PC:TX->01; RX->01
case 2: modbusUsart_Transmit(2); break; // PC:TX->02; RX->02
case 3: modbusUsart_Transmit(3); break; // PC:TX->03; RX->03
default: break;
}
}
#include "modbusUsart.h"
int main() {
modbusUsart_Init();
while(1) {
// TODO
}
}

void Timer2_Init() { // 初始化Timer2
TIM_TimeBaseInitTypeDef timer;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); // 时钟TIM2使能
TIM_DeInit(TIM2);
timer.TIM_Period=1000-1; // 设置自动重载计数周期值:1000,1us*1000=1ms
timer.TIM_Prescaler=72-1; // 分频系数:72M/72=1MHZ,1us
timer.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频因子
timer.TIM_CounterMode=TIM_CounterMode_Up; // 计数方式:TIM 向上计数
TIM_TimeBaseInit(TIM2,&timer); // 初始化TIM2
TIM_Cmd(TIM2,ENABLE); // 使能TIM2
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE); // 允许中断更新
// NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStructure);
}
void TIM2_IRQHandler() { // Timer2溢出中断
u8 timFlagStatus;
timFlagStatus= TIM_GetFlagStatus(TIM2, TIM_FLAG_Update);
if(timFlagStatus==SET) {
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
timerCounter++;
}
}
#include "timer.h"
#include "modbusUsart.h"
#include "core_cm3.h"
void delay_us(u32 delay_us);
void delay_ms(u16 delay_ms);
u16 timerCounter=0;
int main() {
Timer2_Init(); // Timer2初始化
modbusUsart_Init(); // modbusUsart初始化
while(1) {
modbusUsart_Transmit(timerCounter/1000);
delay_ms(1000);
}
}

#include "modbusCRC.h"
const unsigned char crcTableHigh[] = { // CRC字节值表高位
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
const unsigned char crcTableLow[] = { // CRC字节值表低位
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
unsigned int ModbusCRC16(unsigned char *sendBuffer, unsigned int sendCounter) { // ModbusCRC16校验
unsigned char crcHigh = 0xFF; // CRC高字节初始化
unsigned char crcLow = 0xFF; // CRC低字节初始化
unsigned long crcIndex; // CRC索引
while(sendCounter--) {
crcIndex = crcHigh ^ *sendBuffer++;
crcHigh = crcLow ^ crcTableHigh[crcIndex];
crcLow = crcTableLow[crcIndex];
}
return (crcHigh<<8|crcLow);
}
#ifndef __MODBUS_H
#define __MODBUS_H
#include "stm32f10x.h"
typedef struct {
u8 mAddress; // modbus: 本从设备地址
u8 mReceiveBuffer[50]; // modbus: 接受数据缓存
u8 mSendBuffer[50]; // modbus: 发送数据缓存
u16 mTimer; // modbus: 计时器,记录字符间隔
u8 mTimerFlag; // modbus: 计时器标志,0停止计时器,1启动计时器
u8 mReceiveCounter; // modbus: 接收数据计数器
u8 mReceiveFlag; // modbus: 接收标志,1接收完成,0正在接收
} MODBUS;
extern MODBUS modbus; // modbus引用
void Modbus_Init(void); // Func1: modbus 初始化
void Modbus_Event(void); // Func2: modbus 事件
void Modbus_FunctionCode3(void); // Func3: modbus功能码03功能实现
void Modbus_FunctionCode6(void); // Func4: modbus功能码06功能实现
#endif
#include "timer.h"
#include "modbusUsart.h"
#include "modbus.h"
int main() {
Timer2_Init(); // Timer2初始化
Modbus_Init(); // modbus初始化
while(1) {
Modbus_Event(); // 处理MODbus数据
}
}
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定