Программирование STM32 подобно STM8 (уровень регистра GPIO) - PullRequest
0 голосов
/ 17 февраля 2019

Я запрограммировал STM8 GPIO как PD_ODR_ODR4 = 1;, но stm32f10x.h не имеет этой функции. Есть ли файл .h с определением битов.

Извините, но я не знаю, как объяснитьэта проблема лучше.

Я пробовал несколько библиотек GPIO.

сильный текст

Ответы [ 2 ]

0 голосов
/ 21 февраля 2019

Ответ, предоставленный @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 без библиотек или заголовочных файлов.

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

  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#design-scroll
  2. Справочное руководство STM32 (содержит определения регистров): RM0008, Справочное руководство для STM32F101xx, 101xx, 103xx и т. Д.
  3. Таблица данных STM32 (содержит базуадреса): DS5319

Читать RM0008 p161-162. enter image description here

Я не буду вдаваться во все детали (см. выше), но для чтения для вывода требуется 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

enter image description here

enter image description here

Мы можем видеть смещения и направление данных следующим образом:

| 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

Теперь давайте вручную определим регистры:

#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

0 голосов
/ 18 февраля 2019

Вы упоминаете 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)

, чтобы изменить отдельные выводы, но это не так гибко, как могло бы бытьНапример, вы не можете взять адрес одного контакта и передать его в переменных.

Bit Banding

контроллеры Cortex-M (не все из них, но* Серии 1054 *) имеют эту функцию, чтобы сделать отдельные биты во внутренней ОЗУ и в аппаратных регистрах адресуемыми.Каждый бит в диапазоне 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.

...