reform-tiny-fw.ino 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. * MNT Reform 0.3+ ATtiny 841 controller firmware
  3. * Copyright 2018 MNT Media and Technology UG, Berlin
  4. * SPDX-License-Identifier: GPL-3.0-or-later
  5. */
  6. #define SCL_PIN 0
  7. #define SCL_PORT PORTA
  8. #define SDA_PIN 1
  9. #define SDA_PORT PORTA
  10. #define HALL_SENSOR_PIN A5
  11. #define HALL_SENSOR_SUPPLY_PIN 4
  12. #define INA_ADDR 0x4e
  13. #include <SoftI2CMaster.h>
  14. #include <SoftwareSerial.h>
  15. int16_t ina_read16(unsigned char reg) {
  16. uint16_t val = 0;
  17. if (i2c_start((INA_ADDR << 1) | I2C_WRITE)) {
  18. i2c_write(reg);
  19. i2c_rep_start((INA_ADDR << 1) | I2C_READ);
  20. val = ((uint16_t)i2c_read(false)) << 8;
  21. val |= i2c_read(true);
  22. i2c_stop();
  23. }
  24. return val;
  25. }
  26. #define ST_EXPECT_DIGIT_0 0
  27. #define ST_EXPECT_DIGIT_1 1
  28. #define ST_EXPECT_DIGIT_2 2
  29. #define ST_EXPECT_DIGIT_3 3
  30. #define ST_EXPECT_CMD 4
  31. #define ST_SYNTAX_ERROR 5
  32. #define ST_EXPECT_RETURN 6
  33. #define LID_CLOSED 1
  34. #define LID_OPEN 0
  35. SoftwareSerial softSerial(8, 3);
  36. float ampSecs = 5*3600.0;
  37. unsigned char hallState = LID_OPEN;
  38. int thresh = 100;
  39. int window = 10;
  40. int hallSense = 0;
  41. unsigned char state = ST_EXPECT_DIGIT_0;
  42. unsigned int inputNumber = 0;
  43. unsigned long lastTime = 0;
  44. float volts = 0;
  45. float current = 0;
  46. char cmd = 'a';
  47. unsigned char echo = 1;
  48. char hallSenseDir = 1;
  49. char hallSenseEvents = 0;
  50. // TODO if there is no battery power, ignore lid sensor (values >900)
  51. void handleLidSensor() {
  52. hallSense = analogRead(HALL_SENSOR_PIN);
  53. if (hallState==LID_OPEN && (hallSenseDir == 0 && hallSense>(thresh+window) || hallSenseDir == 1 && hallSense<(thresh-window))) {
  54. hallState = LID_CLOSED;
  55. }
  56. if (hallState==LID_CLOSED && (hallSenseDir == 0 && hallSense<(thresh-window) || hallSenseDir == 1 && hallSense>(thresh+window))) {
  57. hallState = LID_OPEN;
  58. if (hallSenseEvents) softSerial.println("event:wake");
  59. }
  60. }
  61. void handleCommands() {
  62. char chr = softSerial.read();
  63. if (echo) softSerial.print(chr);
  64. // states:
  65. // 0-3 digits of optional command argument
  66. // 4 command letter expected
  67. // 5 syntax error (unexpected character)
  68. // 6 command letter entered
  69. if (state>=ST_EXPECT_DIGIT_0 && state<=ST_EXPECT_DIGIT_3) {
  70. // read number or command
  71. if (chr >= '0' && chr <= '9') {
  72. inputNumber*=10;
  73. inputNumber+=(chr-'0');
  74. state++;
  75. } else if (chr >= 'a' && chr <= 'z') {
  76. // command entered instead of digit
  77. cmd = chr;
  78. state = ST_EXPECT_RETURN;
  79. } else if (chr == '\n' || chr == ' ') {
  80. // ignore newlines or spaces
  81. } else if (chr == '\r') {
  82. softSerial.println("error:syntax");
  83. state = ST_EXPECT_DIGIT_0;
  84. inputNumber = 0;
  85. } else {
  86. // syntax error
  87. state = ST_SYNTAX_ERROR;
  88. }
  89. }
  90. else if (state == ST_EXPECT_CMD) {
  91. // read command
  92. if (chr >= 'a' && chr <= 'z') {
  93. cmd = chr;
  94. state = ST_EXPECT_RETURN;
  95. } else {
  96. state = ST_SYNTAX_ERROR;
  97. }
  98. }
  99. else if (state == ST_SYNTAX_ERROR) {
  100. // syntax error
  101. if (chr == '\r') {
  102. softSerial.println("error:syntax");
  103. state = ST_EXPECT_DIGIT_0;
  104. inputNumber = 0;
  105. }
  106. }
  107. else if (state == ST_EXPECT_RETURN) {
  108. if (chr == '\n' or chr == ' ') {
  109. // ignore newlines or spaces
  110. }
  111. else if (chr == '\r') {
  112. // execute
  113. if (cmd == 'a') {
  114. // get current (mA)
  115. softSerial.println((int)(current*1000.0));
  116. }
  117. else if (cmd == 'v') {
  118. // get voltage
  119. softSerial.println((int)(volts*1000.0));
  120. }
  121. else if (cmd == 'c') {
  122. // set/get battery capacity (mAh)
  123. if (inputNumber>0) {
  124. ampSecs = ((float)inputNumber)*3.6;
  125. }
  126. softSerial.println((int)(ampSecs/3.6));
  127. }
  128. else if (cmd == 's') {
  129. // print sensor analog reading 0-1023
  130. softSerial.println(hallSense);
  131. }
  132. else if (cmd == 'l') {
  133. // print lid open/close state
  134. softSerial.println(hallState);
  135. }
  136. else if (cmd == 't') {
  137. // set open/closed threshold
  138. if (inputNumber>0) {
  139. thresh = inputNumber;
  140. }
  141. softSerial.println(thresh);
  142. }
  143. else if (cmd == 'w') {
  144. // set open/closed threshold hysteresis window
  145. if (inputNumber>0) {
  146. window = inputNumber;
  147. }
  148. softSerial.println(window);
  149. }
  150. else if (cmd == 'u') {
  151. // uptime of attiny in seconds
  152. softSerial.println(millis()/1000);
  153. }
  154. else if (cmd == 'e') {
  155. // toggle serial echo
  156. echo = inputNumber?1:0;
  157. //softSerial.print("echo:");
  158. //softSerial.println(echo);
  159. }
  160. else if (cmd == 'o') {
  161. // toggle lid sensor magnet orientation
  162. hallSenseDir = inputNumber?1:0;
  163. //softSerial.print("orientation:");
  164. softSerial.println(hallSenseDir);
  165. }
  166. else if (cmd == 'k') {
  167. // toggle lid sensor waKe events
  168. hallSenseEvents = inputNumber?1:0;
  169. //softSerial.print("events:");
  170. //softSerial.println(hallSenseEvents);
  171. }
  172. else {
  173. softSerial.println("error:command");
  174. }
  175. state = ST_EXPECT_DIGIT_0;
  176. inputNumber = 0;
  177. } else {
  178. state = ST_SYNTAX_ERROR;
  179. }
  180. }
  181. }
  182. void handleBattery() {
  183. float raw_volts = (float)ina_read16(0x2);
  184. float raw_current = (float)ina_read16(0x1);
  185. volts = raw_volts * 0.00125;
  186. current = raw_current * 0.001;
  187. if (current>-0.02 && current<0.02) current = 0; // clamp to zero
  188. unsigned long thisTime = millis();
  189. if (lastTime>0 && thisTime>lastTime) {
  190. unsigned long millisPassed = thisTime - lastTime;
  191. if (millisPassed >= 1000) {
  192. lastTime = thisTime;
  193. // decrease estimated battery capacity
  194. ampSecs -= current*(millisPassed/1000);
  195. }
  196. } else {
  197. // timer uninitialized or timer wrap
  198. lastTime = thisTime;
  199. }
  200. }
  201. void loop() {
  202. handleBattery();
  203. handleLidSensor();
  204. if (softSerial.available() > 0) {
  205. handleCommands();
  206. }
  207. }
  208. void setup() {
  209. softSerial.begin(2400);
  210. softSerial.println("reform:attiny:0.4.0:boot");
  211. if (!i2c_init()) {
  212. softSerial.println("error:i2c");
  213. }
  214. // physical pin 8 (J34 pin 3) for hall effect sensor
  215. pinMode(HALL_SENSOR_PIN, INPUT);
  216. // physical pin 7 (J34 pin 5) for hall effect sensor supply voltage
  217. pinMode(HALL_SENSOR_SUPPLY_PIN, OUTPUT);
  218. digitalWrite(HALL_SENSOR_SUPPLY_PIN, HIGH);
  219. // PWRON output (TODO: ULVO)
  220. pinMode(7, OUTPUT);
  221. digitalWrite(7, HIGH);
  222. }