From 2d1942f76a52ea748617791bc22022fb447f182c Mon Sep 17 00:00:00 2001 From: Daniel Thompson Date: Sun, 9 Aug 2020 20:06:45 +0100 Subject: [PATCH] k9: Add support for Senbono K9 The K9 is similar to the PineTime and P8 devices but does not appear to use the CST[78]16 touch screen controllers. At present the protocol is not known (readfrom yields all zeros, readfrom_mem provokes an exception) so we have a hugely limited interface consisting of the side button and the touchscreen interrupts (in other words we can treat the touchscreen like a second button). Works suprisingly well considering... Signed-off-by: Daniel Thompson --- bootloader | 2 +- micropython | 2 +- reloader | 2 +- wasp/boards/k9/manifest.py | 45 ++++++++++++ wasp/boards/k9/watch.py.in | 139 +++++++++++++++++++++++++++++++++++++ wasp/drivers/touch.py | 84 ++++++++++++++++++++++ 6 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 wasp/boards/k9/manifest.py create mode 100644 wasp/boards/k9/watch.py.in create mode 100644 wasp/drivers/touch.py diff --git a/bootloader b/bootloader index 89ba9a8..91fad65 160000 --- a/bootloader +++ b/bootloader @@ -1 +1 @@ -Subproject commit 89ba9a874fa052ae7b0df8a9b6a6f8916309cd29 +Subproject commit 91fad65a8a0c5b3be0468e914a5788a519591c52 diff --git a/micropython b/micropython index c7cf47a..6df2db3 160000 --- a/micropython +++ b/micropython @@ -1 +1 @@ -Subproject commit c7cf47a3681ab1625b52b7b8663bfa95dd5d9096 +Subproject commit 6df2db3f4cbe046eac17d7893d66a6c077228402 diff --git a/reloader b/reloader index b4512e5..9f55c66 160000 --- a/reloader +++ b/reloader @@ -1 +1 @@ -Subproject commit b4512e5d92d062a65a216d68bffcdc03ff1625cf +Subproject commit 9f55c66be8f129fd00e3eda60988b4cbeffd8840 diff --git a/wasp/boards/k9/manifest.py b/wasp/boards/k9/manifest.py new file mode 100644 index 0000000..1c1d7e2 --- /dev/null +++ b/wasp/boards/k9/manifest.py @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2020 Daniel Thompson + +freeze('.', 'watch.py', opt=3) +freeze('../..', + ( + 'apps/clock.py', + 'apps/flashlight.py', + 'apps/heart.py', + 'apps/launcher.py', + 'apps/pager.py', + 'apps/settings.py', + 'apps/steps.py', + 'apps/stopwatch.py', + 'apps/testapp.py', + 'boot.py', + 'draw565.py', + 'drivers/bma421.py', + 'drivers/battery.py', + 'drivers/hrs3300.py', + 'drivers/nrf_rtc.py', + 'drivers/signal.py', + 'drivers/st7789.py', + 'drivers/touch.py', + 'drivers/vibrator.py', + 'fonts/__init__.py', + 'fonts/clock.py', + 'fonts/sans24.py', + 'fonts/sans28.py', + 'fonts/sans36.py', + 'gadgetbridge.py', + 'icons.py', + 'ppg.py', + 'shell.py', + 'wasp.py', + 'widgets.py', + ), + opt=3 +) +freeze('../../drivers/flash', + ( + 'bdevice.py', + 'flash/flash_spi.py' + ), opt=3 +) diff --git a/wasp/boards/k9/watch.py.in b/wasp/boards/k9/watch.py.in new file mode 100644 index 0000000..5126a87 --- /dev/null +++ b/wasp/boards/k9/watch.py.in @@ -0,0 +1,139 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2020 Daniel Thompson + +def nop(): + pass +schedule = nop +def _callback(obj): + schedule() + +# Start measuring time (and feeding the watchdog) before *anything* else +from machine import RTCounter +from drivers.nrf_rtc import RTC +rtc = RTC(RTCounter(1, mode=RTCounter.PERIODIC, period=1, callback=_callback)) +rtc.counter.start() + +import os +import time + +import draw565 + +from machine import I2C +from machine import Pin +#from machine import Signal +from machine import SPI + +from drivers.battery import Battery +from drivers.bma421 import BMA421 +from drivers.touch import TouchButton +from drivers.hrs3300 import HRS3300 +from drivers.signal import Signal +from drivers.st7789 import ST7789_SPI +from drivers.vibrator import Vibrator +from flash.flash_spi import FLASH + +from ubluepy import uart_connected as connected + +class Backlight(object): + lo = Pin("BL_LO", Pin.OUT, value=0) + mid = Pin("BL_MID", Pin.OUT, value=1) + hi = Pin("BL_HI", Pin.OUT, value=1) + + def __init__(self, level=1): + self.set(level) + + def set(self, level): + hi = 1 + mid = 1 + lo = 0 + + if level >= 3: + hi = 0 + mid = 0 + elif level == 2: + hi = 0 + elif level == 1: + mid = 0 + + self.hi(hi) + self.mid(mid) + self.lo(lo) + +# Setup the display (and manage the backlight) +backlight = Backlight(0) +spi = SPI(0) +spi.init(polarity=1, phase=1, baudrate=8000000) +display = ST7789_SPI(240, 240, spi, + cs=Pin("DISP_CS", Pin.OUT), + dc=Pin("DISP_DC", Pin.OUT), + res=Pin("DISP_RST", Pin.OUT)) +drawable = draw565.Draw565(display) + +def boot_msg(s): + drawable.string(s, 0, 108, width=240) + if safe_mode: + time.sleep_ms(500) + +safe_mode = True +boot_msg("Init button") +button = Pin('BUTTON', Pin.IN) +safe_mode = button.value() +if safe_mode: + backlight.set(2) + time.sleep(1) + +try: + # Setup the last few bits and pieces + boot_msg("Init battery") + battery = Battery( + Pin('BATTERY', Pin.IN), + Signal(Pin('CHARGING', Pin.IN), invert=True), + Signal(Pin('USB_PWR', Pin.IN), invert=True)) + boot_msg("Init I2C") + i2c = I2C(1, scl='I2C_SCL', sda='I2C_SDA') + boot_msg("Init BMA421") + accel = BMA421(i2c) + boot_msg("Init HRS3300") + hrs = HRS3300(i2c) + boot_msg("Init touch") + touch = TouchButton(Pin('TP_INT', Pin.IN), + Pin('TP_RST', Pin.OUT, value=0), _callback) + boot_msg("Init motor") + vibrator = Vibrator(Pin('MOTOR', Pin.OUT, value=0), active_low=True) + + # Release flash from deep power-down + boot_msg("Wake SPINOR") + nor_cs = Pin('NOR_CS', Pin.OUT, value=1) + nor_cs(0) + spi.write('\xAB') + nor_cs(1) + + # Mount the filesystem + boot_msg("Init SPINOR") + flash = FLASH(spi, (nor_cs,)) + try: + boot_msg("Mount FS") + os.mount(flash, '/flash') + except AttributeError: + # Format the filesystem (and provide a default version of main.py) + boot_msg("Format FS") + os.VfsLfs2.mkfs(flash) + boot_msg("Retry mount FS") + os.mount(flash,'/flash') + boot_msg("Write main.py") + with open('/flash/main.py', 'w') as f: + f.write('''\ +#include('main.py') +''') + + # Only change directory if the button is not pressed (this will + # allow us access to fix any problems with main.py)! + if not safe_mode: + boot_msg("Enter /flash") + os.chdir('/flash') + boot_msg("Run main.py") + else: + boot_msg("Safe mode") +except: + drawable.string("FAILED", 0, 136, width=240) + backlight.set(2) diff --git a/wasp/drivers/touch.py b/wasp/drivers/touch.py new file mode 100644 index 0000000..0b08653 --- /dev/null +++ b/wasp/drivers/touch.py @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2020 Daniel Thompson + +"""Basic touch sensor driver +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" + +import array +import time +from machine import Pin +from watch import rtc + +class TouchButton: + """Simple touch controller driver. + + .. automethod:: __init__ + """ + + def __init__(self, intr, rst, schedule=None): + """Specify the bus used by the touch controller. + + :param machine.I2C bus: I2C bus for the CST816S. + """ + self.tp_int = intr + self.tp_rst = rst + self.schedule = schedule + self.event = array.array('H', (0, 0, 0)) + + self._reset() + self.tp_int.irq(trigger=Pin.IRQ_FALLING, handler=self.get_touch_data) + + def _reset(self): + self.tp_rst.off() + time.sleep_ms(5) + self.tp_rst.on() + time.sleep_ms(50) + self.event[0] = 0 + self._wake_at = rtc.get_uptime_ms() + 300 + + def get_touch_data(self, pin_obj): + """Synthesize a right swipe during interrupt. + """ + self.event[0] = 4 + + if self.schedule: + self.schedule(self) + + def get_event(self): + """Receive a touch event. + + Check for a pending touch event and, if an event is pending, + prepare it ready to go in the event queue. + + :return: An event record if an event is received, None otherwise. + """ + if rtc.get_uptime_ms() < self._wake_at: + self.event[0] = 0 + + if self.event[0] == 0: + return None + + return self.event + + def reset_touch_data(self): + """Reset touch data. + + Reset touch data, call this function after processing an event. + """ + self.event[0] = 0 + + def wake(self): + """Wake up touch controller chip. + + Just reset the chip in order to wake it up + """ + self._reset() + + def sleep(self): + """Put touch controller chip on sleep mode to save power. + """ + self.tp_rst.off() + + # Ensure get_event() cannot return anything + self.event[0] = 0