CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

General Program Strategy / Architecture

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
matt redmond



Joined: 26 Sep 2003
Posts: 6

View user's profile Send private message

General Program Strategy / Architecture
PostPosted: Fri Sep 26, 2003 2:31 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Sep 26, 2003 3:25 pm     Reply with quote

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
PostPosted: Fri Sep 26, 2003 3:26 pm     Reply with quote

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

View user's profile Send private message Send e-mail

PostPosted: Fri Sep 26, 2003 4:48 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Sep 26, 2003 5:22 pm     Reply with quote

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

View user's profile Send private message

Wow, what a great set of responses...
PostPosted: Sat Sep 27, 2003 1:58 am     Reply with quote

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

View user's profile Send private message

PostPosted: Fri May 15, 2020 11:32 pm     Reply with quote

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: 19195

View user's profile Send private message

PostPosted: Sat May 16, 2020 12:15 am     Reply with quote

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: 542
Location: Ottawa, Ontario, Canada

View user's profile Send private message

PostPosted: Sat May 16, 2020 7:11 am     Reply with quote

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: 19195

View user's profile Send private message

PostPosted: Mon May 18, 2020 12:46 am     Reply with quote

I've generated a link to this in the 'Best of' forum, since it covers many
points that I hope people will find interesting. Smile

Best Wishes
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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