Официальный сайт компании Arduino по адресу arduino.cc
Подключение Arduino к VGA монитору
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
Давно бьюсь над применением 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
Может кто подскажет, в каком направлении копать. Пытался разрешить эту проблему ещё года три назад, сейчас вроде и знаю намного больше, но не получается.
А чё у Ника не спросите?
Тут все дело в логике. Нужно не ардуино подключать к VGA монитору, а VGA монитор к ардуино, тогда все получится !
А чё у Ника не спросите?
А не рациональнее для этой затеи использовать что-нибудь вроде stm32f103?
Зачем это вообще нужно ? Что с этим делать ?
По-любому должен получиться ТВ-тюнер для цифрового телевиденья.
И чего только не делают "из говна и палок".
И чего только не делают "из говна и палок".
Ваистену.
Дендрально-фекальное творчество не знает предела.
Если не можете помочь, зачем засорять форум? Если б я мог на stm32f103, вряд ли я был на этом форуме.
Кроме поприкалываться кто-то может что дельное написать?
Дельное может написать только тот, кто это делал и кто детально в этом разберётся. Лезть туд с ушами и детально разбираться желающих не густо. Так что ждите, может появится тот кто уже делал и разобрался.
А так, самое дельное было в #1 - попробуйте ещё раз.
В принципе там не должно быть ничего сложного. Вы пискель умеете засветить? А если умеете, то какие у Вас проюлемы? Не понимаю. Или Вы не поняли объяснений Ника, а просто хотите его примером как чёрным ящиком воспользоваться? И засвечивать те пиксели, которые хотите не умеете?
seri0shka, по-моему, вы неверно команды отсылаете. Вот, например, для перевода курсора в позицию x,y
Собственно, если бы работали операторы, о которых писал в первом посте, то вполне можно было бы управлять как чёрным ящиком. Неважно, что внутри, скетч заливается один раз и первая плата превращается в видеокарту, по сути. А уж вторая плата управляет текстом на экране, здесь нужно разбираться в коде.
Из объяснений Ника понимаю не всё. Понимал бы всё, не спрашивал бы.
Собственно, если бы работали операторы, о которых писал в первом посте
sendString(), сами писали ? - в ней явные ошибки
Спасибо огромное! Ваш код работает!
Я писал sendString() только в 1й и 3й строках второго кода в первом посте. Остальное от Ника. В каких строках ошибки, ткните носом, пожалуйста.
В каких строках ошибки, ткните носом, пожалуйста.
Тут сложно указать строки - у вас просто нечеткое понимание.
Например, Wire.write(s) . где s - строка, требует, чтобы строка закнчивалась терминатором. А в ваших строках clearScreen или gotoXY нет терминатора, то есть строго говоря это не строки. а массив байт. Для отсылки массива байт применяется другой синтаксис
Wire.write(data, size)
или можно отсылать по одному байту, как я сделал выше.
Второе - когда вы отсылаете системные команды, они должны отсылаться одной посылкой. А вы их пробовали слать двумя-тремя sendString() - не удивительно, что они не работали.
А вообще, в Вашем коде, в комментариях Ника - очень четко все описано. Я полчаса назад вообще ничего не знал про VGA - все это я подсмотрел в коде
Давно бьюсь над применением Arduino для вывода текста на VGA монитор. Больше всего мне нравится решение отсюда: https://www.gammon.com.au/forum/?id=11608
Размер текста нельзя сделать меньше?
Можно, но тогда он не будет занимать весь экран.
Можно, но тогда он не будет занимать весь экран.
Уменьшится разрешение экрана?
Больше 20 символов встроке нельзя разместить?
Можно, но тогда он не будет занимать весь экран.
Уменьшится разрешение экрана?
С какой стати?
Меньше текста - меньшую часть экрана он будет занимать.
Больше 20 символов встроке нельзя разместить?
Раз возникают такие вопросы, возвожно, Вам будет интересно: http://arduino.ru/forum/proekty/s-stm32f103-na-televizor-polnyi-tv-signal