Программирование 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 без библиотек или заголовочных файлов.

Нам понадобится:

  1. Основная справочная страница для этого чипа: 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
  2. Справочное руководство по STM32 (содержит определения регистров): RM0008, Справочное руководство по STM32F101xx, 101xx, 103xx и т. Д.
  3. Спецификация 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;
  }
}

КОНЕЦ

Другие вопросы по тегам