// 30/6/18 Tachometer code modified 4 output to a serial LCD with rpm and bar graph
/*
Author: Jeffrey Nelson <nelsonjm@macpod.net>
Description: Very quickly written code to read tachometer port data of the SX2 mini mill and likely the SC2 mini lathe.
for more information check http://macpod.net
This code was written on an 5v 16Mhz Arduino. I'm planning on writing
Tachometer Port Interface:
2 GND
2 5V+
1 LCDCS - Frame indicator, pulled low during the transmission of a frame.
1 LCDCL - Clock line, pulled low when data should be read (we read on the falling edges)
1 LCDDI - Data line.
Data information:
Reports if the spindel is stopped or running
Reports speed of the spindel in 10rpm increments
Data format:
Every .75 seconds a packet is sent over the port.
Each packet consists of:
(ONLY If the mill uses the new mill protocol): a 36 bit header
Following this potential header are 4 frames. Each Frame consists of 17 bytes.
The first 8 bits represent an address, and the other bits represent data.
Frame 0: Represents 7-segment data used for 1000's place of rpm readout.
Address: 0xA0
Data: First bit is always 0
Next 7 bits indicate which of the 7-segments to light up.
Last bit is always 0
Frame 1: Represents 7-segment data used for 100's place of rpm readout.
Address: 0xA1
Data: First bit is always 0
Next 7 bits indicate which of the 7-segments to light up.
Last bit is always 0
Frame 2: Represents 7-segment data used for 10's place of rpm readout.
Address: 0xA2
Data: First bit is always 0
Next 7 bits indicate which of the 7-segments to light up.
Last bit is 1 if the spindel is not rotating, 0 otherwise.
Frame 3: Represents 7-segment data used for 1's place of rpm readout. This isn't used.
Address: 0xA3
Data: This is always 0x20
7-segment display layout:
d
c g
f
b e
a
abcdefg
00 = 1111101
01 = 0000101
02 = 1101011
03 = 1001111
04 = 0010111
05 = 1011110
06 = 1111110
07 = 0001101
08 = 1111111
09 = 1011111
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#define LCDCS 2
#define LCDCL 3
#define LCDCL_INTERRUPT 1
#define LCDDI 4
#define PACKET_BITS 68
// For newer mills there is a 36 bit header.
#define PACKET_BITS_HEADER 36
// Uncomment for newer mill protocol
#define PACKET_BITS_COUNT PACKET_BITS+PACKET_BITS_HEADER
// Uncomment for old mill protocol
//#define PACKET_BITS_COUNT PACKET_BITS
#define MAXCOUNT 503500
volatile uint8_t packet_bits[PACKET_BITS_COUNT];
volatile uint8_t packet_bits_pos;
void setup() {
Serial.begin(9600);
pinMode(LCDCS, INPUT);
pinMode(LCDCL, INPUT);
pinMode(LCDDI, INPUT);
// Serial.write(0xFE); Serial.write(0x0C); // display on and hide cursor
// Serial.write(0xFE); Serial.write(0x01); // clear screen
// Serial.println(" Lathe RPM Boo is stupid ");
// Serial.write(0x7C); Serial.write(0x0A); // set 0a set splash 09 toggleon/off
// Serial.write(0x7C); Serial.write(0x9D); // backlight full
// Serial.write(0x7C); Serial.write(0x04); // 16
// Serial.write(0x7C); Serial.write(0x06); // 2
writecharLCD(); // at bot sets up custom chars
EIMSK |= (1<<INT1); //Enable INT1
//Trigger on falling edge of INT1//
EICRA |= (1<<ISC11);
EICRA &= ~(1<<ISC10);
TIMSK0 &= ~(1<<TOIE0); // Disable timer0
}
void loop() {
int i;
int rpm;
unsigned long count;
packet_bits_pos = 0;
while (digitalRead(LCDCS) == LOW); // Wait to end of packet if we are in one.
block_delay(227272); // ~200ms delay
// Now we are ready to read a packet..
EIMSK |= (1 << INT1);
for (count = 0; packet_bits_pos < PACKET_BITS_COUNT && count < MAXCOUNT; count++) { // Loop until all bits are read or we timeout ~600ms
asm("nop");
}
EIMSK &= ~(1 << INT1);
if (packet_bits_pos == PACKET_BITS_COUNT && count < MAXCOUNT) {
/*
// Debug stuff...
print_bits(0, 8); // Register 0
Serial.print(": ");
print_bits(8, 9);
Serial.print("\t");
print_bits(17, 8); // Register 1
Serial.print(": ");
print_bits(25, 9);
Serial.print("\t");
print_bits(34, 8); // Register 2
Serial.print(": ");
print_bits(42, 9);
Serial.print("\t");
print_bits(51, 8); // Register 3
Serial.print(": ");
print_bits(59, 9);
Serial.print("\n");
*/
rpm = get_rpm();
if (rpm == -1) {
Serial.println("");
Serial.write(0xFE); Serial.write(0x01); // clear display & setCursor 0,0
Serial.print("Data just Silly");
}
else if (rpm == 0) {
Serial.println("");
Serial.write(0xFE); Serial.write(0x01); // clear display & setCursor 0,0
// Serial.print("Stopped");
Serial.write(6);
Serial.print(" Awaiting your Pleasure ");
Serial.write(7);
}
else {
Serial.println("");
Serial.write(0xFE); Serial.write(0x01); // clear display & setCursor 0,0
if ( rpm < 10 ) Serial.print(" ");
if ( rpm < 100 ) Serial.print(" ");
if ( rpm < 1000 ) Serial.print(" ");
Serial.print(rpm, DEC);
Serial.print(" rpm");
LCDbar(rpm/25); // prercent of max
}
}
else {
// Serial.print(".");
// Serial.write(0xFE); Serial.write(0x01); // clear display & setCursor 0,0
// Serial.print("Sorry no data.");
}
for (packet_bits_pos = 0; packet_bits_pos < PACKET_BITS_COUNT; packet_bits_pos++) {
packet_bits[packet_bits_pos] = 0;
}
}
//----------------------------------------------------------------------------------------------------
// Assumes 68 bits were received properly. Returns the spindle speed or -1 if the values are absurd.
int get_rpm()
{
int temp, ret = 0;
if (build_address(0) != 0xA0) {
return -1;
}
temp = get_digit_from_data(build_data(8));
if (temp == -1) {
return -1;
}
ret += temp*1000;
if (build_address(17) != 0xA1) {
return -1;
}
temp = get_digit_from_data(build_data(25));
if (temp == -1) {
return -1;
}
ret += temp*100;
if (build_address(34) != 0xA2) {
return -1;
}
temp = get_digit_from_data(build_data(42));
if (temp == -1) {
return -1;
}
ret += temp*10;
if (build_address(51) != 0xA3) {
return -1;
}
temp = build_data(59);
if (temp != 0x20) {
return -1;
}
return ret;
}
// An address is 8 bits long
uint8_t build_address(uint8_t start_address)
{
uint8_t ret = 0x1;
uint8_t i;
if (PACKET_BITS_COUNT != PACKET_BITS) {
// Compensate for header
start_address += PACKET_BITS_HEADER;
}
for (i = start_address; i < start_address + 8; i++) {
ret = (ret << 1) ^ ((packet_bits[i] & B00010000) ? 1 : 0);
}
return ret;
}
// Data is 9 bits long
uint16_t build_data(uint8_t start_address)
{
uint16_t ret = 0;
uint8_t i;
if (PACKET_BITS_COUNT != PACKET_BITS) {
// Compensate for header
start_address += PACKET_BITS_HEADER;
}
for (i = start_address; i < start_address + 9; i++) {
ret = (ret << 1) ^ ((packet_bits[i] & B00010000) ? 1 : 0);
}
return ret;
}
int get_digit_from_data(uint16_t data)
{
uint16_t segments = (data & 0xFE) >> 1;
int ret = 0;
switch(segments) {
case 0x7D:
ret = 0;
break;
case 0x05:
ret = 1;
break;
case 0x6B:
ret = 2;
break;
case 0x4F:
ret = 3;
break;
case 0x17:
ret = 4;
break;
case 0x5E:
ret = 5;
break;
case 0x7E:
ret = 6;
break;
case 0x0D:
ret = 7;
break;
case 0x7F:
ret = 8;
break;
case 0x5F:
ret = 9;
break;
default:
ret = -1;
break;
}
return ret;
}
// Returns 1 if stopped, 0 otherwise.
uint8_t spindle_stopped(uint16_t data)
{
return data & 0x1;
}
//-----------------------------------------------------------------------------------------------------
void print_bits(int start, int len) {
if (PACKET_BITS_COUNT != PACKET_BITS) {
// Compensate for header
start += PACKET_BITS_HEADER;
}
for (int i = start; i < start+len; i++) {
if (packet_bits[i] & B00010000) {
Serial.print('1');
}
else {
Serial.print('0');
}
}
}
// 100000 ~= 88ms
void block_delay(unsigned long units)
{
unsigned long i;
for (i = 0; i < units; i++) {
asm("nop"); // Stop optimizations
}
}
SIGNAL(INT1_vect)
{
packet_bits[packet_bits_pos] = PIND;
packet_bits_pos++;
}
void writecharLCD() {
uint8_t charArray[][8] = { //Self defined bar characters
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F, // bottom line
0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x1F, // 1
0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x1F, // 11
0x00,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1F, // 111
0x00,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1F, // 1111
0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F, // 11111
0x00,0x0A,0x00,0x11,0x1E,0x00,0x00,0x00, // happy
0x00,0x0A,0x15,0x11,0x1A,0x04,0x00,0x00 // heart
};
for (int j = 0; j < 8; j++) {
Serial.write(254);
Serial.write(64+j*8);
for (int i = 0; i < 8; i++) {
Serial.write(charArray[j][i]);
} //end inner char for
}// end outter for
delay(100);
}
void LCDbar(int percent) {
Serial.write(0xFE);
Serial.write(192); // line 1 (not 0)
if (percent > 100) {
percent = 100;
}
int dots = 80*percent/100;
int chars = dots/5;
if (chars > 0) {
for (int i = 1; i <= chars; i++) {
Serial.write(5);
}
}
Serial.write(dots%5);
while (chars < 15) { // clear to end
Serial.write(0);
chars ++;
}
}
void lcdPos(int x, int y){ //takes x (0-15) and y (0-1) position arguments
if (y==0) y=128;
if (y==1) y=192;
Serial.write(0xFE);
Serial.write(x+y);
}