multithreaded app

This commit is contained in:
2021-07-28 21:26:40 +01:00
parent c2e37543ff
commit 71ccbeffe6
66 changed files with 2527 additions and 260 deletions

50
RpiLedBars/res/.asoundrc Normal file
View File

@ -0,0 +1,50 @@
#The below 2 sections are commented, they control the default sound card to use
#This set up is for a Pi with an I2S microphone attached using the guide
#from adafruit at
# https://learn.adafruit.com/adafruit-i2s-mems-microphone-breakout
#Uncomment and the I2S will be your default card (assuming same setup)
#but you won't get audio playback because both recording and playback will be
#defaulted
#TODO - Figure out how to set default for recording separately
#To adjust use aplay -l to work out the devices you have and their card number
#For recording devices use arecord -l
#pcm.!default {
# type hw
# card 1
#}
#ctl.!default {
# type hw
# card 1
#}
#This section makes a reference to your I2S hardware, adjust the card name
# to what is shown in arecord -l after card x: before the name in []
#You may have to adjust channel count also but stick with default first
pcm.dmic_hw {
type hw
card sndrpii2scard
channels 1
format S32_LE
}
#This is the software volume control, it links to the hardware above and after
# saving the .asoundrc file you can type alsamixer, press F6 to select
# your I2S mic then F4 to set the recording volume and arrow up and down
# to adjust the volume
# After adjusting the volume - go for 50 percent at first, you can do
# something like
# arecord -D dmic_sv -c2 -r 48000 -f S32_LE -t wav -V mono -v myfile.wav
pcm.dmic_sv {
type softvol
slave.pcm dmic_hw
control {
name "Boost Capture Volume"
card sndrpii2scard
}
min_dB -3.0
max_dB 30.0
}

View File

@ -0,0 +1,18 @@
https://makersportal.com/blog/recording-stereo-audio-on-a-raspberry-pi
https://learn.adafruit.com/adafruit-i2s-mems-microphone-breakout/raspberry-pi-wiring-test
sudo pip3 install --upgrade adafruit-python-shell
cd /tmp
sudo wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/i2smic.py
sudo python3 i2smic.py
# plug microphone
sudo reboot
arecord -l
arecord -D plughw:1 -c1 -r 48000 -f S32_LE -t wav -V mono -v file.wav
# Control record volume
cp res/.asoundrc ~/.asoundrc
# alsa API
http://www.equalarea.com/paul/alsa-audio.html

View File

@ -0,0 +1,308 @@
// 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

View File

@ -0,0 +1,204 @@
// 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

View File

@ -0,0 +1,341 @@
// 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 <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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_smi);
// 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_smi(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 terminate_smid
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_smi(0);
}
// Free memory segments and exit
void terminate_smi(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 ledChan, int ns, int setup, int strobe, int hold) {
int width = ledChan > 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 < 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

View File

@ -0,0 +1,57 @@
#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))
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_smi(int sig);
void init_smi(int ledChan, 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__

View File

@ -0,0 +1,112 @@
// Secondary Memory Interface definitions for Raspberry Pi
//
// v0.01 JPB 12/7/20 Adapted from rpi_smi_test v0.19
// Register definitions
#define SMI_BASE (PHYS_REG_BASE + 0x600000)
#define SMI_CS 0x00 // Control & status
#define SMI_L 0x04 // Transfer length
#define SMI_A 0x08 // Address
#define SMI_D 0x0c // Data
#define SMI_DSR0 0x10 // Read settings device 0
#define SMI_DSW0 0x14 // Write settings device 0
#define SMI_DSR1 0x18 // Read settings device 1
#define SMI_DSW1 0x1c // Write settings device 1
#define SMI_DSR2 0x20 // Read settings device 2
#define SMI_DSW2 0x24 // Write settings device 2
#define SMI_DSR3 0x28 // Read settings device 3
#define SMI_DSW3 0x2c // Write settings device 3
#define SMI_DMC 0x30 // DMA control
#define SMI_DCS 0x34 // Direct control/status
#define SMI_DCA 0x38 // Direct address
#define SMI_DCD 0x3c // Direct data
#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
// Union of 32-bit value with register bitfields
#define REG_DEF(name, fields) \
typedef union { \
struct { \
volatile uint32_t fields; \
}; \
volatile uint32_t value; \
} name
// Control and status register
#define SMI_CS_FIELDS \
enable: \
1, done : 1, active : 1, start : 1, clear : 1, write : 1, _x1 : 2, teen : 1, intd : 1, intt : 1, \
intr : 1, pvmode : 1, seterr : 1, pxldat : 1, edreq : 1, _x2 : 8, _x3 : 1, aferr : 1, \
txw : 1, rxr : 1, txd : 1, rxd : 1, txe : 1, rxf : 1
REG_DEF(SMI_CS_REG, SMI_CS_FIELDS);
// Data length register
#define SMI_L_FIELDS \
len: \
32
REG_DEF(SMI_L_REG, SMI_L_FIELDS);
// Address & device number
#define SMI_A_FIELDS \
addr: \
6, _x1 : 2, dev : 2
REG_DEF(SMI_A_REG, SMI_A_FIELDS);
// Data FIFO
#define SMI_D_FIELDS \
data: \
32
REG_DEF(SMI_D_REG, SMI_D_FIELDS);
// DMA control register
#define SMI_DMC_FIELDS \
reqw: \
6, reqr : 6, panicw : 6, panicr : 6, dmap : 1, _x1 : 3, dmaen : 1
REG_DEF(SMI_DMC_REG, SMI_DMC_FIELDS);
// Device settings: read (1 of 4)
#define SMI_DSR_FIELDS \
rstrobe: \
7, rdreq : 1, rpace : 7, rpaceall : 1, rhold : 6, fsetup : 1, mode68 : 1, rsetup : 6, rwidth : 2
REG_DEF(SMI_DSR_REG, SMI_DSR_FIELDS);
// Device settings: write (1 of 4)
#define SMI_DSW_FIELDS \
wstrobe: \
7, wdreq : 1, wpace : 7, wpaceall : 1, whold : 6, wswap : 1, wformat : 1, wsetup : 6, wwidth : 2
REG_DEF(SMI_DSW_REG, SMI_DSW_FIELDS);
// Direct control register
#define SMI_DCS_FIELDS \
enable: \
1, start : 1, done : 1, write : 1
REG_DEF(SMI_DCS_REG, SMI_DCS_FIELDS);
// Direct control address & device number
#define SMI_DCA_FIELDS \
addr: \
6, _x1 : 2, dev : 2
REG_DEF(SMI_DCA_REG, SMI_DCA_FIELDS);
// Direct control data
#define SMI_DCD_FIELDS \
data: \
32
REG_DEF(SMI_DCD_REG, SMI_DCD_FIELDS);
// Debug register
#define SMI_FLVL_FIELDS \
fcnt: \
6, _x1 : 2, flvl : 6
REG_DEF(SMI_FLVL_REG, SMI_FLVL_FIELDS);
#define CLK_SMI_CTL 0xb0
#define CLK_SMI_DIV 0xb4
// EOF

View File

@ -0,0 +1,315 @@
/*
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
Copyright (c) 2016-2020 Stephan Ruloff
https://github.com/rstephan/ArtnetnodeWifi
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, under version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <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;
}
}

View File

@ -0,0 +1,151 @@
#ifndef ARTNETNODEWIFI_H
#define ARTNETNODEWIFI_H
/*
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
Copyright (c) 2016 Stephan Ruloff
https://github.com/rstephan
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, under version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <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

View File

@ -0,0 +1,22 @@
#ifndef NODEREPORTCODES_H
#define NODEREPORTCODES_H
// List of hex values and discriptions of Node Report Codes
#define RcDebug 0x0000 // Booted in debug mode (Only used in development)
#define RcPowerOk 0x0001 // Power On Tests successful
#define RcPowerFail 0x0002 // Hardware tests failed at Power On
#define RcSocketWr1 0x0003 // Last UDP from Node failed due to truncated length, Most likely caused by a collision.
#define RcParseFail 0x0004 // Unable to identify last UDP transmission. Check OpCode and \packet length.
#define RcUdpFail 0x0005 // Unable to open Udp Socket in last transmission attempt
#define RcShNameOk 0x0006 // Confirms that Short Name programming via ArtAddress, was successful.
#define RcLoNameOk 0x0007 // Confirms that Long Name programming via ArtAddress, was successful.
#define RcDmxError 0x0008 // DMX512 receive errors detected.
#define RcDmxUdpFull 0x0009 // Ran out of internal DMX transmit buffers.
#define RcDmxRxFull 0x000a // Ran out of internal DMX Rx buffers.
#define RcSwitchErr 0x000b // Rx Universe switches conflict.
#define RcConfigErr 0x000c // Product configuration does not match firmware.
#define RcDmxShort 0x000d // DMX output short detected. See GoodOutput field.
#define RcFirmwareFail 0x000e // Last attempt to upload new firmware failed.
#define RcUserFail 0x000f // User changed switch settings when address locked by remote programming. User changes ignored.
#endif

View File

@ -0,0 +1,136 @@
/*
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
Copyright (c) 2016 Stephan Ruloff
https://github.com/rstephan
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, under version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <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;
}

View File

@ -0,0 +1,188 @@
#ifndef POLLREPLY_H
#define POLLREPLY_H
/*
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, under version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <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

View File

@ -0,0 +1,128 @@
#if !defined(__ARTNET_OP_CODES_H__)
#define __ARTNET_OP_CODES_H__
/* List of hex values and discriptions of Opcodes */
/* This is an ArtPoll packet, no other data is contained in this UDP packet */
#define OpPoll 0x2000
/* This is an ArtPollReply Packet. It contains device status information. */
#define OpPollReply 0x2100
/* Diagnostics and data logging packet. */
#define OpDiagData 0x2300
/* Used to send text based parameter commands. */
#define OpCommand 0x2400
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
* Universe. */
#define OpOutput 0x5000
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
* Universe. */
#define OpDmx 0x5000
/* This is an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information
* for a single Universe. */
#define OpNzs 0x5100
/* This is an ArtAddress packet. It contains remote programming information for a Node. */
#define OpAddress 0x6000
/* This is an ArtInput packet. It contains enable disable data for DMX inputs. */
#define OpInput 0x7000
/* This is an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM
* discovery. */
#define OpTodRequest 0x8000
/* This is an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery. */
#define OpTodData 0x8100
/* This is an ArtTodControl packet. It is used to send RDM discovery control messages. */
#define OpTodControl 0x8200
/* This is an ArtRdm packet. It is used to send all non discovery RDM messages. */
#define OpRdm 0x8300
/* This is an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data. */
#define OpRdmSub 0x8400
/* This is an ArtVideoSetup packet. It contains video screen setup information for nodes that
* implement the extended video features. */
#define OpVideoSetup 0xa010
/* This is an ArtVideoPalette packet. It contains colour palette setup information for nodes that
* implement the extended video features. */
#define OpVideoPalette 0xa020
/* This is an ArtVideoData packet. It contains display data for nodes that implement the extended
* video features. */
#define OpVideoData 0xa040
/* This is an ArtMacMaster packet. It is used to program the Nodes 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__

View File

@ -0,0 +1,26 @@
#if !defined(__PROTOCOL_SETTINGS_H__)
#define __PROTOCOL_SETTINGS_H__
// UDP port
#define ARTNET_PORT 6454
// Buffers
#define ARTNET_MAX_BUFFER 530
#define DMX_MAX_BUFFER 512
// Packet constants
#define ARTNET_ID "Art-Net"
#define ARTNET_DMX_START_LOC 18
// Packet confines
#define ARTNET_SHORT_NAME_MAX_LENGTH 17
#define ARTNET_LONG_NAME_MAX_LENGTH 63
// DMX settings
#define DMX_MAX_OUTPUTS 4
#define DMX_MS_BETWEEN_TICKS 25
// RDM
#define DMX_RDM_STARTCODE 0xCC
#endif // __PROTOCOL_SETTINGS_H__

View File

@ -0,0 +1,159 @@
#include "rpi_microphone.h"
#include <alsa/asoundlib.h>
#include <stdio.h>
#define CHANNELS_COUNT 1
#define SAMPLE_RATE 44100
snd_pcm_t *capture_handle;
int write_to_fftw_input_buffers(int16_t frames, int16_t buf[frames * 2], audio_data_t *audio);
void *microphone_listen(void *arg) {
audio_data_t *audio = (audio_data_t *)arg;
microphone_setup(audio);
while (1) {
microphone_exec(audio);
}
}
void microphone_setup(audio_data_t *audio) {
int err;
snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t frames = audio->FFTtreblebufferSize;
unsigned int sampleRate = SAMPLE_RATE;
if ((err = snd_pcm_open(&capture_handle, audio->source, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
fprintf(stderr, "cannot open audio device %s (%s)\n", audio->source, snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, CHANNELS_COUNT)) < 0) {
fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &sampleRate, NULL)) < 0) {
fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &frames, NULL)) <
0) {
fprintf(stderr, "cannot set period size (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
exit(1);
}
snd_pcm_hw_params_get_rate(hw_params, &audio->rate, NULL);
snd_pcm_hw_params_free(hw_params);
if ((err = snd_pcm_prepare(capture_handle)) < 0) {
fprintf(stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror(err));
exit(1);
}
}
void microphone_exec(audio_data_t *audio) {
int err;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
snd_pcm_uframes_t frames = audio->FFTtreblebufferSize;
snd_pcm_get_params(capture_handle, &buffer_size, &period_size);
int16_t buf[period_size];
frames = period_size / 2;
err = snd_pcm_readi(capture_handle, buf, frames);
if (err == -EPIPE) {
/* EPIPE means overrun */
fprintf(stderr, "overrun occurred\n");
snd_pcm_prepare(capture_handle);
} else if (err < 0) {
fprintf(stderr, "error from read: %s\n", snd_strerror(err));
} else if (err != (int)frames) {
fprintf(stderr, "short read, read %d %d frames\n", err, (int)frames);
}
pthread_mutex_lock(&audio->lock);
write_to_fftw_input_buffers(frames, buf, audio);
pthread_mutex_unlock(&audio->lock);
}
void reset_output_buffers(audio_data_t *data) {
memset(data->in_bass_l, 0, sizeof(double) * data->FFTbassbufferSize);
memset(data->in_mid_l, 0, sizeof(double) * data->FFTmidbufferSize);
memset(data->in_treble_l, 0, sizeof(double) * data->FFTtreblebufferSize);
memset(data->in_bass_l_raw, 0, sizeof(double) * data->FFTbassbufferSize);
memset(data->in_mid_l_raw, 0, sizeof(double) * data->FFTmidbufferSize);
memset(data->in_treble_l_raw, 0, sizeof(double) * data->FFTtreblebufferSize);
}
int write_to_fftw_input_buffers(int16_t frames, int16_t buf[frames * 2], audio_data_t *audio) {
if (frames == 0)
return 0;
for (uint16_t n = audio->FFTbassbufferSize; n > frames; n = n - frames) {
for (uint16_t i = 1; i <= frames; i++) {
audio->in_bass_l_raw[n - i] = audio->in_bass_l_raw[n - i - frames];
}
}
for (uint16_t n = audio->FFTmidbufferSize; n > frames; n = n - frames) {
for (uint16_t i = 1; i <= frames; i++) {
audio->in_mid_l_raw[n - i] = audio->in_mid_l_raw[n - i - frames];
}
}
for (uint16_t n = audio->FFTtreblebufferSize; n > frames; n = n - frames) {
for (uint16_t i = 1; i <= frames; i++) {
audio->in_treble_l_raw[n - i] = audio->in_treble_l_raw[n - i - frames];
}
}
uint16_t n = frames - 1;
for (uint16_t i = 0; i < frames; i++) {
audio->in_bass_l_raw[n] = buf[i * 2];
audio->in_mid_l_raw[n] = audio->in_bass_l_raw[n];
audio->in_treble_l_raw[n] = audio->in_bass_l_raw[n];
n--;
}
// Hann Window
for (int i = 0; i < audio->FFTbassbufferSize; i++) {
audio->in_bass_l[i] = audio->bass_multiplier[i] * audio->in_bass_l_raw[i];
}
for (int i = 0; i < audio->FFTmidbufferSize; i++) {
audio->in_mid_l[i] = audio->mid_multiplier[i] * audio->in_mid_l_raw[i];
}
for (int i = 0; i < audio->FFTtreblebufferSize; i++) {
audio->in_treble_l[i] = audio->treble_multiplier[i] * audio->in_treble_l_raw[i];
}
return 0;
}

