Подключение Arduino к VGA монитору

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

Давно бьюсь над применением  Arduino для вывода текста на VGA монитор. Больше всего мне нравится решение отсюда: https://www.gammon.com.au/forum/?id=11608

Используются две платы Arduino, одна выводит изображение на монитор, вторая передаёт текст по интерфейсу I2C на первую. В статье по ссылке всё очень хорошо описано, только один нюанс: плату, выводящую на монитор, нужно прошивать в Arduino1.0, иначе будете долго мучиться, и безрезультатно. Вторую (передающую) плату можно прошивать в любой Arduino, никаких проблем.

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

Думаю, не у всех есть желание и возможность попробовать подключение к монитору, но, возможно кто-то разгадает загадку по коду.

Вот код передающей платы:

// Video text sender
// Author: Nick Gammon
// Date:    20th April 2012
// License: Released to public domain.

#include <Wire.h>

const byte i2cAddress = 42;

enum SEND_COMMANDS { CLRSCR = 1, CLREOL, GOTOXY, ESC = 27 };
int count = 0;

char clearScreen [] = { ESC, CLRSCR };
char clearToEndOfLine [] = { ESC, CLREOL };
char gotoXY [] = { ESC, GOTOXY };  // followed by x and y

void setup ()
{
  Wire.begin ();
  TWBR = 4;   // fast .. fast .. I2C : 16000000 / (16 + 2 * 4) = 666666 Hz
}  // end of setup

void sendString (const char * s)
  {
  Wire.beginTransmission (i2cAddress);
  Wire.write (s);
  Wire.endTransmission ();
  }  // end of sendString

char buf [20];

void loop ()
{
  sendString (clearScreen);
  sendString ("Hi there\n");
  sendString ("Everyone\n");

  delay (2000);

  // clear screen test
  sendString (clearScreen);
  sendString ("After CLRSCR\n");

  // display a counter
  sprintf (buf, "%i\n", count++);

  sendString (buf);

  delay (2000);
}  // end of loop

Вот операторы, котрые не использованы в примере, должны работать, но не работают:

  sendString (clearToEndOfLine); // перевод курсора к началу строки 
  sendString (" qwerty1234\n");
  sendString (gotoXY); // перевод к позиции, как задать координаты 
  // позиции, тоже не ясно, пробовал sendString (15);sendString (15);
  // в результате просто печатаются символы с указанными номерами
  sendString ("12345678\n");

Вот код принимающей и выводящей на монитор платы, интересующая часть примерно с 150 строки по 265:

/*
 VGA video generation

 Author:   Nick Gammon
 Date:     20th April 2012
 Version:  1.2

 Version 1.0: initial release
 Version 1.1: code cleanups
 Version 1.2: more cleanups, added clear screen (0x0C), added scrolling


 Connections:

 D1 : Pixel output (470 ohms in series to each one of R, G, B)   --> Pins 1, 2, 3 on DB15 socket
 D3 : Horizontal Sync (68 ohms in series) --> Pin 13 on DB15 socket
 D10 : Vertical Sync (68 ohms in series) --> Pin 14 on DB15 socket

 Gnd : --> Pins 5, 6, 7, 8, 10 on DB15 socket

 PERMISSION TO DISTRIBUTE

 Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 and associated documentation files (the "Software"), to deal in the Software without restriction,
 including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.


 LIMITATION OF LIABILITY

 The software is provided "as is", without warranty of any kind, express or implied,
 including but not limited to the warranties of merchantability, fitness for a particular
 purpose and noninfringement. In no event shall the authors or copyright holders be liable
 for any claim, damages or other liability, whether in an action of contract,
 tort or otherwise, arising from, out of or in connection with the software
 or the use or other dealings in the software.

*/

#include <TimerHelpers.h>
#include <avr/pgmspace.h>
#include "screenFont.h"
#include <avr/sleep.h>
#include <Wire.h>

#define BETA_ARDUINO ARDUINO < 100

const byte pixelPin = 1;     // <------- Pixel data
const byte hSyncPin = 3;     // <------- HSYNC
const byte MSPIM_SCK = 4;    // <-- we aren't using it directly
const byte vSyncPin = 10;    // <------- VSYNC

const int horizontalBytes = 20;  // 160 pixels wide
const int verticalPixels = 480;  // 480 pixels high

const byte i2cAddress = 42;

// Timer 1 - Vertical sync

// output    OC1B   pin 16  (D10) <------- VSYNC

//   Period: 16.64 ms (60 Hz)
//      1/60 * 1e6 = 16666.66 µs
//   Pulse for 64 µs  (2 x HSync width of 32 µs)
//    Sync pulse: 2 lines
//    Back porch: 33 lines
//    Active video: 480 lines
//    Front porch: 10 lines
//       Total: 525 lines

