在创客圈里,用CNC写字机实现自动书写一直是个热门项目。但很多朋友在搭建过程中都会遇到一个共同难题:Z轴的抬笔/落笔控制。专业方案通常使用步进电机,但对于预算有限或空间受限的项目,9G舵机是个更经济实惠的选择。今天我们就来彻底解决这个问题,通过修改GRBL 0.9j源码,让普通舵机完美适配写字机的笔控需求。
首先确保你的开发环境已经准备好:
bash复制# 安装ARM开发工具链
sudo apt-get install gcc-arm-none-eabi
# 克隆GRBL 0.9j源码
git clone -b v0.9j https://github.com/grbl/grbl.git
提示:建议使用PlatformIO进行项目管理,可以自动处理依赖关系
GRBL的控制逻辑主要围绕以下几个关键函数展开:
c复制// 典型调用链示例
main() → protocol_main_loop() → protocol_execute_line() → gc_execute_line()
在原始GRBL中,Z轴控制是通过步进电机实现的。我们需要重点关注以下代码段:
c复制// stepper.c中的定时器中断处理
void TIM3_IRQHandler(void) {
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
// 步进电机脉冲生成逻辑
...
}
}
9G舵机通常使用50Hz PWM信号(周期20ms),其中:
我们需要修改定时器配置来生成适合舵机的PWM信号:
c复制// 定时器配置示例(针对STM32F103)
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 设置72MHz时钟,分频后1MHz计数频率
TIM_TimeBaseStructure.TIM_Period = 19999; // 20ms周期
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1500; // 初始1.5ms脉宽
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
GRBL默认使用绝对坐标,我们需要将其映射到舵机角度:
| G代码Z值 | 舵机角度 | 笔状态 |
|---|---|---|
| >0 | 90° | 抬笔 |
| ≤0 | 0° | 落笔 |
修改gc_execute_line函数中的Z轴处理逻辑:
c复制// 修改后的Z轴处理逻辑
if(axis_command == AXIS_COMMAND_MOTION_MODE) {
if(bit_istrue(axis_words,bit(Z_AXIS))) {
float z_value = gc_block.values[Z_AXIS];
if(z_value > 0) {
// 抬笔位置
TIM_SetCompare1(TIM3, 2500); // 2.5ms脉宽
} else {
// 落笔位置
TIM_SetCompare1(TIM3, 500); // 0.5ms脉宽
}
}
}
舵机抖动问题:
响应延迟问题:
$30参数(最大步进速率)plan_buffer_line()函数中添加延时补偿st_prep_buffer函数中添加预判逻辑,提前准备舵机动作c复制// 舵机平滑移动算法示例
void servo_smooth_move(uint16_t target_ccr) {
uint16_t current = TIM_GetCapture1(TIM3);
while(abs(current - target_ccr) > 10) {
current += (target_ccr > current) ? 10 : -10;
TIM_SetCompare1(TIM3, current);
delay_ms(20);
}
}
以下是关键修改点的完整代码示例:
c复制// 在stepper.c中添加舵机控制函数
void servo_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA6为PWM输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 定时器3 PWM初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseStructure.TIM_Period = 19999;
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1500;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
TIM_CtrlPWMOutputs(TIM3, ENABLE);
}
// 在motion_control.c中修改Z轴处理
void mc_line(float *target, plan_line_data_t *pl_data) {
if(target[Z_AXIS] > 0) {
TIM_SetCompare1(TIM3, 2500); // 抬笔
} else {
TIM_SetCompare1(TIM3, 500); // 落笔
}
// 原有运动控制逻辑...
}
虽然本文聚焦电子改造,但机械适配同样重要:
注意:舵机扭矩有限,建议笔的重量不超过50g
在实际项目中,我发现最经济的方案是用PVC板切割支架,配合热熔胶固定。测试时先用低粘度墨水,避免因机械振动导致墨水晕染。经过3-5次迭代后,书写效果可以接近专业级写字机。