Продолжу пока, не забыл, вроде не оффтоп.
"Кинематика" механизма сброса суточного пробега спидометра 3110:
ход штока по прямой, на моем спидометре составляет 4 мм, плечо рычага - 19 мм. Угол на который необходимо поворачивать рычаг - 12,9 градусов.
Сброс без штока осуществляется серводвигателем:
(с описанием работы "серв" можно ознакомиться здесь:
https://arduinomaster.ru/motor-dvigatel ... pravlenie/). Управление серводвигателем осуществляется с помощью широтно-импульсной модуляции ШИМ (бурж. PWM), необходимо подобрать длительность импульсов, чтобы двигатель поворачивал вал с качалкой примерно на 13 градусов, плюс особенность двигателя имеющегося в наличии состоит в том, что он не возвращает вал в исходное положение после снятия управляющего сигнала.
Из документации на двигатель Running degree 110°(when 900~2100 µ sec), на градус поворота вала приходится (2100 - 900)/110 = 10,9 мк с длительности импульса.
В крайние положения двигатель лучше не загонять, выставить вал в центр - 1500 мк с, и двигать относительно этого положения => дельта длительности управляющего сигнала будет составлять 10,9 х 12,9 ~ 130,8 мк с => для сброса пробега нужно повернуть вал на 12,9 градусов, для чего подать на двигатель сигнал с импульсом длительностью 1500 + 130,8 = 1630 мк с и затем для возврата вала в центральное положение, подать сигнал с длительностью импульса 1500 мк с.
Формирование ШИМ можно осуществлять различными способами, выбрано управление с помощью микроконтроллера ATTiny 2313 (datasheet:
https://yadi.sk/i/IAuhmz1dvTsiUA) в корпусе PDIP/SOIC.
Цена на момент написания поста 112 рублей в местных радиодеталях:
http://radioremont.com/product/attiny2313a_pu/
Питание для данной микросхемы 2,7 - 5,5 В раздел Features, Operating voltages даташита стр.1
В автомобиле бортовая сеть -12 В.
Преобразователь 12>5 В собран на типовой схеме интегрального стабилизатора напряжения 7805:
Общая схема дополняется двумя диодами 1N4007: VD1 - для защиты от "переполюсовки" и VD2 - для защиты микросхемы 7805, и на данном этапе имеет вид:
Далее, имеются рекомендации по типовому подключению микроконтроллеров:
http://easyelectronics.ru/podklyuchenie ... ikbez.html
Схема сброса
Резистор на RESET. Вообще в AVR есть своя внутренняя схема сброса, а сигнал RESET изнутри уже подтянут резистором в 100кОм к Vcc. НО! Подтяжка это настолько дохлая, что микроконтроллер ловит сброс от каждого чиха. Например, от касания пальцем ножки RST, а то и просто от задевания пальцем за плату. Поэтому крайне рекомендуется RST подтянуть до питания резистором в 10к.
схема дополняется резистором R1 и конденсатором C5:
Пока схема никакой функции не выполняет, микроконтроллер необходимо настроить.
На борту микроконтроллера имеется 16-ти разрядный счетчик (16 bit Timer/Counter1)стр. 82 datasheet.
Функция - "тикает", как часы, и при этом считает. Длительность по времени одного "тика" задается
кварцем и составляет 1/Q, например 1/3276800 = 3.052х10^-7 секунды.
Кварц подключается к микроконтроллеру согласно рекомендациям страница 23:
Длительность "тика" может задавться другими способами, в этом устройстве - не используются.
Для счетчика выделена специальная облать памяти - регистр TCNT1.
При каждом "тике" значение регистра TCNT1 увеличивается на единицу, от 0 до 65535
(2^16 = 65535, таймер/счетчик 16-ти разрядный), либо от нуля до произвольного значения из
диапазона 0-65535 (задается настройкой режима функционирования счетчика).
Соответственно, при кварце Q = 3276800 (3,2768 МГц), таймер дотикает до значения 65535 за время
3,052х10^-7 х 65535 = 0,02 с. Далее произойдет сброс значения данных в регистре
TCNT1 в 0, и таймер/счетчик продолжит считать от 0 до 65535. Таким образом имеется периодическое колебание
с периодом T = 3,052х10^-7 х 65535 = 0,02 с или с частотой f = 1/T = 1/0,02 = 50 Гц,
необходимой для сигнала управления серводвигателем.
Также во время счета имеется возможность менять состояние определенных выводов (ног)
микроконтроллера - задавать длительность импульса, т.е. иметь на выходе ШИМ, в ATiny есть
походящий режим работы
счетчика Fast PWM Mode стр. 96 datasheet.
Временная диаграмма:
Из описания и временной диаграммы сначала не совсем понятно (было не только мне
https://www.cyberforum.ru/avr/thread2088328.html), но время
до которого "нога" OC1B (вывод МК №16) будет включена задается регистром OCR1B, а последнее значение до которого считает счетчик - задается регистром OCR1A.
дополненная илюстрация:
Счетчик начинает считать от 0, увеличивая значение TCNT1 на единицу, при этом на выходе OC1B держит логическую "1", когда TCNT1 становится равным OCR1B, счетчик устанавливает на выходе OC1B логический "0" идержит его до тех пор, пока значение в TCNT1 не будет равно OCR1A.
Расчет частоты ШИМ для этого режима осуществляется по формуле: f = fclk/(N(1+TOP) (стр. 97),
где fclk - частота кварца, N - величина предделителя частоты (в устройстве N =1), TOP = 65535 - значение до которого считает счетчик (регистр OCR1A).
Чтобы счетчик микроконтроллера заработал в выше рассмотренном режиме FastPWM, необходимо выставить определенные биты в специальных областях, согласно таблице:
стр 106.
Mode 15, Timer/Counter Mode of operation - FastPWM, биты WGM13=1, WGM12=1, WGM11=1, WGM10=1.
Специальные области являются регистрами управления счетчиком Timer/Counter1 Control Register A (сокращенно TCCR1A) стр.104 и Timer/Counter1 Control Register B (сокращенно TCCR1B) стр. 107.
Биты WGM10 и WGM11 находятся в регистре TCCR1A, биты WGM12 и WGM13 находятся в регистре TCCR1B.
Я для себя представляю это двумя виртуальными панелями тумблеров внутри микроконтроллера
Для включения этих "тумблеров", да и вообще, далее понадобится компилятор.
Я работал в IAR AVR (тут имеется описание преимуществ:
https://chipenable.ru/index.php/item/2 и описание некоторых частей нужных для написания первой программы)
Здесь описано создание первого проекта в картинках:
https://chipenable.ru/index.php/item/33 (
https://yadi.sk/d/mDbVnow7ccdqmQ)
На пункте:
Выбираем тип микроконтроллера
General Options > Target > Processor configuration
У меня это ATmega8535.
Из списка выбирается ATTiny2313
Проекту дается какое-нибудь осмысленное название, типа "mileage_reset".
В окне main.c на данном этапе имеется только такая запись:
int main()
{
return 0;
}
Для каждого типа микроконтроллера есть свой заголовочный файл. Для ATMega8535 этот файл называется iom8535.h, для ATMega16 – iom16.h. По идее мы должны в начале каждой программы подключать заголовочный файл того микроконтроллера, который мы используем. Умные люди немного облегчили нам жизнь и написали заголовочный файл ioavr.h. Препроцессор обрабатывает этот файл и в зависимости от настроек проекта включает в нашу программу нужный заголовочный файл.
Итак, следущая строчка программы
Оригинал:
https://chipenable.ru/index.php/item/2
Перед первой строкой добавляется: #include <ioavr.h>
#include <ioavr.h>
int main()
{
return 0;
}
Основу любой сишной программы составляют функции, и любая программа на Си имеет хотя бы одну функцию – main().Вообще-то на примере main() не хотелось бы объяснять синтаксис функций, потому что main() хоть и является функцией, но вызывается не как обычно, а автоматически. С этой функции микроконтроллер начинает выполнение написанной нами программы. Вызовы всех других функций, наших или библиотечных, должны быть записаны в коде. Как вызывается функция, мы увидим дальше.
У функции есть заголовок – int main(void) и тело – оно ограниченно фигурными скобками {}. В тело функции мы и будем добавлять наш код.
Оригинал:
https://chipenable.ru/index.php/item/2
#include <ioavr.h>
int main(void)
{
return 0;
}
Далее указываются данные для регистров OCR1A и OCR1B (для примера - половина периода 65535/2 = 32767):
#include <ioavr.h>
int main(void)
{
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
return 0;
}
Далее в программе настраивается таймер, выше было, что Биты WGM10 и WGM11 находятся в регистре TCCR1A:
стр. 104
При инициализации, включении микроконтроллера TCCR1A=00000000 (initial values, восемь нулей), и для режима FastPWM необходимо "включить" биты WGM10 и WGM11 под номерами #0 и #1 соответственно, установив их в "1" => TCCR1A=00000011
Также во время счета имеется возможность менять состояние определенных выводов (ног)
стр. 104
3-я строка описывает этот необходимый режим: сброс в логический ноль ноги OC1B при совпадении (со значением регистра OCR1B) и установку в логическую единицу вывода OC1B при достижении счетчиком значения TOP (значение в регистре OCR1A). Необходимо "включить" бит COM1B1 под №5 (#5). TCCR1A уже имеет вид 00000011, при изменении бита #5: TCCR1A=00100011.
Запись вида TCCR1A=00100011 нельзя подсунуть в IAR AVR, но можно перевести с помощью стандартного калькулятора ОС Windows, режим "программист", число в шестнадцатиричную систему (HEX) и записать как TCCR1A = 0x23;
Код программы на данном этапе будет иметь вид:
#include <ioavr.h>
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A = 0x23;
return 0;
}
Далее необходимо настроить регистр TCCR1B, выше упоминалось, что здесь находятся биты биты WGM12 и WGM13:
стр. 107
При инициализации, включении микроконтроллера TCCR1B=00000000 и для режима FastPWM необходимо "включить" биты WGM12 и WGM13 под номерами #3 и #4 соответственно => TCCR1B=00011000.
В регистре TCCR1B находится еще одна настройка, предделитель таймера. С помощью данного делителя можно в некоторых пределах изменять длительность "тика" таймера/счетчика:
При инициализации, таймер выключен - биты CS12, CS11 и CS10 равны 0, в схеме предделитель не используется fclk/1 бит CS10=1 =>
TCCR1B=00011001 или в hex TCCR1B=0x19.
Код программы на данном этапе будет иметь вид:
#include <ioavr.h>
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A = 0x23;
TCCR1B=0x19;
return 0;
}
После данных настроек необходимо сконфигурировать порт, с которого будет получатся управляющий сигнал.
https://www.radiokot.ru/start/mcu_fpga/avr/06/
PortX содержит информацию, предназначенную для вывода.
PinX содержит вводимую информацию
DDRX содержит информацию о том, какой канал настроен на ввод, какой - на вывод.
То есть, DDRX определяет, грубо говоря, какая ножка микросхемы будет подключена к PinX, какая - к PortX:
0 - ввод
1 - вывод
В устройстве - необходимо Выводить сигнал с OC1B, согласно программе выше, он же PB4 нога 16 микросхемы (стр. 2), т.е порт "B".
"B" указывается вместо "X" в записи DDRX, тк PB4 = > нужен бит #4 и DDRX = DDRB = 00010000 в hex DDRB=0x10
Код программы на данном этапе будет иметь вид:
#include <ioavr.h>
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A = 0x23;
TCCR1B=0x19;
DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
return 0;
}
Сохранить и откомпилировать согласно
https://chipenable.ru/index.php/item/2 в IAR AVR.
Где-то на этом этапе возникает желание посмотреть, чо получилось, и желательно без пайки.
Для этого можно воспользоваться программой Proteus 8, у меня Release 8.9 SP2 (Build 28501).
Расположить в проекте микроконтроллер ATTiny2313:
Остальную "обвязку", которая фигурировала выше на принципиальной схеме, подключать необязательно.
Далее двойным щелчком левой кнопки мыши вызвать меню, которое заполнить следующим образом:
Отключен встроенный делитель частоты, подключен внешний "кварц", частота кварца в симуляции установлена 3,2768 МГц
Если все делалось согласно статьи
https://chipenable.ru/index.php/item/2, то где-то на компьютере должна быть папка с таким содержимым:
В папке "Debug", в подпапке "Exe":
будет находиться файл прошивки с расширением .hex:
В программе Proteus 8, в меню для компонента ATTiny2313 "Edit Component" в поле "Program file", путем нажатия на изображения папки, выбирается файл прошивки mileage_reset.hex:
Сигнал снимается с вывода ОС1B, на него для контроля вешается частотомер (раздел INSTRUMENTS > COUNTER TIMER):
настраивается по двойному клику левой кнопки мыши следующим образом, Operating mode > Frequency:
и осциллограф (раздел INSTRUMENTS > OSCILLOSCOPE):
Внизу запускается симуляция:
и результат работы программы:
Частота сигнала - 50 Гц, скважность - 2 (коэффициент заполнения 0,5) - как и было запланировано.
Файлы на данном этапе:
https://yadi.sk/d/aqviIdm5yQia7A схема в Proteus
https://yadi.sk/d/2eG10n3nsMYzpQ - прошивка
Далее, записи вида:
TCCR1A = 0x23;
TCCR1B=0x19;
DDRB=0x10;
являются рабочими, но не совсем корректными в тексте программы, поэтому:
https://chipenable.ru/index.php/item/4
текст программы после приведения к "красивому" виду:
#include <ioavr.h>
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10)|(1<<COM1B1);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x23;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
return 0;
}
Чтобы компилятор принял такую запись и не выдал ошибку:
Разрешаем использование имен битов определенных в хедер файле
В General Options > System ставим галочку Enable bit definitions in I/O-Include files
оригинал:
https://chipenable.ru/index.php/item/33
Далее, для аутентичности необходимо, чтобы сброс суточного пробега осуществлялся с помощью ручки сброса расположенной рядом с часами. В оригинале она с помощью троса крутит барабаны, в устройстве же с помощью данной ручки планируется "нажимать" на два микро переключателя (микрика) или кнопки, как реализовано на плате панели приборов ВАЗ 2110:
Нажатие кнопок должно приводить к подаче микроконтроллером импульсов управления серводвигателем.
Подключения кнопки к микроконтроллеру с подтянутой землей.
При таком подключении состояние линии порта ввода вывода будет:
— при отжатой кнопке равно лог.«0»;
— при нажатой кнопке равно лог.«1»;
Если подключить один из контактов кнопки, например, к общему проводу («земле»), а второй к выбранной линии порта ввода/вывода микроконтроллера, который переключен в режим «Вход», то выяснится, что такой метод не работает. При нажатии кнопки линия порта микроконтроллера соединяется с землей, и программа будет считывать лог.«0» с этой линии порта ввода/вывода, но при отпущенной кнопке вывод микроконтроллера не будет соединен ни с чем, что часто и называют «висит в воздухе». В таком случае программа будет считать с вывода и лог.«0» и лог.«1» случайным образом, так как на не к чему не присоединённую линию порта ввода/вывода будут наводится наводки.
Правильное подключение предполагает, что в разомкнутом состоянии вывод микроконтроллера должен быть соединен через резистор, например с шиной питания, а в замкнутом — с землей, либо наоборот. Сопротивление резистора не должно быть слишком маленьким, чтобы ток, текущий через него при замкнутых контактах кнопки не был слишком большим. Обычно используют значения порядка 10-100 кОм.
Оригинал:
https://habr.com/ru/post/256269/?mobile=no
Электрическая схема на данном этапе имеет вид:
В программе при первичной инициализации таймера отключается вывод OC1B, сразу при включении импульсы с него поступать не будут:
TCCR1A|=(1<<WGM11)|(1<<WGM10); //исключается |(1<<COM1B1)
//TCCR1A=0x03
#include <ioavr.h>
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
return 0;
}
Далее необходимо опросить ногу к которой подключена кнопка, выполняется с помощью "if"
if (условие выполняется)
{
сделать что-то;
}
else
{
ничего не делать;
}
таким образом:
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
TCCR1A|=(1<<COM1B1); //Включается вывод OC1B
}
Опрос кнопки надо выполнять постоянно, для реализации желания водителя сбросить суточный пробег:
нужно повторить кусок программы, зациклить его. Для этих целей в Си существуют три типа циклов: for, while и do. Мы используем while.
while(condition){
statement1;
statement2;
statement3;
}
while – имеет условие выполнения (condition), оно записано в скобках () и тело цикла, оно заключено между фигурными скобками {}. В качестве условия цикла может выступать переменная, константа, выражение или функция, возвращающая значение. Перед каждым выполнением цикла происходит проверка условия, если условие истинно, цикл выполняется, если условие ложно, цикл не выполняется. Любое ненулевое значение в скобках оператор воспримет как истину, и цикл будет выполняться.
while(1){ //этот цикл будет выполняться бесконечно
statement1;
statement2;
statement3;
}
Оригинал:
https://chipenable.ru/index.php/item/2
while (1)
{
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
TCCR1A|=(1<<COM1B1); //Включается вывод OC1B
}
}
И общий текст программы:
#include <ioavr.h>
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
while (1)
{
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
TCCR1A|=(1<<COM1B1); //Включается вывод OC1B
}
}
return 0;
}
Далее, для кнопок характерен дребезг контактов:
https://uscr.ru/drebezg-kontaktov-i-spo ... -drebezga/
Программный способ избавления от дребезга
Суть программного способа в том, чтобы внутри программы отличить случайные срабатывания из-за дребезга от настоящего нажатия. Самый простой способ: добавить задержку сразу после первого срабатывания. Тогда при срабатывании кнопки мы останавливаем выполнение программы и дожидаемся, пока контакты кнопки перестанут дребезжать. Ложных срабатываний не будет.
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
__delay_cycles(10000); // Задержка
if ((PIND&(1<<PD1))!=0) //Если кнопка все еще "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
//то выполняется действие:
{
TCCR1A|=(1<<COM1B1); //Включается вывод OC1B
}
}
чтобы компилятор правильно обработал строку "__delay_cycles(10000);" - необходимо добавить в начало программы "#include <intrinsics.h>"
общий текст программы:
#include <ioavr.h>
#include <intrinsics.h>
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
while (1)
{
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
__delay_cycles(10000); // Задержка
if ((PIND&(1<<PD1))!=0) //Если кнопка все еще "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
//то выполняется действие:
{
TCCR1A|=(1<<COM1B1); //Включается вывод OC1B
}
}
}
return 0;
}
Схема в эмуляторе Proteus, дополняется источником постоянного напряжения DC 12 В, стабилизатором 7805 на 5В, кнопкой SB1 и резистором R2 номиналом 10 кОм:
Схема:
https://yadi.sk/d/aTLedMyq7FAxIw
Прошивка:
https://yadi.sk/d/4hSMaZA8GAGRWw
Величина OCR1B равная 32767 была выбрана для примера, чтобы выставить вал двигателя в центральное положение, необходимо подать импульс длительностью 1500 мкс, 1 "тик" таймера 3,052х10^-7 с => число тиков для формирования данного импульса должно составлять: N=(1500x10^-6)/(3,052x10^-7)=4915.2, примерно 4915 "тиков".
OCR1B=4915;
После нажатия кнопки SB1, длительность импульса должна увеличиться до значения 1630 мкс, или до примерно 5341 "тиков" таймера, OCR1B=5341.
Возникает необходимость регистру OCR1B, по нажатию кнопки SB1, присваивать значение некоторой переменной, с осмысленным названием, например angle типа unsigned int и начальным значением 4915. Тип представляет положительное целое число. В зависимости от архитектуры процессора может занимать 2 байта (16 бит) или 4 байта (32 бита), и из-за этого диапазон предельных значений может меняться: от 0 до 65535 (для 2 байт), либо от 0 до 4 294 967 295 (для 4 байт). Применение данного типа при введении в программу некоторой переменной гарантирует, что она никогда не станет отрицательной. Кроме того, если имеются только положительные числа, можно воспользоваться тем, что данные указанного типа могут принимать большие значения, чем данные эквивалентного типа со знаком. Обычно это применяется при адресации памяти и организации счетчиков.
общий текст программы:
#include <ioavr.h>
#include <intrinsics.h>
//объявление переменных:
unsigned int angle=4915;
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
while (1)
{
OCR1B=angle;
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
__delay_cycles(10000); // Задержка
if ((PIND&(1<<PD1))!=0) //Если кнопка все еще "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
//то выполняется действие:
{
TCCR1A|=(1<<COM1B1); //Включается вывод OC1B
}
}
}
return 0;
}
Дальнейшие действия основаны на прерываниях.
https://chipenable.ru/index.php/item/5
разрешается общее прерывание, используя встроенную функцию.
__enable_interrupt();
Функция прерывания
Это не совсем простая функция, поскольку ее вызывает микроконтроллер, и она имеет особенный синтаксис.
Cинтаксис функции прерывания:
Функция прерывания задается с помощью директивы #pragma vector= и служебного слова __interrupt. Функция должна иметь тип void и не должна принимать никаких параметров.
#pragma vector = Address
__interrupt void Name(void)
{
//здесь располагается наш код
}
Name – имя функции, выбираем на наше усмотрение
Address – адрес вектора прерывания, можно задавать числом, можно именами определенными в заголовочном файле микроконтроллера (iom8535.h – раздел Interrupt Vector Definitions)
Будет использоваться прерывание по совпадению Таймера/счетчика 1:
Данное прерывание будет возникать каждый раз, когда содержимое регистра TCNT таймера/счетчика 1 будет совпадать со значением находящемся в регистре OCR1B.
Функция прерывания:
//прерывание по совпадению TCNT=OCR1B
#pragma vector=0x18
__interrupt void Timer1_Compare_Match_B (void)
{
n=n+1;
tmp=n;
if (tmp==5)
{
TCCR1A|=(1<<COM1B1);
}
}
Здесь на каждом окончании положительного импульса будет происходить увеличение временных переменных n и tmp, также на окончании пятого импульса включение вывода ОС1В.
Если оставить включение как в предыдущей логике, то на реальной схеме произойдет подача первого импульса случайной длительности, что приведет к "рывку" серводвигателя и поломке механики спидометра.
Также необходимо разрешить прерывание по совпадению для Таймера/счетчика1:
//разрешить прерывания для Timer/Counter1
TIMSK|=(1<<OCIE1B); стр. 109
переменные n и tmp надо прописать особым образом,
https://chipenable.ru/index.php/program ... ction.html:
volatile unsigned int //Атомарные переменные
//используются в прерываниях,
//частично не оптимизируются компилятором:
n=0,tmp=0;
//функция получения переменной tmp:
__monitor unsigned int get_tmp(void)
{
return tmp;
}
общий текст программы:
#include <ioavr.h>
#include <intrinsics.h>
//объявление переменных:
unsigned int angle=4915;
volatile unsigned int //Атомарные переменные
//используются в прерываниях,
//частично не оптимизируются компилятором:
n=0,tmp=0;
//функция получения переменной tmp
__monitor unsigned int get_tmp(void)
{
return tmp;
}
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
//OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
//Разрешение общего прерывания
__enable_interrupt();
//Разрешение прерывания по совпадению Таймера/счетчика 1 TCNT=OCR1B
TIMSK|=(1<<OCIE1B);
while (1)
{
OCR1B=angle;
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
__delay_cycles(10000); // Задержка
if ((PIND&(1<<PD1))!=0) //Если кнопка все еще "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
//то выполняется действие:
{
//TCCR1A|=(1<<COM1B1); //Включается вывод OC1B
}
}
}
return 0;
}
//Прерывания:
#pragma vector=0x18
__interrupt void Timer1_Compare_Match_B (void)
{
n=n+1;
tmp=n;
if (tmp==5)
{
TCCR1A|=(1<<COM1B1);
}
}
Есть смысл не запускать процесс сброса - движения серводвигателя, если он уже запущен и двигатель куда-то двигается. Поэтому добавляется перменная reset, reset=1; когда двигатель уже в работе и reset=0; когда двигатель остановлен. Соответствующая проверка добавляется в прерывание по совпадению: if (tmp==5 && reset==0) и в обработку нажатия кнопки:
if (reset==0)
{
if ((PIND&(1<<PD1))!=0) .......
Далее в каждом прерывании по совпадению происходит увеличение временной переменной tmp, используя это увеличение можно плавно изменять угол поворота серводвигателя:
В бесконечном цикле сравнивается значение переменной из прерывания с временной переменной tmpold, если они не различаются, то никакого действия не происходит:
if (get_tmp()==tmpold)
{
//ничего не делать;
}
Если tmp не равно tmpold, то необходимо увеличить либо уменьшить значение угла поворота: angle=anle+(какое-то значение) или angle:=angle-(какое-то значение).
В качестве какого-то значения можно задать переменную, с осмыссленным названием delta_angle. Величина данной переменной будет также определять скорость поворота качалки серводвигателя.
Как обычно практика разошлась с теорией, и чтобы сбросить пробег в моем случае пришлось отклонять качалку на больший угол и подавать для крайнего правого положения импульс с длительностью отличной от 1630 мк с, 1967 мк с или в "тиках" таймера 6475.
Скорость поворота практически выбрана 30=delta_angle => 6475-4915 = 1560, и при скорости изменения 30 переменная angle достигнет нужных значений за 52 раза изменения переменной tmp (1560/30=52). Вывод OC1B, для минимизации переходных процессов, включается со значения переменной tmp=5, => 5+52=57 после данного значения переменной tmp необходимо разворачивать поворот двигателя в обратную сторону т.е. angle=angle-delta_angle. После значения tmp=109 (57+52) необходимо остановить двигатель TCCR1A&=(~(1<<COM1B1)); и присвоить переменной reset значение 0, а переменной tmpold присвоить значение tmp:
if (get_tmp()==tmpold)
{
//ничего не делать
}
else
{
if (reset==1)
{
if (tmp>5 && tmp<57)
{
angle=angle+delta_angle;
}
if (angle>right_angle)
{
angle=right_angle;
}
if (tmp>57)
{
angle=angle-delta_angle;
}
if (angle<left_angle)
{
angle=left_angle;
}
if (tmp>109)
{
TCCR1A&=(~(1<<COM1B1));
reset=0;
}
}
tmpold=tmp;
}
OCR1B=angle;
Общий текст программы:
#include <ioavr.h>
#include <intrinsics.h>
//объявление переменных:
unsigned int angle=4915,
reset=0,
right_angle=6475,
left_angle=4915,
tmpold=0,
delta_angle=30,
oldState;
volatile unsigned int //Атомарные переменные
//используются в прерываниях,
//частично не оптимизируются компилятором:
n=0,tmp=0;
//функция получения переменной tmp
__monitor unsigned int get_tmp(void)
{
return tmp;
}
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
//OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
//Разрешение общего прерывания
__enable_interrupt();
//Разрешение прерывания по совпадению Таймера/счетчика 1 TCNT=OCR1B
TIMSK|=(1<<OCIE1B);
while (1)
{
if (get_tmp()==tmpold)
{
//do nothing
}
else
{
if (reset==1)
{
if (tmp>5 && tmp<57)
{
angle=angle+delta_angle;
}
if (angle>right_angle)
{
angle=right_angle;
}
if (tmp>57)
{
angle=angle-delta_angle;
}
if (angle<left_angle)
{
angle=left_angle;
}
if (tmp>109)
{
TCCR1A&=(~(1<<COM1B1));
reset=0;
}
}
tmpold=tmp;
}
OCR1B=angle;
/* обработка нажатия кнопки*/
if (reset==0)
{
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
__delay_cycles(10000); // Задержка
if ((PIND&(1<<PD1))!=0) //Если кнопка все еще "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
//то выполняется действие:
{
reset=1; //работа двигателя, процесс сброса "reset"
oldState = __save_interrupt(); //сохраняется регистр SREG
__disable_interrupt(); //запрещаются прерывания
n=0; //обнуляется значение n-используемой в прерывании
__restore_interrupt(oldState); //восстанавливается SREG
tmp=0;
tmpold=0;
}
}
}
}
return 0;
}
//Прерывания:
//прерывание по совпадению TCNT=OCR1B
#pragma vector=0x18
__interrupt void Timer1_Compare_Match_B (void)
{
n=n+1;
tmp=n;
if (tmp==5 && reset==1)
{
TCCR1A|=(1<<COM1B1);
}
}
Файл для Proteus остается предыдущий,
прошивка на данном этапе:
https://yadi.sk/d/UVsG4Iu0B-1E4Q
визуализация:
Далее, рекомендация из руководства по эксплуатации:
Чтобы исключить данную возможность, на схему через ключ на полевом транзисторе VT1 (
https://yandex.ru/turbo/sdelaysam-svoim ... store.html), резисторы R3, R4 и фильтрующий конденсатор С8 берутся прямоугольные импульсы с сдатчика скорости.
В микроконтроллере настраивается внешнее прерывание на любое изменение уровня на выводе INT1 (по фронту и тылу импульса):
В данном прерывании переменной block присваивается значение 1:
//внешнее прерывание на любое изменение лог. уровня на выводе INT1
#pragma vector=0x04
__interrupt void External_Interrupt_Request_1 (void)
{
block=1;
}
чтобы данное прерывание заработало, необходимо дополнительно включить его в регистре General Interrupt Mask Register (GIMSK) стр. 59
GIMSK=(1<<INT1);
а также задать возникновение прерывания по фронту и спаду импульса в регистре MCU Control Register (MCUCR) стр. 59
MCUCR=(1<<ISC10);
в обработку нажатия кнопки и включения вывода OC1B добавляется проверка переменной block, если block=1 - автомобиль движется и включать сброс суточного пробега нельзя:
if (reset==0 && block==0)
{
//обработка нажатий кнопки "Reset":
if ((PIND&(1<<PD1))!=0) ....
и в прерывании по совпадению:
if (tmp==5 && reset==1 && block==0)
{
TCCR1A|=(1<<COM1B1);
} ...
Общий текст программы:
#include <ioavr.h>
#include <intrinsics.h>
//объявление переменных:
unsigned int angle=4915,
reset=0,
right_angle=6475,
left_angle=4915,
tmpold=0,
delta_angle=30,
oldState;
volatile unsigned int //Атомарные переменные
//используются в прерываниях,
//частично не оптимизируются компилятором:
n=0,tmp=0;
//функция получения переменной tmp
__monitor unsigned int get_tmp(void)
{
return tmp;
}
int main(void)
{
//инициализация счетчика:
OCR1A=65535;
//OCR1B=32767;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
//Разрешение общего прерывания
__enable_interrupt();
//Разрешение прерывания по совпадению Таймера/счетчика 1 TCNT=OCR1B
TIMSK|=(1<<OCIE1B);
//настройка внешних прерываний
MCUCR=(1<<ISC10);
GIMSK=(1<<INT1);
while (1)
{
if (get_tmp()==tmpold)
{
//do nothing
}
else
{
if (reset==1)
{
if (tmp>5 && tmp<57)
{
angle=angle+delta_angle;
}
if (angle>right_angle)
{
angle=right_angle;
}
if (tmp>57)
{
angle=angle-delta_angle;
}
if (angle<left_angle)
{
angle=left_angle;
}
if (tmp>109)
{
TCCR1A&=(~(1<<COM1B1));
reset=0;
}
}
tmpold=tmp;
}
OCR1B=angle;
/* обработка нажатия кнопки*/
if (reset==0 && block==0)
{
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
__delay_cycles(10000); // Задержка
if ((PIND&(1<<PD1))!=0) //Если кнопка все еще "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
//то выполняется действие:
{
reset=1; //работа двигателя, процесс сброса "reset"
oldState = __save_interrupt(); //сохраняется регистр SREG
__disable_interrupt(); //запрещаются прерывания
n=0; //обнуляется значение n-используемой в прерывании
__restore_interrupt(oldState); //восстанавливается SREG
tmp=0;
tmpold=0;
}
}
}
}
return 0;
}
//Прерывания:
//прерывание по совпадению TCNT=OCR1B
#pragma vector=0x18
__interrupt void Timer1_Compare_Match_B (void)
{
n=n+1;
tmp=n;
if (tmp==5 && reset==1 && block==0)
{
TCCR1A|=(1<<COM1B1);
}
}
//внешнее прерывание на любое изменение лог. уровня на выводе INT1
#pragma vector=0x04
__interrupt void External_Interrupt_Request_1 (void)
{
block=1;
}
При такой логике сброс суточного пробега будет срабатывать только до первого импульса с датчика скорости, т.к. никогда до перезагрузки микроконтроллера не произойдет обнуление переменной block.
Необходимо организовать сброс переменной block.
На борту микроконтроллера ATTiny 2313 остается еще один 8-битный таймер/счетчик 0 (8-bit Timer/Counter0) стр. 62
Частоту сброса переменной на данном таймере в режиме Normal аналогично FastPWM можно рассчитать по формуле:
стр. 69
По наблюдениям спидометр адекватно работает до частоты поступающих импульсов 5 Гц, частота же счетчика 8-bit Timer/Counter0 в режиме Normal даже с самым большим предделителем N=1024 будет f= 3276800/(1024x256) = 12,5 Гц и сброс переменной будет происходить в течении еще не закончившегося импульса. Поэтому сброс нужно выполнить хотя бы на 4-е переполнение счетчика Timer/Counter0 и частота составит 12,5/4 примерно 3 Гц.
Добавляется еще одно прерывание, по переполнению Timer/Counter0
//прерывание по переполнению счетчика Timer/Counter0
#pragma vector=0x0C
__interrupt void TIMER0_OVF0 (void)
{
n1=n1+1;
}
В нем будет считатся значение новой переменной n1, если n1=4 -наступило четвертое переполнение, частота 3 Гц, необходимо сбросить переменную block:
//прерывание по переполнению счетчика Timer/Counter0
#pragma vector=0x0C
__interrupt void TIMER0_OVF0 (void)
{
n1=n1+1;
if (n1==4)
{
block=0;
n1=0;
}
}
и обновить переменную n1 для дальнейшего корректного счета.
Также необходимо включить и настроить счетчик Timer/Counter0 стр.73:
//инициализация таймера T0:
TCCR0B|=(1<<CS02)|(1<<CS00);
//режим Normal
//предделитель частоты 1024
и разрешить прерывание по переполнению для счетчика T0:
TIMSK|=(1<<TOIE0);
Общий текст программы:
#include <ioavr.h>
#include <intrinsics.h>
//объявление переменных:
unsigned int angle=4915,
reset=0,
right_angle=6475,
left_angle=4915,
tmpold=0,
delta_angle=30,
oldState;
volatile unsigned int //Атомарные переменные
//используются в прерываниях,
//частично не оптимизируются компилятором:
n=0,n1,tmp=0;
//функция получения переменной tmp
__monitor unsigned int get_tmp(void)
{
return tmp;
}
int main(void)
{
//инициализация таймера T0:
TCCR0B|=(1<<CS02)|(1<<CS00);
//режим Normal
//предделитель частоты 1024
//инициализация счетчика T1:
OCR1A=65535;
//OCR1A - значение до которого считает счетчик, определяющее частоту ШИМ
//OCR1B - значение до которого считает счетчик, определяющее длительность импульса
TCCR1A|=(1<<WGM11)|(1<<WGM10);
TCCR1B|=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
//TCCR1A = 0x03;
//TCCR1B=0x19;
DDRB|=(1<<DDB4);
//DDRB=0x10;
//Конфигурирование порта B, OC1B на выход
//Разрешение общего прерывания
__enable_interrupt();
//Разрешение прерывания по совпадению Таймера/счетчика 1 TCNT=OCR1B
TIMSK|=(1<<OCIE1B)|(1<<TOIE0);
//настройка внешних прерываний
MCUCR=(1<<ISC10);
GIMSK=(1<<INT1);
while (1)
{
if (get_tmp()==tmpold)
{
//do nothing
}
else
{
if (reset==1)
{
if (tmp>5 && tmp<57)
{
angle=angle+delta_angle;
}
if (angle>right_angle)
{
angle=right_angle;
}
if (tmp>57)
{
angle=angle-delta_angle;
}
if (angle<left_angle)
{
angle=left_angle;
}
if (tmp>109)
{
TCCR1A&=(~(1<<COM1B1));
reset=0;
}
}
tmpold=tmp;
}
OCR1B=angle;
/* обработка нажатия кнопки*/
if (reset==0 && block==0)
{
if ((PIND&(1<<PD1))!=0) //Если кнопка "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
{
__delay_cycles(10000); // Задержка
if ((PIND&(1<<PD1))!=0) //Если кнопка все еще "Сброс пробега SB1" нажата (на входе PD1/TXD/(Pin3) лог. "1")
//то выполняется действие:
{
reset=1; //работа двигателя, процесс сброса "reset"
oldState = __save_interrupt(); //сохраняется регистр SREG
__disable_interrupt(); //запрещаются прерывания
n=0; //обнуляется значение n-используемой в прерывании
__restore_interrupt(oldState); //восстанавливается SREG
tmp=0;
tmpold=0;
}
}
}
}
return 0;
}
//Прерывания:
//прерывание по совпадению TCNT=OCR1B
#pragma vector=0x18
__interrupt void Timer1_Compare_Match_B (void)
{
n=n+1;
tmp=n;
if (tmp==5 && reset==1 && block==0)
{
TCCR1A|=(1<<COM1B1);
}
}
//внешнее прерывание на любое изменение лог. уровня на выводе INT1
#pragma vector=0x04
__interrupt void External_Interrupt_Request_1 (void)
{
block=1;
}
//прерывание по переполнению счетчика Timer/Counter0
#pragma vector=0x0C
__interrupt void TIMER0_OVF0 (void)
{
n1=n1+1;
if (n1==4)
{
block=0;
n1=0;
}
}
Ситуация, когда идет сброс суточного пробега и началось движение, на Волге мне кажется маловероятной.
Схема для Proteus:
https://yadi.sk/d/Jk43DwXjCDECzA
Прошивка для микроконтроллера:
https://yadi.sk/d/hh3ZWk1DB_HJ5w
Окончательный вариант схемы электрической принципиальной:
С8 - фильтрующий конденсатор, VD3 - диод от "переполюсовки".
Печатная плата для данной реализации SprintLayout 6:
https://yadi.sk/d/WYboVBCen9YHzw
Вид со стороны дорожек.
Для минимизации размеров без части для внутрисхемного программирования (ISP).
Для переноса написанной нами программы на компьютере, в память микроконтроллера, после компиляции, необходим программатор. Другими словами, именно с помощью программатора, мы и прошиваем микроконтроллер. В интернете есть множество схем программаторов, начинающим могу порекомендовать для сборки программатор Громова, схема очень простая, в крайнем случае, программатор можно приобрести готовый, например, на Али экспресс. Для того чтобы прошить микроконтроллер, необходимо соединить шесть его выводов — ножек, с одноименными пинами, в колодке программатора:
https://el-shema.ru/publ/kontroller/pro ... /9-1-0-336
В качестве платы можно использовать что-нибудь готовое, типа
https://www.tindie.com/products/bugrovs ... ttiny2313/
Я изготовил свою:
В качестве программатора использовалось устройство от "Мастеркит" ВМ9010 (
https://masterkit.ru/shop/1321257):
Запустить под Windows 7 мне его не удалось, но имеется ноутбук под Windows XP.
Схема корректора спидометра 3110 на микроконтроллере Atmega8535:
R12 - Входной сигнал от датчика на КПП;
Выход на спидометр - 18-ая нога микроконтроллера (PD4/OC1B);
Светодиод D8 - индикация режима настройки;
Светодиоды D1-D7 - индикация коэффициента/изменения коэффициента коррекции в режиме настройки.
Корректировка коэффициента проводится по какому-нибудь GPS/Глонас - навигатору.
Схема для Proteus8, прошивка контроллера, EEPROM и содержимое файла main.c:
https://disk.yandex.ru/d/7cQdWB5EXBrOLg
Разводка платы в корпус реле указателя поворотов 575.3777 12В для SprintLayout6:
https://disk.yandex.ru/d/vw-T2MB1vGnOQg
Выносной пульт с диодами VD1-8: