绘图仪(写字机)——GRBL移植到STM32F407
本文最后更新于2 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

一、GRBL

GRBL 是一款高性能、开源的嵌入式固件,它让廉价的 Arduino 开发板(如 Arduino Uno)能够控制 CNC 机床、激光雕刻机、绘图仪等机器的运动。

但是,搜罗网上各种教程,并没有讲怎么样移植的内容,所以再制作写字机的过程中,写下此篇记录自己的成长。

本内容只会讲怎么移植,不涉及具体内容,如算法等。

二、代码详解

虽然我也不知道算算详解,应该也没有人能看到。

关于创建工程和导入文件不再赘述。

初始工程如下:

创建一个grbl 的文件夹,将从github上下载的grbl源码导入进去。

1、map_cpu.h

map_cpu.h文件为 ATmega328P(Arduino Uno)处理器提供了默认的引脚映射。它是 GRBL 固件的核心配置文件之一,确保固件知道哪个功能使用哪个引脚。

(1)步进脉冲输出引脚

   #define STEP_DDR        DDRD
   #define STEP_PORT       PORTD
   #define X_STEP_BIT     2  // Uno Digital Pin 2
   #define Y_STEP_BIT     3  // Uno Digital Pin 3
   #define Z_STEP_BIT     4  // Uno Digital Pin 4
   #define STEP_MASK       ((1<<X_STEP_BIT)|(1<<Y_STEP_BIT)|(1<<Z_STEP_BIT)) // All step bits

先了解下原本(AVR)的控制模型

DDRx(Data Direction Register):为方向控制,配置引脚为输入或者输出,就好比STM32的输入输出(MODE),模拟输入,推挽输出等。

PORTx输出控制,当引脚设为输出时,控制其电平,类似STM32的ODR(输出数据)寄存器

PINx:这个就不必说了吧,选择某个引脚

关键的点在于,对于原本的 ATmega328P(Arduino Uno)来说,不需要手动”开启时钟”,因为它的时钟系统非常简单且大部分外设默认就是开启的。而要移植到STM32上,那就必须要手动开启时钟了。

我选择将PA0、PA1、PA2作为步进脉冲输出引脚

更改如下:

   // Define step pulse output pins. NOTE: All step bit pins must be on the same port.
   //定义步进脉冲输出引脚。注:所有的步进脉冲引脚必须定义在同一个端口上。
   #define STEP_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOA_CLK_ENABLE() //如果使用CubeIDE,按理说                                                                 //不用,但还是写上吧
   #define STEP_GPIO_PORT        GPIOA
   #define X_STEP_Pin            GPIO_PIN_1  // GPIOA
   #define Y_STEP_Pin            GPIO_PIN_2  //
   #define Z_STEP_Pin            GPIO_PIN_3  //
   #define STEP_MASK             (X_STEP_PIN | Y_STEP_PIN | Z_STEP_PIN)// All step bits
	  // 兼容原GRBL的位定义
	#define X_STEP_BIT      			2
	#define Y_STEP_BIT      			3
	#define Z_STEP_BIT      			4
	#define STEP_MASK    ((1<<X_STEP_BIT)|(1<<Y_STEP_BIT)|(1<<Z_STEP_BIT)) //此处为11100

(2)串口相关

   // Define serial port pins and interrupt vectors.
   #define SERIAL_RX     USART_RX_vect // 串口接收完成中断向量
   #define SERIAL_UDRE   USART_UDRE_vect // 串口数据寄存器空中断向量

额,怎么说呢,跟中断相关,直接删去也行。

因为AVR的中断跟STM32的中断机制不一样,所以不使用这个也没关系,当要保证中断要处理相同的东西即可。后面会在serial.c这个文件里面详细说说。

对了,我打算使用串口1(USART1),对应的引脚是PA9和PA10吧。

(3)步进电机方向控制引脚

   // Define step direction output pins. NOTE: All direction pins must be on the same port.
   //定义方向电平输出引脚。注:所有的方向引脚必须定义在同一个端口上。
   #define DIRECTION_DDR     DDRD
   #define DIRECTION_PORT    PORTD
   #define X_DIRECTION_BIT   5  // Uno Digital Pin 5
   #define Y_DIRECTION_BIT   6  // Uno Digital Pin 6
   #define Z_DIRECTION_BIT   7  // Uno Digital Pin 7

  // 兼容原GRBL的位定义
  #define X_DIRECTION_BIT   				5
  #define Y_DIRECTION_BIT   				6
  #define Z_DIRECTION_BIT   				7
  #define DIRECTION_MASK   ((1<<X_DIRECTION_BIT)|(1<<Y_DIRECTION_BIT)|(1<<Z_DIRECTION_BIT))

与步进脉冲输出引脚类似的配置方式,不过这个是控制步进电机旋转方向的。

我打算分配PA4、PA5、PA6给X、Y、Z。

修改如下:

   // Define step direction output pins. NOTE: All direction pins must be on the same port.
   //定义方向电平输出引脚。注:所有的方向引脚必须定义在同一个端口上。
   #define DIRECTION_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOA_CLK_ENABLE()
   #define DIRECTION_GPIO_PORT   GPIOA
   #define X_DIRECTION_PIN     GPIO_PIN_4      // PA4
   #define Y_DIRECTION_PIN     GPIO_PIN_5      // PA5
   #define Z_DIRECTION_PIN     GPIO_PIN_6      // PA6
   #define DIRECTION_MASK       (X_DIRECTION_PIN | Y_DIRECTION_PIN | Z_DIRECTION_PIN)                                                                   // All direction bits

(4)步进电机驱动器的使能/禁用控制引脚

   // Define stepper driver enable/disable output pin.
   #define STEPPERS_DISABLE_DDR   DDRB
   #define STEPPERS_DISABLE_PORT   PORTB
   #define STEPPERS_DISABLE_BIT   0  // Uno Digital Pin 8
   #define STEPPERS_DISABLE_MASK   (1<<STEPPERS_DISABLE_BIT)

一眼丁真,使能电机驱动模块。没什么好说的。我用的是TMC2209模块,低电平使能。且两个驱动模块共用同一个IO口以保持协调一致。

初始电平为高电平,同时

