commit b5e1f485b770acfd0ae69d1a8e078d964583e85c
parent da2212949bc8ff1db258ddb79682167dc6bcbbda
Author: mntmn <lukas@mnt.mn>
Date: Sat, 10 Nov 2018 16:56:32 +0100
Merge branch 'master' of https://github.com/mntmn/reform
Diffstat:
2 files changed, 338 insertions(+), 106 deletions(-)
diff --git a/reform-tiny-fw/reform-tiny-fw.ino b/reform-tiny-fw/reform-tiny-fw.ino
@@ -1,145 +1,241 @@
-#include <SoftwareSerial.h>
-SoftwareSerial softSerial(8,3);
+/*
+ * MNT Reform 0.3+ ATtiny 841 controller firmware
+ * Copyright 2018 MNT Media and Technology UG, Berlin
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
#define SCL_PIN 0
#define SCL_PORT PORTA
#define SDA_PIN 1
#define SDA_PORT PORTA
#include <SoftI2CMaster.h>
+#include <SoftwareSerial.h>
-/*
- * MNT Reform 0.3+ ATtiny 841 controller firmware
- */
-
-// I2C scanner
-/*void scan() {
- byte error, address;
- int nDevices;
-
- nDevices = 0;
- for (address = 1; address < 127; address++ )
- {
- error = !i2c_start((address<<1)|I2C_WRITE);
- i2c_stop();
- if (!error)
- {
- if (address < 127) {
- softSerial.print(" ++");
- nDevices++;
- }
- } else {
- softSerial.print(" --");
- }
- softSerial.print(address, HEX);
- }
- softSerial.println("");
- if (nDevices == 0) {
- softSerial.println(":(");
- }
- else
- {
- softSerial.println(":)");
- }
-}*/
-
-void setup() {
- softSerial.begin(9600);
- if (!i2c_init())
- softSerial.println("i2c fail");
- else {
- softSerial.println("i2c ok");
- }
-
- // physical pin 3 for hall effect sensor
- pinMode(A10, INPUT);
- // physical pin 2 for hall effect sensor supply voltage
- pinMode(0, OUTPUT);
- digitalWrite(0, HIGH);
-
- // PWRON output (TODO: ULVO)
- pinMode(7, OUTPUT);
- digitalWrite(7, HIGH);
-}
-
-#define ina_addr 0x4e
+#define INA_ADDR 0x4e
-int16_t ina_read16(byte reg) {
+int16_t ina_read16(unsigned char reg) {
uint16_t val = 0;
- if (i2c_start((ina_addr<<1)|I2C_WRITE)) {
+ if (i2c_start((INA_ADDR << 1) | I2C_WRITE)) {
i2c_write(reg);
- i2c_rep_start((ina_addr<<1)|I2C_READ);
- val = ((uint16_t)i2c_read(false))<<8;
+ i2c_rep_start((INA_ADDR << 1) | I2C_READ);
+ val = ((uint16_t)i2c_read(false)) << 8;
val |= i2c_read(true);
i2c_stop();
}
return val;
}
+#define ST_EXPECT_DIGIT_0 0
+#define ST_EXPECT_DIGIT_1 1
+#define ST_EXPECT_DIGIT_2 2
+#define ST_EXPECT_DIGIT_3 3
+#define ST_EXPECT_CMD 4
+#define ST_SYNTAX_ERROR 5
+#define ST_EXPECT_RETURN 6
+
#define LID_CLOSED 1
-#define LID_OPEN 0
+#define LID_OPEN 0
+
+SoftwareSerial softSerial(8, 3);
float ampSecs = 5*3600.0;
-byte hallState = LID_OPEN;
+unsigned char hallState = LID_OPEN;
int thresh = 500;
int window = 10;
+int hallSense = 0;
-void loop() {
- float raw_volts = (float)ina_read16(0x2);
- float raw_current = (float)ina_read16(0x1);
+unsigned char state = ST_EXPECT_DIGIT_0;
+unsigned int inputNumber = 0;
+unsigned long lastTime = 0;
- float volts = raw_volts * 0.00125;
- float current = raw_current * 0.001;
+float volts = 0;
+float current = 0;
- if (current>-0.02 && current<0.02) current = 0; // clamp to zero
-
- ampSecs -= current;
+char cmd = 'a';
+unsigned char echo = 1;
- int hallSense = analogRead(A10);
+void handleLidSensor() {
+ hallSense = analogRead(A10);
if (hallSense>(thresh+window) && hallState==LID_OPEN) {
- //softSerial.println("lid_closed");
hallState = LID_CLOSED;
}
if (hallSense<(thresh+window) && hallState==LID_CLOSED) {
- softSerial.println("lid_open");
+ softSerial.println("event:wake");
hallState = LID_OPEN;
}
+}
- if (softSerial.available() > 0) {
- char cmd = softSerial.read();
-
- if (cmd == 'p') {
- softSerial.print(ampSecs/3600.0);
- softSerial.print("Ah\t");
- softSerial.print(volts);
- softSerial.print("V\t");
- softSerial.print(current);
- softSerial.println("A");
- }
- else if (cmd == 'b') {
- // reset battery capacity to 10Ah
- ampSecs = 10*3600.0;
- }
- else if (cmd == 'h') {
- softSerial.println(hallSense);
+void handleCommands() {
+ char chr = softSerial.read();
+ if (echo) softSerial.print(chr);
+
+ // states:
+ // 0-3 digits of optional command argument
+ // 4 command letter expected
+ // 5 syntax error (unexpected character)
+ // 6 command letter entered
+
+ if (state>=ST_EXPECT_DIGIT_0 && state<=ST_EXPECT_DIGIT_3) {
+ // read number or command
+ if (chr >= '0' && chr <= '9') {
+ inputNumber*=10;
+ inputNumber+=(chr-'0');
+ state++;
+ } else if (chr >= 'a' && chr <= 'z') {
+ // command entered instead of digit
+ cmd = chr;
+ state = ST_EXPECT_RETURN;
+ } else if (chr == '\n' || chr == ' ') {
+ // ignore newlines or spaces
+ } else if (chr == '\r') {
+ softSerial.println("error:syntax");
+ state = ST_EXPECT_DIGIT_0;
+ inputNumber = 0;
+ } else {
+ // syntax error
+ state = ST_SYNTAX_ERROR;
}
- else if (cmd == 'l') {
- softSerial.println(hallState);
+ }
+ else if (state == ST_EXPECT_CMD) {
+ // read command
+ if (chr >= 'a' && chr <= 'z') {
+ cmd = chr;
+ state = ST_EXPECT_RETURN;
+ } else {
+ state = ST_SYNTAX_ERROR;
}
- else if (cmd == '1') {
- thresh+=10;
- softSerial.println(thresh);
+ }
+ else if (state == ST_SYNTAX_ERROR) {
+ // syntax error
+ if (chr == '\r') {
+ softSerial.println("error:syntax");
+ state = ST_EXPECT_DIGIT_0;
+ inputNumber = 0;
}
- else if (cmd == '2') {
- thresh-=10;
- softSerial.println(thresh);
+ }
+ else if (state == ST_EXPECT_RETURN) {
+ if (chr == '\n' or chr == ' ') {
+ // ignore newlines or spaces
}
- else if (cmd == '3') {
- window+=1;
- softSerial.println(window);
+ else if (chr == '\r') {
+ // execute
+ if (cmd == 'p') {
+ // print power stats
+ softSerial.print("power(Ah,V,A):");
+ softSerial.print(ampSecs/3600.0);
+ softSerial.print("\t");
+ softSerial.print(volts);
+ softSerial.print("\t");
+ softSerial.println(current);
+ }
+ else if (cmd == 'c') {
+ // set battery capacity
+ if (inputNumber>0) {
+ ampSecs = inputNumber*60.0;
+ }
+ softSerial.print("capacity(Ah):");
+ softSerial.println(ampSecs/3600.0);
+ }
+ else if (cmd == 'h') {
+ // print hall sensor analog reading
+ softSerial.print("sensor:");
+ softSerial.println(hallSense);
+ }
+ else if (cmd == 'l') {
+ // print lid open/close state
+ softSerial.print("lid:");
+ softSerial.println(hallState);
+ }
+ else if (cmd == 't') {
+ // set open/closed threshold
+ if (inputNumber>0) {
+ thresh = inputNumber;
+ }
+ softSerial.print("threshold:");
+ softSerial.println(thresh);
+ }
+ else if (cmd == 'w') {
+ // set open/closed threshold fuzz window
+ if (inputNumber>0) {
+ window = inputNumber;
+ }
+ softSerial.print("window:");
+ softSerial.println(window);
+ }
+ else if (cmd == 'u') {
+ // uptime of attiny in seconds
+ softSerial.print("uptime(s):");
+ softSerial.println(millis()/1000);
+ }
+ else if (cmd == 'e') {
+ // toggle serial echo
+ echo = inputNumber?1:0;
+ softSerial.print("echo:");
+ softSerial.println(echo);
+ }
+ else {
+ softSerial.println("error:command");
+ }
+
+ state = ST_EXPECT_DIGIT_0;
+ inputNumber = 0;
+ } else {
+ state = ST_SYNTAX_ERROR;
}
- else if (cmd == '4') {
- window-=1;
- softSerial.println(window);
+ }
+}
+
+void handleBattery() {
+ float raw_volts = (float)ina_read16(0x2);
+ float raw_current = (float)ina_read16(0x1);
+
+ volts = raw_volts * 0.00125;
+ current = raw_current * 0.001;
+
+ if (current>-0.02 && current<0.02) current = 0; // clamp to zero
+
+ unsigned long thisTime = millis();
+ if (lastTime>0 && thisTime>lastTime) {
+ unsigned long millisPassed = thisTime - lastTime;
+
+ if (millisPassed >= 1000) {
+ lastTime = thisTime;
+
+ // decrease estimated battery capacity
+ ampSecs -= current*(millisPassed/1000);
}
+ } else {
+ // timer uninitialized or timer wrap
+ lastTime = thisTime;
+ }
+}
+
+void loop() {
+ handleBattery();
+ handleLidSensor();
+
+ if (softSerial.available() > 0) {
+ handleCommands();
+ }
+}
+
+void setup() {
+ softSerial.begin(2400);
+ softSerial.println("reform:attiny:0.4.0:boot");
+ if (!i2c_init()) {
+ softSerial.println("error:i2c");
}
- delay(1000);
+
+ // physical pin 3 for hall effect sensor
+ pinMode(A10, INPUT);
+ // physical pin 2 for hall effect sensor supply voltage
+ pinMode(0, OUTPUT);
+ digitalWrite(0, HIGH);
+ // physical pin 5 for hall effect sensor GND
+ pinMode(2, OUTPUT);
+ digitalWrite(2, LOW);
+
+ // PWRON output (TODO: ULVO)
+ pinMode(7, OUTPUT);
+ digitalWrite(7, HIGH);
}
diff --git a/reformd/reformd.sh b/reformd/reformd.sh
@@ -0,0 +1,136 @@
+#!/bin/bash
+#
+# MNT Reform 0.3+ Daemon for Battery, Lid and Fan Control
+# Copyright 2018 MNT Media and Technology UG, Berlin
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+set_fan_speed () {
+ set +e; echo 0 > /sys/class/pwm/pwmchip1/export 2>/dev/null ; set -e
+ echo 10000 > /sys/class/pwm/pwmchip1/pwm0/period
+ echo "$1" > /sys/class/pwm/pwmchip1/pwm0/duty_cycle
+ echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable
+}
+
+function setup_serial {
+ # 2400 baud 8N1, raw
+ # cargoculted by exporting parameters with stty after using screen
+ stty 406:0:8bb:8a30:3:1c:7f:15:4:2:64:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 -F /dev/ttymxc1
+}
+
+function disable_echo {
+ setup_serial
+ exec 99<>/dev/ttymxc1
+ :<&99
+ printf "0e\r" >&99
+ exec 99>&-
+ set +e; timeout 2 head /dev/ttymxc1; set -e
+}
+
+function get_battery_state {
+ setup_serial
+ exec 99<>/dev/ttymxc1
+ :<&99
+ printf "p\r" >&99
+ IFS=$':\t\r'
+ read -r -t 1 msg bat_capacity bat_volts bat_amps <&99
+ exec 99>&-
+}
+
+function get_soc_temperature {
+ read soc_temperature < /sys/class/thermal/thermal_zone0/temp
+}
+
+function get_lid_state {
+ setup_serial
+ exec 99<>/dev/ttymxc1
+ :<&99
+ printf "l\r" >&99
+ IFS=$':\t\r'
+ read -r -t 1 msg lid_state <&99
+ lid_state=$(echo "$lid_state" | tr -dc '0-9')
+
+ exec 99>&-
+}
+
+function reset_bat_capacity {
+ setup_serial
+ exec 99<>/dev/ttymxc1
+ :<&99
+ printf "0600b\r" >&99
+ exec 99>&-
+}
+
+function system_suspend {
+ setup_serial
+
+ # wake up on serial port 2 traffic
+ echo enabled > /sys/devices/soc0/soc/2100000.aips-bus/21e8000.serial/tty/ttymxc1/power/wakeup
+
+ # drain serial port
+ set +e; timeout 1 head /dev/ttymxc1; set -e
+
+ # zzZzzZ
+ systemctl suspend
+}
+
+function regulate_fan {
+ get_soc_temperature
+
+ if [ "$soc_temperature" -lt 65000 ]
+ then
+ set_fan_speed 5000
+ fi
+
+ if [ "$soc_temperature" -gt 70000 ]
+ then
+ set_fan_speed 10000
+ fi
+}
+
+voltage_alert=0
+function regulate_voltage {
+ # TODO low voltage/capacity alert
+ echo "not yet implemented"
+}
+
+function main {
+ # 1. if the system is getting too hot, we want to cool it with the fan
+ regulate_fan
+
+ # 2. log all stats, especially battery stats, to a TSV file
+ # so it can be graphed and we can estimate remaining running time
+ # TODO actually append to log and rotate it out
+ # TODO interval?
+ timestamp=$(date +%Y-%m-%dT%H:%M:%S)
+ get_soc_temperature
+ get_battery_state
+ get_lid_state
+
+ if [ "$bat_amps" == "0.00A" ]
+ then
+ reset_bat_capacity
+ fi
+
+ printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$timestamp" "$soc_temperature" "$bat_capacity" "$bat_volts" "$bat_amps" "$voltage_alert" "$lid_state"
+ printf '%s,%s,%s,%s,%s,%s,%s\n' "$timestamp" "$soc_temperature" "$bat_capacity" "$bat_volts" "$bat_amps" "$voltage_alert" "$lid_state" > /var/log/reformd
+
+ # 3. if the lid is closed, we want to suspend the system
+ # important: this works only if the kernel option no_console_suspend=1 is set!
+ # also, requires kernel patch when using PCIe cards: https://github.com/sakaki-/novena-kernel-patches/blob/master/0017-pci-fix-suspend-on-i.MX6.patch
+ # (workaround for erratum "PCIe does not support L2 Power Down")
+ if [ "$lid_state" ] && [ "$lid_state" -eq "1" ]
+ then
+ system_suspend
+ exit
+ fi
+}
+
+brightnessctl s 5
+disable_echo
+
+while true; do
+ main
+ sleep 1
+done
+