multithreaded app
This commit is contained in:
@@ -1,315 +0,0 @@
|
||||
/*
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#include <ArtnetnodeWifi.h>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,151 +0,0 @@
|
||||
#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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#if defined(ARDUINO_SAMD_MKR1000)
|
||||
#include <WiFi101.h>
|
||||
#else
|
||||
#include <WiFiNINA.h>
|
||||
#endif
|
||||
#else
|
||||
#error "Architecture not supported!"
|
||||
#endif
|
||||
#include <WiFiUdp.h>
|
||||
#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
|
@@ -1,22 +0,0 @@
|
||||
#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
|
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
// Class for saving details to and for constructing pollreply packets
|
||||
|
||||
#include <PollReply.h>
|
||||
|
||||
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;
|
||||
}
|
@@ -1,188 +0,0 @@
|
||||
#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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
// Class for saving details to and for constructing pollreply packets
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFiUdp.h>
|
||||
#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
|
@@ -1 +0,0 @@
|
||||
#include "artnet.h"
|
@@ -1,4 +0,0 @@
|
||||
#if !defined(__ARTNET_H__)
|
||||
#define __ARTNET_H__
|
||||
|
||||
#endif // __ARTNET_H__
|
@@ -1,128 +0,0 @@
|
||||
#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__
|
@@ -1,26 +0,0 @@
|
||||
#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__
|
49
RpiLedBars/src/drivers/common.c
Normal file
49
RpiLedBars/src/drivers/common.c
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Use mmap to obtain virtual address, given physical
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size) {
|
||||
mp->phys = phys;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE);
|
||||
mp->virt = map_segment(phys, mp->size);
|
||||
return (mp->virt);
|
||||
}
|
||||
|
||||
// Free mapped peripheral or memory
|
||||
void unmap_periph_mem(MEM_MAP *mp) {
|
||||
if (mp) {
|
||||
unmap_segment(mp->virt, mp->size);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- VIRTUAL MEMORY -----
|
||||
|
||||
// Get virtual memory segment for peripheral regs or physical mem
|
||||
void *map_segment(void *addr, int size) {
|
||||
int fd;
|
||||
void *mem;
|
||||
|
||||
size = PAGE_ROUNDUP(size);
|
||||
if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0)
|
||||
fprintf(stderr, "Error: can't open /dev/mem, run using sudo\n");
|
||||
mem = mmap(0, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr);
|
||||
close(fd);
|
||||
#if DEBUG
|
||||
printf("Map %p -> %p\n", (void *)addr, mem);
|
||||
#endif
|
||||
if (mem == MAP_FAILED)
|
||||
fprintf(stderr, "Error: can't map memory\n");
|
||||
return (mem);
|
||||
}
|
||||
|
||||
// Free mapped memory
|
||||
void unmap_segment(void *mem, int size) {
|
||||
if (mem)
|
||||
munmap(mem, PAGE_ROUNDUP(size));
|
||||
}
|
50
RpiLedBars/src/drivers/common.h
Normal file
50
RpiLedBars/src/drivers/common.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#if !defined(__COMMON_H__)
|
||||
#define __COMMON_H__
|
||||
|
||||
// Location of peripheral registers in physical memory
|
||||
#define PHYS_REG_BASE PI_23_REG_BASE
|
||||
#define PI_01_REG_BASE 0x20000000 // Pi Zero or 1
|
||||
#define PI_23_REG_BASE 0x3F000000 // Pi 2 or 3
|
||||
#define PI_4_REG_BASE 0xFE000000 // Pi 4
|
||||
|
||||
#define CLOCK_HZ 250000000 // Pi 2 - 4
|
||||
//#define CLOCK_HZ 400000000 // Pi Zero
|
||||
|
||||
// Location of peripheral registers in bus memory
|
||||
#define BUS_REG_BASE 0x7E000000
|
||||
|
||||
// Get virtual 8 and 32-bit pointers to register
|
||||
#define REG8(m, x) ((volatile uint8_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
|
||||
#define REG32(m, x) ((volatile uint32_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
|
||||
// Get bus address of register
|
||||
#define REG_BUS_ADDR(m, x) ((uint32_t)(m.bus) + (uint32_t)(x))
|
||||
// Convert uncached memory virtual address to bus address
|
||||
#define MEM_BUS_ADDR(mp, a) ((uint32_t)a - (uint32_t)mp->virt + (uint32_t)mp->bus)
|
||||
// Convert bus address to physical address (for mmap)
|
||||
#define BUS_PHYS_ADDR(a) ((void *)((uint32_t)(a) & ~0xC0000000))
|
||||
|
||||
// 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))
|
||||
|
||||
// Structure for mapped peripheral or memory
|
||||
typedef struct {
|
||||
int fd, // File descriptor
|
||||
h, // Memory handle
|
||||
size; // Memory size
|
||||
void *bus, // Bus address
|
||||
*virt, // Virtual address
|
||||
*phys; // Physical address
|
||||
} MEM_MAP;
|
||||
|
||||
// Use mmap to obtain virtual address, given physical
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size);
|
||||
|
||||
// Free mapped peripheral or memory
|
||||
void unmap_periph_mem(MEM_MAP *mp);
|
||||
|
||||
void *map_segment(void *addr, int size);
|
||||
void unmap_segment(void *addr, int size);
|
||||
|
||||
#endif // __COMMON_H__
|
90
RpiLedBars/src/drivers/dma/rpi_dma.c
Normal file
90
RpiLedBars/src/drivers/dma/rpi_dma.c
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "rpi_dma.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "rpi_videocore.h"
|
||||
|
||||
// DMA channels and data requests
|
||||
#define DMA_SMI_DREQ 4
|
||||
#define DMA_PWM_DREQ 5
|
||||
#define DMA_SPI_TX_DREQ 6
|
||||
#define DMA_SPI_RX_DREQ 7
|
||||
#define DMA_BASE (PHYS_REG_BASE + 0x007000)
|
||||
// DMA register addresses offset by 0x100 * chan_num
|
||||
#define DMA_CS 0x00
|
||||
#define DMA_CONBLK_AD 0x04
|
||||
#define DMA_TI 0x08
|
||||
#define DMA_SRCE_AD 0x0c
|
||||
#define DMA_DEST_AD 0x10
|
||||
#define DMA_TXFR_LEN 0x14
|
||||
#define DMA_STRIDE 0x18
|
||||
#define DMA_NEXTCONBK 0x1c
|
||||
#define DMA_DEBUG 0x20
|
||||
#define DMA_REG(ch, r) ((r) == DMA_ENABLE ? DMA_ENABLE : (ch)*0x100 + (r))
|
||||
#define DMA_ENABLE 0xff0
|
||||
// DMA register values
|
||||
#define DMA_WAIT_RESP (1 << 3)
|
||||
#define DMA_CB_DEST_INC (1 << 4)
|
||||
#define DMA_DEST_DREQ (1 << 6)
|
||||
#define DMA_CB_SRCE_INC (1 << 8)
|
||||
#define DMA_SRCE_DREQ (1 << 10)
|
||||
#define DMA_PRIORITY(n) ((n) << 16)
|
||||
|
||||
// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
|
||||
MEM_MAP dma_regs;
|
||||
|
||||
char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD",
|
||||
"TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""};
|
||||
|
||||
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad) {
|
||||
map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE);
|
||||
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
*txdata = (uint8_t *)(cbs + offset);
|
||||
enable_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].dest_ad = dest_ad;
|
||||
}
|
||||
|
||||
void dma_close() { unmap_periph_mem(&dma_regs); }
|
||||
|
||||
// Enable and reset DMA
|
||||
void enable_dma(int chan) {
|
||||
*REG32(dma_regs, DMA_ENABLE) |= (1 << chan);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Start DMA, given first control block
|
||||
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) {
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 | csval; // Start DMA
|
||||
}
|
||||
|
||||
// Return remaining transfer length
|
||||
uint32_t dma_transfer_len(int chan) { return (*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); }
|
||||
|
||||
// Check if DMA is active
|
||||
uint32_t dma_active(int chan) { return ((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); }
|
||||
|
||||
// Halt current DMA operation by resetting controller
|
||||
void stop_dma(int chan) {
|
||||
if (dma_regs.virt)
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Display DMA registers
|
||||
void disp_dma(int chan) {
|
||||
volatile uint32_t *p = REG32(dma_regs, DMA_REG(chan, DMA_CS));
|
||||
int i = 0;
|
||||
|
||||
while (dma_regstrs[i][0]) {
|
||||
printf("%-7s %08X ", dma_regstrs[i++], *p++);
|
||||
if (i % 5 == 0 || dma_regstrs[i][0] == 0)
|
||||
printf("\n");
|
||||
}
|
||||
}
|
33
RpiLedBars/src/drivers/dma/rpi_dma.h
Normal file
33
RpiLedBars/src/drivers/dma/rpi_dma.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#if !defined(__RPI_DMA_H__)
|
||||
#define __RPI_DMA_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#define DMA_CHAN_A 10
|
||||
#define DMA_CHAN_B 11
|
||||
|
||||
// DMA control block (must be 32-byte aligned)
|
||||
typedef struct {
|
||||
uint32_t ti, // Transfer info
|
||||
srce_ad, // Source address
|
||||
dest_ad, // Destination address
|
||||
tfr_len, // Transfer length
|
||||
stride, // Transfer stride
|
||||
next_cb, // Next control block
|
||||
debug, // Debug register, zero in control block
|
||||
unused;
|
||||
} DMA_CB __attribute__((aligned(32)));
|
||||
|
||||
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad);
|
||||
void dma_close();
|
||||
|
||||
void enable_dma(int chan);
|
||||
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval);
|
||||
uint32_t dma_transfer_len(int chan);
|
||||
uint32_t dma_active(int chan);
|
||||
void stop_dma(int chan);
|
||||
void disp_dma(int chan);
|
||||
|
||||
#endif // __RPI_DMA_H__
|
125
RpiLedBars/src/drivers/dma/rpi_videocore.c
Normal file
125
RpiLedBars/src/drivers/dma/rpi_videocore.c
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "rpi_videocore.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Mailbox command/response structure
|
||||
typedef struct {
|
||||
uint32_t len, // Overall length (bytes)
|
||||
req, // Zero for request, 1<<31 for response
|
||||
tag, // Command number
|
||||
blen, // Buffer length (bytes)
|
||||
dlen; // Data length (bytes)
|
||||
uint32_t uints[32 - 5]; // Data (108 bytes maximum)
|
||||
} VC_MSG __attribute__((aligned(16)));
|
||||
|
||||
void disp_vc_msg(VC_MSG *msgp);
|
||||
|
||||
int open_mbox(void);
|
||||
void close_mbox(int fd);
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp);
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size);
|
||||
|
||||
void videocore_setup(MEM_MAP *mp, int size) { map_uncached_mem(mp, size); }
|
||||
void videocore_close(MEM_MAP *mp) {
|
||||
unmap_periph_mem(mp);
|
||||
if (mp->fd) {
|
||||
unlock_vc_mem(mp->fd, mp->h);
|
||||
free_vc_mem(mp->fd, mp->h);
|
||||
close_mbox(mp->fd);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate uncached memory, get bus & phys addresses
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size) {
|
||||
void *ret;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->fd = open_mbox();
|
||||
ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 &&
|
||||
(mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 &&
|
||||
(mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0
|
||||
? mp->virt
|
||||
: 0;
|
||||
printf("VC mem handle %u, phys %p, virt %p\n", mp->h, mp->bus, mp->virt);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
// Allocate memory on PAGE_SIZE boundary, return handle
|
||||
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags) {
|
||||
VC_MSG msg = {
|
||||
.tag = 0x3000c, .blen = 12, .dlen = 12, .uints = {PAGE_ROUNDUP(size), PAGE_SIZE, flags}};
|
||||
return (msg_mbox(fd, &msg));
|
||||
}
|
||||
// Lock allocated memory, return bus address
|
||||
void *lock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000d, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? (void *)msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Unlock allocated memory
|
||||
uint32_t unlock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000e, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Free memory
|
||||
uint32_t free_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000f, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
uint32_t set_vc_clock(int fd, int id, uint32_t freq) {
|
||||
VC_MSG msg1 = {.tag = 0x38001, .blen = 8, .dlen = 8, .uints = {id, 1}};
|
||||
VC_MSG msg2 = {.tag = 0x38002, .blen = 12, .dlen = 12, .uints = {id, freq, 0}};
|
||||
msg_mbox(fd, &msg1);
|
||||
disp_vc_msg(&msg1);
|
||||
msg_mbox(fd, &msg2);
|
||||
disp_vc_msg(&msg2);
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Display mailbox message
|
||||
void disp_vc_msg(VC_MSG *msgp) {
|
||||
int i;
|
||||
|
||||
printf("VC msg len=%X, req=%X, tag=%X, blen=%x, dlen=%x, data ", msgp->len, msgp->req, msgp->tag,
|
||||
msgp->blen, msgp->dlen);
|
||||
for (i = 0; i < msgp->blen / 4; i++)
|
||||
printf("%08X ", msgp->uints[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Open mailbox interface, return file descriptor
|
||||
int open_mbox(void) {
|
||||
int fd;
|
||||
|
||||
if ((fd = open("/dev/vcio", 0)) < 0)
|
||||
fprintf(stderr, "Error: can't open VC mailbox\n");
|
||||
return (fd);
|
||||
}
|
||||
// Close mailbox interface
|
||||
void close_mbox(int fd) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Send message to mailbox, return first response int, 0 if error
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp) {
|
||||
uint32_t ret = 0, i;
|
||||
|
||||
for (i = msgp->dlen / 4; i <= msgp->blen / 4; i += 4)
|
||||
msgp->uints[i++] = 0;
|
||||
msgp->len = (msgp->blen + 6) * 4;
|
||||
msgp->req = 0;
|
||||
if (ioctl(fd, _IOWR(100, 0, void *), msgp) < 0)
|
||||
printf("VC IOCTL failed\n");
|
||||
else if ((msgp->req & 0x80000000) == 0)
|
||||
printf("VC IOCTL error\n");
|
||||
else if (msgp->req == 0x80000001)
|
||||
printf("VC IOCTL partial error\n");
|
||||
else
|
||||
ret = msgp->uints[0];
|
||||
#if DEBUG
|
||||
disp_vc_msg(msgp);
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
33
RpiLedBars/src/drivers/dma/rpi_videocore.h
Normal file
33
RpiLedBars/src/drivers/dma/rpi_videocore.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#if !defined(__RPI_VIDEOCORE_H__)
|
||||
#define __RPI_VIDEOCORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// Videocore mailbox memory allocation flags, see:
|
||||
// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
typedef enum {
|
||||
MEM_FLAG_DISCARDABLE = 1 << 0, // can be resized to 0 at any time. Use for cached data
|
||||
MEM_FLAG_NORMAL = 0 << 2, // normal allocating alias. Don't use from ARM
|
||||
MEM_FLAG_DIRECT = 1 << 2, // 0xC alias uncached
|
||||
MEM_FLAG_COHERENT = 2 << 2, // 0x8 alias. Non-allocating in L2 but coherent
|
||||
MEM_FLAG_ZERO = 1 << 4, // initialise buffer to all zeros
|
||||
MEM_FLAG_NO_INIT = 1 << 5, // don't initialise (default is initialise to all ones)
|
||||
MEM_FLAG_HINT_PERMALOCK = 1 << 6, // Likely to be locked for long periods of time
|
||||
MEM_FLAG_L1_NONALLOCATING = (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) // Allocating in L2
|
||||
} VC_ALLOC_FLAGS;
|
||||
|
||||
// VC flags for unchached DMA memory
|
||||
#define DMA_MEM_FLAGS (MEM_FLAG_DIRECT | MEM_FLAG_ZERO)
|
||||
|
||||
void videocore_setup(MEM_MAP *mp, int size);
|
||||
void videocore_close(MEM_MAP *mp);
|
||||
|
||||
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags);
|
||||
void *lock_vc_mem(int fd, int h);
|
||||
uint32_t unlock_vc_mem(int fd, int h);
|
||||
uint32_t free_vc_mem(int fd, int h);
|
||||
uint32_t set_vc_clock(int fd, int id, uint32_t freq);
|
||||
|
||||
#endif // __RPI_VIDEOCORE_H__
|
75
RpiLedBars/src/drivers/gpio/rpi_gpio.c
Normal file
75
RpiLedBars/src/drivers/gpio/rpi_gpio.c
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "rpi_gpio.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// GPIO register definitions
|
||||
#define GPIO_BASE (PHYS_REG_BASE + 0x200000)
|
||||
#define GPIO_MODE0 0x00
|
||||
#define GPIO_SET0 0x1c
|
||||
#define GPIO_CLR0 0x28
|
||||
#define GPIO_LEV0 0x34
|
||||
#define GPIO_GPPUD 0x94
|
||||
#define GPIO_GPPUDCLK0 0x98
|
||||
|
||||
#define GPIO_MODE_STRS "IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"
|
||||
|
||||
// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
|
||||
MEM_MAP gpio_regs;
|
||||
|
||||
char *gpio_mode_strs[] = {GPIO_MODE_STRS};
|
||||
|
||||
// definitions
|
||||
|
||||
void gpio_setup() { map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE); }
|
||||
|
||||
void gpio_close() { unmap_periph_mem(&gpio_regs); }
|
||||
|
||||
// Set input or output with pullups
|
||||
void gpio_set(int pin, int mode, int pull) {
|
||||
gpio_mode(pin, mode);
|
||||
gpio_pull(pin, pull);
|
||||
}
|
||||
// Set I/P pullup or pulldown
|
||||
void gpio_pull(int pin, int pull) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_GPPUDCLK0) + pin / 32;
|
||||
|
||||
*REG32(gpio_regs, GPIO_GPPUD) = pull;
|
||||
usleep(2);
|
||||
*reg = pin << (pin % 32);
|
||||
usleep(2);
|
||||
*REG32(gpio_regs, GPIO_GPPUD) = 0;
|
||||
*reg = 0;
|
||||
}
|
||||
|
||||
// Set input or output
|
||||
void gpio_mode(int pin, int mode) {
|
||||
if (gpio_regs.virt) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_MODE0) + pin / 10, shift = (pin % 10) * 3;
|
||||
*reg = (*reg & ~(7 << shift)) | (mode << shift);
|
||||
}
|
||||
}
|
||||
|
||||
// Set an O/P pin
|
||||
void gpio_out(int pin, int val) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, val ? GPIO_SET0 : GPIO_CLR0) + pin / 32;
|
||||
*reg = 1 << (pin % 32);
|
||||
}
|
||||
|
||||
// Get an I/P pin value
|
||||
uint8_t gpio_in(int pin) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_LEV0) + pin / 32;
|
||||
return (((*reg) >> (pin % 32)) & 1);
|
||||
}
|
||||
|
||||
// Display the values in a GPIO mode register
|
||||
void disp_mode_vals(uint32_t mode) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
printf("%u:%-4s ", i, gpio_mode_strs[(mode >> (i * 3)) & 7]);
|
||||
printf("\n");
|
||||
}
|
26
RpiLedBars/src/drivers/gpio/rpi_gpio.h
Normal file
26
RpiLedBars/src/drivers/gpio/rpi_gpio.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#if !defined(__RPI_GPIO_H__)
|
||||
#define __RPI_GPIO_H__
|
||||
|
||||
// GPIO I/O definitions
|
||||
#define GPIO_IN 0
|
||||
#define GPIO_OUT 1
|
||||
#define GPIO_ALT0 4
|
||||
#define GPIO_ALT1 5
|
||||
#define GPIO_ALT2 6
|
||||
#define GPIO_ALT3 7
|
||||
#define GPIO_ALT4 3
|
||||
#define GPIO_ALT5 2
|
||||
|
||||
#define GPIO_NOPULL 0
|
||||
#define GPIO_PULLDN 1
|
||||
#define GPIO_PULLUP 2
|
||||
|
||||
void gpio_setup();
|
||||
|
||||
void gpio_close();
|
||||
|
||||
void gpio_pull(int pin, int pull);
|
||||
|
||||
void gpio_mode(int pin, int mode);
|
||||
|
||||
#endif // __RPI_GPIO_H__
|
132
RpiLedBars/src/drivers/leddriver/rpi_leddriver.c
Normal file
132
RpiLedBars/src/drivers/leddriver/rpi_leddriver.c
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "rpi_leddriver.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#include "../dma/rpi_dma.h"
|
||||
#include "../dma/rpi_videocore.h"
|
||||
#include "../gpio/rpi_gpio.h"
|
||||
#include "../smi/rpi_smi.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_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
|
||||
|
||||
// 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))
|
||||
|
||||
/* Global */
|
||||
MEM_MAP vc_mem;
|
||||
TXDATA_T *txdata;
|
||||
TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)];
|
||||
|
||||
void swap_bytes();
|
||||
|
||||
void leddriver_setup() {
|
||||
gpio_setup();
|
||||
videocore_setup(&vc_mem, VC_MEM_SIZE);
|
||||
smi_setup(LED_NCHANS, SMI_TIMING, &vc_mem, TX_BUFF_LEN(CHAN_MAXLEDS), &txdata);
|
||||
}
|
||||
|
||||
void leddriver_close() {
|
||||
printf("Closing\n");
|
||||
videocore_close(&vc_mem);
|
||||
smi_close(LED_NCHANS);
|
||||
gpio_close();
|
||||
}
|
||||
|
||||
void set_color(uint32_t rgb, int index) {
|
||||
int msk;
|
||||
TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]);
|
||||
|
||||
// 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, int index) {
|
||||
int i, n, msk;
|
||||
TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]);
|
||||
|
||||
// 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 ? 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;
|
||||
for (i = 0; i < LED_NCHANS; i++) {
|
||||
if (rgbs[i] & msk)
|
||||
txd[1] |= (1 << i);
|
||||
}
|
||||
txd += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
void leddriver_refresh() {
|
||||
#if LED_NCHANS <= 8
|
||||
swap_bytes();
|
||||
#endif
|
||||
|
||||
while (dma_active(DMA_CHAN_A)) {
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
memcpy(txdata, tx_buffer, TX_BUFF_SIZE(CHAN_MAXLEDS));
|
||||
enable_dma(DMA_CHAN_A);
|
||||
start_smi(&vc_mem);
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
// Swap adjacent bytes in transmit data
|
||||
void swap_bytes() {
|
||||
uint16_t *wp = (uint16_t *)tx_buffer;
|
||||
int len = TX_BUFF_SIZE(CHAN_MAXLEDS);
|
||||
|
||||
len = (len + 1) / 2;
|
||||
while (len-- > 0) {
|
||||
*wp = __builtin_bswap16(*wp);
|
||||
wp++;
|
||||
}
|
||||
}
|
19
RpiLedBars/src/drivers/leddriver/rpi_leddriver.h
Normal file
19
RpiLedBars/src/drivers/leddriver/rpi_leddriver.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#if !defined(__RPI_LEDDRIVER_H__)
|
||||
#define __RPI_LEDDRIVER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define CHAN_MAXLEDS 6 * 60 // Maximum number of LEDs per channel
|
||||
#define LED_NCHANS 8 // Number of LED channels (8 or 16)
|
||||
|
||||
void leddriver_setup();
|
||||
|
||||
void leddriver_close();
|
||||
|
||||
void set_color(uint32_t rgb, int index);
|
||||
|
||||
void rgb_txdata(int *rgbs, int index);
|
||||
|
||||
void leddriver_refresh();
|
||||
|
||||
#endif // __RPI_LEDDRIVER_H__
|
@@ -1,6 +1,30 @@
|
||||
// Secondary Memory Interface definitions for Raspberry Pi
|
||||
//
|
||||
// v0.01 JPB 12/7/20 Adapted from rpi_smi_test v0.19
|
||||
#include "rpi_smi.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../dma/rpi_dma.h"
|
||||
#include "../gpio/rpi_gpio.h"
|
||||
|
||||
// GPIO first pin
|
||||
#define SMI_SD0_PIN 8
|
||||
|
||||
// Data widths
|
||||
#define SMI_8_BITS 0
|
||||
#define SMI_16_BITS 1
|
||||
#define SMI_18_BITS 2
|
||||
#define SMI_9_BITS 3
|
||||
|
||||
// Clock registers and values
|
||||
#define CLK_BASE (PHYS_REG_BASE + 0x101000)
|
||||
// #define CLK_PWM_CTL 0xa0
|
||||
// #define CLK_PWM_DIV 0xa4
|
||||
#define CLK_SMI_CTL 0xb0
|
||||
#define CLK_SMI_DIV 0xb4
|
||||
#define CLK_PASSWD 0x5a000000
|
||||
#define PWM_CLOCK_ID 0xa
|
||||
|
||||
// DMA request threshold
|
||||
#define REQUEST_THRESH 2
|
||||
|
||||
// Register definitions
|
||||
#define SMI_BASE (PHYS_REG_BASE + 0x600000)
|
||||
@@ -23,15 +47,6 @@
|
||||
#define SMI_FD 0x40 // FIFO debug
|
||||
#define SMI_REGLEN (SMI_FD * 4)
|
||||
|
||||
// Data widths
|
||||
#define SMI_8_BITS 0
|
||||
#define SMI_16_BITS 1
|
||||
#define SMI_18_BITS 2
|
||||
#define SMI_9_BITS 3
|
||||
|
||||
// DMA request
|
||||
#define DMA_SMI_DREQ 4
|
||||
|
||||
// Union of 32-bit value with register bitfields
|
||||
#define REG_DEF(name, fields) \
|
||||
typedef union { \
|
||||
@@ -109,7 +124,100 @@ REG_DEF(SMI_DCD_REG, SMI_DCD_FIELDS);
|
||||
6, _x1 : 2, flvl : 6
|
||||
REG_DEF(SMI_FLVL_REG, SMI_FLVL_FIELDS);
|
||||
|
||||
#define CLK_SMI_CTL 0xb0
|
||||
#define CLK_SMI_DIV 0xb4
|
||||
// Pointers to SMI registers
|
||||
volatile SMI_CS_REG *smi_cs;
|
||||
volatile SMI_L_REG *smi_l;
|
||||
volatile SMI_A_REG *smi_a;
|
||||
volatile SMI_D_REG *smi_d;
|
||||
volatile SMI_DMC_REG *smi_dmc;
|
||||
volatile SMI_DSR_REG *smi_dsr;
|
||||
volatile SMI_DSW_REG *smi_dsw;
|
||||
volatile SMI_DCS_REG *smi_dcs;
|
||||
volatile SMI_DCA_REG *smi_dca;
|
||||
volatile SMI_DCD_REG *smi_dcd;
|
||||
|
||||
// EOF
|
||||
MEM_MAP smi_regs, clk_regs;
|
||||
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len);
|
||||
|
||||
void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp,
|
||||
uint8_t **txdata) {
|
||||
map_periph(&smi_regs, (void *)SMI_BASE, PAGE_SIZE);
|
||||
map_periph(&clk_regs, (void *)CLK_BASE, PAGE_SIZE);
|
||||
|
||||
int width = channels > 8 ? SMI_16_BITS : SMI_8_BITS;
|
||||
int i, divi = ns / 2;
|
||||
|
||||
smi_cs = (SMI_CS_REG *)REG32(smi_regs, SMI_CS);
|
||||
smi_l = (SMI_L_REG *)REG32(smi_regs, SMI_L);
|
||||
smi_a = (SMI_A_REG *)REG32(smi_regs, SMI_A);
|
||||
smi_d = (SMI_D_REG *)REG32(smi_regs, SMI_D);
|
||||
smi_dmc = (SMI_DMC_REG *)REG32(smi_regs, SMI_DMC);
|
||||
smi_dsr = (SMI_DSR_REG *)REG32(smi_regs, SMI_DSR0);
|
||||
smi_dsw = (SMI_DSW_REG *)REG32(smi_regs, SMI_DSW0);
|
||||
smi_dcs = (SMI_DCS_REG *)REG32(smi_regs, SMI_DCS);
|
||||
smi_dca = (SMI_DCA_REG *)REG32(smi_regs, SMI_DCA);
|
||||
smi_dcd = (SMI_DCD_REG *)REG32(smi_regs, SMI_DCD);
|
||||
smi_cs->value = smi_l->value = smi_a->value = 0;
|
||||
smi_dsr->value = smi_dsw->value = smi_dcs->value = smi_dca->value = 0;
|
||||
if (*REG32(clk_regs, CLK_SMI_DIV) != divi << 12) {
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | (1 << 5);
|
||||
usleep(10);
|
||||
while (*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7))
|
||||
;
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_DIV) = CLK_PASSWD | (divi << 12);
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | 6 | (1 << 4);
|
||||
usleep(10);
|
||||
while ((*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) == 0)
|
||||
;
|
||||
usleep(100);
|
||||
}
|
||||
if (smi_cs->seterr)
|
||||
smi_cs->seterr = 1;
|
||||
smi_dsr->rsetup = smi_dsw->wsetup = setup;
|
||||
smi_dsr->rstrobe = smi_dsw->wstrobe = strobe;
|
||||
smi_dsr->rhold = smi_dsw->whold = hold;
|
||||
smi_dmc->panicr = smi_dmc->panicw = 8;
|
||||
smi_dmc->reqr = smi_dmc->reqw = REQUEST_THRESH;
|
||||
smi_dsr->rwidth = smi_dsw->wwidth = width;
|
||||
for (i = 0; i < channels; i++)
|
||||
gpio_mode(SMI_SD0_PIN + i, GPIO_ALT1);
|
||||
|
||||
setup_smi_dma(mp, nsamp, txdata, width + 1);
|
||||
}
|
||||
|
||||
void smi_close(int channels) {
|
||||
for (size_t i = 0; i < channels; ++i)
|
||||
gpio_mode(SMI_SD0_PIN + i, GPIO_IN);
|
||||
if (smi_regs.virt) {
|
||||
*REG32(smi_regs, SMI_CS) = 0;
|
||||
}
|
||||
stop_dma(DMA_CHAN_A);
|
||||
unmap_periph_mem(&clk_regs);
|
||||
unmap_periph_mem(&smi_regs);
|
||||
dma_close();
|
||||
}
|
||||
|
||||
// Start SMI DMA transfers
|
||||
void start_smi(MEM_MAP *mp) {
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
start_dma(mp, DMA_CHAN_A, &cbs[0], 0);
|
||||
smi_cs->start = 1;
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
// Set up SMI transfers using DMA
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len) {
|
||||
smi_dmc->dmaen = 1;
|
||||
smi_cs->enable = 1;
|
||||
smi_cs->clear = 1;
|
||||
smi_cs->pxldat = 1;
|
||||
smi_l->len = nsamp * len;
|
||||
smi_cs->write = 1;
|
||||
|
||||
dma_setup(mp, DMA_CHAN_A, nsamp, txdata, len, REG_BUS_ADDR(smi_regs, SMI_D));
|
||||
}
|
14
RpiLedBars/src/drivers/smi/rpi_smi.h
Normal file
14
RpiLedBars/src/drivers/smi/rpi_smi.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#if !defined(__RPI_SMI_H__)
|
||||
#define __RPI_SMI_H__
|
||||
|
||||
#include "../common.h"
|
||||
#include <stdint.h>
|
||||
|
||||
void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp,
|
||||
uint8_t **txdata);
|
||||
|
||||
void smi_close(int channels);
|
||||
|
||||
void start_smi(MEM_MAP *mp);
|
||||
|
||||
#endif // __RPI_SMI_H__
|
@@ -1,147 +1,133 @@
|
||||
#include <ctype.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "rpi_artnet.h"
|
||||
#include "rpi_pixleds.h"
|
||||
#include "rpi_smi_defs.h"
|
||||
#include "drivers/leddriver/rpi_leddriver.h"
|
||||
#include "drivers/selector/rpi_selector.h"
|
||||
|
||||
#include "rpi_selector.h"
|
||||
#include "rpi_midi_controller.h"
|
||||
#include "tasks/artnet/rpi_artnet.h"
|
||||
#include "tasks/cava/rpi_cava.h"
|
||||
|
||||
/* Command-line parameters */
|
||||
bool IsTestMode = false;
|
||||
int chanLedCount = 0;
|
||||
|
||||
/* Global */
|
||||
MEM_MAP vc_mem;
|
||||
TXDATA_T *txdata;
|
||||
pthread_t *bgTasks[4] = {NULL};
|
||||
|
||||
void parseCommandLineArgs(int argc, char const *argv[]);
|
||||
|
||||
void terminate(int sig);
|
||||
|
||||
void execute_test_mode();
|
||||
|
||||
void execute_artnet_mode();
|
||||
|
||||
void execute_autonomous_mode();
|
||||
|
||||
void execute_autonomous2_mode();
|
||||
void execute_manual_mode();
|
||||
|
||||
void adjust_loop(struct timespec const *loopStart);
|
||||
|
||||
int main(int argc, char const *argv[]) {
|
||||
int previousMode = 0;
|
||||
int previousMode = -1;
|
||||
|
||||
// setup
|
||||
parseCommandLineArgs(argc, argv);
|
||||
|
||||
signal(SIGINT, terminate);
|
||||
|
||||
struct sched_param sp;
|
||||
sp.sched_priority = 32;
|
||||
if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) {
|
||||
fprintf(stderr, "WARNING: Failed to set stepper thread to real-time priority\n");
|
||||
}
|
||||
|
||||
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 led
|
||||
// map_devices();
|
||||
// init_smi(LED_NCHANS, SMI_TIMING);
|
||||
// map_uncached_mem(&vc_mem, VC_MEM_SIZE);
|
||||
|
||||
setup_smi_dma(&vc_mem, TX_BUFF_LEN(chanLedCount), &txdata);
|
||||
// setup_smi_dma(&vc_mem, TX_BUFF_LEN(chanLedCount), &txdata);
|
||||
|
||||
leddriver_setup();
|
||||
|
||||
setup_midi_controller();
|
||||
artnet_init();
|
||||
setup_cava();
|
||||
|
||||
// loop
|
||||
while (1) {
|
||||
struct timespec loopStart;
|
||||
clock_gettime(CLOCK_MONOTONIC, &loopStart);
|
||||
int mode = get_selector_position();
|
||||
execute_midi_controller();
|
||||
|
||||
if (mode != previousMode) {
|
||||
if (mode != -1) {
|
||||
if (mode != previousMode) {
|
||||
#if defined(_DEBUG)
|
||||
print_selector();
|
||||
printf("swtching to mode : %d\n", mode);
|
||||
#endif
|
||||
}
|
||||
/* stop previous bg task */
|
||||
switch (previousMode) {
|
||||
case 1:
|
||||
stop_artnet_bg_worker();
|
||||
break;
|
||||
case 2:
|
||||
stop_cava_bg_worker();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
/* start new bg task */
|
||||
switch (mode) {
|
||||
case 1:
|
||||
start_artnet_bg_worker();
|
||||
break;
|
||||
case 2:
|
||||
start_cava_bg_worker();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
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:
|
||||
// manual mode
|
||||
execute_manual_mode();
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("error in selector \n");
|
||||
break;
|
||||
default:
|
||||
if (mode != previousMode) {
|
||||
fprintf(stderr, "Error in selector\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
previousMode = mode;
|
||||
}
|
||||
previousMode = mode;
|
||||
adjust_loop(&loopStart);
|
||||
}
|
||||
|
||||
// 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[]) {
|
||||
@@ -170,73 +156,105 @@ void parseCommandLineArgs(int argc, char const *argv[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void terminate(int sig) {
|
||||
leddriver_close();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Pointer to uncached Tx data buffer
|
||||
TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data
|
||||
// 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;
|
||||
static int i = 0, offset = 0;
|
||||
|
||||
if (ledIndex < chanLedCount) {
|
||||
set_color(ledIndex <= offset % chanLedCount ? on_rgbs[i] : off_rgbs,
|
||||
&tx_buffer[LED_TX_OSET(ledIndex)]);
|
||||
++ledIndex;
|
||||
for (size_t ledIndex = 0; ledIndex < chanLedCount; ++ledIndex) {
|
||||
set_color(ledIndex <= offset % chanLedCount ? on_rgbs[i] : off_rgbs, ledIndex);
|
||||
}
|
||||
|
||||
leddriver_refresh();
|
||||
|
||||
if (offset < chanLedCount) {
|
||||
++offset;
|
||||
} 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;
|
||||
offset = 0;
|
||||
if (i < 7) {
|
||||
++i;
|
||||
} else {
|
||||
offset = 0;
|
||||
if (i < 7) {
|
||||
++i;
|
||||
} else {
|
||||
i = 0;
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RGB data
|
||||
int rgb_data[CHAN_MAXLEDS][LED_NCHANS];
|
||||
|
||||
void execute_artnet_mode() {
|
||||
uint8_t *dmxData;
|
||||
|
||||
for (size_t ledBar = 0; ledBar < LED_NCHANS; ledBar++) {
|
||||
if (artnet_get_dmx_data(ledBar, &dmxData) == 0) {
|
||||
for (size_t i = 0; i < chanLedCount; ++i) {
|
||||
uint8_t *rgb = dmxData + (i * 3);
|
||||
rgb_data[i][ledBar] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
||||
rgb_txdata(rgb_data[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leddriver_refresh();
|
||||
}
|
||||
|
||||
void execute_artnet_mode() {
|
||||
artDmx_t *artDmx;
|
||||
// RGB data
|
||||
int rgb_data[CHAN_MAXLEDS][LED_NCHANS];
|
||||
void execute_autonomous_mode() {
|
||||
int ret;
|
||||
uint16_t *buffer;
|
||||
|
||||
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 ((ret = get_cava_buffer(&buffer)) == 0) {
|
||||
for (size_t ledBarIndex = 0; ledBarIndex < 4; ++ledBarIndex) {
|
||||
uint16_t barMax = 0;
|
||||
for (size_t bar = 0; bar < 20 / 4; ++bar) {
|
||||
unsigned barIndex = ledBarIndex * 20 / 4 + bar;
|
||||
if (barMax < buffer[barIndex]) {
|
||||
barMax = buffer[barIndex];
|
||||
}
|
||||
}
|
||||
unsigned ledToLight = barMax * chanLedCount / UINT16_MAX;
|
||||
|
||||
for (size_t i = 0; i < ledToLight; ++i) {
|
||||
rgb_data[i][ledBarIndex] = 0xff0000;
|
||||
rgb_txdata(rgb_data[i], i);
|
||||
}
|
||||
for (size_t i = ledToLight; i < chanLedCount; ++i) {
|
||||
rgb_data[i][ledBarIndex] = 0x000000;
|
||||
rgb_txdata(rgb_data[i], 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);
|
||||
leddriver_refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_autonomous_mode() {}
|
||||
void execute_manual_mode() {}
|
||||
|
||||
void execute_autonomous2_mode() {}
|
||||
void adjust_loop(struct timespec const *loopStart) {
|
||||
struct timespec loopEnd;
|
||||
long elapsedTimeUs, remainingTimeUs;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &loopEnd);
|
||||
elapsedTimeUs = (loopEnd.tv_nsec - loopStart->tv_nsec) / 1E3 +
|
||||
((unsigned)(loopEnd.tv_sec - loopStart->tv_sec)) * 1E6;
|
||||
remainingTimeUs = 16000 - elapsedTimeUs;
|
||||
|
||||
if (remainingTimeUs >= 0) {
|
||||
// printf("loop remaining time %ld\n", remainingTimeUs);
|
||||
usleep(remainingTimeUs);
|
||||
} else {
|
||||
printf("loop overlap by %06ldus\n", -remainingTimeUs);
|
||||
printf("loop start %ld.%09lds\n", loopStart->tv_sec, loopStart->tv_nsec);
|
||||
printf("loop end %ld.%09lds\n", loopEnd.tv_sec, loopEnd.tv_nsec);
|
||||
}
|
||||
}
|
@@ -1,70 +0,0 @@
|
||||
#include "rpi_artnet.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "rpi_artnet_utils.h"
|
||||
|
||||
int udpSocket = -1;
|
||||
char buffer[1024];
|
||||
|
||||
void sendPollReply(struct sockaddr_in srcAddr);
|
||||
|
||||
void artnet_init() {
|
||||
struct sockaddr_in serverAddr;
|
||||
|
||||
/*Create UDP socket*/
|
||||
udpSocket = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (udpSocket < 0) {
|
||||
perror("Opening socket failed");
|
||||
}
|
||||
|
||||
/* Configure settings in address struct */
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_port = htons(ARTNET_PORT);
|
||||
serverAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
|
||||
memset(serverAddr.sin_zero, '\0', sizeof(serverAddr.sin_zero));
|
||||
|
||||
/* Bind socket with address struct */
|
||||
bind(udpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
|
||||
}
|
||||
|
||||
int artnet_read(artDmx_t **artDmx) {
|
||||
struct sockaddr_in srcAddr;
|
||||
socklen_t srcLen = sizeof(srcAddr);
|
||||
ssize_t bufferLen =
|
||||
recvfrom(udpSocket, buffer, ARTNET_MAX_BUFFER, 0, (struct sockaddr *)&srcAddr, &srcLen);
|
||||
|
||||
if (bufferLen <= ARTNET_MAX_BUFFER && bufferLen > sizeof(artnetHeader_t)) {
|
||||
artnetHeader_t *artnetHeader = (artnetHeader_t *)buffer;
|
||||
if (memcmp(artnetHeader->id, ARTNET_ID, sizeof(ARTNET_ID)) == 0) {
|
||||
switch (artnetHeader->opCode) {
|
||||
case OpDmx:
|
||||
if (bufferLen >= 20) {
|
||||
*artDmx = (artDmx_t *)buffer;
|
||||
}
|
||||
break;
|
||||
case OpPoll:
|
||||
sendPollReply(srcAddr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return artnetHeader->opCode;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void sendPollReply(struct sockaddr_in srcAddr) {
|
||||
/* Configure settings in address struct */
|
||||
srcAddr.sin_family = AF_INET;
|
||||
srcAddr.sin_port = htons(ARTNET_PORT);
|
||||
memset(srcAddr.sin_zero, '\0', sizeof(srcAddr.sin_zero));
|
||||
|
||||
sendto(udpSocket, (uint8_t *)&artPollReply, sizeof(artPollReply_t), 0,
|
||||
(struct sockaddr *)&srcAddr, sizeof(srcAddr));
|
||||
}
|
@@ -1,307 +0,0 @@
|
||||
// Raspberry Pi DMA utilities; see https://iosoft.blog for details
|
||||
//
|
||||
// Copyright (c) 2020 Jeremy P Bentham
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "rpi_dma_utils.h"
|
||||
|
||||
// If non-zero, print debug information
|
||||
#define DEBUG 0
|
||||
// If non-zero, enable PWM hardware output
|
||||
#define PWM_OUT 0
|
||||
|
||||
char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD",
|
||||
"TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""};
|
||||
char *gpio_mode_strs[] = {GPIO_MODE_STRS};
|
||||
|
||||
// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
|
||||
MEM_MAP pwm_regs, gpio_regs, dma_regs, clk_regs;
|
||||
|
||||
// Use mmap to obtain virtual address, given physical
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size) {
|
||||
mp->phys = phys;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE);
|
||||
mp->virt = map_segment(phys, mp->size);
|
||||
return (mp->virt);
|
||||
}
|
||||
|
||||
// Allocate uncached memory, get bus & phys addresses
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size) {
|
||||
void *ret;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->fd = open_mbox();
|
||||
ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 &&
|
||||
(mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 &&
|
||||
(mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0
|
||||
? mp->virt
|
||||
: 0;
|
||||
printf("VC mem handle %u, phys %p, virt %p\n", mp->h, mp->bus, mp->virt);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
// Free mapped peripheral or memory
|
||||
void unmap_periph_mem(MEM_MAP *mp) {
|
||||
if (mp) {
|
||||
if (mp->fd) {
|
||||
unmap_segment(mp->virt, mp->size);
|
||||
unlock_vc_mem(mp->fd, mp->h);
|
||||
free_vc_mem(mp->fd, mp->h);
|
||||
close_mbox(mp->fd);
|
||||
} else
|
||||
unmap_segment(mp->virt, mp->size);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- GPIO -----
|
||||
|
||||
// Set input or output with pullups
|
||||
void gpio_set(int pin, int mode, int pull) {
|
||||
gpio_mode(pin, mode);
|
||||
gpio_pull(pin, pull);
|
||||
}
|
||||
// Set I/P pullup or pulldown
|
||||
void gpio_pull(int pin, int pull) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_GPPUDCLK0) + pin / 32;
|
||||
|
||||
*REG32(gpio_regs, GPIO_GPPUD) = pull;
|
||||
usleep(2);
|
||||
*reg = pin << (pin % 32);
|
||||
usleep(2);
|
||||
*REG32(gpio_regs, GPIO_GPPUD) = 0;
|
||||
*reg = 0;
|
||||
}
|
||||
|
||||
// Set input or output
|
||||
void gpio_mode(int pin, int mode) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_MODE0) + pin / 10, shift = (pin % 10) * 3;
|
||||
*reg = (*reg & ~(7 << shift)) | (mode << shift);
|
||||
}
|
||||
|
||||
// Set an O/P pin
|
||||
void gpio_out(int pin, int val) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, val ? GPIO_SET0 : GPIO_CLR0) + pin / 32;
|
||||
*reg = 1 << (pin % 32);
|
||||
}
|
||||
|
||||
// Get an I/P pin value
|
||||
uint8_t gpio_in(int pin) {
|
||||
volatile uint32_t *reg = REG32(gpio_regs, GPIO_LEV0) + pin / 32;
|
||||
return (((*reg) >> (pin % 32)) & 1);
|
||||
}
|
||||
|
||||
// Display the values in a GPIO mode register
|
||||
void disp_mode_vals(uint32_t mode) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
printf("%u:%-4s ", i, gpio_mode_strs[(mode >> (i * 3)) & 7]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ----- VIDEOCORE MAILBOX -----
|
||||
|
||||
// Open mailbox interface, return file descriptor
|
||||
int open_mbox(void) {
|
||||
int fd;
|
||||
|
||||
if ((fd = open("/dev/vcio", 0)) < 0)
|
||||
fail("Error: can't open VC mailbox\n");
|
||||
return (fd);
|
||||
}
|
||||
// Close mailbox interface
|
||||
void close_mbox(int fd) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Send message to mailbox, return first response int, 0 if error
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp) {
|
||||
uint32_t ret = 0, i;
|
||||
|
||||
for (i = msgp->dlen / 4; i <= msgp->blen / 4; i += 4)
|
||||
msgp->uints[i++] = 0;
|
||||
msgp->len = (msgp->blen + 6) * 4;
|
||||
msgp->req = 0;
|
||||
if (ioctl(fd, _IOWR(100, 0, void *), msgp) < 0)
|
||||
printf("VC IOCTL failed\n");
|
||||
else if ((msgp->req & 0x80000000) == 0)
|
||||
printf("VC IOCTL error\n");
|
||||
else if (msgp->req == 0x80000001)
|
||||
printf("VC IOCTL partial error\n");
|
||||
else
|
||||
ret = msgp->uints[0];
|
||||
#if DEBUG
|
||||
disp_vc_msg(msgp);
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
||||
|
||||
// Allocate memory on PAGE_SIZE boundary, return handle
|
||||
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags) {
|
||||
VC_MSG msg = {
|
||||
.tag = 0x3000c, .blen = 12, .dlen = 12, .uints = {PAGE_ROUNDUP(size), PAGE_SIZE, flags}};
|
||||
return (msg_mbox(fd, &msg));
|
||||
}
|
||||
// Lock allocated memory, return bus address
|
||||
void *lock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000d, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? (void *)msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Unlock allocated memory
|
||||
uint32_t unlock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000e, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Free memory
|
||||
uint32_t free_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000f, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
uint32_t set_vc_clock(int fd, int id, uint32_t freq) {
|
||||
VC_MSG msg1 = {.tag = 0x38001, .blen = 8, .dlen = 8, .uints = {id, 1}};
|
||||
VC_MSG msg2 = {.tag = 0x38002, .blen = 12, .dlen = 12, .uints = {id, freq, 0}};
|
||||
msg_mbox(fd, &msg1);
|
||||
disp_vc_msg(&msg1);
|
||||
msg_mbox(fd, &msg2);
|
||||
disp_vc_msg(&msg2);
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Display mailbox message
|
||||
void disp_vc_msg(VC_MSG *msgp) {
|
||||
int i;
|
||||
|
||||
printf("VC msg len=%X, req=%X, tag=%X, blen=%x, dlen=%x, data ", msgp->len, msgp->req, msgp->tag,
|
||||
msgp->blen, msgp->dlen);
|
||||
for (i = 0; i < msgp->blen / 4; i++)
|
||||
printf("%08X ", msgp->uints[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ----- VIRTUAL MEMORY -----
|
||||
|
||||
// Get virtual memory segment for peripheral regs or physical mem
|
||||
void *map_segment(void *addr, int size) {
|
||||
int fd;
|
||||
void *mem;
|
||||
|
||||
size = PAGE_ROUNDUP(size);
|
||||
if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0)
|
||||
fail("Error: can't open /dev/mem, run using sudo\n");
|
||||
mem = mmap(0, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr);
|
||||
close(fd);
|
||||
#if DEBUG
|
||||
printf("Map %p -> %p\n", (void *)addr, mem);
|
||||
#endif
|
||||
if (mem == MAP_FAILED)
|
||||
fail("Error: can't map memory\n");
|
||||
return (mem);
|
||||
}
|
||||
// Free mapped memory
|
||||
void unmap_segment(void *mem, int size) {
|
||||
if (mem)
|
||||
munmap(mem, PAGE_ROUNDUP(size));
|
||||
}
|
||||
|
||||
// ----- DMA -----
|
||||
|
||||
// Enable and reset DMA
|
||||
void enable_dma(int chan) {
|
||||
*REG32(dma_regs, DMA_ENABLE) |= (1 << chan);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Start DMA, given first control block
|
||||
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) {
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 | csval; // Start DMA
|
||||
}
|
||||
|
||||
// Return remaining transfer length
|
||||
uint32_t dma_transfer_len(int chan) { return (*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); }
|
||||
|
||||
// Check if DMA is active
|
||||
uint32_t dma_active(int chan) { return ((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); }
|
||||
|
||||
// Halt current DMA operation by resetting controller
|
||||
void stop_dma(int chan) {
|
||||
if (dma_regs.virt)
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Display DMA registers
|
||||
void disp_dma(int chan) {
|
||||
volatile uint32_t *p = REG32(dma_regs, DMA_REG(chan, DMA_CS));
|
||||
int i = 0;
|
||||
|
||||
while (dma_regstrs[i][0]) {
|
||||
printf("%-7s %08X ", dma_regstrs[i++], *p++);
|
||||
if (i % 5 == 0 || dma_regstrs[i][0] == 0)
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ----- PWM -----
|
||||
|
||||
// Initialise PWM
|
||||
void init_pwm(int freq, int range, int val) {
|
||||
stop_pwm();
|
||||
if (*REG32(pwm_regs, PWM_STA) & 0x100) {
|
||||
printf("PWM bus error\n");
|
||||
*REG32(pwm_regs, PWM_STA) = 0x100;
|
||||
}
|
||||
#if USE_VC_CLOCK_SET
|
||||
set_vc_clock(mbox_fd, PWM_CLOCK_ID, freq);
|
||||
#else
|
||||
int divi = CLOCK_HZ / freq;
|
||||
*REG32(clk_regs, CLK_PWM_CTL) = CLK_PASSWD | (1 << 5);
|
||||
while (*REG32(clk_regs, CLK_PWM_CTL) & (1 << 7))
|
||||
;
|
||||
*REG32(clk_regs, CLK_PWM_DIV) = CLK_PASSWD | (divi << 12);
|
||||
*REG32(clk_regs, CLK_PWM_CTL) = CLK_PASSWD | 6 | (1 << 4);
|
||||
while ((*REG32(clk_regs, CLK_PWM_CTL) & (1 << 7)) == 0)
|
||||
;
|
||||
#endif
|
||||
usleep(100);
|
||||
*REG32(pwm_regs, PWM_RNG1) = range;
|
||||
*REG32(pwm_regs, PWM_FIF1) = val;
|
||||
#if PWM_OUT
|
||||
gpio_mode(PWM_PIN, PWM_PIN == 12 ? GPIO_ALT0 : GPIO_ALT5);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Start PWM operation
|
||||
void start_pwm(void) { *REG32(pwm_regs, PWM_CTL) = PWM_CTL_USEF1 | PWM_ENAB; }
|
||||
|
||||
// Stop PWM operation
|
||||
void stop_pwm(void) {
|
||||
if (pwm_regs.virt) {
|
||||
*REG32(pwm_regs, PWM_CTL) = 0;
|
||||
usleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
@@ -1,204 +0,0 @@
|
||||
// Raspberry Pi DMA utility definitions; see https://iosoft.blog for details
|
||||
//
|
||||
// Copyright (c) 2020 Jeremy P Bentham
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Location of peripheral registers in physical memory
|
||||
#define PHYS_REG_BASE PI_23_REG_BASE
|
||||
#define PI_01_REG_BASE 0x20000000 // Pi Zero or 1
|
||||
#define PI_23_REG_BASE 0x3F000000 // Pi 2 or 3
|
||||
#define PI_4_REG_BASE 0xFE000000 // Pi 4
|
||||
|
||||
#define CLOCK_HZ 250000000 // Pi 2 - 4
|
||||
//#define CLOCK_HZ 400000000 // Pi Zero
|
||||
|
||||
// Location of peripheral registers in bus memory
|
||||
#define BUS_REG_BASE 0x7E000000
|
||||
|
||||
// If non-zero, print debug information
|
||||
#define DEBUG 0
|
||||
|
||||
// If non-zero, set PWM clock using VideoCore mailbox
|
||||
#define USE_VC_CLOCK_SET 0
|
||||
|
||||
// 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))
|
||||
|
||||
// Structure for mapped peripheral or memory
|
||||
typedef struct {
|
||||
int fd, // File descriptor
|
||||
h, // Memory handle
|
||||
size; // Memory size
|
||||
void *bus, // Bus address
|
||||
*virt, // Virtual address
|
||||
*phys; // Physical address
|
||||
} MEM_MAP;
|
||||
|
||||
// Get virtual 8 and 32-bit pointers to register
|
||||
#define REG8(m, x) ((volatile uint8_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
|
||||
#define REG32(m, x) ((volatile uint32_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
|
||||
// Get bus address of register
|
||||
#define REG_BUS_ADDR(m, x) ((uint32_t)(m.bus) + (uint32_t)(x))
|
||||
// Convert uncached memory virtual address to bus address
|
||||
#define MEM_BUS_ADDR(mp, a) ((uint32_t)a - (uint32_t)mp->virt + (uint32_t)mp->bus)
|
||||
// Convert bus address to physical address (for mmap)
|
||||
#define BUS_PHYS_ADDR(a) ((void *)((uint32_t)(a) & ~0xC0000000))
|
||||
|
||||
// GPIO register definitions
|
||||
#define GPIO_BASE (PHYS_REG_BASE + 0x200000)
|
||||
#define GPIO_MODE0 0x00
|
||||
#define GPIO_SET0 0x1c
|
||||
#define GPIO_CLR0 0x28
|
||||
#define GPIO_LEV0 0x34
|
||||
#define GPIO_GPPUD 0x94
|
||||
#define GPIO_GPPUDCLK0 0x98
|
||||
// GPIO I/O definitions
|
||||
#define GPIO_IN 0
|
||||
#define GPIO_OUT 1
|
||||
#define GPIO_ALT0 4
|
||||
#define GPIO_ALT1 5
|
||||
#define GPIO_ALT2 6
|
||||
#define GPIO_ALT3 7
|
||||
#define GPIO_ALT4 3
|
||||
#define GPIO_ALT5 2
|
||||
#define GPIO_MODE_STRS "IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"
|
||||
#define GPIO_NOPULL 0
|
||||
#define GPIO_PULLDN 1
|
||||
#define GPIO_PULLUP 2
|
||||
|
||||
// Videocore mailbox memory allocation flags, see:
|
||||
// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
typedef enum {
|
||||
MEM_FLAG_DISCARDABLE = 1 << 0, // can be resized to 0 at any time. Use for cached data
|
||||
MEM_FLAG_NORMAL = 0 << 2, // normal allocating alias. Don't use from ARM
|
||||
MEM_FLAG_DIRECT = 1 << 2, // 0xC alias uncached
|
||||
MEM_FLAG_COHERENT = 2 << 2, // 0x8 alias. Non-allocating in L2 but coherent
|
||||
MEM_FLAG_ZERO = 1 << 4, // initialise buffer to all zeros
|
||||
MEM_FLAG_NO_INIT = 1 << 5, // don't initialise (default is initialise to all ones)
|
||||
MEM_FLAG_HINT_PERMALOCK = 1 << 6, // Likely to be locked for long periods of time
|
||||
MEM_FLAG_L1_NONALLOCATING = (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) // Allocating in L2
|
||||
} VC_ALLOC_FLAGS;
|
||||
// VC flags for unchached DMA memory
|
||||
#define DMA_MEM_FLAGS (MEM_FLAG_DIRECT | MEM_FLAG_ZERO)
|
||||
|
||||
// Mailbox command/response structure
|
||||
typedef struct {
|
||||
uint32_t len, // Overall length (bytes)
|
||||
req, // Zero for request, 1<<31 for response
|
||||
tag, // Command number
|
||||
blen, // Buffer length (bytes)
|
||||
dlen; // Data length (bytes)
|
||||
uint32_t uints[32 - 5]; // Data (108 bytes maximum)
|
||||
} VC_MSG __attribute__((aligned(16)));
|
||||
|
||||
// DMA channels and data requests
|
||||
#define DMA_CHAN_A 10
|
||||
#define DMA_CHAN_B 11
|
||||
#define DMA_PWM_DREQ 5
|
||||
#define DMA_SPI_TX_DREQ 6
|
||||
#define DMA_SPI_RX_DREQ 7
|
||||
#define DMA_BASE (PHYS_REG_BASE + 0x007000)
|
||||
// DMA register addresses offset by 0x100 * chan_num
|
||||
#define DMA_CS 0x00
|
||||
#define DMA_CONBLK_AD 0x04
|
||||
#define DMA_TI 0x08
|
||||
#define DMA_SRCE_AD 0x0c
|
||||
#define DMA_DEST_AD 0x10
|
||||
#define DMA_TXFR_LEN 0x14
|
||||
#define DMA_STRIDE 0x18
|
||||
#define DMA_NEXTCONBK 0x1c
|
||||
#define DMA_DEBUG 0x20
|
||||
#define DMA_REG(ch, r) ((r) == DMA_ENABLE ? DMA_ENABLE : (ch)*0x100 + (r))
|
||||
#define DMA_ENABLE 0xff0
|
||||
// DMA register values
|
||||
#define DMA_WAIT_RESP (1 << 3)
|
||||
#define DMA_CB_DEST_INC (1 << 4)
|
||||
#define DMA_DEST_DREQ (1 << 6)
|
||||
#define DMA_CB_SRCE_INC (1 << 8)
|
||||
#define DMA_SRCE_DREQ (1 << 10)
|
||||
#define DMA_PRIORITY(n) ((n) << 16)
|
||||
|
||||
// DMA control block (must be 32-byte aligned)
|
||||
typedef struct {
|
||||
uint32_t ti, // Transfer info
|
||||
srce_ad, // Source address
|
||||
dest_ad, // Destination address
|
||||
tfr_len, // Transfer length
|
||||
stride, // Transfer stride
|
||||
next_cb, // Next control block
|
||||
debug, // Debug register, zero in control block
|
||||
unused;
|
||||
} DMA_CB __attribute__((aligned(32)));
|
||||
|
||||
// PWM controller registers
|
||||
#define PWM_BASE (PHYS_REG_BASE + 0x20C000)
|
||||
#define PWM_CTL 0x00 // Control
|
||||
#define PWM_STA 0x04 // Status
|
||||
#define PWM_DMAC 0x08 // DMA control
|
||||
#define PWM_RNG1 0x10 // Channel 1 range
|
||||
#define PWM_DAT1 0x14 // Channel 1 data
|
||||
#define PWM_FIF1 0x18 // Channel 1 fifo
|
||||
#define PWM_RNG2 0x20 // Channel 2 range
|
||||
#define PWM_DAT2 0x24 // Channel 2 data
|
||||
// PWM register values
|
||||
#define PWM_CTL_RPTL1 (1 << 2) // Chan 1: repeat last data when FIFO empty
|
||||
#define PWM_CTL_USEF1 (1 << 5) // Chan 1: use FIFO
|
||||
#define PWM_DMAC_ENAB (1 << 31) // Start PWM DMA
|
||||
#define PWM_ENAB 1 // Enable PWM
|
||||
#define PWM_PIN 12 // GPIO pin for PWM output, 12 or 18
|
||||
|
||||
// Clock registers and values
|
||||
#define CLK_BASE (PHYS_REG_BASE + 0x101000)
|
||||
#define CLK_PWM_CTL 0xa0
|
||||
#define CLK_PWM_DIV 0xa4
|
||||
#define CLK_PASSWD 0x5a000000
|
||||
#define PWM_CLOCK_ID 0xa
|
||||
|
||||
void fail(char *s);
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size);
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size);
|
||||
void unmap_periph_mem(MEM_MAP *mp);
|
||||
void gpio_set(int pin, int mode, int pull);
|
||||
void gpio_pull(int pin, int pull);
|
||||
void gpio_mode(int pin, int mode);
|
||||
void gpio_out(int pin, int val);
|
||||
uint8_t gpio_in(int pin);
|
||||
void disp_mode_vals(uint32_t mode);
|
||||
int open_mbox(void);
|
||||
void close_mbox(int fd);
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp);
|
||||
void *map_segment(void *addr, int size);
|
||||
void unmap_segment(void *addr, int size);
|
||||
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags);
|
||||
void *lock_vc_mem(int fd, int h);
|
||||
uint32_t unlock_vc_mem(int fd, int h);
|
||||
uint32_t free_vc_mem(int fd, int h);
|
||||
uint32_t set_vc_clock(int fd, int id, uint32_t freq);
|
||||
void disp_vc_msg(VC_MSG *msgp);
|
||||
void enable_dma(int chan);
|
||||
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval);
|
||||
uint32_t dma_transfer_len(int chan);
|
||||
uint32_t dma_active(int chan);
|
||||
void stop_dma(int chan);
|
||||
void disp_dma(int chan);
|
||||
void init_pwm(int freq, int range, int val);
|
||||
void start_pwm(void);
|
||||
void stop_pwm(void);
|
||||
|
||||
// EOF
|
95
RpiLedBars/src/rpi_midi_controller.c
Normal file
95
RpiLedBars/src/rpi_midi_controller.c
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "rpi_midi_controller.h"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
static snd_seq_t *seq_handle;
|
||||
static int sys_port;
|
||||
static int in_port;
|
||||
|
||||
void subscribe_system_port();
|
||||
|
||||
void subscribe_midi_controller(snd_seq_addr_t controller);
|
||||
|
||||
void handle_system_port_events(snd_seq_event_t *ev);
|
||||
|
||||
void handle_in_port_events(snd_seq_event_t *ev);
|
||||
|
||||
void setup_midi_controller() {
|
||||
if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK) != 0) {
|
||||
SNDERR("snd_seq_open");
|
||||
}
|
||||
|
||||
if (snd_seq_set_client_name(seq_handle, "Midi Listener") != 0) {
|
||||
SNDERR("snd_seq_set_client_name");
|
||||
}
|
||||
|
||||
sys_port = snd_seq_create_simple_port(seq_handle, "listen:in",
|
||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
if (sys_port < 0) {
|
||||
SNDERR("snd_seq_create_simple_port");
|
||||
}
|
||||
|
||||
subscribe_system_port();
|
||||
|
||||
in_port = snd_seq_create_simple_port(seq_handle, "listen:in",
|
||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
if (in_port < 0) {
|
||||
SNDERR("snd_seq_create_simple_port");
|
||||
}
|
||||
|
||||
snd_seq_addr_t controller = {.client = 24, .port = 0};
|
||||
subscribe_midi_controller(controller);
|
||||
}
|
||||
|
||||
void execute_midi_controller() {
|
||||
snd_seq_event_t *ev = NULL;
|
||||
int ret;
|
||||
while ((ret = snd_seq_event_input(seq_handle, &ev)) > 0) {
|
||||
if (ev->dest.port == sys_port) {
|
||||
handle_system_port_events(ev);
|
||||
} else if (ev->dest.port == in_port) {
|
||||
printf("ev %02d : %#010x - %#010x - %#010x\n", ev->type, ev->data.raw32.d[0],
|
||||
ev->data.raw32.d[1], ev->data.raw32.d[2]);
|
||||
} else {
|
||||
fprintf(stderr, "unkonwn midi dest port\n");
|
||||
}
|
||||
}
|
||||
if (ret < 0 && ret != -EAGAIN) {
|
||||
SNDERR("snd_seq_event_input");
|
||||
}
|
||||
}
|
||||
|
||||
void close_midi_controller() {}
|
||||
|
||||
void subscribe_system_port() {
|
||||
snd_seq_addr_t sender = {.client = 0, .port = SND_SEQ_PORT_SYSTEM_ANNOUNCE};
|
||||
snd_seq_connect_from(seq_handle, sys_port, sender.client, sender.port);
|
||||
}
|
||||
|
||||
void subscribe_midi_controller(snd_seq_addr_t controller) {
|
||||
snd_seq_connect_from(seq_handle, in_port, controller.client, controller.port);
|
||||
}
|
||||
|
||||
void handle_system_port_events(snd_seq_event_t *ev) {
|
||||
if (ev->type == SND_SEQ_EVENT_PORT_START) {
|
||||
snd_seq_addr_t *newport = (snd_seq_addr_t *)&ev->data;
|
||||
if (newport->client != snd_seq_client_id(seq_handle)) {
|
||||
fprintf(stdout, "New port %d:%d\n", newport->client, newport->port);
|
||||
subscribe_midi_controller(*newport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_in_port_events(snd_seq_event_t *ev) {
|
||||
if (ev->type == SND_SEQ_EVENT_PORT_START) {
|
||||
snd_seq_addr_t *newport = (snd_seq_addr_t *)&ev->data;
|
||||
if (newport->client != snd_seq_client_id(seq_handle)) {
|
||||
fprintf(stdout, "New port %d:%d\n", newport->client, newport->port);
|
||||
subscribe_midi_controller(*newport);
|
||||
}
|
||||
}
|
||||
}
|
10
RpiLedBars/src/rpi_midi_controller.h
Normal file
10
RpiLedBars/src/rpi_midi_controller.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#if !defined(__RPI_MIDI_CONTROLLER_H__)
|
||||
#define __RPI_MIDI_CONTROLLER_H__
|
||||
|
||||
void setup_midi_controller();
|
||||
|
||||
void execute_midi_controller();
|
||||
|
||||
void close_midi_controller();
|
||||
|
||||
#endif /* __RPI_MIDI_CONTROLLER_H__ */
|
1
RpiLedBars/src/rpi_param.c
Normal file
1
RpiLedBars/src/rpi_param.c
Normal file
@@ -0,0 +1 @@
|
||||
#include "rpi_param.h"
|
4
RpiLedBars/src/rpi_param.h
Normal file
4
RpiLedBars/src/rpi_param.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#if !defined(__RPI_PARAM_H__)
|
||||
#define __RPI_PARAM_H__
|
||||
|
||||
#endif /* __RPI_PARAM_H__ */
|
@@ -1,344 +0,0 @@
|
||||
// Raspberry Pi WS2812 LED driver using SMI
|
||||
// For detailed description, see https://iosoft.blog
|
||||
//
|
||||
// Copyright (c) 2020 Jeremy P Bentham
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// v0.01 JPB 16/7/20 Adapted from rpi_smi_adc_test v0.06
|
||||
// v0.02 JPB 15/9/20 Addded RGB to GRB conversion
|
||||
// v0.03 JPB 15/9/20 Added red-green flashing
|
||||
// v0.04 JPB 16/9/20 Added test mode
|
||||
// v0.05 JPB 19/9/20 Changed test mode colours
|
||||
// v0.06 JPB 20/9/20 Outlined command-line data input
|
||||
// v0.07 JPB 25/9/20 Command-line data input if not in test mode
|
||||
// v0.08 JPB 26/9/20 Changed from 4 to 3 pulses per LED bit
|
||||
// Added 4-bit zero preamble
|
||||
// Added raw Tx data test
|
||||
// v0.09 JPB 27/9/20 Added 16-channel option
|
||||
// v0.10 JPB 28/9/20 Corrected Pi Zero caching problem
|
||||
// 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 <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "rpi_artnet.h"
|
||||
#include "rpi_smi_defs.h"
|
||||
|
||||
// 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;
|
||||
|
||||
// Pointers to SMI registers
|
||||
volatile SMI_CS_REG *smi_cs;
|
||||
volatile SMI_L_REG *smi_l;
|
||||
volatile SMI_A_REG *smi_a;
|
||||
volatile SMI_D_REG *smi_d;
|
||||
volatile SMI_DMC_REG *smi_dmc;
|
||||
volatile SMI_DSR_REG *smi_dsr;
|
||||
volatile SMI_DSW_REG *smi_dsw;
|
||||
volatile SMI_DCS_REG *smi_dcs;
|
||||
volatile SMI_DCA_REG *smi_dca;
|
||||
volatile SMI_DCD_REG *smi_dcd;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
|
||||
// int main(int argc, char *argv[]) {
|
||||
// // setup
|
||||
// int args = 0, n;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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)]);
|
||||
// }
|
||||
|
||||
// #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);
|
||||
// sleep(1);
|
||||
// }
|
||||
|
||||
// artnet_init();
|
||||
|
||||
// // 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
|
||||
int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan) {
|
||||
int i = 0;
|
||||
char *p;
|
||||
|
||||
while (chan < LED_NCHANS && i < CHAN_MAXLEDS && hexdig(*s) >= 0) {
|
||||
rgbs[i++][chan] = strtoul(s, &p, 16);
|
||||
s = *p ? p + 1 : p;
|
||||
}
|
||||
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) {
|
||||
int i, n, msk;
|
||||
|
||||
// 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 ? 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;
|
||||
for (i = 0; i < LED_NCHANS; i++) {
|
||||
if (rgbs[i] & msk)
|
||||
txd[1] |= (1 << i);
|
||||
}
|
||||
txd += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
// Swap adjacent bytes in transmit data
|
||||
void swap_bytes(void *data, int len) {
|
||||
uint16_t *wp = (uint16_t *)data;
|
||||
|
||||
len = (len + 1) / 2;
|
||||
while (len-- > 0) {
|
||||
*wp = __builtin_bswap16(*wp);
|
||||
wp++;
|
||||
}
|
||||
}
|
||||
|
||||
// Return hex digit value, -ve if not hex
|
||||
int hexdig(char c) {
|
||||
c = toupper(c);
|
||||
return ((c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1);
|
||||
}
|
||||
|
||||
// Map GPIO, DMA and SMI registers into virtual mem (user space)
|
||||
// If any of these fail, program will be terminated
|
||||
void map_devices(void) {
|
||||
map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE);
|
||||
map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE);
|
||||
map_periph(&clk_regs, (void *)CLK_BASE, PAGE_SIZE);
|
||||
map_periph(&smi_regs, (void *)SMI_BASE, PAGE_SIZE);
|
||||
}
|
||||
|
||||
// Catastrophic failure in initial setup
|
||||
void fail(char *s) {
|
||||
printf(s);
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
// Free memory segments and exit
|
||||
void terminate(int sig) {
|
||||
int i;
|
||||
printf("Closing\n");
|
||||
if (gpio_regs.virt) {
|
||||
for (i = 0; i < LED_NCHANS; i++)
|
||||
gpio_mode(LED_D0_PIN + i, GPIO_IN);
|
||||
}
|
||||
if (smi_regs.virt)
|
||||
*REG32(smi_regs, SMI_CS) = 0;
|
||||
stop_dma(DMA_CHAN);
|
||||
unmap_periph_mem(&vc_mem);
|
||||
unmap_periph_mem(&smi_regs);
|
||||
unmap_periph_mem(&dma_regs);
|
||||
unmap_periph_mem(&gpio_regs);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Initialise SMI, given data width, time step, and setup/hold/strobe counts
|
||||
// Step value is in nanoseconds: even numbers, 2 to 30
|
||||
void init_smi(int width, int ns, int setup, int strobe, int hold) {
|
||||
int i, divi = ns / 2;
|
||||
|
||||
smi_cs = (SMI_CS_REG *)REG32(smi_regs, SMI_CS);
|
||||
smi_l = (SMI_L_REG *)REG32(smi_regs, SMI_L);
|
||||
smi_a = (SMI_A_REG *)REG32(smi_regs, SMI_A);
|
||||
smi_d = (SMI_D_REG *)REG32(smi_regs, SMI_D);
|
||||
smi_dmc = (SMI_DMC_REG *)REG32(smi_regs, SMI_DMC);
|
||||
smi_dsr = (SMI_DSR_REG *)REG32(smi_regs, SMI_DSR0);
|
||||
smi_dsw = (SMI_DSW_REG *)REG32(smi_regs, SMI_DSW0);
|
||||
smi_dcs = (SMI_DCS_REG *)REG32(smi_regs, SMI_DCS);
|
||||
smi_dca = (SMI_DCA_REG *)REG32(smi_regs, SMI_DCA);
|
||||
smi_dcd = (SMI_DCD_REG *)REG32(smi_regs, SMI_DCD);
|
||||
smi_cs->value = smi_l->value = smi_a->value = 0;
|
||||
smi_dsr->value = smi_dsw->value = smi_dcs->value = smi_dca->value = 0;
|
||||
if (*REG32(clk_regs, CLK_SMI_DIV) != divi << 12) {
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | (1 << 5);
|
||||
usleep(10);
|
||||
while (*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7))
|
||||
;
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_DIV) = CLK_PASSWD | (divi << 12);
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | 6 | (1 << 4);
|
||||
usleep(10);
|
||||
while ((*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) == 0)
|
||||
;
|
||||
usleep(100);
|
||||
}
|
||||
if (smi_cs->seterr)
|
||||
smi_cs->seterr = 1;
|
||||
smi_dsr->rsetup = smi_dsw->wsetup = setup;
|
||||
smi_dsr->rstrobe = smi_dsw->wstrobe = strobe;
|
||||
smi_dsr->rhold = smi_dsw->whold = hold;
|
||||
smi_dmc->panicr = smi_dmc->panicw = 8;
|
||||
smi_dmc->reqr = smi_dmc->reqw = REQUEST_THRESH;
|
||||
smi_dsr->rwidth = smi_dsw->wwidth = width;
|
||||
for (i = 0; i < LED_NCHANS; i++)
|
||||
gpio_mode(LED_D0_PIN + i, GPIO_ALT1);
|
||||
}
|
||||
|
||||
// Set up SMI transfers using DMA
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T **txdata) {
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
*txdata = (TXDATA_T *)(cbs + 1);
|
||||
smi_dmc->dmaen = 1;
|
||||
smi_cs->enable = 1;
|
||||
smi_cs->clear = 1;
|
||||
smi_cs->pxldat = 1;
|
||||
smi_l->len = nsamp * sizeof(TXDATA_T);
|
||||
smi_cs->write = 1;
|
||||
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].dest_ad = REG_BUS_ADDR(smi_regs, SMI_D);
|
||||
}
|
||||
|
||||
// Start SMI DMA transfers
|
||||
void start_smi(MEM_MAP *mp) {
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
start_dma(mp, DMA_CHAN, &cbs[0], 0);
|
||||
smi_cs->start = 1;
|
||||
}
|
||||
|
||||
// EOF
|
@@ -1,62 +0,0 @@
|
||||
#if !defined(__RPI_PIXLEDS_H__)
|
||||
#define __RPI_PIXLEDS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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__
|
@@ -1,37 +0,0 @@
|
||||
#include "VitualMemory.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "utils.h"
|
||||
#include <fcntl.h>
|
||||
// #include <sys/ioctl.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
#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__
|
@@ -1,4 +0,0 @@
|
||||
// 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))
|
139
RpiLedBars/src/tasks/artnet/rpi_artnet.c
Normal file
139
RpiLedBars/src/tasks/artnet/rpi_artnet.c
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "rpi_artnet.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "rpi_artnet_utils.h"
|
||||
|
||||
int udpSocket = -1;
|
||||
char buffer[1024];
|
||||
bool isUdpListenerRunning = false;
|
||||
pthread_t udpListener;
|
||||
uint8_t artDmxBufferArray[16][512];
|
||||
|
||||
static void *artnet_udp_handler(void *arg);
|
||||
|
||||
void artnet_send_poll_reply(struct sockaddr_in srcAddr);
|
||||
|
||||
void artnet_init() {}
|
||||
|
||||
void start_artnet_bg_worker() {
|
||||
isUdpListenerRunning = true;
|
||||
|
||||
if (pthread_create(&udpListener, NULL, artnet_udp_handler, NULL) < 0) {
|
||||
perror("pthread_create");
|
||||
}
|
||||
}
|
||||
|
||||
void stop_artnet_bg_worker() {
|
||||
isUdpListenerRunning = false;
|
||||
|
||||
if (pthread_join(udpListener, NULL) != 0) {
|
||||
perror("pthread_join");
|
||||
}
|
||||
}
|
||||
|
||||
int artnet_get_dmx_data(unsigned int univerve, uint8_t **dmxData) {
|
||||
if (univerve > 8) {
|
||||
fprintf(stderr, "Universe %d out of bounds %d\n", univerve, 16);
|
||||
*dmxData = NULL;
|
||||
return -1;
|
||||
}
|
||||
*dmxData = artDmxBufferArray[univerve];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *artnet_udp_handler(void *arg) {
|
||||
struct sockaddr_in serverAddr;
|
||||
struct pollfd fds[1];
|
||||
int timeoutMs;
|
||||
int ret;
|
||||
int flags;
|
||||
|
||||
/* Create UDP socket */
|
||||
udpSocket = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (udpSocket < 0) {
|
||||
perror("Opening socket failed");
|
||||
}
|
||||
|
||||
/* Set non-blocking socket */
|
||||
flags = fcntl(udpSocket, F_GETFL, 0);
|
||||
fcntl(udpSocket, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
/* pollfd structure and timeout */
|
||||
memset(fds, 0, sizeof(fds));
|
||||
fds[0].fd = udpSocket;
|
||||
fds[0].events = POLLIN;
|
||||
timeoutMs = 10;
|
||||
|
||||
/* Configure settings in address struct */
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_port = htons(ARTNET_PORT);
|
||||
serverAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
|
||||
memset(serverAddr.sin_zero, '\0', sizeof(serverAddr.sin_zero));
|
||||
|
||||
/* Bind socket with address struct */
|
||||
bind(udpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
|
||||
|
||||
struct sockaddr_in srcAddr;
|
||||
socklen_t srcLen = sizeof(srcAddr);
|
||||
|
||||
while (isUdpListenerRunning) {
|
||||
|
||||
if ((ret = poll(fds, 1, timeoutMs)) == 1) {
|
||||
ssize_t bufferLen =
|
||||
recvfrom(udpSocket, buffer, ARTNET_MAX_BUFFER, 0, (struct sockaddr *)&srcAddr, &srcLen);
|
||||
|
||||
if (bufferLen <= ARTNET_MAX_BUFFER && bufferLen > sizeof(artnetHeader_t)) {
|
||||
artnetHeader_t *artnetHeader = (artnetHeader_t *)buffer;
|
||||
|
||||
if (memcmp(artnetHeader->id, ARTNET_ID, sizeof(ARTNET_ID)) == 0) {
|
||||
switch (artnetHeader->opCode) {
|
||||
case OpDmx:
|
||||
if (bufferLen >= 20) {
|
||||
artDmx_t *artDmx = (artDmx_t *)buffer;
|
||||
uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo;
|
||||
uint8_t *artDmxBuffer = artDmxBufferArray[artDmx->subUni & 0x00ff];
|
||||
if (dmxLength <= 512) {
|
||||
// store for later use
|
||||
memcpy(artDmxBuffer, artDmx->data, dmxLength);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OpPoll:
|
||||
artnet_send_poll_reply(srcAddr);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
fprintf(stderr, "error polling %d: %s\n", udpSocket, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
close(udpSocket);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void artnet_send_poll_reply(struct sockaddr_in srcAddr) {
|
||||
/* Configure settings in address struct */
|
||||
srcAddr.sin_family = AF_INET;
|
||||
srcAddr.sin_port = htons(ARTNET_PORT);
|
||||
memset(srcAddr.sin_zero, '\0', sizeof(srcAddr.sin_zero));
|
||||
|
||||
sendto(udpSocket, (uint8_t *)&artPollReply, sizeof(artPollReply_t), 0,
|
||||
(struct sockaddr *)&srcAddr, sizeof(srcAddr));
|
||||
}
|
@@ -5,6 +5,10 @@
|
||||
|
||||
void artnet_init();
|
||||
|
||||
int artnet_read(artDmx_t **artDmx);
|
||||
void start_artnet_bg_worker();
|
||||
|
||||
void stop_artnet_bg_worker();
|
||||
|
||||
int artnet_get_dmx_data(unsigned int univerve, uint8_t **dmxData);
|
||||
|
||||
#endif // __RPI_ARTNET_H__
|
146
RpiLedBars/src/tasks/cava/rpi_cava.c
Normal file
146
RpiLedBars/src/tasks/cava/rpi_cava.c
Normal file
@@ -0,0 +1,146 @@
|
||||
#include "rpi_cava.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define MAXLINECHAR 128 * (5 + 1) + 1
|
||||
|
||||
pid_t cavaPid;
|
||||
bool isFifoReaderRunning = false;
|
||||
pthread_t fifoReader;
|
||||
int cavaFifo;
|
||||
char lineBuffer[MAXLINECHAR];
|
||||
uint16_t buffer[128 + 2];
|
||||
|
||||
static void *fifo_to_buffer(void *arg);
|
||||
|
||||
void setup_cava() {
|
||||
if ((cavaPid = fork()) == -1) {
|
||||
perror("fork");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (cavaPid == 0) {
|
||||
/* Child process*/
|
||||
pthread_setschedprio(pthread_self(), 30);
|
||||
char *args[] = {"/usr/local/bin/cava", "-p", "/home/pi/LedBars/RpiLedBars/cava_config", NULL};
|
||||
|
||||
if (execv(args[0], args) != 0) {
|
||||
perror("execv");
|
||||
}
|
||||
|
||||
} else {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void close_cava() {
|
||||
stop_cava_bg_worker();
|
||||
kill(cavaPid, SIGTERM);
|
||||
}
|
||||
|
||||
void start_cava_bg_worker() {
|
||||
isFifoReaderRunning = true;
|
||||
pthread_create(&fifoReader, NULL, fifo_to_buffer, NULL);
|
||||
}
|
||||
|
||||
void stop_cava_bg_worker() {
|
||||
isFifoReaderRunning = false;
|
||||
if (pthread_join(fifoReader, NULL) != 0) {
|
||||
perror("pthread_join");
|
||||
}
|
||||
}
|
||||
|
||||
int get_cava_buffer(uint16_t **buffer_dst) {
|
||||
*buffer_dst = buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t valueIndex = 0, charOffset = 0;
|
||||
char strValue[6] = "0\0";
|
||||
bool hasToBeDiscarded = false;
|
||||
|
||||
static void *fifo_to_buffer(void *arg) {
|
||||
struct pollfd fds[1];
|
||||
int timeoutMs;
|
||||
int ret;
|
||||
|
||||
if ((cavaFifo = open("/tmp/cava_output", O_RDONLY | O_NONBLOCK)) < 0) {
|
||||
perror("open");
|
||||
close_cava();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
memset(fds, 0, sizeof(fds));
|
||||
fds[0].fd = cavaFifo;
|
||||
fds[0].events = POLLIN;
|
||||
timeoutMs = 10;
|
||||
|
||||
hasToBeDiscarded = true;
|
||||
|
||||
while (isFifoReaderRunning) {
|
||||
|
||||
if ((ret = poll(fds, 1, timeoutMs)) == 1) {
|
||||
int nread;
|
||||
nread = read(cavaFifo, lineBuffer, 128 + 1);
|
||||
|
||||
if (nread >= 0) {
|
||||
for (size_t i = 0; i < nread; ++i) {
|
||||
char current = lineBuffer[i];
|
||||
|
||||
if (hasToBeDiscarded) {
|
||||
if (current == '\n') {
|
||||
charOffset = 0;
|
||||
strValue[charOffset] = '\0';
|
||||
valueIndex = 0;
|
||||
hasToBeDiscarded = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
if ('0' <= current && current <= '9') {
|
||||
strValue[charOffset++] = current;
|
||||
} else if (current == '\n' || current == ';') {
|
||||
strValue[charOffset] = '\0';
|
||||
charOffset = 0;
|
||||
buffer[valueIndex++] = atoi(strValue);
|
||||
|
||||
if (current == '\n' || valueIndex > 129) {
|
||||
valueIndex = 0;
|
||||
|
||||
if (valueIndex > 129) {
|
||||
fprintf(stderr, "Buffer overflow, \\n missed, discarding next\n");
|
||||
hasToBeDiscarded = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
fprintf(stderr, "Unexpected char %d [%c]\n", current, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (errno != EAGAIN) {
|
||||
perror("read");
|
||||
}
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
fprintf(stderr, "error polling %d: %s\n", cavaFifo, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
close(cavaFifo);
|
||||
|
||||
return NULL;
|
||||
}
|
16
RpiLedBars/src/tasks/cava/rpi_cava.h
Normal file
16
RpiLedBars/src/tasks/cava/rpi_cava.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#if !defined(__RPI_CAVA_H__)
|
||||
#define __RPI_CAVA_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void setup_cava();
|
||||
|
||||
int get_cava_buffer(uint16_t **buffer_dst);
|
||||
|
||||
void close_cava();
|
||||
|
||||
void start_cava_bg_worker();
|
||||
|
||||
void stop_cava_bg_worker();
|
||||
|
||||
#endif /* __RPI_CAVA_H__ */
|
@@ -1,8 +0,0 @@
|
||||
|
||||
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
#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__
|
Reference in New Issue
Block a user