grblHAL的代码学习笔记和解读

源代码在 https://github.com/grblHal 目前日期2024/07/09
或许几年以后代码会做变更. 仅供参考.

grbl是个很好的库. grblHAL是在grbl的基础上改进来的.
grbl的板子通过串口或者SD卡或网络与上位机进行通信, 上位机负责查看和人机交互.
上位机把CAM软件生成的代码读入以后,转换成grbl的通信代码.
grblHAL目前支持的G代码如下

- 非模态命令: G4, G10L2, G10L20, G28, G30, G28.1, G30.1, G53, G65*****, G92, G92.1
  - 额外非模态命令: G10L1*, G10L10*, G10L11*
  - 运动模式: G0, G1, G2****, G3****, G5, G5.1, G38.2, G38.3, G38.4, G38.5, G80, G33*
  - 可编程循环: G73, G81, G82, G83, G85, G86, G89, G98, G99
  - 重复循环: G76*
  - 进制速率模式: G93, G94, G95*, G96*, G97*
  - 单位模式: G20, G21
  - 缩放模式: G50, G51
  - Lat 模式: G7*, G8*
  - 距离模式: G90, G91
  - IJK距离模式: G91.1
  - 平面选择模式: G17, G18, G19
  - 工具长度偏移模式: G43*, G43.1, G43.2*, G49
  - 刀具补偿模式: G40
  - 坐标系模式: G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3
  - 控制模式: G61
  - 程序流程: M0, M1, M2, M30, M60
  - 冷却剂控制: M7, M8, M9
  - 转子控制: M3, M4, M5
  - 工具更换: M6* (两种可能:手动** - 支持 jogging, ATC), M61
  - 开关: M48, M49, M50, M51, M53
  - 输入/输出控制***: M62, M63, M64, M65, M66, M67, M68
  - 模态状态处理*: M70, M71, M72, M73
  - 从宏返回*****: M99
  - 有效的非命令词: A*, B*, C*, D, E*, F, H*, I, J, K, L, N, O*, P, Q*, R, S, T, U*, V*, W*, X, Y, Z

上位机与下位机板子之间的通信格式如下:

https://github.com/gnea/grbl/wiki/Grbl-v1.1-Interface

最终问题, 如何开始写一个属于自己机器的驱动?

答案是把 https://github.com/grblHAL/Templates 项目当做你的机器的驱动的模板,
然后实现其中的4个主要文件,

一个是ioports.c 这个文件中主要定义了, 引脚跟物理端口的映射关系.
一个是eeprom.c, 这个文件主要是用来连接存储的, 存储一些配置
一个是serial.c, 通讯端口
一个是driver.c 驱动各个设备的文件
这3个逐个实现,即完成了属于你自己机器的适配…(我还没动手, 目前来看是这个样子实现的),
第一步, 把https://github.com/grblHAL/Templates 的代码git clone 下来, 然后在文件夹下,合适的地方新建个文件夹叫grbl, 然后进入grbl文件夹,
第二步, 把https://github.com/grblHAL/core拉下来放到这个grbl目录里. 然后就可以开始了.

想要更快的了解别人是怎么写的,可以参考: https://github.com/grblHAL/STM32F4xx

grblHAL中几个重要的概念

代码抽象分为2层, 分别是与具体硬件无关的算法层, 也叫HAL层
另外一层是与硬件有关的设备驱动层. 也叫driver层.

driver 驱动层, 指的是非HAL层之外的代码, 用来驱动不同的步进电机, 不同的水冷机,不同的主轴, 限位开关, 等等具体的实现都是放在driver层实现的.

