grbl移植日志第四天分析grbl数据流向图

移植完步进电机驱动, 但是发现执行代码 G1 X10 Y10 F1000\n时,偶尔会卡住. 无奈只好全流程的从前往后分析整个系统的执行流程. 这篇文章算是核心, 对整个grbl进行了系统性的分析. 看完这篇文章, 基本上对整个系统做了哪些事情, 经过哪些文件和函数有个大致的了解.

G代码是如何从电脑到执行的? 中间经过了那几个代码文件和函数?又经过了哪些变量?

为了容易理解后来我又加了一张图. 方便排查问题
在这里插入图片描述

  1. 首先是serial.c串口RX中断函数将数据读取到 rxbuffer
  2. 将 stream.c的函数指向 serialGetC()从而读取 rxbuffer的数据
  3. protocol.c 的 protocol_main_loop主循环中循环读取G代码.
while((c = hal.stream.read()) != SERIAL_NO_DATA) {
....
})

读取到每个字符, 然后开始解析G代码. 当读取到 ‘\0’ 的时候开始
将读取到的代码读取到 xcommand变量, 然后执行 gcode.c中的 gc_execute_block(xcommand)
4. gcode.c 解析G代码.字符串转变量
gcode.c中这个函数主要的功能是将G-Code的字符串代码转换成可以使用的内存变量.

 // Handle extra command (internal stream)
        if(xcommand[0] != '\0') {

            if (xcommand[0] == '$') // Grbl '$' system command
                system_execute_line(xcommand);
            else if (state_get() & (STATE_ALARM|STATE_ESTOP|STATE_JOG)) // Everything else is gcode. Block if in alarm, eStop or jog state.
                grbl.report.status_message(Status_SystemGClock);
            else // Parse and execute g-code block.
                gc_execute_block(xcommand);

            xcommand[0] = '\0';
        }
  1. motion_control.c 根据变量实现运动画圈,画线等功能
    然后在解析G代码的时候调用 motion_control.c中的 mc_line(),mc_arc(),mc_dwell(),等函数. 实现运动功能 .
// Execute linear motion in absolute millimeter coordinates. Feed rate given in millimeters/second
// unless invert_feed_rate is true. Then the feed_rate means that the motion should be completed in
// (1 minute)/feed_rate time.
// NOTE: This is the primary gateway to the grbl planner. All line motions, including arc line
// segments, must pass through this routine before being passed to the planner. The separation of
// mc_line and plan_buffer_line is done primarily to place non-planner-type functions from being
// in the planner and to let backlash compensation or canned cycle integration simple and direct.

 
//  在绝对毫米坐标中执行线性运动。进给速度以毫米/秒为单位
//  除非 invert_feed_rate 为 true。否则,feed_rate 表示运动应在(1分钟)/进给速度时间。
//  注意:这是grbl规划器的主要入口。所有直线运动,包括弧线
// 在传递给规划器之前,必须通过这个例程mc_line 和 plan_buffer_line 的主要目的是放置非规划器类型的函数,以免
//  在计划器中,可以简单直接地进行间隙补偿或屏蔽循环集成。

bool mc_line (float *target, plan_line_data_t *pl_data)
{
...
}

motion_control.c中调用 plan_buffer_line() 函数, 将运动操作添加到运动规划中.

  1. planner.c plan_buffer_line() 函数运动规划
/* Add a new linear movement to the buffer. target[N_AXIS] is the signed, absolute target position
   in millimeters. Feed rate specifies the speed of the motion. If feed rate is inverted, the feed
   rate is taken to mean "frequency" and would complete the operation in 1/feed_rate minutes.
   All position data passed to the planner must be in terms of machine position to keep the planner
   independent of any coordinate system changes and offsets, which are handled by the g-code parser.
   NOTE: Assumes buffer is available. Buffer checks are handled at a higher level by motion_control.
   In other words, the buffer head is never equal to the buffer tail.  Also the feed rate input value
   is used in three ways: as a normal feed rate if invert_feed_rate is false, as inverse time if
   invert_feed_rate is true, or as seek/rapids rate if the feed_rate value is negative (and
   invert_feed_rate always false).
   The system motion condition tells the planner to plan a motion in the always unused block buffer
   head. It avoids changing the planner state and preserves the buffer to ensure subsequent gcode
   motions are still planned correctly, while the stepper module only points to the block buffer head
   to execute the special system motion. */

//     将新的线性移动添加到缓冲区。target[N_AXIS]是有符号的绝对目标位置
// 单位为毫米。进给速度指定了运动的速度。如果进给率反转,进给速率是指“频率”,将在1/进给速率分钟内完成操作。
//    所有传递给计划器的位置数据必须以机器位置为准,以保持计划器的准确性
//    独立于任何坐标系变化和偏移,这些由g代码解析器处理。
//    注意:假设缓冲区可用。缓冲区的检查由motion_control在更高的层次上处理。
//    换句话说,缓冲区头永远不会等于缓冲区尾。此外,进给率输入值
//    有三种使用方式:如果 invert_feed_rate 为 false,则作为正常进给率;如果 invert_feed_rate 为 true,则作为反时;如果 invert_feed_rate 为 false,则作为反时
//    如果 invert_feed_rate 为真,或者如果 feed_rate 的值为负(并且
//    逆进给速度始终为假)。
//    系统运动条件告诉计划器在始终未使用的块缓冲区中计划一个运动
// 头它避免了改变计划器状态并保留缓冲区以确保后续的gcode
//    运动仍然正确计划,而步进器模块仅指向块缓冲区头
//    以执行特殊系统运动。

bool plan_buffer_line (float *target, plan_line_data_t *pl_data)
{
}

  1. protocol.c protocol_auto_cycle_start()

上面只是解析命令. 并未真正执行, 真正的执行是在.
这些数据都处理好之后,protocol.c文件会在 gc_execute_block(xcommand); 之后直接执行
protocol_auto_cycle_start()函数. 这个函数会通过调用plan_get_current_block() 读取前面解析生产的运动规划数据. 然后调用 system_set_exec_state_flag(EXEC_CYCLE_START);函数.
中也只是修改了一个变量Flag.

// Auto-cycle start triggers when there is a motion ready to execute and if the main program is not
// actively parsing commands.
// NOTE: This function is called from the main loop, buffer sync, and mc_line() only and executes
// when one of these conditions exist respectively: There are no more blocks sent (i.e. streaming
// is finished, single commands), a command that needs to wait for the motions in the buffer to
// execute calls a buffer sync, or the planner buffer is full and ready to go.
//当有动作准备执行时,如果主程序未执行,则自动循环启动会触发
//主动解析命令。
//注意:此函数仅从主循环、缓冲区同步和mc_line()调用并执行
//当分别存在以下条件之一时:不再发送块(即流式传输
//已完成,单个命令),该命令需要等待缓冲区中的运动
//execute调用缓冲区同步,或者计划缓冲区已满并准备就绪。
void protocol_auto_cycle_start (void)
{
    if (plan_get_current_block() != NULL) // Check if there are any blocks in the buffer.
        system_set_exec_state_flag(EXEC_CYCLE_START); // If so, execute them!
}
  1. system_set_exec_state_flag() 这个函数是个宏定义只是设置了一个状态. 并未执行
#define system_set_exec_state_flag(mask) hal.set_bits_atomic(&sys.rt_exec_state, (mask))

那么这些plan.c规划好的命令是在什么地方被执行的呢?
经过分析这些plan 既然被保存,肯定会被使用, 只要搜索在什么地方使用它的函数即可.
plan.c中有个函数是plan_get_current_block , 这个函数明显的是给外部使用的接口.

