|
|
View previous topic :: View next topic |
Author |
Message |
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
Not interrupting at the expected rate? (via MPLAB sim) |
Posted: Mon Jul 26, 2010 7:19 am |
|
|
Hi, I've finally gotten simulation happening under MPLAB so I can time various functions etc., but it's indicating that my interrupt routine isn't happening at the rate I calculate?
PIC: 18F2620 with 20MHz crystal, 1/4cycle
Compiler: CCS 4.057
MPLAB IDE 8.53
Timer0 interrupt is set to RTCC_INTERNAL|RTCC_8_bit, which by my calculations should be:
20MHz => 5MHz cycle
5,000,000 / 256 = interrupt every 51.2us
but according to MPLAB sim's stopwatch, it's happening about every 102.2 - 102.6us?
Can anyone tell if I've done something wrong here? Code stripped back, still getting the same timing; Interrupt sets flags for which ADC channel to read, and whether to read, plus servicing of the debounce system and an LED PWMing up and down.
.h file:
Code: | #include <18F2620.h>
#device adc=8
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES HS //High speed Osc (> 4mhz)
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOIESO //Internal External Switch Over mode disabled
#FUSES BROWNOUT //Reset when brownout detected
#FUSES BORV21 //Brownout reset at 2.1V
#FUSES NOPUT //No Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOCPB //No Boot Block code protection
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOFCMEN //Fail-safe clock monitor disabled
#FUSES NOXINST //Extended set extension and Indexed Addressing mode enabled
#FUSES NOPBADEN //PORTB pins are configured as digital I/O on RESET
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES NOMCLR //Master Clear pin used for I/O
#use delay(clock=20000000)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8) |
.c file (my breakpoint is at the start of the RTCC_isr "rtccCount++", NOT under the "every 2nd time" bit that follows, so it's not an obvious issue like that):
Code: | #include "F:\PICC_Code\proj3accel.h"
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdlibm.h>
#include <string.h>
#define MAX_DUTY 255
#byte timer0low = 0xfd6 //direct access to the TMR0L byte
volatile int8 duty_cycle = 0; // Start with 0 duty cycle
volatile int1 increment = 1;
volatile int1 startUp = 1;
volatile int8 rtccCount = 0;
volatile int8 rtccCountLED = 0;
volatile int8 ADC_proc = 0;
volatile int1 ADC_flag = 0;
volatile int1 butt1On = 0;
volatile int1 butt2On = 0;
volatile int8 butt1Count = 0;
volatile int8 butt2Count = 0;
volatile int1 active = 1;
volatile int1 butt1Responding = 0;
volatile int1 butt2Responding = 0;
volatile int8 butt1RelCnt = 0;
volatile int8 butt2RelCnt = 0;
volatile int16 adc_value_x;
volatile int16 adc_value_y;
volatile int16 adc_value_z;
void pulseLED(void)
{
if(active)
{
if(startUp)
{
if(increment != 0)
{
duty_cycle++;
if(duty_cycle == MAX_DUTY) increment = 0;
}
else
{
duty_cycle--;
if(duty_cycle == 194) increment = 1;
}
}else
{
duty_cycle--;
}
}else
{
duty_cycle = 0; //clear
}
set_pwm1_duty(duty_cycle);
//debounce routine
if(butt1On)
{
if(butt1Count <255) butt1Count++; //don't bother counting over 255
}else
{
butt1Count = 0;
if(butt1RelCnt <10)
butt1RelCnt++;
else
butt1Responding = 0; //reset for next response, after 10 counts
}
if(butt2On)
{
if(butt2Count <255) butt2Count++;
}else
{
butt2Count = 0;
if(butt2RelCnt <10)
butt2RelCnt++;
else
butt2Responding = 0; //reset for next response, after 10 counts
}
}
#int_RTCC
void RTCC_isr(void) //interrupt every 51.2us
{
rtccCount++;
if(rtccCount == 2)
{
rtccCountLED++;
if(ADC_proc != 5)
{
ADC_proc++;
}else
{
ADC_proc = 0;
}
ADC_flag = 1;
rtccCount = 0;
}
}
void main()
{
port_b_pullups(TRUE);
setup_adc_ports(AN0_TO_AN2|VSS_VDD);
setup_adc(ADC_CLOCK_INTERNAL);
setup_spi(SPI_SS_DISABLED);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL|RTCC_8_bit);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DIV_BY_1,255,1);
setup_timer_3(T3_DISABLED|T3_DIV_BY_1);
setup_ccp1(CCP_PWM);
setup_ccp2(CCP_PWM);
set_pwm1_duty(duty_cycle);
set_pwm2_duty(0);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
//Setup_Oscillator parameter not selected from Intr Oscillotar Config tab
set_adc_channel(0);
while(1){
if(rtccCountLED >60)
{
pulseLED();
rtccCountLED = 0;
}
if(ADC_flag == 1) //does ADC system require servicing?
{
if(ADC_proc == 0) //space out time between setting and reading by incrementing through on interrupt
{
set_adc_channel(0);
}else if(ADC_proc == 1)
{
adc_value_x = read_adc();
}else if(ADC_proc == 2)
{
set_adc_channel(1);
}else if(ADC_proc == 3)
{
adc_value_y = read_adc();
}else if(ADC_proc == 4)
{
set_adc_channel(2);
}else if(ADC_proc == 5)
{
adc_value_z = read_adc();
}
ADC_flag = 0;
}
if(!input(PIN_B0)) //pulled low
{
butt1On = 1;
output_bit(PIN_A3, 1); /LED indicator
}else
{
butt1On = 0;
output_bit(PIN_A3, 0);
}
if(!input(PIN_B1)) //pulled low
{
butt2On = 1;
output_bit(PIN_A4, 1);
}else
{
butt2On = 0;
output_bit(PIN_A4, 0);
}
if((butt1Count > 4 && !butt1Responding) && (butt2Count > 4 && !butt2Responding))
{
butt1Responding = 1;
butt1RelCnt = 0;
butt2Responding = 1;
butt2RelCnt = 0;
if(!active)
{
active = 1;
printf("\n\rACTIVE ");
printf("%u ", active);
}else //shutdown
{
startUp = 0;
printf("\n\rstartUp ");
printf("%u ", startUp);
}
}
}
} |
Basically it loops away madly in main, keeping an eye on the two buttons, until an interrupt happens and a flag is set to tell the main loop to perform one of the sub functions.
I've got a few more functions I need to squeeze in so I'm trying to figure out how much spare time I have between interrupts, but the figures aren't anything like I expected so I don't want to forge forward when there's something obviously wrong in either my logic, code, or environment?
Any help would be greatly appreciated. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19506
|
|
Posted: Mon Jul 26, 2010 10:02 am |
|
|
First comment. Have you set the frequency in the simulator to 20MHz. This has to be done separately, and unless it is, the simulator's idea of speed will not be what is expected...
Now some comments on the code.
First, read the data sheet about the ADC. Should 'ADC_CLOCK_INTERNAL' be used at 20MHz.....
Second, the adc readings will take a lot of time. Consider changing your state machine, so that this time is also handled by the interrupt. So, something like:
Code: |
int8 Adc_Channel=0;
enum (select=0,start,read,last) adc_phase=select;
int16 adc_vals[3];
int1 do_action=false;
#int_RTCC
void RTCC_isr(void) //interrupt every 51.2us {
static int1 rtccCount=0; //Since you don't use this outside, keep it local
rtccCount^=1; //Toggle
if (rtccCount) { //every second interrupt
rtccCountLED++;
if (do_action==false) { //Ensure phase is _not_ updated, till the main
//code has handled the current one.....
adc_phase++;
if (adc_phase==last) {
adc_chan++;
if (adc_chan==3) {
adc_chan=0;
}
adc_phase=select;
}
}
do_action=true; //For each change, the main code needs to act
}
}
//Then in the main:
if (do_action) {
switch (adc_phase) {
case select:
set_adc_channel(Adc_channel);
break;
case start:
read_adc(ADC_START_ONLY); //trigger an ADC conversion
break;
case read: //read the waiting value
adc_vals(Adc_channel) = read_adc(ADC_READ_ONLY);
break;
}
do_action=false; //have acted, wait for interrupt to trigger next
}
|
This way the 'main' loop will be fast, and sequence through selecting each channel in turn, then triggering the conversion, then reading it.
Third comment 'volatile' does nothing in CCS. Even if it did though, it should _only_ be used for variables that are potentially changed by interrupt code, since on compliers that do pay attention to it, it adds the code to ensure that the variables are always read/written with interrupts disabled.
Fourth, the printf when you switch to 'active', takes a long time. 8 characters at 9600bps. Two potentially in the hardware buffer, so six character times. 6.25mSec. Problem here is that the state machine in the interrupt, advances even if the 'main' has not serviced it. Hence states _will_ be missed. In my example, the interrupt only advances the state, if the main has serviced it. Avoiding problems from this.
Best Wishes |
|
|
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
|
Posted: Mon Jul 26, 2010 6:13 pm |
|
|
Thanks Ttelmah, I'll have a closer look at your code tonight when I get home from work.
Sim clock is definitely set to 20MHz.
RE: ADC clock, I'm pretty fuzzy on that bit, I've mainly just been looking for examples on the forum and trying to implement them; found the TAD vs Device Operating Frequencies table, but it doesn't list 20MHz as such, it lists 22.86MHz with 16TOSC, does this mean I should set the ADC clock to ADC_CLOCK_DIV_16?
My theory for the state machine (as it were) was to toggle through the setting and getting of the ADC based on interrupt, so I'd set a channel, wait til the next interrupt, read the channel, wait, etc.; as from my rough research people seemed to reckon about 20us between setting and reading was the way to go? But obviously I'll go with your method if you think it's the better way (pretty sure I noticed things being skipped along the way with my method).
On a related note, is there any way to force an interrupt to return to the START of a main loop, rather than the point where it interrupted? I guess that's the point of your do_action flag, to make sure it doesn't skip a function when it returns to main past where that function is, ensuring the next interrupt doesn't increment things before they've been looped to? |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19506
|
|
Posted: Tue Jul 27, 2010 2:13 am |
|
|
The table in the data sheet, lists _maximum_ clock rates for a given divisor. So you can use DIV_16 fine.
There are a whole sequence of things that happen with the ADC. Internally, the ADC value is read from a capacitor inside the chip. This takes time to charge to get near to the actual voltage outside (depends on the source impedance, the internal resistances, the capacitor value itself, and how close you want it to get.....). The capacitance, and resistances inside the chip vary with PIC model. The sequence is:
1) Connect the external voltage to the capacitor (set_adc_channel).
2) Wait for the capacitor to charge.
3) Start the ADC conversion
4) Wait for the conversion to finish, and read the result.
Now on old PICs '2' was typically around 10uSec, to charge within 0.5 of a 'bit' level. On your chip it is only 1.2uSec, but longer acquisition is generally 'better', but at a cost in sample speed...
The simple 'read_adc' instruction puts '3' and '4' together in one instruction, or can be split using the constants I show.
The time between '3', and '4', is 11 cycles of the ADC clock, plus one processor instruction cycle. So with processor clock/16, will take (16*11)+4 clock cycles (180). 11.25uSec.
It is entirely down to whether you want to save time in the instruction loop, whether it is 'easier' to simply let the standard CCS instruction do it's default operation, or trigger the reading, and then actually take the result on the next pass. You can reduce the states used by one, by simply combining the 'select' operations with the 'reading' operations, selecting the new channel, as soon as the reading has been taken.
Best Wishes |
|
|
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
|
Posted: Tue Jul 27, 2010 5:05 am |
|
|
Thanks Ttelmah, I've tried your code (with a couple of fixes) and I still get the same timing, so tried completely stripping it down to nothing but a while loop and an interrupt, and I still get the same timing:
Code: | #include "F:\PICC_Code\proj3accel.h"
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdlibm.h>
#include <string.h>
int1 do_action=false;
#int_RTCC
void RTCC_isr(void)
{
static int1 rtccCount=0; //Breakpoint added here, breaking on every entry
rtccCount^=1; //Toggle
}
void main()
{
port_b_pullups(TRUE);
setup_adc_ports(AN0_TO_AN2|VSS_VDD);
setup_adc(ADC_CLOCK_DIV_16);
setup_spi(SPI_SS_DISABLED);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL|RTCC_8_bit);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DIV_BY_1,255,1);
setup_timer_3(T3_DISABLED|T3_DIV_BY_1);
setup_ccp1(CCP_PWM);
setup_ccp2(CCP_PWM);
set_pwm1_duty(duty_cycle);
set_pwm2_duty(0);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
//Setup_Oscillator parameter not selected from Intr Oscillotar Config tab
while(1){
if (do_action)
{
do_action=false; //have acted, wait for interrupt to trigger next
}
}
}
|
Should I be getting the timing I thought I should, or have I messed up? The sim is set under Debugger/Settings to use a Processor Frequency of 20MHz, and it comes up as that on the stopwatch, but I'm still getting a timing twice that expected.
The ADC part is kind of just a side issue, the ISR is mainly being used to page through an array to produce sound at a consisent frequency. But I need to figure out how much spare time (if any) I have with my processes to determine how to process some of the sound in conjunction with the ADC readings.
But while it's being brought up, does the "time between 3 and 4" happen seperate from the main cycle, like the hardware PWM system, or does it delay everything else?
Will I ever see the ADC system cycle through in a Sim if I don't have any "fake data" being applied, ie. are you saying that "adc_phase == last" only occurs when a reading has completed, but it won't complete in a sim?
Thanks again |
|
|
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
|
Posted: Tue Jul 27, 2010 4:53 pm |
|
|
OK I get the "phase==last" bit now, did some reading on "enum", essentially instead of saying "0,1,2,3", you are giving it readable names, "select,start,read,last". So "last" should always occur anyway if do_action == false. Unless it hangs on "adc_vals[Adc_channel] = read_adc(ADC_READ_ONLY)" if there is nothing to read from?
But yeah, the issue still remains that even the simplest of code with an interrupt is giving me a 2* difference from what I expect under the sim's stopwatch. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19506
|
|
Posted: Wed Jul 28, 2010 2:03 am |
|
|
OK.
The reason here is very simple. You are not turning the prescaler 'off'.
If you look at the data sheet, you will find that the clock always routes through the prescaler, with the 'default' value here (0), giving clock/2. To turn the prescaler completely off, a separate bit in the timer register has to be set (PSA).
setup_timer_0(RTCC_INTERNAL|RTCC_8_bit|RTCC_DIV_1);
Best Wishes |
|
|
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
|
Posted: Wed Jul 28, 2010 5:21 am |
|
|
Oh. Huh. I just kinda "assumed", based on code that I'd found around the place, that there'd be no prescaler if I didn't specify one. Lesson learnt; never assume anything in this game!
Oh well, learned a few other things along the way as well, which should give me a bit more of a footing as I progress with this. For one, I realised that I don't really need to sample my ADC anywhere near as often as I had it set up previously, I reckon maybe 10 samples per second for each channel should do the trick for what I need. And for getting some baseline figures from this procedure, I should probably store to an array in SRAM, then kill everything off and dump the data via printf, as printf takes waaaaay too long to be doing it inline. I only need a couple of seconds of data for various typical scenarios to see what kind of processing I need to do on my data.
Thanks for your help Ttelmah, and I appreciate that you try to "nudge" rather than spoon-feed. |
|
|
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
|
Posted: Thu Aug 19, 2010 6:48 am |
|
|
I'll post my next query here as it relates to a few of Ttelmah's points raised about the ADC and also printf times, figured it's best to continue in one place where all of the info is in one thread.
Interrupt is all sorted now, I actually tweaked things a bit so I'm again using an RTCC_DIV_2, but this time I know that's what I'm doing, and have managed to tweak the counter with a preloaded value that gives me almost exactly the interrupt frequency I was after, 16kHz (getting 62.4uS interrupt now).
As stated in my last post, I need to get some baseline reading from the ADC system in order to determine what kind of maths I need to do for the rest of my project. But if I want to do, say, 100 samples per second (*3, for X,Y and Z of the accelerometer), it wouldn't work trying to send this data straight out via a printf. I really need to store a certain amount of data, then dump the lot to the PC after shutting down the ADC system etc.
What I'm not sure of is how much data I should be expecting? The 18F2620 I'm using has 4KB of SRAM. I assume some of that will be taken up by general processing (though I'll be stripping the system right back to be ONLY the ADC state system), so say for argument's sake that there's 3KB free. The ADC is set to 8bit, as is the storing array. Does this mean that each sample is a BYTE, and so there should be room for 3000 samples? So 3 sets of samples (X,Y,Z), each sampling 100 times a second, giving ten seconds worth of recording before I stop the ADC, plug in the serial cable, and press a button to start a printf dump? Or am I out be a factor of 8, or worse?
I want to "put it through the motions" while sampling at the same rate as it would "for real", instead of how I last tried to sample data, which was just running a while loop and printf'ing on the fly; obviously a much slower rate than what I want to be sampling at, but I didn't realise that until Ttelmah pointed out how much time it takes to send the data.
Oh, on that note, what's a "safe" baud rate? I was just using the default 9600, is 57600 or 115200 fine?
Thanks for any help or pointers, I tried searching for answers but either no-one is doing store-and-dumps in this manner or I don't know what the correct terms are. |
|
|
|
|
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
|