Jump to content
gordu

lm4f and porting of AccelStepper lib

Recommended Posts

I thought it would be pretty straight forward to use the accelStepper lib. First issue was the current release of energia had problems with the inclusion of the math libraries...no problem I grabbed the latest source and compiled it (lots of didling to get all the right libraries but eventually i got there). Started energia, works fiine. Tried some examples, no problem.

Then I tried using the accelStepper lib, it compiled fine but when I load it....nothing. the board is dead. I mixed the code with the led blink example and played around a bit and it seems a soon the accelStepper constructor makes any function call I am hooped. If I comment out the function calls in the constructor I am fine the led blinks, but uncomment them or even add a ecall to Serial.print, then nothing.

 

I am at a loss...I dont know how to debug a board that is not (seemingly) running.

 

help

 

 

Share this post


Link to post
Share on other sites

I had similar problem. Perhaps it only worked for me, but you might try the same, so we could verify and that would give some hints to the developers:

 

Rename constructor to a method in AccelStepper.h and AccelStepper.cpp, for example:

void AccelStepper::begin(uint8_t interface=AccelStepper::FULL4WIRE, uint8_t pin1=2, uint8_t pin2=3, uint8_t pin3=4, uint8_t pin4=5, bool enable=true) {
// paste the original constructor method here
} 

Make an empty default constructor for AccelStepper in AccelStepper.h:

AccelStepper::AccelStapper() {} 

Use it in your code:

AccelStepper mystepper;

void setup() {
   mystepper.begin(AccelStepper::FULL4WIRE, coil1, coil2, coil3, coil4);
}

void loop() {
   mystepper.runSpeed();
}

edit: steps_per_revolution replaced with AccelStepper::FULL4WIRE.

 

with kind regards,

Szymon

Share this post


Link to post
Share on other sites

I decided not to start another thread, because what I am about to present here is actually my attempt to port AccelStepper library to LM4F. Specifically my port makes use of the timer interrupts to schedule stepping, instead of using polled fashion execution of run() method. I used the AccelStepper source code revision 1.39 and I had to move around number of members from private to public:

  • _stepInterval
  • _direction
  • _currentPos
  • _targetPos
  • _speed
  • _pin[4]

Modified AccelStepper can be used for inheritance. It is limited to the DRIVER variant, but can be easily extended with FULL4WIRE, HALF4WIRE and others.

 

iAccelStepper.h:

#ifndef iAccelStepper_h
#define iAccelStepper_h

#include <Energia.h>
#include "AccelStepper.h"

#include "driverlib/sysctl.h"

//*****************************************************************************
//
// The list of Timer peripherals.
//
//*****************************************************************************
static const unsigned long g_ulTIMERPeriph[3] =
{
#if defined(PART_TM4C1233H6PM) || defined(PART_LM4F120H5QR)
//  SYSCTL_PERIPH_TIMER0, // wiring_analog.c analogWrite()
  SYSCTL_PERIPH_TIMER1,
  SYSCTL_PERIPH_TIMER2,
  SYSCTL_PERIPH_TIMER3
//  SYSCTL_PERIPH_TIMER4, // Tone.c
//  SYSCTL_PERIPH_TIMER5, // wiring.c - millis() micros()
//  SYSCTL_PERIPH_TIMER6,
//  SYSCTL_PERIPH_TIMER7
#else
  #error "**** No PART defined or unsupported PART ****"
#endif
};

static const unsigned long g_ulTIMERBase[3] =
{
#if defined(PART_TM4C1233H6PM) || defined(PART_LM4F120H5QR)
//  TIMER0_BASE, // wiring_analog.c analogWrite()
  TIMER1_BASE,
  TIMER2_BASE,
  TIMER3_BASE
//  TIMER4_BASE, // Tone.c
//  TIMER5_BASE, // wiring.c - millis() micros()
//  TIMER6_BASE,
//  TIMER7_BASE
#else
  #error "**** No PART defined or unsupported PART ****"
#endif
};

class iAccelStepper : public AccelStepper
{
public:
  iAccelStepper() : AccelStepper() {};
  void begin(uint8_t pin1 = 2, uint8_t pin2 = 3);
  void moveTo(long absolute);
  void move(long relative);
  boolean run(void) { return running; };
  void ISR(void);
private:
  boolean running;
  unsigned int id;
};
#endif

iAccelStepper.cpp:

#include "iAccelStepper.h"

#include "driverlib/timer.h"

volatile boolean state[3];
static iAccelStepper* me[3];
static unsigned int all_instances;