// Returns address of first planner block, if available. Called by various main program functions.
plan_block_t *plan_get_current_block (void)
{
    return block_buffer_head == block_buffer_tail ? NULL : block_buffer_tail;
}
  1. state_machine.c 状态机循环执行plan
    经过一番搜索plan_get_current_block . 发现是在 state_machine.c中执行的.
    在 void state_set (sys_state_t new_state) 函数中不停的进行状态切换, 其中当状态为STATE_CYCLE时进行读取. 事实并非如此简单 . 还有另外一个循环. 状态机是有主循环调用的. 但是步进电机的输出还有另外一个循环.后面再说, 先看代码.
void state_set (sys_state_t new_state)
{
    if (new_state != sys_state) {

        switch(new_state) {    // Set up new state and handler

            case STATE_IDLE:
                sys.suspend = false;        // Break suspend state.
                sys.step_control.flags = 0; // Restore step control to normal operation.
                sys.parking_state = Parking_DoorClosed;
                sys.holding_state = Hold_NotHolding;
                sys_state = pending_state = new_state;
                park.flags.value = 0;
                stateHandler = state_idle;
                break;

            case STATE_CYCLE:
                if (sys_state == STATE_IDLE) {
                    // Start cycle only if queued motions exist in planner buffer and the motion is not canceled.
                    plan_block_t *block;
                    if ((block = plan_get_current_block())) {
                        sys_state = new_state;
                        sys.steppers_deenergize = false;    // Cancel stepper deenergize if pending.
                        st_prep_buffer();                   // Initialize step segment buffer before beginning cycle.
                        if (block->spindle.state.synchronized) {

                            if (block->spindle.hal->reset_data)
                                block->spindle.hal->reset_data();

                            uint32_t index = block->spindle.hal->get_data(SpindleData_Counters)->index_count + 2;

                            while(index != block->spindle.hal->get_data(SpindleData_Counters)->index_count); // check for abort in this loop?

                        }
                        st_wake_up();
                        stateHandler = state_cycle;
                    }
                }
                break;

            case STATE_JOG:
                if (sys_state == STATE_TOOL_CHANGE)
                    pending_state = STATE_TOOL_CHANGE;
                sys_state = new_state;
                stateHandler = state_cycle;
                break;

            case STATE_TOOL_CHANGE:
                sys_state = new_state;
                stateHandler = state_await_toolchanged;
                break;

            case STATE_HOLD:
                if (sys.override.control.sync && sys.override.control.feed_hold_disable)
                    sys.flags.feed_hold_pending = On;
                if (!((sys_state & STATE_JOG) || sys.override.control.feed_hold_disable)) {
                    if (!initiate_hold(new_state)) {
                        sys.holding_state = Hold_Complete;
                        stateHandler = state_await_resume;
                    }
                    sys_state = new_state;
                    sys.flags.feed_hold_pending = Off;
                }
                break;

            case STATE_SAFETY_DOOR:
                if ((sys_state & (STATE_ALARM|STATE_ESTOP|STATE_SLEEP|STATE_CHECK_MODE)))
                    return;
                grbl.report.feedback_message(Message_SafetyDoorAjar);
                // no break
            case STATE_SLEEP:
                sys.parking_state = Parking_Retracting;
                if (!initiate_hold(new_state)) {
                    if (pending_state != new_state) {
                        sys_state = new_state;
                        state_await_hold(EXEC_CYCLE_COMPLETE); // "Simulate" a cycle stop
                    }
                } else
                    sys_state = new_state;
                break;

            case STATE_ALARM:
            case STATE_ESTOP:
            case STATE_HOMING:
            case STATE_CHECK_MODE:
                sys_state = new_state;
                sys.suspend = false;
                stateHandler = state_noop;
                break;
        }

        if (!(sys_state & (STATE_ALARM|STATE_ESTOP)))
            sys.alarm = Alarm_None;

        if (grbl.on_state_change)
            grbl.on_state_change(new_state);
    }
}

