Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: volatile всё-таки нужен или нет?
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > MCS51, AVR, PIC, STM8, 8bit
sigmaN
Имеется программный ШИМ:
Код
typedef struct
{
    uint8_t    counter;
    uint8_t    top;
    uint8_t    compare_val;
    uint8_t    pin;
    uint8_t    enabled;
    volatile uint8_t    *port;        
} pwm_channel;

static pwm_channel    channels[PWM_MAXCHANNELS];


ISR(TIMER1_COMPA_vect)
{
    uint8_t    i;
    
    for ( i = 0; i < chnls_cnt; i++ )
    {        
        if ( ! channels[i].enabled )
            continue;
        
        channels[i].counter++;
        if ( channels[i].counter == channels[i].compare_val )
        {
            *channels[i].port |= 1 << channels[i].pin;        
        }
        else
        {    
            if ( channels[i].counter >= channels[i].top )
            {
                channels[i].counter = 0;
                *channels[i].port &= ~(1 << channels[i].pin);                                
            }    
        }                        
    }    
}

При этом channels[i].enabled, channels[i].top и channels[i].compare_val меняются посредством вызова функции spwm_configchannel(), нужно ли делать их volatile и почему всё работает и без этого?
Верно ли утверждение, что все глобальные переменные, доступ к которым осуществляется как в прерывании, так и в основном цикле нужно обязательно объявлять с volatile?
Я в принципе так всегда и делал, но вот тут вдруг всё работает даже с -O3 blink.gif

P.S. скурил Nine ways to break your systems code using volatile.. страшно. Щас ещё баръеры памяти расставлять побегу )
xemul
Цитата(sigmaN @ Mar 9 2013, 06:18) *
При этом channels[i].enabled, channels[i].top и channels[i].compare_val меняются посредством вызова функции spwm_configchannel(), нужно ли делать их volatile и почему всё работает и без этого?
Верно ли утверждение, что все глобальные переменные, доступ к которым осуществляется как в прерывании, так и в основном цикле нужно обязательно объявлять с volatile?

volatile должны быть объявлены глобальные переменные, которые и _изменяются_ в прерывании, и используются вне прерывания.
channels[i].enabled, channels[i].top и channels[i].compare_val в прерывании не изменяются, и им volatile'ность не требуется.
Используется ли вне прерывания channels[i].counter++, не понятно.
Тем не менее, прерывание TIMER1_COMPA при исполнении spwm_configchannel() может привести к сбою формирования ШИМ, если не подстелить соломки.
(н-р, из приведённого кода не очевидно, что в spwm_configchannel() после "channels[i].enabled = 0" выполняется что-то, устанавливающее пин в определённое состояние, вроде "*channels[i].port &= ~(1 << channels[i].pin);", или что не приключится пропуск такта ШИМ при изменении channels[i].compare_val, или что ...; но, возможно, оно Вам и не актуально)
Сергей Борщ
QUOTE (sigmaN @ Mar 9 2013, 04:18) *
Верно ли утверждение, что все глобальные переменные, доступ к которым осуществляется как в прерывании, так и в основном цикле нужно обязательно объявлять с volatile?
Да, верно.

QUOTE (xemul @ Mar 9 2013, 10:20) *
volatile должны быть объявлены глобальные переменные, которые и _изменяются_ в прерывании, и используются вне прерывания.
channels[i].enabled, channels[i].top и channels[i].compare_val в прерывании не изменяются, и им volatile'ность не требуется.
А вот это неверно. Если оставить их без volatile, то компилятор может в основном цикле просто закешировать их значение в регистрах и никогда не складывать в память. А может вообще выкинуть все операции с этими переменными, ибо в основном цикле есть только запись в эти переменные, но нет чтения. Следовательно, результат этой записи никому не нужен и считать его тоже не нужно.

QUOTE (sigmaN @ Mar 9 2013, 04:18) *
но вот тут вдруг всё работает даже с -O3
Если хотите после обновления компилятора или изменения какой-нибудь галочки в его настройках или изменения исходника долго и с удовольствием искать, почему же все вдруг перестало работать - оставьте так. Если же будете добавлять volatile, то в местах, где есть несколько доступов к одной переменной вроде вот этого:
CODE
        channels[i].counter++;
        if ( channels[i].counter == channels[i].compare_val )
используйте временную не-volatile переменную:
CODE
        uint8_t counter_cache = channels[i].counter++;
        if ( counter_cache == channels[i].compare_val )
