Прерывания: использование ключевого слова «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()

, 👍4


2 ответа


Лучший ответ:

4

Я думаю, что проблема в 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; ) и использовать новый тип везде.

,

4

Правильное объявление указателя может быть совсем не очевидным, особенно когда речь идет о const и volatile (которые синтаксически эквивалентны). Ваша проблема возникла из-за путаницы между «volatile pointer to struct» и «pointer to volatile struct». Видите разницу? На словах это довольно ясно. В C это заставляет вас — и меня — бежать к таблице приоритетов в вашем руководстве по C.

Я держу под рукой копию статьи Дэна Сакса "Const T vs. T Const" как раз на этот случай — похоже, мне всегда нужно в нее заглядывать.

И пока вы там, ознакомьтесь также с некоторыми из других его статей. Я узнаю что-то новое каждый раз, когда читаю одну.

,