"Need for speed" - help needed optimising code

Chris Kelly

Well-known member
Hi guys

I've hit a speed wall in my program. I purchased a 40X2 with 16MHz resonator in the hope to include some extra code/features with an ongoing circular buffer program I've been working on to drive a home-made drum machine at the desired speed of 120bpm.

A quick summary of the program's aim is to work through a buffer of length 128, driving each bPtr increment with an external clock. 120bpm on a 16-step sequence, actually works out at 64 increments per second, since each step on the drum machine is sub-divided into 8 increments.
For 120bpm on a standard drum pattern, each 'beat' is usually every 4 steps:

X - - - X - - - X - - - X - - -
|<- 1 ->|<- 2 ->|<- 3 ->|<- 4 ->|

My original code was too sluggish, so I removed nested If..then statements involving the same variables where possible, but still too slow. I've since stripped down most of the features but the fastest I can achieve is about 60bpm.

If anyone can spot any statements in my code that can be optimised to do the same job but quicker, I'd really appreciate feedback. I know it might be asking a lot to speed things up even further, because the do …. loop section of code in essence needs to complete in 15ms or less.

Code:
#picaxe 40X2
#no_data

'==== LOOPER CIRCUIT.
'A sequence can be from 1-16 "steps" in length.

'Data is input at either C.2 ("hard") or C.3 ("soft")
'and will output at either B.0 or B.1 when bPtr
'detects data at the current address.

'C.2 alone will input data. C.3 only adds data if
'data from C.2 is already at the address, or
'if C.2 and C.3 add data simultaneously

'Input at C.1 will overwrite or wipe any existing
'data at that address, allowing patterns of data
'to be overwritten in real-time

'An external clock input at C.0 drives the program,
'and 8 clock pulses will constitute one whole "step"
'of the 16 step pattern.
'i.e/ a full pattern is 128 pulses in total

'An ADC input at C.7 cause a subroutine to change
'the total buffer length, in multiples of 8 clock
'pulses. i.e/ the ADC determines how many "steps"
'are in the sequence.

'Two buffer start addresses allow for two separate
'banks of data to be accessed.

'A sequence can reset at the end of a full "step"
'and will revert to the buffer start address

symbol HighOutPin       = B.0       ' High Output
symbol LowOutPin        = B.1        ' Low Output
symbol BankOutPin        = B.2        ' Bank Swap Indicator
symbol ClkPin           = pinC.0    ' Clock Input
symbol OverWritePin    = pinC.1    ' Overwrite data
symbol HardPin          = pinC.2    ' Data Input (Hard)
symbol SoftPin        = pinC.3    ' Data Input (Soft)
symbol RstPin        = pinC.4    ' Reset Loop
symbol BankPin        = pinC.5    ' Bank Swap pin
symbol CopyPin        = pinC.6    ' Copy Bank to other
symbol SeqLength        = b4    ' Sequence Length Bank 1
symbol SeqLength2        = b5     ' Sequence Length Bank 2
symbol BufLen           = b6  ' Buffer Length Bank 1
symbol BufEnd           = b7  ' Address of buffer end 1
symbol Active        = b8    ' Shows data on that step
symbol ClockCount        = b9    ' Count 1-8 clock pulses
symbol HardPin1Shot    = b10    ' Add data (one-shot)
symbol RstPin1Shot    = b11    ' Add Reset (one-shot)
symbol BankPin1Shot    = b12 ' Add BankSwap(one-shot)
symbol BankSwap        = b13 ' For new bptr start posn
symbol ActiveBank        = b14 ' Indicates current bank
symbol ADC1            = b15 ' Detect ADC change
symbol ADC2            = b16

symbol BufEnd2          = w14  ' Address of buffer end 2
symbol MyPtr            = w15

symbol ClkPinCopy       = bit0   
symbol ClkPinmem        = bit1     
symbol ClkOneShot       = bit2 ' On Clock rising edge
symbol HardPinCopy    = bit8   
symbol HardPinMem        = bit9   
symbol HardOneShot    = bit10 ' On Data rising edge
symbol RstPinCopy        = bit16   
symbol RstPinMem        = bit17   
symbol RstOneShot        = bit18 ' On Reset rising edge
symbol BankPinCopy    = bit24
symbol BankPinMem        = bit25
symbol BankOneShot    = bit26 ' On Bankswap rising edge

