Что понимают под понятием семисегментный код
Перейти к содержимому

Что понимают под понятием семисегментный код

  • автор:

Тема 2. Шифраторы и дешифраторы, элементы цифровой индикации

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

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

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

Для формирования двоичного кода, вводимого в однокристальную микроЭВМ (ОЭВМ) при нажатии соответствующей клавиши на клавиатуре, применяется цифровое устройство, называемое шифратором (Ш). Обработка и управление процессом обработки информации осуществляется микропроцессором (МП), входящим в состав микроЭВМ. Хранение исходных данных, промежуточных и конечных результатов, а также программ, по которым происходит обработка информации, осуществляется запоминающим устройством (ЗУ), входящим также в состав ОЭВМ. Вывод информации осуществляется на семисегментный светодиодный индикатор.

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

Семисегментный индикатор – представляет собой светодиодную матрицу, состоящую из семи светодиодов с общим анодом или катодом в одном корпусе. На рисунке 2.2 показан внешний вид и схемы подключения светодиодного индикатора АЛС320. Различные комбинации светящихся сегментов, обеспечиваемые внешней коммутацией, позволяют воспроизвести цифры от 0 до 9 и некоторые символы. В 8-сегментных индикаторах (АЛС321) восьмой сегмент отображает десятичную точку.

Шифратор (кодер) – это комбинационное логическое устройство, вырабатывающее на выходах параллельный двоичный код при подаче сигнала только на один какой-либо его вход. Такое кодирующее устройство применяется для преобразования символов определенного кода в n-разрядный двоичный код. Число информационных входов шифратора равно числу преобразуемых символов (клавиш клавиатуры) и удовлетворяет условию , где n – число информационных выходов. Разрядность шифратора соответствует разрядности выходного двоичного кода. 4-х разрядный шифратор позволяет получить шестнадцать (24=16) вариантов в ыходного двоичного кода, достаточных для преобразования шестнадцатеричных цифр от 0 до F.

Дешифратор (декодер) – это комбинационное логическое устройство, которое при появлении на входах параллельного двоичного кода вырабатывает выходной сигнал на одном из выходов. Как правило, номер выхода, на котором появляется выходной сигнал, соответствует определенному входному двоичному коду. Такое декодирующее устройство применяется для распознавания входных двоичных кодов и преобразования двоичного кода в другие виды кодов, например, в семисегментный код для управления семисегментными индикаторами. Число входов дешифратора равно числу разрядов входного двоичного кода, а число выходов определяется выражением , где n – число информационных входов. Полный двоичный дешифратор имеет 2n выходов. Разрядность дешифратора определяется разрядностью входного кода. Так полный 4-разрядный дешифратор имеет четыре входа и шестнадцать выходов.

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

Предварительное задание к эксперименту.

Изучив работу светодиодного индикатора, дешифратора-формирователя семисегментного кода и шифратора, определите для цифры, соответствующей Вашему номеру варианта, двоичный и семисегментный коды. Результаты запишите в соответствующие графы таблицы 2.1.

Зачем резистoры семисегментнoму индикатoру?

Есть у меня вот такой дешифратор для семисегментного индикатора tec.org.ru/board/127-1-0-470 и примерно вот такой индикатор ocenil.ru/photos/semisegmentnyy-indikator-kitay-se. и у него (индикатора -дсплея) 10 ножек.

1) зачем вообще подключать каждую ножку через резистор, не проще ли подключить все напрямую и работать на меньших напряжениях?

2) как узнать какие нужны резисторы?

3) С каких минимульных напряжений заведется индикатор?

  • Вопрос задан более трёх лет назад
  • 8066 просмотров

2 комментария

Оценить 2 комментария

Lici @Lici Автор вопроса

Напрямую на циферблат подаю 3 вольта от 2х батареек — горит. Подаю 1.5 от одной — не горит. Думаю, буду юзать 3 вольта =)

Lici @Lici Автор вопроса

Насчет резисторов понял, за картинку с цветами от DIHALT отдельное спасибо. С элементами разобрался, уже распаял, но не соединил. Теперь вопрос о том как их между собой вообще соединить тут habrahabr.ru/qa/27337/

Решения вопроса 0
Ответы на вопрос 4

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

Минимальное напряжение при котором заведется индикатор зависит от цвета. Оно должно быть не ниже падения напряжения светодиода.

image

Вот тут поглядите.

Резистор считается просто. Берем напряжение питания, вычитаем из него напряжение падения светодиода. Получаем рабочее напряжение U. Теперь надо подобрать такой резистор, чтобы по закону Ома (U=I*R) ток не превышал предельно допустимый для данного светодиода. У обычного индикаторного светодиода (не суперяркого) ток обычно 0.010А или даже 0.005А

Ответ написан более трёх лет назад
Нравится 14 4 комментария

Кстати, а где вообще можно почитать о таких вещах (типа того, что после «Вот тут поглядите» — коротко и по делу), для разных элементов?

Для совсем нубов почитай Электроника Шаг за Шагом за авторством Рудольфа Свореня. Правда книжка старовата, ее технологический предел 80е годы. Но про транзисторы, резисторы, диоды, простую логику, каскады усиления всосешь железно.

Чтобы совсем коротко и по делу и обо всем на свете…

Стюарт Болл, аналоговые интерфейсы микроконтроллеров. Очень хорошая книжка, но требуется базовые понятия.

Nickel3000 @Nickel3000

2 Krypt: а еще может оказаться полезным зайти на сайт easyelectronics.ru, DIHALT его скромно не упомянул. По основам наверняка найдете много полезного.

MichaelBorisov @MichaelBorisov

Есть отличная книжка Хоровиц и Хилл «Искусство схемотехники», 2е изд… Для начала чтения требуется только знание школьной физики. На доходчивом уровне вводятся все элементы аналоговой и цифровой схемотехники, а затем и сложные схемы на их базе. Изложение очень доступное, все явления объясняются простым языком, не оставляя пробелов. Книжка издана в 1989г, но не потеряла актуальность и по сей день.

По теме автора: в цепь светодиодов обычно нужно включать резистор потому, что у светодиодов крутая вольт-амперная характеристика. Малые изменения напряжения на светодиоде приводят к большим изменениям тока. Подашь напряжение на 0.1В меньше, чем надо — светодиод не загорится, подашь на 0.1В больше — ток превысит максимально допустимый, и светодиод сгорит. Более того, из-за технологического разброса напряжение светодиода, при котором через него протекает номинальный ток, разное даже для светодиодов одного типа. Также оно зависит от температуры.

В этой ситуации надо иметь устройство, которое подстраивает напряжение так, чтобы ток был равен заданному. Простейшим вариантом такого устройства является резистор. Включенный в цепь светодиода, он «гасит» на себе часть напряжения. При этом, если по какой-либо причине ток через светодиод возрастет — то возрастет (по закону Ома) напряжение на резисторе, соответственно напряжение на светодиоде упадет, что приведет к уменьшению тока. И наоборот. Получается отрицательная обратная связь, эффект стабилизации. Хотя, конечно, резистор лишь приблизительно обеспечивает стабилизацию тока. Чем больше падение напряжения на резисторе — тем лучше его стабилизирующие свойства, но тем хуже кпд схемы. Грубо говоря, падение напряжения на резисторе должно быть не меньше, чем падение на самом светодиоде. При этом обеспечивается кпд 50% (без учета кпд самого светодиода). Для повышения кпд вместо резисторов в цепь светодиодов включают более сложные схемы импульсного действия.

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

Введение

Для отображения цифровой информации в системах на базе микроконтроллеров используются светодиодные семисегментные индикаторы. Они просты в управлении, имеет высокую яркость, широкий диапазон рабочих температур и низкую стоимость. К недостаткам светодиодных индикаторов относятся – высокое энергопотребление, отсутствие управляющего контроллера и скудные возможности по выводу буквенной информации.
Светодиодный семисегментный индикатор представляет собой группу светодиодов расположенных в определенном порядке и объединенных конструктивно. Зажигая одновременно несколько светодиодов можно формировать на индикаторе символы цифр. Индикаторы различаются по типу соединения светодиодов – общий анод, общий катод, по количеству отображаемых разрядов – однораразрядные, двух разрядные и т.д. и по цвету – красные, зеленые, желтые и т.д.

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

