Pull to refresh

Реверс-инжиниринг первых умных часов Seiko UC-2000

Reading time 32 min
Views 31K


Где-то в конце 1983 — начале 84 года, японская компания Seiko начала продавать первые в истории компьютеризированные часы — Seiko Data-2000 и Seiko UC-2000. Data-2000 имели возможность хранить 2КБ заметок, их нужно было вводить с помощью специальной компактной клавиатуры, которая шла в комплекте. UC-2000, по сути, те же Data-2000 с корпусом другого цвета, но они уже позиционировались как часть Наручной Информационной Системы, которая, среди прочего, включала терминал UC-2200, представляющий из себя компьютер с Z80-совместимым процессором, интерпретатором Бэйсика и термопринтером, но без экрана, в качестве которого использовались часы (как это не странно). Среди прочего, терминал давал возможность загружать на часы приложения со специальных картриджей. Подробнее о линейке ранних умных часов Seiko можно почитать, например, в этой статье. В этом же посте я расскажу, как написал (возможно) первую, за более чем 33 года, программу для этих часов.


Заметка о часах в журнале Popular Science 84 года, на левой нижней фотографии представлена вся периферия.

Небольшое введение


О UC-2000 я узнал пару лет назад, примерно тогда же оставил такой комментарий на GT. С тех пор меня не покидала мысль купить часы и попытаться для них что-то написать. Изначально я склонялся к покупке Epson RC-20, но они настолько редкие, что за это время я видел только одно предложение на Ebay которое по глупости упустил. В итоге, мне надоело ждать и я купил гораздо более доступные UC-2000, без терминала и клавиатуры, которые для моих целей были не нужны. На самом деле, конечно, мне пришлось бы покупать док-станцию, для того чтобы снять дамп ПЗУ из картриджа с приложениями и понять работу беспроводного интерфейса который часы использовали для связи с периферией. Но, к моему счастью, эту работу сделал один человек еще в 2002 году, у него был проект по созданию собственных электронных часов, а у Seiko он хотел позаимствовать экран. Не знаю чем закончилась его работа, но то, что опубликовано у него на сайте, мне очень помогло. Этот человек (к сожалению, не знаю его имени) опубликовал приложения с комплектного картриджа, описал протокол беспроводного индуктивного интерфейса, а также написал приложение, во многом реализующее функционал терминала.

Итак, имея эти вводные, я выписал часы и приступил к поиску информации. Тут надо уточнить, что я не силен ни в ассемблере (за всю жизнь до этого написал на нем, может быть, полторы программы) ни, тем более, в электронике, но как обычно бывает, отсутствие знаний компенсировалось большим желанием. Из-за этого, мои методы и терминология могут показаться странными или вовсе быть ошибочными, надеюсь, в комментариях вы меня поправите.

Реверс-инжиниринг


Что же было известно об архитектуре этих часов?
Вот на этом рекламном постере, пожалуй, отображена вся доступная техническая информация:


4bit CPU, 6KB ROM, 1.5KB LCD ROM, 2KB RAM.
И все.

Фотографии внутренностей не дают какой-то дополнительной полезной информации — все микросхемы ожидаемо бескорпусные без какой-либо маркировки:






Сверху вниз, слева направо: катушка индуктивности используемая для беспроводного интерфейса и отсек для батареи; печатная плата с процессорным модулем на керамической подложке — 2 микросхемы, ЦПУ и ОЗУ; матричная ЖК панель — 4 строки по 10 символов, 5x7 точек каждый; обратная сторона платы — 4 микросхемы контроллера дисплея.

Ничего не остается, как попытаться вручную дизассемблировать программы со штатного картриджа, всего их 5:

  • Amida — игра, японский лабиринт;
  • Сard — карточная игра;
  • Hit — игра, отстреливаемся от врагов;
  • Race — симулятор тотализатора;
  • Scheduler — ежедневник, единственное практичное приложение.

Судя по рекламе, был еще картридж, с Японско-Английским словарем, но видимо продавался он только на японском рынке, поэтому найти его не легко.

Смотрим hex-дамп одного из приложений, например игры HIT
00000000 CC 0F CC F5 CD 4A C1 09 C0 E0 CC 18 CC 22 CC 7C М.МхНJБ.АаМ.М"М|
00000010 CC CB C2 A9 C3 07 C3 9C C0 9B CD AE B0 00 45 78 МЛВГ.ГњА›Н°.Ex
00000020 45 5E 45 22 3C 10 A8 F2 88 82 3C 18 A8 F2 C2 27 E^E"<.Ёт€‚<.ЁтВ'
00000030 57 48 43 4E D4 21 3C 10 E1 22 45 22 CC 21 AE 89 WHCNФ!<.б"E"М!‰
00000040 AE 55 C2 43 57 48 43 56 D4 28 3C 10 FA 02 AE 49 UВCWHCVФ(<.ъ.I
00000050 C2 83 3C 18 8B 20 8B 0C 36 19 D0 3A 8B 22 8B 00 Вѓ<.‹ ‹.6.Р:‹"‹.
00000060 36 19 D0 3A 8B 14 36 19 D0 3A 8B 24 8B 08 36 19 6.Р:‹.6.Р:‹$‹.6.
00000070 D0 3A CC 4B 8B 20 8B 06 22 19 3E 10 9B 0E 9B 29 Р:МK‹ ‹.".>.›.›)
00000080 E7 21 CC 4A 3A 0E D4 4A 4E 11 EB 21 CC 4A 3B 0C з!МJ:.ФJN.л!МJ;.
00000090 D4 4A 4E 11 CC 4C 4E 11 3E 10 9B 2F 9B 0E 36 19 ФJN.МLN.>.›/›.6.
000000A0 D4 5D 5C 8F AE 55 3C 10 8A 08 AE 1D D8 59 8B 00 Ф]\ЏU<.Љ..ШY‹.
000000B0 CC 5B 8A 02 AE 46 AE 4C CC 28 83 23 83 02 AF F0 М[Љ.FLМ(ѓ#ѓ.Їр
000000C0 80 79 80 58 83 2B 83 0A AF F0 81 79 81 58 83 33 ЂyЂXѓ+ѓ.ЇрЃyЃXѓ3
000000D0 83 12 AF F0 82 79 82 58 83 21 83 00 AF F0 80 39 ѓ.Їр‚y‚Xѓ!ѓ.ЇрЂ9
000000E0 80 18 3C 10 B6 01 CC 5C AE 1D D8 78 8B 00 CC 7A Ђ.<.¶.М\.Шx‹.Мz
000000F0 8A 02 AE 46 AE 4C CC 5C 57 48 43 58 D4 85 3C 10 Љ.FLМ\WHCXФ…<.
00000100 F2 06 F6 0D FA 1C FE 02 AE 49 C2 DF CC BD 3C 00 т.ц.ъ.ю.IВЯМЅ<.
00000110 46 E8 3C 10 8A 02 AE 55 AE 46 AE 4C CC 85 8A 04 Fи<.Љ.UFLМ…Љ.
00000120 79 86 7C 10 3C 18 8C C7 8D C7 8E C7 8F C7 8C 85 y†|.<.ЊЗЌЗЋЗЏЗЊ…
00000130 AE 22 AE 55 A7 FD A7 F2 AE 46 AE 4C CC 85 3C 18 "U§э§тFLМ…<.
00000140 FA 24 8B 20 8B 14 22 19 CC A8 8B 22 8B 1C 2A 19 ъ$‹ ‹.".МЁ‹"‹.*.
00000150 3E 10 9B 35 9B 14 36 19 D0 B1 9B 2F 9B 0E 36 19 >.›5›.6.Р±›/›.6.
00000160 D4 BC 5C 8F AE 55 3C 10 8A 08 AE 1D D8 B9 8B 00 Фј\ЏU<.Љ..Ш№‹.
00000170 CC BB 8A 02 AE 46 AE 4C CC 85 A8 F2 88 82 8A 02 М»Љ.FLМ…Ёт€‚Љ.
00000180 5C 8F 45 22 3C 18 A8 F2 AE 22 3C 08 45 68 AE 55 \ЏE"<.Ёт"<.EhU
00000190 AE 46 AE 4C CC 85 57 48 43 58 D4 D1 3C 10 FA 02 FLМ…WHCXФС<.ъ.
000001A0 AE 49 C3 83 3C 18 80 BF 80 9E 87 D7 86 CF 85 C7 IГѓ<.ЂїЂћ‡Ч†П…З
000001B0 87 11 4F 11 80 F9 80 D8 82 A7 82 86 81 A3 81 82 ‡.O.ЂщЂШ‚§‚†ЃЈЃ‚
000001C0 AF B6 80 6D 80 4C 81 AB 81 8A AF B6 81 6D 81 4C Ї¶ЂmЂLЃ«ЃЉЇ¶ЃmЃL
000001D0 81 B3 81 92 AF B6 82 6D 82 4C 81 A1 81 80 AF B6 ЃіЃ’Ї¶‚m‚LЃЎЃЂЇ¶
000001E0 80 2D 80 0C 80 F5 80 D4 CC D1 3C 00 F5 81 CC FC Ђ-Ђ.ЂхЂФМС<.хЃМь
000001F0 3C 10 FA 09 3A 0A D0 FD C1 90 5C 8F 4F A5 FB A1 <.ъ.:.РэБђ\ЏOҐыЎ
00000200 CD 02 8A 06 CC FC 4C 5C 8F 44 34 5C D5 0F 8A 0A Н.Љ.МьL\ЏD4\Х.Љ.
00000210 8F A5 5C 8F 8E 21 41 32 AE 4C AE 5A CD 48 12 22 ЏҐ\ЏЋ!A2LZНH."
00000220 FA 21 CD 13 5C 8F E9 2B E5 21 CD 48 3C 18 8B 24 ъ!Н.\Џй+е!НH<.‹$
00000230 8B 0E 36 19 D1 1E 3C 10 8A A4 8A 8E CD 48 3E 18 ‹.6.С.<.Љ¤ЉЋНH>.
00000240 9A F1 9A D0 8B E0 8B CE 35 DF D1 2E 8B E2 8B C2 љсљР‹а‹О5ЯС.‹в‹В
00000250 35 DF D1 34 8B D6 35 DF D1 3A CD 40 8B E4 8B CC 5ЯС4‹Ц5ЯС:Н@‹д‹М
00000260 36 DF D1 48 85 DF CD 48 8B E2 8B C4 36 DF D1 48 6ЯСH…ЯНH‹в‹Д6ЯСH
00000270 85 DF CD 48 8B E2 8B D8 36 DF D1 48 85 DF CD 48 …ЯНH‹в‹Ш6ЯСH…ЯНH
00000280 87 CF 88 E0 88 D6 2B C7 36 DF D1 47 29 C7 8C C7 ‡П€а€Ц+З6ЯСG)ЗЊЗ
00000290 AE 46 CC FC 3C 10 FA 0A B6 01 C1 1A 4F 08 D9 53 FМь<.ъ.¶.Б.O.ЩS
000002A0 4C 5C AE 4F CD 54 8A 02 AE 46 CD 4D AE 46 AE 4F L\OНTЉ.FНMFO
000002B0 3C 10 8C C7 10 02 FC 01 CD 60 8C 00 3E 00 9B 0D <.ЊЗ..ь.Н`Њ.>.›.
000002C0 ED 38 45 30 10 22 B4 21 CD 66 8C 21 01 18 11 02 н8E0."ґ!НfЊ!....
000002D0 B9 00 CD 7E CD 81 CD 84 CD 87 CD 8A CD 8D CD 90 №.Н~НЃН„Н‡НЉНЌНђ
000002E0 CD 93 CD 96 CD 99 CD 9C CD 9F CD A2 CD A5 CD A8 Н“Н–ННњНџНўНҐНЁ
000002F0 CD AB 41 2E AE 97 CD 7C AF 13 CD 4D AE E2 AE EE Н«A.—Н|Ї.НMво
00000300 CD 7C AE EE AE CA CD 7C AE EE AE E2 CD 7C AE E2 Н|оКН|овН|в
00000310 AE CA CD 7C AE E2 AE CA CD 7C AE EE AE CA CD 7C КН|вКН|оКН|
00000320 AE E2 AE EE CD 7C AE EE AE CA CD 7C AE E2 AE EE воН|оКН|во
00000330 CD 7C AE E2 AE EE CD 7C AE E2 AE CA CD 7C AE E2 Н|воН|вКН|в
00000340 AE CA CD 7C AE E2 AE EE CD 7C AE EE AE CA CD 7C КН|воН|оКН|
00000350 AE E2 AE EE CD 7C AE E2 AE CA CD 7C 3C 00 F1 81 воН|вКН|<.сЃ
00000360 CD B4 3C 10 AE 89 CD BC 3C 10 BA 00 CD BD CD C4 Нґ<.‰Нј<.є.НЅНД
00000370 CD DA CD F3 CE 08 CD F3 C9 8F 78 23 7D 30 7D 31 НЪНуО.НуЙЏx#}0}1
00000380 7D 54 7C 90 AE 0D CD BC 78 00 AE 18 78 07 AE 73 }T|ђ.Нјx..x.s
00000390 AE 12 78 41 AE 7E AB D7 AB D7 7C 90 7D 52 7D 15 .xA~«Ч«Ч|ђ}R}.
000003A0 7D 11 7D 14 7D 71 7C 90 7C 90 7C F7 79 86 7C 11 }.}.}q|ђ|ђ|чy†|.
000003B0 A7 EF CD BC AE 2D 3C 18 AE 5F AE 52 AE 3A AE 6A §пНј-<._R:j
000003C0 AE 3F 3C 10 78 00 AE 73 8B 22 7F F7 78 22 7F F7 ?<.x.s‹".чx".ч
000003D0 7F F7 7F F7 7F F7 FB 23 8B 24 78 44 CD E7 78 66 .ч.ч.чы#‹$xDНзxf
000003E0 AE 7E 7F F7 CD BC 78 00 AE 0D 78 06 7D 37 7D 56 ~.чНјx..x.}7}V
000003F0 7D 15 7D 52 AB D7 AB D7 7D 71 7D 37 7D 55 7D 52 }.}R«Ч«Ч}q}7}U}R
00000400 7C 90 AE 12 78 84 AE 7E 79 84 AB E3 A7 EF CD BC |ђ.x„~y„«г§пНј
00000410 78 23 AE 18 78 40 AE 73 CD BC 7D 17 7D 11 7D 35 x#.x@sНј}.}.}5
00000420 7D 15 B0 00 7D 50 7D 37 7D 31 7D 36 7D 54 B0 00 }.°.}P}7}1}6}T°.
00000430 7D 54 7D 31 7D 35 7D 15 B0 00 8B 80 8B 62 8B 42 }T}1}5}.°.‹Ђ‹b‹B
00000440 34 5C B0 00 88 62 88 5C 89 62 89 5C 8A 62 8A 5C 4\°.€b€\‰b‰\ЉbЉ\
00000450 88 22 88 1C 8A 20 8A 12 B0 00 78 04 A0 AB A0 AB €"€.Љ Љ.°.x. « «
00000460 78 26 A0 AB A0 AB 78 60 A0 AB A0 AB 78 82 A0 AB x& « «x` « «x‚ «
00000470 A0 AB B0 00 3C 10 60 D7 7C B2 3C 18 B0 00 3C 10  «°.<.`Ч|І<.°.<.
00000480 62 95 7D F3 61 D7 7E 74 3C 18 B0 00 3C 00 46 62 b•}уaЧ~t<.°.<.Fb
00000490 B0 00 3C 00 42 7C B0 00 3C 00 45 F0 B0 00 3C 00 °.<.B|°.<.Eр°.<.
000004A0 46 70 B0 00 62 11 7F F5 B0 00 3C 00 EE A1 CE 59 Fp°.b..х°.<.оЎОY
000004B0 5C 4F B0 00 3C 00 EE A1 CE 5E 5D 0F B0 00 60 D7 \O°.<.оЎО^].°.`Ч
000004C0 7F F2 61 D7 7F F2 62 D7 7F F2 63 D7 7F F2 60 95 .тaЧ.тbЧ.тcЧ.т`•
000004D0 7F F2 B0 00 60 53 7C F6 61 53 7C F6 62 53 7C F6 .т°.`S|цaS|цbS|ц
000004E0 60 11 7C F6 B0 00 F0 81 CE 7A 7C 90 F0 61 CE 7B `.|ц°.рЃОz|ђрaО{
000004F0 7C 90 CE 7C 6C 80 6C 60 6C 40 B0 00 F1 81 CE 85 |ђО|lЂl`l@°.сЃО…
00000500 7C 90 F1 61 CE 86 7C 90 CE 87 6D 80 6D 60 6D 40 |ђсaО†|ђО‡mЂm`m@
00000510 B0 00 8C C7 8D C7 8E 85 5C 8F 41 30 A7 EF AE 46 °.ЊЗЌЗЋ…\ЏA0§пF
00000520 AE 4C 45 E8 3C 08 41 60 41 40 41 20 B0 00 3C 18 LEи<.A`A@A °.<.
00000530 82 A7 82 86 AE B2 80 F5 80 D4 82 AF 82 8E AE B2 ‚§‚†ІЂхЂФ‚Ї‚ЋІ
00000540 81 F5 81 D4 82 B7 82 96 AE B2 82 F5 82 D4 82 BF ЃхЃФ‚·‚–І‚х‚Ф‚ї
00000550 82 9E AE B2 83 F5 83 D4 82 A5 82 84 AE B2 80 B5 ‚ћІѓхѓФ‚Ґ‚„ІЂµ
00000560 80 94 B0 00 8B A0 8B 80 36 9D D2 C8 8B A0 8B 88 Ђ”°.‹ ‹Ђ6ќТИ‹ ‹€
00000570 36 9D D2 C8 8B A0 8B 9C 36 9D D2 C8 8B A2 8B 90 6ќТИ‹ ‹њ6ќТИ‹ў‹ђ
00000580 36 9D D2 C8 8B A4 8B 84 36 9D D2 C8 4E 95 CE C9 6ќТИ‹¤‹„6ќТИN•ОЙ
00000590 8E 85 B0 00 3C 18 3E 10 80 33 80 12 86 4B 85 43 Ћ…°.<.>.Ђ3Ђ.†K…C
000005A0 9B 01 BB 00 CE D6 CE D9 CE DC CE DF 88 60 88 48 ›.».ОЦОЩОЬОЯ€`€H
000005B0 CE E1 88 60 88 5C CE E1 88 62 88 50 CE E1 88 64 Об€`€\Об€b€PОб€d
000005C0 88 44 B0 00 3C 18 81 A3 81 82 AE FA 80 6D 80 4C €D°.<.ЃЈЃ‚ъЂmЂL
000005D0 81 B3 81 92 AE FA 82 6D 82 4C B0 00 3C 18 81 AB ЃіЃ’ъ‚m‚L°.<.Ѓ«
000005E0 81 8A AE FA 81 6D 81 4C 81 A1 81 80 AE FA 80 2D ЃЉъЃmЃLЃЎЃЂъЂ-
000005F0 80 0C B0 00 8B A2 8B 9C 35 9D D3 10 8B A0 8B 92 Ђ.°.‹ў‹њ5ќУ.‹ ‹’
00000600 35 9D D3 10 8B A2 8B 86 35 9D D3 10 8B A2 8B 9A 5ќУ.‹ў‹†5ќУ.‹ў‹љ
00000610 35 9D D3 10 8B A4 8B 8E 35 9D D3 10 4D 85 CF 12 5ќУ.‹¤‹Ћ5ќУ.M…П.
00000620 89 A2 89 9C B0 00 3C 18 81 A3 81 82 82 A7 82 86 ‰ў‰њ°.<.ЃЈЃ‚‚§‚†
00000630 AF B6 80 F5 80 D4 82 AF 82 8E AF B6 81 F5 81 D4 Ї¶ЂхЂФ‚Ї‚ЋЇ¶ЃхЃФ
00000640 82 B7 82 96 AF B6 82 F5 82 D4 82 BF 82 9E AF B6 ‚·‚–Ї¶‚х‚Ф‚ї‚ћЇ¶
00000650 83 F5 83 D4 82 A5 82 84 AF B6 80 B5 80 94 80 6D ѓхѓФ‚Ґ‚„Ї¶ЂµЂ”Ђm
00000660 80 4C 83 23 83 02 AF F0 80 79 80 58 3C 10 B6 02 ЂLѓ#ѓ.ЇрЂyЂX<.¶.
00000670 3C 18 CF 3B CF AF 81 AB 81 8A 82 A7 82 86 AF B6 <.П;ПЇЃ«ЃЉ‚§‚†Ї¶
00000680 80 F5 80 D4 82 AF 82 8E AF B6 81 F5 81 D4 82 B7 ЂхЂФ‚Ї‚ЋЇ¶ЃхЃФ‚·
00000690 82 96 AF B6 82 F5 82 D4 82 BF 82 9E AF B6 83 F5 ‚–Ї¶‚х‚Ф‚ї‚ћЇ¶ѓх
000006A0 83 D4 82 A5 82 84 AF B6 80 B5 80 94 81 6D 81 4C ѓФ‚Ґ‚„Ї¶ЂµЂ”ЃmЃL
000006B0 83 2B 83 0A AF F0 81 79 81 58 3C 10 B6 02 3C 18 ѓ+ѓ.ЇрЃyЃX<.¶.<.
000006C0 CF 62 CF AF 81 B3 81 92 82 A7 82 86 AF B6 80 F5 ПbПЇЃіЃ’‚§‚†Ї¶Ђх
000006D0 80 D4 82 AF 82 8E AF B6 81 F5 81 D4 82 B7 82 96 ЂФ‚Ї‚ЋЇ¶ЃхЃФ‚·‚–
000006E0 AF B6 82 F5 82 D4 82 BF 82 9E AF B6 83 F5 83 D4 Ї¶‚х‚Ф‚ї‚ћЇ¶ѓхѓФ
000006F0 82 A5 82 84 AF B6 80 B5 80 94 82 6D 82 4C 83 33 ‚Ґ‚„Ї¶ЂµЂ”‚m‚Lѓ3
00000700 83 12 AF F0 82 79 82 58 3C 10 B6 02 3C 18 CF 89 ѓ.Їр‚y‚X<.¶.<.П‰
00000710 CF AF 81 A1 81 80 82 A7 82 86 AF B6 80 F5 80 D4 ПЇЃЎЃЂ‚§‚†Ї¶ЂхЂФ
00000720 82 AF 82 8E AF B6 81 F5 81 D4 82 B7 82 96 AF B6 ‚Ї‚ЋЇ¶ЃхЃФ‚·‚–Ї¶
00000730 82 F5 82 D4 82 BF 82 9E AF B6 83 F5 83 D4 82 A5 ‚х‚Ф‚ї‚ћЇ¶ѓхѓФ‚Ґ
00000740 82 84 AF B6 80 B5 80 94 80 2D 80 0C 83 21 83 00 ‚„Ї¶ЂµЂ”Ђ-Ђ.ѓ!ѓ.
00000750 AF F0 80 39 80 18 3C 10 B6 02 3C 18 CF B5 AE 1D ЇрЂ9Ђ.<.¶.<.Пµ.
00000760 DB B3 8B 00 CF B4 8A 02 AE 4C B0 00 35 95 D7 EE Ыі‹.ПґЉ.L°.5•Чо
00000770 3E 10 90 ED 90 CC 89 A2 89 9C 8E 85 3C 00 EE A1 >.ђнђМ‰ў‰њЋ…<.оЎ
00000780 CF C2 5C 4F CF C3 3C 10 4D 4C 8B 80 8B 6A 8B 40 ПВ\OПГ<.ML‹Ђ‹j‹@
00000790 35 5C DB EE D7 D5 8B 66 24 5C 45 24 8E 21 3C 00 5\ЫоЧХ‹f$\E$Ћ!<.
000007A0 EE A1 CF D3 5D 0F CF D4 CF EE 8B 82 8B 60 35 5C оЎПУ].ПФПо‹‚‹`5\
000007B0 D7 EE 8B 80 8B 66 24 5C 45 28 8E 21 3C 00 EE A1 Чо‹Ђ‹f$\E(Ћ!<.оЎ
000007C0 CF E2 5D 0F CF E3 3C 18 8B 24 8B 0C 36 19 3C 10 Пв].Пг<.‹$‹.6.<.
000007D0 D3 EC 89 E4 89 CC CF EE 89 E2 89 D6 3C 18 B0 00 Ум‰д‰МПо‰в‰Ц<.°.
000007E0 36 19 D7 FD 8B 22 8B 1C 3C 00 EE A1 CF F8 5C 4F 6.Чэ‹"‹.<.оЎПш\O
000007F0 CF F9 3C 10 8A 08 5C 8F 8E 21 3C 18 B0 00 03 00 Пщ<.Љ.\ЏЋ!<.°...


Если бы я раньше программировал контроллеры AVR на ассемблере, я бы сразу принял первые 14 пар байт за таблицу векторов прерываний, но я не программировал AVR на ассемблере, поэтому посчитал это просто неким набором вызовов подпрограмм, т.е. предположил, что:

  • инструкции занимают 2 байта (это очевидно еще и потому, что четные байты часто повторяются, а нечетные носят гораздо более случайный характер);
  • опкод находится в первом байте, а операнды во втором и, скорее всего, частично в первом;
  • инструкции вида 0xC*** это команда CALL с адресом подпрограммы.

После этих 28 байт идет пара 0xB000, эта же инструкция встречается далее по всей программе, в том числе в самом конце (не считая последних двух байт, которые оказались просто неким индексом программы в ROM), я предположил, что это возврат из подпрограммы, команда RET.

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

Тогда попытался зайти с другой стороны и начал смотреть наборы инструкций всех подряд 4-битных микропроцессоров и микроконтроллеров 70-80-ых годов до которых мог дотянуться гугл — ничего похожего. Я, по-моему, вообще не встретил ни одного 4-битного процессора с двухбайтовыми инструкциями постоянной длины. Была некоторая надежда на современные 4-битные контроллеры от Epson, думал, что их система команд могла восходить к началу 80-х, но нет, и тут ничего. В итоге, решено было ждать часы и действовать методом тыка.

Приехали часы, на скорую руку собрал такой передатчик:




С катушкой от первого подвернувшегося трансформатора не заработало, точнее работало только без задней крышки часов, со второй подвернувшейся катушкой удалось получить стабильную передачу. Для загрузки программ использовал немного модифицированное приложение опубликованное на sigma957.org.
В последствии, я намотал катушку и собрал передатчик на ATtiny85, более пригодный для практического применения:



Еще один способ взаимодействия с часами используя телефон
Когда делал передатчики, обратил внимание, что часы в принципе могут принимать данные на вдвое меньшей частоте — 16384 Гц, хотя процент ошибок резко возрастает. Это навело на мысль, которая сначала казалась безумной: использовать динамик телефона в качестве катушки для передачи данных на часы. Удивлению моему не было предела, когда идея оказалась вполне работоспособной, вот видео получившейся программы, которая полностью эмулирует работу компактной клавиатуры:

Конечно, процент ошибок велик, около 10-20%, поэтому, без обратной связи, возможности передать приложения нет. Зато, например, можно настроить контраст при отсутствии нормальной клавиатуры (после смены батареи он всегда задран на столько, что пользоваться часами невозможно). Ну и работает это далеко не на всех телефонах, я тестировал на Nexus 5X — отлично, Galaxy S8 — плохо, Nexus 5 — очень плохо, проходят только отдельные байты.

Первым делом поигрался со всеми штатными приложениями, затем начал заменять подозрительные инструкции на 0x0000 (тогда я ошибочно предположил, что это NOP) и буквально через пару замен наткнулся на участок с выводом текста. В копилку добавил две инструкции, установка позиции курсора:

0111 10ii iii0 0iii, где i - позиция курсора, начиная с верхнего левого угла

и непосредственно вывод символа:

0111 11ii iii1 0iii, где i - код символа

Вот из-за этого интересного расположения битов и вышло то, что я не смог найти строки сразу.
Я был на седьмом небе, тут же написал «Hello world», который работал как-то странно из-за того, что я не понимал назначения таблицы переходов в заголовке, но это уже было что-то.

Дальше разобрался с адресацией команды CALL — оказалось, что подпрограммы, которые вызывают такие инструкции из заголовка, не заканчиваются на RET, как можно было бы предположить, а заканчиваются они вызовами подпрограмм из ПЗУ. Получается, я ошибся, инструкции вида 0xC*** больше похожи на безусловный переход JMP A:

0011 AAAA AAAA AAAA, где A - адрес перехода, адресация двухбайтовая

а вызовом подпрограммы CALL A оказалась инструкция:

1010 AAAA AAAA AAAA

Дальше процесс опять застопорился. Уже можно было делить программу на отдельные блоки, функции которых были в целом понятны, например, можно было точно определить, что такой-то набор инструкций выводит счет в игре HIT, но вот понять что делает каждая инструкция мне никак не удавалось.

Потупив над hex'ами пару дней, решил воспользоваться старым добрым методом — выкидывать из программы все, что ее не ломает, таким образом максимально ее упростив, а параллельно изучать то, что ломает. Для препарирования я выбрал программу Scheduler, она имеет минимальный размер (код занимает только 2/3, оставшееся место используется для хранения 31-ой заметки по 20 символов) и выглядела она проще остальных.

Вот ее hex-дамп
00000000 CC2A CD6F CE6A C109 C0E0 CE83 CE8F CEA4 М*НoОjБ.АаОѓОЏО¤
00000010 CEAC CEB7 C307 CC41 C09B CCD3 CC30 B000 О¬О·Г.МAА›МУМ0°.
00000020 3C10 A8F2 3C18 77FC 6509 3E00 F501 CC20 <.Ёт<.wьe.>.х.М
00000030 8D01 77FC 6501 8AEA 8AD2 8AA0 9C87 AE26 Ќ.wьe.ЉкЉТЉ њ‡&
00000040 AC26 8C43 3C08 9B4C F746 CC31 8257 8236 ¬&ЊC<.›LчFМ1‚W‚6
00000050 8215 B000 CC10 4578 455E 4532 458E C227 ‚.°.М.ExE^E2EЋВ'
00000060 CC10 4570 4548 452A 89F0 89C0 5C03 3C18 М.EpEHE*‰р‰А\.<.
00000070 AC3E AEB4 A7EF 79C5 7C11 CC0F 8822 8808 ¬>ґ§пyЕ|.М.€"€.
00000080 B000 3C00 3990 D045 CC9D 3C18 3E08 9E7C °.<.9ђРEМќ<.>.ћ|
00000090 3A84 D857 3A94 D44E 3A6A D09D 8501 8B22 :„ШW:”ФN:jРќ….‹"
000000A0 8B08 2919 8940 2112 6102 6664 CC7B F692 ‹.).‰@!.a.fdМ{ц’
000000B0 83F3 1BEC D89D 8B22 8B1C 8920 8914 BBE0 ѓу.мШќ‹"‹.‰ ‰.»а
000000C0 CC73 CC9F CC76 CC7B CC81 CC88 CC6F CC8F МsМџМvМ{МЃМ€МoМЏ
000000D0 CC98 CC9B F266 3A78 D49F ACA0 CC9F ACA0 М.М›тf:xФџ¬ Мџ¬ 
000000E0 C4EC ACA0 C4D7 ACA0 3C00 C532 8B08 3419 Дм¬ ДЧ¬ <.Е2‹.4.
000000F0 D09D 4C11 CC9D 8B24 8B0E 3419 D09D 4C01 РќL.Мќ‹$‹.4.РќL.
00000100 CC9D 3419 DC86 ACA6 AC3E CC9D 2809 CC9D Мќ4.Ь†¬¦¬>Мќ(.Мќ
00000110 3419 D88D ACBB AC3E CC9D 2009 CC9D 3419 4.ШЌ¬»¬>Мќ .Мќ4.
00000120 DC95 8B08 3419 DC95 CC9D 8822 881C CC9D Ь•‹.4.Ь•Мќ€"€.Мќ
00000130 ACA6 AC3E CC9D ACBB AC3E AEB4 45F0 C39C ¬¦¬>Мќ¬»¬>ґEрГњ
00000140 3C08 3E00 8D22 4170 4190 B000 894A 8932 <.>.Ќ"ApAђ°.‰J‰2
00000150 8900 360A D0B1 8B40 8B22 8B08 2A1A ACB6 ‰.6.Р±‹@‹"‹.*.¬¶
00000160 CCB5 8A4E 8A3C 8A10 ACB6 B000 4C5B DCBA МµЉNЉ<Љ.¬¶°.L[Ьє
00000170 8866 8840 B000 894E 893C 8910 360A D0C6 €f€@°.‰N‰<‰.6.РЖ
00000180 8B40 8B22 8B08 221A ACCB CCCA 8A4A 8A32 ‹@‹"‹.".¬ЛМКЉJЉ2
00000190 8A00 ACCB B000 8B66 8B40 345B D4D1 8C43 Љ.¬Л°.‹f‹@4[ФСЊC
000001A0 CCD2 4C4B B000 3C00 F190 F583 3990 D0DB МТLK°.<.сђхѓ9ђРЫ
000001B0 CCE4 ACE9 CCE4 ACE9 8501 8B28 8B00 2119 Мд¬йМд¬й….‹(‹.!.
000001C0 6111 7C11 AEB4 45F0 C98F 3C18 41EE ACA0 a.|.ґEрЙЏ<.Aо¬ 
000001D0 CCE4 3C18 7800 7CB2 81A3 8182 2585 81C6 Мд<.x.|ІЃЈЃ‚%…ЃЖ
000001E0 77FD 674B 83E6 38D0 DCF6 13E2 E3E1 CD00 wэgKѓж8РЬц.вгбН.
000001F0 F8D3 8BA6 8B82 359D D91A 8B80 2D9D CD19 шУ‹¦‹‚5ќЩ.‹Ђ-ќН.
00000200 8BA6 8B84 359D D91A 8B82 2D9D 8BC2 8B80 ‹¦‹„5ќЩ.‹‚-ќ‹В‹Ђ
00000210 359E D519 4DC6 CD0C 8BA4 8B92 F345 359D 5ћХ.MЖН.‹¤‹’уE5ќ
00000220 D91A 4F9D 2D9D CD19 4F8D 359D D91A 4F9D Щ.Oќ-ќН.OЌ5ќЩ.Oќ
00000230 2D9D 4DC6 8BC4 8BA4 8B92 F346 359E D121 -ќMЖ‹Д‹¤‹’уF5ћС!
00000240 CD24 89C6 89A0 8982 39DA DD2E 1DD4 DD2B Н$‰Ж‰ ‰‚9ЪЭ..ФЭ+
00000250 7C90 6DC0 CD30 7CD1 6DC0 CD30 7C90 7CD1 |ђmАН0|СmАН0|ђ|С
00000260 7CB5 F1A3 6DA0 6D80 CD37 7C90 6D80 7C90 |µсЈm mЂН7|ђmЂ|ђ
00000270 8147 8960 2543 8B62 8B48 355B D941 2D5B ЃG‰`%C‹b‹H5[ЩA-[
00000280 CD3B 8B60 8B4E 355B D946 2D5B B940 CD4E Н;‹`‹N5[ЩF-[№@НN
00000290 CD52 CD56 CD5A CD5E CD62 CD66 7D53 7D55 НRНVНZН^НbНf}S}U
000002A0 7D36 CD69 7D35 7D37 7D36 CD69 7D54 7D55 }6Нi}5}7}6Нi}T}U
000002B0 7D15 CD69 7D57 7D15 7D14 CD69 7D54 7D30 }.Нi}W}.}.Нi}T}0
000002C0 7D55 CD69 7D16 7D52 7D31 CD69 7D53 7D11 }UНi}.}R}1Нi}S}.
000002D0 7D54 ABD7 ABD7 6202 ABC6 ABC6 B000 3C00 }T«Ч«Чb.«Ж«Ж°.<.
000002E0 8F03 8B58 E1E1 CD7A 341B D57A 398E D57F Џ.‹XббНz4.Хz9ЋХ.
000002F0 4782 C190 398E D179 E381 CE54 439C 3C10 G‚Бђ9ЋСyгЃОTCњ<.
00000300 3E00 4118 9C86 77FD 67CF 3C18 E9EF 3C10 >.A.њ†wэgП<.йп<.
00000310 89C6 89A0 8982 348E D597 F3C1 CD97 88C4 ‰Ж‰ ‰‚4ЋХ—уБН—€Д
00000320 88A4 8892 9486 3C18 45E8 CD97 41F6 3C10 €¤€’”†<.EиН—Aц<.
00000330 3E18 98E6 F4C4 38D8 D5A4 F4E5 CDA4 38F8 >..жфД8ШХ¤феН¤8ш
00000340 D5A4 4504 CDA4 4502 AE34 E101 CDAD F3C2 Х¤E.Н¤E.4б.Н.уВ
00000350 F7C3 CDAD 4D96 CDAD 4D86 958E 9C86 AE34 чГН.M–Н.M†•Ћњ†4
00000360 978E 89C2 89AC 899A E102 E50E CDCF 3BC2 —Ћ‰В‰¬‰љб.е.НП;В
00000370 D5BA 4D86 9E9E 228E 9796 4FD6 3BDE D5C1 ХєM†ћћ"Ћ—–OЦ;ЮХБ
00000380 8BC6 77FD 67C7 CDCF 3BC0 D5C7 4D86 9E8E ‹ЖwэgЗНП;АХЗM†ћЋ
00000390 228E 9596 4FC6 3BC8 D5C1 8BC0 CDC1 8F22 "Ћ•–OЖ;ИХБ‹АНБЏ"
000003A0 8B64 9877 9856 9835 3C18 359E D9DF 299E ‹d.w.V.5<.5ћЩЯ)ћ
000003B0 8BC0 8BA2 8B9E 359E 3C10 D9EA CDE7 2B8E ‹А‹ў‹ћ5ћ<.ЩкНз+Ћ
000003C0 89C0 89A2 899E 378E 3C10 D9ED CDE7 8A42 ‰А‰ў‰ћ7Ћ<.ЩнНзЉB
000003D0 8A3E CDF0 9A4D 9A2C CDF0 9A5D 9A3C CE05 Љ>НрљMљ,Нрљ]љ<О.
000003E0 8960 8942 8928 363A D21A 6023 AE2E AE2E ‰`‰B‰(6:Т.`#..
000003F0 AE2E AE2E 4E32 202B 896E 895E 8938 342B ..N2 +‰n‰^‰84+
00000400 D604 886A 8852 8820 CDF0 8960 8942 8928 Ц.€j€R€ Нр‰`‰B‰(
00000410 363A D21A 282B 896A 894E 8938 342B D613 6:Т.(+‰j‰N‰84+Ц.
00000420 886E 885C 8830 6023 AE2E AE2E AE2E AE2E €n€\€0`#....
00000430 4E32 CE05 92E3 92C2 92A1 3C18 3E00 9C87 N2О.’г’В’Ў<.>.њ‡
00000440 AC3E 8C43 AE26 AC26 AEB4 CE54 8FE7 83D7 ¬>ЊC&¬&ґОTЏзѓЧ
00000450 758C 67C7 66A6 64C7 6485 B000 6743 6743 uЊgЗf¦dЗd…°.gCgC
00000460 6743 6743 6743 B000 8D86 8E86 8AA2 18C2 gCgCgC°.Ќ†Ћ†Љў.В
00000470 F0D3 81E6 38D0 DE3D 11E2 E1E1 CE42 F8C4 рУЃж8РЮ=.вббОBшД
00000480 8A9C CE4A 8A9E CE4A 77FD 67CF F3C2 8A98 ЉњОJЉћОJwэgПуВЉ.
00000490 CE4A 8A9A 2196 CE37 8E85 8A94 F0A3 2196 ОJЉљ!–О7Ћ…Љ”рЈ!–
000004A0 18A2 CE4E 2186 B000 F581 CE62 E6E2 E2E6 .ўОN!†°.хЃОbжввж
000004B0 CE62 E2E8 FA29 4E21 FA28 CE62 3A44 DE64 Оbвиъ)N!ъ(Оb:DЮd
000004C0 4E42 FA43 3C18 41FA C190 A7FD 3C18 41FC NBъC<.AъБђ§э<.Aь
000004D0 45E4 CE64 3C00 F581 CE81 E6E2 E2E8 CE81 EдОd<.хЃОЃжввиОЃ
000004E0 E2F0 3C18 E5E1 CE81 E1E9 ACA6 CE7C 3C18 вр<.ебОЃб鬦О|<.
000004F0 E5E1 CE81 E1E3 ACBB 45E2 CE7F 41FC 3C00 ебОЃб㬻EвО.Aь<.
00000500 4670 AEB4 C11A 5748 434E D68E 3C18 EDE2 FpґБ.WHCNЦЋ<.нв
00000510 45F0 CE8E 41EE ACA0 AEB4 A81C C243 5748 EрОЋAо¬ ґЁ.ВCWH
00000520 4356 D69D 3E00 9B4C F342 F748 CE9D 8D22 CVЦќ>.›LуBчHОќЌ"
00000530 AEB4 5C04 A7F2 79C5 7C10 C283 A81C ACA0 ґ\.§тyЕ|.ВѓЁ.¬ 
00000540 3C18 41EE 3C00 C262 5748 435A D6AB 3C18 <.Aо<.ВbWHCZЦ«<.
00000550 ACA6 AEB4 A81C C2DF 5748 435C D6B3 3C18 ¬¦ґЁ.ВЯWHC\Ці<.
00000560 ACBB AEB4 A81C C383 3C00 4662 B000 3C00 ¬»ґЁ.Гѓ<.Fb°.<.
00000570 F581 CEBE 3C18 E5E1 CEBE 41FA AEB4 426E хЃОѕ<.ебОѕAъґBn
00000580 C2A9 C000 C000 C000 C000 C000 0000 0000 ВА.А.А.А.А.....
00000590 0000 0000 0000 0000 0000 0000 0000 0000 ................
000005A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000005B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000005C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000005D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000005E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000005F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000600 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000610 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000620 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000630 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000640 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000650 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000660 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000670 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000680 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000690 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000700 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000710 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000720 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000730 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000740 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000750 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000760 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000770 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000780 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000790 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007F0 0000 0000 0000 0000 0000 0000 0100 0100 ................


Такая тактика сразу начала приносить плоды. Найдя подпрограммы обработки нажатий кнопок, стало понятно, что таблица в заголовке это переходы на адреса обработчиков событий (не прерываний), с этой таблицей я до сих пор до конца не разобрался, вот что на текущий момент мне известно:
Индекс в заголовке Описание Адрес стандартного обработчика
0 Запуск приложения 0x227
1 Секундный таймер 0x190
2 - 0x11A
3 - 0x109
4 - 0x0E0
5 Нажатие кнопки Mode 0x243
6 Нажатие кнопки Transmit 0x283
7 Нажатие правой кнопки 0x2DF
8 Нажатие левой кнопки 0x383
9 - 0x2A9
10 - 0x307
11 Получен байт на катушку 0x39C
12 - 0x09B
13 До конца не понял, но в обработчике обычно реализуется перерисовка экрана 0x98F
14 На внешней клавиатуре нажата кнопка APL (запуск приложения) -

Благодаря тем же обработчикам кнопок, которыми пользователь выбирал нужную заметку, идентифицировал инструкции сложения и вычитания — 0x221A и 0x2A1A, правда, еще не разобравшись с операндами. Затем был опознан один из условных переходов, опять же без операндов.

Scheduler к этому времени выглядел примерно так
00000000 CC28 B000 B000 B000 B000 B000 CC58 CC38 М(°.°.°.°.°.МXМ8
00000010 CC48 B000 B000 B000 B000 CCD3 B000 B000 МH°.°.°.°.МУ°.°.
00000020 3C18 0000 3E00 9C87 8C43 0000 8AE4 8ADC <...>.њ‡ЊC..ЉдЉЬ
00000030 8AA0 8257 8236 8215 3C08 0000 F74A CC2E Љ ‚W‚6‚.<...чJМ.
00000040 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050 CC10 4578 455E 4532 458E C227 B000 0000 М.ExE^E2EЋВ'°...
00000060 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070 3C18 AC7D C2DF B000 0000 0000 0000 0000 <.¬}ВЯ°.........
00000080 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090 3C18 AC9D C383 B000 0000 0000 0000 0000 <.¬ќГѓ°.........
000000A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000B0 3C18 AC98 C283 B000 0000 0000 0000 0000 <.¬.Вѓ°.........
000000C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000D0 3C18 AC98 3C00 4670 C11A B000 0000 0000 <.¬.<.FpБ.°.....
000000E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000F0 0000 0000 0000 0000 0000 8B40 8B22 8B08 ..........‹@‹"‹.
00000100 2A1A AC88 CC87 8A4E 8A3C 8A10 AC88 B000 *.¬€М‡ЉNЉ<Љ.¬€°.
00000110 4C5B DC8C 8866 8840 B000 0000 0000 0000 L[ЬЊ€f€@°.......
00000120 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000130 0000 0000 0000 0000 0000 8B40 8B22 8B08 ..........‹@‹"‹.
00000140 221A ACA8 CCA7 8A4A 8A32 8A00 ACA8 B000 ".¬ЁМ§ЉJЉ2Љ.¬Ё°.
00000150 8B66 8B40 345B D4AE 8C43 CCAF 4C4B B000 ‹f‹@4[ФЊCМЇLK°.
00000160 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000170 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000180 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000190 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001A0 0000 0000 0000 3C00 F190 F583 3990 D0DB ......<.сђхѓ9ђРЫ
000001B0 CCE4 ACE9 CCE4 ACE9 8501 8B28 8B00 2119 Мд¬йМд¬й….‹(‹.!.
000001C0 6111 7C11 AC01 45F0 C98F 3C18 41EE AC01 a.|.¬.EрЙЏ<.Aо¬.
000001D0 CCE4 3C18 7800 7CB2 81A3 8182 2585 81C6 Мд<.x.|ІЃЈЃ‚%…ЃЖ
000001E0 72FC 674B 83E6 38D0 DCF6 13E2 E3E1 CD00 rьgKѓж8РЬц.вгбН.
000001F0 F8D3 8BA6 8B82 359D D91A 8B80 2D9D CD19 шУ‹¦‹‚5ќЩ.‹Ђ-ќН.
00000200 8BA6 8B84 359D D91A 8B82 2D9D 8BC2 8B80 ‹¦‹„5ќЩ.‹‚-ќ‹В‹Ђ
00000210 359E D519 4DC6 CD0C 8BA4 8B92 F345 359D 5ћХ.MЖН.‹¤‹’уE5ќ
00000220 D91A 4F9D 2D9D CD19 4F8D 359D D91A 4F9D Щ.Oќ-ќН.OЌ5ќЩ.Oќ
00000230 2D9D 4DC6 8BC4 8BA4 8B92 F346 359E D121 -ќMЖ‹Д‹¤‹’уF5ћС!
00000240 CD24 89C6 89A0 8982 39DA DD2E 1DD4 DD2B Н$‰Ж‰ ‰‚9ЪЭ..ФЭ+
00000250 7C90 6DC0 CD30 7CD1 6DC0 CD30 7C90 7CD1 |ђmАН0|СmАН0|ђ|С
00000260 7CB5 F1A3 6DA0 6D80 CD37 7C90 6D80 7C90 |µсЈm mЂН7|ђmЂ|ђ
00000270 8147 8960 2543 8B62 8B48 355B D941 2D5B ЃG‰`%C‹b‹H5[ЩA-[
00000280 CD3B 8B60 8B4E 355B D946 2D5B B940 CD4E Н;‹`‹N5[ЩF-[№@НN
00000290 CD52 CD56 CD5A CD5E CD62 CD66 7D53 7D55 НRНVНZН^НbНf}S}U
000002A0 7D36 CD69 7D35 7D37 7D36 CD69 7D54 7D55 }6Нi}5}7}6Нi}T}U
000002B0 7D15 CD69 7D57 7D15 7D14 CD69 7D54 7D30 }.Нi}W}.}.Нi}T}0
000002C0 7D55 CD69 7D16 7D52 7D31 CD69 7D53 7D11 }UНi}.}R}1Нi}S}.
000002D0 7D54 ABD7 ABD7 6202 ABC6 ABC6 B000 0000 }T«Ч«Чb.«Ж«Ж°...
000002E0 5445 5354 0000 0000 0000 0000 0000 0000 TEST............
000002F0 5400 0000 0000 0000 0000 0054 0000 T..........T..


При этом программа практически полностью сохранила функционал работы с заметками. Как я позднее понял, были выпилены блоки ввода заметок с клавиатуры, различные проверки на граничные условия и тому подобный «избыточный» код. Оставшиеся подпрограммы я перетасовал в удобный для работы и компактный вид, благо инструкции переходов мне уже были известны. Компактность, кстати, была очень актуальна, т.к. передача 2048 байт на часы занимает порядка 30 секунд. Представьте процесс — обнуляем два байта, отправляем на часы, ждем 30 секунд, смотрим что получилось — если все сильно ломается, то возвращаем инструкцию, если не ломается то высматриваем следующую жертву и опять 30 секунд. Медленно, очень медленно. Поэтому с Scheduler'ом сокращенным до 512 байт работа пошла гораздо быстрее.

Дальнейшее тыканье палкой привело к пониманию того, что инструкциями 0x8AE4 0x8ADC 0x8AA0 в обработчике запуска программы задается адрес текущей заметки, их я интерпретировал как загрузку непосредственного значения в регистр LDI R, I

1000 10RR RRRi iii-, где R - регистр назначения, i - загружаемое значение

Теперь стало понятно, что у нас есть 32 четырехбитных регистра, к тому же маячившие по-всюду инструкции 0x3C00, 0x3C08, 0x3С10, 0x3C18 по некоторым признакам я принял за выбор текущего банка регистров, что в дальнейшем подтвердилось. Итого, имеем 4 банка по 32 регистра. Нахождение регистров было одним из ключевых моментов, после которого я действительно поверил в возможность дизасемблировать программы этих часов.

Расположившиеся рядом 0x8257 0x8236 0x8215 оказались инструкциями копирования регистров MOV Rd, Rs

1000 00dd ddds ssss, где d - регистр назначения, s - исходный регистр

После этого, я не без труда разобрался с операндами ранее найденных операций сложения и вычитания, это оказались операции над группами регистров ADM Rd, Rs и SBM Rd, Rs (M — от Multiple). Эти инструкции, оперирующие группами регистров, показали, что каждая банка регистров делится еще на 4 страницы по 8 регистров. Каждая инструкция, работающая с группами регистров, может оперировать любым количеством регистров в пределах страницы, при этом индексы начального и конечного регистра группы должны совпадать для обоих операндов. Я, наверно, не совсем понятно объясняю, поэтому попробую еще раз на картинке:



Получается, некоторые команды могут оперировать сразу 4-байтными значениями — неплохо для четырехбитного процессора. Для простоты использования групповых команд, я решил именовать регистры по типу RA0, RA1… RA7, RB0, RB1 и т.д. и т.п.

Итак, в целом синтаксис инструкций стал примерно понятен, осталось дело техники — формально описать как можно больше инструкций. Для этого, во-первых, я сделал список всех инструкций из штатных программ в двоичном виде и разделил его на группы по шести первым битам, так можно было сразу прикинуть, какими данными оперирует команда, например, если правый бит в группе всегда 0, то почти наверняка второй операнд является непосредственным значением. Во-вторых, к этому моменту я уже знал достаточно инструкций, чтобы написать в машинных кодах программу для часов, которая бы отображала текущие значения регистров всех банков — нажимаешь на одну кнопку — отображается значения регистров 0-го банка, на другую 1-го итп. Это была моя первая полезная программа для UC-2000!


Содержимое нулевого банка регистров

В процессе написания этой программы я ловил какие-то ужасные глюки со значением нулевого регистра каждого банка и никак не мог понять в чем дело. Как выяснилось, причина была в том, что я использовал 0x0000 в качестве NOP, а на самом деле это команда сложения регистров, т.е. я постоянно в разных местах складывал нулевой регистр сам с собой).

С этими двумя инструментами, через пару дней я уже формально описал более 40 инструкций. Много времени отнимали инструкции с нестандартным синтаксисом, типа INC и DEC.
Еще больше времени заняла инструкция с опкодом 010101, которая всегда шла первой в обработчиках кнопок и встречалась в некоторых других местах. Как оказалось, она копировала значение в регистр текущего банка из некоей группы регистров, которую я назвал регистрами ввода-вывода. Опытным путем установил, что этих регистров всего 16, описание назначения тех, что мне удалось понять в следующей таблице:

Регистр Описание
IO0 0-й бит — установлен, если нажата левая или правая кнопка; 2-й — нажата кнопка MODE или TRANSMIT; 3-й — получен байт на катушку
IO1 Флаг приема данных на катушку
IO2 Флаг ошибки при передаче
IO3 -
IO4 -
IO5 Старший полубайт принятый на катушку
IO6 Младший полубайт принятый на катушку
IO7 Флаги прерываний кнопок, 0-й бит — левая кнопка, 1-й — правая. 2-й — MODE,
3-й — TRANSMIT
IO8 Флаги нажатых кнопок, та же последовательность что и в IO7
IO9 -
IO10 -
IO11 -
IO12 -
IO13 -
IO14 Счетчик 1/16 секунды
IO15

Еще, с помощью программы отображения значений регистров я выяснил, что первые два банка регистров используются внутренней программой часов, поэтому, по понятным причинам, в качестве регистров общего назначения лучше использовать только 2 и 3 банки.

Назначение регистров 0-го банка:
Регистр Описание
RA0 Текущие минуты, младший разряд, BCD
RA1 Текущие минуты, старший разряд, BCD
RA2 Текущий час в 12-часовом формате
RA3 12-часовой интервал, 0 — AM, 1 — PM
RA4 Текущая дата, младший разряд, BCD
RA5 Текущая дата, старший разряд, BCD
RA6 Текущий месяц
RA7 Текущий день недели. 0 — воскресенье и далее
RB0 Минуты будильника, младший разряд, BCD
RB1 Минуты будильника, старший разряд, BCD
RB2 Часы будильника в 12-часовом формате
RB3 0-й бит — AM/PM будильника, 1-й — включен/выключен
RB4 Текущий режим, 0 — часы, 1 — загружаемая программа и далее, будильник, секундомер, установка времени, передача данных
RA5 Текущие секунды, младший разряд, BCD
RA6 Текущие секунды, старший разряд, BCD
RA7… RD7 -

Во 1-м банке интересны только регистры RB0 — RB3, они используются для указания событий, на которые будет реагировать загруженная программа. Остальные регистры этого банка используются по-разному в разных режимах работы часов, поэтому я не стал их анализировать (хотя, конечно, там может быть что-то интересное)

Итогом всей этой работы стал набор из 61 формально описанной инструкции. К сожалению, мне не удалось восстановить все, что видно из таблицы (которая чуть ниже под спойлером), есть пробел на интервале 5800-5BFF, может быть, там спряталась одна или даже несколько инструкций. Есть очень специализированные команды, типа пикнуть пьезодинамиком, за ними, скорее всего, стоит что-то большее, я даже не стал давать имена таким инструкциям. Под вопросом NOP. Во многих инструкциях есть незначащие биты, помеченные мной как "-", на самом деле, конечно, некоторые могут влиять на что-то, что я упустил.

Набор инструкций


Много времени отнял подбор подходящих мнемоник, некоторые команды названы довольно некрасиво (особенно в середине таблицы), я сам же их постоянно забывал.

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

Ассемблер


Я наивно предполагал, что существуют готовые реализации универсальных ассемблеров, в голове крутилось когда-то слышанное словосочетание «табличный ассемблер» по которому я и начал гуглить. Оказалось да, действительно, есть такой класс ассемблеров, называемых «table driven assembler», но я не смог найти ничего подходящего — это были либо давно заброшенные продукты из 90-х типа TDASM, либо современные сильно ограниченные проекты «на коленке». И это бы все ничего, но у всех этих табличных ассемблеров был серьезный недостаток — они имели каждый свою нотацию, которая прекрасно описывала инструкции процессоров из поддерживаемого списка, но никак не хотела описывать набор инструкций UC-2000: некоторые просто не могли работать с операндами не кратными байтам, некоторые не могли адресовать одновременно и байты и двухбайтовые команды и т.п.

Я уже склонялся к написанию своего предельно простого ассемблера, но наткнулся на axasm (статья с описанием автора). Кратко суть в том, что для описания инструкций используются макроподстановки С, за счет чего получается большая универсальность, а весь разбор кода ложится на препроцессор, нам остается только предварительно конвертировать код вида «MOV Rd, Rs» в MOV(Rd, Rs), для этого автор использует AWK, а автоматизируется весь процесс Bash-скриптом. При этом, конечно, у этого метода есть минусы — невозможно на одну ассемблерную команду повесить несколько инструкций, под Windows придется использовать MinGW (хотя можно конечно все переписать, но мне было лень), ну и вообще, все это довольно громоздко.

Итак, сделал описание известных мне команд для axasm и дело пошло гораздо лучше, не хватало только дизассемблера для удобного разбора штатных приложений, что я быстро исправил написав свой совсем простенький, но полностью функциональный.
К тому времени я уже описал большинство инструкций, и можно было браться за написание чего-нибудь «серьезного».

Программирование


В качестве первой программы я опять выбрал Тетрис.

Конечно, никаких иллюзий на счет возможностей 4-битного процессора в электронных часах 83 года я не питал, но все равно был неприятно удивлен отсутствию казалось бы элементарных команд:

  • Нету битового сдвига! Ни в каком виде. Вроде бы, базовая операция, отсутствие которой многое усложняет. Левый сдвиг, допустим, можно заменить сложением регистра самого с собой, но вот чем заменить правый сдвиг?(
  • Нет ни одной логической операции над двумя регистрами! AND, OR и XOR только регистра с непосредственным значением. Да что уж там, нет даже отрицания.
  • Ни в одной команде нельзя использовать косвенную адресацию регистров, это наверно не сильно удивительно, но сильно неудобно.
  • Глубина стека вызова подпрограмм всего 3 уровня, это тоже не удивительно, но я, не привыкший к таким ограничениям, сначала ловил непонятные зависания программы и долго не мог понять в чем дело.

Вот, например, как пришлось извращаться для реализации дизъюнкции двух регистров RA0, RA1:

	XORI RA0, 0xF       ;вместо отрицания
	BTJR RA0, 0, +1     ;тестим 0-й бит
	ORI RA1, 1          ;если не установлен то OR'им с единицей
	BTJR RA0, 1, +1     ;а если установлен то сразу прыгаем сюда
	ORI RA1, 2          ;и дальше в том же духе
	BTJR RA0, 2, +1
	ORI RA1, 4
	BTJR RA0, 3, +1
	ORI RA1, 8          ;как хорошо, что у нас всего 4 бита

А ведь каждая инструкция отнимает у нас 2 байта из 2048, кошмар.

Зато все арифметические команды имеют аналоги с десятичной коррекцией. Это наводит на мысль, что микропроцессор изначально разрабатывался и использовался инженерами Seiko-Epson для чего-то типа часов с функцией калькулятора и уже потом был адаптирован для UC-2000.

Еще одним неприятным сюрпризом, для меня, было отсутствие доступа к отдельным пикселям дисплея, хотя фотографии часов, на которых видно, что время отображается нестандартными символами цифр, занимающими сразу два знакоместа, казалось, намекают на такую возможность. Правда, как только я увидел таблицу символов, никаких иллюзий не осталось — половинки цифр просто отдельные символы.
Поэтому, для Тетриса пришлось использовать символы псевдографики, благо их достаточно, чтобы используя символы "█", "▀", "▄" и " " разбить экран на 8x10 «пикселей», игровое поле получается примерно таким:



«Стакан» повернут и занимает весь экран, но даже так, конечно, сильно не дотягивает до классических 10x20 клеток.

Не смотря на этот ограниченный набор инструкций, Тетрис уместился в 700 байт, хотя, когда я только приступил к нему, опасался, что не уложусь и в 2 КБ.

Исходный код с комментариями
        ;Даем регистрам более подходящие имена
        ##define brdAdr RA2 
        ##define brdValTop RA3  
        ##define brdValBtm RA4  
        ##define ind RA5    
        ##define drawLine RA6
        ##define appState RA7
        
        ##define curLine RB0
        ;9, 10
        ##define preLine RB3
        ##define scoresL RB4
        ;13, 14
        ##define scoresH RB7
        
        ##define piceAngle RC0
        ##define piceNum RC1
        ##define piceSelecter RC2
        ##define piceShift RC3
        ##define scoresAddL RC4
        ;21;22
        ##define scoresAddH RC7
        ##define curPosL RC4
        ##define curPosH RC5
        
        ##define piceLn0t RD0
        ##define piceLn0b RD1
        ##define piceLn1t RD2
        ##define piceLn1b RD3
        ##define piceLn2t RD4
        ##define piceLn2b RD5
        ##define piceLn3t RD6
        ##define piceLn3b RD7
        
        ;Вычисляем адрес игрового поля
	##define brdAdr2b (((board*2)>>8) & 0x0F)
	##define brdAdr1b (((board*2)>>4) & 0x0F)
	##define brdAdr0b (((board*2)   ) & 0x0F)
        
        ;Вычисляем адрес фигур
	##define picesAdr2b (((pices*2)>>8) & 0x0F)
	##define picesAdr1b (((pices*2)>>4) & 0x0F)
	##define picesAdr0b (((pices*2)   ) & 0x0F)

        ORG 0 
    
        ;Таблица переходов на обработчики событий
        JMP start                         ;Адреса обработчиков нужных событий
        JMP timer
        JMP 0x11A                         ;или адреса штатных обработчиков для ненужных событий
        JMP 0x109
        JMP 0x0E0
        JMP btn_mode
        JMP btn_transm
        JMP btn_right
        JMP btn_left
        JMP 0x2A9
        JMP 0x307
        JMP 0x39C
        JMP 0x09B
        JMP update_screen
        RET

        ;Обработчик запуска приложения
start:
        CLRM RB1, RB3%8
        LDI RB2, 0xF                      ;Подписываемся на события нажатия всех 4-х кнопок
        LCRB B2
        LDI appState, 1
        JMP 0x227                         ;Все обработчики должны завершаться переходом на определенные адреса 
    
        ;Обработчик нажатия MODE
btn_mode:
        CPJR RB3, 0, btn_mode_startapp    ;Если приложение уже запущено, то отписываемся от всех событий
        CLRM RB1, RB3%8                   ;иначе обработчики так и будут вызываться даже после "выхода" из программы
        JMP 0x243
    btn_mode_startapp:
        LDI RB3, 8                        ;А если запуск только произошел то подписываемся на таймер
        LDI RB1, 1                        ;и перерисовку экрана
        JMP 0x243

        ;Обработчик TRANSMIT
btn_transm:
        LARB B3
        LCRB B2
        ADI piceAngle, 4                  ;Поворачиваем фигуру
        JMP update_screen
    
        ;Обработчик правой кнопки
btn_right:
        LARB B3
        LCRB B2
        CPJR appState, 2, btn_right_c1
        CALL prepareNewGame
        JMP 0x2DF
    btn_right_c1:
        INC piceShift, piceShift%8        ;Перемещаем фигуру вправо
        JMP 0x2DF
    
        ;Обработчик левой кнопки
btn_left:
        LARB B3
        LCRB B2
        DEC piceShift, piceShift%8        ;Перемещаем фигуру влево
        JMP 0x383
    
        ;Обработчик секундного таймера
timer:
        LARB B3
        LCRB B2
        CPJR appState, 2, timer_draw
        CPJR appState, 3, timer_clear_lines_screen
        JMP 0x190
    timer_clear_lines_screen:
        LDI appState, 2                   ;Возвращаемся к игре после отображения очков
        JMP update_screen
    timer_draw:
        INC curLine, curLine%8            ;Падение фигуры
        JMP update_screen
    
update_screen:
        LARB B3
        LCRB B2
        CPJR appState, 2, update_screen_draw
        PLAI 125                          ;Очищаем
        STLI 0                            ;экран
        CPJR appState, 0, update_screen_gover
        CPJR appState, 3, update_screen_clear_lines
    update_screen_start:
        PLAI 12
        PSAI STR_TETRIS                   ;Пишем TETRIS на заставке
        CALL OS_PRINT0-6                  ;используя функцию внутренней программы часов
        JMP 0x98F
    update_screen_gover:
        PLAI 10                           ;GAME OVER
        PSAI STR_GAMEOVER
        CALL OS_PRINT0-10
        JMP update_screen_print_scores
    update_screen_clear_lines:
        PLAI 10                           ;После каждой сброшенной линии
        PSAI STR_SCORE                    ;на секунду показываем заработанные очки
        CALL OS_PRINT0-8
        STL scoresAddL+1
        STL scoresAddL
        PSAI STR_TOTAL
    update_screen_print_scores:
        CALL OS_PRINT0-6                  ;Выводим Total
        STL scoresH                       ;и
        STL scoresH-1                     ;общее
        STL scoresH-2                     ;количество
        STL scoresL                       ;очков
        JMP 0x98F
    update_screen_draw:
        CALL updatePice                   ;Обновляем состояние фигуры
        CALL draw                         ;и все перерисовываем
        JMP 0x98F

        ;Инициализация новой игры
prepareNewGame:
        CALL OS_CLRCB
        LDI appState, 2
        LDI brdAdr, brdAdr2b
        LDI brdAdr-1, brdAdr1b
        LDI brdAdr-2, brdAdr0b
        PSAM brdAdr-2, brdAdr%8 
        CLRM scoresL, scoresH%8
        CLRM brdValTop, brdValBtm%8
        LDI ind, 10
    prepareNewGame_loop:
        STSM brdValTop, brdValBtm%8
        DEC ind, ind%8
        JNC prepareNewGame_loop
        
        ;Подготовка новой фигуры
prepareNewPice:
        CLRM curLine, preLine%8
        LDI piceShift, 2
    prepareNewPice_random:
        IN piceAngle, 14
        ADD piceNum, piceAngle
        CPI piceNum, 7
        JNC prepareNewPice_random
        LDI piceAngle, 0
        CMP scoresAddL, scoresAddL+1
        JZ updatePice
        ADBM scoresL, scoresAddH
        LDI appState, 3
        JMP update_screen
    
        ;обновляем фигуру
updatePice:
        LDI brdAdr, picesAdr2b
        LDI brdAdr-1, picesAdr1b
        LDI brdAdr-2, picesAdr0b
        ADM brdAdr-2, piceSelecter
        PSAM brdAdr-2, brdAdr%8 
        LDSM piceLn0t, piceLn0b%8
        LDSM piceLn1t, piceLn1b%8
        LDSM piceLn2t, piceLn2b%8
        LDSM piceLn3t, piceLn3b%8
    
        ;двигаем фигуру влево-вправо
shiftPice:
        MOV ind, piceShift
    shiftPice_loop:
        CPJR ind, 0, shiftPice_end
        ADM piceLn0t, piceLn3b            ;Замена левого сдвига на сложение регистра самого с собой
        BTJR piceLn1t, 0, +4              ;По ходу движения проверяем выход фигуры
        BTJR piceLn2t, 0, +3              ;за левую границу
        BTJR piceLn3t, 0, +2              ;игрового поля
        DEC ind, ind%8
        JMP shiftPice_loop
        JMP processCollision
    shiftPice_end:
  
        CPI preLine, 15
        JNZ checkCollision
        ;Объединяем упавшую фигуру с содержимым игрового поля и сохраняем полученное в board
storePice:
        LDI brdAdr, brdAdr2b
        LDI brdAdr-1, brdAdr1b
        LDI brdAdr-2, brdAdr0b
        ADM brdAdr-2, curLine+2
        CLRM scoresAddL, scoresAddH%8
    
        LDI ind, 3
    storePice_loop:
        PSAM brdAdr-2, brdAdr%8 
        LDSM brdValTop, brdValBtm%8
        
        XORI piceLn0t, 0xF                ;ужасный OR двух пар регистров  
        XORI piceLn0b, 0xF
        BTJR piceLn0t, 0, +1
        ORI brdValTop, 1
        BTJR piceLn0t, 1, +1
        ORI brdValTop, 2
        BTJR piceLn0t, 2, +1
        ORI brdValTop, 4
        BTJR piceLn0t, 3, +1
        ORI brdValTop, 8
        BTJR piceLn0b, 0, +1
        ORI brdValBtm, 1
        BTJR piceLn0b, 1, +1
        ORI brdValBtm, 2
        BTJR piceLn0b, 2, +1
        ORI brdValBtm, 4
        BTJR piceLn0b, 3, +1
        ORI brdValBtm, 8
        
        PSAM brdAdr-2, brdAdr%8 
        STSM brdValTop, brdValBtm%8
        
        CMP piceLn0t, piceLn0b            ;заодно проверяем нет ли заполненных линий
        JZ storePice_loop2_end
        CPI brdValTop, 0xF
        JNZ storePice_loop2_end
        CPI brdValBtm, 0xF
        JNZ storePice_loop2_end
        MVACM brdAdr-2, brdAdr%8
        ADBM scoresAddL, scoresAddH       ;и подсчитываем
        INCB scoresAddL, scoresAddH%8     ;очки если есть
    storePice_loop2:
        DEC brdAdr-2, brdAdr%8            ;и удаляем заполненные линии
        PSAM brdAdr-2, brdAdr%8 
        LDSM brdValTop, brdValBtm%8
        STSM brdValTop, brdValBtm%8
        CPJR brdValBtm, 0, +1
        JMP storePice_loop2
        CPJR brdValTop, 0, +1
        JMP storePice_loop2
        MVCAM brdAdr-2, brdAdr%8
    storePice_loop2_end:
      
        LSHM piceLn3b, 0
        LSHM piceLn3b, 0
        
        INC brdAdr-2, brdAdr%8
        DEC ind, ind%8
        JNC storePice_loop
    
        JMP prepareNewPice
    
        ;проверяем столкновение фигуры с содержимым игрового поля
checkCollision:
        LDI brdAdr, brdAdr2b
        LDI brdAdr-1, brdAdr1b
        LDI brdAdr-2, brdAdr0b
        ADM brdAdr-2, curLine+2
        PSAM brdAdr-2, brdAdr%8 
        
        MVACM piceLn0t, piceLn3b
        LDI ind, 4
    checkCollision_loop:
        LDSM brdValTop, brdValBtm%8
       
    checkCollision_loop2:
        CMP piceLn0t, piceLn0b
        JZ checkCollision_end2
        ADM piceLn0t, piceLn0b
        JC checkCollision_compare
        ADM brdValTop, brdValBtm
        JMP checkCollision_loop2
    checkCollision_compare:
        ADM brdValTop, brdValBtm
        JC processCollision
        JMP checkCollision_loop2
    checkCollision_end2:
        
        LSHM piceLn3b, 0
        LSHM piceLn3b, 0
        DEC ind, ind%8
        JNZ checkCollision_loop
        MVCAM piceLn0t, piceLn3b    
        
        MVACM piceAngle, piceShift        ;сохраняем валидную
        MOV preLine, curLine              ;позицию фигуры
        RET

        ;обрабатываем столкновение
processCollision:
        CPJR curLine, 0, gameOver
        CMP curLine, preLine
        JZ processCollision_pre
        MOV curLine, preLine              ;восстанавливаем валидную позицию фигуры
        LDI preLine, 15                   ;маркер, что фигура уперлась в пол и нужна новая
        processCollision_pre:
        MVCAM piceAngle, piceShift        ;восстанавливаем валидную позицию фигуры
        JMP updatePice

        ;конец игры
gameOver:
        LDI appState, 0
        JMP update_screen

        ;рисуем игровое поле
draw:
        LDI brdAdr, brdAdr2b
        LDI brdAdr-1, brdAdr1b
        LDI brdAdr-2, brdAdr0b
        PSAM brdAdr-2, brdAdr%8           ;Указываем на адрес игрового поля
        MVAC curLine, curLine             ;Копированием в дополнительный банк удобно бэкапить регистры
        LDI curPosL, 0xE                  ;Значение регистров с позицией экранного курсора
        LDI curPosH, 0x1                  ;на 4 строку первого столбца
    
        LDI ind, 0
    draw_loop0:                           ;проходим поочередно все строки игрового поля сверху вниз [...]
        LDSM brdValTop, brdValBtm%8       ;Загружаем регистры brdValTop и brdValBtm первой строкой игрового поля,
                                          ;указатель после этого перейдет на следующий байт автоматически
        CMP curLine, ind                  ;если фигура пересекается с текущей отображаемой строкой то рисуем и ее
        JNZ draw_ORend
        XORI piceLn0b, 0xF                ;еще один ужасный OR
        XORI piceLn0t, 0xF
        BTJR piceLn0b, 0, +1
        ORI brdValBtm, 1
        BTJR piceLn0b, 1, +1
        ORI brdValBtm, 2
        BTJR piceLn0b, 2, +1
        ORI brdValBtm, 4
        BTJR piceLn0b, 3, +1
        ORI brdValBtm, 8
        BTJR piceLn0t, 0, +1
        ORI brdValTop, 1
        BTJR piceLn0t, 1, +1
        ORI brdValTop, 2
        BTJR piceLn0t, 2, +1
        ORI brdValTop, 4
        BTJR piceLn0t, 3, +1
        ORI brdValTop, 8
        LSHM piceLn3b, 0                  ;Вместо отсутствующей косвенной адресации регистров, сдвигаем регистры с фигурой
        LSHM piceLn3b, 0                  ;на байт влево на каждом шаге цикла, и получаем нужное значение всегда в piceLn0t-piceLn0b
        CMP piceLn0t, piceLn0b
        JZ draw_ORend
        INC curLine, curLine%8  
    draw_ORend:
   
        CPJR ind, 0, draw_b1
    draw_loop1:                           ;[...] слева направо
        PLAM curPosL, curPosH%8           ;устанавливаем экранный курсор на нужную позицию
        BTJR brdValBtm, 2, +5
        BTJR brdValBtm, 3, +2
        STLI 0x20                         ;и 
        JMP draw_else2
        STLI 0xE8                         ;рисуем
        JMP draw_else2
        BTJR brdValBtm, 3, +2
        STLI 0xE7                         ;подходящий
        JMP draw_else2
        STLI 0xFF                         ;символ
    draw_else2: 
        ADM brdValTop, brdValBtm          ;Сдвигаем текущую строку игрового поля на 2 бита
        ADM brdValTop, brdValBtm          ;т.к. каждое знакоместо у нас отображает состояние 2 бит
        SBI curPosL, 0xA                  ;Вот так сложно на
        JNC draw_loop1                    ;4-битном процессоре
        DEC curPosH, curPosH%8            ;вычесть 10
        JNC draw_loop1                    ;у пары регистров (так переходим к предыдущей строке на экране)
    
        LDI curPosH, 0x1                  ;после того, как нарисовали линию
        ADI curPosL, 0x9                  ;возвращаем курсор опять на 4 строку
        JNC draw_b1                       ;но уже на
        INC curPosH, curPosH%8            ;следующее знакоместо
    draw_b1:
    
        INC ind, ind%8                    ;переходим к следующей строке игрового поля
        CPI ind, 11                       ;если еще не все
        JNZ draw_loop0
        
        MVCA curLine, curLine             ;восстанавливаем curLine
        RET
    
        ;Область данных
STR_TETRIS:
        DS "TETRIS"
        
STR_GAMEOVER:
        DS "GAME OVER!"
        
STR_SCORE:
        DS "Score  +"
        
STR_TOTAL:
        DS "Total "
    
        ;игровое поле, каждая строка аккуратно умещается в байт
board:
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b00000000
        DB 0b11111111
    
        ;Фигуры вместе с поворотами
        ;Конечно, сильно избыточно, но при этому получается очень удобно:
        ;на адресацию каждой фигуры нужно использовать минимум 3 полубайта (в 3-х регистрах),
        ;при этом, просто сложив младший полубайт с 4 мы меняем поворот фигуры,
        ;а инкрементируя средний меняем фигуру.
        ;Красиво и никаких проверок выхода за границы и прочих усложнений кода.
pices:
        DB 0b00000000 ;o 0°
        DB 0b00000011
        DB 0b00000011
        DB 0b00000000
        DB 0b00000000 ;o 90°
        DB 0b00000011
        DB 0b00000011
        DB 0b00000000
        DB 0b00000000 ;o 180°
        DB 0b00000011
        DB 0b00000011
        DB 0b00000000
        DB 0b00000000 ;o 270°
        DB 0b00000011
        DB 0b00000011
        DB 0b00000000
        DB 0b00000000 ;I 0°
        DB 0b00001111
        DB 0b00000000
        DB 0b00000000
        DB 0b00000001 ;I 90°
        DB 0b00000001
        DB 0b00000001
        DB 0b00000001
        DB 0b00000000 ;I 180°
        DB 0b00001111
        DB 0b00000000
        DB 0b00000000
        DB 0b00000001 ;I 270°
        DB 0b00000001
        DB 0b00000001
        DB 0b00000001
        DB 0b00000000 ;s 0°
        DB 0b00000011
        DB 0b00000110
        DB 0b00000000
        DB 0b00000010 ;s 90°
        DB 0b00000011
        DB 0b00000001
        DB 0b00000000
        DB 0b00000000 ;s 180°
        DB 0b00000011
        DB 0b00000110
        DB 0b00000000
        DB 0b00000010 ;s 270°
        DB 0b00000011
        DB 0b00000001
        DB 0b00000000
        DB 0b00000000 ;z 0°
        DB 0b00000110
        DB 0b00000011
        DB 0b00000000
        DB 0b00000001 ;z 90°
        DB 0b00000011
        DB 0b00000010
        DB 0b00000000
        DB 0b00000000 ;z 180°
        DB 0b00000110
        DB 0b00000011
        DB 0b00000000
        DB 0b00000001 ;z 270°
        DB 0b00000011
        DB 0b00000010
        DB 0b00000000
        DB 0b00000000 ;L 0°
        DB 0b00000111
        DB 0b00000100
        DB 0b00000000
        DB 0b00000010 ;L 90°
        DB 0b00000010
        DB 0b00000011
        DB 0b00000000 
        DB 0b00000001 ;L 180°
        DB 0b00000111
        DB 0b00000000
        DB 0b00000000
        DB 0b00000011 ;L 270°
        DB 0b00000001
        DB 0b00000001
        DB 0b00000000
        DB 0b00000000 ;J 0°
        DB 0b00000111
        DB 0b00000001
        DB 0b00000000
        DB 0b00000001 ;J 90°
        DB 0b00000001
        DB 0b00000011
        DB 0b00000000 
        DB 0b00000100 ;J 180°
        DB 0b00000111
        DB 0b00000000
        DB 0b00000000
        DB 0b00000011 ;J 270°
        DB 0b00000010
        DB 0b00000010
        DB 0b00000000
        DB 0b00000000 ;T 0°
        DB 0b00000111
        DB 0b00000010
        DB 0b00000000
        DB 0b00000010 ;T 90°
        DB 0b00000011
        DB 0b00000010
        DB 0b00000000 
        DB 0b00000010 ;T 180°
        DB 0b00000111
        DB 0b00000000
        DB 0b00000000
        DB 0b00000001 ;T 270°
        DB 0b00000011
        DB 0b00000001
        DB 0b00000000
        
        END


Итог: через три с небольшим недели, после получения посылки с часами, я уже смог поиграть в Тетрис на UC-2000:



В процессе написания Тетриса стало ясно, что обработчики событий вызываются всегда, пока они указаны в регистрах RB0-RB3 второго банка (о них я говорил выше), независимо от того, запущена программа или нет. Благодаря этому, можно полностью переделать функционал часов, просто рисуя то, что нужно поверх штатных режимов. Для демонстрации этого, я написал программу, которая добавляет к дефолтному циферблату 6 дополнительных, переключение между которыми происходит комбинацией левой кнопки + MODE. При этом, дополнительные циферблаты органично вписываются в работу остальных функций часов:



Одна из самых востребованных функций современных умных часов — пользовательские циферблаты, потенциально была доступна уже в 83 году, удивительно! Странно, что Seiko никак не развила это направление. И вообще, немного обидно, что потенциал этих часов остался так и не раскрытым полностью. Хотя, конечно, ничего странного в этом нет, из 2017 года все выглядит совсем не так, как из 83-го. Тем более, если вспомнить, сколько не очень успешных попыток изобрести умные часы было после них. Но это, как говорится, уже другая история.

Все описанное я опубликовал на GitHub'е.
Tags:
Hubs:
+139
Comments 35
Comments Comments 35

Articles