📜 ⬆️ ⬇️

Working with external device registers in the C language, part 2

It was midnight and Shehrezad continued the speeches.

To begin with, a comment to the previous article - I allowed myself to speak disrespectfully about ASSERT - I will explain my position. I don't really like it. that the ASSERT check is performed only at the execution stage, the check at the compilation stage is more efficient. I was told in person about the existence of a construct that allows you to do the necessary checks at the compilation stage (I checked it really works), I think that a post on this topic will soon appear in the sandbox.
So we continue ...
As mentioned above, the registers of external devices are characterized by their location in the address space (here we have already decided) and the composition of the control elements. The fact is that many control elements are not large enough to occupy the whole word of a particular MC and, in order to save address space, several control elements can be packed into one register. This solution also allows, under certain conditions, to slightly increase the speed of the programs that interact with this external device, which is why it occurs quite often. At the same time, this solution causes certain problems regarding the programming language, which we will talk about.

For example, consider an external device that has a control register, and the various bits of the register perform various functions, namely, the low (zero) digit contains the device busy flag (0 - the device is free and ready to execute the next command, 1 - the device is busy processing some command) , and 3 digits, starting from the second seniority of 30..28 (we will assume that we have a 32-bit word), contain the command code (if such a construction did not seem logical to you, then you did not see the real descriptions of the registers). How can we organize an appeal to the individual fields of such a register using C language? .. And if we change any one field, the other bits of the register should not be changed.
The first thing that comes to mind (and often the only thing that remains there) is the bit masks. For the above case, we will have something like:
#define IO_STATUS_CMD_MASK 0x70000000 #define IO_STATUS_CMD_BIT 28 #define IO_STATUS_FLAG_MASK 0x01 while (*pio_status & IIO_STATUS_FLAG) {}; *pio_status=(*pio_status & ~IO_STATUS_CMD_MASK) | ((command << IO_STATUS_CMD_BIT) & IO_STATUS_CMD_MASK); 
Probably, some of the brackets are superfluous here, if you remember the sequence of operations, but I have always held the opinion that brackets again do not cost anything at the execution stage, and it is easier to put an extra pair than remember the corresponding priority tables or God forbid be wrong. What do we see? In the first line, we tell the compiler that three consecutive bits (theoretically, there may be fields of non-consecutive bits, but this is already very similar to a perversion, but we are still normal people) form a single field.
The next line reports the low-order number of this field. The third line defines a bit field of one bit, for it we will not determine the number of the lowest (single) bit, since we will not need it. In the fourth line, we select the (single) bit that we need from the register and check its identity. But the fifth line looks somewhat threatening, but also not difficult - we parse from left to right: take the current value of the register, leave ALL bits in it, EXCEPT those that need to be changed, take the new field value, shift it to the left to the number of the least significant bit of the field, we leave in the result obtained ONLY those bits that we want to change, and we combine the two obtained values, getting what we need. In order not to write the last line every time, we can create a macro
 #define DATA_SET(ADR,MASK,BIT,DATA) (ADR)=((ADR) & ~(MASK)) | (((DATA) <<(BIT)) & (MASK)) DATA_SET(*pio_status,IO_STATUS_CMD_MASK,IO_STATUS_CMD_BIT,command); 
Since the post is not over, this solution has flaws - show them. First, the first two lines are obviously interconnected; nevertheless, they exist differently from each other, and we must monitor their own correspondence. It is easy to write a macro, which of the bit mask will make a bit mask with a single low bit.
 #define LOWBIT(MASK) (((~(MASK)-1)) & (MASK)) #define LOWBIT(MASK) (~(MASK << 1) & (MASK)) #define LOWBIT(MASK) (~(MASK)+1) & (MASK) 
, harder (I did not succeed), a beautiful macro that highlights the number of the least significant bit. But such a bit mask is enough for us, and the code for transmitting the value macro will change slightly
 #define DATA_SET(ADR,MASK,DATA) (ADR)=((ADR) & ~(MASK)) | (((DATA) * LOWBIT(MASK)) & (MASK)) DATA_SET(*pio_status,IO_STATUS_CMD_MASK,command); 
An attentive reader will ask, what about efficiency? In the old version there was a shift, which in ARM is done per clock, and in the new multiplication (and, as is required for reading, and division)? Well, if we set constant values, the macro will collapse, and if the command field is a variable? Fortunately, compilers are designed by truly talented people, and if we include at least the optimization level of the average, then multiplication and division by a constant from one bit turns into an appropriate number of shifts (at least for me). This (or similar) version of the code is widely distributed and presented in various libraries of programs for working with device registers (BSP). From the point of view of the author, his need to use a macro to refer to the field (I don’t even want to consider the option of directly using line 5 of the first example in the text is simply monstrous) and the need to carefully monitor the mask values ​​used in macros, which is not make offensive type errors
 DATA_SET(*pio_status,IO_STATUS_FLAG_MASK,command); 
, because the compiler cannot verify anything for us. Of course, you can write another macro
 #define IO_CMD_SET(DATA) DATA_SET(*pio_status,IO_STATUS_CMD_MASK,DATA) IO_CMD_SET(command); 
, but very quickly there will be a bit of such macros and the possibility of an error will arise again Another disadvantage is the lack of type checking, that is, an operation like
 IO_CMD_SET(36); 
