Shazaam_Glomutron
Posts: 3
Joined: Thu Aug 24, 2017 7:19 pm

Reading from Multiple Encoders

Thu Aug 24, 2017 10:18 pm

I'm a total beginner when it comes to GPIO programming, which is unfortunate considering the project that I need to accomplish. I have a few questions for the group and they relate to how to get data from a specific encoder.
Here's a link to the encoder (I'll be using the 12-bit encoder, for maximum encoder resolution):
https://www.usdigital.com/products/enco ... /shaft/MA3

Here's a list of the power specs:
  • Parameter / Min. / Typ. / Max. / Units
    Power Supply / 4.5 / 5.0 / 5.5 / Volts
    Supply Current / - / 16 / 20 / mA
    Power-up Time / - / - / 50 / mS
The Pin Out is very simple:
  • Pin / Name / Description
    1 / 5 / +5VDC power
    2 / A / PWM output
    3 / G / Ground
This is the square wave math listed with the 12-bit encoder
  • 12-bit PWM:
    x = ((t on * 4098) / (t on+ t off)) -1
    If x <= 4094, then Position = x
    If x = 4096, then Position = 4095
So... the questions:
  • Q: How do I read a square wave via GPIO programming
  • Q: I assume I can do this with python, yes?
  • Q: Is it easy to do this with C++?
  • Q: Can I attach two encoders, power them both, and get unique data for them both?
  • Q: What's the frequency at which I'll get data from the encoders?
  • Q: Is this a defined by the computer clock speed, or the encoder speed?
  • Q: Is this something I can do on the RPi Zero?
Ideally, all I'm trying to do is get the encoder positions at any point in real time. I'd like to be able to wire at least two of these devices onto a single board and preferably I'd like to use the Raspberry Zero because of it's smaller form factor. Once I've collected the data, I'll be broadcasting it over the Ethernet port, because RJ45 allows for long cable runs without signal degradation.

Thank you,
Shazaam_Glomutron

dwelch67
Posts: 848
Joined: Sat May 26, 2012 5:32 pm

Re: Reading from Multiple Encoders

Fri Aug 25, 2017 12:56 am

mentioning python means you are not wanting to do bare metal? this is a bare metal forum not a python on an operating system forum. C++ is close to the same story but possible bare metal, better to just use C.

there are other encoders that would have been easier.

first and foremost the gpio pins are 3.3v, I am not sure if they are 5V tolerant. quick googling without any real confirmation sounds like they are not, so you would want/need a voltage divider.

sounds like you have the 12bit pwm model, it tells you in the data sheet that the frequency is 220 to 268 hz, that is pretty slow, but maybe still
too fast, dont know off hand would have to look around or do experiments, I would think it is okay.

ideally you want to measure rise to fall and fall to rise against some on chip (raspberry pi) timer. the gpio in the pi appears to have rise detect and falling detect so you could tie this to two gpio pins configure one to interrupt on rise one to interrupt on fall. and in each interrupt save the timer tick value, perhaps a little more work in how the data is stored, like a circular buffer perhaps with a high low mark in the data (say use 31 bits of the timer and one bit is high or low to indicate which direction caused that time stamp). the host code could then periodically examine part of the buffer and determine from four samples the high/low time and determine the percentage.

at the max frequency of 268hz that is a period of 3.73ms which is an eternity. take a 1ghz pi-zero that is 3.7 million processor clocks per clock on the pwm, should have time to deal with that. could probably even just poll the thing.

so maybe it isnt a bad encoder after all...interesting...i assume designed to simply amplify and directly feed into a motor or light or something like that.

dwelch67
Posts: 848
Joined: Sat May 26, 2012 5:32 pm

Re: Reading from Multiple Encoders

Fri Aug 25, 2017 1:17 am

sure you can get two of them, problem is as they drift relative to each other (see the datasheet it tells you there is an internal RC oscillator tuned to 5%...) you will have periods where the interrupts are landing on/near each other and the accuracy may or may not suffer, simply depends on how fast your handler ends up being.

encoders like these that output a gray code or some sort of similar code
https://www.sparkfun.com/products/9117
these basically use more I/O pins but dont change state unless the encoder moves, then every so many degrees one of the pins will change you can tell from the prior state to the current state which direction that pin moved. in software you +1 or -1 a variable that is keeping count and that is your position, the datasheet tells you how many degrees per state change so you can figure counts per full rotation, etc...

Not sure about the one you chose but some of these are infinite rotation so you dont know where you started, when you start up the software has to assume or have saved the last virtual position, etc. then +/-1 per state change from there.

same deal with the one you have have to look and see if it has stops or if it is infinite, what does it define as zero degrees, does that change on each power cycle, etc. read your datasheet that is what bare metal is all about the programming is the easy part.

there are many ways to skin this cat, other encoders that work similar or different to these. at the end of the day you end up with a number that represents a position in the rotation of the dial, and then you can forward that number upstream to whomever needs it.

you could also setup a periodic interrupt based on a pi timer and sample a single gpio pin and map out high and low from that end up with the same kind of deal a high time and low time and the ratio is the position. could sample both gpio pins from both encoders per interrupt. could just circular buffer it and let the foreground code wade through the long list of ones and zeros to find the edges and the ratio. could easily use python for the forground tasks for either/any of these solutions.

I know little about linux on the pi, it supposedly has pretty good hooks into the peripherals better than some other linux/arm platforms, so dont know how to hook the gpio interrupts.

the pi-zero doesnt have ethernet as far as I know, there is a wifi one. would need to add another $15 bucks or so for a pi hat or some usb thing after buying some more stuff to get at the usb. it is a bit of overkill for this task a microcontroller is better suited, there are a number of solutions out there off hand I know about a $20 board from ti with a cortex-m4 microcontroller on it EK-TM4C1294XL but if you are asking Phython/C++ questions then maybe maybe not on that, note if you are just using this as a front end to send the current state over ethernet, udp is very easy to generate packets, you can do a lot of cheating to make a udp stack, basically answer ARP, then have the host ask you for the position every so often, using say a 64 byte packet, you steal the ip and mac address from that request and just echo it back, you can generally get away with that and not actually arp them. swap the macs swap the ips swap the ports, stick the data in, dont have to compute the checksum i fyou dont want not required with udp, and spit that packet out. easy... otherwise you are integrating someone elses full blown stack

we are in the world of IoT so there are many wireless solutions that you can get for pi prices but without the shipping or you could add one of those to a $20 microcontroller board for under 10 have everything you need and not have to learn about ethernet/networking packets/protocols let the esp8266 or whatever do that for you. I am sure there is an easy to program calling apis solution using an arduino.

the pi will work at least one of them, ethernet, you would ahve to solve you could add an esp8266 or other similar device to it to get your wireless if you dont want to deal with the pi-zero W. but if you can pull this off running linux then the network stack is right there you just use it.

blah,blah,blah just rambling now...

LdB
Posts: 583
Joined: Wed Dec 07, 2016 2:29 pm

Re: Reading from Multiple Encoders

Fri Aug 25, 2017 4:09 am

The encoder given isn't ideal it's 5V the Pi IO is strictly 3.3V so there will need to be some interface work, it would be better if possible to get a 3.3V encoder as it would make it directly wireable to the pi.

With that sort of encoder you essentially just set the PWM output from the encoder as a GPIO interrupt source. On the given encoder you would get the ARM system timer on the lifting edge which they call zero and you would read it again when you get the falling edge. As the arm timer is running at 1Mhz it will closely match the value of the encoder. If you want exact you can increase the clock speed.

Strictly it will not work with raspbian linux and python the speed requirements for the interrupt response time and clock requirements are too fast. The normal response time for an interrupt under linux is around 10us, with some special playing around I have seen claims you can get it down to 3-5us. However for you that is still 3-5 counts at start and same at end. Then you have access to the system timer which will have a time for both reads. So it can't be done accurately but if the probably 100 position inaccuracy was acceptable you could use it. Basically the value will appear to jitter and you would need to roll the read value right by the jitter amount. Likely your 4096 position encoder will be something like 60-120 positions when you did it so the question is actually how many discrete positions do you need.

In Baremetal the entry time is roughly 120ns for an interrupt even faster if connected to the FIQ the clock access is a few nanosconds and you could resolve to basically +-1 count. It's pretty much a walk in the park.

If you really need python you may look at what the interrupt response times are for other O/S's on the PI because that is the real limiting factor you need to sort out.

User avatar
rpdom
Posts: 11865
Joined: Sun May 06, 2012 5:17 am
Location: Essex, UK

Re: Reading from Multiple Encoders

Fri Aug 25, 2017 4:39 am

I think I'd look to using a dedicated microcontroller for sampling the PWM inputs and connect it to the Pi via i2c or serial or something.

There have been discussions on this elsewhere. I just did a web search for "i2c PWM input".

One person suggested converting the PWM to analogue with low-pass filter and then using a standard ADC to give a reading, but the accuracy isn't perfect. But then you've already got 5% on the PWM.

LdB
Posts: 583
Joined: Wed Dec 07, 2016 2:29 pm

Re: Reading from Multiple Encoders

Fri Aug 25, 2017 8:10 am

If you are going to do something with hardware it would be easier to do an FPGA or CPLD with a 4Mhz xtal, the VHDL code is simple took me less than 100 lines. If board already has a clock easy enough to convert to req frequency.

Operation is take reset high then low to start. Then data_changed will go high with each new sample ready to read. Read the value and take data_clear high which will clear the signal. You have 4096us to do the read.

Nice easy job to start learning FPGA and VHDL on and no such thing as design error because you can change it.
iCE40 or Bugblat board or any other Pi FPGA hat should work ... Just saying :-)

Code: Select all

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

ENTITY read_encoder IS 

   PORT (
      --[ RESET SIGNAL INPUT ACTIVE LOW ]--
      reset: IN STD_LOGIC;

      --[ 4Mhz XTAL OSCILLATOR INPUT/OUTPUT ]--
      x1: IN STD_LOGIC;
	  x2: BUFFER STD_LOGIC;
      
      --[ SIGNAL SELECTION INPUTS ]--
	  PWM_input: IN STD_LOGIC;

      --[ READ ENCODER OUTPUTS ]--
	  data_clear: IN STD_LOGIC;
	  data_changed: OUT STD_LOGIC;
	  position: OUT STD_LOGIC_VECTOR (13 downto 0)
  
    );
END read_encoder;

ARCHITECTURE behaviour OF read_encoder IS

   --[ INTERNAL SIGNALS ] --
   SIGNAL in4Mhz: STD_LOGIC;
   SIGNAL counter_on: STD_LOGIC;
   SIGNAL latch_counter: STD_LOGIC;
   SIGNAL readout_avail: STD_LOGIC;
   SIGNAL data_avail_reset: STD_LOGIC;

   --[ STATE MACHINE SIGNALS ]--
   TYPE state_values IS (st0, st1);
   SIGNAL pres_state: state_values;
   SIGNAL int_pos: STD_LOGIC_VECTOR (15 downto 0); 
   
BEGIN

   --------------------------------------------------------------------------
   -- CREATE 4Mhz XTAL OSCILLATOR PROCESS
   --------------------------------------------------------------------------
   oscilator: PROCESS (x1, x2)
   BEGIN
	   x2 <= NOT x1;                                   -- create inverter
       in4Mhz <= x2;                                   -- create internal oscilator out
   END PROCESS oscilator;

   --------------------------------------------------------------------------
   -- PWM Lifting signal starts clock
   --------------------------------------------------------------------------
   start_count: PROCESS (reset, PWM_input) 
   BEGIN
      IF (reset = '1') THEN
		 counter_on <= '0';							   -- counter on set to off
      ELSIF rising_edge (PWM_input) THEN
         counter_on <= '1';							   -- counter starts on rising edge
      END IF;
   END PROCESS;

   --------------------------------------------------------------------------
   -- PWM Falling signal latches count
   --------------------------------------------------------------------------
   stop_count: PROCESS (reset, PWM_input) 
   BEGIN
      IF (reset = '1') THEN
		 latch_counter <= '0';							-- counter data latch clear
      ELSIF falling_edge (PWM_input) THEN
         latch_counter <= '1';							-- counter latch signal
      END IF;
   END PROCESS;

   --------------------------------------------------------------------------
   -- Data_Changed reset signal created by OR of reset and Data_Clear
   --------------------------------------------------------------------------
   data_ready_reset: PROCESS (reset, data_clear) 
   BEGIN
		 data_avail_reset <= reset or data_clear;        -- 2 input OR gate
   END PROCESS;

   --------------------------------------------------------------------------
   -- Data changed signal output
   --------------------------------------------------------------------------
   data_change_output: PROCESS (data_avail_reset, readout_avail) 
   BEGIN
      IF (data_avail_reset = '1') THEN
		 data_changed <= '0';							-- Data changed set to zero
      ELSIF rising_edge (readout_avail) THEN
         latch_counter <= '1';							-- Data changed set to one
      END IF;
   END PROCESS;

   --------------------------------------------------------------------------
   -- STATE MACHINE PROCESS
   --------------------------------------------------------------------------
   state_machine: PROCESS (counter_on, latch_counter, in4Mhz)
   BEGIN
       IF (counter_on = '0') THEN				        -- reset is active
		   pres_state <= st0;                           -- set initial state ST0 aka counting 
		   readout_avail <= '0';		                -- clear readout available
		   int_pos <= (OTHERS => '0');		            -- zero position 
	   ELSIF rising_edge (in4Mhz) THEN                  -- lifting clock edge
           CASE pres_state IS
			  WHEN st0 =>				                -- [ st0 = count state ]
			     IF (latch_counter = '1') THEN
				    pres_state <= st1;                  -- move to set readout avail state
					position <= int_pos (15 downto 2);	-- transfer top 14 bits of position out
				 ELSE
 				    pres_state <= st0;                  -- state unchanged
					int_pos <= std_logic_vector( unsigned(int_pos) + 1 ); -- increment position
				 END IF;
			  WHEN st1 =>                               --[ st1 = set readout avail ]--
				 readout_avail <= '1';                  -- set readout available
				 pres_state <= st0;                     -- move to next state
		   END CASE;
	   END IF;
	END PROCESS state_machine;

END behaviour;

dwelch67
Posts: 848
Joined: Sat May 26, 2012 5:32 pm

Re: Reading from Multiple Encoders

Fri Aug 25, 2017 1:43 pm

LdB wrote:
Fri Aug 25, 2017 4:09 am
Strictly it will not work with raspbian linux and python the speed requirements for the interrupt response time and clock requirements are too fast. The normal response time for an interrupt under linux is around 10us, with some special playing around I have seen claims you can get it down to 3-5us. However for you that is still 3-5 counts at start and same at end. Then you have access to the system timer which will have a time for both
the requirement is in milliseconds so linux is about 1000 times faster than it needs to be to catch these interrupts, correct? if you are saying linux can respond in 3us and the encoder uses a 3.8ms period for each of the pwm steps you should have no problem.

LdB
Posts: 583
Joined: Wed Dec 07, 2016 2:29 pm

Re: Reading from Multiple Encoders

Fri Aug 25, 2017 2:13 pm

Look again at the data sheet the pulses counts are 1 MICROSECOND you have to count those pulses. Even if I assume a 3us linux response you already lost 0,1,2 or 3 counts before you start, you will also have the same at end. If it was a constant amount it would be nice but it won't be it will be the full range.

The response time isn't the issue you have to count MICROSECONDS look at the timing again.

The pseudocode goes
PWM LIFTING EDGE -> START TIME COUNT (1MHZ pulse count start)
PWM FAILLING EDGE -> STOP TIME COUNT (1Mhz pulse count stop)

How many 1MHZ pulses did you count between START/STOP = ENCODER position

The PI has a nice 1Mhz clock so that bit is easy read time at start, read time at end subtract two times = encoder position.

So at best you can possible do is X +-6 counts .. assuming that your encoder can only resolve
4096/6 = 682 positions on the 12 Bit version
1024/6 = 170 positions on the 10 Bit version

I suspect the jitter will be likely worse than that because the interrupts will be coming continuously. So as I said if a couple hundred positions per rev is acceptable then it will be fine but under no situation can you do encoder resolution because the last 1 sometimes 2 digits will flicker around. If you just using it as a knob it's probably okay but if you are trying to run a servo off it at resolution forget it. If you want to do multiple of them it also gets worse because the interrupts will collide.

You need a hardware help if you want no jitter. In the VHDL code I took the clock up to 4MHz to make sure I clocked each edge and the response time for the clocking will be a couple of nanoseconds. So my accuracy is +- 0 clocks it's as accurate as your clock speed. The other option is a gated pulse counter hardware that some micros might carry. They basically do the same thing they allow you to count some internal clock pulses between a start and stop signal.

The other possible answer if you need the resolution is change to a standard quadrature square wave line encoder. The jitter will be in the deadtime between edges. The limit is the rotation speed will be limited to the jitter so around 3 us per line max speed.

As an embedded coder monkey I am used to doing these sorts of calculations day in day out. Baremetal I could make it but any sort of O/S you will struggle with.

dwelch67
Posts: 848
Joined: Sat May 26, 2012 5:32 pm

Re: Reading from Multiple Encoders

Sat Aug 26, 2017 2:00 am

if that is what it says then yes, wont work...

dwelch67
Posts: 848
Joined: Sat May 26, 2012 5:32 pm

Re: Reading from Multiple Encoders

Sat Aug 26, 2017 2:10 am

yep I see where I made my mistake...

User avatar
OutoftheBOTS
Posts: 442
Joined: Tue Aug 01, 2017 10:06 am

Re: Reading from Multiple Encoders

Sun Aug 27, 2017 11:22 am

