|
|
View previous topic :: View next topic |
Author |
Message |
luisjoserod
Joined: 10 Jan 2019 Posts: 16 Location: Maracaibo, Venezuela
|
Sampling both 1kHz and very slow Signals ADC |
Posted: Wed Feb 13, 2019 4:19 pm |
|
|
Hello Guys, i'm working for months on this project, it's not the first time that i work with CCS but i'm learning a lot of things. The project it's supposed to adquire the waveform of a 1000Hz Signal and send it via serial. I managed to sample slow aperiodic signals very well with TIMER1, but now i want to use TIMER0 for the fast 1000Hz periodic signal.
Here's my full code:
Code: |
#include <16F876A.h>
#device adc=10
#FUSES NOWDT
#FUSES XT
#FUSES PUT
#FUSES NOPROTECT
#FUSES NODEBUG
#FUSES BROWNOUT
#FUSES NOLVP
#FUSES NOCPD
#FUSES NOWRT
#FUSES RESERVED
#use delay(clock=4000000)
#use rs232(baud=9600,xmit=PIN_C6,rcv=PIN_C7,parity=N)
#use standard_io (B)
#use standard_io (C)
#include <stdlib.h>
#define LED PIN_B6
#define ON PIN_B7
// different commands
char en1 [] = "aperiodic";
char en2 [] = "periodic";
char wk [] = "wakeup";
char signal[50];
char input_str[10];
int LIMIT=2, max_sample=30;
int8 cont=0, medicion;
int1 muestrear=FALSE;
#int_TIMER0
void TIMER0_isr(void)
{
/*if(cont0==LIMIT0)
{
muestrear=TRUE;
output_toggle(LED);
cont0=0;
}
set_TIMER0(189); //inicializa el timer1
cont0++;*/
muestrear=TRUE;
output_toggle(LED);
set_TIMER0(189);
}
#int_TIMER1
void TIMER1_isr(void)
{
if(cont==LIMIT)
{
muestrear=TRUE;
output_toggle(ON);
cont=0;
}
set_TIMER1(3036);
cont++;
}
void periodic();
void aperiodic();
void cap();
void commands_menu();
void main() {
set_tris_b(0b01111111);
setup_port_a( ALL_ANALOG );
setup_adc(ADC_CLOCK_DIV_8);
set_adc_channel( 0 );
delay_us(20);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
enable_interrupts(INT_TIMER1);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
enable_interrupts(INT_TIMER0);
enable_interrupts(GLOBAL);
set_TIMER0(189);
set_TIMER1(3036);
while(TRUE)
{
commands_menu();
}
}
void periodic()
{
int sample=0;
set_TIMER0(189);
do
{
if (muestrear)
{
medicion = read_adc() >> 2;
putc(medicion);
muestrear=FALSE;
sample++;
}
} while (sample<max_sample);
}
void aperiodic()
{
int sample=0;
set_TIMER1(3036);
do
{
if (muestrear)
{
medicion = read_adc() >> 2;
putc(medicion);
muestrear=FALSE;
sample++;
}
} while (sample<max_sample);
LIMIT=2;
}
void cap()
{
gets(input_str);
LIMIT = 2*atoi(input_str);
gets(input_str);
max_sample = atoi(input_str);
aperiodic();
}
void commands_menu()
{
gets(input_str);
if (!strcmp(input_str,wk)) // to check connectivity before enter to menu
{
printf("OK");
gets(input_str);
if (!strcmp(input_str,en1))
aperiodic();
if (!strcmp(input_str,en2))
cap();
}
}
|
I'm using PIC16F876A with crystal 4Mhz, can you give me simple, basic code of using TIMER0 to trigg the ADC at least at 2*1000Hz ?
Here's how actually i make the sampling with delays, but it's rudimentary and it's not what i want. I want precission with TIMER0 if possible, i readed a lot of posts by Telmah but i'm still very confused.
Code: |
void sample_uggly(struct data *ptr)
{
for(int w=0;w<30;w++)
{
ptr->sample[w]= read_adc() >> 2;
delay_us(32);
for(int w=0;w<max_sample;w++)
{
putc(ptr->sample[w]);
}
}
|
Thanks for reading.
Last edited by luisjoserod on Fri Feb 15, 2019 6:52 pm; edited 1 time in total |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19551
|
|
Posted: Thu Feb 14, 2019 1:19 am |
|
|
No....
Use a faster master oscillator.
Do it a very different way. Problem is that the ADC reading takes a
lot of time, and then having two interrupts both running just doesn't give
enough time.
Why have you got two interrupts?. It is always better to just have one
'tick', and do other things in this (if you want them at an interval). Getting
into and out of an interrupt uses so much time.
Timer1, seems to only be toggling an LED, so do this in Timer0 instead.
However best to not use the timer interrupt at all.
(not complete, but an 'overview'):
Code: |
#include <16F876A.h>
#device adc=8
//seriously, why are you reading the ADC as 10bit, then wasting
//instructions rotating it. Just read as 8bit
#FUSES NOWDT
#FUSES XT
#FUSES PUT
#FUSES NOPROTECT
#FUSES NODEBUG
#FUSES BROWNOUT
#FUSES NOLVP
#FUSES NOCPD
#FUSES NOWRT
#FUSES RESERVED
#use delay(clock=4000000)
#use rs232(baud=57600,xmit=PIN_C6,rcv=PIN_C7,parity=N,ERRORS)
//The hardware UART, should _always_ have ERRORS selected unless
//you are performing error handling yourself.
//Note the baud rate...
#use standard_io (B)
#use standard_io (C)
#include <stdlib.h>
#define LED PIN_B6
int8 medicion;
int1 newval=FALSE;
#INT_ADC
{
//Will trigger 2000* per second
static int8 tick=0;
if (tick++>=200)
{
tick=0;
output_toggle(LED); //flash an LED 10* per second
//demo of how to use one tick to do two jobs.
}
medicion=read_adc(ADC_READ_ONLY);
clear_interrupt(INT_CCP2);
//ADC has already done it's conversion at this point, so just read.
newval=TRUE;
}
void main(void)
{
set_tris_b(0b01111111);
setup_port_a( AN0);
//don't select channels to the ADC multiplexer that you are not
//using. Doing so increases noise on the ADC.
setup_adc(ADC_CLOCK_DIV_8);
//Hurrah!. Good selection.
set_adc_channel( 0 );
delay_us(20);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_4);
//DIV_8 won't give an integer division for 2000* per second
setup_ccp2(CCP_COMPARE_RESET_TIMER);
CCP_2=124; //125 counts of timer1 gives 2000* per second
//When this CCP triggers, it'll automatically start the ADC. The ADC will
//then interrupt when it has completed it's conversion
enable_interrupts(GLOBAL);
enable_interrupts(INT_ADC);
while (TRUE)
{
if (newval)
{
newval=FALSE;
putc(medicion);
}
}
}
//There is an issue with your baud rate. At 9600bps, it basically takes 1mSec
//to send a character. If you are generating new values 2000* per second
//You need a baud rate fast enough to send these. So >20000bps.
|
It 'costs' typically about 60 instruction times to get into and out of an
interrupt. With two running all the time, your processor would have little
spare time. One at 2000Hz, would be using about 20% of the entire
processor time (500 instructions between each interrupt, and then the
interrupt has to actually 'do' something). Speeding up the processor
would help a lot. |
|
|
luisjoserod
Joined: 10 Jan 2019 Posts: 16 Location: Maracaibo, Venezuela
|
|
Posted: Thu Feb 14, 2019 10:21 am |
|
|
Thanks for your beautiful answer and fast response!
I was really starting to feel desperated about it.
But now it's more clear to me how to get this done.
In order to do a succesful compilation of the code you provided
i'd to change #INT_ADC for #INT_AD, declared a function name
and also changed 57600 to 62500 (but not a solution at the end)
*** Error 96 "main.c" Line 19(5,62): Baud rate out of range (try 62500)
My RX device has only this possible configuration values:
2400, 4800, 9600 (default), 14400, 19200, 38400, 57600 and 115200.
Selecting any of these values >20000 gives the same error,
it seems i should use a faster master oscillator to get rid of that error.
Ttelmah wrote: | //seriously, why are you reading the ADC as 10bit, then wasting
//instructions rotating it. Just read as 8bit
|
I'm doing 10bits ADC Readings i'm order to reduce noise
or that's what i think i'm doing (shifting the 2 less significant bits)
but correct me if i'm wrong. When doing physical tests,
with 8 bits readings there's an error of 30mV wich is greater than
the resolution (1/255)*5V approx 20mV.
I thought using 10bits reading, with that higher resolution
(1/1023)*5 approx 5mV would have better results but
for my surprise i got the same 30mV error, any idea why?
But of course;
Ttelmah wrote: | //don't select channels to the ADC multiplexer that you are not
//using. Doing so increases noise on the ADC.
|
This will help a lot, i wasn't conscious of that!
Ttelmah wrote: | One at 2000Hz, would be using about 20% of the entire
processor time (500 instructions between each interrupt, and then the
interrupt has to actually 'do' something)
|
I'm sorry but this is the only thing i don't get, processor time isn't 1Mhz ?
+++++++++++++++++++++++++++++++++++++++++++
Finally, let's say (alternative case) i keep using 4Mhz at 9600Bauds,
and that i don't want to send samples in real time as we're tryingnow,
but to store N samples (30~50) in an arry, them turn off the ADC,
and take all the time needed for sending data.
This is because the RX doesn't have the ability to update
the graph screen, it just took a whole package of data
and makes static graph. For now i don't need dynamic graph.
Store those values would cost me a LOT of RAM memory,
do you have any idea of how to implement and optimized version of this idea?
Thanks in advance,
Luis. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9246 Location: Greensville,Ontario
|
|
Posted: Thu Feb 14, 2019 10:46 am |
|
|
comments
1) PIC clock speed. Use a 20MHx xtal. That way the PIC will run 5X faster so you can increase the UART speed to 115200 Baud, I just checked.
2) ADC readings. Unless your're really good at PCB layout, wiring,etc. use 8 bit ADC mode. Analog noise can be very difficult to eliminate when using 10 ,12 or 16 bit ADC peripherals ! Hopefully you have an oscilloscope to SEE the signal. Even a 35 year old, 20MHz CRT will be a GREAT help.
Jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19551
|
|
Posted: Thu Feb 14, 2019 12:16 pm |
|
|
I had a nasty feeling 57600 would require a higher clock rate....
At 4MHz clock the CPU executes 1MIPS. Peripherals are also generally
clocked off the same Fosc/4 frequency.
The 8bit ADC setting seturns the top 8bits of the ADC register. Exactly
the same as you will get by rotating by two bits.
Your ADC noise is dependant on the layout to the input, and the noise
on your supply rail. Remember you are using the supply as your Vref.
Add an external RAM nenory. Preferably parallel accessed so you only
need a couple of instructions to output an address and save a byte.
You can then store lots of values. |
|
|
luisjoserod
Joined: 10 Jan 2019 Posts: 16 Location: Maracaibo, Venezuela
|
|
Posted: Thu Feb 14, 2019 7:41 pm |
|
|
Thanks Ttelmah and temtronic, you're awesome
Ttelmah wrote: | The 8bit ADC setting seturns the top 8bits of the ADC register. Exactly the same as you will get by rotating by two bits.
|
I really appreciate your knowledge, i didn't knew this.
From now on i will use 8bits.
2000Hz didn't work, doing some math
i realized that the minimum freq. required
to draw a fancy 1kHz wave it's about 17khz.
I don't have a 20Mhz XTAL on hand,
but i changed the code and run the simulation.
The result is perfect, i'd captured 2 exactly periods
and the waveform it's pretty good.
Here's full code:
Code: |
#include <16F876A.h>
#device adc=8
#FUSES NOWDT
#FUSES HS
#FUSES PUT
#FUSES NOPROTECT
#FUSES NODEBUG
#FUSES BROWNOUT
#FUSES NOLVP
#FUSES NOCPD
#FUSES NOWRT
#FUSES RESERVED
#use delay(clock=20000000)
#use rs232(baud=9600,xmit=PIN_C6,rcv=PIN_C7,parity=N,ERRORS)
// i'm still using 9600 , but don't worry, i dont want to send it in real time
#use standard_io (B)
#use standard_io (C)
#include <stdlib.h>
#define LED PIN_B6
char en1 [] = "1kh";
char signal[34], input_str[10];
int8 max_sample=34; // 17*2=34 this will give me 2 periods
int8 medicion;
int1 newval=FALSE;
#INT_AD
void ADC_ready(void)
{
static int8 tick=0;
if (tick++>=200) // i didn't changed this, but i know the meaning of using ticks here now :D
{
tick=0;
output_toggle(LED);
}
newval=TRUE;
medicion=read_adc(ADC_READ_ONLY); // thanks, i didn't know i were wasting time with read_adc()
clear_interrupt(INT_CCP2);
}
void periodic();
void menu();
void main(void)
{
set_tris_b(0b01111111);
setup_port_a( AN0 );
setup_adc(ADC_CLOCK_DIV_32); // to guarantee minimum Tad= 1.6us :D Hurrah! Again
set_adc_channel( 0 );
delay_us(20);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_2); // will give integer division for 17kHz :D
setup_ccp2(CCP_COMPARE_RESET_TIMER);
CCP_2=147; // 147 counts gives 17khz :D
while (TRUE)
{
menu();
}
}
void periodic(){
enable_interrupts(INT_AD); // i moved this
enable_interrupts(GLOBAL); // should i keep this line ?
int sample=0;
do
{
if (newval)
{
newval=FALSE;
signal[sample]=medicion;
sample++;
}
} while (sample<max_sample);
disable_interrupts(INT_AD); //at this moment i disabled it because i don't want to have running
// the adc when user doesn't want to measure.
// that command menu will have another functions different than adc
for (int i = 0; i < max_sample; ++i) // is this the best way of send data at the outside the INT_AD?
{
putc(signal[i]);
}
}
void menu()
{
gets(input_str);
if (!strcmp(input_str,en1))
periodic();
}
|
Please, tell me what you think about the commented lines.
Finally, I have another important question (because i never used INT_AD or CCP2 before):
1. I'm thinking to use the ticks inside INT_AD
actually to change the sampling time, but keeping
CPP_2=147. if i make ticks an int16 or int32 i could manage to wait
long times for very slow functions like a Sensor Temp.
BUT If i move the read_adc statement inside the
if (tick++>=200) the ADC will adquire values
several times more than needed (every time it
enter INT_AD instead every time if satisfy if statemente)
That could be a problem? it gonna be better to do different
setups of CPP2 and TIMER1 in subfunctions for all the time
cases i need to cover?
Can you provide me some code illustrating
How to combine this fast 17kHz readings with slow readings?
Maybe i'm thinking with a wrong idea like my first post :D
Thanks Again for your time,
Luis. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19551
|
|
Posted: Fri Feb 15, 2019 1:50 am |
|
|
Yes, you do need to keep the enable_interrupts(GLOBAL).
The point about 'ticks' was to show how to do something else at a lower
frequency than an interrupt.
Yes you could use it to do something else, but 'beware' if this takes
much time.
You can decrement and test an int8, in just a couple of instructions
and int16 only involves a handful.
A basic 'read_adc', involves the ADC going off for 13 cycles of the ADC
clock, the returning the value. Using the CCP, the ADC is being started
and does this all while the processor is still doing other thimgs. It
interrupts when everything has finished, so you can then just read the
value. So you get the ADC interrupt at the interval determined by the
CCP, but 'delayed' by the time the ADC takes to do the conversion.
There is an issue on a real chip, since your code 'drops off the end'
after the for loop finishes. On a real chip this will result in the last
couple of characters being lost as the chip then goes to sleep before
these have been sent...
Now it sounds as if you are talking about wanting to read other ADC
channels?. If so, you have to remember that the ADC requires the
channel is selected for Tacq, before you start reading. If you want
to read another value, then stop the interrupt select the channel, pause,
and read the other channel. There is only one ADC. Though lots of
channels, only one actual ADC (some chip have more). Better honestly
to do this outside the interrupt. |
|
|
luisjoserod
Joined: 10 Jan 2019 Posts: 16 Location: Maracaibo, Venezuela
|
|
Posted: Fri Feb 15, 2019 6:50 pm |
|
|
Your last message was very helpful
Ttelmah wrote: | Now it sounds as if you are talking about wanting to read other ADC
channels? |
Actually, yes.
I would like to have one command for sampling channel 0 (fast channel)
and other command for sampling channel 1 (slow channel)
I think i got what you're saying about how to change those channels,
but just to be sure, this is what you meant? or should i explicit disable
INT_AD in conjunction with INT_GLOBAL?
I did some simulations and couldn't clarify it by myself.
Code: | .... // some code
....
disable_interrupts( GLOBAL );
set_adc_channel( 0 );
delay_us(20);
enable_interrupts( INT_AD );
enable_interrupts( GLOBAL );
...
... // some other code here to process data
...
disable_interrupts( GLOBAL );
set_adc_channel( 1 );
delay_us(20);
enable_interrupts( INT_AD );
enable_interrupts( GLOBAL );
... // some code to process data
... |
It took me all day long to make this rudimentary modification
for sampling the slow channel (still using channel 0, for test purpose)
It works, but still needs improvements :
Code: | #INT_AD
void ADC_ready(void)
{
static unsigned int32 slow_tick=0; // i had to make this big to wait for 6s
static unsigned int16 mid_sec=0;
if (mid_sec++>=8503) // just and indicator of ADC Power ON ever 0.5 s :D
{
mid_sec=0;
output_toggle(CLOCK_LED);
}
if (slow_tick++>=102041) // 102041 interrupts (every int is 58.8uS) are 6 s
{
slow_tick=0;
output_toggle(SLOW_LED);
newval_slow=TRUE;
medicion_slow=read_adc(ADC_READ_ONLY);
}
newval_fast=TRUE; //
medicion=read_adc(ADC_READ_ONLY); // i would like to put this lines inside a if or switch case
clear_interrupt(INT_CCP2); // that way i would have different cases and only 1 flag & 1 medicion var
}
|
As you see, i have 2 different variable to keep the reading, and 2 different flags.
That's not cool. I'm looking forward to make a switch case
checking for the command received, i know C language doesn't allow to check
for strings on switch but looking deep on the forums i found this:
http://www.ccsinfo.com/forum/viewtopic.php?t=57317
You were very helpful on that thread, and ManuSisko did something in his #INT_TIMER1
very close to what I'm trying to do in my #INT_AD:
Code: | ...
...
int cmdToExe = cmdBuff[0];
switch(cmdToExe){
case CMD_CLR_TIME:
timeH = 0;
timeL = 0;
totalCMDCount += CMD_CLR_TIME;
break;
case CMD_T0_ON:
enable_interrupts(INT_TIMER0);
totalCMDCount += CMD_T0_ON;
break;
...
...
|
It's ok to only look at the first character?
Otherwise, i would have to make sure that
every command starts with different char
Thanks for your time,
Luis |
|
|
|
|
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
|