// Timer 2 - Horizontal sync

// output    OC2B   pin 5  (D3)   <------- HSYNC

//   Period: 32 µs (31.25 kHz)
//      (1/60) / 525 * 1e6 = 31.74 µs
//   Pulse for 4 µs (96 times 39.68 ns)
//    Sync pulse: 96 pixels
//    Back porch: 48 pixels
//    Active video: 640 pixels
//    Front porch: 16 pixels
//       Total: 800 pixels

// Pixel time =  ((1/60) / 525 * 1e9) / 800 = 39.68  ns
//  frequency =  1 / (((1/60) / 525 * 1e6) / 800) = 25.2 MHz

// However in practice, it is the SPI speed, namely a period of 125 ns
//     (that is 2 x system clock speed)
//   giving an 8 MHz pixel frequency. Thus the characters are about 3 times too wide.
// Thus we fit 160 of "our" pixels on the screen in what usually takes 3 x 160 = 480

const byte screenFontHeight = 8;
const byte screenFontWidth = 8;

const int verticalLines = verticalPixels / screenFontHeight / 2;  // double-height characters
const int horizontalPixels = horizontalBytes * screenFontWidth;

const byte verticalBackPorchLines = 35;  // includes sync pulse?
const int verticalFrontPorchLines = 525 - verticalBackPorchLines;

volatile int vLine;
volatile int messageLine;
volatile byte backPorchLinesToGo;

enum SEND_COMMANDS { CLRSCR = 1, CLREOL, GOTOXY, ESC = 27 };
enum STATES { NORMAL, GOT_ESCAPE, GOT_GOTOXY, GOT_X };

char message [verticalLines]  [horizontalBytes];
byte column, line;
STATES state = NORMAL;
byte x, y;  // for gotoxy


// ISR: Vsync pulse
ISR (TIMER1_OVF_vect)
  {
  vLine = 0;
  messageLine = 0;
  backPorchLinesToGo = verticalBackPorchLines;
  } // end of TIMER1_OVF_vect

// ISR: Hsync pulse ... this interrupt merely wakes us up
ISR (TIMER2_OVF_vect)
  {
  } // end of TIMER2_OVF_vect

// called by interrupt service routine when incoming data arrives

/*
Expected formats are:
   * ordinary text:           gets displayed
   * carriage-return (0x0D):  returns cursor to start of current line
   * newline (0x0A):          drops down a line and also goes to the start of the line
   * clear screen (0x0C):     clear screen, return cursor to 1,1
   * ESC (0x1B) followed by:
      * 1 : clear screen, return cursor to 1,1
      * 2 : clear to end of current line
      * 3 : go to x,y ... next two bytes are X and then Y: one-relative

  All writing wraps, eg. text wraps at end of line, then end of screen back to line 1, column 1.
  A gotoxy out of range is ignored.
*/

void receiveEvent (int howMany)
 {
  while (Wire.available () > 0)
  {
    byte c;
#if BETA_ARDUINO
    c = Wire.receive ();
#else
    c = Wire.read ();
#endif

    // first check state ... see if we are expecting a command or an x/y position
    switch (state)
      {
      // normal is, well, normal unless we get an ESC character
      case NORMAL:
          switch (c)
            {
            case ESC:
              state = GOT_ESCAPE;
              break;

            // otherwise just display the character
            default:
               message [line] [column] = c;
               if (++column >= horizontalBytes)
                 {
                 column = 0;
                 line++;
                 } // end wrapped line

              if (line < verticalLines)
                  break;
            // if wrapped past end of buffer, fall through to do a newline which will scroll up

            // newline starts a new line, and drops down to do a carriage-return as well
            case '\n':
              // end end? scroll
              if (++line >= verticalLines)
                {
                // move line 2 to line 1 and so on ...
                memmove (& message [0] [0], & message [1] [0], sizeof message - horizontalBytes);
                // clear last line
                memset (&message [verticalLines - 1] [0], ' ', horizontalBytes);
                // put cursor on last line
                line = verticalLines - 1;    // back to last line
                }
            // fall through ...

            // carriage-return returns to start of line
            case '\r':
              column = 0;
              break;

            // clear screen
            case '\f':
              memset (message, ' ', sizeof message);
              line = column = 0;
              break;

             }  // end of switch on incoming character

          break;  // end of NORMAL

        // got ESC previously
        case GOT_ESCAPE:
          switch (c)
            {
            // clear screen ... just do it
            case CLRSCR:
              memset (message, ' ', sizeof message);
              line = column = 0;
              state = NORMAL;
              break;

            // clear to end of line
            case CLREOL:
              memset (&message [line] [column], ' ', horizontalBytes - column);
              state = NORMAL;
              break;

            // gotoxy expects two more bytes (x and y)
            case GOTOXY:
              state = GOT_GOTOXY;
              break;

            // unexpected ... not recognized command
            default:
              state = NORMAL;
              break;
            } // end of switch on command type
          break;  // end of GOT_ESCAPE

        // we got x, now we want y
        case GOT_GOTOXY:
          x = c - 1;  // make zero-relative
          state = GOT_X;
          break;

        // we now have x and y, we can move the cursor
        case GOT_X:
          y = c - 1;  // make zero-relative

          // if possible that is
          if (x < horizontalBytes && y < verticalLines)
            {
            column = x;
            line = y;
            }
          state = NORMAL;
          break;

        // unexpected ... not recognized state
        default:
          state = NORMAL;
          break;
      } // end of switch on state

  }  // end of while available
}  // end of receiveEvent

void setup()
  {

  // initial message ... change to suit
  for (int i = 0; i < verticalLines; i++)
    sprintf (message [i], "Line %03i - hello!", i);

  // disable Timer 0
  TIMSK0 = 0;  // no interrupts on Timer 0
  OCR0A = 0;   // and turn it off
  OCR0B = 0;

  // Timer 1 - vertical sync pulses
  pinMode (vSyncPin, OUTPUT);
  Timer1::setMode (15, Timer1::PRESCALE_1024, Timer1::CLEAR_B_ON_COMPARE);
  OCR1A = 259;  // 16666 / 64 µs = 260 (less one)
  OCR1B = 0;    // 64 / 64 µs = 1 (less one)
  TIFR1 = bit (TOV1);   // clear overflow flag
  TIMSK1 = bit (TOIE1);  // interrupt on overflow on timer 1

  // Timer 2 - horizontal sync pulses
  pinMode (hSyncPin, OUTPUT);
  Timer2::setMode (7, Timer2::PRESCALE_8, Timer2::CLEAR_B_ON_COMPARE);
  OCR2A = 63;   // 32 / 0.5 µs = 64 (less one)
  OCR2B = 7;    // 4 / 0.5 µs = 8 (less one)
  TIFR2 = bit (TOV2);   // clear overflow flag
  TIMSK2 = bit (TOIE2);  // interrupt on overflow on timer 2

  // Set up USART in SPI mode (MSPIM)

  // baud rate must be zero before enabling the transmitter
  UBRR0 = 0;  // USART Baud Rate Register
  pinMode (MSPIM_SCK, OUTPUT);   // set XCK pin as output to enable master mode
  UCSR0B = 0;
  UCSR0C = bit (UMSEL00) | bit (UMSEL01) | bit (UCPHA0) | bit (UCPOL0);  // Master SPI mode

  // prepare to sleep between horizontal sync pulses
  set_sleep_mode (SLEEP_MODE_IDLE);

  // for incoming data to display from I2C
  Wire.begin (i2cAddress);
  Wire.onReceive (receiveEvent);

}  // end of setup

// draw a single scan line
void doOneScanLine ()
  {

  // after vsync we do the back porch
  if (backPorchLinesToGo)
    {
    backPorchLinesToGo--;
    return;
    }  // end still doing back porch

  // if all lines done, do the front porch
  if (vLine >= verticalPixels)
    return;

  // pre-load pointer for speed
  const register byte * linePtr = &screen_font [ (vLine >> 1) & 0x07 ] [0];
  register char * messagePtr =  & (message [messageLine] [0] );

  // how many pixels to send
  register byte i = horizontalBytes;

  // turn transmitter on
  UCSR0B = bit (TXEN0);  // transmit enable (starts transmitting white)

  // blit pixel data to screen
  while (i--)
    UDR0 = pgm_read_byte (linePtr + (* messagePtr++));

  // wait till done
  while (!(UCSR0A & bit(TXC0)))
    {}

  // disable transmit
  UCSR0B = 0;   // drop back to black

  // finished this line
  vLine++;

  // every 16 pixels it is time to move to a new line in our text
  //  (because we double up the characters vertically)
  if ((vLine & 0xF) == 0)
    messageLine++;

  }  // end of doOneScanLine

void loop()
  {
  // sleep to ensure we start up in a predictable way
  sleep_mode ();
  doOneScanLine ();
 }  // end of loop

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А чё у Ника не спросите?

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Тут все дело в логике. Нужно не ардуино подключать к VGA монитору, а VGA монитор к ардуино, тогда все получится !

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

ЕвгенийП пишет:

А чё у Ника не спросите?

Пытался когда-то. Или не ответил, или не получил.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

А не рациональнее для этой затеи использовать что-нибудь вроде stm32f103?

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Зачем это вообще нужно ? Что с этим делать ?

sadman41
Онлайн
Зарегистрирован: 19.10.2016

По-любому должен получиться ТВ-тюнер для цифрового телевиденья.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

И чего только не делают "из говна и палок".

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

brokly пишет:

И чего только не делают "из говна и палок".

Ваистену. 

Дендрально-фекальное творчество не знает предела. 

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

Если не можете помочь, зачем засорять форум? Если б я мог на stm32f103, вряд ли я был на этом форуме.

Кроме поприкалываться кто-то может что дельное написать?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Дельное может написать только тот, кто это делал и кто детально в этом разберётся. Лезть туд с ушами и детально разбираться желающих не густо. Так что ждите, может появится тот кто уже делал и разобрался.

А так, самое дельное было в #1 - попробуйте ещё раз.

В принципе там не должно быть ничего сложного. Вы пискель умеете засветить? А если умеете, то какие у Вас проюлемы? Не понимаю. Или Вы не поняли объяснений Ника, а просто хотите его примером как чёрным ящиком воспользоваться? И засвечивать те пиксели, которые хотите не умеете?

b707
Offline
Зарегистрирован: 26.05.2017

seri0shka, по-моему, вы неверно команды отсылаете. Вот, например, для перевода курсора в  позицию x,y

void VGA_moveto (byte x, byte y)
  {
  Wire.beginTransmission (i2cAddress);
  Wire.write (ESC);
  Wire.write (GOTOXY);
  Wire.write (x);
  Wire.write (y);
  Wire.endTransmission ();
  }  

 

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

Собственно, если бы работали операторы, о которых писал в первом посте, то вполне можно было бы управлять как чёрным ящиком. Неважно, что внутри, скетч заливается один раз и первая плата превращается в видеокарту, по сути. А уж вторая плата управляет текстом на экране, здесь нужно разбираться в коде.

Из объяснений Ника понимаю не всё. Понимал бы всё, не спрашивал бы.

b707
Offline
Зарегистрирован: 26.05.2017

seri0shka пишет:

Собственно, если бы работали операторы, о которых писал в первом посте

sendString(), сами писали ? - в ней явные ошибки

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

b707 пишет:
sendString(), сами писали ? - в ней явные ошибки

Спасибо огромное! Ваш код работает!

Я писал sendString() только в 1й и 3й строках второго кода в первом посте. Остальное от Ника. В каких строках ошибки, ткните носом, пожалуйста.

b707
Offline
Зарегистрирован: 26.05.2017

seri0shka пишет:

В каких строках ошибки, ткните носом, пожалуйста.

Тут сложно указать строки - у вас просто нечеткое понимание.

Например, Wire.write(s) . где s - строка, требует, чтобы строка закнчивалась терминатором. А в ваших строках clearScreen или gotoXY нет терминатора, то есть строго говоря это не строки. а массив байт. Для отсылки массива байт применяется другой синтаксис

Wire.write(data, size)

или можно отсылать по одному байту, как я сделал выше.

Второе - когда вы отсылаете системные команды, они должны отсылаться одной посылкой. А вы их пробовали слать двумя-тремя sendString() - не удивительно, что они не работали.

А вообще, в Вашем коде, в комментариях Ника - очень четко все описано. Я полчаса назад вообще ничего не знал про VGA - все это я подсмотрел в коде

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

b707 пишет:
Например, Wire.write(s) . где s - строка, требует, чтобы строка закнчивалась терминатором. А в ваших строках clearScreen или gotoXY нет терминатора...
clearScreen не мои строки, а автора.

b707 пишет:
у вас просто нечеткое понимание.
С этим не спорю.

Gost2019
Offline
Зарегистрирован: 28.04.2019

seri0shka пишет:

Давно бьюсь над применением  Arduino для вывода текста на VGA монитор. Больше всего мне нравится решение отсюда: https://www.gammon.com.au/forum/?id=11608

Размер текста нельзя сделать меньше? 

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Можно, но тогда он не будет занимать весь экран.

Gost2019
Offline
Зарегистрирован: 28.04.2019

andriano пишет:

Можно, но тогда он не будет занимать весь экран.

Уменьшится разрешение экрана?

Больше 20 символов  встроке нельзя разместить?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Gost2019 пишет:

andriano пишет:

Можно, но тогда он не будет занимать весь экран.

Уменьшится разрешение экрана?

С какой стати?

Меньше текста - меньшую часть экрана он будет занимать.

Цитата:

Больше 20 символов  встроке нельзя разместить?

Раз возникают такие вопросы, возвожно, Вам будет интересно: http://arduino.ru/forum/proekty/s-stm32f103-na-televizor-polnyi-tv-signal