修改如下:

   // Define stepper driver enable/disable output pin.
   //定义步进电机驱动器使能引脚
   #define STEPPERS_DISABLE_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOB_CLK_ENABLE()
   #define STEPPERS_DISABLE_GPIO_PORT GPIOB
   #define STEPPERS_DISABLE_Pin   GPIO_PIN_0 //PB0
   // 兼容原GRBL的位定义
   #define STEPPERS_DISABLE_BIT                 0
   #define STEPPERS_DISABLE_MASK                 (1 << STEPPERS_DISABLE_BIT)

(5)限位开关(硬限位/归位开关)的硬件配置

   // Define homing/hard limit switch input pins and limit interrupt vectors.
   // NOTE: All limit bit pins must be on the same port, but not on a port with other input pins (CONTROL).
   #define LIMIT_DDR       DDRB
   #define LIMIT_PIN       PINB
   #define LIMIT_PORT       PORTB
   #define X_LIMIT_BIT     1  // Uno Digital Pin 9
   #define Y_LIMIT_BIT     2  // Uno Digital Pin 10
   #ifdef VARIABLE_SPINDLE // Z Limit pin and spindle enabled swapped to access hardware PWM on Pin 11.
     #define Z_LIMIT_BIT   4 // Uno Digital Pin 12
   #else
     #define Z_LIMIT_BIT   3  // Uno Digital Pin 11
   #endif
   #if !defined(ENABLE_DUAL_AXIS)
     #define LIMIT_MASK     ((1<<X_LIMIT_BIT)|(1<<Y_LIMIT_BIT)|(1<<Z_LIMIT_BIT)) // All limit bits
   #endif
   #define LIMIT_INT       PCIE0  // Pin change interrupt enable pin
   #define LIMIT_INT_vect   PCINT0_vect
   #define LIMIT_PCMSK     PCMSK0 // Pin change interrupt register

限位开关用于机器行程的极限位置检测,以及归零操作。使用STM32的外部中断即可,注意要能产生中断的IO口。

2025.12.22:目前来讲,在我能正常运行grbl时,完全没使用过限位开关这一功能(暂时),简单来说,先让代码不报错吧。

修改如下:

   // Define homing/hard limit switch input pins and limit interrupt vectors.
   // NOTE: All limit bit pins must be on the same port, but not on a port with other input pins (CONTROL).
   //定义原点/硬限位开关输入引脚和限位中断向量
   //注意:所有的限位引脚必须使用同一个中断处理函数。
   #define LIMIT_GPIO_CLK_ENABLE()           __HAL_RCC_GPIOC_CLK_ENABLE()
   #define LIMIT_PORT       GPIOC
   #define X_LIMIT_PIN                     GPIO_PIN_1  // PC1
   #define Y_LIMIT_PIN                     GPIO_PIN_2  // PC2
   #define Z_LIMIT_PIN                     GPIO_PIN_3  // PC3
 ​
   #define LIMIT_INT_PORTSOURCE GPIO_PortSourceGPIOC //中断源端口
   #define X_LIMIT_INT_PINSOURCE GPIO_PinSource1   //中断源引脚 注:这三个为标准库风格
   #define Y_LIMIT_INT_PINSOURCE GPIO_PinSource2 //中断源引脚
   #define Z_LIMIT_INT_PINSOURCE GPIO_PinSource3 //中断源引脚
   #define X_LIMIT_INT_LINE EXTI_LINE_1 //中断线
   #define Y_LIMIT_INT_LINE EXTI_LINE_2 //中断线
   #define Z_LIMIT_INT_LINE EXTI_LINE_3 //中断线
   #define X_LIMIT_EXTI_IRQn                 EXTI1_IRQn    //中断请求号
   #define Y_LIMIT_EXTI_IRQn                 EXTI2_IRQn
   #define Z_LIMIT_EXTI_IRQn                 EXTI3_IRQn
   // 物理引脚掩码(用于GPIO操作)
   #define LIMIT_PINS_MASK                   (X_LIMIT_PIN | Y_LIMIT_PIN | Z_LIMIT_PIN)
 ​
   // 中断优先级(必须定义)
   #define LIMIT_IRQ_PRIORITY               0  // 最高优先级
   // 触发边沿(必须定义)
   #define LIMIT_EXTI_TRIGGER               EXTI_TRIGGER_FALLING  // 下降沿触发
 ​
   // 兼容原GRBL的位定义
   #define X_LIMIT_BIT     1
   #define Y_LIMIT_BIT     2
   #define Z_LIMIT_BIT     3
   #define LIMIT_MASK     ((1<<X_LIMIT_BIT)|(1<<Y_LIMIT_BIT)|(1<<Z_LIMIT_BIT)) // All limit bits
 ​

(6)用户控制按钮(操作面板) 的硬件配置

 // Define user-control controls (cycle start, reset, feed hold) input pins.
   // NOTE: All CONTROLs pins must be on the same port and not on a port with other input pins (limits).
   #define CONTROL_DDR       DDRC
   #define CONTROL_PIN       PINC
   #define CONTROL_PORT     PORTC
   #define CONTROL_RESET_BIT         0  // Uno Analog Pin 0
   #define CONTROL_FEED_HOLD_BIT     1  // Uno Analog Pin 1
   #define CONTROL_CYCLE_START_BIT   2  // Uno Analog Pin 2
   #define CONTROL_SAFETY_DOOR_BIT   1  // Uno Analog Pin 1 NOTE: Safety door is shared with feed hold. Enabled by config define.
   #define CONTROL_INT       PCIE1  // Pin change interrupt enable pin
   #define CONTROL_INT_vect PCINT1_vect
   #define CONTROL_PCMSK     PCMSK1 // Pin change interrupt register
   #define CONTROL_MASK     ((1<<CONTROL_RESET_BIT)|(1<<CONTROL_FEED_HOLD_BIT)|(1<<CONTROL_CYCLE_START_BIT)|(1<<CONTROL_SAFETY_DOOR_BIT))
   #define CONTROL_INVERT_MASK   CONTROL_MASK // May be re-defined to only invert certain control pins.

用户控制按钮(操作面板) 的硬件配置,包括循环启动、复位、进给保持等功能按键。这是 CNC 系统的人机交互接口。是不是更复杂了?刚好和LVGL联动?

