2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




На страницу 1, 2  След.
 
 видимость глобальной переменной в С для avr
Сообщение23.05.2018, 21:05 
Столкнулся с такой ситуацией: при объявлении глобальной переменной и последующем её изменении в обработчике прерываний, она то ли вовсе не меняется, то ли не видна в основной программе. Вот код:
код: [ скачать ] [ спрятать ]
Используется синтаксис C
int st=0;


ISR (ADC_vect)                   // функция обработки прерываний АЦП
{

st++;

}


int main(void) {  

// -- конфигурирование выводов --
DDRD |= (1 << PORTD0);              // пин PORTD0 порта DDRD как выход
PORTD &= ~(1 << PORTD0);

//       конфигурирование АЦП
ADMUX = 0b11000000;
ADCSRA =0b11101111;  

sei();                                  // ----- разрешить все прерывания  
     
// -- основной цикл программы --
while (1)                              
     {
         
          if (st>5000)                    
               PORTD |= (1 << PORTD0);  // высокий уровень на пин PD0 порта DDRD
          else
                PORTD &= ~(1 << PORTD0); // низкий  уровень на пин PD0 порта DDRD
                           
                                         
         if (st>10000)
                         st=0;
   
     }
return 0;
}
 


К порту PD0 подключен светодиод, по которому я и сужу о работе.
Должно быть так: по каждому прерыванию АЦП значение счётчика увеличивается на 1 ,
по достижении $st=5000$ на PD0 подаётся высокий уровень и диод должен загореться. Через такое же время счётчик, когда $st=10000$, $st$ обнуляется и диод должен гаснуть.

Но диод не загорается совсем.

P.S: При непосредственном переключении внутри бесконечного цикла
Используется синтаксис C
while (1)                              
     {
         
         PORTD |= (1 << PORTD0);
        _delay_ms(3000);
        PORTD &= ~(1 << PORTD0);
               
     }
 

диод мигает.

АЦП тоже работает, так как при изменении состояния PD0 в обработчике прерываний АЦП, диод загорается при прикосновении рукой к выводу ADC0, а при удалении руки - он гаснет.

Используется синтаксис C
ISR (ADC_vect)                   // функция обработки прерываний АЦП
{

       if (ADCW>750)
                 PORTD |= (1 << PORTD0);
        else
         PORTD &= ~(1 << PORTD0);
}
 


Думаю тут всё дело в переменной st, но в чём конкретно - понять не могу ...

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение23.05.2018, 21:40 
Andrey_Kireew
На 90% проблема что переменная st не однобайтовая, а значит операции с ней точно не атомарные. Объявите её байтовой и возможно всё заработает, хотя код и останется неправильным.
Правильный код - с атомарными обращениями к переменной вне процедуры прерывания. Вариантов реализации много, самый простой - на время обращения к переменной запрещать вообще все прерывания (и не забывать разрешать после операций с переменной, в том числе и при возникновении ошибок и исключений). Например весь код
Код:
if (st>10000) st=0;
надо забирать в атомарность, а не только отдельно чтение и отдельно запись. Я частенько не парюсь и добавляю две процедуры
Код:
int atomReadInt(int * ptr) {
int temp;
  asm("cli"); temp = * ptr; asm("sei"); return temp;
}
void atomWriteInt(int * ptr, int value) {
  asm("cli"); * ptr = value; asm("sei");
}
//Пример использования: не if(st>5000), а if(atomReadInt(st)>5000).
Но в том if выше даже это не спасёт.
И кстати операция st=0 (как и чтение переменной) тоже не атомарная, это минимум две (или четыре, смотря сколько байтов переменная) команды записи в память - и между ними тоже может придти прерывание и получится бред. Операции чтения и записи атомарные только для байтовых переменных, но для битовых - снова не обязательно, зависит от компилятора, для надёжности лучше считать не атомарными.
В общем изучайте инфу об атомарных операциях и думайте.

Дальше разбираться честно говоря неохота.

-- 23.05.2018, 21:44 --

PS. Так что название топика левое, проблема вовсе не в видимости переменной.
PPS. И почему снова не пользуетесь подсветкой синтаксиса? С ней текст программы понятнее.

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение23.05.2018, 23:03 
Спасибо большое Dmitriy40 теперь появилась ясность.
"Засунул" содержимое бесконечного цикла прямо в обработчик прерывания - всё работает как надо.

Пытался делать 2 счётчика char или проверять только верхний байт st через указатель (чтобы было обращение только к атомарным переменным) - всё без толку, диод не светится. Будто бы переменную просто не видно.

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение23.05.2018, 23:14 
Если бы её было не видно - компилятор ругнулся бы на неизвестное ему имя. Вероятно под "не видно" Вы подразумевали отличие адресов переменной в прерывании и главной программе, но это можно проверить по асм тексту или в отладчике или просто поверить компилятору что всё правильно.
Деление счётчика на два байта атомарность операций над всем счётчиком не даст разумеется, лишь над байтами, да и то, не полную (конструкция if(st>50) st=0; целиком атомарной не станет).

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 00:10 
Dmitriy40 в сообщении #1314450 писал(а):
Деление счётчика на два байта атомарность операций над всем счётчиком не даст разумеется, лишь над байтами, да и то, не полную (конструкция if(st>50) st=0; целиком атомарной не станет).


я не делил счётчик на 2 байта, а делил на 2 счётчика, да и условный оператор этот вынес в обработчик, так - тоже не работает:

код: [ скачать ] [ спрятать ]
Используется синтаксис C
unsigned char st=0, st2=0;


ISR (ADC_vect)                   // функция обработки прерываний АЦП
{

st++;
if(st==255)
        st2++;

if (st2>40)
        st2=0;
 

}


int main(void) {                 // начало программы

................................
     
// -- основной цикл программы --
while (1)                              
     {
         
                 if (st2>20)                     // сравнение с порогом    
                         PORTD |= (1 << PORTD0);  // высокий уровень на пин PD0 порта DDRD
                  else
                          PORTD &= ~(1 << PORTD0);

     }
return 0;
}
 


теперь то точно всё атомарное.

На счёт несоответствия адресов переменной - спасибо за подсказку, может оно и так.
Я вот ещё думаю, может проблема в том, что я использую режим автоматического преобразования. Там флажок ADSC "преобразование запущено" никогда не сбрасывается в "0". Но не знаю как это может повлиять ...
asm - кода у меня нет, сразу hex код генерируется.

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 00:25 
Проблема в том, что компилятор не знает, что переменная может измениться за пределами функции main() и оптимизирует чтение из переменной только один раз. Чтобы этого не было, добавьте ключевое слово volatile к объявлению переменной: volatile int st=0;

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 00:40 
Огромное спасибо venco,
действительно , сделал как Вы посоветовали и стало работать как надо!

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 00:48 
Andrey_Kireew в сообщении #1314457 писал(а):
так - тоже не работает:
А вот тут я уже не понимаю почему не работает, должно. Возможно причину указал venco и после оптимизации там действительно чёрти что.
Флаг ADSC ни при чём, он и не должен сбрасываться, преобразования же идут. Факт завершения определяется по ADIF, но у вас очевидно ADIE=1 и сразу же вызывается прерывание со сбросом ADIF, так что тут всё работает правильно.
Насчёт hex only, разбираться в нём это изврат разумеется, поищите в настройках своего компилятора галочку типа Generate asm, в некоторых (если не почти всех) она есть (вот тут по первой же ссылке из гугла кажется сразу несколько вариантов получения асм текста из студии: https://www.avrfreaks.net/forum/how-see ... avr-studio ).

-- 24.05.2018, 00:50 --

Хм, таки дело в компиляторе, надо же, получается я пользуюсь слишком старым/кривым, который такое не оптимизирует.

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 01:06 
Хорошо, буду иметь в виду про asm код, но так как проблема решена, разбираться в нём пока наверное не стоит, тем более, что я не очень то в нём и понимаю.

-- 24.05.2018, 02:11 --

У меня WinAvr2010, тоже не новый, из консоли работает ... это наверное всё потому, что я не люблю обновления :)

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 01:11 
Разумеется. Это лишь мне, извращенцу, интересно во что компилятор превращает такой красивый и понятный С код. :D

-- 24.05.2018, 01:35 --

Что характерно, добавление volatile мой компилятор интерпретировал как-то странно: не стал помещать переменную в регистры, но и никаких признаков атомарности в коде я не увидел.
Вот исходник с сокращениями:
Используется синтаксис C
volatile unsigned short Cnt = 0;
ISR(T0OVF) {//Прерывание по таймеру
  Cnt++;
}

void main () {
  while(1) Cnt--;
}
И скомплились оба обращения к переменной в почти идентичный (разница в adiw или sbiw) ассемблерный код:
Код:
lds  R30,_Cnt
lds  R31,_Cnt+1
adiw R30,1
sts  _Cnt,R30
sts  _Cnt+1,R31
И никакой защиты от вызова прерывания в середине процедуры уменьшения в основной программе нет!

Andrey_Kireew
Потому я бы не стал утверждать что теперь всё работает. Скорее Вы просто не дождались появления глюка, но при долгой работе он обязательно вылезет (прерывание попадёт ровно между командами обращения к переменной).
Ну или Ваш компилятор сильно умнее моего. ;-)
Но про атомарность операций читать всё равно придётся.

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 02:09 
Да, всё верно, я сейчас почитал про volatile. Этот спецификатор заставляет компилятор размещать переменную в ОЗУ, а не в регистрах, так как в регистрах она может "быть не точной" (уж не знаю, что под этим понимается). Как раз рекомендуется использовать в таких случаях, когда переменная меняется в прерываниях. Где то пишут, что вместо этого можно принудительно отключать прерывания.

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 02:25 
Andrey_Kireew
Скачал WinAvr2010, поставил, скомпилил свой пример им (avr-gcc) - разница несущественная, вот что получилось в асме:
Код:
lds  r24,Cnt
lds  r25,(Cnt)+1
sbiw r24,1
sts  (Cnt)+1,r25
sts  Cnt,r24
Запись обратно в другом порядке, скобочки добавил, регистры другие, но никакой атомарности всё равно не замечено.

Ну а про запрет прерываний на время работы с переменной в основной программе я сразу сказал, как один из возможных вариантов.

По идее volatile используется для адресации аппаратных регистров: данные в них могут меняться независимо от программы (как например ADCW объявлена) и потому хранить копию в регистрах для оптимизации нельзя. Грубо говоря всего лишь запрет кэширования, но не атомарность операций.

PS. Асм текст можно получить в .s файле если добавить компилятору опцию -save-temps. А с опцией -S он на получении асм и остановится, дальше компиляция не пойдёт. Ну просто Вам для информации.

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 12:51 
СпасибоDmitriy40 ! можно будет потом asm код оптимизировать.
Только я не совсем понял, как записать команду:
Код:
avr-gcc -s prog_asm.s -mmcu atmega8 -DF_CPU=4000000L -O3 prog.c

так правильно?

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 13:45 
Волатиле и не будет (и не обязано) как-то гарантировать или вообще иметь какое-либо отношение к атомарности. Это всего лишь указание компилятору не умничать и не оптимизировать кот типа
Используется синтаксис C
uint8 a = 1;
while (a) {}

который терпеливо ждет изменение (зануление) переменной в прерывании для продолжения основного кода (понятно что это блокирует поток выполнения, но отчасти пример искусственный, хотя и имеет право быть в определенных ситуациях).

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

 
 
 
 Re: видимость глобальной переменной в С для avr
Сообщение24.05.2018, 16:35 
Andrey_Kireew в сообщении #1314550 писал(а):
так правильно?
Нет, -s prog_asm.s лишнее, вместо него надо добавить -save-temps:
Код:
avr-gcc -save-temps -mmcu atmega8 -DF_CPU=4000000L -O3 prog.c
Появится кучка временных файлов (в том числе .i и .o) и среди них и prog.s с асм текстом.

Кстати, я предпочитаю настройки указывать в тексте исходника, а не в параметрах вызова компилятора, и тип контроллера, и его частоту. Пример:
Используется синтаксис C
#define __AVR_ATmega8__ //Имя константы смотреть в io.h
#define F_CPU   4000000UL
И тогда команда вызова сокращается до
Код:
avr-gcc -save-temps -Os prog.c

 
 
 [ Сообщений: 17 ]  На страницу 1, 2  След.


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group