Making an output pin name variable

cpedw

Senior Member
I am trying to set up control of several channels (currently aiming for 3 channels) of APA102 LEDs. The target Picaxe is 14M2. APA102 uses SPI and there are bit-banging macros and routines provided with PE6 (File/Open Samples/Basic/APA102/40X2/Example 6). They use these declarations:
Code:
; Define the hardware pins
Symbol SCK = C.3 : Symbol PIN_SCK = pinC.3
Symbol SDO = C.5 : Symbol PIN_SDO = pinC.5
I change C.3 to C.0 and C.5 to B.0 for the 14M2. (Incidentally, PIN_SCK isn't used anywhere.)

The subroutine that matters looks like this (corrected and a bit streamlined from the PE6 example):
Code:
Sendb0:
  ; This routine will output a byte of data from variable
  ; 'b0' over SPI using bit-banging 
  PIN_SDO = bit7 : pulsout SCK, 1
  PIN_SDO = bit6 : pulsout SCK, 1
  PIN_SDO = bit5 : pulsout SCK, 1
  PIN_SDO = bit4 : pulsout SCK, 1
  PIN_SDO = bit3 : pulsout SCK, 1
  PIN_SDO = bit2 : pulsout SCK, 1
  PIN_SDO = bit1 : pulsout SCK, 1
  PIN_SDO = bit0 : pulsout SCK, 1
  return

I was hoping that a structure like this (only 1 channel shown here to keep it short) might work to allow the routine to be used across different channels :
Code:
; Define the hardware pins for channel 1
Symbol SCK1 = C.0 : Symbol PIN_SCK1 = pinC.0
Symbol SDO1 = B.5 : Symbol PIN_SDO1 = pinB.5

symbol PIN_SCK = b8
symbol PIN_SDO = b7
symbol SCK       = b6
symbol SDO       = b5

...
;Send b0 to Channel 1
PIN_SCK = PIN_SCK1
PIN_SDO = PIN_SDO1
SCK     = SCK1
SDO     = SDO1

Gosub Sendb0
...

But my reading suggests that the PIN_SDO part won't work as I want, although it compiles and runs in the simulator. I haven't tested it in hardware.
Will modifying each line of the subroutine like this do what I want?
Code:
IF bit7=1 THEN
    high SDO
ELSE
    low SDO
ENDIF
pulsout SCK, 1

I was hoping to save program space by using the original routine with variable pins but this method (if valid) would expand the code somewhat. I there a shorter way?
 
I'm not sure of the numbering on M2 parts, but on 28X2 / 40X2 pins can be addressed numerically.
Code:
' Num Port  Leg    Usage                  Num   Port Leg    Usage
' --- ----  ---   -----                   ---   ---- ---    -----
'  0     B.0     33    IRinput         16     A.0     2    n13
'  1     B.1     34    n1                17     A.1     3    n14
'  2     B.2     35    n2                18     A.2     4    n15
'  3     B.3     36    n3                19     A.3     5    n16
'  4     B.4     37    n4                20     A.4     7    Serout
'  5     B.5     38    n5                21     A.5     8    StatusLED
'  6     B.6     39    n6                22     A.6     9    Spare
'  7     B.7     40    n7                23     A.7     10    Spare
'  8     C.0     15    n8                24     D.0     19    n17
'  9     C.1     16    n9                25     D.1     20    n18
' 10     C.2     17    Piezo           26     D.2     21    n19
' 11     C.3     18    i2c                27     D.3     22    n20
' 12     C.4     23    i2c                28     D.4     27    n21
' 13     C.5     24    n10                29     D.5     28    n22
' 14     C.6     25    n11                30     D.6     29    n23
' 15     C.7     26    n12                31     D.7     30    n24

The numbers in the Num column ( 0 - 31 ) correspond to 8 bits on each of the 4 ports. These were used with HIGH/LOW, e.g HIGH 9 energised C.1

This mean you can bit-bang your pin, and vary which pin by changing a variable. 14M2 has C.1, try HIGH 9 and LOW 9.
 
If you want to know the number of a pin - e.g. C.1 then (in the simulator or for real) execute
Code:
sertxd(#C.1)
and, as Buzby says, you can use it in a variable. In most cases, you can use the pin name as you were doing - e.g.
Code:
b0 = C.1
but there are a few cases (e.g. ADC on some chips) where it doesn't work and you need to use the number.
 
Thanks for your comments but I haven't explained myself well. I would like to achieve setting variable output pins to the value of a bit using something like

let <variable pin name> = bitx

Can it be done?
 
Can it be done?
I have not tested this, but I believe that it can be done.

From the manual:
The variable pinsX is broken down into individual bit variables for reading from
individual inputs with an if...then command. Only valid input pins are
implemented e.g.
pinsB = pinB.7 : pinB.6 : pinB.5 : pinB.4 :
pinB.3 : pinB.2 : pinB.1 : pinB.0

The variable outpinX is broken down into individual bit variables for writing
outputs directly. Only valid output pins are implemented. e.g.
outpinsB = outpinB.7 : outpinB.6 : outpinB.5 : outpinB.4 :
outpinB.3 : outpinB.2 : outpinB.1 : outpinB.0

So this code writes bit 7 to bit 5 of the _input_ register for port B, not the output register:
Code:
Symbol PIN_SDO = pinC.5
PIN_SDO = bit7

and it needs to be changed to write bit7 to outpinC.5, not pinC.5:
Code:
Symbol PIN_SDO = outpinC.5
PIN_SDO = bit7
 
@Flenser, I don't think that gives me a Variable output pin name but your description of Outpins has inspired me.

Here's an extract from my latest plan, still to be tested.

Code:
#picaxe 14m2
; Define the hardware pins for 3 channels
Symbol SCK1 = C.0
Symbol SDO1 = B.5 : Symbol PIN_SDO1 = 128

Symbol SCK2 = C.1
Symbol SDO2 = B.4 : Symbol PIN_SDO2 = 64

Symbol SCK3 = C.2
Symbol SDO3 = B.3 : Symbol PIN_SDO3 = 32

;b0 is reserved for SPI use

symbol  ....

symbol PIN_SDO     = b16
symbol SCK         = b17
symbol SDO         = b18


; Define channel
; Must define channel before using a bitbang macro
#macro chan(n1)
  SELECT CASE n1
    CASE 1
        PIN_SDO = PIN_SDO1
        SCK     = SCK1
        SDO     = SDO1
    CASE 2
        PIN_SDO = PIN_SDO2
        SCK     = SCK2
        SDO     = SDO2
    CASE 3
        PIN_SDO = PIN_SDO3
        SCK     = SCK3
        SDO     = SDO3
  ENDSELECT
#endmacro
    
...

; Send a four byte packet out via bit-banged SPI
#macro sendPacket( n1, n2, n3, n4 )
  b0 = n1 : gosub Sendb0
  b0 = n2 : gosub Sendb0
  b0 = n3 : gosub Sendb0
  b0 = n4 : gosub Sendb0
#endmacro
....

Sendb0:
  ; This routine will output a byte of data from variable
  ; 'b0' over SPI using bit-banging 
  OUTPINSB = PIN_SDO * bit7 : pulsout SCK, 1
  OUTPINSB = PIN_SDO * bit6 : pulsout SCK, 1
...
  OUTPINSB = PIN_SDO * bit1 : pulsout SCK, 1
  OUTPINSB = PIN_SDO * bit0 : pulsout SCK, 1
  return

This will set all port B pins low apart from the active one, but that's not a problem for my application.
 
But my reading suggests that the PIN_SDO part won't work as I want, although it compiles and runs in the simulator. I haven't tested it in hardware.

I was hoping to save program space by using the original routine with variable pins but this method (if valid) would expand the code somewhat. Is there a shorter way?
Hi,

I've not followed exactly what you want to achieve or why you think it "won't work", but often the Simulator can be a better indicator of a Program's behaviour than the User Manual(s) or even the on-line command help, excluding "Hardware" aspects such as the PEEKSFR/POKESFR commands, etc., which are not supported.

The PIN_SDO = bit7 : pulsout SCK, 1 structure reproduced in post #1 is primarily used for maximum speed; a more compact arrangement can be achieved using (left or right) shifting of a "mask" byte (or in this case the data byte), but "closing the loop" takes additional time, for example:
Code:
b0 = DataByte       ; Note that value in bo will be destroyed on exit from the loop
for b1 = 0 to 7
  PIN_SDO = bit7    ; MSb of b0
  pulsout SCK, 1
  b0 = b0 + b0      ; Left shift (slightly faster than b0 * 2)
next b1

As far as your code in post #6 is concerned, note that a MACRO does not save codespace (compared with a subroutine call) and in my experience the SELECT ... CASE structure and particularly Subroutine CALLs can add very significantly to the execution time of a program. So you will probably need to decide whether your primary aim is in reducing program size or NOT increasing its execution time.

Cheers, Alan.
 
cpedw,

I've had another look at your original post and I think the easiest way to answer your question might be for you to incrementally test some changes we suggest, starting with mine.

First test:
You have some APA102 LEDs so the first test I suggest that you do is hook up one LED string and run the program File/Open Samples/Basic/APA102/40X2/Example 6 with these changes:
1. Update the #PICAXE directive with the chip you are using (if it not a 40X2)
2. Update the symbol constants SCK and SDO with suitable pins on the chip you are using (if it not a 40X2)
3. Update the number of LEDs in the string.
4. Fix this bug in the SendB27 subroutine:
Code:
SendB27:
  ; This routine will output a byte of data from variable
  ; 'b27' over SPI using bit-banging
  PIN_SDO = b20 / $80 : pulsout SCK, 1    <-- b20 looks like a typo and probably should be b27.
  PIN_SDO = b27 / $40 : pulsout SCK, 1
  ... lines cut ...
  PIN_SDO = b27 / $01 : pulsout SCK, 1
  return

It's important that you do not change anything else at this point because proving that the example 6 program will work (as modified for the chip you are using) will provide a working baseline version that you can fall back to if any of our suggestions do not work.

Replace the constant SCK with a variable:
In the statements "symbol Symbol SCK = C.3" and "Symbol SDO = C.5" the entries C.3 and C.5 are constants but the PULSOUT command accepts "a variable/constant which specifies the i/o pin to use." so my second test is to modify the example 6 program to use a variable for the pin parameter instead of the constant SCK.

1. Add a variable definition to use for the pin parameter and assign the value of the pin you have chosen for your chip, like this:
Code:
symbol SCK_VAR = b28
SCK_VAR =  <C.3 for a 40X2 or the pin you have chosen for your chip>
2. Update the subroutine SendB27 to change SCK to SCK_VAR in all the PULSOUT commands.

This modified version of example 6 should work exactly as the version you created for my test 1.

I do not think that you can replace the name PIN_SDO with a variable:
pinC.3 is the name for a bit in port C and it acts like a variable in that you can assign bit values to it with statements like pinC.3=1 or PIN_SDO = b20 / $80

PICAXE BASIC uses statements like "PIN_SDO = pinC.5" to create the alias name "PIN_SDO" for the pin name "pinC.5" or VAR1 = B1 to create the alias name "VAR1" for the variable name "B1".
⦁ PIN_SCK = PIN_SCK1 will not do what you expect because both PIN_SCK and PIN_SCK1 are names so this statement simply assigns the value contained in the name aliased with PIN_SCK1 to the name aliased by PIN_SCK.
⦁ i.e this is exactly the same as the statement B1=B2 which assigns the value of the variable location identified by the name B2 to the variable location identified by the name B1. It does not make the name B1 now point to the variable location named B2.

I can't think of any way to work around this.
The only solution I can think of is to have a separate send subroutine for each LED string, something like this:
Code:
Symbol SCK1 = C.3 : Symbol PIN_SCK1 = pinC.3
Symbol SDO1 = C.5 : Symbol PIN_SDO1 = pinC.5
Symbol SCK2 = D.0 : Symbol PIN_SCK2 = pinD.0
Symbol SDO2 = D.1 : Symbol PIN_SDO2 = pinD.1

B27=<the value for string1>
GOSUB sendstring1
B27=<the value for string2>
GOSUB sendstring2

sendstring1:
  SDO1 = b27 / $80 : pulsout SCK1, 1
  ... lines cut ...
  SDO1 = b27 / $01 : pulsout SCK1, 1
return

sendstring2:
  SDO2 = b27 / $80 : pulsout SCK2, 1
  ... lines cut ...
  SDO2 = b27 / $01 : pulsout SCK2, 1
return

As AllyCat has already pointed out you can get some improvement in speed, at the expense of a larger program, by using a macro in place of the subroutine and it's GOSUBs.
 
Thanks for all the advice so far. Unfortunately I'm away from home and Picaxe for 2 weeks so I can't try any further tests.
 
Now I'm back and I tried out the scheme described in post 6. It works fine so I can now operate several strings of APA102 LEDs independently.

It is quite slow, even running at 32 MHz. Running 3 channels, it takes around 1 second (guesstimated, not timed) to set each LED. There's scope for improvement ...
 
My first suggestion is to go back to using the SPI bitbang code from the PE APA102 example:
Code:
  PIN_SDO = bit7 : pulsout SCK, 1
  PIN_SDO = bit6 : pulsout SCK, 1
  PIN_SDO = bit5 : pulsout SCK, 1
  PIN_SDO = bit4 : pulsout SCK, 1
  PIN_SDO = bit3 : pulsout SCK, 1
  PIN_SDO = bit2 : pulsout SCK, 1
  PIN_SDO = bit1 : pulsout SCK, 1
  PIN_SDO = bit0 : pulsout SCK, 1

In discussing the original topic "Making an output pin name variable" our code examples have all included statements that use arithmetic or logical expressions to calculate the value of each bit assigned to the SDO pin. This did not matter for the original topic but it will matter now that you are trying to improve the speed.
 
cpedw,

Just cleaning up my open tabs from researching bitbanging SPI and I spotted this post by AllyCat:
"The M2 chips don't have native SPI instructions, but the "Base Chip" still contains the SPI hardware module, which can be used with POKESFR instructions"

I've never driven the hardware SPI module using PEEKSFR/POKESFR instructions but prompted by Allycat's comment I did a search of the forum which came up with this hit M2 series hardware SPI where GrahamGo has posted some working code for a 14M2 chip. Talk about serendipity!

He quotes a 10x speed improvement over the bigbang routine but has a problem with the speed of the /CS signal that prevents him getting even better speed. Never having used SPI I don't know what the implications of this might be for your code.
 
Hi,
I did a search of the forum which came up with this hit M2 series hardware SPI where GrahamGo has posted some working code for a 14M2 chip. Talk about serendipity!

That's an interesting find, which I hadn't seen before; I must admit that I haven't proceeded with my plans to use the hardware SPI, one reason being that most signals are only available on one dedicated pin (each). These are usually shared with the I2C Bus (in the MSSP module) and IMHO the I2C Bus is generally so much superior to SPI, that I can't see many applications where I would forego the availability of the I2C hardware interface. Similarly, in this current thread the OP is using three separate SPI Buses, but there is only one MSSP module in the 14M2 (or two in the 20M2). Normally, multiple devices on a single SPI Bus are selected by separate "Chip Select" signals, but the LED strings don't appear to have this input facility. :(

However, I don't believe that it's necessary to duplicate pins for both the Clock and Data Signals. The approach that I would investigate is a common Data pin for all the LED strings, so that only a single "in-line" data-bit-banging routine can be used for all the channels. Then, separate Clock signals can be generated for each channel, noting that PULSOUT <variable>,<width> , where for example a prior variable b1 = C.2 , is a valid construction. Incidentally, note that PULSOUT has a very high "overhead" (token decoding delay) so that a short pulse width such as 1 will have a duty cycle of around 1%, even with directly repeated commands. This might cause an issue with a Logic Analyser or Sampling 'Scope "missing" pulses, which can be solved by using a LOW <pin> : HIGH <pin> structure (to replace the PULSOUT), which has a very similar execution time, but with nearly an equal mark : space ratio (and only about one byte larger program code size).

Finally, I've already mentioned some ways to keep the execution speed as high as possible, but basically avoid ALL subroutine calls, SELECT ... CASE and even IF ... ELSE structures, and keep any { IF } GOTO (loops) to a minimum. Also, the bit variables are usually faster than (mathematical) Logic Operations, or the fastest way to Left-Shift a variable in M2s is W0 = W0 + W0 (or perhaps a READ{TABLE} <byte> in extreme circumstances). ;)

Cheers, Alan.
 
I don't believe that it's necessary to duplicate pins for both the Clock and Data Signals. The approach that I would investigate is a common Data pin for all the LED strings, so that only a single "in-line" data-bit-banging routine can be used for all the channels. Then, separate Clock signals can be generated for each channel,
This would complete an answer to cpedw's original topic "Making an output pin name variable" as a variable can be used to specify each separate Clock pin in the PULSOUT command.

Incidentally, note that PULSOUT has a very high "overhead" (token decoding delay) ... which can be solved by using a LOW <pin> : HIGH <pin> structure
I did some testing of the command durations of PULSOUT vs HIGH <pin>: LOW <pin> and on an 08M2 chip at 32MHz:
  • PULSOUT, C.2, 1 55.40us
  • HIGH C.2: LOW C.2 67.60us
 
This would complete an answer to cpedw's original topic "Making an output pin name variable" as a variable can be used to specify each separate Clock pin in the PULSOUT command.
I agree wholeheartedly (in principle - still to test it out with APA102s) that Alan's idea is a neat and simple solution to my question.

I had started to investigate demux chips (MAX350) as an alternative solution but I'm obsessed by minimising component count.

I'm also interested to find SPI in an 08M2 so I will study that possibility but at first glance it looks complicated and not compatible with switching the Clock between channels.
 
Hi,
I did some testing of the command durations of PULSOUT vs HIGH <pin>: LOW <pin> and on an 08M2 chip at 32MHz:
Code:
   PULSOUT C.2 , 1      ;  55.40us
   HIGH C.2 : LOW C.2   ;  67.60us

Yes, so if one increases the parameter to 10, to permit a reliable digital sampling rate of 100 kHz (still faster than the PCB 'scope for example), then the two structures will have very similar execution times.

But beware that the 08M2 is significantly faster than the 14M2, because the latter (like the 20M2) has three internal 8-bit Buses (A, B and C) which need to be "mapped" to suit the pin numbering of the PICaxe's two external buses (B and C). On my measurements, the 20M2 "pin-related" instructions are about 45% slower than the 08M2's. The non-pin-related instructions are around 15% slower than the 08M2, presumably because of the larger "Address Space" (e.g. Table memory and more hardware modules) of the larger chips.

I'm also interested to find SPI in an 08M2 so I will study that possibility but at first glance it looks complicated and not compatible with switching the Clock between channels.

Yes, and even more complicated at a second glance :( . Which is one of the other reasons why I've reduced my interest in exploring the SPI hardware. If I'm forced to use an SPI-based peripheral chip, then I'll probably just bit-bang it. ;)

Cheers, Alan.
 
But beware that the 08M2 is significantly faster than the 14M2,
The pulsout command is being sent for every bit of every byte sent to every LED and I did have the thought that cpedw will probably have to do the definitive test of trying out high:low instead of pulsout to see if it has a noticable effect on the speed.

But I already had an 08M2 ready to go on a breadboard and I was too lazy to switch to a 14M2.
 
Last edited:
at first glance it looks complicated
I expect that this will usually be the case when driving the underlying microcontroller hardware directly using the low level PEEKSFR & POKESFR commands instead of the high level PICAXE BASIC commands.

and not compatible with switching the Clock between channels
I've never used SPI but a bit of quick web research shows that the MOSO, MOSI & SCLK signals are intended to be a bus that is shared between channels, which is why you get one SPI hardware module on the PIC microcontroller. i.e. you don't need to switch the clock between channels.

The slaves only action a request if they have been enabled with their /CS pin and when the SPI protocol timing they receive on the MOSO, MOSI & SCLK pins is correct.
So the slaves don't know, or care, whether they are sharing the MOSO, MOSI & SCLK signals with other slaves and you can bitbang SPI using different sets of pins to send data and clock to each slave as you have done in your code. Put another way, you are bitbanging 3 SPI busses that each have one slave.

From this TI document about SPI:
⦁ An SPI bus can have only one controller, but may control multiple slaves
⦁ Peripheral select: Selects the peripheral device for communication. Commonly labeled as CS or SYNC in TI data converters
⦁ SCLK originates from controller and shared with all slaves
⦁ MOSI: Can be shared between peripheral devices
⦁ MOSO: Can be shared between peripheral devices

and here is a image showing several slaves wired up this way:
Introduction-to-SPI-Multiple-Slave-Configuration-Separate-Slave-Select.png
So you only need one subroutine or macro that uses a single constant defined for the SCK & SDO pins and your code would become something like this:
Code:
Symbol SCK = C.0
Symbol SDO = B.5
SYMBOL CS1=B.1
SYMBOL CS2=B.2
SYMBOL CS3=B.3

B0=<the value for string1>
low CS1
GOSUB sendstring
high CS1

B0=<the value for string2>
low CS2
GOSUB sendstring
high CS2

etc
 
Last edited:
Hi,

But you appear to be overlooking that the APA102 LED (strings) have only 6 pins, Supply, Ground, Data In, Data Out, Clock In and Clock Out; No Chip Select . The CS/CE signal(s) requirement is one of the reasons that I "dislike" (read Hate) the SPI Bus concept. ;)

[Post #13] Normally, multiple devices on a single SPI Bus are selected by separate "Chip Select" signals, but the LED strings don't appear to have this input facility. :(

Of course you might be able to switch the LED's power supply, or use some external "Glue Logic" for the Data and/or Clock signals from the PICaxe, but neither appears to meet the OP's "Minimum Hardware" desire. Most PICaxes do have the DSM (Digital Signal Modulator) module, but that has only one output pin, shared with the SR Latch Output (which could be another vague possibility), so I think it unlikely to be able to split the SCK or SDO pin signals three ways without some external hardware.

Cheers, Alan.
 
But you appear to be overlooking that the APA102 LED (strings) have only 6 pins,
OK, that means using PEEKSFR & POKESFR to drive the microcontroller's hardware SPI module is not and option for cpedw and he is left with tuning the bitbang code he already has for the best perfomance.
 
Hi,

Following links from my DSM snippet thread above reminded me that the M2 chips have Analogue Comparators (two on most chips), accessible via SFR commands (but no other support from the PICaxe Basic), which can be commissioned as simple (digital) Buffers or Gates. However, the DSM, SR Latch /Q and Comparator 2 all share the same Output pin. Similarly, Comparator 1 and the SR Latch Q share another Output pin, so we still have a possibility of gating only two signals internally. :(

However, activating an internal Weak Pullup resistor, and with One external diode (e.g. 1N4148) for each Output could implement a "Wired OR" gate, if the OP was really desperate for faster multiple-SPI (Hardware) Outputs. ;) But still not as fast as I2C data transfers with an M2, because individual SPI bytes need to be loaded via SFR commands, whilst the PICaxe Basic supports strings of I2C bytes (of almost unlimited length) at the full Interpreter speed.

Cheers, Alan.
 
cpedw,

The benefit you get from adding the complexity of driving the internal SPI module using PEEKSFR & POKESFR to your code would be the speed improvement that you get and the speed improvement is probably going to depend on the sequence you use to update the LEDs in the three strings.

What sequence do you use?
1. Is it update all the LEDs in string 1, then all in string 2, then string 3, or
2. Is update the 1st LED in all 3 strings, then the 2nd LED in all 3 strings, then the 3rd LED, etc

How many LEDs are there in each string?
 
Flenser,
Currently the sequence is #2, update 1 LED in each string then on to the next. It would not be difficult to switch to setting all LEDs in 1 string then next string ...
Each string has 40 LEDS (though one string only works on 27 LEDs - waiting for delivery of fresh APA102s).

I am about to try out working with 1 Data line for all channels, just using separate clocks for the 3 channels. A small change of software but it needs some butchery of the circuit board.
 
I am about to try out working with 1 Data line for all channels, just using separate clocks for the 3 channels. A small change of software but it needs some butchery of the circuit board.
Hi,

Might not need any "Butchery" (yet). You can Tri-State two of the output pins (set as inputs and make sure that you don't write to those pins) and then just link them to the active Data Output.

Cheers, Alan.
 
I am about to try out working with 1 Data line for all channels, just using separate clocks for the 3 channels. A small change of software but it needs some butchery of the circuit board.
AllyCat suggestion makes it easy for you to test without having to hack the circuit board too much.
My understanding is that your original code has three SDO pins defined like this:
PIN_SDO1 = pinA.B
PIN_SDO2 = pinC.D
PIN_SDO2 = pinE.F

Expanding AllyCat's suggestion to test using one common SDO pin with 3 separate SCKn pins:
1) Define the pins for PIN_SDO2 and PIN_SDO3 to be inputs. Your test code is not going to use these two pins so it should never send any data to them.
2) At convenient spots on the circuit board:
- Solder a "bodge" wire from the PIN_SDO1 pin's trace on the circuit board to the PIN_SDO2 pin's trace.
- Solder a "bodge" wire from the PIN_SDO1 pin's trace on the circuit board to the PIN_SDO3 pin's trace.

This will allow you to test sending data to all three strings using just the single PIN_SDO1 pin without having to make any permanent changes to the circuit board before you find out of the changes are worth making permanent.
 
Last edited:
It's done and working. Yes the necessary butchery was minimal. The code is now noticeably faster. The critical parts are:
Code:
;Rainbow_Multichan_1DataPin_14M2 developed from APA102_Example_6_Multichan_14M2
#picaxe 14m2
#no_data

SETFREQ M32

; Running 3 separate channels on one Data line but separate Clocks for each channel 

; Define the hardware pins for 3 channels
Symbol SDO  = B.3 : Symbol PIN_SDO = pinB.3  ; leg 10
Symbol SCK1 = C.0                            ; leg 7
Symbol SCK2 = C.1                            ; leg 6
Symbol SCK3 = C.2                            ; leg 5

Symbol SCK0 = 7    ;    For SCKn, SCK = SCK0 + n
...
; APA102 Macros
; Define channel
; Must define a channel before using any bitbang macro
#macro chan(n1)
     SCK = SCK0 + n1
#endmacro
;Other macros as  in APA102 Example file.
...
Sendb0:
  ; This routine will output a byte of data from variable
  ; 'b0' over SPI using bit-banging 
    PIN_SDO = bit7 : pulsout SCK, 1
    PIN_SDO = bit6 : pulsout SCK, 1
    PIN_SDO = bit5 : pulsout SCK, 1
    PIN_SDO = bit4 : pulsout SCK, 1
    PIN_SDO = bit3 : pulsout SCK, 1
    PIN_SDO = bit2 : pulsout SCK, 1
    PIN_SDO = bit1 : pulsout SCK, 1
    PIN_SDO = bit0 : pulsout SCK, 1
return
Using adjacent numbered pins for the Clock signals means simple arithmetic defines SCK for the channel in use. Having a single Data pin makes the Sendb0 subroutine much simpler again. I'm very pleased.
 
cpedw,

As understand your program it has the following structure.
Code:
FOR string = 1 to 3
   <some code?>                <-- The total time reduction is only 3 times any reduction you make here

   FOR LED = 1 to 40
      <code block start>          
      ... calculate the values for this LED ...    <-- The total time reduction is only 120 times any reduction you make here
      <code block end>

      sendPacket( $FF, blue, green, red )

         Subroutine Sendb0            <-- The total time reduction is 3 strings x 40 LEDS x 4 bytes x 8 bits = 3,840 times for any reduction you make in Sendb0.

   NEXT LED
NEXT string

Improvements in sendB0 have the greatest multiplier of 3,840.
- Testing on my 14M2 shows that switching from "SDO = b27 / $80" to "PIN_SDO = bit7" in Sendb0 saves 394ms, so your program should be taking about 0.6s now?
- The "PIN_SDO = bit7: pulsout SCK, 1" commands are the fastest way I can think of to do this bigbanging. So I think that the performance you get with your latest version in post #27 is about the best you can get from the bitbanging section of the code.

Improvements in the code to calculate the values for each LED have a multiplier of 120 so, depending upon how complicated these calculations are, you might be able to speed things up by tuning this code.
- Every 83us you can save in this code will speed the rate to update all 120 LEDs by 0.1s.
- You can find out the limit to how fast this bitbanging can update 120 LEDs by deleting all this code and replacing it with the single command "b0=<any constant>" in the initialization section where it will on run once outside of the loops.

I don't imagine that you have much code after the "FOR string = 1 to 3" command and it may not matter because with a multiplier of only 3 tuning this code probably won't make any noticible change in the rate to update all the LEDs.
 
Last edited:
cpedw,

I've realized that in my previous post I put the string and LED loops in the wrong order but befrore I correct my post I thought of two questions I have for you.

1) Is your intention to be able to display a different pattern on each of the three strings or do you intend to display the same pattern on all 3 strings?

2) Are the two loops in your program coded string first, like this:
Code:
FOR string = 1 to 3
  FOR LED = 1 to 40
  NEXT
NEXT
or are they coded LED first, like this:
Code:
FOR LED = 1 to 40
  FOR string = 1 to 3
  NEXT
NEXT
 
Last edited:
Currently, showing the same pattern on all 3 strings. The plan, now that is working, is to have different patterns on the 3 strings.

String is the inner loop ( as in your second example). It was an arbitrary choice; I can't see any advantage to either arrangement.
 
I can't see any advantage to either arrangement
For showing the same pattern on all 3 strings having the strings as the inner loop will be faster because you only need to calculate the 3 colour bytes for 40 LEDs in the outer loop.

When you move to showing a different pattern on each string you will need to do the calculation for 120 LEDs no matter which order you have the two loops.
 
Cpedw,

FYI, I tested replacing the BitBanging code with the PEEKSFR/POKESFR instructions from GraemeGo's post to use the hardware SPI module and it only reduced the cycle time by about 0.1s.
 
Cpedw,

To refresh 120 LEDs the BitBanging code calls the subroutine SendB0 504 times.
- 480 times to send 4 bytes for 120 LEDS
- 24 times to send the start and end instructions for each string. 2 instructions x 4 bytes x 3 strings.

On my 14M2 replacing the sendB0 subroutine with a macro saves 0.22s to refresh all three strings, BUT at a large cost for the size of the code. This is 877 bytes for the PE APA102 example 6, from 271 bytes for the original example 6 with the subroutine to 1148 bytes the the subroutine calls replaced with macro calls.

If you'd like to test the speed improvement and check the effect it has on the size of your program:
- Add a new macro SendB0
Code:
#macro sendB0()
  PIN_SDO = bit7 : pulsout SCK, 1
  PIN_SDO = bit6 : pulsout SCK, 1
  PIN_SDO = bit5 : pulsout SCK, 1
  PIN_SDO = bit4 : pulsout SCK, 1
  PIN_SDO = bit3 : pulsout SCK, 1
  PIN_SDO = bit2 : pulsout SCK, 1
  PIN_SDO = bit1 : pulsout SCK, 1
  PIN_SDO = bit0 : pulsout SCK, 1
#endmacro

- Modify the existing macro sendPacket to use the new macro sendB0:
#macro sendPacket( n1, n2, n3, n4 )
b0 = n1
SendB0
b0 = n2
SendB0
b0 = n3
SendB0
b0 = n4
SendB0
#endmacro

- Comment out the existing subroutine sendB0.

In the PE APA102 example 6 code there are 12 calls to sendB0 that are done once at the start of the program to clear the LED strings before sending any colour data.
Because these are done once then can be left using a call to the sendB0 subroutine, which will reduce the size of the program by reducing the number of copies of the BitBanging code from 28 to 17 (16 macro substitutons & 1 subroutine).

Is your program is based on the PE APA102 example 6 code, and if it is, do you have this section of code in the SetLeds subroutine?
Code:
    if w1 = w0 then
    send( b10, b11, b12 ) ; varying colour
    else
      send( $00, $00, $00 ) ; off
    end if
 
[post #20] ...... I think it unlikely to be able to split the SCK or SDO pin signals three ways without some external hardware.
Hi,

Actually, it does appear to be possible to split the SDO Hardware signal three ways with the 14M2. It can be output on two different pins, selected by the APFCON0 (Alternate Pin Function Control) SFR (Special Function Register); and for neither you could set the Output(s) to Tri-State (i.e. as an Input with high impedance, or via the TRIS register). Also, the SDO(1) signal can be internally connected (via input 10) to the Modulation Input of the DSR (see #22 above), so that three different output pins are potentially available and controllable by software. ;) The 20M2 has an SDO(2) Output as well.

However, it's really the SCK pin (which doesn't have APFCON nor DSR input options) that we want to split, or all three strings will still be clocked (probably with garbage data on the unselected SDO lines). But a trick could be to connect a medium-valued resistor (say 10k) from the SDO of the last LED back to the SDI of the first LED in each string. Then (with the associated PICaxe Data Output tri-stated) the data will be rotated around the "unselected" Strings, with the required string driven by the selected (Low Impedance) Output from the PICaxe.

But a better "minimal external hardware" configuration could use any pin number (including Input Only) and any number of pins. Basically, connect a signal diode (anode) from each desired controllable "SCK" pin to the specific Hardware SCK Output pin of the PICaxe. Then Enable the internal "Weak Pullup Resistor" on the relevant pin(s), or connect an external pullup resistor for any Output pin that doesn't have the WPU facility available (i.e. some pins on the 18M2 and X2 chips). The WPU then pulls the pin(s) High, whilst the corresponding diode pulls them Low, to generate the desired clock waveform. To disable the signal on any pin, command the pin Low, or switch the TRIS (I/O selection) pin to Output (Low), or switch off the internal WPU resistor (in this case a "Very Weak" Pulldown resistor might to needed overcome any stray capacitive coupling). Note that these new controllable SCK pins must NOT be commanded to High Output, because it would conflict with (i.e. short circuit via the diode) a Low from the real SCK Output pin.

I won't say any more about software operating speed here, except that MACROs do potentially use much more Program space. But this doesn't matter unless you're running out of program memory (or might do so in the future). :)

Cheers, Alan.
 
- Modify the existing macro sendPacket to use the new macro sendB0:
#macro sendPacket( n1, n2, n3, n4 )
b0 = n1
SendB0
b0 = n2
SendB0
b0 = n3
SendB0
b0 = n4
SendB0
#endmacro
I implemented that and the program size leapt from 500 to 1500 bytes. That's still manageable but might have to be abandoned if my pattern generation gets much more complex.

Incidentally, I was surprised that
Code:
b0 = n1 : Sendb0
generates a syntax error.
 
Is your program is based on the PE APA102 example 6 code, and if it is, do you have this section of code in the SetLeds subroutine?
Code:
    if w1 = w0 then
    send( b10, b11, b12 ) ; varying colour
    else
      send( $00, $00, $00 ) ; off
    end if
I've taken the macros from Example 6, with correction and slight modification.

That code snippet switches off all but one LED in the string at a time. I am lighting many at once so I don't have that.
 
@alleycat I might yet delve into the internal SPI module. With diode-OR on the clock, I can continue with a single data line and speed things up plus release more code space.
 
Incidentally, I was surprised that
B0=N1: SendB0
generates a syntax error.
SendB0 is the name of the macro and all the text of that macro definition gets substituted in place of the string "SendB0" exactly where that string exists in the code before being passed to the compiler.

I struck this too when I first created the macro as an exact copy of the body of the subroutine. The output from the precompiler with the substituted text appended starting at the end of the line "B0=N1:" ended up being invalid BASIC syntax.

I didn't go to the trouble of investigating the problem syntax, Because the macro was a multi-line macro I simply put the macro name on a separate line where I knew the text substitution would likely not cause a problem.
 
Last edited:
Back
Top