Начнем с теории. Интерфейс ввода/вывода (далее GPIO) является важной составляющей МК, без которой нам попросту не обойтись. Любая подключаемая внешняя периферия не обходится без настройки GPIO. Поэтому необходимо четко представлять работу GPIO и уметь правильно его настроить. Понимание интерфейса, не раз спасет ваше драгоценное время.

GPIO представляет собой токопроводящие выводы (контакты) МК сгруппированные по портам и имеющие возможность конфигурации на вход или выход.

Рассмотрим как GPIO устроен в STM32.

Казалось бы что там сложного, всего лишь ввод и вывод, а нет, не все так просто.

Pin(вывод МК) может работать в логическом режиме и в аналоговом.

В логическом режиме значение 0 или 1 принимается в зависимости от напряжения. Важно отметить, что STM32 работает с напряжением 3.3В, но многие модели толерантны к 5В.

Для входа, преобразователем уровней напряжения служит TTL Shmitt trigger. Для выхода реализована схема на транзисторах. Т.е. при подаче логической единицы открывается транзистор P-MOS и на выход поступает питание от Vdd(3.3В), при 0 открывается N-MOS, соответственно на выходе устанавливается 0В.

В аналоговом режиме сигнал от GPIO поступает на вход аналого-цифрового преобразователя(ADC).

Данные о состоянии порта, хранятся в регистрах Output data register и Input data register. Регистры являются 16 разрядными, т.е. хранят информацию о 16 выводах (например PA0-PA15). При этом Input data register можно использовать только для чтения, а Output data register, как для записи, так и для чтения. Так как информация хранится в регистрах, можно производить такие же операции, как и с другими регистрами, например операции сдвига. Что в свою очередь не потребует создание промежуточных переменных и сократит количество операций.

По мимо основных режимов работы, GPIO так же имеет режим Alternate Function(Режим альтернативной функции). Данный режим необходим МК для передачи управления выводом внутреннему периферийному устройству. В таком случае, после соответствующей настройки, мы можем не заботиться о состоянии вывода, всю ответственность берет на себя периферийное устройство. Стоит добавить что периферийные устройства могут работать только со строго фиксированными выводами. Например UART1 в STM32F107VC может с выводами PA9/PA10 или PB6/PB7. От сюда всплывает термин Remapping, так как появляется необходимость выбора варианта подключения. Для каждого периферийного устройства имеется значение по умолчанию(XXX_REMAP = 0), например для UART1, USART1_REMAP = 0 соответствует выводам PA9/PA10. Может возникнуть вопрос, зачем это нужно? Для ответа на данный вопрос, представим ситуацию. Собрали мы довольно сложную схему, справа от МК поставили микросхему RS232(Uart to RS232), подключив ее к выводам PA9/PA10. И так получилось, что с правой стороны что-то не помещается. Тогда мы можем разместить микросхему сверху от МК и подключить ее к выводам PB6/PB7. Все поместилось и мы довольны работой.

Не рассмотренными из выше приведенной схемы остались только два резистора в секции Input driver. Резисторы одной стороной подключены непосредственно к выводу, а другой, через включатель идут к Vdd(3.3В) и Vss(0В или GND). Включатели управляются программно. Стоит отметить, что оба резистора не могут быть включены в цепь одновременно. Предназначены они для подтяжки вывода к питанию или к нулю. Необходимость в подтяжки линии связана с уменьшением помех на линии, либо с требованием, периферийного устройства (некоторые устройства требую высокого уровня на линии).

Последними остались диоды Protection diode. Не сложно догадаться, что они служат для защиты входа от превышения напряжения. Соответственно положительное превышение уйдет вниз на Vss(Gnd), а отрицательное вверх на Vdd.

С теорией закончили, пора переходить к практике.

Создадим проект для STM32F107VC (макетная плата Open107V) в CubeMX. Рассмотрим схему к макетной плате.

У нас имеются 4 светодиода на выводах PB0, PB1, PB14, PB15, кнопка User Key на выводе PC13, Joystick, с которого мы возьмем только кнопку JSTICK_K5(центральное нажатие) на выводе PC9 и USART1, в котором TX заведен на PB6, а RX на PB7. Также имеется внешний кварц с частотой 25 МГц.

В CubeMX выберем внешний источник тактирования: RCC->High Speed Clock (HSE)->Crystal/Ceramic Resonator.

Ниже выберем USART1 и выставим Mode->Asynchronous. Обратите внимание, CubeMX, как и положена, по умолчанию для USART1 назначила выводы PA9/PA10, что не соответствует нашей схеме. Для смены нажимаем на выводе PB6 левой кнопкой мыши и выбираем USART1_TX, второй вывод установится автоматически.

Назначим выводы Led1-Led4. Для этого левой кнопкой мыши нажимаем на PB0 и выбираем GPIO_Output, то же самое для выводов PB1, PB14 и PB15. Так же еще раз пройдемся по этим выводам, но нажмем правую кнопку мыши и выберем Enter User Label, в появившемся окне введем имя светодиода Led1-Led4 соответственно.

Назначим вывод для User_key. Для этого левой кнопкой мыши нажимаем на PС13 и выбираем GPIO_Input. Так же назначим Label User_key.

Назначим вывод для JSTICK_K5. Так как мы уже изучили прерывания, вместо обычного входа (GPIO_Input) выберем режим GPIO_EXTI. Для этого левой кнопкой мыши нажимаем на PС9, выбираем GPIO_EXTI9 и назначим Label JSTICK_K5.

Перейдем во вкладку Clock Configuration для настройки тактирования. Input frequency меняем на 25 МГц, а HCLK (MHz) на 72 МГц. После нажатия Enter, CubeMX подберет необходимы коэффициенты на PLL. Остается только убедится, что мы можем проследовать от внешнего кварца, до HCLK.

Перейдем во вкладку Configuration. В разделе System выберем GPIO. Здесь мы можем выбрать дополнительные настройки для выводов МК. Начнем по порядку.

Светодиоды, согласно схемы подключены одной стороной к МК, другой к GND, значит включать их будем высоким уровнем на выводе, а при инициализации на выводе необходим низкий уровень. Соответственно GPIO output level для них должен быть Low. Обращаю на это внимание, так как существует множество периферии, которая включается низким уровнем.

User_key подключен к GND, при нажатии на кнопку на выводе МК установится низкий уровень, значит в обычном состоянии на выводе должен быть высокий уровень. Для этого нам необходимо подтянуть вывод к питанию (к верху). Выберем в GPIO Pull-up/Pull-down -> Pull-up.

JSTICK_K5 так же подключен к GND, соответственно вывод, таким же методом, необходимо подтянуть к питанию. Так как на данном выводе у нас будет настроено прерывание, необходимо выбрать по какому фронту оно будет срабатывать. Под фронтом подразумевается смена сигнала с высокого на низкий или наоборот. В нашем случае из-за подтяжки к питанию изначально на выводе высокий уровень, а после нажатия на кнопку, уровень станет низким. Т.е. сигнал изменится от высокого к низкому уровню, что соответствует спадающему фронту (Falling edge). Но мы будем реагировать не на нажатие, а на отпуск кнопки (Rising edge). Поэтому в GPIO mode выбираем External Interrupt Mode with Rising edge trigger detection.

Нажимаем Ok и в разделе System выберем NVIC (настройка прерываний). Здесь нам необходимо только поставить галачку на EXTI line[9:5] interrupts. Тем самым мы включили прерывание от JSTICK_K5.

Работа с CubeMX закончена. Генерируем код и смотрим что получилось.

main.c

static void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* Включаем тактирование порта PB и PC */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin : User_key_Pin */
  GPIO_InitStruct.Pin = User_key_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(User_key_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : Led1_Pin Led2_Pin Led3_Pin Led4_Pin */
  GPIO_InitStruct.Pin = Led1_Pin|Led2_Pin|Led3_Pin|Led4_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pin : JSTICK_K5_Pin */
  GPIO_InitStruct.Pin = JSTICK_K5_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(JSTICK_K5_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, Led1_Pin|Led2_Pin|Led3_Pin|Led4_Pin, GPIO_PIN_RESET);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

}
Как видим CubeMX вместо значений выводов подставил наши Labels. Их значения определены в файле mxconstants.h
#define User_key_Pin GPIO_PIN_13
#define User_key_GPIO_Port GPIOC
#define Led1_Pin GPIO_PIN_0
#define Led1_GPIO_Port GPIOB
#define Led2_Pin GPIO_PIN_1
#define Led2_GPIO_Port GPIOB
#define Led3_Pin GPIO_PIN_14
#define Led3_GPIO_Port GPIOB
#define Led4_Pin GPIO_PIN_15
#define Led4_GPIO_Port GPIOB
#define JSTICK_K5_Pin GPIO_PIN_9
#define JSTICK_K5_GPIO_Port GPIOC
Настройки портов периферийных устройств расположены в файле stm32f1xx_hal_msp.c
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(huart->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();
  
    /**USART1 GPIO Configuration    
    PB6     ------> USART1_TX
    PB7     ------> USART1_RX 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    __HAL_AFIO_REMAP_USART1_ENABLE();

  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }

}

Рассказывать о назначении функций инициализации и о структурах, не вижу смысла, т.к. считаю их интуитивно понятными. Если вы сомневаетесь во входящих аргументах функции, вы можете прочитать описание функции в соответствующем хидере нажав правой кнопкой по функции и выбрав пункт Go To Reference To...

Дополним код для функциональности.

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
#include <stdbool.h>
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
volatile bool pressed_JSTICK_K5;
uint8_t ledPos;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* Private function prototypes -----------------------------------------------*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == JSTICK_K5_Pin)
    {
        if (!pressed_JSTICK_K5) 
        {
            HAL_UART_Transmit(&huart1, (uint8_t*)"Working Toggle\n\r", 16, 100);
            pressed_JSTICK_K5 = true;
        }
    }
}
//============================================================================//
void Leds_Snake(bool direct)//мигаем светодиодами в заданном направлении
{
    if (direct)
    {
        if (ledPos > 0) ledPos--;
        else ledPos = 3;
    }
    else
    {
        if (ledPos < 3) ledPos++;
        else ledPos = 0;
    }
    
    switch (ledPos)
    {
        case 0: HAL_GPIO_TogglePin(GPIOB, Led1_Pin); break;
        case 1: HAL_GPIO_TogglePin(GPIOB, Led2_Pin); break;
        case 2: HAL_GPIO_TogglePin(GPIOB, Led3_Pin); break;
        case 3: HAL_GPIO_TogglePin(GPIOB, Led4_Pin); break;
    }
}
//============================================================================//
int main(void)
{
    bool working = true;
    bool direct = false;
    bool pressed_User_key = false;
    uint8_t timerLeds = 20;

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();

    ledPos = 4;
    HAL_UART_Transmit(&huart1, (uint8_t*)"Start Ok\n\r", 10, 100);

  while (1)
  {
        //Отслеживаем нажатие кнопки User_key
        if (HAL_GPIO_ReadPin(User_key_GPIO_Port, User_key_Pin) == GPIO_PIN_RESET)
        {
            if (!pressed_User_key)
            {
                pressed_User_key = true;
                if (direct) 
                {
                    HAL_UART_Transmit(&huart1, (uint8_t*)"Direction Right\n\r", 17, 100);
                    direct = false;
                    ledPos = 4;
                }
                else 
                {
                    HAL_UART_Transmit(&huart1, (uint8_t*)"Direction Left\n\r", 16, 100);
                    direct = true;
                    ledPos = 0;
                }
                HAL_GPIO_WritePin(GPIOB, Led1_Pin|Led2_Pin|Led3_Pin|Led4_Pin, GPIO_PIN_RESET);
            }
        }
        else pressed_User_key = false;
        //
        if (pressed_JSTICK_K5)
        {
            working = !working;
            pressed_JSTICK_K5 = false;
        }
        //
        if (working)
        {
            if (timerLeds) timerLeds--;
            else
            {
                timerLeds = 20;
                Leds_Snake(direct);
            }
        }
        HAL_Delay(10);
  }
}
//============================================================================//

В программе мы мигаем светодиодами в заданном направлении и с возможностью остановки работы. Направление меняется с помощью кнопки User_key. Остановка или запуск осуществляется с помощью кнопки JSTICK_K5. Значение направления хранится в переменной direct. , а состояния работы в working. Нажатие кнопки User_key обрабатывается в основном цикле с периодичностью 10 мс. Ее значение считывается с помощью функции HAL_GPIO_ReadPin.

Значение переменной working инвертируется так же в основном цикле при pressed_JSTICK_K5 = true. Переменная pressed_JSTICK_K5 является глобальной и меняет свое значение в обработчике прерываний HAL_GPIO_EXTI_Callback. Такая конструкция обеспечивает снижение реакции на "дребезг контакта". Под дребезгом контакта подразумевается множество скачкообразных изменений сигнала в короткое время, что ведет к нескольким срабатываниям прерывания. Это связано с несовершенством работы контактной группы. Для полного исключения дребезга необходимо применять специализированные алгоритмы.

В основном цикле при working = true, производится декремент timerLeds. При достижении 0, сбрасываем счетчик на 20 и выполняем функцию Leds_Snake. Это обеспечивает выполнение функции Leds_Snake с периодичностью 200 мс.

Функция Leds_Snake в зависимости от входящего параметра направления движения (bool direct), выполняет смену состояния выводов в соответствующем направлении светодиодов посредством функции HAL_GPIO_TogglePin.

Помимо работы со светодиодами, в USART1 отправляются сообщения о старте программы и о смене режимов с помощью функции HAL_UART_Transmit.

Проект закончен, компилируем и зашиваем в МК.


Архив с проектом  - ссылка на скачивание.

Схема макетной платы Open107V - ссылка на скачивание.

Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.


© Copyright 2017. Все права защищены.
Яндекс.Метрика