Skip to content

Latest commit

 

History

History

04_gpio

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

Задание 04 "gpio"

После ознакомления с устройством GPIO в микроконтроллере STM32 на лекции и вспомогательной статье, попробуйте выполнить несколько практических задач.

Мигание светодиодом

На отладочной плате, используемой в курсе установлены два светодиода, подключенных к выводам 8 и 9 порта GPIOC.

Возьмите за основу пустой проект и добавьте функцию инициализации системы тактирования rcc_config, которую можно взять из прошлых заданий. Далее необходимо вызвать её в main. Также добавьте следующий список заголовочных файлов:

#include "stm32f0xx_ll_rcc.h"
#include "stm32f0xx_ll_system.h"
#include "stm32f0xx_ll_bus.h"
#include "stm32f0xx_ll_gpio.h"

Теперь необходимо подать тактирование на модуль порта GPIOC и настроить непосредственно выводы со светодиодами в режим цифровой выход. Имейте ввиду, что все порты ввода-вывода подключены к шине AHB. Для этого создайте функцию gpio_config и поместите в нее следующий код:

LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOC);
LL_GPIO_SetPinMode(GPIOC, LL_GPIO_PIN_8, LL_GPIO_MODE_OUTPUT);
return;

Добавьте вызов данной функции в main. Заметьте, что конфигурацию выходного драйвера менять не нужно: вывод не имеет подтяжки, работает в PushPull режиме и имеет низкую скорость работы.

Теперь вернитесь в main и выполните переключение состояние 8-ого вывода в бесконечном цикле. Для изменения состояния используйте функции LL_GPIO_SetOutputPin и LL_GPIO_ResetOutputPin из главы LL GPIO библиотеки LL.

LL_GPIO_SetOutputPin(GPIOC, LL_GPIO_PIN_8); LL_GPIO_ResetOutputPin(GPIOC, LL_GPIO_PIN_8);

Помните, что в данных функциях происходит доступ к регистру BSRR: запись единицы в младшие 16 бит приводит к установке конкретного вывода, запись единицы в старшие 16 бит приводит к сбросу заданного вывода. Его основным преимуществом по сравнению с ODR заключается в том, что запись нуля не приводит к каким-либо изменениям, в то время как запись нуля в ODR приводит к сбросу соответствующего вывода.

Теперь добавьте задержку между вызовами функций сброса и установки выхода. Можно использовать ту же самую функцию delay из прошлых занятий. На самом деле высокая точность здесь не нужна, поэтому можно сделать функцию delay, написанную на C и состоящую из пустого цикла на 100000 итераций.

Подключение кнопки

Следующим шагом напишите программу, которая будет переключать состояние светодиода при нажатии на кнопку. Воспользуйтесь кнопкой, подключенной ко входу 0 порта GPIOA. Данная кнопка установлена на отладочной плате с надписью USER.

В первую очередь включите тактирование порта GPIOA в функции gpio_config:

LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);

Следующим шагом необходимо активировать режим цифровой вход (который на самом деле активирован по умолчанию на всех пинах):

LL_GPIO_SetPinMode(GPIOC, LL_GPIO_PIN_0, LL_GPIO_MODE_INPUT);

Вернитесь в основной цикл и напишите код, который будет опрашивать состояние кнопки каждый проход цикла. После первого нажатия светодиод должен загораться, после второго потухнуть.

Для опроса состояние кнопки необходимо считывать состояние регистра IDR с помощью функции из LL:

// 1 - кнопка нажата; 0 - отжата
status = LL_GPIO_IsInputPinSet(GPIOA, LL_GPIO_PIN_0);

После реализации вы можете заметить, что схема работает не очень стабильно из-за дребезга контактов. Попробуйте решить проблему дребезга за счет выставления задержки в случае, если кнопка была нажата. Как и в случае с миганием светодиода, реализуйте задержку на 0.1 секунды и вставьте ее вызов внутрь условного оператора:

if (PINA_0 == 1) {
    delay_0.1ms();
    if led is on then off else on;
}

Подумайте какие недостатки у данной реализации антидребезга. Предложите реализацию, которая была бы лишена данных недостатков (она была рассказана в конце лекции).

ЗАМЕЧАНИЕ: в данной задаче необходимо отлавливать именно момент переключение входного состояния, то есть переключение с 0 в 1 (или наоборот). В приведенной выше реализации светодиод будет переключаться постоянно, пока кнопка нажата. Ваша задача: предложить реализацию, лишенного данного недостатка.

Семисегментный индикатор

Поработав с базовыми элементами, перейдем к подключению семисегментного индикатора (seven-segment display). Индикатор имеет примерно следующий вид:

seven_segment.jpg

Семисегментный индикатор

Устройство имеет 10 выводов, центральный вывод в каждом ряду это общий анод/катод в зависимости от типа индикатора. Остальные выводы подключаются непосредственно к каждому из сегментов a,b,c,d,e,f,g,dp(точка).

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

Теперь подключите индикатор к порту микроконтроллера. Для примера возьмите порт GPIOB и подсоедините индикатор по следующей схеме (не забудьте на каждый сегмент подключить последовательно резистор на каждый сегмент номиналом 200-300 Ом):

a - GPIOB PIN0
b - GPIOB PIN1
c - GPIOB PIN2
...
g - GPIOB PIN6

Катод/анод подключите к GPIOB PIN7. Настройте соответствующие выходы в режим цифровой выход.

Теперь, чтобы отобразить цифру 1, необходимо подать на порт B значение 0x0006 (нужно помнить, что 7 выход порта должен быть в нуле для индикатора с общим катодом, и всегда в единице для индикатора с общим анодом). Убедитесь в этом (не забудьте подать тактирование на порт B):

// Вариант для случая, если индикатор с общим катодом
LL_GPIO_WriteOutputPort(GPIOB, 0x0006);
// Вот так выглядела бы запись для индикатора с общим анодом
LL_GPIO_WriteOutputPort(GPIOB, 0xFF & ~0x0006);

Теперь напишите функцию, которая принимает на вход число для отображения (от 0 до f) и показывает его на индикаторе.

void show_digit(digit)
{
    /* 
     * Выберите необходимый набор выходов в зависимости от числа на входе
     * (можно использовать массив):
     * uint12_t decoder[16] = {
     *     [0] =  LL_GPIO_PIN_0 | LL_GPIO_PIN_1 | LL_GPIO_PIN_2 | LL_GPIO_PIN_3 | \
     *            LL_GPIO_PIN_4 | LL_GPIO_PIN_5,
     *     [1] = ...
     *     ...
     *     [15] = ...
     * }
     * uint16_t out = decoder[digit];
     */
     
    /*
     * Настройте правильно состояние анода. Подумайте какими битовыми операциями
     * можно включить/выключить ТОЛЬКО 7 бит переменной out.
     * Для катода ситуация с точностью до наоборот.
     */
     
    LL_GPIO_WriteOutputPort(GPIOB, out);
    return;
}

Последним шагом сделайте счетчик по нажатию кнопки: при каждом нажатии цифра на экране инкрементируется (от 0 до f). Используйте усовершенственный метод для антидребезга.

Динамическая индикация

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

Предположим, что необходимо подключить N индикаторов к микроконтроллеру. Каждый индикатор имеет 8 выходов (7 на каждый из сегментов и 1 на точку) и один вывод для общего катода/анода. В наивной реализации необходимо было бы подключить каждый индикатор по отдельности, в сумме затрачивая 8*N + N = 9N выходов. Немало. Чтобы снизить количество используемых выводов, используется динамическая индикация, главный принцип которой заключается в использовании свойства инерционности глаза.

Подробное описание принципа динамической индикации можно почитать тут. Проще говоря, можно сгруппировать одноименные сегменты (к примеру, сегменты a для каждого индикатора объединяются в один) и последовательно выводить цифры на устройство, активируя аноды индикаторов по отдельности. Таким образом цифры появляются по очереди. Если поднять частоту переключения индикаторов, то можно получить "статическую" картину. При этом в конкретный момент времени будет гореть только один индикатор, а не все, как в предыдущем случае. Количество используемых выводов при этом снизится до 8+N.

Попробуйте подключить данный индикатор в микроконтроллеру и вывести на него число. Для определенности положим, что N=4, то есть индикатор состоит из 4 цифр. В первую очередь определите распиновку вашего индикатора. Составьте таблицу, где каждому сегменту и общему аноду/катоду соответствует номер вывода на индикаторе. Подключите индикатор по следующей схеме:

a - GPIOB PIN0
b - GPIOB PIN1
c - GPIOB PIN2
...
g - GPIOB PIN6

Anode1/Cathode1 - GPIOC PIN0
Anode2/Cathode2 - GPIOC PIN1
Anode3/Cathode3 - GPIOC PIN2
Anode4/Cathode4 - GPIOC PIN3

Инициализируйте соответствующие выходы микроконтроллера. Используйте массив decoder из предыдущего примера с добавленным состоянием под номером 16, где все сегменты выключены (поданы лог. нули на сегменты в случае индикаторы с общим катодом или все лог. единицы в случае с общим анодом). Напишите функцию, которая реализует динамическую индикацию на индикаторе:

void dyn_display(uint16_t number)
{
    /* static переменная сохраняет свое значение между вызовами функции */
    static int digit_num = 0;
    /*
     * Выберите необходимый набор выходов в зависимости от числа на входе
     * (можно использовать массив):
     * uint12_t decoder[17] = {
     *     [0] =  LL_GPIO_PIN_0 | LL_GPIO_PIN_1 | LL_GPIO_PIN_2 | LL_GPIO_PIN_3 | \
     *            LL_GPIO_PIN_4 | LL_GPIO_PIN_5,
     *     [1] = ...
     *     ...
     *     [16] = состояние с выключенными сегментами
     * }
     
    /* Выключите все аноды/катоды */
    LL_GPIO_WriteOutputPort(GPIOC, ...); 
    
    switch (digit_num) {
    case 0:
        /* Включите анод/катод для первой цифры на индикаторе */
        /* number & 0x000F это маска, чтобы получить 4 младший бита, 
           хранящих первую цифру */
        out = decoder[number & 0x000F];
        break;
    case 1:
        /* Включите анод/катод для второй цифры на индикаторе */
        out = decoder[number & 0x00F0];
        break;
    case 2:
        ...
        break;
    case 3:
        break;
    default:
        break;
    }
    
    /* Инкрементируйте переменную с номером текущего индикатора */
    digit_num = (digit_num + 1) % 4;
    return;
}

Вставьте вызов данной функции в основной цикл программы. Добавьте задержку, иначе скорость переключение будет очень большой и светодиоды не будут успевать переключаться. Попробуйте поставить очень большую задержку, чтобы увидеть, как происходит переключение.

Подумайте, как можно добавить в код опрос кнопки, при нажатии на которую инкрементировалось бы число на индикаторе.

Далее напишите декодер hex2dec, который выводит на экран число в десятичной системе, а не шестнадцатеричной, как сейчас.

dyn_display(hex2dec(number));

Для этого используйте формат BCD, в котором каждая тетрада хранит свой разряд в десятичной системе. Например, 1239 в десятичной выглядело бы как 0x1239 в шестнадцатеричной.

Используя семисегметнтый индикатор можно выводить не только цифры, но еще и буквы. Попробуйте вывести некоторые слова, составленные из цифр шестнадцатеричной системы, например beaf.

Бегущая строка

Подумайте, как можно было бы вывести бегущую строку на индикаторе и выведите HELLO PEOPLE.

Подсказка: заведите дополнительный буфер на 15 символов и передавайте окно из 4 символов функции dyn_display каждые M итерацией цикла, так чтобы было достаточно времени для чтения текста.

Идеальный антидребезг

Здесь приведен надежный алгоритм для опроса кнопки:

  1. Если кнопка нажата, то выставить флаг и сбросить счетчик;
  2. Если флаг выставлен, то инкрементировать счетчик до некоторое порогового значения, к примеру 5, и подождать 10 мс;
  3. Если значение счетчика больше порогового, то кнопка действительно нажата, сбросить флаг;
  4. Перейти на шаг 1.

Таким образом, если во время нажатия кнопки произойдет дребезг, то только последний стабильный переход в состояние 0 даст досчитать счетчику и выполнить обработчик нажатий. Предшествующий стабильный переход 1 будет постоянно сбрасывать счетчик, ровно как и частые переходы с периодом меньше 50 мс. Грубо говоря, это обычный фильтр низких частот, не пропускающий сигнал с частотой выше 20 Гц.

debouncer.jpg

Антидребезг на диаграмме