For me using this type of encoders is a difficult type of encoder to use for RPi. I would think it would be easier to analog encoders (Potentiometer ) with a ADC. I personnel use the ADS1115 it is a 16 bit 4 channel I2C ADC. You could have 4 analog encoders hooked up to each ADS1115 and anytime you need to know the position of any of the encoders you just read the 2 register (high , low byte) on the ADS1115.

This would be both cheaper and easier.

User avatar
OutoftheBOTS
Posts: 442
Joined: Tue Aug 01, 2017 10:06 am

Re: Reading from Multiple Encoders

Sun Aug 27, 2017 11:26 am

The reality is the encoder that your trying to use is probably built from a Potentiometer with an ADC that converts the analog to a digital PWM wave. It is easier if the ADC converts to a digital value that is stored in a register to be read easily rather than it is converted to a PWM then you read the PWM to convert the PWM to a digital number to store in RPi memory. It is sort of a long way around to get to the goal you want

LdB
Posts: 583
Joined: Wed Dec 07, 2016 2:29 pm

Re: Reading from Multiple Encoders

Sun Aug 27, 2017 2:59 pm

Agree with your first post but nope the encoders are not just a pot it's all there on the datasheet.

Code: Select all

Non-contacting magnetic single chip sensing technology

The MA3 absolute encoder contains a small internal magnet, mounted on the end of the shaft that 
generates a weak magnetic field extending outside the housing of each encoder. If two MA3 units 
are to be installed closer than 1 inch apart (measured between the center of both shafts), a magnetic 
shield, such as a small steel plate should be installed in between to prevent one encoder from causing
 small changes in reported position through magnetic field cross-talk.
They are designed around electrical isolation as the main thing so either very heavy electrical noise enviroments like x-rays, welders and plasma cutters. Or say for electrical safety where an electronics failure needs to be contained.

The only equivalent solutions would probably be optical encoders but whether any of that is important would need the OP to say.

piras77
Posts: 148
Joined: Mon Jun 13, 2016 11:39 am

Re: Reading from Multiple Encoders

Sun Aug 27, 2017 3:29 pm

Shazaam_Glomutron wrote:
Thu Aug 24, 2017 10:18 pm
How do I read a square wave via GPIO programming
By sampling the level or by edge detection (I feel like in school again).
Shazaam_Glomutron wrote:
Thu Aug 24, 2017 10:18 pm
I assume I can do this with python, yes?
You can try.
Shazaam_Glomutron wrote:
Thu Aug 24, 2017 10:18 pm
Is it easy to do this with C++?
Sure.
Shazaam_Glomutron wrote:
Thu Aug 24, 2017 10:18 pm
Can I attach two encoders, power them both, and get unique data for them both?
Absolutely.
Shazaam_Glomutron wrote:
Thu Aug 24, 2017 10:18 pm
What's the frequency at which I'll get data from the encoders?
Its in the datasheet.
Shazaam_Glomutron wrote:
Thu Aug 24, 2017 10:18 pm
Is this a defined by the computer clock speed, or the encoder speed?
The encoder provides the data at the encoder's speed. The computer samples the data at the computer's speed.
Shazaam_Glomutron wrote:
Thu Aug 24, 2017 10:18 pm
Is this something I can do on the RPi Zero?
Yes.

piras77
Posts: 148
Joined: Mon Jun 13, 2016 11:39 am

Re: Reading from Multiple Encoders

Sun Aug 27, 2017 3:35 pm

rpdom wrote:
Fri Aug 25, 2017 4:39 am
I think I'd look to using a dedicated microcontroller,,,
Well, there isn't always this option. I would guess it is possible on a Pi, even in userland. Set up the Event Detect Register, poll for the edges (a tight loop manages 10M queries per second), and take the time. This will block the thread for at least 4.098ms. If interrupted or scheduled, it might be necessary to retry. So a multi core Pi may be the better choice.

User avatar
OutoftheBOTS
Posts: 442
Joined: Tue Aug 01, 2017 10:06 am

Re: Reading from Multiple Encoders

Sun Aug 27, 2017 8:23 pm

So what is the purpose that your using them for?

Is it just for the purpose of having 2 dials that the RPi zero can read the position of. What is the reason that u have chosen to use this partially really hard to use encoder?

I did a course about building computer systems it was run by 2 PHDs, 1 of the guys designs chip-sets and the other is a assembler programmer that designs compilers and OS, they were very smart engineers. The quote they kept pushing when designing was a leonardo da vinci quote "Simplicity is the ultimate sophistication", I have another friend that is a engineer and he says to me "any engineer can design a complex machine to do a complex task, only a great engineer can design a simple machine to do a complex task"

