1. 项目概述:当GRBL遇上STM32F103C8T6
去年帮朋友改造一台老式雕刻机时,我第一次尝试将GRBL移植到STM32F103C8T6。这个蓝色小板子价格不到20元,却完美替代了原装的Arduino Uno控制板,还实现了脱机运行和OLED状态显示。整个过程踩过不少坑,也积累了些实战经验,今天就把完整方案分享给大家。
GRBL作为开源CNC控制软件,原本运行在AVR架构的Arduino上。STM32F103C8T6作为Cortex-M3内核的MCU,具有72MHz主频和64KB Flash,性能远超传统Arduino。移植后的系统支持:
- 三轴步进电机控制(XYZ轴)
- 通过OLED实时显示加工状态
- 旋转编码器进行手动控制
- 脱离PC独立运行(脱机模式)
- G代码文件存储与调用
特别提醒:STM32的定时器配置与AVR完全不同,移植时需要重写所有硬件相关代码,这是整个项目的技术难点所在。
2. 硬件准备与电路设计
2.1 核心器件选型
我的BOM清单如下表所示,所有元件均来自常见电商平台:
| 器件 | 型号 | 数量 | 备注 |
|---|---|---|---|
| MCU | STM32F103C8T6 | 1 | 最小系统板即可 |
| OLED | SSD1306 128x64 | 1 | I2C接口,0.96寸 |
| 编码器 | EC11 | 1 | 带按键功能 |
| 步进驱动 | A4988 | 3 | 或DRV8825 |
| 存储 | W25Q16 | 1 | 16MB SPI Flash |
2.2 关键电路连接
STM32的引脚分配需要特别注意定时器通道对应关系:
c复制// 步进电机控制引脚
#define X_STEP_PIN PB8 // TIM4_CH3
#define X_DIR_PIN PB9
#define Y_STEP_PIN PB6 // TIM4_CH1
#define Y_DIR_PIN PB7
#define Z_STEP_PIN PA6 // TIM3_CH1
#define Z_DIR_PIN PA7
// OLED I2C接口
#define OLED_SDA PB11
#define OLED_SCL PB10
// 编码器接口
#define ENCODER_A PA0
#define ENCODER_B PA1
#define ENCODER_SW PA2
实测发现:STM32的GPIO驱动能力较强,可以直接驱动OLED模块,但步进电机驱动信号建议增加74HC245缓冲器提升稳定性。
3. GRBL移植关键技术实现
3.1 定时器重配置
原版GRBL使用AVR的16位定时器,移植到STM32需要重写定时器中断服务程序。以X轴为例:
c复制// TIM4配置为步进脉冲生成
void TIM4_Init(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 2000-1; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 72-1; // 1MHz计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC3Init(TIM4, &TIM_OCInitStructure); // X轴对应通道3
TIM_Cmd(TIM4, ENABLE);
}
3.2 运动控制算法适配
GRBL的步进算法需要修改以适应STM32硬件:
- 将原avr/planner.c中的定时器相关代码替换为STM32 HAL库调用
- 重新实现加速度曲线计算函数
- 修改运动缓冲区管理策略(STM32 RAM更大)
关键参数调整经验值:
- 默认脉冲频率:20kHz(周期50us)
- 最小步进间隔:5us
- 缓冲区大小:32个运动块(原版为16)
4. 脱机运行系统搭建
4.1 文件系统实现
使用SPI Flash存储G代码文件,我移植了FatFs文件系统:
c复制// 文件系统初始化
FATFS fs;
FIL file;
FRESULT res;
res = f_mount(&fs, "0:", 1); // 挂载SPI Flash
if(res == FR_NO_FILESYSTEM) {
// 首次使用需要格式化
res = f_mkfs("0:", 0, 0);
if(res == FR_OK) {
f_mount(NULL, "0:", 0); // 卸载后重新挂载
f_mount(&fs, "0:", 1);
}
}
// 文件读取示例
res = f_open(&file, "0:/test.gcode", FA_READ);
if(res == FR_OK) {
char buf[128];
UINT br;
f_read(&file, buf, sizeof(buf), &br);
f_close(&file);
}
4.2 用户交互设计
OLED界面采用分层菜单结构:
- 主界面显示坐标、速度、进度
- 文件管理菜单(列表/打开/删除)
- 参数设置菜单(加速度、微步等)
- 手动控制模式
编码器操作逻辑:
- 旋转:菜单导航/数值调整
- 单击:确认选择
- 长按:返回上级
5. 常见问题与解决方案
5.1 步进电机丢步问题
现象:加工尺寸不准确,特别是圆弧插补时
排查步骤:
- 用示波器检查STEP脉冲是否连续
- 检查DIR信号切换时机(应在STEP下降沿后至少1us)
- 降低最大加速度参数($120=500 → $120=300)
- 增加电源滤波电容(每驱动板加1000uF)
5.2 OLED显示异常
典型故障表现:
- 屏幕全亮:检查I2C地址(通常0x78或0x7A)
- 显示乱码:确认初始化序列正确
- 内容不更新:检查I2C时钟速率(不宜超过400kHz)
调试技巧:先用现成的OLED测试程序验证硬件正常,再集成到GRBL系统中。
6. 系统优化与功能扩展
6.1 性能调优建议
- 启用STM32的硬件FPU加速浮点运算
c复制// 在system_stm32f10x.c中启用
#define __FPU_PRESENT 1
#define __FPU_USED 1
- 使用DMA传输SPI Flash数据
c复制// W25Qxx读取配置DMA
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
DMA_Cmd(DMA1_Channel2, ENABLE);
- 优化运动规划算法循环:
- 将三角函数计算改为查表法
- 使用Q格式定点数替代部分浮点运算
6.2 实用功能扩展
- 无线控制模块(ESP-01S):
c复制// 通过AT指令接收G代码
USART_Cmd(USART1, ENABLE);
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
char cmd = USART_ReceiveData(USART1);
- 刀具长度自动测量:
- 增加限位开关作为探针
- 修改gcodalarms.c实现自动触碰检测
- 加工进度预测:
c复制// 解析G代码时统计总行数
uint32_t total_lines = 0;
while(f_gets(line, sizeof(line), &file)) {
if(!isComment(line)) total_lines++;
}
// 运行时计算进度百分比
progress = (current_line*100)/total_lines;
移植过程中最深的体会是:STM32的定时器配置必须精确匹配GRBL的时序要求,特别是脉冲间隔和方向信号建立时间。建议先用信号发生器模拟步进信号验证驱动器响应,再实际连接电机。完整工程代码已整理在GitHub仓库(链接见文末),包含详细注释和配置文件。
