Генерация аналоговых сигналов микроконтроллером. Ч1

07/08/2013 - 20:43 Павел Бобков

Введение

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

1. Принцип генерации аналогового сигнала с помощью ШИМ (PWM)

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


   Таким образом, меня этот коэффициент, можно генерировать аналоговые сигналы произвольной формы. Причем как переменные, например, синусоида, пила или человеческая речь, так и постоянные (произвольный уровень напряжения). 

1.1 Характеристики сигнала

   Максимальная амплитуда выходного аналогового сигнала будет определяться амплитудой логической единицы цифрового ШИМ сигнала. Если микроконтроллер питается от +5 В, то грубо говоря, амплитуда выходного аналогового сигнала будет от 0 до 5 В. 

   Минимальный шаг изменения аналогового сигнала (разрешение) будет определяться выражением:


dUa = Umax/2^n,


где Umax максимальная амплитуда аналогового сигнала (В), а n - разрядность счетчика реализующего ШИМ. 

   Например, ШИМ сигнал формируется с помощью программного 8-ми разрядного счетчика. Количество градаций ШИМ сигнала, которые можно получить с помощью этого счетчика, равно 2^8 = 256. Тогда разрешение аналогового сигнала при Umax = 5 В будет равно 


dUa = 5/256 = 0,0195 В. 


   Частота ШИМ сигнала будет определять так:


Fpwm = Fcpu/(K*2^n),


где Fcpu - тактовая частота микроконтроллера (Гц), K - коэффициент предделителя счетчика, n - разрядность счетчика. 

   Например, тактовая частота микроконтроллера 8 МГц, коэффициент предделителя равен 8, разрядность счетчика 8 бит. Тогда частота выходного ШИМ сигнала будет равна:

Fpwm = 8000000/(8*256) = ~3906 Гц


   Частота выходного аналогового сигнала будет определяться выражением:

Fa = Fpwm/Ns = Fcpu/(K*2^n*Ns),


где Fpwm - частота ЩИМ сигнала, а Ns - количество отсчетов аналогового сигнала. 
   
   Например, ШИМ сигнал реализуется на 8-ми разрядном счетчике с коэффициентом предделителя равным 8 и тактовой частотой микроконтроллера 8 МГц. В памяти микроконтроллера записано 32 отсчета синусоидального сигнала, которые представляют собой один его период. Тогда частота выходной синусоиды будет равна:

Fa = 8000000/(8*2^8 * 32) = ~122 Гц

   Разрядность ЦАП`a сделанного на основе ШИМ эквивалентна разрядности используемого счетчика. 

1.2 Аппаратная реализация ШИМ

   Все современные микроконтроллеры имеют в своем составе таймеры/счетчики. Один или несколько режимов этих таймеров предназначены для генерации ШИМ сигнала. Как правило этот сигнал генерируется на специальных выводах. Например, у микроконтроллера mega16 фирмы Atmel 8-ми разрядный таймер/счетчик Т0 имеет два режима генерации ШИМ сигнала (быстрый ШИМ и ШИМ с точной фазой), а для вывода сигнала используется пин порта B - OC0 (PINB3).

   Достоинство аппаратной реализации ШИМ сигнала - это низкая загрузка микроконтроллера (прерывание вызывается один раз в период ШИМ сигнала), простота использования и точность (если в системе мало прерываний). Из недостатков можно отметить - ограниченное разрешение счетчиков, невысокая частота, ограниченное число каналов, на которых можно генерировать ШИМ сигналы. Хотя существуют специальные микроконтроллеры специально "заточенные" для генерации большого количества ШИМ сигналов.

1.3 Программная реализация ШИМ

   Также можно генерировать ШИМ сигнал программно. Для этого нужно просто создать программный счетчик и по сигналу аппаратного таймера инкрементировать его значение и отслеживать достижение крайних значений счетчика, в которых ШИМ сигнал меняет состояние. 

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

   Однако, несмотря на это, программная реализация ШИМ`а тоже имеет место быть, если требуется генерировать постоянный аналоговый сигнал или переменный, но с невысокой частотой. 

   Ниже приведен пример кода, который выполняет функцию генерацию аналогового сигнала с помощью аппаратной и программной широтно-импульсной модуляции. Код написан для микроконтроллера atmega16, тактовая частота 8 МГц, компилятор IAR. На выходах PB2 и PB3 генерируются две синусоиды (разной частоты) из 32 двух отсчетов. 


#include <ioavr.h>
#include <intrinsics.h>
#include <stdint.h>

#define SPWM_PIN 2

//таблица синуса
__flash uint8_t tableSin[] =
{
   152,176,198,218,234,245,253,255,
   253,245,234,218,198,176,152,128,
   103, 79, 57, 37, 21, 10, 2, 0,
   2, 10, 21, 37, 57, 79,103,128
};

uint8_t softCount = 0;
uint8_t softComp = 0;

int main( void )
{
//настройка портов
PORTB = 0;
DDRB = 0xff;

//разрешение прерывания по совпадению Т0
TIMSK = (1<<OCIE0);
//режим FastPWM, неинв. шим сигнал, предделитель 8
TCCR0 = (1<<WGM01)|(1<<WGM00)|(1<<COM01)|(0<<COM00)| \
(0<<CS02)|(1<<CS01)|(0<<CS00);

//обнуляем счетный регистр
TCNT0 = 0;
OCR0 = 0;

__enable_interrupt();
while(1);
return 0;
}

//прерывание таймера Т0
#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect(void)
{
   static uint8_t i = 0;
   static uint8_t j = 0;

   OCR0 = tableSin[i];
   i = (i + 1) & 31;

   //программный ШИМ
   softCount++;
   if (softCount == 0){
      PORTB |= (1<<SPWM_PIN);
      softComp = tableSin[j];
      j = (j + 1) & 31;
   }

   if (softCount == softComp){
      PORTB &= ~(1<<SPWM_PIN);
   }
}


1.4 Фильтр для ШИМ

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

   Рассмотрим пример. ШИМ сигнал генерируется аппаратным 8-ми разрядным счетчиком с коэффициентом предделителя равным 8, тактовая частота микроконтроллера 8МГц, количество отсчетов аналогового сигнала - 32.

Частота ШИМ сигнала будет равна:


Fpwm = Fcpu/(K*2^n) = 8000000/(8*256) = ~3906 Гц


Частота аналогового сигнала будет равна:


Fa = Fpwm/Ns = 3906/32 = 122 Гц


   Выберем частоту среза равную 200 Гц и рассчитаем номиналы пассивного низкочастотного RC фильтра. Частота среза такого фильтра определяется выражением:


Fc = 1/(2*Pi*R*C),


где R - номинал резистора (Ом), а C -емкость конденсатора (Ф).

   Задавшись номиналом одного из компонентов можно вычислить номинал второго. Для резистора номиналом 1 кОм, емкость конденсатора будет равна:


C = 1/(2*Pi*Fc*R) = 1/(6.28 * 1000*200) = ~0.8 мкФ 


   Выбираем ближайшее значение из ряда E12 - 0.82 мкФ. При таких номиналах фильтра мы получим уже похожий аналоговый сигнал.

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

Comments   

# alex.S 2013-08-08 05:37
Спасибо, познавательно.
# Peter 2013-08-08 08:02
1. При softCount==soft Comp==0 на выходе программного ШИМ будет коротенький ложный выброс. Пустячок, конечно, но зачем? Использование еще одной переменной решает этот вопрос, заодно улучшая дрожание фронтов - всегда запись в бит порта будет в одном и том же месте.
2. Таймера с прерываниями лучше настраивать в хрестоматийной последовательно сти. Такой подход никогда не подведёт, ни когда глобальные прерывания при этом процессе уже разрешены, ни когда запрещены:
- остановка таймера,
- задать режим работы(не стартуя),
- задать регистры TCNT, OCR, ICR......
- TIFR = (сбрасить флаг, возможно, от предыдущих запусков этого таймера),
- задать TIMSK,
- старт таймера операцией |= (CSii)
# Pashgan 2013-08-08 09:25
1. Я знаю, просто так код на пару строк короче. Аппаратный шим точно так же себя ведет.
2. Запомню.
# Neptun 2013-08-13 13:56
Если использовать фазокоректирров аный ШИМ будет всё оК, выброса не будет - проверял.
# Pashgan 2013-08-13 14:01
Да, в режиме Fast PWM он есть, а в режиме Phase Correct PWM его нет.
# _Артём_ 2013-08-08 12:55
Quoting Peter:

- старт таймера операцией |= (CSii)

Лучше операцией =
Code:
TCCR0 = (1<<WGM01)|(1<<WGM00)|(1<<COM01)|(0<<COM00)| \
(0<<CS02)|(1<<CS01)|(0<<CS00);
# Peter 2013-08-08 14:46
"Отделяй логическое от физического". То, что биты запуска находятся в одном регистре с некоторыми битами установки режимов - не должно Вас смущать. Логически это разные шаги. Соблазн велик, хотите - объединяйте, здесь это не страшно. Но когда в Вашем портфолио будет 5 совершенно разных процессоров - то оцените мудрость разделения сущностей.

Могу сказать как именно Ваш случай привел однажды к трудноуловимому глюку у моего коллеги. Я башку сломал пока понял в чем дело. Представьте: Tаймер. Задается СТС режим с ТОРом, определяемым регистром ICR. Шаги:
-останов,
-задание режима в TCCRnA,
-задание TCNT,
-задание ICR (внимание!!!!!)
-задание TIFR,TIMSK...
- TCCRnB = (режим и запуск) Сэкономили!
Поехали!
Блин - ПЕРИОД ТАЙМЕРА отрабатывается НЕ ТОТ! Нулевой почему-то стал, период-то! (Счастье еще, что быстро заметили это)
Какого хрена?
А вот какого: Вход захвата таймера ICn отключается от захватничества только тогда, когда режим с TOP=ICR задан! А пипл решил сэкономить, режим был задан не полностью, а только в TCCRnA. Парень спокойно суёт ICR=XX, думая что это надёжно сохранится, а на вход IC извне подавалась некая частота. И фронт этой частоты иногда успевал переписать TCNT в ICR пока дело доходило до TCCRnB! Вот откуда получался "нулевой" период. А если б парень нормально задал бы режим, и в TCCRnA и в TCCRnB, то такого бы не произошло.
AVR шуток не любит. Ошибок не прощает))
# _Артём_ 2013-08-08 20:12
Quoting Peter:
Логически это разные шаги. Соблазн велик, хотите - объединяйте, здесь это не страшно.

Шаги объдинять не нужно, но лучше делать их с минимальными затратами.

Quoting Peter:

Но когда в Вашем портфолио будет 5 совершенно разных процессоров - то оцените мудрость разделения сущностей.

Пока есть только AVR Mega/ AVR Xmega/ LPC11 CM-0/ EFM32 и когда-то давно были x51 и Sx52. Но что-то к разделению сущностей не прихожу, а скорей ухожу.

Quoting Peter:
А вот какого: Вот откуда получался "нулевой" период.

Да, бывают случаи...разные. Навочно не придуиаешь.
Quoting Peter:

А если б парень нормально задал бы режим, и в TCCRnA и в TCCRnB, то такого бы не произошло.

Ригистр для сброса таймера в исходное состояние очень бы не помешал.

Quoting Peter:

AVR шуток не любит. Ошибок не прощает))

:)
# САБ 2013-08-19 12:21
Quote:
Таким образом, меня длительность цифрового ШИМ сигнала, можно генерировать аналоговые сигналы
Наверное не длительность, а скважность или коэффициент заполнения (duty).
# Pashgan 2013-08-19 15:04
Да, поправлю.
# Plotnik 2013-08-19 16:56
Pashgan,правиль но ли я понимаю,что используя аппноут AVR314,можно реализовать генерацию DTMF сигнала? Было бы интересно продолжение в этом направление. Спасибо.
# Pashgan 2013-08-19 17:31
Ну да, там же в первом предложении это написано. Quote:
This application note describes how DTMF (Dual-Tone Multiple Frequencies) signaling can be implemented using any AVR microcontroller with PWM and SRAM.
# Plotnik 2013-08-19 17:41
Я прочел на русском:www.gaw.ru/html.cgi/txt/app/micros/avr/AVR314.htm
Мне будет интересно реализовать DTMF сигнал в проекте с матричной клавой 4*4.Ждем вторую часть...
# FM 2017-01-10 19:00
вы писали
"Хотя существуют специальные микроконтроллер ы специально "заточенные" для генерации большого количества ШИМ сигналов."
какие именно можете указать ?
# Pashgan 2017-01-26 23:53
Серия at90pwm, например. На сайте Атмел есть список всех контроллеров с фильтрами. Там подобрать контроллер с требуемым числом ШИМ каналов.
# FM 2017-01-10 19:12
в коде есть
i = (i + 1) & 31;
& 31 - зачем ?
# Pashgan 2017-01-27 00:03
Наложение маски. Это избавляет от проверки на максимум. Счетчик будет считать от 0 до 31.
# Keroronsk 2017-11-01 07:04
А в чём принципиальная разница между формированием аналогового сигнала ШИМ-ом, и, к примеру, ЦАП-ом на R-2R резисторах?

У вас недостаточно прав для комментирования.