Динамическое управление (динамическая индикация) подразумевает поочередное зажигание разрядов индикатора с частотой, не воспринимаемой человеческим глазом. Схема подключения индикатора в этом случае на порядок экономичнее благодаря тому, что одинаковые сегменты разрядов индикатора объединены.

Эксперименты с семисегментным индикатором

Рассмотрим простейший случай управления индикатором – вывод одной цифры. Схема для наших экспериментов приведена ниже.

Чтобы зажечь на индикаторе какую-то цифру нужно настроить порты, к которым подключен индикатор, в режим выхода, “открыть” транзистор (в данном случае подать на базу “единицу”) и установить в порту микроконтроллера её код.
В зависимости от того, куда подключены сегменты индикатора – коды могут быть разные. Для нашего случая коды цифр будут выглядеть так.

unsigned char number[] =
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
>;

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

Пример 1. Вывод цифр от 0 до 9
#include
#include

unsigned char number[] =
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
>;

unsigned char count = 0;
int main( void )
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;

//вывод, к которому подкл. катод
PORTD |= (1 <<0);
DDRD |= (1<<0);

while (1) PORTB = number[count];
count++;
if (count == 10) count = 0;
__delay_cycles (8000000);
>
return 0;
>
Эта программа каждую секунду выводит значение переменной count на семисегментный индикатор. Индикация в данном случае — статическая.

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

Пример2. Вывод цифр от 0 до 99
#include
#include

unsigned char number[] =
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
>;

unsigned char
count = 0;

//числа для вывода на индикатор
unsigned char data1 = 2;
unsigned char data2 = 5;

int main( void )
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;

//порт, к которому подкл. катод
PORTD = 0;
DDRD = (1<<1)|(1<<0);

while (1)

//выводим в порт код цифры
//и зажигаем следующий разряд
if (count == 0) PORTB = number[data2];
PORTD |= (1 <<1);
>
if (count == 1) PORTB = number[data1];
PORTD |= (1 <<0);
>

count++;
if (count == 2) count = 0;

//частота смены разрядов будет 100 Гц при кварце 8МГц
__delay_cycles (800000);
>
return 0;
>
Эта программа просто выводит любое поразрядно заданное число от 0 до 99.
Частота смены разрядов семисегментного индикатора задается с помощью программной задержки __delay_cycles(). Это не самое удачное решение, потому что добавление каких-нибудь других задач в цикл while будет мешать выводу на индикатор. Давайте организуем смену разрядов индикатора с помощью аппаратного таймера/счетчика Т0

Пример3. Вывод цифр от 0 до 99. Смена разрядов выполняется в прерывании таймера
#include
#include

unsigned char number[] =
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
>;

//числа для вывода на индикатор
volatile unsigned char data1 = 0;
volatile unsigned char data2 = 0;

int main( void )
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;

//порт, к которому подкл. катод
PORTD = 0;
DDRD |= (1<<1)|(1<<0);

//инициализация таймера Т0
//частота прерываний 100Гц при кварце 8МГц
TIMSK = (1 <TCCR0 = (1 <TCNT0 = 0xb2;

__enable_interrupt ();
while (1) //программный счетчик секунд
data1++;
if (data1 == 10) data1 = 0;
data2++;
if (data2 == 10) data2 = 0;
>
__delay_cycles (8000000);
>
return 0;
>

//прерывания таймера Т0 — вывод на индикатор
#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0_Ovf( void )
static unsigned char count = 0;
TCNT0 = 0xb2;

//выводим в порт код цифры
//и зажигаем следующий разряд
if (count == 0) PORTB = number[data2];
PORTD |= (1 <<1);
>
if (count == 1) PORTB = number[data1];
PORTD |= (1 <<0);
>

count++;
if (count == 2) count = 0;
>
Переменные data1, data2 объявлены с ключевым словом volatile, потому что они используются и в основном коде и в прерывании. В проекте под GCC я забыл поставить его поставить, и компилятор выкинул обе переменные, посчитав их ненужными!

Прерывания таймера происходят параллельно выполнению цикла while. Это позволяет выполнять в цикле какую-нибудь полезную задачу. В данном примере с помощью двух переменных в цикле организован программный счетчик от 0 до 99.
Использовать две восьмиразрядные переменные для организации счетчика от 0 до 99 неудобно и расточительно, ведь такой счетчик можно сделать и на одной переменной типа unsigned char. Хорошо, счетчик мы сделаем, а как вывести его значение на семисегментный индикатор? Нужен код “разбивающий” десятичное число на отдельные разряды и вот как он выглядит:

//программный счетчик
unsigned char counterProg = 35;

//”разбиваем” значение счетчика на отдельные разряды
data1 = counterProg % 10;
data2 = counterProg/10;

data1 = counterProg % 10 – это операция деления по модулю 10 (деление с остатком). Результатом этого выражения будет остаток от деления переменной counterProg на 10, то есть для нашего случая 5.

counterProg/10 – это целочисленное деление на 10. Результатом этого выражения будет число 3.

Таким образом, в переменные data2, data1 будут записаны числа 3 и 5 соответственно, значение счетчика counterProg при этом не изменится.

Пример 4. Вывод цифр от 0 до 99.
Преобразование двоичных чисел в двоично-десятичные (BCD)

#include
#include

unsigned char number[] =
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
>;

//числа для вывода на индикатор
volatile unsigned char data1 = 0;
volatile unsigned char data2 = 0;

//программный счетчик секунд
unsigned char counterProg = 0;

int main( void )
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;

//порт, к которому подкл. катод
PORTD = 0;
DDRD |= (1<<1)|(1<<0);

__enable_interrupt ();
while (1) //программный счетчик секунд
counterProg++;
if (counterProg == 100) counterProg = 0;
data1 = counterProg % 10;
data2 = counterProg/10;
__delay_cycles (8000000);
>
return 0;
>

//прерывания таймера Т0 — вывод на индикатор
#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0_Ovf( void )
static unsigned char count = 0;
TCNT0 = 0xb2;

//выводим код цифры в порт
//и зажигаем следующий разряд
if (count == 0) PORTB = number[data2];
PORTD |= (1 <<1);
>
if (count == 1) PORTB = number[data1];
PORTD |= (1 <<0);
>

count++;
if (count == 2) count = 0;
>
Следующий этап работы над программой – выделение кода обслуживающего светодиодный семисегментный индикатор в отдельные функции. Какой минимальный набор функций нам необходим? Функция инициализации, функция вывода на индикатор и функция преобразования чисел и записи их в буфер.

Функция инициализации

#define PORT_IND PORTB
#define DDR_IND DDRB
#define PORT_K PORTD
#define DDR_K DDRD
#define KAT1 0
#define KAT2 1

volatile unsigned char data[2];

void IND_Init( void )
//порт к которому подкл. сегменты
PORT_IND = 0xff;
DDR_IND = 0xff;

//очистка буфера
data[0] = 0;
data[1] = 0;
>

Порты, к которым подключен семисегментный индикатор, определены с помощью директивы #define – в будущем это позволит быстро править код. Вместо двух переменных data1, data2 удобнее использовать массив unsigned char data[2].

Функция преобразования

void IND_Conv( unsigned char value)
unsigned char tmp;
tmp = value % 10;
data[0] = number[tmp];
tmp = value/10;
data[1] = number[tmp];
>

Процедура преобразования чисел аналогична описанной выше. Единственное отличие – в буфере (data[]) мы теперь сохраняем не результат преобразования, а коды цифр. Зачем делать в прерывании то, что можно сделать в основном цикле программы?

Функция вывода на индикатор

void IND_Update( void )
static unsigned char count = 0;

//выводим в порт код цифры
PORT_IND = data[count];

//зажигаем нужный разряд
if (count == 0) PORT_K |= (1 < if (count == 1) PORT_K |= (1<

count++;
if (count == 2) count = 0;
>

Эта функция будет вызываться в прерывании таймера. В принципе, для экономии ресурсов ее можно было бы сделать встраиваемой.

Пример 5. Код программы с использованием этих функций //программный счетчик секунд
unsigned char counterProg = 0;

int main( void )
IND_Init();

__enable_interrupt ();
while (1) counterProg++;
if (counterProg == 100) counterProg = 0;
IND_Conv(counterProg);
__delay_cycles (8000000);
>
return 0;
>

//прерывания таймера Т0 – вывод на индикатор
#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0_Ovf( void )
TCNT0 = 0xb2;
IND_Update();
>
И, наконец – разбиение программы на модули.

Создаем два файла – indicator.h и indicator.c. Сохраняем их в папке проекта.
В хидер файле у нас будут директивы условной компиляции, макроопределения и прототипы функций.

#ifndef INDICATOR_H
#define INDICATOR_H

#define PORT_IND PORTB
#define DDR_IND DDRB
#define PORT_K PORTD
#define DDR_K DDRD
#define KAT1 0
#define KAT2 1

void
IND_Init( void );
void IND_Conv( unsigned char value);
void IND_Update( void );

#endif //INDICATOR_H

В сишном файле – #include “indicator.h”, объявления переменных и реализация трех функций. Функции описаны выше, поэтому здесь их не привожу.

Подключаем файл indicator.c к проекту.
В файл main.c инклюдим заголовочный файл нашего драйвера семисегментного индикатора — #include “indicator.h”

Все. Теперь мы можем использовать функции для работы с индикатором в файле main.
Полный код драйвера смотрите в проектах.

Продолжение следует… Скоро будет дополнение к этой статье — описание вольтметра на микроконтроллере – в проекте используется 4-ех разрядный семисегментный индикатор.

Управление семисегментным дисплеем с помощью ПЛИС

Привет, Хабр! Хочу внести свою посильную лепту в продвижение ПЛИС. В этой статье я постараюсь объяснить, как на языке VHDL описать устройство, управляющее семисегментным дисплеем. Но перед тем как начать, хочу кратко рассказать о том как я пришел к ПЛИС и почему я выбрал язык VHDL.

Где-то пол года назад решил попробывать свои силы в программировании ПЛИС. До этого со схемотехникой никогда не сталкивался. Был небольшой опыт использования микроконтроллеров (Atmega328p, STM32). Сразу после решения освоиться с ПЛИС, встал вопрос выбора языка, который я буду использовать. Выбор пал на VHDL из-за его строгой типизации. Мне, как новичку, хотелось как можно больше возможных проблем отловить на этапе синтеза, а не на рабочем устройстве.

Почему именно семисегментный дисплей? Мигать светодиодом уже надоело, да и логика мигания им не представляет из себя ничего интересного. Логика управления дисплеем с одной стороны сложнее, чем мигание светодиодом (т. е. писать ее интереснее), а с другой достаточно простая в реализации.

Что я использовал в процессе создания устройства:

  • ПЛИС Altera Cyclone II (знаю, что он безнадежно устарел, зато у китайцев его можно купить за копейки)
  • Quartus II версии 13.0.0 (на сколько я знаю это последняя версия поддерживающая Cyclone II)
  • Симулятор ModelSim
  • Семисегментный дисплей со сдвиговым регистром

Задача

Создать устройство, которое будет в цикле показывать числа 0 — 9. Раз в секунду отображаемое на дисплее значение должно увеличиваться на 1.

Реализовать данную логику можно по-разному. Я разделю данное устройство на модули, каждый из которых будет выполнять какое-то действие и результат этого действия будет передаваться следующему модулю.

Модули

  • Данное устройство должно уметь отсчитывать время. Для подсчета времени я создал модуль «delay». Этот модуль имеет 1 входящий и 1 исходящий сигнал. Модуль принимает частотный сигнал ПЛИС и, через указанное количество периодов входящего сигнала, меняет значение исходящего сигнала на противоположное.
  • Устройство должно считать от 0 до 9. Для этого будет использоваться модуль bcd_counter.
  • Для того, чтобы зажечь сегмент на дисплее, нужно выставить в сдвиговом регистре дисплея соответствующий сегменту бит в 0, а для того, чтобы погасить сегмент в бит нужно записать 1 (мой дисплей имеет инвертированную логику). Установкой и сбросом нужных битов будет заниматься декодер bcd_2_7seg.
  • За передачу данных будет отвечать модуль transmitter.

Для наглядности, привожу схему данного устройства

схема

Как видно из схемы устройство имеет 1 входящий сигнал (clk) и 3 исходящих сигнала (sclk, dio, rclk). Сигнал clk приходит в 2 делителя сигнала (sec_delay и transfer_delay). Из устройства sec_delay выходит исходящий сигнал с периодом 1с. По переднему фронту этого сигнала счетчик (bcd_counter1) начинает генерировать следующее число для отображения на дисплее. После того, как число сгенерировано, декодер (bcd_2_7seg1) преобразует двоичное представление числа в горящие и не горящие сегменты на дисплее. Которые, с помощью передатчика (transmitter1), передаются на дисплей. Тактирование передатчика осуществляется с помощью устройства transfer_delay.

Код

Для создания устройства в VHDL используется конструкция из двух составляющих entity и architecture. В entity декларируется интерфейс для работы с устройством. В architecture описывается логика работы устройства.

Вот как выглядит entity устройства delay

entity delay is -- При объявлении entity, поле generic не является обязательным generic (delay_cnt: integer); -- Описываем входные и выходные сигналы устройства port(clk: in std_logic; out_s: out std_logic := '0'); end entity delay; 

Через поле generic мы можем задать устройству нужную задержку. А в поле ports описываем входящие и исходящие сигналы устройства.

Архитектура устройства delay выглядит следующим образом

-- В секции architecture описывается то, как устройство будет работать -- С одной entity может быть связано 0 или более архитектур architecture delay_arch of delay is begin delay_proc: process(clk) variable clk_cnt: integer range 0 to delay_cnt := 0; variable out_v: std_logic := '0'; begin -- Если имеем дело с передним фронтом сигнала if(rising_edge(clk)) then clk_cnt := clk_cnt + 1; if(clk_cnt >= delay_cnt) then -- switch/case в языке VHDL case out_v is when '0' => out_v := '1'; when others => out_v := '0'; end case; clk_cnt := 0; -- Устанавливаем в сигнал out_s значение переменной out_v out_s  

Код внутри секции process исполняется последовательно, любой другой код исполняется параллельно. В скобках, после ключевого слова process указываются сигналы, по изменению которых данный процесс будет запускаться (sensivity list).

Устройство bcd_counter в плане логики выполнения идентично устройству delay. Поэтому на нем я подробно останавливаться не буду.

Вот как выглядит entity и architecture декодера

entity bcd_to_7seg is port(bcd: in std_logic_vector(3 downto 0) := X"0"; disp_out: out std_logic_vector(7 downto 0) := X"00"); end entity bcd_to_7seg; architecture bcd_to_7seg_arch of bcd_to_7seg is signal not_bcd_s: std_logic_vector(3 downto 0) := X"0"; begin not_bcd_s  

Вся логика данного устройства выполняется параллельно. О том как получить формулы для данного устройства я рассказывал в одном из видео на своем канале. Кому интересно, вот ссылка на видео.

В устройстве transmitter я комбинирую последовательную и параллельную логику

entity transmitter is port(enable: in boolean; clk: in std_logic; digit_pos: in std_logic_vector(7 downto 0) := X"00"; digit: in std_logic_vector(7 downto 0) := X"00"; sclk, dio: out std_logic := '0'; ready: buffer boolean := true); end entity transmitter; architecture transmitter_arch of transmitter is constant max_int: integer := 16; begin sclk  

В сигнал sclk я перенаправляю значение входящего в передатчик сигнала clk, но только в том случае, если устройство в данный момент выполняет передачу данных (сигнал ready = false). В противном случае значение сигнала sclk будет равно 0. В начале передачи данных (сигнал enable = true), я объединяю данные из двух входящих в устройство 8-и битных векторов (digit_pos и digit) в 16-и битный вектор (data_v) и передаю данные из этого вектора по одному биту за такт, устанавливая значение передаваемого бита в исходящий сигнал dio. Из интересного в этом устройстве хочу отметить то, что данные в dio устанавливаются на задний фронт сигнала clk, а в сдвиговый регистр дисплея данные с пина dio будут записаны по приходу переднего фронта сигнала sclk. По завершению передачи, установкой сигнала ready
Вот как выглядит entity и architecture устройства display

entity display is port(clk: in std_logic; sclk, rclk, dio: out std_logic := '0'); end entity display; architecture display_arch of display is component delay is generic (delay_cnt: integer); port(clk: in std_logic; out_s: out std_logic := '0'); end component; component bcd_counter is port(clk: in std_logic; bcd: out std_logic_vector(3 downto 0)); end component; component bcd_to_7seg is port(bcd: in std_logic_vector(3 downto 0); disp_out: out std_logic_vector(7 downto 0)); end component; component transmitter is port(enable: in boolean; clk: in std_logic; digit_pos: in std_logic_vector(7 downto 0); digit: in std_logic_vector(7 downto 0); sclk, dio: out std_logic; ready: buffer boolean); end component; signal sec_s: std_logic := '0'; signal bcd_counter_s: std_logic_vector(3 downto 0) := X"0"; signal disp_out_s: std_logic_vector(7 downto 0) := X"00"; signal tr_enable_s: boolean; signal tr_ready_s: boolean; signal tr_data_s: std_logic_vector(7 downto 0) := X"00"; -- Этот флаг, совместно с tr_ready_s контролирует -- установку и сброс rclk сигнала signal disp_refresh_s: boolean; signal transfer_clk: std_logic := '0'; begin sec_delay: delay generic map(25_000_000) port map(clk, sec_s); transfer_delay: delay generic map(10) port map(clk, transfer_clk); bcd_counter1: bcd_counter port map(sec_s, bcd_counter_s); bcd_to_7seg1: bcd_to_7seg port map(bcd_counter_s, disp_out_s); transmitter1: transmitter port map(tr_enable_s, transfer_clk, X"10", tr_data_s, sclk, dio, tr_ready_s); tr_proc: process(transfer_clk) variable prev_disp: std_logic_vector(7 downto 0); variable rclk_v: std_logic := '0'; begin if(rising_edge(transfer_clk)) then -- Если передатчик готов к передаче следующей порции данных if(tr_ready_s) then -- Если передаваемые данные не были только что переданы if(not (prev_disp = disp_out_s)) then prev_disp := disp_out_s; -- Помещаем передаваемые данные в шину данных передатчика tr_data_s  

Это устройство управляет другими устройствами. Здесь, перед объявлением вспомогательных сигналов, я объявляю компоненты которые буду использовать. В самой архитектуре (после ключевого слова begin) я создаю экземпляры устройств:

  • sec_delay — экземпляр компонента delay. Исходящий сигнал направляется в сигнал sec_s.
  • transfer_delay — экземпляр компонента delay. Исходящий сигнал направляется в сигнал transfer_clk.
  • bcd_counter1 — экземпляр компонента bcd_counter. Исходящий сигнал направляется в сигнал bcd_counter_s.
  • bcd_to_7seg1 — экземпляр компонента bcd_to_7seg. Исходящий сигнал направляется в сигнал disp_out_s.
  • transmitter1 — экземпляр компонента transmitter. Исходящие сигналы направляются в сигналы sclk, dio, tr_ready_s.

Если передатчик не занят, то процесс инициализирует начало передачи данных

 if(tr_ready_s) then if(not (prev_disp = disp_out_s)) then prev_disp := disp_out_s; -- Помещаем передаваемые данные в -- шину данных передатчика tr_data_s  

Устанавливает и сбрасывает сигнал rclk после завершения передачи данных

 if(rclk_v = '1') then disp_refresh_s  

Временная диаграмма

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

timing diagram

Сначала передаются данные “10011111“. Затем передается позиция числа на дисплее “00010000“ (этот параметр приходит в передатчик, как константа X”10”). В обоих случаях первым передается крайний правый бит (lsb).

Весь код можно посмотреть на github. Файлы с припиской *_tb.vhd — это отладочные файлы для соответствующих компонентов (например transmitter_tb.vhd — отладочный файл для передатчика). Их я на всякий случай тоже залил на github. Данный код был загружен и работал на реальной плате. Кому интересно, иллюстрацию работы кода можно посмотреть вот тут (начиная с 15:30). Спасибо за внимание.

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

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