问题
I have some code that displays a single pixel on screen through VGA but am a bit stuck on how I could set multiple pixels on screen where I want them. I set up two Timers for Vertical Sync and Horizontal Sync then using the V-Sync interrupt, I set a flag to allow PA8 to toggle and output a pixel at the correct timing based on the SetCompare value I set on the timer's channel. The STM32f103c8 is also overclocked to 128MHz. Here's the code:
#include "Arduino.h"
//640x480 at 60Hz
static volatile int vflag = 0;
void setup() {
#define FLASH_ACR (*(volatile uint32_t*)(0x40022000))
FLASH_ACR = 0b110010; //enable flash prefetch and wait state to increase stability at higher freq
pinMode(PA0, PWM); //31,468.75Hz (Horizontal Sync) (Channel 1)
Timer2.pause();
Timer2.setOverflow(4067); //reload register value
Timer2.setPrescaleFactor(1); //number that divides main clock
Timer2.setCompare(1, 488); //12% duty cycle (Syncpulse/Wholeline)
Timer2.setCompare(2, 2000); //0-4067 = vertical line going left or right respectively
Timer2.attachInterrupt(2, TRIGGER);
Timer2.refresh();
Timer2.resume();
pinMode(PA6, PWM); //60Hz (Vertical Sync) (Channel 1)
Timer3.pause();
Timer3.setOverflow(4183); //reload register value
Timer3.setPrescaleFactor(510); //number that divides main clock
Timer3.setCompare(1, 16); //0.38% duty cycle (Syncpulse/Wholeframe)
Timer3.setCompare(2, 2000); //0-4183 = horizontal line going up or down respectively
Timer3.attachInterrupt(2, TRIGGER2);
Timer3.refresh();
Timer3.resume();
pinMode(PA8, OUTPUT); //need to set PinMode in order for the ODR register to work
}
void loop() {
}
void TRIGGER(){
if(vflag==1){
__asm__ volatile (
"ldr r0, =(0x4001080C) \n\t" //GPIOA base address is 0x40010800 and ODR offset is 0x0C
"ldr r1, =(1<<8) \n\t" //turn on PA8
"ldr r2, =0 \n\t" //turn off PA8
"str r1, [r0] \n\t" //turn on PA8
"str r2, [r0] \n\t" //turn off PA8
);
vflag = 0; //we set the vflag back to zero when were done outputing pixels.
}
}
I understand there's graphical defects/glitches and the code can be improved on but I'm trying to focus on how in theory this works. What I want to do is have a word display on screen, that word will be made up of letters, and those letters will be made up of groups of pixels. So then whats the best (or simplest) way to group pixels and execute them multiple times on-screen? Or how is this usually done?
回答1:
I do not code for STM32 so even the code looks foreign to me however it sounds like you are hard-coding the individual pixels with timer... and generating VGA signal by some GPIO. That combination of methods is problematic to use for programmable graphics.
I am using AVR32 (UC3A with much slower clock then yours) to doing VGA image using:
screen buffer (videoram)
simply I have entire screen image stored in MCU memory. So you can simply change it contents without changing the VGA output code ...
The problem is you need to have enough memory for the image (encoded in a way to enable direct transfer to VGA connector). I am using AVR32 with 16+32+32 KByte of RAM but most MCUs have much less RAM (static images can be stored in EPROM but then it would not be possible to change the image output). So in case you do not have enough either lower resolution to fit into memory or add external memory to your system.
use integrated HW peripherial for VGA signal generation
I have more versions of VGA signal generation working:
- SDRAM ... using SDRAM interface of the MCU
- SMC ... using SMC interface of the MCU
- SSC ... using synchronous serial interface
It all boils down to copying the screen buffer to IO interface at the VGA dot clock (~30MHz). Then on HW side combining the used MCU pins into VGA signals
for speed you can use DMA.
On top of all this you need to generate the sync signals and that is all.
So look at your MCU datasheet and find interface capable of synchronous transfer at least 3 bit data (R,G,B) with VGA dot clock speed. The closer the clock is to VGA dot clock the better (as some VGA monitors and LCDs do not tolerate too big difference)
the sync signals can be hardcoded or even encoded in the video ram.
The fastest is to use serial interface but the output is just B&W instead of RGB (unless you got 3 SSC units/channels) however you can send entire 8/16/32 pixels at once or by DMA directly so the MCU has time for other stuff and also requires much less VRAM.
My favourite is SDRAM interface (using just its data bus)
Here image from mine system:
Mine interconnection looks like this (using MCUs SDRAM interface version):
VGA <- AT32UC3A0512
R PX10 (EBI_D0)
G PX09 (EBI_D1)
B PX08 (EBI_D2)
Bright PX07 (EBI_D3)*
HS PA03
VS PA04
Here the relevant source:
VGA_EBI_SDRAMC.h:
//------------------------------------------------------------------------------------------------
#define _PA_VGA_HS 8
#define _PA_VGA_VS 16
#define _PAmo 24
volatile avr32_gpio_port_t *port_PA=&GPIO.port[AVR32_PIN_PA00>>5];
volatile U8 *SDRAM=(U8*)AVR32_EBI_CS0_ADDRESS;
//------------------------------------------------------------------------------------------------
//--- VGA 640x480x4 60Hz -------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
#define VRAM_xs 304
#define VRAM_ys 400
#define VRAM_bs 4
#define VRAM_ls (VRAM_xs>>1)
U8 VRAM[VRAM_ls*VRAM_ys];
U8 VRAM_empty[VRAM_ls];
// Horizontal timing [us]
#define VGA_t0 3
#define VGA_t1 5
#define VGA_t2 32
// Vertikal timing [lines ~31.817us] aby voslo viac bodov tak je to natiahnute na 32us++
#define VGA_ys 525
#define VGA_VS 2
#define VGA_y0 (36+40)
#define VGA_y1 (VGA_y0+VRAM_ys)
//------------------------------------------------------------------------------------------------
void VGA_init();
void VGA_screen();
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
void VGA_init()
{
system_init();
Disable_global_interrupt();
gpio_configure_pins(port_PA,_PAmo,GPIO_DIR_OUTPUT|GPIO_INIT_HIGH);
static const gpio_map_t EBI_GPIO_MAP[] =
{
{ AVR32_EBI_DATA_0_PIN, AVR32_EBI_DATA_0_FUNCTION},
{ AVR32_EBI_DATA_1_PIN, AVR32_EBI_DATA_1_FUNCTION},
{ AVR32_EBI_DATA_2_PIN, AVR32_EBI_DATA_2_FUNCTION},
{ AVR32_EBI_DATA_3_PIN, AVR32_EBI_DATA_3_FUNCTION},
};
gpio_enable_module(EBI_GPIO_MAP, sizeof(EBI_GPIO_MAP) / sizeof(EBI_GPIO_MAP[0]));
AVR32_SDRAMC.mr=0; // normal mode
AVR32_SDRAMC.tr=0; // no refresh (T=0)
AVR32_SDRAMC.cr=
(AVR32_SDRAMC_CR_NC_11_COLUMN_BITS <<AVR32_SDRAMC_CR_NC_OFFSET)
|(AVR32_SDRAMC_CR_NR_13_ROW_BITS <<AVR32_SDRAMC_CR_NR_OFFSET)
|(AVR32_SDRAMC_CR_NB_TWO_BANKS <<AVR32_SDRAMC_CR_NB_OFFSET)
|(AVR32_SDRAMC_CAS_ONE_CYCLE <<AVR32_SDRAMC_CR_CAS_OFFSET)
|(AVR32_SDRAMC_DBW_16_BITS <<AVR32_SDRAMC_CR_DBW_OFFSET)
|(0 <<AVR32_SDRAMC_TWR_OFFSET)
|(0 <<AVR32_SDRAMC_TRC_OFFSET)
|(0 <<AVR32_SDRAMC_TRP_OFFSET)
|(0 <<AVR32_SDRAMC_TRCD_OFFSET)
|(0 <<AVR32_SDRAMC_TRAS_OFFSET)
|(0 <<AVR32_SDRAMC_TXSR_OFFSET);
AVR32_SDRAMC.hsr=AVR32_SDRAMC_HSR_DA_MASK;
AVR32_SDRAMC.mdr=AVR32_SDRAMC_MDR_MD_SDRAM;
// map SDRAM CS -> memory space
AVR32_HMATRIX.sfr[AVR32_EBI_HMATRIX_NR]|=1<<AVR32_EBI_SDRAM_CS;
AVR32_HMATRIX.sfr[AVR32_EBI_HMATRIX_NR];
U32 a;
for (a=0;a<VRAM_ls*VRAM_ys;a++) VRAM[a]=0;
for (a=0;a<VRAM_ls;a++) VRAM_empty[a]=0;
}
//------------------------------------------------------------------------------------------------
void VGA_screen()
{
U32 a,x,y,c,PA,t0;
wait_start(t0);
for (;;)
{
for (PA=_PAmo,a=0,y=0;y<VGA_ys;y++)
{
wait_start(t0);
if (y== 0) PA^=_PA_VGA_VS; else PA^=0; // VS on
if (y==VGA_VS) PA^=_PA_VGA_VS; else PA^=0; // VS off
PA^=_PA_VGA_HS; // HS on
port_PA->ovrc=PA^_PAmo;
port_PA->ovrs=PA;
wait_us(t0,VGA_t0);
PA^=_PA_VGA_HS; // HS off
port_PA->ovrc=PA^_PAmo;
port_PA->ovrs=PA;
wait_us(t0,VGA_t1);
*SDRAM=0; // blank (black)
if ((y>=VGA_y0)&&(y<VGA_y1))
for (x=0;x<VRAM_ls;x++)
{
c=VRAM[a];
*SDRAM=c>>4; // write pixel into SDRAM interface (address is ignored as I use only data bus pins)
a++;
*SDRAM=c; // write pixel into SDRAM interface (address is ignored as I use only data bus pins)
}
*SDRAM=0; // blank (black)
wait_us(t0,VGA_t2);
}
}
}
//------------------------------------------------------------------------------------------------
Main.cpp:
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
#include "System\include.h"
#include "pic_zilog_inside.h"
//#include "VGA_EBI_SMC.h"
#include "VGA_EBI_SDRAMC.h"
//#include "VGA_SSC.h"
//------------------------------------------------------------------------------------------------
void pic_copy(U8 *dst,U32 dst_xs,U32 dst_ys,U32 dst_bs,U8 *src,U32 src_xs,U32 src_ys,U32 src_bs)
{
U32 x0,y0,a0,l0;
U32 x1,y1,a1,l1;
U32 a; U8 c,m;
l0=1; l1=1;
if (dst_bs==1) l0=dst_xs>>3;
if (dst_bs==2) l0=dst_xs>>2;
if (dst_bs==4) l0=dst_xs>>1;
if (dst_bs==8) l0=dst_xs;
if (src_bs==1) l1=src_xs>>3;
if (src_bs==2) l1=src_xs>>2;
if (src_bs==4) l1=src_xs>>1;
if (src_bs==8) l1=src_xs;
for (a0=0;a0<dst_ys*l0;a0++) dst[a0]=0;
for (y0=0;y0<dst_ys;y0++)
{
y1=(y0*(src_ys-1))/(dst_ys-1);
a0=l0*y0;
a1=l1*y1;
for (x0=0;x0<dst_xs;x0++)
{
x1=(x0*(src_xs-1))/(dst_xs-1);
c=0;
if (src_bs==1)
{
c=src[a1+(x1>>3)];
c>>=7-(x1&7);
c&=1;
}
if (src_bs==4)
{
c=src[a1+(x1>>1)];
if (U32(x0&1)==0) c>>=4;
c&=15;
}
if (dst_bs==1)
{
c<<=7-(x0&7);
a=a0+(x0>>3);
dst[a]|=c; if (!c) dst[a]^=c;
}
if (dst_bs==4)
{
if (c) c=15;
if (U32(x0&1)==0) { c<<=4; m=0x0F; } else m=0xF0;
a=a0+(x0>>1);
dst[a]&=m;
dst[a]|=c;
}
}
}
}
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
int main(void)
{
VGA_init();
pic_copy
(
(U8*)VRAM,
VRAM_xs,
VRAM_ys,
VRAM_bs,
(U8*)pic_zilog_inside,
pic_zilog_inside_xs,
pic_zilog_inside_ys,
pic_zilog_inside_bs
);
VGA_screen();
}
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
pic_zilog_inside.h:
const U32 pic_zilog_inside_xs=640;
const U32 pic_zilog_inside_ys=480;
const U32 pic_zilog_inside_bs=1;
const U32 pic_zilog_inside[]= // hard coded image
{
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
...
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
};
The function pic_copy
just copies hard-coded image into VRAM.
The function VGA_screen()
creates the VGA signal in endless loop so other tasks must be encoded into ISRs or hard coded into pause code or between individual frames (however this is really demanding on mine setup as I got small MCU clock so there is not much room for other stuff to do). The VRAM is encoded in 16 colors (4 bits per pixel)
8 4 2 1
Brightness B G R
The brightness should just adds some voltage to R,G,B with few resistors and diodes but newer implemented it on HW side instead I have this circuit (8 colors only):
The diodes must be fast with the same barrier voltage and capacitors are 1nF. Its to avoid glitching of the image due to used interface data bus timing. Also the diodes are needed for the brightness if added in future for more info see the R2R current problem in here:
- Generating square wave in AVR Assembly without PWM
来源:https://stackoverflow.com/questions/59886239/vga-pixel-grouping-on-stm32