Why should I use & in this syntax? Problem with SPI register - embedded

I'm writing a program for SPI communication betweend LPC2109/2 and MCP4921. This is an assignment
on studies. My tutor ask me a question why "&" is necessary in this line? In this line we wait for the end of SPI transmission. Which answer should be right?
#define SPI_SPIF_bm (1<<7)
...
while((S0SPSR & SPI_SPIF_bm) == 0){}
We use "&" as logic AND, for instance: (0000 & 1000) gives us 0000 instead of (0000 | 1000) gives us 1000.
Can I use only this line of code: while((S0SPSR) == 0){}? In my opinion - no. We need to compare value in register S0SPSR with bit SPIF SPI_SPIF_bm.
Is there maybe different solution?
Attachment
User Manual for LPC2129/01: https://www.nxp.com/docs/en/user-guide/UM10114.pdf

The SPI peripheral of LPC2109/2 sets different bits of S0SPSR depending on the actual event that happens which may depend on external circumstances. For example if there's a write collision on the SPI line it sets the WCOL bit instead of SPIF.
If you use while((S0SPSR) == 0){} it will wait until either a successful transaction or an error happens because it will exit the loop if any of the bits of S0SPSR is set.
while((S0SPSR & SPI_SPIF_bm) == 0){} only checks if the transaction has completed successfully. It is a good practice to check the error bits too because in case of an error you would stuck in this loop forever as SPIF is never goint to be set.
For a robust solution I would go with something like this:
while(S0SPSR == 0) {}
if (S0SPSR & SPI_SPIF_bm) { /* SPI_SPIF_bm remains set until data register has not been accessed */
/* Success, read the data register, return data, etc. */
} else {
/* Handle error */
}
If you are interested in the particular type of the error you need to store S0SPSR in a variable in each cycle as those bits are cleared on reading S0SPSR. Also you should to add a counter or a more sophisticated timeout solution to the loop to exit if none of the flags are sets in a reasonable period.
You might think these errors would never happen because you have a simple circuit but they do happen in real life and it's worth doing proper error handling.

Related

STM32CubeMX I2C code writing to reserved register bits

I'm developing an I2C driver on the STM32F74 family processors. I'm using the STM32CubeMX Low Level drivers and I can't make sense of the generated defines for I2C start and stop register values (CR2).
The code is generated in stm32f7xx_ll_i2c.h and is as follows.
/** #defgroup I2C_LL_EC_GENERATE Start And Stop Generation
* #{
*/
#define LL_I2C_GENERATE_NOSTARTSTOP 0x00000000U
/*!< Don't Generate Stop and Start condition. */
#define LL_I2C_GENERATE_STOP (uint32_t)(0x80000000U | I2C_CR2_STOP)
/*!< Generate Stop condition (Size should be set to 0). */
#define LL_I2C_GENERATE_START_READ (uint32_t)(0x80000000U | I2C_CR2_START | I2C_CR2_RD_WRN)
/*!< Generate Start for read request. */
My question is why is bit 31 included in these defines? (0x80000000U). The reference manual (RM0385) states "Bits 31:27 Reserved, must be kept at reset value.". I can't decide between modifying the generated code or keeping the 31 bit. I'll happily take recommendations simply whether its more likely that this is something needed or that I'm going to break things by writing to a reserved bit.
Thanks in advance!
I am guessing here because who knows what was on the minds of the library authors? (Not a lot if you look at the source code!). But I would guess that it is a "dirty-trick" to check that when calling LL functions you are using the specified macros.
However it is severely flawed because the "trick" is only valid for Cortex-M3/4 STM32 variants (e.g. F1xx, F2xx, F4xx) where the I2C peripheral is very different and registers such as I2C_CR2 are only 15 bits wide.
The trick is that the library functions have parameter checking asserts such as:
assert_param(IS_TRANSFER_REQUEST(Request));
Where the IS_TRANSFER_REQUEST is defined thus:
#define IS_TRANSFER_REQUEST(REQUEST) (((REQUEST) == I2C_GENERATE_STOP) || \
((REQUEST) == I2C_GENERATE_START_READ) || \
((REQUEST) == I2C_GENERATE_START_WRITE) || \
((REQUEST) == I2C_NO_STARTSTOP))
This forces you to use the LL defined macros as parameters and not some self-defined or calculated mask because they all have that "unused" check bit in them.
If that truly is the the reason, it is an ill-advised practice that did not envisage the newer I2C peripheral. You might think that the bit was stripped from the parameter before being written to the register. I have checked, it is not. And if did you would be paying for that overhead on every call, which is also undesirable.
As an error detection technique if that is what it is, it is not even applied consistently; for example all the GPIO_PIN_xx macros are 16 bits wide and since they are masks not pin numbers, using bit 31 could for example guard against passing a literal pin-number 10 where the mask 1<<10 is in fact required. Passing 10 would refer to pins 3 and 1 not 10. And to be honest that mistake is far more likely than, passing an incorrect I2C transfer request type.
In the end however "Reserved" generally means "unused but may be used in future implementations", and requiring you to use the "reset value" is a way of ensuring forward binary compatibility. If you had such a device no doubt there would be a corresponding library update to support it - but it would require re-compilation of the code. The risk is low and probably only a problem if you attempt to run this binary on a newer incompatible part that used this bits.
I agree with Clifford, the ST CubeMC / HAL / LL library code is, in places, some of the worst written code imaginable. I have a particular issue with lines such as "TIMx->CCER &= ~TIM_CCER_CC1E" where TIM_CCER_CC1e is defined as 0x0001 and the CCER register contains reserved bits that should remain at 0. There are hundreds of such examples all throughout the library code. ST remain silent to my request for advice.

