Неожиданный отпуск не повод расслабляться, и поэтому надо в срочном порядке сломать что-нибудь нужное и сделать что-нибудь ненужное.
Под катом немного электроники и совсем чуть-чуть ассемблера, специально для любителей ненормального железячного программирования.
«Hello, world!» — программа, результатом работы которой является вывод на экран или иное устройство фразы «Hello, world!» (с) Wiki
А устройством у нас будет передняя панель от древней, как копыто мамонта, автомагнитолы Китаенвуд, которая (не магнитола, а панель) бережно хранилась в закромах Родины как раз для
такого случая.
Вскрываем девайс и видим небольшой дисплейчик, а также кучу кнопок и симпатичных двухцветных слеподиодов.
С другой стороны платы обнаружен драйвер неизвестного устройства LCD-дисплея с возможностью опроса клавиатуры LC75883.
Управляется драйвер с помощью SPI-подобного интерфейса CCB фирмы Sanyo, подробно описанного в даташите на микросхему, что позволяет легко решить поставленную задачу.
Интерфейс имеет 4 линии:
CL — тактирование;
CE — разрешение работы микросхемы;
DI — запись адреса/данных;
DO — чтение данных.
Также имеется вход сброса RES, который должен быть в логической 1 во время работы устройства.
Запись данных в драйвер происходит следующим образом:
Традиционно, для записи бита его значение устанавливается на линии DI и вырабатывается тактовый импульс по линии CL. В CCB данные идут начиная с младшего значащего бита LSB. При низком уровне CE происходит запись адреса (для режима записи данных 0x42). Далее устанавливается логическая 1 по линии CE и записываются данные. Запись данных происходит тремя блоками по 72 бита (9 байт) каждый. Каждый блок оканчивается битами DD, определяющими номер блока и принимающими значение 00, 01 или 10. Перед передачей каждого блока данных требуется запись адреса.
Блоки данных содержат:
- данные для отображения на дисплее;
- биты, управляющие спящим режимом;
- биты определения режима работы (сканирование клавиатуры/сегменты LCD и сегменты LCD/обычные выводы) для некоторых выводов;
- биты управления состоянием дисплея (включен/выключен);
- биты определения формы импульсов для LCD.
Чтение данных происходит аналогично:
Для чтения надо записать в драйвер адрес 0x43 и считать 32 бита (4 байта) данных по линии DO. Считываются биты состояния кнопок и бит спящего режима.
Более подробные подробности интересующиеся лица могут посмотреть в даташите на микросхему.
Как мы видим, протокол обмена известен, но очень не хватает данных о соответствии элементов LCD дисплея соответствующим выводам драйвера. Определять их будем экспериментально, брутфорсом :)
Неоценимую помощь в определении некоторых параметров конфигурации драйвера (режим работы выводов, отвечающих за сканирование клавиатуры, управление сегментами LCD или являющимися обычными выводами) может дать сервис-мануал на магнитолу, который оказался в интернете.
Но я немного стормозил и определил эту схему обычной прозвонкой, и лишь потом сверился с сервис-мануалом. Зато из мануала стало ясно, что для подсветки (лампы накаливания где-то под LCD) и светодиодов требуется питание +12В.
Таким образом для запуска панельки потребуются: RES, CL, CE, DI, DO(опционально), +5В и DGND (цифровой общий) для логики, +12В и GND (общий) для иллюминации.
Испытывать панельку будем, подключив ее к [ардуино] самопальному PICKit-подобному изделию студенческих времен, именуемом в дальнейшем «девайс».
Девайс имеет в своем составе: PIC16F877A в DIP-панельке. Панелька позволяет использовать практически любой PIC в 40-выводном корпусе, чего за глаза хватит для любого прототипирования и отладки программы в железе. Так же есть 6 кнопок + 1 резет, 4 7-сегментных LED (точнее 2 14-сегментных), MAX220 для связи с компом (и даже программирования радиостанций), панелька под память I2C. Наплатная периферия подключается либо джамперами к фиксированным лапам контроллера, либо проволочными перемычками через контактные площадки к любым выводам. Кварцы сменные для изменения тактовой частоты. Внешние соединения также втыкаются в разъемчики от советских телевизоров :). Питание +5V можно брать прямо с платы. Программируется микроконтроллер с помощью разъема ICSP, куда подключается самодельный программатор ICD2.
Программа для управления панелькой написана на pure MPASM с ассемблерными вставками.
Интерфейс CCB реализуется программно. Прежде всего по даташиту на драйвер определяем необходимые тайминги. Производителем заявлено, что необходимые длительности импульсов, нарастаний и спадов фронтов, промежутки между импульсами должны составлять минимум 160 нс. В сумме на каждый бит потребуется ~0.5 мкс. Время исполнения инструкции микроконтроллером при кварце 4 МГц составит 1 мкс. Таким образом можно реализовывать обмен данными с драйвером в одном цикле, без задержек по таймеру и т.п. Но на всякий случай, для обеспечения стабильности работы, между инструкциями выдачи сигналов в порты вывода, будем добавлять пару-тройку nop (пустых инструкций).
В основном цикле программы вызываются задача вывода данных на LED индикаторы и сканирования кнопок на девайсе, задачи отправки и получения данных на драйвер LCD. Выполнение задачи происходит, если установлен флаг ее разрешения. Флаги устанавливаются либо по таймеру (скан кнопок и динамическая индикация), либо по событию (запись/чтение данных на драйвер). Таким образом, имеется некое подобие RTOS, что с положительной стороны зарекомендовало себя в проектах сложнее чем «помигать светодиодом».
MAINLOOP nop
btfsc LEDE ;Check LED out & keyscan
call LEDIO
btfsc WDI
call LCDWR ;Write Subprogram
btfsc RDO
call LCDRD ;Read Subprogram
goto MAINLOOP
Прерывание по таймеру происходит с частотой 200 Гц, с этой частотой устанавливается флаг задачи динамической индикации. Т.к. имеется 4 знакоместа, то вывод кажой цифры производится с частотой 50 Гц, что комфортно для глаз оператора :) Опрос кнопок происходит также в задаче динамической индикации, но в 50 раз реже. Для устранения явления «дребезга контактов», кнопка считается нажатой, если два цикла опроса подряд соответствующий бит порта был в значении «1». Т.о. опрос кнопок происходит с частотой 2 Гц.
Непосредственно обработка прерывания:
;[Timer0]:
btfss INTCON, TMR0IF
goto ENDISR
bcf INTCON, TMR0IF
movf T0SET, W
addwf TMR0, F ;Correct timer
;Function:
bsf LEDE ;Enable LED output (200 Hz)
decfsz IOCNT, F ;IOCNT--
goto $+4
movlw CNTIO
movwf IOCNT
bsf KSE ;Enable keyscan (2 Hz)
;Конец обработки где-то здесь
Схемотехнически матрица кнопок строками (допустим) на девайсе подключается к общим анодам 7-сегментых индикаторов, поэтому импульсы сканирования выдаются на соответствующую пару кнопок, когда зажжен один из индикаторов. При нажатии кнопки (или двух сразу) на соответствующих столбцах матрицы, и, следовательно, на портах, сконфигурированных как входы от кнопок, появляются логические «1». Диоды используются, чтобы одновременно можно было нажимать хоть все 6 кнопок, и это не нарушало бы динамическую индикацию. Такое техническое решение позволяет опрашивать любое число нажатых кнопок без ошибок интерпретации результата.
Динамическая индикация (для одной цифры):
LEDIO nop
bcf LEDE
movf LEDCNT, W
addwf PCL, F
nop
goto LEDIO4
goto LEDIO3
goto LEDIO2
goto LEDIO1
;LED1
LEDIO1 nop
bcf LED4
movf ADDRED, W
andlw b'11110000'
movwf TEMP
swapf TEMP, W
call HEXTOLCD
bcf PCLATH, 1 ;Prev 256 byte block
movwf LEDS
bsf LED1
btfsc KSE ;Check KeyScanEnabled
call KEYSCAN12
goto LEDEND
;
; Здесь почти то же самое, но для других цифр
;
LEDEND nop
decfsz LEDCNT, F ;LEDCNT--
goto $+3 ;If not Zero
movlw CNTLED
movwf LEDCNT
return
Скан кнопок и полезная функция по нажатию (для одной пары):
KEYSCAN12 nop
;bcf KSE
nop
nop
nop
;Scan SB1
SB1SCAN nop
btfss SB135
goto SB1N
btfsc SB1o
bsf SB1p
btfss SB1o
bsf SB1o
goto SB2SCAN
SB1N bcf SB1o
;Scan SB2
SB2SCAN nop
btfss SB246
goto SB2N
btfsc SB2o
bsf SB2p
btfss SB2o
bsf SB2o
goto KSCEND12
SB2N bcf SB2o
KSCEND12 nop
;Set WDI&RDO
btfsc SB1p
bcf SB1o
btfsc SB2p
bcf SB2o
btfsc SB1p
bsf WDI
btfsc SB2p
bsf RDO
bcf SB1p
bcf SB2p
return
На LED индикаторы выводятся в шестнадцатиричном виде:
1. адрес ячейки памяти, содержащий данные для передачи драйверу LCD;
2. содержимое этой ячейки.
Преобразование шестнадцатиричного числа в формат, пригодный для отображения на 7-сегментном индикаторе производится путем выделения старших (или младших) 4 бит и чтении из таблицы байта состояния сегментов индикатора.
6 кнопок позволяют осуществить следующее:
1. устанваливать флаг разрешения записи данных в драйвер LCD;
2. устанваливать флаг разрешения чтения данных из драйвера LCD;
3,4. изменять (инкремент и декремент) адрес показываемой на LED ячейки памяти
5,6. Изменять содержимое текущей ячейки памяти путем сдвига содержимого ячейки влево и установки младшего бита в «1» или «0».
При инициализации, все биты данных, передаваемые на драйвер (кроме битов управления), устанавливаются в значение «1». Биты управления устанавливаются в соответствии с даташитом так, чтобы драйвер начал функционировать в обычном режиме.
Запись данных в драйвер:
LCDWR nop ;Write
movlw DATAWR1
movwf FSR ;FSR = Addr of data
;Write address
LCDWWA nop
movlw CNT8
movwf RWBCOUNT ;RWBCOUNT = CNT8
movf ADDRWRITE, W ;Addr write
movwf RWBUF
;Write address byte
LCDWWAB nop
;CE
nop
btfsc CE
bcf CE ;CE = 0
;CL
nop
bcf CL ;CL = 0
;DI
nop
btfss RWBUF, 7
bcf DI ;DI = 0
btfsc RWBUF, 7
bsf DI ;DI = 1
;CL
nop
nop
nop
bsf CL ;CL = 1
;Shift
rlf RWBUF, F ;Shift buffer
decfsz RWBCOUNT, F ;Counter--
goto LCDWWAB ;If not Zero then next bit
;Write data
LCDWWD nop
movlw CNT8
movwf RWBCOUNT ;RWBCOUNT = CNT8
movf INDF, W
movwf RWBUF
;Write data byte
LCDWWDB nop
;CL
nop
bcf CL ;CL = 0
;CE
nop
btfss CE
bsf CE ;CE = 1
;DI
nop
btfss RWBUF, 7
bcf DI ;DI = 0
btfsc RWBUF, 7
bsf DI ;DI = 1
;CL
nop
nop
nop
bsf CL ;CL = 1
rlf RWBUF, F ;Shift buffer
decfsz RWBCOUNT, F ;Counter--
goto LCDWWDB ;If not Zero then next bit
incf FSR, F ;FSR++
movlw DATAWR2
xorwf FSR, W
btfsc STATUS, Z
goto LCDWWA ;Write address
movlw DATAWR3
xorwf FSR, W
btfsc STATUS, Z
goto LCDWWA ;Write address
movlw DATAWR3 + 9 ;Test End on Write
xorwf FSR, W
btfss STATUS, Z
goto LCDWWD ;Write data
LCDWREND nop
bcf CL
nop
bcf CE
nop
bcf DI
nop
bcf WDI
return
Перед заливкой в железо проверяем работу в протеусе и сравниваем полученные осциллограммы с желаемыми (в даташите).
CL — желтый;
CE — синий;
DI — красный.
Запись адреса и 1 байта данных:
Один цикл записи (9 байт):
Вся процедура записи данных (27 байт):
Чтение из драйвера происходит практически аналогично, поэтому этот момент оставим на рассмотрение читателя.
Заливаем прошивку в девайс, подсоединяем пациента и производим первое включение:
Жмем кнопку записи в драйвер, все элементы LCD и светодиоды зажглись. Работает, зараза :)
Обнулим случайным образом биты в некоторых ячейках памяти, соответствующие (но пока неизвестно какие) сегменты погасли.
Теперь последовательно, как марксисты, будем обнулять по биту в каждой ячейке, записывать результат в драйвер, записывать результат на бумажку. 27 байт * 8 бит * 2 нажатия = 432 нажатия. Через ~40 минут трудов (+ время для худжественных работ по изображению дисплея на бумаге) имеем искомую таблицу соответствия:
Теперь можно составить и долгожданную фразу «Hello, world!». Посокольку годных знакомест всего 8, ограничимся фразой покороче «HI WORLd». Закодируем в двоичном виде, кнопками (мы, комсомольцы, не можем без трудностей) забьем биты в ячейки памяти, и…
Для интересующихся, полный код прошивки здесь.
P.S. Между прочим, вмешавшись в обмен между самой магнитолой и панелькой и посылая свои команды, можно перехватывать нажатия кнопок и выводить на дисплей что-нибудь интересное. Но это никоим образом не призыв ломать работающее оборудование. Действуйте на свой страх и риск, да поможет вам св. Януарий :)
Автор: Int_13h