Генерация ШИМ(PWM) для сервомашинки

В просторах интернета, очень много различных реализации программного ШИМ, для сервомашин, но суть одна и та же, генерация на ноге МК импульсов, с регулируемой длительностью. Единственное, что они написаны на разных языках, бэйсике, сишке, а на ассемблере, но только на rtos, который опять же, не отображает полный процесс работы МК, может быть непонятен, или не устраивать по другим причинам.

В принципе наверно можно назвать стандартом управления сервами, импульс высокого уровня длительностью от 0,0008с до -0.0023с. И паузу между импульсами равную от 0.015с, до 0.020с.

И сразу приступим к работе! Есть у меня Микро-Сервомашинка WK-7.6-3 от радиоуправляемого вертолета, её и использую.

Ставим задачу – управлять положением головки сервы  потенциометром,  для этого, надо будет задействовать АЦП. Но прежде чем приступить к расчетам, нужно сделать предварительные расчеты.

По скольку в нашем случае используется внутренний тактовый генератор на один мегагерц, то для него и будем вести расчеты, ну и в случай чего, не долго пересчитать под другую кратную частоту (8МГц, 16МГц).

Самое простое – посчитать паузу,  берем делитель и умножаем на дополнительный коэффициент и делим все это на частоту генератора. Нам надо получить примерно 0,02с – меньше можно, больше – не надо.

(76*256)/1000000= 0,019456 секунд.

1000000 – частота генератора, 256 – делитель, 76 – дополнительный коэффициент.

Нас вполне устраивает такое значение. Теперь усложняем задачу, у нас есть десяти-битный АЦП и восьми-битный регистр дополнительного регистра нашего программно-аппаратного делителя часты. Так уж сложилось, что времени на расчет крайних положений сервы нет, то сделаем как получится.

Из десяти-битного АЦП очень легко извлечь старших восемь бит, программированием бита ADLAR в единицу.Таким образом старшие восемь бит запишутся в восьмиразрядный регистр ADCH. Смотрим страницу 209, даташита на Atmega8.

Теперь у нас есть восемь бит, находящиеся в регистре ADCH, которые мы можем записать в регистр дополнительного коэффициента деления. Если вы жалеете два бита, которые остались в регистре ADCL, то не переживайте! их мы спишем как шум и дребезг. Но и это еще не все! Нам слишком много восемь разрядов, придется еще от одного младшего избавиться, командой LSR. И сейчас вы поймете почему. Рассчитаем крайнее положение (0.0008с). Считаем так же как и в первом случае.

(8*100)/1000000=0,0008 секунд

8 – делитель, 100 – дополнительный коэффициент,  1000000 – тактовая частота.

Второе крайнее положение, рассчитываем исходя из того, что у нас всего семь бит, а значит максимальное десятичное значение 127. 100+127 = 227 – не маловато ли!? мало, а что делать? Придется для предыдущего крайнего случая взять коэффициент на 100 а 120 , так будет примерно посередине, т.е.  первое крйнее положение

(8*120)/1000000=0,00096 секунд

8 – делитель, 120 – дополнительный коэффициент, 1000000 – тактовая частота.
(8*247)/1000000=0,001976 секунд

8 – делитель, 247 – дополнительный коэффициент, 1000000 – тактовая частота.

Приблизительные частоты мы рассчитали, теперь самое интересное! как мы будем писать для этих расчетов,наш код и на как будем его вызывать? Самое правильное, это в начале нарисовать блок-схему. А вызывать будем по прерыванию по переполнению таймера.

Поняв принцип , на котором основана работа, приступаем к написанию кода.

Первым делом, надо сконфигурировать работу таймера и АЦП SBI DDRB,DDB0


SBI DDRB,DDB0 ; PB0 работает как выоход

; АЦП

LDI R16,(1<<REFS0)|(1<<ADLAR) ; опорное напряжение берем с AVCC , и старшие биты в ADCH
OUT ADMUX,R16

LDI R16,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(7<<ADPS0) ; тут включаем АЦП с максимальным делением частоты, и разрешаем прерывание по переполнению таймера.
OUT ADCSRA,R16

; Таймер

LDI R16,(1<<TOIE0) ; разрешаем прерывание по переполнению таймера Timer0
OUT TIMSK,R16

LDI R16,177 ; типа дополнительный коэффициент деления.
OUT TCNT0,R16

LDI R16,(1<<CS02) ; запускаем таймер с делителем 256
OUT TCCR0,R16

Хотелось бы отметить, что в регистр TCNT0, который мы используем как дополнительный коэффициент деления, мы записываем не прямой коэффициент, а разницу между 255 и коэффициентом деления, т.е. 255-78=177.

Следующий шаг, это описание прерывания по завершению аналого-цифрового преобразования.


ADC_I:	IN	R16,ADCH
STS ADC_Data,R16
RETI

Отмечу, что мы записываем полученные с АЦП данные в ячейку ОЗУ, командой STS. Так не просто интересней, а правильней, с точки зрения обмена данными между подпрограммами, да и регистры не занимаем.

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



TIM0_OVF:
SBIS PORTB,PORTB0
RJMP TIM0_OVF2

//если высокий уровень
CBI PORTB,PORTB0 ; подаем низкий уровень на PortB0 и включаем таймер на 20 мс
LDI R16,177 // BGF 255-78
OUT TCNT0,R16
LDI R16,(1<<CS02) // делитель 256
OUT TCCR0,R16

LDI R16,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(7<<ADPS0)
OUT ADCSRA,R16
RETI

TIM0_OVF2:
//если низкий уровень
SBI PORTB,PORTB0 ; подаем высокий уровень на PortB0 и включаем таймер на время, рассчитанное из АЦП

LDS	R16,ADC_Data		; Берем данные из АЦП, записанные в ОЗУ
LSR	R16			; Оставляем только важных для нас семь бит
SUBI R16,-120		; выставляем в начальное положение

LDI	R17,255		; отнимаем 78 от 255
SUB	R17,R16		; т.к. будем считать вверх тормашками

OUT	TCNT0,R17		; отправляем в таймер значение положения

LDI R16,(2<<CS00)
OUT	TCCR0,R16			; и запускаем таймер

RETI

Собственно говоря, все достаточно подробно расписано и добавить нечего, полный исходник можно скачать по ссылке Исходник

Запись опубликована в рубрике Atmega с метками , . Добавьте в закладки постоянную ссылку.

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>