STM32F4 UART HAL driver 'save string in variable buffer'

I am in the process of writing software for an STM32F4. The STM32 needs to pull in a string via a UART. This string is variable in length and comes in from a sensor every second. The string is stored in a fixed buffer, so the buffer content changes continuously.
The incoming string looks like this: "A12941;P2507;T2150;C21;E0;"
The settings of the UART:
Baud Rate: 19200
Word lengt: 8Bits
Parity: None
Stop Bids: 1
Over sampling: 16 Samples
Global interrupt: Enabled
No DMA settings
Part of the used code in the main.c function:
uint8_t UART3_rxBuffer[25];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(&huart3, UART3_rxBuffer, 25); //restart interrupt reception mode
int main(void)
{
HAL_UART_Receive_IT (&huart3, UART3_rxBuffer,25);
}
while (1)
{
}
}
Part of the code in stm32f4xx_it.c
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
It does work to fill the buffer with the variable strings in this way, but because the buffer is constantly being replenished, it is difficult to extract a beginning and an end of the string. For example, the buffer might look like this:
[0]'E' [1]'0' [2]'/n' [3]'A' [4]'1' [5]'2' [6]'9' [7]'4' [8]'1' [9]';' [10]'P' etc....
But I'd like to have a buffer that starts on 'A'.
My question is, how can I process incoming strings on the uart correctly so that I only have the string "A12941;P2507;T2150;C21;E0;"?
Thanks in advance!!
I can see three possibilities:
Do all of your processing in the interrupt. When you get to the end of a variable-length message then do everything that you need to do with the information and then change the location variable to restart filling the buffer from the start.
Use (at least) two buffers in parallel. When you detect the end of the variable-length message in interrupt context then start filling a different buffer from position zero and signal to main context that previous buffer is ready for processing.
Use two buffers in series. Let the interrupt fill a ring buffer in a circular way that takes no notice of when a message ends. In main context scan from the end of the previous message to see if you have a whole message yet. If you do, then copy it out into another buffer in a way that makes it start at the start of the buffer. Record where it finished in the ring-buffer for next time, and then do your processing on the linear buffer.
Option 1 is only suitable if you can do all of your processing in less than the time it takes the transmitter to send the next byte or two. The other two options use a bit more memory and are a bit more complicated to implement. Option 3 could be implemented with circular mode DMA as long as you poll for new messages frequently enough, which avoids the need for interrupts. Option 2 allows to queue up multiple messages if your main context might not poll frequently enough.
I would like to share a sample code related to your issue. However it is not what you are exactly looking for. You can edit this code snippet as you wish. If i am not wrong you can also edit it according to option 3.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
HAL_UART_Receive_IT(&huart2,&rData,1);
rxBuffer[pos++] = rData;
if (rData == '\n') {
pos = 0;
}
}
Before start, in the main function, before while loop you should enable interrupt for one byte using "HAL_UART_Receive_IT(&huart2,&rData,1);". If your incoming data has limiter like '\n', so you can save whole data which may have different length for each frame.
If you want data frame start with some specific character, then you can wait to save data until you get this character. In this case you can edit this code by changing '\n' as your character, and after you get that character, you should start to save following data to inside the buffer.

