Ответ, предоставленный @berendi, и комментарий @P__J__ уже весьма полезны, но я хотел предоставить более глубокое понимание.Для необработанного («голого») отслеживания регистров GPIO STM32F103CB для чтения и записи без библиотек или заголовочных файлов перейдите прямо к основанию. Цель этого ответа состоит в том, чтобы * научить вас *, как самостоятельно читать таблицы данных и документацию, чтобы вы могли применять эти методы для * любого адреса памяти или для регистрации * в * любом микроконтроллере *, включая STM32.
Обратите внимание, что приведенный ниже пример «необработанный, без заголовка» предназначен для образовательных целей: я рекомендую использовать только заголовочные файлы, предоставляемые CMSIS и STM32, в зависимости от обстоятельств, а не писать свои собственные.Однако в некоторых случаях вам может понадобиться быстро получить доступ к регистру, и вот как.
Краткое руководство:
Определение ЛЮБОГО адреса для чтения / записи:
#define MY_REGISTER (*(volatile uint32_t *)(ADDRESS_TO_MY_REGISTER)) // Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
Определите ЛЮБОЙ адрес, который будет доступен только для чтения:
#define MY_REGISTER (*(volatile const uint32_t *)(ADDRESS_TO_MY_REGISTER)) // Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
Подробности: как определить любое расположение адреса или зарегистрировать в памяти в C, чтобы он был читаемым /Writable:
Стандартный (и единственный способ) получить доступ к любому адресу памяти в C - это использовать следующую конструкцию энергозависимого указателя на #define
:
#define MY_REGISTER (*(volatile uint32_t *)(ADDRESS_TO_MY_REGISTER)) // Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
КакПрочитайте это:
(по сути, чтение справа налево): «Возьмите ADDRESS_TO_MY_REGISTER и приведите его к указателю на энергозависимую группу из 4 байтов (то есть: группу из 4 энергозависимых байтов), затем возьмите содержимое этой группы из 4 байтов и сделать то, что означает MY_REGISTER
. "то есть: MY_REGISTER теперь изменяет содержимое памяти в этом месте адреса.
Приведение к указателю требуется для преобразования местоположения адреса в фактический адрес памяти (указатель), а разыменование (*
) в крайнем левом углу заставляет нас изменять содержимое , которые регистрируют или запоминают по этому адресу, а не просто изменяют сам адрес.Ключевое слово volatile
требуется для предотвращения оптимизации компилятора, которая в противном случае может попытаться предположить, что находится в этом регистре, и оптимизировать ваш код, который читает или записывает данные из этого регистра или в этот регистр.volatile
всегда требуется при доступе к регистрам, поскольку следует предполагать, что они могут быть изменены другими процессами, внешними событиями или изменениями выводов, или аппаратными средствами и / или периферийными устройствами в самом mcu.
Несмотря на то, что эта конструкция работает на всех устройствах в C (не только STM32), обратите внимание, что размер типа, который вы приводите (uint8_t
, uint32_t
и т. Д.), Важен для вашей архитектуры.То есть: НЕ пытайтесь использовать типы uint32_t
на 8-битном микроконтроллере, потому что, хотя это может показаться работоспособным, атомарный доступ к 32-битному фрагменту памяти на 8-битном микроконтроллере НЕ гарантируется.Однако 8-битный доступ к 8-битному микроконтроллеру AVR фактически гарантированно будет автоматически атомарным (связанное чтение: C ++ уменьшение элемента однобайтового (энергозависимого) массива не является атомарным! ПОЧЕМУ? (Также: как заставить атомарность в Atmel AVR mcus / Arduino) ).Однако для mcu STM32 32-битный или меньший доступ к памяти автоматически становится атомарным, как я исследовал и описал здесь: https://stackoverflow.com/a/52785864/4561887.
Этот тип конструкции на основе #define
вышеиспользуется всеми микроконтроллерами повсюду, и вы можете использовать его для произвольного доступа к любой ячейке памяти, которую вы считаете нужным, буквально, на любом микроконтроллере, если в техническом описании и / или справочном руководстве не указано иное (например, некоторые регистры сначала требуют специальных инструкций по разблокировке).Если вы, например, проследите регистры на AVRLibc (используется Arduino - скачайте здесь: https://www.nongnu.org/avr-libc/ -> раздел «Загрузки»), и выполните все расширения макросов, вы увидите, что все регистры8 бит и сводятся к следующему:
#define TCCR2A (*(volatile uint8_t *)(0xB0))
Здесь регистр TCCR2A
или «Регистр управления счетчиком таймера A для таймера 2» устанавливается равным 1 байту по адресу 0xB0.
То же самое относится и к STM32, за исключением того, что регистры, как правило, 32-битные, поэтому вы можете использовать uint32_t
вместо uint8_t
(хотя uint8_t
также работает на STM32), и они часто используют struct-основанные конструкции вместоНапример, "stm32f767xx.h" :
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
Где GPIO_TypeDef
является структурой:
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
И __IO
определяется просто как volatile
.Поскольку каждый член этой структуры имеет 4 байта, и у вас есть 4 байта выравнивания, структура автоматически упаковывается, поэтому вы в конечном итоге получаете каждый новый элемент структуры, просто указывающий на адресное местоположение «Смещение адреса» (как показано накомментарии справа) дальше от базового адреса, так что все работает!
Альтернативой использованию, например, определенной в STM32 конструкции типа GPIOD->BSRR
, было бы сделать это вручную, например, так:
#define GPIOD_BSRR (*(volatile uint32_t *)(GPIOD_BASE + 0x18UL)) // Don't forget the `U` or `UL` at the end of 0x18 here!
Что если вы хотите сделать регистр доступным только для чтения?Просто добавьте const
в любом месте к влево из *
в приведении к указателю:
#define MY_REGISTER (*(volatile const uint32_t *)(ADDRESS_TO_MY_REGISTER)) // Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
Получение, настройка и очистка битов:
Теперь вы можете установить или получить любой бит в регистре, используя битовое смещение и битовые маски и битовые манипуляции, или используя некоторые макросы, которые вы можете определить.
Пример:
// get bit30 from the address location you just described above
bool bit30 = (MY_REGISTER >> 30UL) & 0x1UL;
// or (relies on the fact that anything NOT 0 in C is automatically `true`):
bool bit30 = MY_REGISTER & (1UL << 30UL);
// set bit 30 to 1
MY_REGISTER |= (1UL << 30UL);
// clear bit 30 to 0
MY_REGISTER &= ~(1UL << 30UL);
ИЛИ: (Например, как Arduino делает здесь: https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Arduino.h)
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
Затем:
// get bit 30
bool bit30 = bitRead(MY_REGISTER, 30);
// set bit 30 to 1
bitSet(MY_REGISTER, 30);
// or
bitWrite(MY_REGISTER, 30, 1);
// clear bit 30 to 0
bitClear(MY_REGISTER, 30);
// or
bitWrite(MY_REGISTER, 30, 0);
Необработанное («голое железо») отслеживание регистров чтения и записи GPIO STM32F103CB без библиотек или заголовочных файлов.
Нам понадобится:
- Основная справочная страница для этого чипа: https://www.st.com/content/st_com/en/products/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus/stm32-mainstream-mcus/stm32f1-series/stm32f103/stm32f103cb.html#design-scroll
- Справочное руководство STM32 (содержит определения регистров): RM0008, Справочное руководство для STM32F101xx, 101xx, 103xx и т. Д.
- Таблица данных STM32 (содержит базуадреса): DS5319
Читать RM0008 p161-162. ![enter image description here](https://i.stack.imgur.com/qS868.png)
Я не буду вдаваться во все детали (см. выше), но для чтения для вывода требуется GPIOx_IDR
(регистр входных данных GPIO). Чтобы записать вывод для 0 или 1, вам необходимо GPIOx_ODR
(регистр выходных данных GPIO).в соответствии с формулировкой в RM0008, как показано выше), записи в GPIOx_ODR
не являются атомарными как группа, поэтому, если вы хотите, чтобы группа выводов на порту была записана атомарно (все в том жене забудьте использовать GPIOx_BSRR
(регистр установки / сброса битов GPIO) или GPIOx_BRR
(регистр сброса битов GPIO - можно сбросить биты только до 0).
Предполагая, что мы собираемся использовать только порт A, это означает, что нам нужны определения для следующих регистров:
GPIOA_IDR // Input Data Register (for reading pins on Port A)
GPIOA_ODR // Output Data Register (for *nonatomically* writing 0 or 1 to pins on Port A)
GPIOA_BSRR // Bit Set/Reset Register (for *atomically* setting (writing 1) or resetting (writing 0) pins on Port A)
GPIOA_BRR // Bit Reset Register (for *atomically* resetting (writing 0) pins on Port A)
Давайте найдем адреса для этих регистров!
См. RM0008 p172 до 174.
![enter image description here](https://i.stack.imgur.com/xi3e6.png)
![enter image description here](https://i.stack.imgur.com/Nr4vs.png)
![enter image description here](https://i.stack.imgur.com/4UPxM.png)
Мы можем видеть смещения и направление данных следующим образом:
| Register | "Address offset"| direction
|------------|-----------------|---------------
| GPIOA_IDR | 0x08 | r (read only)
| GPIOA_ODR | 0x0C | rw (read/write)
| GPIOA_BSRR | 0x10 | w (write only)
| GPIOA_BRR | 0x14 | w (write only)
Теперь нам просто нужен базовый адрес для порта A. Перейдите к DS5319 Глава 4: Отображение памяти, Рисунок 11. Карта памяти , и вы увидите, что базовый адрес для «порта А» равен 0x40010800 , как показано и выделено здесь:
![enter image description here](https://i.stack.imgur.com/6DSC2.png)
Теперь давайте вручную определим регистры:
#define GPIOA_IDR (*(volatile const uint32_t *)(0x40010800UL + 0x08UL)) // use `const` since this register is read-only
#define GPIOA_ODR (*(volatile uint32_t *)(0x40010800UL + 0x0CUL))
#define GPIOA_BSRR (*(volatile uint32_t *)(0x40010800UL + 0x10UL))
#define GPIOA_BRR (*(volatile uint32_t *)(0x40010800UL + 0x14UL))
Теперь давайте прочитаем и напишем пин-код:
// Choose a pin number from 0 to 15
uint8_t pin_i = 0; // pin index
// Read it
bool pin_state = (GPIOA_IDR >> (uint32_t)pin_i) & 0x1;
// Write it to 1
GPIOA_ODR |= 1UL << (uint32_t)pin_i; // not to be used for writing to more than 1 pin at a time since apparently its operation is not atomic?
// OR
GPIOA_BSRR |= 1UL << (uint32_t)pin_i; // GOOD! RECOMMENDED approach
// Write it to 0
GPIOA_ODR &= ~(1UL << (uint32_t)pin_i); // not to be used for writing to more than 1 pin at a time since apparently its operation is not atomic?
// OR
GPIOA_BSRR |= (1UL << (uint32_t)pin_i) << 16UL; // GOOD! RECOMMENDED approach, but a bit confusing obviously
// OR
GPIOA_BRR |= 1UL << (uint32_t)pin_i; // GOOD! RECOMMENDED approach
ИЛИ: просто используйте HALбиблиотеки и все готово.
Пример: из "STM32Cube_FW_F1_V1.6.0 / Drivers / STM32F1xx_HAL_Driver / Src / stm32f1xx_hal_gpio.c":
HAL_GPIO_ReadPin()
(обратите внимание, что для чтения используется регистр GPIOx->IDR
):
/**
* @brief Reads the specified input port pin.
* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
* @param GPIO_Pin: specifies the port bit to read.
* This parameter can be GPIO_PIN_x where x can be (0..15).
* @retval The input port pin value.
*/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
GPIO_PinState bitstatus;
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
{
bitstatus = GPIO_PIN_SET;
}
else
{
bitstatus = GPIO_PIN_RESET;
}
return bitstatus;
}
HAL_GPIO_WritePin()
(обратите внимание, что они используют регистр GPIOx->BSRR
для записи булавки в 0 и 1):
/**
* @brief Sets or clears the selected data port bit.
*
* @note This function uses GPIOx_BSRR register to allow atomic read/modify
* accesses. In this way, there is no risk of an IRQ occurring between
* the read and the modify access.
*
* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
* @param GPIO_Pin: specifies the port bit to be written.
* This parameter can be one of GPIO_PIN_x where x can be (0..15).
* @param PinState: specifies the value to be written to the selected bit.
* This parameter can be one of the GPIO_PinState enum values:
* @arg GPIO_BIT_RESET: to clear the port pin
* @arg GPIO_BIT_SET: to set the port pin
* @retval None
*/
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if(PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
}
}
END