直流减速电机 + Arduino 接线方案
总体接线图12V电源 ├── BTS7960 → 37GB545D电机动力线 └── LM2596 → 5V → Arduino / 编码器 / 传感器 Arduino ├── D5 → BTS7960 RPWM ├── D6 → BTS7960 LPWM ├── D2 ← 编码器A相 ├── D3 ← 编码器B相 ├── D7 ← HOME限位 └── A0 ← FSR压力传感器1.BTS7960 接电源和电机BTS7960连接B12V电源正极B-12V电源负极M电机动力线1M-电机动力线22.BTS7960 接 ArduinoBTS7960Arduino作用VCC5V逻辑电源GNDGND信号地RPWMD5正转PWMLPWMD6反转PWMR_END8右桥使能L_END9左桥使能3.37GB545D 编码器接 Arduino编码器线ArduinoVCC5VGNDGNDA相D2B相D34.LM2596降压模块接线12V电源 → LM2596 IN 12V电源 - → LM2596 IN- LM2596 OUT → Arduino 5V LM2596 OUT- → Arduino GND5.FSR402压力传感器接线Arduino 5V │ FSR402 │ Arduino A0 │ 10kΩ电阻 │ GND完整功能代码/* * 37GB545D BTS7960 AB编码器 FSR402 * 控制流程 * 上电回零 - FSR待机检测 - 闭合剪切 * - 保持 - 张开 - 等待压力释放 - 再次待机 * * Arduino Uno 引脚 * D2 - 编码器A * D3 - 编码器B * D5 - BTS7960 RPWM * D6 - BTS7960 LPWM * D7 - HOME限位开关可以通过宏关闭 * D8 - BTS7960 R_EN * D9 - BTS7960 L_EN * A0 - FSR402分压输出 */ // // 1. 功能选择 // // 使用HOME限位开关1 // 不使用HOME限位开关0 #define USE_HOME_SWITCH 1 // 编码器方向修正 // 正向运行时计数增加设为1 // 正向运行时计数减少改为-1 #define ENCODER_SIGN 1 // // 2. 引脚定义 // #define ENC_A_PIN 2 #define ENC_B_PIN 3 #define RPWM_PIN 5 #define LPWM_PIN 6 #define HOME_PIN 7 #define R_EN_PIN 8 #define L_EN_PIN 9 #define FSR_PIN A0 // // 3. 位置参数 // // 回零后张开位置为0 #define OPEN_POSITION 0L // 闭合位置必须根据你的机构重新标定 // 第一次测试先设置小一点例如100200 #define CLOSE_POSITION 200L // 位置允许误差 #define DEAD_ZONE 6L // // 4. 电机控制参数 // // 位置P控制增益 #define KP 0.8f // 电机克服减速箱静摩擦所需的最低PWM #define MIN_PWM 50 // 限制最大PWM避免一开始速度过快 #define MAX_PWM 160 // 回零速度 // 如果回零方向反了把-70改成70 #define HOME_PWM -70 // // 5. 时间参数 // #define HOME_TIMEOUT 6000UL #define CLOSE_TIMEOUT 4000UL #define OPEN_TIMEOUT 4000UL #define HOLD_TIME 300UL // FSR上电空载校准时间 #define FSR_CALIBRATION_TIME 2000UL // FSR采样周期 #define FSR_SAMPLE_INTERVAL 20UL // // 6. FSR参数 // // 触发阈值 空载基准 触发偏移 #define FSR_TRIGGER_OFFSET 100 // 释放阈值必须小于触发偏移形成滞回 #define FSR_RELEASE_OFFSET 40 // 连续多少次超过阈值才触发 #define TRIGGER_CONFIRM_COUNT 5 // 连续多少次低于释放阈值才认为果梗已离开 #define RELEASE_CONFIRM_COUNT 10 // // 7. 编码器变量 // volatile long encoderCount 0; volatile uint8_t previousEncoderState 0; /* * 正交编码器状态转换表。 * 前两位为上一次AB状态后两位为当前AB状态。 */ const int8_t QUADRATURE_TABLE[16] { 0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0 }; // // 8. 状态机 // enum CutterState { STATE_HOMING, STATE_IDLE, STATE_CLOSING, STATE_HOLDING, STATE_OPENING, STATE_WAIT_RELEASE, STATE_ERROR }; CutterState currentState STATE_IDLE; unsigned long stateStartTime 0; unsigned long lastFSRSampleTime 0; unsigned long lastPrintTime 0; int basePressure 0; int currentPressure 0; uint8_t triggerConfirmCounter 0; uint8_t releaseConfirmCounter 0; bool manualCutRequest false; // // 9. 初始化 // void setup() { Serial.begin(115200); // BTS7960 pinMode(RPWM_PIN, OUTPUT); pinMode(LPWM_PIN, OUTPUT); pinMode(R_EN_PIN, OUTPUT); pinMode(L_EN_PIN, OUTPUT); setMotor(0); enableMotorDriver(true); // 编码器 pinMode(ENC_A_PIN, INPUT_PULLUP); pinMode(ENC_B_PIN, INPUT_PULLUP); previousEncoderState (digitalRead(ENC_A_PIN) 1) | digitalRead(ENC_B_PIN); attachInterrupt( digitalPinToInterrupt(ENC_A_PIN), encoderISR, CHANGE ); attachInterrupt( digitalPinToInterrupt(ENC_B_PIN), encoderISR, CHANGE ); // HOME开关 pinMode(HOME_PIN, INPUT_PULLUP); Serial.println(F()); Serial.println(F(37GB545D剪切控制系统启动)); Serial.println(F(请确保FSR上没有果梗或其他物体)); Serial.println(F()); calibrateFSR(); #if USE_HOME_SWITCH enterState(STATE_HOMING); Serial.println(F(开始自动回零)); #else /* * 不使用HOME开关时 * 上电前必须人工把刀片放到完全张开位置。 */ writeEncoderCount(0); enterState(STATE_IDLE); Serial.println(F(未启用HOME开关)); Serial.println(F(已将上电位置设为张开零点)); #endif Serial.println(F(串口命令)); Serial.println(F(C强制执行一次剪切)); Serial.println(F(S软件停止)); Serial.println(F(R故障复位)); Serial.println(F(Z将当前位置设为编码器零点)); } // // 10. 主循环 // void loop() { unsigned long now millis(); handleSerialCommand(); updatePressure(now); printStatus(now); switch (currentState) { // -------------------------------------------------- // 自动回零 // -------------------------------------------------- case STATE_HOMING: #if USE_HOME_SWITCH if (digitalRead(HOME_PIN) LOW) { setMotor(0); writeEncoderCount(0); Serial.println(F(回零完成编码器已清零)); enterState(STATE_IDLE); } else if (now - stateStartTime HOME_TIMEOUT) { enterError(F(故障回零超时)); } else { setMotor(HOME_PWM); } #else setMotor(0); writeEncoderCount(0); enterState(STATE_IDLE); #endif break; // -------------------------------------------------- // 等待FSR触发 // -------------------------------------------------- case STATE_IDLE: positionControl(OPEN_POSITION); if (manualCutRequest) { manualCutRequest false; triggerConfirmCounter 0; Serial.println(F(串口命令触发剪切)); enterState(STATE_CLOSING); } else if (pressureTriggerConfirmed()) { Serial.println(F(FSR检测到果梗开始剪切)); enterState(STATE_CLOSING); } break; // -------------------------------------------------- // 闭合剪切 // -------------------------------------------------- case STATE_CLOSING: if (positionControl(CLOSE_POSITION)) { setMotor(0); Serial.println(F(到达闭合位置)); enterState(STATE_HOLDING); } else if (now - stateStartTime CLOSE_TIMEOUT) { enterError(F(故障闭合动作超时)); } break; // -------------------------------------------------- // 剪切保持 // -------------------------------------------------- case STATE_HOLDING: positionControl(CLOSE_POSITION); if (now - stateStartTime HOLD_TIME) { Serial.println(F(保持完成开始张开)); enterState(STATE_OPENING); } break; // -------------------------------------------------- // 张开复位 // -------------------------------------------------- case STATE_OPENING: #if USE_HOME_SWITCH /* * 如果张开过程中先碰到HOME开关 * 立即停止并重新清零。 */ if (digitalRead(HOME_PIN) LOW) { setMotor(0); writeEncoderCount(0); Serial.println(F(HOME开关触发张开完成)); enterState(STATE_WAIT_RELEASE); break; } #endif if (positionControl(OPEN_POSITION)) { setMotor(0); Serial.println(F(张开位置到达)); enterState(STATE_WAIT_RELEASE); } else if (now - stateStartTime OPEN_TIMEOUT) { enterError(F(故障张开动作超时)); } break; // -------------------------------------------------- // 等待果梗或物体离开FSR // -------------------------------------------------- case STATE_WAIT_RELEASE: positionControl(OPEN_POSITION); if (pressureReleaseConfirmed()) { Serial.println(F(压力已释放重新进入待机)); enterState(STATE_IDLE); } break; // -------------------------------------------------- // 故障状态 // -------------------------------------------------- case STATE_ERROR: setMotor(0); break; } } // // 11. 电机驱动函数 // void enableMotorDriver(bool enable) { digitalWrite(R_EN_PIN, enable ? HIGH : LOW); digitalWrite(L_EN_PIN, enable ? HIGH : LOW); } /* * speed范围-255255 * 正数一个方向 * 负数相反方向 * 0停止 */ void setMotor(int speedValue) { speedValue constrain(speedValue, -255, 255); if (speedValue 0) { analogWrite(RPWM_PIN, speedValue); analogWrite(LPWM_PIN, 0); } else if (speedValue 0) { analogWrite(RPWM_PIN, 0); analogWrite(LPWM_PIN, -speedValue); } else { analogWrite(RPWM_PIN, 0); analogWrite(LPWM_PIN, 0); } } // // 12. 位置闭环控制 // /* * 返回true到达目标位置 * 返回false尚未到达 */ bool positionControl(long targetPosition) { long currentPosition readEncoderCount(); long error targetPosition - currentPosition; if (labs(error) DEAD_ZONE) { setMotor(0); return true; } int controlOutput (int)(KP * error); controlOutput constrain( controlOutput, -MAX_PWM, MAX_PWM ); /* * 小误差时P控制输出可能太小 * 电机无法克服减速箱摩擦因此设置最低PWM。 */ if (controlOutput 0 controlOutput MIN_PWM) { controlOutput MIN_PWM; } if (controlOutput 0 controlOutput -MIN_PWM) { controlOutput -MIN_PWM; } setMotor(controlOutput); return false; } // // 13. 编码器中断 // void encoderISR() { uint8_t currentEncoderState (digitalRead(ENC_A_PIN) 1) | digitalRead(ENC_B_PIN); uint8_t tableIndex (previousEncoderState 2) | currentEncoderState; encoderCount ENCODER_SIGN * QUADRATURE_TABLE[tableIndex]; previousEncoderState currentEncoderState; } /* * Arduino Uno是8位单片机 * 读取4字节long变量时需要临时关闭中断。 */ long readEncoderCount() { noInterrupts(); long value encoderCount; interrupts(); return value; } void writeEncoderCount(long value) { noInterrupts(); encoderCount value; interrupts(); } // // 14. FSR空载校准 // void calibrateFSR() { unsigned long calibrationStart millis(); long pressureSum 0; unsigned int sampleCount 0; while (millis() - calibrationStart FSR_CALIBRATION_TIME) { pressureSum analogRead(FSR_PIN); sampleCount; delay(10); } if (sampleCount 0) { basePressure pressureSum / sampleCount; } currentPressure basePressure; Serial.print(F(FSR空载基准值)); Serial.println(basePressure); Serial.print(F(FSR触发阈值)); Serial.println( basePressure FSR_TRIGGER_OFFSET ); Serial.print(F(FSR释放阈值)); Serial.println( basePressure FSR_RELEASE_OFFSET ); } // // 15. FSR读取和触发判断 // void updatePressure(unsigned long now) { if (now - lastFSRSampleTime FSR_SAMPLE_INTERVAL) { return; } lastFSRSampleTime now; currentPressure analogRead(FSR_PIN); } bool pressureTriggerConfirmed() { int triggerThreshold basePressure FSR_TRIGGER_OFFSET; if (currentPressure triggerThreshold) { if (triggerConfirmCounter TRIGGER_CONFIRM_COUNT) { triggerConfirmCounter; } } else { triggerConfirmCounter 0; } if (triggerConfirmCounter TRIGGER_CONFIRM_COUNT) { triggerConfirmCounter 0; return true; } return false; } bool pressureReleaseConfirmed() { int releaseThreshold basePressure FSR_RELEASE_OFFSET; if (currentPressure releaseThreshold) { if (releaseConfirmCounter RELEASE_CONFIRM_COUNT) { releaseConfirmCounter; } } else { releaseConfirmCounter 0; } if (releaseConfirmCounter RELEASE_CONFIRM_COUNT) { releaseConfirmCounter 0; return true; } return false; } // // 16. 状态切换和故障处理 // void enterState(CutterState newState) { currentState newState; stateStartTime millis(); triggerConfirmCounter 0; releaseConfirmCounter 0; } void enterError(const __FlashStringHelper *message) { setMotor(0); enableMotorDriver(false); currentState STATE_ERROR; Serial.println(message); Serial.println(F(发送R命令进行复位)); } // // 17. 串口调试命令 // void handleSerialCommand() { while (Serial.available() 0) { char command Serial.read(); if (command a command z) { command command - a A; } switch (command) { case C: if (currentState STATE_IDLE) { manualCutRequest true; } break; case S: enterError(F(软件停止命令触发)); break; case R: setMotor(0); enableMotorDriver(true); #if USE_HOME_SWITCH Serial.println(F(故障复位重新回零)); enterState(STATE_HOMING); #else Serial.println(F(故障复位当前位置设为零)); writeEncoderCount(0); enterState(STATE_IDLE); #endif break; case Z: if (currentState STATE_IDLE || currentState STATE_ERROR) { setMotor(0); writeEncoderCount(0); Serial.println(F(当前位置已设为编码器零点)); } break; } } } // // 18. 串口状态打印 // void printStatus(unsigned long now) { if (now - lastPrintTime 250) { return; } lastPrintTime now; Serial.print(F(状态)); Serial.print((int)currentState); Serial.print(F( 编码器)); Serial.print(readEncoderCount()); Serial.print(F( FSR)); Serial.println(currentPressure); }调试校准步骤第一步基础硬件验证不通单片机先测电源给 12V 上电测 LM2596 输出是否为 5V确认无误后再接 Arduino。电机空载测试注释掉回零逻辑直接用setMotor(100)和setMotor(-100)测试正反转确认电机转向、接线无错误。编码器验证打开串口监视器手动转动电机轴观察脉冲数是否连续增减无跳数、无反向。第二步机械参数校准脉冲数标定让电机转一整圈记录encoderCount变化值替换PULSES_PER_REV。行程标定手动让剪刀完全张开记录脉冲数 → 设为OPEN_POSITION手动让剪刀完全闭合刀刃交错到位记录脉冲数 → 设为CLOSE_POSITION压力阈值校准参考你之前的校准方法修改FSR_THRESHOLD。第三步闭环与回零调试先测试回零功能确认触发限位后电机立即停止计数清零。再测试位置闭环发送指令让电机走到指定位置观察是否平稳到位、无振荡。最后联调 FSR 触发验证完整剪切流程。

