2014 dxdy logo

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

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




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


15/12/22
254
Dmitriy40 в сообщении #1684803 писал(а):
Это только в случае если прерывания i2c будут идти одно за другим без перерыва, но такого не будет, при передаче байта по i2c времени хватит чтобы выйти из прерывания и попасть в основной цикл,

я этого не могу понять, как такое возможно если выставлен флаг TWIE? Он выставляется ещё в обработчике INT0 или внутри write_i2c, и далее каждый раз, перед выходом из TWI. Так, что после выхода из TWI программа снова в него же и заходит, и так до удачного или неудачного завершения, когда флаг TWIE не выставляется. Сами же пишите, что онновной цикл не выполняется, до тех пор, пока не сбросятся все флаги прерываний. Как такое возможно? Это ведь противоречит алгоритму работы МК

-- 03.05.2025, 11:15 --

До меня дошло, TWIE не запускает, а просто разрешает прерывания

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


20/08/14
12188
Россия, Москва
Missir в сообщении #1684812 писал(а):
я этого не могу понять, как такое возможно если выставлен флаг TWIE? Он выставляется ещё в обработчике INT0 или внутри write_i2c, и далее каждый раз, перед выходом из TWI. Так, что после выхода из TWI программа снова в него же и заходит,
Вы походу спутали TWIE и YWINT - TWIE лишь разрешает прерывания (при TWINT=1), а сам флаг запроса прерывания - TWINT. И только когда выставлены оба (ещё и глобальный I в status) - происходит прерывание. При этом TWINT выставляется аппаратно, запись в него 1 означает не выставление, а его сброс (после обработки ситуации) и начало следующей операции на I2C шине. Если операция будет дольше чем окончание обработчика (и нет других разрешённых запросов прерываний), то управление вернётся в основной код. Для передачи байта за 22.5мкс это так и есть.
Непрерывно обработчик будет вызываться только если за время его окончания (break+while+return, это навскидку тактов 10-15 или 1-2мкс) успеет выставиться новый флаг прерываний TWINT (ну или другого разрешённого прерывания - тогда оно и вызовется). Такое может быть с сигналом start (и stop) и repeat start, но вряд ли с любым байтом (2мкс<22.5мкс), иначе прерывания будут только хуже и стоит оставить как было в самом начале без них.

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


15/12/22
254
Если тогда сделать так:
Используется синтаксис C
static inline void write_i2c(uchar waddr, uchar wdat)
{ while (!(GIFR & (1<<INTF0))){};  while (cond){}; cond=2; addr=waddr; data=wdat; TWCR=SEND|(1<<TWSTA); }
 

после запуска функция ждёт прерывания INT0, потом ждёт завершения транзакции, а потом сразу выполняется, в этот момент следующий INT0 ещё долго не поступит

-- 03.05.2025, 12:17 --

чем боятся, появится оно между while (cond){}; и cond=2; ведь проще его дождаться

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


20/08/14
12188
Россия, Москва
Да, так можно - если точно уверены что нет других прерываний которые могут вдруг забрать всё время между двумя тиками int0. Так плюсом ещё и максимизируется время на write_i2c до следующего тика int0.
Но ведь int0 могут и не приходить до инициализации GY521, это надо проверить по доке на него. А может GY521 вообще нет, а тики от него ждутся ... I2C в этом случае даст nACK на адрес и это можно обработать (например сообщить компу о проблеме или дёрнуть питание), а вот из цикла ожидания отсутствующих тиков не выйти.
Плюс при разрешённых прерываниях от int0 флаг INTF0 в основном коде (и write_i2c из него вызванной) никогда не обнаружите - как только он аппаратно установится, так следующим же тактом будет вызов обработчика прерываний и сброс INTF0. Я не уверен что даже теоретически можно попасть в это вот время между аппаратным выставлением INTF0 и переходом к обработчику.
Потому наиболее надёжно - на время while(cond) {} cond=2; просто запретить прерывания int0: INT0=0; while(cond) {} cond=2; INT0=1; - так сработавшие прерывания до момента запрета будут обработаны, а потом автомат I2C запустится на write_i2c, а прерывания если успеют придти до момента освобождения шины - проигнорируются (по if(!cond) return; в обработчике).

А ещё проще - вообще отказаться от прерываний ont0 и опрашивать флаг INTF0 в основном цикле, как выставился - сбросить и запустить автомат I2C на чтение данных, как закончит их обрабатывать, потом снова станет ждать INTF0. И не надо париться с арбитражем запуска автомата I2C ибо он запускается только из основного цикла (и вызванных из него функций), не прерываний. Ну а если ушли в write_i2c в основном цикле - то и не будет опроса INTF0. Как вернётся, так и продолжит опрос.

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


15/12/22
254
Dmitriy40 в сообщении #1684847 писал(а):
Но ведь int0 могут и не приходить до инициализации GY521

да, они не будут приходить, я не подумал, но можно ведь сделать GIFR |= (1<<INTF0), а потом, перед разрешением прерываний сбросить

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


20/08/14
12188
Россия, Москва
Так можно.

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


15/12/22
254
Dmitriy40 ещё такой вопрос, Вы вот пишите что при reset выводы atmega плавают. А потом что же? Какие там вообще стадии загрузки-перезагрузки? Могут ли они в определённый момент сконфигурироваться как выходы и встать в состояние "1", ещё до запуска программы? и сколько максимум длится процесс загрузки и запуска? это где нибудь описано?

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


20/08/14
12188
Россия, Москва
Под ресетом выводы не плавают (может я плохо выразился), они в Z-состоянии, т.е. отключены оба выходных полевика в КМОП, и P и N, и подтяжка тоже отключена. Но остаются защитные диоды к плюсу и земле. И остаётся конечное сопротивление закрытых полевиков (обычно это гигаомы, хотя ток Input Leakage Current I/O Pin указан максимум 1мкА, т.е. всего лишь 5МОм, маловато, думаю реально сильно больше, для аналогового входа ток 50нА и соответственно 100МОм). Так что в пределах -0.5В...+VCC+0.5В напряжение на выводе может быть любым - вот в этом смысле оно и плавает, например от любых наводок. В частности, наводки легко хватит для переключения входного состояния вывода и например выставления прерывания от него - просто от шума из эфира. Плюс к этому входной буфер порта потребляет ток при переключении состояния, потому общий ток может увеличиваться при наличии наводок и плавающих входах. Потому для любых входов кроме аналоговых входов, которые чисто в принципе могут оказаться подвешенными в воздухе (отвалился контакт или отсутствует плата/девайс или ещё что), настоятельно рекомендуется включать встроенную подтяжку или ставить внешнюю. Для аналоговых входов в более продвинутых мегах предусмотрено отключение входного цифрового буфера и зануление его (как и в этой по сигналу SLEEP, т.е. после команды Sleep если спячка разрешена и пока нет запросов разрешённых прерываний), т.е. читаться будет всегда лог.0 и не будет реагировать на наводки. Вам с мегой 8а только подтяжки, встроенная или внешние (ту можно и к земле). Если волнует ток потребления, иначе да и пусть щелкают.

После ресета, выводы так и остаются в этом состоянии вплоть до записи в PORT и DDR регистры управления портами. Кто именно произведёт эту запись - смотрите сами, обычно компилятор этого не делает, но если подключите какую библиотеку, она вполне может настраивать себе выводы при инициализации (что впрочем делается вызовом функции библиотеки).
Соответственно без разницы сколько будет длиться процесс загрузки (reset timeout, bod, старт кварца), когда он завершится, тогда кто-то и запишет новые значения в порты - и только тогда они изменят своё состояние. Обычно это начало main(), уже после настройки стека и обнуления ОЗУ (это делает сам компилятор С в прологе, его в принципе можно и посмотреть и даже поменять на свой, но как именно поищите сами конкретно для своего компилятора, и там асм код, не С).
Отдельно стоят исключения: выводы SPI если разрешено ISP (fuse SPIEN=0), вход RESET (если fuse RSTDISBL=1).
Вся информация по выводам есть в разделе I/O Ports доки. Там несколько запутанно, особенно про альтернативные функции выводов, но разобраться можно.
По действиям до попадания в main() - смотреть про свой компилятор, это его вотчина.
Время от окончания reset timeout от снятия внешнего или внутреннего reset до перехода по вектору reset - пара тактов, чисто выборка команды из флеш (хотя измерить его и нереально, но ничего другого мега не делает). Про сброс есть целый раздел доки 11. System Control and Reset.

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


15/12/22
254
Dmitriy40 я тут прочитал много чего про порты ввода-вывода.
Хотелось бы Вас уточнить, правильно ли я всё понял.
После ресет все порты устанавливаются по дефолту в высокоимпедансное состояние. Их можно подтягивать хоть к VCC хоть к GND.
Управляют портами 4 регистра: DDRx (устанавливает тип ножек - входы или выходы), PORTx (устанавливает состояние ножек: 1 или 0), MCUCR (бит PUD устанавливает безусловный запрет на подтягивающие резисторы, независимо от других настроек), PINx служит для считывания состояния входа, но если в него записать 1, то происходит переключение PORTx на противоположное независимо от DDRx.
По умолчанию все эти регистры после включения содержат нули. Т.е. это входы без подтягивающих резисторов.

Чтобы сконфигугировать ножку на управляющий вывод для IRLML2244 нужно сначала записать 1 в PORTx. Это просто подключит подтягивающий резистор 30-50 кОм с VCC, на выходе ничего не изменится, особенно если есть ещё и внешний подтягивающий резистор. Только потом нужно записать 1 в DDRx, порт перейдёт в режим выхода и сразу будет в состоянии лог. 1. Т.е. ключ IRLML2244 будет всё это время закрыт. Потом, о мере необходимости, можно будет его открывать, записью 0 в DDRx.

Если же не дай Бог, сразу записать 1 в DDRx, конфигурируя его как выход, то на нём появится низкоимпедансный 0 и ключ IRLML2244 откроется. Можно его конечно потом закрыть, записав 1 в PORTx, но за это время успеет проскочить импульс, как минимум в такт.

Чтобы такого не было, вместо КМОП ключа можно использовать повторитель тока на биполярном тр. Тогда при конфигурировании ножки как выход она будет в состоянии 0, а повторитель будет закрыт.

Всё правильно?

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


20/08/14
12188
Россия, Москва
Missir в сообщении #1685060 писал(а):
MCUCR (бит PUD
В меге8а бит PUD находится в SFIOR (стр.62 pdf). В разных МК биты могут в разных регистрах, засада, смиритесь и помните.
Missir в сообщении #1685060 писал(а):
PINx служит для считывания состояния входа, но если в него записать 1, то происходит переключение PORTx на противоположное независимо от DDRx.
Происходит именно переключение регистра, не вывода! И да, независимо от DDRx. Это первое.
Второе, эта фича в меге8а не реализована! Посмотрите на схему порта (Figure 13-2 на стр.57 pdf), там нет обратной связи из PORTx через мультиплексор сразу же обратно в PORTx прямо над триггером PORTx, как например в более новой мега324P (стр.96 pdf).
Поэтому надо всегда держать под рукой актуальный pdf именно на свой МК и постоянно с ним сверяться. Особенно по советам и исходникам из сети (тем более если там прямо не указано к какому МК они относятся).

Missir в сообщении #1685060 писал(а):
По умолчанию все эти регистры после включения содержат нули. Т.е. это входы без подтягивающих резисторов.
Про обычные выводы да, Вы всё поняли правильно. Но есть куча исключений/тонкостей.
Для вывода может действовать сигнал альтернативной функции (PUOEx=1, см. схему вывода на стр.57 pdf и таблицы далее). Под ресетом такое возможно только для:
PC6(RESET) если RSTDISBL=1, т.е. вывод используется как RESET и на нём обязательно будет подтяжка независимо от DDR, PORT, PUD;
PB7,PB6 - если они используются для тактирования (CKSEL fuse), подтяжка будет отключена;
PB5,PB4,PB3 - используются при ISP, про подтяжку не сказано, вероятно нет, но на 100% не уверен, а проверять на живой меге лень. Плюс про SCK сказано (стр.228 pdf) что вход в режим ISP будет только если SCK=0 в момент подачи RESET=0 - потому весьма полезно иметь подтяжку к VCC на SCK для исключения случайного запуска режима перепрошивки при активном RESET=0, пусть даже наводки вряд ли совпадут с командами ISP, но подтянуть SCK ещё повысит надёжность.
Для других мег надо тоже внимательно смотреть какие выводы могут быть активными под ресетом (например выводы JTAG при fuse JTAGEN=0, а он такой по умолчанию, это вечная засада если они как-то используются, они просто недоступны программно, сколько раз натыкался, не однажды приходилось офигевать на экран осцила и потом платы переразводить).

Missir в сообщении #1685060 писал(а):
Если же не дай Бог, сразу записать 1 в DDRx, конфигурируя его как выход, то на нём появится низкоимпедансный 0 и ключ IRLML2244 откроется. Можно его конечно потом закрыть, записав 1 в PORTx, но за это время успеет проскочить импульс, как минимум в такт.
Да, порядок записи в PORT и DDR бывает важен, Вы правы. Причём для переключения в 0 и в 1 может потребоваться разный порядок записи этих регистров (например если нужен выход с открытым коллектором/стоком). Надо просто об этом помнить.
Ещё надо помнить что вместо записи в PORT и DDR намного лучше всегда делать их модификацию (|= или &= или ^=) - чтобы не дай бог тронуть другие биты. Лучше сделать 8 операций |= чем одну запись. Исключение - критичные участки кода (типа прерываний), но тогда нужно быть особенно внимательным и аккуратным.
И нет, в такт не уложится, минимум два: ldi+out. А компиляторы любят вместо out делать sts за два такта (ради единообразия доступа к памяти), а то и константу читать из flash (ldi+ldi+lpm за 5 тактов) из области начальных значений переменных, итого может и до 7 тактов дойти ... Да, я перфекционист и не доверяю компиляторам с языков высокого уровня (только с асма), сколько раз как гляну в асм код после них - так волосы дыбом и неуёмное желание переписать вот буквально вообще всё на асме. :facepalm:

Missir в сообщении #1685060 писал(а):
Чтобы такого не было, вместо КМОП ключа можно использовать повторитель тока на биполярном тр. Тогда при конфигурировании ножки как выход она будет в состоянии 0, а повторитель будет закрыт.
Я не уверен что правильно понимаю что такое "повторитель тока на биполяре". Вы даже полярность биполяра не указали, pnp или npn. Если эмиттерный повторитель npn и в плюсе, эмиттером к нагрузке, то на нём же будет падать напряжение база-эмиттер независимо от тока и базы (ну почти, в ключевом режиме, мы же про него) и коллектора, это примерные 0.6В (0.45В-0.75В), при том что просто на ножке порта падает расчётно 0.12В - ну и зачем такой биполяр тогда?!

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение09.05.2025, 05:29 


15/12/22
254
Dmitriy40, извиняюсь за назойливость, но мой код мне показался уж очень неудачным,
я его немного усовершенствовал. От проверки GIFR решил избавится, вместо этого в cnd сначала добавляется 2 бит через или. Если между while (cnd & 3){}; и cnd|=2; будет прерывание, то cnd станет равной 3 и while (cnd & 1); вернёт функцию назад, ждать пока не закончится обработка транзакции и не сбросятся 3 последние бита cnd. Здесь сама собой вырисовывается и функция read_i2c(), позволяющая читать данные из основной программы, по произвольному адресу и в произвольном количестве. С прерыванием она насколько я понимаю конфликтовать тоже не должна. Вот такой код, библиотека <util/twi.h> оказывается не нужна, в ней только константы:
код: [ скачать ] [ спрятать ]
Используется синтаксис C
#define GY521  0x68   // адрес GY-521
#define SEND  (1<<TWIE) |(1<<TWINT)|(1<<TWEN)
#define SENDN (1<<TWIE) |(1<<TWINT)|(1<<TWEN)|(1<<TWEA)
#define START (1<<TWIE) |(1<<TWINT)|(1<<TWEN)|(1<<TWSTA)
#define STOP  (1<<TWSTO)|(1<<TWINT)|(1<<TWEN)

volatile union {uchar byts[14]; short vals[7];} num;
volatile uchar cnd=0, addr, dat, nb;                    
// 3:0 bits of cnd: recording error/succes, reading is completed/failed, i2c is busy/free, reading/recording

static inline void read_i2c(uchar addres, uchar nbyte)
{ do { while (cnd & 7){}; cnd|=2;} while (cnd & 1); cnd|=1; addr=addres; nb=nbyte-1; TWCR=START; }

static inline void set_i2c(uchar addres, uchar data)
{ do { while (cnd & 3){}; cnd|=2;} while (cnd & 1); addr=addres; dat=data; nb=1; TWCR=START; }

ISR(INT0_vect)
{ if (!(cnd & 7)) { cnd=3; addr=0x3B; nb=13; TWCR=START; }}  

ISR(TWI_vect)    // TWI automate
{ do
  { switch(TWSR & 0xF8)
    { case 0x08: TWDR = GY521<<1; TWCR = SEND; break;                // i2c bus started: send slave addres
      case 0x18: TWDR = addr; TWCR = SEND; break;                    // slave addres received: send memory addres
      case 0x28: if (cnd & 1) { TWCR = START; break; }               // data received: if read data then restart i2c bus
                 if (nb--) { TWDR=dat; TWCR=SEND; break; }           //  -----------   if write data is no end then send byte
                 TWCR=STOP; cnd&=12; break;                          //  -----------   if write data is end then stop i2c bus
      case 0x10: TWDR = (GY521<<1)|0x01; TWCR=SEND; break;           // bus restarted: send slave addres for read      
      case 0x40: TWCR = SENDN; break;                                // slave addres for read received: resive first byte
      case 0x50: num.byts[nb--]=TWDR;                                // data sended: save byte in reverse order (little endian)                        
                 if (nb) TWCR=SENDN; else TWCR=SEND; break;
      case 0x58: num.byts[0]=TWDR; TWCR=STOP; cnd&=8; cnd|=4; break; // sending completed: save last byte, stop i2c bus                      
      default:   TWCR=STOP; if (cnd&1) cnd&=8; else cnd&=4; cnd|=8;  // errors: stop i2c bus                                
    }
  } while (TWCR&(1<<TWINT));  
}
 


инициализация такая же как и раньше, за исключением того, что я убрал переменную err и вместо этого за ошибки записи теперь отвечает 4 бит cnd, чтобы не разводить много глобальных переменных:
код: [ скачать ] [ спрятать ]
Используется синтаксис C
int main(void)
{
  uchar i;
  static short buf[14];

  // 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 { cnd&=0xF7;
       set_i2c(0x6B, 0x80);          
       _delay_ms(100);
       set_i2c(0x68, 0x07);          
       _delay_ms(100);
       set_i2c(0x6B, 0x01);        
       set_i2c(0x6A, 0x04);        
       set_i2c(0x19, 0x32);          
       set_i2c(0x1A, 0x05);      
                                   
       set_i2c(0x23, 0xF8);        
       set_i2c(0x38, 0x01);        
       set_i2c(0x6A, 0x40);      

       while (cnd & 2){};
     } while (cnd & 8);

  // Interrupt settings
  DDRD &= ~(1<<PD2);                 // INT0 как вход
  PORTD |= (1<<PD2);                 // подтяжка PD2 к лог.1
  MCUCR |= (1<<ISC01)|(0<<ISC00);    // срабатывание по ниспадающему фронту
  GIFR = (1<<INTF0);                 // сброс запроса на прерывание
  GICR  |= (1<<INT0);                // разрешить INT0

 


теперь при удачном чтении данных в массив num, хоть из прерывания, хоть из read_i2c() выставляется 3 бит cnd, после этого чтение данных блокируется, пока данные не заберут и не сбросят этот бит. Не знаю насколько это рационально, но основной цикл программы так получается очень простым, и гарантируется, что в нём всегда выводятся только новые данные без ошибок:
Используется синтаксис C
 while (1)
  { if (cnd & 4)
    {  for (i=0; i<7; i++) buf[i]=num.vals[i]; cnd &= 0xFB;
       send_uart('\r'); for (i=0; i<7; i++) write_uart(buf[6-i]);
    }
  }  
  return 0;
}
 


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

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


20/08/14
12188
Россия, Москва
Missir в сообщении #1685414 писал(а):
Если между while (cnd & 3){}; и cnd|=2; будет прерывание, то cnd станет равной 3
С чего это Вы взяли? Она может стать и 2 - и прерывание, которое выставило cnd=3, будет потеряно, точнее информация что оно было. Ещё раз: операция |= не атомарна! Она сначала читает значение, меняет его, потом пишет обратно. И возможна ситуация когда меняющее cnd прерывание приходит ровно между чтением и записью обратно! Прочитается 0, придёт прерывание и выставит 3, а 0 изменится на 2 и запишется обратно. Не атомарные операции правильно работают в прерываниях - лишь потому что их прервать уже некому. Я не пойму зачем ходить по этим граблям если можно их убрать.
Впрочем, если с i2c не работать после включения прерываний int0 - можно и так, тогда опять же некому прервать этот кусок кода и изменить значение cnd. Но зачем тогда весь этот огород ...

И ниже снова, if (cnd & 4) делать можно, тут значение никто не изменит, а вот cnd &= 0xFB делать нельзя, это не атомарная операция! Вот запись байта - атомарна. Но только сама запись, не комбинация чтения с проверкой и записью.

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение12.05.2025, 05:33 


15/12/22
254
Dmitriy40 в сообщении #1685676 писал(а):
Ещё раз: операция |= не атомарна!

ещё раз ... будто бы я об этом знал, вся идея в том и была, что уж она то точно никак не сможет прерваться

а инкремент хоть атомарная операция, или тоже нет? какие вообще операции атомарные а какие нет?

-- 12.05.2025, 05:45 --

Dmitriy40 в сообщении #1685676 писал(а):
а вот cnd &= 0xFB делать нельзя, это не атомарная операция!

тут как раз можно, при cnd & 4 обработчик прерывания блокируется и изменить cnd не может

 Профиль  
                  
 
 Re: Странное поведение fuses в atmega8a
Сообщение12.05.2025, 07:59 


27/08/16
11899
Missir в сообщении #1685682 писал(а):
а инкремент хоть атомарная операция, или тоже нет? какие вообще операции атомарные а какие нет?
Всё очень сильно зависит от процессора и компилятора. В общем случае нужно обязательно проверять ассемблерный код. У современных процессоров всё бывает очень и очень сложно, но у AVR атомарные операции те, которые компилируются ровно в одну команду ядра.

У AVR большинство выражений в С не атомарные, кроме нескольких команд манипуляции с периферийными регистрами: установить один бит / сбросить один бит в периферийном регистре. Может быть ещё что-то, что я уже забыл. Соответствующие побитовые операции с этими регистрами в С компилируются в одну команду и получаются атомарными. Но нужно обязательно проверять.

И ещё, периферийные регистры обязательно должны быть описаны как volatile.

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

-- 12.05.2025, 08:14 --

Missir в сообщении #1684677 писал(а):
Код:
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;
}




Инлайн функции совершенно не обязательно писать в одну строчку. От количества пробелов и переводов строки в вашем исходнике не изменяется в работе программы вообще ничего. А читать код без правильного его форматирования сложно даже вам самому.

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


20/08/14
12188
Россия, Москва
Missir в сообщении #1685682 писал(а):
а инкремент хоть атомарная операция, или тоже нет? какие вообще операции атомарные а какие нет?
Нет не атомарная.
AVR является регистровой архитектурой, соответственно практически все операции выполняются исключительно в регистрах, если переменная лежит в памяти, то она сначала читается, изменяется, потом пишется обратно, если она дальше нужна снова - снова читается, меняется, снова пишется. Для не volatile переменных промежуточные записи и чтения компилятор имеет право (но не обязанность!) оптимизировать и сохранить значение в регистре. Для volatile переменных - права не имеет, будет действовать именно как перечислено.
В итоге атомарными являются:
чтение байта;
запись байта;
если компилятор использует команды sbi/cbi, то установка и сброс бита в портах с адресами с 0 по 0x1F=31, адреса регистров есть в конце любой доки на кристалл, причём многие регистры находятся по разным адресам в разных кристаллах, смотреть как всегда надо именно на свой. Но как я уже говорил выше, не факт что компилятор будет использовать эти команды, он имеет право и делать обычную последовательность |= или &=, да ещё и константу читать из flash (например ради единообразия хранения констант), это выльется в 6 команд (ldi+ldi+lpm+lds+or+sts) и уж точно не атомарно.
Всё, все другие операции считать не атомарными. В том числе операции над несколькими байтами.

Missir в сообщении #1685682 писал(а):
тут как раз можно, при cnd & 4 обработчик прерывания блокируется и изменить cnd не может
Возможно, я в логику переключений cnd не вникал. Потому что лучше сразу исключить возможные глюки чем потом при очередной модификации кода получить трудноуловимые глюки.

realeugene в сообщении #1685684 писал(а):
Также атомарность может быть не важна, если переменная изменяется в одной процедуре, но читается в разных.
Исключительно если переменная пишется в память атомарно т.е. строго одной командой. Для AVR это только байтовые переменные.

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

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



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

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


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

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