Current Measuring using SN-ACS712 and SK28A

Introduction

SN-ACS712 is a breakout board for the fully integrated Hall Effect based linear current sensor, ACS712. There are several types of current sensors and many different ways to measure current of a circuit, but we prefer using Hall effect sensor because it will not affect the load voltage as shunt resistor did. 

We did a tutorial showing current measurement using ACS756, a 50 ampere current sensor, check it out here. BTW, both these sensors are able to measure DC or AC current. Just if you plan to measure AC current, be more cautions as AC voltage is normally much higher, 110V to 240V. Do take note.

In this tutorial, I am going to use ACS712 – 5 ampere version of current sensor to detect smaller amount of current and make the whole system portable. With some program tweaking, this Amp meter can show real time current or Peak current.

Hardware 

Software

Working Principle

The working principle of ACS712 is similar to ACS756. However, ACS756 can measure up to 50A of current in both direction and therefore the resolution is not as small as ACS712 which can measure up to 5A of current in both direction.

The features of ACS712:

  • x05B (5 Amp) version
  • Low noise analog signal path
  • Device bandwidth is set via the FILTER pin
  • 5us output rise time in response to input current
  • 80kHz bandwidth
  • 1.5% output error at 25 degrees C
  • 1.2mOhm internal conductor resistance
  • 2.1 kVRMS minimum isolation voltage from pins 1-4 to pins 5-8
  • 5.0 VDC, single supply operation
  • 185 mV/A output sensitivity
  • Output voltage proportional to AC or DC currents
  • Factory-trimmed for accuracy
  • Extremely stable output offset voltage
  • Nearly zero magnetic hysteresis
  • Ratiometric output from supply voltage
I guess the interface is quite straight forward. 5V should be supply to Vcc of ACS712 breakout board and the GND should be the negative of 0V of supply. Once it is powered, the Vout should output voltage that represent current going through the sensing pads. If there is no current, it should produce VCC/2 = 5V/2 = 2.5V at Vout. That indicates the current going through the sensor is 0 amp.
ACS712 is able to measure current in two direction.  Output voltage more than 2.5V (VCC/2) indicates current in one direction (eg. from A to B) and voltage less than 2.5V indicates current in another direction (eg. B to A). We will be using SK28A with PIC18F2550 to read the voltage from ACS712’s Vout and convert it to current (ampere), further display the ampere value on LCD.

Connection

Basically, the connections:

(IMPORTANT: please read the precaution before turning on the switch or the 12VDC supply.)
currentsensor

Precaution

You can either use battery supply or connect a 12V DC supply adapter on SK28C, BUT !!! Please take note that:

  • The diode is a must protect the high voltage from 12V DC supply if the slide switch is closed. 
  • For the diode, connect anode to the battery’s positive terminal, and the cathode (with white line) to Vin pin of SK28A.
  • It is advisable to add a capacitor between the FILT and GND pin on the ASC712 breakout board to filter noise. Ceramic capacitor with 1nF is good enough.
  • You can add terminal block or spring terminal to the circuit to ease the wiring when you need to measure current. They should be parallel with the terminals (the two big circle pads) of the ACS712 breakout board. 

Loading Program

Before going to the coding, there are some basic knowledge to program the PIC18F2550 on SK28A. (Skip this part if you already know it or you are using PICkit3.)
First, to program a PIC microcontroller, you need to connect Vpp (Reset or MCLR), Vdd, Gnd, PGD and PGC of PIC microcontroller to programmer.

18f2550

From the PIC18F2550 datasheet, we can see that the the Vpp, Vdd, GND, PGC and PGD is at pin 1, 20, 8, 27 and 28 respectively.
However, SK28A had makes everything easier. SK28A had made the ICSP pinout to fit the universal PICkit programmer.
(AUX is not used in this case).
pickita
If you are using UIC00B programmer, lets look at its pin out.
 
uic00b
 
 
If you are using the UIC00B programmer, you can connect directly those pins using female-female jumper or a better and easier alternative is to use a ICSP Universal Socket. Just simply connect as shown in below.
 
with cable
Finally, you can start to program it now!
 
