I programmed STM8 GPIO like PD_ODR_ODR4 = 1;
but stm32f10x.h doesn't have this function.Is there any .h file that has definition for bits.
Sorry but I don't know how to explain this problem better.
I tried multiple GPIO libraries.
strong text
I programmed STM8 GPIO like PD_ODR_ODR4 = 1;
but stm32f10x.h doesn't have this function.Is there any .h file that has definition for bits.
Sorry but I don't know how to explain this problem better.
I tried multiple GPIO libraries.
strong text
You mention
stm32f10x.h
in the question, so I'm assuming it's about the STM32F1 series of controllers. Other series have some differences, but the general procedure is the same.GPIO pins are arranged in banks of 16 called ports, each having it's own set of control registers, named
GPIOA
,GPIOB
, etc. They are defined as pointers toGPIO_TypeDef
structures. There are 3 control registers that affect pin outputs.Writing
ODR
sets all 16 pins at once, e.g.GPIOB->ODR = 0xF00F
sets pinsB0
throughB3
andB12
throughB15
to 1, andB4
throughB11
to 0, regardless of their previous state. One can writeGPIOD->ODR |= (1<<4)
to set pinGPIOD4
to 1, orGPIOD->ODR &= ~(1<<4)
to reset it.Writing
BSRR
treats the value written as two bitmasks. The low halfword is the set mask, bits with value 1 set the corresponding bit inODR
to 1. The high halfword is the reset mask, bits with value 1 set the corresponding bit inODR
to 0.GPIOC->BSRR = 0x000701E0
would set pinsC5
thoughC8
to 1, resetC0
throughC2
to 0, and leave all other port bits alone. Trying to both set and reset the same bit when writingBSRR
, then it will be set to 1.Writing
BRR
is the same as writing the reset bitmask inBSRR
, i.e.GPIOx->BRR = x
is equivalent toGPIOx->BSRR = (x << 16)
.Now it's possible to write some macros like
to change single pins, but it's not as flexible as it could be, e.g. you cannot take the address of a single pin and pass it around in variables.
Bit Banding
Cortex-M controllers (not all of them, but the
STM32F1
series do) have this feature to make individual bits in internal RAM and in hardware registers addressable. Each bit in the0x40000000-0x400FFFFF
range is mapped to a full 32-bit word in the0x42000000-0x43FFFFFF
range. It doesn't work with peripherals outside this address range, like USB or NVIC.The bit-banding address of a peripheral register can be calculated with this macro
and you can treat the resulting pointer as the base of an array holding 32 words, each corresponding to a single bit in the peripheral registers. Now it's possible to
and use it in assignments. Reading it will give 0 or 1 as its value, values written to it copy the least significant bit of the written value to the peripheral register bit. You can even take its address, and pass it to a function that does something with the pin.
Bit-banding is documented in PM0056 Cortex®-M3 programming manual.
The answer provided by @berendi, and comment by @P__J__ are already quite helpful, but I wanted to provide more insight. For the raw (bare-metal) track-down of an STM32F103CB's GPIO read and write registers, with NO libraries or header files, jump straight to the bottom. The purpose of this answer is to *teach you* how to read datasheets and documentation yourself so you can apply these techniques to *any memory address or register* in *any microcontroller whatsoever*, including STM32.
Note that the "raw, no-header whatsoever" example at the bottom is for educational purposes: I recommend just using the header files provided by CMSIS and STM32, as applicable, rather than writing your own. However, in some circumstances you may need to quickly get access to a register, and this is how.
Quick Reference:
Define ANY address to be readable/writable:
Define ANY address to be readable only:
Details: how to define any address location or register in memory in C so that it is readable/writable:
The standard (and only way really) way to access any memory address location in C is to use the following
#define
-based volatile pointer construct:How to read this:
(Essentially reading right to left): "Take the ADDRESS_TO_MY_REGISTER and cast it to a pointer to a volatile group of 4 bytes (ie: a group of 4 volatile bytes), then grab the contents of that group of 4 bytes, and make that what
MY_REGISTER
means." ie: MY_REGISTER now modifies the contents of the memory at this address location.The cast to a pointer is required to convert the address location to an actual memory address (a pointer), and the dereference (
*
) at the far left is to make us modify the contents of that register or memory at that address, rather than just modifying the address itself. The keywordvolatile
is required to prevent compiler optimization which might otherwise try to assume what is in that register and optimize out your code which reads or writes from or to that register.volatile
is always required when accessing registers, as one must assume they can be changed from other processes, external events or pin changes, or the hardware and/or peripherals in the mcu itself.Even though this construct works on all devices in C (not just STM32), note that the size of the type you cast to (
uint8_t
,uint32_t
, etc) is important for your architecture. ie: do NOT try to useuint32_t
types on an 8-bit microcontroller because even though it might seem to work, atomic access to a 32-bit chunk of memory on an 8-bit microcontroller is NOT guaranteed. 8-bit accesses on an 8-bit AVR microcontroller, however, are in fact guaranteed to be automatically atomic (related reading: C++ decrementing an element of a single-byte (volatile) array is not atomic! WHY? (Also: how do I force atomicity in Atmel AVR mcus/Arduino)). For an STM32 mcu, however, 32-bit or smaller memory accesses are automatically atomic, as I've researched and described here: https://stackoverflow.com/a/52785864/4561887.This type of
#define
-based construct above is used by all microcontrollers everywhere, and you can use it to arbitrarily access any memory location you see fit, literally, on any microcontroller, unless the datasheet and/or Reference Manuals state otherwise (ex: some registers require special unlocking instructions first). If you trace down the registers on AVRLibc, for instance (used by Arduino--download here: https://www.nongnu.org/avr-libc/ --> "Downloads" section), and do all the macro expansions, you'll see that all registers are 8-bits and boil down to this:Here, register
TCCR2A
, or "Timer Counter Control Register A for Timer 2", is set to be 1-byte at address 0xB0.The same is true of STM32, except that the registers are generally 32-bits instead, so you can use
uint32_t
instead ofuint8_t
(althoughuint8_t
also works on STM32), and they frequently use struct-based constructs instead. Ex from "stm32f767xx.h":Where
GPIO_TypeDef
is a struct:And
__IO
is defined simply asvolatile
. Since each member of this struct is 4-bytes, and you have 4-byte alignment, the struct is automatically packed, so you end up with each new element of the struct simply pointing to the address location "Address offset" (as shown in the comments to the right) farther from the base address, so everything works out!An alternative to using the STM32-defined
GPIOD->BSRR
type construct, for instance, would be to manually do it yourself like this:What if you want to make a register read-only? Simply add
const
anywhere to the left of the*
in the cast-to-a-pointer:Getting, setting, & clearing bits:
Now, you can set or get any bit in the register using bit-shifting and bitmasks and bit manipulation, or using some macros you might define.
Ex:
OR: (Ex, as Arduino does here: https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Arduino.h)
Then:
Raw (bare-metal) track-down of an STM32F103CB's GPIO read and write registers, with NO libraries or header files.
We will need:
Read RM0008 p161-162.
I won't go into all the details (read above), but to read a pin you need the
GPIOx_IDR
(GPIO Input Data Register). To write a pin to 0 or 1 you needGPIOx_ODR
(GPIO Output Data Register). Apparently (based on the wording in RM0008 as shown above) the writes toGPIOx_ODR
are not atomic as a group, so if you want a bunch of pins on a port to be written atomically (all in the same instant) you need to use theGPIOx_BSRR
(GPIO Bit Set/Reset Register) or theGPIOx_BRR
(GPIO Bit Reset Register--can clear bits to 0 only) instead.Assuming we are only going to do Port A, that means we need definitions for the following registers:
Let's go find the addresses to these registers!
See RM0008 p172 to 174.
We can see the offsets and data direction are as follows:
Now we just need the base address for Port A. Go to DS5319 Chapter 4: Memory mapping, Figure 11. Memory Map, and you'll see that the base address for "Port A" is 0x40010800, as shown and highlighted here:
Now, let's manually define the registers:
Now let's read and write a pin:
OR: just use the HAL libraries and be done.
Ex: from "STM32Cube_FW_F1_V1.6.0/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio.c":
HAL_GPIO_ReadPin()
(notice they use theGPIOx->IDR
register for reading):HAL_GPIO_WritePin()
(notice they use theGPIOx->BSRR
register for writing a pin to both 0 and 1):END