Тахометр на Attiny.Продолжаем знакомство с AVR.

AVR
Для тех, кто не знает, тахометр-прибор для измерения частоты вращения.В этой статье я расскажу, как сделать такой прибор на микроконтроллере AVR Attiny2313.Попутно мы разберем такие функции МК, как таймер/счетчик, прерывания по таймеру/счетчику, внешние прерывания и работа с LCD дисплеем и переменными.
Для начала оговорим принцип работы и необходимые для изготовления детали.Прежде всего, нужен собственно датчик, который будет считать обороты.Его я взял из старого принтера.Там он использовался для определения наличия бумаги.
Датчик работает так: внутри него находится оптопара, состоящая из инфракрасного светодиода и фоторезистора.Когда объект(желательно белого цвета для лучшего отражения) находится над оптопарой свет, излучаемый светодиодом, отражается от него и попадает на фототранзистор.Это то же самое, что и обычный транзистор, только база управляется не подаваемым на нее током, а светом.Следовательно транзистор открывается, передавая логическую единицу на МК.
Схема оптрона
Так выглядит датчик, который использовал я:
Датчик
Транзистор нужно подключать к питанию с резистором 4,7кОм, можно взять другой номинал, но 4,7кОм оптимальный вариант по току:
Схема подключения оптрона
Для того чтобы, мы могли считать информацию понадобится дисплей.У меня под руками был однострочный 16-символьный алфавитно-цифровой экземпляр.Для регулировки контрастности дисплея желательно прикрутить переменный резистор.Даташит моего дисплея(ROHM 2034R) гласит, что нужен резистор от 10 до 20 кОм, но традицию использовать не то, что надо, а то что есть никто не отменял, поэтому я откопал резистор от 0 до 33 кОм.Если не найдете нужный переменный резистор или просто не захотите его ставить, можно сделать простой делитель напряжения из двух резисторов.Контрастность у дисплея при этом регулироваться не будет, конечно.
Дисплей+резистор

На этом скромный список деталек заканчивается, приступаем к составлению схемы.
Дисплей подключается к микроконтроллеру так, как показано на схеме(в данном случае для порта В).
Схема подключения дисплея
Аналогично для порта С:
LCD
Переменный резистор, как уже было сказано используется для регулировки контрастности, подключается к 3му выводу дисплея(обозначен, как LCD HEADER V0).
Схема вышла, в общем, несложная.В архиве выложу ее полностью в формате spl(SPLAN).

Теперь время заняться прошивкой.
Любой проект с дисплеем начинается с того, что мы прописываем микроконтроллеру порт, на котором висит дисплей.Причем, это делается с тэгом #asm(тэг для ассемблерной команды).
Адрес конкретного порта ищем в библиотеке МК, которая валяется в папке с компилятором:
PORTB
#asm
.equ __lcd_port=0x18
#endasm

Подключаем 2 необходимые библиотеки:

#include <lcd.h> //библиотека работы с дисплеем
#include <tiny2313.h> //библиотека МК

После подключения библиотек объявляем переменные.int-целые числа от -32768 до 32767.Для числа оборотов в секунду этого хватит.

int rps=0; //Rotates Per Second - число оборотов в секунду
int pr=0; //Счетчик переполнений таймера

Главная программа:
void main()

