Reply by Reinhardt Behm September 29, 20202020-09-29
On 9/28/20 6:12 PM, pozz wrote:
>> You are not supposed to use a watchdog for that - you are supposed to >> use good software development techniques and comprehensive testing. >> (Yes, I know the real world is not perfect.) > > Yes I know, but Good Software Development Techniques aren't perfect, so > the watchdog is one of the last chance to put the system in a safe state > (restarting in my case).
What make you so sure that the same guy who messed up the real software get's the watchdog right?
Reply by David Brown September 29, 20202020-09-29
On 29/09/2020 08:44, pozz wrote:
> Il 28/09/2020 12:56, David Brown ha scritto: >> On 28/09/2020 12:12, pozz wrote: >>> Il 28/09/2020 11:26, David Brown ha scritto: >>>> On 28/09/2020 08:38, pozz wrote: >>>>> Il 27/09/2020 18:38, David Brown ha scritto: >>>>>> On 27/09/2020 17:56, pozz wrote: >>>>>>> I usually enable watchdog during boot init code. I usually use the >>>>>>> internal watchdog that is available in the MCU I'm using. I know an >>>>>>> external watchdog can be more secure, but internal watchdog is >>>>>>> usually >>>>>>> better than nothing. >>>>>>> >>>>>>> Some MCUs can be programmed to enable watchdog immediately after >>>>>>> reset >>>>>>> and stays enabled forever. Other times I enable watchdog timer >>>>>>> immediately after some initialization code. >>>>>>> >>>>>>> During my previous thread "Power On Self Test", Richard Damon said: >>>>>>> >>>>>>>> "Yes, testing watchdogs is tricky." >>>>>>> >>>>>>> Tricky? Why? >>>>>>> >>>>>> >>>>>> Certainly it is not hard to test a watchdog trigger by simply not >>>>>> kicking it for a while. >>>>>> >>>>>> But think about what the watchdog is for - what are you actually >>>>>> trying >>>>>> to do with it?&#4294967295; Are you trying to find where you have an infinite >>>>>> loop >>>>>> in your program, that you have failed to find with other debugging? >>>>>> The >>>>>> watchdog /might/ help as a last resort, but you typically have no >>>>>> idea >>>>>> what has gone wrong. >>>>> >>>>> Yes, you're right, but I don't think watchdog is a solution for every >>>>> issues that could happen in the field. It is a solution for big >>>>> problems >>>>> with the execution flow of the program, because normal flow feeds the >>>>> watchdog, unexepected flow doesn't. >>>>> >>>>> >>>>>> The "good" reason for a watchdog is to handle unexpected hardware >>>>>> issues >>>>>> that have lead to a broken system - things like out-of-spec >>>>>> electrical >>>>>> interference, cosmic ray bit-flips, power glitches, etc.&#4294967295; How do you >>>>>> test that the watchdog does its job in such situations?&#4294967295; If these >>>>>> were >>>>>> faults that were easily provoked, you'd already have hardware >>>>>> solutions >>>>>> in place to deal with them. >>>>> >>>>> I didn't think watchdog was used for hardware issues, but software >>>>> bugs. >>>>> For example, when you use a wrongly initialized function pointer and >>>>> jump to a wrong address in the code... or similar things. >>>>> >>>> >>>> You are not supposed to use a watchdog for that - you are supposed to >>>> use good software development techniques and comprehensive testing. >>>> (Yes, I know the real world is not perfect.) >>> >>> Yes I know, but Good Software Development Techniques aren't perfect, so >>> the watchdog is one of the last chance to put the system in a safe state >>> (restarting in my case). >>> >>> I admit it can be useful for hardware issues too. >>> >>> >>> <ot> >>> >>>> In embedded development, treat function pointers like dynamic memory - >>>> something you /really/ want to avoid unless there is no other option, >>>> because it is often simple to get wrong, >>> >>> Why? Poor code (responsability of the developer) or and intrinsic >>> characteristics of the function pointers? >> >> As I said - they are easy to get wrong, and difficult to analyse.&#4294967295; They >> make a mockery of call chain analysis, stack size checking, and other >> things that depend on a static code flow.&#4294967295; They make it difficult or >> impossible to follow code flows backwards, or simply to answer the >> question "where can this code be called from?". >> >> Sometimes function pointers /are/ the best way to organise code.&#4294967295; But >> they should be used with care and consideration. > > Sure, but I imagine this is true for every code.
True. But some code techniques have higher "cost" than others, where cost includes risk of mistakes, risk of misunderstandings (other people may be maintaining the code), portability cost (a function pointer is efficient on an ARM but /very/ inefficient on an 8051), challenges in debugging, difficulty in analysis, and run-time efficiency cost. The cost/benefit trade-offs for small embedded systems are often very different from "big" system programming. Function pointers might be the right choice in a big system program, but wrong in a small embedded system.
> > >> (I even recommend minimising the use of data pointers, for similar >> reasons, though obviously data pointers have more essential uses.) >> >>> >>> Even null-terminated strings can be risky if you process a string >>> without the null char at the end. Your "Good Software Development >>> Techniques" should help to avoid such errors. >>> >> >> Making mistakes in programming is certainly easy. >> >>> I think function pointers are a good solution in many cases. For >>> example, I don't like a long if/else: >>> >>> &#4294967295;&#4294967295; if (me->type == LAMP1) lamp1_func(); >>> &#4294967295;&#4294967295; else if (me->type == LAMP2) lamp2_func(); >>> &#4294967295;&#4294967295; else if (me->type == LAMP3) lamp3_func(); >>> >>> I prefer to put a function pointer in the struct and call it directly: >>> >>> &#4294967295;&#4294967295; me->func(); >> >> That, to me, is a very bad design choice. >> >> typedef enum { lampType1, lampType2, lampType3 } lampTypes; >> >> Whatever the "me" struct is, have its "type" field (after a name change) >> of type "lampTypes".&#4294967295; Then you have: >> >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;switch (me->lamp_type) { >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case lampType1 : lamp1_func(); break; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case lampType2 : lamp2_func(); break; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case lampType3 : lamp3_func(); break; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;} >> >> >> This way you have a clear structure in the call graphs - you know >> exactly what can be called, from where, by what.&#4294967295; You have flexibility - >> the lampX_func functions don't have to have the same signature.&#4294967295; You >> have better optimisation, as the compiler can combine parts of these (if >> they are visible or you are using LTO).&#4294967295; You can't get bad or >> uninitialised function pointers.&#4294967295; > > As a function pointer can assume a bad or uninitialized value, don't you > think the lamp_type variable could assume a bad or uninitialized value? >
No. How could it have an uninitialised value? That would be a fault in the code that first sets up the lamp object, and when this is done in one place, it's hard to get wrong. How could it have a bad value? You've made this an enumerated type - stick to your programming standard that says you only use elements of the type, combine it with compiler warnings or third-party linters and only assign valid lampTypes values. (If you are using C++, use a class enum here.) Of course you could have stack corruption, or runaway pointers, that result in the memory for lamp_type being overwritten with rubbish - but then you have serious problems anyway. (And avoiding function pointers means you can analyse your stack usage more realistically, and minimising data pointers makes runaway pointers less of a risk.) You can also add a clause default : panic("lamp_type is %i", (int) lamp_type); if you think it is an issue. Note that this will catch /any/ bad value, unlike any check on a function pointer.
> > You can't miss out on anything, >> because your compiler or third-party static analysis tool will tell you >> if an enumeration is missed in the switch. > > But you need to write the possibly-long switch statement every time you > have to call a function that depends on the type of me.
Yes.
> The code will be cluttered with those switch(), instead of a simple > function call: > > void lamp_on(struct mystruct *me) { me->on(); } > void lamp_off(struct mystruct *me) { me->off(); } > void lamp_dim(struct mystruct *me, uint8_t level) { me->dim(level); } > void lamp_rgb(struct mystruct *me, uint32_t rgb) { me->rgb(rgb); } >
You'd rather clutter it with lots of function pointers in your "mystruct" struct (wasting ram, code, run-time, source code, and opening new risks for errors) ? A good clue that your design choices are questionable (I won't say "wrong" - there are no absolutes here) is when you have far more flexibility than you need. Your arrangement means a lamp can have an "on" function that uses a GPIO and an "off" function that uses SPI. With my method, if your lamp_type is "lampGPIO" then that is used for off(), on(), and everything else.
> Suppose you need to create a new lamp type, you need to touch many parts > of the code, instead of initialize function pointers in one point only. >
Yes. It is a small price to pay.
> >>> This helps to mimic OOP in C too. >> >> That is not a good thing.&#4294967295; If you want to use OOP, use C++.&#4294967295; (C++ >> compilers and analysis tools know far more about virtual functions than >> about unrestricted function pointers.) >> >>> >>>> it is far more difficult to >>>> analyse than static alternatives, >>> >>> This is true. >>> >>>> and it is also less efficient. >>> >>> Do you think my example above is less efficient with function pointer? >> >> It is not unlikely, but it depends on the rest of the source code and >> organisation.&#4294967295; If the source of these lampX_func functions is known to >> the compiler when it generates the switch statement, and they are >> declared "static" (or if you are using LTO), then yes, the switch >> solution will probably be more efficient. >> >> But efficiency arguments are secondary to making code easier to write >> correctly, harder to write incorrectly (these are not the same thing), >> easier to analyse, and easier to debug. > > I'm not an expert and I don't pretend to be a programming guru. > However I usually study embedded programming by reading online blogs, > newsgroups (like this) and books, and I see mentioneds function pointers > many time.
Certainly function pointers /are/ used in this way. But I am telling you that /I/ do not think they are a good idea in such contexts - I don't think they are a good cost/benefit trade-off. I have also read a lot of online blogs, websites, etc., and while some are good, a lot of them are crap. Some dangerously so.
> > For example, in Grenning's book "Test Driven Development for Embedded C" > there's a chapter "SOLID[1] C Design Models" where function pointers are > used to reduce (completely avoid) switch statement. > > > [1] Here SOLID stands for > - S Single Responsability Principle > - O Open Closed Principle > - L Liskov Substitution Principle > - I Interface Segregation principle > - D Dependecy Inversion Principle >
Based /solely/ on those comments (and I know doing so is grossly unfair), that book would make an excellent stand for a pot plant. If you want to do something like this, use C++. Not C. Making half-arsed sort-of-OOP code in C leaves you with something that is the worst of both languages. (That doesn't mean you can't do TDD in C - but it is not the same as doing it in C++.) If you program in decent, clean C, your code flow is clear and can be followed in the code, by analysis programs, in call-graphs, by the debugger, by stack size checkers. But you have to write your switches and you have to use manual rules about enums and other restrictions. If you program in solid, modern C++, your code flow is harder to track, your debugging is tougher as there is a wider separation between source code and object code, but the language and the compiler enforce rules for you. You don't need to worry about bad or uninitialised data because the language won't let you create such objects.
Reply by David Brown September 29, 20202020-09-29
On 28/09/2020 17:34, Rick C wrote:
> On Monday, September 28, 2020 at 5:29:29 AM UTC-4, David Brown > wrote: >> On 28/09/2020 09:17, Rick C wrote:
>>> I recall a software team who thought it was a good idea to use a >>> timer driven interrupt routine to tickle the watchdog. >>> >> >> That is fine - /if/ the kicking (I kick my watchdogs, rather than >> tickle them!) is conditional on other checks. For example, each >> other task in the system sets a boolean flag when it runs its loop, >> and the watchdog check in the timer interrupt checks that all flags >> are on before kicking the watchdog and resetting all the flags. >> It's a standard way to get the effect of having multiple >> watchdogs. > > Yes, but they weren't doing that.
Then I agree with your criticism. It sounds a lot like "The software spec said implement a watchdog routine. We made one" without actually thinking about what would be /useful/.
> Also, you need to vet the watchdog > software very carefully. A "sometimes" watchdog is about the same as > none. >
Agreed.
Reply by pozz September 29, 20202020-09-29
Il 28/09/2020 12:56, David Brown ha scritto:
> On 28/09/2020 12:12, pozz wrote: >> Il 28/09/2020 11:26, David Brown ha scritto: >>> On 28/09/2020 08:38, pozz wrote: >>>> Il 27/09/2020 18:38, David Brown ha scritto: >>>>> On 27/09/2020 17:56, pozz wrote: >>>>>> I usually enable watchdog during boot init code. I usually use the >>>>>> internal watchdog that is available in the MCU I'm using. I know an >>>>>> external watchdog can be more secure, but internal watchdog is usually >>>>>> better than nothing. >>>>>> >>>>>> Some MCUs can be programmed to enable watchdog immediately after reset >>>>>> and stays enabled forever. Other times I enable watchdog timer >>>>>> immediately after some initialization code. >>>>>> >>>>>> During my previous thread "Power On Self Test", Richard Damon said: >>>>>> >>>>>>> "Yes, testing watchdogs is tricky." >>>>>> >>>>>> Tricky? Why? >>>>>> >>>>> >>>>> Certainly it is not hard to test a watchdog trigger by simply not >>>>> kicking it for a while. >>>>> >>>>> But think about what the watchdog is for - what are you actually trying >>>>> to do with it?&#4294967295; Are you trying to find where you have an infinite loop >>>>> in your program, that you have failed to find with other debugging? >>>>> The >>>>> watchdog /might/ help as a last resort, but you typically have no idea >>>>> what has gone wrong. >>>> >>>> Yes, you're right, but I don't think watchdog is a solution for every >>>> issues that could happen in the field. It is a solution for big problems >>>> with the execution flow of the program, because normal flow feeds the >>>> watchdog, unexepected flow doesn't. >>>> >>>> >>>>> The "good" reason for a watchdog is to handle unexpected hardware >>>>> issues >>>>> that have lead to a broken system - things like out-of-spec electrical >>>>> interference, cosmic ray bit-flips, power glitches, etc.&#4294967295; How do you >>>>> test that the watchdog does its job in such situations?&#4294967295; If these were >>>>> faults that were easily provoked, you'd already have hardware solutions >>>>> in place to deal with them. >>>> >>>> I didn't think watchdog was used for hardware issues, but software bugs. >>>> For example, when you use a wrongly initialized function pointer and >>>> jump to a wrong address in the code... or similar things. >>>> >>> >>> You are not supposed to use a watchdog for that - you are supposed to >>> use good software development techniques and comprehensive testing. >>> (Yes, I know the real world is not perfect.) >> >> Yes I know, but Good Software Development Techniques aren't perfect, so >> the watchdog is one of the last chance to put the system in a safe state >> (restarting in my case). >> >> I admit it can be useful for hardware issues too. >> >> >> <ot> >> >>> In embedded development, treat function pointers like dynamic memory - >>> something you /really/ want to avoid unless there is no other option, >>> because it is often simple to get wrong, >> >> Why? Poor code (responsability of the developer) or and intrinsic >> characteristics of the function pointers? > > As I said - they are easy to get wrong, and difficult to analyse. They > make a mockery of call chain analysis, stack size checking, and other > things that depend on a static code flow. They make it difficult or > impossible to follow code flows backwards, or simply to answer the > question "where can this code be called from?". > > Sometimes function pointers /are/ the best way to organise code. But > they should be used with care and consideration.
Sure, but I imagine this is true for every code.
> (I even recommend minimising the use of data pointers, for similar > reasons, though obviously data pointers have more essential uses.) > >> >> Even null-terminated strings can be risky if you process a string >> without the null char at the end. Your "Good Software Development >> Techniques" should help to avoid such errors. >> > > Making mistakes in programming is certainly easy. > >> I think function pointers are a good solution in many cases. For >> example, I don't like a long if/else: >> >> &#4294967295; if (me->type == LAMP1) lamp1_func(); >> &#4294967295; else if (me->type == LAMP2) lamp2_func(); >> &#4294967295; else if (me->type == LAMP3) lamp3_func(); >> >> I prefer to put a function pointer in the struct and call it directly: >> >> &#4294967295; me->func(); > > That, to me, is a very bad design choice. > > typedef enum { lampType1, lampType2, lampType3 } lampTypes; > > Whatever the "me" struct is, have its "type" field (after a name change) > of type "lampTypes". Then you have: > > switch (me->lamp_type) { > case lampType1 : lamp1_func(); break; > case lampType2 : lamp2_func(); break; > case lampType3 : lamp3_func(); break; > } > > > This way you have a clear structure in the call graphs - you know > exactly what can be called, from where, by what. You have flexibility - > the lampX_func functions don't have to have the same signature. You > have better optimisation, as the compiler can combine parts of these (if > they are visible or you are using LTO). You can't get bad or > uninitialised function pointers.
As a function pointer can assume a bad or uninitialized value, don't you think the lamp_type variable could assume a bad or uninitialized value? You can't miss out on anything,
> because your compiler or third-party static analysis tool will tell you > if an enumeration is missed in the switch.
But you need to write the possibly-long switch statement every time you have to call a function that depends on the type of me. The code will be cluttered with those switch(), instead of a simple function call: void lamp_on(struct mystruct *me) { me->on(); } void lamp_off(struct mystruct *me) { me->off(); } void lamp_dim(struct mystruct *me, uint8_t level) { me->dim(level); } void lamp_rgb(struct mystruct *me, uint32_t rgb) { me->rgb(rgb); } Suppose you need to create a new lamp type, you need to touch many parts of the code, instead of initialize function pointers in one point only.
>> This helps to mimic OOP in C too. > > That is not a good thing. If you want to use OOP, use C++. (C++ > compilers and analysis tools know far more about virtual functions than > about unrestricted function pointers.) > >> >>> it is far more difficult to >>> analyse than static alternatives, >> >> This is true. >> >>> and it is also less efficient. >> >> Do you think my example above is less efficient with function pointer? > > It is not unlikely, but it depends on the rest of the source code and > organisation. If the source of these lampX_func functions is known to > the compiler when it generates the switch statement, and they are > declared "static" (or if you are using LTO), then yes, the switch > solution will probably be more efficient. > > But efficiency arguments are secondary to making code easier to write > correctly, harder to write incorrectly (these are not the same thing), > easier to analyse, and easier to debug.
I'm not an expert and I don't pretend to be a programming guru. However I usually study embedded programming by reading online blogs, newsgroups (like this) and books, and I see mentioneds function pointers many time. For example, in Grenning's book "Test Driven Development for Embedded C" there's a chapter "SOLID[1] C Design Models" where function pointers are used to reduce (completely avoid) switch statement. [1] Here SOLID stands for - S Single Responsability Principle - O Open Closed Principle - L Liskov Substitution Principle - I Interface Segregation principle - D Dependecy Inversion Principle
Reply by Rick C September 28, 20202020-09-28
On Monday, September 28, 2020 at 5:29:29 AM UTC-4, David Brown wrote:
> On 28/09/2020 09:17, Rick C wrote: > > On Monday, September 28, 2020 at 2:38:40 AM UTC-4, pozz wrote: > >> Il 27/09/2020 18:38, David Brown ha scritto: > >>> On 27/09/2020 17:56, pozz wrote: > >>>> I usually enable watchdog during boot init code. I usually use > >>>> the internal watchdog that is available in the MCU I'm using. I > >>>> know an external watchdog can be more secure, but internal > >>>> watchdog is usually better than nothing. > >>>> > >>>> Some MCUs can be programmed to enable watchdog immediately > >>>> after reset and stays enabled forever. Other times I enable > >>>> watchdog timer immediately after some initialization code. > >>>> > >>>> During my previous thread "Power On Self Test", Richard Damon > >>>> said: > >>>> > >>>>> "Yes, testing watchdogs is tricky." > >>>> > >>>> Tricky? Why? > >>>> > >>> > >>> Certainly it is not hard to test a watchdog trigger by simply > >>> not kicking it for a while. > >>> > >>> But think about what the watchdog is for - what are you actually > >>> trying to do with it? Are you trying to find where you have an > >>> infinite loop in your program, that you have failed to find with > >>> other debugging? The watchdog /might/ help as a last resort, but > >>> you typically have no idea what has gone wrong. > >> > >> Yes, you're right, but I don't think watchdog is a solution for > >> every issues that could happen in the field. It is a solution for > >> big problems with the execution flow of the program, because normal > >> flow feeds the watchdog, unexepected flow doesn't. > >> > >> > >>> The "good" reason for a watchdog is to handle unexpected hardware > >>> issues that have lead to a broken system - things like > >>> out-of-spec electrical interference, cosmic ray bit-flips, power > >>> glitches, etc. How do you test that the watchdog does its job in > >>> such situations? If these were faults that were easily provoked, > >>> you'd already have hardware solutions in place to deal with > >>> them. > >> > >> I didn't think watchdog was used for hardware issues, but software > >> bugs. For example, when you use a wrongly initialized function > >> pointer and jump to a wrong address in the code... or similar > >> things. > > > > I recall a software team who thought it was a good idea to use a > > timer driven interrupt routine to tickle the watchdog. > > > > That is fine - /if/ the kicking (I kick my watchdogs, rather than tickle > them!) is conditional on other checks. For example, each other task in > the system sets a boolean flag when it runs its loop, and the watchdog > check in the timer interrupt checks that all flags are on before kicking > the watchdog and resetting all the flags. It's a standard way to get > the effect of having multiple watchdogs.
Yes, but they weren't doing that. Also, you need to vet the watchdog software very carefully. A "sometimes" watchdog is about the same as none. -- Rick C. -+ Get 1,000 miles of free Supercharging -+ Tesla referral code - https://ts.la/richard11209
Reply by David Brown September 28, 20202020-09-28
On 28/09/2020 12:12, pozz wrote:
> Il 28/09/2020 11:26, David Brown ha scritto: >> On 28/09/2020 08:38, pozz wrote: >>> Il 27/09/2020 18:38, David Brown ha scritto: >>>> On 27/09/2020 17:56, pozz wrote: >>>>> I usually enable watchdog during boot init code. I usually use the >>>>> internal watchdog that is available in the MCU I'm using. I know an >>>>> external watchdog can be more secure, but internal watchdog is usually >>>>> better than nothing. >>>>> >>>>> Some MCUs can be programmed to enable watchdog immediately after reset >>>>> and stays enabled forever. Other times I enable watchdog timer >>>>> immediately after some initialization code. >>>>> >>>>> During my previous thread "Power On Self Test", Richard Damon said: >>>>> >>>>>> "Yes, testing watchdogs is tricky." >>>>> >>>>> Tricky? Why? >>>>> >>>> >>>> Certainly it is not hard to test a watchdog trigger by simply not >>>> kicking it for a while. >>>> >>>> But think about what the watchdog is for - what are you actually trying >>>> to do with it?&#4294967295; Are you trying to find where you have an infinite loop >>>> in your program, that you have failed to find with other debugging?&#4294967295; >>>> The >>>> watchdog /might/ help as a last resort, but you typically have no idea >>>> what has gone wrong. >>> >>> Yes, you're right, but I don't think watchdog is a solution for every >>> issues that could happen in the field. It is a solution for big problems >>> with the execution flow of the program, because normal flow feeds the >>> watchdog, unexepected flow doesn't. >>> >>> >>>> The "good" reason for a watchdog is to handle unexpected hardware >>>> issues >>>> that have lead to a broken system - things like out-of-spec electrical >>>> interference, cosmic ray bit-flips, power glitches, etc.&#4294967295; How do you >>>> test that the watchdog does its job in such situations?&#4294967295; If these were >>>> faults that were easily provoked, you'd already have hardware solutions >>>> in place to deal with them. >>> >>> I didn't think watchdog was used for hardware issues, but software bugs. >>> For example, when you use a wrongly initialized function pointer and >>> jump to a wrong address in the code... or similar things. >>> >> >> You are not supposed to use a watchdog for that - you are supposed to >> use good software development techniques and comprehensive testing. >> (Yes, I know the real world is not perfect.) > > Yes I know, but Good Software Development Techniques aren't perfect, so > the watchdog is one of the last chance to put the system in a safe state > (restarting in my case). > > I admit it can be useful for hardware issues too. > > > <ot> > >> In embedded development, treat function pointers like dynamic memory - >> something you /really/ want to avoid unless there is no other option, >> because it is often simple to get wrong, > > Why? Poor code (responsability of the developer) or and intrinsic > characteristics of the function pointers?
As I said - they are easy to get wrong, and difficult to analyse. They make a mockery of call chain analysis, stack size checking, and other things that depend on a static code flow. They make it difficult or impossible to follow code flows backwards, or simply to answer the question "where can this code be called from?". Sometimes function pointers /are/ the best way to organise code. But they should be used with care and consideration. (I even recommend minimising the use of data pointers, for similar reasons, though obviously data pointers have more essential uses.)
> > Even null-terminated strings can be risky if you process a string > without the null char at the end. Your "Good Software Development > Techniques" should help to avoid such errors. >
Making mistakes in programming is certainly easy.
> I think function pointers are a good solution in many cases. For > example, I don't like a long if/else: > > &#4294967295; if (me->type == LAMP1) lamp1_func(); > &#4294967295; else if (me->type == LAMP2) lamp2_func(); > &#4294967295; else if (me->type == LAMP3) lamp3_func(); > > I prefer to put a function pointer in the struct and call it directly: > > &#4294967295; me->func();
That, to me, is a very bad design choice. typedef enum { lampType1, lampType2, lampType3 } lampTypes; Whatever the "me" struct is, have its "type" field (after a name change) of type "lampTypes". Then you have: switch (me->lamp_type) { case lampType1 : lamp1_func(); break; case lampType2 : lamp2_func(); break; case lampType3 : lamp3_func(); break; } This way you have a clear structure in the call graphs - you know exactly what can be called, from where, by what. You have flexibility - the lampX_func functions don't have to have the same signature. You have better optimisation, as the compiler can combine parts of these (if they are visible or you are using LTO). You can't get bad or uninitialised function pointers. You can't miss out on anything, because your compiler or third-party static analysis tool will tell you if an enumeration is missed in the switch.
> > This helps to mimic OOP in C too.
That is not a good thing. If you want to use OOP, use C++. (C++ compilers and analysis tools know far more about virtual functions than about unrestricted function pointers.)
> >> it is far more difficult to >> analyse than static alternatives, > > This is true. > >> and it is also less efficient. > > Do you think my example above is less efficient with function pointer?
It is not unlikely, but it depends on the rest of the source code and organisation. If the source of these lampX_func functions is known to the compiler when it generates the switch statement, and they are declared "static" (or if you are using LTO), then yes, the switch solution will probably be more efficient. But efficiency arguments are secondary to making code easier to write correctly, harder to write incorrectly (these are not the same thing), easier to analyse, and easier to debug.
Reply by pozz September 28, 20202020-09-28
Il 28/09/2020 11:26, David Brown ha scritto:
> On 28/09/2020 08:38, pozz wrote: >> Il 27/09/2020 18:38, David Brown ha scritto: >>> On 27/09/2020 17:56, pozz wrote: >>>> I usually enable watchdog during boot init code. I usually use the >>>> internal watchdog that is available in the MCU I'm using. I know an >>>> external watchdog can be more secure, but internal watchdog is usually >>>> better than nothing. >>>> >>>> Some MCUs can be programmed to enable watchdog immediately after reset >>>> and stays enabled forever. Other times I enable watchdog timer >>>> immediately after some initialization code. >>>> >>>> During my previous thread "Power On Self Test", Richard Damon said: >>>> >>>>> "Yes, testing watchdogs is tricky." >>>> >>>> Tricky? Why? >>>> >>> >>> Certainly it is not hard to test a watchdog trigger by simply not >>> kicking it for a while. >>> >>> But think about what the watchdog is for - what are you actually trying >>> to do with it?&#4294967295; Are you trying to find where you have an infinite loop >>> in your program, that you have failed to find with other debugging?&#4294967295; The >>> watchdog /might/ help as a last resort, but you typically have no idea >>> what has gone wrong. >> >> Yes, you're right, but I don't think watchdog is a solution for every >> issues that could happen in the field. It is a solution for big problems >> with the execution flow of the program, because normal flow feeds the >> watchdog, unexepected flow doesn't. >> >> >>> The "good" reason for a watchdog is to handle unexpected hardware issues >>> that have lead to a broken system - things like out-of-spec electrical >>> interference, cosmic ray bit-flips, power glitches, etc.&#4294967295; How do you >>> test that the watchdog does its job in such situations?&#4294967295; If these were >>> faults that were easily provoked, you'd already have hardware solutions >>> in place to deal with them. >> >> I didn't think watchdog was used for hardware issues, but software bugs. >> For example, when you use a wrongly initialized function pointer and >> jump to a wrong address in the code... or similar things. >> > > You are not supposed to use a watchdog for that - you are supposed to > use good software development techniques and comprehensive testing. > (Yes, I know the real world is not perfect.)
Yes I know, but Good Software Development Techniques aren't perfect, so the watchdog is one of the last chance to put the system in a safe state (restarting in my case). I admit it can be useful for hardware issues too. <ot>
> In embedded development, treat function pointers like dynamic memory - > something you /really/ want to avoid unless there is no other option, > because it is often simple to get wrong,
Why? Poor code (responsability of the developer) or and intrinsic characteristics of the function pointers? Even null-terminated strings can be risky if you process a string without the null char at the end. Your "Good Software Development Techniques" should help to avoid such errors. I think function pointers are a good solution in many cases. For example, I don't like a long if/else: if (me->type == LAMP1) lamp1_func(); else if (me->type == LAMP2) lamp2_func(); else if (me->type == LAMP3) lamp3_func(); I prefer to put a function pointer in the struct and call it directly: me->func(); This helps to mimic OOP in C too.
> it is far more difficult to > analyse than static alternatives,
This is true.
> and it is also less efficient.
Do you think my example above is less efficient with function pointer?
Reply by David Brown September 28, 20202020-09-28
On 28/09/2020 09:17, Rick C wrote:
> On Monday, September 28, 2020 at 2:38:40 AM UTC-4, pozz wrote: >> Il 27/09/2020 18:38, David Brown ha scritto: >>> On 27/09/2020 17:56, pozz wrote: >>>> I usually enable watchdog during boot init code. I usually use >>>> the internal watchdog that is available in the MCU I'm using. I >>>> know an external watchdog can be more secure, but internal >>>> watchdog is usually better than nothing. >>>> >>>> Some MCUs can be programmed to enable watchdog immediately >>>> after reset and stays enabled forever. Other times I enable >>>> watchdog timer immediately after some initialization code. >>>> >>>> During my previous thread "Power On Self Test", Richard Damon >>>> said: >>>> >>>>> "Yes, testing watchdogs is tricky." >>>> >>>> Tricky? Why? >>>> >>> >>> Certainly it is not hard to test a watchdog trigger by simply >>> not kicking it for a while. >>> >>> But think about what the watchdog is for - what are you actually >>> trying to do with it? Are you trying to find where you have an >>> infinite loop in your program, that you have failed to find with >>> other debugging? The watchdog /might/ help as a last resort, but >>> you typically have no idea what has gone wrong. >> >> Yes, you're right, but I don't think watchdog is a solution for >> every issues that could happen in the field. It is a solution for >> big problems with the execution flow of the program, because normal >> flow feeds the watchdog, unexepected flow doesn't. >> >> >>> The "good" reason for a watchdog is to handle unexpected hardware >>> issues that have lead to a broken system - things like >>> out-of-spec electrical interference, cosmic ray bit-flips, power >>> glitches, etc. How do you test that the watchdog does its job in >>> such situations? If these were faults that were easily provoked, >>> you'd already have hardware solutions in place to deal with >>> them. >> >> I didn't think watchdog was used for hardware issues, but software >> bugs. For example, when you use a wrongly initialized function >> pointer and jump to a wrong address in the code... or similar >> things. > > I recall a software team who thought it was a good idea to use a > timer driven interrupt routine to tickle the watchdog. >
That is fine - /if/ the kicking (I kick my watchdogs, rather than tickle them!) is conditional on other checks. For example, each other task in the system sets a boolean flag when it runs its loop, and the watchdog check in the timer interrupt checks that all flags are on before kicking the watchdog and resetting all the flags. It's a standard way to get the effect of having multiple watchdogs.
Reply by David Brown September 28, 20202020-09-28
On 28/09/2020 08:38, pozz wrote:
> Il 27/09/2020 18:38, David Brown ha scritto: >> On 27/09/2020 17:56, pozz wrote: >>> I usually enable watchdog during boot init code. I usually use the >>> internal watchdog that is available in the MCU I'm using. I know an >>> external watchdog can be more secure, but internal watchdog is usually >>> better than nothing. >>> >>> Some MCUs can be programmed to enable watchdog immediately after reset >>> and stays enabled forever. Other times I enable watchdog timer >>> immediately after some initialization code. >>> >>> During my previous thread "Power On Self Test", Richard Damon said: >>> >>>> "Yes, testing watchdogs is tricky." >>> >>> Tricky? Why? >>> >> >> Certainly it is not hard to test a watchdog trigger by simply not >> kicking it for a while. >> >> But think about what the watchdog is for - what are you actually trying >> to do with it?&#4294967295; Are you trying to find where you have an infinite loop >> in your program, that you have failed to find with other debugging?&#4294967295; The >> watchdog /might/ help as a last resort, but you typically have no idea >> what has gone wrong. > > Yes, you're right, but I don't think watchdog is a solution for every > issues that could happen in the field. It is a solution for big problems > with the execution flow of the program, because normal flow feeds the > watchdog, unexepected flow doesn't. > > >> The "good" reason for a watchdog is to handle unexpected hardware issues >> that have lead to a broken system - things like out-of-spec electrical >> interference, cosmic ray bit-flips, power glitches, etc.&#4294967295; How do you >> test that the watchdog does its job in such situations?&#4294967295; If these were >> faults that were easily provoked, you'd already have hardware solutions >> in place to deal with them. > > I didn't think watchdog was used for hardware issues, but software bugs. > For example, when you use a wrongly initialized function pointer and > jump to a wrong address in the code... or similar things. >
You are not supposed to use a watchdog for that - you are supposed to use good software development techniques and comprehensive testing. (Yes, I know the real world is not perfect.) In embedded development, treat function pointers like dynamic memory - something you /really/ want to avoid unless there is no other option, because it is often simple to get wrong, it is far more difficult to analyse than static alternatives, and it is also less efficient.
Reply by Rick C September 28, 20202020-09-28
On Monday, September 28, 2020 at 2:38:40 AM UTC-4, pozz wrote:
> Il 27/09/2020 18:38, David Brown ha scritto: > > On 27/09/2020 17:56, pozz wrote: > >> I usually enable watchdog during boot init code. I usually use the > >> internal watchdog that is available in the MCU I'm using. I know an > >> external watchdog can be more secure, but internal watchdog is usually > >> better than nothing. > >> > >> Some MCUs can be programmed to enable watchdog immediately after reset > >> and stays enabled forever. Other times I enable watchdog timer > >> immediately after some initialization code. > >> > >> During my previous thread "Power On Self Test", Richard Damon said: > >> > >>> "Yes, testing watchdogs is tricky." > >> > >> Tricky? Why? > >> > > > > Certainly it is not hard to test a watchdog trigger by simply not > > kicking it for a while. > > > > But think about what the watchdog is for - what are you actually trying > > to do with it? Are you trying to find where you have an infinite loop > > in your program, that you have failed to find with other debugging? The > > watchdog /might/ help as a last resort, but you typically have no idea > > what has gone wrong. > > Yes, you're right, but I don't think watchdog is a solution for every > issues that could happen in the field. It is a solution for big problems > with the execution flow of the program, because normal flow feeds the > watchdog, unexepected flow doesn't. > > > > The "good" reason for a watchdog is to handle unexpected hardware issues > > that have lead to a broken system - things like out-of-spec electrical > > interference, cosmic ray bit-flips, power glitches, etc. How do you > > test that the watchdog does its job in such situations? If these were > > faults that were easily provoked, you'd already have hardware solutions > > in place to deal with them. > > I didn't think watchdog was used for hardware issues, but software bugs. > For example, when you use a wrongly initialized function pointer and > jump to a wrong address in the code... or similar things.
I recall a software team who thought it was a good idea to use a timer driven interrupt routine to tickle the watchdog. -- Rick C. - Get 1,000 miles of free Supercharging - Tesla referral code - https://ts.la/richard11209