element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Arduino
  • Products
  • More
Arduino
Blog NeoPixel Portenta Implementation
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Arduino to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: MSchmidtty
  • Date Created: 5 Mar 2024 9:00 PM Date Created
  • Views 1912 views
  • Likes 6 likes
  • Comments 1 comment
  • portenta h7
  • neopixels
  • spi
  • led
  • iot
  • arduino
Related
Recommended

NeoPixel Portenta Implementation

MSchmidtty
MSchmidtty
5 Mar 2024

Having researched for a way to control NeoPixels with a Arduino Portenta I was surprised how much information there was available yet nobody had yet created a working library. I spent the past few days getting this working myself so I wanted to provide what I'd create so that I might save some others the effort. 

Another blog was written on this: 

(+) Porting NeoPixels to the Arduino Portenta - element14 Community

However after referencing the possible usage of SPI, this author write a driver using blocking code (Delays). This is unfortunately not an option for me so I chose to try and accomplish with SPI what evidently didn't work for others. 

In order for this to work, I also gained a lot of information from Josh.com 

NeoPixels Revealed: How to (not need to) generate precisely timed signals | josh.com

This blog was very helpful for showing how the protocol worked and what tolerances it would allow. 

The SPI driver for the neopixel operates by creating each bit in the neopixel protocol using a byte in the SPI protocol. By changing the value in the SPI message, you can essentially create variable length pulses that mimic the neopixel protocol. SPI's ability to "transfer" whole messages makes this an ideal tool. This library thus crafts a message where each byte represents a bit of neopixel data and then schedules the transfer over the SPI bus. The end result is a very successful neopixel driver. 
I completed all my testing using RGB neopixels but I attempted to implement the library to support the other's I've heard of. My testing wasn't exhaustive but it should get you very close to a working solution. 

This image shows the SPI creating a few "1" bits follows by a few "0"s. The SPI interpreter on the Saleae reveals how this message is input to the SPI hardware.

image

/*
  NeoPixelSPI.h - Library for controlling neopixels with SPI on the Arduino Portenta H7.
  Created by Mark Schmidt, March 5, 2024.
  Released into the public domain.

  This class utilizes the SPI module to control NeoPixels. This is accomplished 
  by having SPI output bytes which coorespond to bits in the neopixel protocol. 
  The value of the SPI byte controls the length of the neopixel bit.

  See https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
  for more information as it was very helpful in the creation of the library. I am 
  not associated with josh.com and merely benefitted from his work.
*/

#ifndef NeoPixelSPI_h
#define NeoPixelSPI_h

#include "mbed.h"
#include "Arduino.h"

enum LED_TYPES_E
{
	LED_TYPES_W, //Select for any single color neopixel
	LED_TYPES_RGB,
	LED_TYPES_RGBW,
};

///
struct LED_CONFIG_S
{
	LED_TYPES_E type = LED_TYPES_RGB;
	byte red = 0;
	byte green = 0;
	byte blue = 0;
	byte white = 0;
};

class NeoPixelSPI
{
public:

  	/// Constructor: Creates an instance of neoPixelSPI
	/// Param: spi_device - should be an SPI device which hasn't been initialized. Only 
	///    the MOSI line is required, the other two lines will be configured by setup()
	///    but can be turned back off after calling setup().
	/// Param: numberNeoPixels - this specifies the max (if varying) number of
	///     numberNeoPixels in the chain. This will allocate a sufficient output array
	/// 	as if every neopixel is RGBW. This could be wasteful if you care a lot about ram.
    NeoPixelSPI(mbed::SPI * spi_device, int numberNeoPixels);
	
  	/// Constructor: Creates an instance of neoPixelSPI
	/// Param: spi - should be an SPI device which hasn't been initialized. Only the 
	///    MOSI line is required, the other two lines will be configured by setup()
	///    but can be turned back off after calling setup().
	/// Param: numberNeoPixels - this specifies the max (if varying) number of
	///     numberNeoPixels in the chain. This will allocate a sufficient output array
	/// 	as if every neopixel is RGBW. This could be wasteful if you care a lot about ram.
	/// Param: countingColors - set this to true if you are providing the number of led colors
	///		in the full chain (Add 4 for each RGBW, 3 for RGB, 1 for W). This is more memory efficient
	///		but otherwise just extra hassle. If set false, this operates the same as the other constructor.
    NeoPixelSPI(mbed::SPI * spi_device, int numberNeoPixels, bool countingColors);

