Например TDA7294

РадиоКот >Конкурсы >Поздравь Кота по-человечески 2014! >

Видеокарта для микроконтроллера

Автор: sobs, antoha_sob_ne@mail.ru
Опубликовано 20.09.2014.
Создано при помощи КотоРед.

      Недавно я увлекся ПЛИСками. Помигал светодиодами, вывел цифры на семисегментник и подумал, что надо что-то полезное сделать. Не знаю почему, но на ум сразу пришла видеокарта для микроконтроллера – устройство с простым входным интерфейсом (я выбрал SPI) и выходом VGA. Также захотелось убрать недостаток контроллеров дисплеев – отсутствие буферной памяти. Если на экране большое разрешение и не достаточно быстрый контроллер, то видно как заполняется экран. Данная видеокарта лишена этого недостатка. Когда приходят данные, они записываются в одну оперативную память, а на экран выводится содержимое другой оперативной памяти. Когда кадр записан, ОЗУ меняются местами. Таким образом, не видно как кадр рисуется.
      Данное устройство может использоваться для вывода отладочных данных, изображений и показаний. Нет никакого ограничения на диагональ монитора, в конце есть демонстрация работы на 22 дюймовом мониторе и 40 дюймовом телевизоре.

Основные характеристики:
      1. Разрешение экрана – 640х400
      2. Количество цветов – монохром
      3. Двойная буферизация
      4. Интерфейс – SPI с дополнительными сигналами (выводы RESET, MOSI, SCK, STROBE). Формат совместим с контроллером T6963.
      5. Отсутствие команд инициализации

Сразу фото того, что получилось

Специально для этого дела развел отладочную плату на CPLD EPM240T100C5N.

Фото отладочной платы

Распиновка отладочной платы

Принципиальная схема отладочной платы

На плате присутствуют:
     1. ПЛИС, 70 выводов которой выведены на штырьки
     2. 1 пользовательский светодиод
     3. VGA выход (по 2 бита на цвет)
     4. Генератор на 25.175 МГц для режима 640х480 60 Гц
     5. JTAG разъем
     6. MiniUSB для питания платы
     7. Стабилизатор напряжения 3.3 вольта.

Печатная плата:


     Далее нужны 2 микросхемы ОЗУ. Так как я начинающий в ПЛИС, то выбрал SRAM. Единственные ОЗУ этого типа, которые я нашел на платах, были HY62256ALJ-70 объемом 32768 байт. Тут я чуть приуныл, так как этого объема даже для черно – белого изображения не хватает на полное разрешение 640х480. Нужно минимум 640*480/8 = 38400 байт. Поэтому было решено чуть уменьшить выводимую область до 640*400.

Фото платы расширения SRAM

Принципиальная схема платы расширения

Печатная плата

Фото собранной видеокарты

      После долгих раздумий была нарисована следующая упрощенная блок схема устройства, которая успешно была реализована в Quartus II Web Edition 13.0 SP1 на Verilog.

      Тут вроде как все наглядно, единственное может быть не понятно - зачем нужен синхронизатор. Дело в том, что сама видеокарта тактируется от своего тактового генератора, а сигнал, приходящий на нее с микроконтроллера, никак не с ним не синхронизирован. Из-за этого триггеры будут часто переходить в неопределенное состояние, и на экране будет искаженное изображение. Чтобы синхронизировать входные данные с видеокартой, надо каждый синхронизируемый сигнал пропустить через 2 последовательно соединенных D-триггера, тактируемых от генератора видеокарты. Этим и занимается блок «Синхронизатор». Также на него возложена роль переключать мультиплексор, по каждому фронту сигнала STROBE.

 

Прошивка ПЛИС


     Для того чтобы прошить ПЛИС нужно установить Quartus II. Quartus II Web Edition можно бесплатно скатать с сайта Альтеры. В качестве программатора можно использовать готовый китайский USB Blaster – примерно $10 на ebay.

Или собрав LPT программатор, например описанный тут или тут

Процесс прошивки:
     1. Открываем Quartus II и переходим в Tools -> Programmer
     2. Нажимаем Hardware Setup.. и из списка выбираем свой программатор и нажимаем Close
     3. Нажимаем Add File.. и выбираем файл VGA.pof
     4. Ставим галочку Program/Configure
     5. Нажимаем Start и прошивка пошла.

Описание входного интерфейса:

      Входной интерфейс представляет собой SPI с дополнительными сигналами (RESET и STROBE).
      По спаду сигнала RESET обнуляются все счетчики и устройство переходит в начальное состояние. Перед началом использования рекомендуется опустить эту линию в 0 на пару миллисекунд.
      По фронту сигнала STROBE также обнуляются все счетчики и происходит смена ОЗУ. Этот сигнал надо поднять и опустить после записи нового кадра.
      По фронту сигнала SCK происходит запись данных с линии MOSI.

