STM32 macro for port and pin read and write - Page 1
Forum section: electronics / general

author: rvalente
author_rank: Frequent Contributor
timestamp: December 07, 2019, 03:26:01 pm
content: Hello Mates,i'm starting my first stm32 serious project, CUBE32MX IDE, CUBEmx libraries with hal and etc..What I find very annoying is the ARM read and write ports, always need to declare GPIO port and PIN, very boring.Do you guys use any macro to make this less painfull?Code:RA0 = 0; //Just like pics doCode:digitalWrite(13, 1); //Just like arduinosBANG! DONE!On STM32:Code:HAL_GPIO_WritePin(DO0_GPIO_Port, DO0_Pin, 1); //ohhh, c'mon, boring, makes passing pins to functions as arguments twice painful

author: JohnnyBerg
author_rank: Frequent Contributor
timestamp: December 07, 2019, 04:14:22 pm
content: Not a macro, but a simple C++ solution:Code:class Pin{private:GPIO_TypeDef *port;uint16_t pin;public:Pin::Pin(GPIO_TypeDef *_port, uint16_t _pin) : port(_port), pin(_pin){};void Pin::write(bool value) { value ? LL_GPIO_SetOutputPin(port, pin) : LL_GPIO_ResetOutputPin(port, pin); }};

author: rvalente
author_rank: Frequent Contributor
timestamp: December 07, 2019, 04:17:08 pm
content: Quote from: JohnnyBerg on December 07, 2019, 04:14:22 pmThats nice but, i'm using C not C++This hole trouble is due to me converting the sevseg library from arduino to stm32

author: emece67
author_rank: Frequent Contributor
timestamp: December 07, 2019, 04:42:21 pm
content: .

author: rvalente
author_rank: Frequent Contributor
timestamp: December 07, 2019, 04:46:40 pm
content: Quote from: emece67 on December 07, 2019, 04:42:21 pmWill try it, tks!

