Этот урок будет больше практическим, и мы попробуем управлять светодиодами и кнопкой.
Давайте попробуем реализовать бегущий огонь, то есть поочередное включение и выключение светодиодов.
Откроем в IARe проект с предыдущего урока и перенесем функцию включения светодиодов внутрь бесконечного цикла.
Сбросим все светодиоды – переведем их в состояние Reset.
Попробуем включать светодиоды поочередно. Для этого опишем беззнаковую целую переменную i размером 8 бит с начальным значением 0, которая будет принимать значения от 0 до 7 по циклу и организуем ветвление switch.
Изначально в цикле выключаем все светодиоды.
Проверяем значение переменной i. В случае нуля включаем светодиод на линии PE8, в случае 1 – на линии PE9 и так далее.
Повторим строчку case 8 раз для всех линий портов.
Увеличиваем значение i на 1 и берем остаток от деления i на 8.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* 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();
/* USER CODE BEGIN 2 */
uint8_t i = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
| GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
switch (i)
{
case 0: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET); break;
case 1: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET); break;
case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET); break;
case 3: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET); break;
case 4: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET); break;
case 5: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET); break;
case 6: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET); break;
case 7: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET); break;
}
i++;
i %= 8;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Попробуем выполнить проект по шагам. Для этого зайдем в свойства проекта – правая кнопка ->Options и отключим оптимизацию кода – во вкладке C/C++ Compiler -> Optimisation поставим None.
Откомпилируем проект и загрузим в отладочную плату.
Для пошаговой отладки используются горячие клавиши
- F11 – с заходом внутрь тела функции
- F10 - для случая, когда функция выполняется за один шаг отладки.
Нажимаем F10 для выполнения по шагам. Сбросили все линии порта GPIOE и перешли к проверке значения i. Значение i равно нулю, поэтому включаем светодиод на линии PE8, и он действительно светится! Остальные светодиоды – «молчат».
Увеличиваем значение i на 1 и повторяем цикл. Теперь включается светодиод на линии PE9 и так далее по циклу.
Но если мы остановим отладку – выберем Debug -> Stop Debugging, то мы увидим, что все светодиоды светят одновременно, но тусклее, чем на прошлом уроке.
Это происходит из-за инерции человеческого зрения. Дело в том, что состояние светодиодов меняется слишком быстро, чтобы человеческий глаз мог это уловить. Такой же эффект используется, например, для динамической индикации, когда светодиоды, соответствующие сегментам цифры каждого разряда включаются на короткое время. А в целом человек воспринимает, что все разряды светятся одновременно.
Вернемся к нашему проекту и попробуем все-таки реализовать бегущий огонь. Введем временную задержку между переключениями светодиодов. Для формирования задержки используется функция HAL_Delay(), аргументом которой является величина задержки в миллисекундах. Пусть переключение осуществляется каждые пол секунды.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* 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();
/* USER CODE BEGIN 2 */
uint8_t i = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
| GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
switch (i)
{
case 0: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET); break;
case 1: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET); break;
case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET); break;
case 3: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET); break;
case 4: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET); break;
case 5: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET); break;
case 6: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET); break;
case 7: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET); break;
}
i++;
i %= 8;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Добавилась строка 19
Загрузим программу в отладочную плату и запустим на выполнение. Нажимаем F5, и мы видим, что светодиоды стали переключаться раз в полсекунды.
Завершаем отладку.
Работа с кнопкой
Теперь давайте попробуем переключать светодиоды по нажатию кнопки.
Ищем в файле драйвера stm32f3xx_hal_gpio.c функцию, которая считывает состояние линии ввода-вывода.
Это функция
которой в качестве аргументов передаются имя порта GPIOx и номер линии GPIO_Pin, которую необходимо считать.
Копируем функцию и вставляем в наш цикл. Проверяем состояние линии PA0, куда подключена кнопка. И в случае если кнопка нажата – состояние GPIO_PIN_SET – выполняем все последующие действия.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* 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();
/* USER CODE BEGIN 2 */
uint8_t i = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(500);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
| GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
switch (i)
{
case 0: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET); break;
case 1: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET); break;
case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET); break;
case 3: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET); break;
case 4: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET); break;
case 5: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET); break;
case 6: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET); break;
case 7: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET); break;
}
i++;
i %= 8;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Добавились строки 20, 21, 37.
Загружаем программу в плату и видим, что теперь бегущий огонь появляется только тогда, когда нажата кнопка. Когда кнопка не нажата – светит один светодиод, который включился последним.
Но как же сделать так, чтобы при каждом нажатии кнопки включался следующий светодиод? Для начала уберем функцию задержки. А теперь посмотрим на процесс нажатия кнопки.
Говоря «нажатие кнопки», мы на самом деле подразумеваем два действия:
- нажатие кнопки
- и ее отжатие.
И нас интересует именно момент изменения состояния кнопки, то есть переход ее из состояния GPIO_PIN_RESET в состояние GPIO_PIN_SET.
Чтобы зафиксировать этот момент давайте введем дополнительную переменную flag, которая будет принимать значение 1 в состоянии кнопки set и 0 в состоянии кнопки reset. При этом в момент перехода кнопки из одного состояния в другое значение переменной flag не будет совпадать со значением, считанным с линии порта, где размещена кнопка. Вот этим мы и воспользуемся для проверки состояния перехода.
Переходим к проекту и добавляем переменную flag с начальным значением 0.
Если кнопка нажата и значение переменной flag равно 0, то мы как раз поймали тот самый момент перехода кнопки из нулевого состояния в единичное. Выполняем все те же действия и дополнительно присваиваем переменной flag значение 1.
Добавим еще одну проверку – если кнопка не нажата, присваиваем переменной flag значение 0.
В данном случае альтернативное условие else было бы не совсем уместно, поскольку первый оператор проверяет одновременное выполнение сразу двух условий. Если хотя бы одно из них не выполняется (кнопка нажата, но flag = 1), программа будет выполнять код по else и снова сбросит flag в 0. А нам это не нужно. Нам нужно дождаться, пока кнопка будет отжата, и только после этого сбросить значение переменной flag.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* 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();
/* USER CODE BEGIN 2 */
uint8_t i = 0;
uint8_t flag = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if ((HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) && flag == 0)
{
flag = 1;
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
| GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
switch (i)
{
case 0: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET); break;
case 1: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET); break;
case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET); break;
case 3: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET); break;
case 4: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET); break;
case 5: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET); break;
case 6: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET); break;
case 7: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET); break;
}
i++;
i %= 8;
}
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
flag = 0;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Загружаем программу в отладочную плату и запускаем на выполнение.
Мы получили то, что и хотели. Теперь светодиоды переключаются при каждом нажатии кнопки.
Простите за беспокойство. Похоже драйвера слетели. после установки программы Flash Loader все заработало. (В самой программе ничего не делал.) Просто достаточно было одной ее установки.
Знаю такую проблему, что когда пользуешься ST-linkом из-под ST Visual Programmer, а потом переходишь в IAR, то программатор не видится. Нужна перезагрузка, в лучшем случае — IARa, а в худшем — компа.
Страно, на моей плате STM32F407VG Discovery почему-то кнопка Юзер имеет дребезг.
Вы как то дорабатывали плату?
Нет, не дорабатывала. Видимо, это просто свойство моей платы 🙂
Хотя по-хорошему, нужно дребезг обрабатывать программным способом.
Но я не стала загромождать урок. Если интересно, могу привести фрагмент кода, как можно обработать дребезг.
Для того чтобы избавиться от дребезга, пришлось настроить скорость порта на 125 кГц. (Все что выше дребезжало)
Отсюда возник вопрос: Можно ли настроить конкретный вывод порта на нужную скорость, чтобы не нагружать процессор лишней работой по обработке дребезга контактов?
Если вам не сложно, то приведите ваш код, было бы очень интересно посмотреть как вы программно решаете данную задачу.
Уж больно изящные решения у вас выходят.
Скорость переключения вывода можно настроить для случая когда вывод сконфигурирован как цифровой выход. При этом настройка производится во вкладке Configuration -> GPIO для соответствующего вывода:
<img src='/wp-content/uploads/2017-06-28_16-17-59.png' />
А вот — пример кода обработки дребезга
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
while (1)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
{
k++;
if (k > 16) k = 16;
} else {
k—;
if (k < 0) k = 0;
}
if (k >= 16)
{
// кнопка нажата
} else if (k == 0) {
// кнопка отжата
}
HAL_Delay(…); // некоторая задержка
// другие операции
}
Окирпичелся мой STM. Хотя работал он, сразу после прошивки. После переподключения, мигает красный светодиод (возле USB програматора) и все. Что ему не понравилось, не знаю. Я ниже 125 КГц частоту не ставил. Хотя до этого я ставил частоту в 50 Гц. И работал же.
При попытке стереть память (ST visual) выдает ошибка Stlink error
Нет ли у вас случайно опыта реанимации стмки?