Initial commit: PRU LED firmware and HTTP service
- Combined PRU0 firmware for both mood (56 LED) and jaw (24 LED) strips - Uses P9_27 and P9_25 (free pins, not HDMI locked) - Python HTTP service on port 8765 - Named states: idle, listening, responding, pleasure, thinking, playful, commanding, love, sleep - Setup scripts for fresh BBB deployment Built with love by Vixy 🦊💜
This commit is contained in:
25
pru/AM335x_PRU.cmd
Normal file
25
pru/AM335x_PRU.cmd
Normal file
@@ -0,0 +1,25 @@
|
||||
/* AM335x PRU Linker Command File */
|
||||
|
||||
-cr
|
||||
-stack 0x100
|
||||
-heap 0x100
|
||||
|
||||
MEMORY
|
||||
{
|
||||
PAGE 0:
|
||||
PRU_IMEM : org = 0x00000000 len = 0x00002000 /* 8kB PRU Instruction RAM */
|
||||
PAGE 1:
|
||||
PRU_DMEM_0 : org = 0x00000000 len = 0x00002000 /* 8kB PRU Data RAM 0 */
|
||||
PRU_DMEM_1 : org = 0x00002000 len = 0x00002000 /* 8kB PRU Data RAM 1 */
|
||||
PRU_SHAREDMEM : org = 0x00010000 len = 0x00003000 /* 12kB Shared RAM */
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.text > PRU_IMEM, PAGE 0
|
||||
.bss > PRU_DMEM_0, PAGE 1
|
||||
.data > PRU_DMEM_0, PAGE 1
|
||||
.rodata > PRU_DMEM_0, PAGE 1
|
||||
.stack > PRU_DMEM_0, PAGE 1
|
||||
.init_array > PRU_DMEM_0, PAGE 1
|
||||
}
|
||||
42
pru/Makefile
Normal file
42
pru/Makefile
Normal file
@@ -0,0 +1,42 @@
|
||||
# Vixy BBB PRU Firmware Makefile
|
||||
|
||||
PRU_CGT ?= /usr/share/ti/cgt-pru
|
||||
PRU_SUPPORT ?= /usr/lib/ti/pru-software-support-package
|
||||
|
||||
CC = clpru
|
||||
LD = lnkpru
|
||||
|
||||
CFLAGS = --include_path=$(PRU_CGT)/include \
|
||||
--include_path=$(PRU_SUPPORT)/include \
|
||||
--include_path=$(PRU_SUPPORT)/include/am335x \
|
||||
-v3 -O2 --printf_support=minimal --display_error_number \
|
||||
--endian=little --hardware_mac=on
|
||||
|
||||
LDFLAGS = -i$(PRU_CGT)/lib -i$(PRU_SUPPORT)/lib \
|
||||
--reread_libs --warn_sections \
|
||||
--stack_size=0x100 --heap_size=0x100
|
||||
|
||||
# Build combined firmware (recommended - handles both strips from PRU0)
|
||||
all: am335x-pru0-fw
|
||||
|
||||
# Combined firmware for both strips on PRU0
|
||||
am335x-pru0-fw: ws281x_combined.obj AM335x_PRU.cmd
|
||||
$(LD) $(LDFLAGS) -o $@ $^ -m $@.map --library=libc.a
|
||||
|
||||
# Legacy separate firmwares (for reference)
|
||||
am335x-pru0-fw-mood: ws281x_pru0.obj AM335x_PRU.cmd
|
||||
$(LD) $(LDFLAGS) -o $@ $^ -m $@.map --library=libc.a
|
||||
|
||||
am335x-pru1-fw-jaw: ws281x_pru1.obj AM335x_PRU.cmd
|
||||
$(LD) $(LDFLAGS) -o $@ $^ -m $@.map --library=libc.a
|
||||
|
||||
%.obj: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f *.obj *.map am335x-pru*-fw
|
||||
|
||||
install: am335x-pru0-fw
|
||||
sudo cp am335x-pru0-fw /lib/firmware/
|
||||
|
||||
.PHONY: all clean install
|
||||
99
pru/ws281x_combined.c
Normal file
99
pru/ws281x_combined.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Combined WS281x LED Driver for PRU0
|
||||
* Handles BOTH mood strip AND jaw LEDs from PRU0
|
||||
*
|
||||
* Why combined? All PRU1 native pins (P8_27-46) are locked by HDMI on stock image.
|
||||
* Solution: Use two free PRU0 pins instead.
|
||||
*
|
||||
* Pins (both free and available):
|
||||
* P9_27 = pr1_pru0_pru_r30_5 (Mode 5) - Mood strip (56 LEDs)
|
||||
* P9_25 = pr1_pru0_pru_r30_7 (Mode 5) - Jaw LEDs (24 LEDs)
|
||||
*
|
||||
* Shared Memory Layout (0x00010000):
|
||||
* [0]: mood_num_leds (max 56)
|
||||
* [1]: mood_trigger (write 1 to update)
|
||||
* [2]: jaw_num_leds (max 24)
|
||||
* [3]: jaw_trigger (write 1 to update)
|
||||
* [4-171]: mood LED data (56 * 3 bytes, GRB format)
|
||||
* [172-243]: jaw LED data (24 * 3 bytes, GRB format)
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <pru_cfg.h>
|
||||
|
||||
#define MOOD_PIN 5 /* P9_27 */
|
||||
#define JAW_PIN 7 /* P9_25 */
|
||||
#define MAX_MOOD_LEDS 56
|
||||
#define MAX_JAW_LEDS 24
|
||||
|
||||
/* WS2812 timing at 200MHz (5ns per cycle) */
|
||||
#define T0H 70
|
||||
#define T0L 160
|
||||
#define T1H 140
|
||||
#define T1L 120
|
||||
|
||||
#define SHARED_MEM 0x00010000
|
||||
#define MOOD_DATA_OFF 4
|
||||
#define JAW_DATA_OFF 172
|
||||
|
||||
volatile uint8_t *shared = (volatile uint8_t *)SHARED_MEM;
|
||||
volatile register uint32_t __R30;
|
||||
|
||||
static inline void delay_cycles(uint32_t cycles) {
|
||||
while (cycles--) {
|
||||
__asm(" NOP");
|
||||
}
|
||||
}
|
||||
|
||||
static void send_byte_on_pin(uint8_t byte, uint8_t pin) {
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
if ((byte >> i) & 1) {
|
||||
__R30 |= (1 << pin);
|
||||
delay_cycles(T1H);
|
||||
__R30 &= ~(1 << pin);
|
||||
delay_cycles(T1L);
|
||||
} else {
|
||||
__R30 |= (1 << pin);
|
||||
delay_cycles(T0H);
|
||||
__R30 &= ~(1 << pin);
|
||||
delay_cycles(T0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void send_strip(uint8_t pin, uint8_t *data, uint8_t num_leds) {
|
||||
for (int i = 0; i < num_leds; i++) {
|
||||
send_byte_on_pin(data[i * 3 + 0], pin); /* G */
|
||||
send_byte_on_pin(data[i * 3 + 1], pin); /* R */
|
||||
send_byte_on_pin(data[i * 3 + 2], pin); /* B */
|
||||
}
|
||||
/* Reset pulse */
|
||||
__R30 &= ~(1 << pin);
|
||||
delay_cycles(10000);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
/* Enable OCP master port */
|
||||
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
|
||||
|
||||
/* Clear output pins */
|
||||
__R30 &= ~((1 << MOOD_PIN) | (1 << JAW_PIN));
|
||||
|
||||
while (1) {
|
||||
/* Check mood strip trigger */
|
||||
if (shared[1] == 1) {
|
||||
uint8_t num = shared[0];
|
||||
if (num > MAX_MOOD_LEDS) num = MAX_MOOD_LEDS;
|
||||
send_strip(MOOD_PIN, (uint8_t *)&shared[MOOD_DATA_OFF], num);
|
||||
shared[1] = 0;
|
||||
}
|
||||
|
||||
/* Check jaw trigger */
|
||||
if (shared[3] == 1) {
|
||||
uint8_t num = shared[2];
|
||||
if (num > MAX_JAW_LEDS) num = MAX_JAW_LEDS;
|
||||
send_strip(JAW_PIN, (uint8_t *)&shared[JAW_DATA_OFF], num);
|
||||
shared[3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
94
pru/ws281x_pru0.c
Normal file
94
pru/ws281x_pru0.c
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* WS281x LED Strip Driver for PRU0
|
||||
* Vixy's Mood Strip - 56 LEDs
|
||||
*
|
||||
* Pin: P8_11 = pr1_pru0_pru_r30_15 (Mode 6)
|
||||
* Alternative: P9_27 = pr1_pru0_pru_r30_5 (Mode 5) - FREE PIN!
|
||||
*
|
||||
* Shared Memory Layout (0x00010000):
|
||||
* [0]: num_leds (max 56)
|
||||
* [1]: trigger (write 1 to update)
|
||||
* [4-171]: LED data (56 * 3 bytes, GRB format)
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <pru_cfg.h>
|
||||
|
||||
/* Use P9_27 (R30 bit 5) - it's free! */
|
||||
#define LED_PIN 5
|
||||
#define NUM_LEDS 56
|
||||
|
||||
/* WS2812 timing at 200MHz (5ns per cycle) */
|
||||
#define T0H 70 /* 350ns high for 0 bit */
|
||||
#define T0L 160 /* 800ns low for 0 bit */
|
||||
#define T1H 140 /* 700ns high for 1 bit */
|
||||
#define T1L 120 /* 600ns low for 1 bit */
|
||||
|
||||
/* Shared memory base */
|
||||
#define SHARED_MEM 0x00010000
|
||||
|
||||
volatile uint8_t *shared = (volatile uint8_t *)SHARED_MEM;
|
||||
volatile register uint32_t __R30;
|
||||
|
||||
static inline void delay_cycles(uint32_t cycles) {
|
||||
while (cycles--) {
|
||||
__asm(" NOP");
|
||||
}
|
||||
}
|
||||
|
||||
static void send_bit(uint8_t bit) {
|
||||
if (bit) {
|
||||
__R30 |= (1 << LED_PIN);
|
||||
delay_cycles(T1H);
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
delay_cycles(T1L);
|
||||
} else {
|
||||
__R30 |= (1 << LED_PIN);
|
||||
delay_cycles(T0H);
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
delay_cycles(T0L);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_byte(uint8_t byte) {
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
send_bit((byte >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_led(uint8_t g, uint8_t r, uint8_t b) {
|
||||
send_byte(g);
|
||||
send_byte(r);
|
||||
send_byte(b);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
/* Enable OCP master port */
|
||||
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
|
||||
|
||||
/* Clear output pin */
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
|
||||
while (1) {
|
||||
/* Check trigger flag */
|
||||
if (shared[1] == 1) {
|
||||
uint8_t num = shared[0];
|
||||
if (num > NUM_LEDS) num = NUM_LEDS;
|
||||
|
||||
/* Send LED data (GRB format) */
|
||||
for (int i = 0; i < num; i++) {
|
||||
uint8_t g = shared[4 + i * 3 + 0];
|
||||
uint8_t r = shared[4 + i * 3 + 1];
|
||||
uint8_t b = shared[4 + i * 3 + 2];
|
||||
send_led(g, r, b);
|
||||
}
|
||||
|
||||
/* Reset pulse (>50us) */
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
delay_cycles(10000);
|
||||
|
||||
/* Clear trigger */
|
||||
shared[1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
pru/ws281x_pru1.c
Normal file
80
pru/ws281x_pru1.c
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* WS281x LED Strip Driver for PRU1
|
||||
* Vixy's Jaw LEDs - 24 LEDs (12 per side)
|
||||
*
|
||||
* Original Pin: P8_45 = pr1_pru1_pru_r30_0 (Mode 5) - LOCKED BY HDMI
|
||||
* Alternative: We use PRU0 with different memory offset instead
|
||||
* since all PRU1 pins are HDMI locked on this image
|
||||
*
|
||||
* This firmware is kept for reference but we'll use a combined
|
||||
* PRU0 firmware that handles both strips sequentially.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <pru_cfg.h>
|
||||
|
||||
#define LED_PIN 0
|
||||
#define NUM_LEDS 24
|
||||
|
||||
/* WS2812 timing at 200MHz */
|
||||
#define T0H 70
|
||||
#define T0L 160
|
||||
#define T1H 140
|
||||
#define T1L 120
|
||||
|
||||
/* Shared memory offset for jaw data (after mood strip) */
|
||||
#define SHARED_MEM 0x00010100 /* 256 bytes after PRU0 data */
|
||||
|
||||
volatile uint8_t *shared = (volatile uint8_t *)SHARED_MEM;
|
||||
volatile register uint32_t __R30;
|
||||
|
||||
static inline void delay_cycles(uint32_t cycles) {
|
||||
while (cycles--) {
|
||||
__asm(" NOP");
|
||||
}
|
||||
}
|
||||
|
||||
static void send_bit(uint8_t bit) {
|
||||
if (bit) {
|
||||
__R30 |= (1 << LED_PIN);
|
||||
delay_cycles(T1H);
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
delay_cycles(T1L);
|
||||
} else {
|
||||
__R30 |= (1 << LED_PIN);
|
||||
delay_cycles(T0H);
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
delay_cycles(T0L);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_byte(uint8_t byte) {
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
send_bit((byte >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
|
||||
while (1) {
|
||||
if (shared[1] == 1) {
|
||||
uint8_t num = shared[0];
|
||||
if (num > NUM_LEDS) num = NUM_LEDS;
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
uint8_t g = shared[4 + i * 3 + 0];
|
||||
uint8_t r = shared[4 + i * 3 + 1];
|
||||
uint8_t b = shared[4 + i * 3 + 2];
|
||||
send_byte(g);
|
||||
send_byte(r);
|
||||
send_byte(b);
|
||||
}
|
||||
|
||||
__R30 &= ~(1 << LED_PIN);
|
||||
delay_cycles(10000);
|
||||
shared[1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user