|
| 1 | +// Support for WS2812 type "neopixel" LEDs using SPI hardware for timing |
| 2 | +// |
| 3 | +// Copyright (C) 2025 Russell Cloran <rcloran@gmail.com> |
| 4 | +// |
| 5 | +// This file may be distributed under the terms of the GNU GPLv3 license. |
| 6 | + |
| 7 | +#include "basecmd.h" // oid_alloc |
| 8 | +#include "board/irq.h" // irq_poll |
| 9 | +#include "board/misc.h" // timer_read_time |
| 10 | +#include "command.h" // DECL_COMMAND |
| 11 | +#include "sched.h" // shutdown |
| 12 | +#include <string.h> // memcpy |
| 13 | +#include "spicmds.h" // spidev_transfer |
| 14 | + |
| 15 | +// This code uses a SPI bus to generate neopixel-compatible data by sending |
| 16 | +// different bytes on the SPI bus, each of which represents a bit in the |
| 17 | +// neopixel data. |
| 18 | +// |
| 19 | +// A neopixel 0 can be represented by holding the line high for anywhere |
| 20 | +// between 200 and 500 ns, and a 1 for at least 550ns. The amount of time the |
| 21 | +// line must then be held low is actually fairly tolerant: |
| 22 | +// |
| 23 | +// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ |
| 24 | +// |
| 25 | +// 2 bits of SPI data take 200ns at 10MHz and 500ns at 4MHz |
| 26 | +// 4 bits of SPI data take 550ns at 7.27MHz, or longer at slower rates |
| 27 | + |
| 28 | +#define ONE_BIT 0b01111000 |
| 29 | +#define ZERO_BIT 0b01100000 |
| 30 | + |
| 31 | +/**************************************************************** |
| 32 | + * Neopixel interface |
| 33 | + ****************************************************************/ |
| 34 | + |
| 35 | +struct neopixel_spi_s { |
| 36 | + struct spidev_s *spi; |
| 37 | + uint32_t last_req_time, reset_min_ticks; |
| 38 | + uint16_t data_size; |
| 39 | + uint8_t data[0]; |
| 40 | +}; |
| 41 | + |
| 42 | +void |
| 43 | +command_config_neopixel_spi(uint32_t *args) |
| 44 | +{ |
| 45 | + uint16_t data_size = args[2]; |
| 46 | + if (data_size & 0x8000) |
| 47 | + shutdown("Invalid neopixel data_size"); |
| 48 | + struct neopixel_spi_s *n = oid_alloc(args[0], command_config_neopixel_spi |
| 49 | + , sizeof(*n) + data_size); |
| 50 | + |
| 51 | + n->spi = spidev_oid_lookup(args[1]); |
| 52 | + |
| 53 | + n->data_size = data_size; |
| 54 | + n->reset_min_ticks = args[3]; |
| 55 | +} |
| 56 | +DECL_COMMAND(command_config_neopixel_spi, "config_neopixel_spi oid=%c" |
| 57 | + " bus_oid=%u data_size=%hu reset_min_ticks=%u"); |
| 58 | + |
| 59 | +static int |
| 60 | +send_data_spi(struct neopixel_spi_s *n) |
| 61 | +{ |
| 62 | + // Make sure the reset time has elapsed since last request |
| 63 | + uint32_t last_req_time = n->last_req_time, rmt = n->reset_min_ticks; |
| 64 | + uint32_t cur = timer_read_time(); |
| 65 | + while (cur - last_req_time < rmt) { |
| 66 | + irq_poll(); |
| 67 | + cur = timer_read_time(); |
| 68 | + } |
| 69 | + |
| 70 | + // Transmit data |
| 71 | + uint8_t *data = n->data; |
| 72 | + uint_fast16_t data_len = n->data_size; |
| 73 | + uint8_t msg[24] = {0}; |
| 74 | + |
| 75 | + while (data_len) { |
| 76 | + for (uint_fast8_t i = 0; i < 3; i++) { |
| 77 | + uint_fast8_t byte = *data++; |
| 78 | + data_len--; |
| 79 | + for (uint_fast8_t bit = 0; bit < 8; bit++) { |
| 80 | + if (byte & 0x80) { |
| 81 | + msg[i * 8 + bit] = ONE_BIT; |
| 82 | + } else { |
| 83 | + msg[i * 8 + bit] = ZERO_BIT; |
| 84 | + } |
| 85 | + byte <<= 1; |
| 86 | + } |
| 87 | + } |
| 88 | + spidev_transfer(n->spi, 0, sizeof(msg), msg); |
| 89 | + } |
| 90 | + |
| 91 | + n->last_req_time = timer_read_time(); // + transfer time? |
| 92 | + return 0; |
| 93 | +} |
| 94 | + |
| 95 | +void |
| 96 | +command_neopixel_update_spi(uint32_t *args) |
| 97 | +{ |
| 98 | + uint8_t oid = args[0]; |
| 99 | + struct neopixel_spi_s *n = oid_lookup(oid, command_config_neopixel_spi); |
| 100 | + uint_fast16_t pos = args[1]; |
| 101 | + uint_fast8_t data_len = args[2]; |
| 102 | + uint8_t *data = command_decode_ptr(args[3]); |
| 103 | + if (pos & 0x8000 || pos + data_len > n->data_size) |
| 104 | + shutdown("Invalid neopixel update command"); |
| 105 | + memcpy(&n->data[pos], data, data_len); |
| 106 | +} |
| 107 | +DECL_COMMAND(command_neopixel_update_spi, |
| 108 | + "neopixel_update_spi oid=%c pos=%hu data=%*s"); |
| 109 | + |
| 110 | +void |
| 111 | +command_neopixel_send_spi(uint32_t *args) |
| 112 | +{ |
| 113 | + uint8_t oid = args[0]; |
| 114 | + struct neopixel_spi_s *n = oid_lookup(oid, command_config_neopixel_spi); |
| 115 | + int ret = 1; |
| 116 | + ret = send_data_spi(n); |
| 117 | + sendf("neopixel_result oid=%c success=%c", oid, ret ? 0 : 1); |
| 118 | +} |
| 119 | +DECL_COMMAND(command_neopixel_send_spi, "neopixel_send_spi oid=%c"); |
0 commit comments