diff --git a/RpiLedBars/Makefile b/RpiLedBars/Makefile index 8e32a6e..6e0de35 100644 --- a/RpiLedBars/Makefile +++ b/RpiLedBars/Makefile @@ -1,6 +1,6 @@ CC=gcc -CFLAGS=-Wall -g #-DDEBUG -LDFLAGS=#-lpthread +CFLAGS=-Wall -g -D_DEBUG +LDFLAGS=-lwiringPi #-lpthread SRCDIR=src OBJDIR=obj BINDIR=bin diff --git a/RpiLedBars/src/artnet/ArtnetnodeWifi.cpp b/RpiLedBars/src/artnet/ArtnetnodeWifi.cpp new file mode 100644 index 0000000..acfc76d --- /dev/null +++ b/RpiLedBars/src/artnet/ArtnetnodeWifi.cpp @@ -0,0 +1,315 @@ +/* + +Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015 + +Copyright (c) 2016-2020 Stephan Ruloff +https://github.com/rstephan/ArtnetnodeWifi + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, under version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +#include + + +const char ArtnetnodeWifi::artnetId[] = ARTNET_ID; + +ArtnetnodeWifi::ArtnetnodeWifi() +{ + // Initalise DMXOutput array + for (int i = 0; i < DMX_MAX_OUTPUTS; i++) { + DMXOutputs[i][0] = 0xFF; + DMXOutputs[i][1] = 0xFF; + DMXOutputs[i][2] = 0; + } + + // Start DMX tick clock + msSinceDMXSend = 0; + + // Init DMX buffers + for (int i = 0; i < DMX_MAX_OUTPUTS; i++) { + memset(DMXBuffer[i], 0, sizeof(DMXBuffer[i])); + } + + sequence = 1; + physical = 0; + outgoingUniverse = 0; + dmxDataLength = 0; +} + +/** +@retval 0 Ok +*/ +uint8_t ArtnetnodeWifi::begin(String hostname) +{ + byte mac[6]; + + Udp.begin(ARTNET_PORT); + localIP = WiFi.localIP(); + localMask = WiFi.subnetMask(); + localBroadcast = IPAddress((uint32_t)localIP | ~(uint32_t)localMask); + + WiFi.macAddress(mac); + PollReplyPacket.setMac(mac); + PollReplyPacket.setIP(localIP); + PollReplyPacket.canDHCP(true); + PollReplyPacket.isDHCP(true); + + host = hostname; + + return 0; +} + +void ArtnetnodeWifi::setShortName(const char name[]) +{ + PollReplyPacket.setShortName(name); +} + +void ArtnetnodeWifi::setLongName(const char name[]) +{ + PollReplyPacket.setLongName(name); +} + +void ArtnetnodeWifi::setName(const char name[]) +{ + PollReplyPacket.setShortName(name); + PollReplyPacket.setLongName(name); +} + +void ArtnetnodeWifi::setNumPorts(uint8_t num) +{ + PollReplyPacket.setNumPorts(num); +} + +void ArtnetnodeWifi::setStartingUniverse(uint16_t startingUniverse) +{ + PollReplyPacket.setStartingUniverse(startingUniverse); +} + +uint16_t ArtnetnodeWifi::read() +{ + uint8_t startcode; + + packetSize = Udp.parsePacket(); + + if (packetSize <= ARTNET_MAX_BUFFER && packetSize > 0) { + Udp.read(artnetPacket, ARTNET_MAX_BUFFER); + + // Check that packetID is "Art-Net" else ignore + if (memcmp(artnetPacket, artnetId, sizeof(artnetId)) != 0) { + return 0; + } + + opcode = artnetPacket[8] | artnetPacket[9] << 8; + + switch (opcode) { + case OpDmx: + return handleDMX(0); + case OpPoll: + return handlePollRequest(); + case OpNzs: + startcode = artnetPacket[13]; + if (startcode != 0 && startcode != DMX_RDM_STARTCODE) { + return handleDMX(startcode); + } + } + + return opcode; + } + + return 0; +} + +uint16_t ArtnetnodeWifi::makePacket(void) +{ + uint16_t len; + uint16_t version; + + memcpy(artnetPacket, artnetId, sizeof(artnetId)); + opcode = OpDmx; + artnetPacket[8] = opcode; + artnetPacket[9] = opcode >> 8; + version = 14; + artnetPacket[10] = version >> 8; + artnetPacket[11] = version; + artnetPacket[12] = sequence; + sequence++; + if (!sequence) { + sequence = 1; + } + artnetPacket[13] = physical; + artnetPacket[14] = outgoingUniverse; + artnetPacket[15] = outgoingUniverse >> 8; + len = dmxDataLength + (dmxDataLength % 2); // make an even number + artnetPacket[16] = len >> 8; + artnetPacket[17] = len; + + return len; +} + +int ArtnetnodeWifi::write(void) +{ + uint16_t len; + + len = makePacket(); + Udp.beginPacket(host.c_str(), ARTNET_PORT); + Udp.write(artnetPacket, ARTNET_DMX_START_LOC + len); + + return Udp.endPacket(); +} + +int ArtnetnodeWifi::write(IPAddress ip) +{ + uint16_t len; + + len = makePacket(); + Udp.beginPacket(ip, ARTNET_PORT); + Udp.write(artnetPacket, ARTNET_DMX_START_LOC + len); + + return Udp.endPacket(); +} + +void ArtnetnodeWifi::setByte(uint16_t pos, uint8_t value) +{ + if (pos > 512) { + return; + } + artnetPacket[ARTNET_DMX_START_LOC + pos] = value; +} + + +bool ArtnetnodeWifi::isBroadcast() +{ + if (Udp.remoteIP() == localBroadcast){ + return true; + } else { + return false; + } +} + +uint16_t ArtnetnodeWifi::handleDMX(uint8_t nzs) +{ + if (isBroadcast()) { + return 0; + } else { + // Get universe + uint16_t universe = artnetPacket[14] | artnetPacket[15] << 8; + + // Get DMX frame length + uint16_t dmxDataLength = artnetPacket[17] | artnetPacket[16] << 8; + + // Sequence + uint8_t sequence = artnetPacket[12]; + + if (artDmxCallback) { + (*artDmxCallback)(universe, dmxDataLength, sequence, artnetPacket + ARTNET_DMX_START_LOC); + } + + for(int a = 0; a < DMX_MAX_OUTPUTS; a++){ + if(DMXOutputs[a][1] == universe){ + for (int i = 0 ; i < DMX_MAX_BUFFER ; i++){ + if(i < dmxDataLength){ + DMXBuffer[a][i] = artnetPacket[i+ARTNET_DMX_START_LOC]; + } + else{ + DMXBuffer[a][i] = 0; + } + } + } + } + + if (nzs) { + return OpNzs; + } else { + return OpDmx; + } + } +} + +uint16_t ArtnetnodeWifi::handlePollRequest() +{ + if (1 || isBroadcast()) { + Udp.beginPacket(localBroadcast, ARTNET_PORT); + Udp.write(PollReplyPacket.printPacket(), sizeof(PollReplyPacket.packet)); + Udp.endPacket(); + + return OpPoll; + } else{ + return 0; + } +} + +void ArtnetnodeWifi::enableDMX() +{ + DMXOutputStatus = true; +} + +void ArtnetnodeWifi::disableDMX() +{ + DMXOutputStatus = false; +} + +void ArtnetnodeWifi::enableDMXOutput(uint8_t outputID) +{ + DMXOutputs[outputID][2] = 1; + + int numEnabled = 0; + for(int i = 0; i < DMX_MAX_OUTPUTS; i++){ + if(DMXOutputs[i][2]==1){ + if(numEnabled<4){ + numEnabled++; + } + } + } + PollReplyPacket.setNumPorts(numEnabled); + PollReplyPacket.setOutputEnabled(outputID); +} + +void ArtnetnodeWifi::disableDMXOutput(uint8_t outputID) +{ + DMXOutputs[outputID][2] = 0; + + int numEnabled = 0; + for(int i = 0; i < DMX_MAX_OUTPUTS; i++){ + if(DMXOutputs[i][2]==1){ + if(numEnabled<4){ + numEnabled++; + } + } + } + PollReplyPacket.setNumPorts(numEnabled); + PollReplyPacket.setOutputDisabled(outputID); +} + +uint8_t ArtnetnodeWifi::setDMXOutput(uint8_t outputID, uint8_t uartNum, uint16_t attachedUniverse) +{ + // Validate input + if(outputID < DMX_MAX_OUTPUTS && uartNum != 0xFF && attachedUniverse != 0xFF){ + DMXOutputs[outputID][0] = uartNum; + DMXOutputs[outputID][1] = attachedUniverse; + DMXOutputs[outputID][2] = 0; + PollReplyPacket.setSwOut(outputID, attachedUniverse); + return 1; + } else { + return 0; + } +} + +void ArtnetnodeWifi::tickDMX(uint32_t time) +{ + msSinceDMXSend += time; + if(msSinceDMXSend > DMX_MS_BETWEEN_TICKS){ + sendDMX(); + msSinceDMXSend = 0; + } +} diff --git a/RpiLedBars/src/artnet/ArtnetnodeWifi.h b/RpiLedBars/src/artnet/ArtnetnodeWifi.h new file mode 100644 index 0000000..0b5cf87 --- /dev/null +++ b/RpiLedBars/src/artnet/ArtnetnodeWifi.h @@ -0,0 +1,151 @@ +#ifndef ARTNETNODEWIFI_H +#define ARTNETNODEWIFI_H +/* + +Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015 + +Copyright (c) 2016 Stephan Ruloff +https://github.com/rstephan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, under version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +#include +#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) +#include +#elif defined(ARDUINO_ARCH_ESP8266) +#include +#elif defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_SAMD_MKR1000) +#include +#else +#include +#endif +#else +#error "Architecture not supported!" +#endif +#include +#include "OpCodes.h" +#include "NodeReportCodes.h" +#include "StyleCodes.h" +#include "PriorityCodes.h" +#include "ProtocolSettings.h" +#include "PollReply.h" + + +class ArtnetnodeWifi +{ +public: + ArtnetnodeWifi(); + + uint8_t begin(String hostname = ""); + uint16_t read(); + + // Node identity + void setShortName(const char name[]); + void setLongName(const char name[]); + void setName(const char name[]); + void setNumPorts(uint8_t num); + + void setStartingUniverse(uint16_t startingUniverse); + + // Transmit + int write(void); + int write(IPAddress ip); + void setByte(uint16_t pos, uint8_t value); + + inline void setUniverse(uint16_t universe) + { + outgoingUniverse = universe; + } + + inline void setPhysical(uint8_t port) + { + physical = port; + } + + inline void setLength(uint16_t len) + { + dmxDataLength = len; + } + + inline void setPortType(uint8_t port, uint8_t type) + { + PollReplyPacket.setPortType(port, type); + } + + // DMX controls + void enableDMX(); + void disableDMX(); + void enableDMXOutput(uint8_t outputID); + void disableDMXOutput(uint8_t outputID); + + uint8_t setDMXOutput(uint8_t outputID, uint8_t uartNum, uint16_t attachedUniverse); + + // DMX tick + void tickDMX(uint32_t time); + + // Return a pointer to the start of the DMX data + inline uint8_t* getDmxFrame(void) + { + return artnetPacket + ARTNET_DMX_START_LOC; + } + + inline void setArtDmxCallback(void (*fptr)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)) + { + artDmxCallback = fptr; + } + +private: + WiFiUDP Udp; + PollReply PollReplyPacket; + String host; + + // Packet handlers + uint16_t handleDMX(uint8_t nzs); + uint16_t handlePollRequest(); + + // Packet vars + uint8_t artnetPacket[ARTNET_MAX_BUFFER]; + uint16_t packetSize; + uint16_t opcode; + uint8_t sequence; + uint8_t physical; + uint16_t outgoingUniverse; + uint16_t dmxDataLength; + IPAddress localIP; + IPAddress localMask; + IPAddress localBroadcast; + + // Packet functions + bool isBroadcast(); + uint16_t makePacket(void); + + // DMX settings + bool DMXOutputStatus; + uint16_t DMXOutputs[DMX_MAX_OUTPUTS][3]; + uint8_t DMXBuffer[DMX_MAX_OUTPUTS][DMX_MAX_BUFFER]; + + uint16_t startingUniverse; + + // DMX tick + void sendDMX(); + uint8_t* getDmxFrame(uint8_t outputID); + uint8_t msSinceDMXSend; + + void (*artDmxCallback)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data); + static const char artnetId[]; +}; + +#endif diff --git a/RpiLedBars/src/artnet/NodeReportCodes.h b/RpiLedBars/src/artnet/NodeReportCodes.h new file mode 100644 index 0000000..25953bd --- /dev/null +++ b/RpiLedBars/src/artnet/NodeReportCodes.h @@ -0,0 +1,22 @@ +#ifndef NODEREPORTCODES_H +#define NODEREPORTCODES_H +// List of hex values and discriptions of Node Report Codes + +#define RcDebug 0x0000 // Booted in debug mode (Only used in development) +#define RcPowerOk 0x0001 // Power On Tests successful +#define RcPowerFail 0x0002 // Hardware tests failed at Power On +#define RcSocketWr1 0x0003 // Last UDP from Node failed due to truncated length, Most likely caused by a collision. +#define RcParseFail 0x0004 // Unable to identify last UDP transmission. Check OpCode and \packet length. +#define RcUdpFail 0x0005 // Unable to open Udp Socket in last transmission attempt +#define RcShNameOk 0x0006 // Confirms that Short Name programming via ArtAddress, was successful. +#define RcLoNameOk 0x0007 // Confirms that Long Name programming via ArtAddress, was successful. +#define RcDmxError 0x0008 // DMX512 receive errors detected. +#define RcDmxUdpFull 0x0009 // Ran out of internal DMX transmit buffers. +#define RcDmxRxFull 0x000a // Ran out of internal DMX Rx buffers. +#define RcSwitchErr 0x000b // Rx Universe switches conflict. +#define RcConfigErr 0x000c // Product configuration does not match firmware. +#define RcDmxShort 0x000d // DMX output short detected. See GoodOutput field. +#define RcFirmwareFail 0x000e // Last attempt to upload new firmware failed. +#define RcUserFail 0x000f // User changed switch settings when address locked by remote programming. User changes ignored. + +#endif \ No newline at end of file diff --git a/RpiLedBars/src/artnet/PollReply.cpp b/RpiLedBars/src/artnet/PollReply.cpp new file mode 100644 index 0000000..1cfeac9 --- /dev/null +++ b/RpiLedBars/src/artnet/PollReply.cpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015 + +Copyright (c) 2016 Stephan Ruloff +https://github.com/rstephan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, under version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +// Class for saving details to and for constructing pollreply packets + +#include + +PollReply::PollReply(){ + // Zero out vars + memset(packet.IPAddr, 0, sizeof(packet.IPAddr)); + memset(packet.NodeReport, 0, sizeof(packet.NodeReport)); + memset(packet.PortTypes, 0, sizeof(packet.PortTypes)); + memset(packet.GoodInput, 0, sizeof(packet.GoodInput)); + memset(packet.GoodOutput, 0, sizeof(packet.GoodOutput)); + memset(packet.SwIn, 0, sizeof(packet.SwIn)); + memset(packet.SwOut, 0, sizeof(packet.SwOut)); + memset(packet.Filler, 0, sizeof(packet.Filler)); + setStartingUniverse(0); +} + +void PollReply::setMac(byte mac[]){ + packet.Mac[0] = mac[0]; + packet.Mac[1] = mac[1]; + packet.Mac[2] = mac[2]; + packet.Mac[3] = mac[3]; + packet.Mac[4] = mac[4]; + packet.Mac[5] = mac[5]; +} + +void PollReply::setIP(IPAddress IP){ + packet.IPAddr[0] = IP[0]; + packet.IPAddr[1] = IP[1]; + packet.IPAddr[2] = IP[2]; + packet.IPAddr[3] = IP[3]; +} + +void PollReply::setShortName(const char name[]){ + int shortNameLen = sizeof(packet.ShortName); + + memset(packet.ShortName, 0, shortNameLen); + + shortNameLen--; + for(int i = 0; i < shortNameLen && name[i] != 0; i++){ + packet.ShortName[i] = name[i]; + } +} + +void PollReply::setLongName(const char name[]){ + int longNameLen = sizeof(packet.LongName); + + memset(packet.LongName, 0, longNameLen); + + longNameLen--; + for(int i = 0; i < longNameLen && name[i] != 0; i++){ + packet.LongName[i] = name[i]; + } +} + +void PollReply::canDHCP(bool can){ + if(can){ + packet.Status2 = packet.Status2 | 0b00100000; + } + else{ + packet.Status2 = packet.Status2 & (~0b00100000); + } +} + +void PollReply::isDHCP(bool is){ + if(is){ + packet.Status2 = packet.Status2 | 0b01000000; + } + else{ + packet.Status2 = packet.Status2 & (~0b01000000); + } +} + +void PollReply::setNumPorts(uint8_t num){ + packet.NumPortsLo = num; +} + +void PollReply::setPortType(uint8_t port, uint8_t type) +{ + if (port < 4) { + packet.PortTypes[port] = type; + } +} + +void PollReply::setSwOut(uint8_t port, uint16_t universe){ + if(port < 4){ + packet.SwOut[port] = universe & 0b0000000000001111; + } +} + +void PollReply::setOutputEnabled(uint8_t port){ + if(port < 4){ + packet.PortTypes[port] = packet.PortTypes[port] | 0b10000000; + packet.GoodOutput[port] = packet.GoodOutput[port] | 0b10000000; + setSwOut(port, startingUniverse + port); + } +} + +void PollReply::setOutputDisabled(uint8_t port){ + if(port < 4){ + packet.PortTypes[port] = packet.PortTypes[port] & (~0b10000000); + packet.GoodOutput[port] = packet.GoodOutput[port] & (~0b10000000); + setSwOut(port, 0); + } +} + +void PollReply::setStartingUniverse(uint16_t startUniverse){ + startingUniverse = startUniverse; + packet.NetSwitch = startUniverse >> 8; + packet.SubSwitch = (startUniverse & 0b000000011111111) >> 4; +} + +uint8_t* PollReply::printPacket(){ + return (uint8_t *)&packet; +} diff --git a/RpiLedBars/src/artnet/PollReply.h b/RpiLedBars/src/artnet/PollReply.h new file mode 100644 index 0000000..e032c54 --- /dev/null +++ b/RpiLedBars/src/artnet/PollReply.h @@ -0,0 +1,188 @@ +#ifndef POLLREPLY_H +#define POLLREPLY_H +/* + +Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, under version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +// Class for saving details to and for constructing pollreply packets + +#include +#include +#include "OpCodes.h" +#include "NodeReportCodes.h" +#include "StyleCodes.h" +#include "PriorityCodes.h" +#include "ProtocolSettings.h" + +struct replyPollPacket{ + char ID[8] = "Art-Net"; // protocol ID = "Art-Net" + //char ID[8]; // protocol ID = "Art-Net" + uint16_t OpCode = OpPollReply; // == OpPollReply + uint8_t IPAddr[4]; // 0 if not yet configured + uint16_t Port = 0x1936; + + uint8_t VersionInfoHi = 0; // The node's current FIRMWARE VERS hi + uint8_t VersionInfoLo = 1; // The node's current FIRMWARE VERS lo + + uint8_t NetSwitch = 0; // Bits 14-8 of the 15 bit universe number are encoded into the bottom 7 bits of this field. + // This is used in combination with SubSwitch and Swin[] or Swout[] to produce the full universe address. + uint8_t SubSwitch = 0; // Bits 7-4 of the 15 bit universe number are encoded into the bottom 4 bits of this field. + // This is used in combination with NetSwitch and Swin[] or Swout[] to produce the full universe address. + + uint16_t Oem = 0x0190; // Manufacturer code, bit 15 set if + // extended features avail + + uint8_t UbeaVersion = 0; // Firmware version of UBEA + + uint8_t Status = 0; + // bit 0 = 0 UBEA not present + // bit 0 = 1 UBEA present + // bit 1 = 0 Not capable of RDM (Uni-directional DMX) + // bit 1 = 1 Capable of RDM (Bi-directional DMX) + // bit 2 = 0 Booted from flash (normal boot) + // bit 2 = 1 Booted from ROM (possible error condition) + // bit 3 = Not used + // bit 54 = 00 Universe programming authority unknown + // bit 54 = 01 Universe programming authority set by front panel controls + // bit 54 = 10 Universe programming authority set by network + // bit 76 = 00 Indicators Normal + // bit 76 = 01 Indicators Locate + // bit 76 = 10 Indicators Mute + + uint8_t EstaMan[2] = {0, 0}; // ESTA manufacturer id, lo byte + + char ShortName[18] = "ElLab Artnetnode\0"; // short name defaults to IP + char LongName[64] = "Electric Laboratory Artnetnode\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // long name + char NodeReport[64]; // Text feedback of Node status or errors + // also used for debug info + + uint8_t NumPortsHi = 0; // 0 + uint8_t NumPortsLo = 0; // 4 If num i/p ports is dif to output ports, return biggest + + uint8_t PortTypes[4]; + // bit 7 is output + // bit 6 is input + // bits 0-5 are protocol number (0= DMX, 1=MIDI) + // for DMX-Hub ={0xc0,0xc0,0xc0,0xc0}; + + + uint8_t GoodInput[4]; + // bit 7 is data received + // bit 6 is data includes test packets + // bit 5 is data includes SIP's + // bit 4 is data includes text + // bit 3 set is input is disabled + // bit 2 is receive errors + // bit 1-0 not used, transmitted as zero. + // Don't test for zero! + + uint8_t GoodOutput[4]; + // bit 7 is data is transmitting + // bit 6 is data includes test packets + // bit 5 is data includes SIP's + // bit 4 is data includes text + // bit 3 output is merging data. + // bit 2 set if DMX output short detected on power up + // bit 1 set if DMX output merge mode is LTP + // bit 0 not used, transmitted as zero. + + uint8_t SwIn[4]; + // Bits 3-0 of the 15 bit universe number are encoded into the low nibble + // This is used in combination with SubSwitch and NetSwitch to produce the full universe address. + // THIS IS FOR INPUT - ART-NET or DMX + // NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO. + + + uint8_t SwOut[4]; + // Bits 3-0 of the 15 bit universe number are encoded into the low nibble + // This is used in combination with SubSwitch and NetSwitch to produce the full universe address. + // data belongs + // THIS IS FOR OUTPUT - ART-NET or DMX. + // NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO. + + uint8_t SwVideo = 0; + // Low nibble is the value of the video + // output channel + + uint8_t SwMacro = 0; + // Bit 0 is Macro input 1 + // Bit 7 is Macro input 8 + + + uint8_t SwRemote = 0; + // Bit 0 is Remote input 1 + // Bit 7 is Remote input 8 + + + uint8_t Spare1 = 0; // Spare, currently zero + uint8_t Spare2 = 0; // Spare, currently zero + uint8_t Spare3 = 0; // Spare, currently zero + uint8_t Style = 0; // Set to Style code to describe type of equipment + + uint8_t Mac[6]; // Mac Address, zero if info not available + + uint8_t BindIp[4]; // If this unit is part of a larger or modular product, this is the IP of the root device. + uint8_t BindIndex = 0; // Set to zero if no binding, otherwise this number represents the order of bound devices. A lower number means closer to root device. + + uint8_t Status2 = 0b00000000; + // bit 0 = 0 Node does not support web browser + // bit 0 = 1 Node supports web browser configuration + + // bit 1 = 0 Node's IP address is manually configured + // bit 1 = 1 Node's IP address is DHCP configured + + // bit 2 = 0 Node is not DHCP capable + // bit 2 = 1 Node is DHCP capable + + // bit 2-7 not implemented, transmit as zero + + + + uint8_t Filler[26]; // Filler bytes, currently zero. +}; + +class PollReply{ +public: + PollReply(); + + void setMac(byte mac[]); + void setIP(IPAddress IP); + void setShortName(const char name[]); + void setLongName(const char name[]); + + void setNumPorts(uint8_t num); + + void setSwOut(uint8_t id, uint16_t universe); + void setPortType(uint8_t port, uint8_t type); + + void setOutputEnabled(uint8_t port); + void setOutputDisabled(uint8_t port); + + void canDHCP(bool can); + void isDHCP(bool is); + + void setStartingUniverse(uint16_t startUniverse); + + uint8_t* printPacket(); + + struct replyPollPacket packet; + +private: + uint16_t startingUniverse; +}; + +#endif diff --git a/RpiLedBars/src/artnet/artnet.cpp b/RpiLedBars/src/artnet/artnet.cpp new file mode 100644 index 0000000..9a2aaa9 --- /dev/null +++ b/RpiLedBars/src/artnet/artnet.cpp @@ -0,0 +1 @@ +#include "artnet.h" diff --git a/RpiLedBars/src/artnet/artnet.h b/RpiLedBars/src/artnet/artnet.h new file mode 100644 index 0000000..93a36a4 --- /dev/null +++ b/RpiLedBars/src/artnet/artnet.h @@ -0,0 +1,4 @@ +#if !defined(__ARTNET_H__) +#define __ARTNET_H__ + +#endif // __ARTNET_H__ diff --git a/RpiLedBars/src/artnet/artnet_op_codes.h b/RpiLedBars/src/artnet/artnet_op_codes.h new file mode 100644 index 0000000..7318a7a --- /dev/null +++ b/RpiLedBars/src/artnet/artnet_op_codes.h @@ -0,0 +1,128 @@ +#if !defined(__ARTNET_OP_CODES_H__) +#define __ARTNET_OP_CODES_H__ + +/* List of hex values and discriptions of Opcodes */ + +/* This is an ArtPoll packet, no other data is contained in this UDP packet */ +#define OpPoll 0x2000 + +/* This is an ArtPollReply Packet. It contains device status information. */ +#define OpPollReply 0x2100 + +/* Diagnostics and data logging packet. */ +#define OpDiagData 0x2300 + +/* Used to send text based parameter commands. */ +#define OpCommand 0x2400 + +/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single + * Universe. */ +#define OpOutput 0x5000 + +/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single + * Universe. */ +#define OpDmx 0x5000 + +/* This is an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information + * for a single Universe. */ +#define OpNzs 0x5100 + +/* This is an ArtAddress packet. It contains remote programming information for a Node. */ +#define OpAddress 0x6000 + +/* This is an ArtInput packet. It contains enable – disable data for DMX inputs. */ +#define OpInput 0x7000 + +/* This is an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM + * discovery. */ +#define OpTodRequest 0x8000 + +/* This is an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery. */ +#define OpTodData 0x8100 + +/* This is an ArtTodControl packet. It is used to send RDM discovery control messages. */ +#define OpTodControl 0x8200 + +/* This is an ArtRdm packet. It is used to send all non discovery RDM messages. */ +#define OpRdm 0x8300 + +/* This is an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data. */ +#define OpRdmSub 0x8400 + +/* This is an ArtVideoSetup packet. It contains video screen setup information for nodes that + * implement the extended video features. */ +#define OpVideoSetup 0xa010 + +/* This is an ArtVideoPalette packet. It contains colour palette setup information for nodes that + * implement the extended video features. */ +#define OpVideoPalette 0xa020 + +/* This is an ArtVideoData packet. It contains display data for nodes that implement the extended + * video features. */ +#define OpVideoData 0xa040 + +/* This is an ArtMacMaster packet. It is used to program the Node’s MAC address, Oem device type and + * ESTA manufacturer code. This is for factory initialisation of a Node. It is not to be used by + * applications. */ +#define OpMacMaster 0xf000 + +/* This is an ArtMacSlave packet. It is returned by the node to acknowledge receipt of an + * ArtMacMaster packet. */ +#define OpMacSlave 0xf100 + +/* This is an ArtFirmwareMaster packet. It is used to upload new firmware or firmware extensions to + * the Node. */ +#define OpFirmwareMaster 0xf200 + +/* This is an ArtFirmwareReply packet. It is returned by the node to acknowledge receipt of an + * ArtFirmwareMaster packet or ArtFileTnMaster packet. */ +#define OpFirmwareReply 0xf300 + +/* Uploads user file to node. */ +#define OpFileTnMaster 0xf400 + +/* Downloads user file from node. */ +#define OpFileFnMaster 0xf500 + +/* Node acknowledge for downloads. */ +#define OpFileFnReply 0xf600 + +/* This is an ArtIpProg packet. It is used to reprogramme the IP, Mask and Port address of the Node. + */ +#define OpIpProg 0xf800 + +/* This is an ArtIpProgReply packet. It is returned by the node to acknowledge receipt of an + * ArtIpProg packet. */ +#define OpIpProgReply 0xf900 + +/* This is an ArtMedia packet. It is Unicast by a Media Server and acted upon by a Controller. */ +#define OpMedia 0x9000 + +/* This is an ArtMediaPatch packet. It is Unicast by a Controller and acted upon by a Media Server. + */ +#define OpMediaPatch 0x9100 + +/* This is an ArtMediaControl packet. It is Unicast by a Controller and acted upon by a Media + * Server. */ +#define OpMediaControl 0x9200 + +/* This is an ArtMediaControlReply packet. It is Unicast by a Media Server and acted upon by a + * Controller. */ +#define OpMediaContrlReply 0x9300 + +/* This is an ArtTimeCode packet. It is used to transport time code over the network. */ +#define OpTimeCode 0x9700 + +/* Used to synchronise real time date and clock */ +#define OpTimeSync 0x9800 + +/* Used to send trigger macros */ +#define OpTrigger 0x9900 + +/* Requests a node's file list */ +#define OpDirectory 0x9a00 + +/* Replies to OpDirectory with file list */ +#define OpDirectoryReply 0x9b00 + +#endif // __ARTNET_OP_CODES_H__ \ No newline at end of file diff --git a/RpiLedBars/src/artnet/artnet_protocol_settings.h b/RpiLedBars/src/artnet/artnet_protocol_settings.h new file mode 100644 index 0000000..a6a676b --- /dev/null +++ b/RpiLedBars/src/artnet/artnet_protocol_settings.h @@ -0,0 +1,26 @@ +#if !defined(__PROTOCOL_SETTINGS_H__) +#define __PROTOCOL_SETTINGS_H__ + +// UDP port +#define ARTNET_PORT 6454 + +// Buffers +#define ARTNET_MAX_BUFFER 530 +#define DMX_MAX_BUFFER 512 + +// Packet constants +#define ARTNET_ID "Art-Net" +#define ARTNET_DMX_START_LOC 18 + +// Packet confines +#define ARTNET_SHORT_NAME_MAX_LENGTH 17 +#define ARTNET_LONG_NAME_MAX_LENGTH 63 + +// DMX settings +#define DMX_MAX_OUTPUTS 4 +#define DMX_MS_BETWEEN_TICKS 25 + +// RDM +#define DMX_RDM_STARTCODE 0xCC + +#endif // __PROTOCOL_SETTINGS_H__ diff --git a/RpiLedBars/src/main.c b/RpiLedBars/src/main.c new file mode 100644 index 0000000..3a96531 --- /dev/null +++ b/RpiLedBars/src/main.c @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "rpi_artnet.h" +#include "rpi_pixleds.h" +#include "rpi_smi_defs.h" + +#include "rpi_selector.h" + +/* Command-line parameters */ +bool IsTestMode = false; +int chanLedCount = 0; + +/* Global */ +MEM_MAP vc_mem; +TXDATA_T *txdata; + +void parseCommandLineArgs(int argc, char const *argv[]); + +void execute_test_mode(); + +void execute_artnet_mode(); + +void execute_autonomous_mode(); + +void execute_autonomous2_mode(); + +int main(int argc, char const *argv[]) { + int previousMode = 0; + + // setup + parseCommandLineArgs(argc, argv); + + signal(SIGINT, terminate); + + setup_selector(); + + // setup led + map_devices(); + init_smi(LED_NCHANS > 8 ? SMI_16_BITS : SMI_8_BITS, SMI_TIMING); + map_uncached_mem(&vc_mem, VC_MEM_SIZE); + + setup_smi_dma(&vc_mem, TX_BUFF_LEN(chanLedCount), &txdata); + + artnet_init(); + + // loop + while (1) { + int mode = get_selector_position(); + + if (mode != previousMode) { +#if defined(_DEBUG) + print_selector(); +#endif + } + + switch (mode) { + case 0: + // mode test + execute_test_mode(); + break; + case 1: + // artnet mode + execute_artnet_mode(); + break; + case 2: + execute_autonomous_mode(); + break; + case 3: + // autonomous mode 2 + execute_autonomous2_mode(); + break; + + default: + printf("error in selector \n"); + break; + } + previousMode = mode; + } + + // printf("%s %u LED%s per channel, %u channels\n", IsTestMode ? "Testing" : "Setting", + // chanLedCount, + // chanLedCount == 1 ? "" : "s", LED_NCHANS); + + // for (size_t colorIndex = 0; colorIndex < 3; ++colorIndex) { + // for (size_t i = 0; i < chanLedCount; ++i) { + // set_color(on_rgbs[colorIndex], &tx_buffer[LED_TX_OSET(i)]); + // } + + // #if LED_NCHANS <= 8 + // swap_bytes(tx_buffer, TX_BUFF_SIZE(chanLedCount)); + // #endif + + // memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chanLedCount)); + // start_smi(&vc_mem); + // usleep(CHASE_MSEC * 1000); + // sleep(1); + // } + + // artnet_init(); + + // // loops + // if (IsTestMode) { + // while (1) { + // test_leds(); + // sleep(3); + // } + // } else { + // while (1) { + // artDmx_t *artDmx; + // if (artnet_read(&artDmx) == OpDmx) { + // uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo; + // unsigned int ledCountInFrame = dmxLength / 3; + // uint16_t maxBound = ledCountInFrame < chanLedCount ? ledCountInFrame : chanLedCount; + // unsigned int universe = artDmx->subUni & (LED_NCHANS - 1); + // for (size_t i = 0; i < maxBound; ++i) { + // uint8_t *rgb = artDmx->data + (i * 3); + // rgb_data[i][universe] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + // rgb_txdata(rgb_data[i], &tx_buffer[LED_TX_OSET(i)]); + // } + + // #if LED_NCHANS <= 8 + // swap_bytes(tx_buffer, TX_BUFF_SIZE(chanLedCount)); + // #endif + + // memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chanLedCount)); + // // enable_dma(DMA_CHAN); + // start_smi(&vc_mem); + // // usleep(10); + // // while (dma_active(DMA_CHAN)) + // // usleep(10); + // } + // } + // } + // terminate(0); +} + +void parseCommandLineArgs(int argc, char const *argv[]) { + int args = 0; + + while (argc > ++args) // Process command-line args + { + if (argv[args][0] == '-') { + switch (toupper(argv[args][1])) { + case 'N': // -N: number of LEDs per channel + if (args >= argc - 1) + fprintf(stderr, "Error: no numeric value\n"); + else + chanLedCount = atoi(argv[++args]); + break; + case 'T': // -T: test mode + IsTestMode = true; + break; + default: // Otherwise error + printf("Unrecognised option '%c'\n", argv[args][1]); + printf("Options:\n" + " -n num number of LEDs per channel\n" + " -t Test mode (flash LEDs)\n"); + exit(1); + } + } + } +} +// Pointer to uncached Tx data buffer +TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data + +void execute_test_mode() { + // RGB values for test mode (1 value for each of 16 channels) + uint32_t on_rgbs[] = {0xef0000, 0x00ef00, 0x0000ef, 0xefef00, 0xef00ef, 0x00efef, 0xefefef}; + uint32_t off_rgbs = 0x000000; + + static int i = 0, offset = 0, ledIndex = 0; + + if (ledIndex < chanLedCount) { + set_color(ledIndex <= offset % chanLedCount ? on_rgbs[i] : off_rgbs, + &tx_buffer[LED_TX_OSET(ledIndex)]); + ++ledIndex; + + } else { + ledIndex = 0; + +#if LED_NCHANS <= 8 + swap_bytes(tx_buffer, TX_BUFF_SIZE(chanLedCount)); +#endif + + memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chanLedCount)); + start_smi(&vc_mem); + usleep(CHASE_MSEC * 1000); + if (offset < chanLedCount) { + ++offset; + } else { + offset = 0; + if (i < 7) { + ++i; + } else { + i = 0; + } + } + } +} + +void execute_artnet_mode() { + artDmx_t *artDmx; + // RGB data + int rgb_data[CHAN_MAXLEDS][LED_NCHANS]; + + if (artnet_read(&artDmx) == OpDmx) { + uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo; + unsigned int ledCountInFrame = dmxLength / 3; + uint16_t maxBound = ledCountInFrame < chanLedCount ? ledCountInFrame : chanLedCount; + unsigned int universe = artDmx->subUni & (LED_NCHANS - 1); + for (size_t i = 0; i < maxBound; ++i) { + uint8_t *rgb = artDmx->data + (i * 3); + rgb_data[i][universe] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + rgb_txdata(rgb_data[i], &tx_buffer[LED_TX_OSET(i)]); + } + +#if LED_NCHANS <= 8 + swap_bytes(tx_buffer, TX_BUFF_SIZE(chanLedCount)); +#endif + + memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chanLedCount)); + enable_dma(DMA_CHAN); + start_smi(&vc_mem); + usleep(10); + while (dma_active(DMA_CHAN)) + usleep(10); + } +} + +void execute_autonomous_mode() {} + +void execute_autonomous2_mode() {} \ No newline at end of file diff --git a/RpiLedBars/src/rpi_pixleds.c b/RpiLedBars/src/rpi_pixleds.c index 58f7b99..68873ec 100644 --- a/RpiLedBars/src/rpi_pixleds.c +++ b/RpiLedBars/src/rpi_pixleds.c @@ -30,6 +30,8 @@ // v0.11 JPB 29/9/20 Added enable_dma before transfer (in case still active) // Corrected DMA nsamp value (was byte count) +#include "rpi_pixleds.h" + #include #include #include @@ -41,37 +43,8 @@ #include #include "rpi_artnet.h" -#include "rpi_dma_utils.h" #include "rpi_smi_defs.h" -#if PHYS_REG_BASE == PI_4_REG_BASE // Timings for RPi v4 (1.5 GHz) -#define SMI_TIMING 10, 15, 30, 15 // 400 ns cycle time -#else // Timings for RPi v0-3 (1 GHz) -#define SMI_TIMING 10, 10, 20, 10 // 400 ns cycle time -#endif - -#define TX_TEST 0 // If non-zero, use dummy Tx data -#define LED_D0_PIN 8 // GPIO pin for D0 output -#define LED_NCHANS 8 // Number of LED channels (8 or 16) -#define LED_NBITS 24 // Number of data bits per LED -#define LED_PREBITS 4 // Number of zero bits before LED data -#define LED_POSTBITS 4 // Number of zero bits after LED data -#define BIT_NPULSES 3 // Number of O/P pulses per LED bit -#define CHAN_MAXLEDS 50 // Maximum number of LEDs per channel -#define CHASE_MSEC 100 // Delay time for chaser light test -#define REQUEST_THRESH 2 // DMA request threshold -#define DMA_CHAN 10 // DMA channel to use - -// Length of data for 1 row (1 LED on each channel) -#define LED_DLEN (LED_NBITS * BIT_NPULSES) - -// Transmit data type, 8 or 16 bits -#if LED_NCHANS > 8 -#define TXDATA_T uint16_t -#else -#define TXDATA_T uint8_t -#endif - // Structures for mapped I/O devices, and non-volatile memory extern MEM_MAP gpio_regs, dma_regs; MEM_MAP vc_mem, clk_regs, smi_regs; @@ -88,132 +61,110 @@ volatile SMI_DCS_REG *smi_dcs; volatile SMI_DCA_REG *smi_dca; volatile SMI_DCD_REG *smi_dcd; -// Ofset into Tx data buffer, given LED number in chan -#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n))) - -// Size of data buffers & NV memory, given number of LEDs per chan -#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS) -#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T)) -#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS)) - // RGB values for test mode (1 value for each of 16 channels) -int on_rgbs[16] = {0xff0000, 0x00ff00, 0x0000ff, 0xffffff, 0xff4040, 0x40ff40, 0x4040ff, 0x404040, - 0xff0000, 0x00ff00, 0x0000ff, 0xffffff, 0xff4040, 0x40ff40, 0x4040ff, 0x404040}; -int off_rgbs[16]; +// uint32_t on_rgbs[] = {0xef0000, 0x00ef00, 0x0000ef, 0xefef00, 0xef00ef, 0x00efef, 0xefefef}; +// uint32_t off_rgbs = 0x000000; -#if TX_TEST -// Data for simple transmission test -TXDATA_T tx_test_data[] = {1, 2, 3, 4, 5, 6, 7, 0}; -#endif +// TXDATA_T *txdata; // Pointer to uncached Tx data buffer +// TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data +// int testmode, chan_ledcount = 1; // Command-line parameters +int rgb_data[CHAN_MAXLEDS][LED_NCHANS]; // RGB data +int chan_num; -TXDATA_T *txdata; // Pointer to uncached Tx data buffer -TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data -int testmode, chan_ledcount = 1; // Command-line parameters -int rgb_data[CHAN_MAXLEDS][LED_NCHANS]; // RGB data -int chan_num; // Current channel for data I/P +// Current channel for data I/P -void rgb_txdata(int *rgbs, TXDATA_T *txd); -int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan); -void swap_bytes(void *data, int len); -int hexdig(char c); -void map_devices(void); -void fail(char *s); -void terminate(int sig); -void init_smi(int width, int ns, int setup, int hold, int strobe); -void setup_smi_dma(MEM_MAP *mp, int nsamp); -void start_smi(MEM_MAP *mp); +// int main(int argc, char *argv[]) { +// // setup +// int args = 0, n; -int main(int argc, char *argv[]) { - // setup - int args = 0, n, oset = 0; - memset(off_rgbs, 0, sizeof(int) * 16); +// while (argc > ++args) // Process command-line args +// { +// if (argv[args][0] == '-') { +// switch (toupper(argv[args][1])) { +// case 'N': // -N: number of LEDs per channel +// if (args >= argc - 1) +// fprintf(stderr, "Error: no numeric value\n"); +// else +// chan_ledcount = atoi(argv[++args]); +// break; +// case 'T': // -T: test mode +// testmode = 1; +// break; +// default: // Otherwise error +// printf("Unrecognised option '%c'\n", argv[args][1]); +// printf("Options:\n" +// " -n num number of LEDs per channel\n" +// " -t Test mode (flash LEDs)\n"); +// return (1); +// } +// } else if (chan_num < LED_NCHANS && hexdig(argv[args][0]) >= 0 && +// (n = str_rgb(argv[args], rgb_data, chan_num)) > 0) { +// chan_ledcount = n > chan_ledcount ? n : chan_ledcount; +// chan_num++; +// } +// } +// signal(SIGINT, terminate); +// map_devices(); +// init_smi(LED_NCHANS > 8 ? SMI_16_BITS : SMI_8_BITS, SMI_TIMING); +// map_uncached_mem(&vc_mem, VC_MEM_SIZE); - while (argc > ++args) // Process command-line args - { - if (argv[args][0] == '-') { - switch (toupper(argv[args][1])) { - case 'N': // -N: number of LEDs per channel - if (args >= argc - 1) - fprintf(stderr, "Error: no numeric value\n"); - else - chan_ledcount = atoi(argv[++args]); - break; - case 'T': // -T: test mode - testmode = 1; - break; - default: // Otherwise error - printf("Unrecognised option '%c'\n", argv[args][1]); - printf("Options:\n" - " -n num number of LEDs per channel\n" - " -t Test mode (flash LEDs)\n"); - return (1); - } - } else if (chan_num < LED_NCHANS && hexdig(argv[args][0]) >= 0 && - (n = str_rgb(argv[args], rgb_data, chan_num)) > 0) { - chan_ledcount = n > chan_ledcount ? n : chan_ledcount; - chan_num++; - } - } - signal(SIGINT, terminate); - map_devices(); - init_smi(LED_NCHANS > 8 ? SMI_16_BITS : SMI_8_BITS, SMI_TIMING); - map_uncached_mem(&vc_mem, VC_MEM_SIZE); +// setup_smi_dma(&vc_mem, TX_BUFF_LEN(chan_ledcount)); +// printf("%s %u LED%s per channel, %u channels\n", testmode ? "Testing" : "Setting", +// chan_ledcount, +// chan_ledcount == 1 ? "" : "s", LED_NCHANS); - setup_smi_dma(&vc_mem, TX_BUFF_LEN(chan_ledcount)); - printf("%s %u LED%s per channel, %u channels\n", testmode ? "Testing" : "Setting", chan_ledcount, - chan_ledcount == 1 ? "" : "s", LED_NCHANS); +// for (size_t colorIndex = 0; colorIndex < 3; ++colorIndex) { +// for (size_t i = 0; i < chan_ledcount; ++i) { +// set_color(on_rgbs[colorIndex], &tx_buffer[LED_TX_OSET(i)]); +// } - memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); - start_smi(&vc_mem); - artnet_init(); +// #if LED_NCHANS <= 8 +// swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount)); +// #endif - // loops - if (testmode) { - while (1) { - if (chan_ledcount < 2) - rgb_txdata(oset & 1 ? off_rgbs : on_rgbs, tx_buffer); - else { - for (n = 0; n < chan_ledcount; n++) { - rgb_txdata(n == oset % chan_ledcount ? on_rgbs : off_rgbs, &tx_buffer[LED_TX_OSET(n)]); - } - } - oset++; -#if LED_NCHANS <= 8 - swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount)); -#endif - memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); - start_smi(&vc_mem); - usleep(CHASE_MSEC * 1000); - } - } else { - while (1) { - artDmx_t *artDmx; - if (artnet_read(&artDmx) == OpDmx) { - uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo; - unsigned int ledCountInFrame = dmxLength / 3; - uint16_t maxBound = ledCountInFrame < chan_ledcount ? ledCountInFrame : chan_ledcount; - unsigned int universe = artDmx->subUni & (LED_NCHANS - 1); - for (size_t i = 0; i < maxBound; ++i) { - uint8_t *rgb = artDmx->data + (i * 3); - rgb_data[i][universe] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; - rgb_txdata(rgb_data[i], &tx_buffer[LED_TX_OSET(i)]); - } +// memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); +// start_smi(&vc_mem); +// usleep(CHASE_MSEC * 1000); +// sleep(1); +// } -#if LED_NCHANS <= 8 - swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount)); -#endif +// artnet_init(); - memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); - // enable_dma(DMA_CHAN); - start_smi(&vc_mem); - // usleep(10); - // while (dma_active(DMA_CHAN)) - // usleep(10); - } - } - } - terminate(0); -} +// // loops +// if (testmode) { +// while (1) { +// test_leds(); +// sleep(3); +// } +// } else { +// while (1) { +// artDmx_t *artDmx; +// if (artnet_read(&artDmx) == OpDmx) { +// uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo; +// unsigned int ledCountInFrame = dmxLength / 3; +// uint16_t maxBound = ledCountInFrame < chan_ledcount ? ledCountInFrame : +// chan_ledcount; unsigned int universe = artDmx->subUni & (LED_NCHANS - 1); for (size_t +// i = 0; i < maxBound; ++i) { +// uint8_t *rgb = artDmx->data + (i * 3); +// rgb_data[i][universe] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; +// rgb_txdata(rgb_data[i], &tx_buffer[LED_TX_OSET(i)]); +// } + +// #if LED_NCHANS <= 8 +// swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount)); +// #endif + +// memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); +// // enable_dma(DMA_CHAN); +// start_smi(&vc_mem); +// // usleep(10); +// // while (dma_active(DMA_CHAN)) +// // usleep(10); +// } +// } +// } +// terminate(0); +// } // Convert RGB text string into integer data, for given channel // Return number of data points for this channel @@ -227,6 +178,28 @@ int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan) { } return (i); } + +// Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan +// Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low +void set_color(uint32_t rgb, TXDATA_T *txd) { + int msk; + + // For each bit of the 24-bit RGB values.. + for (size_t n = 0; n < LED_NBITS; n++) { + // Mask to convert RGB to GRB, M.S bit first + msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1; + // 1st byte or word is a high pulse on all lines + txd[0] = (TXDATA_T)0xffff; + // 2nd has high or low bits from data + // 3rd is a low pulse + txd[1] = txd[2] = 0; + if (rgb & msk) { + txd[1] = (TXDATA_T)0xffff; + } + txd += BIT_NPULSES; + } +} + // Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan // Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low void rgb_txdata(int *rgbs, TXDATA_T *txd) { @@ -235,7 +208,7 @@ void rgb_txdata(int *rgbs, TXDATA_T *txd) { // For each bit of the 24-bit RGB values.. for (n = 0; n < LED_NBITS; n++) { // Mask to convert RGB to GRB, M.S bit first - msk = n == 0 ? 0x8000 : n == 8 ? 0x800000 : n == 16 ? 0x80 : msk >> 1; + msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1; // 1st byte or word is a high pulse on all lines txd[0] = (TXDATA_T)0xffff; // 2nd has high or low bits from data @@ -284,16 +257,7 @@ void fail(char *s) { // Free memory segments and exit void terminate(int sig) { int i; - printf("Closing\n"); - for (size_t i = 0; i < chan_ledcount; ++i) { - rgb_txdata(off_rgbs, &tx_buffer[LED_TX_OSET(i)]); - } - swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount)); - memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); - start_smi(&vc_mem); - sleep(1); - if (gpio_regs.virt) { for (i = 0; i < LED_NCHANS; i++) gpio_mode(LED_D0_PIN + i, GPIO_IN); @@ -352,10 +316,10 @@ void init_smi(int width, int ns, int setup, int strobe, int hold) { } // Set up SMI transfers using DMA -void setup_smi_dma(MEM_MAP *mp, int nsamp) { +void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T **txdata) { DMA_CB *cbs = mp->virt; - txdata = (TXDATA_T *)(cbs + 1); + *txdata = (TXDATA_T *)(cbs + 1); smi_dmc->dmaen = 1; smi_cs->enable = 1; smi_cs->clear = 1; @@ -365,7 +329,7 @@ void setup_smi_dma(MEM_MAP *mp, int nsamp) { enable_dma(DMA_CHAN); cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP; cbs[0].tfr_len = nsamp; - cbs[0].srce_ad = MEM_BUS_ADDR(mp, txdata); + cbs[0].srce_ad = MEM_BUS_ADDR(mp, *txdata); cbs[0].dest_ad = REG_BUS_ADDR(smi_regs, SMI_D); } diff --git a/RpiLedBars/src/rpi_pixleds.h b/RpiLedBars/src/rpi_pixleds.h new file mode 100644 index 0000000..344bd96 --- /dev/null +++ b/RpiLedBars/src/rpi_pixleds.h @@ -0,0 +1,62 @@ +#if !defined(__RPI_PIXLEDS_H__) +#define __RPI_PIXLEDS_H__ + +#include + +#include "rpi_dma_utils.h" + +#if PHYS_REG_BASE == PI_4_REG_BASE // Timings for RPi v4 (1.5 GHz) +#define SMI_TIMING 10, 15, 30, 15 // 400 ns cycle time +#else // Timings for RPi v0-3 (1 GHz) +#define SMI_TIMING 10, 10, 20, 10 // 400 ns cycle time +#endif + +#define TX_TEST 0 // If non-zero, use dummy Tx data +#define LED_D0_PIN 8 // GPIO pin for D0 output +#define LED_NCHANS 8 // Number of LED channels (8 or 16) +#define LED_NBITS 24 // Number of data bits per LED +#define LED_PREBITS 4 // Number of zero bits before LED data +#define LED_POSTBITS 4 // Number of zero bits after LED data +#define BIT_NPULSES 3 // Number of O/P pulses per LED bit +#define CHAN_MAXLEDS 6 * 60 // Maximum number of LEDs per channel +#define CHASE_MSEC 20 // Delay time for chaser light test +#define REQUEST_THRESH 2 // DMA request threshold +#define DMA_CHAN 10 // DMA channel to use + +// Length of data for 1 row (1 LED on each channel) +#define LED_DLEN (LED_NBITS * BIT_NPULSES) + +// Transmit data type, 8 or 16 bits +#if LED_NCHANS > 8 +#define TXDATA_T uint16_t +#else +#define TXDATA_T uint8_t +#endif + +// Ofset into Tx data buffer, given LED number in chan +#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n))) + +// Size of data buffers & NV memory, given number of LEDs per chan +#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS) +#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T)) +#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS)) + +#if TX_TEST +// Data for simple transmission test +TXDATA_T tx_test_data[] = {1, 2, 3, 4, 5, 6, 7, 0}; +#endif + +void test_leds(); +int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan); +void set_color(uint32_t rgb, TXDATA_T *txd); +void rgb_txdata(int *rgbs, TXDATA_T *txd); +void swap_bytes(void *data, int len); +int hexdig(char c); +void map_devices(void); +void fail(char *s); +void terminate(int sig); +void init_smi(int width, int ns, int setup, int hold, int strobe); +void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T **txdata); +void start_smi(MEM_MAP *mp); + +#endif // __RPI_PIXLEDS_H__ \ No newline at end of file diff --git a/RpiLedBars/src/rpi_selector.c b/RpiLedBars/src/rpi_selector.c new file mode 100644 index 0000000..e526379 --- /dev/null +++ b/RpiLedBars/src/rpi_selector.c @@ -0,0 +1,32 @@ +#include +#include +#include + +unsigned const selectorPinNumber = 4; +int selectorPins[4] = {5, 6, 26, 27}; + +void setup_selector() { + wiringPiSetupGpio(); + + for (size_t i = 0; i < selectorPinNumber; ++i) { + pinMode(selectorPins[i], INPUT); + pullUpDnControl(selectorPins[i], PUD_DOWN); + } +} + +int get_selector_position() { + for (size_t i = 0; i < selectorPinNumber; ++i) { + if (digitalRead(selectorPins[i])) { + return i; + } + } + return -1; +} + +void print_selector() { + char modeStr[] = "0 | 0 | 0 | 0 \n"; + for (size_t i = 0; i < selectorPinNumber; ++i) { + modeStr[i * 4] = digitalRead(selectorPins[i]) ? '1' : '0'; + } + printf(modeStr); +} \ No newline at end of file diff --git a/RpiLedBars/src/rpi_selector.h b/RpiLedBars/src/rpi_selector.h new file mode 100644 index 0000000..e084f1a --- /dev/null +++ b/RpiLedBars/src/rpi_selector.h @@ -0,0 +1,10 @@ +#if !defined(__RPI_SELECTOR_H__) +#define __RPI_SELECTOR_H__ + +void setup_selector(); + +int get_selector_position(); + +void print_selector(); + +#endif /* __RPI_SELECTOR_H__ */ \ No newline at end of file diff --git a/RpiLedBars/src/smi/VitualMemory.cpp b/RpiLedBars/src/smi/VitualMemory.cpp new file mode 100644 index 0000000..be258ce --- /dev/null +++ b/RpiLedBars/src/smi/VitualMemory.cpp @@ -0,0 +1,37 @@ +#include "VitualMemory.hpp" + +#include + +#include "utils.h" +#include +// #include +#include +#include +#include + +VitualMemory::VitualMemory(void *addr, int size) { + int fd; + + roundSize = PAGE_ROUNDUP(size); + + if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0) { + throw std::runtime_error("Error: can't open /dev/mem, run using sudo"); + } + + mem = mmap(0, roundSize, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr); + close(fd); + +#if _DEBUG + printf("Map %p -> %p\n", (void *)addr, mem); +#endif // _DEBUG + + if (mem == MAP_FAILED) { + throw std::runtime_error("Error: can't map memory"); + } +} + +VitualMemory::~VitualMemory() { + if (mem) { + munmap(mem, roundSize); + } +} \ No newline at end of file diff --git a/RpiLedBars/src/smi/VitualMemory.hpp b/RpiLedBars/src/smi/VitualMemory.hpp new file mode 100644 index 0000000..db9b053 --- /dev/null +++ b/RpiLedBars/src/smi/VitualMemory.hpp @@ -0,0 +1,14 @@ +#if !defined(__VIRTUAL_MEMORY_H__) +#define __VIRTUAL_MEMORY_H__ + +class VitualMemory { +public: + VitualMemory(void *addr, int size); + ~VitualMemory(); + +private: + int roundSize; + void *mem; +}; + +#endif // __VIRTUAL_MEMORY_H__ diff --git a/RpiLedBars/src/smi/utils.h b/RpiLedBars/src/smi/utils.h new file mode 100644 index 0000000..97869bb --- /dev/null +++ b/RpiLedBars/src/smi/utils.h @@ -0,0 +1,4 @@ +// Size of memory page +#define PAGE_SIZE 0x1000 +// Round up to nearest page +#define PAGE_ROUNDUP(n) ((n) % PAGE_SIZE == 0 ? (n) : ((n) + PAGE_SIZE) & ~(PAGE_SIZE - 1)) \ No newline at end of file diff --git a/RpiLedBars/src/tmp/main.c b/RpiLedBars/src/tmp/main.c new file mode 100644 index 0000000..2ff2ac0 --- /dev/null +++ b/RpiLedBars/src/tmp/main.c @@ -0,0 +1,8 @@ + + + +int main(int argc, char const *argv[]) +{ + + return 0; +} diff --git a/RpiLedBars/src/tmp/rpi_pixleds.h b/RpiLedBars/src/tmp/rpi_pixleds.h new file mode 100644 index 0000000..2bffdc1 --- /dev/null +++ b/RpiLedBars/src/tmp/rpi_pixleds.h @@ -0,0 +1,15 @@ +#if !defined(__RPI_PIXLEDS_H__) +#define __RPI_PIXLEDS_H__ + +void rgb_txdata(int *rgbs, TXDATA_T *txd); +int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan); +void swap_bytes(void *data, int len); +int hexdig(char c); +void map_devices(void); +void fail(char *s); +void terminate(int sig); +void init_smi(int width, int ns, int setup, int hold, int strobe); +void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T *txdata); +void start_smi(MEM_MAP *mp); + +#endif // __RPI_PIXLEDS_H__ \ No newline at end of file