//

Проекты (Автор: dez)

Измерение частоты вращения - это далеко не великое тайное искусство. Крепим на ротор метку, отслеживаем её датчиком (либо оптическим, либо Холла), полученный интервал между срабатываниями пересчитываем по формуле в то, что нам нужно. Такой простой принцип используется и при измерении скорости автомобиля - зная диаметр колеса и какой редуктор стоит на мосту, мы можем пересчитать меняющуюся частоту сигнала с датчика Холла в скорость движения. Однако, если какая-то из этих двух констант поменялась, то в результате получим гонево недостоверные данные. Примерно такая ситуация возникла с одним экземпляром УАЗ Hunter.

Как мне сказали, датчик скорости и спидометр в конкретной модели оказались расчитаны на мосты типа "Спайсер", но на самом деле были установлены мосты с другим соотношением у редуктора. Причем ЭБУ был в курсе происходящего, потому что пробег в мозгах отличался от пробега на спидометре. Вообще, изначально предполагалось, что я решу эту проблему корректором частоты на attiny 2313, который будет вкорячен в разрыв между датчиком и спидометром. Однако наличие лишь одного 16-битного таймера (вынужденного быть входом, ибо ICP) и обратная зависимость между скоростью и периодом чутка усложняли задачу. В итоге я рисковал либо раздувать код, либо получить крупные и резкие шаги стрелки спидометра на высоких скоростях.

Пока я откладывал это дело, возникла новая потребность и концепция поменялась. Дело в том, что родной спидометр в УАЗах расположен справа от руля, в таком месте, в котором его не шибко удобно наблюдать. И вот так, лёгким движением руки проект корректора частоты превращается в проект цифрового спидометра, монтируемого куда захочется :) В схему добавился семисегментный индикатор, а это уже и красивее, и удобнее отлаживать, а вслед за индикатором и еще одной идейкой тинька поменялась на мегу.... Впрочем, давайте по порядку.

Железо

Принципиальная схема будет очень маленькая. Нам всего лишь нужен датчик скорости, микроконтроллер и 3-разрядный индикатор. Датчик скорости используется 6-импульсный (6000 имульсов на 1 км). Причем он уже стоит на мосту - остается лишь подвести проводок и распознать сигнал. Естественно, в автомобилях кругом 14 вольт и все дела, так что долбиться будем не в ногу, а в базу VT1. Каскад с общим эмиттером, думаю, объяснять не нужно. Замечу лишь, что низкий уровень сигнала с датчика может оказаться "недостаточно земляным", и при слишком низком сопротивлении R1 транзистор будет всё время открыт.

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

Принципиальная схема спидометра / Schematic diagram

Подобные девайсы хочется делать компактными. Рассчитывая применить плату еще в кое-где, я поставил целью габариты не более 25х70 мм. Как в них уложиться? Для начала, подавляем потенциальное желание поставить импульсник и выбираем 5-вольтовый линейный стабилизатор типа lm1117 в корпусе SOT-89. Во-вторых, делаем двухслойную плату и сводим использование сквозного монтажа к минимуму. В-третьих, перенимаем опыт умных людей и умещаем кучу мелких деталей под пузом индикатора. Ну и цепляем индикатор не так, чтобы программу писать удобно было, а так, чтобы дорожки не путались.

Результат - на паре мутных фоток ниже. Слои совпали неидеально, и пришлось одну дорожку луженкой слегка восстановить, но в целом результат для меня сносный. Выкладывать шаблоны платы, пожалуй, пока не буду, из-за ряда недоразумений в них. По большей части они связаны с тем, что я местами забывал про нюансы наколенного производства. Но самый большой прикол, конечно, хорошо виден на фотографиях справа. Что это за тентакли, спросите вы? Это последствия неправильного вращения разъема программирования.

uaz speedometer photo1

Прошивка

После элементарных проверок работоспособности платы пора браться за прошивку, и следующий шаг - вывод информации на дисплей. Понятно, что нужна будет таблица символов с учетом извращенских соединений с пинами контроллера. Есть ещё и другая задача - динамическая индикация с регулировкой яркости. У меня это сделано следующим образом. Таймер 0 настроен на прерывание по переполнению (примерно каждую миллисекунду). В прерывании инкрементируется переменная ticks. В бесконечном цикле при изменении ticks на один из разрядов выводится соответствующий символ из буфера display. Разряд каждый раз переключается выбором другого общего катода. Ну а для задания яркости весь процесс индикации поделен на периоды по 12 тиков (4 обхода всех трех разрядов), в которых индикация разрешена в течение 3 или 9 тиков (для 25% и 75% мощи соответственно).

 //отображение информации

