readadc speed

Captain Haddock

Senior Member
How quickly can I expect a readadc to run in a do-loop? I am trying to set a delay via adc and want to check for changes in the control during the delay period so I using a do-loop which re-checks the adc in each loop and compares to see if it has changed but I am getting a rapid speeding up in the middle of the adc range, debug shows the result as being the same each loop so it's not the reading actually changing.
Am I just over running the readadc command? Would I do better to cut the loop numbers by 10 and add a pause 10 into the loop?
It's always when adc value reads 92, I tried with a +-4 option and it still seems to go astray.
 

lbenson

Senior Member
READADC is quite fast, but DEBUG is quite slow. You can do hundreds of ADC calls in a second. If you are displaying the results, SERTXD would be less likely than DEBUG to cause timing problems to affect your results.

But it's unclear exactly what you are asking--could you post your code and say what it's trying to do?
 

Captain Haddock

Senior Member
The timing issues are without debug as well, as soon as I take out the bit to check adc within the loop it's fine, code is below, it's the commented out bit that's annoying me, the gosub for it is at the bottom.

Code:
#picaxe 20x2

Symbol pulsetime = w8    ;time to start wiper
Symbol wipetime = 1500    ;time to return to park
;Relay pins symbols
Symbol portwash = B.0     
Symbol sbwash = B.1     
Symbol portslow = B.2   
Symbol sbslow = B.3   
Symbol fast = B.4   

let dirsB = %11111111             ; Port B as outputs
let adcsetup = %0000000010001000    ;set C.7 & C.3 as ADC pins
input c.1,c.2                ;set inputs for wash switches
setint or %00000110,%00000110        ;set interrupts for wash switches on C.1 & C.2

main:

readadc c.3,b0            ;12 pos switch
readadc C.7,b9            ;trimpot on board for pulse time adjust
let pulsetime = b9 * 5 + 250

changed:
    if b0 > 228 and b0 < 250 then low fast : high portslow: high sbslow:goto main:endif;low speed on
    
    if b0 < 228 then low portslow : low sbslow;low speed off
    endif
    
    if b0 > 250 then low portslow : low sbslow :high fast:goto main;high speed on
    endif
    
    if b0 < 250 then low fast;high speed off
    endif
    
    if b0 < 3 then goto main;off

gosub convert            ;get intermittant times
    high portslow : high sbslow
    pause pulsetime
    low portslow : low sbslow
    for w6 = 0 to w1

    ;readadc c.3,b4        ;check switch position between loops
    ;let b5 = b4 - 4        ;add +- allowance
    ;let b6 = b4 + 4        ;add +- allowance
    ;if b5 < b0 or b6 > b0 then gosub change    ;check if switch position changed
    
    pause 10
    next w6   

goto main

interrupt:
pause 20
low portslow
low sbslow          ;cut off wipers
if pinC.1=1 then do
    high portwash
    loop while pinC.1=1    ;wash while pressed
    low portwash        ;end wash
     high portslow        ;start wipe
     pause pulsetime        ;bring into self park zone
     low portslow        ;end wipe
     pause wipetime        ;wait till parked
endif    ;wash p
 
 if pinC.2=1 then do
    high sbwash
    loop while pinC.2=1
    low sbwash
     high sbslow
     pause pulsetime
     low sbslow
     pause wipetime
endif    ;wash p
 
low b.2
low b.3

setint or %00000110,%00000110
return

convert:    ;set intermittant times

    if b0 > 10 and b0 < 30 then let w1 = 1500 ;36secs 17.5
    endif
    if b0 > 31 and b0 < 50 then let w1 = 1100 ;26secs  13.2
    endif
    if b0 > 51 and b0 < 75 then let w1 = 900 ;22secs  11
    endif
    if b0 > 76 and b0 < 99 then let w1 = 900 ;14secs ??? the troublesome one!!!.  7.7
    endif
    if b0 > 100 and b0 < 125 then let w1 = 400 ;9secs  5.5
    endif
    if b0 > 128 and b0 < 150 then let w1 = 280 ;6.7secs  4.2
    endif
    if b0 > 153 and b0 < 170 then let w1 = 180;4secs  3
    endif
    if b0 > 172 and b0 < 200 then let w1 = 150;3.5secs  2.8
    endif
    if b0 > 202 and b0 < 225 then let w1 = 110;2.6secs  2.3
    endif
    return