symbol BufStart         = 32  ' Buf Start address Bank 1
symbol BufStart2        = 160 ' Buf Start address Bank 2
  
'==== MAIN PROGRAM STARTS ====
'=============================

gosub ReadSeqLength     
bptr = BufStart          


'==== The following do...loop takes place during each
'clock cycle =============================

do

main:
readadc C.7,b15
if ADC1 != ADC2 then
    gosub ReadSeqLength
    end if

MyPtr = bptr

'==== ONE-SHOT DECLARATIONS ====

ClkPinCopy  = ClkPin    ' Advance clock on rising edge
ClkOneShot  = ClkPinCopy &/ ClkPinMem     
ClkPinMem   = ClkPinCopy                 
HardPinCopy = HardPin    ' Add Data on rising edge
HardOneShot = HardPinCopy &/ HardPinMem
HardPinMem  = HardPinCopy
RstPinCopy  = RstPin    ' Add Reset on rising edge
RstOneShot  = RstPinCopy &/ RstPinMem
RstPinMem   = RstPinCopy
BankPinCopy = BankPin    ' Add Reset on rising edge
BankOneShot = BankPinCopy &/ BankPinMem
BankPinMem  = BankPinCopy

'==== SET DATA ONE-SHOT BETWEEN CLOCK PULSES ====
'============


if OverWritePin = 1 then
    @bptr = 0
end if

if HardOneShot = 1 then       
    HardPin1Shot = 1         
end if

if RstOneShot = 1 then       
    RstPin1Shot = 1         
end if

if BankOneShot = 1 then       
    BankPin1Shot = 1
    BankSwap = 1
end if

'==== DETECT RISING EDGE OF CLOCK PULSE AT C.1 ====
'==============


if ClkOneShot = 1 then       
    
'==== the following code triggers a flashing indicator for when the 
'current data bank will be swapped
'==========

if Bankswap = 1 then
    select ClockCount
    case 0
        high BankOutPin
    case 1
        high BankOutPin
    case 2
        low BankOutPin
    case 3
        low BankOutPin
    case 4
        high BankOutPin
    case 5
        high BankOutPin
    case 6
        low BankOutPin
    case 7
        low BankOutPin
    end select
end if


' ==== Detect data entry
' Adds a 1 to buffer position for High hit
' Adds a 2 to buffer position for Soft hit
'==========


if HardPin1Shot = 1 then
    Select SoftPin
    case 0
    @bptr = HardPin1Shot     
    case 1
    @bptr = HardPin1Shot + 1
    end select
end if
    
if @bptr = 1 and SoftPin = 1 then
    @bptr = 2
end if


'==== Output at one of the pins to trigger either
'a hard hit (B.0) or soft hit (B.1)
'====

Active = @bptr
select Active
    case 0
        low HighOutPin    ' No Output
        low LowOutPin
    case 1
        high HighOutPin    ' Output data at B.0
        low LowOutPin
    case 2
        high LowOutPin    ' Output data at B.1
        low HighOutPin
end select

'==== Increment buffer.=====
'Program keeps count of each clock pulse. Since each
'step of the pattern requires 8 clock pulses, the
'code tracks the "Clock Count" and only executes
'certain features at the start of each step
' i.e/ when ClockCount = 0
'=========

inc bptr
inc ClockCount
if ClockCount > 7 then
    ClockCount = 0
end if   

if ClockCount = 0 then
    
'======
' when reset pin pressed, the buffer returns to the start
'once the current "step" has completed. If Bankswap is required
'then the buffer jumps to the second bank of data
'======

    if     RstPin1shot = 1 and ActiveBank = 0 then
        select Bankswap
        case 0
            bptr = BufStart
            RstPin1Shot = 0
        case 1   
            bptr = BufStart2
            BankSwap = 0
            ActiveBank = 1
            RstPin1Shot = 0   
        end select
    end if   
    if     RstPin1shot = 1 and ActiveBank = 1 then
        select Bankswap
        case 0
            bptr = BufStart2
            RstPin1Shot = 0
        case 1   
            bptr = BufStart
            BankSwap = 0
            ActiveBank = 0
            RstPin1Shot = 0   
        end select
    end if   