if(ticks - lastDisplay > 0){
    lastDisplay = ticks;
    PORTD &= 0x9B; //снятие выбора с разрядов
    if(ticks%12 < displayMode){ //управление яркостью
    switch(ticks%3){
        case 0: showSymbol(display[0]); //отображение 1 разряда
                PORTD |= (1<<PD2);
                break;
        case 1: showSymbol(display[1]); //2-й разряд
                PORTD |= (1<<PD5);
                break;
        case 2: showSymbol(display[2]); //3-й разряд
                PORTD |= (1<<PD6);
                break;
       default: PORTD &= 0x9B;
                break;
       }
    }
}//--!отображение

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

if(light>200 && displayMode==DISPLAY_DARK) displayMode = DISPLAY_BRIGHT;
else if(light<150 && displayMode==DISPLAY_BRIGHT) displayMode = DISPLAY_DARK;

Но самая суть, конечно, в измерении периода и его переводе в скорость. Танцевать будем от настроек 16-разрядного таймера 1. Для пущей точности затактируем мегу кварцем на 12 МГц (просто потому что такой есть). При делителе 64 таймер будет переполняться за 0.35 секунд, чего хватит для измерения скоростей выше 1.5 км/ч. Для наших целей вполне пойдёт. Осталось только вывести формулу. Если мы знаем период сигнала от 6-импульсного датчика, то скорость найдём следующим образом:

формула - период в скорость

Ну и зная, что таймер работает на 187.5 кГц (12Мгц/64), приходим к формуле:

формула - отсчеты в скорость

И получаем функцию:

//преобразование отсчетов таймера (16 бит, 12 МГц, делитель 64) в скорость
unsigned char countsToSpeed(unsigned int cnts){
    unsigned long counts = cnts; //считаем в лонгах, иначе ловим переполнения
    unsigned long spd = 0;
    if(cnts!=0) spd = 675000000UL/(6000*counts);
    else spd = 0;
    if(spd>200) return 200; //отбрасывание нереалистичных показаний
    return (unsigned char)spd;
}

И вроде бы на этом должно было всё закончиться, но та самая обратная зависимость между скоростью и периодом дала о себе знать на высоких скоростях - показания колебались на 2-3 км/ч и пользоваться спидометром становилось некомфортно. Поэтому напоследок добавился ещё один аспект программы - фильтрация.

Выбор пал на алгоритм простой и известный - "скользящее среднее". Берём окно из нескольких последних значений и вычисляем среднее арифметическое. На практике для скоростей более 80 км/ч окно в 4 измерения не особо хорошо зашло, а вот 8 - в самый раз. Но надо иметь в виду, что на низких скоростях периоды больше и прерывания с новыми значениями приходят реже, поэтому слишком большое окно делать ни к чему. Иначе показания будут "запаздывать" на разгоне или торможении.

//прерывание по захвату
volatile unsigned int averageCap = 0; //скользящее среднее захватов периода
ISR(TIMER1_CAPT_vect){
    static unsigned long movingCaps[8] = {0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF}; //окно скользящего среднего
    static unsigned int inputCap = 0;
    unsigned long movingSum = 0;
    inputCap = ICR1; //захват
    TCNT1 = 0; //сброс счетчика
    overflowCount = 0;
    if(inputCap>500){ //показания правдивы (скорость меньше ~200 км/ч)
        for(char i=0;i<7;i++) movingCaps[i] = movingCaps[i+1]; //сдвиг окна
        movingCaps[7] = inputCap; //новое значение ICR1
        averageCap = 0;
        for(char i=0;i<8;i++) movingSum += movingCaps[i];
        averageCap = (unsigned int)(movingSum>>3);
    }
}

В текущем виде окно постоянное, но на самом деле можно без труда добавить фильтру "адаптивности" и менять размер окна в зависимости от последнего захвата (или вообще отключать фильтр на низкой скорости). Но пока и в текущем виде нормально работает и приносит пользу. А дальше - жизнь покажет.

Статья опубликована 2017-11-18 11:47:15, её прочитали 428 раз(а).

Добавить комментарий

Защитный код
Обновить