I'm trying to utilise some spare GPIO pins from the BBB for soft SPI to control my APA102 LED strip in a bit banging fashion.

My project constraints require the use of the Analog I/O and as I understand I can't use the HW SPI0 and Analog I/O at the same time on BBB. Additionally I can see what I believe is soft SPI setup for CTAG via the /lib/firmware/BB-BELA-CTAG-SPI-00A0.dtbo overlay which I used to seed my soft SPI attempts.

I'm using a rev B5 board and Bela v0.5.0alpha2. I'm attempting to use P8.39 and P8.40 (after referring to the P8 pin outs) as I only need CLK and MOSI. I want to try use the cAPA102 lib as mentioned in https://forum.bela.io/d/1284-capa102-not-working, which I found here: https://github.com/CoorFun/cAPA102/blob/master/README.md -- it appears to use /dev/spidevX.X under the hood.

So far I've had no success loading ANY device tree overlays other than the defaults for BBB. Each time my Bela goes into a reboot cycle and powers down, at which point I restore the uEnv.txt and try again. I've tried following the instructions to enable config-pin usage via /lib/firmware/cape-universalh-00A0.dtbo, I cloned the repo and ran make all install which appeared to copy successfully, and changing my uEnv.txt having universalh being the only overlay, being the first and being the last. All failed to boot. I'm not quite sure what the debugging procedure is for the overlays from boot.

As for the spare GPIO pins, I've gone and created an overlay that compiles successfully:

/*
 * Copyright (C) 2017 Henrik Langer henni19790@googlemail.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
/dts-v1/;
/plugin/;

/ {
	compatible = "ti,beaglebone", "ti,beaglebone-black", "ti,beaglebone-green";

	/* identification */
	part-number = "BELA-DOTSTAR";
	version = "00A0", "A0";

	/* state the resources this cape uses */
	exclusive-use =
		/* the pin header uses */
		"P8.39",	/* spi_sclk */
		"P8.40",	/* spi_mosi */
		/* the hardware ip uses */
		"spi_gpio";

	fragment@0 {
		target = <&am33xx_pinmux>;
		__overlay__ {
			bb_spi_gpio_pins: pinmux_bb_spi_gpio_pins {
				pinctrl-single,pins = <
					0x0B8 0x37 /* spi_sclk, 	MODE7 | INPUT_PULLUP | SPI_SCLK P8_39 */
					0x0BC 0x17 /* spi_mosi, 	MODE7 | OUTPUT_PULLUP | SPI_MOSI P8_40 */
				>;
			};
		};
	};

	fragment@1 {
		target = <&spi_gpio>;
		__overlay__ {
			#address-cells = <1>;
			#size-cells = <0>;

			status = "okay";

			pinctrl-names = "default";
			pinctrl-0 = <&bb_spi_gpio_pins>;

			channel@0 {
				#address-cells = <1>;
				#size-cells = <0>;

				compatible = "spidev";

				reg = <0>;
				spi-max-frequency = <100000>;
			};

			channel@1 {
				#address-cells = <1>;
				#size-cells = <0>;

				compatible = "spidev";

				reg = <1>;
				spi-max-frequency = <100000>;
			};
		};
	};
};

