свой драйвер I2C (TWI) требуется помощь ..

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Не понял как, но утерял предыдущую реализацию 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()  {}

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

А вот что происходит:

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() не происходит вовсе .. или они оказываются каким-то образом закрыты.

Может кто-то хоть что-то пояснить по ситуации? Спасибо.

vde69
Offline
Зарегистрирован: 10.01.2016

а чем типовая библиотека не устраивает ? я про wire

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

по многим причинам:

Типовая библиотека "жрет" 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 тут работает проще и существенно шустрее.

Далеко не полный перечень "чем не устраивает". На порядок больше возможностей при меньшем требовании к памяти МК и существенно большей скорости связи. :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

И? Нет идей, "что происходит"? Или "всё слишком сложно" и можно как-то поупрощать? :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Как-то ожидалось что идеи появятся таки .. жаль.

vde69
Offline
Зарегистрирован: 10.01.2016

Такую оптимизацию стоит делать для промышленых систем, а форум по ардуинке - это уровень ламеров....

В 90х я пробовал писать на ASM под однокристалки, и понимаю на сколько текущие библиотеки не оптимальны, но для текущих задач они выполняют свои задачи... то чем ты занимаешься - это для 99.9999% посетителей форума явные излишества.

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

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Примерно год назад я уже выражал свое мнение по этому вопросу, повторюсь: Ардуино - проект по большей части учебный, и как всякий учебный материал должен иметь разные варианты решений на разный уровень обучающегося .. но, в любом варианте код должен быть тщательно выверен и проработан, а не "писан на коленке". Тут и не только, а много где и практически все, а не только лишь избранные пишут про ламерские решения в Wiring.

То что он ваще есть этот wiring - просто замечательно, но это решение для очень-очень начинающего уровня. Только как "пример" .. ни о каком применении ни дома ни в производстве тут речи не идет от слова "вовсе".

То, что я делаю в качестве arhat.h (и этот код туда же!) - уже несколько попродвинутее, как мне кажется и уже позволяет решать кое-какие задачи. Несмотря на то, что сейчас развивается исключительно уровень "С-АСМ", без поддержки плюсов .. но это нижний уровень, и он просто обязан быть "вылизан" и "максимально расширен" по своим возможностям применения.

Потому что ИМХО, но учить надо не на наколенных поделках, а на качественном, хорошо прописанном коде. Вот, как-то так.

P.S.

Пока нет возможности добраться до платы, может таки кто выскажется "что происходит?" Ещё раз: есть какие-то идеи? Ошибки в приемной части - уже вижу, но меня сейчас волнует передающая часть: почему после отправки трех байт из буфера не выскакивает прерывание для TWI? Или тут где-то "утечка памяти" лезет?

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

У нас разные взгляды на wiring, ну да чорт с ней.

Конкретно:

Если проследить за потоком управления, то видно, что после отправки 3-х байт в счетчике 0, и по else мы просто обязаны попасть в TWI_SEND_STOP, но этого не происходит.

После взведения флага преравания в макросе twiReply() прерывание неизбежно, если его не запретить глобально в SREG. То есть НИЧЕГО не может произойти.

-------------------------

Утечка памяти? Вы,как мне кажется, совсем не новичек, и сумеете написать пример, где динамического распределения не будет почти совсем, чтобы избежать такого рода сбоев. Так что этот вариант я не стану рассматривать.

------------------------

Следовательно сбой в момент диагностического вывода на serial, больше негде. Там просто больше никакого кода не выполняется, но это ИМХО. Легко проверить, может я и не прав.

И еще: я бы сперва прогнал всю конструкцию без прерывания. Просто проверяя бит TWINT в TWCR, а прерывание пусть будет стороннее, чтобы код не переделывать. Просто повесьте этот обработчик на любой таймер и в "голове" прочитайте TWINT, если "взведен" - работаем, если нет - выходим. Заодно станет видно (если сбой сохранится), что именно запрещено: байт в шину "не пролез" или преравания глобально запрещены?

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Вот, хоть один взялся помогать, спасибо.

В том-то и дело, что поток управления честно отдает в интерфейс 3 байта, но в он в else ЕЩЁ не попадает: инкремент счетчика производится ПОСЛЕ проверки на ноль .. то есть он должен отправить последний байт, как обычно взвести прерывание (как и перед ним 2 байта проходят) и завершить работу. А вот при следующем ответе приемника (получил третий байт) как раз должно обнаружиться пустое значение и попасть в else .. но его похоже НЕ происходит этого "следующего прерывания" вовсе или происходит "не пойми что"... пока дунька недоступна накидал себе "схему" проверки, что и как можно поисключать, дабы понять что происходит:

1. Втыкнуть в loop() вывод в сериал - дабы убедится что сюда мы не доходим точно, на всяк случай.

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

3. Как-то убедиться что по шине третий байт в реальности отдается в дисплей и дисплей таки отвечат ACK/NACK .. осцила нет, попробую занизить общую скорость работы дуньки через главный прескалер и выйти на частоту интерфейса в пару герц, дабы посмотреть светодиодиками что там на проводках мелькает.. 490 герц я глазом не распознаю всяко.

.. пока больше идей нет.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Вступление понятно, это я и имел ввиду.

Вы взводите прерывание и выходите из обработчика. TWI Атмеги должен уже сам отправить байт и получить или не получит ACK. Я и написал, что никакой код, ПОСЛЕ выхода из обработчика и возврата в него снова, уже после переданного третьего байта, НЕ ДОЛЖЕН выполняться, если мы с Вами правильно понимаем работу Атмеги и не разучились читать датаШит по аглицки ;). Первый раз со статусом 0х18 и 0х28 все прошло нормально, так? Тогда чем отличается последний байт от предыдущего? Ведь явно - ничем. Шаг назад мы покинули обработчик с TWCR = 00000101 и статусом 0х28 и все было нормально, мы пришли за третьим байтом и, снова, покидаем обработчик вектора с TWCR= 00000101 и статусом 0х28, так? и больше в него не возвращаемся.

Поскольку TWINT взведен, то никакой код после выхода из обработчика не успеет выполнится, только РОВНО одна команда, так процессор атмеги устроен: после выхода из прерывания выполняется команда по программному указателю, после снова проверяются наличие прерывавний. Что там может выполнится? Понятия не имею! Поэтому и предложил вывести обработчик из TWI вектора и повесить его на какой нибудь таймер - тогда сбой отловить легче будет.

--------------------

Про осцил - он есть у всех. В любом компе есть звуковуха. Программ - полно. Я на cxem.net какую-то старую скачивал и ей постоянно пользуюсь. Мой личный алгоритм такой: Любимым ;) 838-м тестером определяем в режимах DC и AC амплитуды сигнала. Иходя из максимума показаний делаем на двух резисторах делитель до нужных нам 2Вольт, если постоянная составляющая нас не очень волнует, а это почти всегда так, градуировать псевдоосцил - полная дурь, то можно пропустить через конденсатор, не стану объяснять какой - через керамический, выбранный слепым методом из кассы в столе ;). И придется отрезать шнурок от старых наушников.

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

Сейчас поищу свою прогу и выложу ссылку.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Заценил, про осцил. Пасибки. Касса резисторов и кондюков есть, старых ушей нет, но не беда .. есть кучка дохлых блоков питания и чего-то ишо с такими соединителями .. "тюльпан" кажись зовутся .. подобрать можно. :)

Там не совсем так. Дело в том, что в третий раз выход из прерывания делается "да", ровно также как и при первых двух байтах. Но TWI достаточно медленный интерфейс для МК .. прерывание он выставит не сразу, а только апосля отдачи в шину 8 байт плюс 1 такт шины TWI (!) на прием ответа с той стороны. Плюсом - пропуск 1 команды МК как реакция на прерывание (если он не торчит в более высоком приоритете - таймер0 напр. выше). То есть даже тактируя TWI(I2C) 1Мгц (=1мкс) мы имеем "паузу" между прерываниями в 9мкс + 1 команда. На 16Мгц проц отбарарабанит .. 16*9 = 144+1 = 145 тактов. Команд, на самом деле, поменьше - около 100. Операции с ОЗУ занимают 2 такта и есть ещё тормозные команды. А на типовых 100кгц интерфейса имеем в 10 раз больше .. около 1000команд основной проги.

