Writing an efficient driver time-out for an embedded system could be tough. It’s tempting to imagine that the whole lot will work as anticipated, however that’s sometimes solely the case within the lab. Whereas transmitting information over a serial interface or studying a worth from a sensor looks like it could possibly by no means fail, there are conditions the place {hardware} can lock up or glitch. When one thing goes flawed, an anticipated bit isn’t flipped, or a peripheral doesn’t reply, you don’t need your system to lock up. You need your software program to detect that an excessive amount of time has handed and permit it to proceed executing code. This submit will discover a number of easy however efficient strategies for growing time-outs in your embedded software program.
The State of Trade Code
Writing a time-out in your driver or code isn’t rocket science. It’s an issue that was solved a few years in the past, but should you look at open-source and vendor-provided code, you’ll uncover that almost all software program doesn’t embrace a time-out! In actual fact, should you take a look at how they write code that interacts with {hardware} and sensors, you’ll discover code that appears like the next:
whereas(isConversionComplete == false){
isConversionComplete = (REGISTER & REGISTER_BIT) >> REGISTER_SHIFT;
}
As you may see, the code has some time loop that expects one thing to occur in {hardware}. If that factor doesn’t occur, then the code is trapped! An unintentional infinite loop has been created! You would possibly take into account this okay should you anticipate your watchdog to swoop in and save the day. If you wish to write dependable software program that doesn’t simply cling and reset however can detect the hang-up and recuperate, then this tactic of interacting with {hardware} is not going to suffice. As a substitute, you should construct a time-out into your code.
Monitoring Time in Low-Stage Drivers
When you concentrate on monitoring time at low ranges inside your embedded software program, you’ll uncover that it presents a number of issues. First, monitoring time will add a time dependency to your code. It’s essential to determine some mechanism that can be utilized to trace time. Subsequent, the mechanism you select might waste clock cycles, or it might add further complexity to your software program.
There are a number of totally different mechanisms that you need to use to trace time and create an efficient driver time-out, akin to:
- Loop till time expiration
- Sleep till time-out
- Sleep till occasion
The time-out mechanism you employ will rely in your software program structure and whether or not you employ a real-time working system (RTOS). Let’s look at a number of of those and a few potential implementation concepts.
Loop till time expiration
Whenever you first add a time-out to your {hardware} test loop, you’ll almost definitely find yourself writing some code that appears one thing like the next:
isConversionComplete = (REGISTER & REGISTER_BIT) >> REGISTER_SHIFT;
whereas((isConversionComplete == false) && (isTimeout == false)){
isConversionComplete = (REGISTER & REGISTER_BIT) >> REGISTER_SHIFT;
TimeNow = SystemTime_Get();
if(TimeNow >= Timeout){
isTimeout = true;
}
}
At first look, this appears okay, however this method has a number of issues. First, we’re almost definitely coupling SystemTime to our driver code. The code appears okay, however I usually see that some exterior module is introduced into the code base. When that is executed, you find yourself with a bunch of modules dependent upon a single module, and the code coupling will get uncontrolled. Second, we’ve some code duplication as a result of we test the isConversionComplete standing earlier than the whereas loop, after which whether it is false, we add one other test.
The second difficulty could be solved fairly simply by changing the whereas loop right into a do . . . whereas loop. The change ensures that the code is executed no less than as soon as and helps us to refactor the code in order that it appears cleaner, as proven under:
do{
isConversionComplete = (REGISTER & REGISTER_BIT) >> REGISTER_SHIFT;
TimeNow = SystemTime_Get();
if(TimeNow >= Timeout){
isTimeout = true;
}
} whereas((isConversionComplete == false) && (isTimeout == false));
The primary drawback could be a little extra fascinating. You may embrace further modules to extend your dependencies and code coupling or use dependency injection. Dependency injection is the place we use the operate’s parameter to inject any dependencies, akin to time dependency.
In our instance, we’d like to have the ability to entry the present system time. We’d inject a pointer to the operate in our driver capabilities to make use of dependency injection. There are two methods to do that. First, you may inject it into the driving force initialization code after which handle the pointer within the driver. Second, you may move the pointer as a continuing pointer to each operate within the driver. Whereas the second seems interesting as a result of we will move the operate as a const, we could also be forcing further coupling and dependency to higher-level utility code. So, if we go along with the primary possibility, our code would possibly look one thing like the next:
Error_t MyDriver(uint32_t const (*Time_Get)(void));
It’s a easy method to get time monitoring into the driving force or different code with out including a bunch of dependencies. Whereas it is a easy method, our implementation is a polled method! That’s not very environment friendly. There are undoubtedly different approaches you may use. Let’s take a look at one different instance that makes use of sleep till time-out. You’ll discover the overall mechanism we use is dependency injection, so you may simply tailor the method for sleep till the occasion as nicely.
Sleep Till Time-Out and Sleep Till Occasion
Should you use an RTOS, you could resolve that whilst you look forward to the {hardware} to reply, you do not need to sit down and ballot the clock however as an alternative sleep the thread. An RTOS normally supplies a mechanism to get the kernel time to test for a time-out and occasion flags or semaphores to attend for an occasion.
You should utilize dependency injection to inject a sleep operate into the operate. For instance, you would possibly discover that MyDriver now appears like the next:
Error_t MyDriver(uint32_t const (*Time_Get)(void), uint32_t const (*Rtos_Sleep)(uint32_t));
The calling operate, if it have been to make use of ThreadX, would possibly appear to be the next:
MyDriver(tx_time_get, tx_thread_sleep);
MyDriver has no clue what RTOS is getting used, and it doesn’t care. It simply wants a pointer to the time and sleep capabilities which can be offered by the RTOS in order that the code can sleep and hold observe of time accurately.
Conclusions on Time-Outs
It’s frequent in an embedded system to attend for {hardware} to return a worth or standing earlier than persevering with code execution. Sadly, many code in the present day assumes the whole lot will work nicely, and that standing will ultimately come. That’s a poor assumption. As a substitute, it’s best to write your code assuming there shall be an issue. When this occurs, you’ll want to incorporate some form of time-out to return an error to a higher-level code.
Time-outs are an efficient mechanism to make sure you don’t find yourself with driver code that creates an infinite loop. A halting system or one which resets periodically is a nuisance. As a substitute, begin with one of many time-out mechanisms mentioned on this submit and adapt it to work together with your system necessities. You’ll discover that it helps you write extra dependable low-level code that doesn’t assume the whole lot will work out okay.