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

17/09/2009 - 21:00

Для начала добавим в наш старый 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

Comments   

# skiff 2009-09-21 14:09
Очень хорошая статья и все выложенно на тарелке, только остаеться проглатить.Уваж аемый админ, хотелось бы побольше таких статей позновательных, например-"Что такое АЦП и с чем его едят. Делаем первое преобразование! ","Работа УАРТ. Привет компьютеру от микроконтроллер а!"Соответствен но в среде IAR Systems.Я как не слишком опытный писатель программ был бы рад увидеть такие статьи... :-)
# Pashgan 2009-09-21 14:21
Рад, что тебе понравилась статья. Следующая статья будет про прерывания. Она уже написана. Потом накатаю и по твоей теме.
# skiff 2009-09-21 14:49
Огромное Thanks!буду ждать...
# Guest 2009-10-18 15:02
Статья очень понравилась. Написано понятно и доходчиво!!!
# Guest 2010-02-01 20:25
Неплохо было бы в конце статьи давать задания. В этой статье можно было бы дать такое:
Заставьте диод бегать в одну сторону, затем в другую в цикле.
Или типа того. :)
# Guest 2010-02-08 20:29
Супер !!!
# Pashgan 2010-02-15 19:23
Я, конечно, могу добавить задания, но мне кажется, их мало кто будет делать. Хотя... надо подумать.
# Guest 2010-02-20 12:10
Спасибо за статью
# Guest 2010-03-01 17:24
Огромное спасибо, всё просто и понятно, именно таких статей не хватает мне для освоения МК ( вот решил в 45 изучать)
# Guest 2010-03-02 22:18
Спасибо за статьи, очень давно собирался начать изучение мк, но все никак. Наткнулся на этот раздел и понял, что это то чего мне так не хватало - простых и понятных разъяснений. СПАСИБО! Успехов в дальнейших начинаниях и надеемся на дальнейшие уроки ;-)
# Guest 2010-04-11 20:41
Однако я не понял почему в типе led применяется char?
Разьве int не естественнее?
# Pashgan 2010-04-12 14:48
Нет, не естественнее. AVR - 8 ми разрядный микроконтроллер . Рабочие регистры, которые в его составе - 8-ми разрядные. Поэтому обработку 8-ми разрядных данных он выполняет быстрее всего.
Порт ввода-вывода AVRa - тоже 8-ми разрядный. Зачем заводить двух байтную переменную для led, если использоваться будет только половинка?
# Guest 2010-05-25 09:05
Спасибо за отличную статью...Все получилось :)
# plumber 2010-05-27 20:43
Уроки отличные.
Задания,если есть возможность, добавьте.Кто хочет научиться будет выполнять.Атам появятся новые вопросы,обсужде ния,пути решения задач.Для обучения лучше не придумаешь.
Большое Вам спасибо.
# dnsss 2010-07-01 12:28
Подскажите пожалуйста как в IAR задать переменную в двоичном коде. Жутко привык к тому что можно делать это напрямую в AVRStudio и MPLAB, а здесь ни 0b...ни b... не проходят
# Pashgan 2010-09-02 19:55
Можно задать двоичные числа с помощью директивы #define, а можно просто переучиться на использование чисел в шестнадцатиричн ом коде. Мне тоже было неудобно, когда перешел с ассемблера на Си. Что ж поделать, не поддерживает Си стандарт представление чисел в двоичном коде.
# Guest 2010-07-06 14:41
Ну просто ооочень понятная статья- даже для "блондинки", которая по долгу службы работает с программировани ем МК. Спасибо автору!!!
# fon_harry 2010-11-03 07:31
Операция сдвига влево эквивалентна умножению на 2n.

ошибочка, на 2 в степени n
# Pashgan 2010-11-03 18:55
Статью в ворде писал, при переносе на сайт - степень исчезла. Подправлю.
# zek_plt1990@bk.ru 2011-03-15 19:58
а как программно заставить включится например порт pc4 и pc5??
# Pashgan 2011-03-15 20:00
Почитай статью про работу с битами макросы http://chipenable.ru/index.php/programming-c/4-set-bits-avr-define.html
# ALEX1991 2011-03-20 16:45
Pashgan очень крутая статья молодец
у меня тут есть вопрос по поводу сдвига байта в право
вот код
Code:#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.h>

int main(void)
{
unsigned char led = 255;
DDRB = 255;

while(1)
{
PORTB = ~led;
_delay_ms(150);
led = led>>1;
if (led == 0)
led = 255;
}
return 0;
}

так вот не загорается первый светодиод PBO я подумал из-за результата “деления” прочитал в вашей статье про макроопределени я
так я не понял из-за результата “деления” или в коде накосячил
# Pashgan 2011-03-20 18:34
Все правильно. Так и должно быть.
Поменяй две строчки местами и PB0 будет зажигаться.
Code:
if (led == 0) led = 255;
led = led>>1;
# ALEX1991 2011-03-21 14:35
Pashgan спасибо за помощь
но мне не понятно почему мой код после изменения местами двух строчек и PB0 заработал
Я в программировани и начинающий хочу знать на будующее
# lenar 2012-03-06 18:32
потому что надо сначала проверить, передвинуть,пер едать на порт,. у тя получается , передвинул допустим дошел до нуля проверил if==0 значит led=255, и это число передаешь на выход, конечно PB0 не сработает
# Vanjook 2011-06-07 04:00
Спасибо за статьи. Вот, немного помучившись написал програмку бегущего светодиода туда-сюда. Code:#include <ioavr.h>
#include <intrinsics.h>
int main(void)
{
unsigned char led = 1;
DDRB = 255;
while(1){
do {
__delay_cycles(300000);
PORTB = led;
led = led<<1;}
while (led!=128);
do {
__delay_cycles(300000);
PORTB = led;
led = led>>1;}
while (led!=1);
}
return 0;}

Материал закреплен. Хотелось бы видеть статью про синтаксис СИ в IAR вкратце по всем конструкциям и выражениям, желательно с примерами.
# Cudi 2011-09-13 15:54
Блин не понимаю данный момент:
unsigned char tmp = 1;
tmp = tmp
# Cudi 2011-09-14 14:56
Всё, понял:) я думал там восмеричная система а там десятичная))
# Артем 2011-11-26 21:15
Или вот так (бегающий светодиодик):
#include
#include

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

while(1){
if (led!=0){
PORTC = ~led;
delay_ms(100);
led = led1;}

}
return 0;
# Артем 2011-11-26 21:17
Или вот так (бегающий светодиодик)пре дыдущее удалите пож: #include
#include

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

while(1){
if (led!=0){
PORTC = ~led;
delay_ms(100);
led = led1;}

}
return 0;
# lenar 2012-03-06 18:20
чёт ты здесь намутил вроде. объясни по шагам как у тя всё работает
# Артем 2011-11-26 21:19
Почему то при копировании часть текста исчезает :cry:
# Сергей1 2011-12-01 14:44
Уважаемый автор, Вы не подскажете, есть ли в Си аналог ассемблерной команды ror? Заранее спасибо.
# Nick 2012-02-15 12:07
Спасибо! Для меня, только-только начинающего, самое оно то!
# Max 2013-02-28 19:35
Огромное спасибо! :lol:
# alec220 2013-03-01 08:44
Второй урок пройден, программатор побежден, Attmega328 похоронена. Стал умнее не только в программировани и :) - На ISP разъем надо было ставить разъем с ключом. Не любят меги переполюсовки.. .
# Pashgan 2013-03-01 12:41
Я один раз mega16 в DIP панель наоборот воткнул и подал питание - контроллер остался живой. Правда переполюсовки там не было, скорее он паразитно запитался через защитные диоды на выводах общего назначения.
# Vimork 2014-01-13 19:48
Правильно ли я понял, что в данной программе переменная типа char рассматривается как число?
# Pashgan 2014-01-14 08:23
Да.
Quote:
unsigned char – это беззнаковое символьное число, его размер 1 байт, а диапазон значений от 0 до 255
# Azerhud 2016-01-08 15:58
Pashgan Вы все ещё поддерживаете этот сайт?

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