Подключение ЦАП к Ардуино по SPI

Добрый день! Обращался уже с неделю назад по вопросу неправильного напряжения на выходе цап, мне указали на ошибку в инициализации CS, все заработало, но макет был ужасный, приходилось припаивать смд SOT8 на обычную макетную плату с отверстиями 2,54. Купил специальных переходников с смд на 2,54, сделал на той же макетке поприличнее и все перестало работать: с цапа выходит ерунда, на первой схеме лабораторный блок питания стабильно показывал 10мА, на новой ток получился нестабильный (иногда постепенно растет до 150мА и колеблется ± 50мА, иногда сразу колеблется на высоких значениях и напряжение скачет), при прозвонке земли и питания мультиметр может показывать и разрыв, и ±700, и 400 в прямом направлении и 150-400 (иногда вообще звенит) в обратном
После безуспешных попыток диагностики проблемы пересобрал схему уже на пластиковой макетке - точно так же, вытравил плату - точно так же
При том, что схема сборки не отличается от той, которая была изначально
Конечно, качество пайки все еще не лучшее, потому что разные элементы менялись/выпаивались и так далее
Первая была на нано, вторую тестил и на нано, и на уно
Из еще минимальных отличий в схемах: разные места пайки (паяльники, флюсы, припои) и разные ноутбуки, с которых произвожу прошивку (ноут на винде изменился на мак)
Сейчас параллельно с написанием поста сделал еще несколько тестов, в целом комбинаций результатов (ток потребления, напряжение на цап, сериал выход и тд) очень много, получил более менее понятный на зеленой макетке: ток потребления стабильные 10мА опять, но цап молчит и напряжение на выходе пару милливольт
Из-за чего такая проблема может наблюдаться и как ее решить?

Фото, трассировка, принципиальная схема










Код
#include <SPI.h>

#define weight_of_standard 220  // Калибровочный вес
#define power_down() \          
bitSet(_SFR_IO8(_Write), _clkPin); \
delayMicroseconds(64)
#define power_up() bitClear(_SFR_IO8(_Write), _clkPin)  // Макрос для включения питания АЦП
#define GAIN64A 2                                       // Установка Коэффициента усиления
#define REF_GR_DAC 100.00                                  // Референсное значение для ЦАП в граммах (=максимум шкалы измерения)
#define SPI_CLK 13
#define SPI_SDI 11

  class DAC {
public:
  DAC(int i = 1) {
  }
  void begin(uint8_t CS_PIN, byte Regs[2]) {  // Инициализация ЦАП, Pins - пины SPI
    CLK_PIN = SPI_CLK;           // Запись пинов SPI
    SDI_PIN = SPI_SDI;
    _Mode = Regs[0];
    _Write = Regs[1];
    bitSet(_SFR_IO8(_Mode), CS_PIN);
    bitSet(_SFR_IO8(_Write), CS_PIN);
    SPI.begin();                                                       // Запуск интерфейса SPI
    SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));  // Начало транзакции, 20МГц, Старший байт первый, Мод0
    
  }
  void write(float value) {  // Запись значения в ЦАП
    if (value < 0) {          // Если значение опускается меньше нуля, записывать ноль
      _VAL = 0;
    } else if (value > REF_GR_DAC) {  // Если значение выше референсного, записывать максимальное
      _VAL = 65535;
    } else {
      _VAL = uint16_t(65535 * value / REF_GR_DAC);  // Расчет значения по пропорции
    }
    bitClear(_SFR_IO8(_Write), CS_PIN);   // Запись в чипселект 0 для выбора ЦАП для передачи
    
    SPI.transfer16(_VAL);         // передача ЦАП
    //delay(15);
    bitSet(_SFR_IO8(_Write), CS_PIN);  // Запись в чипселект 1 для окончания передачи
  }
  uint16_t get_value () {
    return _VAL;
  }
  //MSBFIRST в настройке spi = избегаем разделения значений по двум байтам, выдача начинается со старшего байта
private:
  uint16_t _VAL;
  uint8_t CS_PIN, CLK_PIN, SDI_PIN;
  int16_t REFERENCE_MV;
  byte _Mode, _Write;
};

class SCALE {
public:
  /*Порядок записи в массив: DR, DW, DM, CW, CM, DP, CP*/
  SCALE(int i = 1) {
  }

  /*ИНИЦИАЛИЗАЦИЯ ДАТЧИКА*/
  void begin(byte Regs[3], byte Pins[2]) { // Инициализация АЦП
    _Read = Regs[0]; // Запись значений регистров и пинов
    _Write = Regs[1];
    _Mode = Regs[2];
    _dataPin = Pins[0];
    _clkPin = Pins[1];
    bitSet(_SFR_IO8(_Mode), _clkPin); // Настройка регистров
    bitClear(_SFR_IO8(_Mode), _dataPin);
    bitClear(_SFR_IO8(_Write), _clkPin);
    bitClear(_SFR_IO8(_Write), _dataPin);
    power_down(); //  Выключение и включение АЦП
    power_up();
    _chan = GAIN64A;
    _weight = 0;
    _value = 0;
    _offset = 0;
    _scale = 1;
  }