void iAccelStepper::ISR(void) {
  TimerIntClear(g_ulTIMERBase[id], TIMER_TIMA_TIMEOUT);
  if(state[id]) {
    if(_direction == DIRECTION_CW)
      // Clockwise
      ++_currentPos;
    else
      // Anticlockwise
      --_currentPos;
    // prepare for the next period
    computeNewSpeed();
    digitalWrite(_pin[1], _direction);
    TimerLoadSet(g_ulTIMERBase[id], TIMER_A, 1);
    TimerEnable(g_ulTIMERBase[id], TIMER_A);
  } else {
    // switch off timer at the falling edge
    if(_stepInterval == 0) {
      TimerDisable(g_ulTIMERBase[id], TIMER_A);
      running = false;
    } else {
      TimerLoadSet(g_ulTIMERBase[id], TIMER_A, (80*_stepInterval)-1);
      TimerEnable(g_ulTIMERBase[id], TIMER_A);
    }
  }
  digitalWrite(_pin[0], state[id]);
  state[id] ^= 1;
}

void timerISR0(void) { me[0]->ISR(); }
void timerISR1(void) { me[1]->ISR(); }
void timerISR2(void) { me[2]->ISR(); }
//void timerISR3(void) { me[3]->ISR(); }
//void timerISR4(void) { me[4]->ISR(); }

typedef void (*ISR_ptr_t)(void);
//ISR_ptr_t timerISR_ptr[5] = { timerISR0, timerISR1, timerISR2, timerISR3, timerISR4 };
ISR_ptr_t timerISR_ptr[3] = { timerISR0, timerISR1, timerISR2 };

void iAccelStepper::begin(uint8_t pin1, uint8_t pin2)
{
  //                                        STEP  DIR
  AccelStepper::begin(AccelStepper::DRIVER, pin1, pin2);

  if(all_instances < 3) {
    id = all_instances;

    // Configure timer
    SysCtlPeripheralEnable(g_ulTIMERPeriph[id]);
    TimerConfigure(g_ulTIMERBase[id], TIMER_CFG_ONE_SHOT);
    TimerIntEnable(g_ulTIMERBase[id], TIMER_TIMA_TIMEOUT);
    TimerIntRegister(g_ulTIMERBase[id], TIMER_A, timerISR_ptr[id]);

    me[id] = this;
    state[id] = false;
    running = false;
    ++all_instances;
  }
}

void iAccelStepper::move(long relative)
{
  moveTo(_currentPos + relative);
}

void iAccelStepper::moveTo(long absolute)
{
  AccelStepper::moveTo(absolute);

  if(!running && (distanceToGo() != 0)) {
    digitalWrite(_pin[1], _direction);
    computeNewSpeed();
    state[id] = true;
    digitalWrite(_pin[0], state[id]);
    running = true;
    TimerLoadSet(g_ulTIMERBase[id], TIMER_A, 1);
    TimerEnable(g_ulTIMERBase[id], TIMER_A);
  }
}

Comments are welcome, especially on the usage of other timers: there are only 3 left as far as I can see.

 

Szymon

Share this post


Link to post
Share on other sites

Hi again,

 

I improved the code further: the driverlib calls were replaced with direct HWREG() writes and reads. I found the timer1 unreliable - as far as I can see it is not used for anything else, and when used for triggering steps it is "stuck" after a while. So far I was not able to find the root-cause of this problem - it is only apparent with timer1 - not with the two remaining timers 2 and 3.

 

https://github.com/sq7bti/iAccelStepper

 

iAccelStepper.h

#ifndef iAccelStepper_h
#define iAccelStepper_h

#include <Energia.h>
#include "AccelStepper.h"

#include "driverlib/sysctl.h"

#define MAX_INST 2

//*****************************************************************************
//
// The list of Timer peripherals.
//
//*****************************************************************************
static const unsigned long g_ulTIMERPeriph[MAX_INST] =
{
#if defined(PART_TM4C1233H6PM) || defined(PART_LM4F120H5QR)
//  SYSCTL_PERIPH_TIMER0, // wiring_analog.c analogWrite()
//  SYSCTL_PERIPH_TIMER1,
  SYSCTL_PERIPH_TIMER2,
  SYSCTL_PERIPH_TIMER3
//  SYSCTL_PERIPH_TIMER4, // Tone.c
//  SYSCTL_PERIPH_TIMER5, // wiring.c - millis() micros()
//  SYSCTL_PERIPH_TIMER6,
//  SYSCTL_PERIPH_TIMER7
#else
  #error "**** No PART defined or unsupported PART ****"
#endif
};

static const unsigned long g_ulTIMERBase[MAX_INST] =
{
#if defined(PART_TM4C1233H6PM) || defined(PART_LM4F120H5QR)
//  TIMER0_BASE, // wiring_analog.c analogWrite()
//  TIMER1_BASE,
  TIMER2_BASE,
  TIMER3_BASE
//  TIMER4_BASE, // Tone.c
//  TIMER5_BASE, // wiring.c - millis() micros()
//  TIMER6_BASE,
//  TIMER7_BASE
#else
  #error "**** No PART defined or unsupported PART ****"
#endif
};

class iAccelStepper : public AccelStepper
{
public:
  iAccelStepper() : AccelStepper() {};
  void begin(uint8_t pin1 = 2, uint8_t pin2 = 3, uint8_t pin3 = 4);
  void moveTo(long absolute);
  void move(long relative);
  boolean run(void) { return running; };
  unsigned long stepInterval() { return _stepInterval; };
  void ISR(void);
private:
  volatile boolean running;
  unsigned int id;
};

#endif

iAccelStepper.cpp

#include "iAccelStepper.h"
#include "driverlib/timer.h"
#include "inc/hw_gpio.h"
#include "inc/hw_timer.h"

static iAccelStepper* me[MAX_INST];
static uint32_t _port_step[MAX_INST];
static uint8_t _pin_step[MAX_INST];
static uint32_t _port_dir[MAX_INST];
static uint8_t _pin_dir[MAX_INST];
static boolean direction[MAX_INST];
static boolean _state[MAX_INST];
static unsigned char all_instances;
static uint32_t ulPeriod;

void iAccelStepper::ISR(void) {
  //TimerIntClear(g_ulTIMERBase[id], TIMER_TIMA_TIMEOUT);
  HWREG(g_ulTIMERBase[id] + TIMER_O_ICR) = TIMER_TIMA_TIMEOUT;

  // falling edge produce space for length _stepInterval
  if(_state[id]) {
    _state[id] = false;

    // prepare for the next period
    // falling edge - calculate everything necessary and calculate _stepInterval
    computeNewSpeed();

    if(direction[id] != _direction) {
      direction[id] = _direction;
      HWREG(_port_dir[id]) = _direction?_pin_dir[id]:0;
    }

    HWREG(_port_step[id]) = 0;
    //TimerLoadSet(g_ulTIMERBase[id], TIMER_A, _stepInterval - ulPeriod);
    HWREG(g_ulTIMERBase[id] + TIMER_O_TAILR) = _stepInterval - ulPeriod;
    //TimerEnable(g_ulTIMERBase[id], TIMER_A);
    HWREG(g_ulTIMERBase[id] + TIMER_O_CTL) |= TIMER_A & (TIMER_CTL_TAEN | TIMER_CTL_TBEN);

  } else {

    // either fire the timer again for another period or switch it off when the move is finished
    if((_stepInterval == 0) || (abs(distanceToGo()) < 1)) {
      //TimerDisable(g_ulTIMERBase[id], TIMER_A);
      HWREG(g_ulTIMERBase[id] + TIMER_O_CTL) &= ~(TIMER_A & (TIMER_CTL_TAEN | TIMER_CTL_TBEN));
      running = false;
    } else {
      _state[id] = true;

      if(_direction == DIRECTION_CW)
        // Clockwise
        ++_currentPos;
      else
        // Anticlockwise
        --_currentPos;

      //TimerLoadSet(g_ulTIMERBase[id], TIMER_A, ulPeriod);
      HWREG(g_ulTIMERBase[id] + TIMER_O_TAILR) = ulPeriod;
      HWREG(_port_step[id]) = _pin_step[id];
      //TimerEnable(g_ulTIMERBase[id], TIMER_A);
      HWREG(g_ulTIMERBase[id] + TIMER_O_CTL) |= TIMER_A & (TIMER_CTL_TAEN | TIMER_CTL_TBEN);
    }
  }
}

void timerISR0(void) { me[0]->ISR(); }
void timerISR1(void) { me[1]->ISR(); }
//void timerISR2(void) { me[2]->ISR(); }
//void timerISR3(void) { me[3]->ISR(); }
//void timerISR4(void) { me[4]->ISR(); }

typedef void (*ISR_ptr_t)(void);
//ISR_ptr_t timerISR_ptr[5] = { &timerISR0, &timerISR1, timerISR2, timerISR3, timerISR4 };
ISR_ptr_t timerISR_ptr[MAX_INST] = { timerISR0, timerISR1 };