В целом тоже не вижу, где и что может утекать в этом примере .. по сути кроме таймера 0 и нет ничего. Он у меня тоже самописный .. но ведь уже почти как год пашет "как часы". В общем, добрался до дуньки, сел проверять далее.

 

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

2. Небольшое насилие над twi.h позволяет обнаружить что дисплей стабильно работает вплоть до частот интерфейса в 800кГц ..

Вот жеж правильный вброс. А что из этого следует? Да то, что обмен будет гдето 20 тактов на  бит, и до 200 тактов на байт. А на кой при такой скорости прерывание, если вход в прерывание, сохранение контекста выколупывание очередного байта, запуск обмена и выход из прерывания займет пошти стокоже, ну пусть немного менше. В основной программе выполнить десяток байт до следующего прерывания. А оно того стоит? Это ж быстрая переферия, делайте ногодрыг. Все упрощается на порядок. И летает. А если ногодрыг завернуть в функцию, указатель на которую скармливать классам устройств использующих шину ;)))  А  если эту функцию в шаблончик и либку, то можна жить по новому

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ваши рассуждения неверны в корне. Перечитайте последний пост:

ДАЖЕ на скорости I2C в 1 мегагерц проц успевает выполнить до 145 тактов команд. Это далеко не "десяток-другой" .. примерно на порядок больше. Это на предельно допустимой скорости для данного девайса тут. Быстрее аппаратный I2C не может, а программный ногодрыг будет заведомо медленней и куда как затратнее по ресурсам.

Штатные сокорости - это 400килогерц или даже и вовсе 100 килогерц .. то есть уже около 1000 тактов "в среднем" .. это достаточно солидный кусок проги .. особенно ежели учесть что ногодрыг, ШИМ и прочие побрякушки это часто 1-3 команды "от силы".

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Так .. теперь я ваще ничего не понимаю .. вот вывод в сериал с натыканным доп. выводом:

SP=0x21F7, Stack=0x21F7RamEnd=0xFFFF
TWCR=5, TWBR=125, TWSR=251,SPs=0x21F3

,start1: twiMode=6,start2: twiMode=2
, s1=0x8, cr=0x5
,ls3: twiMode=2,SPs=0x21F3
, s1=0x18, cnt1=3, cnt2=2, cr=0x5
,start1: twiMode=2
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x
, s1=0x28, cnt1=2, cnt2=1, cr=0x521EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x
, s1=0x28, cnt1=1, cnt2=0, cr=0x521EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SPw=0x21EC
SP

1. вывод SP= это запоминание значений в самом начале setup() первыми командами и их вывод уже после Serial.begin();

2. Вывод SPs= добавлен сразу после вывода регистров TWI в lcdSetup() перед первой отправкой.

3. Строка ls3 - там же где и была: перед второй попыткой вывода, только добавлен вывод стека

4. выводы SPw - из цикла ожидания освобождения TWI_READY в функции twiWrite() .. вместо пустого цикла втыкнул вывод стека.

Итого: стек никуда не плывет, после отправки третьего байта прерывания TWIE не происходит вовсе, прога висит в twiWrite() в ожидании когда же закончится предыдущая передача.

При выводе последнего байта видно что прерывание произошло в момент попытки вывода стека в одной строке видим SPw=0x и после отработки прерывания видим CR=0x521EC вот 21EC - остаток вывода после прерывания ..

А также видно что прога "повисает" (в смысле ваще перестает что-то выводить в последней строке. В сериале больше ничего не появляется.

Что происходит?!?

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

Ваши рассуждения неверны в корне. Перечитайте последний пост:

ДАЖЕ на скорости I2C в 1 мегагерц проц успевает выполнить до 145 тактов команд. Это далеко не "десяток-другой" .. примерно на порядок больше. 

Читаете бегло, но с пропусками. Повторю. Но один раз. Дальше долбитесь до следующего просветления, как хотите.

Logik пишет:

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

Arhat109-2 пишет:

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

При кривых руках - однозначно. А по нормальному один ноп добавить надо.

Arhat109-2 пишет:

Штатные сокорости - это 400килогерц или даже и вовсе 100 килогерц .. то есть уже около 1000 тактов "в среднем" .. это достаточно солидный кусок проги .. особенно ежели учесть что ногодрыг, ШИМ и прочие побрякушки это часто 1-3 команды "от силы".

про 100Кгц речи нет. Про 800 разговор.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Сохранение ПОЛНОГО контекста - да гемморой .. есть attribute(NAKED) для борьбы с этим. И ручное сохранение того, что вам в реальности надо. Тут, как видите, практически не надо почти ничего .. :) За оставшиеся 50-60 команд МК в окне 800кгц я успею ещё и половину датчиков опросить, да по нескольку раз .. и в основном коде буду тупо читать "всегда готовые" значения .. :)

Вот ещё вариант. Вывод в сериал остался только внутри обработчика прерывания .. и упс. Третий байт уходит и прерывание есть. Но, сериал дохнет напрочь (надо свой делать). :)

SP=0x21F7, Stack=0x21F7,twiWrite start: twiMode=215
, s1=0x8, sp=0x21D5, sr=0x2, start!, END: cr=0x85, sp=0x21D5, sr=0x216
, s1=0x18, sp=0x21D8, sr=0x2,MTACK: cnt1=3, cnt2=2, END: cr=0x85, sp=0x21D8, sr=0x217
, s1=0x28, sp=0x21D8, sr=0x2,MTACK: cnt1=2, cnt2=1, END: cr=0x85, sp=0x21D8, sr=0x218
, s1=0x28, sp=0x21D8, sr=0x2,MTACK: cnt1=1, cnt2=0, END: cr=0x85, sp=0x21D8, sr=0x219
, s1=0x28, sp=0x21D8, sr=0x2,MTACK: cnt1

 

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

Сохранение ПОЛНОГО контекста - да гемморой .. есть attribute(NAKED) для борьбы с этим. И ручное сохранение того, что вам в реальности надо. 

А Вы его использовали? В какой строке? А какие регистры Вы сохранять ручками собрались, Вы знаете каки задействовал компилятор, чтоб не сохранить лишнего?

Не пишите о том, чего не знаете и не используете.

Вы бы лучше свой код дизасамблировали да посмотрели чего и скоко там сохраняется, и скока на это времени уходит оценили. Заодно и сколько тактов на опросы датчиков. Так, для справки, "тест показал, что для микроконтроллера ATmega328, работающего на частоте 16 МГц, вызов функции micros занимает 3.5625 мкс", пошти 60 тактов.

Я вобщем, чего взялся Вам писать, седня Вы наконец то про 800КГц постигли, хотя я это не раз на форуме писал, подумал что наконец дошло, может и дальше постигните, но увы ))) Ниче. Бывает.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Всё, нашел. Ошибка в разворачивании макроса twiReleaseBus() внутри макроса twiStop(). Поправил, все сразу заработало. Итого преобразователь из I2C в параллельный интерфейс LCD1602A вполне способен работать на частотах шины I2C от 10кгц до 800кгц. Но, времена указанные в даташите не верны! Время исполнения типовых команд - 4 миллисекунды!

То есть lcdSend() можно смело переводить на хук таймера и не делать в нем delay(). Очень долго.

Вопрос можно закрывать.

to Logik: мы с вами не знакомы, и не надо утверждать чего я знаю, а чего нет. Вместо этого, лучше откройте arhat.h и посмотрите сами что и КАК уже используется. :)

Ассемблер конечно же смотрел. И этот обработчик будет в него преобразован и оптимизирован в асме, конечно же. Кстати, обнаружил что кумпилятыр не умеет генерить таблиную версию switch() .. придется делать макрос. :(

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Arhat109-2 пишет:

Кстати, обнаружил что кумпилятыр не умеет генерить таблиную версию switch() .. придется делать макрос. :(

Уважаемый, у Вас же одни волатили кругом, компилятор их боиццо! ;)

-----------------------

Еще добавлю: Я, все таки, сторонник подхода коллеги Евгения П., не нужно мешать компилятору работать и не стоит экономить два байта и три такта. Уже не времена PICа - 8-ми битного, с ресурсами, как у Тиньки 13. Да и Тиньку, если и использовать, то только на самом простом уровне, если лень схему на двух транзисторах сделать.

Если мало ресурсов, то нужно взять контроллер на 100 рублей дороже.С частотой 84 Мгц и RAMом, которого точно хватит. Мало его - есть еще круче.  

При этом я помню, как сам в 64к памяти уладывал весьма серьезные алгоритмы обработки графики, но сегодня считаю это не верным. Ресурсы микроконтроллера - на порядок дешевле ресурсов программиста. Даже в нашем варианте - в качестве хобби. Ардуино Дуе на али уже меньше 1000 рублей, чего уж там говорить.

---------------

Но это так, просто май хамбл опиньён. Работу Вы ведете интересную, как хобби и оттачивание мастерства. Только не переоценивайте практическую ценность.

Если говорить о практической ценности - то нужно собирать gcc с правильными флагами и делать свой обвес к нему и "дуде" и свою среду в формате "опер сорс". Денег не принесет, но как альтернативная среда - принесет славу среди полутора десятков ботанов.

Но тут - ловушка: как только вы соберете первый раз gcc так, как вам надо и зальете прогу чистой "дудой", то для себя решите, что ни "студия" ни, тем более" Ардуино  IDE, Вам не нужно. Своя экосистема, свои библиотеки, которые или самопис или тщательно отобранные в сети. Любимый редактор, не сложнее VIMа.

А откуда юниксоиды беруться, как Вы думали? ;).

Только вот массоовость Ардуинке дала именно простая IDE и wiring, который скрывает от новичка кучу лишней инфы. И опытный программист тоже понимает, что "робота", "поливную систему" или "заумный дом" проще писать и отлаживать на простейшем инструменте. Ведь это и есть Ардуино. Если отказываться от wiring, то, очевидно, нужно и от загрузчика отказываться - он ведь тоже глючный и причина сбоев. Тогда IDE уже тоже не нужен. И тогда зачем ардуинка, есть макетки с контроллером, или просто контроллер, некоторые - в удобных DIP корпусах, да и QFP тоже ничего, с нормальным паяльником и BGA  нам не страшен. Но куда, в этом случае, делась чудесная, в своей цельности Ардуинка?

Обратите внимание на форум - писатели осцилографа - почти сразу поняли, что для них wiring - не подходит совсем, а строители роботов и "умных" домов - никогда не столкнуться с критическими по времени или памяти местами. Это совсем разные типы задач.

------------------------

(*) если вдруг кому не понятно, то "дуда" это avrdude.

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

to Logik: мы с вами не знакомы, и не надо утверждать чего я знаю, а чего нет. Вместо этого, лучше откройте arhat.h и посмотрите сами что и КАК уже используется. :)

Ваш код говорит о Ваших знаниях. Спасибо, arhat.h я уже смотрел с Вашего совет осенью. И отписался тогда Вам почему это безпоезная трата времени, оно не жилец. О многократных инклудах до сих пор вспоминаю с содроганием. 

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

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Отвечу обоим сразу, ибо холиварить уже не интересно ни разу.

wdrakula, Logik, В части применяемости wiring и его круга задач - вы оба, безусловно правы и я с этим согласен и уже писал "замечательно что он вообще есть". Вы как-то это не заметили, акцентирую явно. Для целей и средств вовлечения И обучения начинающих, просто любопытсвующих этот проект - просто замечателен.

Ровно также, я с вами обоими (и остальными, кто тут не участвует, но читает) согласен и в плане того, что задачу надо решать теми средствами, которые наиболее подходящи для её решения. Безусловно в общем виде. А вот далее начинаются "нюансы" в части критериев "подходящности" .. и тут уже, КАЖДЫЙ РЕШАЕТ ЗА СЕБЯ - и подходяшим часто оказывается не то, что "подходит под задачу". У другого "специалиста" с его тараканами (знаниями, опытом и т.д.) будет иное мнение (два спеца - три мнения): один возьмет "тиньку" и решит задачу, для которой другому надо не ниже "двухядерного ксенона". Отсюда, не желаю спорить про "подходящие инструменты".

Есть иной критерий: "минимально возможные средства". Так вот, задачу МОЖНО решать "подходящими средствами", а можно "минимально возможными". Ощущаете разницу? :)

И, в целях обучения, коим я и занимаюсь "на дому", я считаю (ИМХО), что обучать надо минимально возможным решениям, красивым решениям (они как правило совпадают), правильному алгоритмированию задачи, которая наглядно ПОКАЗЫВАЕТ и подсказывает сама это самое красивое решение .. а не говнокодерству воведенному в ранг аксиомы, активно и агрессивно утвердившемуся по жизни среди массы недоучек. Да, экономика говна - рулит. Но это не аргумент учить плохому.

.. собственно для ЭТОГО и пишу свой arhat.h, а вовсе не для того, чтобы получить "славу или бабки на телефон или признание миллионов", как вы ожидаете (судя по постам). Мне глубоко пофиг ваша оценка меня, как специалиста или какая ещё (как общения на этом форуме) .. вполне хватает задач, где моя помощь востребована на 8-12 часов каждый день. И, чем больше говнокодеров в мире, тем чаще зовут таких же "старых велосипедистов". Так что работой нас обеспечивают, даже на пенсиях..

Выкладываю, только потому что "может кому-то пригодится" этот велосипед. Но это делается как альтернатива Wiring и в первую очередь как более эффективный код. И вопрос "взять двухядерный ксеон" - у меня не стоит. Есть Дунька, на ней и обучаем..

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Там нет "многократных инклудов" кроме одного(!) вложения распиновки в главный хидер .. или вы не смотрели или нифига не поняли.

Там полно "многократных" вложений макроопределений .. это "плата" за возможность работать с пинами напрямую "1 оператор = 1 команда МК", а не 400 как digitalWrite(). Предпочитаю расплачиваться ресурсами препроцессора, чем дунькиными при выполнении. В отличии от Cyberlib, у меня решен вопрос настройки на конкретный МК такого подхода, и решен вопрос использования типовых вызовов digitalWrite() и пр., позволяющее использовать публикуемые скетчи практически "один в один" с этой библиотекой.

Там жеж есть и "ассемблерные вставки", коих вы похоже не видели даже .. там жеж есть и иное. Я не пишу, а его пользую .. и этот обработчик постепенно появится там жеж. Когда? Да когда сочту этот момент полезным. :)

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Во первых обещал ссылку на программу для псевдоосцила на звуковухе. Я пользуюсь вот этим:http://radiopill.net/Programs/digital_oscilloscope_v.3.0.zip.

Во вторых: Архат, зря Вы на меня огорчились! Я как раз горячо приветствую Вашу работу, я просто заметил, что wiring Вы не замените, по указанным выше причинам. Но для людей, понимающих где и зачем им нужно больше, чем может дать wiring - Ваша работа ОЧЕНЬ нужна.

Простите, если задел.

=================

