Прерывания: использование ключевого слова «volatile» с указателем структуры для устранения дребезга кнопок
Я написал небольшой скетч, предназначенный для Arduino Uno (ATmega328P), чтобы устранить дребезг механической кнопки, используя технику суммирования/интегрирования:
#include <IntegratingDebounce.h>
#define PIN_BTN 4
IntegratingDebounce *btn;
ISR (TIMER2_OVF_vect) {
ideb_poll(btn);
} // конец ISR переполнения таймера 2
void set_debounce_timer() {
TCNT2 = 0; // Очистить таймер
TCCR2A &= ~( bit( WGM21 ) | bit( WGM20 ) ); // Установить нормальный режим генерации сигнала
TCCR2B &= ~bit( WGM22 );
TIMSK2 |= bit( TOIE2 ); // Включить прерывание при переполнении
TCCR2B |= 7; // Запускаем отсчет времени таймера
} // set_debounce_timer()
void setup() {
Serial.begin(9600);
pinMode(PIN_BTN, INPUT_PULLUP);
ideb_init(&btn, PIN_BTN, 4);
set_debounce_timer();
} // настраивать()
void loop() {
while (ideb_read(btn) == HIGH); // ждем, пока контакт кнопки не перейдет в низкий уровень
Serial.println("Pressed!");
delay(100);
} // петля()
Структура IntegratingDebounce
хранит данные, необходимые для выполнения debounce. Я храню указатель на структуру, btn
, в глобальном пространстве памяти.
Процедура ISR переполнения timer2 периодически опрашивает состояние контакта кнопки, обновляет внутренний интегратор и соответственно устанавливает (также внутренний) отсеченный выход. Функция ideb_read()
считывает отсеченный выход из структуры.
У меня возникли проблемы с определением того, где следует разместить ключевое слово volatile
в объявлении указателя структуры btn
. Сначала я объявил его как:
volatile IntegratingDebounce *btn;
Я думал, что это сделает все поля в структуре, на которую указывает btn
, изменчивыми, но нажатия кнопок не определяются в loop(). Объявление btn
как изменчивого указателя, а не указателя на изменчивую структуру, генерирует ожидаемый вывод:
ИнтеграцияDebounce *volatile btn;
Это кажется обратным. Почему это работает?
IntegratingDebounce.h
#ifndef __INTEGRATING_DEBOUNCE_H__
#define __INTEGRATING_DEBOUNCE_H__
#include <Arduino.h>
#include <stdint.h>
extern "C" {
typedef struct IntegratingDebounce_S IntegratingDebounce;
typedef uint8_t ideb_size_t;
// Создаем структуру устранения дребезга и инициализируем.
void ideb_init(
IntegratingDebounce **ideb,
uint8_t pin, // вывод ввода-вывода для устранения дребезга
ideb_size_t clamp // зажим для устранения дребезга; максимальное значение
// внутренняя сумма может принимать на себя
);
// Опросить состояние вывода, интегрировать и обновить устраненный дребезг выходного сигнала.
void ideb_poll(IntegratingDebounce *ideb);
// Считываем состояние устранения дребезга контакта.
uint8_t ideb_read(IntegratingDebounce *ideb);
// Сбросить внутренний интегратор в состояние LOW или HIGH.
void ideb_reset(IntegratingDebounce *ideb, uint8_t state);
} // внешний C
#endif
IntegratingDebounce.c
#include "IntegratingDebounce.h"
struct IntegratingDebounce_S {
uint8_t pin; // вывод для устранения дребезга
uint8_t output; // вывод с устраненным дребезгом
ideb_size_t clamp; // зажим для устранения дребезга
ideb_size_t integrator; // фиксированная текущая сумма
};
void ideb_init(IntegratingDebounce **ideb, uint8_t pin, uint8_t clamp) {
*ideb = (IntegratingDebounce *)malloc(sizeof(IntegratingDebounce));
(*ideb)->pin = pin;
(*ideb)->clamp = clamp;
(*ideb)->output = 0;
(*ideb)->integrator = 0;
} // ideb_init()
void ideb_poll(IntegratingDebounce *ideb) {
// обновить интегратор на основе текущего состояния контакта:
// - если LOW, уменьшить целочисленный множитель; если HIGH, увеличить интегратор
// - ограничить интегратор значениями от 0 до 'clamp'
if (digitalRead(ideb->pin) == LOW) {
if (ideb->integrator > 0) ideb->integrator--;
} // если
else {
if (ideb->integrator < ideb->clamp) ideb->integrator++;
} // еще
// обновить вывод на основе значения интегратора; вывод
// не изменится, пока интегратор не достигнет предельного значения
if (ideb->integrator == 0 && ideb->output == HIGH) {
ideb->output = LOW;
} // если
else if (ideb->integrator == ideb->clamp && ideb->output == LOW) {
ideb->output = HIGH;
} // иначе если
} // ideb_poll()
uint8_t ideb_read(IntegratingDebounce *ideb) {
return ideb->output;
} // ideb_read()
void ideb_reset(IntegratingDebounce *ideb, uint8_t state) {
if (state == LOW) {
ideb->integrator = 0;
ideb->output = LOW;
} // если
else {
ideb->integrator = ideb->clamp;
ideb->output = HIGH;
} // еще
} // ideb_reset()
@w_hile, 👍4
2 ответа
Лучший ответ:
Я думаю, что проблема в ideb_read
, который ожидает «указатель на (неизменяемую) структуру», и поэтому его не волнует повторное чтение ideb->output
, если его можно как-то оптимизировать (например, поместив в свободный регистр).
IMHO ideb_read
должен иметь параметр, объявленный как (volatile IntegratingDebounce *ideb)
(чтобы знать, что структура, на которую указывает ideb
, является изменчивой) или использовать глобальную переменную btn
(которая является указателем на изменчивую структуру) вместо параметра указателя на неизменяемую структуру.
А почему работает второй вариант, так это потому, что он заставляет перечитывать параметр ideb_read
, и поэтому его нельзя где-то сохранить, а функция косвенно вынуждена ожидать измененный указатель, поэтому ей приходится использовать его «новое значение» и считывать структуру (так как это проще, чем гарантировать, что ideb
имеет то же значение, что и при последнем вызове, и поэтому вся структура там (которая объявлена как неизменяемая в заголовке ideb_read
) может быть кэширована)
Другая формулировка вышесказанного:
Объявляя volatile IntegratingDebounce *btn;
, основная программа в loop
просто берет значение btn
(которое является неизменяемым указателем на изменчивую структуру) и копирует это (неизменяемое) значение в параметр ideb
ideb_read, который является неизменяемым указателем на неизменяемую структуру. ideb_read
использует его таким образом, и компилятор каким-то образом смог кэшировать все это и поэтому не считывает ideb->output
снова, так как знает, что это часть неизменяемой структуры. (Поэтому это не работает)
Объявляя IntegratingDebounce *volatile btn;
, вы делаете btn
изменчивой, поэтому она считывается каждый раз, когда вызывается ideb_read
, а «новое фактическое значение» передается в (неизменяемый) параметр ideb
(которому таким образом присваивается «новое значение», и поэтому он не может кэшироваться между вызовами), и это приводит к считыванию (неизменяемой) даты из «нового» места, на которое указывает ideb
. Так что это работает (по ошибке).
Правильным решением было бы правильно объявить volatile IntegratingDebounce *btn;
в основной программе И правильно объявить uint8_t ideb_read(volatile IntegratingDebounce *ideb)
, чтобы функция ideb_read
каждый раз принудительно считывала значение, на которое указывает ideb
(что является правильным решением)
И его следует использовать также для всех других функций ideb_**
.
И я думаю, что было бы проще объявить некий typedef для этой изменчивой структуры (что-то вроде typedef struct { ... } volatile ideb_t;
) и использовать новый тип везде.
Правильное объявление указателя может быть совсем не очевидным, особенно когда речь идет о const
и volatile
(которые синтаксически эквивалентны). Ваша проблема возникла из-за путаницы между «volatile pointer to struct» и «pointer to volatile struct». Видите разницу? На словах это довольно ясно. В C это заставляет вас — и меня — бежать к таблице приоритетов в вашем руководстве по C.
Я держу под рукой копию статьи Дэна Сакса "Const T vs. T Const" как раз на этот случай — похоже, мне всегда нужно в нее заглядывать.
И пока вы там, ознакомьтесь также с некоторыми из других его статей. Я узнаю что-то новое каждый раз, когда читаю одну.
- Устранение дребезга кнопки с помощью прерывания
- Как прервать функцию цикла и перезапустить ее?
- Прерывание при нажатии кнопки + устранение дребезга
- Программное обеспечение, устраняющее дребезг кнопки при отпускании
- Почему необходимо использовать ключевое слово volatile для глобальных переменных при обработке прерываний в ардуино?
- Хорошая кнопка debouncing/Библиотека StateChange
- Серийное прерывание
- Влияет ли `millis()` на длинные ISR?