Соответствие выводов на плате и сигналов интерфейса:
     18 – STROBE
     19 – MOSI
     20 – SCK
     21 - RESET

    Данные записываются таком же формате как и в дисплей с контроллером T6963, то есть горизонтально, по 8 пикселей в байте, младшим битом вперед.

    Для конвертирования картинок можно использовать программу LCDAssistant. В настройках в графе Byte orientation выбираем Horizontal, остальное без изменений.

    Для демонстрации работы видеокарты была написана программа для STM32F103RBT6. Использован программный SPI.

#include <stm32f10x.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_gpio.h>
#include "Biker.h"
#include "Yoda.h"

//Настройки, можно изменить выводы

#define VGA_PORT_EN RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN
#define MOSI_GPIO GPIOC
#define MOSI_PIN GPIO_Pin_0
#define MOSI_SET MOSI_GPIO -> BSRR = GPIO_BSRR_BS0
#define MOSI_RESET MOSI_GPIO -> BSRR = GPIO_BSRR_BR0

#define SCK_GPIO GPIOA
#define SCK_PIN GPIO_Pin_2
#define SCK_SET SCK_GPIO -> BSRR = GPIO_BSRR_BS2
#define SCK_RESET SCK_GPIO -> BSRR = GPIO_BSRR_BR2

#define STROBE_GPIO GPIOC
#define STROBE_PIN GPIO_Pin_2
#define STROBE_SET STROBE_GPIO -> BSRR = GPIO_BSRR_BS2
#define STROBE_RESET STROBE_GPIO -> BSRR = GPIO_BSRR_BR2

#define RESET_GPIO GPIOA
#define RESET_PIN GPIO_Pin_3
#define RESET_SET RESET_GPIO -> BSRR = GPIO_BSRR_BS3
#define RESET_RESET RESET_GPIO -> BSRR = GPIO_BSRR_BR3

//Программный SPI
void SPI_Write(uint8_t data){
      uint8_t i;
      for (i = 0; i < 8; i++){
           if (data & (1 << i)) MOSI_SET; else MOSI_RESET;
           SCK_SET;
           SCK_RESET;
      }
}

//Функция вывода изображения из буфера
void Draw(uint8_t x, uint16_t y, uint8_t size_x, uint16_t size_y, const uint8_t *bitmap){
      uint16_t posy;
      uint8_t posx;
      for (posy = 0; posy < 400; posy++)
           for (posx = 0; posx < 80; posx++){
                if ((posx >= x) && (posx < x + size_x) && (posy >= y) && (posy < y + size_y)){
                     SPI_Write(*bitmap++);
                } else SPI_Write(0);
           }
      STROBE_SET;
      STROBE_RESET;
}

//Функция вывода негативного изображения из буфера
void Draw_inv(uint8_t x, uint16_t y, uint8_t size_x, uint16_t size_y, const uint8_t *bitmap){
      uint16_t posy;
      uint8_t posx;
      for (posy = 0; posy < 400; posy++)
           for (posx = 0; posx < 80; posx++){
                if ((posx >= x) && (posx < x + size_x) && (posy >= y) && (posy < y + size_y)){
                     SPI_Write(~(*bitmap++));
                } else SPI_Write(0);
           }
      STROBE_SET;
      STROBE_RESET;
}

//"Инициализация"
void VGA_Init(){
      uint8_t i;
      RCC -> APB2ENR = VGA_PORT_EN;
      GPIO_InitTypeDef gpio;

      gpio.GPIO_Mode = GPIO_Mode_Out_PP;
      gpio.GPIO_Pin = MOSI_PIN;
      gpio.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(MOSI_GPIO, &gpio);

      gpio.GPIO_Mode = GPIO_Mode_Out_PP;
      gpio.GPIO_Pin = SCK_PIN;
      gpio.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(SCK_GPIO, &gpio);

      gpio.GPIO_Mode = GPIO_Mode_Out_PP;
      gpio.GPIO_Pin = STROBE_PIN;
      gpio.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(STROBE_GPIO, &gpio);

      gpio.GPIO_Mode = GPIO_Mode_Out_PP;
      gpio.GPIO_Pin = RESET_PIN;
      gpio.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(RESET_GPIO, &gpio);
      RESET_RESET;
      for (i = 0; i < 10; i++);
      RESET_SET;
      for (i = 0; i < 10; i++);
      STROBE_SET;
      STROBE_RESET;
}

int main(void){
      uint32_t i = 0;
      for (i = 0; i < 50000; i++);
      VGA_Init();

      while(1){
            Draw_inv(0, 0, 80, 400, bitmap);
            for (i = 0; i < 5000000; i++);
            Draw(0, 0, 80, 400, bitmap);
            for (i = 0; i < 5000000; i++);
            Draw_inv(14, 0, 51, 400, yoda);
            for (i = 0; i < 5000000; i++);
            Draw(14, 0, 51, 400, yoda);
            for (i = 0; i < 5000000; i++);
     }
}

 

Видео того, что получилось

 

      В будущем есть смысл докупить ОЗУ и сделать цветную видеокарту.

      На этом все, спасибо за внимание.


Файлы:
Исходники, схемы, печатные платы


Все вопросы в Форум.


Как вам эта статья?

Заработало ли это устройство у вас?

87 10 5
1 0 0