Gallery, Projects and General > Project Logs
X2 X-axis Stepper motor Power Feed - (possible CNC conversion?)
<< < (12/19) > >>
kvom:
WRT controlling the motor in an interrupt:

Putting a delay in an interrupt routine is poor programming practice.  Interrupts should perform limited functions and return quickly.  Assuming that delay_us() is a system routine, it is almost certainly itself setting a timer interrupt and suspending execution pending that timer.

If I read your code correctly, the outer while loop continuously checks for a button push and then continues with the new or current motor movement.  In any case, your code is not checking for 2 or 3 buttons pressed simultaneously.  In addition to correcting this, you may need to "debounce" the buttons, since a button may remain "pressed" for a short time it's released by a finger.  The code could get a little messy to do this, so the likely easiest solution is to default to motor stop when more than one button is pressed.  Something like this:


   int button= 0;
   if (left)
     button += 1;
   if (right)
     button += 2;
   if (stop) || (button > 2)
     motorRun = 0;
   direction = (button == 1 ? 0 : 1);
raynerd:
Kvom, I have tried to put the step on an interupt triggered by the clock timer. I totally agree with what you say regarding the delay but apparently since the delay_ms is 1 microsecond, apparently that is about 4 lines of assembler so no time at all. However, I appareciate what you say about the bad practice so I believe I could set the step high at the start of the interupt, then "do some more code" - say the limit stuff and then set the step low. Would that not have the same effect of a super short delay as it runs the code between the step high and the step low?

This code allows the user no control of when the interupt occurs and therefore the stepper pulses. All speed control has been lost now in this re-worked code. By using the ADC or even presets to alter the timer count between triggering the interupt it should be able to control the speed.

