Связь 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;
}
@jungle_jim, 👍2
Обсуждение1 ответ
По совету @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). Обновлю после дальнейшего устранения неполадок.
- Как использовать SPI на Arduino?
- OVF в последовательном мониторе вместо данных
- Как отправить строку с подчиненного устройства Arduino с помощью SPI?
- Проблема совместного использования MISO с несколькими RFID-считывателями RC522
- Программирование ведомого SPI для Arduino
- Последовательная связь между несколькими устройствами (или ардуино)
- Максимальная скорость SPI для ведомого Uno/clone?
- SPI.transfer(buffer, size) не отправляет данные из буфера
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