View File

@ -0,0 +1,42 @@
#if !defined(__RPI_MICROPHONE_H__)
#define __RPI_MICROPHONE_H__
#include <pthread.h>
#include <stdbool.h>
typedef struct {
int FFTbassbufferSize;
int FFTmidbufferSize;
int FFTtreblebufferSize;
int bass_index;
int mid_index;
int treble_index;
double *bass_multiplier;
double *mid_multiplier;
double *treble_multiplier;
double *in_bass_l_raw;
double *in_mid_l_raw;
double *in_treble_l_raw;
double *in_bass_l;
double *in_mid_l;
double *in_treble_l;
int format;
unsigned int rate;
char *source; // alsa device, fifo path or pulse source
int im; // input mode alsa, fifo or pulse
unsigned int channels;
bool left, right, average;
int terminate; // shared variable used to terminate audio thread
char error_message[1024];
pthread_mutex_t lock;
} audio_data_t;
void *microphone_listen(void *arg);
void microphone_setup(audio_data_t *audio);
void microphone_exec(audio_data_t *audio);
void reset_output_buffers(audio_data_t *data);
#endif /* __RPI_MICROPHONE_H__ */

View File

@ -0,0 +1,447 @@
#include "rpi_spectrum.h"
#include <fftw3.h>
#include <math.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "rpi_microphone.h"
typedef struct {
int framerate;
int number_of_bars;
int lower_cut_off, upper_cut_off;
} param_t;
typedef struct {
double sens;
double gravity;
double integral;
double userEQ[128];
} config_t;
param_t param = {
.framerate = 60, .number_of_bars = 128, .lower_cut_off = 50, .upper_cut_off = 10000};
config_t conf = {.sens = 1, .gravity = 0, .integral = 0, .userEQ = {1}};
pthread_t microphoneListener;
audio_data_t audio;
fftw_complex *out_bass_l;
fftw_plan p_bass_l;
fftw_complex *out_mid_l;
fftw_plan p_mid_l;
fftw_complex *out_treble_l;
fftw_plan p_treble_l;
// input: init
int *bars_left;
double *temp_l;
int bass_cut_off = 150;
int treble_cut_off = 2500;
void init_plan(int bufferSize, double **in, double **in_raw, fftw_complex **out, fftw_plan *p);
void spectrum_start_bg_worker();
void spectrum_stop_bg_worker();
void spectrum_setup(char *audio_source) {
for (size_t i = 0; i < 128; i++) {
conf.userEQ[i] = 1;
}
memset(&audio, 0, sizeof(audio));
audio.source = malloc(1 + strlen(audio_source));
strcpy(audio.source, audio_source);
audio.format = -1;
audio.rate = 0;
audio.FFTbassbufferSize = 4096;
audio.FFTmidbufferSize = 2048;
audio.FFTtreblebufferSize = 1024;
audio.terminate = 0;
audio.channels = 1;
audio.average = false;
audio.left = true;
audio.right = false;
audio.bass_index = 0;
audio.mid_index = 0;
audio.treble_index = 0;
audio.bass_multiplier = (double *)malloc(audio.FFTbassbufferSize * sizeof(double));
audio.mid_multiplier = (double *)malloc(audio.FFTmidbufferSize * sizeof(double));
audio.treble_multiplier = (double *)malloc(audio.FFTtreblebufferSize * sizeof(double));
temp_l = (double *)malloc((audio.FFTbassbufferSize / 2 + 1) * sizeof(double));
bars_left = (int *)malloc(256 * sizeof(int));
for (int i = 0; i < audio.FFTbassbufferSize; i++) {
audio.bass_multiplier[i] = 0.5 * (1 - cos(2 * M_PI * i / (audio.FFTbassbufferSize - 1)));
}
for (int i = 0; i < audio.FFTmidbufferSize; i++) {
audio.mid_multiplier[i] = 0.5 * (1 - cos(2 * M_PI * i / (audio.FFTmidbufferSize - 1)));
}
for (int i = 0; i < audio.FFTtreblebufferSize; i++) {
audio.treble_multiplier[i] = 0.5 * (1 - cos(2 * M_PI * i / (audio.FFTtreblebufferSize - 1)));
}
// BASS
// audio.FFTbassbufferSize = audio.rate / 20; // audio.FFTbassbufferSize;
// audio.in_bass_l = fftw_alloc_real(audio.FFTbassbufferSize);
// audio.in_bass_l_raw = fftw_alloc_real(audio.FFTbassbufferSize);
// out_bass_l = fftw_alloc_complex(audio.FFTbassbufferSize / 2 + 1);
// memset(out_bass_l, 0, (audio.FFTbassbufferSize / 2 + 1) * sizeof(fftw_complex));
// p_bass_l =
// fftw_plan_dft_r2c_1d(audio.FFTbassbufferSize, audio.in_bass_l, out_bass_l, FFTW_MEASURE);
init_plan(audio.FFTbassbufferSize, &audio.in_bass_l, &audio.in_bass_l_raw, &out_bass_l,
&p_bass_l);
// MID
// audio.FFTmidbufferSize = audio.rate / bass_cut_off; // audio.FFTbassbufferSize;
// audio.in_mid_l = fftw_alloc_real(audio.FFTmidbufferSize);
// audio.in_mid_l_raw = fftw_alloc_real(audio.FFTmidbufferSize);
// out_mid_l = fftw_alloc_complex(audio.FFTmidbufferSize / 2 + 1);
// memset(out_mid_l, 0, (audio.FFTmidbufferSize / 2 + 1) * sizeof(fftw_complex));
// p_mid_l = fftw_plan_dft_r2c_1d(audio.FFTmidbufferSize, audio.in_mid_l, out_mid_l,
// FFTW_MEASURE);
init_plan(audio.FFTmidbufferSize, &audio.in_mid_l, &audio.in_mid_l_raw, &out_mid_l, &p_mid_l);
// TRIEBLE
// audio.FFTtreblebufferSize = audio.rate / treble_cut_off; // audio.FFTbassbufferSize;
// audio.in_treble_l = fftw_alloc_real(audio.FFTtreblebufferSize);
// audio.in_treble_l_raw = fftw_alloc_real(audio.FFTtreblebufferSize);
// out_treble_l = fftw_alloc_complex(audio.FFTtreblebufferSize / 2 + 1);
// memset(out_treble_l, 0, (audio.FFTtreblebufferSize / 2 + 1) * sizeof(fftw_complex));
// p_treble_l = fftw_plan_dft_r2c_1d(audio.FFTtreblebufferSize, audio.in_treble_l,
// out_treble_l,
// FFTW_MEASURE);
init_plan(audio.FFTtreblebufferSize, &audio.in_treble_l, &audio.in_treble_l_raw, &out_treble_l,
&p_treble_l);
reset_output_buffers(&audio);
spectrum_start_bg_worker();
}
void spectrum_start_bg_worker() {
pthread_mutex_init(&audio.lock, NULL);
if (pthread_create(&microphoneListener, NULL, microphone_listen, (void *)&audio) < 0) {
perror("pthread_create");
}
int timeout_counter = 0;
while (audio.rate == 0) {
usleep(2000);
++timeout_counter;
if (timeout_counter > 2000) {
fprintf(stderr, "could not get rate and/or format, problems with audio thread? "
"quiting...\n");
exit(EXIT_FAILURE);
}
}
}
void spectrum_stop_bg_worker() {
if (pthread_cancel(microphoneListener) != 0) {
perror("pthread_cancel");
}
if (pthread_join(microphoneListener, NULL) != 0) {
perror("pthread_join");
}
}
void spectrum_execute() {
int bars[256];
int bars_mem[256];
int bars_last[256];
int previous_frame[256];
int fall[256];
float bars_peak[256];
int height, lines, width, remainder, fp;
bool reloadConf = false;
for (int n = 0; n < 256; n++) {
bars_last[n] = 0;
previous_frame[n] = 0;
fall[n] = 0;
bars_peak[n] = 0;
bars_mem[n] = 0;
bars[n] = 0;
}
width = 256;
height = UINT16_MAX;
// getting numbers of bars
int number_of_bars = param.number_of_bars;
if (number_of_bars > 256)
number_of_bars = 256; // cant have more than 256 bars
// process [smoothing]: calculate gravity
float g = conf.gravity * ((float)height / 2160) * pow((60 / (float)param.framerate), 2.5);
// calculate integral value, must be reduced with height
double integral = conf.integral;
if (height > 320)
integral = conf.integral * 1 / sqrt((log10((float)height / 10)));
// process: calculate cutoff frequencies and eq
double userEQ_keys_to_bars_ratio;
if (number_of_bars > 0) {
userEQ_keys_to_bars_ratio = (double)(((double)(number_of_bars < 128 ? number_of_bars : 128)) /
((double)number_of_bars));
}
// calculate frequency constant (used to distribute bars across the frequency band)
double frequency_constant = log10((float)param.lower_cut_off / (float)param.upper_cut_off) /
(1 / ((float)number_of_bars + 1) - 1);
float cut_off_frequency[256];
float upper_cut_off_frequency[256];
float relative_cut_off[256];
double center_frequencies[256];
int FFTbuffer_lower_cut_off[256];
int FFTbuffer_upper_cut_off[256];
double eq[256];
int bass_cut_off_bar = -1;
int treble_cut_off_bar = -1;
bool first_bar = true;
int first_treble_bar = 0;
int bar_buffer[number_of_bars + 1];
for (int n = 0; n < number_of_bars + 1; n++) {
double bar_distribution_coefficient = frequency_constant * (-1);
bar_distribution_coefficient +=
((float)n + 1) / ((float)number_of_bars + 1) * frequency_constant;
cut_off_frequency[n] = param.upper_cut_off * pow(10, bar_distribution_coefficient);
if (n > 0) {
if (cut_off_frequency[n - 1] >= cut_off_frequency[n] &&
cut_off_frequency[n - 1] > bass_cut_off)
cut_off_frequency[n] =
cut_off_frequency[n - 1] + (cut_off_frequency[n - 1] - cut_off_frequency[n - 2]);
}
relative_cut_off[n] = cut_off_frequency[n] / (audio.rate / 2);
// remember nyquist!, per my calculations this should be rate/2
// and nyquist freq in M/2 but testing shows it is not...
// or maybe the nq freq is in M/4
eq[n] = pow(cut_off_frequency[n], 1);
// the numbers that come out of the FFT are very high
// the EQ is used to "normalize" them by dividing with this very huge number
eq[n] *= (float)height / pow(2, 28);
eq[n] *= conf.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)];
eq[n] /= log2(audio.FFTbassbufferSize);
if (cut_off_frequency[n] < bass_cut_off) {
// BASS
bar_buffer[n] = 1;
FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTbassbufferSize / 2);
bass_cut_off_bar++;
treble_cut_off_bar++;
if (bass_cut_off_bar > 0)
first_bar = false;
eq[n] *= log2(audio.FFTbassbufferSize);
} else if (cut_off_frequency[n] > bass_cut_off && cut_off_frequency[n] < treble_cut_off) {
// MID
bar_buffer[n] = 2;
FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTmidbufferSize / 2);
treble_cut_off_bar++;
if ((treble_cut_off_bar - bass_cut_off_bar) == 1) {
first_bar = true;
FFTbuffer_upper_cut_off[n - 1] = relative_cut_off[n] * (audio.FFTbassbufferSize / 2);
} else {
first_bar = false;
}
eq[n] *= log2(audio.FFTmidbufferSize);
} else {
// TREBLE
bar_buffer[n] = 3;
FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTtreblebufferSize / 2);
first_treble_bar++;
if (first_treble_bar == 1) {
first_bar = true;
FFTbuffer_upper_cut_off[n - 1] = relative_cut_off[n] * (audio.FFTmidbufferSize / 2);
} else {
first_bar = false;
}
eq[n] *= log2(audio.FFTtreblebufferSize);
}
if (n > 0) {
if (!first_bar) {
FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1;
// pushing the spectrum up if the exponential function gets "clumped" in the
// bass and caluclating new cut off frequencies
if (FFTbuffer_lower_cut_off[n] <= FFTbuffer_lower_cut_off[n - 1]) {
FFTbuffer_lower_cut_off[n] = FFTbuffer_lower_cut_off[n - 1] + 1;
FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1;
if (bar_buffer[n] == 1)
relative_cut_off[n] =
(float)(FFTbuffer_lower_cut_off[n]) / ((float)audio.FFTbassbufferSize / 2);
else if (bar_buffer[n] == 2)
relative_cut_off[n] =
(float)(FFTbuffer_lower_cut_off[n]) / ((float)audio.FFTmidbufferSize / 2);
else if (bar_buffer[n] == 3)
relative_cut_off[n] =
(float)(FFTbuffer_lower_cut_off[n]) / ((float)audio.FFTtreblebufferSize / 2);
cut_off_frequency[n] = relative_cut_off[n] * ((float)audio.rate / 2);
}
} else {
if (FFTbuffer_upper_cut_off[n - 1] <= FFTbuffer_lower_cut_off[n - 1])
FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n - 1] + 1;
}
upper_cut_off_frequency[n - 1] =
cut_off_frequency[n]; // high_relative_cut_off * ((float)audio.rate / 2);
center_frequencies[n - 1] =
pow((cut_off_frequency[n - 1] * upper_cut_off_frequency[n - 1]), 0.5);
}
}
// process: execute FFT and sort frequency bands
pthread_mutex_lock(&audio.lock);
fftw_execute(p_bass_l);
fftw_execute(p_mid_l);
fftw_execute(p_treble_l);
pthread_mutex_unlock(&audio.lock);
// process: separate frequency bands
for (int n = 0; n < number_of_bars; n++) {
temp_l[n] = 0;
// process: add upp FFT values within bands
for (int i = FFTbuffer_lower_cut_off[n]; i <= FFTbuffer_upper_cut_off[n]; i++) {
if (n <= bass_cut_off_bar) {
temp_l[n] += hypot(out_bass_l[i][0], out_bass_l[i][1]);
} else if (n > bass_cut_off_bar && n <= treble_cut_off_bar) {
temp_l[n] += hypot(out_mid_l[i][0], out_mid_l[i][1]);
} else if (n > treble_cut_off_bar) {
temp_l[n] += hypot(out_treble_l[i][0], out_treble_l[i][1]);
}
}
// getting average multiply with sens and eq
temp_l[n] /= FFTbuffer_upper_cut_off[n] - FFTbuffer_lower_cut_off[n] + 1;
temp_l[n] *= conf.sens * eq[n];
bars_left[n] = temp_l[n];
}
// process [filter]
// if (p.monstercat) {
// if (p.stereo) {
// bars_left = monstercat_filter(bars_left, number_of_bars / 2, p.waves, p.monstercat);
// bars_right = monstercat_filter(bars_right, number_of_bars / 2, p.waves, p.monstercat);
// } else {
// bars_left = monstercat_filter(bars_left, number_of_bars, p.waves, p.monstercat);
// }
// }
// processing signal
// bool senselow = true;
for (int n = 0; n < number_of_bars; n++) {
// // mirroring stereo channels
// if (p.stereo) {
// if (n < number_of_bars / 2) {
// bars[n] = bars_left[number_of_bars / 2 - n - 1];
// } else {
// bars[n] = bars_right[n - number_of_bars / 2];
// }
// } else {
bars[n] = bars_left[n];
}
// process [smoothing]: falloff
// if (g > 0) {
// if (bars[n] < bars_last[n]) {
// bars[n] = bars_peak[n] - (g * fall[n] * fall[n]);
// if (bars[n] < 0)
// bars[n] = 0;
// fall[n]++;
// } else {
// bars_peak[n] = bars[n];
// fall[n] = 0;
// }
// bars_last[n] = bars[n];
// }
// process [smoothing]: integral
// if (p.integral > 0) {
// bars[n] = bars_mem[n] * integral + bars[n];
// bars_mem[n] = bars[n];
// int diff = height - bars[n];
// if (diff < 0)
// diff = 0;
// double div = 1 / (diff + 1);
// // bars[n] = bars[n] - pow(div, 10) * (height + 1);
// bars_mem[n] = bars_mem[n] * (1 - div / 20);
// }
memcpy(previous_frame, bars, 256 * sizeof(int));
for (size_t i = 0; i < number_of_bars; i++) {
printf("%5d, ", bars[i]);
}
printf("\n");
}
int *spectrum_display_bars() { return bars_left; }
void init_plan(int bufferSize, double **in, double **in_raw, fftw_complex **out, fftw_plan *p) {
*in = fftw_alloc_real(bufferSize);
*in_raw = fftw_alloc_real(bufferSize);
*out = fftw_alloc_complex(bufferSize / 2 + 1);
memset(*out, 0, (bufferSize / 2 + 1) * sizeof(fftw_complex));
*p = fftw_plan_dft_r2c_1d(bufferSize, *in, *out, FFTW_MEASURE);
}
void compute_param() {}
void compute_config() {}

