Posts Tagged timer

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

Starting on the vsync interrupts

Now it’s time to adjust the sync signals to get vsync working, so I need an interrupt when the signal goes low so I can adjust the sync pulse on the next cycle.  I have no idea how to start the interrupts.

I did a search in the ChibiOS code for the term “vect”, and found a bunch of them in hal/platforms/STM32L1xx/hal_lld.h, of which TIM11_IRQHandler looks the most appropriate.  But there seems to be only one vector all possible events, like an overflow or a compare match.

It looks like “fast” interrupt handlers look like this:

volatile int line;
CH_IRQ_HANDLER(TIM11_IRQHandler) {
  ++line;
}

It compiles at least… but did it register as an interrupt handler?  I tried the trick with AVRs that shows disassembled code:

arm-none-eabi-objdump -S -h build/ch.elf

It shows that the first section starts at address 0x08000100… just after the vector table, which should appear at the start! Right at the top though, there’s a “startup” section at the correct address. After stuffing around with various objdump options, this showed me the vector table:

arm-none-eabi-objdump -h -s --special-syms build/ch.elf

Table 34 of the reference manual lists all of the interrupts, and TIM11 is at offset 0xAC. Objdump doesn’t show anything promising in this location!  But there’s something strange in hal_lld.h:

#define TIM9_IRQHandler         VectorA0    /**< TIM9.                      */
#define TIM10_IRQHandler        VectorA4    /**< TIM10.                     */
#define TIM11_IRQHandler        VectorA8    /**< TIM11.                     */
#define LCD_IRQHandler          VectorAC    /**< LCD.                       */

The datasheet though says that the LCD is vector A0, TIM9 is A4, 10 is A8 and 11 is AC.  I guess the way to find out is to try both and see what happens.

First the timer needs to be configured to use those interrupts.

  TIM11->DIER = TIM_DIER_UIE; // enable interrupt on "update" (ie. overflow)

So does this do anything? I used the debugger to find out:

$ arm-none-eabi-gdb build/ch.elf 
....
Reading symbols from /home/damien/projects/stm32/video/build/ch.elf...done.
(gdb) p lineA8
$1 = 0
(gdb) p lineAC
$2 = 0
(gdb) cont
^C
Program received signal SIGINT, Interrupt.
0x08000598 in _idle_thread (p=)
    at /opt/ChibiOS_2.4.2/os/kernel/src/chsys.c:62
62	  chRegSetThreadName("idle");
(gdb) p lineAC
$3 = 0
(gdb) p lineA8
$4 = 0
(gdb)

So it’s doing nothing!  Why not? I looked through the ChibiOS sources where the timers are configured, and found a call to nvicEnableVector, which looks promising.  It needs a “priority” parameter though, so what should that be?  gpt_lld.h lists some priorities.  ChibiOS always sends the priorities through a macro called CORTEX_PRIORITY_MASK, but that seems to move the number to the correct place in a register.  The CPU manual (section 5.3) says that lower numbers have higher priority, and this needs to be very high, so I’ll choose 2.

  nvicEnableVector(TIM11_IRQn, CORTEX_PRIORITY_MASK(2));

It doesn’t like that much – the light doesn’t blink, so it looks like the chip crashed!  I’m starting to get a bit annoyed with my slow progress; maybe I’ll try to rewrite my code using ChibiOS as much as I can.  While this chip is powerful, its also very complicated, which makes me think it’s not that practical to program it directly.  I soldiered on anyway

Leave a Comment

Coding PWM on the STM32L

So how about coding up the PWM to produce the hsync signal from my previous post?  Here’s my first attempt:

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

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

  rccEnableAPB2(RCC_APB2ENR_TIM11EN, 0); // Enable TIM11 clock, run at SYSCLK

  // TIM11 outputs on PA7, PB9 or PB15
  GPIOB->MODER &= ~GPIO_MODER_MODER9;
  GPIOB->MODER |= GPIO_MODER_MODER9_1; // alternate function on pin B9
  TIM11->CCR1 = TIM_CR1_ARPE // buffer ARR, needed for PWM (?)
          | TIM_CR1_CEN; // counter enable... proably important!
  TIM11->CCMR1 &= ~(TIM_CCMR1_CC1S); // configure output pin
  TIM11->CCMR1 =
          TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0  // output high on compare match
          | TIM_CCMR1_OC1PE; // preload enable
  TIM11->CCER = TIM_CCER_CC1P // active low output
          | TIM_CCER_CC1E; // enable output
  TIM11->ARR = STM32_SYSCLK * 0.000064;   // horizontal line duration
  TIM11->CCR1 = STM32_SYSCLK * 0.0000047; // hsync pulse duration

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

I’ll attach my DSO Nano to PB9, and… nothing!  I don’t think it’s the DSO; that should be good to a few μs.

