Macro for efficient stm32 gpio handling - Page 1
Forum section: electronics / general

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 09:21:28 am
content: I must admit HAL it's ok-ish when starting with stm32, otherwise it'll be overwhelming at first.But after a while you start seeing the issues and limitations everywhere, and start fiddling with registers.The whole HAL is so badly done! For a simple IO Pin status change, it makes a code jump (adding stack overhead, and likely missing the prefetch cache), an if/else, and returns. It's a total waste of cpu time.However using the HAL defines is very convenient. So today I made some GPIO macros that works great, compatible with the HAL format.Most operations use 3 instructions, and no jumps, so it's very efficient..It's done for the F4 series, others might require some changes, but they're mostly the same.The target was to not hardcode anything. So any change in CubeMX works right away.But I found a big issue: HAL doesn't declare the pin numbers anywhere. They're hardcoded as Pin2=0x2, Pin8=0x100, Pin15=0x8000...Thus, you're not going to get the pin number for "LED_Pin" anywhere for making a bitmask.I had to do a some bit hacking to overcome that. Crappy HAL!It might look huge, but since it's a macro, all the calculations are done by the preprocessor at compile time, and the real code will use a constant.Updated: Revisited this after some years and made some enhancements:Code: (MACRO CODE)/** gpio.h** Created on: 20 sept. 2025* Author: David*/#ifndef INC_GPIO_H_#define INC_GPIO_H_/*GPIO macros to interface HALFor convenience, two types of the same function are provided.non-HAL: Independent Pin and Port. x_GPIO_Port/x_Pin, or GPIOx/GPIO_PIN_xHAL: Assumes pin named in cube MX, thus the input can be greatly simplified. Example:Pin "LED" defined in CubeMX. This leads to the automatic creation of "LED_GPIO_Port" and "LED_Pin" definitions.Knowing that, we can simplify to just "LED" and generate the rest.No-named pins are only compatible with non-HAL functions:writePin(port,pin,val): writePin(GPIOA, GPIO_PIN_1, SET);setPinHigh(port,pin) setPinHigh(GPIOA, GPIO_PIN_1);setPinMode(port,pin,mode) setPinMode(GPIOA, GPIO_PIN_1, MODE_OUTPUT);setPinOtype(port,pin,Otype) setPinOtype(GPIOA, GPIO_PIN_1, OUTPUT_OD);Defined pins can be used with either:writePin(port,pin,val): writePin(LED_GPIO_Port, LED_Pin, SET);setPinHigh(port,pin) setPinHigh(LED_GPIO_Port, LED_Pin);setPinMode(port,pin,mode) setPinMode(LED_GPIO_Port, LED_Pin, MODE_OUTPUT);setPinOtype(port,pin,Otype) setPinOtype(LED_GPIO_Port, LED_Pin, OUTPUT_OD);HAL_writePin(name,val) HAL_writePin(LED, SET);HAL_setPinHigh(port,pin) HAL_setPinHigh(LED);HAL_setPinMode(name,mode) HAL_setPinMode(LED, MODE_OUTPUT);HAL_setPinOtype(name,Otype) HAL_setPinOtype(LED, OUTPUT_OD);*/#define _CON2(a,b) (a##b)#define _PORT(p) (_CON2(p,_GPIO_Port))#define _PIN(p) (_CON2(p,_Pin))// Converts GPIO_PIN_x into pin number// 0b00000001->0 0b00100000->5#define __get_GPIO_Pos(x) (15*(1&&(x&(1<<15))) + \14*(1&&(x&(1<<14))) + \13*(1&&(x&(1<<13))) + \12*(1&&(x&(1<<12))) + \11*(1&&(x&(1<<11))) + \10*(1&&(x&(1<<10))) + \9*(1&&(x&(1<<9))) + \8*(1&&(x&(1<<8))) + \7*(1&&(x&(1<<7))) + \6*(1&&(x&(1<<6))) + \5*(1&&(x&(1<<5))) + \4*(1&&(x&(1<<4))) + \3*(1&&(x&(1<<3))) + \2*(1&&(x&(1<<2))) + \1*(1&&(x&(1<<1))))// Expands 16bit to 32bit. 0b1001->0b11000011// Source: https://stackoverflow.com/questions/38881877/bit-hack-expanding-bits#define __expand_1(x) ((x | (x << 8)) & 0x00FF00FF)#define __expand_2(x) ((x | (x << 4)) & 0x0F0F0F0F)#define __expand_3(x) ((x | (x << 2)) & 0x33333333)#define __expand_4(x) ((x | (x << 1)) & 0x55555555)#define __expand_5(x) ( x | (x << 1))#define __expand_16to32(x) (__expand_5(__expand_4(__expand_3(__expand_2(__expand_1(x))))))// GPIOx_MODER// mode: MODE_INPUT, MODE_OUTPUT, MODE_AF, MODE_ANALOG#define setPinMode(port,pin,mode) (port->MODER = (port->MODER & ~(__expand_16to32(pin))) | mode<<(__get_GPIO_Pos(pin)*2))#define HAL_setPinMode(name,mode) (_PORT(name)->MODER = (_PORT(name)->MODER & ~(__expand_16to32(_PIN(name)))) | mode<<(__get_GPIO_Pos(_PIN(name))*2))// GPIOx_OTYPER// Otype: OUTPUT_OD, OUTPUT_PP#define setPinOtype(port,pin,Otype) (port->OTYPER = (port->OTYPER & ~(pin)) | (pin*Otype))#define HAL_setPinOtype(name,Otype) (_PORT(name)->OTYPER = (_PORT(name)->OTYPER & ~(_PIN(name))) | (_PIN(name)*Otype))// GPIOx_OSPEEDR// speed: GPIO_SPEED_FREQ_LOW, GPIO_SPEED_FREQ_MEDIUM, GPIO_SPEED_FREQ_HIGH, GPIO_SPEED_FREQ_VERY_HIGH#define setPinSpeed(port,pin,speed) (port->OSPEEDR = (port->OSPEEDR & ~(__expand_16to32(pin))) | speed<<(__get_GPIO_Pos(pin)*2))#define HAL_setPinSpeed(name,speed) (_PORT(name)->OSPEEDR = (_PORT(name)->OSPEEDR & ~(__expand_16to32(_PIN(name)))) | speed<<(__get_GPIO_Pos(_PIN(name))*2))// GPIOx_PUPDR// pull: GPIO_NOPULL, GPIO_PULLUP, GPIO_PULLDOWN#define setPinPull(port,pin,pull) (port->PUPDR = (port->PUPDR & ~(__expand_16to32(pin))) | pull<<(__get_GPIO_Pos(pin)*2))#define HAL_setPinPull(name,pull) (_PORT(name)->PUPDR = (_PORT(name)->PUPDR & ~(__expand_16to32(_PIN(name)))) | pull<<(__get_GPIO_Pos(_PIN(name))*2))// GPIOx_IDR#define readPort(port) (port->IDR)#define HAL_readPort(name) (_PORT(name)->IDR)#define readPin(port,pin) (1&&(port->IDR & pin))#define HAL_readPin(name) (1&&(_PORT(name)->IDR & _PIN(name)))// GPIOx_ODR#define writePort(port,val) (port->ODR = val)#define HAL_writePort(name,val) (_PORT(name)->ODR = val)// GPIOx_BSRR#define writePin(port,pin,val) (port->BSRR = pin << ((val != 0)*16))#define HAL_writePin(name,val) (_PORT(name)->BSRR = _PIN(name) << ((val != 0)*16))#define setPinHigh(port,pin) ((port->BSRR = pin)#define HAL_setPinHigh(name) (_PORT(name)->BSRR = _PIN(name))#define setPinLow(port,pin) (port->BSRR = pin<<16)#define HAL_setPinLow(name) (_PORT(name)->BSRR = _PIN(name)<<16)#define togglePin(port,pin) (port->BSRR = pin << (((port->ODR & pin) != 0)*16))#define HAL_togglePin(name) (_PORT(name)->BSRR = _PIN(name) << (((_PORT(name)->ODR & _PIN(name)) != 0)*16))#endif /* INC_GPIO_H_ */Code: (ASSEMBLY, NO OPTIMIZATIONS)129 setPinMode(LED_GPIO_Port, LED_Pin, MODE_INPUT);08000c66: ldr r3, [pc, #384] ; (0x8000de8 <main+440>)08000c68: ldr r3, [r3, #0]08000c6a: ldr r2, [pc, #380] ; (0x8000de8 <main+440>)08000c6c: bic.w r3, r3, #201326592 ; 0xc00000008000c70: str r3, [r2, #0]130 setPinMode(LED_GPIO_Port, LED_Pin, MODE_OUTPUT);08000c72: ldr r3, [pc, #372] ; (0x8000de8 <main+440>)08000c74: ldr r3, [r3, #0]08000c76: bic.w r3, r3, #201326592 ; 0xc00000008000c7a: ldr r2, [pc, #364] ; (0x8000de8 <main+440>)08000c7c: orr.w r3, r3, #67108864 ; 0x400000008000c80: str r3, [r2, #0]131 setPinMode(LED_GPIO_Port, LED_Pin, MODE_AF);08000c82: ldr r3, [pc, #356] ; (0x8000de8 <main+440>)08000c84: ldr r3, [r3, #0]08000c86: bic.w r3, r3, #201326592 ; 0xc00000008000c8a: ldr r2, [pc, #348] ; (0x8000de8 <main+440>)08000c8c: orr.w r3, r3, #134217728 ; 0x800000008000c90: str r3, [r2, #0]132 setPinMode(LED_GPIO_Port, LED_Pin, MODE_ANALOG);08000c92: ldr r3, [pc, #340] ; (0x8000de8 <main+440>)08000c94: ldr r3, [r3, #0]08000c96: ldr r2, [pc, #336] ; (0x8000de8 <main+440>)08000c98: orr.w r3, r3, #201326592 ; 0xc00000008000c9c: str r3, [r2, #0]134 setPinSpeed(LED_GPIO_Port, LED_Pin, GPIO_SPEED_FREQ_LOW);08000c9e: ldr r3, [pc, #328] ; (0x8000de8 <main+440>)08000ca0: ldr r3, [r3, #8]08000ca2: ldr r2, [pc, #324] ; (0x8000de8 <main+440>)08000ca4: bic.w r3, r3, #201326592 ; 0xc00000008000ca8: str r3, [r2, #8]135 setPinSpeed(LED_GPIO_Port, LED_Pin, GPIO_SPEED_FREQ_MEDIUM);08000caa: ldr r3, [pc, #316] ; (0x8000de8 <main+440>)08000cac: ldr r3, [r3, #8]08000cae: bic.w r3, r3, #201326592 ; 0xc00000008000cb2: ldr r2, [pc, #308] ; (0x8000de8 <main+440>)08000cb4: orr.w r3, r3, #67108864 ; 0x400000008000cb8: str r3, [r2, #8]136 setPinSpeed(LED_GPIO_Port, LED_Pin, GPIO_SPEED_FREQ_HIGH);08000cba: ldr r3, [pc, #300] ; (0x8000de8 <main+440>)08000cbc: ldr r3, [r3, #8]08000cbe: bic.w r3, r3, #201326592 ; 0xc00000008000cc2: ldr r2, [pc, #292] ; (0x8000de8 <main+440>)08000cc4: orr.w r3, r3, #134217728 ; 0x800000008000cc8: str r3, [r2, #8]137 setPinSpeed(LED_GPIO_Port, LED_Pin, GPIO_SPEED_FREQ_VERY_HIGH);08000cca: ldr r3, [pc, #284] ; (0x8000de8 <main+440>)08000ccc: ldr r3, [r3, #8]08000cce: ldr r2, [pc, #280] ; (0x8000de8 <main+440>)08000cd0: orr.w r3, r3, #201326592 ; 0xc00000008000cd4: str r3, [r2, #8]139 setPinOtype(LED_GPIO_Port, LED_Pin, MODE_OD);08000cd6: ldr r3, [pc, #272] ; (0x8000de8 <main+440>)08000cd8: ldr r3, [r3, #4]08000cda: bic.w r3, r3, #139264 ; 0x2200008000cde: ldr r2, [pc, #264] ; (0x8000de8 <main+440>)08000ce0: orr.w r3, r3, #131072 ; 0x2000008000ce4: str r3, [r2, #4]140 setPinOtype(LED_GPIO_Port, LED_Pin, MODE_PP);08000ce6: ldr r3, [pc, #256] ; (0x8000de8 <main+440>)08000ce8: ldr r3, [r3, #4]08000cea: ldr r2, [pc, #252] ; (0x8000de8 <main+440>)08000cec: bic.w r3, r3, #8192 ; 0x200008000cf0: str r3, [r2, #4]142 setPinPull(LED_GPIO_Port, LED_Pin, GPIO_PULLUP);08000cf2: ldr r3, [pc, #244] ; (0x8000de8 <main+440>)08000cf4: ldr r3, [r3, #12]08000cf6: bic.w r3, r3, #201326592 ; 0xc00000008000cfa: ldr r2, [pc, #236] ; (0x8000de8 <main+440>)08000cfc: orr.w r3, r3, #67108864 ; 0x400000008000d00: str r3, [r2, #12]143 setPinPull(LED_GPIO_Port, LED_Pin, GPIO_PULLDOWN);08000d02: ldr r3, [pc, #228] ; (0x8000de8 <main+440>)08000d04: ldr r3, [r3, #12]08000d06: bic.w r3, r3, #201326592 ; 0xc00000008000d0a: ldr r2, [pc, #220] ; (0x8000de8 <main+440>)08000d0c: orr.w r3, r3, #134217728 ; 0x800000008000d10: str r3, [r2, #12]144 setPinPull(LED_GPIO_Port, LED_Pin, GPIO_NOPULL);08000d12: ldr r3, [pc, #212] ; (0x8000de8 <main+440>)08000d14: ldr r3, [r3, #12]08000d16: ldr r2, [pc, #208] ; (0x8000de8 <main+440>)08000d18: bic.w r3, r3, #201326592 ; 0xc00000008000d1c: str r3, [r2, #12]146 writePort(LED_GPIO_Port,0x1234);08000d1e: ldr r3, [pc, #200] ; (0x8000de8 <main+440>)08000d20: movw r2, #4660 ; 0x123408000d24: str r2, [r3, #20]147 data = readPort(LED_GPIO_Port);08000d26: ldr r3, [pc, #192] ; (0x8000de8 <main+440>)08000d28: ldr r3, [r3, #16]08000d2a: str r3, [r7, #4]149 togglePin(LED_GPIO_Port, LED_Pin);08000d2c: ldr r3, [pc, #184] ; (0x8000de8 <main+440>)08000d2e: ldr r3, [r3, #20]08000d30: ldr r2, [pc, #180] ; (0x8000de8 <main+440>)08000d32: eor.w r3, r3, #8192 ; 0x200008000d36: str r3, [r2, #20]151 writePin(LED_GPIO_Port, LED_Pin,0);08000d38: ldr r3, [pc, #172] ; (0x8000de8 <main+440>)08000d3a: mov.w r2, #536870912 ; 0x2000000008000d3e: str r2, [r3, #24]152 writePin(LED_GPIO_Port, LED_Pin,1);08000d40: ldr r3, [pc, #164] ; (0x8000de8 <main+440>)08000d42: mov.w r2, #8192 ; 0x200008000d46: str r2, [r3, #24]153 setPinHigh(LED_GPIO_Port, LED_Pin);08000d48: ldr r3, [pc, #156] ; (0x8000de8 <main+440>)08000d4a: mov.w r2, #8192 ; 0x200008000d4e: str r2, [r3, #24]154 setPinLow(LED_GPIO_Port, LED_Pin);08000d50: ldr r3, [pc, #148] ; (0x8000de8 <main+440>)08000d52: mov.w r2, #536870912 ; 0x2000000008000d56: str r2, [r3, #24]155 data = readPin(LED_GPIO_Port, LED_Pin);08000d58: ldr r3, [pc, #140] ; (0x8000de8 <main+440>)08000d5a: ldr r3, [r3, #16]08000d5c: and.w r3, r3, #8192 ; 0x200008000d60: cmp r3, #008000d62: ite ne08000d64: movne r3, #108000d66: moveq r3, #008000d68: uxtb r3, r308000d6a: str r3, [r7, #4]

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: April 28, 2021, 11:46:14 am
content: It's not that inefficient to use the HAL if you use link time optimization which may inline small functions. Functions are cleaner than macros

author: ajb
author_rank: Super Contributor
timestamp: April 28, 2021, 03:42:44 pm
content: Quote from: DavidAlfa on April 28, 2021, 09:21:28 amI'm not sure I understand this part. Where is LED_Pin defined? Is that a HAL thing for the dev boards that have LEDs on particular pins, or does HAL generate that from what you tell it your pin allocations are? Or is it defined by you, as your example shows, in which case why wouldn't you just define LED_Pin as "13" instead of as "GPIO_PIN_13"? I guess other parts of HAL depend on the pin being defined that way maybe, if you're using other library functions against that pin? Switching from defining LED_Pin as a mask to defining it as an offset would allow you to skip the `__get_GPIO_Pos` macro and replace that `__expand_16to32` macro chain with `(0x03<<(2*x))`I don't use HAL so don't care about compatibility, but it's possible to make usage even simpler by adding an extra macro layer:Code:#define IO_OUT_SET(...)IO_OUT_SET_SUB(__VA_ARGS__)#define IO_OUT_SET_SUB(port, pin)GPIO##port->BSRR = (1<<pin)The extra variadic macro allows multiple arguments to be passed in as a single macro and expands them as the macros are resolved, so you can do this:Code:#define LED_PIN A,6IO_OUT_SET(LED_PIN);Without the extra macro layer the preprocessor will complain about the wrong number of arguments. Each time I work on a new part family I make a set of macros in this style for it, so everything's consistent, and usually the first code I write in a new project is a list of defines for all of the IO pins transcribed from the schematic.Quote from: JOEBOBSICLE on April 28, 2021, 11:46:14 amThat second bit is a rather broad statement. Macros can certainly get very ugly when used improperly, but then so can functions (just look at HAL). A situation like this where you have one to two lines that are very simple presents very little risk of macros getting out of hand. Functionsmayget optimized down to equivalent instructions as the macros, and often they will, but it's not guaranteed. Optimization of functions depends not just on the optimization settings but also on the type and qualification of any arguments. With macros, and defined constants as their arguments, you end up giving the optimizer a set of statements that is unambiguously constant and therefore more likely to get optimized in all cases. Obviously that doesn't mean that EVERYTHING should be macros instead of functions, but for stuff like this where you will use them everywhere across a project and they're so simple to write once, I think macros make a lot of sense. And even if the macros aren't BETTER than function calls, I don't see how they're especially cleaner in situations like this.

author: newbrain
author_rank: Super Contributor
timestamp: April 28, 2021, 03:52:30 pm
content: As another alternative, there's the LL library, which mostly consist of static inline functions.They are very similar to the macros defined above, and for GPIO an init function is also provided, though the HAL one can be used too.I have yet to see a case where they were not inlined, from -Og onwards.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 04:19:25 pm
content: Quote from: ajb on April 28, 2021, 03:42:44 pmAs I said, the main point is to keep CubeMX for quick peripheral inititalization. It really saves a lot of time.If I assigned LED to PC3, but I later change it to PA0, I'd like to avoid doing anything else to apply the changes.About the pins, yes, it's weird. Look at this example:It will generate this:Code: (main.h)#define LED_Pin GPIO_PIN_13#define LED_GPIO_Port GPIOC#define VBUS_Pin GPIO_PIN_2#define VBUS_GPIO_Port GPIOB#define SCK_Pin GPIO_PIN_13#define SCK_GPIO_Port GPIOB#define DATA_Pin GPIO_PIN_14#define DATA_GPIO_Port GPIOB#define TEST_Pin GPIO_PIN_15#define TEST_GPIO_Port GPIOB#define BREAK_Pin GPIO_PIN_8#define BREAK_GPIO_Port GPIOA#define PREV_Pin GPIO_PIN_8#define PREV_GPIO_Port GPIOB#define NEXT_Pin GPIO_PIN_9#define NEXT_GPIO_Port GPIOBAnd the GPIO declarations (GPIO_PIN_x) are:Code: (stm32f4xx_hal_gpio.h)#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */#define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */#define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */#define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */#define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */#define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */#define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */#define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */#define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */So yes, you can't get the pin index anywhere. I could do a switch, but I wanted to avoid using CPU power when a macro can do it at compilation time.Quote from: ajb on April 28, 2021, 03:42:44 pmThis looks very interesting!I'm not a professional coder, learning these little things is great.But again that will require to change the code if the pin is modified in CubeMX.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 04:25:00 pm
content: Here is what I use for thishttps://github.com/ataradov/mcu-starter-projects/blob/master/stm32g071/hal_gpio.hThe use is similar, but you get nice type checked functions for each pin, and you don't need to pass port/pin parameters on every call. First define the pins once:Code:HAL_GPIO_PIN(LED, D, 0)HAL_GPIO_PIN(BUTTON, B, 0)Then access them though dedicated functions with readable names:Code:HAL_GPIO_LED_out();HAL_GPIO_LED_set();HAL_GPIO_BUTTON_in();HAL_GPIO_BUTTON_pullup();if (HAL_GPIO_BUTTON_read()) ....

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 04:31:34 pm
content: Looks good! Why nobody taking all these contributions and doing a new library? It would be great to get rid of that Cube crap once for all

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 04:45:13 pm
content: Because a universal library that works for everyone would be bloated again. Everyone who wants things simple just made their own file, others just use Cube. I personally would not switch to any other library when I have one header file that does everything I need.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 04:52:49 pm
content: The HAL wouldn't be so bad if the guys did things right.For example, HAL_GPIO_Toggle:Code:void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){uint32_t odr;odr = GPIOx->ODR;GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);}Instead:Code:void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){GPIOx->ODR^=GPIO_Pin;}Since the peripheral registers are volatile, the compiler won't optimize these too much.Really, why so much mess? Makes me thing what people are behind it. Newbies?Also they could declare it as inline? Eve It will use a little bit more of flash, but also I measured it 3x faster

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 28, 2021, 04:58:47 pm
content: Quote from: newbrain on April 28, 2021, 03:52:30 pmYep.And that said, use what's best for your requirements. Who cares if setting an IO takes a couple more cycles that it would with fully optimized assembly code, unless you actually NEED to save those extra cycles for performance reasons. I'm against execssive bloat, but also against excessive optimization when it's not actually needed. Sometimes it is, but most of the time, if you really need ultra-fast GPIO toggling, then you're probably going to have to use a dedicated peripheral anyway (such as output compare, serial or parallel bus, or whatever else...) rather than bit-banging stuff. Those MCUs are complex stuff and are pretty fast. You can't think of them as you would have when dealing with crippled 8-bit MCUs running at a couple MHz.So yeah, GPIO-related functions in the HAL are definitely not what causes issues in most cases. There are HAL functions that are easy to use, but definitely NOT efficient though. Functions handling peripheral block access via DMA in just one function, for instance (ADC, DAC, SPI, ...). As they often include the DMA setup code, they can take hundreds of cycles and are good examples of what can be hand optimized.And as ataradov said, if you think you can replace one general-purpose, one-size-fits-all library with another, better one, you're probably deluded.Just use existing libraries when they fit your requirements, and hand-written code when they do not.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 05:08:33 pm
content: Quote from: DavidAlfa on April 28, 2021, 04:52:49 pmThe first code is actually correct if you ever call that from an interrupt. The second code would fail and set the wrong bits if interrupt and main code coincide. Not newbies, quite the opposite.I use ^ too, but that't the responsibility I'm taking. And I may forget about it and run into bugs that I have to debug. But at least, if it is my bug, I'm the one to blame for time spent debugging.And that's another argument against universal libraries. In my code I make allowances for my general coding and architecture style. If you were to make a universal code that should just work for everyone, things will get much more complicated.Also, with a simple code like this that manipulates single pins, you are assuming that you won't need to modify multiple pins. Or you need to make an API for that too. Again, in my case I know that modifications to multiple pins are rarely used by me. Usually it happens in very tightly optimized sections, where completely custom code is justified anyway. So I never bother to implement anything generic for manipulating multiple pins.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 28, 2021, 05:23:27 pm
content: Quote from: ataradov on April 28, 2021, 05:08:33 pmAbsolutely. The two versions absolutely DO NOT do the same thing. The ST versions uses the BSRR register to ultimately toggle the IO. This guarantees atomicity. Using the ODR register only instead does not guarantee atomicity.And that said, compare the assembly code generated for both versions with compiler optimizations enabled. The second one is likely not that much more efficient, and doesn't guarantee atomicity.You can have a quick look there:https://electronics.stackexchange.com/questions/392954/understanding-atomic-writes-to-gpio-on-different-arm-cortex-msReminds me that on the PIC32 MCUs, there are SET, RESET and INVERT registers (not sure about the exact names anymore), and thus you can actually toggle IOs in just one instruction writing to the INVERT register. Convenient. I don't think the STM32 MCUs have such "invert" registers (I guess STM would have used them if so), but they are cool.With that said, as I wrote above, if you really need to "toggle" IOs, and toggle them with strict timing requirements, then it smells like a peripheral such as output compare would definfitely be called for instead of trying to big-bang it.

author: ttt
author_rank: Regular Contributor
timestamp: April 28, 2021, 05:28:08 pm
content: Quote from: DavidAlfa on April 28, 2021, 04:52:49 pmYour version with using ODR only is not interrupt save, right? If an interrupt modifies one of the other GPIOs on that port you can end up with a race. That's why the use of BSRR is preferred to be the default. Admittedly an edge case since we are talking about a 2 cycle chance of that happening but still possible. And that HAS happened to me in deployed code. Super hard to track down.As for inlining, -flto makes this a non-issue for practical applications. I take my type safety over using macros any day.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 05:32:31 pm
content: Quote from: ttt on April 28, 2021, 05:28:08 pmMy macros are type safe, since they generate a bunch of dedicated functions. Those things are marked as inline, but yes, LTO would further hammer that down.There is a common confusion between simplicity of the code and it being optimal. Optimal code is more often than not is messier.In my case I go so simplicity of reading and maintenance rather than ultimate optimization. I let the compiler optimize as much as it can, and if that it not enough, I do it by hand. It is rarely needed.Code that is simple to read is often more optimal than some stuff with a ton of abstraction, but that is not given.

author: agehall
author_rank: Frequent Contributor
timestamp: April 28, 2021, 05:49:36 pm
content: One also has to remember that the HAL is an abstraction that needs to work across multiple devices - it has to account for differences in a way that doesn’t break the APIs it presents. This can lead to some “over complicated” architecture/design in some places.But from what I’ve seen of it, it isn’t all that bad. Just complicated at times.Back in the days, I used to write super optimized assembly code to convert data into assembly files that could be compiled into the software I was working on. Completely pointless as that was a one-off thing but I wanted everything optimized 100%. These days, I care much more about code readability and simplicity for the *reader* rather than optimizing everything. I trust the C compiler to do that well enough so that I don’t have to care.And one thing is certain, compared to the steaming pile of crap that is the Arduino framework and related libraries, the STM32 HAL is a beautiful piece of art.

author: ttt
author_rank: Regular Contributor
timestamp: April 28, 2021, 05:55:53 pm
content: Quote from: ataradov on April 28, 2021, 05:32:31 pmIn principle everybody can agree on that. A secondary goal should also be to make the code more easy to debug. Extensive use of macros and abstractions can make it vastly more difficult to debug code in the debugger. But then I heard there are still are pure printfers out there.

author: lucazader
author_rank: Regular Contributor
timestamp: April 28, 2021, 05:56:53 pm
content: I think an important point that has been bought up by a few users here is try not to over optimize things from the beginning, and then only start optimizing if you run into performance issues.If you want a lighter weight library than the HAL, then using the LL library is probably the easiest way forward.@ataradov your macro to generate the inline functions is quite nice, I hadn't thought to do this. Thanks for sharing.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 06:12:23 pm
content: Quote from: ttt on April 28, 2021, 05:55:53 pmIm all ears! What are other possible options for MCUs like STM32? Else thant print or SWV trace? SWV will allow tracing of only 4 variables.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 06:25:06 pm
content: Quote from: DavidAlfa on April 28, 2021, 06:12:23 pmAn actual debugger.Although I generally don't use debuggers. Somehow I can figure things out with printfs and GPIO toggling faster than with a debugger. But I also have a ton of helpful debug code snippets that I have accumulated over the years.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 06:32:08 pm
content: Well, that's obvious . I was expecteting some magic!I've recently seen people claiming that debugging by watching variables, breakpoints and stepping the code is something from the past.So I was curious, I don't know other way.printf is always a great thing to avoid using 876 breakpoints and opening 51 files!If certain conditions are supposed to happen, then just print them has they come in.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 06:39:31 pm
content: There are advantages and disadvantages for both. printfs affect timing and your serial interface may not even have enough bandwidth to get all the information out. Breakpoints and singe stepping has its own gotchas. One of the most common ones is watching the peripheral registers while debugging. If those registers have clear on read flags, then debugger will clear them and the application will not see them.You need to get comfortable using all the tools available when appropriate. That's part of the deal. There is no one universal tool for everything.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 07:02:04 pm
content: Yeah, but for example, with stm32 you redirect the output to SWO, that's pretty fast (2MHz, or more with better debuggers).I agree that it will slow down the code a bit but nothing serious if you limit the output instead printing books.But when something just fails without easy spot, you need to stop and check every register, and for the peripherals, if you're lucky you can use the DBGMCU bits.But for others like DMA, SPI, you're screwed. I had a lot of trouble with these as I couldn't stop the hardware, the interrupts kepts stacking while halted, fooling myself into thinking there were more bugs.BTW, Taradov sound funny in spanish, "Tarado" means faulty, crazy, mad, freaked put.

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: April 28, 2021, 07:02:36 pm
content: My point about link time optimization is that I see a lot of developers who don't know about that or understand the tool chain completely.Writing abstract code in 2021 is fine. Writing macro filled code without a HAL will cause you a whole crap load of problems when your MCU goes out of stock or is obsoleted.My advice is to never modify peripheral registers in the same file as your business logic.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 07:10:08 pm
content: You would need a slightly different version for every family. Not too hard with a few defines.GPIO is very similar on all, that's a 10 minute job for every family.I understand yout thinking from a professional point of view.You want to develop fast, and avoid losing days in debugging a strange bug without knowing if it's your library or the code itself.Anyways, HAL itself is not reliable with its bugs waiting to happen under very specific conditions.I hope there are no HAL-programmed STM32s in airplanes!. Oh, ask Boing for their 737s... Who knows!

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 28, 2021, 07:32:09 pm
content: Quote from: ataradov on April 28, 2021, 06:25:06 pmI practically never use debuggers on embedded targets either. My experience is similar; they actually tend to make you waste a lot of time. Well-written debug code is usually much more effective, and the added benefit is that they leave a lasting trace of your debugging. Of course it requires a bit of thought. Using a debugger won't leave any trace. Usually. I reckon people paid by the hour may prefer having to think less, and spend more time frantically hitting on keys.Another point is that "real-time" issues can be very hard to track down using a debugger. Toggling IOs can help here. Even sending some info through SWO or even some UART - long gone are the days where you were limited to very low baud rates. You can set up SWO or UART for several Mbits/s, and thus make any logging have mininal overhead.Now for any "complex" logic that could have a high probably of having bugs, what I sometimes do is just compile it on a PC, writing minimal additional code to test it. It gives you a way of adding automated testing without having to do this on the final target (which can be a pain with MCUs), while being able to occasionally use a debugger if required, with usually many more features than what you get with a MCU. So that's what I usually do for any complex logic, algorithms or even DSP. The benefit of C here (or C++) is that correctly written in a portable way, it can be compiled on a PC unmodified.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 07:34:13 pm
content: Quote from: DavidAlfa on April 28, 2021, 07:10:08 pmYou yourself just wrote the code with a bug waiting to happen under certain conditions.Quote from: DavidAlfa on April 28, 2021, 07:10:08 pmThere is plenty of HAL in airplanes. Writing custom code for cars and airplanes is even harder, since it need to meed requirements of things like MISRA-C and similar standards.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 28, 2021, 07:49:25 pm
content: Quote from: ataradov on April 28, 2021, 07:34:13 pmYup. IIRC, STM32's HAL is MISRA-C compliant. It can partially explain why the source code sometimes looks a bit clunky. You have to follow the rules.Oh, and humility contributes to code quality much more so than certainties IMHO.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 08:03:48 pm
content: Yeah, toggling GPIOs was my approach too.A number of 10uS pulses to debug what was happening, so I could debug with the logic analyzer in real time.Ex.: 1 pulse=Rx start, 2 pulses=RX end, 3 pulses=RX Error... It made my life much easier

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: April 28, 2021, 08:23:42 pm
content: If you're shocked about the abstraction in the HAL then ditch your car, it's running on MATLAB auto-generated code written by non software engineers plus autosar to hold it together.

author: mikerj
author_rank: Super Contributor
timestamp: April 28, 2021, 08:24:06 pm
content: Quote from: ajb on April 28, 2021, 03:42:44 pmFWIW you don't need variadic macros to pass multiple arguments, only if you want to pass a variable number of arguments. I wrote something similar to this maybe 15 years back for an AVR Mega128 project and it worked ok but, as others have mentioned, I would shy away from using macros for this kind of thing as it makes debugging annoying at best.Each pin macro defines port, bit, direction, initialisation level, and assert level. Every function-like macro accepts all these arguments but uses only the ones it needs. Not the greatest bit of code in retrospect, but we all learn from experience.Code:/* Define the pins: port, bit, ddr, init level, assert*/#define PIN_A0A,0,1,1,1#define PIN_A1A,1,1,0,1#define PIN_A2A,2,1,0,1#define PIN_A3A,3,1,1,1#define PIN_A4A,4,1,1,1#define PIN_A5A,5,1,1,1#define PIN_A6A,6,1,1,1#define PIN_A7A,7,1,1,1#define IO_MASK(b) (unsigned char)(1 << (b))/* Set a logic 1 level on a pin */#define SET_PORT_BIT_(p,b,d,l,s) do{(PORT ## p) |= IO_MASK(b)}while(0)#define SetPin(x) SET_PORT_BIT_(x)/* Set a logic 0 level on a pin */#define CLR_PORT_BIT_(p,b,d,l,s) do{(PORT ## p) &= ~IO_MASK(b)}while(0)#define ClearPin(x) CLR_PORT_BIT_(x)/* Assert the output level on a pin, this takes into account the polarity bit */#define ASSERT_PIN_(p,b,d,l,s) do{if(s) { SET_PORT_BIT_(p,b,d,l,s); } else { CLR_PORT_BIT_(p,b,d,l,s); }}while(0)#define AssertPin(x) ASSERT_PIN_(x)/* Deassert the output level on a pin, this takes into account the polarity bit */#define DEASSERT_PIN_(p,b,d,l,s) do{if(s) { CLR_PORT_BIT_(p,b,d,l,s); } else { SET_PORT_BIT_(p,b,d,l,s); }}while(0)#define DeassertPin(x) DEASSERT_PIN_(x)......

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 08:52:15 pm
content: Given:#define setPinHigh(port, pin) ...whateverLet's say I do:setPinHigh(GPIOA, GPIO_Pin_8)Is there any way to extract the "8" from "GPIO_Pin_8" before the preprocessor replaces it with the value defined elsewhere as "0x100" ?

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 08:56:41 pm
content: Why not do setPinHigh(GPIOA, 8 ) and use 8 to do whatever you want?

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 09:14:07 pm
content: Because I want to use the HAL defines, I already said it a lot of timesWhen opening CubeMX and setting pin A8 as "Button1" it will make this:#define Button1_GPIO_Port GPIOA#define Button1_PIN GPIO_PIN_8GPIO_PIN_8 is defined in HAL:#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */So I'd like to somehow extract the "8". Just to know if it's possible.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 09:42:29 pm
content: The only thing that comes to mind is a macro that contains a huge expression that checks all 16 values and returns the corresponding index.There is no way to extract it from the actual define name.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 09:50:37 pm
content: That's what I've been doing since the beginningIn the first post:Code:#define __get_GPIO_Pos(x) (15*(1&&(x&(1<<15))) + \14*(1&&(x&(1<<14))) + \13*(1&&(x&(1<<13))) + \12*(1&&(x&(1<<12))) + \11*(1&&(x&(1<<11))) + \10*(1&&(x&(1<<10))) + \9*(1&&(x&(1<<9))) + \8*(1&&(x&(1<<8))) + \7*(1&&(x&(1<<7))) + \6*(1&&(x&(1<<6))) + \5*(1&&(x&(1<<5))) + \4*(1&&(x&(1<<4))) + \3*(1&&(x&(1<<3))) + \2*(1&&(x&(1<<2))) + \1*(1&&(x&(1<<1))))

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 28, 2021, 09:54:54 pm
content: Quote from: DavidAlfa on April 28, 2021, 09:50:37 pmReally.

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 10:10:13 pm
content: Quote from: DavidAlfa on April 28, 2021, 09:50:37 pmThat is the shortest way of doing it. I really see no point in doing that at all thoguh.Defining numbers though numbered constant is one of the stupidest things libraries do. Just use the number directly. "HAL_GPIO_8" does not add more meaning compared to simple "8".

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 28, 2021, 10:16:35 pm
content: Yeah, I complained at ST's forum some time ago.They could use pin numbers, then bitshifting to get the mask.Ex:#define GPIO_PIN_8 8And whenever they want to modify pin8:void whatever(pin){x = (1<<pin)}Don't ask me why they do these things. It's very easy to make a bitshift, but not to revert it!

author: ataradov
author_rank: Super Contributor
timestamp: April 28, 2021, 11:41:51 pm
content: They designed their APIs to not need the raw number. You design yours however you want. I really see no problem here. I don't understand why you want to use their defines for your system.