那么这里有个疑问, 这个状态机是什么时候启动的?
经过分析, 是在 protocol.c的主循环中, protocol_main_loop () 内部循环的给状态state_set()进行赋值.然后调用的 state_set()的时候就进行了代码执行.

  1. 然后在state_machine.c中装入步进电机的配置并调用步进电机

  2. 在步进电机驱动层还实现了一个主循环,循环消费plan中的数据
    后来经过分析. 发现Stepper模块也是一个自主独立运行的循环. 类似多线程. 由定时器触发中断,中断中循环消费planner.c中st_prep_buffer()给stepper的数据.
    在这个st_prep_buffer() 函数里. 会将plan取出放入变量pl_block, 然后又放入st_prep_block变量.
    最后拼装成 变量 prep_segment, 将st_prep_block赋值给
    prep_segment->exec_block = st_prep_block;
    这里prep_segment是个指针指向的是 segment_buffer_head.

  3. stepper.c 中断函数中进行直线插补算法
    然后在步进电机的 中断函数 stepper_driver_interrupt_handler中.
    st.exec_block = st.exec_segment->exec_block;
    如此, 数据最终到达st.exec_block 变量.
    然后就是在 stepper_driver_interrupt_handler 中断函数中. 根据Bresenham算法生成对应的
    移动轨迹. Bresenham算法应该是插补算法.
    最终生成各个轴对应的输出电平数字信号. 放入 st.step_outbits.value 中.
    这个中断函数的触发是由定时器中触发的. .

st.step_outbits.value = step_outbits.value;

然后在stepper_driver_interrupt_handler 中断函数中继续调用 hal.stepper.pulse_start(). 触发driver_stepper.c 中的脉冲输出函数.

  hal.stepper.pulse_start(&st);
  1. driver_stepper.c 执行输出控制芯片引脚
    由于hal.stepper.pulse_start 指向的是 stepperPulseStartDelayed 或 stepperPulseStart
    hal.stepper.pulse_start()函数最终会调用. set_step_outputs执行电平信号输出.
	set_step_outputs(stepper->step_outbits)

至此完. .
总结一下, 大概分以下步.,
第一步 是将字符串转换成变量.
第二步 实现画线,画圆
第三步 运动规划放入buffer.
第四步:状态机中循环取规划,循环执行规划. 并调用步进电机的输出功能,
第五步 步进电机首先使用Bresenham算法进行运动插补.
第六步 调用步进电机.进行输出.

GRBL是一款开源的控制器软件,主要用于激光切割和CNC雕刻机等数控设备。而STM32是一款常用的32位单片机,具有强大的性能和丰富的外设资源。移植GRBL到STM32平台的教程如下: 1. 准备工作:首先,准备一款带有STM32芯片的开发板,如STM32F103C8T6等,以及相应的编程软件和驱动。 2. 下载GRBL源代码:在GitHub或其他开源网站上搜索并下载GRBL的源代码,获取GRBL主程序和相关配置文件。 3. 配置编程环境:使用开发板专用的编程软件,如Keil或IAR等,创建一个新的工程,并将GRBL的源代码导入到工程中。 4. 修改配置文件:根据开发板的硬件资源和外设情况,修改GRBL的配置文件,如配置针脚映射、限位开关、脉冲发生器等参数,确保GRBL能够正确地与硬件进行通信。 5. 编译和烧录:使用编程软件,对GRBL源代码进行编译生成可执行程序,并将程序通过JTAG或ST-LINK等方式烧录到STM32开发板中。 6. 调试和测试:连接STM32开发板与相应的数控设备,如激光切割机或CNC雕刻机,并通过串口或其他通信方式与GRBL进行通信。 7. 优化和调整:根据使用情况和实际需求,对GRBL进行进一步的优化和调整,以提高系统的性能和稳定性。 通过以上步骤,我们可以成功地将GRBL移植到STM32平台上,实现控制和操作数控设备的功能。需要提醒的是,在移植过程中可能会遇到一些问题和困难,需要根据具体情况进行调试和解决。因此,在进行移植之前,建议具备一定的单片机开发和嵌入式系统的基础知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值