Про псевдо осцил: есть много схем щупа к нему - сами в сети найдете. Я - не делаю, просто понимая, что и куда можно "совать" ;). Но для безопасности можете сделать. Причем можно сделать с автоопределением амплитуты и автоподбором аттенюатора/усилителя. Но, как по мне,  так это баловство, подростковый перфекционизм. Но - если интересно - велком, схему нарисую. Аттенюатор 100, 10 и1 для диапазонов 20-200, 2-20 и < 2 В, а дальше "опен/шорт" замыкать или нет кондер, и дальше колесико делителя обратной связи в копеечном ОУ, питаться - от 5 В USB, для универсальности.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ни разу ни на кого не обиделся .. стиль общения у меня такой .. пенсионерский .. иногда. :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Упс, вынужден извиниться, ибо с цифирьками экономии байтиков я поднаврал выше. Оказалось, что существенное уменьшение размеров было при отключенной компиляции Slave режима .. только мастер, вот и меньше существенно. В полном объеме экономия невелика .. байт 20-30 по причине того, что switch оптимизируется компилятором достаточно слабо. Примерно половина размера кода драйвера в ассемблере - это переходы "туда-сюда обратно" из-за большого количества ветвлений. Делать проверку в цикле и хранить в памяти программ табличку значений и переходов и обойтись одним косвенным jump - он не умеет вовсе.

В целом, работа по части освоения интерфейса оказалась полезной, и как вывод twi.h автоподключаемый к библиотеке Wire.h писан в среднем очень даже неплохо. Существенный недостаток только один - внутренняя буферизация обмена. Без неё "то на то" и выходит, если не углубляться в ассемблер.

P.S. Да, и по части "сохранения контекста" и "геммороя с ним" - по большей части, уже в 1.6.4 этот момент исправлен. Обработчик прерывания сохраняет ровно тот контекст, который портит.

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

P.S. Да, и по части "сохранения контекста" и "геммороя с ним" - по большей части, уже в 1.6.4 этот момент исправлен. Обработчик прерывания сохраняет ровно тот контекст, который портит.

.. а портит  все, если он достаточно развит.

Снова художественный треп. Дизасемблированого кода нет, расчета или измерения тактов на обслуживание прерывания нет, художественная литератута одна. Скока Вам вдалбливать, компилирует GCC, важна его версия а не версия ИДЕ. И к стати GCC косвеный goto поддерживает уже лет 10 как, используйте вместо свича и получите косвеный jamp. Пример на форуме был. 

Писец какой процесс, скоко возни, буфера, переходы,  вместо функции из трех строк в шаблоне.

void ssd1306_i2cWriteByte##scl(byte v)           \
{                                            \
    SDA_OUT();                       \
    for (byte i=8;i;i--)  { if (v & 0x80)   SDA_HIGHT();  else  SDA_LOW();SCL_HIGHT();  v <<=1; SCL_LOW(); }                \
  SDA_IN(); SCL_HIGHT(); WITH_HIGHT_SDA(); SCL_LOW();        \
}\

Кстати работает на любых пинах контролера ;) Можна хоть пять экранов навесить )))

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Покажите уж тогда и организацию глобального вызова по адресу Global Call таких вот контроллеров и связь промеж них в парралельном режиме с чередованием Мастер-Слейв .. двухстрочный вы наш! Свести большой обработчик до ваших двух строк - дело не хитрое. Вы лучше покажите КАК эти ваши строчки по записи байтика в шину одновременно и асинхронно будут принимать его от соседней дуньки .. что уж на самом деле! кому нужен очередной ногодрыг на шине?

Обработчик прерывания - это полный конечный автомат, позволяющий ВСЁ ЭТО делать ОДНОВРЕМЕННО И АСИНХРОННО .. и на тех самых 800кгц.

<del> Извиняюсь за свой вредный характер.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Да, и ещё. Реализация полноценного общения по I2C вашими макросами, да при передачах длинных пачек во внешнем цикле будет не только надолго завешивать контроллер, вплоть ДО СЕКУНД, но ещё и по размеру реализации ВЫЙДЕТ БОЛЬШЕ. А то и в разы, смотря сколько устройств на шине и как часто они общаются друг с другом.

Ваш макрос не подходит НИ ДЛЯ чего более, кроме как писать на экран, и в его единственно примененном месте: реализация какой-нить lcdWrite(). Ни для чего иного он не подходит.

dhog1
Offline
Зарегистрирован: 01.03.2016

Располагая функциями чтения и передачи байта, ув. 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 "и вы тоже правы".

dhog1
Offline
Зарегистрирован: 01.03.2016

Речь шла о форматировании ранее набранного текста.

[Del]

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

dhog1, спасибо. Поясню ряд моментов:

1. Скорость TWI у атмела настраивается битретом вплоть до 1Мгц включительно. Запустить его с LCD1602 на 1Мгц мне - не удалось, но вот 880кгц он держит вполне уверенно.

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

Остальные режимы только компилировались, но ещё не проверялись. Там же воткнул и пример работы с LCD1602. Пока тоже только примитив вывода заданного количества байтиков. Остальное буду добавлять "по мере надобности".

2. Внутри транзакции, между stop/start одна железка, конечно будет работать только в одном режиме. Запустить (задавал тут где-то вопрос - его сообщество тупо проигнорило) TWI на одном МК в режиме "самопрослушки", когда мастер педерает и сам же обнаруживает себя на шине как слейв - не удастся по причине того что регистр статуса - один. Часть состояний потеряется или у мастера или у слейва.

3. Но, тем не менее, одна железка вполне может работать ОДНОВРЕМЕННО, последовательными транзакциями И как мастер И как слейв. Эти режимы у атмела - полностью независимы, за исключением варианта "мастер-прием" + "слейв-прием" .. сигнал ACK один, но используется с разной логикой. И вот как раз, реализовать ТАКОЙ режим макросами "прямиого ногодрыга" - нельзя "по определению". У таких макросов есть ещё один существенный недостаток: дублирование кода при повторном использовании. Макрос большой и код будет разрастаться существенно, и при этом он сотается блокирующим.

Что сделано:

1. Разделение компиляции на "отдельные куски": можно включать в код обработчика только мастера-передатчика, только мастер-приемник, только слейв-приемник, только слейв-передатчик .. или в произвольной их комбинации. Это для сокращения размера кода, если что-то не востребовано. Каждый блок тянет около 200байт кода по отдельности. Все вместе тянут на почти 430байт. (против 610 у wiring). И это на уровне "С". Ассемблером можно ещё ужать 76 байт "пролога и эпилога" обработчика. Там столько не требуется и упростить ветвления (очень существенная часть обработчика). Теоретически, один мастер-передатчик ужать до размера приведенного макроса-ногодрыга - вполне реально.

2. Убрана внутренняя буферизация. Экономим около 200байт оперативы, и плюсом дополнительно устраняем вспомогательные перетасовки из классов "верхнего уровня".

Пока как-то так. Пробуйте, проверяйте .. пишите письма, ежели что не так.

Logik
Offline
Зарегистрирован: 05.08.2014

dhog1 пишет:

Располагая функциями чтения и передачи байта, ув. Logik без всякого сомнения может творить с шиной TWI что пожелает. Такой код (как в приведенном примере) дает блокирующие (ожидающие своего исполнения) функции, но очень часто мы сами не знаем что нам бы еще поделать, не получив ответа от I2C устройства, например. Т.е. логика тут такая - запросил (передал) данные, дождался, обработал, пошли дальше.

Угу.Суть верна, и спорить не о чем. Только  замечу шо код обладает интересными свойствами - пины указывается по ардуиновски из скетча, приче любые, а код не по ардуиновски быстрый т.е. digitalWrite можна и обойти. Там под макросами работа с портом, ну в общем для тех кто в теме :)) А на верх идет только указатель на функцию. Получается класс экрана один, методы в одном экземпляре, а экранов хоть 5 штук, опять же для ценителей ;)

dhog1 пишет:

 Ну вот (худший) пример работы с текстовыми I2C дисплеями. Цепочка взаимодействия там такая ....

Ну это ад и израель в одном плафоне. Нам чего попроще один бит - один пиксель, ну или один байт или ворд на крайняк. Это вызывает рост обемов и соответственно проблему скорости. Графика - наше все,  ssd1306  например, а эти символьные , шо до сих пор ещё живы?  Какие проблемы скорости у "простейших!, 32 байта, пусть даже на 4 умножить, как Вы пишете не успевают? И  паузы для обработки нам тоже чужды. Грузить и поскорей, 800КГц - отлично. И 1МГц еще лучше бы было, да не тянут экраны.

dhog1 пишет:

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

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

А в общем основная фишка в том, что ждать завершения блокирующего при шине 100КГц долго, лучше не блокирующе через прерывание, при 400КГц блокирующее не очень рационално а при 800КГц почти эквивалентно работе через прерывания разница не значительная, и ещё очень неизвестно в чю пользу (это ТС многократно подтверждает скрывая дизассемблер своего кода и обещая что потом в ассемблере всё будет, ага, щас, уже  много такого было, знаем))), а гемора намного меньше, код короче и без буфера как правило можно. Это и есть основная моя мысль в этой теме.

dhog1 пишет:

дальнейшее в основном к ув. Arhat109-2.

Ну и ОК, хотя дальнейшее тоже в общем без возражений.

 
Arhat109-2
Offline
Зарегистрирован: 24.09.2015

to Logik, "Только  замечу шо код обладает интересными свойствами - пины указывается по ардуиновски из скетча, причем любые, а код не по ардуиновски быстрый ... Получается класс экрана один, методы в одном экземпляре"

Да, да. Это то, что я вам и отписал: применение этого макроса в коде просто обязательно должно быть в ровно ЕДИНСТВЕННОМ месте. У вас это некий метод одного класса. Ибо этот "невероятно" универсальный и быстрый код .. внезапно заплодит всю память при его повторных вызовах .. ибо он велик.

Собственно, ничто вам не мешает вставить его инлайн прямо в метод, для которого он и писан. Макрос - инструмент для сокрытия и упрощения писания кода .. ваще-то. Это раз.

И второе: блокирующие функции - это все тот же самый delay() только в "скрытом виде". Управление железяками как-бы "не предполагает" ожидания в коде на время реакции железа. Дал команду и пошел считать/проверять/управлять далее. Как правило (и I2C - такой жеж), пока железка прочухается можно много чего ещё поделать. На 90% случаев, из того что видел за свою долгую программерскую жизнь, "блокирующие" алгоритмы часто от .. да-да от НЕЗНАНИЯ ЧТО ДЕЛАТЬ ПРОГРАММИСТУ с неблокирующими реализациями, типа : "а как жеж тогда?". :)

А нормально и "как обычно". Отдал по I2C и пошел далее .. упс, "пошутила" периферия и прислала "а пошел ты NACK" .. автомат тупо закроет сеанс и прекратит передачу: "ну и пойду" .. ещё и оповестить основной код может: "меня тут послали с вашей передачей NACK". А вот код при неблокирующей работе, вполне может и в ряде случаев просто обязан проверять результаты неблокирующих действий прежде чем "готовить следующую отправку" .. там даже в wiring реализации это ПРЕДУСМОТРЕНО. Просто часто и много наблюдю и наблюдал как НЕЖЕЛАНИЕ думать производит "блокирующие" реализации. Только потом регулярно "а чё оно так тормозит-то?" .. так ежели "программист-торомоз" .. а, с другой стороны: его учили "хорошему"?!? Или только "подходящим решениям"?

.. никакая программа НЕ делает НИЧЕГО, кроме того что было реализовано программистом.

P.S. Мне казалось, что дизассемблировать публично выложенный код для вас не является проблемой .. видимо ошибся, давайте помогу:

1. Препроцессорный вывод того что получается из обработчика в режиме "только мастер-передатчик" (работа с дисплеем большего и не требует), ловите:

extern "C" void __vector_39 (void) __attribute__ ((signal,used, externally_visible)) ; void __vector_39 (void)
{
    uint8_t _cr;
    uint8_t _md = twiMode;
    uint8_t _st = twiState=((*(volatile uint8_t *)(0xB9)) & ((1 << (7))|(1 << (6))|(1 << (5))|(1 << (4))|(1 << (3))));
# 307 "/home/user/sketchbook/libraries/Arhat/arhat_twi.h"
        if( (_st == 0x08) || (_st == 0x10) )
        {


            (*(volatile uint8_t *)(0xBB)) = twiSLARW;
            _cr = ((1 << (2))|(1 << (7))|(1 << (0))|((_md & TWI_IS_SLAVE)? (1 << (6)):0));
        }else{
            if( (_st == 0x20) || (_st == 0x30) )
            {


                twiSendStop(_md); return;
            }
            if( (_st == 0x18) || (_st == 0x28) )
            {


                if( twiMT_Count-- ){
                    (*(volatile uint8_t *)(0xBB)) = *twiMT_Ptr++;
                    _cr = ((1 << (2))|(1 << (7))|(1 << (0))|((_md & TWI_IS_SLAVE)? (1 << (6)):0));
                }else{
                    twiSendStop(_md); return;
                }
            }else{
# 364 "/home/user/sketchbook/libraries/Arhat/arhat_twi.h"
            }
        }




    twiMode = _md;
    (*(volatile uint8_t *)(0xBC)) = _cr;
}

Результат его же ассемблирования:

 357               		.type	__vector_39, @function
 358               	__vector_39:
 363 0000 1F92      		push r1
 364 0002 0F92      		push r0
 365 0004 0FB6      		in r0,__SREG__
 366 0006 0F92      		push r0
 367 0008 1124      		clr __zero_reg__
 368 000a 0BB6      		in r0,__RAMPZ__
 369 000c 0F92      		push r0
 370 000e 2F93      		push r18
 371 0010 3F93      		push r19
 372 0012 4F93      		push r20
 373 0014 5F93      		push r21
 374 0016 6F93      		push r22
 375 0018 7F93      		push r23
 376 001a 8F93      		push r24
 377 001c 9F93      		push r25
 378 001e AF93      		push r26
 379 0020 BF93      		push r27
 380 0022 EF93      		push r30
 381 0024 FF93      		push r31

 392 002a 3091 B900 		lds r19,185
 393 002e 932F      		mov r25,r19
 394 0030 987F      		andi r25,lo8(-8)
 395 0032 9093 0000 		sts twiState,r25
 398 0036 9830      		cpi r25,lo8(8)
 399 0038 01F0      		breq .L22
 402 003a 9031      		cpi r25,lo8(16)
 403 003c 01F4      		brne .L23
 404               	.L22:
 407 003e 9091 0000 		lds r25,twiSLARW
 408               	.L40:
 409 0042 9093 BB00 		sts 187,r25
 412 0046 80FD      		sbrc r24,0
 413 0048 00C0      		rjmp .L24
 414 004a 25E8      		ldi r18,lo8(-123)
 415 004c 00C0      		rjmp .L25
 416               	.L23:
 419 004e 387E      		andi r19,lo8(-24)
 420 0050 3032      		cpi r19,lo8(32)
 421 0052 01F0      		breq .L29
 424 0054 9831      		cpi r25,lo8(24)
 425 0056 01F0      		breq .L28
 428 0058 9832      		cpi r25,lo8(40)
 429 005a 01F4      		brne .L25
 430               	.L28:
 433 005c 9091 0000 		lds r25,twiMT_Count
 434 0060 2FEF      		ldi r18,lo8(-1)
 435 0062 290F      		add r18,r25
 436 0064 2093 0000 		sts twiMT_Count,r18
 437 0068 9923      		tst r25
 438 006a 01F0      		breq .L29
 441 006c E091 0000 		lds r30,twiMT_Ptr
 442 0070 F091 0000 		lds r31,twiMT_Ptr+1
 443 0074 9F01      		movw r18,r30
 444 0076 2F5F      		subi r18,-1
 445 0078 3F4F      		sbci r19,-1
 446 007a 3093 0000 		sts twiMT_Ptr+1,r19
 447 007e 2093 0000 		sts twiMT_Ptr,r18
 448 0082 9081      		ld r25,Z
 449 0084 00C0      		rjmp .L40
 450               	.L24:
 453 0086 25EC      		ldi r18,lo8(-59)
 454 0088 00C0      		rjmp .L25
 455               	.L29:
 458 008a 0E94 0000 		call twiSendStop
 459 008e 00C0      		rjmp .L21
 460               	.L25:
 463 0090 8093 0000 		sts twiMode,r24
 466 0094 2093 BC00 		sts 188,r18
 467               	.L21:
 472 0098 FF91      		pop r31
 473 009a EF91      		pop r30
 474 009c BF91      		pop r27
 475 009e AF91      		pop r26
 476 00a0 9F91      		pop r25
 477 00a2 8F91      		pop r24
 478 00a4 7F91      		pop r23
 479 00a6 6F91      		pop r22
 480 00a8 5F91      		pop r21
 481 00aa 4F91      		pop r20
 482 00ac 3F91      		pop r19
 483 00ae 2F91      		pop r18
 484 00b0 0F90      		pop r0
 485 00b2 0BBE      		out __RAMPZ__,r0
 486 00b4 0F90      		pop r0
 487 00b6 0FBE      		out __SREG__,r0
 488 00b8 0F90      		pop r0
 489 00ba 1F90      		pop r1
 490 00bc 1895      		reti

Как видим, вызов twiSendStop() - единственный и его можно развернуть инлайн. Сокращаем код этой функции за ненадобностью хука рестарта, и вызов/возврат и ускоряем;

Далее. Обнаруживаем, что нам 3 временных регистра _cr, _md, r25(TWDR) одновременно не нужны, и можно обойтись двумя и даже вовсе одним. Соответственно пролог/эпилог достаточен при сохранении только 3 регистров: 29,30,31. Из 36 байт каждого можно оставить только по 6. Итого 60байт прямой экономии.

Кроме этого, обнаруживаем что *ptr++ не пользует команду ld r25,Z+, которая устраняет ещё пару байт кода.

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

Надеюсь, самостоятельно сделать это упражнение самостоятельно - вы таки способны. :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Я не стал этого делать по одной причине: компиляция примера из апа выдает такое:

"Размер скетча в двоичном коде: 1 510 байт" что меня более чем устраивает. :)

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

У вас это некий метод одного класса. Ибо этот "невероятно" универсальный и быстрый код .. внезапно заплодит всю память при его повторных вызовах .. ибо он велик.

Не выдумывайте чепухи. Это не метод класса. Это функция в скетче, создается через макрос обявленый в либе. Я ж писал - в класс передаются указател на эту функцию.

Arhat109-2 пишет:
Собственно, ничто вам не мешает вставить его инлайн прямо в метод, для которого он и писан.
Снова ваши фантазии, причем вредные. 

Arhat109-2 пишет:
Макрос - инструмент для сокрытия и упрощения писания кода .. ваще-то. Это раз.

Угу. И я его использую для этого. Что не так?

Arhat109-2 пишет:
И второе: блокирующие функции - это все тот же самый delay() только в "скрытом виде". 
Бред. Например Serial.begin блокирующая, выполняет определенное действие тратя на это некоторое время, а delay() только тратит время. Это далеко не тоже. 

Arhat109-2 пишет:
Управление железяками как-бы "не предполагает" ожидания в коде на время реакции железа.  
Целиком зависит от времени реакции. Секунды и микросекунды - разные подходы, не ровняйте под одну гребенку.

Arhat109-2 пишет:
.. никакая программа НЕ делает НИЧЕГО, кроме того что было реализовано программистом. 
Да неужели! Как свежо! Распишите подробней;)

Arhat109-2 пишет:
P.S. Мне казалось, что дизассемблировать публично выложенный код для вас не является проблемой .. видимо ошибся, давайте помогу: 

Для меня не проблема, я от Вас ожидал еще и оценки времени исполнения. Не дождался. Но и пол шага в нужную сторону - уже хороше. По крайней мере не скажите "код не мой". Время я сам посчитаю. Для типичного прохода - отправки очередного быйта данных, стопы, старты, адреса - алах  с ними, погоды не делают. Поехали по коду, информация о числе циклов на команду из  известного 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 циклов.

Строки 48-62. Это собственно сам вывод, само мясо. 2+1+1+2+1+1+2+2+1+1+1+2+2+2+2=23
Строки 23-30 и 40-46. Проверки нужные чтоб добратся до "мяса". Соответственно 2+1+1+2+1+1+1+2=11 и 1+1+1+1+2=6 Вместе 17.
Строки 34-36, 37-38 или 64-65 неважно одинаковы, 70-71. Завершение, после "мяса". . Соответственно
2+2+2=6  2+2=4 2+2=4 Вместе 14.
 
Итого в обработчику нужно 23+17+14=54 цикла.
Всего обработка прерывания займет 77+54=131цикл.
 
Из заявленых Вами 145 циклов на отправку байта. Обработка прерывания потянула почти все процессорное время. Как я Вас и предупреждал в первом своем посте темы.  Знаете откуда я это знал? Все просто. Когда-то борясь за скорость вывода на экран я тоже попробовал через прерывание. Потом посчитал, подумал и переписал блокирующее. Ведь у нас есть и другие прерывания - последовательный порт, таймер и т.д. Они быстро доедят оставшиеся 10% и все упадет, с чего Вы же и создали тему! Порт и  i2c не работают вместе. Вот и разгадка. Строить неблокирующую работу при таком раскладе безсмыслено, овчинка выделки не стоит. А если вспомнить о приятных бонусах как несколько шин не привязаных к аппаратным пинам. Типичный вариант - экран на програмном быстром i2c на 800КГц, а датчики - на програмном на 100КГц по прерываниям. И никто никому не мешает 

Arhat109-2 пишет:
Как видим, вызов twiSendStop() - единственный и его можно развернуть инлайн. Сокращаем код этой функции за ненадобностью хука рестарта, и вызов/возврат и ускоряем;

Далее. Обнаруживаем, что нам 3 временных регистра _cr, _md, r25(TWDR) одновременно не нужны, и можно обойтись двумя и даже вовсе одним. Соответственно пролог/эпилог достаточен при сохранении только 3 регистров: 29,30,31. Из 36 байт каждого можно оставить только по 6. Итого 60байт прямой экономии.

Кроме этого, обнаруживаем что *ptr++ не пользует команду ld r25,Z+, которая устраняет ещё пару байт кода.

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

Надеюсь, самостоятельно сделать это упражнение самостоятельно - вы таки способны. :)

Ваши влажные мечтания сами и реализовуйте, в эти ню-васюки только самый наивный поверит. Если так лучше - чего сразу так не писали? ;))) После отладок, тестов и доработок код только растет. Хотите посоревноватся с компилятором - бог помощ, с удовольствием почитаю "GCC & Arhat109-2"

ПС. Вам самому не стыдно публиковать код, у которого 30% текста - "магические цифры"? За такой код лупят нещадно везде, где видят. И правильно делают. Я обычно толерантен в этом вопросе, но такого количества  я сдается еще не видел. Вам выслать на почту файл обявлений регистров и битов микроконтроллера? Или Вы реально думаете что дефайники увеличивают размер кода?

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Logik пишет:
Не выдумывайте чепухи. Это не метод класса. Это функция в скетче, создается через макрос обявленый в либе. Я ж писал - в класс передаются указател на эту функцию ... Снова ваши фантазии, причем вредные.
Я уж подумал, что не так вас понял, однако "чепуху" как раз Вы и написали. Нет никакой разницы промеж "метода класса" как у меня написано и вашим указателем на .. единственную функцию. Перечитайте и не пишите чепухи, и так агрессивно. (Я уже успокоился, чего и вам желаю) :)

Logik пишет:
Arhat109-2 пишет:
И второе: блокирующие функции - это все тот же самый delay() только в "скрытом виде". 
Бред. ... Например Serial.begin блокирующая ... на програмном быстром i2c на 800КГц, а датчики - на програмном на 100КГц по прерываниям.
Сами вы бред пишите. Особенно в приложении к Serial.begin() и ведь понимаете насколько оно бред! Далее ведь сами же аргументирете про I2C "на 100кгц" и "на 800" .. Так вот это было как раз, про применение вашего макроса для I2C ... на 1кгц. А ведь такого рода решений в популярных библиотеках - достаточно .. и тоже вы в курсе. Фи, стыдно.

Logik пишет:
Вам самому не стыдно публиковать код, у которого 30% текста - "магические цифры"? За такой код лупят нещадно везде, где видят. И правильно делают. Я обычно толерантен в этом вопросе, но такого количества  я сдается еще не видел.
Это понятно. Вы - не внимательны, видимо "как обычно". Там вам прописано: "результат работы ПРЕПРОЦЕССОРА" .. как вижу - "не читатель". :)

Logik пишет:
.. я от Вас ожидал еще и оценки времени исполнения. Не дождался.
.. У меня Хрустальный Шар на ремонте. В следующий раз выражайте свои пожелания явно. :)

Logik пишет:
Итого в обработчику нужно 23+17+14=54 цикла. Всего обработка прерывания займет 77+54=131цикл. .. Из заявленых Вами 145 циклов на отправку байта. ... Обработка прерывания потянула почти все процессорное время. Как я Вас и предупреждал в первом своем посте темы ... я тоже попробовал через прерывание. ... быстро доедят оставшиеся 10% и все упадет ... Вот и разгадка. Строить неблокирующую работу при таком раскладе безсмыслено, овчинка выделки не стоит.
Верно, столько ему нужно. Это обыкновенная "С" реализация, и практически всё что можно на уровне языка - тут уже выжато. А теперь, следим за пальцами:

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герц .. то он ЗАВЕДОМО предпочтительнее ДВУХ реализаций как у вас.

Logik пишет:
Ваши влажные мечтания сами и реализовуйте, в эти ню-васюки только самый наивный поверит.
Поверит не наивный, а ГРАМОТНЫЙ. А своё отсутсвие грамотности, вы тут уже наглядно продемонстрировали .. в первом посту.

Вот это я и называю агрессивным говнокодерством в целом. Мало того, что сами попробовали "абы как" и не получилось, так вы ещё и остальных учите плохому, так и этого мало! Вместо того, чтобы осваивать и учиться на этом примере .. агрессивно пишите всякую фигню. Фи в квадрате.

P.S. Впрочем я сам и реализовал. Пользуйтесь. :)

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

Нет никакой разницы промеж "метода класса" как у меня написано и вашим указателем на .. единственную функцию.  

Вы неук, господин хороший! Шо действительно! Что метод класса, что указатель на функцию? ))))) 

Arhat109-2 пишет:
 Так вот это было как раз, про применение вашего макроса для I2C ... на 1кгц. А ведь такого рода решений в популярных библиотеках - достаточно .. и тоже вы в курсе. Фи, стыдно. 

Читать умеете?

http://arduino.ru/node/#comment-184606

Logik пишет:

Arhat109-2 пишет:

2. Небольшое насилие над twi.h позволяет обнаружить что дисплей стабильно работает вплоть до частот интерфейса в 800кГц ..

 

Вот жеж правильный вброс. А что из этого следует?

- здесь http://arduino.ru/forum/programmirovanie/svoi-draiver-i2c-twi-trebuetsya-pomoshch#comment-184606

"про 100Кгц речи нет. Про 800 разговор."

здесь  http://arduino.ru/forum/programmirovanie/svoi-draiver-i2c-twi-trebuetsya-pomoshch#comment-184616

Какой нафиг 1кГц?! Крутитесь как вюн на сковородке.

Arhat109-2 пишет:
Logik пишет:
.. я от Вас ожидал еще и оценки времени исполнения. Не дождался.
.. У меня Хрустальный Шар на ремонте. В следующий раз выражайте свои пожелания явно. :)
 
Не надо ля-ля. Все писал. Все ходы записаны.
 
"Вы бы лучше свой код дизасамблировали да посмотрели чего и скоко там сохраняется, и скока на это времени уходит оценили. " здесь http://arduino.ru/forum/programmirovanie/svoi-draiver-i2c-twi-trebuetsya-pomoshch#comment-184622
"Дизасемблированого кода нет, расчета или измерения тактов на обслуживание прерывания нет," здесь http://arduino.ru/forum/programmirovanie/svoi-draiver-i2c-twi-trebuetsya-pomoshch#comment-185299
Еще ясней?

Arhat109-2 пишет:

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 и еще дето.  С собой дискутируйте. Но по сути да, много. 

Arhat109-2 пишет:
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герц ..

Хватит сказки расказывать, напишите - посмотрим. Не все так просто. Я рассматриваю только реализации, мечты не компилируются.  Так и не ответили, почему сразу не пишете кратко, быстро и ясно, как рассказываете.

 

Arhat109-2 пишет:
то он ЗАВЕДОМО предпочтительнее ДВУХ реализаций как у вас.

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

 

Arhat109-2 пишет:
Logik пишет:
Ваши влажные мечтания сами и реализовуйте, в эти ню-васюки только самый наивный поверит.
Поверит не наивный, а ГРАМОТНЫЙ. 
Не поверю, каким бы размером букв не писали. Верю фактам, коду. А сказкам - нет.

 

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Я не пойму что вы доказать-то хотите? Что я называл разные оценки в разное время обсуждения и оно "не работает" а ваш ногодрыг - "рассово-верен"?

Так я и НЕ считал потактово, мне это нафиг не надо .. вы просили посмотреть и прикинуть .. ну, посмотрел и прикинул .. вы сами посчитали точнее - и опять я не считал, а всего лишь поверил вам .. и чё? Драйвер - работает, точка. Возьмите код - он публичный и наслаждайтесь. И, если надо "ещё шустрее", то куда и на сколько его можно улучшить - в теме уже показано. Кому понадобится, тот сделает. Не переживайте. :)

.. или Вы хотите утвердждать что 100% блокирующий код ногодрыга лучше, пусть даже 80% НЕ блокирующего аппаратного драйвера? .. Вы в своем уме?

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

dhog1
Offline
Зарегистрирован: 01.03.2016

Уважаемые 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 прерывания с конечным автоматом на обслуживание транзакции (передал-принял-закончил). Комментарии тоже мои на псево-английской мове.

* twi interrupt driven transaction */
// isr (twi_vect)
#pragma vector=25
__interrupt void twi_irq (void) {

    switch (twsr & 0xf8) {
        case 0x08:              // start has been transmitted
                   if (_twi_out_len) {               // first is transmitting;    sla+w expected
                      twdr = (_twi_sla << 1);
                      twcr = (1<<twint)(1<<twen)(1<<twie);
                   } else if (_twi_in_len) {         // if we need receive data;  sla+r expected
                      twdr = (_twi_sla << 1) | 0x01;
                      twcr = (1<<twint)|(1<<twen)|(1<<twie);
                   };
                   break;
        case 0x10:              // repeated start has been transmitted; switch to mr mode
                    twdr = (_twi_sla << 1) | 0x01;    // sla+r
                    twcr = (1<<twint)|(1<<twen)|(1<<twie);
                   break;
        case 0x18:              // sla+w has been transmitted; ack received
                    if (_twi_out_len) {             // data byte expected
                       twdr = (char)*(_twi_out_data - _twi_out_len);
                       _twi_out_len--;
                       twcr = (1<<twint)|(1<<twen)|(1<<twie);
                    } else if (_twi_in_len) {       // all txed, stop or repeated start expected
                       twcr = (1<<twint)|(1<<twen)|(1<<twsta)|(1<<twie); // send repeated start condition
                    } else {                        // no more data
                       _twi_busy = false;
                       twcr = (1<<twint)|(1<<twen)|(1<<twsto);
                    };
                   break;
        case 0x28:              // data byte transmitted; ack received
                    if (_twi_out_len) {             // more data byte expected
                       twdr = (char)*(_twi_out_data - _twi_out_len);
                       _twi_out_len--;
                       twcr = (1<<twint)|(1<<twen)|(1<<twie);
                    } else if (_twi_in_len) {       // there are data to receive, so switch to mr mode
                       twcr = (1<<twint)|(1<<twen)|(1<<twsta)|(1<<twie); // send repeated start
                    } else {                        // no more deal
                       _twi_busy = false;
                       twcr = (1<<twint)|(1<<twen)|(1<<twsto);
                    };
                   break;
        case 0x40:              // sla+r has been transmitted; ack received
                    if (_twi_in_len > 1) {          // data byte expected
                       twcr = (1<<twint)|(1<<twen)|(1<<twea)|(1<<twie);     // with ask
                    } else
                       twcr = (1<<twint)|(1<<twen)|(1<<twie);               // with nask
                   break;
        case 0x50:              // data byte has been received; ack nas been returned
                    *(_twi_in_data-_twi_in_len) = twdr;
                    _twi_in_len--;
                    if (_twi_in_len > 1) {          // more data bytes expected
                       twcr = (1<<twint)|(1<<twen)|(1<<twea)|(1<<twie);     // with ask
                    } else
                       twcr = (1<<twint)|(1<<twen)|(1<<twie);               // with nask
                   break;
        case 0x58:              // data byte has been received; nack has been returned
                    *(_twi_in_data-_twi_in_len) = twdr;
                    _twi_busy = false;
                    twcr = (1<<twint)|(1<<twen)|(1<<twsto);
                   break;
        default:
                    _twi_busy = false;
                    twcr = (1<<twint)|(1<<twen)|(1<<twsto);
                   break;
    }
}

Думаю, что при умелом использовании 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, показывает что-то, например, то это беззнаковая арифметика что-то сделала (с данными в регистрах) и реальная скорость...

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Всё в общем-то верно, и мне тоже не понятно что хочет доказать 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шт. Память - позволяет.

Вот вам и "конкурентное" применение И мастера И слейва в одном флаконе. :) (собственно для примерно такого соединения дунек оно и делается для наших "робототехнических" задачек)

Logik
Offline
Зарегистрирован: 05.08.2014

dhog1 пишет:

Ну вот зачем, парни, вы оба трогаете бедное создание - 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 и подобных. Там скорость востребована, ибо чисто графический и пикселей много (ну по жизни экрана много не бывает конечно, но сравнительно много). 

 

dhog1 пишет:

Ув. Arhat пишет библиотеку, которая сгенерирует любого "тяни-толкая". Но лучше (на самом деле единственно идеологически правильно), imho, ограничить результат master'ом (тупым, например, только на передачу, или обычным, как и положено, с приемом по запросу) и ... Вот тут возникает некая пауза, чуть ниже о ней. Смысл фразы в том, что устройства, для которых сгенерится драйвер (хороший, однополый), все-таки разного пола (зачеркнуто) назначения - это _либо_ master (никого не слушает, способен передавать и принимать, но по своему же запросу), _либо_ slave (всегда слушает и способен принять, а если нужно и передать по запросу).

Обычно в этом месте возникает вопрос - а сам-то чьих будешь (чтобы менторски учить всех)? Отвечаю - прочитавших спецификацию i2c шины (ну тут уже в гугл). Уверовавших в архитектуру этой шины как промышленного стандарта. Если вы встречаете у atmel упоминание _аппаратной_ поддержки twi, это означает поддержку на двухпроводной шине twi протокола i2c (т.е. определенной последовательности - старт, передача адреса, повторный старт и т.п.). В иных случаях (usi семейства attiny) это _возможность_ такой поддержки (с собственной ручной кодировкой части i2c протокола), но может быть и собственная реализация синхронного байт-ориентированного протокола (не i2c).

Ну вот как пример к последнему - tm1637 - вроде и похоже на i2c, но не i2c. Но похоже, тоже twi, но не совсем. Аппаратная поддержка atmel не поможет, но "ногодрыгом" сделать получится.

 

Да. Целиком согласен. Именно "мастер" устройствоспецифичный. Одному надо паузы межбайтные, у другого адресация не как у всех и т.д.  Ну и конечно для экрана, его специфика - скорость и возможность пихать в шину напрямую из флеша, шоб картинки и шрифты не гонять в буфер.  А вот слейв, не быстрый конечно, можно и на аппаратном с прерываниями. И конечно в принципе "сферический конь".

dhog1 пишет:

1. Непонятно, как многочисленные макросы могут привести к раздуванию кода при наличии оптимизирующего компилятора.

 Ну тут и от радиуса кривизны рук зависит. Можна так заморочить, что и оптимизатор не справится. Но на самом деле этот вопрос - просто попытка ТС уйти в сторону. Макрос один в либе, включается в код столько раз сколько шин i2c предполагается, т.е. одна шина - значить один раз. В макросе обявление функции а указатель на неё передается всем заинтересованым. Никакого раздувания очевидно нет, по крайней мере для одной шины. Но Arhat толи не понял, толи сделал вид что не понял, и пошло раздувание.

dhog1 пишет:

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

 Мне тоже, но поединок с GCC должен быть интересен. Чисто как зрелище )))

dhog1 пишет:
Вот код пресловутого twi прерывания с конечным автоматом на обслуживание транзакции (передал-принял-закончил)..... 
Так код как код, все подобное пишут пока не нужно высокой скорости. И для Ssd1306 я год назад подобное начал строчить, но потом выбросил, т.к. он по скорости не тянет, а если в него добавить извлечение картинок из флеша да знакогенератор так он и 400КГц будет тормозить. В результате вывод картинки на весь экран уходит на десяток-другой милисекунд сплошных прерываний. Ясно, что ляжет за это время вся система. Нафиг надо! В результате мастер I2c стал очень специфичным для экрана, зато быстрым простым не жрущим ОЗУ и цепляемым к любым 2-м свободным пинам. Что  собственно позволило цеплять его даже для отладки куда попало и я им целиком удовлетворен.

Собственно этот опыт и пытался передать ТС. Что из этого вышло и как воспринято видно выше.

 

dhog1
Offline
Зарегистрирован: 01.03.2016

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 - если отбросить эмоции (они должны быть!), было интересно с собеседниками такого уровня.

Надеюсь на встречу в других темах (более жесткую, ну это нормально) и удачи всем.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

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

По деталям: Я работаю с ATmega2560-AU16 - на плате Адруино Мега2560 китайского клона на CH340. Это не подделка, а какая-то собственная разработка косоглазых, что позволило нам принять участие в соревнованиях Робофест. Безрезультатно по неопытности, но это иной вопрос .. Есть ещё УНО и какая-то мелочь, типа Мини на базе ATmega328p - вот. Все разработки в arhat.h в первую очередь ведутся для семейства 2560 и его урезанных вариантов, а потом уже для 328P (как оказалось буковка тут похоже важна) и его урезанных вариантов.

По частоте I2C: Обе дуньки вполне нормально справляются с установкой скорости интерфейса вплоть до 880 килогерц. Проверочный скетч ещё не готов (мало времени и занят чуть другим), поэтому пока только оценочно по факту прописывания таких делителей и наблюдению за работой. Вот 1 Мгц похоже они не тянут, ибо на шину ничего не вылазит, или я не заметил.