Изначально поставка задачи была такой. У меня была в наличии плата TM1638. Нужно было научиться с ней работать (ну и проверить работоспособность самой платы) для того, чтобы использовать её в одном интересном проекте (о нём в другой раз). Под рукой оказалась платка Arduino Nano. Хотелось быстро отделаться проверить работу самой платы при помощи ардуиновской библиотеки SPI.h – не получилось. В результате проделанный объём работы вылился в эту заметку.
Для начала скажу, что покупала я эту плату для выполнения заданий Школы Синтеза. И когда заказывала, я не смотрела на то, что она из себя представляет (у Школы есть готовые скрипты для подключения железа, что очень удобно при старте обучения с нуля). Когда она пришла, я поняла, что без скриптов я её подключить не могу, потому что у неё интерфейс SPI, а я с ним прежде никогда не работала даже в микроконтроллерах (вот уж не знаю как так вышло, что он обошёл меня стороной).
Поэтому я решила описать весь процесс работы, вдруг кому-то это будет полезно.
В классическом варианте SPI использует для передачи четыре провода: SCLK для передачи тактового сигнала, MOSI и MISO – для передачи данных от мастера (ведущего) к ведомому и обратно и SS для выбора ведомого. Подробнее о самом SPI и о подключении устройств к шине можно почитать в интернете (например, тут описано, на мой взгляд, хорошо и с картинками).
Модуль же TM1638 имеет пять пинов, два из них это земля и питание, а вот остальные три для обмена данными. Один для передачи ему тактового сигнала от мастера (CLK), один для управления процессом передачи (STB) и один для передачи самих данных (DIO). Т.е. данная реализация SPI является полудуплексной — для передачи и приёма данных используется один и тот же провод.
Для управления модулем есть несколько команд: управление яркостью (и включение/выключение) (Display Control), задание адресации (Address Command) и выбор чтения/записи (Data Command).
Для настройки яркости используются следующие команды (переведённая и сокращённая мной таблица из документации):
Для выбора направления передачи и способа адресации есть следующие команды:
Для передачи адреса используются команды, начинающиеся с 0xC:
Для тех, кто не в курсе на всякий случай (ну вдруг) поясню, что если число, начинается с 0x или заканчивается на h, то это значит, что оно записано в шестнадцатеричной системе счисления.
С командами вроде бы всё относительно понятно. Первая сложность, с которой мне пришлось столкнуться, это странная адресация памяти в микросхеме. Немного погуглив и почитав найденные статьи стало понятно, что адреса индикаторов чередуются с адресами кнопок и светодиодов.
Немного модифицированная мной табличка из документации:
Из неё понятно (ну или на самом деле не очень), что чётные адреса с 00h по 0Eh отвечают за подключение семисегментных индикаторов. Нечётные адреса же – за подключение кнопок и светодиодов. Причём светодиоды подключены к SEG10, а кнопки к SEG9.
Т.е. чтобы вывести цифру на первый слева индикатор, нужно записать данные по адресу 00h. Для того, чтобы зажечь первый справа светодиод, нужно записать число 2 по адресу 0fh (чтобы погасить, нужно записать ноль).
Собственно про цифры. В документации имеется распиновка индикатора и схема его подключения к самому чипу.
Т.е. сегмент a — это младший бит, а dp — соответственно, старший. Для того, чтобы вывести цифру ноль нужно зажечь все сегменты, кроме g и dp — это 0x3f. Чтобы вывести единицу, нужно зажечь сегменты b и c — это 0x06 и т.д. В таблице приведены двоичные и шестнадцатеричные коды для всех цифр.
Биты у меня в таблице с цифрами располагаются младшим справа, в таблице адресов из документации младший бит стоит слева. Мне привычней располагать младшие биты справа, поэтому я себе таблицу с цифрами рисовала именно так, если вам так сложно воспринимать, то можно её отзеркалить. Хотя в программе, скорей всего, вы всё равно будете писать младший бит слева, по крайней мере, у меня именно так.
Прочитав документацию по диагонали, и ещё пару статей в интернете, я стала подключать модуль TM1638 к Arduino Nano. Для этого мне понадобилось заглянуть в распиновку адруины и выбрать там пины аппаратного SPI. Получились такие: пин 13 — SCK, пин 11 — MOSI, пин 10 — SS (ножек три, так как у нас полудуплекс).
Подключив библиотеку, я написала код передачи данных на плату и он не заработал — завис на первой же библиотечной функции, функции инициализации. Почесав затылок, я решила, что не хочу копаться в непонятной мне библиотеке и решила делать без неё (тем более, что приём через неё всё равно не реализовать из-за полудуплексного подключения). Правда для облегчения своей работы я использовала встроенную функцию shiftOut, но она, слава богу, заработала, хотя я морально уже была готова писать и её.
Ножки при этом я выбрала другие, но на самом деле это абсолютно неважно. Передача команды у меня выглядела так:
void transfer (uint8_t data){
digitalWrite (SS,LOW);
shiftOut (DATA_PIN, CLK, LSBFIRST, data);
digitalWrite (SS,HIGH);
_delay_ms(100);
}
Где SS — это пин, к которому подключен вход STB модуля TM1638, CLK — вход SCLK модуля, DATA_PIN — вход DIO. У меня это были пины 10, 12 и 11 (PB2, PB4 и PB3). Писать функцию digitalWrite через регистры я тоже не стала, ибо в данной ситуации мне это ни к чему. Функция shiftOut осуществляет сдвиг данных, т.е. выдаёт биты по одному на ножку DATA_PIN, сопровождая при этом передачу тактовыми импульсами на ножке CLK. Параметр LSBFIRST указывает, что передача будет осуществляться старшим битом вперёд (да, в документации указано, что передача должна осуществляться именно так).
Выдав с помощью этой функции команду включения и настройки яркости, команду настройки инкремента адреса (в данном случае подходит только запись с фиксированным адресом, т.к. адреса расположены не по порядку), сам адрес и какие-то данные, я ожидала, что всё заработает. Экран зажегся (уже прогресс) и вывел всякий мусор.
Потратив уйму времени на перечитывание документации и статей в интернете, я эту проблему решить не смогла. Хотя делала всё по примеру, но он оказался неполный. Спасибо коллеге, он смог нагуглить, что я делаю не так. Оказалось, что после передачи адреса ячейки не нужно поднимать провод выбора микросхемы (SS) и делать паузу, а нужно сразу следом за адресом передавать данные.
Вообще, это всё описано в документации и даже с картинками, но я до туда не дочитала (как всегда).
Вот, собственно, временные диаграммы передачи данных из документации:
Где:
Command1: установить режим записи с фиксированным адресом
Command2: установить адрес
Data1: Отправка данных по адресу, указанному в Command2
Command3: установить адрес
Data2: отправка данных по адресу, указанному в Command3 и т.д.
Т.е. для отправки команд настройки, нужно сначала опустить в ноль STB, отправить восемь бит команды (не забывая передавать импульсы по CLK), поднять STB (установить логическую единицу) и сделать паузу. Для того, чтобы передать данные, нужно опустить STB, отправить восемь бит адреса, затем восемь бит данных и только потом поднять STB.
Исходя из этой информации, я дописала вторую функцию для передачи данных.
void transferData(uint8_t addr, uint8_t data)
{
digitalWrite(SS,LOW);
shiftOut(DATA_PIN, CLK, LSBFIRST, addr);
shiftOut(DATA_PIN, CLK, LSBFIRST, data);
digitalWrite(SS,HIGH);
_delay_ms(100);
}
В общем, после всех мытарств, оно заработало и вывело красивые циферки.
Для осуществления приёма с кнопок я попробовала использовать функцию shiftIn(). И оно не заработало. Если верить документации, то при отправке запроса на чтение, плата должна вернуть четыре байта друг за другом, в которых биты, соответствующие нажатым кнопкам установлены в единицы. У меня же принимается 32 нуля. Продолжу свои попытки и опишу результат в следующий раз.
Полный код из двадцати строк здесь.
Буду рада, если кому-то это оказалось полезным.
Комментарии (17)
kalapanga
29.04.2024 06:47"Всё
украденонаписано до нас" :)GennPen
29.04.2024 06:47+1Самому учиться программировать интересней. Но ... В этой библиотеке используется стандартная функция shiftOut() в которой по сути используется то же самое ногодрыганье: https://github.com/arduino/ArduinoCore-avr/blob/63092126a406402022f943ac048fa195ed7e944b/cores/arduino/wiring_shift.c#L40
Я не спорю хорошо это или плохо, но не лучше ли сразу учиться использовать нужные интерфейсы правильно?kass_andra_go Автор
29.04.2024 06:47+1Изначально мне нужно было проверить работоспособность купленной платы, но потом я решила воспользоваться ситуацией и детальней разобраться с тем как устроен и работает SPI. Я сейчас пытаюсь реализовать аппаратный spi на fpga, опять же исключительно в целях самообразования. И «ногодрыганье» мне помогло это получше в голове уложить.
Честно говоря, я долго думала, делиться своим «велосипедом» или нет,но если бы не поделилась, то не получила бы столько ценных советов и ссылок
GennPen
29.04.2024 06:47Все нормально. Если знаний в голове прибавилось - значит время потрачено не зря. =)
AVKinc
29.04.2024 06:47+1В этой микросхеме самое интересное, что там есть вариант хитрого подключения когда один байт это не все сегменты одного разряда индикатора, а один сегмент восьми разрядов. И вот там код выходит прикольный.
Я писал полностью свою реализацию отображения на нем. У меня волосы зашевелились на голове когда я прочитал описание адресации сегментов )))))
Yuri0128
29.04.2024 06:47есть вариант хитрого подключения
Так а в чем проблема? Не используйте индикаторы с общим анодом, для индикаторов с общим катодом там все норм. А если вдруг надо - то перекодировать буфер, ортогонально развернув индикацию, - ну несколько минут для написания программы и немного тактов для его обслуживания.
А вот написать полудуплекс для чтения клавиатуры совсестно с выводом индикации на TM1638 при работе на пристойных скоростях SPI (>2 MHz, ну для Ардуино пристойных) - ну задачка уже поинтереснее.
kass_andra_go Автор
29.04.2024 06:47Я когда стала читать на неё описание, не могла никак принять, почему именно так в ней всё странно расположено
Vedga
29.04.2024 06:47Раз уж влезла в программирование мк, не пожалей 7-10т.р. на простенький осциллограф. На пару каналов, хотя бы. Тогда вопросы "почему не работает/работает не так" будут решаться быстрее.
Или, как вариант, макетка FPGA с включённым анализатором сигналов. Особенно если потом в область verilog залезть захочется.
kass_andra_go Автор
29.04.2024 06:47+1У меня есть возможность воспользоваться осциллографом, но почему-то конкретно в этом случае мне такая мысль в голову не пришла
За логический анализатор на FPGA отдельное спасибо! это интересно, обязательно попробую воспользоваться
sim2q
29.04.2024 06:47В подавляющем большинстве случаев для начала достаточно логического анализатора на cy7c68013a за 400руб.
fiego
29.04.2024 06:47Параметр LSBFIRST указывает, что передача будет осуществляться старшим битом вперёд (да, в документации указано, что передача должна осуществляться именно так).
Показания документации на
shiftOut()
расходятся с вашими:MSBFIRST or LSBFIRST. (Most Significant Bit First, or, Least Significant Bit First).
sim2q
29.04.2024 06:47Но вообще-то он не SPI, а I2C без адреса, отправляется на него по I2C нормально, а вот чтение несколько сложнее.
GennPen
Использовать ногодрыганье когда есть аппаратный SPI - такое себе.
Вот тут посмотрите как реализована работа по SPI с этим чипом: https://github.com/i2make/TM1638_using_hardwareSPI/blob/91d131355ccdd123a5a64fbfb2d4ce565ab7c0c8/src/TM16XX.cpp#L141
kass_andra_go Автор
посмотрю, спасибо