Свое OSD и проблемы графического video overlay

xmailer

Создаю свое OSD (atmega8+кварц16Мгц+lm1881), для себя, или точнее учусь работать с видео и микроконтроллерами. Просмотрел здесь много тем - очень классно у многих получается. По разработке есть вопрос и просто не знаю где найти ответы, перечитано много информации. Парни, подскажите в чем проблема, чувствую что что-то делаю в корне не так. Т.к. используется atmega8 - много не жду, SRAM памяти всего 1024 байт, но все таки возникают проблемы, которые на таком камне решаемы, видел примеры. Подскажите пож-та:

  1. во всех open source OSD вывод текста и графики осуществляется через SPI интерфейс, присваивание регистра SPDR и фактически дерганьем MOSI пина и switch DDR черно-белого пина. SPI интерфейс - это единственное решение? почему все используют SPI - простота или другие причины?
  2. Из нескольких статей Ричарда решил сделать по алгоритму как у него описано, только не создание видео с ноля, а видео оверлей через Lm1881. Полный контроль над выводом, все то что мне нужно, можно замоделить несколько аппаратных градаций черно-белого изображения. Но как раз по данному алгоритму получаются полные грабли. Схема, код и результат ниже. Прерывание работает некорректно, отсчет строк ползет, возможно код в прерывании долго отрабатывает. Теоретически все должно работать, практически полный абзац.

Буду очень благодарен любому направлению, совету.
Спасибо!

Схема:

#define F_CPU 16000000UL // 16 MHz

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define GRAPHICS_HEIGHT 64
#define GRAPHICS_WIDTH GRAPHICS_HEIGHT/8

#define bit_is_set(sfr,bit) (_SFR_BYTE(sfr) & _BV(bit))

uint8_t screen[GRAPHICS_WIDTH][GRAPHICS_HEIGHT];

uint16_t line = 1; // текущая строка
uint8_t box_line = 50;
uint8_t active_line = 0;
uint8_t bit;

void setPixel(uint8_t x, uint8_t y, uint8_t state) {
    uint8_t bitPos = 7-(x%8);
    uint8_t temp = screen[x/8][y];
    if (state == 0) {
        temp &= ~(1<<bitPos);
    }
    else if (state == 1) {
        temp |= (1<<bitPos);
    }
    else {
        temp ^= (1<<bitPos);
    }
    screen[x/8][y] = temp;
}

int main(void)
{
    setPixel(0,0,1);
    //setPixel(0,63,1);
    //setPixel(63,0,1);
    //setPixel(63,63,1);

    DDRD  &=~(1<<DDD2); // INT0 as input
    DDRD  &=~(1<<DDD3); // INT1 as input
    //
    DDRB  |= (1<<DDB3); // PB3 output MOSI
    PORTB &=~(1<<PB3);

    // enable INT0:1 interrupts
    MCUCR = (1<<ISC00) | (1<<ISC01);
    GICR = (1<<INT0)|(1<<INT1);    /* Enable interrupt for interrupt0            */

    sei(); // set interrupt enable

    while (1)
    {
    }
}

// VSYNC interrupt
ISR(INT1_vect)
{
    line = 1;
}

// HSYNC interrupt
ISR(INT0_vect)
{
    if((line >= 50) && (line < (50+GRAPHICS_HEIGHT))){
        _delay_us(5);

        active_line = line-50;
        //
        for (uint8_t i = 0; i < GRAPHICS_WIDTH; ++i) {
            bit = screen[i][active_line];
            //
            for (uint8_t bitPos = 7; bitPos >= 0; bitPos--) {
                if(bit_is_set(bit, bitPos)){
                    PORTB |= (1<<PB3);
                    //_delay_us(0.1);
                    }else{
                    PORTB &=~(1<<PB3);
                }
            }
        }
    }
    ++line;
}

Текущий результат banned link

xmailer

Пересмотрел много open source кода по OSD, video overlay и пробовал делать по аналогии (часть кода и алгоритм был взят из проекта osxosd) вывод графики через spi интерфейс. Но вывод в spi байта 0b10000000 приводит к отрисовке не небольшого участка - пиксела, а небольшой линии, тоже с задержками какая-то борода.

Конечная цель понять алгоритмику рисования и отрисовывать графику приблизительно как Smalltim, PitLab, Oleg70. Понятно дело что у них используются серьезные камни, но на данном этапе мне хотя бы понять как они это делают, алгоритм скорее всего один.
Может кто знает какие методы у них используются для video overlay? spi? просто порты и задержки?

#define F_CPU 16000000UL // CPU speed

