Using timer(s) to capture multiple inputs

Posted on
  • Hi, I read this page with interest: http://www.espruino.com/STM32+Peripheral­s.

    I followed the "Counting edges" section and got it working - very happy.

    I'd like to capture 3 inputs using this method. Is this possible using more channels on TIM1? Am not clear how pin A8 gets associated with TIM1 CH1 and how I'd do this for other pins. Any pointers would be appreciated.

    EDIT: TL;DR
    Completed project write up here: https://github.com/jugglingcats/pulse-co­unter

    Thanks,
    Alfie.

  • Great! You're doing this with a Pico?

    On the F4, certain peripherals are wired to certain pins. Usually you have the option of having them in one of two places - it's controlled by something called the 'Alternate Function register'. The datasheet for the F4 tends to have a big table of what can go where, which I've digitised for Espruino here:

    https://github.com/espruino/Espruino/blo­b/master/boards/pins/stm32f401_af.csv

    You could look at the reference manual (or probably googling for some examples is easier) but it's not that nice.

    Your best bet is just to look at the pinout docs for the Pico (or WiFi board, if you use that): https://www.espruino.com/Pico#pinout

    Hover the mouse over the PWM tags and look at the tooltips - and voila - there's the timer and channel that's being used :)

  • That's helpful Gordon thanks. Yes I am using the Pico.

    So you think if I pick, say B6 and B7 (TIM4, CH1 and CH2 respectively), I should be able to do the same trick as you did with TIM1, just by looking up the relevant register addresses/offsets?

  • I got it all working - thanks!

    Here is my code in case it's useful to anyone...

    var DEFS={
      TIM1: {
        base: 0x40010000,
        pin: A8
      },
      TIM3: {
        base: 0x40000400,
        pin: B4
      },
      TIM4: {
        base: 0x40000800,
        pin: B6
      }
    };
    
    function init(tim) {
      const def=DEFS[tim];
      if ( !def ) {
        throw "Invalid timer!";
      }
      const BASE=def.base;
      
      // slave mode control register
      var SMCR = BASE+0x0008;
      // event generation register
      var EGR = BASE+0x0014;
      // Capture compare mode register
      var CCMR1 = BASE+0x0018;
      // Capture/compare enable register
      var CCER = BASE+0x0020;
      // counter
      var CNT = BASE+0x0024;
      // prescaler
      var PSC = BASE+0x0028;
      // auto reload register
      var ARR = BASE+0x002C;
    
      // enable PWM on A8 (TIM1 CH1)
      analogWrite(def.pin,0.5,{freq:10});
      // CC1E = 0 (Turn channel 1 off)
      poke16(CCER, peek16(CCER) & ~1);
      // CC1S[1:0]=01 (rising edge), IC1F[7:4]=0 (no filter)
      poke16(CCMR1, (peek16(CCMR1) & ~0b11110011) | (0b00000001));
      // CC1P=0, CC1NP=0 (detect rising edge), CC1E[0] = 1 (Turn channel 1 on)
      poke16(CCER, peek16(CCER) & ~(0b1011) | (0b0001));
      // SMS[2:0]=111 (ext clock), TS[6:4]=101 (CH1 as trigger)
      poke16(SMCR, (peek16(SMCR) & ~0b1110111) | 0b1010111);
      // Prescaler to 0 - use every transition
      poke16(PSC, 0);
      // auto-reload with the full range of values
      poke16(ARR, 65535);
      // poke the UG[0] bit to reset the counter and update the prescaler
      poke16(EGR, 1);
    
      return {
        get: function() {
          return peek16(CNT);
        },
        reset: function() {
          poke16(CNT, 0);
        }
      };
    }
    
    const TIM1=init("TIM1");
    const TIM4=init("TIM4");
    const TIM3=init("TIM3");
    
    function update(){
      g.clear();
      g.drawString("Steps X: "+TIM1.get(), 2, 2);
      g.drawString("Steps Y: "+TIM4.get(), 2, 20);
      g.drawString("Steps Z: "+TIM3.get(), 2, 38);
      g.flip(); 
    }
    
    // SPI
    var s = new SPI();
    s.setup({mosi: B15 /* D1 */, sck:B13 /* D0 */});
    var g = require("SH1106").connectSPI(s, B14 /* DC */, B10 /* RST - can leave as undefined */, function() {
      setInterval(update, 100);
    });
    
    setWatch(function(e) {
      TIM1.reset();
      TIM4.reset();
      TIM3.reset();
    }, BTN, { repeat: true });
    
  • Nearly done...

  • Great for tutorial... can you write up a little bit more about the application... and what you sense? Is the published code the final one?

    Then this has to go onto the key forum pages... (--> @Gordon)

  • That's fantastic - thanks for posting it up! So you're doing it with 3 different timers in the end?

    I guess you might have to - if the pulses aren't all aligned you'll need a separate counter to count the time each one is high.

  • It just seemed to be easier to use 3 timers in the end because the register offsets are all the same, so I could write some generic code to initialise and read them etc.

    I've put all the code on Github...

    https://github.com/jugglingcats/pulse-co­unter

  • I'd now like to go further and handle step and direction (with direction being high/low signal on another gpio). Having trouble understanding how to translate the example given below into Espruino equivalent!

    https://stackoverflow.com/questions/3294­7972/stm32-how-to-make-pulse-count-up-do­wn-with-timer

  • Yeah, that code is using ST's API rather than poking registers (mostly!).

    You'll need to look at the ST reference manual, check out the registers, and what stuff like timer.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3 actually corresponds to.

    It might help to cheat and look at what ST's API does itself: https://github.com/espruino/Espruino/blo­b/master/targetlibs/stm32f4/lib/stm32f4x­x_tim.c#L302

    Note that ST tend to change their API quite a lot and I got fed up chasing them a few years ago, so Espruino's libraries are old. HAL_TIM_Base_Init will be pretty much the same as TIM_TimeBaseInit - they just randomly changed the name to make it painful for everyone :/

  • After a lot of messing about and reading the spec, I don't think it's possible to have the counter DIR bit change according to a simple high/low on another GPIO as required. The encoder mode of the timer is designed for a quadrature encoder.

    I ended up with a little inline C to switch this bit according to GPIO input edge change like this:

    const native=E.compiledC(`
      // void dir(bool)
      unsigned char *addr=(unsigned char *) 0x40010000;
      void dir(bool state) {
        *addr = (*addr & ~0b00010000) | (state * 0b00010000);
      }
    `);
    
    setWatch(native.dir, B7, {repeat: true, edge: "both", irq: true});
    
  • ...the world is hybrid! Congrats to your great solution!

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Using timer(s) to capture multiple inputs

Posted by Avatar for jugglingcats @jugglingcats

Actions