Микроконтроллеры


Создание видеосигнала программным способом.


Итак, эта часть о том, как программным способом получить видеосигнал. Для того, чтобы понять как это делается, необходимо чтобы Вы хорошо усвоили ранее изложенный материал. Если Вы хорошо понимаете, что представляет собой видеосигнал, его легко будит получить программным путем имея процессор с неограниченными возможностями. Проблема в том, что требования к процессору достаточно высоки. Но даже если Вы не имеете мощного процессора кое-что все же можно сделать. Поговорим сначала о написании кода.
В моих примерах кода в этой части используются два следующих макроса:
DNOP –двойная команда NOP –макрос, который обеспечивает паузу в течении двух циклов. dnop MACRO LOCAL label label goto label+1 ENDM DELAY – макрос паузы, длительность которой в три раза больше числа, записанного в регистр W. Delay MACRO LOCAL label movwf delaycnt label decfsz delaycnt goto label ENDM
Схемное решение.
Для генерации видеосигнала необходима некая схема, способная создавать сигналы с амплитудой напряжения от 0 до 1В. Чтобы создать изображение Вам необходимо как минимум три уровня сигнала. Телевизор должен получать уровень черного и уровень синхросигнала для того, чтобы синхронизировать изображение. Если Вы хотите большего, чем просто черный экран, Вам понадобится некоторый уровень серого или белого. Для получения трех необходимых уровней аналогового сигнала требуется два бита данных цифрового сигнала. Стандартное входное сопротивление видеовхода телевизора – 75 Ом. Используя два резистора и два выхода порта микроконтроллера можно создать требуемые уровни напряжения.
При соединении обоих выходов D0 и D1 с землей, напряжение на видеовходе
телевизора будит равно 0, что соответствует синхроуровню.

Выход D1 соединен с землей, а выход D0 - с +5В. В этом случае резистор 450 Ом включен параллельно 75 Ом-ному сопротивлению видеовхода телевизора, а резистор 900 Ом подключен к этой цепи последовательно. Этот делитель напряжения позволяет получить на видеовходе уровень 0,33В, что очень близко к уровню черного. (Истинный уровень черного 0,3В.)




Выход D0 соединен с землей, а выход D1 - с +5В. В этом случае резистор 900 Ом включен параллельно 75 Ом-ному сопротивлению видеовхода телевизора, а резистор 450 Ом подключен к этой цепи последовательно. Этот делитель напряжения позволяет получить на видеовходе уровень 0,67В. Это уровень серого.

Оба выхода D0 и D1 соединены с +5В. В этом случае резисторы 900 Ом и 450 Ом включены параллельно, а 75 Ом-ное сопротивление видеовхода телевизора, подключено к этой цепи последовательно. Этот делитель напряжения позволяет получить на видеовходе уровень 1В. Это уровень белого.

Эта схема позволяет получить четыре уровня напряжения. На рисунках представлены эквивалентные схемы четырех различных уровней напряжения и показано, каким образом они получаются. Номинал резисторов не критичен. Вы можете использовать большие, стандартные значения: 470 Ом и 1 кОм вместо 450 Ом и 900 Ом. Схема будит работать, просто немного изменится яркость изображения.
Итак, мы можем создать синхроуровень, уровни черного, серого и белого. Этого достаточно чтобы создать простое изображение, как в играх Pong и Tetris. Возможно создание и большего числа уровней яркости если использовать большее число бит выходного порта. Но в этом случае Вы не сможете с помощью них выполнять другие функции.

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


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

