Rotary encoder with blue pill

Post here all questions related to LibMaple core if you can't find a relevant section!
sigi
Posts: 57
Joined: Mon Sep 28, 2020 3:17 pm

Re: Rotary encoder with blue pill

Post by sigi »

ok thanks... well I do my own rotary lib long ago...
https://drive.google.com/file/d/1iWFBES ... share_link

... and this is the way u setup it...

#include <RotaryEncoder.h>
float maxMark = 12.0;
float minMark = 0.0;
float incMark = 0.25;
#define Enc_CLK PB8
#define Enc_DT PB9
RotaryEncoder EncoderPulse (Enc_CLK,Enc_DT,incMark,minMark,maxMark,4);

(4 is for the number of latches the encoder is using... cuadrature etc)

thats all I think anybody needs to do using that library, the min, the max and the specific increment u want to use... all in a easy to use 1 line setup

this soft version works good for me, I'm just looking for a good hardware based alternative with the same easy of use... so Im looking for a solution that does not need to use digitalRead in order to work.
sigi
Posts: 57
Joined: Mon Sep 28, 2020 3:17 pm

Re: Rotary encoder with blue pill

Post by sigi »

playing with the code I solved the initial problem... it just need a Refresh()...
not A simple code like this works very good...

Code: Select all

#include "HardwareTimer.h"
HardwareTimer timer(4);//PB6 and PB7
float units=0.0;
float incre=0.1;

void func(){
if (timer.getDirection()) 
	{units-=incre;} 
	else
	{units+=incre;}
}


void setup() {
//define the Timer channels as inputs. 
//  pinMode(D2, INPUT_PULLUP);  //channel A
//  pinMode(D3, INPUT_PULLUP);  //channel B
 
timer.pause(); 
//timer.setMode(0, TIMER_ENCODER);
//timer.setCompare(TIMER_CH1, 1);
timer.setPrescaleFactor(1);
timer.setOverflow(3);
timer.setEdgeCounting(TIMER_SMCR_SMS_ENCODER3);
//timer.setCount(0);
timer.refresh(); // <<<<<<<<<<   FIX!!!   
timer.resume();


timer.attachInterrupt(0, func);
}

void loop() {
Serial.print("Units: ");
Serial.println(units);
}
now I just need to know how to specify the pins used... as showed here by default the first 2 channels are used... so u do not need to declare them as inputs.
sigi
Posts: 57
Joined: Mon Sep 28, 2020 3:17 pm

Re: Rotary encoder with blue pill

Post by sigi »

playing more u can even work without attachedinterrupts...

Code: Select all

#include "HardwareTimer.h"
HardwareTimer MyTimer(1);
float units=0.0;

float incre=0.5;
float min = -4;
float max = 3.5;
float start =-2.0;

int range =4*(max-min)/incre;

void setup() {
//timer 1 pins
pinMode(PA8 , INPUT_PULLUP); 
pinMode(PA9 , INPUT_PULLUP); 
pinMode(PA10, INPUT_PULLUP); 
pinMode(PA11, INPUT_PULLUP); 
	
MyTimer.pause(); 
MyTimer.setPrescaleFactor(1);
MyTimer.setOverflow(range);
MyTimer.setEdgeCounting(TIMER_SMCR_SMS_ENCODER3);
	 MyTimer.setMode(TIMER_CH1, TIMER_DISABLED);
	 MyTimer.setMode(TIMER_CH2, TIMER_DISABLED);
	 MyTimer.setMode(TIMER_CH3, TIMER_DISABLED);
	 MyTimer.setMode(TIMER_CH4, TIMER_DISABLED);
MyTimer.refresh();  
MyTimer.setCount(4*abs(min-start)/incre);
MyTimer.resume();
}

void loop() {
units = ((MyTimer.getCount()/4)*incre)+min;
Serial.print("Units: ");
Serial.println(units);
}
it seems there is no way to disable or decide which channel u can use,,, low level will be needed.
rsonnicksen
Posts: 9
Joined: Fri Dec 16, 2022 4:28 pm

Re: Rotary encoder with blue pill

Post by rsonnicksen »

I have been struggling to get the hardware timers to work in encoder mode. I'm using a BluePill (STM32F103C8) and Roger Clarks core for Arduino.

I'm setting it up in ENCODER3 mode (TIMER4_BASE->SMCR = TIMER_SMCR_SMS_ENCODER3; //SMS = 011) which according to the reference manual should trigger counts on the rise AND fall of both inputs TI1 and TI2. I am getting counts on half of those transitions, which ALMOST works, except it doesn't handle jitter. If input TI1 toggles true/false, my counter doesn't count UP, then DOWN. It only counts up. I'm using this for position control of a linear actuator, and eventually these small errors caused by jitter accumulate and become significant.

I "think" I'm setting up the timer according to the reference manual, but maybe there is some secret switch I need to set for the configuration to be accepted by the board. Here is my code.

Code: Select all

/*
 * STM32F102C8 - Blue Pill
 * Arduino IDE
 * Roger Clark STM32 for Arduino Library
 * Test using Timer 4 as an encoder input.
 * T4C1 is pin PB6
 * T4C2 is pin PB7
 */

void setup() {
  RCC_BASE->APB2ENR |= RCC_APB2ENR_IOPBEN;
  RCC_BASE->APB1ENR |= RCC_APB1ENR_TIM4EN;
  pinMode(PB6, INPUT_PULLUP);//So I can easily toggle the input by shorting to ground
  pinMode(PB7, INPUT_PULLUP);//So I can easily toggle the input by shorting to ground
  
  //Setup timer 4 inputs 1 and 2 as encoder inputs
  TIMER4_BASE->ARR = 65535;  //set the rollover value to maximum
  //configure channels 1 and 2 as inputs from their respective pins PB6 and PB7
  TIMER4_BASE->CCMR1 |= (TIMER_CCMR1_CC1S_INPUT_TI1 | TIMER_CCMR1_CC2S_INPUT_TI2);
  TIMER4_BASE->SMCR =  TIMER_SMCR_SMS_ENCODER3;  //SMS = 011
  TIMER4_BASE->CR1 |= TIMER_CR1_CEN;  //Enable counter
  Serial.begin(57600);
}

void loop() {
    Serial.println(TIMER4_BASE->CNT);
    delay(1000);
}
sigi
Posts: 57
Joined: Mon Sep 28, 2020 3:17 pm

Re: Rotary encoder with blue pill

Post by sigi »

Hi

this is my actual code about this...

test it MAYBE it can give u some info...
Attachments
HardTimer Encoder.rar
(9.03 KiB) Downloaded 105 times
rsonnicksen
Posts: 9
Joined: Fri Dec 16, 2022 4:28 pm

Re: Rotary encoder with blue pill

Post by rsonnicksen »

Thanks,
I'll give this a try. It looks like you're only triggering the counter on transitions of input channel1.
MyTimer.setMode(1, TIMER_ENCODER );
MyTimer.setEdgeCounting(TIMER_SMCR_SMS_ENCODER1);

I'm curious how this .setMode function works. I've traced it to.
C:\Program Files (x86)\Arduino\hardware\Arduino_STM32-master\STM32F1\cores\maple\HardwareTimer.cpp

Code: Select all

void HardwareTimer::setMode(int channel, timer_mode mode) {
    timer_set_mode(this->dev, (uint8)channel, (timer_mode)mode);
}
--------------------------------------------------------------------------
which calls
C:\Program Files (x86)\Arduino\hardware\Arduino_STM32-master\STM32F1\cores\maple\libmaple\timer.c

Notice the comment "//find a way to pass all the needed stuff on the 8bit var"
Seems like this case isn't finished.
Author didn't know how to pass all of the info needed.

Code: Select all

void timer_set_mode(timer_dev *dev, uint8 channel, timer_mode mode) {
    ASSERT_FAULT(channel > 0 && channel <= 4);

    /* TODO decide about the basic timers */
    ASSERT(dev->type != TIMER_BASIC);
    if (dev->type == TIMER_BASIC)
        return;

    switch (mode) {
    case TIMER_DISABLED:
        disable_channel(dev, channel);
        break;
    case TIMER_PWM:
        pwm_mode(dev, channel);
        break;
    case TIMER_OUTPUT_COMPARE:
        output_compare_mode(dev, channel);
        break;
    //added by CARLOS. 
    case TIMER_ENCODER: 
        encoder_mode(dev, channel); //find a way to pass all the needed stuff on the 8bit var
        break;
    case TIMER_INPUT_CAPTURE:// code from @Cesco
        input_capture_mode(dev, channel, TIMER_IC_INPUT_DEFAULT);
        break;		
    }
}
----------------------------------------------------------------------
which calls this (from same file timer.c)

Notice how the channel__attribute parameter is unused?
Also note how this routine by default sets the timer to encoder3 mode (which is supposed to trigger a count on each rise AND fall of BOTH channel 1 and channe 2 inputs (See STM32F1 reference manual).
It also applies the maximum debounce filtering (TIMER_CCMR1_IC1F)

Code: Select all

static void encoder_mode(timer_dev *dev, uint8 channel __attribute__((unused))) {
    
    //prescaler. 
    //(dev->regs).gen->PSC = 1;

    //map inputs. 
    (dev->regs).gen->CCMR1 = TIMER_CCMR1_CC1S_INPUT_TI1 | TIMER_CCMR1_CC2S_INPUT_TI2 | TIMER_CCMR1_IC2F | TIMER_CCMR1_IC1F ;

    (dev->regs).gen->SMCR = TIMER_SMCR_SMS_ENCODER3; //choose encoder 3, counting on both edges. 

    //polarity
    //(dev->regs).gen->CCER = TIMER_CCER_CC1P; //to invert the counting, only one of the inputs should be inverted.  

    //set the interval used by the encoder.
    //timer_set_reload(dev, 1000);

//    (dev->regs).gen->CR1  |=TIMER_CR1_UDIS_BIT;

    //run timer
    timer_resume(dev);
}
Can you tell me how your program performs? Does count on both rising and falling edges of both inputs, or only channel 1 input?
sigi
Posts: 57
Joined: Mon Sep 28, 2020 3:17 pm

Re: Rotary encoder with blue pill

Post by sigi »

look that code works well for me , of course it tracks the encoder movements 100% accurately... no missed ticks at all... no matter what u do in normal sketch code... maybe u miss to show a change but the changes are always tracked... just how it must be.

THE PROBLEM: the internal counter goes from 0 to any number u decide with the overflow... after that it will return to zero or to the max number on the counter memory object, that's makes necessary use of code to control the limits. That s why I include 2 examples to see the difference.
That means u can not avoid the ticks bellow zero or above the overflow limits.

A low level way to NOT count when u reach that limits is what I want to find.
For me an encoder with no control of the limits for the user is useless...

SO by now I continue to use my old software based library... included here.
Attachments
RotaryEncoder.rar
(42.07 KiB) Downloaded 112 times
rsonnicksen
Posts: 9
Joined: Fri Dec 16, 2022 4:28 pm

Re: Rotary encoder with blue pill

Post by rsonnicksen »

Sigi,
Thanks for the example code. I ran it on my Blue Pill and it worked correctly in ENCODER1 mode.
Triggered on rise and fall of input channel 1.

I changed

MyTimer.setEdgeCounting(TIMER_SMCR_SMS_ENCODER1);
to
MyTimer.setEdgeCounting(TIMER_SMCR_SMS_ENCODER3);

and it triggered on rise AND fall of BOTH input channels 1 AND 2 which is what I've been struggling to make happen. YEAH!!!!

I'm going to drill into that HardwareTimer code and see if I can find a difference between what your code is doing vs my attempt to manipulate the timer registers directly. There must be something different in the sequence of commands that is making my code not work.

Maybe it's in the setEdgeCounting function. Anyway, thanks again for your response which help me solve my problem.
rsonnicksen
Posts: 9
Joined: Fri Dec 16, 2022 4:28 pm

Re: Rotary encoder with blue pill

Post by rsonnicksen »

Here is the result of my tracing the functions in HardwareTimer

Code: Select all

#include "HardwareTimer.h"
HardwareTimer MyTimer(4);
MyTimer.pause(); //Disable timer CEN bit.
MyTimer.setMode(1, TIMER_ENCODER );
//setMode(int channel, timer_mode mode)  located in HardwareTimer.cpp
//calls timer_set_mode(this->dev, (uint8)channel, (timer_mode)mode) in  timer.c routine.
//void timer_set_mode(dev,channel, mode)
//switch(mode)
//case TIMER_ENCODER:
  //encoder_mode(dev,channel)    - calls encoder_mode in timer.c routine.

//void encoder_mode(timer_dev *dev, uint8 channel __attribute__((unused))) {
//   (dev->regs).gen->CCMR1 = TIMER_CCMR1_CC1S_INPUT_TI1 | TIMER_CCMR1_CC2S_INPUT_TI2 | TIMER_CCMR1_IC2F | TIMER_CCMR1_IC1F ;
//
//    (dev->regs).gen->SMCR = TIMER_SMCR_SMS_ENCODER3; //choose encoder 3, counting on both edges. 

MyTimer.setPrescaleFactor(1);  //sets the prescaler to 0 (parameter - 1)
MyTimer.setOverflow(range+1);  //sets the ARR to parameter
MyTimer.setEdgeCounting(TIMER_SMCR_SMS_ENCODER3); 
//.setEdgeCounting(uint32 counting)  (located in HardwareTimer.h)
//this calls setSlaveFlags(counting) in HardwareTimer.h

//    void setSlaveFlags(uint32 flags) {
//        ((this->dev)->regs).gen->SMCR = flags;  Overwrites the contents of the SMCR (slave mode control register)
//    }


MyTimer.refresh(); 
//.refresh() located in HardwareTimer.cpp
//calls timer_generate_update(this->dev) in timer.h

//static inline void timer_generate_update(timer_dev *dev) {
//    *bb_perip(&(dev->regs).bas->EGR, TIMER_EGR_UG_BIT) = 1;  Sets the UG bit in the EGR register
//}

//NOTE:  Value of TIMER_EGR_UG_BIT = 0.



MyTimer.resume();
//This simply sets the CEN bit in the CR1 register enabling the timer.

dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: Rotary encoder with blue pill

Post by dannyf »

a simpler approach may be to poll the pins periodically (driven by a timer) and use a state machine to read the encoder.

should be fairly simple.
Post Reply

Return to “General discussion”