Skip to content

Instantly share code, notes, and snippets.

@kasperkamperman
Last active February 4, 2018 20:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kasperkamperman/10ae89fb2a0fdc96d46ca6f05243ed2c to your computer and use it in GitHub Desktop.
Save kasperkamperman/10ae89fb2a0fdc96d46ca6f05243ed2c to your computer and use it in GitHub Desktop.
Output DMX data on the Particle Photon. Transmission is done in the background with the DMA implementation. You can use any pin, so you are not restricted to the serial port. Counterpart is that it uses a lot of memory (4 bytes for every transmitted DMX bit).
/* Demo to create a DMX output on every pin of the Photon.
It send the information through DMA, so transmitting data won't cost you
extra CPU cycles. Counterpart of this method is that each bit occupies 32bits
(unt32_t) in memory. So almost half of the Photon's memory is used for the
data array.
You can choose to use continuousMode is DMX is transmitted on the maximum speed
of around 44 fps. You can't go faster (at least not if you use the whole buffer).
This code supports on pin, however without any additional memory cost you could
create more DMX pins (more universes) as long as you stay on the same GPIO port.
See also my demo code at:
https://www.kasperkamperman.com/blog/particle-photon-stm32f205-dma-control-gpio-pins/
Thanks Ulrich Radig for the idea and example code (STM32 Discovery Example):
https://www.ulrichradig.de/home/index.php/dmx/8-kanal-art-net
MIT licence
kasperkamperman.com (04-02-2018)
*/
#include "Particle.h"
// Select the DMX output pin
const int dmxPin = D2;
// only use this when you build local
//SYSTEM_MODE(MANUAL);
// DMX timing
#define DMX_BIT_TIME 4 // 4us
#define DMX_BREAK_TIME 92 // 92us low
#define DMX_MARK 12 // 12 us high output
#define DMX_MTBF 0 // mark time between frames, not obligatory, you could make it 4
#define DMX_START_BIT 1
#define DMX_DATA_BIT 8
#define DMX_STOP_BIT 2
#define DMX_TRANSMIT_BYTES 513 //512 Bytes + Startcode 0x00
#define DMX_BUFFER_SIZE ((DMX_START_BIT + DMX_DATA_BIT + DMX_STOP_BIT + (DMX_MTBF/DMX_BIT_TIME))*DMX_TRANSMIT_BYTES) \
+ ((DMX_MARK+DMX_BREAK_TIME)/DMX_BIT_TIME)
// if you make it through DMX will be transmitted in the background automatically
// so you don't have to call transmitDMXFrame()
const bool continuousMode = false;
// if we don't use continuousMode we do a check to see if transmission of the
// current buffer is still in progress
bool bufferTransmissionInProgress = false;
const uint16_t pinMapMask = PIN_MAP[dmxPin].gpio_pin;
const uint16_t pinMapHigh = PIN_MAP[dmxPin].gpio_pin;
const uint32_t dmxPinLow = (0 ^ pinMapMask) << 16;
const uint32_t dmxPinHigh = pinMapHigh + ((pinMapHigh ^ pinMapMask) << 16);
uint32_t dmxBSRRBuffer[DMX_BUFFER_SIZE];
void dmxWriteChannel(uint16_t channel, uint8_t byte);
void transmitDMXBuffer();
void dmxBufferInit();
void timerInit();
void dmaInit();
int fadeCounter = 0;
void setup() {
//WiFi.off(); // only local build!
Serial.begin(57600);
pinMode(dmxPin, OUTPUT);
dmxBufferInit();
timerInit();
dmaInit();
}
void loop() {
fadeCounter++;
if(fadeCounter>255) fadeCounter=0;
Serial.println(fadeCounter);
dmxWriteChannel(1, fadeCounter);
dmxWriteChannel(3, fadeCounter);
if(!continuousMode) transmitDMXBuffer();
// 1000/25 = 40fps
delay(25);
}
void dmxWriteChannel(uint16_t channel, uint8_t byte) {
// dmx channel 0 doesn't exist
// it's empty (although not all lights follow this spec I've found out)
if(channel==0) return;
uint16_t bufferPosition =
((DMX_START_BIT + DMX_DATA_BIT + DMX_STOP_BIT + (DMX_MTBF/DMX_BIT_TIME)) * channel) +
((DMX_MARK+DMX_BREAK_TIME)/DMX_BIT_TIME);
bufferPosition += DMX_START_BIT;
for(int i = 0; i < DMX_DATA_BIT; i++) {
if(byte & (1<<i)) {
dmxBSRRBuffer[bufferPosition] = dmxPinHigh;
}
else {
dmxBSRRBuffer[bufferPosition] = dmxPinLow;
}
bufferPosition++;
}
}
void transmitDMXBuffer() {
if(bufferTransmissionInProgress == true) {
if (DMA_GetFlagStatus(DMA2_Stream1, DMA_FLAG_TCIF1) == true)
{ // Transmission done. Stop DMA before new restart.
DMA_Cmd(DMA2_Stream1, DISABLE);
DMA_ClearFlag(DMA2_Stream1, DMA_FLAG_TCIF1);
bufferTransmissionInProgress = false;
// since transmission is not in progress we can transmit the buffer again
transmitDMXBuffer();
}
}
else {
DMA_Cmd(DMA2_Stream1, ENABLE);
bufferTransmissionInProgress = true;
}
}
void dmxBufferInit(void) {
uint32_t *buffer_pointer = &dmxBSRRBuffer[0];
// Break
for(int i = 0; i < (DMX_BREAK_TIME/DMX_BIT_TIME); i++) {
*buffer_pointer++ = dmxPinLow;
}
// Mark After Break
for(int i = 0; i < (DMX_MARK/DMX_BIT_TIME); i++) {
*buffer_pointer++ = dmxPinHigh;
}
// Data
for(int i = 0; i < (DMX_TRANSMIT_BYTES); i++) {
// start bit 1
for(int j = 0; j < DMX_START_BIT; j++) {
*buffer_pointer++ = dmxPinLow;
}
// data bits 8
for(int j = 0; j < DMX_DATA_BIT; j++) {
*buffer_pointer++ = dmxPinLow;
}
// stop bits 2
for(int j = 0; j < DMX_STOP_BIT; j++) {
*buffer_pointer++ = dmxPinHigh;
}
// Mark time between frames (not always necessary)
for(int j = 0; j < (DMX_MTBF/DMX_BIT_TIME); j++) {
*buffer_pointer++ = dmxPinHigh;
}
}
// Data end
}
void timerInit (void) {
// https://github.com/pkourany/SparkIntervalTimer/blob/master/src/SparkIntervalTimer.cpp
const uint16_t SIT_PRESCALERu = (uint16_t)(SystemCoreClock / 1000000UL) - 1; //To get TIM counter clock = 1MHz
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_DeInit(TIM8);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);
TIM_TimeBaseStructure.TIM_Prescaler = SIT_PRESCALERu;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 3; //gives 4us on the oscilloscope;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM8,&TIM_TimeBaseStructure);
TIM_ClearFlag(TIM8,TIM_FLAG_Update);
TIM_Cmd(TIM8, ENABLE);
}
void dmaInit(void) {
// DMA2 only connects to GPIO ports...
// DMA2 channel 7 stream 1 connects to TIM8_UP
DMA_InitTypeDef DMA_InitStructure;
// Clock enable
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_Cmd(DMA2_Stream1, DISABLE);
DMA_DeInit(DMA2_Stream1);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_Channel = DMA_Channel_7;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = DMX_BUFFER_SIZE;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t) dmxBSRRBuffer;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_PeripheralBaseAddr = ((uint32_t)&(PIN_MAP[dmxPin].gpio_peripheral->BSRRL));
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
if(continuousMode) {
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
}
else {
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
}
DMA_Init(DMA2_Stream1, &DMA_InitStructure);
if(continuousMode) {
DMA_Cmd(DMA2_Stream1, ENABLE);
}
// DMA-Timer8 enable
TIM_DMACmd(TIM8,TIM_DMA_Update,ENABLE);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment