diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2a98046
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+src/waveform_*.wav
+src/pydemod
+src/test
+*.o
+
diff --git a/doc/galaxy_s2.jpg b/doc/galaxy_s2.jpg
new file mode 100644
index 0000000..5a23d96
Binary files /dev/null and b/doc/galaxy_s2.jpg differ
diff --git a/doc/vfd_display.jpg b/doc/vfd_display.jpg
new file mode 100644
index 0000000..3148d25
Binary files /dev/null and b/doc/vfd_display.jpg differ
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..c3dd5a1
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,53 @@
+CC = gcc
+STD_CFLAGS = -Wall -std=gnu99 -c -g -O3
+
+# Enable ARM-specific options only on ARM, and compilation of the app only on ARM
+UNAME := $(shell uname -m)
+
+# Determine the hardware platform. Below, pi1 stands for the RaspberryPi 1 (the original one),
+# and pi2 stands for both the RaspberryPi 2 and 3.
+ifeq ($(UNAME), armv6l)
+ CFLAGS = $(STD_CFLAGS) -march=armv6 -mtune=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -ffast-math -DRASPI=1
+ TARGET = pi1
+else ifeq ($(UNAME), armv7l)
+ CFLAGS = $(STD_CFLAGS) -march=armv7-a -mtune=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -ffast-math -DRASPI=2
+ TARGET = pi2
+else
+ CFLAGS = $(STD_CFLAGS)
+ TARGET = other
+endif
+
+ifneq ($(TARGET), other)
+
+app: rds.o waveforms.o pi_fm_adv.o fm_mpx.o control_pipe.o mailbox.o
+ $(CC) -o pi_fm_adv rds.o waveforms.o mailbox.o pi_fm_adv.o fm_mpx.o control_pipe.o -lm -lsndfile
+
+endif
+
+
+rds_wav: rds.o waveforms.o rds_wav.o fm_mpx.o
+ $(CC) -o rds_wav rds_wav.o rds.o waveforms.o fm_mpx.o -lm -lsndfile
+
+rds.o: rds.c waveforms.h
+ $(CC) $(CFLAGS) rds.c
+
+control_pipe.o: control_pipe.c control_pipe.h rds.h
+ $(CC) $(CFLAGS) control_pipe.c
+
+waveforms.o: waveforms.c waveforms.h
+ $(CC) $(CFLAGS) waveforms.c
+
+mailbox.o: mailbox.c mailbox.h
+ $(CC) $(CFLAGS) mailbox.c
+
+pi_fm_rds.o: pi_fm_adv.c control_pipe.h fm_mpx.h rds.h mailbox.h
+ $(CC) $(CFLAGS) pi_fm_adv.c
+
+rds_wav.o: rds_wav.c
+ $(CC) $(CFLAGS) rds_wav.c
+
+fm_mpx.o: fm_mpx.c fm_mpx.h
+ $(CC) $(CFLAGS) fm_mpx.c
+
+clean:
+ rm -f *.o
diff --git a/src/control_pipe.c b/src/control_pipe.c
new file mode 100644
index 0000000..0045610
--- /dev/null
+++ b/src/control_pipe.c
@@ -0,0 +1,120 @@
+/*
+ PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ Copyright (C) 2017 Miegl
+
+ See https://github.com/Miegl/PiFmAdv
+
+ rds_wav.c is a test program that writes a RDS baseband signal to a WAV
+ file. It requires libsndfile.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "rds.h"
+#include "control_pipe.h"
+
+#define CTL_BUFFER_SIZE 100
+
+FILE *f_ctl;
+
+/*
+ * Opens a file (pipe) to be used to control the RDS coder, in non-blocking mode.
+ */
+int open_control_pipe(char *filename) {
+ int fd = open(filename, O_RDONLY | O_NONBLOCK);
+ if(fd == -1) return -1;
+
+ int flags;
+ flags = fcntl(fd, F_GETFL, 0);
+ flags |= O_NONBLOCK;
+ if( fcntl(fd, F_SETFL, flags) == -1 ) return -1;
+
+ f_ctl = fdopen(fd, "r");
+ if(f_ctl == NULL) return -1;
+
+ return 0;
+}
+
+
+/*
+ * Polls the control file (pipe), non-blockingly, and if a command is received,
+ * processes it and updates the RDS data.
+ */
+int poll_control_pipe() {
+ static char buf[CTL_BUFFER_SIZE];
+
+ char *res = fgets(buf, CTL_BUFFER_SIZE, f_ctl);
+ if(res == NULL) return -1;
+ if(strlen(res) > 3 && res[2] == ' ') {
+ char *arg = res+3;
+ if(arg[strlen(arg)-1] == '\n') arg[strlen(arg)-1] = 0;
+ if(res[0] == 'P' && res[1] == 'S') {
+ arg[8] = 0;
+ set_rds_ps(arg);
+ printf("PS set to: \"%s\"\n", arg);
+ return CONTROL_PIPE_PS_SET;
+ }
+ if(res[0] == 'R' && res[1] == 'T') {
+ arg[64] = 0;
+ set_rds_rt(arg);
+ printf("RT set to: \"%s\"\n", arg);
+ return CONTROL_PIPE_RT_SET;
+ }
+ if(res[0] == 'T' && res[1] == 'A') {
+ int ta = ( strcmp(arg, "ON") == 0 );
+ set_rds_ta(ta);
+ printf("Set TA to ");
+ if(ta) printf("ON\n"); else printf("OFF\n");
+ return CONTROL_PIPE_TA_SET;
+ }
+ }
+
+ if(strlen(res) > 4 && res[3] == ' ') {
+ char *arg = res+4;
+ if(arg[strlen(arg)-1] == '\n') arg[strlen(arg)-1] = 0;
+ if(res[0] == 'P' && res[1] == 'T' && res[2] == 'Y') {
+ int pty = atoi(arg);
+ if (pty >= 0 && pty <= 31) {
+ set_rds_pty(pty);
+ if (pty == 0) {
+ printf("PTY disabled\n");
+ }
+ else {
+ printf("PTY set to: %i\n", pty);
+ }
+ }
+ else {
+ printf("Wrong PTY code! The PTY code range is 0 - 31.\n");
+ }
+ return CONTROL_PIPE_PTY_SET;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Closes the control pipe.
+ */
+int close_control_pipe() {
+ if(f_ctl) return fclose(f_ctl);
+ else return 0;
+}
diff --git a/src/control_pipe.h b/src/control_pipe.h
new file mode 100644
index 0000000..873d0cf
--- /dev/null
+++ b/src/control_pipe.h
@@ -0,0 +1,32 @@
+/*
+ PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ Copyright (C) 2017 Miegl
+
+ See https://github.com/Miegl/PiFmAdv
+
+ rds_wav.c is a test program that writes a RDS baseband signal to a WAV
+ file. It requires libsndfile.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+
+#define CONTROL_PIPE_PS_SET 1
+#define CONTROL_PIPE_RT_SET 2
+#define CONTROL_PIPE_TA_SET 3
+#define CONTROL_PIPE_PTY_SET 4
+
+extern int open_control_pipe(char *filename);
+extern int close_control_pipe();
+extern int poll_control_pipe();
diff --git a/src/fm_mpx.c b/src/fm_mpx.c
new file mode 100644
index 0000000..59a5d6c
--- /dev/null
+++ b/src/fm_mpx.c
@@ -0,0 +1,324 @@
+/*
+ PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ Copyright (C) 2017 Miegl
+
+ See https://github.com/Miegl/PiFmAdv
+
+ rds_wav.c is a test program that writes a RDS baseband signal to a WAV
+ file. It requires libsndfile.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+#include
+#include
+
+#include "rds.h"
+
+
+#define PI 3.141592654
+
+
+#define FIR_HALF_SIZE 30
+#define FIR_SIZE (2*FIR_HALF_SIZE-1)
+
+
+size_t length;
+
+// coefficients of the low-pass FIR filter
+float low_pass_fir[FIR_HALF_SIZE];
+
+
+float carrier_38[] = {0.0, 0.8660254037844386, 0.8660254037844388, 1.2246467991473532e-16, -0.8660254037844384, -0.8660254037844386};
+
+float carrier_19[] = {0.0, 0.5, 0.8660254037844386, 1.0, 0.8660254037844388, 0.5, 1.2246467991473532e-16, -0.5, -0.8660254037844384, -1.0, -0.8660254037844386, -0.5};
+
+int phase_38 = 0;
+int phase_19 = 0;
+
+
+float downsample_factor;
+
+
+float *audio_buffer;
+int audio_index = 0;
+int audio_len = 0;
+float audio_pos;
+
+float fir_buffer_mono[FIR_SIZE] = {0};
+float fir_buffer_stereo[FIR_SIZE] = {0};
+int fir_index = 0;
+int channels;
+
+float *last_buffer_val;
+float preemphasis_prewarp;
+float preemphasis_coefficient;
+
+int transmit_only_stereo;
+int no_rds;
+
+SNDFILE *inf;
+
+
+
+float *alloc_empty_buffer(size_t length) {
+ float *p = malloc(length * sizeof(float));
+ if(p == NULL) return NULL;
+
+ bzero(p, length * sizeof(float));
+
+ return p;
+}
+
+
+int fm_mpx_open(char *filename, size_t len, float cutoff_freq, float preemphasis_corner_freq, int raw, int onlystereo, int nords) {
+ length = len;
+
+ if(filename != NULL) {
+ // Open the input file
+ SF_INFO sfinfo;
+
+ if(raw) {
+ sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
+ sfinfo.samplerate = 44100;
+ sfinfo.channels = 2;
+ }
+
+ // stdin or file on the filesystem?
+ if(filename[0] == '-') {
+ if(! (inf = sf_open_fd(fileno(stdin), SFM_READ, &sfinfo, 0))) {
+ fprintf(stderr, "Error: could not open stdin for audio input.\n") ;
+ return -1;
+ } else {
+ printf("Using stdin for audio input.\n");
+ }
+ } else {
+ if(! (inf = sf_open(filename, SFM_READ, &sfinfo))) {
+ fprintf(stderr, "Error: could not open input file %s.\n", filename) ;
+ return -1;
+ } else {
+ printf("Using audio file: %s\n", filename);
+ }
+ }
+
+ int in_samplerate = sfinfo.samplerate;
+ downsample_factor = 228000. / in_samplerate;
+
+ printf("Input: %d Hz, upsampling factor: %.2f\n", in_samplerate, downsample_factor);
+
+ channels = sfinfo.channels;
+ if(channels > 1) {
+ printf("%d channels, generating stereo multiplex.\n", channels);
+ } else {
+ printf("1 channel, monophonic operation.\n");
+ }
+
+ // Create the preemphasis
+ last_buffer_val = (float*) malloc(sizeof(float)*channels);
+ for(int i=0;i= downsample_factor) {
+ audio_pos -= downsample_factor;
+
+ if(audio_len == 0) {
+ for(int j=0; j<2; j++) { // one retry
+ audio_len = sf_read_float(inf, audio_buffer, length);
+ if (audio_len < 0) {
+ fprintf(stderr, "Error reading audio\n");
+ return -1;
+ }
+ if(audio_len == 0) {
+ if( sf_seek(inf, 0, SEEK_SET) < 0 ) {
+ //fprintf(stderr, "Could not rewind in audio file, terminating\n");
+ //return -1;
+ return 0;
+ }
+ } else {
+ //apply preemphasis
+ int k;
+ int l;
+ float tmp;
+ for(k=0;k= FIR_SIZE) fir_index = 0;
+
+ // Now apply the FIR low-pass filter
+
+ /* As the FIR filter is symmetric, we do not multiply all
+ the coefficients independently, but two-by-two, thus reducing
+ the total number of multiplications by a factor of two
+ */
+ float out_mono = 0;
+ float out_stereo = 0;
+ int ifbi = fir_index; // ifbi = increasing FIR Buffer Index
+ int dfbi = fir_index; // dfbi = decreasing FIR Buffer Index
+ for(int fi=0; fi 1) {
+ out_stereo +=
+ low_pass_fir[fi] *
+ (fir_buffer_stereo[ifbi] + fir_buffer_stereo[dfbi]);
+ }
+ ifbi++;
+ if(ifbi >= FIR_SIZE) ifbi = 0;
+ }
+ // End of FIR filter
+
+ if (transmit_only_stereo) { // If set, PiFmAdv will transmit audio only on stereo subcarrier (38Khz). This is useful when testing FM receivers.
+ if (no_rds) {
+ mpx_buffer[i] =
+ 4.05 * carrier_38[phase_38] * out_mono; // Unmodulated monophonic (or stereo-sum) signal on 38Khz (stereo) subcarrier
+ } else {
+ mpx_buffer[i] =
+ rds_buffer[i] + // RDS data samples are currently in mpx_buffer
+ 4.05 * carrier_38[phase_38] * out_mono; // Unmodulated monophonic (or stereo-sum) signal on 38Khz (stereo) subcarrier
+ }
+
+ if(channels>1) {
+ mpx_buffer[i] +=
+ 1.0 * carrier_19[phase_19]; // Stereo pilot tone
+
+ phase_19++;
+ phase_38++;
+ if(phase_19 >= 12) phase_19 = 0;
+ if(phase_38 >= 6) phase_38 = 0;
+ }
+ }
+ else {
+ if (no_rds) {
+ mpx_buffer[i] =
+ 4.05 * out_mono; // Unmodulated monophonic (or stereo-sum) signal
+ } else {
+ mpx_buffer[i] =
+ rds_buffer[i] + // RDS data samples are currently in mpx_buffer
+ 4.05 * out_mono; // Unmodulated monophonic (or stereo-sum) signal
+ }
+
+ if(channels>1) {
+ mpx_buffer[i] +=
+ 4.05 * carrier_38[phase_38] * out_stereo + // Stereo difference signal
+ 1.0 * carrier_19[phase_19]; // Stereo pilot tone
+
+ phase_19++;
+ phase_38++;
+ if(phase_19 >= 12) phase_19 = 0;
+ if(phase_38 >= 6) phase_38 = 0;
+ }
+ }
+ audio_pos++;
+ }
+
+ return 0;
+}
+
+
+int fm_mpx_close() {
+ if(sf_close(inf) ) {
+ fprintf(stderr, "Error closing audio file");
+ }
+
+ if(audio_buffer != NULL) free(audio_buffer);
+
+ return 0;
+}
diff --git a/src/fm_mpx.h b/src/fm_mpx.h
new file mode 100644
index 0000000..8c3020a
--- /dev/null
+++ b/src/fm_mpx.h
@@ -0,0 +1,26 @@
+/*
+ PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ Copyright (C) 2017 Miegl
+
+ See https://github.com/Miegl/PiFmAdv
+
+ rds_wav.c is a test program that writes a RDS baseband signal to a WAV
+ file. It requires libsndfile.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+extern int fm_mpx_open(char *filename, size_t len, float cutoff_freq, float preemphasis_corner_freq, int raw, int onlystereo, int nords);
+extern int fm_mpx_get_samples(float *mpx_buffer, float *rds_buffer);
+extern int fm_mpx_close();
diff --git a/src/generate_pulses.py b/src/generate_pulses.py
new file mode 100644
index 0000000..23f75bb
--- /dev/null
+++ b/src/generate_pulses.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+
+# PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+# Copyright (C) 2017 Miegl
+#
+# See https://github.com/Miegl/PiFmAdv
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+# This program generates a WAV file with a 1-second sine wave at 440 Hz,
+# followed by a 1-second silence.
+
+
+import scipy.io.wavfile as wavfile
+import numpy
+
+sample_rate = 228000
+samples = numpy.zeros(2 * sample_rate, dtype=numpy.dtype('>i2'))
+
+# 1-second tune
+samples[:sample_rate] = (numpy.sin(2*numpy.pi*440*numpy.arange(sample_rate)/sample_rate)
+ * 20000).astype(numpy.dtype('>i2'))
+
+wavfile.write("pulses.wav", sample_rate, samples)
diff --git a/src/generate_waveforms.py b/src/generate_waveforms.py
new file mode 100644
index 0000000..f396719
--- /dev/null
+++ b/src/generate_waveforms.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+
+
+# PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+# Copyright (C) 2017 Miegl
+#
+# See https://github.com/Miegl/PiFmAdv
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+# This program generates the waveform of a single biphase symbol
+#
+# This program uses Pydemod, see https://github.com/ChristopheJacquet/Pydemod
+
+import pydemod.app.rds as rds
+import numpy
+import scipy.io.wavfile as wavfile
+import io
+import matplotlib.pyplot as plt
+
+sample_rate = 228000
+
+outc = io.open("waveforms.c", mode="w", encoding="utf8")
+outh = io.open("waveforms.h", mode="w", encoding="utf8")
+
+header = u"""
+/* This file was automatically generated by "generate_waveforms.py".
+ (C) 2014 Christophe Jacquet.
+ Released under the GNU GPL v3 license.
+*/
+
+"""
+
+outc.write(header)
+outh.write(header)
+
+def generate_bit(name):
+ offset = 240
+ l = 96
+ count = 2
+
+
+ sample = numpy.zeros(3*l)
+ sample[l] = 1
+ sample[2*l] = -1
+
+ # Apply the data-shaping filter
+ sf = rds.pulse_shaping_filter(96*8, 228000)
+ shapedSamples = numpy.convolve(sample, sf)
+
+
+ out = shapedSamples[528-288:528+288] #[offset:offset+l*count]
+ #plt.plot(sf)
+ #plt.plot(out)
+ #plt.show()
+
+ iout = (out * 20000./max(abs(out)) ).astype(numpy.dtype('>i2'))
+ wavfile.write(u"waveform_{}.wav".format(name), sample_rate, iout)
+
+ outc.write(u"float waveform_{name}[] = {{{values}}};\n\n".format(
+ name = name,
+ values = u", ".join(map(unicode, out/2.5))))
+ # note: need to limit the amplitude so as not to saturate when the biphase
+ # waveforms are summed
+
+ outh.write(u"extern float waveform_{name}[{size}];\n".format(name=name, size=len(out)))
+
+
+generate_bit("biphase")
+
+outc.close()
+outh.close()
diff --git a/src/mailbox.c b/src/mailbox.c
new file mode 100644
index 0000000..902f6bb
--- /dev/null
+++ b/src/mailbox.c
@@ -0,0 +1,276 @@
+/*
+Copyright (c) 2012, Broadcom Europe Ltd.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mailbox.h"
+
+#define PAGE_SIZE (4*1024)
+
+void *mapmem(unsigned base, unsigned size)
+{
+ int mem_fd;
+ unsigned offset = base % PAGE_SIZE;
+ base = base - offset;
+ /* open /dev/mem */
+ if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
+ printf("can't open /dev/mem\nThis program should be run as root. Try prefixing command with: sudo\n");
+ exit (-1);
+ }
+ void *mem = mmap(
+ 0,
+ size,
+ PROT_READ|PROT_WRITE,
+ MAP_SHARED/*|MAP_FIXED*/,
+ mem_fd,
+ base);
+#ifdef DEBUG
+ printf("base=0x%x, mem=%p\n", base, mem);
+#endif
+ if (mem == MAP_FAILED) {
+ printf("mmap error %d\n", (int)mem);
+ exit (-1);
+ }
+ close(mem_fd);
+ return (char *)mem + offset;
+}
+
+void *unmapmem(void *addr, unsigned size)
+{
+ int s = munmap(addr, size);
+ if (s != 0) {
+ printf("munmap error %d\n", s);
+ exit (-1);
+ }
+
+ return NULL;
+}
+
+/*
+ * use ioctl to send mbox property message
+ */
+
+static int mbox_property(int file_desc, void *buf)
+{
+ int ret_val = ioctl(file_desc, IOCTL_MBOX_PROPERTY, buf);
+
+ if (ret_val < 0) {
+ printf("ioctl_set_msg failed:%d\n", ret_val);
+ }
+
+#ifdef DEBUG
+ unsigned *p = buf; int i; unsigned size = *(unsigned *)buf;
+ for (i=0; i= 0) {
+ printf("Using mbox device " DEVICE_FILE_NAME ".\n");
+ return file_desc;
+ }
+
+ // Try to create one
+ unlink(LOCAL_DEVICE_FILE_NAME);
+ if(mknod(LOCAL_DEVICE_FILE_NAME, S_IFCHR|0600, makedev(MAJOR_NUM_A, 0)) >= 0 &&
+ (file_desc = open(LOCAL_DEVICE_FILE_NAME, 0)) >= 0) {
+ printf("Using local mbox device file with major %d.\n", MAJOR_NUM_A);
+ return file_desc;
+ }
+
+ unlink(LOCAL_DEVICE_FILE_NAME);
+ if(mknod(LOCAL_DEVICE_FILE_NAME, S_IFCHR|0600, makedev(MAJOR_NUM_B, 0)) >= 0 &&
+ (file_desc = open(LOCAL_DEVICE_FILE_NAME, 0)) >= 0) {
+ printf("Using local mbox device file with major %d.\n", MAJOR_NUM_B);
+ return file_desc;
+ }
+
+ return -1;
+}
+
+void mbox_close(int file_desc) {
+ close(file_desc);
+}
diff --git a/src/mailbox.h b/src/mailbox.h
new file mode 100644
index 0000000..5315591
--- /dev/null
+++ b/src/mailbox.h
@@ -0,0 +1,50 @@
+/*
+Copyright (c) 2012, Broadcom Europe Ltd.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include
+
+// Newer kernels (>= 4.1) use major 249, older ones major 100.
+#define MAJOR_NUM_A 249
+#define MAJOR_NUM_B 100
+#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM_B, 0, char *)
+#define DEVICE_FILE_NAME "/dev/vcio"
+#define LOCAL_DEVICE_FILE_NAME "mbox"
+
+int mbox_open();
+void mbox_close(int file_desc);
+
+unsigned get_version(int file_desc);
+unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigned flags);
+unsigned mem_free(int file_desc, unsigned handle);
+unsigned mem_lock(int file_desc, unsigned handle);
+unsigned mem_unlock(int file_desc, unsigned handle);
+void *mapmem(unsigned base, unsigned size);
+void *unmapmem(void *addr, unsigned size);
+
+unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigned r1, unsigned r2, unsigned r3, unsigned r4, unsigned r5);
+unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control, unsigned noflush, unsigned timeout);
+unsigned qpu_enable(int file_desc, unsigned enable);
diff --git a/src/noise_22050.wav b/src/noise_22050.wav
new file mode 100644
index 0000000..5fb079a
Binary files /dev/null and b/src/noise_22050.wav differ
diff --git a/src/pi_fm_adv.c b/src/pi_fm_adv.c
new file mode 100644
index 0000000..0bc5c15
--- /dev/null
+++ b/src/pi_fm_adv.c
@@ -0,0 +1,651 @@
+/*
+ * PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ * Copyright (C) 2017 Miegl
+ * Copyright (C) 2014, 2015 Christophe Jacquet, F8FTK
+ * Copyright (C) 2012, 2015 Richard Hirst
+ * Copyright (C) 2012 Oliver Mattos and Oskar Weigl
+ *
+ * See https://github.com/Miegl/PiFmAdv
+ *
+ * PI-FM-ADV: RaspberryPi FM transmitter, with RDS.
+ *
+ * This file contains the VHF FM modulator. All credit goes to the original
+ * authors, Oliver Mattos and Oskar Weigl for the original idea, and to
+ * Richard Hirst for using the Pi's DMA engine, which reduced CPU usage
+ * dramatically.
+ *
+ * I (Christophe Jacquet) have adapted their idea to transmitting samples
+ * at 228 kHz, allowing to build the 57 kHz subcarrier for RDS BPSK data.
+ *
+ * To make it work on the Raspberry Pi 2, I used a fix by Richard Hirst
+ * (again) to request memory using Broadcom's mailbox interface. This fix
+ * was published for ServoBlaster here:
+ * https://www.raspberrypi.org/forums/viewtopic.php?p=699651#p699651
+ *
+ * Never use this to transmit VHF-FM data through an antenna, as it is
+ * illegal in most countries. This code is for testing purposes only.
+ * Always connect a shielded transmission line from the RaspberryPi directly
+ * to a radio receiver, so as *not* to emit radio waves.
+ *
+ * ---------------------------------------------------------------------------
+ * These are the comments from Richard Hirst's version:
+ *
+ * RaspberryPi based FM transmitter. For the original idea, see:
+ *
+ * http://www.icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter
+ *
+ * All credit to Oliver Mattos and Oskar Weigl for creating the original code.
+ *
+ * I have taken their idea and reworked it to use the Pi DMA engine, so
+ * reducing the CPU overhead for playing a .wav file from 100% to about 1.6%.
+ *
+ * I have implemented this in user space, using an idea I picked up from Joan
+ * on the Raspberry Pi forums - credit to Joan for the DMA from user space
+ * idea.
+ *
+ * The idea of feeding the PWM FIFO in order to pace DMA control blocks comes
+ * from ServoBlaster, and I take credit for that :-)
+ *
+ * This code uses DMA channel 0 and the PWM hardware, with no regard for
+ * whether something else might be trying to use it at the same time (such as
+ * the 3.5mm jack audio driver).
+ *
+ * I know nothing much about sound, subsampling, or FM broadcasting, so it is
+ * quite likely the sound quality produced by this code can be improved by
+ * someone who knows what they are doing. There may be issues realting to
+ * caching, as the user space process just writes to its virtual address space,
+ * and expects the DMA controller to see the data; it seems to work for me
+ * though.
+ *
+ * NOTE: THIS CODE MAY WELL CRASH YOUR PI, TRASH YOUR FILE SYSTEMS, AND
+ * POTENTIALLY EVEN DAMAGE YOUR HARDWARE. THIS IS BECAUSE IT STARTS UP THE DMA
+ * CONTROLLER USING MEMORY OWNED BY A USER PROCESS. IF THAT USER PROCESS EXITS
+ * WITHOUT STOPPING THE DMA CONTROLLER, ALL HELL COULD BREAK LOOSE AS THE
+ * MEMORY GETS REALLOCATED TO OTHER PROCESSES WHILE THE DMA CONTROLLER IS STILL
+ * USING IT. I HAVE ATTEMPTED TO MINIMISE ANY RISK BY CATCHING SIGNALS AND
+ * RESETTING THE DMA CONTROLLER BEFORE EXITING, BUT YOU HAVE BEEN WARNED. I
+ * ACCEPT NO LIABILITY OR RESPONSIBILITY FOR ANYTHING THAT HAPPENS AS A RESULT
+ * OF YOU RUNNING THIS CODE. IF IT BREAKS, YOU GET TO KEEP ALL THE PIECES.
+ *
+ * NOTE ALSO: THIS MAY BE ILLEGAL IN YOUR COUNTRY. HERE ARE SOME COMMENTS
+ * FROM MORE KNOWLEDGEABLE PEOPLE ON THE FORUM:
+ *
+ * "Just be aware that in some countries FM broadcast and especially long
+ * distance FM broadcast could get yourself into trouble with the law, stray FM
+ * broadcasts over Airband aviation is also strictly forbidden."
+ *
+ * "A low pass filter is really really required for this as it has strong
+ * harmonics at the 3rd, 5th 7th and 9th which sit in licensed and rather
+ * essential bands, ie GSM, HAM, emergency services and others. Polluting these
+ * frequencies is immoral and dangerous, whereas "breaking in" on FM bands is
+ * just plain illegal."
+ *
+ * "Don't get caught, this GPIO use has the potential to exceed the legal
+ * limits by about 2000% with a proper aerial."
+ *
+ *
+ * As for the original code, this code is released under the GPL.
+ *
+ * Richard Hirst December 2012
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "rds.h"
+#include "fm_mpx.h"
+#include "control_pipe.h"
+
+#include "mailbox.h"
+#define MBFILE DEVICE_FILE_NAME /* From mailbox.h */
+
+#if (RASPI)==1
+#define PERIPH_VIRT_BASE 0x20000000
+#define PERIPH_PHYS_BASE 0x7e000000
+#define DRAM_PHYS_BASE 0x40000000
+#define MEM_FLAG 0x0c
+#elif (RASPI)==2
+#define PERIPH_VIRT_BASE 0x3f000000
+#define PERIPH_PHYS_BASE 0x7e000000
+#define DRAM_PHYS_BASE 0xc0000000
+#define MEM_FLAG 0x04
+#else
+#error Unknown Raspberry Pi version (variable RASPI)
+#endif
+
+#define NUM_SAMPLES 50000
+#define NUM_CBS (NUM_SAMPLES * 2)
+
+#define BCM2708_DMA_NO_WIDE_BURSTS (1<<26)
+#define BCM2708_DMA_WAIT_RESP (1<<3)
+#define BCM2708_DMA_D_DREQ (1<<6)
+#define BCM2708_DMA_PER_MAP(x) ((x)<<16)
+#define BCM2708_DMA_END (1<<1)
+#define BCM2708_DMA_RESET (1<<31)
+#define BCM2708_DMA_INT (1<<2)
+
+#define DMA_CS (0x00/4)
+#define DMA_CONBLK_AD (0x04/4)
+#define DMA_DEBUG (0x20/4)
+
+#define DMA_BASE_OFFSET 0x00007000
+#define DMA_LEN 0x24
+#define PWM_BASE_OFFSET 0x0020C000
+#define PWM_LEN 0x28
+#define CLK_BASE_OFFSET 0x00101000
+#define CLK_LEN 0xA8
+#define GPIO_BASE_OFFSET 0x00200000
+#define GPIO_LEN 0x100
+
+#define DMA_VIRT_BASE (PERIPH_VIRT_BASE + DMA_BASE_OFFSET)
+#define PWM_VIRT_BASE (PERIPH_VIRT_BASE + PWM_BASE_OFFSET)
+#define CLK_VIRT_BASE (PERIPH_VIRT_BASE + CLK_BASE_OFFSET)
+#define GPIO_VIRT_BASE (PERIPH_VIRT_BASE + GPIO_BASE_OFFSET)
+#define PCM_VIRT_BASE (PERIPH_VIRT_BASE + PCM_BASE_OFFSET)
+
+#define PWM_PHYS_BASE (PERIPH_PHYS_BASE + PWM_BASE_OFFSET)
+#define PCM_PHYS_BASE (PERIPH_PHYS_BASE + PCM_BASE_OFFSET)
+#define GPIO_PHYS_BASE (PERIPH_PHYS_BASE + GPIO_BASE_OFFSET)
+
+
+#define PWM_CTL (0x00/4)
+#define PWM_DMAC (0x08/4)
+#define PWM_RNG1 (0x10/4)
+#define PWM_FIFO (0x18/4)
+
+#define PWMCLK_CNTL 40
+#define PWMCLK_DIV 41
+
+#define CM_GP0DIV (0x7e101074)
+
+#define GPCLK_CNTL (0x70/4)
+#define GPCLK_DIV (0x74/4)
+
+#define PWMCTL_MODE1 (1<<1)
+#define PWMCTL_PWEN1 (1<<0)
+#define PWMCTL_CLRF (1<<6)
+#define PWMCTL_USEF1 (1<<5)
+
+#define PWMDMAC_ENAB (1<<31)
+// I think this means it requests as soon as there is one free slot in the FIFO
+// which is what we want as burst DMA would mess up our timing.
+#define PWMDMAC_THRSHLD ((15<<8)|(15<<0))
+
+#define GPFSEL0 (0x00/4)
+
+#define PLLFREQ 500000000. // PLLD is running at 500MHz
+
+typedef struct {
+ uint32_t info, src, dst, length,
+ stride, next, pad[2];
+} dma_cb_t;
+
+#define BUS_TO_PHYS(x) ((x)&~0xC0000000)
+
+
+static struct {
+ int handle; /* From mbox_open() */
+ unsigned mem_ref; /* From mem_alloc() */
+ unsigned bus_addr; /* From mem_lock() */
+ uint8_t *virt_addr; /* From mapmem() */
+} mbox;
+
+
+
+static volatile uint32_t *pwm_reg;
+static volatile uint32_t *clk_reg;
+static volatile uint32_t *dma_reg;
+static volatile uint32_t *gpio_reg;
+
+struct control_data_s {
+ dma_cb_t cb[NUM_CBS];
+ uint32_t sample[NUM_SAMPLES];
+};
+
+#define PAGE_SIZE 4096
+#define PAGE_SHIFT 12
+#define NUM_PAGES ((sizeof(struct control_data_s) + PAGE_SIZE - 1) >> PAGE_SHIFT)
+
+static struct control_data_s *ctl;
+
+static void
+udelay(int us)
+{
+ struct timespec ts = { 0, us * 1000 };
+
+ nanosleep(&ts, NULL);
+}
+
+static void
+terminate(int num)
+{
+ // Stop outputting and generating the clock.
+ if (clk_reg && gpio_reg && mbox.virt_addr) {
+ // Set GPIO4 to be an output (instead of ALT FUNC 0, which is the clock).
+ gpio_reg[GPFSEL0] = (gpio_reg[GPFSEL0] & ~(7 << 12)) | (1 << 12);
+
+ // Disable the clock generator.
+ clk_reg[GPCLK_CNTL] = 0x5A;
+ }
+
+ if (dma_reg && mbox.virt_addr) {
+ dma_reg[DMA_CS] = BCM2708_DMA_RESET;
+ udelay(10);
+ }
+
+ fm_mpx_close();
+ close_control_pipe();
+
+ if (mbox.virt_addr != NULL) {
+ unmapmem(mbox.virt_addr, NUM_PAGES * 4096);
+ mem_unlock(mbox.handle, mbox.mem_ref);
+ mem_free(mbox.handle, mbox.mem_ref);
+ }
+
+ printf("Terminating: cleanly deactivated the DMA engine and killed the carrier.\n");
+
+ exit(num);
+}
+
+static void
+fatal(char *fmt, ...)
+{
+ va_list ap;
+ fprintf(stderr,"ERROR: ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ terminate(0);
+}
+
+static void
+warn(char *fmt, ...)
+{
+ va_list ap;
+ fprintf(stderr,"WARNING: ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+
+static uint32_t
+mem_virt_to_phys(void *virt)
+{
+ uint32_t offset = (uint8_t *)virt - mbox.virt_addr;
+
+ return mbox.bus_addr + offset;
+}
+
+static uint32_t
+mem_phys_to_virt(uint32_t phys)
+{
+ return phys - (uint32_t)mbox.bus_addr + (uint32_t)mbox.virt_addr;
+}
+
+static void *
+map_peripheral(uint32_t base, uint32_t len)
+{
+ int fd = open("/dev/mem", O_RDWR | O_SYNC);
+ void * vaddr;
+
+ if (fd < 0)
+ fatal("Failed to open /dev/mem: %m.\n");
+ vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base);
+ if (vaddr == MAP_FAILED)
+ fatal("Failed to map peripheral at 0x%08x: %m.\n", base);
+ close(fd);
+
+ return vaddr;
+}
+
+
+
+#define SUBSIZE 1
+#define DATA_SIZE 5000
+
+
+int tx(uint32_t carrier_freq, char *audio_file, uint16_t pi, char *ps, char *rt, float ppm, float deviation, float cutoff, float preemphasis_cutoff, char *control_pipe, int raw, int pty, int onlystereo, int nords) {
+ // Catch all signals possible - it is vital we kill the DMA engine
+ // on process exit!
+ for (int i = 0; i < 64; i++) {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = terminate;
+ sigaction(i, &sa, NULL);
+ }
+
+ dma_reg = map_peripheral(DMA_VIRT_BASE, DMA_LEN);
+ pwm_reg = map_peripheral(PWM_VIRT_BASE, PWM_LEN);
+ clk_reg = map_peripheral(CLK_VIRT_BASE, CLK_LEN);
+ gpio_reg = map_peripheral(GPIO_VIRT_BASE, GPIO_LEN);
+
+ // Use the mailbox interface to the VC to ask for physical memory.
+ mbox.handle = mbox_open();
+ if (mbox.handle < 0)
+ fatal("Failed to open mailbox. Check kernel support for vcio / BCM2708 mailbox.\n");
+ printf("Allocating physical memory: size = %d ", NUM_PAGES * 4096);
+ if(! (mbox.mem_ref = mem_alloc(mbox.handle, NUM_PAGES * 4096, 4096, MEM_FLAG))) {
+ fatal("Could not allocate memory.\n");
+ }
+ // TODO: How do we know that succeeded?
+ printf("mem_ref = %u ", mbox.mem_ref);
+ if(! (mbox.bus_addr = mem_lock(mbox.handle, mbox.mem_ref))) {
+ fatal("Could not lock memory.\n");
+ }
+ printf("bus_addr = %x ", mbox.bus_addr);
+ if(! (mbox.virt_addr = mapmem(BUS_TO_PHYS(mbox.bus_addr), NUM_PAGES * 4096))) {
+ fatal("Could not map memory.\n");
+ }
+ printf("virt_addr = %p\n", mbox.virt_addr);
+
+
+ // GPIO4 needs to be ALT FUNC 0 to output the clock
+ gpio_reg[GPFSEL0] = (gpio_reg[GPFSEL0] & ~(7 << 12)) | (4 << 12);
+
+ // Program GPCLK to use MASH setting 1, so fractional dividers work
+ clk_reg[GPCLK_CNTL] = 0x5A << 24 | 6;
+ udelay(100);
+ clk_reg[GPCLK_CNTL] = 0x5A << 24 | 1 << 9 | 1 << 4 | 6;
+
+ ctl = (struct control_data_s *) mbox.virt_addr;
+ dma_cb_t *cbp = ctl->cb;
+ uint32_t phys_sample_dst = CM_GP0DIV;
+ uint32_t phys_pwm_fifo_addr = PWM_PHYS_BASE + 0x18;
+
+
+ // Calculate the frequency control word
+ // The fractional part is stored in the lower 12 bits
+ uint32_t freq_ctl = ((float)(PLLFREQ / carrier_freq)) * ( 1 << 12 );
+
+
+ for (int i = 0; i < NUM_SAMPLES; i++) {
+ ctl->sample[i] = 0x5a << 24 | freq_ctl; // Silence
+ // Write a frequency sample
+ cbp->info = BCM2708_DMA_NO_WIDE_BURSTS | BCM2708_DMA_WAIT_RESP;
+ cbp->src = mem_virt_to_phys(ctl->sample + i);
+ cbp->dst = phys_sample_dst;
+ cbp->length = 4;
+ cbp->stride = 0;
+ cbp->next = mem_virt_to_phys(cbp + 1);
+ cbp++;
+ // Delay
+ cbp->info = BCM2708_DMA_NO_WIDE_BURSTS | BCM2708_DMA_WAIT_RESP | BCM2708_DMA_D_DREQ | BCM2708_DMA_PER_MAP(5);
+ cbp->src = mem_virt_to_phys(mbox.virt_addr);
+ cbp->dst = phys_pwm_fifo_addr;
+ cbp->length = 4;
+ cbp->stride = 0;
+ cbp->next = mem_virt_to_phys(cbp + 1);
+ cbp++;
+ }
+ cbp--;
+ cbp->next = mem_virt_to_phys(mbox.virt_addr);
+
+ // Here we define the rate at which we want to update the GPCLK control
+ // register.
+ //
+ // Set the range to 2 bits. PLLD is at 500 MHz, therefore to get 228 kHz
+ // we need a divisor of 500000 / 2 / 228 = 1096.491228
+ //
+ // This is 1096 + 2012*2^-12 theoretically
+ //
+ // However the fractional part may have to be adjusted to take the actual
+ // frequency of your Pi's oscillator into account. For example on my Pi,
+ // the fractional part should be 1916 instead of 2012 to get exactly
+ // 228 kHz. However RDS decoding is still okay even at 2012.
+ //
+ // So we use the 'ppm' parameter to compensate for the oscillator error
+
+ float divider = (500000./(2*228*(1.+ppm/1.e6)));
+ uint32_t idivider = (uint32_t) divider;
+ uint32_t fdivider = (uint32_t) ((divider - idivider)*pow(2, 12));
+
+ printf("ppm corr is %.4f, divider is %.4f (%d + %d*2^-12) [nominal 1096.4912].\n",
+ ppm, divider, idivider, fdivider);
+
+ pwm_reg[PWM_CTL] = 0;
+ udelay(10);
+ clk_reg[PWMCLK_CNTL] = 0x5A000006; // Source=PLLD and disable
+ udelay(100);
+ // theorically : 1096 + 2012*2^-12
+ clk_reg[PWMCLK_DIV] = 0x5A000000 | (idivider<<12) | fdivider;
+ udelay(100);
+ clk_reg[PWMCLK_CNTL] = 0x5A000216; // Source=PLLD and enable + MASH filter 1
+ udelay(100);
+ pwm_reg[PWM_RNG1] = 2;
+ udelay(10);
+ pwm_reg[PWM_DMAC] = PWMDMAC_ENAB | PWMDMAC_THRSHLD;
+ udelay(10);
+ pwm_reg[PWM_CTL] = PWMCTL_CLRF;
+ udelay(10);
+ pwm_reg[PWM_CTL] = PWMCTL_USEF1 | PWMCTL_PWEN1;
+ udelay(10);
+
+
+ // Initialise the DMA
+ dma_reg[DMA_CS] = BCM2708_DMA_RESET;
+ udelay(10);
+ dma_reg[DMA_CS] = BCM2708_DMA_INT | BCM2708_DMA_END;
+ dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb);
+ dma_reg[DMA_DEBUG] = 7; // clear debug error flags
+ dma_reg[DMA_CS] = 0x10880001; // go, mid priority, wait for outstanding writes
+
+
+ uint32_t last_cb = (uint32_t)ctl->cb;
+
+ // Data structures for baseband data
+ float data[DATA_SIZE];
+ float rdsdata[DATA_SIZE];
+ int data_len = 0;
+ int data_index = 0;
+
+ // Initialize the baseband generator
+ if(fm_mpx_open(audio_file, DATA_SIZE, cutoff, preemphasis_cutoff, raw, onlystereo, nords) < 0) return 1;
+
+ // Initialize the RDS modulator
+ char myps[9] = {0};
+ set_rds_pi(pi);
+ set_rds_rt(rt);
+ set_rds_pty(pty);
+ uint16_t count = 0;
+ uint16_t count2 = 0;
+ int varying_ps = 0;
+
+ if(ps) {
+ set_rds_ps(ps);
+ printf("PI: %04X, PS: \"%s\".\n", pi, ps);
+ } else {
+ printf("PI: %04X, PS: .\n", pi);
+ varying_ps = 1;
+ }
+ printf("RT: \"%s\"\n", rt);
+
+ // Initialize the control pipe reader
+ if(control_pipe) {
+ if(open_control_pipe(control_pipe) == 0) {
+ printf("Reading control commands on %s.\n", control_pipe);
+ } else {
+ printf("Failed to open control pipe: %s.\n", control_pipe);
+ control_pipe = NULL;
+ }
+ }
+
+
+ printf("Starting to transmit on %3.1f MHz.\n", carrier_freq/1e6);
+
+ for (;;) {
+ // Default (varying) PS
+ if(varying_ps) {
+ if(count == 512) {
+ snprintf(myps, 9, "%08d", count2);
+ set_rds_ps(myps);
+ count2++;
+ }
+ if(count == 1024) {
+ set_rds_ps("RPi-Live");
+ count = 0;
+ }
+ count++;
+ }
+
+ if(control_pipe && poll_control_pipe() == CONTROL_PIPE_PS_SET) {
+ varying_ps = 0;
+ }
+
+ usleep(5000);
+
+ uint32_t cur_cb = mem_phys_to_virt(dma_reg[DMA_CONBLK_AD]);
+ int last_sample = (last_cb - (uint32_t)mbox.virt_addr) / (sizeof(dma_cb_t) * 2);
+ int this_sample = (cur_cb - (uint32_t)mbox.virt_addr) / (sizeof(dma_cb_t) * 2);
+ int free_slots = this_sample - last_sample;
+
+ if (free_slots < 0)
+ free_slots += NUM_SAMPLES;
+
+ while (free_slots >= SUBSIZE) {
+ // get more baseband samples if necessary
+ if(data_len == 0) {
+ if( fm_mpx_get_samples(data, rdsdata) < 0 ) {
+ //terminate(0);
+ return 0;
+ }
+ data_len = DATA_SIZE;
+ data_index = 0;
+ }
+
+ float dval = data[data_index] * (deviation / 10.);
+ data_index++;
+ data_len--;
+
+ int intval = (int)((floor)(dval));
+ //int frac = (int)((dval - (float)intval) * SUBSIZE);
+
+
+ ctl->sample[last_sample++] = (0x5A << 24 | freq_ctl) + intval; //(frac > j ? intval + 1 : intval);
+ if (last_sample == NUM_SAMPLES)
+ last_sample = 0;
+
+ free_slots -= SUBSIZE;
+ }
+ last_cb = (uint32_t)mbox.virt_addr + last_sample * sizeof(dma_cb_t) * 2;
+ }
+
+ return 0;
+}
+
+#define PREEMPHASIS_EU 3185
+#define PREEMPHASIS_US 2120
+
+#define CUTOFF_COMPLIANT 15000
+#define CUTOFF_QUALITY 22050
+
+
+int main(int argc, char **argv) {
+ char *audio_file = NULL;
+ char *control_pipe = NULL;
+ uint32_t carrier_freq = 107900000;
+ char *ps = NULL;
+ char *rt = "PiFmAdv: live FM-RDS transmission from the RaspberryPi";
+ uint16_t pi = 0x1234;
+ float ppm = 0;
+ float deviation = 75.0;
+ float cutoff = CUTOFF_COMPLIANT;
+ float preemphasis_cutoff = PREEMPHASIS_EU;
+ int raw = 0;
+ int pty = 15;
+ int onlystereo = 0;
+ int nords = 0;
+
+
+
+ // Parse command-line arguments
+ for(int i=1; i 108e6)
+ warn("Frequency should be in megahertz between 76.0 and 108.0, but is %f MHz\n", atof(param));
+ } else if(strcmp("-pi", arg)==0 && param != NULL) {
+ i++;
+ pi = (uint16_t) strtol(param, NULL, 16);
+ } else if(strcmp("-ps", arg)==0 && param != NULL) {
+ i++;
+ ps = param;
+ } else if(strcmp("-rt", arg)==0 && param != NULL) {
+ i++;
+ rt = param;
+ } else if(strcmp("-dev", arg)==0 && param != NULL) {
+ i++;
+ deviation = atof(param);
+ } else if(strcmp("-ppm", arg)==0 && param != NULL) {
+ i++;
+ ppm = atof(param);
+ } else if(strcmp("-preemph", arg)==0 && param != NULL) {
+ i++;
+ if(strcmp("eu", param)==0) {
+ preemphasis_cutoff = PREEMPHASIS_EU;
+ } else if(strcmp("us", param)==0) {
+ preemphasis_cutoff = PREEMPHASIS_US;
+ }
+ else {
+ preemphasis_cutoff = atof(param);
+ }
+ } else if(strcmp("-cutoff", arg)==0 && param != NULL) {
+ i++;
+ if(strcmp("compliant", param)==0) {
+ cutoff = CUTOFF_COMPLIANT;
+ } else if(strcmp("quality", param)==0) {
+ cutoff = CUTOFF_QUALITY;
+ }
+ else {
+ cutoff = atof(param);
+ }
+ } else if(strcmp("-ctl", arg)==0 && param != NULL) {
+ i++;
+ control_pipe = param;
+ } else if(strcmp("-raw", arg)==0) { //expect raw input of 44.1khz, 2 channels, 16 bit pcm.
+ i++;
+ raw = 1;
+ } else if(strcmp("-onlystereo", arg)==0) {
+ i++;
+ onlystereo = 1;
+ } else if(strcmp("-nords", arg)==0) {
+ i++;
+ nords = 1;
+ } else if(strcmp("-pty", arg)==0 && param != NULL) {
+ i++;
+ pty = atof(param);
+ } else {
+ fatal("Unrecognised argument: %s.\n"
+ "Syntax: pi_fm_adv [-freq freq] [-audio file] [-ppm ppm_error] [-pi pi_code]\n"
+ " [-ps ps_text] [-rt rt_text] [-pty program type] [-dev deviation] [-raw]\n"
+ " [-cutoff cutoff_freq] [-preemph preemphasis_mode] [-ctl control_pipe] [-onlystereo]\n", arg);
+ }
+ }
+
+ int errcode = tx(carrier_freq, audio_file, pi, ps, rt, ppm, deviation, cutoff, preemphasis_cutoff, control_pipe, raw, pty, onlystereo, nords);
+
+ terminate(errcode);
+}
diff --git a/src/pulses.wav b/src/pulses.wav
new file mode 100644
index 0000000..c910e6c
Binary files /dev/null and b/src/pulses.wav differ
diff --git a/src/rds.c b/src/rds.c
new file mode 100644
index 0000000..c584fed
--- /dev/null
+++ b/src/rds.c
@@ -0,0 +1,297 @@
+/*
+ PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ Copyright (C) 2017 Miegl
+
+ See https://github.com/Miegl/PiFmAdv
+
+ rds_wav.c is a test program that writes a RDS baseband signal to a WAV
+ file. It requires libsndfile.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include "waveforms.h"
+
+#define RT_LENGTH 64
+#define PS_LENGTH 8
+#define GROUP_LENGTH 4
+
+struct {
+ uint16_t pi;
+ int ta;
+ char ps[PS_LENGTH];
+ char rt[RT_LENGTH];
+ int pty;
+} rds_params = { 0 };
+/* Here, the first member of the struct must be a scalar to avoid a
+ warning on -Wmissing-braces with GCC < 4.8.3
+ (bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119)
+*/
+
+/* The RDS error-detection code generator polynomial is
+ x^10 + x^8 + x^7 + x^5 + x^4 + x^3 + x^0
+*/
+#define POLY 0x1B9
+#define POLY_DEG 10
+#define MSB_BIT 0x8000
+#define BLOCK_SIZE 16
+
+#define BITS_PER_GROUP (GROUP_LENGTH * (BLOCK_SIZE+POLY_DEG))
+#define SAMPLES_PER_BIT 192
+#define FILTER_SIZE (sizeof(waveform_biphase)/sizeof(float))
+#define SAMPLE_BUFFER_SIZE (SAMPLES_PER_BIT + FILTER_SIZE)
+
+uint16_t offset_words[] = {0x0FC, 0x198, 0x168, 0x1B4};
+// We don't handle offset word C' here for the sake of simplicity
+
+/* Classical CRC computation */
+uint16_t crc(uint16_t block) {
+ uint16_t crc = 0;
+
+ for(int j=0; j> (POLY_DEG-1)) & 1;
+ crc <<= 1;
+ if((msb ^ bit) != 0) {
+ crc = crc ^ POLY;
+ }
+ }
+
+ return crc;
+}
+
+/* Possibly generates a CT (clock time) group if the minute has just changed
+ Returns 1 if the CT group was generated, 0 otherwise
+*/
+int get_rds_ct_group(uint16_t *blocks) {
+ static int latest_minutes = -1;
+
+ // Check time
+ time_t now;
+ struct tm *utc;
+
+ now = time (NULL);
+ utc = gmtime (&now);
+
+ if(utc->tm_min != latest_minutes) {
+ // Generate CT group
+ latest_minutes = utc->tm_min;
+
+ int l = utc->tm_mon <= 1 ? 1 : 0;
+ int mjd = 14956 + utc->tm_mday +
+ (int)((utc->tm_year - l) * 365.25) +
+ (int)((utc->tm_mon + 2 + l*12) * 30.6001);
+
+ blocks[1] = 0x4400 | (mjd>>15);
+ blocks[2] = (mjd<<1) | (utc->tm_hour>>4);
+ blocks[3] = (utc->tm_hour & 0xF)<<12 | utc->tm_min<<6;
+
+ utc = localtime(&now);
+
+ int offset = utc->tm_gmtoff / (30 * 60);
+ blocks[3] |= abs(offset);
+ if(offset < 0) blocks[3] |= 0x20;
+
+ //printf("Generated CT: %04X %04X %04X\n", blocks[1], blocks[2], blocks[3]);
+ return 1;
+ } else return 0;
+}
+
+/* Creates an RDS group. This generates sequences of the form 0A, 0A, 0A, 0A, 2A, etc.
+ The pattern is of length 5, the variable 'state' keeps track of where we are in the
+ pattern. 'ps_state' and 'rt_state' keep track of where we are in the PS (0A) sequence
+ or RT (2A) sequence, respectively.
+*/
+void get_rds_group(int *buffer) {
+ static int state = 0;
+ static int ps_state = 0;
+ static int rt_state = 0;
+ uint16_t blocks[GROUP_LENGTH] = {rds_params.pi, 0, 0, 0};
+
+ // Generate block content
+ if(! get_rds_ct_group(blocks)) { // CT (clock time) has priority on other group types
+ if(state < 4) {
+ blocks[1] = 0x0400 | ps_state;
+ if(rds_params.ta) blocks[1] |= 0x0010;
+ blocks[2] = 0xCDCD; // no AF
+ blocks[3] = rds_params.ps[ps_state*2]<<8 | rds_params.ps[ps_state*2+1];
+ ps_state++;
+ if(ps_state >= 4) ps_state = 0;
+ } else { // state == 5
+ blocks[1] = 0x2400 | rt_state;
+ blocks[2] = rds_params.rt[rt_state*4+0]<<8 | rds_params.rt[rt_state*4+1];
+ blocks[3] = rds_params.rt[rt_state*4+2]<<8 | rds_params.rt[rt_state*4+3];
+ rt_state++;
+ if(rt_state >= 16) rt_state = 0;
+ }
+
+ state++;
+ if(state >= 5) state = 0;
+ }
+
+ switch(rds_params.pty) {
+ case 0: break;
+ case 1: blocks[1] = (blocks[1] & 0xFC1F) | 0x20; break;
+ case 2: blocks[1] = (blocks[1] & 0xFC1F) | 0x40; break;
+ case 3: blocks[1] = (blocks[1] & 0xFC1F) | 0x60; break;
+ case 4: blocks[1] = (blocks[1] & 0xFC1F) | 0x80; break;
+ case 5: blocks[1] = (blocks[1] & 0xFC1F) | 0xA0; break;
+ case 6: blocks[1] = (blocks[1] & 0xFC1F) | 0xC0; break;
+ case 7: blocks[1] = (blocks[1] & 0xFC1F) | 0xE0; break;
+ case 8: blocks[1] = (blocks[1] & 0xFC1F) | 0x100; break;
+ case 9: blocks[1] = (blocks[1] & 0xFC1F) | 0x120; break;
+ case 10: blocks[1] = (blocks[1] & 0xFC1F) | 0x140; break;
+ case 11: blocks[1] = (blocks[1] & 0xFC1F) | 0x160; break;
+ case 12: blocks[1] = (blocks[1] & 0xFC1F) | 0x180; break;
+ case 13: blocks[1] = (blocks[1] & 0xFC1F) | 0x1A0; break;
+ case 14: blocks[1] = (blocks[1] & 0xFC1F) | 0x1C0; break;
+ case 15: blocks[1] = (blocks[1] & 0xFC1F) | 0x1E0; break;
+ case 16: blocks[1] = (blocks[1] & 0xFC1F) | 0x200; break;
+ case 17: blocks[1] = (blocks[1] & 0xFC1F) | 0x220; break;
+ case 18: blocks[1] = (blocks[1] & 0xFC1F) | 0x240; break;
+ case 19: blocks[1] = (blocks[1] & 0xFC1F) | 0x260; break;
+ case 20: blocks[1] = (blocks[1] & 0xFC1F) | 0x280; break;
+ case 21: blocks[1] = (blocks[1] & 0xFC1F) | 0x2A0; break;
+ case 22: blocks[1] = (blocks[1] & 0xFC1F) | 0x2C0; break;
+ case 23: blocks[1] = (blocks[1] & 0xFC1F) | 0x2E0; break;
+ case 24: blocks[1] = (blocks[1] & 0xFC1F) | 0x300; break;
+ case 25: blocks[1] = (blocks[1] & 0xFC1F) | 0x320; break;
+ case 26: blocks[1] = (blocks[1] & 0xFC1F) | 0x340; break;
+ case 27: blocks[1] = (blocks[1] & 0xFC1F) | 0x360; break;
+ case 28: blocks[1] = (blocks[1] & 0xFC1F) | 0x380; break;
+ case 29: blocks[1] = (blocks[1] & 0xFC1F) | 0x3A0; break;
+ case 30: blocks[1] = (blocks[1] & 0xFC1F) | 0x3C0; break;
+ case 31: blocks[1] = (blocks[1] & 0xFC1F) | 0x3E0; break;
+ }
+
+ // Calculate the checkword for each block and emit the bits
+ for(int i=0; i= SAMPLES_PER_BIT) {
+ if(bit_pos >= BITS_PER_GROUP) {
+ get_rds_group(bit_buffer);
+ bit_pos = 0;
+ }
+
+ // do differential encoding
+ cur_bit = bit_buffer[bit_pos];
+ prev_output = cur_output;
+ cur_output = prev_output ^ cur_bit;
+
+ inverting = (cur_output == 1);
+
+ float *src = waveform_biphase;
+ int idx = in_sample_index;
+
+ for(int j=0; j= SAMPLE_BUFFER_SIZE) idx = 0;
+ }
+
+ in_sample_index += SAMPLES_PER_BIT;
+ if(in_sample_index >= SAMPLE_BUFFER_SIZE) in_sample_index -= SAMPLE_BUFFER_SIZE;
+
+ bit_pos++;
+ sample_count = 0;
+ }
+
+ float sample = sample_buffer[out_sample_index];
+ sample_buffer[out_sample_index] = 0;
+ out_sample_index++;
+ if(out_sample_index >= SAMPLE_BUFFER_SIZE) out_sample_index = 0;
+
+
+ // modulate at 57 kHz
+ // use phase for this
+ switch(phase) {
+ case 0:
+ case 2: sample = 0; break;
+ case 1: break;
+ case 3: sample = -sample; break;
+ }
+ phase++;
+ if(phase >= 4) phase = 0;
+
+ *buffer++ = sample;
+ sample_count++;
+ }
+}
+
+void set_rds_pi(uint16_t pi_code) {
+ rds_params.pi = pi_code;
+}
+
+void set_rds_rt(char *rt) {
+ strncpy(rds_params.rt, rt, 64);
+ for(int i=0; i<64; i++) {
+ if(rds_params.rt[i] == 0) rds_params.rt[i] = 32;
+ }
+}
+
+void set_rds_ps(char *ps) {
+ strncpy(rds_params.ps, ps, 8);
+ for(int i=0; i<8; i++) {
+ if(rds_params.ps[i] == 0) rds_params.ps[i] = 32;
+ }
+}
+
+void set_rds_pty(int pty) {
+ rds_params.pty = pty;
+}
+
+void set_rds_ta(int ta) {
+ rds_params.ta = ta;
+}
diff --git a/src/rds.h b/src/rds.h
new file mode 100644
index 0000000..3943d9e
--- /dev/null
+++ b/src/rds.h
@@ -0,0 +1,38 @@
+/*
+ PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ Copyright (C) 2017 Miegl
+
+ See https://github.com/Miegl/PiFmAdv
+
+ rds_wav.c is a test program that writes a RDS baseband signal to a WAV
+ file. It requires libsndfile.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef RDS_H
+#define RDS_H
+
+
+#include
+
+extern void get_rds_samples(float *buffer, int count);
+extern void set_rds_pi(uint16_t pi_code);
+extern void set_rds_rt(char *rt);
+extern void set_rds_ps(char *ps);
+extern void set_rds_ta(int ta);
+void set_rds_pty(int pty);
+
+
+#endif /* RDS_H */
diff --git a/src/rds_wav.c b/src/rds_wav.c
new file mode 100644
index 0000000..d8649f4
--- /dev/null
+++ b/src/rds_wav.c
@@ -0,0 +1,101 @@
+/*
+ PiFmAdv - FM/RDS transmitter for the Raspberry Pi
+ Copyright (C) 2017 Miegl
+
+ See https://github.com/Miegl/PiFmAdv
+
+ rds_wav.c is a test program that writes a RDS baseband signal to a WAV
+ file. It requires libsndfile.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+
+#include
+#include
+#include
+#include
+#include
+
+#include "rds.h"
+#include "fm_mpx.h"
+
+
+#define LENGTH 114000
+
+
+/* Simple test program */
+int main(int argc, char **argv) {
+ if(argc < 4) {
+ fprintf(stderr, "Error: missing argument.\n");
+ fprintf(stderr, "Syntax: rds_wav \n");
+ return EXIT_FAILURE;
+ }
+
+ set_rds_pi(0x1234);
+ set_rds_ps(argv[3]);
+ set_rds_rt(argv[3]);
+
+ char *in_file = argv[1];
+ if(strcmp("NONE", argv[1]) == 0) in_file = NULL;
+
+ if(fm_mpx_open(in_file, LENGTH) != 0) {
+ printf("Could not setup FM mulitplex generator.\n");
+ return EXIT_FAILURE;
+ }
+
+
+
+ // Set the format of the output file
+ SNDFILE *outf;
+ SF_INFO sfinfo;
+
+ sfinfo.frames = LENGTH;
+ sfinfo.samplerate = 228000;
+ sfinfo.channels = 1;
+ sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+ sfinfo.sections = 1;
+ sfinfo.seekable = 0;
+
+ // Open the output file
+ char *out_file = argv[2];
+ if (! (outf = sf_open(out_file, SFM_WRITE, &sfinfo))) {
+ fprintf(stderr, "Error: could not open output file %s.\n", out_file);
+ return EXIT_FAILURE;
+ }
+
+ float mpx_buffer[LENGTH];
+
+ for(int j=0; j<40; j++) {
+ if( fm_mpx_get_samples(mpx_buffer) < 0 ) break;
+
+ // scale samples
+ for(int i=0; i