Получить адрес транзакции I2C при регистрации нескольких адресов

У меня есть приложение, опрашивающее 4 АЦП по I2C. Приложение также должно записывать в некоторые регистры АЦП. Чтобы протестировать это, я хотел бы использовать один Arduino UNO, позволяющий ему получать сообщения I2C по 4 различным адресам, маскируя регистр TWAMR. Таким образом, я могу «эмулировать» 4 АЦП. на одном Arduino.

Когда приложение запрашивает чтение в Arduino по некоторому адресу, я могу получить значение требуемого адреса, прочитав регистр TWDR:

void requestEvent(){
  byte addr = TWDR >> 1;
  if (0x4A == addr){ // делаем что-то }
  else if (0x4B == addr){ // делаем что-то }
  // ...
}

void setup(){
  // ...
  Wire.onRequest(requesEvent);
}

Однако, когда приложение запрашивает запись чего-либо в определенный АЦП (с его собственным адресом), я не могу получить значение адреса в обратном вызове onReceive(int):

void receiveEvent(int howMany){
  byte addr = TWDR >> 1; // возвращает первый байт ПОСЛЕ адреса в сообщении I2C
}

Я делаю что-то не так? Есть ли способ достичь моей цели?

Спасибо.

Лука

Изменить:

Читая даташит ATmega (п. 22.9.4, стр. 241), проблема чтения регистра TWDR состоит в том, что он содержит последний полученный байт, что в режиме подчиненного приемника - это не адрес, а последние данные, отправленные мастер.

И вообще, есть ли способ решения этой проблемы?

, 👍2

Обсуждение

Мне это кажется излишне сложным. Вы говорите, что проводите опрос, так почему бы просто не отправить запрос на 0x4A и дождаться ответа, затем на 0x4B и дождаться ответа и так далее?, @Nick Gammon

На самом деле я написал что-то не так. Приложение предназначено не только для опросов. но и запись в некоторые регистры АЦП для его настройки. В частности, процедура для каждого АЦП следующая: 1) запись в регистр конфигурации АЦП для запуска преобразования; 2) опрашивать его непрерывно, пока преобразование не завершится; 3) запросить меру после завершения преобразования. Не знаю, почему приложение так написано, я взял как есть. Вероятно, с этой стороны есть какие-то улучшения, но я работаю над другой стороной — симулятором для тестирования приложения., @lumaca96

Мне все еще кажется проще использовать циклический подход. Сначала сообщите каждому АЦП начать преобразование (т. е. начать A, начать B, начать C, начать D), затем (опрос A, опрос B, опрос C, опрос D), пока некоторые или все не завершат преобразование, затем (Читать А, Читать Б, Читать В, Читать Г). Мне кажется слишком сложным возиться с аппаратными регистрами, когда в этом нет необходимости., @Nick Gammon

Я не уверен, что понял ваш ответ. Если вы советуете изменить дизайн устройства, с которым общается мой Arduino, к сожалению, я не смогу этим управлять., @lumaca96

Я не сторонник каких-либо аппаратных изменений. Почему бы просто не отправлять команды одному АЦП за другим? Если вы запросите ответ у конкретного АЦП и дождетесь его, то наверняка вы знаете, какой АЦП ответил?, @Nick Gammon

Как сказано в другой теме, я моделирую поведение АЦП с помощью Arduino. Arduino реализует симуляцию АЦП. Другое оборудование (которое я не могу контролировать) связывается с Arduino, полагая, что это настоящий АЦП. На самом деле все немного сложнее, поскольку один Arduino имитирует несколько АЦП. По сути, реальное оборудование считает, что оно взаимодействует с ADC_i (с i = 1,...,6), но на самом деле оно взаимодействует только с одним Arduino, который реализует логику нескольких АЦП. Я думаю, это обычный случай в моделировании HIL., @lumaca96

В этом смысле я не могу изменить то, что делает настоящее аппаратное обеспечение и как оно отправляет команды АЦП. Настоящее аппаратное обеспечение (то есть микроконтроллер со своими собственными алгоритмами управления) разработано другими, и у меня есть только его требования, чтобы понять, как оно взаимодействует с несколькими АЦП. Я создам моделирование АЦП в Arduino, способное обрабатывать все данные, которые микроконтроллер отправляет по двухпроводному интерфейсу. Надеюсь, теперь это стало яснее., @lumaca96