The Code
I am using MPLAB IDE v8.63, and the code is compiled using Microchip MPLAB XC8 V1.12 compiler.
It is important to know which type and version of compiler you are using. Why I said so because you won’t build the code successfully if you use a different compiler such as Microchip MPLAB C18 compiler or CCS.Why is this happening? This is because every compiler had their own “standards” of defining variables and ways to write configuration bits. For example, for Microchip MPLAB C18 compiler, there is no __delay_ms() function built in. You need to make your own delay function. Another example is that you need to define the bits first like this:

[sourcecode language=”cpp” wraplines=”true”]
#define RB0 PORTBbits.RB0
[/sourcecode]

before you can use RB0. However, if you are using Microchip MPLAB XC8 compiler, there is built in macro for delay and you can utilize it right away You can use __delay_ms() function directly (but it must be a constant value inside the bracket instead of variable).

Actually, the code I am using is just a modification from the previous tutorial did by AngJiaYi. Click here to see the tutorial.
The complete code can be download at the bottom of this page.

[sourcecode language=”cpp” wraplines=”true”]
int main()
{
unsigned int value = 0; // declare a variable to store
unsigned int valueans = 0; // declare a variable to store
unsigned int peakvalue = 0; // declare a variable to store
unsigned int condition = 0; // declare a variable to store

PORTA = 0; // ensure the hardware port in zero initially
PORTB = 0; // ensure the hardware port in zero initially
PORTC = 0; // ensure the hardware port in zero initially

TRISA = 0b00000001; // for Port A, A0 is used to read the value on the sensor
// so it is an input(1)
TRISB = 0b00000001; // for Port B, B0 in SK28A is connected to the switch(SW1)
// thus, it should be an input too(1)

lcd_init(); //Initialize lcd to be use, can refers to lcd.c

adc_initialize(); //Initialize ADC to be use, can refers to the bottom part of the program

ADCON0bits.ADON=1; //Setting the ADON bit in ADCON0 register to 1, enable ADC module
[/sourcecode]

The actual code that read ADC and convert into current, display on LCD.

[sourcecode language=”cpp” wraplines=”true”]

while(1)
{
if(condition==0)
{
lcd_clr(); // clear the LCD
lcd_putstr(“Current”); // print “Current” in LCD
lcd_2ndline(); // set the cursor to second line
lcd_putstr(” 0.000A”); // print the initial value 0A to LCD

while(condition<1) { value = read_adc_value(); // call function read_adc_value(), the result return // will keep in “value” variable for further process if(value>=512) // if the current flowing in positive direction,
{ // it will return a value more than 512
valueans =(value-512)*25; // formula to convert ADC to current in ampere
lcd_goto(0x40); // go to second line of LCD
lcd_putstr(“-“); // put a – to indicate a direction
lcd_goto(0x41);
lcd_bcd(5,valueans); // operation to print the value to LCD
}
else // if the current flowing in another direction,
{ // it will return a value less than 512
valueans =(512-value)*25; // formula to convert ADC to current in ampere
lcd_goto(0x40); // go to second line of LCD
lcd_putstr(” “); // put a “space” to indicate another direction
lcd_goto(0x41); // go to second line
lcd_bcd(5,valueans); // print to LCD
}

[/sourcecode]

From the code we set the divide the routine at 512 and multiplication of 25, Why?

Before that i would like to explain about resolution first. Resolution is the smallest analog change resulting from changing of one bit. For your information, the ADC system in PIC18F2550 is a 10-bit ADC system. Now, let’s look at the datasheet.

zero
It is stated that the zero current output voltage will be Vcc x 0.5. Which means that when 5V is supply to the sensor, and when there is no current flow, the sensor will output 2.5V. However, in real cases, the Vcc might not be exact 5V. It maybe will be a little bit higher or lower depending on the voltage regulator. In my case, my Vcc or Vref is about 4.88V (measured using a multimeter). Thus, in my case, when there is no current flow, the voltage output will be 2.44V and the ADC value would be 2.44V/4.88V*1024 which equals to 512.

To calculate the resolution, simply divide your Vref by 1024 (2^10 because of 10-bit ADC system) and we get 4.76mV/bit. In other words, an increase of 1 bit for ADC constitute to 4.76mV or 0.00476V. Then, let’s look at the datasheet again.

acs712data
From the datasheet, we can see that the sensitivity of the sensor is 185mV/A or 0.185V/A.
This means that if 1A is flow through the sensor, there will be 0.185V changes at Vout. Increment or decrement will depend on the current direction.

To get the resolution in terms of current, we need to divide the resolution in voltage with the sensitivity of the sensor. So, 4.76mV/bit divide by 0.185V/A we get 25mA/bit (neglecting the decimals). This means that increase in 1 bit constitute to increment of 25mA. Thus, to get the actual current value, what we need to do is just finding the difference between the instantaneous ADC value and initial ADC value (which is 512) and multiply it with the resolution (in current).

Let’s try out an example.
So, for my case, when there is no current flow, the sensor will gives out 2.44V as stated previously (Vcc in this case is 4.88V). However, when 1A of current flow the the sensor, the sensor will output 2.625V. (2.44V+0.185V). Convert to 10 bit system, (2.625/4.88)*1024, it will be around 551. (cant take decimals because ADC cant read decimals)

Then 551-512=39. After that, 39*25mA = 975mA which is almost 1A. That is the reason why we set the divide the routine at 512 and multiplication of 25.

To use those lcd_putstr(), lcd_2ndline(), lcd_init(), just remember to include the lcd.h file in the beginning of the code. read_adc_value() function will be explain later.

[sourcecode language=”cpp” wraplines=”true”]

if(SW1==0) // if switch on SK28B is pressed
{
while(SW1 == 0); // when the switch is released
condition = 1; // change the condition variable to 1,
}
[/sourcecode]

We need to do a while loop before changing the condition. This is because the microcontroller executes instructions very fast. The moment we press the button (maybe about 0.5 seconds) and release, it had already detected the button press and changed the “condition” to 1, and further executes functions under condition 1, and detected the button is pressed again, and changed “condition” back to 0, and executes instruction under condition 0 and keep on looping for several times. All these happen in 0.5 seconds without us noticing it. We need to add loop to wait the switch to be release before proceed the rest of program, thus the while (SW1 == 0); The program will stuck and wait at the while loop as long as the button is still pressed. Once it is released, the condition variable will changed to 1, and it will executes the codes under condition 1.

[sourcecode language=”cpp” wraplines=”true”]

if(condition==1) // the second mode of the program
{
lcd_clr();
lcd_putstr(“Peak”);
lcd_2ndline();
lcd_putstr(“00.000A”);
peakvalue=0; // need to initialize it to 0 first

while(condition>0)
{
value = read_adc_value(); // call read_adc_value() function
// the result return will keep in “value”
if(value>=512)
{
valueans =(value-512)*25; // mathematics

if(valueans<=peakvalue) // if the value obtain at this moment is less than the previous peak value
{valueans=peakvalue;} // this value will be replaced by the previous peak value

else // else (means if the value obtain is more than previous peak value)
{peakvalue = valueans;} // this value will ovewrite the previous peak value for record

lcd_goto(0x40); // go to second line
lcd_bcd(5,valueans); // and print
}

[/sourcecode]

This is the code under condition 1 (to show peak value only), and while value obtain from read_adc_value() is greater than 512. After the value obtain from read_adc_value() undergoes the mathematical operation (minus 512 and multiply by 25), it is being being compared to the previous peakvalue. Initially, the peakvalue will be 0. The value obtained must be greater than zero. Thus, it will jump to the “else” and the value inside “valueans” is copied to “peakvalue” and the “peakvalue” is no longer zero. The program will keep on looping inside the “while(condition>0)” loop as long as no button is pressed. So, the new value obtained from read_adc_value() will undergo the mathematical operation again and will be brought to compared with “peakvalue”. So, if “valueans” is greater than the “peakvalue” the same thing will happen (value inside “valueans is copied to “peakvalue”). However, if this time the “valueans” is less than the “peakvalue”, the value inside “valueans” will be replaced by the value inside “peakvalue”. Then the value inside the “valueans” will be printed in LCD.

Let’s take a look at other private functions.

[sourcecode language=”cpp” wraplines=”true”]

void delay_ms(unsigned int num) // delay is used during communicate with the LCD, can refer to lcd.c
{ // the function is expect to get an integer
while(num– > 0) // the following instruction will do how many times depends on the integer get
__delay_ms(1); // the __delay_ms(); was built in if you use XC8 compiler
}
[/sourcecode]

As mention earlier, the __delay_ms() function is built in macro if you use XC8 compiler.
Just remember that the __delay_ms() function cannot take a variable. (eg. __delay_ms(k) )
It must be a constant number. (eg. __delay_ms(5))
So if you are planning to do a variable delay, you can do like the code as shown above.
The “num” variable is used in the delay_ms() function, and do “num” times of __delay_ms(1);.

[sourcecode language=”cpp” wraplines=”true”]
void adc_initialize(void)
{
ADCON1=0b00001110; // setting ADCON1 register
ADCON2=0b10101010; // setting ADCON2 register
ADCON0=0b00000000; // setting ADcon0 register
} // detailed of the setting can refer to PIC18F2550 datasheet section 21.0 on 10bit ADC module
[/sourcecode]

Setting of ADCON register can refers to the datasheet. Let’s look at the example to set ADCON0 register below.
adcon0
From the datasheet, we can see that the register consist of 8 bits (bit-7 to bit-0).
The 7th and 6th bit will be 0. Since we are going to read the ADC value using AN0 or A0 pin, we set to channel 0. Thus, bit-5 to bit-2 should be zero. For initialization, bit-1 (GO) and bit-0 (ADON) set to 0. Both these bits will be used during ADC conversion.

Thus, simply configure ADCON0 by entering ADCON0=0b00000000;

To set a certain bit on that register, for example bit 0 in ADCON0, you can enter
ADCON0bits.ADON=1 (ADON is the bit’s name, see datasheet)

[sourcecode language=”cpp” wraplines=”true”]

unsigned int read_adc_value(void)
{
unsigned int temp = 0; // declare a temporary local variable to use
unsigned int temp_sum = 0; // declare a temporary local variable to use

for(unsigned int i=0; i<10; i++) // loop to take the reading 10 times and average it
{
ADCON0bits.GO=1; // start the 10 bit ADC system
while(ADCON0bits.DONE==1); // wait for it to done convertion
temp = ADRESH ; // the result will be 16bit, take the high 8bit to keep in the variable
temp=temp << 8; // shift the result to high set of it
temp = (temp | ADRESL); // logically OR them up
temp_sum = temp_sum + temp; // sum up the result because we are going to take the result for 10 times
}
temp_sum = temp_sum / 10; // after sum up 10 times, average it by dividing it by 10
return temp_sum; // return the value
}
[/sourcecode]

To read ADC value, we will need to convert it to digital value. Start the conversion by setting 1 to the GO bit in ADCON0. The microcontroller will further wait for the Go bit becomes 0, which indicates the ADC conversion is completed and the value will be in ADRESH and ADRESL register. (The MSB 2 bit at ADRESH and LSB 8 bit at ADRESL)

Since it is 10 bit conversion, we need to combine both high bits in ADRESH and the low bits ADRESL as well.
There are many ways to combine them together. For this program is like this:

First, retrieve the high 8 bit from ADRESH, put it in unsigned integer “temp” variable (an unsigned integer can store value up to 2 bytes which is 65,535, it’s more than enough). The binary value inside “temp” now will be

temp = 00000000 000000XX (XX represents value obtained from ADRESH)

temp=temp << 8; // shift the variable 8 bit to left and it should become:

temp = 000000XX 00000000 (after shifted left 8 bit)

temp = (temp | ADRESL); means perform bitwise “OR” operation on temp and ADRESL .

For those who maybe not familiar with digital. The OR truth table is as shown below.
or

temp      =  000000XX 00000000
ADRESL   =                 XXXXXXXX (value obtained from ADRESL register)

temp      =  000000XX XXXXXXXX

Finally, we had successfully retrieve the value from ADRESH and ADRESL and combined them together!!!

The “temp” is further being accumulate into “tempsum”.
The whole routine will repeated for 9 more iterations and the accumulated “tempsum” is divided by 10 to get the average value.
Lastly, the value inside “tempsum” is return to the code where this read_adc_value() function is called. Check out the video. BTW, if you have any question, please do discuss in our technical forum as we seldom check the comment section in tutorial site.

 

Sample Code

Buy

, ,

Related Post

AmpMeter using SK28A and 7-Segment Display

Smart Intruder Detector with PIR and GSM

Method to interface and use Flexibend sensor

Current Measuring using BB-ACS756 with SK28A

Leave a Reply

Your email address will not be published. Required fields are marked *