Skip to main content
2 of 2
added 83 characters in body

STM32 VGA Output - don't understand why lines are jagged

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: enter image description here

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();
}