Выход VGA STM32 — не понимаю, почему линии неровные

pwm

Я использую микроконтроллер STM32F103C8 (http://wiki.stm32duino.com/index.php?title=Blue_Pill) для создания VGA-выхода. PB6 подключается к VSync, PB0 — к HSync, а PA0 — к красному проводу через резистор 278 Ом.

Я не понимаю, почему линии «рваные», как на картинке:

Я буду признателен за любые мнения.

Кстати, мой реальный код сложнее и может отображать 256x192 с 64 цветами. Поскольку у этой платы 20 КБ памяти, я использую отдельные области для пикселей и цветов, как у Sinclair ZX (https://en.wikipedia.org/wiki/ZX_Spectrum_graphic_modes).

Это мой код:

// обработчики прерываний
void EndVBackPorch();
void ShockAbsorber();

volatile uint8 *GPIO_ODR;
volatile int vflag = 0; /* При 1 можно рисовать на экране */


void setup()
{
  pinMode(PB6, PWM); // VSync
  pinMode(PB0, PWM); // HSync

  // PA0..PA5 настроен на ВЫХОД с максимальной скоростью 50 МГц
  GPIOA->regs->CRL = 0x88333333;
  GPIO_ODR = (volatile uint8_t *)&GPIOA->regs->ODR;

  // 640 x 480 @ 60 Гц (стандарт, тактовая частота пикселей 25,17 МГц)
  //InitHSync(false, 2287, 275, 400);
  //InitVSync(false, 525, 2, 35 + 48);

  // 640 x 480 @ 60 Гц (нестандартно, тактовая частота пикселей 24,0 МГц)
  InitHSync(true, 2376, 264, 370);
  InitVSync(true, 505, 5, 10 + 74);
}

void loop() 
{
    // Все происходит в прерываниях
}

void InitVSync(
  bool isNegative,
  int wholeFrame,
  int syncPulse,
  int startDraw)
{
  HardwareTimer timerVSync(4);

  timerVSync.pause();
  timerVSync.setPrescaleFactor(1);
  timerVSync.setOverflow(wholeFrame); /* Vertical lines */

  // VSync на выводе 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);

  // Ведомый режим: управляемый, запускается 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);

  // Основной режим, 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 на выводе 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);

  // Триггер ведомого режима, запускается 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;

  // Основной режим, 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();

  // Также запускает timerHSync
  timerShockAbsorber.resume();
}

void EndVBackPorch()
{
  vflag = 1;
}

void ShockAbsorber()
{
  // Ожидание прерывания
  __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();
}

, 👍0


1 ответ


3

Я столкнулся с аналогичной проблемой, когда разрабатывал код для отображения на мониторе VGA.

Причина небольшого дрожания заключается в том, что прерывание, срабатывающее при срабатывании, не может остановиться в середине инструкции. Поэтому до обработки прерывания может пройти один или два дополнительных такта.

Эти такты объясняют небольшую неровность справа. Чтобы избежать этого, нужно перейти в спящий режим между горизонтальными линиями (режим ожидания). Это приводит к мгновенному пробуждению, но, что важно, пробуждение происходит в начале цикла инструкции, поэтому задержка в один или два такта не возникает.

Я не вижу никакого сна в вашем коде, и не уверен, что ваш STM32F103C8 сможет это компенсировать, но определенно стоит попробовать.

Я не вижу «основного» цикла, вероятно, он не выполняет никаких действий, пока не сработает прерывание.

Мой основной цикл такой:

void loop()
  {
  // спать, чтобы обеспечить предсказуемый старт
  sleep_mode ();
  doOneScanLine ();
 }  // конец цикла

Обратите внимание на сон, который гарантирует, что отрисовка начнется каждый раз в одно и то же время (конечно, прерывание займет время, но, по крайней мере, на каждую строку сканирования уходит одинаковое время).

,