using cmake to build project and submodules

This commit is contained in:
2021-10-18 17:20:39 +01:00
parent aff6d66622
commit 2faa518b32
52 changed files with 356 additions and 119 deletions

View File

@@ -0,0 +1,149 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
- Regex: '.*'
Priority: 1
SortPriority: 0
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
...

14
RpiLedBars/backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
bin
obj
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
build

View File

@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.12)
# set the project name
project(RpiLedBars VERSION 0.5 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
add_subdirectory(libs)
add_subdirectory(src)

View File

@@ -0,0 +1,87 @@
# RpiLedBars
LedBars version for raspberry pi
## Contains
* a python test program (main.py)
* a C program using :
* ws2812 with dma/smi from Jeremy P Bentham - https://iosoft.blog/category/neopixel/
* artnet from Stephan Ruloff - https://github.com/rstephan/ArtnetnodeWifi
## Install
### I2S Microphone
#### Install dependencies
~~~bash
sudo apt install python3-pip
sudo pip3 install --upgrade adafruit-python-shell
~~~
#### Build and install module
~~~bash
cd /tmp
sudo wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/i2smic.py
sudo python3 i2smic.py
~~~
After a kernel update this step as to be executed again (after reboot -> depends on `uname`)
#### Test microphone
Plug the microphone
~~~bash
sudo reboot
arecord -l
arecord -D plughw:1 -c1 -r 48000 -f S32_LE -t wav -V mono -v file.wav
~~~
#### Control record volume
~~~bash
cp res/.asoundrc ~/.asoundrc
~~~
* Intall i2s mems microphone - https://learn.adafruit.com/adafruit-i2s-mems-microphone-breakout/raspberry-pi-wiring-test
* Configuring alsa - https://makersportal.com/blog/recording-stereo-audio-on-a-raspberry-pi
* Alsa API - http://www.equalarea.com/paul/alsa-audio.html
### Project depenencies
~~~bash
sudo apt install libasound2-dev wiringpi
~~~
#### Cava
~~~bash
sudo apt install libfftw3-dev libasound2-dev libtool automake
cd cava_
./autogen.sh
./configure
make
sudo make install
~~~
### Access point
~~~bash
grep "Access point conf" $(find /etc -type f)
~~~
* https://learn.sparkfun.com/tutorials/setting-up-a-raspberry-pi-3-as-an-access-point/all
## Biblio:
### Art-Net
* Protocol Spec :
* https://art-net.org.uk/how-it-works/
* https://artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf
* ESP32 library from Stephan Ruloff - https://github.com/rstephan/ArtnetnodeWifi
### ws28xx rgb leds
* ws2812 datasheet - https://cdn-shop.adafruit.com/datasheets/WS2812.pdf
* ws2815 datasheet - https://pdf1.alldatasheet.com/datasheet-pdf/view/1134588/WORLDSEMI/WS2815B.html
* ws2812 with dma/smi from Jeremy P Bentham :
* ws2812 - https://iosoft.blog/2020/09/29/raspberry-pi-multi-channel-ws2812/
* dma - https://iosoft.blog/2020/05/25/raspberry-pi-dma-programming/
* smi - https://iosoft.blog/2020/07/16/raspberry-pi-smi/

View File

@@ -0,0 +1,24 @@
[general]
framerate = 60
autosens = 0
sensitivity = 200
bars = 128
[input]
method = alsa
source = plughw:1
[output]
method = raw
channels = mono
mono_option = left
data_format = ascii
ascii_max_range=65535
bit_format = 16bit
[smoothing]
integral = 77
monstercat = 0
waves = 0
gravity = 0

View File

@@ -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

View File

@@ -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

5
RpiLedBars/backend/install.sh Executable file
View File

@@ -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"

View File

@@ -0,0 +1,14 @@
include(FetchContent)
FetchContent_Declare(
logc
GIT_REPOSITORY "https://github.com/Tropicananass/log.c.git"
)
FetchContent_Declare(
ws
GIT_REPOSITORY "https://github.com/Tropicananass/wsServer.git"
)
# add the log.c and ws libraries
FetchContent_MakeAvailable(logc ws)

View File

@@ -0,0 +1,12 @@
[Unit]
Description=pixled
After=network.target
[Service]
ExecStart=pixled -n 60 -d 2
Restart=always
User=root
Group=root
[Install]
WantedBy=multi-user.target

3
RpiLedBars/backend/sgdb.sh Executable file
View File

@@ -0,0 +1,3 @@
#! /bin/bash
sudo /usr/bin/gdb "$@"

View File

@@ -0,0 +1,14 @@
add_subdirectory(tasks)
add_subdirectory(drivers)
# add the executable
add_executable(${PROJECT_NAME}
rpi_midi_controller.c
rpi_param.c
rpi_pattern.c
main.c)
target_link_libraries(${PROJECT_NAME} PRIVATE m asound)
target_link_libraries(${PROJECT_NAME} PRIVATE logc)
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_tasks ${PROJECT_NAME}_drivers)

View File

@@ -0,0 +1,14 @@
# add the executable
add_library(${PROJECT_NAME}_drivers
common.c
dma/rpi_dma.c
dma/rpi_videocore.c
gpio/rpi_gpio.c
leddriver/rpi_leddriver.c
selector/rpi_selector.c
smi/rpi_smi.c)
target_link_libraries(${PROJECT_NAME}_drivers PRIVATE wiringPi)
target_link_libraries(${PROJECT_NAME}_drivers PRIVATE logc)
target_include_directories(${PROJECT_NAME}_drivers PUBLIC dma gpio leddriver selector smi)

View File

@@ -0,0 +1,56 @@
#include "common.h"
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include "log.h"
// Use mmap to obtain virtual address, given physical
void *map_periph(MEM_MAP *mp, void *phys, int size) {
mp->phys = phys;
mp->size = PAGE_ROUNDUP(size);
mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE);
mp->virt = map_segment(phys, mp->size);
return (mp->virt);
}
// Free mapped peripheral or memory
void unmap_periph_mem(MEM_MAP *mp) {
if (mp) {
unmap_segment(mp->virt, mp->size);
}
}
// ----- VIRTUAL MEMORY -----
// Get virtual memory segment for peripheral regs or physical mem
void *map_segment(void *addr, int size) {
int fd;
void *mem;
size = PAGE_ROUNDUP(size);
if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0) {
log_fatal("can't open /dev/mem, run using sudo");
exit(-EXIT_FAILURE);
}
mem = mmap(0, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr);
close(fd);
log_info("Map %p -> %p", (void *)addr, mem);
if (mem == MAP_FAILED) {
log_fatal("can't map memory");
exit(-EXIT_FAILURE);
}
return (mem);
}
// Free mapped memory
void unmap_segment(void *mem, int size) {
if (mem) {
munmap(mem, PAGE_ROUNDUP(size));
}
}

View File

@@ -0,0 +1,50 @@
#if !defined(__COMMON_H__)
#define __COMMON_H__
// Location of peripheral registers in physical memory
#define PHYS_REG_BASE PI_23_REG_BASE
#define PI_01_REG_BASE 0x20000000 // Pi Zero or 1
#define PI_23_REG_BASE 0x3F000000 // Pi 2 or 3
#define PI_4_REG_BASE 0xFE000000 // Pi 4
#define CLOCK_HZ 250000000 // Pi 2 - 4
//#define CLOCK_HZ 400000000 // Pi Zero
// Location of peripheral registers in bus memory
#define BUS_REG_BASE 0x7E000000
// Get virtual 8 and 32-bit pointers to register
#define REG8(m, x) ((volatile uint8_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
#define REG32(m, x) ((volatile uint32_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
// Get bus address of register
#define REG_BUS_ADDR(m, x) ((uint32_t)(m.bus) + (uint32_t)(x))
// Convert uncached memory virtual address to bus address
#define MEM_BUS_ADDR(mp, a) ((uint32_t)a - (uint32_t)mp->virt + (uint32_t)mp->bus)
// Convert bus address to physical address (for mmap)
#define BUS_PHYS_ADDR(a) ((void *)((uint32_t)(a) & ~0xC0000000))
// Size of memory page
#define PAGE_SIZE 0x1000
// Round up to nearest page
#define PAGE_ROUNDUP(n) ((n) % PAGE_SIZE == 0 ? (n) : ((n) + PAGE_SIZE) & ~(PAGE_SIZE - 1))
// Structure for mapped peripheral or memory
typedef struct {
int fd, // File descriptor
h, // Memory handle
size; // Memory size
void *bus, // Bus address
*virt, // Virtual address
*phys; // Physical address
} MEM_MAP;
// Use mmap to obtain virtual address, given physical
void *map_periph(MEM_MAP *mp, void *phys, int size);
// Free mapped peripheral or memory
void unmap_periph_mem(MEM_MAP *mp);
void *map_segment(void *addr, int size);
void unmap_segment(void *addr, int size);
#endif // __COMMON_H__

View File

@@ -0,0 +1,90 @@
#include "rpi_dma.h"
#include <stdio.h>
#include "rpi_videocore.h"
// DMA channels and data requests
#define DMA_SMI_DREQ 4
#define DMA_PWM_DREQ 5
#define DMA_SPI_TX_DREQ 6
#define DMA_SPI_RX_DREQ 7
#define DMA_BASE (PHYS_REG_BASE + 0x007000)
// DMA register addresses offset by 0x100 * chan_num
#define DMA_CS 0x00
#define DMA_CONBLK_AD 0x04
#define DMA_TI 0x08
#define DMA_SRCE_AD 0x0c
#define DMA_DEST_AD 0x10
#define DMA_TXFR_LEN 0x14
#define DMA_STRIDE 0x18
#define DMA_NEXTCONBK 0x1c
#define DMA_DEBUG 0x20
#define DMA_REG(ch, r) ((r) == DMA_ENABLE ? DMA_ENABLE : (ch)*0x100 + (r))
#define DMA_ENABLE 0xff0
// DMA register values
#define DMA_WAIT_RESP (1 << 3)
#define DMA_CB_DEST_INC (1 << 4)
#define DMA_DEST_DREQ (1 << 6)
#define DMA_CB_SRCE_INC (1 << 8)
#define DMA_SRCE_DREQ (1 << 10)
#define DMA_PRIORITY(n) ((n) << 16)
// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
MEM_MAP dma_regs;
char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD",
"TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""};
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad) {
map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE);
DMA_CB *cbs = mp->virt;
*txdata = (uint8_t *)(cbs + offset);
enable_dma(chan);
cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP;
cbs[0].tfr_len = nsamp;
cbs[0].srce_ad = MEM_BUS_ADDR(mp, *txdata);
cbs[0].dest_ad = dest_ad;
}
void dma_close() { unmap_periph_mem(&dma_regs); }
// Enable and reset DMA
void enable_dma(int chan) {
*REG32(dma_regs, DMA_ENABLE) |= (1 << chan);
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
}
// Start DMA, given first control block
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) {
*REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp);
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag
*REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 | csval; // Start DMA
}
// Return remaining transfer length
uint32_t dma_transfer_len(int chan) { return (*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); }
// Check if DMA is active
uint32_t dma_active(int chan) { return ((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); }
// Halt current DMA operation by resetting controller
void stop_dma(int chan) {
if (dma_regs.virt)
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
}
// Display DMA registers
void disp_dma(int chan) {
volatile uint32_t *p = REG32(dma_regs, DMA_REG(chan, DMA_CS));
int i = 0;
while (dma_regstrs[i][0]) {
printf("%-7s %08X ", dma_regstrs[i++], *p++);
if (i % 5 == 0 || dma_regstrs[i][0] == 0)
printf("\n");
}
}

View File

@@ -0,0 +1,33 @@
#if !defined(__RPI_DMA_H__)
#define __RPI_DMA_H__
#include <stdint.h>
#include "../common.h"
#define DMA_CHAN_A 10
#define DMA_CHAN_B 11
// DMA control block (must be 32-byte aligned)
typedef struct {
uint32_t ti, // Transfer info
srce_ad, // Source address
dest_ad, // Destination address
tfr_len, // Transfer length
stride, // Transfer stride
next_cb, // Next control block
debug, // Debug register, zero in control block
unused;
} DMA_CB __attribute__((aligned(32)));
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad);
void dma_close();
void enable_dma(int chan);
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval);
uint32_t dma_transfer_len(int chan);
uint32_t dma_active(int chan);
void stop_dma(int chan);
void disp_dma(int chan);
#endif // __RPI_DMA_H__

View File

@@ -0,0 +1,128 @@
#include "rpi_videocore.h"
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "log.h"
// Mailbox command/response structure
typedef struct {
uint32_t len, // Overall length (bytes)
req, // Zero for request, 1<<31 for response
tag, // Command number
blen, // Buffer length (bytes)
dlen; // Data length (bytes)
uint32_t uints[32 - 5]; // Data (108 bytes maximum)
} VC_MSG __attribute__((aligned(16)));
void disp_vc_msg(VC_MSG *msgp);
int open_mbox(void);
void close_mbox(int fd);
uint32_t msg_mbox(int fd, VC_MSG *msgp);
void *map_uncached_mem(MEM_MAP *mp, int size);
void videocore_setup(MEM_MAP *mp, int size) { map_uncached_mem(mp, size); }
void videocore_close(MEM_MAP *mp) {
unmap_periph_mem(mp);
if (mp->fd) {
unlock_vc_mem(mp->fd, mp->h);
free_vc_mem(mp->fd, mp->h);
close_mbox(mp->fd);
}
}
// Allocate uncached memory, get bus & phys addresses
void *map_uncached_mem(MEM_MAP *mp, int size) {
void *ret;
mp->size = PAGE_ROUNDUP(size);
mp->fd = open_mbox();
ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 &&
(mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 &&
(mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0
? mp->virt
: 0;
log_info("VC mem handle %u, phys %p, virt %p", 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)
log_error("can't open VC mailbox");
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) {
log_error("VC IOCTL failed");
} else if ((msgp->req & 0x80000000) == 0) {
log_error("VC IOCTL error");
} else if (msgp->req == 0x80000001) {
log_error("VC IOCTL partial error");
} else {
ret = msgp->uints[0];
}
#if DEBUG
disp_vc_msg(msgp);
#endif
return (ret);
}

View File

@@ -0,0 +1,33 @@
#if !defined(__RPI_VIDEOCORE_H__)
#define __RPI_VIDEOCORE_H__
#include <stdint.h>
#include "../common.h"
// Videocore mailbox memory allocation flags, see:
// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
typedef enum {
MEM_FLAG_DISCARDABLE = 1 << 0, // can be resized to 0 at any time. Use for cached data
MEM_FLAG_NORMAL = 0 << 2, // normal allocating alias. Don't use from ARM
MEM_FLAG_DIRECT = 1 << 2, // 0xC alias uncached
MEM_FLAG_COHERENT = 2 << 2, // 0x8 alias. Non-allocating in L2 but coherent
MEM_FLAG_ZERO = 1 << 4, // initialise buffer to all zeros
MEM_FLAG_NO_INIT = 1 << 5, // don't initialise (default is initialise to all ones)
MEM_FLAG_HINT_PERMALOCK = 1 << 6, // Likely to be locked for long periods of time
MEM_FLAG_L1_NONALLOCATING = (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) // Allocating in L2
} VC_ALLOC_FLAGS;
// VC flags for unchached DMA memory
#define DMA_MEM_FLAGS (MEM_FLAG_DIRECT | MEM_FLAG_ZERO)
void videocore_setup(MEM_MAP *mp, int size);
void videocore_close(MEM_MAP *mp);
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags);
void *lock_vc_mem(int fd, int h);
uint32_t unlock_vc_mem(int fd, int h);
uint32_t free_vc_mem(int fd, int h);
uint32_t set_vc_clock(int fd, int id, uint32_t freq);
#endif // __RPI_VIDEOCORE_H__

View File

@@ -0,0 +1,88 @@
#include "rpi_gpio.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include "../common.h"
// GPIO register definitions
#define GPIO_BASE (PHYS_REG_BASE + 0x200000)
#define GPIO_MODE0 0x00
#define GPIO_SET0 0x1c
#define GPIO_CLR0 0x28
#define GPIO_LEV0 0x34
#define GPIO_GPPUD 0x94
#define GPIO_GPPUDCLK0 0x98
#define GPIO_MODE_STRS "IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"
bool isInitialized = false;
// 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() {
if (!isInitialized) {
map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE);
isInitialized = true;
}
}
void gpio_close() {
if (isInitialized) {
unmap_periph_mem(&gpio_regs);
isInitialized = true;
}
}
// 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");
}

View File

@@ -0,0 +1,26 @@
#if !defined(__RPI_GPIO_H__)
#define __RPI_GPIO_H__
// GPIO I/O definitions
#define GPIO_IN 0
#define GPIO_OUT 1
#define GPIO_ALT0 4
#define GPIO_ALT1 5
#define GPIO_ALT2 6
#define GPIO_ALT3 7
#define GPIO_ALT4 3
#define GPIO_ALT5 2
#define GPIO_NOPULL 0
#define GPIO_PULLDN 1
#define GPIO_PULLUP 2
void gpio_setup();
void gpio_close();
void gpio_pull(int pin, int pull);
void gpio_mode(int pin, int mode);
#endif // __RPI_GPIO_H__

View File

@@ -0,0 +1,210 @@
#include "rpi_leddriver.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "../../rpi_param.h"
#include "../common.h"
#include "../dma/rpi_dma.h"
#include "../dma/rpi_videocore.h"
#include "../gpio/rpi_gpio.h"
#include "../smi/rpi_smi.h"
#if PHYS_REG_BASE == PI_4_REG_BASE // Timings for RPi v4 (1.5 GHz)
#define SMI_TIMING 10, 15, 30, 15 // 400 ns cycle time
#else // Timings for RPi v0-3 (1 GHz)
#define SMI_TIMING 10, 10, 20, 10 // 400 ns cycle time
#endif
#define TX_TEST 0 // If non-zero, use dummy Tx data
#define LED_NBITS 24 // Number of data bits per LED
#define LED_PREBITS 4 // Number of zero bits before LED data
#define LED_POSTBITS 4 // Number of zero bits after LED data
#define BIT_NPULSES 3 // Number of O/P pulses per LED bit
// Length of data for 1 row (1 LED on each channel)
#define LED_DLEN (LED_NBITS * BIT_NPULSES)
// Transmit data type, 8 or 16 bits
#if LED_NCHANS > 8
#define TXDATA_T uint16_t
#else
#define TXDATA_T uint8_t
#endif
// Ofset into Tx data buffer, given LED number in chan
#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n)))
// Size of data buffers & NV memory, given number of LEDs per chan
#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS)
#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T))
#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS))
/* Global */
MEM_MAP vc_mem;
TXDATA_T *txdata;
TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)];
void swap_bytes();
void leddriver_setup() {
videocore_setup(&vc_mem, VC_MEM_SIZE);
gpio_setup();
smi_setup(LED_NCHANS, SMI_TIMING, &vc_mem, TX_BUFF_LEN(CHAN_MAXLEDS), &txdata);
}
void leddriver_close() {
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++;
}
}
/* source :
* https://github.com/adafruit/Adafruit_NeoPixel/blob/216ccdbff399750f5b02d4cc804c598399e39713/Adafruit_NeoPixel.cpp#L2414
*/
uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {
uint8_t r, g, b;
// Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
// 0 is not the start of pure red, but the midpoint...a few values above
// zero and a few below 65536 all yield pure red (similarly, 32768 is the
// midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
// each for red, green, blue) really only allows for 1530 distinct hues
// (not 1536, more on that below), but the full unsigned 16-bit type was
// chosen for hue so that one's code can easily handle a contiguous color
// wheel by allowing hue to roll over in either direction.
hue = (hue * 1530L + 32768) / 65536;
// Because red is centered on the rollover point (the +32768 above,
// essentially a fixed-point +0.5), the above actually yields 0 to 1530,
// where 0 and 1530 would yield the same thing. Rather than apply a
// costly modulo operator, 1530 is handled as a special case below.
// So you'd think that the color "hexcone" (the thing that ramps from
// pure red, to pure yellow, to pure green and so forth back to red,
// yielding six slices), and with each color component having 256
// possible values (0-255), might have 1536 possible items (6*256),
// but in reality there's 1530. This is because the last element in
// each 256-element slice is equal to the first element of the next
// slice, and keeping those in there this would create small
// discontinuities in the color wheel. So the last element of each
// slice is dropped...we regard only elements 0-254, with item 255
// being picked up as element 0 of the next slice. Like this:
// Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0
// Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0
// Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254
// and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
// the constants below are not the multiples of 256 you might expect.
// Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
if (hue < 510) { // Red to Green-1
b = 0;
if (hue < 255) { // Red to Yellow-1
r = 255;
g = hue; // g = 0 to 254
} else { // Yellow to Green-1
r = 510 - hue; // r = 255 to 1
g = 255;
}
} else if (hue < 1020) { // Green to Blue-1
r = 0;
if (hue < 765) { // Green to Cyan-1
g = 255;
b = hue - 510; // b = 0 to 254
} else { // Cyan to Blue-1
g = 1020 - hue; // g = 255 to 1
b = 255;
}
} else if (hue < 1530) { // Blue to Red-1
g = 0;
if (hue < 1275) { // Blue to Magenta-1
r = hue - 1020; // r = 0 to 254
b = 255;
} else { // Magenta to Red-1
r = 255;
b = 1530 - hue; // b = 255 to 1
}
} else { // Last 0.5 Red (quicker than % operator)
r = 255;
g = b = 0;
}
// Apply saturation and value to R,G,B, pack into 32-bit result:
uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
uint16_t s1 = 1 + sat; // 1 to 256; same reason
uint8_t s2 = 255 - sat; // 255 to 0
return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
(((((g * s1) >> 8) + s2) * v1) & 0xff00) | (((((b * s1) >> 8) + s2) * v1) >> 8);
}

