понедельник, 16 декабря 2013 г.

Светодиод, кнопка и arduino

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


Существует немало способов подключения светодиода к микроконтроллеру. Я даже не буду пытаться описать их все. Остановлюсь на паре простейших:



Светодиод будет светить только если катод подключить к земле, а на анод подать питание. Иначе света его не увидеть :) Значит если ножку микроконтроллера настроить как выход и установить на ней низкий логический уровень, то светодиод в первой схеме светить не будет. Будет светить светодиод подключенный по второй схеме. А если установить на ножке высокий логический уровень, то светодиод в первой схеме станет светить, а во второй схеме свечения не будет.

Резистор необходим для ограничения тока протекающего через светодиод и ножку микроконтроллера. Без резистора сгорит светодиод или ножка микроконтроллера. Вообще микроконтроллеры AVR способны пропускать через себя сравнительно большой ток. 20 миллиампер переживет любая ножка этих МК. Этого достаточно для обычного светодиода. Мощные осветительные светодиоды подключать описанными способами нельзя.

Итак, чтобы включать и выключать светодиод необходимо прежде всего настроить ножку микроконтроллера как выход. В приведенных ниже примерах предполагается, что светодиод подключен к 8 цифровому выводу ардуино. Для ATmega328 установленного в Arduino Uno это соответствует первому пину порта B.

Код использующий функцию стандартной библиотеки среды разработки ардуино:

pinMode(8, OUTPUT);

Я рассказывал об этой функции в статье о простейшей программе для ардуино. Сейчас расскажу об этой функции более подробно. Ей необходимо передать два параметра. Первый это номер вывода ардуино. От 0 до 13 для цифровых выводов и от 14 до 19 для аналоговых выводов. Второй параметр это специально зарезервированное слово. Оно и определяет будет ножка входом или выходом. Увидеть исходный код этой функции можно в файле wiring_digital.c У меня он находится по адресу C:\arduino-1.0.3\hardware\arduino\cores\arduino\wiring_digital.c Если загляните туда, то увидите как много всего "лишнего" делает эта функция. Это цена универсальности и удобства. Основные константы и макросы содержатся в файле Arduino.h У меня он расположен по адресу C:\arduino-1.0.3\hardware\arduino\cores\arduino\Arduino.h Отсюда можно узнать, что для компилятора OUTPUT представляет собой число 1.

Код на Си (непосредственная работа с ножкой микроконтроллера):

DDRB |= (1<<0);

Ранее упомянутая функция библиотеки ардуино в конечном счете сводится именно к этой строке кода.

Код на ассемблере:

sbi DDRB, 0

Ассемблерная команда sbi устанавливает бит в регистре ввода/вывода. У неё два параметра: первый это адрес регистра, второй это номер бита (от 0 до 7). В моем примере вместо адреса использовано название регистра. После выполнения этой команды значение бита регистра будет равно 1.

Теперь я покажу как менять логический уровень на ножке МК.

Начну с кода использующего функцию стандартной библиотеки среды разработки ардуино:

/* устанавливаем высокий логический уровень на IO8 */
digitalWrite(8, HIGH);
/* устанавливаем низкий логический уровень на IO8 */
digitalWrite(8, LOW);

Эта функция также появлялась в статьях написанных ранее. Но, если я немного повторюсь, то думаю хуже не будет. У этой функции два параметра. Первый это номер вывода ардуино. От 0 до 13 для цифровых выводов и от 14 до 19 для аналоговых выводов. Второй параметр это специально зарезервированное слово. Как не трудно догадаться, HIGH предписывает установить на выводе высокий логический уровень (ножка МК соединяется с VCC), а LOW предписывает установить на выводе низкий логический уровень (ножка МК соединяется с GND). Исходный код этой функции также можно найти в wiring_digital.c А, что же на самом деле скрывается за HIGH и LOW можно узнать заглянув в Arduino.h

Код на Си (непосредственная работа с ножкой микроконтроллера):

/* устанавливаем высокий логический уровень на первой ножке порта B (IO8) */
PORTB |= (1<<0);
/* устанавливаем низкий логический уровень на первой ножке порта B (IO8) */
PORTB &= ~(1<<0);

Код на ассемблере:

; устанавливаем высокий логический уровень на первой ножке порта B (IO8)
sbi PORTB, 0
; устанавливаем низкий логический уровень на первой ножке порта B (IO8)
cbi PORTB, 0

О команде sbi я писал выше. Повторятся не буду. Команда cbi сбрасывает бит в регистре ввода/вывода. Параметры этой команды идентичны параметрам команды sbi. Но, после её выполнения значение бита регистра будет равно 0.

Про светодиод пока достаточно. Пора перейти к рассказу о кнопке.

Способов подключения кнопки к микроконтроллеру также немало. Но в этой статье я остановлюсь всего на двух:



В представленных схемах резистор R1 нужен для защиты микроконтроллера. Он ограничивает ток протекающий через ножку микроконтроллера. Если ножка микроконтроллера настроена как вход, то этот резистор не нужен. Сопротивление входа очень велико. Протекающий ток ничтожен (около 5 микроампера). Но если по ошибке кнопка подключена к ножке микроконтроллера настроенной как выход, то этот резистор может спасти эту ножку, порт или весь контроллер.

Резистор R2 обеспечивает стабильное логическое состояние на ножке МК к которой подключена кнопка. В первой схеме этот резистор прижимает ножку к земле. И если кнопка не нажата, то чтение состояния этой ножки гарантировано даст логический ноль. Если же кнопку нажать, логический ноль сменится логической единицей. Во второй схеме резистор R2 подтягивает ножку к питанию. Поэтому когда кнопка не нажата, на ножке читается логическая единица. Нажатие на кнопку приводит к смене логической единицы на логический ноль. Из этой схемы резистор R2 можно убрать, так как у микроконтроллеров AVR есть встроенный подтягивающий резистор. Его можно включить программно.

На схемах указаны наиболее подходящие для большинства случаев номиналы. Если устройство будет работать в условиях значительных помех, то сопротивление резистора R2 может быть снижено. Нужно учесть, что при этом возрастет ток протекающий через кнопку, когда она будет в нажатом состоянии. Снижение сопротивления этого резисторы до 1 КОм переживут многие кнопки. А вот резистор R1 должен гарантировать то, что при нажатии кнопки ток не превысит максимально допустимый для ножки микроконтроллера. Для микроконтроллеров AVR в 20 миллиамперах на ножку нет ничего опасного. Однако если сопротивление этого резистора будет немного больше, то хуже не будет.

Пора переходить к описанию кода необходимого для работы с кнопкой.

Код использующий функцию стандартной библиотеки среды разработки ардуино:

pinMode(14, INPUT);

Этот код подходит для всех приведенных выше схем. Для включения внутреннего подтягивающего резистора следует вызвать эту функцию следующим образом:

pinMode(14, INPUT_PULLUP);

Этот код должен использоваться при подключении кнопки по второй схеме и при отсутствии внешнего подтягивающего резистора.

Код на Си для любой схемы:

/* первая ножка порта C (A0) настроена как вход */
DDRC &= ~(1<<0);
/* внутренний подтягивающий резистор первой ножки порта C (A0) отключен */
PORTC &= ~(1<<0);

Код на Си подходящий для второй схемы, но без внешнего подтягивающего резистора:

/* первая ножка порта C (A0) настроена как вход */
DDRC &= ~(1<<0);
/* внутренний подтягивающий резистор первой ножки порта C (A0) включен */
PORTC |= (1<<0);

Код на ассемблере для любой схемы:

; первая ножка порта C (A0) настроена как вход
cbi DDRC, 0
; внутренний подтягивающий резистор первой ножки порта C (A0) отключен
cbi PORTC, 0

И код на ассемблере для второй схемы подключения кнопки, включающий внутренний подтягивающий резистор микроконтроллера:

; первая ножка порта C (A0) настроена как вход
cbi DDRC, 0
; внутренний подтягивающий резистор первой ножки порта C (A0) включен
sbi PORTC, 0

Итак, кнопка подключена, ножка МК настроена и осталось рассказать о том как прочитать состояние ножки.

Стандартный код ардуино:

digitalRead(14);

Этой функции передается лишь один параметр. Это номер вывода ардуино к которому подключена кнопка. Эта функция возвращает LOW или HIGH. Исходный код этой функции можно найти в ранее уже упоминавшемся файле wiring_digital.c

Эту функцию можно использовать с if

if ( digitalRead(14) == LOW )
{

}

Код содержащийся внутри фигурных скобок будет выполнен только если в момент проверки на ножке к которой подключена кнопка будет логический ноль.

Еще функция digitalRead может использоваться с while

while ( digitalRead(14) == HIGH ) ;

Инструкции следующие за этой конструкцией не будут выполнятся пока на ножке к которой подключена кнопка присутствует логическая единица.

На Си проверка ножки к которой подключена кнопка может выполняется нижеприведенной конструкцией:

PINC & (1 << 0)

Если на ножке логический ноль, то и результат ноль. А вот если на ножке логическая единица, то результатом является не ноль.

Использование с if

if ( (PINC & (1 << 0)) != 0 )
{

}

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

Использование с while

while ( (PINC & (1 << 0)) == 0 ) ;

Инструкции следующие за этой конструкцией не будут выполнятся пока на ножке к которой подключена кнопка присутствует логический ноль.

На ассемблере есть две команды прекрасно подходящие для проверки ножки с подключенной кнопкой.

sbic PINC, 0

Эта команда проверяет состояние бита в регистре ввода/вывода. И если этот бит очищен (ноль), то следующая команда пропускается.

sbis PINC, 0

Эта команда тоже проверяет состояние бита в регистре ввода/вывода. Но она приводит к пропуску следующей команды, если проверяемый бит установлен (единица).

Ассемблерная конструкция аналогичная сишной if может выглядеть так:

        sbic PINC, 0
        rjmp label
        nop
label:  nop

Если на первой ножке порта C логический ноль, то команда rjmp пропускается. В результате выполняются две инструкции nop. Если на первой ножке порта C логическая единица, то команда rjmp выполняется и происходит переход к метке label. В результате выполняется только одна инструкция nop, которая следует за меткой label, а инструкция nop размещенная перед label пропускается.

На ассемблере код аналогичный Си конструкции while может быть таким:

wait:   sbic PINC, 0
        rjmp wait
        nop

Если на первой ножке порта C логическая единица, то команда rjmp выполняется и происходит переход к метке wait. И так до тех пор пока на первой ножке порта C не появится логический ноль. Тогда команда rjmp будет пропущена и будет выполнена инструкция nop.

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

Светодиод подключен к цифровому выводу 8 по второй схеме. Кнопка подключена к аналоговому выводу 0 также по второй схеме. Светодиод будет гореть, пока нажата кнопка.

Программа для arduino uno (среда разработки ардуино)

////////////////////////
//
// Arduino UNO
//
////////////////////////
//
// Sketch: LED and BUTTON
//

const unsigned char LED = 8;
const unsigned char BUTTON = 14;

void setup()
{
    pinMode(LED, OUTPUT);
    pinMode(BUTTON, INPUT);
}

void loop()
{
    if ( digitalRead(BUTTON) == LOW )
    {
        digitalWrite(LED, LOW);
    }

    else
    {
        digitalWrite(LED, HIGH);
    }
}

//
// End
//
////////////////////////

Программа для ATmega328 на C  (AVR Studio)

/*******************************************
Program                 : LED and BUTTON
Compiler                : AVR GCC
Chip type               : ATmega328
System Clock            : 16 MHz
*******************************************/

#include <avr/io.h>

int main( void )
{
    // Input/Output Ports initialization

    // LED
    DDRB |= (1<<0);
    PORTB |= (1<<0);

    // BUTTON
    DDRC &= ~(1<<0);
    PORTC &= ~(1<<0);

    while (1)
    {
        if ( (PINC & (1<<0)) == 0 )
        {
            PORTB &= ~(1<<0);
        }

        else
        {
            PORTB |= (1<<0);
        }
    }

    return 0;
}

/******************************************/

Программа для ATmega328 на ассемблере (AVR Studio)

;---------------------------------------------
; Program      : LED and BUTTON
; Compiler     : AVR Studio
; Chip type    : ATmega328
; System Clock : 16 MHz
;---------------------------------------------

.include "m328def.inc"

;---------------------------------------------

        .cseg
        .org 0x0000

;---------------------------------------------
; Steck initialization

        ldi R16, Low(RAMEND)
        out SPL, R16
        ldi R16, High(RAMEND)
        out SPH, R16

;---------------------------------------------
; Input/Output Ports initialization

        ; LED
        sbi DDRB, 0
        sbi PORTB, 0

        ; BUTTON
        cbi DDRC, 0
        cbi PORTC, 0

;---------------------------------------------
; main loop

main:
        sbic PINC, 0
        rjmp label1

        cbi PORTB, 0
        rjmp label2
label1:
        sbi PORTB, 0
label2:
        rjmp main

;---------------------------------------------

В приведенных примерах не предусмотрена борьба с дребезгом контактов. Программы слишком простые и в этом нет необходимости. Но, при возникновении такой необходимости проблема решается небольшой задержкой миллисекунд в 100.