Подготовка
Создаем в пустом workspace`е новый проектProject > Create New Project…
Тип шаблона C > mainКопируем в папку проекта файлы исходника библиотеки для работы с энкодером
encoder.h и encoder.c
Подключаем к нашему проекту файл encoder.c
Правая кнопка мышки в окне workspace и в открывшемся меню Add > Add Files…
Копируем файл bits_macros.h в папку проекта.
Подключаем заголовочные файлы
В начале файла main.c забиваем следующие строки
#include <ioavr.h>
#include <intrinsics.h>
#include "encoder.h"
#include "bits_macros.h"
Задаем настройки проекта
Project > Options
Тип микроконтроллера
General Options > Target > Processor Configuration > ATMega8535
Разрешение использования имен битов определенных в заголовочных файлах
General Options > System > Enable bit defenitions...
Оптимизация кода по размеру
C/C++ Compiler > Optimisations >Size High
Тип выходного файла
Linker > Output File галочка Override default и поменять расширение на hex
Linker > Format > Other выбрать Intel Standart
Жмем Ок. Сохраняем проект и workspace.
Теперь у нас есть пустой проект с подключенной либой и заданными настройками.
Задача
Заставить микроконтроллер генерировать меандр с частотой от 1 до 100 Гц. Значение частоты должно задаваться с помощью энкодера. Поворот энкодера на одну позицию должен соответствовать изменению частоты генератора на 1 Гц.
Схема для нашего примера
К выводу, на котором будет генерироваться меандр, подключен светодиод, чтобы хоть как-то видеть результат работы программы. Вряд ли у многих под рукой осциллограф.Алгоритм программы
Прямоугольный сигнал генерируется с помощью 16 разрядного таймера Т1, который работает в режиме СТС – сброс при совпадении. Во флэш памяти микроконтроллера храниться массив, содержащий для каждого значения требуемой частоты константу. Для доступа к элементам массива используется переменная pTimerValue. В прерываниях таймера Т1 значение константы считывается и записывается в регистр сравнения.
Для генерации сигнала используется вывод PD5 (OC1A). У него есть альтернативные функции – он может менять свое состояние на противоположное при равенстве счетного регистра и регистра сравнения.
Опрос энкодера выполняется в прерывании таймера Т0. Частота работы таймера ~ единицы килогерц. Режим работы – Normal (здесь не принципиально, какой режим).
В основной программе в бесконечном цикле while микроконтроллер опрашивает буфер энкодера и в зависимости от его значения уменьшает или увеличивает переменную pTimerValue.
В самом начале main`а располагается код инициализации периферии и необходимых переменных.
Структура программы
Для наглядности я изобразил структуру программы в виде диаграммы.- Инициализация.
- Бесконечный цикл (так называемый superloop), в котором происходит ожидание события, обычно в виде опроса флагов или какого-нибудь буфера.
- Параллельная работа периферийных устройств, вызывающих прерывания. В них выполняется какой-то код (желательно короткий) и выставляются флаги.
Для простых задач такого подхода хватает за глаза. Для сложных существуют другие способы организации программ. Наберитесь терпения, скоро и до них дойдет дело.
Расчет констант для таймера Т1
Рассчитаем значение константы для частоты 1 Гц. Подобный расчет я уже приводил, но будет не лишним его вспомнить
Тактовая частота микроконтроллера 16 МГц (смотрите схему). Коэффициент предделителя таймера - 256. Он позволяет получить прерывания с любой частотой из нашего диапазона.
Период одного тика таймера будет равен 1/(16 МГц/ 256) = 16 мкс
На выводе PD5 нам нужно получить сигнал частотой 1 Гц. Вывод меняет свое состояние на каждое прерывание таймера и значит, частота прерываний должна быть в 2 раза больше. Для нашего случая - 2 Гц.
Сколько тиков таймера уложится в 2 Герца? (1/2 Гц)/16 мкс = 31250Это и есть искомая константа.
Остальные значения рассчитываются аналогично. Я для этого обычно использую Exel.
Полученные значения мы помещаем в массив
__flash unsigned int timerValue[] =
{
};
сохраняем его в отдельном файле – timer_value.h и подлючаем его к файлу main.c
#include "timer_value.h"
Да, еще нужно добавить парочку констант в этот файл
#define MAX_TIM_VALUE 99
#define MIN_TIM_VALUE 0
Убедимся, что правильно рассчитали константы для таймера. Запустим его. Код программы будет такой.
//программирование AVR на Си
//пример использования энкодера
//ChipEnable.ru 17.10.09
#include <ioavr.h>
#include <intrinsics.h>
#include "encoder.h"
#include "bits_macros.h"
#include "timer_value.h"
//индекс для доступа к элементам массива
volatile unsigned char pTimerValue = 0;
int main(void)
{
//инициализация таймера Т1
TCNT1 = 0;
TCCR1A = (0<<COM1A1)|(1<<COM1A0)|(0<<WGM11)|(0<<WGM10);
TCCR1B = (0<<WGM13)|(1<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10);
OCR1A = timerValue[pTimerValue];
//настройка вывода PD5 на выход
SetBit(PORTD, PD5);
SetBit(DDRD, PD5);
//ничего не делаем в бесконечном цикле
while(1);
return 0;
}
Думаю, пояснения требует только кусок инициализации таймера.
Обнуление счетного регистра
TCNT1 = 0;
Инициализация конфигурационных регистров таймера Т1.
TCCR1A = (0<<COM1A1)|(1<<COM1A0)|(0<<WGM11)|(0<<WGM10);
TCCR1B = (0<<WGM13)|(1<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10);
Где биты WGM13, WGM12, WGM11, WGM10 задают режим работы таймера – СТС,
CS12, CS11, CS10 – определяют коэффициент предделителя таймера –256,
COM1A1, COM1A0 – определяют поведение вывода PD5(OC1F) – в данном случае по сигналу таймера он будет менять свое состояние на противоположное
Инициализация регистра совпадения начальным значением.
OCR1A = timerValue[pTimerValue];
Компилируем программу и грузим в микроконтроллер. Светодиод должен моргать с частотой 1 Гц.
В программе нет никаких прерываний. Нет никаких манипуляций с выводом PD5. Однако светодиод моргает!
Программа
Теперь нужно “прикрутить” к этой программе энкодер. Зададим настройки в хедер файле encoder.h – порт и выводы, к которым подключен энкодер, значения констант.
#define PORT_Enc PORTA
#define PIN_Enc PINA
#define DDR_Enc DDRA
#define Pin1_Enc 2
#define Pin2_Enc 1
#define RIGHT_SPIN 0x01
#define LEFT_SPIN 0xff
Хедер содержит прототипы трех функций. Вспомним их назначение.
void ENC_InitEncoder(void) настраивает выводы микроконтроллера, к которым подключен энкодер на вход. Эту функцию нужно вызвать в начале main`а.
void ENC_PollEncoder(void) – однократно опрашивает энкодер, анализирует текущее и предыдущее состояния и записывает в буфер соответствующие константы (RIGHT_SPIN и LEFT_SPIN). Эта функция будет сидеть в прерывании таймера Т0.
unsigned char ENC_GetStateEncoder(void) – возвращает содержимое буфера энкодера. Если поворот на одну позицию не был зафиксирован – функция вернет 0, если поворот был зафиксирован функция вернет значение соответствующей константы. При этом значение буфера очистится. Эта функция будет вызываться в основном программе – в цикле while.
Дополняем нашу программу. Можете попробовать сделать это самостоятельно.
//программирование AVR на Си
//пример использования энкодера
//ChipEnable.ru 17.10.09
#include <ioavr.h>
#include <intrinsics.h>
#include "encoder.h"
#include "bits_macros.h"
#include "timer_value.h"
#define TCNT0_const 253
#define TCCR0_const 5
volatile unsigned char pTimerValue = 0;
int main(void)
{
ENC_InitEncoder();
//инициализация таймера т0
TCNT0 = TCNT0_const;
TCCR0 = TCCR0_const;
//инициализация таймера т1
TCNT1 = 0;
TCCR1A = (0<<COM1A1)|(1<<COM1A0)|(0<<WGM11)|(0<<WGM10);
TCCR1B = (0<<WGM13)|(1<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10);
OCR1A = timerValue[pTimerValue];
//разрешение прерываний от таймеров
//т0 - по переполнению, т1 - по совпадению
TIMSK = (1<<TOIE0)|(1<<OCIE1A);
//настраиваем PD5 на выход
SetBit(PORTD, PD5);
SetBit(DDRD, PD5);
__enable_interrupt();
while(1){
//считываем содержимое буфера энкодера
//после считывания он очищается
unsigned char stateEnc = ENC_GetStateEncoder();
//если не пустой
if (stateEnc != 0){
//определяем направление вращения и изменяем переменную timerValue
if (stateEnc == RIGHT_SPIN){
if (pTimerValue == MAX_TIM_VALUE) pTimerValue = MIN_TIM_VALUE;
else pTimerValue++;
}
if (stateEnc == LEFT_SPIN) {
if (pTimerValue == MIN_TIM_VALUE) pTimerValue = MAX_TIM_VALUE;
else pTimerValue--;
}
}
}
return 0;
}
//опрос энкодера
#pragma vector=TIMER0_OVF_vect
__interrupt void timer0_ovf_my(void)
{
TCNT0 = TCNT0_const;
ENC_PollEncoder();
}
#pragma vector=TIMER1_COMPA_vect
__interrupt void timer1_compa_my(void)
{
//обновляем значение регистра стравнения
OCR1A = timerValue[pTimerValue];
}
Вроде все должно быть понятно.
Кусок кода, в котором изменяется значение pTimerValue, можно было бы написать еще так:
if (stateEnc != 0) {
pTimerValue = pTimerValue + stateEnc;
if (pTimerValue == (MAX_TIM_VALUE + 1)) pTimerValue = MIN_TIM_VALUE;
else if (pTimerValue == (MIN_TIM_VALUE - 1)) pTimerValue = MAX_TIM_VALUE;
}
При вращении энкодера вправо pTimerValue складывается с 1, то есть инкрементируется.
При вращении энкодера влево pTimerValue складывается с 0хff, что равносильно вычитанию 1. Одна и та же операция, а результат прямо противоположный.
(Двоичные числа можно вычитать складывая. Для этого одно из чисел нужно представить в дополнительном коде – вычесть из него 1 и проинвертировать все разряды. 0xff – это единица в дополнительном коде)
Файлы
Энкодер. Генератор на AVR – проект для IARa.Энкодер. Генератор на AVR – проект для WINAVR.
Comments
в проекте для IAR
в файле main.c забыли добавить
#include "encoder.c" //:)
без этого ошибку выдает.
P.S.
Спасибо за статьи по IAR, на просторах и-нета мало информации на руском и с обьяснениями !
в main`e включен хедер
#include "encoder.h"
у меня никаких ошибок нет.
if (tmp == b11100001) bufEnc = LEFT_SPIN;
if (tmp == b11010010) bufEnc = RIGHT_SPIN;
проверяю в протеусе
Возможно Вы неправильно подключили энкодер. "Общий" вывод не всегда посередине.
Quote: почему в 2 раза больше, не пойму? )
1****2****3****4****
цифры(1,2,3,4) - это моменты вызова обработчика прерывания.
звездочки(*)- тактовые импульсы таймера/счетчик а, период которых составляет, допустим, 1 секунду.
Согласно этой последовательно сти после каждой 4-ой секунды происходит прерывание. В одном прерывании мы устанавливаем на выводе микроконтроллер а лог. 1, в следующем прерывании - лог. 0 и т.д.
Частота прерываний будет равна 1/4с= 0,25 Гц. А частота генерируемого сигнала 1/8с=0,125 Гц. То есть в два раза меньше.
#include <mega16.h>
Подключил 7-сегм. индикатор и энкодер к мк. пытаюсь при вращении вывести на дисплей цифры, но ничего не меняется.#include <indicator.c>
#include <encoder.c>
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
IND_Update();
ENC_PollEncoder();
}
unsigned int data=0;
void main(void)
{
IND_Init();
ENC_InitEncoder();
DDRB=(1<<DDB7) | (1<<DDB6) | (1<<DDB5) | (1<<DDB4) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1) | (1<<DDB0);
PORTB=(1<<PORTB7) | (1<<PORTB6) | (1<<PORTB5) | (1<<PORTB4) | (1<<PORTB3) | (1<<PORTB2) | (1<<PORTB1) | (1<<PORTB0);
DDRC=(0<<DDC7) | (0<<DDC6) | (0<<DDC5) | (0<<DDC4) | (0<<DDC3) | (0<<DDC2) | (0<<DDC1) | (0<<DDC0);
PORTC=(1<<PORTC7) | (1<<PORTC6) | (1<<PORTC5) | (1<<PORTC4) | (1<<PORTC3) | (1<<PORTC2) | (1<<PORTC1) | (1<<PORTC0);
DDRD=(1<<DDD7) | (1<<DDD6) | (1<<DDD5) | (1<<DDD4) | (1<<DDD3) | (1<<DDD2) | (1<<DDD1) | (1<<DDD0);
PORTD=(1<<PORTD7) | (1<<PORTD6) | (1<<PORTD5) | (1<<PORTD4) | (1<<PORTD3) | (1<<PORTD2) | (1<<PORTD1) | (1<<PORTD0);
TCCR0=(0<<WGM00) | (1<<COM01) | (0<<COM00) | (0<<WGM01) | (0<<CS02) | (0<<CS01) | (1<<CS00);
TCNT0=0x00;
OCR0=0x00;
TCCR1A=(0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (0<<WGM11) | (0<<WGM10);
TCCR1B=(0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (0<<WGM12) | (0<<CS12) | (0<<CS11) | (0<<CS10);
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
ASSR=0<<AS2;
TCCR2=(0<<PWM2) | (0<<COM21) | (0<<COM20) | (0<<CTC2) | (0<<CS22) | (0<<CS21) | (0<<CS20);
TCNT2=0x00;
OCR2=0x00;
TIMSK=(0<<OCIE2) | (0<<TOIE2) | (0<<TICIE1) | (0<<OCIE1A) | (0<<OCIE1B) | (0<<TOIE1) | (0<<OCIE0) | (1<<TOIE0);
// Global enable interrupts
#asm("sei")
while (1)
{
unsigned char stateEnc = ENC_GetStateEncoder();
if (stateEnc != 0)
{
if (stateEnc == RIGHT_SPIN) {
data++;
}
if (stateEnc == LEFT_SPIN) {
data--;
}
}
IND_Output(data, 0);
}
}
Изменил код Code:
#include "mega16.h"
#include "indicator.h"
#include "encoder.c"
volatile unsigned int data = 500;
void main( void )
{
ENC_InitEncoder();
IND_Init();
//инициализация таймера Т0
TIMSK = (1<<TOIE0);
TCCR0 = (1<<CS02)|(0<<CS01)|(1<<CS00);
TCNT0 = 0xff;
#asm("sei");
while(1){
unsigned char stateEnc = ENC_GetStateEncoder();
if (stateEnc != 0)
{
//IND_Output(111, 0); // проверка
if (stateEnc == RIGHT_SPIN) {
data++;
}
if (stateEnc == LEFT_SPIN) {
data--;
}
}
IND_Output(data, 0);
//delay_ms(10);
}
}
interrupt [TIM0_OVF] void Timer(void)
{
TCNT0 = 0xff;
ENC_PollEncoder();
IND_Update();
}
результат также нулевой.
если сделать такую проверкуCode:
if (stateEnc == 0)
то получается, что stateEnc у меня ноль. Не могу понять свою ошибку.{
IND_Output(111, 0); // проверка
}
Пересоберу на макетке с диодом, посмотрим что выйдет.
Pashgan, как думаете, если оставить внутренний генератор 1 МГц и пересчитать массив timerValue, будет работать?
UPD. Со фьюзами разобрался, правда светодиод просто горит. Если не получится решить проблему, создам тему на форуме
UPD. Уряяя! Светик моргает, частота меняется :D . Теперь осталось индикатор присобачить
Quote: Ну видишь, не зря мучался.
RSS feed for comments to this post