Author Topic: STM32 macro for port and pin read and write  (Read 15080 times)

0 Members and 1 Guest are viewing this topic.

Offline rvalenteTopic starter

  • Frequent Contributor
  • **
  • Posts: 726
  • Country: br
STM32 macro for port and pin read and write
« on: December 07, 2019, 03:26:01 pm »
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: [Select]
RA0 = 0; //Just like pics do
Code: [Select]
digitalWrite(13, 1); //Just like arduinos
BANG! DONE!

On STM32:

Code: [Select]
HAL_GPIO_WritePin(DO0_GPIO_Port, DO0_Pin, 1); //ohhh, c'mon, boring, makes passing pins to functions as arguments twice painful
 
The following users thanked this post: grantb5

Offline JohnnyBerg

  • Frequent Contributor
  • **
  • Posts: 474
  • Country: de
Re: STM32 macro for port and pin read and write
« Reply #1 on: December 07, 2019, 04:14:22 pm »
Not a macro, but a simple C++ solution:

Code: [Select]
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); }
};

 

Offline rvalenteTopic starter

  • Frequent Contributor
  • **
  • Posts: 726
  • Country: br
Re: STM32 macro for port and pin read and write
« Reply #2 on: December 07, 2019, 04:17:08 pm »
Not a macro, but a simple C++ solution:

Code: [Select]
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); }
};



Thats nice but, i'm using C not C++

This hole trouble is due to me converting the sevseg library from arduino to stm32
 

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: STM32 macro for port and pin read and write
« Reply #3 on: December 07, 2019, 04:42:21 pm »
.
« Last Edit: August 19, 2022, 02:41:37 pm by emece67 »
 
The following users thanked this post: thm_w

Offline rvalenteTopic starter

  • Frequent Contributor
  • **
  • Posts: 726
  • Country: br
Re: STM32 macro for port and pin read and write
« Reply #4 on: December 07, 2019, 04:46:40 pm »
First:
Code: [Select]
#ifndef PINOUT_BLUE_PILL_H
#define PINOUT_BLUE_PILL_H

#include "GPIO_STM32f10x.h"

typedef struct {
  GPIO_TypeDef * const  port;
  uint8_t               pos;
} pin_t;

#define SIZE_OF_PIN                 (sizeof(pin_t))
                                   
#define PIN_BLUE_PILL_PA0           ((pin_t const){GPIOA, 0U})
#define PIN_BLUE_PILL_PA1           ((pin_t const){GPIOA, 1U})
                                   
...                                 

#define PIN_BLUE_PILL_WKUP                (PIN_BLUE_PILL_PA0)
#define PIN_BLUE_PILL_USART2_CTS          (PIN_BLUE_PILL_PA0)
#define PIN_BLUE_PILL_ADC12_IN0           (PIN_BLUE_PILL_PA0)

#define PIN_BLUE_PILL_TIM2_CH1_ETR        (PIN_BLUE_PILL_PA1)
#define PIN_BLUE_PILL_USART2_RTS          (PIN_BLUE_PILL_PA1)
#define PIN_BLUE_PILL_TIM2_CH2            (PIN_BLUE_PILL_PA1)

...

#endif  // PINOUT_BLUE_PILL_H

Then:
Code: [Select]
#define PIN_WRITE(pin, lvl)         (GPIO_PinWrite((pin).port, (pin).pos, (lvl)))
#define PIN_READ(pin)               (GPIO_PinRead((pin).port, (pin).pos))

Then, if you like:
Code: [Select]
#define LED                         (PIN_BLUE_PILL_PA0)
#define SWITCH                      (PIN_BLUE_PILL_PA1)

And finally:
Code: [Select]
PIN_WRITE(LED, 1);
a = PIN_READ(SWITCH);

Note that I'm not using the STM HAL, but cmsis, but that's the idea.

Hope this helps, regards.


Will try it, tks!
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8184
  • Country: fi
Re: STM32 macro for port and pin read and write
« Reply #5 on: December 07, 2019, 04:57:30 pm »
This is what I use:

Code: [Select]

