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