#include <stdlib.h>
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdbool.h>

#include "timings.h"

// swith pin 0 port B
#define NORM DDRB &=~(1<<DDB0)
#define DIM     DDRB |=(1<<DDB0)
// wait SPI transfer
#define WAIT while(!(SPSR & (1<<SPIF)))
// graphic size
#define GRAPHICS_HEIGHT 80
#define GRAPHICS_WIDTH GRAPHICS_HEIGHT/8

// buffer
uint8_t screen[GRAPHICS_WIDTH][GRAPHICS_HEIGHT];
// line counter
static uint16_t line = 1;

void setPixel(uint8_t x, uint8_t y, uint8_t state) {
    uint8_t bitPos = 7-(x%8);
    uint8_t temp = screen[x/8][y];
    if (state == 0) {
        temp &= ~(1<<bitPos);
    }
    else if (state == 1) {
        temp |= (1<<bitPos);
    }
    else {
        temp ^= (1<<bitPos);
    }
    screen[x/8][y] = temp;
}


int main(void)
{
    // set white color to 7 bit on buffer array screen 0,0
    setPixel(0,0,1);
    //
    DDRB  = 0b00111100;    // SPI master output
    PORTB = 0b00000100;    // SS=1
    DDRD  = 0b00000000;    // INT0:1 as input
    DDRC  = 0b00000000;    // PC0 & PC1 as input
    // INT1 on falling; INT0 on any change
    MCUCR = (1<<ISC01) | (1<<ISC11); /* Set interrupt on falling edge             */
    GICR  = (1<<INT0)|(1<<INT1);    // enable INT0:1 interrupts
    // SPI enable | master mode | phase shift | invert polarity
    SPCR =(1<<SPE)|(1<<MSTR)|(1<<CPHA)|(1<<CPOL); // CPOL - ???
    // set interrupt enable
    sei();

    while (1)
    {
    }
}

// VSYNC interrupt
ISR(INT1_vect)
{
    line = 1;
}

// HSYNC interrupt
ISR(INT0_vect)
{
   // reset color
   NORM;

    if ((line >= 60) && (line < (60 + GRAPHICS_HEIGHT))){
        // skip begin pulse
        delay4700ns;
        delay5700ns;
        // dim color
        DIM;
        // speed spi 2x faster
        SPSR = (1<<SPI2X);
        // out current line from screen array
        for (uint8_t i = 0; i < GRAPHICS_WIDTH; ++i) {
            SPDR = screen[i][line-60];
            WAIT;
        }
        // reset color
        NORM;
        SPDR=0x00;
    }
    ++line;
}
emax

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

xmailer
emax:

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

провел эксперемент:

  1. Вывожу в прерывании INT0 (начало новой строки) через SPI следующим образом при размере графики 80x80, т.е. массив screen[10][80]
#define WAIT while(!(SPSR & (1<<SPIF)))
//
for (uint8_t i = 0; i < GRAPHICS_WIDTH; ++i) {
            SPDR = screen[i][currentline];
            WAIT;
        }

80x80 результат
32x32 результат

  1. Все тоже что и в первом эксперименте, не не пользуюсь макрос WAIT, т.е. просто кидаю в регистр SPDR байт и NOP-ами делаю задержки. Какие NOP-ы и где подсмотрел в cl-osd.
for (uint8_t i = 0; i < GRAPHICS_WIDTH; ++i) {
            DELAY_9_NOP();
            SPDR = screen[i][currentline];
            DELAY_9_NOP();
            DELAY_1_NOP();
}
DELAY_10_NOP        ();
DELAY_7_NOP            ();
SPDR = 0x00;

получаю допустимый 80x80 результат
---------- резюме -------------
1 эксперимент своего рода tcp (гарантия проталкивания SPDR), 2 эксперимент второй udp (без гарантии проталкивания SPDR), т.е. гарантия проталкивания SPDR приводит к “конским” задержкам.

Вопрос по теме несколько сужается: как рассчитываются данные NOP-ы? каждый NOP - это NoOPeration один такт, в моем случае 1/16Мгц=62.5us. Длина строки 64us. Таким образом перед внесением в регистр SPDR байта заставляем сделать 9 NOP-ов, т.е. 62.5*9=562.5us и столько же после - почему? Как рассчитать какие NOP-ы использовать? И где их делать - сколько до внесения в SPDR, сколько после, сколько после всей строки?

oleg70
xmailer:

SPI интерфейс - это единственное решение?

Нет не единственное, но самое выгодное, т.к. это фактически аппаратный сдвиговый регистр.
(сдвигает биты байта памяти на одну ногу по сигналу тактирования, а это как раз то что нужно…)

