STM32F407移植FreemodbusV1.6
准备工作 工具介绍 软件 Keil5: 代码编译调试 CubeMX: 代码生成工具 VSCode: 代码编辑器 Modbus Poll && Slave: 调试工具 硬件 STM32F407ZGT6: 普中麒麟F407开发板 两个串口转USB: 用于连接主机和从机 创建工程 CubeMX工程创建 选择芯片型号 设置时钟(外部晶振) 设置调试接口和基准时钟 配置串口(USART1~3) 开启FreeRTOS 创建3个任务 生成项目 Keil工程整合 将下载的Freemodbus源码复制到项目根目录,添加所需源文件: modbus/functions/ 下所有文件 modbus/rtu/ 下所有文件 mb.c 和 mb_m.c 移植详解 port.c - 临界区实现 #include "FreeRTOS.h" #include "task.h" #include "semphr.h" void EnterCriticalSection(void) { taskENTER_CRITICAL(); } void ExitCriticalSection(void) { taskEXIT_CRITICAL(); } portevent.c - 事件标志 使用FreeRTOS事件组实现Modbus事件通知。 portserial.c - 串口收发 实现串口初始化、收发控制,使用FIFO缓冲区接收数据。 porttimer.c - 定时器 实现Modbus帧间隔检测,波特率大于19200时固定为1750us。 主机移植 主机移植与从机类似,主要区别在于: portevent_m.c 中添加了响应事件标志 使用信号量实现主机操作同步 主机使用示例 void MasterTaskFun(void *argument) { for(;;) { eMBMasterPoll(); } } void MasterSendTaskFun(void *argument) { uint16_t data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for(;;) { eMBMasterReqWriteMultipleHoldingRegister(1, 0, 10, data, 100); for (uint16_t i = 0; i < 10; i++) { data[i] += 1; } osDelay(1000); } } 从机使用示例 void Slave_TaskFun(void *argument) { for(;;) { eMBPoll(); } } 测试 使用Modbus Poll和Modbus Slave软件进行测试: ...
Freemodbus主从库
Freemodbus 简介 Freemodbus 是一款开源的 Modbus 协议栈,由armink大神移植。同时支持主机和从机的功能。 FreeModbus 主机源码是开源的,这使得在嵌入式产品中实现Modbus主机功能变得简单。 特性 新增加的主机源码与原有从机的风格及接口保持一致 支持主机与从机在同一协议栈运行 支持实时操作系统及裸机移植 为应用提供多种请求模式,可以选择阻塞还是非阻塞模式 支持所有常用的Modbus方法 文件结构 源文件 描述 FreeModbus/modbus/mb.c Modbus从机设置及轮询接口 FreeModbus/modbus/mb_m.c Modbus主机设置及轮询接口 FreeModbus/modbus/rtu/mbrtu.c 从机RTU模式设置 FreeModbus/modbus/rtu/mbrtu_m.c 主机RTU模式设置 FreeModbus/port/portserial.c 从机串口移植 FreeModbus/port/portserial_m.c 主机串口移植 FreeModbus/port/user_mb_app.c 从机数据缓冲区 FreeModbus/port/user_mb_app_m.c 主机数据缓冲区 数据缓冲区 数据缓冲区定义在 user_mb_app_m.c 文件中,共4种数据类型: 线圈(Coils) 离散输入(Discrete Inputs) 保持寄存器(Holding Registers) 输入寄存器(Input Registers) API详解 写单个保持寄存器 eMBMasterReqWriteHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usRegData, LONG lTimeOut ); 读多个保持寄存器 eMBMasterReqReadHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usNRegs, LONG lTimeOut ); 读输入寄存器 eMBMasterReqReadInputRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usNRegs, LONG lTimeOut ); 返回值说明 返回值 描述 MB_MRE_NO_ERR 正常 MB_MRE_NO_REG 寄存器地址出错 MB_MRE_TIMEDOUT 响应超时 MB_MASTER_BUSY 主机忙 获取方式 GitHub: https://github.com/RT-Thread-packages/freemodbus
DIY四足机器人
四足机器人软硬件架构 🤦♀️飞书画的感觉不咋地 四足机器人硬件(所有连接仅供参考,任何问题与本人无关) 电池 🐱👓请一定用万用表检测一下正负极是否和pcb接口对应以及电压是否正常,理论上来说2s~4s锂电池都可以接入 【淘宝】http://e.tb.cn/h.gKCOp83eZ0tq1GN?tk=drWN3hhtbwJ HU9196 「电动玩具枪锂电池7.4V充电器四驱越野遥控车充电电池挖掘机软弹枪」 点击链接直接打开 或者 淘宝搜索直接打开 需要购买一套电池 降压模块 👀保证至少可以过3A电流。 【淘宝】限时满20减2 http://e.tb.cn/h.gKaxrAQtpJIKUid?tk=qcLp3hhDj74 MF7997 「MP1584EN 3A 5A可调降压电源模块板稳压航模24V-12V 9V转5VDC-DC」 点击链接直接打开 或者 淘宝搜索直接打开 需要购买1块降压模块 NodeMcu 8266开发板 🤷♀️这个神板因该人手一个,不过现在乐鑫已经推出esp32c3了,新神板来了 【淘宝】限时官方立减0.73元 http://e.tb.cn/h.gKQFJ88xUs7VLfy?tk=jwn13hGPIay HU0854 「ESP8266串口WIFI模块 CP2102/CH340 NodeMCU Lua V3物联网开发板」 点击链接直接打开 或者 淘宝搜索直接打开 需要购买1块开发板,esp8266 的硬件资源介绍及使用教程请自行学习 ...
理解环形队列
解读环形队列-C语言实现 环形队列(Circular Queue)是一种特殊的队列数据结构。与普通的线性队列不同,环形队列在物理存储上表现为一个首尾相连的环状结构,在逻辑上则仍然遵循先进先出(FIFO)的原则。 特点 循环利用空间:当队列中的元素被删除后,原来的空间可以被重新使用 队头队尾指针:使用head和end两个指针标识队列的头部和尾部 队满与队空判断:需要特殊处理,通常预留一个位置不使用 动态调整:可以动态调整数组大小来适应数据量变化 应用场景 缓冲区管理:在网络编程或硬件驱动程序中管理固定大小的缓冲区 生产者消费者模型:在多线程环境中用作数据共享的数据结构 滑动窗口算法:实现高效的数据处理策略 串口接收:STM32中常用于串口接收,实现数据异步使用 图解 非循环队列 非循环队列由一个头指针head和一个尾指针end构成。 入队操作: end = end + 1,然后将数据存入end指向的空间: 出队操作: 出队就是将head指向空间的数据抛出,然后head加一: 判断队空: end = -1 表示队空 end + 1 = head 表示队空 判断队满:end + 1 = MAX_SIZE 循环队列 循环队列的head指针并不指向数据头,而是指向上一次被删除的数据地址。 下标范围控制: end = (end+1) % MAX_SIZE head = (head+1) % MAX_SIZE 入队操作: 出队操作: 判断队满:head = (end+1) % MAX_SIZE 判断队空:head == end ...
SPI通信协议深度解析
SPI(Serial Peripheral Interface)是一种广泛应用的高速、全双工、同步串行通信协议。本质上是主从结构的总线协议,适用于MCU与各种外设间的短距离高速通信。 SPI协议基础 硬件连接 SPI通信需要四根信号线: 信号 全称 方向 说明 SCLK Serial Clock Master→Slave 时钟信号,由主机产生 MOSI Master Out Slave In Master→Slave 主机发送数据 MISO Master In Slave Out Slave→Master 主机接收数据 CS/SS Chip Select / Slave Select Master→Slave 片选信号,选择通信的从设备 MCU (Master) Slave Device ========== ============= +-------------+ +-------------+ | SCLK|------------->|SCLK | | MOSI|------------->|MOSI | | MISO|<-------------|MISO | | CS |------------->|CS | +-------------+ +-------------+ 多从设备连接 MCU (Master) ========== +---------------+ | SCLK|-----------------------------------+ | MOSI|-----------------------------------+ | MISO|<----------------------------------+ | CS0 |-------------------+ | | CS1 |--------------------------+ | | CS2 |-----------------------------+ | +---------------+ | | | | | | | | v v v v v v +-----+-----+-----+ +-----+-----+-----+ | CS0 | CS1 | CS2 | | SS0 | SS1 | SS2 | +-----+-----+-----+ +-----+-----+-----+ 独立片选模式:每从设备独占一个CS引脚,最多可连接n个从设备 级联模式:多个从设备共享同一CS,数据线串联 SPI工作模式 SPI有四种工作模式,由时钟极性(CPOL)和时钟相位(CPHA)共同决定: ...
嵌入式系统内存管理详解
在嵌入式系统中,内存资源通常非常有限,合理的内存管理策略直接影响系统的稳定性、可靠性和性能。本文将深入探讨嵌入式系统中的各种内存管理技术,帮助你设计出更高效的内存使用方案。 嵌入式内存架构 在深入了解内存管理之前,先明确嵌入式系统的内存布局: +-------------------+ 0x00000000 | Flash | (代码和常量) | (只读存储器) | +-------------------+ 0xXXXXXXXX | RAM | (读写数据) | - .data | (已初始化全局变量) | - .bss | (未初始化全局变量) | - Heap | (动态分配) | - Stack | (函数调用、局部变量) +-------------------+ 0xXXXXXXXX 静态内存分配 静态内存分配在编译时确定,不会分配失败,无内存碎片,是嵌入式开发的首选方案。 全局变量与静态变量 /* * 全局变量 - 整个程序生命周期内存在 * 位于 .data 或 .bss 段 */ // 已初始化全局变量 (.data段) uint32_t system_tick = 0; char device_name[] = "STM32F407"; // 未初始化全局变量 (.bss段,系统自动清零) static uint32_t error_count; uint8_t buffer[256]; // 静态局部变量 - 函数退出后保留值 uint32_t get_sequence_id(void) { static uint32_t id = 0; // 只初始化一次 return ++id; } 常量与只读数据 /* * const数据通常放在Flash中,节省RAM空间 */ // 字符串常量 - 存储在Flash const char *HELLO_MSG = "Hello, Embedded World!"; const char *VERSION = "V1.0.0"; // 查表法 - 将计算结果预先存储 const uint16_t sin_table[361] = { 0, 28, 57, 85, 114, ... // sin(angle) * 1000 }; // 配置表 - 存储设备参数 const DeviceConfig default_config = { .baudrate = 115200, .parity = 0, .stop_bits = 1, .timeout_ms = 1000 }; 静态内存池 预分配固定大小的内存块,分配和释放速度快,无碎片: ...
深入理解RTOS任务调度原理
实时操作系统(RTOS)的核心价值在于提供确定性的响应时间和任务调度能力。作为嵌入式开发者,理解RTOS的任务调度原理对于合理设计任务优先级、避免优先级反转、优化系统性能至关重要。 任务与进程 在RTOS中,“任务”(Task)通常对应一个执行流。与传统操作系统的进程不同: 特性 进程 RTOS任务 地址空间 隔离 共享 栈空间 独立 独立 资源隔离 强 弱 创建/切换开销 大 小 RTOS任务更像是轻量级的线程,共享相同的地址空间,但拥有独立的栈和上下文。 任务控制块(TCB) 任务控制块(TCB - Task Control Block)是RTOS管理任务的核心数据结构: typedef struct tcb { volatile void *sp; // 栈指针 uint32_t priority; // 任务优先级 uint16_t stack_size; // 栈大小 StackType_t *stack_base; // 栈底 // 状态信息 TaskState state; uint32_t time_slice; // 时间片计数 uint32_t tick_count; // 滴答计数 // 链表节点(用于就绪队列/阻塞队列) ListItem_t state_list_item; ListItem_t event_list_item; // 任务函数 TaskFunction_t func; void *param; // 任务名(用于调试) char name[configMAX_TASK_NAME_LEN]; } TCB_t; 任务状态模型 RTOS中的任务通常有以下几种状态: ...
状态机在嵌入式开发中的应用
状态机(FSM - Finite State Machine)是嵌入式开发中最常用的设计模式之一。它能够帮助开发者将复杂的逻辑分解为清晰的状态和转换,特别适合处理按键检测、协议解析、模式切换等场景。 什么是状态机? 状态机是一种抽象的计算模型,由以下要素组成: 要素 说明 状态 (State) 系统在某一时刻的稳定条件或工作模式 事件 (Event) 触发状态转换的外部信号或内部条件 转换 (Transition) 状态之间的有向转移 动作 (Action) 状态转换时执行的操作 状态转移图 状态机可以用状态转移图直观地表示: +-----------------+ | | | IDLE |<---------------+ | | | +--------+--------+ | | | 事件:EV_KEY_PRESS | | | v | +--------+--------+ | | | | | PRESSED |-------+ | | | | | +-----------------+ | | | | 定时器超时 | | 按键释放 (消抖完成) | | | | v | +-----------------+ +--------+------>+ | | | | RELEASED |---------------------->+ | | 消抖完成,进入IDLE +-----------------+ 核心要素详解 状态定义 typedef enum { STATE_IDLE, // 空闲状态 STATE_PRESSED, // 按键按下(消抖中) STATE_ACTIVE, // 按键确认按下 STATE_RELEASED, // 按键释放(消抖中) } KeyState; 事件定义 typedef enum { EV_KEY_PRESS, // 按键按下 EV_KEY_RELEASE, // 按键释放 EV_TIMEOUT, // 定时器超时 } KeyEvent; 状态表 typedef struct { KeyState current_state; KeyEvent event; KeyState next_state; void (*action)(void); // 转换时执行的动作 } StateTransition; // 状态转换表 static const StateTransition g_trans_table[] = { {STATE_IDLE, EV_KEY_PRESS, STATE_PRESSED, Start_DebounceTimer}, {STATE_PRESSED, EV_TIMEOUT, STATE_ACTIVE, On_KeyConfirmed}, {STATE_ACTIVE, EV_KEY_RELEASE,STATE_RELEASED, Start_DebounceTimer}, {STATE_RELEASED, EV_TIMEOUT, STATE_IDLE, On_KeyReleased}, }; 实现方式 1. Switch-Case 实现 最简单直观的方式: ...
嵌入式裸机程序架构设计
在嵌入式开发中,良好的架构设计是项目可维护性和可扩展性的基础。没有操作系统的情况下,如何组织代码结构显得尤为重要。本文将介绍几种常见的裸机架构模式,帮助你选择适合自己项目的方案。 常见的裸机架构模式 1. 超级循环(Super Loop) 超级循环是最简单也最直接的架构模式。主循环依次调用各任务处理函数,顺序执行所有业务逻辑。 代码示例: int main(void) { System_Init(); while (1) { Task_KeyScan(); // 按键扫描 Task_Display(); // 显示屏刷新 Task_Sensor(); // 传感器采集 Task_Communication(); // 通信处理 // 可选:加入延时控制循环频率 delay_ms(10); } } 优点: 代码结构简单,易于理解和实现 无额外资源消耗,无需调度器 中断响应确定性好 缺点: 任务耦合严重,修改一个任务可能影响其他任务 实时性差,所有任务必须等待循环执行 任务数量和复杂度受循环时间限制 适用场景: 任务简单、实时性要求不高的简单产品,如玩具、小家电控制板。 2. 前后台系统(Foreground-Background) 将紧急任务放在中断前台处理,非紧急任务在主循环后台执行。这是最常用的裸机架构模式。 代码示例: // 前台:中断服务程序 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { // 紧急事件处理,如按键按下、外部报警 g_key_pressed = 1; g_system_error = ERROR_NONE; EXTI_ClearITPendingBit(EXTI_Line0); } } void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { // 定时器中断,标记时间基准 g_timer_flag = 1; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } // 后台:主循环 int main(void) { System_Init(); while (1) { if (g_timer_flag) { g_timer_flag = 0; Task_10ms(); // 10ms周期任务 } if (g_key_pressed) { g_key_pressed = 0; Task_KeyProcess(); // 按键处理 } Task_Display(); // 显示屏刷新 Task_Communication(); // 通信处理 } } 优点: ...
欢迎来到我的技术博客
你好,欢迎来到我的技术博客。 作为一名嵌入式软件工程师,我创建这个博客是为了记录学习过程、分享开发经验,并与同行交流技术心得。 关于我 我目前专注于嵌入式软件领域,主要涉及以下几个方向: 裸机程序开发:在资源受限的单片机平台上实现高效、可靠的业务逻辑 RTOS应用开发:基于FreeRTOS、RT-Thread等实时操作系统构建复杂应用 驱动程序开发:GPIO、UART、SPI、I2C、ADC等外设驱动的设计与实现 系统优化:提升系统实时性、降低功耗、优化内存使用 技术栈 我的主要开发环境和技术工具: 分类 技术/工具 MCU STM32、NXP LPC、Cypress PSoC RTOS FreeRTOS、RT-Thread 调试 J-Link、ST-Link、Oscilloscope IDE Keil MDK、IAR EWARM、VS Code 语言 C、嵌入式汇编、Python脚本 博客内容规划 本博客将持续更新以下几个方向的内容: 嵌入式开发实战:结合实际项目,分享开发经验和踩坑记录 RTOS内核解析:深入剖析实时操作系统的核心机制 驱动开发笔记:各类外设驱动的设计思路与实现细节 工具链使用:编译器、调试器、自动化工具的使用技巧 为什么写博客? 知识沉淀:写下来能加深理解,也方便日后回顾 技术交流:希望能与同行交流,互相学习 社区贡献:把遇到的问题和解决方案分享出去,帮助有需要的人 联系我 如果你有任何问题或建议,欢迎通过以下方式与我交流: GitHub: 我的GitHub 希望我的文章能对你有所帮助!