После ознакомления с устройством 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). Индикатор имеет примерно следующий вид:
Семисегментный индикатор
Устройство имеет 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
итерацией цикла, так чтобы было достаточно времени для чтения текста.
Здесь приведен надежный алгоритм для опроса кнопки:
- Если кнопка нажата, то выставить флаг и сбросить счетчик;
- Если флаг выставлен, то инкрементировать счетчик до некоторое порогового значения, к примеру 5, и подождать 10 мс;
- Если значение счетчика больше порогового, то кнопка действительно нажата, сбросить флаг;
- Перейти на шаг 1.
Таким образом, если во время нажатия кнопки произойдет дребезг, то только последний стабильный переход в состояние 0
даст досчитать счетчику и выполнить обработчик нажатий. Предшествующий стабильный переход 1
будет постоянно сбрасывать счетчик, ровно как и частые переходы с периодом меньше 50 мс. Грубо говоря, это обычный фильтр низких частот, не пропускающий сигнал с частотой выше 20 Гц.
Антидребезг на диаграмме