View File

@@ -0,0 +1,18 @@
#if !defined(__RPI_LEDDRIVER_H__)
#define __RPI_LEDDRIVER_H__
#include <stdint.h>
void leddriver_setup();
void leddriver_close();
void set_color(uint32_t rgb, int index);
void rgb_txdata(int *rgbs, int index);
void leddriver_refresh();
uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val);
#endif // __RPI_LEDDRIVER_H__

View File

@@ -0,0 +1,65 @@
/** @file rpi_selector.c
* @brief This module is used to manage selector on gpio
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "rpi_selector.h"
#include <stdio.h>
#include <stdlib.h>
#include <wiringPi.h>
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
unsigned const selectorPinNumber = 4;
/* TODO use GPIO function from ../gpio/gipo.h (same pin number) */
int selectorPins[4] = {26, 27, 6, 5};
char modeStr[] = "0 | 0 | 0 | 0 \n";
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void selector_setup() {
wiringPiSetupGpio();
for (size_t i = 0; i < selectorPinNumber; ++i) {
pinMode(selectorPins[i], INPUT);
pullUpDnControl(selectorPins[i], PUD_DOWN);
}
}
int selector_get_position() {
int newPosition = -1;
for (size_t i = 0; i < selectorPinNumber; ++i) {
if (digitalRead(selectorPins[i])) {
newPosition = i;
}
}
return newPosition;
}
char *selector_tostr() {
for (size_t i = 0; i < selectorPinNumber; ++i) {
modeStr[i * 4] = digitalRead(selectorPins[i]) ? '1' : '0';
}
return modeStr;
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/

View File

@@ -0,0 +1,42 @@
/** @file rpi_selector.h
* @brief This module is used to manage selector on gpio
*/
#if !defined(__RPI_SELECTOR_H__)
#define __RPI_SELECTOR_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include <stdbool.h>
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
/**
* @brief setup the selector module
**/
void selector_setup();
/**
* @brief get the selector position
**/
int selector_get_position();
/**
* @brief Get string with selector debug info
**/
char *selector_tostr();
#endif /* __RPI_SELECTOR_H__ */

View File

@@ -0,0 +1,223 @@
#include "rpi_smi.h"
#include <unistd.h>
#include "../dma/rpi_dma.h"
#include "../gpio/rpi_gpio.h"
// GPIO first pin
#define SMI_SD0_PIN 8
// Data widths
#define SMI_8_BITS 0
#define SMI_16_BITS 1
#define SMI_18_BITS 2
#define SMI_9_BITS 3
// Clock registers and values
#define CLK_BASE (PHYS_REG_BASE + 0x101000)
// #define CLK_PWM_CTL 0xa0
// #define CLK_PWM_DIV 0xa4
#define CLK_SMI_CTL 0xb0
#define CLK_SMI_DIV 0xb4
#define CLK_PASSWD 0x5a000000
#define PWM_CLOCK_ID 0xa
// DMA request threshold
#define REQUEST_THRESH 2
// Register definitions
#define SMI_BASE (PHYS_REG_BASE + 0x600000)
#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));
}

View File

@@ -0,0 +1,14 @@
#if !defined(__RPI_SMI_H__)
#define __RPI_SMI_H__
#include "../common.h"
#include <stdint.h>
void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp,
uint8_t **txdata);
void smi_close(int channels);
void start_smi(MEM_MAP *mp);
#endif // __RPI_SMI_H__

View File

@@ -0,0 +1,371 @@
/** @file main.c
* @brief This is the main exectution program
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "log.h"
#include "drivers/leddriver/rpi_leddriver.h"
#include "artnet.h"
#include "cava.h"
#include "selector.h"
#include "websocket.h"
#include "rpi_midi_controller.h"
#include "rpi_param.h"
#include "rpi_pattern.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
/* Command-line parameters */
bool IsTestMode = false;
int logLevel = 2;
int previousMode = -1;
unsigned long mainLoopCycle = 0;
pthread_mutex_t logLockMutex = PTHREAD_MUTEX_INITIALIZER;
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
void parseCommandLineArgs(int argc, char const *argv[]);
void terminate(int sig);
void manage_tasks(int previousMode, int currentMode);
void execute_task(int mode);
void execute_test_mode();
void execute_artnet_mode();
void execute_autonomous_mode();
void execute_manual_mode();
void adjust_loop(struct timespec const *loopStart);
void log_lock_helper(bool lock, void *udata);
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
int main(int argc, char const *argv[]) {
// setup
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: %s\n",
strerror(errno));
}
log_set_lock(log_lock_helper, &logLockMutex);
param_setup();
midi_controller_setup();
leddriver_setup();
websocket_start();
selector_start();
parseCommandLineArgs(argc, argv);
// loop
while (1) {
struct timespec loopStart;
int mode;
clock_gettime(CLOCK_MONOTONIC, &loopStart);
if (IsTestMode) {
if (mainLoopCycle % (180 * 3) < 180) {
mode = 0;
} else if (mainLoopCycle % (180 * 3) < 180 * 2) {
mode = 1;
} else {
mode = 2;
}
} else {
mode = param_access->pixled.mode;
}
/* todo thread ? */
midi_controller_execute();
if (mode != -1) {
if (mode != previousMode) {
log_info("swtching to mode : %d", mode);
manage_tasks(previousMode, mode);
} else {
execute_task(mode);
}
previousMode = mode;
}
adjust_loop(&loopStart);
}
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/
void parseCommandLineArgs(int argc, char const *argv[]) {
int args = 0;
while (argc > ++args) // Process command-line args
{
if (argv[args][0] == '-') {
switch (toupper(argv[args][1])) {
case 'N': // -N: number of LEDs per channel
if (args >= argc - 1) {
log_error("no numeric value");
exit(-EXIT_FAILURE);
} else {
param_access->pixled.chanLedCount = atoi(argv[++args]);
}
break;
case 'D': // -D: debug level
if (args >= argc - 1) {
log_error("no debug level");
exit(-EXIT_FAILURE);
} else {
logLevel = atoi(argv[++args]);
log_set_level(logLevel);
}
break;
case 'T': // -T: test mode
IsTestMode = true;
break;
default: // Otherwise error
log_error("Unrecognised option '%c'\n", argv[args][1]);
fprintf(stderr, "Options:\n"
" -t Test mode (flash LEDs)\n"
" -n num number of LEDs per channel\n"
" -d lvl debug level\n");
exit(-EXIT_FAILURE);
}
}
}
}
void terminate(int sig) {
manage_tasks(previousMode, -1);
leddriver_close();
log_info("Goodbye !");
exit(EXIT_SUCCESS);
}
void manage_tasks(int previousMode, int currentMode) {
/* stop previous bg task */
switch (previousMode) {
case 1:
artnet_stop();
break;
case 2:
case 3:
cava_stop();
break;
default:
break;
}
/* start new bg task */
switch (currentMode) {
case 1:
artnet_start();
break;
case 2:
case 3:
cava_start();
break;
default:
break;
}
}
void execute_task(int mode) {
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:
break;
}
}
// Pointer to uncached Tx data buffer
// TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data
void execute_test_mode() {
// RGB values for test mode (1 value for each of 16 channels)
uint32_t on_rgbs[] = {0x5f0000, 0x005f00, 0x00005f, 0x5f5f00, 0x5f005f, 0x005f5f, 0x5f5f5f};
uint32_t off_rgbs = 0x000000;
static int i = 0, offset = 0;
for (size_t ledIndex = 0; ledIndex < param_access->pixled.chanLedCount; ++ledIndex) {
set_color(ledIndex <= offset % param_access->pixled.chanLedCount ? on_rgbs[i] * .5 : off_rgbs,
ledIndex);
}
leddriver_refresh();
if (offset < param_access->pixled.chanLedCount) {
++offset;
} else {
offset = 0;
if (i < 7) {
++i;
} else {
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 < param_access->pixled.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_autonomous_mode() {
int ret;
uint16_t *buffer;
if ((ret = cava_get_buffer(&buffer)) == 0) {
switch (param_access->auton.pattern) {
case 0:
bounce_led_and_color(buffer, CAVA_BAR_NUMBER);
break;
case 1:
bounce_led(buffer, CAVA_BAR_NUMBER);
break;
case 2:
bounce_led_and_travel(buffer, CAVA_BAR_NUMBER);
break;
default:
break;
}
}
}
unsigned baseLed[LED_NCHANS] = {0};
void execute_manual_mode() {
int ret;
uint16_t *buffer;
if ((ret = cava_get_buffer(&buffer)) == 0) {
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
uint16_t barMax = 0;
// uint16_t hueInterval = UINT16_MAX - (param_access->ledbar[ledBarIndex].hueInterval -
// param_access->ledbar[ledBarIndex].hueBase);
for (size_t cavaBar = 0; cavaBar < CAVA_BAR_NUMBER / LED_NCHANS; ++cavaBar) {
unsigned barIndex = ledBarIndex * CAVA_BAR_NUMBER / LED_NCHANS + cavaBar;
if (barMax < buffer[barIndex]) {
barMax = buffer[barIndex];
}
}
barMax *= param_access->auton.sensitivity * param_access->ledbar[ledBarIndex].sensitivity;
unsigned ledToLight = barMax * param_access->pixled.chanLedCount / 2 / UINT16_MAX;
unsigned long hueShift =
(long)barMax * (long)param_access->ledbar[ledBarIndex].hueInterval / UINT16_MAX;
uint16_t hue = param_access->ledbar[ledBarIndex].hueBase - hueShift;
uint32_t color = ColorHSV(hue, 255, param_access->ledbar[ledBarIndex].luminosity);
for (size_t i = 0; i < ledToLight; ++i) {
rgb_data[i][ledBarIndex] = color;
rgb_txdata(rgb_data[i], i);
}
for (size_t i = ledToLight; i < param_access->pixled.chanLedCount; ++i) {
rgb_data[i][ledBarIndex] = 0x000000;
rgb_txdata(rgb_data[i], i);
}
}
leddriver_refresh();
}
}
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) {
log_trace("cycle %lu, loop remaining time %ld", mainLoopCycle, remainingTimeUs);
usleep(remainingTimeUs);
} else {
log_warn("loop overlap by %06ldus", -remainingTimeUs);
log_info("loop start %ld.%09lds - loop end %ld.%09lds", loopStart->tv_sec, loopStart->tv_nsec,
loopEnd.tv_sec, loopEnd.tv_nsec);
}
++mainLoopCycle;
}
void log_lock_helper(bool lock, void *udata) {
pthread_mutex_t *pLogLockMutex = (pthread_mutex_t *)(udata);
if (lock) {
pthread_mutex_lock(pLogLockMutex);
} else {
pthread_mutex_unlock(pLogLockMutex);
}
}

