diff --git a/RpiLedBars/.vscode/launch.json b/RpiLedBars/.vscode/launch.json index fd94da6..393334c 100644 --- a/RpiLedBars/.vscode/launch.json +++ b/RpiLedBars/.vscode/launch.json @@ -11,7 +11,7 @@ "program": "${workspaceFolder}/bin/pixled", "args": [ "-n", - "5", + "60", // "-t" ], "stopAtEntry": false, @@ -27,7 +27,10 @@ } ], "miDebuggerPath": "${workspaceFolder}/sgdb.sh", - "preLaunchTask": "${defaultBuildTask}" + "preLaunchTask": "${defaultBuildTask}", + "logging": { + "engineLogging": true + } } ] } \ No newline at end of file diff --git a/RpiLedBars/Makefile b/RpiLedBars/Makefile index 6e0de35..d7cb743 100644 --- a/RpiLedBars/Makefile +++ b/RpiLedBars/Makefile @@ -1,24 +1,24 @@ -CC=gcc -CFLAGS=-Wall -g -D_DEBUG -LDFLAGS=-lwiringPi #-lpthread -SRCDIR=src -OBJDIR=obj -BINDIR=bin -SRC=$(notdir $(wildcard $(SRCDIR)/*.c)) -OBJ=$(SRC:.c=.o) -BIN=pixled +CC := gcc +CFLAGS := -Wall -g -D_DEBUG +LDFLAGS := -lwiringPi -lasound -lfftw3 -lpthread -lm -all: $(addprefix $(BINDIR)/, $(BIN)) +SRC := src +OBJ := obj +BIN := bin/pixled -$(OBJDIR)/%.o: $(SRCDIR)/%.c - if [ ! -d $(OBJDIR) ]; then mkdir "$(OBJDIR)"; fi - $(CC) -c -o $@ $< $(CFLAGS) +# SOURCES := $(wildcard $(SRC)/*.c) +SOURCES := $(shell find $(SRC) -type f -name "*.c") +OBJECTS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SOURCES)) + +all: $(BIN) + +$(OBJ)/%.o: $(SRC)/%.c + if [ ! -d "$(dir $@)" ]; then mkdir -p "$(dir $@)"; fi + $(CC) -I$(SRC) -c $< -o $@ $(CFLAGS) -$(BINDIR)/$(BIN) : $(addprefix $(OBJDIR)/, $(OBJ)) - if [ ! -d "$(BINDIR)" ]; then mkdir "$(BINDIR)"; fi +$(BIN) : $(OBJECTS) + if [ ! -d "$(dir $(BIN))" ]; then mkdir -p "$(dir $(BIN))"; fi $(CC) -o $@ $^ $(LDFLAGS) clean: - rm -rf $(BINDIR)/* $(OBJDIR)/* - if [ -d $(OBJDIR) ]; then rmdir "$(OBJDIR)"; fi - if [ -d "$(BINDIR)" ]; then rmdir "$(BINDIR)"; fi \ No newline at end of file + rm -rf $(OBJ) \ No newline at end of file diff --git a/RpiLedBars/cava_config b/RpiLedBars/cava_config new file mode 100644 index 0000000..b3c7247 --- /dev/null +++ b/RpiLedBars/cava_config @@ -0,0 +1,25 @@ +[general] +framerate = 60 +autosens = 0 +sensitivity = 200 +bars = 128 + +[input] +method = alsa +source = plughw:1 + +[output] +method = raw +channels = mono +mono_option = left +raw_target = /tmp/cava_output +data_format = ascii +ascii_max_range=65535 +bit_format = 16bit + +[smoothing] +integral = 0 +monstercat = 1 +waves = 0 +gravity = 0 + diff --git a/RpiLedBars/cava_test b/RpiLedBars/cava_test new file mode 100644 index 0000000..24f246c --- /dev/null +++ b/RpiLedBars/cava_test @@ -0,0 +1,25 @@ +[general] +framerate = 60 +autosens = 0 +sensitivity = 200 +bars = 20 + +[input] +method = alsa +source = plughw:1 + +[output] +method = raw +channels = mono +mono_option = left +;raw_target = /tmp/cava_output +data_format = ascii +ascii_max_range=65535 +bit_format = 16bit + +[smoothing] +integral = 0 +monstercat = 0 +waves = 0 +gravity = 0 + diff --git a/RpiLedBars/file.wav b/RpiLedBars/file.wav new file mode 100644 index 0000000..9aa2a94 Binary files /dev/null and b/RpiLedBars/file.wav differ diff --git a/RpiLedBars/install.sh b/RpiLedBars/install.sh new file mode 100755 index 0000000..c3dbf49 --- /dev/null +++ b/RpiLedBars/install.sh @@ -0,0 +1,5 @@ +# make +cp ./bin/pixled bin/service_pixled +sudo -s bash -c "cp pixled.service /etc/systemd/system; systemctl daemon-reload" +# sudo -s bash -c "systemctl enable pixled" +sudo -s bash -c "systemctl restart pixled" \ No newline at end of file diff --git a/RpiLedBars/pixled.service b/RpiLedBars/pixled.service new file mode 100644 index 0000000..dc428da --- /dev/null +++ b/RpiLedBars/pixled.service @@ -0,0 +1,12 @@ +[Unit] +Description=pixled +After=network.target + +[Service] +ExecStart=/home/pi/LedBars/RpiLedBars/bin/service_pixled -n 60 +Restart=always +User=root +Group=root + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/RpiLedBars/res/.asoundrc b/RpiLedBars/res/.asoundrc new file mode 100644 index 0000000..6d1ca12 --- /dev/null +++ b/RpiLedBars/res/.asoundrc @@ -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 +} + + diff --git a/RpiLedBars/res/install_i2s_mems_mic b/RpiLedBars/res/install_i2s_mems_mic new file mode 100644 index 0000000..7b9e69e --- /dev/null +++ b/RpiLedBars/res/install_i2s_mems_mic @@ -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 \ No newline at end of file diff --git a/RpiLedBars/src/rpi_dma_utils.c b/RpiLedBars/res/pixled/rpi_dma_utils.c similarity index 99% rename from RpiLedBars/src/rpi_dma_utils.c rename to RpiLedBars/res/pixled/rpi_dma_utils.c index 6b814ae..476d207 100644 --- a/RpiLedBars/src/rpi_dma_utils.c +++ b/RpiLedBars/res/pixled/rpi_dma_utils.c @@ -80,6 +80,7 @@ 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; diff --git a/RpiLedBars/src/rpi_dma_utils.h b/RpiLedBars/res/pixled/rpi_dma_utils.h similarity index 100% rename from RpiLedBars/src/rpi_dma_utils.h rename to RpiLedBars/res/pixled/rpi_dma_utils.h diff --git a/RpiLedBars/src/rpi_pixleds.c b/RpiLedBars/res/pixled/rpi_pixleds.c similarity index 97% rename from RpiLedBars/src/rpi_pixleds.c rename to RpiLedBars/res/pixled/rpi_pixleds.c index 68873ec..eef1ccb 100644 --- a/RpiLedBars/src/rpi_pixleds.c +++ b/RpiLedBars/res/pixled/rpi_pixleds.c @@ -32,17 +32,13 @@ #include "rpi_pixleds.h" -#include #include -#include #include #include #include #include -#include #include -#include "rpi_artnet.h" #include "rpi_smi_defs.h" // Structures for mapped I/O devices, and non-volatile memory @@ -103,7 +99,7 @@ int chan_num; // chan_num++; // } // } -// signal(SIGINT, terminate); +// 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); @@ -163,7 +159,7 @@ int chan_num; // } // } // } -// terminate(0); +// terminate_smi(0); // } // Convert RGB text string into integer data, for given channel @@ -240,7 +236,7 @@ int hexdig(char c) { } // Map GPIO, DMA and SMI registers into virtual mem (user space) -// If any of these fail, program will be terminated +// 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); @@ -251,11 +247,11 @@ void map_devices(void) { // Catastrophic failure in initial setup void fail(char *s) { printf(s); - terminate(0); + terminate_smi(0); } // Free memory segments and exit -void terminate(int sig) { +void terminate_smi(int sig) { int i; printf("Closing\n"); if (gpio_regs.virt) { @@ -274,7 +270,8 @@ void terminate(int sig) { // 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) { +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); diff --git a/RpiLedBars/src/rpi_pixleds.h b/RpiLedBars/res/pixled/rpi_pixleds.h similarity index 90% rename from RpiLedBars/src/rpi_pixleds.h rename to RpiLedBars/res/pixled/rpi_pixleds.h index 344bd96..18beb1c 100644 --- a/RpiLedBars/src/rpi_pixleds.h +++ b/RpiLedBars/res/pixled/rpi_pixleds.h @@ -41,11 +41,6 @@ #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); @@ -54,8 +49,8 @@ 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 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); diff --git a/RpiLedBars/src/rpi_smi_defs.h b/RpiLedBars/res/pixled/rpi_smi_defs.h similarity index 99% rename from RpiLedBars/src/rpi_smi_defs.h rename to RpiLedBars/res/pixled/rpi_smi_defs.h index 80c452b..5745f8e 100644 --- a/RpiLedBars/src/rpi_smi_defs.h +++ b/RpiLedBars/res/pixled/rpi_smi_defs.h @@ -29,9 +29,6 @@ #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 { \ diff --git a/RpiLedBars/src/artnet/ArtnetnodeWifi.cpp b/RpiLedBars/res/src/artnet/ArtnetnodeWifi.cpp similarity index 100% rename from RpiLedBars/src/artnet/ArtnetnodeWifi.cpp rename to RpiLedBars/res/src/artnet/ArtnetnodeWifi.cpp diff --git a/RpiLedBars/src/artnet/ArtnetnodeWifi.h b/RpiLedBars/res/src/artnet/ArtnetnodeWifi.h similarity index 100% rename from RpiLedBars/src/artnet/ArtnetnodeWifi.h rename to RpiLedBars/res/src/artnet/ArtnetnodeWifi.h diff --git a/RpiLedBars/src/artnet/NodeReportCodes.h b/RpiLedBars/res/src/artnet/NodeReportCodes.h similarity index 100% rename from RpiLedBars/src/artnet/NodeReportCodes.h rename to RpiLedBars/res/src/artnet/NodeReportCodes.h diff --git a/RpiLedBars/src/artnet/PollReply.cpp b/RpiLedBars/res/src/artnet/PollReply.cpp similarity index 100% rename from RpiLedBars/src/artnet/PollReply.cpp rename to RpiLedBars/res/src/artnet/PollReply.cpp diff --git a/RpiLedBars/src/artnet/PollReply.h b/RpiLedBars/res/src/artnet/PollReply.h similarity index 100% rename from RpiLedBars/src/artnet/PollReply.h rename to RpiLedBars/res/src/artnet/PollReply.h diff --git a/RpiLedBars/src/artnet/artnet_op_codes.h b/RpiLedBars/res/src/artnet/artnet_op_codes.h similarity index 100% rename from RpiLedBars/src/artnet/artnet_op_codes.h rename to RpiLedBars/res/src/artnet/artnet_op_codes.h diff --git a/RpiLedBars/src/artnet/artnet_protocol_settings.h b/RpiLedBars/res/src/artnet/artnet_protocol_settings.h similarity index 100% rename from RpiLedBars/src/artnet/artnet_protocol_settings.h rename to RpiLedBars/res/src/artnet/artnet_protocol_settings.h diff --git a/RpiLedBars/res/src/cava/rpi_microphone.c b/RpiLedBars/res/src/cava/rpi_microphone.c new file mode 100644 index 0000000..9085577 --- /dev/null +++ b/RpiLedBars/res/src/cava/rpi_microphone.c @@ -0,0 +1,159 @@ +#include "rpi_microphone.h" + +#include +#include + +#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; +} diff --git a/RpiLedBars/res/src/cava/rpi_microphone.h b/RpiLedBars/res/src/cava/rpi_microphone.h new file mode 100644 index 0000000..bd241a9 --- /dev/null +++ b/RpiLedBars/res/src/cava/rpi_microphone.h @@ -0,0 +1,42 @@ +#if !defined(__RPI_MICROPHONE_H__) +#define __RPI_MICROPHONE_H__ + +#include +#include + +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__ */ \ No newline at end of file diff --git a/RpiLedBars/res/src/cava/rpi_spectrum.c b/RpiLedBars/res/src/cava/rpi_spectrum.c new file mode 100644 index 0000000..cae11d9 --- /dev/null +++ b/RpiLedBars/res/src/cava/rpi_spectrum.c @@ -0,0 +1,447 @@ +#include "rpi_spectrum.h" + +#include +#include +#include +#include +#include +#include +#include + +#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(µphoneListener, 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() {} \ No newline at end of file diff --git a/RpiLedBars/res/src/cava/rpi_spectrum.h b/RpiLedBars/res/src/cava/rpi_spectrum.h new file mode 100644 index 0000000..500d144 --- /dev/null +++ b/RpiLedBars/res/src/cava/rpi_spectrum.h @@ -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__ */ \ No newline at end of file diff --git a/RpiLedBars/res/src/output/raw.c b/RpiLedBars/res/src/output/raw.c new file mode 100644 index 0000000..f7a5737 --- /dev/null +++ b/RpiLedBars/res/src/output/raw.c @@ -0,0 +1,15 @@ +#include +#include +#include +#include + +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; +} diff --git a/RpiLedBars/res/src/output/raw.h b/RpiLedBars/res/src/output/raw.h new file mode 100644 index 0000000..f2f50cf --- /dev/null +++ b/RpiLedBars/res/src/output/raw.h @@ -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]); diff --git a/RpiLedBars/src/smi/VitualMemory.cpp b/RpiLedBars/res/src/smi/VitualMemory.cpp similarity index 100% rename from RpiLedBars/src/smi/VitualMemory.cpp rename to RpiLedBars/res/src/smi/VitualMemory.cpp diff --git a/RpiLedBars/src/smi/VitualMemory.hpp b/RpiLedBars/res/src/smi/VitualMemory.hpp similarity index 100% rename from RpiLedBars/src/smi/VitualMemory.hpp rename to RpiLedBars/res/src/smi/VitualMemory.hpp diff --git a/RpiLedBars/src/smi/utils.h b/RpiLedBars/res/src/smi/utils.h similarity index 100% rename from RpiLedBars/src/smi/utils.h rename to RpiLedBars/res/src/smi/utils.h diff --git a/RpiLedBars/src/tmp/main.c b/RpiLedBars/res/src/tmp/main.c similarity index 100% rename from RpiLedBars/src/tmp/main.c rename to RpiLedBars/res/src/tmp/main.c diff --git a/RpiLedBars/res/src/tmp/rpi_cava.c b/RpiLedBars/res/src/tmp/rpi_cava.c new file mode 100644 index 0000000..c8bbc0b --- /dev/null +++ b/RpiLedBars/res/src/tmp/rpi_cava.c @@ -0,0 +1,93 @@ +#include "rpi_cava.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#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; +} diff --git a/RpiLedBars/res/src/tmp/rpi_cava.h b/RpiLedBars/res/src/tmp/rpi_cava.h new file mode 100644 index 0000000..5c59e1a --- /dev/null +++ b/RpiLedBars/res/src/tmp/rpi_cava.h @@ -0,0 +1,16 @@ +#if !defined(__RPI_CAVA_H__) +#define __RPI_CAVA_H__ + +#include + +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__ */ \ No newline at end of file diff --git a/RpiLedBars/res/src/tmp/rpi_microphone.c b/RpiLedBars/res/src/tmp/rpi_microphone.c new file mode 100644 index 0000000..2a9036a --- /dev/null +++ b/RpiLedBars/res/src/tmp/rpi_microphone.c @@ -0,0 +1,103 @@ +#include "rpi_microphone.h" + +#include +#include +#include +#include + +#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); } \ No newline at end of file diff --git a/RpiLedBars/res/src/tmp/rpi_microphone.h b/RpiLedBars/res/src/tmp/rpi_microphone.h new file mode 100644 index 0000000..482014b --- /dev/null +++ b/RpiLedBars/res/src/tmp/rpi_microphone.h @@ -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__ */ \ No newline at end of file diff --git a/RpiLedBars/src/tmp/rpi_pixleds.h b/RpiLedBars/res/src/tmp/rpi_pixleds.h similarity index 100% rename from RpiLedBars/src/tmp/rpi_pixleds.h rename to RpiLedBars/res/src/tmp/rpi_pixleds.h diff --git a/RpiLedBars/src/artnet/artnet.cpp b/RpiLedBars/src/artnet/artnet.cpp deleted file mode 100644 index 9a2aaa9..0000000 --- a/RpiLedBars/src/artnet/artnet.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "artnet.h" diff --git a/RpiLedBars/src/artnet/artnet.h b/RpiLedBars/src/artnet/artnet.h deleted file mode 100644 index 93a36a4..0000000 --- a/RpiLedBars/src/artnet/artnet.h +++ /dev/null @@ -1,4 +0,0 @@ -#if !defined(__ARTNET_H__) -#define __ARTNET_H__ - -#endif // __ARTNET_H__ diff --git a/RpiLedBars/src/drivers/common.c b/RpiLedBars/src/drivers/common.c new file mode 100644 index 0000000..32611f6 --- /dev/null +++ b/RpiLedBars/src/drivers/common.c @@ -0,0 +1,49 @@ +#include "common.h" + +#include +#include +#include +#include +#include + +// 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)); +} \ No newline at end of file diff --git a/RpiLedBars/src/drivers/common.h b/RpiLedBars/src/drivers/common.h new file mode 100644 index 0000000..5652292 --- /dev/null +++ b/RpiLedBars/src/drivers/common.h @@ -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__ \ No newline at end of file diff --git a/RpiLedBars/src/drivers/dma/rpi_dma.c b/RpiLedBars/src/drivers/dma/rpi_dma.c new file mode 100644 index 0000000..8c9510b --- /dev/null +++ b/RpiLedBars/src/drivers/dma/rpi_dma.c @@ -0,0 +1,90 @@ +#include "rpi_dma.h" + +#include + +#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"); + } +} \ No newline at end of file diff --git a/RpiLedBars/src/drivers/dma/rpi_dma.h b/RpiLedBars/src/drivers/dma/rpi_dma.h new file mode 100644 index 0000000..0d96c5e --- /dev/null +++ b/RpiLedBars/src/drivers/dma/rpi_dma.h @@ -0,0 +1,33 @@ +#if !defined(__RPI_DMA_H__) +#define __RPI_DMA_H__ + +#include + +#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__ \ No newline at end of file diff --git a/RpiLedBars/src/drivers/dma/rpi_videocore.c b/RpiLedBars/src/drivers/dma/rpi_videocore.c new file mode 100644 index 0000000..1cd66a0 --- /dev/null +++ b/RpiLedBars/src/drivers/dma/rpi_videocore.c @@ -0,0 +1,125 @@ +#include "rpi_videocore.h" + +#include +#include +#include +#include + +// 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); +} \ No newline at end of file diff --git a/RpiLedBars/src/drivers/dma/rpi_videocore.h b/RpiLedBars/src/drivers/dma/rpi_videocore.h new file mode 100644 index 0000000..e83951b --- /dev/null +++ b/RpiLedBars/src/drivers/dma/rpi_videocore.h @@ -0,0 +1,33 @@ +#if !defined(__RPI_VIDEOCORE_H__) +#define __RPI_VIDEOCORE_H__ + +#include + +#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__ \ No newline at end of file diff --git a/RpiLedBars/src/drivers/gpio/rpi_gpio.c b/RpiLedBars/src/drivers/gpio/rpi_gpio.c new file mode 100644 index 0000000..3e3d24c --- /dev/null +++ b/RpiLedBars/src/drivers/gpio/rpi_gpio.c @@ -0,0 +1,75 @@ +#include "rpi_gpio.h" + +#include +#include +#include + +#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"); +} \ No newline at end of file diff --git a/RpiLedBars/src/drivers/gpio/rpi_gpio.h b/RpiLedBars/src/drivers/gpio/rpi_gpio.h new file mode 100644 index 0000000..9f39848 --- /dev/null +++ b/RpiLedBars/src/drivers/gpio/rpi_gpio.h @@ -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__ \ No newline at end of file diff --git a/RpiLedBars/src/drivers/leddriver/rpi_leddriver.c b/RpiLedBars/src/drivers/leddriver/rpi_leddriver.c new file mode 100644 index 0000000..1ad781e --- /dev/null +++ b/RpiLedBars/src/drivers/leddriver/rpi_leddriver.c @@ -0,0 +1,132 @@ +#include "rpi_leddriver.h" + +#include +#include +#include + +#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++; + } +} \ No newline at end of file diff --git a/RpiLedBars/src/drivers/leddriver/rpi_leddriver.h b/RpiLedBars/src/drivers/leddriver/rpi_leddriver.h new file mode 100644 index 0000000..68e7a19 --- /dev/null +++ b/RpiLedBars/src/drivers/leddriver/rpi_leddriver.h @@ -0,0 +1,19 @@ +#if !defined(__RPI_LEDDRIVER_H__) +#define __RPI_LEDDRIVER_H__ + +#include + +#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__ \ No newline at end of file diff --git a/RpiLedBars/src/rpi_selector.c b/RpiLedBars/src/drivers/selector/rpi_selector.c similarity index 100% rename from RpiLedBars/src/rpi_selector.c rename to RpiLedBars/src/drivers/selector/rpi_selector.c diff --git a/RpiLedBars/src/rpi_selector.h b/RpiLedBars/src/drivers/selector/rpi_selector.h similarity index 100% rename from RpiLedBars/src/rpi_selector.h rename to RpiLedBars/src/drivers/selector/rpi_selector.h diff --git a/RpiLedBars/src/drivers/smi/rpi_smi.c b/RpiLedBars/src/drivers/smi/rpi_smi.c new file mode 100644 index 0000000..c6ed788 --- /dev/null +++ b/RpiLedBars/src/drivers/smi/rpi_smi.c @@ -0,0 +1,223 @@ +#include "rpi_smi.h" + +#include + +#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) +#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) + +// 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); + +// 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; + +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)); +} \ No newline at end of file diff --git a/RpiLedBars/src/drivers/smi/rpi_smi.h b/RpiLedBars/src/drivers/smi/rpi_smi.h new file mode 100644 index 0000000..79ea29d --- /dev/null +++ b/RpiLedBars/src/drivers/smi/rpi_smi.h @@ -0,0 +1,14 @@ +#if !defined(__RPI_SMI_H__) +#define __RPI_SMI_H__ + +#include "../common.h" +#include + +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__ \ No newline at end of file diff --git a/RpiLedBars/src/main.c b/RpiLedBars/src/main.c index 3a96531..978686c 100644 --- a/RpiLedBars/src/main.c +++ b/RpiLedBars/src/main.c @@ -1,147 +1,133 @@ #include +#include #include #include #include #include #include -#ifdef _WIN32 -#include -#else +#include #include -#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() {} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/RpiLedBars/src/rpi_artnet.c b/RpiLedBars/src/rpi_artnet.c deleted file mode 100644 index 889e00f..0000000 --- a/RpiLedBars/src/rpi_artnet.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "rpi_artnet.h" - -#include -#include -#include -#include - -#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)); -} \ No newline at end of file diff --git a/RpiLedBars/src/rpi_midi_controller.c b/RpiLedBars/src/rpi_midi_controller.c new file mode 100644 index 0000000..929efcf --- /dev/null +++ b/RpiLedBars/src/rpi_midi_controller.c @@ -0,0 +1,95 @@ +#include "rpi_midi_controller.h" + +#include + +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); + } + } +} diff --git a/RpiLedBars/src/rpi_midi_controller.h b/RpiLedBars/src/rpi_midi_controller.h new file mode 100644 index 0000000..73b6066 --- /dev/null +++ b/RpiLedBars/src/rpi_midi_controller.h @@ -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__ */ \ No newline at end of file diff --git a/RpiLedBars/src/rpi_param.c b/RpiLedBars/src/rpi_param.c new file mode 100644 index 0000000..30f0937 --- /dev/null +++ b/RpiLedBars/src/rpi_param.c @@ -0,0 +1 @@ +#include "rpi_param.h" diff --git a/RpiLedBars/src/rpi_param.h b/RpiLedBars/src/rpi_param.h new file mode 100644 index 0000000..8b0cc72 --- /dev/null +++ b/RpiLedBars/src/rpi_param.h @@ -0,0 +1,4 @@ +#if !defined(__RPI_PARAM_H__) +#define __RPI_PARAM_H__ + +#endif /* __RPI_PARAM_H__ */ \ No newline at end of file diff --git a/RpiLedBars/src/tasks/artnet/rpi_artnet.c b/RpiLedBars/src/tasks/artnet/rpi_artnet.c new file mode 100644 index 0000000..49de7ad --- /dev/null +++ b/RpiLedBars/src/tasks/artnet/rpi_artnet.c @@ -0,0 +1,139 @@ +#include "rpi_artnet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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)); +} \ No newline at end of file diff --git a/RpiLedBars/src/rpi_artnet.h b/RpiLedBars/src/tasks/artnet/rpi_artnet.h similarity index 51% rename from RpiLedBars/src/rpi_artnet.h rename to RpiLedBars/src/tasks/artnet/rpi_artnet.h index 1cf76ee..818e59f 100644 --- a/RpiLedBars/src/rpi_artnet.h +++ b/RpiLedBars/src/tasks/artnet/rpi_artnet.h @@ -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__ diff --git a/RpiLedBars/src/rpi_artnet_op_codes.h b/RpiLedBars/src/tasks/artnet/rpi_artnet_op_codes.h similarity index 100% rename from RpiLedBars/src/rpi_artnet_op_codes.h rename to RpiLedBars/src/tasks/artnet/rpi_artnet_op_codes.h diff --git a/RpiLedBars/src/rpi_artnet_packets.h b/RpiLedBars/src/tasks/artnet/rpi_artnet_packets.h similarity index 100% rename from RpiLedBars/src/rpi_artnet_packets.h rename to RpiLedBars/src/tasks/artnet/rpi_artnet_packets.h diff --git a/RpiLedBars/src/rpi_artnet_utils.h b/RpiLedBars/src/tasks/artnet/rpi_artnet_utils.h similarity index 100% rename from RpiLedBars/src/rpi_artnet_utils.h rename to RpiLedBars/src/tasks/artnet/rpi_artnet_utils.h diff --git a/RpiLedBars/src/tasks/cava/rpi_cava.c b/RpiLedBars/src/tasks/cava/rpi_cava.c new file mode 100644 index 0000000..4b823bb --- /dev/null +++ b/RpiLedBars/src/tasks/cava/rpi_cava.c @@ -0,0 +1,146 @@ +#include "rpi_cava.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#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; +} diff --git a/RpiLedBars/src/tasks/cava/rpi_cava.h b/RpiLedBars/src/tasks/cava/rpi_cava.h new file mode 100644 index 0000000..0aaf0cd --- /dev/null +++ b/RpiLedBars/src/tasks/cava/rpi_cava.h @@ -0,0 +1,16 @@ +#if !defined(__RPI_CAVA_H__) +#define __RPI_CAVA_H__ + +#include + +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__ */ \ No newline at end of file diff --git a/RpiLedBars/wget-log b/RpiLedBars/wget-log new file mode 100644 index 0000000..4779e59 --- /dev/null +++ b/RpiLedBars/wget-log @@ -0,0 +1,11 @@ +--2021-07-24 09:17:35-- https://forums.adafruit.com/download/file.php?id=49380 +Resolving forums.adafruit.com (forums.adafruit.com)... 34.200.112.132 +Connecting to forums.adafruit.com (forums.adafruit.com)|34.200.112.132|:443... connected. +HTTP request sent, awaiting response... 200 OK +Length: 1510 (1.5K) [application/octet-stream] +Saving to: ‘file.php?id=49380’ + + file.php?id=49380 0%[ ] 0 --.-KB/s file.php?id=49380 100%[===================================================================================================================>] 1.47K --.-KB/s in 0s + +2021-07-24 09:17:36 (3.03 MB/s) - ‘file.php?id=49380’ saved [1510/1510] +