其实就是就是按某个按键执行某个函数而已,应该是复位/急停、暂停/继续、安全门、专用循环启动。

修改如下:

   // Define user-control controls (cycle start, reset, feed hold) input pins.
   // NOTE: All CONTROLs pins must be on the same port and not on a port with other input pins (limits).
   #define CONTROL_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOB_CLK_ENABLE()
   #define CONTROL_GPIO_PORT             GPIOB
   #define CONTROL_RESET_PIN             GPIO_PIN_4  // PB4
   #define CONTROL_FEED_HOLD_PIN         GPIO_PIN_5  // PB5
   #define CONTROL_CYCLE_START_PIN       GPIO_PIN_6  // PB6
   #define CONTROL_SAFETY_DOOR_Pin GPIO_PIN_7  // PB7
 ​
   #define CONTROL_INT_PORTSOURCE GPIO_PortSourceGPIOB //中断源端口
   #define RESET_INT_PINSOURCE GPIO_PinSource4 //中断源引脚:复位
   #define FEED_HOLD_INT_PINSOURCE GPIO_PinSource5 //中断源引脚:给进保持
   #define CYCLE_START_INT_PINSOURCE   GPIO_PinSource6 //中断源引脚:循环开始
   #define SAFETY_DOOR_INT_PINSOURCE   GPIO_PinSource7 //中断源引脚:紧急停车
   #define RESET_INT_LINE EXTI_LINE_4 //中断线:复位
   #define FEED_HOLD_INT_LINE EXTI_LINE_5 //中断线:给进保持
   #define CYCLE_START_INT_LINE   EXTI_LINE_6 //中断线:循环开始
   #define SAFETY_DOOR_INT_LINE   EXTI_LINE_7 //中断线:紧急停车
 ​
   // 中断请求号
   #define RESET_EXTI_IRQn               EXTI4_IRQn       // EXTI线4独立中断
   #define FEED_HOLD_EXTI_IRQn           EXTI9_5_IRQn     // EXTI线5-9共享中断
   #define CYCLE_START_EXTI_IRQn         EXTI9_5_IRQn     // 同上
   #define SAFETY_DOOR_EXTI_IRQn         EXTI9_5_IRQn     // 同上
 ​
   // 兼容原GRBL的位定义
   #define CONTROL_RESET_BIT         0  // Uno Analog Pin 0
   #define CONTROL_FEED_HOLD_BIT     1  // Uno Analog Pin 1
   #define CONTROL_CYCLE_START_BIT   2  // Uno Analog Pin 2
   #define CONTROL_SAFETY_DOOR_BIT   3  // Uno Analog Pin 1 NOTE: Safety door is shared with feed hold. Enabled by config define.
   #define CONTROL_MASK     ((1<<CONTROL_RESET_BIT)|(1<<CONTROL_FEED_HOLD_BIT)|(1<<CONTROL_CYCLE_START_BIT)|(1<<CONTROL_SAFETY_DOOR_BIT))
   #define CONTROL_INVERT_MASK   CONTROL_MASK // May be re-defined to only invert certain control pins.

(7)探针(Probe)开关的输入引脚

   // Define probe switch input pin.
   //定义探测开关输入引脚
   #define PROBE_DDR       DDRC
   #define PROBE_PIN       PINC
   #define PROBE_PORT     PORTC
   #define PROBE_BIT       5  // Uno Analog Pin 5
   #define PROBE_MASK     (1<<PROBE_BIT)

其实我的写字机应该用不上,但万一用到呢?所以还是写上了。、

2025.12.22:确实没用上

如下:

   // Define probe switch input pin.
   //定义探测开关输入引脚
   #define PROBE_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOB_CLK_ENABLE()
   #define PROBE_Pin                     GPIO_PIN_1
   #define PROBE_GPIO_Port GPIOB
 ​
   // 兼容原GRBL的位定义
   #define PROBE_BIT       5  // Uno Analog Pin 5
   #define PROBE_MASK     (1<<PROBE_BIT)

最后,再添加一段写字机专用的宏:

 // 写字机专用:抬笔/落笔控制
 #define PEN_SERVO_PWM_GPIO_PORT    GPIOA
 #define PEN_SERVO_PWM_PIN          GPIO_PIN_8      // PA8,定时器1通道1
 #define PEN_UP_POSITION            1500           // 舵机角度:抬笔
 #define PEN_DOWN_POSITION          1000           // 舵机角度:落笔

2025.12.22:也没用上

先来总结一下IO口的分配

GPIOEXTIMODE宏定义
PA1输出X_STEP_Pin
PA2输出Y_STEP_Pin
PA3输出Z_STEP_Pin
PA4输出X_DIRECTION_PIN
PA5输出Y_DIRECTION_PIN
PA6输出Z_DIRECTION_PIN
PA7输出PEN_SERVO_PWM_PIN
PB0输出STEPPERS_DISABLE_Pin
PB1输出PROBE_Pin
PB4EXTI_LINE_4下拉输入CONTROL_RESET_PIN
PB5EXTI_LINE_5下拉输入CONTROL_FEED_HOLD_PIN
PB6EXTI_LINE_6下拉输入CONTROL_CYCLE_START_PIN
PB7EXTI_LINE_7下拉输入CONTROL_SAFETY_DOOR_Pin
PC1EXTI_LINE_1下拉输入X_LIMIT_PIN
PC2EXTI_LINE_2下拉输入Y_LIMIT_PIN
PC3EXTI_LINE_3下拉输入Z_LIMIT_PIN

到此,宏定义的修改就差不多了,接下来到各函数的修改。

2、coolant_control.c

对于写字机项目,冷却液(coolant)功能不需要。报错就删掉。

3、eeprom.c

EEPROM 操作函数,用于读写非易失性存储器(存储 GRBL 的配置参数)。

暂时用STM32的Flash来模拟EEPROM。

重构unsigned char eeprom_get_char( unsigned int addr )void eeprom_put_char( unsigned int addr, unsigned char new_value )即可。

2025.12.22:就EEPROM而言,目前还没搞好,使用Flash来模拟EEPROM的话,要考虑到STM32中,要擦除某一段数据,就需要整个扇区进行擦除,很麻烦,现在而言只不过让代码不报错而已,后面应该会换成专用的EEPROM芯片,如24C64等。

