To the question of the order of operators

  • Tutorial
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 focus on not repeating the mistakes of others, which is also an unproductive waste of such a valuable resource as time. And it seems that the error is not so fatal and there are a lot of examples where it is excluded and one could learn to avoid it for a long time, but for some reason with persistence worthy of a better application, it occurs again and again in the source codes of programs for MK (maybe for large systems, too, but I don’t deal with 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 this post is read by you (when I try to enter the combination “after reading” in a strictly defined place in the text, Word To Go crashes 6 times to me - for the first time in 2 years of use, so I put up with it and wrote a little differently - this is about the issue of errors, although this behavior is unlikely to stem from the one I'm writing about, otherwise it would be especially piquant). You will forever understand the inadmissibility of such an erroneous design and do not step on this rake, because so many others are waiting around their turn.

We formulate the problem - 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 (it takes time to complete the current operation, but again we prefer an extended formulation). In order to determine the readiness of a resource, there is a certain procedure for checking the state, and the actual interaction should be carried out when a certain value of this state is reached. If we go into the specific language, then in an example that caught my eye, we considered the process of transferring data via the SPI interface to ATMEL MK (well, in general, again we are talking about Arduino, you can not read further).

So, the task is to organize such an interaction, taking into account the above features, we will need to check the readiness and initialization of the operation and the question is in what order to apply them. Since we have only two entities, you can arrange them in only two ways - the first in front or the second. If translated into the language of a specific task, the question is whether it is necessary to check the readiness of the device before transmission, waiting for one, or whether it is possible to transfer first, and then wait for its completion.

Let's look at the code, here is the first (erroneous) option 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 company in the description of the chip and application examples, we will further criticize the solutions from the company ATMEL. And here is the second (correct) option, 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 cannot but note the correct use of the static and const keywords in the function signature, we consider inline as a neutral solution.

In principle, it would be possible to conclude this discussion, just apply the correct option and that’s all, but since the wrong one occurs again and again, you have to prove that the second solution is better than the first both 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 in front of codes 112737 000060 177566, this is exactly what needs to be done and this is not even discussed (hello, colleagues), but for everyone else we will continue.

INVARIANTS
The first option requires that 1) when entering the function, the device is ready for service, moreover, it is understood 2) that once the prepared readiness cannot be canceled other than by initializing the operation. If the second statement for this particular device is true, then the first 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 in comparison with the first), which is undoubtedly a plus, since it expands the scope. Of course, they (hereinafter “they” are those who use the first option, in particular, programmers from ATMEL) will say that in this particular case all invariants are satisfied and will be right, but we will prefer the universal option - 0: 1 In our favor.

SEQUENCE
In any procedure for using the resource, three phases can be distinguished - the beginning of work, repetition, the end of work. The first option is not bad in the first phase (if the invariants are fulfilled), good in the second, but in the third it does too much work - it waits until the resource is ready, which we no longer need. The second option is not bad in the first phase (it does only a little extra work if the invariant is fulfilled), good in the second, and good in the third - it stops working on time. We understand that this approach contradicts 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 for it and it is in their neighbor’s favor, and they will be right, but still - 0: 1 in our favor.

COMPATIBILITY
The first option will have problems interacting with the second, the second will feel great, even if someone adheres to the first strategy. Of course, they will say that strategies should not be mixed, and they will be right, but we are in real life, and here everything happens -0: 1 in our favor.

SECURITY
From now on in more detail. Suppose we have competition for a resource, then we must consider the interval of vulnerability - the shorter it is, the more secure we are. In the first version, this is the interval from the beginning of the operation to the availability of the device, in the second - from the availability of the device to the start of the operation, and this interval is once (or even orders of magnitude) less than the first. Accordingly, the second option is almost safe, but as they say, you can not be a little pregnant, we will improve the situation.

The natural protection of the resource in this case is the use of the critical section in one form or another and what we see with this implementation (I am of the opinion that the shorter the critical section, the better, you may 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 immediately indicate the correct protection:

 static inline void writeSPI(const byte b) {
  bооlean IsOk = False;
  while (IsOk == False) {
    if ( (SPSR & bit(SPIF)) != 0 ) {
       СriticalStart();
	If  ( (SPSR & bit (SPIF)) != 0 ) {
	   SPDR = b;
	   IsOk=True;
        };
	CriticalStop();      
    };
  };
}

Yes, it’s much more complicated, yes, two checks are needed and we should create a macro or inline function following the DRY principle (I didn’t do this specifically to emphasize such a need), yes, the built-in function here is a very big question, but it’s short ( in time) the critical section, which in 99.999% of cases will be executed in one attempt, but will remain safe in 0.001% of the remaining ones.

We note one more important circumstance - in the general case, if we have an atomic operation of checking and replacing, or exchanging with a flag, then in the second variant we can do without the critical section (there is no such operation in this particular variant), but for the first A variant of such an operation does not exist in principle. Of course, they will say that resource sharing is rarely used in MK, that this option should be implemented through a handler, and they will be right, but in real life everything happens and extra security probably happens, but it is extremely rare - 0: 1 in our favor.

EFFICIENCY. Last but Not Least
In principle, this paragraph would be enough, but then the post would be much shorter. The first option in terms of time can be described as follows: we initiate the operation and wait until it is completed (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 - we wait until the previous operation is over, initiate the next operation and exit the function, that is, we can carry out additional operations, while the operation on the resource runs in parallel with them, if necessary, repeat the cycle. Obviously, we win the minimum time of additional operations or execution time on the resource, and none of the second ones are zero. In a specific situation, the gain can be very significant and increase the system performance up to two times (in case of coincidence of the two indicated 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 they will be right, but nonetheless 0: 1 in our favor.

UNDERSTANDING
If anyone has arguments in favor of one of the options, I ask for comment, I do not have them - 0: 0 (except for the safe implementation of the second option, which is obviously more complicated than all the others).

CONCLUSIONS The
second method provides wider application, is more economical in operation, better compatible, safer, definitely more efficient and no less clear than the first.
The total score is 0: 5 in our favor, that is, the second method is better in all respects.

If someone can argue in favor of the first option, I ask in the comments, I can not think of anything. To the only question - why the company chose this particular method - there is 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 right to me, another answer is more likely from the same film "Because."

Also popular now: