Двоичная адресация и кодирование для управления матрицей мультиплексирования с 96 входами

Пытаюсь разобраться с использованием двоичных адресов в мультиплексоре. Безуспешно. В схеме используются шесть 16-канальных аналоговых мультиплексоров CD74HC4067, работающих в качестве ведомых устройств по отношению к ведущему восьмиканальному мультиплексору CD54HC4051. Они собирают показания 96 датчиков (ИК-приемники, диоды через операционные усилители) в массив. который, в свою очередь, используется для постоянного обновления множества светодиодов через ряд регистров сдвига (TLC5940) с использованием библиотеки Arduino TLC5940.

Я включил возможную схему электропроводки, которую код пытается контролировать. Вероятно, его тоже нужно доработать.

Вопрос: Я видел и частично понимаю матрицы, которые обрабатывают 32 входа. (4 x 8 бит), но не совсем понимаю, как я могу адресовать шесть чипов мультиплексора с 96 входами. Или можно ли вообще сделать это таким образом? Пример с этого форума: Как написать код для каскадного соединения мультиплексоров? Возможно ли это с этой матрицей из 96 входов, и как? Может, я лаю не на то дерево?

Код на данный момент:

    #include <Tlc5940.h> //Библиотека Arduino TLC5940 управляет отдельными светодиодами — «Пикселями»

int ledCount = 96; //изменить количество светодиодов "PIXEL" в матрице...а также количество входов ИК-датчика

int thresh = 10; //значение для полезного расстояния, считанного с ИК-датчика

// основные структуры для хранения состояния включения/выключения светодиодной пиксельной матрицы, показаний ИК-приемника
byte dsply_state[ledCount];    //пиксельная сетка включения-выключения, нужна для tlc5940
uint_16  recv_state[ledCount];           // Показания ИК - ИЗ настройки мультиплексора


// Контакты главного ИК-мультиплексорного датчика для Arduino Pro MINI
#define  M_S0 2
#define  M_S1 4
#define  M_S2 5
// Контакты ведомого ИК-мультиплексора
#define  S_S0 6
#define  S_S1 7
#define  S_S2 8
#define  S_S3 12


// АНАЛОГОВЫЙ ВЫВОД:
#define  InputFromMux A0

void setup() {
    //Serial.begin(9600);

    //tlc5940 Библиотека Arduino
    Tlc.init(0);

    // НАСТРОЙКА АДРЕСНЫХ ПИН-КОДОВ
    pinMode(M_S0, OUTPUT);
    pinMode(M_S1, OUTPUT);
    pinMode(M_S2, OUTPUT);

    pinMode(S_S0, OUTPUT);
    pinMode(S_S1, OUTPUT);
    pinMode(S_S2, OUTPUT);
    pinMode(S_S3, OUTPUT);
    pinMode(InputFromMux, INPUT); 

}


void loop() {
    // ПРОХОДИМ ПО ВСЕМ АДРЕСАМ ВЕДУЩЕГО И ПОДЧИНЕННЫХ УСТРОЙСТВ.

  for (int i = 0; i < ledCount; i++) {

    digitalWrite(M_S2, i & 0b1000000);
    digitalWrite(M_S1, i & 0b0100000);    
    digitalWrite(M_S0, i & 0b0010000);
    digitalWrite(S_S3, i & 0b0001000);       
    digitalWrite(S_S2, i & 0b0000100);
    digitalWrite(S_S1, i & 0b0000010);
    digitalWrite(S_S0, i & 0b0000001);

    delay(2);

    //отредактировано в соответствии с @Gerben. большое спасибо

    //получить показания отдельного датчика от ИК-приемника/мультиплексора операционного усилителя, сохранить их в переменной
     int IRstate = analogRead(InputFromMux);

    //заполнить принимающий массив показаниями
     recv_state[i] = IRstate;

    //заполнить массив отображения для tlc5940 для выдачи
     dsply_state[i] = recv_state[i];

} //конец цикла for(i...)

      //пройти по массиву состояний дисплея, чтобы проверить, превышает ли сохраненное показание пороговое значение
      //Если да, то включаем "пиксель". Если нет, то обнуляем.
      //

       for (int x = 0; x < ledCount; x++)
          if (dsply_state[x] >= thresh ) {  

               Tlc.set(dsply_state[x], 4000);

             //или если чтение из мультиплекса равно нулю
          } else if (dsply_state[x] < thresh) {

              Tlc.set(dsply_state[x], 0);
              }

            //tlc5940 Библиотека Arduino - update() тактирует чипы
            // переместить update(); за пределы цикла for(x...) для скорости?
        Tlc.update();
        delay(10);

      } //конец цикла For(x..)

}//endMainLoop

