initial
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018, Alexey Dynda
|
||||
|
||||
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.
|
||||
|
||||
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 "intf/vga/vga.h"
|
||||
// Never include vga128x64_isr.h here!!!
|
||||
#include "intf/ssd1306_interface.h"
|
||||
#include "lcd/lcd_common.h"
|
||||
#include "lcd/vga_commands.h"
|
||||
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE) && defined(__AVR_ATmega328P__)
|
||||
|
||||
extern uint16_t ssd1306_color;
|
||||
|
||||
/* This buffer fits 128x64 pixels
|
||||
Each 8 pixels are packed to 3 bytes:
|
||||
|
||||
BYTE1: B7 R2 G2 B2 B8 R1 G1 B1
|
||||
BYTE2: G7 R4 G4 B4 G8 R3 G3 B3
|
||||
BYTE3: R7 R6 G6 B6 R8 R5 G5 B5
|
||||
|
||||
Yeah, a little bit complicated, but this allows to quickly unpack structure
|
||||
*/
|
||||
|
||||
// Set to ssd1306 compatible mode by default
|
||||
static uint8_t s_mode = 0x01;
|
||||
static uint8_t s_vga_command = 0xFF;
|
||||
static uint8_t s_vga_arg = 0;
|
||||
static uint8_t s_column = 0;
|
||||
static uint8_t s_column_end = 0;
|
||||
static uint8_t s_cursor_x = 0;
|
||||
static uint8_t s_cursor_y = 0;
|
||||
volatile uint8_t s_vga_frames;
|
||||
|
||||
static void vga_controller_init(void)
|
||||
{
|
||||
s_vga_command = 0xFF;
|
||||
}
|
||||
|
||||
static void vga_controller_stop(void)
|
||||
{
|
||||
s_vga_command = 0xFF;
|
||||
}
|
||||
|
||||
static void vga_controller_close(void)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Function sends 8 vertical pixels to buffer
|
||||
*/
|
||||
static inline void vga_controller_put_pixels(uint8_t x, uint8_t y, uint8_t pixels)
|
||||
{
|
||||
uint16_t addr = (x >> 3) + (uint16_t)(y * 16);
|
||||
uint8_t offset = x & 0x07;
|
||||
uint8_t mask = 1 << offset;
|
||||
if (addr >= 16*64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (uint8_t i=8; i>0; i--)
|
||||
{
|
||||
if (pixels & 0x01) __vga_buffer[addr] |= mask;
|
||||
else __vga_buffer[addr] &= ~mask;
|
||||
addr += 16;
|
||||
pixels >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void vga_controller_send_byte(uint8_t data)
|
||||
{
|
||||
if (s_vga_command == 0xFF)
|
||||
{
|
||||
s_vga_command = data;
|
||||
return;
|
||||
}
|
||||
if (s_vga_command == 0x40)
|
||||
{
|
||||
vga_controller_put_pixels(s_cursor_x, s_cursor_y, data);
|
||||
s_cursor_x++;
|
||||
if (s_cursor_x > s_column_end)
|
||||
{
|
||||
s_cursor_x = s_column;
|
||||
s_cursor_y += 8;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// command mode
|
||||
if (!s_vga_command)
|
||||
{
|
||||
s_vga_command = data;
|
||||
s_vga_arg = 0;
|
||||
}
|
||||
if (s_vga_command == VGA_SET_BLOCK)
|
||||
{
|
||||
// set block
|
||||
if (s_vga_arg == 1)
|
||||
{
|
||||
s_column = data >= ssd1306_lcd.width ? ssd1306_lcd.width - 1 : data ;
|
||||
s_cursor_x = s_column;
|
||||
}
|
||||
if (s_vga_arg == 2) { s_column_end = data >= ssd1306_lcd.width ? ssd1306_lcd.width - 1 : data; }
|
||||
if (s_vga_arg == 3) { s_cursor_y = (data << 3); }
|
||||
if (s_vga_arg == 4) { s_vga_command = 0; }
|
||||
}
|
||||
if (s_vga_command == VGA_SET_MODE)
|
||||
{
|
||||
if (s_vga_arg == 1) { s_mode = data; s_vga_command = 0; }
|
||||
}
|
||||
s_vga_arg++;
|
||||
}
|
||||
|
||||
static void vga_controller_send_bytes(const uint8_t *buffer, uint16_t len)
|
||||
{
|
||||
while (len--)
|
||||
{
|
||||
ssd1306_intf.send(*buffer);
|
||||
buffer++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void init_vga_crt_driver(uint8_t enable_jitter_fix)
|
||||
{
|
||||
cli();
|
||||
if (enable_jitter_fix)
|
||||
{
|
||||
// Configure Timer 0 to calculate jitter fix
|
||||
TIMSK0=0;
|
||||
TCCR0A=0;
|
||||
TCCR0B=1;
|
||||
OCR0A=0;
|
||||
OCR0B=0;
|
||||
TCNT0=0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sorry, we still need to disable timer0 interrupts to avoid wake up in sleep mode
|
||||
TIMSK0 &= ~(1<<TOIE0);
|
||||
}
|
||||
|
||||
// Timer 1 - vertical sync pulses
|
||||
pinMode (V_SYNC_PIN, OUTPUT);
|
||||
TCCR1A=(1<<WGM10) | (1<<WGM11) | (1<<COM1B1);
|
||||
TCCR1B=(1<<WGM12) | (1<<WGM13) | (1<<CS12) | (1<<CS10); //1024 prescaler
|
||||
OCR1A = 259; // 16666 / 64 us = 260 (less one)
|
||||
OCR1B = 0; // 64 / 64 us = 1 (less one)
|
||||
TIFR1 = (1<<TOV1); // clear overflow flag
|
||||
TIMSK1 = (1<<TOIE1); // interrupt on overflow on timer 1
|
||||
|
||||
// Timer 2 - horizontal sync pulses
|
||||
pinMode (H_SYNC_PIN, OUTPUT);
|
||||
TCCR2A=(1<<WGM20) | (1<<WGM21) | (1<<COM2B1); //pin3=COM2B1
|
||||
TCCR2B=(1<<WGM22) | (1<<CS21); //8 prescaler
|
||||
OCR2A = 63; // 32 / 0.5 us = 64 (less one)
|
||||
OCR2B = 7; // 4 / 0.5 us = 8 (less one)
|
||||
// if (enable_jitter_fix)
|
||||
{
|
||||
TIFR2 = (1<<OCF2B); // on end of h-sync pulse
|
||||
TIMSK2 = (1<<OCIE2B); // on end of h-sync pulse
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// TIFR2 = (1<<TOV2); // int on start of h-sync pulse
|
||||
// TIMSK2 = (1<<TOIE2); // int on start of h-sync pulse
|
||||
// }
|
||||
|
||||
// Set up USART in SPI mode (MSPIM)
|
||||
|
||||
pinMode(14, OUTPUT);
|
||||
pinMode(15, OUTPUT);
|
||||
pinMode(16, OUTPUT);
|
||||
PORTC = 0;
|
||||
|
||||
sei();
|
||||
}
|
||||
|
||||
void ssd1306_vga_controller_128x64_init_no_output(void)
|
||||
{
|
||||
ssd1306_intf.spi = 0;
|
||||
ssd1306_intf.start = vga_controller_init;
|
||||
ssd1306_intf.stop = vga_controller_stop;
|
||||
ssd1306_intf.send = vga_controller_send_byte;
|
||||
ssd1306_intf.send_buffer = vga_controller_send_bytes;
|
||||
ssd1306_intf.close = vga_controller_close;
|
||||
}
|
||||
|
||||
void ssd1306_vga_controller_128x64_init_enable_output(void)
|
||||
{
|
||||
ssd1306_vga_controller_128x64_init_no_output();
|
||||
init_vga_crt_driver(1);
|
||||
}
|
||||
|
||||
void ssd1306_vga_controller_128x64_init_enable_output_no_jitter_fix(void)
|
||||
{
|
||||
ssd1306_vga_controller_128x64_init_no_output();
|
||||
init_vga_crt_driver(0);
|
||||
// set_sleep_mode (SLEEP_MODE_IDLE);
|
||||
}
|
||||
|
||||
void ssd1306_debug_print_vga_buffer_128x64(void (*func)(uint8_t))
|
||||
{
|
||||
for(int y = 0; y < ssd1306_lcd.height; y++)
|
||||
{
|
||||
for(int x = 0; x < ssd1306_lcd.width; x++)
|
||||
{
|
||||
uint8_t color = __vga_buffer[(x >> 3) + y * 16] & (1<< (x&0x07));
|
||||
if (color)
|
||||
{
|
||||
func('#');
|
||||
func('#');
|
||||
}
|
||||
else
|
||||
{
|
||||
func(' ');
|
||||
func(' ');
|
||||
}
|
||||
}
|
||||
func('\n');
|
||||
}
|
||||
func('\n');
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018, Alexey Dynda
|
||||
|
||||
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.
|
||||
|
||||
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 "intf/vga/vga.h"
|
||||
// Never include vga96x40_isr.h here!!!
|
||||
#include "intf/ssd1306_interface.h"
|
||||
#include "lcd/lcd_common.h"
|
||||
#include "lcd/vga_commands.h"
|
||||
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE) && defined(__AVR_ATmega328P__)
|
||||
|
||||
extern uint16_t ssd1306_color;
|
||||
|
||||
/* This buffer fits 96x40 pixels
|
||||
Each 8 pixels are packed to 3 bytes:
|
||||
|
||||
BYTE1: B7 R2 G2 B2 B8 R1 G1 B1
|
||||
BYTE2: G7 R4 G4 B4 G8 R3 G3 B3
|
||||
BYTE3: R7 R6 G6 B6 R8 R5 G5 B5
|
||||
|
||||
Yeah, a little bit complicated, but this allows to quickly unpack structure
|
||||
*/
|
||||
|
||||
// Set to ssd1306 compatible mode by default
|
||||
static uint8_t s_mode = 0x01;
|
||||
static uint8_t s_vga_command = 0xFF;
|
||||
static uint8_t s_vga_arg = 0;
|
||||
static uint8_t s_column = 0;
|
||||
static uint8_t s_column_end = 0;
|
||||
static uint8_t s_cursor_x = 0;
|
||||
static uint8_t s_cursor_y = 0;
|
||||
volatile uint8_t s_vga_frames;
|
||||
|
||||
static void vga_controller_init(void)
|
||||
{
|
||||
s_vga_command = 0xFF;
|
||||
}
|
||||
|
||||
static void vga_controller_stop(void)
|
||||
{
|
||||
s_vga_command = 0xFF;
|
||||
}
|
||||
|
||||
static void vga_controller_close(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* This buffer fits 96x40 pixels
|
||||
Each 8 pixels are packed to 3 bytes:
|
||||
|
||||
BYTE1: B7 R2 G2 B2 B8 R1 G1 B1
|
||||
BYTE2: G7 R4 G4 B4 G8 R3 G3 B3
|
||||
BYTE3: R7 R6 G6 B6 R8 R5 G5 B5
|
||||
|
||||
Yeah, a little bit complicated, but this allows to quickly unpack structure
|
||||
*/
|
||||
static inline void vga_controller_put_pixel3(uint8_t x, uint8_t y, uint8_t color)
|
||||
{
|
||||
uint16_t addr = (x >> 3)*3 + (uint16_t)y*36;
|
||||
uint8_t offset = x & 0x07;
|
||||
if (addr >= 36*40)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (offset < 6)
|
||||
{
|
||||
if (x&1)
|
||||
{
|
||||
__vga_buffer[addr + (offset>>1)] &= 0x8F;
|
||||
__vga_buffer[addr + (offset>>1)] |= (color<<4);
|
||||
}
|
||||
else
|
||||
{
|
||||
__vga_buffer[addr + (offset>>1)] &= 0xF8;
|
||||
__vga_buffer[addr + (offset>>1)] |= color;
|
||||
}
|
||||
}
|
||||
else if (offset == 6)
|
||||
{
|
||||
__vga_buffer[addr+0] &= 0x7F;
|
||||
__vga_buffer[addr+0] |= ((color & 0x01) << 7);
|
||||
__vga_buffer[addr+1] &= 0x7F;
|
||||
__vga_buffer[addr+1] |= ((color & 0x02) << 6);
|
||||
__vga_buffer[addr+2] &= 0x7F;
|
||||
__vga_buffer[addr+2] |= ((color & 0x04) << 5);
|
||||
}
|
||||
else // if (offset == 7)
|
||||
{
|
||||
__vga_buffer[addr+0] &= 0xF7;
|
||||
__vga_buffer[addr+0] |= ((color & 0x01) << 3);
|
||||
__vga_buffer[addr+1] &= 0xF7;
|
||||
__vga_buffer[addr+1] |= ((color & 0x02) << 2);
|
||||
__vga_buffer[addr+2] &= 0xF7;
|
||||
__vga_buffer[addr+2] |= ((color & 0x04) << 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void vga_controller_send_byte4(uint8_t data)
|
||||
{
|
||||
if (s_vga_command == 0xFF)
|
||||
{
|
||||
s_vga_command = data;
|
||||
return;
|
||||
}
|
||||
if (s_vga_command == 0x40)
|
||||
{
|
||||
uint8_t color = ((data & 0x80) >> 5) | ((data & 0x10) >> 3) | ((data & 0x02)>>1);
|
||||
vga_controller_put_pixel3(s_cursor_x, s_cursor_y, color);
|
||||
if (s_mode == 0x00)
|
||||
{
|
||||
s_cursor_x++;
|
||||
if (s_cursor_x > s_column_end)
|
||||
{
|
||||
s_cursor_x = s_column;
|
||||
s_cursor_y++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_cursor_y++;
|
||||
if ((s_cursor_y & 0x07) == 0)
|
||||
{
|
||||
s_cursor_y -= 8;
|
||||
s_cursor_x++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// command mode
|
||||
if (!s_vga_command)
|
||||
{
|
||||
s_vga_command = data;
|
||||
s_vga_arg = 0;
|
||||
}
|
||||
if (s_vga_command == VGA_SET_BLOCK)
|
||||
{
|
||||
// set block
|
||||
if (s_vga_arg == 1) { s_column = data; s_cursor_x = data; }
|
||||
if (s_vga_arg == 2) { s_column_end = data; }
|
||||
if (s_vga_arg == 3) { s_cursor_y = data; }
|
||||
if (s_vga_arg == 4) { s_vga_command = 0; }
|
||||
}
|
||||
if (s_vga_command == VGA_SET_MODE)
|
||||
{
|
||||
if (s_vga_arg == 1) { s_mode = data; s_vga_command = 0; }
|
||||
}
|
||||
s_vga_arg++;
|
||||
}
|
||||
|
||||
static void vga_controller_send_bytes(const uint8_t *buffer, uint16_t len)
|
||||
{
|
||||
while (len--)
|
||||
{
|
||||
ssd1306_intf.send(*buffer);
|
||||
buffer++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void init_vga_crt_driver(uint8_t enable_jitter_fix)
|
||||
{
|
||||
cli();
|
||||
if (enable_jitter_fix)
|
||||
{
|
||||
// Configure Timer 0 to calculate jitter fix
|
||||
TIMSK0=0;
|
||||
TCCR0A=0;
|
||||
TCCR0B=1;
|
||||
OCR0A=0;
|
||||
OCR0B=0;
|
||||
TCNT0=0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sorry, we still need to disable timer0 interrupts to avoid wake up in sleep mode
|
||||
TIMSK0 &= ~(1<<TOIE0);
|
||||
}
|
||||
|
||||
// Timer 1 - vertical sync pulses
|
||||
pinMode (V_SYNC_PIN, OUTPUT);
|
||||
TCCR1A=(1<<WGM10) | (1<<WGM11) | (1<<COM1B1);
|
||||
TCCR1B=(1<<WGM12) | (1<<WGM13) | (1<<CS12) | (1<<CS10); //1024 prescaler
|
||||
OCR1A = 259; // 16666 / 64 us = 260 (less one)
|
||||
OCR1B = 0; // 64 / 64 us = 1 (less one)
|
||||
TIFR1 = (1<<TOV1); // clear overflow flag
|
||||
TIMSK1 = (1<<TOIE1); // interrupt on overflow on timer 1
|
||||
|
||||
// Timer 2 - horizontal sync pulses
|
||||
pinMode (H_SYNC_PIN, OUTPUT);
|
||||
TCCR2A=(1<<WGM20) | (1<<WGM21) | (1<<COM2B1); //pin3=COM2B1
|
||||
TCCR2B=(1<<WGM22) | (1<<CS21); //8 prescaler
|
||||
OCR2A = 63; // 32 / 0.5 us = 64 (less one)
|
||||
OCR2B = 7; // 4 / 0.5 us = 8 (less one)
|
||||
// if (enable_jitter_fix)
|
||||
{
|
||||
TIFR2 = (1<<OCF2B); // on end of h-sync pulse
|
||||
TIMSK2 = (1<<OCIE2B); // on end of h-sync pulse
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// TIFR2 = (1<<TOV2); // int on start of h-sync pulse
|
||||
// TIMSK2 = (1<<TOIE2); // int on start of h-sync pulse
|
||||
// }
|
||||
|
||||
// Set up USART in SPI mode (MSPIM)
|
||||
|
||||
pinMode(14, OUTPUT);
|
||||
pinMode(15, OUTPUT);
|
||||
pinMode(16, OUTPUT);
|
||||
PORTC = 0;
|
||||
|
||||
sei();
|
||||
}
|
||||
|
||||
void ssd1306_vga_controller_96x40_init_no_output(void)
|
||||
{
|
||||
ssd1306_intf.spi = 0;
|
||||
ssd1306_intf.start = vga_controller_init;
|
||||
ssd1306_intf.stop = vga_controller_stop;
|
||||
ssd1306_intf.send = vga_controller_send_byte4;
|
||||
ssd1306_intf.send_buffer = vga_controller_send_bytes;
|
||||
ssd1306_intf.close = vga_controller_close;
|
||||
}
|
||||
|
||||
void ssd1306_vga_controller_96x40_init_enable_output(void)
|
||||
{
|
||||
ssd1306_vga_controller_96x40_init_no_output();
|
||||
init_vga_crt_driver(1);
|
||||
}
|
||||
|
||||
void ssd1306_vga_controller_96x40_init_enable_output_no_jitter_fix(void)
|
||||
{
|
||||
ssd1306_vga_controller_96x40_init_no_output();
|
||||
init_vga_crt_driver(0);
|
||||
// set_sleep_mode (SLEEP_MODE_IDLE);
|
||||
}
|
||||
|
||||
void ssd1306_debug_print_vga_buffer_96x40(void (*func)(uint8_t))
|
||||
{
|
||||
for(int y = 0; y < ssd1306_lcd.height; y++)
|
||||
{
|
||||
for(int x = 0; x < ssd1306_lcd.width; x++)
|
||||
{
|
||||
uint8_t color = (__vga_buffer[(y*ssd1306_lcd.width + x)/2] >> ((x&1)<<2)) & 0x0F;
|
||||
if (color)
|
||||
{
|
||||
func('#');
|
||||
func('#');
|
||||
}
|
||||
else
|
||||
{
|
||||
func(' ');
|
||||
func(' ');
|
||||
}
|
||||
}
|
||||
func('\n');
|
||||
}
|
||||
func('\n');
|
||||
}
|
||||
|
||||
void ssd1306_vga_delay(uint32_t ms)
|
||||
{
|
||||
while (ms >= 16)
|
||||
{
|
||||
uint8_t vga_frames;
|
||||
vga_frames = s_vga_frames;
|
||||
while (vga_frames == s_vga_frames);
|
||||
ms-=16;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018, Alexey Dynda
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @file vga_isr.h VGA ISR code to communicate with VGA-monitor from the sketch directly
|
||||
*
|
||||
* @details Please, include this file only once in your sketch, if you want to control
|
||||
* vga monitor directly from your sketch.
|
||||
* This header supports different modes: VGA_CONTROLLER_DEBUG, SSD1306_VGA_SLEEP_MODE.
|
||||
* If you want to use vga_controller module in debug mode without producing VGA output,
|
||||
* define VGA_CONTROLLER_DEBUG before including this header. If you want to use library with
|
||||
* AVR sleep mode, then jitter fix is not required, you will able to use TIMER0, so define
|
||||
* SSD1306_VGA_SLEEP_MODE before including this header.
|
||||
*/
|
||||
|
||||
#ifndef _SSD1306_VGA_ATMEGA328P_ISR_H_
|
||||
#define _SSD1306_VGA_ATMEGA328P_ISR_H_
|
||||
|
||||
#include "intf/vga/vga.h"
|
||||
#include "ssd1306_hal/io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Including this header defines ISR handlers in your application automatically
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE) && defined(__AVR_ATmega328P__)
|
||||
|
||||
#ifndef DEJITTER_OFFSET
|
||||
#define DEJITTER_OFFSET -4
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_VGA_128X64_ENABLE)
|
||||
|
||||
static const uint16_t __VGA_VERTICAL_PIXELS = 64;
|
||||
static const uint16_t __VGA_LINE_BYTES = 16;
|
||||
static const uint16_t __VGA_PIXEL_HEIGHT = 7;
|
||||
volatile uint8_t __vga_buffer[16*64] = {0};
|
||||
static const volatile uint8_t *__VGA_BUFFER_PTR = &__vga_buffer[0];
|
||||
|
||||
void ssd1306_vga_controller_128x64_init_no_output(void);
|
||||
void ssd1306_vga_controller_128x64_init_enable_output(void);
|
||||
void ssd1306_vga_controller_128x64_init_enable_output_no_jitter_fix(void);
|
||||
void ssd1306_debug_print_vga_buffer_128x64(void (*func)(uint8_t));
|
||||
|
||||
#elif defined(CONFIG_VGA_96X40_ENABLE)
|
||||
|
||||
static const uint16_t __VGA_VERTICAL_PIXELS = 40;
|
||||
static const uint16_t __VGA_LINE_BYTES = 36;
|
||||
static const uint16_t __VGA_PIXEL_HEIGHT = 10;
|
||||
volatile uint8_t __vga_buffer[36*40] = {0};
|
||||
static const volatile uint8_t * __VGA_BUFFER_PTR = &__vga_buffer[0];
|
||||
|
||||
void ssd1306_vga_controller_96x40_init_no_output(void);
|
||||
void ssd1306_vga_controller_96x40_init_enable_output(void);
|
||||
void ssd1306_vga_controller_96x40_init_enable_output_no_jitter_fix(void);
|
||||
void ssd1306_debug_print_vga_buffer_96x40(void (*func)(uint8_t));
|
||||
|
||||
#else
|
||||
#error "Please, define one of VGA options"
|
||||
#endif
|
||||
|
||||
#if !defined(VGA_CONTROLLER_DEBUG)
|
||||
|
||||
// Total number of lines used in specific scan mode
|
||||
static const uint16_t VGA_TOTAL_MODE_LINES = __VGA_VERTICAL_PIXELS*__VGA_PIXEL_HEIGHT;
|
||||
|
||||
// Lines to skip before starting to draw first line of the screen content
|
||||
// This includes V-sync signal + front porch
|
||||
static const uint8_t V_BACKPORCH_LINES = 40;
|
||||
|
||||
// Lines to skip before starting to draw first line of the screen content
|
||||
// This includes V-sync signal + front porch
|
||||
volatile int s_current_scan_line;
|
||||
|
||||
volatile uint8_t s_lines_to_skip;
|
||||
volatile uint8_t s_scan_line_index;
|
||||
volatile const uint8_t * volatile s_current_scan_line_data = __VGA_BUFFER_PTR;
|
||||
extern volatile uint8_t s_vga_frames;
|
||||
extern unsigned long timer0_millis;
|
||||
// ISR: Vsync pulse
|
||||
ISR(TIMER1_OVF_vect)
|
||||
{
|
||||
s_current_scan_line = 0;
|
||||
s_scan_line_index = 0;
|
||||
s_current_scan_line_data = __VGA_BUFFER_PTR;
|
||||
s_lines_to_skip = V_BACKPORCH_LINES;
|
||||
s_vga_frames++;
|
||||
timer0_millis += 16;
|
||||
} // end of TIMER1_OVF_vect
|
||||
|
||||
#if defined(CONFIG_VGA_128X64_ENABLE)
|
||||
static inline void /*__attribute__ ((noinline))*/ do_scan_line()
|
||||
{
|
||||
// output all pixels
|
||||
|
||||
asm volatile(
|
||||
".rept 16\n\t"
|
||||
|
||||
"ld r24, Z+\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
"lsr r24\n\t"
|
||||
"nop\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
"lsr r24\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
"lsr r24\n\t"
|
||||
"nop\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
"lsr r24\n\t"
|
||||
"nop\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
"lsr r24\n\t"
|
||||
"nop\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
"lsr r24\n\t"
|
||||
"nop\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
"lsr r24\n\t"
|
||||
"nop\n\t"
|
||||
"out %[port], r24\n\t"
|
||||
|
||||
".endr\n\t"
|
||||
"nop\n\t"
|
||||
"ldi r24,0\n\t"
|
||||
"out %[port], r24 \n\t"
|
||||
"nop\n\t"
|
||||
:
|
||||
: [port] "I" (_SFR_IO_ADDR(PORTC)),
|
||||
"z" "I" ((uint8_t *)s_current_scan_line_data )
|
||||
: "r24", "memory"
|
||||
);
|
||||
}
|
||||
#elif defined(CONFIG_VGA_96X40_ENABLE)
|
||||
static inline void /*__attribute__ ((noinline))*/ do_scan_line()
|
||||
{
|
||||
// output all pixels
|
||||
|
||||
asm volatile(
|
||||
".rept 12\n\t"
|
||||
|
||||
"ld r24, Z+\n\t" // r24 = 82227111
|
||||
// "nop\n\t" // to make pixel wider in 96x40 mode
|
||||
"out %[port], r24\n\t" // 111
|
||||
|
||||
"ld r25, Z+\n\t" // r25 = 84447333
|
||||
"swap r24\n\t" // r24 = 71118222
|
||||
"out %[port], r24\n\t" // 222
|
||||
|
||||
"andi r24, 0x88\n\t" // r24 = 70008000
|
||||
"ld r20, Z+\n\t" // r20 = 86667555
|
||||
"out %[port], r25\n\t" // 333
|
||||
|
||||
"swap r25\n\t" // r25 = 73338444
|
||||
"lsr r24\n\t" // r24 = 00080007
|
||||
"lsr r24\n\t" // r24 = 00800070
|
||||
"out %[port], r25\n\t" // 444
|
||||
|
||||
"andi r25, 0x88\n\t" // r25 = 70008000
|
||||
"lsr r24\n\t" // r24 = 08000700
|
||||
"lsr r25\n\t" // r25 = 00080007
|
||||
"out %[port], r20\n\t" // 555
|
||||
|
||||
"swap r20\n\t" // r20 = 75558666
|
||||
"lsr r25\n\t" // r25 = 00800070
|
||||
"or r24, r25\n\t" // r24 = 08800770
|
||||
"out %[port], r20\n\t" // 666
|
||||
|
||||
"andi r20, 0x88\n\t" // r20 = 70008000
|
||||
"lsr r20\n\t" // r20 = 00080007 b
|
||||
"or r24, r20\n\t" // r24 = 08880777 rgb
|
||||
"out %[port], r24\n\t" // 777
|
||||
|
||||
"swap r24\n\t" // r24 = 07770888
|
||||
// "nop\n\t" // to make pixel wider in 96x40 mode
|
||||
"nop\n\t"
|
||||
"out %[port], r24\n\t" // 888
|
||||
|
||||
".endr\n\t"
|
||||
// "nop\n\t" // to make pixel wider in 96x40 mode
|
||||
"nop\n\t"
|
||||
"ldi r24,0\n\t"
|
||||
"out %[port], r24 \n\t"
|
||||
"nop\n\t"
|
||||
:
|
||||
: [port] "I" (_SFR_IO_ADDR(PORTC)),
|
||||
"z" "I" ((uint8_t *)s_current_scan_line_data )
|
||||
: "r24", "r25", "r20", "memory"
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
//#ifdef SSD1306_VGA_SLEEP_MODE
|
||||
//ISR(TIMER2_OVF_vect) // for start of h-sync pulse
|
||||
//#else
|
||||
ISR(TIMER2_COMPB_vect) // for end of h-sync pulse
|
||||
//#endif
|
||||
{
|
||||
// ISR should work as fast as possible
|
||||
if (s_lines_to_skip)
|
||||
{
|
||||
s_lines_to_skip--;
|
||||
return;
|
||||
}
|
||||
else if (s_current_scan_line >= VGA_TOTAL_MODE_LINES)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#ifndef SSD1306_VGA_SLEEP_MODE
|
||||
// This is dejitter code, it purpose to start pixels output at the same offset after h-sync
|
||||
asm volatile(
|
||||
" lds r24, %[timer0] \n\t" //
|
||||
" subi r24, %[toffset] \n\t" // some offset, calculated experimentally
|
||||
" andi r24, 7 \n\t" // use module 8 value from Timer 0 counter
|
||||
" ldi r31, pm_hi8(LW) \n\t" // load label address
|
||||
" ldi r30, pm_lo8(LW) \n\t" //
|
||||
" add r30, r24 \n\t" // no need to multiply by 2 since AVR addresses are half-values
|
||||
" adc r31, __zero_reg__ \n\t" //
|
||||
" ijmp \n\t" //
|
||||
"LW: \n\t" //
|
||||
" nop \n\t" //
|
||||
" nop \n\t" //
|
||||
" nop \n\t" //
|
||||
" nop \n\t" //
|
||||
" nop \n\t" //
|
||||
" nop \n\t" //
|
||||
" nop \n\t" //
|
||||
:
|
||||
: [timer0] "i" (&TCNT0),
|
||||
[toffset] "i" ((uint8_t)DEJITTER_OFFSET)
|
||||
: "r30", "r31", "r24", "r25");
|
||||
#endif
|
||||
do_scan_line();
|
||||
s_current_scan_line++;
|
||||
s_scan_line_index++;
|
||||
if ( s_scan_line_index >= __VGA_PIXEL_HEIGHT )
|
||||
{
|
||||
s_scan_line_index=0;
|
||||
s_current_scan_line_data += __VGA_LINE_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // VGA_CONTROLLER_DEBUG
|
||||
|
||||
#if defined(CONFIG_VGA_128X64_ENABLE)
|
||||
|
||||
static inline void ssd1306_vga_controller_init(void)
|
||||
{
|
||||
// if there is no builtin support then only debug mode is available
|
||||
#if defined(VGA_CONTROLLER_DEBUG)
|
||||
ssd1306_vga_controller_128x64_init_no_output();
|
||||
#elif defined(SSD1306_VGA_SLEEP_MODE)
|
||||
ssd1306_vga_controller_128x64_init_enable_output_no_jitter_fix();
|
||||
#else
|
||||
ssd1306_vga_controller_128x64_init_enable_output();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ssd1306_debug_print_vga_buffer(void (*func)(uint8_t))
|
||||
{
|
||||
ssd1306_debug_print_vga_buffer_128x64(func);
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_VGA_96X40_ENABLE)
|
||||
|
||||
static inline void ssd1306_vga_controller_init(void)
|
||||
{
|
||||
// if there is no builtin support then only debug mode is available
|
||||
#if defined(VGA_CONTROLLER_DEBUG)
|
||||
ssd1306_vga_controller_96x40_init_no_output();
|
||||
#elif defined(SSD1306_VGA_SLEEP_MODE)
|
||||
ssd1306_vga_controller_96x40_init_enable_output_no_jitter_fix();
|
||||
#else
|
||||
ssd1306_vga_controller_96x40_init_enable_output();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ssd1306_debug_print_vga_buffer(void (*func)(uint8_t))
|
||||
{
|
||||
ssd1306_debug_print_vga_buffer_96x40(func);
|
||||
}
|
||||
|
||||
#endif // CONFIG_VGA_XXX_ENABLE
|
||||
|
||||
#endif // SSD1306_BUILTIN_VGA_SUPPORT
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Credits to https://github.com/bitluni/ESP32CompositeVideo
|
||||
*/
|
||||
|
||||
#include "CompositeOutput.h"
|
||||
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE) && defined(__XTENSA__)
|
||||
|
||||
#include "driver/i2s.h"
|
||||
|
||||
#define I2S_VGA_SAMPLE_RATE (4000000)
|
||||
static const i2s_port_t I2S_PORT = (i2s_port_t)I2S_NUM_0;
|
||||
|
||||
const TechProperties PALProperties = {
|
||||
.lineMicros = 64,
|
||||
.syncMicros = 4.7,
|
||||
.blankEndMicros = 10.4,
|
||||
.backMicros = 1.65,
|
||||
.shortVSyncMicros = 2.35,
|
||||
.overscanLeftMicros = 1.6875,
|
||||
.overscanRightMicros = 1.6875,
|
||||
.syncVolts = -0.3,
|
||||
.blankVolts = 0.0,
|
||||
.blackVolts = 0.005,//specs 0.0,
|
||||
.whiteVolts = 0.7,
|
||||
.lines = 625,
|
||||
.linesFirstTop = 23,
|
||||
.linesOverscanTop = 9,
|
||||
.linesOverscanBottom = 9,
|
||||
.imageAspect = 4./3.
|
||||
};
|
||||
|
||||
const TechProperties NTSCProperties = {
|
||||
.lineMicros = 63.492,
|
||||
.syncMicros = 4.7,
|
||||
.blankEndMicros = 9.2,
|
||||
.backMicros = 1.5,
|
||||
.shortVSyncMicros = 2.3,
|
||||
.overscanLeftMicros = 0,//1.3,
|
||||
.overscanRightMicros = 0,//1,
|
||||
.syncVolts = -0.286,
|
||||
.blankVolts = 0.0,
|
||||
.blackVolts = 0.05, //specs 0.054,
|
||||
.whiteVolts = 0.714,
|
||||
.lines = 525,
|
||||
.linesFirstTop = 20,
|
||||
.linesOverscanTop = 6,
|
||||
.linesOverscanBottom = 9,
|
||||
.imageAspect = 4./3.
|
||||
};
|
||||
|
||||
CompositeOutput::CompositeOutput(Mode mode, double Vcc)
|
||||
:properties((mode==NTSC) ? NTSCProperties: PALProperties)
|
||||
{
|
||||
double dacPerVolt = 255.0 / Vcc;
|
||||
levelSync = 0;
|
||||
levelBlank = (properties.blankVolts - properties.syncVolts) * dacPerVolt + 0.5;
|
||||
levelBlack = (properties.blackVolts - properties.syncVolts) * dacPerVolt + 0.5;
|
||||
levelWhite = (properties.whiteVolts - properties.syncVolts) * dacPerVolt + 0.5;
|
||||
grayValues = levelWhite - levelBlack + 1;
|
||||
}
|
||||
|
||||
void CompositeOutput::init(int xres, int yres, int bpp)
|
||||
{
|
||||
const int LINES_SYNC_TOP = 5;
|
||||
const int LINES_SYNC_BOTTOM = 3;
|
||||
|
||||
m_buffer_width = xres;
|
||||
m_buffer_height = yres;
|
||||
m_bpp = bpp;
|
||||
|
||||
linesOdd = properties.lines / 2;
|
||||
linesEven = properties.lines - linesOdd;
|
||||
linesEvenActive = linesEven - properties.linesFirstTop - LINES_SYNC_BOTTOM;
|
||||
linesOddActive = linesOdd - properties.linesFirstTop - LINES_SYNC_BOTTOM;
|
||||
linesEvenVisible = linesEvenActive - properties.linesOverscanTop - properties.linesOverscanBottom;
|
||||
linesOddVisible = linesOddActive - properties.linesOverscanTop - properties.linesOverscanBottom;
|
||||
|
||||
targetYresOdd = (yres / 2 < linesOddVisible) ? yres / 2 : linesOddVisible;
|
||||
targetYresEven = (yres - targetYresOdd < linesEvenVisible) ? yres - targetYresOdd : linesEvenVisible;
|
||||
targetYres = targetYresEven + targetYresOdd;
|
||||
|
||||
linesEvenBlankTop = properties.linesFirstTop - LINES_SYNC_TOP + properties.linesOverscanTop + (linesEvenVisible - targetYresEven) / 2;
|
||||
linesEvenBlankBottom = linesEven - linesEvenBlankTop - targetYresEven - LINES_SYNC_BOTTOM;
|
||||
linesOddBlankTop = linesEvenBlankTop;
|
||||
linesOddBlankBottom = linesOdd - linesOddBlankTop - targetYresOdd - LINES_SYNC_BOTTOM;
|
||||
|
||||
double samplesPerSecond = I2S_VGA_SAMPLE_RATE;
|
||||
double samplesPerMicro = samplesPerSecond * 0.000001;
|
||||
m_samples_per_line = (int)(samplesPerMicro * properties.lineMicros + 1.5) & ~1;
|
||||
samplesSync = samplesPerMicro * properties.syncMicros + 0.5;
|
||||
samplesBlank = samplesPerMicro * (properties.blankEndMicros - properties.syncMicros + properties.overscanLeftMicros) + 0.5;
|
||||
samplesBack = samplesPerMicro * (properties.backMicros + properties.overscanRightMicros) + 0.5;
|
||||
samplesActive = m_samples_per_line - samplesSync - samplesBlank - samplesBack;
|
||||
|
||||
targetXres = xres < samplesActive ? xres : samplesActive;
|
||||
|
||||
samplesVSyncShort = samplesPerMicro * properties.shortVSyncMicros + 0.5;
|
||||
|
||||
samplesBlackLeft = (samplesActive - targetXres) / 2;
|
||||
samplesBlackRight = samplesActive - targetXres - samplesBlackLeft;
|
||||
|
||||
// pixelAspect = (float(samplesActive) / (linesEvenVisible + linesOddVisible)) / properties.imageAspect;
|
||||
|
||||
line = (uint16_t*)malloc(sizeof(uint16_t) * m_samples_per_line * 2);
|
||||
m_ptr = line;
|
||||
m_end = line + m_samples_per_line * 2;
|
||||
|
||||
init_hardware();
|
||||
}
|
||||
|
||||
void CompositeOutput::init_hardware()
|
||||
{
|
||||
i2s_config_t i2s_config = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
|
||||
.sample_rate = I2S_VGA_SAMPLE_RATE, //not really used
|
||||
.bits_per_sample = (i2s_bits_per_sample_t)I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
|
||||
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 2,
|
||||
.dma_buf_len = m_samples_per_line //a buffer per line
|
||||
};
|
||||
|
||||
i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); //start i2s driver
|
||||
i2s_set_pin(I2S_PORT, NULL); //use internal DAC
|
||||
i2s_set_sample_rates(I2S_PORT, I2S_VGA_SAMPLE_RATE); //dummy sample rate, since the function fails at high values
|
||||
}
|
||||
|
||||
void CompositeOutput::fillValues(uint8_t value, int count)
|
||||
{
|
||||
for(int j = 0; j < count; j++)
|
||||
{
|
||||
*m_ptr = ((uint16_t)value << 8);
|
||||
m_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
void CompositeOutput::sendFrameHalfResolution(const uint8_t *frame)
|
||||
{
|
||||
generate_long_sync(); // 1
|
||||
generate_long_sync(); // 2
|
||||
generate_long_short_sync(); // 3
|
||||
generate_short_sync(); // 4
|
||||
generate_short_sync(); // 5
|
||||
for (int y = 0; y < linesEvenBlankTop; y++)
|
||||
{
|
||||
generate_blank_line(); // top blank lines
|
||||
}
|
||||
|
||||
for (int y = 0; y < targetYresEven; y++)
|
||||
{
|
||||
generate_line_from_buffer( &frame[(y >> 3)*(m_buffer_width * m_bpp / 8)] ); // real data
|
||||
}
|
||||
for (int y = 0; y < linesEvenBlankBottom; y++)
|
||||
{
|
||||
generate_blank_line(); // bottom blank lines
|
||||
}
|
||||
generate_short_sync(); // 311
|
||||
generate_short_sync(); // 312
|
||||
|
||||
generate_short_long_sync(); // 313
|
||||
generate_long_sync(); // 314
|
||||
generate_long_sync(); // 315
|
||||
generate_short_sync(); // 316
|
||||
generate_short_sync(); // 317
|
||||
|
||||
generate_short_blank_sync(); // 318
|
||||
for (int y = 0; y < linesOddBlankTop; y++)
|
||||
{
|
||||
generate_blank_line(); // top blank lines
|
||||
}
|
||||
for (int y = 0; y < targetYresOdd; y++)
|
||||
{
|
||||
generate_line_from_buffer( &frame[(y>>3)*(m_buffer_width * m_bpp / 8)] ); // real data
|
||||
}
|
||||
for(int y = 0; y < linesOddBlankBottom; y++)
|
||||
{
|
||||
generate_blank_line(); // bottom blank lines
|
||||
}
|
||||
generate_blank_short_sync(); // 623
|
||||
generate_short_sync(); // 624
|
||||
generate_short_sync(); // 625
|
||||
if (m_ptr != line) // force to send data
|
||||
{
|
||||
size_t bytes_written;
|
||||
i2s_write(I2S_PORT, (char*)line, (m_ptr - line) * sizeof(uint16_t), &bytes_written, portMAX_DELAY);
|
||||
m_ptr = line;
|
||||
}
|
||||
}
|
||||
|
||||
void CompositeOutput::generate_long_sync()
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
fillValues(levelSync, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
fillValues(levelBlank, samplesVSyncShort);
|
||||
}
|
||||
check_buffer();
|
||||
}
|
||||
|
||||
void CompositeOutput::generate_short_sync()
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
fillValues(levelSync, samplesVSyncShort);
|
||||
fillValues(levelBlank, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
}
|
||||
check_buffer();
|
||||
}
|
||||
|
||||
void CompositeOutput::generate_long_short_sync()
|
||||
{
|
||||
fillValues(levelSync, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
fillValues(levelBlank, samplesVSyncShort);
|
||||
fillValues(levelSync, samplesVSyncShort);
|
||||
fillValues(levelBlank, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
check_buffer();
|
||||
}
|
||||
|
||||
void CompositeOutput::generate_short_long_sync()
|
||||
{
|
||||
fillValues(levelSync, samplesVSyncShort);
|
||||
fillValues(levelBlank, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
fillValues(levelSync, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
fillValues(levelBlank, samplesVSyncShort);
|
||||
check_buffer();
|
||||
}
|
||||
|
||||
void CompositeOutput::generate_short_blank_sync()
|
||||
{
|
||||
fillValues(levelSync, samplesVSyncShort);
|
||||
fillValues(levelBlank, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
fillValues(levelBlank, m_samples_per_line / 2);
|
||||
check_buffer();
|
||||
}
|
||||
|
||||
void CompositeOutput::generate_blank_short_sync()
|
||||
{
|
||||
fillValues(levelSync, samplesSync);
|
||||
fillValues(levelBlank, m_samples_per_line / 2 - samplesSync);
|
||||
fillValues(levelSync, samplesVSyncShort);
|
||||
fillValues(levelBlank, m_samples_per_line / 2 - samplesVSyncShort);
|
||||
check_buffer();
|
||||
}
|
||||
|
||||
void CompositeOutput::generate_blank_line()
|
||||
{
|
||||
fillValues(levelSync, samplesSync);
|
||||
fillValues(levelBlank, samplesBlank);
|
||||
fillValues(levelBlack, samplesActive);
|
||||
fillValues(levelBlank, samplesBack);
|
||||
check_buffer();
|
||||
}
|
||||
|
||||
const uint8_t* CompositeOutput::generate_line_from_buffer(const uint8_t *pixels)
|
||||
{
|
||||
fillValues(levelSync, samplesSync);
|
||||
fillValues(levelBlank, samplesBlank);
|
||||
fillValues(levelBlack, samplesBlackLeft);
|
||||
for (int x = 0; x < targetXres; x++)
|
||||
{
|
||||
uint8_t color = pixels[(x >> 3)] & (1<< (x&0x07));
|
||||
// *m_ptr = (levelBlack + pixels[x]) << 8;
|
||||
*m_ptr = (levelBlack + (color ? 0x80: 0x00 )) << 8;
|
||||
m_ptr++;
|
||||
}
|
||||
fillValues(levelBlack, samplesBlackRight);
|
||||
fillValues(levelBlank, samplesBack);
|
||||
check_buffer();
|
||||
return pixels + m_buffer_width * m_bpp / 8;
|
||||
// return pixels + targetXres;
|
||||
}
|
||||
|
||||
void CompositeOutput::check_buffer()
|
||||
{
|
||||
if (m_ptr == m_end)
|
||||
{
|
||||
size_t bytes_written;
|
||||
i2s_write(I2S_PORT, (char*)line, sizeof(uint16_t) * (m_end - line), &bytes_written, portMAX_DELAY);
|
||||
m_ptr = line;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Credits to https://github.com/bitluni/ESP32CompositeVideo
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssd1306_hal/io.h"
|
||||
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE) && defined(__XTENSA__)
|
||||
|
||||
#include <stdint.h>
|
||||
#include "driver/i2s.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float lineMicros;
|
||||
float syncMicros;
|
||||
float blankEndMicros;
|
||||
float backMicros;
|
||||
float shortVSyncMicros;
|
||||
float overscanLeftMicros;
|
||||
float overscanRightMicros;
|
||||
float syncVolts;
|
||||
float blankVolts;
|
||||
float blackVolts;
|
||||
float whiteVolts;
|
||||
short lines;
|
||||
short linesFirstTop;
|
||||
short linesOverscanTop;
|
||||
short linesOverscanBottom;
|
||||
float imageAspect;
|
||||
} TechProperties;
|
||||
|
||||
class CompositeOutput
|
||||
{
|
||||
public:
|
||||
|
||||
enum Mode
|
||||
{
|
||||
PAL,
|
||||
NTSC
|
||||
};
|
||||
|
||||
CompositeOutput(Mode mode, double Vcc = 3.3);
|
||||
|
||||
void init(int xres, int yres, int bpp);
|
||||
|
||||
void fillValues(uint8_t value, int count);
|
||||
|
||||
void sendFrameHalfResolution(const uint8_t *frame);
|
||||
|
||||
private:
|
||||
const TechProperties &properties;
|
||||
|
||||
int m_samples_per_line = 0;
|
||||
int samplesSync = 0;
|
||||
int samplesBlank = 0;
|
||||
int samplesBack = 0;
|
||||
int samplesActive = 0;
|
||||
int samplesBlackLeft = 0;
|
||||
int samplesBlackRight = 0;
|
||||
|
||||
int samplesVSyncShort = 0;
|
||||
int samplesVSyncLong = 0;
|
||||
|
||||
uint8_t levelSync = 0;
|
||||
uint8_t levelBlank = 0;
|
||||
uint8_t levelBlack = 0;
|
||||
uint8_t levelWhite = 0;
|
||||
uint8_t grayValues = 0;
|
||||
|
||||
int targetXres = 0;
|
||||
int targetYres = 0;
|
||||
int targetYresEven = 0;
|
||||
int targetYresOdd = 0;
|
||||
|
||||
int linesEven = 0;
|
||||
int linesOdd = 0;
|
||||
int linesEvenActive = 0;
|
||||
int linesOddActive = 0;
|
||||
int linesEvenVisible = 0;
|
||||
int linesOddVisible = 0;
|
||||
int linesEvenBlankTop = 0;
|
||||
int linesEvenBlankBottom = 0;
|
||||
int linesOddBlankTop = 0;
|
||||
int linesOddBlankBottom = 0;
|
||||
|
||||
int m_buffer_width = 0;
|
||||
int m_buffer_height = 0;
|
||||
int m_bpp = 0;
|
||||
|
||||
// float pixelAspect;
|
||||
|
||||
uint16_t *line = nullptr;
|
||||
uint16_t *m_end = nullptr;
|
||||
uint16_t *m_ptr = nullptr;
|
||||
|
||||
void init_hardware();
|
||||
void check_buffer();
|
||||
void generate_vsync();
|
||||
void generate_long_sync();
|
||||
void generate_short_sync();
|
||||
void generate_long_short_sync();
|
||||
void generate_short_long_sync();
|
||||
void generate_short_blank_sync();
|
||||
void generate_blank_short_sync();
|
||||
void generate_blank_line();
|
||||
const uint8_t * generate_line_from_buffer(const uint8_t * buffer);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019, Alexey Dynda
|
||||
|
||||
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.
|
||||
|
||||
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 "intf/ssd1306_interface.h"
|
||||
#include "intf/vga/vga.h"
|
||||
#include "lcd/lcd_common.h"
|
||||
#include "lcd/vga_commands.h"
|
||||
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE) && defined(ESP32)
|
||||
|
||||
#include "CompositeOutput.h"
|
||||
|
||||
//#define VGA_CONTROLLER_DEBUG
|
||||
|
||||
static uint8_t *__vga_buffer = nullptr;
|
||||
extern uint16_t ssd1306_color;
|
||||
|
||||
// Set to ssd1306 compatible mode by default
|
||||
static uint8_t s_mode = 0x01;
|
||||
static uint8_t s_vga_command = 0xFF;
|
||||
static uint8_t s_vga_arg = 0;
|
||||
static uint8_t s_column = 0;
|
||||
static uint8_t s_column_end = 0;
|
||||
static uint8_t s_cursor_x = 0;
|
||||
static uint8_t s_cursor_y = 0;
|
||||
static uint8_t s_width = 0;
|
||||
static uint8_t s_height = 0;
|
||||
static uint8_t s_bpp = 0;
|
||||
|
||||
static CompositeOutput output(CompositeOutput::PAL);
|
||||
|
||||
static void compositeCore(void *data)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
//just send the graphics frontbuffer whithout any interruption
|
||||
output.sendFrameHalfResolution(__vga_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static void vga_controller_init(void)
|
||||
{
|
||||
s_vga_command = 0xFF;
|
||||
}
|
||||
|
||||
#ifdef VGA_CONTROLLER_DEBUG
|
||||
#include <stdio.h>
|
||||
void uart_send_byte(uint8_t data)
|
||||
{
|
||||
printf("%c", data);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void vga_controller_stop(void)
|
||||
{
|
||||
s_vga_command = 0xFF;
|
||||
#ifdef VGA_CONTROLLER_DEBUG
|
||||
ssd1306_debug_print_vga_buffer( uart_send_byte );
|
||||
#endif
|
||||
}
|
||||
|
||||
static void vga_controller_close(void)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Function sends 8 vertical pixels to buffer
|
||||
*/
|
||||
static inline void vga_controller_put_pixels(uint8_t x, uint8_t y, uint8_t pixels)
|
||||
{
|
||||
uint16_t addr = (x >> 3) + (uint16_t)(y * 16);
|
||||
uint8_t offset = x & 0x07;
|
||||
uint8_t mask = 1 << offset;
|
||||
if (addr >= 16*64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (uint8_t i=8; i>0; i--)
|
||||
{
|
||||
if (pixels & 0x01) __vga_buffer[addr] |= mask;
|
||||
else __vga_buffer[addr] &= ~mask;
|
||||
addr += 16;
|
||||
pixels >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void vga_controller_send_byte(uint8_t data)
|
||||
{
|
||||
if (s_vga_command == 0xFF)
|
||||
{
|
||||
s_vga_command = data;
|
||||
return;
|
||||
}
|
||||
if (s_vga_command == 0x40)
|
||||
{
|
||||
vga_controller_put_pixels(s_cursor_x, s_cursor_y, data);
|
||||
s_cursor_x++;
|
||||
if (s_cursor_x > s_column_end)
|
||||
{
|
||||
s_cursor_x = s_column;
|
||||
s_cursor_y += 8;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// command mode
|
||||
if (!s_vga_command)
|
||||
{
|
||||
s_vga_command = data;
|
||||
s_vga_arg = 0;
|
||||
}
|
||||
if (s_vga_command == VGA_SET_BLOCK)
|
||||
{
|
||||
// set block
|
||||
if (s_vga_arg == 1)
|
||||
{
|
||||
s_column = data >= ssd1306_lcd.width ? ssd1306_lcd.width - 1 : data ;
|
||||
s_cursor_x = s_column;
|
||||
}
|
||||
if (s_vga_arg == 2) { s_column_end = data >= ssd1306_lcd.width ? ssd1306_lcd.width - 1 : data; }
|
||||
if (s_vga_arg == 3) { s_cursor_y = (data << 3); }
|
||||
if (s_vga_arg == 4) { s_vga_command = 0; }
|
||||
}
|
||||
else if (s_vga_command == VGA_SET_MODE)
|
||||
{
|
||||
if (s_vga_arg == 1) { s_mode = data; s_vga_command = 0; }
|
||||
if (s_vga_arg == 2) { s_vga_command = 0; }
|
||||
}
|
||||
else if (s_vga_command == VGA_SET_RESOLUTION )
|
||||
{
|
||||
if (s_vga_arg == 1) { s_width = data; }
|
||||
if (s_vga_arg == 2) { s_height = data; }
|
||||
if (s_vga_arg == 3) { s_bpp = data; s_vga_command = 0; }
|
||||
}
|
||||
else if (s_vga_command == VGA_DISPLAY_ON )
|
||||
{
|
||||
__vga_buffer = (uint8_t *) malloc(s_width * s_height * s_bpp / 8);
|
||||
output.init(s_width, s_height, s_bpp);
|
||||
xTaskCreatePinnedToCore(compositeCore, "c", 1024, NULL, 1, NULL, 0);
|
||||
s_vga_command = 0;
|
||||
}
|
||||
s_vga_arg++;
|
||||
}
|
||||
|
||||
static void vga_controller_send_bytes(const uint8_t *buffer, uint16_t len)
|
||||
{
|
||||
while (len--)
|
||||
{
|
||||
ssd1306_intf.send(*buffer);
|
||||
buffer++;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ssd1306_CompositeVideoInit_esp32(void);
|
||||
void ssd1306_CompositeVideoInit_esp32(void)
|
||||
{
|
||||
ssd1306_intf.spi = 0;
|
||||
ssd1306_intf.start = vga_controller_init;
|
||||
ssd1306_intf.stop = vga_controller_stop;
|
||||
ssd1306_intf.send = vga_controller_send_byte;
|
||||
ssd1306_intf.send_buffer = vga_controller_send_bytes;
|
||||
ssd1306_intf.close = vga_controller_close;
|
||||
}
|
||||
|
||||
void ssd1306_debug_print_vga_buffer_128x64(void (*func)(uint8_t))
|
||||
{
|
||||
for(int y = 0; y < ssd1306_lcd.height; y++)
|
||||
{
|
||||
for(int x = 0; x < ssd1306_lcd.width; x++)
|
||||
{
|
||||
uint8_t color = __vga_buffer[(x >> 3) + y * 16] & (1<< (x&0x07));
|
||||
if (color)
|
||||
{
|
||||
func('#');
|
||||
}
|
||||
else
|
||||
{
|
||||
func(' ');
|
||||
}
|
||||
}
|
||||
func('\n');
|
||||
}
|
||||
func('\n');
|
||||
}
|
||||
|
||||
void ssd1306_debug_print_vga_buffer(void (*func)(uint8_t))
|
||||
{
|
||||
ssd1306_debug_print_vga_buffer_128x64(func);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019, Alexey Dynda
|
||||
|
||||
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.
|
||||
|
||||
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 "ssd1306_hal/io.h"
|
||||
#include "vga.h"
|
||||
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE) && defined(ESP32)
|
||||
extern void ssd1306_CompositeVideoInit_esp32(void);
|
||||
void ssd1306_vgaInit()
|
||||
{
|
||||
ssd1306_CompositeVideoInit_esp32();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ssd1306_vgaInit()
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2019, Alexey Dynda
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @file vga.h VGA basic data. Do not include this header in your project!!!
|
||||
*/
|
||||
|
||||
#ifndef _SSD1306_VGA_H_
|
||||
#define _SSD1306_VGA_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "ssd1306_hal/io.h"
|
||||
|
||||
#if defined(CONFIG_VGA_AVAILABLE) && defined(CONFIG_VGA_ENABLE)
|
||||
|
||||
#ifndef DOXYGEN_SHOULD_SKIP_THIS
|
||||
|
||||
#if defined(__AVR_ATmega328P__)
|
||||
/* TODO: Move defines out of this file */
|
||||
static const uint8_t H_SYNC_PIN = 3;
|
||||
static const uint8_t V_SYNC_PIN = 10;
|
||||
extern volatile uint8_t __vga_buffer[];
|
||||
|
||||
/**
|
||||
* Make ms milliseconds delay. This function has very low precision: 16ms.
|
||||
*
|
||||
* @param ms time in milliseconds
|
||||
*/
|
||||
void ssd1306_vga_delay(uint32_t ms);
|
||||
|
||||
#elif defined(ESP32)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // DOXYGEN_SHOULD_SKIP_THIS
|
||||
|
||||
/**
|
||||
* Prints vga buffer in text form using passed callback
|
||||
*
|
||||
* @param func callback to use for printing single character
|
||||
*/
|
||||
void ssd1306_debug_print_vga_buffer(void (*func)(uint8_t));
|
||||
|
||||
/**
|
||||
* Initializes hardware VGA controller
|
||||
* Be careful, this function reinitialized Atmega328p timers.
|
||||
* delay() function will not work after call.
|
||||
*/
|
||||
//void ssd1306_vga_controller_init(void);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void ssd1306_vgaInit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user