Примеры с сайта AVR+ Electronics

Автор: Internet. Опубликовано в Начинающим CV AVR

Содержание материала

Практические примеры CodeVisionAVR с сайта AVR+ Electronics

Автор: Krik99


Прерывания по переполнению таймера…

Бывает когда надо выполнять, не зависимо от основной программы код, например та же динамическая индикация и тогда приходит в помощь прерывания по переполнению таймера. Когда происходит прерывание по переполнению таймера, то основная программа останавливается и начинает выполняться код, записанный в самом прерывание, после завершения исполнения прерывания, МК возвращается к исполнению основной программы в том месте, где остановился. Но если у нас кусков программы, требующих прерывания больше одного, а таймер всего один, тогда можно воспользоваться простенькой конструкцией с использованием оператора switch. То есть каждый раз, попадая в прерывания будет выполняться следующий кусок программы и так по кругу. Сначала первый кусок кода, в следующий раз - второй и т.д. главное чтобы куски кода не занимали много времени, так как мы всё же в прерываниях. Оператор switch имеет такой вид:

Code
switch(i) {
       case 0: { какой-то код 0 }
       case 1: { какой-то код 1 }
       case 2: { какой-то код 2 }
       case 3: { какой-то код 3 }
       case 4: { какой-то код 4 }
}

Работать это будет так: сравнивается значение переменной i с case, то есть если i=2, тогда будет выполняться код который записан в case 2. То есть зашли в прерывание сравнили переменную i с case и выполнили код, добавили 1 к переменной i и вышли, в следующий раз при входе в прерывание будет выполняться следующий код, так как переменная i стала больше на 1 и следовательно будет выполняться следующий case. Для начала настраиваем таймер на прерывание по переполнению, это всё делаем по даташиту, главное, что бы прерывания происходили по чаще. Код будет иметь приблизительно такой вид:

Code


unsigned char i_vect;
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void) {
       switch(i_vect){
              case 0: {
              //тут типа первый код,
              //который требует прерывания.
              break; }
              case 1: {
              //тут типа второй код,
              //который требует прерывания.
              break; }
              case 2: {
              //тут типа третий код,
              //который требует прерывания.
              break; }
              case 3: {
              //тут типа четвёртый код,
              //который требует прерывания.
              break; }
              case 4: {
              //тут типа пятый код,
              //который требует прерывания.
              break; }
       }
       i_vect++;
       if(i_vect==5) i_vect=0;
}

Главное правило кода, который набит в switch помнить, о том, что код не должен занимать много времени на выполнение, так как нормальная работа прерывания нарушится, а именно в case не должен тупить в цикле и использовать delay. Не советую использовать в switch’е код, который требует большой точности по таймингу. И чем меньше будет нагружен таймер, а именно чем меньше будет case’ов в операторе switch и чем быстрее будем выходить из прерываний, тем быстрее будут выполняться остальные case. Вот собственно и закончим на этом, так как рассказывать вроде больше нечего. Удачи в опытах и программных извратах..... 


Работа с матричной клавиатурой 4х4.

Сегодня поговорим о работе МК с матричной клавиатурой. Собственно матричная клавиатура представляет собой n-количество кнопок подключённых матрицей, тем самым уменьшается количество используемых пинов МК, но для работы нужно использовать динамический опрос. Что касается динамического опроса, всё что нужно от МК, это поочередно опускать столбцы матрицы в лог.0 и слушать строки, когда происходит нажатие кнопки, вычисляем в каком столбце и в какой строке было нажатие. А далее, обрабатываем полученные данные, как хотим. Для наглядного примера я написал программу под клавиатуру 4х4. Программа как обычно под Codevision. Вот схема:



Code
/*****************************************************
Автор: Krik99
Project : 4x4 keys
Chip type           : ATtiny2313
Clock frequency     : 4,000000 MHz
*****************************************************/
#include <tiny2313.h>
#include <delay.h>
#define stolb1 PORTB.7
#define stolb2 PORTB.6
#define stolb3 PORTB.5
#define stolb4 PORTB.4
#define str1 PINB.3
#define str2 PINB.2
#define str3 PINB.1
#define str4 PINB.0
unsigned char button[16];
void usart_tx(unsigned char data){
    while ( !(UCSRA & (1<<5)) );    //ждём очистки регистра данных USART       
    UDR=data;    //отправить
/*
void usart_tx_text(flash char *text){
  for(;*text;text++)usart_tx(*text);         
unsigned char usart_rx(void){ 
    while ( !(UCSRA & (1<<7)) );    //ждём очистки регистра данных USART      
    return UDR;    //читаем данные
*/   
void main(void){    
unsigned char x;
// Port A initialization
PORTA=0x00;
DDRA=0x00;
// Port B initialization
PORTB=0xFF;
DDRB=0xF0;
// Port D initialization
PORTD=0x00;
DDRD=0x00;
// USART initialization
// USART Baud rate: 9600
UCSRB=0x08; UCSRC=0x06; UBRRL=0x19;
         
while (1)
      {    
      for (x = 0; x < 4; x++){   //проверяем 4 столбца
         delay_ms(6);                   //делаем задержку в 6мс
         switch(x){       //управление столбцами
            case 0:{ stolb4=1; stolb1=0;  break; };  //1 столбец
            case 1:{ stolb1=1; stolb2=0;  break; };  //2 столбец
            case 2:{ stolb2=1; stolb3=0;  break; };  //3 столбец
            case 3:{ stolb3=1; stolb4=0;  break; };  //4 столбец
         } 
         delay_ms (3);                  //задержка                  
         button[0+x]=str1;   //проверка 1 строки
         delay_ms (3);                  //задержка
         button[4+x]=str2;   //проверка 2 строки
         delay_ms (3);                  //задержка
         button[8+x]=str3;   //проверка 3 строки
         delay_ms (3);                  //задержка
         button[12+x]=str4;  //проверка 4 строки
      }
      if(button[0]==0) usart_tx('0'); delay_ms(15);   //если нажат клавиша 0
      if(button[1]==0) usart_tx('1'); delay_ms(15);   //если нажат клавиша 1
      if(button[2]==0) usart_tx('2'); delay_ms(15);   //если нажат клавиша 2
      if(button[3]==0) usart_tx('3'); delay_ms(15);   //если нажат клавиша 3
      if(button[4]==0) usart_tx('4'); delay_ms(15);   //если нажат клавиша 4
      if(button[5]==0) usart_tx('5'); delay_ms(15);   //если нажат клавиша 5
      if(button[6]==0) usart_tx('6'); delay_ms(15);   //если нажат клавиша 6
      if(button[7]==0) usart_tx('7'); delay_ms(15);   //если нажат клавиша 7
      if(button[8]==0) usart_tx('8'); delay_ms(15);   //если нажат клавиша 8
      if(button[9]==0) usart_tx('9'); delay_ms(15);   //если нажат клавиша 9
      if(button[15]==0) usart_tx(' r'); delay_ms(15);  //если нажат клавиша 16           
      };
}

Что касается программы, там всё просто. Программа сканирует клавиатуру и по нажатию кнопок 0…9 отправляет значение по UART. 15 клавиша это перейти на строку ниже. Если будет большая загрузка МК, программу обработку кнопок желательно запускать по прерыванию. Можно было это всё завести от внешних прерываний, но тогда надо использовать ещё одну ножку. Ну, вот собственно и всё, исходник можно скачать здесь: 4d1854_key.zip (исходный код, прошивка,  proteus).


Использование внутренней EEPROM.

Иногда нужно сохранить данные, да так, чтобы после выключения питания можно было их восстановить. Большинство это данные о последних настройках, последних событиях и т. п. Для этого нам подойдёт энергонезависимая память. Почти все микроконтроллеры AVR имеют ёё у себя на борту. О размере EEPROM можно прочитать в даташите на конкретный МК. В большинстве случаев у EEPROM есть 10000 перезаписей, хотя точную цифру можно посмотреть в том же даташите на конкретный МК. А теперь рассмотрим небольшой пример работы с EEPROM в компиляторе Code Vision AVR. Для объявления переменной eeprom

 в CodeVision мы пишем так eeprom unsigned char start; этой строкой мы объявили переменную в области еепром. Для чтения значений обращаемся как с обычной переменной, а чтобы записать новое значение в переменную еепром пишем так start=12; этой строкой мы передали новое значение в еепром и теперь в переменной еепром записано число 12. Я решил, чтобы показать наглядно работу с EEPROM написать программу мигалки, которая перестаёт работать после n-ного количества включений, так сказать demo версия.

Вот схема:


В исходниках всё понятно, да к тому же код простейший. Скачать проект и файлы к нему можно здесь eeprom.zip (исходный код, прошивка,  proteus).

Code
/*****************************************************
Автор: Krik99
Project : test eeprom                            
Chip type           : ATtiny13
Clock frequency     : 9,600000 MHz
Memory model        : Tiny
External SRAM size  : 0
Data Stack size     : 16
*****************************************************/
#include <tiny13.h>
#include <delay.h>
eeprom unsigned char start;  //переменная в eeprom
void main(void)
{
unsigned char a; 
start=start+1;          //при каждом вкл. записываем +1 в eeprom
// Port B initialization
PORTB=0x00;
DDRB=0x1F;
while (start<5){            //проверяем сколько было запусков если<5 тогда 
          for(a=0;a<32;a++){       //собственно сама мигалка
              PORTB=a;
              delay_ms(100);
          }
      };
}


AVR и регистр сдвига 74HC164.

Сегодня поговорим о регистрах сдвига (shift registr) на примере 74HC164. Бывают ситуации, когда у МК не хватает свободных портов, особенно если в устройстве используется светодиодная индикация, тогда и можно использовать регистр сдвига. Да, они широко используются и в бытовой аппаратуре для работы с дисплеем. Немного теории… Из управляющих входов у нас есть: DATA, RESET, CLK ,а на выходе мы получаем 8-выходов. Немного о назначение входов. О пине RESET – сброс установленных значений на выходе, обычно не используется, так как занимает лишний пин у МК. Он должен всегда быть поднятым, для этого подрубаем его к Vcc, а сбрасывать значение будем посылкой 8бит лог.1 О пине CLK – этим пином продвигаем значение по регистру. DATA – собственно сами данные о значение следующего выхода. Плавно подходим к заполнению регистра. Для заполнения регистра выполняем такую последовательность:

1. выдаём первый старший бит из пачки на линию DATA

2. опускаем в лог. 0 линию CLC

3. поднимаем в лог.1 линию CLC

4. повторяем все пункты с 1, пока не выдадим все биты.

Для сброса, просто запускаем новые 8бит данных. Для наглядности можно собрать это всё в Proteus и потыкать кнопочками. Думаю теории хватит, перейдём к практике. Для наглядного примера я собрал такой девайс на Тини13, который выводит числа от 0 до 99 на 2х разрядном 7-ми сегментном LED дисплее. Так как Тини13 сам зажечь дисплей не сможет, поставил я ему в помощь 74HC164.

Вот схема:


На программе останавливаться не буду, так как в исходниках всё предельно ясно. Исходники на Си. Скачать можно тут ain.zip (исходный код, прошивка,  proteus).

Вот собственно и всё, надеюсь кому-то пригодиться.

Code
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Author          : Krik99
Chip type       : ATtiny13
Clock frequency : 9.6MHz
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#include <tiny13.h>
#include     
char digit_out[2], cur_dig;
flash char buf[] = {     //массив с цифрами
0b00000011, //0
0b10011111, //1 
0b00100101, //2
0b00001101, //3
0b10011001, //4
0b01001001, //5
0b01000001, //6
0b00011111, //7
0b00000001, //8
0b00001001, //9
0b11111111
};        
     
void hc164_wr(unsigned char dig){  //подпрограмма вывода данных в регистр
unsigned char a;
 for(a=0; a<8; a++){            //цикл для вывода 8 бит данных из массива
   PORTB.0 = buf[dig] & 1<<a; <span="" data-mce-bogus="1" class="mceItemHidden mceItemNbsp">  //выводим в линию DATA текущий бит 
   PORTB.1=0;         //опускаем линию CLK в лог.0
   PORTB.1=1;         //поднимаем линию CLK в лог.1
 }
}    
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{                              
switch (cur_dig){   
case 0:{PORTB.3=0; PORTB.2=0; hc164_wr(digit_out[cur_dig]); PORTB.2=1; break;};  //первая цифра
case 1:{PORTB.2=0; PORTB.3=0; hc164_wr(digit_out[cur_dig]); PORTB.3=1; break;};  //вторая цифра
}                          
cur_dig++;                        
if (cur_dig==2) cur_dig=0; 
}           
void main(void)
{   
unsigned char f;
// Port B initialization
PORTB=0x00;
DDRB=0xFF; 
// Timer/Counter 0 initialization
TCCR0A=0x00;
TCCR0B=0x03;
// Timer/Counter 0 Interrupt(s) initialization
TIMSK0=0x02;
// Global enable interrupts
#asm("sei") 
while (1){ 
   for(f=0; f<100; f++){ 
   delay_ms(50);
   digit_out[0]=f/10;
   digit_out[1]=f%10;
   }
      };
}


Управляем RGB светодиодом.

Так как скоро Новый Год, расскажу как управлять RGB светодиодом. RGB светодиод, это по сути 3 светодиода (Red - Green - Blue) собранные под общей линзой. В теории для изменения цвета, надо зажигать светодиоды с определённой яркостью для каждого из 3-х (красного, зелёного, синего), и будем получать различные цвета. Для плавного изменения цвета свечения светодиода, будем использовать 3-х канальный ШИМ. И так долго рассказывать не буду, в исходниках всё и так понятно. Для начала собираем вот такую схему:


В этом проекте я использовал программный ШИМ аля "дёрганья ножками”. Многие скажут что софтовый ШИМ занимает много ресурсов у МК, но так как задача у нас простая плавно регулировать 3 светодиода, то для этого ресурсов МК вполне хватит. МК тактируется от внутреннего RC генератора на частоте 9.6мгц. Исходник написан на Си, под компилятор Code Vision AVR.

Исходник: soft_pwm_rgb

Code
/*****************************************************
Автор: Krik99
Chip type           : ATtiny13
Clock frequency     : 9,600000 MHz
*****************************************************/
#include      
#include <delay.h>
void main(void)
{
unsigned int k, i;  
unsigned char a;
// Port B initialization
PORTB=0xFF;
DDRB=0x07;
while (1)
      {     
       k=3000;
      for(i=1; i<=k; i=i+1)
          {   
             if(a==0){
             PORTB.0=0;  
             PORTB.1=1;
             }
             if(a==1){
             PORTB.1=0;
             PORTB.2=1;
             }       
             if(a==2){  
             PORTB.2=0;
             PORTB.0=1;
             }
             delay_us(5);
          }
      for(i=k; i<=3000; i=i+1)
          {
             if(a==0){
             PORTB.0=1;  
             PORTB.1=0;
             }
             if(a==1){
             PORTB.1=1;
             PORTB.2=0;
             }       
             if(a==2){  
             PORTB.2=1;
             PORTB.0=0;
             }
             delay_us(5); 
          }    
          k=k-10; 
       a++;
       if(a>2)a=0;
      };
}


 Цифровая кость или генератор случайных чисел.

В этой статье я покажу, как создать генератор случайных чисел вывод их на 7-ми сегментный LED дисплей. И так устройство собрано на МК ATmega8. При нажатии кнопки МК считает дребезг контактов кнопки и выводит это число на LED. При последующем нажатии цикл повторяется с нова. Вот схема:


Мк тактируется от внутреннего RC-генератора на 1мгц.

Подключаем питание к МК +5V к 7-й ножке, а -5V к 8-й ножке.

Вот исходник для Code Vision AVR:

Code
/*****************************************************
Project: Chifrovay kost.
*****************************************************/
#include<mega8.h>
unsigned char clik;
void main(void)
{
//Port B initialization
PORTB=0×00;
DDRB=0×00;
//Port C initialization
PORTC=0xFF;
DDRC=0×00;
//Port D initialization
PORTD=0xFF;
DDRD=0xFF;
while(1){
switch(clik){                     //выводим то число которое в переменной clik
case 0:{PORTD=0b11000000; break;} //0
case 1:{PORTD=0b11111001; break;}  //1
case 2:{PORTD=0b10100100; break;} //2
case 3:{PORTD=0b10110000; break;}  //3
case 4:{PORTD=0b10011001; break;}  //4
case 5:{PORTD=0b10010010; break;}  //5
case 6:{PORTD=0b10000010; break;}  //6
case 7:{PORTD=0b11111000; break;}  //7
case 8:{PORTD=0b10000000; break;}  //8
case 9:{PORTD=0b10010000; break;}  //9
}
if(!PINC.0){                                               //если нажата кнопка
clik++;                                                   //прибавляем 1 к переменной
if(clik>9)clik=0;                                   //не даём переменной быть больше 9
}
};
}

    RC5 декодер на Tiny2313.

Сегодня разберём протокол RC5. В протоколе RC5, тут кодирование информации осуществляется не длительностью импульса. Такой способ кодирования информации называется еще манчестерским. RC5 посылка на выходе интегрального приёмника TSOP36 , который на выходе фильтрует несущую частоту 36кГц:


Длительность посылки в протоколе RC5 составляет - 24.9мс, а период повторения - 114мс. Посылка состоит из 14бит. Первые два бита в посылке (S1 и S2) это стартовые биты они всегда должны быть равны 1. Третий бит (Т) это бит триггера, он меняет состояние каждый раз, когда на пульте нажимается кнопка. Служит для отличия многократного нажатия кнопки на пульте. После бита триггера идут 5 бит адреса устройства. Далее идут 6 бит самой команды. Ну, в прочем хватит теории. Для отладки я собрал вот такое устройство, для приёма команд с пульта с протоколом RC5. Собираем такую схему.


О прошивке долго рассказывать не буду. Код написан под компилятор Code Vision AVR. В исходниках и так всё понятно. Устройство тактируется от внутреннего генератора на частоте 8мгц. При получении кода, МК выводит полученную команду на 2-х разрядный LED дисплей. Вот фото:


Исходники можно скачать тут rc5_main

Благодарности: 

Goodefine - за исходники и помощь с их переделкой.

Code
/*****************************************************
Chip type           : ATtiny2313
Clock frequency     : 8,000000 MHz
Data Stack size     : 64
*****************************************************/
#include <tiny2313.h>
#include <delay.h>
#define digit0 PORTD.0      //питание дисплея
#define digit1 PORTD.3      //питание дисплея
flash char digits[] = {     //массив с цифрами
0b01010000, //0
0b01011111, //1
0b00110010, //2
0b00010110, //3 
0b00011101, //4
0b10010100, //5
0b10010000, //6
0b01011110, //7
0b00010000, //8
0b00010100 //9
};   
char digit_out[2], cur_dig; 
//Временные пределы
#define Tmin 40 //длинный промежуток
#define Tmax 68
#define TminK 22 //короткий промежуток
#define TmaxK 34
unsigned char sct_bit = 0;      //Счетчик битов RC5
unsigned char RC5_buffer [14];  //Буфер RC5
bit centre =  0;                //Флаг центра 
bit not_korr = 0;               //Флаг попадания в промежутки
unsigned char Timer = 0;        //число в счетчике таймера
unsigned char trigger = 0;      //переменная триггера
unsigned int device = 0;        //переменная адреса ПДУ
unsigned char command = 0;      //переменная для команды
void rc5_cl_buf(void){            //очищаем буфер
  char i = 0;      
  for (i=0; i<14; i++){ RC5_buffer [i] = 0;  }
}   
void rc5_ti_stop(void){           //остановка таймера
   GIMSK |=0x00;
   TCCR0B = 0x00;
   TCNT0 = 0;
   sct_bit = 0;
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
Timer = TCNT0; //запоминаем значение счетчика   
TCNT0 = 0;     //обнуляем счетчик
not_korr = 1;                         
if(sct_bit==0){
                  TCCR0B = 0x04; //запускаем таймер  (31.250 KHz)
                  RC5_buffer [sct_bit] = !PIND.2;//записываем в эл.массива
                  sct_bit++;            //+1 к счётчику принятых битов
                  centre = 1;           
        }else{
                  if ((Timer>TminK)&&(Timer<tmaxk)){ <span="" data-mce-bogus="1" class="mceItemHidden mceItemNbsp">  // проверка короткого промежутка
                                                  if (centre) {
                                                              centre = 0;
                                                              not_korr = 0; 
                                                  }else{
                                                              centre = 1;
                                                              RC5_buffer [sct_bit] = !PIND.2;
                                                              sct_bit++;
                                                              not_korr=0;
                                                  };
                  }; 
                  if ((Timer>Tmin)&&(Timer
                                                  RC5_buffer [sct_bit] = !PIND.2;
                                                  sct_bit++;
                                                  not_korr = 0;
                  };                               
                  if (not_korr == 1) {             // если не попали ни в один из промежутков то  
                                       rc5_ti_stop();   //останавливаем таймер
                                       rc5_cl_buf();    //очищаем буфер
                  };
                  if (sct_bit == 14){        // если бит последний то 
                    rc5_ti_stop();   //останавливаем таймер
                     
                    trigger = RC5_buffer [2];  //формируем переменную триггера 
                   
                    device = (RC5_buffer [3] << 4) |(RC5_buffer [4] << 3) |(RC5_buffer [5] << 2) |(RC5_buffer [6] << 1) |RC5_buffer [7];                                    //формируем адрес ПДУ                
                    command = (RC5_buffer [8] << 5) |(RC5_buffer [9] << 4) |(RC5_buffer [10] << 3 ) |(RC5_buffer [11] << 2) |(RC5_buffer [12] << 1) |RC5_buffer [13];        //формируем команду
                    
                    //выводим данные на дисплей   
                    digit_out[0]=command%10;   //перевод для 1 цифры
                    command=command/10;                //подготовка 
                    digit_out[1]=command%10;   //перевод для 2 цифры 
       
                    EIFR=0x40;   // сбрасываем флаг прерывания по входу INT0
                       };                                                                         
        };    
}
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{   
rc5_ti_stop();        //останавливаем таймер
rc5_cl_buf();        //очищаем буфер
GIMSK |=0x40; //разрешаем прерывания по входу
EIFR=0x40;  //сбрасываем флаг прерывания (если произошло)
//динамическая индикация
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
PORTB=0xFF;                   //чтобы небыло тени
switch (cur_dig)                             //---
{   
case 0:{digit1=0;digit0=1;break;};  //подаём питание на 1
case 1:{digit0=0;digit1=1;break;};  //подаём питание на 2
}                          
PORTB=digits[digit_out[cur_dig]]; 
cur_dig++;                        
if (cur_dig==2) cur_dig=0;
}
void main(void)
{
// Port A initialization
PORTA=0x00;
DDRA=0x00;
// Port B initialization
PORTB=0x00;
DDRB=0xFF;
// Port D initialization
PORTD=0x04;
DDRD=0x0B;
// Timer/Counter 0 initialization
TCCR0A=0x00;
TCCR0B=0x00;
TCNT0=0x00;
OCR0A=0x00;
OCR0B=0x00;
// Timer/Counter 1 initialization
TCCR1A=0x00;
TCCR1B=0x01;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Any change
// INT1: Off
// Interrupt on any change on pins PCINT0-7: Off
GIMSK=0x40;
MCUCR=0x01;
EIFR=0x40;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x82;
// Global enable interrupts
#asm("sei")
while (1)
      {
      };
}