xmailer:

алгоритм скорее всего один

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

xmailer:

Конечная цель понять алгоритмику рисования

Для меги8 вся алгоритмика сводится к выводу по строчному синхроимпульсу (int0 у Вас) расчетного количества байт в строку,
похоже у Вас уже это работает, в чём тогда вопрос (?)…
У stm32 всё одновременно и сложней и проще, там DMA есть, но куча других нюансов, требующих отдельного разговора…

xmailer

Большое спасибо за ответы!!!

oleg70:

Для меги8 вся алгоритмика сводится к выводу по строчному синхроимпульсу (int0 у Вас) расчетного количества байт в строку, похоже у Вас уже это работает, в чём тогда вопрос (?)…

Вы не могли бы пояснить по NOP-ам в конце моего поста не совсем понял как правильно их считать (то что у меня работает я подсмотрел в cl-osd) и как грамотно использовать, чтобы четко попадать во время.

oleg70:

У stm32 всё одновременно и сложней и проще, там DMA есть, но куча других нюансов, требующих отдельного разговора…

Скажите в stm32 Вы рисуете задержками или каким-то образом используете SPI? Здесь на форуме видел схему Вашего OSD и изучал конфигурацию stm-а, конечно большого опята нет, у Вас там TIM1,TIM2,TIM9 и SPI1 используется по схеме и судя по точным задержкам таймеров они Вам нужны для отрисовки строк. Интересуюсь чисто алгоритмически.

SPI при таком использовании очень удобен, закинул байт и забыл, но в как быть с обводкой черным (или градацией черного) белого цвета. Меняя направление порта черного цвета (в моем случае DDB0) можно сделать все нулевые биты данным цветом, т.е. получается черный фон, вопрос как сделать именно окаймление черным. На данном форуме читал, что для этого на stm-ах используют SPI1,SPI2 и получается выводят два слоя черный и белый?

На STM32 Вы контролируете вывод каждого пиксела?

oleg70
xmailer:

как правильно их считать

Считать - дело неблагодарное, проще подогнать экспериментально, смысл простой:
после строчного прерывания необходимо сделать некоторую задержку NOPами до вывода первого байта (левый отступ) и затем в цикле “кидать” в SPDR необходимое количество байт (в зависимости от горизонтального разрешения)… Ну и далее аналогично следующую строку и т.д. до окончания кадра. всё…

xmailer:

Вы рисуете задержками или каким-то образом используете SPI?

Тот же SPI, но только через DMA, и с внешним тактированием от таймера.

xmailer:

но в как быть с обводкой черным (или градацией черного) белого цвета.

У меня работает синхронно два SPI, т.е. один выводит белый цвет другой чёрный, естественно оперативки надо в два раза больше, ну и символы соответственно хранятся в двухцветном виде…

xmailer:

судя по точным задержкам таймеров они Вам нужны для отрисовки строк. Интересуюсь чисто алгоритмически.

Здесь задача такая: максимально избавиться от программного переключения между строками и между полукадрами, (при разрешении выше чем 320х240 уже необходимо контролировать и полукадры у видеосигнала) поэтому куча таймеров и т.д., всё фактически выводится аппаратно… Был у меня вариант (рабочий) где вообще вся отрисовка картинки была аппаратной, т.е. ядро контроллера вообще не участвовало в работе, но там маленькая обвязочка из одной микрухи всёж понадобилась…
Естественно всё это возможно только на “продвинутых” камнях с мощной аппаратной периферией, типа stm…

xmailer:

вопрос как сделать именно окаймление черным.

на Меге8 реально никак, можно только сделать белый символ на черном фоне (целое знакоместо)

xmailer

Oleg70, спасибо!!!
Получил ответы на все вопросы, будем искать. По результатам, обязательно отчитаюсь здесь.

oleg70
xmailer:

или градацией черного

Да кстати, градации серого делаются (даже на Меге) по другому принципу, уже не через SPI, а параллельным выводом байта в порт ввода/вывода, тогда отдельные ноги порта (биты) при помощи внешнего резистивного делителя смешиваются, и получается нечто вроде ЦАП и полученное напряжение уже рисует пиксель… Сам я такой изврат не пробовал, но пробовал использовать аппаратный ЦАП stm, получается круто, только вот за пикселями “тянется хвост” из-за высокоомного выхода самого порта, короче побаловался и забил…

RW9UAO

посмотрите как у hydra или как его там сделано на stm32f4, кроме дма spi еще пара резисторов на подтяжку уровней белого и черного.
видел в cleanflight вроде.