Порт D настраиваем на ввод и ставим единицы по умолчанию на все его биты.
{
DDRD=0x00;
PORTD=0xff;

Инициализируем дисплей.В скобках пишем число символов в строке.
lcd_init(16);


Дальше надо настроить таймер.Он примечателен тем, что тикает даже во время выполнения программы.В этом его главное отличие от обычной задержки(команды delay).Эта задержка полностью остановит МК, но в данном случае это недопустимо, так как прибор должен считать обороты без остановок.Тут и приходит на помощь таймер/счетчик.
Смысл программы такой: по внешнему прерыванию(от датчика) запускается цикл, в котором прибавляется единица к переменной rps.Таймер в это время продолжает тикать.Как только он доходит до 1 секунды, стартует другое прерывание по таймеру/счетчику.В нем переменная rps выводится на дисплей и обнуляется.Таким образом, частота обновления показаний 1 секунда.

В Attiny2313 есть 2 таймера 8 и 16 разрядный.Мы воспользуемся 8-разрядным.Он обозначается, как таймер/счетчик 0. 8 разрядов таймера означает, что в нем 2 в 8 степени позиций = 256.
Настройка таймера начинается с регистра управления TCCR0:
Регистр TCCR0
Расчеты таймера основаны на тактовой частоте, а в этом регистре мы выбираем предделитель тактовой частоты, с которой будет тикать таймер.Это очень сильно облегчает расчеты.К примеру, если МК работает с частотой 8 мегагерц, поделив ее на 1024, мы получаем сравнительно небольшое число, работать с которым намного легче.
Программируем биты в соответствии с таблицей:
Выбор предделителя
TCCR0=0x05;//делим тактовую частоту на 1024

На этом этапе нужно определиться с начальным значением таймера.Оно следует из того, сколько раз должен переполниться таймер для достижения определенного времени в соответствии с тактовой частотой с предделителем.
Расчеты:
8000000/1024=7812,5 (делим тактовую частоту МК на выбранный предделитель)
7812,5/256=30,52 (считаем число переполнений)
Значит потребуется примерно 30 переполнений всего таймера(с 0) для достижения 1 секунды.
Ставим таймер в 0.
TCNT0=(0);

В регистр TIFR-флаг переполнения таймера.Когда таймер переполнен автоматически устанавливается 1.
Этот регистр нужно сбросить в 0:
TIFR=0;

TIMSK — регистр прерываний по таймеру.
Регистр TIMSK
Разрешаем прерывания по таймеру/счетчику 0.
TIMSK=0x02;

Также нам понадобятся и прерывания по внешнему сигналу(в данном случае с датчика).
Они управляются регистром GIMSK.INT1(PD3)-выход микроконтроллера, к которому будем цеплять датчик.
Регистр GIMSK
Разрешаем прерывания по внешнему сигналу с порта INT1:
GIMSK=0x80;

Регистр МCUCR управляет видом внешних прерываний.Для тахометра подойдет прерывание по спадающему фронту.Только в этом случае он будет показывать реальное число оборотов.
Регистр MCUCR
Программируем регистр в соответствии с таблицей:
Таблица управления прерываниями
MCUCR=0x09;

Ассемблерной командой разрешаем все прерывания:
#asm
sei
#endasm

Чтобы программа никогда не завершалась добавляем бесконечный цикл:

count:while(1);
goto count;


Помимо главной программы в проекте присутствуют еще 2 подпрограммы прерываний-по таймеру и по внешнему сигналу.
По таймеру:
Обозначаем начало подпрограммы прерываний:
interrupt[TIM0_OVF]void timer0_overflow(void)

Нужно снова обнулить таймер, чтобы он начал отсчитывать новую секунду.
{
TCNT0=(0);

Мы посчитали, что для отсчета одной секунды надо, чтобы таймер переполнился 30 раз.
Поэтому, как только счетчик прерываний(отдельная переменная pr, объявленная вначале)станет равен 30, число оборотов в секунду выводится на дисплей, а обе переменные обнуляются.

С выводом переменной на экран пришлось повозиться отдельно.Как выяснилось, выводить напрямую переменную нельзя, нужно либо сделать из нее строку, либо преобразовать ее в последовательность номеров из таблицы символов(есть в даташите на любой дисплей):
Таблица символов LCD
Первый способ можно устроить с помощью функции sprintf, но она ест слишком много памяти, поэтому на тиньках корректно не работает.
Воспользуемся вторым способом.Будем выводить переменную посимвольно с помощью команды lcd_putchar('код символа в таблице ').Переходим от цифры к коду символа путем деления переменной с остатком на числа кратные 10 и прибавлением числа 48(для совпадения с табличным значением).В этой программе прописан вывод четырех символов, но можно изменить ее для любого другого числа.Недостаток метода-вместо чисел превышающих 9999 будут выводится левые знаки, но вряд ли что-то сможет крутиться с частотой 10 килогерц, да и датчик от принтера потянет максимум 1 килогерц, если верить даташиту.
if (pr==30)
{
lcd_gotoxy(0,0);//переходим на 1-ую строку,1-ый символ(представьте координатную плоскость x и y)
lcd_putchar(rps/1000+48);
lcd_putchar(rps%1000/100+48);
lcd_putchar(rps%1000%100/10+48);
lcd_putchar(rps%1000%100%10+48);
rps=0;
pr=0;
}

Если таймер переполнился но, счетчик еще не достиг 30, просто прибавляем к нему 1 и ждем следующего переполнения.
else
pr=pr+1;
}

Вот и вся программа.Шьем МК и испытываем девайс в действии.
В таком оформлении выглядит не очень красиво, но работает.Можно будет сделать что-нибудь покрасивее, как сделаю, обязательно выложу фотки.
Окончательное оформление. Добавлены 2 конденсатора на питание, кнопка и светодиод(для экспериментов), а также разъем под программатор Громова.
Окончательное оформление
Старое оформление тахометра
Наконец, видео.В качестве демонстрационного полигона пришлось соорудить из подручных средств «вентилятор»:

Еще испытал это изобретение на шуруповерте.Результат порадовал.Показал 4 оборота в секунду, производителем заявлено 250 об/мин.Из целых вариантов показаний 4 самый точный, который прибор мог вывести, т.к. 4*60=240, а 5*60 это уже 300 :).
В архиве: проект CVAVR под 8 мегагерц с исходниками, прошивка, схема(SPLAN), фьюзы для Attiny2313(8 мегагерц от встроенного тактового генератора).
Во втором архиве, на всякий случай, даташиты на дисплей и датчик.Мало ли что, может пригодятся…

  • 0
  • 30 июня 2011, 12:59
  • rad
  • 2
Файлы в топике: taxometr.zip, datasheets.zip

Комментарии (17)

RSS свернуть / развернуть
count:while(1);
goto count;


хе-хе. если while не сработает, поможет goto?)

interrupt[TIM0_OVF]void timer0_overflow(void)
{
TCNT0=(0);
if (pr==30)
{
lcd_gotoxy(0,0);
lcd_putchar(rps/1000+48);
lcd_putchar(rps%1000/100+48);
lcd_putchar(rps%1000%100/10+48);
lcd_putchar(rps%1000%100%10+48);
rps=0;
pr=0;
}


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

алсо, ставь пробелы после точки!
0
=)))
0
Напомнило бородатый анекдот:
Зачем тебе два джампа подряд?
А вдруг первый не сработает!
0
Может это была таблица переходов?
0
Перестраховался :-)
0
А "%1000%100%10" вас не смутило? Вдруг остаток от деления на 10 сразу не сработает, надо постепенно :)
Вообще автору респект и плюсик.
0
хе-хе. если while не сработает, поможет goto?)
Где не спасет перезапуск, поможет Format
0
#asm
.equ __lcd_port=0x18
#endasm

А не проще сделать так:
#define __lcd_port PORTB

?
0
Equ — это директива ассемблера, а define — препроцессора Си. В библиотеке CVAVR почти весь код работы с ЖКИ написан на ассемблере, в том числе и с портом подключения, поэтому в данном случае define не будет иметь никакой силы.
0
Нда. Неплохой пример, как не надо проектировать и реализовывать программу. В плане проектирования — низкая точность отсчета опорного интервала времени, хотя ничто не мешает сделать его с точностью до нескольких тактов, да и вообще считать количество оборотов в секунду идея так себе. И работа с дисплеем в прерывании туда же. В плане программирования… Ну, про это уже написали.
-1
  • avatar
  • Vga
  • 30 июня 2011, 19:29
А можно поинтересоваться, чем грозит работа с дисплеем в прерывании?
Ну а в плане идеи, она специфическая, конечно, в повседневной жизни может и не каждому пригодится, но ее практическое применение все же возможно.
0
Потому что не надо слоупочить в прерываниях, делая там ненужную работу. А либа дисплея еще и скорее всего содержит задержки. Да и не единственное это, чем код отличился.
Идея мерить частоту тоже так себе, точнее мерить период. Если же мерить частоту так, как это делаешь ты — разрешение получается всего лишь 60 RPM. Это в большинстве случаев больше влияет на точность, чем отсчитываемый с точностью около 2% опорный интервал.
0
count:while(1);
goto count;
… ну если нет надежды в while() — то тогда я не знаю чему можно доверять в Си
0
В регистр TIFR-флаг переполнения таймера.Когда таймер переполнен автоматически устанавливается 1.
Этот регистр нужно сбросить в 0:
TIFR=0;
Что-то ты намудрил с регистром флагов TIFR.
Во-первых в AVR флаги сбрасываются записью в них логической единицы.
Во-вторых зачем в инициализации записывать в этот регистр 0, если флаг не установлен то там 0, ну а если установлен то это ни к чему не приведёт.

Ну и добавлю насчёт прерывания по переполнению Тimer0, в данном случае не необходимости обнулять счётный регистр TCNT0=(0), т.к. при прирывании он уже обнулён.
0
Идея очистить TIFR взята из учебной книжки по AVR(Голубцов М.С.Микроконтроллеры AVR: от простого к сложному).Там в примере с таймером при его настройке такой код:
TIFR=0; //очистить флаги прерываний по Timer0

Если интересно, могу эту книгу в электронном виде скинуть…
0
Зачем переменная pr типа int, если она больше 30 не становиться.

Расчеты:
8000000/256=31250 (делим тактовую частоту МК на выбранный предделитель)
31250/256=122,07 (считаем число переполнений)
Так будет точнее
0
.Следовательно транзистор открывается, передавая логическую единицу на МК.
В вашей схеме он прижимает вход МК к земле = логический ноль.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.