Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Вариант реализации атомарного счетчика
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
turnon
Нужен счетчик количества микросекунд, прошедших с момента старта МК (STM32).

Насколько мне известно, переменные длинее байта обновляются не за одну команду МК. И надо чтобы в момент обновления была уверенность, что переменную в это время не читают, потому как она скорее всего будет иметь некорректное значение (1 байт обновился, второй еще и нет и т.д.)

Вот набросал такой счетчик, для проверки атомарности используется флаг _busy. Имеет право на жизнь такая конструкция или чего-то не учел?

Код
class AtomicUint64
{
  private:
    volatile uint8_t _busy;
    volatile uint64_t _counterBackup;
    volatile uint64_t _counterValue;
  public:
    AtomicUint64(){
      _busy = 0;
      _counterBackup = 0;
      _counterValue = 0;
    }
    
    inline void inc(uint8_t value = 1){
      _counterBackup = _counterValue + value;
      _busy = 1;
      _counterValue += value;
      _busy = 0;
    }
    
    inline uint64_t get(){
      if (_busy)
        return _counterBackup;
      else    
        return _counterValue;
    }
};

extern AtomicUint64 gTick1MsecCounter;


Как используется. В прерывании 1 мsec вызывается gTick1MsecCounter.inc(), в остальном коде где нужно знать текущее значение счетчика - gTick1MsecCounter.get()
menzoda
Цитата
Насколько мне известно, переменные длинее байта обновляются не за одну команду МК.

Нет, зависит от архитектуры.

Цитата
Имеет право на жизнь такая конструкция или чего-то не учел?

Не учел. Представь такую ситуацию: функция get вызывается из фона, проверяется флаг busy. Так как он равен нулю, то осуществляется переход к return. Чтобы возвратить 64-битное значение нужно скопировать его из памяти в регистры R0 и R1. Копирование происходит по 32 бита. Одна часть скопировалась. Тут происходит прерывание в котором вызывается inc, которая увеличивает значение счетчика. Прерывание завершается и управление возвращается get, которая копирует оставшиеся 32 бита, потенциально изменившиеся в прерывании. В итоге получается хрень.

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

Код
static uint64_t counter;

void inc(uint32_t value)
{
    __disable_interrupt();
    counter += value;
    __enable_interrupt();
}

uint64_t get(void)
{
    __disable_interrupt();
    uint64_t copy = counter;
    __enable_interrupt();  

    return copy;
}
Сергей Борщ
Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:
1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).
2) Копируете в еще одну временную переменную старшее слово счетчика
3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную. Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.
menzoda
Цитата(Сергей Борщ @ Aug 30 2015, 14:10) *
Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.

Так тут ведь та же самая проблема будет.
zltigo
QUOTE (Сергей Борщ @ Aug 30 2015, 14:10) *
Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:
1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).

Да
QUOTE
2) Копируете в еще одну временную переменную старшее слово счетчика

Да
QUOTE
3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную.

Да
QUOTE
Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.

Нет. В общем случае надо повторять в цикле с пункта 1). Просто неизвестно сколько времени проходит между обращениями и счетчик уже может прокрутиться на все младшие биты.
Собственно предложенное это классика для считывания аппаратных счетчиков, которые нельзя остановить.
Сергей Борщ
Цитата(zltigo @ Aug 30 2015, 14:47) *
Просто неизвестно сколько времени проходит между обращениями и счетчик уже может прокрутиться на все младшие биты.
Ну это же несерьезно даже для счетчика из 8-битных чисел wink.gif
turnon
Цитата(Сергей Борщ @ Aug 30 2015, 14:10) *
Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:
1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).
2) Копируете в еще одну временную переменную старшее слово счетчика
3) Сравниваете старшую часть первой переменной со второй. Если они одинаковы - отдаете первую переменную. Если они разные - считываете счетчик в первую переменную еще раз и отдаете ее.

Чего-то не пойму принцип работы. Почему на шаге 2 именно старшее слово? Меняться начинает со старшего слова?
Сергей Борщ
Цитата(menzoda @ Aug 30 2015, 14:34) *
Так тут ведь та же самая проблема будет.
Какая та же самая? Если старшие байты совпадают, то младший гарантированно относится к текущему значению старшего.
zltigo
QUOTE (Сергей Борщ @ Aug 30 2015, 20:37) *
Ну это же несерьезно даже для счетчика из 8-битных чисел wink.gif

Лучше написать так, что-бы потом не думать серьезно это или несерьезно. Я не просто так написал, что это стандартный прием считывания аппаратных счетчиков. Мне, например, доводилось считывать счетчик тактов процессора в 16bit контроллере. Если речь идет о счетчике секунд, то тогда, конечно, уже можно рассуждать ну как же так он может неуспеть? А можно написать раз и навсегда, так, что бы и думать не пришлось.
maksimp
Цитата(Сергей Борщ @ Aug 30 2015, 14:10) *
Предложу еще один вариант, который не требует запрета прерываний и работает как с ОС, так и без нее:
1) Копируете во временную переменную счетчик (пусть он будет 64-битный, а копирование идет по 32 бита).
2) Копируете в еще одну временную переменную старшее слово счетчика
3) Сравниваете старшую часть первой переменной со второй.....

Цитата(turnon @ Aug 30 2015, 20:38) *
Чего-то не пойму принцип работы. Почему на шаге 2 именно старшее слово? Меняться начинает со старшего слова?

Это если в пункте 1 скопировать сначала старшее слово, затем младшее.

Более простой для понимания вариант, но не оптимальный.
1) Копируете во временную переменную счетчик.
2) Копируете в другую временную переменную счетчик.
3) Сравниваете, если совпало то возвращаете, иначе всё сначала - то есть идти на шаг 1

Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.