如下:

 void Flash_WriteData( unsigned int addr, unsigned char new_value )
 {
  HAL_FLASH_Unlock();
  uint32_t PageError = 0;
  __disable_irq(); //关闭全局中断
 ​
  if(HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) == HAL_OK) //擦除
  {
 ​
 ​
  }
 ​
  __enable_irq(); //开全局中断
 ​
 ​
  HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, new_value);
 ​
  HAL_FLASH_Lock();
 ​
 }
 ​
 uint_8 Flash_ReadData( unsigned int addr )
 {
  return *(__IO uint16_t*)addr;
 ​
 }
 unsigned char eeprom_get_char( unsigned int addr )
 {
  unsigned char data;
  data = Flash_ReadData(addr);
  return data;
 ​
 }
 ​
 ​
 void eeprom_put_char( unsigned int addr, unsigned char new_value )
 {
  Flash_WriteData(addr, new_value);
 ​
 }

4、gcode.c

整个GRBL中的核心文件,用来解析G代码命令,负责将文本形式的 G 代码转换为机器能够执行的内部指令。

不需要修改。

5、jog.c

这个文件处理 手动控制移动 功能,允许用户通过手动控制(如按钮、摇杆)或 G 代码命令($J=)来移动机器,而不需要执行完整的 G 代码程序。不需要修改

6、limits.c

唉,应该是改动量最大的一个文件了?

2025.12.22:目前为止,使用的是软限位,还未添加硬件限位功能,

limits.cGRBL 的限位开关和归位(Homing)功能的核心实现文件。这个文件非常重要,负责机器的安全保护和自动零点定位。

接下来分点来说说要修改的函数

(1)void limits_init()

这个函数其实就是对引脚的初始化配置,如果用的STM32CubeIDE,应该是自动配置完成了,就是要注意初始电平是什么。

不只是要配置引脚,还要对中断进行配置。

 void limits_init()
 {
 ​
   if (bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE)) {
   HAL_NVIC_EnableIRQ(X_LIMIT_EXTI_IRQn);
   HAL_NVIC_EnableIRQ(Y_LIMIT_EXTI_IRQn);
   HAL_NVIC_EnableIRQ(Z_LIMIT_EXTI_IRQn);
  } else {
     limits_disable();
  }
 ​
   //需要去抖?
   //注意:中断配置已经在CubeIDE配置好了,这里主要是使能外部中断和关闭中断
   // 如果不是使用STM32CubeIDE或STM32CubeMX,需要自己配置中断
 }

(2)void limits_disable()

关闭中断,没什么好说的

 // Disables hard limits.
 void limits_disable()
 {
  HAL_NVIC_DisableIRQ(X_LIMIT_EXTI_IRQn);
  HAL_NVIC_DisableIRQ(Y_LIMIT_EXTI_IRQn);
  HAL_NVIC_DisableIRQ(Z_LIMIT_EXTI_IRQn);
 }

(3)uint8_t limits_get_state()

这是一个 GRBL 中获取限位开关状态 的函数,用于检测限位开关的状态,并返回一个表示哪个轴触发了限位的位掩码。

 uint8_t limits_get_state()
 {
   uint8_t limit_state = 0;
   uint8_t pin = (uint8_t)(((HAL_GPIO_ReadPin(LIMIT_PORT, X_LIMIT_PIN) << X_LIMIT_BIT) | \
    (HAL_GPIO_ReadPin(LIMIT_PORT, Y_LIMIT_PIN)  << Y_LIMIT_BIT) | \
    (HAL_GPIO_ReadPin(LIMIT_PORT, Z_LIMIT_PIN)  << Z_LIMIT_BIT)) & LIMIT_MASK);
 ​
  #ifdef INVERT_LIMIT_PIN_MASK
  pin ^= INVERT_LIMIT_PIN_MASK;
  #endif
   if (bit_isfalse(settings.flags,BITFLAG_INVERT_LIMIT_PINS)) { pin ^= LIMIT_MASK; }
   if (pin) {
     uint8_t idx;
     for (idx=0; idx<N_AXIS; idx++) {
       if (pin & get_limit_pin_mask(idx)) { limit_state |= (1 << idx); }
    }
     #ifdef ENABLE_DUAL_AXIS
       if (pin & (1<<DUAL_LIMIT_BIT)) { limit_state |= (1 << N_AXIS); }
     #endif
  }
   return(limit_state);
 }

(4)ISR(LIMIT_INT_vect)

AVR的中断服务例程(ISR),用于处理限位引脚变化中断。

需要改造成适配STN32的中断

如下:

 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
 {
  switch(GPIO_Pin)
  {
  case X_LIMIT_Pin:
     if (sys.state != STATE_ALARM) {
       if (!(sys_rt_exec_alarm)) {
         #ifdef HARD_LIMIT_FORCE_STATE_CHECK
           // Check limit pin state.
           if (limits_get_state()) {
             mc_reset(); // Initiate system kill.
             system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event
          }
         #else
           mc_reset(); // Initiate system kill.
           system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event
         #endif
      }
    }
     break;
 ​
  case Y_LIMIT_Pin:
     if (sys.state != STATE_ALARM) {
       if (!(sys_rt_exec_alarm)) {
         #ifdef HARD_LIMIT_FORCE_STATE_CHECK
           // Check limit pin state.
           if (limits_get_state()) {
             mc_reset(); // Initiate system kill.
             system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event
          }
         #else
           mc_reset(); // Initiate system kill.
           system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event
         #endif
      }
    }
     break;
 ​
  case Z_LIMIT_Pin:
     if (sys.state != STATE_ALARM) {
       if (!(sys_rt_exec_alarm)) {
         #ifdef HARD_LIMIT_FORCE_STATE_CHECK
           // Check limit pin state.
           if (limits_get_state()) {
             mc_reset(); // Initiate system kill.
             system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event
          }
         #else
           mc_reset(); // Initiate system kill.
           system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event
         #endif
      }
    }
     break;
 ​
 ​
  }
 ​
  //暂未消抖
 }
 ​

7、motion_control.c

这个文件包含了运动控制的核心算法,比如直线插补、圆弧插补等。由于这些算法通常是数学计算,不直接依赖于硬件,因此该代码无需修改。

