├── .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 |
--------------------------------------------------------------------------------