Всем привет! Хотелось бы поделиться с сообществом своей историей модернизации тахометра ТХ-193
Неделю назад обратился ко мне один человек с довольно нестандартным заданием — нужно было обеспечить работу древнего тахометра ТХ-193(ВАЗ 2106) с современным двигателем ВАЗ21126(Приора), имеющем систему зажигания с индивидуальными катушками на каждый цилиндр, а значит просто подключить ТХ-193 к катушке зажигания уже не получится. К тому-же заказчик хотел повысить эксплуатационные качества прибора, оставив не тронутым его внешний вид и дизайн. В общем дело кончилось тем, что я взялся выпотрошить электронную начинку прибора и разработать свою, с блэкджеком и шлюхами. Информацию о частоте вращения коленчатого вала тахометр теперь будет получать от ЭБУ Январь 7.2, для чего в последнем имеется специальный вывод.
Под катом фото, видео, схема, исходники и много текста, повествующего о логарифмах и о том как правильно масштабировать данные и отделаться от запятой.
Хард
Начнем с устройства ТХ-193. Механическая часть прибора представляет из себя миллиамперметр классической конструкции, с постоянным магнитом и подвижной катушкой, приводящей в движение стрелку.
Для разработки схемы по сути достаточно было знать о миллиамперметре лишь то, что при токе порядка 10мА стрелка отклоняется до предела, а сопротивление обмотки равно примерно 180Ом. В качестве
Миллиамперметром контроллер управляет, естественно, используя ШИМ. Для чего был задействован 16ти разрядный таймер в режиме Phase and Frequency Correct PWM.
Информация о частоте вращения коленчатого вала передается от ЭБУ в виде импульсов от 0 — 12В. Активный уровень низкий. 2 импульса за 1 оборот коленчатого вала. Для захвата этих импульсов используется внешнее прерывание INT0 и соответствующая цепочка из стабилитрона на 4.7В и резистора, для защиты порта от 12В. В общем и целом схемотехника устройства довольно типична и я с удивлением обнаружил, что только что так много написал о ней. Но да не судите строго, первая статья всё-таки.
Собранный прибор без циферблата теперь выглядит так:
Софт
На самом деле ещё до вычерчивания схемы я оперативно собрал всё это дело на макетке, взяв контроллер в DIP корпусе и сразу же принялся махать стрелкой))
В общем то софт оказался немного интереснее харда.
Начнем с общей архитектуры:
Таймер 0 тикает с частотой 250кГц, а значит период тика = 4мкс прерывание по переполнению происходит с частотой 250кГц / 256 = 0.976кГц
а значит прерывание происходит один раз в 1024мкс. Можно было заморочиться и подогнать это дело ближе к одной миллисекунде путем обновления счетчика таймера в прерывании, но в данной задаче это не к чему. Т.е. мы можем измерять время с точностью 4мкс, что вполне достаточно для заданной точности прибора.
Таймер 0 у нас не только отсчитывает время, но ещё и выставляет флажки для запуска тех или иных задач с определенной периодичностью.
Задачи у нас две. Давать отмашку прерыванию INT0 на измерение периода импульсов на входе и изменять положение стрелки.
Таймер 1 тикает с частотой 16мГц, но т.к. он 16ти битный и используется режим Phase and Frequency Correct PWM — итоговая частота ШИМ оказывается очень небольшой и составляет что-то около 122Гц. Это потому, что таймер тикает сначала вверх, а потом вниз. Зато имеем тру 16битный ШИМ и можем очень точно рулить стрелкой! В даташите найдутся все подробности.
Механика, к слову сказать, оказалась отвратительного качества, плавно двигать стрелку было не реально из-за повышенного трения в механизме, который пришлось для начала хотя-бы смазать трансмиссионным маслом. Но это уже детали.
Была составлена таблица соответствия показаний прибора с соответствующим значением регистра таймера в ШИМ попугаях.
В исходниках это дело называется GAUGE_TABLE и вынесено по привычке в отдельный файл.
Далее было обнаружено, что если просто одним махом изменить ток в цепи амперметра для того, чтобы к примеру передвинуть стрелку на 1000 вперед, то она совершит два-три-четыре колебания в районе целевой отметки, что было совершенно неприемлемо и на что заказчик обращал особое внимание. Дело в том, что эти тахометры изначально имеют такую проблему и несколько раз газанув в такт колебаниям можно заставить стрелку раскачиваться со значительной амплитудой(более половины шкалы!).
С этим нужно было что-то делать. Идея моя заключалась в том, чтобы подводить стрелку к отметке серией более мелких шагов, постепенно приближаясь к цели. Собственно говоря эта часть и является самой интересной и полезной для новичков, т.к. требует некоторой сноровки. Ведь имея дело с микроконтроллером вызов log2() в цикле является, мягко говоря, не самой удачной идеей. К тому-же 8битная архитектура накладывает ещё больше ограничений. Ну а про «плавучку» (floating point) и вовсе нужно забыть. Но все эти трудности, как всегда, приводят лишь к более глубокому пониманию процессов и расчётов, производимых процессором.
Текста почему-то получается всё больше, но не остановиться более подробно на этом моменте я просто не могу!
Итак, понятно, что нам нужна логарифмическая прогрессия. Шаг изменения тока в цепи миллиамперметра должен уменьшаться по мере приближения к целевой отметке. Ресурсы на вес золота, а значит только табличный метод. Точек тоже по возможности минимум.
Начнем с построения логарифмической таблицы.
Всё очень просто: запускаем excel и несколькими взмахами мыши получаем 50 значений логарифма по основанию 2 для последовательности от 1 до 50. Для наглядности строим красивый график.
Прекрасно! То, что нужно! Но во-первых — точек аж 50, а во вторых все числа с плавающей точкой. Это нам никак не подходит!
Поэтому отбираем из имеющегося массива 5 точек с шагом 10. Получаем что-то вроде этого:
Уже лучше. Последовательное приближение к цели всё ещё сохраняется, но точек в 10 раз меньше.
Дальше нужно нормировать полученный набор. Т.е. сделать так, чтобы все значения находились в диапазоне от 0 до 1. Для этого просто разделим каждый элемент на 5,64385618977472 (максимальное значение нашего массива).
Таким образом получаем всё ту-же логарифмическую зависимость, но уже в на много более удобном для дальнейших вычислений виде. Такую таблицу уже можно довольно легко применять, если бы не точка после нуля. Но с этим мы тоже довольно легко разберемся.
Теперь я хочу, чтобы мы приняли красивое значение 1024 за единицу и снова пересчитали нашу таблицу. Получаем
Как видим, форма графика не изменилась, но цифры теперь укладываются в 16битный диапазон и нет никаких дробей.
В исходниках полученный массив называется logtable[]
Масштабирующий коэффициент(если можно его так назвать) 1024 появился здесь не случайно и нужно очень хорошо понимать почему именно 1024.
Во-первых это степень двойки и выбрана она потому, что дорогие операции деления и умножения на степень двойки можно заменить дешевым сдвигом влево/вправо и было-бы глупо не использовать такую возможность.
Во-вторых коэффициент должен выбираться и исходя из масштабов тех данных, к которым он будет применяться. В нашем случае это значения регистра 16ти разрядного таймера, который управляет заполнением ШИМа. Экспериментально было выявлено, что неудовлетворительные колебания стрелки обнаруживаются даже при её резком смещении на 200 об/мин. Т.е. если нужно двинуть стрелку на более чем ~200 об/мин — потребуется сглаживание. Из таблицы GAUGE_TABLE видно, что соседние ячейки в среднем отличаются на 4000 ШИМ попугаев, что соответствует примерно 500 об/мин на шкале прибора. Не трудно прикинуть, что в цифрах смещение стрелки на 200об будет 4000 / 2,5 = 1600 ШИМ попугаев.
Следовательно масштабирующий коэффициент нужно выбрать таким образом, чтобы во-первых он был как можно бОльшим, потому что иначе мы теряем разряды и точность, а во-вторых как можно меньшим, чтобы не заставлять нас переходить от 16ти разрядных переменных к 32х разрядным и не расходовать ресурсы понапрасну. В итоге выбираем наименьшую степень двойки, которая меньше 1600 и обеспечивает достаточную точность. Это и будет 1024.
Этот момент очень важен. Я сам до сих пор порою испытываю трудности с выбором правильных коэффициентов и размеров переменных.
Ну а дальше уж пошло-поехало. Находим в коде реализацию display_rpm() и видим, что для определения конкретного значения в ШИМ попугаях используется таблица GAUGE_TABLE[] и предположение, что между соседними отметками шкала линейна. Для организации изменения тока по логарифмическому закону введен массив на 5 точек pwm_cuve[] в котором содержится набор значений, который нужно последовательно отнять или прибавить(в зависимости от направления движения стрелки) от pwm_ocr1a_cur_val чтобы заставить стрелку двигаться плавно и чётко.
каждый шаг формируется путем умножения значения pwm_delta на коэффициент из нашей таблицы logtable[];
Перед умножением значение предварительно масштабируется путем деления на 1024.
Конечный расчётный пункт назначения стрелки target_pwm записывается в pwm_cuve[] как есть, потому что из-за проблем с округлением и из-за ограничения размерности переменных 16битами точное значение в результате расчётов будет там образовываться весьма не часто, поэтому приходится обеспечить гарантию того, что стрелка окончит свой путь в заданной точке.
В общем то всё вышесказанное по сути заключено в одной строке
pwm_cuve[ table_i ] = pwm_ocr1a_cur_val + (pwm_delta / LOG_TABLE_MAX * logtable[ table_i ]);
Далее главный цикл по сигналу от таймера0 раз в PWM_UPD_PERIOD выгребает значения из pwm_cuve и присваивает их переменной pwm_ocr1a_cur_val, значение которой в прерывании будет присвоено регистру OCR1A, что немедленно приведет к изменению заполнения ШИМа и изменению тока в цепи миллиамперметра.
Вот, собственно и почти все хитрости, за исключением перевода периода, представленного в тиках таймера в частоту вращения коленчатого вала, которая измеряется в об/мин.
Сократилось всё это до engine_rpm = (uint16_t)(15000000UL / (uint32_t)rot_time);
О том как получилась эта цифра мы можем поговорить или не поговорить в следующий раз, потому что и без того текста получилось не мало и явно не многие дочитают даже до этого места.
Честно гвооря в коде применено ещё несколько «хитростей», которые могут показаться новичкам не совсем очевидными. Если кому-то захочется подробнее разобраться — вэлкам в каменты и лс.
Немного видео, как и обещал
На точность показаний не обращайте внимание, стрелка нормально не одета + циферблат не закручен.
Движение стрелки с шагом 1000об/мин одним скачком.
Плавное изменение тока
Дело ясное, что в реальности скачков в 1000об/мин не будет и те незначительные перелеты стрелки, которые всё-же можно наблюдать на видео не станут проблемой. Просто если устранить и их — то можно здорово потерять в быстродействии прибора и его показания будут отставать от реальности.
P.S. Не сказать, что в архиве совсем говнокод, но да, местами можно было сделать красивее. Да, я знаю, что магические числа это плохо и да, я мог бы лучше. С другой стороны потеряться в исходнике в 200строк довольно сложно, поэтому кое-где я позволил себе немного на халтурить.
Просто зарегаться на хабре хотелось уже давно, а написать сколько-нибудь подробную статью по прошествии времени после реализации проекта становится всё сложнее, поэтому я решил, что сегодня будут «вести с полей».
Так что реальный код с реального устройства, собранного за реальный срок в 7 вечеров, которое завтра будет установлено на славный автомобиль ВАЗ 2108 с двигателем 21126 и надеюсь будет ещё долго радовать владельца, согласившегося выложить за мои труды аж 100 вечнозеленых.
Но мы то с вами знаем, что проделал я весь этот путь не только и не столько ради денег. Ведь так приятно, когда ты создал что-то и оно даже работает!
В архиве проект Atmel studio и схема+плата в Altium designer. Изготавливалась плата методом ЛУТ.
До новых встреч!
Автор: mrsom