HAL 算法层包含下面这些功能:
gc G代码解析器,读取G代码的算法,算是GRBL核心算法了
planner G代码执行规划器算法,用来缓冲移动命令并管理加速度剖面图, 代码在planner.c中
report 输出一些系统当前的状态信息之类的代码. 估计跟上位机通信用的. 代码在report.c中
spindle 主轴相关操作, 支持多个主轴.
stepper 步进算法, 移动相关的操作.算是GRBL的核心算法了
pid PID算法的实现.
nvs基于RAM的非易失性存储缓冲区/模拟
motor_pins 电机控制引脚映射文件, 这个文件不能直接修改, 需要在驱动层复制一份去改.
plugins 插件, 实现一些自定义指令, 特殊功能扩展, 例如黄绿灯,水冷器,等自己机器特有的,可以通过插件的形式实现它. grblHAL中有一个项目是专门讲解这个的. 可以自己去了解一下.

grblHAL STM32F4xx 项目中主要的几个文件

main.c 主入口文件, 进入grbl的主函数 grbl_enter();
driver.c 各个设备的驱动. 里面把各个模块都写到一起了. 包含步进电机, 主轴,等等. 似乎都写一块了. 我感觉代码组织的不是很好 .乱糟糟的.

如何衔接grbl核心层和具体设备驱动层

对于驱动开发人员来讲, 最重要的莫过于理解grblHAL的内部代码结构.
grblHAL把核心的代码解析和运动规划等抽象成了HAL层. 代码在项目
core中. 编译的时候需要把core代码复制到你的项目中.
core代码和驱动之间的连接使用的是全局变量. hal, grbl, settings,
hal的定义在hal.h中.
grbl的定义在core_handlers.h中声明, 在grbl.c中定义 grbl全局变量中主要都包含的是一些函数的指针.
settings 读取和写入全局配置的变量
在驱动层, include 好需要的.h文件, 然后直接使用上面的变量即可实现与算法层的交互

extern grbl_hal_t hal; //!< Global HAL struct.

驱动层必须要实现的几个功能

1.步进电机的控制与中断
2.主轴的控制
3.限位开关的中断
4.安全门的中断
5.电子手轮的中断

比较重要的几个全局变量

sys 包含了一些机器当前状态的信息 在system.h中

//! 全局系统变量结构体。
// 注意:如果出现问题时,probe_position 和 position 变量可能需要声明为 volatile。
typedef struct system {
    bool abort;                             //!< 系统中止标志。强制退出主循环以进行复位。
    bool cancel;                            //!< 系统取消标志。
    bool suspend;                           //!< 系统挂起状态标志。
    bool position_lost;                     //!< 当机器移动时调用 mc_reset 时设置。
    bool reset_pending;                     //!< 当进行复位处理时设置。
    volatile bool steppers_deenergize;      //!< 设置为 true 以使步进电机断电
    axes_signals_t tlo_reference_set;       //!< 设置了工具长度参考偏移的轴
    int32_t tlo_reference[N_AXIS];          //!< 工具长度参考偏移
    alarm_code_t alarm_pending;             //!< 延迟警报,当前用于探针保护
    system_flags_t flags;                   //!< 各种状态标志
    step_control_t step_control;            //!< 根据系统状态控制步进段生成器。
    axes_signals_t homing_axis_lock;        //!< 在限位触发时锁定轴。在步进 ISR 中用作轴运动掩码。
    axes_signals_t homing;                  //!< 启用了回零的轴。
    overrides_t override;                   //!< 覆盖值和状态
    report_tracking_flags_t report;         //!< 跟踪何时向状态报告添加数据。
    parking_state_t parking_state;          //!< 跟踪停车状态
    hold_state_t holding_state;              //!< 跟踪保持状态
    int32_t probe_position[N_AXIS];         //!< 最后一次探针位置(以机器坐标和步进为单位)。
    volatile probing_state_t probing_state; //!< 探针状态值。用于与步进 ISR 协调探针循环。
    volatile rt_exec_t rt_exec_state;       //!< 实时执行器位标志变量,用于状态管理。参见 EXEC 位掩码。
    volatile uint_fast16_t rt_exec_alarm;   //!< 实时执行器位标志变量,用于设置各种警报。
    int32_t var5399;                        //!< M66 的最后结果 - 等待输入
#ifdef PID_LOG
    pid_data_t pid_log;
#endif
//! @name 以下变量在位置可能丢失时仅在软复位时清除,请勿移动。 homed 必须是第一个!
//@{
    axes_signals_t homed;                   //!< 指示哪些轴已被回零。
    float home_position[N_AXIS];            //!< 已回零轴的回零位置
//@}
//!  @name 以下变量在软复位时不清除,请勿移动。 alarm 必须是第一个!
//@{
    alarm_code_t alarm;                     //!< 当前警报,只有当系统状态为 STATE_ALARM 时才有效。
    bool cold_start;                        //!< 在启动时设置为 true,在后续的软复位中为 false。
    bool driver_started;                    //!< 驱动程序初始化完成后设置为 true。
    bool mpg_mode;                          //!< 将移动到 system_flags_t
    signal_event_t last_event;              //!< 最后的信号事件(控制和限位信号)。
    int32_t position[N_AXIS];               //!< 实时机器(即回零)位置向量(以步进为单位)。
//@}
} system_t;

hal 在hal.h文件中, 用于驱动程序接口的HAL结构。
该结构包含内核用于与驱动程序通信的属性和函数指针(指向处理程序)。
驱动程序必须在driver_init()函数中设置所需的函数指针。
__注:__需要那些未标记为可选的。如果不是由驾驶员分配,则行为未定义,并且
可能在某个点导致控制器崩溃。


typedef struct {
    uint32_t version;               //!< HAL 版本,由核心设置。驱动程序应在 _driver_init()_ 函数中检查此版本。
    char *info;                     //!< 指向驱动程序信息字符串的指针,通常是处理器/平台的名称。
    char *driver_version;           //!< 指向驱动程序版本日期字符串的指针,格式为 YYMMDD。
    char *driver_options;           //!< 指向包含驱动程序选项的逗号分隔字符串的指针。
    char *driver_url;               //!< 指向驱动程序的 URL 的指针。
    char *board;                    //!< 指向可选的板名称字符串的指针。
    char *board_url;                //!< 指向板的 URL 的指针。
    uint32_t f_step_timer;          //!< 主步进定时器的频率(以 Hz 为单位)。
    uint32_t f_mcu;                 //!< MCU 的频率(以 MHz 为单位)。
    uint32_t rx_buffer_size;        //!< 输入流缓冲区大小(以字节为单位)。
    uint32_t max_step_rate;         //!< 当前未使用。
    uint8_t driver_axis_settings;   //!< 当前未使用。

    /*! \brief 可选的指针,用于获取空闲内存(堆中所有空闲块的总和)。 */
    get_free_mem_ptr get_free_mem;

    /*! \brief 驱动程序设置处理程序。
    在核心加载设置后,由核心调用一次。驱动程序应在提供的函数中启用 MCU 外设。
    \param settings 指向 settings_t 结构的指针。
    \returns 如果成功完成并且驱动程序支持 _settings->version_ 号,则返回 true,否则返回 false。
    */
    driver_setup_ptr driver_setup;

    /*! \brief 毫秒延迟。

    如果回调参数为 \a NULL,则调用将阻塞,直到延迟过期,否则立即返回,并在超时到期时调用回调函数。

    可以通过调用延迟设置为 \a 0 且回调为 \a NULL 的函数来终止挂起的回调。
    \param ms 延迟的毫秒数。
    \param callback 作为 \a delay_callback_ptr 函数指针的函数。
    */
    void (*delay_ms)(uint32_t ms, delay_callback_ptr callback);

    /*! \brief 原子地设置位。
    \param value 指向 16 位无符号整数的指针。
    \param bits 要设置的位。
    */
    void (*set_bits_atomic)(volatile uint_fast16_t *value, uint_fast16_t bits);

    /*! \brief 原子地清除位。
    \param value 指向 16 位无符号整数的指针。
    \param bits 要清除的位。
    \returns 清除位前的值。
    */
    uint_fast16_t (*clear_bits_atomic)(volatile uint_fast16_t *value, uint_fast16_t v);

    /*! \brief 原子地设置值。
    \param value 指向 16 位无符号整数的指针。
    \param v 要设置的值。
    \returns 设置值前的值。
    */
    uint_fast16_t (*set_value_atomic)(volatile uint_fast16_t *value, uint_fast16_t bits);

    //! \brief 可选的处理程序,用于禁用全局中断。
    void (*irq_enable)(void);

    //! \brief 可选的处理程序,用于启用全局中断。
    void (*irq_disable)(void);

    //! \brief 可选的处理程序,用于声明更高优先级的中断。在启动时设置为 dummy 处理程序。
    irq_claim_ptr irq_claim;

    limits_ptrs_t limits;                   //!< 限位开关的处理程序。
    homing_ptrs_t homing;                   //!< 限位开关的处理程序,用于回零循环。
    control_signals_ptrs_t control;         //!< 控制开关的处理程序。
    coolant_ptrs_t coolant;                 //!< 冷却的处理程序。
    spindle_data_ptrs_t spindle_data;       //!< 获取/重置主轴数据的处理程序(RPM、角位置等)。
    stepper_ptrs_t stepper;                 //!< 步进电机的处理程序。
    io_stream_t stream;                     //!< 流 I/O 的处理程序。
    stream_select_ptr stream_select;        //!< 可选的处理程序,用于在 I/O 流之间切换。
    settings_changed_ptr settings_changed;  //!< 在加载或更改设置时调用的回调处理程序。
    probe_ptrs_t probe;                     //!< 可选的处理程序,用于探针输入。
    tool_ptrs_t tool;                       //!< 可选的处理程序,用于工具更改。
    rtc_ptrs_t rtc;                         //!< 可选的处理程序,用于实时时钟(RTC)。
    io_port_t port;                         //!< 可选的处理程序,用于辅助 I/O(支持 M62-M66)。
    periph_port_t periph_port;              //!< 可选的处理程序,用于外围引脚注册。
    driver_reset_ptr driver_reset;          //!< 可选的处理程序,在软复位时调用。核心在启动时将其设置为 dummy 处理程序。
    nvs_io_t nvs;                           //!< 可选的处理程序,用于存储/检索设置和数据到/从非易失性存储(NVS)。
    enumerate_pins_ptr enumerate_pins;      //!< 可选的处理程序,用于枚举驱动程序使用的引脚。
    bool (*driver_release)(void);           //!< 可选的处理程序,在退出前释放硬件资源。
    uint32_t (*get_elapsed_ticks)(void);    //!< 可选的处理程序,用于获取自启动以来的经过的 1ms 计数。多个插件需要此功能。
    void (*pallet_shuttle)(void);           //!< 可选的处理程序,用于在程序结束时执行托盘穿梭(M60)。
    void (*reboot)(void);                   //!< 可选的处理程序,用于重新启动控制器。当接收到 #ASCII_ESC 后跟 #CMD_REBOOT 时将调用此函数。

    user_mcode_ptrs_t user_mcode;           //!< 可选的处理程序,用于用户定义的 M 代码。
    encoder_ptrs_t encoder;                 //!< 可选的处理程序,用于编码器支持。

    /*! \brief 可选的处理程序,用于获取当前轴位置。
    \returns 在 int32_array 中的轴位置。
    */
    bool (*get_position)(int32_t (*position)[N_AXIS]);

#ifdef DEBUGOUT
    void (*debug_out)(bool on);
    io_stream_t debug;                     //!< 调试流 I/O 的处理程序。
#endif

    /*! \brief 在阻塞调用中检查软复位或中止。
    由核心在启动时设置。

    通常在输出缓冲区已满且它们正在阻塞等待缓冲区中可用空间时,从流输出函数中调用。
    \returns 当阻塞循环应退出时返回 false,否则返回 true。
    */
    bool (*stream_blocking_callback)(void);

    driver_cap_t driver_cap;                //!< 基本驱动程序功能标志。
    control_signals_t signals_cap;          //!< 核心支持的控制输入信号。

} grbl_hal_t;

