Учебный курс. Понятие переменной. Побитовый сдвиг влево. Побитовая инверсия. Оператор ветвления 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

Комментарии   

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

ошибочка, на 2 в степени n
Ответить | Ответить с цитатой | Цитировать
# Pashgan 03.11.2010 18:55
Статью в ворде писал, при переносе на сайт - степень исчезла. Подправлю.
Ответить | Ответить с цитатой | Цитировать
# zek_plt1990@bk.ru 15.03.2011 19:58
а как программно заставить включится например порт pc4 и pc5??
Ответить | Ответить с цитатой | Цитировать
# Pashgan 15.03.2011 20:00
Почитай статью про работу с битами макросы http://chipenable.ru/index.php/programming-c/4-set-bits-avr-define.html
Ответить | Ответить с цитатой | Цитировать
# ALEX1991 20.03.2011 16:45
Pashgan очень крутая статья молодец
у меня тут есть вопрос по поводу сдвига байта в право
вот код
Код:#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 20.03.2011 18:34
Все правильно. Так и должно быть.
Поменяй две строчки местами и PB0 будет зажигаться.
Код:
if (led == 0) led = 255;
led = led>>1;
Ответить | Ответить с цитатой | Цитировать
# ALEX1991 21.03.2011 14:35
Pashgan спасибо за помощь
но мне не понятно почему мой код после изменения местами двух строчек и PB0 заработал
Я в программировани и начинающий хочу знать на будующее
Ответить | Ответить с цитатой | Цитировать
# lenar 06.03.2012 18:32
потому что надо сначала проверить, передвинуть,пер едать на порт,. у тя получается , передвинул допустим дошел до нуля проверил if==0 значит led=255, и это число передаешь на выход, конечно PB0 не сработает
Ответить | Ответить с цитатой | Цитировать
# Vanjook 07.06.2011 04:00
Спасибо за статьи. Вот, немного помучившись написал програмку бегущего светодиода туда-сюда. Код:#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 13.09.2011 15:54
Блин не понимаю данный момент:
unsigned char tmp = 1;
tmp = tmp
Ответить | Ответить с цитатой | Цитировать
# Cudi 14.09.2011 14:56
Всё, понял:) я думал там восмеричная система а там десятичная))
Ответить | Ответить с цитатой | Цитировать
# Артем 26.11.2011 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;
Ответить | Ответить с цитатой | Цитировать
# Артем 26.11.2011 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 06.03.2012 18:20
чёт ты здесь намутил вроде. объясни по шагам как у тя всё работает
Ответить | Ответить с цитатой | Цитировать
# Артем 26.11.2011 21:19
Почему то при копировании часть текста исчезает :cry:
Ответить | Ответить с цитатой | Цитировать
# Сергей1 01.12.2011 14:44
Уважаемый автор, Вы не подскажете, есть ли в Си аналог ассемблерной команды ror? Заранее спасибо.
Ответить | Ответить с цитатой | Цитировать
# Nick 15.02.2012 12:07
Спасибо! Для меня, только-только начинающего, самое оно то!
Ответить | Ответить с цитатой | Цитировать
# Max 28.02.2013 19:35
Огромное спасибо! :lol:
Ответить | Ответить с цитатой | Цитировать
# alec220 01.03.2013 08:44
Второй урок пройден, программатор побежден, Attmega328 похоронена. Стал умнее не только в программировани и :) - На ISP разъем надо было ставить разъем с ключом. Не любят меги переполюсовки.. .
Ответить | Ответить с цитатой | Цитировать
# Pashgan 01.03.2013 12:41
Я один раз mega16 в DIP панель наоборот воткнул и подал питание - контроллер остался живой. Правда переполюсовки там не было, скорее он паразитно запитался через защитные диоды на выводах общего назначения.
Ответить | Ответить с цитатой | Цитировать
# Vimork 13.01.2014 19:48
Правильно ли я понял, что в данной программе переменная типа char рассматривается как число?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 14.01.2014 08:23
Да.
Цитата:
unsigned char – это беззнаковое символьное число, его размер 1 байт, а диапазон значений от 0 до 255
Ответить | Ответить с цитатой | Цитировать
# Azerhud 08.01.2016 15:58
Pashgan Вы все ещё поддерживаете этот сайт?
Ответить | Ответить с цитатой | Цитировать

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

Защитный код
Обновить