Учебный курс. Что такое массив. Микроконтроллер играет мелодию.


Массив

Массив – это однородная структура данных, широко применяемая в программировании. Все элементы массива имеют одинаковый тип данных (отсюда и название однородная структура) и к любому из них можно обратиться, используя имя массива и индекс элемента.
   Для того чтобы использовать массив, его нужно предварительно объявить – указать тип данных массива, имя и размер, который определяет, сколько элементов может содержать массив. Размер массива указывается в квадратных скобках [] после имени.

    unsigned char Array[12];  //массив из 12-ти элементов типа  unsigned char
    int OtherArray[8];             //массив из 8-ми элементов типа int

   Первому элементу массива соответствует индекс 0, второму – 1, третьему – 2 .. и так далее. Последний элемент имеет индекс – (размер массива -1). Может показаться, что это несколько неудобно... ничего, дело привычки.

    Array[0] = 255; //присвоить первому элементу массива значение 255
    Array[11] = 15; //присвоить двенадцатому элементу массива значение 15

   При объявлении массива его можно инициализировать, то есть присвоить конкретные значения его элементам. Значения элементов перечисляются в фигурных скобках {} через запятую. После последнего значения запятую ставить не нужно.

    unsigned char Data[3] = {13, 8, 57};  //объявляем массив из 3-ех элементов
                                                             //и присваиваем им всем значения
    
    unsigned char Counters[6] = {0, 0, 0, 0, 0, 0};
    //объявляем массив из 6-ти элементов и обнуляем все элементы

   Си допускает не указывать размерность массива при его объявлении, при этом размерность массива определяется автоматически по числу инициализированных элементов.

    unsigned char Counters[] = {0, 0, 0, 0, 0, 0};
    //размерность массива будет равна 6
        
   Массив может располагаться в ОЗУ, EEPROM или flash памяти микроконтроллера. По умолчанию компилятор размещает массив  в оперативной памяти. Если элементы массива неизменны на протяжении всей программы (константы) то flash память лучше всего подходит для его хранения. Для того чтобы указать компилятору IARа, что массив должен располагаться во flash памяти - перед типом данных массива нужно использовать ключевое слово __flash (два символа подчеркивания подряд!).

__flash unsigned char Array[] = {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f};
  //объявляем массив из 8-ми элементов во flash памяти микроконтроллера

   Ключевое слово __flash автоматически определяет массив как константный. Если вы попытаетесь в программе изменить содержимое массива компилятор выдаст ошибку.

  Задача: написать программу проигрывающую мелодию по нажатию кнопки.
  Схема для нашего примера.

   Мелодия – это последовательность нот, и массив как нельзя кстати подходит для ее хранения.  Нота имеет две характеристики – длительность звучания и высоту тона. По идее для каждой ноты мелодии мы должны хранить в памяти микроконтроллера эти данные. Но я для простоты выбрал мелодию, где все ноты имеют одинаковую длительность, поэтому в массиве мы будем хранить только тон ноты.
   Каждой ноте соответствует своя частота колебаний. Для того чтобы микроконтроллер мог воспроизводить ноты, нужно пересчитать эти частоты в константы для таймера Т0. Как это делать мы уже разбирали.
   Я рассчитал константы для двух октав и записал их в отдельный файл note.h. Дополнительно добавил две константы pause и end. Думаю, что смысл их понятен из названия.

  Код нашей программы


//программирование микроконтроллеров AVR на Си
//микроконтроллер играет мелодию
//пример использования массива

#include <ioavr.h>
#include <intrinsics.h>
#include "note.h"
#include "bits_macros.h"

#define button  0
#define buzer   1

__flash unsigned char Sound[] = {n1E,n1Fd,n1G,n1A,n1B,n1G,n1B,n1B,
                                               n1Ad,n1Fd,n1Ad,n1Ad,n1A,n1F,n1A,pause,
                                               n1E,n1Fd,n1G,n1A,n1B,n1G,n1B,n2E,
                                                n2D,n1B,n1G,n1B,n2D,n2D,pause,pause,end};

int main(void)
{
  unsigned char CurrentNote;
  unsigned char index;
 
  //инициализация периферии
  DDRD = (1<<buzer);
  PORTD = (1<<button);
  TIMSK = 0;
  TCCR0 = (1<<WGM01)|(0<<WGM00)|(0<<CS02)|(1<<CS01)|(1<<CS00);
  TCNT0 = 0;
  OCR0 = 0;
 
  //разрешение прерываний
  __enable_interrupt();
 
  //опрашиваем кнопку в бесконечном цикле
 //если кнопка нажата - микроконтроллер будет играть
  //мелодию пока не дойдет до конца

  while(1)
  {
    if (BitIsClear(PIND,button))
    {  
      index = 0;
      CurrentNote = Sound[index];
      while (CurrentNote != end)
      {
          if (CurrentNote != pause)
          {
            OCR0 = CurrentNote;        
            SetBit(TIMSK, OCIE0);
          }else{            
            TIMSK = 0;
          }
          index++;
          CurrentNote = Sound[index];
          __delay_cycles(2000000);
      }
    }    
  }
  return 0;
}

//обработчик прерывания таймера Т0
#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect(void)
{
    InvBit(PORTD,buzer);
}


Пояснения к коду

   В начале программы мы как обычно подключаем заголовочные файлы. Стандартные IARовские и 2 своих.

  С помощью директивы define задаем символические имена выводам микроконтроллера к которым у нас подключены кнопка и бузер.
   Определяем константный массив во флэш памяти микроконтроллера, в котором содержится наша мелодия.

__flash unsigned char Sound[] = ....

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

В функции main() объявляем две переменных

  unsigned char CurrentNote;
  unsigned char index;

   В CurrentNote микроконтроллер считывает ноту мелодии, проверяет, что она не последняя (то есть не равна константе end) и не является паузой и загружает ее в регистр сравнения таймера Т0. Переменная index – нужна для доступа к очередной ноте мелодии (к элемент массива).   

   С инициализацией периферии все понятно.

   Разрешаем прерывания и попадаем в наш любимый while(1).

   В цикле AVR опращивает кнопку. Если кнопка нажата, то обнуляет переменную index, чтобы она указывала на первую ноту мелодии (на нулевой элемент массива), а в CurrentNote считывает из массива значение ноты.

while(1)
  {
    if (BitIsClear(PIND,button))
    {  
      index = 0;
      CurrentNote = Sound[index];
      
        ...
      }
    }    
  }


   Попадаем в другой цикл while (Current Note != end). Он выполняется пока микроконтроллер не дойдет до конца мелодии.
   В цикле микроконтроллер проверяет является Current Note нотой или паузой, и в зависимости от этого загружает переменную в регистр сравнения и разрешает прерывание  таймера Т0 или наоборот запрещает его.  


if (CurrentNote != pause){
            OCR0 = CurrentNote;        
            SetBit(TIMSK, OCIE0);
          }else{            
            TIMSK = 0;
          }

  Затем инкрементирует index, считывает следующую ноту и ждет. Программная задержка здесь определяет длительность ноты/паузы.

           index++;
          CurrentNote = Sound[index];
          __delay_cycles(2000000);

   В обработчике прерывания микроконтроллер дергает ножкой.

Схема для нашего примера
.
Ring-IAR.rar - проект для IAR
Ring-WinAvr.rar - проект для WinAvr

 

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