	/// Setup: Enables and configures the SPI driver.
    void setup();

	/// Transfer: Set the value of all the neopixels in the chain.
	///	Param: ledConfigurations should be an array of LED Configurations
    void transfer(LED_CONFIG_S * ledConfigs, int numLEDs);

private:
	/// Convert a byte (one LED color config) into the SPI encoding.
	void byteToSPI(byte * byteString, byte value);

	/// Takes in the LED configurations and writes over the outputString 
	/// returns the length of the SPI message.
	int buildLEDMsg(LED_CONFIG_S * ledConfigs, int numLEDs);

	/// The SPI Device which this class should control.
	mbed::SPI * spi;
	/// The output string array
	byte * outputString;
};

#endif

#include "neoPixelSPI.h"

//Number of bits to send per neopixel color.
#define BYTE_TRANSFER_LENGTH 	8
//Default length per neopixel unless otherwise specified.
#define RGBW_TRANSFER_LENGTH 	4
//Probably can be skipped but is added to provide a "latch" prior to starting the message.
#define HEADER_BITS				30
//This just ensures we meet the "latch" timing
#define FOOTER_BITS				30
//SPI Bus frequency: 5.7MHz (175 ns bits) It doesn't actually achieve this specific time but we can dream
#define FREQUENCY 5700000


NeoPixelSPI::NeoPixelSPI(mbed::SPI * spi_device, int numberNeoPixels)
{
	//Store the SPI device we should use.
	spi = spi_device;
	//Create our output string which will be fed to the SPI module. This needs to be the max length.
	outputString = (byte*)malloc((numberNeoPixels * RGBW_TRANSFER_LENGTH * BYTE_TRANSFER_LENGTH) + HEADER_BITS + FOOTER_BITS);
}

NeoPixelSPI::NeoPixelSPI(mbed::SPI * spi_device, int numberNeoPixels, bool countingColors)
{
	//Store the SPI device we should use.
	spi = spi_device;

	//Create our output string which will be fed to the SPI module. This needs to be the max length.
	if(countingColors)
	{
		//The user is brave and wants to save some bytes.
		outputString = (byte*)malloc((numberNeoPixels * BYTE_TRANSFER_LENGTH) + HEADER_BITS + FOOTER_BITS);
	}
	else
	{
		//Why would you use this constructor then? We can't trust this user, assume 4 color neopixels.
		outputString = (byte*)malloc((numberNeoPixels * RGBW_TRANSFER_LENGTH * BYTE_TRANSFER_LENGTH) + HEADER_BITS + FOOTER_BITS);
	}
}


void NeoPixelSPI::setup()
{
	spi->frequency(FREQUENCY); 
	spi->format(8,0);//8 bits data (meaning each neopixel bit will take 8*frequency to send).
}

void NeoPixelSPI::transfer(LED_CONFIG_S * ledConfigs, int numLEDs)
{
	int length = buildLEDMsg(ledConfigs, numLEDs);
	spi->transfer((byte*) outputString, length, (byte*)0, 0, 0);
}


void NeoPixelSPI::byteToSPI(byte * byteString, byte value)
{
	for(int i = 0; i < BYTE_TRANSFER_LENGTH; i++)
	{
		if(value % 2 == 1)
		{
			byteString[BYTE_TRANSFER_LENGTH - i - 1] = 0xF0;
		} 
		else
		{
			byteString[BYTE_TRANSFER_LENGTH - i - 1] = 0xC0;
		}
		value = value >> 1;
	}
}


