Работа с основной библиотекой Wire

Я изучаю драйвер кода библиотеки Wire utility/twi.h, чтобы узнать, как работают буферизация twi и управление ISR.

На самом деле я скопировал заголовочные/исходные файлы из пути Arduino F:\Program Files\Arduino\hardware\arduino\avr\libraries\Wire\src\utility и поместил их в пользовательские библиотеки Arduino и изменил .c на .cpp, и все заработало.

На самом деле я пробовал разные библиотеки i2c для avr, и эта библиотека кода, по моему мнению, одна из лучших.

Мой тестовый код предназначен для инициализации HMC5883L и считывания этих настроек конфигурации из первых SFR HMC5883L.

Я использую Serial.prints внутри/вне ISR для отладки своего кода. Он работает почти нормально. Я не уверен, что это может вызвать какие-либо проблемы.

Но поскольку у меня нет полного понимания того, как работает twi, я сейчас пытаюсь понять, как отображается контент, поступающий в ISR.

Я думаю, что инициализация работает нормально, но процедура чтения может быть не такой, как должна быть.

Вот мой код: код приложения в Arduino IDE и код в библиотеке.

Код Arduino IDE:

#include "twi.h"    
/////////////////////////////////////////////////// //////////////////////////
// HMC5883L
#define HMC5883L                    0x1E
#define CONFIG_REG_A                0x00  //Чтение/Запись
#define CONFIG_REG_B                0x01  //Чтение/Запись
#define MODE_REGISTER               0x02  //Чтение/Запись
#define DATA_OUTPUT_X_MSB_REGISTER  0x03  //Чтение
#define DATA_OUTPUT_X_LSB_REGISTER  0x04  //Чтение
#define DATA_OUTPUT_Z_MSB_REGISTER  0x05  //Чтение
#define DATA_OUTPUT_Z_LSB_REGISTER  0x06  //Чтение
#define DATA_OUTPUT_Y_MSB_REGISTER  0x07  //Чтение
#define DATA_OUTPUT_Y_LSB_REGISTER  0x08  //Чтение
#define STATUS_REGISTER             0x09  //Чтение
#define IDENTIFICATION_REGISTER_A   0x10  //Чтение
#define IDENTIFICATION_REGISTER_B   0x11  //Чтение
#define IDENTIFICATION_REGISTER_C   0x12  //Чтение
#define DECLINATION_ANGLE 3.46

void hmc5883l_init(void);
void init_read(void);
void hmc5883l_print_serial(void);

void setup() {
  Serial.begin(9600);
  twi_init();
  hmc5883l_init();
  init_read();
}

void loop() {
  hmc5883l_print_serial();
}

void hmc5883l_init(void){
  uint8_t err,HMC5883L_ini[] = {CONFIG_REG_A,0x78,0x20,0x00};//
    err = twi_writeTo(HMC5883L,HMC5883L_ini,4,1,1);
    if(err != 0){Serial.print("error of init is");Serial.println(err);}
    Serial.println();
}

void init_read(void){
    uint8_t err,len,dat[3];
    err = twi_writeTo(HMC5883L,0x00,1,0,1);
    if(err != 0){Serial.print("error of init is");Serial.println(err);}
    Serial.println();   

    len = twi_readFrom(HMC5883L,dat,3,1);
    Serial.print("length of read op:              ");
    Serial.println(len);
    Serial.println();        

    Serial.print("config settings:                ");
    Serial.print(dat[0],HEX);Serial.print("\t");
    Serial.print(dat[1],HEX);Serial.print("\t");
    Serial.println(dat[2],HEX);    
}

void hmc5883l_print_serial(void){
  int16_t x,z,y;
  uint8_t data[6],len,err, dst_reg[] = {0x03};
  float heading,heading_in_degrees,declination_angle_YANBU;

  err = twi_writeTo(HMC5883L,dst_reg,1,0,1);
  if(err != 0){Serial.print("error of init is");Serial.println(err);}
  Serial.println();

  len = twi_readFrom(HMC5883L,data,6,1);
  Serial.print("len is: ");Serial.println(len);
  x = data[0] << 8 | data[1];
  z = data[2] << 8 | data[3];
  y = data[4] << 8 | data[5];

  heading = atan2(y,x);
  declination_angle_YANBU = ((3.0 + (52.0 / 60.0)) / (180 / M_PI));  // в городе Янбу это
  heading += declination_angle_YANBU;

  if (heading < 0){heading += 2 * PI;}
  if (heading > 2 * PI){heading -= 2 * PI;} 

  heading_in_degrees = heading * 180 / M_PI;
  Serial.println(heading_in_degrees);

  //Serial.print("ось x\t");Serial.print("ось z\t");Serial.println("ось y");
  Serial.print(x);Serial.print("\t");
  Serial.print(z);Serial.print("\t");
  Serial.println(y);
}

А это основная библиотека:

В этой части я поместил Serial.prints в корпуса ISR, чтобы отобразить, что происходит с начала буферов tx/rx. Кроме того, я вырезал ведомую половину кода, потому что в этой задаче в этом нет необходимости, поскольку я имею дело только с HMC5883L как с ведомым устройством, а я как с главным.

#include <math.h>
#include <stdlib.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/twi.h>
#include "Arduino.h" // для digitalWrite

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#include "pins_arduino.h"
#include "twi.h"

static volatile uint8_t twi_state;
static volatile uint8_t twi_slarw;
static volatile uint8_t twi_sendStop;           // должна ли сделка завершиться стопом
static volatile uint8_t twi_inRepStart;         // в середине повторного запуска

static void (*twi_onSlaveTransmit)(void);
static void (*twi_onSlaveReceive)(uint8_t*, int);

static uint8_t twi_masterBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_masterBufferIndex;
static volatile uint8_t twi_masterBufferLength;

static uint8_t twi_txBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_txBufferIndex;
static volatile uint8_t twi_txBufferLength;

static uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_rxBufferIndex;

static volatile uint8_t twi_error;

void twi_init(void)
{
  // инициализируем состояние
  twi_state = TWI_READY;
  twi_sendStop = true;      // значение по умолчанию
  twi_inRepStart = false;

  // активируем внутренние подтягивания для twi.
  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);

  // инициализируем двойной прескалер и скорость передачи данных
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

  // включаем модуль twi, подтверждения и прерывание twi
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}

void twi_disable(void)
{
  // отключаем модуль twi, подтверждения и прерывание twi
  TWCR &= ~(_BV(TWEN) | _BV(TWIE) | _BV(TWEA));

  // деактивируем внутренние подтягивания для twi.
  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);
}

uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sendStop)
{
  uint8_t i;

  // гарантируем, что данные поместятся в буфер
  if(TWI_BUFFER_LENGTH < length){
    return 0;
  }

  // ждем, пока twi будет готов, становимся главным получателем
  while(TWI_READY != twi_state){
    continue;
  }
  twi_state = TWI_MRX;
  twi_sendStop = sendStop;
  // сброс состояния ошибки (0xFF.. ошибок не произошло)
  twi_error = 0xFF;

  // инициализируем переменные итерации буфера
  twi_masterBufferIndex = 0;
  twi_masterBufferLength = length-1;  // Это не интуитивно понятно, читайте дальше...

  twi_slarw = TW_READ;
  twi_slarw |= address << 1;

  if (true == twi_inRepStart) {
    twi_inRepStart = false;         // помните, мы имеем дело с ASYNC ISR
    do {
      TWDR = twi_slarw;
    } while(TWCR & _BV(TWWC));
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE);  // включаем INT, но не START
  }
  else
    // отправляем условие начала
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);

  // ждем завершения операции чтения
  while(TWI_MRX == twi_state){
    continue;
  }

  if (twi_masterBufferIndex < length)
    length = twi_masterBufferIndex;

  // копируем буфер twi в данные
  for(i = 0; i < length; ++i){
    data[i] = twi_masterBuffer[i];
  }

  return length;
}

uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait, uint8_t sendStop)
{
  uint8_t i;

  // гарантируем, что данные поместятся в буфер
  if(TWI_BUFFER_LENGTH < length){
    return 1;
  }

  // ждем, пока twi будет готов, становимся главным передатчиком
  while(TWI_READY != twi_state){
    continue;
  }
  twi_state = TWI_MTX;
  twi_sendStop = sendStop;
  // сброс состояния ошибки (0xFF.. ошибок не произошло)
  twi_error = 0xFF;

  // инициализируем переменные итерации буфера
  twi_masterBufferIndex = 0;
  twi_masterBufferLength = length;

  // копируем данные в twi-буфер
  for(i = 0; i < length; ++i){
    twi_masterBuffer[i] = data[i];
  }

  // построить sla+w, адрес ведомого устройства + бит w
  twi_slarw = TW_WRITE;
  twi_slarw |= address << 1;

  // если у нас повторный старт, то мы уже отправили START
  // в ISR. Не делай этого снова.
  //
  if (true == twi_inRepStart) {
    // если мы находимся в состоянии повторного старта, то мы уже отправили старт,
    // (@@@ мы надеемся), а конечный автомат TWI просто ждет адресного байта.
    // Нам нужно выйти из состояния повторного запуска, прежде чем мы разрешим прерывания,
    // поскольку ISR является ASYNC, и мы можем запутаться, если нажмем на ISR перед очисткой
    // вверх. Кроме того, не разрешайте прерывание START. Может быть один ожидающий рассмотрения из
    // повторный запуск, который мы отправили сами, и это действительно запутало бы ситуацию.
    twi_inRepStart = false;         // помните, мы имеем дело с ASYNC ISR
    do {
      TWDR = twi_slarw;             
    } while(TWCR & _BV(TWWC));
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE);  // включаем INT, но не START
  }
  else
    // отправляем условие запуска
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA); // включаем INT

  // ждем завершения операции записи
  while(wait && (TWI_MTX == twi_state)){
    continue;
  }

  if (twi_error == 0xFF)
    return 0;   // успех
  else if (twi_error == TW_MT_SLA_NACK)
    return 2;   // ошибка: адрес отправлен, Nack получен
  else if (twi_error == TW_MT_DATA_NACK)
    return 3;   // ошибка: данные отправлены, но нет получено
  else
    return 4;   // другая ошибка twi
}

void twi_reply(uint8_t ack)
{
  if(ack){
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
  }else{
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
  }
}

void twi_stop(void)
{
  // отправляем стоп-условие
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);

  while(TWCR & _BV(TWSTO)){
    continue;
  }

  // обновляем два состояния
  twi_state = TWI_READY;
}

/* 
 * Function twi_releaseBus
 * Desc     releases bus control
 * Input    none
 * Output   none
 */
void twi_releaseBus(void)
{
  // освобождаем шину
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);

  // обновляем два состояния
  twi_state = TWI_READY;
}

ISR(TWI_vect)
{
  switch(TW_STATUS){
    // Все мастера
    case TW_START:     // отправлено условие запуска
    case TW_REP_START: // отправлено повторное условие запуска
      // копируем адрес устройства и бит чтения/записи в выходной регистр и подтверждаем
      TWDR = twi_slarw;
      Serial.print("mt add sent is:                 0x");
      Serial.println(twi_slarw, HEX);
      twi_reply(1);
      break;

    // Главный передатчик
    case TW_MT_SLA_ACK:  // подтвержденный адрес подчиненного получателя
    case TW_MT_DATA_ACK: // подтвержденные данные подчиненного получателя
      // если есть данные для отправки, отправьте их, иначе остановитесь
      if(twi_masterBufferIndex < twi_masterBufferLength){
        // копируем данные в выходной регистр и подтверждаем
        Serial.print("mt byte transmitted no:         ");
        Serial.println(twi_masterBufferIndex);
        Serial.print("content of tx byte:             0x");
        Serial.println(twi_masterBuffer[twi_masterBufferIndex], HEX);       
        TWDR = twi_masterBuffer[twi_masterBufferIndex++];
        twi_reply(1);
      }else{
    if (twi_sendStop){
        Serial.println("mt stop");
        twi_stop();
    }

    else {
      Serial.println("mt repst");
      twi_inRepStart = true;    // мы отправим START
      TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
      twi_state = TWI_READY;
    }
      }
      break;
    case TW_MT_SLA_NACK:  // адрес отправлен, NACK получен
    Serial.println("mt add sent nack");
      twi_error = TW_MT_SLA_NACK;
      twi_stop();
      break;
    case TW_MT_DATA_NACK: // данные отправлены, Nack получен
      twi_error = TW_MT_DATA_NACK;
      twi_stop();
      break;
    case TW_MT_ARB_LOST: // арбитраж шины проигран
      twi_error = TW_MT_ARB_LOST;
      twi_releaseBus();
      break;

    // Главный получатель
    case TW_MR_DATA_ACK: // данные получены, подтверждение отправлено
      // помещаем байт в буфер
        Serial.print("mr byte received no:            ");
        Serial.println(twi_masterBufferIndex);      
        Serial.print("content of rx byte:             0x");
        Serial.println(twi_masterBuffer[twi_masterBufferIndex], HEX);       
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
    case TW_MR_SLA_ACK:  // адрес отправлен, подтверждение получено
      // подтверждаем, если ожидается больше байт, в противном случае — нет
      if(twi_masterBufferIndex < twi_masterBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;
    case TW_MR_DATA_NACK: // данные получены, Nack отправлен
      // помещаем последний байт в буфер
        Serial.print("mr last received byte:          ");
        Serial.println(twi_masterBufferIndex);  
        Serial.print("content of last rx byte:        0x");
        Serial.println(twi_masterBuffer[twi_masterBufferIndex], HEX);       
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
    if (twi_sendStop)
          twi_stop();
    else {
      twi_inRepStart = true;
      TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
      twi_state = TWI_READY;
    }    
    break;
    case TW_MR_SLA_NACK:
      twi_stop();
      break;
}

Теперь проблема в следующем:

Я получаю эти показания на последовательном мониторе Arduino. Примечание. Я написал свои примечания к каждому разделу.

// инициализация
// Я думаю, что этот процесс выполняется нормально, отправляются точные байты
// но каждый раз, когда я открываю последовательный монитор, я сначала получаю мусор
// окна, как этот перевернутый вопросительный знак, который я получаю чаще всего
// Есть ли что-то вроде функции сброса для очистки?
⸮mt add sent is:                 0x3C
mt byte transmitted no:         0
content of tx byte:             0x0
mt byte transmitted no:         1
content of tx byte:             0x78
mt byte transmitted no:         2
content of tx byte:             0x20
mt byte transmitted no:         3
content of tx byte:             0x0
mt stop


// записываем целевой регистр 0x00, но получаем 0xA3 ??!
m
mt byte transmitted no:         0
content of tx byte:             0xA3
mt stop

// Здесь, запрашивая 6-байтовые данные, я отправляю чтение HMC5883L, который равен 0x3D
// но 1-й байт должен быть 0x78, затем 2-й 0x20, 3-й 0x00
// но результат другой! как будто они как-то сдвинуты

// В конце я пытаюсь прочитать переданный массив функции, но
// Я получаю 0!!
mt add sent is:                 0x3D
mr byte received no:            0
content of rx byte:             0xA3
mr byte received no:            1
content of rx byte:             0x78
mr last received byte:          2
content of last rx byte:        0x20
length of read op:              3

config settings:                0   0   0

, 👍1

Обсуждение

Это не основная библиотека, эти функции нижнего уровня являются частью библиотеки Wire., @Jot

Ааа, ок, я тогда не различал основные/нижние функции. Хорошо, а что может быть похоже на основную библиотеку? Являются ли Arduino.h и другие связанные библиотеки, находящиеся в папке Arduino в папке установки, основными библиотеками для Arduino?, @R1S8K

Не существует «основной» библиотеки. Существует ряд библиотек Wire (официальных и других совместимых библиотек) для разных процессоров. Так уж получилось, что библиотека Wire для микроконтроллеров avr имеет два слоя., @Jot

Да, спасибо за разъяснения. Конечно, я думаю, что одна библиотека не может охватить все семейства микроконтроллеров, она может сосредоточиться на одном конкретном семействе или группе микроконтроллеров., @R1S8K


1 ответ


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

1

Второй параметр twi_writeTo должен быть указателем на буфер. Теперь у вас есть

twi_writeTo(HMC5883L,0x00,1,0,1);

который отправляет чтение байта по адресу 0x00. измените его на

uint8_t b = 0x00;
twi_writeTo(HMC5883L,&b,1,0,1);

(Массивы C являются указателем на первый элемент)

,

Да, я сделал это. Ничего страшного. Но проблема в чтении регистров конфигурации. Я могу автоматически читать регистры данных, но не могу автоматически читать регистры конфигурации! Я должен сделать это один за другим., @R1S8K

это совсем другой вопрос. в оригинале речь шла о twi (под)библиотеке. новое касается конкретного устройства. автоматическое приращение регистра предназначено для ускорения обычного варианта использования. с настройкой не нужно торопиться, @Juraj

Да, извините, на самом деле он подключен к устройству, но, возможно, принцип общий для устройств i2c. Проблема, которую я пытаюсь понять, заключается в этой особенности общения с устройствами i2c. Я думаю, что существует два типа связи с устройствами i2c; по крайней мере, к HMC5883L. Я думаю, если я хочу разобраться с чтением регистров конфигурации, то мне придется делать это по одному, если с чтением регистров данных, то я могу сделать это автоматически., @R1S8K

это зависит от устройства, а не от функции I2C, @Juraj

А, ок, понятно, что у меня действительно не так уж много информации :) Спасибо чувак за помощь, я очень ценю это. Сначала я опубликовал весь этот длинный пост и действительно не знаю, чего пытаюсь добиться, потому что мне нужно знать очень много связанных с этим проблем. Думаю удалить тему! Что вы думаете ? Вы согласны ? а затем я снова привожу себя в порядок и рассматриваю еще одну полезную тему. Есть рекомендации?, @R1S8K

Вы имеете в виду последнее обновление поста?, @R1S8K