├── LICENSE ├── M5Atom ├── AtomicGPSTest │ └── AtomicGPSTest.ino ├── DTUNBIOTPassthroughTest │ └── DTUNBIOTPassthroughTest.ino ├── EnvIVTest │ └── EnvIVTest.ino └── atom_dtu_nb_iot.py ├── M5AtomS3 ├── DeepSleepTest │ └── DeepSleepTest.ino └── ReadDualButtonViaPbHub │ └── ReadDualButtonViaPbHub.ino ├── M5Cardputer └── Unit4RelayDemo │ └── Unit4RelayDemo.ino ├── M5Core2 ├── BatteryMonitor │ └── BatteryMonitor.ino ├── DeepSleepWakeFromTouch │ └── DeepSleepWakeFromTouch.ino ├── IoTBasePSM.py ├── LightSleepWakeFromTouch │ └── LightSleepWakeFromTouch.ino ├── SimpleTone.py └── TouchButtonTest │ └── TouchButtonTest.ino ├── M5Core2v1.1 └── BlueLEDTest │ └── BlueLEDTest.ino ├── M5CoreInk ├── CountDownThenPowerOff.py └── RTC_Clock │ └── RTC_Clock.ino ├── M5CoreS3 ├── COMLTETest │ └── COMLTETest.ino ├── CamWebServer │ ├── CamWebServer.ino │ ├── app_httpd.cpp │ └── camera_index.h ├── I2CScanInternalGroove │ └── I2CScanInternalGroove.ino ├── SDCard │ └── SDCard.ino ├── SDCardAndLCD │ └── SDCardAndLCD.ino ├── TouchButtonABC.py └── TouchRandomCircle │ └── TouchRandomCircle.ino ├── M5Dial ├── EncAndBrightTest │ └── EncAndBrightTest.ino ├── GC9A01_demo │ └── GC9A01_demo.ino ├── I2CScanIntExt │ └── I2CScanIntExt.ino ├── RS485ModbusACSSR │ └── RS485ModbusACSSR.ino ├── ShutdownTest │ └── ShutdownTest.ino └── SleepTest │ └── SleepTest.ino ├── M5LANBase └── LinkTest │ └── LinkTest.ino ├── M5Paper ├── GestureSensor │ └── GestureSensor.ino ├── I2CScanInternalAndAllPorts │ └── I2CScanInternalAndAllPorts.ino ├── Lightsleep.py ├── NonFlickeringUpdateAfterShutdown │ └── NonFlickeringUpdateAfterShutdown.ino ├── ShutdownWakeAfterHours │ └── ShutdownWakeAfterHours.ino └── SleepTest │ └── SleepTest.ino ├── M5PaperS3 ├── LightSleepTouchWakeup │ └── LightSleepTouchWakeup.ino └── RTCClockNonFlickering │ └── RTCClockNonFlickering.ino ├── M5Stack ├── Cam2CoreExtended │ ├── Cam2CoreExtended.ino │ ├── uart_frame.cpp │ └── uart_frame.h └── OTAoverLTE │ └── OTAoverLTE.ino ├── M5StampC3 ├── NonBlockingBlink │ └── NonBlockingBlink.ino └── Unit4RelayDemo │ └── Unit4RelayDemo.ino ├── M5Station ├── LEDTest │ └── LEDTest.ino └── RS485ModbusACSSR │ └── RS485ModbusACSSR.ino ├── M5StickCPlus2 ├── I2CScanInternalGrooveHat │ └── I2CScanInternalGrooveHat.ino └── SleepAndPowerOffTest │ └── SleepAndPowerOffTest.ino ├── M5Tough ├── LightSleepWakeFromTouch │ └── LightSleepWakeFromTouch.ino ├── RS485ModbusACSSR │ └── RS485ModbusACSSR.ino └── ThreeBottomButtons.py └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 by GWENDESIGN 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 | -------------------------------------------------------------------------------- /M5Atom/AtomicGPSTest/AtomicGPSTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // Based on: https://github.com/m5stack/M5AtomS3/blob/main/examples/AtomicBase/AtomicGPS/GPS/GPS.ino 5 | // Adapted to: M5Atom + Atomic GPS Base 6 | // 7 | // https://github.com/mikalhart/TinyGPSPlus 8 | 9 | #include 10 | #include 11 | 12 | TinyGPSPlus gps; 13 | 14 | // This custom version of delay() ensures that the gps object 15 | // is being "fed". 16 | static void smartDelay(unsigned long ms) { 17 | unsigned long start = millis(); 18 | do { 19 | while (Serial2.available()) gps.encode(Serial2.read()); 20 | } while (millis() - start < ms); 21 | } 22 | 23 | static void printFloat(float val, bool valid, int len, int prec) { 24 | if (!valid) { 25 | while (len-- > 1) Serial.print('*'); 26 | Serial.print(' '); 27 | } else { 28 | Serial.print(val, prec); 29 | int vi = abs((int)val); 30 | int flen = prec + (val < 0.0 ? 2 : 1); // . and - 31 | flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1; 32 | for (int i = flen; i < len; ++i) Serial.print(' '); 33 | } 34 | smartDelay(0); 35 | } 36 | 37 | static void printInt(unsigned long val, bool valid, int len) { 38 | char sz[32] = "*****************"; 39 | if (valid) sprintf(sz, "%ld", val); 40 | sz[len] = 0; 41 | for (int i = strlen(sz); i < len; ++i) sz[i] = ' '; 42 | if (len > 0) sz[len - 1] = ' '; 43 | Serial.print(sz); 44 | smartDelay(0); 45 | } 46 | 47 | static void printDateTime(TinyGPSDate &d, TinyGPSTime &t) { 48 | if (!d.isValid()) { 49 | Serial.print(F("********** ")); 50 | } else { 51 | char sz[32]; 52 | sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year()); 53 | Serial.print(sz); 54 | } 55 | 56 | if (!t.isValid()) { 57 | Serial.print(F("******** ")); 58 | } else { 59 | char sz[32]; 60 | sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second()); 61 | Serial.print(sz); 62 | } 63 | 64 | printInt(d.age(), d.isValid(), 5); 65 | smartDelay(0); 66 | } 67 | 68 | static void printStr(const char *str, int len) { 69 | int slen = strlen(str); 70 | for (int i = 0; i < len; ++i) Serial.print(i < slen ? str[i] : ' '); 71 | smartDelay(0); 72 | } 73 | 74 | void setup() { 75 | Serial.begin(115200); 76 | Serial2.begin(9600, SERIAL_8N1, GPIO_NUM_22, -1); 77 | } 78 | 79 | void loop() { 80 | printInt(gps.satellites.value(), gps.satellites.isValid(), 5); 81 | printFloat(gps.hdop.hdop(), gps.hdop.isValid(), 6, 1); 82 | printFloat(gps.location.lat(), gps.location.isValid(), 11, 6); 83 | printFloat(gps.location.lng(), gps.location.isValid(), 12, 6); 84 | printInt(gps.location.age(), gps.location.isValid(), 5); 85 | printDateTime(gps.date, gps.time); 86 | printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2); 87 | printFloat(gps.course.deg(), gps.course.isValid(), 7, 2); 88 | printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2); 89 | printStr( 90 | gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.deg()) : "*** ", 91 | 6); 92 | 93 | printInt(gps.charsProcessed(), true, 6); 94 | printInt(gps.sentencesWithFix(), true, 10); 95 | printInt(gps.failedChecksum(), true, 9); 96 | Serial.println(); 97 | 98 | smartDelay(1000); 99 | 100 | if (millis() > 5000 && gps.charsProcessed() < 10) 101 | Serial.println(F("No GPS data received: check wiring")); 102 | } 103 | -------------------------------------------------------------------------------- /M5Atom/DTUNBIOTPassthroughTest/DTUNBIOTPassthroughTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // https://github.com/m5stack/M5GFX 5 | // https://github.com/m5stack/M5Unified 6 | 7 | #include 8 | 9 | #define SIM7020_BAUDRATE 115200 10 | #define SIM7020_RX_PIN GPIO_NUM_19 11 | #define SIM7020_TX_PIN GPIO_NUM_22 12 | 13 | void setup() 14 | { 15 | auto cfg = M5.config(); 16 | cfg.serial_baudrate = 115200; 17 | M5.begin(cfg); 18 | Serial2.begin(115200, SERIAL_8N1, SIM7020_RX_PIN, SIM7020_TX_PIN); 19 | delay(2000); 20 | Serial.printf("Start SIM7020 passthrough test\n"); 21 | } 22 | 23 | void loop() 24 | { 25 | while(Serial.available()) 26 | { 27 | char c = Serial.read(); 28 | Serial2.write(c); 29 | } 30 | while(Serial2.available()) 31 | { 32 | char c = Serial2.read(); 33 | Serial.write(c); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /M5Atom/EnvIVTest/EnvIVTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/FastLED/FastLED 5 | // https://github.com/adafruit/Adafruit_BMP280_Library 6 | // https://github.com/adafruit/Adafruit_BusIO 7 | // https://github.com/adafruit/Adafruit_Sensor 8 | // https://github.com/Sensirion/arduino-core 9 | // https://github.com/Sensirion/arduino-i2c-sht4x 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | Adafruit_BMP280 bmp; // uses Wire by default 16 | SensirionI2CSht4x sht4x; 17 | 18 | float temp, pres, humi; 19 | uint16_t error; 20 | char errorMessage[256]; 21 | 22 | void setup() 23 | { 24 | M5.begin(true, false, false); 25 | Wire.begin(26, 32); // Groove port (red) 26 | 27 | while(!bmp.begin(0x76)) 28 | { 29 | Serial.println(F("BMP280 init fail")); 30 | } 31 | Serial.println(F("BMP280 init success")); 32 | bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, 33 | Adafruit_BMP280::SAMPLING_X2, 34 | Adafruit_BMP280::SAMPLING_X16, 35 | Adafruit_BMP280::FILTER_X16, 36 | Adafruit_BMP280::STANDBY_MS_500); 37 | 38 | uint32_t serialNumber; 39 | 40 | sht4x.begin(Wire, 0x44); 41 | error = sht4x.serialNumber(serialNumber); 42 | if(error) 43 | { 44 | Serial.print("Error trying to execute serialNumber(): "); 45 | errorToString(error, errorMessage, 256); 46 | Serial.println(errorMessage); 47 | } 48 | else 49 | { 50 | Serial.print("Serial Number: "); 51 | Serial.println(serialNumber); 52 | } 53 | } 54 | 55 | void loop() 56 | { 57 | pres = bmp.readPressure() / 100; 58 | Serial.printf("Pres: %2.0f hPa\n", pres); 59 | 60 | error = sht4x.measureHighPrecision(temp, humi); 61 | if(error) 62 | { 63 | Serial.print("Error trying to execute measureHighPrecision(): "); 64 | errorToString(error, errorMessage, 256); 65 | Serial.println(errorMessage); 66 | temp = 0.0; 67 | humi = 0.0; 68 | } 69 | Serial.printf("Temp: %2.0f\n", temp); 70 | Serial.printf("Humi: %2.0f %%\n", humi); 71 | 72 | delay(1000); 73 | } 74 | -------------------------------------------------------------------------------- /M5Atom/atom_dtu_nb_iot.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | from m5stack import * 5 | from m5ui import * 6 | from uiflow import * 7 | from easyIO import * 8 | from numbers import Number 9 | import time 10 | 11 | btnAPressed = None 12 | 13 | def sendAT(cmd): 14 | print((str('> ') + str(cmd))) 15 | uart1.write(str(cmd)+"\r\n") 16 | wait_ms(250) 17 | 18 | def readResponse(timeout, waitFor): 19 | rgb.setColorAll(0x000000) 20 | while timeout: 21 | if uart1.any(): 22 | response = (uart1.readline()).decode() 23 | response = response.strip() 24 | if response != '': 25 | print((str('< ') + str(response))) 26 | if response == waitFor: 27 | break 28 | wait_ms(100) 29 | timeout = (timeout if isinstance(timeout, Number) else 0) + -100 30 | print(str(timeout)) 31 | if timeout == 0: 32 | print('timeout') 33 | rgb.setColorAll(0xff0000) 34 | else: 35 | print('found') 36 | rgb.setColorAll(0x00ff00) 37 | 38 | def buttonA_wasPressed(): 39 | global btnAPressed 40 | btnAPressed = True 41 | pass 42 | btnA.wasPressed(buttonA_wasPressed) 43 | 44 | 45 | uart1 = machine.UART(1, tx=22, rx=19) 46 | uart1.init(115200, bits=8, parity=None, stop=1) 47 | while True: 48 | if btnAPressed: 49 | btnAPressed = False 50 | sendAT('AT') 51 | readResponse(10000, 'OK') 52 | wait_ms(2) 53 | -------------------------------------------------------------------------------- /M5AtomS3/DeepSleepTest/DeepSleepTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // M5AtomS3 powered via Groove port: 5 V 5 | // Measured current in deep sleep: 257 uA 6 | // Measured with: EEVblog uCurrent Gold 7 | 8 | #include "M5AtomS3.h" 9 | 10 | void setup() 11 | { 12 | M5.begin(); 13 | M5.Lcd.println("Deep Sleep Test"); 14 | delay(3000); 15 | esp_sleep_enable_timer_wakeup(1000L * 1000L * 10L); 16 | esp_deep_sleep_start(); 17 | } 18 | 19 | void loop() 20 | { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /M5AtomS3/ReadDualButtonViaPbHub/ReadDualButtonViaPbHub.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5AtomS3 5 | // https://github.com/m5stack/M5Unified 6 | // https://github.com/m5stack/M5GFX 7 | // https://github.com/m5stack/M5Unit-PbHub 8 | // https://github.com/FastLED/FastLED 9 | // 10 | // You need: M5AtomS3, ATOMIC ABC, PbHub unit v1.1, Dual Button unit 11 | // Place M5AtomS3 onto ATOMIC ABC, connect PbHub to red port, connect Dual Button unit to channel 2 12 | 13 | #include 14 | #include 15 | 16 | M5UnitPbHub pbhub; 17 | 18 | #define PBHUB_CHANNEL 2 // 0 - 5 19 | #define BUTTON_BLUE_INDEX 0 20 | #define BUTTON_RED_INDEX 1 21 | 22 | void setup() 23 | { 24 | auto cfg = M5.config(); 25 | AtomS3.begin(cfg); 26 | 27 | if(!pbhub.begin(&Wire, UNIT_PBHUB_I2C_ADDR, 38, 39, 400000U)) 28 | { 29 | Serial.println("Pbhub not found"); 30 | while(1) delay(1); 31 | } 32 | Serial.println("Pbhub found"); 33 | } 34 | 35 | void loop() 36 | { 37 | if(pbhub.digitalRead(PBHUB_CHANNEL, BUTTON_BLUE_INDEX) == true) 38 | Serial.println("Blue button released"); 39 | else 40 | Serial.println("Blue button pressed"); 41 | 42 | if(pbhub.digitalRead(PBHUB_CHANNEL, BUTTON_RED_INDEX) == true) 43 | Serial.println("Red button released"); 44 | else 45 | Serial.println("Red button pressed"); 46 | 47 | delay(1000); 48 | } 49 | -------------------------------------------------------------------------------- /M5Cardputer/Unit4RelayDemo/Unit4RelayDemo.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5GFX 5 | // https://github.com/m5stack/M5Unified 6 | // https://github.com/m5stack/M5Cardputer 7 | // https://github.com/m5stack/M5Module-4Relay 8 | 9 | #include "M5Cardputer.h" 10 | #include "Unit_4RELAY.h" 11 | 12 | UNIT_4RELAY relay; 13 | 14 | void setup() 15 | { 16 | auto cfg = M5.config(); 17 | cfg.serial_baudrate = 115200; 18 | M5.begin(cfg); 19 | 20 | Serial.print("Unit 4Relay Demo"); 21 | M5.Display.print("Unit 4Relay Demo"); 22 | 23 | relay.begin(&Wire, GPIO_NUM_2, GPIO_NUM_1); // Port A 24 | relay.Init(1); // synchronous mode 25 | } 26 | 27 | void loop() 28 | { 29 | relay.relayWrite(0, 1); // turn on relay 1 30 | delay(1000); 31 | relay.relayWrite(0, 0); // turn off relay 1 32 | delay(1000); 33 | relay.relayAll(1); // turn on all relay 34 | delay(1000); 35 | relay.relayAll(0); // turn off all relay 36 | delay(1000); 37 | } 38 | -------------------------------------------------------------------------------- /M5Core2/BatteryMonitor/BatteryMonitor.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5GFX 5 | // https://github.com/m5stack/M5Unified 6 | 7 | #include 8 | 9 | void setup() 10 | { 11 | auto cfg = M5.config(); 12 | M5.begin(cfg); 13 | M5.Display.setTextSize(2); 14 | } 15 | 16 | void loop() 17 | { 18 | int32_t bc = M5.Power.getBatteryCurrent(); 19 | int32_t bl = M5.Power.getBatteryLevel(); 20 | int16_t bv = M5.Power.getBatteryVoltage(); 21 | m5::Power_Class::is_charging_t ic = M5.Power.isCharging(); 22 | 23 | M5.Display.setCursor(0, 0); 24 | M5.Display.printf("Battery Monitor v 1.1\n\n"); 25 | M5.Display.printf("Charge Current: %03d mA\n", bc); 26 | M5.Display.printf("Level : %03d %%\n", bl); 27 | M5.Display.printf("Voltage : %04d mV\n", bv); 28 | M5.Display.printf("Is Charging : %01d\n", ic); 29 | 30 | delay(1000); 31 | } 32 | -------------------------------------------------------------------------------- /M5Core2/DeepSleepWakeFromTouch/DeepSleepWakeFromTouch.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | void setup() 7 | { 8 | M5.begin(); 9 | 10 | esp_sleep_enable_ext0_wakeup((gpio_num_t)CST_INT, LOW); 11 | Serial.println("before deep sleep"); 12 | Serial.flush(); 13 | esp_deep_sleep_start(); 14 | Serial.println("never reached"); 15 | // Never reached - after deep sleep ESP32 restarts 16 | } 17 | 18 | void loop() 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /M5Core2/IoTBasePSM.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | from m5stack import * 5 | from m5stack_ui import * 6 | from uiflow import * 7 | from easyIO import * 8 | import time 9 | 10 | screen = M5Screen() 11 | screen.clean_screen() 12 | screen.set_screen_bg_color(0xFFFFFF) 13 | 14 | cmd = None 15 | response = None 16 | timeout = None 17 | waitFor = None 18 | logentry = None 19 | btnAPressed = None 20 | btnBPressed = None 21 | btnCPressed = None 22 | list1 = None 23 | list2 = None 24 | list3 = None 25 | list4 = None 26 | list5 = None 27 | list6 = None 28 | list7 = None 29 | 30 | action = M5Label('action', x=10, y=20, color=0x000, font=FONT_MONT_14, parent=None) 31 | l_list1 = M5Label('l_list1', x=9, y=50, color=0x000, font=FONT_MONT_14, parent=None) 32 | l_list2 = M5Label('l_list2', x=10, y=70, color=0x000, font=FONT_MONT_14, parent=None) 33 | l_list3 = M5Label('l_list3', x=10, y=90, color=0x000, font=FONT_MONT_14, parent=None) 34 | l_list4 = M5Label('l_list4', x=10, y=110, color=0x000, font=FONT_MONT_14, parent=None) 35 | l_list5 = M5Label('l_list5', x=10, y=130, color=0x000, font=FONT_MONT_14, parent=None) 36 | l_list6 = M5Label('l_list6', x=10, y=150, color=0x000, font=FONT_MONT_14, parent=None) 37 | l_list7 = M5Label('l_list7', x=10, y=170, color=0x000, font=FONT_MONT_14, parent=None) 38 | l_timeout = M5Label('l_timeout', x=10, y=210, color=0x000, font=FONT_MONT_14, parent=None) 39 | 40 | from numbers import Number 41 | 42 | # Describe this function... 43 | def powerOn(): 44 | global cmd, timeout, waitFor, logentry, btnAPressed, btnBPressed, btnCPressed, list1, list2, response, list3, list4, list5, list6, list7, uart1 45 | digitalWrite(27, 0) 46 | wait_ms(100) 47 | digitalWrite(27, 1) 48 | wait_ms(100) 49 | digitalWrite(27, 0) 50 | wait_ms(100) 51 | while (uart1.any()) == 0: 52 | sendAT('AT') 53 | readResponse(1000, 'OK') 54 | sendAT('AT+CPSMS=0') 55 | readResponse(1000, 'OK') 56 | readResponse(10000, '+CPSMSTATUS: "EXIT PSM"') 57 | 58 | # Describe this function... 59 | def sendAT(cmd): 60 | global timeout, waitFor, logentry, btnAPressed, btnBPressed, btnCPressed, list1, list2, response, list3, list4, list5, list6, list7, uart1 61 | printLog((str('> ') + str(cmd))) 62 | uart1.write(str(cmd)+"\r\n") 63 | wait_ms(250) 64 | 65 | # Describe this function... 66 | def readResponse(timeout, waitFor): 67 | global cmd, logentry, btnAPressed, btnBPressed, btnCPressed, list1, list2, response, list3, list4, list5, list6, list7, uart1 68 | while timeout: 69 | if uart1.any(): 70 | response = (uart1.readline()).decode() 71 | response = response.strip() 72 | if response != '': 73 | printLog((str('< ') + str(response))) 74 | if response == waitFor: 75 | break 76 | wait_ms(100) 77 | timeout = (timeout if isinstance(timeout, Number) else 0) + -100 78 | l_timeout.set_text(str(timeout)) 79 | if timeout == 0: 80 | l_timeout.set_text('timeout') 81 | else: 82 | l_timeout.set_text('found') 83 | 84 | # Describe this function... 85 | def gotoPSM(): 86 | global cmd, timeout, waitFor, logentry, btnAPressed, btnBPressed, btnCPressed, list1, list2, response, list3, list4, list5, list6, list7, uart1 87 | sendAT('AT+CPSMSTATUS=1') 88 | readResponse(1000, 'OK') 89 | sendAT('AT+CPSMS=1,,,"01011111","00000001"') 90 | readResponse(1000, 'OK') 91 | readResponse(30000, '+CPSMSTATUS: "ENTER PSM"') 92 | 93 | # Describe this function... 94 | def printLog(logentry): 95 | global cmd, timeout, waitFor, btnAPressed, btnBPressed, btnCPressed, list1, list2, response, list3, list4, list5, list6, list7, uart1 96 | list1 = list2 97 | list2 = list3 98 | list3 = list4 99 | list4 = list5 100 | list5 = list6 101 | list6 = list7 102 | list7 = logentry 103 | l_list1.set_text(str(list1)) 104 | l_list2.set_text(str(list2)) 105 | l_list3.set_text(str(list3)) 106 | l_list4.set_text(str(list4)) 107 | l_list5.set_text(str(list5)) 108 | l_list6.set_text(str(list6)) 109 | l_list7.set_text(str(list7)) 110 | 111 | def buttonA_wasPressed(): 112 | global btnAPressed, btnBPressed, btnCPressed, list1, timeout, list2, cmd, response, list3, list4, list5, list6, list7, logentry, waitFor, uart1 113 | btnAPressed = True 114 | pass 115 | btnA.wasPressed(buttonA_wasPressed) 116 | 117 | def buttonB_wasPressed(): 118 | global btnAPressed, btnBPressed, btnCPressed, list1, timeout, list2, cmd, response, list3, list4, list5, list6, list7, logentry, waitFor, uart1 119 | btnBPressed = True 120 | pass 121 | btnB.wasPressed(buttonB_wasPressed) 122 | 123 | def buttonC_wasPressed(): 124 | global btnAPressed, btnBPressed, btnCPressed, list1, timeout, list2, cmd, response, list3, list4, list5, list6, list7, logentry, waitFor, uart1 125 | btnCPressed = True 126 | pass 127 | btnC.wasPressed(buttonC_wasPressed) 128 | 129 | uart1 = machine.UART(1, tx=0, rx=35) 130 | uart1.init(115200, bits=8, parity=None, stop=1) 131 | while uart1.any(): 132 | action.set_text(str(uart1.readline())) 133 | while True: 134 | if btnAPressed: 135 | btnAPressed = False 136 | action.set_text('PowerOn start') 137 | powerOn() 138 | action.set_text('PowerOn end') 139 | if btnBPressed: 140 | btnBPressed = False 141 | action.set_text('SendAT start') 142 | sendAT('AT') 143 | readResponse(1000, 'OK') 144 | action.set_text('SendAT end') 145 | if btnCPressed: 146 | btnCPressed = False 147 | action.set_text('GotoPSM start') 148 | gotoPSM() 149 | action.set_text('GotoPSM end') 150 | wait_ms(2) 151 | -------------------------------------------------------------------------------- /M5Core2/LightSleepWakeFromTouch/LightSleepWakeFromTouch.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | void setup() 7 | { 8 | M5.begin(); 9 | } 10 | 11 | void loop() 12 | { 13 | gpio_wakeup_enable((gpio_num_t) CST_INT, GPIO_INTR_LOW_LEVEL); 14 | esp_sleep_enable_gpio_wakeup(); 15 | Serial.println("before light sleep"); 16 | Serial.flush(); 17 | esp_light_sleep_start(); 18 | Serial.println("after light sleep"); 19 | delay(3000); 20 | } 21 | -------------------------------------------------------------------------------- /M5Core2/SimpleTone.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | # Note: LVGL Micropython firmware (not UIFlow) is required for this example to run. 5 | # https://github.com/lvgl/lv_micropython/tree/master/ports/esp32/boards/M5CORE2 6 | 7 | # Tone example adapted for M5Core2 8 | # https://github.com/miketeachman/micropython-i2s-examples/blob/master/examples/play_tone.py 9 | import os 10 | import math 11 | import struct 12 | from machine import I2S 13 | from machine import Pin 14 | 15 | def make_tone(rate, bits, frequency): 16 | # create a buffer containing the pure tone samples 17 | samples_per_cycle = rate // frequency 18 | sample_size_in_bytes = bits // 8 19 | samples = bytearray(samples_per_cycle * sample_size_in_bytes) 20 | volume_reduction_factor = 4 21 | range = pow(2, bits) // 2 // volume_reduction_factor 22 | 23 | if bits == 16: 24 | format = " 8 | #include 9 | 10 | m5gfx::touch_point_t tp; 11 | LGFX_Button btn1; 12 | LGFX_Button btn2; 13 | 14 | void setup() 15 | { 16 | auto cfg = M5.config(); 17 | cfg.serial_baudrate = 115200; 18 | M5.begin(cfg); 19 | 20 | btn1.initButton(&M5.Lcd, 100, 120, 100, 50, TFT_RED, TFT_YELLOW, TFT_BLACK, "Btn 1"); 21 | btn1.drawButton(); 22 | btn2.initButton(&M5.Lcd, 220, 120, 100, 50, TFT_RED, TFT_YELLOW, TFT_BLACK, "Btn 2"); 23 | btn2.drawButton(); 24 | } 25 | 26 | void loop() 27 | { 28 | M5.update(); 29 | 30 | if(M5.Touch.getCount() > 0) 31 | { 32 | tp = M5.Touch.getTouchPointRaw(); 33 | } 34 | else 35 | { 36 | tp.x = -1; 37 | tp.y = -1; 38 | } 39 | 40 | if(btn1.contains(tp.x, tp.y)) 41 | { 42 | btn1.press(true); 43 | } 44 | else 45 | { 46 | btn1.press(false); 47 | } 48 | if(btn1.justPressed()) 49 | { 50 | Serial.println("Btn1 just pressed"); 51 | btn1.drawButton(true); 52 | } 53 | if(btn1.isPressed()) 54 | { 55 | Serial.println("Btn1 is pressed"); 56 | } 57 | if(btn1.justReleased()) 58 | { 59 | Serial.println("Btn1 just released"); 60 | btn1.drawButton(false); 61 | } 62 | 63 | if(btn2.contains(tp.x, tp.y)) 64 | { 65 | btn2.press(true); 66 | } 67 | else 68 | { 69 | btn2.press(false); 70 | } 71 | if(btn2.justPressed()) 72 | { 73 | Serial.println("Btn2 just pressed"); 74 | btn2.drawButton(true); 75 | } 76 | if(btn2.isPressed()) 77 | { 78 | Serial.println("Btn2 is pressed"); 79 | } 80 | if(btn2.justReleased()) 81 | { 82 | Serial.println("Btn2 just released"); 83 | btn2.drawButton(false); 84 | } 85 | 86 | delay(50); 87 | } 88 | -------------------------------------------------------------------------------- /M5Core2v1.1/BlueLEDTest/BlueLEDTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5GFX 5 | // https://github.com/m5stack/M5Unified 6 | // 7 | // Note: by default the blue LED indicates the battery charging state. 8 | // In order to control it, the mode needs to be set to manual. 9 | 10 | #include 11 | 12 | #define AXP2101_ADDR 0x34 13 | #define CHG_LED_CFG 0x69 14 | #define CHG_LED_EN 0b00000001 // Enable 15 | #define CHG_LED_MAN 0b00000100 // Manual mode 16 | #define CHG_LED_ON 0b00110000 // Drive low 17 | #define CHG_LED_OFF 0b00000000 // HiZ 18 | 19 | void setup() 20 | { 21 | auto cfg = M5.config(); 22 | M5.begin(cfg); 23 | } 24 | 25 | void loop() 26 | { 27 | M5.In_I2C.writeRegister8(AXP2101_ADDR, CHG_LED_CFG, CHG_LED_EN | CHG_LED_MAN | CHG_LED_ON, 200000); 28 | delay(50); 29 | M5.In_I2C.writeRegister8(AXP2101_ADDR, CHG_LED_CFG, CHG_LED_EN | CHG_LED_MAN | CHG_LED_OFF, 200000); 30 | delay(2000); 31 | } 32 | -------------------------------------------------------------------------------- /M5CoreInk/CountDownThenPowerOff.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | from m5stack import * 5 | from m5ui import * 6 | from uiflow import * 7 | import time 8 | 9 | setScreenColor(lcd.WHITE) 10 | my_time = None 11 | label0 = M5TextBox(41, 64, "Text", lcd.FONT_DejaVu56, lcd.BLACK, rotate=0) 12 | from numbers import Number 13 | 14 | setScreenColor(lcd.WHITE) 15 | coreInkShow() 16 | my_time = 10 17 | while my_time > 0: 18 | my_time = (my_time if isinstance(my_time, Number) else 0) + -1 19 | label0.setText(str(my_time)) 20 | coreInkParitalShow(0, 64, 200, 56) 21 | wait(1) 22 | speaker.sing(889, 1) 23 | wait(2) 24 | power.off() 25 | -------------------------------------------------------------------------------- /M5CoreInk/RTC_Clock/RTC_Clock.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5Core-Ink 5 | // https://github.com/m5stack/M5GFX 6 | 7 | 8 | // This RTC clock example keeps the system mostly in shutdown mode and 9 | // only wakes up every 58 seconds for a brief period of time during 10 | // which the time and date are updated on the ink display. 11 | // 12 | // When started initially or via power button a full ink display refresh 13 | // is executed to clear the display. 14 | // The current time and date are fetched via NTP and shown on the ink display. 15 | // After waiting for the full minute the system is put into shutdown 16 | // mode for about 58 seconds. 17 | // When the RTC timer expires (just befor the next minute change) the 18 | // system is powered on. 19 | // The ink display is updated with the current time and date. 20 | // Then the system goes back into shutdown mode for about 58 seconds and 21 | // the cycle begins anew. 22 | // Every hour a full ink display refresh is executed to keep the ink 23 | // display crisp. 24 | // 25 | // Note: If WiFi connection fails - some fantasy time and date are used. 26 | // Note: System will not enter shutdown mode while USB is connected. 27 | 28 | #include "M5CoreInk.h" 29 | #include 30 | #include "time.h" 31 | 32 | const char* ssid = "YOUR_SSID"; 33 | const char* password = "YOUR_PASSWORD"; 34 | const char* ntpServer = "pool.ntp.org"; 35 | const long gmtOffset_sec = 3600; 36 | const int daylightOffset_sec = 3600; 37 | 38 | // every hour at minute 45 do a full ink display refresh 39 | #define FULL_REFRESH_MINUTE (45) 40 | 41 | Ink_Sprite TimePageSprite(&M5.M5Ink); 42 | 43 | void printLocalTimeAndSetRTC() 44 | { 45 | struct tm timeinfo; 46 | 47 | if(getLocalTime(&timeinfo) == false) 48 | { 49 | Serial.println("Failed to obtain time"); 50 | return; 51 | } 52 | Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); 53 | 54 | RTC_TimeTypeDef time; 55 | time.Hours = timeinfo.tm_hour; 56 | time.Minutes = timeinfo.tm_min; 57 | time.Seconds = timeinfo.tm_sec; 58 | M5.rtc.SetTime(&time); 59 | 60 | RTC_DateTypeDef date; 61 | date.Date = timeinfo.tm_mday; 62 | date.Month = timeinfo.tm_mon + 1; 63 | date.Year = timeinfo.tm_year + 1900; 64 | M5.rtc.SetDate(&date); 65 | } 66 | 67 | void getNTPTime() 68 | { 69 | // Try to connect for 10 seconds 70 | uint32_t connect_timeout = millis() + 10000; 71 | 72 | Serial.printf("Connecting to %s ", ssid); 73 | WiFi.begin(ssid, password); 74 | while((WiFi.status() != WL_CONNECTED) && (millis() < connect_timeout)) 75 | { 76 | delay(500); 77 | Serial.print("."); 78 | } 79 | if(WiFi.status() != WL_CONNECTED) 80 | { 81 | // WiFi connection failed - set fantasy time and date 82 | RTC_TimeTypeDef time; 83 | time.Hours = 6; 84 | time.Minutes = 43; 85 | time.Seconds = 50; 86 | M5.rtc.SetTime(&time); 87 | 88 | RTC_DateTypeDef date; 89 | date.Date = 4; 90 | date.Month = 12; 91 | date.Year = 2020; 92 | M5.rtc.SetDate(&date); 93 | return; 94 | } 95 | 96 | Serial.println("Connected"); 97 | 98 | configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); 99 | printLocalTimeAndSetRTC(); 100 | 101 | WiFi.disconnect(true); 102 | WiFi.mode(WIFI_OFF); 103 | } 104 | 105 | void drawTimeAndDate(RTC_TimeTypeDef time, RTC_DateTypeDef date) 106 | { 107 | char buf[11]; 108 | 109 | snprintf(buf, 6, "%02d:%02d", time.Hours, time.Minutes); 110 | TimePageSprite.drawString(40, 20, buf, &AsciiFont24x48); 111 | snprintf(buf, 11, "%02d.%02d.%02d", date.Date, date.Month, date.Year - 2000); 112 | TimePageSprite.drawString(4, 70, buf, &AsciiFont24x48); 113 | } 114 | 115 | void setup() 116 | { 117 | // Check power on reason before calling M5.begin() 118 | // which calls Rtc.begin() which clears the timer flag. 119 | uint8_t data = 0; 120 | Wire1.begin(21, 22); 121 | Wire1.beginTransmission(BM8563_I2C_ADDR); 122 | Wire1.write(0x01); 123 | Wire1.endTransmission(); 124 | if(Wire1.requestFrom(BM8563_I2C_ADDR, 1)) 125 | { 126 | data = Wire1.read(); 127 | } 128 | 129 | // Do not yet init ink display 130 | M5.begin(false); 131 | 132 | // Green LED - indicates ESP32 is running 133 | digitalWrite(LED_EXT_PIN, LOW); 134 | 135 | RTC_TimeTypeDef time; 136 | RTC_DateTypeDef date; 137 | 138 | // Check timer flag 139 | if((data & 0b00000100) == 0b00000100) 140 | { 141 | Serial.println("Power on by: RTC timer"); 142 | 143 | M5.rtc.GetTime(&time); 144 | M5.rtc.GetDate(&date); 145 | 146 | // Full refresh once per hour 147 | if(time.Minutes == FULL_REFRESH_MINUTE - 1) 148 | { 149 | // Full ink display init 150 | M5.M5Ink.begin(); 151 | M5.M5Ink.clear(); 152 | } 153 | else 154 | { 155 | // Partial ink display init to avoid flickering 156 | M5.M5Ink.init_without_reset(); 157 | M5.M5Ink.setEpdMode(epd_mode_t::epd_text); 158 | M5.M5Ink.invertDisplay(true); 159 | } 160 | } 161 | else 162 | { 163 | Serial.println("Power on by: power button"); 164 | 165 | // Full ink display init 166 | M5.M5Ink.begin(); 167 | M5.M5Ink.clear(); 168 | 169 | // Fetch current time from Internet 170 | getNTPTime(); 171 | M5.rtc.GetTime(&time); 172 | M5.rtc.GetDate(&date); 173 | } 174 | 175 | // After every shutdown the sprite is created anew. 176 | // But the sprite doesn't know about the current image on the 177 | // ink display therefore the same time and date, as have been 178 | // drawn before the shutdown, are redrawn. 179 | // This is required, else drawing new time and date only adds 180 | // pixels to the already drawn pixels instead of clearing the 181 | // previous time and date and then draw the new time and date. 182 | TimePageSprite.creatSprite(0, 0, 200, 200); 183 | 184 | drawTimeAndDate(time, date); 185 | 186 | TimePageSprite.pushSprite(); 187 | 188 | // Wait until full minute, e.g. seconds are 0 189 | while((time.Seconds != 0)) 190 | { 191 | M5.rtc.GetTime(&time); 192 | delay(200); 193 | } 194 | M5.rtc.GetDate(&date); 195 | 196 | // Draw new time and date 197 | drawTimeAndDate(time, date); 198 | 199 | TimePageSprite.pushSprite(); 200 | 201 | Serial.printf("Shutdown...\n"); 202 | Serial.flush(); 203 | 204 | // Full refresh once per hour 205 | if(time.Minutes == FULL_REFRESH_MINUTE - 1) 206 | { 207 | // Allow extra time for full ink refresh 208 | // Shutdown for 55 seconds only 209 | M5.shutdown(55); 210 | return; 211 | } 212 | // Shutdown for 58 seconds 213 | M5.shutdown(58); 214 | } 215 | 216 | void loop() 217 | { 218 | 219 | } 220 | -------------------------------------------------------------------------------- /M5CoreS3/COMLTETest/COMLTETest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5CoreS3 5 | // https://github.com/m5stack/M5GFX 6 | // https://github.com/m5stack/M5Unified 7 | 8 | // Note: set DIP switch 16 and 17 to ON position in COM.LTE(4G) module 9 | #define RX_PIN 18 10 | #define TX_PIN 17 11 | 12 | #include "M5CoreS3.h" 13 | 14 | String myCmd = ""; 15 | String myRes = ""; 16 | int myCount = 0; 17 | 18 | void setup() 19 | { 20 | auto cfg = M5.config(); 21 | CoreS3.begin(cfg); 22 | CoreS3.Display.setTextSize(2); 23 | Serial2.begin(115200, SERIAL_8N1, RX_PIN, TX_PIN); 24 | } 25 | 26 | void loop() 27 | { 28 | myCmd = "AT\r"; 29 | Serial2.print(myCmd); 30 | delay(100); 31 | myRes = ""; 32 | while(Serial2.available() > 0) 33 | { 34 | myRes += Serial2.readStringUntil('\n') + " "; 35 | delay(10); 36 | } 37 | CoreS3.Display.clear(); 38 | CoreS3.Display.setCursor(0, 0); 39 | CoreS3.Display.printf("# : %d\n", myCount++); 40 | CoreS3.Display.printf("cmd: %s\n", myCmd.c_str()); 41 | CoreS3.Display.printf("res: %s\n", myRes.c_str()); 42 | delay(1000); 43 | } 44 | -------------------------------------------------------------------------------- /M5CoreS3/CamWebServer/CamWebServer.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5GFX 5 | // https://github.com/m5stack/M5Unified 6 | // https://github.com/m5stack/M5CoreS3 7 | // 8 | // Based on CameraWebServer example by Espressif 9 | // https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer 10 | // 11 | // Notes: 12 | // - Maximum frame size of M5CoreS3 camera GC0308 is 640x480 (VGA) 13 | // - M5CoreS3 camera GC0308 doesn't support JPEG 14 | // - most setting in the webpage have no effect 15 | // - crashes sometimes at startup 16 | 17 | #include 18 | #include "esp_camera.h" 19 | #include 20 | 21 | #define PWDN_GPIO_NUM -1 22 | #define RESET_GPIO_NUM -1 23 | #define XCLK_GPIO_NUM 2 24 | #define SIOD_GPIO_NUM 12 25 | #define SIOC_GPIO_NUM 11 26 | 27 | #define Y9_GPIO_NUM 47 28 | #define Y8_GPIO_NUM 48 29 | #define Y7_GPIO_NUM 16 30 | #define Y6_GPIO_NUM 15 31 | #define Y5_GPIO_NUM 42 32 | #define Y4_GPIO_NUM 41 33 | #define Y3_GPIO_NUM 40 34 | #define Y2_GPIO_NUM 39 35 | #define VSYNC_GPIO_NUM 46 36 | #define HREF_GPIO_NUM 38 37 | #define PCLK_GPIO_NUM 45 38 | 39 | const char *ssid = "YourSSID"; 40 | const char *password = "YourPW"; 41 | 42 | void startCameraServer(); 43 | void setupLedFlash(int pin); 44 | 45 | void setup() { 46 | auto cfg = M5.config(); 47 | CoreS3.begin(cfg); 48 | 49 | delay(2000); 50 | 51 | Serial.setDebugOutput(true); 52 | Serial.println("hello"); 53 | M5.Lcd.println("hello"); 54 | 55 | camera_config_t config; 56 | config.ledc_channel = LEDC_CHANNEL_0; 57 | config.ledc_timer = LEDC_TIMER_0; 58 | config.pin_d0 = Y2_GPIO_NUM; 59 | config.pin_d1 = Y3_GPIO_NUM; 60 | config.pin_d2 = Y4_GPIO_NUM; 61 | config.pin_d3 = Y5_GPIO_NUM; 62 | config.pin_d4 = Y6_GPIO_NUM; 63 | config.pin_d5 = Y7_GPIO_NUM; 64 | config.pin_d6 = Y8_GPIO_NUM; 65 | config.pin_d7 = Y9_GPIO_NUM; 66 | config.pin_xclk = XCLK_GPIO_NUM; 67 | config.pin_pclk = PCLK_GPIO_NUM; 68 | config.pin_vsync = VSYNC_GPIO_NUM; 69 | config.pin_href = HREF_GPIO_NUM; 70 | config.pin_sccb_sda = SIOD_GPIO_NUM; 71 | config.pin_sccb_scl = SIOC_GPIO_NUM; 72 | config.pin_pwdn = PWDN_GPIO_NUM; 73 | config.pin_reset = RESET_GPIO_NUM; 74 | config.xclk_freq_hz = 20000000; 75 | config.frame_size = FRAMESIZE_QVGA; // FRAMESIZE_VGA is slow 76 | config.pixel_format = PIXFORMAT_RGB565; // GC0308 doesn't support JPEG 77 | config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; 78 | config.fb_location = CAMERA_FB_IN_PSRAM; 79 | config.fb_count = 2; 80 | config.sccb_i2c_port = M5.In_I2C.getPort(); 81 | M5.In_I2C.release(); 82 | 83 | // camera init 84 | esp_err_t err = esp_camera_init(&config); 85 | if (err != ESP_OK) { 86 | Serial.printf("Camera init failed with error 0x%x.", err); 87 | M5.Lcd.printf("Camera init failed with error 0x%x.", err); 88 | return; 89 | } 90 | Serial.println("Camera init ok."); 91 | M5.Lcd.println("Camera init ok."); 92 | 93 | sensor_t *s = esp_camera_sensor_get(); 94 | if (s == NULL) { 95 | Serial.println("No camera sensor found!"); 96 | M5.Lcd.println("No camera sensor found!"); 97 | return; 98 | } 99 | Serial.println("Camera sensor found."); 100 | M5.Lcd.println("Camera sensor found."); 101 | 102 | if (s->id.PID != GC0308_PID) { 103 | Serial.printf("Wrong camera PID: 0x%x. Should be: 0x%x.\n", s->id.PID, GC0308_PID); 104 | M5.Lcd.printf("Wrong camera PID: 0x%x. Should be: 0x%x.\n", s->id.PID, GC0308_PID); 105 | } 106 | Serial.printf("Camera PID: 0x%x GC0308_PID: 0x%x.\n", s->id.PID, GC0308_PID); // GC0308_PID 107 | M5.Lcd.printf("Camera PID: 0x%x GC0308_PID: 0x%x.\n", s->id.PID, GC0308_PID); // GC0308_PID 108 | 109 | WiFi.begin(ssid, password); 110 | WiFi.setSleep(false); 111 | 112 | while (WiFi.status() != WL_CONNECTED) { 113 | delay(500); 114 | Serial.print("."); 115 | M5.Lcd.print("."); 116 | } 117 | Serial.println(""); 118 | Serial.println("WiFi connected"); 119 | M5.Lcd.println(""); 120 | M5.Lcd.println("WiFi connected"); 121 | 122 | startCameraServer(); 123 | 124 | Serial.print("Camera Ready! Use 'http://"); 125 | Serial.print(WiFi.localIP()); 126 | Serial.println("' to connect"); 127 | M5.Lcd.print("Camera Ready! Use 'http://"); 128 | M5.Lcd.print(WiFi.localIP()); 129 | M5.Lcd.println("' to connect"); 130 | } 131 | 132 | void loop() { 133 | // Do nothing. Everything is done in another task by the web server 134 | delay(10000); 135 | } 136 | -------------------------------------------------------------------------------- /M5CoreS3/CamWebServer/app_httpd.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "esp_http_server.h" 15 | #include "esp_timer.h" 16 | #include "esp_camera.h" 17 | #include "img_converters.h" 18 | #include "fb_gfx.h" 19 | #include "esp32-hal-ledc.h" 20 | #include "sdkconfig.h" 21 | #include "camera_index.h" 22 | 23 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 24 | #include "esp32-hal-log.h" 25 | #endif 26 | 27 | // Face Detection will not work on boards without (or with disabled) PSRAM 28 | #ifdef BOARD_HAS_PSRAM 29 | // Face Recognition takes upward from 15 seconds per frame on chips other than ESP32S3 30 | // Makes no sense to have it enabled for them 31 | #if CONFIG_IDF_TARGET_ESP32S3 32 | #define CONFIG_ESP_FACE_RECOGNITION_ENABLED 1 33 | #define CONFIG_ESP_FACE_DETECT_ENABLED 1 34 | #else 35 | #define CONFIG_ESP_FACE_RECOGNITION_ENABLED 0 36 | #define CONFIG_ESP_FACE_DETECT_ENABLED 0 37 | #endif 38 | #else 39 | #define CONFIG_ESP_FACE_DETECT_ENABLED 0 40 | #define CONFIG_ESP_FACE_RECOGNITION_ENABLED 0 41 | #endif 42 | 43 | #if CONFIG_ESP_FACE_DETECT_ENABLED 44 | 45 | #include 46 | #include "human_face_detect_msr01.hpp" 47 | #include "human_face_detect_mnp01.hpp" 48 | 49 | #define TWO_STAGE 1 /* very large firmware, very slow, reboots when streaming... 62 | 63 | #define FACE_ID_SAVE_NUMBER 7 64 | #endif 65 | 66 | #define FACE_COLOR_WHITE 0x00FFFFFF 67 | #define FACE_COLOR_BLACK 0x00000000 68 | #define FACE_COLOR_RED 0x000000FF 69 | #define FACE_COLOR_GREEN 0x0000FF00 70 | #define FACE_COLOR_BLUE 0x00FF0000 71 | #define FACE_COLOR_YELLOW (FACE_COLOR_RED | FACE_COLOR_GREEN) 72 | #define FACE_COLOR_CYAN (FACE_COLOR_BLUE | FACE_COLOR_GREEN) 73 | #define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED) 74 | #endif 75 | 76 | // Enable LED FLASH setting 77 | #define CONFIG_LED_ILLUMINATOR_ENABLED 0 78 | 79 | // LED FLASH setup 80 | #if CONFIG_LED_ILLUMINATOR_ENABLED 81 | 82 | #define LED_LEDC_GPIO 22 //configure LED pin 83 | #define CONFIG_LED_MAX_INTENSITY 255 84 | 85 | int led_duty = 0; 86 | bool isStreaming = false; 87 | 88 | #endif 89 | 90 | typedef struct { 91 | httpd_req_t *req; 92 | size_t len; 93 | } jpg_chunking_t; 94 | 95 | #define PART_BOUNDARY "123456789000000000000987654321" 96 | static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; 97 | static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; 98 | static const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n"; 99 | 100 | httpd_handle_t stream_httpd = NULL; 101 | httpd_handle_t camera_httpd = NULL; 102 | 103 | #if CONFIG_ESP_FACE_DETECT_ENABLED 104 | 105 | static int8_t detection_enabled = 0; 106 | 107 | // #if TWO_STAGE 108 | // static HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 109 | // static HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 110 | // #else 111 | // static HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 112 | // #endif 113 | 114 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 115 | static int8_t recognition_enabled = 0; 116 | static int8_t is_enrolling = 0; 117 | 118 | #if QUANT_TYPE 119 | // S16 model 120 | FaceRecognition112V1S16 recognizer; 121 | #else 122 | // S8 model 123 | FaceRecognition112V1S8 recognizer; 124 | #endif 125 | #endif 126 | 127 | #endif 128 | 129 | typedef struct { 130 | size_t size; //number of values used for filtering 131 | size_t index; //current value index 132 | size_t count; //value count 133 | int sum; 134 | int *values; //array to be filled with values 135 | } ra_filter_t; 136 | 137 | static ra_filter_t ra_filter; 138 | 139 | static ra_filter_t *ra_filter_init(ra_filter_t *filter, size_t sample_size) { 140 | memset(filter, 0, sizeof(ra_filter_t)); 141 | 142 | filter->values = (int *)malloc(sample_size * sizeof(int)); 143 | if (!filter->values) { 144 | return NULL; 145 | } 146 | memset(filter->values, 0, sample_size * sizeof(int)); 147 | 148 | filter->size = sample_size; 149 | return filter; 150 | } 151 | 152 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 153 | static int ra_filter_run(ra_filter_t *filter, int value) { 154 | if (!filter->values) { 155 | return value; 156 | } 157 | filter->sum -= filter->values[filter->index]; 158 | filter->values[filter->index] = value; 159 | filter->sum += filter->values[filter->index]; 160 | filter->index++; 161 | filter->index = filter->index % filter->size; 162 | if (filter->count < filter->size) { 163 | filter->count++; 164 | } 165 | return filter->sum / filter->count; 166 | } 167 | #endif 168 | 169 | #if CONFIG_ESP_FACE_DETECT_ENABLED 170 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 171 | static void rgb_print(fb_data_t *fb, uint32_t color, const char *str) { 172 | fb_gfx_print(fb, (fb->width - (strlen(str) * 14)) / 2, 10, color, str); 173 | } 174 | 175 | static int rgb_printf(fb_data_t *fb, uint32_t color, const char *format, ...) { 176 | char loc_buf[64]; 177 | char *temp = loc_buf; 178 | int len; 179 | va_list arg; 180 | va_list copy; 181 | va_start(arg, format); 182 | va_copy(copy, arg); 183 | len = vsnprintf(loc_buf, sizeof(loc_buf), format, arg); 184 | va_end(copy); 185 | if (len >= sizeof(loc_buf)) { 186 | temp = (char *)malloc(len + 1); 187 | if (temp == NULL) { 188 | return 0; 189 | } 190 | } 191 | vsnprintf(temp, len + 1, format, arg); 192 | va_end(arg); 193 | rgb_print(fb, color, temp); 194 | if (len > 64) { 195 | free(temp); 196 | } 197 | return len; 198 | } 199 | #endif 200 | static void draw_face_boxes(fb_data_t *fb, std::list *results, int face_id) { 201 | int x, y, w, h; 202 | uint32_t color = FACE_COLOR_YELLOW; 203 | if (face_id < 0) { 204 | color = FACE_COLOR_RED; 205 | } else if (face_id > 0) { 206 | color = FACE_COLOR_GREEN; 207 | } 208 | if (fb->bytes_per_pixel == 2) { 209 | //color = ((color >> 8) & 0xF800) | ((color >> 3) & 0x07E0) | (color & 0x001F); 210 | color = ((color >> 16) & 0x001F) | ((color >> 3) & 0x07E0) | ((color << 8) & 0xF800); 211 | } 212 | int i = 0; 213 | for (std::list::iterator prediction = results->begin(); prediction != results->end(); prediction++, i++) { 214 | // rectangle box 215 | x = (int)prediction->box[0]; 216 | y = (int)prediction->box[1]; 217 | w = (int)prediction->box[2] - x + 1; 218 | h = (int)prediction->box[3] - y + 1; 219 | if ((x + w) > fb->width) { 220 | w = fb->width - x; 221 | } 222 | if ((y + h) > fb->height) { 223 | h = fb->height - y; 224 | } 225 | fb_gfx_drawFastHLine(fb, x, y, w, color); 226 | fb_gfx_drawFastHLine(fb, x, y + h - 1, w, color); 227 | fb_gfx_drawFastVLine(fb, x, y, h, color); 228 | fb_gfx_drawFastVLine(fb, x + w - 1, y, h, color); 229 | #if TWO_STAGE 230 | // landmarks (left eye, mouth left, nose, right eye, mouth right) 231 | int x0, y0, j; 232 | for (j = 0; j < 10; j += 2) { 233 | x0 = (int)prediction->keypoint[j]; 234 | y0 = (int)prediction->keypoint[j + 1]; 235 | fb_gfx_fillRect(fb, x0, y0, 3, 3, color); 236 | } 237 | #endif 238 | } 239 | } 240 | 241 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 242 | static int run_face_recognition(fb_data_t *fb, std::list *results) { 243 | std::vector landmarks = results->front().keypoint; 244 | int id = -1; 245 | 246 | Tensor tensor; 247 | tensor.set_element((uint8_t *)fb->data).set_shape({fb->height, fb->width, 3}).set_auto_free(false); 248 | 249 | int enrolled_count = recognizer.get_enrolled_id_num(); 250 | 251 | if (enrolled_count < FACE_ID_SAVE_NUMBER && is_enrolling) { 252 | id = recognizer.enroll_id(tensor, landmarks, "", true); 253 | log_i("Enrolled ID: %d", id); 254 | rgb_printf(fb, FACE_COLOR_CYAN, "ID[%u]", id); 255 | } 256 | 257 | face_info_t recognize = recognizer.recognize(tensor, landmarks); 258 | if (recognize.id >= 0) { 259 | rgb_printf(fb, FACE_COLOR_GREEN, "ID[%u]: %.2f", recognize.id, recognize.similarity); 260 | } else { 261 | rgb_print(fb, FACE_COLOR_RED, "Intruder Alert!"); 262 | } 263 | return recognize.id; 264 | } 265 | #endif 266 | #endif 267 | 268 | #if CONFIG_LED_ILLUMINATOR_ENABLED 269 | void enable_led(bool en) { // Turn LED On or Off 270 | int duty = en ? led_duty : 0; 271 | if (en && isStreaming && (led_duty > CONFIG_LED_MAX_INTENSITY)) { 272 | duty = CONFIG_LED_MAX_INTENSITY; 273 | } 274 | ledcWrite(LED_LEDC_GPIO, duty); 275 | //ledc_set_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL, duty); 276 | //ledc_update_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL); 277 | log_i("Set LED intensity to %d", duty); 278 | } 279 | #endif 280 | 281 | static esp_err_t bmp_handler(httpd_req_t *req) { 282 | camera_fb_t *fb = NULL; 283 | esp_err_t res = ESP_OK; 284 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 285 | uint64_t fr_start = esp_timer_get_time(); 286 | #endif 287 | fb = esp_camera_fb_get(); 288 | if (!fb) { 289 | log_e("Camera capture failed"); 290 | httpd_resp_send_500(req); 291 | return ESP_FAIL; 292 | } 293 | 294 | httpd_resp_set_type(req, "image/x-windows-bmp"); 295 | httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.bmp"); 296 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 297 | 298 | char ts[32]; 299 | snprintf(ts, 32, "%ld.%06ld", fb->timestamp.tv_sec, fb->timestamp.tv_usec); 300 | httpd_resp_set_hdr(req, "X-Timestamp", (const char *)ts); 301 | 302 | uint8_t *buf = NULL; 303 | size_t buf_len = 0; 304 | bool converted = frame2bmp(fb, &buf, &buf_len); 305 | esp_camera_fb_return(fb); 306 | if (!converted) { 307 | log_e("BMP Conversion failed"); 308 | httpd_resp_send_500(req); 309 | return ESP_FAIL; 310 | } 311 | res = httpd_resp_send(req, (const char *)buf, buf_len); 312 | free(buf); 313 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 314 | uint64_t fr_end = esp_timer_get_time(); 315 | #endif 316 | log_i("BMP: %llums, %uB", (uint64_t)((fr_end - fr_start) / 1000), buf_len); 317 | return res; 318 | } 319 | 320 | static size_t jpg_encode_stream(void *arg, size_t index, const void *data, size_t len) { 321 | jpg_chunking_t *j = (jpg_chunking_t *)arg; 322 | if (!index) { 323 | j->len = 0; 324 | } 325 | if (httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK) { 326 | return 0; 327 | } 328 | j->len += len; 329 | return len; 330 | } 331 | 332 | static esp_err_t capture_handler(httpd_req_t *req) { 333 | camera_fb_t *fb = NULL; 334 | esp_err_t res = ESP_OK; 335 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 336 | int64_t fr_start = esp_timer_get_time(); 337 | #endif 338 | 339 | #if CONFIG_LED_ILLUMINATOR_ENABLED 340 | enable_led(true); 341 | vTaskDelay(150 / portTICK_PERIOD_MS); // The LED needs to be turned on ~150ms before the call to esp_camera_fb_get() 342 | fb = esp_camera_fb_get(); // or it won't be visible in the frame. A better way to do this is needed. 343 | enable_led(false); 344 | #else 345 | fb = esp_camera_fb_get(); 346 | #endif 347 | 348 | if (!fb) { 349 | log_e("Camera capture failed"); 350 | httpd_resp_send_500(req); 351 | return ESP_FAIL; 352 | } 353 | 354 | httpd_resp_set_type(req, "image/jpeg"); 355 | httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); 356 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 357 | 358 | char ts[32]; 359 | snprintf(ts, 32, "%ld.%06ld", fb->timestamp.tv_sec, fb->timestamp.tv_usec); 360 | httpd_resp_set_hdr(req, "X-Timestamp", (const char *)ts); 361 | 362 | #if CONFIG_ESP_FACE_DETECT_ENABLED 363 | size_t out_len, out_width, out_height; 364 | uint8_t *out_buf; 365 | bool s; 366 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 367 | bool detected = false; 368 | #endif 369 | int face_id = 0; 370 | if (!detection_enabled || fb->width > 400) { 371 | #endif 372 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 373 | size_t fb_len = 0; 374 | #endif 375 | if (fb->format == PIXFORMAT_JPEG) { 376 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 377 | fb_len = fb->len; 378 | #endif 379 | res = httpd_resp_send(req, (const char *)fb->buf, fb->len); 380 | } else { 381 | jpg_chunking_t jchunk = {req, 0}; 382 | res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk) ? ESP_OK : ESP_FAIL; 383 | httpd_resp_send_chunk(req, NULL, 0); 384 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 385 | fb_len = jchunk.len; 386 | #endif 387 | } 388 | esp_camera_fb_return(fb); 389 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 390 | int64_t fr_end = esp_timer_get_time(); 391 | #endif 392 | log_i("JPG: %uB %ums", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start) / 1000)); 393 | return res; 394 | #if CONFIG_ESP_FACE_DETECT_ENABLED 395 | } 396 | 397 | jpg_chunking_t jchunk = {req, 0}; 398 | 399 | if (fb->format == PIXFORMAT_RGB565 400 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 401 | && !recognition_enabled 402 | #endif 403 | ) { 404 | #if TWO_STAGE 405 | HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 406 | HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 407 | std::list &candidates = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 408 | std::list &results = s2.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}, candidates); 409 | #else 410 | HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 411 | std::list &results = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 412 | #endif 413 | if (results.size() > 0) { 414 | fb_data_t rfb; 415 | rfb.width = fb->width; 416 | rfb.height = fb->height; 417 | rfb.data = fb->buf; 418 | rfb.bytes_per_pixel = 2; 419 | rfb.format = FB_RGB565; 420 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 421 | detected = true; 422 | #endif 423 | draw_face_boxes(&rfb, &results, face_id); 424 | } 425 | s = fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, PIXFORMAT_RGB565, 90, jpg_encode_stream, &jchunk); 426 | esp_camera_fb_return(fb); 427 | } else { 428 | out_len = fb->width * fb->height * 3; 429 | out_width = fb->width; 430 | out_height = fb->height; 431 | out_buf = (uint8_t *)malloc(out_len); 432 | if (!out_buf) { 433 | log_e("out_buf malloc failed"); 434 | httpd_resp_send_500(req); 435 | return ESP_FAIL; 436 | } 437 | s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf); 438 | esp_camera_fb_return(fb); 439 | if (!s) { 440 | free(out_buf); 441 | log_e("To rgb888 failed"); 442 | httpd_resp_send_500(req); 443 | return ESP_FAIL; 444 | } 445 | 446 | fb_data_t rfb; 447 | rfb.width = out_width; 448 | rfb.height = out_height; 449 | rfb.data = out_buf; 450 | rfb.bytes_per_pixel = 3; 451 | rfb.format = FB_BGR888; 452 | 453 | #if TWO_STAGE 454 | HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 455 | HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 456 | std::list &candidates = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 457 | std::list &results = s2.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}, candidates); 458 | #else 459 | HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 460 | std::list &results = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 461 | #endif 462 | 463 | if (results.size() > 0) { 464 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 465 | detected = true; 466 | #endif 467 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 468 | if (recognition_enabled) { 469 | face_id = run_face_recognition(&rfb, &results); 470 | } 471 | #endif 472 | draw_face_boxes(&rfb, &results, face_id); 473 | } 474 | 475 | s = fmt2jpg_cb(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, jpg_encode_stream, &jchunk); 476 | free(out_buf); 477 | } 478 | 479 | if (!s) { 480 | log_e("JPEG compression failed"); 481 | httpd_resp_send_500(req); 482 | return ESP_FAIL; 483 | } 484 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 485 | int64_t fr_end = esp_timer_get_time(); 486 | #endif 487 | log_i("FACE: %uB %ums %s%d", (uint32_t)(jchunk.len), (uint32_t)((fr_end - fr_start) / 1000), detected ? "DETECTED " : "", face_id); 488 | return res; 489 | #endif 490 | } 491 | 492 | static esp_err_t stream_handler(httpd_req_t *req) { 493 | camera_fb_t *fb = NULL; 494 | struct timeval _timestamp; 495 | esp_err_t res = ESP_OK; 496 | size_t _jpg_buf_len = 0; 497 | uint8_t *_jpg_buf = NULL; 498 | char *part_buf[128]; 499 | #if CONFIG_ESP_FACE_DETECT_ENABLED 500 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 501 | bool detected = false; 502 | int64_t fr_ready = 0; 503 | int64_t fr_recognize = 0; 504 | int64_t fr_encode = 0; 505 | int64_t fr_face = 0; 506 | int64_t fr_start = 0; 507 | #endif 508 | int face_id = 0; 509 | size_t out_len = 0, out_width = 0, out_height = 0; 510 | uint8_t *out_buf = NULL; 511 | bool s = false; 512 | #if TWO_STAGE 513 | HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 514 | HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 515 | #else 516 | HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 517 | #endif 518 | #endif 519 | 520 | static int64_t last_frame = 0; 521 | if (!last_frame) { 522 | last_frame = esp_timer_get_time(); 523 | } 524 | 525 | res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); 526 | if (res != ESP_OK) { 527 | return res; 528 | } 529 | 530 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 531 | httpd_resp_set_hdr(req, "X-Framerate", "60"); 532 | 533 | #if CONFIG_LED_ILLUMINATOR_ENABLED 534 | isStreaming = true; 535 | enable_led(true); 536 | #endif 537 | 538 | while (true) { 539 | #if CONFIG_ESP_FACE_DETECT_ENABLED 540 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 541 | detected = false; 542 | #endif 543 | face_id = 0; 544 | #endif 545 | 546 | fb = esp_camera_fb_get(); 547 | if (!fb) { 548 | log_e("Camera capture failed"); 549 | res = ESP_FAIL; 550 | } else { 551 | _timestamp.tv_sec = fb->timestamp.tv_sec; 552 | _timestamp.tv_usec = fb->timestamp.tv_usec; 553 | #if CONFIG_ESP_FACE_DETECT_ENABLED 554 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 555 | fr_start = esp_timer_get_time(); 556 | fr_ready = fr_start; 557 | fr_encode = fr_start; 558 | fr_recognize = fr_start; 559 | fr_face = fr_start; 560 | #endif 561 | if (!detection_enabled || fb->width > 400) { 562 | #endif 563 | if (fb->format != PIXFORMAT_JPEG) { 564 | bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); 565 | esp_camera_fb_return(fb); 566 | fb = NULL; 567 | if (!jpeg_converted) { 568 | log_e("JPEG compression failed"); 569 | res = ESP_FAIL; 570 | } 571 | } else { 572 | _jpg_buf_len = fb->len; 573 | _jpg_buf = fb->buf; 574 | } 575 | #if CONFIG_ESP_FACE_DETECT_ENABLED 576 | } else { 577 | if (fb->format == PIXFORMAT_RGB565 578 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 579 | && !recognition_enabled 580 | #endif 581 | ) { 582 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 583 | fr_ready = esp_timer_get_time(); 584 | #endif 585 | #if TWO_STAGE 586 | std::list &candidates = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 587 | std::list &results = s2.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}, candidates); 588 | #else 589 | std::list &results = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 590 | #endif 591 | #if CONFIG_ESP_FACE_DETECT_ENABLED && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 592 | fr_face = esp_timer_get_time(); 593 | fr_recognize = fr_face; 594 | #endif 595 | if (results.size() > 0) { 596 | fb_data_t rfb; 597 | rfb.width = fb->width; 598 | rfb.height = fb->height; 599 | rfb.data = fb->buf; 600 | rfb.bytes_per_pixel = 2; 601 | rfb.format = FB_RGB565; 602 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 603 | detected = true; 604 | #endif 605 | draw_face_boxes(&rfb, &results, face_id); 606 | } 607 | s = fmt2jpg(fb->buf, fb->len, fb->width, fb->height, PIXFORMAT_RGB565, 80, &_jpg_buf, &_jpg_buf_len); 608 | esp_camera_fb_return(fb); 609 | fb = NULL; 610 | if (!s) { 611 | log_e("fmt2jpg failed"); 612 | res = ESP_FAIL; 613 | } 614 | #if CONFIG_ESP_FACE_DETECT_ENABLED && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 615 | fr_encode = esp_timer_get_time(); 616 | #endif 617 | } else { 618 | out_len = fb->width * fb->height * 3; 619 | out_width = fb->width; 620 | out_height = fb->height; 621 | out_buf = (uint8_t *)malloc(out_len); 622 | if (!out_buf) { 623 | log_e("out_buf malloc failed"); 624 | res = ESP_FAIL; 625 | } else { 626 | s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf); 627 | esp_camera_fb_return(fb); 628 | fb = NULL; 629 | if (!s) { 630 | free(out_buf); 631 | log_e("To rgb888 failed"); 632 | res = ESP_FAIL; 633 | } else { 634 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 635 | fr_ready = esp_timer_get_time(); 636 | #endif 637 | 638 | fb_data_t rfb; 639 | rfb.width = out_width; 640 | rfb.height = out_height; 641 | rfb.data = out_buf; 642 | rfb.bytes_per_pixel = 3; 643 | rfb.format = FB_BGR888; 644 | 645 | #if TWO_STAGE 646 | std::list &candidates = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 647 | std::list &results = s2.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}, candidates); 648 | #else 649 | std::list &results = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 650 | #endif 651 | 652 | #if CONFIG_ESP_FACE_DETECT_ENABLED && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 653 | fr_face = esp_timer_get_time(); 654 | fr_recognize = fr_face; 655 | #endif 656 | 657 | if (results.size() > 0) { 658 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 659 | detected = true; 660 | #endif 661 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 662 | if (recognition_enabled) { 663 | face_id = run_face_recognition(&rfb, &results); 664 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 665 | fr_recognize = esp_timer_get_time(); 666 | #endif 667 | } 668 | #endif 669 | draw_face_boxes(&rfb, &results, face_id); 670 | } 671 | s = fmt2jpg(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len); 672 | free(out_buf); 673 | if (!s) { 674 | log_e("fmt2jpg failed"); 675 | res = ESP_FAIL; 676 | } 677 | #if CONFIG_ESP_FACE_DETECT_ENABLED && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 678 | fr_encode = esp_timer_get_time(); 679 | #endif 680 | } 681 | } 682 | } 683 | } 684 | #endif 685 | } 686 | if (res == ESP_OK) { 687 | res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); 688 | } 689 | if (res == ESP_OK) { 690 | size_t hlen = snprintf((char *)part_buf, 128, _STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec); 691 | res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); 692 | } 693 | if (res == ESP_OK) { 694 | res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); 695 | } 696 | if (fb) { 697 | esp_camera_fb_return(fb); 698 | fb = NULL; 699 | _jpg_buf = NULL; 700 | } else if (_jpg_buf) { 701 | free(_jpg_buf); 702 | _jpg_buf = NULL; 703 | } 704 | if (res != ESP_OK) { 705 | log_e("Send frame failed"); 706 | break; 707 | } 708 | int64_t fr_end = esp_timer_get_time(); 709 | 710 | #if CONFIG_ESP_FACE_DETECT_ENABLED && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 711 | int64_t ready_time = (fr_ready - fr_start) / 1000; 712 | int64_t face_time = (fr_face - fr_ready) / 1000; 713 | int64_t recognize_time = (fr_recognize - fr_face) / 1000; 714 | int64_t encode_time = (fr_encode - fr_recognize) / 1000; 715 | int64_t process_time = (fr_encode - fr_start) / 1000; 716 | #endif 717 | 718 | int64_t frame_time = fr_end - last_frame; 719 | frame_time /= 1000; 720 | #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO 721 | uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time); 722 | #endif 723 | log_i( 724 | "MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps)" 725 | #if CONFIG_ESP_FACE_DETECT_ENABLED 726 | ", %u+%u+%u+%u=%u %s%d" 727 | #endif 728 | , 729 | (uint32_t)(_jpg_buf_len), (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, avg_frame_time, 1000.0 / avg_frame_time 730 | #if CONFIG_ESP_FACE_DETECT_ENABLED 731 | , 732 | (uint32_t)ready_time, (uint32_t)face_time, (uint32_t)recognize_time, (uint32_t)encode_time, (uint32_t)process_time, (detected) ? "DETECTED " : "", face_id 733 | #endif 734 | ); 735 | } 736 | 737 | #if CONFIG_LED_ILLUMINATOR_ENABLED 738 | isStreaming = false; 739 | enable_led(false); 740 | #endif 741 | 742 | return res; 743 | } 744 | 745 | static esp_err_t parse_get(httpd_req_t *req, char **obuf) { 746 | char *buf = NULL; 747 | size_t buf_len = 0; 748 | 749 | buf_len = httpd_req_get_url_query_len(req) + 1; 750 | if (buf_len > 1) { 751 | buf = (char *)malloc(buf_len); 752 | if (!buf) { 753 | httpd_resp_send_500(req); 754 | return ESP_FAIL; 755 | } 756 | if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { 757 | *obuf = buf; 758 | return ESP_OK; 759 | } 760 | free(buf); 761 | } 762 | httpd_resp_send_404(req); 763 | return ESP_FAIL; 764 | } 765 | 766 | static esp_err_t cmd_handler(httpd_req_t *req) { 767 | char *buf = NULL; 768 | char variable[32]; 769 | char value[32]; 770 | 771 | if (parse_get(req, &buf) != ESP_OK) { 772 | return ESP_FAIL; 773 | } 774 | if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) != ESP_OK || httpd_query_key_value(buf, "val", value, sizeof(value)) != ESP_OK) { 775 | free(buf); 776 | httpd_resp_send_404(req); 777 | return ESP_FAIL; 778 | } 779 | free(buf); 780 | 781 | int val = atoi(value); 782 | log_i("%s = %d", variable, val); 783 | sensor_t *s = esp_camera_sensor_get(); 784 | int res = 0; 785 | 786 | if (!strcmp(variable, "framesize")) { 787 | if (s->pixformat == PIXFORMAT_JPEG) { 788 | res = s->set_framesize(s, (framesize_t)val); 789 | } 790 | } else if (!strcmp(variable, "quality")) { 791 | res = s->set_quality(s, val); 792 | } else if (!strcmp(variable, "contrast")) { 793 | res = s->set_contrast(s, val); 794 | } else if (!strcmp(variable, "brightness")) { 795 | res = s->set_brightness(s, val); 796 | } else if (!strcmp(variable, "saturation")) { 797 | res = s->set_saturation(s, val); 798 | } else if (!strcmp(variable, "gainceiling")) { 799 | res = s->set_gainceiling(s, (gainceiling_t)val); 800 | } else if (!strcmp(variable, "colorbar")) { 801 | res = s->set_colorbar(s, val); 802 | } else if (!strcmp(variable, "awb")) { 803 | res = s->set_whitebal(s, val); 804 | } else if (!strcmp(variable, "agc")) { 805 | res = s->set_gain_ctrl(s, val); 806 | } else if (!strcmp(variable, "aec")) { 807 | res = s->set_exposure_ctrl(s, val); 808 | } else if (!strcmp(variable, "hmirror")) { 809 | res = s->set_hmirror(s, val); 810 | } else if (!strcmp(variable, "vflip")) { 811 | res = s->set_vflip(s, val); 812 | } else if (!strcmp(variable, "awb_gain")) { 813 | res = s->set_awb_gain(s, val); 814 | } else if (!strcmp(variable, "agc_gain")) { 815 | res = s->set_agc_gain(s, val); 816 | } else if (!strcmp(variable, "aec_value")) { 817 | res = s->set_aec_value(s, val); 818 | } else if (!strcmp(variable, "aec2")) { 819 | res = s->set_aec2(s, val); 820 | } else if (!strcmp(variable, "dcw")) { 821 | res = s->set_dcw(s, val); 822 | } else if (!strcmp(variable, "bpc")) { 823 | res = s->set_bpc(s, val); 824 | } else if (!strcmp(variable, "wpc")) { 825 | res = s->set_wpc(s, val); 826 | } else if (!strcmp(variable, "raw_gma")) { 827 | res = s->set_raw_gma(s, val); 828 | } else if (!strcmp(variable, "lenc")) { 829 | res = s->set_lenc(s, val); 830 | } else if (!strcmp(variable, "special_effect")) { 831 | res = s->set_special_effect(s, val); 832 | } else if (!strcmp(variable, "wb_mode")) { 833 | res = s->set_wb_mode(s, val); 834 | } else if (!strcmp(variable, "ae_level")) { 835 | res = s->set_ae_level(s, val); 836 | } 837 | #if CONFIG_LED_ILLUMINATOR_ENABLED 838 | else if (!strcmp(variable, "led_intensity")) { 839 | led_duty = val; 840 | if (isStreaming) { 841 | enable_led(true); 842 | } 843 | } 844 | #endif 845 | 846 | #if CONFIG_ESP_FACE_DETECT_ENABLED 847 | else if (!strcmp(variable, "face_detect")) { 848 | detection_enabled = val; 849 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 850 | if (!detection_enabled) { 851 | recognition_enabled = 0; 852 | } 853 | #endif 854 | } 855 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 856 | else if (!strcmp(variable, "face_enroll")) { 857 | is_enrolling = !is_enrolling; 858 | log_i("Enrolling: %s", is_enrolling ? "true" : "false"); 859 | } else if (!strcmp(variable, "face_recognize")) { 860 | recognition_enabled = val; 861 | if (recognition_enabled) { 862 | detection_enabled = val; 863 | } 864 | } 865 | #endif 866 | #endif 867 | else { 868 | log_i("Unknown command: %s", variable); 869 | res = -1; 870 | } 871 | 872 | if (res < 0) { 873 | return httpd_resp_send_500(req); 874 | } 875 | 876 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 877 | return httpd_resp_send(req, NULL, 0); 878 | } 879 | 880 | static int print_reg(char *p, sensor_t *s, uint16_t reg, uint32_t mask) { 881 | return sprintf(p, "\"0x%x\":%u,", reg, s->get_reg(s, reg, mask)); 882 | } 883 | 884 | static esp_err_t status_handler(httpd_req_t *req) { 885 | static char json_response[1024]; 886 | 887 | sensor_t *s = esp_camera_sensor_get(); 888 | char *p = json_response; 889 | *p++ = '{'; 890 | 891 | if (s->id.PID == OV5640_PID || s->id.PID == OV3660_PID) { 892 | for (int reg = 0x3400; reg < 0x3406; reg += 2) { 893 | p += print_reg(p, s, reg, 0xFFF); //12 bit 894 | } 895 | p += print_reg(p, s, 0x3406, 0xFF); 896 | 897 | p += print_reg(p, s, 0x3500, 0xFFFF0); //16 bit 898 | p += print_reg(p, s, 0x3503, 0xFF); 899 | p += print_reg(p, s, 0x350a, 0x3FF); //10 bit 900 | p += print_reg(p, s, 0x350c, 0xFFFF); //16 bit 901 | 902 | for (int reg = 0x5480; reg <= 0x5490; reg++) { 903 | p += print_reg(p, s, reg, 0xFF); 904 | } 905 | 906 | for (int reg = 0x5380; reg <= 0x538b; reg++) { 907 | p += print_reg(p, s, reg, 0xFF); 908 | } 909 | 910 | for (int reg = 0x5580; reg < 0x558a; reg++) { 911 | p += print_reg(p, s, reg, 0xFF); 912 | } 913 | p += print_reg(p, s, 0x558a, 0x1FF); //9 bit 914 | } else if (s->id.PID == OV2640_PID) { 915 | p += print_reg(p, s, 0xd3, 0xFF); 916 | p += print_reg(p, s, 0x111, 0xFF); 917 | p += print_reg(p, s, 0x132, 0xFF); 918 | } 919 | 920 | p += sprintf(p, "\"xclk\":%u,", s->xclk_freq_hz / 1000000); 921 | p += sprintf(p, "\"pixformat\":%u,", s->pixformat); 922 | p += sprintf(p, "\"framesize\":%u,", s->status.framesize); 923 | p += sprintf(p, "\"quality\":%u,", s->status.quality); 924 | p += sprintf(p, "\"brightness\":%d,", s->status.brightness); 925 | p += sprintf(p, "\"contrast\":%d,", s->status.contrast); 926 | p += sprintf(p, "\"saturation\":%d,", s->status.saturation); 927 | p += sprintf(p, "\"sharpness\":%d,", s->status.sharpness); 928 | p += sprintf(p, "\"special_effect\":%u,", s->status.special_effect); 929 | p += sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode); 930 | p += sprintf(p, "\"awb\":%u,", s->status.awb); 931 | p += sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain); 932 | p += sprintf(p, "\"aec\":%u,", s->status.aec); 933 | p += sprintf(p, "\"aec2\":%u,", s->status.aec2); 934 | p += sprintf(p, "\"ae_level\":%d,", s->status.ae_level); 935 | p += sprintf(p, "\"aec_value\":%u,", s->status.aec_value); 936 | p += sprintf(p, "\"agc\":%u,", s->status.agc); 937 | p += sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain); 938 | p += sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling); 939 | p += sprintf(p, "\"bpc\":%u,", s->status.bpc); 940 | p += sprintf(p, "\"wpc\":%u,", s->status.wpc); 941 | p += sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma); 942 | p += sprintf(p, "\"lenc\":%u,", s->status.lenc); 943 | p += sprintf(p, "\"hmirror\":%u,", s->status.hmirror); 944 | p += sprintf(p, "\"dcw\":%u,", s->status.dcw); 945 | p += sprintf(p, "\"colorbar\":%u", s->status.colorbar); 946 | #if CONFIG_LED_ILLUMINATOR_ENABLED 947 | p += sprintf(p, ",\"led_intensity\":%u", led_duty); 948 | #else 949 | p += sprintf(p, ",\"led_intensity\":%d", -1); 950 | #endif 951 | #if CONFIG_ESP_FACE_DETECT_ENABLED 952 | p += sprintf(p, ",\"face_detect\":%u", detection_enabled); 953 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 954 | p += sprintf(p, ",\"face_enroll\":%u,", is_enrolling); 955 | p += sprintf(p, "\"face_recognize\":%u", recognition_enabled); 956 | #endif 957 | #endif 958 | *p++ = '}'; 959 | *p++ = 0; 960 | httpd_resp_set_type(req, "application/json"); 961 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 962 | return httpd_resp_send(req, json_response, strlen(json_response)); 963 | } 964 | 965 | static esp_err_t xclk_handler(httpd_req_t *req) { 966 | char *buf = NULL; 967 | char _xclk[32]; 968 | 969 | if (parse_get(req, &buf) != ESP_OK) { 970 | return ESP_FAIL; 971 | } 972 | if (httpd_query_key_value(buf, "xclk", _xclk, sizeof(_xclk)) != ESP_OK) { 973 | free(buf); 974 | httpd_resp_send_404(req); 975 | return ESP_FAIL; 976 | } 977 | free(buf); 978 | 979 | int xclk = atoi(_xclk); 980 | log_i("Set XCLK: %d MHz", xclk); 981 | 982 | sensor_t *s = esp_camera_sensor_get(); 983 | int res = s->set_xclk(s, LEDC_TIMER_0, xclk); 984 | if (res) { 985 | return httpd_resp_send_500(req); 986 | } 987 | 988 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 989 | return httpd_resp_send(req, NULL, 0); 990 | } 991 | 992 | static esp_err_t reg_handler(httpd_req_t *req) { 993 | char *buf = NULL; 994 | char _reg[32]; 995 | char _mask[32]; 996 | char _val[32]; 997 | 998 | if (parse_get(req, &buf) != ESP_OK) { 999 | return ESP_FAIL; 1000 | } 1001 | if (httpd_query_key_value(buf, "reg", _reg, sizeof(_reg)) != ESP_OK || httpd_query_key_value(buf, "mask", _mask, sizeof(_mask)) != ESP_OK 1002 | || httpd_query_key_value(buf, "val", _val, sizeof(_val)) != ESP_OK) { 1003 | free(buf); 1004 | httpd_resp_send_404(req); 1005 | return ESP_FAIL; 1006 | } 1007 | free(buf); 1008 | 1009 | int reg = atoi(_reg); 1010 | int mask = atoi(_mask); 1011 | int val = atoi(_val); 1012 | log_i("Set Register: reg: 0x%02x, mask: 0x%02x, value: 0x%02x", reg, mask, val); 1013 | 1014 | sensor_t *s = esp_camera_sensor_get(); 1015 | int res = s->set_reg(s, reg, mask, val); 1016 | if (res) { 1017 | return httpd_resp_send_500(req); 1018 | } 1019 | 1020 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1021 | return httpd_resp_send(req, NULL, 0); 1022 | } 1023 | 1024 | static esp_err_t greg_handler(httpd_req_t *req) { 1025 | char *buf = NULL; 1026 | char _reg[32]; 1027 | char _mask[32]; 1028 | 1029 | if (parse_get(req, &buf) != ESP_OK) { 1030 | return ESP_FAIL; 1031 | } 1032 | if (httpd_query_key_value(buf, "reg", _reg, sizeof(_reg)) != ESP_OK || httpd_query_key_value(buf, "mask", _mask, sizeof(_mask)) != ESP_OK) { 1033 | free(buf); 1034 | httpd_resp_send_404(req); 1035 | return ESP_FAIL; 1036 | } 1037 | free(buf); 1038 | 1039 | int reg = atoi(_reg); 1040 | int mask = atoi(_mask); 1041 | sensor_t *s = esp_camera_sensor_get(); 1042 | int res = s->get_reg(s, reg, mask); 1043 | if (res < 0) { 1044 | return httpd_resp_send_500(req); 1045 | } 1046 | log_i("Get Register: reg: 0x%02x, mask: 0x%02x, value: 0x%02x", reg, mask, res); 1047 | 1048 | char buffer[20]; 1049 | const char *val = itoa(res, buffer, 10); 1050 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1051 | return httpd_resp_send(req, val, strlen(val)); 1052 | } 1053 | 1054 | static int parse_get_var(char *buf, const char *key, int def) { 1055 | char _int[16]; 1056 | if (httpd_query_key_value(buf, key, _int, sizeof(_int)) != ESP_OK) { 1057 | return def; 1058 | } 1059 | return atoi(_int); 1060 | } 1061 | 1062 | static esp_err_t pll_handler(httpd_req_t *req) { 1063 | char *buf = NULL; 1064 | 1065 | if (parse_get(req, &buf) != ESP_OK) { 1066 | return ESP_FAIL; 1067 | } 1068 | 1069 | int bypass = parse_get_var(buf, "bypass", 0); 1070 | int mul = parse_get_var(buf, "mul", 0); 1071 | int sys = parse_get_var(buf, "sys", 0); 1072 | int root = parse_get_var(buf, "root", 0); 1073 | int pre = parse_get_var(buf, "pre", 0); 1074 | int seld5 = parse_get_var(buf, "seld5", 0); 1075 | int pclken = parse_get_var(buf, "pclken", 0); 1076 | int pclk = parse_get_var(buf, "pclk", 0); 1077 | free(buf); 1078 | 1079 | log_i("Set Pll: bypass: %d, mul: %d, sys: %d, root: %d, pre: %d, seld5: %d, pclken: %d, pclk: %d", bypass, mul, sys, root, pre, seld5, pclken, pclk); 1080 | sensor_t *s = esp_camera_sensor_get(); 1081 | int res = s->set_pll(s, bypass, mul, sys, root, pre, seld5, pclken, pclk); 1082 | if (res) { 1083 | return httpd_resp_send_500(req); 1084 | } 1085 | 1086 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1087 | return httpd_resp_send(req, NULL, 0); 1088 | } 1089 | 1090 | static esp_err_t win_handler(httpd_req_t *req) { 1091 | char *buf = NULL; 1092 | 1093 | if (parse_get(req, &buf) != ESP_OK) { 1094 | return ESP_FAIL; 1095 | } 1096 | 1097 | int startX = parse_get_var(buf, "sx", 0); 1098 | int startY = parse_get_var(buf, "sy", 0); 1099 | int endX = parse_get_var(buf, "ex", 0); 1100 | int endY = parse_get_var(buf, "ey", 0); 1101 | int offsetX = parse_get_var(buf, "offx", 0); 1102 | int offsetY = parse_get_var(buf, "offy", 0); 1103 | int totalX = parse_get_var(buf, "tx", 0); 1104 | int totalY = parse_get_var(buf, "ty", 0); 1105 | int outputX = parse_get_var(buf, "ox", 0); 1106 | int outputY = parse_get_var(buf, "oy", 0); 1107 | bool scale = parse_get_var(buf, "scale", 0) == 1; 1108 | bool binning = parse_get_var(buf, "binning", 0) == 1; 1109 | free(buf); 1110 | 1111 | log_i( 1112 | "Set Window: Start: %d %d, End: %d %d, Offset: %d %d, Total: %d %d, Output: %d %d, Scale: %u, Binning: %u", startX, startY, endX, endY, offsetX, offsetY, 1113 | totalX, totalY, outputX, outputY, scale, binning 1114 | ); 1115 | sensor_t *s = esp_camera_sensor_get(); 1116 | int res = s->set_res_raw(s, startX, startY, endX, endY, offsetX, offsetY, totalX, totalY, outputX, outputY, scale, binning); 1117 | if (res) { 1118 | return httpd_resp_send_500(req); 1119 | } 1120 | 1121 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1122 | return httpd_resp_send(req, NULL, 0); 1123 | } 1124 | 1125 | static esp_err_t index_handler(httpd_req_t *req) { 1126 | httpd_resp_set_type(req, "text/html"); 1127 | httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 1128 | sensor_t *s = esp_camera_sensor_get(); 1129 | if (s != NULL) { 1130 | if (s->id.PID == OV3660_PID) { 1131 | return httpd_resp_send(req, (const char *)index_ov3660_html_gz, index_ov3660_html_gz_len); 1132 | } else if (s->id.PID == OV5640_PID) { 1133 | return httpd_resp_send(req, (const char *)index_ov5640_html_gz, index_ov5640_html_gz_len); 1134 | } else { 1135 | return httpd_resp_send(req, (const char *)index_ov2640_html_gz, index_ov2640_html_gz_len); 1136 | } 1137 | } else { 1138 | log_e("Camera sensor not found"); 1139 | return httpd_resp_send_500(req); 1140 | } 1141 | } 1142 | 1143 | void startCameraServer() { 1144 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 1145 | config.max_uri_handlers = 16; 1146 | 1147 | httpd_uri_t index_uri = { 1148 | .uri = "/", 1149 | .method = HTTP_GET, 1150 | .handler = index_handler, 1151 | .user_ctx = NULL 1152 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1153 | , 1154 | .is_websocket = true, 1155 | .handle_ws_control_frames = false, 1156 | .supported_subprotocol = NULL 1157 | #endif 1158 | }; 1159 | 1160 | httpd_uri_t status_uri = { 1161 | .uri = "/status", 1162 | .method = HTTP_GET, 1163 | .handler = status_handler, 1164 | .user_ctx = NULL 1165 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1166 | , 1167 | .is_websocket = true, 1168 | .handle_ws_control_frames = false, 1169 | .supported_subprotocol = NULL 1170 | #endif 1171 | }; 1172 | 1173 | httpd_uri_t cmd_uri = { 1174 | .uri = "/control", 1175 | .method = HTTP_GET, 1176 | .handler = cmd_handler, 1177 | .user_ctx = NULL 1178 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1179 | , 1180 | .is_websocket = true, 1181 | .handle_ws_control_frames = false, 1182 | .supported_subprotocol = NULL 1183 | #endif 1184 | }; 1185 | 1186 | httpd_uri_t capture_uri = { 1187 | .uri = "/capture", 1188 | .method = HTTP_GET, 1189 | .handler = capture_handler, 1190 | .user_ctx = NULL 1191 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1192 | , 1193 | .is_websocket = true, 1194 | .handle_ws_control_frames = false, 1195 | .supported_subprotocol = NULL 1196 | #endif 1197 | }; 1198 | 1199 | httpd_uri_t stream_uri = { 1200 | .uri = "/stream", 1201 | .method = HTTP_GET, 1202 | .handler = stream_handler, 1203 | .user_ctx = NULL 1204 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1205 | , 1206 | .is_websocket = true, 1207 | .handle_ws_control_frames = false, 1208 | .supported_subprotocol = NULL 1209 | #endif 1210 | }; 1211 | 1212 | httpd_uri_t bmp_uri = { 1213 | .uri = "/bmp", 1214 | .method = HTTP_GET, 1215 | .handler = bmp_handler, 1216 | .user_ctx = NULL 1217 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1218 | , 1219 | .is_websocket = true, 1220 | .handle_ws_control_frames = false, 1221 | .supported_subprotocol = NULL 1222 | #endif 1223 | }; 1224 | 1225 | httpd_uri_t xclk_uri = { 1226 | .uri = "/xclk", 1227 | .method = HTTP_GET, 1228 | .handler = xclk_handler, 1229 | .user_ctx = NULL 1230 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1231 | , 1232 | .is_websocket = true, 1233 | .handle_ws_control_frames = false, 1234 | .supported_subprotocol = NULL 1235 | #endif 1236 | }; 1237 | 1238 | httpd_uri_t reg_uri = { 1239 | .uri = "/reg", 1240 | .method = HTTP_GET, 1241 | .handler = reg_handler, 1242 | .user_ctx = NULL 1243 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1244 | , 1245 | .is_websocket = true, 1246 | .handle_ws_control_frames = false, 1247 | .supported_subprotocol = NULL 1248 | #endif 1249 | }; 1250 | 1251 | httpd_uri_t greg_uri = { 1252 | .uri = "/greg", 1253 | .method = HTTP_GET, 1254 | .handler = greg_handler, 1255 | .user_ctx = NULL 1256 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1257 | , 1258 | .is_websocket = true, 1259 | .handle_ws_control_frames = false, 1260 | .supported_subprotocol = NULL 1261 | #endif 1262 | }; 1263 | 1264 | httpd_uri_t pll_uri = { 1265 | .uri = "/pll", 1266 | .method = HTTP_GET, 1267 | .handler = pll_handler, 1268 | .user_ctx = NULL 1269 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1270 | , 1271 | .is_websocket = true, 1272 | .handle_ws_control_frames = false, 1273 | .supported_subprotocol = NULL 1274 | #endif 1275 | }; 1276 | 1277 | httpd_uri_t win_uri = { 1278 | .uri = "/resolution", 1279 | .method = HTTP_GET, 1280 | .handler = win_handler, 1281 | .user_ctx = NULL 1282 | #ifdef CONFIG_HTTPD_WS_SUPPORT 1283 | , 1284 | .is_websocket = true, 1285 | .handle_ws_control_frames = false, 1286 | .supported_subprotocol = NULL 1287 | #endif 1288 | }; 1289 | 1290 | ra_filter_init(&ra_filter, 20); 1291 | 1292 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 1293 | recognizer.set_partition(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "fr"); 1294 | 1295 | // load ids from flash partition 1296 | recognizer.set_ids_from_flash(); 1297 | #endif 1298 | log_i("Starting web server on port: '%d'", config.server_port); 1299 | if (httpd_start(&camera_httpd, &config) == ESP_OK) { 1300 | httpd_register_uri_handler(camera_httpd, &index_uri); 1301 | httpd_register_uri_handler(camera_httpd, &cmd_uri); 1302 | httpd_register_uri_handler(camera_httpd, &status_uri); 1303 | httpd_register_uri_handler(camera_httpd, &capture_uri); 1304 | httpd_register_uri_handler(camera_httpd, &bmp_uri); 1305 | 1306 | httpd_register_uri_handler(camera_httpd, &xclk_uri); 1307 | httpd_register_uri_handler(camera_httpd, ®_uri); 1308 | httpd_register_uri_handler(camera_httpd, &greg_uri); 1309 | httpd_register_uri_handler(camera_httpd, &pll_uri); 1310 | httpd_register_uri_handler(camera_httpd, &win_uri); 1311 | } 1312 | 1313 | config.server_port += 1; 1314 | config.ctrl_port += 1; 1315 | log_i("Starting stream server on port: '%d'", config.server_port); 1316 | if (httpd_start(&stream_httpd, &config) == ESP_OK) { 1317 | httpd_register_uri_handler(stream_httpd, &stream_uri); 1318 | } 1319 | } 1320 | 1321 | void setupLedFlash(int pin) { 1322 | #if CONFIG_LED_ILLUMINATOR_ENABLED 1323 | ledcAttach(pin, 5000, 8); 1324 | #else 1325 | log_i("LED flash is disabled -> CONFIG_LED_ILLUMINATOR_ENABLED = 0"); 1326 | #endif 1327 | } 1328 | -------------------------------------------------------------------------------- /M5CoreS3/I2CScanInternalGroove/I2CScanInternalGroove.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | TwoWire *wire; 9 | int sda; 10 | int scl; 11 | } i2cBus_t; 12 | 13 | i2cBus_t i2cBus_Internal = {&Wire, GPIO_NUM_12, GPIO_NUM_11}; 14 | i2cBus_t i2cBus_Groove_Port = {&Wire, GPIO_NUM_2, GPIO_NUM_1}; 15 | 16 | void setup() 17 | { 18 | Serial.begin(115200); 19 | delay(3000); 20 | Serial.println("Start I2C scan"); 21 | } 22 | 23 | void scanI2C(TwoWire *wire, int sda, int scl) 24 | { 25 | int address; 26 | int error; 27 | 28 | wire->begin(sda, scl); 29 | 30 | for(address = 1; address < 127; address++) 31 | { 32 | wire->beginTransmission(address); 33 | error = wire->endTransmission(); 34 | if(error == 0) Serial.printf("0x%02x ", address); 35 | else Serial.print("."); 36 | delay(10); 37 | } 38 | Serial.println(); 39 | wire->end(); 40 | } 41 | 42 | void loop() 43 | { 44 | Serial.println("I2C Scan - internal"); 45 | scanI2C(i2cBus_Internal.wire, i2cBus_Internal.sda, i2cBus_Internal.scl); 46 | Serial.println("I2C Scan - Groove Port"); 47 | scanI2C(i2cBus_Groove_Port.wire, i2cBus_Groove_Port.sda, i2cBus_Groove_Port.scl); 48 | delay(2000); 49 | } 50 | -------------------------------------------------------------------------------- /M5CoreS3/SDCard/SDCard.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // Note: requires PR https://github.com/m5stack/M5CoreS3/pull/35 to set SD card voltage to 3.3 V. 5 | // Note: breaks TFT as SD card needs MOSI as input whereas TFT uses MOSI as DC which is an output. 6 | 7 | #include 8 | #include 9 | 10 | #define SD_CS 4 11 | #define SPI_MOSI 37 12 | #define SPI_MISO 35 13 | #define SPI_SCK 36 14 | 15 | void setup() 16 | { 17 | M5.begin(); 18 | SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); 19 | if(!SD.begin(SD_CS, SPI)) 20 | { 21 | USBSerial.println("SD card error!"); 22 | while(true); 23 | } 24 | USBSerial.println("SD ok!"); 25 | } 26 | 27 | void loop() 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /M5CoreS3/SDCardAndLCD/SDCardAndLCD.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // Note: requires PR https://github.com/m5stack/M5CoreS3/pull/35 to set SD card voltage to 3.3 V. 5 | 6 | // Note: The LCD uses the MISO GPIO as DC, so for the 7 | // LCD : MISO/DC GPIO needs to be an output 8 | // SD card : MISO/DC GPIO needs to be an input 9 | // In order to be able to switch back and forth the same SPI 10 | // instance needs to be used for the LCD and the SD card. 11 | // Luckily the LCD library provides a function to get the 12 | // SPI instance. E.g. M5.Lcd.getSPIinstance(); 13 | 14 | #include 15 | #include 16 | 17 | #define SD_CS 4 18 | 19 | // Helpers to switch MISO/DC GPIO to output or input 20 | #define SPI_MODE_LCD { pinMode(TFT_DC, OUTPUT); digitalWrite(TFT_DC, HIGH); } 21 | #define SPI_MODE_SDCARD { pinMode(TFT_DC, INPUT); } 22 | 23 | void setup() 24 | { 25 | M5.begin(); 26 | 27 | delay(1000); 28 | 29 | SPI_MODE_SDCARD; 30 | // Use LCD SPI instance for SD card 31 | if(!SD.begin(SD_CS, M5.Lcd.getSPIinstance())) 32 | { 33 | USBSerial.println("SD card error!"); 34 | while(1); 35 | } 36 | USBSerial.println("SD ok!"); 37 | 38 | SPI_MODE_LCD; 39 | USBSerial.println("*** 1"); 40 | M5.Lcd.setTextSize(2); 41 | M5.Lcd.setCursor(100, 120); 42 | M5.Lcd.print("Hello World"); 43 | 44 | SPI_MODE_SDCARD; 45 | if(SD.exists("/hello.txt")) 46 | { 47 | USBSerial.println("hello.txt exists."); 48 | } 49 | else 50 | { 51 | USBSerial.println("hello.txt doesn't exist."); 52 | } 53 | 54 | SPI_MODE_LCD; 55 | USBSerial.println("*** 2"); 56 | M5.Lcd.setTextSize(2); 57 | M5.Lcd.setCursor(100, 140); 58 | M5.Lcd.print("Hello World 2"); 59 | 60 | SPI_MODE_SDCARD; 61 | if(SD.exists("/hello.txt")) 62 | { 63 | USBSerial.println("hello.txt exists."); 64 | } 65 | else 66 | { 67 | USBSerial.println("hello.txt doesn't exist."); 68 | } 69 | } 70 | 71 | void loop() 72 | { 73 | } 74 | -------------------------------------------------------------------------------- /M5CoreS3/TouchButtonABC.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | import os, sys, io 5 | import M5 6 | from M5 import * 7 | import time 8 | 9 | label0 = None 10 | 11 | def cb_BtnA(): 12 | global label0 13 | label0.setText(str('Button A')) 14 | time.sleep_ms(250) 15 | label0.setText(str(' ')) 16 | 17 | def cb_BtnB(): 18 | global label0 19 | label0.setText(str('Button B')) 20 | time.sleep_ms(250) 21 | label0.setText(str(' ')) 22 | 23 | def cb_BtnC(): 24 | global label0 25 | label0.setText(str('Button C')) 26 | time.sleep_ms(250) 27 | label0.setText(str(' ')) 28 | 29 | def setup(): 30 | global label0 31 | 32 | M5.begin() 33 | Widgets.fillScreen(0x222222) 34 | label0 = Widgets.Label("label0", 74, 85, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu40) 35 | 36 | class MyButton: 37 | def __init__(self, x, y, w, h, c, fc, cb): 38 | self.x = x 39 | self.y = y 40 | self.w = w 41 | self.h = h 42 | self.cb = cb 43 | self.tx = -1 44 | self.ty = -1 45 | self.rect = Widgets.Rectangle(x, y, w, h, c, fc) 46 | 47 | def update(self): 48 | if M5.Touch.getCount() > 0: 49 | tx = M5.Touch.getX() - 20 # offset??? 50 | ty = M5.Touch.getY() 51 | if tx != self.tx or ty != self.ty: 52 | self.tx = tx; self.ty = ty 53 | if tx >= self.x and tx <= (self.x + self.w) and ty >= self.y and ty <= (self.y + self.h): 54 | if callable(self.cb): 55 | self.cb() 56 | 57 | MyButtonList = [] 58 | 59 | global MyButtonUpdate 60 | def MyButtonUpdate(): 61 | for Btn in MyButtonList: 62 | Btn.update() 63 | 64 | BtnA = MyButton(0, 230, 106, 30, 0xff0000, 0x880000, cb_BtnA) 65 | MyButtonList.append(BtnA) 66 | 67 | BtnB = MyButton(107, 230, 106, 30, 0x00ff00, 0x008800, cb_BtnB) 68 | MyButtonList.append(BtnB) 69 | 70 | BtnC = MyButton(214, 230, 106, 30, 0x0000ff, 0x000088, cb_BtnC) 71 | MyButtonList.append(BtnC) 72 | 73 | def loop(): 74 | global label0 75 | M5.update() 76 | MyButtonUpdate() 77 | 78 | if __name__ == '__main__': 79 | try: 80 | setup() 81 | while True: 82 | loop() 83 | except (Exception, KeyboardInterrupt) as e: 84 | try: 85 | from utility import print_error_msg 86 | print_error_msg(e) 87 | except ImportError: 88 | print("please update to latest firmware") 89 | -------------------------------------------------------------------------------- /M5CoreS3/TouchRandomCircle/TouchRandomCircle.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5CoreS3 5 | 6 | #include 7 | 8 | int16_t x_last = -1; 9 | int16_t y_last = -1; 10 | 11 | void setup() 12 | { 13 | M5.begin(); 14 | } 15 | 16 | void loop() 17 | { 18 | M5.update(); 19 | 20 | auto p = M5.Touch.getDetail(); 21 | 22 | if((p.x != x_last) && (p.y != y_last)) 23 | { 24 | x_last = p.x; 25 | y_last = p.y; 26 | M5.Lcd.drawCircle(p.x, p.y, 10, M5.Lcd.color565(random(), random(), random())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /M5Dial/EncAndBrightTest/EncAndBrightTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5Dial 5 | // https://github.com/m5stack/M5GFX 6 | // https://github.com/m5stack/M5Unified 7 | 8 | #include "M5Dial.h" 9 | 10 | #define ENC_MIN 0 11 | #define ENC_INIT 16 12 | #define ENC_MAX 64 13 | #define BRIGHT_MIN 0 14 | #define BRIGHT_MAX 255 15 | 16 | long oldPos = -999; 17 | 18 | void setup() 19 | { 20 | auto cfg = M5.config(); 21 | M5Dial.begin(cfg, true, false); 22 | M5Dial.Display.setTextDatum(middle_center); 23 | M5Dial.Display.setTextSize(4); 24 | M5Dial.Display.setBrightness(map(ENC_INIT, ENC_MIN, ENC_MAX, BRIGHT_MIN, BRIGHT_MAX)); 25 | M5Dial.Encoder.write(ENC_INIT); 26 | } 27 | 28 | void loop() 29 | { 30 | M5Dial.update(); 31 | 32 | long newPos = M5Dial.Encoder.read(); 33 | 34 | if(newPos != oldPos) 35 | { 36 | if(newPos > ENC_MAX) 37 | { 38 | M5Dial.Encoder.write(ENC_MAX); 39 | return; 40 | } 41 | if(newPos < ENC_MIN) 42 | { 43 | M5Dial.Encoder.write(ENC_MIN); 44 | return; 45 | } 46 | oldPos = newPos; 47 | M5Dial.Display.setBrightness(map(newPos, ENC_MIN, ENC_MAX, BRIGHT_MIN, BRIGHT_MAX)); 48 | M5Dial.Display.clear(); 49 | M5Dial.Display.drawString(String(newPos), M5Dial.Display.width() / 2, M5Dial.Display.height() / 2); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /M5Dial/GC9A01_demo/GC9A01_demo.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // Adapted for M5Dial from 5 | // https://github.com/carlfriess/GC9A01_demo 6 | 7 | #include 8 | #include 9 | #include "GC9A01.h" 10 | 11 | #define MY_MOSI GPIO_NUM_5 12 | #define MY_SCLK GPIO_NUM_6 13 | #define MY_CS GPIO_NUM_7 14 | #define MY_DC GPIO_NUM_4 15 | #define MY_RST GPIO_NUM_8 16 | #define MY_BL GPIO_NUM_9 17 | 18 | void GC9A01_set_reset(uint8_t val) 19 | { 20 | digitalWrite(MY_RST, val); 21 | } 22 | 23 | void GC9A01_set_data_command(uint8_t val) 24 | { 25 | digitalWrite(MY_DC, val); 26 | } 27 | 28 | void GC9A01_set_chip_select(uint8_t val) 29 | { 30 | digitalWrite(MY_CS, val); 31 | } 32 | 33 | void GC9A01_spi_tx(uint8_t *data, size_t len) 34 | { 35 | while (len--) 36 | { 37 | SPI.transfer(*data); 38 | data++; 39 | } 40 | } 41 | 42 | void GC9A01_delay(uint16_t ms) 43 | { 44 | delay(ms); 45 | } 46 | 47 | void setup() 48 | { 49 | pinMode(MY_RST, OUTPUT); 50 | pinMode(MY_DC, OUTPUT); 51 | pinMode(MY_CS, OUTPUT); 52 | pinMode(MY_BL, OUTPUT); 53 | digitalWrite(MY_BL, HIGH); 54 | SPI.begin(MY_SCLK, -1, MY_MOSI, -1); 55 | GC9A01_init(); 56 | struct GC9A01_frame frame = {{0, 0},{239, 239}}; 57 | GC9A01_set_frame(frame); 58 | } 59 | 60 | void loop() 61 | { 62 | uint8_t color[3]; 63 | 64 | // Triangle 65 | color[0] = 0xFF; 66 | color[1] = 0xFF; 67 | for(int x = 0; x < 240; x++) 68 | { 69 | for(int y = 0; y < 240; y++) 70 | { 71 | if(x < y) color[2] = 0xFF; 72 | else color[2] = 0x00; 73 | 74 | if(x == 0 && y == 0) GC9A01_write(color, sizeof(color)); 75 | else GC9A01_write_continue(color, sizeof(color)); 76 | } 77 | } 78 | delay(1000); 79 | 80 | // Rainbow 81 | float frequency = 0.026; 82 | for(int x = 0; x < 240; x++) 83 | { 84 | color[0] = sin(frequency*x + 0) * 127 + 128; 85 | color[1] = sin(frequency*x + 2) * 127 + 128; 86 | color[2] = sin(frequency*x + 4) * 127 + 128; 87 | for(int y = 0; y < 240; y++) 88 | { 89 | if(x == 0 && y == 0) GC9A01_write(color, sizeof(color)); 90 | else GC9A01_write_continue(color, sizeof(color)); 91 | } 92 | } 93 | delay(1000); 94 | 95 | // Checkerboard 96 | for(int x = 0; x < 240; x++) 97 | { 98 | for(int y = 0; y < 240; y++) 99 | { 100 | if((x / 10) % 2 == (y / 10) % 2) 101 | { 102 | color[0] = 0xFF; 103 | color[1] = 0xFF; 104 | color[2] = 0xFF; 105 | } 106 | else 107 | { 108 | color[0] = 0x00; 109 | color[1] = 0x00; 110 | color[2] = 0x00; 111 | } 112 | if(x == 0 && y == 0) GC9A01_write(color, sizeof(color)); 113 | else GC9A01_write_continue(color, sizeof(color)); 114 | } 115 | } 116 | delay(1000); 117 | 118 | // Swiss flag 119 | color[0] = 0xFF; 120 | for(int x = 0; x < 240; x++) 121 | { 122 | for(int y = 0; y < 240; y++) 123 | { 124 | if((x >= 1*48 && x < 4*48 && y >= 2*48 && y < 3*48) || 125 | (x >= 2*48 && x < 3*48 && y >= 1*48 && y < 4*48)) 126 | { 127 | color[1] = 0xFF; 128 | color[2] = 0xFF; 129 | } 130 | else 131 | { 132 | color[1] = 0x00; 133 | color[2] = 0x00; 134 | } 135 | if(x == 0 && y == 0) GC9A01_write(color, sizeof(color)); 136 | else GC9A01_write_continue(color, sizeof(color)); 137 | } 138 | } 139 | delay(1000); 140 | } 141 | -------------------------------------------------------------------------------- /M5Dial/I2CScanIntExt/I2CScanIntExt.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // Based upon: I2C scanner for M5Tough 5 | // Adapted to: M5Dial 6 | // 7 | // https://github.com/m5stack/M5Dial 8 | // https://github.com/m5stack/M5GFX 9 | // https://github.com/m5stack/M5Unified 10 | 11 | #include "M5Dial.h" 12 | 13 | void scanI2C(m5::I2C_Class* scanWire) 14 | { 15 | bool result[0x80]; 16 | 17 | M5Dial.Lcd.clearDisplay(TFT_BLACK); 18 | M5Dial.Lcd.setFont(&fonts::Font4); 19 | M5Dial.Lcd.setTextColor(TFT_WHITE, TFT_BLACK); 20 | M5Dial.Lcd.setCursor(0, M5Dial.Lcd.height() / 2 - 6); 21 | 22 | if(scanWire == &M5.In_I2C) M5Dial.Lcd.print("Int."); 23 | else M5Dial.Lcd.print("Ext."); 24 | 25 | M5Dial.Lcd.setCursor(200, M5Dial.Lcd.height() / 2 - 6); 26 | M5Dial.Lcd.print("I2C"); 27 | 28 | M5Dial.Lcd.setFont(&fonts::AsciiFont8x16); 29 | scanWire->scanID(result); 30 | for(int i = 0x08; i < 0x78; ++i) 31 | { 32 | bool hit = result[i]; 33 | std::size_t y = i >> 3; 34 | std::size_t x = i & 7; 35 | M5Dial.Lcd.setCursor(50 + x * 18, 7 + y * 14); 36 | M5Dial.Lcd.setTextColor(hit ? TFT_BLACK : TFT_LIGHTGRAY , hit ? TFT_GREEN : TFT_WHITE); 37 | M5Dial.Lcd.printf("%02x", i); 38 | } 39 | } 40 | 41 | void setup() 42 | { 43 | auto cfg = M5.config(); 44 | M5Dial.begin(cfg, false, false); 45 | } 46 | 47 | void loop() 48 | { 49 | scanI2C(&M5.In_I2C); 50 | delay(3000); 51 | scanI2C(&M5.Ex_I2C); 52 | delay(3000); 53 | } 54 | -------------------------------------------------------------------------------- /M5Dial/RS485ModbusACSSR/RS485ModbusACSSR.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5Dial 5 | // https://github.com/m5stack/M5GFX 6 | // https://github.com/m5stack/M5Unified 7 | // https://github.com/m5stack/ArduinoModbus 8 | // https://github.com/m5stack/ArduinoRS485 9 | 10 | #include "M5Dial.h" 11 | #include 12 | #include 13 | 14 | #define ACSSR_DEFAULT_SLAVE_ID 0x04 15 | #define ACSSR_RELAY_COIL_ADDR 0x0000 16 | #define ACSSR_LED_HOLDING_ADDR 0x0000 17 | #define ACSSR_VERSION_HOLDING_ADDR 0x0001 18 | #define ACSSR_ID_HOLDING_ADDR 0x0002 19 | 20 | RS485Class RS485(Serial2, GPIO_NUM_1, GPIO_NUM_2, -1, -1); // RX, TX, ?, ReadEnable 21 | 22 | bool bToggle = false; 23 | 24 | void setup() 25 | { 26 | auto cfg = M5.config(); 27 | M5.begin(cfg); 28 | 29 | M5.Lcd.setTextSize(3); 30 | M5.Lcd.setCursor(60, 40); 31 | M5.Lcd.println("M5Dial"); 32 | M5.Lcd.setTextSize(2); 33 | M5.Lcd.setCursor(10, 70); 34 | M5.Lcd.println("Modbus Test v1.0.0"); 35 | M5.Lcd.setTextSize(2); 36 | M5.Lcd.setCursor(20, 100); 37 | M5.Lcd.println("Click to toggle\n relay state."); 38 | 39 | ModbusRTUClient.begin(RS485, 115200, SERIAL_8N1); 40 | } 41 | 42 | void loop() 43 | { 44 | M5.update(); 45 | 46 | if(M5.BtnA.wasPressed() == true) 47 | { 48 | if(bToggle == false) 49 | { 50 | bToggle = true; 51 | 52 | Serial.println("on"); 53 | // ACSSR CTL ON 54 | ModbusRTUClient.coilWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_RELAY_COIL_ADDR, 0x01); 55 | // ACSSR LED CTL RED COLOR 56 | ModbusRTUClient.holdingRegisterWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_LED_HOLDING_ADDR, 0xF800); 57 | } 58 | else 59 | { 60 | bToggle = false; 61 | 62 | Serial.println("off"); 63 | // ACSSR CTL OFF 64 | ModbusRTUClient.coilWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_RELAY_COIL_ADDR, 0x00); 65 | // ACSSR LED CTL GREEN COLOR 66 | ModbusRTUClient.holdingRegisterWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_LED_HOLDING_ADDR, 0x07E0); 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /M5Dial/ShutdownTest/ShutdownTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5Dial 5 | // https://github.com/m5stack/M5GFX 6 | // https://github.com/m5stack/M5Unified 7 | // 8 | // Notes: 9 | // - M5Dial can only shutdown when powered from battery. 10 | // - M5Dial cannot shutdown when powered from USB-C. 11 | // - M5Dial cannot shutdown when powered from green connector. *) 12 | // 13 | // *) This is due to a connection between +5VIN and M5V of the M5StampS3 14 | // which keeps the M5StampS3 powered even though the MOSFET is off. 15 | // See schematic here: https://docs.m5stack.com/en/core/M5Dial 16 | #include "M5Dial.h" 17 | 18 | void setup() 19 | { 20 | auto cfg = M5.config(); 21 | M5Dial.begin(cfg, false, false); 22 | 23 | delay(3000); 24 | 25 | // // Shutdown with timer 26 | // // Note: Power on by pressing wake button or after 30 seconds by RTC 27 | // M5Dial.Power.timerSleep(30); 28 | 29 | // Shutdown w/o timer 30 | // Note: Power on by pressing wake button 31 | M5Dial.Power.powerOff(); 32 | } 33 | 34 | void loop() 35 | { 36 | 37 | } 38 | -------------------------------------------------------------------------------- /M5Dial/SleepTest/SleepTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5Dial 5 | // https://github.com/m5stack/M5GFX 6 | // https://github.com/m5stack/M5Unified 7 | 8 | #include "M5Dial.h" 9 | 10 | void setup() 11 | { 12 | auto cfg = M5.config(); 13 | M5Dial.begin(cfg, true, false); 14 | delay(3000); 15 | Serial.println("Sleep tests"); 16 | } 17 | 18 | void loop() 19 | { 20 | Serial.println("in loop - before"); 21 | Serial.flush(); 22 | delay(1000); 23 | 24 | // Deep sleep - wakeup from touch screen 25 | // esp_sleep_enable_ext0_wakeup(GPIO_NUM_14, LOW); 26 | // esp_deep_sleep_start(); 27 | 28 | // Light sleep - wakeup from touch screen 29 | // esp_sleep_enable_ext0_wakeup(GPIO_NUM_14, LOW); 30 | // esp_light_sleep_start(); 31 | 32 | // Light sleep - wakeup from dial button 33 | gpio_wakeup_enable(GPIO_NUM_42, GPIO_INTR_LOW_LEVEL); 34 | esp_sleep_enable_gpio_wakeup(); 35 | esp_light_sleep_start(); 36 | 37 | // Note: never reached after deep sleep 38 | Serial.println(""); 39 | Serial.println("in loop - after"); 40 | Serial.flush(); 41 | delay(1000); 42 | } 43 | -------------------------------------------------------------------------------- /M5LANBase/LinkTest/LinkTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // Based upon: M5Stack LinkStatus example for M5Module-LAN-13.2 5 | // Adapted to: M5Stack LAN Base 6 | // Works with: M5Core, M5Core2, M5Tough and M5CoreS3 7 | // Libraries: 8 | // - https://github.com/m5stack/M5Unified 9 | // - https://github.com/m5stack/M5GFX 10 | // - https://github.com/m5stack/M5-Ethernet 11 | // - https://github.com/m5stack/M5Module-LAN-13.2 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "utility/w5100.h" 18 | 19 | uint8_t cs_pin; 20 | uint8_t rst_pin; 21 | uint8_t int_pin; 22 | 23 | byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x89}; 24 | 25 | M5Module_LAN LAN; 26 | 27 | #define LINE1_Y 0 28 | #define LINE2_Y 60 29 | #define LINE3_Y 90 30 | #define LINE4_Y 120 31 | #define LINE5_Y 180 32 | 33 | void setup() 34 | { 35 | M5.begin(); 36 | M5.Display.begin(); 37 | M5.Display.setTextColor(WHITE, BLACK); 38 | M5.Display.setTextSize(2); 39 | 40 | M5.Display.setCursor(0, LINE1_Y); 41 | m5::board_t board = M5.getBoard(); 42 | switch(board) 43 | { 44 | case m5::board_t::board_M5Stack: 45 | M5.Display.println("Core: M5Stack (Grey/Fire)"); 46 | cs_pin = 26; 47 | rst_pin = 13; 48 | int_pin = 34; 49 | break; 50 | case m5::board_t::board_M5StackCore2: 51 | case m5::board_t::board_M5Tough: 52 | M5.Display.println("Core: M5Core2 or M5Tough"); 53 | cs_pin = 26; 54 | rst_pin = 19; 55 | int_pin = 34; 56 | break; 57 | case m5::board_t::board_M5StackCoreS3: 58 | M5.Display.println("Core: M5CoreS3"); 59 | cs_pin = 9; 60 | rst_pin = 7; 61 | int_pin = 14; 62 | break; 63 | default: 64 | M5.Display.println("Unsupported core."); 65 | 66 | while(true) delay(1); 67 | break; 68 | } 69 | 70 | SPI.begin(SCK, MISO, MOSI, -1); 71 | LAN.setResetPin(rst_pin); 72 | LAN.reset(); 73 | LAN.init(cs_pin); 74 | // W/o below line the check for Ethernet hardware present always fails 75 | W5100.init(); 76 | // Check for Ethernet hardware present 77 | if(Ethernet.hardwareStatus() == EthernetNoHardware) 78 | { 79 | M5.Display.setCursor(0, LINE2_Y); 80 | M5.Display.println("Ethernet HW not found."); 81 | // Do nothing; no point running w/o Ethernet hardware 82 | while(true) delay(1); 83 | } 84 | M5.Display.setCursor(0, LINE2_Y); 85 | M5.Display.println("Ethernet HW found. "); 86 | // Wait for Ethernet cable to be attached 87 | while(Ethernet.linkStatus() == LinkOFF) 88 | { 89 | M5.Display.setCursor(0, LINE3_Y); 90 | M5.Display.println("Cable not connected."); 91 | delay(1000); 92 | } 93 | M5.Display.setCursor(0, LINE3_Y); 94 | M5.Display.println("Cable connected. "); 95 | // Initiate DHCP 96 | while(LAN.begin(mac) != 1) 97 | { 98 | M5.Display.setCursor(0, LINE4_Y); 99 | M5.Display.println("DHCP failed. Trying again."); 100 | delay(2000); 101 | } 102 | 103 | IPAddress IP = LAN.localIP(); 104 | M5.Display.setCursor(0, LINE4_Y); 105 | M5.Display.print("Got IP: "); 106 | M5.Display.print(IP.toString()); 107 | M5.Display.print(" "); 108 | } 109 | 110 | void loop() 111 | { 112 | delay(500); 113 | auto link = LAN.linkStatus(); 114 | M5.Display.setCursor(0, LINE5_Y); 115 | M5.Display.print("Link status: "); 116 | switch(link) 117 | { 118 | case Unknown: 119 | M5.Display.println("Wait"); 120 | break; 121 | case LinkON: 122 | { 123 | M5.Display.println("ON "); 124 | } 125 | break; 126 | case LinkOFF: 127 | M5.Display.println("OFF "); 128 | break; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /M5Paper/GestureSensor/GestureSensor.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | #include 6 | 7 | M5EPD_Canvas canvas(&M5.EPD); 8 | DFRobot_PAJ7620U2 paj(&Wire1); 9 | 10 | void setup() 11 | { 12 | M5.begin(true, false, true, true, false); 13 | 14 | Wire1.begin(25, 32, 400000U); 15 | while(paj.begin() != 0) 16 | { 17 | Serial.println("check PAJ7620U2"); 18 | delay(500); 19 | } 20 | Serial.println("PAJ7620U2 success"); 21 | paj.setGestureHighRate(true); 22 | 23 | M5.EPD.SetRotation(90); 24 | M5.EPD.Clear(true); 25 | M5.RTC.begin(); 26 | canvas.createCanvas(540, 960); 27 | canvas.setTextSize(3); 28 | canvas.drawString("Hello World", 10, 10); 29 | canvas.pushCanvas(0, 0, UPDATE_MODE_DU4); 30 | } 31 | 32 | void loop() 33 | { 34 | DFRobot_PAJ7620U2::eGesture_t gesture = paj.getGesture(); 35 | if(gesture != paj.eGestureNone) 36 | { 37 | canvas.fillCanvas(0); 38 | canvas.drawString(paj.gestureDescription(gesture), 10, 10); 39 | canvas.pushCanvas(0, 0, UPDATE_MODE_DU4); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /M5Paper/I2CScanInternalAndAllPorts/I2CScanInternalAndAllPorts.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | typedef struct { 7 | TwoWire *wire; 8 | int sda; 9 | int scl; 10 | } i2cBus_t; 11 | 12 | i2cBus_t i2cBus_Internal = {&Wire, GPIO_NUM_21, GPIO_NUM_22}; 13 | i2cBus_t i2cBus_Port_A = {&Wire1, M5EPD_PORTA_Y_PIN, M5EPD_PORTA_W_PIN}; // 25, 32 14 | i2cBus_t i2cBus_Port_B = {&Wire1, M5EPD_PORTB_Y_PIN, M5EPD_PORTB_W_PIN}; // 26, 33 15 | i2cBus_t i2cBus_Port_C = {&Wire1, M5EPD_PORTC_Y_PIN, M5EPD_PORTC_W_PIN}; // 18, 19 16 | 17 | void setup() 18 | { 19 | M5.begin(); 20 | } 21 | 22 | void scanI2C(TwoWire *wire, int sda, int scl) 23 | { 24 | int address; 25 | int error; 26 | 27 | if(wire == &Wire1) wire->begin(sda, scl); 28 | 29 | for(address = 1; address < 127; address++) 30 | { 31 | wire->beginTransmission(address); 32 | error = wire->endTransmission(); 33 | if(error == 0) Serial.printf("0x%02x ", address); 34 | else Serial.print("."); 35 | delay(10); 36 | } 37 | Serial.println(); 38 | 39 | if(wire == &Wire1) wire->end(); 40 | } 41 | 42 | void loop() 43 | { 44 | Serial.println("I2C Scan - internal"); 45 | scanI2C(i2cBus_Internal.wire, i2cBus_Internal.sda, i2cBus_Internal.scl); 46 | Serial.println("I2C Scan - Port A"); 47 | scanI2C(i2cBus_Port_A.wire, i2cBus_Port_A.sda, i2cBus_Port_A.scl); 48 | Serial.println("I2C Scan - Port B"); 49 | scanI2C(i2cBus_Port_B.wire, i2cBus_Port_B.sda, i2cBus_Port_B.scl); 50 | Serial.println("I2C Scan - Port C"); 51 | scanI2C(i2cBus_Port_C.wire, i2cBus_Port_C.sda, i2cBus_Port_C.scl); 52 | delay(2000); 53 | } 54 | -------------------------------------------------------------------------------- /M5Paper/Lightsleep.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | from m5stack import * 5 | from m5ui import * 6 | from uiflow import * 7 | import time 8 | 9 | setScreenColor(15) 10 | 11 | while True: 12 | power.ext_power_on() # turn Groove port power on 13 | wait(1) 14 | power.ext_power_off() # turn Groove port power off 15 | 16 | from machine import Pin, lightsleep 17 | 18 | Pin(2, Pin.OUT, value=1, pull=Pin.PULL_HOLD) # M5EPD_MAIN_PWR_PIN 19 | lightsleep(1000*60) 20 | 21 | wait_ms(2) 22 | 23 | -------------------------------------------------------------------------------- /M5Paper/NonFlickeringUpdateAfterShutdown/NonFlickeringUpdateAfterShutdown.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | #include "time.h" 6 | 7 | M5EPD_Canvas CountPageSprite(&M5.EPD); 8 | 9 | char myBuf[11]; 10 | uint8_t myCount = 0; 11 | 12 | // EEPROM 8bit address: | 1 | 0 | 1 | 0 | A2 | A1 | A0 | R/W | 13 | // Note: A2, A1, A0 all low 14 | #define EEPROM_ADDR (0b10100000 >> 1) // 7bit address 15 | 16 | void writeEEPROM(int addr, unsigned int location, byte data) 17 | { 18 | Wire.beginTransmission(addr); 19 | Wire.write((int)(location & 0xFF)); // LSB 20 | Wire.write(data); 21 | Wire.endTransmission(); 22 | delay(5); 23 | } 24 | 25 | byte readEEPROM(int addr, unsigned int location) 26 | { 27 | byte rdata = 0xFF; 28 | 29 | Wire.beginTransmission(addr); 30 | Wire.write((int)(location & 0xFF)); // LSB 31 | Wire.endTransmission(); 32 | 33 | Wire.requestFrom(addr, 1); 34 | if(Wire.available()) 35 | { 36 | rdata = Wire.read(); 37 | } 38 | return rdata; 39 | } 40 | 41 | void setup() 42 | { 43 | // Enable everything except SD and RTC 44 | M5.begin(true, false, true, true, true); 45 | // Read wake up reason before RTC begin which clears interrupts 46 | uint8_t data = M5.RTC.readReg(0x01); 47 | // Start RTC and clear interrupt 48 | M5.RTC.begin(); 49 | 50 | M5.EPD.SetRotation(0); 51 | M5.TP.SetRotation(0); 52 | 53 | // Check timer flag 54 | if((data & 0b00000100) == 0b00000100) 55 | { 56 | Serial.println("Power on by: RTC timer"); 57 | M5.EPD.Clear(false); 58 | } 59 | else 60 | { 61 | Serial.println("Power on by: power button"); 62 | M5.EPD.Clear(true); 63 | myCount = 0; 64 | writeEEPROM(EEPROM_ADDR, 0, myCount); 65 | } 66 | 67 | // After every shutdown the sprite is created anew. 68 | // But the sprite doesn't know about the current image on the 69 | // ink display therefore the same count, as has been 70 | // drawn before the shutdown, is redrawn. 71 | // This is required, else drawing new count only adds 72 | // pixels to the already drawn pixels instead of clearing the 73 | // previous count and then draw the new count. 74 | CountPageSprite.createCanvas(960, 540); 75 | CountPageSprite.fillCanvas(1); 76 | 77 | CountPageSprite.setTextSize(3); 78 | CountPageSprite.setFreeFont(&FreeSansBold12pt7b); 79 | CountPageSprite.setTextDatum(TL_DATUM); 80 | 81 | // First draw old count 82 | myCount = readEEPROM(EEPROM_ADDR, 0); 83 | snprintf(myBuf, 10, "%04d ", myCount); 84 | CountPageSprite.drawString(myBuf, 20, 20); 85 | CountPageSprite.pushCanvas(0, 0, UPDATE_MODE_DU4); 86 | } 87 | 88 | void loop() 89 | { 90 | // Then draw new count 91 | myCount++; 92 | writeEEPROM(EEPROM_ADDR, 0, myCount); 93 | snprintf(myBuf, 10, "%04d ", myCount); 94 | CountPageSprite.drawString(myBuf, 20, 20); 95 | CountPageSprite.pushCanvas(0, 0, UPDATE_MODE_DU4); 96 | delay(250); // required, else no update 97 | 98 | Serial.printf("Shutdown...\n"); 99 | Serial.flush(); 100 | 101 | M5.shutdown(10); 102 | 103 | // This part is only executed if running from USB power 104 | delay(1000); 105 | // Undo what shutdown did 106 | M5.RTC.clearIRQ(); 107 | M5.enableMainPower(); 108 | // Kill some time 109 | Serial.println("Should not be reachded when battery powered 1"); 110 | delay(10000); 111 | Serial.println("Should not be reachded when battery powered 2"); 112 | } 113 | -------------------------------------------------------------------------------- /M5Paper/ShutdownWakeAfterHours/ShutdownWakeAfterHours.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | M5EPD_Canvas canvas(&M5.EPD); 7 | rtc_date_t myDate; 8 | rtc_time_t myTime; 9 | 10 | void setup() 11 | { 12 | // Enable everything except SD and RTC 13 | M5.begin(true, false, true, true, true); 14 | // Read wake up reason before RTC begin which clears interrupts 15 | uint8_t data = M5.RTC.readReg(0x01); 16 | // Start RTC and clear interrupt 17 | M5.RTC.begin(); 18 | // Setup paper display 19 | M5.EPD.SetRotation(0); 20 | M5.EPD.Clear(true); 21 | canvas.createCanvas(960, 540); 22 | canvas.setTextSize(3); 23 | // Check alarm flag 24 | if((data & 0b00001000) == 0b00001000) 25 | { 26 | canvas.drawString("Power on by RTC", 0, 0); 27 | canvas.pushCanvas(0, 0, UPDATE_MODE_DU4); 28 | } 29 | else 30 | { 31 | canvas.drawString("Power on by power button", 0, 0); 32 | canvas.pushCanvas(0, 0, UPDATE_MODE_DU4); 33 | // Small delay to allow paper display to update 34 | delay(250); 35 | // Set RTC date and time (arbitrarily) 36 | myDate.year = 1900; 37 | myDate.mon = 1; 38 | myDate.day = 1; 39 | myTime.hour = 0; 40 | myTime.min = 0; 41 | myTime.sec = 0; 42 | M5.RTC.setDate(&myDate); 43 | M5.RTC.setTime(&myTime); 44 | // Set alarm time 7 hours from now 45 | myTime.hour += 7; 46 | M5.shutdown(myDate, myTime); 47 | } 48 | } 49 | 50 | void loop() 51 | { 52 | delay(100); 53 | } 54 | -------------------------------------------------------------------------------- /M5Paper/SleepTest/SleepTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | M5EPD_Canvas canvas(&M5.EPD); 7 | 8 | void setup() 9 | { 10 | M5.begin(); 11 | M5.EPD.SetRotation(90); 12 | M5.EPD.Clear(true); 13 | canvas.createCanvas(540, 960); 14 | canvas.setTextSize(5); 15 | canvas.drawString("Touch The Screen!", 20, 400); 16 | canvas.pushCanvas(0, 0, UPDATE_MODE_DU4); 17 | } 18 | 19 | void loop() 20 | { 21 | // Clear out all previous touch screen interrupts 22 | while(digitalRead(GPIO_NUM_36) == LOW) 23 | { 24 | M5.TP.flush(); 25 | delay(10); 26 | } 27 | 28 | Serial.println("before sleep"); 29 | Serial.flush(); 30 | delay(1000); 31 | 32 | // Enable timer (10 s) 33 | esp_sleep_enable_timer_wakeup(10 * 1000000UL); 34 | 35 | // Enable touch screen 36 | esp_sleep_enable_ext0_wakeup(GPIO_NUM_36, LOW); 37 | 38 | // Test light sleep or ... 39 | // esp_light_sleep_start(); 40 | 41 | // ... test deep sleep 42 | // Keep power mosfet on while in deep sleep 43 | gpio_hold_en((gpio_num_t) M5EPD_MAIN_PWR_PIN); 44 | gpio_deep_sleep_hold_en(); 45 | esp_deep_sleep_start(); 46 | 47 | // Only reached after light sleep 48 | Serial.println("after sleep"); 49 | delay(1000); 50 | } 51 | -------------------------------------------------------------------------------- /M5PaperS3/LightSleepTouchWakeup/LightSleepTouchWakeup.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // Notes: 5 | // - Touch wake-up is only possible in ESP light sleep. 6 | // - Touch wake-up from ESP deep sleep is NOT possible as that would require the touch 7 | // interrupt line to be connected to a so called RTC GPIO which is not the case in M5PaperS3. 8 | // - Touch wake-up from shutdown mode is NOT possible as the eInk display and touch IC are 9 | // not running in shutdown mode. 10 | 11 | #include 12 | #include 13 | 14 | void setup(void) 15 | { 16 | auto cfg = M5.config(); 17 | cfg.serial_baudrate = 115200; 18 | cfg.internal_imu = false; 19 | M5.begin(cfg); 20 | 21 | M5.Display.setTextSize(5); 22 | if (M5.Display.isEPD()) 23 | { 24 | M5.Display.setEpdMode(epd_mode_t::epd_fastest); 25 | } 26 | M5.Display.setRotation(M5.Display.getRotation() ^ 1); 27 | 28 | delay(2000); 29 | Serial.printf("Starting...\n"); 30 | 31 | // LED on (Note: inverted) 32 | M5.Power.setLed(0); 33 | } 34 | 35 | void myPrintText(String t) 36 | { 37 | M5.Display.startWrite(); 38 | M5.Display.println(t.c_str()); 39 | M5.Display.endWrite(); 40 | delay(100); 41 | Serial.println(t.c_str()); 42 | Serial.flush(); 43 | } 44 | 45 | void loop() 46 | { 47 | // Clear out old touch interrupts 48 | M5.update(); 49 | delay(150); 50 | M5.update(); 51 | delay(150); 52 | M5.update(); 53 | delay(150); 54 | // Clear display 55 | M5.Display.fillScreen(TFT_BLACK); 56 | M5.Display.fillScreen(TFT_WHITE); 57 | 58 | M5.Display.setCursor(0, 0); 59 | myPrintText("Light sleep touch test"); 60 | myPrintText("Going to light sleep now"); 61 | myPrintText("Touch to wake up"); 62 | // LED off (Note: inverted) 63 | M5.Power.setLed(255); 64 | delay(100); 65 | // Touch interrupt is connected to GPIO48 which in NOT an RTC GPIO, 66 | // therefore touch wakeup only works with light sleep. 67 | gpio_wakeup_enable((gpio_num_t) GPIO_NUM_48, GPIO_INTR_LOW_LEVEL); 68 | esp_sleep_enable_gpio_wakeup(); 69 | esp_light_sleep_start(); 70 | gpio_wakeup_disable((gpio_num_t) GPIO_NUM_48); 71 | 72 | myPrintText("Awake after light sleep"); 73 | // LED on (Note: inverted) 74 | M5.Power.setLed(0); 75 | delay(2000); 76 | } 77 | -------------------------------------------------------------------------------- /M5PaperS3/RTCClockNonFlickering/RTCClockNonFlickering.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // This RTC clock example keeps the system mostly in shutdown mode and 5 | // only wakes up every 56 seconds for a brief period of time during 6 | // which the time and date are updated on the eInk display. 7 | // 8 | // When started initially or via power button a full eInk display refresh 9 | // is executed to clear the display. 10 | // The current time and date are fetched via NTP and shown on the eInk display. 11 | // After waiting for the full minute the system is put into shutdown 12 | // mode for about 56 seconds. 13 | // When the RTC timer expires (just before the next minute change) the 14 | // system is powered on. 15 | // The eInk display is updated with the current time and date. 16 | // Then the system goes back into shutdown mode for about 56 seconds and 17 | // the cycle begins anew. 18 | // Every hour a full eInk display refresh is executed to keep the ink 19 | // display crisp. 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "time.h" 26 | 27 | const char* ssid = "YOUR_SSID"; 28 | const char* password = "YOUR_PASSWORD"; 29 | const char* ntpServer = "pool.ntp.org"; 30 | // Time zones (https://remotemonitoringsystems.ca/time-zone-abbreviations.php) 31 | const char* TZ_INFO = "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"; 32 | 33 | // every hour at minute 45 do a full ink display refresh 34 | #define FULL_REFRESH_MINUTE (45) 35 | 36 | bool bRTCRestart = false; 37 | 38 | void printLocalTimeAndSetRTC() 39 | { 40 | struct tm timeinfo; 41 | 42 | if(getLocalTime(&timeinfo) == false) 43 | { 44 | Serial.println("Failed to obtain time"); 45 | return; 46 | } 47 | Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); 48 | 49 | m5::rtc_time_t time; 50 | time.hours = timeinfo.tm_hour; 51 | time.minutes = timeinfo.tm_min; 52 | time.seconds = timeinfo.tm_sec; 53 | M5.Rtc.setTime(&time); 54 | 55 | m5::rtc_date_t date; 56 | date.date = timeinfo.tm_mday; 57 | date.month = timeinfo.tm_mon + 1; 58 | date.year = timeinfo.tm_year + 1900; 59 | M5.Rtc.setDate(&date); 60 | } 61 | 62 | void getNTPTime() 63 | { 64 | // Try to connect for 10 seconds 65 | uint32_t connect_timeout = millis() + 10000; 66 | 67 | Serial.printf("Connecting to %s ", ssid); 68 | WiFi.begin(ssid, password); 69 | while((WiFi.status() != WL_CONNECTED) && (millis() < connect_timeout)) 70 | { 71 | delay(500); 72 | Serial.print("."); 73 | } 74 | if(WiFi.status() != WL_CONNECTED) 75 | { 76 | // WiFi connection failed - set fantasy time and date 77 | m5::rtc_time_t time; 78 | time.hours = 6; 79 | time.minutes = 43; 80 | time.seconds = 50; 81 | M5.Rtc.setTime(&time); 82 | 83 | m5::rtc_date_t date; 84 | date.date = 4; 85 | date.month = 12; 86 | date.year = 2032; 87 | M5.Rtc.setDate(&date); 88 | return; 89 | } 90 | 91 | Serial.println("Connected"); 92 | 93 | configTime(0, 0, ntpServer); 94 | setenv("TZ", TZ_INFO, 1); 95 | tzset(); 96 | 97 | printLocalTimeAndSetRTC(); 98 | 99 | WiFi.disconnect(true); 100 | WiFi.mode(WIFI_OFF); 101 | } 102 | 103 | void drawTimeAndDate(m5::rtc_time_t time, m5::rtc_date_t date) 104 | { 105 | char buf[11]; 106 | 107 | M5.Display.startWrite(); 108 | M5.Display.setTextSize(4); 109 | M5.Display.setFont(&FreeSansBold24pt7b); 110 | M5.Display.setTextDatum(TC_DATUM); 111 | snprintf(buf, 8, " %02d:%02d ", time.hours, time.minutes); 112 | M5.Display.drawString(buf, 960/2, 50); 113 | snprintf(buf, 11, " %02d.%02d.%02d ", date.date, date.month, date.year - 2000); 114 | M5.Display.drawString(buf, 960/2, 260); 115 | M5.Display.endWrite(); 116 | } 117 | 118 | void drawBatteryVoltage() 119 | { 120 | int16_t bat = M5.Power.getBatteryVoltage(); 121 | M5.Display.startWrite(); 122 | M5.Display.fillRect(20, 480, 280, 60, TFT_BLACK); 123 | M5.Display.endWrite(); 124 | M5.Display.startWrite(); 125 | M5.Display.fillRect(20, 480, 280, 60, TFT_WHITE); 126 | M5.Display.endWrite(); 127 | 128 | M5.Display.startWrite(); 129 | M5.Display.setTextSize(3); 130 | M5.Display.setFont(&FreeSansBold12pt7b); 131 | M5.Display.setTextDatum(TL_DATUM); 132 | M5.Display.drawString(String(bat) + String("mV"), 20, 480); 133 | M5.Display.endWrite(); 134 | } 135 | 136 | void setup(void) 137 | { 138 | auto cfg = M5.config(); 139 | cfg.serial_baudrate = 115200; 140 | cfg.internal_imu = false; 141 | cfg.internal_rtc = false; 142 | cfg.clear_display = false; 143 | M5.begin(cfg); 144 | 145 | uint8_t data = M5.Rtc.readRegister8(0x01); 146 | if(M5.In_I2C.isEnabled()) 147 | { 148 | Serial.printf("*** RTC begin\n"); 149 | M5.Rtc.begin(); 150 | } 151 | Serial.printf("*** data: %X\n", data); 152 | if((data & 0b00000101) == 0b00000101) 153 | { 154 | bRTCRestart = true; 155 | } 156 | M5.Rtc.disableIRQ(); 157 | M5.Rtc.clearIRQ(); 158 | uint8_t data2 = M5.Rtc.readRegister8(0x01); 159 | Serial.printf("*** data2: %X\n", data2); 160 | 161 | M5.Power.setLed(127); 162 | 163 | m5::rtc_time_t time; 164 | m5::rtc_date_t date; 165 | 166 | if(bRTCRestart == true) 167 | { 168 | Serial.println("Power on by: RTC timer"); 169 | 170 | M5.Rtc.getTime(&time); 171 | M5.Rtc.getDate(&date); 172 | 173 | // Full refresh once per hour 174 | if(time.minutes == FULL_REFRESH_MINUTE - 1) 175 | { 176 | // Full ink display init 177 | M5.Display.clearDisplay(); 178 | } 179 | } 180 | else 181 | { 182 | Serial.println("Power on by: power button"); 183 | 184 | // Full ink display init 185 | M5.Display.clearDisplay(); 186 | 187 | // Fetch current time from Internet 188 | getNTPTime(); 189 | M5.Rtc.getTime(&time); 190 | M5.Rtc.getDate(&date); 191 | } 192 | 193 | if (M5.Display.isEPD()) 194 | { 195 | M5.Display.setEpdMode(epd_mode_t::epd_fastest); 196 | } 197 | M5.Display.setRotation(M5.Display.getRotation() ^ 1); 198 | 199 | drawBatteryVoltage(); 200 | 201 | // After every shutdown the eInk buffer is initialized anew. 202 | // But the eInk buffer doesn't match the current image on the 203 | // ink display therefore the same time and date, as have been 204 | // drawn before the shutdown, are redrawn. 205 | // This is required, else drawing new time and date only adds 206 | // pixels to the already drawn pixels instead of clearing the 207 | // previous time and date and then draw the new time and date. 208 | 209 | drawTimeAndDate(time, date); 210 | 211 | while((time.seconds != 0)) 212 | { 213 | M5.Rtc.getTime(&time); 214 | delay(200); 215 | } 216 | M5.Rtc.getDate(&date); 217 | 218 | drawTimeAndDate(time, date); 219 | 220 | Serial.printf("Shutdown...\n"); 221 | Serial.flush(); 222 | 223 | // Full refresh once per hour 224 | if(time.minutes == FULL_REFRESH_MINUTE - 1) 225 | { 226 | // Allow extra time for full ink refresh 227 | // Shutdown for 53 seconds only 228 | M5.Power.timerSleep(53); 229 | return; 230 | } 231 | // Shutdown for 56 seconds 232 | M5.Power.timerSleep(56); 233 | } 234 | 235 | void loop(void) 236 | { 237 | } 238 | -------------------------------------------------------------------------------- /M5Stack/Cam2CoreExtended/Cam2CoreExtended.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // Based on CAM2CORE example by M5Stack 5 | // https://github.com/m5stack/M5-ProductExampleCodes/tree/master/Unit/UNIT_CAM/CAM2CORE 6 | 7 | #include 8 | #include "uart_frame.h" 9 | 10 | // Serial connection between M5Stack and Camera 11 | // - receive picture frames from camera 12 | // - send commands to camera 13 | #define UART_RX_PIN 36 14 | #define UART_TX_PIN 26 15 | 16 | typedef enum { 17 | kImage = 0x00, 18 | // cam config 19 | kFrameSize, 20 | kQuality, 21 | kContrast, 22 | kBrightness, 23 | kSaturation, 24 | kGainceiling, 25 | kColorbar, 26 | kAwb, 27 | kAgc, 28 | kAec, 29 | kHmirror, 30 | kVflip, 31 | kAwbGain, 32 | kAgcGain, 33 | kAecValue, 34 | kAec2, 35 | kDcw, 36 | kBpc, 37 | kWpc, 38 | kRawGma, 39 | kLenc, 40 | kSpecialEffect, 41 | kWbMode, 42 | kAeLevel, 43 | kCamCmdEnd, 44 | // usr cmd 45 | kSetDeviceMode, 46 | kGetDeviceMode, 47 | kSaveDeviceConfig, 48 | kGetCamConfig, 49 | kSaveCamConfig, 50 | kSetWiFi, 51 | kGetWifiSSID, 52 | kGetWifiIP, 53 | kGetWifiState, 54 | kTimingTime, 55 | kFactoryTest, 56 | kErrorOccur, 57 | KRestart, 58 | } CmdList_t; 59 | 60 | typedef enum { 61 | FRAMESIZE_96X96, // 96x96 62 | FRAMESIZE_QQVGA, // 160x120 63 | FRAMESIZE_QCIF, // 176x144 64 | FRAMESIZE_HQVGA, // 240x176 65 | FRAMESIZE_240X240, // 240x240 66 | FRAMESIZE_QVGA, // 320x240 67 | FRAMESIZE_CIF, // 400x296 68 | FRAMESIZE_HVGA, // 480x320 69 | FRAMESIZE_VGA, // 640x480 70 | FRAMESIZE_SVGA, // 800x600 71 | FRAMESIZE_XGA, // 1024x768 72 | FRAMESIZE_HD, // 1280x720 73 | FRAMESIZE_SXGA, // 1280x1024 74 | FRAMESIZE_UXGA, // 1600x1200 75 | // 3MP Sensors 76 | FRAMESIZE_FHD, // 1920x1080 77 | FRAMESIZE_P_HD, // 720x1280 78 | FRAMESIZE_P_3MP, // 864x1536 79 | FRAMESIZE_QXGA, // 2048x1536 80 | // 5MP Sensors 81 | FRAMESIZE_QHD, // 2560x1440 82 | FRAMESIZE_WQXGA, // 2560x1600 83 | FRAMESIZE_P_FHD, // 1080x1920 84 | FRAMESIZE_QSXGA, // 2560x1920 85 | FRAMESIZE_INVALID 86 | } framesize_t; 87 | 88 | typedef struct _JpegFrame_t { 89 | uint8_t *buf; 90 | uint32_t size; 91 | } JpegFrame_t; 92 | 93 | static QueueHandle_t jpeg_fream_queue = NULL; 94 | 95 | void frame_recv_callback(int cmd, const uint8_t*buf, int len) 96 | { 97 | JpegFrame_t frame; 98 | 99 | frame.buf = (uint8_t *) malloc(sizeof(uint8_t) * len); 100 | memcpy(frame.buf, buf, len); 101 | frame.size = len; 102 | 103 | if(xQueueSend(jpeg_fream_queue, &frame, 0) != pdTRUE) 104 | { 105 | free(frame.buf); 106 | } 107 | } 108 | 109 | void setup() 110 | { 111 | M5.begin(true, false, true, false); 112 | uart_frame_init(UART_RX_PIN, UART_TX_PIN, 1500000); 113 | jpeg_fream_queue = xQueueCreate(2, sizeof(JpegFrame_t)); 114 | } 115 | 116 | void loop() 117 | { 118 | JpegFrame_t frame; 119 | 120 | if(xQueueReceive(jpeg_fream_queue, &frame, portMAX_DELAY) == pdTRUE) 121 | { 122 | M5.Lcd.drawJpg(frame.buf, frame.size, 0, 0); 123 | free(frame.buf); 124 | } 125 | 126 | M5.update(); 127 | if(M5.BtnA.wasPressed() == true) 128 | { 129 | // uint8_t b[2] = {1}; 130 | // uart_frame_send(kHmirror, b, 2, true); 131 | // uint8_t b[2] = {5}; 132 | // uart_frame_send(kQuality, b, 2, true); 133 | uint8_t b[2] = {FRAMESIZE_QQVGA}; 134 | uart_frame_send(kFrameSize, b, 2, true); 135 | } 136 | if(M5.BtnB.wasPressed() == true) 137 | { 138 | // uint8_t b[2] = {0}; 139 | // uart_frame_send(kHmirror, b, 2, true); 140 | // uint8_t b[2] = {30}; 141 | // uart_frame_send(kQuality, b, 2, true); 142 | uint8_t b[2] = {FRAMESIZE_HQVGA}; 143 | uart_frame_send(kFrameSize, b, 2, true); 144 | } 145 | if(M5.BtnC.wasPressed() == true) 146 | { 147 | // uint8_t b[2] = {60}; 148 | // uart_frame_send(kQuality, b, 2, true); 149 | uint8_t b[2] = {FRAMESIZE_QVGA}; 150 | uart_frame_send(kFrameSize, b, 2, true); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /M5Stack/Cam2CoreExtended/uart_frame.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "freertos/FreeRTOS.h" 3 | #include "freertos/task.h" 4 | #include "driver/gpio.h" 5 | #include "driver/uart.h" 6 | #include "string.h" 7 | #include "esp_heap_caps.h" 8 | #include "uart_frame.h" 9 | 10 | #define UART_NUM UART_NUM_1 11 | #define UART_QUEUE_LENGTH 10 12 | #define TX_BUF_SIZE 0 13 | #define FRAME_MAX_SIZE (16 * 1024) 14 | #define RX_BUF_SIZE 2 * 1024 15 | 16 | #define PACK_FIRST_BYTE 0xAA 17 | #define PACK_SECOND_BYTE 0x55 18 | 19 | volatile frame_state_n frame_state; 20 | static QueueHandle_t uart_queue = NULL; 21 | static QueueHandle_t uart_buffer_queue = NULL; 22 | 23 | static void uart_frame_task(void *arg); 24 | static void uart_frame_send_task(void *arg); 25 | 26 | typedef struct _UartFrame_t { 27 | bool free_buffer; 28 | uint8_t* buffer; 29 | uint32_t len; 30 | } UartFrame_t; 31 | 32 | int32_t baud_rate = 1500000; 33 | uint16_t uart_tx_pin = 26; 34 | uint16_t uart_rx_pin = 36; 35 | 36 | void __attribute__((weak)) frame_post_callback(uint8_t cmd) { 37 | 38 | } 39 | 40 | void __attribute__((weak)) frame_recv_callback(int cmd, const uint8_t*buf, int len) { 41 | 42 | } 43 | 44 | static void uart_base_init() { 45 | uart_driver_delete(UART_NUM); 46 | const uart_config_t uart_config = { 47 | .baud_rate = baud_rate, 48 | .data_bits = UART_DATA_8_BITS, 49 | .parity = UART_PARITY_DISABLE, 50 | .stop_bits = UART_STOP_BITS_1, 51 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE 52 | }; 53 | uart_param_config(UART_NUM, &uart_config); 54 | uart_set_pin(UART_NUM, uart_tx_pin, uart_rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); 55 | uart_driver_install(UART_NUM, RX_BUF_SIZE, TX_BUF_SIZE, UART_QUEUE_LENGTH, &uart_queue, ESP_INTR_FLAG_LOWMED); 56 | uart_set_rx_timeout(UART_NUM, 50); 57 | } 58 | 59 | void uart_frame_init(int32_t rx, int32_t tx, int baud) { 60 | baud_rate = baud; 61 | uart_rx_pin = rx; 62 | uart_tx_pin = tx; 63 | uart_buffer_queue = xQueueCreate(1, sizeof(UartFrame_t)); 64 | uart_base_init(); 65 | xTaskCreatePinnedToCore(uart_frame_task, "uart_queue_task", 4 * 1024, NULL, 3, NULL, 0); 66 | xTaskCreatePinnedToCore(uart_frame_send_task, "uart_frame_send_task", 2 * 1024, NULL, 1, NULL, 0); 67 | } 68 | 69 | void uart_update_pin(int32_t rx, int32_t tx) { 70 | uart_set_pin(UART_NUM, tx, rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); 71 | } 72 | 73 | typedef enum { 74 | kWaitStart = 0x00, 75 | kRecvLenght, 76 | kLenghtCheck, 77 | kRecvCMD, 78 | kRecvData, 79 | kCrcCheck, 80 | kSuccess, 81 | } frame_state_t; 82 | 83 | static void uart_frame_send_task(void *arg) { 84 | UartFrame_t frame; 85 | 86 | for (;;) { 87 | xQueueReceive(uart_buffer_queue, &frame, portMAX_DELAY); 88 | uart_wait_tx_done(UART_NUM, portMAX_DELAY); 89 | uart_write_bytes(UART_NUM, (const char *)frame.buffer, frame.len); 90 | uart_wait_tx_done(UART_NUM, portMAX_DELAY); 91 | frame_post_callback(frame.buffer[7]); 92 | if (frame.free_buffer) { 93 | free(frame.buffer); 94 | } 95 | } 96 | vTaskDelete(NULL); 97 | } 98 | 99 | static void uart_frame_task(void *arg) { 100 | uart_event_t xEvent; 101 | // uint32_t buf_pos = 0; 102 | 103 | uint8_t *buf = (uint8_t *)heap_caps_calloc(FRAME_MAX_SIZE, sizeof(uint8_t), MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL); 104 | uint8_t *recv_buf = (uint8_t *)malloc(1024 * sizeof(uint8_t)); 105 | uint8_t* ptr = recv_buf; 106 | uint32_t packet_pos = 0; 107 | uint8_t packet_ahead[] = {PACK_FIRST_BYTE, PACK_SECOND_BYTE}; 108 | uint32_t frame_length = 0; 109 | uint8_t crc_xor = 0x00; 110 | uint16_t size; 111 | buf[0] = PACK_FIRST_BYTE; 112 | buf[1] = PACK_SECOND_BYTE; 113 | frame_state_t frame_state = kWaitStart; 114 | 115 | for(;;) { 116 | if (xQueueReceive(uart_queue, (void*)&xEvent, portMAX_DELAY) == pdTRUE) { 117 | switch(xEvent.type) { 118 | case UART_DATA: { 119 | uart_read_bytes(UART_NUM, recv_buf, xEvent.size, portMAX_DELAY); 120 | ptr = recv_buf; 121 | size = xEvent.size; 122 | while (size) { 123 | switch (frame_state) { 124 | case kWaitStart: 125 | buf[packet_pos] = *ptr; 126 | ptr += 1; 127 | size -= 1; 128 | 129 | if (buf[packet_pos] == packet_ahead[packet_pos]) { 130 | packet_pos += 1; 131 | if (packet_pos == 2) { 132 | frame_length = 0; 133 | frame_state = kRecvLenght; 134 | 135 | } 136 | } else { 137 | packet_pos = 0; 138 | } 139 | break; 140 | 141 | case kRecvLenght: 142 | buf[packet_pos] = *ptr; 143 | ptr += 1; 144 | size -= 1; 145 | packet_pos += 1; 146 | if (packet_pos == (2 + 4)) { 147 | crc_xor = 0x000; 148 | frame_state = kLenghtCheck; 149 | } 150 | break; 151 | 152 | case kLenghtCheck: 153 | buf[packet_pos] = *ptr; 154 | ptr += 1; 155 | size -= 1; 156 | crc_xor = 0x00 ^ buf[2] ^ buf[3] ^ buf[4] ^ buf[5]; 157 | if (crc_xor == buf[packet_pos]) { 158 | packet_pos += 1; 159 | frame_length = (buf[2] << 24) | (buf[3] << 16) | (buf[4] << 8) | (buf[5] << 0); 160 | if (frame_length > FRAME_MAX_SIZE - 9) { 161 | packet_pos = 0; 162 | frame_state = kWaitStart; 163 | } else { 164 | frame_state = kRecvCMD; 165 | } 166 | } else { 167 | packet_pos = 0; 168 | frame_state = kWaitStart; 169 | } 170 | break; 171 | 172 | case kRecvCMD: { 173 | buf[packet_pos] = *ptr; 174 | ptr += 1; 175 | size -= 1; 176 | packet_pos += 1; 177 | frame_state = kRecvData; 178 | break; 179 | } 180 | 181 | case kRecvData: 182 | while(size > 0) { 183 | buf[packet_pos] = *ptr; 184 | ptr += 1; 185 | size -= 1; 186 | packet_pos += 1; 187 | if (packet_pos == (frame_length + 6)) { 188 | frame_state = kCrcCheck; 189 | break; 190 | } 191 | } 192 | break; 193 | 194 | case kCrcCheck: 195 | buf[packet_pos] = *ptr; 196 | ptr += 1; 197 | size -= 1; 198 | crc_xor = 0x00; 199 | 200 | for (uint16_t i = 0; i < packet_pos; i++) { 201 | crc_xor = buf[i] ^ crc_xor; 202 | } 203 | 204 | if (crc_xor != buf[packet_pos]) { 205 | packet_pos = 0; 206 | frame_state = kWaitStart; 207 | break; 208 | } 209 | 210 | case kSuccess: 211 | packet_pos = 0; 212 | frame_state = kWaitStart; 213 | frame_recv_callback(buf[7], &buf[8], frame_length - 2); 214 | break; 215 | 216 | default: 217 | break; 218 | } 219 | } 220 | break; 221 | } 222 | 223 | case UART_FIFO_OVF: { 224 | xQueueReset(uart_queue); 225 | break; 226 | } 227 | 228 | case UART_BUFFER_FULL: { 229 | uart_flush_input(UART_NUM); 230 | xQueueReset(uart_queue); 231 | break; 232 | } 233 | 234 | case UART_BREAK: { 235 | break; 236 | } 237 | 238 | case UART_PARITY_ERR: { 239 | break; 240 | } 241 | 242 | case UART_FRAME_ERR: { 243 | break; 244 | } 245 | 246 | default: { 247 | break; 248 | } 249 | } 250 | } 251 | } 252 | vTaskDelete(NULL); 253 | } 254 | 255 | void uart_frame_send(uint8_t cmd, const uint8_t* frame, uint32_t len, bool wait_finish) { 256 | uint32_t out_len = 9 + len; 257 | uint8_t* out_buf = (uint8_t *)malloc(sizeof(uint8_t) * out_len); 258 | 259 | out_buf[0] = PACK_FIRST_BYTE; 260 | out_buf[1] = PACK_SECOND_BYTE; 261 | out_buf[2] = (out_len - 7) >> 24; 262 | out_buf[3] = (out_len - 7) >> 16; 263 | out_buf[4] = (out_len - 7) >> 8; 264 | out_buf[5] = (out_len - 7); 265 | out_buf[6] = 0x00 ^ out_buf[2] ^ out_buf[3] ^ out_buf[4] ^ out_buf[5]; 266 | out_buf[7] = cmd; 267 | memcpy(&out_buf[8], frame, len); 268 | int xor_resilt = 0x00; 269 | for (uint32_t i = 0; i < out_len - 1; i++) { 270 | xor_resilt = out_buf[i] ^ xor_resilt; 271 | } 272 | out_buf[out_len - 1] = xor_resilt; 273 | 274 | if (wait_finish) { 275 | while (uxQueueMessagesWaiting(uart_buffer_queue)) { 276 | vTaskDelay(pdMS_TO_TICKS(10)); 277 | } 278 | } 279 | 280 | UartFrame_t uart_frame; 281 | uart_frame.buffer = out_buf; 282 | uart_frame.len = out_len; 283 | uart_frame.free_buffer = true; 284 | 285 | xQueueSend(uart_buffer_queue, &uart_frame, portMAX_DELAY); 286 | } 287 | -------------------------------------------------------------------------------- /M5Stack/Cam2CoreExtended/uart_frame.h: -------------------------------------------------------------------------------- 1 | #ifndef _UART_FRAME_H 2 | #define _UART_FRAME_H 3 | 4 | #include 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/queue.h" 7 | 8 | typedef enum { 9 | IDLE = 0x00, 10 | WAIT_FINISH , 11 | FINISH, 12 | } frame_state_n; 13 | 14 | extern volatile frame_state_n frame_state; 15 | 16 | void uart_frame_init(int32_t rx, int32_t tx, int baud); 17 | void uart_frame_send(uint8_t cmd, const uint8_t* frame, uint32_t len, bool wait_finish); 18 | void uart_update_pin(int32_t rx, int32_t tx); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /M5Stack/OTAoverLTE/OTAoverLTE.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // Based on TinyGSM - FileDownload example 5 | // https://github.com/vshymanskyy/TinyGSM/blob/master/examples/FileDownload/FileDownload.ino 6 | 7 | #define TINY_GSM_MODEM_SIM7080 8 | //#define TINY_GSM_MODEM_SIM7600 // Modem: A7600C1, SIM7600G 9 | 10 | #define DO_NOT_USE_SSL 11 | 12 | #define PIN_MODEM_RX GPIO_NUM_35 13 | #define PIN_MODEM_TX GPIO_NUM_0 14 | #define PIN_MODEM_PWR GPIO_NUM_12 15 | #define GSM_PIN "1234" 16 | 17 | const char apn[] = "your_apn"; 18 | const char gprsUser[] = ""; 19 | const char gprsPass[] = ""; 20 | 21 | const char server[] = "your_ota_server"; 22 | #ifdef DO_NOT_USE_SSL 23 | const int port = 80; 24 | const char resource[] = "/firmware.bin"; 25 | #else 26 | const int port = 443; 27 | const char resource[] = "/firmware.bin"; 28 | #endif 29 | 30 | #define SerialMon Serial 31 | #define SerialAT Serial1 32 | 33 | // Increase RX buffer to capture the entire response 34 | // Chips without internal buffering (A6/A7, ESP8266, M590) 35 | // need enough space in the buffer for the entire response 36 | // else data will be lost (and the http library will fail). 37 | #if !defined(TINY_GSM_RX_BUFFER) 38 | #define TINY_GSM_RX_BUFFER 1024 39 | #endif 40 | 41 | #define TINY_GSM_DEBUG SerialMon 42 | 43 | #include 44 | #include 45 | 46 | TinyGsm modem(SerialAT); 47 | #ifdef DO_NOT_USE_SSL 48 | TinyGsmClient client(modem); 49 | #else 50 | TinyGsmClientSecure client(modem); 51 | #endif 52 | bool bReboot = false; 53 | 54 | void setup() 55 | { 56 | SerialMon.begin(115200); 57 | delay(10); 58 | 59 | pinMode(PIN_MODEM_PWR, OUTPUT); 60 | digitalWrite(PIN_MODEM_PWR, LOW); 61 | delay(500); 62 | digitalWrite(PIN_MODEM_PWR, HIGH); 63 | delay(500); 64 | digitalWrite(PIN_MODEM_PWR, LOW); 65 | 66 | SerialMon.println("Wait..."); 67 | 68 | SerialAT.begin(115200, SERIAL_8N1, PIN_MODEM_RX, PIN_MODEM_TX); 69 | delay(6000); 70 | 71 | // Restart takes quite some time 72 | // To skip it, call init() instead of restart() 73 | SerialMon.println("Initializing modem..."); 74 | modem.restart(); 75 | // modem.init(); 76 | 77 | // Note: for A7600C1, SIM7600G 78 | // Allow unsolicited messages e.g. 79 | // +CPIN: READY 80 | // SMS DONE 81 | // PB DONE 82 | // to arrive _before_ the download is started 83 | // else the download might end prematurely 84 | modem.testAT(30000); 85 | 86 | String modemInfo = modem.getModemInfo(); 87 | SerialMon.print("Modem Info: "); 88 | SerialMon.println(modemInfo); 89 | 90 | if(GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } 91 | } 92 | 93 | void printPercent(uint32_t readLength, uint32_t contentLength) 94 | { 95 | if(contentLength != (uint32_t)-1) 96 | { 97 | SerialMon.print("\r "); 98 | SerialMon.print((100.0 * readLength) / contentLength); 99 | SerialMon.print('%'); 100 | } 101 | else 102 | { 103 | SerialMon.println(readLength); 104 | } 105 | } 106 | 107 | void loop() 108 | { 109 | SerialMon.print(F("Waiting for network...")); 110 | if(!modem.waitForNetwork()) 111 | { 112 | SerialMon.println(F(" fail")); 113 | delay(10000); 114 | return; 115 | } 116 | SerialMon.println(F(" success")); 117 | 118 | if(modem.isNetworkConnected()) { SerialMon.println(F("Network connected")); } 119 | 120 | // GPRS connection parameters are usually set after network registration 121 | SerialMon.print(F("Connecting to ")); 122 | SerialMon.print(apn); 123 | if(!modem.gprsConnect(apn, gprsUser, gprsPass)) 124 | { 125 | SerialMon.println(F(" fail")); 126 | delay(10000); 127 | return; 128 | } 129 | SerialMon.println(F(" success")); 130 | 131 | if(modem.isGprsConnected()) { SerialMon.println(F("GPRS connected")); } 132 | 133 | SerialMon.print(F("Connecting to ")); 134 | SerialMon.print(server); 135 | if(!client.connect(server, port)) { 136 | SerialMon.println(F(" fail")); 137 | delay(10000); 138 | return; 139 | } 140 | SerialMon.println(F(" success")); 141 | 142 | client.print(String("GET ") + resource + " HTTP/1.0\r\n"); 143 | client.print(String("Host: ") + server + "\r\n"); 144 | client.print("Connection: close\r\n\r\n"); 145 | 146 | uint32_t timeElapsed = millis(); 147 | 148 | SerialMon.println(F("Waiting for response header")); 149 | 150 | // While we are still looking for the end of the header (i.e. empty line 151 | // FOLLOWED by a newline), continue to read data into the buffer, parsing each 152 | // line (data FOLLOWED by a newline). If it takes too long to get data from 153 | // the client, we need to exit. 154 | 155 | const uint32_t clientReadTimeout = 5000; 156 | uint32_t clientReadStartTime = millis(); 157 | String headerBuffer; 158 | bool finishedHeader = false; 159 | uint32_t contentLength = 0; 160 | 161 | while(!finishedHeader) 162 | { 163 | int nlPos; 164 | 165 | if(client.available()) 166 | { 167 | clientReadStartTime = millis(); 168 | while(client.available()) 169 | { 170 | char c = client.read(); 171 | headerBuffer += c; 172 | 173 | // Uncomment the lines below to see the data coming into the buffer 174 | // if (c < 16) 175 | // SerialMon.print('0'); 176 | // SerialMon.print(c, HEX); 177 | // SerialMon.print(' '); 178 | // if (isprint(c)) 179 | // SerialMon.print(reinterpret_cast c); 180 | // else 181 | // SerialMon.print('*'); 182 | // SerialMon.print(' '); 183 | 184 | // Let's exit and process if we find a new line 185 | if(headerBuffer.indexOf(F("\r\n")) >= 0) break; 186 | } 187 | } 188 | else 189 | { 190 | if(millis() - clientReadStartTime > clientReadTimeout) 191 | { 192 | SerialMon.println(F(">>> Client Timeout !")); 193 | break; 194 | } 195 | } 196 | 197 | nlPos = headerBuffer.indexOf(F("\r\n")); 198 | 199 | if(nlPos > 0) 200 | { 201 | headerBuffer.toLowerCase(); 202 | if(headerBuffer.startsWith(F("content-length:"))) 203 | { 204 | contentLength = headerBuffer.substring(headerBuffer.indexOf(':') + 1).toInt(); 205 | } 206 | headerBuffer.remove(0, nlPos + 2); 207 | } 208 | else if(nlPos == 0) 209 | { 210 | finishedHeader = true; 211 | } 212 | } 213 | 214 | Serial.printf("Content-length: %d\n", contentLength); 215 | if(Update.begin(contentLength)) 216 | { 217 | Serial.println(F("Update begin ok")); 218 | } 219 | else 220 | { 221 | Serial.println(F("Update begin not ok")); 222 | 223 | while (true) { delay(1000); } 224 | } 225 | 226 | // The two cases which are not managed properly are as follows: 227 | // 1. The client doesn't provide data quickly enough to keep up with this 228 | // loop. 229 | // 2. If the client data is segmented in the middle of the 'Content-Length: ' 230 | // header, 231 | // then that header may be missed/damaged. 232 | // 233 | 234 | uint32_t readLength = 0; 235 | 236 | if(finishedHeader) 237 | { 238 | SerialMon.println(F("Reading response data")); 239 | clientReadStartTime = millis(); 240 | 241 | printPercent(readLength, contentLength); 242 | while(readLength < contentLength && client.connected() && 243 | millis() - clientReadStartTime < clientReadTimeout) 244 | { 245 | while(client.available()) 246 | { 247 | uint8_t c = client.read(); 248 | Update.write(&c, 1); 249 | readLength++; 250 | if(readLength % (contentLength / 13) == 0) 251 | { 252 | printPercent(readLength, contentLength); 253 | } 254 | clientReadStartTime = millis(); 255 | } 256 | } 257 | printPercent(readLength, contentLength); 258 | } 259 | 260 | timeElapsed = millis() - timeElapsed; 261 | SerialMon.println(); 262 | 263 | if(Update.end()) 264 | { 265 | Serial.println(F("OTA done!")); 266 | if(Update.isFinished()) 267 | { 268 | Serial.println(F("Update successfully completed. Rebooting.")); 269 | bReboot = true; 270 | } 271 | else 272 | { 273 | Serial.println(F("Update not finished? Something went wrong!")); 274 | } 275 | } 276 | else 277 | { 278 | Serial.println("Error Occurred. Error #: " + String(Update.getError())); 279 | } 280 | 281 | client.stop(); 282 | SerialMon.println(F("Server disconnected")); 283 | 284 | modem.gprsDisconnect(); 285 | SerialMon.println(F("GPRS disconnected")); 286 | 287 | float duration = float(timeElapsed) / 1000; 288 | 289 | SerialMon.println(); 290 | SerialMon.print("Content-Length: "); 291 | SerialMon.println(contentLength); 292 | SerialMon.print("Actually read: "); 293 | SerialMon.println(readLength); 294 | SerialMon.print("Duration: "); 295 | SerialMon.print(duration); 296 | SerialMon.println("s"); 297 | 298 | if(bReboot == true) ESP.restart(); 299 | 300 | // Do nothing forevermore 301 | while (true) { delay(1000); } 302 | } 303 | -------------------------------------------------------------------------------- /M5StampC3/NonBlockingBlink/NonBlockingBlink.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // https://github.com/adafruit/Adafruit_NeoPixel 5 | #include 6 | #include 7 | 8 | #define LED_PIN 2 9 | #define NUMPIXELS 1 10 | #define MY_INTERVAL 1000 11 | 12 | Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); 13 | uint32_t nextTime = 0; 14 | bool myFlag = false; 15 | 16 | void setup() 17 | { 18 | Serial.begin(115200); 19 | pixels.begin(); 20 | pixels.clear(); 21 | } 22 | 23 | void loop() 24 | { 25 | if((millis() - nextTime) > MY_INTERVAL) 26 | { 27 | nextTime = millis(); 28 | myFlag = !myFlag; 29 | if(myFlag == true) 30 | { 31 | pixels.setPixelColor(0, pixels.Color(0, 150, 0)); 32 | } 33 | else 34 | { 35 | pixels.setPixelColor(0, pixels.Color(150, 0, 0)); 36 | } 37 | pixels.show(); 38 | } 39 | 40 | // Do other stuff here 41 | 42 | } 43 | -------------------------------------------------------------------------------- /M5StampC3/Unit4RelayDemo/Unit4RelayDemo.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | #include "Unit_4RELAY.h" 6 | 7 | UNIT_4RELAY relay; 8 | 9 | void setup() 10 | { 11 | Serial.begin(115200); 12 | relay.begin(&Wire, 1, 0); // port A 13 | relay.Init(1); // synchronous mode 14 | } 15 | 16 | void loop() 17 | { 18 | relay.relayWrite(0, 1); // turn on relay 1 19 | delay(1000); 20 | relay.relayWrite(0, 0); // turn off relay 1 21 | delay(1000); 22 | relay.relayAll(1); // turn on all relay 23 | delay(1000); 24 | relay.relayAll(0); // turn off all relay 25 | delay(1000); 26 | } 27 | -------------------------------------------------------------------------------- /M5Station/LEDTest/LEDTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // https://github.com/FastLED/FastLED 5 | 6 | #include 7 | #include 8 | 9 | #define NEO_PIXEL_NUM 7 10 | #define NEO_PIXEL_PIN 4 11 | 12 | #define NEO_PIXEL_BUTTON 3 13 | 14 | CRGB NeoPixel[NEO_PIXEL_NUM]; 15 | 16 | void setup() 17 | { 18 | M5.begin(); 19 | 20 | FastLED.addLeds(NeoPixel, NEO_PIXEL_NUM); 21 | } 22 | 23 | void loop() 24 | { 25 | NeoPixel[NEO_PIXEL_BUTTON] = CRGB::Red; 26 | FastLED.show(); 27 | delay(500); 28 | 29 | NeoPixel[NEO_PIXEL_BUTTON] = CRGB::Green; 30 | FastLED.show(); 31 | delay(500); 32 | 33 | NeoPixel[NEO_PIXEL_BUTTON] = CRGB::Blue; 34 | FastLED.show(); 35 | delay(500); 36 | 37 | NeoPixel[NEO_PIXEL_BUTTON] = CRGB::Black; 38 | FastLED.show(); 39 | delay(500); 40 | } 41 | -------------------------------------------------------------------------------- /M5Station/RS485ModbusACSSR/RS485ModbusACSSR.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // https://docs.m5stack.com/en/unit/acssr 5 | // https://github.com/m5stack/ArduinoModbus 6 | // https://github.com/m5stack/ArduinoRS485 7 | 8 | #include "M5Station.h" 9 | #include "M5_ACSSR.h" 10 | #include 11 | #include 12 | 13 | RS485Class RS485(Serial2, GPIO_NUM_3, GPIO_NUM_1, -1, GPIO_NUM_2); // RX, TX, ?, ReadEnable 14 | 15 | void setup() 16 | { 17 | M5.begin(true, false, false); 18 | 19 | M5.Lcd.setTextSize(2); 20 | M5.Lcd.setCursor(0, 100); 21 | M5.Lcd.println(" ON - FW ver - OFF"); 22 | 23 | ModbusRTUClient.begin(115200, SERIAL_8N1); 24 | } 25 | 26 | void loop() 27 | { 28 | M5.update(); 29 | 30 | if(M5.BtnA.wasPressed() == true) 31 | { 32 | // ACSSR CTL ON 33 | ModbusRTUClient.coilWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_RELAY_COIL_ADDR, 0x01); 34 | // ACSSR LED CTL RED COLOR 35 | ModbusRTUClient.holdingRegisterWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_LED_HOLDING_ADDR, 0xF800); 36 | } 37 | else if(M5.BtnB.wasPressed() == true) 38 | { 39 | M5.Lcd.setCursor(0, 80); 40 | M5.Lcd.println(" "); 41 | delay(500); 42 | // GET ACSSR FW VERSION 43 | if(ModbusRTUClient.requestFrom(ACSSR_DEFAULT_SLAVE_ID, HOLDING_REGISTERS, ACSSR_VERSION_HOLDING_ADDR, 1)) 44 | { 45 | while(ModbusRTUClient.available()) 46 | { 47 | M5.Lcd.setCursor(0, 80); 48 | M5.Lcd.printf(" 0x%02x", ModbusRTUClient.read()); 49 | } 50 | } 51 | } 52 | else if(M5.BtnC.wasPressed() == true) 53 | { 54 | // ACSSR CTL OFF 55 | ModbusRTUClient.coilWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_RELAY_COIL_ADDR, 0x00); 56 | // ACSSR LED CTL GREEN COLOR 57 | ModbusRTUClient.holdingRegisterWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_LED_HOLDING_ADDR, 0x07E0); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /M5StickCPlus2/I2CScanInternalGrooveHat/I2CScanInternalGrooveHat.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | TwoWire *wire; 9 | int sda; 10 | int scl; 11 | } i2cBus_t; 12 | 13 | i2cBus_t i2cBus_Internal = {&Wire, GPIO_NUM_21, GPIO_NUM_22}; 14 | i2cBus_t i2cBus_Groove_Port = {&Wire, GPIO_NUM_32, GPIO_NUM_33}; 15 | i2cBus_t i2cBus_Hat_Port = {&Wire, GPIO_NUM_0, GPIO_NUM_26}; 16 | 17 | void setup() 18 | { 19 | Serial.begin(115200); 20 | delay(3000); 21 | Serial.println("Start I2C scan"); 22 | } 23 | 24 | void scanI2C(TwoWire *wire, int sda, int scl) 25 | { 26 | int address; 27 | int error; 28 | 29 | wire->begin(sda, scl); 30 | for(address = 1; address < 127; address++) 31 | { 32 | wire->beginTransmission(address); 33 | error = wire->endTransmission(); 34 | if(error == 0) Serial.printf("0x%02x ", address); 35 | else Serial.print("."); 36 | delay(10); 37 | } 38 | Serial.println(); 39 | wire->end(); 40 | } 41 | 42 | void loop() 43 | { 44 | Serial.println("I2C Scan - internal"); 45 | scanI2C(i2cBus_Internal.wire, i2cBus_Internal.sda, i2cBus_Internal.scl); 46 | Serial.println("I2C Scan - Groove Port"); 47 | scanI2C(i2cBus_Groove_Port.wire, i2cBus_Groove_Port.sda, i2cBus_Groove_Port.scl); 48 | Serial.println("I2C Scan - Hat Port"); 49 | scanI2C(i2cBus_Hat_Port.wire, i2cBus_Hat_Port.sda, i2cBus_Hat_Port.scl); 50 | delay(2000); 51 | } 52 | -------------------------------------------------------------------------------- /M5StickCPlus2/SleepAndPowerOffTest/SleepAndPowerOffTest.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | // 4 | // https://github.com/m5stack/M5StickCPlus2 5 | // https://github.com/m5stack/M5GFX 6 | // https://github.com/m5stack/M5Unified 7 | 8 | #include 9 | 10 | #define TEST_DEEP_SLEEP 11 | #define TEST_POWER_OFFx 12 | 13 | void setup() 14 | { 15 | auto cfg = M5.config(); 16 | cfg.serial_baudrate = 115200; 17 | M5.begin(cfg); 18 | 19 | M5.Display.setRotation(1); 20 | M5.Display.setTextSize(2); 21 | 22 | #ifdef TEST_DEEP_SLEEP 23 | // Note: works when powered from battery or USB 24 | M5.Display.println("Deep sleep in 3 s\n\nPress button A\nto wake up."); 25 | delay(3000); 26 | // Set button A to wakeup ESP32 from deep sleep 27 | esp_sleep_enable_ext0_wakeup((gpio_num_t) GPIO_NUM_37, LOW); 28 | // Keep power mosfet on while in deep sleep 29 | gpio_hold_en((gpio_num_t) GPIO_NUM_4); 30 | gpio_deep_sleep_hold_en(); 31 | esp_deep_sleep_start(); 32 | #endif // TEST_DEEP_SLEEP 33 | 34 | #ifdef TEST_POWER_OFF 35 | // Note: only works when powered from battery 36 | M5.Display.println("Powering off in 3 s\n\nPress power button\nto (re-)start."); 37 | delay(3000); 38 | M5.Power.powerOff(); 39 | #endif // TEST_POWER_OFF 40 | } 41 | 42 | void loop() 43 | { 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /M5Tough/LightSleepWakeFromTouch/LightSleepWakeFromTouch.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | void setup() 7 | { 8 | M5.begin(true, false, true, false); 9 | M5.Lcd.setTextSize(3); 10 | M5.Lcd.print("Light Sleep Test"); 11 | delay(3000); 12 | } 13 | 14 | void loop() 15 | { 16 | gpio_wakeup_enable((gpio_num_t) CST_INT, GPIO_INTR_LOW_LEVEL); 17 | esp_sleep_enable_gpio_wakeup(); 18 | M5.Axp.SetLDOEnable(3, false); 19 | Serial.println("before light sleep"); 20 | Serial.flush(); 21 | esp_light_sleep_start(); 22 | Serial.println("after light sleep"); 23 | M5.Axp.SetLDOEnable(3, true); 24 | delay(3000); 25 | } 26 | -------------------------------------------------------------------------------- /M5Tough/RS485ModbusACSSR/RS485ModbusACSSR.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 by GWENDESIGN. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // https://docs.m5stack.com/en/unit/acssr 5 | // https://github.com/m5stack/ArduinoModbus 6 | // https://github.com/m5stack/ArduinoRS485 7 | 8 | #include "M5Tough.h" 9 | #include "M5_ACSSR.h" 10 | #include 11 | #include 12 | 13 | RS485Class RS485(Serial2, GPIO_NUM_27, GPIO_NUM_19, -1, -1); // RX, TX, ?, ReadEnable 14 | 15 | bool gbToggle = false; 16 | 17 | void setup() 18 | { 19 | M5.begin(true, false, true, false); 20 | 21 | M5.Lcd.setTextSize(3); 22 | M5.Lcd.setCursor(0, 5); 23 | M5.Lcd.println("M5Tough"); 24 | M5.Lcd.setTextSize(2); 25 | M5.Lcd.setCursor(0, 40); 26 | M5.Lcd.println("Modbus Test v1.0.0"); 27 | M5.Lcd.setTextSize(2); 28 | M5.Lcd.setCursor(0, 75); 29 | M5.Lcd.println("Touch to toggle\nrelay state."); 30 | 31 | ModbusRTUClient.begin(RS485, 115200, SERIAL_8N1); 32 | } 33 | 34 | void loop() 35 | { 36 | M5.update(); 37 | 38 | if(M5.Touch.ispressed() == true) 39 | { 40 | if(gbToggle == false) 41 | { 42 | gbToggle = true; 43 | // ACSSR CTL ON 44 | ModbusRTUClient.coilWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_RELAY_COIL_ADDR, 0x01); 45 | // ACSSR LED CTL RED COLOR 46 | ModbusRTUClient.holdingRegisterWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_LED_HOLDING_ADDR, 0xF800); 47 | } 48 | else 49 | { 50 | gbToggle = false; 51 | // ACSSR CTL OFF 52 | ModbusRTUClient.coilWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_RELAY_COIL_ADDR, 0x00); 53 | // ACSSR LED CTL GREEN COLOR 54 | ModbusRTUClient.holdingRegisterWrite(ACSSR_DEFAULT_SLAVE_ID, ACSSR_LED_HOLDING_ADDR, 0x07E0); 55 | } 56 | delay(800); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /M5Tough/ThreeBottomButtons.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 by GWENDESIGN. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | # Note: LVGL Micropython firmware (not UIFlow) is required for this example to run. 5 | # https://github.com/lvgl/lv_micropython/tree/master/ports/esp32/boards/M5CORE2 6 | 7 | import lvgl as lv 8 | 9 | def btnl_event_cb(ev): 10 | print("Btn Left clicked") 11 | 12 | def btnm_event_cb(ev): 13 | print("Btn Middle clicked") 14 | 15 | def btnr_event_cb(ev): 16 | print("Btn Right clicked") 17 | 18 | scr = lv.obj() 19 | scr.clear_flag(lv.obj.FLAG.SCROLLABLE) 20 | 21 | style = lv.style_t() 22 | style.init() 23 | style.set_width(106) 24 | style.set_height(50) 25 | 26 | btnl = lv.btn(scr) 27 | btnl.add_event_cb(btnl_event_cb,lv.EVENT.CLICKED, None) 28 | btnl.add_style(style, 0) 29 | btnl.align(lv.ALIGN.BOTTOM_LEFT, 0, 34) 30 | label = lv.label(btnl) 31 | label.align(lv.ALIGN.TOP_MID, 0, -8) 32 | label.set_text("Left") 33 | 34 | btnm = lv.btn(scr) 35 | btnm.add_event_cb(btnm_event_cb,lv.EVENT.CLICKED, None) 36 | btnm.add_style(style, 0) 37 | btnm.align(lv.ALIGN.BOTTOM_MID, 0, 34) 38 | label = lv.label(btnm) 39 | label.align(lv.ALIGN.TOP_MID, 0, -8) 40 | label.set_text("Middle") 41 | 42 | btnr = lv.btn(scr) 43 | btnr.add_event_cb(btnr_event_cb,lv.EVENT.CLICKED, None) 44 | btnr.add_style(style, 0) 45 | btnr.align(lv.ALIGN.BOTTOM_RIGHT, 0, 34) 46 | label = lv.label(btnr) 47 | label.align(lv.ALIGN.TOP_MID, 0, -8) 48 | label.set_text("Right") 49 | 50 | lv.scr_load(scr) 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyM5StackExamples 2 | Some examples for M5Stack devices. 3 | 4 | ### M5Atom 5 | - **[AtomicGPSTest.ino](./M5Atom/AtomicGPSTest/AtomicGPSTest.ino)** : get GPS data from Atomic GPS kit 6 | - **[DTUNBIOTPassthroughTest.ino](./M5Atom/DTUNBIOTPassthroughTest/DTUNBIOTPassthroughTest.ino)** : send and receive AT commands via serial console from ATOM DTU NB IOT 7 | - **[EnvIVTest.ino](./M5Atom/EnvIVTest/EnvIVTest.ino)** : read temperature, humidity and pressure from EnvIV unit 8 | - **[atom_dtu_nb_iot.py](./M5Atom/atom_dtu_nb_iot.py)** : send AT command and wait for response 9 | 10 | ### M5AtomS3 11 | - **[DeepSleepTest.ino](./M5AtomS3/DeepSleepTest/DeepSleepTest.ino)** : Deep sleep test to measure current 12 | - **[ReadDualButtonViaPbHub.ino](./M5AtomS3/ReadDualButtonViaPbHub/ReadDualButtonViaPbHub.ino)** : Read Dual Button states via PbHub unit 13 | 14 | ### M5Cardputer 15 | - **[Unit4RelayDemo.ino](./M5Cardputer/Unit4RelayDemo/Unit4RelayDemo.ino)** : turn relay on / off over i2C every second 16 | 17 | ### M5Core2 18 | - **[BatteryMonitor.ino](./M5Core2/BatteryMonitor/BatteryMonitor.ino)** : show battery charging current, level, voltage and whether the battery is being charged or not 19 | - **[DeepSleepWakeFromTouch.ino](./M5Core2/DeepSleepWakeFromTouch/DeepSleepWakeFromTouch.ino)** : put ESP32 into deep sleep then wake when screen is touched 20 | - **[LightSleepWakeFromTouch.ino](./M5Core2/LightSleepWakeFromTouch/LightSleepWakeFromTouch.ino)** : put ESP32 into light sleep then wake when screen is touched 21 | - **[TouchButtonTest.ino](./M5Core2/TouchButtonTest/TouchButtonTest.ino)** : Touch button using M5Unified and M5GFX 22 | - **[IoTBasePSM.py](./M5Core2/IoTBasePSM.py)** : button A powers modem on; button B sends AT command; button C puts modem into power save mode 23 | - **[SimpleTone.py](./M5Core2/SimpleTone.py)** : play a tone 24 | 25 | ### M5Core2v1.1 26 | - **[BlueLEDTest.ino](./M5Core2v1.1/BlueLEDTest/BlueLEDTest.ino)** : blink blue LED 27 | 28 | ### M5CoreInk 29 | - **[CountDownThenPowerOff.py](./M5CoreInk/CountDownThenPowerOff.py)** : count down from 10 then power off 30 | - **[RTC_Clock.ino](./M5CoreInk/RTC_Clock/RTC_Clock.ino)** : low power clock with non-flickering ink display update 31 | 32 | ### M5CoreS3 33 | - **[COMLTETest.ino](./M5CoreS3/COMLTETest/COMLTETest.ino)** : COM.LTE module - send AT command and read response 34 | - **[CamWebServer](./M5CoreS3/CamWebServer)** : adapted CameraWebServer version for M5CoreS3 35 | - **[I2CScanInternalGroove.ino](./M5CoreS3/I2CScanInternalGroove/I2CScanInternalGroove.ino)** : I2C scan internal and Port A (red) 36 | - **[SDCard.ino](./M5CoreS3/SDCard/SDCard.ino)** : init SD card (Note: breaks LCD) 37 | - **[SDCardAndLCD.ino](./M5CoreS3/SDCardAndLCD/SDCardAndLCD.ino)** : SD card and LCD both work 38 | - **[TouchRandomCircle.ino](./M5CoreS3/TouchRandomCircle/TouchRandomCircle.ino)** : Touch to draw random colored circle 39 | - **[TouchButtonABC.py](./M5CoreS3/TouchButtonABC.py)** : Button A, B and C at the bottom 40 | 41 | ### M5Dial 42 | - **[GC9A01_demo.ino](./M5Dial/GC9A01_demo/GC9A01_demo.ino)** : TFT demo 43 | - **[I2CScanIntExt.ino](./M5Dial/I2CScanIntExt/I2CScanIntExt.ino)** : Scan I2C - internal and external (red port A) 44 | - **[RS485ModbusACSSR.ino](./M5Dial/RS485ModbusACSSR/RS485ModbusACSSR.ino)** : use RS485 unit on port B with Modbus protocol. Click button to toggle ACSSR relay on / off 45 | - **[ShutdownTest.ino](./M5Dial/ShutdownTest/ShutdownTest.ino)** : Shutdown test - power on via knob button or RTC timer 46 | - **[SleepTest.ino](./M5Dial/SleepTest/SleepTest.ino)** : Light/Deep sleep test - wakeup via touch screen or knob button 47 | 48 | ### M5LANBase 49 | - **[LinkTest.ino](./M5LANBase/LinkTest/LinkTest.ino)** : LAN Base link test for M5Core, M5Core2, M5Tough and M5CoreS3 50 | 51 | ### M5Paper 52 | - **[GestureSensor.ino](./M5Paper/GestureSensor/GestureSensor.ino)** : read gesture sensor and display result 53 | - **[I2CScanInternalAndAllPorts.ino](./M5Paper/I2CScanInternalAndAllPorts/I2CScanInternalAndAllPorts.ino)** : i2c scan (internal and port A, B und C). 54 | - **[NonFlickeringUpdateAfterShutdown.ino](./M5Paper/NonFlickeringUpdateAfterShutdown/NonFlickeringUpdateAfterShutdown.ino)** : display non flickering count after waking up from shutdown 55 | - **[ShutdownWakeAfterHours.ino](./M5Paper/ShutdownWakeAfterHours/ShutdownWakeAfterHours.ino)** : Shutdown (after being turned on by power button) and wake up after 7 hours by RTC 56 | - **[SleepTest.ino](./M5Paper/SleepTest/SleepTest.ino)** : ESP32 light / deep sleep test with timer and touch screen wakeup 57 | - **[Lightsleep.py](./M5Paper/Lightsleep.py)** : put ESP32 into light sleep for 60 seconds 58 | 59 | ### M5PaperS3 60 | - **[RTCClockNonFlickering.ino](./M5PaperS3/RTCClockNonFlickering/RTCClockNonFlickering.ino)** : low power clock with non flickering eInk display 61 | - **[LightSleepTouchWakeup.ino](./M5PaperS3/LightSleepTouchWakeup/LightSleepTouchWakeup.ino)** : light sleep with touch wakeup example 62 | 63 | ### M5Stack 64 | - **[Cam2CoreExtended.ino](./M5Stack/Cam2CoreExtended/Cam2CoreExtended.ino)** : receive data from TimerCam and display on LCD; use button A, B and C to change frame size 65 | - **[OTAoverLTE.ino](./M5Stack/OTAoverLTE/OTAoverLTE.ino)** : firmware update over the air using an LTE modem 66 | 67 | ### M5StampC3 68 | - **[NonBlockingBlink.ino](./M5StampC3/NonBlockingBlink/NonBlockingBlink.ino)** : blink LED w/o using delays 69 | - **[Unit4RelayDemo.ino](./M5StampC3/Unit4RelayDemo/Unit4RelayDemo.ino)** : turn relay on / off over i2C every second 70 | 71 | ### M5Station 72 | - **[LEDTest.ino](./M5Station/LEDTest/LEDTest.ino)** : change LED color every half second 73 | - **[RS485ModbusACSSR.ino](./M5Station/RS485ModbusACSSR/RS485ModbusACSSR.ino)** : use RS485 port with Modbus protocol; button A turns relay on; button B reads the firmware of the relay; button C turns the relay off 74 | 75 | ### M5StickCPlus2 76 | - **[I2CScanInternalGrooveHat.ino](./M5StickCPlus2/I2CScanInternalGrooveHat/I2CScanInternalGrooveHat.ino)** : I2C scan (internal, Groove, Hat) 77 | - **[SleepAndPowerOffTest.ino](./M5StickCPlus2/SleepAndPowerOffTest/SleepAndPowerOffTest.ino)** : Shows how to put ESP32 into deep sleep and wakeup from button A. It also shows how to power off device and restart from power button. 78 | 79 | ### M5Tough 80 | - **[LightSleepWakeFromTouch.ino](./M5Tough/LightSleepWakeFromTouch/LightSleepWakeFromTouch.ino)** : put ESP32 into light sleep then wake when screen is touched 81 | - **[RS485ModbusACSSR.ino](./M5Tough/RS485ModbusACSSR/RS485ModbusACSSR.ino)** : use RS485 port with Modbus protocol and toggle ACSSR relay on / off every 800 ms 82 | - **[ThreeBottomButtons.py](./M5Tough/ThreeBottomButtons.py)** : three bottom button for M5Tough 83 | --------------------------------------------------------------------------------