Posts Tagged chibios

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

ChibiOS on the STM32L Discovery

I previously used the libraries provided by ST to do something on the STM32L Discovery, now I’ve made a short demo of using ChibiOS.  Hopefully by using an OS to write code, I don’t have to mess around with timers myself to process events periodically, which I always found time consuming on the AVR.  Here’s what I did:

  1. Download and extract ChibiOS.  I used version 2.4.2.  I extracted the archive to /opt.
  2. Make a copy of this directory: ChibiOS_2.4.2/demos/ARMCM3-STM32L152-DISCOVERY.  This is what I edited for my demo.
  3. Delete the keil and iar directories; they’re for different IDEs and we don’t need them.
  4. Replace main.c with this:
    #include <ch.h>
    #include <hal.h>
    
    int main(void) {
      halInit();
      chSysInit();
    
      palSetPadMode(GPIOB, 7, PAL_MODE_OUTPUT_PUSHPULL);
      while (1) {
        palSetPad(GPIOB, 7);
        chThdSleepMilliseconds(500);
        palClearPad(GPIOB, 7);
        chThdSleepMilliseconds(500);
      }
    }

    I adapted this from another blog.

  5. In the Makefile, change the CHIBIOS variable to point to where you extracted ChibiOS.
  6. Run make, then upload the binary to the Discovery.  The correct compiler should be invoked if the Linaro bare-metal compiler is on your PATH, like I did for my first coding attempt.

It’s as simple as that – a light that flashes once a second!

So what does this code do?

halInit() and chSysInit() seem to go at the start of any ChibiOS program. The HAL is what tries to abstract out the peripherals on each chip. The document about the architecture explains this some more.

pal means “Port Abstraction Layer”, and functions relating to this start with pal. Functions starting with ch relate to the kernel. There are two reference manuals in ChibiOS: one for the kernel (the cross platform stuff, although there’s a separate manual for each compiler), and one for the peripherals (the more chip-specific stuff).

It looks like once you call chSysInit, that function continues to run as a thread.  I would hope because we’re calling a sleep function, the CPU is actually sleeping and not busy-waiting.

I’m surprised how easy this was to get running.  The Makefile is very good – you don’t need to keep a copy of the entire operating system in your project directory; it will find it and compile the relevant parts.  There’s no code directly accessing the hardware either, so the I/O code is no more complicated than Arduino code.  Of course I’m expecting to have to learn more about ChibiOS and the STM32L as I make more complicated things.

I’d like to use ChibiOS to write firmware to drive a television from the Discovery, so I can learn about RTOS scheduling and DMA, which I plan to use to copy the framebuffer to the output.  I imagine I’ll need to learn more about how clocks work to control the speed the data is written to the TV.

Leave a Comment