Функция printByte использовалась на этапе разработки для вывода двоичных данных. Несмотря на то что Serial.print может выводить двоичные значения, она не добавляет ведущие нули, что немного усложняет интерпретацию данных. Функция printByte, напротив, всегда выводит все 8 бит.
Чтобы увидеть данные, поступающие от микросхемы MCP3008, уберите символы // перед двумя вызовами printByte, и данные появятся в окне монитора последовательного порта.
Наиболее интересный для нас код сосредоточен в функции readADC, которая принимает номер канала АЦП (от 0 до 7). Прежде всего нужно выполнить некоторые манипуляции с битами, чтобы создать конфигурационный байт, определяющий вид преобразования аналогового сигнала и номер канала.
Микросхема поддерживает два режима работы АЦП. В одном выполняется сравнение двух аналоговых каналов, а во втором, несимметричном режиме (который используется в этом примере), возвращается значение, прочитанное из указанного канала, как в случае с аналоговыми входами на плате Arduino. В документации к MCP3008 (http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) указывается, что в настроечной команде должны быть установлены четыре бита: первый бит должен быть установлен в 1, чтобы включить несимметричный режим, следующие три бита определяют номер канала (от 0 до 7).
Микросхема MCP3008 не поддерживает режим побайтовой передачи, в котором действует библиотека SPI. Чтобы MCP3008 распознала эти четыре бита, их нужно разбить на два байта. Далее показано, как это делается:
unsigned int configWord = 0b11000 | channel;
byte configByteA = (configWord >> 1);
byte configByteB = ((configWord & 1) << 7);
Первый байт конфигурационного сообщения содержит две единицы, первая из которых может не понадобиться, а вторая — это бит режима (в данном случае несимметричного). Другие два бита в этом байте — старшие два бита номера канала. Оставшийся бит из этого номера передается во втором конфигурационном байте как самый старший бит.
Следующая строка устанавливает уровень LOW в линии выбора ведомого устройства, чтобы активировать его:
digitalWrite(chipSelectPin, LOW);
После этого отправляется первый конфигурационный байт:
SPI.transfer(configByteA);
byte readingH = SPI.transfer(configByteB);
byte readingL = SPI.transfer(0);
digitalWrite(chipSelectPin, HIGH);
Аналоговые данные не будут передаваться обратно, пока не будет отправлен второй байт. 10 битов данных из АЦП разбиты на два байта, поэтому, чтобы подтолкнуть отправку оставшихся данных, выполняется передача нулевого байта.
Затем в линии выбора ведомого устанавливается уровень HIGH как признак того, что передача завершена.
Полученное 10-битное значение пересчитывается, как показано в следующей строке:
int reading = ((readingH & 0b00011111) << 5)
+ ((readingL & 0b11111000) >> 3);
Каждый из двух байт содержит пять бит данных из десяти. Первый байт содержит данные в пяти младших битах. Все остальные биты, кроме этих пяти, маскируются, и затем выполняется сдвиг 16-битного значения int влево на пять разрядов. Младший байт содержит остальные данные в пяти старших битах. Они также выделяются маской, сдвигаются вправо на три разряда и прибавляются к 16-битному значению int.
Для проверки откройте монитор последовательного порта. Вы должны увидеть, как в нем появляются некоторые данные. Если повернуть шток переменного сопротивления по часовой стрелке, чтобы увеличить напряжение на аналоговом входе с 0 до 5 В, вы должны увидеть картину, похожую на рис. 9.7. Первые два двоичных числа — это два байта, полученных от MCP3008, а последнее десятичное число — это аналоговое значение между 0 и 1023.
Рис. 9.7. Просмотр сообщений в двоичном виде
В заключение
Организовать взаимодействие через интерфейс SPI без применения библиотеки очень непросто. Вам придется пройти тернистый путь проб и ошибок, чтобы добиться нужного результата. Занимаясь отладкой любого кода, всегда начинайте со сбора информации и исследования принимаемых данных. Шаг за шагом вы нарисуете полную картину происходящего и затем сможете сконструировать код, помогающий достичь желаемого результата.
В следующей главе мы исследуем последний стандартный интерфейс, поддерживаемый Arduino, — последовательный порт ТТЛ. Это стандартный вид связи «точка–точка», а не шина, но тем не менее очень удобный и широко используемый механизм обмена данными.
10. Программирование последовательного интерфейса