void iAccelStepper::begin(uint8_t pin1, uint8_t pin2, uint8_t pin3)
{
  //                                        STEP  DIR
  AccelStepper::begin(AccelStepper::DRIVER, pin1, pin2);
  //                         ENABLE
  AccelStepper::setEnablePin(pin3);
  AccelStepper::setPinsInverted(false, false, true);

  // specs of DRV8825 requires 2us, A3967 and A4988 requires atleast 1us step pulse
//  ulPeriod = clockCyclesPerMicrosecond() * 2;
  ulPeriod = 1;

  if(all_instances < MAX_INST) {
    id = all_instances;
    // Configure timer
    SysCtlPeripheralEnable(g_ulTIMERPeriph[id]);

    TimerConfigure(g_ulTIMERBase[id], TIMER_CFG_ONE_SHOT);
    //TimerIntEnable(g_ulTIMERBase[id], TIMER_TIMA_TIMEOUT);
    HWREG(g_ulTIMERBase[id] + TIMER_O_IMR) |= TIMER_TIMA_TIMEOUT;
    TimerIntRegister(g_ulTIMERBase[id], TIMER_A, timerISR_ptr[id]);

    me[id] = this;
    running = false;
    ++all_instances;
    _port_step[id] = (uint32_t)portBASERegister(digitalPinToPort(pin1)) + (GPIO_O_DATA + (digitalPinToBitMask(pin1) << 2));
    _pin_step[id] = (uint8_t)digitalPinToBitMask(pin1);
    _port_dir[id]  = (uint32_t)portBASERegister(digitalPinToPort(pin2)) + (GPIO_O_DATA + (digitalPinToBitMask(pin2) << 2));
    _pin_dir[id]  = (uint8_t)digitalPinToBitMask(pin2);
    direction[id] = false;
    _state[id] = false;
  }
}

void iAccelStepper::move(long relative)
{
  moveTo(_currentPos + relative);
}

void iAccelStepper::moveTo(long absolute)
{
  AccelStepper::moveTo(absolute);

  if(!running && (distanceToGo() != 0)) {
    running = true;

    computeNewSpeed();

    if(direction[id] != _direction) {
      direction[id] = _direction;
      HWREG(_port_dir[id]) = _direction?_pin_dir[id]:0;
    }

    HWREG(_port_step[id]) = 0;
    _state[id] = false;

    //TimerLoadSet(g_ulTIMERBase[id], TIMER_A, _stepInterval - ulPeriod);
    HWREG(g_ulTIMERBase[id] + TIMER_O_TAILR) = _stepInterval - ulPeriod;
    //TimerEnable(g_ulTIMERBase[id], TIMER_A);
    HWREG(g_ulTIMERBase[id] + TIMER_O_CTL) |= TIMER_A & (TIMER_CTL_TAEN | TIMER_CTL_TBEN);
  }
}

Inheritance prerequisites remains as described in previous post. Remarks welcome.

 

With kind regards,

Szymon

 

/EDIT: added github

Share this post


Link to post
Share on other sites

Few suggestions -

 

I decided not to start another thread, because what I am about to present here is actually my attempt to port AccelStepper library to LM4F. Specifically my port makes use of the timer interrupts to schedule stepping, instead of using polled fashion execution of run() method. I used the AccelStepper source code revision 1.39 and I had to move around number of members from private to public:

  • _stepInterval
  • _direction
  • _currentPos
  • _targetPos
  • _speed
  • _pin[4]

 

 

 

Consider making these variables protected, rather than public.  Then can use them in subclass without interference from outside.

 

Move g_ulTIMERPeriph and g_ulTIMERBase into iAccelStepper.cpp (that looks like only place they are used).  

 

Make the timer ISRs (timerISR0, timerISR1, etc.) friends, so that can make iAccelStepper::ISR protected.  (Helps with information hiding, might also help compiler in deciding about inlining ISR.

 

Might be worth noting in the comments about timer selection that the Servo library uses timer 2

and that timers 0 through 3 may be used by analog write (depending on which pin(s) you are writing to.)

 

Might think about adding support for wide timers - just to give more flexibility in selecting a timer that isn't used by something else.

Share this post


Link to post
Share on other sites

Thanks for your input.

 

BTW, Regarding the timer used by other libraries I also discovered why I had problems with Timer1: there was a conflict because in my project I am using pin PF_3 for PWM generation with analogWrite().

 

Perhaps it would be even more flexible yet safe to allow selecting (with begin() method) a specific timer instead of choosing timers one by one from the list of hard-coded list of available ones.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...