ItsyBitsy M4 and NeoPixel ZeroDMA on pin 5

Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
DilvishEhldar
 
Posts: 17
Joined: Sun Aug 12, 2018 2:58 pm

ItsyBitsy M4 and NeoPixel ZeroDMA on pin 5

Post by DilvishEhldar »

According to the docs at https://learn.adafruit.com/introducing- ... m4/pinouts

#5 - GPIO #5. This is a special OUTPUT-only pin that can PWM. It is level-shifted up to Vhi voltage, so its perfect for driving NeoPixels that want a ~5V logic level input. You can use this with our NeoPixel DMA control library to automatically write NeoPixel data without needing any processor time.

But then the product page says:

1 x Special Vhigh output pin gives you the higher voltage from VBAT or VUSB, for driving NeoPixels, servos, and other 5V-logic devices. Digital 5 level-shifted output for high-voltage logic level output.
Can drive NeoPixels or DotStars on any pins, with enough memory to drive 60,000+ pixels. DMA-NeoPixel support on the VHigh pin so you can drive pixels without having to spend any processor time on it.

the vhigh pin is not that #5 pin on the ItsyBitsy. So which pin allows for NeoPixel DMA library?

I currently have an application that has the NeoPxel wired to digital pin 5. This works with the regular neopixel library. When i replace it with the ZeroDMA version it sets the color once and hangs in the show() method.

#include <Adafruit_NeoPixel_ZeroDMA.h>

Adafruit_NeoPixel_ZeroDMA leds = Adafruit_NeoPixel_ZeroDMA(NUM_LEDS, NEO_PIXEL_PORT, NEO_GRB);

I would like to use this version so that I can reduce the interrupt issues with the NeoPixel version.

User avatar
DilvishEhldar
 
Posts: 17
Joined: Sun Aug 12, 2018 2:58 pm

Re: ItsyBitsy M4 and NeoPixel ZeroDMA on pin 5

Post by DilvishEhldar »

Still looking for information on this. Perhaps an wiring/code example of ZeroDMA being used on an ItsyBitsy M4 would also suffice.

User avatar
MartinL2
 
Posts: 60
Joined: Sun Mar 10, 2019 2:17 pm

Re: ItsyBitsy M4 and NeoPixel ZeroDMA on pin 5

Post by MartinL2 »

Hi DilvishEhldar,
Perhaps an wiring/code example of ZeroDMA being used on an ItsyBitsy M4 would also suffice.
Here's a code example using the TC3 timer and the DMA on digtial pin D5 for the Itsy Bitsy M4:

Code: Select all

// Drive NeoPixels on Itsy Bitsy M4 digital pin 5 with the DMAC and TC3
#define NEOPIXEL_NO 8
#define DMAC_CHANNEL 11

struct __packed
{
  struct __packed
  {
    uint8_t green[8];
    uint8_t red[8];
    uint8_t blue[8];
  } neoPixelGRB[NEOPIXEL_NO];
  uint8_t resetCode = 0;
} neoPixelData;

typedef struct    // DMAC descriptor structure
{                      
  uint16_t BANNED;
  uint16_t BANNED;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));           // DMAC write back descriptor array
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));     // DMAC channel descriptor array
dmacdescriptor descriptor __attribute__ ((aligned (16)));                          // DMAC place holder descriptor

void setup() 
{
  neoPixelBegin();              // Initialise timer TC3 and DMAC 
}

void loop()                                             // Test code...
{
  for (uint8_t i = 0; i < NEOPIXEL_NO; i++)
  {
    neoPixelWrite(i, 0, 32, 0);                         // Configure memory to set up 8 NeoPixels to display red at 1/8th intensity 
  }
  neoPixelDisplay();                                    // Send data to the NeoPixels
    
  delay(1000);                                          // Wait 1 second
  for (uint8_t i = 0; i < NEOPIXEL_NO; i++)
  {
    neoPixelWrite(i, 32, 0, 0);                         // Configure memory to set up 8 NeoPixels to display green at 1/8th intensity
  }
  neoPixelDisplay();                                    // Send data to the NeoPixels 
  delay(1000);                                          // Wait 1 second
}

void neoPixelBegin()                                    // Initialise timer TC3 and the DMAC
{
  GCLK->PCHCTRL[26].reg = GCLK_PCHCTRL_BANNED |        // Enable perhipheral channel
                          GCLK_PCHCTRL_GEN_GCLK2;    // Connect 100MHz generic clock 2 to TC2 and TC3

  // Enable the pin multiplxer for digital pin 5
  PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;
  // Switch the pin mulitplexer to peripheral E for digital pin 5
  PORT->Group[g_APinDescription[5].ulPort].PMUX[g_APinDescription[5].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);

  TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 |     // Set prescaler to 1, 100MHz/1 = 100MHz
                           TC_CTRLA_MODE_COUNT8;         // Set the counter to 8-bit mode
                         
  TC3->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NPWM;           // Set timer to output Normal PWM mode (NPWM)

  TC3->COUNT8.PER.reg = 124;                             // Set the timer period PER for a PWM frequency of 800kHz
  while (TC3->COUNT8.SYNCBUSY.bit.PER);                  // Wait for synchronization

  TC3->COUNT8.CC[1].reg = 0;                             // Set the duty-cycle to 0% or 0V
  while (TC3->COUNT8.SYNCBUSY.bit.CC1);                  // Wait for synchronization

  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;              // Set the base address descriptor
  DMAC->WRBADDR.reg = (uint32_t)wrb;                              // Set the write back address
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);    // Enable the DMAC peripheral and all priority levels 

  // Set-up DMAC to trigger on TC3 overflow
  DMAC->Channel[DMAC_CHANNEL].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TC3_DMAC_ID_OVF) | DMAC_CHCTRLA_TRIGACT_BURST;
  descriptor.descaddr = 0;                                                                  // Indicate that this is the only descriptor (no linked list)
  descriptor.srcaddr = (uint32_t)&neoPixelData + sizeof(neoPixelData);                      // Set the source as the head of the neoPixelData structure
  descriptor.dstaddr = (uint32_t)&TC3->COUNT8.CCBUF[1].reg;                                 // Set the destination as the TC3 buffered counter compare register
  descriptor.BANNED = sizeof(neoPixelData);                                                  // Send neoPixelData structure in bytes   
  descriptor.BANNED = DMAC_BANNED_BEATSIZE_BYTE | DMAC_BANNED_SRCINC | DMAC_BANNED_VALID;   // Send data as bytes, increment the source memory
  memcpy(&descriptor_section[DMAC_CHANNEL], &descriptor, sizeof(descriptor));               // Copy the descriptor to the descriptor for the DMAC channel
  
  TC3->COUNT8.CTRLA.bit.ENABLE = 1;               // Enable TC3
  while (TC3->COUNT8.SYNCBUSY.bit.ENABLE);        // Wait for synchronization 
}

void neoPixelWrite(uint16_t neoPixelNumber, uint8_t green, uint8_t red, uint8_t blue)
{
  uint32_t colour = green << 16 | red << 8 | blue;                                // Combine the green, red and blue colour data
  uint8_t* pNeoPixelData = (uint8_t*)&neoPixelData.neoPixelGRB[neoPixelNumber];   // Select the appropriate NeoPixel in memory with a byte pointer
  for (uint32_t bitMask = 0x00800000; bitMask > 0; bitMask >>= 1)                 // Iterate through each colour bit
  {   
    *pNeoPixelData++ = (colour & bitMask) ? 79 : 39;     // Load the memory with GRB colour duty cycle pulse widths for each bit  
  }
}

void neoPixelDisplay()
{
  DMAC->Channel[DMAC_CHANNEL].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;                  // Trigger a DMAC transfer of the data to the NeoPixels
}
Just specify the number of NeoPixels:

Code: Select all

#define NEOPIXEL_NO 8
Initialise in the setup() portion of your sketch:

Code: Select all

neoPixelBegin();                                      // Initialise timer TC3 and DMAC
Specify the NeoPixel number and GRB colour with an intensity from 0 to 255:

Code: Select all

neoPixelWrite(i, 0, 32, 0);                         // Configure memory to set up 8 NeoPixels to display red at 1/8th intensity
When the NeoPixels have all been configured, pull the trigger on the DMAC to send the data:

Code: Select all

neoPixelDisplay();                                    // Send data to the NeoPixels 
The code is a little less efficient on memory than the Adafruit library, using 24 bytes per pixel, however the timing exactly matches NeoPixel specification using an 800kHz binary coded PWM signal with 32% and 64% duty-cycle pulse widths.

User avatar
DilvishEhldar
 
Posts: 17
Joined: Sun Aug 12, 2018 2:58 pm

Re: ItsyBitsy M4 and NeoPixel ZeroDMA on pin 5

Post by DilvishEhldar »

This is really great! I will try this right away. It looks very promising!

Thank you SO much for taking the time to respond.

User avatar
DilvishEhldar
 
Posts: 17
Joined: Sun Aug 12, 2018 2:58 pm

Re: ItsyBitsy M4 and NeoPixel ZeroDMA on pin 5

Post by DilvishEhldar »

Thank you again for taking the time to share an example. It worked perfectly. Unfortunately it does not work when I combine it with other libraries and code. In fact I found that the AdaFruit_NeoPixel_ZeroDMA also works when I created an isolated test case. The issue for my project seems to be incompatibility between the the AdaFruit branch of the Teensy Audio library. Even though that uses the AdaFruit_ZeroDMA library, there is some major incompatibility that I have not tracked down. Perhaps it is with timers. Perhaps it is a bug in the ZeroDMA library when multiple channels are allocated.

User avatar
MartinL2
 
Posts: 60
Joined: Sun Mar 10, 2019 2:17 pm

Re: ItsyBitsy M4 and NeoPixel ZeroDMA on pin 5

Post by MartinL2 »

Are you able to check what's being output on D5 with an oscilloscope?

Usually the DMA works pretty well with multiple channels. Although, when I've used it to simultaneously generate 16 high speed, binary coded PWM pulses on 8 outputs, it was necessary to add two 0's to both the beginning and end of the transmitted data structure (for the 8 DMA channels), in order to allow all the PWM outputs to be clocked out successfully. Otherwise it would sometimes miss a pulse, either at the beginning or end of the sequence.

If it's a timer compatibility issue, then timers TCC1 and TCC2 are also available on D5.

Locked
Please be positive and constructive with your questions and comments.

Return to “Itsy Bitsy Boards”