change:
    low portslow : low sbslow    ;wipers off
    pause 20
    readadc c.3,b0
    pause 2000                ;let wipers park
    goto changed

    return
 

AllyCat

Senior Member
Hi,

READADC executes in less than 1 ms (at 4 MHz), about the same as most PICaxe Basic instructions and significantly faster than a GOSUB .... RETURN or any interrupt execution.

I'd be looking at that PAUSE 2000 (2 seconds) and that you appear to have a GOTO instead of a RETURN when leaving the subroutine !!!!!! :(

Cheers, Alan.
 

AllyCat

Senior Member
Hi,
Code:
    ;let b5 = b4 - 4        ;add +- allowance
    ;let b6 = b4 + 4        ;add +- allowance
    ;if b5 < b0 or b6 > b0 then gosub change    ;check if switch position changed
Also I think you probably have the +4 and -4 the wrong way around. The above is nearly always "true", particularly when b4 = b0.

Cheers, Alan.
 

Captain Haddock

Senior Member
Thanks very much for above suggestions.
OK I've done away with the gosub 'change' and bought it into the main program and added min 0 and max 255 to my +-4 allowance and still get the same problem, if I comment out the read adc's and use let b0 = 92 (the problem figure) it's spot on, I've done a quick test program just to readadc and debug and found my multi position switch gives a jumpy reading of 92/93 in the problem position so it's right on the change, all other positions are rock solid responces, I would have though the +-4 bit would deal with issues like that but clearly not.
What's the best way around this.
 

Captain Haddock

Senior Member
I think I have found the problem, it seems I have my < & > the wrong way round, maybe I should change my name to Captain Dopey!
Thanks for all your help guys, AllyCat you were spot on. (y)
 

lbenson

Senior Member
I would have though the +-4 bit would deal with issues like that but clearly not
Why doesn't this work for you? If I understand your intent, it should. Please post your current code. Also, please provide a link to the multi position switch.

(Or perhaps your last post meant that everything is OK now?)
 
Last edited:

AllyCat

Senior Member
Hi,

The "easy" way to find such errors is to test individual sections of the code in the simulator, for example (with the + and - exchanged):
Code:
b0 = 83                     ; Previous switch position
for b4 = 75 to 90      ; Present switch position
    let b5 = b4 + 4                ; add +- allowance
    let b6 = b4 - 4                ; add +- allowance
    if b5 < b0 or b6 > b0 then
