/ caliper-teensy31.ino
caliper-teensy31.ino
  1  /*
  2      USB HID driver for Harbor Freight 100mm Electronic Digital Caliper 
  3      Copyright © 2017 Jeff Epler
  4  
  5      This program is free software: you can redistribute it and/or modify
  6      it under the terms of the GNU General Public License as published by
  7      the Free Software Foundation, either version 3 of the License, or
  8      (at your option) any later version.
  9  
 10      This program is distributed in the hope that it will be useful,
 11      but WITHOUT ANY WARRANTY; without even the implied warranty of
 12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13      GNU General Public License for more details.
 14  
 15      You should have received a copy of the GNU General Public License
 16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
 17   */
 18  
 19  #if defined(__MK20DX256__) && defined(TEENSYDUINO) && TEENSYDUINO == 140
 20  // internet lore says that ARDUINO_BOARD_TEENSY31 should have been defined, but
 21  // not for me (in arduino 1.8.5)!
 22  #define TEENSY32
 23  #endif
 24  
 25  #if defined(TEENSY32)
 26  // note: actually only works with Teensy 3.2, DAC wasn't exposed on 3.1
 27  // but this case will be selected anyway :-/
 28  constexpr auto PIN_V1_5 = A14;
 29  constexpr auto PIN_nACT = 12;
 30  constexpr auto PIN_LED = 13;
 31  constexpr auto DAC_SETTING_V1_5 = int(1.5 * 4096 / 3.3 + .5);
 32  bool read_clk() { return !!(CMP0_SCR & CMP_SCR_COUT); }
 33  bool read_data() { return !!(CMP1_SCR & CMP_SCR_COUT); }
 34  void board_setup() {
 35  // Set up the analog comparators to read the low-voltage signals.
 36  //
 37  // Unfortunately, analog comparators aren't standardized by the Arduino library,
 38  // or by any third-party library I ran across, so we're going to go directly
 39  // down to the register level for this.
 40    SIM_SCGC4 |= SIM_SCGC4_CMP; // enable clock to comparator module
 41  
 42  // CMP1 (data pin)
 43    CMP1_CR0 = CMP_CR0_FILTER_CNT(1) | CMP_CR0_HYSTCTR(1);
 44    CMP1_CR1 = CMP_CR1_INV | CMP_CR1_EN;
 45    CMP1_MUXCR = CMP_MUXCR_PSEL(0) | CMP_MUXCR_MSEL(7);
 46  // set DAC1 ref to code 14, since 3.3v * 14/64 = .67v
 47    CMP1_DACCR = CMP_DACCR_DACEN | CMP_DACCR_VRSEL | CMP_DACCR_VOSEL(14);
 48  
 49  // CMP0 (clock pin)
 50    CMP0_CR0 = CMP_CR0_FILTER_CNT(1) | CMP_CR0_HYSTCTR(1);
 51    CMP0_CR1 = CMP_CR1_INV | CMP_CR1_EN;
 52    CMP0_MUXCR = CMP_MUXCR_PSEL(0) | CMP_MUXCR_MSEL(7);
 53  // set DAC0 ref to code 14, since 3.3v * 14/64 = .67v
 54    CMP0_DACCR = CMP_DACCR_DACEN | CMP_DACCR_VRSEL | CMP_DACCR_VOSEL(14);
 55  // For other pin assignment possibilities, see the reference manual
 56  // https://cache.freescale.com/files/32bit/doc/ref_manual/K20P64M72SF1RM.pdf
 57  // 3.7.2.1 (page 105) for internal connections to the analog comparator,
 58  // and 10.3.1 (page 207ff) for external pin assignments.  Remember that
 59  // each signal needs to go to a different CMPx comparator (e.g., CMP0 and CMP1,
 60  // CMP1 and CMP2, or CMP0 and CMP2)
 61  }
 62  void keyboard_write(const char *buf) {
 63      Keyboard.write(buf);
 64  }
 65  #elif defined(ARDUINO_TRINKET_M0)
 66  #warning "this is only aspirational support for now"
 67  constexpr auto PIN_V1_5 = A0;
 68  constexpr auto DAC_SETTING_V1_5 = int(1.5 * 4096 / 3.3 + .5);
 69  constexpr auto PIN_nACT = 12;
 70  // PIN_LED is predefined
 71  bool read_clk() { return !(REG_AC_STATUSA & 1); }
 72  bool read_data() { return !(REG_AC_STATUSA & 2); }
 73  void board_setup() {
 74      // enable APB clock to the analog comparator
 75      REG_PM_APBCMASK |= PM_APBCMASK_AC;
 76  
 77      // Enable GCLK0 to comparator digital section
 78      REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |
 79          GCLK_CLKCTRL_GEN_GCLK0 |
 80          GCLK_CLKCTRL_ID_AC_DIG;
 81  
 82      // Enable GCLK0 to comparator analog section
 83      REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |
 84          GCLK_CLKCTRL_GEN_GCLK0 |
 85          GCLK_CLKCTRL_ID_AC_ANA;
 86  
 87      // Enable AC
 88      REG_AC_CTRLA |= AC_CTRLA_ENABLE;
 89  // https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf page 21
 90  
 91  // AC IN[2] is SAMD21E package pin 7 (clk pin), labeled "4"
 92      REG_AC_COMPCTRL0 =
 93          AC_COMPCTRL_FLEN_MAJ5 |
 94          AC_COMPCTRL_HYST |
 95          AC_COMPCTRL_MUXPOS(2) |
 96          AC_COMPCTRL_MUXNEG(5) |
 97          AC_COMPCTRL_ENABLE;
 98  
 99      REG_AC_SCALER0 = 14;
100  // AC IN[3] is SAMD21E package pin 8 (data pin), labeled "3"
101      REG_AC_COMPCTRL1 =
102          AC_COMPCTRL_FLEN_MAJ5 |
103          AC_COMPCTRL_HYST |
104          AC_COMPCTRL_MUXPOS(3) |
105          AC_COMPCTRL_MUXNEG(5) |
106          AC_COMPCTRL_ENABLE;
107  
108      REG_AC_SCALER1 = 14;
109   }
110  void keyboard_write(const char *buf) {}
111  #else
112  #error "need to define a board type macro (or your board is not supported)"
113  #endif
114  
115  char buf[81];
116  
117  static_assert(sizeof(1ull) == 8, "unsigned long long is 64 bits");
118  
119  void setup() {
120    Serial.begin(9600);
121    analogWriteResolution(12);
122    pinMode(PIN_nACT, INPUT_PULLUP);
123    pinMode(PIN_LED, OUTPUT);
124    analogWrite(PIN_V1_5, DAC_SETTING_V1_5);
125    snprintf(buf, sizeof(buf), "no data");
126    board_setup();
127  
128  }
129  
130  // (I don't use an enum because of a deficiency in arduino's handling of enums: https://playground.arduino.cc/Code/Enum)
131  #define STATE_WAIT_IDLE (0)
132  #define STATE_WAIT_LOW (1)
133  #define STATE_WAIT_HIGH (2)
134  
135  uint8_t state, bitno;
136  bool clk, data, stable;
137  uint16_t time_idle;
138  uint64_t reading, old_reading;
139  
140  uint8_t wait_idle() {
141    if (!clk)
142      time_idle ++;
143    else
144      time_idle = 0;
145    if (time_idle > 300) {
146      reading = 0;
147      bitno = 0;
148      time_idle = 0;
149      return STATE_WAIT_HIGH;
150    }
151    return STATE_WAIT_IDLE;
152  }
153  
154  uint8_t wait_high() {
155    if (!clk) return STATE_WAIT_HIGH;
156    return STATE_WAIT_LOW;
157  }
158  
159  
160  uint8_t wait_low() {
161    if (clk) return STATE_WAIT_LOW;
162    if (!data) reading = reading | (1ull << bitno); // (data << bitno);
163    
164    bitno ++;
165    if (bitno == 48) {
166      stable = (reading == old_reading);
167      old_reading = reading;
168      auto position = (reading & 0xfffffull);
169      auto signbit = bool(reading & 0x100000ull);
170      auto inch = bool(reading & (1ull << 47));
171  
172      digitalWrite(PIN_LED, stable);
173      
174      if (inch) {
175        snprintf(buf, sizeof(buf), " %s%d.%03d%c",
176               signbit ? "-" : "",
177               (int)(position / 2000),
178               (int)(position / 2) % 1000,
179               position % 2 ? '5' : '0');
180      } else {
181        snprintf(buf, sizeof(buf), " %s%d.%02d",
182               signbit ? "-" : "",
183               (int)(position / 100),
184               (int)(position % 100));
185      }
186      Serial.write(buf);
187      Serial.write("\n");
188  
189      return STATE_WAIT_IDLE;
190    }
191    return STATE_WAIT_HIGH;
192  }
193  
194  bool old_act;
195  
196  void loop() {
197    bool act = !digitalRead(PIN_nACT);
198    if (act && !old_act && stable) {
199      keyboard_write(buf);
200      *buf = 0;
201      old_reading = ~0;
202      stable = false; // this ends up acting like a debounce
203      digitalWrite(PIN_LED, false);
204    }
205    old_act = act;
206    clk = read_clk();
207    data = read_data();
208  
209    switch (state) {
210      case STATE_WAIT_IDLE: state = wait_idle(); break;
211      case STATE_WAIT_LOW:  state = wait_low(); break;
212      case STATE_WAIT_HIGH: state = wait_high(); break;
213    }
214  }