Update files

master
wagiminator 2021-03-27 20:18:24 +01:00
parent 8986dfce3c
commit 923cfcb835
9 changed files with 563 additions and 12 deletions

View File

@ -1,19 +1,95 @@
# USB Power Tester based on ATtiny45/85
Simple USB Power Tester based on ATtiny45/85 andINA219. You can switch between different screens by pressing the SET button.
# USB Power Tester based on ATtiny25/45/85
Simple USB Power Tester based on ATtiny25/45/85 and INA219. The device measures voltage, current, power, energy, capacity and displays the values on an OLED screen. You can switch between different screens by pressing the SET button.
Project Video: https://youtu.be/QKx8Vn_IfjU
- Project Video (YouTube): https://youtu.be/QKx8Vn_IfjU
- Project Files (EasyEDA): https://easyeda.com/wagiminator/attiny85-usb-tester
Project at EasyEDA: https://easyeda.com/wagiminator/attiny85-usb-tester
![pic6.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny85-USB-Tester/master/documentation/USB_Tester_pic6.jpg)
# Hardware
## USB Connectors
The device is equipped with a USB-A plug for the input and a USB-A socket for the output, so that it can be plugged between the power supply and the consumer. D+ and D- are passed through so that the consumer can negotiate the charging protocol.
## Voltage and Current Measurement
An [INA219](https://www.ti.com/lit/ds/symlink/ina219.pdf) is used to measure voltage and current. The INA219 is a current shunt and power monitor with an I²C-compatible interface. The device monitors both shunt voltage drop and bus supply voltage, with programmable conversion times and filtering. A programmable calibration value, combined with an internal multiplier, enables direct readouts of current in amperes. The selected shunt resistance of 8 milliohms enables both a very small influence on the circuit and a measurement with a resolution of 1 milliampere. For an accurate measurement, a shunt resistor with a low tolerance (1% or better) should be selected.
## User Interface
The user interface utilizes two buttons and a [128x64 pixels OLED display](http://aliexpress.com/wholesale?SearchText=128+64+0.96+oled+new+4pin). An [ATtiny24/45/85](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf) microcontroller handles the user interface as well as the calculation and display of the values.
![pic3.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny85-USB-Tester/master/documentation/USB_Tester_pic3.jpg)
![pic4.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny85-USB-Tester/master/documentation/USB_Tester_pic4.jpg)
# Software
## Basic Principle
The INA219 continuously measures current and voltage and transmits the values to the ATtiny via I²C. From this, the ATtiny calculates the other values and displays them on the OLED screen.
## I²C OLED Implementation
The I²C protocol implementation is based on a crude bitbanging method. It was specifically designed for the limited resources of ATtiny10 and ATtiny13, but it works with some other AVRs (including the ATtiny25/45/85) as well. The functions for the OLED are adapted to the SSD1306 OLED module, but they can easily be modified to be used for other modules. In order to save resources, only the basic functionalities which are needed for this application are implemented. For a detailed information on the working principle of the I²C OLED implementation visit [TinyOLEDdemo](https://github.com/wagiminator/attiny13-tinyoleddemo).
## Accuracy of Time and Capacity Determination
The internal oscillator of the ATtiny is used to determine energy and capacity. The accuracy of the internal oscillator is +/-10% with the factory calibration. This can be improved to +/-2% or better by [manual calibration](https://github.com/wagiminator/ATtiny84-TinyCalibrator). The calibration value determined in this way can be set in the source code.
## Compiling and Uploading
Since there is no ICSP header on the board, you have to program the ATtiny either before soldering using an [SOP adapter](https://aliexpress.com/wholesale?SearchText=sop-8+150mil+adapter), or after soldering using an [EEPROM clip](https://aliexpress.com/wholesale?SearchText=sop8+eeprom+programming+clip). The [AVR Programmer Adapter](https://github.com/wagiminator/AVR-Programmer/tree/master/AVR_Programmer_Adapter) can help with this.
### If using the Arduino IDE
- Make sure you have installed [ATtinyCore](https://github.com/SpenceKonde/ATTinyCore).
- Go to **Tools -> Board -> ATtinyCore** and select **ATtiny25/45/85 (No bootloader)**.
- Go to **Tools** and choose the following board options:
- **Chip:** ATtiny25 or 45 or 85 (depending on your chip)
- **Clock:** 8 MHz (internal)
- **Millis/Micros:** disabled
- **B.O.D.Level:** B.O.D. enabled (2.7V)
- Leave the rest at the default settings
- Connect your programmer to your PC and to the ATtiny.
- Go to **Tools -> Programmer** and select your ISP programmer (e.g. [USBasp](https://aliexpress.com/wholesale?SearchText=usbasp)).
- Go to **Tools -> Burn Bootloader** to burn the fuses.
- Open USB_Tester sketch and click **Upload**.
### If using the precompiled hex-file
- Make sure you have installed [avrdude](https://learn.adafruit.com/usbtinyisp/avrdude).
- Connect your programmer to your PC and to the ATtiny.
- Open a terminal.
- Navigate to the folder with the hex-file.
- Execute the following command (if necessary replace "usbasp" with the programmer you use):
```
avrdude -c usbasp -p t85 -U lfuse:w:0xe2:m -U hfuse:w:0xd5:m -U efuse:w:0xff:m -U flash:w:usb_tester.hex
```
### If using the makefile (Linux/Mac)
- Make sure you have installed [avr-gcc toolchain and avrdude](http://maxembedded.com/2015/06/setting-up-avr-gcc-toolchain-on-linux-and-mac-os-x/).
- Connect your programmer to your PC and to the ATtiny.
- Open the makefile and change the chip if you are not using ATtiny85 and the programmer if you are not using usbasp.
- Open a terminal.
- Navigate to the folder with the makefile and the Arduino sketch.
- Run "make install" to compile, burn the fuses and upload the firmware.
# Operating Instructions
1. Connect the device between a power supply and a consumer.
2. Use the SET button to switch between the different screens.
3. Use the RESET button to reset all values.
![pic1.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny85-USB-Tester/master/documentation/USB_Tester_pic1.jpg)
![pic2.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny85-USB-Tester/master/documentation/USB_Tester_pic2.jpg)
# Characteristics
|Parameter|Value|
|-|-|
|Voltage|3V - 12V|
|Current|max 5A|
|Resolution|4mV / 1mA|
|Voltage Measurement Resolution|4mV|
|Current Measurement Resolution|1mA|
![IMG_20200528_172038_x.jpg](https://image.easyeda.com/pullimage/4j7P3MET7HhN669FMKdTfiZ1XwqVarHYLU3UOEZv.jpeg)
![IMG_20200528_172119_x.jpg](https://image.easyeda.com/pullimage/LORMqUaLmmN483w0B8SMBrNtKXEOCVjlLunBzuNg.jpeg)
![IMG_20200528_172221_x.jpg](https://image.easyeda.com/pullimage/dZ775PpXM0R2nRWyUrkCIs53FVuBVIK6hRWURprz.jpeg)
![IMG_20200528_172239_x.jpg](https://image.easyeda.com/pullimage/moE1NVdTYPggxly5Qm69yMyY5wfAJkTwBuSVVanD.jpeg)
![IMG_20200809_110421_x.jpg](https://image.easyeda.com/pullimage/ljzX1HyNLBwO1GvyoRBCQjaGlI3ycSk1FJ5oKsjH.jpeg)
![IMG_20200809_110600_x.jpg](https://image.easyeda.com/pullimage/PXM2S4wXi5KYAfG4INnevNDf3QakvV5zXRmoI4AY.jpeg)
# References, Links and Notes
1. [UBS-C Version](https://github.com/wagiminator/ATtiny85-USB-C-Tester)
2. [ATtiny25/45/85 Datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf)
3. [INA219 Datasheet](https://www.ti.com/lit/ds/symlink/ina219.pdf)
4. [SSD1306 Datasheet](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf)
![pic5.jpg](https://raw.githubusercontent.com/wagiminator/ATtiny85-USB-Tester/master/documentation/USB_Tester_pic5.jpg)
# License
![license.png](https://i.creativecommons.org/l/by-sa/3.0/88x31.png)
This work is licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License.
(http://creativecommons.org/licenses/by-sa/3.0/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -0,0 +1,475 @@
// USB Power Tester - Basic
//
// This code implements the basic functionality for the USB Power Tester.
// It reads voltage, current and power from the INA219, calculates
// capacity and shows the values on the OLED. The SET button is used to
// switch between different screens.
//
// +-\/-+
// RESET --- A0 (D5) PB5 1| |8 Vcc
// SET ----- A3 (D3) PB3 2| |7 PB2 (D2) A1 ---- OLED/INA (SCK)
// A2 (D4) PB4 3| |6 PB1 (D1)
// GND 4| |5 PB0 (D0) ------- OLED/INA (SDA)
// +----+
//
// Core: ATtinyCore (https://github.com/SpenceKonde/ATTinyCore)
// Board: ATtiny25/45/85 (No bootloader)
// Chip: ATtiny25 or 45 or 85 (depending on your chip)
// Clock: 8 MHz (internal)
// Millis: disabled
// B.O.D.: 2.7V
// Leave the rest on default settings. Don't forget to "Burn bootloader" !
//
// The I²C OLED implementation is based on TinyOLEDdemo
// https://github.com/wagiminator/ATtiny13-TinyOLEDdemo
//
// 2020 by Stefan Wagner
// Project Files (EasyEDA): https://easyeda.com/wagiminator
// Project Files (Github): https://github.com/wagiminator
// License: http://creativecommons.org/licenses/by-sa/3.0/
// oscillator calibration value (uncomment and set if necessary)
//#define OSCCAL_VAL 0x48
// Libraries
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// Pin definitions
#define I2C_SDA PB0 // I2C serial data pin
#define I2C_SCL PB2 // I2C serial clock pin
#define SETBUTTON PB3 // SET button
// -----------------------------------------------------------------------------
// I2C Implementation
// -----------------------------------------------------------------------------
// I2C macros
#define I2C_SDA_HIGH() DDRB &= ~(1<<I2C_SDA) // release SDA -> pulled HIGH by resistor
#define I2C_SDA_LOW() DDRB |= (1<<I2C_SDA) // SDA as output -> pulled LOW by MCU
#define I2C_SCL_HIGH() DDRB &= ~(1<<I2C_SCL) // release SCL -> pulled HIGH by resistor
#define I2C_SCL_LOW() DDRB |= (1<<I2C_SCL) // SCL as output -> pulled LOW by MCU
#define I2C_SDA_READ() (PINB & (1<<I2C_SDA)) // read SDA line
#define I2C_DELAY() asm("lpm") // delay 3 clock cycles
#define I2C_CLOCKOUT() I2C_SCL_HIGH();I2C_DELAY();I2C_SCL_LOW() // clock out
// I2C init function
void I2C_init(void) {
DDRB &= ~((1<<I2C_SDA)|(1<<I2C_SCL)); // pins as input (HIGH-Z) -> lines released
PORTB &= ~((1<<I2C_SDA)|(1<<I2C_SCL)); // should be LOW when as ouput
}
// I2C transmit one data byte to the slave, ignore ACK bit, no clock stretching allowed
void I2C_write(uint8_t data) {
for(uint8_t i=8; i; i--, data<<=1) { // transmit 8 bits, MSB first
(data & 0x80) ? (I2C_SDA_HIGH()) : (I2C_SDA_LOW()); // SDA HIGH if bit is 1
I2C_CLOCKOUT(); // clock out -> slave reads the bit
}
I2C_DELAY(); // delay 3 clock cycles
I2C_SDA_HIGH(); // release SDA for ACK bit of slave
I2C_CLOCKOUT(); // 9th clock pulse is for the ignored ACK bit
}
// I2C start transmission
void I2C_start(uint8_t addr) {
I2C_SDA_LOW(); // start condition: SDA goes LOW first
I2C_SCL_LOW(); // start condition: SCL goes LOW second
I2C_write(addr); // send slave address
}
// I2C restart transmission
void I2C_restart(uint8_t addr) {
I2C_SDA_HIGH(); // prepare SDA for HIGH to LOW transition
I2C_SCL_HIGH(); // restart condition: clock HIGH
I2C_start(addr); // start again
}
// I2C stop transmission
void I2C_stop(void) {
I2C_SDA_LOW(); // prepare SDA for LOW to HIGH transition
I2C_SCL_HIGH(); // stop condition: SCL goes HIGH first
I2C_SDA_HIGH(); // stop condition: SDA goes HIGH second
}
// I2C receive one data byte from the slave (ack=0 for last byte, ack>0 if more bytes to follow)
uint8_t I2C_read(uint8_t ack) {
uint8_t data = 0; // variable for the received byte
I2C_SDA_HIGH(); // release SDA -> will be toggled by slave
for(uint8_t i=8; i; i--) { // receive 8 bits
data<<=1; // bits shifted in right (MSB first)
I2C_DELAY(); // delay 3 clock cycles
I2C_SCL_HIGH(); // clock HIGH
if(I2C_SDA_READ()) data |=1; // read bit
I2C_SCL_LOW(); // clock LOW -> slave prepares next bit
}
if (ack) I2C_SDA_LOW(); // pull SDA LOW to acknowledge (ACK)
I2C_DELAY(); // delay 3 clock cycles
I2C_CLOCKOUT(); // clock out -> slave reads ACK bit
return(data); // return the received byte
}
// -----------------------------------------------------------------------------
// OLED Implementation
// -----------------------------------------------------------------------------
// OLED definitions
#define OLED_ADDR 0x78 // OLED write address
#define OLED_CMD_MODE 0x00 // set command mode
#define OLED_DAT_MODE 0x40 // set data mode
#define OLED_INIT_LEN 11 // 9: no screen flip, 11: screen flip
// OLED init settings
const uint8_t OLED_INIT_CMD[] PROGMEM = {
0xA8, 0x1F, // set multiplex for 128x32
0x20, 0x01, // set vertical memory addressing mode
0xDA, 0x02, // set COM pins hardware configuration to sequential
0x8D, 0x14, // enable charge pump
0xAF, // switch on OLED
0xA1, 0xC8 // flip the screen
};
// OLED 6x16 font
const uint8_t OLED_FONT[] PROGMEM = {
0x7C, 0x1F, 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x7C, 0x1F, // 0 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x1F, // 1 1
0x00, 0x1F, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x7C, 0x00, // 2 2
0x00, 0x00, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x7C, 0x1F, // 3 3
0x7C, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x7C, 0x1F, // 4 4
0x7C, 0x00, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x00, 0x1F, // 5 5
0x7C, 0x1F, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x00, 0x1F, // 6 6
0x7C, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x7C, 0x1F, // 7 7
0x7C, 0x1F, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x7C, 0x1F, // 8 8
0x7C, 0x00, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x82, 0x20, 0x7C, 0x1F, // 9 9
0x00, 0x00, 0xF0, 0x3F, 0x8C, 0x00, 0x82, 0x00, 0x8C, 0x00, 0xF0, 0x3F, // 10 A
0x00, 0x00, 0xFE, 0x07, 0x00, 0x18, 0x00, 0x20, 0x00, 0x18, 0xFE, 0x07, // 11 V
0x00, 0x00, 0xFE, 0x1F, 0x00, 0x20, 0x00, 0x1F, 0x00, 0x20, 0xFE, 0x1F, // 12 W
0x00, 0x00, 0xFE, 0x3F, 0x00, 0x01, 0x80, 0x00, 0x80, 0x00, 0x00, 0x3F, // 13 h
0x00, 0x00, 0x80, 0x3F, 0x80, 0x00, 0x80, 0x3F, 0x80, 0x00, 0x00, 0x3F, // 14 m
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, // 15 .
0x00, 0x00, 0x00, 0x00, 0x30, 0x06, 0x30, 0x06, 0x00, 0x00, 0x00, 0x00, // 16 :
0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, // 17 -
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 18 SPACE
};
// Character definitions
#define DECIMAL 15
#define COLON 16
#define SPACE 18
// For BCD conversion
const uint16_t divider[5] = {10000, 1000, 100, 10, 1};
// OLED init function
void OLED_init(void) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_CMD_MODE); // set command mode
for (uint8_t i=0; i<OLED_INIT_LEN; i++)
I2C_write(pgm_read_byte(&OLED_INIT_CMD[i])); // send the command bytes
I2C_stop(); // stop transmission
}
// OLED set the cursor
void OLED_setCursor(uint8_t xpos, uint8_t ypos) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_CMD_MODE); // set command mode
I2C_write(0x22); // command for min/max page
I2C_write(ypos); I2C_write(ypos+1); // min: ypos; max: ypos+1
I2C_write(xpos & 0x0F); // set low nibble of start column
I2C_write(0x10 | (xpos >> 4)); // set high nibble of start column
I2C_write(0xB0 | (ypos)); // set start page
I2C_stop(); // stop transmission
}
// OLED clear screen
void OLED_clearScreen(void) {
OLED_setCursor(0, 0); // set cursor at upper half
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
uint8_t i = 0; // count variable
do {I2C_write(0x00);} while (--i); // clear upper half
I2C_stop(); // stop transmission
OLED_setCursor(0, 2); // set cursor at lower half
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
do {I2C_write(0x00);} while (--i); // clear upper half
I2C_stop(); // stop transmission
}
// OLED plot a character
void OLED_plotChar(uint8_t ch) {
ch = (ch << 3) + (ch << 2); // calculate position of character in font array
I2C_write(0x00); I2C_write(0x00); // print spacing between characters
for(uint8_t i=12; i; i--) I2C_write(pgm_read_byte(&OLED_FONT[ch++])); // print character
I2C_write(0x00); I2C_write(0x00); // print spacing between characters
}
// OLED print a character
void OLED_printChar(uint8_t ch) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
OLED_plotChar(ch); // plot the character
I2C_stop(); // stop transmission
}
// OLED print a string from program memory; terminator: 255
void OLED_printPrg(const uint8_t* p) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
uint8_t ch = pgm_read_byte(p); // read first character from program memory
while (ch < 255) { // repeat until string terminator
OLED_plotChar(ch); // plot character on OLED
ch = pgm_read_byte(++p); // read next character
}
I2C_stop(); // stop transmission
}
// OLED print 16-bit value as 5-digit decimal (BCD conversion by substraction method)
void OLED_printDec16(uint16_t value) {
uint8_t leadflag = 0; // flag for leading spaces
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
for(uint8_t digit = 0; digit < 5; digit++) { // 5 digits
uint8_t digitval = 0; // start with digit value 0
while (value >= divider[digit]) { // if current divider fits into the value
leadflag = 1; // end of leading spaces
digitval++; // increase digit value
value -= divider[digit]; // decrease value by divider
}
if (leadflag || (digit == 4)) OLED_plotChar(digitval); // print the digit
else OLED_plotChar(SPACE); // or print leading space
}
I2C_stop(); // stop transmission
}
// OLED print 16-bit value as 3-digit decimal (BCD conversion by substraction method)
void OLED_printDec12(uint16_t value) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
for(uint8_t digit = 2; digit < 5; digit++) { // 3 digits
uint8_t digitval = 0; // start with digit value 0
while (value >= divider[digit]) { // if current divider fits into the value
digitval++; // increase digit value
value -= divider[digit]; // decrease value by divider
}
OLED_plotChar(digitval); // print the digit
}
I2C_stop(); // stop transmission
}
// OLED print 8-bit value as 2-digit decimal (BCD conversion by substraction method)
void OLED_printDec8(uint8_t value) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
uint8_t digitval = 0; // start with digit value 0
while (value >= 10) { // if current divider fits into the value
digitval++; // increase digit value
value -= 10; // decrease value by divider
}
OLED_plotChar(digitval); // print first digit
OLED_plotChar(value); // print second digit
I2C_stop(); // stop transmission
}
// -----------------------------------------------------------------------------
// INA219 Implementation
// -----------------------------------------------------------------------------
// INA219 register values
#define INA_ADDR 0x80 // I2C write address of INA219
#define INA_CONFIG 0b0000011001100111 // INA config register according to datasheet
#define INA_CALIB 5120 // INA calibration register according to R_SHUNT
#define INA_REG_CONFIG 0x00 // INA configuration register address
#define INA_REG_CALIB 0x05 // INA calibration register address
#define INA_REG_SHUNT 0x01 // INA shunt voltage register address
#define INA_REG_VOLTAGE 0x02 // INA bus voltage register address
#define INA_REG_POWER 0x03 // INA power register address
#define INA_REG_CURRENT 0x04 // INA current register address
// INA219 write a register value
void INA_write(uint8_t reg, uint16_t value) {
I2C_start(INA_ADDR); // start transmission to INA219
I2C_write(reg); // write register address
I2C_write(value >> 8); // write register content high byte
I2C_write(value); // write register content low byte
I2C_stop(); // stop transmission
}
// INA219 read a register
uint16_t INA_read(uint8_t reg) {
uint16_t result; // result variable
I2C_start(INA_ADDR); // start transmission to INA219
I2C_write(reg); // write register address
I2C_restart(INA_ADDR | 0x01); // restart for reading
result = (uint16_t)(I2C_read(1) << 8) | I2C_read(0); // read register content
I2C_stop(); // stop transmission
return(result); // return result
}
// INA219 write inital configuration and calibration values
void INA_init(void) {
INA_write(INA_REG_CONFIG, INA_CONFIG); // write INA219 configuration
INA_write(INA_REG_CALIB, INA_CALIB); // write INA219 calibration
}
// INA219 read voltage
uint16_t INA_readVoltage(void) {
return((INA_read(INA_REG_VOLTAGE) >> 1) & 0xFFFC);
}
// INA219 read sensor values
uint16_t INA_readCurrent(void) {
uint16_t result = INA_read(INA_REG_CURRENT);
if (result > 32767) result = 0;
return(result);
}
// -----------------------------------------------------------------------------
// Millis Counter Implementation for Timer0
// -----------------------------------------------------------------------------
volatile uint32_t MIL_counter = 0; // millis counter variable
// Init millis counter
void MIL_init(void) {
OCR0A = 124; // TOP: 124 = 8000kHz / (64 * 1kHz) - 1
TCCR0A = (1<<WGM01); // timer0 CTC mode
TCCR0B = (1<<CS01)|(1<<CS00); // start timer0 with prescaler 64
TIMSK = (1<<OCIE0A); // enable output compare match interrupt
sei(); // enable global interrupts
}
// Read millis counter
uint32_t MIL_read(void) {
cli(); // disable interrupt for atomic read
uint32_t result = MIL_counter; // read millis counter
sei(); // enable interrupts
return(result); // return millis counter value
}
// Timer0 compare match A interrupt service routine (every millisecond)
ISR(TIM0_COMPA_vect) {
MIL_counter++; // increase millis counter
}
// -----------------------------------------------------------------------------
// Main Function
// -----------------------------------------------------------------------------
// Some "strings"
const uint8_t mA[] PROGMEM = { 14, 10, 18, 255 }; // "mA "
const uint8_t mV[] PROGMEM = { 14, 11, 18, 255 }; // "mV "
const uint8_t mW[] PROGMEM = { 14, 12, 18, 255 }; // "mW "
const uint8_t mAh[] PROGMEM = { 14, 10, 13, 255 }; // "mAh"
const uint8_t mWh[] PROGMEM = { 14, 12, 13, 255 }; // "mWh"
const uint8_t SEP[] PROGMEM = { 18, 17, 18, 255 }; // " - "
int main(void) {
// Local variables
uint16_t voltage, current, power; // voltage in mV, current in mA, power in mW
uint16_t minvoltage = 65535, maxvoltage = 0; // min/max voltages in mV
uint16_t mincurrent = 65535, maxcurrent = 0; // min/max current in mA
uint16_t minpower = 65535, maxpower = 0; // min/max power in mV
uint32_t lastmillis, nowmillis, interval; // for timing calculation in millis
uint32_t duration = 0; // total duration in millis
uint16_t seconds; // total duration in seconds
uint32_t capacity = 0, energy = 0; // counter for capacity in uAh and energy in uWh
uint8_t primescreen = 0; // screen selection flag
uint8_t lastbutton = 1; // button flag (0: button pressed)
// Set oscillator calibration value
#ifdef OSCCAL_VAL
OSCCAL = OSCCAL_VAL; // set the value if defined above
#endif
// Setup
PORTB = (1<<SETBUTTON); // pullup for set button
MIL_init(); // init millis counter
I2C_init(); // init I2C
INA_init(); // init INA219
OLED_init(); // init OLED
OLED_clearScreen(); // clear screen
lastmillis = MIL_read(); // read millis counter
// Loop
while(1) {
// Read sensor values
voltage = INA_readVoltage(); // read voltage from INA219
current = INA_readCurrent(); // read current from INA219
power = (uint32_t)voltage * current / 1000; // calculate power in mW
// Calculate timings
nowmillis = MIL_read(); // read millis counter
interval = nowmillis - lastmillis; // calculate time interval
lastmillis = nowmillis; // reset lastmillis
duration += interval; // calculate total duration in millis
seconds = duration / 1000; // calculate total duration in seconds
// Calculate capacity in uAh and energy in uWh
capacity += interval * current / 3600; // calculate uAh
energy += interval * power / 3600; // calculate uWh
// Update min/max values
if (minvoltage > voltage) minvoltage = voltage;
if (maxvoltage < voltage) maxvoltage = voltage;
if (mincurrent > current) mincurrent = current;
if (maxcurrent < current) maxcurrent = current;
if (minpower > power ) minpower = power;
if (maxpower < power ) maxpower = power;
// Check SET button and set screen flag accordingly
if (PINB & (1<<SETBUTTON)) lastbutton = 0;
else if (!lastbutton) {
if (++primescreen > 4) primescreen = 0;
OLED_clearScreen();
lastbutton = 1;
}
// Display values on the OLED
switch (primescreen) {
case 0: OLED_setCursor(0,0);
OLED_printDec16(voltage); OLED_printPrg(mV);
OLED_printDec16(power); OLED_printPrg(mW);
OLED_setCursor(0,2);
OLED_printDec16(current); OLED_printPrg(mA);
OLED_printDec16(capacity / 1000); OLED_printPrg(mAh);
break;
case 1: OLED_setCursor(0,0);
OLED_printDec16(minvoltage); OLED_printPrg(SEP);
OLED_printDec16(maxvoltage); OLED_printPrg(mV);
OLED_setCursor(0,2);
OLED_printDec16(mincurrent); OLED_printPrg(SEP);
OLED_printDec16(maxcurrent); OLED_printPrg(mA);
break;
case 2: OLED_setCursor(0,1);
OLED_printDec16(minpower); OLED_printPrg(SEP);
OLED_printDec16(maxpower); OLED_printPrg(mW);
break;
case 3: // ATtiny25 without decimal places to fit into the flash
#if defined(__AVR_ATtiny25__)
OLED_setCursor(32,0);
OLED_printDec16(capacity / 1000); OLED_printPrg(mAh);
OLED_setCursor(32,2);
OLED_printDec16(energy / 1000); OLED_printPrg(mWh);
#else
OLED_setCursor(16,0);
OLED_printDec16(capacity / 1000); OLED_printChar(DECIMAL);
OLED_printDec12(capacity % 1000); OLED_printPrg(mAh);
OLED_setCursor(16,2);
OLED_printDec16(energy / 1000); OLED_printChar(DECIMAL);
OLED_printDec12(energy % 1000); OLED_printPrg(mWh);
#endif
break;
case 4: OLED_setCursor(32,1);
OLED_printDec8(seconds / 3600); OLED_printChar(COLON);
seconds %= 3600;
OLED_printDec8(seconds / 60 ); OLED_printChar(COLON);
OLED_printDec8(seconds % 60 );
break;
default: break;
}
_delay_ms(100);
}
}

View File

@ -7,7 +7,7 @@
# Type "make help" in the command line.
# Input and Output File Names
SKETCH = USB_Tester_v1.2.ino
SKETCH = USB_Tester_v1.3.ino
TARGET = usb_tester
# Microcontroller Options