  /*УСТАНОВКА МАСШТАБА*/
  void set_scale(float scale) {
    _scale = 1.0 / scale;
  }

  /*КАЛИБРОВКА*/
  void calibrate_scale(uint8_t weight) {
    long read_av = 0;
    for (uint8_t i = 0; i < 15; i++) { // Калибровка на 15 значениях среднее арифметическое
      read_av += read();
    }
    read_av /= 15;
    _scale = (1.0 * weight) / (read_av - _offset); // получаем масштаб
  }

  /*ВЕРНУТЬ МАСШТАБ*/
  float get_scale() {
    return 1 / _scale;
  }

  /*ТАРИРОВАНИЕ*/
  void tare() {
    for (uint8_t i = 0; i < 3; i++) {  // тарируем на фильтрованном значении
      _offset += filt();
    }
    _offset = (_offset / 3) / _scale;
  }

  /*ЧТЕНИЕ СЫРОГО ЗНАЧЕНИЯ*/
  long read() {
    while (bitRead(_SFR_IO8(_Read), _dataPin) == HIGH) yield();
    _weight = 0;
    for (uint8_t i = 0; i < 24; i++) { // посылка сигналов тактирования
      bitSet(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, HIGH);
      delayMicroseconds(1);
      _weight <<= 1;
      if (bitRead(_SFR_IO8(_Read), _dataPin)) _weight |= 1;
      bitClear(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, LOW);
      delayMicroseconds(1);
    }
    for (uint8_t i = 0; i < _chan + 1; i++) {
      bitSet(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, 1);
      delayMicroseconds(1);
      bitClear(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, 0);
      delayMicroseconds(1);
    }
    if (_weight & 0x800000) _weight |= 0xFF000000;  // отрицательные
    return _weight;
  }

  /*ПЕРЕВОД СЫРОГО ЗНАЧЕНИЯ В ГРАММЫ*/
  float value() {
    read();
    _value = (_weight - _offset) * _scale;
    return _value;
  }

  /*ВЕРНУТЬ ЗНАЧЕНИЕ В ГРАММАХ*/
  float get_value() {
    return _value;
  }

  /*ФИЛЬТРАЦИЯ ЗНАЧЕНИЯ В ГРАММАХ*/
  float filt() {
    float newVal = value(); 
    byte k = 5;
    static float filt = newVal;
    if (abs(newVal - filt) <= 0.5) k = 30; // Выбор коэффициента для бегущего среднего в зависимости от величины изменения
    else if (abs(newVal - filt) >= 0.51 && abs(newVal - filt) < 2.7) k = 4;
    else if (abs(newVal - filt) >= 2.7 && abs(newVal - filt) < 3.5) k = 2;
    else if (abs(newVal - filt) >= 3.5 && abs(newVal - filt) < 5) k = 6;
    else if (abs(newVal - filt) >= 5 && abs(newVal - filt) < 7) k = 10;
    else if (abs(newVal - filt) >= 7) k = 20;

    filt += (newVal - filt) / k; // фильтрация по бегущему среднему
    return filt;
  }
  // float filt_raw() {
  //   float newVal = read();
  //   byte k = 7;
  //   static float filt = newVal;
  //   // if (abs(newVal - filt) <=0.5) k = 30;
  //   // else if(abs(newVal-filt) >= 0.51 && abs(newVal-filt)<=15) k = 3;
  //   // else if (abs(newVal-filt) > 10) k = 10;

  //   filt += (newVal - filt) / k;
  //   return filt;
  // }


private:
  byte _Read, _Write, _Mode;
  byte _dataPin, _clkPin, _chan;
  long _weight, _offset;
  float _scale, _value;
};

// Инициализация глобальных переменных
float values[4] = {0, 0, 0, 0};
bool tare_sig = false;
long timer = 0;
uint32_t t = 0;

/*Адресса регистров HX PIND, PORTD, DDRD  PORTB DDRB*/
byte D_Adresses[3] = { 0x09, 0x0B, 0x0A /*, 0x05, 0x04*/ };
// DDRC PORTC
byte DAC_C_Regs[2] = {0x07, 0x08};
// Пины CS для DAC      CS0 CS1 CS2 CS3
byte SPI_Adresses[4] = {0, 1, 2, 3};

// Пины данных и тактирования для HX711
byte D_Pins[4][2] = {
  { 1, 0 },
  { 2, 3 },
  { 4, 5 },
  { 6, 7 },
};





SCALE scale[4];  // создание объектов АЦП
DAC dac[4];      // Создание объектов ЦАП


// ISR(PCINT1_vect) {  // Обработчик прерывания на пине А4
//   scale[1].begin(D_Adresses, D_Pins[1]); // инициализация АЦП
//   scale[1].set_scale(386.54); // установка масштаба
//   scale[1].tare(); // тарирование
//   scale[1].set_scale(386.54);  // Вызов функции тарирование
//   tare_sig = true;  // Установка флага
// }

void setup() {
  Serial.begin(9600);
  // PCICR |= 1 << 1;
  // PCMSK1 |= 1 << 4;  // НАСТРОЙКА ПРЕРЫВАНИЯ НА ПИНЕ А4
  // SREG |= 1 << SREG_I;
// КОД ДЛЯ 4 ДАТЧИКОВ
  // for (int i = 0; i < 4; i++) {
  // scale[i].begin(D_Adresses, D_Pins[i]); // инициализация АЦП
  // scale[i].set_scale(386.54); // установка масштаба
  // scale[i].tare(); // тарирование
  // scale[i].set_scale(386.54);
  // dac[i].begin(SPI_Adresses); // инициализация ЦАП
  // }
  
  scale[1].begin(D_Adresses, D_Pins[1]); // инициализация АЦП
  scale[1].set_scale(386.54); // установка масштаба
  scale[1].tare(); // тарирование
  scale[1].set_scale(386.54);
  dac[1].begin(SPI_Adresses[1], DAC_C_Regs); // инициализация ЦАП
}

void loop() {
  tare_sig = false; // Сброс флага
    
   while (tare_sig == false) { // Выполнение при активном флаге
      values[1] = scale[1].filt(); //запись фильтрованного значения
      //Serial.println(values[1]);
      //  исполняемый код для передачи на плк
      dac[1].write(values[1]); // запись в ЦАП
      uint16_t value = dac[1].get_value();
      Serial.print("DAC: ");
      Serial.print(value);
      Serial.print(" ; ");
      Serial.print("ACD: ");
      Serial.println(values[1]);
    }
}

отмой платы для начала

Ну в следующий раз не будешь трогать то, что работало. А то же самое соберёшь из новых комплектующих и на новой макетке. По крайней мере будет с чем сравнивать с помощью тестера или осцила. Пайка конечно ужасная. Скорее всего в ней дело.

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

Так же не понял, почему ОУ без питания.

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

Проверял и визуально, и мультиметром, все, что должно быть подключено - подключено, смущают только, что при прозвонке земли и питания дает небольшие значения, разные в разных направлениях
ОУ без питания в соответствии с даташитом


Но даже если снимать напряжение не с ОУ, а сразу с ЦАП, ничего не меняется

Ошибаетесь, здесь питание подразумевается, это упрощённая схема

1 лайк

Возможно, какому-то элементу “кирдык”. Перерезайте дорожки/отключайте по очереди микросхемы от шины питания(при отключенном питании) пока это явление не уйдёт.
Будет ясно, где косяк.

Ну да, перегнул немного, здесь можно обойтись : ОУ и так отключен, ардуинку можно вынуть из разъёма,… так что малой кровью))

Это все пробовал, сейчас заменил мультиметр и это ушло, видимо, тот свое отжил, хотя кроме этого других симптомов я не замечал
ОУ запитал, но это не помогло, теперь просто 300-500мВ на выходе колеблются вне зависимости от нагрузки на тензодатчик

Я бы , сначала убедился что ЦАП работает. Т.е написать простой код для проверки. А затем и датчик подключать

1 лайк

меня эта строка сильно настораживает, но я не настоящий сталевар ежели что

    SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
2 лайка

Как вариант, попробую на небольшой макетке собрать отладочную плату сейчас

Тут не должно быть проблем, в единственной библиотеке на этот цап такая конфигурация, сверился с даташитом, кушает со старшего бита и подходит мод0

нано -уно всего 4 мегагерца, а тут аж 20, нолик убери и попробуй

3 лайка

В частоте опечатался. Нано не умеет 20 МГц SPI, у нее тактовая ядра всего 16

1 лайк

99.9% ua6em прав, и в этом дело.
Но если нет, просто опечатка, то не пойму, зачем отдельную плату собирать? Прям на этой лучше отлаживать, заодно и пайка проверится))
P.S. Учесть все пины , не забыть, вход/выход, от греха подальше…

1 лайк

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

Да, возможно библиотека установила максимально допустимую частоту F_CPU / 2, т.е. 8MHz. Но лучше на такой не работать ИМХО

1 лайк