Hello All.
I have a need to sniff an absolute encoder going to a machine at work.
It uses the SSI protocol where the controller sends clock pulses and the encoder sends back data bits at each rising edge of the clock that it sees.
Using a teensy, I started off simulating a master SSI device like the controller, then a Slave device like the encoder and moved on to the real purpose the Sniffer device. I decided to refactor out my pre-processor #defines into runtime selection of the mode and selection of the hardware. My point is I've spent some time on this.
To keep it simple for me, to keep track of clock edges I digitalReadFast() the clock line every loop and digitalReadFast() the data line on a rising edge and Serial.print() output on count change with rate limitting.
I nearly get away with this but loop timing occasionally goes a bit too high due to interrupts and i miss a clock transition.
I know(think) it is interrupts becuase I've tested and the time spike is not in yield()
Can i redo this with interrupts for clock detection or is there delay there too.
I like the sound of FTM, tried reading the relevant chapter in the hardware manual, looking at headers but I can't work it out. Is this the solution and does someone have an arduino wrapper type thing for the input mode of FTM I'm after, or can walk me through how I apply something from the hardware manual into code in general?
Is there some other hardware module i should be using for this?
Would changing to a teensy 4.0 help?
Green: Output from Teensy3.2 Toggled on clock rise detected
Yellow: Output toggled around Serial.print() and Serial.flush()
Red: RS485 levels of clock.
Blue: Output toggled at the start of every loop
Note the green shows 25 clocks detected. You can see from the green where it misses the clock. The loop also takes a bit longer at that point.
This is the function that loops continiously. I'm sure improvements could be made but I'm thinking my approach just isn't ideal for the task.
Thanks all.
Gavin.
I have a need to sniff an absolute encoder going to a machine at work.
It uses the SSI protocol where the controller sends clock pulses and the encoder sends back data bits at each rising edge of the clock that it sees.
Using a teensy, I started off simulating a master SSI device like the controller, then a Slave device like the encoder and moved on to the real purpose the Sniffer device. I decided to refactor out my pre-processor #defines into runtime selection of the mode and selection of the hardware. My point is I've spent some time on this.
To keep it simple for me, to keep track of clock edges I digitalReadFast() the clock line every loop and digitalReadFast() the data line on a rising edge and Serial.print() output on count change with rate limitting.
I nearly get away with this but loop timing occasionally goes a bit too high due to interrupts and i miss a clock transition.
I know(think) it is interrupts becuase I've tested and the time spike is not in yield()
Can i redo this with interrupts for clock detection or is there delay there too.
I like the sound of FTM, tried reading the relevant chapter in the hardware manual, looking at headers but I can't work it out. Is this the solution and does someone have an arduino wrapper type thing for the input mode of FTM I'm after, or can walk me through how I apply something from the hardware manual into code in general?
Is there some other hardware module i should be using for this?
Would changing to a teensy 4.0 help?
Green: Output from Teensy3.2 Toggled on clock rise detected
Yellow: Output toggled around Serial.print() and Serial.flush()
Red: RS485 levels of clock.
Blue: Output toggled at the start of every loop
Note the green shows 25 clocks detected. You can see from the green where it misses the clock. The loop also takes a bit longer at that point.
This is the function that loops continiously. I'm sure improvements could be made but I'm thinking my approach just isn't ideal for the task.
Code:
static inline __attribute__((always_inline)) void ssi_sniffer_loop()
{
for(;;)
{
toggle_debug3_state(); //Blue channel on scope
update_clock_status(); // updates clock_rise_detected and clock_fall_detected flags
if(clock_rise_detected)
{
clock_rise_detected = false;
//delayNanoseconds(SSI_TV * 500); //not needed. I think i misunderstood ssi spec
bool data_bit = read_data_bit();
add_bit_to_frame(data_bit);
toggle_debug1_state(); //Green channel on scope showing when clock rise was detected
}
else if (clock_fall_detected)
{
clock_fall_detected = false;
last_clk_fall = current_time;
clock_fall_processed = false;
}
uint32_t delta_clk_fall = current_time - last_clk_fall;
if (((delta_clk_fall) > 20) && clock_fall_processed == false) // clock has stopped to allow loading a new encoder count
{
/*
* At this point (in the case of sniffing the bosch rho3 controller talking to ssi encoder) I will have 12 bits of multiturn greycode position,
* 12 bits of singleturn greycode position, one power fail bit (should be a 0) and one extra bit that can be ignored*. 26 bits total.
*
* I need to remove this bit before decoding.
*
* * On the bosch rho3 the oscilliscope shows we clock out 25 bits. This matches the original Stegmann AG661 encoder datasheet.
* At some point these encoders became unavailable and we moved to the Sick AFM60E-S1AL004096 Ident-Nr 1037651.
* The manual for this specific encoder and the general ssi interface description where sick references this model both claim a data structure
* That can't be correct (27 bits or 30 bits) and no mention of the 24 bits (12 bits multiturn, 12 bits singleturn) that I see on the scope,
* That the working rho3 controller is working and using.
* This last bit could be usefull as an error bit for me to monitor, but if the datasheed cant get the number of singleturn bits correct
* I'm not going to interpret it as anything when i decode it.
*
*/
struct ssiData decoded_ssi = decode_ssi(ssi_frame>>1);//Decode ssi_frame minus the last bit read
/*
* Only print the encoder position to Serial terminal if the position has changed, or 'r' is in the serial buffer
* I do this here because this is only seen once after 20uS of no clock pulse so we are going to be in between SSI encoder transmissions.
* In the case of the Bosch rho3 we have 5ms before the next position is read from the encoder (200hz ssi frames).
*/
char serialInput = noWaitGetCharFromSerial();
if ((last_decoded_ssi.singleturn != decoded_ssi.singleturn) || (last_decoded_ssi.multiturn != decoded_ssi.multiturn)|| ( serialInput == 'r'))
{
toggle_debug2_state(); //Yellow channel on scope
print_encoder_data(decoded_ssi);
Serial.flush();
toggle_debug2_state(); //Yellow channel on scope
}
last_decoded_ssi = decoded_ssi;
clear_frame();
last_clk_fall = current_time;
clock_fall_processed = true;
}
//toggle_debug1_state(); //blue trace
yield();//Runtime mode/hardware selection costs me some hundreds of ns in switch statement. Never returing to loop() and manually calling yeild() avoids that cost after the first scan of loop()
//toggle_debug3_state();//green trace
}
}
Thanks all.
Gavin.