2014 dxdy logo

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

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




Начать новую тему Ответить на тему На страницу Пред.  1, 2, 3, 4, 5  След.
 
 Re: Странное поведение fuses в atmega8a
Сообщение30.04.2025, 16:26 


15/12/22
254
Dmitriy40 в сообщении #1684472 писал(а):
И если TWINT=1, то он будет обработан и сброшен где-то внутри обработчика

в этом и вопрос, если не выходить из обработчика то уже не нужно разрешать прерывание (1<<TWIE), но в программе оно всё равно разрешается, хотя и так уже разрешено (при TWINT=1), это не вызовет трудноуловимых ошибок? Может надёжнее этот цикл убрать, или так тоже надёжно?

Dmitriy40 в сообщении #1684472 писал(а):
Например нет обработки отсутствия ACK после адреса

если добавить default: и в нём поставить stop i2c, то это решит проблему? При любых неправильных статусах шина будет останавливаться и всё. Или это не решение?

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение30.04.2025, 17:10 
Заслуженный участник


20/08/14
12188
Россия, Москва
Missir в сообщении #1684475 писал(а):
в этом и вопрос, если не выходить из обработчика то уже не нужно разрешать прерывание (1<<TWIE), но в программе оно всё равно разрешается, хотя и так уже разрешено (при TWINT=1),
Ну разрешено и разрешено, когда выйдет - тогда и будет важно разрешено или нет, а до выхода - без разницы, avr не поддерживает вложенных прерываний (за исключением классов приоритета в более старших мегах). Т.е. до выхода из прерывания их можно что разрешать, что запрещать - сколько угодно, важно что останется после выхода.

Missir в сообщении #1684475 писал(а):
Может надёжнее этот цикл убрать, или так тоже надёжно?
Без разницы, на надёжность это не влияет, лишь на скорость.
Из минусов: на это время тормозится обработка всех других прерываний (и основного цикла), т.е. увеличивается лаг срабатываний того же таймера. Не частота, а именно лаг реакции на метку времени. В данном случае это пофиг, всё равно пока I2C не освободилась новую транзакцию начинать нельзя (хотя проверки что i2c свободна (например TWIE==0) у вас в таймере и нет!), но в других случаях полезно помнить.

Missir в сообщении #1684475 писал(а):
если добавить default: и в нём поставить stop i2c, то это решит проблему? При любых неправильных статусах шина будет останавливаться и всё. Или это не решение?
Это хорошее решение. Но не полное - если на шине глюк или есть другой мастер или завис девайс на шине, то может не получиться выдать стоп - могут не дать поднять SDA. Некоторые такие ситуации (довольно большая часть от всех) лечатся ожиданием внешнего стопа (от другого мастера или отпусканием шины от девайса), некоторые не лечатся вообще никак кроме снятия питания.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение30.04.2025, 21:00 


15/12/22
254
Dmitriy40 в сообщении #1684482 писал(а):
если на шине глюк или есть другой мастер или завис девайс на шине, то может не получиться выдать стоп

единственное, что мне пока приходит в голову - это запустить Watchdog и сбрасывать его в основном цикле по условию done=1
данные пришли - всё нормально, данных подозрительно долго нет - перезагрузка,
но тут 2 проблемы: 1 МК как то нужно вернуть в актуальное состояние, расписание событий ведь собьётся, но это наверное решаемо;
2 - если i2c зависнет из за slave, то перезагрузка МК может ничего и не дать, а вывода reset у slave нет, можно конечно питать от ножки МК через ключ, тогда получится сбросить просто отключением от питания, но ключ нужен с очень малым падением напряжения, там ведь vcc всего 3.3V.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение01.05.2025, 08:09 
Заслуженный участник


20/08/14
12188
Россия, Москва
Missir в сообщении #1684510 писал(а):
единственное, что мне пока приходит в голову - это запустить Watchdog и сбрасывать его в основном цикле по условию done=1
Идея сбрасывать собаку в основном цикле только при получении правильных пакетов очень правильная (только период собаки сделайте на несколько пакетов, чтобы случайный пропуск одного-двух не приводил к сбросу, это (таймер сработал второй раз, а предыдущего пакета не было) лучше обрабатывать самому в программе), но я смысла не вижу: i2c в меге вряд ли подвиснет настолько что понадобится ресет. Да и сигнал стоп в i2c ресет же не выдаст. Так что ресет не решает почти никакую из возможных проблем i2c. Но сам по себе Watchdog, как хорошая гарантия от зависаний МК, полезен.

Missir в сообщении #1684510 писал(а):
можно конечно питать от ножки МК через ключ, тогда получится сбросить просто отключением от питания, но ключ нужен с очень малым падением напряжения, там ведь vcc всего 3.3V.
Это Вы с чего взяли? На GY521 указано 3В-5В (там же кренка по входу) и ток макс. 4мА, значит от 3.3В допустимо падение напряжение на ключе до 0.3В, при токе 4мА это максимум 75Ом. Ключи в меге на токе 4мА дают падение 0.12В (Figure 29-20. I/O Pin Output Voltage vs. Source Current (VCC = 3V)).
Кроме того, можно включить внешний биполяр с резистором в базе, при токе 4мА падение указывают 0.04В (для BC857A). Или скажем дешёвый (2р в рознице) полевик BSS84 с сопротивлением максимум 10Ом (т.е. падение 0.04В) при напряжении на затворе от 2.5В (мне полевики нравятся больше биполяров). Или столь же недорогой (3р в рознице) и любимый IRLML2244 с сопротивлением 0.095Ом (падение 0.0004В) при напряжении на затворе от 1.5В.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение01.05.2025, 22:56 


15/12/22
254
Dmitriy40 в сообщении #1684531 писал(а):
GY521 указано 3В-5В (там же кренка по входу) и ток макс. 4мА, значит от 3.3В допустимо падение напряжение на ключе до 0.3В, при токе 4мА это максимум 75Ом. Ключи в меге на токе 4мА дают падение 0.12В

я мерил, там 4.5 ма, так же как и сам atmega8a пишут 5 ма, а берёт все 10, ну это ладно, я и имел в виду подключить GY521 через внешний ключ и управлять питанием ножкой atmaga8a, вешать прямо на ножку как то не очень, IRLML2244 думаю подойдёт отлично

Но я тут слегка усовершенствовал программу, запись по i2c была без использования прерываний TWI, решил это исправить:
код: [ скачать ] [ спрятать ]
Используется синтаксис C
volatile uchar wr_ok, rd_ok, addr, data;
volatile union {uchar byts[14]; short vals[7];} num;

.................

static inline uchar write_i2c(uchar waddr, uchar wdat)
{ GICR &= ~(1<<INT0); wr_ok=0; while (!rd_ok){}; addr=waddr;
  data=wdat; TWCR=(1<<TWIE)|(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);   // START i2c
  while (!wr_ok){}; return wr_ok;
}

ISR(INT0_vect)
{ GICR &= ~(1<<INT0); rd_ok=0; addr=0x3B;  
  TWCR=(1<<TWIE)|(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);              // START i2c
}

ISR(TWI_vect)
{   static uchar i;
    do
    { switch(TWSR & 0xF8)
      { case START:       TWDR = GY521<<1; TWCR = (1<<TWIE)|(1<<TWINT)|(1<<TWEN); break;            // SEND DEV ADDR
        case ADDR_WRITE:  TWDR = addr; TWCR = (1<<TWIE)|(1<<TWINT)|(1<<TWEN); i=0; break;           // SEND MEM ADDR                                                                                    
        case ADDR_MEMORY: if (!rd_ok) {TWCR = (1<<TWIE)|(1<<TWINT)|(1<<TWSTA)|(1<<TWEN); break;}    // RESTART i2c
                          if (!i) {TWDR=data; TWCR=(1<<TWIE)|(1<<TWINT)|(1<<TWEN); i++; break;}     // SEND DATA
                          wr_ok=1; TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO); break;                     // SUCCES STOP i2c  
        case RESTART:     TWDR = (GY521<<1)|0x01; TWCR = (1<<TWIE)|(1<<TWINT)|(1<<TWEN); break;     // SEND DEV ADDR FOR READ        
        case ADDR_READ:   TWCR = (1<<TWIE)|(1<<TWINT)|(1<<TWEN)|(1<<TWEA); break;                   // requesting data
        case NEXT_DATA:   num.byts[i^1]=TWDR;                                                       // []=2,1,4,3,6,5,8,7, ...
                          if (i==12) TWCR = (1<<TWIE)|(1<<TWINT)|(1<<TWEN);                         // PENULTIMATE BYTE
                          else TWCR = (1<<TWIE)|(1<<TWINT)|(1<<TWEN)|(1<<TWEA);          
                          i++;  break;
        case END_DATA:    num.byts[12]=TWDR; rd_ok=1;
                          TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO); break;                              // LAST BYTE, STOP i2c                  
        default:          TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO); if (!rd_ok) rd_ok=2; else wr_ok=2;  // UNSUCCES STOP i2c
                           
      }
    } while (TWCR&(1<<TWINT));  
}

int main(void)
{
  uchar i;

  unsigned short ubrr;               
  ubrr = F_CPU/(16UL*9600)-1;        
  UBRRH = (uchar) (ubrr >> 8);      
  UBRRL = (uchar) ubrr;          
  UCSRB = (1<<RXEN)|(1<<TXEN);   
  UCSRC = (1<<URSEL)|(3<<UCSZ0);    

  // I2C Bus initialization
  TWBR = (F_CPU / 400000UL - 16)/2;  // скорость I2C для 400 кГц

  // Interrupt settings
  DDRD &= ~(1<<PD2);                
  PORTD |= (1<<PD2);                
  GIFR = (1<<INTF0);            
  MCUCR |= (1<<ISC01)|(0<<ISC00);    
  sei();                          
 
  // GY-521 settings
  do
  {
    i=write_i2c(0x6B, 0x80);        
   _delay_ms(100);
   i+=write_i2c(0x68, 0x07);        
   _delay_ms(100);
   i+=write_i2c(0x6B, 0x01);        
   i+=write_i2c(0x6A, 0x04);      
   i+=write_i2c(0x19, 0x32);        
   i+=write_i2c(0x1A, 0x05);                                        
   i+=write_i2c(0x23, 0xF8);      
   i+=write_i2c(0x38, 0x01);    
   i+=write_i2c(0x6A, 0x04);    
  } while (i!=9);
   
  while (1)
  { .............................
    GICR  |= (1<<INT0);              // разрешить INT0
    if (rd_ok==1) { send_uart('\r'); for (int i=0; i<7; i++) write_uart(num.vals[i]); }
  }  
  return 0;
}
 



теперь ничего не работает, данные не идут по uart, не могли бы Вы Dmitriy40
подсказать, где допущена ошибка?

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение02.05.2025, 01:41 


15/12/22
254
Я нашел ошибку, нужно было при объявлении инициализировать rd_ok=1, без этого функция записи ждала когда завершится чтение,
я также убрал из начала обработчика INT0 запрет на прерывания GICR &= ~(1<<INT0); а из основного цикла убрал разрешение прерываний INT0 GICR |= (1<<INT0);
всё это поместил в функцию записи, вот так:
Используется синтаксис C
static inline uchar write_i2c(uchar waddr, uchar wdat)
{ GICR &= ~(1<<INT0); wr_ok=0; while (!rd_ok){}; addr=waddr;
  data=wdat; TWCR=(1<<TWIE)|(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);   // START i2c
  while (!wr_ok){}; GICR |= (1<<INT0); return wr_ok;
}
 

теперь прерывание запрещается только на время записи по i2c, а остальное время разрешено,
не знаю хорошо так или нет и насколько надёжно, но всё работает

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение02.05.2025, 10:35 
Заслуженный участник


20/08/14
12188
Россия, Москва
Зачем такие сложности, ведь инициализация GY521 делается один раз, можно было её делать до инициализации таймера и без прерываний I2C, как в самом начале была write_i2c(), ведь в это время скорость неважна. Вот как инициализировалось, так включать таймер (предварительно сбросив флаг прерываний I2C). Ну конечно если delay_ms() не завязана на таймер. А если завязана, то можно прерывания от I2C разрешать в таймере только если выставлен специальный флаг (который выставить после инициализации GY521). И не надо никаких wr_ok, addr, data и заморочки с прерываниями.

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

(Дальнейшее развитие)

Очередной бесполезный совет: по хорошему, надо писать нормальный драйвер I2C, чтобы он умел отсылать сразу строку из буфера (если длина больше нуля) и потом с повторным i2c_start принимать строку в буфер (если задана ненулевая длина). Вот прям произвольной длины, и включая и адрес. По своим прерываниям, которые никогда запрещать и не надо. Это понятно сложнее, хотя почтим всё уже готово, зато пишется один раз, а использовать можно с чем угодно. Ещё можно добавить сигналы об ошибках (отсутствие ответа девайса (ACK на адрес), ошибки при обмене - и как-то обрабатывать их на верхнем уровне, не в драйвере. А потом просто пишете в строку что надо отослать, выставляете её длину, выставляете сколько принять, выдаёте i2c_start, начинается отсылка, ждёте пока не примет всё, анализируете, красота же. Для полной универсальности можно и адреса строк передавать, и отсылаемой и принимаемой (причём адреса могут и совпадать).
Сразу об этом не говорил так как без нормального понимания работы i2c такое написать весьма нетривиально и лучше сначала пройтись по всем тонкостям и лишь потом делать универсально.
Аналогично можно и с uart замутить, с ним даже проще, меньше состояний, правда приём и передача могут быть одновременными.
Вообще, работа посимвольно - это базовый уровень, мне быстро надоедает и пишу сразу передачу и приём строк произвольной длины. Передачу - отдельно из ОЗУ и из флеш (люблю константные сообщения выводить, а пересылать их в ОЗУ - извращение, да и места может не хватить).

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение02.05.2025, 13:30 


15/12/22
254
В итоге сделал так, прерывания можно не трогать, теперь запись и чтение начинаются только когда шина свободна:
код: [ скачать ] [ спрятать ]
Используется синтаксис C
#define SEND (1<<TWIE) |(1<<TWINT)|(1<<TWEN)
#define STOP (1<<TWSTO)|(1<<TWINT)|(1<<TWEN)

static inline void write_i2c(uchar waddr, uchar wdat)
{ while (cond){}; cond=2; addr=waddr; data=wdat; TWCR=SEND|(1<<TWSTA); }  // START i2c

ISR(INT0_vect)
{ while (cond){}; cond=1; err=0; addr=0x3B; TWCR=SEND|(1<<TWSTA); }       // START i2c

ISR(TWI_vect)
{   static uchar i;
    do
    { switch(TWSR & 0xF8)
      { case START:       TWDR = GY521<<1; TWCR = SEND; break;            // SEND DEV ADDR
        case ADDR_WRITE:  TWDR = addr; TWCR = SEND; i=0; break;           // SEND MEM ADDR                                                                                    
        case ADDR_MEMORY: if (cond==1) {TWCR = SEND|(1<<TWSTA); break;}   // RESTART i2c
                          if (!i) {TWDR=data; TWCR=SEND; i++; break;}     // SEND DATA
                          TWCR=STOP; cond=0; break;                       // SUCCES STOP i2c  
        case RESTART:     TWDR = (GY521<<1)|0x01; TWCR = SEND; break;     // SEND DEV ADDR FOR READ        
        case ADDR_READ:   TWCR = SEND|(1<<TWEA); break;                   // requesting data
        case NEXT_DATA:   num.byts[i^1]=TWDR; if (i==12) TWCR = SEND;     // []=2,1,4,3,6,5,8,7, ...
                          else TWCR = SEND|(1<<TWEA); i++;  break;        // PENULTIMATE BYTE                  
        case END_DATA:    num.byts[12]=TWDR; TWCR=STOP; cond=0; break;    // LAST BYTE, STOP i2c                  
        default:          TWCR=STOP; err|=cond; cond=0;                   // UNSUCCES STOP i2c
                           
      }
    } while (TWCR&(1<<TWINT));  
}
 

заодно определяются и ошибки чтения/передачи err, запись блоками здесь реализовать несложно, но ненужно, адреса нужных регистров не идут подряд, даже при инициализации. На счёт сложностей, не уверен. Мне кажется, что этот код получился и быстрее и понятнее и даже меньше того что было раньше. На счёт надёжности правда непонятно, отлавливает он все ошибки, или нет?

Как на Ваш взгляд Dmitriy40, в таком виде будет хорошо? или есть недостатки?

Кстати, заметил, что FT232 что то глючит, сморю на экране какой то мусор, думал программа сбоит, отключил RxD от платы, подключил снова - всё пошло нормально. Т.е. если вставлять в usb FT232 прямо с подключенной платой (запитывается она сейчас тоже от него), то на экране появляется мусор. Если вставить одну FT232 а потом к ней подключить плату, то всё работает нормально. Непонятно с чем это связано, ведь раньше такого не было.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение02.05.2025, 16:58 
Заслуженный участник


20/08/14
12188
Россия, Москва
Missir в сообщении #1684730 писал(а):
Как на Ваш взгляд Dmitriy40, в таком виде будет хорошо? или есть недостатки?
Бесконечное ожидание в прерывании совсем не есть гуд.
Например если I2C шина занята в момент срабатывания таймера, то прерывание повиснет в ожидании освобождения шины, но та никогда не освободится! Так как прерывание I2C не сможет прервать сработавшее прерывание таймера (мега8 не поддерживает вложенные прерывания) с бесконечным ожиданием. И всё, зависон.
По идее в таймере надо если cond!=0 то просто выходить из прерывания без запуска I2C. Будет потеряна метка времени, ну так I2C всё равно занята, опросится в следующий раз.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение02.05.2025, 18:42 


15/12/22
254
Dmitriy40 в сообщении #1684753 писал(а):
По идее в таймере надо если cond!=0 то просто выходить из прерывания без запуска I2C

это понятно, учту, в принципе наверное правильно, прерывания поступают постоянно, если уж не успевают обрабатываться то и нечего их копить, просто обрабатывать те что получается, а остальные пропускать,
а как с while(cond) в функции write_i2c ? там можно так оставить, или лучше тоже как то переделать?

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение02.05.2025, 21:52 
Заслуженный участник


20/08/14
12188
Россия, Москва
Missir в сообщении #1684767 писал(а):
а как с while(cond) в функции write_i2c ? там можно так оставить, или лучше тоже как то переделать?
А вот там цикл не в прерывании (Вы же не собираетесь её вызывать из прерываний?) и потому ничему не мешает.
Но нужно обеспечить чтобы таймер не начал работать до завершения инициализации GY521, а то он запустит опрос до инициализации. И через cond этого не сделать, таймер может успеть занять шину раньше write_i2c(). Т.е. должно быть сначала полная инициализация GY521, только потом разрешение прерываний таймера (или вообще его настройка и запуск).
Плюс конструкция while (cond){}; cond=2; - ненадёжна: между выходом из while и cond=2 (это ведь разные команды, проверка в цикле и присваивание) может случиться прерывание таймера и cond станет =1. Будет сбой. Ждать надо как-то так: while (1) { cli(); if (cond==0) cond=2; sei(); if(cond==2) break; } - т.е. проверять и менять с запрещёнными прерываниями (по крайней мере от таймера, не обязательно общими). Либо ждать как есть while(cond) {} cond=2;, но перед этим запретить прерывания от таймера чтобы он не мог занять шину (и не забыть разрешить их обратно, можно сразу после cond=2). В прерываниях такие сложности с cond не нужны, их никто прервать и вмешаться не может.

Вообще работа с переменными, которые могут меняться в прерываниях - трудное дело, одной volatile недостаточно для арбитража доступа, она фактически арбитража и не делает, лишь запрещает оптимизацию компилятору. Например вот у Вас vals[] двухбайтовый, Вы понимаете что при обращении к нему в основной программе старший и младший байт одного числа могут оказаться из разных ответов? Вот пришло прерывание i2c чтения второго байта ровно между чтениями байтов двухбайтового значения и привет. Никакая volatile этому не мешает. И такой глюк в тестах можете ждать годами, пока ещё прерывание попадёт точно между двумя командами. Легче поймать глюк с обновлением части vals[], когда 7 чисел будут от разных ответов (может даже и не двух соседних, а больше, если обрабатывать подольше). Volatile помогает с арбитражем лишь с однобайтовыми переменными, они пишутся и читаются одной командой (если это не команда модификации типа += или ++, которых фактически две). По хорошему, надо сделать как раньше было, с done=1 по готовности данных, который сбрасывается в 0 только после обработки vals[] (в основной программе), а в прерывании i2c проверять и если всё ещё done=1 к моменту заполнения byts[], то или игнорировать пакет (с выдачей i2c_stop и пропуском done=1) или выйти из прерывания i2c без сброса флага прерывания с запретом новых прерываний. А разрешать прерывания i2c лишь в основной программе при сбросе done=0 (и не в таймере! или по крайней мере только если done=0). Тогда если пакет не успел обработаться, то новый ответ будет приниматься лишь по завершению обработки предыдущего и конфликта в vals[] не будет.
Через cond (сбрасывать его лишь после обработки vals[] в основной программе) этого не сделать: если вызовете write_i2c, и до момента while (cond) {} сработает таймер и займёт шину, то vals[] обработан не будет и cond останется ненулевым (основная программа не получит управление, ждём же в write_i2c).
Это как бы всё не про работу с i2c, в том числе по прерываниям, приём то в byts[] будет правильный, это отдельный вопрос про арбитраж меняемых независимо многобайтных переменных (vals[] в прерывании и в основной программе). Я как-то в начале предлагал при завершении приёма в прерывании сразу же скопировать принятые данные в другой буфер (vals2[], тоже volatile) с выставлением done=1 и в основной программе работать только с ним, сбрасывать done в 0 только после обработки, а в прерывании не копировать и не выставлять done=1 если он не сброшен. Памяти лишь на 14 байтов больше, зато больше времени на обработку принятых данных и гарантия что все данные в буфере строго из одного ответа.
Собственно это стандартные вопросы/проблемы работы в многозадачной среде - а прерывания вместе с основной программой можно считать двумя параллельными вычислительными потоками - и для доступа к любым общим переменным требуется арбитраж. Исключать его можно лишь в исключительных случаях, когда точно гарантированно отсутствие коллизий при доступе (как например только чтение или только запись (но не модификация!) однобайтовых переменных). Volatile не выоплняет арбитраж, лишь запрещает компилятору оптимизировать работу с такими переменными (например временно хранить в регистрах вместо чтения каждый раз из памяти если переменная нужна 100500 раз по коду). Хотите писать надёжные программы с прерываниями - или вообще не пользуйтесь общими переменными, или изучайте параллельное/многозадачное программирование (хотя бы основы), иначе будут постоянные глюки, очень трудно отлавливаемые тестами.

Missir в сообщении #1684767 писал(а):
в принципе наверное правильно, прерывания поступают постоянно, если уж не успевают обрабатываться то и нечего их копить, просто обрабатывать те что получается, а остальные пропускать,
Тут Ваша идея была понятна, типа если чуть-чуть не успели шину освободить, то немного подождать и счастье. Но засада в том что нет определения насколько велико это "чуть-чуть не успели" (и нет возможности шине освободиться пока ждём). Если строгая регулярность опроса не важна (а бывает что важна) и можно опрос делать с произвольным лагом, главное столько то раз в секунду, то можно удвоить частоту таймера и запускать опрос каждое чётное срабатывание если шина свободна, если же занята, то оставлять запрос на занятие шины (cond_future=1) и запускать в следующее нечётное срабатывание. А в write_i2c ждать не только cond=0 (свободной шины), но и cond_future=0 (шина не нужна таймеру). Тогда есть надежда что если в чётное срабатывание опрос не прошёл, то пройдёт в следующее нечётное. При успехе запроса cond_future сбрасывать. Ну а если уж в чётном срабатывании cond_future=1 - значит это пропуск опроса, шина была занята и в нечётном срабатывании. Это будет работать если шина не занимается более чем на половину периода опроса. Если может заниматься на 2/3 периода - частоту таймера утроить. Если на 3/4 - учетверить. Или уже забить и вернуться к обычной с пропуском целого такта.

Так что по поводу большей понятности кода Вы выше скорее погорячились. ;-)

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение02.05.2025, 23:57 


15/12/22
254
Dmitriy40 в сообщении #1684783 писал(а):
Вы же не собираетесь её вызывать из прерываний?) и потому ничему не мешает.
Но нужно обеспечить чтобы таймер не начал работать до завершения инициализации GY521, а то он запустит опрос до инициализации. И через cond этого не сделать, таймер может успеть занять шину раньше write_i2c(). Т.е. должно быть сначала полная инициализация GY521, только потом

сразу хотелось бы отметить, что функция write_i2c() не только для инициализации, она должна функционировать и во время работы, т.к. может появиться необходимость изменения настроек, калибровки GY521 и т.п. Но правда её запуски по сравнению с прерыванием INT0 будут очень редкими, важно, чтобы это не приводило к сбоям.
У меня идея такая: от while(cond){}; в ней никуда не деться, т.к. всё равно нельзя запускать запись пока шина занята, да она запустится между прерываниями INT0, но cond станет = 2 и пока не пройдёт вся обработка, ни обработчик, ни последующие вызовы этой функции на шину не повлияют. Запрещать прерывание INT0 смысла не вижу, проще пропускать пакеты, ведь в итоге, запрет прерываний точно также приведёт к потере пакетов. Всё правильно, или я что то упускаю?

Dmitriy40 в сообщении #1684783 писал(а):
Плюс конструкция while (cond){}; cond=2; - ненадёжна: между выходом из while и cond=2 (это ведь разные команды, проверка в цикле и присваивание) может случиться прерывание таймера и cond станет =1.

так и задумано, если вдруг стало cond=1 то значит уже начался приём, шина занята, пусть ждёт дальше. Приём у меня в приоритете. Смысл в том, что между внешними прерываниями будут приличные перерывы (иначе я просто не смогу обработать данные), и между ними будет вклиниваться write_i2c(). Вод почему нельзя её вызывать из прерываний, не понимаю, а это может понадобится. Ведь INT0 имеет наивысший приоритет, а остальные прерывания должны выполняться в оставшееся время. Кстати, из-за этого из обработчика INT0 действительно лучше выходить поскорее, а не ждать в цикле, чтоб не подвесить всё остальное, сделаю вместо while() if().

Dmitriy40 в сообщении #1684783 писал(а):
Я как-то в начале предлагал при завершении приёма в прерывании сразу же скопировать принятые данные в другой буфер (vals2[], тоже volatile) с выставлением done=1


сейчас у меня так:
код: [ скачать ] [ спрятать ]
Используется синтаксис C
int main(void)
{
  uchar i;
  static short buf[7];

  // USART initialization
  unsigned short ubrr;               // значение UBRR
  ubrr = F_CPU/(16UL*9600)-1;        // скорость обмена 9600 бит/с, !только если F_CPU/(16*9600) = round(F_CPU/(16*9600))
  UBRRH = (uchar) (ubrr >> 8);       // старшие биты UBRR
  UBRRL = (uchar) ubrr;              // младшие биты UBRR
  UCSRB = (1<<RXEN)|(1<<TXEN);       // включаем приёмник и передатчик
  UCSRC = (1<<URSEL)|(3<<UCSZ0);     // асинхронный режим, размер посылки 8 бит, проверка чётности отключена, 1 стоп-бит

  // I2C Bus initialization
  TWBR = (F_CPU / 400000UL - 16)/2;  // скорость I2C для 400 кГц

  sei();                             // разрешить прерывания глобально
 
  // GY-521 settings
  do
  { err=0;
    write_i2c(0x6B, 0x80);           // сброс регистров GY-521
    _delay_ms(100);
    write_i2c(0x68, 0x07);           // сброс аналоговых и цифровых цепей датчиков и i2c
    _delay_ms(100);
    write_i2c(0x6B, 0x01);           // тактирование от выхода гироскопа
    write_i2c(0x6A, 0x04);           // сброс FIFO
    write_i2c(0x19, 0x64);           // делитель частоты дискретизации 100
    write_i2c(0x1A, 0x06);           // установка ФНЧ на 5 Гц
                                     // чувствительость акселерометра
    write_i2c(0x23, 0xF8);           // загружать данные в FIFO
    write_i2c(0x38, 0x01);           // прерывания по готовности данных
    write_i2c(0x6A, 0x04);           // включить FIFO
    _delay_ms(100);
  } while (err & 2);                 // ошибки передачи

  // External interrupt settings
  DDRD &= ~(1<<PD2);                 // INT0 как вход
  PORTD |= (1<<PD2);                 // подтяжка PD2 к лог.1
  MCUCR |= (1<<ISC01)|(0<<ISC00);    // срабатывание по ниспадающему фронту
  GIFR = (1<<INTF0);                 // сброс запроса на прерывание
  GICR  |= (1<<INT0);                // разрешить INT0
 
  while (1)
  { if (!(cond | err))    
    { for (i=0; i<7; i++) buf[i]=num.vals[i];
      send_uart('\r'); for (i=0; i<7; i++) write_uart(buf[i]);
    }
  }  
  return 0;
}
 

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

Dmitriy40 в сообщении #1684783 писал(а):
можно удвоить частоту таймера и запускать опрос каждое чётное срабатывание если шина свободна, если же занята, то оставлять запрос на занятие шины (cond_future=1) и запускать в следующее нечётное срабатывание. А в write_i2c ждать не только cond=0 (свободной шины), но и cond_future=0 (шина не нужна таймеру). Тогда есть надежда что если в чётное срабатывание опрос не прошёл, то пройдёт в следующее нечётное

мне кажется пропуск пакетов приведёт примерно к тому же самому, это позволит если только в 2 раза сократить промежутки между пропусками, но в нормальном режиме предполагается, что все приходящие пакеты должны обрабатываться полностью, пропуски - это уже сбои, они допустимы только в моменты записи с помощью write_i2c, но думаю и этого можно избежать.
Нужно будет прикинуть время считывания по i2c, рискну предположить, что это не менее 20 байт, при частоте 400 кгц, получится наверное не более 1 мс, запись займёт ещё меньше. Если внешние прерывания будут поступать с частотой не более 100 гц, то всё успеет и отправиться и приняться с большим запасом. Так ведь? Здесь главное чтобы нигде ничего не подвисло.
Что касается частоты прерываний, то я это решил. Как выясняется в GY521 есть внутренний ФНЧ, и он кажется даже работает. Просто нужно выбрать нужную частоты среза и выставить в 2 раза большую частоту дискретизации. Это позволит избавится от необходимости усреднения данных средствами МК, и сохранит всю полезную информацию. Сейчас у меня в коде частота среда 5 Гц, а частота дискретизации 1000/100=10 гц. Для передачи по uart и отображения на мониторе этого более чем достаточно, но в перспективе, конечно нужно будет её немного увеличить. В общем проблему арбитража я планирую решить путём снижения sample rate.

Посмотрите пожалуйста Dmitriy40, правильно ли у меня сделана инициализация GY521, или есть смысл как то это изменить?

Еще 1 вопрос, можно ли подключать ножку МК прямо к затвору IRLML2244TRPBF, или между затвором и vcc поставить резистор? Эо чтобы можно было программно отключать GY521.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение03.05.2025, 01:31 
Заслуженный участник


20/08/14
12188
Россия, Москва
Missir в сообщении #1684792 писал(а):
так и задумано, если вдруг стало cond=1 то значит уже начался приём, шина занята, пусть ждёт дальше.
Похоже Вы не поняли: while(cond) видит cond=0 и выходит из цикла, но до момента cond=2 срабатывает прерывание таймера и ставит cond=1, выдавая при этом и старт на шину. Что будет дальше - вопрос тонкий, вдруг успеет обработаться и прерывание i2c и продолжит выдачу запроса чтения, а потом уже cond сменится на два и попытается выдаться новый старт прямо посреди обмена. Чтобы таких тонких вопросов не было - и надо не допускать таких двухсмысленностей.

Missir в сообщении #1684792 писал(а):
Вод почему нельзя её вызывать из прерываний, не понимаю, а это может понадобится. Ведь INT0 имеет наивысший приоритет, а остальные прерывания должны выполняться в оставшееся время.
Приоритет влияет только на порядок обработки возникших одновременно прерываний: если активны более одного флага прерываний, то обработается сначала с высшим приоритетом и только потом оставшиеся (по порядку приоритетов).
А почему нельзя вызывать write_i2c в том виде из прерываний я же объяснил: вложенные прерывания аппаратно не реализованы (а про программную реализацию я упорно молчу, это весьма нетривиально), сработало первое, вызвало write_i2c, и если шина оказалась занята (таймером), то цикл ожидания никогда не завершится, ведь некому сбросить cond в 0 - прерывания i2c выставляются, но не обрабатываются пока не завершится прерывание вызвавшее write_i2c.

Missir в сообщении #1684792 писал(а):
т.е. данные копируются в буфер, правда не volatile а просто static, так что все данные из одного и того же пакета,
Для buf[] volatile и не нужно, он же не меняется в другом потоке (в прерываниях). И static ему не нужно (глобальные и так всегда статик, а не глобальный buf в других функциях и не нужен).
А вот что данные правильные - это нет: кто вам гарантировал что в момент копирования buf[]=vals[] не возникнет прерывание по таймеру, не отработаются прерывания i2c и не появятся в vals[] новые данные? Только лично ваша надежда что if(cond) и for() buf[]=vals[] отработаются быстрее и такой накладки не произойдёт. Надежда, не гарантия! Сейчас работает, а потом, нагрузите вычислениями и другими прерываниями - и перестанет работать.

Missir в сообщении #1684792 писал(а):
Нужно будет прикинуть время считывания по i2c, рискну предположить, что это не менее 20 байт, при частоте 400 кгц, получится наверное не более 1 мс, запись займёт ещё меньше. Если внешние прерывания будут поступать с частотой не более 100 гц, то всё успеет и отправиться и приняться с большим запасом. Так ведь?
Нет, не так. I2C это не UART и не SPI master, его частота не фиксирована и может падать вплоть до нуля. Да, наверное конкретно GY521 не затягивает обмен, но в будущем могут быть и другие устройства на I2C шине, которые могут и снижать частоту тактирования - а ведь адрес устройств распознаётся (два раза, в команде записи и в команде чтения) всеми устройствами на шине, и сигналы SCL будут затягиваться по самому медленному устройству. Да, такие устройства (с замедлением обмена) редки, но всё же. Потому пока у вас только GY521 или EEPROM (они тоже не затягивают обмен вплоть до мегагерца)- всё работает, а потом, при расширении функционала - может раз и перестать работать. И хрен Вы это увидите без осцила! Ведь замедление тактирования SCL нигде не отображается, это нормальная стандартная ситуация, протокол I2C специально так и разработан чтобы позволять замедляться обмену до нужной каждому девайсу скорости.

Missir в сообщении #1684792 писал(а):
Посмотрите пожалуйста Dmitriy40, правильно ли у меня сделана инициализация GY521, или есть смысл как то это изменить?
Э нет, разбираться ещё и с GY521 мне влом.
Что вижу: перед while (err & 2) надо дождаться конца транзакции, delay_ms(100) ненадёжно (да и перебор), лучше while(cond) вместо неё, или любого другого флага что передача завершена.
Что прерывания (тьфу, они же не от таймера, с чего я вообще про него подумал?! они же внешние) разрешаются после инициализации - это хорошо.

Missir в сообщении #1684792 писал(а):
Еще 1 вопрос, можно ли подключать ножку МК прямо к затвору IRLML2244TRPBF, или между затвором и vcc поставить резистор? Эо чтобы можно было программно отключать GY521.
Можно, в этом и преимущество полевика, что не нужно ограничение тока затвора (ну если он не амперный как для импульсных бп). Резистор ставят для уменьшения помех (от броска большого тока в ёмкость затвора) и для защиты ножки от пробития полевика. Но тут напруга маленькая, 3В, а сопротивление канала ножки большое (десятки Ом), так что резистор считайте уже и так встроен в ножку. Да и частота переключений мизерная (важно что не сотни кГц). И время в активном состоянии полевика доли мкс, тоже пофиг, тем более на токе 4мА. Защищаться от пробития полевика ... ну, это по желанию (и умению правильно рассчитать номинал резистора), как-то не ожидаю от 3В и 5мА возможности пробития 12В (по затвору) и 4А полевика.
Ещё совет, тоже бесполезный (должны и так знать): не забывайте что под ресетом ножки вовсе не притянуты никуда! Потому если это (потенциал ножек под ресетом) важно, стоит ставить внешнюю подтяжку. Тем более для полевика, это биполяр не откроется от мелкой эфирной наводки (а они есть практически всегда!), ему ток базы подавай, а полевику хватит и наноампер зарядить ёмкость затвора. А МК может и секунды под ресетом сидеть (например при перепрошивке или BOD сработает или задержка для старта кварца на полсотни мс). Об этом вечно забывают. С подтяжкой преимущество полевика от биполяра исчезает - и там и там по одному резистору (хотя я предпочитаю и к биполяру ставить подтяжку, ток базы дело такое, то он есть, то его нет ... а тут хватит и десятка мкА чтобы его приоткрыть на 4мА нагрузки, но это лично моя заморочка). Но напомню что даже и голая ножка вполне может питать GY521, во всяком случае пока питание МК выше 3.15В.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение03.05.2025, 02:51 


15/12/22
254
Dmitriy40 в сообщении #1684796 писал(а):
while(cond) видит cond=0 и выходит из цикла, но до момента cond=2 срабатывает прерывание таймера и ставит cond=1, выдавая при этом и старт на шину. Что будет дальше

в этом слукчае сначала отработает обработчик INT0, так как он в приоритете, запустит прерывание TWI, далее до конца отработает автомат TWI и вернёт cond=0, функция всё это время будет ждать, так как прерывания в приоритете, только потом она присвоит cond=2 и начнёт запись. Пока программа в прерывании, насколько я знаю основное тело, где будет функция, не выполняется. Единственное что - здесь может быть пропущен момент считывания данных, так как cond=0 сразу же меняется на cond=2. Но тут при запуске чтения можно сбрасывать только первый бит cond а при запуске записи - только второй бит cond, соотв. при завершении их же и выставлять обратно.
По идее это решает проблему, или я не прав?

Dmitriy40 в сообщении #1684796 писал(а):
прерывания i2c выставляются, но не обрабатываются пока не завершится прерывание вызвавшее write_i2c.

т.е. говоря "нет вложенных прерываний Вы имеете в виду, что сами прерывания не прерываются, если уж оно обрабатывается, то обработается до конца, независимо от приоритета?

Dmitriy40 в сообщении #1684796 писал(а):
И static ему не нужно (глобальные и так всегда статик, а не глобальный buf в других функциях и не нужен

это на всякий случай, чтобы при выходе в прерывание ничего не случилось

Dmitriy40 в сообщении #1684796 писал(а):
кто вам гарантировал что в момент копирования buf[]=vals[] не возникнет прерывание по таймеру, не отработаются прерывания i2c и не появятся в vals[] новые данные? Только лично ваша надежда что if(cond) и for() buf[]=vals[] отработаются быстрее

Ну так если чтение длится 1 мс, и сразу после него начинается копирование, а до следующего чтения ещё10 или даже 100 мс, то как бы надежда на это имеется довольно таки сильная. Просто не забывать об этом и всё, а потом посмотрим.

Dmitriy40 в сообщении #1684796 писал(а):
лучше while(cond) вместо неё, или любого другого флага

там весь смысл в том, чтобы она прошла безошибочно, если была хоть одна ошибка - то по новой, задержка нужна чтобы дождаться завершения последней write_i2c(0x6A, 0x04); ведь после выхода из неё TWI ещё какое то время работает, нужно дождаться когда она завершится. Можно вместо этой задержки поставить while (cond&2){}; так нормально будет?

Dmitriy40 в сообщении #1684796 писал(а):
С подтяжкой преимущество полевика от биполяра исчезает - и там и там по одному резистору

да мне резистора как раз не жалко, главное чтобы нормально работало. Правильно я понял, что затвор подключить нужно прямо к ноге МК, и соединить их с землёй резистором на 100 кОм? Тогда при всех ресетах GY521 будет выключен, а как подам на ногу "1" то он и запустится. Так?

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение03.05.2025, 08:47 
Заслуженный участник


20/08/14
12188
Россия, Москва
Missir в сообщении #1684798 писал(а):
в этом слукчае сначала отработает обработчик INT0, так как он в приоритете, запустит прерывание TWI, далее до конца отработает автомат TWI и вернёт cond=0, функция всё это время будет ждать, так как прерывания в приоритете, только потом она присвоит cond=2 и начнёт запись.
Это только в случае если прерывания i2c будут идти одно за другим без перерыва, но такого не будет, при передаче байта по i2c времени хватит чтобы выйти из прерывания и попасть в основной цикл, где между while(cond) и cond=2 всего пара команд и они точно успеют отработать между прерываниями i2c до конца пакета.
И в любом случае так делать неправильно, полагаться на скорость генерации и обработки прерываний. Надо исключать такие моменты "гонок" (как это называется), это лишний фактор возможных сбоев.

Missir в сообщении #1684798 писал(а):
т.е. говоря "нет вложенных прерываний Вы имеете в виду, что сами прерывания не прерываются, если уж оно обрабатывается, то обработается до конца, независимо от приоритета?
Именно, любой обработчик прерывания больше никем не прервётся, выполнится до конца, следующий обработчик (может и этот же) вызовется лишь после выхода из текущего. Независимо от приоритета, который про другое.

Missir в сообщении #1684798 писал(а):
это на всякий случай, чтобы при выходе в прерывание ничего не случилось
Volatile сильно мешает компилятору и в итоге снижает скорость вычислений, так что просто так ставить его не нужно.

Missir в сообщении #1684798 писал(а):
то как бы надежда на это имеется довольно таки сильная.
Надежда, а не гарантия. Вы сами расставляете по коду источники возможных глюков. Ну, дело ваше.

Missir в сообщении #1684798 писал(а):
Можно вместо этой задержки поставить while (cond&2){}; так нормально будет?
Я ведь так и написал, while(cond), двойка не обязательна, единицы там быть не может ведь int0 ещё запрещены. А на ошибки проверит цикл do while после завершения транзакции.

Missir в сообщении #1684798 писал(а):
Правильно я понял, что затвор подключить нужно прямо к ноге МК, и соединить их с землёй резистором на 100 кОм? Тогда при всех ресетах GY521 будет выключен, а как подам на ногу "1" то он и запустится. Так?
Полевик P канальный, он включается в разрыв плюса,(очень не люблю рвать землю) как pnp биполяр, соответственно открывается затвором об землю (лог.0 на ножке). Подтяжка для закрытия нужна на плюс. 100кОм хороший номинал, вот 1МОм многовато (наводки могут и пересилить), а 10кОм току жалко (0.3мА, но всё же).

-- 03.05.2025, 09:12 --

Missir в сообщении #1684798 писал(а):
далее до конца отработает автомат TWI
В первом пункте неверна вот эта посылка, что весь автомат отработает до конца единым махом, без возврата управления в основную программу, этого не будет. За время пересылки байта в 22.5мкс обработчик i2c точно успеет завершиться и вернуться в основную программу, ещё и прилично времени ей оставить, иначе нет смысла городить огород с прерываниями.
Говорил же вам посмотреть на реальные времена обработки прерываний осциллографом. Даже если нет нормального, то его можно сварганить почти из чего угодно, хоть из программатора (пиков), хоть из другой меги (частоты сэмплирования 1МГц тут хватит, а такое мега успеет если аккуратно на SPI сэмплирование сделать).
Можно даже прямо на этой же меге сделать измерение реального времени, просто читать TCNT (в общем любой, хоть 0, хоть 1, хоть 2, какой запущен, но лучше не 1, он 16 битный, это просто дольше) и писать куда-то в две переменные, выставив флаг срабатывания, чтобы в основной программе вычесть одно из другого и получить точное (до 1-2мкс) время выполнения куска кода. Как уж его переслать в комп для анализа - дело второе, можно просто искать минимум и максимум и отсылать раз в секунду только их (ну ещё и количество срабатываний, к примеру). Это вообще получится полезный инструмент кстати, а пишется за полдня вместе с отладкой.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 75 ]  На страницу Пред.  1, 2, 3, 4, 5  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group