8、nuts_bolts.c

主要包含 GRBL 的通用工具函数和宏定义,里面有几个延时函数需要替换为STM32的。

9、planner.c

主要涉及数学计算和数据结构操作,通常不需要修改

10、print.c

主要是格式化输出逻辑,需要修改的在serial.c这个文件。

修改void printPgmString(const char *s)如下即可:

 // Print a string stored in PGM-memory
 void printPgmString(const char *s)
 {
   printString(s);
 }

11、prode.c

这是 GRBL 的探针功能文件,其实就是初始化GPIO的一些代码,对于我的写字机来说暂时不需要,删掉报错的就好。

12、protocol.c

这个文件是 GRBL 的 G 代码解析和通信协议处理核心,通常大部分不需要修改

13、report.c

这个文件是 GRBL 的状态报告和消息发送模块

在标准C中,PSTR是一个宏,通常用于AVR编程,它将字符串字面量放入程序存储器(Flash)中,并通过指针访问。

定义以下宏即可

 #define PSTR(str) (str)

14、serial.c

该文件直接处理串口通信硬件层,需要修改

(1)serial_init()

串口初始化函数,如果使用STM32CubeIDE或MX可能方便点,如果是Keil就得自己手动配置,还要有中断配置

(2)void serial_write(uint8_t data)

将AVR的串口中断使能换成STM32的即可,如下:

 // Writes one byte to the TX serial buffer. Called by main program.
 void serial_write(uint8_t data) {
  // Calculate next head
  uint8_t next_head = serial_tx_buffer_head + 1;
  if (next_head == TX_RING_BUFFER) { next_head = 0; }
 ​
  // Wait until there is space in the buffer
  while (next_head == serial_tx_buffer_tail) {
    // TODO: Restructure st_prep_buffer() calls to be executed here during a long print.
    if (sys_rt_exec_state & EXEC_RESET) { return; } // Only check for abort to avoid an endless loop.
  }
 ​
  // Store data and advance head
  serial_tx_buffer[serial_tx_buffer_head] = data;
  serial_tx_buffer_head = next_head;
 ​
  // Enable Data Register Empty Interrupt to make sure tx-streaming is running
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE); //
 }

记得引入stm32的头文件

(3)中断相关函数

没使用回调函数。因为回调函数是在中断完成时调用,与grbl 的逻辑不符。

 void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */


	if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE) &&  //发�??
			__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TXE))
	{
		uint8_t tail = serial_tx_buffer_tail;
		USART1->DR = serial_tx_buffer[tail]; //将数据传给发送寄存器
		tail++;
		if (tail == TX_RING_BUFFER) { tail = 0; } //当数据下标为105时,重置下标
		serial_tx_buffer_tail = tail;
		// Turn off Data Register Empty Interrupt to stop tx-streaming if this concludes the transfer
		if (tail == serial_tx_buffer_head) { __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE); } //发�?�完成就关闭中断
	}

	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) &&  //接收
				__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE))
	{
		uint8_t data = USART1->DR; //读取�??个数据并赋给data
		//HAL_UART_Transmit(&huart1, &data, 1, 1000);
		uint8_t next_head;
			switch (data)
			{
			  case CMD_RESET:         mc_reset(); break; // Call motion control reset routine.
			  case CMD_STATUS_REPORT: system_set_exec_state_flag(EXEC_STATUS_REPORT); break; // Set as true
			  case CMD_CYCLE_START:   system_set_exec_state_flag(EXEC_CYCLE_START); break; // Set as true
			  case CMD_FEED_HOLD:     system_set_exec_state_flag(EXEC_FEED_HOLD); break; // Set as true
			  default :
				if (data > 0x7F) { // Real-time control characters are extended ACSII only.
				  switch(data) {
					case CMD_SAFETY_DOOR:   system_set_exec_state_flag(EXEC_SAFETY_DOOR); break; // Set as true
					case CMD_JOG_CANCEL:
					  if (sys.state & STATE_JOG) { // Block all other states from invoking motion cancel.
						system_set_exec_state_flag(EXEC_MOTION_CANCEL);
					  }
					  break;

					case CMD_FEED_OVR_RESET: system_set_exec_motion_override_flag(EXEC_FEED_OVR_RESET); break;
					case CMD_FEED_OVR_COARSE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_PLUS); break;
					case CMD_FEED_OVR_COARSE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_MINUS); break;
					case CMD_FEED_OVR_FINE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_PLUS); break;
					case CMD_FEED_OVR_FINE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_MINUS); break;
					case CMD_RAPID_OVR_RESET: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_RESET); break;
					case CMD_RAPID_OVR_MEDIUM: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_MEDIUM); break;
					case CMD_RAPID_OVR_LOW: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_LOW); break;
					case CMD_SPINDLE_OVR_RESET: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_RESET); break;
					case CMD_SPINDLE_OVR_COARSE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_PLUS); break;
					case CMD_SPINDLE_OVR_COARSE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_MINUS); break;
					case CMD_SPINDLE_OVR_FINE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_PLUS); break;
					case CMD_SPINDLE_OVR_FINE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_MINUS); break;
					case CMD_SPINDLE_OVR_STOP: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_STOP); break;
					case CMD_COOLANT_FLOOD_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_FLOOD_OVR_TOGGLE); break;
					#ifdef ENABLE_M7
					  case CMD_COOLANT_MIST_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_MIST_OVR_TOGGLE); break;
					#endif
				  }
				  // Throw away any unfound extended-ASCII character by not passing it to the serial buffer.
				} else { // Write character to buffer
				  next_head = serial_rx_buffer_head + 1;
				  if (next_head == RX_RING_BUFFER) { next_head = 0; }

				  // Write data to buffer unless it is full.
				  if (next_head != serial_rx_buffer_tail) {
					serial_rx_buffer[serial_rx_buffer_head] = data;
					serial_rx_buffer_head = next_head;
				  }
				}
		}
	}

  /* USER CODE END USART1_IRQn 1 */
}

15、settings.c

把__flash删掉。

基本不需要修改。

16、spindle_control.c

是 GRBL 的主轴控制模块,负责控制 CNC 主轴(铣床/雕刻机的主轴电机)。

可以替换为舵机进行修改。

