openpnp - 二手西门子电动飞达的测试

news2024/11/25 11:36:25

文章目录

    • 二手西门子电动飞达的初步测试
    • 概述
    • 飞达正常的判断标准
    • 先挑出一个手工控制好使的二手飞达用于测试.
    • 推料的手工检测
    • 扒皮的手工检测
    • 飞达测试的接线
    • 通讯的测试
    • 用串口助手测试通讯
    • 先看看是否发送给飞达的管脚是自己接的那个
    • 查看所有可以用到的上位机通讯命令
    • M115 - 打印固件版本信息
    • M600 - 撕皮预动作
    • M601 - 高级送料模式
    • M602 - 取飞达状态
    • M603 - 取飞达送料计数值
    • M604 - 取0x42错误次数
    • M605 - 取0x43错误次数
    • M606 取0x44错误次数
    • M607 取复位次数
    • M608 - 取飞达的料之间的距离(步进值)
    • M628 - 切换步进值
    • M610 取飞达ID
    • M640 设置飞达ID
    • M630 读飞达内的E2PROM
    • M615 读飞达的固件版本
    • M650 - 自测试开始
    • M651 - 自测试结束
    • 备注
    • 改过的飞达控制板的mega2560r3测试工程
    • SchultzController.ino
    • config.h
    • Feeder.h
    • Feeder.cpp
    • gcode.ino
    • END

二手西门子电动飞达的初步测试

概述

现在手头的openpnp设备和自己做的散料飞达都正常用了, 已经将飞达控制板贴出来了.
在这里插入图片描述

下面, 准备将西门子二手飞达接入设备, 让自己的openpnp设备也变成一个有灵魂的贴片机.

当时定制设备时, 同学给我定了50个二手西门子电动飞达.
因为当时设备刚到手, 还在进行设备调试. 飞达到货后, 只是通电看灯是否亮, 发个包, 看看是否能用通讯控制飞达有动作响应. 只是初步抽检.

现在因为前置条件(设备调试完成, 飞达控制板已经贴好了)都好了, 准备将这50个飞达中好的飞达挑出来用, 将不好的飞达尝试通过换件法拼成能用的正常飞达.

那第一步就是判断这些飞达是否可以正常用(是否可以通过通讯来控制? 按钮控制即使坏了也无所谓, 用不到), 进行一些测试, 通过测试了, 就可以正常接入设备用.
以8x2飞达为例

飞达正常的判断标准

  • 可以正常推料, 供吸嘴来取料.

  • 可以正常将物料编带的透明塑料皮扒下来, 拉紧, 防止塑料蒙皮挡住供料窗.
    撕皮有2个部件:
    飞达前面的塑料皮挡板, M600 NX命令后让挡板向飞达后方动5mm, 靠着撕料齿轮压板的力量, 将塑料皮撕开.
    飞达中部的撕料齿轮
    只有这2个部件都是好的, 撕皮操作才正常.

初步用飞达上的按钮来测试, 不能用也无所谓, 只要用通讯能控制这2个动作就行.

从飞达正后方看, 左边是0号飞达, 右边是1号飞达.

先挑出一个手工控制好使的二手飞达用于测试.

因为我是要先采用通讯控制来控制飞达, 不管按钮好不好使, 主要通讯控制能正常控制飞达就行.
但是初步, 我要挑出一个正常能用按钮来控制的飞达来做第一次的测试.
试了4个, 才挑出一个手工按钮控制都好使的8x2的二手飞达.

推料的手工检测

在这里插入图片描述
飞达后端的绿色箭头按钮是进料按钮, 每按一下, 飞达前部的供料齿轮就转一个角度, 带动物料编带向前走.
如果飞达无响应, 也不一定意味着不能用, 也许按钮接触不好(或者按钮控制电路问题). 后续要用通讯协议来控制, 能用就好.

扒皮的手工检测

在这里插入图片描述
飞达正后方的黄色圆形按钮, 是扒皮按钮.
按下后, 如果正常, 飞达中部的扒皮齿轮会转动. 等后续, 将编带的塑料皮夹入2个扒皮齿轮中间就行. 在正常贴片流程中, 就会随着扒皮齿轮的转动, 将废弃的塑料皮带到飞达下部的垃圾仓. 同时, 将物料编带的保护皮撕下来.

飞达测试的接线

在这里插入图片描述
在这里插入图片描述
飞达有4根线:
棕色 : VCC(资料上说要给DC28V, 市面上没有DC28V或者DC30V的电源, 只能是用DC24V的开关电源来供电, 能用)
白色: GND
绿色: 通讯线TX(飞达控制板to飞达的发送, 飞达控制者发包)
黄色: 通讯线RX(飞达to飞达控制板的接收, 飞达控制者收包)
通讯的电平是24V的, 如果和MCU通讯, 要转成MCU适用的电平.

飞达控制板上的控制部件用的是官方原版mega2560R3(支持通讯时触发DTR信号重启设备), 通过USB线连接PC端.
飞达控制板子的供电用的开关电源的DC24V.
和飞达的通讯通过插座(XHD-2*10Y)连接, 线自己压出来, 接飞达航插板.

飞达航插板的供电是开关电源的DC24V, 从飞达控制板来的通讯信号连接到航空插头座(公头).
飞达航插板的航插座(公头)带着24V, GND, TX, RX接飞达出来的航插母头.

通讯的测试

从开源工程上, 将飞达数量改为1, 避免控制不准, 造成错觉(发个包给飞达, 如果发错飞达就尴尬了.).

#if defined (BOARD_MEGA2560)
// 用内存的地方
// FeederClass feeders[NUMBER_OF_FEEDERS];
// 如果不优化(不开DEBUG标记), 只能支持46把飞达
// FEEDER_CNT 47个 : 全局变量使用 8305 个字节(101%)的动态内存,剩下 -113 个字节用于局部变量。最大值为 8192 字节。
// FEEDER_CNT 46个 : 全局变量使用 7903 个字节(96%)的动态内存,剩下 289 个字节用于局部变量。最大值为 8192 字节。
// FEEDER_CNT 45个 : 个全局变量使用 7683 个字节(93%)的动态内存,剩下 509 个字节用于局部变量。最大值为 8192 字节。
// FEEDER_CNT 44个 : 个全局变量使用 7469 个字节(91%)的动态内存,剩下 723 个字节用于局部变量。最大值为 8192 字节。
// 好像局部变量的内存用量, 并没有估计到编译结果中. 局部变量要用多少, 需要自己估计.
// 能编译过的飞达数量是47组, 但是局部变量空间不够. 可以正常运行的飞达数量是44组.
#define FEEDER_CNT 1 // this only for test
#else
#define FEEDER_CNT 20
#endif

确定飞达通讯控制的管脚对应的mega2560R3的数字IO号码.

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我现在的测试连接, 使这个飞达的通讯接到了D21来控制.
在meg2560r3工程中, 将唯一的发达发送管脚定为D21.

/*
       *  Feeder number to TX port mapping (uses D port numbers)
       */
#if defined (BOARD96PIN)
      const uint8_t TXportPin[20] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2 };
#elif defined (BOARD4PIN)
      const uint8_t TXportPin[20] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12 }; // Matches ref des order of board
      //const uint8_t TXportPin[20] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; // J20 is port 0, then goes counter-clockwise
#elif defined (BOARD_MEGA2560) // BOARD_MEGA2560 已经定义
      // 官方arduino mega2560 R3 一共有70个数字IO(D0~D69), D0/D1 被编程串口占用了不能用. 剩下的可用数字IO位68个(D2~D69)
      // 其中D14/D15 = UART3, D16/D17 = UART2, D18/D19 = UART1, 这3个串口留着调试用, 剩下的可用数字IO为62个(D2~D13, D20~D69)   
const uint8_t TXportPin[FEEDER_CNT] = { 
        // D2 => D31 was F1 => F30
        21, // pin = D21 // 就定义这一个引脚
        /*2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 
        12, 13, 20, 21, 22, 23, 24, 25, 26, 27, 
        28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 
        38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 
        48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
        58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 
        68, 69*/
        };
#else
      const uint8_t TXportPin[20] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12, 14, 15, 16, 17, 18, 19, 20, 21 };
#endif

为了弄懂飞达控制的整个流程, 我加了调试宏 DEBUG, 打印了串口调试语句(被DEBUG宏包围), 哪里不对劲, 就可以根据调试信息去代码里面去找问题.

在arduinoIDE中, 指定开发板和串口, 编译工程, 上传到mega2560R3.

用串口助手测试通讯

测试命令都是M码, 以\n为结尾. 结尾的\n标志着命令发完, 否则飞达控制板不响应命令(没有\n就认为用户命令还没有输入完成).

先看看是否发送给飞达的管脚是自己接的那个

在这里插入图片描述
看到通讯发送的管脚是D21, 没错.

查看所有可以用到的上位机通讯命令

工程改了一下, 可以用M999看到所有可以用到的通讯命令.
在这里插入图片描述
现在挨个来测试通讯命令, 有的命令是和飞达控制板来通讯; 有的命令是通过飞达控制板中转来和飞达通讯.

M115 - 打印固件版本信息

在这里插入图片描述
这个是和飞达控制板通讯, 打印固件版本号, 防止固件版本不是最新.

M600 - 撕皮预动作

在这里插入图片描述
M600 N0 是控制0号位飞达送料, 用眼睛观察, 看到飞达前部左边的撕皮机构, 向后动了一下. 但是中间撕皮齿轮没有动作.
在这里插入图片描述
M600 N1 是控制0号位飞达送料, 用眼睛观察, 看到飞达前部右边的撕皮机构, 向后动了一下. 但是中间撕皮齿轮没有动作.

M601 - 高级送料模式

在这里插入图片描述
M601 N0X0 返回错误, 说没有胶带张力, 因为我现在没有正常上料, 以后再测试这个命令.
在这里插入图片描述
M601 N1X0 返回错误, 说没有胶带张力, 因为我现在没有正常上料, 以后再测试这个命令.

在这里插入图片描述
M601 N0X1 执行后, 只看到0号飞达前面的送料齿轮步进了一下, 没看到撕料机构往后动.
在这里插入图片描述
M601 N0X1 执行后, 只看到1号飞达前面的送料齿轮步进了一下, 没看到撕料机构往后动.

M602 - 取飞达状态

在这里插入图片描述
M602 N0 是取0号位飞达的状态, 现在由于没有正常上料, 飞达编带塑料膜拉紧机构没有检测到张力.

String FeederClass::showStatus() {
  switch (this->feederStatus) {
    case STATUS_OK:
      return "getFeederStatus: feeder OK";
      break;
    case STATUS_INVALID:
      return "getFeederStatus: invalid, status not updated";
      break;
    case STATUS_NO_TAPE_TENSION:
      return "getFeederStatus: No tape tension.  Tape may be broken";
      break;
    case STATUS_NO_TAPE_TRIGGER:
      return "getFeederStatus: Tape take-up not triggered after multiple feeds";
      break;
    case STATUS_FEED_ERROR:
      return "getFeederStatus: Feed motor did not advance";
      break;
    default:
      char statusCode[34];
      sprintf(statusCode, "Unrecognized status code %02X", this->feederStatus);
      return statusCode;
  }
}
/* -----------------------------------------------------------------
*  FEEDER STATUS
*  ----------------------------------------------------------------- */
#define STATUS_OK 0x40
#define STATUS_NO_TAPE_TENSION 0x42 	// cover tape won't tension (no tape, or broken tape)
#define STATUS_NO_TAPE_TRIGGER 0x43  	// didn't trigger a retension after 2 feeds
#define STATUS_FEED_ERROR 0x44          // feed motor stuck
#define STATUS_INVALID 0                // status not checked since last feed

从代码中看到, 只有飞达回0x40时, 才说明飞达没问题.
在这里插入图片描述
M602 N1 是取1号位飞达的状态, 现在由于没有正常上料, 飞达编带塑料膜拉紧机构没有检测到张力.

M603 - 取飞达送料计数值

在这里插入图片描述
M603 N0 命令取回的飞达计数不对, 应该是协议不对. 不过这不重要, 这个命令不在正式场景下应用.
在这里插入图片描述
M603 N1 命令取回的飞达计数不对, 应该是协议不对(以后条件许可时, 可以通过编程器, 读回飞达内的51MCU的bin实现, 逆向分析一下. 听同学说, 这个C51实现是没没有加密的, 直接能读回来). 不过这不重要, 这个命令不在正式场景下应用.

M604 - 取0x42错误次数

在这里插入图片描述

M605 - 取0x43错误次数

在这里插入图片描述

M606 取0x44错误次数

在这里插入图片描述

M607 取复位次数

在这里插入图片描述
看到向2个飞达位发送的命令都是一样的, 说明这个复位次数指的是飞达上电复位的次数, 因为断电再上电, 2个子飞达是一起复位的.

M608 - 取飞达的料之间的距离(步进值)

在这里插入图片描述
看到当前物料之间的距离为4mm.
可以通过飞达按钮的组合来改物料步进值(这个没有原版飞达文档, 得自己去实验). 还可以用M628 NX来切换步进

M628 - 切换步进值

在这里插入图片描述
M628 N0 不带参数, 看到设置的步进类型是0.
读一下步进
在这里插入图片描述
看到当前步进值为2mm
M628 N0 不带参数, 看到设置的步进类型是1.
在这里插入图片描述

读一下步进值
在这里插入图片描述
当前步进是4mm
这个命令等于是每次都先取一下当前步进值, 然后将步进值改变为下一个.
e.g. 8mm飞达只有2个步进值(2mm, 4mm), 每执行一次 M628NX, 就切换到下一种步进值.

M628 N1 同理

M610 取飞达ID

在这里插入图片描述

M640 设置飞达ID

在这里插入图片描述
现在已经将飞达ID设置为了1234
现在读取一下飞达ID看看改过来没有?
在这里插入图片描述
飞达ID已经改过来了.
这个飞达ID有用, 如果某个飞达需要特殊处理, 可以根据飞达ID和飞达位来判断.

M630 读飞达内的E2PROM

这个命令对原厂的工程师才有用.
在这里插入图片描述
这个命令, 从N0/N1的E2PROM中读取了16个字节出来.

M615 读飞达的固件版本

在这里插入图片描述
我现在测试这个飞达版本是v2.4

M650 - 自测试开始

在这里插入图片描述
执行这个命令后, 飞达开始自测试, 能看到所有飞达位的主要的机械结构(推料齿轮, 前面的撕料挡板, 中部的卷带齿轮)都在周期性的在动.
执行这个命令, 可以知道这对应飞达位的这3个机械装置是否完好, 也就知道是否可以通过通讯协议来控制飞达. 如果要初步测试飞达是否可以用通讯协议来控制飞达的机械机构, 用 M650 N0 这1个命令就行了.

执行这个命令时, 不能在上料的情况下进行, 测试的进料忽忽的, 不知道要浪费多少料…

M651 - 自测试结束

在这里插入图片描述
执行 M651 N0后, 自测试就停止了.

备注

西门子二手飞达能提供的协议就这些, 现在只能说飞达可以和飞达控制板通讯, 因为我没看到完整的进料撕膜动作.
下一步, 得用这个飞达连到openpnp中看看效果(在正常上料的情况下), 如果在openpnp中能正常控制送料, 撕膜, 能完成一种物料的连续的正常贴片流程, 才说明这个飞达没问题.

改过的飞达控制板的mega2560r3测试工程

在这里插入图片描述
arduino工程的文件包含关系, 是放在工程目录下的文件都算是源文件, 不是显势包含.
已经试过了, 如果将gcode.ino移动到同级目录下的bk目录, 工程就编译不过了, 因为好多实现在gcode.ino中.
主工程是SchultzController.ino, 因为这个文件中才有setup()和loop().

等用串口助手测试过了, 要给openpnp用时, 要注释掉DEBUG宏, 否则调试信息的回包, 会干扰openpnp的的判断.

SchultzController.ino

/*
* Author: Bill Ruckman
* (c)2020
*
* Adapted from 0816feeder by mrgl
*    https://github.com/mgrl/0816-feeder-firmware
*
* This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* http://creativecommons.org/licenses/by-nc-sa/4.0/
*
* current version: v0.0
*
*
*/

// ------------------  I N C  L I B R A R I E S ---------------

#include "config.h"
#include "Feeder.h"


// ------------------  V A R  S E T U P -----------------------

FeederClass feeders[NUMBER_OF_FEEDERS];

// ------------------  U T I L I T I E S ---------------


// ------------------  S E T U P -----------------------
void setup() {
  byte i;

  Serial.begin(SERIAL_BAUD);
  while (!Serial)
    ;
  Serial.println(PRJ_NAME " " PRJ_VER " "
                          "starting...");
  Serial.flush();

  Serial1.begin(9600);  // The hardware RX port

  for (i = 0; i < NUMBER_OF_FEEDERS; i++) {
    feeders[i].setup(i, i / LANES_PER_PORT, i % LANES_PER_PORT);  // initialize with feeder number, port and lane
  }

  // setup listener to serial stream
  setupGCodeProc();

  Serial.println(PRJ_NAME " " PRJ_VER " "
                          "ready.");
}



// ------------------  L O O P -----------------------

void loop() {
  // Process incoming serial data and perform callbacks
  listenToSerialStream();
}

config.h

#ifndef _CONFIG_h
#define _CONFIG_h
#include "arduino.h"

#define PRJ_NAME "my_SchultzController"
#define PRJ_VER "v2.0.1.2_build_2023_0909_1333"

//#define BOARD96PIN
//#define BOARD4PIN
#define BOARD_MEGA2560

// prints some extra information via serial1
// uncomment to disable in production
#define DEBUG
// #define DEBUG1 // my debug flag

// simulates connected feeders
//#define SIMULATE


#define LANES_PER_PORT 2  // only supports 2 for now
// The serial port = Feeder Number / LANES_PER_PORT
// The lane within the port = Feeder Number % LANES_PER_PORT

#if defined (BOARD_MEGA2560)
// 用内存的地方
// FeederClass feeders[NUMBER_OF_FEEDERS];
// 如果不优化(不开DEBUG标记), 只能支持46把飞达
// FEEDER_CNT 47个 : 全局变量使用 8305 个字节(101%)的动态内存,剩下 -113 个字节用于局部变量。最大值为 8192 字节。
// FEEDER_CNT 46个 : 全局变量使用 7903 个字节(96%)的动态内存,剩下 289 个字节用于局部变量。最大值为 8192 字节。
// FEEDER_CNT 45个 : 个全局变量使用 7683 个字节(93%)的动态内存,剩下 509 个字节用于局部变量。最大值为 8192 字节。
// FEEDER_CNT 44个 : 个全局变量使用 7469 个字节(91%)的动态内存,剩下 723 个字节用于局部变量。最大值为 8192 字节。
// 好像局部变量的内存用量, 并没有估计到编译结果中. 局部变量要用多少, 需要自己估计.
// 能编译过的飞达数量是47组, 但是局部变量空间不够. 可以正常运行的飞达数量是44组.
#define FEEDER_CNT 1
#else
#define FEEDER_CNT 20
#endif

// 如果是单个飞达做实验, 如果重新插拔了F0TX, 需要给飞达重新上电, 否则不通讯.
// pin0, 1 和arduino上传串口有冲突(也许是同一个串口), 实际操作的D0, 是D2.

// 将飞达接到D2, 然后操作D0, 就可以控制
// pin2 => D0 => FD0, FD1
// pin3 => D1 => FD2, FD3
// pin4 => D2 => FD4, FD5
// pin5 => D3 => FD6, FD7 // M601N6X1\n, M601N7X1\n
// pin6 => D4 => FD8, FD9
// pin7 => D5 => FD10, FD11
// pin8 => D6 => FD12, FD13
// pin9 => D7 => FD14, FD15
// pin10 => D8 => FD16, FD17
// pin11 => D9 => FD18, FD19
// pin12 => D10 => FD20, FD21
// pin13 => D11 => FD22, FD23 // M601N22X1\n M601N23X1\n

// pin14, pin15 串口3, 留出来通讯用
// pin16, pin17 串口2, 留出来通讯用
// pin18, pin19 串口1, 留出来通讯用, 接收飞达回包. pin19是RXD1 // 总的RX接到了pin19(RXD1)

// pin20 => D18 => FD24, FD25
// pin21 => D19 => FD26, FD27 // M601N26X1\n M601N27X1\n
// pin22 => D20 => FD28, FD29 // M601N28X1\n M601N29X1\n
// pin23 => D21 => FD30, FD31
// pin24 => D22 => FD32, FD33
// pin25 => D23 => FD34, FD35
// pin26 => D24 => FD36, FD37
// pin27 => D25 => FD38, FD39 // M601N38X1\n M601N39X1\n
// pin28 => D26 => FD40, FD41
// pin29 => D27 => FD42, FD43
// pin30 => D28 => FD44, FD45
// pin31 => D29 => FD46, FD47
// pin32 => D30 => FD48, FD49
// pin33 => D31 => FD50, FD51
// pin34 => D32 => FD52, FD53
// pin35 => D33 => FD54, FD55
// pin36 => D34 => FD56, FD57 //
// pin37 => D35 => FD58, FD59
// pin38 => D36 => FD60, FD61
// pin39 => D37 => FD62, FD63
// pin40 => D38 => FD64, FD65
// pin41 => D39 => FD66, FD67
// pin42 => D40 => FD68, FD69
// pin43 => D41 => FD70, FD71
// pin44 => D42 => FD72, FD73
// pin45 => D43 => FD74, FD75
// pin46 => D44 => FD76, FD77
// pin47 => D45 => FD78, FD79
// pin48 => D46 => FD80, FD81
// pin49 => D47 => FD82, FD83
// pin50 => D48 => FD84, FD85
// pin51 => D49 => FD86, FD87 // M601N86X1\n M601N87X1\n

// pin52 => D50 => FD88, FD89
// pin53 => D51 => FD90, FD91
// #define FEEDER_CNT 46 !!!

// pin54 => D52 => FD92, FD93
// pin55 => D53 => FD94, FD95
// pin56 => D54 => FD96, FD97
// pin57 => D55 => FD98, FD99
// pin58 => D56 => FD100, FD101
// pin59 => D57 => FD102, FD103
// pin60 => D58 => FD104, FD105
// pin61 => D59 => FD106, FD107
// pin62 => D60 => FD108, FD109
// pin63 => D61 => FD110, FD111
// pin64 => D62 => FD112, FD113
// pin65 => D63 => FD114, FD115
// pin66 => D64 => FD116, FD117
// pin67 => D65 => FD118, FD119
// pin68 => D66 => FD120, FD121
// pin69 => D67 => FD122, FD123




#define NUMBER_OF_FEEDERS (LANES_PER_PORT * FEEDER_CNT)  // number of ports(20) * LANES_PER_PORT

/*
 *  Upstream Serial
 */
#define SERIAL_BAUD 115200

//buffer size for serial commands received
#define MAX_BUFFFER_MCODE_LINE 64	// no line can be longer than this


/* -----------------------------------------------------------------
*  FEEDER COMMANDS
*  ----------------------------------------------------------------- */
#define CMD_INFO 0x01
#define CMD_PRE_PICK 0x02
#define CMD_ADVANCE 0x03
#define CMD_STATUS 0x15
#define CMD_EEPROM_READ 0x17
#define CMD_SELF_TEST 0x18
#define CMD_EEPROM_WRITE 0x19
#define CMD_SET_PITCH 0x1c

/* -----------------------------------------------------------------
*  FEEDER STATUS
*  ----------------------------------------------------------------- */
#define STATUS_OK 0x40
#define STATUS_NO_TAPE_TENSION 0x42 	// cover tape won't tension (no tape, or broken tape)
#define STATUS_NO_TAPE_TRIGGER 0x43  	// didn't trigger a retension after 2 feeds
#define STATUS_FEED_ERROR 0x44          // feed motor stuck
#define STATUS_INVALID 0                // status not checked since last feed

/* -----------------------------------------------------------------
*  FEEDER COMM CONSTANTS
*  ----------------------------------------------------------------- */
#define ACK_TIMEOUT 50
#define RESP_TIMEOUT 50
#define MSG_ACK 0xE0


/* -----------------------------------------------------------------
*  M-CODES
*  ----------------------------------------------------------------- */
#define MCODE_DRIVER_INFO 115
#define MCODE_PRE_PICK 600
#define MCODE_ADVANCE 601
#define MCODE_FEEDER_STATUS 602
#define MCODE_GET_FEED_COUNT 603
#define MCODE_CLEAR_FEED_COUNT 623
#define MCODE_GET_ERR42_COUNT 604
#define MCODE_GET_ERR43_COUNT 605
#define MCODE_GET_ERR44_COUNT 606
#define MCODE_GET_RESET_COUNT 607
#define MCODE_GET_PITCH 608
#define MCODE_TOGGLE_PITCH 628
#define MCODE_GET_FEEDER_ID 610
#define MCODE_SET_FEEDER_ID 640
#define MCODE_READ_EEPROM 630
#define MCODE_GET_FIRMWARE_INFO 615
#define MCODE_START_SELF_TEST 650
#define MCODE_STOP_SELF_TEST 651
#define MCODE_CMD_HELP 999


//DEFINE config_h-ENDIF!!!
#endif

Feeder.h

#ifndef _FEEDER_h
#define _FEEDER_h

#include "arduino.h"
#include "config.h"


class FeederClass {
	protected:

			//on initialize it gets a number.
			int feederNo=-1;
			
			uint8_t port;	// TX serial port for this feeder
			uint8_t lane;	// lane within port
			uint8_t feederStatus = 0;	// initialized to invalid

      #ifdef SIMULATE
        uint8_t eeprom[16]; // simulates eeprom storage for read and write commands
      #endif

      /*
       *  Feeder number to TX port mapping (uses D port numbers)
       */
#if defined (BOARD96PIN)
      const uint8_t TXportPin[20] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2 };
#elif defined (BOARD4PIN)
      const uint8_t TXportPin[20] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12 }; // Matches ref des order of board
      //const uint8_t TXportPin[20] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; // J20 is port 0, then goes counter-clockwise
#elif defined (BOARD_MEGA2560)
      // 官方arduino mega2560 R3 一共有70个数字IO(D0~D69), D0/D1 被编程串口占用了不能用. 剩下的可用数字IO位68个(D2~D69)
      // 其中D14/D15 = UART3, D16/D17 = UART2, D18/D19 = UART1, 这3个串口留着调试用, 剩下的可用数字IO为62个(D2~D13, D20~D69)   
const uint8_t TXportPin[FEEDER_CNT] = { 
        // D2 => D31 was F1 => F30
        21, // pin = D21
        /*2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 
        12, 13, 20, 21, 22, 23, 24, 25, 26, 27, 
        28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 
        38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 
        48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
        58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 
        68, 69*/
        };
#else
      const uint8_t TXportPin[20] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12, 14, 15, 16, 17, 18, 19, 20, 21 };
#endif

      bool inverse_logic = true;  // inverted logic for serial output
      
      bool sendCommand(uint8_t command);  // sends a simple command
      bool sendCommand(uint8_t command, uint8_t *dataBuf);  // sends a simple command, gets a response in dataBuf
      bool sendCommand(uint8_t command, uint8_t *dataBuf, uint8_t offset);  // sends a simple command with extra byte (offset) after lane, gets a response in dataBuf
      bool sendCommand(uint8_t command, uint8_t len, uint8_t *dataBuf);  // sends a simple command followed by data in dataBuf
      bool receiveACK();
      bool receiveMessage(uint8_t *dataBuf);

      /*
       * software serial routines - adapted from SendOnlySoftwareSerial by Nick Gammon 30th December 2016
       */
      uint8_t _transmitBitMask;
      volatile uint8_t *_transmitPortRegister;
      uint8_t m_u8_tx_pin; // 具体是操作核心板哪个引出的管脚

      // Expressed as 4-cycle delays (must never be 0!)
      uint16_t _tx_delay;

      // private methods
      void setTX(uint8_t transmitPin);

      // Return num - sub, or 1 if the result would be < 1
      static uint16_t subtract_cap(uint16_t num, uint16_t sub);

      // private static method for timing
      static inline void tunedDelay(uint16_t delay);

	public:

    //store last timestamp command was sent for timeout
    unsigned long lastTimeCommandSent;
    
    void setup(uint8_t _feeder_no, uint8_t port, uint8_t lane);

    bool sendPrePick();
    bool sendAdvance(bool overrideError);
    bool setPitch(uint8_t pitch);
    bool clearFeedCount();
    bool getFeederStatus();
    bool readEEPROM(uint8_t *buf);
    bool readInfo(uint8_t *buf);
    bool startSelfTest();
    bool stopSelfTest();
    bool setID(int32_t feederID);
    
    String reportStatus();
    String showStatus();
    bool feederIsOk();

    //software serial
    void begin(long speed);
    virtual size_t write(uint8_t byte);
};

extern FeederClass Feeder;

#endif

Feeder.cpp


#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <Arduino.h>
#include <util/delay_basic.h>

#include "Feeder.h"
#include "config.h"

void FeederClass::setup(uint8_t _feederNo, uint8_t port, uint8_t lane) {
  this->feederNo = _feederNo;
  this->port = port;
  this->lane = lane + 1;  // lanes are numbered starting from 1

  FeederClass::setTX(TXportPin[port]);
  FeederClass::begin(9600);  // serial baud rate

#ifdef DEBUG
  Serial.print("FeederClass::setup(_feederNo = ");
  Serial.print(_feederNo);

  Serial.print(", port = ");
  Serial.print(port);

  Serial.print(", lane = ");
  Serial.print(lane);

  Serial.print("), TXportPin[port] = ");
  Serial.print(TXportPin[port]);

  Serial.print("), this->m_u8_tx_pin = ");
  Serial.println(this->m_u8_tx_pin);

#endif
}

bool FeederClass::receiveACK() {
  bool b_rc = false;
  uint8_t RXbuf[4];

  while (!Serial1.available()) {
    if ((millis() - this->lastTimeCommandSent) > ACK_TIMEOUT) {
#ifdef DEBUG
      Serial.println(" ACK timeout!");
#endif
      return false;
    }
  }

  Serial1.readBytes(RXbuf, 3);

#ifdef DEBUG
  Serial.print("FeederClass::receiveACK() : recv : ");
  Serial.print(RXbuf[0], HEX);
  Serial.print(' ');
  Serial.print(RXbuf[1], HEX);
  Serial.print(' ');
  Serial.println(RXbuf[2], HEX);
#endif

  b_rc = ((RXbuf[0] == 1) && (RXbuf[1] == 0xE0) && (RXbuf[2] == 0xE1));
#ifdef DEBUG
  Serial.print("FeederClass::receiveACK() : b_rc = ");
  Serial.println(b_rc);
#endif

  return b_rc;
}

bool FeederClass::receiveMessage(uint8_t *dataBuf) {
  uint8_t RXbuf[4];
  uint8_t inChar;
  uint8_t msgLen = 0;
  uint8_t RXckSum = 0;
  bool gotError = false;

  while (!Serial1.available()) {
    if ((millis() - this->lastTimeCommandSent) > RESP_TIMEOUT) {
#ifdef DEBUG
      Serial.println(" ack timeout!");
#endif
      return false;
    }
  }

  // expecting ack message
  Serial1.readBytes(RXbuf, 3);

#ifdef DEBUG
  Serial.print("FeederClass::receiveMessage() : recv : ");
  Serial.print(RXbuf[0], HEX);
  Serial.print(' ');
  Serial.print(RXbuf[1], HEX);
  Serial.print(' ');
  Serial.println(RXbuf[2], HEX);
#endif

  gotError = (RXbuf[0] != 1) || (RXbuf[1] != 0xE0) || (RXbuf[2] != 0xE1);

  // followed by response message
  while (!Serial1.available()) {
    if ((millis() - this->lastTimeCommandSent) > RESP_TIMEOUT) {
#ifdef DEBUG
      Serial.println(" message timeout!");
#endif
      return false;
    }
  }

  // get message length
  inChar = (uint8_t)Serial1.read();
  msgLen = inChar + 1;
#ifdef DEBUG
  Serial.print("recv msgLen = ");
  Serial.println(msgLen);
#endif
  if ((msgLen > 1) && (msgLen < 64)) {  // valid message is 1 to 64 bytes, otherwise ignore it
    dataBuf[0] = inChar;                // store length
    Serial1.readBytes(&dataBuf[1], msgLen);

#ifdef DEBUG
    Serial.print("recv(HEX) : ");
    for (uint8_t i = 0; i <= msgLen; i++) {
      Serial.print(dataBuf[i], HEX);
      Serial.print(" ");
    }
    Serial.println("");
#endif

    // verify checksum
    RXckSum = 0;
    for (uint8_t i = 0; i < msgLen; i++) {
      RXckSum += dataBuf[i];
    }

    if (RXckSum != dataBuf[msgLen]) {  // verify checksum
#ifdef DEBUG
      Serial.print(dataBuf[dataBuf[0] + 1], HEX);
      Serial.print(" != ");
      Serial.print(RXckSum, HEX);
      Serial.println(" Checksum failed!");
#endif
      gotError = true;
    }
    return !gotError;
  } else {
#ifdef DEBUG
    Serial.println("err : msgLen must > 1 && < 64");
#endif
    return false;
  }
}

bool FeederClass::sendCommand(uint8_t command) {
  uint8_t cmdLen = 2;
  uint8_t buf[] = { cmdLen, command, this->lane, 0 };
  uint8_t i;
  uint8_t checksum = 0;

  while (Serial1.available()) {  // get rid of any leftover input data
    Serial1.read();
  }

  // calculate checksum
  for (i = 0; i < cmdLen + 1; i++) {
    checksum += buf[i];
  }
  buf[i] = checksum;

#ifdef DEBUG
  Serial.print("sending to port[");
  Serial.print(this->port);
  Serial.print("], m_u8_tx_pin[");
  Serial.print(m_u8_tx_pin);
  Serial.print("] =>(HEX) ");
  for (i = 0; i < cmdLen + 2; i++) {
    Serial.print(buf[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
  Serial1.write(command);
#endif

#ifdef SIMULATE
  return true;
#endif
  for (i = 0; i < cmdLen + 2; i++) {
    FeederClass::write(buf[i]);
  }
  this->lastTimeCommandSent = millis();

  return FeederClass::receiveACK();
}

bool FeederClass::sendCommand(uint8_t command, uint8_t *dataBuf) {
  uint8_t cmdLen = 2;
  uint8_t buf[] = { cmdLen, command, this->lane, 0 };
  uint8_t i;
  uint8_t checksum = 0;

  while (Serial1.available()) {  // get rid of any leftover input data
    Serial1.read();
  }

  // calculate checksum
  for (i = 0; i < cmdLen + 1; i++) {
    checksum += buf[i];
  }
  buf[i] = checksum;

#ifdef DEBUG

  Serial.print("sending to port[");
  Serial.print(this->port);
  Serial.print("], m_u8_tx_pin[");
  Serial.print(m_u8_tx_pin);
  Serial.print("] =>(HEX) ");

  for (i = 0; i < cmdLen + 2; i++) {
    Serial.print(buf[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
  Serial1.write(command);
#endif

#ifdef SIMULATE
  return true;
#endif
  for (i = 0; i < cmdLen + 2; i++) {
    FeederClass::write(buf[i]);
  }
  this->lastTimeCommandSent = millis();

  return FeederClass::receiveMessage(dataBuf);
}

bool FeederClass::sendCommand(uint8_t command, uint8_t *dataBuf, uint8_t offset) {
  uint8_t cmdLen = 3;
  uint8_t buf[] = { cmdLen, command, this->lane, offset, 0 };
  uint8_t i;
  uint8_t checksum = 0;

  while (Serial1.available()) {  // get rid of any leftover input data
    Serial1.read();
  }

  // calculate checksum
  for (i = 0; i < cmdLen + 1; i++) {
    checksum += buf[i];
  }
  buf[i] = checksum;

#ifdef DEBUG
  Serial.print("sending to port[");
  Serial.print(this->port);
  Serial.print("], m_u8_tx_pin[");
  Serial.print(m_u8_tx_pin);
  Serial.print("] =>(HEX) ");
  for (i = 0; i < cmdLen + 2; i++) {
    Serial.print(buf[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
  Serial1.write(command);
#endif

#ifdef SIMULATE
  return true;
#endif
  for (i = 0; i < cmdLen + 2; i++) {
    FeederClass::write(buf[i]);
  }
  this->lastTimeCommandSent = millis();

  return FeederClass::receiveMessage(dataBuf);
}

bool FeederClass::sendCommand(uint8_t command, uint8_t dataLen, uint8_t *data) {
  uint8_t msgLen = dataLen + 2;
  uint8_t buf[msgLen + 2];
  uint8_t i;
  uint8_t checksum = 0;

  buf[0] = msgLen;
  buf[1] = command;
  buf[2] = this->lane;

  for (i = 0; i < dataLen; i++) {
    buf[i + 3] = data[i];
  }

  // calculate checksum
  for (i = 0; i < msgLen + 1; i++) {
    checksum += buf[i];
  }
  buf[i] = checksum;

#ifdef DEBUG
  // Serial.println(checksum, HEX);
  Serial.print("sending to port[");
  Serial.print(this->port);
  Serial.print("], m_u8_tx_pin[");
  Serial.print(m_u8_tx_pin);
  Serial.print("] =>(HEX) ");
  for (i = 0; i < msgLen + 2; i++) {
    Serial.print(buf[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
#endif

  while (Serial1.available()) {  // get rid of any leftover input data
    Serial1.read();
  }

#ifdef SIMULATE
  for (i = 0; i < 16; i++) {
    this->eeprom[i] = buf[i + 3];
  }
  return true;
#endif

  for (i = 0; i < msgLen + 2; i++) {
    FeederClass::write(buf[i]);
  }
  this->lastTimeCommandSent = millis();

  return FeederClass::receiveACK();
}

bool FeederClass::sendPrePick() {
  uint8_t dataBuf[6];

#ifdef DEBUG
  Serial.println("send Pre-Pick command");
#endif

  if (!FeederClass::sendCommand(CMD_PRE_PICK, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }
#ifdef DEBUG
  Serial.println("Received ACK, check status");
#endif

#ifdef SIMULATE
  this->feederStatus = STATUS_OK;
#else
  this->feederStatus = dataBuf[1];  // should verify that byte 2 matches the lane?
#ifdef DEBUG
  Serial.print("feederStatus(dataBuf[1]) = 0x");
  Serial.println(this->feederStatus, HEX);
  Serial.println(this->showStatus());
#endif
#endif
  return true;
}

bool FeederClass::sendAdvance(bool overrideError) {
  if (this->feederStatus == STATUS_INVALID) {  // need to read status from feeder if it is not up to date
    FeederClass::getFeederStatus();
  }

#ifdef DEBUG
  Serial.println("advance triggered");
  Serial.println(this->showStatus());
#endif

  //check whether feeder is OK before every advance command
  if (this->feederStatus != STATUS_OK) {
    //feeder is in error state, usually this would lead to exit advance with false and no advancing command sent

    if (!overrideError) {
//error, and error was not overridden -> return false, advance not successful
#ifdef DEBUG
      Serial.println("error, and error was not overridden -> return false, advance not successful");
#endif
      return false;
    } else {
#ifdef DEBUG
      Serial.println("overridden error temporarily");
#endif
    }
  }
#ifdef DEBUG
  Serial.println("send advance command");
#endif

#ifdef SIMULATE
  if (++this->eeprom[2] == 0) ++this->eeprom[3];
#endif

  if (!FeederClass::sendCommand(CMD_ADVANCE)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }
  return true;
}

bool FeederClass::setPitch(uint8_t pitch) {
  uint8_t dataBuf[22];

  for (uint8_t i = 0; i < 22; i++) {
    dataBuf[i] = 0;
  }

#ifdef DEBUG
  Serial.print("Set pitch to ");
  Serial.println(pitch);
#endif

  dataBuf[0] = pitch;
  dataBuf[1] = 0;

  if (!FeederClass::sendCommand(CMD_SET_PITCH, 1, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

  // update pitch field in eeprom
  //   Read current EEPROM data
#ifdef DEBUG
  Serial.println("1. send read EEPROM command");
#endif

  if (!FeederClass::sendCommand(CMD_EEPROM_READ, dataBuf, 0)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

#ifdef SIMULATE
  for (uint8_t i = 0; i < 16; i++) {
    dataBuf[i + 1] = this->eeprom[i];
  }
#endif

  for (uint8_t i = 1; i < 17; i++) {
    dataBuf[i] = dataBuf[i + 3];
  }

  dataBuf[0] = 0;
  dataBuf[5] = pitch;  // pitch byte

#ifdef DEBUG
  Serial.println("send write EEPROM command");
#endif

#ifdef DEBUG
  Serial.print("EEPROM: ");
  for (uint8_t i = 0; i < 17; i++) {
    Serial.print(dataBuf[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
#endif

  if (!FeederClass::sendCommand(CMD_EEPROM_WRITE, 17, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

#ifdef SIMULATE
  for (uint8_t i = 0; i < 16; i++) {
    this->eeprom[i] == dataBuf[i + 1];
  }
#endif

  return true;
}

bool FeederClass::feederIsOk() {
  if (this->feederStatus == STATUS_OK) {
    return true;
  } else {
    return false;
  }
}

bool FeederClass::getFeederStatus() {
  int i = 0;
  uint8_t dataBuf[6];
#ifdef DEBUG
  Serial.println("send status command(getFeederStatus)");
#endif

  for (i = 0; i < 6; i++) {
    dataBuf[i] = 0;
  }

  if (!FeederClass::sendCommand(CMD_STATUS, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

#ifdef DEBUG
  Serial.print("getFeederStatus(), ACK from feeder(HEX) : ");
  for (i = 0; i < 6; i++) {
    Serial.print(dataBuf[i], HEX);
    Serial.print(" ");
  }
  Serial.println("");


  Serial.print("feederStatus is dataBuf[1] = 0x");
  Serial.print(dataBuf[1], HEX);
  Serial.println(" ");
#endif

#ifdef SIMULATE
  this->feederStatus = STATUS_OK;
#else
  this->feederStatus = dataBuf[1];  // should verify that byte 2 matches the lane?
#endif
  return true;
}

bool FeederClass::readEEPROM(uint8_t *dataBuf) {
#ifdef DEBUG
  Serial.println("2. send read EEPROM command");
#endif

  if (!FeederClass::sendCommand(CMD_EEPROM_READ, dataBuf, 0)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

#ifdef SIMULATE
  for (uint8_t i = 0; i < 16; i++) {
    dataBuf[i] = this->eeprom[i];
  }
#else
  uint8_t len = dataBuf[0];
  #ifdef DEBUG
    Serial.print("len = ");
    Serial.println(len);
  #endif
  uint8_t i = 0;

  // len = 2时, 没进下面这个循环
  for (; i < len - 4; i++) {
    dataBuf[i] = dataBuf[i + 4];
  }
  dataBuf[i] = 0;
#endif

  return true;
}

bool FeederClass::readInfo(uint8_t *dataBuf) {
#ifdef DEBUG
  Serial.println("send read info command");
#endif

  if (!FeederClass::sendCommand(CMD_INFO, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

  return true;
}

bool FeederClass::clearFeedCount() {
  uint8_t dataBuf[22];

  // Read current EEPROM data
#ifdef DEBUG
  Serial.println("3. send read EEPROM command");
#endif

  if (!FeederClass::sendCommand(CMD_EEPROM_READ, dataBuf, 0)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

#ifdef SIMULATE
  for (uint8_t i = 0; i < 16; i++) {
    dataBuf[i + 1] = this->eeprom[i];
  }
#endif

  for (uint8_t i = 1; i < 17; i++) {
    dataBuf[i] = dataBuf[i + 3];
  }

  dataBuf[0] = 0;

  dataBuf[3] = 0;  // count low byte
  dataBuf[4] = 0;  // count mid byte
  dataBuf[6] = 0;  // count high byte
#ifdef DEBUG
  Serial.println("send write EEPROM command");
#endif

  if (!FeederClass::sendCommand(CMD_EEPROM_WRITE, 17, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

#ifdef SIMULATE
  for (uint8_t i = 0; i < 16; i++) {
    this->eeprom[i] == dataBuf[i + 1];
  }
#endif

  return true;
}

bool FeederClass::setID(int32_t feederID) {
  uint8_t dataBuf[22];

  for (uint8_t i = 0; i < 22; i++) {
    dataBuf[i] = 0;
  }

  if (this->lane == 1) {
    dataBuf[1] = feederID & 0xFF;         // low byte of ID
    dataBuf[2] = (feederID >> 8) & 0xFF;  // high byte of ID
    dataBuf[7] = 0x31;
    dataBuf[8] = 1;
  } else {
    dataBuf[8] = 0x3c;
  }

#ifdef DEBUG
  Serial.println("send write EEPROM command");
#endif

  if (!FeederClass::sendCommand(CMD_EEPROM_WRITE, 17, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  }

#ifdef SIMULATE
  for (uint8_t i = 0; i < 16; i++) {
    this->eeprom[i] == dataBuf[i + 1];
  }
#endif

  return true;
}


String FeederClass::reportStatus() {
  FeederClass::getFeederStatus();
  return FeederClass::showStatus();
}

String FeederClass::showStatus() {
  switch (this->feederStatus) {
    case STATUS_OK:
      return "getFeederStatus: feeder OK";
      break;
    case STATUS_INVALID:
      return "getFeederStatus: invalid, status not updated";
      break;
    case STATUS_NO_TAPE_TENSION:
      return "getFeederStatus: No tape tension.  Tape may be broken";
      break;
    case STATUS_NO_TAPE_TRIGGER:
      return "getFeederStatus: Tape take-up not triggered after multiple feeds";
      break;
    case STATUS_FEED_ERROR:
      return "getFeederStatus: Feed motor did not advance";
      break;
    default:
      char statusCode[34];
      sprintf(statusCode, "Unrecognized status code %02X", this->feederStatus);
      return statusCode;
  }
}

bool FeederClass::startSelfTest() {
  uint8_t dataBuf[2];

#ifdef DEBUG
  Serial.println("send self test command");
#endif

  dataBuf[0] = 5;
  dataBuf[1] = 0;

  if (!FeederClass::sendCommand(CMD_SELF_TEST, 1, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  } else {
    return true;
  }
}

bool FeederClass::stopSelfTest() {
  uint8_t dataBuf[2];

#ifdef DEBUG
  Serial.println("send stop self test command");
#endif

  dataBuf[0] = 7;
  dataBuf[1] = 0;

  if (!FeederClass::sendCommand(CMD_SELF_TEST, 1, dataBuf)) {
#ifdef DEBUG
    Serial.println("No ACK from feeder");
#endif
    return false;
  } else {
    return true;
  }
}

void FeederClass::setTX(uint8_t tx) {
  // First write, then set output. If we do this the other way around,
  // the pin would be output low for a short while before switching to
  // output high. Now, it is input with pullup for a short while, which
  // is fine. With inverse logic, either order is fine.
  digitalWrite(tx, this->inverse_logic ? LOW : HIGH);
  pinMode(tx, OUTPUT);
  this->_transmitBitMask = digitalPinToBitMask(tx);
  uint8_t port = digitalPinToPort(tx);
  this->_transmitPortRegister = portOutputRegister(port);
  this->m_u8_tx_pin = tx;
}

uint16_t FeederClass::subtract_cap(uint16_t num, uint16_t sub) {
  if (num > sub)
    return num - sub;
  else
    return 1;
}

/* static */
inline void FeederClass::tunedDelay(uint16_t delay) {
  _delay_loop_2(delay);
}

void FeederClass::begin(long speed) {
  this->_tx_delay = 0;

  // Precalculate the various delays, in number of 4-cycle delays
  uint16_t bit_delay = (F_CPU / speed) / 4;

  // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit,
  // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits,
  // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit
  // These are all close enough to just use 15 cycles, since the inter-bit
  // timings are the most critical (deviations stack 8 times)
  this->_tx_delay = subtract_cap(bit_delay, 15 / 4);
}


size_t FeederClass::write(uint8_t b) {
  // By declaring these as local variables, the compiler will put them
  // in registers _before_ disabling interrupts and entering the
  // critical timing sections below, which makes it a lot easier to
  // verify the cycle timings
  volatile uint8_t *reg = this->_transmitPortRegister;
  uint8_t reg_mask = this->_transmitBitMask;
  uint8_t inv_mask = ~this->_transmitBitMask;
  uint8_t oldSREG = SREG;
  bool inv = this->inverse_logic;
  uint16_t delay = this->_tx_delay;

  if (inv)
    b = ~b;

  cli();  // turn off interrupts for a clean txmit

  // Write the start bit
  if (inv)
    *reg |= reg_mask;
  else
    *reg &= inv_mask;

  tunedDelay(delay);

  // Write each of the 8 bits
  for (uint8_t i = 8; i > 0; --i) {
    if (b & 1)           // choose bit
      *reg |= reg_mask;  // send 1
    else
      *reg &= inv_mask;  // send 0

    tunedDelay(delay);
    b >>= 1;
  }

  // restore pin to natural state
  if (inv)
    *reg &= inv_mask;
  else
    *reg |= reg_mask;

  SREG = oldSREG;  // turn interrupts back on
  tunedDelay(delay);

  return 1;
}

gcode.ino

#include "config.h"

String inputBuffer[] = { "", "", "", "" };  // Buffer for incoming G-Code lines
int bufPtr = 0;

/**
* Look for character /code/ in the inputBuffer and read the float that immediately follows it.
* @return the value found.  If nothing is found, /defaultVal/ is returned.
* @input code the character to look for.
* @input defaultVal the return value if /code/ is not found.
**/
float parseParameter(String inBuf, char code, float defaultVal) {
  int codePosition = inBuf.indexOf(code);
  if (codePosition != -1) {
    //code found in buffer

    //find end of number (separated by " " (space))
    int delimiterPosition = inBuf.indexOf(" ", codePosition + 1);

    float parsedNumber = inBuf.substring(codePosition + 1, delimiterPosition).toFloat();

    return parsedNumber;
  } else {
    return defaultVal;
  }
}

void setupGCodeProc() {
  for (int i = 0; i < 4; i++) {
    inputBuffer[i].reserve(MAX_BUFFFER_MCODE_LINE);
  }
}

void sendAnswer(uint8_t error, String message) {
  switch (error) {
    case 0:
      {
        Serial.print("ok ");
        Serial.println(message);
        break;
      }

    case 1:
      {
        Serial.print("error ");
        Serial.println(message);
        break;
      }

    case 2:
      {
        Serial.println(message);
        Serial.println("ok ");
        break;
      }
  }
}

bool validFeederNo(int8_t signedFeederNo, uint8_t feederNoMandatory = 0) {
  bool b_rc = false;

  do {
    if (signedFeederNo == -1 && feederNoMandatory >= 1) {
      //no number given (-1) but it is mandatory.
      break;
    } else {
      //state now: number is given, check for valid range
      if (signedFeederNo < 0 || signedFeederNo > (NUMBER_OF_FEEDERS - 1)) {
        //error, number not in a valid range
        break;
      } else {
        //valid number
        b_rc = true;
        break;
      }
    }
  } while (0);

  if (!b_rc) {
#ifdef DEBUG
    Serial.print("signedFeederNo valid range = [0 ~ ");
    Serial.print((NUMBER_OF_FEEDERS - 1));
    Serial.println("]");
#endif
  }

  return b_rc;
}

bool validPitch(int8_t pitch, uint8_t pitchMandatory = 0) {
  if (pitch == -1 && pitchMandatory >= 1) {
    //no number given (-1) but it is mandatory.
    return false;
  } else {
    //state now: number is given, check for valid range
    if (pitch < 0 || pitch > 1) {
      //error, number not in a valid range
      return false;
    } else {
      //valid number
      return true;
    }
  }
}

bool validFeederID(int32_t signedFeederID, uint8_t feederIDMandatory = 0) {
  if (signedFeederID == -1 && feederIDMandatory >= 1) {
    //no number given (-1) but it is mandatory.
    return false;
  } else {
    //state now: number is given, check for valid range
    if (signedFeederID < 0 || signedFeederID > 65535) {
      //error, number not in a valid range
      return false;
    } else {
      //valid number
      return true;
    }
  }
}

/**
* Read the input buffer and find any recognized commands.  One G or M command per line.
*/
void processCommand(String cmdBuf) {

  //get the command, default -1 if no command found
  int cmd = parseParameter(cmdBuf, 'M', -1);

#ifdef DEBUG1
  Serial.print("command found: M");
  Serial.println(cmd);
#endif


  switch (cmd) {

    // M115
    case MCODE_DRIVER_INFO:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_DRIVER_INFO");
#endif
        sendAnswer(2, "FIRMWARE_NAME: " PRJ_NAME ", FIRMWARE_VERSION: " PRJ_VER);
        break;
      }

      /*
		FEEDER-CODES
		*/

    case MCODE_PRE_PICK:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_PRE_PICK");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);

#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of a mandatory FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        //send pre pick command
        if (!feeders[signedFeederNo].sendPrePick()) {
          sendAnswer(1, "No acknowledge from feeder");
        } else {
          sendAnswer(0, "Shutter opened");
        }

        break;
      }

    case MCODE_ADVANCE:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_ADVANCE");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        int8_t overrideErrorRaw = (int)parseParameter(cmdBuf, 'X', -1);
#ifdef DEBUG
        Serial.print("overrideErrorRaw = ");
        Serial.println(overrideErrorRaw);
#endif
        bool overrideError = false;
        if (overrideErrorRaw >= 1) {
          overrideError = true;
#ifdef DEBUG
          Serial.println("Argument X1 found, error will be ignored");
#endif
        }

        //check for presence of a mandatory FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        //send feed command
        if (!feeders[signedFeederNo].sendAdvance(overrideError)) {
          sendAnswer(1, "Unable to advance tape");
        } else {
          sendAnswer(0, "Tape advanced");
        }

        break;
      }

    case MCODE_FEEDER_STATUS:
      {
        

#ifdef DEBUG
        Serial.println("cmdproc : MCODE_FEEDER_STATUS");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
        
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        sendAnswer(0, feeders[signedFeederNo].reportStatus());

        break;
      }

    case MCODE_GET_FEED_COUNT:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_FEED_COUNT");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }

        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
#ifdef DEBUG
          Serial.print("inBuf(HEX) = [");
          for (int i = 0; i < 24; i++) {
            Serial.print(inBuf[i], HEX);
            Serial.print(" ");
          }
          Serial.println("]");
#endif

          uint32_t counth = inBuf[5];
          counth <<= 16;
          uint32_t countm = inBuf[3];
          countm <<= 8;
          uint32_t count = counth + countm + inBuf[2];
          char countStr[22];
          sprintf(countStr, "Feed count: %lu", count);
          sendAnswer(0, countStr);
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_CLEAR_FEED_COUNT:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_CLEAR_FEED_COUNT");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        if (!feeders[signedFeederNo].clearFeedCount()) {
          sendAnswer(1, "Unable to clear feed count");
        } else {
          sendAnswer(0, "Feed count cleared");
        }

        break;
      }

    case MCODE_GET_ERR42_COUNT:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_ERR42_COUNT");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }
        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          uint16_t count = inBuf[12] & 0x0f;
          count <<= 8;
          count += inBuf[8];
          char countStr[22];
          sprintf(countStr, "Error 42 count: %u", count);
          sendAnswer(0, countStr);
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_GET_ERR43_COUNT:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_ERR43_COUNT");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }
        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          uint16_t count = inBuf[12] & 0xf0;
          count <<= 4;
          count += inBuf[9];
          char countStr[22];
          sprintf(countStr, "Error 43 count: %u", count);
          sendAnswer(0, countStr);
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_GET_ERR44_COUNT:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_ERR44_COUNT");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }
        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          uint16_t count = inBuf[13] & 0x0f;
          count <<= 8;
          count += inBuf[10];
          char countStr[22];
          sprintf(countStr, "Error 44 count: %u", count);
          sendAnswer(0, countStr);
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_GET_RESET_COUNT:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_RESET_COUNT");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        if (signedFeederNo % 2 != 0) {  // Reset count is only in the lane 1 field, so need to adjust if it is lane 2
          signedFeederNo--;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }
        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          uint16_t count = inBuf[13] & 0xf0;
          count <<= 4;
          count += inBuf[11];
          char countStr[22];
          sprintf(countStr, "Reset count: %u", count);
          sendAnswer(0, countStr);
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_READ_EEPROM:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_READ_EEPROM");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }

        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          char hex[50];
          sprintf(hex, "%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X ", inBuf[0], inBuf[1], inBuf[2], inBuf[3], inBuf[4], inBuf[5], inBuf[6], inBuf[7], inBuf[8], inBuf[9], inBuf[10], inBuf[11], inBuf[12], inBuf[13], inBuf[14], inBuf[15]);
          sendAnswer(0, hex);
        } else {
          sendAnswer(1, " no response from feeder");
        }
        break;
      }

    case MCODE_GET_FEEDER_ID:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_FEEDER_ID");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }
        bool left = true;
        if (signedFeederNo % 2 != 0) {  // ID is only in the lane 1 field, so need to adjust if it is lane 2
          left = false;
          signedFeederNo--;
        }

        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          uint16_t id = (inBuf[1] << 8) + inBuf[0];
          char idStr[22];
          sprintf(idStr, "ID: %u%s", id, (left ? "L" : "R"));
          sendAnswer(0, idStr);
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_SET_FEEDER_ID:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_SET_FEEDER_ID");
#endif

        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif
        int32_t newFeederID = (int)parseParameter(cmdBuf, 'X', -1);

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        //must be run from lane 1 of feeder, so adjust.
        if ((signedFeederNo % 2) != 0) {
          --signedFeederNo;
        }

        //check for presence of FeederID
        if (!validFeederID(newFeederID, 1)) {
          sendAnswer(1, "feederID missing or invalid");
          break;
        }


        if (feeders[signedFeederNo].setID(newFeederID)) {
          if (feeders[signedFeederNo + 1].setID(0)) {  // need to clear 2nd lane also
            sendAnswer(0, "ID set");
          } else {
            sendAnswer(1, " no response from feeder");
          }
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_GET_PITCH:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_PITCH");
#endif

        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }
        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          char pitch[6];
          sprintf(pitch, "%d MM", (inBuf[4] == 0 ? 2 : 4));
          sendAnswer(0, pitch);
        } else {
          sendAnswer(1, " no response from feeder");
        }
        break;
      }

    case MCODE_TOGGLE_PITCH:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_TOGGLE_PITCH");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif
        int8_t pitch;

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[24];
        for (int i = 0; i < 24; i++) {
          inBuf[i] = 0;
        }
        if (feeders[signedFeederNo].readEEPROM(inBuf)) {
          if (inBuf[4] == 0) {
            pitch = 1;
          } else {
            pitch = 0;
          }
          //send set pitch command
          if (!feeders[signedFeederNo].setPitch(pitch)) {
            sendAnswer(1, "Unable to set pitch");
          } else {
            sendAnswer(0, "Pitch set");
          }

        } else {
          sendAnswer(1, " no response from feeder");
        }
        break;
      }

    case MCODE_GET_FIRMWARE_INFO:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_GET_FIRMWARE_INFO");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        uint8_t inBuf[64];
        for (int i = 0; i < 64; i++) {
          inBuf[i] = 0;
        }
        if (feeders[signedFeederNo].readInfo(inBuf)) {
          char version[25];
          sprintf(version, "Firmware version %d.%d", inBuf[2], inBuf[3]);
          sendAnswer(0, version);
        } else {
          sendAnswer(1, " no response from feeder");
        }

        break;
      }

    case MCODE_START_SELF_TEST:
      {
        bool b_rc = false;
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_START_SELF_TEST");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of a mandatory FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        //send self test command
        b_rc = feeders[signedFeederNo].startSelfTest();
#ifdef DEBUG
        Serial.print("startSelfTest() return b_rc = ");
        Serial.println(b_rc);
#endif
        if (!b_rc) {
          sendAnswer(1, "err: No acknowledge from feeder");
        } else {
          sendAnswer(0, "ok: Self test started");
        }

        break;
      }

    case MCODE_STOP_SELF_TEST:
      {
#ifdef DEBUG
        Serial.println("cmdproc : MCODE_STOP_SELF_TEST");
#endif
        int8_t signedFeederNo = (int)parseParameter(cmdBuf, 'N', -1);
#ifdef DEBUG
        Serial.print("signedFeederNo = ");
        Serial.println(signedFeederNo);
#endif

        //check for presence of a mandatory FeederNo
        if (!validFeederNo(signedFeederNo, 1)) {
          sendAnswer(1, "feederNo missing or invalid");
          break;
        }

        //send self test command
        if (!feeders[signedFeederNo].stopSelfTest()) {
          sendAnswer(1, "No acknowledge from feeder");
        } else {
          sendAnswer(0, "Self test stopped");
        }

        break;
      }

    case MCODE_CMD_HELP:
      {
        Serial.println("command list below:");
        Serial.println("M115 MCODE_DRIVER_INFO ");
        Serial.println("M600 MCODE_PRE_PICK ");
        Serial.println("M601 MCODE_ADVANCE e.g. M601N0X1 is ok, M601N0X0 is err");
        Serial.println("M602 MCODE_FEEDER_STATUS ");
        Serial.println("M603 MCODE_GET_FEED_COUNT ");
        Serial.println("M623 MCODE_CLEAR_FEED_COUNT ");
        Serial.println("M604 MCODE_GET_ERR42_COUNT ");
        Serial.println("M605 MCODE_GET_ERR43_COUNT ");
        Serial.println("M606 MCODE_GET_ERR44_COUNT ");
        Serial.println("M607 MCODE_GET_RESET_COUNT ");
        Serial.println("M608 MCODE_GET_PITCH ");
        Serial.println("M628 MCODE_TOGGLE_PITCH ");
        Serial.println("M610 MCODE_GET_FEEDER_ID ");
        Serial.println("M640 MCODE_SET_FEEDER_ID ");
        Serial.println("M630 MCODE_READ_EEPROM ");
        Serial.println("M615 MCODE_GET_FIRMWARE_INFO ");
        Serial.println("M650 MCODE_START_SELF_TEST ");
        Serial.println("M651 MCODE_STOP_SELF_TEST ");
        Serial.println("M999 MCODE_CMD_HELP ");
      }
      break;


    default:
      sendAnswer(0, "unknown or empty command ignored");

      break;
  }
}

void listenToSerialStream() {

  while (Serial.available()) {

    // get the received byte, convert to char for adding to buffer
    char receivedChar = (char)Serial.read();

    // print back for debugging
    //#ifdef DEBUG
    Serial.print(receivedChar);
    //#endif

    // add to buffer
    inputBuffer[bufPtr] += receivedChar;

    // if the received character is a newline, processCommand
    if (receivedChar == '\n') {
#ifdef DEBUG
      Serial.print("buffer ");
      Serial.println(bufPtr);
#endif
      int curBuf = bufPtr;
      // switch to next buffer while processing this one
      if (++bufPtr > 3)
        bufPtr = 0;

      //remove comments
      inputBuffer[curBuf].remove(inputBuffer[curBuf].indexOf(";"));
      inputBuffer[curBuf].trim();


      processCommand(inputBuffer[curBuf]);

      //clear buffer
      inputBuffer[curBuf] = "";
    }
  }
}

END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/993545.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯官网练习题(颠倒的价牌)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小李的店里专卖其它店中下架的样品电视机&#xff0c;可称为&#xff1a;样品电视专卖店。 其标价都是 4 位数字&#xff08;即千元不等&#xff09;。 小李为了标…

BWMT的思考

从bw4开始&#xff0c;sap把建模的功能从系统的rsa1移除&#xff0c;改成BWMT的客户端。以前对java开发的eclipse不是很喜欢&#xff0c;总有点排斥。今天突然好像明白sap为啥要这样做&#xff1f; 1 最重要的是减少数据库的数据量和系统的负荷。把这种开发工作的程序和功能放在…

基于微信小程序美食菜品预订点餐预约系统uniapp+vue

点餐预约系统主要是为了提高用户的工作效率和更方便快捷的满足用户&#xff0c;更好存储所有数据信息及快速方便的检索功能&#xff0c;对点餐预约系统的各个模块是通过许多今天的发达点餐预约系统做出合理的分析来确定考虑用户的可操作性&#xff0c;遵循开发的系统优化的原则…

Games101作业5解读

文章目录 整体思路阅读Render 整体思路阅读 Scene scene(1280, 960);auto sph1 std::make_unique<Sphere>(Vector3f(-1, 0, -12), 2);sph1->materialType DIFFUSE_AND_GLOSSY;sph1->diffuseColor Vector3f(0.6, 0.7, 0.8);auto sph2 std::make_unique<Spher…

530. 二叉搜索树的最小绝对差

给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#xff1a;1示例 2&#xff1a; 输入&#xff1a;root [1,…

Windows创建sqlite3的lib库

1、下载相关文件 SQLite Download Page这是sqlite的官网下载地址&#xff0c;需要下载三个文件&#xff1a; 下载后解压得到代码文件&#xff1a; dll选择一个就行&#xff0c;x86或者x64根据自己的需要下载。 dll得到动态库文件&#xff1a; tools得到exe文件&#xff1a; 2…

LabVIEW开发人体动态电位器设计及应用

LabVIEW开发人体动态电位器设计及应用 随着集成电路产业的快速发展&#xff0c;电子设备的集成密度显著提高。电子设备的防静电能力变差&#xff0c;电子设备对静电放电的易感性也更严重。人类是各种科研活动的主要焦点&#xff0c;也是静电防护中的主要危险源之一。如果一个人…

【C++进阶】:AVL树(平衡因子)

AVL树 一.概念二.插入1.搜索二叉树2.平衡因子 三.旋转1.更新平衡因子2.旋转1.左单旋2.右单旋3.先右旋再左旋4.先左旋再右旋 四.完整代码 一.概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元…

CMS指纹识别

一.什么是指纹识别 常见cms系统 通过关键特征&#xff0c;识别出目标的CMS系统&#xff0c;服务器&#xff0c;开发语言&#xff0c;操作系统&#xff0c;CDN&#xff0c;WAF的类别版本等等 1.识别对象 1.CMS信息&#xff1a;比如Discuz,织梦&#xff0c;帝国CMS&#xff0…

MongoDB ubuntu 上安装 MongoDB7.0 附带配置文件说明

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis &#xff0c;Oracle ,Oceanbase 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加微信号 liuaustin3 &#xff08;…

TDesign的input标签

目录 一、 新建页面01-todolist 二、 t-input标签、t-button标签 2.1 t-input标签 2.1.1 01-todolist.wxml页面 2.2 01-todolist.json页面 2.3 01-todolist.js页面 2.4 01-todolist.wxss页面 2.2 t-button标签 示例1&#xff1a; 示例2 &#xff1a; 一、 新建页面0…

【LeetCode题目详解】第九章 动态规划part08 139.单词拆分 (day46补)

本文章代码以c为例&#xff01; 一、力扣第139题&#xff1a;单词拆分 题目&#xff1a; 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典…

linux系统中rootfs根文件系统制作及挂载基本操作

​今天给大家介绍一下rootfs根文件系统制作和挂载方式&#xff0c;希望这篇文章对大家有所帮助。 本章主要是对rootfs根文件系统制作和挂载方式进行详细讲解。 Linux“三巨头”已经完成了2个了&#xff0c;就剩最后一个 rootfs(根文件系统)了&#xff0c;本章我们就来学习一下…

日常开发小汇总(4)空对象创造

创建空对象的目的其实是想要一个干净的对象&#xff0c;最直接的方式是将对象的隐式原型干掉&#xff0c;如 下图就会得到一个干净的对象&#xff0c;但是这种方式不推荐&#xff0c;不要直接操作__proto__ 方式一 Object.create(null) 方法二 Object.setPrototypeOf(对象&a…

SpringMVC文件上传、文件下载多文件上传及jrebel的使用与配置

一.文件上传 1.导入依赖 <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.3</version> </dependency> 2.配置文件上传解析器 在spring-mvc.xml文件中添加文件…

掌握API数据检索:过滤和排序的综合指南

API可以返回大量的数据&#xff0c;这使得开发人员很难只检索他们需要的信息。这就是API的过滤和排序功能的用武之地。 过滤和排序是API设计的两个基本功能&#xff0c;它们允许开发人员有效地从API检索特定的数据。过滤使开发人员能够通过指定返回的数据必须满足的标准来缩小A…

【用unity实现100个游戏之11】复刻经典消消乐游戏

文章目录 前言开始项目开始一、方块网格生成二、方块交换三、添加交换的动画效果四、水平消除检测五、垂直消除检测六、完善删除功能七、效果优化&#xff08;移动方块后再进行消除检测&#xff09;八、方块下落十、方块填充十一、后续 源码参考完结 前言 欢迎来到经典消消乐游…

第13节-PhotoShop基础课程-裁剪工具

文章目录 前言1.裁剪工具1.基本操作 Alt Shift2.拉直3.内容识别 自动填充 2.透视裁剪工具3.切片工具-长图分成多个4.切片选择工具5. 存储为一张一张 前言 1.裁剪工具 1.基本操作 Alt Shift 2.拉直 可以矫正图片 3.内容识别 自动填充 2.透视裁剪工具 可以拉正图片 3.切片工具-…

SpringAOP的实现机制(底层原理)、应用场景等详解

SpringAOP的实现机制&#xff08;底层原理&#xff09;应用场景等详解 ​ Spring框架是Java开发中最流行的应用程序框架之一。它提供了广泛的功能&#xff0c;其中之一就是面向切面编程&#xff08;AOP&#xff09;。Spring AOP允许我们将关注点&#xff08;例如日志记录、事务…

微信小程序源码

1&#xff1a;仿豆瓣电影微信小程序 https://github.com/zce/weapp-demo 2&#xff1a;微信小程序移动端商城 https://github.com/liuxuanqiang/wechat-weapp-mall 3&#xff1a;Gank微信小程序 https://github.com/lypeer/wechat-weapp-gank 4&#xff1a;微信小程序高仿QQ…