Skip to content

Instantly share code, notes, and snippets.

@Mictronics
Last active February 26, 2018 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Mictronics/0fd2f99e0f2ae3fe89f5c473044f9534 to your computer and use it in GitHub Desktop.
Save Mictronics/0fd2f99e0f2ae3fe89f5c473044f9534 to your computer and use it in GitHub Desktop.
Controls and reads actual measured distance from LCD bus of a Sndway SW-A40 laser distance meter.
/**
* Controls and reads actual measured distance from LCD bus of a Sndway SW-A40 laser distance meter.
*
* Serial control characters:
*
* ? Return identifier string.
* c Clears measurement results and turns off laser.
* Can be used as keep alive command to prevent auto power off after 150s.
* M Turns on device when off and triggers a measurement.
* S Start continuous measurement mode. Device must be turned on prior to this command.
* Command c or M will stop that mode.
* L Turns on laser but not triggering measurement.
* o Turns off device.
* u Cycles distance unit.
* r Cycle reference of measurement.
* R Get reference of measurement.
* f = front
* m = middle
* b = back (default)
*
* Make sure your Arduino Mini board is 3V compatible! By default it is not!
*
* Developed and tested on Arduino Mini 328P@16MHz.
* Mictronics 2018 @ www.mictronics.de
*/
#include <avr/wdt.h>
/**
* Pin connection to LCD SPI bus.
*/
#define CLKPIN 2 // LCD clock signal = IO2
#define DATAPIN 4 // LCD data signal = IO4
#define CSPIN 3 // LCD chip select signal = IO3
/**
* Pin connection for device control.
* Key signals are low active.
*/
#define UNITKEY 5 // Key changing units = IO5
#define MEASUREKEY 6 // Key switching device ON and taking measurement = IO6
#define CLEARKEY 7 // Key switching device OFF and clearing measurement = IO7
#define REFKEY 8 // Key changing the measurement reference = IO8
const word lutNumber[] = {'8', '0', '9', '6', '5', '3', 'E', '2', '4', '7', '1', 'r', '-'};
const word lutFrame0[] = {0x0006, 0x0006, 0x0004, 0x0006, 0x0004, 0x0004, 0x0002, 0x0002, 0x0004, 0x0004, 0x0004, 0x0002, 0x0000};
const word lutFrame1[] = {0x0006, 0x0004, 0x0006, 0x0002, 0x0002, 0x0006, 0x0002, 0x0006, 0x0006, 0x0004, 0x0004, 0x0002, 0x0002};
const word lutFrame2[] = {0x0004, 0x0004, 0x0004, 0x0004, 0x0004, 0x0004, 0x0004, 0x0004, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
const word lutFrame3[] = {0x0006, 0x0006, 0x0006, 0x0006, 0x0006, 0x0004, 0x0006, 0x0004, 0x0002, 0x0004, 0x0000, 0x0000, 0x0000};
#define DIGIT5 0x0002
#define SIGN 0x0001
#define UNIT_ft 0x1000
#define UNIT_in 0x4800
#define UNIT_m 0xC000
#define REF 0x04
#define LASERON 0x08
byte lcdFrameBuffer[70];
byte data = 0;
byte bitCount = 8;
byte byteCount = 0;
char serialOutBuffer[20];
byte serialOutIndex = 0;
bool takeMeasurement = false;
bool laserIsOn = false;
bool deviceIsOn = false;
unsigned long lcdTimeout;
char reference = 'b';
void setup() {
pinMode(CLKPIN, INPUT);
pinMode(DATAPIN, INPUT);
pinMode(CSPIN, INPUT);
digitalWrite(UNITKEY, HIGH);
digitalWrite(MEASUREKEY, HIGH);
digitalWrite(CLEARKEY, HIGH);
digitalWrite(REFKEY, HIGH);
pinMode(UNITKEY, OUTPUT);
pinMode(MEASUREKEY, OUTPUT);
pinMode(CLEARKEY, OUTPUT);
pinMode(REFKEY, OUTPUT);
Serial.begin(115200);
lcdTimeout = millis();
wdt_enable(WDTO_4S);
/* External interrupt 0&1 enable on rising edge. */
EICRA = _BV(ISC11) | _BV(ISC10) | _BV(ISC01) | _BV(ISC00) ;
EIMSK = _BV(INT1) | _BV(INT0);
}
static void clickKey(byte pin, unsigned long t){
digitalWrite(pin, LOW);
delay(t);
digitalWrite(pin, HIGH);
}
void loop() {
word frame0 = 0, frame1 = 0, frame2 = 0, frame3 = 0;
word mask0 = 0, mask1 = 0, mask2 = 0, mask3 = 0;
/* Skip LCD init frame and start reception inbetween two frames */
if((byteCount > 2) && (lcdFrameBuffer[0] != 0x10) && (lcdFrameBuffer[1] != 0x00)){
delay(50);
byteCount = bitCount =0;
}
/* Process a complete LCD frame */
if(byteCount == 68){
noInterrupts();
// Check for correct frame order
if((lcdFrameBuffer[0] == 0x10) &&(lcdFrameBuffer[1] == 0x00) &&(lcdFrameBuffer[2] == 0xC0)){
// Save last LCD line data to work variables
frame0 = lcdFrameBuffer[10];
frame0 <<= 8;
frame0 += lcdFrameBuffer[11];
frame1 = lcdFrameBuffer[27];
frame1 <<= 8;
frame1 += lcdFrameBuffer[28];
frame2 = lcdFrameBuffer[44];
frame2 <<= 8;
frame2 += lcdFrameBuffer[45];
frame3 = lcdFrameBuffer[61];
frame3 <<= 8;
frame3 += lcdFrameBuffer[62];
// Get measurement reference
if((lcdFrameBuffer[5] & REF)){
reference = 'm';
} else if((lcdFrameBuffer[22] & REF)){
reference = 'b';
} else if((lcdFrameBuffer[39] & REF)){
reference = 'f';
} else {
reference = '?';
}
}
/* Get ready to receive new frame */
byteCount = 0;
interrupts();
// Process last frame, get distance and unit from LCD data
// Sign
if((frame0 & SIGN) == SIGN){ serialOutBuffer[serialOutIndex++] = '-';}
// Digit 5
if((frame2 & DIGIT5) == DIGIT5){ serialOutBuffer[serialOutIndex++] = '1';}
// Remaining 5 digits
for(byte d = 0; d < 5; d++){
for(byte n = 0; n < 13; n++){
mask0 = lutFrame0[n];
mask1 = lutFrame1[n];
mask2 = lutFrame2[n];
mask3 = lutFrame3[n];
mask0 = (lutFrame0[n] << (d*2));
mask1 = (lutFrame1[n] << (d*2));
mask2 = (lutFrame2[n] << (d*2));
mask3 = (lutFrame3[n] << (d*2));
if(((frame0 & mask0) == mask0) && ((frame1 & mask1) == mask1) && ((frame2 & mask2) == mask2) && ((frame3 & mask3) == mask3)){
serialOutBuffer[serialOutIndex++] = lutNumber[n];
break;
}
}
// Decimal points
if((d == 1) && ((frame2 & 0x0020) == 0x0020)){ serialOutBuffer[serialOutIndex++] = '.'; }
else if((d == 2) && ((frame2 & 0x0080) == 0x0080)){ serialOutBuffer[serialOutIndex++] = '.'; }
else if((d == 3) && ((frame2 & 0x0200) == 0x0200)){ serialOutBuffer[serialOutIndex++] = '.'; }
}
// Unit
if((serialOutBuffer[0] != 'E') && (serialOutBuffer[0] != '-')){
if((frame2 & UNIT_ft) == UNIT_ft){
serialOutBuffer[serialOutIndex++] = 'f';
serialOutBuffer[serialOutIndex++] = 't';
} else if((frame2 & UNIT_in) == UNIT_in){
serialOutBuffer[serialOutIndex++] = 'i';
serialOutBuffer[serialOutIndex++] = 'n';
} else if((frame2 & UNIT_m) == UNIT_m){
serialOutBuffer[serialOutIndex++] = 'm';
}
}
serialOutBuffer[serialOutIndex++] = '\n';
if(serialOutIndex > 2){
Serial.write(serialOutBuffer, serialOutIndex);
}
serialOutIndex = 0;
deviceIsOn = true;
}
/* Get and process serial input */
if(Serial.available()){
char c = (char)Serial.read();
switch(c){
// Return device id
case '?':
Serial.println("Sndway Rangefinder v1.0");
break;
// Clear measurement result and switch off laser
case 'c':
clickKey(CLEARKEY, 5);
laserIsOn = false;
lcdTimeout = millis();
break;
// Switch on device and take measurement
case 'M':
// Switch on device
if(deviceIsOn == false){
clickKey(MEASUREKEY, 5);
delay(2000);
}
// Trigger laser on
if(laserIsOn == false){
clickKey(MEASUREKEY, 5);
delay(500);
}
// Trigger measurement
clickKey(MEASUREKEY, 5);
lcdTimeout = millis();
laserIsOn = false;
break;
case 'S':
clickKey(MEASUREKEY, 500);
laserIsOn = true;
break;
// Switch on laser
case 'L':
if(laserIsOn == false){
clickKey(MEASUREKEY, 5);
laserIsOn = true;
lcdTimeout = millis();
}
break;
// Switch off device
case 'o':
clickKey(CLEARKEY, 500);
laserIsOn = false;
deviceIsOn = false;
break;
// Cycle unit
case 'u':
clickKey(UNITKEY, 5);
lcdTimeout = millis();
break;
case 'r':
clickKey(REFKEY, 5);
lcdTimeout = millis();
break;
case 'R':
Serial.println(reference);
break;
default:
break;
}
}
// Device auto turns off after 150s
if(deviceIsOn && ((millis()-lcdTimeout) > 150000)){
laserIsOn = false;
deviceIsOn = false;
Serial.println("off");
}
wdt_reset();
}
/**
* Reads the data pin on CLK rising edge.
*/
ISR(INT0_vect) {
if(digitalRead(CSPIN) == LOW) {
if(bitCount == 8) {
// Skip data/command identifier bit
--bitCount;
return;
}
asm volatile("nop;");
data |= (digitalRead(DATAPIN) << bitCount);
--bitCount;
}
}
/**
* Save a single data byte to LCD frame buffer on CS rising edge.
*/
ISR(INT1_vect) {
lcdFrameBuffer[byteCount] = data;
++byteCount;
data = 0;
bitCount = 8;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment