Программирование STM32 подобно STM8(уровень регистра GPIO)
Я запрограммировал STM8 GPIO как PD_ODR_ODR4 = 1;
но stm32f10x.h не имеет этой функции. Есть ли.h файл, который имеет определение для битов.
Извините, но я не знаю, как лучше объяснить эту проблему.
Я пробовал несколько библиотек GPIO.
сильный текст
2 ответа
Вы упоминаете stm32f10x.h
в вопросе, поэтому я предполагаю, что речь идет о серии контроллеров STM32F1. Другие серии имеют некоторые отличия, но общая процедура та же.
Выводы GPIO расположены в банках по 16 называемых портов, каждый из которых имеет свой собственный набор управляющих регистров, называемых GPIOA
, GPIOB
и т. д. Они определяются как указатели на GPIO_TypeDef
структур. Есть 3 контрольных регистра, которые влияют на выводы выводов.
Пишу ODR
устанавливает все 16 контактов одновременно, например GPIOB->ODR = 0xF00F
устанавливает булавки B0
через B3
а также B12
через B15
до 1 и B4
через B11
до 0, независимо от их предыдущего состояния. Можно написать GPIOD->ODR |= (1<<4)
установить пин GPIOD4
до 1 или GPIOD->ODR &= ~(1<<4)
сбросить его.
Пишу BSRR
обрабатывает значение, записанное как две битовые маски. Младшее половинное слово - это установленная маска, биты со значением 1 устанавливают соответствующий бит в ODR
1. Старшее половинное слово - маска сброса, биты со значением 1 устанавливают соответствующий бит в ODR
до 0. GPIOC->BSRR = 0x000701E0
установил бы булавки C5
хоть C8
до 1, сброс C0
через C2
до 0 и оставьте все остальные биты порта в покое. Попытка установить и сбросить один и тот же бит при записи BSRR
тогда он будет установлен в 1.
Пишу BRR
это то же самое, что запись битовой маски сброса в BSRR
т.е. GPIOx->BRR = x
эквивалентно GPIOx->BSRR = (x << 16)
,
Теперь можно написать несколько макросов, таких как
#define GPIOD_OUT(pin, value) GPIOD->BSRR = ((0x100 + value) << pin)
#define GPIOD4_OUT(value) GPIOD_SET(4, value)
изменить отдельные контакты, но это не так гибко, как могло бы быть, например, вы не можете взять адрес одного контакта и передать его в переменных.
Битовая полоса
Контроллеры Cortex-M (не все, кроме STM32F1
у серий есть) эта функция делает отдельные биты во внутренней ОЗУ и в аппаратных регистрах адресуемыми. Каждый бит в 0x40000000-0x400FFFFF
диапазон сопоставляется с полным 32-битным словом в 0x42000000-0x43FFFFFF
спектр. Он не работает с периферийными устройствами за пределами этого диапазона адресов, такими как USB или NVIC.
Адрес полосы битов периферийного регистра может быть рассчитан с помощью этого макроса
#define BB(reg) ((uint32_t *)(PERIPH_BB_BASE + ((uint32_t)&(reg) - PERIPH_BASE) * 32U))
и вы можете рассматривать полученный указатель как основу массива, содержащего 32 слова, каждое из которых соответствует одному биту в периферийных регистрах. Теперь можно
#define PD_ODR_ODR4 (BB(GPIOD->ODR)[4])
и использовать его в заданиях. Чтение его даст 0 или 1 в качестве значения, записанные в него значения копируют младший значащий бит записанного значения в бит периферийного регистра. Вы даже можете взять его адрес и передать его функции, которая что-то делает с помощью булавки.
Битовая полоса описана в руководстве по программированию PM0056 Cortex®-M3.
Ответ, предоставленный @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, чтобы оно было доступно для чтения / записи:
Стандартный (и единственный способ действительно) получить доступ к любому адресу памяти в 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-битный или меньший доступ к памяти автоматически становится атомарным, как я исследовал и описал здесь: /questions/4860921/kakie-tipyi-razmeryi-peremennyih-yavlyayutsya-atomarnyimi-na-mikrokontrollerah-stm32/4860934#4860934.
Этот тип #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), и вместо этого они часто используют конструкции на основе структур. Например, из "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
- Справочное руководство по STM32 (содержит определения регистров): RM0008, Справочное руководство по STM32F101xx, 101xx, 103xx и т. Д.
- Спецификация STM32 (содержит базовые адреса): DS5319
Прочитайте RM0008 p161-162.
Я не буду вдаваться во все детали (читайте выше), но чтобы прочитать пин-код, вам нужно 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.
Мы можем видеть смещения и направление данных:
| 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. Карта памяти, и вы увидите, что базовый адрес для "Порт A" равен 0x40010800, как показано и выделено здесь:
Теперь давайте вручную определим регистры:
#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;
}
}
КОНЕЦ