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

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

概述

现在手头的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 命令取回的飞达计数不对, 应该是协议不对. 不过这不重要, 这个命令不在正式场景下应用.
也有可能是, 必须在飞达状态OK时, 才可以去取飞达计数.否则飞达计数回包中是其他含义.
在这里插入图片描述
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] = "";
    }
  }
}

飞达上物料编带正常装入后的通讯测试

飞达上物料编带已经安装好了(openpnp - 二手西门子电动飞达 - 物料编带安装的正确姿势), 再测试一下通讯, 看看和空飞达有何不同

此时的固件版本中, 已经去掉了DEBUG宏.
启动后, DTR生效, 开机信息如下:

my_SchultzController v2.0.1.2_build_2023_0909_1333 starting...
my_SchultzController v2.0.1.2_build_2023_0909_1333 ready.

查看可用命令列表

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

查看固件版本

M115
FIRMWARE_NAME: my_SchultzController, FIRMWARE_VERSION: v2.0.1.2_build_2023_0909_1333
ok 

送料之前

M600 N0
ok Shutter opened
M600 N1
ok Shutter opened

先进模式

M601 N0X0
ok Tape advanced
M601 N1X0
ok Tape advanced

M601 N0X1
ok Tape advanced
M601 N1X1
ok Tape advanced
M601 N1
ok Tape advanced
M601 N0
ok Tape advanced

看起来先进送料模式不需要NX后的XX参数.
执行M601 NX后, 直接送料并拉皮.

取飞达状态

M602 N0
ok getFeederStatus: feeder OK
M602 N1
ok getFeederStatus: feeder OK

只要飞达还好的, 而且物料编带正常载入, 飞达状态就是OK.

取飞达总送料计数

M603 N0
ok Feed count: 206
M603 N1
ok Feed count: 307

清除飞达计数

M623 N0
ok Feed count cleared
M623 N1
ok Feed count cleared

// 再取一次飞达计数, 已经变为0了.
M603 N0
ok Feed count: 0
M603 N1
ok Feed count: 0

取0x42错误计数

M604 N0
ok Error 42 count: 13
M604 N1
ok Error 42 count: 5

取0x43错误计数

M605 N0
ok Error 43 count: 0
M605 N1
ok Error 43 count: 2

取0x44错误计数

M606 N0
ok Error 44 count: 0
M606 N1
ok Error 44 count: 0

取飞达复位计数

M607 N0
ok Reset count: 4
M607 N1
ok Reset count: 4

取物料步进值

M608 N0
ok 2 MM
M608 N1
ok 4 MM

切换物料步进值

都改成4mm

M628 N0
ok Pitch set
M628 N1
ok Pitch set
M628 N1
ok Pitch set
M608 N0
ok 4 MM
M608 N1
ok 4 MM

取飞达ID

M610 N0
ok ID: 1234L
M610 N1
ok ID: 1234R

设置飞达ID

M640 N0X1001
ok ID set
M640 N1X1002
ok ID set
M610 N0
ok ID: 1002L
M610 N1
ok ID: 1002R

看到, 飞达的ID对于子飞达来说, 都是一样的, 子飞达只是有左右之分.

读E2PROM

M630 N0
ok EA 03 00 00 00 00 31 01 00 00 00 00 00 00 00 00 
M630 N1
ok 00 00 00 00 00 00 00 3C 00 00 00 00 00 00 00 00 

取飞达固件信息

M615 N0
ok Firmware version 2.4
M615 N1
ok Firmware version 2.4

西门子飞达上主控芯片只有一个C51, 所以子飞达的固件版本都是一样的

自测试

M650 MCODE_START_SELF_TEST
M651 MCODE_STOP_SELF_TEST

现在飞达上已经正常载入了物料编带, 自测试不敢做了.
自测试命令可以用在单独检测飞达主要机械部分(推料齿轮, 前面的撕料挡板, 中部的卷带齿轮)是否正常, 可以对飞达好坏有个初步的判断(e.g. 买了二手飞达, 到货后可以快速判断一下飞达是否大概是好的).

补充 - 挂好料后, 一个命令就可以测试飞达是否能用

飞达上电, 飞达封皮拉紧按钮和进料按钮好使(不用太好使, 毕竟都是二手飞达, 只需要按几下按钮, 能动作一下就行), 因为飞达换料过程中要用到这2个按钮(e.g. 飞达使用过程中出现错误, 需要用这2个按钮动作来消除错误)

挂载好物料编带, 飞达的指示灯是灭的.

M601 N0X1
M601 N1X1

进料的同时, 封皮可以拉紧就正常.

二手飞达测试出的可用率

设备到手前, 同学一并帮我买了一堆飞达.
当时刚接触openpnp, 还没到接入二手西门子电动飞达这一步, 也只是上电, 按钮按下是否有反应. 其实看不出飞达是否可用.
现在已经有了可用的飞达控制板, 接了一路, 用通讯控制飞达好使.
将手头的飞达都逐个接到飞达控制板的这一路, 剪断2个小段(10个物料左右)物料编带, 将2个子飞达都正常上料. 使飞达错误指示灯是灭的(说明飞达状态正常, 没有错误发生).
然后用通讯命令逐个测试飞达的推料(M601 NXX1), 如果能连续执行推料, 就测试过了.

先测试8mm飞达, 好的飞达一共41把, 坏的飞达7把. 可用率 = 41/48 = 85%
其他边带宽度飞达(16mm, 24mm)还没测试, 等用到的时候再说.

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值