ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于STM32的三轴雕刻机源码解析与步进电机控制实战

基于STM32的三轴雕刻机源码解析与步进电机控制实战 简介基于STM32F103C8T6微控制器的CNC雕刻机源码工程适合嵌入式开发学习者、创客以及数控爱好者使用其核心是将开源Grbl固件移植到STM32平台实现G代码解析与步进电机精确驱动解决自制雕刻机的运动控制难题。压缩包包含255个文件大小约10.11MB其中主要文件类型有C源码、头文件、编译中间产物、Keil工程配置等覆盖gcode、stepper、planner、protocol等关键模块目录结构清晰便于按模块阅读。目前已有1765人下载学习在同类STM32数控源码中具有一定参考价值。通过阅读代码可以掌握STM32的GPIO、定时器、中断等底层外设配置理解Grbl从指令解析到电机脉冲输出的完整流程同时了解如何将软件逻辑与硬件资源有效结合。对于计划自制雕刻机或深入钻研CNC控制原理的读者这份源码提供了从系统移植到功能调试的完整参考可直接基于Keil工程二次开发。 在嵌入式圈子里STM32雕刻机源码一直是需求量很大的项目很多刚入行的朋友拿着网上零散的代码跑不通、改不动最后只能放弃。这篇内容我结合自己做的基于STM32的三轴雕刻机项目把源码设计、步进电机控制、G代码解析、调试踩坑一次性讲清楚代码全部基于标准库Keil5直接打开就能编译硬件用常见的STM32F103C8T6小板加A4988驱动模块看完你不仅能复现还能自己往外扩展。1. 为什么选STM32——雕刻机控制器从Arduino升级的真实原因很多人在入门雕刻机时第一反应是用Arduino因为网上的开源方案多抄起来快。但如果你真正跑过几次加工就会发现Arduino方案有几个很尴尬的瓶颈一是脉冲频率上不去UNO那个16MHz的芯片在高细分、高速度下脉冲输出容易断续电机丢步是常事二是定时器资源太少三轴运动要用3路脉冲输出还要做加减速一个芯片上可用的16位定时器就那么几个留给舵机、主轴、编码器的位置基本没有三是内存太紧张G代码缓冲区稍微长一点就直接溢出。所以折腾过一轮之后我基本把低端方案淘汰了直接换STM32。STM32家里这边的好处做控制的应该都能数得过来72MHz主频对于1kHz到100kHz这个范围的步进脉冲输出来说绰绰有余Flash和RAM的容量可以让我们把整个G代码文件直接缓冲进内存不用频繁读SD卡高级定时器可以生成多路PWM还能硬件上同时更新整个脉冲周期这也是雕刻机核心的插补输出需求。在这个项目里我选的是STM32F103C8T6这个小蓝板为什么选它理由很实际淘宝三五块钱一片引脚足够下载调试直接ST-Link搞定坏了也不心疼。如果你要做更复杂的闭环系统或者带触摸屏那可以换F407系列源码结构基本不用动改一下定时器初始化就行。所以我写这个项目的时候目标很明确用最便宜的芯片做最完整的雕刻机控制方案源码要能让初学者从头看到尾而不是抄一堆散装Demo拼在一起。项目里涉及了USART串口接收、定时器PWM、中断、Flash存储、看门狗等一堆基础外设非常适合拿来做毕业设计或者搞个人DIY这也是我推荐大家用这个项目作为进阶学习的原因。2. 雕刻机源码的整体骨架与模块划分拿到源码第一件事不是编译而是先搞清楚文件结构。雕刻机说白了就是个三轴运动控制器上位机发G代码单片机负责解析、插补、输出脉冲顺便再干点主轴开关、限位检测这类杂活。我源码里按功能拆成了几个模块修改任何一个部分都不用动其他代码。2.1 源码目录结构与功能对照下面这个表是核心目录里各文件的职责我建议你先对着你的板子把外设引脚核对一遍再上电模块文件功能职责对应外设main.c主循环状态机轮询全局调度parser.cG代码逐行解析核心逻辑planner.c运动规划加减速曲线计算核心逻辑stepper.c脉冲输出定时器中断控制TIM2/TIM3/TIM4serial.c串口接收上位机指令USART1spindle.c主轴继电器/调速PWM控制TIM1/GPIOlimit.c限位开关检测与回原点外部中断这样的分层有一个明显的好处调试的时候哪一步出了问题直接定位到模块不用在一坨代码里反复找。很多初学者喜欢把所有逻辑堆在一个main函数里跑通了还好一旦报错就懵了。我建议你从一开始就按这种方式组织哪怕代码多写几个文件编译时间和调试成本都低很多。2.2 主循环的设计思路主循环里我用了典型的状态机结构。单片机没有操作系统不能让解析、插补、显示这些活打架所以要靠一个全局状态变量来切换。代码如下// 主循环状态机 while (1) { switch (sys_state) { case STATE_IDLE: // 空闲状态等待指令 if (rx_data_ready) { sys_state STATE_PARSE; } break; case STATE_PARSE: // 解析缓冲区的G代码行 parse_line(serial_buffer); sys_state STATE_PLAN; break; case STATE_PLAN: // 把解析出的运动指令加入队列 plan_line(target_x, target_y, target_z, feed_rate); sys_state STATE_EXEC; break; case STATE_EXEC: // 使能运动执行剩余动作全部交给中断 stepper_start_move(); sys_state STATE_IDLE; break; } }这个状态机看着简单但它保证了每个周期内只有一个模块在操作共享数据避免了中断和主循环抢数据的竞态问题。这里想提醒一个最关键的问题G代码解析产物不能直接塞进定时器中断里执行一定要经过一个运动规划缓冲区。没有这个缓冲上位机发送速度稍微快一点数据就会在你解析半截的时候被覆盖这是串口类控制项目最典型的故障来源。3. 步进电机控制线序判断与脉冲生成步进电机是整个雕刻机的心脏。我收到过很多私信问“电机不转是怎么回事”排查下来一半以上的原因是线没接对。所以这块我展开写把最容易出错的线序判断和脉冲生成讲透。3.1 步进电机线序怎么区分市面上最常见的两相四线步进电机引出四根线分别是A、A-、B、B-但颜色没有统一标准有的厂家是红蓝绿黑有的是黑白红绿。你拿着厂商说明书当然好办问题是很多DIY玩家手里是拆机电机根本找不到资料。这时候就需要万用表的通断档或者电阻档上场了。判断方法很简单两相电机的两组线圈是互相独立的同一组线圈的两根引线之间阻值很小一般是1到5欧姆左右不同组的引线之间应该是断路阻值无穷大。所以你把四根线两两组合测一遍能测出通路的两个组合就是两组线圈剩下的事就是把A组接在驱动器的A/A-上B组接在B/B-上。如果接反了某个极性电机只会抖动不转这个现象也可以在试转后反向纠正。注意测试前一定要断电否则万用表容易烧掉而且步进电机驱动器上的大电容放电后也可能触发测量误差最好等几秒再接表笔。3.2 脉冲信号怎么精确输出步进电机驱动器A4988也好TMC2209也好接收的实际上是脉冲串一个脉冲走一步。那么单片机就只需要干一件事按固定的节奏给对方发方波。脉冲频率决定了转速脉冲数量决定了位移距离。这里面有个硬性要求脉冲宽度要足够让驱动器识别A4988要求高电平至少5微秒太窄就会丢脉冲。我用的是STM32定时器PWM加更新中断的方式来产生脉冲。初始化代码如下void TIM2_PWM_Init(uint32_t pls_freq) { TIM_TimeBaseInitTypeDef TIM_InitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA0 复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_InitStructure.TIM_Period 72000000 / pls_freq / 2 - 1; TIM_InitStructure.TIM_Prescaler 0; TIM_InitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse TIM_InitStructure.TIM_Period / 2; TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_Cmd(TIM2, ENABLE); }这里我把PWM频率从外部传入做加减速曲线时只需要不停重载自动重装值也就是动态改变脉冲频率就能实现速度平滑变化。注意看主频72MHz分频后我们取的是半周期作为占空比50%的方波这个占空比对于驱动器来说是标准的。如果你用PA0这个引脚它还可以顺便复用定时器2的映射不用额外占一个IO去翻转。3.3 梯形加减速的必要性很多新手一开始会直接给定时器设一个固定的PWM频率让电机一个劲儿跑。低速加工问题不大但速度一高启动瞬间电机丢步雕刻出来的线条就会歪掉。原因很简单步进电机的同步转矩是有限的频率突变时转子跟不上定子磁场就会失步。解决办法是梯形加减速启动时以较低的脉冲频率开始按加速度逐步提高到目标速度结束前再逐步降下来。我代码里的planner.c就是用到了这个思路每个运动段开始时计算加速度然后在一个定时器更新中断里逐级更新脉冲周期让频率曲线呈梯形变化。下面是一个核心计算片段// 梯形加减速的核心更新当前脉冲频率 void stepper_trapezoid_update(void) { int32_t target_freq planner.current_feedrate; int32_t current_freq planner.step_freq; if (current_freq target_freq) { current_freq planner.acceleration_step; if (current_freq target_freq) current_freq target_freq; } else if (current_freq target_freq) { current_freq - planner.acceleration_step; if (current_freq target_freq) current_freq target_freq; } // 重新装载定时器频率 TIM_PrescalerConfig(STEP_TIM, (72000000 / (current_freq * 2)) - 1, TIM_PSCReloadMode_Immediate); planner.step_freq current_freq; }这里acceleration_step的取值很关键要根据你电机的扭矩和机械负载去调。我给的默认值是50Hz/毫秒也就是每毫秒频率增加50Hz这个数值在轻载的铝型材机架上能用。如果你的机床偏重建议减小到20Hz以下否则声音听着没毛病实际早就丢步了。测试方法也很简单把高速移动的雕刻速度从6600mm/min往上涨一格一格的看电机有没有咔咔声和堵转在哪个速度开始丢就说明加速度或目标速度要回调。4. G代码解析与直线插补的实现思路雕刻机的中枢指令是G代码上位机生成的G代码都是文本行比如“G1 X30 Y15 F800”表示以800mm/min的速度走直线到(30,15)这个坐标。单片机的第一件事就是从串口缓冲区一行行把这些文本取出转换成结构化的运动指令。4.1 解析策略逐行处理与容错设计在设计解析器时我建议用最简单可靠的办法逐行读取遇到回车换行结束然后对这一行做关键词匹配。这样即使某一行G代码写错了也能定位那一行报错而不是让整个缓冲区乱掉。伪代码是这样的void parse_line(char *line) { char *token; float target_x current_x, target_y current_y, target_z current_z; uint32_t feed last_feed; // 用strtok分割空格逐段处理 token strtok(line, ); while (token ! NULL) { switch (token[0]) { case G: if (atoi(token 1) 0) mode RAPID; else if (atoi(token 1) 1) mode LINEAR; else mode UNSUPPORTED; break; case X: target_x atof(token 1); break; case Y: target_y atof(token 1); break; case Z: target_z atof(token 1); break; case F: feed atof(token 1); break; default: // 忽略未知指令甚至可以在这里报错 break; } token strtok(NULL, ); } // 解析完成后送入运动规划缓冲 plan_line(target_x, target_y, target_z, feed); }这里有三个坑我踩过你也大概率会踩第一atof和atoi会占用大量Flash和RAM在老款F103C8T6上能省则省可以自己写个轻量级的字符串转浮点函数第二G代码里有些行是注释用分号开头解析器要主动跳过否则会在注释内容里误匹配到坐标关键字第三绝对坐标G90和相对坐标G91模式要区分开不然程序在相对模式下走错了位置整个工件就废了。4.2 直线插补从A点到B点不简单把G代码转成目标坐标后还有一个算法问题怎么让三轴同时协调运动走出一条平滑的直线从数学上讲就是要让各轴的运动速度之比等于坐标增量的比例这就是插补。STM32的内存和主频决定了我们不能跑太高端的Bresenham变种我选的是DDA直线插补算法它对整数运算友好效率高而且能很容易扩展到圆弧插补。DDA的核心思路是在每个固定的时间片里为每个轴分配一个“步进累加器”。每产生一个插补周期累加器的值加上轴的增量和加速度因子超过阈值就输出一个脉冲表示这一轴走一步。代码里的stepper.c就是干这个的// DDA插补每次定时器中断执行 void stepper_dda_interrupt(void) { // X轴累加 dda_x_acc dda_x_step; if (dda_x_acc DDA_THRESHOLD) { dda_x_acc - DDA_THRESHOLD; // 输出X轴一个脉冲 GPIO_SetBits(X_STEP_PORT, X_STEP_PIN); delay_short_us(1); GPIO_ResetBits(X_STEP_PORT, X_STEP_PIN); } // Y轴累加 dda_y_acc dda_y_step; if (dda_y_acc DDA_THRESHOLD) { dda_y_acc - DDA_THRESHOLD; GPIO_SetBits(Y_STEP_PORT, Y_STEP_PIN); delay_short_us(1); GPIO_ResetBits(Y_STEP_PORT, Y_STEP_PIN); } // Z轴同理这里省略 }这个函数里的dda_x_step是预先算好的是根据运动线段长度和每轴分辨率求出来的比例值。注意脉冲宽度不能太短所以用delay_short_us延时1微秒后再拉低这是调试过程中总结出来的保险做法。如果你的驱动器对脉冲宽度要求高可以改成直接把PWM周期控制交给定时器而不是软件翻转但软件翻转的调试更直观套到不同驱动器上兼容性也更好。4.3 F进给速度的真实含义G代码里的F值代表进给速度单位是mm/min但我们输出的是脉冲频率两者之间要经过换算。核心参数有两个电机每转需要的脉冲数也就是驱动器的细分数乘以步进电机步距角对应的脉冲数以及丝杠或同步轮每转的直线行程。比如42步进电机1.8度步距角8细分驱动器2mm导程的丝杠那么电机转一圈要1600个脉冲移动2mm所以每毫米需要800个脉冲。换算公式就是脉冲频率 进给速度(mm/min) × 每毫米脉冲数 / 60。我一般在planner里把这个换算封装好uint32_t feedrate_mmmin_to_hz(float mm_min) { return (uint32_t)(mm_min * pulse_per_mm / 60.0f); }这里pulse_per_mm是一个全局常数根据你的机械结构改一次就行。别小看这个换算很多雕刻机跑出来的尺寸和软件里设计尺寸对不上不是控制板问题而是这个参数错了。测量方法很土但很有效让电机走10000个脉冲用卡尺量实际走了多少毫米反推每毫米脉冲数闭着眼睛算出来的理论值不一定精确实测值最准。5. 工程搭建与调试心得从编译到跑通的全流程源码再干净搭建工程还是绕不开的。我建议直接用标准库而不是HAL库原因很简单标准库代码直观寄存器操作看得见摸得着对学习原理最有帮助。虽然是老技术但在F103上它足够稳定可靠网上资料也最多除了个别新芯片型号不支持绝大多数F1/F4都通用。5.1 新建工程与烧录配置如果你用Keil5新建工程的时候选好STM32F103C8芯片型号然后把源码包里的Core、SYSTEM、HARDWARE这些模块全部加进工程启动文件选择startup_stm32f10x_md.s编译环境就能通。烧录工具我用ST-Link在Options for Target里选ST-Link Debugger接口选SW速度默认4MHz足够。这也是新手最容易卡壳的地方。当你点下下载按钮Keil提示“no stm32 target found! if your product embedsdebug authentication”这类错误时别慌按顺序排查第一检查ST-Link的SWDIO、SWCLK、GND三条线有没有接错松动的杜邦线常常是罪魁祸首第二目标板必须独立供电尤其电机驱动器工作电流大电脑USB口供不起不要用同一个USB口既烧录又给电机供电第三如果芯片BOOT0不小心接到了高电平单片机不会正常运行用户程序也会导致下载失败。大多数“找不到目标”其实就这三件事。5.2 串口通信与G代码上传调试雕刻机时最方便的上位机方式是串口。电脑发文本单片机回OK简单直接。不过要注意一个容易踩的坑STM32的USART接收中断里不能做耗时的解析操作否则一帧数据过来时如果还在处理上一帧缓存就溢出了。我代码里在接收中断中只往环形缓冲区写数据中断时间极短解析操作全在main主循环里做。这样即使波特率调到115200也不会丢数据。上位机工具我推荐用轻量级的串口助手比如SSCOM或者XCOM发送G代码时勾选发送新行否则单片机等不到你的换行符解析器不会触发。调试时一定要开串口日志。我在源码里加了一个debug_printf函数把所有关键状态打印出来收到指令、解析成功、进入运动、运动结束。你在上位机就能实时看到整条指令的流转过程。哪一步没执行日志里一目了然比你拿着示波器满板子戳信号快得多。5.3 延时函数卡死的排查链路项目里如果只是跑一个呼吸灯Demo延时卡死可能注意不到但雕刻机这种既要跑PWM又要跑中断的程序就很容易触发。有个热搜词是“stm32延时函数delay卡死”我的项目里也踩过这里把完整排查思路分享出来。我曾经在电机运动过程中调用延时函数结果程序直接停在那里不动了。我当时的排查链路是这样的先用调试器在线仿真暂停后发现程序停在delay函数的SysTick等待循环里说明SysTick没产生中断。再查SysTick配置发现延时函数里写成了一次性重装ARR而中断服务函数里又手动清了Counter两者冲突导致SysTick中断无法进入。修复方式很简单延时函数不要依赖中断标志位改成查询SysTick的CTRL寄存器的COUNTFLAG位或者直接关掉PWM中断里对SysTick的操作。一句话总结延时函数和高级定时器中断不能共享同一个时基否则相互干扰就是卡死。// 标准库delay函数改为查询COUNTFLAG避免中断冲突 void delay_us(uint32_t nus) { uint32_t temp; SysTick-LOAD nus * SystemCoreClock / 1000000 - 1; SysTick-VAL 0; SysTick-CTRL | SysTick_CTRL_ENABLE_Msk; do { temp SysTick-CTRL; } while ((temp SysTick_CTRL_COUNTFLAG_Msk) 0); SysTick-CTRL ~SysTick_CTRL_ENABLE_Msk; }这个修改在标准库工程里是兼容的直接替换原delay函数即可。不过还是建议在大循环运动控制里尽量减少调用延时函数实在要延时就用状态机去跳转不要让主循环卡在一个地方。6. 从能跑到跑好那些值得继续深挖的优化点源码跑通只代表机器能动离“好用”还差得远。以下是我自己在后续维护过程中总结的几个优化路线按性价比从高到低排下来你按需挑选。6.1 限位开关与自动回原点纸上雕刻和实际雕刻最大的区别在于机器开机时不知道刀具在哪。没有限位开关就只能用手把坐标对到工件的某一个角太低效了。加三个机械限位开关X/Y/Z轴负极限然后写一段回原点逻辑开机后三个轴各自低速往负方向找开关碰到立即反向停止并把该轴坐标清零。这块在代码里就是一个简单的状态机加在limit.c里就行。注意限位开关要接常闭触点这样断线时会被识别为触发属于安全冗余设计。6.2 脱机控制与SD卡读取电脑连着串口雕刻虽然方便但体积大移动不方便。进阶玩法是加一个SD卡模块把生成好的G代码文件直接放卡里按一下按钮机器自己跑完整个流程。要做这个功能你需要给工程加上FATFS文件系统读G代码文件时像读串口一样逐行解析就好了。F1的RAM有点紧张建议G代码文件不要超过几十KB否则缓冲不够。我在源码里预留了spool_file接口就是为这个扩展留的。6.3 兼容Grbl协议换上位机软件不用愁市面上的雕刻机控制软件比如GrblController、CNCjs、LaserGRBL走的是Grbl串口协议虽然我的源码不是Grbl但协议层可以兼容一部分。比如在serical.c里加入对“$H”回原点、“?”状态查询这类指令的响应上位机就能识别并和你的机器交互。这个改造说难也难说简单也简单本质上是把协议解析和运动控制解耦开需要看Grbl官方文档逐条对齐。如果你感兴趣建议先把我的源码跑通再去改协议层否则同时调两套东西很容易精神崩溃。6.4 从开环到闭环的一点思考FOC伺服和闭环步进确实能让雕刻机性能上一个台阶但代价是从代码到接线全部翻新电机也要换成带编码器的版本。我的看法是新手阶段不要碰闭环先把手上的开环系统调出自己的感觉理解丢步和机械谐振等你能把开环跑到接近理想状态时再上闭环这时候你就懂得看哪些参数优化闭环了。7. 关于源码使用的一些个人建议写了这么多最后分享三点我个人体会比较深的东西。第一遇到问题先看机械再看代码。雕刻机很多“控制问题”本质上是机械问题比如连轴器打滑、丝杠间隙、皮带松动这些都会造成定位不准但你查代码怎么改都查不出原因。机器跑起来有异响先拧一遍螺丝说不定就好了。第二“源码”这个价值在于结构而不是能一键跑通。你拿到一个开源项目第一件要做的是把每个模块的入口和出口标注出来而不是急着烧录。烧录前通读一遍代码我用了整整一个下午但之后调试从未再迷茫过。第三研究STM32雕刻机不要只看控制代码雕刻工艺同样重要。主轴转速、刀具类型、进给速度、切深这四个参数是相互影响的代码里只有一个简单的feed输出但怎么组合参数是门手艺。你可以在铝、木、亚克力上分别多试几组参数积累一组自己的工艺表这比盲目调代码有用得多。如果有机会后续我会继续开源圆弧插补和限位回零这些扩展模块也会写一篇关于雕刻路径生成和上位机参数设置的文章。你先把手里的源码跑起来遇到问题随时留言我看到了会回复。本文还有配套的精品资源点击获取
返回列表