свой драйвер I2C (TWI) требуется помощь ..
- Войдите на сайт для отправки комментариев
Ср, 06/04/2016 - 07:44
Не понял как, но утерял предыдущую реализацию I2C .. накатал ещё раз и .. не могу понять что происходит.
Вот код:
#include "arhat.h" // ******* #include "arhat_twi.h" ************ // // ------------ All states TWI status register AND twiState: ------------- // // Misc #define TWI_ERROR 0x00 // Misc: illegal start or stop condition #define TWI_NO_INFO 0xF8 // Misc: no state information available // I am Master #define TWI_START 0x08 // start condition transmitted #define TWI_REP_START 0x10 // repeated start condition transmitted #define TWI_MTR_ARB_LOST 0x38 // arbitration lost in SLA+W or data // Master Transmitter #define TWI_MT_SLA_ACK 0x18 // address: SLA+W transmitted, ACK received #define TWI_MT_SLA_NACK 0x20 // address: SLA+W transmitted, NACK received #define TWI_MT_DATA_ACK 0x28 // data: transmitted, ACK received #define TWI_MT_DATA_NACK 0x30 // data: transmitted, NACK received // Master Receiver #define TWI_MR_SLA_ACK 0x40 // address: SLA+R transmitted, ACK received #define TWI_MR_SLA_NACK 0x48 // address: SLA+R transmitted, NACK received #define TWI_MR_DATA_ACK 0x50 // data: received, ACK returned #define TWI_MR_DATA_NACK 0x58 // data: received, NACK returned // I am Slave // Slave Receiver #define TWI_SR_SLA_ACK 0x60 // address: SLA+W received, ACK returned #define TWI_SR_ARB_LOST_SLA_ACK 0x68 // arbitration lost in SLA+RW, SLA+W received, ACK returned #define TWI_SR_GCALL_ACK 0x70 // general call received, ACK returned #define TWI_SR_ARB_LOST_GCALL_ACK 0x78 // arbitration lost in SLA+RW, general call received, ACK returned #define TWI_SR_DATA_ACK 0x80 // data: received, ACK returned #define TWI_SR_DATA_NACK 0x88 // data: received, NACK returned #define TWI_SR_GCALL_DATA_ACK 0x90 // general call data received, ACK returned #define TWI_SR_GCALL_DATA_NACK 0x98 // general call data received, NACK returned #define TWI_SR_STOP 0xA0 // stop or repeated start condition received while selected // Slave Transmitter #define TWI_ST_SLA_ACK 0xA8 // address: SLA+R received, ACK returned #define TWI_ST_ARB_LOST_SLA_ACK 0xB0 // arbitration lost in SLA+RW, SLA+R received, ACK returned #define TWI_ST_DATA_ACK 0xB8 // data: transmitted, ACK received #define TWI_ST_DATA_NACK 0xC0 // data: transmitted, NACK received #define TWI_ST_LAST_DATA 0xC8 // last data byte transmitted, ACK received // ------------ Macros for TWI ------------- // #define TWI_STATUS_MASK (_BV(TWS7)|_BV(TWS6)|_BV(TWS5)|_BV(TWS4)|_BV(TWS3)) // Two LSB are prescaler bits #define TWI_STATUS (TWSR & TWI_STATUS_MASK) // Get status from TWSR #define TWI_READ 1 // for SLA+R address #define TWI_WRITE 0 // SLA+W address #define TWI_ACK 1 #define TWI_NACK 0 #define twiOn() (PRR0 &= ~_BV(7)) // =0: TWI power is On (default on power!) #define twiOff() (PRR0 |= _BV(7)) // =1: TWI power is Off /** * Set twi bit rate and prescaler: _twbr:[0..255], _twsr:[0..3] * ------------------------------------------------------------ * _twsr = 3: scl freq. = [ 490 .. 111_111] hz * _twsr = 2: scl freq. = [ 1_960 .. 333_333] hz * _twsr = 1: scl freq. = [ 7_782 .. 666_667] hz * _twsr = 0: scl freq. = [30_418 .. 888_889] hz * IF _twbr = 0: frequency equal 1 Mhz with any _twsr! * * @example twiSetRate(72,0) : use 100_000hz standart mode * @example twiSetRate(12,0) : use 400_000hz standart mode * @example twiSetRate(0,0) : use 1 Mhz mode */ #define twiSetRate(_twbr, _twsr) \ { \ _SFR_BYTE(TWSR) = (uint8_t)(_twsr); \ TWBR = (uint8_t)(_twbr); \ } /* * Relinquishes bus master status */ #define twiStop(ack) \ { \ TWCR = twiReleaseBus(ack) | _BV(TWSTO); \ while(TWCR & _BV(TWSTO)); \ } #define twiStart(ack) (TWCR = _BV(TWEN)|_BV(TWINT)|_BV(TWIE)|_BV(TWSTA)|((ack)?_BV(TWEA):0)) #define twiSetAddress(address, isGcall) (TWAR = (uint8_t)(((address) << 1)|((isGcall)&0x01))) #define twiSetMaskAddress(mask) (TWAMR = (uint8_t)(mask)) #define twiReply(ack) (TWCR = _BV(TWEN)|_BV(TWINT)|_BV(TWIE)|((ack)?_BV(TWEA):0)) #define twiReleaseBus(ack) (TWCR = _BV(TWEN)|_BV(TWINT)|((ack)?_BV(TWEA):0)) #ifdef __cplusplus extern "C" { #endif // ------------ TWI internal variables ------------- // enum TWI_Modes { TWI_IS_SLAVE = 1 // have I slave mode too? ,TWI_SEND_STOP = 2 // is need send stop when Master is ending? ,TWI_READY = 4 // previous work is ended }; volatile uint8_t twiMode; volatile uint8_t twiState; // state TWI automat volatile uint8_t twiSLARW; // address for send to (SLARW) volatile uint16_t twiMT_Count; // остаток байт для передачи мастером volatile uint8_t * twiMT_Ptr; // указатель текущего байта внешнего буфера передачи мастером volatile uint16_t twiRX_Count; // остаток байт для приема мастером/слейвом volatile uint8_t * twiRX_Ptr; // указатель текущего байта внешнего буфера приема мастером/слейвом volatile uint16_t twiST_Count; // остаток байт для передачи слейвом volatile uint8_t * twiST_Ptr; // указатель текущего байта внешнего буфера передачи слейвом volatile void (* twiHookRestart)(void) = 0; // указатель на функцию перезапуска без освобождения шины (TWI_SEND_STOP) volatile void (* twiMasterReader)(void) = 0; // указатель на функцию "Master принял данные, куда их?" volatile void (* twiSlaveReader)(void) = 0; // указатель на функцию "Slave принял данные, куда их?" volatile void (* twiSlaveWriter)(void) = 0; // указатель на функцию "Slave всё отправил, что дальше?" // ------------ TWI functions ------------- // /** * Autocalculate and set twi prescaler and bit rate * 1Mhz .. 30.418khz : TWSR=0! * 30.42khz .. 7.782khz : TWSR=1 * 7.782khz .. 1.960khz : TWSR=2 * 1/960khz .. 0.490khz : TWSR=3 */ void twiSpeed(uint32_t freq) { uint16_t bitRate = (F_CPU / freq) - 16; uint8_t bitMul = 0; while( (bitRate > 511) && (bitMul < 3) ){ bitRate /= 4; bitRate += 1; bitMul++; } bitRate /= 2; if( bitRate > 255 ) return; twiSetRate(bitRate, bitMul); } /** * for Arduino setup() as Master or Slave or Both modes * freq:[490 .. 1 000 000], mode:[0,TWI_IS_SLAVE] */ void twiSetup(uint32_t freq, uint8_t mode) { digitalWrite(I2C_SDA, HIGH); // internal pullup is ON. digitalWrite(I2C_SCL, HIGH); twiSpeed(freq); // set bitrate and prescaler for frequency twiMode = mode; TWCR = _BV(TWEN)|_BV(TWIE)|((mode&TWI_IS_SLAVE)?_BV(TWEA):0); // module, acks, and interrupt is ON } /** * ISR for TWI interface: realised master and slave modes * ------------------------------------------------------ */ ISR(TWI_vect) { uint8_t isSlave = twiMode & TWI_IS_SLAVE; // Am I is Slave too? set TWEA or not twiState=TWI_STATUS; Serial.println(""); Serial.print(", s1=0x"); Serial.print(twiState, 16); switch(twiState) { // ------------ All modes ------------ // // -------------------------------------------------------------------------- // case TWI_NO_INFO: // no state information break; case TWI_ERROR: // bus error, illegal stop/start twiStop(isSlave); break; // ------------ All Masters ------------ // // -------------------------------------------------------------------------- // case TWI_START: // Прошла отправка стартовой посылки case TWI_REP_START: // Прошла отправка повторного старта TWDR = twiSLARW; twiReply(isSlave); break; case TWI_MTR_ARB_LOST: // Упс. Мастер потерял шину: освобождаем и ждем/слушаем. twiReleaseBus(isSlave); break; case TWI_MR_SLA_NACK: // Упс. Отправитель NACK .. не откликается зараза. case TWI_MT_SLA_NACK: // Упс. Получатель NACK .. не откликается зараза. case TWI_MT_DATA_NACK: // Упс. data NACK: Получатель не хотит? twiStop(isSlave); break; // ------------ Master is Transmitterer ------------ // // -------------------------------------------------------------------------- // case TWI_MT_SLA_ACK: // Адрес получателя отправлен успешно, начинаем case TWI_MT_DATA_ACK: // Байт данных отправлен, продолжаем Serial.print(", cnt1="); Serial.print(twiMT_Count, 10); if( twiMT_Count-- ){ TWDR = *twiMT_Ptr++; Serial.print(", cnt2="); Serial.print(twiMT_Count, 10); twiReply(isSlave); }else{ goto TWI_SEND_STOP; } break; // ------------ Master is Receiver ------------ // // Режим требует наличия хотя бы одного байта для приема данных!!! // // В этом режиме TWEA это что отправить передатчику! Нет Slave Reciever Mode! // // -------------------------------------------------------------------------- // case TWI_MR_DATA_ACK: // байт принят, ACK отправлен *twiRX_Ptr++ = TWDR; case TWI_MR_SLA_ACK: // Отправитель найден, начинаем прием twiReply(--twiRX_Count); // Можно ещё принять? break; case TWI_MR_DATA_NACK: // Упс. Получен последний байт отправителя *twiRX_Ptr = TWDR; if( twiMasterReader ) twiMasterReader(); isSlave = twiMode & TWI_IS_SLAVE; // возможно изменение режимов в хуке! TWI_SEND_STOP: Serial.print(", ptr=0x"); Serial.print((int)twiMT_Ptr, 16); Serial.print(", cnt="); Serial.print(twiMT_Count, 10); Serial.print(", mode=0b"); Serial.print(twiMode, 2); if (twiMode & TWI_SEND_STOP){ // Освобождать шину, или надо ещё (напр. прием после передачи)? Serial.print(", Stop!"); twiStop(isSlave); twiMode |= TWI_READY; // Сеанс завершен. }else{ Serial.print(", Restart!"); if( twiHookRestart ){ Serial.print(", Restart HOOK!"); twiHookRestart(); // процедура подготовки след. посылки должна открыть прерывание! TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN); // посылаем restart, но без старта } else { Serial.print(", Def Restart!"); twiSLARW |= TWI_READ; // а нет Хука! Типовой режим "чтение после записи" twiStart(isSlave); } } break; } Serial.print(", cr=0x"); Serial.print(TWCR, 16); } /** * Master-TX:: Передача length байт по адресу получателя. Только запуск! */ void twiWrite(uint8_t address, uint8_t* data, uint8_t length) { Serial.println(""); Serial.print(",start1: twiMode="); Serial.print(twiMode, 10); while( !(TWI_READY & twiMode) ); // Ждем завершения предыдущей работы мастера twiMode &= ~TWI_READY; // сбрасываем признак завершения сеанса Serial.print(",start2: twiMode="); Serial.print(twiMode, 10); twiMT_Ptr = (volatile uint8_t *)data; twiMT_Count = length; twiMode |= TWI_SEND_STOP; // только 1 бит! Могли быть иные режимы.. twiSLARW = (address<<1 | TWI_WRITE); // Режим передачи! twiStart(twiMode & TWI_IS_SLAVE); } /** * MASTER-RX:: Прием length байт из адреса отправителя. Только запуск! */ void twiRead(uint8_t address, uint8_t* data, uint8_t length) { while( !(TWI_READY & twiMode) ); // Ждем завершения предыдущей работы мастера twiMode &= ~TWI_READY; // сбрасываем признак завершения сеанса twiMode |= TWI_SEND_STOP; twiRX_Ptr = (volatile uint8_t *)data; twiRX_Count = length; twiSLARW = (address<<1 | TWI_READ); // Режим приема! twiStart(twiMode & TWI_IS_SLAVE); } /** * Master Read-after-Write:: Чтение данных после отправки команды. Только запуск. * !!! Не совместимо с Slave Receive Mode - буфер приема общий !!! */ void twiRAW(uint8_t address // адрес устройства , uint8_t* command, uint8_t clength // команда и её длина , uint8_t* data, uint8_t dlength // буфер приема данных и его длина ){ while( !(TWI_READY & twiMode) ); // Ждем завершения предыдущей работы мастера twiMode &= ~TWI_READY; // сбрасываем признак завершения сеанса twiMT_Ptr = (volatile uint8_t *)command; twiMT_Count = clength; twiRX_Ptr = (volatile uint8_t *)data; twiRX_Count = dlength; twiHookRestart = 0; // типовой переход на чтение этого же Slave twiMode &= ~TWI_SEND_STOP; // авто-рестарт после отправки команды twiSLARW = (address<<1 | TWI_WRITE); // Сначала режим передачи! twiStart(twiMode & TWI_IS_SLAVE); } #ifdef __cplusplus } #endif // ******* #include "lcd1602.h" ************ // #define LCD_I2C_SPEED 1000 // @TODO Можно поиграться с этим .. итого: LCD1602A тянет до 800кгц включительно!!! // LCD on chip HD44780 команды или их коды операций: #define LCD_CLEAR 0x01 #define LCD_HOME 0x02 #define LCD_SHIFTS 0x04 // часть! #define LCD_SHOWS 0x08 // часть! #define LCD_MODES 0x20 // часть! #define LCD_FONT_RAM 0x40 // адрес в таблицу шрифтов 6бит #define LCD_SHOW_RAM 0x80 // адрес текущей позиции 7бит #define LCD_CURSOR_LEFT 0x10 // команда сдвига курсора влево #define LCD_CURSOR_RIGHT 0x14 // команда сдвига курсора вправо #define LCD_ROW_LEFT 0x18 // команда сдвига всей строки влево #define LCD_ROW_RIGHT 0x1C // команда сдвига всей строки вправо // for LCD_SHIFTS: #define LCD_INC 0x02 // Инкремент: сдвиг вправо при записи байта #define LCD_SHIFT_ON 0x01 // сдвиг и строки тоже при записи байта (в обратную сторону! Курсор как-бы на месте) // for LCD_SHOWS: #define LCD_SHOW_ON 0x04 // включить отображение #define LCD_CURSOR_UL 0x02 // включить курсор подчерком #define LCD_CURSOR_BLINK 0x01 // включить мигание курсора // for LCD_MODES: #define LCD_8BIT 0x10 // шина 8бит/4бит #define LCD_2LINE 0x08 // память 2/1 строки #define LCD_5x10 0x04 // фонт 5х10 / 5х8 точек // Пустышки для полноты картины: #define LCD_DEC 0 // сдвиг курсора/экрана влево #define LCD_SHIFT_OFF 0 // сдвиг строки отключен #define LCD_SHOW_OFF 0 // дисплей выключен #define LCD_CURSOR_OFF 0 // курсор выключен #define LCD_5x8 0 #define LCD_1LINE 0 // только одна строка (1 буфер 80символов, иначе 2х40) #define LCD_4BIT 0 // 4бита: каждый байт идет 2 посылками "подряд" по линиям D7..D4 // Маски бит данных (D3,D2,D1,D0), управляющие сигналами напрямую: #define LCD_BACK B00001000 // bit D3 управляет подсветкой экрана при каждом выводе в I2C! #define LCD_E B00000100 // Enable bit - Strobe for read/write >230msec. #define LCD_RW B00000010 // Read/Write bit - ==0 всегда! #define LCD_RS B00000001 // Команда(0) или данные(1) #define LCD_WAIT_BOOT0 15 // тиков Т0: >15мсек пауза на включение #define LCD_WAIT_BOOT1 5 // тиков Т0: >4,1мсек пауза повторной 0x30 #define LCD_WAIT_BOOT2 400 // (по 250нсек) >100мксек пауза третьей 0x30 #define LCD_WAIT_1 5 // тиков Т0: 4мсек - мало! (datasheet: >37мксек пауза на команду "в среднем" - фигвам!) /** * Отправка буфера дисплею по 4-битному протоколу с "ручным" стробированием * и типовой задержкой на выполнение команды */ #define lcdSend(len) \ { \ twiWrite(lcdAddress, lcdBuffer, (uint8_t)(len)); \ delay(LCD_WAIT_1); \ } #define lcdWrite1(d) {lcdPrepare((uint8_t)(d), 1); lcdSend(6);} #define lcdCommand(p) {lcdPrepare((uint8_t)(p), 0); lcdSend(6);} #define lcdClear() lcdCommand(LCD_CLEAR) #define lcdHome() lcdCommand(LCD_HOME) #define lcdCursorLeft() lcdCommand(LCD_CURSOR_LEFT) #define lcdCursorRight() lcdCommand(LCD_CURSOR_RIGHT) #define lcdRowLeft() lcdCommand(LCD_ROW_LEFT) #define lcdRowRight() lcdCommand(LCD_ROW_RIGHT) #define lcdFontAddress(f) lcdCommand(LCD_FONT_RAM | ((uint8_t)(f)&0x3F)) #define lcdShowAddress(a) lcdCommand(LCD_SHOW_RAM | ((uint8_t)(a)&0x7F)) /** * Установить абсолютную позицию курсора */ #define lcdSetCursor(_col,_row) \ (lcdCommand(LCD_SHOW_RAM | (((_row)? 0x40 : 0x00) + (uint8_t)(_col) & 0x7f))) /** * Установить позицию записи в память шрифтов */ #define lcdGoChar5x8(_ch) (lcdCommand(LCD_FONT_RAM | ((uint8_t)(_ch))<<3)&0x3f) #ifdef __cplusplus extern "C" { #endif uint8_t lcdModes = LCD_MODES | LCD_8BIT; // PowerON: 8bit mode, 1 line, 5x8 font uint8_t lcdShifts = LCD_SHIFTS | LCD_INC; // PowerON: cursor shift to right, not screen! uint8_t lcdShows = LCD_SHOWS; // PowerON: show off, cursor off, blink off uint8_t lcdBackLight = LCD_BACK; // PowerON: Backlight is ON uint8_t lcdAddress = 0x27; // for myLCD1602 parameters as default uint8_t lcdCols = 16; uint8_t lcdRows = 2; uint8_t lcdBuffer[6]; // Буфер для потоковой тетрадной записи в дисплей /** * Подготовка байта в буфер потоковой записи * @param _rs=[0 -- команда,!0 -- данные] */ void lcdPrepare(uint8_t _data, uint8_t _rs) { uint8_t nibble = (_data&0xf0) | lcdBackLight; if( _rs ) nibble |= LCD_RS; lcdBuffer[2] = lcdBuffer[0] = nibble; nibble |= LCD_E; lcdBuffer[1] = nibble; nibble = ((_data&0x0f)<<4) | lcdBackLight; if( _rs ) nibble |= LCD_RS; lcdBuffer[5] = lcdBuffer[3] = nibble; nibble |= LCD_E; lcdBuffer[4] = nibble; } /** * Вывод строки заданной длины (буфера) на экран. * Повторная установка скорости работы дисплея по I2C и режима (мало ли кто и как работает ещё) */ void lcdWrite(const void *buf, uint8_t len) { uint8_t *_b = (uint8_t *)buf; uint8_t _l = len; twiMode |= TWI_SEND_STOP; while(_l--) lcdWrite1(*_b++); } /** * for setup(): powerON initialization for LCD with Hitachi HD44780 (up to 40 cols, 2 rows) */ void lcdSetup(uint8_t _address, uint8_t _cols, uint8_t _rows, uint8_t _backLight) { lcdAddress = _address; lcdCols = _cols; lcdRows = _rows; lcdBackLight = (_backLight? LCD_BACK : 0); lcdModes = LCD_MODES; lcdShifts = LCD_SHIFTS | LCD_INC; lcdShows = LCD_SHOWS | LCD_SHOW_ON; #ifndef TWI_SETUP twiSetup(LCD_I2C_SPEED, TWI_READY|TWI_SEND_STOP); // только если не запущен ранее! Serial.println(""); Serial.print("TWCR="); Serial.print(TWCR, 10); Serial.print(", TWBR="); Serial.print(TWBR, 10); Serial.print(", TWSR="); Serial.print(_SFR_BYTE(TWSR), 10); Serial.println(""); #define TWI_SETUP "lcd1602.h" // фиксим, что I2C инициализируется этой библиотекой #endif if( _rows>1 ) { lcdModes |= LCD_2LINE; } // else 1 line // @see datasheet: power on sequence p45-46 { lcdPrepare(0x30, 0); // HD44780: RS=0 запись в регистр команд дисплея. delay(LCD_WAIT_BOOT0); // powerOn it needs wait from 15 (+5v) upto 50msec (+2.7v) lcdSend(3); // 1x3 отправка: по включению режим 8-бит - вторая тетрада не нужна! Serial.println(""); Serial.print(",ls3: twiMode="); Serial.print(twiMode, 10); delay(LCD_WAIT_BOOT1); // ждем >4.1ms lcdSend(3); // вторая отправка согласно даташит 8-бит.. delayMicro16(LCD_WAIT_BOOT2); // ждем >100us lcdSend(3); // третья отправка 8-бит.. lcdPrepare(0x20, 0); // и только теперь переводим в режим 4-бита lcdSend(3); // и только теперь режим 4-бита и отправляем тетрады попарно! } lcdCommand(lcdModes); // повторяем режим 4 бита, но уже + сколько строк и какой шрифт lcdCommand(lcdShows); // включаем дисплей и курсор lcdCommand(lcdShifts); // настройка режимов сдвига курсора/экрана lcdClear(); // очень долго очищаем экран (>15.2мсек) delay(500); lcdHome(); } #ifdef __cplusplus } // extern "C" #endif // ************* Example *********** // void setup() { Serial.begin(115200); lcdSetup(0x27, 16, 2, 1); lcdWrite("Hello, Arhat!", 14); // lcdHome(); lcdSetCursor(0,1); lcdWrite("Bye, Bye..", 10); } void loop() {}
А вот что происходит:
1. подключение файла lcd1602.h (тут инклуден для наглядности) к стандартному twi.h из Wiring дает его стабильную работу на этом примере. Подставлял разные упр. команды - всё более-менее гладко. Не все, но тем не менее.
2. Небольшое насилие над twi.h позволяет обнаружить что дисплей стабильно работает вплоть до частот интерфейса в 800кГц ..
3. тестовый вывод в сериал этого скетча показывает следующее (на частоте 100кгц, тут изменено на 1 кГц - эффект тот же)
TWCR=5, TWBR=72, TWSR=248
, start1: twiMode=6, start2: twiMode=2
, s1=0x8, cr=0x5
, s1=0x18, cnt1=3, cnt2=2, cr=0x5
, ls3: twiMode=2
, s1=0x28, cnt1=2, cnt2=1, cr=0x5
, start1: twiMode=2
, s1=0x28, cnt1=1, cnt2=0, cr=0x5
... и на этом ФСЁ. Далее, судя по всему скетч висит в цикле ожидания когда же twiMode устанвит 4-й бит TWI_READY .. прерывания TWI INT на котором обработчик должен был обнаружить конец посылки и сказать twiStop() не происходит вовсе .. или они оказываются каким-то образом закрыты.
Может кто-то хоть что-то пояснить по ситуации? Спасибо.
а чем типовая библиотека не устраивает ? я про wire
по многим причинам:
Типовая библиотека "жрет" 250 байт суммарно под разные буфера и переменные классов Print, Serial, LiquidCristal_I2C .. и в итоге больше по коду на 1200 байт (примерно). Тут жрется .. 36 байт оперативы. Чистый выигрыш примерно составляет 220 байт ОЗУ и 1200 Флеш при полном обработчике для мастер И слев режимов. Это в "полном режиме" компиляции со статистикой и слейвом.
Тут показан только тот код, который использован в этом примере (после препроцессора). Из него удален за ненадобностью режим слейв и наработка статистики интерфейса ибо подключается макроопределениями TWI_LOG_ON=1, TWI_IS_SLAVE=1 .. что позволяет сэкономить ещё около 400 байт флеш.
Из кода устранена промежуточная буферизация как на уровне twi так и вышележащих классов, что исключает множественные перетасовки данных с места на место и многоуровневую виртуализаю (косвенный вызов) функций. В итоге это дает возможность работы на максимально возможной скорости .. раз в 100 выше чем в Wiring .. предыдущий вариант связывал 2 дуньки в легкую на 1Мгц при типовых вызовах lcdWrite(). Всё нормально успевает отрабатывать.
Работа этого I2C - легко настраивается (и правильно, в отличии от Wiring!) на произвольную частоту шины и это можно делать да, даже руками в лоб через twiSetRate() макрос. Тут ставится 1кгц в целях отладки и видено, что настроен верно и работает.
Работа тут может вестись через "хуки" (callback), которые можно применять для полноценного автоматного программирования. Именно с этой целью запуск передачи делается асинхронно, что видно на тестовых данных. В раннем wiring такое тоже было возможно, но с версии 1.6.5 это закрыто в twi.h
Тут работа с каждый устройством может вестись на его любимой частоте, хоть даже и вперемежку .. twiSetSpeed() перед вызовом и вперед.
Реализован режим "чтение после записи" в аппаратном виде.
Возможность выводить строковые константы "напрямую" (только с версии 1.6.6 кажется, уже не помню) ..
Ну и сам LCD тут работает проще и существенно шустрее.
Далеко не полный перечень "чем не устраивает". На порядок больше возможностей при меньшем требовании к памяти МК и существенно большей скорости связи. :)
И? Нет идей, "что происходит"? Или "всё слишком сложно" и можно как-то поупрощать? :)
Как-то ожидалось что идеи появятся таки .. жаль.
Такую оптимизацию стоит делать для промышленых систем, а форум по ардуинке - это уровень ламеров....
В 90х я пробовал писать на ASM под однокристалки, и понимаю на сколько текущие библиотеки не оптимальны, но для текущих задач они выполняют свои задачи... то чем ты занимаешься - это для 99.9999% посетителей форума явные излишества.
Это как художник который может до бесконечности улучшать картину, но в этом процессе нужно уметь остановится на неком приемлемом компромисе.
Примерно год назад я уже выражал свое мнение по этому вопросу, повторюсь: Ардуино - проект по большей части учебный, и как всякий учебный материал должен иметь разные варианты решений на разный уровень обучающегося .. но, в любом варианте код должен быть тщательно выверен и проработан, а не "писан на коленке". Тут и не только, а много где и практически все, а не только лишь избранные пишут про ламерские решения в Wiring.
То что он ваще есть этот wiring - просто замечательно, но это решение для очень-очень начинающего уровня. Только как "пример" .. ни о каком применении ни дома ни в производстве тут речи не идет от слова "вовсе".
То, что я делаю в качестве arhat.h (и этот код туда же!) - уже несколько попродвинутее, как мне кажется и уже позволяет решать кое-какие задачи. Несмотря на то, что сейчас развивается исключительно уровень "С-АСМ", без поддержки плюсов .. но это нижний уровень, и он просто обязан быть "вылизан" и "максимально расширен" по своим возможностям применения.
Потому что ИМХО, но учить надо не на наколенных поделках, а на качественном, хорошо прописанном коде. Вот, как-то так.
P.S.
Пока нет возможности добраться до платы, может таки кто выскажется "что происходит?" Ещё раз: есть какие-то идеи? Ошибки в приемной части - уже вижу, но меня сейчас волнует передающая часть: почему после отправки трех байт из буфера не выскакивает прерывание для TWI? Или тут где-то "утечка памяти" лезет?
У нас разные взгляды на wiring, ну да чорт с ней.
Конкретно:
Если проследить за потоком управления, то видно, что после отправки 3-х байт в счетчике 0, и по else мы просто обязаны попасть в TWI_SEND_STOP, но этого не происходит.
После взведения флага преравания в макросе twiReply() прерывание неизбежно, если его не запретить глобально в SREG. То есть НИЧЕГО не может произойти.
-------------------------
Утечка памяти? Вы,как мне кажется, совсем не новичек, и сумеете написать пример, где динамического распределения не будет почти совсем, чтобы избежать такого рода сбоев. Так что этот вариант я не стану рассматривать.
------------------------
Следовательно сбой в момент диагностического вывода на serial, больше негде. Там просто больше никакого кода не выполняется, но это ИМХО. Легко проверить, может я и не прав.
И еще: я бы сперва прогнал всю конструкцию без прерывания. Просто проверяя бит TWINT в TWCR, а прерывание пусть будет стороннее, чтобы код не переделывать. Просто повесьте этот обработчик на любой таймер и в "голове" прочитайте TWINT, если "взведен" - работаем, если нет - выходим. Заодно станет видно (если сбой сохранится), что именно запрещено: байт в шину "не пролез" или преравания глобально запрещены?
Вот, хоть один взялся помогать, спасибо.
В том-то и дело, что поток управления честно отдает в интерфейс 3 байта, но в он в else ЕЩЁ не попадает: инкремент счетчика производится ПОСЛЕ проверки на ноль .. то есть он должен отправить последний байт, как обычно взвести прерывание (как и перед ним 2 байта проходят) и завершить работу. А вот при следующем ответе приемника (получил третий байт) как раз должно обнаружиться пустое значение и попасть в else .. но его похоже НЕ происходит этого "следующего прерывания" вовсе или происходит "не пойми что"... пока дунька недоступна накидал себе "схему" проверки, что и как можно поисключать, дабы понять что происходит:
1. Втыкнуть в loop() вывод в сериал - дабы убедится что сюда мы не доходим точно, на всяк случай.
2. Втыкнуть в цикл ожидания проверки доступности интерфейса в twiWrite() какой-нить вывод (наверное значения указателя стека + флаг общего запрета прерываний), дабы убедиться что следующая операция вывода в twi повисла тут, а не "где-то ишо". А заодно убедится что "стек не ползет" между делом к области переменных (мало ли где ещё возможен косяк, вылезающий внезапно тут..). Другой утечки памяти тут быть не может, ибо нет динамического выделения - вовсе.
3. Как-то убедиться что по шине третий байт в реальности отдается в дисплей и дисплей таки отвечат ACK/NACK .. осцила нет, попробую занизить общую скорость работы дуньки через главный прескалер и выйти на частоту интерфейса в пару герц, дабы посмотреть светодиодиками что там на проводках мелькает.. 490 герц я глазом не распознаю всяко.
.. пока больше идей нет.
Вступление понятно, это я и имел ввиду.
Вы взводите прерывание и выходите из обработчика. TWI Атмеги должен уже сам отправить байт и получить или не получит ACK. Я и написал, что никакой код, ПОСЛЕ выхода из обработчика и возврата в него снова, уже после переданного третьего байта, НЕ ДОЛЖЕН выполняться, если мы с Вами правильно понимаем работу Атмеги и не разучились читать датаШит по аглицки ;). Первый раз со статусом 0х18 и 0х28 все прошло нормально, так? Тогда чем отличается последний байт от предыдущего? Ведь явно - ничем. Шаг назад мы покинули обработчик с TWCR = 00000101 и статусом 0х28 и все было нормально, мы пришли за третьим байтом и, снова, покидаем обработчик вектора с TWCR= 00000101 и статусом 0х28, так? и больше в него не возвращаемся.
Поскольку TWINT взведен, то никакой код после выхода из обработчика не успеет выполнится, только РОВНО одна команда, так процессор атмеги устроен: после выхода из прерывания выполняется команда по программному указателю, после снова проверяются наличие прерывавний. Что там может выполнится? Понятия не имею! Поэтому и предложил вывести обработчик из TWI вектора и повесить его на какой нибудь таймер - тогда сбой отловить легче будет.
--------------------
Про осцил - он есть у всех. В любом компе есть звуковуха. Программ - полно. Я на cxem.net какую-то старую скачивал и ей постоянно пользуюсь. Мой личный алгоритм такой: Любимым ;) 838-м тестером определяем в режимах DC и AC амплитуды сигнала. Иходя из максимума показаний делаем на двух резисторах делитель до нужных нам 2Вольт, если постоянная составляющая нас не очень волнует, а это почти всегда так, градуировать псевдоосцил - полная дурь, то можно пропустить через конденсатор, не стану объяснять какой - через керамический, выбранный слепым методом из кассы в столе ;). И придется отрезать шнурок от старых наушников.
Килогерц до 15 - показывает очень мило. В настройках компа убрать всякуй муть типа предусиления и "уровня записи". и помнить, что картинка может быть инверсной - это от звуковухи зависит, можно усложнить идею одним транзистором, но пропадает прелесть реализации - ничего не нужно, тольно тестер, голова на плечах и кабель от наушников. (касса резисторов в столе - предполагается по умолчанию).
Сейчас поищу свою прогу и выложу ссылку.
Заценил, про осцил. Пасибки. Касса резисторов и кондюков есть, старых ушей нет, но не беда .. есть кучка дохлых блоков питания и чего-то ишо с такими соединителями .. "тюльпан" кажись зовутся .. подобрать можно. :)
Там не совсем так. Дело в том, что в третий раз выход из прерывания делается "да", ровно также как и при первых двух байтах. Но TWI достаточно медленный интерфейс для МК .. прерывание он выставит не сразу, а только апосля отдачи в шину 8 байт плюс 1 такт шины TWI (!) на прием ответа с той стороны. Плюсом - пропуск 1 команды МК как реакция на прерывание (если он не торчит в более высоком приоритете - таймер0 напр. выше). То есть даже тактируя TWI(I2C) 1Мгц (=1мкс) мы имеем "паузу" между прерываниями в 9мкс + 1 команда. На 16Мгц проц отбарарабанит .. 16*9 = 144+1 = 145 тактов. Команд, на самом деле, поменьше - около 100. Операции с ОЗУ занимают 2 такта и есть ещё тормозные команды. А на типовых 100кгц интерфейса имеем в 10 раз больше .. около 1000команд основной проги.
В целом тоже не вижу, где и что может утекать в этом примере .. по сути кроме таймера 0 и нет ничего. Он у меня тоже самописный .. но ведь уже почти как год пашет "как часы". В общем, добрался до дуньки, сел проверять далее.
2. Небольшое насилие над twi.h позволяет обнаружить что дисплей стабильно работает вплоть до частот интерфейса в 800кГц ..
Вот жеж правильный вброс. А что из этого следует? Да то, что обмен будет гдето 20 тактов на бит, и до 200 тактов на байт. А на кой при такой скорости прерывание, если вход в прерывание, сохранение контекста выколупывание очередного байта, запуск обмена и выход из прерывания займет пошти стокоже, ну пусть немного менше. В основной программе выполнить десяток байт до следующего прерывания. А оно того стоит? Это ж быстрая переферия, делайте ногодрыг. Все упрощается на порядок. И летает. А если ногодрыг завернуть в функцию, указатель на которую скармливать классам устройств использующих шину ;))) А если эту функцию в шаблончик и либку, то можна жить по новому
Ваши рассуждения неверны в корне. Перечитайте последний пост:
ДАЖЕ на скорости I2C в 1 мегагерц проц успевает выполнить до 145 тактов команд. Это далеко не "десяток-другой" .. примерно на порядок больше. Это на предельно допустимой скорости для данного девайса тут. Быстрее аппаратный I2C не может, а программный ногодрыг будет заведомо медленней и куда как затратнее по ресурсам.
Штатные сокорости - это 400килогерц или даже и вовсе 100 килогерц .. то есть уже около 1000 тактов "в среднем" .. это достаточно солидный кусок проги .. особенно ежели учесть что ногодрыг, ШИМ и прочие побрякушки это часто 1-3 команды "от силы".
Так .. теперь я ваще ничего не понимаю .. вот вывод в сериал с натыканным доп. выводом:
1. вывод SP= это запоминание значений в самом начале setup() первыми командами и их вывод уже после Serial.begin();
2. Вывод SPs= добавлен сразу после вывода регистров TWI в lcdSetup() перед первой отправкой.
3. Строка ls3 - там же где и была: перед второй попыткой вывода, только добавлен вывод стека
4. выводы SPw - из цикла ожидания освобождения TWI_READY в функции twiWrite() .. вместо пустого цикла втыкнул вывод стека.
Итого: стек никуда не плывет, после отправки третьего байта прерывания TWIE не происходит вовсе, прога висит в twiWrite() в ожидании когда же закончится предыдущая передача.
При выводе последнего байта видно что прерывание произошло в момент попытки вывода стека в одной строке видим SPw=0x и после отработки прерывания видим CR=0x521EC вот 21EC - остаток вывода после прерывания ..
А также видно что прога "повисает" (в смысле ваще перестает что-то выводить в последней строке. В сериале больше ничего не появляется.
Что происходит?!?
Ваши рассуждения неверны в корне. Перечитайте последний пост:
ДАЖЕ на скорости I2C в 1 мегагерц проц успевает выполнить до 145 тактов команд. Это далеко не "десяток-другой" .. примерно на порядок больше.
Читаете бегло, но с пропусками. Повторю. Но один раз. Дальше долбитесь до следующего просветления, как хотите.
...200 тактов на байт. А на кой при такой скорости прерывание, если вход в прерывание, сохранение контекста выколупывание очередного байта, запуск обмена и выход из прерывания займет пошти стокоже, ну пусть немного менше.
Быстрее аппаратный I2C не может, а программный ногодрыг будет заведомо медленней и куда как затратнее по ресурсам.
Штатные сокорости - это 400килогерц или даже и вовсе 100 килогерц .. то есть уже около 1000 тактов "в среднем" .. это достаточно солидный кусок проги .. особенно ежели учесть что ногодрыг, ШИМ и прочие побрякушки это часто 1-3 команды "от силы".
Сохранение ПОЛНОГО контекста - да гемморой .. есть attribute(NAKED) для борьбы с этим. И ручное сохранение того, что вам в реальности надо. Тут, как видите, практически не надо почти ничего .. :) За оставшиеся 50-60 команд МК в окне 800кгц я успею ещё и половину датчиков опросить, да по нескольку раз .. и в основном коде буду тупо читать "всегда готовые" значения .. :)
Вот ещё вариант. Вывод в сериал остался только внутри обработчика прерывания .. и упс. Третий байт уходит и прерывание есть. Но, сериал дохнет напрочь (надо свой делать). :)
Сохранение ПОЛНОГО контекста - да гемморой .. есть attribute(NAKED) для борьбы с этим. И ручное сохранение того, что вам в реальности надо.
А Вы его использовали? В какой строке? А какие регистры Вы сохранять ручками собрались, Вы знаете каки задействовал компилятор, чтоб не сохранить лишнего?
Не пишите о том, чего не знаете и не используете.
Вы бы лучше свой код дизасамблировали да посмотрели чего и скоко там сохраняется, и скока на это времени уходит оценили. Заодно и сколько тактов на опросы датчиков. Так, для справки, "тест показал, что для микроконтроллера ATmega328, работающего на частоте 16 МГц, вызов функции micros занимает 3.5625 мкс", пошти 60 тактов.
Я вобщем, чего взялся Вам писать, седня Вы наконец то про 800КГц постигли, хотя я это не раз на форуме писал, подумал что наконец дошло, может и дальше постигните, но увы ))) Ниче. Бывает.
Всё, нашел. Ошибка в разворачивании макроса twiReleaseBus() внутри макроса twiStop(). Поправил, все сразу заработало. Итого преобразователь из I2C в параллельный интерфейс LCD1602A вполне способен работать на частотах шины I2C от 10кгц до 800кгц. Но, времена указанные в даташите не верны! Время исполнения типовых команд - 4 миллисекунды!
То есть lcdSend() можно смело переводить на хук таймера и не делать в нем delay(). Очень долго.
Вопрос можно закрывать.
to Logik: мы с вами не знакомы, и не надо утверждать чего я знаю, а чего нет. Вместо этого, лучше откройте arhat.h и посмотрите сами что и КАК уже используется. :)
Ассемблер конечно же смотрел. И этот обработчик будет в него преобразован и оптимизирован в асме, конечно же. Кстати, обнаружил что кумпилятыр не умеет генерить таблиную версию switch() .. придется делать макрос. :(
Кстати, обнаружил что кумпилятыр не умеет генерить таблиную версию switch() .. придется делать макрос. :(
Уважаемый, у Вас же одни волатили кругом, компилятор их боиццо! ;)
-----------------------
Еще добавлю: Я, все таки, сторонник подхода коллеги Евгения П., не нужно мешать компилятору работать и не стоит экономить два байта и три такта. Уже не времена PICа - 8-ми битного, с ресурсами, как у Тиньки 13. Да и Тиньку, если и использовать, то только на самом простом уровне, если лень схему на двух транзисторах сделать.
Если мало ресурсов, то нужно взять контроллер на 100 рублей дороже.С частотой 84 Мгц и RAMом, которого точно хватит. Мало его - есть еще круче.
При этом я помню, как сам в 64к памяти уладывал весьма серьезные алгоритмы обработки графики, но сегодня считаю это не верным. Ресурсы микроконтроллера - на порядок дешевле ресурсов программиста. Даже в нашем варианте - в качестве хобби. Ардуино Дуе на али уже меньше 1000 рублей, чего уж там говорить.
---------------
Но это так, просто май хамбл опиньён. Работу Вы ведете интересную, как хобби и оттачивание мастерства. Только не переоценивайте практическую ценность.
Если говорить о практической ценности - то нужно собирать gcc с правильными флагами и делать свой обвес к нему и "дуде" и свою среду в формате "опер сорс". Денег не принесет, но как альтернативная среда - принесет славу среди полутора десятков ботанов.
Но тут - ловушка: как только вы соберете первый раз gcc так, как вам надо и зальете прогу чистой "дудой", то для себя решите, что ни "студия" ни, тем более" Ардуино IDE, Вам не нужно. Своя экосистема, свои библиотеки, которые или самопис или тщательно отобранные в сети. Любимый редактор, не сложнее VIMа.
А откуда юниксоиды беруться, как Вы думали? ;).
Только вот массоовость Ардуинке дала именно простая IDE и wiring, который скрывает от новичка кучу лишней инфы. И опытный программист тоже понимает, что "робота", "поливную систему" или "заумный дом" проще писать и отлаживать на простейшем инструменте. Ведь это и есть Ардуино. Если отказываться от wiring, то, очевидно, нужно и от загрузчика отказываться - он ведь тоже глючный и причина сбоев. Тогда IDE уже тоже не нужен. И тогда зачем ардуинка, есть макетки с контроллером, или просто контроллер, некоторые - в удобных DIP корпусах, да и QFP тоже ничего, с нормальным паяльником и BGA нам не страшен. Но куда, в этом случае, делась чудесная, в своей цельности Ардуинка?
Обратите внимание на форум - писатели осцилографа - почти сразу поняли, что для них wiring - не подходит совсем, а строители роботов и "умных" домов - никогда не столкнуться с критическими по времени или памяти местами. Это совсем разные типы задач.
------------------------
(*) если вдруг кому не понятно, то "дуда" это avrdude.
to Logik: мы с вами не знакомы, и не надо утверждать чего я знаю, а чего нет. Вместо этого, лучше откройте arhat.h и посмотрите сами что и КАК уже используется. :)
Ваш код говорит о Ваших знаниях. Спасибо, arhat.h я уже смотрел с Вашего совет осенью. И отписался тогда Вам почему это безпоезная трата времени, оно не жилец. О многократных инклудах до сих пор вспоминаю с содроганием.
Вы пишете что смотрели свой ассемблер - так разкажите всем о увиденом. А то есть сомнения в наличии у Вас квалификации для анализа дизассамблированого кода. Отсутствие ответа буду трактовать как подтверждение сомнений.
Отвечу обоим сразу, ибо холиварить уже не интересно ни разу.
wdrakula, Logik, В части применяемости wiring и его круга задач - вы оба, безусловно правы и я с этим согласен и уже писал "замечательно что он вообще есть". Вы как-то это не заметили, акцентирую явно. Для целей и средств вовлечения И обучения начинающих, просто любопытсвующих этот проект - просто замечателен.
Ровно также, я с вами обоими (и остальными, кто тут не участвует, но читает) согласен и в плане того, что задачу надо решать теми средствами, которые наиболее подходящи для её решения. Безусловно в общем виде. А вот далее начинаются "нюансы" в части критериев "подходящности" .. и тут уже, КАЖДЫЙ РЕШАЕТ ЗА СЕБЯ - и подходяшим часто оказывается не то, что "подходит под задачу". У другого "специалиста" с его тараканами (знаниями, опытом и т.д.) будет иное мнение (два спеца - три мнения): один возьмет "тиньку" и решит задачу, для которой другому надо не ниже "двухядерного ксенона". Отсюда, не желаю спорить про "подходящие инструменты".
Есть иной критерий: "минимально возможные средства". Так вот, задачу МОЖНО решать "подходящими средствами", а можно "минимально возможными". Ощущаете разницу? :)
И, в целях обучения, коим я и занимаюсь "на дому", я считаю (ИМХО), что обучать надо минимально возможным решениям, красивым решениям (они как правило совпадают), правильному алгоритмированию задачи, которая наглядно ПОКАЗЫВАЕТ и подсказывает сама это самое красивое решение .. а не говнокодерству воведенному в ранг аксиомы, активно и агрессивно утвердившемуся по жизни среди массы недоучек. Да, экономика говна - рулит. Но это не аргумент учить плохому.
.. собственно для ЭТОГО и пишу свой arhat.h, а вовсе не для того, чтобы получить "славу или бабки на телефон или признание миллионов", как вы ожидаете (судя по постам). Мне глубоко пофиг ваша оценка меня, как специалиста или какая ещё (как общения на этом форуме) .. вполне хватает задач, где моя помощь востребована на 8-12 часов каждый день. И, чем больше говнокодеров в мире, тем чаще зовут таких же "старых велосипедистов". Так что работой нас обеспечивают, даже на пенсиях..
Выкладываю, только потому что "может кому-то пригодится" этот велосипед. Но это делается как альтернатива Wiring и в первую очередь как более эффективный код. И вопрос "взять двухядерный ксеон" - у меня не стоит. Есть Дунька, на ней и обучаем..
Там нет "многократных инклудов" кроме одного(!) вложения распиновки в главный хидер .. или вы не смотрели или нифига не поняли.
Там полно "многократных" вложений макроопределений .. это "плата" за возможность работать с пинами напрямую "1 оператор = 1 команда МК", а не 400 как digitalWrite(). Предпочитаю расплачиваться ресурсами препроцессора, чем дунькиными при выполнении. В отличии от Cyberlib, у меня решен вопрос настройки на конкретный МК такого подхода, и решен вопрос использования типовых вызовов digitalWrite() и пр., позволяющее использовать публикуемые скетчи практически "один в один" с этой библиотекой.
Там жеж есть и "ассемблерные вставки", коих вы похоже не видели даже .. там жеж есть и иное. Я не пишу, а его пользую .. и этот обработчик постепенно появится там жеж. Когда? Да когда сочту этот момент полезным. :)
Во первых обещал ссылку на программу для псевдоосцила на звуковухе. Я пользуюсь вот этим:http://radiopill.net/Programs/digital_oscilloscope_v.3.0.zip.
Во вторых: Архат, зря Вы на меня огорчились! Я как раз горячо приветствую Вашу работу, я просто заметил, что wiring Вы не замените, по указанным выше причинам. Но для людей, понимающих где и зачем им нужно больше, чем может дать wiring - Ваша работа ОЧЕНЬ нужна.
Простите, если задел.
=================
Про псевдо осцил: есть много схем щупа к нему - сами в сети найдете. Я - не делаю, просто понимая, что и куда можно "совать" ;). Но для безопасности можете сделать. Причем можно сделать с автоопределением амплитуты и автоподбором аттенюатора/усилителя. Но, как по мне, так это баловство, подростковый перфекционизм. Но - если интересно - велком, схему нарисую. Аттенюатор 100, 10 и1 для диапазонов 20-200, 2-20 и < 2 В, а дальше "опен/шорт" замыкать или нет кондер, и дальше колесико делителя обратной связи в копеечном ОУ, питаться - от 5 В USB, для универсальности.
Ни разу ни на кого не обиделся .. стиль общения у меня такой .. пенсионерский .. иногда. :)
Упс, вынужден извиниться, ибо с цифирьками экономии байтиков я поднаврал выше. Оказалось, что существенное уменьшение размеров было при отключенной компиляции Slave режима .. только мастер, вот и меньше существенно. В полном объеме экономия невелика .. байт 20-30 по причине того, что switch оптимизируется компилятором достаточно слабо. Примерно половина размера кода драйвера в ассемблере - это переходы "туда-сюда обратно" из-за большого количества ветвлений. Делать проверку в цикле и хранить в памяти программ табличку значений и переходов и обойтись одним косвенным jump - он не умеет вовсе.
В целом, работа по части освоения интерфейса оказалась полезной, и как вывод twi.h автоподключаемый к библиотеке Wire.h писан в среднем очень даже неплохо. Существенный недостаток только один - внутренняя буферизация обмена. Без неё "то на то" и выходит, если не углубляться в ассемблер.
P.S. Да, и по части "сохранения контекста" и "геммороя с ним" - по большей части, уже в 1.6.4 этот момент исправлен. Обработчик прерывания сохраняет ровно тот контекст, который портит.
P.S. Да, и по части "сохранения контекста" и "геммороя с ним" - по большей части, уже в 1.6.4 этот момент исправлен. Обработчик прерывания сохраняет ровно тот контекст, который портит.
.. а портит все, если он достаточно развит.
Снова художественный треп. Дизасемблированого кода нет, расчета или измерения тактов на обслуживание прерывания нет, художественная литератута одна. Скока Вам вдалбливать, компилирует GCC, важна его версия а не версия ИДЕ. И к стати GCC косвеный goto поддерживает уже лет 10 как, используйте вместо свича и получите косвеный jamp. Пример на форуме был.
Писец какой процесс, скоко возни, буфера, переходы, вместо функции из трех строк в шаблоне.
Кстати работает на любых пинах контролера ;) Можна хоть пять экранов навесить )))
Покажите уж тогда и организацию глобального вызова по адресу Global Call таких вот контроллеров и связь промеж них в парралельном режиме с чередованием Мастер-Слейв .. двухстрочный вы наш! Свести большой обработчик до ваших двух строк - дело не хитрое. Вы лучше покажите КАК эти ваши строчки по записи байтика в шину одновременно и асинхронно будут принимать его от соседней дуньки .. что уж на самом деле! кому нужен очередной ногодрыг на шине?
Обработчик прерывания - это полный конечный автомат, позволяющий ВСЁ ЭТО делать ОДНОВРЕМЕННО И АСИНХРОННО .. и на тех самых 800кгц.
<del> Извиняюсь за свой вредный характер.
Да, и ещё. Реализация полноценного общения по I2C вашими макросами, да при передачах длинных пачек во внешнем цикле будет не только надолго завешивать контроллер, вплоть ДО СЕКУНД, но ещё и по размеру реализации ВЫЙДЕТ БОЛЬШЕ. А то и в разы, смотря сколько устройств на шине и как часто они общаются друг с другом.
Ваш макрос не подходит НИ ДЛЯ чего более, кроме как писать на экран, и в его единственно примененном месте: реализация какой-нить lcdWrite(). Ни для чего иного он не подходит.
Располагая функциями чтения и передачи байта, ув. Logik без всякого сомнения может творить с шиной TWI что пожелает. Такой код (как в приведенном примере) дает блокирующие (ожидающие своего исполнения) функции, но очень часто мы сами не знаем что нам бы еще поделать, не получив ответа от I2C устройства, например. Т.е. логика тут такая - запросил (передал) данные, дождался, обработал, пошли дальше.
Такой подход может столкнуться (а может и не заморачиваться) с вопросами производительности. Ну вот (худший) пример работы с текстовыми I2C дисплеями. Цепочка взаимодействия там такая - MCU с работой по шине TWI, сдвиговый регистр PCF8574 с 4-бит интерфейсом к HD44780, сам интерфейс HD44780. Для вывода одного символа на LCD нам потребуется передать 4 байта - два раза строб линии E интерфейса HD44780 и два раза данные (по 4 бит за раз). После чего нам прийдется (перед подачей следующего символа) дождаться отработки (еще и) интерфейса HD44780. Оцениваем характерное время. Частота на шине TWI 100 kHz (для PCF8574 по документации), характерное время такта 10 мкс, передача одного байта около 9х10 = 90 мкс (+ack бит), четырех байт - около 360 мкс. (без учета байта адресации устройства). И характерное время отработки HD44780 интерфейса 120 мкс (кроме отдельных команд), что может не учитываться (время подачи "сразу" следующей команды для LCD заведомо больше необходимой паузы отработки HD44780). Т.е. выжимание скорости из TWI не всегда (это в мягкой форме) приводит к производительности работы с I2C устройством (в этом примере не только ограничение "сверху" в 100 kHz на работу с самим устройством, но еще и способ (4 байта) взаимодействия с конечным устройством).
Тоже самое иными словами. Зачастую в простых ситуациях (измерить и отобразить, или вывести длинную цепочку байт куда-то там) логика приложения просто не знает, что делать без ожидания окончания I2C операции. Поэтому использование блокирующих функций оправдано, имеет место быть и иметь будет (тех, кто за абсолютное совершенство кода, шутка).
Капитан Очевидность здесь закончил, дальнейшее в основном к ув. Arhat109-2.
Оно, конечно, Александр Македонский герой, но зачем же стулья ломать?
Давайте отнесем это к полемическому задору. Но все-таки (вздыхая) разберем, потому что работу вы делаете интересную и (мне, например) читать ваш код одно удовольствие.
Вот нижеприведенная политически корректная ваша фраза.
"организаци(я) глобального вызова по адресу Global Call таких вот контроллеров и связь промеж них в парралельном режиме с чередованием Мастер-Слейв ... КАК эти ... строчки по записи байтика в шину одновременно и асинхронно будут принимать его от соседней дуньки .. что уж на самом деле! Обработчик прерывания - это полный конечный автомат, позволяющий ВСЁ ЭТО делать ОДНОВРЕМЕННО И АСИНХРОННО .. и на тех самых 800кгц."
Вот ее разбор.
1. т.н. Global Call это обращение к устройствам на TWI шине по адресу 0000 0001 и передача какой-нибудь общей команды для откликнувшихся. Чтение здесь не предусматривается. Ну наверное (этот вызов) может использоваться, если подобрать несколько однотипных устройств, понимающих и адекватно исполняющих подобную команду. Магия здесь не предусмотрена.
2. Вот на вопрос о том, КАК строчки записи/чтения байтика в шину одновременно и асинхронно будут что-то там делать ответить можно. Никак. Плохие это строчки или хорошие, но одновременно и асинхронно что-то делать на шине I2C - это сапоги всмятку.
3. Обработчик прерывания (имеется в виду TWI прерывание) - конечный автомат, позволяющий полностью выполнить I2C транзакцию. Транзакция здесь это передача и прием каких-то байт (twi протокол байт ориентированный), один мастер, одно ведомое, мастер начал, мастер закончил. Все, что между START condition и STOP condition - транзакция (поддерживаемая кодами состояния status регистра, _предполагающими_ определенный порядок действий, т.е. протокол, I2C в данном случае). В конкретный момент времени (и до окончания транзакции) _только_ один мастер (на, быть может, многомастерной шине) и только одно ведомое устройство (slave). Только синхронность (конкретный мастер, и только он, тактирует шину), только _одно_ ведомое (slave) устройство. (Можно ли "разговаривать" с несколькими устройствами между start condition и stop condition? Конечно можно, потому что каждый конкретный запрос мастера (запись ли, чтение) будет сопровождаться передачей адреса ведомого (slave) устройства, но вот сортировать логику запросов - не драйверское это дело. В любом случае _такое_ поведение регулироваться конечным автоматом TWI прерывания не может).
Это же другими словами. Конечный автомат в обработчике прерывания подразумевает _последовательный_ проход через его состояния, потому что обработка каждого (ну почти) состояния предполагает переход на следующее (отличное, другое) состояние автомата. Передали start condition, _ожидаем_ передачи SLA(R/W) и т.д.
Иными (опять) словами взаимодействие с шиной I2C на уровне interrupt-driven позволяет построить неблокирующие функции работы с этой шиной. Например, стартовать начало транзакции (передача - прием между MCU и _одним_ каким-то адресуемым устройством), далее делать _что хочу_ в основном коде, пока не узнаю, что транзакция закончилась и можно стартовать новую транзакцию (с _другим_, например, ведомым устройством). Кстати, _правильный_ мастер не просто "выщелкивает" ack бит с частотой SCL, он _ждет_ освобождения шины (HIGH, подтяжка же). Это означает, что ответ ведомого (slave) может прийти и через 5 минут, или 5 недель (slave выставляет задержку), пока мастер контроллирует шину.
Разбор закончен.
Напоследок о скорости TWI, без учета способности ведомого (slave) устройства работать на высокой скорости (либо может, либо нет). IMHO непонятно, как для семейства ATmega выставить скорость более 400 kHz. Кроме конкретного указания о предельной скорости TWI в документации (на MCU, имеющих аппаратную поддержку TWI, в 400 kHz), есть просто физическая неспособность выставить настройки бит-рэйт и прескэйлер регистров выше 400 kHz (ну там по формуле). С семейством ATtiny (@20MHz, там софт SCL для USI) есть варианты, но пока не готов обсудить. Если речь о других семействах, ничего сказать не могу.
Понимаю, что общение на высоких скоростях по I2C вы подразумеваете для, т.с. межпроцессорной коммуникации, , но мои эксперименты пока дают негативный результат.
Последнее. Пост написан с уважением (и интересом) к работе г-на Arhat109-2 и с посланием г-ну Logik "и вы тоже правы".
Речь шла о форматировании ранее набранного текста.
[Del]
dhog1, спасибо. Поясню ряд моментов:
1. Скорость TWI у атмела настраивается битретом вплоть до 1Мгц включительно. Запустить его с LCD1602 на 1Мгц мне - не удалось, но вот 880кгц он держит вполне уверенно.
Выложил на гитхаб первую версию с тестированным мастером, можете поиграться сами, там есть функция установки битрейта для произвольной скорости.
Остальные режимы только компилировались, но ещё не проверялись. Там же воткнул и пример работы с LCD1602. Пока тоже только примитив вывода заданного количества байтиков. Остальное буду добавлять "по мере надобности".
2. Внутри транзакции, между stop/start одна железка, конечно будет работать только в одном режиме. Запустить (задавал тут где-то вопрос - его сообщество тупо проигнорило) TWI на одном МК в режиме "самопрослушки", когда мастер педерает и сам же обнаруживает себя на шине как слейв - не удастся по причине того что регистр статуса - один. Часть состояний потеряется или у мастера или у слейва.
3. Но, тем не менее, одна железка вполне может работать ОДНОВРЕМЕННО, последовательными транзакциями И как мастер И как слейв. Эти режимы у атмела - полностью независимы, за исключением варианта "мастер-прием" + "слейв-прием" .. сигнал ACK один, но используется с разной логикой. И вот как раз, реализовать ТАКОЙ режим макросами "прямиого ногодрыга" - нельзя "по определению". У таких макросов есть ещё один существенный недостаток: дублирование кода при повторном использовании. Макрос большой и код будет разрастаться существенно, и при этом он сотается блокирующим.
Что сделано:
1. Разделение компиляции на "отдельные куски": можно включать в код обработчика только мастера-передатчика, только мастер-приемник, только слейв-приемник, только слейв-передатчик .. или в произвольной их комбинации. Это для сокращения размера кода, если что-то не востребовано. Каждый блок тянет около 200байт кода по отдельности. Все вместе тянут на почти 430байт. (против 610 у wiring). И это на уровне "С". Ассемблером можно ещё ужать 76 байт "пролога и эпилога" обработчика. Там столько не требуется и упростить ветвления (очень существенная часть обработчика). Теоретически, один мастер-передатчик ужать до размера приведенного макроса-ногодрыга - вполне реально.
2. Убрана внутренняя буферизация. Экономим около 200байт оперативы, и плюсом дополнительно устраняем вспомогательные перетасовки из классов "верхнего уровня".
Пока как-то так. Пробуйте, проверяйте .. пишите письма, ежели что не так.
Располагая функциями чтения и передачи байта, ув. Logik без всякого сомнения может творить с шиной TWI что пожелает. Такой код (как в приведенном примере) дает блокирующие (ожидающие своего исполнения) функции, но очень часто мы сами не знаем что нам бы еще поделать, не получив ответа от I2C устройства, например. Т.е. логика тут такая - запросил (передал) данные, дождался, обработал, пошли дальше.
Угу.Суть верна, и спорить не о чем. Только замечу шо код обладает интересными свойствами - пины указывается по ардуиновски из скетча, приче любые, а код не по ардуиновски быстрый т.е. digitalWrite можна и обойти. Там под макросами работа с портом, ну в общем для тех кто в теме :)) А на верх идет только указатель на функцию. Получается класс экрана один, методы в одном экземпляре, а экранов хоть 5 штук, опять же для ценителей ;)
Ну вот (худший) пример работы с текстовыми I2C дисплеями. Цепочка взаимодействия там такая ....
Ну это ад и израель в одном плафоне. Нам чего попроще один бит - один пиксель, ну или один байт или ворд на крайняк. Это вызывает рост обемов и соответственно проблему скорости. Графика - наше все, ssd1306 например, а эти символьные , шо до сих пор ещё живы? Какие проблемы скорости у "простейших!, 32 байта, пусть даже на 4 умножить, как Вы пишете не успевают? И паузы для обработки нам тоже чужды. Грузить и поскорей, 800КГц - отлично. И 1МГц еще лучше бы было, да не тянут экраны.
Тоже самое иными словами. Зачастую в простых ситуациях (измерить и отобразить, или вывести длинную цепочку байт куда-то там) логика приложения просто не знает, что делать без ожидания окончания I2C операции.
Сразу видно не голый теоретик. Мало того, может как бы так сложится, что устройство на том конце глупо пошутило и прикольнулось "не аск", и если мы не ждали и ушли вперед, новый буфер начали делать то что? двойную буферизацию не предлогать из за расточительности.
А в общем основная фишка в том, что ждать завершения блокирующего при шине 100КГц долго, лучше не блокирующе через прерывание, при 400КГц блокирующее не очень рационално а при 800КГц почти эквивалентно работе через прерывания разница не значительная, и ещё очень неизвестно в чю пользу (это ТС многократно подтверждает скрывая дизассемблер своего кода и обещая что потом в ассемблере всё будет, ага, щас, уже много такого было, знаем))), а гемора намного меньше, код короче и без буфера как правило можно. Это и есть основная моя мысль в этой теме.
дальнейшее в основном к ув. Arhat109-2.
Ну и ОК, хотя дальнейшее тоже в общем без возражений.
to Logik, "Только замечу шо код обладает интересными свойствами - пины указывается по ардуиновски из скетча, причем любые, а код не по ардуиновски быстрый ... Получается класс экрана один, методы в одном экземпляре"
Да, да. Это то, что я вам и отписал: применение этого макроса в коде просто обязательно должно быть в ровно ЕДИНСТВЕННОМ месте. У вас это некий метод одного класса. Ибо этот "невероятно" универсальный и быстрый код .. внезапно заплодит всю память при его повторных вызовах .. ибо он велик.
Собственно, ничто вам не мешает вставить его инлайн прямо в метод, для которого он и писан. Макрос - инструмент для сокрытия и упрощения писания кода .. ваще-то. Это раз.
И второе: блокирующие функции - это все тот же самый delay() только в "скрытом виде". Управление железяками как-бы "не предполагает" ожидания в коде на время реакции железа. Дал команду и пошел считать/проверять/управлять далее. Как правило (и I2C - такой жеж), пока железка прочухается можно много чего ещё поделать. На 90% случаев, из того что видел за свою долгую программерскую жизнь, "блокирующие" алгоритмы часто от .. да-да от НЕЗНАНИЯ ЧТО ДЕЛАТЬ ПРОГРАММИСТУ с неблокирующими реализациями, типа : "а как жеж тогда?". :)
А нормально и "как обычно". Отдал по I2C и пошел далее .. упс, "пошутила" периферия и прислала "а пошел ты NACK" .. автомат тупо закроет сеанс и прекратит передачу: "ну и пойду" .. ещё и оповестить основной код может: "меня тут послали с вашей передачей NACK". А вот код при неблокирующей работе, вполне может и в ряде случаев просто обязан проверять результаты неблокирующих действий прежде чем "готовить следующую отправку" .. там даже в wiring реализации это ПРЕДУСМОТРЕНО. Просто часто и много наблюдю и наблюдал как НЕЖЕЛАНИЕ думать производит "блокирующие" реализации. Только потом регулярно "а чё оно так тормозит-то?" .. так ежели "программист-торомоз" .. а, с другой стороны: его учили "хорошему"?!? Или только "подходящим решениям"?
.. никакая программа НЕ делает НИЧЕГО, кроме того что было реализовано программистом.
P.S. Мне казалось, что дизассемблировать публично выложенный код для вас не является проблемой .. видимо ошибся, давайте помогу:
1. Препроцессорный вывод того что получается из обработчика в режиме "только мастер-передатчик" (работа с дисплеем большего и не требует), ловите:
Результат его же ассемблирования:
Как видим, вызов twiSendStop() - единственный и его можно развернуть инлайн. Сокращаем код этой функции за ненадобностью хука рестарта, и вызов/возврат и ускоряем;
Далее. Обнаруживаем, что нам 3 временных регистра _cr, _md, r25(TWDR) одновременно не нужны, и можно обойтись двумя и даже вовсе одним. Соответственно пролог/эпилог достаточен при сохранении только 3 регистров: 29,30,31. Из 36 байт каждого можно оставить только по 6. Итого 60байт прямой экономии.
Кроме этого, обнаруживаем что *ptr++ не пользует команду ld r25,Z+, которая устраняет ещё пару байт кода.
Обнаружив что twiSendStop используется только однажды и возвращается из прерывания .. перетасовываем проверки и переходы .. сокращаем ещё несколько команд.
Надеюсь, самостоятельно сделать это упражнение самостоятельно - вы таки способны. :)
Я не стал этого делать по одной причине: компиляция примера из апа выдает такое:
"Размер скетча в двоичном коде: 1 510 байт" что меня более чем устраивает. :)
У вас это некий метод одного класса. Ибо этот "невероятно" универсальный и быстрый код .. внезапно заплодит всю память при его повторных вызовах .. ибо он велик.
Угу. И я его использую для этого. Что не так?
Для меня не проблема, я от Вас ожидал еще и оценки времени исполнения. Не дождался. Но и пол шага в нужную сторону - уже хороше. По крайней мере не скажите "код не мой". Время я сам посчитаю. Для типичного прохода - отправки очередного быйта данных, стопы, старты, адреса - алах с ними, погоды не делают. Поехали по коду, информация о числе циклов на команду из известного Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.
Строки 3-21. 16 push по 2 цикла + 2 in по 1 циклу +clr по 1 циклу = 35 циклов.
Строки 73-91. 16 pop по 2 цикла + 2 out по 1 циклу +reti по 4 цикла = 38 циклов.
Само прерывание 4 цикла, если доктор нам не врет. Итого издержки на вход-выход 35+38+4=77 циклов.
Далее. Обнаруживаем, что нам 3 временных регистра _cr, _md, r25(TWDR) одновременно не нужны, и можно обойтись двумя и даже вовсе одним. Соответственно пролог/эпилог достаточен при сохранении только 3 регистров: 29,30,31. Из 36 байт каждого можно оставить только по 6. Итого 60байт прямой экономии.
Кроме этого, обнаруживаем что *ptr++ не пользует команду ld r25,Z+, которая устраняет ещё пару байт кода.
Обнаружив что twiSendStop используется только однажды и возвращается из прерывания .. перетасовываем проверки и переходы .. сокращаем ещё несколько команд.
Надеюсь, самостоятельно сделать это упражнение самостоятельно - вы таки способны. :)
ПС. Вам самому не стыдно публиковать код, у которого 30% текста - "магические цифры"? За такой код лупят нещадно везде, где видят. И правильно делают. Я обычно толерантен в этом вопросе, но такого количества я сдается еще не видел. Вам выслать на почту файл обявлений регистров и битов микроконтроллера? Или Вы реально думаете что дефайники увеличивают размер кода?
1. Обсуждаем частоту шины 800кгц или 1.25мксек на период .. посылка = 9бит. Итого 9*1.25 = 11.25мксек. Или на 16Мгц имеем 180 циклов. То есть, даже чисто языковая реализация отработает на 800кгц. Да, загрузка МК составит около 131/180 = 72%. Много, не спорю. Но никак не ваши 10%. :)
2. А теперь разгадка. "Обнаруживаем, что нам 3 временных регистра _cr, _md, r25(TWDR) одновременно не нужны, и можно обойтись двумя и даже вовсе одним. Соответственно пролог/эпилог достаточен при сохранении только 3 регистров: 29,30,31. Из 36 байт каждого можно оставить только по 6. Итого 60байт прямой экономии." А ровно также и ЦИКЛОВ ПРОЦЕССОРА. Каждый push/pop = 2 байта И 2 цикла на команду. Не, не вы писали? :)
Итого, из 16 пушей оставляем 3 .. минус 13. Столько же "поп". 26*2 = 52 цикла. .. и упс. Остается 131 - 52 = 79 .. или 79/180 = 43% .. менее ПОЛОВИНЫ процессорного времени .. на оптимизации Z+ выигрываем ещё пару команд и 3-4 цикла, на оптимизации переходов (хотя тут - сложно) можно "ещё отжать пару-тройку циклов .. и упс. Приходим к плотной, но вовсе не критически загрузке процентов в 40%. .. непосредственно в момент работы с I2C в режиме передачи .. то есть на 1% от всего кода исполнения. А при том, что ЭТОТ ЖЕ код способен работать и НЕБЛОКУРИУЮЩЕ вплоть до 490герц .. то он ЗАВЕДОМО предпочтительнее ДВУХ реализаций как у вас.
Вот это я и называю агрессивным говнокодерством в целом. Мало того, что сами попробовали "абы как" и не получилось, так вы ещё и остальных учите плохому, так и этого мало! Вместо того, чтобы осваивать и учиться на этом примере .. агрессивно пишите всякую фигню. Фи в квадрате.
P.S. Впрочем я сам и реализовал. Пользуйтесь. :)
Нет никакой разницы промеж "метода класса" как у меня написано и вашим указателем на .. единственную функцию.
Читать умеете?
http://arduino.ru/node/#comment-184606
2. Небольшое насилие над twi.h позволяет обнаружить что дисплей стабильно работает вплоть до частот интерфейса в 800кГц ..
Вот жеж правильный вброс. А что из этого следует?
"про 100Кгц речи нет. Про 800 разговор."
здесь http://arduino.ru/forum/programmirovanie/svoi-draiver-i2c-twi-trebuetsya-pomoshch#comment-184616
Какой нафиг 1кГц?! Крутитесь как вюн на сковородке.
1. Обсуждаем частоту шины 800кгц или 1.25мксек на период .. посылка = 9бит. Итого 9*1.25 = 11.25мксек. Или на 16Мгц имеем 180 циклов. То есть, даже чисто языковая реализация отработает на 800кгц. Да, загрузка МК составит около 131/180 = 72%. Много, не спорю. Но никак не ваши 10%. :)
Вы используете 145 тактов при расчетах и оценках . Здесь http://arduino.ru/forum/programmirovanie/svoi-draiver-i2c-twi-trebuetsya-pomoshch#comment-184584 здесь http://arduino.ru/forum/programmirovanie/svoi-draiver-i2c-twi-trebuetsya-pomoshch#comment-184612 и еще дето. С собой дискутируйте. Но по сути да, много.
Итого, из 16 пушей оставляем 3 .. минус 13. Столько же "поп". 26*2 = 52 цикла. .. и упс. Остается 131 - 52 = 79 .. или 79/180 = 43% .. менее ПОЛОВИНЫ процессорного времени .. на оптимизации Z+ выигрываем ещё пару команд и 3-4 цикла, на оптимизации переходов (хотя тут - сложно) можно "ещё отжать пару-тройку циклов .. и упс. Приходим к плотной, но вовсе не критически загрузке процентов в 40%. .. непосредственно в момент работы с I2C в режиме передачи .. то есть на 1% от всего кода исполнения. А при том, что ЭТОТ ЖЕ код способен работать и НЕБЛОКУРИУЮЩЕ вплоть до 490герц ..
Так реализаций две только для функции вывода байта. пару-тройку десятков команд излишних. Для каждой I2C своя. Небольшая плата за возможность поддержки нескольких шин, обмен без буферирования, быстрый вывод на экран и т.д А класс использующие их может быть и один (а может и несколько, каждой выдан указатель на функцию обмена и заметьте - нет проблемы синхронизации вывода из разных классов и мест кода вообще), он же через указатель на функцию работает, ну это как раз то, чего вы там не поняли воще.
Я не пойму что вы доказать-то хотите? Что я называл разные оценки в разное время обсуждения и оно "не работает" а ваш ногодрыг - "рассово-верен"?
Так я и НЕ считал потактово, мне это нафиг не надо .. вы просили посмотреть и прикинуть .. ну, посмотрел и прикинул .. вы сами посчитали точнее - и опять я не считал, а всего лишь поверил вам .. и чё? Драйвер - работает, точка. Возьмите код - он публичный и наслаждайтесь. И, если надо "ещё шустрее", то куда и на сколько его можно улучшить - в теме уже показано. Кому понадобится, тот сделает. Не переживайте. :)
.. или Вы хотите утвердждать что 100% блокирующий код ногодрыга лучше, пусть даже 80% НЕ блокирующего аппаратного драйвера? .. Вы в своем уме?
вот как раз это, ещё раз, я и называю агрессивное говнокодерство. Успокойтесь уже и пользуйтесь. Работает он на 800кгц. И кое-что даже остается коду прогаммы. А от вашего ногодрына - гарантированно не остается ничего, ибо он блокирующий .. как delay().
Уважаемые logik и arhat (можно без цифр?), вклиниваясь между вами (уважаемыми горячими финскими парнями) рискую получить от обоих. Ну это нормально.
Ув. Arhat пишет самописный универсальный драйвер twi шины, который как бы одновременно может быть master'ом и немного slave'ом. Это неправильно, шина с вами не согласна (и конечные автоматы atmel тоже). На шине i2c (тут постоянное смешение twi и i2c, но об этом позже) есть мастера (и они глухие), и есть ведомые slave (но они немые). Конкретное устройство на шине не может разорваться между "умным" и "красивым" (зачеркнуто) между master и slave. Это архитектура шины. Есть master'а, есть slave'ы. Одни глухи, как топор, другие слушают и если что - готовы ответить.
Ув. Arhat пишет библиотеку, которая сгенерирует любого "тяни-толкая". Но лучше (на самом деле единственно идеологически правильно), imho, ограничить результат master'ом (тупым, например, только на передачу, или обычным, как и положено, с приемом по запросу) и ... Вот тут возникает некая пауза, чуть ниже о ней. Смысл фразы в том, что устройства, для которых сгенерится драйвер (хороший, однополый), все-таки разного пола (зачеркнуто) назначения - это _либо_ master (никого не слушает, способен передавать и принимать, но по своему же запросу), _либо_ slave (всегда слушает и способен принять, а если нужно и передать по запросу).
Обычно в этом месте возникает вопрос - а сам-то чьих будешь (чтобы менторски учить всех)? Отвечаю - прочитавших спецификацию i2c шины (ну тут уже в гугл). Уверовавших в архитектуру этой шины как промышленного стандарта. Если вы встречаете у atmel упоминание _аппаратной_ поддержки twi, это означает поддержку на двухпроводной шине twi протокола i2c (т.е. определенной последовательности - старт, передача адреса, повторный старт и т.п.). В иных случаях (usi семейства attiny) это _возможность_ такой поддержки (с собственной ручной кодировкой части i2c протокола), но может быть и собственная реализация синхронного байт-ориентированного протокола (не i2c).
Ну вот как пример к последнему - tm1637 - вроде и похоже на i2c, но не i2c. Но похоже, тоже twi, но не совсем. Аппаратная поддержка atmel не поможет, но "ногодрыгом" сделать получится.
Еще раз о природе master и slave на шине i2c. Вот (например) ничего на шине не происходит. Мастера собой заняты, ведомые слушают шину. Вдруг начинается активность. Кто ее инициировал? Ведомые - нет, они могут только слушать. Мастера? Конечно, и есть (в протоколе i2c) механизм разрешения конфликтов попыток одновременного доступа к шине со стороны мастеров (арбитраж шины). Arhat, разумеется у вас это прописано в коде драйвера, верно? (Видимо я плохо искал, если что - просто тыкнете пальцем).
Реализация slave режима (и не только для i2c) сталкивается с понятийными затруднениями. Вы много видели в инете просто реализаций slave для i2c, one wire, того же spi? Правильно, это сферический конь в вакууме. Вот есть i2c термометр ds1621, температуру измеряет и термостат еще. Понятно, что от него ожидать. Он, в свою очередь, предоставляет услуги "команд" - начать измерять температуру, выдать результат, задать границы термостата. Или распространенный пример i2c устройств - fm радиомодули (приемники радиовещательного fm диапазона) вроде ar1010 или rda5807m с доступом "в регистры" - вы что-то пишите в эти "регистры" и устройство отрабатывает. Иными словами нам, как правило, в реальной жизни нужен master, чтобы работать с устройствами. Но предположим, что в качестве корреспондента выступает mcu, нам так захотелось. Мое утверждение состоит в том, что без конкретных знаний о желаемом функционале slave устройства (измерить что-то, отработать что-то), написание соответствующего slave драйвера сведется к простому буферу, куда мы будем складывать непонятно что поступающее от master'а.
Не настолько самоуверен, но последнюю фразу о slave устройствах и связанной с этим разработкой slave драйверов прошу прочитать еще один раз. Реализация (slave стороны) зависит от понимания, чего мы хотим получить _в конечном_ итоге (функционально), а не от самой возможности, что вот у нас _все_уже_это есть (буфера приема, неважно, можно и по внешей ссылке на буфера, гоните ваши данные, все у нас учтено).
О гондурасе, т.е. эффективности кода, из-за которой тут срач (зачеркнуто) дисскусия разгорается.
1. Непонятно, как многочисленные макросы могут привести к раздуванию кода при наличии оптимизирующего компилятора.
2. Непонятно, зачем ручная оптимизация (если правильно понял) для сохранения, например, только необходимых регистров при вызове функции.
Обычно речь идет о gcc, эффективность которого для нас интересна только в контексте используемых toolchain (ну как понимаю). Померяться дизасемблером мысль, конечно, интересная. Но что делать со мной, который пользуется компилятором для ansi с в среде cvavr v3.24 (да, лицензионный) и имеет для full master i2c atmega328p (максимальная оптимизация по размеру) менее 300 байт (без иронии - это немало imho)? Вот код пресловутого twi прерывания с конечным автоматом на обслуживание транзакции (передал-принял-закончил). Комментарии тоже мои на псево-английской мове.
Думаю, что при умелом использовании IAR EWAVR еще удивительнее (оптимизация), иначе зачем он оперся (за такие деньги)? Наверное можно расслабиться и наблюдать, например, за красивым (это без намека на иронию) кодом Arhat'а.
Последнее, чтобы не расслаблялись.
Ну вот зачем, парни, вы оба трогаете бедное создание - LCD экраны 1602 или 2004 (что одно и то же)? Страшно далеки эти экраны от i2c, а общаемся мы в известных случаях со сдвиговым i2c регистром "народным" pcf8574, чтобы он жив был со своими максимальными 100kHz за 56 руб. на Ali. Его продвинутые версии (другая маркировка, не встречал живьем) и 400kHz могут выдержать. 100 kHz, Карл! Бедный 1602 со своим собственным интерфейсом HD44780 и 20 kHz не потянет. Но 800kHz! 880 kHz!
(вкрадчиво) и если 1602 вдруг работает с выставленными в регистрах twi (для atmega328p) 800 kHz, показывает что-то, например, то это беззнаковая арифметика что-то сделала (с данными в регистрах) и реальная скорость...
Всё в общем-то верно, и мне тоже не понятно что хочет доказать Logik, об чем его уже и спросил.
Даташит на экраны утверждает, что они способны принимать команды со скоростью до .. 2 мегагерц. И 1Мгц - уже точно и даже в сдвоенном режиме работы. Вот отрабатывает он команды нешустро .. но это уже иной вопрос. :)
Позволяет или нет сдвиговый регистр поднять скорость со стандартных 100кгц, на которые заточен согласно даташиту - не знаю, осцилом не смотрел, а правила таковы, что приемник может сам тормозить мастера когда ему надо.
Собственно мое утверждение ровно о том, что Атмел позволяет выставлять частоту на шине 880гкц и этот мастер-трансмиттер с таковой теоретически справляется. Регистры вполне нормально прописываются теми значениями, которые требуются. Никакой "беззнаковой фигни" не происходит.
В целом, полностью соглашусь, что повышать скорость работы с экраном подниманием частоты I2C - ваще не решение. Тормоз - он и есть тормоз. Надо шустрей - берем другой экран на SPI .. а "ещё шустрее" .. Атмел не позволит "все равно". Даже пустой ногодрыг не работает шустрее 5.3Мгц. И оформление его в вызов, да ещё и косвенный уже занижает его частоту к 1 мгц "с хвостиком" .. добавление каждой последующей полезной команды (оно жеж ещё и делать что-то должен!) снижает оперативную частоту ещё больше.
И это ВСЕГДА блокирующий алгоритм. В любом применении.
Мне был интересен аппаратный неблокирующий "минимально-оптимальный" драйвер.
P.S. Да, стандарт мастера и слейва описан верно. Но реализация "мастер-слейв" вполне имеет место быть для распределенной сети "ардуин" на базе этой шины. Весьма актуально, ибо стандарт позволяет организовать арбитраж. Как раз сейчас занят проверкой и отладкой остальных блоков драйвера .. так вот пока идея такая:
Мега2560 - мастер, имеет датчик цвета TCS3200, работающий через таймерный хук + Уно - слейв, имеет узв. датчик HCSR-04 замеряющий дальность по прерываниям PCINT + дисплей LCD1602.
Делаем так:
1. Мастер -> Слейв передаем результат замера цвета и освещенности на предельно возможной частоте и Слейв их выводит на экран в нижнюю строку на типовой в 100кгц;
2. Слейв -> Мастер передаем данные о расстоянии на частоте 400кгц и Мастер их выводит на экран на тех же 100кгц.
Оба МК старательно замеряют реальное время работы на шине. Для достоверности, данные передаем "размножением" до пачки байт .. скажем в 100-500-1000шт. Память - позволяет.
Вот вам и "конкурентное" применение И мастера И слейва в одном флаконе. :) (собственно для примерно такого соединения дунек оно и делается для наших "робототехнических" задачек)
Ну вот зачем, парни, вы оба трогаете бедное создание - LCD экраны 1602 или 2004 (что одно и то же)? Страшно далеки эти экраны от i2c, а общаемся мы в известных случаях со сдвиговым i2c регистром "народным" pcf8574, чтобы он жив был со своими максимальными 100kHz за 56 руб. на Ali. Его продвинутые версии (другая маркировка, не встречал живьем) и 400kHz могут выдержать. 100 kHz, Карл! Бедный 1602 со своим собственным интерфейсом HD44780 и 20 kHz не потянет. Но 800kHz! 880 kHz!
(вкрадчиво) и если 1602 вдруг работает с выставленными в регистрах twi (для atmega328p) 800 kHz, показывает что-то, например, то это беззнаковая арифметика что-то сделала (с данными в регистрах) и реальная скорость...
Я немощного не обижаю! Ему те 800КГц, даже если потянет, как мертвому припарк. Ему нечего просто выводить. Да и старик свое отжил полюбэ. Речь о Ssd1306 и подобных. Там скорость востребована, ибо чисто графический и пикселей много (ну по жизни экрана много не бывает конечно, но сравнительно много).
Ув. Arhat пишет библиотеку, которая сгенерирует любого "тяни-толкая". Но лучше (на самом деле единственно идеологически правильно), imho, ограничить результат master'ом (тупым, например, только на передачу, или обычным, как и положено, с приемом по запросу) и ... Вот тут возникает некая пауза, чуть ниже о ней. Смысл фразы в том, что устройства, для которых сгенерится драйвер (хороший, однополый), все-таки разного пола (зачеркнуто) назначения - это _либо_ master (никого не слушает, способен передавать и принимать, но по своему же запросу), _либо_ slave (всегда слушает и способен принять, а если нужно и передать по запросу).
Обычно в этом месте возникает вопрос - а сам-то чьих будешь (чтобы менторски учить всех)? Отвечаю - прочитавших спецификацию i2c шины (ну тут уже в гугл). Уверовавших в архитектуру этой шины как промышленного стандарта. Если вы встречаете у atmel упоминание _аппаратной_ поддержки twi, это означает поддержку на двухпроводной шине twi протокола i2c (т.е. определенной последовательности - старт, передача адреса, повторный старт и т.п.). В иных случаях (usi семейства attiny) это _возможность_ такой поддержки (с собственной ручной кодировкой части i2c протокола), но может быть и собственная реализация синхронного байт-ориентированного протокола (не i2c).
Ну вот как пример к последнему - tm1637 - вроде и похоже на i2c, но не i2c. Но похоже, тоже twi, но не совсем. Аппаратная поддержка atmel не поможет, но "ногодрыгом" сделать получится.
Да. Целиком согласен. Именно "мастер" устройствоспецифичный. Одному надо паузы межбайтные, у другого адресация не как у всех и т.д. Ну и конечно для экрана, его специфика - скорость и возможность пихать в шину напрямую из флеша, шоб картинки и шрифты не гонять в буфер. А вот слейв, не быстрый конечно, можно и на аппаратном с прерываниями. И конечно в принципе "сферический конь".
1. Непонятно, как многочисленные макросы могут привести к раздуванию кода при наличии оптимизирующего компилятора.
2. Непонятно, зачем ручная оптимизация (если правильно понял) для сохранения, например, только необходимых регистров при вызове функции.
Собственно этот опыт и пытался передать ТС. Что из этого вышло и как воспринято видно выше.
Logik и Arhat
спасибо за ваши взвешенные э-э.. реакции, могли бы и на куски, если что (оба можете). Вас (множественное число) учить - только портить. (Шутка, учителя здесь не наблюдается).
Мы себя в танцах покажем. Если серьезно, надеюсь встретиться на теме, которую через некоторое время постараюсь открыть - о протоколе One Wire. Это через некоторое время.
Для себя, резюмируя. Никакой помощи Arhat'у в написании драйвера I2C не требуется, на грабли (понятийные) он наступил, и дальше сам может кого научить. Вылижет свой драйвер, и если будет такое "Lego" (генерация master'a _или_ slave'а) воспользуюсь для включения в свой код.
Для Arhat.
Это не для дисскусии, но. Всякий раз говорю о семействах ATtiny или ATMega. Вы же, как понимаю, ориентируетесь на XMEGA, где сказать мне нечего.
Мое утверждение состоит в принципиальной невозможности для упомянутых семейств (MCU) выставить частоту I2C выше 400 MHz. Это чисто теоретический спор. Критерием может быть что? Замер каких-то скоростей передачи-приема между MCU и _очень быстрым устройством_, например таким же MCU (или быстрыми I2C EEPROM в режиме блокового чтения, например). Времени на это сейчас жалко.
На практике это не имеет значения (в рамках упомянутых семейств MCU), поэтому уклоняюсь от спора. Мои устройства (часы, память, некоторые датчики) работают (по документации и на практике) на 400 MHz (это fast i2c), ну и слава Б-гу.
Для Logik
И вашим и нашим. Наши - это Arhat. Но. (философски) любая активность ни шине, даже аппаратная, это "ногодрыг", и если мы его берем под контроль (а так и получается в самых младших семействах MCU atmel), разве это плохо? Дело же не в том как мы назовем управление шиной. Ну, например, прямое. Или с аппаратной поддержкой (через чертов автомат на прерываниях). И таки да, граф. дисплей намного интереснее неповоротливых LCD (это IMHO). И там частота на шине очень даже, да. Надеюсь с вами встретиться в будующей теме об One Wire, ну как с оппонентом, к которому прислушиваются.
Для полноты картины только сейчас (это не фигура речи) закончил штучку (подделку), которая (простите, Arhat) ну никак не протворечит тому, что вы тут говорили, Logik. (Это TM1637 по необходимости и несколько i2c датчиков - грубый и экономный мастер i2c ну или как бы).
Спасибо Arhat и Logik - если отбросить эмоции (они должны быть!), было интересно с собеседниками такого уровня.
Надеюсь на встречу в других темах (более жесткую, ну это нормально) и удачи всем.
И Вам спасибо, за то что грамотно попытались (а Вам это удалось) охладить эмоциональный накал дискуссиии. Кое-что из вашего стиля возьму и себе "на вооружение", пожалуй (век живи - век учись).
По деталям: Я работаю с ATmega2560-AU16 - на плате Адруино Мега2560 китайского клона на CH340. Это не подделка, а какая-то собственная разработка косоглазых, что позволило нам принять участие в соревнованиях Робофест. Безрезультатно по неопытности, но это иной вопрос .. Есть ещё УНО и какая-то мелочь, типа Мини на базе ATmega328p - вот. Все разработки в arhat.h в первую очередь ведутся для семейства 2560 и его урезанных вариантов, а потом уже для 328P (как оказалось буковка тут похоже важна) и его урезанных вариантов.
По частоте I2C: Обе дуньки вполне нормально справляются с установкой скорости интерфейса вплоть до 880 килогерц. Проверочный скетч ещё не готов (мало времени и занят чуть другим), поэтому пока только оценочно по факту прописывания таких делителей и наблюдению за работой. Вот 1 Мгц похоже они не тянут, ибо на шину ничего не вылазит, или я не заметил.