grbl 在core_handlers.h文件中定义
这里是一些其它函数的指针. 也就是可以在你的代码里调用其它的模块.


typedef struct {
    // report entry points set by core at reset.
    report_t report; // 报告入口点,由核心在复位时设置。
    // grbl核心事件-可以由驱动程序或核心订阅。
    on_state_change_ptr on_state_change; // 状态改变事件处理函数指针。
    on_override_changed_ptr on_override_changed; // 覆盖改变事件处理函数指针。
    on_report_handlers_init_ptr on_report_handlers_init; // 报告处理程序初始化事件处理函数指针。
    on_spindle_programmed_ptr on_spindle_programmed; // 主轴编程事件处理函数指针。
    on_program_completed_ptr on_program_completed; // 程序完成事件处理函数指针。
    on_execute_realtime_ptr on_execute_realtime; // 实时执行事件处理函数指针。
    on_execute_realtime_ptr on_execute_delay; // 实时延迟执行事件处理函数指针。
    on_unknown_accessory_override_ptr on_unknown_accessory_override; // 未知附件覆盖事件处理函数指针。
    on_report_options_ptr on_report_options; // 报告选项事件处理函数指针。
    on_report_command_help_ptr on_report_command_help; // 报告命令帮助事件处理函数指针。
    on_global_settings_restore_ptr on_global_settings_restore; // 全局设置恢复事件处理函数指针。
    on_get_alarms_ptr on_get_alarms; // 获取警报事件处理函数指针。
    on_get_errors_ptr on_get_errors; // 获取错误事件处理函数指针。
    on_get_settings_ptr on_get_settings; // 获取设置事件处理函数指针。
    on_realtime_report_ptr on_realtime_report; // 实时报告事件处理函数指针。
    on_unknown_feedback_message_ptr on_unknown_feedback_message; // 未知反馈消息事件处理函数指针。
    on_unknown_realtime_cmd_ptr on_unknown_realtime_cmd; // 未知实时命令事件处理函数指针。
    on_unknown_sys_command_ptr on_unknown_sys_command;  //!< return Status_Unhandled if not handled. // 未知系统命令事件处理函数指针,如果未处理则返回 Status_Unhandled。
    on_get_commands_ptr on_get_commands; // 获取命令事件处理函数指针。
    on_user_command_ptr on_user_command; // 用户命令事件处理函数指针。
    on_stream_changed_ptr on_stream_changed; // 流改变事件处理函数指针。
    on_homing_rate_set_ptr on_homing_rate_set; // 设置回零速率事件处理函数指针。
    on_homing_completed_ptr on_homing_completed; // 回零完成事件处理函数指针。
    on_probe_fixture_ptr on_probe_fixture; // 探针夹具事件处理函数指针。
    on_probe_start_ptr on_probe_start; // 探针开始事件处理函数指针。
    on_probe_completed_ptr on_probe_completed; // 探针完成事件处理函数指针。
    on_tool_selected_ptr on_tool_selected;              //!< Called prior to executing M6 or after executing M61. // 在执行M6或执行M61之前/之后调用工具选择事件处理函数指针。
    on_toolchange_ack_ptr on_toolchange_ack;            //!< Called from interrupt context. // 从中断上下文中调用工具更换确认事件处理函数指针。
    on_jog_cancel_ptr on_jog_cancel;                    //!< Called from interrupt context. // 从中断上下文中调用 Jog 取消事件处理函数指针。
    on_laser_ppi_enable_ptr on_laser_ppi_enable; // 激光 PPI 启用事件处理函数指针。
    on_spindle_select_ptr on_spindle_select;            //!<  Called before spindle is selected, hook in HAL overrides here // 在选择主轴之前调用,在这里挂钩 HAL 覆盖。
    on_spindle_selected_ptr on_spindle_selected;        //!<  Called when spindle is selected, do not change HAL pointers here! // 在选择主轴时调用,不要更改 HAL 指针!
    on_reset_ptr on_reset;                              //!< Called from interrupt context. // 从中断上下文中调用复位事件处理函数指针。
    // core entry points - set up by core before driver_init() is called.
    enqueue_gcode_ptr enqueue_gcode; // 入队 G 代码事件处理函数指针。
    enqueue_realtime_command_ptr enqueue_realtime_command; // 入队实时命令事件处理函数指针。
} grbl_t;

下面的电机和外部设备的驱动实现设备代码, 通过全局变量hal 来调用和获取其它信息.

对设备的特性配置

编译开关应在具体实现的代码 my_machine.h 中定义.
例如:

https://github.com/grblHAL/STM32F4xx/tree/master/Inc/my_machine.h

驱动代码的过程.

// main.c->
//	grbl_enter()->
//	1.  driver_init() //在derive.c文件中实现
//	2.  driver_setup()//在derive.c文件中实现
//

C语言中的函数指针

typedef void (*spindle_update_pwm_ptr)(uint_fast16_t pwm);

在这个例子中,我们定义了一个函数指针spindle_update_pwm_ptr,它接受一个无符号整数(uint_fast16_t)参数pwm,并将其传递给一个名为update_pwm的函数。这个函数的指针类型是void (*)(uint_fast16_t pwm)。

要使用这个函数指针,首先需要定义一个函数,该函数具有与函数指针类型匹配的参数列表和返回类型。例如,我们可以定义一个名为update_pwm的函数:

void update_pwm(uint_fast16_t pwm) {
    // 在这里实现您的代码
}

然后,您可以将update_pwm函数的地址分配给spindle_update_pwm_ptr类型的变量。例如:

spindle_update_pwm_ptr spindle_update_pwm = update_pwm;

现在,您可以像使用普通函数一样使用spindle_update_pwm变量。当您调用spindle_update_pwm时,它会自动调用update_pwm函数。例如:

spindle_update_pwm(100); // 调用 update_pwm 函数,传递参数 100

请注意,函数指针可以指向任何类型的函数,包括函数声明。因此,您可以根据需要使用不同的函数类

ain()主函数首先执行下面初始化函数 serial_init(); // Setup serial baud rate and interrupts settings_init(); // Load Grbl settings from EEPROM stepper_init(); // 配置步进方向和中断定时器 system_init(); // 配置引脚分配别针和pin-change中断 memset(&sys, 0, sizeof(system_t)); // Clear all system variables sys.abort = true; // Set abort to complete initialization 完成初始化设置中止 sei(); // Enable interrupts #ifdef HOMING_INIT_LOCK //宏运算(settings.flags & (1 << 4)) != 0结果flags等于执行sys.state = STATE_ALARM //系统状态赋值为报警状态 if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { sys.state = STATE_ALARM; } #endif _____________________________________________________________________________________________________________________________________ 接下来是一些主要初始化循环 for(;;) { serial_reset_read_buffer(); // Clear serial read buffer gc_init(); // Set g-code parser to default state spindle_init(); //主轴 coolant_init(); //冷却液 limits_init(); //极限开关 probe_init(); //探测 plan_reset(); // Clear block buffer and planner variables 清晰块缓冲区和规划师变量 st_reset(); // Clear stepper subsystem variables. 清晰的步进系统变量。 // Sync cleared gcode and planner positions to current system position. 同步清除gcode和策划师职位当前系统位置。 plan_sync_position(); gc_sync_position(); // Reset system variables. sys.abort = false; //系统中止标志 sys_rt_exec_state = 0; //系统执行标志状态变量状态位清零。 sys_rt_exec_alarm = 0; //系统执行警报标志变量清零。 sys.suspend = false; //系统暂停标志位,取消,和安全的门。 sys.soft_limit = false; //系统限制标志状态机复位。(布尔) protocol_main_loop(); //主协议循环 } // ___________________________________________________________________________
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值