Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Tirdad Sadri Nejad
Published © GPL3+

Self-Calibrating USB Voltage/Current Meter

With a code size of 1KB on an ATtiny13A MCU, this tiny module measures voltages up to 22 volts and current up to 5 amps.

IntermediateFull instructions provided4 hours2,568
Self-Calibrating USB Voltage/Current Meter

Things used in this project

Hardware components

Microchip Attiny13A
×1
TM1637 7Segment Driver
×1
4 char, common anode 7 segment
×1
USB Connector, USB Type A
USB Connector, USB Type A
×1
USB Connector, Micro USB Type B
USB Connector, Micro USB Type B
×1
Texas Instruments LM1117 - 3.3v
×1

Software apps and online services

Proteus suite
Microchip Studio
Microchip Studio

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

proteus project

the whole project consisting of schematic and PCB

.hex file

the output .hex for easy making

Schematics

Schematic

The Schematic

Code

main code

C/C++
it's not deeply commented, but it's self explanatory :)
#define RESAMPLES	255   // values taken from ADC before averaging

#include <avr/io.h>
#include <util/delay.h>
#include <avr/eeprom.h>
#include "TTM1637.h"    // the library to drive a TM1637 display driver

uint16_t V;             // Voltage, obviously
uint16_t C;             // Current, obviously
uint8_t Rv,ShowMode;    // Voltage dividers, and a display mode variable


/* this function is used to read and average ADC values*/
uint16_t readADC(uint8_t chn)
{
	uint16_t samples;
	uint32_t adcVal = 0;
	ADMUX = 0x40 | chn;
	ADCSRA	|= (1 << ADSC);
	while (!ADIF) ;
	for (uint8_t i = 0; i < RESAMPLES; i++)
	{
		ADCSRA	|= (1 << ADSC);
		while (!ADIF) ;
		samples = ADCL | (ADCH << 8);
		adcVal += samples;
		_delay_us(500);
	}
	return (adcVal / RESAMPLES);
}

/* this one just shows a number. the sprintf is so cumbersome*/
void ShowValue(uint16_t Val)
{
	TM_WritePos(0, Val / 10000, false);
	TM_WritePos(1, (Val % 10000) / 1000, true);
	TM_WritePos(2, (Val % 1000) / 100, false);
	TM_WritePos(3, (Val % 100) / 10, false);
}

/* hell goes here*/
int main()
{
  /* read the divider value from previous calibrations*/
	Rv = eeprom_read_byte(0);
	
	TM_Init(); // init the display driver IC
	
	PORTB	|= (1 << PORTB4);
	ADMUX	= (1<<REFS0);
	ADCSRA	= (1 << ADEN) | 6;
	ADCSRA	|= (1 << ADSC);


  /* check if the button is pressed in startup. if it is, calculated the divider value and save it to eeprom(it should be a byte!)*/
	if (!(PINB & (1 << PINB4)))	
	{
		TM_SendData(0, 0x39);
		_delay_ms(2000);
		Rv = readADC(1) / 5;
		eeprom_write_byte(0, Rv);
	}
	
	
	while (1)
	{
	  /* a big integer is still better than a floating point. so calculate milliVolts, not volts*/
		V = (readADC(1) * 1000L) / Rv;
		
		/* these values for current are experimental. based on 0.033 sense resistor I used*/
		C = (readADC(3) * 4827L) / (247L);
		
		/* check if the key is pressed and change display mode accordingly*/
		if (!(PINB & (1 << PINB4)))	
		{
			TM_Clear();
			if(ShowMode < 2)	ShowMode++;
			else				ShowMode = 0;
		}
		if (ShowMode == 1)
		{
			ShowValue(V);   // just show the voltage
			//TM_SendData(3, 0x1C);
		}
		else if (ShowMode == 2)
		{
			ShowValue(C);   // just show the current
			//TM_SendData(3, 0x77);
		}
		else
		{
			// show both current and voltage
			TM_WritePos(0, V / 1000, true);
			TM_WritePos(1, (V % 1000) / 100, false);
			TM_WritePos(2, C / 1000, true);
			TM_WritePos(3, (C % 1000) / 100, false);
		}
	}
}

TM1637 general driver library

C/C++
many of the characters which can be displayed on a 7 segment are removed from TM_DIGITS array due to constrained attiny13A flash. if you want to use it for another project, uncomment them. other modifications are applied on the header file alone.
// Instructions
#define TM_DATA_CMD             0x40
#define TM_DISP_CTRL            0x80
#define TM_ADDR_CMD             0xC0

// Data command set
#define TM_WRITE_DISP           0x00
#define TM_READ_KEYS            0x02
#define TM_FIXED_ADDR           0x04

// Display control command
#define TM_DISP_PWM_MASK        0x07 // First 3 bits are brightness (PWM controlled)
#define TM_DISP_ENABLE          0x08


#include "TTM1637.h"


/* UNCOMMENT UNUSED CHARACTERS IF YOU WANT TO USE IT ON ANOTHER PROJECT*/
 const uint8_t FLASH_DIR TM_DIGITS[] =
{
    0x3F, // 0
    0x06, // 1
    0x5B, // 2
    0x4F, // 3
    0x66, // 4
    0x6D, // 5
    0x7D, // 6
    0x07, // 7
    0x7F, // 8
    0x6F, // 9

//    0x77, // A
//    0x7C, // b
//	  0x39, // C
//    0x5E, // d
//    0x79, // E
//    0x71, // F
//
//
//    0x3D, // G
//    0x76, // H
//    0x06, // I
//    0x1F, // J
//    0x76, // K 
//    0x38, // L
//    0x15, // M
//    0x54, // n
//    0x3F, // O
//    0x73, // P
//    0x67, // Q
//    0x50, // r
//    0x6D, // S
//    0x78, // t
//    0x3E, // U
//    0x1C, // V
//    0x2A, // W
//    0x76, // X 
//    0x6E, // Y
//    0x5B  // Z
};




