Posts Tagged interrupt

More on the vsync interrupts

I had a thought after my previous disheartening attempt to get the vsync working: since they might have stuffed up timer 11 in ChibiOS, I might change to timer 4.  One massive advantage is that timers 2, 3 and 4 have 4 compare registers, instead of one!  This means I can use one to turn off the vsync pulse, and one to trigger the DMA interrupt and adjust the sync timings on the next line.  Changing everything to use TIM4 was fairly straightforward, the only tricky part being changing the single compare register to use compare register 4, and enabling APB1 instead of APB2 since that’s where timer 4 is.  (I originally used register 1, but that is connected to PORTB6 which is attached to the blue LED.)

There was one catch though when compiling:

$ make
Compiling main.c
Linking build/ch.elf
build/obj/main.o: In function `VectorB8':
/home/damien/projects/stm32/video/main.c:8: multiple definition of `VectorB8'
build/obj/pwm_lld.o:/opt/ChibiOS_2.4.2/os/hal/platforms/STM32/pwm_lld.c:221: first defined here

Looking in pwm_lld.c suggests that I need to unset STM32_PWM_USE_TIM4.  I notice that one example has another configuration file: /demos/ARMCM3-STM32L152-DISCOVERY/mcuconf.h, which declares this symbol.  I copied this file to my project directory, hoping the makefile would use it in preference.  Now there were no timers available for ChibiOS, its PWM module made the compiler complain because it had no timers to use, so I had to set HAL_USE_PWM to FALSE in halconf.h.

The good news: my interrupt is being called! I connected the debugger and used the “p” command to show the contents of “line”.  The bad news: my PB7 light isn’t blinking, which means the ChibiOS main thread isn’t working.  Maybe the interrupt is using all of the CPU?  It shouldn’t, since I thought each line took about 1000 cycles to run, and the interrupt shouldn’t be using more than about 50.

I remember seeing somewhere that ARMs don’t reset their interrupt flags automatically, like AVRs do.  If this is the case, my interrupt will return, and the NVIC (the “nested vectored interrupt controller”, apparently) will see the flag is still set, and call the interrupt again.  The interrupt handler in ChibiOS’ pal_lld.c contains this line, which would clear this flag:

STM32_TIM1->SR = ~TIM_SR_UIF;

In my case, this would be:

TIM4->SR &= ~TIM_SR_UIF;

and my light blinks again, so that seems to have worked!  I checked with gdb that the “line” variable is still incrementing.

I’ll try setting the PWM duration registers during the interrupt, so my interrupt handler looks like this:

    if (line & 1) {
        TIM4->ARR = STM32_SYSCLK * 0.0001;   // horizontal line duration
        TIM4->CCR4 = STM32_SYSCLK * 0.00009; // hsync pulse duration
    } else {
        TIM4->ARR = STM32_SYSCLK * 0.000064;   // horizontal line duration
        TIM4->CCR4 = STM32_SYSCLK * 0.0000047; // hsync pulse duration
    }

That seems to have worked fine.  That’s about everything I need to generate the vsync signals.  I’ve done something slightly wrong though – I’d be better off using one of the compare registers to trigger the interrupt instead.  In the interrupt handler, I’d initiate a DMA transfer for the current line, then set the timing registers for the next line.

Now my code looks like this (maybe I should start putting it on Github):

#include "ch.h"
#include "hal.h"
#include "stm32l1xx.h"

volatile int line;

CH_IRQ_HANDLER(TIM4_IRQHandler) {
    TIM4->SR &= ~TIM_SR_UIF;
    ++line;

    if (line & 1) {
        TIM4->ARR = STM32_SYSCLK * 0.0001;   // horizontal line duration
        TIM4->CCR4 = STM32_SYSCLK * 0.00009; // hsync pulse duration
    } else {
        TIM4->ARR = STM32_SYSCLK * 0.000064;   // horizontal line duration
        TIM4->CCR4 = STM32_SYSCLK * 0.0000047; // hsync pulse duration
    }
}

