protocol.c和protocol.h是实现控制grbl的方法和程序执行协议。涉及到了system.h;stepper.h;print.h;report.h;
system.h 是系统级命令和实时进程。stepper.h是步进电机驱动器,使用步进电机执行planner.c的运动计划。
print.h定义了一组格式化输出字符串的函数。report.h定义了报告和传递消息的方法。
基本函数功能:
#ifndef protocol_h
#define protocol_h
// 从要执行的串行输入流中的行缓冲区大小。
// 注意:除了极端情况之外,没有问题,但是行缓冲区的大小可能太小了。
// 和G代码块可以截断。正式的代码标准,支持高达256
// 字符。在以后的版本中,这将增加,当我们知道有多少额外的
// 内存空间可以投资到这里或重新编写G代码解释器没有这个
// 缓冲区。
#ifndef LINE_BUFFER_SIZE
#define LINE_BUFFER_SIZE 80
#endif
// 开始主循环的方法。它处理来自串行端口的所有传入字符并执行。
// 当它们完成时。它还负责完成初始化过程。
void protocol_main_loop();
// 在主程序的各个停止点检查并执行实时命令。
void protocol_execute_realtime();
//如果启用,则执行自动循环功能。
void protocol_auto_cycle_start();
// 阻塞直到执行所有缓冲步骤。
void protocol_buffer_synchronize();
// 通知步进系统开始在缓冲区执行G代码程序。
// void protocol_cycle_start();
//重新初始化缓冲区当一个进给保持一个resume后
// void protocol_cycle_reinitialize();
// 启动正在运行的程序的进给。
// void protocol_feed_hold();
#endif
各个函数的具体实现:
// 为预解析定义不同的注释类型。
#define COMMENT_NONE 0
#define COMMENT_TYPE_PARENTHESES 1//括号
#define COMMENT_TYPE_SEMICOLON 2//分号
static char line[LINE_BUFFER_SIZE]; // 要执行的行。零终止。
// 指导和执行一行格式化输入从protocol_process。而大多
// 输入流的代码块,这也指导和执行方法的内部命令,
// 如设置,启动归巢的周期(homing cycle),和切换开关状态。
static void protocol_execute_line(char *line)
{
protocol_execute_realtime(); //运行时命令检查点。
if (sys.abort) { return; } // 系统中止时调用函数的保释
#ifdef REPORT_ECHO_LINE_RECEIVED
report_echo_line_received(line);
#endif
if (line[0] == 0) {
// Empty or comment line. Send status message for syncing purposes.
//空或者注释行,送状态信息给syncing purposes
report_status_message(STATUS_OK);
} else if (line[0] == '$') {
// grbl ' $ '系统命令
report_status_message(system_execute_line(line));
} else if (sys.state == STATE_ALARM) {
// Everything else is gcode. Block if in alarm mode.
report_status_message(STATUS_ALARM_LOCK);
} else {
// parse和执行G代码块。
report_status_message(gc_execute_line(line));
}
}
/*
GRBL PRIMARY LOOP:
*/
void protocol_main_loop()
{
// ------------------------------------------------------------
// 完成了初始化程序在上电或复位。
// ------------------------------------------------------------
// Print welcome message
report_init_message();
// 复位,错误或初次加电后检查并报告报警状态。
if (sys.state == STATE_ALARM) {
report_feedback_message(MESSAGE_ALARM_LOCK);
} else {
// 所有系统都去! 但首先检查安全门。
if (system_check_safety_door_ajar()) {
bit_true(sys.rt_exec_state, EXEC_SAFETY_DOOR);
protocol_execute_realtime(); // 进入安全门模式。 应该返回为IDLE状态。
} else {
sys.state = STATE_IDLE; // 将系统设置为就绪。 清除所有状态标志。
}
system_execute_startup(line); // 执行启动脚本。
}
// ---------------------------------------------------------------------------------
// 主循环! 在系统中止时,这会退回到main()来重置系统。
// ---------------------------------------------------------------------------------
uint8_t comment = COMMENT_NONE;
uint8_t char_counter = 0;
uint8_t c;
for (;;) {
// 处理一行输入串行数据,因为数据可用。 执行一个
// 通过删除空格和注释进行初始过滤并将所有字母大写.
// NOTE: While comment, spaces, and block delete(if supported) handling should technically
//注意:尽管在技术上应该注释,空格和块删除(如果支持)处理
// 可以在g代码解析器中完成,这样做有助于将传入的数据压缩成Grbl
// 行缓冲区,它的大小是有限的。 g代码标准实际上表明一条线不能
// 超过256个字符,但Arduino Uno没有这个内存空间。
// 使用更好的处理器,将这个初始解析作为一个非常容易
//独立的任务由g代码解析器和Grbl系统命令共享。
while((c = serial_read()) != SERIAL_NO_DATA) {
if ((c == '\n') || (c == '\r')) { // End of line reached
line[char_counter] = 0; // 设置字符串终止字符。
protocol_execute_line(line); // Line is complete. Execute it!
comment = COMMENT_NONE;
char_counter = 0;
} else {
if (comment != COMMENT_NONE) {
// 扔掉所有评论字符
if (c == ')') {
// End of comment. Resume line. But, not if semicolon type comment.
//评论结束。 恢复行。 但是,不是如果分号类型的评论。
if (comment == COMMENT_TYPE_PARENTHESES) { comment = COMMENT_NONE; }
}
} else {
if (c <= ' ') {
// 扔掉白色空间和控制字符
} else if (c == '/') {
// 阻止删除不支持。 忽略字符。
// 注意:如果支持,只需要检查系统是否启用了块删除。
} else if (c == '(') {
// 启用注释标志并忽略所有字符,直到')'或EOL。
// 注意:这并不完全遵循NIST定义,但现在足够好了。
// 将来,我们可以简单地删除评论中的项目,但保留这些项目
//注释控制字符,以便g代码解析器可以对其进行错误检查。
comment = COMMENT_TYPE_PARENTHESES;
} else if (c == ';') {
// 注意: ';' 对EOL的评论是LinuxCNC的定义。 不是NIST。
comment = COMMENT_TYPE_SEMICOLON;
//TODO:安装'%'功能
// } else if (c == '%') {
// 程序开始结束百分号不受支持。
// 注意:可以安装它来告诉Grbl当程序运行时与手动输入时,
// 在程序中,系统自动循环启动将继续执行
// 直到下一个'%'符号。 这将有助于解决某些问题的恢复问题
// 清空规划器缓冲区以按时执行其任务的函数。
} else if (char_counter >= (LINE_BUFFER_SIZE-1)) {
// 检测行缓冲区溢出。 报告错误并重置行缓冲区。
report_status_message(STATUS_OVERFLOW);
comment = COMMENT_NONE;
char_counter = 0;
} else if (c >= 'a' && c <= 'z') { // Upcase lowercase
line[char_counter++] = c-'a'+'A';
} else {
line[char_counter++] = c;
}
}
}
}
// 如果串行读取缓冲区中没有更多字符需要处理和执行,
// 这表明g代码流已经填充了规划缓冲区或已经存在
// 完成。 在任何一种情况下,自动循环启动(如果启用)都会排队。
protocol_auto_cycle_start();
protocol_execute_realtime(); // 运行时命令检查点。
if (sys.abort) { return; } // 保存到main()程序循环来重置系统。
}
return; /* Never reached */
}
/*
需要时执行运行时命令。
这是从主程序中的各个检查点调用的,
主要是在可能存在等待缓冲区清空空间的while循环
或从上一个检查点执行时间可能超过几分之一秒的任何点
*/
/*
这是一种使用grbl的g-code解析和规划函数异步执行实时命令(又名多任务)的方法。
该函数还可以作为中断的接口来设置系统实时标志,只有主程序可以处理这些标志,
从而不需要定义更多计算量大的易失性变量。
这也提供了一种控制方式来执行某些任务,
而不需要有两个或更多相同任务的实例,例如规划人员在提供或重写时重新计算缓冲区。
*/
// 注:sys.rt_exec_state变量标志由任何进程,步骤或串行中断,引脚排列,
// 限位开关或主程序。
void protocol_execute_realtime()
{
uint8_t rt_exec; // 临时变量避免多次调用volatile。
do { // 如果系统暂停,暂停循环在这里重新启动。
//检查并执行警报。
rt_exec = sys.rt_exec_alarm; // 复制volatile sys.rt_exec_alarm.
if (rt_exec) { // 只有在任何位标志为真时才进入
// 系统报警。 一切都因严重错误的事情而关闭。 报告
// 错误的来源给用户。 如果关键,Grbl通过输入无限循环来禁用,直到系统重置/中止。
sys.state = STATE_ALARM; // 设置系统报警状态
if (rt_exec & EXEC_ALARM_HARD_LIMIT) {
report_alarm_message(ALARM_HARD_LIMIT_ERROR);
} else if (rt_exec & EXEC_ALARM_SOFT_LIMIT) {
report_alarm_message(ALARM_SOFT_LIMIT_ERROR);
} else if (rt_exec & EXEC_ALARM_ABORT_CYCLE) {
report_alarm_message(ALARM_ABORT_CYCLE);
} else if (rt_exec & EXEC_ALARM_PROBE_FAIL) {
report_alarm_message(ALARM_PROBE_FAIL);
} else if (rt_exec & EXEC_ALARM_HOMING_FAIL) {
report_alarm_message(ALARM_HOMING_FAIL);
}
// 暂停关键事件标志上的所有内容。 目前硬限制和软限制标记此。
if (rt_exec & EXEC_CRITICAL_EVENT) {
report_feedback_message(MESSAGE_CRITICAL_EVENT);
bit_false_atomic(sys.rt_exec_state,EXEC_RESET); //禁用任何现有的重置
do {
/*
没有。 阻止一切,直到用户重新设置或关闭电源循环。
硬性限制通常在无人照管或不注意的情况下发生。
为用户提供时间在重置之前执行所需操作,例如杀死传入流。 软限制也是如此。
虽然这个位置并没有丢失,但是如果这个位置继续下去的话,
这个流入的流可能仍然会发生并导致严重的崩溃。
*/
// TODO:在严重警报期间允许状态报告。 仍然需要考虑这一点的含义。
// if (sys.rt_exec_state & EXEC_STATUS_REPORT) {
// report_realtime_status();
// bit_false_atomic(sys.rt_exec_state,EXEC_STATUS_REPORT);
// }
} while (bit_isfalse(sys.rt_exec_state,EXEC_RESET));
}
bit_false_atomic(sys.rt_exec_alarm,0xFF); // 清除所有报警标志
}
// 检查并执行实时命令
rt_exec = sys.rt_exec_state; // Copy volatile sys.rt_exec_state.
if (rt_exec) { // Enter only if any bit flag is true
// 执行系统中止。
if (rt_exec & EXEC_RESET) {
sys.abort = true; // 只有这个设置是正确的。
return; // 没有别的事要做,但退出。
}
// 执行和连续打印状态
if (rt_exec & EXEC_STATUS_REPORT) {
report_realtime_status();
bit_false_atomic(sys.rt_exec_state,EXEC_STATUS_REPORT);
}
// 执行保持状态。
// 注:涉及计算保持的数学应该足够低,对于大多数(如果不是全部的话)
/*
操作场景。 一旦启动保持,系统进入挂起状态以阻止所有主程序进程,直到重置或恢复。
*/
if (rt_exec & (EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR)) {
// TODO:检查模式? 如何处理这个? 没什么可能,因为它只在IDLE时才起作用,然后重置Grbl。
// 状态检查保持方法的允许状态。
if ((sys.state == STATE_IDLE) || (sys.state & (STATE_CYCLE | STATE_HOMING | STATE_MOTION_CANCEL | STATE_HOLD | STATE_SAFETY_DOOR))) {
// 如果处于CYCLE状态,所有保持状态立即启动运动HOLD。
if (sys.state == STATE_CYCLE) {
st_update_plan_block_parameters(); // 通知步进模块重新计算保持减速度。
sys.suspend = SUSPEND_ENABLE_HOLD; // 用标志启动保持周期。
}
// 如果IDLE,Grbl没有运动。 只需指示暂停就绪状态。
if (sys.state == STATE_IDLE) { sys.suspend = SUSPEND_ENABLE_READY; }
/*
用减速度执行并标记一个动作取消并返回到空闲状态。 主要通过探测周期来停止和取消剩余的动作。
*/
if (rt_exec & EXEC_MOTION_CANCEL) {
// MOTION_CANCEL仅在CYCLE期间发生,但HOLD和SAFETY_DOOR可能已预先启动
// 保持CYCLE。 如果是,则只标记该动作取消完成。
if (sys.state == STATE_CYCLE) { sys.state = STATE_MOTION_CANCEL; }
sys.suspend |= SUSPEND_MOTION_CANCEL; // 恢复时指示运动取消。 特别议案完成。
}
// 仅在循环期间执行带减速的进给保持。
if (rt_exec & EXEC_FEED_HOLD) {
// 阻止SAFETY_DOOR状态过早变回HOLD。
if (bit_isfalse(sys.state,STATE_SAFETY_DOOR)) { sys.state = STATE_HOLD; }
}
// 仅在一个循环中执行带进给保持的安全门止动,并禁用主轴/冷却液
/*
注意:安全门与进给保持不同,不管状态如何,停止所有动作的设备(主轴/冷却液),
停止一切,直到开关重新接通。 如果IDLE,或者CYCLE通过EXEC_CYCLE_STOP标志完成,那么执行掉电。
*/
if (rt_exec & EXEC_SAFETY_DOOR) {
report_feedback_message(MESSAGE_SAFETY_DOOR_AJAR);
// 如果已处于激活状态,准备恢复保持状态,则设置CYCLE_STOP标志强制断电。
// 注:仅临时设置'rt_exec'变量,而不是易变的'rt_exec_state'变量.
if (sys.suspend & SUSPEND_ENABLE_READY) { bit_true(rt_exec,EXEC_CYCLE_STOP); }
sys.suspend |= SUSPEND_ENERGIZE;
sys.state = STATE_SAFETY_DOOR;
}
}
bit_false_atomic(sys.rt_exec_state,(EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR));
}
// 通过启动步进器中断来执行周期启动,以开始执行队列中的块。
if (rt_exec & EXEC_CYCLE_START) {
// 如果与保持命令同时被呼叫,则阻止:进给保持,动作取消和安全门。
// 如果没有明确的用户输入,确保自动循环启动不会恢复保持状态。
if (!(rt_exec & (EXEC_FEED_HOLD | EXEC_MOTION_CANCEL | EXEC_SAFETY_DOOR))) {
// 仅当IDLE或保持完成并准备好恢复时才开始循环。
// 注意:SAFETY_DOOR被隐式阻止。 当门关闭时它会恢复到HOLD。
if ((sys.state == STATE_IDLE) || ((sys.state & (STATE_HOLD | STATE_MOTION_CANCEL)) && (sys.suspend & SUSPEND_ENABLE_READY))) {
// 如果被SAFETY_DOOR禁用,则重新激活供电组件。
if (sys.suspend & SUSPEND_ENERGIZE) {
// 延迟任务:重新启动主轴和冷却液,延迟加电,然后恢复循环。
if (gc_state.modal.spindle != SPINDLE_DISABLE) {
spindle_set_state(gc_state.modal.spindle, gc_state.spindle_speed);
delay_ms(SAFETY_DOOR_SPINDLE_DELAY); // TODO:阻止函数调用。 最终需要一个非阻塞的。
}
if (gc_state.modal.coolant != COOLANT_DISABLE) {
coolant_set_state(gc_state.modal.coolant);
delay_ms(SAFETY_DOOR_COOLANT_DELAY); // TODO:阻止函数调用。 最终需要一个非阻塞的。
}
// TODO: Install return to pre-park position.
}
// 仅当计划器缓冲区中存在排队运动并且运动未取消时才开始循环。
if (plan_get_current_block() && bit_isfalse(sys.suspend,SUSPEND_MOTION_CANCEL)) {
sys.state = STATE_CYCLE;
st_prep_buffer(); // 在开始循环之前初始化步段缓冲区。
st_wake_up();
} else { // 否则,什么都不要做。 设置并恢复空闲状态。
sys.state = STATE_IDLE;
}
sys.suspend = SUSPEND_DISABLE; //暂停状态。
}
}
bit_false_atomic(sys.rt_exec_state,EXEC_CYCLE_START);
}
//重新初始化循环计划和步进系统后,饲料保持一个简历。 被称为
//在主程序中执行实时命令,确保规划人员安全地重新计划。
//注意:Bresenham算法变量仍然通过规划器和步进器来维护
//循环重新初始化。 步进路径应该继续,就像没有任何事情发生一样。
//注意:当周期或进给保持完成时,由步进子系统设置EXEC_CYCLE_STOP。
if (rt_exec & EXEC_CYCLE_STOP) {
if (sys.state & (STATE_HOLD | STATE_SAFETY_DOOR)) {
//保持完成。 设置为指示准备恢复。 保持HOLD或DOOR状态直到用户
//已发出恢复命令或重置。
if (sys.suspend & SUSPEND_ENERGIZE) { // 如果安全门已打开,则断开系统电源。
spindle_stop();
coolant_stop();
// TODO: Install parking motion here.
}
bit_true(sys.suspend,SUSPEND_ENABLE_READY);
} else { // 动作完成。 包括CYCLE,HOMING和MOTION_CANCEL状态。
sys.suspend = SUSPEND_DISABLE;
sys.state = STATE_IDLE;
}
bit_false_atomic(sys.rt_exec_state,EXEC_CYCLE_STOP);
}
}
//覆盖标志字节(sys.override)并执行应该在这里安装,因为它们
//是实时的,并且需要直接和受控制的主步进程序界面。
//重新加载段缓冲区
if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_MOTION_CANCEL | STATE_SAFETY_DOOR | STATE_HOMING)) { st_prep_buffer(); }
//如果安全门打开,主动检查安全门何时关闭并准备恢复。
//注意:这会将SAFETY_DOOR状态解锁为HOLD状态,以便CYCLE_START可以激活简历。
if (sys.state == STATE_SAFETY_DOOR) {
if (bit_istrue(sys.suspend,SUSPEND_ENABLE_READY)) {
if (!(system_check_safety_door_ajar())) {
sys.state = STATE_HOLD; // 更新为保持状态以指示门已关闭并准备恢复。
}
}
}
} while(sys.suspend); // 退出前检查系统暂停状态。
}
// 阻塞直到所有缓冲步骤都被执行或处于循环状态。 work with 进给保持
// 在同步调用期间,如果它发生。 此外,等待清洁周期结束。
/*
阻塞直到所有缓冲步骤都被执行或处于循环状态。 如果发生同步调用,则与feed feed一起工作。 此外,等待清洁周期结束。
*/
void protocol_buffer_synchronize()
{
// 如果系统已排队,则确保循环在自动启动标志存在的情况下继续。
protocol_auto_cycle_start();
do {
protocol_execute_realtime(); // 检查并执行运行时命令
if (sys.abort) { return; } //检查系统中止
} while (plan_get_current_block() || (sys.state == STATE_CYCLE));
}
//自动循环启动有两个目的:1.从函数中恢复一个plan_synchronize()调用
//需要规划器缓冲区清空(主轴启用,停顿等)2.作为用户设置
//当用户手动输入有效的动作命令时自动开始循环。 这是
//打算作为初学者功能来帮助新用户了解g代码。 它可以被禁用
//作为初学者工具,但(1.)仍在运行。 如果禁用,则循环开始的操作是
//每当用户准备好并且有一个有效的动作时手动发出一个循环开始命令
//计划器队列中的命令。
//注意:仅在主循环,缓冲区同步和mc_line()中调用此函数并执行
//当这些条件之一分别存在时:没有更多的块被发送(即流式传输
//完成,单个命令),需要等待缓冲区中的运动的命令
//执行调用缓冲区同步,或者规划器缓冲区已满并准备就绪。
void protocol_auto_cycle_start() { bit_true_atomic(sys.rt_exec_state, EXEC_CYCLE_START); }
目前我对这个protocol.c部分的理解就是对serial传来的数据进行固定的格式解析,得到指令的含义。