author: Siwastaja
author_rank: Super Contributor
timestamp: December 07, 2019, 04:57:30 pm
content: This is what I use:Code:// Outputs:#define HI(port, idx) do{(port)->BSRR = 1UL<<(idx);}while(0)#define LO(port, idx) do{(port)->BSRR = 1UL<<(16+idx);}while(0)// Inputs:#define IN(port, idx) ((port)->IDR & (1UL<<(idx)))// Always 0 or 1:#define IN_LOGICAL(port, idx) (!!((port)->IDR & (1UL<<(idx))))// Configuration:#define IO_TO_GPI(port, idx) do{ uint32_t _tmp_ = (port)->MODER; _tmp_ &= ~(0b11UL<<((idx)*2)); (port)->MODER = _tmp_; }while(0)#define IO_TO_GPO(port, idx) do{ uint32_t _tmp_ = (port)->MODER; _tmp_ &= ~(1UL<<((idx)*2+1)); _tmp_ |= 1UL<<((idx)*2); (port)->MODER = _tmp_; }while(0)#define IO_TO_ALTFUNC(port, idx) do{ uint32_t _tmp_ = (port)->MODER; _tmp_ &= ~(1UL<<((idx)*2)); _tmp_ |= 1UL<<((idx)*2+1); (port)->MODER = _tmp_; }while(0)#define IO_TO_ANALOG(port, idx) do{ uint32_t _tmp_ = (port)->MODER; _tmp_ |= 0b11<<((idx)*2); (port)->MODER = _tmp_; }while(0)#define IO_PULLUP_ON(port, idx) do{ uint32_t _tmp_ = (port)->PUPDR; _tmp_ &= ~(1UL<<((idx)*2+1)); _tmp_ |= 1UL<<((idx)*2); (port)->PUPDR = _tmp_; }while(0)#define IO_PULLDOWN_ON(port, idx) do{ uint32_t _tmp_ = (port)->PUPDR; _tmp_ &= ~(1UL<<((idx)*2)); _tmp_ |= 1UL<<((idx)*2+1); (port)->PUPDR = _tmp_; }while(0)#define IO_PULLUPDOWN_OFF(port, idx) do{ uint32_t _tmp_ = (port)->PUPDR; _tmp_ &= ~(0b11UL<<((idx)*2)); (port)->PUPDR = _tmp_; }while(0)#define IO_PUSHPULL(port, idx) do{(port)->OTYPER &= ~(1UL<<(idx));}while(0)#define IO_OPENDRAIN(port, idx) do{(port)->OTYPER |= (1UL<<(idx));}while(0)#define IO_SPEED(port, idx, speed) do{uint32_t _tmp_ = (port)->OSPEEDR; _tmp_ &= ~(0b11UL<<((idx)*2)); _tmp_ |= (speed)<<((idx)*2); (port)->OSPEEDR = _tmp_; }while(0)#define IO_SET_ALTFUNC(port, pin, af) do{ _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wshift-count-negative\"") _Pragma("GCC diagnostic ignored \"-Wshift-count-overflow\"") \if((pin)<8) {uint32_t _tmp_ = (port)->AFR; _tmp_ &= ~(0b1111UL<<((pin)*4)); _tmp_ |= af<<((pin)*4); (port)->AFR = _tmp_;} \else {uint32_t _tmp_ = (port)->AFR; _tmp_ &= ~(0b1111UL<<(((pin)-8)*4)); _tmp_ |= af<<(((pin)-8)*4); (port)->AFR = _tmp_;} _Pragma("GCC diagnostic pop") }while(0)#define IO_ALTFUNC(port, pin, af) do{ IO_TO_ALTFUNC((port),(pin)); IO_SET_ALTFUNC((port),(pin),(af));}while(0)Usage:Code:IO_TO_GPI(PORTA, 5);IO_TO_GPO(PORTB, 6);if(IN(PORTA,5)){HI(PORTB, 6);}// Full uart configuration example for STM32H743:RCC->APB2ENR |= 1UL<<4;IO_ALTFUNC(GPIOB, 14, 4);IO_ALTFUNC(GPIOB, 15, 4);USART1->BRR = 100000000/115200;USART1->CR1 = 0UL<<5 /*RX interrupt*/ | 1UL<<3 /*TX ena*/ | 1UL<<2 /*RX ena*/ | 1UL /*USART ENA*/;

author: ataradov
author_rank: Super Contributor
timestamp: December 07, 2019, 05:23:51 pm
content: https://github.com/ataradov/mcu-starter-projects/blob/master/stm32g0/hal_gpio.hUse:Code:HAL_GPIO_PIN(LED, D, 0)HAL_GPIO_PIN(BUTTON, B, 0)int main(void){HAL_GPIO_LED_out();HAL_GPIO_LED_set();HAL_GPIO_BUTTON_in();HAL_GPIO_BUTTON_pullup();if (HAL_GPIO_BUTTON_read()){// ...}}

author: jhpadjustable
author_rank: Frequent Contributor
timestamp: December 07, 2019, 07:29:34 pm
content: STM32 libraries are well-known for bloat and verbosity. One of Cube's main goals is to makeproductdesign more efficient, by making the software and hardware more independent, so that software can more easily adapt to changes in a larger system of hardware. For example, if a signal moves to another pin or a different connector, just relabel in Cube and recompile, without needing to edit files by hand. Or, you can build a .c file for setting up some circuit block which you use a lot, and copy that file and the circuit into a new design, setting pin labels to suit your new configuration. Or, you can easily move your code from a development board to a right-sized device for production just by changing the device/package and relabeling pins to match.If your design will only live on a dev board forever, you won't see those gains and don't need to do things the HAL way. You could just as well:Code:HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, 1);or use the C preprocessor to compact two parameters into one for less typing:Code:#define D13_pin GPIOA, GPIO_PIN_13...HAL_GPIO_WritePin(D13_pin, 1);HAL_GPIO_TogglePin(D13_pin);If I need to pass around one and only one parameter, such as when dynamically configuring ports at run-time, I do something like this:Code:/* ... in the header ... */static const GPIO_TypeDef * const gpioports[] = { GPIOA, GPIOB, GPIOC, GPIOD };#define BLUEPILL_PIN_4 ((1 << 4) + 15) /* B15 = GPIOB (1) << 4, + bit 15 */#define BLUEPILL_PIN_5 ((0 << 4) + 8) /* A8 = GPIOA (0) << 4, + bit 8 *//* ... */static inline void dwrite(int pin, bool val){assert(pin < (16 * sizeof(gpioports)/sizeof(GPIO_TypeDef *)));HAL_GPIO_WritePin(gpioports[pin >> 4], 1 << (pin & 0xf), val);}/* ... in the program ... */dwrite(BLUEPILL_PIN_5, 1);There are many ways to sugar-coat the reality of a sophisticated MCU, according to your own taste and habits. But youmustbe willing to confront C and its preprocessor and the hardware, on their own terms, which aren't necessarily beautiful or fun. If you need entertainment and beauty and fun, why not stick with a HAL that promises those qualities.

author: mark03
author_rank: Frequent Contributor
timestamp: December 08, 2019, 12:25:00 am
content: This is one (of many) examples where the simplicity of direct register access really shines.GPIOB->BSRR = 1 << 4; // set PB4 highGPIOB->BSRR = 1 << (4+16); // set PB4 lowHard to see how it could get any simpler. Although @Siwastaja's shorthand is kind of nice too.

author: ataradov
author_rank: Super Contributor
timestamp: December 08, 2019, 12:28:53 am
content: Well, my macros translate to a similar minimal code, except that you operate with actual application specific names. This makes is very easy to read the code and change the pin assignments.

author: emece67
author_rank: Frequent Contributor
timestamp: December 08, 2019, 01:36:52 pm
content: .

author: SiliconWizard
author_rank: Super Contributor
timestamp: December 08, 2019, 05:36:18 pm
content: Quote from: ataradov on December 07, 2019, 05:23:51 pmNot bad. That covers a lot of use cases and is simple enough.I think there are still cases when using the port (pointer) and pin number is useful, and when I use it: for instance writing "drivers" for some external peripherals - I usually prefer passing the required pins as parameters, so that the same functions can be reused with several instances, rather than defining them as macros in the driver code itself. When the driver is a bit heavy, including it as all inline functions would be too heavy. But for simpler stuff, it certainly works.

author: lucazader
author_rank: Regular Contributor
timestamp: December 08, 2019, 10:37:27 pm
content: I would tend to avoid register access macros like this, and then use an inline function as emece67 suggests.If you dont want this to then call a HAL function for speed reasons, then you could place some of the suggested direct register access expression in the inlined functions.This way you get all the benefits of the c type safety system with optimized code.Inline functions are usually a bit easier to debug than macros, as well as they cant affect the control flow of software accidentally like some macros can (if you dont use the do-while(0) trick with them).And with a decent optimizing compiler with the optimization level set to anything but off, these inline function should be optimized to the direct register read and writes anyway!Personally I will only ever use a macro in C to define the size of an array.However this is a matter of choice for me, as I have been bitten by liberal use of macros in the past, and now avoid them like the plague.

author: emece67
author_rank: Frequent Contributor
timestamp: December 08, 2019, 11:51:25 pm
content: .

author: donotdespisethesnake
author_rank: Super Contributor
timestamp: December 09, 2019, 09:53:55 pm
content: Quote from: mark03 on December 08, 2019, 12:25:00 amThat's terrible code though, a nightmare to debug and maintain. In general, if you need a comment to explain the code like that, the code is inadequate. What should you aim to be writing is lucid code like:Code:set_high (PB4);set_low (PB4);which can be easily and efficiently achieved using macros or inline functions (preferred).Comments not required.

author: pigrew
author_rank: Frequent Contributor
timestamp: December 09, 2019, 11:07:50 pm
content: I've never found a perfect GPIO interface. Some will let you set multiple bits at once, others don't. Sometimes you want to read the ODR, other times you want to read the IDR.One way in C99 to achieve close to the PIC syntax is to use bit slices, something like:struct bitAccessStruct {unsigned int p0 : 1;unsigned int p1 : 1;unsigned int p2 : 1;unsigned int p3 : 1;unsigned int p4 : 1;unsigned int p5 : 1;/* more bits here */} __attribute__((packed));#define PA0_OUT ((struct bitAccessStruct*)(&(GPIOA->ODR)))->p0#define ODR(port,pin) (((struct bitAccessStruct*)(&((GPIO##port)->ODR)))->p##pin)#define IDR(port,pin) (((struct bitAccessStruct*)(&((GPIO##port)->IDR)))->p##pin)void f() {ODR(A,3) = IDR(A,2);}The advantage of this syntax is that it is fast (everything is inlined), and can be used as both a rvalue and a lvalue.I don't think it's possible in C for it to automatically select the ODR when used as a lvalue and the IDR as a rvalue.... so probably READ and WRITE macros are the best solution, as has been already mentioned.PS: Personally, I just use the HAL functions....

author: andersm
author_rank: Super Contributor
timestamp: December 09, 2019, 11:44:53 pm
content: If you want to set a pin to a non-constant value, you can doCode:GPIOn->BSRR = (1 << 16+pin) | (val << pin);wrapped in your macro/function/idiom of choice. Ie. if you set and clear a pin at the same time, the set operation takes precedence.

author: westfw
author_rank: Super Contributor
timestamp: December 10, 2019, 03:32:34 am
content: QuoteDon't do this on SET or CLEAR registers, though. Behind the scenes, a line like:Code:bitclears.p5 = 1;Will result in a read/modify/write of the whole register. If it reads "the current bits" of the port, the "write" part will clear ALL the bits that were set. I've seen this be responsible for "weird" operation of ISRs where interrupt causes were reset by writing ones...

author: Siwastaja
author_rank: Super Contributor
timestamp: December 10, 2019, 08:40:02 am
content: Quote from: emece67 on December 08, 2019, 11:51:25 pmIt's a stupid C quirk required in macros so that they can be used function-like, including cases likeif(something)function_like_macro();Not required in single statements if you ditch {} as well, but I wanted to write all the macros similarly in that file.static inline functions tend to work mostly properly now, but there is a reason I didn't use them, and still don't fully trust them. At the very least, use -Winline with gcc so it reports back when it fails to do the trivial inlining. Attribute always_inline also suggested.

author: SiliconWizard
author_rank: Super Contributor
timestamp: December 10, 2019, 03:38:07 pm
content: Quote from: Siwastaja on December 10, 2019, 08:40:02 amTrue. And it's also a pretty clear statement from the C standard: the 'inline' keyword is merely an hint to the compiler, which can do whatever it wants with it.GCC will often inline static functions automatically (even without the 'inline' qualifier) at the higher opt. levels (don't remember which, but it sure does at -O3), but there's no guarantee about it (it depends on a lot of factors including the resulting code size), and the "inline" qualifier is often nicely ignored. The '-Winline' parameter to get warnings about it is cool (purely GCC though?), but if GCC ever emits warnings about some of your inline-qualified functions, you'll be glad to know. But basically stuck, or finding yourself trying to tweak stuff until GCC gets to inline all of them. Yuck.Macros are fine when you know what you're doing. Just avoid calling them with parameters that have side-effects (or make sure you only use all macro parameters ONCE in the macro expansion, which is not always possible).