2025.12.22:最后使用Z轴映射为舵机

17、stepper.c

对于 stepper.c 文件,这是 GRBL 中最关键、最复杂、实时性要求最高的文件。需要大量修改,因为它直接控制步进电机的精确脉冲时序。

(1)st_wake_up()

唤醒空闲状态下的步进电机系统,使其准备执行新的运动命令

 void st_wake_up()
 {
   // Enable stepper drivers. 使能步进驱动
   if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE))
  {
   //STEPPERS_DISABLE_GPIO_PORT |= (1<<STEPPERS_DISABLE_BIT); //
   HAL_GPIO_WritePin(STEPPERS_DISABLE_GPIO_PORT, STEPPERS_DISABLE_Pin, 0);
 ​
  }
   else
  {
   //STEPPERS_DISABLE_GPIO_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
   HAL_GPIO_WritePin(STEPPERS_DISABLE_GPIO_PORT, STEPPERS_DISABLE_Pin, 0);
  }
 ​
   // Initialize stepper output bits to ensure first ISR call does not step.
   st.step_outbits = step_port_invert_mask;
 ​
   // Initialize step pulse timing from settings. Here to ensure updating after re-writing.
   #ifdef STEP_PULSE_DELAY
     // Set total step pulse time after direction pin set. Ad hoc computation from oscilloscope.
     st.step_pulse_time = -(((settings.pulse_microseconds+STEP_PULSE_DELAY-2)*TICKS_PER_MICROSECOND) >> 3);
     // Set delay between direction pin write and step command.
     OCR0A = -(((settings.pulse_microseconds)*TICKS_PER_MICROSECOND) >> 3);
   #else // Normal operation
     // Set step pulse time. Ad hoc computation from oscilloscope. Uses two's complement.
     st.step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3);
   #endif
 ​
   // Enable Stepper Driver Interrupt
     HAL_TIM_Base_Start_IT(&htim2);
 }

(2)void st_go_idle()

使步进电机系统进入空闲状态,停止当前的运动

 void st_go_idle()
 {
   // Disable Stepper Driver Interrupt. Allow Stepper Port Reset Interrupt to finish, if active.
   HAL_TIM_Base_Stop_IT(&htim2); // //禁用定时器2中断
 ​
   busy = false;
 ​
   // Set stepper driver idle state, disabled or enabled, depending on settings and circumstances.
   bool pin_state = false; // Keep enabled. 保持启用状态
   if (((settings.stepper_idle_lock_time != 0xff) || sys_rt_exec_alarm || sys.state == STATE_SLEEP) && sys.state != STATE_HOMING) {
     // Force stepper dwell to lock axes for a defined amount of time to ensure the axes come to a complete
     // stop and not drift from residual inertial forces at the end of the last movement.
  MyDelay_ms(settings.stepper_idle_lock_time);
     pin_state = true; // Override. Disable steppers. 手动干预:禁用步进电机
  }
   if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { pin_state = !pin_state; } // Apply pin invert. 引脚取反
   if (pin_state) { HAL_GPIO_WritePin(STEPPERS_DISABLE_GPIO_PORT, STEPPERS_DISABLE_Pin, 1); }
   else { HAL_GPIO_WritePin(STEPPERS_DISABLE_GPIO_PORT, STEPPERS_DISABLE_Pin, 1); }
 }

(3)ISR(TIMER1_COMPA_vect)

这个就是Arduino Uno的中断,是步进电机脉冲生成的核心中断服务程序,由AVR单片机的Timer1比较匹配A中断触发,负责生成精确的步进脉冲和方向控制信号。

需要替换为STM32的中断

