Files
vixy-bbb/pru/ws281x_combined.c
Vixy ce4f46ec18 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 🦊💜
2026-01-29 21:25:08 -06:00

100 lines
2.8 KiB
C

/*
* 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;
}
}
}