views:

163

answers:

3

I am working on the firmware for a device that uses an 8bit mcu (8051 architecture). I am using SDCC (Small Device C Compiler). I have a function that I use to set the speed of a stepper motor that my circuit is driving. The speed is set by loading a desired value into the reload register for a timer. I have a variable, MotorSpeed that is in the range of 0 to 1200 which represents pulses per second to the motor. My function to convert MotorSpeed to the correct 16bit reload value is shown below. I know that float point operations are pretty slow and I am wondering if there is a faster way of doing this...

void SetSpeed()
{
    float t = MotorSpeed;
    unsigned int j = 0;
    t = 1/t ;
    t = t / 0.000001;
    j = MaxInt - t;
    TMR3RL = j;      // Set reload register for desired freq
    return;
}
+1  A: 

The classic way is to use fixed-point, by scaling up before dividing, and doing it all as integer.

j = (MotorSpeed * 65536) / 1200;

This still requires actual division (by 1200), but at least it's all-integer. The scaling should be very quick, since it's possible to implement using a shift.

unwind
Sorry why do you divide by 1200?
Jordan S
@Jordan S: Because you said the variable's value was in the 0..1200 range. What you want is (MotorSpeed/1200) * 65536, but you can't evaluate the first term as int.
unwind
If I understand correctly how the timer works, it increments a register up to 65535 and then, instead of overflowing to 0 it loads the initial value that Jordan is trying to calculate and increments it further. The register is incremented at the rate of 1 MHz if I understand the constant 0.000001 correctly
Maciej Hehl
At a guess, this normalizes to 65K as the "max", then integer divides down to get the fractional component?
Paul Nathan
+2  A: 

If I understand correctly what's going on You want to calculate the expression

MaxInt - 1000000/MotorSpeed

where MotorSpeed is a number form 0 to 1200 and represents number of pulses per second.

If Your compiler supports floating point arithmetic it must support integer division. Why don't You try that. If the speed is > 15 There is no problem, but for speeds in the range 0 to 15 the result is negative. It means that it's simply impossible to generate pulses with lower frequency than 16 Hz, if the counter is 16 bit wide and it's incremented at the rate of 1MHz. Do you have an additional prescaler that allows to reduce the frequency of increments? (I don't know 8051).

Maciej Hehl
This seems like the most straightforward way to go. For `MotorSpeed < 16`, set `TMR3RL = 0`. For all other input values, this will only require one integer divide and one subtract, which should be very fast (casting the result of the divide to a 16-bit number may also speed the code up a tad). The 8051 only has 8-bit ALU registers so the compiler will have to provide extra code to handle arithmetic on 16-bit or larger numbers. The "multiply by 65536" method someone mentioned would require at least 32-bit arithmetic, which would be much slower due to all the extra code required.
bta
This seems like the correct answer to me - division is needed, but not floating point division. If you want to keep the calculations all within 16 bit values, at the cost of accuracy at very slow motor speeds, you can use `MaxInt - 65449 / ((MotorSpeed * 43 + 328) / 657)` (also requires bta's suggested test for `MotorSpeed < 16`, to avoid divide-by-zero).
caf
A: 

unwind is right about fixed point.

I wrote a fixed library for SDCC on the 8051 that uses 32 bit numbers. Simply determine what precision you want in your fractions and apply the appropriate shift to the values.

For example, my fixed point library uses 2 bytes for fractional space.

So each number x is represented as x * 65535. You can use normal signed long addition and subraction.

For multiplication and division you need to adjust for the offset. Simple multiplication would give (x * 65535) * (y * 65535). Just break up and factor out the offset for each part of the numbers and add them all up.

Just break up your fixed point number into bytes or 16 bit ints and work on them in pieces.

Take a look at this article on embedded.com.

daotoad