View File

@ -0,0 +1,14 @@
#if !defined(__RPI_SPECTRUM_H__)
#define __RPI_SPECTRUM_H__
void spectrum_setup(char *audio_source);
void spectrum_start_bg_worker();
void spectrum_stop_bg_worker();
void spectrum_execute();
int *spectrum_get_bars();
#endif /* __RPI_SPECTRUM_H__ */

View File

@ -0,0 +1,15 @@
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
int16_t buf_16;
int8_t buf_8;
int buffer[200];
int print_raw_out(int bars_count, int fd, int is_binary, int bit_format, int ascii_range,
char bar_delim, char frame_delim, int const f[200]) {
memcpy(buffer, f, sizeof(int) * bars_count);
return 0;
}

View File

@ -0,0 +1,2 @@
int print_raw_out(int bars_count, int fd, int is_binary, int bit_format, int ascii_range,
char bar_delim, char frame_delim, int const f[200]);

View File

@ -0,0 +1,37 @@
#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);
}
}

View File

@ -0,0 +1,14 @@
#if !defined(__VIRTUAL_MEMORY_H__)
#define __VIRTUAL_MEMORY_H__
class VitualMemory {
public:
VitualMemory(void *addr, int size);
~VitualMemory();
private:
int roundSize;
void *mem;
};
#endif // __VIRTUAL_MEMORY_H__

