Связь L9780 и SPI

Я работаю над программой для взаимодействия с отладочной платой, которую я сделал для микросхемы широкополосного контроллера кислорода L9780. Сейчас я пытаюсь получить стабильное считывание «статуса», но продолжаю получать неожиданные результаты, которые я объясню ниже. Это первый «настоящий» случай, когда я пытаюсь написать последовательность SPI для микросхемы с нуля, поэтому буду благодарен за проверку!

Первым вопросом/проверкой, который у меня возник, был режим/настройка SPI. Судя по описанию, согласно спецификации, мой тактовый сигнал находится в состоянии ожидания (low), а первое подтверждение данных (на SI) происходит до перехода тактового фронта в высокий уровень. Это должно соответствовать режиму MODE0. В разделе 6.3 он указан как MSB first. В таблице синхронизации SPI также указана частота передачи данных 8 МГц; всё вышеперечисленное должно дать мне мою конфигурацию, верно?

Мой второй вопрос/проверка касалась использования функции «SPI.transfer()». Я изучил эту тему и понимаю её синтаксис, так что, думаю, с этой частью всё в порядке.

В разделе 6.3.1 говорится о бите «REG», который используется для фиксации, содержит ли следующий кадр SO входной регистр или текущий регистр «status». Именно поэтому в моей функции «get_status» я отправляю запрос, а затем выполняю ещё одну передачу по SPI (через функцию) для чтения результата. Это верно?

Причина, по которой, как мне кажется, у меня проблемы, заключается в том, что в регистре состояния старший бит игнорируется, а затем следуют три бита версии. Время от времени при использовании моего текущего кода для тестирования я получаю изменение с dec(1) на dec(2). Это, очевидно, неправильно, поэтому, хотя мне и удаётся каким-то образом «общаться» с чипом, я предполагаю, что где-то моё форматирование или порядок байтов — это FUBAR.

Может быть, дело в том, что Arduino использует байты с прямым порядком байтов, и я связываю это с SPI, который передаёт старший байт первым? Судя по различным 64-битным словам, которые я получил, это тоже не так, учитывая, что последний байт тоже меняется.

Кроме того, согласно техническому описанию, есть несколько фиксированных битов (CB) и битов ошибок SPI, которые должны давать представление о состоянии текущего фрейма SPI. Оба из них тоже вызывают у меня проблемы, что ещё раз указывает на то, что у меня что-то не так с коммуникацией по SPI.

По всем этим причинам я был бы благодарен за помощь! Ниже приведены некоторые «выходные данные» моего текущего кода, просто как пример того, о чём я говорю.

  • Регистрация SI при запуске (без передачи настроек, ничего)

    • 0x4503458300C2CD31
    • 0x450345A300E26E32
    • 0x450345A300E27732
  • Регистр SI после «отправки конфигурации H/W в L9780»

    • 0x370045A30002F933
    • 0x370045A30082FA33
  • регистрация состояния при запуске (без передачи настроек, ничего)

    • 0x450345A300024632
    • Один и тот же результат, несколько раз
  • регистр состояния после «отправки конфигурации H/W в L9780»

    • 0x380045A3FFE1FA33
    • 0x380045A300E2FA33
    • 0x370045A300C2F933
  • Регистр состояния после прогрева и «включения VCCS» и «включения stgINRC»

    • 0x1A031AA3001AFA33
    • 0x0C030CA3007AFA33
    • 0x1A0319A300FAFA33
    • 0x150315A3009AFA33

Техническое описание L9780

/*  Program is taken from the development firmware but simplified
 *   to menu actions for preliminary debug.
 *   
 *   NOTES
 *   -------------
 *   -HTR PWM output from uC seems to be inverse. When applying a 
 *      15% duty, the heater draw is higher than 85% duty
 */

//*****включает
#include <SPI.h>

//*****общие определения и данные
#define serial_baud       9600                //скорость последовательного порта
#define L9780_SPI_SPD           8000000                         //Частота SPI (согласно временной диаграмме L9780)
#define L9780_SPI_ORDR      MSBFIRST                        //определить порядок SPI как MSB first (согласно временной диаграмме L9780)
#define L9780_SPI_MODE      SPI_MODE0                       // определить режим SPI, тактовый сигнал Idle LOW, первая команда CMD отправляется без SCK (согласно временной диаграмме L9780)
#define L9780_SO_SI       true                //используется для «получения статуса» — помещает регистр входа (SI) в SO
#define L9780_SO_STAT     false               //используется для «Получения статуса» — помещает регистр статуса в SO
//pn_SPI0_MOSI 11 //!pinDef: SPI0 ведущий выход ведомый вход
//pn_SPI0_MISO 12 //!pinDef: SPI0 ведущий вход ведомый выход
//pn_SPI0_SCK 13 //!pinDef: часы SPI0
#define pn_L9780_CS       10                  //!pinDef: выбор микросхемы для L9780
#define pn_htr_out        9                     //!pinDef: выход ручного управления нагревателем