How to handle GSM buffer on the Microcontroller?

I have a GSM module hooked up to PIC18F87J11 and they communicate just fine . I can send an AT command from the Microcontroller and read the response back. However, I have to know how many characters are in the response so I can have the PIC wait for that many characters. But if an error occurs, the response length might change. What is the best way to handle such scenario?
For Example:
AT+CMGF=1
Will result in the following response.
\r\nOK\r\n
So I have to tell the PIC to wait for 6 characters. However, if there response was an error message. It would be something like this.
\r\nERROR\r\n
And if I already told the PIC to wait for only 6 characters then it will mess out the rest of characters, as a result they might appear on the next time I tell the PIC to read the response of a new AT command.
What is the best way to find the end of the line automatically and handle any error messages?
Thanks!
In a single line
There is no single best way, only trade-offs.
In detail
The problem can be divided in two related subproblems.
1. Receiving messages of arbitrary finite length
The trade-offs:
available memory vs implementation complexity;
bandwidth overhead vs implementation complexity.
In the simplest case, the amount of available RAM is not restricted. We just use a buffer wide enough to hold the longest possible message and keep receiving the messages bytewise. Then, we have to determine somehow that a complete message has been received and can be passed to further processing. That essentially means analyzing the received data.
2. Parsing the received messages
Analyzing the data in search of its syntactic structure is parsing by definition. And that is where the subtasks are related. Parsing in general is a very complex topic, dealing with it is expensive, both in computational and laboriousness senses. It's often possible to reduce the costs if we limit the genericity of the data: the simpler the data structure, the easier to parse it. And that limitation is called "transport layer protocol".
Thus, we have to read the data to parse it, and parse the data to read it. This kind of interlocked problems is generally solved with coroutines.
In your case we have to deal with the AT protocol. It is old and it is human-oriented by design. That's bad news, because parsing it correctly can be challenging despite how simple it can look sometimes. It has some terribly inconvenient features, such as '+++' escape timing!
Things become worse when you're short of memory. In such situation we can't defer parsing until the end of the message, because it very well might not even fit in the available RAM -- we have to parse it chunkwise.
...And we are not even close to opening the TCP connections or making calls! And you'll meet some unexpected troubles there as well, such as these dreaded "unsolicited result codes". The matter is wide enough for a whole book. Please have a look at least here:
http://en.wikibooks.org/wiki/Serial_Programming/Modems_and_AT_Commands. The wikibook discloses many more problems with the Hayes protocol, and describes some approaches to solve them.
Let's break the problem down into some layers of abstraction.
At the top layer is your application. The application layer deals with the response message as a whole and understands the meaning of a message. It shouldn't be mired down with details such as how many characters it should expect to receive.
The next layer is responsible from framing a message from a stream of characters. Framing is extracting the message from a stream by identifying the beginning and end of a message.
The bottom layer is responsible for reading individual characters from the port.
Your application could call a function such as GetResponse(), which implements the framing layer. And GetResponse() could call GetChar(), which implements the bottom layer. It sounds like you've got the bottom layer under control and your question is about the framing layer.
A good pattern for framing a stream of characters into a message is to use a state machine. In your case the state machine includes states such as BEGIN_DELIM, MESSAGE_BODY, and END_DELIM. For more complex serial protocols other states might include MESSAGE_HEADER and MESSAGE_CHECKSUM, for example.
Here is some very basic code to give you an idea of how to implement the state machine in GetResponse(). You should add various types of error checking to prevent a buffer overflow and to handle dropped characters and such.
void GetResponse(char *message_buffer)
{
unsigned int state = BEGIN_DELIM1;
bool is_message_complete = false;
while(!is_message_complete)
{
char c = GetChar();
switch(state)
{
case BEGIN_DELIM1:
if (c = '\r')
state = BEGIN_DELIM2;
break;
case BEGIN_DELIM2:
if (c = '\n')
state = MESSAGE_BODY:
break;
case MESSAGE_BODY:
if (c = '\r')
state = END_DELIM;
else
*message_buffer++ = c;
break;
case END_DELIM:
if (c = '\n')
is_message_complete = true;
break;
}
}
}

Keeping time using timer interrupts an embedded microcontroller

This question is about programming small microcontrollers without an OS. In particular, I'm interested in PICs at the moment, but the question is general.
I've seen several times the following pattern for keeping time:
Timer interrupt code (say the timer fires every second):
...
if (sec_counter > 0)
sec_counter--;
...
Mainline code (non-interrupt):
sec_counter = 500; // 500 seconds
while (sec_counter)
{
// .. do stuff
}
The mainline code may repeat, set the counter to various values (not just seconds) and so on.
It seems to me there's a race condition here when the assignment to sec_counter in the mainline code isn't atomic. For example, in PIC18 the assignment is translated to 4 ASM statements (loading each byte at the time and selecting the right byte from the memory bank before that). If the interrupt code comes in the middle of this, the final value may be corrupted.
Curiously, if the value assigned is less than 256, the assignment is atomic, so there's no problem.
Am I right about this problem?
What patterns do you use to implement such behavior correctly? I see several options:
Disable interrupts before each assignment to sec_counter and enable after - this isn't pretty
Don't use an interrupt, but a separate timer which is started and then polled. This is clean, but uses up a whole timer (in the previous case the 1-sec firing timer can be used for other purposes as well).
Any other ideas?
The PIC architecture is as atomic as it gets. It ensures that all read-modify-write operations to a memory file are 'atomic'. Although it takes 4-clocks to perform the entire read-modify-write, all 4-clocks are consumed in a single instruction and the next instruction uses the next 4-clock cycle. It is the way that the pipeline works. In 8-clocks, two instructions are in the pipeline.
If the value is larger than 8-bit, it becomes an issue as the PIC is an 8-bit machine and larger operands are handled in multiple instructions. That will introduce atomic issues.
You definitely need to disable the interrupt before setting the counter. Ugly as it may be, it is necessary. It is a good practice to ALWAYS disable the interrupt before configuring hardware registers or software variables affecting the ISR method. If you are writing in C, you should consider all operations as non-atomic. If you find that you have to look at the generated assembly too many times, then it may be better to abandon C and program in assembly. In my experience, this is rarely the case.
Regarding the issue discussed, this is what I suggest:
ISR:
if (countDownFlag)
{
sec_counter--;
}
and setting the counter:
// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;
...
// Countdown finished
countDownFlag = false;
You need an extra variable and is better to wrap everything in a function:
void startCountDown(int startValue)
{
sec_counter = 500;
countDownFlag = true;
}
This way you abstract the starting method (and hide ugliness if needed). For example you can easily change it to start a hardware timer without affecting the callers of the method.
Write the value then check that it is the value required would seem to be the simplest alternative.
do {
sec_counter = value;
} while (sec_counter != value);
BTW you should make the variable volatile if using C.
If you need to read the value then you can read it twice.
do {
value = sec_counter;
} while (value != sec_counter);
Because accesses to the sec_counter variable are not atomic, there's really no way to avoid disabling interrupts before accessing this variable in your mainline code and restoring interrupt state after the access if you want deterministic behavior. This would probably be a better choice than dedicating a HW timer for this task (unless you have a surplus of timers, in which case you might as well use one).
If you download Microchip's free TCP/IP Stack there are routines in there that use a timer overflow to keep track of elapsed time. Specifically "tick.c" and "tick.h". Just copy those files over to your project.
Inside those files you can see how they do it.
It's not so curious about the less than 256 moves being atomic - moving an 8 bit value is one opcode so that's as atomic as you get.
The best solution on such a microcontroller as the PIC is to disable interrupts before you change the timer value. You can even check the value of the interrupt flag when you change the variable in the main loop and handle it if you want. Make it a function that changes the value of the variable and you could even call it from the ISR as well.
Well, what does the comparison assembly code look like?
Taken to account that it counts downwards, and that it's just a zero compare, it should be safe if it first checks the MSB, then the LSB. There could be corruption, but it doesn't really matter if it comes in the middle between 0x100 and 0xff and the corrupted compare value is 0x1ff.
The way you are using your timer now, it won't count whole seconds anyway, because you might change it in the middle of a cycle.
So, if you don't care about it. The best way, in my opinion, would be to read the value, and then just compare the difference. It takes a couple of OPs more, but has no multi-threading problems.(Since the timer has priority)
If you are more strict about the time value, I would automatically disable the timer once it counts down to 0, and clear the internal counter of the timer and activate once you need it.
Move the code portion that would be on the main() to a proper function, and have it conditionally called by the ISR.
Also, to avoid any sort of delaying or missing ticks, choose this timer ISR to be a high-prio interrupt (the PIC18 has two levels).
One approach is to have an interrupt keep a byte variable, and have something else which gets called at least once every 256 times the counter is hit; do something like:
// ub==unsigned char; ui==unsigned int; ul==unsigned long
ub now_ctr; // This one is hit by the interrupt
ub prev_ctr;
ul big_ctr;
void poll_counter(void)
{
ub delta_ctr;
delta_ctr = (ub)(now_ctr-prev_ctr);
big_ctr += delta_ctr;
prev_ctr += delta_ctr;
}
A slight variation, if you don't mind forcing the interrupt's counter to stay in sync with the LSB of your big counter:
ul big_ctr;
void poll_counter(void)
{
big_ctr += (ub)(now_ctr - big_ctr);
}
No one addressed the issue of reading multibyte hardware registers (for example a timer.
The timer could roll over and increment its second byte while you're reading it.
Say it's 0x0001ffff and you read it. You might get 0x0010ffff, or 0x00010000.
The 16 bit peripheral register is volatile to your code.
For any volatile "variables", I use the double read technique.
do {
t = timer;
} while (t != timer);

Event handling in embedded code

I want to know how events are used in embedded system code.
Main intention is to know how exactly event flags are set/reset in code. and how to identify which task is using which event flag and which bits of the flag are getting set/reset by each task.
Please put your suggestion or comments about it.
Thanks in advance.
(edit 1: copied from clarification in answer below)
Sorry for not specifying the details required. Actually I am interested in the analysis of any application written in C language using vxworks/Itron/OSEK OS. For example there is eventLib library in vxworks to support event handling. I want to know that how one can make use of such system routines to handle events in task. What is event flag(is it global/local...or what ?), how to set bits of any event flag and which can be the possible relationship between task and event flags ??
How task can wait for multiple events in AND and OR mode ??
I came across one example in which the scenario given below looks dangerous, but why ??
Scenarios is ==> *[Task1 : Set(e1), Task2 : Wait(e1) and Set(e2), Task3 : Wait(e2) ]*
I know that multiple event flags waited by one task or circular dependency between multiple tasks(deadlock) are dangerous cases in task-event relationship, but how above scenario is dangerous, I am not getting it....Kindly explain.
(Are there any more such scenarios possible in task-event handling which should be reviewed in code ?? )
I hope above information is sufficient ....
Many embedded systems use Interrupt Service Routines (ISR) to handle events. You would define an ISR for a given "flag" and reset that flag after you handle the event.
For instance say you have a device performing Analog to Digital Conversions (ADC). On the device you could have an ISR that fires each time the ADC completes a conversion and then handle it within the ISR or notify some other task that the data is available (if you want to send it across some communications protocol). After you complete that you would reset the ADC flag so that it can fire again at it's next conversion.
Usually there are a set of ISRs defined in the devices manual. Sometimes they provide general purpose flags that you could also handle as you wish. Each time resetting the flag that caused the routine to fire.
The eventLib in VxWorks is similar to signal() in unix -- it can indicate to a different thread that something occurred. If you need to pass data with the event, you may want to use Message Queues instead.
The events are "global" between the sender and receiver. Since each sender indicates which task the event is intended for, there can be multiple event masks in the system with each sender/receiver pair having their own interpretation.
A basic example:
#define EVENT1 0x00000001
#define EVENT2 0x00000002
#define EVENT3 0x00000004
...
#define EVENT_EXIT 0x80000000
/* Spawn the event handler task (event receiver) */
rcvTaskId = taskSpawn("tRcv",priority,0,stackSize,handleEvents,0,0,0,0,0,0,0,0,0,0);
...
/* Receive thread: Loop to receive events */
STATUS handleEvents(void)
{
UINT32 rcvEventMask = 0xFFFFFFFF;
while(1)
{
UINT32 events = 0;
if (eventReceive(rcvEventMask. EVENTS_WAIT_ANY, WAIT_FOREVER, &events) == OK)
{
/* Process events */
if (events & EVENT1)
handleEvent1();
if (events & EVENT2)
handleEvent2();
...
if (events & EVENT_EXIT)
break;
}
}
return OK;
}
The event sender is typically a hardware driver (BSP) or another thread. When a desired action occurs, the driver builds a mask of all pertinent events and sends them to the receiver task.
The sender needs to obtain the taskID of the receiver. The taskID can be a global,
int RcvTaskID = ERROR;
...
eventSend(RcvTaskID, eventMask);
it can be registered with the driver/sender task by the receiver,
static int RcvTaskID = ERROR;
void DRIVER_setRcvTaskID(int rcvTaskID)
{
RcvTaskID = rcvTaskID;
}
...
eventSend(RcvTaskID, eventMask);
or the driver/sender task can call a receiver API method to send the event (wrapper).
static int RcvTaskID;
void RECV_sendEvents(UINT32 eventMask)
{
eventSend(RcvTaskID, eventMask);
}
This question needs to provide more context. Embedded systems can be created using a wide range of languages, operating systems (including no operating system), frameworks etc. There is nothing universal about how events are created and handled in an embedded system, just as there is nothing universal about how events are created and handled in computing in general.
If you're asking how to set, clear, and check the various bits that represent events, this example may help. The basic strategy is to declare a (usually global) variable and use one bit to represent each condition.
unsigned char bit_flags = 0;
Now we can assign events to the bits:
#define TIMER_EXPIRED 0x01 // 0000 0001
#define DATA_READY 0x02 // 0000 0010
#define BUFFER_OVERFLOW 0x04 // 0000 0100
And we can set, clear, and check bits with bitwise operators:
// Bitwise OR: bit_flags | 00000001 sets the first bit.
bit_flags |= TIMER_EXPIRED; // Set TIMER_EXPIRED bit.
// Bitwise AND w/complement clears bits: flags & 11111101 clears the 2nd bit.
bit_flags &= ~DATA_READY; // Clear DATA_READY bit.
// Bitwise AND tests a bit. The result is BUFFER_OVERFLOW
// if the bit is set, 0 if the bit is clear.
had_ovflow = bit_flags & BUFFER_OVERFLOW;
We can also set or clear combinations of bits:
// Set DATA_READY and BUFFER_OVERFLOW bits.
bit_flags |= (DATA_READY | BUFFER_OVERFLOW);
You'll often see these operations implemented as macros:
#define SET_BITS(bits, data) data |= (bits)
#define CLEAR_BITS(bits, data) data &= ~(bits)
#define CHECK_BITS(bits, data) (data & (bits))
Also, a note about interrupts and interrupt service routines: they need to run fast, so a typical ISR will simply set a flag, increment a counter, or copy some data and exit immediately. Then you can check the flag and attend to the event at your leisure. You probably do not want to undertake lengthy or error-prone activities in your ISR.
Hope that's helpful!
Sorry for not specifying the details required. Actually I am interested in the analysis of any application written in C language using vxworks/Itron/OSEK OS.
For example there is eventLib library in vxworks to support event handling.
I want to know that how one can make use of such system routines to handle events in task. What is event flag(is it global/local...or what ?), how to set bits of any event flag and which can be the possible relationship between task and event flags ??
I hope above information is sufficient ....
If you're interested in using event-driven programming at the embedded level you should really look into QP. It's an excellent lightweight framework and if you get the book "Practical UML Statecharts in C/C++" by Miro Samek you find everything from how to handle system events in an embedded linux kernel (ISR's etc) to handling and creating them in a build with QP as your environment. (Here is a link to an example event).
In one family of embedded systems I designed (for a PIC18Fxx micro with ~128KB flash and 3.5KB RAM), I wrote a library to handle up to 16 timers with 1/16-second resolution (measured by a 16Hz pulse input to the CPU). The code is set up to determine whether any timer is in the Expired state or any dedicated wakeup pin is signaling, and if not, sleep until the next timer would expire or a wakeup input changes state. Quite a handy bit of code, though I should in retrospect probably have designed it to work with multiple groups of eight timers rather than one set of 16.
A key aspect of my timing routines which I have found to be useful is that they mostly aren't driven by interrupts; instead I have a 'poll when convenient' routine which updates the timers off a 16Hz counter. While it sometimes feels odd to have timers which aren't run via interrupt, doing things that way avoids the need to worry about interrupts happening at odd times. If the action controlled by a timer wouldn't be able to happen within an interrupt (due to stack nesting and other limitations), there's no need to worry about the timer in an interrupt--just keep track of how much time has passed.