View File

@@ -0,0 +1,240 @@
/** @file .c
* @brief This module
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "rpi_midi_controller.h"
#include <alsa/asoundlib.h>
#include "log.h"
#include "rpi_param.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
#define C3 48
#define Gd3 56
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
/**
* Boolean for interrupting the main thread loop
*/
static snd_seq_t *seq_handle;
/**
* Boolean for interrupting the main thread loop
*/
static int sys_port;
/**
* Boolean for interrupting the main thread loop
*/
static int in_port;
/* state machine */
int destChannel = 0xf;
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
/**
* @brief
*/
void subscribe_system_port();
/**
* @brief
*
* @param controller
*/
void subscribe_midi_controller(snd_seq_addr_t controller);
/**
* @brief
*
* @param ev
*/
void handle_system_port_events(snd_seq_event_t *ev);
/**
* @brief
*
* @param ev
*/
void handle_in_port_events(snd_seq_event_t *ev);
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void midi_controller_setup() {
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, "sys:in",
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
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 midi_controller_execute() {
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) {
handle_in_port_events(ev);
} else {
log_warn("Unkonwn midi dest port %d", ev->dest.port);
}
}
if (ret < 0 && ret != -EAGAIN) {
SNDERR("snd_seq_event_input");
}
}
void midi_controller_close() {}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/
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)) {
log_info("New port %d:%d", newport->client, newport->port);
subscribe_midi_controller(*newport);
}
}
}
void handle_in_port_events(snd_seq_event_t *ev) {
switch (ev->type) {
case SND_SEQ_EVENT_PGMCHANGE:
if (ev->data.control.value < LED_NCHANS) {
if (ev->data.control.value != destChannel) {
destChannel = ev->data.control.value;
log_info("Control param channel %d", ev->data.control.value);
}
} else if (destChannel != GLOBAL_CHANNEL) {
log_info("Control global param");
destChannel = GLOBAL_CHANNEL;
}
break;
case SND_SEQ_EVENT_NOTEON:
if (ev->data.note.note == C3) {
if (destChannel != GLOBAL_CHANNEL) {
destChannel = GLOBAL_CHANNEL;
log_info("Control global param");
}
} else if (ev->data.note.note >= Gd3) {
set_pattern(ev->data.note.note - Gd3);
} else {
log_debug("NOTEON : ch %#04x - note %d - vel %d", ev->data.note.channel, ev->data.note.note,
ev->data.note.velocity);
}
break;
case SND_SEQ_EVENT_NOTEOFF:
break;
case SND_SEQ_EVENT_CONTROLLER:
switch (ev->data.control.param) {
/* Pots */
case 1:
set_sensitivity(destChannel, ev->data.control.value);
break;
case 2:
set_luminosity(destChannel, ev->data.control.value);
break;
case 3:
set_hue_base(destChannel, ev->data.control.value);
break;
case 4:
set_hue_interval(destChannel, ev->data.control.value);
break;
case 5:
set_gravity(ev->data.control.value);
break;
case 6:
set_saturation(destChannel, ev->data.control.value);
break;
case 7:
set_hue_auto_shift_interval(destChannel, ev->data.control.value);
break;
case 8:
set_hue_auto_shift_speed(destChannel, ev->data.control.value);
break;
/* Pads in CC */
case 26:
set_hue_auto_shift(ev->data.control.value != 0);
break;
default:
log_debug("CONTROLLER : ch %#04x - param %u - value %d", ev->data.control.channel,
ev->data.control.param, ev->data.control.value);
break;
}
break;
default:
log_debug("ev %02d : %#010x - %#010x - %#010x", ev->type, ev->data.raw32.d[0],
ev->data.raw32.d[1], ev->data.raw32.d[2]);
break;
}
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)) {
log_debug("New port %d:%d", newport->client, newport->port);
subscribe_midi_controller(*newport);
}
}
}

View File

@@ -0,0 +1,42 @@
/** @file .h
* @brief This module
*/
#if !defined(__RPI_MIDI_CONTROLLER_H__)
#define __RPI_MIDI_CONTROLLER_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
/**
* @brief
*/
void midi_controller_setup();
/**
* @brief
*/
void midi_controller_execute();
/**
* @brief
*/
void midi_controller_close();
#endif /* __RPI_MIDI_CONTROLLER_H__ */

View File

@@ -0,0 +1,202 @@
/** @file .c
* @brief This module
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "rpi_param.h"
#include <math.h>
#include <pthread.h>
#include <stdlib.h>
#include "log.h"
#include "drivers/selector/rpi_selector.h"
#include "websocket.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
pixled_param_t const dummyPixledParam = {.mode = 0, .chanLedCount = 0};
auton_param_t const dummyAutonParam = {
.sensitivity = 10, .gravity = 80, .pattern = 0, .isHueAutoShiftEnabled = true};
ledbar_param_t const dummyLedbarParam = {.sensitivity = 1.,
.hueBase = 121,
.hueInterval = -100,
.hueAutoShift = 0,
.hueAutoShiftInterval = 0,
.hueAutoShiftSpeed = 1.,
.isIncreasing = false,
.luminosity = 20,
.saturation = 180};
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
param_t param;
pthread_mutex_t paramLockMutex = PTHREAD_MUTEX_INITIALIZER;
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void param_setup() {
param.pixled = dummyPixledParam;
param.auton = dummyAutonParam;
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i] = dummyLedbarParam;
}
param_access = &param;
}
void set_mode(unsigned int mode) {
if (mode != param.pixled.mode) {
pthread_mutex_lock(&paramLockMutex);
param.pixled.mode = mode;
log_debug("Mode : %d", mode);
websocket_send_mode(mode);
pthread_mutex_unlock(&paramLockMutex);
}
}
void set_pattern(unsigned int pattern) {
if (pattern != param.auton.pattern) {
pthread_mutex_lock(&paramLockMutex);
param.auton.pattern = pattern;
log_debug("Pattern : %d", pattern);
pthread_mutex_unlock(&paramLockMutex);
}
}
void set_gravity(int8_t gravity8) {
param.auton.gravity = gravity8 * 100 / INT8_MAX;
log_debug("Gravity : %d", param.auton.gravity);
}
void set_hue_auto_shift(bool HasToBeEnabled) {
if (HasToBeEnabled != param.auton.isHueAutoShiftEnabled) {
param.auton.isHueAutoShiftEnabled = HasToBeEnabled;
log_debug("Hue auto shift : %s", HasToBeEnabled ? "Enabled" : "Disabled");
if (!HasToBeEnabled) {
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i].hueAutoShift = 0;
}
}
}
}
void set_sensitivity(int channel, int8_t sensitivity8) {
float sensitivity = pow(1.065, sensitivity8 - INT8_MAX / 2);
if (channel >= LED_NCHANS) {
param.auton.sensitivity = sensitivity;
} else {
param.ledbar[channel].sensitivity = sensitivity;
}
log_debug("Sensitivity : %f", sensitivity);
}
void set_hue_base(int channel, int8_t hueBase8) {
uint16_t hueBase = hueBase8 * HUEBASE_MAX / INT8_MAX;
if (channel < LED_NCHANS) {
param.ledbar[channel].hueBase = hueBase;
} else {
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i].hueBase = hueBase;
}
}
log_debug("Hue base : %u", hueBase);
}
void set_hue_interval(int channel, int8_t hueInterval8) {
int16_t hueInterval = hueInterval8 - ((INT8_MAX + 1) / 2);
if (-30 <= hueInterval && hueInterval <= 30) {
hueInterval = hueInterval * (HUEINTERVAL_MAX + 1) / 30;
} else if (hueInterval <= -30) {
hueInterval = 22 * (hueInterval + 30) - HUEINTERVAL_MAX;
} else {
hueInterval = 22 * (hueInterval - 30) + HUEINTERVAL_MAX;
}
if (channel < LED_NCHANS) {
param.ledbar[channel].hueInterval = hueInterval;
} else {
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i].hueInterval = hueInterval;
}
}
log_debug("Hue interval [%2d] : %d", channel, hueInterval);
}
void set_hue_auto_shift_interval(int channel, int8_t hueInterval8) {
int16_t hueInterval = hueInterval8 - ((INT8_MAX + 1) / 2);
if (-30 <= hueInterval && hueInterval <= 30) {
hueInterval = hueInterval * (HUEINTERVAL_MAX + 1) / 30;
} else if (hueInterval <= -30) {
hueInterval = 22 * (hueInterval + 30) - HUEINTERVAL_MAX;
} else {
hueInterval = 22 * (hueInterval - 30) + HUEINTERVAL_MAX;
}
if (channel < LED_NCHANS) {
param.ledbar[channel].hueAutoShiftInterval = hueInterval;
} else {
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i].hueAutoShiftInterval = hueInterval;
}
}
log_debug("Hue auto shift interval [%2d] : %d", channel, hueInterval);
}
void set_hue_auto_shift_speed(int channel, int8_t hueSpeed8) {
float hueSpeed = pow(1.065, hueSpeed8 - INT8_MAX / 2);
if (channel < LED_NCHANS) {
param.ledbar[channel].hueAutoShiftSpeed = hueSpeed;
} else {
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i].hueAutoShiftSpeed = hueSpeed;
}
}
log_debug("Hue auto shift speed [%2d] : %d", channel, hueSpeed);
}
void set_luminosity(int channel, int8_t luminosity8) {
uint8_t luminosity = luminosity8 * UINT8_MAX / INT8_MAX;
if (channel < LED_NCHANS) {
param.ledbar[channel].luminosity = luminosity;
} else {
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i].luminosity = luminosity;
}
}
log_debug("Luminosity : %u", luminosity);
}
void set_saturation(int channel, int8_t saturation8) {
uint8_t saturation = saturation8 * UINT8_MAX / INT8_MAX;
if (channel < LED_NCHANS) {
param.ledbar[channel].saturation = saturation;
} else {
for (size_t i = 0; i < LED_NCHANS; ++i) {
param.ledbar[i].saturation = saturation;
}
}
log_debug("Saturation : %u", saturation);
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/

View File

@@ -0,0 +1,144 @@
/** @file .h
* @brief This module
*/
#if !defined(__RPI_PARAM_H__)
#define __RPI_PARAM_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include <stdbool.h>
#include <stdint.h>
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
#define CHAN_MAXLEDS 60 // Maximum number of LEDs per channel
#define LED_NCHANS 8 // Number of LED channels (8 or 16)
#define GLOBAL_CHANNEL 15
#define HUE_MAX 359
#define HUEBASE_MIN 0
#define HUEBASE_MAX HUE_MAX
#define HUEINTERVAL_MIN -HUE_MAX
#define HUEINTERVAL_MAX HUE_MAX
#define LUMINOSITY_MIN 0
#define LUMINOSITY_MAX 255
#define SATURATION_MIN 0
#define SATURATION_MAX 255
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/**
*
*/
typedef struct {
int mode;
unsigned int chanLedCount;
} pixled_param_t;
/**
*
*/
typedef struct {
float sensitivity;
unsigned int gravity;
unsigned int pattern;
bool isHueAutoShiftEnabled;
} auton_param_t;
/**
*
*/
typedef struct {
float sensitivity;
unsigned int hueBase;
int hueInterval;
unsigned int hueAutoShift;
int hueAutoShiftInterval;
float hueAutoShiftSpeed;
bool isIncreasing;
unsigned int luminosity;
unsigned int saturation;
} ledbar_param_t;
/**
*
*/
typedef struct {
pixled_param_t pixled;
auton_param_t auton;
ledbar_param_t ledbar[LED_NCHANS];
} param_t;
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
param_t *param_access;
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
/**
* @brief
*/
void param_setup();
/**
* @brief
*
* @param[in] mode
*/
void set_mode(unsigned int mode);
/**
* @brief
*
* @param[in] pattern
*/
void set_pattern(unsigned int pattern);
/**
* @brief
*
* @param[in] HasToBeEnabled
*/
void set_hue_auto_shift(bool HasToBeEnabled);
/**
* @brief
*
* @param[in] gravity8
*/
void set_gravity(int8_t gravity8);
/**
* @brief
*
* @param[in] channel
*
* @param[in] sensitivity8
*/
void set_sensitivity(int channel, int8_t sensitivity8);
void set_hue_base(int channel, int8_t hueBase8);
void set_hue_interval(int channel, int8_t hueInterval8);
void set_hue_auto_shift_interval(int channel, int8_t hueInterval8);
void set_hue_auto_shift_speed(int channel, int8_t hueSpeed8);
void set_luminosity(int channel, int8_t luminosity8);
void set_saturation(int channel, int8_t saturation8);
#endif /* __RPI_PARAM_H__ */

View File

@@ -0,0 +1,259 @@
/** @file .c
* @brief This module
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "rpi_pattern.h"
#include <stdlib.h>
#include "log.h"
#include "drivers/leddriver/rpi_leddriver.h"
#include "rpi_param.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
/**
* Index to channel translattion
*/
int channelTranslation[LED_NCHANS] = {0, 1, 2, 3, 4, 6, 7, 5};
/**
* RGB data buffer. Persistency is not needed but, as of it size, it must be declared in heap
*/
int rgbData[CHAN_MAXLEDS][LED_NCHANS];
/**
* Number of led previously lighted per led bar
*/
int lightedLedCountArray[LED_NCHANS];
/**
* Index of first previously lighted per led bar
*/
int baseLedIndexArray[LED_NCHANS];
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
uint16_t get_max_on_interval(uint16_t *array, unsigned int length, size_t intervalMin,
size_t intervalSize);
uint16_t apply_sensitivity(uint16_t value, unsigned int valueIndex);
unsigned int get_led_to_light_count(uint16_t barValue, unsigned int barIndex);
unsigned int get_first_led_to_light(unsigned int barIndex);
uint32_t get_hsv_color(unsigned int barIndex, float shiftRatio);
void increment_autoshift(unsigned int barIndex);
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void bounce_led_and_color(uint16_t *spectrumArray, unsigned int spectrumLength) {
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
size_t intervalSize = spectrumLength / LED_NCHANS;
uint16_t barMax = 0;
unsigned int ledToLightCount = 0;
barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
intervalSize);
barMax = apply_sensitivity(barMax, ledBarIndex);
ledToLightCount = get_led_to_light_count(barMax, ledBarIndex);
uint32_t color = get_hsv_color(ledBarIndex, (ledToLightCount - 1) /
(float)param_access->pixled.chanLedCount);
for (size_t i = 0; i < ledToLightCount; ++i) {
rgbData[i][ledBarIndex] = color;
rgb_txdata(rgbData[i], i);
}
for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
rgbData[i][ledBarIndex] = 0x000000;
rgb_txdata(rgbData[i], i);
}
increment_autoshift(ledBarIndex);
}
leddriver_refresh();
}
void bounce_led(uint16_t *spectrumArray, unsigned int spectrumLength) {
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
size_t intervalSize = spectrumLength / LED_NCHANS;
uint16_t barMax = 0;
unsigned int ledToLightCount = 0;
barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
intervalSize);
barMax = apply_sensitivity(barMax, ledBarIndex);
ledToLightCount = get_led_to_light_count(barMax, ledBarIndex);
for (size_t i = 0; i < ledToLightCount; ++i) {
uint32_t color = get_hsv_color(ledBarIndex, i / (float)param_access->pixled.chanLedCount);
rgbData[i][ledBarIndex] = color;
rgb_txdata(rgbData[i], i);
}
for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
rgbData[i][ledBarIndex] = 0x000000;
rgb_txdata(rgbData[i], i);
}
increment_autoshift(ledBarIndex);
}
leddriver_refresh();
}
void bounce_led_and_travel(uint16_t *spectrumArray, unsigned int spectrumLength) {
for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
size_t intervalSize = spectrumLength / LED_NCHANS;
uint16_t barMax = 0;
unsigned int ledToLightCount = 0, firstLedToLight = get_first_led_to_light(ledBarIndex);
uint32_t color = 0;
barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
intervalSize);
barMax = apply_sensitivity(barMax, ledBarIndex) / 4;
ledToLightCount = get_led_to_light_count(barMax, ledBarIndex) + 1;
if (!param_access->auton.isHueAutoShiftEnabled) {
color = get_hsv_color(ledBarIndex,
(ledToLightCount - 1) * 4L / (float)param_access->pixled.chanLedCount);
}
for (size_t i = 0; i < ledToLightCount; ++i) {
unsigned int ledIndex = firstLedToLight + i;
ledIndex = ledIndex < param_access->pixled.chanLedCount
? ledIndex
: ledIndex - param_access->pixled.chanLedCount;
if (param_access->auton.isHueAutoShiftEnabled) {
color = get_hsv_color(ledBarIndex, ledIndex / (float)param_access->pixled.chanLedCount);
}
rgbData[ledIndex][ledBarIndex] = color;
rgb_txdata(rgbData[ledIndex], ledIndex);
}
for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
unsigned int ledIndex = firstLedToLight + i;
ledIndex = ledIndex < param_access->pixled.chanLedCount
? ledIndex
: ledIndex - param_access->pixled.chanLedCount;
rgbData[ledIndex][ledBarIndex] = 0x000000;
rgb_txdata(rgbData[ledIndex], ledIndex);
}
increment_autoshift(ledBarIndex);
}
leddriver_refresh();
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/
uint16_t get_max_on_interval(uint16_t *array, unsigned int length, size_t intervalMin,
size_t intervalSize) {
uint16_t barMax = 0;
for (size_t i = 0; i < intervalSize; ++i) {
unsigned barIndex = intervalMin + i;
if (barMax < array[barIndex]) {
barMax = array[barIndex];
}
}
return barMax;
}
uint16_t apply_sensitivity(uint16_t value, unsigned int valueIndex) {
return value * param_access->auton.sensitivity * param_access->ledbar[valueIndex].sensitivity;
}
unsigned int get_led_to_light_count(uint16_t barValue, unsigned int barIndex) {
unsigned int nbLightedLed = lightedLedCountArray[barIndex];
unsigned int ledToLightCount = barValue * param_access->pixled.chanLedCount / UINT16_MAX;
if (ledToLightCount < nbLightedLed) {
/* apply gravity */
ledToLightCount = ((ledToLightCount * (100 - param_access->auton.gravity)) +
(nbLightedLed * param_access->auton.gravity)) /
100;
}
lightedLedCountArray[barIndex] = ledToLightCount;
return ledToLightCount;
}
unsigned int get_first_led_to_light(unsigned int barIndex) {
unsigned int firstLed = baseLedIndexArray[barIndex];
if (lightedLedCountArray[barIndex] != 0) {
firstLed += lightedLedCountArray[barIndex] / 4 + 1;
firstLed = firstLed < param_access->pixled.chanLedCount
? firstLed
: firstLed - param_access->pixled.chanLedCount;
}
baseLedIndexArray[barIndex] = firstLed;
return firstLed;
}
uint32_t get_hsv_color(unsigned int barIndex, float shiftRatio) {
int hueShift = shiftRatio * param_access->ledbar[barIndex].hueInterval;
uint16_t hue = param_access->ledbar[barIndex].hueBase +
(param_access->ledbar[barIndex].hueAutoShift) + hueShift;
// log_trace("hueAutoShift : %010x, hueShift : %d, hue %u",
// param_access->ledbar[barIndex].hueAutoShift, hueShift, hue);
uint32_t color =
ColorHSV(hue * UINT16_MAX / HUEBASE_MAX, param_access->ledbar[barIndex].saturation,
param_access->ledbar[barIndex].luminosity);
return color;
}
void increment_autoshift(unsigned int barIndex) {
static float increment[LED_NCHANS];
if (param_access->auton.isHueAutoShiftEnabled) {
int const hueInterval = param_access->ledbar[barIndex].hueAutoShiftInterval;
int hueAutoShift = param_access->ledbar[barIndex].hueAutoShift;
if ((hueInterval > 0 && param_access->ledbar[barIndex].isIncreasing) ||
(hueInterval < 0 && !param_access->ledbar[barIndex].isIncreasing)) {
increment[barIndex] += param_access->ledbar[barIndex].hueAutoShiftSpeed;
} else if (hueInterval != 0) {
increment[barIndex] -= param_access->ledbar[barIndex].hueAutoShiftSpeed;
}
if (abs(increment[barIndex]) > 1) {
hueAutoShift += increment[barIndex];
increment[barIndex] = increment[barIndex] - (int)increment[barIndex];
}
if ((hueInterval > 0 && hueAutoShift < 0) || (hueInterval < 0 && hueAutoShift > 0)) {
hueAutoShift = -hueAutoShift;
param_access->ledbar[barIndex].isIncreasing = !param_access->ledbar[barIndex].isIncreasing;
} else if ((hueInterval > 0 && hueAutoShift > hueInterval) ||
(hueInterval < 0 && hueAutoShift < hueInterval)) {
hueAutoShift = hueInterval;
param_access->ledbar[barIndex].isIncreasing = !param_access->ledbar[barIndex].isIncreasing;
}
if (barIndex == 0) {
log_trace("hueInterval : %d, hueAutoShift : %d, increase : %s", hueInterval, hueAutoShift,
param_access->ledbar[barIndex].isIncreasing ? "yes" : "no");
}
param_access->ledbar[barIndex].hueAutoShift = hueAutoShift;
}
}

View File

@@ -0,0 +1,57 @@
/** @file .h
* @brief This module
*/
#if !defined(__RPI_PATTERN_H__)
#define __RPI_PATTERN_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include <stdint.h>
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
/**
* @brief
*
* @param[in] spectrumArray array of value from spectral analysis
*
* @param[in] spectrumLength length of spectrum array
*/
void bounce_led_and_color(uint16_t *spectrumArray, unsigned int spectrumLength);
/**
* @brief
*
* @param[in] spectrumArray array of value from spectral analysis
*
* @param[in] spectrumLength length of spectrum array
*/
void bounce_led(uint16_t *spectrumArray, unsigned int spectrumLength);
/**
* @brief
*
* @param[in] spectrumArray array of value from spectral analysis
*
* @param[in] spectrumLength length of spectrum array
*/
void bounce_led_and_travel(uint16_t *spectrumArray, unsigned int spectrumLength);
#endif /* __RPI_PATTERN_H__ */

View File

@@ -0,0 +1,11 @@
# add the executable
add_library(${PROJECT_NAME}_tasks
artnet/artnet.c
cava/cava.c
selector/selector.c
websocket/websocket.c)
target_link_libraries(${PROJECT_NAME}_tasks PRIVATE pthread)
target_link_libraries(${PROJECT_NAME}_tasks PRIVATE logc ws)
target_include_directories(${PROJECT_NAME}_tasks PUBLIC includes)

View File

@@ -0,0 +1,198 @@
/** @file rpi_artnet.c
* @brief This module contains continuous tasks for artnet node communications.
*
* This is the implementation file.
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "artnet.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "artnet_packets.h"
#include "artnet_utils.h"
#include "log.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
/**
* Boolean for interrupting the main thread loop
*/
bool isUdpListenerRunning = false;
/**
* Task thread identifier
*/
pthread_t udpListener;
/**
* Buffer for storing artnet dmx data received
*/
uint8_t artDmxBufferArray[16][512];
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
/**
* @brief This function is used to thread main execution function
*
* @param arg not used.
*
* @return NULL.
*/
static void *artnet_udp_handler(void *arg);
/**
* @brief This function is used to send an artnet poll reply packet
*
* @param[in] fdUdpSocket The UDP socket file descriptor.
*
* @param[in] senderAddr The adress of poll emiter.
*/
static void artnet_send_poll_reply(int fdUdpSocket, struct sockaddr_in senderAddr);
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void artnet_start() {
isUdpListenerRunning = true;
if (pthread_create(&udpListener, NULL, artnet_udp_handler, NULL) < 0) {
log_error("pthread_create: %s", strerror(errno));
}
}
void artnet_stop() {
isUdpListenerRunning = false;
if (pthread_join(udpListener, NULL) != 0) {
log_error("pthread_join: %s", strerror(errno));
}
}
int artnet_get_dmx_data(unsigned int univerve, uint8_t *dmxData[]) {
if (univerve > 8) {
log_error("Universe %d out of bounds %d\n", univerve, 16);
*dmxData = NULL;
return -1;
}
*dmxData = artDmxBufferArray[univerve];
log_trace("%d;%d;%d;%d", (*dmxData)[0], (*dmxData)[1], (*dmxData)[2], (*dmxData)[3]);
return 0;
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/
static void *artnet_udp_handler(void *arg) {
int fdUdpSocket = -1;
struct pollfd pollFdArray[1];
int timeoutMs;
int flags;
char buffer[ARTNET_MAX_BUFFER];
struct sockaddr_in serverAddr, srcAddr;
socklen_t srcLen = sizeof(struct sockaddr_in);
/* Create UDP socket */
fdUdpSocket = socket(PF_INET, SOCK_DGRAM, 0);
if (fdUdpSocket < 0) {
log_error("Opening socket failed: %s", strerror(errno));
}
/* Set non-blocking socket */
flags = fcntl(fdUdpSocket, F_GETFL, 0);
fcntl(fdUdpSocket, F_SETFL, flags | O_NONBLOCK);
/* pollfd structure and timeout */
memset(pollFdArray, 0, sizeof(pollFdArray));
pollFdArray[0].fd = fdUdpSocket;
pollFdArray[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(fdUdpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
while (isUdpListenerRunning) {
int pollReturn;
if ((pollReturn = poll(pollFdArray, 1, timeoutMs)) == 1) {
ssize_t bufferLen =
recvfrom(fdUdpSocket, 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(fdUdpSocket, srcAddr);
break;
default:
break;
}
}
}
} else if (pollReturn < 0) {
log_error("error polling %d: %s", fdUdpSocket, strerror(errno));
}
}
close(fdUdpSocket);
return NULL;
}
static void artnet_send_poll_reply(int fdUdpSocket, struct sockaddr_in senderAddr) {
/* Configure settings in address struct */
senderAddr.sin_family = AF_INET;
senderAddr.sin_port = htons(ARTNET_PORT);
memset(senderAddr.sin_zero, '\0', sizeof(senderAddr.sin_zero));
if (sendto(fdUdpSocket, (uint8_t *)&artPollReply, sizeof(artPollReply_t), 0,
(struct sockaddr *)&senderAddr, sizeof(senderAddr)) < 0) {
log_error("%s: sending poll reply to \"%s:%d\": %s", inet_ntoa(senderAddr.sin_addr),
senderAddr.sin_port, strerror(errno));
}
}

View File

@@ -0,0 +1,128 @@
#if !defined(__RPI_ARTNET_OP_CODES_H__)
#define __RPI_ARTNET_OP_CODES_H__
/* List of hex values and discriptions of Opcodes */
/* This is an ArtPoll packet, no other data is contained in this UDP packet */
#define OpPoll 0x2000
/* This is an ArtPollReply Packet. It contains device status information. */
#define OpPollReply 0x2100
/* Diagnostics and data logging packet. */
#define OpDiagData 0x2300
/* Used to send text based parameter commands. */
#define OpCommand 0x2400
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
* Universe. */
#define OpOutput 0x5000
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
* Universe. */
#define OpDmx 0x5000
/* This is an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information
* for a single Universe. */
#define OpNzs 0x5100
/* This is an ArtAddress packet. It contains remote programming information for a Node. */
#define OpAddress 0x6000
/* This is an ArtInput packet. It contains enable disable data for DMX inputs. */
#define OpInput 0x7000
/* This is an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM
* discovery. */
#define OpTodRequest 0x8000
/* This is an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery. */
#define OpTodData 0x8100
/* This is an ArtTodControl packet. It is used to send RDM discovery control messages. */
#define OpTodControl 0x8200
/* This is an ArtRdm packet. It is used to send all non discovery RDM messages. */
#define OpRdm 0x8300
/* This is an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data. */
#define OpRdmSub 0x8400
/* This is an ArtVideoSetup packet. It contains video screen setup information for nodes that
* implement the extended video features. */
#define OpVideoSetup 0xa010
/* This is an ArtVideoPalette packet. It contains colour palette setup information for nodes that
* implement the extended video features. */
#define OpVideoPalette 0xa020
/* This is an ArtVideoData packet. It contains display data for nodes that implement the extended
* video features. */
#define OpVideoData 0xa040
/* This is an ArtMacMaster packet. It is used to program the Nodes MAC address, Oem device type and
* ESTA manufacturer code. This is for factory initialisation of a Node. It is not to be used by
* applications. */
#define OpMacMaster 0xf000
/* This is an ArtMacSlave packet. It is returned by the node to acknowledge receipt of an
* ArtMacMaster packet. */
#define OpMacSlave 0xf100
/* This is an ArtFirmwareMaster packet. It is used to upload new firmware or firmware extensions to
* the Node. */
#define OpFirmwareMaster 0xf200
/* This is an ArtFirmwareReply packet. It is returned by the node to acknowledge receipt of an
* ArtFirmwareMaster packet or ArtFileTnMaster packet. */
#define OpFirmwareReply 0xf300
/* Uploads user file to node. */
#define OpFileTnMaster 0xf400
/* Downloads user file from node. */
#define OpFileFnMaster 0xf500
/* Node acknowledge for downloads. */
#define OpFileFnReply 0xf600
/* This is an ArtIpProg packet. It is used to reprogramme the IP, Mask and Port address of the Node.
*/
#define OpIpProg 0xf800
/* This is an ArtIpProgReply packet. It is returned by the node to acknowledge receipt of an
* ArtIpProg packet. */
#define OpIpProgReply 0xf900
/* This is an ArtMedia packet. It is Unicast by a Media Server and acted upon by a Controller. */
#define OpMedia 0x9000
/* This is an ArtMediaPatch packet. It is Unicast by a Controller and acted upon by a Media Server.
*/
#define OpMediaPatch 0x9100
/* This is an ArtMediaControl packet. It is Unicast by a Controller and acted upon by a Media
* Server. */
#define OpMediaControl 0x9200
/* This is an ArtMediaControlReply packet. It is Unicast by a Media Server and acted upon by a
* Controller. */
#define OpMediaContrlReply 0x9300
/* This is an ArtTimeCode packet. It is used to transport time code over the network. */
#define OpTimeCode 0x9700
/* Used to synchronise real time date and clock */
#define OpTimeSync 0x9800
/* Used to send trigger macros */
#define OpTrigger 0x9900
/* Requests a node's file list */
#define OpDirectory 0x9a00
/* Replies to OpDirectory with file list */
#define OpDirectoryReply 0x9b00
#endif // __RPI_ARTNET_OP_CODES_H__

View File

@@ -0,0 +1,178 @@
#if !defined(__RPI_ARTNET_PACKETS_H__)
#define __RPI_ARTNET_PACKETS_H__
#include <stdint.h>
#include "artnet_op_codes.h"
#include "artnet_utils.h"
typedef struct {
char id[8];
uint16_t opCode;
} __attribute__((__packed__)) artnetHeader_t;
typedef struct {
artnetHeader_t artnetHeader;
uint16_t protVer;
uint8_t sequence;
uint8_t physical;
uint8_t subUni;
uint8_t net;
uint8_t lengthHi;
uint8_t lengthLo;
uint8_t data[512];
} __attribute__((__packed__)) artDmx_t;
typedef struct {
char ID[8];
uint16_t OpCode;
uint8_t IPAddr[4];
uint16_t Port;
uint16_t VersionInfo; // The node's current FIRMWARE VERS
// Bits 14-8 of the 15 bit universe number are encoded into the bottom 7 bits of this field.
uint8_t NetSwitch;
// This is used in combination with SubSwitch and Swin[] or Swout[] to produce the full universe
// address. Bits 7-4 of the 15 bit universe number are encoded into the bottom 4 bits of this
// field.
uint8_t SubSwitch;
uint16_t Oem; // Manufacturer code, bit 15 set if
// extended features avail
uint8_t UbeaVersion; // Firmware version of UBEA
uint8_t Status;
// bit 0 = 0 UBEA not present
// bit 0 = 1 UBEA present
// bit 1 = 0 Not capable of RDM (Uni-directional DMX)
// bit 1 = 1 Capable of RDM (Bi-directional DMX)
// bit 2 = 0 Booted from flash (normal boot)
// bit 2 = 1 Booted from ROM (possible error condition)
// bit 3 = Not used
// bit 54 = 00 Universe programming authority unknown
// bit 54 = 01 Universe programming authority set by front panel controls
// bit 54 = 10 Universe programming authority set by network
// bit 76 = 00 Indicators Normal
// bit 76 = 01 Indicators Locate
// bit 76 = 10 Indicators Mute
uint8_t EstaMan[2]; // ESTA manufacturer id, lo byte
char ShortName[18]; // short name defaults to IP
char LongName[64];
// Text feedback of Node status or errors also used for debug info
char NodeReport[64];
uint8_t NumPortsHi; // 0
uint8_t NumPortsLo; // 4 If num i/p ports is dif to output ports, return biggest
uint8_t PortTypes[4];
// bit 7 is output
// bit 6 is input
// bits 0-5 are protocol number (0= DMX, 1=MIDI)
// for DMX-Hub ={0xc0,0xc0,0xc0,0xc0};
uint8_t GoodInput[4];
// bit 7 is data received
// bit 6 is data includes test packets
// bit 5 is data includes SIP's
// bit 4 is data includes text
// bit 3 set is input is disabled
// bit 2 is receive errors
// bit 1-0 not used, transmitted as zero.
// Don't test for zero!
uint8_t GoodOutput[4];
// bit 7 is data is transmitting
// bit 6 is data includes test packets
// bit 5 is data includes SIP's
// bit 4 is data includes text
// bit 3 output is merging data.
// bit 2 set if DMX output short detected on power up
// bit 1 set if DMX output merge mode is LTP
// bit 0 not used, transmitted as zero.
uint8_t SwIn[4];
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
// THIS IS FOR INPUT - ART-NET or DMX
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
uint8_t SwOut[4];
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
// data belongs
// THIS IS FOR OUTPUT - ART-NET or DMX.
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
uint8_t SwVideo;
// Low nibble is the value of the video
// output channel
uint8_t SwMacro;
// Bit 0 is Macro input 1
// Bit 7 is Macro input 8
uint8_t SwRemote;
// Bit 0 is Remote input 1
// Bit 7 is Remote input 8
uint8_t Spare1; // Spare, currently zero
uint8_t Spare2; // Spare, currently zero
uint8_t Spare3; // Spare, currently zero
uint8_t Style; // Set to Style code to describe type of equipment
uint8_t Mac[6]; // Mac Address, zero if info not available
uint8_t BindIp[4]; // If this unit is part of a larger or modular product, this is the IP of the
// root device.
uint8_t BindIndex; // Set to zero if no binding, otherwise this number represents the order of
// bound devices. A lower number means closer to root device.
uint8_t Status2;
// bit 0 = 0 Node does not support web browser
// bit 0 = 1 Node supports web browser configuration
// bit 1 = 0 Node's IP address is manually configured
// bit 1 = 1 Node's IP address is DHCP configured
// bit 2 = 0 Node is not DHCP capable
// bit 2 = 1 Node is DHCP capable
// bit 2-7 not implemented, transmit as zero
uint8_t Filler[26]; // Filler bytes, currently zero.
} artPollReply_t;
static artPollReply_t const artPollReply = {
.ID = "Art-Net",
.OpCode = OpPollReply,
.IPAddr = {0},
.Port = ARTNET_PORT,
.VersionInfo = 1,
.NetSwitch = 0,
.SubSwitch = 0,
.Oem = 0x0190,
.UbeaVersion = 0,
.Status = 0,
.EstaMan = {0, 0},
.ShortName = "Tropicananass",
.LongName = "Tropicananass Artnetnode",
.NodeReport = {0},
.NumPortsHi = 0,
.NumPortsLo = 1,
.PortTypes = {0x80, 0, 0, 0},
.GoodInput = {0},
.GoodOutput = {0},
.SwIn = {0},
.SwOut = {0},
.SwVideo = 0,
.SwMacro = 0,
.SwRemote = 0,
.Spare1 = 0,
.Spare2 = 0,
.Spare3 = 0,
.Style = 0,
.Mac = {0},
.BindIp = {0},
.BindIndex = 0,
.Status2 = 0b00000000,
.Filler = {0},
};
#endif // __RPI_ARTNET_PACKETS_H__

View File

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

View File

@@ -0,0 +1,225 @@
/** @file rpi_cava.h
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "cava.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include "log.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/**
* Constant for Maximum char in a line (128 number of (5 char + 1 delim) + \n )
*/
#define MAXLINECHAR CAVA_BAR_NUMBER *(5 + 1) + 1
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
/**
* Cava process id
*/
pid_t cavaPid;
/**
* Boolean for interrupting the main thread loop
*/
bool isFifoReaderRunning = false;
/**
* Task thread identifier
*/
pthread_t fifoReader;
int fdCavaInput;
char lineBuffer[MAXLINECHAR];
uint16_t buffer[128 + 2];
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
/**
* @brief This function is used to thread main execution function
*
* @param arg not used.
*
* @return NULL.
*/
static void *fifo_to_buffer(void *arg);
static int start_cava_process();
static void stop_cava_process();
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void cava_start() {
isFifoReaderRunning = true;
pthread_create(&fifoReader, NULL, fifo_to_buffer, NULL);
}
void cava_stop() {
isFifoReaderRunning = false;
if (pthread_join(fifoReader, NULL) != 0) {
log_error("pthread_join: %s", strerror(errno));
}
}
int cava_get_buffer(uint16_t **buffer_dst) {
*buffer_dst = buffer;
log_trace("%d;%d;%d;%d", buffer[0], buffer[1], buffer[2], buffer[3]);
return 0;
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/
static void *fifo_to_buffer(void *arg) {
struct pollfd fds[1];
int timeoutMs;
int ret;
size_t valueIndex = 0, charOffset = 0;
char strValue[6] = "0\0";
bool hasToBeDiscarded = true;
if ((fdCavaInput = start_cava_process()) < 0) {
log_error("y'a un truc qui a pas marché");
}
memset(fds, 0, sizeof(fds));
fds[0].fd = fdCavaInput;
fds[0].events = POLLIN;
timeoutMs = 10;
while (isFifoReaderRunning) {
if ((ret = poll(fds, 1, timeoutMs)) == 1) {
int nread;
nread = read(fdCavaInput, 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) {
log_warn("Buffer overflow, \\n missed, discarding next");
hasToBeDiscarded = true;
}
}
} else {
log_warn("Unexpected char %d [%c]\n", current, current);
}
}
}
} else {
if (errno != EAGAIN) {
log_error("read: %s", strerror(errno));
}
}
} else if (ret < 0) {
log_error("polling %d: %s\n", fdCavaInput, strerror(errno));
}
}
stop_cava_process();
close(fdCavaInput);
return NULL;
}
static int start_cava_process() {
int fdCavaPipe[2];
if (pipe(fdCavaPipe) < 0) {
log_error("Cava pipe failure: %s", strerror(errno));
return -EXIT_FAILURE;
}
if ((cavaPid = fork()) < 0) {
log_error("Cava fork failure: %s", strerror(errno));
exit(-EXIT_FAILURE);
}
if (cavaPid == 0) {
/* Child process*/
// int fdLogOut;
char *args[] = {"cava", "-p", "/home/pi/LedBars/RpiLedBars/cava_config", NULL};
/* Close reading end of the pipe */
close(fdCavaPipe[0]);
/* Dup writing end of the pipe in place of stdout */
dup2(fdCavaPipe[1], STDOUT_FILENO);
/* Close writing end of the pipe */
close(fdCavaPipe[1]);
/* Open / create a log file for cava */
// fdLogOut = open("/dev/null", O_WRONLY, NULL);
/* Dup file in place of stderr */
// dup2(fdLogOut, STDERR_FILENO);
execvp(args[0], args);
log_error("Cava execvp failure or return: %s", strerror(errno));
exit(-EXIT_FAILURE);
} else {
int flags;
/* Close writing end of the pipe */
close(fdCavaPipe[1]);
/* Set reading end of the pipe non-blocking */
flags = fcntl(fdCavaPipe[0], F_GETFL, 0);
fcntl(fdCavaPipe[0], F_SETFL, flags | O_NONBLOCK);
/* Return reading end of the pipe */
return fdCavaPipe[0];
}
/* Unreachable */
return -1;
}
static void stop_cava_process() {
kill(cavaPid, SIGTERM);
waitpid(0, NULL, WNOHANG);
}

