PIC Урок 7. Семисегментный индикатор. Динамическая индикация



Продолжаем работать с программированием микроконтроллеров PIC.

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

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

 

 

Многоразрядные индикаторы, также как и одноразрядные существуют с общими анодами и с общими катодами. У нас индикатор будет именно с общим анодом, поэтому код для зажигания сегментов у нас практически не изменится, то есть сегмент мы зажигмем именно логическим нулём.

Вживую индикатор, который я исползую выглядит вот таким образом

 

 

Вид снизу

 

 

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

 

 

А вот маркировка маленького индикатора

 

 

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

Осталось дело за малым. Реализовать динамическую индикацию на деле, используя наши знания по программированию.

Микроконтроллер мы будем использовать такой же, как ина предыдущем занятииPIC16F876A, поэтому и проект мы сделаем из проекта прошлого занятия, только назовём мы его, соответственно, LED_DYN.X. Проекты мы создавать из других умеем, поэтому рассказывать об этом уже нет никакого смысла.

Откроем наш проект в MPLAB.X и попробуем его собрать.

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

Давайте пока создадим заголовочный файл main.h. Думаю, на первых порах нужно показать, как это делается.

В дереве проектов в папке с нашим проектом вызовем контекстное меню нажатием правой кнопки мыши на папке «Header Files» и выберем в нём пункт, показанный на следующем рисунке

 

 

Изменим имя

 

 

И сохраним новый файл, нажав кнопку Finish.

В открывшемся файле main.h удалим полностью весь автокод и добавим следующий

 

#ifndef MAIN_H

#define MAIN_H

//--------------------------------------------------------------

#include <xc.h>

//--------------------------------------------------------------

#define _XTAL_FREQ 4000000

// CONFIG

#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator)

#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)

#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)

#pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled)

#pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)

#pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)

#pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)

#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)

//--------------------------------------------------------------

#endif /* MAIN_H */

 

 

Мы вынесли весь конфигурационный код из файла main.c в данный файл. Поэтому в файле main.c мы подобный код удалим, а вместо него подключим файл main.h

 

#include "main.h"

//--------------------------------------------------------------

void segchar (unsigned int seg)

 

А код, предназначенный для управления светодиодным индикатором, мы также вынесем в отдельный модуль. Поэтому создадим аналогичным образом ещё один файл led.c, используя на этот раз контекстное меню в разделе дерева проекта «Source Files«, используя пункт «New -> main.c«. Также меняем имя файле на «led» и попадаем в новый файл, удалив заетм оттуда весь автокод.

Затем создадим заголовочный файл led.h со следующим содержимым

 

#ifndef LED_H

#define LED_H

//--------------------------------------------------------------

#include <xc.h>

//--------------------------------------------------------------

//--------------------------------------------------------------

#endif /* LED_H */

 

Вернёмся в файл led.c и подключим заголовочный файл, а также перенесём туда из файла main.c с последющим удалением оттуда код для зажигания цифр в разряде

 

#include "led.h"

//--------------------------------------------------------------

void segchar (unsigned int seg)

{

  switch (seg)

  {

    case 1:

      PORTB = 0b11111001;

      break;

    case 2:

      PORTB = 0b10100100;

      break;

    case 3:

      PORTB = 0b10110000;

      break;

    case 4:

      PORTB = 0b10011001;

      break;

    case 5:

      PORTB = 0b10010010;

      break;

    case 6:

      PORTB = 0b10000010;

      break;

    case 7:

      PORTB = 0b11111000;

      break;

    case 8:

      PORTB = 0b10000000;

      break;

    case 9:

      PORTB = 0b10010000;

      break;

    case 0:

      PORTB = 0b11000000;

      break;

  }

}

//--------------------------------------------------------------

 

Создадим прототип функции в заголовочном файле led.h для видимости её в других модулях

 

#include <xc.h>

//--------------------------------------------------------------

void segchar (unsigned int seg);

 

Также в файле main.h подключим файл led.h

 

#include <xc.h>

#include "led.h"

 

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

 

 

По схеме мы видим, что катоды, соответствующие сегментам, у нас соединены параллельно во всех разрядах и подключены к соответствующим ножкам порта B, а общие аноды посредством транзисторных ключей на транзисторах ss8050 подключены через резисторы по 2 килоома (можно и больше) к ножкам 0-3 порта C.

Также не следует забывать о питании, резисторе на ножке MCLR, кварцевом резонаторе и конденсаторах.

А вот так выглядит схема на макетной плате с живым контроллером (нажмите на картинку для увеличения изображения)

 

 

Как мы видим, резисторы, идущие от ножек портов к базам транзисторов, у меня все разные, но не менее 2 килоом. Максимальный номинал также существует и зависит от тока базы, требуемому для открытия транзистора, но я этим не заморачивался, вроде и так всё работает.

