|
|
View previous topic :: View next topic |
Author |
Message |
matt redmond
Joined: 26 Sep 2003 Posts: 6
|
General Program Strategy / Architecture |
Posted: Fri Sep 26, 2003 2:31 pm |
|
|
Hi All,
I'm just getting started PIC programming. I'm pretty comfortable with C but my hardware experience is very limited. I'm reading as much as I can but thought I'd solicit some advice from the board, so here goes:
My application will incorporate input from the user in the form of button presses or IrDA communication. It (the PIC) will also be talking to a realtime clock, RAM and one or more serial peripherals. It will provide output to the user in the form of flashes of one or more LEDs.
During all of this, the program needs to remain responsive to both the user's button presses and to anything coming in from the various peripherals.
I don't want my program locked up in tight delay loops during things like flashing an LED - if that will affect its responsiveness to input.
How do most of you deal with issues like this?
Should I incorporate a cheap 2nd PIC and use a few IO pins to tell it what to present to the user? For example, 000 = do nothing, 001 = single red flash, 010 = two red flashes, etc... This would give me eight different output options and my "primary" PIC could treat it as a 'one shot' kind of deal - set the bits and forget about it.
Am I on a decent track here - or are there better (or cheaper) options?
Also - do any of you know of an IC that will do both of the following: (1) Multiplex serial devices so I can use the PICs single UART to communicate with multiple devices, and (2) Buffer incoming data so I can check it at my liesure?
Thanks for your time!
Matt Redmond |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Fri Sep 26, 2003 3:25 pm |
|
|
Code: |
int1 Clock_mS_Tic;
int1 Clock_S_Tic;
int32 Ticker;
int8 Ticker_byte_2;
#locate Ticker_byte_2 = Ticker + 2
Int32 Seconds;
#inline
void Initialize_RTC(void);
/*****************
* Initialize RTC *
*****************/
#inline
void Initialize_RTC(void)
{ Ticker = 19660800; // Set to crystle speed
setup_timer_2 ( T2_DIV_BY_4, 0xff, 1 ); // Interupt every 2 to the power of 16 crystle osolation
enable_interrupts( INT_TIMER2 ); // Start counting time
}
/***************
* Service RTC *
***************/
#int_TIMER2
void TIMER2_isr()
{ Clock_mS_Tic=1; // Cleared in Main after checking
--Ticker_byte_2; // Decrement ticker by 2 to the power of 16
if(Ticker_byte_2) // If ticker < 2 to the power of 16
{ Ticker+=19660800; // Add number of crystle cycles per second to ticker
Seconds++;
Clock_S_Tic=1; // Cleared in Main after checking
}
}
|
Instead of delay use an interupt to keep time. |
|
|
Guest
|
Re: General Program Strategy / Architecture |
Posted: Fri Sep 26, 2003 3:26 pm |
|
|
matt redmond wrote: | Hi All,
I'm just getting started PIC programming. I'm pretty comfortable with C but my hardware experience is very limited. I'm reading as much as I can but thought I'd solicit some advice from the board, so here goes:
My application will incorporate input from the user in the form of button presses or IrDA communication. It (the PIC) will also be talking to a realtime clock, RAM and one or more serial peripherals. It will provide output to the user in the form of flashes of one or more LEDs.
During all of this, the program needs to remain responsive to both the user's button presses and to anything coming in from the various peripherals.
I don't want my program locked up in tight delay loops during things like flashing an LED - if that will affect its responsiveness to input.
How do most of you deal with issues like this?
Should I incorporate a cheap 2nd PIC and use a few IO pins to tell it what to present to the user? For example, 000 = do nothing, 001 = single red flash, 010 = two red flashes, etc... This would give me eight different output options and my "primary" PIC could treat it as a 'one shot' kind of deal - set the bits and forget about it.
Am I on a decent track here - or are there better (or cheaper) options?
Also - do any of you know of an IC that will do both of the following: (1) Multiplex serial devices so I can use the PICs single UART to communicate with multiple devices, and (2) Buffer incoming data so I can check it at my liesure?
Thanks for your time!
Matt Redmond |
Look at a good state machine.
If done well you should be able to keep things moving along.
Understand the PIC first.
I have no idea what your application is, but from your description I
'd say no problem in doing any of the thing you mention. and to the end user "all at the same time"
Hans W |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Fri Sep 26, 2003 4:48 pm |
|
|
The current product I am working on has the following:
LCD Display
32 key keypad
28 LEDs for the keys
RS232 port
RS485 port
USB
Inputs for switch presses
Relay outputs
Analog Input for photocell
Realtime Clock
The product is a lighting controller. It is programmed via the keypad and LCD, there is also an ACSII terminal interface or modem interface as well as the USB. The RS485 is for networking the controllers. It has an event scheduler for controlling the lights. The keypad is arranged in a 6X6 configuration so the micro is responsible for scanning that. The LEDs are multiplexed so the micro is responsible for controlling that as well. The inputs are read through 74HC165's so the micro is responsible for that also. The outputs are driven through 74HC373's so the micro has to do that too. There are pilot light drivers for switches that are also driven through 373's. The analog input must be read. There is a software controlled piezo buzzer. Zero crossing circuit so the relays close at zero cross. External eeprom and Realtime clock on an I2C bus. The product is very responsive. So the answer would be yes. Know your pic and its abilities, design your software well and you will be amazed at what they can do. BTW, I am using a 18F6720 and about 122K worth of code!
Regards,
Mark |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Fri Sep 26, 2003 5:22 pm |
|
|
You need a cheap way to do multi-tasking.
Here's what I do:
1. Setup a main loop, that calls several "tasks" (ie., functions)
continuously, in a "round robin" manner.
2. You probably want to have the tasks execute at different
rates. For example, you might want to check your buttons
every 20 ms. Maybe you want to flash your LEDs every
500 ms. So you need to maintain several software timers.
This is done by setting up the RTCC (Timer0) to do an
interrupt every 10 ms. In other words, 10 ms is your
"timer tick".
3. At the beginning of each task, check a "timer" variable.
If it's non-zero, then just return. This means it's not
yet time to execute that task. By setting the timer variable
to an appropriate value, you can have the task execute
every 10 ms, or at any interval up to 2550 ms.
(You could make it be longer by using another variable).
You can have state machines in each task, by using static variables
within the task function. You can reload the task timer with a different
value, depending upon where you are in the task's state machine.
You might do this to blink the LEDs in a pattern with different
durations for the "on" and "off" times.
I've just written sort of an outline. You'll have to fill in
the code for the tasks. I realize some people may have
better ways to do it. This is a quick way to implement
multi-tasking on small projects, and it works for me.
Code: | char gc_old_button_status;
main()
{
gc_old_button_status = input_b(); // Initialize the button status
// Initialize the software timers for each task.
// These initial values could even be different from
// the "normal" value, if you wanted the initial delay
// to be different than the normal task execution rate.
gc_buttons_timer = BUTTONS_TIMER_TICKS;
gc_IRDA_timer = IRDA_TIMER_TICKS;
gc_uart_timer = UART_TIMER_TICKS;
gc_LED_timer = LED_TIMER_TICKS;
// Setup the RTCC.
setup_counters(RTCC_INTERNAL, RTCC_DIV_256);
set_rtcc(RTCC_PRELOAD);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
// This is the main loop. You put your "tasks" here.
while(1)
{
check_buttons();
check_IRDA();
check_LEDs();
check_uart();
}
}
//==============================
// If the button debounce delay has not counted down yet,
// then just return. If it has counted down to 0,
// then reload the timer and execute the rest of
// this function.
// The timer tick is set for a 10 ms period, so let's
// say a suitable debounce time would be 20 ms or more.
// So we'll set CHECK_BUTTONS_TICKS = 2.
// So this function will execute approximately every 2 ticks,
// or 20 ms.
void check_buttons(void)
{
char new_status;
if(gc_buttons_timer)
return;
else
gc_buttons_timer = BUTTONS_TIMER_TICKS;
new_status = input_b(); // Read the buttons
// Have the switches changed ?
if(new_status != gc_old_button_status)
{
// If so, do something.
}
// Save button status for next time.
gc_old_button_status = new_status;
}
//--------------------------------------------------------
void check_IRDA(void)
{
if(gc_IRDA_timer)
return;
else
gc_IRDA_timer = IRDA_TIMER_TICKS;
// Handle the IRDA here.
}
//--------------------------------------------------------
void check_LEDs(void)
{
if(gc_LED_timer)
return;
else
gc_LED_timer = LED_TIMER_TICKS;
// Put your LED blinking state machine here.
}
//--------------------------------------------------------
void check_uart(void)
{
if(gc_uart_timer)
return;
else
gc_uart_timer = UART_TIMER_TICKS;
// Check the interrupt-driven software fifo here,
// to see if any characters are in the buffer.
// If so, handle them. See EX_SISR.C for
// an example of how to do the software fifo.
// Make the size of the fifo be large enough,
// so that it doesn't overflow between the
// times that you come into this task to check it.
// Or, set the gc_uart_timer value to a lower
// value, so that you check the fifo sufficiently often.
// Once you've read the characters, then put
// code here to act on them. If it's a command
// sent through the serial port, then set a global
// flag. Then, one of the other tasks can read
// the flag and it can do something -- perhaps
// it could start a complex sequence of LED blinking.
// (It would also clear the global flag).
}
//--------------------------------------------------------
// The rtcc interrupt occurs when the rtcc rolls over from FF to 00.
// I have programmed it to interrupt at a 100 Hz rate.
//
// RTCC interrupt rate = Fosc / (4 * rtcc pre-scaler * rtcc pre-load)
//
// = 4 MHz / (4 * 256 * 39)
//
// = 100.16 Hz
//
// This gives us a timer tick approx. every 10 ms (9.98 ms actually).
#int_rtcc
void rtcc_isr(void)
{
// Reload the RTCC, so it will keep overflowing every 10 ms.
set_rtcc(RTCC_PRELOAD);
// Decrement any timers that are running.
if(gc_buttons_timer)
gc_buttons_timer--;
if(gc_IRDA_timer)
gc_IRDA_timer--;
if(gc_LED_timer)
gc_LED_timer--;
if(gc_uart_timer)
gc_uart_timer--;
}
=============================================
In the TIMERS.H file, you should have something like this:
// timers.h
//
//----------------------------------------------------------------------
// DEFINES
//
// With a 4 MHz oscillator, a RTCC pre-scaler of 256, and a RTCC
// preload of 39, we get an rtcc interrupt rate of 100.16 Hz (Approx.
// every 10 ms).
// This will be our "tick" clock that we use for various event timers.
#define RTCC_PRELOAD (256 - 39)
// Multiply the following values x 10 ms to get the delay times,
// since each timer tick is 10 ms.
#define BUTTONS_TIMER_TICKS 2 // 20 ms
#define IRDA_TIMER_TICKS 10 // 100 ms
#define LED_TIMER_TICKS 50 // 500 ms
#define UART_TIMER_TICKS 10 // 100 ms
// GLOBALS
char gc_buttons_timer;
char gc_IRDA_timer;
char gc_LED_timer;
char gc_uart_timer;
//----------------------
// End of program |
|
|
|
matt redmond
Joined: 26 Sep 2003 Posts: 6
|
Wow, what a great set of responses... |
Posted: Sat Sep 27, 2003 1:58 am |
|
|
to my question. Thanks all of you!
These definitely get me moving in the right direction. I'll work on this over the weekend and see what I can make happen.
Thanks again! |
|
|
ChicoCyber
Joined: 09 May 2020 Posts: 7
|
|
Posted: Fri May 15, 2020 11:32 pm |
|
|
This is helping me in 2020
Thank you very much!
This opened my mind!
PCM programmer wrote: | You need a cheap way to do multi-tasking.
Here's what I do:
1. Setup a main loop, that calls several "tasks" (ie., functions)
continuously, in a "round robin" manner.
2. You probably want to have the tasks execute at different
rates. For example, you might want to check your buttons
every 20 ms. Maybe you want to flash your LEDs every
500 ms. So you need to maintain several software timers.
This is done by setting up the RTCC (Timer0) to do an
interrupt every 10 ms. In other words, 10 ms is your
"timer tick".
3. At the beginning of each task, check a "timer" variable.
If it's non-zero, then just return. This means it's not
yet time to execute that task. By setting the timer variable
to an appropriate value, you can have the task execute
every 10 ms, or at any interval up to 2550 ms.
(You could make it be longer by using another variable).
You can have state machines in each task, by using static variables
within the task function. You can reload the task timer with a different
value, depending upon where you are in the task's state machine.
You might do this to blink the LEDs in a pattern with different
durations for the "on" and "off" times.
I've just written sort of an outline. You'll have to fill in
the code for the tasks. I realize some people may have
better ways to do it. This is a quick way to implement
multi-tasking on small projects, and it works for me.
Code: | char gc_old_button_status;
main()
{
gc_old_button_status = input_b(); // Initialize the button status
// Initialize the software timers for each task.
// These initial values could even be different from
// the "normal" value, if you wanted the initial delay
// to be different than the normal task execution rate.
gc_buttons_timer = BUTTONS_TIMER_TICKS;
gc_IRDA_timer = IRDA_TIMER_TICKS;
gc_uart_timer = UART_TIMER_TICKS;
gc_LED_timer = LED_TIMER_TICKS;
// Setup the RTCC.
setup_counters(RTCC_INTERNAL, RTCC_DIV_256);
set_rtcc(RTCC_PRELOAD);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
// This is the main loop. You put your "tasks" here.
while(1)
{
check_buttons();
check_IRDA();
check_LEDs();
check_uart();
}
}
//==============================
// If the button debounce delay has not counted down yet,
// then just return. If it has counted down to 0,
// then reload the timer and execute the rest of
// this function.
// The timer tick is set for a 10 ms period, so let's
// say a suitable debounce time would be 20 ms or more.
// So we'll set CHECK_BUTTONS_TICKS = 2.
// So this function will execute approximately every 2 ticks,
// or 20 ms.
void check_buttons(void)
{
char new_status;
if(gc_buttons_timer)
return;
else
gc_buttons_timer = BUTTONS_TIMER_TICKS;
new_status = input_b(); // Read the buttons
// Have the switches changed ?
if(new_status != gc_old_button_status)
{
// If so, do something.
}
// Save button status for next time.
gc_old_button_status = new_status;
}
//--------------------------------------------------------
void check_IRDA(void)
{
if(gc_IRDA_timer)
return;
else
gc_IRDA_timer = IRDA_TIMER_TICKS;
// Handle the IRDA here.
}
//--------------------------------------------------------
void check_LEDs(void)
{
if(gc_LED_timer)
return;
else
gc_LED_timer = LED_TIMER_TICKS;
// Put your LED blinking state machine here.
}
//--------------------------------------------------------
void check_uart(void)
{
if(gc_uart_timer)
return;
else
gc_uart_timer = UART_TIMER_TICKS;
// Check the interrupt-driven software fifo here,
// to see if any characters are in the buffer.
// If so, handle them. See EX_SISR.C for
// an example of how to do the software fifo.
// Make the size of the fifo be large enough,
// so that it doesn't overflow between the
// times that you come into this task to check it.
// Or, set the gc_uart_timer value to a lower
// value, so that you check the fifo sufficiently often.
// Once you've read the characters, then put
// code here to act on them. If it's a command
// sent through the serial port, then set a global
// flag. Then, one of the other tasks can read
// the flag and it can do something -- perhaps
// it could start a complex sequence of LED blinking.
// (It would also clear the global flag).
}
//--------------------------------------------------------
// The rtcc interrupt occurs when the rtcc rolls over from FF to 00.
// I have programmed it to interrupt at a 100 Hz rate.
//
// RTCC interrupt rate = Fosc / (4 * rtcc pre-scaler * rtcc pre-load)
//
// = 4 MHz / (4 * 256 * 39)
//
// = 100.16 Hz
//
// This gives us a timer tick approx. every 10 ms (9.98 ms actually).
#int_rtcc
void rtcc_isr(void)
{
// Reload the RTCC, so it will keep overflowing every 10 ms.
set_rtcc(RTCC_PRELOAD);
// Decrement any timers that are running.
if(gc_buttons_timer)
gc_buttons_timer--;
if(gc_IRDA_timer)
gc_IRDA_timer--;
if(gc_LED_timer)
gc_LED_timer--;
if(gc_uart_timer)
gc_uart_timer--;
}
=============================================
In the TIMERS.H file, you should have something like this:
// timers.h
//
//----------------------------------------------------------------------
// DEFINES
//
// With a 4 MHz oscillator, a RTCC pre-scaler of 256, and a RTCC
// preload of 39, we get an rtcc interrupt rate of 100.16 Hz (Approx.
// every 10 ms).
// This will be our "tick" clock that we use for various event timers.
#define RTCC_PRELOAD (256 - 39)
// Multiply the following values x 10 ms to get the delay times,
// since each timer tick is 10 ms.
#define BUTTONS_TIMER_TICKS 2 // 20 ms
#define IRDA_TIMER_TICKS 10 // 100 ms
#define LED_TIMER_TICKS 50 // 500 ms
#define UART_TIMER_TICKS 10 // 100 ms
// GLOBALS
char gc_buttons_timer;
char gc_IRDA_timer;
char gc_LED_timer;
char gc_uart_timer;
//----------------------
// End of program |
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Sat May 16, 2020 12:15 am |
|
|
Interrupts.
Correctly handled.
A master 'tick' that scans the keyboard, and flashes the LED. Written so
little time is actually spent 'in' the interrupt routine.
PCM_Programmer's approach is the way to go. |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Sat May 16, 2020 7:11 am |
|
|
If I may add my two cents, my MCU is tied to a modem and a bunch of other external peripherrals. The MCU talks to the modem over a virtual UART using one virtual channel for AT commands to control the modem and the other virtual channel as a data passthrough to the IP network.
The main loop has two functions, one that processes a TX message queue and one that processes an RX message queue:
main()
{
DequeueTXMessage();
DequeueRXMessage();
}
These queues are strcutures with parameters and I have a 40-deep array where both queues contain an 'in' counter and an 'out' counter. Then the rest of the code is interrupt driven.
On the receive-end, the received UART data is stored in the 'in' position of the queue using a function I called EnqueueRXMessage() and later, the data at the 'out' position is processed using the DequeueRXMessage().
Same idea with the TX queue: if the PIC needs to send a UART message to the modem, then it stores the command via the EnqueueTXMessage() at the 'in' postition in the TX queue and when the correct time arrives to actually send the command, then the DequeueTXMessage() is called and the data at the 'out' position is processed and sent to the DMA buffer for transmission.
All this runs using a PIC24EP512GP806 with a 29.4912MHz clock setup with multipliers to run at 129.024 MHz (Thanks TTelmah!!).
Using this precise frequency of oscillator overclocked at that exact multiplier enables the PIC to do real-time processing of incoming UART data from an embedded modem at 115,200 bps but also lets the PIC send AT commands to that modem while also generating exact PWM frequencies of 8kHz, 128kHz and 3.072MHz.
Anyhow, it is a bit long and complicated to explain but basically, all of this setup enables the PIC to queue incoming AT messages and outgoing AT commands for later processing but can also control external perripherrals over I2C and SPI, process real-time audio from a CODEC, get external hardware interrupts (pushbttons), process a crap-load of counters using the timer interrupt, store data on an SD card and so-on.
Given that everything runs on interrupts, I had to create structures for timers (which contains a .IsStarted and .Count variables) and I have a whole bunch of these structures for different tasks. I have a 'start' and a 'stop' timer which really just sets the .IsStarted flag. Then in the timer interrupt, if that flag is set for a specific counter, the .Count value is increased by 1 until that count variable reaches a specific value in which case the counter is stopped by clearing the .IsStarted flag, the .Count value is reset and then a function is called to perform a specific task later-on... something that quickly gets put in the TX queue for later processing when the correct time window is available.
When UART data is sent-out to the modem (using the DequeueTXMessage() function), the PIC uses a DMA channel so it's super fast.
The whole system runs perfectly like a Swiss watch. Been using this setup actively for at least 4 years and it's about as to close to being flawless as flawless can be. Looking at it using a logic analyzer, everything is perfectly intertwined. Took me a while to figure-out how to get all of this to work together but the effort was worthwhile. I strongly suggest you get yourself a logic analyzer like the Saleae devices because this was a lifesaver for me and drastically reduced troubleshooting time and they are quite cheap.
I use the following interrupts:
INT_RDA
INT_RDA2
INT_DMA0
INT_UART1E
INT_TIMER1
INT_TBE
INT_EXT0
INT_EXT1
INT_EXT2
INT_EXT3
INT_EXT4
INTR_CN_PIN | PIN_x
Hope this helps.
Ben |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Mon May 18, 2020 12:46 am |
|
|
I've generated a link to this in the 'Best of' forum, since it covers many
points that I hope people will find interesting.
Best Wishes |
|
|
|
|
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
|