先写一个在中断操作的函数:

 void stepper_timer_interrupt(void)
 {
     // 防重入检查(与原始代码相同)
     if (busy) { return; }
 ​
     // 在步进脉冲引脚前设置方向引脚(提前几个纳秒)
     // 原始: DIRECTION_PORT = (DIRECTION_PORT & ~DIRECTION_MASK) | (st.dir_outbits & DIRECTION_MASK);
     HAL_GPIO_WritePin(DIRECTION_GPIO_PORT, X_DIRECTION_PIN,
                      (st.dir_outbits & (1<<X_DIRECTION_BIT)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
     HAL_GPIO_WritePin(DIRECTION_GPIO_PORT, Y_DIRECTION_PIN,
                      (st.dir_outbits & (1<<Y_DIRECTION_BIT)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
     HAL_GPIO_WritePin(DIRECTION_GPIO_PORT, Z_DIRECTION_PIN,
                      (st.dir_outbits & (1<<Z_DIRECTION_BIT)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
 ​
     // 然后产生步进脉冲
     // 原始: STEP_PORT = (STEP_PORT & ~STEP_MASK) | st.step_outbits;
     HAL_GPIO_WritePin(STEP_GPIO_PORT, X_STEP_Pin,
                      (st.step_outbits & (1<<X_STEP_BIT)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
     HAL_GPIO_WritePin(STEP_GPIO_PORT, Y_STEP_Pin,
                      (st.step_outbits & (1<<Y_STEP_BIT)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
     HAL_GPIO_WritePin(STEP_GPIO_PORT, Z_STEP_Pin,
                      (st.step_outbits & (1<<Z_STEP_BIT)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
 ​
     // 测试
 //   HAL_GPIO_WritePin(DIRECTION_GPIO_PORT, X_DIRECTION_PIN, 1);
 //
 //   HAL_GPIO_WritePin(STEP_GPIO_PORT, X_STEP_Pin,
 //                     GPIO_PIN_SET);
 //   HAL_GPIO_WritePin(STEP_GPIO_PORT, Y_STEP_Pin,
 //   GPIO_PIN_SET);
 //   HAL_GPIO_WritePin(STEP_GPIO_PORT, Z_STEP_Pin,
 //   GPIO_PIN_SET);
 ​
 ​
 ​
     // 启用步进脉冲复位定时器(TIM2),在settings.pulse_microseconds微秒后复位信号
     //HW_TIM_PortResetInterrupt_ValueConfig(8, st.step_pulse_time);
     //__HAL_TIM_SET_AUTORELOAD(&htim3, st.exec_segment->cycles_per_tick);
 ​
     __HAL_TIM_SET_COUNTER(&htim3, 0); //设置定时器3的初值为0
     HAL_TIM_Base_Start_IT(&htim3); //使能定时器3,在10us后拉低步进引脚
 ​
     busy = true; //原为true
     __enable_irq(); // 重新启用中断,允许脉冲宽度定时器中断准时触发
 ​
     //舵机
     if(st.steps[X_AXIS]==0 && st.steps[Y_AXIS]==0 && st.steps[Z_AXIS]!=0)
    {
    if((st.dir_outbits & (1<<Z_DIRECTION_BIT)) != 0)
    {
    //启动舵机?
    Servo_SetPWM(45);
 ​
    }
 ​
    if((st.dir_outbits & (1<<Z_DIRECTION_BIT)) == 0)
    {
      //启动舵机?
      Servo_SetPWM(130);
 ​
    }
 ​
    }
 ​
 //   else
 //   {
 //   Servo_SetPWM(45);
 //
 //   }
 ​
     // 如果没有步进段,尝试从步进缓冲区弹出一个
     if (st.exec_segment == NULL) {
         if (segment_buffer_head != segment_buffer_tail) {
             // 初始化新的步进段
             st.exec_segment = &segment_buffer[segment_buffer_tail];
 ​
 #ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
             // 禁用AMASS时,为慢步进频率(<250Hz)设置定时器预分频
             // 原始: TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (st.exec_segment->prescaler<<CS10);
             uint32_t prescaler;
             switch (st.exec_segment->prescaler) {
                 case 1: prescaler = 1; break;
                 case 8: prescaler = 8; break;
                 case 64: prescaler = 64; break;
                 case 256: prescaler = 256; break;
                 case 1024: prescaler = 1024; break;
                 default: prescaler = 8; break;
            }
             // 转换到STM32: 2MHz时钟,保持相同频率
             HW_TIM_DriverInterrupt_ValueConfig(prescaler, st.exec_segment->cycles_per_tick);
 #else
             // 启用AMASS
             //HW_TIM_DriverInterrupt_ValueConfig(8, st.exec_segment->cycles_per_tick>>1);
             //__HAL_TIM_SET_AUTORELOAD(&htim2, st.exec_segment->cycles_per_tick - 1);
             __HAL_TIM_SET_AUTORELOAD(&htim2, st.exec_segment->cycles_per_tick - 1);
             __HAL_TIM_SET_COUNTER(&htim2, 0);
 #endif
 ​
             st.step_count = st.exec_segment->n_step;
 ​
             // 如果新段开始一个新的规划块,初始化步进变量和计数器
             if (st.exec_block_index != st.exec_segment->st_block_index) {
                 st.exec_block_index = st.exec_segment->st_block_index;
                 st.exec_block = &st_block_buffer[st.exec_block_index];
 ​
                 // 初始化Bresenham线和距离计数器
                 st.counter_x = st.counter_y = st.counter_z = (st.exec_block->step_event_count >> 1);
            }
             // 这里更新方向位!
             st.dir_outbits = st.exec_block->direction_bits ^ dir_port_invert_mask;
 ​
 #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
             // 启用AMASS时,根据AMASS级别调整Bresenham轴增量计数器
             st.steps[X_AXIS] = st.exec_block->steps[X_AXIS] >> st.exec_segment->amass_level;
             st.steps[Y_AXIS] = st.exec_block->steps[Y_AXIS] >> st.exec_segment->amass_level;
             st.steps[Z_AXIS] = st.exec_block->steps[Z_AXIS] >> st.exec_segment->amass_level;
 #endif
        } else {
             // 段缓冲区空,关闭
             st_go_idle(); //结束,会关闭定时器2中断
             system_set_exec_state_flag(EXEC_CYCLE_STOP);
             return;
        }
    }
 ​
     // 检查探测状态
     if (sys_probe_state == PROBE_ACTIVE) {
         probe_state_monitor();
    }
 ​
     // 复位步出位
     st.step_outbits = 0;
 ​
     // 通过Bresenham线算法执行步进位移轮廓
 #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
     st.counter_x += st.steps[X_AXIS];
 #else
     st.counter_x += st.exec_block->steps[X_AXIS];
 #endif
     if (st.counter_x > st.exec_block->step_event_count) {
         st.step_outbits |= (1<<X_STEP_BIT);
         st.counter_x -= st.exec_block->step_event_count;
         if (st.exec_block->direction_bits & (1<<X_DIRECTION_BIT)) {
             sys_position[X_AXIS]--;
        } else {
             sys_position[X_AXIS]++;
        }
    }
 ​
 #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
     st.counter_y += st.steps[Y_AXIS];
 #else
     st.counter_y += st.exec_block->steps[Y_AXIS];
 #endif
     if (st.counter_y > st.exec_block->step_event_count) {
         st.step_outbits |= (1<<Y_STEP_BIT);
         st.counter_y -= st.exec_block->step_event_count;
         if (st.exec_block->direction_bits & (1<<Y_DIRECTION_BIT)) {
             sys_position[Y_AXIS]--;
        } else {
             sys_position[Y_AXIS]++;
        }
    }
 ​
 #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
     st.counter_z += st.steps[Z_AXIS];
 #else
     st.counter_z += st.exec_block->steps[Z_AXIS];
 #endif
     if (st.counter_z > st.exec_block->step_event_count) {
         st.step_outbits |= (1<<Z_STEP_BIT);
         st.counter_z -= st.exec_block->step_event_count;
         if (st.exec_block->direction_bits & (1<<Z_DIRECTION_BIT)) {
             sys_position[Z_AXIS]--;
        } else {
             sys_position[Z_AXIS]++;
        }
    }
 ​
     // 回零周期中,锁定并防止所需轴移动
     if (sys.state == STATE_HOMING) {
         st.step_outbits &= sys.homing_axis_lock;
    }
 ​
     // 减少步事件计数
     st.step_count--;
     if (st.step_count == 0) {
         // 段完成,丢弃当前段并推进段索引
         st.exec_segment = NULL;
         if (++segment_buffer_tail == SEGMENT_BUFFER_SIZE) {
             segment_buffer_tail = 0;
        }
    }
 ​
     // 应用步端口反转掩码
     st.step_outbits ^= step_port_invert_mask;
     busy = false;
 }

该函数需要放在中断函数里面

(4)ISR(TIMER0_OVF_vect)

该函数是是定时器0溢出中断服务程序。

其实就是在stepper_timer_interrupt()(原ISR(TIMER1_COMPA_vect))函数执行时,启动定时器1来产生一个脉冲,然后在定时器1的中断程序中拉高对应引脚的电平,同时使能定时器0,定时器0在10us后产生中断,将引脚拉低 ,以此来产生脉冲信号。

对应的中断操作函数如下:

使用定时器的回调函数,这里我是用定时器2来代替AVR中的定时器1,定时器3代替AVR中的定时器0

 //定时器回调函数
 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
 {
      if (htim->Instance == TIM2)
      {
             // TIM2更新中断 - 步进定时
             stepper_timer_interrupt();
      }
      else if (htim->Instance == TIM3)
      {
          //10us已到 拉低引脚
          HAL_GPIO_WritePin(STEP_GPIO_PORT, X_STEP_Pin, 0);
          HAL_GPIO_WritePin(STEP_GPIO_PORT, Y_STEP_Pin, 0);
 //       HAL_GPIO_WritePin(STEP_GPIO_PORT, Z_STEP_Pin, 0);
          HAL_TIM_Base_Stop_IT(&htim3); //关闭定时器3
      }
 ​
 }

(5)void st_reset(void)

用于复位步进电机控制系统

复位倒没啥好说的,如下:

 void st_reset()
 {
   // Initialize stepper driver idle state.
   st_go_idle();
 ​
   // Initialize stepper algorithm variables.
   memset(&prep, 0, sizeof(st_prep_t));
   memset(&st, 0, sizeof(stepper_t));
   st.exec_segment = NULL;
   pl_block = NULL;  // Planner block pointer used by segment buffer
   segment_buffer_tail = 0;
   segment_buffer_head = 0; // empty = tail
   segment_next_head = 1;
   busy = false;
 ​
   st_generate_step_dir_invert_masks();
   st.dir_outbits = dir_port_invert_mask; // Initialize direction bits to default.
 ​
   // Initialize step and direction port pins.
   // STEP_PORT = (STEP_PORT & ~STEP_MASK) | step_port_invert_mask;
    HAL_GPIO_WritePin(STEP_GPIO_PORT,X_STEP_Pin,bit_istrue(step_port_invert_mask,bit(X_STEP_BIT)));
    HAL_GPIO_WritePin(STEP_GPIO_PORT,Y_STEP_Pin,bit_istrue(step_port_invert_mask,bit(Y_STEP_BIT)));
    HAL_GPIO_WritePin(STEP_GPIO_PORT,Z_STEP_Pin,bit_istrue(step_port_invert_mask,bit(Z_STEP_BIT)));
 ​
   // DIRECTION_PORT = (DIRECTION_PORT & ~DIRECTION_MASK) | dir_port_invert_mask;
    HAL_GPIO_WritePin(DIRECTION_GPIO_PORT,X_DIRECTION_PIN,bit_istrue(dir_port_invert_mask,bit(X_DIRECTION_BIT)));
    HAL_GPIO_WritePin(DIRECTION_GPIO_PORT,Y_DIRECTION_PIN,bit_istrue(dir_port_invert_mask,bit(Y_DIRECTION_BIT)));
    HAL_GPIO_WritePin(DIRECTION_GPIO_PORT,Z_DIRECTION_PIN,bit_istrue(dir_port_invert_mask,bit(Z_DIRECTION_BIT)));
   
   #ifdef ENABLE_DUAL_AXIS
     st.dir_outbits_dual = dir_port_invert_mask_dual;
     STEP_PORT_DUAL = (STEP_PORT_DUAL & ~STEP_MASK_DUAL) | step_port_invert_mask_dual;
     DIRECTION_PORT_DUAL = (DIRECTION_PORT_DUAL & ~DIRECTION_MASK_DUAL) | dir_port_invert_mask_dual;
   #endif
 }

(6)void stepper_init(void)

嗯,步进电机初始化,如果是用STM32CubeMX或STM32CubeIDE,生成的代码自动初始化,没什么必要,但还是写上吧

 // Initialize and start the stepper motor subsystem
 void stepper_init()
 {
  MX_GPIO_Init();
  MX_TIM2_Init();
 ​
 ​
   #ifdef STEP_PULSE_DELAY
     TIMSK0 |= (1<<OCIE0A); // Enable Timer0 Compare Match A interrupt
   #endif
 }

18、system.c

负责管理整个CNC控制器的系统状态、初始化、主循环以及系统级别的功能。

SREG是AVR单片机(如ATmega328P)中的状态寄存器(Status Register)。它是一个8位的寄存器,包含了当前处理器的状态信息,如全局中断使能标志、进位标志、零标志等。

前面基本不需要修改,后面获取中断状态需要修改为STM32的。

 void system_set_exec_state_flag(uint8_t mask) {
   //uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_state |= (mask);
  __set_PRIMASK(primask);
 }
 ​
 void system_clear_exec_state_flag(uint8_t mask) {
   //uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_state &= ~(mask);
  __set_PRIMASK(primask);
 }
 ​
 void system_set_exec_alarm(uint8_t code) {
  // uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_alarm = code;
  __set_PRIMASK(primask);
 }
 ​
 void system_clear_exec_alarm() {
   //uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_alarm = 0;
  __set_PRIMASK(primask);
 }
 ​
 void system_set_exec_motion_override_flag(uint8_t mask) {
   //uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_motion_override |= (mask);
  __set_PRIMASK(primask);
 }
 ​
 void system_set_exec_accessory_override_flag(uint8_t mask) {
   //uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_accessory_override |= (mask);
  __set_PRIMASK(primask);
 }
 ​
 void system_clear_exec_motion_overrides() {
   //uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_motion_override = 0;
  __set_PRIMASK(primask);
 }
 ​
 void system_clear_exec_accessory_overrides() {
   //uint8_t sreg = SREG;
  uint32_t primask = __get_PRIMASK();
  sei();
  sys_rt_exec_accessory_override = 0;
  __set_PRIMASK(primask);
 }

至此,grbl的初步移植算是完成,等组装好机器后,就开始调试,后面的话还有一些参数需要修改。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