xemul
Цитата(Сергей Борщ @ Mar 9 2013, 13:01) *
А вот это неверно. Если оставить их без volatile, то компилятор может в основном цикле просто закешировать их значение в регистрах и никогда не складывать в память. А может вообще выкинуть все операции с этими переменными, ибо в основном цикле есть только запись в эти переменные, но нет чтения. Следовательно, результат этой записи никому не нужен и считать его тоже не нужно.

Тогда необходимо объявлять volatile любую переменную, которая в одной единице компиляции только читается, а в другой - только пишется.
sigmaN
Цитата
Используется ли вне прерывания channels[i].counter++, не понятно.
Тем не менее прерывание TIMER1_COMPA при исполнении spwm_configchannel() может привести к сбою формирования ШИМ, если не подстелить соломки.
да да, знаю. В моём случае никаких проблем это не вызывает.

Цитата
Если хотите после обновления компилятора или изменения какой-нибудь галочки в его настройках или изменения исходника долго и с удовольствием искать, почему же все вдруг перестало работать - оставьте так.
Вот да! Я так и думал!

Цитата
Если же будете добавлять volatile, то в местах, где есть несколько доступов к одной переменной вроде вот этого: используйте временную не-volatile переменную:
Хороший совет. Спасибо!
Herz
Цитата(Сергей Борщ @ Mar 9 2013, 11:01) *
Если же будете добавлять volatile, то в местах, где есть несколько доступов к одной переменной вроде вот этого:
Код
        channels[i].counter++;
        if ( channels[i].counter == channels[i].compare_val )
используйте временную не-volatile переменную:
Код
        uint8_t counter_cache = channels[i].counter++;
        if ( counter_cache == channels[i].compare_val )

Это чтобы при прерывании между обращениями к переменной не пришлось работать с разными её значениями?
_Pasha
Немного о другом.
Зачем в теле цикла инкрементировать счетчик для каждого канала, если он для всех один?
У Вас есть варианты, когда каналы совсем независимые? channels[i].top разный?
ps
CODE
typedef struct
{
uint8_t compare_val;
uint8_t pin;
uint8_t enabled;
volatile uint8_t *port;
} pwm_channel;

static volatile pwm_channel channels[PWM_MAXCHANNELS];
static volatile uint8_t counter, top;

ISR(TIMER1_COMPA_vect)
{
uint8_t i;
if(++counter > top) counter = 0;

for ( i = 0; i < chnls_cnt; i++ )
{
if ( ! channels[i].enabled ) continue;
if(counter == channels[i].compare_val)
*channels[i].port |= 1 << channels[i].pin;
else if (!counter)
*channels[i].port &= ~(1 << channels[i].pin);
}
}
sigmaN
Цитата
Это чтобы при прерывании между обращениями к переменной не пришлось работать с разными её значениями?
но ведь в примере мы уже в прерывании. Это чтобы полностью не связывать руки оптимизатору. Программист то точно знает, что после channels[i].counter++; эта переменная не изменится и её можно закешировать для дальнейшего использования в цикле.

Цитата
У Вас есть варианты, когда каналы совсем независимые? channels[i].top разный?

Да. Каналы абсолютно независимы. И top и compare_val задаются индивидуально. Это пасхалка, чтоб музычку сыграть на форсунках )))
Herz
Цитата(sigmaN @ Mar 9 2013, 14:01) *
но ведь в примере мы уже в прерывании. Это чтобы полностью не связывать руки оптимизатору. Программист то точно знает, что после channels[i].counter++; эта переменная не изменится и её можно закешировать для дальнейшего использования в цикле.

Тогда ничего не понял. Если она точно не изменится, зачем её кэшировать?
sigmaN
В приведенном примере после инкремента счётчика я в цикле дважды сравниваю его с другими переменными. А ведь может быть такое, что не два раза, а 30 раз или 100. И здесь кэширование в локальную переменную даст выигрыш, потому что компилятор сможет оптимизировать доступ, вместо того чтобы каждый раз вычитывать её из памяти. И как раз тот факт, что эта переменная не изменится(и только программист это знает наверняка) позволяет нам её закешировать и дать компилятору добро на оптимизацию.
Herz
Понятно, спасибо. Хотя я отнёс бы такой случай к исключительным и кэшировал, только если действительно нужно много обращений. 30 или 100? Даже не могу себе такое представить...
sigmaN
Ну 30 или 100 это я так, для контраста придумал )
Хотя... не так уж и фантастично звучит, когда цикл проходится по нескольку десятков объектов...может быть не в прерывании, может быть прерывание запрещено или стоит какой-то иной флаг, который гарантирует, что сейчас можно volatile переменную безболезненно закешировать и выиграть на этом...а может быть и не на AVR всё это происходит ))
Тут как-бы сама идея интересная же.
XVR
Цитата(xemul @ Mar 9 2013, 14:20) *
Тогда необходимо объявлять volatile любую переменную, которая в одной единице компиляции только читается, а в другой - только пишется.
Нет. Компилятор производит межпроцедурный анализ, что бы определить, какие переменные и где читаются и пишутся. Если компилятор не может произвести такой анализ (ну например у него нет режима оптимизации полной программы, такого как LTO), то он будет придерживаться пессимистических предположений, и считать что все (ну почти все) глобальные переменные могут измениться при вызове неизвестной ему функции. Но сами места вызовов он все же видит. Т.е. он считает, что если никаких функций не вызывалось, то и измениться ничего глобального не могло (я несколько упрощаю). С прерываниями все гораздо хуже - компилятор не знает, когда они происходят, так что либо он должен считать, что где угодно может произойти прерывание, которое может изменить что угодно (а это сразу ставит крест на любых оптимизациях с участием не локальных переменных), либо отдать это на откуп программисту (с модификатором volatile)

xemul
Цитата(XVR @ Mar 11 2013, 12:36) *
Нет...

Вообще-то, примерно это я и написал (и даже перечислил, то, что не требует объявления volatile, т.к. волатильно _не_изменяется_; даже то, что волатильно изменяется, но опять же не требует volatile).
Отквоченная Вами фраза - доказательство (точнее, в данном случае, опровержение другого утверждения) от противного, ПМСМ, довольно очевидное.
sigmaN
Цитата(xemul @ Mar 9 2013, 11:20) *
volatile должны быть объявлены глобальные переменные, которые и _изменяются_ в прерывании, и используются вне прерывания.
channels[i].enabled, channels[i].top и channels[i].compare_val в прерывании не изменяются, и им volatile'ность не требуется.
Используется ли вне прерывания channels[i].counter++, не понятно.
Хотелось-бы пруфлинк, если можно. Сколько копал на тему volatile - нигде не видел подобного разделения на то где требуется, а где не требуется volatile.
xemul
Цитата(sigmaN @ Mar 11 2013, 14:58) *
Хотелось-бы пруфлинк, если можно. Сколько копал на тему volatile - нигде не видел подобного разделения на то где требуется, а где не требуется volatile.

ISO9899 Programming Languages C
Цитата
5.1.2.3 Program execution
...
8
EXAMPLE 1
An implementation might define a one-to-one correspondence between abstract and actual
semantics: at every sequence point, the values of the actual objects would agree with those specified by the
abstract semantics. The keyword volatile would then be redundant
.
...

Можно также посмотреть п.п. 4, 5, 9.
sigmaN
ой не знаю не знаю...
An implementation might define... а мэй нот дэфайн...
что делать, придется мне почитать стандарт.

ну вот, а чуть ниже
Цитата
Alternatively, an implementation might perform various optimizations within each translation unit, such
that the actual semantics would agree with the abstract semantics only when making function calls across
translation unit boundaries. In such an implementation, at the time of each function entry and function
return where the calling function and the called function are in different translation units, the values of all
externally linked objects and of all objects accessible via pointers therein would agree with the abstract
semantics. Furthermore, at the time of each such function entry the values of the parameters of the called
function and of all objects accessible via pointers therein would agree with the abstract semantics. In this
type of implementation, objects referred to by interrupt service routines activated by the signal function
would require explicit specification of volatile storage, as well as other implementation-defined
restrictions.
xemul
Цитата(sigmaN @ Mar 11 2013, 22:46) *
а мэй нот дэфайн...

Если сомневаетесь, загляните в ассемблерный листинг. У меня почему-то есть уверенность, что там ничего лишнего не соптимизировалось. Да и сами говорите "тут вдруг всё работает даже с -O3".

Процитированный Вами п.9 о том, что усилия одного оптимизатора могут привести к странноватым результатам, о чём писал Сергей Борщ, а усилия другого - не привести, и что знакомство с "implementation-defined restrictions" приветствуется.
В этом смысле volatile на всё, поминаемое в прерываниях - платформо- и компиляторо-независимая панацея.
Но мне проще подстелить соломки какого-либо вида.
Andron77
Нашел понятное обьяснение про volatile переменные с примерами.
Нажмите для просмотра прикрепленного файла
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.