Life is so short that it is barely enough to make the necessary number of mistakes, and repeating them is an unacceptable luxury.
In this post we will discuss not to repeat the mistakes of others, which is also a waste of such a valuable resource as time. And it seems that the error is not so fatal and there are plenty of examples where it is excluded and you could learn how to avoid it for a long time, but for some reason with persistence worthy of a better use, it occurs again and again in the source codes of the programs for the MC (maybe for large systems, too, but I do not do them), and the authors of these programs are not so new to embedded programming, but nevertheless we see what we see. I sincerely hope that after you read this post (when I tried to enter the combination “after reading” in a strictly defined place of the text, I had Word To Go 6 times - for the first time in 2 years of use, so I put up with it and wrote to the other is to the question of errors, although this behavior is unlikely due to the one about which I am writing, otherwise it would be especially piquant). You will forever understand the inadmissibility of such an erroneous construction and do not step on this rake, because there are so many others waiting for their turn.
We formulate the task - we need to interact with some resource (hardware, but we will not limit ourselves), which due to internal features is not always ready for work (takes time to perform the current operation, but again we prefer the extended formulation). In order to determine the readiness of a resource, there is some procedure for checking the state and the interaction itself should be carried out when a certain value of this state is reached. If we go to the language of specifics, then in the example, which caught my eye, the process of transmitting data via the SPI interface to the ATMEL MK company was considered (well, again, we’re talking about Arduino, you can not read further).
So, the task is to organize such interaction, taking into account the above features, we will need a readiness check and initialization of the operation and the question is in what order to apply them. Since we have only two entities, they can be positioned only in two ways - the first one in front or the second one. If translated into the language of a specific task, the question is whether the device’s readiness should be checked before the transfer, waiting for one, or you can first make the transfer and then wait for its completion.
Let's look at the code, here is the first (erroneous) version taken from the implementation in question:
')
static inline void writeSPI(const byte b) { SPDR = b; asm ("nop"); while ( ! (SPSR & bit(SPIF)) ); };
And since it (except for the third line) coincides with the manufacturer recommended by the manufacturer in the chip description and application examples, we will further criticize the solutions from the ATMEL company. And here is the second (correct) version, proposed as an alternative in the same notation:
static inline void writeSPI(const byte b) { while ( (SPSR & bit(SPIF)) == 0 ); SPDR = b; }
By the way, I can not fail to note the correct use of the static and const keywords in the function signature, we consider the inline as a neutral solution.
In principle, it would be possible to conclude the discussion on this, just use the correct version and everything, but since the incorrect one occurs again and again, you will have to prove that the second solution is better than the first one in this particular case, and in general. For a person who was educated in the 80s of the last century, it is completely natural that codes 105737 177564 100375 should be located before codes 112737 000060 177566, this is exactly what should be done and this is not even discussed (hello, colleagues), and for everyone else we will continue.
INVARIANTSThe first option requires that 1) when entering the function, the device is ready for servicing, moreover, it is implied, 2) that once formed readiness cannot be canceled otherwise than by initializing the operation. If the second statement for this particular device is true, then the first one is by no means guaranteed at the beginning of its use. The second option does not impose any restrictions (or rather, imposes, but they are vanishingly small compared to the first), which is undoubtedly a plus, as it expands the scope. Of course, they (hereinafter “they” are those who use the first option, in particular, ATMEL programmers) will say that in this particular case all invariants are fulfilled and will be right, but we prefer the universal option - 0: 1 In our favor.
SEQUENCEIn any procedure for using a resource, three phases can be distinguished - the beginning of work, the repetition, the end of work. The first option is not bad in the first phase (if the invariants are fulfilled), it is good in the second, but in the third it performs unnecessary work - it waits for a resource that we no longer need. The second option is not bad in the first phase (performs only a little extra work if the invariant is executed), is good in the second, and good in the third - it stops working in time. We understand that this approach is contrary to the “scout principle”, which the first option adheres to, but in real life scouts are not always an example to follow. Of course, they will say that there is so much extra work and it is in favor of her neighbor, and they will be right, but still - 0: 1 in our favor.
COMPATIBILITYThe first option will have problems in dealing with the second, the second will feel great, even if someone follows the first strategy. Of course, they will say that strategies should not be confused, and they will be right, but we are in real life, and here everything is -0: 1 in our favor.
SECURITYFrom this point on in more detail. Suppose we have a competition for a resource, then we must consider the interval of vulnerability - the shorter it is, the safer we are. In the first embodiment, this is the interval from the start of the operation to the device’s readiness, in the second, from the receipt of the device’s readiness to the beginning of the operation, and this interval is once (or orders of magnitude less) than the first. Accordingly, the second option is almost safe, but as they say, you cannot be a little pregnant, we will improve the situation.
Natural protection of the resource in this case is the use of the critical section in one form or another and what we see in this implementation (I am of the opinion that the shorter the critical section, the better, you can have something else). First option:
static inline void writeSPI(const byte b) { CriticalStart(); SPDR = b; while ( ! (SPSR & bit(SPIF)) ); CriticalStop(); };
And we made a critical section of considerable time. We will not even consider the implementation in a similar style for the second option, but we will immediately indicate the correct protection:
static inline void writeSPI(const byte b) { blean IsOk = False; while (IsOk == False) { if ( (SPSR & bit(SPIF)) != 0 ) { riticalStart(); If ( (SPSR & bit (SPIF)) != 0 ) { SPDR = b; IsOk=True; }; CriticalStop(); }; }; }
Yes, it is much more difficult, yes, two checks are necessary and we should create a macro or inline function, following the DRY principle (I did not specifically do this to emphasize this need), yes, the function being built in here is a very big question, but it is short ( in time) the critical section, which in 99.999% of cases will be executed in one attempt, but will remain safe and in 0.001% remaining.
We note one more important circumstance - in general, if we have an atomic check and replace operation, or an exchange with a flag, then we generally can do without the critical section (in the specific version under consideration there is no such operation), but for the first the option of such an operation does not exist in principle. Of course, they will say that resource sharing is rarely used in MCs, that this option should be implemented through a handler, and they will be right, but in real life everything happens and there is probably extra security, but it is extremely rare - 0: 1 in our favor.
EFFICIENCY. Last but Not LeastIn principle, this item would be enough, but then the post would be much shorter. The first option in terms of time costs can be described as follows: we initiate the operation and wait until it is executed (we cannot do anything), if necessary, we carry out some additional operations (prepare the next character for transmission) and repeat the cycle. The second option is to wait until the previous operation is completed, we initiate the next operation and exit the function, that is, we can carry out additional operations, while the operation on the resource goes in parallel with them, if necessary, repeat the cycle. It is obvious that we win the minimum of the time of additional operations or the time of execution on the resource, and neither one nor the other is zero. In a particular situation, the gain can be very significant and increase the system speed up to two times (in case of coincidence of two specified times). Of course, they will say that the maximum possible speed is not always required, that special measures are needed to achieve it, that the gain will not always be so significant and will be right, but still 0: 1 in our favor.
UNDERSTANDIf someone has arguments in favor of one of the options, I ask in the commentary, I don’t have them - 0: 0 (except for the safe implementation of the second option, which is obviously harder than all the others).
FINDINGSThe second method provides a wider application, is more economical in operation, is more compatible, more secure, uniquely more efficient and no less understandable than the first.
The total score of 0: 5 in our favor, that is, the second method is better in all respects.
If someone can give arguments in favor of the first option, please in the comments, I can not think of anything. The only question - why the company chose this particular method - is there an answer in the style of the famous film “Maybe it's because you ...”, which, of course, “explains a lot”, but still does not seem to me correct, rather another answer from the same film "Because".