A more generic approach to port operations - Page 1
Forum section: electronics / general

author: dannyf
author_rank: Super Contributor
timestamp: March 29, 2014, 11:36:32 pm
content: I talked about a set of macros that help to simplify port operations on a STM32F3 chip:Code:#define PIN_SET(port, pins) port->ODR |= (pins) //set pins on port#define PIN_CLR(port, pins) port->ODR &=~(pins) //clear pins on port#define PIN_FLP(port, pins) port->ODR ^= (pins) //flip pins on port#define PIN_GET(port, pins) ((port->IDR) & (pins)) //get pins//user code#define GPIO_LED GPIOA#define LED0 (1<<1) //led0 on gpioa.1#define LED1 (1<<9) //led1 on gpioa.9PIN_SET(GPIO_LED, LED0); //set led0PIN_FLP(GPIO_LED, LED1); //flip led1...The advantage with this approach is that once your code has been built on this set of routines, you can easily retarget the code if you simply replace the macros with the ones appropriate on the new target.Obviously, this approach is possible because most of the CMx chips (the only exception I know of is the TI / Luminary chips) come with the port registers nicely defined via a struct in the device header file.So the question is: is it possible to do the same with other chips, like PIC24F, or AVR, or the lowly PIC?

author: dannyf
author_rank: Super Contributor
timestamp: March 29, 2014, 11:51:07 pm
content: The obvious answer is "maybe".For the PIC24 (and PIC32), you will notice that the register map for the ports starts with TRISx, then PORTx, LATx, and ODCx. For example, for dsPIC33FJ128GP804, TRISA is at 0x02c0, PORTA at 0x02c2, LATA at 0x02c4, and ODCA at 0x02c6.So we can define our own structure like this:Code://gpio definitionstypedef struct {volatile unsigned short TRIS;//direction register - offset 0x0000volatile unsigned short PORT;//input data registervolatile unsigned short LAT;//output data registervolatile unsigned short ODC;//open drain register} GPIO_TypeDef;//gpio type definitionsThe easy and hard way would be to define a struct pointer that points to TRISx:Code:#define GPIOA ((GPIO_TypeDef *) 0x02c0) //gpioA pointerIn your user code, you would do thisCode:#define PIN_SET(port, pins) port->LAT |= (pins) //set pins on port#define PIN_CLR(port, pins) port->LAT &=~(pins) //clear pins on port#define PIN_FLP(port, pins) port->LAT ^= (pins) //flip pins on port#define PIN_GET(port, pins) ((port->PORT) & (pins)) //get pins#define PIN_OUT(port, pins) port->TRIS &=~(pins) //pins as output#define PIN_FLP(port, pins) port->TRIS |= (pins) //pins as input//user code#define GPIO_LED GPIOA#define LED0 (1<<1) //led0 on gpioa.1#define LED1 (1<<9) //led1 on gpioa.9PIN_OUT(GPIO_LED, LED0 | LED1); //led0 and led1 as outputPIN_SET(GPIO_LED, LED0); //set led0PIN_FLP(GPIO_LED, LED1); //flip led1...So the user code remains unchanged, even though the inner works of the port are vastly different between STM32F3 that the code originally ran on and PIC24 that it runs on now.

author: dannyf
author_rank: Super Contributor
timestamp: March 29, 2014, 11:55:32 pm
content: That is great.However, each time we move the code to a new chip (within the same family), we have to check the datasheet and make sure that we define GPIOx to whatever the appropriate addresses are.That's a major pain in the you-know-where.One way to get around that would be to take advantage of the predefined macros:Code://#define GPIOA ((GPIO_TypeDef *) 0x02c0) //gpioA pointer - old way of doing business#define GPIOA ((GPIO_TypeDef *) &TRISA)...#if defined(TRISH) //for PORTH, if it exists on the target#define GPIOH ((GPIO_TypeDef *) &TRISH)#endifSomething like this would be infinitely portable.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:08:16 am
content: How to pull everything together?You can always copy and paste the macros into your code. But that's too much work.Instead, we will create a header file and put all the gpio-related code into it.Code://mygpio.h//my own gpio definitions for PIC24/32#ifndef _MYGPIO_H#define _MYGPIO_H//gpio definitionstypedef struct {volatile unsigned short TRIS;//direction register - offset 0x0000volatile unsigned short PORT;//input data registervolatile unsigned short LAT;//output data registervolatile unsigned short ODC;//open drain register} GPIO_TypeDef;//gpio type definitions//gpio macros#define GPIOA ((GPIO_TypeDef *) &TRISA)#define GPIOB ((GPIO_TypeDef *) &TRISB)#if defined(TRISC) //for PORTx, if it exists on the target#define GPIOC ((GPIO_TypeDef *) &TRISC)#endif#if defined(TRISD) //for PORTx, if it exists on the target#define GPIOD ((GPIO_TypeDef *) &TRISD)#endif#if defined(TRISE) //for PORTx, if it exists on the target#define GPIOE ((GPIO_TypeDef *) &TRISE)#endif#if defined(TRISF) //for PORTF, if it exists on the target#define GPIOF ((GPIO_TypeDef *) &TRISF)#endif...#if defined(TRISH) //for PORTx, if it exists on the target#define GPIOH ((GPIO_TypeDef *) &TRISH)#endif//port / pin macros#define PIN_SET(port, pins) port->LAT |= (pins) //set pins on port#define PIN_CLR(port, pins) port->LAT &=~(pins) //clear pins on port#define PIN_FLP(port, pins) port->LAT ^= (pins) //flip pins on port#define PIN_GET(port, pins) ((port->PORT) & (pins)) //get pins#define PIN_OUT(port, pins) port->TRIS &=~(pins) //pins as output#define PIN_FLP(port, pins) port->TRIS |= (pins) //pins as input#endif //_MYGPIO_HSave that to a file ("mygpio.h" for example), and your user code would look like this:Code:#include <_yourdevice.h> //device header file#include "mygpio.h" //my gpio macros#define LED_PORT GPIOC#define LED (1<<4) //led on PORTC.4#define SW_PORT ~GPIOA //switch active low#define SW (1<<6) //switch on PORTA.6PIN_OUT(LED_PORT, LED); //led as outputPIN_IN(SW_PORT, LED); //switch as input...if (PIN_GET(SW_PORT, SW)) PIN_SET(LED_PORT, LED); //set port.led if switch is pressed (low)This approach works on C30/XC16 for as many pic24 chips as I have checked.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:13:27 am
content: Limitations?1) As you may have figured out, this approach is endian-dependent. If the target compiler has a different endianess, you just have to reverse the order in the GPIO_TypeDef struct.2) It is dependent on your chips' memory mapping - or the consistency of its memory mapping. It presumes that the order of port registers is always the same - the registers don't have to be consecutively packed (PIC32), as long as the order is consistent: if there are gaps among the registers, the gaps have to be the same.For example, you can port this approach to AVR, but you cannot port it to PIC(10/12/16): on those PICs, the port registers are all over the place. Most modern chips have consistent register mapping so this approach is portable to them all.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:23:03 am
content: Here is an example of porting this to AVR.AVRs have a slightly different register mapping: PINx followed by DDRx and then PORTx. They are 8-bit registers too.So the "mygpio.h" file is slightly different:Code://mygpio.h//my own gpio definitions for PIC24/32#ifndef _MYGPIO_H#define _MYGPIO_H//gpio definitionstypedef struct {volatile unsigned char PIN;//input data register - offset 0x00volatile unsigned char DDR;//direction registervolatile unsigned char PORT;//output data register} GPIO_TypeDef;//gpio type definitions//gpio macros#define GPIOA ((GPIO_TypeDef *) &PINA)#define GPIOB ((GPIO_TypeDef *) &PINB)#if defined(PINC) //for PORTx, if it exists on the target#define GPIOC ((GPIO_TypeDef *) &PINC)#endif#if defined(PIND) //for PORTx, if it exists on the target#define GPIOD ((GPIO_TypeDef *) &PIND)#endif#if defined(PINE) //for PORTx, if it exists on the target#define GPIOE ((GPIO_TypeDef *) &PINE)#endif#if defined(PINF) //for PORTF, if it exists on the target#define GPIOF ((GPIO_TypeDef *) &PINF)#endif...#if defined(PINH) //for PORTx, if it exists on the target#define GPIOH ((GPIO_TypeDef *) &PINH)#endif//port / pin macros#define PIN_SET(port, pins) port->PORT |= (pins) //set pins on port#define PIN_CLR(port, pins) port->PORT &=~(pins) //clear pins on port#define PIN_FLP(port, pins) port->PORT ^= (pins) //flip pins on port#define PIN_GET(port, pins) ((port->PIN) & (pins)) //get pins#define PIN_OUT(port, pins) port->DDR |= (pins) //pins as output#define PIN_FLP(port, pins) port->DDR &=~(pins) //pins as input#endif //_MYGPIO_HYour user code would remain identical, as expected:Code:#include <_yourdevice.h> //device header file#include "mygpio.h" //my gpio macros#define LED_PORT GPIOC#define LED (1<<4) //led on PORTC.4#define SW_PORT ~GPIOA //switch active low#define SW (1<<6) //switch on PORTA.6PIN_OUT(LED_PORT, LED); //led as outputPIN_IN(SW_PORT, LED); //switch as input...if (PIN_GET(SW_PORT, SW)) PIN_SET(LED_PORT, LED); //set port.led if switch is pressed (low)So in this case, our code went from a 32-bit chip to a 8-bit chip, with no change, aside from dropping in the "mygpio.h" for the new target and recompiling on the target: the code is fully insulated from the inner-working of the port registers.Something that tedious and that lowly is best handled by the compiler,edit: corrected the PIN_IN() and PIN_OUT() macros - they were flipped.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:26:51 am
content: Disclaimer:1) use this approach at your own risk.2) I did not check all pic24/32/avr datasheets to make sure that their register mappings are consistent. To the extent that they are not, this approach will yield incorrect results.3) be aware of your compiler's endianess, as mentioned earlier. Make necessary adjustments if needed;4) be aware of your target's register mappings, if you are to port this approach to a new chip. Make necessary adjustments if needed.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:29:19 am
content: Extensions:1) I prefer to use masks vs. bit fields. To the extent that you want to use bit fields, you can implement a set of structs / unions within GPIO_TypeDef. This is particularly attractive under gcc.2) You can take the same approach to other peripherals. However, given the diversity there, it is not nearly as neat and useful....

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:40:31 am
content: For many of the modern chips, there are many input and output modes. So setting a pin to those modes is quite more involved than to set / clear bits in the direction registers.For those cases, you are likely to write up PIN_OUT() and PIN_IN() functions. Thus, you may consider a mygpio.c file as well.

author: westfw
author_rank: Super Contributor
timestamp: March 30, 2014, 02:27:30 am
content: QuoteI'm not quite sure what you mean.Many microcontrollers allow you to group io registers into a struct. The registers need not be contiguous to do this. You may be dependent on the quality of the C compiler to produce reasonable code from bit-logical operation on structure members :-(It's CERTAINLY possible to create PIN_SET/etc macros for any processor, perhaps with some limitations on functionality. Having structure-like definitions of the IO port is largely irrelevant. It's nice for RUN-TIME non-constant values for "port", but not needed for compile-time constant values, if only because C macros are smarter than you think.You mentioned TI not having the structure like defs (both Keil and IAR DO have such structures, apparently derived from CMSIS "SVD" data (which is xml provided by the vendor, I think.) But suppose we're stuck with the #defines from "Tivaware" that look like:Code:#define GPIO_PORTA_DATA_R (*((volatile unsigned long *)0x400043FC))#define GPIO_PORTB_DATA_R (*((volatile unsigned long *)0x400053FC))#define GPIO_PORTC_DATA_R (*((volatile unsigned long *)0x400063FC))then you can just make your macros like:Code:#define PIN_SET(port, pins) GPIO_ ## port ## _DATA_R |= pinsand invoke it like:Code:PIN_SET(PORTC, 0x11);You may run into one of those "implementation dependent" issues with your C compiler if you try to use another macro for the port name (which happens first? Macro expansion or symbol concatenation, hmm?) But I've got recent working code that contains stuff like:Code:#define LED_PORT DATA_BITS_R(E):LED_PORT |= LED_BITMASK;(The first line is TI's (well, luminary's) solution to atomic bit operations; it uses bits from the address to control which bits are actually written.)As another example, you should note that the Arduino function digitalWrite(pin, val), where "pin" is a completely abstracted number having nothing to do with actual ports, bitmasks, or pin numbers, has be implemented for several different architectures in several different ways, that yield optimal (eg 1-instruction) object code for the case where the parameters are compile-time constants. (given "reasonable" C compilers and optimization levels, of course.)

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 03:04:59 am
content: QuoteTwo suggestions:1) learn C;2) read up on the datasheet.There is zero chance your code will ever work.

author: TerminalJack505
author_rank: Super Contributor
timestamp: March 30, 2014, 03:57:12 am
content: Quote from: dannyf on March 30, 2014, 03:04:59 amwestfw has been on this forum as well as others for some time and I can vouch for his knowledge of C and C++.I'm sure this is what he meant to say:Code:#define PIN_SET(port, pins) GPIO_ ## port ## _DATA_R |= pins /* No semi-colon here */...int foo(void){PIN_SET(PORTC, 0x11);...}Which is completely valid C. (Seethis linkas to why you don't want your macros to end with a semi-colon.)As to whether or not that will work for some particular MCU, I can't say.

author: westfw
author_rank: Super Contributor
timestamp: March 30, 2014, 05:08:14 am
content: What TerminalJack said. Sigh. Long day today!Here's some actual code (for TI), rather than badly edited cut&paste:Code:#define AMSEL_R(port) GPIO_PORT ## port ## _AMSEL_R#define DIR_R(port) GPIO_PORT ## port ## _DIR_R#define DEN_R(port) GPIO_PORT ## port ## _DEN_R#define PCTL_R(port) GPIO_PORT ## port ## _PCTL_R#define AFSEL_R(port) GPIO_PORT ## port ## _AFSEL_R#define DATA_R(port) GPIO_PORT ## port ## _DATA_R#define DATA_BITS_R(port) GPIO_PORT ## port ## _DATA_BITS_R#define PUR_R(port) GPIO_PORT ## port ## _PUR_R#define GPIO_CLOCK_M(port) SYSCTL_RCGC2_GPIO ##port#define PCTL_M(port, bit) GPIO_PCTL_P##port##bit##_M:AMSEL_R(E) &= ~(LED_BITMASK | SW_BITMASK); // disable analogAFSEL_R(E) &= ~(LED_BITMASK | SW_BITMASK); // disable other alt funcs.PCTL_R(E) &= ~(PCTL_M(E,0) |PCTL_M(E,1)); // Configure as GPIODIR_R(E) &= ~SW_BITMASK; // SW1 is an inputDIR_R(E) |= LED_BITMASK; // LED is an outputDEN_R(E) |= LED_BITMASK | SW_BITMASK; // Enable digital pins

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 05:21:04 am
content: Quote from: dannyf on March 30, 2014, 03:04:59 amMaybe dial down the attitude?

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:16:40 pm
content: We talked earlier about how this approach could be used to mcus whose port register mapping is discontinuous, but consistently discontinuous. We mentioned PIC32 being such a case. I was asked how this approach could be implemented on PIC32.If you look at the datasheet for say PIC32MX1xx/2xx, here is the mapping of port registers:offset 0x6010, TRISAoffset 0x6020, PORTAoffset 0x6030, LATAoffset 0x6040, ODCA...offset 0x6110, TRISBoffset 0x6120, PORTBoffset 0x6130, LATBoffset 0x6140, ODCB...So the gap between each (named) register is 12 bytes, or ***3*** uint32_t types.The number '3' should immediately ring a bell for those of us who know the chip well. Yes, they are the xCLR, xSET and xINV registers.So we have an option in defining the GPIO_TypeDef struct: we can name those registers or just pad up the struct to fill the void between the named registers.Here is what GPIO_TypeDef for a PIC32 may look like:Code://gpio definitionstypedef struct {volatile uint32_t TRIS; //output direction register//volatile uint32_t reserved1; //paddingvolatile uint32_t TRISCLR;volatile uint32_t TRISSET;volatile uint32_t TRISINV;volatile uint32_t PORT; //input data registervolatile uint32_t reserved2; //paddingvolatile uint32_t LAT; //output data register//volatile uint32_t reserved3; //paddingvolatile uint32_t LATCLR;volatile uint32_t LATSET;volatile uint32_t LATINV;volatile uint32_t ODC; //opendrain register} GPIO_TypeDef;#define GPIOA ((GPIO_TypeDef *) &TRISA)#define GPIOB ((GPIO_TypeDef *) &TRISB)...As you can see, I implemented TRIS, PORT, LAT and ODC for a port, as well as the corresponding xCLR, xSET and xINV for TRIS and LAT registers - if you wish, you could implement those for PORT and ODC as well.The PIN_SET(), PIN_CLR() and other macros would remain the same.However, the introduction of xCLR, xSET and xINV registers introduce a new way of doing things. The PIN_SET() and etc. macros are read-modify-write operations. With xCLR, xSET and xINV registers, you can make them into simple write operations, by introducing a set of fast PIN_xxx() macros:Code://fast port operations#define FPIN_OUT(port, pins) port->TRISCLR = (pins)#define FPIN_IN(port, pins) port->TRISSET = (pins)#define FPIN_SET(port, pins) port->LATSET = (pins)#define FPIN_CLR(port, pins) port->LATCLR = (pins)#define FPIN_FLP(port, pins) port->LATINV = (pins)#define FPIN_GET(port, pins) IO_GET(port, pins) //no fast operations for PORT as we did not implement xCLR/xSET/xINV for PORTBy swapping out PIN_xxx() with FPIN_xxx() macros, you would have significantly increased the speed of port operations.Disclaimer:1) the xCLR/xSET/xINV registers' offsets are not documented in any PIC32 documents that I have seen so you are running the risk that Microchip may not have implemented them consistently from chip to chip;2) I don't have a real PIC32 chip to test but the above simulates as expected in C32/XC32.A side note: the concept of FPIN_xxx() can be implemented on other chips, like STM32F via BSRR and BRR registers. Its performance gain will depend on both the compiler and hardware - BME for example offers better performance than FPIN_xxx() macros.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:20:30 pm
content: RMW: PIN_xxx() macros are done via read-modify-write operations. I was asked if that could be an issue.Two aspects:1) RMW operations: those macros are certainly RMW operations and offer lower speed than FPIN_xxx() macros, everything else being equal;2) RMW issues: however, none of those macros have the RMW issues that you often see on older PICs or 8051, or other mcus where output and input data registers are combined (some LPC ARM chips for example). That's because write operations (PIN_SET(), PIN_CLR() and PIN_FLP()) are always done via output data registers and read operations (PIN_GET()) via input data registers.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 12:31:19 pm
content: QuoteI am sure he does a good job talking about C. The unfortunate truth is that there are many people who can talk and talk but cannot walk the walk.There are quite a few mistakes in that one post of his that are so basic and fundamental to even an entry-level C programmer that raise doubts in my mind about to what extent he can walk the walk.QuoteThat particularly mistake unfortunately is so basic that if a student in C101 made it, you should fail him; if a gainfully employed C programmer made it, he should be canned on the spot.I also don't buy this "I was too tired" argument. Not adding a semi-colon to your macros is like riding a bike: it is one of those basic skills that once you have learned it, you cannot unlearn it.He made the same mistake more than once, in different spots. If copy-and-paste did it, he should ask copy-and-paste by a lottery ticket for him,Worse yet, his code:Code:#define PIN_SET(port, pins) GPIO_ ## port ## _DATA_R |= pins;will never work on that particular chip, even without the semi-colon at the end.Writing something like that shows his profound lack of understanding of that particular chip and an important mechanism for the arm family of chips, and his willingness to shoot from the you-know-where,He can find out why by running the code on a real chip or reading up on the datasheet.

author: TerminalJack505
author_rank: Super Contributor
timestamp: March 30, 2014, 01:14:04 pm
content: Well, I can tell you one thing, I'd much rather work with a westfw than a dannyf!Knowledge only counts for so much in a workplace. If you exhibit the same attitude at your workplace that you do here in this forum then I can guarantee you that your fellow employeescan't stand you.I don't know if you're suffering from an inferiority complex, a superiority complex, or perhaps Asperger syndrome but the fact is that practically none of you posts on this forum are helpful. All you do is post crap that boils down to "you're an idiot and I'm better you." All your posts seem to be self-serving and never actually helpful.If you worked for me, you'd be the one "canned on the spot." People like you just poison the work environment. You might know better than others but that doesn't mean anything since you always have to shove that fact in everyone else's face.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 01:14:32 pm
content: Quote from: dannyf on March 30, 2014, 12:31:19 pmThis speaks to the view that preprocessor macros are quite a bit different than functions, and one should surround macro trickery with yellow caution tape. I'm not saying your stuff counts as trickery, but if it looks like a macro library with function names in ALL CAPS, some people will assume that. If you can design your API to appear as normal C functions, that might be preferable.Case in point: theChibios/RT PAL driver. Chibios includes a standard API for I/O pins that runs on half a dozen different chip families (no PIC, as far as I can tell). The API appears to the programmer as if they're C functions. Much of the PAL driver is actually implemented as macros that "disappear" at compile time, but as an application programmer, you don't have to concern yourself with the difference, which is nice.Implementors need to understand macro expansion in detail. Users should be able to get their work done with a simpler view.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 02:35:56 pm
content: QuoteThanks for that great lecture on life.Unfortunately, the fact remains that that buggy piece of "thing" that he wrote will never work on the target he wrote it for.So unless you can show it otherwise, the discussion with you is over.

author: TerminalJack505
author_rank: Super Contributor
timestamp: March 30, 2014, 03:09:37 pm
content: Quote from: dannyf on March 30, 2014, 02:35:56 pmMy beef is that your response "There is zero chance your code will ever work." doesn't bring anything to the table. If it's wrong then state why that is so. Use it as a teaching opportunity instead of belittling the guy.The nice thing about this particular business is that you can fairly easily prove most claims. You never seem to be willing to do that for some reason. Your response is pretty much always just "you're wrong, educate yourself." There no value-add to that. A response like that is just noise. I guess it might boost your ego but it doesn't bring anything to the conversation.

author: mazurov
author_rank: Frequent Contributor
timestamp: March 30, 2014, 03:37:31 pm
content: For C++, there is a better way to abstract pin operations ->https://github.com/KonstantinChizhov/Mcucpp/tree/master/mcucppusing templates. It works very well, produces tight assembly code and allows to do wonderful things, like driving a character LCD either from individual pins or 595 latch with the same piece of code. I used this approach on several occasions; the biggest issue is that C++ is not available for every MCU.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 03:58:23 pm
content: Quote from: mazurov on March 30, 2014, 03:37:31 pmIs there an example of this in use? I'm not really getting it from looking at the implementation.

author: westfw
author_rank: Super Contributor
timestamp: March 30, 2014, 04:12:36 pm
content: QuoteAh, but I wasn't writing code at the time. I was copying code from a working program into a forum message, trying to make it more generic and understandable, while pointing out how fundamentally different macros based on argument concatenation are from macros based on structures.I'm sorry that the resulting errors in the code-like portions of my message distracted you from seeing my point, or answering my implied questions about what you are actually trying to accomplish. Hopefully some people other than Jack "got it."I have gone back and fixed the message (maybe. It's still a forum message, and not a finished program that has actually been run through a compiler, much less a debug cycle.)What world do you live in where employees are judged and/or "canned on the spot" based on errors in rough first drafts and casual conversations? I'm glad I live elsewhere! (You know, I've heard that it's become an interview technique to see if an applicant can write perfect code on a whiteboard. That's SO 70s, in it's way: "be sure to have your code perfect before you submit it to the keypunch operators, because any error will cost big bucks when it has to be re-done." I'm a big believer in letting the computer tools (the compiler) find typos and simple syntax errors...)

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 04:22:48 pm
content: As long as we're on the topic, one thing that's tripped me up on multiple occasions is not properly initializing GPIO pins. Sometimes it's a matter of simply forgetting to do the initialization at all (dumb). Other times, it's not seeing a mistake in the init code. There are often multiple steps involved in setting up a single pin and if you change your board layout a little, that will ripple back to initialization.Anway, to keep myself sane, I've started using the "X Macro" technique to generate GPIO initialization. Here's a snippet from a board.h file in a project I'm working on:Code:/** This file generates a enum definition for each pin, the name of which is* the concatenation of the port and the name of the pin. E.g., pin 7 on* port 'A' can be read using the ChibiOS API as follows:** ... palReadPad(GPIOA, GPIOA_PIN7) ...** The ALL_PINS macro, defined below, contains the I/O pin architecture-* specific GPIO configuration for the application. The format used here is* different from the usual ChibiOS method, and hopefully easier to maintain.** port: GPIOA .. GPIOI* pin: 0 .. 15* name: any identifier* mode: INPUT | OUTPUT | ANALOG | ALTERNATE* type: PP "push pull" | OD "open drain"* pupdr: NOPULL | PULLUP | PULLDN* odr: 0 | 1* af: 0 .. 15*/// port, pin, name, mode, type, speed, pupdr, odr, af#define ALL_PINS \PIN(GPIOA, 0, CHRG_PG, INPUT, PP, 100, PULLUP, 0, 0) \PIN(GPIOA, 1, CHRG_STAT2, INPUT, PP, 100, PULLUP, 0, 0) \PIN(GPIOA, 2, CHRG_STAT1, INPUT, PP, 100, PULLUP, 0, 0) \PIN(GPIOA, 3, CHRG_PROG2, OUTPUT, PP, 100, NOPULL, 1, 0) \PIN(GPIOA, 4, DISP_CS, OUTPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOA, 5, DISP_SCK, ALTERNATE, PP, 100, NOPULL, 0, 5) \PIN(GPIOA, 6, DISP_MISO, ALTERNATE, PP, 100, NOPULL, 0, 5) \PIN(GPIOA, 7, DISP_MOSI, ALTERNATE, PP, 100, NOPULL, 0, 5) \\PIN(GPIOA, 8, CARD_PWR, OUTPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOA, 9, USB_VBUS, INPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOA, 10, USB_ID, ALTERNATE, PP, 100, NOPULL, 0, 10) \PIN(GPIOA, 11, USB_DM, ALTERNATE, PP, 100, NOPULL, 0, 10) \PIN(GPIOA, 12, USB_DP, ALTERNATE, PP, 100, NOPULL, 0, 10) \PIN(GPIOA, 13, SWDIO, ALTERNATE, PP, 100, NOPULL, 0, 0) \PIN(GPIOA, 14, SWCLK, ALTERNATE, PP, 100, NOPULL, 0, 0) \PIN(GPIOA, 15, FSYNC, OUTPUT, PP, 100, NOPULL, 0, 0) \\PIN(GPIOB, 0, CARD_DETECT, INPUT, PP, 100, PULLUP, 0, 0) \PIN(GPIOB, 1, PIN1, INPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOB, 2, BOOT1, ALTERNATE, PP, 100, NOPULL, 1, 7) \PIN(GPIOB, 3, INT_9250, INPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOB, 4, INT1_3115, INPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOB, 5, INT2_3115, INPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOB, 6, I2C1_SCL, ALTERNATE, OD, 2, NOPULL, 0, 4) \PIN(GPIOB, 7, I2C1_SDA, ALTERNATE, OD, 2, NOPULL, 0, 4) \\PIN(GPIOB, 8, BUTTON_1, INPUT, PP, 100, PULLUP, 0, 0) \PIN(GPIOB, 9, BUTTON_0, INPUT, PP, 100, PULLUP, 0, 0) \PIN(GPIOB, 10, UART_TX, ALTERNATE, PP, 100, NOPULL, 0, 7) \PIN(GPIOB, 11, UART_RX, ALTERNATE, PP, 100, NOPULL, 0, 7) \PIN(GPIOB, 12, DISP_IO0, OUTPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOB, 13, DISP_IO1, OUTPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOB, 14, DISP_IO2, OUTPUT, PP, 100, NOPULL, 0, 0) \PIN(GPIOB, 15, DISP_IO3, OUTPUT, PP, 100, NOPULL, 0, 0) \...enum all_pins_defs {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) port ## _ ## name = pin,ALL_PINS#undef PIN};The basic idea is to define a single macro (ALL_PINS) that carries all the information about every GPIO pin. That macro gets replayed multiple times to generate pin enums (e.g., GPIOA_DISP_MOSI) that are used by application code, as well as the initialization code that runs after reset.The ALL_PINS macro is formatted to look like a nice table because it doubles as concise documentation about which pins are being used and how.PS. Yes, I realize that defaulting slew rate control to 100MHz isn't optimal.PPS. This definitely counts as macro trickery. ;-)

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 04:31:24 pm
content: ... following up ...Here's another use of the ALL_PINS macro that generates initialization code. In this case, the system is built using ChibiOS/RT, which needs an array of structures at boot time to set the pins. This code creates those structures from the definition of ALL_PINS. It's buried in a .cc file, and you don't actually have to touch it when your pin assignments change. Just edit the .h file with the ALL_PINS macro definition.Code:#include "board.h"enum pull_t {NOPULL = 0,PULLUP = 1,PULLDN = 2};enum ospeed_t {MHZ_2 = 0,MHZ_25 = 1,MHZ_50 = 2,MHZ_100 = 3};enum otype_t {PP = 0, // push pullOD = 1 // open drain};enum moder_t {INPUT = 0,OUTPUT = 1,ALTERNATE = 2,ANALOG = 3};/** The following enums are semi-magic and are used to generate shifted bit-masks* used in the GPIO pin configuration.*/// PIN(port,pin,name,mode,ppod,speed,pull,value,altfn)enum all_pins_moder {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) \MODER_ ## port ## _ ## pin = mode << 2*pin,ALL_PINS#undef PIN};enum all_pins_otyper {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) \OTYPER_ ## port ## _ ## pin = ppod << pin,ALL_PINS#undef PIN};enum all_pins_ospeedr {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) \OSPEEDR_ ## port ## _ ## pin = (speed <= 2 ? MHZ_2 : (speed <= 25 ? MHZ_25 : (speed <= 50 ? MHZ_50 : MHZ_100))) << 2*pin,ALL_PINS#undef PIN};enum all_pins_pupdr {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) \PUPDR_ ## port ## _ ## pin = pull << 2*pin,ALL_PINS#undef PIN};enum all_pins_odr {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) \ODR_ ## port ## _ ## pin = (value & 1) << pin,ALL_PINS#undef PIN};enum all_pins_afrl {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) \AFRL_ ## port ## _ ## pin = (pin < 8 ? altfn : 0) << 4*(pin & 7),ALL_PINS#undef PIN};enum all_pins_afrh {#define PIN(port,pin,name,mode,ppod,speed,pull,value,altfn) \AFRH_ ## port ## _ ## pin = (pin >= 8 ? altfn : 0) << 4*(pin & 7),ALL_PINS#undef PIN};#define OR16(prefix) \(uint32_t) (prefix ## _0 | prefix ## _1 | prefix ## _2 | prefix ## _3 | \prefix ## _4 | prefix ## _5 | prefix ## _6 | prefix ## _7 | \prefix ## _8 | prefix ## _9 | prefix ## _10 | prefix ## _11 | \prefix ## _12 | prefix ## _13 | prefix ## _14 | prefix ## _15)#define CONFIG(port) \{ \OR16(MODER_ ## port), \OR16(OTYPER_ ## port), \OR16(OSPEEDR_ ## port), \OR16(PUPDR_ ## port), \OR16(ODR_ ## port), \OR16(AFRL_ ## port), \OR16(AFRH_ ## port) \}const PALConfig pal_default_config = {CONFIG(GPIOA),CONFIG(GPIOB),CONFIG(GPIOC),CONFIG(GPIOD),CONFIG(GPIOE),CONFIG(GPIOF),CONFIG(GPIOG),CONFIG(GPIOH),CONFIG(GPIOI)};This is for a STM32F4, by the way.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 04:59:11 pm
content: QuoteIt is similar to the approaches we discussed earlier, except that it doesn't require the explicit create of a GPIO_TypeDef struct. The basic idea is the same: using a common set of api / protocols to operate on pins to enhance portability of the user code.

author: nctnico
author_rank: Super Contributor
timestamp: March 30, 2014, 05:07:40 pm
content: Quote from: dannyf on March 29, 2014, 11:36:32 pmI usually take this approach one step further and define macro's which have a descriptive name like:SET_FAULT_RELAY(), RED_LED_ON(), RED_LED_OFF() and so on. Most of the GPIO stuff is very much related to the actual hardware (microcontroller + surrounding circuitry) so having a generic GPIO API is not very useful.In my projects I have a file called hardware.h where all these kind of hardware related macros are defined.

author: hans
author_rank: Super Contributor
timestamp: March 30, 2014, 05:12:41 pm
content: I've tried general purpose general purpose I/O code before ( O0 ), and this is what I came up with. I also define a struct with important I/O registers as a struct:Code://PIC24 = 16-bit wide, may vary over platforms.typedef volatile unsigned int* IOPortReg;typedef struct IOPort_s{IOPortReg TRIS;IOPortReg PORT;IOPortReg LAT;IOPortReg ODC;} IOPort_t;But also, for an IOPin:Code:typedef struct IOPin_s{unsigned int port;unsigned int pin;unsigned int invert;} IOPin_t;This is all general code. For each platform, I would go about and implement an array of PORTx registers:Code:typedef enum GpioPortIndex_e{#ifdef PORTAIO_A,#endif#ifdef PORTBIO_B,#endif#ifdef PORTCIO_C,#endifNR_OF_PORTS};#define IOPortPIC24(l) { ((IOPortReg)&TRIS##l), ((IOPortReg)&PORT##l), ((IOPortReg)&LAT##l), ((IOPortReg)&ODC##l) },const IOPort_t IOPorts = {#ifdef PORTAIOPortPIC24(A)#endif#ifdef PORTBIOPortPIC24(B)#endif#ifdef PORTCIOPortPIC24(C)#endif};So I've now got an array with all TRIS, PORT, LAT and ODC indexed.After this, it's really easy to write a few macro's for this:Code:#define GPIO_Read(port, pin) (*(IOPorts[IO_##port].PORT) & (1 << pin))#define SGPIO_ReadGPIO(gpio) (*(IOPorts[gpio.port].PORT) & (1<<gpio.pin))#define PIN(port, pin) (IOPin_t) { IO_##port, pin };I haven't tested this code on speed before, but it felt quick enough. I've now set XC16 to optimize level 1, and this is what I got:Code:volatile int i = 0;IOPin_t swPin2 = PIN(A, 1);const IOPin_t swPin = PIN(A, 1);int main(void){i++;// test 1if (PORTAbits.RA1 != 0){i++;}i--;// test 2if (SGPIO_ReadGPIO(swPin) != 0){i++;}i--;// test 3if (GPIO_Read(A, 1) != 0){i++;}i--;// test 4if (SGPIO_ReadGPIO(swPin2) != 0){i++;}i--;swPin2.pin++;// test 5if (SGPIO_ReadGPIO(swPin2) != 0){i++;}i--;while(1);return 0;}The variable "i" is there to distuingish the code more easily. It's volatile so doesn't get optimized out. I'm testing 4 situations:- Testing GPIO with a fixed value, could also be from a macro- Testing GPIO in a const IOPin_t object- Testing GPIO in a RAM IOPin_t object (also note swPin2.pin is modified afterwards).- Control: testing a GPIO with the supplied xc.h libraryThe assembler output is as follows (XC16 v1.21 GCC -O1)Code:Line Address Opcode Label DisAssy348 02B6 EC2800 main INC i349 02B8 AB22C2 .LSM2 BTST PORTA, #1 // Test 1350 02BA 320001 BRA Z, 0x2BE351 02BC EC2800 .LSM3 INC i352 02BE ED2800 .L2 DEC i353 02C0 AB22C2 .LSM5 BTST PORTA, #1 // Test 2354 02C2 320001 BRA Z, 0x2C6355 02C4 EC2800 .LSM6 INC i356 02C6 ED2800 .LSM7 DEC i357 02C8 AB22C2 .LSM8 BTST PORTA, #1 // Test 3358 02CA 320001 BRA Z, 0x2CE359 02CC EC2800 .LSM9 INC i360 02CE ED2800 .LSM10 DEC i361 02D0 804014 .LSM11 MOV swPin2, W4 // Test 4362 02D2 DD2243 SL W4, #3, W4363 02D4 E88204 INC2 W4, W4364 02D6 282985 MOV #0x8298, W5365 02D8 7A0265 MOV [W5+W4], W4366 02DA 804025 MOV 0x804, W5367 02DC 200016 MOV #0x1, W6368 02DE DD3305 SL W6, W5, W6369 02E0 630314 AND W6, , W6370 02E2 320001 BRA Z, 0x2E6371 02E4 EC2800 .LSM12 INC i372 02E6 ED2800 .LSM13 DEC i373 02E8 E80285 .LSM14 INC W5, W5374 02EA 884025 MOV W5, 0x804 // Test 5375 02EC 200016 .LSM15 MOV #0x1, W6376 02EE DD3285 SL W6, W5, W5377 02F0 628214 AND W5, , W4378 02F2 320001 BRA Z, 0x2F6379 02F4 EC2800 .LSM16 INC i380 02F6 ED2800 .L6 DEC i381 02F8 37FFFF .L7 BRA 0x2F8As you can see, if you define a GPIO as const it's pretty efficient (as it uses BITTEST). It's easy to expand, you only have to do this once for the whole platform. And it doesn't rely on the order of a few registers.I haven't used the invert option in this example, but it should be easy to implement - and if GCC sees the invert bit is constant, it will optimize it out I am sure.I don't use this in practice, often I do what nctnico said and create a bsp.h with all the GPIO stuff in it. If I need to port it to another board/controller, I can just copy&paste another chip's file and work my way through it.The implementation of andyturk also seems nice. On a STM32 project (with a huge 144 QFP chip, lots of pins) I also used GPIO init with a few structs that contained GPIO pin index & options, and looped through them in a initGpio() function. Maybe it's not a bad idea to combine both macro's, with a Macro table that defines both init code & IOPin objects for use with macro's in application code.

author: Kjelt
author_rank: Super Contributor
timestamp: March 30, 2014, 08:20:56 pm
content: Quote from: andyturk on March 30, 2014, 04:22:48 pmLittle warning, we used x macros for pin definition and io setup in our company, and the x macro would expand to huge amounts.If everything works and you really know what you are doing this is a nice approach, however the rest of your colleagues will probably not like it very much because fault finding or expanding is a b*tch!We never ever had soo much complaints from co workers till we introduced this and i regret it to this day. It is tough to see through it or even understand it unless you have written it, and even then after a few years you forgot about it.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 08:46:41 pm
content: Quote from: Kjelt on March 30, 2014, 08:20:56 pmWell, dannyf would fire those stupid co-workers on the spot! ;-)On a more serious note, there is a real potential for confusion with X macros. When first introduced to the idea, most people respond with: "What? You can do that?"The only way to debug the stuff is to get comfortable with the -E switch on the compiler.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 09:01:36 pm
content: Good discussions now we no longer have to deal with life's lessons,Code:#define IOPortPIC24(l) { ((IOPortReg)&TRIS##l), ((IOPortReg)&PORT##l), ((IOPortReg)&LAT##l), ((IOPortReg)&ODC##l) },const IOPort_t IOPorts = {#ifdef PORTAIOPortPIC24(A)#endif#ifdef PORTBIOPortPIC24(B)#endif#ifdef PORTCIOPortPIC24(C)#endif};I don't see the advantage here. You went all the way to great a struct, and yet then step back and use an array to hold a set of pointers to fixed memory addresses. To me, that would be a disadvantage.Also, when you port the system, you will have to remap the various registers, etc. in the IOPortPIC24 macro.I don't see why that is more preferable than to simply use macros on those registers - something we discussed in the first post. The whole point of this exercise is to move away from (user) references to TRIS, LAT or PORT registers and use just one "name" to reference them all.I think your performance test is interesting: I haven't so far focused on that. When I get more time, I will investigate further.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 09:02:55 pm
content: QuoteSomething like that should be outlawed: more hack than code.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 09:09:38 pm
content: Quote from: dannyf on March 30, 2014, 09:02:55 pmJust because you don't understand it, doesn't mean it's a hack.

