OpenRPNCalc: делаем бескомпромиссный калькулятор

в 23:05, , рубрики: программирование микроконтроллеров
Калькулятор как он есть.
Калькулятор как он есть.

Вообще-то в блогах я обычно выступаю в роли фриковатого научного сотрудника, занудным голосом вещающего про какое-нибудь измерение параметров Стандартной Модели. Но сейчас я попробую совсем другой жанр. Короче говоря, в один прекрасный день я понял, что мне не хватает «железного» научного калькулятора. Пришлось делать самому. 

Но зачем? 

По работе (а я занимаюсь физикой элементарных частиц), кроме серьёзных вычислений, часто нужно делать какие-то прикидки «на коленке», и калькулятор так или иначе приходится использовать. Долгое время я пользовался чем придётся — чаще всего приложением на телефоне или питоновским интерпретатором. Но всё же удобно, когда на столе есть железяка, которая выполняет свою функцию, и выполняет её хорошо. Ну и на Facebook меньше поводов отвлекаться.

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

Как оказалось, есть небольшая фирма SwissMicros, которая выпускает неплохие копии старых программируемых калькуляторов Hewlett Packard (HP) на основе современных ARM-процессоров и симулятора Free42 с открытым кодом. Но опять же, это не идеал — есть некоторые функции (об этом ниже), которые мне пришлось бы программировать, а запускать программы это совсем не то же самое, что нажать на кнопку. 

Вот примерно такая мотивация привела меня к решению сделать свой калькулятор (с трудом удерживаюсь от цитирования персонажа Футурамы). Хотя, честно, полностью рациональным такое решение назвать нельзя, и по большей части оно объясняется диагнозом «руки чесались». 

Для разнообразия я решил в кои-то веки сделать проект, который не выглядит слишком «колхозно», которым реально можно пользоваться, и может быть даже не слишком прячась от коллег по работе. Хотя это и не первый раз, когда я делаю какую-то электронику, до сих пор я в основном возился с DIP-корпусами, макетками и синей изолентой, а тут сам бог велел сделать что-то посовременнее. Соответственно, я получил море новых впечатлений, разбираясь с многими вещами с нуля (программирование для ARM, пайка SMD, разработка в KiCAD и OpenSCAD, 3D-печать). Готовьтесь, сейчас я ими здесь поделюсь. Вдруг кому-то поможет, или кто из более опытных посоветует что-нибудь дельное.

Код, как и вся, с позволения сказать, документация выложены на GitHub. Да, код ужасен. Да, постараюсь исправиться :) 

Концепция

Итак, будем делать научный, непрограммируемый, калькулятор, в который при желании можно добавлять новые функции. Как бывший член экипажа лунолёта «Кон-Тики», я, конечно, обязан был сделать калькулятор с обратной бесскобочной (она же польская, она же RPN) логикой. Благо, её и программировать легче. Ещё одним преимуществом RPN поделился со мной пользователь с сайта Hackaday: такой калькулятор у вас вряд ли кто попросит попользоваться на время.

Итак, что хотелось мне иметь в идеале в своей машинке: 

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

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

  • Режим вычислений с ошибками (точнее, неопределённостями), хотя бы без учета корреляций. Такой режим есть в нескольких калькуляторах на Android, но в «железных», насколько я знаю, такого нигде нет, а жаль. 

  • Некоторые очень специфические функции, в основном связанные с релятивистской кинематикой. 

  • Стандартные режимы отображения SCI (с мантиссой и порядком) и ENG (с порядком, кратным трём) и изменяемым количеством значащих цифр мантиссы (3-10). В режиме ENG, к тому же, можно для удобства сделать показ префиксов единиц СИ (m, k, M и т. д.). Диапазона double будет более чем достаточно. SwissMicros делает калькуляторы c quarduple precision (что ещё ждать от швейцарской-то фирмы?), но в нашей немудрёной науке, если в вычислении используется больше шести-семи значащих цифр — с вычислением что-то не так.

  • Обратная бесскобочная логика со стеком из 4 элементов (X,Y,Z,T) плюс регистр предыдущего результата (LASTx или X1) как у HP или Б3-34. Есть ещё вариант сделать бесконечный стек, как у старших моделей HP, но пока я ограничился более простым вариантом. 

  • Что бы самодельщик ни делал, получаются либо часы, либо погодная станция, поэтому я решил отказаться от любых функций, которые не относятся непосредственно к калькулятору. Часы у меня и на руке есть, а погодную станцию я уже собрал на Raspberry Pi. Единственный датчик, который есть в проекте — напряжение питания батареи.

Электроника

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

С экраном вопросов нет — это будет монохромный ЖКИ дисплей Sharp Memory LCD, как у SwissMicros DM42. Судя по многим отзывам, это практически идеальный дисплей — с хорошей контрастностью, очень малой потребляемой мощностью, и управляется по последовательной шине SPI. В нашем случае это будет модуль LS027B7DH01 размером 2.7” (размер изображения 60x40 мм) и разрешением 400x240 точек. С таким разрешением можно показывать все 4 регистра стека одновременно, да и для режима вычислений с ошибками это будет полезно. Модуль потребляет всего около 20 мкА от 5В в режиме «показываю, но ничего не делаю».

Процессор, недолго думая, я тоже взял из DM42: STM32L476, правда, в корпусе LQFP64 (модификация STM32L476RG). В DM42 стоит тот же процессор в корпусе LQFP100 (100 пинов), но нам не нужны ни внешний Flash, ни SD-карта, так что 64 пина хватит за глаза. Процессор может работать на частоте до 80 МГц, есть 128 кБ оперативки и 1 МБ программного флеша — ought to be enough for anybody. Ну и ещё много всяческого добра, которым мы по большей части не будем пользоваться. 

С клавиатурой вопросов больше. Многие обозреватели жалуются, что у SwissMicros слишком жёсткая клавиатура, быстро кнопки нажимать неудобно, и вообще ничто не может сравниться с классикой HP. Попробуем найти что-то получше, чем купольные кнопки на DM42. Первые попавшиеся тактовые кнопки с AliExpress мне показались слишком тугими. Порывшись по каталогам, я нашёл самые мягкие и достаточно плоские из тех, которые можно заказать, не особо напрягаясь — Panasonic EVQQ2B01W с усилием нажатия 50 г (при том, что обычные кнопки, которые продаются на каждом углу, обычно требуют усилие в 150-200 г). 

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

Схема всего девайса элементарная и показана на рисунке выше. Собственно, кроме STM32 в стандартном подключении, клавиатурной матрицы и пары разъемов (один для ЖКИ, другой для программатора) там есть только преобразователь напряжения 3В в 5В для питания ЖКИ на очень экономном чипе TPS61222. STM запитан непосредственно от литиевой батарейки. Не знаю, хорошая ли это идея, или лучше было поставить стабилизированный преобразователь. Кварц для тактирования процессора решил не ставить (можно и встроенным RC генератором обойтись), но на всякий случай поставил часовой кварц. 

Кстати, по поводу питания ЖКИ. То, что нарисовано сейчас на схеме, хоть и работает, но не совсем правильно. Как оказалось уже после того, как я развел и заказал плату, преобразователь TPS61222 не полностью отключает выходную цепь от питания при низком уровне сигнала «5V_EN», а только выключает сам преобразователь, оставляя на выходе 3В вместо пяти. Надо внимательнее читать даташиты! Попутно оказалось, что и от трех вольт ЖКИ прекрасно работает, и даже контрастность не страдает. Может быть, в следующей версии платы преобразователь можно просто выкинуть? 

Рисовал схему и разводил плату в KiCAD. Почти все элементы там нашлись в стандартной библиотеке, кроме 10-пинового разъёма Molex с шагом 0.5 мм для ЖКИ, его пришлось нарисовать самому по образцу какого-то другого с другим шагом. 

С лазерным утюгом я не дружу, поэтому плату заказал на одном из специализированных сайтов (в плате нет никаких тонкостей, так что любой дешёвый PCB-сервис должен с ней справиться). Дисплей Sharp и разная мелочь продаётся на AliExpress, а вот с покупкой процессора STM я, похоже, пал жертвой дефицита чипов. Три китайских продавца меня кинули (причём один сделал вид, что всё выслал, тянул две недели, после чего уверял, что посылку задержала таможня, а сам поднял цену на тот же чип раза в три). К счастью, в один прекрасный момент несколько сотен нужных чипов «выбросили» на сайте Mouser, из которых я и отхватил несколько штук. На том же Маузере я заказал и кнопки Panasonic, т.к. на Али практически все кнопки noname с непонятно какими характеристиками.

Несмотря на мой изначальный страх, пайка SMD пошла на удивление легко, даже разъём LCD и сам STM32 с ножками с шагом в 0.5 мм паяются без проблем. Оказалось, пора уже было забыть про натуральную сосновую канифоль и перейти на современную бездушную паяльную пасту. Немного больше тренировки потребовала пайка разной мелочи типоразмера 0603 (резисторы, конденсаторы). 

Собранная плата с обратной стороны. К плюсовому контакту батарейки припаян не предусмотренный в проекте штырь для того, чтобы запитывать плату от программатора STLink и измерять потребляемый ток.
Собранная плата с обратной стороны. К плюсовому контакту батарейки припаян не предусмотренный в проекте штырь для того, чтобы запитывать плату от программатора STLink и измерять потребляемый ток.
Собранная плата с уже прошитым STM32, вид спереди. Дисплей прилеплен на двусторонний скотч.
Собранная плата с уже прошитым STM32, вид спереди. Дисплей прилеплен на двусторонний скотч.

Прошивка STM32

Как оказалось, найти информацию для того, чтобы начать программирование на STM32 с нуля, не так-то просто, похоже, из-за того, что альтернативных инструментов очень много, они быстро появляются и устаревают. Наверное, в конце концов лучше учиться писать на голом gcc, но для начала я хотел взять какой-нибудь IDE в стиле «для чайников» с визуальным конфигурированием процессора. В результате я использовал STM32Cube IDE. Я так и не смог добиться, чтобы он работал в Ubuntu, поэтому пришлось ставить ради него целую виртуальную машину с Windows 10. 

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

Функции для работы с дисплеем Sharp я писал сам по даташиту, и там всё оказалось очень просто. Система команд дисплея состоит, практически, из 2-х команд. Первая — это очистка экрана. Вторая — передача массива информации, который состоит из номера строки и 50 байт данных строки. Одна тонкость работы с дисплеем — когда он включен, ему нужен постоянный внешний сигнал около 1 Гц для периодического изменения полярности электрического поля на ЖК-матрице. Этот сигнал генерируется по прерыванию от внутреннего таймера STM. При выключенном ЖКИ этот сигнал надо также выключать.

Собственно саму реализацию алгоритма работы калькулятора я сперва отладил на большом компьютере, написав «заглушки» для функций работы с клавиатурой и дисплеем. STM32L476 поддерживает полную математическую библиотеку gcc, более того, вычисления с плавающей точкой там реализованы в железе, так что всё работает очень быстро. Я понизил частоту работы процессора до 8 МГц, чтобы ограничить максимальный потребляемый ток (который тогда получается около 4 мА при полной нагрузке), при этом никаких видимых задержек при вычислениях не появляется. При меньшей частоте начинает заметно тормозить вывод на экран. 

Для прошивки я купил один из китайских клонов программатора/отладчика ST-Link v2, которые продают где угодно за копейки. С ним вышла небольшая проблема: судя по всему, мой экземпляр не умеет делать «connect under reset», из-за чего STM в состоянии спячки я программировать не могу. Пришлось предусмотреть в прошивке волшебное сочетание кнопок (Shift+RESET), при котором контроллер не уходит в STOP, а ждёт соединения с программатором. Неприятно, но не смертельно. 

Вся прошивка занимает примерно 120 кБ программной памяти. При этом большую часть объёма составляют растровые экранные шрифты (размером от 6x8 до 24x40).

Корпус и клавиатура

Первоначальная наивная задумка была сделать корпус, фрезерованный из алюминия, так, чтоб на века. Но фрезерного станка у меня нет, а после изучения расценок на штучные детали на сайтах типа 3DHubs и Xometry планы пришлось подкорректировать и смотреть в сторону 3D-печати из пластика. Поскольку до этого я ничего, кроме кошечек по готовым чертежам, на своём 3D-принтере не печатал, заодно пришлось разобраться с софтом для 3D-дизайна.

Промаявшись несколько вечеров с глючным FreeCAD’ом (не покупать же программы Autodesk за бешеные деньги), я понял, что гораздо легче написать программу, которая описывает геометрию детали, чем ползать мышкой по меню, поэтому перешёл на OpenSCAD. Хотя у него есть свои ограничения: например, в отличие от FreeCAD, в нём сложно делать фаски и скругления граней. На OpenSCAD дело пошло гораздо веселее.

Корпус и клавиатура, нарисованные в OpenSCAD. 
Корпус и клавиатура, нарисованные в OpenSCAD. 

Корпус состоит из двух частей, скрепляющихся простыми защёлками, без единого болта. Клавиатура представляет собой единую деталь в виде тонкой решётки, к которой за нижнее ребро прикреплены клавиши. После первой попытки изготовления клавиатуры оказалось, что клавиши с такой конструкцией срабатывают очень ненадёжно, в зависимости от того, на какую их часть нажать пальцем. Пришлось делать дополнительную деталь с толкателями, которые передают усилие с клавиши на шток кнопки. Возможно, если бы я заказал кнопки с более длинными штоками (в моих он выступает над поверхностью всего на 0.2 мм), такой проблемы бы не возникло. 

Две части клавиатуры. Можно было бы напечатать их как одну деталь, но тогда пришлось бы делать поддержки и отколупывать их потом от каждой клавиши. На клавиши уже наклеены стикеры с надписями.
Две части клавиатуры. Можно было бы напечатать их как одну деталь, но тогда пришлось бы делать поддержки и отколупывать их потом от каждой клавиши. На клавиши уже наклеены стикеры с надписями.

Корпус получился не очень тонким (полная толщина 12.5 мм), хотя внутри он по большей части пустой. Всё из-за того, что гнездо для батарейки CR2032 довольно высокое (5.5 мм) и с этим сложно что-либо поделать. Для следующей версии надо будет изучить вопрос, существуют ли более тонкие держатели для CR2032, которые к тому же можно установить в вырез на печатной плате. Таким образом можно было бы сэкономить как минимум пару-тройку миллиметров толщины. 

Не буду здесь долго описывать сагу про печать корпуса — скажу только, что он получился лишь с четвёртой или пятой попытки, при этом пришлось тонко настраивать геометрию моего Ender 3 (перпендикулярность осей X и Y), иначе корпус вело винтом после соединения двух половин. Печатал пластиком PETG.

Надписи на кнопках и на корпусе я напечатал на тонкой матовой клеящейся плёнке для лазерных принтеров (чего только не существует в мире канцелярских товаров!). Кажется, эту идею я нашёл на каком-то форуме любителей самостоятельно делать наклейки для русификации клавиатур (да, такое тоже бывает). Сам PDF-файл с надписями и линиями отреза генерируется питоновским скриптом с использованием библиотеки matplotlib. А что ещё можно использовать для графики с обилием математических символов? Напечатанный текст держится на стикерах намертво, их можно даже мыть. Тот стикер, который на корпусе, за счёт большой площади прилип надёжно. К сожалению, стикеры на кнопках не очень прочно прилипают и неосторожным движением их можно отодрать. Впоследствии, возможно, их лучше будет покрыть сверху лаком, но пока надписи на клавиатуре не устаканились, и так сойдет. Минус такой технологии — наклейки будут хорошо видны только на белом корпусе.

Результат

Вот что в результате получилось. Дисплей в режиме отображения ENG с 6 значащими цифрами и активированным режимом вычислений с неопределенностями. 
Вот что в результате получилось. Дисплей в режиме отображения ENG с 6 значащими цифрами и активированным режимом вычислений с неопределенностями. 

Калькулятор в его сегодняшнем состоянии показан на картинке. Большая часть кнопок пояснений не требует. Вот список тех, которые могут быть неочевидными: 

  • F, G — клавиши «shift». Пока в основном задействована только «F». «G» нужна только для обратного направления преобразований η(θ), γ(β), а дальше будет использоваться для новых функций, если таковые появятся. 

  • Mode — изменение режима отображения чисел (FIX, SCI, ENG). 

  • Uncr — включение/выключение режима вычислений с неопределённостями (UNCERT). 

  • Prec — переключение количества значащих цифр мантиссы (от 10 до 3 циклически, с «F» в обратную сторону). 

  • Drop, X​<>Y, Rot↑ — работа со стеком. LASTx — вызов результата предыдущей операции. 

  • DR — переключение измерения углов между градусами и радианами, D<>R — то же с преобразованием значения угла в регистре X. 

  • R→P, P→R — перевод между декартовыми и полярными координатами. 

  • Nσ(x), Nσ(x-y) работают только в режиме неопределённостей, и выполняют, соответственно, вычисление стат. значимости значения в регистре X (значение, деленное на неопредёленность) и значимости разности значений в регистрах X и Y. 

  • η(θ), γ(β), p(z→xy) — те самые специфические функции, которые мало кому нужны: вычисление псевдобыстроты (pseudorapidity), релятивистского гамма-фактора, вычисление импульса в центре масс двухчастичного распада. 

  • «/-/» — обычное изменение знака числа в регистре X, а «±» — переключение между вводом значения и ошибки в режиме UNCERT.

Планы на будущее

В принципе, машинка уже сейчас вполне функциональная, но всегда можно что-то улучшить: 

  • Есть ещё резервы в плане уменьшения энергопотребления. Сейчас калькулятор расходует около 50 мкА в режиме со включенным дисплеем, и 40 мкА с выключенным. Как я уже говорил, полностью питание с дисплея сейчас не снимается, хотя надо бы это пофиксить. Кроме того, можно улучшить алгоритм опроса клавиатуры: сейчас, когда калькулятор включен и работает дисплей, процессор не засыпает, пока нажатая кнопка не отпущена, и потребляет при этом около 4 мА. Надо бы здесь тоже задействовать внешние прерывания и режим STOP. 

  • Функция сканирования клавиатуры понимает только одну нажатую кнопку за раз. Хотелось бы сделать режим «two-key rollover», когда регистрируется кнопка, нажатая до того, как отпущена предыдущая, чтобы кнопки надёжнее срабатывали при быстром наборе. 

  • Как я уже писал выше, можно было бы сделать более тонкий корпус за счёт другого держателя батареи. 

  • Клавиатуре всё ещё далеко до HP и даже до Citizen. Кнопки нажимаются легко, но глубина нажатия всего 0.2 мм — это не очень комфортно. Не знаю, можно ли сделать что-то сильно лучше в домашних условиях, не заказывая кастомную мембранную клавиатуру. 

  • Что хотелось бы из нового функционала. Более удобное отображение значений с неопределённостью, когда есть ненулевой порядок, в виде (1±0.1)e-10 вместо теперешнего 1e-10 ± 1e-11. Больше регистров памяти (пока только один). Целочисленный режим с булевыми функциями и переводом между двоичной, десятичной и шестнадцатеричной системами. Новые функции по мере надобности (вычисление распределений Пуассона и хи-квадрат пока не сделал, но это дело техники). 

По мере работы над проектом, я выкладываю новости в блог Hackaday.io.

P.S. Спасибо @Boomburumза приглашение и советы.

Автор: apoluekt

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js