int NeoPixelSPI::buildLEDMsg(LED_CONFIG_S * ledConfigs, int numLEDs)
{
	byte header [HEADER_BITS] = {0};
	byte footer [FOOTER_BITS] = {0};
	
	//Keeps track where we are in our message
	int writeIndex = 0;

	//Write Header - just gives us a clean off period prior to sending data
	for(int i = 0; i < HEADER_BITS; i++)
	{
		outputString[writeIndex] = header[i];
		writeIndex++;
	}

	//Add in the data for all the LEDs
	for(int i = 0; i < numLEDs; i++)
	{
		if(ledConfigs[i].type == LED_TYPES_RGB || ledConfigs[i].type == LED_TYPES_RGBW)
		{
			///  Red - Green - Blue
			byteToSPI(&outputString[writeIndex], ledConfigs[i].red);
			writeIndex += 8;
			byteToSPI(&outputString[writeIndex], ledConfigs[i].green);
			writeIndex += 8;
			byteToSPI(&outputString[writeIndex], ledConfigs[i].blue);
			writeIndex += 8;
		}
		if(ledConfigs[i].type == LED_TYPES_W || ledConfigs[i].type == LED_TYPES_RGBW)
		{
			///  White
			byteToSPI(&outputString[writeIndex], ledConfigs[i].white);
			writeIndex += 8;
		}
	}
	
	//Write Footer
	for(int i = 0; i < FOOTER_BITS; i++)
	{
		outputString[writeIndex] = footer[i];
		writeIndex++;
	}
	return writeIndex;
}

Here is some example code utilizing the library

/*
Arduino Portenta neopixelSPI example
*/

#include "mbed.h"
#include "Arduino.h"
#include "neoPixelSPI.h"

using namespace mbed;

/*********************************************************************
 *  Constants
 *********************************************************************/

const unsigned long LED_FLIP_PERIOD = 500; //milliseconds

// SPI-LED Driver Definitions
#define NUMBER_LEDS				10

/*********************************************************************
 *  Global Variables
 *********************************************************************/
// When was the lsat time the LED was flipped.
unsigned long previousLEDSwitch = 0;

SPI spi(PC_3, PC_2, PI_1);
NeoPixelSPI neoPixelSpi(&spi, NUMBER_LEDS);
LED_CONFIG_S leds [NUMBER_LEDS];

/*********************************************************************
 *  Application
 *********************************************************************/

void setup() 
{
	//Configure debug LEDs
	pinMode(LEDR, OUTPUT);
	pinMode(LEDG, OUTPUT);
	pinMode(LEDB, OUTPUT);

	digitalWrite(LEDR, LOW);
	digitalWrite(LEDG, HIGH);
	digitalWrite(LEDB, HIGH);


	neoPixelSpi.setup();

	//Prepopulate LED Data
	leds[0].red = 255;
	leds[0].green = 255;
	leds[0].blue = 255;

	leds[1].red = 255;
	leds[1].green = 0;
	leds[1].blue = 0;

	leds[2].red = 0;
	leds[2].green = 255;
	leds[2].blue = 0;

	leds[3].red = 0;
	leds[3].green = 0;
	leds[3].blue = 255;

	leds[4].red = 255;
	leds[4].green = 255;
	leds[4].blue = 0;

	leds[5].red = 255;
	leds[5].green = 0;
	leds[5].blue = 255;

	leds[6].red = 0;
	leds[6].green = 255;
	leds[6].blue = 255;

	leds[7].red = 127;
	leds[7].green = 127;
	leds[7].blue = 127;

	leds[8].red = 255;
	leds[8].green = 255;
	leds[8].blue = 255;

	leds[9].red = 255;
	leds[9].green = 255;
	leds[9].blue = 255;

	//Turn off Red LED to indicate Setup Complete.
	digitalWrite(LEDR, HIGH);
}

void loop() 
{
	// What is the current time?
	unsigned long currentTimeMS = millis();
	
	/////////////////////LED UPDATE//////////////////////
	if(currentTimeMS - previousLEDSwitch > LED_FLIP_PERIOD)
	{
		neoPixelSpi.transfer(leds, NUMBER_LEDS);

		digitalWrite(LEDB, !digitalRead(LEDB));
		previousLEDSwitch = currentTimeMS;
	}
}

I hope this works for you! If you notice any issues, feel free to comment. 

  • Sign in to reply
  • b18shea
    b18shea over 1 year ago

    How do you have the NeoPixels and Portenta connected? Which pin(s) from the Portenta?

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube