среда, 27 сентября 2017 г.

STM32. Регистры GPIO. Светодиод и кнопка. [Примеры]



Эту кнопку и этот светодиод будем использовать в примере.

В предыдущей статье я попытался сделать частичный перевод 9.1 главы мануала посвещенный работе с GPIO портами. Теперь попробуем поиграться с I/O портами на практике.


Поставим перед собой небольшую задачку №1: помигать встроенным светодиодом (вывод PB1).

Для ее решения нужно:
  1. Настроить PB1 в Режим Двухтактоного Выхода.
  2. Менять выходное значение GPIO вывода.
И сразу опишем задачку №2: включить/выключить встроенный светодиод PB1 по нажатию стандартной кнопки PB8.

Для ее решения нужно:
  1. Настроить PB1 в Режим Двухтактоного Выхода.
  2. Настроить PB8 в Режим Вход с подтяжкой на минус.
  3. Если кнопка нажата - включать светодиод, если нет - выключать.

Включаем Режим Двухтактного Выхода.


Настраиваем PB1 с помощью GPIOB_CRL регистра, который отвечает за конфигурирование первых 8 битов порта. Теперь устанавливаем Режим Двухтактного Выхода, для этого смотрим в Таблицу 20 главы 9.1 мануала и видим, что биты CNF1, CNF0 должны быть сброшены (значение = 0).
Выбираем выходную частоту 50 МГц - биты MODE1, MODE0 - должны быть установленны (значение = 1).
Теперь эти знания нужно перевести в код. Для этого используем стандартную CMSIS библиотеку, а именно stm32f10x.h файл, в котором описаны адреса регистров и их названия с помощью define.
Используй поиск по stm32f10x.h файлу чтобы найти правильные названия битов и их регистров.
Получилось это:

GPIOB->CRL &=~(GPIO_CRL_CNF1_0);
GPIOB->CRL &=~(GPIO_CRL_CNF1_1);
 GPIOB->CRL |= (GPIO_CRL_MODE1_1);
 GPIOB->CRL |= (GPIO_CRL_MODE1_0);

Описание:
  • &=~  сбросить бит, т.е. выставить значение 0;
  • |=      установить бит, т.е. выставить значение 1;
  • GPIOB->CRL - обращаемся к GPIOx_CRL регистру порта B;
  • GPIO_CRL_CNF1_0 - адрес CNF0 бита, который служит для настройки 1 бита порта B;

Немного о тактировании.


Что бы GPIO порт работал ему нужно тактирование.
По умолчанию порт не имеет досnупа к тактовому генератору. По-видимому это сделано для того, чтобы не работающие порты не потребляли лишнюю энергию.
Чтобы подключить порт B к тактовому генератору, нужно установить RCC_APB2ENR_IOPBEN бит в регистре APB2ENR.

RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

Если этого не сделать, порт работать не будет.


Меняем выходное значение GPIO вывода.


После того как мы настроили вывод порта на выход уже можно менять его выходное значение.

Первый способ, стандартный, с помощью изменения значения ODR регистра. Значение сначала считывается из регистра, меняется, потом записывается новое, а это долго и для быстрых задач не совсем подходит. Плюс может случиться прерывание между чтением и записью.


GPIOB->ODR |= GPIO_ODR_ODR1; // устанавливаем
GPIOB->ODR &=~ GPIO_ODR_ODR1; // сбрасываем

Второй способ, с помощью использования атомарного доступа к регистру ODR, через использование регистра BSRR. Этот способ лучше тем, что меняется конкретный бит, а не полностью значение всего регистра. В даташите также написано, что при одновременном сбросе и установке одного и того же бита в результате бит будет установлен.


GPIOB->BSRR = GPIO_BSRR_BS1; // устанавливаем
GPIOB->BSRR = GPIO_BSRR_BR1; // сбрасываем

Еще можно сбрасывать бит спомощью BRR регистра.


GPIOB->BRR |= GPIO_BRR_BR1;

Собрав все это в кучу получаем решение первой задачи. Правда пришлось добавить импровизированную функцию задержки, чтобы можно было заметить мигание.
Так же в код добавил пример блокировки конфигурации вывода. Но об этом ниже.

Полный код:


#include "stm32f10x.h"

//tact in one ms
uint32_t tactsInOneMs = 1;

//delay in ms
void delay(uint16_t waitInMs) {
  uint32_t countTacts = waitInMs * tactsInOneMs;
 
  for(; countTacts != 0; countTacts--) {

  }
}

void main(void) {
  SystemInit();
  //calculate how many CPU tacks in one ms for the delay function
  tactsInOneMs = SystemCoreClock / 10000;
  //Enabling clock for GPIO
  RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;
  //Configuring GPIO1 as push-pull output
  GPIOB->CRL &=~(GPIO_CRL_CNF1_0);
  GPIOB->CRL &=~(GPIO_CRL_CNF1_1);
  //set Led pin as output:
  GPIOB->CRL |= (GPIO_CRL_MODE1_1);
  GPIOB->CRL |= (GPIO_CRL_MODE1_0);
 
  while (1) {
    //first way change line output
    GPIOB->ODR |= GPIO_ODR_ODR1;
    delay(500);
    GPIOB->ODR &=~ GPIO_ODR_ODR1;
    delay(500);
   
    //second way change line output
    //GPIOB->BSRR |= GPIO_BSRR_BS1;
    //delay(500);
    //GPIOB->BSRR |= GPIO_BSRR_BR1;
    //delay(500);
   
    ////try BRR. Only reset
    //GPIOB->BRR |= GPIO_BRR_BR1;
    //delay(500);
    /*
    //lock port bit. With required sequence 1-0-1 

    //and two times reading GPIO_LCKR_LCKK bit:
    GPIOB->LCKR = GPIO_LCKR_LCK1 | GPIO_LCKR_LCKK;
    GPIOB->LCKR = GPIO_LCKR_LCK1;
    GPIOB->LCKR = GPIO_LCKR_LCK1 | GPIO_LCKR_LCKK;
    GPIOB->LCKR;GPIOB->LCKR;
    //try to change configuration.

    //if lock doesnt work the led will turned off
    GPIOB->CRL &=~ (GPIO_CRL_MODE1_1);
    GPIOB->CRL &=~ (GPIO_CRL_MODE1_0);
    */

  }
}
Теперь поговорим о возможности блокировки битов конфигурации вывода.

Блокировка изменения конфигурации GPIO вывода.


Попробуем заблокировать изменение конфигурации PB1 вывода. Для этого необходимо записать последовательность значений 1-0-1 и два раза прочитать бит GPIO_LCKR_LCKK.


GPIOB->LCKR = GPIO_LCKR_LCK1 | GPIO_LCKR_LCKK;
GPIOB->LCKR = GPIO_LCKR_LCK1;
GPIOB->LCKR = GPIO_LCKR_LCK1 | GPIO_LCKR_LCKK;

GPIOB->LCKR; GPIOB->LCKR;

//так можно проверить блокировку
//если не включать блокировку, то светодод не загорится после этих строк
GPIOB->CRL &=~ (GPIO_CRL_MODE1_1);
GPIOB->CRL &=~ (GPIO_CRL_MODE1_0);

Включаем Режим Вход с подтяжкой на минус.


Для решения второй задачки, необходимо выполнить второй шаг.  Чтобы настроить вывод на вход, нужно биты MODE1, MODE0, CNF0 сбросить (0), а бит CNF1 установить (1). Чтобы включить подтяжку на минус нужно сбросить соответсвующий ODR бит. Наглядно это проиллюстрированно все в той же таблице даташита.

//Configuring PB8 as input with pull-down
GPIOB->CRL &=~(GPIO_CRH_CNF8_0);
GPIOB->CRL |=(GPIO_CRH_CNF8_1);
GPIOB->CRL &=~ (GPIO_CRH_MODE8_1);
GPIOB->CRL &=~ (GPIO_CRH_MODE8_0);
GPIOB->ODR &=~ GPIO_ODR_ODR8; //enable pull-down

И в цикле проверяем, если кнопка нажата, то включаем светодиод, если нет - выключаем.

while (1) {
   if ((GPIOB->IDR & GPIO_IDR_IDR8) == GPIO_IDR_IDR8) {
      GPIOB->BSRR |= GPIO_BSRR_BS1;
  } else {
      GPIOB->BSRR |= GPIO_BSRR_BR1;
  }
}

Все, задачку №2 сделали вот полный код:


#include "stm32f10x.h"

void main(void)
{
  //Enabling clock for GPIO
  RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;

  //Configuring GPIO1 as push-pull output
  GPIOB->CRL &=~(GPIO_CRL_CNF1_0);
  GPIOB->CRL &=~(GPIO_CRL_CNF1_1);
  //set Led pin as output:
  GPIOB->CRL |= (GPIO_CRL_MODE1_1);
  GPIOB->CRL |= (GPIO_CRL_MODE1_0);

  //Configuring PB8 as input with pull-down
  GPIOB->CRH &=~(GPIO_CRH_CNF8_0);
  GPIOB->CRH |=(GPIO_CRH_CNF8_1);
  GPIOB->CRH &=~ (GPIO_CRH_MODE8_1);
  GPIOB->CRH &=~ (GPIO_CRH_MODE8_0);
  GPIOB->ODR &=~ GPIO_ODR_ODR8; //enable pull-down
 
  while (1)
  {
    if ((GPIOB->IDR & GPIO_IDR_IDR8) == GPIO_IDR_IDR8) {
      GPIOB->BSRR |= GPIO_BSRR_BS1;
    } else {
      GPIOB->BSRR |= GPIO_BSRR_BR1;
    }
  }
}

На этом пока что все, в следующей статье рассмотрим внешние прерывания.

Комментариев нет:

Отправить комментарий