I'm using code to configure a simple robot. I'm using WinAVR, and the code used there is similar to C, but without stdio.h libraries and such, so code for simple stuff should be entered manually (for example, converting decimal numbers to hexadecimal numbers is a multiple-step procedure involving ASCII character manipulation).
Example of code used is (just to show you what I'm talking about :) )
.
.
.
DDRA = 0x00;
A = adc(0); // Right-hand sensor
u = A>>4;
l = A&0x0F;
TransmitByte(h[u]);
TransmitByte(h[l]);
TransmitByte(' ');
.
.
.
For some circumstances, I must use WinAVR and cannot external libraries (such as stdio.h). ANYWAY, I want to apply a signal with pulse width of 1 ms or 2 ms via a servo motor. I know what port to set and such; all I need to do is apply a delay to keep that port set before clearing it.
Now I know how to set delays, we should create empty for loops such as:
int value= **??**
for(i = 0; i<value; i++)
;
What value am I supposed to put in "value" for a 1 ms loop ?
Chances are you'll have to calculate a reasonable value, then look at the signal that's generated (e.g., with an oscilloscope) and adjust your value until you hit the right time range. Given that you apparently have a 2:1 margin, you might hit it reasonably close the first time, but I wouldn't be much on it.
For your first approximation, generate an empty loop and count the instruction cycles for one loop, and multiply that by the time for one clock cycle. That should give at least a reasonable approximation of time taken by a single execution of the loop, so dividing the time you need by that should get you into the ballpark for the right number of iterations.
Edit: I should also note, however, that (at least most) AVRs have on-board timers, so you might be able to use them instead. This can 1) let you do other processing and/or 2) reduce power consumption for the duration.
If you do use delay loops, you might want to use AVR-libc's delay loop utilities to handle the details.
If my program is simple enough there is not a need of explicit timer programming, but it should be portable. One of my choices for a defined delay would be AVR Libc's delay function:
#include <delay.h>
_delay_ms (2) // Sleeps 2 ms
Is this going to go to a real robot? All you have is a CPU, no other integrated circuits that can give a measure of time?
If both answers are 'yes', well... if you know the exact timing for the operations, you can use the loop to create precise delays. Output your code to assembly code, and see the exact sequence of instructions used. Then, check the manual of the processor, it'll have that information.
If you need a more precise time value you should employ an interrupt service routine based on an internal timer. Remember a For loop is a blocking instruction, so while it is iterating the rest of your program is blocked. You could set up a timer based ISR with a global variable that counts up by 1 every time the ISR runs. You could then use that variable in an "if statement" to set the width time. Also that core probably supports PWM for use with the RC type servos. So that may be a better route.
This is a really neat little tasker that I use sometimes. It's for an AVR.
************************Header File***********************************
// Scheduler data structure for storing task data
typedef struct
{
// Pointer to task
void (* pTask)(void);
// Initial delay in ticks
unsigned int Delay;
// Periodic interval in ticks
unsigned int Period;
// Runme flag (indicating when the task is due to run)
unsigned char RunMe;
} sTask;
// Function prototypes
//-------------------------------------------------------------------
void SCH_Init_T1(void);
void SCH_Start(void);
// Core scheduler functions
void SCH_Dispatch_Tasks(void);
unsigned char SCH_Add_Task(void (*)(void), const unsigned int, const unsigned int);
unsigned char SCH_Delete_Task(const unsigned char);
// Maximum number of tasks
// MUST BE ADJUSTED FOR EACH NEW PROJECT
#define SCH_MAX_TASKS (1)
************************Header File***********************************
************************C File***********************************
#include "SCH_AVR.h"
#include <avr/io.h>
#include <avr/interrupt.h>
// The array of tasks
sTask SCH_tasks_G[SCH_MAX_TASKS];
/*------------------------------------------------------------------*-
SCH_Dispatch_Tasks()
This is the 'dispatcher' function. When a task (function)
is due to run, SCH_Dispatch_Tasks() will run it.
This function must be called (repeatedly) from the main loop.
-*------------------------------------------------------------------*/
void SCH_Dispatch_Tasks(void)
{
unsigned char Index;
// Dispatches (runs) the next task (if one is ready)
for(Index = 0; Index < SCH_MAX_TASKS; Index++)
{
if((SCH_tasks_G[Index].RunMe > 0) && (SCH_tasks_G[Index].pTask != 0))
{
(*SCH_tasks_G[Index].pTask)(); // Run the task
SCH_tasks_G[Index].RunMe -= 1; // Reset / reduce RunMe flag
// Periodic tasks will automatically run again
// - if this is a 'one shot' task, remove it from the array
if(SCH_tasks_G[Index].Period == 0)
{
SCH_Delete_Task(Index);
}
}
}
}
/*------------------------------------------------------------------*-
SCH_Add_Task()
Causes a task (function) to be executed at regular intervals
or after a user-defined delay
pFunction - The name of the function which is to be scheduled.
NOTE: All scheduled functions must be 'void, void' -
that is, they must take no parameters, and have
a void return type.
DELAY - The interval (TICKS) before the task is first executed
PERIOD - If 'PERIOD' is 0, the function is only called once,
at the time determined by 'DELAY'. If PERIOD is non-zero,
then the function is called repeatedly at an interval
determined by the value of PERIOD (see below for examples
which should help clarify this).
RETURN VALUE:
Returns the position in the task array at which the task has been
added. If the return value is SCH_MAX_TASKS then the task could
not be added to the array (there was insufficient space). If the
return value is < SCH_MAX_TASKS, then the task was added
successfully.
Note: this return value may be required, if a task is
to be subsequently deleted - see SCH_Delete_Task().
EXAMPLES:
Task_ID = SCH_Add_Task(Do_X,1000,0);
Causes the function Do_X() to be executed once after 1000 sch ticks.
Task_ID = SCH_Add_Task(Do_X,0,1000);
Causes the function Do_X() to be executed regularly, every 1000 sch ticks.
Task_ID = SCH_Add_Task(Do_X,300,1000);
Causes the function Do_X() to be executed regularly, every 1000 ticks.
Task will be first executed at T = 300 ticks, then 1300, 2300, etc.
-*------------------------------------------------------------------*/
unsigned char SCH_Add_Task(void (*pFunction)(), const unsigned int DELAY, const unsigned int PERIOD)
{
unsigned char Index = 0;
// First find a gap in the array (if there is one)
while((SCH_tasks_G[Index].pTask != 0) && (Index < SCH_MAX_TASKS))
{
Index++;
}
// Have we reached the end of the list?
if(Index == SCH_MAX_TASKS)
{
// Task list is full, return an error code
return SCH_MAX_TASKS;
}
// If we're here, there is a space in the task array
SCH_tasks_G[Index].pTask = pFunction;
SCH_tasks_G[Index].Delay =DELAY;
SCH_tasks_G[Index].Period = PERIOD;
SCH_tasks_G[Index].RunMe = 0;
// return position of task (to allow later deletion)
return Index;
}
/*------------------------------------------------------------------*-
SCH_Delete_Task()
Removes a task from the scheduler. Note that this does
*not* delete the associated function from memory:
it simply means that it is no longer called by the scheduler.
TASK_INDEX - The task index. Provided by SCH_Add_Task().
RETURN VALUE: RETURN_ERROR or RETURN_NORMAL
-*------------------------------------------------------------------*/
unsigned char SCH_Delete_Task(const unsigned char TASK_INDEX)
{
// Return_code can be used for error reporting, NOT USED HERE THOUGH!
unsigned char Return_code = 0;
SCH_tasks_G[TASK_INDEX].pTask = 0;
SCH_tasks_G[TASK_INDEX].Delay = 0;
SCH_tasks_G[TASK_INDEX].Period = 0;
SCH_tasks_G[TASK_INDEX].RunMe = 0;
return Return_code;
}
/*------------------------------------------------------------------*-
SCH_Init_T1()
Scheduler initialisation function. Prepares scheduler
data structures and sets up timer interrupts at required rate.
You must call this function before using the scheduler.
-*------------------------------------------------------------------*/
void SCH_Init_T1(void)
{
unsigned char i;
for(i = 0; i < SCH_MAX_TASKS; i++)
{
SCH_Delete_Task(i);
}
// Set up Timer 1
// Values for 1ms and 10ms ticks are provided for various crystals
OCR1A = 15000; // 10ms tick, Crystal 12 MHz
//OCR1A = 20000; // 10ms tick, Crystal 16 MHz
//OCR1A = 12500; // 10ms tick, Crystal 10 MHz
//OCR1A = 10000; // 10ms tick, Crystal 8 MHz
//OCR1A = 2000; // 1ms tick, Crystal 16 MHz
//OCR1A = 1500; // 1ms tick, Crystal 12 MHz
//OCR1A = 1250; // 1ms tick, Crystal 10 MHz
//OCR1A = 1000; // 1ms tick, Crystal 8 MHz
TCCR1B = (1 << CS11) | (1 << WGM12); // Timer clock = system clock/8
TIMSK |= 1 << OCIE1A; //Timer 1 Output Compare A Match Interrupt Enable
}
/*------------------------------------------------------------------*-
SCH_Start()
Starts the scheduler, by enabling interrupts.
NOTE: Usually called after all regular tasks are added,
to keep the tasks synchronised.
NOTE: ONLY THE SCHEDULER INTERRUPT SHOULD BE ENABLED!!!
-*------------------------------------------------------------------*/
void SCH_Start(void)
{
sei();
}
/*------------------------------------------------------------------*-
SCH_Update
This is the scheduler ISR. It is called at a rate
determined by the timer settings in SCH_Init_T1().
-*------------------------------------------------------------------*/
ISR(TIMER1_COMPA_vect)
{
unsigned char Index;
for(Index = 0; Index < SCH_MAX_TASKS; Index++)
{
// Check if there is a task at this location
if(SCH_tasks_G[Index].pTask)
{
if(SCH_tasks_G[Index].Delay == 0)
{
// The task is due to run, Inc. the 'RunMe' flag
SCH_tasks_G[Index].RunMe += 1;
if(SCH_tasks_G[Index].Period)
{
// Schedule periodic tasks to run again
SCH_tasks_G[Index].Delay = SCH_tasks_G[Index].Period;
SCH_tasks_G[Index].Delay -= 1;
}
}
else
{
// Not yet ready to run: just decrement the delay
SCH_tasks_G[Index].Delay -= 1;
}
}
}
}
// ------------------------------------------------------------------
************************C File***********************************
Most ATmega AVR chips, which are commonly used to make simple robots, have a feature known as pulse-width modulation (PWM) that can be used to control servos. This blog post might serve as a quick introduction to controlling servos using PWM. If you were to look at the Arduino platform's servo control library, you would find that it also uses PWM.
This might be a better choice than relying on running a loop a constant number of times as changes to compiler optimization flags and the chip's clock speed could potentially break such a simple delay function.
You should almost certainly have an interrupt configured to run code at a predictable interval. If you look in the example programs supplied with your CPU, you'll probably find an example of such.
Typically, one will use a word/longword of memory to hold a timer, which will be incremented each interrupt. If your timer interrupt runs 10,000 times/second and increments "interrupt_counter" by one each time, a 'wait 1 ms' routine could look like:
extern volatile unsigned long interrupt_counter;
unsigned long temp_value = interrupt_counter;
do {} while(10 > (interrupt_counter - temp_value));
/* Would reverse operands above and use less-than if this weren't HTML. */
Note that as written the code will wait between 900 µs and 1000 µs. If one changed the comparison to greater-or-equal, it would wait between 1000 and 1100. If one needs to do something five times at 1 ms intervals, waiting some arbitrary time up to 1 ms for the first time, one could write the code as:
extern volatile unsigned long interrupt_counter;
unsigned long temp_value = interrupt_counter;
for (int i=0; 5>i; i++)
{
do {} while(!((temp_value - interrupt_counter) & 0x80000000)); /* Wait for underflow */
temp_value += 10;
do_action_thing();
}
This should run the do_something()'s at precise intervals even if they take several hundred microseconds to complete. If they sometimes take over 1 ms, the system will try to run each one at the "proper" time (so if one call takes 1.3 ms and the next one finishes instantly, the following one will happen 700 µs later).
Related
i have to write a function that, by calling it only a single time, have to:
turn on an output pin
the pin stays high for 200mS
at the end of the timer the pin need to be low again.
the pin stays low for 200mS
at the end of the timer the function can be called again.
to turn on and off an output pin I already have wrote and tested the funcions:
outOn(pin_id);
outOff(pin_id);
now, i am trying to write the function that does the above mentioned actions and this is what l've come out with so far:
void outOnT02(enum e_outs ou){
outOn(ou);
gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2;
if(gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer) {
outOff(ou);
}
}
the function is named outOnT02 because:
it is an output;
after calling it, the pin became high;
T02 because the pin stays high for 0.2 Seconds.
outOn(ou); makes the pin go high,
outOff(ou); makes the pin go low,
gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2;
starts a 200mS timer,
and gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer is true when the timer has run out.
it works but, as you can tell, I have to put it in a cycle otherwise gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer will never be true and so,the pin will stay high forever.
this is where i am stuck. i can't use a SLEEP(200); because i can't interrupt the execution of the code.
the language is C, the ide is MPLAB X IDE v6.00, the compiler is XC8 v2.31 and the cpu is a PIC16F15355.
This post is a little old but it is worth to answer since it is both a good question and a common problem. Now this problem is very common in embedded world when we develop applications that has to run on only one CPU. Hence there is no real parallelism in the workflow. Also since the application will not run on top of any OS, there will be no scheduler, no timers, no threads etc. Especially in small scaled microcontrollers there is no way to run many of the true RTOSs.
But this shouldn't be an obstacle for developing applications that runs tasks concurrently. We can develop an application using some tricks so that it runs the tasks concurrently and behave as a small OS. Running concurrently means that no task blocks the CPU using busy waiting checks or something alike but we block a task that needs to wait some event to occur.
When we block a task, the specific data and the next execution point on that task must be preserved so that it can continue from where it should in the next execution. Knowing what we need to preserve helps us to create a thread-like structures that executes until it has to wait some event to occur (eg. time delay). When it has to wait (means that it will be blocked) the next state of it must be preserved and it exits to give the control to the CPU so that it executes other tasks.
When we need to deal with periodic tasks as in the question, it is relatively easier to implement without blocking the CPU execution and meanwhile handle other tasks. Moreover no interrupt usage needed for this type of tasks unless the tasks are extremely time sensitive.
Well, enough with the story part, let's get into it. I will base the examples on the OP's output flashing problem. However the same techniques can be applied for other situations like I/O events, hardware events etc.
Let's sum up the requirement briefly, we have a task that runs atomically. That is, when it is called it must run to completion so that it can be called again (this is what I understand from the OP's requirement):
Turns on an output pin for 200ms
Then turns off the pin for 200ms
Once turned off and 200ms time has elapsed it can be executed again.
Note Some functions in this example are not implemented since they can be application or microcontroller specific.
Task-like Functions
Let's assume we want to schedule the following two task-like functions each of which keeps track of its execution continuation points.
The static cp variables are declared in each function so that they remember where to continue whenever they are called. The content of cp variable will not be destroyed by the compiler when the function returns since we declare it as static. The cp needs to be updated upon the expected events occur in order to proceed to the next step whenever it is called.
Note that in outputTask, the call source must be known to control its atomic behaviour. Since the requirement for this task is that once it triggered or called, it must run to completion. So we have to know where the task is called from, in order it to decide what to do on each call. If it has been triggered from another task, it can't be triggered anymore until it completes its flashing prosess. If it is called from the scheduler (main loop) it knows it is a periodic call and will keep track of the time. This control is achieved using a parameter called periodic. When it is called from the scheduler this parameter must be set to 1, and 0 for the calls other than the scheduler.
/*
* This task-like function performs what the OP wants to achieve
*/
void outputTask(unsigned char periodic) {
static unsigned char cp = 0; // Continuation Point holder
static unsigned char currentMillis;
/*
* Check whether it is a periodic call or a new output signal call.
* If it is a periodic call and signalling has been initialized,
* proceed for time keeping.
* If it is a new signalling call and the task hasn't completed yet,
* simply ignore and return.
*/
if(!periodic && cp != 0) {
return;
}
switch(cp) {
case 0:
outOn(pin_id); // Turn on the output
cp = 1; // Next execution point
currentMillis = 200; // Load the 200ms counter for time keeping
break;
case 1:
currentMillis--;
if(currentMillis == 0) {
// 200ms time for output high has elapsed, proceed to next step
outOff(pin_id); // Turn off the output
currentMillis = 200; // Reload the counter value
cp = 2; // Proceed to the next step
}
break;
case 2:
currentMillis--;
if(currentMillis == 0) {
// 200ms time for output low has elapsed, proceed to next step
cp = 0; // Last step is done, reset the state for new calls
}
break;
default:
// For anything else, reset the task state to the initials
cp = 0 // Reset the task state to zero so that it accepts new calls
}
}
/*
* Let's say this task will wait for a button press event and will
* trigger the outputTask upon the event occurs
*/
void outputTriggerTask() {
static unsigned char cp = 0;
static unsigned char currentMillis;
switch(cp) {
case 0:
if(isButtonPressed()) { // Platform specific function
// A button press has been detected, debounce first
currentMillis = 50;
cp = 1; // Next step, check for the elapsed time
}
else {
break;
}
case 1:
currentMillis--;
if(currentMillis == 0) {
// Check whether the button press is consistent
if(isButtonPressed()) {
// Yes still consistent, handle the button press by triggering the output task
outputTask(0); // Not a periodic call
cp = 2; // Next step is to check whether button is released
}
else {
cp = 0; // Reset the task state
}
}
break;
case 2:
if(isButtonReleased()) { // Platform specific function
currentMillis = 50; // Reload the time counter
cp = 3;
}
else {
break;
}
case 3:
currentMillis--;
if(currentMillis == 0) {
// Check whether the button release is consistent
if(isButtonReleased()) {
// Yes still consistent, handle the button release if needed
cp = 0; // Reset the task to its initial state
}
}
break;
default:
cp = 0; // Reset to initials
}
}
Scheduling Approches
The following approches are for non RTOS small embedded systems. They are suitable for wide range of 8-bit microcontrollers.
Approach 1 - Create Delay Based Timebase to Schedule Tasks
Scheduling using CPU blocking delay is suitable for hobby and educational purposes while it is not suitable for real projects. This example uses a platform specific delay_ms function (or can be a macro) to create a 1ms heartbeat for the application so that the tasks can keep track of time.
void main(void) {
systemInit(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// The first thing we do is to create a 1ms timebase using delay.
// This is the heartbeat for the application
delay_ms(1000); // Platform specific function
// 1ms has elapsed check the tasks
outputTriggerTask(); // Check whether any button press event has occured
outputTask(1); // It is a periodic call for the output task
// Maybe more tasks go here...
}
}
Approach 2 - Create Hardware Timer Based Timebase
void main(void) {
systemInit(); // Platform specific function
// Setup a hardware timer for 1ms overflow without interrupt
initTimerForOneMs(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// Wait for the timer to overflow
while(!isTimerOverflow()) // Platform specific function
;
// Timer has overflowed, reload and check tasks
reloadTimer(); // Platform specific function
// 1ms has elapsed check the tasks
outputTriggerTask(); // Check whether any button press event has occured
outputTask(1); // It is a periodic call for the output task
// Maybe more tasks go here...
}
}
Approach 3 Put the Processor to Sleep for 1ms Timebase
void main(void) {
systemInit(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// Put the Processor to sleep along with a watchdog timer to wake it up
clearWatchdogTimer(); // Platform specific function
sleep(); // Platform specific function
// CPU slept for 1ms and woke up, handle the periodic tasks
outputTriggerTask(); // Check whether any button press event has occured
clearWatchdogTimer(); // Platform specific function
outputTask(1); // It is a periodic call for the output task
clearWatchdogTimer(); // Platform specific function
// Maybe more tasks go here...
}
}
And Last But not Least Time Checking Approach
In this approach the tasks will be keeping the time by checking better say comparing the elapsed time to the desired time to delay tasks without blocking the CPU. For this, we will need to use a free running timer. This will be like the millis function of the Arduino API.
Rewriting the Tasks for the Time Checking Approach
/*
* This task-like function performs what the OP wants to achieve
*/
void outputTask(unsigned char periodic) {
static unsigned char cp = 0; // Continuation Point holder
static unsigned short currentMillis; // 16 bit millisecond holder
/*
* Check whether it is a periodic call or a new output signal call.
* If it is a periodic call and signalling has been initialized,
* proceed for time keeping.
* If it is a new signalling call and the task hasn't completed yet,
* simply ignore and return.
*/
if(!periodic && cp != 0) {
return;
}
switch(cp) {
case 0:
outOn(pin_id); // Turn on the output
cp = 1; // Next execution point
currentMillis = getCurrentMillis(); // Platform specific function
break;
case 1:
if(getCurrentMillis() - currentMillis >= 200) {
// 200ms time for output high has elapsed, proceed to next step
outOff(pin_id); // Turn off the output
currentMillis = getCurrentMillis(); // Reload the counter value
cp = 2; // Proceed to the next step
}
break;
case 2:
if(getCurrentMillis() - currentMillis >= 200) {
// 200ms time for output low has elapsed, proceed to next step
cp = 0; // Last step is done, reset the state for new calls
}
break;
default:
// For anything else, reset the task state to the initials
cp = 0 // Reset the task state to zero so that it accepts new calls
}
}
/*
* Let's say this task will wait for a button press event and will
* trigger the outputTask upon the event occurs
*/
void outputTriggerTask() {
static unsigned char cp = 0;
static unsigned short currentMillis;
switch(cp) {
case 0:
if(isButtonPressed()) { // Platform specific function
// A button press has been detected, debounce first
currentMillis = getCurrentMillis(); // Platform specific function
cp = 1; // Next step, check for the elapsed time
}
else {
break;
}
case 1:
if(getCurrentMillis() - currentMillis >= 50) {
// Check whether the button press is consistent
if(isButtonPressed()) {
// Yes still consistent, handle the button press by triggering the output task
outputTask(0); // Not a periodic call
cp = 2; // Next step is to check whether button is released
}
else {
cp = 0; // Reset the task state
}
}
break;
case 2:
if(isButtonReleased()) { // Platform specific function
currentMillis = getCurrentMillis();
cp = 3;
}
else {
break;
}
case 3:
if(getCurrentMillis() - currentMillis >= 50) {
// Check whether the button release is consistent
if(isButtonReleased()) {
// Yes still consistent, handle the button release if needed
cp = 0; // Reset the task to its initial state
}
}
break;
default:
cp = 0; // Reset to initials
}
}
Scheduler for Time Checking Approach
void main(void) {
systemInit(); // Platform specific function
initMillisTimerWithInterrupt(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// Now that we use a free running millis timer no need to block the CPU to create a timebase
// Just call tasks sequentially. Each task will know what to do individually
outputTriggerTask(); // Check whether any button press event has occured
outputTask(1); // It is a periodic call for the output task
// Maybe more tasks go here...
}
}
I have in a project of mine a small delay function that I have written myself by making use of a timer peripheral of my MCU:
static void delay100Us(void)
{
uint_64 ctr = TIMER_read(0); //10ns resolution
uint_64 ctr2 = ctr + 10000;
while(ctr <= ctr2) //wait 100 microseconds(10000)
{
ctr = TIMER_read(0);
}
}
The counter is a freerunning hw counter with 10ns resolution so I wrote that function as to give approximately 100us delay.
I think this should work in principle however there could be the situation where the timer is less than 10000 from overflowing and so ctr2 will get assigned a value which is more than ctr can actually reach and therefore I would end up getting stuck into an infinite loop.
I need to generate a delay using this timer in my project so I need to somehow make sure that I always get the same delay(100us) while at the same time protect myself from getting stuck there.
Is there any way I can do this or is this just a limitation that I can't get passed?
Thank you!
Edit:
ctr_start = TimerRead(); //get initial value for the counter
interval = TimerRead() - ctr_start;
while(interval <= 10000)
{
interval = ( TimerRead() - ctr_start + countersize ) % countersize;
}
Where countersize = 0xFFFFFFFFFFFFFFFF;
It can be dangerous to wait for a specific timer value in case an interrupt happens at just that moment and you miss the required count. So it is better to wait until the counter has reached at least the target value. But as noticed, comparing the timer with a target value creates a problem when the target is lower than the initial value.
One way to avoid this problem is to consider the interval that has elapsed with unsigned variables and arithmetic. Their behaviour is well defined when values wrap.
A hardware counter is almost invariably of size 8, 16, 32 or 64 bits, so choose a variable type to suit. Suppose the counter is 32-bit:
void delay(uint32_t period)
{
uint32_t mark = TIMER_read(0);
uint32_t interval;
do {
interval = TIMER_read(0) - mark; // underflow is well defined
} while(interval < period);
}
Obviously, the required period must be less than the counter's period. If not, either scale the timer's clock, or use another method (such as a counter maintained by interrupt).
Sometimes a one-shot timer is used to count down the required period, but using a free-run counter is easy, and using a one-shot timer means it can't be used by another process at the same time.
Summary
I'm trying to write an embedded application for an MC9S12VR microcontroller. This is a 16-bit microcontroller but some of the values I deal with are 32 bits wide and while debugging I've captured some anomalous values that seem to be due to torn reads.
I'm writing the firmware for this micro in C89 and running it through the Freescale HC12 compiler, and I'm wondering if anyone has any suggestions on how to prevent them on this particular microcontroller assuming that this is the case.
Details
Part of my application involves driving a motor and estimating its position and speed based on pulses generated by an encoder (a pulse is generated on every full rotation of the motor).
For this to work, I need to configure one of the MCU timers so that I can track the time elapsed between pulses. However, the timer has a clock rate of 3 MHz (after prescaling) and the timer counter register is only 16-bit, so the counter overflows every ~22ms. To compensate, I set up an interrupt handler that fires on a timer counter overflow, and this increments an "overflow" variable by 1:
// TEMP
static volatile unsigned long _timerOverflowsNoReset;
// ...
#ifndef __INTELLISENSE__
__interrupt VectorNumber_Vtimovf
#endif
void timovf_isr(void)
{
// Clear the interrupt.
TFLG2_TOF = 1;
// TEMP
_timerOverflowsNoReset++;
// ...
}
I can then work out the current time from this:
// TEMP
unsigned long MOTOR_GetCurrentTime(void)
{
const unsigned long ticksPerCycle = 0xFFFF;
const unsigned long ticksPerMicrosecond = 3; // 24 MHZ / 8 (prescaler)
const unsigned long ticks = _timerOverflowsNoReset * ticksPerCycle + TCNT;
const unsigned long microseconds = ticks / ticksPerMicrosecond;
return microseconds;
}
In main.c, I've temporarily written some debugging code that drives the motor in one direction and then takes "snapshots" of various data at regular intervals:
// Test
for (iter = 0; iter < 10; iter++)
{
nextWait += SECONDS(secondsPerIteration);
while ((_test2Snapshots[iter].elapsed = MOTOR_GetCurrentTime() - startTime) < nextWait);
_test2Snapshots[iter].position = MOTOR_GetCount();
_test2Snapshots[iter].phase = MOTOR_GetPhase();
_test2Snapshots[iter].time = MOTOR_GetCurrentTime() - startTime;
// ...
In this test I'm reading MOTOR_GetCurrentTime() in two places very close together in code and assign them to properties of a globally available struct.
In almost every case, I find that the first value read is a few microseconds beyond the point the while loop should terminate, and the second read is a few microseconds after that - this is expected. However, occasionally I find the first read is significantly higher than the point the while loop should terminate at, and then the second read is less than the first value (as well as the termination value).
The screenshot below gives an example of this. It took about 20 repeats of the test before I was able to reproduce it. In the code, <snapshot>.elapsed is written to before <snapshot>.time so I expect it to have a slightly smaller value:
For snapshot[8], my application first reads 20010014 (over 10ms beyond where it should have terminated the busy-loop) and then reads 19988209. As I mentioned above, an overflow occurs every 22ms - specifically, a difference in _timerOverflowsNoReset of one unit will produce a difference of 65535 / 3 in the calculated microsecond value. If we account for this:
A difference of 40 isn't that far off the discrepancy I see between my other pairs of reads (~23/24), so my guess is that there's some kind of tear going on involving an off-by-one read of _timerOverflowsNoReset. As in while busy-looping, it will perform one call to MOTOR_GetCurrentTime() that erroneously sees _timerOverflowsNoReset as one greater than it actually is, causing the loop to end early, and then on the next read after that it sees the correct value again.
I have other problems with my application that I'm having trouble pinning down, and I'm hoping that if I resolve this, it might resolve these other problems as well if they share a similar cause.
Edit: Among other changes, I've changed _timerOverflowsNoReset and some other globals from 32-bit unsigned to 16-bit unsigned in the implementation I now have.
You can read this value TWICE:
unsigned long GetTmrOverflowNo()
{
unsigned long ovfl1, ovfl2;
do {
ovfl1 = _timerOverflowsNoReset;
ovfl2 = _timerOverflowsNoReset;
} while (ovfl1 != ovfl2);
return ovfl1;
}
unsigned long MOTOR_GetCurrentTime(void)
{
const unsigned long ticksPerCycle = 0xFFFF;
const unsigned long ticksPerMicrosecond = 3; // 24 MHZ / 8 (prescaler)
const unsigned long ticks = GetTmrOverflowNo() * ticksPerCycle + TCNT;
const unsigned long microseconds = ticks / ticksPerMicrosecond;
return microseconds;
}
If _timerOverflowsNoReset increments much slower then execution of GetTmrOverflowNo(), in worst case inner loop runs only two times. In most cases ovfl1 and ovfl2 will be equal after first run of while() loop.
Calculate the tick count, then check if while doing that the overflow changed, and if so repeat;
#define TCNT_BITS 16 ; // TCNT register width
uint32_t MOTOR_GetCurrentTicks(void)
{
uint32_t ticks = 0 ;
uint32_t overflow_count = 0;
do
{
overflow_count = _timerOverflowsNoReset ;
ticks = (overflow_count << TCNT_BITS) | TCNT;
}
while( overflow_count != _timerOverflowsNoReset ) ;
return ticks ;
}
the while loop will iterate either once or twice no more.
Based on the answers #AlexeyEsaulenko and #jeb provided, I gained understanding into the cause of this problem and how I could tackle it. As both their answers were helpful and the solution I currently have is sort of a mixture of the two, I can't decide which of the two answers to accept, so instead I'll upvote both answers and keep this question open.
This is how I now implement MOTOR_GetCurrentTime:
unsigned long MOTOR_GetCurrentTime(void)
{
const unsigned long ticksPerMicrosecond = 3; // 24 MHZ / 8 (prescaler)
unsigned int countA;
unsigned int countB;
unsigned int timerOverflowsA;
unsigned int timerOverflowsB;
unsigned long ticks;
unsigned long microseconds;
// Loops until TCNT and the timer overflow count can be reliably determined.
do
{
timerOverflowsA = _timerOverflowsNoReset;
countA = TCNT;
timerOverflowsB = _timerOverflowsNoReset;
countB = TCNT;
} while (timerOverflowsA != timerOverflowsB || countA >= countB);
ticks = ((unsigned long)timerOverflowsA << 16) + countA;
microseconds = ticks / ticksPerMicrosecond;
return microseconds;
}
This function might not be as efficient as other proposed answers, but it gives me confidence that it will avoid some of the pitfalls that have been brought to light. It works by repeatedly reading both the timer overflow count and TCNT register twice, and only exiting the loop when the following two conditions are satisfied:
the timer overflow count hasn't changed while reading TCNT for the first time in the loop
the second count is greater than the first count
This basically means that if MOTOR_GetCurrentTime is called around the time that a timer overflow occurs, we wait until we've safely moved on to the next cycle, indicated by the second TCNT read being greater than the first (e.g. 0x0001 > 0x0000).
This does mean that the function blocks until TCNT increments at least once, but since that occurs every 333 nanoseconds I don't see it being problematic.
I've tried running my test 20 times in a row and haven't noticed any tearing, so I believe this works. I'll continue to test and update this answer if I'm wrong and the issue persists.
Edit: As Vroomfondel points out in the comments below, the check I do involving countA and countB also incidentally works for me and can potentially cause the loop to repeat indefinitely if _timerOverflowsNoReset is read fast enough. I'll update this answer when I've come up with something to address this.
The atomic reads are not the main problem here.
It's the problem that the overflow-ISR and TCNT are highly related.
And you get problems when you read first TCNT and then the overflow counter.
Three sample situations:
TCNT=0x0000, Overflow=0 --- okay
TCNT=0xFFFF, Overflow=1 --- fails
TCNT=0x0001, Overflow=1 --- okay again
You got the same problems, when you change the order to: First read overflow, then TCNT.
You could solve it with reading twice the totalOverflow counter.
disable_ints();
uint16_t overflowsA=totalOverflows;
uint16_t cnt = TCNT;
uint16_t overflowsB=totalOverflows;
enable_ints();
uint32_t totalCnt = cnt;
if ( overflowsA != overflowsB )
{
if (cnt < 0x4000)
totalCnt += 0x10000;
}
totalCnt += (uint32_t)overflowsA << 16;
If the totalOverflowCounter changed while reading the TCNT, then it's necessary to check if the value in tcnt is already greater 0 (but below ex. 0x4000) or if tcnt is just before the overflow.
One technique that can be helpful is to maintain two or three values that, collectively, hold overlapping portions of a larger value.
If one knows that a value will be monotonically increasing, and one will never go more than 65,280 counts between calls to "update timer" function, one could use something like:
// Note: Assuming a platform where 16-bit loads and stores are atomic
uint16_t volatile timerHi, timerMed, timerLow;
void updateTimer(void) // Must be only thing that writes timers!
{
timerLow = HARDWARE_TIMER;
timerMed += (uint8_t)((timerLow >> 8) - timerMed);
timerHi += (uint8_t)((timerMed >> 8) - timerHi);
}
uint32_t readTimer(void)
{
uint16_t tempTimerHi = timerHi;
uint16_t tempTimerMed = timerMed;
uint16_t tempTimerLow = timerLow;
tempTimerMed += (uint8_t)((tempTimerLow >> 8) - tempTimerMed);
tempTimerHi += (uint8_t)((tempTimerMed >> 8) - tempTimerHi);
return ((uint32_t)tempTimerHi) << 16) | tempTimerLow;
}
Note that readTimer reads timerHi before it reads timerLow. It's possible that updateTimer might update timerLow or timerMed between the time readTimer reads
timerHi and the time it reads those other values, but if that occurs, it will
notice that the lower part of timerHi needs to be incremented to match the upper
part of the value that got updated later.
This approach can be cascaded to arbitrary length, and need not use a full 8 bits
of overlap. Using 8 bits of overlap, however, makes it possible to form a 32-bit
value by using the upper and lower values while simply ignoring the middle one.
If less overlap were used, all three values would need to take part in the
final computation.
The problem is that the writes to _timerOverflowsNoReset isn't atomic and you don't protect them. This is a bug. Writing atomic from the ISR isn't very important, as the HCS12 blocks the background program during interrupt. But reading atomic in the background program is absolutely necessary.
Also, have in mind that Codewarrior/HCS12 generates somewhat ineffective code for 32 bit arithmetic.
Here is how you can fix it:
Drop unsigned long for the shared variable. In fact you don't need a counter at all, given that your background program can service the variable within 22ms real-time - should be very easy requirement. Keep your 32 bit counter local and away from the ISR.
Ensure that reads of the shared variable are atomic. Disassemble! It must be a single MOV instruction or similar; otherwise you must implement semaphores.
Don't read any volatile variable inside complex expressions. Not only the shared variable but also the TCNT. Your program as it stands has a tight coupling between the slow 32 bit arithmetic algorithm's speed and the timer, which is very bad. You won't be able to reliably read TCNT with any accuracy, and to make things worse you call this function from other complex code.
Your code should be changed to something like this:
static volatile bool overflow;
void timovf_isr(void)
{
// Clear the interrupt.
TFLG2_TOF = 1;
// TEMP
overflow = true;
// ...
}
unsigned long MOTOR_GetCurrentTime(void)
{
bool of = overflow; // read this on a line of its own, ensure this is atomic!
uint16_t tcnt = TCNT; // read this on a line of its own
overflow = false; // ensure this is atomic too
if(of)
{
_timerOverflowsNoReset++;
}
/* calculations here */
return microseconds;
}
If you don't end up with atomic reads, you will have to implement semaphores, block the timer interrupt or write the reading code in inline assembler (my recommendation).
Overall I would say that your design relying on TOF is somewhat questionable. I think it would be better to set up a dedicated timer channel and let it count up a known time unit (10ms?). Any reason why you can't use one of the 8 timer channels for this?
It all boils down to the question of how often you do read the timer and how long the maximum interrupt sequence will be in your system (i.e. the maximum time the timer code can be stopped without making "substantial" progress).
Iff you test for time stamps more often than the cycle time of your hardware timer AND those tests have the guarantee that the end of one test is no further apart from the start of its predecessor than one interval (in your case 22ms), all is well. In the case your code is held up for so long that these preconditions don't hold, the following solution will not work - the question then however is whether the time information coming from such a system has any value at all.
The good thing is that you don't need an interrupt at all - any try to compensate for the inability of the system to satisfy two equally hard RT problems - updating your overflow timer and delivering the hardware time is either futile or ugly plus not meeting the basic system properties.
unsigned long MOTOR_GetCurrentTime(void)
{
static uint16_t last;
static uint16_t hi;
volatile uint16_t now = TCNT;
if (now < last)
{
hi++;
}
last = now;
return now + (hi * 65536UL);
}
BTW: I return ticks, not microseconds. Don't mix concerns.
PS: the caveat is that such a function is not reentrant and in a sense a true singleton.
I'm trying to interface a pressure sensor (MS5803-14BA) with my board (NUCLEO-STM32L073RZ).
According to the datasheet (page 3), the pressure sensor requires some milliseconds before the measurement is ready to be read. For my project, I would be interested in the highest resolution that requires around 10 ms for the conversion of the raw data.
Unfortunately, this pressure sensor doesn't have any interrupt pin that can be exploited to see when the measurement is ready, and therefore I temporarily solved the problem putting a delay after the request of new data.
I don't like my current solution, since in those 10 ms I could put the mcu working on something else (I have several other sensors attached to my board), but without any interrupt pin, I'm not sure about what is the best way to solve this problem.
Another solution came into my mind: Using a timer that triggers every say 20 ms and performs the following operations:
1.a Read the current value stored in the registers (discarding the first value)
1.b Ask for a new value
In this way, at the next iteration I would just need to read the value requested at the end of the previous iteration.
What I don't like is that my measurement would be always 20 ms old. Until the delay remains 20 ms, it should be still fine, but if I need to reduce the rate, the "age" of the reading with my solution would increase.
Do you have any other idea about how to deal with this?
Thank you.
Note: Please let me know if you would need to see my current implementation.
How to do high-resolution, timestamp-based, non-blocking, single-threaded cooperative multi-tasking
This isn't a "how to read a sensor" problem, this is a "how to do non-blocking cooperative multi-tasking" problem. Assuming you are running bare-metal (no operating system, such as FreeRTOS), you have two good options.
First, the datasheet shows you need to wait up to 9.04 ms, or 9040 us.
Now, here are your cooperative multi-tasking options:
Send a command to tell the device to do an ADC conversion (ie: to take an analog measurement), then configure a hardware timer to interrupt you exactly 9040 us later. In your ISR you then can either set a flag to tell your main loop to send a read command to read the result, OR you can just send the read command right inside the ISR.
Use non-blocking time-stamp-based cooperative multi-tasking in your main loop. This will likely require a basic state machine. Send the conversion command, then move on, doing other things. When your time stamp indicates it's been long enough, send the read command to read the converted result from the sensor.
Number 1 above is my preferred approach for time-critical tasks. This isn't time-critical, however, and a little jitter won't make any difference, so Number 2 above is my preferred approach for general, bare-metal cooperative multi-tasking, so let's do that.
Here's a sample program to demonstrate the principle of time-stamp-based bare-metal cooperative multi-tasking for your specific case where you need to:
request a data sample (start ADC conversion in your external sensor)
wait 9040 us for the conversion to complete
read in the data sample from your external sensor (now that the ADC conversion is complete)
Code:
enum sensorState_t
{
SENSOR_START_CONVERSION,
SENSOR_WAIT,
SENSOR_GET_CONVERSION
}
int main(void)
{
doSetupStuff();
configureHardwareTimer(); // required for getMicros() to work
while (1)
{
//
// COOPERATIVE TASK #1
// Read the under-water pressure sensor as fast as permitted by the datasheet
//
static sensorState_t sensorState = SENSOR_START_CONVERSION; // initialize state machine
static uint32_t task1_tStart; // us; start time
static uint32_t sensorVal; // the sensor value you are trying to obtain
static bool newSensorVal = false; // set to true whenever a new value arrives
switch (sensorState)
{
case SENSOR_START_CONVERSION:
{
startConversion(); // send command to sensor to start ADC conversion
task1_tStart = getMicros(); // get a microsecond time stamp
sensorState = SENSOR_WAIT; // next state
break;
}
case SENSOR_WAIT:
{
const uint32_t DESIRED_WAIT_TIME = 9040; // us
uint32_t tNow = getMicros();
if (tNow - task1_tStart >= DESIRED_WAIT_TIME)
{
sensorState = SENSOR_GET_CONVERSION; // next state
}
break;
}
case SENSOR_GET_CONVERSION:
{
sensorVal = readConvertedResult(); // send command to read value from the sensor
newSensorVal = true;
sensorState = SENSOR_START_CONVERSION; // next state
break;
}
}
//
// COOPERATIVE TASK #2
// use the under-water pressure sensor data right when it comes in (this will be an event-based task
// whose running frequency depends on the rate of new data coming in, for example)
//
if (newSensorVal == true)
{
newSensorVal = false; // reset this flag
// use the sensorVal data here now for whatever you need it for
}
//
// COOPERATIVE TASK #3
//
//
// COOPERATIVE TASK #4
//
// etc etc
} // end of while (1)
} // end of main
For another really simple timestamp-based multi-tasking example see Arduino's "Blink Without Delay" example here.
General time-stamp-based bare-metal cooperative multi-tasking architecture notes:
Depending on how you do it all, in the end, you basically end up with this type of code layout, which simply runs each task at fixed time intervals. Each task should be non-blocking to ensure it does not conflict with the run intervals of the other tasks. Non-blocking on bare metal means simply "do not use clock-wasting delays, busy-loops, or other types of polling, repeating, counting, or busy delays!". (This is opposed to "blocking" on an operating-system-based (OS-based) system, which means "giving the clock back to the scheduler to let it run another thread while this task 'sleeps'." Remember: bare metal means no operating system!). Instead, if something isn't quite ready to run yet, simply save your state via a state machine, exit this task's code (this is the "cooperative" part, as your task must voluntarily give up the processor by returning), and let another task run!
Here's the basic architecture, showing a simple timestamp-based way to get 3 Tasks to run at independent, fixed frequencies withOUT relying on any interrupts, and with minimal jitter, due to the thorough and methodical approach I take to check the timestamps and update the start time at each run time.
1st, the definition for the main() function and main loop:
int main(void)
{
doSetupStuff();
configureHardwareTimer();
while (1)
{
doTask1();
doTask2();
doTask3();
}
}
2nd, the definitions for the doTask() functions:
// Task 1: Let's run this one at 100 Hz (every 10ms)
void doTask1(void)
{
const uint32_t DT_DESIRED_US = 10000; // 10000us = 10ms, or 100Hz run freq
static uint32_t t_start_us = getMicros();
uint32_t t_now_us = getMicros();
uint32_t dt_us = t_now_us - t_start_us;
// See if it's time to run this Task
if (dt_us >= DT_DESIRED_US)
{
// 1. Add DT_DESIRED_US to t_start_us rather than setting t_start_us to t_now_us (which many
// people do) in order to ***avoid introducing artificial jitter into the timing!***
t_start_us += DT_DESIRED_US;
// 2. Handle edge case where it's already time to run again because just completing one of the main
// "scheduler" loops in the main() function takes longer than DT_DESIRED_US; in other words, here
// we are seeing that t_start_us is lagging too far behind (more than one DT_DESIRED_US time width
// from t_now_us), so we are "fast-forwarding" t_start_us up to the point where it is exactly
// 1 DT_DESIRED_US time width back now, thereby causing this task to instantly run again the
// next time it is called (trying as hard as we can to run at the specified frequency) while
// at the same time protecting t_start_us from lagging farther and farther behind, as that would
// eventually cause buggy and incorrect behavior when the (unsigned) timestamps start to roll over
// back to zero.
dt_us = t_now_us - t_start_us; // calculate new time delta with newly-updated t_start_us
if (dt_us >= DT_DESIRED_US)
{
t_start_us = t_now_us - DT_DESIRED_US;
}
// PERFORM THIS TASK'S OPERATIONS HERE!
}
}
// Task 2: Let's run this one at 1000 Hz (every 1ms)
void doTask2(void)
{
const uint32_t DT_DESIRED_US = 1000; // 1000us = 1ms, or 1000Hz run freq
static uint32_t t_start_us = getMicros();
uint32_t t_now_us = getMicros();
uint32_t dt_us = t_now_us - t_start_us;
// See if it's time to run this Task
if (dt_us >= DT_DESIRED_US)
{
t_start_us += DT_DESIRED_US;
dt_us = t_now_us - t_start_us; // calculate new time delta with newly-updated t_start_us
if (dt_us >= DT_DESIRED_US)
{
t_start_us = t_now_us - DT_DESIRED_US;
}
// PERFORM THIS TASK'S OPERATIONS HERE!
}
}
// Task 3: Let's run this one at 10 Hz (every 100ms)
void doTask3(void)
{
const uint32_t DT_DESIRED_US = 100000; // 100000us = 100ms, or 10Hz run freq
static uint32_t t_start_us = getMicros();
uint32_t t_now_us = getMicros();
uint32_t dt_us = t_now_us - t_start_us;
// See if it's time to run this Task
if (dt_us >= DT_DESIRED_US)
{
t_start_us += DT_DESIRED_US;
dt_us = t_now_us - t_start_us; // calculate new time delta with newly-updated t_start_us
if (dt_us >= DT_DESIRED_US)
{
t_start_us = t_now_us - DT_DESIRED_US;
}
// PERFORM THIS TASK'S OPERATIONS HERE!
}
}
The code above works perfectly but as you can see is pretty redundant and a bit irritating to set up new tasks. This job can be a bit more automated and much much easier to do by simply defining a macro, CREATE_TASK_TIMER(), as follows, to do all of the redundant timing stuff and timestamp variable creation for us:
/// #brief A function-like macro to get a certain set of events to run at a desired, fixed
/// interval period or frequency.
/// #details This is a timestamp-based time polling technique frequently used in bare-metal
/// programming as a basic means of achieving cooperative multi-tasking. Note
/// that getting the timing details right is difficult, hence one reason this macro
/// is so useful. The other reason is that this maro significantly reduces the number of
/// lines of code you need to write to introduce a new timestamp-based cooperative
/// task. The technique used herein achieves a perfect desired period (or freq)
/// on average, as it centers the jitter inherent in any polling technique around
/// the desired time delta set-point, rather than always lagging as many other
/// approaches do.
///
/// USAGE EX:
/// ```
/// // Create a task timer to run at 500 Hz (every 2000 us, or 2 ms; 1/0.002 sec = 500 Hz)
/// const uint32_t PERIOD_US = 2000; // 2000 us pd --> 500 Hz freq
/// bool time_to_run;
/// CREATE_TASK_TIMER(PERIOD_US, time_to_run);
/// if (time_to_run)
/// {
/// run_task_2();
/// }
/// ```
///
/// Source: Gabriel Staples
/// https://stackoverflow.com/questions/50028821/best-way-to-read-from-a-sensors-that-doesnt-have-interrupt-pin-and-require-some/50032992#50032992
/// #param[in] dt_desired_us The desired delta time period, in microseconds; note: pd = 1/freq;
/// the type must be `uint32_t`
/// #param[out] time_to_run A `bool` whose scope will enter *into* the brace-based scope block
/// below; used as an *output* flag to the caller: this variable will
/// be set to true if it is time to run your code, according to the
/// timestamps, and will be set to false otherwise
/// #return NA--this is not a true function
#define CREATE_TASK_TIMER(dt_desired_us, time_to_run) \
{ /* Use scoping braces to allow multiple calls of this macro all in one outer scope while */ \
/* allowing each variable created below to be treated as unique to its own scope */ \
time_to_run = false; \
\
/* set the desired run pd / freq */ \
const uint32_t DT_DESIRED_US = dt_desired_us; \
static uint32_t t_start_us = getMicros(); \
uint32_t t_now_us = getMicros(); \
uint32_t dt_us = t_now_us - t_start_us; \
\
/* See if it's time to run this Task */ \
if (dt_us >= DT_DESIRED_US) \
{ \
/* 1. Add DT_DESIRED_US to t_start_us rather than setting t_start_us to t_now_us (which many */ \
/* people do) in order to ***avoid introducing artificial jitter into the timing!*** */ \
t_start_us += DT_DESIRED_US; \
/* 2. Handle edge case where it's already time to run again because just completing one of the main */ \
/* "scheduler" loops in the main() function takes longer than DT_DESIRED_US; in other words, here */ \
/* we are seeing that t_start_us is lagging too far behind (more than one DT_DESIRED_US time width */ \
/* from t_now_us), so we are "fast-forwarding" t_start_us up to the point where it is exactly */ \
/* 1 DT_DESIRED_US time width back now, thereby causing this task to instantly run again the */ \
/* next time it is called (trying as hard as we can to run at the specified frequency) while */ \
/* at the same time protecting t_start_us from lagging farther and farther behind, as that would */ \
/* eventually cause buggy and incorrect behavior when the (unsigned) timestamps start to roll over */ \
/* back to zero. */ \
dt_us = t_now_us - t_start_us; /* calculate new time delta with newly-updated t_start_us */ \
if (dt_us >= DT_DESIRED_US) \
{ \
t_start_us = t_now_us - DT_DESIRED_US; \
} \
\
time_to_run = true; \
} \
}
Now, there are multiple ways to use it, but for the sake of this demo, in order to keep the really clean main() loop code which looks like this:
int main(void)
{
doSetupStuff();
configureHardwareTimer();
while (1)
{
doTask1();
doTask2();
doTask3();
}
}
Let's use the CREATE_TASK_TIMER() macro like this. As you can see, the code is now much cleaner and easier to set up a new task. This is my preferred approach, because it creates the really clean main loop shown just above, with just the various doTask() calls, which are also easy to write and maintain:
// Task 1: Let's run this one at 100 Hz (every 10ms, or 10000us)
void doTask1(void)
{
bool time_to_run;
const uint32_t DT_DESIRED_US = 10000; // 10000us = 10ms, or 100Hz run freq
CREATE_TASK_TIMER(DT_DESIRED_US, time_to_run);
if (time_to_run)
{
// PERFORM THIS TASK'S OPERATIONS HERE!
}
}
// Task 2: Let's run this one at 1000 Hz (every 1ms)
void doTask2(void)
{
bool time_to_run;
const uint32_t DT_DESIRED_US = 1000; // 1000us = 1ms, or 1000Hz run freq
CREATE_TASK_TIMER(DT_DESIRED_US, time_to_run);
if (time_to_run)
{
// PERFORM THIS TASK'S OPERATIONS HERE!
}
}
// Task 3: Let's run this one at 10 Hz (every 100ms)
void doTask3(void)
{
bool time_to_run;
const uint32_t DT_DESIRED_US = 100000; // 100000us = 100ms, or 10Hz run freq
CREATE_TASK_TIMER(DT_DESIRED_US, time_to_run);
if (time_to_run)
{
// PERFORM THIS TASK'S OPERATIONS HERE!
}
}
Alternatively, however, you could structure the code more like this, which works equally as well and produces the same effect, just in a slightly different way:
#include <stdbool.h>
#include <stdint.h>
#define TASK1_PD_US (10000) // 10ms pd, or 100 Hz run freq
#define TASK2_PD_US (1000) // 1ms pd, or 1000 Hz run freq
#define TASK3_PD_US (100000) // 100ms pd, or 10 Hz run freq
// Task 1: Let's run this one at 100 Hz (every 10ms, or 10000us)
void doTask1(void)
{
// PERFORM THIS TASK'S OPERATIONS HERE!
}
// Task 2: Let's run this one at 1000 Hz (every 1ms)
void doTask2(void)
{
// PERFORM THIS TASK'S OPERATIONS HERE!
}
// Task 3: Let's run this one at 10 Hz (every 100ms)
void doTask3(void)
{
// PERFORM THIS TASK'S OPERATIONS HERE!
}
int main(void)
{
doSetupStuff();
configureHardwareTimer();
while (1)
{
bool time_to_run;
CREATE_TASK_TIMER(TASK1_PD_US, time_to_run);
if (time_to_run)
{
doTask1();
}
CREATE_TASK_TIMER(TASK2_PD_US, time_to_run);
if (time_to_run)
{
doTask2();
}
CREATE_TASK_TIMER(TASK3_PD_US, time_to_run);
if (time_to_run)
{
doTask3();
}
}
}
Part of the art (and fun!) of embedded bare-metal microcontroller programming is the skill and ingenuity involved in deciding exactly how you want to interleave each task and get them to run together, all as though they were running in parallel. Use one of the above formats as a starting point, and adapt to your particular circumstances. Message-passing can be added between tasks or between tasks and interrupts, tasks and a user, etc, as desired, and as required for your particular application.
Here's an example of how to configure a timer for use as a timestamp-generator on an STM32F2 microcontroller.
This shows functions for configureHardwareTimer() and getMicros(), used above:
// Timer handle to be used for Timer 2 below
TIM_HandleTypeDef TimHandle;
// Configure Timer 2 to be used as a free-running 32-bit hardware timer for general-purpose use as a 1-us-resolution
// timestamp source
void configureHardwareTimer()
{
// Timer clock must be enabled before you can configure it
__HAL_RCC_TIM2_CLK_ENABLE();
// Calculate prescaler
// Here are some references to show how this is done:
// 1) "STM32Cube_FW_F2_V1.7.0/Projects/STM32F207ZG-Nucleo/Examples/TIM/TIM_OnePulse/Src/main.c" shows the
// following (slightly modified) equation on line 95: `Prescaler = (TIMxCLK/TIMx_counter_clock) - 1`
// 2) "STM32F20x and STM32F21x Reference Manual" states the following on pg 419: "14.4.11 TIMx prescaler (TIMx_PSC)"
// "The counter clock frequency CK_CNT is equal to fCK_PSC / (PSC[15:0] + 1)"
// This means that TIMx_counter_clock_freq = TIMxCLK/(prescaler + 1). Now, solve for prescaler and you
// get the exact same equation as above: `prescaler = TIMxCLK/TIMx_counter_clock_freq - 1`
// Calculating TIMxCLK:
// - We must divide SystemCoreClock (returned by HAL_RCC_GetHCLKFreq()) by 2 because TIM2 uses clock APB1
// as its clock source, and on my board this is configured to be 1/2 of the SystemCoreClock.
// - Note: To know which clock source each peripheral and timer uses, you can look at
// "Table 25. Peripheral current consumption" in the datasheet, p86-88.
const uint32_t DESIRED_TIMER_FREQ = 1e6; // 1 MHz clock freq --> 1 us pd per tick, which is what I want
uint32_t Tim2Clk = HAL_RCC_GetHCLKFreq() / 2;
uint32_t prescaler = Tim2Clk / DESIRED_TIMER_FREQ - 1; // Don't forget the minus 1!
// Configure timer
// TIM2 is a 32-bit timer; See datasheet "Table 4. Timer feature comparison", p30-31
TimHandle.Instance = TIM2;
TimHandle.Init.Period = 0xFFFFFFFF; // Set pd to max possible for a 32-bit timer
TimHandle.Init.Prescaler = prescaler;
TimHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0; // NA (has no significance) for this timer
// Initialize the timer
if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
{
// handle error condition
}
// Start the timer
if (HAL_TIM_Base_Start(&TimHandle) != HAL_OK)
{
// handle error condition
}
}
// Get the 1 us count value on Timer 2.
// This timer will be used for general purpose hardware timing that does NOT rely on interrupts.
// Therefore, the counter will continue to increment even with interrupts disabled.
// The count value increments every 1 microsecond.
// Since it is a 32-bit counter it overflows every 2^32 counts, which means the highest value it can
// store is 2^32 - 1 = 4294967295. Overflows occur every 2^32 counts / 1 count/us / 1e6us/sec
// = ~4294.97 sec = ~71.6 min.
uint32_t getMicros()
{
return __HAL_TIM_GET_COUNTER(&TimHandle);
}
References:
https://www.arduino.cc/en/tutorial/BlinkWithoutDelay
Doxygen: What's the right way to reference a parameter in Doxygen?
Enum-based error codes for error handling: Error handling in C code
Other architectural styles in C, such as "object-based" C via opaque pointers: Opaque C structs: various ways to declare them
See also:
A full, runnable Arduino example with an even better version of my CREATE_TASK_TIMER() macro from above:
My answer for C and C++, including microcontrollers and Arduino (or any other system): Full coulomb counter example demonstrating the above concept with timestamp-based, single-threaded, cooperative multi-tasking
First of all thank you for your suggestions. I tried to analyze every single possible solution you proposed.
The solution proposed by Peter seemed very interesting but I have to say that, after having gone through the datasheet several times, I don't believe that is feasible. My consideration is based on the following facts.
Using a scope I see that the acknowledge is received right after sending the command for doing conversion. See following image concerning the temperature conversion:
It seems quite clear to me the acknowledge bit right after the command. After that the SDA line (yellow) goes high, therefore I don't see how it is possible that I can exploit that for detecting when the conversion is ready.
Concerning the solution when using SPI, yes, the SDO remains low during the conversion, but I cannot use it: I need to stick with I2C. Furthermore, I have other sensors attached to that SPI bus and I agree with what Gabriel Staples says.
After my consideration I went for the solution proposed by Gabriel Staples (considering that, in order to read pressure value, I also need to read and convert temperature).
My current solution is based on a state machine with 6 states. In my solution, I distinguish between the wait time for the pressure conversion and the wait time for the temperature conversion with the idea the I could try to see how much the pressure reading degrades if I use a less precise temperature reading.
Here is my current solution. The following function is called inside the main while:
void MS5803_update()
{
static uint32_t tStart; // us; start time
switch (sensor_state)
{
case MS5803_REQUEST_TEMPERATURE:
{
MS5803_send_command(MS5803_CMD_ADC_CONV + TEMPERATURE + baro.resolution);
tStart = HAL_GetTick();
sensor_state = MS5803_WAIT_RAW_TEMPERATURE;
break;
}
case MS5803_WAIT_RAW_TEMPERATURE:
{
uint32_t tNow = HAL_GetTick();
if (tNow - tStart >= conversion_time)
{
sensor_state = MS5803_CONVERTING_TEMPERATURE;
}
break;
}
case MS5803_CONVERTING_TEMPERATURE:
{
MS5803_send_command(MS5803_CMD_ADC_READ);
uint8_t raw_value[3]; // Read 24 bit
MS5803_read_value(raw_value,3);
temperature_raw = ((uint32_t)raw_value[0] << 16) + ((uint32_t)raw_value[1] << 8) + raw_value[2];
sensor_state = MS5803_REQUEST_PRESSURE;
break;
}
case MS5803_REQUEST_PRESSURE:
{
MS5803_send_command(MS5803_CMD_ADC_CONV + PRESSURE + baro.resolution);
tStart = HAL_GetTick();
sensor_state = MS5803_WAIT_RAW_PRESSURE;
break;
}
case MS5803_WAIT_RAW_PRESSURE:
{
uint32_t tNow = HAL_GetTick();
if (tNow - tStart >= conversion_time)
{
sensor_state = MS5803_CONVERTING_PRESSURE;
}
break;
}
case MS5803_CONVERTING_PRESSURE:
{
MS5803_send_command(MS5803_CMD_ADC_READ);
uint8_t raw_value[3]; // Read 24 bit
MS5803_read_value(raw_value,3);
pressure_raw = ((uint32_t)raw_value[0] << 16) + ((uint32_t)raw_value[1] << 8) + raw_value[2];
// Now I have both temperature and pressure raw and I can convert them
MS5803_updateMeasurements();
// Reset the state machine to perform a new measurement
sensor_state = MS5803_REQUEST_TEMPERATURE;
break;
}
}
}
I don't pretend that my solution is better. I just post it in order to have an opinion from you guys. Note: I'm still working on it. Therefore I cannot guarantee is bug-free!
For PeterJ_01: I could agree that this is not strictly a teaching portal, but I believe that everybody around here asks questions to learn something new or to improve theirselves. Therefore, if you believe that the solution using the ack is better, it would be great if you could show us a draft of your idea. For me it would be something new to learn.
Any further comment is appreciated.
For the last two days i wrote a program that in basic terms generates a fairly accurate user adjustable pulse signal (both frequency and duty cycle adjustable). It basically uses the micros() function to keep track of time in order to pull low or high the 4 digital output channels.
These 4 channels need to have a phase difference of 90 degrees (think a 4cyl engine) always. In order for the user to change settings an ISR is implemented which returns a flag to the main loop to re-initialise the program. This flag is defined as a boolean 'set4'. When it is false a 'while' statement in the main loop will run the outputs. When it is true an 'if' statement will perform the necessary recalculations and reset the flag so that the 'while' statement will resume.
The program works perfectly with the initial values. Phase is perfect. However when the ISR is called and comes back to the main loop, from how i understand it resumes the program in the 'while' statement from where was originally interrupted, until it finishes and re-checks the flag 'set4' to see it is now true and it should stop.
Then, even though the 'if' statement afterwards resets and re-calculates all the necessary variables the phase between these 4 output channels is lost. Tested manually i see depending on which time the ISR is called it will give different results, usually having all 4 output channels synchronised together!
This happens even though i might don't change any values (thus the 'if' routine resets the variables to exactly the same ones when you first power up the arduino!). However, if i comment out this routine and just leave the line which resets the flag 'set4' the program will continue normally like nothing never happened!
I'm pretty sure that this is somehow caused because of the micros() timer because the loop will be resumed from where the ISR was called. I've tried to do it differently by checking and disabling for interrupts using cli() and sei() but i couldn't get it to work because it will just freeze when the arguments for cli() are true. The only solution that i can think of (i've tried everything, spend the whole day searching and trying out stuff) is to force the ISR to resume from the start of the loop so that the program may initialize properly. Another solution that comes to mind is to maybe reset the micros() timer somehow..but this would mess up the ISR i believe.
To help you visualise what is going on here's a snip of my code (please don't mind the 'Millis" name in the micros variables and any missing curly brackets since it is not pure copy-paste :p):
void loop()
{
while(!set4)
{
currentMillis = micros();
currentMillis2 = micros();
currentMillis3 = micros();
currentMillis4 = micros();
if(currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW)
{
interval = ONTIME;
ledState = HIGH;
}
else
{
interval = OFFTIME;
ledState = LOW;
}
// set the LED with the ledState of the variable:
digitalWrite(ledPin, ledState);
}
.
.
//similar code for the other 3 output channels
.
.
}
if (set4){
//recalculation routine - exactly the same as when declaring the variables initially
currentMillis = 0;
currentMillis2 = 0;
currentMillis3 = 0;
currentMillis4 = 0;
//Output states of the output channels, forced low seperately when the ISR is called (without messing with the 'ledState' variables)
ledState = LOW;
ledState2 = LOW;
ledState3 = LOW;
ledState4 = LOW;
previousMillis = 0;
previousMillis2 = 0;
previousMillis3 = 0;
previousMillis4 = 0;
//ONTIME is the HIGH time interval of the pulse wave (i.e. dwell time), OFFTIME is the LOW time interval
//Note the calculated phase/timing offset at each channel
interval = ONTIME+OFFTIME;
interval2 = interval+interval/4;
interval3 = interval+interval/2;
interval4 = interval+interval*3/4;
set4=false;
}
}
Any idea what is going wrong?
Kind regards,
Ken
The problem is here:
previousMillis = 0;
previousMillis2 = 0;
previousMillis3 = 0;
previousMillis4 = 0;
All if statement will be true on the next loop.
Try with:
previousMillis = micros();
previousMillis2 = micros();
previousMillis3 = micros();
previousMillis4 = micros();