FWIW Gray Code state machine/truth table with self modifying code
pedward
Posts: 1,642
Hi all, I was reading an article on quadrature encoders and decided to make a spreadsheet that determines the direction of a gray code encoder. I looked at the PID code that Kwabena W. Agyeman wrote for reading encoders and thought I'd share this bit. The reason I'm rehashing a topic is that I think encoder calculations can be made faster with my method.
Here is the table, I had to make it a link because the forum migration totally borked tables.
apsoft.com/~pedward/graycode_table.html
I will attach the spreadsheet too.
The current code in K's object is:
My proposed code:
Here is the table, I had to make it a link because the forum migration totally borked tables.
apsoft.com/~pedward/graycode_table.html
I will attach the spreadsheet too.
The current code in K's object is:
mov buffer, ina ' Sample left and right inputs. test buffer, leftEncoderPin wc ' test buffer, rightEncoderPin wz ' muxc encoderCurrent, #2 ' Store left and right inputs. muxnz encoderCurrent, #1 ' initializeOnce mov encoderPrevious, encoderCurrent ' Initializes previous state once. mov initializeOnce, #0 ' cmp encoderPrevious, encoderCurrent wz ' Update current state. if_nz rev encoderPrevious, #30 ' if_nz xor encoderPrevious, encoderCurrent ' if_nz cmpsub encoderPrevious, #2 wc, nr ' if_nz sumc encoderPosition, #1 ' wrlong encoderPosition, positionAddress ' mov encoderPrevious, encoderCurrent ' Update previous state.
My proposed code:
' previous in low half of nibble and current in high half of nibble ' 0 1 2 3 4 5 6 7 8 9 A B C D E F GrayCode long 0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0 mov buffer, ina ' Sample left and right inputs. test buffer, leftEncoderPin wc ' test buffer, rightEncoderPin wz ' muxc update, #8 ' Store left and right inputs. muxnz update, #4 ' update adds encoderPosition, GrayCode 'self modifying code muxc update, #2 ' Store previous value muxnz update, #1 ' wrlong encoderPosition, positionAddress '
Comments
I like the table based idea. Much better!
Thanks,
The Logic is like this:
A simple wave drive stepper waveform would be:
0001
0010
0100
1000
If you index that into an array, you get:
Curr Next
0001 0010
0010 0100
0100 1000
1000 0001
I omitted the other entries, but you end up with a 16 entry table of 4 bits each. You use the current port value as the index to read the next port value. I used pointers for direction and had an up and down table.
After the Gray code exercise last night, I thought about making a TTL gray code counter but alas the 7400 competition is over! :frown:
Here is a chunk of sample code I ran through the compiler:
Here is the corresponding compiler output:
Thanks for the feedback. This is the first PASM I've written, I learned ASM on the x86 about 17 years ago.
Also, res should only be used at the end of the code. And while we are here, in your case you want jmp #init (direct jump).
A not quite, org doesn't relocate stuff for you, it just messes with the address reference for the assembler, i.e. even if you start a fragment with org 100 and pass it to cognew you usually don't get what you expect (as the first insn still ends up at address 0)
Here's a good way to build the table:
Thanks,
;o)
It is a way, but for me it's more like a hack than a good way. What is the benefit of running code vs. setting up the values directly? The extra waste of runtime? What's the problem putting the data at the end of the code?
Used up pretty much all of the cogs's power for this. Only 4 longs left of space and 8 clock cycles wasted every loop at 64 KHz...
NOTE: The posted driver is completely untested... the code has never been run. I'm only posting it as an example.
The application will be a classic double PID loop, the inner controller will be a current mode PID controller for a DC brushed motor control, then an outer position control loop. I've been exposed to a number of motor control systems, current mode, velocity mode, and systems that do both of those at multiple levels.
For a design where you don't have a tachometer, the current mode, with encoder feedback, seems the simplest and most reliable.
I've already got a quad H-bridge driver I made last year that has 4 push-pull MOSFET channels and drivers. I'll use this to try and PWM control a motor. I'm taking the approach of using dedicated low pin count micros to handle the peripheral control, letting the Propeller handle higher level tasks.
My goal is to make a couple of peripheral devices that work in conjunction with the Propeller to form a robust CNC motion control platform. Using dedicated micros allows me to increase the peripheral count without increasing the pin usage.
I want to make a PCB router and a PnP machine.
For the motor controller IC, I intend to use the standard servo protocol to drive direction and current, and make the micro implement the current PID control.
I'm using the 68HC908QT4 and QY4 chips for this project.
Man that's clever!
Okay, I'll integrate this right now
Thanks,
I want to do that in 6 instructions... but, the CMPSUB instruction does not set Z=1 if pwm0Cycle=0, only when pwm0Cycle=1. This means that the code will lock up if pwm0Cycle gets set to 0 if I am not doing that compare.
I guess I could pass a dira variable to the driver. But, that means more overhead for the code that will control this driver. My goal is to keep the upper interface as simple as possible.
I think I'll leave the speed at 64 KHz for now. Even though I can now hit 75 KHz it does not cleanly divide by 256 (I'm not worried about jitter by this because the master 75 KHz frequency will not have jitter... but, I want a nice clean number to report instead).
The cog will consume less power I guess now
---
If I could get that down to 6 instructions from 8 then I could hit 80 KHz.
I notice that you test the 'quadrature' bit for every iteration, this is unnecessary because you aren't going to change encoder types on the fly. Change that RDLONG into a TEST.
I thought centralizing DIRA would be a good idea too, until I considered the actual run. If you polled the DIRA value every cycle, that's ~64,000 HUBops, whereas the distributed DIRA checking is done only when the counter expires, which should be many less HUBops per second.
My new code should reduce your instruction count, and if you change the RDLONG to a TEST, it will speed it up more.
Normally it's 3 instructions through, 7 when the counter reloads.
You're only hitting the HUB window on counter reload anyway, so it's not deterministic.
The waitcnt makes it deterministic.
Just noticed that the update for Onoff is slightly re-ordered but maybe it's still workable.
Wait for pinmask to not equal last read value
read encoder pins into "pins"
copy pins to "pinstmp"
repeat 14 times
process lowest 2 bits of pins
update pinCNTx
shift pins right by 2
repeat 14 times
copy pinCNTx block to HUB
copy "pinstmp" into "oldpins"
You could do all the same, just remove the WAITPNE and process the mask by shift until done, interleav the WRLONG calls however you want to not waste HUB clocks.
@pedward - I have to save cogs so I need to double up the processing here with the PWM. My goal is to have really clean output and input so I have to use the waitcnt to make sure the output and direction registers change their state always at the same time. I don't want any jitter.
Thanks,
14 regular/quadrature encoder channels and 15 PWM channels in one cog with a sample resolution of 80 KHz (assuming the clock frequency is 96 MHz).