Reputation: 3
I have a bare metal system based on a 16 bit microcontroller PIC18. The system receives input via 2 Analog inputs, 2 Digital Inputs and via CAN and then runs an algorithm based on these inputs. I have to transmit data via CAN every 10 millisecond based on the analog and digital inputs received, do switch debouncing on received Digital input and turn off or on digital outputs for timed interval of say x seconds while receiving data in CAN and based on the Analog/ digital inputs received. I am able to send and receive data via CAN and able to program the inputs as well as outputs in a while(1) loop I am not sure on how to architect this system keeping the timing constraints. I am using C and have no OS or scheduler for this.
I need to use some kind of timer but not sure on how to create state machines or tasks.
Upvotes: 0
Views: 725
Reputation: 116
You can go with a very simple scheduler based on a hardware timer. You setup your timer to give you an interrupt every 1 or 10 ms. You can poll the interrupt flag in the while(1)
loop of your main()
, or use a volatile bool is_timer_elapsed
flag as @Lundin suggested.
Every module (CAN, Analog inputs, Digital inputs) has its own tick
function in which it executes its business logic, for example the debouncing. The trick here is to make these functions non-blocking. State machines are very good at it.
When you detect that the hardware timer elapsed, you can call these tick
functions one after the other. After these functions were executed, check that your timer has not elapsed again. If this is the case, you have to do less things in a tick
cycle of your modules, e.g. add an intermediary state to break down a long operation.
Some hints:
void can_tick(void){
switch(can_state){...}
}
// Digital input: detect button press without debouncing
void digital_in_tick(void){
unsigned int duration_counter = 0U;
switch (button_state){
case NOT_PRESSED:
if (gpio_is_set(BUTTON_PIN)){
button_state = PRESSED;
}
break;
case PRESSED:
if (gpio_is_set(BUTTON_PIN)){
duration_counter++;
} else {
handle_button_pressed(duration_counter); // This could be writing a flag into a queue, or toggling a pin, etc.
button_state = NOT_PRESSED;
duration_counter = 0;
}
break;
default: break;
}
}
...
int main(void){
// timer init, etc
while(1){
if (is_timer_elapsed) {
is_timer_elapsed = false;
can_tick();
digital_input_tick();
...
if (is_timer_elapsed){
// This should not happen!
// Rework your tick functions (or reset)
}
}
}
Upvotes: 1
Reputation: 214495
The first thing an experienced developer does in pretty much any project, is to create timer drivers and a HAL on top of those (they'll have the HAL ready from previous projects even), which allows you to register simple callback functions for things like debouncing or the 10ms cyclic communication. HAL isn't necessary but it helps.
Either way, you need one or several hardware timers that are interrupt based - busy-wait polling won't work. You set them up to trigger once per 1ms, or once per 10ms or whatever is feasible. The interrupts need to be minimalistic - you might be able to squeeze in the GPIO read/debouncing in one, but you can't put the CAN communication stuff inside an ISR. So the timer keeping track of the ISR just has to be a volatile bool ready_to_send;
flag. In the main application, like main()
etc, you check this flag if(ready_to_send)
and then execute the CAN code from there.
Similarly, you could put ADC code in timers, depending on how often you need to read it and if you are fine with missing samples or not. Triggering on ADC conversion interrupts will be very disruptive to the program since these are fast and frequent, so only use that over timers in case you aren't allowed to miss a single conversion.
Also in case this happens to be one of them dysfunctional PICs that come with a maximum function call depth of 8, that is going to have some major impact on the C code design. In that case, it is strongly recommend to throw the PIC in the electronics recycling and go with a modern MCU instead. Old parts should be avoided in general.
Upvotes: 0
Reputation:
Based on a PDF file I've found about timers for the PIC18 at https://owd.tcnj.edu/~hernande/ELC343/Chapter_08.pdf I've written a function for a n*10ms delay. I'm concerned that when used many times, error may accumulate, but it might inspire a better solution.
void delay (char cx){
int i;
T0CON = 0x83; /* for TMR0, instruction clock, & prescaler=16 */
for (i = 0; i < cx; i++) {
TMR0 = 60535; /* it rolls over in 5000 clock cycles as 16-bit timer */
INTCONbits.TMR0IF = 0;
while(!(INTCONbits.TMR0IF)) doOtherTask(); /* until TMR0 rolls over */
}
return;
}
Upvotes: 0