-1

I need to capture a single parallel 'snapshot' from a 10-bit shaft encoder. I understand that I can read individual bits and shift them into position, but any given input pin might change while reading the bits sequentially, which would give an bogus value. In fact, given the asynchronous nature of the device this is guaranteed to happen on occasion.

I've read hints that it might be possible to combine pins into a 'port' for this purpose but I can't find any examples. Is this ability available only on certain processors? Or is there some way to latch the input pins while scanning them into a pair of bytes, to avoid them changing while I read?

If not I suppose I could use external latches but I'm hoping to avoid additional hardware.

It did occur to me that I could sync readings using an ISR triggered by a change in the LSB, but alas, this encoder outputs Gray code. :(

4
  • 1
    Which encoder? It seems odd you have to read all bits at precisely the same moment. You haven't said which Arduino you have but most of them are 8-bit processors so the maximum you can read atomically is 8 bits.
    – Nick Gammon
    Commented Jan 18, 2017 at 0:38
  • @NickGammon It isn't so odd, it's just asynchronous. I didn't specify which Arduino in case there are only some that support this, but I'd lean toward the Uno. I realize that with 8-bit ports I'd have to make two reads but I can handle the possibility of data smear with only two sequential reads as opposed to ten.
    – Jim Mack
    Commented Jan 18, 2017 at 0:50
  • 1
    What encoder? Link to datasheet?
    – Nick Gammon
    Commented Jan 18, 2017 at 1:48
  • 1
    @NickGammon Sorry, but I don't see the point. I'm not asking anyone to diagnose at a different level or to solve any problems. It's a fairly simple question about reading data in parallel which could have many applications. Maybe we could forget the encoder and think of it more generically...?
    – Jim Mack
    Commented Jan 18, 2017 at 4:07

4 Answers 4

3

If you would like to read multiple inputs, you can access port register directly. For 8 bit AVR:

DDRA = 0;            // set pins A0-A7 as input, modeInpit() in Arduino
uint8_t data = PINA; // read all A0-A7 inputs (8 bits)

To read more than 8 bits, you need 32 bit ARM based MCU/board like Arduino Due, other Atmel SAM, STM32Fxxx MCU etc. For STM32, there is Arduino like IDE available - http://www.stm32duino.com/

Even on 32 bit MCU you can read only 16 GPIO simultaneously (GPIO registers are 32 bit but handles 16 GPIO).

3
  • Moving over here... the reason I'm asking about examples is that what I've read is inconsistent. Some docs, including the two you reference, use different names for the port registers. Is there a "port A" and a PINA instruction? Not in most docs, which say that "port C" is for the analog pins and PINC reads them. Also, you reference "GPIO_xx designations" which I can't locate in the docs I have, as a way to limit ambiguity. Any docs for that syntax?
    – Jim Mack
    Commented Jan 18, 2017 at 20:47
  • in AVR world, PINA and PORTA are both REGISTER names; they refer to same physical GPIOA (general purpose input output) pins belonging to A register (pin might designated as PA or GPIOA); for reading PINA is used, for writing PORTA register is used; so they are kinda aliases; same goes for PINB/PORTB, PINC/PORTC etc; exact physical pin and function (mostly) different on every MCU, so this is where the datasheet is needed; On Atmega328 UNO, PINC/PORTC (PC) are analog inputs (while on Atmega128 it is digital); GPIO is kinda universal naming for physical pins, IMHO it is preferred
    – Flanker
    Commented Jan 18, 2017 at 22:16
  • Nice drawings here for some Arduino GPIOs
    – Flanker
    Commented Jan 18, 2017 at 22:18
2

Without knowing the encoder in question (which you seem extremely reluctant to disclose for some reason that I have trouble understanding) it is hard to give a good answer. However ...


The 8-bit AVR processors can only read 8 bits atomically. This is indeed called a port. The Atmega328P (used in the Uno) organizes its I/O ports as ports B, C and D (I don't know where A went).

Exposed on the board are PORTB (bits 0 to 5), PORT C (bits 0 to 5) and PORT D (bits 0 to 7) - that's 20 pins in total.

Thus, port D is the closest you will get, as you could read 8 bits at once. However having said that, two of those bits are used for serial I/O so you couldn't use the serial hardware at the same time.

To read all of port D you could just do this:

byte foo = PIND;

(For inputting it is PINx, for outputting it is PORTx).


What could help is an I/O expander such as the MCP23017.

I'm hoping to avoid additional hardware

Well, that's a pity.


I'm a tiny bit surprised that the device doesn't support some sort of "latching" of the data which gives you a finite time to extract the information from it. However without reading the datasheet I can't comment further.

10
  • It's not reluctance to disclose, it's a desire to avoid distractions. I know the hardware and how it operates; I'm constrained to use it; so the problem domain is 'reading data in parallel'. Thanks for addressing that, and for the pointer to the I/O expander.
    – Jim Mack
    Commented Jan 18, 2017 at 4:52
  • Given the answer from @Flanker, would it be possible to combine a read of PORT D (which I gather uses the digital pins) with a read of PORT A (which I gather uses the analog pins) to get a 16-bit wide value in two reads?
    – Jim Mack
    Commented Jan 18, 2017 at 4:54
  • 1
    I know the hardware and how it operates; - you might, I don't.
    – Nick Gammon
    Commented Jan 18, 2017 at 6:00
  • ... with a read of PORT A - where did you find PORT A on the datasheet? I can't spot it.
    – Nick Gammon
    Commented Jan 18, 2017 at 6:01
  • 1
    it's a desire to avoid distractions - distractions like the people who are trying to answer your question knowing what hardware you are talking about?
    – Nick Gammon
    Commented Jan 18, 2017 at 6:03
2

I'm sure time has already passed for this persons project. But for those visiting this page for whatever reason (mine was different than the information here) here is a solution I would implement. Also this is assuming the individual is not willing to use a latched external buffer where they can read from it however they need to. Simply split the total pins among 2 registers making sure the following

  1. The pins used are in sequential order to both the MCU and the encoder (gray coding will require some thought to this).
  2. Group as many on a single register as possible and this will be the low order value.

Read from the low order value first (the one that will be changing at the highest rate). Then read from the high order value (where it will be far less likely to change between the low order and the high order read). You need to understand bit masks and bit shifting to do this...

uint16_t nEncoder = (PINB & 0x1F); // Example if your 5 pins are the low ones on PORTB

uint16_t nEncoder = ((PINB & 0x7C) >> 2); // Example if your 5 pins are 6:2 where you pull your set of pins off of PORTB then bitshift them down into their proper place.

then you read in the high order part

nEncoder |= ((PIND & 0x1F) << 5); // Example if your 5 pins are the low ones on PORTD. However you pull them off of the next port, you will need to make sure you bitshift them into the upper 10 bits of nEncoder to get your final value.

In the above, you can see that the more bits (pins) you can use on your first register (port), the less likely your second read will have a pin change.

Gray coding will be different than binary. The above example assumes binary where the high order bit will change the least and the low order the most. Gray coding is specifically used because of how minimally the bits change in-between each number change (i.e. they are suppose to only have 1 bit change per number change no matter the number rather than 10 bits change when a binary count rolls over - i.e. it minimizes this exact problem). So for the original poster, most of this should be irrelevant (since they said they were using a gray code encoder).

But for those seeking an optimal solution (for binary counters or just because) where they cannot stick all pins on a single port. Splitting it between two registers with only two reads will optimize your read time and minimize the pin change possibility.

0

I mean, you could look up the port and the pin in the port and read in the portByte. This example does work:

//given two GPIOs on a port(PortC) eg: 12(right button) & 13(left button)     
byte portByte = (*portInputRegister(digitalPinToPort(rightButtonGpio)));
rightButtonPinHigh = portByte & (digitalPinToBitMask(rightButtonGpio));
leftButtonPinHigh = portByte & (digitalPinToBitMask(leftButtonGpio));

However, in my experience, your output will look pretty much the same as if you had done two sequential Arduino blessed digitalRead. I suppose you might (in the example above) get the leftButtonHigh value 1 instruction ealier, but I don't know if the register can even update at that resolution.

1
  • 1
    digitalRead() can be super slow, something like 100 CPU cycles. In contrast, reading a port input register is a single cycle. And the register updates at the CPU clock speed, i.e. 16 million times per second on an Uno. Commented Jun 28, 2020 at 19:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.