Matching the structure of a bit field with a volatile register

I am writing an application that interacts with several registers that are defined in VHDL. The registers are 32 bits wide and are divided into groups. I am given the base address of the group and with 32-bit offsets for each member of the group. The following is an example of one group, a registry within a group, and a register structure.

Group 1 | base addr | offset | data_port

data_port | alt_u32 data0: 12; | alt_u32 data1: 1; | ....

I / O is currently being processed using the following bitfield structure,

typedef struct { uint32_t data0 : 12; uint32_t data1 : 1; ... }volatile data_port; 

and changing fields using a pointer to addresses,

 data_port *const p_data = (data_port *)0xc006380; 

Although this may work on this platform, with the current compiler, I am worried about portability. I was wondering if there is a better way to handle the hardware interface when it is forced to use these non-traditional data types?

One of the alternatives that I can think of is to create a different level between the hardware and registration structures, volatile unsigned int pointer and use the bitfield structure in the application layer. The problem is that the data still needs to be copied from bit fields that can be aligned differently on another platform, to int, which may be a different topic in general.

Edit:
I think what I'm really looking for is a way to eliminate the use of bit fields. It seems that comparing the structure with the elements of the bit field at the hardware level is bad. So, to exclude this, I will use one of the following instead as a pointer to a volatile memory address,

 #define PeripheralBase ((uint32_t volatile *)BASE) 

or

 uint32_t volatile *const peripheral_base = (uint32_t *) BASE; 

Hopefully as soon as I get to this point everything will be well aligned within 32 bits. One of the methods that I was thinking about was to create the same data_port structure, but remove the bitmap, and then directly the function, specifically for each register, to shift the bits into an unsigned int, which can then be transferred to the register. using volatile pointer.

Sort of,

 static inline uint32_t struct_to_uint(data_port *data) { return data->data0 + ((uint32_t)data->data1 << 12) + ((uint32_t)data->data2 << 13) + .....; } 

I'm not sure if the syntax is correct, but the idea is to bias values โ€‹โ€‹without having to worry about the compiler or the platform. What is the reason for this? Are there any portability issues with this approach?

+6
source share
3 answers

The best option, apparently, is to completely eliminate the use of the structure of the bit field. Thus, to handle input to the register, instead of splitting the bits, simply create a structure with the components of the register.

 typedef struct data_port { uint32_t data0; uint32_t data1; .... }data_port; 

Although this structure will not directly control the hardware interface, it is a useful way to process data at the application level. A pointer to a register can be created using a macro or a pointer to volatile const uint32_t.

 uint32_t volatile *const peripheral_base = (uint32_t *) BASE; 

A portable solution for copying data from a structure to an unsigned 32-bit value is to use a function to switch each value to the correct place in the register and then add values.

 static inline uint32_t struct_to_uint(data_port *data) { return data->data0 + (data->data1 << 12) + (data->data2 << 13) + .....; } 

Writing to a register can be processed by calling a function.

 *peripheral_base = stuct_to_uint(&data_port); 

The caveat here is that bit fields were not used, the values โ€‹โ€‹assigned to the data_port structure in the application should be checked to ensure that they do not exceed the bounds. Otherwise, data written to the register will produce unexpected results.

0
source

While bit fields are terribly implementation dependent, you can use macros to identify your registers:

 typedef struct { uint32_t data0 : 12; uint32_t data1 : 1; ... } data_port; #define DATA_PORT (*(volatile data_port *) 0xc006380) 

then the access bit is like this:

  DATA_PORT.data0 = 1; // set data0 bit of DATA_PORT to 1 
+5
source

A typical implementation-independent method for accessing fields in a hardware register is to use offsets (and masks). For instance:

 #define DATA0_SHIFT 0 #define DATA0_MASK 0x3FF #define DATA1_SHIFT 12 #define DATA1_MASK 0x1 #define DATA2_SHIFT 13 #define DATA2_MASK 0x1 // ... uint32_t data = 0 | ((data0 & DATA0_MASK) << DATA0_SHIFT) | ((data1 & DATA1_MASK) << DATA1_SHIFT) | ((data2 & DATA2_MASK) << DATA2_SHIFT); 

For the register itself, something like this:

 #define DATA_PORT_ADDR 0xc006380 #define DATA_PORT_REG (*(volatile uint32_t *)(DATA_PORT_ADDR)) 

This means you can do this:

 DATA_PORT_REG = data; // Value from above. 

also:

  • Do not use bit fields for this kind of thing. They are implementation dependent and therefore may exhibit unexpected behavior. The above method should work on any platform.
  • #define for a register must use an implementation-independent type of type uint32_t to explicitly show its size.
0
source

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


All Articles