|
|
View previous topic :: View next topic |
Author |
Message |
potato
Joined: 23 Mar 2014 Posts: 16
|
#int_TIMER0 for multiplexing 8 displays |
Posted: Sun Mar 23, 2014 12:17 pm |
|
|
Hi everyone, and thanks for dedicating this post some of your time.
I'm using a PIC18F4550 and I want to manage 8 displays - with dynamic visualisation, of course. I am using a 3 to 8 DEMUX to avoid using so many I/O pins in the PIC. So far I've managed to display any number in the displays, but using delays The problem is that I also have a LCD, buttons, leds, etc. and I cannot delay my program for the displays, as they are supposed to be ALWAYS on.
The solution that I found was to use the timer0 instead of the delays, so whenever it overflows (every 5 ms, so I need to start the timer at 0xEB) I choose a different number and a different display.
Up to now I'm only trying to manage 2 displays with a fixed number. I'm simulating everything in Proteus 8, and the CCS version that I use is the 4.032.
Here is the code:
Code: |
#include "D:\...\prova_comptador.h"
//global variables
unsigned int8 comptador0=34;
unsigned int8 d0,u0;
#byte portd=0xF83 //direction of portD
#byte porte=0xF84 //direction of portE
//------------------------------------------------------------------------------------
void config_ports (void)
{
set_tris_d(0x00); //output
set_tris_e(0x00); //output
}
//------------------------------------------------------------------------------------
unsigned int8 conversio_bcd (unsigned int8 comptador,*u,*d)
{
unsigned int8 comptador_updated;
if (comptador<=99)
{
*d = comptador/10;
*u = comptador%10;
return(comptador);
}
else //if it arrives to 99, reset it to 00
{
comptador_updated = 0;
*d = 0;
*u = 0;
return (comptador_updated);
}
}
//------------------------------------------------------------------------------------
#int_TIMER0
void TIMER0_isr ()
{
porte = 0x00; //select the 1st display
portd = u0; //write the number stored in u0
set_TIMER0(0xEB); //init timer0 to 5 ms
porte = 0x01; //select the 2nd display
portd = d0; //show the next number
set_TIMER0(0xEB); //init timer0 to 5 ms
}
//------------------------------------------------------------------------------------
void main(void)
{
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256|RTCC_8_bit);
enable_interrupts(INT_TIMER0);
enable_interrupts(GLOBAL);
set_TIMER0(0xEB); //init timer 0
config_ports(); //call the subroutine
while(1)
{
comptador0 = conversio_bcd(comptador0,&u0,&d0);
}
}
|
I am completely lost about the interruption routine, maybe it's completely wrong... I've been looking at many threads but I've been unable to find something that helped me, so I'd really appreciate if somebody could throw some light on the case.
Hundreds of thanks! |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Mar 23, 2014 12:38 pm |
|
|
Post this file: prova_comptador.h |
|
|
potato
Joined: 23 Mar 2014 Posts: 16
|
|
Posted: Sun Mar 23, 2014 2:12 pm |
|
|
PCM programmer wrote: | Post this file: prova_comptador.h |
In fact I haven't edited this file, it is automatically created... The quartz crystal that I'm using is a 4 MHz one.
Code: | #include <18F4550.h>
#device adc=8
//WDT,
#FUSES WDT1 //Watch Dog Timer uses 1:1 Postscale
#FUSES XT //Crystal osc <= 4mhz
#FUSES NOPROTECT //Code not protected from reading
#FUSES BROWNOUT //Reset when brownout detected
#FUSES BORV20 //Brownout reset at 2.0V
#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 LVP //Low Voltage Programming on B3(PIC16) or B5(PIC18)
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES PBADEN //PORTB pins are configured as analog input channels on RESET
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOCPB //No Boot Block code protection
#FUSES MCLR //Master Clear pin enabled
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES XINST //Extended set extension and Indexed Addressing mode enabled
#FUSES PLL12 //Divide By 12(48MHz oscillator input)
#FUSES CPUDIV4 //System Clock by 4
#FUSES USBDIV //USB clock source comes from PLL divide by 2
#FUSES VREGEN //USB voltage regulator enabled
#FUSES ICPRT //ICPRT enabled
#use delay(clock=4000000) |
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19510
|
|
Posted: Sun Mar 23, 2014 3:29 pm |
|
|
The Wizard, has no intelligence at all.
It only creates what you tell it, and the settings _will_ be wrong, unless you know what you are doing.
Some serious errors leap out in the fuses:
First LVP. Unless you are using a programmer that requires this (99% of programmers don't), and have a board wired to use this, the chip will not run properly with this selected. You need NOLVP.
Then you must not have ICPRT selected. This must only be selected on 44pin devices, with external ICPRT hardware. NOICPRT needed.
Then XINST. CCS does not support this. You need NOXINST selected, or code won't run.
Then you are saying you have a 4MHz crystal, but that you want to use division by 4, and yet you have the clock set at 4Mhz. You need CPUDIV1 to run at 4MHz with a 4MHz crystal.
You should always have PUT when using a crystal (this just causes erratic initial behaviour).
There are several more that will stop other things working, but these are the ones that will prevent the chip from initially working.... |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Mar 23, 2014 4:01 pm |
|
|
He's using Proteus. It shows what a terrible learning tool Proteus (ISIS) is.
Fuses (Config Bits) are critical in real hardware and ISIS doesn't care.
In your code below, why are reloading Timer0 twice ? It only needs to
be done once:
Quote: |
#int_TIMER0
void TIMER0_isr ()
{
porte = 0x00; //select the 1st display
portd = u0; //write the number stored in u0
set_TIMER0(0xEB); //init timer0 to 5 ms
porte = 0x01; //select the 2nd display
portd = d0; //show the next number
set_TIMER0(0xEB); //init timer0 to 5 ms
} |
|
|
|
potato
Joined: 23 Mar 2014 Posts: 16
|
|
Posted: Mon Mar 24, 2014 3:11 am |
|
|
Thanks for your comments Ttelmah. Now my .h code is the following:
Quote: | #include <18F4550.h>
#device adc=8
#FUSES WDT1 //Watch Dog Timer uses 1:1 Postscale
#FUSES XT //Crystal osc <= 4mhz
#FUSES NOPROTECT //Code not protected from reading
#FUSES BROWNOUT //Reset when brownout detected
#FUSES BORV20 //Brownout reset at 2.0V
#FUSES PUT //------------desnegat -- 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 //-----------negat--- Low Voltage Programming on B3(PIC16) or B5(PIC18)
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES NOPBADEN //PORTB pins are configured as analog input channels on RESET
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOCPB //No Boot Block code protection
#FUSES MCLR //Master Clear pin enabled
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES NOXINST //-------------negat--Extended set extension and Indexed Addressing mode enabled
#FUSES PLL12 //Divide By 12(48MHz oscillator input)
#FUSES CPUDIV1 //-----canviat a CPUDIV1 enlloc de 4 -- System Clock by 4
#FUSES USBDIV //USB clock source comes from PLL divide by 2
#FUSES VREGEN //USB voltage regulator enabled
#FUSES NOICPRT //-----------negat---ICPRT enabled
#use delay(clock=4000000) |
PCM programmer, is it only necessary to reload Timer0 once? If so, when do I have to do it, in the Timer0 interruption routines or in the main? (at the moment I have it in both places, despite I've tried to take it off the main... with the same result). Only the units display is showing the number, and the DEMUX is not changing its outputs...
Code: | #int_TIMER0
void TIMER0_isr ()
{
set_TIMER0(0xEB);
porte = 0x00;
portd = u0;
porte = 0x01;
portd = d0;
} |
The funny thing is that, if I change the order of this code -i.e. writing first porte = 0x01 and portd = d0, then the only display that is ON is the one corresponding to the tens... So it seems like the interruption is only valid for the first set of lines. Any ideas why?
Thanks. |
|
|
potato
Joined: 23 Mar 2014 Posts: 16
|
|
Posted: Mon Mar 24, 2014 5:12 am |
|
|
By the way, is it necessary to clear the interrupts or is it automatically done? And what about disable/enable the interrupts in the same interruption?
I've seen hundreds of codes and I've tried this, just to see if I was luckier, but no
Code: | #int_TIMER0
void TIMER0_isr ()
{
set_TIMER0(0xEB); //only once
disable_interrupts(INT_TIMER0);
porte = 0x01; //2nd display
portd = d0; //
clear_interrupt(INT_TIMER0); // clears the timer0 interrupt flag
enable_interrupts(INT_TIMER0);
disable_interrupts(INT_TIMER0);
porte = 0x00; //1st display
portd = u0;
clear_interrupt(INT_TIMER0); // clears the timer0 interrupt flag
enable_interrupts(INT_TIMER0);
} |
Is it better or is it a complete disaster? |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1348
|
|
Posted: Mon Mar 24, 2014 6:55 am |
|
|
Interrupts clear automatically at the end of the interrupt due to CCS doing it for you under the hood.
I'm not as familiar with the PIC18F4550, but on the PIC24 chips, trying to write to the PORTx registers twice in a row leads to a race condition where the first write overrides the 2nd. You should probably be using the output_e() and output_d() functions anyways.
I am confused why you make two changes in a row to the same ports in the interrupt. One will always overwrite the other. Shouldn't you be using a state machine to cycle through the displays instead? |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon Mar 24, 2014 7:44 am |
|
|
Quote: | So it seems like the interruption is only valid for the first set of lines. |
The #int_timer0 doesn't interrupt itself. You get an interrupt, then
the entire code in your #int_timer0 routine executes once, then it exits
the routine. It won't execute that routine again until the next interrupt
occurs about 5ms later.
Quote: | disable_interrupts(INT_TIMER0);
clear_interrupt(INT_TIMER0);
enable_interrupts(INT_TIMER0); |
Remove all this type of code (above) which is inside the #int_timer0
routine. It's not necessary and won't do anything to help you. As I said,
you get one pass through the #int_timer0 routine every 5ms.
Now, maybe Jeremiah's comment about using a state machine will make
sense to you. He wants you to create a static variable inside the
#int_timer0 routine. Initialize it in the declaration statement to TRUE.
Then every time the routine executes, check that flag and choose which
of the two blocks of PortE code you want to execute. Use if-else
statements to do this. Then toggle the flag variable to the opposite
state. If it was TRUE, set it to FALSE, or vice-versa. Remember you
will declare this flag (could be an int8) as 'static'. Then the next you
come into the routine, 5 ms later, your if-else statements will execute
the other block of PortE code.
For you to understand this, it's essential that you get the concept that one
Timer0 interrupt occurs every 5ms, and executes your #int_timer0 routine
one time per interrupt, and then exits. |
|
|
potato
Joined: 23 Mar 2014 Posts: 16
|
|
Posted: Mon Mar 24, 2014 5:03 pm |
|
|
PCM programmer wrote: |
The #int_timer0 doesn't interrupt itself. You get an interrupt, then
the entire code in your #int_timer0 routine executes once, then it exits
the routine. It won't execute that routine again until the next interrupt
occurs about 5ms later. |
PCM programmer wrote: |
For you to understand this, it's essential that you get the concept that one
Timer0 interrupt occurs every 5ms, and executes your #int_timer0 routine
one time per interrupt, and then exits. |
Thanks a lot! This has really helped me!
About the states machine, I considered it to be too complex for what I wanted to do, so my solution, knowing what PCM programmer has said, has been to create a global variable indicating the display which I wanted to light on, and do a switch contemplating the 8 values.
The result is the following:
Code: | #int_TIMER0
void TIMER0_isr ()
{
set_TIMER0(0xEB);
porte = display;
if (display<7)
{
switch (display)
{
case 0: portd = u0; break;
case 1: portd = d0; break;
case 2: portd = u1; break;
case 3: portd = d1; break;
case 4: portd = u2; break;
case 5: portd = d2; break;
case 6: portd = u3; break;
case 7: portd = d3; break;
}
display++;
}
else
display = 0;
} |
The only strange thing is that, if I write (display<=7), as it should be, then it copies what is stored in the 7th display to the 0... That's why I've omitted the '='.
And, dear all, NOW IT WORKS!!!
The next step is to use the interruption int0 to detect when a button has been pressed... I hope the functionality is quite similar to timer0! Moreover, I've seen there is an example and some explanation on the CCS manual, so maybe it is easier.
Thanks a lot!! |
|
|
|
|
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
|