author: nctnico
author_rank: Super Contributor
timestamp: March 30, 2014, 09:43:08 pm
content: The major downside of using structs is that the compiler may not put it in the memory as you would imagine. Some compilers align on 2 or 4 byte boundaries by default. Some processor don't like unaligned words. If the compiler can't figure out if a struct is aligned or not it will access a 16 or 32 word on a byte basis make code bloated and slow.Therefore I try to avoid mapping structs on registers and incoming packets as much as possible. Too much can go wrong.

author: hans
author_rank: Super Contributor
timestamp: March 30, 2014, 09:58:30 pm
content: I use C macro tables often. Even in my tiny webserver to define a router table of pages handlers. It's great to have a list of stuff iterated over multiple times, and create unique enumerator ID's, (either with hand-assigned ID, or the use of __COUNTER__), create an struct array, etc.I differ from andyturk's approach though, I usually do it like this:Code:#define TABLE(row) \row(1, 0x1234) \row(2, 0x2345) \row(3, 0x3456)// And then:#define TABLE_ROW_ENUMERATOR(a,b) TABLEROW_##a#define TABLE_ROW_STRUCTATOR(a,b) { a, b },typedef enum{TABLE(TABLE_ROW_ENUMERATOR),NR_OF_ROWS}dummyStruct myGeneratedStructTable = {TABLE(TABLE_ROW_STRUCTATOR)};If you can understand nested macro's, you can understand this.IMHO, it's the only possible way to define proper lists in standard C with IDE's (i.e. without using a special make file), and keep the code debug gable (instead of generating code precompilation, which isn't included in the original source). Inherently it's hard, because otherwise C would probably have had some kind of table syntax.Back to subject:First of all, I haven't made up this code specifically for this thread, this was just a copy/clean/paste from one of my github repo's from 2 years back. So unless I am in the movie back from the future..Anyway, I define let's say an "user space" I/O struct, where as you create a different view of the hardware via macro's. In that case you have to port that view (struct pointer) onto every platform and compiler (alignment issues), and becomes impossible on PIC16's.When you define your own view, much more is possible.Your second approach (if I looked correctly) is to define macro's for each port & register, and use those in the PIN functions. Well, OK that's fine, a bit of typing.. but, you don't have runtime addressable GPIO's (If you really want it over-the-top general purpose!). That's why I think you need a table in there. It wasn't really a spec to begin with in my project, but I liked the functionality of it.The assembler for "runtime" GPIO's is a bit of a clutter, but it will compile and do the job. For any constant GPIO access it should be quick&fast, which is it also is. And also the way it should be on a microcontroller (not like how it is done on Arduino, with the very slow digitalRead/write functions, which has to do like 4 table lookups in software)..The readability of the portable code is discussable. I could have left out the macro.. but this way I can't mess up a letter on any of the 4 registers.I haven't looked if the table is in memory after optimization. For example if the whole application can use bittests. I can imagine GCC will leave it out. Maybe I will do that tomorrow

author: c4757p
author_rank: Super Contributor
timestamp: March 30, 2014, 10:06:54 pm
content: Quote from: dannyf on March 30, 2014, 09:02:55 pmOn the contrary, it seems to me to be a painfully obvious and quite useful application for the C preprocessor. Maybe it's not always appropriate, but that doesn't mean it's always wrong.

author: dannyf
author_rank: Super Contributor
timestamp: March 30, 2014, 10:18:33 pm
content: QuoteThat's true. I don't think it is practical, desirable or possible to produce a generic approach that works on all mcus and all compilers. Thus the title of this thread is about a "more generic" approach. "more generic" in this case is actually "less generic",There are times that this approach will fail on some mcus (PIC), be less effective (Luminary / TI), or downright not workable - I am sure someone somewhere is working on a compiler that doesn't work with this approach. The point is that once you get it working on your compiler / mcu combo, it can be quite convenient.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 30, 2014, 10:27:55 pm
content: Quote from: hans on March 30, 2014, 09:58:30 pmHuh, interesting. I guess the “structator” gets rid of the need to #define and then #undef the row macro. Are the two approaches equivalent?

author: mazurov
author_rank: Frequent Contributor
timestamp: March 31, 2014, 02:27:28 am
content: Quote from: andyturk on March 30, 2014, 03:58:23 pmhttps://github.com/felis/USB_Host_Shield_2.0files avrpins.h, usbhost.h . The author also wrote an article explaining this approach (plus all other methods mentioned in this thread as well as couple others) ->http://easyelectronics.ru/rabota-s-portami-vvoda-vyvoda-mikrokontrollerov-na-si.html, however, if you don't know Russian it won't help you much (and if you do, read the article comments for the links to a discussion, also in Russian). Take a look at the works of Andrei Alexandrescu ->http://en.wikipedia.org/wiki/Andrei_Alexandrescu,his books are available in English, it may help you understand.

author: Kjelt
author_rank: Super Contributor
timestamp: March 31, 2014, 07:48:10 am
content: Quote from: nctnico on March 30, 2014, 05:07:40 pmWell why not combine the two idea's?Define each Pin as an alias or function and create a generic set/get/toggle function:Code://so something like this#define PIN_RED_LED (IO_PORT_A,0x02)#define PIN_SET(port, pins) port->ODR |= (pins) //set pins on portPIN_SET(PIN_RED_LED);

author: westfw
author_rank: Super Contributor
timestamp: March 31, 2014, 07:48:43 am
content: I'm not a C++ programmer, but I've been pretty impressed by what people can accomplish with "Templates" - it looks like you can essentially use them as a much more advanced sort of macro processor.

author: miguelvp
author_rank: Super Contributor
timestamp: March 31, 2014, 08:06:04 am
content: Templates have their own can of worms.Yes they are powerful and on small projects like mcu's can be controlled. On big project however if you use 3rd party libraries that have templates and you happen to use the same name and argument types then you can run into trouble. Reason being is that the compiler will mangle the name and end-up the same. All the object files that use the template with the same name (mangled) will have the function within it. The linker will pick one and apply it to the whole code base.What are the chances, well not many but it happens. Also they might have supplied debug libraries and the name mangler that produces the object code names. To the linker it will look the same, but the debug vs release code will be different creating all kinds of problems.So I would stay away from templates on large projects. Also the code they produce might be more expensive in code space.

author: dannyf
author_rank: Super Contributor
timestamp: March 31, 2014, 11:05:00 am
content: -Well why not combine the two idea's?-I used that approach in rewriting arduonos it needs a cheater macro in between and worked on an earlier version of gcc but not the later.

author: westfw
author_rank: Super Contributor
timestamp: March 31, 2014, 11:42:17 pm
content: QuoteIn this context I'm specifically talking about those templates that use a combination of static variables, inline functions, and template-evaluation-time processing to minimize the runtime code size. (This is not the only way that templates are used in general, of course.) I wasn't aware of template name collision issues, though: thanks for pointing that out.There is another example of using C++ templates to get optimal code for arduino-style "pin" abstraction here:http://code.google.com/p/rtoslibs/downloads/list(in the DigitalIO20130221.zip download.) I was really impressed how much cleaner this was than the digitalWriteFast C macros using __builtin_constant_p(), until I realized that C can also optimize away references to constant indices of static const arrays.

author: nctnico
author_rank: Super Contributor
timestamp: April 01, 2014, 12:07:27 am
content: Quote from: miguelvp on March 31, 2014, 08:06:04 amFor that reason C++ has namespaces. You can have the same problem with libraries which may use the same name for a particular function.

author: miguelvp
author_rank: Super Contributor
timestamp: April 02, 2014, 02:24:55 am
content: Quote from: nctnico on April 01, 2014, 12:07:27 amExactly, that's why I avoid anything that uses the std namespaceSeriously I have been hit by this many times, 3rd party libraries compiled with debug versions of std::string and the linker using those instead of the release ones. No source given just libraries so, I don't want std's in my codeEdit: Or worse yet, a different template implementation with the same name, you are asking for trouble using STL, it will give you STD's

author: Q-Kernel
author_rank: Contributor
timestamp: April 02, 2014, 03:30:56 am
content: Code:static inline __attribute__ ((always_inline)) void qBitMemSetAtomic(unsigned Bit) {__asm__ __volatile__(" lsr %0, #4, w0 \n"" bsw.z , %0 \n":: "r"(Bit) : "w0", "cc");}I use standard inline functions like the one above where I specify the bit number in the Special Function Registers. So if I want to set (make high) RA3 I write a "1" to bit 3 in SFR LATA which has address 0x02c4.Code:qBitMemSetAtomic(LAT_A3); // LAT_A3 is a define for the value 0x2c43I have those inline functions for Set, Clr, Togle and Test.There are a lot of ways to implement this but the most important part of it is that they need to be atomic. Most compilers, depending on the level of optimizing, don't generate atomic code even if you use something like thisCode:LATAbits.LATA4 = 1

author: andyturk
author_rank: Frequent Contributor
timestamp: April 02, 2014, 06:08:20 am
content: Quote from: miguelvp on April 02, 2014, 02:24:55 amPeople who use std::string or STL in embedded projects need a smack upside the head.

author: Kjelt
author_rank: Super Contributor
timestamp: April 02, 2014, 07:12:33 am
content: Anyway one of the trends that I can see is that port and peripheral configuration will be done in kind of the same way as Altera did with Quartus and their FPGA pin configuration: GUI.Select, click and generate the config header file, you include.Might be ideal for starters, but unsure if it is not a PITA for experienced users that dynamically want to change pin/peripheral configurations etc.An example, I got this email about the STM32 Cube software, I might be mistaken because I haven't checked it out, but it sure looks and sounds a lot that this is the way they are going to do it.Quote

author: westfw
author_rank: Super Contributor
timestamp: April 02, 2014, 09:26:58 am
content: I'm really unimpressed by the TI GUI pin-configuration tool ("PINMUX"):1) It generates calls to the Tivaware library (optionally, the one in ROM), without an option to generate raw register writes.2) it doesn't aggregate pin settings, even though the library supports that. Set 3 pins in a port to output, and you get three calls to the relevant function, plus a call to enable the clock. Use two ports, you get a call to enable to clock for each one. Bleh.3) It apparently only supports a small subset of the features (no pullup or pulldown, drive strength, etc.) As the name implies, it's more of a pin function selector than a real configuration tool.4) It plasters a TI copyright on its OUTPUT files.In short, this sort of utility is a vendor's chance to implement something better and smarter than you could achieve with your own preprocessor macros, and TI fails to do so. Sad :-((Especially, it ought to be good for those "tiny" ARMs that everyone says will replace 8bit chips.)Does ST do better?

author: dannyf
author_rank: Super Contributor
timestamp: April 02, 2014, 10:42:34 am
content: QuoteI would agree. I think the general trend is to flatten the learning curve for any device and use as much machine generated code as possible: that has happened on computers (from binary coding to assembly to C to app inventor), as well as on mcus: going from assembly to C to C++ to drag-and-drop.I think you will see a decline in human-generated code, in terms of %, going forward.The cube, however, is incomplete at this point and is a significant problem for people who have coded on the ST libraries.

author: Kjelt
author_rank: Super Contributor
timestamp: April 02, 2014, 11:23:33 am
content: Quote from: dannyf on April 02, 2014, 10:42:34 amIndeed. When the cube is finished it promises an effortless transition from for instance an STM32F1xx to a STM32F2xx which currently (what I have heard) is also a problem with the ST libraries which are not compatible between these two series?

author: Seg
author_rank: Regular Contributor
timestamp: April 02, 2014, 06:26:05 pm
content: Man, y'all really love your macros.static inline, people.static inline.http://gcc.gnu.org/onlinedocs/gcc/Inline.htmlAlso if you're playing memory layout games with structs, you want thepackedattribute:http://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html#Type-Attributes

author: Q-Kernel
author_rank: Contributor
timestamp: April 02, 2014, 06:39:12 pm
content: Quote from: Seg on April 02, 2014, 06:26:05 pmWith inline functions you can really improve performance. By specifying the arguments as registers you get fast code and you prevent a call which is bad for the execution pipeline. Also what a lot of people forget is that a call throws away half of your registers because the compiler can only rely on the callee save registers.We use this technique everywhere in our RTOS and that contributes to get the best performance. Google Q-Kernel and download the source to see how it is used.

author: megajocke
author_rank: Regular Contributor
timestamp: April 05, 2014, 12:13:34 am
content: I and a friend are working on a project, and we have a GPIO implementation where one can first declare ports like this:Code://IO_DECLARE_PIN(name, port, pin)IO_DECLARE_PIN(DACC_SH_FAST,GPIOB, 14)IO_DECLARE_PIN(DACC_PATCH_LATCH, GPIOB, 12)//IO_DECLARE_BUS(name, width, port, start)IO_DECLARE_BUS(DACC_SH_ADDR, 8, GPIOA, 4) /* GPIOA, 11 downto 4 */and then control these IO pins (and buses) like so:Code:ioSetPin(DACC_SH_FAST, !bitarrayGet(currentBuffer->fastModeBA, currentChannel) );ioSetBus(DACC_SH_ADDR, muxAddress);// Pulse patch latch signal low and then high again.ioSetPin( DACC_PATCH_LATCH, 0 );INACCURATE_DELAY(8);ioSetPin( DACC_PATCH_LATCH, 1 );I think it is very useful that each signal can be referred to by a single name rather than a pair of values (port and pin) like I have seen in many codebases. Being able to move parallell buses to any port and starting pin with no other changes than to the port definition header is also a nice extra.The pins have to be contiguous though.Here is what the header file looks like, for an STM ARM microcontroller:Code:#include "stm32F10x.h"void ioInternalInitPins(GPIO_TypeDef* port, uint16_t pins, GPIOMode_TypeDef mode);void ioInternalInitGpioOutput(GPIO_TypeDef* port, uint16_t pins, uint16_t onMask);void ioInternalInitAfioOutput(GPIO_TypeDef* port, uint16_t pins);/////////////////////////////////////////////////////// Macros for pin and bus definitions//typedef struct ioBusdef {GPIO_TypeDef * const port;uint16_t const mask;uint16_t const offset;} IO_BUSDEF;#define IO_PORT(name)(name).port#define IO_MASK(name)(name).mask#define IO_OFFSET(name)(name).offset// Macro that defines port constants#define IO_DECLARE_BUS(name, width, port, start) \static const IO_BUSDEF name = {port, ((1UL << (width))-1) << (start), start};// For single pins:#define IO_PINMASK(name)(name##_PINMASK)#define IO_DECLARE_PIN(name, port, pin)\IO_DECLARE_BUS(name, 1, port, pin)\static const uint16_t IO_PINMASK(name) = 1UL << (pin);// Shift and mask value from bus to port#define ioCalcOutMask(name, value) ( (((uint32_t)(value)) << IO_OFFSET(name)) & IO_MASK(name) )/////////////////////////////////// External use//void ioInitialize(void);#define ioSetPinHigh(name) ( IO_PORT(name)->BSRR = IO_PINMASK(name) )#define ioSetPinLow(name) ( IO_PORT(name)->BRR = IO_PINMASK(name) )// Alternative version:// #define ioSetPinLow(name) ( IO_PORT(name)->BSRR = IO_PINMASK(name) << 16 )#define ioSetPin(name, value) ((void)( (value) ? ioSetPinHigh(name) : ioSetPinLow(name) ))#define ioGetPin(name) ( (IO_PORT(name)->IDR & IO_PINMASK(name)) ? 1 : 0 )#define ioSetBus(name, value) ( IO_PORT(name)->BSRR = ioCalcOutMask(name, value) | ((uint32_t)IO_MASK(name) << 16) )#define ioGetBus(name) ( (IO_PORT(name)->IDR & IO_MASK(name)) >> IO_OFFSET(name) )#define ioInitGpioOutput(name, initialValue) \ioInternalInitGpioOutput(IO_PORT(name), IO_MASK(name), ioCalcOutMask(name, initialValue))#define ioInitAfioOutput(name) \ioInternalInitAfioOutput(IO_PORT(name), IO_MASK(name))#define ioInitMode(name, mode) \ioInternalInitPins(IO_PORT(name), IO_MASK(name), mode)Actually, it would be possible to get rid of most of the macros and use static inline functions instead. The indirection through the IO_PORT(name) macros was done because I didn't use static const structs initially, but separate constant variables just like what the IO_PINMASK(name) macro does. Especially if using structs, almost no macros are needed at all.

author: dannyf
author_rank: Super Contributor
timestamp: April 05, 2014, 12:41:56 am
content: QuoteOn that note, I will present a hacky solution - not recommended however.Code:typedef struct {unsigned char B0:1;unsigned char B1:1;unsigned char B2:1;unsigned char B3:1;unsigned char B4:1;unsigned char B5:1;unsigned char B6:1;unsigned char B7:1;unsigned char B8:1;unsigned char B9:1;} BIT16_T;#define PORTCbits(*(volatile BIT16_T *) &(GPIOC->ODR))#define _PORT(id, pin)PORT ## id ## bits.B ## pin#define PORT(pin)_PORT(pin)I only expanded BIT16_T through B9 because B9 is all I need for this example.In user code, you useCode:#define LEDG_PINC, 9//led on gpioc.9...PORT(LEDG_PIN) = 1; //set ledg_pinPORT(LEDG_PIN) = 0; //clera ledg_pinYou can expand the code to cover other functionalities, like turning it as output, od, input, etc.

author: dannyf
author_rank: Super Contributor
timestamp: April 05, 2014, 12:45:24 am
content: With the above approach, you can easily shrink and report Arduino wiring to AVR or any other chip - with the appropriate compiler support.

author: megajocke
author_rank: Regular Contributor
timestamp: April 05, 2014, 01:00:04 am
content: Quote from: andyturk on March 30, 2014, 04:22:48 pmThat's quite neat. I did something similar to be able to collect initialization and declarations in the same place, but did it the alternative way they mention by #including the same file multiple times.The way your code collects all the pin settings so they can be all initialized at once is very neat!I didn't go that far, so I have a function call for each pin definition by expanding the port map include file into my global initialization function. This means pins are setup in a well-defined order however, which could matter in some cases (but not really in mine).

author: megajocke
author_rank: Regular Contributor
timestamp: April 05, 2014, 01:18:21 am
content: Quote from: dannyf on April 05, 2014, 12:41:56 amI'd stay away from the "PORT(LEDG_PIN) = 1;" client syntax because it imposes restrictions on the IO library implementation which may not work well on many architectures. Function-like syntax is more flexible, regardless of whether you choose to implement it using macros or actual functions.For example, on the microcontroller I was working with you wrote a "1" to the bit under consideration of a "BRR" register to set output low or a "1" to the corresponding bit of the "BSRR" register to set the output high. The upper 16 bits of the BSRR register also worked like the BRR register (with set having higher priority than reset) so that you in one atomic operation could, for each bit of a port, either leave it alone, set it high or set it low.On this microcontroller it was not possible to get atomic updates of parts of the output ports using the output data register, although there was some trickery by which you could get at individual bits using a special part of the address space...Using bitfields might also create very inefficient code with some compilers. So forcing the IO library to use such an implementation is suboptimal in my opinion.But not being guaranteed to be able to implement atomic updates of individual GPIO pins is kind of a show-stopper. It kind of sucks if you need to disable interrupts every time you want to twiddle an IO pin.It might be so that on your specific architecture, accessing individual bits in bitfields mapped to hardware registers is atomic (using some bit set/clear instruction), but this is not so in the general case.

author: westfw
author_rank: Super Contributor
timestamp: April 06, 2014, 08:24:06 am
content: QuoteWell, yes and no.One thing to keep an eye on as you implement these "port operations" is who you're having do the work for your "abstractions." Most of the suggestions so far (including mine) do a lot of work in the compiler, which is fine, and usually desirable, but it doesn't solve everything.The Arduino digital Pin functions take a lot of slack for being slow and bloated, but they allow both the "pin number" and the "value" used as arguments to be variables. If you actually NEED that functionality, it turns out that it's pretty difficult to do much better than the arduino code. Most of the "improvements" insist on both values being compile-time constants.