change:                                   ; check if switch position changed
        sertxd(cr,lf,#b4," changed")
    else
        sertxd(cr,lf,#b4," NOT changed")
    endif
next b4
BTW, I'd normally code it is as follows (but using symbol names) : ;)
Code:
b0 = 83                     ; Previous switch position
for b4 = 75 to 90
   b5 = b4 - b0 + 4                   ; Bring maximum negative offset up to zero  
   if b5 > 8 then                        ; Compare with the band of acceptable offsets
      sertxd(cr,lf,#b4," changed")
   else
      sertxd(cr,lf,#b4," NOT changed")
   endif
next b4
Presumably you've fixed the w1 value for 14 secs now?

Cheers, Alan.
 

hippy

Technical Support
Staff member
One trick for avoiding getting confused as to how the actual code should be is to give variables names using SYMBOL definitions. That helps make mistakes easier to spot.

Another trick is to compare using SELECT CASE using the "CASE <comparison>" options -

Code:
Symbol expected   = b0
Symbol lowerLimit = b1
Symbol upperLimit = b2
Symbol value      = b3

expected = 10
lowerLimit = expected Min 4 - 4
upperLimit = expected + 4 Max 255

value = 12
Select Case value
  Case < lowerLimit : SerTxd( #value, " below ", #lowerLimit, CR, LF )
  Case > upperLimit : SerTxd( #value, " above ", #upperLimit, CR, LF )
  Else              : SerTxd( #value, " within range ", #lowerLimit, "..", #upperLimit, CR, LF )
End Select
 

BESQUEUT

Senior Member
The timing issues are without debug as well, as soon as I take out the bit to check adc within the loop it's fine, code is below, it's the commented out bit that's annoying me, the gosub for it is at the bottom.

Code:
#picaxe 20x2


    if b0 > 10 and b0 < 30 then let w1 = 1500 ;36secs 17.5
    endif
    if b0 > 31 and b0 < 50 then let w1 = 1100 ;26secs  13.2
    endif
    if b0 > 51 and b0 < 75 then let w1 = 900 ;22secs  11
    endif
    if b0 > 76 and b0 < 99 then let w1 = 900 ;14secs ??? the troublesome one!!!.  7.7
    endif
    if b0 > 100 and b0 < 125 then let w1 = 400 ;9secs  5.5
    endif
    if b0 > 128 and b0 < 150 then let w1 = 280 ;6.7secs  4.2
    endif
    if b0 > 153 and b0 < 170 then let w1 = 180;4secs  3
    endif
    if b0 > 172 and b0 < 200 then let w1 = 150;3.5secs  2.8
    endif
    if b0 > 202 and b0 < 225 then let w1 = 110;2.6secs  2.3
    endif
    return


    return
What happen if b0=30 or b0=50 etc ... ?
This probably can be replaced with a direct formula :
if linear : w0=255-b0*4
if exponential : w0=255-b0 : w0=w0*w0/50
.
PAUSE within an interrupt routine is heretical...
.
By the way : remember to avoid the GOTO command : it's never needed, but frequently source of problems...
 
Last edited:

hippy

Technical Support
Staff member
PAUSE within an interrupt routine is heretical...
Generally true, but in this case it looks like it's only being used as a trigger to start sequencing.

But if that's the case it would be just as easy to poll for the trigger and not have any interrupt at all.

In fact, refactoring the code would probably help greatly, simplify things, make it easier to ensure it does what it should.

The best way to approach that would be to provide a specification of what it does, what functionality it has, then figure out how to best implement that. I'm sure it could all be a lot simpler than how it it currently is. With any development there usually comes a point where it makes sense to 'rip it up' and start again from scratch using what one's learned. I think it might have reached that point.
 

hippy

Technical Support
Staff member
How to do this got me thinking so I set about designing a wiper implementation and describing how I went about that. It all uses "top down" design, then filling in the missing bits which is often a good design strategy.

This is the overall design of a wiper controller as typically implemented in a vehicle. There are four wipe modes; "off", "wipe once" a single wipe then off until activated again; "intermittent" where there is a wipe and a delay before the next wipe; and "continuous" where wipes repeat one after another with no delay.

We don't have to worry yet about how we select the mode, how the wipers are actually controlled, or what the pause between intermittent wipes should be or how that is calculated.

We also don't yet have to worry that wiping for a boat may alternate between port and starboard.
Code:
Symbol MODE_OFF          = 0
Symbol MODE_WIPE_ONCE    = 1
Symbol MODE_INTERMITTENT = 2
Symbol MODE_CONTINUOUS   = 3

Symbol mode              = b6

MainLoop:
  Do
    Gosub ReadMode
    Select Case mode
      Case MODE_WIPE_ONCE
        Gosub Wipe
        Do
          Gosub ReadMode
        Loop Until mode <> MODE_WIPE_ONCE
      Case MODE_INTERMITTENT:
        Do
          Gosub Wipe
          Gosub PauseBetweenWipes
        Loop Until mode <> MODE_INTERMITTENT 
      Case MODE_CONTINUOUS:
        Do
          Gosub Wipe
        Loop Until mode <> MODE_CONTINUOUS
        If mode = MODE_INTERMITTENT Then
          Gosub PauseBetweenWipes
        End If
      Else
        Gosub WipersOff
    End Select
  Loop
The next ting is to determine the mode. In this case we are reading an ADC and determining which of four settings it has.

Here we'll just assume there's a POT input divided into four equal sections, one per mode, "off", "single", "intermittent" and "continuous".

The order of the modes reflects a typical steering wheel wiper control; down for a single wipe, up for intermittent, then up further for continuous.

Code:
Symbol MODE_ADC           = C.0

Symbol reserveW0         = w0 ; b1:b0

ReadMode:
  ReadAdc MODE_ADC, b0
  Select Case b0
    Case < $40 : mode = MODE_WIPE_ONCE
    Case < $80 : mode = MODE_OFF
    Case < $C0 : mode = MODE_INTERMITTENT
    Else       : mode = MODE_CONTINUOUS
  End Select
  Return
Next we'll handle wiper control. This will depend on hardware but here we are simply setting one output high for a time which is the forward wipe, then another pin high for a backwards wipe. When done the wipe has completed.
Code:
Symbol MOTOR_FORWARD     = B.4
Symbol MOTOR_BACKWARD    = B.5

WipersOff:
  Low MOTOR_FORWARD
  Low MOTOR_BACKWARD
  Return

Wipe:
  High  MOTOR_FORWARD
  Pause 3000
  Low   MOTOR_FORWARD
  High  MOTOR_BACKWARD
  Pause 3000
  Low   MOTOR_BACKWARD
  Return
Now we'll deal with the delay between wipes when in intermittent modes. We will read another pot which determines a delay, here, of between 1 and 10 seconds, 1000ms to 10000ms.

Code:
Symbol INTERVAL_ADC      = C.4

Symbol MIN_INTERVAL_MS   =  1000
Symbol MAX_INTERVAL_MS   = 10000

Symbol interval          = w1 ; b3:b2

PauseBetweenWipes:
  ReadAdc INTERVAL_ADC, b0
  interval = MAX_INTERVAL_MS - MIN_INTERVAL_MS * b0 / 255 + MIN_INTERVAL_MS
  Pause interval
  Return
But there's a problem with that. We have a hard delay so any adjustment of the interval delay or mode won't be seen until after that delay is ended. It would be better to have that more responsive.

We do that by counting how long we have waited for, checking what the interval and mode should be and exiting when the mode changes or the interval has been exceeded.

Code:
Symbol waited            = w2 ; b5:b4

DetermineInterval:
  ReadAdc INTERVAL_POT, b0
  interval = MAX_INTERVAL_MS - MIN_INTERVAL_MS * b0 / 255 + MIN_INTERVAL_MS
  Return

PauseBetweenWipes:
  waited = 0
  Do
    Pause 100
    waited = waited + 100
    Gosub ReadMode
    Gosub DetermineInterval
  Loop Until mode <> MODE_INTERMITTENT or waited >= interval
  Return
So we are done. Except for a bug which is an overflow in our 'DetermineInterval' routine. When maximum we read '255' and '255*10000" overflows. We can fix that with -
Code:
DetermineInterval:
  ReadAdc INTERVAL_POT, b0
  If b0 <= 65 Then
    interval = MAX_INTERVAL_MS - MIN_INTERVAL_MS * b0 / 255 + MIN_INTERVAL_MS
  Else
    interval = MAX_INTERVAL_MS - MIN_INTERVAL_MS / 4 * b0 / 64 + MIN_INTERVAL_MS
  End If
  Return
Now, for a boat, we are going to add separate and alternative port and starboard wiping.

The first thing we do is update our wiper control code -
Code:
Symbol PORT_MOTOR_FORWARD  = B.2
Symbol PORT_MOTOR_BACKWARD = B.3
Symbol STRB_MOTOR_FORWARD  = B.4
Symbol STRB_MOTOR_BACKWARD = B.5

WipersOff:
  Low PORT_MOTOR_FORWARD
  Low PORT_MOTOR_BACKWARD
  Low STRB_MOTOR_FORWARD
  Low STRB_MOTOR_BACKWARD
  Return

WipeBoth:
  High  PORT_MOTOR_FORWARD
  High  STRB_MOTOR_FORWARD
  Pause 3000
  Low   PORT_MOTOR_FORWARD
  Low   STRB_MOTOR_FORWARD
  High  PORT_MOTOR_BACKWARD
  High  STRB_MOTOR_BACKWARD
  Pause 3000
  Low   PORT_MOTOR_BACKWARD
  Low   STRB_MOTOR_BACKWARD
  Return

WipePort:
  High  PORT_MOTOR_FORWARD
  Pause 3000
  Low   PORT_MOTOR_FORWARD
  High  PORT_MOTOR_BACKWARD
  Pause 3000
  Low   PORT_MOTOR_BACKWARD
  Return

WipeStarboard:
  High  STRB_MOTOR_FORWARD
  Pause 3000
  Low   STRB_MOTOR_FORWARD
  High  STRB_MOTOR_BACKWARD
  Pause 3000
  Low   STRB_MOTOR_BACKWARD
  Return
And now we need to make the alternating sequence work -
Code:
MainLoop:
  Do
    Gosub ReadMode
    Select Case mode
      Case MODE_WIPE_ONCE
        Gosub WipePort
        Gosub WipeStarboard
        Do
          Gosub ReadMode
        Loop Until mode <> MODE_WIPE_ONCE
      Case MODE_INTERMITTENT:
        Do
          Gosub WipePort
          Gosub WipeStarboard
          Gosub PauseBetweenWipes
        Loop Until mode <> MODE_INTERMITTENT 
      Case MODE_CONTINUOUS:
        Do
          Gosub WipePort
          Gosub WipeStarboard
        Loop Until mode <> MODE_CONTINUOUS
        If mode = MODE_INTERMITTENT Then
          Gosub PauseBetweenWipes
        End If 
      Else
        Gosub WipersOff
    End Select
  Loop
And our complete code, barring changes for any specific application, is as follows -
Code:
Symbol MODE_ADC            = C.0
Symbol INTERVAL_ADC        = C.4

Symbol PORT_MOTOR_FORWARD  = B.2
Symbol PORT_MOTOR_BACKWARD = B.3
Symbol STRB_MOTOR_FORWARD  = B.4
Symbol STRB_MOTOR_BACKWARD = B.5

Symbol MIN_INTERVAL_MS     =  1000
Symbol MAX_INTERVAL_MS     = 10000

Symbol MODE_WIPE_ONCE      = 0
Symbol MODE_OFF            = 1
Symbol MODE_INTERMITTENT   = 2
Symbol MODE_CONTINUOUS     = 3

Symbol reserveW0           = w0 ; b1:b0
Symbol interval            = w1 ; b3:b2
Symbol waited              = w2 ; b5:b4

Symbol mode                = b10

MainLoop:
  Do
    Gosub ReadMode
    Select Case mode
      Case MODE_WIPE_ONCE
        Gosub WipePort
        Gosub WipeStarboard
        Do
          Gosub ReadMode
        Loop Until mode <> MODE_WIPE_ONCE
      Case MODE_INTERMITTENT:
        Do
          Gosub WipePort
          Gosub WipeStarboard
          Gosub PauseBetweenWipes
        Loop Until mode <> MODE_INTERMITTENT 
      Case MODE_CONTINUOUS:
        Do
          Gosub WipePort
          Gosub WipeStarboard
        Loop Until mode <> MODE_CONTINUOUS
        If mode = MODE_INTERMITTENT Then
          Gosub PauseBetweenWipes
        End If
      Else
        Gosub WipersOff
    End Select
  Loop

ReadMode:
  ReadAdc MODE_ADC, b0
  Select Case b0
    Case < $40 : mode = MODE_WIPE_ONCE
    Case < $80 : mode = MODE_OFF
    Case < $C0 : mode = MODE_INTERMITTENT
    Else       : mode = MODE_CONTINUOUS
  End Select
  Return

WipersOff:
  Low PORT_MOTOR_FORWARD
  Low PORT_MOTOR_BACKWARD
  Low STRB_MOTOR_FORWARD
  Low STRB_MOTOR_BACKWARD
  Return

WipeBoth:
  High  PORT_MOTOR_FORWARD
  High  STRB_MOTOR_FORWARD
  Pause 3000
  Low   PORT_MOTOR_FORWARD
  Low   STRB_MOTOR_FORWARD
  High  PORT_MOTOR_BACKWARD
  High  STRB_MOTOR_BACKWARD
  Pause 3000
  Low   PORT_MOTOR_BACKWARD
  Low   STRB_MOTOR_BACKWARD
  Return

WipePort:
  High  PORT_MOTOR_FORWARD
  Pause 3000
  Low   PORT_MOTOR_FORWARD
  High  PORT_MOTOR_BACKWARD
  Pause 3000
  Low   PORT_MOTOR_BACKWARD
  Return

WipeStarboard:
  High  STRB_MOTOR_FORWARD
  Pause 3000
  Low   STRB_MOTOR_FORWARD
  High  STRB_MOTOR_BACKWARD
  Pause 3000
  Low   STRB_MOTOR_BACKWARD
  Return

DetermineInterval:
  ReadAdc INTERVAL_ADC, b0
  If b0 <= 65 Then
    interval = MAX_INTERVAL_MS - MIN_INTERVAL_MS * b0 / 255 + MIN_INTERVAL_MS
  Else
    interval = MAX_INTERVAL_MS - MIN_INTERVAL_MS / 4 * b0 / 64 + MIN_INTERVAL_MS
  End If
Return

PauseBetweenWipes:
  waited = 0
  Do
    Pause 100
    waited = waited + 100
    Gosub ReadMode
    Gosub DetermineInterval
  Loop Until mode <> MODE_INTERMITTENT or waited >= interval
  Return
All untested except for simulation so there may be some bugs or other operational issues.
 
Last edited:
Top