View File

@ -0,0 +1,4 @@
// Size of memory page
#define PAGE_SIZE 0x1000
// Round up to nearest page
#define PAGE_ROUNDUP(n) ((n) % PAGE_SIZE == 0 ? (n) : ((n) + PAGE_SIZE) & ~(PAGE_SIZE - 1))

View File

@ -0,0 +1,8 @@
int main(int argc, char const *argv[])
{
return 0;
}

View File

@ -0,0 +1,93 @@
#include "rpi_cava.h"
#include <errno.h>
#include <fcntl.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
pid_t cavaPid;
pthread_t fifoReader;
int cavaFifo;
uint16_t buffer[20];
static void *fifo_to_buffer(void *arg);
void setup_cava() {}
void close_cava() { stop_cava_bg_worker(); }
void start_cava_bg_worker() {
if ((cavaPid = fork()) == -1) {
perror("fork");
exit(1);
}
if (cavaPid == 0) {
/* Child process*/
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);
if ((cavaFifo = open("/tmp/cava_output", O_RDONLY)) < 0) {
perror("open");
close_cava();
exit(1);
}
pthread_create(&fifoReader, NULL, fifo_to_buffer, NULL);
}
}
void stop_cava_bg_worker() {
if (pthread_cancel(fifoReader) != 0) {
perror("pthread_cancel");
}
if (pthread_join(fifoReader, NULL) != 0) {
perror("pthread_join");
}
close(cavaFifo);
kill(cavaPid, SIGTERM);
}
int get_cava_buffer(uint16_t buffer_dst[20]) {
memcpy(buffer_dst, buffer, 40);
return 0;
}
static void *fifo_to_buffer(void *arg) {
while (1) {
int totalRead = 0;
while (totalRead < 40) {
int nread;
nread = read(cavaFifo, ((uint8_t *)buffer) + totalRead, 40 - totalRead);
if (nread >= 0) {
totalRead += nread;
} else {
if (errno != EAGAIN) {
perror("read");
}
}
}
printf("data[%d] : ", totalRead);
for (size_t i = 0; i < 20; i++) {
printf("%5u;", buffer[i]);
}
printf("\n");
}
return NULL;
}