So why isn’t it running? Maybe the timer is running, but the pin output isn’t.  I started OpenOCD, and attached GDB to it.  The datasheet says that TIM11 is at 0x40011000, and the reference manual says the counter is at offset 0x24.  With this information, I can look at the contents of the timer – the GDB manual says I can look at memory using the “x” command:

$ arm-none-eabi-gdb build/ch.elf
GNU gdb (GNU Tools for ARM Embedded Processors) 7.3.1.20120613-cvs
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-linux-gnu --target=arm-none-eabi".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/damien/projects/stm32/video/build/ch.elf...done.
(gdb) target remote :3333
Remote debugging using :3333
ResetHandler () at /opt/ChibiOS_2.4.2/os/ports/GCC/ARMCMx/crt0.c:262
262      asm volatile ("cpsid   i");
(gdb) cont
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x08000588 in _idle_thread (p=<optimized out>)
at /opt/ChibiOS_2.4.2/os/kernel/src/chsys.c:62
62      chRegSetThreadName("idle");
(gdb) x 0x40011024
0x40011024:    0x000002ed
(gdb) cont
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x08000588 in _idle_thread (p=<optimized out>)
at /opt/ChibiOS_2.4.2/os/kernel/src/chsys.c:62
62      chRegSetThreadName("idle");
(gdb) x 0x40011024
0x40011024:    0x0000067c
(gdb) x 0x40011024
0x40011024:    0x000005b8
(gdb) x 0x40011024
0x40011024:    0x0000050c
(gdb) x 0x40011024
0x40011024:    0x00000774
(gdb)

The value is changing – that’s a good start! Note that I didn’t have to continue the program to see the timer incrementing – it runs even though the debugger has stopped execution! Also, the timer never seems to set the top 4 bits, which suggests it’s resetting at some value before it overflows its 16 bits. This maximum value should be 32MHz*64μs=0x800, so it’s looking good.

There’s an application note discussing the timers in the STM32 chips, and reading that closely it looks like I’ve been confusing the “output compare” mode and the PWM mode.  I think in the AVR these are essentially the same, but in the STM32 there doesn’t look like there’s a way to reset the pin upon overflow when in output compare mode (so this mode doesn’t seem to be that useful to me, apart from one-shot events).  The ChibiOS PWM code is worth looking at too.

A few days have passed since I did the above bit…

I read the reference on GPIOs a bit more. I would have guessed that since I told the chip to connect to the timer, the timer would look after that pin. But after reading section 6.3.2 of the datasheet about configuring pins for the alternate function, it looks like there’s a AFR register to set. Figure 18 suggests I need to set the AFRH register to “3” to enable the timer on this pin. I added these lines:

  GPIOB->AFRH &= ~GPIO_AFRH_AFRH9;
  GPIOB->AFRH |= 0x3 << 4; // ChibiOS doesn't seem to have constants for these

and finally I get some output! The timing looks right, but there’s some strange stuff happening with the amplitude of this waveform. I’m hoping it’s aliasing with my DSO, so hopefully I’ll get to use a CRO in a few days to confirm this. I made the timings a bit longer, and it didn’t look as bad, but maybe there’s some other aliasing going on with the clocks in the chip.

Another problem is that the waveform is the wrong way around! There seem to be two settings that affect this: the OC1M bits in CCMR1, which say whether the waveform is active when the counter is less than the compare register; the other specifies the polarity of the output. Maybe I only have to change one of these? It seems odd to have two registers which do mostly the same thing. I’ll change the polarity in CC1P.

That’s looking better! I swapped CCER and OC1M around, and the output looked the same.

I noticed that the signal doesn’t rise very quickly. Looking through the GPIO registers again, I got the OTYPER setting wrong which made it open drain. Changing this around fixed this problem.

So here’s my complete code:

  rccEnableAPB2(RCC_APB2ENR_TIM11EN, 0); // Enable TIM11 clock, run at SYSCLK

  // TIM11 outputs on PA7, PB9 or PB15
  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 |= 0x3 << 4; // ChibiOS doesn't seem to have constants for these   TIM11->CR1 |= TIM_CR1_ARPE; // buffer ARR, needed for PWM (?)
  TIM11->CCMR1 &= ~(TIM_CCMR1_CC1S); // configure output pin
  TIM11->CCMR1 =
          TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 // output high on compare match
          | TIM_CCMR1_OC1PE; // preload enable
  TIM11->CCER = TIM_CCER_CC1P // active low output
           | TIM_CCER_CC1E; // enable output
  TIM11->ARR = STM32_SYSCLK * 0.000064;   // horizontal line duration
  TIM11->CCR1 = STM32_SYSCLK * 0.0000047; // hsync pulse duration

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

Next I need an interrupt when the signal goes low, so I can adjust the signal timings for the vertical sync.

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