0

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();
}
1
  • You haven't said which Arduino you are using, which would help inform the answers.
    – Nick Gammon
    Commented Apr 21, 2018 at 6:40

1 Answer 1

3

I found a similar issue when I developed code to display on a VGA monitor.

The reason for the slight jitter is that when an interrupt fires, it can't stop in the middle of an instruction. So there are potentially one or two extra clock cycles before the interrupt is processed.

Those clock cycles would account for the slight jaggedness on the right. The way to avoid that is to go to sleep between horizontal lines (idle sleep mode). This wakes up instantly, but the important thing is it wakes up at the start of an instruction cycle, so you don't get a one or two cycle delay.

I can't see any sleeping in your code, and I'm not sure if your STM32F103C8 would compensate for that, but certainly it is worth trying.

I can't see a "main" loop, presumably it doesn't do much until an interrupt fires.

My main loop is this:

void loop()
  {
  // sleep to ensure we start up in a predictable way
  sleep_mode ();
  doOneScanLine ();
 }  // end of loop

Note the sleep which ensures that it starts the drawing at the same time each time (of course the interrupt will take time, but at least it takes the same time every scan line).

4
  • Thank you for your quick response! I will try your idea as soon as I get home. >>I can't see any sleeping in your code, and I'm not sure if your STM32F103C8 would compensate for that, but certainly it is worth trying<< I am using "shock absorber" idea (on NVIC_TIMER3) as described here: cliffle.com/article/2015/06/11/matrix. >> I can't see a "main" loop, presumably it doesn't do much until an interrupt fires. << Sorry, missed it, it actually doesn't do anything Commented Apr 20, 2018 at 13:51
  • It must do something, or nothing would execute. Does it call some other function which loops? The main loop (or should I say, main function?) is where code starts executing.
    – Nick Gammon
    Commented Apr 21, 2018 at 6:37
  • I updated the source, added empty loop. It is empty because everything happens in the interrupts. There are 3 interrupts: EndVBackPorch - updates "vertical" flag; ShockAbsorber - happens a little before Draw, its goal to reduce random delays; and Draw - that draws horizontal line (in this example, just 6 "dots"). Each "dot" is actually at least 2 real "dots", however my resolution is 256, and real resolution is 640, so it is OK. Commented Apr 22, 2018 at 2:01
  • The objective is to be sleeping before the interrupt occurs so that the interrupt wakes in a predictable way.
    – Nick Gammon
    Commented Apr 22, 2018 at 3:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.