View File

@@ -0,0 +1,50 @@
/** @file artnet.h
* @brief This module contains continuous tasks for artnet node communications.
*
* This is the header file for the definition of services to allow managing thread acting as artnet
* node.
*/
#if !defined(__ARTNET_H__)
#define __ARTNET_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include <stdint.h>
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
/**
* @brief Start artnet node module
**/
void artnet_start();
/**
* @brief Stop artnet node module
*/
void artnet_stop();
/**
* @brief Get last DMX data received for the specified universe
*
* @param[in] univerve The universe to get data from
*
* @param[out] dmxData The pointer to the DMX data array
*/
int artnet_get_dmx_data(unsigned int univerve, uint8_t *dmxData[]);
#endif /* __ARTNET_H__ */

View File

@@ -0,0 +1,37 @@
/** @file cava.h
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
*/
#if !defined(__CAVA_H__)
#define __CAVA_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include <stdint.h>
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
#define CAVA_BAR_NUMBER 128
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
int cava_get_buffer(uint16_t **buffer_dst);
void cava_start();
void cava_stop();
#endif /* __CAVA_H__ */

View File

@@ -0,0 +1,34 @@
/** @file selector.h
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
*/
#if !defined(__SELECTOR_TASK_H__)
#define __SELECTOR_TASK_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
/**
* @brief Start selector listener module
**/
void selector_start();
void selector_stop();
#endif /* __SELECTOR_TASK_H__ */

View File

@@ -0,0 +1,43 @@
/** @file websocket.h
* @brief This module
*/
#if !defined(__WEBSOCKET_H__)
#define __WEBSOCKET_H__
/***************************************************************************************************
* Includes
**************************************************************************************************/
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Global Variables
**************************************************************************************************/
/***************************************************************************************************
* External Function Prototypes
**************************************************************************************************/
/**
* @brief Start websocket module
**/
void websocket_start();
/**
* @brief Start websocket module
**/
void websocket_stop();
/**
* @brief Send mode to websocket client
**/
void websocket_send_mode(int mode);
#endif /* __WEBSOCKET_H__ */

View File

@@ -0,0 +1,87 @@
/** @file selector.c
* @brief This module contains continuous tasks for cava (spectrum analyzer) comunication
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "selector.h"
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include "../../drivers/selector/rpi_selector.h"
#include "../../rpi_param.h"
#include "log.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
#define LISTENER_INTERVAL 500000
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
pthread_t selectorListener;
/**
* Boolean for interrupting the main thread loop
*/
bool isSelectorListenerRunning = false;
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
/**
* @brief This function is used to thread main execution function
*
* @param arg not used.
*
* @return NULL.
*/
static void *listen_to_selector(void *arg);
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void selector_start() {
isSelectorListenerRunning = true;
pthread_create(&selectorListener, NULL, listen_to_selector, NULL);
}
void selector_stop() {
isSelectorListenerRunning = false;
if (pthread_join(selectorListener, NULL) != 0) {
log_error("pthread_join: %s", strerror(errno));
}
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/
static void *listen_to_selector(void *arg) {
int previousPosition;
selector_setup();
while (isSelectorListenerRunning) {
int newPosition = selector_get_position();
if (newPosition != -1 && newPosition != previousPosition) {
set_mode(newPosition);
previousPosition = newPosition;
}
usleep(LISTENER_INTERVAL);
}
return NULL;
}

View File

@@ -0,0 +1,194 @@
/** @file websocket.c
* @brief This module
*/
/***************************************************************************************************
* Includes
**************************************************************************************************/
#include "websocket.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "ws.h"
#include "../../rpi_param.h"
/***************************************************************************************************
* Preprocessor Constants and Macros
**************************************************************************************************/
#define MAX_TOKEN 10
/***************************************************************************************************
* Type and Contant Definitions
**************************************************************************************************/
/***************************************************************************************************
* Persistent Variables
**************************************************************************************************/
int client_fd = -1;
/***************************************************************************************************
* Internal Function Prototypes
**************************************************************************************************/
/**
* @brief Called when a client connects to the server.
*
* @param fd File Descriptor belonging to the client. The @p fd parameter
* is used in order to send messages and retrieve informations
* about the client.
*/
void onopen(int fd);
/**
* @brief Called when a client disconnects to the server.
*
* @param fd File Descriptor belonging to the client. The @p fd parameter
* is used in order to send messages and retrieve informations
* about the client.
*/
void onclose(int fd);
/**
* @brief Called when a client connects to the server.
*
* @param fd File Descriptor belonging to the client. The
* @p fd parameter is used in order to send messages and
* retrieve informations about the client.
*
* @param msg Received message, this message can be a text
* or binary message.
*
* @param size Message size (in bytes).
*
* @param type Message type.
*/
void onmessage(int fd, const unsigned char *msg, uint64_t size, int type);
void mode_command_handler(char *payload);
void pattern_command_handler(char *payload);
void color_command_handler(char *payload);
/***************************************************************************************************
* External Function Definitions
**************************************************************************************************/
void websocket_start() {
struct ws_events evs;
evs.onopen = &onopen;
evs.onclose = &onclose;
evs.onmessage = &onmessage;
ws_socket(&evs, 8080, 1);
}
void websocket_stop() {}
void websocket_send_mode(int mode) {
char msg[] = "m:0";
msg[2] = '0' + mode;
if (client_fd != -1) {
log_debug("Sending mode \"%s\"", msg);
ws_sendframe_txt(client_fd, msg, true);
}
}
/***************************************************************************************************
* Internal Function Definitions
**************************************************************************************************/
void onopen(int fd) {
char *cli;
cli = ws_getaddress(fd);
log_debug("Connection opened, client: %d | addr: %s", fd, cli);
free(cli);
client_fd = fd;
}
void onclose(int fd) {
char *cli;
cli = ws_getaddress(fd);
log_debug("Connection closed, client: %d | addr: %s", fd, cli);
free(cli);
client_fd = -1;
}
void onmessage(int fd, const unsigned char *msg, uint64_t size, int type) {
char msgStr[size + 1];
char *command, *payload;
char *cli = ws_getaddress(fd);
log_trace("Received message: %s (size: %" PRId64 ", type: %d), from: %s/%d", msg, size, type, cli,
fd);
strcpy(msgStr, (char *)msg);
command = strtok(msgStr, ":");
if (command != NULL) {
payload = strtok(NULL, ":");
if (payload != NULL && strlen(command) == 1) {
switch (command[0]) {
case 'm':
mode_command_handler(payload);
break;
case 'p':
pattern_command_handler(payload);
break;
case 'c':
color_command_handler(payload);
break;
default:
log_warn("Unkown command in \"%s\"", msgStr);
break;
}
} else {
log_warn("Empty payload in \"%s\"", msgStr);
}
} else {
log_warn("No command found in \"%s\"", msgStr);
}
free(cli);
}
void mode_command_handler(char *payload) {
int newMode = atoi(payload);
if (0 <= newMode && newMode <= 3) {
set_mode(newMode);
} else {
log_warn("Unknown mode : %s", payload);
}
}
void pattern_command_handler(char *payload) {
int newPattern = atoi(payload);
if (0 <= newPattern && newPattern <= 3) {
set_pattern(newPattern);
} else {
log_warn("Unknown pattern : %s", payload);
}
}
void color_command_handler(char *payload) {
int i = 0;
char *tokenArray[MAX_TOKEN] = {NULL};
tokenArray[i] = strtok(payload, ",");
while (tokenArray[i] && i < MAX_TOKEN - 1) {
tokenArray[++i] = strtok(NULL, ",");
}
log_debug("tokens[%d]", i);
for (size_t n = 0; n < i; ++n) {
log_debug(" - %s", tokenArray[n]);
}
if (atoi(tokenArray[0]) == 0) {
set_hue_base(LED_NCHANS, atoi(tokenArray[1]) * INT8_MAX / HUE_MAX);
set_saturation(LED_NCHANS, atoi(tokenArray[2]) * INT8_MAX / 100);
set_luminosity(LED_NCHANS, atoi(tokenArray[3]) * INT8_MAX / 100);
}
}