Выход VGA STM32 — не понимаю, почему линии неровные
Я использую микроконтроллер 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();
}
@Andrey Belykh, 👍0
1 ответ
Я столкнулся с аналогичной проблемой, когда разрабатывал код для отображения на мониторе VGA.
Причина небольшого дрожания заключается в том, что прерывание, срабатывающее при срабатывании, не может остановиться в середине инструкции. Поэтому до обработки прерывания может пройти один или два дополнительных такта.
Эти такты объясняют небольшую неровность справа. Чтобы избежать этого, нужно перейти в спящий режим между горизонтальными линиями (режим ожидания). Это приводит к мгновенному пробуждению, но, что важно, пробуждение происходит в начале цикла инструкции, поэтому задержка в один или два такта не возникает.
Я не вижу никакого сна в вашем коде, и не уверен, что ваш STM32F103C8 сможет это компенсировать, но определенно стоит попробовать.
Я не вижу «основного» цикла, вероятно, он не выполняет никаких действий, пока не сработает прерывание.
Мой основной цикл такой:
void loop()
{
// спать, чтобы обеспечить предсказуемый старт
sleep_mode ();
doOneScanLine ();
} // конец цикла
Обратите внимание на сон, который гарантирует, что отрисовка начнется каждый раз в одно и то же время (конечно, прерывание займет время, но, по крайней мере, на каждую строку сканирования уходит одинаковое время).
- Установите частоту ШИМ на 25 кГц.
- Какова частота PWM-выхода на Arduino
- Управление скоростью вентилятора с помощью библиотеки Arduino PID
- Как устранить шум от вентилятора 12 В с ШИМ-управлением на низкой скорости
- Генерация частоты ШИМ выше 125 кГц с помощью Arduino Uno
- Увеличить разрядность PWM
- Как вывести истинное аналоговое напряжение на выходной контакт
- PWM-вывод Arduino Nano не функционирует