Ага, понятно. Вы притворяетесь АЦП, а не разговариваете с другими АЦП. Теперь это стало яснее. В этом случае то, что сказал Крисл, имеет смысл., @Nick Gammon


1 ответ


Лучший ответ:

1

Чтение регистра TWDR в обратном вызове onReceive() не дает вам адрес из-за времени, в которое он вызывается. Обратный вызов onReceive() вызывается, когда все байты уже получены и помещены во внутренний буфер библиотеки Wire. Таким образом, в этом обратном вызове TWDR по-прежнему будет хранить последний полученный байт данных.

Обратный вызов onRequest(), с другой стороны, вызывается сразу после получения адреса. Таким образом, TWDR по-прежнему сохраняет полученный адрес.

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

Вы можете найти файлы библиотеки Wire внутри используемого ядра (для меня Uno использует путь ~/.arduino15/packages/arduino/hardware/avr/1.8.5/libraries/Wire). Посмотрите строку 581 файла src/utility/twi.c. Это находится внутри TWI ISR, предложения case TW_SR_SLA_ACK, которое будет выполняться при получении/сопоставлении адреса. Здесь вы можете вставить свою функцию обратного вызова.

Если вы хотите сделать это таким же удобным способом, как библиотека Wire делает с другими обратными вызовами, вам нужно изменить еще несколько вещей:

  1. Вставьте указатель функции для этого в начало файла twi.c, рядом с другими указателями функций:

     static void (*twi_onSlaveAddressMatch)(uint8_t);
    
  2. Добавьте выполнение этой функции в описанный выше случай в ISR и укажите содержимое TWDR (сдвинутое для исключения бита направления) в качестве параметра:

     twi_onSlaveAddressMatch(TWDR >> 1);
    
  3. Добавьте функцию для установки указателя функции обратного вызова в twi.c:

     void twi_attachSlaveAddressMatchEvent( void (*function)(uint8_t) ){
         twi_onSlaveAddressMatch = function;
     }
    
  4. Добавьте функцию установки обратного вызова в качестве прототипа функции в twi.h:

     void twi_attachSlaveAddressMatchEvent( void (*function)(uint8_t) );
    
  5. В Wire.h добавьте указатель функции для нового обратного вызова и соответствующую служебную функцию в разделе private::

     static void (*user_onSlaveAddressMatch)(uint8_t);
     static void onSlaveAddressMatchService(uint8_t);
    
  6. В Wire.h добавьте функцию для установки функции обратного вызова пользователя в раздел public::

     void onSlaveAddressMatch( void (*)(uint8_t) );
    
  7. В Wire.cpp добавьте реализацию сервисной функции:

     void TwoWire::onSlaveAddressMatchService(uint8_t address){
         if(!user_onSlaveAddressMatch){
             return;
         }
         user_onSlaveAddressMatch(address);
     }
    
  8. В Wire.cpp добавьте реализацию функции установки обратного вызова:

     void TwoWire::onSlaveAddressMatch( void (*function)(uint8_t) ) {
         user_onSlaveAddressMatch = function;
     }
    

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

uint8_t received_address = 0;

void getAddress(uint8_t address){
    received_address = address;
}

void setup(){
    ...
    Wire.onSlaveAddressMatch(getAddress);
}

В обратном вызове onReceive() вы можете проверить наличие этой глобальной переменной.

Примечание: я не тестировал и не компилировал это. Я просто посмотрел на логику библиотеки Wire и скопировал способ, которым она уже определяет существующие функции обратного вызова.

,

Привет @chrisl, спасибо за ответ. Я спросил здесь как последнюю надежду не пересобирать основные библиотеки. Я этого ожидал. Другим решением моей проблемы может быть добавление следующих строк в twi.c (в случае TW_SR_SLA_ACK): if (twi_sr_store_address) { twi_rxBuffer[twi_rxBufferIndex++] = TWDR >> 1; } где twi_sr_store_address — это просто логическое значение, которое можно установить через Wire. Таким образом, метод Wire.read() теперь будет возвращать также байт адреса. Как вы думаете, это решение может работать так же хорошо, как ваше? Если нет, то почему? Я просто прошу научиться. Спасибо еще раз за помощь, Лука, @lumaca96

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