Учебный курс. Понятие переменной. Побитовый сдвиг влево. Побитовая инверсия. Оператор ветвления if...else.

Для начала добавим в наш старый workspace новый проект
Project > Create NewProject…
Выбираем шаблон проекта C > main
Обзываем проект led2 и сохраняем его в новой папке.
Сохраняем  workspace - File > Save Workspace
Задаем настройки Project > Options… для конфигурации Release
Все то же самое как и в первой статье.


Схема для нашего примера.

   Светодиод мы зажигаем, выставляя в порту ноль. У нас восемь светодиодов и нам нужно зажигать их друг за другом, по одному. По сути дела нам нужно записывать в порт последовательность чисел
0b11111110
0b11111101
0b11111011
0b11110111
и так далее пока нолик не дойдет до старшего разряда.
    В принципе мы могли бы сохранить в памяти микроконтроллера эту последовательность чисел, а потом просто загружать эти числа по одному в порт, но мы поступим по-другому – будем генерировать их, а для этого нам понадобится переменная.

________________________________________________

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

   С двумя целочисленными типами переменных мы уже сталкивались. Теперь познакомимся с наиболее родным типом данных для нашего 8-ми битного AVR микроконтроллера – unsigned char

unsigned char – это беззнаковое символьное число, его размер 1 байт, а диапазон значений от 0 до 255.

unsigned char tmp;              //объявление переменной    
unsigned char var1, var2;    //можно объявлять сразу несколько переменных    
unsigned char led = 0;       //объявляя переменную ей можно присвоить значение

   Написание имени переменной подчиняется определенным правилам. Можно использовать заглавные и строчные буквы, цифры и символ подчеркивания _. Первый символ имени переменной должен быть либо буквой, либо символом подчеркивания. В качестве имени переменной нельзя использовать ключевые слова (while, if, return и все остальные зарезервированные слова). Желательно, чтобы имя переменной отражало смысл ее содержимого, это облегчает чтение программы.
   У AVR микроконтроллерa есть несколько видов памяти – память программ или flash память, EEPROM, оперативная память (ОЗУ)  и регистры. Память программ, помимо своего прямого назначения, используется для хранения констант – переменых которые не меняют своего значения в ходе выполнения программы. EEPROM имеет ограниченное число циклов перезаписи, поэтому хранить там переменную можно только если ее значение изменяется довольно редко. А вот оперативная память и регистры наилучшим образом подходят для хранения переменных.
   Переменные объявленные внутри функции называются локальными, их область видимости ограничена телом функции, Это означает, во-первых, что другие функции не могут обращаться к этим переменным (они их не видят), а во вторых, что в функциях могут быть объявлены переменные с одинаковыми именами. Локальные переменные хранятся в регистрах общего назначения микроконтроллера. При выходе из функции значение локальной переменной не сохраняется. Если нам нужно сохранять значение локальной переменной, то нужно использовать статические локальные переменные. Они сохраняются в оперативной памяти. Об этом мы поговорим позже.
   Переменные объявленные вне функций называются глобальными, к ним можно обратиться из любой функции. Глобальные переменные хранятся в оперативной памяти. Когда происходит обращение к глобальной переменной, ее значение считывается в регистры общего назначения.

________________________________________________

Итак, вот кусок нашей программы

//программа бегущего светодиода
#include <ioavr.h>
#include <intrinsics.h>

int main(void)
{
  unsigned char led;    
  DDRC = 255;        
 
  while(1){           
 

  }
  return 0;
}
    
   Мы подключили заголовочные файлы, объявили локальную переменную, настроили порт C на выход. Поскольку программа будет постоянно выполнять один и тот же кусок, сразу вписали бесконечный цикл.
Нам нужно заставить нолик сдвигаться влево, при этом все остальные разряды должны быть единичками. И вот как мы это реализуем. В начальном состоянии в переменной led будет единица. Мы будем сдвигать ее влево на один разряд, а в порт B будем записывать проинвертированное значение led.
   Подозреваю, что вы не знакомы с битовыми операциями. Поэтому две из  них мы сейчас разберем.

________________________________________________

 

    - побитный сдвиг влево
 
  Сдвигает число на n разрядов влево. Старшие n разрядов при этом исчезают, а младшие n  разрядов заполняются нулями. Операция сдвига влево эквивалентна умножению на 2n.


        unsigned char tmp = 1;
        tmp = tmp << 1;   
        //теперь в переменной tmp число 2 или 0b00000010
        
        tmp = tmp << 3;
        //теперь в переменной tmp число 16  или 0b00010000
        
        tmp = 255;
        tmp <= 2; //сокращенный вариант записи
        //теперь в переменной tmp число 252 или 0b11111100

~    - побитная инверсия
 
Поразрядно инвертирует число. Разряды, в которых были нули – заполняются единицами. Разряды, в которых были единицы – заполняются нулями.

    
        unsigned char tmp =  94;     //0b01011110
        tmp = ~tmp;
        //теперь в переменной tmp число 161 или 0b10100001
        
        tmp = ~tmp;
        //теперь в tmp снова число 94 или 0b01011110

             


С учетом новых знаний, можно написать уже это.
    
//программа бегущего светодиода
#include <ioavr.h>
#include <intrinsics.h>


int main(void)
{
  unsigned char led = 1;
  DDRC = 255;
 
  while(1){
    PORTC = ~led;    
    __delay_cycles(400000);
    led = led<<1;
  }
  return 0;
}
 
   Выражение на Си читаются справа налево.
Строчка PORTC = ~led; означает – взять значение переменной led, проинвертировать его и присвоить PORTC (записать в PORTC). Значение led при этом никак не меняется!
    Величину программной задержки я уменьшил, чтобы бегущий светодиод эффектней смотрелся.
    Данный вариант программы будет работать, но ... не долго. Как только единичка в переменной led будет сдвинута 8 раз,  она затрется и в led окажется нулевое значение. А ноль как не сдвигай, толку никакого. Получается, что нам нужно после каждого сдвига проверять, не обнулилась ли наша переменная и как только это произойдет снова записать в led единицу. Для этого воспользуемся оператором ветвления if

______________Синтаксис if...else________________

   Операторы ветвления предназначены для управления ходом выполнения программы. Они выбирают, какую часть программы выполнять в зависимости от истинности или ложности заданных условий. Одним из типов ветвлений в Си является if ... else. Этот тип ветвления осуществляет выбор между двумя альтернативами. Он имеет следующий синтаксис.

if (condition){
    statement1;  
    statement2;
}
else{
    statement3;
    statement4;
}

  Если условие (condition) окажется истинным, будет выполняться блок, следующий сразу после if, если ложным – блок следующий после else. В качестве условия может выступать переменная, выражение или функция,  возвращающая значение. Чаще всего условие задается операторами отношени. В языке Си есть шесть таких операторов.

Оператор Значение
 == равно
 !=  не равно
>  больше
 >=  больше или равно
 <  меньше
 <=  меньше или рав

   Выражение условия, записанное с помощью операторов отношения это по сути своей обычное школьное неравенство, с тем лишь отличием, что используются немного другие знаки.
Вот как, например, записать для нашей программы проверку переменной led на нулевое значение:

    if (led == 0){    //если переменная led равна 0           
        ……                //выполнить этот блок
     }
__________________________________________
Дополнительные сведения по оператору if…else

•    Использование else не является обязательным.

if (condition){
    statement1;
    statement2;
}

•    Если блок состоит из одного оператора, то фигурные скобки {} можно не ставить.

if (condition)
      statement1;

•    Ветвления могут быть вложенными.

if (condition1){
    if (conditon2)
           statement1;
}
else{
    if (condition3)    {
        statement3;
        statement4;
    }
}

____________________________________________________
Наиболее типичные ошибки при использовании оператора if  это:

•    ставить точку с запятой после условия

if (tmp == 0);    
      tmp= 1;    //tmp всегда будет присваиваться 1

Синтаксически здесь все правильно и компилятор не выдаст ошибку, однако программа будет вести себя не так, как вы задумали. Эта ошибка еще встречается при использовании цикла while

•    при задании условия вместо оператора отношения равно == использовать оператор присвоения =

if (tmp = 0){    //в этом случае tmp присвоится 0, а блок операторов не будет выполнятся
      tmp = 1;
       ……….
}

IARовский компилятор выдает предупреждение на подобную ошибку.

•    неправильно организовывать вложенные ветвления

if (condition1)
          if (conditon2)
                    statement1;
else
          statement3;

   По задумке программера else должен относиться к первому оператору if. А компилятор связывает else со вторым оператором if. Общее правило здесь такое - else связан с последним из операторов if, который не имеет собственного блока else. Чтобы этого не происходило, ограничивайте тело оператора if…else фигурными скобками. Вообще считается, что скобки нужно ставить всегда, даже если тело оператора if…else (да и многих других операторов, где допускается опускать скобки) состоит из одного выражения.
Вот так будет правильней.

if (condition1){
          if (conditon2)
                    statement1;
}
else
          statement3;

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

if (condition1){
          if (conditon2){   
                 statement1;
           }
}
else{
          statement3;

________________________________________________

Вот мы и подошли к окончательной версии нашей программы.

//программа бегущего светодиода
#include <ioavr.h>
#include <intrinsics.h>


int main(void)
{
  unsigned char led = 1;
  DDRC = 255;
 
  while(1){
    PORTC = ~led;
    __delay_cycles(400000);
    led = led<<1;
    if (led == 0)
      led = 1;
  }
  return 0;
}

   Надеюсь все понятно. Нажимаем F7. Ищем файл прошивки в директории проекта. Грузим в микроконтроллер и наслаждаемся бегущим огоньком.

Файлы

Схема для нашего примера.
Проект для IAR AVR. Бегущий светодиод
Проект для WINAVR
Проект для CodeVision

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