Вернёмся в проект и в файле led.c добавим несколько глобальных переменных для хранения цифр разрядов и счёта таймера

 

#include "led.h"

//--------------------------------------------------------------

unsigned char n_count=0, R1=0, R2=0, R3=0, R4=0;

//--------------------------------------------------------------

 

 

Добавим также функцию для включения анода определённого разряда

 

//--------------------------------------------------------------

void TIM0_Callback(void)

{

  if(n_count==0)

  {

    PORTCbits.RC0 = 1;

    PORTCbits.RC1 = 0;

    PORTCbits.RC2 = 0;

    PORTCbits.RC3 = 0;

    segchar(R1);

  }

  if(n_count==1)

  {

    PORTCbits.RC0 = 0;

    PORTCbits.RC1 = 1;

    PORTCbits.RC2 = 0;

    PORTCbits.RC3 = 0;

    segchar(R2);

}

if(n_count==2)

{

    PORTCbits.RC0 = 0;

    PORTCbits.RC1 = 0;

    PORTCbits.RC2 = 1;

    PORTCbits.RC3 = 0;

    segchar(R3);

}

if(n_count==3)

{

    PORTCbits.RC0 = 0;

    PORTCbits.RC1 = 0;

    PORTCbits.RC2 = 0;

    PORTCbits.RC3 = 1;

    segchar(R4);

  }

  n_count++;

  if (n_count>3) n_count=0;

}

//--------------------------------------------

 

В данной функции мы постепенно при каждом входе считаем от 0 до 3, а затем обнуляем счётчик и счёт начинается сначала. Также, в зависимости от значения счётчика мы попадаем в соответствующий этому значению кейс и выполняем код, находящийся в нём, который включает необходимый анод, а остальные выключает. Также мы в кейсе переключаем цифру, соответствующую данному разряду. Это код не окончательный и он будет впоследствии несколько оптимизирован. Почему возникнет данная необходимость, вы увидите позднее.

Также добавим ещё одну функцию, которая будет выводить цифру на индикатор

 

//--------------------------------------------

void ledprint(unsigned int number)

{

  R1 = number%10;

  R2 = number%100/10;

  R3 = number%1000/100;

  R4 = number/1000;

}

//--------------------------------------------

 

Данная функция распределяет по разрядам число, которое пришло во входном параметре.

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

 

void segchar (unsigned int seg);

void TIM0_Callback(void);

void ledprint(unsigned int number);

 

Перейдём в файл main.c в функцию main() и изменим разрядность переменной для счёта, так как мы будем считать до 9999

 

unsigned int i;

 

Удалим пока весь код из бесконечного цикла.

Также изменим код инициализации портов и инициализируем таймер 0, он и будет перебирать наши разряды, а по окончании инициализации зажжем какую-нибудь цифру на индикаторе

 

unsigned int i;

TRISB = 0x00;

PORTB = 0xFF;

TRISC = 0x00;

PORTC = 0x00;

OPTION_REG=0b00000111; //Prescaler 256

INTCON=0xA0;

TMR0=0;

ledprint(1234);

 

Я специально включил работу таймера на самой медленной частоте, задействовав самый большой делитель, чтобы мы сначала увидели воочию, как перебираются разряды, а затем мы его уменьшим.

Пока наш код не будет работать, так как нам необходимо написать обработчик прерывания от таймера по окончанию счёта. Добавим его над функцией main()

 

//--------------------------------------------------------------

void interrupt timer0()

{

  TIM0_Callback();

  T0IF=0;

}

//--------------------------------------------

 

В данном обработчике мы вызываем нашу функцию перебора разрядов, и затем обнуляем флаг прерывания.

Соберём проект и запустим его пока в протеусе. Мы увидим следующую картину

 

 

Разряды наши включаются по очереди, как и положено. Теперь попробуем данный код прошить в настоящий контроллер и увидим там подобную картину (на видео конечно будет нагляднее, так что смотрите видеоурок)

 

 

Изменим значение делителя в функции main()

 

OPTION_REG=0b00000011; //Prescaler 16

 

Соберём код и посмотрим результат

 

 

Вроде бы всё хорошо, но не совсем. Мы видим отображение цифр других разрядов в виде елезаметных артефактов.

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

Поэтому перейдём в файл led.c и заменим код в обработчике, заодно немного оптимизируем его и в другом плане. Нам не зачем тушить каждый раз три разряда, достаточно тушить только один, который у нас в данный момент светится

 

void TIM0_Callback(void)

{

  if(n_count==0)

  {

    PORTCbits.RC3 = 0;

    segchar(R1);

    PORTCbits.RC0 = 1;

  }

  else if(n_count==1)

  {

    PORTCbits.RC0 = 0;

    segchar(R2);

    PORTCbits.RC1 = 1;

  }

  else if(n_count==2)

  {

    PORTCbits.RC1 = 0;

    segchar(R3);

    PORTCbits.RC2 = 1;

  }

  else if(n_count==3)

  {

    PORTCbits.RC2 = 0;

    segchar(R4);

    PORTCbits.RC3 = 1;

  }

  n_count++;

  if (n_count>3) n_count=0;

}

 

Теперь мы уже сначала тушим предыдущий разряд, затем готовим цифру для следующего разряда, а затем только зажигаем следующий разряд. Также вместо if мы используем else if, чтобы код попадал только куда надо, а затем не перебирал все условия. То есть мы основательно оптимизировали свой код.

Соберём код, прошьём контроллер, а вот и результат налицо

 

 

Никаких артефактов!

Теперь давайте сделаем так, чтобы у нас отображалось не одно и то же число, а чтобы число увеличивалось, например 5 раз в секунду. Мы удалим вывод числа 1234 и напишем соответствующий код в бесконечный цикл функции main() файла main.c

 

ledprint(1234);

while(1)

{

  for(i=0;i<10000;i++)

  {

    ledprint(i);

    __delay_ms(200);

  }

}

 

Соберём код, прошьём контроллер и посмотрим результат нашей работы на индикаторе

 

 

Счётчик наш прекрасно считает и никаких артефактов нет.

Таким образом, мы сегодня освоили вывод информации на многоразрядный светодиодный семисегментный индикатор с применением метода динамической индикации. Также мы оптимизировали код динамической индикации, который мы применяли ранее на других контроллерах и повторили работу таймера.

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование МК PIC Следующий урок

 

Исходный код

 

 

Купить программатор (неоригинальный) можно здесь: PICKit3

Купить программатор (оригинальный) можно здесь: PICKit3 original

Отладочную плату PIC Open18F4520-16F877A можно приобрести здесь: PIC Open18F4520-16F877A

Семисегментный чертырехразрядный индикатор красный с общим анодом 10 шт

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

PIC Семисегментный индикатор. Динамическая индикация

17 комментариев на “PIC Урок 7. Семисегментный индикатор. Динамическая индикация
  1. P32L:

    при компиляции вываливается всегда две ошибки.Подскажите,что делаю не так?
    [img]https://b.radikal.ru/b00/1809/96/a5bf5f1c5008.jpg[/img]

  2. P32L:

    пробовал пересобрать с очисткой,результат тот же.

  3. Виталий:

    Здравствуйте! А нет ли примера динамической индикации с использованием 74НС595?

  4. Иван:

    с первого раза не собиралось, пока не обратил внимание на то, что в #include «main.h» используются кавычки, а в #include — скобки. после исправления все собирается. можно прокомментировать, в чем разница?
    спасибо

  5. Sergey:

    Вместо
    #ifndef LED_H
    #define LED_H
    //________
    #endif
    использовал
    #pragma once
    .
    Все прошло успешно и это удобнее.

  6. Николай:

    Спасибо большое за Ваш труд. Я сейчас осваиваю язык С. Работаю с PIC контроллерами. Ваша публикация мне очень помогла.

  7. Zilvinas:

    Добрый день. Я одновременно учу PIC и AVR. В динамической индикаций заметил что ножки анода в AVR подключены через коллектор а в PIC через эммитер. Питание транзисторов соответсвенно через эммитер и коллектор. Почему так? Объясните пожалуйста.

  8. Олег:

    разбирался с кодом 3 дня, вроде бы все понял, но не понял для чего вообще это сделано на видео с 21:40 минуты, для чего транзисторы все время включаются/отключаются, подавая/отключая сигнал на анод каждой цифры? Можно ли просто по счетчику включать когда нужно последующий транзистор и не отключать его? расскажите для чего это делается, почему так, я не вьеду

  9. Олег:

    на видео уроке с 21:40 минуты, объясните пожалуйста, для чего или из-за чего делается это моргание, вкл/выкл транзисторов, уменьшая делитель, оно моргает быстрее, что глазами не видим моргание, но для чего вообще это моргание, я не могу понять, расскажите.

  10. Юра:

    В подключенном индикаторе можно зажечь дополнительно запятую (чтобы замигал после второй цифры), немного доработал код и все работает:
    case 10:
    PORTB = 0b01111111; // запятая горит
    break;
    case 11:
    PORTB = 0b11111111; // запятая тухнет
    break;
    }
    }
    //—————————————————————
    void ledprint(unsigned int number)
    {
    R1 = number%10;
    R2 = number%100/10;
    R3 = number%1000/100;
    R4 = number/1000;
    if(R5==10)
    {
    R5=11;
    }
    else
    {
    R5=10;
    }
    }

    и немного здесь:
    else if(n_count==3)
    {
    segchar(R5); // управление запятой после второй цифры
    }
    else if(n_count==4)
    {
    PORTСbits.RA2 = 0;
    segchar(R4);
    PORTСbits.RA3 = 1;
    }
    n_count++;
    if (n_count>4) n_count=0;
    }

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*