the compiler will not cause any doubts, although the result of execution may unpleasantly surprise you.
Well, now that we have seen all the shortcomings of the common method, I’m just the same to provide a way to describe the registers, which are devoid of them (but at the same time have my own). So, bit fields appear on the scene, which are quite described in any C book, where they save memory by placing “short” objects in one word. At the same time, nothing prevents us from using these language tools to describe existing registers. With this, we must be extremely precise in controlling the process of packing fields into words, namely the direction of packing and the requirements for alignment. To control these processes in the compiler in question there is a directive:
 #pragma bitfields = disjoint_types //  ,      #pragma bitfields = reversed //  ,      
Then the structure of the register in question can be described as follows:
 #pragma bitfields=reverse typedef struct { unsigned :1; //    (31) unsigned code:3; // (30..28) unsigned : 27; //  27  (27..1) unsigned flag:1; // (0 -  ) } tIO_STATUS; #pragma bitfields=default volatile tIO_STATUS * const pio_status = (tIO_STATUS *) (IO_ADR); 
Note: the penultimate line of the example returns the field placement mode to its default value, in case there are further fragments, the authors of which did not concern themselves with the inclusion of the pragma before their definition (but we are not the same, we are prudent). We note the disadvantage of this method - we must use our hands to calculate the length of the fields to fill in (more on this later) and then proceed to enumerate the merits: we don’t have to define any masks and bits (the compiler does everything for us), moreover, it also checks the recorded in the field constant data on validity. Register fields are now accessed by standard language tools.
 while (pio_status->flag) {}; poi_status->code=3; 
, moreover, autocompletion and type checking, that is, the operator
 pio_status->code=34; 
will receive a warning from the compiler.
Now about efficiency - I don’t know how it is on other compilers, but on mine (that is, on IAR, of course) there is no difference in the generated code, that is, the compiler turns bit fields into appeal to bit masks with all the necessary shifts and logical operations, but no more. Moreover, for MCs that support bitwise addressing, the code for working with a one-bit field can be more efficient. Summing up, the use of bit fields gives us much more comfortable work in the absence of overhead costs. The only drawback of such a method is the impossibility of working in one operator with several fields at once in a standard way, which is possible using masks. At the same time, operators of type
 *pio_status=(*pio_status & ~(IO_STATUS_CMD_MASK | IO_STATUS_FLAG_MASK)) | ((command << IO_STATUS_CMD_BIT) & IO_STATUS_CMD_MASK) | (IO_STATUS_FLAG_MASK * flagset); 
in no way can be recommended for use due to maintenance difficulties. However, if there is a need for them, that is, such simultaneous access to the fields is required by the features of the external device, then constructions like
 #define WORD(adr) *(int*)(adr) WORD(pio_status)=WORD(pio_status) & ... 
will allow us to extend the rope to the required size, although personally in such a situation I would prefer to create a temporary structure, modify it, and then send it to the register,
 tIO_SATUS tmp; tmp=*pio_status; //      tmp.code=command; tmp.flag=flagset; *pio_status=tmp; 
but sometimes this may be unacceptable for reasons of efficiency. Another note is that if you need such operators, certain difficulties will arise if you describe some fields in the structure as const
 #pragma bitfields=reverse typedef struct { unsigned :1; //    (31) unsigned code:3; // (30..28) unsigned : 27; //  26  (27..2) unsigned const readflag:1; //     (1) unsigned flag:1; // (0 -  ) } tIO_STATUS; #pragma bitfields=default volatile tIO_STATUS * const pio_status = (tIO_STATUS *) (IO_ADR); tIO_STATUS tmp={0,0,1}; //  ,       tmp=*pio_status; //     *(int*)(&tmp)=*(int*)pio_device; //   tmp.code=3; tmp.flag=1; *(int*)(pio_device)=*(int*)(&tmp); 
That looks a bit clumsy.
Well, in conclusion about the disadvantage associated with manual calculation of bits - when the post was conceived, there should have been a smart macro that received a register description like
 REGISTR(tIO_STATUS,code[30..28],readflag[1],flag[0]) 
and significantly simplified the work of the programmer by creating an appropriate description of the structure for later use (those who programmed in MASM met similar macros). And here an unpleasant surprise awaited me - the C preprocessor is not a macro language, since it lacks a number of necessary possibilities. It really is just a preprocessor and its text processing abilities are very, very limited. At first I did not believe in such a discovery, and for about an hour I considered myself an idiot, unable to find information. Then I considered the authors as idiots on the Internet for an hour and tried using perverted ways to construct the macro I needed. In principle, perhaps it can still be done under the condition of a two-pass preprocessor, but it will look awful. Well, and then I realized that the preprocessor developers did not set themselves the task of creating a macro language (it is not clear why, but they know better), so that they are all great and I am not so bad either. Nevertheless, the problem is not solved - it is possible to use external preprocessors such as M4 or PowerShell, it is possible to use scripting languages ​​like Perl, you can even have your own minimal preprocessor in the form of a Java module or even an executable file, but all of these are crutches and do not seem beautiful and, most importantly, convenient by decision. If I misunderstood something, tell me in the comments.
The general conclusion is that the C bitfields are an effective and convenient tool for describing the registers of external devices, which is strongly recommended for use when taking minimal control measures.
Something again turned out to be long, so the question of filling out the fields (# define VS enum) and part of related issues will be taken out in the third part.

')

Source: https://habr.com/ru/post/221459/


All Articles