В начало | Зарегистрироваться | Заказать наши киты почтой
 
 
 
 

Грызём микроконтроллеры. Урок 3. Циклы, прерывания и массивы

📆11 декабря 2008   ✒️Spirit   🔎44.083   💬5  
Эту статью я начну с провокационного вопроса…
А какую конструкцию на основе МК хотите создать ВЫ?
Устроим, так сказать, небольшой конкурс идей. Рассказывайте свои задумки, а я выберу самую интересную, с точки зрения программной реализации, конструкцию (а может даже и не одну) и мы попробуем ее создать!
Список всех частей:
Грызём микроконтроллеры. Урок 1. Моргаем 8-ю светодиодами. CodeVision, Proteus, ISIS
Грызём микроконтроллеры. Урок 2. CodeVision и С
Грызём микроконтроллеры. Урок 3. Циклы, прерывания и массивы
Грызём микроконтроллеры. Урок 4. Мерим температуру или напряжение
Грызём микроконтроллеры. Урок 5. Кодовый замок
Грызём микроконтроллеры. Урок 6. Прошиваем МК
Грызём микроконтроллеры. Урок 7. Подключение к МК кнопок, клавиатуры, энкодера
Грызём микроконтроллеры. Урок 8. Программирование кнопок, клавиатуры, энкодера
Грызём микроконтроллеры. Урок 9. Клавиатура вглубину



Начну я с дополнения предыдущей статьи. В ней остались не рассмотрены циклы, прерывания и массивы.

Массивы.

Массив – это простейший способ хранения однотипных данных.
Массивы бывают одномерными (строка), и многомерными (двумерный, трехмерный и т.д.)

Объявление:
char massiv[10];

создает одномерный массив из 10 элементов.
char massiv[10]={0,1,2,3,4,5,6,7,8,9};

то же самое, но мы сразу присваиваем значения элементов.

Объявление многомерных массивов происходит так же, только нужно указать несколько размерностей
char massiv[10][10];

Квадратная матрица 10х10
char massiv[10][10][10];

Кубическая 10х10х10

Обращение к элементам массива происходит по номеру элемента, причем нумерация начинается с “0” (нуля)
char massiv[10][10];
massiv[0][0]=0; // обращение к первому элементу массива
massiv[9][9]=5; // обращение к последнему элементу массива



Оператор цикла FOR

Синтаксис: for(операция_перед_началом;условие;операция_после_итерации)

Например:
char i, sum=0;
for(i=1;i<10;i++)
{
 sum=sum+I;
}

В самом начале выполнения цикла выполняется команда i=1, после чего первый раз выполняются все действия, заключенные в фигурные скобки. Каждый такой проход называется итерацией.
ИТЕРАЦИЯ (от лат. iteratio - повторение), повторное применение какой-либо математической операции.
После каждой итерации выполняется действие, описанное как операция_после_итерации. То есть, после каждого прохода в нашем случае i будет увеличиваться на единицу (i++).
Цикл будет продолжаться до тех пор, пока условие будет истинным, т.е. пока i не станет равно 10.
Таким образом, в нашем цикле вычисляется сумма всех чисел от 1 до 9.


Оператор цикла WHILE

Перевод – «до тех пор, пока»

Синтаксис: while(условие)

Пример:

char a=0;
while(a<15)
{
 a++;
}

Цикл будет выполняться до тех пор, пока условие не примет значение лжи, т.е., пока a не достигнет значения 15.


Прерывания

Для быстрой обработки каких-либо внешних сигналов или внутренних событий микроконтроллеров, разработчики добавили в МК функции прерываний
.
С аппаратной точки зрения, прерывание - это сигнал, вызывающий остановку выполнения основной программы и переход к программе обработки этого прерывания.

С программной точки зрения – это сама функция обработки прерывания. То есть, это те команды, которые должен выполнить МК при возникновении сигнала этого самого прерывания.

О названиях и применении прерываний я расскажу дальше вместе с примерами.



По теоретическим основам программирования вроде закончили, пора перейти к практике!



Как я и обещал, будем дорабатывать программу для нашей мигалки.

Начнем с записи данных во все выводы порта сразу.