void TM_PortInit()
{
  DIO_SETOUT;
  CLK_SETOUT;
  DIO_HI;
  CLK_HI;
}

void TM_Start()
{
  TM_PortInit();
  DEL_US;
  DIO_LO;
  DEL_US;
}

void TM_Stop()
{
  CLK_LO;
  DEL_US;
  CLK_HI;
  DIO_LO;
  DEL_US;
  DIO_HI;
}

void TM_Send(uint8_t b)
{
	uint8_t i;
  // Clock data bits
  for ( i = 8; i; --i, b >>= 1)
  {
    CLK_LO;
    if (b & 1)
      DIO_HI;
    else
      DIO_LO;
    DEL_US;

    CLK_HI;
    DEL_US;
  }

  // Clock out ACK bit
  CLK_LO;
  DIO_LO;
  DEL_US;

  CLK_HI;
  DEL_US;
}

void TM_SendCMD(uint8_t cmd)
{
  TM_Start();
  TM_Send(cmd);
  TM_Stop();
}

void TM_SendData(const uint8_t addr, const uint8_t dat)
{
  TM_SendCMD(TM_DATA_CMD | TM_FIXED_ADDR);

  TM_Start();
  TM_Send(TM_ADDR_CMD | addr);
  TM_Send(dat);
  TM_Stop();

  DEL_US;
}

void TM_SetBrightness(const uint8_t brightness)
{
  TM_SendCMD(TM_DISP_CTRL | TM_DISP_ENABLE | (brightness & TM_DISP_PWM_MASK));
}

void TM_Clear()
{
	uint8_t a;
  for (a = 0; a != TM1637_DIGITS; ++a)
    TM_SendData(a, 0x00);
}

void TM_Init()
{
  TM_PortInit();
  TM_SendCMD(TM_DATA_CMD | TM_WRITE_DISP);
  TM_SendCMD(TM_DISP_CTRL | TM_DISP_ENABLE | TM_DISP_PWM_MASK);

  TM_Clear();
}



void TM_WritePos(const uint8_t addr, const uint8_t dat, bool dot)
{
	if(dat<36)
		TM_SendData(addr,TM_DIGITS[dat] | ((uint8_t)dot<<7));
	else if (dat>64 && dat<91)
		TM_SendData(addr,TM_DIGITS[dat-55] | ((uint8_t)dot<<7));
	else if (dat>96 && dat<123)
		TM_SendData(addr,TM_DIGITS[dat-87] | ((uint8_t)dot<<7));
}

void TM_Write(uint16_t dat)
{
	TM_SendCMD(TM_DATA_CMD);

  TM_Start();
  TM_Send(TM_ADDR_CMD);
  TM_Send(TM_DIGITS[dat/1000]);
	TM_Send(TM_DIGITS[(dat%1000)/100]);
	TM_Send(TM_DIGITS[(dat%100)/10]);
	TM_Send(TM_DIGITS[dat%10]);
  TM_Stop();

  DEL_US;
}

void TM_WriteSTR(char const* str)
{
	uint8_t i=0;
	
	while(str[i] != '\0')
	{
		TM_WritePos(i,str[i],false);
		i++;
	}
}

TM1637 general driver library, header file

C/C++
the header file can be customized for other platforms or microcontrollers. read the comments
#ifndef __TTM1637_H__
#define __TTM1637_H__


// Enter MCU specific header files here: make sure they are "include guard"ed
#include <avr/io.h>
#include <util/delay.h>
//=================================//
// END OF MCU specific header inclusion
//=================================//

// Define the instruction which makes the pin output (e.g. DDRA |= (1<<5))
// DON'T ADD ";" in the end.
// CLK Corresponds to TM1637 clock and DIO corresponds to TM1637 DATA pin
#define CLK_SETOUT   	DDRB |= (1<<PORTB0)
#define DIO_SETOUT   	DDRB |= (1<<PORTB1)

// Define the instruction which makes the pins High or Low (e.g. PORTA |= (1<<5))
#define CLK_HI				PORTB |= (1<<PORTB0)
#define CLK_LO				PORTB &= ~(1<<PORTB0)
#define DIO_HI				PORTB |= (1<<PORTB1)
#define DIO_LO				PORTB &= ~(1<<PORTB1)
// Define a few microseconds delay. (e.g. _delay_ms(1))
#define DEL_US				//_delay_us(1)
// Define an Special directive to store data on flash 
#define FLASH_DIR	
// Define the connected 7segments displays
#define TM1637_DIGITS   4
//=================================//
// END OF USER DEFINITIONS
//=================================//



// initialize pins and other stuff
void TM_Init();
// set brightness from 0 to 7
void TM_SetBrightness(const uint8_t brightness);
// writes at the position in 7seg. from 0 to TM1637_DIGITS-1
// also, you can write any character like 'A' or 'a' but not symbols like '+'
void TM_WritePos(const uint8_t addr, const uint8_t dat, bool dot);
void TM_SendData(const uint8_t addr, const uint8_t dat);
// writes a number on screen. TM_Write(1942);
void TM_Write(uint16_t dat);
// writes an string on screen.TM_WriteSTR("Err");
void TM_WriteSTR(char const* str);
// obviously clears the display
void TM_Clear();


#endif

Credits

Tirdad Sadri Nejad

Tirdad Sadri Nejad

6 projects • 20 followers
AI masters degree student. a guitar player and an electronic hobbyist.

Comments