文章目录
- 二手西门子电动飞达的初步测试
- 概述
- 飞达正常的判断标准
- 先挑出一个手工控制好使的二手飞达用于测试.
- 推料的手工检测
- 扒皮的手工检测
- 飞达测试的接线
- 通讯的测试
- 用串口助手测试通讯
- 先看看是否发送给飞达的管脚是自己接的那个
- 查看所有可以用到的上位机通讯命令
- 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] = "";
}
}
}