author: JohnnyBerg
author_rank: Frequent Contributor
timestamp: December 17, 2019, 07:51:04 am
content: Not a macro but the power of C++, giving abstraction, maintainability, readability, namespaces, strong typing, domain driven design and unit testing. Also very easy to port to another board.(example based on cmsis headers from ST, board is a STM32F103 blue pill)Code:int main(){GPIO::Pin<GPIOC_BASE, 13> led;while(1){led.set();led.clear();}}No explanation needed whats going onThe class:Code:namespace GPIO{template<uint32_t P, uint8_t p>class Pin{public:void set(){memory<uint32_t>(P + offsetof(GPIO_TypeDef, BSRR)) = BIT(p);}void clear(){memory<uint32_t>(P + offsetof(GPIO_TypeDef, BSRR)) = BIT(p) << 16;}};}Two helper functions:Code:template<typename T>volatile T &memory(const size_t loc)noexcept{return *reinterpret_cast<T*>(loc);}constexpr auto BIT(const unsigned long i)noexcept{return 1 << i;}When compiled (-Os) gives the following assembler:Code:main:mov r1, #8192mov r2, #536870912ldr r3, .L3.L2:str r1, [r3, #16]str r2, [r3, #16]b .L2.L3:.word 1073811456AFAIK is this the most optimal result, that cannot be beaten by any other code.. or?

author: cv007
author_rank: Frequent Contributor
timestamp: December 17, 2019, 01:38:13 pm
content: Most optimal if you don't want the led to blink. Maybe your model is different, but the one I have needs the mode set and the port clock enabled.I received an stm32g031k8 nucleo32 and had a compiler explorer page I was playing around with while deciding whether I wanted to install another ide-https://godbolt.org/z/g62ezafired up stm32 whatever ide (eclipse variation) on a spare pc, created a few files from the above, and had a blinky light on the first try. I'm not fond of eclipse for some reason, and its 4 pixel wide scroll bars are not helping, but getting a blinky going with just the datasheet and some c++ is a good thing. Maybe it goes downhill from here.I have variations of the same thing for a pic32mm, avr0/1, and a samd10.Not that anyone cares, but a variation where the pin alternate functions are grouped into the pin info-https://godbolt.org/z/2_nhJRso you can have each pin in the PINS namespace store information about each pin (in a struct), such as what the alternate function names/values are, default properties, etc. You are then unable to use an alternate function name for something that does not apply to the pin as the alternate function is an enum as well as the altFunc function argument type. Since these are constant expressions, there is no extra cost in using them.

author: JohnnyBerg
author_rank: Frequent Contributor
timestamp: December 17, 2019, 02:17:38 pm
content: Quote from: cv007 on December 17, 2019, 01:38:13 pmI left setting up port clock and pin mode out for clarity.I tried to use provided header files, which is far from ideal. On the other hand, defining the structure is also a lot of work and error prone.Looking at svdconv, there is a possibility to generate a header in various styles from a svd file. I wonder if anyone has tried to generate some sensible c++ out of a svd file?

author: JohnnyBerg
author_rank: Frequent Contributor
timestamp: December 19, 2019, 11:13:55 am
content: Quote from: cv007 on December 17, 2019, 01:38:13 pmHmm .. embedded is mostly the domain of hardware guys, writing firmware using -O0 as a compiler option.Do not expect applause here

author: ogden
author_rank: Super Contributor
timestamp: December 19, 2019, 11:31:44 am
content: Quote from: jhpadjustable on December 07, 2019, 07:29:34 pmNah. Cube's main goal is to make "Hello world" design more efficientMost vendor-supplied (not only ST) free libraries are in "use at your own risk" category, I would not use such for professional work and rather go in-house or 3rd party solution. DIY "products" obviously are fine with such.

author: cv007
author_rank: Frequent Contributor
timestamp: December 19, 2019, 01:25:59 pm
content: >Hmm .. embedded is mostly the domain of hardware guys, writing firmware using -O0 as a compiler option.Do not expect applause hereHave no idea what you are talking about, I use nothing but -Os. I'm not sure what domain I'm in, or what domain I'm supposed to be in, but I seem to be dealing with hardware and software and probably am in no single category.The links should already have that info--Os -mcpu=cortex-m0plus -std=c++14Another variation, where the pin properties can be used in the constructor, and in any order or not at all-https://godbolt.org/z/Xs39xDGpio<PC6, LOWISON> led(SPEEDHI, OUTPUT);Gpio<PA0, LOWISON> sw1(INPUT, PULLUP);Gpio<PA1> led2(OUTPUT);Gpio<PA2> analog1;Gpio<PA3, LOWISON> pump(ODRAIN, PULLUP, OUTPUT);The 2 template parameters need to remain as template parameters (second has a default value) as that info is needed in the functions (the template parameters are carried around wherever the var name goes, so no storage of that info is needed).If you miss all the macro work, in c++ you can replace all that fun with a different fun- templates....and another variation, where only one template parameter needed-https://godbolt.org/z/JVJVXythe pin is the only template parameter, and each pin has an alternate name with L added to it for LOWISON-Gpio<PC6L> led(SPEEDHI, OUTPUT);Gpio<PA0L> sw1(INPUT, PULLUP);Gpio<PA1> led2(OUTPUT);Gpio<PA2> analog1;Gpio<PA3L> pump(ODRAIN, PULLUP, OUTPUT);The LOWISON is needed for the on/off/isOn/isOff functions, so you do not need to keep track of what value is on/off. When created, you specify whether low is on or not, and when used you just need to do on()/off() and no longer need to know what on/off actual values are-led.on(); //whether that is high/low, don't need to know as it was already specified at 'led' creation (PC6L)

author: rvalente
author_rank: Frequent Contributor
timestamp: December 19, 2019, 02:10:34 pm
content: Tks guy, you helped me a lot!

author: twiddle
author_rank: Contributor
timestamp: December 20, 2019, 10:55:12 pm
content: Quote from: JohnnyBerg on December 17, 2019, 02:17:38 pmI use python and the cmsis-svd library to generate cppreg-based headers from the svds.Generated headers look likeCode:struct AES {using AES_pack = cppreg::RegisterPack<0x50060000,0x400>;//control registerstruct CR : cppreg::PackedRegister<AES_pack, cppreg::RegBitSize::b32, 8 *0x0> {using DMAOUTEN = cppreg::Field<CR,1,0xc,cppreg::read_write>; //Enable DMA management of data output phaseusing DMAINEN = cppreg::Field<CR,1,0xb,cppreg::read_write>; //Enable DMA management of data input phaseusing ERRIE = cppreg::Field<CR,1,0xa,cppreg::read_write>; //Error interrupt enableusing CCFIE = cppreg::Field<CR,1,0x9,cppreg::read_write>; //CCF flag interrupt enableusing ERRC = cppreg::Field<CR,1,0x8,cppreg::read_write>; //Error clearusing CCFC = cppreg::Field<CR,1,0x7,cppreg::read_write>; //Computation Complete Flag Clearusing CHMOD = cppreg::Field<CR,2,0x5,cppreg::read_write>; //AES chaining modeusing MODE = cppreg::Field<CR,2,0x3,cppreg::read_write>; //AES operating modeusing DATATYPE = cppreg::Field<CR,2,0x1,cppreg::read_write>; //Data type selectionusing EN = cppreg::Field<CR,1,0x0,cppreg::read_write>; //AES enable};<snip>};Driver code looks like :Code:template <typename Peripheral>class TADC{public:static void Enable(){RCC::APB2ENR::ADCEN::set();Peripheral::CR::ADEN::set();//wait for adrdy flag}static void Disable(){Peripheral::CR::ADDIS::set();//wait for ADDIS to clear}static void Start(){Peripheral::CR::ADSTART::set();}application code:Code:TADC<ADC>::EnableChannel<15>();TADC<ADC>::Enable();TADC<ADC>::SetSampleTime(FieldValues<ADC::SMPR::Data>::SAMPLETIME_19CYCLE5);TADC<ADC>::Calibrate();Possibly more verbose than people like, and I know everybody likes to hate on templates (many likely wouldn't consider this *sensible* C++), but the generated assembly is equal to or better than the CMSIS equivalent (some performance comparisons are @https://github.com/sendyne/cppreg/blob/master/Performance.md) and I gain the benefits of more compile-time verification, compile-time enforcement of readonly/writeonly registers, the ability to merge a number of writes into a single operation, etc etc.Of course the issue here is that different vendors tend to mess their svd files up in various ways. NXP like to leave out the core registers and only include their peripherals, for example, there's sometimes fields missing in STM svds. So this approach is only as good as the SVD files.For the smaller projects I work on, where I have fun trying to make the compiler do as much verification and testing/checking for me as I can, it's been very good, and allows me to avoid having to jump through hoops with C linkage for my interrupt handlers etc (with my framework, there's no dependency on CMSIS headers and the reset handler is the only thing requiring C linkage in the entire project).

author: JohnnyBerg
author_rank: Frequent Contributor
timestamp: December 21, 2019, 11:18:29 am
content: Quote from: twiddle on December 20, 2019, 10:55:12 pmVery nice! I'm trying to put some c++ together to do the same thing. When presentable, I'll put it on github.I've been struggling with the interrupt vectors, but did not find a viable solution. So I am still jumping though C hoops. Any pointers in the right direction?

author: twiddle
author_rank: Contributor
timestamp: December 21, 2019, 08:09:12 pm
content: It depends on which bit you're struggling with - if you're having trouble placing your table at the right location you can use attributes to forcibly place it in a 'vector table' section defined in your linker script.Once you've got it in the right spot its just a matter of doing whatever you want to construct a contiguous array (so use a C array or std::array) containing the function pointers for your table.

author: taemun
author_rank: Regular Contributor
timestamp: July 30, 2020, 05:31:13 am
content: If someone else finds this on Google in the future, here's a half-step solution to the problem, allowing reuse of the STM32Cube provided functions and auto-generated variables:Code:#define OUT_LOW(var) HAL_GPIO_WritePin(var##_GPIO_Port, var##_Pin, 0)#define OUT_HIGH(var) HAL_GPIO_WritePin(var##_GPIO_Port, var##_Pin, 1)#define IN(var) HAL_GPIO_ReadPin(var##_GPIO_Port, var##_Pin)#define OUT_TOGGLE(var) HAL_GPIO_TogglePin(var##_GPIO_Port, var##_Pin)Can be used like so:Code:OUT_HIGH(ADC_START); // start conversionwhile (IN(ADC_DRDY)) {} // wait until conversion is completeOUT_LOW(ADC_START);

author: grantb5
author_rank: Regular Contributor
timestamp: August 09, 2020, 10:41:05 pm
content: Another noob here just curious about your thoughts of doing it like below. I'm just working through a book that uses the demo version of Keil. In the before time, like the OP, my statement would look like this (8051 C code):Code:sbit pLCD_E = P1^7; //  LCD active-high enable.:pLCD_E = 1; // E high.pLCD_E = 0; // E low again.STM32: So I don't know if "inline" buys me anything with Keil C (not C++), but here is a function that's kind of equivalent. I'm not sure how to do this in a macro.Code:#define LCD_CTRLPORT GPIOB // LCD control pins on Port B#define LCD_EN 5:static void pLCD_E(uint32_t lvl){LCD_CTRLPORT->BSRR = ((uint32_t)1<<LCD_EN) << (16 * !lvl);}:pLCD_E(1);pLCD_E(0);

author: ataradov
author_rank: Super Contributor
timestamp: August 09, 2020, 10:57:20 pm
content: With inline there is a chance that compiler would actually optimize things. Since you are calling it with constants as arguments, it is way better to have it inlined so there will be a trivial amount of code inlined for each possible argument value. In case if function is not inlined, compiler may assume that lvl can be any value, not just 0 or 1, and will generate an actual multiplication code.Also, it is better to just have two version - one for set and one for clear. It makes no difference for the code, but avoids all the math. And in that case just use BOP and BC instead of BSRR.Also, seriously look at my macros, they are really good for this stuff.

author: grantb5
author_rank: Regular Contributor
timestamp: August 09, 2020, 11:39:41 pm
content: Thanks. That's interesting. I was having a look at stdbool.h but didn't investigate what that would do yet, if it would even let me.The assembly code generated varies remarkably depending on the optimization. The implementation is definitely more bizarre than I expected. Almost thinking that an assembler module or function might be better, but with the speed and code size beyond what I'm used to, it would only be a mental exercise.Edit: A multiplication would only add,what, a cycle? Hard to wrap my head around just leaving things as 32-bit where before, even 8 would be a waste.

author: ataradov
author_rank: Super Contributor
timestamp: August 09, 2020, 11:41:30 pm
content: stdbool.h will not do anything useful here.There is no need for assembly, just don't interfere with the compiler's ability to optimize and you will get code as optimal as anything you can write by hand.

author: grantb5
author_rank: Regular Contributor
timestamp: August 14, 2020, 12:14:59 am
content: It took a while to find a function or macro that Keil wouldn't turn into something crazy. Turns out that simplest was best. I like to have my bit-banging determinable and symmetric where possible. This worked out really well for just setting or clearing a pin. I have the optimization off and will probably leave it that way, at least for this module.Code:#define pLCD_E(lvl) LCD_CTRLPORT->BSRR = ((uint32_t)1<<LCD_EN) << (16 * !lvl)It basically loads the constant for the BSRR address in a register and the constant for the bit in another, and Bob's your uncle. The only variation in execution speed is just if that address is already in a register. (And yes, interrupts are/will be OK).Putting it in a function or other cleverness just messes it up. Now, as I expected, I need NOP's to meet the timing requirements. The 48MHz 8051 needed them, so when I saw my STM32 sucking horribly speed-wise I knew that something wasn't right.Thanks for help and encouragement. I've never wasted so much time on a couple of pins haha. The abstraction of the libraries might be OK. Now that I understand what's possible I can evaluate the trade-offs.

author: cv007
author_rank: Frequent Contributor
timestamp: August 14, 2020, 04:32:00 am
content: You may want to note that the BRR register has a purpose. When using a pin bitmask to set a pin via BSRR, its counterpart to clear a pin (BRR) can use the same bitmask (assuming setting/clearing are relatively close to each other in the code). The instruction set will favor using an offset register rather than getting bits into the upper bytes, so having the same bitmask can be an advantage.You can compare asm output so which is 'better'-https://godbolt.org/z/nne4xENot much of a game changer, but it doesn't hurt to realize there can be a better option. Maybe not all stm32 have both registers but I would suspect most do.

author: grantb5
author_rank: Regular Contributor
timestamp: August 14, 2020, 01:29:49 pm
content: Thanks, that's an interesting site! I will definitely dig deeper there.The "Blue Pill" STM32F103 does have the BRR. I am using Keil for now. I'm just learning this MPU, so I took the easy route for toolchain installation (on Windows). Someone also pointed out that part of the Peripheral space is bit addressable too ("bit-banding" - how I hate that term).I am working through the book,The STM32F103 Arm Microcontroller and Embedded Systems: Using Assembly and C, and the LCD chapter was the first one that had any meaningful code for real life. Previously it was purely educational stuff. I didn't really care for the solution in the textbook, so I branched out. I do recommend this book so far. You have to start someplace and this is decent.