Управляем PWM на ATmega32 через UART

Приближаясь к основной цели — получить устройство для управления bluetooth вертолетом, продолжаем осваивать микроконтроллеры. Мною был приобретен avrовский МК ATmega32A в сороконогом DIP корпусе, чрезвычайно удобном для макетирования и экспериментов.

После загрузки тестовой программы, мигающей светодиодом, пришло время подружить микроконтроллер с компом и научится управлять внешней нагрузкой. Для начала я решил написать программу управления через COM порт яркостью светодиода, подключенного к выводу МК. на котором аппаратно реализован ШИМ (PWM)- широтно-импульсная модуляция — вид модуляции цифровых сигналов, при которой частота сигнала остается постоянной, а меняется только его длительность.

То есть необходимо решить две отдельные задачи:

  1. Научится выводить PWM сигнал заданной частоты и скважности на соответствующий вывод МК
  2. Научиться использовать UART интерфейс микроконтроллера.

И одну комплексную — изменять скважность PWM сигнала через UART.

Теории в сети достаточно много, поэтому остановимся на практической реализации. Atmega32 имеет четыре вывода с аппаратным PWM: OC1(PB3), OC1A(PD5), OC1B(PD4), OC2(PD7). За параметры PWM на этих выводах отвечают соответственно следующие таймеры/счетчики микроконтроллера Т0 с 8-ми битным регистром TCCR0, Т1 с 16-ти битным регистром TCCR1 (выводы OC1A и OC1B), Т2 с 8-ми битным регистром TCCR2. То есть для получения ШИМ сигнала с выводов, нужно настроить соответствующий таймер МК, выставив нужные биты в принадлежащем ему регистре.

Решил я написать программу в AVR Studio, которая будет плавно менять яркость светодиода, подключенного к выводу PB3. В качестве подготовительных мероприятий, подключаем библиотеки и в дефайнах прописываем, какой порт и вывод будут использоваться.

#include <avr/io.h>//библиотека ввода/вывода
#include <util/delay.h>//Библиотека функций
 
#define PORT_PWM DDRB // порт МК
#define PIN_PWM 3     // вывод порта B - PB3
#include <avr/io.h>//библиотека ввода/вывода
#include <util/delay.h>//Библиотека функций

#define PORT_PWM DDRB // порт МК
#define PIN_PWM 3     // вывод порта B - PB3

Затем настраиваем таймер ТО,  мне это удобнее сделать в отдельной функции, и потом включить ее в основную программу:

//Программа инициализации ШИМ
void init_pwm (void)
{
 /* Настраиваем 8-ми битный таймер*/
  TCCR0 = 1<<WGM00|1<<COM01|1<<WGM01|1<<CS02;// предделитель 256
  // Начальные значения ШИМ на выводах МК
  OCR0=0x00;
}
//Программа инициализации ШИМ
void init_pwm (void)
{
 /* Настраиваем 8-ми битный таймер*/
  TCCR0 = 1<<WGM00|1<<COM01|1<<WGM01|1<<CS02;// предделитель 256
  // Начальные значения ШИМ на выводах МК
  OCR0=0x00;
}

За что конкретно отвечает каждый бит регистра TCCR0 таймера, можно узнать из даташита МК. Кратко, по умолчанию все биты — нули, WGM00=1 и WGM01=1 — соответствует Fast PWM режиму таймера, бит COM01=1 определяет сигнал как неинвертированый, CS02=1  выставляет предделитель в 256 (то есть уменьшая частоту ШИМ/ подробности в даташите). За величину скважность ШИМ сигнала отвечает регистр OCR0, который при данной настройке может иметь значение от 0 до 255, начальное значение задаем нулевое.

Главная функция программы:

/*--Основная программа-----------------------------------------------*/
int main(void)
{
// бесконечный цикл
 while (1) {
        // увеличиваем яркость светодиода (скважность PWM)
        for (i=0;i<255;i++) {
            OCR0++;
            _delay_ms(5);
        }
        // уменьшаем яркость светодиода (скважность PWM)
        for (i=0;i<255;i++) {
            OCR0--;
            _delay_ms(5);
        }
    }
 return 1;
}
/*--Основная программа-----------------------------------------------*/
int main(void)
{
// бесконечный цикл
 while (1) {
 		// увеличиваем яркость светодиода (скважность PWM)
		for (i=0;i<255;i++) {
			OCR0++;
			_delay_ms(5);
		}
		// уменьшаем яркость светодиода (скважность PWM)
		for (i=0;i<255;i++) {
			OCR0--;
			_delay_ms(5);
		}
	}
 return 1;
}

Собственно. из комментариев к коду все понятно. что происходит в цикле. Не забывая указать в конфигурации проекта МК ATmega32 и частоту кварца (в моем случае16МГц).

Для проверки написамной проги, я пользовался Proteus‘ом. Накидал по-быстрому самую простую схемку:

Установил параметры МК, CKEL fuse 11110, частоту 16МГц, и подключил хексу с прошивкой. После запуска моделирования, на экране виртуального осциллографа увидел плавно меняющуюся длительность PWM на выводе PB3.)

Собственно первая задача решена. Я конечно же прошил эту прогу и в реальный контроллер, интересно все-таки.) Благо с помощью bootloader’a это вообще не проблема.

 Теперь приступим ко второй задаче, а заодно и комплексную закроем. Необходимо инициализировать USART (или UART, в данном случае разницы нет), и научить МК изменять PWM сигнал в зависимости от того, что будет отправлено с компа по этому интерфейсу.

Для начала добавим в начало программы библиотеку прерываний, ибо одно из них, наступающие по приему символа через USART здесь понадобится#include <avr/interrupt.h> //Библиотека прерываний.

Инициализацию интерфейса я написал в отдельной функции, параметрами которой будут скорость передачи данных и частота кварца МК.

//Программа инициализации USART
void USART_Init( unsigned long baud, unsigned long SsFrqnc )
{
    #define XTAL SsFrqnc
    #define baudrate baud
    #define bauddivider (XTAL/(16*baudrate)-1)
    #define HI(x) ((x)>>8)
    #define LO(x) ((x)& 0xFF)
 
    UBRRL = LO(bauddivider);
    UBRRH = HI(bauddivider);
    UCSRA = 0;
    UCSRB = 1<<RXEN|1<<TXEN|1<<RXCIE|0<<TXCIE;
    UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;
 
    sei();//Разрешение глобальных прерываний
 
}
//Программа инициализации USART
void USART_Init( unsigned long baud, unsigned long SsFrqnc )
{
	#define XTAL SsFrqnc
	#define baudrate baud
	#define bauddivider (XTAL/(16*baudrate)-1)
	#define HI(x) ((x)>>8)
	#define LO(x) ((x)& 0xFF)

	UBRRL = LO(bauddivider);
	UBRRH = HI(bauddivider);
	UCSRA = 0;
	UCSRB = 1<<RXEN|1<<TXEN|1<<RXCIE|0<<TXCIE;
	UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;

	sei();//Разрешение глобальных прерываний

}

При таких настройках, разрешены прием и передача символов (RXEN, TXEN), и прерывания по приему (RXCIE). Формат обмена данными идет по 8 бит с одним стоповым. По приходу символа в USART возникает прерывание, обработчик которого такой:

// Прерывание по приходу байта в USART
ISR(USART_RXC_vect)
{
    unsigned char data;
    data = UDR;
    switch(data) {
        case 'A': OCR0=20;
            break;
        case 'B': OCR0=125;
            break;
        case 'C': OCR0=200;
            break;
        default: break;
    }
    put(data);
 
}
// Прерывание по приходу байта в USART
ISR(USART_RXC_vect)
{
	unsigned char data;
	data = UDR;
	switch(data) {
		case 'A': OCR0=20;
		 	break;
		case 'B': OCR0=125;
		 	break;
		case 'C': OCR0=200;
		 	break;
		default: break;
	}
	put(data);

}

Для тестов, я определил, что при получении символа (в регистр UDR), изменяем скважность PWM. При этом установим дискретные значения скважности для трех символов A, B, С. Ни отправим полученный символ обратно в COM порт функцией put(), описание которой следующее:

//Запись в UART:
void put(unsigned char data) { // Put a char
    while (!(UCSRA & (1<<UDRE)));
    UDR = data;
}
//Запись в UART:
void put(unsigned char data) { // Put a char
	while (!(UCSRA & (1<<UDRE)));
	UDR = data;
}

Теперь главная функция программы выглядит так (бесконечный цикл пустой, все что раньше там было — удалено):

/*--Основная программа-----------------------------------------------*/
int main(void)
{
 PORT_PWM = 1<<PIN_PWM;     //Инициализация порта PB3 как выхода.
 init_pwm();                //Вызов процедуры инициализации PWM
 USART_Init(19200,16000000);//Инициализация USART
 
 // бесконечный цикл
 while (1) {
    }
 return 1;
} // закрываем main
/*--Основная программа-----------------------------------------------*/
int main(void)
{
 PORT_PWM = 1<<PIN_PWM;     //Инициализация порта PB3 как выхода.
 init_pwm();				//Вызов процедуры инициализации PWM
 USART_Init(19200,16000000);//Инициализация USART

 // бесконечный цикл
 while (1) {
	}
 return 1;
} // закрываем main

B Proteus добавил к схеме виртуальный терминал. При запуске моделирования появляется окно терминала, если ввести символ ‘A’, ‘B’, ‘C’ — величина ШИМ’а изменяется в соответствии с определенными в проге значениями, а в ответ приходит посланный символ.

Теперь можно прошить и в настоящий МК и поиграться.)

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

         
Подписаться на новые статьи блога:

10 комментариев: Управляем PWM на ATmega32 через UART

  1. redbeard говорит:

    Отлично! Спасибо 🙂

  2. Juri говорит:

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

  3. Juri говорит:

    Не могли бы вы мне помочь? Мне нужно тоже самое точь-в-точь, но у меня ATmega328P, там настройка uart’a идёт как-то странно, плюс почему-то в proteus’e нельзя в настройках МК установить внешний кварц на 16Mhz. Очень нужно, уже которую неделю маюсь с этим

    • Juri говорит:

      На самом деле у меня pwm работает, но вот uart никак не хочет отправлять и получать данные

      • Delirium говорит:

        Добрый день! Я нашел проект из статьи у себя на компе, может даже ту же версию, посмотрите http://skabinsky.ru/wp-content/uploads/2015/04/PWM_m32.zip Нужны подробности, чтобы помочь, а именно — что конкретно не работает? Порт иницализируется? Я у Atmega168 (вроде ближе к вашему МК) настраивал UART, вроде работало. Найду исходники — выложу.

        • Juri говорит:

          Ну как сказал уже, pwm настроен, и он бы работал, если бы через uart смог что-то отправлять.
          Всё вроде также настроил для uart, но в proteus’e запускаю терминал — отправляю и ничего не происходит…
          Заметил, что работа программы очень зависит от настроек на кварц в Протеусе.. Может что-то в нём..
          Простите, могу прямо тут выложить код
          <br /> #define F_CPU 16000000UL<br /> #include<br /> #include<br /> #include </p> <p>// minun muuttujat<br /> #define PORT_PWM DDRB<br /> // PortB, 3 pin OC2A<br /> #define PIN_PWM 3</p> <p>void usart_init( unsigned long baud, unsigned long SsFrqnc){<br /> #define XTAL SsFrqnc<br /> #define baudrate baud<br /> #define bauddivider (XTAL/(16*baudrate)-1)<br /> #define HI(x) ((x)>>8)<br /> #define LO(x) ((x)& 0xFF)</p> <p> UBRR0L = LO(bauddivider);<br /> UBRR0H = HI(bauddivider);<br /> UCSR0A = 0;<br /> UCSR0B = 1<<RXEN0 | 1<<TXEN0 | 1<< RXCIE0 | 1<< TXCIE0;<br /> UCSR0C = 1<<UCSZ00 | 1<< UCSZ01;</p> <p>}<br /> ISR(USART_RX_vect){<br /> unsigned char data;<br /> data = UDR0;<br /> switch(data){<br /> case '0': OCR2A=0;<br /> break;<br /> case '1': OCR2A=51;<br /> break;<br /> case '2': OCR2A=102;<br /> break;<br /> case '3': OCR2A=153;<br /> break;<br /> case '4': OCR2A=204;<br /> break;<br /> case '5': OCR2A=255;<br /> break;<br /> default: break;<br /> }<br /> put(data);<br /> }<br /> void put(unsigned char data){<br /> while(!(UCSR0A & (1<<UDRE0)));<br /> UDR0 = data;<br /> }<br /> void init_pwm(void){<br /> TCCR2A = 1<<WGM20 | 1<<COM2A1 | 1<<WGM21;<br /> TCCR2B = 1<<CS22|1<<CS21;<br /> OCR2A = 0x00;<br /> OCR2B = 0x00;<br /> }</p> <p>int main(void){<br /> PORT_PWM = 1<<PIN_PWM;<br /> init_pwm();<br /> usart_init(19200,16000000);<br /> while(1){}<br /> return 1;<br /> sei();<br /> }<br />

          • Delirium говорит:

            sei(); — вынести нужно из бесконечного цикла вверх (в инициализацию), ибо зачем каждый цикл разрешать прерывания? Предлагаю попробовать put(«A»); вынести в while(1) и проверить, работает ли отправка в UART символов. Настройки UART для Atmega 168:

            #define BAUD 9600 // скорость UART
            #define MYUBRR ((F_CPU/(16UL*BAUD))-1)
            #define sleepTime 300

            //настройка uart
            inline void init_uart(void)
            {
            /*Set baud rate */
            UBRR0H = (unsigned char) (MYUBRR>>8);
            UBRR0L = (unsigned char) (MYUBRR);
            /*Enable receiver and transmitter */
            UCSR0B = (1<

  4. Juri говорит:

    Вот ссылка на картинку с Протеуса
    http://s012.radikal.ru/i320/1504/21/32707cd88676.png

    • Delirium говорит:

      Ок. Только во-первых мелко, а во-вторых ничего не понятно. Симуляция запускается? Сделайте скрин, где Мк «работает» и на выходе есть PWM с какой-то там скважностью.

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

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

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.