Is it possible to use bare metal programming to read 2 time critical PWM waves with RPi , maybe
Is it possible to use 2 embedded microcontrollers to read the PWM and con vert it to a binary number to send to the RPi, for sure
Both of these seem to be very complex when they maybe a simpler way to engineer the machine to do the needed task.

LdB
Posts: 583
Joined: Wed Dec 07, 2016 2:29 pm

Re: Reading from Multiple Encoders

Mon Aug 28, 2017 6:15 am

Answer to your question yes you could read those baremetal but it will take about half one of the CPU core power to do it with no jitter. So if you could afford to give up one of the four cores of a Pi2 or Pi3 it would not be a problem. You can't do it under linux the timing requirements are too strict. Yes there are a number of micros with pulse counter hardware a couple in the PIC series spring to mind and I think a couple even have duals so you could do it with 1 micro.

The rest of your post only the OP can answer. So there are two options I guess the OP has made a bad selection or they need the isolation which is why those devices exist and the market they fill. They are definitely one the simpler ways of doing a full isolated potentiometer (the other being a optical slit wheel) and as you say always opt for simplicity. To try and get power to the isolated side is a pain if you try and do it as a normal PWM circuit and I would take one of those devices any day over that.

piras77
Posts: 148
Joined: Mon Jun 13, 2016 11:39 am

Re: Reading from Multiple Encoders

Mon Aug 28, 2017 6:32 am

LdB wrote:
Mon Aug 28, 2017 6:15 am
You can't do it under linux the timing requirements are too strict.
Sure you can, why shouldn't one?

The period of a pulse is 4098us (datasheet p.7). You have to detect the edges or sample-and-count.

For example, with a tight busy loop you can scan the event detect register several million times per second, so you get the location of the edges fairly pecisely. The SOC provides counters with up to a few ns resolutions to take the time (ARM counter). The "only" problem are process suspension and interruption. You have to detect those (i.e. by checking the involuntary context switch count) and retry if so.

You could also sample the pin levels by DMA. A preferred solution on a single core. Though this would take a bit of memory since you need two DMA control blocks per sample (sample + time) or three (read event, take time, reset event).

LdB
Posts: 583
Joined: Wed Dec 07, 2016 2:29 pm

Re: Reading from Multiple Encoders

Mon Aug 28, 2017 7:20 am

Ok gotcha so you are sort of resolving it down to the same baremetal solution as I would use. Sorry well outside my ability to beat linux into submission like that but I can see how it would work.

If you had a sample or link of doing that sort of thing with linux I would be interested because there are a couple of situations I could then use linux :-)

piras77
Posts: 148
Joined: Mon Jun 13, 2016 11:39 am

Re: Reading from Multiple Encoders

Mon Aug 28, 2017 1:03 pm

This is straight forward. I added it this morning to my lib since I was also a bit curious. It should be quite clear from context:

Code: Select all

    auto t0 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,true) ;
    gpio.reset(mask) ; // clears flags
    while (0 == (gpio.getEvents() & mask))
	t0 = counter.clock() ;
    auto t1 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,false) ;

    auto t2 = t1 ;
    gpio.enable(mask,Rpi::Gpio::Event::Fall,true) ;
    gpio.reset(mask) ;
    while (0 == (gpio.getEvents() & mask))
	t2 = counter.clock() ;
    auto t3 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Fall,false) ;

    auto t4 = t3 ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,true) ;
    gpio.reset(mask) ;
    while (0 == (gpio.getEvents() & mask))
	t4 = counter.clock() ;
    auto t5 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,false) ;

    std::cout <<       0 << '+' << (t1-t0) << ' '
	      << (t2-t0) << '+' << (t3-t2) << ' '
	      << (t4-t0) << '+' << (t5-t4) << '\n' ;
That results for 1:4094 pulses on a Pi Zero:

Code: Select all

0+3 3+12 40951+3
0+3 40961+1 81906+2
0+3 3+11 40951+2
0+3 12+1 40950+3
0+3 12+1 40951+2
0+2 11+2 40950+2
0+3 40936+2 81876+3
0+3 12+1 40950+3
0+2 40959+2 81900+3
0+3 3+11 40950+3
I used a period of 4095 since this is easy to set up with PWM. The first pair of numbers describes the rising edge, the second one the falling edge and the last one the rising edge again (all in 1/10 us).Three times out of ten the falling edge (which came within 1us) was missed. Wider pulses shouldn't be that problematic. There is also still room to improve the code. Anyway, you can detect the problem and retry (since the seconds rising edge is not within 4096us but doubles). Not apparent here, but also an issue, if the duration of an edge takes too long (due to interrupt/pre-emption). Then you also would like to retry.

For 4094:1 pulses:

Code: Select all

0+3 40940+2 40950+3
0+3 40940+2 40950+3
0+3 40940+1 40950+3
0+3 40940+2 40950+2
0+3 40940+1 40950+3
0+2 40941+2 40951+2
0+3 40940+2 40950+3
0+2 40939+2 40950+3
0+2 40939+1 40949+3
0+3 40940+2 40950+2
This is from my lib. A complete log is attached below if somebody wants to reproduce.

Code: Select all

git clone https://github.com/ma16/rpio.git
cd rpio/cc
git checkout aafd427ab0dc7e5acea3eec1eb96963ce3c3a514
# ...use this hash, the lib changes quite fast so later version may be different

# make requires libboost-dev 
make
# ...this may take tens of minutes on a Pi Zero
# ...GCC 4.9 did not compile, fallback to GCC 4.8 worked fine
# as result there is a binary Console/rpio

# Setup CM with 1M/s for PWM
rpio cm div pwm intgr 500 fract 0
rpio cm ctl pwm src 6 +enab

# Setup PWM channel 1 @ pin 12 with Range=4095us Data=1us
rpio pwm range 1 4095
rpio pwm data 1 1
rpio pwm control +msen1 +pwen1
rpio gpio mode 12 0

# Setup ARM Counter clock for 100ns
rpio clock set on 24

# detect the edges on pin 12 ...
seq 1 1 10 | while read ; do rpio sample pulse 12 ; done
0+3 3+12 40951+3
0+3 40961+1 81906+2
0+3 3+11 40951+2
0+3 12+1 40950+3
0+3 12+1 40951+2
0+2 11+2 40950+2
0+3 40936+2 81876+3
0+3 12+1 40950+3
0+2 40959+2 81900+3
0+3 3+11 40950+3

# Set PWM Data=4094
rpio pwm data 1 4094

# detect edges again...
seq 1 1 10 | while read ; do rpio sample pulse 12 ; done
0+3 40940+2 40950+3
0+3 40940+2 40950+3
0+3 40940+1 40950+3
0+3 40940+2 40950+2
0+3 40940+1 40950+3
0+2 40941+2 40951+2
0+3 40940+2 40950+3
0+2 40939+2 40950+3
0+2 40939+1 40949+3
0+3 40940+2 40950+2

Shazaam_Glomutron
Posts: 3
Joined: Thu Aug 24, 2017 7:19 pm

Re: Reading from Multiple Encoders

Wed Sep 13, 2017 11:33 pm

You guys are amazing! :D :o

We are quickly converging on the probability of doing all of this on bare metal (no OS). First, thank you for letting a noob post here with what may have seemed off topic. Second... the depth of your answers and discussions has been illuminating. Rock on baddasses!

The encoder we're using fits our initial needs for client-use purposes (though not necessarily for development purposes). It's size and encoder resolution are very important. I predict the small jitter won't be noticeable when the output is applied as data lookup on a larger table. The encoder output will be normalized. AN incremental encoder would have been better, but I couldn't find one that was small enough and offered enough native resolution. For this project I can't use simple gearing to increase the effective rotary resolution, which would have been an ideal solution to and allowed for a wider variety of encoders.

In terms of bare metal and python, we've hit the limit on python operational frequency (maxing out at ~40kHz). :evil: :x

I haven't considered a connected microcontroller, but will look in to that.

I like the RPi Zero W because it does have WiFi output which will be important for client-use when in operating environment.
This application will not control another physical device. It is mainly a listening device that crunches the data and broadcasts normalized position over a LAN. Additionally, it's cableless... bonus. and small.... very small... which is important since this is an application that will be retrofitted onto client equipment, so it can't be heavy or bulky since real-estate on the client equipment is sparse, and the equipment itself is expensive. Also, I can attach a small screen to it as well as buttons and tiny joystick https://learn.adafruit.com/adafruit-128 ... i/overview

The standard operational rotation velocity of the encoders will not be very fast. At max rotational velocity, I expect 1080 degrees in 1.5 seconds. Standard rotational velocity is estimated at 360 degrees / 1 second, with long periods of not rotation at all.

Thank you and I'll respond back with more as the project progresses...

Thank you!!!!

Shazaam_Glomutron
Posts: 3
Joined: Thu Aug 24, 2017 7:19 pm

Re: Reading from Multiple Encoders

Wed Sep 13, 2017 11:43 pm

piras77 wrote:
Mon Aug 28, 2017 1:03 pm
This is straight forward. I added it this morning to my lib since I was also a bit curious. It should be quite clear from context:

Code: Select all

    auto t0 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,true) ;
    gpio.reset(mask) ; // clears flags
    while (0 == (gpio.getEvents() & mask))
	t0 = counter.clock() ;
    auto t1 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,false) ;

    auto t2 = t1 ;
    gpio.enable(mask,Rpi::Gpio::Event::Fall,true) ;
    gpio.reset(mask) ;
    while (0 == (gpio.getEvents() & mask))
	t2 = counter.clock() ;
    auto t3 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Fall,false) ;

    auto t4 = t3 ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,true) ;
    gpio.reset(mask) ;
    while (0 == (gpio.getEvents() & mask))
	t4 = counter.clock() ;
    auto t5 = counter.clock() ;
    gpio.enable(mask,Rpi::Gpio::Event::Rise,false) ;

    std::cout <<       0 << '+' << (t1-t0) << ' '
	      << (t2-t0) << '+' << (t3-t2) << ' '
	      << (t4-t0) << '+' << (t5-t4) << '\n' ;
That results for 1:4094 pulses on a Pi Zero:

Code: Select all

0+3 3+12 40951+3
0+3 40961+1 81906+2
0+3 3+11 40951+2
0+3 12+1 40950+3
0+3 12+1 40951+2
0+2 11+2 40950+2
0+3 40936+2 81876+3
0+3 12+1 40950+3
0+2 40959+2 81900+3
0+3 3+11 40950+3
I used a period of 4095 since this is easy to set up with PWM. The first pair of numbers describes the rising edge, the second one the falling edge and the last one the rising edge again (all in 1/10 us).Three times out of ten the falling edge (which came within 1us) was missed. Wider pulses shouldn't be that problematic. There is also still room to improve the code. Anyway, you can detect the problem and retry (since the seconds rising edge is not within 4096us but doubles). Not apparent here, but also an issue, if the duration of an edge takes too long (due to interrupt/pre-emption). Then you also would like to retry.

For 4094:1 pulses:

Code: Select all

0+3 40940+2 40950+3
0+3 40940+2 40950+3
0+3 40940+1 40950+3
0+3 40940+2 40950+2
0+3 40940+1 40950+3
0+2 40941+2 40951+2
0+3 40940+2 40950+3
0+2 40939+2 40950+3
0+2 40939+1 40949+3
0+3 40940+2 40950+2
This is from my lib. A complete log is attached below if somebody wants to reproduce.

Code: Select all

git clone https://github.com/ma16/rpio.git
cd rpio/cc
git checkout aafd427ab0dc7e5acea3eec1eb96963ce3c3a514
# ...use this hash, the lib changes quite fast so later version may be different

# make requires libboost-dev 
make
# ...this may take tens of minutes on a Pi Zero
# ...GCC 4.9 did not compile, fallback to GCC 4.8 worked fine
# as result there is a binary Console/rpio

# Setup CM with 1M/s for PWM
rpio cm div pwm intgr 500 fract 0
rpio cm ctl pwm src 6 +enab

# Setup PWM channel 1 @ pin 12 with Range=4095us Data=1us
rpio pwm range 1 4095
rpio pwm data 1 1
rpio pwm control +msen1 +pwen1
rpio gpio mode 12 0

# Setup ARM Counter clock for 100ns
rpio clock set on 24

# detect the edges on pin 12 ...
seq 1 1 10 | while read ; do rpio sample pulse 12 ; done
0+3 3+12 40951+3
0+3 40961+1 81906+2
0+3 3+11 40951+2
0+3 12+1 40950+3
0+3 12+1 40951+2
0+2 11+2 40950+2
0+3 40936+2 81876+3
0+3 12+1 40950+3
0+2 40959+2 81900+3
0+3 3+11 40950+3

# Set PWM Data=4094
rpio pwm data 1 4094

# detect edges again...
seq 1 1 10 | while read ; do rpio sample pulse 12 ; done
0+3 40940+2 40950+3
0+3 40940+2 40950+3
0+3 40940+1 40950+3
0+3 40940+2 40950+2
0+3 40940+1 40950+3
0+2 40941+2 40951+2
0+3 40940+2 40950+3
0+2 40939+2 40950+3
0+2 40939+1 40949+3
0+3 40940+2 40950+2
Holy DogBalls... That's Amazing Work! And really friggin generous!

It'll take some time to thoroughly grok this, but I'll respond back with progress.

Return to “Bare metal”

Who is online

Users browsing this forum: No registered users and 3 guests