Last active
February 4, 2018 20:06
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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