//*****Данные L9780
#define L9780_rx_reg      0x0800000000000000  //маска для получения входного регистра L9780 - битовое ИЛИ
#define L9780_status_reg  0xF7FFFFFFFFFFFFFF  //маска для получения регистра состояния - bitAND
#define L9780_VCCS_en     0x0040000000000000  //маска для включения оператора - bitOR
#define L9780_STGINRC_en  0x0200000000000000  //маска для включения замыкания на землю на INRC - bitOR
#define LSU49_start_CFG   0x00040F18192A8040  // Конфигурация запуска LSU 4.9 — асинхронный режим
/* Config bit definitions:
 * b63-60 = 0;     not used
 * b59 = REG;      set appropriately when transmitting or receiving
 * b58 = 0;        INRC pull-down (try initially disabled)
 * b57 = 0;        INRC short to ground enable (disable on start)
 * b56 = 0;        INRC gain
 * b55 = 0;        tag resistor network switch (ch1)
 * b54 = 0;        VCCS enable (disable on start)
 * b53 = 0;        VCCS output ch (ch1)
 * b52 = 0;        VCCS pull-down (try initially disabled)
 * b51 = 0;        compensation network (chA)
 * b50 = 1;        VCCS voltage clamp enable
 * b49 = 0;        VCCS voltage clamp switch (ch1)
 * b48 = 0;        VCCS voltage clamp symetry (symetric)
 * b47 = 0;        fault clear (don't clear any faults)
 * b46-43 = 0001;  purge current (-14uA) (taken from L9780 test docs)
 * b42-40 = 111;   FV out gain (12) (taken from L9780 test docs)
 * b39-34 = 000110;VCCS capacitance (assumed Rtag ~130 ohm)
 * b33-32 = 00;    measurement clock period 4MHz
 * b31 = 0;        heater short to battery threshold (250mV)
 * b30-24 = 0011001; RCT1 switch time pulse duration (taken from L9780 test docs)
 * b23 = 0;        operation mode (free-running)
 * b22-16 = 0101010; RCT1 bandgap switch pulse duration (taken from L9780 test docs)
 * b15-14 = 10;    SPI confirmation bits - always 0b10
 * b13 = 0;        not used
 * b12-11 = 00;    heater short to battery fault time (80 Tosc)
 * b10-8 = 000;    INRC switch time pulse duration (taken from L9780 test docs)
 * b7 = 0;         protection FET ch2 (disabled)
 * b6 = 1;         protection FET ch1 (enabled)
 * b5-0 = 0;       not used
 */

void setup() {
  Serial.print(F("Initializing uC.............."));
  Serial.begin(serial_baud);          //запуск последовательного порта. Требуется не только для отладки.
  
  pinMode(pn_L9780_CS, OUTPUT);       // устанавливаем вывод CS как выход
  digitalWrite(pn_L9780_CS, HIGH);    // установить вывод CS в положение HIGH (выкл.)
  SPI.begin();                        // инициализируем SPI -> библиотека begin function устанавливает выходные контакты
  
  pinMode(pn_htr_out, OUTPUT);        // выход ШИМ нагревателя
  digitalWrite(pn_htr_out, HIGH);     // выключить ШИМ htr

  Serial.println(F("Init Complete!"));

  //****отобразить меню
  Serial.println(F("Menu Options"));
  Serial.println(F("--------------------------------------------------------------"));
  Serial.println(F("1)  Init - Send H/W config command to L9780"));
  Serial.println(F("2)  Init - Heatup WBO2 sensor"));
  Serial.println(F("3)  L9780 - Enable VCCS (start operation)"));
  Serial.println(F("4)  L9780 - Enable stgINRC (start closed-loop L9780 control)"));
  Serial.println(F("6)  L9780 - Get status register"));  
  //****конец отображения меню
}

void loop() {
  while (Serial.available() == false){} //ждем ввода пользователя
    int menu_choice = Serial.parseInt(SKIP_ALL);  //получить пользовательский ввод
  Serial.println(menu_choice);

  char c[150];                          //строка символов для вывода меню
  switch (menu_choice){
    case 1:
      L9780_SPI_xfr(LSU49_start_CFG);                   //установить начальные значения конфигурации через SPI
      break;
    case 2:
      heatup_WB02();                                    //последовательность нагрева
      break;
    case 3:
      L9780_SPI_xfr(LSU49_start_CFG | L9780_VCCS_en);   //включить VCCS (начать работу)
      break;
    case 4:
      L9780_SPI_xfr(LSU49_start_CFG | L9780_STGINRC_en);//включить INRCSTG после запуска
      break;
    case 5:
      {uint64_t data = L9780_get_status(L9780_SO_STAT);   //получить текущий вывод статуса
      Serial.print(F("Status Reg (64b): 0x")); SerialPrint_u64(data, true);
      }break;
    default:
      Serial.println(F("Input not recognized, please enter a valid number"));
      break;
  }
  Serial.println(F("--------------------------------------------------------------"));//строки форзаца
}

/*  FUNC:   Serial Print unsigned 64b value
 *  DESC:   Fucntion prints an unsigned 64b wide value
 *  ARGS:   v   - value to print
 *          nl  - true/false to print a new line
 *  RETN:   none
 */
void SerialPrint_u64(uint64_t v, bool nl){
  char c[] = "00000000"; //временный байт для печати
  sprintf(c,"%08lX", (v>>32)); Serial.print(c);
  sprintf(c,"%08lX", v); Serial.print(c);
  if(nl){Serial.println();}                         //если выбрано, вывести новую строку
  return;
}

/*  FUNC:   WBO2 sensor heatup
 *  DESC:   Fucntion called on sensor initialization
 *          and controlls the initial heatup cycle, including
 *          the condensation and ramp phase.
 *  ARGS:   none
 *  RETN:   none
 *  NOTES:  PWM out from L780 is inverse from arduino output
 */
void heatup_WB02(){
  Serial.println(F("-----Starting Sensor Heatup----"));
  int htr_pwm = 211;
  analogWrite(pn_htr_out, htr_pwm);                 //стадия конденсации
  Serial.println("---Condensation Phase");
  Serial.print("HTR PWM %: ");Serial.println(htr_pwm*20/51);
  delay(11000);                                     //упрощенное ожидание в общей сложности ~20 секунд для завершения этапа конденсации
  Serial.println("---Condensation Phase End");

  htr_pwm = 76;                                     //запуск фазы пилообразного сигнала, запуск при 70% нагрузке (приблизительно 8,4 В)
  while(htr_pwm > 0){                               //увеличение на 2/255 каждые 250 мс составляет примерно 0,4 В/с для фазы пилообразного сигнала
    Serial.print("HTR PWM %: ");Serial.println(htr_pwm*20/51);
    analogWrite(pn_htr_out, htr_pwm);               //установить ШИМ
    delay(250); htr_pwm -= 2;                       //увеличение/нарастание
  }
  
  Serial.println(F("-----Sensor Heatup Complete----"));
  return;
}

/*  FUNC:   L9780 SPI transfer
 *  DESC:   Fucntion sends a SPI command and receives its result
 *  ARGS:   d   -   data being sent on a SPI transfer
 *  RETN:   spi_buf - data result
 *  NOTES:  Calls "begin transaction" before every send to ensure
 *                  that if another SPI library is being used that updates
 *                  these values, that they're appropriately set for L9780
 */
uint64_t L9780_SPI_xfr(uint64_t d){
  uint64_t spi_buf = d;                             //данные для отправки
    
    //SPI-передача
    SPI.beginTransaction(SPISettings(L9780_SPI_SPD, L9780_SPI_ORDR, L9780_SPI_MODE)); //запускаем транзакцию SPI
    digitalWrite(pn_L9780_CS, LOW);                                     // установить вывод CS в положение LOW (вкл.)
  SPI.transfer(&spi_buf,8);                         //передать адрес временных данных + размер (8 байт для uint64t)
    digitalWrite(pn_L9780_CS, HIGH);                                // установить вывод CS в положение HIGH (выкл.)
    SPI.endTransaction();                                                           //завершение транзакции SPI
    
  return spi_buf;                                   //возвращаем принятые данные из L9780
}

/*  FUNC:   L9780 Function - Get status register
 *  DESC:   Fucntion reads the current status register
 *  ARGS:   act - true/false to select what is in SO
 *                  true - RX'd data (current SI)
 *                  false - status register 
 *  RETN:   d - SO register
 *  NOTES:  After sending data, per the datasheet the
 *          next SPI frame will have the associated data
 *          in the SO depdending on the asserted REG bit.
 */
uint64_t L9780_get_status(bool act){
  if(act = L9780_SO_SI){L9780_SPI_xfr(L9780_rx_reg);}//запросить входной регистр обратно
  else{L9780_SPI_xfr(L9780_status_reg);}             //запросить статус регистра обратно
  uint64_t d = L9780_SPI_xfr(L9780_status_reg);      //прочитать результат
  return d;
}

, 👍2

Обсуждение

1) В техническом описании указано максимальное значение 8 МГц, но это не значит, что вам обязательно нужно работать на этой частоте. На самом деле, поскольку вы используете Uno с тактовой частотой 16 МГц, вам, вероятно, не следует запускать SPI выше 4 МГц. 2) Я не думаю, что ваш код отправляет правильную команду из-за порядка байтов. Поэтому вместо того, чтобы определить L9780_status_reg как 0xF7FFFFFFFFFFFFFF, определите его с помощью массива L9780_status_reg[] = { 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, это гарантирует, что бит MS байта MS будет отправлен первым с помощью SPI.transfer(L9780_status_reg, sizeof(L9780_status_reg));, @hcheung

@hcheung, спасибо, хороший отзыв. Я уже запускал несколько устройств SPI с частотой около 10 МГц, но никогда не углублялся в их библиотеку, чтобы узнать, корректируют ли они предделитель или нет. Это моё слабое предположение. Никогда не помешает сначала замедлить процессор, так что это будет хорошим началом для проверки., @jungle_jim

@hcheung, спасибо за подсказку о порядке байтов. Это будет легко реализовать, и, по крайней мере, это станет ещё одним известным решением. Я изучал, как работают указатели/адреса с прямым/обратным порядком байтов, опасаясь, что что-то путаю. Я не был на 100% уверен, как работает порядок байтов с #define, который раскрывается во время компиляции, и порядком SPI.transfer. Именно поэтому я использовал вторичную локальную переменную в вызове функции, надеясь, что реализация локальной переменной решит проблемы. Попробую оба варианта и посмотрим, что получится!, @jungle_jim


1 ответ


0

По совету @hcheung я обновил функцию L9780_SPI_xfr, чтобы она преобразовывала переданное 64-битное слово в массив uint16_t, а затем возвращала его обратно в 64-битный результат. Не уверен, что это самый элегантный способ, но теперь, по крайней мере, я получаю ожидаемые ответы (с одним ещё изменением).

Ещё один момент, который я изначально неправильно понял, заключается в том, что в этом конкретном чипе на самом деле нет команды «игнорировать», поэтому при каждой передаче мне нужно отправлять «управляющее слово», а затем обновлять бит REG, чтобы задать, что будет сдвинуто в регистр SO для следующего кадра SPI. После внесения этих двух изменений я могу запросить регистр SI после отправки команды и получить тот же результат.

Мне всё ещё нужно разобраться с некоторыми проблемами, но, похоже, порты SPI теперь работают. При запросе регистра состояния устанавливается бит ошибки SPIF1, что соответствует ошибке «Ошибка длины данных команды SPI» при запросе регистра состояния. В техническом описании указано, что ожидаемая длина составляет [64 + n*8] бит, что мне не совсем понятно, поэтому мне нужно разобраться в этом подробнее. Я не уверен, является ли «n*8» просто обозначением, или же ожидаются и другие биты, выходящие за пределы 64 бит регистра управления/команд.

РЕДАКТИРОВАТЬ:

В качестве быстрого дополнения к этому: немного покопавшись, я обнаружил, что таблица, которую я настроил для генерации управляющего слова, была на 1 бит меньше (проклятия!). Ни одно из трёх условий, при которых должна была быть установлена «ошибка» в бите SPIF[1], не является ошибкой данных, но поскольку биты CB[1,0] были сдвинуты, это вызывало проблему, приводя к возникновению ошибки «неверная команда» / ошибки CB (через SPIF1). Обновлю после дальнейшего устранения неполадок.

,