AVR на C — просто? Часть 3

3. Аппаратный ШИМ (PWM).

3.1. Таймеры/счетчики и регистры управления.

ШИМ (широтно импульсная модуляция) в микроконтроллере реализуется с использованием аппаратных таймеров. Теорию ШИМ рассматривать не будем. Рассмотрим лишь получение плавного изменения напряжения/тока на основе ШИМ. Для начала разберемся как управлять таймерами.

Имеется три таймера два по 8 бит (TC0, TC2) и один 16 бит (TC1). Настройка происходит регистрами TCCRxA, TCCRxB(x – означает выбранный таймер 0, 1 или 2).

Разберем чуточку подробнее на примере таймера TC1 в режиме 8 бит:

Регистр TCCR1A

бит

7

6

5

4

3

2

1

0

 

COM1A1

COM1A0

COM1B1

COM1B0

WGM11

WGM10

Бит 7, 6 (COM1A1, COM1A0)- настройка поведения вывода A по событию.

Бит 5, 4 (COM1B1, COM1B0)- настройка поведения вывода B по событию.

COM1A1/

COM1B1

COM1A0/

COM1B0

События

0

0

Нормальная работа порта, таймер отключен от вывода.

0

1

Переключение порта с 0 на 1 или обратно при совпадении.

1

0

Сброс на 0 при совпадении.

1

1

Установка 1 при совпадении.

Бит 3, 2 — зарезервированы.

Бит 1, 0 (WGM11, WGM10) — позволяют настроить ШИМ, подробно рассматривать не будем из-за объемности описания.

Регистр TCCR1B

бит

7

6

5

4

3

2

1

0

 

FOC1A

FOC1B

WGM12

CS12

CS11

CS10

Бит 7, 6 (FOC1A, FOC1B)- при использовании ШИМ должны быть установлены в 0.

Бит 5, 4 — зарезервированы.

Бит 3 — используется совместно с WGM11, WGM10 для настройки ШИМ.

Бит 2, 1, 0 (CS12, CS11, CS10) — выбор источника тактирования таймера.

CS12

CS11

CS10

Событие

0

0

0

Нет тактового сигнала (таймер остановлен)

0

0

1

CLK

0

1

0

CLK/8

0

1

1

CLK/64

1

0

0

CLK/256

1

0

1

CLK/1024

1

1

0

Внешний тактовый импульс по заднему фронту.

1

1

1

Внешний тактовый импульс по переднему фронту.

Регистр TCNT1

бит

7

6

5

4

3

2

1

0

 

Из регистра TCNT1 можно считать значение или записать начальное значение для таймера.

Регистры OCN1A и OCN1B

бит

7

6

5

4

3

2

1

0

 

Регистры OCN1A и OCN1B содержат значение сравнения связанные с соответствующими выводами микроконтроллера.

Есть еще два регистра TIMSK1 и TIFR1 – они предназначены для настройки маски прерываний и флагов прерываний.

Все перечисленные регистры можно использовать для всех трех таймеров. Для таймера TC1 есть возможность использовать расширенные настройки в связи с его 16-битной структурой.

Данный раздел не претендует на полноту описания работы с таймерами и ШИМ.

3.1. Программа с ШИМ

Для начала построим в Proteus схему и дальше применим ее для моделирования

ATmega328 PWM

Теперь программа.

 #include <avr/io.h>            //Подключаем библиотеку ввода/вывода AVR #include <util/delay.h>        //Подключаем библиотеку формирования задержки выполнения  int main(void) { 	DDRB |= 1<<PB1;             //Устанавливаем как вывод регистр 1 порта B  	TCCR1A |= 1<<(COM1A1) | 1<<(WGM10); //В регистре TCCR1A устанавливаем биты COM1A1 и WGM10 в 1 	TCCR1B |= 1<<(CS10) | 1<<(WGM12);   //В регистре TCCR1B устанавливаем биты CS10 и WGM12 в 1  	int pwm = 0;                //Объявляем переменную для значения сравнения 	int up = 1;                 //Создаем флаг для увеличения или уменьшения скважности ШИМ      while(1)     {  		OCR1A = pwm;            //Заносим значение в регистр сравнения  		pwm += up ? 1 : -1;     //В зависимости от флага uo увеличиваем или уменьшаем значение сравнения pwm 		if (pwm == 255)         //Проверяем pwm на достижение 255 и переключаем флаг uo в 0 			up = 0; 		else if (pwm == 0)      //Проверяем pwm на достижение 0 и переключаем флаг uo в 1 			up = 1;  		_delay_ms(10);          //Задержка выполнения 10мс 	}      return 0; } 

Программа подключает ШИМ к регистру 1 порта B и плавно увеличивает а затем уменьшает ток.

4. Обмен данными по SPI

Вот и добрались до обмена данными между микроконтроллером и внешним миром. Рассмотрим это на связке AVR >> SPI >> MAX7221 >> семисегментные индикаторы.

4.1. SPI

Работу с протоколом обмена SPI на ATmega328 начнем с рассмотрения регистров управления. Всего используется три регистра SPCR, SPSR и SPDR.

Регистр SPCR

бит

7

6

5

4

3

2

1

0

 

SPIE

SPE

DORD

MSTR

CPOL

CPHA

SPR1

SPR0

Бит 7 (SPIE)- разрешает прерывания.

Бит 6 (SPE)- включает аппаратный SPI.

Бит 5 (DORD) — определяет порядок передачи данных (0 — старший бит вперед, 1 — младший бит вперед).

Бит 4 (MSTR) — режим работы master/slave (1/0).

Бит 3 и 2 (CPOL, CPHA) — режим работы SPI.

Mode

CPOL

CPHA

SPI Mode 0

0

0

SPI Mode 1

0

1

SPI Mode 2

1

0

SPI Mode 3

1

1

Бит 1 и 0 (SPR1, SPR0) — частота тактирования SPI.

SPI2X

SPR1

SPR0

Частота тактирования

0

0

0

f/4

0

0

1

f/16

0

1

0

f/64

0

1

1

f/128

1

0

0

f/2

1

0

1

f/8

1

1

0

f/32

1

1

1

f/64

Регистр SPSR

бит

7

6

5

4

3

2

1

0

 

SPIF

WCOL

SPI2X

Бит 7 (SPIF)- флаг прерываний SPI.

Бит 6 (WCOL)- флаг ошибки записи.

Бит 5, 4, 3, 2, 1 — зарезервированы.

Бит 6 (SPI2X)- бит удвоения скорости записи.

Регистр SPDR

бит

7

6

5

4

3

2

1

0

 

MSB

LSB

Регистр SPDR предназначен для записи/чтения в сдвиговый регистр что приводит к активации передачи данных по SPI.

4.2. Использование SPI.

Рассмотрим передачу данных по SPI на примере вывода информации на семисегментные индикаторы. Для начала создадим схему в Proteus для эмуляции процесса.

ATmega328 SPI MAX7221

Программировать будем вывод данных на 5 семисегментных индикатора через микросхему MAX7221, которая принимает данные по 16 бит по протоколу SPI.

 /* Использование семисегментного индикатора с общим катодом и сдвиговым регистром по протоколу SPI */  #include <avr/io.h>         //подключаем библиотеки #include <util/delay.h>  #define SPI_DDR     DDRB    //назначаем имя для изменеия направления на порту B #define SPI_PORT    PORTB   //назначаем имя для используемого порта #define SPI_SS      PB2     //назначаем имя выхода SS #define SPI_MOSI    PB3     //назначаем имя выхода MOSI #define SPI_MISO    PB4     //назначаем имя выхода MISO #define SPI_SCK     PB5     //назначаем имя выхода SCK  char d[18] ={               //кодирование знаков для семисегментного индикатора     0x7E,  //0     0x30,  //1     0x6D,  //2     0x79,  //3     0x33,  //4     0x5B,  //5     0x5F,  //6     0x70,  //7     0x7F,  //8     0x7B,  //9     0x77,  //a     0x1F,  //b     0x4E,  //c     0x3D,  //d     0x4F,  //e     0x47,  //f     0x80,   //.     0x00   //пусто };  void spi(char cmd,char data)                //Функция передачи двух пакетов по 8 бит по протоколу SPI {          SPI_PORT &= ~(1<<SPI_SS);                       //сбрасываем SS в 0         SPDR = cmd;                                     //отправляем данные по SPI адрес         while(!(SPSR&(1<<SPIF)));                       //ждем окончания отправки         SPDR = data;                                    //отправляем данные по SPI данные         while(!(SPSR&(1<<SPIF)));                       //ждем окончания отправки         SPI_PORT |= (1<<SPI_SS);                        //устанавливаем SS в 1  }  void clrdig ()          //Функция очистки индикаторов {     spi(0x01,d[17]);     spi(0x02,d[17]);     spi(0x03,d[17]);     spi(0x04,d[17]);     spi(0x05,d[17]);  } int main() {     SPI_DDR = (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS);    //настраиваем MOSI, SCK, SS как выходы    SPI_PORT |=(1<<SPI_SS);                              //устанавливаем SS в 1    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);       //через регистр SPCR настраиваеи аппаратное SPI     //Инициализация MAX7221      spi(0x0C,0x00);     //Отключение индикаторов     spi(0x09,0x00);     //Отключение декодирования     spi(0x0A,0x0A);     //Интенсивность свечения индикаторов     spi(0x0B,0x04);     //Количество индикаторов начиная с 0     spi(0x0F,0x00);     //Отключение теста индикаторов     spi(0x0C,0x01);     //Включение индикаторов      clrdig();           //Очистка всех индикаторов  int i=0;                //Переменная перебора выводимых символов int j=1;                //Переменная осчета номера индикатора      while(1){           //Бесконечный цикл          spi(j,d[i]);    //Вывод на индикатор j символа i          _delay_ms(1000);    //Задержка выполнения 1000мс          i++;         if(i>17)i=0;         j++;         if(j>5)j=1;      }      return 0; } 

Перед использованием MAX7221 не забываем провести инициализацию.

Передача по SPI проходит по следующей схеме:

  1. Выставляем SS в 0;

  2. Заносим данные в регистр SPDR данные;

  3. Каждый бит по очереди передается по тактам SCK;

  4. Завершаем передачу установкой SS в 1.

Часть 4