Qustion for C experts - array of IO registers? - Page 1
Forum section: electronics / general

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 28, 2024, 02:55:45 pm
content: So I'm using a part with 5 UARTS. The tx registers ( for example) are called U1TXBUF, U2TXBUF etc. in the manufacturer's headersIs there a clever & code-efficient way to be able to access these (for read and write) like an array, UTXREG[0..4] Where the index is a variable, not a constant ?

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 28, 2024, 03:19:18 pm
content: It can be done with an array of pointers but you have to use the * in front of it to access the actual value.Code:uint8_t *uarts_tx;//Setupuarts_tx = U1TXBUF;uarts_tx = U2TXBUF;uarts_tx = U3TXBUF;uarts_tx = U4TXBUF;uarts_tx = U5TXBUF;//Usage*uarts_tx = 0x0A;You can create an array of UART peripherals by using structures and fill in the memory gaps with reserved space like it is done in STM32 header files.Code://Universal Serial Bus Full Speed Devicetypedef struct{__IO uint16_t EP0R; //USB Endpoint 0 register, Address offset: 0x00__IO uint16_t RESERVED0; //Reserved__IO uint16_t EP1R; //USB Endpoint 1 register, Address offset: 0x04__IO uint16_t RESERVED1; //Reserved__IO uint16_t EP2R; //USB Endpoint 2 register, Address offset: 0x08__IO uint16_t RESERVED2; //Reserved__IO uint16_t EP3R; //USB Endpoint 3 register, Address offset: 0x0C__IO uint16_t RESERVED3; //Reserved__IO uint16_t EP4R; //USB Endpoint 4 register, Address offset: 0x10__IO uint16_t RESERVED4; //Reserved__IO uint16_t EP5R; //USB Endpoint 5 register, Address offset: 0x14__IO uint16_t RESERVED5; //Reserved__IO uint16_t EP6R; //USB Endpoint 6 register, Address offset: 0x18__IO uint16_t RESERVED6; //Reserved__IO uint16_t EP7R; //USB Endpoint 7 register, Address offset: 0x1C__IO uint16_t RESERVED7; //Reserved__IO uint16_t CNTR; //Control register, Address offset: 0x40__IO uint16_t RESERVED8; //Reserved__IO uint16_t ISTR; //Interrupt status register, Address offset: 0x44__IO uint16_t RESERVED9; //Reserved__IO uint16_t FNR; //Frame number register, Address offset: 0x48__IO uint16_t RESERVEDA; //Reserved__IO uint16_t DADDR; //Device address register, Address offset: 0x4C__IO uint16_t RESERVEDB; //Reserved__IO uint16_t BTABLE; //Buffer Table address register, Address offset: 0x50__IO uint16_t RESERVEDC; //Reserved} USB_TypeDef;This is the structure for the USB peripheral, but it shows the intention. Use typedefs to name a structure and then incorporate it in a new structure to get an array of them.Code://Structure for Buffer Descriptor Tabletypedef struct{PMA_ENTRY TX_ADDRESS;PMA_ENTRY TX_COUNT;PMA_ENTRY RX_ADDRESS;PMA_ENTRY RX_COUNT;} BTABLE_ENTRY;//Structure for endpoint descriptorstypedef struct{BTABLE_ENTRY EPD;} BTABLE_TypeDef;Again USB stuff, but it shows the intention.Use a #define to map the array of structures to the actual memory.Code:#define USB_BASE 0x40005C00#define USB ((USB_TypeDef *)USB_BASE)

author: Siwastaja
author_rank: Super Contributor
timestamp: November 28, 2024, 03:25:41 pm
content: Yeah, since the registers are not adjacent in memory, you need to add one layer of indirection, i.e. use array of pointers, and then remember to dereference that pointer when accessing.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 28, 2024, 05:31:02 pm
content: Quote from: pcprogrammer on November 28, 2024, 03:19:18 pm

author: Siwastaja
author_rank: Super Contributor
timestamp: November 28, 2024, 05:44:14 pm
content: Yes,volatile uint8_t * uart_txso that the target type itself is volatile; the pointer will not be and doesn't need to be.#define is just fancy text replacement but I would definitely not do what you suggest (hide the pointer-ness) because that's a great way to shoot yourself in the foot as you don't see you are dealing with a pointer and dereferencing it. Why not expose what is really happening everywhere.

author: hans
author_rank: Super Contributor
timestamp: November 28, 2024, 06:11:49 pm
content: Also don't forget to initialize the array with e.g.:Code:volatile uint8_t *uarts_tx = {U1TXBUF, U2TXBUF, U3TXBUF, U4TXBUF, U5TXBUF};somewhere globally or as a static variable inside a function. Otherwise it will keep populating the array of pointer values everytime it enters the function..The array of pointers is a good readable and flexible method.The struct alternative as presented by pcprogrammer does have 1 advantage though: the compiler knows exactly what the memory layout of the I/O looks like and so it possibly can do its own pointer arithmetic. For the array of pointers it just sees a bunch of magic constants that it has to load and then use.I don't know exactly which part you're using. And this is only a very slight 'optimization' with an extra qualification. But if you use a part such as the PIC32MX250, then its uarts are nicely colocated in the I/O space at virtual addresses 6020h, 6220h, 6420h, 6620h, 6820h, etc.Just from observation we could say the memory address we need is: 6020h + idx*200h (with idx = 0 .. 4). Writing it as-is is a bit of a hack. But we can craft a structure that capture this PIC32s I/O space and then use that:Code:typedef struct {volatile uint32_t MODE; // 6000huint32_t _1; uint32_t _2; uint32_t _3;volatile uint32_t STA; // 6010huint32_t _4; uint32_t _5; uint32_t _6;volatile uint8_t TXREG; // 6020huint32_t _7; uint32_t _8; uint32_t _9;volatile uint8_t RXREG; // 6030huint32_t _10; uint32_t _11; uint32_t _12;volatile uint32_t BRG; // 6040huint32_t _13; uint32_t _14; uint32_t _15;// 6050h - 61FCh (108x4bytes): unuseduint32_t _unused;} UARTPeripheral;//...UARTPeripheral* uarts = (UARTPeripheral*) U1MODE;uarts.TXREG = data;It also gets rid of the nasty * before using a register. On MIPS4K CPU,it is ever so marginallyfaster and smaller (-1 instruction, no more constants) with some dummy values for the register locations. On other architectures, YMMV.Note however, untested..BIG downside: there is no sanity check to see if the indexed UART even exists. And this only works because the 5 UARTs are located sequentially inside the memory. On ARM parts this may not be the case at all, so then the array of pointers allows for more flexibility (e.g. you could also reshuffle the order of UARTs if you need to).

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 28, 2024, 07:43:20 pm
content: Quote from: Siwastaja on November 28, 2024, 05:44:14 pmLike Siwastaja wrote, it is risky to do so. With the structure example, which I use a lot in my code, it is clear that it is pointing to a structure by the selection of the structure elements. (-> for when dealing with a pointer to a structure, . (dot) when dealing with the actual structure)Code:uart->tx = 0x0A;

author: grumpydoc
author_rank: Super Contributor
timestamp: November 28, 2024, 08:09:45 pm
content: Quote from: mikeselectricstuff on November 28, 2024, 02:55:45 pmAre the transmit buffers mapped at contiguous addresses?

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 28, 2024, 08:27:05 pm
content: Quote from: Siwastaja on November 28, 2024, 05:44:14 pmActually I disagree - If your hardware regs are a mix of normal for non-array regs, and pointers for arrays, then there is more scope for errors than if they are all used consistently.The array elements will only ever be read from and written to in the same way as normal register definitions, so I can't see why there would be any risk hiding the actual mechanism, and would make the code more obvious. More so if the case matches the normal fixed ones.e.g.Code:for(i=0;i!=4;i++) UTXBUF=buffer;Seems to me to be more obvious than if it was using *utxbuf[]In what situations might this cause any issues ?Especially when I am the only one who will ever need to understand this, possibly years ahead when the next project re-uses the code.BTW the definition in the header file isCode:#define U1TXREG U1TXREGextern volatile uint32_t U1TXREG __attribute__((section("sfrs"), address(0xBF806020)));Not sure what that #define is doing...OK second complication - I'd also need to look at status bits in a register array.The normal way of doing this would be something likeif(U1STAbits.UTXBF)...which I'd like to look likeif(USTAbits.UTXBF)...I don't think there is ever a need to set / clear status bits, and clearing interrupt bits gets messy as the bits are in different positions in different registers - probably need to do something like :Code:IFSCLR=_IFS_UTXIF_MASK;(This is done differently to using the <register>.bits syntax as the XC32 compiler is too dumb to substitute the hardware bit set/clear instructions, and I won't even start on that pain that caused me one occasion debuggin a huge installation in Hong Kong when I had to go out to buy an oscilloscope to use for a couple of hours)This is defined in the header files asCode:#define U1STAT U1STATtypedef union {struct {uint32_t URXDA:1;uint32_t OERR:1;uint32_t FERR:1;uint32_t PERR:1;uint32_t RIDLE:1;uint32_t ADDEN:1;uint32_t URXISEL:2;uint32_t TRMT:1;uint32_t UTXBF:1;uint32_t UTXEN:1;uint32_t UTXBRK:1;uint32_t URXEN:1;uint32_t UTXINV:1;uint32_t UTXISEL:2;uint32_t ADDR:8;uint32_t ADM_EN:1;};struct {uint32_t :6;uint32_t URXISEL0:1;uint32_t URXISEL1:1;uint32_t :6;uint32_t UTXISEL0:1;uint32_t UTXISEL1:1;};struct {uint32_t :14;uint32_t UTXSEL:2;};struct {uint32_t w:32;};} __U1STAbits_t;extern volatile __U1STAbits_t U1STAbits __asm__ ("U1STA") __attribute__((section("sfrs"), address(0xBF806010)));I have no idea what's going on there with that __asm__ ....

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 28, 2024, 08:32:28 pm
content: Quote from: grumpydoc on November 28, 2024, 08:09:45 pmNo, they are interleaved with UART status registers, and even if they were consecutive I wouldn't like to rely on that - the point is to create a code framework that works with an arbitary number of UARTs.Even if the current part maps them at consecutive addresses, other, future larger parts might not, e.g. on a fancy new part with 10 UARTS they decided to put UART6 onwards at a different start address because they ran out of space after UART5

author: grumpydoc
author_rank: Super Contributor
timestamp: November 28, 2024, 09:14:45 pm
content: Quote from: mikeselectricstuff on November 28, 2024, 08:32:28 pmOK, fair enough.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 28, 2024, 09:49:51 pm
content: Usevolatile uart_t *constuart = { &uart0, &uart1, &uart2, &uart3 };Theconstis key, because it tells the compiler that thepointersin the array will not be modified (because it is on the right side of the asterisk). The left side tells what they point to, andthoseare volatile, so accesses to the pointed-touart_tstructures are volatile and mutable.This way, if you enable optimizations, usinguartwhere the value ofnis known at compile time (being a constant or macro or the loop variable in a short loop when loop unrolling is enabled), the compiler will generate the code that uses the structure directly.You can see the difference in the following example:Code:#include <stdint.h>typedef struct {uint8_t rx;uint8_t tx;uint8_t status;uint8_t padding;} uart_t;extern volatile uart_t uart0, uart1, uart2;volatile uart_t *const uart = { &uart0, &uart1, &uart2 };#define uarts (sizeof uart / sizeof uart)void uart_tx_all(uint8_t value) {for (uint_fast8_t i = 0; i < uarts; i++)uart->tx = value;}With sufficient optimizations,uart_tx_all()compiles to threeldr+strbpairs on ARMv7e-m using Thumb, loading the structure address (directly usinguart0,uart1,uart2symbols; not through theuartarray at all), storingvalueto offset 1 within each structure, plus a finalbx lr.If you omit theconst, then for ARMv7e-m and some other architectures you get quite different code. It will first load theuartarray address, then load the address of each structure through that pointer, and finally storevalueto offset 1 relative to those pointers. This is because the compiler cannot assume the current pointers in the array are the initialized values. Only first one is a direct load, the three other loads are indirect (relative to the first).This is one of the rare examples where addingconstcan make a measurable difference to the generated code.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 28, 2024, 10:28:14 pm
content: It depends how they are located in memory. Start by looking up the addresses of UART modules and individual registers, then use C to describe what you see.Most likely this is an array of stucts, each struct containing all registers for a single UART module. If you declare it as such, you can access the structs by index (or by pointer if you prefer). No need to create any indirections or use extra memory.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 29, 2024, 07:08:36 am
content: Quote from: NorthGuy on November 28, 2024, 10:28:14 pmIf all the UARTs have the same memory layout and if they are contiguous in memory then this is of course the best thing to do. But that is a lot of ifs. It is not unheard of to see heterogeneous peripherals; ST especially loves to play such games, so you may have three similar UARTS and then a fourth different UART and three different types of DMAs.Discontinuous IO is even more common in microcontrollers. Just having different amount of gap ruins the idea. So indirection is often needed, but it's not a bad thing. The array of const pointers as shown by Nominal is very simple and efficient.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 29, 2024, 02:20:27 pm
content: Quote from: Siwastaja on November 29, 2024, 07:08:36 amSure. That's why you need to look at addresses first.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 29, 2024, 10:47:59 pm
content: Quote from: Nominal Animal on November 28, 2024, 09:49:51 pmYep.Now, there may be cases for using such an array, but it may not be so obvious. It has a benefit if you, say, must iterate through a number of identical peripherals.But if you only access them individually, it's not obvious to see a benefit of:'uart' compared to 'uart1'.Can be potentially interesting for easily "remapping" peripherals in your firmware, possibly dynamically (if that makes any sense for your application). If statically, it looks harder to justify.Just a thought - there may be specific use cases that justify it, but just mentioning that overly "abstracting" when the abstraction gives no abstraction at all may not be all that useful.OTOH, writing functions that take a pointer to a peripheral struct rather that directly "hard code" the peripheral pointer in it can be a good idea for making them more versatile.So that could look like:Code:void DataSend(volatile uart_t * const uart, size_t n, const uint8_t data){...}

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 29, 2024, 11:38:33 pm
content: Quote from: SiliconWizard on November 29, 2024, 10:47:59 pmThe benefit is that the index can be a variable, so you can have code that works with different numbers of uarts with no code changes, and the same code can handle every UART in a loop

author: tridac
author_rank: Regular Contributor
timestamp: November 30, 2024, 12:03:35 am
content: You will almost certainly need to access other uart registers, so the way I usual orgainise peripherals is to typedef a stucture that maps over the device register set. Here's one for an old project, a Mitsubishi 16 bit cpu:typedef struct {U8 u8SpecModeReg4; /* Special mode register 4 */U8 u8SpecModeReg3; /* .. .. .. 3 */U8 u8SpecModeReg2; /* .. .. .. 2 */U8 u8SpecModeReg; /* Special mode register */U8 u8TxRxMode; /* Transmit/receive mode register */U8 u8BitRate; /* Bit rate generator *//* Even address, no padding to 16 bit boundary */U16 u16TxDataBuf; /* Transmit buffer register */U8 u8TxRxCtl0; /* Transmit/receive control register 0 */U8 u8TxRxCtl1; /* .. .. .. .. 1 *//* Even address, no padding to 16 bit boundary */U16 u16RxDataBuf; /* Receive data buffer register */U8 u8TxIntCtl; /* Transmit interrupt control register */U8 u8RxIntCtl; /* Receive interrupt control register */U8 u8BusColIntCtl; /* Bus collision interrupt control register */} UART_REGISTER;A lot of peripheral device registers are contiguous in memory, but some need padding in the structure, so the element maps correctly. Can be abstracted further, if the register set is alll over the address spece, via pointers to a register.Then, assuming you know the lowest hardware register address of the device, you can then declare a pointer to a structure at that address, to access any register transaparently via that pointer. It's an old idea, but provides a bit of abstraction for the device itself and becomes a common access point for the device. For a set of uarts, just declare an array of structures.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 30, 2024, 12:14:39 am
content: Quote from: SiliconWizard on November 29, 2024, 10:47:59 pmAlso, you'll generally get better machine code if you pass a pointer to the peripheral struct rather than an index into an array of those peripherals.I do not recall if I've ever used/needed an array of structures for peripherals, as I tend to pass and store the pointer to the particular peripheral structure instead. (If configured at build time, then I tend to use a preprocessor macro named according to the use case or purpose (say,LOGGING_UARTorCOMMAND_UART) that evaluates to the actual peripheral structure. Pointers I only use if it varies at run time, or more than one peripheral structure is used with the same functions.) I don't recall if I've ever needed a loop over a set of similar peripherals, except maybe during bootup initialization (where the efficiency difference is absolutely neglible anyway).I just happen to know how I'd do it if I needed to, because I like to test and verify experimentally a lot of stuff that happens to pop into my mind, regardless of whether I have an use case in mind or not. You know, for research purposes.

author: tridac
author_rank: Regular Contributor
timestamp: November 30, 2024, 12:27:26 am
content: "Also, you'll generally get better machine code if you pass a pointer to the peripheral struct rather than an index into an array of those peripherals."Yes, typically passed in a register. It's often more efficient to use a structure and pointer for any function that has more than two or three parameters, and can be used to hold (private) variables common to several layers of functions.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 30, 2024, 04:12:53 am
content: Quote from: Nominal Animal on November 28, 2024, 09:49:51 pmClearly, to write a value to three fixed memory locations it's enough to have one ldr (to load a base address) and three strb with various offsets (assuming it is all close together). And you would get that if you could use a pointer to the array of structs instead of the array of pointers to struct.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 30, 2024, 05:02:31 am
content: Quote from: NorthGuy on November 30, 2024, 04:12:53 amNo, because those structures were externally defined in the example, so their addresses are not known while compiling said code.I took mikeselectricstuff's "Even if the current part maps them at consecutive addresses, other, future larger parts might not, e.g. on a fancy new part with 10 UARTS they decided to put UART6 onwards at a different start address because they ran out of space after UART5" as a requirement for the solution I suggested.However, if you do have consecutive structures in memory, then specifying those in the linker script as a specific section makes most sense:volatile struct uart_registers uart __attribute__((section ("uart.registers")));but, used via a function-like macro,#define UART(i) (uart)so that you can always switch to an array of const pointers:volatile struct uart_registers *const uart_ptr = { &uart0, &uart1, &uart2, &uart3 };#define UART(i) (*uart_ptr)This way, access viaUART(i).tx,UART(i).statuset cetera can always be optimized to best possible machine code, given the circumstances.As an example,Code:extern volatile uart_t uart_struct; // Defined elsewhere, for example in linker scriptvolatile uart_t *const uart_ref = { uart_struct+0, uart_struct+1, uart_struct+2 };#define UARTS (sizeof uart_ref / sizeof uart_ref)#define UART(i) (*uart_ref)void uart_txall(uint8_t value) {for (uint_fast8_t i = 0; i < UARTS; i++)UART(i).tx = value;}with GCC for ARMv7e-m using AAPCS32 (arm-none-eabi) will generate just one load and three stores, plus return from subroutine.That is, even though the structures appear to be accessed via an array of pointers, because the array isconst, the compiler can optimize the access to the initializer values, and omit the array dereferencing altogether. Then, if the accessed structures are consecutive in memory, only one base address is loaded, with stores relative to that. This would work this way even if the members were not in the same order, or if there were multiple sub-arrays in the pointer array! Win-win, all around.The function-like macro notation can be a bit confusing. I've watched mike's videos, and I know he's capable and this wouldn't be a problem, but I didn't want to immediately jump to this. Iwastempted, though!The idea behind theUART(number)macro is to evaluate either to a structure in an array of volatile uart structures, or to a dereferenced pointer in an array of const pointers to volatile structures. In both cases the notation is the same,UART(index).member. That is,UART(number)will always evaluate to the structure, and is perfectly valid even on the left side of the assignment. Of course, it is obvious that it is a function or function-like macro, so if anyone else reads the code, there should be a comment explaining this.If your source files span multiple files, then thevolatile uart_t *const uart_ref = { uart_struct+0, uart_struct+1, uart_struct+2 };initialization should be in a header file, in its entirety. It would NOT suffice for the declaration (omitting the part after the =) only to be in a header file, with the initialization in a C source file, as one would expect, exactly because the compiler has to see the initialization in each compilation unit to be able to fully optimize theUART(number)expressions through the const pointer array.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 30, 2024, 06:00:55 am
content: Let's say you have an architecture with two different type of UARTs, so you describe their register layout withstruct uart_regsandstruct usart_regs. Let's further say that their layouts are utterly incompatible, but they both haverxandtxfields, and you want to be able to use these in an array fashion.In a header file, you declare the structure types, and then the UARTs/USARTs available. One common approach is to define their addresses in a linker script, and the number in a preprocessor macro:Code:#include <stdint.h>struct uart_regs {uint8_t rx;uint8_t tx;uint8_t status;uint8_t padding;};struct usart_regs {uint8_t status;uint8_t padding;uint8_t rx;uint8_t tx;};#define UARTS 3#define USARTS 2extern volatile struct uart_regs uart_array;extern volatile struct usart_regs usart_array;In the same header file, declare the RX and TX arrays like this:Code:#define TXS (UARTS+USARTS)static volatile uint8_t *const tx_ptr = {&(uart_array.tx),&(uart_array.tx),&(uart_array.tx),&(usart_array.tx),&(usart_array.tx),};#define TX(i) (*tx_ptr)Again, theTX(number)macro expands to thetxstructure member of thenumber'th uart or usart structure, and can be used on either side of an assignment (it is a valid "lvalue").Note that because of theconst, the compiler optimizes accesses through the pointer array and macro to direct accesses to the structures themselves, so that when you compile object files, those object files will not have a symbol namedtx_ptr, nor any references to such.So, while it may look like you get copies of that array in your object files, theconstwill save you from that, assuming you enable optimizations (-Os,-O1,-O2;-Ogdoes not unroll the loop and will use the array, so with that you will get a copy of the array in each object file compiled with-Ogthat includes this header file).Let's test the above:Code:void tx_all(uint8_t value) {for (int i = 0; i < TXS; i++)TX(i) = value;}Using GCC 5.4.1 through 14.2.0,-Osor-O2 -march=armv7e-m -mthumb(aapcs32, 32-bit arm-none-eabi),tx_all()compiles toCode:tx_all:ldr r3, .L3ldr r2, .L3+4strb r0, [r3, #1]strb r0, [r3, #9]strb r0, [r3, #17]strb r0, [r2, #3]strb r0, [r2, #7]bx lr.L3:.word uart_array.word usart_arraywhich is basically as optimal as you can make this even if you wrote it by hand (except perhaps for theorderof the above instructions), given that the address ofuart_arrayandusart_arrayare not known yet at the time of this compilation.Even if you reorder the elements in thetx_ptrarray, the generated code stays optimal: only the store machine instruction order will change.The purpose of theTX(number)macro is to simply hide the particular implementation detail, by doing the dereferencing for you. This allows you to modify the implementation, without having to modify any of theTX()-using code. We can discuss whether it is appropriate abstraction here, though. To me, it is just a tiny cost ("odd-looking" code, like assigning values to a function?) to save me from complicated search-and-replace process, if I decide to change the implementation behind it for any reason.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 30, 2024, 06:46:47 am
content: Quote from: Nominal Animal on November 30, 2024, 05:02:31 amI would write it directly. Instead of:Code:extern volatile uart_t uart_struct; // Defined elsewhere, for example in linker scriptvolatile uart_t *const uart_ref = { uart_struct+0, uart_struct+1, uart_struct+2 };#define UARTS (sizeof uart_ref / sizeof uart_ref)#define UART(i) (*uart_ref)I would do:Code:extern volatile uart_t uart_struct; // Defined elsewhere, for example in linker script#define UARTS (sizeof uart_struct / sizeof uart_t)#define UART(i) (uart_struct)I don't see any need for the array of pointers here. If they're not consecutive, the uart_struct definition would change and macros would change accordingly.In practice, I would probably have my own structs for each UART where the pointer to the hardware struct would be a member, plus my own information, states, buffers, counters, and whatnot. If I wanted an array, I would make an array of these structs, or a linked list, or a queue, or whatever I would see fit.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 30, 2024, 07:20:33 pm
content: Quote from: NorthGuy on November 30, 2024, 06:46:47 amYep! Like I said, I am assuming the structures are not linear, or necessarily even all the same layout.The macroUART(i)means that either way, the C code usingUART(i)stays the same, and if compiler optimizations are enabled, you should get near-optimal machine code as a result.Quote from: NorthGuy on November 30, 2024, 06:46:47 amFor typical code, I as well.If you can populate the array at initialization time, making such pointersvolatiletype*constnameand initializing them at variable/object declaration time, in the unit where the functions manipulating the structure are, you can similarly avoid the indirect load and have the functions generate machine code accessing the pointed-to structure directly (using its symbol, not through this structure).In other compilation units (where that initialization is not seen), the code will still load the pointer, once, but the compiler can cache it for any subsequent accesses.If the UART may change at runtime, then the pointer member cannot beconst-qualified anymore. (Type-punning the const-ness out, and replacing the pointer value, will lead to really odd bugs, so don't do that! It is because the unit where that const-ness and initialization value was seen at compile time will still use that initialization value, and the rest of the code the value from the structure... Sometimes being too clever really is a loaded foot-gun.)I did think of an example where the array approach might be very useful: when creating an UART interfacing USB stick, with one serial endpoint being a control endpoint where you can use text commands –– terminal-like –– to configure the UARTs and other endpoints.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 30, 2024, 09:17:26 pm
content: Quote from: Nominal Animal on November 30, 2024, 07:20:33 pmUsually, the reason you use pointers is because you want the same code to work with different entities, different UART modules in this case. In such case the pointer cannot be optimized out.Quote from: Nominal Animal on November 30, 2024, 07:20:33 pmVery good example. For each converter, you would have a struct which contains pointers to endpoints, a pointer to UART, all the necessary settings, buffers, callback functions etc. The C functions would typically take that struct as a parameter. This way, the same code would work for all converters.In OOP this would be called class instance.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: December 02, 2024, 04:23:40 pm
content: You could put the UART registers in an array of structures. When you use C++ you have more options for "syntax sugar". Using C++ instead of C does not have a significant impact on code size as long as you keep to the right "sub set" of C++ (No exceptions, RTTI, be very careful (or avoid) templates). Several of my programs even became smaller when I switched to C++. The biggest disadvantage of C++ is that it's a (much) more complicated language. But I like the use of classes, which guides you into writing more readable and easier to maintain code. But these days I also do not do much programming anymore due to concentration problems.With C++ you can write a class for a single uart, and then put the implementation specific initialization register names in the constructor. This is easy as long as all UART's have the same layout. If specific bits get swapped or moved to other registers for some of the UART's it becomes more difficult.Also: There is a typo "Qustion for c experts" in the title of your topic.