├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md └── lib ├── axp192.py ├── bmp280.py ├── colors.py ├── dht12.py ├── pcf8563.py ├── sgp30.py └── st7789.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Python bytecode 2 | *.pyc 3 | *.mpy 4 | 5 | # VSCode 6 | .pylintrc 7 | .vscode -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Code of Conduct 3 | 4 | As contributors and maintainers of this project, and in the interest of 5 | fostering an open and welcoming community, we pledge to respect all people who 6 | contribute through reporting issues, posting feature requests, updating 7 | documentation, submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project a harassment-free 10 | experience for everyone, regardless of level of experience, gender, gender 11 | identity and expression, sexual orientation, disability, personal appearance, 12 | body size, race, ethnicity, age, religion, or nationality. 13 | 14 | Examples of unacceptable behavior by participants include: 15 | 16 | * The use of sexualized language or imagery 17 | * Personal attacks 18 | * Trolling or insulting/derogatory comments 19 | * Public or private harassment 20 | * Publishing other's private information, such as physical or electronic 21 | addresses, without explicit permission 22 | * Other unethical or unprofessional conduct 23 | 24 | Project maintainers have the right and responsibility to remove, edit, or 25 | reject comments, commits, code, wiki edits, issues, and other contributions 26 | that are not aligned to this Code of Conduct, or to ban temporarily or 27 | permanently any contributor for other behaviors that they deem inappropriate, 28 | threatening, offensive, or harmful. 29 | 30 | By adopting this Code of Conduct, project maintainers commit themselves to 31 | fairly and consistently applying these principles to every aspect of managing 32 | this project. Project maintainers who do not follow or enforce the Code of 33 | Conduct may be permanently removed from the project team. 34 | 35 | This Code of Conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 39 | reported by contacting a project maintainer at gandro@gmx.net. All 40 | complaints will be reviewed and investigated and will result in a response that 41 | is deemed necessary and appropriate to the circumstances. Maintainers are 42 | obligated to maintain confidentiality with regard to the reporter of an 43 | incident. 44 | 45 | 46 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 47 | version 1.3.0, available at https://www.contributor-covenant.org/version/1/3/0/code-of-conduct.html 48 | 49 | [homepage]: https://www.contributor-covenant.org 50 | 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sebastian Wicki 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropython-m5stickc-plus 2 | 3 | This repository contains drivers for various components available on the 4 | [M5StickC Plus](https://github.com/m5stack/M5StickC-Plus) platform. 5 | All drivers are written in pure [Micropython](https://micropython.org/) and are 6 | intended to be used with the generic Micropython build for ESP32-based boards. 7 | 8 | [M5StickCPlus]: https://docs.m5stack.com/#/en/core/m5stickc_plus 9 | 10 | ## Examples 11 | 12 | ```python 13 | import random 14 | import machine 15 | 16 | import axp192 17 | import colors 18 | import pcf8563 19 | import st7789 20 | 21 | # Set up AXP192 PMU 22 | i2c = machine.I2C(0, sda=machine.Pin(21), scl=machine.Pin(22), freq=400000) 23 | pmu = axp192.AXP192(i2c, board=axp192.M5StickCPlus) 24 | print("Battery Status: {:.2f} V".format(pmu.batt_voltage())) 25 | 26 | # Set up BM8563 RTC (clone of the NXP PCF8563) 27 | rtc = pcf8563.PCF8563(i2c) 28 | print("Current Date and Time: {}".format(rtc.datetime())) 29 | 30 | # Set up ST7789 TFT 31 | spi = machine.SPI(1, baudrate=20_000_000, polarity=1, 32 | sck=machine.Pin(13, machine.Pin.OUT), 33 | miso=machine.Pin(4, machine.Pin.IN), # NC 34 | mosi=machine.Pin(15, machine.Pin.OUT)) 35 | 36 | tft = st7789.ST7789(spi, 135, 240, 37 | reset=machine.Pin(18, machine.Pin.OUT), 38 | dc=machine.Pin(23, machine.Pin.OUT), 39 | cs=machine.Pin(5, machine.Pin.OUT), 40 | buf=bytearray(2048)) 41 | 42 | c = colors.rgb565( 43 | random.getrandbits(8), 44 | random.getrandbits(8), 45 | random.getrandbits(8), 46 | ) 47 | tft.fill(c) 48 | tft.text("Hello World", 10, 30, colors.WHITE, c) 49 | ``` 50 | 51 | Using the [M5StickC ENV Hat](https://m5stack.com/products/m5stickc-env-hat): 52 | 53 | ```python 54 | import dht12 55 | 56 | # Hat I2C 57 | hat_i2c = machine.I2C(1, sda=machine.Pin(0), scl=machine.Pin(26), freq=400000) 58 | # DHT12 temperature and humidity sensor 59 | rht = dht12.DHT12(hat_i2c) 60 | temp, humidity = rht.measure() 61 | print("Temp/Humidity: {}°C/{}%".format(temp, humidity)) 62 | # BMP280 temperature and pressure sensor 63 | prt = bmp280.BMP280(hat_i2c, mode=bmp280.MODE_FORCED) 64 | temp, pressure = prt.measure() 65 | print("Temp/Pressure: {}°C/{}Pa".format(temp, pressure)) 66 | 67 | # Groove I2C 68 | gr_i2c = machine.I2C(sda=machine.Pin(32), scl=machine.Pin(33), freq=400000) 69 | # SGP30 indoor air quality sensor 70 | voc = sgp30.SGP30(gr_i2c) 71 | voc.set_absolute_humidity(sgp30.absolute_humidity(temp, humidity)) 72 | eco2, tvoc = voc.measure() 73 | print("eCO2/TVOC: {}ppm/{}ppb".format(eco2, tvoc)) 74 | ``` 75 | 76 | Some of the modules in this repository make use of [`micropython.const`](const) 77 | to optimize memory usage when deployed in [pre-compiled bytecode](mpy) form. 78 | 79 | [const]: http://docs.micropython.org/en/latest/library/micropython.html#micropython.const 80 | [mpy]: http://docs.micropython.org/en/latest/reference/mpyfiles.html 81 | 82 | ## Credits 83 | 84 | The following modules are derived from third-party sources: 85 | 86 | - `st7789`: Directly based on 87 | [devbis/st7789py_mpy](https://github.com/devbis/st7789py_mpy) 88 | by *Ivan Belokobylskiy* (License: MIT) 89 | - `pcf8563`: Micropython port of 90 | [tuupola/pcf8563](https://github.com/tuupola/pcf8563) 91 | by *Mika Tuupola* (License: MIT) 92 | 93 | ## Contributing 94 | 95 | Contributions are welcome! Please read and follow the 96 | [Code of Conduct](CODE_OF_CONDUCT.md) and make sure to acknowledge the 97 | [Developer Certificate of Origin](https://developercertificate.org/) when 98 | contributing. 99 | -------------------------------------------------------------------------------- /lib/axp192.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Sebastian Wicki 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 deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies 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 all 11 | # 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 | Driver for the AXP192 power management unit. 22 | """ 23 | 24 | from micropython import const 25 | 26 | _AXP192_I2C_DEFAULT_ADDR = const(0x34) 27 | 28 | _AXP192_POWER_STATUS = const(0x00) 29 | _AXP192_MODE_CHARGING_STATUS = const(0x01) 30 | 31 | _AXP192_EXTEN_DCDC2_CTRL = const(0x10) 32 | _AXP192_EXTEN_DCDC2_CTRL_EXTEN = const(0b0000_0100) 33 | _AXP192_EXTEN_DCDC2_CTRL_DCDC2 = const(0b0000_0001) 34 | 35 | _AXP192_DCDC13_LDO23_CTRL = const(0x12) 36 | _AXP192_DCDC13_LDO23_CTRL_LDO3 = const(0b0000_1000) 37 | _AXP192_DCDC13_LDO23_CTRL_LDO2 = const(0b0000_0100) 38 | _AXP192_DCDC13_LDO23_CTRL_DCDC3 = const(0b0000_0010) 39 | _AXP192_DCDC13_LDO23_CTRL_DCDC1 = const(0b0000_0001) 40 | 41 | _AXP192_LDO23_OUT_VOLTAGE = const(0x28) 42 | _AXP192_LDO23_OUT_VOLTAGE_LDO2_3_0V = const(0b1100_0000) 43 | _AXP192_LDO23_OUT_VOLTAGE_LDO2_MASK = const(0b1111_0000) 44 | _AXP192_LDO23_OUT_VOLTAGE_LDO3_3_0V = const(0b0000_1100) 45 | _AXP192_LDO23_OUT_VOLTAGE_LDO3_MASK = const(0b0000_1111) 46 | 47 | _AXP192_VBUS_IPSOUT = const(0x30) 48 | _AXP192_VBUS_IPSOUT_IGNORE_VBUSEN = const(0b1000_0000) 49 | _AXP192_VBUS_IPSOUT_VHOLD_LIMIT = const(0b0100_0000) 50 | _AXP192_VBUS_IPSOUT_VHOLD_VOLTAGE_4_4V = const(0b0010_0000) 51 | _AXP192_VBUS_IPSOUT_VHOLD_VOLTAGE_MASK = const(0b0011_1000) 52 | _AXP192_VBUS_IPSOUT_VBUS_LIMIT_CURRENT = const(0b0000_0010) 53 | _AXP192_VBUS_IPSOUT_VBUS_LIMIT_CURRENT_500mA = const(0b0000_0001) 54 | _AXP192_VBUS_IPSOUT_VBUS_LIMIT_CURRENT_100mA = const(0b0000_0000) 55 | 56 | _AXP192_POWER_OFF_VOLTAGE = const(0x31) 57 | _AXP192_POWER_OFF_VOLTAGE_2_6V = const(0b0000) 58 | _AXP192_POWER_OFF_VOLTAGE_2_7V = const(0b0001) 59 | _AXP192_POWER_OFF_VOLTAGE_2_8V = const(0b0010) 60 | _AXP192_POWER_OFF_VOLTAGE_2_9V = const(0b0011) 61 | _AXP192_POWER_OFF_VOLTAGE_3_0V = const(0b0100) 62 | _AXP192_POWER_OFF_VOLTAGE_3_1V = const(0b0101) 63 | _AXP192_POWER_OFF_VOLTAGE_3_2V = const(0b0110) 64 | _AXP192_POWER_OFF_VOLTAGE_3_3V = const(0b0111) 65 | _AXP192_POWER_OFF_VOLTAGE_MASK = const(0b0111) 66 | 67 | _AXP192_POWER_OFF_BATT_CHGLED_CTRL = const(0x32) 68 | _AXP192_POWER_OFF_BATT_CHGLED_CTRL_OFF = const(0b1000_0000) 69 | 70 | _AXP192_CHARGING_CTRL1 = const(0x33) 71 | _AXP192_CHARGING_CTRL1_ENABLE = const(0b1000_0000) 72 | _AXP192_CHARGING_CTRL1_VOLTAGE_4_36V = const(0b0110_0000) 73 | _AXP192_CHARGING_CTRL1_VOLTAGE_4_20V = const(0b0100_0000) 74 | _AXP192_CHARGING_CTRL1_VOLTAGE_4_15V = const(0b0010_0000) 75 | _AXP192_CHARGING_CTRL1_VOLTAGE_4_10V = const(0b0000_0000) 76 | _AXP192_CHARGING_CTRL1_VOLTAGE_MASK = const(0b0110_0000) 77 | _AXP192_CHARGING_CTRL1_CHARGING_THRESH_15PERC = const(0b0001_0000) 78 | _AXP192_CHARGING_CTRL1_CHARGING_THRESH_10PERC = const(0b0000_0000) 79 | _AXP192_CHARGING_CTRL1_CHARGING_THRESH_MASK = const(0b0001_0000) 80 | _AXP192_CHARGING_CTRL1_CURRENT_100mA = const(0b0000_0000) 81 | _AXP192_CHARGING_CTRL1_CURRENT_MASK = const(0b0000_1111) 82 | 83 | _AXP192_CHARGING_CTRL2 = const(0x34) 84 | 85 | _AXP192_BACKUP_BATT = const(0x35) 86 | _AXP192_BACKUP_BATT_CHARGING_ENABLE = const(0b1000_0000) 87 | _AXP192_BACKUP_BATT_CHARGING_VOLTAGE_2_5V = const(0b0110_0000) 88 | _AXP192_BACKUP_BATT_CHARGING_VOLTAGE_3_0V = const(0b0010_0000) 89 | _AXP192_BACKUP_BATT_CHARGING_VOLTAGE_3_1V = const(0b0000_0000) 90 | _AXP192_BACKUP_BATT_CHARGING_VOLTAGE_MASK = const(0b0110_0000) 91 | _AXP192_BACKUP_BATT_CHARGING_CURRENT_400uA = const(0b0000_0011) 92 | _AXP192_BACKUP_BATT_CHARGING_CURRENT_200uA = const(0b0000_0010) 93 | _AXP192_BACKUP_BATT_CHARGING_CURRENT_100uA = const(0b0000_0001) 94 | _AXP192_BACKUP_BATT_CHARGING_CURRENT_50uA = const(0b0000_0000) 95 | _AXP192_BACKUP_BATT_CHARGING_CURRENT_MASK = const(0b0000_0011) 96 | 97 | _AXP192_PEK = const(0x36) 98 | _AXP192_PEK_SHORT_PRESS_1S = const(0b1100_0000) 99 | _AXP192_PEK_SHORT_PRESS_512mS = const(0b1000_0000) 100 | _AXP192_PEK_SHORT_PRESS_256mS = const(0b0100_0000) 101 | _AXP192_PEK_SHORT_PRESS_128mS = const(0b0000_0000) 102 | _AXP192_PEK_SHORT_PRESS_MASK = const(0b1100_0000) 103 | _AXP192_PEK_LONG_PRESS_2_5S = const(0b0011_0000) 104 | _AXP192_PEK_LONG_PRESS_2_0S = const(0b0010_0000) 105 | _AXP192_PEK_LONG_PRESS_1_5S = const(0b0001_0000) 106 | _AXP192_PEK_LONG_PRESS_1_0S = const(0b0000_0000) 107 | _AXP192_PEK_LONG_PRESS_MASK = const(0b0011_0000) 108 | _AXP192_PEK_LONG_PRESS_POWER_OFF = const(0b0000_1000) 109 | _AXP192_PEK_PWROK_DELAY_64mS = const(0b0000_0100) 110 | _AXP192_PEK_PWROK_DELAY_32mS = const(0b0000_0000) 111 | _AXP192_PEK_PWROK_DELAY_MASK = const(0b0000_0100) 112 | _AXP192_PEK_POWER_OFF_TIME_12S = const(0b0000_0011) 113 | _AXP192_PEK_POWER_OFF_TIME_8S = const(0b0000_0010) 114 | _AXP192_PEK_POWER_OFF_TIME_6S = const(0b0000_0001) 115 | _AXP192_PEK_POWER_OFF_TIME_4S = const(0b0000_0000) 116 | _AXP192_PEK_POWER_OFF_TIME_MASK = const(0b0000_0011) 117 | 118 | _AXP192_BATT_TEMP_LOW_THRESH = const(0x38) 119 | _AXP192_BATT_TEMP_HIGH_THRESH = const(0x39) 120 | _AXP192_BATT_TEMP_HIGH_THRESH_DEFAULT = const(0b1111_1100) 121 | 122 | _AXP192_IRQ_1_ENABLE = const(0x40) 123 | _AXP192_IRQ_2_ENABLE = const(0x41) 124 | _AXP192_IRQ_3_ENABLE = const(0x42) 125 | _AXP192_IRQ_4_ENABLE = const(0x43) 126 | _AXP192_IRQ_5_ENABLE = const(0x4a) 127 | 128 | _AXP192_IRQ_1_STATUS = const(0x44) 129 | _AXP192_IRQ_2_STATUS = const(0x45) 130 | _AXP192_IRQ_3_STATUS = const(0x46) 131 | _AXP192_IRQ_4_STATUS = const(0x47) 132 | _AXP192_IRQ_5_STATUS = const(0x4d) 133 | 134 | _AXP192_IRQ_3_PEK_SHORT_PRESS = const(0b0000_0010) 135 | _AXP192_IRQ_3_PEK_LONG_PRESS = const(0b0000_0001) 136 | 137 | _AXP192_ADC_ACIN_VOLTAGE_H = const(0x56) 138 | _AXP192_ADC_ACIN_VOLTAGE_L = const(0x57) 139 | _AXP192_ADC_ACIN_CURRENT_H = const(0x58) 140 | _AXP192_ADC_ACIN_CURRENT_L = const(0x59) 141 | _AXP192_ADC_VBUS_VOLTAGE_H = const(0x5a) 142 | _AXP192_ADC_VBUS_VOLTAGE_L = const(0x5b) 143 | _AXP192_ADC_VBUS_CURRENT_H = const(0x5c) 144 | _AXP192_ADC_VBUS_CURRENT_L = const(0x5d) 145 | _AXP192_ADC_INTERNAL_TEMP_H = const(0x5e) 146 | _AXP192_ADC_INTERNAL_TEMP_L = const(0x5f) 147 | 148 | _AXP192_ADC_BATT_VOLTAGE_H = const(0x78) 149 | _AXP192_ADC_BATT_VOLTAGE_L = const(0x79) 150 | 151 | _AXP192_ADC_BATT_POWER_H = const(0x70) 152 | _AXP192_ADC_BATT_POWER_M = const(0x71) 153 | _AXP192_ADC_BATT_POWER_L = const(0x72) 154 | 155 | _AXP192_ADC_BATT_CHARGE_CURRENT_H = const(0x7a) 156 | _AXP192_ADC_BATT_CHARGE_CURRENT_L = const(0x7b) 157 | _AXP192_ADC_BATT_DISCHARGE_CURRENT_H = const(0x7c) 158 | _AXP192_ADC_BATT_DISCHARGE_CURRENT_L = const(0x7d) 159 | _AXP192_ADC_APS_VOLTAGE_H = const(0x7e) 160 | _AXP192_ADC_APS_VOLTAGE_L = const(0x7f) 161 | 162 | _AXP192_ADC_ENABLE_1 = const(0x82) 163 | _AXP192_ADC_ENABLE_1_BATT_VOL = const(0b1000_0000) 164 | _AXP192_ADC_ENABLE_1_BATT_CUR = const(0b0100_0000) 165 | _AXP192_ADC_ENABLE_1_ACIN_VOL = const(0b0010_0000) 166 | _AXP192_ADC_ENABLE_1_ACIN_CUR = const(0b0001_0000) 167 | _AXP192_ADC_ENABLE_1_VBUS_VOL = const(0b0000_1000) 168 | _AXP192_ADC_ENABLE_1_VBUS_CUR = const(0b0000_0100) 169 | _AXP192_ADC_ENABLE_1_APS_VOL = const(0b0000_0010) 170 | _AXP192_ADC_ENABLE_1_TS_PIN = const(0b0000_0001) 171 | 172 | _AXP192_ADC_ENABLE_2 = const(0x83) 173 | _AXP192_ADC_ENABLE_2_TEMP_MON = const(0b1000_0000) 174 | _AXP192_ADC_ENABLE_2_GPIO0 = const(0b0000_1000) 175 | _AXP192_ADC_ENABLE_2_GPIO1 = const(0b0000_0100) 176 | _AXP192_ADC_ENABLE_2_GPIO2 = const(0b0000_0010) 177 | _AXP192_ADC_ENABLE_2_GPIO3 = const(0b0000_0001) 178 | 179 | _AXP192_ADC_TS = const(0x84) 180 | _AXP192_ADC_TS_SAMPLE_200HZ = const(0b1100_0000) 181 | _AXP192_ADC_TS_SAMPLE_100HZ = const(0b1000_0000) 182 | _AXP192_ADC_TS_SAMPLE_50HZ = const(0b0100_0000) 183 | _AXP192_ADC_TS_SAMPLE_25HZ = const(0b0000_0000) 184 | _AXP192_ADC_TS_SAMPLE_MASK = const(0b1100_0000) 185 | _AXP192_ADC_TS_OUT_CUR_80uA = const(0b0011_0000) 186 | _AXP192_ADC_TS_OUT_CUR_60uA = const(0b0010_0000) 187 | _AXP192_ADC_TS_OUT_CUR_40uA = const(0b0001_0000) 188 | _AXP192_ADC_TS_OUT_CUR_20uA = const(0b0000_0000) 189 | _AXP192_ADC_TS_OUT_CUR_MASK = const(0b0011_0000) 190 | _AXP192_ADC_TS_PIN_TEMP_MON = const(0b0000_0000) 191 | _AXP192_ADC_TS_PIN_EXTERN_ADC = const(0b0000_0100) 192 | _AXP192_ADC_TS_PIN_OUT_ALWAYS = const(0b0000_0011) 193 | _AXP192_ADC_TS_PIN_OUT_SAVE_ENG = const(0b0000_0010) 194 | _AXP192_ADC_TS_PIN_OUT_CHG = const(0b0000_0001) 195 | _AXP192_ADC_TS_PIN_OUT_DIS = const(0b0000_0000) 196 | _AXP192_ADC_TS_PIN_OUT_MASK = const(0b0000_0011) 197 | 198 | _AXP192_GPIO0_FUNCTION = const(0x90) 199 | _AXP192_GPIO0_FUNCTION_FLOATING = const(0b0000_0111) 200 | _AXP192_GPIO0_FUNCTION_LOW_OUTPUT = const(0b0000_0101) 201 | _AXP192_GPIO0_FUNCTION_ADC_INPUT = const(0b0000_0100) 202 | _AXP192_GPIO0_FUNCTION_LDO_OUTPUT = const(0b0000_0010) 203 | _AXP192_GPIO0_FUNCTION_GENERAL_INPUT = const(0b0000_0001) 204 | _AXP192_GPIO0_FUNCTION_OPEN_DRAIN_OUTPUT = const(0b0000_0000) 205 | 206 | _AXP192_GPIO0_LDO_VOLTAGE = const(0x91) 207 | _AXP192_GPIO0_LDO_VOLTAGE_3_3V = const(0b1111_0000) 208 | _AXP192_GPIO0_LDO_VOLTAGE_2_8V = const(0b1010_0000) 209 | _AXP192_GPIO0_LDO_VOLTAGE_1_8V = const(0b0000_0000) 210 | 211 | 212 | class M5StickCPlus: 213 | @staticmethod 214 | def on_init(device): 215 | # Set LDO2 and LDO3 to 3.0V 216 | device.write(_AXP192_LDO23_OUT_VOLTAGE, 217 | _AXP192_LDO23_OUT_VOLTAGE_LDO2_3_0V | 218 | _AXP192_LDO23_OUT_VOLTAGE_LDO3_3_0V) 219 | 220 | # Enable EXTEN, Disable DCDC2 221 | device.write(_AXP192_EXTEN_DCDC2_CTRL, 222 | _AXP192_EXTEN_DCDC2_CTRL_EXTEN) 223 | 224 | # Enable LDO2, LDO3, DCDC1 225 | val = device.read(_AXP192_DCDC13_LDO23_CTRL) 226 | device.write(_AXP192_DCDC13_LDO23_CTRL, val | 227 | _AXP192_DCDC13_LDO23_CTRL_LDO2 | 228 | _AXP192_DCDC13_LDO23_CTRL_LDO3 | 229 | _AXP192_DCDC13_LDO23_CTRL_DCDC1) 230 | 231 | # ADC Sample Rate 200Hz, TS Pin 80uA, Temp Mon, Energy Saving 232 | device.write(_AXP192_ADC_TS, 233 | _AXP192_ADC_TS_SAMPLE_200HZ | 234 | _AXP192_ADC_TS_OUT_CUR_80uA | 235 | _AXP192_ADC_TS_PIN_TEMP_MON | 236 | _AXP192_ADC_TS_PIN_OUT_SAVE_ENG) 237 | 238 | # ADC Enable Battery, VBus, ACIn, APS, TS 239 | device.write(_AXP192_ADC_ENABLE_1, 240 | _AXP192_ADC_ENABLE_1_BATT_VOL | 241 | _AXP192_ADC_ENABLE_1_BATT_CUR | 242 | _AXP192_ADC_ENABLE_1_ACIN_VOL | 243 | _AXP192_ADC_ENABLE_1_ACIN_CUR | 244 | _AXP192_ADC_ENABLE_1_VBUS_VOL | 245 | _AXP192_ADC_ENABLE_1_VBUS_CUR | 246 | _AXP192_ADC_ENABLE_1_APS_VOL | 247 | _AXP192_ADC_ENABLE_1_TS_PIN) 248 | 249 | # VBus limit 500mA, Vbus hold at 4.4V 250 | device.write(_AXP192_VBUS_IPSOUT, 251 | _AXP192_VBUS_IPSOUT_VHOLD_LIMIT | 252 | _AXP192_VBUS_IPSOUT_VHOLD_VOLTAGE_4_4V | 253 | _AXP192_VBUS_IPSOUT_VBUS_LIMIT_CURRENT | 254 | _AXP192_VBUS_IPSOUT_VBUS_LIMIT_CURRENT_500mA) 255 | 256 | # Automatically power off at 3.0V 257 | device.write(_AXP192_POWER_OFF_VOLTAGE, 258 | _AXP192_POWER_OFF_VOLTAGE_3_0V) 259 | 260 | # Battery charging voltage 4.2V, current 100mA 261 | device.write(_AXP192_CHARGING_CTRL1, 262 | _AXP192_CHARGING_CTRL1_ENABLE | 263 | _AXP192_CHARGING_CTRL1_VOLTAGE_4_20V | 264 | _AXP192_CHARGING_CTRL1_CHARGING_THRESH_10PERC | 265 | _AXP192_CHARGING_CTRL1_CURRENT_100mA) 266 | 267 | # PEK Short Press 128ms, Long Press 1.5s, Power Off 4s 268 | device.write(_AXP192_PEK, 269 | _AXP192_PEK_SHORT_PRESS_128mS | 270 | _AXP192_PEK_LONG_PRESS_1_5S | 271 | _AXP192_PEK_LONG_PRESS_POWER_OFF | 272 | _AXP192_PEK_PWROK_DELAY_64mS | 273 | _AXP192_PEK_POWER_OFF_TIME_4S) 274 | 275 | # Ensure high temp threshold default value 276 | device.write(_AXP192_BATT_TEMP_HIGH_THRESH, 277 | _AXP192_BATT_TEMP_HIGH_THRESH_DEFAULT) 278 | 279 | # RTC Backup Battery Enable at 3.0V, charging with 200uA 280 | device.write(_AXP192_BACKUP_BATT, 281 | _AXP192_BACKUP_BATT_CHARGING_ENABLE | 282 | _AXP192_BACKUP_BATT_CHARGING_VOLTAGE_3_0V | 283 | _AXP192_BACKUP_BATT_CHARGING_CURRENT_200uA) 284 | 285 | # Set GPIO0 as LDOIO0 at 3.3V 286 | device.write(_AXP192_GPIO0_LDO_VOLTAGE, 287 | _AXP192_GPIO0_LDO_VOLTAGE_3_3V) 288 | device.write(_AXP192_GPIO0_FUNCTION, 289 | _AXP192_GPIO0_FUNCTION_LDO_OUTPUT) 290 | 291 | 292 | class AXP192: 293 | def __init__(self, i2c, addr=_AXP192_I2C_DEFAULT_ADDR, board=None): 294 | self.i2c = i2c 295 | self.addr = addr 296 | self.buf = bytearray(1) 297 | if self.read(_AXP192_POWER_STATUS) == 0xff: 298 | raise ValueError("device not found") 299 | if board is not None: 300 | board.on_init(self) 301 | 302 | def read(self, regaddr): 303 | self.i2c.readfrom_mem_into(self.addr, regaddr, self.buf) 304 | return self.buf[0] 305 | 306 | def write(self, regaddr, val): 307 | self.buf[0] = val 308 | self.i2c.writeto_mem(self.addr, regaddr, self.buf) 309 | 310 | def batt_voltage(self): 311 | val = self.read(_AXP192_ADC_BATT_VOLTAGE_H) << 4 312 | val |= self.read(_AXP192_ADC_BATT_VOLTAGE_L) 313 | return val * 1.1 / 1000 # 1.1mV per LSB 314 | 315 | def batt_power(self): 316 | val = self.read(_AXP192_ADC_BATT_POWER_H) << 16 317 | val |= self.read(_AXP192_ADC_BATT_POWER_M) << 8 318 | val |= self.read(_AXP192_ADC_BATT_POWER_L) 319 | return val * 1.1 * 0.5 / 1000 # 1.1mV * 0.5mA per LSB 320 | 321 | def batt_charge_current(self): 322 | val = self.read(_AXP192_ADC_BATT_CHARGE_CURRENT_H) << 5 323 | val |= self.read(_AXP192_ADC_BATT_CHARGE_CURRENT_L) 324 | return val * 0.5 / 1000 # 0.5mA per LSB 325 | 326 | def batt_discharge_current(self): 327 | val = self.read(_AXP192_ADC_BATT_DISCHARGE_CURRENT_H) << 5 328 | val |= self.read(_AXP192_ADC_BATT_DISCHARGE_CURRENT_L) 329 | return val * 0.5 / 1000 # 0.5mA per LSB 330 | 331 | def acin_voltage(self): 332 | val = self.read(_AXP192_ADC_ACIN_VOLTAGE_H) << 4 333 | val |= self.read(_AXP192_ADC_ACIN_VOLTAGE_L) 334 | return val * 1.7 / 1000 # 1.7mV per LSB 335 | 336 | def acin_current(self): 337 | val = self.read(_AXP192_ADC_ACIN_CURRENT_H) << 4 338 | val |= self.read(_AXP192_ADC_ACIN_CURRENT_L) 339 | return val * 0.625 / 1000 # 0.625mA per LSB 340 | 341 | def vbus_voltage(self): 342 | val = self.read(_AXP192_ADC_VBUS_VOLTAGE_H) << 4 343 | val |= self.read(_AXP192_ADC_VBUS_VOLTAGE_L) 344 | return val * 1.7 / 1000 # 1.7mV per LSB 345 | 346 | def vbus_current(self): 347 | val = self.read(_AXP192_ADC_VBUS_CURRENT_H) << 4 348 | val |= self.read(_AXP192_ADC_VBUS_CURRENT_L) 349 | return val * 0.375 / 1000 # 0.375mA per LSB 350 | 351 | def aps_voltage(self): 352 | val = self.read(_AXP192_ADC_APS_VOLTAGE_H) << 4 353 | val |= self.read(_AXP192_ADC_APS_VOLTAGE_L) 354 | return val * 1.4 / 1000 # 1.4mV per LSB 355 | 356 | def internal_temp(self): 357 | val = self.read(_AXP192_ADC_INTERNAL_TEMP_H) << 4 358 | val |= self.read(_AXP192_ADC_INTERNAL_TEMP_L) 359 | return val * 0.1 - 144.7 # 0.1C per LSB, offset 144.7C 360 | 361 | def pek_button(self, long=False): 362 | val = self.read(_AXP192_IRQ_3_STATUS) 363 | val &= _AXP192_IRQ_3_PEK_SHORT_PRESS | _AXP192_IRQ_3_PEK_LONG_PRESS 364 | self.write(_AXP192_IRQ_3_STATUS, val) # clear bits 365 | if long: 366 | val &= _AXP192_IRQ_3_PEK_LONG_PRESS 367 | return bool(val) 368 | 369 | def power_off(self): 370 | val = self.read(_AXP192_POWER_OFF_BATT_CHGLED_CTRL) 371 | val |= _AXP192_POWER_OFF_BATT_CHGLED_CTRL_OFF 372 | self.write(_AXP192_POWER_OFF_BATT_CHGLED_CTRL, val) 373 | -------------------------------------------------------------------------------- /lib/bmp280.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Sebastian Wicki 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 deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies 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 all 11 | # 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 | I2C-based driver for the BMP280 temperature and pressure sensor. 22 | """ 23 | from micropython import const 24 | from ustruct import unpack_from 25 | from utime import sleep_ms, sleep_us 26 | 27 | _BMP280_I2C_DEFAULT_ADDR = const(0x76) 28 | 29 | _BMP280_CHIP_ID = const(0xd0) 30 | _BMP280_CHIP_ID_VALUE = const(0x58) 31 | 32 | _BMP280_RESET = const(0xe0) 33 | _BMP280_RESET_VALUE = const(0xb6) 34 | 35 | _BMP280_STATUS = const(0xf3) 36 | _BMP280_CONTROL = const(0xf4) 37 | _BMP280_CONTROL_TEMP_SAMPLES_MASK = const(0b1110_0000) 38 | _BMP280_CONTROL_TEMP_SAMPLES_POS = const(5) 39 | _BMP280_CONTROL_PRESS_SAMPLES_MASK = const(0b0001_1100) 40 | _BMP280_CONTROL_PRESS_SAMPLES_POS = const(2) 41 | _BMP280_CONTROL_MODE_MASK = const(0b0000_0011) 42 | _BMP280_CONTROL_MODE_POS = const(0) 43 | 44 | _BMP280_CONFIG = const(0xf5) 45 | _BMP280_CONFIG_STANDBY_MASK = const(0b1110_0000) 46 | _BMP280_CONFIG_STANDBY_POS = const(5) 47 | _BMP280_CONFIG_IIR_MASK = const(0b0001_1100) 48 | _BMP280_CONFIG_IIR_POS = const(2) 49 | 50 | _BMP280_DATA = const(0xf7) 51 | _BMP280_CALIBRATION = const(0x88) 52 | 53 | _BMP280_DATA_LEN = const(6) 54 | _BMP280_CALIBRATION_LEN = const(24) 55 | 56 | _BMP280_DURATION_PER_SAMPLE_US = const(2000) 57 | _BMP280_DURATION_STARTUP_US = const(1000) 58 | _BMP280_DURATION_PRESS_STARTUP_US = const(500) 59 | 60 | MODE_NORMAL = const(0b11) 61 | MODE_FORCED = const(0b01) 62 | MODE_SLEEP = const(0b00) 63 | 64 | TEMP_SAMPLES_SKIP = const(0b000) 65 | TEMP_SAMPLES_1 = const(0b001) 66 | TEMP_SAMPLES_2 = const(0b010) 67 | TEMP_SAMPLES_4 = const(0b011) 68 | TEMP_SAMPLES_8 = const(0b100) 69 | TEMP_SAMPLES_16 = const(0b111) 70 | 71 | PRESS_SAMPLES_SKIP = const(0b000) 72 | PRESS_SAMPLES_1 = const(0b001) 73 | PRESS_SAMPLES_2 = const(0b010) 74 | PRESS_SAMPLES_4 = const(0b011) 75 | PRESS_SAMPLES_8 = const(0b100) 76 | PRESS_SAMPLES_16 = const(0b111) 77 | 78 | IIR_FILTER_OFF = const(0b0000_0000) 79 | IIR_FILTER_2 = const(0b0000_0100) 80 | IIR_FILTER_4 = const(0b0000_1000) 81 | IIR_FILTER_8 = const(0b0000_1100) 82 | IIR_FILTER_16 = const(0b0001_0000) 83 | 84 | STANDBY_0_5_MS = const(0b0000_0000) 85 | STANDBY_62_5_MS = const(0b0010_0000) 86 | STANDBY_125_MS = const(0b0100_0000) 87 | STANDBY_250_MS = const(0b0110_0000) 88 | STANDBY_500_MS = const(0b1000_0000) 89 | STANDBY_1000_MS = const(0b1010_0000) 90 | STANDBY_2000_MS = const(0b1100_0000) 91 | STANDBY_4000_MS = const(0b1110_0000) 92 | 93 | 94 | class BMP280: 95 | def __init__(self, i2c, addr=_BMP280_I2C_DEFAULT_ADDR, *, 96 | mode=MODE_NORMAL, 97 | press_samples=PRESS_SAMPLES_4, 98 | temp_samples=TEMP_SAMPLES_1, 99 | iir_filter=IIR_FILTER_16, 100 | standby_ms=STANDBY_0_5_MS): 101 | self.i2c = i2c 102 | self.addr = addr 103 | 104 | chipid = self.i2c.readfrom_mem(self.addr, _BMP280_CHIP_ID, 1) 105 | if chipid[0] != _BMP280_CHIP_ID_VALUE: 106 | raise ValueError("device not found") 107 | 108 | self.reset() 109 | sleep_ms(10) 110 | 111 | control = bytearray(1) 112 | control[0] |= ((temp_samples << _BMP280_CONTROL_TEMP_SAMPLES_POS) 113 | & _BMP280_CONTROL_TEMP_SAMPLES_MASK) 114 | control[0] |= ((press_samples << _BMP280_CONTROL_PRESS_SAMPLES_POS) 115 | & _BMP280_CONTROL_PRESS_SAMPLES_MASK) 116 | # MODE_FORCED will be set in the call to measure() 117 | if mode == MODE_NORMAL: 118 | control[0] |= ((MODE_NORMAL << _BMP280_CONTROL_MODE_POS) 119 | & _BMP280_CONTROL_MODE_MASK) 120 | self.i2c.writeto_mem(self.addr, _BMP280_CONTROL, control) 121 | 122 | config = bytearray(1) 123 | config[0] |= ((standby_ms << _BMP280_CONFIG_STANDBY_POS) 124 | & _BMP280_CONFIG_STANDBY_MASK) 125 | config[0] |= ((iir_filter << _BMP280_CONFIG_IIR_POS) 126 | & _BMP280_CONFIG_IIR_MASK) 127 | self.i2c.writeto_mem(self.addr, _BMP280_CONFIG, config) 128 | 129 | calibration = self.i2c.readfrom_mem( 130 | self.addr, _BMP280_CALIBRATION, _BMP280_CALIBRATION_LEN) 131 | self._T1, = unpack_from('> 1) 158 | press_dur_us = _BMP280_DURATION_PER_SAMPLE_US * ((1 << press_os) >> 1) 159 | press_dur_us += _BMP280_DURATION_PRESS_STARTUP_US if press_os else 0 160 | return _BMP280_DURATION_STARTUP_US + temp_dur_us + press_dur_us 161 | 162 | def _measure_prepare(self): 163 | """ 164 | Sets up a measurement if the sensor is in sleep mode. Returns two 165 | booleans indicating whether temperature and pressure measurements are 166 | enabled. 167 | """ 168 | control = bytearray(1) 169 | # read out values from control register so see if we have to force 170 | # a measurement, and if so, how long we have to wait for the result 171 | self.i2c.readfrom_mem_into(self.addr, _BMP280_CONTROL, control) 172 | mode = ((control[0] & _BMP280_CONTROL_MODE_MASK) 173 | >> _BMP280_CONTROL_MODE_POS) 174 | 175 | temp_samples = ((control[0] & _BMP280_CONTROL_TEMP_SAMPLES_MASK) 176 | >> _BMP280_CONTROL_TEMP_SAMPLES_POS) 177 | 178 | temp_en = bool(temp_samples) 179 | press_samples = ((control[0] & _BMP280_CONTROL_PRESS_SAMPLES_MASK) 180 | >> _BMP280_CONTROL_PRESS_SAMPLES_POS) 181 | press_en = bool(press_samples) 182 | 183 | # if sensor was in sleep mode, force a measurement now 184 | if mode == MODE_SLEEP: 185 | control[0] |= MODE_FORCED 186 | self.i2c.writeto_mem(self.addr, _BMP280_CONTROL, control) 187 | # wait for measurement to complete 188 | sleep_us(self._measure_delay_us(temp_samples, press_samples)) 189 | return (temp_en, press_en) 190 | 191 | def measure(self): 192 | """ 193 | Returns the temperature (in °C) and the pressure (in Pa) as a 2-tuple 194 | in the form of: 195 | 196 | (temperature, pressure) 197 | 198 | This function will wake up the sensor for a single measurement if the 199 | sensor is in sleep mode. 200 | """ 201 | temp_en, press_en = self._measure_prepare() 202 | 203 | # Datasheet 3.11.3: Compute t_fine, temperature and pressure 204 | d = self.i2c.readfrom_mem(self.addr, _BMP280_DATA, _BMP280_DATA_LEN) 205 | p_raw = (d[0] << 12) | (d[1] << 4) | (d[2] >> 4) 206 | t_raw = (d[3] << 12) | (d[4] << 4) | (d[5] >> 4) 207 | 208 | # t_fine 209 | var1 = (((t_raw >> 3) - (self._T1 << 1)) * self._T2) >> 11 210 | var2 = (((((t_raw >> 4) - self._T1) * ((t_raw >> 4) - self._T1)) 211 | >> 12) * self._T3) >> 14 212 | t_fine = var1 + var2 213 | 214 | # temperature 215 | temperature = 0.0 216 | if temp_en: 217 | temperature = ((t_fine * 5 + 128) >> 8) / 100.0 218 | 219 | # pressure 220 | pressure = 0.0 221 | if press_en: 222 | var1 = t_fine - 128000 223 | var2 = var1 * var1 * self._P6 224 | var2 = var2 + ((var1 * self._P5) << 17) 225 | var2 = var2 + (self._P4 << 35) 226 | var1 = ((var1 * var1 * self._P3) >> 8) + ((var1 * self._P2) << 12) 227 | var1 = (((1 << 47) + var1) * self._P1) >> 33 228 | if var1 != 0: 229 | p = 1048576 - p_raw 230 | p = (((p << 31) - var2) * 3125) // var1 231 | var1 = (self._P9 * (p >> 13) * (p >> 13)) >> 25 232 | var2 = (self._P8 * p) >> 19 233 | p = ((p + var1 + var2) >> 8) + (self._P7 << 4) 234 | else: 235 | p = 0 236 | pressure = p / 256.0 237 | 238 | return (temperature, pressure) 239 | -------------------------------------------------------------------------------- /lib/colors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Sebastian Wicki 2 | # Copyright (c) 2019 Ivan Belokobylskiy 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | # Based on https://github.com/devbis/st7789py_mpy 23 | 24 | from micropython import const 25 | 26 | BLACK = const(0x0000) 27 | BLUE = const(0x001F) 28 | RED = const(0xF800) 29 | GREEN = const(0x07E0) 30 | CYAN = const(0x07FF) 31 | MAGENTA = const(0xF81F) 32 | YELLOW = const(0xFFE0) 33 | WHITE = const(0xFFFF) 34 | 35 | 36 | def rgb565(r, g=0, b=0): 37 | """Convert red, green and blue values (0-255) into a 16-bit 565 encoding.""" 38 | try: 39 | r, g, b = r # see if the first var is a tuple/list 40 | except TypeError: 41 | pass 42 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 43 | -------------------------------------------------------------------------------- /lib/dht12.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Sebastian Wicki 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 deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies 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 all 11 | # 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 | I2C-based driver for the DHT12 temperature and humidity sensor. 22 | """ 23 | from micropython import const 24 | 25 | _DHT12_I2C_DEFAULT_ADDR = const(0x5c) 26 | 27 | _DHT12_I2C_DATA_LEN = const(5) 28 | 29 | class DHT12: 30 | def __init__(self, i2c, addr=_DHT12_I2C_DEFAULT_ADDR): 31 | self.i2c = i2c 32 | self.addr = addr 33 | 34 | def measure(self): 35 | """ 36 | Returns the temperature (in °C) and humidity (in %) as a 2-tuple in the 37 | form of: 38 | 39 | (temperature, humidity) 40 | """ 41 | buf = self.i2c.readfrom_mem(self.addr, 0x00, _DHT12_I2C_DATA_LEN) 42 | if (buf[0] + buf[1] + buf[2] + buf[3]) & 0xff != buf[4]: 43 | raise Exception("checksum error") 44 | humidity = buf[0] + (buf[1] * 0.1) 45 | temperature = buf[2] + ((buf[3] & 0b0111_1111) * 0.1) 46 | if buf[2] & 0b1000_0000: 47 | temperature = -temperature 48 | return (temperature, humidity) 49 | -------------------------------------------------------------------------------- /lib/pcf8563.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Sebastian Wicki 2 | # Copyright (c) 2020 Mika Tuupola 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | # Based on https://github.com/tuupola/pcf8563 23 | # 24 | # Unimplemented: 25 | # - Timer support 26 | # - CLKOUT configuration 27 | """ 28 | Driver for the PCF8563/BM8563 real-time clock module. 29 | """ 30 | from micropython import const 31 | 32 | _PCF8563_I2C_DEFAULT_ADDR = const(0x51) 33 | 34 | _PCF8563_CONTROL_STATUS1 = const(0x00) 35 | _PCF8563_CONTROL_STATUS1_TEST1 = const(0b1000_0000) 36 | _PCF8563_CONTROL_STATUS1_STOP = const(0b0010_0000) 37 | _PCF8563_CONTROL_STATUS1_TESTC = const(0b0000_1000) 38 | 39 | _PCF8563_CONTROL_STATUS2 = const(0x01) 40 | _PCF8563_CONTROL_STATUS2_TI_TP = const(0b0001_0000) 41 | _PCF8563_CONTROL_STATUS2_AF = const(0b0000_1000) 42 | _PCF8563_CONTROL_STATUS2_TF = const(0b0000_0100) 43 | _PCF8563_CONTROL_STATUS2_AIE = const(0b0000_0010) 44 | _PCF8563_CONTROL_STATUS2_TIE = const(0b0000_0001) 45 | 46 | _PCF8563_SECONDS = const(0x02) 47 | _PCF8563_MINUTES = const(0x03) 48 | _PCF8563_HOURS = const(0x04) 49 | _PCF8563_DAY = const(0x05) 50 | _PCF8563_WEEKDAY = const(0x06) 51 | _PCF8563_MONTH = const(0x07) 52 | _PCF8563_YEAR = const(0x08) 53 | _PCF8563_TIME_SIZE = const(7) 54 | 55 | _PCF8563_CENTURY_BIT = const(0b1000_0000) 56 | 57 | _PCF8563_MINUTE_ALARM = const(0x09) 58 | _PCF8563_HOUR_ALARM = const(0x0a) 59 | _PCF8563_DAY_ALARM = const(0x0b) 60 | _PCF8563_WEEKDAY_ALARM = const(0x0c) 61 | _PCF8563_ALARM_SIZE = const(4) 62 | 63 | _PCF8563_ALARM_DISABLE = const(0b1000_0000) 64 | 65 | _PCF8563_TIMER_CONTROL = const(0x0e) 66 | _PCF8563_TIMER_CONTROL_ENABLE = const(0b1000_0000) 67 | _PCF8563_TIMER_CONTROL_FREQ_4_096KHZ = const(0b0000_0000) 68 | _PCF8563_TIMER_CONTROL_FREQ_64HZ = const(0b0000_0001) 69 | _PCF8563_TIMER_CONTROL_FREQ_1HZ = const(0b0000_0010) 70 | _PCF8563_TIMER_CONTROL_FREQ_1_60HZ = const(0b0000_0011) 71 | _PCF8563_TIMER = const(0x0f) 72 | 73 | def _dec2bcd(decimal): 74 | high, low = divmod(decimal, 10) 75 | return (high << 4) | low 76 | 77 | 78 | def _bcd2dec(bcd): 79 | return (((bcd & 0xff) >> 4) * 10) + (bcd & 0x0f) 80 | 81 | 82 | class PCF8563: 83 | def __init__(self, i2c, *, addr=_PCF8563_I2C_DEFAULT_ADDR, alarm_irq=True): 84 | self.i2c = i2c 85 | self.addr = addr 86 | 87 | status = bytearray(1) 88 | self.i2c.writeto_mem(self.addr, _PCF8563_CONTROL_STATUS1, status) 89 | if alarm_irq: 90 | status[0] |= _PCF8563_CONTROL_STATUS2_AIE 91 | self.i2c.writeto_mem(self.addr, _PCF8563_CONTROL_STATUS2, status) 92 | 93 | def datetime(self, datetime=None): 94 | """ 95 | With no arguments, this method returns an 7-tuple with the current 96 | date and time. With 1 argument (being an 7-tuple) it sets the date and 97 | time. The 7-tuple has the following format: 98 | 99 | (year, month, mday, hour, minute, second, weekday) 100 | 101 | `year` is 1900..2099 102 | `month` is 1..12 103 | `mday` is 1..31 104 | `hour` is 0..23 105 | `minute` is 0..59 106 | `second` is 0..59 107 | `weekday` is 0..6 108 | """ 109 | if datetime is None: 110 | data = self.i2c.readfrom_mem( 111 | self.addr, _PCF8563_SECONDS, _PCF8563_TIME_SIZE) 112 | # 0..59 113 | bcd = data[0] & 0b01111111 114 | second = _bcd2dec(bcd) 115 | # 0..59 116 | bcd = data[1] & 0b01111111 117 | minute = _bcd2dec(bcd) 118 | # 0..23 119 | bcd = data[2] & 0b00111111 120 | hour = _bcd2dec(bcd) 121 | # 1..31 122 | bcd = data[3] & 0b00111111 123 | mday = _bcd2dec(bcd) 124 | # 0..6 125 | bcd = data[4] & 0b00000111 126 | weekday = _bcd2dec(bcd) 127 | # 1..12 128 | bcd = data[5] & 0b00011111 129 | month = _bcd2dec(bcd) 130 | # If the century bit set, assume it is 2000. Note that it seems 131 | # that unlike PCF8563, the BM8563 does NOT automatically 132 | # toggle the century bit when year overflows from 99 to 00. 133 | # The BM8563 also wrongly treats 1900/2100 as leap years. 134 | century = 100 if (data[5] & _PCF8563_CENTURY_BIT) else 0 135 | # Number of years since the start of the century 136 | bcd = data[6] & 0b11111111 137 | year = _bcd2dec(bcd) + century + 1900 138 | 139 | return (year, month, mday, hour, minute, second, weekday) 140 | 141 | (year, month, mday, hour, minute, second, weekday) = datetime 142 | data = bytearray(_PCF8563_TIME_SIZE) 143 | # 0..59 144 | bcd = _dec2bcd(second) 145 | data[0] = bcd & 0b01111111 146 | # 0..59 147 | bcd = _dec2bcd(minute) 148 | data[1] = bcd & 0b01111111 149 | # 0..23 150 | bcd = _dec2bcd(hour) 151 | data[2] = bcd & 0b00111111 152 | # 1..31 153 | bcd = _dec2bcd(mday) 154 | data[3] = bcd & 0b00111111 155 | # 0..6 156 | bcd = _dec2bcd(weekday) 157 | data[4] = bcd & 0b00000111 158 | # 1..12 159 | bcd = _dec2bcd(month) 160 | data[5] = bcd & 0b00011111 161 | # after 2000 set the century bit 162 | if year >= 2000: 163 | data[5] |= _PCF8563_CENTURY_BIT 164 | # 0..99 165 | bcd = _dec2bcd(year % 100) 166 | data[6] = bcd & 0b11111111 167 | 168 | return self.i2c.writeto_mem(self.addr, _PCF8563_SECONDS, data) 169 | 170 | def alarm(self, alarm=None): 171 | """ 172 | Sets or gets the alarm. If no arguments are provided, it returns 173 | the currently set alarm in the form of a 4-tuple. If 1 argument is 174 | provided (being a 4-tuple), the alarm is set. 175 | 176 | (hour, minute, mday, weekday) 177 | 178 | `hour` is 0..23 or None 179 | `minute` is 0..59 or None 180 | `mday` is 1..31 or None 181 | `weekday` is 0..6 or None 182 | 183 | If a tuple field is set to None then it is not considered for triggering 184 | the alarm. If all four fields are set to None, the alarm is disabled. 185 | """ 186 | if alarm is None: 187 | data = self.i2c.readfrom_mem( 188 | self.addr, _PCF8563_MINUTE_ALARM, _PCF8563_ALARM_SIZE) 189 | # 0..59 190 | if _PCF8563_ALARM_DISABLE & data[0]: 191 | minute = None 192 | else: 193 | bcd = data[0] & 0b01111111 194 | minute = _bcd2dec(bcd) 195 | # 0..23 196 | if _PCF8563_ALARM_DISABLE & data[1]: 197 | hour = None 198 | else: 199 | bcd = data[1] & 0b00111111 200 | hour = _bcd2dec(bcd) 201 | # 1..31 202 | if _PCF8563_ALARM_DISABLE & data[2]: 203 | mday = None 204 | else: 205 | bcd = data[2] & 0b00111111 206 | mday = _bcd2dec(bcd) 207 | # 0..6 208 | if _PCF8563_ALARM_DISABLE & data[3]: 209 | weekday = None 210 | else: 211 | bcd = data[3] & 0b00000111 212 | weekday = _bcd2dec(bcd) 213 | 214 | return (hour, minute, mday, weekday) 215 | 216 | (hour, minute, mday, weekday) = alarm 217 | data = bytearray(_PCF8563_ALARM_SIZE) 218 | # 0..59 219 | if minute is None: 220 | data[0] = _PCF8563_ALARM_DISABLE 221 | else: 222 | data[0] = _dec2bcd(minute) 223 | data[0] &= 0b01111111 224 | # 0..23 225 | if hour is None: 226 | data[1] = _PCF8563_ALARM_DISABLE 227 | else: 228 | data[1] = _dec2bcd(hour) 229 | data[1] &= 0b00111111 230 | # 1..31 231 | if mday is None: 232 | data[2] = _PCF8563_ALARM_DISABLE 233 | else: 234 | data[2] = _dec2bcd(mday) 235 | data[2] &= 0b00111111 236 | # 0..6 237 | if weekday is None: 238 | data[3] = _PCF8563_ALARM_DISABLE 239 | else: 240 | data[3] = _dec2bcd(weekday) 241 | data[3] &= 0b00000111 242 | return self.i2c.writeto_mem(self.addr, _PCF8563_MINUTE_ALARM, data) 243 | 244 | def alarm_active(self, clear=False): 245 | """ 246 | Returns True if the alarm is currently active. An active alarm can be 247 | cleared by setting the clear argument to True. 248 | """ 249 | data = bytearray(1) 250 | self.i2c.readfrom_mem_into(self.addr, _PCF8563_CONTROL_STATUS2, data) 251 | active = bool(data[0] & _PCF8563_CONTROL_STATUS2_AF) 252 | if clear: 253 | data[0] &= ~_PCF8563_CONTROL_STATUS2_AF # AF=0 means alarm cleared 254 | data[0] |= _PCF8563_CONTROL_STATUS2_TF # TF=1 mean timer unchanged 255 | self.i2c.writeto_mem(self.addr, _PCF8563_CONTROL_STATUS2, data) 256 | return active 257 | -------------------------------------------------------------------------------- /lib/sgp30.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Sebastian Wicki 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 deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies 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 all 11 | # 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 | I2C-based driver for the SGP30 air quality sensor. 22 | """ 23 | from math import exp 24 | from micropython import const 25 | from ustruct import pack, pack_into, unpack_from 26 | from utime import sleep_ms 27 | from _thread import allocate_lock, start_new_thread 28 | 29 | _SGP30_I2C_DEFAULT_ADDR = const(0x58) 30 | 31 | _SGP30_CMD_INIT = const(0x2003) 32 | _SGP30_CMD_MEASURE = const(0x2008) 33 | _SGP30_CMD_FEATURE_SET = const(0x202f) 34 | _SGP30_CMD_READ_BASELINE = const(0x2015) 35 | _SGP30_CMD_WRITE_BASELINE = const(0x201e) 36 | _SGP30_CMD_WRITE_ABS_HUMIDITY = const(0x2061) 37 | 38 | _SGP30_FEATURE_SET = const(0x0022) 39 | 40 | _SGP30_FRAME_LEN = const(3) 41 | _SGP30_DATA_LEN = const(2) 42 | _SGP30_CMD_LEN = const(2) 43 | 44 | 45 | def crc8(data): 46 | crc = 0xff 47 | for d in data: 48 | crc ^= d 49 | for _ in range(8): 50 | if crc & 0x80: 51 | crc <<= 1 52 | crc ^= 0x31 53 | else: 54 | crc <<= 1 55 | crc &= 0xff 56 | return crc 57 | 58 | 59 | def absolute_humidity(t, rh): 60 | """ 61 | Returns the absolute humidity from the relative humidity and temperature. 62 | This value may be passed to SGP30.set_absolute_humidity for the on-chip 63 | humiditity compensation. 64 | 65 | t is the temperature in °C 66 | rh is the relative humidity in percent (0-100) 67 | """ 68 | return 216.7 * ((rh/100.0)*6.112*exp((17.62*t)/(243.12+t))/(273.15+t)) 69 | 70 | 71 | class SGP30: 72 | def __init__(self, i2c, *, addr=_SGP30_I2C_DEFAULT_ADDR, baseline=None): 73 | self.i2c = i2c 74 | self.addr = addr 75 | self.eco2 = 400 76 | self.tvoc = 0 77 | self.stopped = False 78 | self.lock = allocate_lock() 79 | feature_set = self._read_values(_SGP30_CMD_FEATURE_SET, 1, delay_ms=10) 80 | if feature_set[0] != _SGP30_FEATURE_SET: 81 | raise ValueError("device not found") 82 | self._write_values(_SGP30_CMD_INIT) 83 | if baseline is not None: 84 | if isinstance(baseline, tuple) and len(baseline) == 2: 85 | self._write_values(_SGP30_CMD_WRITE_BASELINE, baseline) 86 | else: 87 | raise ValueError("invalid argument(s) value") 88 | start_new_thread(self._loop, ()) 89 | 90 | def baseline(self): 91 | """ 92 | Gets the baseline as a 2-tuple. This can be passed to the constructor 93 | as the baseline value. 94 | """ 95 | with self.lock: 96 | v = self._read_values(_SGP30_CMD_READ_BASELINE, 2, delay_ms=10) 97 | return tuple(v) 98 | 99 | def _loop(self): 100 | # A sgp30_measure_iaq command has to be sent in regular intervals 101 | # of 1s to ensure proper operation of the dynamic baseline compensation 102 | # algorithm 103 | while True: 104 | sleep_ms(1000) 105 | with self.lock: 106 | if self.stopped: 107 | break 108 | eco2, tvoc = self._read_values(_SGP30_CMD_MEASURE, 2, 109 | delay_ms=12) 110 | self.eco2, self.tvoc = eco2, tvoc 111 | 112 | def set_absolute_humidity(self, ah): 113 | """ 114 | Sets the absolute humidity for the on-chip compensation. The ah value 115 | must represent the absolute humidity in g/m³ 116 | """ 117 | val = int(ah * 256) 118 | if not 0x0001 <= val <= 0xffff: 119 | raise ValueError("value out of range") 120 | with self.lock: 121 | self._write_values(_SGP30_CMD_WRITE_ABS_HUMIDITY, (val,)) 122 | 123 | def measure(self): 124 | """ 125 | Returns the measured CO2-equivalent (in ppm) and TVOC (in ppb) in the 126 | form of 127 | 128 | (eco2, tvoc) 129 | """ 130 | with self.lock: 131 | eco2, tvoc = self.eco2, self.tvoc 132 | return eco2, tvoc 133 | 134 | def stop(self): 135 | with self.lock: 136 | self.stopped = True 137 | 138 | def _read_values(self, cmd, nvalues, delay_ms=1): 139 | self.i2c.writeto(self.addr, pack(">H", cmd), False) 140 | sleep_ms(delay_ms) 141 | 142 | buf = memoryview(bytearray(nvalues * _SGP30_FRAME_LEN)) 143 | self.i2c.readfrom_into(self.addr, buf, True) 144 | 145 | offset = 0 146 | values = [] 147 | for _ in range(nvalues): 148 | value, crc = unpack_from(">HB", buf, offset) 149 | if crc != crc8(buf[offset:offset+_SGP30_DATA_LEN]): 150 | raise Exception("checksum error") 151 | values.append(value) 152 | offset += _SGP30_FRAME_LEN 153 | 154 | return values 155 | 156 | def _write_values(self, cmd, values=()): 157 | nvalues = len(values) 158 | buf = memoryview(bytearray(_SGP30_CMD_LEN + 159 | nvalues * _SGP30_FRAME_LEN)) 160 | 161 | pack_into(">H", buf, 0, cmd) 162 | offset = _SGP30_CMD_LEN 163 | 164 | for value in values: 165 | pack_into(">H", buf, offset, value) 166 | 167 | offset_crc = offset+_SGP30_DATA_LEN 168 | crc = crc8(buf[offset:offset_crc]) 169 | pack_into("b", buf, offset_crc, crc) 170 | 171 | offset += _SGP30_FRAME_LEN 172 | 173 | self.i2c.writeto(self.addr, buf, True) 174 | -------------------------------------------------------------------------------- /lib/st7789.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Sebastian Wicki 2 | # Copyright (c) 2019 Ivan Belokobylskiy 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | # Based on https://github.com/devbis/st7789py_mpy 23 | # 24 | # Notable modifications: 25 | # - Added support for text() 26 | # - Pre-allocated shared buffer for text() and draw_rect() 27 | # - Minor memory optimizations for bytecode builds by using shorter error 28 | # messages, more aggressive inlining, and making most consts private 29 | """ 30 | Driver for the ST7789 display controller. 31 | """ 32 | 33 | import sys 34 | 35 | import framebuf 36 | import ustruct 37 | from micropython import const 38 | from utime import sleep_ms 39 | 40 | # commands 41 | _ST77XX_NOP = const(0x00) 42 | _ST77XX_SWRESET = const(0x01) 43 | _ST77XX_RDDID = const(0x04) 44 | _ST77XX_RDDST = const(0x09) 45 | 46 | _ST77XX_SLPIN = const(0x10) 47 | _ST77XX_SLPOUT = const(0x11) 48 | _ST77XX_PTLON = const(0x12) 49 | _ST77XX_NORON = const(0x13) 50 | 51 | _ST77XX_INVOFF = const(0x20) 52 | _ST77XX_INVON = const(0x21) 53 | _ST77XX_DISPOFF = const(0x28) 54 | _ST77XX_DISPON = const(0x29) 55 | _ST77XX_CASET = const(0x2a) 56 | _ST77XX_RASET = const(0x2b) 57 | _ST77XX_RAMWR = const(0x2c) 58 | _ST77XX_RAMRD = const(0x2e) 59 | 60 | _ST77XX_PTLAR = const(0x30) 61 | _ST77XX_COLMOD = const(0x3a) 62 | _ST7789_MADCTL = const(0x36) 63 | 64 | _ST7789_MADCTL_MY = const(0x80) 65 | _ST7789_MADCTL_MX = const(0x40) 66 | _ST7789_MADCTL_MV = const(0x20) 67 | _ST7789_MADCTL_ML = const(0x10) 68 | _ST7789_MADCTL_BGR = const(0x08) 69 | _ST7789_MADCTL_MH = const(0x04) 70 | _ST7789_MADCTL_RGB = const(0x00) 71 | 72 | _ST7789_RDID1 = const(0xda) 73 | _ST7789_RDID2 = const(0xdb) 74 | _ST7789_RDID3 = const(0xdc) 75 | _ST7789_RDID4 = const(0xdd) 76 | 77 | ColorMode_65K = const(0x50) 78 | ColorMode_262K = const(0x60) 79 | ColorMode_12bit = const(0x03) 80 | ColorMode_16bit = const(0x05) 81 | ColorMode_18bit = const(0x06) 82 | ColorMode_16M = const(0x07) 83 | 84 | _BUF_DEFAULT_LEN = const(512) 85 | 86 | _PIXEL_LEN = const(2) 87 | 88 | _FONT_HEIGHT = const(8) 89 | _FONT_WIDTH = const(8) 90 | 91 | 92 | class ST7789: 93 | def __init__(self, spi, width, height, reset, dc, cs=None, buf=None, 94 | xstart=-1, ystart=-1, init=True, 95 | color_mode=ColorMode_65K | ColorMode_16bit): 96 | """ 97 | display = st7789.ST7789( 98 | SPI(1, baudrate=40000000, phase=0, polarity=1), 99 | 240, 240, 100 | reset=machine.Pin(5, machine.Pin.OUT), 101 | dc=machine.Pin(2, machine.Pin.OUT), 102 | buf=bytearray(128), 103 | ) 104 | """ 105 | self.width = width 106 | self.height = height 107 | self.spi = spi 108 | self.reset = reset 109 | self.dc = dc 110 | self.cs = cs 111 | 112 | if buf is None: 113 | buf = bytearray(_BUF_DEFAULT_LEN) 114 | self.buf = memoryview(buf) 115 | 116 | if sys.byteorder == 'little': 117 | self._to_be16 = lambda c: (c << 8) & 0xff00 | (c >> 8) & 0x00ff 118 | else: 119 | self._to_be16 = lambda c: c 120 | 121 | if xstart >= 0 and ystart >= 0: 122 | self.xstart = xstart 123 | self.ystart = ystart 124 | elif (self.width, self.height) == (240, 240): 125 | self.xstart = 0 126 | self.ystart = 0 127 | elif (self.width, self.height) == (135, 240): 128 | self.xstart = 52 129 | self.ystart = 40 130 | else: 131 | # Unsupported display. Only 240x240 and 135x240 are supported 132 | # without xstart and ystart provided 133 | raise ValueError("invalid argument(s) value") 134 | if init: 135 | self.hard_reset() 136 | self.soft_reset() 137 | self.sleep_mode(False) 138 | sleep_ms(10) 139 | self._set_color_mode(color_mode) 140 | self._set_mem_access_mode(4, True, True, False) 141 | self.inversion_mode(True) 142 | sleep_ms(10) 143 | self.write(_ST77XX_NORON) 144 | sleep_ms(10) 145 | self.fill(0) 146 | self.write(_ST77XX_DISPON) 147 | sleep_ms(10) 148 | 149 | def cs_low(self): 150 | if self.cs: 151 | self.cs.off() 152 | 153 | def cs_high(self): 154 | if self.cs: 155 | self.cs.on() 156 | 157 | def write(self, command=None, data=None): 158 | """SPI write to the device: commands and data""" 159 | self.cs_low() 160 | if command is not None: 161 | self.dc.off() 162 | self.spi.write(bytes([command])) 163 | if data is not None: 164 | self.dc.on() 165 | self.spi.write(data) 166 | self.cs_high() 167 | 168 | def hard_reset(self): 169 | self.cs_low() 170 | if self.reset is not None: 171 | self.reset.on() 172 | sleep_ms(10) 173 | self.reset.off() 174 | sleep_ms(10) 175 | self.reset.on() 176 | sleep_ms(10) 177 | self.cs_high() 178 | 179 | def soft_reset(self): 180 | self.write(_ST77XX_SWRESET) 181 | sleep_ms(120) 182 | 183 | def sleep_mode(self, value): 184 | if value: 185 | self.write(_ST77XX_SLPIN) 186 | else: 187 | self.write(_ST77XX_SLPOUT) 188 | 189 | def inversion_mode(self, value): 190 | if value: 191 | self.write(_ST77XX_INVON) 192 | else: 193 | self.write(_ST77XX_INVOFF) 194 | 195 | def _set_color_mode(self, mode): 196 | self.write(_ST77XX_COLMOD, bytes([mode & 0x77])) 197 | 198 | def _set_mem_access_mode(self, rotation, vert_mirror, horz_mirror, is_bgr): 199 | rotation &= 7 200 | value = { 201 | 0: 0, 202 | 1: _ST7789_MADCTL_MX, 203 | 2: _ST7789_MADCTL_MY, 204 | 3: _ST7789_MADCTL_MX | _ST7789_MADCTL_MY, 205 | 4: _ST7789_MADCTL_MV, 206 | 5: _ST7789_MADCTL_MV | _ST7789_MADCTL_MX, 207 | 6: _ST7789_MADCTL_MV | _ST7789_MADCTL_MY, 208 | 7: _ST7789_MADCTL_MV | _ST7789_MADCTL_MX | _ST7789_MADCTL_MY, 209 | }[rotation] 210 | 211 | if vert_mirror: 212 | value = _ST7789_MADCTL_ML 213 | elif horz_mirror: 214 | value = _ST7789_MADCTL_MH 215 | 216 | if is_bgr: 217 | value |= _ST7789_MADCTL_BGR 218 | self.write(_ST7789_MADCTL, bytes([value])) 219 | 220 | def _encode_pos(self, x, y): 221 | """Encode a postion into bytes.""" 222 | return ustruct.pack(">HH", x, y) 223 | 224 | def _encode_pixel(self, color): 225 | """Encode a pixel color into bytes.""" 226 | return ustruct.pack(">H", color) 227 | 228 | def _set_columns(self, start, end): 229 | if start > end or end >= self.width: 230 | return 231 | start += self.xstart 232 | end += self.xstart 233 | self.write(_ST77XX_CASET, self._encode_pos(start, end)) 234 | 235 | def _set_rows(self, start, end): 236 | if start > end or end >= self.height: 237 | return 238 | start += self.ystart 239 | end += self.ystart 240 | self.write(_ST77XX_RASET, self._encode_pos(start, end)) 241 | 242 | def set_window(self, x0, y0, x1, y1): 243 | self._set_columns(x0, x1) 244 | self._set_rows(y0, y1) 245 | self.write(_ST77XX_RAMWR) 246 | 247 | def vline(self, x, y, length, color): 248 | self.fill_rect(x, y, 1, length, color) 249 | 250 | def hline(self, x, y, length, color): 251 | self.fill_rect(x, y, length, 1, color) 252 | 253 | def pixel(self, x, y, color): 254 | self.set_window(x, y, x, y) 255 | self.write(None, self._encode_pixel(color)) 256 | 257 | def blit_buffer(self, buffer, x, y, width, height): 258 | self.set_window(x, y, x + width - 1, y + height - 1) 259 | self.write(None, buffer) 260 | 261 | def rect(self, x, y, w, h, color): 262 | self.hline(x, y, w, color) 263 | self.vline(x, y, h, color) 264 | self.vline(x + w - 1, y, h, color) 265 | self.hline(x, y + h - 1, w, color) 266 | 267 | def fill_rect(self, x, y, width, height, color): 268 | buf_len = len(self.buf) 269 | chunks, rest = divmod(width * height * _PIXEL_LEN, buf_len) 270 | f = framebuf.FrameBuffer( 271 | self.buf, buf_len // _PIXEL_LEN, 1, framebuf.RGB565) 272 | f.fill(self._to_be16(color)) 273 | 274 | self.set_window(x, y, x + width - 1, y + height - 1) 275 | if chunks: 276 | for _ in range(chunks): 277 | self.write(None, self.buf) 278 | if rest: 279 | self.write(None, self.buf[:rest]) 280 | 281 | def fill(self, color): 282 | self.fill_rect(0, 0, self.width, self.height, color) 283 | 284 | def line(self, x0, y0, x1, y1, color): 285 | # Line drawing function. Will draw a single pixel wide line starting at 286 | # x0, y0 and ending at x1, y1. 287 | steep = abs(y1 - y0) > abs(x1 - x0) 288 | pixel = self._encode_pixel(color) 289 | if steep: 290 | x0, y0 = y0, x0 291 | x1, y1 = y1, x1 292 | if x0 > x1: 293 | x0, x1 = x1, x0 294 | y0, y1 = y1, y0 295 | dx = x1 - x0 296 | dy = abs(y1 - y0) 297 | err = dx // 2 298 | if y0 < y1: 299 | ystep = 1 300 | else: 301 | ystep = -1 302 | while x0 <= x1: 303 | if steep: 304 | self.set_window(y0, x0, y0, x0) 305 | self.write(None, pixel) 306 | else: 307 | self.set_window(x0, y0, x0, y0) 308 | self.write(None, pixel) 309 | err -= dy 310 | if err < 0: 311 | y0 += ystep 312 | err += dx 313 | x0 += 1 314 | 315 | def text(self, s, x, y, fg, bg): 316 | text_width = len(s) * _FONT_WIDTH 317 | text_mem = text_width * _FONT_HEIGHT * _PIXEL_LEN 318 | if text_mem > len(self.buf): 319 | raise ValueError("buffer too small") 320 | 321 | f = framebuf.FrameBuffer(self.buf, text_width, 322 | _FONT_HEIGHT, framebuf.RGB565) 323 | f.fill(self._to_be16(bg)) 324 | f.text(s, 0, 0, self._to_be16(fg)) 325 | self.blit_buffer(self.buf[:text_mem], x, y, text_width, _FONT_HEIGHT) 326 | --------------------------------------------------------------------------------