I don't quite understand why the need for 2 channels, and may be a hangover from copy&paste of the CTAG overlay. I haven't found any decent docs for the spi_gpio fragment (https://www.kernel.org/doc/Documentation/devicetree/bindings/spi/spi-gpio.yaml -- best I could find).

I then compile the above overlay with $ dtc -O dtb -o BELA-DOTSTAR-00A0.dtbo -b 0 -@ BELA-DOTSTAR-00A0.dts and move it to /lib/firmware, finally updating the uEnv.txt:

uenvcmd=echo loading ${fdtfile}; load mmc ${mmcid}:2 ${fdtaddr} boot/dtbs/${uname_r}/${fdtfile}; if env exists uboot_overlay_addr0; then setenv overlay ${uboot_overlay_addr0}; run bela_loadoverlay; fi; if env exists uboot_overlay_addr1; then setenv overlay ${uboot_overlay_addr1}; run bela_loadoverlay; fi; if env exists uboot_overlay_addr2; then setenv overlay ${uboot_overlay_addr2}; run bela_loadoverlay; fi; if env exists uboot_overlay_addr3; then setenv overlay ${uboot_overlay_addr3}; run bela_loadoverlay; fi; if env exists uboot_overlay_addr4; then setenv overlay ${uboot_overlay_addr4}; run bela_loadoverlay; fi; if env exists uboot_overlay_addr5; then setenv overlay ${uboot_overlay_addr5}; run bela_loadoverlay; fi; if env exists uboot_overlay_addr6; then setenv overlay ${uboot_overlay_addr6}; run bela_loadoverlay; fi; if env exists uboot_overlay_addr7; then setenv overlay ${uboot_overlay_addr7}; run bela_loadoverlay; fi; load mmc ${mmcid}:2 ${loadaddr} /boot/vmlinuz-${uname_r}; setenv bootargs console=${console} root=/dev/mmcblk${mmcid}p2 ro rootfstype=ext4 rootwait coherent_pool=1M net.ifnames=0 quiet; bootz ${loadaddr} - ${fdtaddr};
bela_loadoverlay=echo loading ${overlay}; load mmc ${mmcid}:2 ${rdaddr} ${overlay}; fdt addr $fdtaddr; fdt resize 0x60000; fdt apply ${rdaddr}; fdt resize 0x60000;

uboot_overlay_addr2=/lib/firmware/BB-BELA-00A1.dtbo
uboot_overlay_addr3=/lib/firmware/BB-BELA-CTAG-SPI-00A0.dtbo
uboot_overlay_addr4=/lib/firmware/BB-BELA-DOTSTAR-00A0.dtbo

console=ttyS0,115200n8
uname_r=4.14.108-ti-xenomai-r143
mmcid=0

As mentioned above my Bela gets stuck in a reboot cycle and eventually powers down. I've also tried it as the only overlay and the first.

I noticed else where that BBB are deprecating /slots which adds up with why I can't find it to manually try load my overlay at runtime to check dmesg. Any help most appreciated!

The BB-BELA-CTAG-SPI-00A0.dtbo overlay that is loaded by default already provides a soft-SPI interface at these pins:

gpio-sck = <&gpio0 11 0>; //P8.32
gpio-mosi = <&gpio0 9 0>; //P8.33
gpio-miso = <&gpio0 26 0>; //P8.14
cs-gpios = <&gpio0 27 0 &gpio0 10 0>; //P8.17 / P8.31

this comes up as spidev at /dev/spidev32766.0 /dev/spidev3.*

Thanks for the quick response @giuliomoro !

That was actually my first attempt I forgot to mention (I assumed there was some runtime claiming of the pins for CTAG or something), I'm using 5v and GND from the analog strip, CI to P8.32 and MOSI to P8.33 with the code from https://forum.bela.io/d/1284-capa102-not-working/9:

#include <Bela.h>
#include "cAPA102.hpp"
#include <iostream>

AuxiliaryTask lightTask;		// Auxiliary task to read/write BLE

void lights(void* data);

int readCount = 0;			// How long until we read again...
int readIntervalSamples = 100; // How many samples between reads
int readInterval = 1;

bool setup(BelaContext *context, void *userData)
{
	if(cAPA102_Init(72, 3, 0, 128) == 0)
		std::cout<<"works";
	cAPA102_Refresh();
	lightTask = Bela_createAuxiliaryTask(lights, 90, "light");
	return true;
}

void render(BelaContext *context, void *userData)
{
		if(++readCount >= readIntervalSamples) {
			readCount = 0;
			Bela_scheduleAuxiliaryTask(lightTask);
		}
	
}

void cleanup(BelaContext *context, void *userData)
{

}

void lights(void* data){
	cAPA102_Set_Pixel_RGB(5, 255, 0, 0);
	cAPA102_Refresh();
	usleep(5000);
}
root@bela:~# ls -al /dev/spidev*
crw------- 1 root root 153, 1 Jul 13  2021 /dev/spidev2.0
crw------- 1 root root 153, 0 Jul 13  2021 /dev/spidev2.1
crw------- 1 root root 153, 3 Jul 13  2021 /dev/spidev3.0
crw------- 1 root root 153, 2 Jul 13  2021 /dev/spidev3.1

Nothing lights up.

    adeduke crw------- 1 root root 153, 3 Jul 13 2021 /dev/spidev3.0

    right sorry I got it wrong this is the one.

    adeduke n (I assumed there was some runtime claiming of the pins for CTAG or something)

    Those pins are used once the first time the IDE loads or the first time you run a Bela program and then not used again until the next boot.

    What image are you running? 0.3.8g images would require you to run config-pin on P8.33 at runtime:

    config-pin P8.33 spi

    for it to work. Did you verify that you are getting any signal at all on P8.32 and P8.33 ? Ideally, you'd use a scope to verify that. If you don't have one, put a regular LED to ground from each pin and verify that both LED's behaviour change when sending a burst of data.

    Is there a minimum clock speed or maximum inter-bit interval that the APA102 can tolerate ? As this is soft-SPI, max clock will be around 100kHz and inter-bit interval can be arbitrarily long, even several millliseconds.

    I also see that the APA102 datasheet requires at least 0.7*Vdd for the DI voltage. Now, when the LEDs are powered from Vcc=5V, this means that in principle the 3.3V from the Bela's SPI output are not enough (though they work in many cases, like the one you linked to). See possible solutions here https://forum.bela.io/d/3001-control-neopixel-with-pure-data/25

      11 days later

      giuliomoro What image are you running? 0.3.8g images would require you to run config-pin on P8.33 at runtime:

      Bela image, v0.5.0alpha2,  6 January 2022
      
      More info at https://github.com/BelaPlatform/bela-image-builder/releases
      
      Built with bela-image-builder bullseye-2022@fd118edcc8180c6ccb9cc88e38b6bead9414c8d7
      on Thu Jan  6 02:18:52 UTC 2022
      $ root@bela:~# config-pin P8.33 spi
      Invalid mode: spi

      I'm no longer tied to v0.5.0 so can downgrade if it makes debugging easier. I only upgraded to have access to newer versions of C++ and protobufs.

      giuliomoro Did you verify that you are getting any signal at all on P8.32 and P8.33 ? Ideally, you'd use a scope to verify that. If you don't have one, put a regular LED to ground from each pin and verify that both LED's behaviour change when sending a burst of data.

      I attached some LEDs and once I start the program above I can see P8_32 (SCLK) is dimly lit and P8_33 (MOSI) is bright and flashing rapidly. I then got a hold of an oscilloscope:

      It would seem P8_32 (SCLK) doesn't appear to be pulsing and is set high at about ~2.36v, whilst P8_33 (MOSI) looks like data pulses.

      giuliomoro Is there a minimum clock speed or maximum inter-bit interval that the APA102 can tolerate ? As this is soft-SPI, max clock will be around 100kHz and inter-bit interval can be arbitrarily long, even several millliseconds.

      I believe minimum clock speed is 1kHz, however inter-bit interval I can't find a reliable number for yet. Eventually I want to use the second PRU to run the LED strip, but I'm in a prototyping phase so was hoping I could get away with soft spi to develop with.

      It would seem P8_32 (SCLK) doesn't appear to be pulsing and is set high at about 2.36v

      Try opening it with the Gpio class and set it to an output. There must be something else setting it to an input elsewhere maybe?

      Something like

      #include <Gpio.h>
      Gpio gGpio;
      ...
      bool setup(BelaContext* context, void*)
      {
         gGpio.setup(11, Gpio::OUTPUT); // P8.32 is GPIO 11
      ...
      return true;
      }

      That did it! Although gGpio.setup doesn't exist so I assumed you meant gGpio.open. It even appears my strip is working correctly too.

      Any ideas what might be setting P8_32 to input? Or how to find it?

      My updated code for anyone else who might come across this thread.

      #include <Bela.h>
      #include "cAPA102.hpp"
      #include <iostream>
      #include <Gpio.h>
      Gpio gGpio;
      
      AuxiliaryTask lightTask;		// Auxiliary task to read/write BLE
      
      void lights(void* data);
      
      int readCount = 0;			// How long until we read again...
      int readIntervalSamples = 100; // How many samples between reads
      int readInterval = 1;
      
      bool setup(BelaContext *context, void *userData)
      {
      	gGpio.open(11, Gpio::OUTPUT); // P8.32 is GPIO 11
      	
      	if(cAPA102_Init(72, 3, 0, 128) == 0)
      		std::cout<<"works";
      	cAPA102_Refresh();
      	lightTask = Bela_createAuxiliaryTask(lights, 90, "light");
      	return true;
      }
      
      void render(BelaContext *context, void *userData)
      {
      		if(++readCount >= readIntervalSamples) {
      			readCount = 0;
      			Bela_scheduleAuxiliaryTask(lightTask);
      		}
      	
      }
      
      void cleanup(BelaContext *context, void *userData)
      {
      
      }
      
      void lights(void* data){
      	cAPA102_Set_Pixel_RGB(5, 255, 0, 0);
      	cAPA102_Refresh();
      	usleep(5000);
      }

      Thanks for your help so far!

        adeduke Although gGpio.setup doesn't exist so I assumed you meant gGpio.open.

        correct

        adeduke Any ideas what might be setting P8_32 to input? Or how to find it?

        Not sure, but also why worry? Setting it manually is safer anyhow, so you don't rely on defaults. In fact, it would be best to do it for all relevant pins!