int main(void) {
  halInit();
  chSysInit();

  rccEnableAPB1(RCC_APB1ENR_TIM4EN, 0); // Enable TIM4 clock, run at SYSCLK

  nvicEnableVector(TIM4_IRQn, CORTEX_PRIORITY_MASK(7));

  // TIM11 outputs on PB6
  GPIOB->OTYPER &= ~GPIO_OTYPER_OT_9;        // Push-pull output
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9; // 40MHz
  GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR9;
  GPIOB->PUPDR |= GPIO_PUPDR_PUPDR9_0;      // Pull-up
  GPIOB->MODER &= ~GPIO_MODER_MODER9;
  GPIOB->MODER |= GPIO_MODER_MODER9_1; // alternate function on pin B9

  // Reassign port B9
  GPIOB->AFRH &= ~GPIO_AFRH_AFRH9;
  GPIOB->AFRH |= 0x2 << 4; // ChibiOS doesn't seem to have constants for these   TIM4->CR1 |= TIM_CR1_ARPE; // buffer ARR, needed for PWM (?)
  TIM4->CCMR2 &= ~(TIM_CCMR2_CC4S); // configure output pin
  TIM4->CCMR2 =
          TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 /*| TIM_CCMR1_OC1M_0*/  // output high on compare match
          | TIM_CCMR2_OC4PE; // preload enable
  TIM4->CCER = TIM_CCER_CC4P // active low output
           | TIM_CCER_CC4E; // enable output
  TIM4->DIER = TIM_DIER_UIE; // enable interrupt on "update" (ie. overflow)
  TIM4->ARR = STM32_SYSCLK * 0.000064;   // horizontal line duration
  TIM4->CCR4 = STM32_SYSCLK * 0.0000047; // hsync pulse duration

  TIM4->CR1 |= TIM_CR1_CEN; // enable the counter

  palSetPadMode(GPIOB, 7, PAL_MODE_OUTPUT_PUSHPULL);
  while (1) {
    palSetPad(GPIOB, 7);
    chThdSleepMilliseconds(500);
    palClearPad(GPIOB, 7);
    chThdSleepMilliseconds(500);
  }
}

Next I’ll try using DMA, which I’ve never used before.  With any luck I’ll be able to use ChibiOS to do this.

Advertisements

Leave a Comment

More timing video signals on the STM32L Discovery

I’ve looked how ChibiOS does its timing, and worked out that it’s unsuitable for timing video signals.  Now I’ll look at the using the timers directly.

The chip has a number of timers, I can’t work out how many.  The ChibiOS HAL manual says it can use timers 2, 3 and 4, so let’s leave those alone for other uses.  That leaves timers 9, 10 and 11.

If the timers work like the AVR’s timers, they work by starting at 0 and counting up to some maximum value, where the counter is reset to 0.  There’s also a compare register, and when the timer matches the compare register, something can happen – we can trigger an interrupt, change the state of a pin and so on.  Being able to change a pin is how PWM works.

It would be nice to use PWM to produce the sync signals.  I’ve found an excellent description of these signals, and at the start of each line, there’s always a falling signal.  So if we set the timer’s maximum value to the end of the line, and have the signal go low when it overflows, that’s the signal start taken care of.

The signal end is a bit trickier.  The image shows that it varies depending on the line; further, on some of the vertical sync lines it happens twice!  This means that we might need to change the value at which the signal goes high as the timer is running.  The AVR can do this, but in some modes the timer register is double-buffered.  If you write a new value to the timer compare register, it’s only applied the next time the timer resets.  They do this so you don’t set a compare value lower than the current register, which means the timer will keep counting up until it overflows!

So can you adjust the compare registers on the fly on the STM32L, and is it double buffered?  It looks like the compare register is called TIMx_CCR1.  There doesn’t seem to be a CCR2, so maybe these timers only have one output.  In the reference manual, section 17.6.11 says:

It is loaded permanently if the preload feature is not selected in the TIMx_CCMR1 register (bit OC1PE). Else the preload value is copied in the active capture/compare 1 register when an update event occurs.

So if the preload feature is off, the compare register can be updated straight away!  But back in the PWM mode description (section 17.4.9), it says:

You must enable the corresponding preload register by setting the OCxPE bit in the TIMx_CCMRx register

So we’re not so lucky.  We need to use the preload register, and we know it updates on an “update event”.  What’s an update event? Back in section 17.4.1:

The update event is sent when the counter reaches the overflow and if the UDIS bit equals 0 in the TIMx_CR1 register.

So all we need to do is update the compare register one line early!

What about the vertical sync lines, where there are two pulses?  That shouldn’t be a problem; we simply consider them two separate lines in software, so the lines are numbered like this:

This also shows that the maximum value will need to be changed in the same way.  In the reference manual, they call this value the “auto-reload” value, and it’s kept in the TIMx_ARR register.  Section 17.4.1 suggests you can choose whether this is double-buffered or not.  We might as well use this feature since the compare register needs it.

There’s one more thing to look at.  At the start of each line, I’ll need to start transferring data from memory to an external port using DMA, and configure the compare and maybe the maximum value register for the next line.  I could either do both in a single interrupt, or set the registers on the reset interrupt, and start the DMA on the compare interrupt.

Do we have enough time from the start of the interrupt to do anything useful? The horizontal sync pulse on a PAL signal is 4.7μs.  If we assume the CPU runs at 16MHz, this is about 75 instructions.  Section 5.5.1 of the ARM manual suggests that it takes 12 cycles to enter an interrupt.  In the AVR, it’s up to the programmer to save the register state at the start of an interrupt.  This means it’s a good idea to do as little as possible in an interrupt, because the compiler inserts lots of “push” and “pop” instructions around the interrupt.  Since the ARM looks after this for you, and takes a fixed amount of time to enter an interrupt, this isn’t a problem.  If we assume it takes 12 cycles to leave an interrupt too, that leaves about 50 cycles to do stuff.  This stuff is working out how long it should take to raise the signal again, and set the compare register and maybe the reset register.

