2025-11-14 09:24:15 +09:00

263 lines
7.0 KiB
C++

// Feather Power Meter
//
// Small Feather-based power monitor using an INA219 breakout and
// monochrome OLED display.
//
// Author: Tony DiCola (modded by ladyada)
//
// Released under a MIT license: http://opensource.org/licenses/MIT
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_INA219.h>
// Configure orientation of the display.
// 0 = none, 1 = 90 degrees clockwise, 2 = 180 degrees, 3 = 270 degrees CW
#define ROTATION 0
#define NEO_PIN 6
// Create NeoPixel, OLED and INA219 globals.
Adafruit_SSD1306 display;
Adafruit_INA219 ina219;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(144, NEO_PIN, NEO_GRB + NEO_KHZ800);
// Keep track of total time and milliamp measurements for milliamp-hour computation.
uint32_t total_sec = 0;
float total_mA = 0.0;
uint8_t counter = 0;
void pixel_show_and_powerupdate() {
pixels.show();
if (counter == 0) {
update_power_display();
} else {
counter = (counter+1) % 10 ;
}
}
void setup() {
Serial.begin(115200);
while (!Serial) {
// will pause Zero, Leonardo, etc until serial console opens
delay(1);
}
pixels.begin();
pixels.show(); // Initialize all pixels to 'off'
// Try to initialize the INA219
if (! ina219.begin()) {
Serial.println("Failed to find INA219 chip");
while (1) { delay(10); }
}
// By default the INA219 will be calibrated with a range of 32V, 2A.
// However uncomment one of the below to change the range. A smaller
// range can't measure as large of values but will measure with slightly
// better precision.
//ina219.setCalibration_32V_1A();
//ina219.setCalibration_16V_400mA();
// Setup the OLED display.
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
Wire.setClock(400000);
// Clear the display.
display.clearDisplay();
display.display();
// Set rotation.
display.setRotation(ROTATION);
// Set text size and color.
display.setTextSize(2);
display.setTextColor(WHITE);
}
void loop() {
// Some example procedures showing how to display to the pixels:
colorWipe(pixels.Color(255, 0, 0), 50); // Red
colorWipe(pixels.Color(0, 255, 0), 50); // Green
colorWipe(pixels.Color(0, 0, 255), 50); // Blue
//colorWipe(pixels.Color(0, 0, 0, 255), 50); // White RGBW
// Send a theater pixel chase in...
theaterChase(pixels.Color(127, 127, 127), 50); // White
theaterChase(pixels.Color(127, 0, 0), 50); // Red
theaterChase(pixels.Color(0, 0, 127), 50); // Blue
rainbow(20);
rainbowCycle(20);
theaterChaseRainbow(50);
}
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i<pixels.numPixels(); i++) {
pixels.setPixelColor(i, c);
pixel_show_and_powerupdate();
delay(wait);
}
}
void rainbow(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256; j++) {
for(i=0; i<pixels.numPixels(); i++) {
pixels.setPixelColor(i, Wheel((i+j) & 255));
}
pixel_show_and_powerupdate();
delay(wait);
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
for(i=0; i< pixels.numPixels(); i++) {
pixels.setPixelColor(i, Wheel(((i * 256 / pixels.numPixels()) + j) & 255));
}
pixel_show_and_powerupdate();
delay(wait);
}
}
//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
for (int j=0; j<10; j++) { //do 10 cycles of chasing
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < pixels.numPixels(); i=i+3) {
pixels.setPixelColor(i+q, c); //turn every third pixel on
}
pixel_show_and_powerupdate();
delay(wait);
for (uint16_t i=0; i < pixels.numPixels(); i=i+3) {
pixels.setPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
for (int j=0; j < 256; j++) { // cycle all 256 colors in the wheel
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < pixels.numPixels(); i=i+3) {
pixels.setPixelColor(i+q, Wheel( (i+j) % 255)); //turn every third pixel on
}
pixel_show_and_powerupdate();
delay(wait);
for (uint16_t i=0; i < pixels.numPixels(); i=i+3) {
pixels.setPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
void update_power_display() {
// Read voltage and current from INA219.
float shuntvoltage = ina219.getShuntVoltage_mV();
float busvoltage = ina219.getBusVoltage_V();
float current_mA = ina219.getCurrent_mA();
// Compute load voltage, power, and milliamp-hours.
float loadvoltage = busvoltage + (shuntvoltage / 1000);
float power_mW = loadvoltage * current_mA;
(void)power_mW;
total_mA += current_mA;
total_sec += 1;
float total_mAH = total_mA / 3600.0;
(void)total_mAH;
// Update display.
display.clearDisplay();
display.setCursor(0,0);
// Mode 0, display volts and amps.
printSIValue(loadvoltage, "V:", 2, 10);
display.setCursor(0, 16);
printSIValue(current_mA/1000.0, "A:", 5, 10);
display.display();
}
void printSIValue(float value, const char* units, int precision, int maxWidth) {
// Print a value in SI units with the units left justified and value right justified.
// Will switch to milli prefix if value is below 1.
// Add milli prefix if low value.
if (fabs(value) < 1.0) {
display.print('m');
maxWidth -= 1;
value *= 1000.0;
precision = max(0, precision-3);
}
// Print units.
display.print(units);
maxWidth -= strlen(units);
// Leave room for negative sign if value is negative.
if (value < 0.0) {
maxWidth -= 1;
}
// Find how many digits are in value.
int digits = ceil(log10(fabs(value)));
if (fabs(value) < 1.0) {
digits = 1; // Leave room for 0 when value is below 0.
}
// Handle if not enough width to display value, just print dashes.
if (digits > maxWidth) {
// Fill width with dashes (and add extra dash for negative values);
for (int i=0; i < maxWidth; ++i) {
display.print('-');
}
if (value < 0.0) {
display.print('-');
}
return;
}
// Compute actual precision for printed value based on space left after
// printing digits and decimal point. Clamp within 0 to desired precision.
int actualPrecision = constrain(maxWidth-digits-1, 0, precision);
// Compute how much padding to add to right justify.
int padding = maxWidth-digits-1-actualPrecision;
for (int i=0; i < padding; ++i) {
display.print(' ');
}
// Finally, print the value!
display.print(value, actualPrecision);
}