// 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[0]; _tmp_ &= ~(0b1111UL<<((pin)*4));     _tmp_ |= af<<((pin)*4);     (port)->AFR[0] = _tmp_;} \
        else {uint32_t _tmp_ = (port)->AFR[1]; _tmp_ &= ~(0b1111UL<<(((pin)-8)*4)); _tmp_ |= af<<(((pin)-8)*4); (port)->AFR[1] = _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: [Select]
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*/;

« Last Edit: December 07, 2019, 05:00:36 pm by Siwastaja »
 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 11277
  • Country: us
    • Personal site
Re: STM32 macro for port and pin read and write
« Reply #6 on: December 07, 2019, 05:23:51 pm »
https://github.com/ataradov/mcu-starter-projects/blob/master/stm32g0/hal_gpio.h Use:
Code: [Select]
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())
  {
    // ...
  }
}
Alex
 

Offline jhpadjustable

  • Frequent Contributor
  • **
  • Posts: 295
  • Country: us
  • Salt 'n' pepper beard
Re: STM32 macro for port and pin read and write
« Reply #7 on: December 07, 2019, 07:29:34 pm »
STM32 libraries are well-known for bloat and verbosity. One of Cube's main goals is to make product design 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: [Select]
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, 1);
or use the C preprocessor to compact two parameters into one for less typing:
Code: [Select]
#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: [Select]
/* ... 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 you must be 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.
"There are more things in heaven and earth, Arduino, than are dreamt of in your philosophy."
 

Offline mark03

  • Frequent Contributor
  • **
  • Posts: 713
  • Country: us
Re: STM32 macro for port and pin read and write
« Reply #8 on: December 08, 2019, 12:25:00 am »
This is one (of many) examples where the simplicity of direct register access really shines.

GPIOB->BSRR = 1 << 4;            // set PB4 high
GPIOB->BSRR = 1 << (4+16);  // set PB4 low

Hard to see how it could get any simpler.  Although @Siwastaja's shorthand is kind of nice too.

 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 11277
  • Country: us
    • Personal site
Re: STM32 macro for port and pin read and write
« Reply #9 on: December 08, 2019, 12:28:53 am »
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.
Alex
 

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: STM32 macro for port and pin read and write
« Reply #10 on: December 08, 2019, 01:36:52 pm »
.
« Last Edit: August 19, 2022, 02:42:05 pm by emece67 »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14521
  • Country: fr
Re: STM32 macro for port and pin read and write
« Reply #11 on: December 08, 2019, 05:36:18 pm »
https://github.com/ataradov/mcu-starter-projects/blob/master/stm32g0/hal_gpio.h

Not 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.
 

Offline lucazader

  • Regular Contributor
  • *
  • Posts: 221
  • Country: au
Re: STM32 macro for port and pin read and write
« Reply #12 on: December 08, 2019, 10:37:27 pm »
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.
 

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: STM32 macro for port and pin read and write
« Reply #13 on: December 08, 2019, 11:51:25 pm »
.
« Last Edit: August 19, 2022, 02:41:50 pm by emece67 »
 

Offline donotdespisethesnake

  • Super Contributor
  • ***
  • Posts: 1093
  • Country: gb
  • Embedded stuff
Re: STM32 macro for port and pin read and write
« Reply #14 on: December 09, 2019, 09:53:55 pm »
This is one (of many) examples where the simplicity of direct register access really shines.

GPIOB->BSRR = 1 << 4;            // set PB4 high
GPIOB->BSRR = 1 << (4+16);  // set PB4 low

Hard to see how it could get any simpler.  Although @Siwastaja's shorthand is kind of nice too.

That'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: [Select]
  set_high (PB4);
  set_low (PB4);
which can be easily and efficiently achieved using macros or inline functions (preferred).
Comments not required.
Bob
"All you said is just a bunch of opinions."
 
The following users thanked this post: thm_w, rvalente, lucazader

Offline pigrew

  • Frequent Contributor
  • **
  • Posts: 680
  • Country: us
Re: STM32 macro for port and pin read and write
« Reply #15 on: December 09, 2019, 11:07:50 pm »
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....
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: STM32 macro for port and pin read and write
« Reply #16 on: December 09, 2019, 11:44:53 pm »
If you want to set a pin to a non-constant value, you can do
Code: [Select]
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.

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4204
  • Country: us
Re: STM32 macro for port and pin read and write
« Reply #17 on: December 10, 2019, 03:32:34 am »
Quote
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));
Don't do this on SET or CLEAR registers, though.  Behind the scenes, a line like:
Code: [Select]
   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...
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8184
  • Country: fi
Re: STM32 macro for port and pin read and write
« Reply #18 on: December 10, 2019, 08:40:02 am »
This is what I use:
Code: [Select]
// Outputs:
#define HI(port, idx) do{(port)->BSRR = 1UL<<(idx);}while(0)
#define LO(port, idx) do{(port)->BSRR = 1UL<<(16+idx);}while(0)

What's the utility of the do-while(0)? I understand its use in macros containing various statements, but here?

It's a stupid C quirk required in macros so that they can be used function-like, including cases like

if(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.
« Last Edit: December 10, 2019, 08:47:31 am by Siwastaja »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14521
  • Country: fr
Re: STM32 macro for port and pin read and write
« Reply #19 on: December 10, 2019, 03:38:07 pm »
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.

True. 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).

 
The following users thanked this post: Siwastaja

Offline JohnnyBerg

  • Frequent Contributor
  • **
  • Posts: 474
  • Country: de
Re: STM32 macro for port and pin read and write
« Reply #20 on: December 17, 2019, 07:51:04 am »
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: [Select]
int main()
{
    GPIO::Pin<GPIOC_BASE, 13> led;
    while(1)
    {
        led.set();
        led.clear();
    }
}

No explanation needed whats going on :)

The class:

Code: [Select]
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: [Select]
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: [Select]
main:
        mov     r1, #8192
        mov     r2, #536870912
        ldr     r3, .L3
.L2:
        str     r1, [r3, #16]
        str     r2, [r3, #16]
        b       .L2
.L3:
        .word   1073811456

AFAIK is this the most optimal result, that cannot be beaten by any other code.. or?
« Last Edit: December 17, 2019, 08:27:06 am by JohnnyBerg »
 
The following users thanked this post: thm_w, mark03

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 828
Re: STM32 macro for port and pin read and write
« Reply #21 on: December 17, 2019, 01:38:13 pm »
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/g62eza
fired 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_nhJR
so 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.

« Last Edit: December 18, 2019, 06:42:46 am by cv007 »
 
The following users thanked this post: JohnnyBerg

Offline JohnnyBerg

  • Frequent Contributor
  • **
  • Posts: 474
  • Country: de
Re: STM32 macro for port and pin read and write
« Reply #22 on: December 17, 2019, 02:17:38 pm »
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 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?
« Last Edit: December 17, 2019, 02:29:55 pm by JohnnyBerg »
 

Offline JohnnyBerg

  • Frequent Contributor
  • **
  • Posts: 474
  • Country: de
Re: STM32 macro for port and pin read and write
« Reply #23 on: December 19, 2019, 11:13:55 am »
Not that anyone cares, but a variation where the pin alternate functions are grouped into the pin info-
https://godbolt.org/z/2_nhJR


Hmm .. embedded is mostly the domain of hardware guys, writing firmware using -O0 as a compiler option.
Do not expect applause here ;)
 

Offline ogden

  • Super Contributor
  • ***
  • Posts: 3731
  • Country: lv
Re: STM32 macro for port and pin read and write
« Reply #24 on: December 19, 2019, 11:31:44 am »
One of Cube's main goals is to make product design more efficient
Nah. Cube's main goal is to make "Hello world" design more efficient ;) Most 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.
 
The following users thanked this post: jhpadjustable

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 828
Re: STM32 macro for port and pin read and write
« Reply #25 on: December 19, 2019, 01:25:59 pm »
>Hmm .. embedded is mostly the domain of hardware guys, writing firmware using -O0 as a compiler option.
Do not expect applause here ;)

Have 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++14

Another variation, where the pin properties can be used in the constructor, and in any order or not at all-
https://godbolt.org/z/Xs39xD

Gpio<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/JVJVXy

the 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)

« Last Edit: December 20, 2019, 02:09:09 am by cv007 »
 
The following users thanked this post: rvalente, emece67

Offline rvalenteTopic starter

  • Frequent Contributor
  • **
  • Posts: 726
  • Country: br
Re: STM32 macro for port and pin read and write
« Reply #26 on: December 19, 2019, 02:10:34 pm »
Tks guy, you helped me a lot!
 

Offline twiddle

  • Contributor
  • Posts: 19
  • Country: au
Re: STM32 macro for port and pin read and write
« Reply #27 on: December 20, 2019, 10:55:12 pm »
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?

I use python and the cmsis-svd library to generate cppreg-based headers from the svds.

Generated headers look like
Code: [Select]
struct AES {
using AES_pack = cppreg::RegisterPack<0x50060000,0x400>;

//control register
struct 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 phase
using DMAINEN = cppreg::Field<CR,1,0xb,cppreg::read_write>; //Enable DMA management of data input phase
using ERRIE = cppreg::Field<CR,1,0xa,cppreg::read_write>; //Error interrupt enable
using CCFIE = cppreg::Field<CR,1,0x9,cppreg::read_write>; //CCF flag interrupt enable
using ERRC = cppreg::Field<CR,1,0x8,cppreg::read_write>; //Error clear
using CCFC = cppreg::Field<CR,1,0x7,cppreg::read_write>; //Computation Complete Flag Clear
using CHMOD = cppreg::Field<CR,2,0x5,cppreg::read_write>; //AES chaining mode
using MODE = cppreg::Field<CR,2,0x3,cppreg::read_write>; //AES operating mode
using DATATYPE = cppreg::Field<CR,2,0x1,cppreg::read_write>; //Data type selection
using EN = cppreg::Field<CR,1,0x0,cppreg::read_write>; //AES enable
};
<snip>
};
Driver code looks like :
Code: [Select]
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: [Select]
        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++  :-DD), 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).
 
The following users thanked this post: hans, thm_w, JohnnyBerg

Offline JohnnyBerg

  • Frequent Contributor
  • **
  • Posts: 474
  • Country: de
Re: STM32 macro for port and pin read and write
« Reply #28 on: December 21, 2019, 11:18:29 am »

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).

Very 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?
 

Offline twiddle

  • Contributor
  • Posts: 19
  • Country: au
Re: STM32 macro for port and pin read and write
« Reply #29 on: December 21, 2019, 08:09:12 pm »
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.
 

Offline taemun

  • Regular Contributor
  • *
  • Posts: 110
  • Country: au
Re: STM32 macro for port and pin read and write
« Reply #30 on: July 30, 2020, 05:31:13 am »
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: [Select]
#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: [Select]
OUT_HIGH(ADC_START); // start conversion
while (IN(ADC_DRDY)) {} // wait until conversion is complete
OUT_LOW(ADC_START);
 

Offline grantb5

  • Regular Contributor
  • *
  • Posts: 139
  • Country: ca
Re: STM32 macro for port and pin read and write
« Reply #31 on: August 09, 2020, 10:41:05 pm »
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: [Select]
sbit pLCD_E  = P1^7;                   // [OUT] 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: [Select]
#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);




 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 11277
  • Country: us
    • Personal site
Re: STM32 macro for port and pin read and write
« Reply #32 on: August 09, 2020, 10:57:20 pm »
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.
Alex
 

Offline grantb5

  • Regular Contributor
  • *
  • Posts: 139
  • Country: ca
Re: STM32 macro for port and pin read and write
« Reply #33 on: August 09, 2020, 11:39:41 pm »
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.
« Last Edit: August 09, 2020, 11:41:31 pm by grantb5 »
 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 11277
  • Country: us
    • Personal site
Re: STM32 macro for port and pin read and write
« Reply #34 on: August 09, 2020, 11:41:30 pm »
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.

Alex
 

Offline grantb5

  • Regular Contributor
  • *
  • Posts: 139
  • Country: ca
Re: STM32 macro for port and pin read and write
« Reply #35 on: August 14, 2020, 12:14:59 am »
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: [Select]
#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.
« Last Edit: August 14, 2020, 12:18:02 am by grantb5 »
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 828
Re: STM32 macro for port and pin read and write
« Reply #36 on: August 14, 2020, 04:32:00 am »
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/nne4xE

Not 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.
 

Offline grantb5

  • Regular Contributor
  • *
  • Posts: 139
  • Country: ca
Re: STM32 macro for port and pin read and write
« Reply #37 on: August 14, 2020, 01:29:49 pm »
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.


 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf