Интернет-магазин

Просмотр корзины
В корзине:

товаров - 0 шт.



§ 39.Подключение семисегментных индикаторов

Киселев Роман, Июнь 2007
Статья обновлена 26 Мая 2014

Файлы к статье скачать


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


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

На мой взгляд, самые хорошие индикаторы производит KingBright. Они бывают самых разных цветов, размеров, а также есть буквенно-цифровые и матричные индикаторы. Но работа с последними во многом аналогична применению семисегментных и с ними Вы легко разберетесь самостоятельно, научившись пользоваться семисегментными. Все индикаторы одной серии имеют одинаковую распиновку и отличаться могут лишь цветом. Это позволяет, установив индикатор в панельку для микросхем, легко заменить его индикатором другого цвета.

В качестве примера рассмотрим два индикатора: SA39-11xxx (xxx - три-четыре буквы, кодирующие цвет, в моём случае GWA) и BC56-12xxx (у меня xxx = SRWA). Приведу ниже список цветов:

  • HWA - ярко-красный на фосфиде галлия GaP, 700 нм
  • EWA - высокопроизводительный красный на фосфиде-арсениде и фосфиде галлия GaAsP/GaP, 625 нм (не знаю, как по-другому можно перевести "HIGH EFFICIENCY RED")
  • GWA - зеленый на фосфиде галлия GaP, 565 нм
  • YWA - желтый на фосфиде-арсениде и фосфиде галлия GaAsP/GaP, 590 нм
  • SRWA - супер ярко-красный на арсениде галлия-алюминия GaAlAs, 660 нм

В названии индикаторов вторая буква обознаяет тип соединения светодиодов: С - общий катод, А - общий анод. Ниже приведена электрическая схема и чертеж SA39 и SC39. Обратите внимание на буквы, которыми обозначили каждый сегмент (a-g) и обозначение точки (DP). Эти названия мы будем использовать в коде программы для объявления макросов. Это позволит как можно сильнее абстагировать программу от электрической схемы, от способа соединения индикатора и МК.

Значит, "ножки" 3 и 8 нужно подключить к шине +5 В (или, в крайнем случае, подать на них +5 В от МК, но так делать не рекомендуется), а остальные - к какому-либо порту МК. При этом не очень-то важно, в каком порядке, т. к. в случае ошибки Вы просто увидите на индикаторе не цифру, а какую-нибудь букву "зю". Тогда придется либо подключить по-другому, либо внести небольшие изменения в программу. В общем, символ, отображемый на индикаторе, зависит от того, какое число отправить в порт. Всего существует 255 комбинаций, и все они возможны независимо от способа подключения. Я использовал порт D для подключения индикатора.

Остается только лишь написать простенькую программу:

#include "iom16.h"
#define a 1   // Эти макросы содержат числа, соответствующие двойке,
#define b 2   // возведенной в степень, равной номеру "ножки" того
#define c 4   // порта, к которому подключен сегмент индикатора с
#define d 128 // одноименным макросу названием. Для того, чтобы вывести
#define e 64  // какую-либо цифру на индикатор, нужно отправить в порт
#define f 32  // число 255 минус сумму соответствующих сегментам макросов.
#define g 16  // Эти числа позволяют сделать программу независимой от подключения.
#define DP 8  // Измените эти числа, если индикатор выводит букву "зю"

short unsigned int i = 1;
unsigned char DigNumber = 0;
unsigned char Dig[10]; // Массив, в котором хранятся числа, которые нужно
// вывести через порт на индикатор, чтобы он показал цифру, равную номеру
// элемента массива. Числа зависят только от макросов.

void io_init()
{
  DDRD = 0xFF;  // К порту D подключен индикатор
  PORTD = 0xFF;
}

void timer0_init()
{
  OCR0 = 15;  // Таймер срабатывает каждые 1024 такта. Прерывание каждые
  // 1024*16 тактов.
  TCCR0 |= (1 << WGM01) | (1 << CS00) | (1 << CS02);
  TIMSK |= (1 << OCIE0);
}

void Dig_init()
{
  Dig[0] = 255 - (a+b+c+d+e+f);   // Если индикатор с общим анодом,
  Dig[1] = 255 - (b+c);           // нужно сумму макросов отнять от
  Dig[2] = 255 - (a+b+g+e+d);     // 255. Если с общим катодом, то
  Dig[3] = 255 - (a+b+g+c+d);     // отнимать не нужно.
  Dig[4] = 255 - (f+g+b+c);       // Имена макросов соответствуют
  Dig[5] = 255 - (a+f+g+c+d);     // именам сегментов индикатора
  Dig[6] = 255 - (a+f+g+c+d+e);
  Dig[7] = 255 - (a+b+c);
  Dig[8] = 255 - (a+b+c+d+e+f+g);
  Dig[9] = 255 - (a+b+c+d+f+g);
}

void main()
{
  io_init();
  timer0_init();
  Dig_init();
  SREG |= (1 << 7);
  PORTD = Dig[0];   //Выводим на индикатор цифру "0"
  while(1)
  {}
}

#pragma vector = TIMER0_COMP_vect
__interrupt void Indic_change()
{
  if (i < 675)  // 675*16*1024 = 11,0592 МГц
  {             // Каждую секунду меняется цифра на индикаторе
    i++;
  }
  else
  {
    i = 1;
    if (DigNumber < 9)
      DigNumber++;
    else
      DigNumber = 0;
    PORTD = Dig[DigNumber];
  }
}

Как это работает, думаю, ясно из комментариев к коду. Макросы после #define позволяют сделать код почти независящим от способа подключения индикатора. Если, например, Вы подключили 5-й вывод индикатора (d-сегмент) к выводу PD3 МК, то для того, чтобы все правильно работало, нужно сопоставить макросу d число 23=8, написав

#define d 8

Откомпилировав программу и прошив ее в МК, если все правильно подключено и соответствующе описано в #define, получаем цифровые электронные часы, которые умеют считать до 10. Если у Вас установлен резонатор на 11,0592 МГц, то эти часы будут тикать раз в секунду и с, казалось бы, поразительной точностью: за час они не собьются ни на секунду! Но удивительного тут ничего нет: вся точность упирается в качество резонатора и отсутствие ошибок в программе. Кстати, в электронных часах стоят такие же резонаторы, только меньших размеров и частот (обычно 3 КГц). Но собирать часы на МК несколько нерационально, потому что существуют специализированные микросхемы для этого, умеющие отсчитывать время, дату, месяц, день недели, год и т.д. Связав с такой микросхемой МК, можно научить его "чувствовать" время.

Но мы несколько отвлеклись от темы нашей статьи. Теперь пора рассмотреть работу с трехзначным индикатором, коим является BC56-12xxx. По логике у него должно быть 8*3+1=25 выводов. Но у него их только лишь 12. Давайте посмотрим его документацию.

Оказывается, внутри находятся три семисегментных индикатора, соединенные параллельно, и от каждого из них выходит один общий электрод (катод или анод). Чтобы было понятнее, покажу электрическую схему:

Итак, подавая напряжение на выводы 1, 2, 3, 4, 5, 7, 10, 11, формируют цифру (символ), мы их назовем информационными или шиной данных, а управляя выводами 8, 9, 12, определяют позицию этой цифры. Выводы 8, 9, 12 назовем выводами выбора устройства или шиной адреса. Понятно, что нельзя одновременно зажечь две или три разные цифры. Придется использовать стробирование, т. е. зажигать цифры по очереди с высокой скоростью. Они будут мерцать, но глаз этого не успеет заметить.

Значит, с выводами 1, 2, 3, 4, 5, 7, 10, 11 работа совершенно аналогична. А выводы 8, 9, 12 нужно замыкать на землю по очереди. Казалось бы, чего проще - присоединить их к МК и все. Но, как я уже говорил, не следует цеплять к МК общий электрод - это слишком большая нагрузка для него, и его порт ввода-вывода может сгореть. Следует использовать полевые транзисторы, как это было описано в предыдущей статье.

Мне кажется, настало время рассказать о шифраторах и дешифраторах. Дешифраторами называются комбинационные устройства, преобразующие n-разрядный двоичный код на их входе в логический сигнал, появляющийся на том выходе, десятичный номер которого соответствует двоичному коду. Дешифраторы имеют как правило 3 или 4 входа и 8 или 16 выходов соответственно. Как работает дешифратор? Подадим на вход число в двоичном коде, например, 5, установив лог. 1 на первом и третьем выводах на входе. Тогда появится лог. 1 на пятом выходе. Шифраторы выполняют обратную операцию - если подать лог. 1 на один из входов, на выходе появится двоичный код номера этого входа.

Дешифраторы позволяют решить проблему нехватки выходов. Например, у МК есть 3 свободных выхода для выбора устройства, а мы хотим подключить семь устройств. Но ведь у этих трех выходов могут быть 8 различных состояний! Подключив дешифратор, из трех выходов сделаем восемь работающих по очереди и решим т. о. проблему.

В школах и особенно вузах часто на уроках информатики рисуют логическую схему работы памяти, где есть груда логических элементов "И" и/или "ИЛИ" для выборки ячейки памяти, какая-то шина данных, шина адреса, ячейки памяти... Короче, обычно преподаватель сам не может понять, что он нарисовал, как это все работает и не загибается. Оказывается, там стоит дешифратор и все работает очень просто. По шине адреса приходит адрес ячейки памяти в двоичном коде, в которую надо записать информацию (или прочитать) и поступает в дешифратор. Дешифратор подключает требуемую ячейку к цепи питания, ячейка начинает работать и сохраняет тот двоичный код, который установлен на шине данных (или выдает туда свое содержимое). После этого исчезает адрес на шине адреса и дешифратор отключает эту ячейку до следующего использования. При запуске компьютера в программе Setup можно установить напряжение на ЦПУ (там можно выбрать из нескольких значений). А как это происходит? Есть специальная шина, по которой двоичный код поступает в дешифратор. Дешифратор открывает один из нескольких транзисторов и через этот транзистор идет питание требуемого напряжения на ЦПУ.

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

Вернемся к нашей конструкции. Схема очень проста. Я прицепил к порту А половинку панельки для широких микросхем, так чтобы подключенными оказались выводы 1, 2, 3, 4, 5, 7, 10, 11 вставленного в нее семисегментного индикатора BC56-12xxx. Другие три вывода подключены через уже знакомые нам транзисторы IRF7311 к выводам порта С (вывод 12 к РС5, 9 - к РС6, 8 - к РС7). Один из транзисторов (корпуса два, а их четыре) остался про запас.

Код программы приведен ниже. Поскольку в будущем может возникнуть необходимость некоторое трехзначное число, хранящееся в переменной, вывести на индикатор, неплохо было бы написать для этого какую-нибудь специальную функцию. Назовем ее Display(). Казалось бы, реализовать ее очень просто, нужно число поделить на 100, найти целое от деления и остаток и потом также поделить на 10. Но... Но МК не знает функций mod и div. Это во-первых. А во-вторых, у него нет математического сопроцессора для деления, и одна операция деления может занимать сотни и даже тысячи тактов процессора, в отличие от сложения/вычитания (1 такт для char) и умножения на целое (2 такта для char). Поэтому всегда старайтесь заменить деление чем-либо другим, если возможно. Иногда бывает даже рациональнее отправить некоторые данные через COM-порт в компьютер, там произвести вычисления и вернуть обратно результат. Как это реализовать, в следующей статье. А сейчас рассмотрим наглядный пример.

Предположим, Вы решили поставить у себя в ванной электронный регулятор температуры воды на МК, который постоянно измеряет температуру воды в душе специальным датчиком и с помощью мотора крутит кран так, чтобы температура стремилась к заданному значению. И Вы используете некий алгоритм, позволяющий с очень большой точностью управлять этим процессом. Но алгоритм содержит операции деления, извлечения корня, вычисления логарифма, косинуса и, несомненно, интеграла. И когда Вы пойдете мыться, после поворота ручки регулировки температуры, МК "зависнет" и будет на Вас несколько секунд будет лить воду температурой 70-80 °С, а потом, когда он все вычислит, с большой точностью выставит нужные Вам 28,32204°С. :)

Этот пример (не я его придумал) показывает, и хорошо показывает, что часто не столь важна точность, сколько быстродействие. Вот код программы. Не забудьте отключить оптимизацию.

#include "iom16.h"
#define a 1
#define b 4
#define c 16
#define d 64  //Меняем эти числа для другого индикатора
#define e 128
#define f 2
#define g 8
#define DP 32

short unsigned int i = 1;
short unsigned int Number = 0;
unsigned char      Dig[10];
// В этих переменных хранятся цифры, которые нужно отобразить
char               Disp5, Disp6, Disp7;

// Функция выделяет цифры из трехзначного числа Number
void Display (short unsigned int Number)
{
  unsigned char Num1, Num2, Num3;
  Num1=Num2=0;
  while (Number >= 100)  
  {
    Number -= 100;  
    Num1++;  
  }
  while (Number >= 10) 
  {
    Number -= 10;  
    Num2++; 
  }
  Num3 = Number;
  Disp5 = Dig[Num1];
  Disp6 = Dig[Num2];
  Disp7 = Dig[Num3];
}

void io_init() //Инициализация портов ввода/вывода
{
  DDRA = 0xFF;
  PORTA = 0;
  DDRC |= (1 << PINC5) | (1 << PINC6) |(1 << PINC7);
  PORTC = 0;
}

void timer0_init()
{
  OCR0 = 15;
  TCCR0 |= (1 << WGM01) | (1 << CS00) | (1 << CS02);
  TIMSK |= (1 << OCIE0);
}

void Dig_init()
{
  Dig[0] = (a+b+c+d+e+f);   // Сейчас у нас схема с общим катодом
  Dig[1] = (b+c);
  Dig[2] = (a+b+g+e+d);
  Dig[3] = (a+b+g+c+d);
  Dig[4] = (f+g+b+c);
  Dig[5] = (a+f+g+c+d);
  Dig[6] = (a+f+g+c+d+e);
  Dig[7] = (a+b+c);
  Dig[8] = (a+b+c+d+e+f+g);
  Dig[9] = (a+b+c+d+f+g);
}

void main()
{
  unsigned char j, k = 0;
  Dig_init();
  Display(0);
  io_init();
  timer0_init();
  SREG |= (1 << 7);
  while(1)
  {
    for (j = 0; j <= 50; j++){} // Задержка для отображения цифры
    (k == 3) ? k = 0 : k++;
    PORTC &= 31;  //Очистка PC7, PC6, PC5
    for (j = 0; j<=30; j++){} // Задержка для выключения транзистора
    switch (k)
    {
      case 0: PORTC |= (1 << PINC7); // Единицы
              PORTA = Disp7;
        break;
      case 1: PORTC |= (1 << PINC6); // Десятки
              PORTA = Disp6;
        break;
      case 2: PORTC |= (1 << PINC5); // Сотни
              PORTA = Disp5;
    }
  }
}

#pragma vector = TIMER0_COMP_vect
__interrupt void Indic_change()
{
  if (i < 675)
  {
    i++;
  }
  else
  {
    i = 1;
    if (Number < 999)
      Number++;
    else
      Number = 0;
      PORTB++;
    Display(Number); // Увеличение отображаемого числа.
  }
}

Функция Display(Number) берет число Number и отнимает от него по сотне, пока Number не станет меньше 100. Количество отнятых сотен сохраняется в Num1. Аналогично потом отнимают десятки. Количество десятков - в переменной Num2. Остаток (единицы) сохраняется в Num3. Глобальным переменным Disp5, Disp6, Disp7 присваиваются значения, которые нужно отправить в порт A, чтобы отобразить исходное число Number. Как это выглядит в действии, увидите, когда прошьёте программу в МК.



© Киселев Роман
Июнь 2007
http://www.kernelchip.ru



© KERNELCHIP 2006 - 2017