├── .gitattributes ├── .gitignore ├── LICENSE ├── ReadMe.md ├── include ├── AP33772_PocketPD.h ├── Button.h ├── EEPROMHandler.hpp ├── INA226.h ├── Image.h ├── Menu.h ├── PocketPDPinOut.h └── StateMachine.h ├── media ├── VoltageCurrent.jpg ├── bootscreen.jpg ├── devicemanager.png ├── normalpdo.jpg ├── normalpdo5V.jpg ├── normalpps.jpg ├── normalpps2.jpg ├── pdoprofile.jpg ├── ppsmenu.jpg ├── ppsprofile.jpg ├── putty.png └── removabledrive.png ├── platformio.ini ├── src ├── AP33772_PocketPD.cpp ├── Button.cpp ├── INA226.cpp ├── Menu.cpp ├── StateMachine.cpp └── main.cpp ├── test └── calibration.cpp └── to_do_list.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | .history -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 CentyLab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | PocketPD is a portable USB-C bench power supply that can fit in your pocket. Combine with a USB-C PD 3.0/3.1 power source and you can ultilize the PPS profile to create a portable power supply with voltage and current adjustment. 3 | 4 | As the DIY community has grown, there are multiple ways to implement control features like adjusting parameters via Wifi, Bluetooth, or touch screen. We want to keep the design language simple, just physical knobs and buttons to control. This will give the system higher reliability when you need it to work. 5 | 6 | ## Links 7 | * [PocketPD Project - Hackaday](https://hackaday.io/project/194295-pocketpd-usb-c-portable-bench-power-supply) 8 | * [PocketPD Hardware - GitHub](https://github.com/CentyLab/PocketPD_HW) 9 | 10 | ## System flow chart 11 | 12 | ```mermaid 13 | flowchart LR 14 | A[Boot up] --> |0.5s|B[Obtain charger capability] 15 | B --> |1.5s|C[Display capabilities] 16 | C --> |3s|D[Normal operation] 17 | D --> |Long Press V/I|E[Menu] 18 | B --> |Short Press any button|D 19 | C --> |Short Press any button|D 20 | E --> |Long Press V/I|D 21 | E --> |Long Press Encoder|D 22 | ``` 23 | 24 | ## Operational manual 25 | ### 1. Boot up sequence 26 | If your charger support PPS (Programable Power Supply) mode, the charger will first enter BOOT screen. 27 | 28 |

29 | 30 |

31 | 32 | The system will then display the available profile from the charger. 33 | 34 |

35 | 36 |

37 | After 3 seconds, the system will enter operating mode. If PPS mode exist, the system will request 5V @ 1A 38 | 39 |

40 | 41 |

42 | 43 | ### 2. Skip boot screen 44 | When the device is at boot screen: 45 | + Press any BUTTON, to skip to NORMAL state (operational screen) 46 | + Rotate ENCODER, to skip to MENU state (select power profile) 47 | 48 | ### 3. In NORMAL state: 49 | + Turning the encoder to increase/decrease voltage/current 50 | + Short press encoder to change increment from fine to coarse 51 | + Short press Volt/Amp button to switch between adjusting Voltage or Current 52 | + Short press On/Off button to enable output 53 | + Long press Volt/Amp button to enter MENU 54 | 55 | ### 4. In MENU state: 56 | + Turning the encoder to select profile 57 | + Long press encoder to activate profile 58 | + Long press Volt/Amp button to return to normal operation and cancel profile change 59 | 60 |

61 | 62 |

63 | 64 | Example when select 5V @ 3A profile 65 | 66 |

67 | 68 |

69 |
70 | 71 | ### 5. Profile example for non PPS charger 72 | If your charger doesn't support PPS profile, PocketPD will directly boot into the first 5V PDO profile. Your menu will looks like this: 73 | 74 |

75 | 76 |

77 | 78 | 79 | ## Compile the code 80 | + You will need [VSCode](https://code.visualstudio.com/download) with [Platform IO extension](https://docs.platformio.org/en/latest/integration/ide/vscode.html#installation). 81 | 82 | + Before letting Platform IO pulling the pico-sdk files. Follow [Important steps for Windows users, before installing](https://arduino-pico.readthedocs.io/en/latest/platformio.html#important-steps-for-windows-users-before-installing) 83 | Else you will encounter: 84 | 85 | ``` 86 | VCSBaseException: VCS: Could not process command ['git', 'clone', '--recursive', 'https://github.com/earlephilhower/arduino-pico.git', 'C:\\Users\\keylo\\.platformio\\.cache\\tmp\\pkg-installing-iypaogfn'] 87 | ``` 88 | 89 | + Go to PlatformIO extension -> Pico -> General -> Build 90 | 91 | + Output of the build process will be in .pio/build/pico/ 92 | 93 | 94 | ## How to flash new firmware 95 | Note: Firmware at and before `0.9.5` is only for `PocketPD HW1.0` 96 | 97 | Step 1: Select the correct hardware version from [PocketPD's Firmware Releases](https://github.com/CentyLab/PocketPD/releases) 98 | 99 | + HW1.0: Also known as "Limited edition". Download `firmware_xx_HW1.0.uf2` 100 | + HW1.1: Our standard production version. Download `firmware_xx_HW1.1.uf2` 101 | 102 | Step 2: Mount PocketPD as a drive in your computer 103 | 104 | For MacBook user: 105 | + Method 1: (Easy) 106 | + Short the BOOT pads at the back of the device with a tweezer in `HW1.0` or hold the BOOT button in `HW1.1`. 107 | + Use a USB-A -> USB-C adapter, then use a USB-A -> USB-C cable to connect PocketPD to computer. PocketPD should pop up as `RPI_RP2` drive. 108 | + Method 2: (Intermediate) 109 | + Use a USB-A -> USB-C adapter, then use a USB-A -> USB-C cable to connect PocketPD to computer. No drive will popup. 110 | + Use any serial monitor, and start a Serial port with 1200 Baudrate. PocketPD should pop up as `RPI_RP2` drive. 111 | 112 | For Windows user: 113 | + Method 1: (Easy) 114 | + Short the BOOT pads at the back of the device with a tweezer in `HW1.0` or hold the BOOT button in `HW1.1`. 115 | + Use any USB cable to connect PocketPD to computer. PocketPD should pop up as `RPI_RP2` drive. 116 | + Method 2: (Intermediate) 117 | + Use any USB cable to connect PocketPD to computer. No drive will pop-up. 118 | + Open [Putty](https://www.putty.org/) and open a Serial port with 1200 Baudrate. PocketPD should pop up as `RPI_RP2` drive. 119 | 120 | Step 3: Drag and drop the `.uf2` file into the drive 121 | 122 | 123 | If you build the firmware directly from VSCode, the `.uf2` file will be in `.pio/build/pico/` 124 | 125 | Detail guide [How to upload new firmware to PocketPD](https://github.com/CentyLab/PocketPD/wiki/How-to-upload-new-firmware-to-PocketPD) 126 | -------------------------------------------------------------------------------- /include/AP33772_PocketPD.h: -------------------------------------------------------------------------------- 1 | /* Structs and Resgister list ported from "AP33772 I2C Command Tester" by Joseph Liang 2 | * Created 11 April 2022 3 | * Added class and class functions by VicentN for PicoPD evaluation board 4 | */ 5 | #ifndef __AP33772_POCKETPD__ 6 | #define __AP33772_POCKETPD__ 7 | 8 | #include 9 | #include 10 | 11 | #define CMD_SRCPDO 0x00 12 | #define CMD_PDONUM 0x1C 13 | #define CMD_STATUS 0x1D 14 | #define CMD_MASK 0x1E 15 | #define CMD_VOLTAGE 0x20 16 | #define CMD_CURRENT 0x21 17 | #define CMD_TEMP 0x22 18 | #define CMD_OCPTHR 0x23 19 | #define CMD_OTPTHR 0x24 20 | #define CMD_DRTHR 0x25 21 | #define CMD_RDO 0x30 22 | 23 | #define AP33772_ADDRESS 0x51 24 | #define READ_BUFF_LENGTH 30 25 | #define WRITE_BUFF_LENGTH 6 26 | #define SRCPDO_LENGTH 28 27 | 28 | typedef enum 29 | { 30 | READY_EN = 1 << 0, // 0000 0001 31 | SUCCESS_EN = 1 << 1, // 0000 0010 32 | NEWPDO_EN = 1 << 2, // 0000 0100 33 | OVP_EN = 1 << 4, // 0001 0000 34 | OCP_EN = 1 << 5, // 0010 0000 35 | OTP_EN = 1 << 6, // 0100 0000 36 | DR_EN = 1 << 7 // 1000 0000 37 | } AP33772_MASK; 38 | 39 | typedef struct 40 | { 41 | union 42 | { 43 | struct 44 | { 45 | byte isReady : 1; 46 | byte isSuccess : 1; 47 | byte isNewpdo : 1; 48 | byte reserved : 1; 49 | byte isOvp : 1; 50 | byte isOcp : 1; 51 | byte isOtp : 1; 52 | byte isDr : 1; 53 | }; 54 | byte readStatus; 55 | }; 56 | byte readVolt; // LSB: 80mV 57 | byte readCurr; // LSB: 24mA 58 | byte readTemp; // unit: 1C 59 | } AP33772_STATUS_T; 60 | 61 | typedef struct 62 | { 63 | union 64 | { 65 | struct 66 | { 67 | byte newNegoSuccess : 1; 68 | byte newNegoFail : 1; 69 | byte negoSuccess : 1; 70 | byte negoFail : 1; 71 | byte reserved_1 : 4; 72 | }; 73 | byte negoEvent; 74 | }; 75 | union 76 | { 77 | struct 78 | { 79 | byte ovp : 1; 80 | byte ocp : 1; 81 | byte otp : 1; 82 | byte dr : 1; 83 | byte reserved_2 : 4; 84 | }; 85 | byte protectEvent; 86 | }; 87 | } EVENT_FLAG_T; 88 | 89 | typedef struct 90 | { 91 | union 92 | { 93 | struct 94 | { 95 | unsigned int maxCurrent : 10; // unit: 10mA 96 | unsigned int voltage : 10; // unit: 50mV 97 | unsigned int reserved_1 : 10; 98 | unsigned int type : 2; 99 | } fixed; 100 | struct 101 | { 102 | unsigned int maxCurrent : 7; // unit: 50mA 103 | unsigned int reserved_1 : 1; 104 | unsigned int minVoltage : 8; // unit: 100mV 105 | unsigned int reserved_2 : 1; 106 | unsigned int maxVoltage : 8; // unit: 100mV 107 | unsigned int reserved_3 : 3; 108 | unsigned int apdo : 2; 109 | unsigned int type : 2; 110 | } pps; 111 | struct 112 | { 113 | byte byte0; 114 | byte byte1; 115 | byte byte2; 116 | byte byte3; 117 | }; 118 | unsigned long data; 119 | }; 120 | } PDO_DATA_T; 121 | 122 | typedef struct 123 | { 124 | union 125 | { 126 | struct 127 | { 128 | unsigned int maxCurrent : 10; // unit: 10mA 129 | unsigned int opCurrent : 10; // unit: 10mA 130 | unsigned int reserved_1 : 8; 131 | unsigned int objPosition : 3; 132 | unsigned int reserved_2 : 1; 133 | } fixed; 134 | struct 135 | { 136 | unsigned int opCurrent : 7; // unit: 50mA 137 | unsigned int reserved_1 : 2; 138 | unsigned int voltage : 11; // unit: 20mV 139 | unsigned int reserved_2 : 8; 140 | unsigned int objPosition : 3; 141 | unsigned int reserved_3 : 1; 142 | } pps; 143 | struct 144 | { 145 | byte byte0; 146 | byte byte1; 147 | byte byte2; 148 | byte byte3; 149 | }; 150 | unsigned long data; 151 | }; 152 | } RDO_DATA_T; 153 | 154 | class AP33772 155 | { 156 | public: 157 | AP33772(TwoWire &wire = Wire); 158 | void begin(); 159 | void setVoltage(int targetVoltage); // Unit in mV 160 | void setPDO(uint8_t PDOindex); 161 | void setMaxCurrent(int targetMaxCurrent); // Unit in mA 162 | void setNTC(int TR25, int TR50, int TR75, int TR100); 163 | void setDeratingTemp(int temperature); 164 | void setMask(AP33772_MASK flag); 165 | void clearMask(AP33772_MASK flag); 166 | void writeRDO(); 167 | int readVoltage(); 168 | int readCurrent(); 169 | int getMaxCurrent() const; 170 | int readTemp(); 171 | void printPDO(); 172 | void reset(); 173 | 174 | int getNumPDO(); 175 | int getPPSIndex(); 176 | int getPDOMaxcurrent(uint8_t PDOindex); 177 | int getPDOVoltage(uint8_t PDOindex); 178 | int getPPSMinVoltage(uint8_t PPSindex); 179 | int getPPSMaxVoltage(uint8_t PPSindex); 180 | int getPPSMaxCurrent(uint8_t PPSindex); 181 | void setSupplyVoltageCurrent(int targetVoltage, int targetCurrent); 182 | byte existPPS = 0; // PPS flag for setVoltage() 183 | 184 | void checkVoltageCurrent(int &targetVoltage, int &targetCurrent); 185 | 186 | private: 187 | void i2c_read(byte slvAddr, byte cmdAddr, byte len); 188 | void i2c_write(byte slvAddr, byte cmdAddr, byte len); 189 | TwoWire *_i2cPort; 190 | byte readBuf[READ_BUFF_LENGTH] = {0}; 191 | byte writeBuf[WRITE_BUFF_LENGTH] = {0}; 192 | byte numPDO = 0; // source PDO number 193 | byte indexPDO = 0; // PDO index, start from index 0 194 | int reqPpsVolt = 0; // requested PPS voltage, unit:20mV 195 | 196 | int8_t PPSindex = 8; 197 | 198 | AP33772_STATUS_T ap33772_status = {0}; 199 | EVENT_FLAG_T event_flag = {0}; 200 | RDO_DATA_T rdoData = {0}; 201 | PDO_DATA_T pdoData[7] = {0}; 202 | }; 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /include/Button.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement long press detection using ezButton library 3 | */ 4 | 5 | #include 6 | 7 | #ifndef BUTTON_H 8 | #define BUTTON_H 9 | 10 | class Button 11 | { 12 | public: 13 | int isButtonPressed(void); 14 | Button(int buttonNum, int mode) : button(buttonNum, mode) { button.setDebounceTime(debounceTime); } 15 | Button(int buttonNum) : button(buttonNum, INPUT_PULLUP) { button.setDebounceTime(debounceTime); } 16 | void clearLongPressedFlag(void) { longPressedFlag = false; } // Call after a succesful long press trigger 17 | void setDebounceTime(unsigned long time); 18 | bool longPressedFlag = false; 19 | void loop(); 20 | 21 | private: 22 | ezButton button; 23 | unsigned long pressedTime = 0; 24 | unsigned long releasedTime = 0; 25 | bool isPressing = false; 26 | bool isLongDetected = false; 27 | const int debounceTime = 50; // Set default debounce time to 50ms 28 | const unsigned long SHORT_PRESS_TIME = 1000; // Short is less than 1s, more than 50ms 29 | const unsigned long LONG_PRESS_TIME = 1500; // Long is longer than 1.5s 30 | }; 31 | 32 | #endif -------------------------------------------------------------------------------- /include/EEPROMHandler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EEPROMHANDLER_HPP 2 | #define EEPROMHANDLER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #define EEPROM_SIZE 4096 // Size of EEPROM in bytes 8 | #define EEPROM_SAVE_INTERVAL 2000 // Time interval to save settings to EEPROM in milliseconds 9 | 10 | 11 | struct Settings { 12 | uint32_t crc; // CRC for settings validation 13 | int32_t targetVoltage; // Target voltage in mV 14 | int32_t targetCurrent; // Target current in mA 15 | int32_t menuPosition; // Current menu position 16 | }; 17 | 18 | class EEPROMHandler { 19 | bool initialized = false; // Flag to check if EEPROM is initialized 20 | bool firstReadOk = false; // Flag to check if the first read from EEPROM was successful 21 | Settings settings; 22 | public: 23 | EEPROMHandler() { } 24 | virtual ~EEPROMHandler() { } 25 | 26 | void begin() { 27 | if(!initialized) { 28 | EEPROM.begin(EEPROM_SIZE); 29 | Serial.printf("EEPROM initialized %4d bytes | Settings size: %4d\n\r", EEPROM_SIZE, sizeof(Settings)); 30 | initialized = true; 31 | } 32 | } 33 | 34 | uint32_t calculateCRC() { 35 | uint32_t crc = 0; 36 | for(size_t i = 1; i < sizeof(Settings)/sizeof(uint32_t); ++i) { 37 | crc ^= ((uint32_t*)&settings)[i]; 38 | } 39 | return crc; 40 | } 41 | 42 | bool loadSettings(Settings &outSettings) { 43 | begin(); 44 | 45 | if(firstReadOk) { 46 | Serial.println("Using cached settings."); 47 | outSettings = settings; 48 | return true; 49 | } 50 | 51 | EEPROM.get(0, settings); 52 | uint32_t crc = calculateCRC(); 53 | 54 | Serial.printf("Crc calculated: %08X, stored crc: %08X\n\r", crc, settings.crc); 55 | Serial.printf("Target Voltage: %d mV, Target Current: %d mA, Menu Position: %d\n\r", 56 | settings.targetVoltage, settings.targetCurrent, settings.menuPosition); 57 | 58 | if(settings.crc != 0 && settings.crc == crc) { 59 | outSettings = settings; 60 | Serial.println("Settings loaded successfully."); 61 | firstReadOk = true; 62 | } else { 63 | Serial.println("Invalid settings in EEPROM."); 64 | firstReadOk = false; 65 | } 66 | return firstReadOk; 67 | } 68 | 69 | bool saveSettings(Settings &newSettings) { 70 | if(!initialized) { 71 | Serial.println("EEPROM not initialized, cannot save settings."); 72 | return false; 73 | } 74 | 75 | newSettings.crc = calculateCRC(); 76 | // Compare new settings with current settings 77 | if(firstReadOk && memcmp(&settings, &newSettings, sizeof(Settings)) == 0) { 78 | Serial.println("No changes in settings, skipping save."); 79 | return true; 80 | } 81 | 82 | settings = newSettings; 83 | 84 | EEPROM.put(0, settings); 85 | if(EEPROM.commit()) { 86 | Serial.println("Settings saved successfully."); 87 | return true; 88 | } else { 89 | Serial.println("Failed to save settings."); 90 | return false; 91 | } 92 | } 93 | }; 94 | #endif // EEPROMHANDLER_HPP -------------------------------------------------------------------------------- /include/INA226.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // FILE: INA226.h 3 | // AUTHOR: Rob Tillaart 4 | // VERSION: 0.6.4 5 | // DATE: 2021-05-18 6 | // PURPOSE: Arduino library for INA226 power sensor 7 | // URL: https://github.com/RobTillaart/INA226 8 | // 9 | // Read the datasheet for the details 10 | 11 | 12 | #include "Arduino.h" 13 | #include "Wire.h" 14 | 15 | 16 | #define INA226_LIB_VERSION (F("0.6.4")) 17 | 18 | 19 | // set by setAlertRegister 20 | #define INA226_SHUNT_OVER_VOLTAGE 0x8000 21 | #define INA226_SHUNT_UNDER_VOLTAGE 0x4000 22 | #define INA226_BUS_OVER_VOLTAGE 0x2000 23 | #define INA226_BUS_UNDER_VOLTAGE 0x1000 24 | #define INA226_POWER_OVER_LIMIT 0x0800 25 | #define INA226_CONVERSION_READY 0x0400 26 | 27 | 28 | // returned by getAlertFlag 29 | #define INA226_ALERT_FUNCTION_FLAG 0x0010 30 | #define INA226_CONVERSION_READY_FLAG 0x0008 31 | #define INA226_MATH_OVERFLOW_FLAG 0x0004 32 | #define INA226_ALERT_POLARITY_FLAG 0x0002 33 | #define INA226_ALERT_LATCH_ENABLE_FLAG 0x0001 34 | 35 | 36 | // returned by setMaxCurrentShunt 37 | #define INA226_ERR_NONE 0x0000 38 | #define INA226_ERR_SHUNTVOLTAGE_HIGH 0x8000 39 | #define INA226_ERR_MAXCURRENT_LOW 0x8001 40 | #define INA226_ERR_SHUNT_LOW 0x8002 41 | #define INA226_ERR_NORMALIZE_FAILED 0x8003 42 | 43 | // See issue #26 44 | #define INA226_MINIMAL_SHUNT_OHM 0.001 45 | 46 | #define INA226_MAX_WAIT_MS 600 // millis 47 | 48 | #define INA226_MAX_SHUNT_VOLTAGE (81.92 / 1000) 49 | 50 | 51 | // for setAverage() and getAverage() 52 | enum ina226_average_enum { 53 | INA226_1_SAMPLE = 0, 54 | INA226_4_SAMPLES = 1, 55 | INA226_16_SAMPLES = 2, 56 | INA226_64_SAMPLES = 3, 57 | INA226_128_SAMPLES = 4, 58 | INA226_256_SAMPLES = 5, 59 | INA226_512_SAMPLES = 6, 60 | INA226_1024_SAMPLES = 7 61 | }; 62 | 63 | 64 | // for BVCT and SVCT conversion timing. 65 | enum ina226_timing_enum { 66 | INA226_140_us = 0, 67 | INA226_204_us = 1, 68 | INA226_332_us = 2, 69 | INA226_588_us = 3, 70 | INA226_1100_us = 4, 71 | INA226_2100_us = 5, 72 | INA226_4200_us = 6, 73 | INA226_8300_us = 7 74 | }; 75 | 76 | 77 | class INA226 78 | { 79 | public: 80 | // address between 0x40 and 0x4F 81 | explicit INA226(const uint8_t address, TwoWire *wire = &Wire); 82 | 83 | bool begin(); 84 | bool isConnected(); 85 | uint8_t getAddress(); 86 | 87 | 88 | // Core functions 89 | float getBusVoltage(); // Volt 90 | float getShuntVoltage(); // Volt 91 | float getCurrent(); // Ampere 92 | float getPower(); // Watt 93 | // See #35 94 | bool isConversionReady(); // conversion ready flag is set. 95 | bool waitConversionReady(uint32_t timeout = INA226_MAX_WAIT_MS); 96 | 97 | 98 | // Scale helpers milli range 99 | float getBusVoltage_mV() { return getBusVoltage() * 1e3; }; 100 | float getShuntVoltage_mV() { return getShuntVoltage() * 1e3; }; 101 | float getCurrent_mA() { return getCurrent() * 1e3; }; 102 | float getPower_mW() { return getPower() * 1e3; }; 103 | // Scale helpers micro range 104 | float getBusVoltage_uV() { return getBusVoltage() * 1e6; }; 105 | float getShuntVoltage_uV() { return getShuntVoltage() * 1e6; }; 106 | float getCurrent_uA() { return getCurrent() * 1e6; }; 107 | float getPower_uW() { return getPower() * 1e6; }; 108 | 109 | 110 | // Configuration 111 | bool reset(); 112 | bool setAverage(uint8_t avg = INA226_1_SAMPLE); 113 | uint8_t getAverage(); 114 | bool setBusVoltageConversionTime(uint8_t bvct = INA226_1100_us); 115 | uint8_t getBusVoltageConversionTime(); 116 | bool setShuntVoltageConversionTime(uint8_t svct = INA226_1100_us); 117 | uint8_t getShuntVoltageConversionTime(); 118 | 119 | 120 | // Calibration 121 | // mandatory to set these values! 122 | // datasheet limit == 81.92 mV; 123 | // to prevent math overflow 0.02 mV is subtracted. 124 | // shunt * maxCurrent <= 81.9 mV otherwise returns INA226_ERR_SHUNTVOLTAGE_HIGH 125 | // maxCurrent >= 0.001 otherwise returns INA226_ERR_MAXCURRENT_LOW 126 | // shunt >= 0.001 otherwise returns INA226_ERR_SHUNT_LOW 127 | int setMaxCurrentShunt(float maxCurrent = 20.0, float shunt = 0.002, bool normalize = true); 128 | // configure provides full user control, not requiring call to setMaxCurrentShunt(args) function 129 | int configure(float shunt = 0.1, float current_LSB_mA = 0.1, float current_zero_offset_mA = 0, uint16_t bus_V_scaling_e4 = 10000); 130 | bool isCalibrated() { return _current_LSB != 0.0; }; 131 | 132 | // These functions return zero if not calibrated! 133 | float getCurrentLSB() { return _current_LSB; }; 134 | float getCurrentLSB_mA() { return _current_LSB * 1e3; }; 135 | float getCurrentLSB_uA() { return _current_LSB * 1e6; }; 136 | float getShunt() { return _shunt; }; 137 | float getMaxCurrent() { return _maxCurrent; }; 138 | 139 | 140 | // Operating mode 141 | bool setMode(uint8_t mode = 7); // default ModeShuntBusContinuous 142 | uint8_t getMode(); 143 | bool shutDown() { return setMode(0); }; 144 | bool setModeShuntTrigger() { return setMode(1); }; 145 | bool setModeBusTrigger() { return setMode(2); }; 146 | bool setModeShuntBusTrigger() { return setMode(3); }; 147 | bool setModeShuntContinuous() { return setMode(5); }; 148 | bool setModeBusContinuous() { return setMode(6); }; 149 | bool setModeShuntBusContinuous() { return setMode(7); }; // default. 150 | 151 | 152 | // Alert 153 | // - separate functions per flag? 154 | // - what is a reasonable limit? 155 | // - which units to define a limit per mask ? 156 | // same as voltage registers ? 157 | // - how to test 158 | bool setAlertRegister(uint16_t mask); 159 | uint16_t getAlertFlag(); 160 | bool setAlertLimit(uint16_t limit); 161 | uint16_t getAlertLimit(); 162 | 163 | 164 | // Meta information 165 | // 166 | // typical value 167 | uint16_t getManufacturerID(); // 0x5449 168 | uint16_t getDieID(); // 0x2260 169 | 170 | 171 | // DEBUG 172 | uint16_t getRegister(uint8_t reg) { return _readRegister(reg); }; 173 | 174 | // 175 | // ERROR HANDLING 176 | // 177 | int getLastError(); 178 | 179 | private: 180 | 181 | uint16_t _readRegister(uint8_t reg); 182 | uint16_t _writeRegister(uint8_t reg, uint16_t value); 183 | 184 | float _current_LSB; 185 | float _shunt; 186 | float _maxCurrent; 187 | float _current_zero_offset = 0; 188 | uint16_t _bus_V_scaling_e4 = 10000; 189 | 190 | uint8_t _address; 191 | TwoWire * _wire; 192 | 193 | int _error; 194 | }; 195 | 196 | 197 | // -- END OF FILE -- -------------------------------------------------------------------------------- /include/Image.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef IMAGE_H 4 | #define IMAGE_H 5 | 6 | static const unsigned char image_check_contour_bits[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x0a,0x04,0x11,0x8a,0x08,0x51,0x04,0x22,0x02,0x04,0x01,0x88,0x00,0x50,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; 7 | static const unsigned char image_cross_contour_bits[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x01,0x8a,0x02,0x51,0x04,0x22,0x02,0x04,0x01,0x88,0x00,0x04,0x01,0x22,0x02,0x51,0x04,0x8a,0x02,0x04,0x01,0x00,0x00,0x00,0x00}; 8 | // 'PocketPD_Logo', 128x64px 9 | const unsigned char image_PocketPD_Logo [] PROGMEM = { 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x01, 0xff, 0x80, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x01, 0x9f, 0xf0, 0x3f, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x01, 0x83, 0xff, 0xfe, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x01, 0x80, 0x3f, 0xf0, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0xfe, 0x01, 0xff, 0xe0, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0xff, 0xc1, 0xff, 0xf8, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x0c, 0x00, 0x07, 0xff, 0xc1, 0xff, 0xfe, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x01, 0x80, 0x07, 0x80, 0x0c, 0x00, 0x07, 0x83, 0xe1, 0xe0, 0x3f, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x01, 0x80, 0x07, 0x80, 0x0c, 0x00, 0x07, 0x81, 0xe1, 0xe0, 0x0f, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x0c, 0x00, 0x07, 0x80, 0xf1, 0xe0, 0x07, 0x80, 0x00, 0x00, 36 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0xf1, 0xe0, 0x07, 0x80, 0x00, 0x00, 37 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0xf1, 0xe0, 0x03, 0xc0, 0x00, 0x00, 38 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x81, 0xe1, 0xe0, 0x03, 0xc0, 0x00, 0x00, 39 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x81, 0xe1, 0xe0, 0x03, 0xc0, 0x00, 0x00, 40 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0xff, 0xe1, 0xe0, 0x03, 0xc0, 0x00, 0x00, 41 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0xff, 0xc1, 0xe0, 0x03, 0xc0, 0x00, 0x00, 42 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0xff, 0x01, 0xe0, 0x03, 0xc0, 0x00, 0x00, 43 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0xf8, 0x01, 0xe0, 0x03, 0xc0, 0x00, 0x00, 44 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xe0, 0x03, 0xc0, 0x00, 0x00, 45 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xe0, 0x07, 0x80, 0x00, 0x00, 46 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xe0, 0x07, 0x80, 0x00, 0x00, 47 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xe0, 0x0f, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xe0, 0x3f, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 50 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xff, 0xfc, 0x00, 0x00, 0x00, 51 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x01, 0xff, 0xe0, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0xfc, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x07, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 74 | }; 75 | 76 | 77 | // '_a_frm0,40', 20x20px 78 | const unsigned char arrow_bitmap_a_frm0_40 [] PROGMEM = { 79 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x02, 0x00, 0x60, 0x0c, 0x00, 0xc0, 80 | 0x0c, 0x00, 0x80, 0x31, 0x00, 0x00, 0x33, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x8c, 81 | 0x00, 0x00, 0xc6, 0x00, 0x00, 0x33, 0x00, 0x80, 0x31, 0x00, 0xc0, 0x0c, 0x00, 0x60, 0x0c, 0x00, 82 | 0x30, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 83 | }; 84 | // '_a_frm1,40', 20x20px 85 | const unsigned char arrow_bitmap_a_frm1_40 [] PROGMEM = { 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x98, 0x01, 0x00, 0x30, 0x03, 0x00, 0x30, 87 | 0x02, 0x00, 0x60, 0x04, 0x00, 0xc0, 0x0c, 0x00, 0x80, 0x19, 0x00, 0x00, 0x33, 0x00, 0x00, 0x33, 88 | 0x00, 0x80, 0x19, 0x00, 0xc0, 0x0c, 0x00, 0x60, 0x04, 0x00, 0x30, 0x02, 0x00, 0x30, 0x03, 0x00, 89 | 0x98, 0x01, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 90 | }; 91 | // '_a_frm2,50', 20x20px 92 | const unsigned char arrow_bitmap_a_frm2_50 [] PROGMEM = { 93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x64, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x98, 94 | 0x01, 0x00, 0x30, 0x03, 0x00, 0x20, 0x03, 0x00, 0x40, 0x06, 0x00, 0xc0, 0x0c, 0x00, 0xc0, 0x0c, 95 | 0x00, 0x40, 0x06, 0x00, 0x20, 0x03, 0x00, 0x30, 0x03, 0x00, 0x98, 0x01, 0x00, 0xcc, 0x00, 0x00, 96 | 0x64, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 97 | }; 98 | // '_a_frm3,40', 20x20px 99 | const unsigned char arrow_bitmap_a_frm3_40 [] PROGMEM = { 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x26, 0x00, 0x00, 0x6c, 0x00, 0x00, 0xc8, 101 | 0x00, 0x00, 0x98, 0x01, 0x00, 0x30, 0x01, 0x00, 0x20, 0x03, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 102 | 0x00, 0x20, 0x03, 0x00, 0x30, 0x01, 0x00, 0x98, 0x01, 0x00, 0xc8, 0x00, 0x00, 0x6c, 0x00, 0x00, 103 | 0x26, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 104 | }; 105 | // '_a_frm4,40', 20x20px 106 | const unsigned char arrow_bitmap_a_frm4_40 [] PROGMEM = { 107 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x64, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x98, 108 | 0x01, 0x00, 0x30, 0x01, 0x00, 0x20, 0x03, 0x00, 0x40, 0x06, 0x00, 0xc0, 0x0c, 0x00, 0xc0, 0x0c, 109 | 0x00, 0x40, 0x06, 0x00, 0x20, 0x03, 0x00, 0x30, 0x01, 0x00, 0x98, 0x01, 0x00, 0xcc, 0x00, 0x00, 110 | 0x64, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 111 | }; 112 | // '_a_frm5,40', 20x20px 113 | const unsigned char arrow_bitmap_a_frm5_40 [] PROGMEM = { 114 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x98, 0x00, 0x00, 0x30, 0x01, 0x00, 0x30, 115 | 0x03, 0x00, 0x40, 0x06, 0x00, 0xc0, 0x0c, 0x00, 0x80, 0x19, 0x00, 0x00, 0x33, 0x00, 0x00, 0x33, 116 | 0x00, 0x80, 0x19, 0x00, 0xc0, 0x0c, 0x00, 0x40, 0x06, 0x00, 0x30, 0x03, 0x00, 0x30, 0x01, 0x00, 117 | 0x98, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 118 | }; 119 | // '_a_frm6,40', 20x20px 120 | const unsigned char arrow_bitmap_a_frm6_40 [] PROGMEM = { 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x02, 0x00, 0x60, 0x06, 0x00, 0xc0, 122 | 0x0c, 0x00, 0x80, 0x39, 0x00, 0x00, 0x33, 0x00, 0x00, 0xce, 0x00, 0x00, 0x98, 0x01, 0x00, 0x98, 123 | 0x01, 0x00, 0xce, 0x00, 0x00, 0x33, 0x00, 0x80, 0x39, 0x00, 0xc0, 0x0c, 0x00, 0x60, 0x06, 0x00, 124 | 0x30, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 125 | }; 126 | // '_a_frm7,40', 20x20px 127 | const unsigned char arrow_bitmap_a_frm7_40 [] PROGMEM = { 128 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x18, 0x00, 0x80, 129 | 0x33, 0x00, 0x00, 0xe6, 0x00, 0x00, 0xcc, 0x01, 0x00, 0x30, 0x03, 0x00, 0x60, 0x0e, 0x00, 0x60, 130 | 0x0e, 0x00, 0x30, 0x03, 0x00, 0xcc, 0x01, 0x00, 0xe6, 0x00, 0x80, 0x33, 0x00, 0xc0, 0x18, 0x00, 131 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 132 | }; 133 | // '_a_frm8,50', 20x20px 134 | const unsigned char arrow_bitmap_a_frm8_50 [] PROGMEM = { 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x11, 0x00, 0x00, 0x33, 0x00, 0x00, 136 | 0x66, 0x00, 0x00, 0xcc, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 137 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0xcc, 0x01, 0x00, 0x66, 0x00, 0x00, 0x33, 0x00, 138 | 0x80, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 139 | }; 140 | // '_a_frm9,40', 20x20px 141 | const unsigned char arrow_bitmap_a_frm9_40 [] PROGMEM = { 142 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc6, 0x00, 0x00, 143 | 0x8c, 0x00, 0x00, 0x18, 0x01, 0x00, 0x30, 0x03, 0x00, 0x20, 0x06, 0x00, 0x40, 0x0c, 0x00, 0x40, 144 | 0x0c, 0x00, 0x60, 0x06, 0x00, 0x30, 0x03, 0x00, 0x18, 0x01, 0x00, 0x8c, 0x00, 0x00, 0xc6, 0x00, 145 | 0x00, 0x63, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 146 | }; 147 | // '_a_frm10,40', 20x20px 148 | const unsigned char arrow_bitmap_a_frm10_40 [] PROGMEM = { 149 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x46, 0x00, 0x00, 0xcc, 0x00, 0x00, 150 | 0x88, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x03, 0x00, 0x60, 0x06, 0x00, 0x40, 0x0c, 0x00, 0x40, 151 | 0x0c, 0x00, 0x60, 0x06, 0x00, 0x30, 0x03, 0x00, 0x18, 0x03, 0x00, 0x88, 0x01, 0x00, 0xcc, 0x00, 152 | 0x00, 0x46, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 153 | }; 154 | // '_a_frm11,40', 20x20px 155 | const unsigned char arrow_bitmap_a_frm11_40 [] PROGMEM = { 156 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc6, 0x00, 0x00, 157 | 0x8c, 0x00, 0x00, 0x18, 0x01, 0x00, 0x30, 0x03, 0x00, 0x60, 0x06, 0x00, 0x40, 0x0c, 0x00, 0x40, 158 | 0x0c, 0x00, 0x60, 0x06, 0x00, 0x30, 0x03, 0x00, 0x18, 0x01, 0x00, 0x8c, 0x00, 0x00, 0xc6, 0x00, 159 | 0x00, 0x63, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 160 | }; 161 | // '_a_frm12,40', 20x20px 162 | const unsigned char arrow_bitmap_a_frm12_40 [] PROGMEM = { 163 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 164 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 165 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 166 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 167 | }; 168 | // '_a_frm13,40', 20x20px 169 | const unsigned char arrow_bitmap_a_frm13_40 [] PROGMEM = { 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 171 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 172 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 173 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 174 | }; 175 | // '_a_frm14,50', 20x20px 176 | const unsigned char arrow_bitmap_a_frm14_50 [] PROGMEM = { 177 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 178 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 179 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 180 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 181 | }; 182 | // '_a_frm15,40', 20x20px 183 | const unsigned char arrow_bitmap_a_frm15_40 [] PROGMEM = { 184 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 185 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 186 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 187 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 188 | }; 189 | // '_a_frm18,40', 20x20px 190 | const unsigned char arrow_bitmap_a_frm18_40 [] PROGMEM = { 191 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 192 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 193 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 194 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 195 | }; 196 | // '_a_frm19,40', 20x20px 197 | const unsigned char arrow_bitmap_a_frm19_40 [] PROGMEM = { 198 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 199 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 200 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 201 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 202 | }; 203 | // '_a_frm16,40', 20x20px 204 | const unsigned char arrow_bitmap_a_frm16_40 [] PROGMEM = { 205 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 206 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 207 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 208 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 209 | }; 210 | // '_a_frm17,40', 20x20px 211 | const unsigned char arrow_bitmap_a_frm17_40 [] PROGMEM = { 212 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 213 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 214 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 215 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 216 | }; 217 | // '_a_frm21,40', 20x20px 218 | const unsigned char arrow_bitmap_a_frm21_40 [] PROGMEM = { 219 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 220 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 221 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 222 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 223 | }; 224 | // '_a_frm20,50', 20x20px 225 | const unsigned char arrow_bitmap_a_frm20_50 [] PROGMEM = { 226 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 227 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 228 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 229 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 230 | }; 231 | // '_a_frm22,40', 20x20px 232 | const unsigned char arrow_bitmap_a_frm22_40 [] PROGMEM = { 233 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 234 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 235 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 236 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 237 | }; 238 | // '_a_frm23,40', 20x20px 239 | const unsigned char arrow_bitmap_a_frm23_40 [] PROGMEM = { 240 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 241 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 242 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 243 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 244 | }; 245 | // '_a_frm25,40', 20x20px 246 | const unsigned char arrow_bitmap_a_frm25_40 [] PROGMEM = { 247 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 248 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 249 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 250 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 251 | }; 252 | // '_a_frm24,40', 20x20px 253 | const unsigned char arrow_bitmap_a_frm24_40 [] PROGMEM = { 254 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 255 | 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x30, 0x06, 0x00, 0x60, 0x0c, 0x00, 0x60, 256 | 0x0c, 0x00, 0x30, 0x06, 0x00, 0x18, 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 257 | 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 258 | }; 259 | // '_a_frm26,50', 20x20px 260 | const unsigned char arrow_bitmap_a_frm26_50 [] PROGMEM = { 261 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x33, 0x00, 0x00, 262 | 0x66, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x98, 0x01, 0x00, 0x30, 0x03, 0x00, 0x60, 0x06, 0x00, 0x60, 263 | 0x06, 0x00, 0x30, 0x03, 0x00, 0x98, 0x01, 0x00, 0xcc, 0x00, 0x00, 0x66, 0x00, 0x00, 0x33, 0x00, 264 | 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 265 | }; 266 | // '_a_frm27,50', 20x20px 267 | const unsigned char arrow_bitmap_a_frm27_50 [] PROGMEM = { 268 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x0c, 0x00, 0xc0, 0x18, 0x00, 0x80, 269 | 0x31, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x18, 0x03, 0x00, 0x18, 270 | 0x03, 0x00, 0x8c, 0x01, 0x00, 0xc6, 0x00, 0x00, 0x63, 0x00, 0x80, 0x31, 0x00, 0xc0, 0x18, 0x00, 271 | 0x40, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 272 | }; 273 | 274 | // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 2240) 275 | const int arrow_bitmapallArray_LEN = 28; 276 | static const unsigned char* arrow_bitmapallArray[28] = { 277 | arrow_bitmap_a_frm0_40, 278 | arrow_bitmap_a_frm1_40, 279 | arrow_bitmap_a_frm2_50, 280 | arrow_bitmap_a_frm3_40, 281 | arrow_bitmap_a_frm4_40, 282 | arrow_bitmap_a_frm5_40, 283 | arrow_bitmap_a_frm6_40, 284 | arrow_bitmap_a_frm7_40, 285 | arrow_bitmap_a_frm8_50, 286 | arrow_bitmap_a_frm9_40, 287 | arrow_bitmap_a_frm10_40, 288 | arrow_bitmap_a_frm11_40, 289 | arrow_bitmap_a_frm12_40, 290 | arrow_bitmap_a_frm13_40, 291 | arrow_bitmap_a_frm14_50, 292 | arrow_bitmap_a_frm15_40, 293 | arrow_bitmap_a_frm16_40, 294 | arrow_bitmap_a_frm17_40, 295 | arrow_bitmap_a_frm18_40, 296 | arrow_bitmap_a_frm19_40, 297 | arrow_bitmap_a_frm20_50, 298 | arrow_bitmap_a_frm21_40, 299 | arrow_bitmap_a_frm22_40, 300 | arrow_bitmap_a_frm23_40, 301 | arrow_bitmap_a_frm24_40, 302 | arrow_bitmap_a_frm25_40, 303 | arrow_bitmap_a_frm26_50, 304 | arrow_bitmap_a_frm27_50, 305 | }; 306 | 307 | #endif -------------------------------------------------------------------------------- /include/Menu.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef MENU_H 7 | #define MENU_H 8 | 9 | /** 10 | * Need to pass through 11 | * u8g2 instance 12 | * button and encoder detection for menu interaction 13 | */ 14 | 15 | /** 16 | * How do I build a menu list 17 | * Ref code: https://github.com/shuzonudas/monoview/blob/master/U8g2/Examples/Menu/simpleMenu/simpleMenu.ino#L278 18 | */ 19 | 20 | class Menu 21 | { 22 | public: 23 | Menu(U8G2_SSD1306_128X64_NONAME_F_HW_I2C *oled, AP33772 *usb, RotaryEncoder *encoder, Button *button_encoder, Button *button_output, Button *button_selectVI); 24 | uint8_t numPDO; // include both fixed PDO and PPS 25 | int menuPosition; 26 | // void get_numPDO(); 27 | void set_qc3flag(bool flag); 28 | void page_selectCapability(); 29 | void page_bootProfile(); 30 | void checkMenuPosition(bool wrapAround); // Check if menuPosition is within range, wrap around if not 31 | 32 | private: 33 | U8G2_SSD1306_128X64_NONAME_F_HW_I2C *u8g2; 34 | AP33772 *usbpd; 35 | RotaryEncoder *_encoder; 36 | Button *_button_encoder; 37 | Button *_button_output; 38 | Button *_button_selectVI; 39 | 40 | bool qc3_0available; 41 | }; 42 | 43 | #endif -------------------------------------------------------------------------------- /include/PocketPDPinOut.h: -------------------------------------------------------------------------------- 1 | 2 | //Encoder 3 | #define pin_encoder_SW 18 // button pin 4 | #define pin_encoder_A 19 // Rotary encoder Pin A //CLK 5 | #define pin_encoder_B 20 // Rotary encoder Pin B //DATA 6 | 7 | #define pin_output_Enable 1 8 | #define pin_button_outputSW 10 9 | #define pin_button_selectVI 11 10 | 11 | 12 | #define pin_SDA 4 13 | #define pin_SCL 5 14 | 15 | -------------------------------------------------------------------------------- /include/StateMachine.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define ALARM_NUM0 0 // Timer 0 13 | #define ALARM_IRQ0 TIMER_IRQ_0 14 | #define DELAY0 33000 // In usecond , 33ms, used for OLED update 15 | 16 | #define ALARM_NUM1 1 // Timer 1 17 | #define ALARM_IRQ1 TIMER_IRQ_1 18 | #define DELAY1 500000 // In usecond , 500ms, used for cursor 19 | 20 | enum class State 21 | { 22 | BOOT, 23 | OBTAIN, 24 | CAPDISPLAY, 25 | NORMAL_PPS, 26 | NORMAL_PDO, 27 | NORMAL_QC, 28 | MENU 29 | }; 30 | enum Supply_Profile 31 | { 32 | PROFILE_PPS, 33 | PROFILE_PDO, 34 | PROFILE_QC 35 | }; 36 | enum Supply_Mode 37 | { 38 | MODE_CV, 39 | MODE_CC 40 | }; 41 | enum Supply_Capability 42 | { 43 | NON_PPS, 44 | WITH_PPS 45 | }; 46 | enum Supply_Adjust_Mode 47 | { 48 | VOTLAGE_ADJUST, 49 | CURRENT_ADJUST 50 | }; 51 | enum Output_Display_Mode 52 | { 53 | OUTPUT_NORMAL, 54 | OUTPUT_ENERGY 55 | }; 56 | 57 | #ifndef STATEMACHINE_H 58 | #define STATEMACHINE_H 59 | 60 | class StateMachine 61 | { 62 | public: 63 | StateMachine() : ina226(0x40), 64 | u8g2(U8G2_R0, U8X8_PIN_NONE), 65 | state(State::BOOT), 66 | startTime(millis()), 67 | bootInitialized(false), 68 | obtainInitialized(false), 69 | displayCapInitialized(false), 70 | normalPPSInitialized(false), 71 | normalPDOInitialized(false), 72 | normalQCInitialized(false), 73 | button_encoder(pin_encoder_SW), 74 | button_output(pin_button_outputSW), 75 | button_selectVI(pin_button_selectVI), 76 | supply_mode(MODE_CV), 77 | voltageIncrement{20, 100, 1000}, 78 | currentIncrement{50, 100, 1000}, 79 | menu(&u8g2, &usbpd, &encoder, &button_encoder, &button_output, &button_selectVI), 80 | eepromHandler(), 81 | forceSave(false), 82 | saveStamp(0) {}; 83 | 84 | void update(); 85 | const char *getState(); 86 | 87 | private: 88 | State state; 89 | INA226 ina226; 90 | AP33772 usbpd; 91 | static RotaryEncoder encoder; //"static" Make sure there is only 1 copy for all object, shared variable 92 | U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2; 93 | Button button_encoder; 94 | Button button_output; 95 | Button button_selectVI; 96 | Menu menu; 97 | EEPROMHandler eepromHandler; 98 | bool forceSave; 99 | uint32_t saveStamp; 100 | 101 | unsigned long startTime; 102 | bool bootInitialized; 103 | bool obtainInitialized; 104 | bool displayCapInitialized; 105 | bool normalPPSInitialized; 106 | bool normalPDOInitialized; 107 | bool normalQCInitialized; 108 | bool buttonPressed; 109 | 110 | static bool timerFlag0; 111 | static bool timerFlag1; 112 | 113 | int targetVoltage; // Unit mV 114 | int targetCurrent; // Unit mA 115 | int voltageIncrementIndex; // just 0,1, or 2 116 | int currentIncrementIndex; // just 0,1, or 2 117 | int voltageIncrement[3]; 118 | int currentIncrement[3]; 119 | float ina_current_ma; // Unit 120 | float vbus_voltage_mv; // Unit mV 121 | int encoder_position; 122 | int encoder_newPos; 123 | Supply_Mode supply_mode; 124 | Supply_Adjust_Mode supply_adjust_mode; 125 | Output_Display_Mode output_display_mode; 126 | 127 | static constexpr long BOOT_TO_OBTAIN_TIMEOUT = 500; // Timeout for BOOT to OBTAIN state in seconds 128 | static constexpr long OBTAIN_TO_CAPDISPLAY_TIMEOUT = 1500; // Timeout for OBTAIN to DISPLAYCAP state in seconds 129 | static constexpr long DISPLAYCCAP_TO_NORMAL_TIMEOUT = 3000; // Timeout for DISPLAYCAP to NORMAL state in seconds 130 | static constexpr int voltage_cursor_position[] = {40, 33, 26}; 131 | static constexpr int current_cursor_position[] = {41, 34, 27}; 132 | void componentInit(); 133 | 134 | void handleBootState(); 135 | void handleObtainState(); 136 | void handleDisplayCapState(); 137 | void handleNormalPPSState(); 138 | void handleNormalPDOState(); 139 | void handleNormalQCState(); 140 | void handleMenuState(); 141 | 142 | void transitionTo(State newState); 143 | void printBootingScreen(); 144 | void printProfile(); 145 | void printOLED_fixed(); 146 | void updateOLED(float voltage, float current, uint8_t requestEN); 147 | void update_supply_mode(); 148 | 149 | void process_request_voltage_current(); 150 | void process_encoder_input(); 151 | void process_output_button(); 152 | static void encoderISR(); 153 | static void timerISR0(); // 100ms 154 | static void timerISR1(); // 1s 155 | char buffer[10]; 156 | 157 | int counter_gif = 0; //Count up to 27 158 | 159 | // Energy tracking 160 | double accumulatedWh = 0.0; // Accumulated watt-hours 161 | double accumulatedAh = 0.0; // Accumulated amp-hours 162 | unsigned long lastEnergyUpdate = 0; // Last time energy was calculated 163 | unsigned long energyStartTime = 0; // Time when device was powered on 164 | unsigned long historicalOutputTime = 0; // Total seconds output has been enabled (across sessions) 165 | unsigned long currentSessionStartTime = 0; // millis() when current output session started 166 | 167 | void handleInitialMode(); 168 | bool saveSettingsToEEPROM(); 169 | bool loadSettingsFromEEPROM(bool vc); 170 | void updateSaveStamp(); 171 | void updateEnergyAccumulation(float voltage, float current); 172 | void updateOLED_Energy(float voltage, float current); 173 | }; 174 | 175 | #endif // STATEMACHINE_H -------------------------------------------------------------------------------- /media/VoltageCurrent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/VoltageCurrent.jpg -------------------------------------------------------------------------------- /media/bootscreen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/bootscreen.jpg -------------------------------------------------------------------------------- /media/devicemanager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/devicemanager.png -------------------------------------------------------------------------------- /media/normalpdo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/normalpdo.jpg -------------------------------------------------------------------------------- /media/normalpdo5V.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/normalpdo5V.jpg -------------------------------------------------------------------------------- /media/normalpps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/normalpps.jpg -------------------------------------------------------------------------------- /media/normalpps2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/normalpps2.jpg -------------------------------------------------------------------------------- /media/pdoprofile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/pdoprofile.jpg -------------------------------------------------------------------------------- /media/ppsmenu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/ppsmenu.jpg -------------------------------------------------------------------------------- /media/ppsprofile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/ppsprofile.jpg -------------------------------------------------------------------------------- /media/putty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/putty.png -------------------------------------------------------------------------------- /media/removabledrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CentyLab/PocketPD/e5e91b5c8129fdd7222c37a653fa4bedcf2ad4cb/media/removabledrive.png -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env] 12 | platform = https://github.com/maxgerhardt/platform-raspberrypi.git 13 | board = pico 14 | framework = arduino 15 | board_build.core = earlephilhower 16 | lib_deps = 17 | olikraus/U8g2@^2.35.19 18 | arduinogetstarted/ezButton@^1.0.4 19 | mathertel/RotaryEncoder@^1.5.3 20 | khoih-prog/RPI_PICO_TimerInterrupt@^1.3.1 21 | 22 | [env:HW1_0] 23 | # debug_tool = cmsis-dap 24 | # upload_protocol = cmsis-dap 25 | # debug_speed = 5000 26 | build_flags = 27 | -DHW1_0 28 | -DVERSION="\"0.9.9\"" 29 | 30 | [env:HW1_1] 31 | debug_tool = cmsis-dap 32 | upload_protocol = cmsis-dap 33 | debug_speed = 5000 34 | build_flags = 35 | -DHW1_1 36 | -DVERSION="\"0.9.9\"" 37 | 38 | -------------------------------------------------------------------------------- /src/AP33772_PocketPD.cpp: -------------------------------------------------------------------------------- 1 | /* Structs and Resgister list ported from "AP33772 I2C Command Tester" by Joseph Liang 2 | * Created 11 April 2022 3 | * Added class and class functions by VicentN for PicoPD evaluation board 4 | * Created 8 August 2023 5 | */ 6 | 7 | #include "AP33772_PocketPD.h" 8 | 9 | /** 10 | * @brief Class constuctor 11 | * @param &wire reference of Wire class. Pass in Wire or Wire1 12 | */ 13 | AP33772::AP33772(TwoWire &wire) 14 | { 15 | _i2cPort = &wire; 16 | } 17 | 18 | /** 19 | * @brief Check if power supply is good and fetch the PDO profile 20 | */ 21 | void AP33772::begin() 22 | { 23 | i2c_read(AP33772_ADDRESS, CMD_STATUS, 1); // CMD: Read Status 24 | ap33772_status.readStatus = readBuf[0]; 25 | 26 | if (ap33772_status.isOvp) 27 | event_flag.ovp = 1; 28 | if (ap33772_status.isOcp) 29 | event_flag.ocp = 1; 30 | 31 | if (ap33772_status.isReady) // negotiation finished 32 | { 33 | if (ap33772_status.isNewpdo) // new PDO 34 | { 35 | if (ap33772_status.isSuccess) // negotiation success 36 | event_flag.newNegoSuccess = 1; 37 | else 38 | event_flag.newNegoFail = 1; 39 | } 40 | else 41 | { 42 | if (ap33772_status.isSuccess) 43 | event_flag.negoSuccess = 1; 44 | else 45 | event_flag.negoFail = 1; 46 | } 47 | } 48 | delay(10); 49 | 50 | // If negotiation is good, let's load in some PDOs from charger 51 | if (event_flag.newNegoSuccess | event_flag.negoSuccess) 52 | { 53 | event_flag.newNegoSuccess = 0; 54 | 55 | i2c_read(AP33772_ADDRESS, CMD_PDONUM, 1); // CMD: Read PDO Number 56 | numPDO = readBuf[0]; 57 | 58 | i2c_read(AP33772_ADDRESS, CMD_SRCPDO, SRCPDO_LENGTH); // CMD: Read PDOs 59 | // copy PDOs to pdoData[] 60 | for (byte i = 0; i < numPDO; i++) 61 | { 62 | pdoData[i].byte0 = readBuf[i * 4]; 63 | pdoData[i].byte1 = readBuf[i * 4 + 1]; 64 | pdoData[i].byte2 = readBuf[i * 4 + 2]; 65 | pdoData[i].byte3 = readBuf[i * 4 + 3]; 66 | 67 | if ((pdoData[i].byte3 & 0xF0) == 0xC0) // PPS profile found 68 | { 69 | PPSindex = i; // Store index 70 | existPPS = 1; // Turn on flag 71 | } 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * @brief Check if target voltage and current are in range 78 | * 79 | * @param targetVoltage 80 | * @param targetCurrent 81 | */ 82 | void AP33772::checkVoltageCurrent(int& targetVoltage, int& targetCurrent) 83 | { int maxVoltage = pdoData[PPSindex].pps.maxVoltage * 100; 84 | int minVoltage = pdoData[PPSindex].pps.minVoltage * 100; 85 | // Check if target voltage is in range 86 | if(targetVoltage < minVoltage) 87 | targetVoltage = minVoltage; // Minimum 5V 88 | else if (targetVoltage > maxVoltage) 89 | targetVoltage = maxVoltage; // Maximum 20V 90 | 91 | int maxCurrent = pdoData[PPSindex].pps.maxCurrent * 50; // Convert to mA 92 | // Check if target current is in range 93 | if(targetCurrent > maxCurrent) 94 | targetCurrent = maxCurrent; // Maximum 3000mA 95 | } 96 | 97 | /** 98 | * @brief Set VBUS voltage and max current 99 | * Current will automatically be in limit mode 100 | * @param targetVoltage, targetCurrent, - mV and mA 101 | */ 102 | void AP33772::setSupplyVoltageCurrent(int targetVoltage, int targetCurrent) 103 | { 104 | if ((existPPS) && (pdoData[PPSindex].pps.maxVoltage * 100 >= targetVoltage) && (pdoData[PPSindex].pps.minVoltage * 100 <= targetVoltage)) 105 | { 106 | indexPDO = PPSindex; 107 | reqPpsVolt = targetVoltage / 20; // Unit in 20mV/LBS 108 | rdoData.pps.objPosition = PPSindex + 1; // index 1 109 | // rdoData.pps.opCurrent = pdoData[PPSindex].pps.maxCurrent; 110 | rdoData.pps.opCurrent = targetCurrent / 50; // Current limit 111 | rdoData.pps.voltage = reqPpsVolt; 112 | writeRDO(); 113 | return; 114 | } 115 | } 116 | 117 | /** 118 | * @brief Set VBUS voltage 119 | * Current is set to MAX all the time 120 | * @param targetVoltage in mV 121 | */ 122 | void AP33772::setVoltage(int targetVoltage) 123 | { 124 | /* 125 | Step 1: Check if PPS can satify request voltage 126 | Step 2: Scan PDOs to see what is the lower closest voltage to request 127 | Step 3: Compare found PDOs votlage and PPS max voltage 128 | */ 129 | byte tempIndex = 0; 130 | if ((existPPS) && (pdoData[PPSindex].pps.maxVoltage * 100 >= targetVoltage) && (pdoData[PPSindex].pps.minVoltage * 100 <= targetVoltage)) 131 | { 132 | indexPDO = PPSindex; 133 | reqPpsVolt = targetVoltage / 20; // Unit in 20mV/LBS 134 | rdoData.pps.objPosition = PPSindex + 1; // index 1 135 | rdoData.pps.opCurrent = pdoData[PPSindex].pps.maxCurrent; 136 | rdoData.pps.voltage = reqPpsVolt; 137 | writeRDO(); 138 | return; 139 | } 140 | else 141 | { 142 | // Step 2: Scan PDOs to see what is the lower closest voltage to request 143 | for (byte i = 0; i < numPDO - existPPS; i++) 144 | { 145 | if (pdoData[i].fixed.voltage * 50 <= targetVoltage) 146 | tempIndex = i; 147 | } 148 | // Step 3: Compare found PDOs votlage and PPS max voltage 149 | if (pdoData[tempIndex].fixed.voltage * 50 > pdoData[PPSindex].pps.maxVoltage * 100) 150 | { 151 | indexPDO = tempIndex; 152 | rdoData.fixed.objPosition = tempIndex + 1; // Index 0 to Index 1 153 | rdoData.fixed.maxCurrent = pdoData[indexPDO].fixed.maxCurrent; 154 | rdoData.fixed.opCurrent = pdoData[indexPDO].fixed.maxCurrent; 155 | writeRDO(); 156 | return; 157 | } 158 | else // If PPS voltage larger or equal to Fixed PDO 159 | { 160 | indexPDO = PPSindex; 161 | reqPpsVolt = pdoData[PPSindex].pps.maxVoltage * 5; // Convert unit: 100mV -> 20mV 162 | rdoData.pps.objPosition = PPSindex + 1; // index 1 163 | rdoData.pps.opCurrent = pdoData[PPSindex].pps.maxCurrent; 164 | rdoData.pps.voltage = reqPpsVolt; 165 | writeRDO(); 166 | return; 167 | } 168 | } 169 | } 170 | 171 | /** 172 | * @brief Request PDO profile. 173 | * @param PDOindex start from index 0 to (PDONum - 1) if no PPS, (PDONum -2) if PPS found 174 | */ 175 | void AP33772::setPDO(uint8_t PDOindex) 176 | { 177 | uint8_t guarding; 178 | 179 | if (PPSindex == 1) 180 | {guarding = numPDO - 2;} 181 | else 182 | guarding = numPDO - 1; // Example array[4] only exist index 0,1,2,3 183 | 184 | if (PDOindex <= guarding) 185 | { 186 | rdoData.fixed.objPosition = PDOindex + 1; // Index 0 to Index 1 187 | rdoData.fixed.maxCurrent = pdoData[PDOindex].fixed.maxCurrent; 188 | rdoData.fixed.opCurrent = pdoData[PDOindex].fixed.maxCurrent; 189 | writeRDO(); 190 | } 191 | } 192 | 193 | /** 194 | * @brief Set max current before tripping at wall plug 195 | * @param targetMaxCurrent in mA 196 | */ 197 | void AP33772::setMaxCurrent(int targetMaxCurrent) 198 | { 199 | /* 200 | Step 1: Check if current profile is PPS, check if max current is lower than request 201 | If yes, set new max current 202 | If no, report fault 203 | Step 2: If profile is PDO, check if max current is lower than request 204 | If yes, set new max current 205 | If no, report fault 206 | */ 207 | if (indexPDO == PPSindex) 208 | { 209 | if (targetMaxCurrent <= pdoData[PPSindex].pps.maxCurrent * 50) 210 | { 211 | rdoData.pps.objPosition = PPSindex + 1; // index 1 212 | rdoData.pps.opCurrent = targetMaxCurrent / 50; // 50mA/LBS 213 | rdoData.pps.voltage = reqPpsVolt; 214 | writeRDO(); 215 | } 216 | else 217 | { 218 | } // Do nothing 219 | } 220 | else 221 | { 222 | if (targetMaxCurrent <= pdoData[indexPDO].fixed.maxCurrent * 10) 223 | { 224 | rdoData.fixed.objPosition = indexPDO + 1; // Index 0 to Index 1 225 | rdoData.fixed.maxCurrent = targetMaxCurrent / 10; // 10mA/LBS 226 | rdoData.fixed.opCurrent = targetMaxCurrent / 10; // 10mA/LBS 227 | writeRDO(); 228 | } 229 | else 230 | { 231 | } // Do nothing 232 | } 233 | } 234 | 235 | /** 236 | * @brief Set resistance value of 10K NTC at 25C, 50C, 75C and 100C. 237 | * Default is 10000, 4161, 1928, 974Ohm 238 | * @param TR25, TR50, TR75, TR100 unit in Ohm 239 | * @attention Blocking function due to long I2C write, min blocking time 15ms 240 | */ 241 | void AP33772::setNTC(int TR25, int TR50, int TR75, int TR100) // Parameter NOT DONE 242 | { 243 | writeBuf[0] = TR25 & 0xff; 244 | writeBuf[1] = (TR25 >> 8) & 0xff; 245 | i2c_write(AP33772_ADDRESS, 0x28, 2); 246 | delay(5); 247 | writeBuf[0] = TR50 & 0xff; 248 | writeBuf[1] = (TR50 >> 8) & 0xff; 249 | i2c_write(AP33772_ADDRESS, 0x2A, 2); 250 | delay(5); 251 | writeBuf[0] = TR75 & 0xff; 252 | writeBuf[1] = (TR75 >> 8) & 0xff; 253 | i2c_write(AP33772_ADDRESS, 0x2C, 2); 254 | delay(5); 255 | writeBuf[0] = TR100 & 0xff; 256 | writeBuf[1] = (TR100 >> 8) & 0xff; 257 | i2c_write(AP33772_ADDRESS, 0x2E, 2); 258 | } 259 | 260 | /** 261 | * @brief Set target temperature (C) when output power through USB-C is reduced 262 | * Default is 120 C 263 | * @param temperature (unit in Celcius) 264 | */ 265 | void AP33772::setDeratingTemp(int temperature) 266 | { 267 | writeBuf[0] = temperature; 268 | i2c_write(AP33772_ADDRESS, CMD_DRTHR, 1); 269 | } 270 | 271 | void AP33772::setMask(AP33772_MASK flag) 272 | { 273 | // First read in what is currently in the MASK 274 | i2c_read(AP33772_ADDRESS, CMD_MASK, 1); 275 | writeBuf[0] = readBuf[0] | flag; 276 | delay(5); // Short break between read/write 277 | i2c_write(AP33772_ADDRESS, CMD_MASK, 1); 278 | } 279 | 280 | void AP33772::clearMask(AP33772_MASK flag) 281 | { 282 | // First read in what is currently in the MASK 283 | i2c_read(AP33772_ADDRESS, CMD_MASK, 1); 284 | writeBuf[0] = readBuf[0] & ~flag; 285 | delay(5); // Short break between read/write 286 | i2c_write(AP33772_ADDRESS, CMD_MASK, 1); 287 | } 288 | 289 | void AP33772::i2c_read(byte slvAddr, byte cmdAddr, byte len) 290 | { 291 | // clear readBuffer 292 | for (byte i = 0; i < READ_BUFF_LENGTH; i++) 293 | { 294 | readBuf[i] = 0; 295 | } 296 | byte i = 0; 297 | Wire.beginTransmission(slvAddr); // transmit to device SLAVE_ADDRESS 298 | Wire.write(cmdAddr); // sets the command register 299 | Wire.endTransmission(); // stop transmitting 300 | 301 | Wire.requestFrom(slvAddr, len); // request len bytes from peripheral device 302 | if (len <= Wire.available()) 303 | { // if len bytes were received 304 | while (Wire.available()) 305 | { 306 | readBuf[i] = (byte)Wire.read(); 307 | i++; 308 | } 309 | } 310 | } 311 | 312 | void AP33772::i2c_write(byte slvAddr, byte cmdAddr, byte len) 313 | { 314 | Wire.beginTransmission(slvAddr); // transmit to device SLAVE_ADDRESS 315 | Wire.write(cmdAddr); // sets the command register 316 | Wire.write(writeBuf, len); // write data with len 317 | Wire.endTransmission(); // stop transmitting 318 | 319 | // clear readBuffer 320 | for (byte i = 0; i < WRITE_BUFF_LENGTH; i++) 321 | { 322 | writeBuf[i] = 0; 323 | } 324 | } 325 | 326 | /** 327 | * @brief Write the desire power profile back to the power source 328 | */ 329 | void AP33772::writeRDO() 330 | { 331 | writeBuf[3] = rdoData.byte3; 332 | writeBuf[2] = rdoData.byte2; 333 | writeBuf[1] = rdoData.byte1; 334 | writeBuf[0] = rdoData.byte0; 335 | i2c_write(AP33772_ADDRESS, CMD_RDO, 4); // CMD: Write RDO 336 | } 337 | 338 | /** 339 | * @brief Read VBUS voltage 340 | * @return voltage in mV 341 | */ 342 | int AP33772::readVoltage() 343 | { 344 | i2c_read(AP33772_ADDRESS, CMD_VOLTAGE, 1); 345 | return readBuf[0] * 80; // I2C read return 80mV/LSB 346 | } 347 | 348 | /** 349 | * @brief Read VBUS current 350 | * @return current in mA 351 | */ 352 | int AP33772::readCurrent() 353 | { 354 | i2c_read(AP33772_ADDRESS, CMD_CURRENT, 1); 355 | return readBuf[0] * 16; // I2C read return 24mA/LSB 356 | } 357 | 358 | /** 359 | * @brief Read maximum VBUS current 360 | * @return current in mA 361 | */ 362 | int AP33772::getMaxCurrent() const 363 | { 364 | if (indexPDO == PPSindex) 365 | { 366 | return pdoData[PPSindex].pps.maxCurrent * 50; 367 | } 368 | else 369 | { 370 | return pdoData[indexPDO].fixed.maxCurrent * 10; 371 | } 372 | } 373 | 374 | /** 375 | * @brief Read NTC temperature 376 | * @return tempearture in C 377 | */ 378 | int AP33772::readTemp() 379 | { 380 | i2c_read(AP33772_ADDRESS, CMD_TEMP, 1); 381 | return readBuf[0]; // I2C read return 1C/LSB 382 | } 383 | 384 | /** 385 | * @brief Hard reset the power supply. Will temporary cause power outage 386 | */ 387 | void AP33772::reset() 388 | { 389 | writeBuf[0] = 0x00; 390 | writeBuf[1] = 0x00; 391 | writeBuf[2] = 0x00; 392 | writeBuf[3] = 0x00; 393 | i2c_write(AP33772_ADDRESS, CMD_RDO, 4); 394 | } 395 | 396 | /** 397 | * @brief Debug code to quickly check power supply profile PDOs 398 | */ 399 | void AP33772::printPDO() 400 | { 401 | Serial.print("Source PDO Number = "); 402 | Serial.print(numPDO); 403 | Serial.println(); 404 | 405 | for (byte i = 0; i < numPDO; i++) 406 | { 407 | if ((pdoData[i].byte3 & 0xF0) == 0xC0) // PPS PDO 408 | { 409 | Serial.print("PDO["); 410 | Serial.print(i + 1); // PDO position start from 1 411 | Serial.print("] - PPS : "); 412 | Serial.print((float)(pdoData[i].pps.minVoltage) * 100 / 1000); 413 | Serial.print("V~"); 414 | Serial.print((float)(pdoData[i].pps.maxVoltage) * 100 / 1000); 415 | Serial.print("V @ "); 416 | Serial.print((float)(pdoData[i].pps.maxCurrent) * 50 / 1000); 417 | Serial.println("A"); 418 | } 419 | else if ((pdoData[i].byte3 & 0xC0) == 0x00) // Fixed PDO 420 | { 421 | Serial.print("PDO["); 422 | Serial.print(i + 1); 423 | Serial.print("] - Fixed : "); 424 | Serial.print((float)(pdoData[i].fixed.voltage) * 50 / 1000); 425 | Serial.print("V @ "); 426 | Serial.print((float)(pdoData[i].fixed.maxCurrent) * 10 / 1000); 427 | Serial.println("A"); 428 | } 429 | } 430 | Serial.println("==============================================="); 431 | } 432 | 433 | /** 434 | * Add on for PPS Bench Power Supply 435 | */ 436 | 437 | /** 438 | * @brief Get the number of power profile, include PPS if exist 439 | */ 440 | int AP33772::getNumPDO() 441 | { 442 | return numPDO; 443 | } 444 | 445 | /** 446 | * @brief Get index of PPS profile 447 | */ 448 | int AP33772::getPPSIndex() 449 | { 450 | return PPSindex; 451 | } 452 | 453 | /** 454 | * @brief MaxCurrent for fixed voltage PDO 455 | * @return Current in mAmp 456 | */ 457 | int AP33772::getPDOMaxcurrent(uint8_t PDOindex) 458 | { 459 | return pdoData[PDOindex].fixed.maxCurrent * 10; 460 | } 461 | 462 | /** 463 | * @brief Get fixed PDO voltage 464 | * @return Voltage in mVolt 465 | */ 466 | int AP33772::getPDOVoltage(uint8_t PDOindex) 467 | { 468 | return pdoData[PDOindex].fixed.voltage * 50; 469 | } 470 | 471 | /** 472 | * @brief Get PPS min votlage 473 | * @return Voltage in mVolt 474 | */ 475 | int AP33772::getPPSMinVoltage(uint8_t PPSindex) 476 | { 477 | return pdoData[PPSindex].pps.minVoltage * 100; 478 | } 479 | 480 | /** 481 | * @brief Get PPS max votlage 482 | * @return Voltage in mVolt 483 | */ 484 | int AP33772::getPPSMaxVoltage(uint8_t PPSindex) 485 | { 486 | return pdoData[PPSindex].pps.maxVoltage * 100; 487 | } 488 | 489 | /** 490 | * @brief Get PPS max current 491 | * @return Current in mAmp 492 | */ 493 | int AP33772::getPPSMaxCurrent(uint8_t PPSindex) 494 | { 495 | return pdoData[PPSindex].pps.maxCurrent * 50; 496 | } -------------------------------------------------------------------------------- /src/Button.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /** 4 | * Return 0 if button not pressed 5 | * 1 if button is short pressed 6 | * 2 if button is long pressed 7 | */ 8 | int Button::isButtonPressed(void) 9 | { 10 | button.loop(); // Must call first 11 | 12 | if (button.isPressed()) 13 | { 14 | pressedTime = millis(); 15 | isPressing = true; 16 | isLongDetected = false; 17 | } 18 | 19 | if (button.isReleased()) 20 | { 21 | isPressing = false; 22 | releasedTime = millis(); 23 | 24 | long pressDuration = releasedTime - pressedTime; 25 | 26 | if (pressDuration < SHORT_PRESS_TIME) 27 | return 1; 28 | } 29 | 30 | // TODO: Trim implementation, right now you have two way of signaling longpress 31 | // One is the return of the function, one is the longPressedFlag 32 | if (isPressing == true && isLongDetected == false) 33 | { 34 | long pressDuration = millis() - pressedTime; 35 | 36 | if (pressDuration > LONG_PRESS_TIME) 37 | { 38 | isLongDetected = true; 39 | longPressedFlag = true; 40 | return 2; 41 | } 42 | } 43 | return 0; 44 | } 45 | 46 | void Button::loop() 47 | { 48 | button.loop(); 49 | } 50 | 51 | // Pass through function 52 | void Button::setDebounceTime(unsigned long time) 53 | { 54 | button.setDebounceTime(time); 55 | } -------------------------------------------------------------------------------- /src/INA226.cpp: -------------------------------------------------------------------------------- 1 | // FILE: INA226.cpp 2 | // AUTHOR: Rob Tillaart 3 | // VERSION: 0.6.4 4 | // DATE: 2021-05-18 5 | // PURPOSE: Arduino library for INA226 power sensor 6 | // URL: https://github.com/RobTillaart/INA226 7 | // 8 | // Read the datasheet for the details 9 | 10 | 11 | #include "INA226.h" 12 | 13 | // REGISTERS 14 | #define INA226_CONFIGURATION 0x00 15 | #define INA226_SHUNT_VOLTAGE 0x01 16 | #define INA226_BUS_VOLTAGE 0x02 17 | #define INA226_POWER 0x03 18 | #define INA226_CURRENT 0x04 19 | #define INA226_CALIBRATION 0x05 20 | #define INA226_MASK_ENABLE 0x06 21 | #define INA226_ALERT_LIMIT 0x07 22 | #define INA226_MANUFACTURER 0xFE 23 | #define INA226_DIE_ID 0xFF 24 | 25 | 26 | // CONFIGURATION MASKS 27 | #define INA226_CONF_RESET_MASK 0x8000 28 | #define INA226_CONF_AVERAGE_MASK 0x0E00 29 | #define INA226_CONF_BUSVC_MASK 0x01C0 30 | #define INA226_CONF_SHUNTVC_MASK 0x0038 31 | #define INA226_CONF_MODE_MASK 0x0007 32 | 33 | 34 | //////////////////////////////////////////////////////// 35 | // 36 | // CONSTRUCTOR 37 | // 38 | INA226::INA226(const uint8_t address, TwoWire *wire) 39 | { 40 | _address = address; 41 | _wire = wire; 42 | // no calibrated values by default. 43 | _current_LSB = 0; 44 | _maxCurrent = 0; 45 | _shunt = 0; 46 | _error = 0; 47 | } 48 | 49 | 50 | bool INA226::begin() 51 | { 52 | if (! isConnected()) return false; 53 | return true; 54 | } 55 | 56 | 57 | bool INA226::isConnected() 58 | { 59 | _wire->beginTransmission(_address); 60 | return ( _wire->endTransmission() == 0); 61 | } 62 | 63 | 64 | uint8_t INA226::getAddress() 65 | { 66 | return _address; 67 | }; 68 | 69 | 70 | //////////////////////////////////////////////////////// 71 | // 72 | // CORE FUNCTIONS 73 | // 74 | float INA226::getBusVoltage() 75 | { 76 | uint16_t val = _readRegister(INA226_BUS_VOLTAGE); 77 | // return val * 1.25e-3 * _bus_V_scaling_e4 / 10000; // fixed 1.25 mV 78 | float voltage = val * 1.25e-3; 79 | if (_bus_V_scaling_e4 != 10000) 80 | { 81 | voltage *= _bus_V_scaling_e4 * 1.0e-4; 82 | } 83 | return voltage; 84 | } 85 | 86 | 87 | float INA226::getShuntVoltage() 88 | { 89 | int16_t val = _readRegister(INA226_SHUNT_VOLTAGE); 90 | return val * 2.5e-6; // fixed 2.50 uV 91 | } 92 | 93 | 94 | float INA226::getCurrent() 95 | { 96 | int16_t val = _readRegister(INA226_CURRENT); 97 | return val * _current_LSB - _current_zero_offset; 98 | } 99 | 100 | 101 | float INA226::getPower() 102 | { 103 | uint16_t val = _readRegister(INA226_POWER); 104 | return val * (_current_LSB * 25); // fixed 25 Watt 105 | } 106 | 107 | 108 | bool INA226::isConversionReady() 109 | { 110 | uint16_t mask = _readRegister(INA226_MASK_ENABLE); 111 | return (mask & INA226_CONVERSION_READY_FLAG) == INA226_CONVERSION_READY_FLAG; 112 | } 113 | 114 | 115 | bool INA226::waitConversionReady(uint32_t timeout) 116 | { 117 | uint32_t start = millis(); 118 | while ( (millis() - start) <= timeout) 119 | { 120 | if (isConversionReady()) return true; 121 | delay(1); // implicit yield(); 122 | } 123 | return false; 124 | } 125 | 126 | 127 | //////////////////////////////////////////////////////// 128 | // 129 | // CONFIGURATION 130 | // 131 | bool INA226::reset() 132 | { 133 | uint16_t mask = _readRegister(INA226_CONFIGURATION); 134 | mask |= INA226_CONF_RESET_MASK; 135 | uint16_t result = _writeRegister(INA226_CONFIGURATION, mask); 136 | // Serial.println(result); 137 | if (result != 0) return false; 138 | // reset calibration 139 | _current_LSB = 0; 140 | _maxCurrent = 0; 141 | _shunt = 0; 142 | return true; 143 | } 144 | 145 | 146 | bool INA226::setAverage(uint8_t avg) 147 | { 148 | if (avg > 7) return false; 149 | uint16_t mask = _readRegister(INA226_CONFIGURATION); 150 | mask &= ~INA226_CONF_AVERAGE_MASK; 151 | mask |= (avg << 9); 152 | _writeRegister(INA226_CONFIGURATION, mask); 153 | return true; 154 | } 155 | 156 | 157 | uint8_t INA226::getAverage() 158 | { 159 | uint16_t mask = _readRegister(INA226_CONFIGURATION); 160 | mask &= INA226_CONF_AVERAGE_MASK; 161 | mask >>= 9; 162 | return mask; 163 | } 164 | 165 | 166 | bool INA226::setBusVoltageConversionTime(uint8_t bvct) 167 | { 168 | if (bvct > 7) return false; 169 | uint16_t mask = _readRegister(INA226_CONFIGURATION); 170 | mask &= ~INA226_CONF_BUSVC_MASK; 171 | mask |= (bvct << 6); 172 | _writeRegister(INA226_CONFIGURATION, mask); 173 | return true; 174 | } 175 | 176 | 177 | uint8_t INA226::getBusVoltageConversionTime() 178 | { 179 | uint16_t mask = _readRegister(INA226_CONFIGURATION); 180 | mask &= INA226_CONF_BUSVC_MASK; 181 | mask >>= 6; 182 | return mask; 183 | } 184 | 185 | 186 | bool INA226::setShuntVoltageConversionTime(uint8_t svct) 187 | { 188 | if (svct > 7) return false; 189 | uint16_t mask = _readRegister(INA226_CONFIGURATION); 190 | mask &= ~INA226_CONF_SHUNTVC_MASK; 191 | mask |= (svct << 3); 192 | _writeRegister(INA226_CONFIGURATION, mask); 193 | return true; 194 | } 195 | 196 | 197 | uint8_t INA226::getShuntVoltageConversionTime() 198 | { 199 | uint16_t mask = _readRegister(INA226_CONFIGURATION); 200 | mask &= INA226_CONF_SHUNTVC_MASK; 201 | mask >>= 3; 202 | return mask; 203 | } 204 | 205 | 206 | //////////////////////////////////////////////////////// 207 | // 208 | // CALIBRATION 209 | // 210 | int INA226::setMaxCurrentShunt(float maxCurrent, float shunt, bool normalize) 211 | { 212 | // https://github.com/RobTillaart/INA226/pull/29 213 | 214 | // #define printdebug true 215 | 216 | // fix #16 - datasheet 6.5 Electrical Characteristics: 81.92 mV 217 | // rounded value to 80 mV 218 | // fix #49 - changed to 81.90 mV to have parameters (0.8, 0.1) working 219 | // 81.90 == datasheet limit - 0.02 mV to catch math overflow 220 | float shuntVoltage = maxCurrent * shunt; 221 | if (shuntVoltage > 0.08190) return INA226_ERR_SHUNTVOLTAGE_HIGH; 222 | if (maxCurrent < 0.001) return INA226_ERR_MAXCURRENT_LOW; 223 | if (shunt < INA226_MINIMAL_SHUNT_OHM) return INA226_ERR_SHUNT_LOW; 224 | 225 | _current_LSB = maxCurrent * 3.0517578125e-5; // maxCurrent / 32768; 226 | 227 | #ifdef printdebug 228 | Serial.println(); 229 | Serial.print("normalize:\t"); 230 | Serial.println(normalize ? " true" : " false"); 231 | Serial.print("initial current_LSB:\t"); 232 | Serial.print(_current_LSB * 1e+6, 1); 233 | Serial.println(" uA / bit"); 234 | #endif 235 | 236 | // normalize the LSB to a round number 237 | // LSB will increase 238 | if (normalize) 239 | { 240 | /* 241 | check if maxCurrent (normal) or shunt resistor 242 | (due to unusual low resistor values in relation to maxCurrent) determines currentLSB 243 | we have to take the upper value for currentLSB 244 | 245 | (adjusted in 0.6.0) 246 | calculation of currentLSB based on shunt resistor and calibration register limits (2 bytes) 247 | cal = 0.00512 / ( shunt * currentLSB ) 248 | cal(max) = 2^15-1 249 | currentLSB(min) = 0.00512 / ( shunt * cal(max) ) 250 | currentLSB(min) ~= 0.00512 / ( shunt * 2^15 ) 251 | currentLSB(min) ~= 2^9 * 1e-5 / ( shunt * 2^15 ) 252 | currentLSB(min) ~= 1e-5 / 2^6 / shunt 253 | currentLSB(min) ~= 1.5625e-7 / shunt 254 | */ 255 | if ( 1.5625e-7 / shunt > _current_LSB ) { 256 | // shunt resistor determines current_LSB 257 | // => take this a starting point for current_LSB 258 | _current_LSB = 1.5625e-7 / shunt; 259 | } 260 | 261 | #ifdef printdebug 262 | Serial.print("Pre-scale current_LSB:\t"); 263 | Serial.print(_current_LSB * 1e+6, 1); 264 | Serial.println(" uA / bit"); 265 | #endif 266 | 267 | // normalize _current_LSB to a value of 1, 2 or 5 * 1e-6 to 1e-3 268 | // convert float to int 269 | uint16_t currentLSB_uA = float(_current_LSB * 1e+6); 270 | currentLSB_uA++; // ceil() would be more precise, but uses 176 bytes of flash. 271 | 272 | uint16_t factor = 1; // 1uA to 1000uA 273 | uint8_t i = 0; // 1 byte loop reduces footprint 274 | bool result = false; 275 | do { 276 | if ( 1 * factor >= currentLSB_uA) { 277 | _current_LSB = 1 * factor * 1e-6; 278 | result = true; 279 | } else if ( 2 * factor >= currentLSB_uA) { 280 | _current_LSB = 2 * factor * 1e-6; 281 | result = true; 282 | } else if ( 5 * factor >= currentLSB_uA) { 283 | _current_LSB = 5 * factor * 1e-6; 284 | result = true; 285 | } else { 286 | factor *= 10; 287 | i++; 288 | } 289 | } while ( (i < 4) && (!result) ); // factor < 10000 290 | 291 | if (result == false) // not succeeded to normalize. 292 | { 293 | _current_LSB = 0; 294 | return INA226_ERR_NORMALIZE_FAILED; 295 | } 296 | 297 | #ifdef printdebug 298 | Serial.print("After scale current_LSB:\t"); 299 | Serial.print(_current_LSB * 1e+6, 1); 300 | Serial.println(" uA / bit"); 301 | #endif 302 | // done 303 | } 304 | 305 | // auto scale calibration if needed. 306 | uint32_t calib = round(0.00512 / (_current_LSB * shunt)); 307 | while (calib > 32767) 308 | { 309 | _current_LSB *= 2; 310 | calib >>= 1; 311 | } 312 | _writeRegister(INA226_CALIBRATION, calib); 313 | 314 | _maxCurrent = _current_LSB * 32768; 315 | _shunt = shunt; 316 | 317 | #ifdef printdebug 318 | Serial.println("\n***** INA 226 SET VALUES *****"); 319 | Serial.print("Shunt:\t"); 320 | Serial.print(_shunt, 4); 321 | Serial.println(" Ohm"); 322 | Serial.print("Current_LSB:\t"); 323 | Serial.print(_current_LSB * 1e+6, 1); 324 | Serial.println(" uA / bit"); 325 | Serial.print("Calibration:\t"); 326 | Serial.println(calib); 327 | Serial.print("Max Measurable Current:\t"); 328 | Serial.print(_maxCurrent, 3); 329 | Serial.println(" A"); 330 | Serial.print("maxShuntVoltage:\t"); 331 | Serial.print(shuntVoltage, 4); 332 | Serial.println(" Volt"); 333 | #endif 334 | 335 | return INA226_ERR_NONE; 336 | } 337 | 338 | 339 | int INA226::configure(float shunt, float current_LSB_mA, float current_zero_offset_mA, uint16_t bus_V_scaling_e4) 340 | { 341 | if (shunt < INA226_MINIMAL_SHUNT_OHM) return INA226_ERR_SHUNT_LOW; 342 | float maxCurrent = min((INA226_MAX_SHUNT_VOLTAGE / shunt), 32768 * current_LSB_mA * 1e-3); 343 | if (maxCurrent < 0.001) return INA226_ERR_MAXCURRENT_LOW; 344 | 345 | _shunt = shunt; 346 | _current_LSB = current_LSB_mA * 1e-3; 347 | _current_zero_offset = current_zero_offset_mA * 1e-3; 348 | _bus_V_scaling_e4 = bus_V_scaling_e4; 349 | _maxCurrent = maxCurrent; 350 | 351 | uint32_t calib = round(0.00512 / (_current_LSB * _shunt)); 352 | _writeRegister(INA226_CALIBRATION, calib); 353 | 354 | // #define printdebug 355 | 356 | #ifdef printdebug 357 | Serial.println("\n***** INA 226 USER SET VALUES *****"); 358 | Serial.print("Shunt:\t"); 359 | Serial.print(_shunt, 4); 360 | Serial.println(" Ohm"); 361 | Serial.print("current_LSB:\t"); 362 | Serial.print(_current_LSB * 1e+6, 1); 363 | Serial.println(" uA / bit"); 364 | Serial.print("Calibration:\t"); 365 | Serial.println(calib); 366 | Serial.print("Max Measurable Current:\t"); 367 | Serial.print(_maxCurrent, 3); 368 | Serial.println(" A"); 369 | Serial.print("maxShuntVoltage:\t"); 370 | Serial.print(_maxCurrent * _shunt, 4); 371 | Serial.println(" Volt"); 372 | #endif 373 | 374 | return INA226_ERR_NONE; 375 | } 376 | 377 | 378 | //////////////////////////////////////////////////////// 379 | // 380 | // OPERATING MODE 381 | // 382 | bool INA226::setMode(uint8_t mode) 383 | { 384 | if (mode > 7) return false; 385 | uint16_t config = _readRegister(INA226_CONFIGURATION); 386 | config &= ~INA226_CONF_MODE_MASK; 387 | config |= mode; 388 | _writeRegister(INA226_CONFIGURATION, config); 389 | return true; 390 | } 391 | 392 | 393 | uint8_t INA226::getMode() 394 | { 395 | uint16_t mode = _readRegister(INA226_CONFIGURATION); 396 | mode &= INA226_CONF_MODE_MASK; 397 | return mode; 398 | } 399 | 400 | 401 | //////////////////////////////////////////////////////// 402 | // 403 | // ALERT 404 | // 405 | bool INA226::setAlertRegister(uint16_t mask) 406 | { 407 | uint16_t result = _writeRegister(INA226_MASK_ENABLE, (mask & 0xFC00)); 408 | // Serial.println(result); 409 | if (result != 0) return false; 410 | return true; 411 | } 412 | 413 | 414 | uint16_t INA226::getAlertFlag() 415 | { 416 | return _readRegister(INA226_MASK_ENABLE) & 0x001F; 417 | } 418 | 419 | 420 | bool INA226::setAlertLimit(uint16_t limit) 421 | { 422 | uint16_t result = _writeRegister(INA226_ALERT_LIMIT, limit); 423 | // Serial.println(result); 424 | if (result != 0) return false; 425 | return true; 426 | } 427 | 428 | 429 | uint16_t INA226::getAlertLimit() 430 | { 431 | return _readRegister(INA226_ALERT_LIMIT); 432 | } 433 | 434 | 435 | //////////////////////////////////////////////////////// 436 | // 437 | // META INFORMATION 438 | // 439 | uint16_t INA226::getManufacturerID() 440 | { 441 | return _readRegister(INA226_MANUFACTURER); 442 | } 443 | 444 | 445 | uint16_t INA226::getDieID() 446 | { 447 | return _readRegister(INA226_DIE_ID); 448 | } 449 | 450 | 451 | //////////////////////////////////////////////////////// 452 | // 453 | // ERROR HANDLING 454 | // 455 | int INA226::getLastError() 456 | { 457 | int e = _error; 458 | _error = 0; 459 | return e; 460 | } 461 | 462 | 463 | //////////////////////////////////////////////////////// 464 | // 465 | // PRIVATE 466 | // 467 | uint16_t INA226::_readRegister(uint8_t reg) 468 | { 469 | _error = 0; 470 | _wire->beginTransmission(_address); 471 | _wire->write(reg); 472 | int n = _wire->endTransmission(); 473 | if (n != 0) 474 | { 475 | _error = -1; 476 | return 0; 477 | } 478 | 479 | uint16_t value = 0; 480 | if (2 == _wire->requestFrom(_address, (uint8_t)2)) 481 | { 482 | value = _wire->read(); 483 | value <<= 8; 484 | value |= _wire->read(); 485 | } 486 | else 487 | { 488 | _error = -2; 489 | return 0; 490 | } 491 | return value; 492 | } 493 | 494 | 495 | uint16_t INA226::_writeRegister(uint8_t reg, uint16_t value) 496 | { 497 | _wire->beginTransmission(_address); 498 | _wire->write(reg); 499 | _wire->write(value >> 8); 500 | _wire->write(value & 0xFF); 501 | int n = _wire->endTransmission(); 502 | if (n != 0) 503 | { 504 | _error = -1; 505 | } 506 | return n; 507 | } 508 | 509 | 510 | // -- END OF FILE -- -------------------------------------------------------------------------------- /src/Menu.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /** 4 | * Constructor 5 | * Pass in pointer to local variable to simplified future funtion calls 6 | */ 7 | Menu::Menu(U8G2_SSD1306_128X64_NONAME_F_HW_I2C *oled, AP33772 *usb, RotaryEncoder *encoder, Button *button_encoder, Button *button_output, Button *button_selectVI) 8 | { 9 | u8g2 = oled; 10 | usbpd = usb; 11 | _encoder = encoder; 12 | _button_encoder = button_encoder; 13 | _button_output = button_output; 14 | _button_selectVI = button_selectVI; 15 | 16 | menuPosition = 0; 17 | numPDO = 0; 18 | qc3_0available = 0; 19 | } 20 | 21 | /** 22 | * @brief Check if menuPosition is within range, wrap around if not 23 | * 24 | * @param wrapAround 25 | */ 26 | void Menu::checkMenuPosition(bool wrapAround) { 27 | if(usbpd) numPDO = usbpd->getNumPDO(); 28 | if (menuPosition < 0) { 29 | if(wrapAround) menuPosition = numPDO + qc3_0available - 1; // wrap around 30 | else menuPosition = 0; // reset to 0 if not wrapping around 31 | } 32 | if(menuPosition > (numPDO + qc3_0available - 1)) { 33 | menuPosition = 0; // wrap around 34 | } 35 | } 36 | 37 | /** 38 | * @brief Handle main menu page for selecting capability 39 | * 40 | */ 41 | void Menu::page_selectCapability() 42 | { 43 | uint8_t linelocation; 44 | static int val; 45 | 46 | if (val = (int8_t)_encoder->getDirection()) 47 | { 48 | menuPosition = menuPosition - val; 49 | checkMenuPosition(true); 50 | } 51 | 52 | u8g2->clearBuffer(); 53 | for (byte i = 0; i < usbpd->getNumPDO(); i++) 54 | { 55 | if (i != usbpd->getPPSIndex()) 56 | { 57 | linelocation = 9 * (i + 1); 58 | u8g2->setCursor(5, linelocation); 59 | u8g2->print("PDO: "); 60 | u8g2->print(usbpd->getPDOVoltage(i) / 1000.0, 0); 61 | u8g2->print("V @ "); 62 | u8g2->print(usbpd->getPDOMaxcurrent(i) / 1000.0, 0); 63 | u8g2->print("A"); 64 | } 65 | else if (usbpd->existPPS) 66 | { 67 | linelocation = 9 * (i + 1); 68 | u8g2->setCursor(5, linelocation); 69 | u8g2->print("PPS: "); 70 | u8g2->print(usbpd->getPPSMinVoltage(i) / 1000.0, 1); 71 | u8g2->print("V~"); 72 | u8g2->print(usbpd->getPPSMaxVoltage(i) / 1000.0, 1); 73 | u8g2->print("V @ "); 74 | u8g2->print(usbpd->getPPSMaxCurrent(i) / 1000.0, 0); 75 | u8g2->print("A"); 76 | } 77 | if (i == menuPosition) 78 | { 79 | u8g2->setCursor(0, linelocation); 80 | u8g2->print(">"); 81 | } 82 | } 83 | 84 | u8g2->sendBuffer(); 85 | } 86 | 87 | void Menu::page_bootProfile() 88 | { 89 | } 90 | 91 | // /** 92 | // * Call this function after usbpd.begin() 93 | // */ 94 | // void Menu::getnumPDO() 95 | // { 96 | // //Pass in AP33772 flag 97 | // numPDO = usbpd->getNumPDO(); 98 | // } -------------------------------------------------------------------------------- /src/StateMachine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Static object require separate implementation 5 | RotaryEncoder StateMachine::encoder(pin_encoder_A, pin_encoder_B, RotaryEncoder::LatchMode::FOUR3); 6 | bool StateMachine::timerFlag0 = false; // Need to initilize Static variable 7 | bool StateMachine::timerFlag1 = false; // Need to initilize Static variable 8 | 9 | /** 10 | * @brief Transistion conditions between states. Call in main.cpp 11 | */ 12 | void StateMachine::update() 13 | { 14 | auto now = millis(); 15 | auto elapsed = now - startTime; 16 | 17 | switch (state) 18 | { 19 | case State::BOOT: 20 | handleBootState(); 21 | if (elapsed >= BOOT_TO_OBTAIN_TIMEOUT) 22 | transitionTo(State::OBTAIN); 23 | 24 | break; 25 | 26 | case State::OBTAIN: 27 | handleObtainState(); 28 | if(encoder.getDirection() != RotaryEncoder::Direction::NOROTATION) { // Encoder rotation 29 | transitionTo(State::MENU); 30 | } else if (button_encoder.isButtonPressed() | button_output.isButtonPressed() | button_selectVI.isButtonPressed()) // Short press 31 | handleInitialMode(); 32 | else if (elapsed >= OBTAIN_TO_CAPDISPLAY_TIMEOUT) 33 | transitionTo(State::CAPDISPLAY); 34 | 35 | break; 36 | 37 | case State::CAPDISPLAY: 38 | handleDisplayCapState(); 39 | if(encoder.getDirection() != RotaryEncoder::Direction::NOROTATION) { // Encoder rotation 40 | transitionTo(State::MENU); 41 | } else if ((button_encoder.isButtonPressed() | button_output.isButtonPressed() | button_selectVI.isButtonPressed() | elapsed >= DISPLAYCCAP_TO_NORMAL_TIMEOUT)) // Short press + timeout 42 | handleInitialMode(); 43 | 44 | break; 45 | 46 | case State::NORMAL_PPS: 47 | handleNormalPPSState(); 48 | if (button_selectVI.longPressedFlag) // Long press isButtonPressed() is called inside the state 49 | { 50 | button_selectVI.clearLongPressedFlag(); 51 | transitionTo(State::MENU); 52 | } 53 | saveSettingsToEEPROM(); 54 | break; 55 | case State::NORMAL_PDO: 56 | handleNormalPDOState(); 57 | button_selectVI.isButtonPressed(); // Call to update long press flag 58 | if (button_selectVI.longPressedFlag) // Long press isButtonPressed() is called inside the state 59 | { 60 | button_selectVI.clearLongPressedFlag(); 61 | transitionTo(State::MENU); 62 | } 63 | saveSettingsToEEPROM(); 64 | break; 65 | case State::NORMAL_QC: 66 | handleNormalQCState(); 67 | if (button_selectVI.longPressedFlag) // Long press isButtonPressed() is called inside the state 68 | { 69 | button_selectVI.clearLongPressedFlag(); 70 | transitionTo(State::MENU); 71 | } 72 | saveSettingsToEEPROM(); 73 | break; 74 | 75 | case State::MENU: 76 | if(usbpd.getNumPDO() == 0) { 77 | transitionTo(State::NORMAL_PDO); 78 | break; // No PDO available, go back to NORMAL_PDO state 79 | } 80 | handleMenuState(); 81 | 82 | button_encoder.isButtonPressed(); 83 | button_output.isButtonPressed(); 84 | button_selectVI.isButtonPressed(); 85 | 86 | if (button_selectVI.longPressedFlag) // Long press 87 | { 88 | button_selectVI.clearLongPressedFlag(); 89 | transitionTo(State::NORMAL_PPS); 90 | } 91 | if (button_encoder.longPressedFlag) // Long press 92 | { 93 | button_encoder.clearLongPressedFlag(); 94 | if (menu.menuPosition == usbpd.getPPSIndex()) { 95 | transitionTo(State::NORMAL_PPS); 96 | } else { 97 | forceSave = true; 98 | transitionTo(State::NORMAL_PDO); 99 | } 100 | } 101 | break; 102 | } 103 | } 104 | 105 | /** 106 | * @return Return current state in string 107 | */ 108 | const char *StateMachine::getState() 109 | { 110 | switch (state) 111 | { 112 | case State::BOOT: 113 | return "BOOT"; 114 | case State::OBTAIN: 115 | return "OBTAIN"; 116 | case State::CAPDISPLAY: 117 | return "CAPABILITY"; 118 | case State::NORMAL_PPS: 119 | return "NORMAL_PPS"; 120 | case State::NORMAL_PDO: 121 | return "NORMAL_PDO"; 122 | case State::NORMAL_QC: 123 | return "NORMAL_QC"; 124 | case State::MENU: 125 | return "MENU"; 126 | default: 127 | return "UNKNOWN"; 128 | } 129 | } 130 | 131 | void StateMachine::componentInit() 132 | { 133 | Wire.setSDA(pin_SDA); 134 | Wire.setSCL(pin_SCL); 135 | 136 | attachInterrupt(digitalPinToInterrupt(pin_encoder_A), encoderISR, CHANGE); 137 | attachInterrupt(digitalPinToInterrupt(pin_encoder_B), encoderISR, CHANGE); 138 | 139 | pinMode(pin_button_outputSW, INPUT_PULLUP); 140 | pinMode(pin_button_selectVI, INPUT_PULLUP); 141 | 142 | pinMode(pin_output_Enable, OUTPUT); // Load Switch 143 | digitalWrite(pin_output_Enable, LOW); 144 | output_display_mode = OUTPUT_NORMAL; 145 | 146 | button_encoder.setDebounceTime(50); // set debounce time to 50 milliseconds 147 | button_output.setDebounceTime(50); 148 | button_selectVI.setDebounceTime(50); 149 | 150 | u8g2.begin(); 151 | ina226.begin(); 152 | // ina226.setMaxCurrentShunt(6, SENSERESISTOR); 153 | // configure(shunt, current_LSB_mA, current_zero_offset_mA, bus_V_scaling_e4) 154 | #ifdef HW1_0 155 | ina226.configure(0.01023, 0.25, 6.4, 9972); //Factory calibration 156 | #endif 157 | 158 | #ifdef HW1_1 159 | ina226.configure(0.00528, 0.25, 10.4, 9972); //Factory calibration 160 | #endif 161 | 162 | voltageIncrementIndex = 2; // 0= 20mV, 1 = 100mV, 2 = 1000mV 163 | currentIncrementIndex = 2; // 0= 50mA, 1 = 100mA, 2 = 1000mA 164 | 165 | // Set up 100ms timer using timer0 166 | hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM0); 167 | // Associate an interrupt handler with the ALARM_IRQ 168 | irq_set_exclusive_handler(ALARM_IRQ0, timerISR0); 169 | // Enable the alarm interrupt 170 | irq_set_enabled(ALARM_IRQ0, true); 171 | // Write the lower 32 bits of the target time to the alarm register, arming it. 172 | timer_hw->alarm[ALARM_NUM0] = timer_hw->timerawl + DELAY0; 173 | 174 | // Set up 1s timer using timer1 175 | hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM1); 176 | // Associate an interrupt handler with the ALARM_IRQ 177 | irq_set_exclusive_handler(ALARM_IRQ1, timerISR1); 178 | // Enable the alarm interrupt 179 | irq_set_enabled(ALARM_IRQ1, true); 180 | // Write the lower 32 bits of the target time to the alarm register, arming it. 181 | timer_hw->alarm[ALARM_NUM1] = timer_hw->timerawl + DELAY1; 182 | 183 | // Init EEPROM 184 | EEPROM.begin(EEPROM_SIZE); 185 | } 186 | 187 | // Only need to declare static in header files 188 | void StateMachine::encoderISR() 189 | { 190 | encoder.tick(); 191 | } 192 | 193 | // ISR for 33ms timer 194 | void StateMachine::timerISR0() 195 | { 196 | // Clear the alarm irq 197 | hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM0); 198 | 199 | // Reset the alarm register 200 | timer_hw->alarm[ALARM_NUM0] = timer_hw->timerawl + DELAY0; 201 | 202 | timerFlag0 = true; 203 | } 204 | 205 | // ISR for 500ms timer 206 | void StateMachine::timerISR1() 207 | { 208 | // Clear the alarm irq 209 | hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM1); 210 | 211 | // Reset the alarm register 212 | timer_hw->alarm[ALARM_NUM1] = timer_hw->timerawl + DELAY1; 213 | 214 | timerFlag1 = !timerFlag1; // toggle 215 | } 216 | 217 | /** 218 | * @brief Tasks for Boot state 219 | */ 220 | void StateMachine::handleBootState() 221 | { 222 | if (!bootInitialized) 223 | { 224 | //* BEGIN Only run once when entering the state */ 225 | // led.turnOn(); // Turn on the LED when entering the BOOT state 226 | componentInit(); 227 | printBootingScreen(); 228 | // ADD Check if connected to PC here 229 | //* END Only run once when entering the state */ 230 | bootInitialized = true; // Mark BOOT state as initialized 231 | Serial.println("Initialized BOOT state"); 232 | } 233 | //* BEGIN state routine */ 234 | // Add additional BOOT state routines here 235 | 236 | //* END state routine */ 237 | //Serial.println("Handling BOOT state"); 238 | } 239 | 240 | /** 241 | * @brief Tasks for Obtain state 242 | */ 243 | void StateMachine::handleObtainState() 244 | { 245 | if (!obtainInitialized) 246 | { 247 | //* BEGIN Only run once when entering the state */ 248 | usbpd.begin(); // Start pulling the PDOs from power supply 249 | menu.numPDO = usbpd.getNumPDO(); // Call after usbpd.begin() 250 | // ADD check QC3.0 code here 251 | //* END Only run once when entering the state */ 252 | bootInitialized = true; // Mark BOOT state as initialized 253 | Serial.println("Initialized BOOT state"); 254 | } 255 | //* BEGIN state routine */ 256 | // Add additional BOOT state routines here 257 | 258 | //* END state routine */ 259 | // Serial.println( "Handling BOOT state" ); 260 | } 261 | 262 | /** 263 | * @brief Tasks for DisplayCapability state 264 | */ 265 | void StateMachine::handleDisplayCapState() 266 | { 267 | if (!displayCapInitialized) 268 | { 269 | //* BEGIN Only run once when entering the state */ 270 | // led.turnOn(); // Turn on the LED when entering the BOOT state 271 | printProfile(); 272 | usbpd.setSupplyVoltageCurrent(targetVoltage, targetCurrent); 273 | 274 | //* END Only run once when entering the state */ 275 | displayCapInitialized = true; 276 | } 277 | // Add CAPABILITY state routines here 278 | // Serial.println( "Handling CAPABILITY state" ); 279 | } 280 | 281 | /** 282 | * @brief Tasks for Normal with PPS state 283 | */ 284 | void StateMachine::handleNormalPPSState() 285 | { 286 | // Run once 287 | if (!normalPPSInitialized) 288 | { 289 | //* BEGIN Only run once when entering the state */ 290 | 291 | 292 | // Load settings from EEPROM 293 | if(loadSettingsFromEEPROM(true)) { 294 | usbpd.checkVoltageCurrent(targetVoltage, targetCurrent); 295 | } else { 296 | targetVoltage = 5000; // Default start up voltage 297 | targetCurrent = 1000; // Default start up current 298 | } 299 | supply_adjust_mode = VOTLAGE_ADJUST; 300 | usbpd.setSupplyVoltageCurrent(targetVoltage, targetCurrent); 301 | 302 | //* END Only run once when entering the state */ 303 | normalPPSInitialized = true; 304 | Serial.println("Initialized NORMAL_PPS state"); 305 | } 306 | 307 | //* BEGIN state routine */ 308 | if (timerFlag0) // Timer with DELAY0 309 | { 310 | ina_current_ma = abs(ina226.getCurrent_mA()); 311 | vbus_voltage_mv = ina226.getBusVoltage_mV(); 312 | 313 | //Place before updateOLED to prevent voltage/current go out of bound, then recover. 314 | 315 | process_request_voltage_current(); 316 | 317 | float currentReading = 0; 318 | float voltageReading = 0; 319 | if (digitalRead(pin_output_Enable)){ 320 | currentReading = ina_current_ma / 1000.0; 321 | voltageReading = vbus_voltage_mv / 1000.0; 322 | } else { 323 | currentReading = 0; 324 | voltageReading = usbpd.readVoltage() / 1000.0; 325 | } 326 | 327 | // Update energy accumulation 328 | updateEnergyAccumulation(voltageReading, currentReading); 329 | 330 | if (output_display_mode == OUTPUT_ENERGY) 331 | { 332 | // Show energy tracking display 333 | updateOLED_Energy(voltageReading, currentReading); 334 | } 335 | else if (output_display_mode == OUTPUT_NORMAL) 336 | { 337 | // Show normal display with output enabled 338 | updateOLED(voltageReading, currentReading,true); 339 | } 340 | 341 | 342 | update_supply_mode(); 343 | timerFlag0 = false; 344 | } 345 | 346 | // Disable encoder button and selectVI button in energy display mode 347 | if (output_display_mode != OUTPUT_ENERGY) 348 | { 349 | if (button_encoder.isButtonPressed() == 1) 350 | { 351 | if (supply_adjust_mode == VOTLAGE_ADJUST) 352 | { 353 | // Make sure the valve loop around 354 | voltageIncrementIndex = (voltageIncrementIndex + 1) % (sizeof(voltageIncrement) / sizeof(int)); 355 | } 356 | else 357 | { 358 | currentIncrementIndex = (currentIncrementIndex + 1) % (sizeof(currentIncrement) / sizeof(int)); 359 | } 360 | } 361 | if (button_selectVI.isButtonPressed() == 1) 362 | { 363 | if (supply_adjust_mode == VOTLAGE_ADJUST) 364 | supply_adjust_mode = CURRENT_ADJUST; 365 | else 366 | supply_adjust_mode = VOTLAGE_ADJUST; 367 | } 368 | 369 | process_encoder_input(); 370 | } 371 | 372 | process_output_button(); 373 | 374 | //* END state routine */ 375 | // Serial.println( "Handling NORMAL_PPS state" ); 376 | } 377 | 378 | /** 379 | * @brief Tasks for Normal with fixed PDO state 380 | */ 381 | void StateMachine::handleNormalPDOState() 382 | { 383 | // Run once 384 | if (!normalPDOInitialized) 385 | { 386 | //* BEGIN Only run once when entering the state */ 387 | usbpd.setPDO(menu.menuPosition); 388 | // Now targetVoltage/targetCurrent is just for display. Do not pass over to AP33772 389 | targetVoltage = usbpd.getPDOVoltage(menu.menuPosition); // For display 390 | targetCurrent = usbpd.getPDOMaxcurrent(menu.menuPosition); // For display 391 | supply_adjust_mode = VOTLAGE_ADJUST; 392 | 393 | //* END Only run once when entering the state */ 394 | normalPDOInitialized = true; 395 | Serial.println(usbpd.getNumPDO()); 396 | Serial.println("Initialized NORMAL_PDO state"); 397 | } 398 | 399 | //* BEGIN state routine */ 400 | float ina_current_ma = abs(ina226.getCurrent_mA()); 401 | 402 | if (timerFlag0) 403 | { 404 | ina_current_ma = abs(ina226.getCurrent_mA()); 405 | vbus_voltage_mv = ina226.getBusVoltage_mV(); 406 | 407 | float currentReading = 0; 408 | float voltageReading = 0; 409 | if (digitalRead(pin_output_Enable)){ 410 | currentReading = ina_current_ma / 1000.0; 411 | voltageReading = vbus_voltage_mv / 1000.0; 412 | } else { 413 | currentReading = 0; 414 | voltageReading = usbpd.readVoltage() / 1000.0; 415 | } 416 | // Update energy accumulation 417 | updateEnergyAccumulation(voltageReading, currentReading); 418 | 419 | if (output_display_mode == OUTPUT_ENERGY) 420 | { 421 | // Show energy tracking display 422 | updateOLED_Energy(voltageReading, currentReading); 423 | } 424 | else if (output_display_mode == OUTPUT_NORMAL) 425 | { 426 | // Show normal display with output enabled 427 | updateOLED(voltageReading, currentReading,true); 428 | } 429 | update_supply_mode(); 430 | timerFlag0 = false; 431 | } 432 | 433 | process_output_button(); 434 | 435 | //* END state routine */ 436 | // Serial.println( "Handling NORMAL_PDO state" ); 437 | } 438 | 439 | /** 440 | * @brief tasks for Normal with QC3.0 state 441 | */ 442 | void StateMachine::handleNormalQCState() 443 | { 444 | // Run once 445 | if (!normalQCInitialized) 446 | { 447 | //* BEGIN Only run once when entering the state */ 448 | // TODO QC implmentation 449 | //* END Only run once when entering the state */ 450 | normalQCInitialized = true; 451 | Serial.println("Initialized NORMAL_QC state"); 452 | } 453 | 454 | //* BEGIN state routine */ 455 | float ina_current_ma = abs(ina226.getCurrent_mA()); 456 | 457 | if (timerFlag0) 458 | { 459 | 460 | vbus_voltage_mv = ina226.getBusVoltage_mV(); 461 | 462 | // Update energy accumulation 463 | float currentReading = 0; 464 | float voltageReading = 0; 465 | if (digitalRead(pin_output_Enable)){ 466 | currentReading = ina_current_ma / 1000.0; 467 | voltageReading = vbus_voltage_mv / 1000.0; 468 | } else { 469 | currentReading = 0; 470 | voltageReading = usbpd.readVoltage() / 1000.0; 471 | } 472 | // Update energy accumulation 473 | updateEnergyAccumulation(voltageReading, currentReading); 474 | 475 | if (output_display_mode == OUTPUT_ENERGY) 476 | { 477 | // Show energy tracking display 478 | updateOLED_Energy(voltageReading, currentReading); 479 | } 480 | else if (output_display_mode == OUTPUT_NORMAL) 481 | { 482 | // Show normal display with output enabled 483 | updateOLED(voltageReading, currentReading,true); 484 | } 485 | timerFlag0 = false; 486 | } 487 | 488 | process_output_button(); 489 | // TODO QC implmentation 490 | 491 | //* END state routine */ 492 | // Serial.println( "Handling NORMAL_QC state" ); 493 | } 494 | 495 | /** 496 | * @brief Tasks for Menu state. Just a wrapper function 497 | */ 498 | void StateMachine::handleMenuState() 499 | { 500 | //* BEGIN state routine */ 501 | menu.page_selectCapability(); 502 | //* END state routine */ 503 | // Check out https://github.com/shuzonudas/monoview/blob/master/U8g2/Examples/Menu/simpleMenu/simpleMenu.ino 504 | // Serial.println( "Handling MENU state" ); 505 | } 506 | 507 | /** 508 | * @brief transitionTo perform flags clear before transisiton. Enable entry tasks in each state. 509 | * @param State::newstate input the next state to transistion 510 | */ 511 | void StateMachine::transitionTo(State newState) 512 | { 513 | // Ex: Print: Transitioning from BOOT to CAPABILITY 514 | Serial.print("Transitioning from "); 515 | Serial.print(getState()); 516 | Serial.print("to "); 517 | Serial.println((newState == State::BOOT ? "BOOT" : newState == State::CAPDISPLAY ? "CAPABILITY" 518 | : newState == State::NORMAL_PPS ? "NORMAL" 519 | : "MENU")); 520 | 521 | state = newState; 522 | startTime = millis(); 523 | 524 | if (newState == State::BOOT) 525 | { 526 | bootInitialized = false; // Reset the initialization flag when entering BOOT 527 | } 528 | if (newState == State::OBTAIN) 529 | { 530 | obtainInitialized = false; // Reset the initialization flag when entering OBTAIN 531 | } 532 | if (newState == State::CAPDISPLAY) 533 | { 534 | displayCapInitialized = false; // Reset the initialization flag when entering CAPDISPLAY 535 | } 536 | if (newState == State::NORMAL_PPS || 537 | newState == State::NORMAL_PDO || 538 | newState == State::NORMAL_QC || 539 | newState == State::MENU) 540 | { 541 | normalPPSInitialized = false; // Reset the initialization flag when entering CAPDISPLAY 542 | normalPDOInitialized = false; 543 | normalQCInitialized = false; 544 | } 545 | buttonPressed = false; // Reset button press flag when transitioning to another state 546 | } 547 | 548 | void StateMachine::printBootingScreen() 549 | { 550 | u8g2.clearBuffer(); 551 | u8g2.drawBitmap(0, 0, 128 / 8, 64, image_PocketPD_Logo); 552 | u8g2.setFont(u8g2_font_profont12_tr); 553 | u8g2.drawStr(67,64, "FW: "); 554 | u8g2.drawStr(87,64, VERSION); 555 | u8g2.sendBuffer(); 556 | } 557 | 558 | /** 559 | * @brief Update value on OLED screen 560 | * @param voltage voltage (mV) to display, big text 561 | * @param current current (mA) to display, big text 562 | * @param requestEN flag to show/not show smaller text. Used for PPS request. 563 | */ 564 | void StateMachine::updateOLED(float voltage, float current, uint8_t requestEN) 565 | { 566 | // TODO Can optimize away requestEN 567 | u8g2.clearBuffer(); 568 | u8g2.setFontMode(1); 569 | u8g2.setBitmapMode(1); 570 | 571 | // Start-Fixed Component 572 | u8g2.setFont(u8g2_font_profont22_tr); 573 | u8g2.drawStr(1, 14, "V"); 574 | u8g2.drawStr(1, 47, "A"); 575 | u8g2.setFont(u8g2_font_profont12_tr); 576 | switch (state) 577 | { 578 | case State::NORMAL_PPS: 579 | u8g2.drawStr(110, 62, "PPS"); 580 | break; 581 | case State::NORMAL_PDO: 582 | u8g2.drawStr(110, 62, "PDO"); 583 | break; 584 | case State::NORMAL_QC: 585 | u8g2.drawStr(110, 62, "QC3.0"); 586 | break; 587 | } 588 | // End-Fixed Component 589 | 590 | // Start-Dynamic component 591 | u8g2.setFont(u8g2_font_profont22_tr); 592 | sprintf(buffer, "%.2f", voltage); 593 | u8g2.drawStr(75 - u8g2.getStrWidth(buffer), 14, buffer); // Right adjust 594 | sprintf(buffer, "%.2f", current); 595 | u8g2.drawStr(75 - u8g2.getStrWidth(buffer), 47, buffer); // Right adjust 596 | 597 | // https://github.com/olikraus/u8g2/discussions/2028 598 | if (requestEN == 1) 599 | { 600 | u8g2.setFont(u8g2_font_profont15_tr); 601 | sprintf(buffer, "%d mV", targetVoltage); 602 | u8g2.drawStr(75 - u8g2.getStrWidth(buffer), 27, buffer); // Right adjust 603 | sprintf(buffer, "%d mA", targetCurrent); 604 | u8g2.drawStr(75 - u8g2.getStrWidth(buffer), 62, buffer); // Right adjust 605 | } 606 | 607 | if (supply_mode) // CV = 0, CC = 1 608 | { 609 | u8g2.setFont(u8g2_font_profont12_tr); 610 | u8g2.drawStr(110, 10, "CV"); 611 | u8g2.drawStr(110, 23, "CC"); 612 | u8g2.setDrawColor(2); 613 | u8g2.drawBox(104, 13, 23, 12); // CC 614 | } 615 | else 616 | { 617 | u8g2.setFont(u8g2_font_profont12_tr); 618 | u8g2.drawStr(110, 10, "CV"); 619 | u8g2.drawStr(110, 23, "CC"); 620 | u8g2.setDrawColor(2); 621 | u8g2.drawBox(104, 0, 23, 12); // CV 622 | } 623 | // End-Dynamic component 624 | 625 | // Blinking cursor 626 | if (timerFlag1 && (state == State::NORMAL_PPS || state == State::NORMAL_QC)) 627 | { 628 | if (supply_adjust_mode == VOTLAGE_ADJUST) 629 | u8g2.drawLine(voltage_cursor_position[voltageIncrementIndex], 28, voltage_cursor_position[voltageIncrementIndex] + 5, 28); 630 | else 631 | u8g2.drawLine(current_cursor_position[currentIncrementIndex], 63, current_cursor_position[currentIncrementIndex] + 5, 63); 632 | } 633 | 634 | // Blinking output arrow 635 | if(digitalRead(pin_output_Enable) == 1) 636 | { 637 | u8g2.drawXBMP(105, 28, 20, 20, arrow_bitmapallArray[counter_gif]); // draw arrow animation 638 | counter_gif = (counter_gif + 1) % 28; 639 | } 640 | 641 | u8g2.sendBuffer(); 642 | } 643 | 644 | /** 645 | * @brief Update energy accumulation (Wh and Ah) - continuous tracking, only resets on power cycle 646 | */ 647 | void StateMachine::updateEnergyAccumulation(float voltage, float current) 648 | { 649 | if (digitalRead(pin_output_Enable) == 1) 650 | { 651 | unsigned long currentTime = millis(); 652 | 653 | // Start new session if needed 654 | if (currentSessionStartTime == 0) 655 | { 656 | currentSessionStartTime = currentTime; 657 | lastEnergyUpdate = currentTime; 658 | return; 659 | } 660 | 661 | // Accumulate energy since last update - ALWAYS accumulate when output enabled 662 | unsigned long deltaMs = currentTime - lastEnergyUpdate; 663 | 664 | // Only accumulate if we have a reasonable delta (not first call edge case, not huge jump) 665 | if (deltaMs > 0 && deltaMs < 1000) 666 | { // Between 0ms and 1 second 667 | if (!isfinite(voltage) || !isfinite(current)) 668 | { 669 | lastEnergyUpdate = currentTime; 670 | return; 671 | } 672 | 673 | double voltage_v = static_cast(voltage); 674 | double current_a = static_cast(current); 675 | 676 | if (voltage_v >= 0.0 && current_a >= 0.0) 677 | { 678 | double power_w = voltage_v * current_a; 679 | double deltaHours = static_cast(deltaMs) / 3600000.0; 680 | 681 | accumulatedWh += power_w * deltaHours; 682 | accumulatedAh += current_a * deltaHours; 683 | } 684 | } 685 | 686 | lastEnergyUpdate = currentTime; 687 | } 688 | else 689 | { 690 | // Output disabled - save current session time to historical 691 | if (currentSessionStartTime > 0) 692 | { 693 | unsigned long sessionDuration = (millis() - currentSessionStartTime) / 1000; 694 | historicalOutputTime += sessionDuration; 695 | currentSessionStartTime = 0; 696 | } 697 | lastEnergyUpdate = 0; 698 | } 699 | } 700 | 701 | /** 702 | * @brief Energy tracking display - shows Wh/Ah with compact V/A at top 703 | */ 704 | void StateMachine::updateOLED_Energy(float voltage, float current) 705 | { 706 | u8g2.clearBuffer(); 707 | u8g2.setDrawColor(1); 708 | u8g2.setFontMode(1); 709 | u8g2.setBitmapMode(1); 710 | 711 | // Top section - Compact real-time V/A 712 | u8g2.setFont(u8g2_font_profont12_tr); 713 | sprintf(buffer, "%.2fV", voltage); 714 | u8g2.drawStr(2, 10, buffer); 715 | sprintf(buffer, "%.2fA", current); 716 | u8g2.drawStr(48, 10, buffer); 717 | 718 | // CV/CC indicator (same size and alignment as V/A) 719 | if (supply_mode == MODE_CC) 720 | { 721 | u8g2.drawStr(94, 10, "CC"); 722 | } 723 | else 724 | { 725 | u8g2.drawStr(94, 10, "CV"); 726 | } 727 | 728 | // Animated arrow (only when output is enabled) 729 | if (digitalRead(pin_output_Enable) == 1) 730 | { 731 | u8g2.drawXBMP(108, 0, 20, 20, arrow_bitmapallArray[counter_gif]); 732 | counter_gif = (counter_gif + 1) % 28; 733 | } 734 | 735 | // Calculate total time: historical + current session 736 | unsigned long totalSeconds = historicalOutputTime; 737 | if (currentSessionStartTime > 0) 738 | { 739 | totalSeconds += (millis() - currentSessionStartTime) / 1000; 740 | } 741 | 742 | // Format time based on totalSeconds (only increments when outputting) 743 | char timeBuffer[10]; 744 | if (totalSeconds < 60) 745 | { 746 | // Under 1 minute: "00:SS" 747 | sprintf(timeBuffer, "00:%02lu", totalSeconds); 748 | } 749 | else if (totalSeconds < 3600) 750 | { 751 | // Under 1 hour: "MM:SS" 752 | sprintf(timeBuffer, "%02lu:%02lu", totalSeconds / 60, totalSeconds % 60); 753 | } 754 | else if (totalSeconds < 86400) 755 | { 756 | // Under 1 day: "#h##m" (e.g., "2h45m") 757 | unsigned long hours = totalSeconds / 3600; 758 | unsigned long minutes = (totalSeconds % 3600) / 60; 759 | sprintf(timeBuffer, "%luh%02lum", hours, minutes); 760 | } 761 | else 762 | { 763 | // 1+ days: "#d##h" (e.g., "3d12h") 764 | unsigned long days = totalSeconds / 86400; 765 | unsigned long hours = (totalSeconds % 86400) / 3600; 766 | sprintf(timeBuffer, "%lud%02luh", days, hours); 767 | } 768 | 769 | // Use larger font for main data rows 770 | u8g2.setFont(u8g2_font_profont17_tr); 771 | 772 | // Left column: Watts (no decimal when >= 100) 773 | float watts = voltage * current; 774 | if (watts >= 100.0) 775 | { 776 | sprintf(buffer, "%.0f", watts); 777 | } 778 | else 779 | { 780 | sprintf(buffer, "%.1f", watts); 781 | } 782 | u8g2.drawStr(2, 35, buffer); 783 | int watts_width = u8g2.getStrWidth(buffer); 784 | u8g2.drawStr(2 + watts_width + 2, 35, "W"); // 2px spacing 785 | 786 | // Time (more spacing from watts) 787 | u8g2.drawStr(2, 55, timeBuffer); 788 | 789 | const double totalWh = accumulatedWh; 790 | const double totalAh = accumulatedAh; 791 | 792 | // Right column: Wh with proper significant digits 793 | if (totalWh < 10.0) 794 | { 795 | sprintf(buffer, "%.2f", totalWh); 796 | } 797 | else if (totalWh < 100.0) 798 | { 799 | sprintf(buffer, "%.1f", totalWh); 800 | } 801 | else 802 | { 803 | sprintf(buffer, "%.0f", totalWh); 804 | } 805 | u8g2.drawStr(70, 35, buffer); 806 | int wh_width = u8g2.getStrWidth(buffer); 807 | u8g2.drawStr(70 + wh_width + 2, 35, "Wh"); // 2px spacing 808 | 809 | // Ah with proper significant digits (more spacing) 810 | if (totalAh < 10.0) 811 | { 812 | sprintf(buffer, "%.2f", totalAh); 813 | } 814 | else if (totalAh < 100.0) 815 | { 816 | sprintf(buffer, "%.1f", totalAh); 817 | } 818 | else 819 | { 820 | sprintf(buffer, "%.0f", totalAh); 821 | } 822 | u8g2.drawStr(70, 55, buffer); 823 | int ah_width = u8g2.getStrWidth(buffer); 824 | u8g2.drawStr(70 + ah_width + 2, 55, "Ah"); // 2px spacing 825 | 826 | // Reset font to default 827 | u8g2.setFont(u8g2_font_profont12_tr); 828 | u8g2.sendBuffer(); 829 | } 830 | 831 | /** Need fixing */ 832 | void StateMachine::update_supply_mode() 833 | { 834 | //Serial.println("Dumping start "); 835 | //Serial.println(targetVoltage); 836 | //Serial.println(vbus_voltage_mv); 837 | //Serial.println(ina_current_ma); 838 | //Serial.println(targetCurrent); 839 | if(state == State::NORMAL_PPS && digitalRead(pin_output_Enable) && 840 | (targetVoltage >= vbus_voltage_mv + 50 + ina_current_ma*(0.166 + 0.022 + 0.005)) && 841 | (ina_current_ma >= targetCurrent - 150 && ina_current_ma <= targetCurrent + 150)) //Current read when hitting limit, need to be around 85-115% limit set 842 | supply_mode = MODE_CC; 843 | else // Other state only display CV mode 844 | supply_mode = MODE_CV; 845 | } 846 | 847 | /** 848 | * @brief Print out PPS/PDO profiles onto OLED 849 | */ 850 | void StateMachine::printProfile() 851 | { 852 | int linelocation = 9; 853 | /** 854 | * Print out PPS if exist, else, print out PDOs 855 | * Missing: cannot display multiple PPS profile 856 | */ 857 | u8g2.clearBuffer(); 858 | 859 | if (usbpd.getNumPDO() == 0) 860 | { 861 | u8g2.setFontMode(1); 862 | u8g2.setFont(u8g2_font_6x13_tr); 863 | u8g2.drawStr(8, 34, "No Profile Detected"); 864 | u8g2.sendBuffer(); 865 | return; 866 | } 867 | 868 | u8g2.setFontMode(1); 869 | u8g2.setBitmapMode(1); 870 | u8g2.setFont(u8g2_font_profont11_tr); 871 | // Print PDOs onto OLED 872 | for (byte i = 0; i < usbpd.getNumPDO(); i++) 873 | { 874 | if (i != usbpd.getPPSIndex()) 875 | { 876 | linelocation = 9 * (i + 1); 877 | u8g2.setCursor(5, linelocation); 878 | u8g2.print("PDO: "); 879 | u8g2.print(usbpd.getPDOVoltage(i) / 1000.0, 0); 880 | u8g2.print("V @ "); 881 | u8g2.print(usbpd.getPDOMaxcurrent(i) / 1000.0, 0); 882 | u8g2.print("A"); 883 | } 884 | else if (usbpd.existPPS) 885 | { 886 | linelocation = 9 * (i + 1); 887 | u8g2.setCursor(5, linelocation); 888 | u8g2.print("PPS: "); 889 | u8g2.print(usbpd.getPPSMinVoltage(i) / 1000.0, 1); 890 | u8g2.print("V~"); 891 | u8g2.print(usbpd.getPPSMaxVoltage(i) / 1000.0, 1); 892 | u8g2.print("V @ "); 893 | u8g2.print(usbpd.getPPSMaxCurrent(i) / 1000.0, 0); 894 | u8g2.print("A"); 895 | } 896 | } 897 | u8g2.sendBuffer(); 898 | } 899 | 900 | /** 901 | * @brief Read changes from encoder, compute target voltage/current 902 | * Just update targetVoltage and targetCurrent 903 | */ 904 | void StateMachine::process_encoder_input() 905 | { 906 | static int val; 907 | if (val = (int8_t)encoder.getDirection()) 908 | { 909 | if (supply_adjust_mode == VOTLAGE_ADJUST) //If cursor is at voltage 910 | { 911 | targetVoltage = targetVoltage + val * voltageIncrement[voltageIncrementIndex]; 912 | } 913 | else //If cursor is at current 914 | { 915 | targetCurrent = targetCurrent + val * currentIncrement[currentIncrementIndex]; 916 | } 917 | updateSaveStamp(); // Update the save stamp to current time to debounce too many writes to EEPROM 918 | } 919 | } 920 | 921 | /** 922 | * @brief Handle output button press 923 | * Short press: Toggle output on/off (never changes display mode) 924 | * Long press: Toggle between NORMAL and ENERGY display modes (never changes output) 925 | */ 926 | void StateMachine::process_output_button() 927 | { 928 | int buttonState = button_output.isButtonPressed(); 929 | 930 | // Check for long press flag first 931 | if (button_output.longPressedFlag) 932 | { 933 | button_output.clearLongPressedFlag(); 934 | // Toggle between NORMAL and ENERGY display modes (output state unchanged) 935 | if (output_display_mode == OUTPUT_NORMAL) 936 | { 937 | output_display_mode = OUTPUT_ENERGY; 938 | } 939 | else // OUTPUT_ENERGY 940 | { 941 | output_display_mode = OUTPUT_NORMAL; 942 | } 943 | } 944 | else if (buttonState == 1) // Short press 945 | { 946 | // Toggle output on/off (display mode unchanged) 947 | if (digitalRead(pin_output_Enable) == LOW) // Currently off 948 | { 949 | digitalWrite(pin_output_Enable, HIGH); 950 | } 951 | else // Currently on 952 | { 953 | digitalWrite(pin_output_Enable, LOW); 954 | } 955 | } 956 | } 957 | 958 | /** 959 | * @brief Read changes from encoder, compute request voltage/current 960 | * Sent request to AP33772 to update 961 | */ 962 | void StateMachine::process_request_voltage_current() 963 | { 964 | static int val; 965 | if (supply_adjust_mode == VOTLAGE_ADJUST) 966 | { 967 | if (usbpd.existPPS) 968 | { 969 | if ((float)usbpd.getPPSMinVoltage(usbpd.getPPSIndex()) <= targetVoltage && (float)usbpd.getPPSMaxVoltage(usbpd.getPPSIndex()) >= targetVoltage) 970 | // usbpd.setVoltage(targetVoltage); 971 | usbpd.setSupplyVoltageCurrent(targetVoltage, targetCurrent); 972 | else if (usbpd.getPPSMinVoltage(usbpd.getPPSIndex()) > targetVoltage) 973 | targetVoltage = usbpd.getPPSMinVoltage(usbpd.getPPSIndex()); // No change 974 | else if (usbpd.getPPSMaxVoltage(usbpd.getPPSIndex()) < targetVoltage) 975 | targetVoltage = usbpd.getPPSMaxVoltage(usbpd.getPPSIndex()); // No change 976 | } 977 | else 978 | { // PDOs only has profile between 5V and 20V 979 | if (targetVoltage > 20000) 980 | targetVoltage = 20000; 981 | else if (targetVoltage < 5000) 982 | targetVoltage = 5000; 983 | usbpd.setVoltage(targetVoltage); 984 | } 985 | } 986 | else 987 | { 988 | if (usbpd.existPPS) 989 | { 990 | if (targetCurrent <= 1000) 991 | targetCurrent = 1000; // Cap at 100mA minimum, no current update 992 | else if (usbpd.getPPSMaxCurrent(usbpd.getPPSIndex()) >= targetCurrent) 993 | // usbpd.setMaxCurrent(targetCurrent); 994 | usbpd.setSupplyVoltageCurrent(targetVoltage, targetCurrent); 995 | else if (usbpd.getPPSMaxCurrent(usbpd.getPPSIndex()) < targetCurrent) 996 | targetCurrent = usbpd.getPPSMaxCurrent(usbpd.getPPSIndex()); // No change 997 | } 998 | else 999 | targetCurrent = usbpd.getMaxCurrent(); // Pull current base on current PDO 1000 | } 1001 | } 1002 | 1003 | /** 1004 | * @brief Handle the initial mode when the device starts up 1005 | * This function checks if settings can be loaded from EEPROM and transitions to the appropriate state. 1006 | */ 1007 | void StateMachine::handleInitialMode() { 1008 | if(loadSettingsFromEEPROM(false)) { 1009 | if(menu.menuPosition == usbpd.getPPSIndex()) 1010 | transitionTo(State::NORMAL_PPS); 1011 | else 1012 | transitionTo(State::NORMAL_PDO); 1013 | } else { 1014 | if(usbpd.existPPS) 1015 | transitionTo(State::NORMAL_PPS); 1016 | else 1017 | transitionTo(State::NORMAL_PDO); 1018 | } 1019 | } 1020 | 1021 | /** 1022 | * @brief Save settings to EEPROM 1023 | * @return true if settings were saved successfully, false otherwise 1024 | */ 1025 | bool StateMachine::saveSettingsToEEPROM() { 1026 | uint32_t currentTime = millis(); 1027 | bool ret = false; 1028 | bool saveVC = state == State::NORMAL_PPS; // Check if we are in NORMAL_PPS state to save PPS settings 1029 | 1030 | if(forceSave || (currentTime - saveStamp >= EEPROM_SAVE_INTERVAL)) { 1031 | saveStamp = currentTime; 1032 | if(usbpd.getNumPDO() == 0) { 1033 | Serial.println("No PDOs available, cannot save settings."); 1034 | return false; // No PDOs available, cannot save settings 1035 | } 1036 | Settings newSettings = { 0 }; 1037 | eepromHandler.loadSettings(newSettings); // Load current settings from EEPROM 1038 | if(saveVC) { 1039 | newSettings.targetVoltage = targetVoltage; 1040 | newSettings.targetCurrent = targetCurrent; 1041 | int32_t menuIndex = menu.menuPosition; 1042 | if(menuIndex == 0) { menuIndex = usbpd.getPPSIndex(); } // If menu position is 0, use PPS index 1043 | newSettings.menuPosition = menuIndex; // Save current menu position 1044 | } else { 1045 | newSettings.menuPosition = menu.menuPosition; // Save current menu position 1046 | } 1047 | 1048 | ret = eepromHandler.saveSettings(newSettings); 1049 | Serial.printf("Settings saved to EEPROM: %d\n\r", ret); 1050 | forceSave = !ret; // If save failed, set forceSave to true to retry next time 1051 | } 1052 | return ret; 1053 | } 1054 | 1055 | /** 1056 | * @brief Load settings from EEPROM 1057 | * @param vc If true, load Voltage/Current settings, otherwise load menu position 1058 | * @return true if settings were loaded successfully, false otherwise 1059 | */ 1060 | bool StateMachine::loadSettingsFromEEPROM(bool vc) { 1061 | Settings loadedSettings; 1062 | bool ret = eepromHandler.loadSettings(loadedSettings); 1063 | if(ret) { 1064 | if(!vc) { 1065 | menu.menuPosition = loadedSettings.menuPosition; // Load menu position 1066 | menu.checkMenuPosition(false); 1067 | } else { 1068 | targetVoltage = loadedSettings.targetVoltage; 1069 | targetCurrent = loadedSettings.targetCurrent; 1070 | } 1071 | Serial.printf("Settings loaded from EEPROM: mV=%4d, mI=%4d, Menu position=%d\n\r", 1072 | targetVoltage, targetCurrent, menu.menuPosition); 1073 | } else { 1074 | Serial.println("Failed to load settings from EEPROM."); 1075 | } 1076 | return ret; 1077 | } 1078 | 1079 | /** 1080 | * @brief Debounce the save stamp 1081 | * 1082 | */ 1083 | void StateMachine::updateSaveStamp() { 1084 | saveStamp = millis(); // Update the save stamp to current time 1085 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | StateMachine statemachine; 5 | 6 | void setup() 7 | { 8 | } 9 | 10 | void loop() 11 | { 12 | statemachine.update(); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /test/calibration.cpp: -------------------------------------------------------------------------------- 1 | // FILE: INA226_calibrate.ino 2 | // AUTHOR: Prashant Kumar 3 | // PURPOSE: accurate calibration of shunt resistance, current zero offset, bus voltage scaling and full user control 4 | // url: https://github.com/pk17r/INA226 5 | 6 | #include "INA226.h" 7 | 8 | INA226 INA(0x40); 9 | 10 | void setup() 11 | { 12 | Serial.begin(115200); 13 | delay(4000); 14 | Serial.println(__FILE__); 15 | Serial.print("INA226_LIB_VERSION: "); 16 | Serial.println(INA226_LIB_VERSION); 17 | 18 | pinMode(1, OUTPUT); // Load Switch 19 | digitalWrite(1, HIGH); 20 | 21 | Wire.setSCL(5); 22 | Wire.setSDA(4); 23 | Wire.begin(); 24 | if (!INA.begin()) 25 | { 26 | Serial.println("could not connect. Fix and Reboot"); 27 | } 28 | 29 | /* STEPS TO CALIBRATE INA226 30 | * 1. Set shunt equal to shunt resistance in ohms. This is the shunt resistance between IN+ and IN- pins of INA226 in your setup. 31 | * 2. Set current_LSB_mA (Current Least Significant Bit mA) equal to your desired least count resolution for IOUT in milli amps. Expected value to be in multiples of 0.050 o 0.010. Recommended values: 0.050, 0.100, 0.250, 0.500, 1, 2, 2.5 (in milli Ampere units). 32 | * 3. Set current_zero_offset_mA = 0, bus_V_scaling_e4 = 10000. 33 | * 4. Build firmware and flash microcontroller. 34 | * 5. Attach a power supply with voltage 5-10V to INA226 on VBUS/IN+ and GND pins, without any load. 35 | * 6. Start Serial Monitor and note Current values. Update current_zero_offset_mA = current_zero_offset_mA + average of 10 Current values in milli Amperes. 36 | * NOTE: Following adjustments shouldn't change values by more than 15-20%. 37 | * 7. Now measure Bus Voltage using a reliable Digital MultiMeter (DMM). Update bus_V_scaling_e4 = bus_V_scaling_e4 / (Displayed Bus Voltage on Serial Monitor) * (DMM Measured Bus Voltage). Can only be whole numbers. 38 | * 8. Now set DMM in current measurement mode. Use a resistor that will generate around 50-100mA IOUT measurement between IN- and GND pins with DMM in series with load. Note current measured on DMM. 39 | * 9. Update shunt = shunt * (Displayed IOUT on Serial Monitor) / (DMM Measured IOUT). 40 | * 10. Build firmware and flash microcontroller. Your INA 226 is now calibrated. It should have less than 1% error in Current and Voltage measurements over a wide range like [5mA, 1A] and [5V, 20V]. 41 | */ 42 | 43 | /* USER SET VALUES */ 44 | 45 | float shunt = 0.01023; /* shunt (Shunt Resistance in Ohms). Lower shunt gives higher accuracy but lower current measurement range. Recommended value 0.020 Ohm. Min 0.001 Ohm */ 46 | float current_LSB_mA = 0.25; /* current_LSB_mA (Current Least Significant Bit in milli Amperes). Recommended values: 0.050, 0.100, 0.250, 0.500, 1, 2, 2.5 (in milli Ampere units) */ 47 | float current_zero_offset_mA = 6.4; /* current_zero_offset_mA (Current Zero Offset in milli Amperes, default = 0) */ 48 | uint16_t bus_V_scaling_e4 = 10026; /* bus_V_scaling_e4 (Bus Voltage Scaling Factor, default = 10000) */ 49 | 50 | if (INA.configure(shunt, current_LSB_mA, current_zero_offset_mA, bus_V_scaling_e4)) 51 | Serial.println("\n***** Configuration Error! Chosen values outside range *****\n"); 52 | else 53 | Serial.println("\n***** INA 226 CONFIGURE *****"); 54 | Serial.print("Shunt:\t"); 55 | Serial.print(shunt, 4); 56 | Serial.println(" Ohm"); 57 | Serial.print("current_LSB_mA:\t"); 58 | Serial.print(current_LSB_mA * 1e+3, 1); 59 | Serial.println(" uA / bit"); 60 | Serial.print("\nMax Measurable Current:\t"); 61 | Serial.print(INA.getMaxCurrent(), 3); 62 | Serial.println(" A"); 63 | 64 | /* CALIBRATION */ 65 | 66 | float bv = 0, cu = 0; 67 | for (int i = 0; i < 10; i++) 68 | { 69 | bv += INA.getBusVoltage(); 70 | cu += INA.getCurrent_mA(); 71 | delay(150); 72 | } 73 | bv /= 10; 74 | cu /= 10; 75 | Serial.println("\nAverage Bus and Current values for use in Shunt Resistance, Bus Voltage and Current Zero Offset calibration:"); 76 | bv = 0; 77 | for (int i = 0; i < 10; i++) 78 | { 79 | bv += INA.getBusVoltage(); 80 | delay(100); 81 | } 82 | bv /= 10; 83 | Serial.print("\nAverage of 10 Bus Voltage values = "); 84 | Serial.print(bv, 3); 85 | Serial.println("V"); 86 | cu = 0; 87 | for (int i = 0; i < 10; i++) 88 | { 89 | cu += INA.getCurrent_mA(); 90 | delay(100); 91 | } 92 | cu /= 10; 93 | Serial.print("Average of 10 Current values = "); 94 | Serial.print(cu, 3); 95 | Serial.println("mA"); 96 | 97 | Serial.println("\nCALIBRATION VALUES TO USE:\t(DMM = Digital MultiMeter)"); 98 | Serial.println("Step 5. Attach a power supply with voltage 5-10V to INA226 on VBUS/IN+ and GND pins, without any load."); 99 | Serial.print("\tcurrent_zero_offset_mA = "); 100 | Serial.print(current_zero_offset_mA + cu, 3); 101 | Serial.println("mA"); 102 | if (cu > 5) 103 | Serial.println("********** NOTE: No resistive load needs to be present during current_zero_offset_mA calibration. **********"); 104 | Serial.print("\tbus_V_scaling_e4 = "); 105 | Serial.print(bus_V_scaling_e4); 106 | Serial.print(" / "); 107 | Serial.print(bv, 3); 108 | Serial.println(" * (DMM Measured Bus Voltage)"); 109 | Serial.println("Step 8. Set DMM in current measurement mode. Use a resistor that will generate around 50-100mA IOUT measurement between IN- and GND pins with DMM in series with load. Note current measured on DMM."); 110 | Serial.print("\tshunt = "); 111 | Serial.print(shunt); 112 | Serial.print(" * "); 113 | Serial.print(cu, 3); 114 | Serial.println(" / (DMM Measured IOUT)"); 115 | if (cu < 40) 116 | Serial.println("********** NOTE: IOUT needs to be more than 50mA for better shunt resistance calibration. **********"); 117 | delay(1000); 118 | 119 | /* MEASUREMENTS */ 120 | 121 | Serial.println("\nBUS(V) SHUNT(mV) CURRENT(mA) POWER(mW)"); 122 | for (int i = 0; i < 5; i++) 123 | { 124 | bv = INA.getBusVoltage(); 125 | float sv = INA.getShuntVoltage_mV(); 126 | cu = INA.getCurrent_mA(); 127 | float po = (bv - sv / 1000) * cu; 128 | Serial.print(bv, 3); 129 | Serial.print("\t"); 130 | Serial.print(sv, 3); 131 | Serial.print("\t\t"); 132 | Serial.print(cu, 1); 133 | Serial.print("\t"); 134 | Serial.print(po, 1); 135 | Serial.println(); 136 | delay(1000); 137 | } 138 | } 139 | 140 | void loop() 141 | { 142 | } 143 | 144 | // -- END OF FILE -- -------------------------------------------------------------------------------- /to_do_list.md: -------------------------------------------------------------------------------- 1 | List of requested UI changes: 2 | + [x] Change screen from "I" to "A" for current fixed reading 3 | + [x] Indication if encoder is mapped to voltage to current setting. With blinking digit can set increment to 1V, 100mV, or 20mV. 4 | + [x] Bootup voltage 5A with limit at 1A is best. 5 | + [x] Add mV suffix to set value 6 | + [x] Pressing encoder in current mode can change between 50mA to 200mA 7 | + [x] Skipping bootup screen by pressing any button 8 | + [x] Default is PPS but turning knob at bootup screen to select desire profile (PPS, PDO, QC3.0). 9 | + [x] Corner of the screen now display protocol PPS, Fixed, or QC3.0 10 | + [x] Addition all menu afterboot up to switch between mode if long press Voltage/Current button 11 | 12 | 13 | + [x] Calibrate current sensor 14 | + [x] Delay CC/CV update to reduce mode flicker. 15 | + [x] Fix CC/CV bug at no load. 16 | 17 | 18 | + [x] Add EEPROM function to remember last used voltage/current and PD mode settings. 19 | 20 | + [x] Change Startup PPS to be Corse **#issue #17** 21 | + [x] Fix quickly turning the encoder makes the power supply not delivering the set voltage. **#issue #11** 22 | + [x] Fix Voltage selection bug near 3.3V **#issue #3** 23 | 24 | Backlog: 25 | + [ ] Add more cable loss when connect through Android adapter, detect computer connection. 26 | + [ ] Add counter for mAh or Wh used **#issue #13** 27 | + [ ] Add reminder boot screen for buttons function **#issue #14** 28 | 29 | ``` 30 | //Tested with USB-C to USB-A, and USB-C to USB-C on PC/Mac, Anker PowerCore24k, and UGREEN 140W 31 | #define MEM32(address) (*(volatile uint32_t*)(address)) 32 | #define ADDR_ENDP MEM32(USBCTRL_REGS_BASE + 0x00) 33 | 34 | value = MEM32(USBCTRL_REGS_BASE + 0x00) & 0x007F; //Masking is only to read bit 0 to 6 35 | if(value != 0) 36 | //USB is acting as device. 37 | else 38 | //Not connected to PC 39 | ``` 40 | 41 | 42 | Code structure note: 43 | Right now the statemachine is handlding the voltage calling as well as OLED update. 44 | OLED update function is a one big function that is now taking in 1 flag to determine what to display and what not to display. 45 | 46 | Increments.. is currently one single global variable but PPS and QC has different adjustment. 47 | 48 | Best if each stage has its own OLED print method, and increment setting or button mapping. 49 | --------------------------------------------------------------------------------