├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md └── firmware ├── boot.py ├── lib ├── input.py ├── ip5306.py └── m5stack.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | firmware/lib/ili934x.py 2 | 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Mika Tuupola 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | PORT := /dev/tty.SLAB_USBtoUART 3 | 4 | help: 5 | @echo "" 6 | @echo "Available tasks:" 7 | @echo " watch Upload changed *.py files to flash automatically" 8 | @echo " shell Start an remote shell session" 9 | @echo " sync Sync contents of firmware folder to flash" 10 | @echo " reset Soft reboot the board" 11 | @echo " repl Start a repl session" 12 | @echo " deps Install dependencies with upip" 13 | @echo "" 14 | 15 | watch: 16 | find . -name "*.py" | entr -c sh -c 'make sync && make reset' 17 | 18 | sync: 19 | rshell --port $(PORT) --timing --buffer-size=32 rsync --mirror --verbose ./firmware /flash 20 | 21 | shell: 22 | rshell --port $(PORT) --timing --buffer-size=32 23 | 24 | repl: 25 | screen $(PORT) 115200 26 | 27 | reset: 28 | rshell --port $(PORT) --timing --buffer-size=32 repl "~ import machine ~ machine.reset()~" 29 | 30 | deps: 31 | @echo "Nothing to install..." 32 | 33 | .PHONY: help watch sync shell repl reset deps 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MicroPython Kitchen Sink for M5Stack 2 | 3 | ![M5Stack](https://appelsiini.net/img/m5-wires-1400.jpg) 4 | 5 | This repository contains few abstractions and helper libraries to help jumpstarting a MicroPython project with [M5Stack development kit](http://www.m5stack.com/). All development is done using [Loboris fork](https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo) of MicroPython. Everything is still evolving. Code should be considered alpha quality. BC breaks will happen. 6 | 7 | Use `make sync` to upload files to the board. Not that you must have [rshell](https://github.com/dhylands/rshell) installed. After uploading `make repl` accesses the serial repl. 8 | 9 | ```shell 10 | $ sudo pip3 install rshell 11 | $ make sync 12 | $ make repl 13 | ``` 14 | 15 | The file `main.py` will contain the kitchen sink example. Helper libraries and absractions are in `lib` folder. 16 | 17 | ## Display 18 | 19 | Light weight wrapper for [Loboris TFT module](https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/display) which retains all the original api and properties but adds a few helper methods. See [Loboris wiki](https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/display) for documentation. 20 | 21 | ```python 22 | import m5stack 23 | tft = m5stack.Display() 24 | 25 | tft.text(tft.CENTER, 45, "`7MMM. ,MMF' \n") 26 | tft.text(tft.CENTER, tft.LASTY, " MMMb dPMM \n") 27 | tft.text(tft.CENTER, tft.LASTY, " M YM ,M MM M******\n") 28 | tft.text(tft.CENTER, tft.LASTY, " M Mb M' MM .M \n") 29 | tft.text(tft.CENTER, tft.LASTY, " M YM.P' MM |bMMAg. \n") 30 | tft.text(tft.CENTER, tft.LASTY, " M `YM' MM `Mb\n") 31 | tft.text(tft.CENTER, tft.LASTY, ".JML. `' .JMML. jM\n") 32 | tft.text(tft.CENTER, tft.LASTY, " (O) ,M9\n") 33 | tft.text(tft.CENTER, tft.LASTY, " 6mmm9 \n") 34 | tft.text(tft.CENTER, tft.LASTY, " \n") 35 | tft.text(tft.CENTER, tft.LASTY, "https://appelsiini.net/") 36 | ``` 37 | 38 | Helper methods for turning the display on and off. Under the hood this just sets `TFT_LED_PIN` high or low. 39 | 40 | ```python 41 | tft.backlight(False) 42 | tft.backlight(True) 43 | ``` 44 | 45 | ## Buttons 46 | 47 | Abstraction for the provided buttons using IRQ. Buttons are debounced and they can detect both pressing and relasing of the button. 48 | 49 | ```python 50 | a = m5stack.ButtonA( 51 | callback=lambda pin, pressed: print("Button A " + ("pressed" if pressed else "released")) 52 | ) 53 | 54 | b = m5stack.ButtonB( 55 | callback=lambda pin, pressed: print("Button B " + ("pressed" if pressed else "released")) 56 | ) 57 | 58 | c = m5stack.ButtonC(callback=button_handler) 59 | 60 | def button_handler(pin, pressed): 61 | if pressed is True: 62 | print("Button C pressed") 63 | else: 64 | print("Button C released") 65 | ``` 66 | 67 | ## Speaker 68 | 69 | Basic support for playing tones in the builtin speaker. 70 | 71 | ```python 72 | import m5stack 73 | 74 | m5stack.tone(2200, duration=10, volume=1) 75 | ``` 76 | 77 | ## Battery 78 | 79 | Basic support getting battery charge level. Value is returned as percentage in steps of 25 ie. 0, 25, 50, 75 and 100. 80 | 81 | ```python 82 | from machine import I2C 83 | from ip5306 import IP5306 84 | 85 | i2c = I2C(scl=Pin(22), sda=Pin(21)) 86 | battery = IP5306(i2c) 87 | print(str(battery.level) + "%")) 88 | ``` 89 | 90 | ## License 91 | 92 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 93 | -------------------------------------------------------------------------------- /firmware/boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | 3 | import sys 4 | sys.path[1] = "/flash/lib" -------------------------------------------------------------------------------- /firmware/lib/input.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of MicroPython M5Stack package 3 | # Copyright (c) 2017 Mika Tuupola 4 | # 5 | # Licensed under the MIT license: 6 | # http://www.opensource.org/licenses/mit-license.php 7 | # 8 | # Project home: 9 | # https://github.com/tuupola/micropython-m5stack 10 | # 11 | 12 | """ 13 | Handle io pin as a digital input. 14 | """ 15 | 16 | # pylint: disable=import-error 17 | import machine 18 | from machine import Pin 19 | # pylint: enable=import-error 20 | 21 | class DigitalInput(object): 22 | 23 | def __init__(self, pin, callback=None, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING): 24 | self._register = bytearray([0b11111111]) 25 | self._user_callback = callback 26 | self._current_state = False 27 | self._previous_state = False 28 | self._pin = pin 29 | self._pin.init(self._pin.IN, trigger=trigger, handler=self._callback) 30 | 31 | def _callback(self, pin): 32 | irq_state = machine.disable_irq() 33 | 34 | while True: 35 | self._register[0] <<= 1 36 | self._register[0] |= pin.value() 37 | 38 | #print("{:08b}".format(self._register[0])) 39 | # All bits set, button has been released for 8 loops 40 | if self._register[0] is 0b11111111: 41 | self._current_state = False 42 | break 43 | 44 | # All bits unset, button has been pressed for 8 loops 45 | if self._register[0] is 0b00000000: 46 | self._current_state = True 47 | break 48 | 49 | # Handle edge case of two consequent rising interrupts 50 | if self._current_state is not self._previous_state: 51 | self._previous_state = self._current_state 52 | self._user_callback(self._pin, self._current_state) 53 | 54 | machine.enable_irq(irq_state) 55 | -------------------------------------------------------------------------------- /firmware/lib/ip5306.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Mika Tuupola 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copied of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | """ 22 | MicroPython I2C driver for IP5306 multi-function power management SOC 23 | """ 24 | 25 | __version__ = "0.1.0-dev" 26 | 27 | # pylint: disable=import-error 28 | import ustruct 29 | from machine import I2C 30 | from micropython import const 31 | # pylint: enable=import-error 32 | 33 | # _SYS_CTL0 = const(0x00) 34 | # _SYS_CTL1 = const(0x01) 35 | # _SYS_CTL2 = const(0x02) 36 | # _CHARGER_CTL0 = const(0x20) 37 | # _CHARGER_CTL1 = const(0x21) 38 | # _CHARGER_CTL2 = const(0x22) 39 | # _CHARGER_CTL3 = const(0x23) 40 | # _REG_READ0 = const(0x70) 41 | # _REG_READ1 = const(0x71) 42 | # _REG_READ2 = const(0x72) 43 | # _REG_READ3 = const(0x77) 44 | 45 | _REG_READ4 = const(0x78) # Cannot find documentation? 46 | _BATTERY_75_BIT = const(0b10000000) 47 | _BATTERY_50_BIT = const(0b01000000) 48 | _BATTERY_25_BIT = const(0b00100000) 49 | _BATTERY_0_BIT = const(0b00010000) 50 | 51 | # _CHARGE_EN = const(0b00000100) # _REG_READ0 52 | # _CHARGE_FULL = const(0b00000100) # _REG_READ1 53 | # _LIGHT_LOAD = const(0b00000010) # _REG_READ2 54 | 55 | class IP5306: 56 | """Class which provides interface IP5306 multi-function power management SOC.""" 57 | def __init__(self, i2c, address=0x75): 58 | self.i2c = i2c 59 | self.address = address 60 | 61 | if self.address not in i2c.scan(): 62 | raise RuntimeError("IP5306 not found in I2C bus") 63 | 64 | @property 65 | def level(self): 66 | """ 67 | Battery level in percentage. 68 | """ 69 | level = self._register_char(_REG_READ4) 70 | 71 | if level & _BATTERY_0_BIT: 72 | return 0 73 | elif level & _BATTERY_25_BIT: 74 | return 25 75 | elif level & _BATTERY_50_BIT: 76 | return 50 77 | elif level & _BATTERY_75_BIT: 78 | return 75 79 | 80 | return 100 81 | 82 | def _register_char(self, register, value=None, buf=bytearray(1)): 83 | if value is None: 84 | self.i2c.readfrom_mem_into(self.address, register, buf) 85 | return buf[0] 86 | 87 | ustruct.pack_into(" Button A pressed. " 54 | ) 55 | m5stack.tone(1800, duration=10, volume=1) 56 | else: 57 | tft.text( 58 | tft.CENTER, BUTTON_Y, "> Button A released. " 59 | ) 60 | m5stack.tone(1300, duration=10, volume=1) 61 | 62 | def button_handler_b(pin, pressed): 63 | if pressed is True: 64 | tft.text( 65 | tft.CENTER, BUTTON_Y, "> Button B pressed. " 66 | ) 67 | m5stack.tone(2000, duration=10, volume=1) 68 | else: 69 | tft.text( 70 | tft.CENTER, BUTTON_Y, "> Button B released. " 71 | ) 72 | m5stack.tone(1500, duration=10, volume=1) 73 | 74 | def button_handler_c(pin, pressed): 75 | if pressed is True: 76 | tft.text( 77 | tft.CENTER, BUTTON_Y, "> Button C pressed. " 78 | ) 79 | m5stack.tone(2200, duration=10, volume=1) 80 | else: 81 | tft.text( 82 | tft.CENTER, BUTTON_Y, "> Button C released. " 83 | ) 84 | m5stack.tone(1800, duration=10, volume=1) 85 | 86 | a = m5stack.ButtonA(callback=button_handler_a) 87 | b = m5stack.ButtonB(callback=button_handler_b) 88 | c = m5stack.ButtonC(callback=button_handler_c) 89 | 90 | def battery_level(timer): 91 | tft.text(tft.RIGHT, 2, str(battery.level) + "%") 92 | 93 | timer_0 = Timer(0) 94 | timer_0.init(period=5000, mode=Timer.PERIODIC, callback=battery_level) 95 | --------------------------------------------------------------------------------