reform

MNT Reform: Open Source Portable Computer
Log (Feed) | Files | Refs (Tags) | README

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:
Mreform-tiny-fw/reform-tiny-fw.ino | 308++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Areformd/reformd.sh | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 +