If you can change and improve my code then please do.... I don`t really know how to move forward from here....

also the current limit setting idea is too complex, it would much better if it worked like this:

//A button that toggles free move or limited.
//at any point in either mode you can simply press the 'memory' button followed
//by left or right to set the limit.

here is the code...the current limit setting function described at the start:


--- Quote ---// Function:
// Press 'mem', light comes on.
// Move left & right until at position.
// Press 'mem' light starts to flash.
// Press left or right to set that limit, light goes out and unit works as before.

// define which pins our buttons are on
#define right PORTC.b0
#define left PORTC.b1
#define stop PORTBC.b2
#define memory PORTC.b3

// define which pins our motor is connected to
#define memoryLed PORTC.b4
#define motorLed PORTC.b5
#define step PORTC.b6
#define direction PORTC.b7

// LCD display config.
sbit LCD_RS at RB4_bit;
sbit LCD_EN at RB5_bit;
sbit LCD_D4 at RB0_bit;
sbit LCD_D5 at RB1_bit;
sbit LCD_D6 at RB2_bit;
sbit LCD_D7 at RB3_bit;

sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D4_Direction at TRISB0_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D7_Direction at TRISB3_bit;
// End LCD module connections


long tablePos = 0;
long limitLeft = -10000;
long limitRight = 10000;
int motorRun = 0;

// mode == 0 : running
// mode == 1 : setting limits
char mode = 0;

void interupt()
{
    TMR0IF_bit = 0;    // clear TMR0IF
    TMR0L  = 96;       // reset counter
    
    if (motorRun)      // interupt only steps motor if motorRun is true.
    {
    if (!mode)         // we only take note of the limits in mode 0 (otherwise we're setting them)
    {
    // if going right then check right limit - if greater than limit, motorRun = 0, stops motor.
        if (direction)
        {
            if (tablePos >= limitRight)
            {
                motorRun = 0;
                return;
            }
        }
        else
        {
            if (tablePos <= limitLeft)
            {
                motorRun = 0;
                return;
            }
            }
            }
    
    // these 3 lines step the motor
    step = 1;       // set the step line high
    delay_us(1);    // wait 1 micro second
    step = 0;       // and low again - the motor will now move one step
      // update the table position
    if (direction)
        tablePos++;
    else
        tablePos--;
 }
 }
 
//===========================================
// when the PIC is first switched on it will start to run at "main"

void main()
{
    char motorSpeed = 3;    // 3 = fast, 255 = slow
    char count;             // misc use
    int adc;                // somewhere to put the ADC we read

    T0CON  = 0xC4;       // Set TMR0 in 8bit mode, assign prescaler to TMR0
    // set ADC's
    ADCON0 = 1;         // enable ADC
    ADCON1 = 0b1110;    // enable ADC only for ADC 0

    // set all the pins on PORT B to 'off' before we start
    PORTC = 0;

    // set the tri-state buffer. Pins set to '1' are input, '0' are output
    // we've done this in binary to make it easier to see
    // each number is 1 pin, right hand is pin 0, left is pin 7
    TRISC = 0b00001111;
    TRISB = 0;
    TRISA = 0b11111111;

    while(1)
    {
        if (left) // is left button pressed?
        {  // if we get in here button must be pressed
            direction = 0;  //set direction to 0
            motorRun = 1; // start the motor
        }

        //------------------------------
        // check for right button
        if (right) // right button pressed
        {
            direction = 1;   // set direction to 1
            motorRun = 1; // start the motor
        }

        //------------------------------
        // check for stopping the motor
        if (stop)   // stop button pressed
            motorRun = 0; // stop the motor

        //------------------------------
        // handle setting the limits.
        //------------------------------
        // is memory button pressed?
        if (memory)
        {
            // wait for it to be released
            while (memory);

            // if in 'run' mode, change to 'limit set' mode
            if (!mode)
                mode = 1;
            else
            {
                // in limit set mode - wait for left or right to be pressed
                while (!left && !right) // if neither are pressed then stay here
                {
                    // be clever here and flash the memory light to show we're waiting
                    delay_ms(2);
                    count++;            // count is 8 bits (char) - when it gets to 255, the next time it increments it goes to zero
                    if (count > 128)    // 5ms * 128 is a bit over  1/4 seconds - so light should flash about 2hz
                        memoryLed = 0;
                    else
                        memoryLed = 1;
                }

                // depending on which button was pressed set the appropriate limit
                if (left)
                    limitLeft = tablePos;
                if (right)
                    limitRight = tablePos;

                // wait for left/right to be released and then we're done
                while (left || right);

                // we're finished so go back to 'run' mode
                mode = 0;
            }
        }

        //------------------------------
        // if in 'limit set' mode then light the memory LED
        if (mode == 1)
            memoryLed = 1;
        else
            memoryLed = 0;

        //------------------------------
        // step the motor
        if (motorRun)   // are we moving the motor?
        {
            motorLed = 1;
            //INITIATES interupt!
      TMR0L  = 96;         // Timer0 initial value
      INTCON = 0xA0;       // Enable TMRO interrupt

            // read the ADC and set the speed (delay)
            adc = 512;                   // MyADC needs replacing with  ADC_Read(0);
            adc = adc >> 2;         // divide by 4
            if (adc < 3)
                adc = 3;
            for(count = 0; count < 10; count++)
                Delay_Cyc(adc);
        }
        else
            motorLed = 0;
        }
}
--- End quote ---
kvom:
Some thoughts:

From the above, I am assuming that the 1 microsecond delay is necessary to create the proper length pulse to drive the motor.  If so, that is the primary requirement for the program and hence should be in the mainline of the program.  There is no need for any explicit interrupts as the only asynchronous events are button pushes, which are comparatively rare and not needing precision.

So I would use the prior code with the following modifications:

1) Ensure that only one button is pressed at a time

2) Stop the motor any time a button is pressed, and restart only when a good left or right button is pressed.  In cases where the motor is stepping in one direction and the opposite button is pressed, I would treat that as a stop and require two presses to reverse direction.

3) When the memory button is pressed, a second press or stop should turn off the LED without setting either limit.

4) You need to check that the right limit is not set less than the left limit and vice-versa.

Another way to think of this type of problem is to regard it as an "automaton".  An automaton is a machine that has a number of clearly defined states and all the valid transitions between them.  You can draw a diagram where the states are labeled circles and the transitions are directional arrows connecting them.

You might have the following states:

stopped
motor right
motor left
memory
memory left
memory right

The transition events are:

buttons
limits

Then a good way to structure the code is to hold the state in a variable and test it in the main loop via a switch statement.  In each case of the switch, you check for valid transition events, and if found change the state.

For complex situations, automata are a better way to organize the code as opposed to complex nested loops and ifs.  It's easy to locate the code for a given state and modify it without affecting other states, and in addition defining additional states is easier.
raynerd:
Kvom, These are not my words, these are actually notes I made from someone else giving me advice:

If I want my stepper motor to run at 3000 steps per second, I can`t start it at that or it will stall. I need to accelerate it to that speed and then once there I need to make sure the train of pulses are at the right frequency. If my code can not guarante that frequency I`ll have to run it slower and won`t be able to use the top speed which has already been discussed on here, will be useful for a fast return! The step pulse can be very small - just a microsecond or so  but the important bit is the distance between pulses. For example 1000 steps per second requires a time of 1/1000 of a second between pulses, 3000 requires 1/3000 th of a second etc and since time = 1 / frequency or frequency = 1 / time to run my stepper at 3khz you need a time of 1/3000th of a second between the pulses and since we know how fast a timer runs on the PIC we can calculate a suitable number to put in it so that it'll reach zero (trigger the interupt) in 1/3000th of a second. If we then set up an interrupt on that timer then 1/3000th of a second later, exactly, we'll get an interrupt and in there can step the motor. Since this is an interupt in our main loop we can do other stuff - write position to an lcd, scan the keypad, flash leds or whatever we want and we don't have to worry that the stepper is stepping correctly - the interrupt will do it for us!

I hope that makes sense...that is my understanding of it mainly taken word for word from an explanation I was given. I appreciate your advice and would quite like to hear your response!

Regarding your other points, I will certainly be using some of those ideas.

Chris
kvom:
That makes sense to use the interrupt in that case.  So if you set the interrupt at 3Khz, you need to define two additional things:

1) Accelleration time
2) Max speed

Assume it takes 100ms to accellerate to max speed, or 0-3Khz.   One way to do this is to vary the interrupt rate.  Assume 10ms intervals.  For the first interval you set the interrupt rate at 300 Hz and generate a step at each interrupt.  Then increase the rate to 60Hz until the motor is at full speed.  You can define an automaton state of 'accellerating' to handle the rate change.  Obviously you can make the accelleration smoother if you want.

Navigation
Message Index
Next page
Previous page

Go to full version