I am using "blue-pill" STM32F103C8 (http://wiki.stm32duino.com/index.php?title=Blue_Pill) to create VGA output. I am connecting PB6 to VSync, PB0 to HSync, and PA0 to red using 278 Ohm resistor.
I don't understand why the lines are "jagged", like in the picture:
I would appreciate any input.
By the way, my real code is more complex and can display 256x192 with 64 colors. Since this board has 20K memory, I am using separate areas for pixels and colors, like Sinclair ZX (https://en.wikipedia.org/wiki/ZX_Spectrum_graphic_modes).
This is my code:
// interrupt handlers
void EndVBackPorch();
void ShockAbsorber();
volatile uint8 *GPIO_ODR;
volatile int vflag = 0; /* When 1, can draw on the screen */
void setup()
{
pinMode(PB6, PWM); // VSync
pinMode(PB0, PWM); // HSync
// PA0..PA5 set to OUTPUT with max speed 50 MHz
GPIOA->regs->CRL = 0x88333333;
GPIO_ODR = (volatile uint8_t *)&GPIOA->regs->ODR;
// 640 x 480 @ 60 Hz (Standard, pixel clock frequency 25.17 MHz)
//InitHSync(false, 2287, 275, 400);
//InitVSync(false, 525, 2, 35 + 48);
// 640 x 480 @ 60 Hz (non-standard, pixel clock frequency 24.0 MHz)
InitHSync(true, 2376, 264, 370);
InitVSync(true, 505, 5, 10 + 74);
}
void loop()
{
// Everything happens in the interrupts
}
void InitVSync(
bool isNegative,
int wholeFrame,
int syncPulse,
int startDraw)
{
HardwareTimer timerVSync(4);
timerVSync.pause();
timerVSync.setPrescaleFactor(1);
timerVSync.setOverflow(wholeFrame); /* Vertical lines */
// VSync on pin PB6
timer_oc_set_mode(timerVSync.c_dev(), TIMER_CH1,
isNegative ? TIMER_OC_MODE_PWM_2 : TIMER_OC_MODE_PWM_1, TIMER_OC_PE);
timerVSync.setCompare(TIMER_CH1, syncPulse);
timerVSync.setChannelMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
timerVSync.setCompare(TIMER_CH4, startDraw);
timerVSync.attachInterrupt(TIMER_CH4, EndVBackPorch);
// Slave mode Gated, triggered by TIM3
timerVSync.c_dev()->regs.adv->SMCR &= (uint16_t) ~((uint16_t)TIMER_SMCR_SMS);
timerVSync.c_dev()->regs.adv->SMCR |= ((uint16_t)TIMER_SMCR_SMS_GATED);
timerVSync.c_dev()->regs.adv->SMCR &= (uint16_t) ~((uint16_t)TIMER_SMCR_TS);
timerVSync.c_dev()->regs.adv->SMCR |= (uint16_t)TIMER_SMCR_TS_ITR2;
//nvic_irq_set_priority(NVIC_TIMER4, 1);
timerVSync.refresh();
timerVSync.resume();
}
void InitHSync(
bool isNegative,
int wholeLine,
int syncPulse,
int startDraw)
{
HardwareTimer timerShockAbsorber(2);
timerShockAbsorber.pause();
timerShockAbsorber.setPrescaleFactor(1);
timerShockAbsorber.setOverflow(wholeLine);
timerShockAbsorber.setChannelMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
timerShockAbsorber.setCompare(TIMER_CH1, 0);
timerShockAbsorber.setChannelMode(TIMER_CH2, TIMER_OUTPUT_COMPARE);
timerShockAbsorber.setCompare(TIMER_CH2, startDraw - 12);
timerShockAbsorber.attachInterrupt(TIMER_CH2, ShockAbsorber);
// Master mode, TIM_TRGOSource_Enable
bitSet(timerShockAbsorber.c_dev()->regs.adv->SMCR, TIMER_SMCR_MSM_BIT);
timerShockAbsorber.setMasterModeTrGo(TIMER_CR2_MMS_ENABLE);
timerShockAbsorber.refresh();
HardwareTimer timerHSync(3);
timerHSync.pause();
timerHSync.setPrescaleFactor(1);
timerHSync.setOverflow(wholeLine);
// HSYNC on pin PB0
timer_oc_set_mode(timerHSync.c_dev(), TIMER_CH3,
isNegative ? TIMER_OC_MODE_PWM_2 : TIMER_OC_MODE_PWM_1, TIMER_OC_PE);
timerHSync.setCompare(TIMER_CH3, syncPulse);
timerHSync.setChannelMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
timerHSync.setCompare(TIMER_CH4, startDraw);
timerHSync.attachInterrupt(TIMER_CH4, Draw);
// Slave mode Trigger, triggered by TIM2
timerHSync.c_dev()->regs.adv->SMCR &= (uint16_t) ~((uint16_t)TIMER_SMCR_SMS);
timerHSync.c_dev()->regs.adv->SMCR |= ((uint16_t)TIMER_SMCR_SMS_TRIGGER);
timerHSync.c_dev()->regs.adv->SMCR &= (uint16_t) ~((uint16_t)TIMER_SMCR_TS);
timerHSync.c_dev()->regs.adv->SMCR |= (uint16_t)TIMER_SMCR_TS_ITR1;
// Master mode, TIM_TRGOSource_Update
bitSet(timerHSync.c_dev()->regs.adv->SMCR, TIMER_SMCR_MSM_BIT);
timerHSync.setMasterModeTrGo(TIMER_CR2_MMS_UPDATE);
nvic_irq_set_priority(NVIC_TIMER3, 0);
timerHSync.refresh();
// Starts timerHSync as well
timerShockAbsorber.resume();
}
void EndVBackPorch()
{
vflag = 1;
}
void ShockAbsorber()
{
// Wait for interrupt
__asm__ volatile("wfi \n\t" :::);
}
void Draw()
{
if (!vflag)
{
return;
}
noInterrupts();
__asm__ volatile(
" mov r0, #3 \n\t"
" nop \n\t"
" nop \n\t"
" nop \n\t"
" strb r0, [%[odr]] \n\t"
" mov r0, #0 \n\t"
" nop \n\t"
" nop \n\t"
" nop \n\t"
" strb r0, [%[odr]] \n\t"
" mov r0, #3 \n\t"
" nop \n\t"
" nop \n\t"
" nop \n\t"
" strb r0, [%[odr]] \n\t"
" mov r0, #0 \n\t"
" nop \n\t"
" nop \n\t"
" nop \n\t"
" strb r0, [%[odr]] \n\t"
" mov r0, #3 \n\t"
" nop \n\t"
" nop \n\t"
" nop \n\t"
" strb r0, [%[odr]] \n\t"
" mov r0, #0 \n\t"
" nop \n\t"
" nop \n\t"
" nop \n\t"
" strb r0, [%[odr]] \n\t"
:
: [odr] "r"(GPIO_ODR)
: "r0");
interrupts();
}