Так как наш порт является 8-и битным, то мы спокойно можем записывать в него (присваивать ему при помощи операции “=”) значение какой-либо переменной или числа.

Заменим в нашей программе строки
       PORTB.0=1;
       delay_ms(500);
      …
       PORTB.7=0;
       delay_ms(500);


На следущее:
       PORTB=0b00000001;
       delay_ms(500);
       PORTB=0b00000010;
       delay_ms(500);
       PORTB=0b00000100;
       delay_ms(500);
       PORTB=0b00001000;
       delay_ms(500);
       PORTB=0b00010000;
       delay_ms(500);
       PORTB=0b00100000;
       delay_ms(500);
       PORTB=0b01000000;
       delay_ms(500);
       PORTB=0b10000000;
       delay_ms(500);

Результат работы такой же, а вот программа стала в 3 раза меньше и гораздо симпатичнее.

А если использовать тут цикл и операцию побитового сдвига, то в программе останется вообще три строчки (не читая фигурных скобок)
for(i=0;i<8;i++)
{
 PORTB= 0b00000001 << i;
delay_ms(500);
}

главное не забыть добавить в начало функции main() инициализацию переменной i
void main(void)
{
// Declare your local variables here
int i;
…



Всё это – простейшие методы реализации “бегущего огня”.

А если мы захотим задать иную последовательность? Да без проблем!

Опишем все варианты в массиве. Пусть у нас будет две точки, бегущих друг другу на встречу и обратно.
void main(void)
{
// Declare your local variables here
int i;
char migalka[8]={
0b10000001,
0b01000010,
0b00100100,
0b00011000,
0b00100100,
0b01000010};

…

while (1)
 {
  for(i=0;i<6;i++)
  {
   PORTB= migalka[i];
   delay_ms(1000);
  }  
 };



Теперь добавим в схему кнопку, которой будем переключать режим работы.



Сами режимы зададим двумерным массивом.
void main(void)
{
// Declare your local variables here
char i,j=0;
char migalka[3][8]={
{0b00000001,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000},
{0b10000000,0b01000000,0b00100000,0b00010000,0b00001000,0b00000100,0b00000010,0b00000001},
{0b01010101,0b10100110,0b11001101,0b00001111,0b11110000,0b00111110,0b11111110,0b11011101}};


Дополним и программу
while (1)
 {

  if(PIND.0==0)
  {
   j++;
   if(j>2)j=0;
  }
  for(i=0;i<8;i++)
  {
   PORTB= migalka[j][i];
   delay_ms(1000);
  }  
  
 };

Обратите внимание на то, что для записи в порт мы используем слово PORT, а для чтения состояния входов нужно использовать PIN. Нередкая ошибка, и как результат – неправильная работа программы, в том, что при попытке определить состояние входа, обращаются к регистру PORT, а в нем содержится информация о том, что выводится в порт, если он настроен как выход. А при настройке его на вход, определяет, включены ли подтягивающие резисторы, находящиеся внутри микроконтроллера.
Подтягивающие резисторы нужны для уменьшения количества элементов обвязки микроконтроллера.
Например, мы можем написать в своей программе
PORTD=0xff;

или
PORTD=0b11111111;

что по сути одно и то же, просто в разных системах счисления. Тогда включатся все потягивающие резисторы на входах D и мы можем подключать кнопки без резисторов (см. схему выше)

А теперь обратите внимание на саму программу. В ней мы проверяем состояние входа PIND.0, и, если она нажата (PIND.0==0), то увеличиваем значение переменной j, отвечающей за выбор элементов массива migalka[], на единицу. Чем и меняем режим работы нашей мигалки. Но т.к. у нас всего три режима, то добавлена строчка
   if(j>2) j=0;

чтобы значения j менялись по кругу 0 -> 1 -> 2 -> 0.

Хотя, у этой программы есть и свои недостатки. Состояние кнопки проверяется один раз за цикл работы программы, а учитывая что, в цикле for у нас 8 итераций длительностью по одной секунде (delay_ms(1000)), то эта проверка происходит с частотой 1 раз в 8 секунд.
Неудобно, особенно если нужно оперативное переключение режимов.

Вот тут мы и подходим к понятию “прерывание” и осознанию всех плюсов в его использовании

Создадим новый проект:

Грызём микроконтроллеры. Урок 3. Циклы, прерывания и массивы


Настроим порты ввода-вывода.


Для входов справа можно указать включены или нет подтягивающие резисторы.
Если установить P (PullUp) – резисторы включены, а если T (Tristate) – выключены, тогда входы как-бы «болтаются» в воздухе.


А теперь перейдем на вкладку External IRQ (внешние прерывания) и включим прерывание INT0 в режиме Falling Edge (по заднему фронту), чтобы прерывание вызывалось в момент нажатия кнопки.



Сгенерируем код и сохраним проект.

Теперь посмотрим на то, что сгенерировал нам CVAvr:

Первое, что бросается в глаза – новая функция
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
// Place your code here

}

Это и есть функция обработки прерывания.

Вставим в ее тело нашу функцию выбора режима, но уже без условия, т.к. эти команды и так будут выполняться только при нажатии кнопки:
interrupt [EXT_INT0] void ext_int0_isr(void)
{

   j++;
   if(j>2)j=0;

}


А в основном цикле программы оставим только строки
while (1)
 {

  for(i=0;i<8;i++)
  {
   PORTB= migalka[j][i];
   delay_ms(1000);
  }  
  
 };


Но теперь переменная j используется не только в функции main(), но и в обработчике прерывания, поэтому мы должны убрать ее инициализацию в основной функции и описать ее как глобальную.
Массив migalka[] тоже советую определить как глобальную переменную. Иначе, компилятор выдаст вам сообщение о переполнении стека. Стек – это хранилище данных, в которое записываются переменные текущей функции, если вызывается другая функция. Делается это для того, чтобы не потерять данные текущей функции. Но наш массив сравнительно большой и просто туда не поместится. А глобальные переменные являются общими для всех функций и помещать их в стек нет никакой необходимости.
В общем, должно получиться примерно так:
…
char j=0;
char migalka[3][8]={
{0b00000001,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000},
{0b10000000,0b01000000,0b00100000,0b00010000,0b00001000,0b00000100,0b00000010,0b00000001},
{0b01010101,0b10100110,0b11001101,0b00001111,0b11110000,0b00111110,0b11111110,0b11011101}};

// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
…
void main(void)
{
// Declare your local variables here
char i;
…


Компилируем и, если нет ошибок, идем дальше!


Теперь подправим схему, подключив кнопку к выводу внешнего прерывания INT0 (PORTD.2).



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

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

Вот она, прелесть прерываний!



Что же, не будем останавливаться на достигнутом!


Попробуем использовать таймер.


Таймер – очень удобная штука, если мы хотим выполнять одни и те же действия через одинаковые промежутки времени.


Снова создаем проект








Но теперь, ко всему прочему, заходим на вкладку Timers -> Timer 1 и устанавливаем тактовую частоту таймера и прерывание при его переполнении (Timer 1 Overflow)

При наших настройках этот таймер будет считать по кругу от 0 до 65535. И каждый раз при переходе от максимального значения к нулю (переполнении), будет вызываться функция прерывания.

Сохраняем, смотрим.

Заметили что-то новенькое?
// Timer 1 overflow interrupt service routine
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
// Place your code here

}

Это функция обработки прерывания при переполнении таймера 1.


Давайте объявим в программе глобальные переменные i, j, migalka[3][8] и вставим код в функции прерываний:
Chip type           : ATtiny2313
Clock frequency     : 1,000000 MHz
Memory model        : Tiny
External SRAM size  : 0
Data Stack size     : 32
*****************************************************/

#include <tiny2313.h>

#include <delay.h>

char i, j=0;
char migalka[3][8]={
{0b00000001,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000},
{0b10000000,0b01000000,0b00100000,0b00010000,0b00001000,0b00000100,0b00000010,0b00000001},
{0b01010101,0b10100110,0b11001101,0b00001111,0b11110000,0b00111110,0b11111110,0b11011101}};


// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{

   j++;
   if(j>2)j=0;

}

// Timer 1 overflow interrupt service routine
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{

   PORTB= migalka[j][i];
   i++;
   if(i>7)i=0;

}

void main(void)
{
….


Теперь основной цикл программы у нас остается совсем пустым и мы можем вписать туда всё, что душе угодно. Например, программу управления каким-либо дополнительным устройством или так всё и оставить, ведь мы достигли поставленной цели – мигалка работает!


Думаю, на сегодня информации для экспериментов и размышлений достаточно.


Домашнее задание:
- проверить все написанные примеры и при обнаружении каких-либо ошибок и неточностей сообщить автору;
- придумать свое устройство на микроконтроллере и рассказать о нем мне.



В следующий раз мы изучим принцип работы АЦП (аналого-цифрового преобразователя), встроенного в большинство современных МК. И разберемся, как можно измерить при помощи него напряжение, ток, мощность или температуру.
До скорых встреч! bully

Камрад, рассмотри датагорские рекомендации

🌼 Полезные и проверенные железяки, можно брать

Опробовано в лаборатории редакции или читателями.




 

Читательское голосование

Нравится

Статью одобрили 17 читателей.

Для участия в голосовании зарегистрируйтесь и войдите на сайт с вашими логином и паролем.
 

Поделись с друзьями!

 

 

Связанные материалы

 

Схема на Датагоре. Новая статья Грызём микроконтроллеры. Урок 2. CodeVision и С... Предлагаю продолжить изучение микроконтроллеров… Второй урок будет посвящен по большей части...
Схема на Датагоре. Новая статья Несколько функций для програмной реализации протокола I2C на AVR... Добрый день, дорогие друзья! Решил поделиться с вами несколькими функция для работы по протоколу...
Схема на Датагоре. Новая статья Программный валкодер... Это не новость и не статья. Это просто развёрнутый комментарий к датагосркой статье "Что есть...
Схема на Датагоре. Новая статья Немного про шину 1-Wire и цифровой термометр DS18b20... Здравствуйте, друзья. Хочу предложить вашему вниманию несколько простых функций для работы с шиной...
Схема на Датагоре. Новая статья Грызём микроконтроллеры. Урок 5. Кодовый замок... Третий свой урок я начинал с вопроса "А какую конструкцию на основе МК хотите создать...
Схема на Датагоре. Новая статья Грызём микроконтроллеры. Урок 1. Моргаем 8-ю светодиодами. CodeVision, Proteus, ISIS... Эту статью (а точнее цикл статей) я решил полностью посвятить микроконтроллерам фирмы Atmel....
Схема на Датагоре. Новая статья Грызём микроконтроллеры. Урок 4. Мерим температуру или напряжение... Мигалки – это хорошо, по новогоднему! Но ведь нельзя останавливаться на достигнутом! Пора сделать...
Схема на Датагоре. Новая статья Ремонт и модернизация мультиметра UNI-T UT60E. Цветной TFT-дисплей, литиевый аккумулятор... Всем датагорцам и гостям нашего кибер-города привет! :bye: В статье я повествую о том, как я...
Схема на Датагоре. Новая статья Программа "Справочник по SMD" v.3.1... Элементы SMD широко используются в современной электронике, да и в радиолюбительских конструкциях...
Схема на Датагоре. Новая статья Анатомия микроконтроллеров ATmega - 3. Прерывания.... Итак, наши светодиоды мигают, но мы не можем никак повлиять на программу, давайте добавим в схему...
Схема на Датагоре. Новая статья Способ хранения радиодетелей - PET преформы... Многие радиолюбители собирают все возможные баночки и контейнеры и приспосабливают их для хранения...
Схема на Датагоре. Новая статья Бокс для небольших ВЧ и СЧ головок из подручных материалов... Приветствую! На радиорынках моего города доступны автомобильные шелковые твиттеры. Пара лежит, но...
 

Комментарии, вопросы, ответы, дополнения, отзывы

 

<
Читатель Датагора

foxit

<
Читатель Датагора

GuntisK

<
Читатель Датагора

AndrewB

<
Читатель Датагора

alexusb

<
Читатель Датагора

raketa

Добавить комментарий, вопрос, отзыв 💬

Камрады, будьте дружелюбны, соблюдайте правила!

  • Смайлы и люди
    Животные и природа
    Еда и напитки
    Активность
    Путешествия и места
    Предметы
    Символы
    Флаги
 
 
В начало | Зарегистрироваться | Заказать наши киты почтой