diff --git a/README.md b/README.md index 33c2fe0..b8966f4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Simple USB Power Tester based on ATtiny25/45/85 and INA219. The device measures 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. +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 8mΩ enables both a very small influence on the circuit and a measurement with a resolution of 1mA. 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. @@ -37,8 +37,7 @@ Since there is no ICSP header on the board, you have to program the ATtiny eithe - 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 + - **Clock:** 1 MHz (internal) - **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. @@ -53,7 +52,7 @@ Since there is no ICSP header on the board, you have to program the ATtiny eithe - 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 + avrdude -c usbasp -p t85 -U lfuse:w:0x62:m -U hfuse:w:0xd5:m -U efuse:w:0xff:m -U flash:w:usb_tester.hex ``` ### If using the makefile (Linux/Mac) diff --git a/software/USB_Tester.ino b/software/USB_Tester.ino new file mode 100644 index 0000000..d20deee --- /dev/null +++ b/software/USB_Tester.ino @@ -0,0 +1,494 @@ +// =================================================================================== +// Project: USB Power Tester based on ATtiny25/45/85 +// Version: v1.4 +// Year: 2020 - 2021 +// Author: Stefan Wagner +// Github: https://github.com/wagiminator +// EasyEDA: https://easyeda.com/wagiminator +// License: http://creativecommons.org/licenses/by-sa/3.0/ +// =================================================================================== +// +// Description: +// ------------ +// 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. +// +// References: +// ----------- +// The I²C OLED implementation is based on TinyOLEDdemo +// https://github.com/wagiminator/ATtiny13-TinyOLEDdemo +// +// Wiring: +// ------- +// +-\/-+ +// RESET --- RST ADC0 PB5 1|° |8 Vcc +// SET ------- ADC3 PB3 2| |7 PB2 ADC1 -------- OLED/INA SCK +// ------- ADC2 PB4 3| |6 PB1 AIN1 OC0B --- +// GND 4| |5 PB0 AIN0 OC0A --- OLED/INA SDA +// +----+ +// +// Compilation Settings: +// --------------------- +// Core: ATtinyCore (https://github.com/SpenceKonde/ATTinyCore) +// Board: ATtiny25/45/85 (No bootloader) +// Chip: ATtiny25 or 45 or 85 (depending on your chip) +// Clock: 1 MHz (internal) +// B.O.D.: 2.7V +// +// Leave the rest on default settings. Don't forget to "Burn bootloader"! +// No Arduino core functions or libraries are used. Use the makefile if +// you want to compile without Arduino IDE. +// +// Note: The internal oscillator may need to be calibrated for precise +// energy and capacity calculation. +// +// Fuse settings: -U lfuse:w:0x62:m -U hfuse:w:0xd5:m -U efuse:w:0xff:m +// +// Operating Instructions: +// ----------------------- +// Connect the device between a power supply and a consumer. +// Use the SET button to switch between the different screens. +// Use the RESET button to reset all values. + + +// =================================================================================== +// Libraries and Definitions +// =================================================================================== + +// Oscillator calibration value (uncomment and set if necessary) +// #define OSCCAL_VAL 0x48 + +// Libraries +#include // for GPIO +#include // for data stored in program memory +#include // for interrupts +#include // for delays + +// Pin definitions +#define PIN_SDA PB0 // I2C serial data pin +#define PIN_SCL PB2 // I2C serial clock pin +#define PIN_SET PB3 // SET button + +// =================================================================================== +// I2C Master Implementation +// =================================================================================== + +// I2C macros +#define I2C_SDA_HIGH() DDRB &= ~(1< pulled HIGH by resistor +#define I2C_SDA_LOW() DDRB |= (1< pulled LOW by MCU +#define I2C_SCL_HIGH() DDRB &= ~(1< pulled HIGH by resistor +#define I2C_SCL_LOW() DDRB |= (1< pulled LOW by MCU +#define I2C_SDA_READ() (PINB & (1< slave reads the bit + } + 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_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_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 + +// OLED BCD conversion array +const uint16_t DIVIDER[] PROGMEM = {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> 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 + uint16_t divider = pgm_read_word(&DIVIDER[digit]); // current divider + while(value >= divider) { // if current divider fits into the value + leadflag = 1; // end of leading spaces + digitval++; // increase digit value + value -= divider; // 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 + uint16_t divider = pgm_read_word(&DIVIDER[digit]); // current divider + while(value >= divider) { // if current divider fits into the value + digitval++; // increase digit value + value -= divider; // 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 0b0000011111111111 // 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); // read current from INA + if(result > 32767) result = 0; // ignore negative currents + return result; // 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 = 1000kHz / (8 * 1kHz) - 1 + TCCR0A = (1< 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< 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 make it 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(50); + } +} diff --git a/software/USB_Tester_v1.3.ino b/software/USB_Tester_v1.3.ino deleted file mode 100644 index 45f0736..0000000 --- a/software/USB_Tester_v1.3.ino +++ /dev/null @@ -1,480 +0,0 @@ -// 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"! -// No Arduino core functions or libraries are used. Use the makefile if -// you want to compile without Arduino IDE. -// -// Note: The internal oscillator may need to be calibrated for precise -// energy and capacity calculation. -// -// 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 -#include -#include -#include - -// 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< pulled HIGH by resistor -#define I2C_SDA_LOW() DDRB |= (1< pulled LOW by MCU -#define I2C_SCL_HIGH() DDRB &= ~(1< pulled HIGH by resistor -#define I2C_SCL_LOW() DDRB |= (1< pulled LOW by MCU -#define I2C_SDA_READ() (PINB & (1< lines released - PORTB &= ~((1< 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> 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 0b0000011111111111 // 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< 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< 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 make it 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); - } -} diff --git a/software/makefile b/software/makefile index fb227c6..ebcc90c 100755 --- a/software/makefile +++ b/software/makefile @@ -1,20 +1,19 @@ # Project: USB-Tester # Author: Stefan Wagner # Year: 2020 -# URL: https://easyeda.com/wagiminator -# https://github.com/wagiminator +# URL: https://github.com/wagiminator # # Type "make help" in the command line. # Input and Output File Names -SKETCH = USB_Tester_v1.3.ino +SKETCH = USB_Tester.ino TARGET = usb_tester # Microcontroller Options DEVICE = attiny85 -CLOCK = 8000000 +CLOCK = 1000000 PROGRMR = usbasp -LFUSE = 0xE2 +LFUSE = 0x62 HFUSE = 0xD5 EFUSE = 0xFF diff --git a/software/usb_tester.hex b/software/usb_tester.hex index 68d3471..6a4319e 100644 --- a/software/usb_tester.hex +++ b/software/usb_tester.hex @@ -1,5 +1,5 @@ -:1000000092C0ACC0ABC0AAC0A9C0A8C0A7C0A6C0BF -:10001000A5C0A4C0BDC1A2C0A1C0A0C09FC07C1F7C +:1000000097C0A6C0A5C0A4C0A3C0A2C0A1C0A0C0E4 +:100010009FC09EC0B3C19CC09BC09AC099C07C1FAA :1000200002200220022002207C1F000000000000AD :10003000000000007C1F001F82208220822082207E :100040007C00000082208220822082207C1F7C0095 @@ -14,122 +14,118 @@ :1000D000003F000000000030003000000000000081 :1000E0000000300630060000000080008000800024 :1000F0008000800080000000000000000000000080 -:100100000000A81F2001DA028D14AFA1C80E0C0D4B -:10011000FF121112FF0E0A0DFF0E0A12FF0E0C1233 -:10012000FF0E0B12FF0011241FBECFE5D2E0DEBF91 -:10013000CDBF10E0A0E6B0E0E6E4F8E002C0059034 -:100140000D92AA36B107D9F720E0AAE6B0E001C0C7 -:100150001D92AE36B207E1F740D173C351CF282FBD -:10016000B89898E080E0880FC895BA98B0998160F7 -:10017000BA9A9150C1F72111B89AC895BA98C89502 -:10018000BA9A0895B89ABA98B898089598E087FFEF -:1001900002C0B89801C0B89ABA98C895BA9A880FA0 -:1001A0009150A9F7C895B898BA98C895BA9A089581 -:1001B0001F93CF93DF93E82FEE0FEE0FEE0F880F14 -:1001C000880FCE2FC80F80E0E1DF80E0DFDFDCE0CA -:1001D000DC0F11E01C0FEC2FF0E0E25EFF4F84918A -:1001E000D5DFC12FD113F5CF80E0D0DF80E0DF91E4 -:1001F000CF911F91CBCFB89ABA9AC8CFCF93DF9344 -:10020000C82F80E8F8DF8C2FC1DFB898BA9881E852 -:10021000F2DF81E0A4DFC82F80E0A1DFD0E0DC2F97 -:10022000CC27C82BAFDFCE01DF91CF910895CF93BC -:10023000C82F88E7E0DF80E4A9DF80E0CA3018F04B -:100240008F5FCA50FBCFB4DF8C2FB2DFCF919ACF34 -:100250000F931F93CF93DF938C0188E7CCDF80E46B -:1002600095DFC4E6D0E02991399180E002171307A9 -:1002700020F08F5F021B130BF9CF9ADF80E0CA36A4 -:10028000D80789F7DF91CF911F910F917BCFEF9224 -:10029000FF920F931F93CF93DF938C0188E7ABDF1F -:1002A00080E474DF80E6E82E80E0F82EC0E0D0E045 -:1002B000F701219131917F0180E00217130728F0A7 -:1002C0008F5F021B130BD1E0F8CFD11103C0C430F4 -:1002D00009F082E16DDFCF5FC53051F7DF91CF913B -:1002E0001F910F91FF90EF904DCFCF93DF93EC01D3 -:1002F00088E781DF80E44ADFFE0184918F3F19F0B7 -:1003000057DF2196F9CFDF91CF913CCFCF93C82F04 -:1003100088E771DF80E43ADF8C2F4ADFCF9132CF5C -:10032000CF93DF93C82FD62F88E765DF80E02EDFDD -:1003300082E22CDF8D2F2ADF81E08D0F27DF8C2FCB -:100340008F7024DF8C2F82958F7080611FDF8D2F3F -:10035000806B1CDFDF91CF9115CFCF9360E080E001 -:10036000DFDF88E748DF80E411DFC0E080E00EDFF8 -:10037000C150E1F707DF62E080E0D2DF88E73BDFD2 -:1003800080E404DF80E002DFC150E1F7CF91FACED4 -:100390001F920F920FB60F9211248F939F93AF93DA -:1003A000BF9380916A0090916B00A0916C00B09116 -:1003B0006D000196A11DB11D80936A0090936B00A2 -:1003C000A0936C00B0936D00BF91AF919F918F91FE -:1003D0000F900FBE0F901F901895CF93DF93CDB75E -:1003E000DEB7A4970FB6F894DEBF0FBECDBF88E08E -:1003F00088BB8CE789BD82E08ABD83E083BF80E152 -:1004000089BF789487B38A7F87BB88B38A7F88BB8C -:1004100080E8F1DE80E0BADE86E0B8DE87E6B6DEB0 -:10042000B1DE80E8E8DE85E0B1DE84E1AFDE80E0C9 -:10043000ADDEA8DE88E7DFDE80E0A8DE22E031E086 -:100440003A8329830DE011E0E981FA8184919EDEEF -:1004500029813A812F5F3F4F3A832983021713077F -:1004600099F790DE7ADFF89440906A0050906B0024 -:1004700060906C0070906D00789431E03CA31BA2FA -:100480001D8A1E8A1F8A188E19821A821B821C825C -:10049000198A1A8A1B8A1C8A188A1F868FEF9FEF77 -:1004A0009E878D871C861B86AFEFBFEFBA87A98713 -:1004B00018861F82EFEFFFEFFE83ED8338EE832E69 -:1004C00033E0932EA12CB12C82E098DE969587958F -:1004D0001C01FCEF2F2284E091DE8C0197FF02C00B -:1004E00000E010E0A80160E070E0C101A0E0B0E031 -:1004F000498F5A8F6B8F7C8F9A01AB01BC01CD0164 -:1005000051D1A50194017BD169017A013E8F2D8FD4 -:10051000F89480916A0090916B00A0916C00B0916A -:100520006D008F8F98A3A9A3BAA378948419950915 -:10053000A609B7092C013D0189899A89AB89BC8933 -:10054000840D951DA61DB71D898B9A8BAB8BBC8B1B -:10055000A3019201698D7A8D8B8D9C8D23D120E131 -:100560003EE040E050E04BD189819A81AB81BC8173 -:10057000820F931FA41FB51F89839A83AB83BC830B -:10058000C701B60188279927A30192010BD120E169 -:100590003EE040E050E033D18D899E89AF89B88D2F -:1005A000820F931FA41FB51F8D8B9E8BAF8BB88FAF -:1005B000AD81BE812A163B0610F43E822D82EF816A -:1005C000F885E215F30510F438862F8229853A85DF -:1005D0000217130710F41A8709878B859C858017EB -:1005E000910710F41C870B87AD85BE85ED8DFE8DC0 -:1005F000EA17FB0710F4DE86CD862F8538898D8DAE -:100600009E8D2817390710F4D88ACF86B3990DC06C -:100610009CA191110CC0ABA1AF5FABA3A53008F0BA -:100620001BA29BDEB1E0BCA304C01CA202C0E1E09F -:10063000ECA3FBA1F23009F46DC020F5FF2309F40F -:1006400048C0F13009F0A0C060E080E069DE8D8133 -:100650009E811DDE81E191E048DE8F81988517DE65 -:1006600081E291E042DE62E080E05ADE89859A858F -:100670000EDE81E191E039DE8B859C8508DE89E123 -:1006800091E056C02BA1233009F454C0243009F066 -:100690007BC069897A898B899C89A5019401AFD037 -:1006A000890161E080E23CDEC80160E17EE093D038 -:1006B0008C01862FBCDD80E129DEC8016CE370E08F -:1006C0008AD0182F862FB3DD80E120DE812FAFDDA9 -:1006D0005BC060E080E024DEC101D9DD81E291E011 -:1006E00004DEC601D4DD8DE191E0FFDD62E080E053 -:1006F00017DEC801CCDD89E191E0F7DD69817A81FF -:100700008B819C81A50194017AD0C901C0DD85E16E -:1007100091E00EC061E080E003DE8D859E85B7DD4F -:1007200081E191E0E2DD8F859889B1DD8DE191E095 -:10073000DCDD2AC060E080E1F3DD69817A818B81B4 -:100740009C81A50194015BD06B017C01C9019FDDF7 -:100750008FE0DCDDC6017CDD85E191E0C6DD62E095 -:1007600080E1DEDD6D897E898F89988DA5019401F8 -:1007700046D06B017C01C9018ADD8FE0C7DDC6016F -:1007800067DD8DE091E0D4CF3FEF80E792E031501C -:1007900080409040E1F700C000004F8C58A069A055 -:1007A0007AA092CEEE27FF27AA27BB2708C0A20F68 -:1007B000B31FE41FF51F220F331F441F551F9695CB -:1007C00087957795679598F37040A9F7009799F703 -:1007D000BD01CF010895AA1BBB1B51E107C0AA1F91 -:1007E000BB1FA617B70710F0A61BB70B881F991FD2 -:1007F0005A95A9F780959095BC01CD010895A1E285 -:100800001A2EAA1BBB1BFD010DC0AA1FBB1FEE1F8A -:10081000FF1FA217B307E407F50720F0A21BB30BD5 -:10082000E40BF50B661F771F881F991F1A9469F751 -:1008300060957095809590959B01AC01BD01CF01AD -:060840000895F894FFCFBB -:0A0846001027E80364000A00010017 +:1001000000001027E80364000A000100A81F200176 +:10011000DA028D14AFA1C80E0C0DFF121112FF0EE2 +:100120000A0DFF0E0A12FF0E0C12FF0E0B12FF003B +:1001300011241FBECFE5D2E0DEBFCDBF20E0A0E698 +:10014000B0E001C01D92A436B207E1F73CD163C311 +:1001500057CF282FB89898E080E0880FBA98B099C8 +:100160008160BA9A9150C9F72111B89ABA98BA9A8F +:100170000895B89ABA98B898089598E087FF02C091 +:10018000B89801C0B89ABA98BA9A880F9150B1F746 +:10019000B898BA98BA9A08951F93CF93DF93E82F2F +:1001A000EE0FEE0FEE0F880F880FCE2FC80F80E0F6 +:1001B000E4DF80E0E2DFDCE0DC0F11E01C0FEC2F7D +:1001C000F0E0E25EFF4F8491D8DFC12FD113F5CF6D +:1001D00080E0D3DF80E0DF91CF911F91CECFB89A3E +:1001E000BA9ACBCFCF93DF93C82F80E8F8DF8C2F5C +:1001F000C4DFB898BA9881E8F2DF81E0AADFC82F9F +:1002000080E0A7DFD0E0DC2FCC27C82BB2DFCE0107 +:10021000DF91CF910895CF93C82F88E7E0DF80E486 +:10022000ACDF80E0CA3018F08F5FCA50FBCFB4DF7C +:100230008C2FB2DFCF919DCF0F931F93CF93DF937E +:100240008C0188E7CCDF80E498DFC0E0D0E0FE01DD +:10025000EA5FFE4F2591349180E00217130720F0EA +:100260008F5F021B130BF9CF97DF2296C630D105A3 +:1002700071F7DF91CF911F910F917BCFEF92FF929A +:100280000F931F93CF93DF937C0188E7A8DF80E46F +:1002900074DF02E011E0C0E0D0E0F8012591349174 +:1002A00080E0E216F30628F08F5FE21AF30AD1E04D +:1002B000F8CFD11103C0C43009F082E16DDFCF5F08 +:1002C0000E5F1F4FC53049F7DF91CF911F910F91FE +:1002D000FF90EF904ECFCF93DF93EC0188E77FDF65 +:1002E00080E44BDFFE0184918F3F19F055DF2196AA +:1002F000F9CFDF91CF913DCFCF93C82F88E76FDF44 +:1003000080E43BDF8C2F48DFCF9133CFCF93DF9357 +:10031000C82FD62F88E763DF80E02FDF82E22DDF52 +:100320008D2F2BDF81E08D0F28DF8C2F8F7025DF45 +:100330008C2F82958F70806120DF8D2F806B1DDF69 +:10034000DF91CF9116CFCF9360E080E0DFDF88E7C9 +:1003500046DF80E412DFC0E080E00FDFC150E1F74C +:1003600008DF62E080E0D2DF88E739DF80E405DF84 +:1003700080E003DFC150E1F7CF91FBCE1F920F92D7 +:100380000FB60F9211248F939F93AF93BF938091D9 +:10039000600090916100A0916200B09163000196AD +:1003A000A11DB11D8093600090936100A093620035 +:1003B000B0936300BF91AF919F918F910F900FBE4B +:1003C0000F901F901895CF93DF93CDB7DEB7A4970A +:1003D0000FB6F894DEBF0FBECDBFC39A8CE789BDC0 +:1003E00082E08ABD83BF80E189BF80E8F8DE80E0DB +:1003F000C4DE87E0C2DE8FEFC0DEBBDE80E8EFDE6A +:1004000085E0BBDE84E1B9DE80E0B7DEB2DE88E7FE +:10041000E6DE80E0B2DE2CE031E03C832B8307E1B6 +:1004200011E0EB81FC818491A8DE2B813C812F5F60 +:100430003F4F3C832B830217130799F79ADE83DF24 +:10044000F894C0906000D0906100E0906200F0905D +:100450006300789431E03A831982198A1A8A1B8AD8 +:100460001C8A412C512C32011D861E861F86188A3B +:100470001C861B864FEF5FEF5A87498718861F82BD +:100480008FEF9FEF9E838D83212C312CAFEFBFEF39 +:10049000BC83AB8338EE832E33E0932EA12CB12C9A +:1004A00082E0A0DE969587958C010C7F84E09ADE31 +:1004B0009CA38BA397FF02C01CA21BA2F8942091BF +:1004C00060003091610040916200509163002F8F75 +:1004D00038A349A35AA378942C193D094E095F0902 +:1004E0002D8B3E8B4F8B588F8D859E85AF85B88920 +:1004F000820F931FA41FB51F8D879E87AF87B88B70 +:10050000ABA1BCA1AD0160E070E0C801A0E0B0E02B +:10051000498F5A8F6B8F7C8F9A01AB01BC01CD0143 +:100520002BD1A501940155D169017A013E8F2D8F00 +:10053000298D3A8D4B8D5C8D6D897E898F89988D43 +:100540001BD120E13EE040E050E043D1420E531E7B +:10055000641E751EC701B601882799272D893E891B +:100560004F89588D09D120E13EE040E050E031D183 +:1005700089899A89AB89BC89820F931FA41FB51FF3 +:10058000898B9A8BAB8BBC8BAB81BC810A171B0709 +:1005900010F41C830B832016310608F41801ED813A +:1005A000FE812BA13CA12E173F0710F43E832D8323 +:1005B0004F8158858BA19CA14817590710F4988743 +:1005C0008F83A985BA85ED8DFE8DEA17FB0710F4A0 +:1005D000DA86C9862B853C854D8D5E8D24173507BF +:1005E00010F4DC86CB86B3990DC05A8151110CC032 +:1005F00089818F5F8983853008F01982A4DE91E0BC +:100600009A8304C01A8202C0A1E0AA83B981B230E1 +:1006100009F46FC018F5BB2309F44BC0B13009F0E1 +:10062000A0C060E080E072DE8B819C8127DE8BE1E0 +:1006300091E051DEC10122DE8BE291E04CDE62E00E +:1006400080E064DE8D819E8119DE8BE191E043DEE6 +:100650008F81988513DE83E291E059C0E981E33010 +:1006600009F457C0E43009F07CC06D857E858F8524 +:100670009889A5019401ADD03E8B2D8B61E080E27D +:1006800045DE8D899E8960E17EE08FD09E8B8D8BCB +:10069000862FC1DD80E130DE8D899E896CE370E0BC +:1006A00084D08D8B862FB7DD80E126DE8D89B3DD8A +:1006B00058C060E080E02ADEC801E0DD8BE291E016 +:1006C0000ADEC601DBDD87E291E005DE62E080E064 +:1006D0001DDE8BA19CA1D2DD83E291E0FCDDC30194 +:1006E000B201A501940175D0C901C8DD8FE191E087 +:1006F0000EC061E080E00ADE89859A85BFDD8BE16E +:1007000091E0E9DD8B859C85B9DD87E291E0E3DD51 +:1007100028C060E080E1FADDC301B201A5019401C7 +:1007200058D06B017C01C901A9DD8FE0E5DDC60170 +:1007300083DD8FE191E0CFDD62E080E1E7DD698973 +:100740007A898B899C89A501940143D06B017C0136 +:10075000C90194DD8FE0D0DDC6016EDD87E191E057 +:10076000D6CF83ED90E30197F1F700C00000CF8C66 +:10077000D8A0E9A0FAA094CEEE27FF27AA27BB278E +:1007800008C0A20FB31FE41FF51F220F331F441F21 +:10079000551F969587957795679598F37040A9F7BB +:1007A000009799F7BD01CF010895AA1BBB1B51E12A +:1007B00007C0AA1FBB1FA617B70710F0A61BB70BD1 +:1007C000881F991F5A95A9F780959095BC01CD0176 +:1007D0000895A1E21A2EAA1BBB1BFD010DC0AA1F82 +:1007E000BB1FEE1FFF1FA217B307E407F50720F09A +:1007F000A21BB30BE40BF50B661F771F881F991F15 +:100800001A9469F760957095809590959B01AC015D +:0A081000BD01CF010895F894FFCF59 :00000001FF