Строка, формирующая вертикальные полосы.
Первый тест, который я сделал, когда начал экспериментировать с программной генерацией видео – это получение вертикальных полос на экране. Надо создать строку, в которой информация о цвете изменяется в такой последовательности: серый – черный – белый – черный – серый. Повторяя эту строку непрерывно, получим на черном фоне экрана изображение трех полос: две серые по краям и одна белая по центру экрана. Сигнал включает горизонтальный синхроимпульс, последующую задержку и информацию о яркости. Телевизионное изображение будит устойчиво по горизонтали, а по вертикали может плавать или дрожать, так как вертикальные синхроимпульсы отсутствует. На рисунке показано как это примерно выглядит.
Осциллограмма сигнала, генерируемого кодом,
который приведен ниже. Низкие импульсы -
это серые полосы, высокий импульс в середине
- белая полоса.
Так выглядит видеосигнал на экране
телевизора. Две серых и одна белая полосы.
main: movlw COLOR_SYNC ;get sync level (1) ;**** 4us sync **** movwf VIDEO_PORT ;set port value (1) movlw 3 ;setup delay time (1) DELAY ;delay for 3us (9) movlw COLOR_BLACK ;get black color (1) ;**** 8us delay **** ; movwf VIDEO_PORT ;set port value (1) movlw 7 ;setup delay time (1) DELAY ;delay for 7us (21) movlw COLOR_GRAY ;get gray color (1) ;*** 52us image data *** movwf VIDEO_PORT ;set port value (1) movlw 3 ;setup delay time (1) DELAY ;delay for 3us (9) movlw COLOR_BLACK ;get black color (1) movwf VIDEO_PORT ;set port value (1) movlw 19 ;setup delay time (1) DELAY ;delay for 19us (57) movlw COLOR_WHITE ;get white color (1) movwf VIDEO_PORT ;set port value (1) movlw 3 ;setup delay time (1) DELAY ;delay for 3us (9) movlw COLOR_BLACK ;get black color (1) movwf VIDEO_PORT ;set port value (1) movlw 19 ;setup delay time (1) DELAY ;delay for 19us (57) movlw COLOR_GRAY ;get gray color (1) movwf VIDEO_PORT ;set port value (1) movlw 2 ;setup delay time (1) DELAY ;delay for 2us (6) DNOP ;delay for two clocks goto main ;once again (2) Этот код позволяет генерировать на черном фоне две серых и одну белую полосы. ( Внимание! Этот код написан мной по памяти и не тестировался, поэтому нет гарантии что он рабочий. )
Как Вы, наверное, заметили, этот фрагмент программы выполняется за 192 машинных цикла и создает строку длительностью 64 мкс.


Это время имеет очень большое значение.

Проблема с низким разрешением.
PIC16F84 при частоте 12 МГц выполняет 3 миллиона инструкций в секунду ( для выполнения одной инструкции ( команды ) необходимо 4 такта генератора ). За 64 мкс ( длительность строки ) выполняется 192 инструкции, а в течении 52 мкс ( видимая часть строки ) – только 156 инструкций. Если в эти 52 мкс значение выходного порта будит изменяться каждой инструкцией, Вы получите разрешение 156 пикселей по оси X. Это очень мало. К тому же даже все эти 156 пикселей не могут использованы та как Вы хотите. Если значение пикселя должно меняться с каждым новым циклом, Вы не сможете за один цикл просчитать значение пикселя и вывести его на экран.

Достижение высокого разрешения с помощью команды сдвига.
Если вы хотите показать 8 пикселей черного и белого, сохраненных в одном байте памяти, то это можно сделать так:

movlw 8 ;number of pixels is 8 (1) movwf counter ;set counter to number of pixels (1) shiftloop: movlw COLOR_BLACK ;set default color to black (1) rrf thedata,f ;rotate data right, make put bit in carry (1) skpnc ;check if carry, if not pixel remains black (1 or 2) movlw COLOR_WITE ;carry was set, set color to white (1) movwf VIDEO_PORT ;set color to DA (1) decfsz counter ;decrease counter, check for zero (1 or 2) goto shiftloop ;if more pixels, keep looping (2) Этот код выводит биты одного байта в видеопорт. Таким образом на экран можно вывести различые изображения.
В этом примере для вывода одного бита требуется 8 машинных циклов. При этом разрешение составляет только 19 пикселей, то есть практически применять этот способ бессмысленно. Однако есть хорошее решение этой проблемы. Можно использовать один порт как сдвиговый регистр, сдвигающий на выход по одному биту в каждом цикле. Но за все надо платить. Вы должны принести в жертву возможность использовать порт для других целей. К тому же этот вариант работает только в черно-белом режиме ( серого цвета нет ). Это может выглядеть так:
Соедините резистор 450 Ом с битом 0 PORTB, а резистор 900 Ом с одним из битов PORTA.


Для использования PORTB как дополнительного все его биты должны быть настроены как выходы. Бит PORTA устанавливается в 1, в PORTB записывается байт и сдвигается на выход. Генерируемые черно-белые уровни соответствуют байту, например как здесь:

movfw thedata ;set up the byte to be shifted out movwf PORTB,f ;now the first bit becomes visible (1 clock) rrf PORTB,f ;second bit is shifted out (1 clock) rrf PORTB,f ;third bit is shifted out (1 clock) rrf PORTB,f ;fourth bit is shifted out (1 clock) rrf PORTB,f ;fifth bit is shifted out (1 clock) rrf PORTB,f ;sixth bit is shifted out (1 clock) rrf PORTB,f ;sevenths bit is shifted out (1 clock) rrf PORTB,f ;eighth bit is shifted out (1 clock) Этот код выводит биты одного байта в видеопорт, как в предыдущем примере, но делает это на много быстрее. При этом программа использует все восемь бит выходного порта.
Этот пример позволяет каждый цикл изменять видеосигнал, что дает разрешение 156 пикселей, если не нужно установочного времени. В действительности обычно всегда есть значительное установочное время до и после вывода 8 бит графики. В течении этого времени выводятся черные или белые пиксели. В игре Pong установочное время можно видеть при отображении на экране текста. Там используется этот метод, поэтому между буквами на экране такое большое пространство. Я думаю реально получить 10 символов в строке, каждый шириной 8 пикселей. Таким образом в среднем для вывода каждого пикселя требуется 1,5 цикла ( включая установочное время ). В Pong биты, которые сдвигаются на выход, берутся из ОЗУ. Для одной строки требуется 8*8бит=8Байт. Для каждой линии эти 8 Байт должны быть получены путем чтения из ППЗУ, где они хранятся. В ходе выполнения программы в нужный момент требуемые данные строки изображения переписываются из ППЗУ в ОЗУ. Для того чтобы сделать все это для 8 символов требуется очень много времени. На это уходит целая строка. Вот почему текст отображается через строку. В тот момент, когда отображается черная строка, микропроцессор производит вычисления для следующей строки.



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

Итак, этот метод позволяет получить изображение с достаточно высоким разрешением, но требует целиком PORTB для сдвиговой операции. Сначала может показаться, что невозможно использовать PORTB для других целей, но это не так. Он легко может быть использован как выходной, если один из выходов PORTA использовать для запрета другим устройствам соединяться с PORTB, когда он используется как сдвиговый регистр. Так же возможно использовать PORTB как вход в момент, когда не выполняется операция сдвига. В моих играх мне требовалось много входов для подключения джойстика. Соединять джойстик напрямую рискованно, так как можно сжечь входной буфер порта. ( Джойстик – это группа выключателей, которые замыкаются на землю при нажатии. ) Я решил эту проблему путем использования 100кОм подтягивающих резисторов 1кОм защитных резисторов. Это позволяет напряжению опускаться до 0, когда джойстик нажат и PORTB используется как вход, но защищает от короткого замыкания при работе PORTB в качестве сдвигового регистра.

DIGITA.ru: огромный выбор телевизоров (обычные, проекционные, 100MHz, плазменные).

Вертикальная синхронизация.
Для получения устойчивого изображения необходимо добавить в видеосигнал импульсы вертикальной синхронизации. Это может быть достигнуто с помощью представленного ниже кода.
shortsync: ; label "Shortsync", entry for short sync generator movwf counter1 ; set counter1 to number of shortsyncs shortsync_l0: ; label "Shortsync_l0", short sync count loop entry bcf porta,0 ; set level to synclevel (bit 1) bcf portb,0 ; set level to synclevel (bit 0) dnop ; movlw 0x1D ; set counter2 to "30us" movwf counter2 ; nop ; bsf porta,0 ; set level to black shortsync_l1: ; label "Shortsync_l1", short sync delay loop decfsz counter2 ; do delay counting goto shortsync_l1 ; loop if not finished with delay decfsz counter1 ; count number of shortsyncs goto shortsync_l0 ; if more shortsyncs, keep looping retlw 5 ; return and set w to number of longsyncs (5 longsyncs) vertsync: ; label "vertsync", this is the label to call from main movlw 5 ; set number of shortsyncs to 5 btfss videostuff,0 ; check if first field movlw 6 ; yes, change number of short syncs to 6 call shortsync ; do those short syncs incf videostuff ; update field for the new frame longsync: ; label "longsync", just to make it easy to understand movwf counter1 ; set synccounter to number of longsyncs longsync_l0 ; label "longsync_l0", long sync count loop entr movlw 0x1D ; set counter to 30us movwf counter2 ; set level to sync (bit 1) bcf porta,0 ; set level to sync (bit 0) bcf portb,0 ; label "Shortsync_l1", long sync delay loop longsync_l1 ; do delay counting decfsz counter2 ; loop if not finished with delay goto longsync_l1 ; nop ; set level to black bsf portb,0 ; nop ; count number of shortsyncs decfsz counter1 ; if more shortsyncs, keep looping goto longsync_l0 ; set number of shortsyncs to 5 movlw 5 ; check if second field btfss videostuff,0 ; yes, do 4 shortsyncs instead movlw 4 ; do those short syncs goto shortsync
Этот код генерирует вертикальные синхроимпульсы, как было описано выше. Код состоит из участков, которые выполняются по нескольку раз. Сначала вызывается подпрограмма коротких импульсов, затем подпрограмма длинных импульсов, потом в последней строке кода снова вызывается подпрограмма коротких импульсов. Программа кажется запутанной, но это позволяет сократить ее длинну и сэкономить память программ.
<


br>Моя игра Pong.
Код игры Pong кажется запутанным и непонятным. Это моя первая программа, связанная с видео. Я многому научился после того, как ее написал и думаю, что возможно, ее можно было бы сделать более эффективной.
Для того, чтобы сохранить мяч в границах экрана, в программе приходится использовать много циклов IF. Первые линии просто белые. Это верхние линии, во время которых выполняются действия, определяющие логику игры. Для остальных линий определены по два значения: одно с мячом, второе – без мяча. В лини без мяча сначала показывается левый игрок, если он есть, потом черный участок, а затем, если необходимо , правый игрок. В линии с мячом выполняется то же самое, но на черном фоне в определенном месте должен быть показан мяч. До и после мяча присутствуют две паузы, во время которых вычисляется положение мяча в строке. Каждая пауза длится три цикла, поэтому мяч перемещается довольно большими скачками.
В низу экрана отображается счет. Для показа счета используется сдвиговый метод, описанный ранее. В большинстве строк проверяется, включен ли звук. Если да, на выход аудио подается сигнал.
Информация о тексте меню хранится в памяти данных в виде строк длинной 8 символов. Текст выводится на экран методом сдвига. Это происходит через строку так как в течении первой строки производится извлечение информации из памяти данных и сохранение ее в буфере, а уже во время второй строки эти данные передаются на выход видео.
.
Игра Pong в действии. Экран меню.
Моя игра Tetris.
Для того, чтобы хорошо понять логику работы программы, прежде чем писать ее на ассемблере для PIC, я сначала написал ее на Borland C для DOS для ПК.
Блоки хранятся в памяти в сжатом виде и в нужный момент распаковываются в буфер, где хранятся в соответствии с координатами места на экране. В программе есть три режима управления блоками в буфере экрана: установка, очистка и тест. Установка и очистка это режимы, в которых блоки могут добавляться и удаляться в/из в определенную часть экрана. В режиме тест проверяется попал ли блок в нужное положение.Такой подход делает программу достаточно структурированной и легкой ( по сравнению с Pong ) для просмотра. Режим вывода на экран похож на вывод текста, просто вместо букв на видеовыход сдвигаются данные о блоках и черных участках между ними.
В игре присутствует звуковое сопровождение, которое изменяется в зависимости от момента игры. Счет отображается сдвиговым методом, здесь все как обычно. Меню в игре нет, так как под него не хватило памяти.

Игра Tetris в действии.

Содержание раздела