'========================================
'when the end of the buffer is reached, bPtr returns to
'the start of the buffer once the current "step" has completed.
'If Bankswap is required, then the buffer jumps to the second bank of data
'================================================

    if bptr > BufEnd and ActiveBank = 0 then
        select BankSwap
            case 0
            BufEnd = BufStart + BufLen - 1
            bptr = BufStart             
            case 1
            BufEnd2 = BufStart + BufLen + 127
            bptr = BufStart2
            BankSwap = 0
            ActiveBank = 1
        end select
    end if
    
    if bptr > BufEnd2 and ActiveBank = 1 then
        select BankSwap
        case 1
        BufEnd2 = BufStart + BufLen + 127
        bptr = BufStart2
        case 0
        BufEnd = BufStart + BufLen - 1
        bptr = BufStart
        BankSwap = 0
        ActiveBank = 0
        end select
    end if
end if


'Any current one-shot variables return to 0
'=================================

HardPin1Shot = 0
BankPin1Shot = 0
    
endif

loop


'Sub routine below is called if ADC value changes
'=================================

ReadSeqLength:

readadc C.7,b4
select SeqLength
case <16
BufLen = 8
case 16 to 31
BufLen = 16
case 32 to 47       
BufLen = 24
case 48 to 63       
BufLen = 32
case 64 to 79       
BufLen = 40
case 80 to 95
BufLen = 48
case 96 to 111
BufLen = 56
case 112 to 127
BufLen = 64
case 128 to 143
BufLen = 72
case 144 to 159   
BufLen = 80
case 160 to 175   
BufLen = 88
case 176 to 191       
BufLen = 96
case 192 to 207       
BufLen = 104
case 208 to 223       
BufLen = 112
case 224 to 239   
BufLen = 120
case 240 to 256   
BufLen = 128
end select
BufEnd = BufStart + BufLen - 1
BufEnd2 = BufStart + BufLen + 127
ADC2 = ADC1
Return
If it's not feasible, then I might just admit defeat and settle for the slower program. (I could even think of it as a buffer of length 64 running at 120bpm!)

Thanks again guys and apologies if I've broken forum etiquette by posting so much code

Chris
 

eclectic

Moderator
Have a search on 80 MHz running,
using a 20 / 25 MHz resonator

eg post#4 here


e
 

Buzby

Senior Member
You've got a 40X2 with a 16MHz xtal ?.

That will run at 64MHz if you use 'setfreq em64', but I didn't see one in your code.

I've not looked through your code properly, but the big 'select/case' statement can probably shortened to a line or two of maths.

Cheers,

Buzby
 

Chris Kelly

Well-known member
Setfreq em64 !? 😮

I never realised it needed stating to activate it. Wow thanks Buzby that gives me some hope!

Will give it a try
Cheers
Chris
 

Chris Kelly

Well-known member
IT WORKS!!! 😀

Thanks Buzby. That has turbocharged the program. Going to start adding back all of the features I'd trimmed off.

Cheers
Chris
 

Buzby

Senior Member
Analogue values from pots, or whatever, inevitably have a bit or two of noise. This will cause your ADC1 / ADC2 comparison to trigger every scan.

Try to think of a way of detecting a significant change in value, not just every tiny change.

Your doing great !.

Buzby
 

AllyCat

Senior Member
Hi,
Code:
    select ClockCount
      case 0
        high BankOutPin
   ......
----------
   select SeqLength
     case <16
       BufLen = 8
   ......
The first select case above might be replaced by an ON ClockCount GOTO.... which (on average) can be up to three or four times faster than the repeated IF ... THEN structure effectively used by SELECT .. CASE. Note that in both formats the earlier cases in the list are executed faster.

As suggested above, the second example would almost certainly be faster (on average) if replaced by a mathematical formula, possibly / 16.
* 8 + 8
.

The examples with only two cases probably would be faster with an IF .. THEN .. ELSE structure.

Cheers, Alan.
 

hippy

Technical Support
Staff member
I would always go with 'maths and READ/TABLE commands' as the quickest way of converting something to something else where possible or implement in a way where no conversion is needed or can be done during the input phase rather than needing to be done every time in the output phase.

Of course, if everything is fine, then what one has is presumably good enough so it's not necessarily worth investing effort in changing things.
 
Top