Fast polling of a digital pin, immediate read of another when it rises

For an AVR (ATMEGA328Por ATTINY of some form) running at 16MHz clocking, is there any faster way to poll a pin until it changes than this (see below) subject to the following requirements:

1.Another pin is to be read as soon as possible after the monitored pin has risen to high.

2.If the pin doesn't go HIGH within a certain time limit, the code should continue, not tay stuck in a while loop forever.

3.After the pin has gone high the code should be able to tell whether the waiting function ended with the pin rising or with the timing out condition.

I tried using a pin change interrupt on the monitored pin, but the time between it rising and the reading of the other pin was pretty long. My use case doesn't need the AVR to be in this state too often, so can tolerate "wasting some clock cycles" doing nothing but waiting and polling the monitored pin, I don't need an interrupt here.

Thank you

 //X,Y,Z and W to be set as appropriate for the pins chosen as A and B      
#define FastPinARead ( (PINX & _BV (Y)) ) 
#define FastPinBRead ( (PINZ & _BV (W)) )
#define InwardTimeout 170
//wait until PinA is high
uint8_t value=0;
uint8_t InwardTiming=0;
for(InwardTiming=0; InwardTiming< InwardTimeout; InwardTiming++){//delays until pin rises, or until too much time elapses
  if(FastPinARead){
    break;
  }
}

if(FastPinBRead){
  value=1;
}else{
  value=0;
}

The posted code certainly won't work.

This is how I would read pin 0 on PORTB into the variable "value"

value = PINB&1;

Which aspect of it won't work? Thanks

It has been working for me with particular pins given using the x,y,z,w values.

Note that the FastPinBRead definition returns a 0 when the pin is low and a 1 when it is high.

Is there anything about the for loop which can be made faster to exit when the pin changes and faster to loop round (I can raise the timeout value then if I still need it as long) so it will detect a change on PinA sooner after that change happens.

Thanks

The posted code won't compile without errors. The error messages should be informative.

This was a snippet copied out of a longer piece of code of mine. The longer version compiles and runs, I've verified with an oscilloscope that it does respond to a rise of PinA, and then reads PinB (i had it toggle another pin just after reading PinB so I could see that on the scope's other channel), but I want to know if it can be made faster.
Thanks

Don't poll, use pin change interrupt.

1 Like

That would be significantly slower (assuming that you can actually stop doing everything else while poling.)

Let's see... Fiddle, fiddle... The loop compiles to:

  for (InwardTiming = 0; InwardTiming < InwardTimeout; InwardTiming++) { //delays until pin rises, or until too much time elapses
    if (FastPinARead) {
 2ee:   48 99           sbic    0x09, 0 ; 9
 2f0:   02 c0           rjmp    .+4             ; 0x2f6 <setup+0x1e>
 2f2:   81 50           subi    r24, 0x01       ; 1
#define FastPinBRead ( (PINB & _BV (1)) )
#define InwardTimeout 170
  //wait until PinA is high
  uint8_t value = 0;
  uint8_t InwardTiming = 0;
  for (InwardTiming = 0; InwardTiming < InwardTimeout; InwardTiming++) { //delays until pin rises, or until too much time elapses
 2f4:   e1 f7           brne    .-8             ; 0x2ee <setup+0x16>
  if (FastPinBRead) {
 2f6:   83 b1           in      r24, 0x03       ; 3

That looks quite good; I don't see doing any faster, using code.

Note that the "maximum timeout" using a single byte for InwardTiming is going to be quite short.
Using uint16_t changes the SBI to an SBIW and adds one cycle to the loop.

You really do need to look at the assembly language produced by the compiler to answer questions like this...

I'll do the o-scope test again later and post exact figures for how many us the delays for the arduino to respond are. I guess that alongside the assembly code you've kindly translated to for me (how did you produce that?) might give some further indications of whether speeding up is possible?

In my case a single byte is big enough for the timeout, I had guessed a uint16_t would be worse, but hadn't realised it would only be worse by one (1/16MHz) clock cycle.

If I kept the requirement to have a timeout, avoid the risk of being stuck waiting forever if the rise never comes, but lost the requirement to know afterwards whether the loop had ended with a rise or a timeout (make no longer term recording of whatever might replace InwardTiming), could the code be made faster to respond?

Thanks

If I learn assembly should I be able to code a function which can handle this operation substantially faster (2x or better, improvements of <2x don't seem worth the complexity) than the example code I initially posted here?

If so, any tips on where to start learning assembly for AVR chips?
Thanks

Hi, could you explain, why would interrupt be slower?
Thanks.

An interrupt takes quite a lot of clock cycles to start, if you need speed, as I do here, you have to sacrifice the abilities that interrupts enable and instead have your MCU sitting waiting, doing nothing else, for the pin to rise/fall. The trick I'm trying to work out is whether polling (waiting, reading each time you go round the loop, until you see a change) can be made faster than my present use of it.

As show in reply #7, the code produced by this particular example is pretty good.
Here is a more detailed analysis:

TEST:   sbic    0x09, 0        ;  test bit, skip next instruction if zero. 1 or 2 cycles.
           rjmp    WASSET       ; if bit was set (no skip), jump to WASSET.  2 cycles.
WASCLR:
DECDELAY:
         subi    r24, 0x01      ; decrement timeout  counter.  1 cycle
         brne    TEST           ; if timeout has NOT occured, loop back to TEST.  1 or 2 cycles
WASSET:  in      r24, 0x03      ; input the "bit to be read."  1 cycle.

In the best case, the bit you are testing will be set immediately before the instruction at TEST. The sbic will be 1 cyce, the rjmp 2 cycles, and the in 1 cycle. 4 clocks from one bit getting set and the other from being read.

In the worst case, the bit gets set immediately AFTER the sbic test. That means that the another cycle (maybe?) for the sbic, 1 for the subi and 2 more for the branch back to TEST - 4 additional cycles, plus the 4 cycles for the other case for 8 cycles total.

You won't get twice that fast in assembly.

You MIGHT be faster if you have both bits on the same port and read them at the same time. In that case, the best case is zero for the actual read (because the bit has already been read), but it will still take about the same number of cycles for the code to notice that you read the changed bit, and I'm not sure if it helps the worst case.

TEST:   in    r0, 0x09          ; read port (1 cycle)
         sbrc  r0, 1            ; check bit in register (1 or 2 cycles)
           rjmp    WASSET       ; if bit was set (no skip), jump to WASSET.  2 cycles.
WASCLR:
DECDELAY:
         subi    r24, 0x01      ; decrement timeout  counter.  1 cycle
         brne    TEST           ; if timeout has NOT occured, loop back to TEST.  1 or 2 cycles
WASSET:  ; other bits from port 9 already available in r0

(but that's an algorithmic change, rather than a benefit of assembly. probably.)

You MIGHT be able to take advantage of things like the PIO on an rp2040, or the CLC on some of the newer AVRs and SAMD chips, but that's more hardware/datasheet interpretation than assembly.

I don't know what to recommend in terms of learning assembly language. There are lots of tutorials and/or books about AVR programming in assembly, but they may all be colored by how much they assume or don't assume about your prior knowledge of computer architectures and other assembly langauges. AVRs assembly for me was probably something like my 10th assembly language "exposure", and that ... changes the way you look at things. (Not always for the better.) The first one is hardest and needs the most attention, and then the one that is dramatically different than that is next hardest.

Thank you, I hadn't thought of that. I'll test it and see what improvement it makes, but I can see how that could the "time between" the rise of one pin and the read of the other effectively zero. I think that's exactly what I need.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.