相关新闻

FS800DTU 开箱分享 — 随手拍随手写的体验贴

FS800DTU 开箱分享 — 随手拍随手写的体验贴

目录 前言1 拆快递:这板子到底长啥样2 接线:全家福来一张3 上电:亮了就是成功了一半4 配置:填几个数就能连上5 跑起来:发个 hello 试试6 几句闲话 前言 手头刚好收到一块飞思创的 FS800DTU 核心板,想着反…

2026/7/23 13:36:34 阅读更多 →
UGEE UE12数位板评测:16K压感手绘屏的专业创作体验

UGEE UE12数位板评测:16K压感手绘屏的专业创作体验

最近在数字绘画和设计领域,很多专业美术生和插画师都在寻找性价比高、性能稳定的手绘设备。作为长期从事数字创作的技术博主,我经常被问到如何选择合适的手绘板或手绘屏。今天就来详细评测一款备受关注的设备——UGEE友基UE12数位板,看看它是…

2026/7/23 7:32:41 阅读更多 →
Web应用UGC内容安全处理:特殊字符过滤与XSS防护实战

Web应用UGC内容安全处理:特殊字符过滤与XSS防护实战

最近在开发一个社交类应用时,遇到了用户昵称和状态动态展示的需求,特别是需要处理一些包含特殊字符、网络流行语或特定组合的文本内容。这类文本往往需要后端进行有效的解析、存储和安全过滤,同时前端要能正确渲染。本文将以一个典型的用户状…

2026/7/24 8:03:39 阅读更多 →

最新新闻

MSP432E4 LCD驱动实战:从硬件连接到图形库适配全解析

MSP432E4 LCD驱动实战:从硬件连接到图形库适配全解析

1. 项目概述如果你正在基于TI的MSP432E4系列微控制器开发带图形界面的嵌入式应用,那么一块稳定、易用的LCD显示屏评估板绝对是你的得力助手。我最近在做一个工业HMI项目,选用了MSP432E411Y作为主控,搭配的正是德州仪器(TI&#xf…

2026/7/24 8:36:50 阅读更多 →
LoRA微调技术:大模型轻量化适配的核心方法

LoRA微调技术:大模型轻量化适配的核心方法

1. LoRA微调技术解析:轻量化适配大模型的黄金法则在自然语言处理领域,全参数微调(Full Fine-Tuning)就像给整个模型做器官移植手术,而LoRA(Low-Rank Adaptation)则更像是精准的靶向治疗。这项由…

2026/7/24 8:36:50 阅读更多 →
AI论文写作工具实测:9款主流工具横向测评与选型建议

AI论文写作工具实测:9款主流工具横向测评与选型建议

1. AI论文写作工具实测背景与选型逻辑 去年帮导师审研究生论文时,发现近三成作业存在明显的AI写作痕迹。这促使我系统测试了市面上主流的9款AI论文辅助工具(包括ChatGPT、Claude、Gemini等国际产品,以及虎贲、笔神等国内专业选手)…

2026/7/24 8:36:50 阅读更多 →
微软PazaBench第二版:非洲语言ASR基准测试工具详解

微软PazaBench第二版:非洲语言ASR基准测试工具详解

微软近期发布了PazaBench第二版,这是一个专门针对非洲语言自动语音识别(ASR)的基准测试工具。对于关注多语言语音技术、本地化部署和资源稀缺语言处理的开发者来说,这个工具提供了重要的评估标准。PazaBench第二版的核心价值在于填…

2026/7/24 8:36:50 阅读更多 →
MSP432P4xx DCO智能时钟系统:从原理到实战的任意频率配置指南

MSP432P4xx DCO智能时钟系统:从原理到实战的任意频率配置指南

1. 项目概述:为什么我们需要一个“聪明”的内部时钟? 在嵌入式开发领域,尤其是基于MSP432这类超低功耗微控制器的项目里,时钟系统往往是决定系统稳定性、功耗和性能的基石。很多开发者,尤其是刚从传统51或STM32平台转过…

2026/7/24 8:36:50 阅读更多 →
AI赋能传统文化:知识图谱与生成模型的技术实践

AI赋能传统文化:知识图谱与生成模型的技术实践

1. 项目背景与核心价值这个项目将华夏文明数千年的智慧积淀与当代人工智能技术进行跨时空对话,通过技术手段实现传统文化元素的数字化重构与创新表达。不同于简单的文化数字化存档,它试图在算法层面建立东方哲学思想与机器学习模型之间的映射关系&#x…

2026/7/24 8:35:49 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