|
|
View previous topic :: View next topic |
Author |
Message |
mcmsat
Joined: 25 May 2018 Posts: 51 Location: Nigeria
|
Using CT For Measuring AC Current With 16F886 |
Posted: Sat Jul 09, 2022 1:36 am |
|
|
First of all I apologize for posting this topic in "Code Library". I noticed it lately and deleted it and bringing it here.
Please I was fiddling with a Current transformer to measure RMS Current. I used Arduino code without a driver header file. It is working in Arduino platform. But as I wanted to convert the code to CCS C platform I became confused in some areas, especially on:
Code: |
int16_t GetpeakTopeak() {
int16_t ctValueHigh = 0;
int16_t ctValueLow = ADC_RESOLUTION;
for (uint8_t i = 0; i < 40; i++) {
if (analogRead(CTmonitor) > ctValueHigh)
ctValueHigh = analogRead(CTmonitor);
if (analogRead(CTmonitor) < ctValueLow)
ctValueLow = analogRead(CTmonitor);
delayMicroseconds(500);
}
// Serial.print("High : "); Serial.print(ctValueHigh);
// Serial.print('\t');
// Serial.print("Low : "); Serial.println(ctValueLow);
return (ctValueHigh - ctValueLow);
} |
The above part of the code is entirely out of "setup" and "loop". As a low level apprentice in CCS C, I am confused as how to place it here. I believe this is the reason it fail to work for me in CCS C platform. Please Can someone correct me where i make mistakes in the conversion.
Code: | #include <16F886.h>
#device ADC = 10
#fuses INTRC_IO,NOWDT,MCLR,NOPROTECT
#use delay(clock = 8000000)
#use rs232(baud = 9600,xmit=PIN_C6,DISABLE_INTS,ERRORS)
#include <Flex_LCD2004.c>
#define ADC_RESOLUTION 1024
float avgResult=0;
float currentInmA=0;
long CTmonitor;
void init()
{
setup_adc(ADC_CLOCK_INTERNAL);
setup_adc_ports( sAN0 |sAN1 | VSS_VDD);// Select Analog Inputs
setup_comparator(NC_NC_NC_NC);
set_adc_channel(0); //Select AN0 as ADC Input for CTmonitor
}
int16 GetpeakTopeak() {
int16 ctValueHigh = 0;
int16 ctValueLow = ADC_RESOLUTION;
for (int i = 0; i < 40; i++) {
if (CTmonitor > ctValueHigh)
ctValueHigh = CTmonitor;
if (CTmonitor < ctValueLow)
ctValueLow = CTmonitor;
delay_ms(500);
}
return (ctValueHigh - ctValueLow);
for(int i=0;i<10;i++){
avgResult += GetpeakTopeak();
}
avgResult = avgResult/10.00;
// currentInmA = 0.0045 * avgResult;
currentInmA = 0.053 * avgResult;
avgResult = 0;
}
void main()
{
init(); // Configure peripherals/hardware
lcd_init(); // Initialize LCD module
lcd_gotoxy(1,1); //colum 1 row 1
lcd_putc("CURRENT TEST");
delay_ms(1); //Wait 1 ms
CTmonitor = read_adc(); //Read AN3 and store in MS2
delay_ms(1); //Wait 1 ms
while(TRUE)
{
lcd_gotoxy(5,2); // colum5 row 2
printf(lcd_putc,"%3.1g ",currentInmA);
printf("CURRENT = %3.1g \n\r",currentInmA);
delay_ms(500);
}
} |
The original Arduino code is this:
Code: |
#include "Arduino.h"
#include "LiquidCrystalWired.h"
#define LCD_ADDRESS (0x7c >> 1)
#define ROW_COUNT 2
#define COL_COUNT 16
#define ADC_RESOLUTION 1024
#define CTmonitor A1
unsigned long previousMillis = 0;// will store last time LCD was updated
const long interval = 1000;// interval at which to refresh (milliseconds)
LiquidCrystalWired lcd = LiquidCrystalWired(
ROW_COUNT, COL_COUNT, FONT_SIZE_5x8, BITMODE_8_BIT);
float avgResult=0;
float currentInmA=0;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
lcd.begin(LCD_ADDRESS, &Wire);
lcd.turnOn();
}
void loop() {
unsigned long currentMillis = millis();
for(int i=0;i<10;i++){
avgResult += GetpeakTopeak();
}
avgResult = avgResult/10.00;
// currentInmA = 0.0045 * avgResult;
currentInmA = 0.053 * avgResult;
// Serial.println(currentInmA);
avgResult = 0;
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
//111111111111111111111111111111111111111111111111111111111
lcd.setCursorPosition(0, 0);
lcd.print("L1:");
lcd.setCursorPosition(0, 3);
lcd.print(currentInmA,2);
//*******************************
if(currentInmA >= 100)
lcd.setCursorPosition(0, 9);
lcd.print("A ");
//*******************************100
if(currentInmA > 10 && currentInmA < 100)
lcd.setCursorPosition(0, 8);
lcd.print("A ");
//*******************************10
if(currentInmA < 10)
lcd.setCursorPosition(0, 7);
lcd.print("A ");
//*******************************1
Serial.print(" The Current RMS value is: ");
Serial.print(currentInmA,2);
Serial.println(" V ");
}
}
int16_t GetpeakTopeak() {
int16_t ctValueHigh = 0;
int16_t ctValueLow = ADC_RESOLUTION;
for (uint8_t i = 0; i < 40; i++) {
if (analogRead(CTmonitor) > ctValueHigh)
ctValueHigh = analogRead(CTmonitor);
if (analogRead(CTmonitor) < ctValueLow)
ctValueLow = analogRead(CTmonitor);
delayMicroseconds(500);
}
// Serial.print("High : "); Serial.print(ctValueHigh);
// Serial.print('\t');
// Serial.print("Low : "); Serial.println(ctValueLow);
return (ctValueHigh - ctValueLow);
} |
_________________ All is well even in the well! |
|
|
PrinceNai
Joined: 31 Oct 2016 Posts: 480 Location: Montenegro
|
|
Posted: Sat Jul 09, 2022 7:13 am |
|
|
There are some problems with the way you translated it. For example, you wrote:
Code: |
int16 GetpeakTopeak() {
int16 ctValueHigh = 0;
int16 ctValueLow = ADC_RESOLUTION;
for (int i = 0; i < 40; i++) {
if (CTmonitor > ctValueHigh)
ctValueHigh = CTmonitor;
if (CTmonitor < ctValueLow)
ctValueLow = CTmonitor;
delay_ms(500);
}
return (ctValueHigh - ctValueLow);
for(int i=0;i<10;i++){
avgResult += GetpeakTopeak();
}
avgResult = avgResult/10.00;
// currentInmA = 0.0045 * avgResult;
currentInmA = 0.053 * avgResult;
avgResult = 0;
}
|
Code after return never gets executed. This part
Code: |
for(int i=0;i<10;i++){
avgResult += GetpeakTopeak();
}
avgResult = avgResult/10.00;
// currentInmA = 0.0045 * avgResult;
currentInmA = 0.053 * avgResult;
avgResult = 0;
|
is in the main loop of the original code. Second, one of your delays is half a second long and you are executing that 40 times. Third, in the original code ADC channel gets read in GetpeakTopeak() function, which your code doesn't do, you read it only once.
I confess that it is unclear to me why ADC channel is read so many times, but you said it works in Arduino so i guess it must be so. I took the liberty to rearrange your code a bit. It is not tested.
Code: |
#include <16F886.h>
#device ADC = 10
#fuses INTRC_IO,NOWDT,MCLR,NOPROTECT
#use delay(clock = 8000000)
#use rs232(baud = 9600,xmit=PIN_C6,DISABLE_INTS,ERRORS)
#include <Flex_LCD2004.c>
#define ADC_RESOLUTION 1024 // max. ADC output
float avgResult=0;
float currentInmA=0;
long CTmonitor; // in Arduino this is a pin or analog channel definition
void init()
{
setup_adc(ADC_CLOCK_INTERNAL);
setup_adc_ports( sAN0 |sAN1 | VSS_VDD);// Select Analog Inputs
setup_comparator(NC_NC_NC_NC);
set_adc_channel(0); //Select AN0 as ADC Input for CTmonitor
}
// function to read ADC channell 0
int16 GetpeakTopeak() {
int16 ctValueHigh = 0; // strange, 0 is high and 1024 is low
int16 ctValueLow = ADC_RESOLUTION;
int16 Analog_Result = 0; //variable to store analog reads
set_adc_channel(0); // probably not even needed, as the ADC channell was already set
for (int i = 0; i < 40; i++) {
Analog_Result = read_adc(); // read ADC every time
if (Analog_Result > ctValueHigh)
ctValueHigh = Analog_Result;
if (Analog_Result < ctValueLow)
ctValueLow = Analog_Result;
// delay_ms(500); // you wait more than 20s here!!!
delay_us(500);
}
return (ctValueHigh - ctValueLow); // retutn value that was somehow averaged
}
void main()
{
init(); // Configure peripherals/hardware
lcd_init(); // Initialize LCD module
lcd_gotoxy(1,1); //colum 1 row 1
lcd_putc("CURRENT TEST");
delay_ms(1); //Wait 1 ms
// CTmonitor = read_adc(); //Read AN3 and store in MS2.
delay_ms(1); //Wait 1 ms
// *********************************************************
while(TRUE){
for(int i=0;i<10;i++){ // you are reading ADC 400x!!!
avgResult += GetpeakTopeak();
}
avgResult = avgResult/10.00;
currentInmA = 0.053 * avgResult;
avgResult = 0;
lcd_gotoxy(5,2); // colum5 row 2
printf(lcd_putc,"%3.1g ",currentInmA);
printf("CURRENT = %3.1g \n\r",currentInmA);
delay_ms(500);
}
}
|
|
|
|
PrinceNai
Joined: 31 Oct 2016 Posts: 480 Location: Montenegro
|
|
Posted: Sat Jul 09, 2022 9:51 am |
|
|
A suggestion. Get rid of the floats. By declaring float avgResult=0 as unsigned int16 the compiled code (without LCD parts) is hugely smaller. With float ROM 12%, RAM 9%, with int16 ROM 5%, RAM 7%. Understand that when reading ADC (or anything else inside a PIC) you are reading, writing and dealing with discrete numbers, in this case numbers from 0 - 1023. Even if you add them together 10 times, they still nicely fit into a 16 bit integer. So for all the mathematics with your displayed resolution floats are not needed.
It should work with while true() like this:
Code: |
while(TRUE){
for(int i=0;i<8;i++){ // you are reading ADC 400x!!!
avgResult += GetpeakTopeak();
}
avgResult = avgResult/8;
// avgResult = avgResult >> 3;
currentInmA = 0.053 * (float)avgResult;
avgResult = 0;
// lcd_gotoxy(5,2); // colum5 row 2
// printf(lcd_putc,"%3.1g ",currentInmA);
// printf("CURRENT = %3.1g \n\r",currentInmA);
delay_ms(500);
}
|
Note that I changed i to 8, because it translates to only 6 RRF instructions in the final code (division by 8 and shifting produce exactly the same code) and is again faster than division by ten. Old hands here will tell if my casting is OK. It would also be nice to know what kind of signal you are sampling. The rate of change. GetpeakTopeak() function takes 20+ ms to return a result, then you do it 10 times before displaying the end result. So 200+ ms for one result plus 500ms waiting... If the signal change rate isn't very, very slow I have much doubt in the end result. If it's a 50 or 60Hz signal, the whole approach is wrong. But than again, I might have misread the code or the philosophy behind it. You are measuring AC signal, otherwise peak to peak has no meaning. With AC phase comes into play, but that doesn't matter in this case. But if I remember my lessons from more than 20 years ago, Nyquist and Shannon proved that if you want to reconstruct your signal, a sampling rate of at least twice the frequency is needed. Per period, of course. |
|
|
mcmsat
Joined: 25 May 2018 Posts: 51 Location: Nigeria
|
|
Posted: Sat Jul 09, 2022 1:12 pm |
|
|
I am sorry for my late reply. It was a natural factor. I thank the contributor to my questions. let me check it out now _________________ All is well even in the well! |
|
|
mcmsat
Joined: 25 May 2018 Posts: 51 Location: Nigeria
|
|
Posted: Sat Jul 09, 2022 2:00 pm |
|
|
@ PrinceNai, I beg you to help me translate this code the way it should be so that I will study it as part of my lessons. everything goes to Arduino but my fun started with CCS C. Though not as a professional. I do some small things in my garden as a hobby.
This is the original Arduino that I have compiled in my Arduino IDE and it is working on my Uno R3. My curiosity is how can I make it work on this 16F886 I have it's board laying around.
Code: | #define ADC_RESOLUTION 1024
#define CTmonitor A1
float avgResult=0;
float currentInmA=0;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
unsigned long currentMillis = millis();
for(int i=0;i<10;i++){
avgResult += GetpeakTopeak();
}
avgResult = avgResult/10.00;
currentInmA = 0.053 * avgResult;
Serial.println(currentInmA);
avgResult = 0;
}
int16_t GetpeakTopeak() {
int16_t ctValueHigh = 0;
int16_t ctValueLow = ADC_RESOLUTION;
for (uint8_t i = 0; i < 40; i++) {
if (analogRead(CTmonitor) > ctValueHigh)
ctValueHigh = analogRead(CTmonitor);
if (analogRead(CTmonitor) < ctValueLow)
ctValueLow = analogRead(CTmonitor);
delayMicroseconds(500);
}
// Serial.print("High : "); Serial.print(ctValueHigh);
// Serial.print('\t');
// Serial.print("Low : "); Serial.println(ctValueLow);
return (ctValueHigh - ctValueLow);
} |
Please any one who can translate it here for me _________________ All is well even in the well! |
|
|
PrinceNai
Joined: 31 Oct 2016 Posts: 480 Location: Montenegro
|
|
Posted: Sat Jul 09, 2022 5:54 pm |
|
|
Some three posts back there is a complete code. That compiles without any problem. |
|
|
mcmsat
Joined: 25 May 2018 Posts: 51 Location: Nigeria
|
|
Posted: Sat Jul 09, 2022 11:36 pm |
|
|
PrinceNai wrote: | Some three posts back there is a complete code. That compiles without any problem. |
@PrinceNai, the code is working now! So much thanks! I am so much happy for this!
Now, I have to study the code to see my mistakes in case for another translation. Thanks Again!
The Code works exactly like this:
Code: |
#include <16F886.h>
#device ADC = 10
#fuses INTRC_IO,NOWDT,MCLR,NOPROTECT
#use delay(clock = 8000000)
#use rs232(baud = 9600,xmit=PIN_C6,DISABLE_INTS,ERRORS)
#include <Flex_LCD2004.c>
#define ADC_RESOLUTION 1024 // max. ADC output
float avgResult=0;
float currentInmA=0;
long CTmonitor; // in Arduino this is a pin or analog channel definition
void init()
{
setup_adc(ADC_CLOCK_INTERNAL);
setup_adc_ports( sAN0 |sAN1 | VSS_VDD);// Select Analog Inputs
setup_comparator(NC_NC_NC_NC);
set_adc_channel(0); //Select AN0 as ADC Input for CTmonitor
}
// function to read ADC channell 0
int16 GetpeakTopeak() {
int16 ctValueHigh = 0; // strange, 0 is high and 1024 is low
int16 ctValueLow = ADC_RESOLUTION;
int16 Analog_Result = 0; //variable to store analog reads
set_adc_channel(0); // probably not even needed, as the ADC channell was already set
for (int i = 0; i < 40; i++) {
Analog_Result = read_adc(); // read ADC every time
if (Analog_Result > ctValueHigh)
ctValueHigh = Analog_Result;
if (Analog_Result < ctValueLow)
ctValueLow = Analog_Result;
// delay_ms(500); // you wait more than 20s here!!!
delay_us(500);
}
return (ctValueHigh - ctValueLow); // retutn value that was somehow averaged
}
void main()
{
init(); // Configure peripherals/hardware
lcd_init(); // Initialize LCD module
lcd_gotoxy(1,1); //colum 1 row 1
lcd_putc("CURRENT TEST");
delay_ms(1); //Wait 1 ms
// CTmonitor = read_adc(); //Read AN3 and store in MS2.
delay_ms(1); //Wait 1 ms
// *********************************************************
while(TRUE){
for(int i=0;i<10;i++){ // you are reading ADC 400x!!!
avgResult += GetpeakTopeak();
}
avgResult = avgResult/10.00;
currentInmA = 0.053 * avgResult;
avgResult = 0;
lcd_gotoxy(5,2); // colum5 row 2
printf(lcd_putc,"%3.1g ",currentInmA);
printf("CURRENT = %3.1g \n\r",currentInmA);
delay_ms(500);
}
} |
You later said this:
Code: |
Code:
while(TRUE){
for(int i=0;i<8;i++){ // you are reading ADC 400x!!!
avgResult += GetpeakTopeak();
}
avgResult = avgResult/8;
// avgResult = avgResult >> 3;
currentInmA = 0.053 * (float)avgResult;
avgResult = 0;
// lcd_gotoxy(5,2); // colum5 row 2
// printf(lcd_putc,"%3.1g ",currentInmA);
// printf("CURRENT = %3.1g \n\r",currentInmA);
delay_ms(500);
}
|
As part of learning I will still do this to see something for myself. _________________ All is well even in the well! |
|
|
PrinceNai
Joined: 31 Oct 2016 Posts: 480 Location: Montenegro
|
|
Posted: Sun Jul 10, 2022 8:33 am |
|
|
I'm glad it works. I suggested division by 8 because it is slightly faster. But the biggest difference is defining avgResult as unsigned int 16. That way you always manipulate only two bytes instead of four (float variable takes four bytes). |
|
|
mcmsat
Joined: 25 May 2018 Posts: 51 Location: Nigeria
|
|
Posted: Sun Jul 10, 2022 9:24 am |
|
|
PrinceNai wrote: | I'm glad it works. I suggested division by 8 because it is slightly faster. But the biggest difference is defining avgResult as unsigned int 16. That way you always manipulate only two bytes instead of four (float variable takes four bytes). |
I thank you for your help PrinceNai. You are such a good concerning my request. I have refined some definitions and adapted it for ZMPT101B AC voltage sensor and it worked like magic. It's better for me than Emonlib in Arduino. I control. I don't know if it can be used with ADS1115 unlike Emonlib in Arduino in which ADC pin is static in the header file itself. I will try the ADS1115 as I have 2pcs of the module.
Thanks so much. _________________ All is well even in the well! |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Mon Jul 11, 2022 1:55 am |
|
|
Well done.
Change the title of the thread to include (solved), if you are happy. |
|
|
PrinceNai
Joined: 31 Oct 2016 Posts: 480 Location: Montenegro
|
|
Posted: Mon Jul 11, 2022 9:46 am |
|
|
I have to correct myself when I said the end result won't be good. Went through the code again and for a 50Hz signal it makes perfect sense. GetpeakTopeak() function scans the analog input for exactly one period (40 x 500us equals 20ms, which is one whole period at 50Hz) and finds out the difference between highest and lowest sample. Then this difference is averaged from 10 results and converted to current . |
|
|
mcmsat
Joined: 25 May 2018 Posts: 51 Location: Nigeria
|
Solved |
Posted: Mon Jul 11, 2022 2:47 pm |
|
|
I thank PrinceNai again. _________________ All is well even in the well! |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Tue Jul 12, 2022 12:19 am |
|
|
There are a couple of tweaks/comments.
First on the PIC, you should not use ADC_CLOCK_INTERNAL above 1MHz
CPU clock. Doing so results in poor ADC accuracy. You should instead be
using the master oscillator divided to give the correct sample time. The
datasheet has a table of the recommended divisions for different master
clock rates.
The second thing is that the timing of the sampling is going to be slightly
slow. The ADC takes time to read, and the loop itself takes time, so as
currently written that sample loop does not take 20mSec, instead it is
sampling for more like 22mSec. This will potentially give slight errors in
the result.
So. Change the ADC to use a fixed divisor, and then work out how long
the ADC readings will take. Then subtract this (and a few uSec more for
the loop), from the delays for the loop.
This should improve the results slightly. |
|
|
mcmsat
Joined: 25 May 2018 Posts: 51 Location: Nigeria
|
|
Posted: Tue Jul 12, 2022 12:28 am |
|
|
Ttelmah wrote: | There are a couple of tweaks/comments.
First on the PIC, you should not use ADC_CLOCK_INTERNAL above 1MHz
CPU clock. Doing so results in poor ADC accuracy. You should instead be
using the master oscillator divided to give the correct sample time. The
datasheet has a table of the recommended divisions for different master
clock rates.
The second thing is that the timing of the sampling is going to be slightly
slow. The ADC takes time to read, and the loop itself takes time, so as
currently written that sample loop does not take 20mSec, instead it is
sampling for more like 22mSec. This will potentially give slight errors in
the result.
So. Change the ADC to use a fixed divisor, and then work out how long
the ADC readings will take. Then subtract this (and a few uSec more for
the loop), from the delays for the loop.
This should improve the results slightly. |
Thanks Ttelmah for the advice. I am doing every thing I could to learn pic in CCS C. _________________ All is well even in the well! |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|