, 👍0

Обсуждение

Число 48 в цикле for должно быть равно 96. Это должно быть digitalWrite(M_S2, i & 0b1000000), а не 0b110000. Вы выполняете anologRead, но ничего не делаете с прочитанным значением (IRstate)., @Gerben

Спасибо, @Gerben. Именно это меня и смущает. Как правильно записать маски(?), которые заставляют чипы циклически проходить через каждый вход? Если я запишу M-S2 в цифровом формате как i и 0b1000000, это будет то же самое, что и M_S1? Или же результат достаточно изменится, если вы добавите шесть нулей вместо пяти — M_S1, i и 0b100000? Что касается IRstate, я думал, что помещаю каждое показание в массив recv_state в цикле for. Или переменная должна быть внутри цикла? Или есть лучший способ поместить каждое показание в массив?, @Curtis Bracher

Вы правильно заметили лишний 0. Ваша маска должна быть длиной 7 бит. 3 бита представляют диапазон от 0 до 7 для первого мультиплексора, 4 бита — диапазон от 0 до 15 для мультиплексоров второго каскада. Возможно, для удобства чтения стоит добавить 0 слева от всех масок., @Gerben

Строка вашего кода выглядит так: IRstate = recv_state[x];. Возможно, вы имели в виду recv_state[x] = IRstate;. Значение правой части — это то, что вы присваиваете левой части. Думаю, вам нужно удалить цикл for( x ...) и добавить recv_state[i] = IRstate; и dsply_state[x] = recv_state[x];, @Gerben

Спасибо, @gerben, я отредактирую маски. Re:IRstate.....То есть, это работает без цикла for? int IRstate = analogRead(InputFromMux); recv_state[i] = IRstate; disply_state[x] = recv_state[i]; Эти три строки берут отдельные показания, помещают все 96 показаний в массив recv-state, а затем отображают recv-state[] в новый массив display_state[]? Тогда это основной цикл, который выполняет загрузку?, @Curtis Bracher

У вас уже есть цикл for. Второй не нужен., @Gerben

Спасибо, @gerben. Вижу, я пропустил первый цикл for(i...), поэтому понимаю, как я повторялся. Однако я не понимаю, как итерируется dsply_state[x]? dsply_state[x] = recv_state[i]; Разве это не просто перезапись x, а не отображение 96 отдельных значений в массив dsply_state? Стоит ли мне просто сделать dsply_state[i] = recv_state[i];? Будет ли это правильным способом отображения массива dsply_state в массив recv_state?, @Curtis Bracher

Да, извините. Забыл заменить x на i. Тогда значения в массиве dsply_state совпадут со значениями в массиве recv_state. P.S. Я насчитал всего 5 }, хотя на самом деле их 6 {., @Gerben

Это очень помогло. Я попробовал отредактировать код, как вы посоветовали. Теперь он стал гораздо понятнее. Спасибо, @gerben., @Curtis Bracher

Если не изменить схему, возникнут проблемы. На микросхеме 4051 есть неиспользуемый вывод A0, а это значит, что ваши битовые последовательности будут потеряны. Лучше использовать выводы A0–A5, оставив A6 и A7 свободными. В противном случае придётся изменить код, чтобы это учесть. В таком случае, вероятно, проще будет использовать два цикла, если у вас уже есть плата и вы не можете её изменить., @Nick Gammon

Вы также можете использовать библиотеку [digitalWriteFast](https://github.com/NicksonYap/digitalWriteFast), которая несколько ускорит все эти digitalWrites в цикле., @Nick Gammon

Беру свои слова обратно насчёт digitalWriteFast. Для этого требуется, чтобы оба аргумента (номер и значение пина) были константами. Само значение константой не является., @Nick Gammon


1 ответ


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

1

Хорошо. Сначала о том, как работает мультиплексор. У меня есть страница о мультиплексорах, на которой есть такой пример 74HC4051:

CD4051

В зависимости от «бинарного» входа A/B/C (обозначенного на схеме как S0/S1/S2) между одним из восьми входов и единственным выходом существует низкоомный путь. CD74HC4067 работает аналогично, но коммутирует 16 входов.

Таким образом, если вы устанавливаете двоичное число 2 (010) в A/B/C, то выбирается третий вход (число 02, поскольку оно относительно нуля).

В вашей схеме есть 6 микросхем CD74HC4067, которые, таким образом, могут иметь 16 x 6 входов (всего 96 входов). Выходы этих 6 микросхем подаются на 54HC4051 (2 не используются), поэтому 54HC4051 может выбрать, какая из 6 микросхем с 16 входами ему нужна в данный момент, установив число от 0 до 5 на 8-входовом мультиплексоре.

Как вы можете видеть на схеме, все четыре двоичных входа 6 x CD74HC4067 соединены вместе.

Таким образом, ваш метод будет следующим:

  • Пройдите цикл от 0 до 5 на главном мультиплексоре (54HC4051). Это позволит выбрать, какой из шести 16-входовых мультиплексоров вас интересует в данный момент.

  • Для каждого из них выполните цикл от 0 до 15 на S0/S1/S2/S3 так, чтобы текущий выбранный CD74HC4067 направил свой вход на свой выход.

  • Прочитайте полученное значение

Эти два цикла дадут вам 96 входных данных, что вы и пытаетесь прочитать.

Для вывода двоичных чисел (3 бита плюс 4 бита) в этом методе требуется 7 выходных контактов. Также требуется один входной контакт для считывания результата.


Судя по схеме, он использует SPI (последовательные данные) для управления шестью сдвиговыми регистрами TLC5940, что позволяет включать и выключать светодиоды с помощью всего двух управляющих выводов (SCK и MOSI, на вашей схеме обозначены как SCLK и SOUT).

Чипы соединены последовательно, поэтому все TLC5940 управляются этими двумя контактами.

Поэтому этот довольно минималистичный дизайн оставил несколько запасных контактов на Arduino Pro.


В основном мне сложно визуализировать код, особенно как правильно написать 7 команд записи двоичных цифровых данных. Не могли бы вы исправить то, что я написал выше, чтобы я мог увидеть закономерность?

Я знаю, что к тому времени, как я это увидел, вы уже изменили вопрос, но код ниже иллюстрирует то, что я подразумеваю под двумя циклами:

const int MUX_CHIPS = 6;
const int LEDS_PER_CHIP = 16;
const int TOTAL_IR_INPUTS = MUX_CHIPS * LEDS_PER_CHIP;
// Контакты главного ИК-мультиплексорного датчика для Arduino Pro MINI
const byte  M_S0 = 2;
const byte  M_S1 = 4;
const byte  M_S2 = 5;
// Контакты ведомого ИК-мультиплексора
const byte  S_S0 = 6;
const byte  S_S1 = 7;
const byte  S_S2 = 8;
const byte  S_S3 = 12;
// АНАЛОГОВЫЙ ВЫВОД:
const byte INPUT_FROM_MUX = A0;

int  recv_state[TOTAL_IR_INPUTS];           // Показания ИК-приемника - ИЗ настройки мультиплексора

void setup() {
    Serial.begin(115200);

    // НАСТРОЙКА АДРЕСНЫХ ПИН-КОДОВ
    pinMode(M_S0, OUTPUT);
    pinMode(M_S1, OUTPUT);
    pinMode(M_S2, OUTPUT);

    pinMode(S_S0, OUTPUT);
    pinMode(S_S1, OUTPUT);
    pinMode(S_S2, OUTPUT);
    pinMode(S_S3, OUTPUT);
    pinMode(INPUT_FROM_MUX, INPUT); 

    // установить предделитель АЦП на 32
    ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // очистить биты предварительного делителя
    ADCSRA |= bit (ADPS0) | bit (ADPS2);                 // 32
} // конец настройки


void loop() {

  unsigned long start = micros ();

  // считывание с ИК-диодов

  int whichLED = 0;
  for (byte master = 0; master < MUX_CHIPS; master++)
    {
    // выбрать соответствующий ведомый на главном MUX
    digitalWrite(M_S2, (master & 0b100) ? HIGH : LOW);
    digitalWrite(M_S1, (master & 0b010) ? HIGH : LOW);    
    digitalWrite(M_S0, (master & 0b001) ? HIGH : LOW);

    // считываем показания каждого подчиненного устройства
    for (byte slave = 0; slave < LEDS_PER_CHIP; slave++)
      {
      digitalWrite(S_S3, (slave & 0b1000) ? HIGH : LOW);       
      digitalWrite(S_S2, (slave & 0b0100) ? HIGH : LOW);
      digitalWrite(S_S1, (slave & 0b0010) ? HIGH : LOW);
      digitalWrite(S_S0, (slave & 0b0001) ? HIGH : LOW);

      // сохранить это показание
      recv_state [whichLED++] = analogRead (INPUT_FROM_MUX);

      } // конец для каждого подчиненного
    } // конец для каждого мастера

  // отобразить результаты здесь ...

} // цикла

Точный код выше потребовал 5148 мкс (около 5 мс) для выполнения цикла на моём Uno. Если убрать ускорение АЦП (analogRead), то это займёт 13252 мкс (13 мс).

Чтобы обеспечить точное подключение, вам нужно игнорировать младший бит на главном мультиплексоре, поскольку вы не используете вход A0_13; в этом случае вам нужно добавить 2 к главному значению во внешнем цикле:

    // выбрать соответствующий ведомый узел на главном MUX
    byte master_plus_2 = master + 2;  // разрешить не использовать вход A0
    digitalWrite(M_S2, (master_plus_2 & 0b100) ? HIGH : LOW);
    digitalWrite(M_S1, (master_plus_2 & 0b010) ? HIGH : LOW);    
    digitalWrite(M_S0, (master_plus_2 & 0b001) ? HIGH : LOW);
,

Спасибо, @nickGammon. Спасибо за объяснение мультиплексирования. Очень полезно. В основном, мне сложно визуализировать код, особенно как правильно написать 7 двоичных цифровых команд записи. Не могли бы вы исправить то, что я написал выше, чтобы я мог увидеть закономерность? И да, пока что эта арт-инсталляция должна быть максимально простой, по крайней мере, пока я не разберусь лучше., @Curtis Bracher

Похоже, вы уже разобрались. Возможно, стоит изменить тактовую частоту АЦП (см. [здесь](http://www.gammon.com.au/adc)). Предделитель 32 сократит время чтения аналоговых сигналов до 26 мкс вместо 104 мкс, что улучшит скорость отклика при чтении 96 сигналов., @Nick Gammon

Я всё равно обновил свой ответ, включив предложенный мной код (внутренний и внешний циклы). Это упростит внесение изменений, если вы оставите A0 отключённым на главном мультиплексоре., @Nick Gammon

Это просто за гранью моего понимания :-) В любом случае , огромное спасибо за уроки, @nickGammon. Объяснения и код очень полезны. Попробую оба кода: более простой и этот с двойным контуром/предделителем, после того как соберу схему. Ещё можно заменить 4051 на A0-A5, как и предлагалось, и попробовать включить туда предделитель для скорости. Особенно учитывая, что 96 значений, в свою очередь, нужно ещё и через TLC5940., @Curtis Bracher