That should be all we need to know about the timers – now I’ll try to use the timer to produce these horizontal sync pulses.

Leave a Comment

Timing video signals from the STM32L Discovery

In my last post, I suggested using ChibiOS to produce video signals from the STM32L Discovery.

The configuration for ChibiOS is held in chconf.h.  An interesting section is this one:

/**
 * @brief   System tick frequency.
 * @details Frequency of the system timer that drives the system ticks. This
 *          setting also defines the system tick time unit.
 */
#if !defined(CH_FREQUENCY) || defined(__DOXYGEN__)
#define CH_FREQUENCY                    1000
#endif

It looks like the operating system wakes up periodically, checking whether there’s anything to do.  It also means that to produce video signals, this number may not be accurate enough.

So what is this number used for?  The only interesting reference I can find is in os/hal/platforms/STM32L1xx/hal_lld.c:

SysTick->LOAD = STM32_HCLK / CH_FREQUENCY - 1;

and of course that’s where the system ticks are initialized.  STM32_HCLK looks interesting, there’s plenty of references to this in os/hal/platforms/STM32F4xx/hal_lld.h:

/**
* @brief   AHB frequency.
*/
#if (STM32_HPRE == STM32_HPRE_DIV1) || defined(__DOXYGEN__)
#define STM32_HCLK                  (STM32_SYSCLK / 1)
#elif STM32_HPRE == STM32_HPRE_DIV2
#define STM32_HCLK                  (STM32_SYSCLK / 2)
...

I remember seeing the text AHB before, this is the bus that connects the CPU to the GPIO ports, and other peripherals.  This code suggests that it’s related to the CPU clock via a prescaler, which the clock tree in the reference manual confirms.  This led me here:

/**
* @brief   System clock source.
*/
#if STM32_NO_INIT || defined(__DOXYGEN__)
#define STM32_SYSCLK                STM32_HSICLK
#elif (STM32_SW == STM32_SW_HSI)
#define STM32_SYSCLK                STM32_HSICLK
...

so STM32_SYSCLK is the system clock, and we can choose the source for this.  “HSI” would be the High Speed Internal clock, which is fixed at 16MHz.  It’s possible to use the PLL to run the CPU at 32MHz too.

So working backwards, with the default setting, STM32_HCLK is 16MHz, and ChibiOS’ default tick is 1000 cycles, which is 62.5μs.  For PAL, the sync pulse length is 4.7µs, and the front porch is much shorter than that, so the ChibiOS timer is far too inaccurate for that.  I could change the system tick to 100, but there’s the risk that ChibiOS won’t have enough time to do its scheduling after it wakes up, and that number still isn’t accurate enough.

While I’m rummaging around the ChibiOS code, what does it use to trigger its scheduler?  It never seems to be read anywhere, but looking at SysTick_Type in os/ports/common/ARMCMx/CMSIS/include/core_cm3.h it looks like some part of the address space, specifically at address 0xE000E010.  The datasheet says this is part of the “Cortex-M3 Internal Peripherals”, but that’s all it says.  The CPU manual might be more helpful here, and it says this is part of the “System Control Space”.  Section 3.1.1 says this address contains the “SysTick Control and Status Register”, and the following registers correspond to the SysTick variable in ChibiOS.

So what is the SysTick for?  Section 5.2 suggests that an interrupt can be triggered on the SysTick firing, which might be what ChibiOS uses for its scheduling.  (WordPress didn’t save my draft from here, so I might be missing a few steps.)  So where is the handler for this?

Earler I found that each program starts with an interrupt table.  The example there has 4 entries, but it can be longer.  The linker script (os/ports/GCC/ARMCMx/STM32L1xx/ld/STM32L152xB.ld) contains a section called “vectors”, which is defined in os/ports/GCC/ARMCMx/STM32L1xx/vectors.c.  The SysTick handler is called SysTickVector, which looks like this (from os/ports/GCC/ARMCMx/chcore_v7m.c; I don’t know whether this is an arm6 or an arm7):

CH_IRQ_HANDLER(SysTickVector) {

  CH_IRQ_PROLOGUE();

  chSysLockFromIsr();
  chSysTimerHandlerI();
  chSysUnlockFromIsr();

  CH_IRQ_EPILOGUE();
}

So this is how the SysTick facility works.  Now this can’t be used for generating the video signals, since it’s not accurate enough – I’d need to use another timer for that.  The timer interrupt would need to be of higher priority than SysTick, otherwise the CPU might be doing something else which would make the image jump around.  The ChibiOS docs suggest that interrupt handlers are like a special thread with higher priority than everything else, which is what I want.

All of this suggests that ARMs are a lot trickier than 8-bit CPUs, because of all of the available features.  I don’t think I’ve even found all of the relevant documentation – with the AVR, one document contains everything you need to know.

Next I’ll look at how the timers work.

Leave a Comment