View 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[20]);
void close_cava();
void start_cava_bg_worker();
void stop_cava_bg_worker();
#endif /* __RPI_CAVA_H__ */

View File

@ -0,0 +1,103 @@
#include "rpi_microphone.h"
#include <alsa/asoundlib.h>
#include <fftw3.h>
#include <limits.h>
#include <stdlib.h>
#define CHANNELS_COUNT 1
#define SAMPLE_RATE 44100
snd_pcm_t *capture_handle;
char const *deviceName = "plughw:1";
void setup_microphone() {
int err;
snd_pcm_hw_params_t *hw_params;
unsigned int rate = 44100;
if ((err = snd_pcm_open(&capture_handle, deviceName, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
fprintf(stderr, "cannot open audio device %s (%s)\n", deviceName, snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0) {
fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, 2)) < 0) {
fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
exit(1);
}
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
exit(1);
}
snd_pcm_hw_params_free(hw_params);
if ((err = snd_pcm_prepare(capture_handle)) < 0) {
fprintf(stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror(err));
exit(1);
}
return;
}
#define BUFFERSIZE 1024
short buf[BUFFERSIZE];
void read_microphone() {
int err;
short max = SHRT_MIN;
if ((err = snd_pcm_readi(capture_handle, buf, BUFFERSIZE)) != BUFFERSIZE) {
fprintf(stderr, "read from audio interface failed (%s)\n", snd_strerror(err));
} else {
for (size_t i = 0; i < BUFFERSIZE; ++i) {
if (buf[i] > max) {
max = buf[i];
}
}
printf("%d, ", max);
printf("\n");
}
}
void fft() {
// fftw_complex *in, *out;
// fftw_plan my_plan;
// in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
// out = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
// my_plan = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
// fftw_execute(my_plan); /* repeat as needed */
// fftw_destroy_plan(my_plan);
// fftw_free(in);
// fftw_free(out);
// return;
}
void close_microphone() { snd_pcm_close(capture_handle); }

View File

@ -0,0 +1,10 @@
#if !defined(__RPI_MICROPHONE_H__)
#define __RPI_MICROPHONE_H__
void setup_microphone();
void read_microphone();
void close_microphone();
#endif /* __RPI_MICROPHONE_H__ */

View File

@ -0,0 +1,15 @@
#if !defined(__RPI_PIXLEDS_H__)
#define __RPI_PIXLEDS_H__
void rgb_txdata(int *rgbs, TXDATA_T *txd);
int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan);
void swap_bytes(void *data, int len);
int hexdig(char c);
void map_devices(void);
void fail(char *s);
void terminate(int sig);
void init_smi(int width, int ns, int setup, int hold, int strobe);
void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T *txdata);
void start_smi(MEM_MAP *mp);
#endif // __RPI_PIXLEDS_H__