AVR Урок 9. Оформление кода. Функции



Урок 9

Оформление кода. Функции

 

Сегодня мы попытаемся немного покрасивее оформить наш код. Главное — это конечно не красота, а то, чтобы код мы в любой момент могли прочитать и понять, причем не только мы.

Для этого существует много способов оформления кода. И один из них — функции. Функции в языке C/C++ используются для того, чтобы объединить какой-то участок кода, который особенно может в любой момент повторяться, или который представляет собой какую-то логически-законченную процедуру в отдельный фрагмент и может быть в любой момент вызван из какого-то другого участка кода.

Как пишутся функции — мы в принципе знаем с самого первого занятия, но до сих пор мы использовали только одну функцию. Это была функция main(), которая является точкой входа в нашу программу. Всё выполнение кода начинается именно с этой функции. А в дальнейшем мы будем использовать очень много различных функций. Поэтому, если кто-то что-то по функциям не поймёт, то в процессе наших дальнейших занятий он обязательно возместит данный пробел за счёт огромной практики использования функций.

Кроме тела у функции также существуют аргументы, которые мы можем передавать данной функции из фрагмента кода при вызове — это входные аргументы. А также функция может возвращать один аргумент. Как правило по данному аргументу мы будем судить, как именно прошел процесс в вызываемой функции.

Сегодня мы попытаемся написать такую функцию, которая благодаря входному аргументу, будет отображать на индикаторе определённую цифру.

Сегодня я уже не буду рассказывать, как создавать проект, я думаю, это уже всем надоело. Запустим уже созданный заранее из кода прошлого занятия и целиком настроенный проект и начнём писать нашу функцию. Напишем мы её до фунции main(), так как интерпретатор команд «смотрит» код сверху вниз и наша функция будет поэтому уже ему «знакома» в коде функции main(), и будет доступна для вызова в коде (или как говорят «видна»). Можно также писать функции и после того места, откуда они вызываются, но тогда нужно будет уже писать прототип функции. С прототипами функций мы познакомимся в более поздних занятиях. А сегодня напишем функцию сразу после объявления глобальных переменных. Глобальные переменные «видны» во всех функциях файла. Вот наша функция — пока без кода, с пустым телом. Дадим ей имя segchar. Имя функции должно говорить тому, кто будет читать код о том, чем данная функция будет заниматься. В нашем случае функция будет с помощью сегментов (seg) определённые символы (char). Вот поэтому и такое название

 

void segchar (unsigned char seg)

{

 

}

 

У данной функции будет только один входной аргумент — это целочисленное короткое значение с именем seg. Возвращать функция ничего не будет, о чём свидетельствует значение void перед её именем, то есть мы будем «верить» функции, что она справится с выводом символа на экран, и проверять это не будем.

Также чтобы функция «понимала», какой именно зажигать символ, нам желательно изучить ещё один оператор — это оператор switch, который в зависимости от значения аргумента выполняет определённый участок кода. Это непростой оператор. Для этих целей конечно подойдёт и оператор if, но когда вариантов слишком много (цифр ведь целых 10), то удобнее всё-таки применить оператор ветвления switch. Вот что это за операор

 

image00

 

Я думаю, несмотря на сложность данного оператора, мы с ним непременно разберёмся в процессе многочисленного его использования. Мастерство приходит с практикой.

 

 

Давайте применим данный оператор в нашей функции

 

void segchar (unsigned char seg)

{

  switch (seg)

  {

    case 1: PORTD = 0b11111001; break;

    case 2: PORTD = 0b10100100; break;

    case 3: PORTD = 0b10110000; break;

    case 4: PORTD = 0b10011001; break;

    case 5: PORTD = 0b10010010; break;

    case 6: PORTD = 0b10000010; break;

    case 7: PORTD = 0b11111000; break;

    case 8: PORTD = 0b10000000; break;

    case 9: PORTD = 0b10010000; break;

    case 0: PORTD = 0b11000000; break;

  }

}

 

Давайте немного разберёмся. Здесь у нас в зависимости от значения переменной seg мы будем попадать в определённый участок кода, и будем его выполнять, пока не встретится оператор break. Как только он попадётся, то мы из тела оператора switch выйдем, ну и так как за закрывающей тело фигурной скобкой следует следующая скобка, то мы вернёмся из функции в то место, откуда мы данную функцию вызвали. Ну конечно попадать, возвращаться, выходить будем не мы, это так образно говорится. Заниматься этим будет АЛУ. Мы просто представляем себя в роли АЛУ.

Здесь уже в значениях, присваваиваемых порту D, я не стал применять инвертирование, а поменял нули на единицы и наоборот.

Перейдём в функцию main() и удалим оттуда ненужные нам комментарии

 

PORTB = 0b00000001; // 1 2 3 4 5 6 7 8

while(1) // 0b|dp|g|f|e|d|c|b|a|

 

Перед тем, как вызвать нашу функцию в бесконечном цикле, мы организуем цикл, который будет перебирать подряд цифры от 0 до 9. Код скопируем из закомментированного участка бесконечного цикла, переместив его до обработчика кнопки, а комментарий потом уберем

 

while(1)

  {

  for (i=0;i<10;i++)

  {

    segchar(i);

    _delay_ms(500);

    }

  if (!(PINB&0b00000001)){

 

 

Код кнопки пока не трогаем, кнопку эту мы сегодня ещё вспомним.

Теперь соберём проект и посмотрим в протеусе. Файл протеуса также был скопирован с переименованием, и в свойствах контроллера был добавлен путь к новой прошивке. Запустим проект в протеусе и убедимся в том, что у нас циферки таким же образом работают, как и на прошлом занятии. Тем самым мы добились значительного сокращения кода в главной функции программы.

Но это ещё не всё. у нас без дела простаивает кнопка. Давайте и ей дадим какую-нибудь работу. Здесь уже одной переменной для кнопки не обойдёмся, так как нам нужен будет флаг состояния кнопки. Добавим переменную

 

unsigned butcount=0,butstate=0;

 

Также нам нужен будет ещё один цикл, но не бесконечный, как я его назвал неправильно в видеоверсии, а условный, то есть тот, который выполняется, пока выполняется условие в скобках. Данный цикл мы вставим в тело цикла for. Условием будет определённое значения флага состояния кнопки. А в тело данного цикла мы вставим весь код отслеживания состояния кнопки, который мы написали на позапрошлом занятии, когда занимались с кнопкой. Также поубираем из данного кода управление состоянием порта D, а вместо этого, если кнопка будет нажата, то мы будем обнулять счётчик. Также в обоих случаях, то есть и в случае нажатой кнопке, и в случае отжатой, мы будем устанавливать статус кнопки в единицу, чтобы выйти из нашего цикла. А после того как мы выйдем из данного цикла, мы будем вызывать функцию отображения нашей цифры, затем вызывать задержку, а затем сбрасывать статус кнопки в ноль, чтобы в следующем цикле оператора for заново попадать в цикл с обработчиком кнопки. Вот такой вот у нас теперь получится код в бесконечном цикле

 

while(1)

{

  for (i=0;i<10;i++)

  {

    while(butstate==0)

    {

      if (!(PINB&0b00000001)){

        if (butcount < 5)

        {

          butcount++;

        }

        else

        {

          i=0;

          butstate=1;

        }

      }

      else

      {

        if (butcount > 0)

        {

          butcount--;

        }

        else

        {

          butstate=1;

        }

      }

    }

    segchar(i);

    _delay_ms(500);

    butstate = 0;

  }

}

 

Жирным шрифтом, как всегда, отмечен новый код. То есть, мы видим, что кода прибавилось немного, зато сколько получилось ветвлений. Но чем сложнее код, тем больше мы начинаем понимать в искусстве программирования. Вот теперь соберем код и проверим опять же в протеусе. Кнопка должна обнулять счётчик и счёт должен будет начинаться заново. В протеусе всё работает. Теперь прошьём контроллер и проверим на живом контроллере, живой кнопке и живом индикаторе. У нас также всё работает

 

image01

 

 

Предыдущий урок Программирование МК AVR Следующий урок

 

Исходный код

 

 

Купить программатор можно здесь (продавец надёжный) USBASP USBISP 2.0

 

Смотреть ВИДЕОУРОК

 

AVR Оформление кода. Функции

 

17 комментариев на “AVR Урок 9. Оформление кода. Функции
  1. Al:

    Все получилось! Идем дальше) 

  2. федя:

    Пытаюсь  для АВР бензогенератора написать на attiny2313 и пока ничего не получается(((. Точнее есть схема и прошивка без исходника но там меня не все устраивает. Можете помочь похожим уроком? 

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

  3. gogaze:

    Пишу сюда, т.к. конкретного урока по переменным не нашел. Как в Atmel Studio создать переменную unsigned int e; для _delay_ms(время)?
    Если делать так
    unsigned int e;
    _delay_ms(40)
    е=40
    а потом использовать _delay_ms(e) компилятор блажит типа это не константа.

    • Сергей:

      _delay_ms оперирует только константами, о чем и говорит ошибка.
      Создайте, например, цикл for с нужной переменной в числе повторов, а внутрь цикла поместите _delay_ms(1)

  4. gogaze:

    Я спросил потому, что в CVAVR это
    delay_ms(40)
    е=40
    а потом использовать _delay_ms(e) проходит.

  5. Андрей:

    Исправьте в коде «butcount—» — здесь два знака «-» заменились длинным дефисом.

  6. Дмитрий:

    В качестве чего выступает переменная butstate?
    И как можно понять логику работы вот этого участка кода:
    while(butstate==0)
    {
    if (!(PINC&0b00000001))//опрос кнопки
    {
    if (butcount 0)
    {
    butcount—;
    }
    else
    {
    butstate=1;
    }
    }
    }
    Особенно интересная работа цикла while с переменной в скобках

    • Дмитрий,
      Переменная butstate выступает в качестве флага, который определяется, в какой мы стадии в данный момент находимся и, в зависимости от этого, мы либо инкрементируем счётчик, либо декрементируем. В скобках переменных нет. Там только регистр PINC, содержащий состояние ножке порта C на данный момент, которую мы логически умножаем на единицу, которая соответствует нулевому биту. Тем самым остальные биты в PINC очищаются и мы получаем значение только нулевого бита, а следовательно и нулевой ножки порта C. Если результат опреации будет ненулевым, то мы попадём в тело верхнего условия, а если нулевой, то не попадём.

  7. ANTON:

    В самом конце не хватает }

  8. jurchik323:

    Здравствуйте! Сразу хочу поблагодарить за Ваш труд. Мне как новичку Ваши уроки очень полезные. Но у меня вопрос не по теме, а как говорится — по ходу. Подскажите пожалуйста как в AtmelStudio настроить редактор кода,чтобы код шёл лесенкой? Как говорится — на скорость не влияет, но когда вижу у людей такую форму кода, аж приятно смотреть. В MPLAB X IDE эта функция по умолчанию. Но мы сейчас работаем на Atmel, а он собака весь код гонит в оду линию. Выглядит пародийно как-то.

  9. Юрий:

    Не совсем понимаю смысл выражения
    segchar(i);
    i-это переменная меняется от 0 до 9
    Но по определению void segchar (unsigned char seg)
    т.е. входные аргументы это unsigned char i и unsigned char seg
    может тогда нужно писать так
    void segchar (unsigned char seg,unsigned char i)
    {
    }

  10. Юрий:

    Заменил переменную seg на i все работает.Часть кода для кнопки убрал.Для
    чего нужна переменная seg?

    #define F_CPU 8000000L
    #include
    #include
    unsigned int i;
    //———————————————
    void segchar (unsigned int i)
    {
    switch (i)
    {
    case 1: PORTD = 0b11111001; break;
    case 2: PORTD = 0b10100100; break;
    case 3: PORTD = 0b10110000; break;
    case 4: PORTD = 0b10011001; break;
    case 5: PORTD = 0b10010010; break;
    case 6: PORTD = 0b10000010; break;
    case 7: PORTD = 0b11111000; break;
    case 8: PORTD = 0b10000000; break;
    case 9: PORTD = 0b10010000; break;
    case 0: PORTD = 0b11000000; break;
    }
    }
    //———————————————
    int main(void)
    {

    DDRD = 0xFF;
    PORTD = 0b00000000;

    while(1)
    {
    for (i=0;i<10;i++)
    {

    segchar(i);
    _delay_ms(500);

    }
    }
    }

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*