aboutsummaryrefslogtreecommitdiffstats
path: root/src/neopixel.c
blob: 41ca98dc79c1a595462099cea1fb9def70856afe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Support for bit-banging commands to WS2812 type "neopixel" LEDs
//
// Copyright (C) 2019  Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.

#include "autoconf.h" // CONFIG_MACH_AVR
#include "board/gpio.h" // gpio_out_write
#include "board/irq.h" // irq_poll
#include "board/misc.h" // timer_read_time
#include "basecmd.h" // oid_alloc
#include "command.h" // DECL_COMMAND

struct neopixel_s {
    struct gpio_out pin;
    uint32_t last_req_time;
};

void
command_config_neopixel(uint32_t *args)
{
    struct gpio_out pin = gpio_out_setup(args[1], 0);
    struct neopixel_s *n = oid_alloc(args[0], command_config_neopixel
                                     , sizeof(*n));
    n->pin = pin;
}
#if !CONFIG_MACH_AVR
DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u");
#endif

uint32_t
timer_from_ns(uint32_t ns)
{
    return timer_from_us(ns * 1000) / 1000000;
}

static int
send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len)
{
    // Make sure at least 50us has passed since last request
    uint32_t last_req_time = n->last_req_time, cur = timer_read_time();
    while (cur - last_req_time < timer_from_us(50)) {
        irq_poll();
        cur = timer_read_time();
    }

    struct gpio_out pin = n->pin;
    uint32_t expire_time = cur + 0x40000000;
    while (data_len--) {
        uint_fast8_t byte = *data++;
        uint_fast8_t bits = 8;
        while (bits--) {
            // Calculate pulse duration
            uint32_t on, off;
            if (byte & 0x80) {
                on = timer_from_ns(700 - 150);
                off = timer_from_ns(600 - 150);
            } else {
                on = timer_from_ns(350 - 150);
                off = timer_from_ns(800 - 150);
            }
            byte <<= 1;

            // Set output high
            gpio_out_toggle(pin);
            uint32_t on_start_time = timer_read_time();
            if (timer_is_before(expire_time, on_start_time))
                goto fail;
            cur = on_start_time;
            while (timer_is_before(cur, on_start_time + on)) {
                irq_poll();
                cur = timer_read_time();
            }

            // Set output low
            gpio_out_toggle(pin);
            uint32_t off_start_time = timer_read_time();
            expire_time = on_start_time + on + timer_from_ns(300);
            if (timer_is_before(expire_time, off_start_time))
                goto fail;
            cur = off_start_time;
            while (timer_is_before(cur, off_start_time + off)) {
                irq_poll();
                cur = timer_read_time();
            }
            expire_time = off_start_time + off + timer_from_ns(300);
        }
    }
    n->last_req_time = cur;
    return 0;
fail:
    // A hardware irq messed up the transmission - report a failure
    n->last_req_time = cur;
    return -1;
}

void
command_neopixel_send(uint32_t *args)
{
    uint8_t oid = args[0];
    struct neopixel_s *n = oid_lookup(oid, command_config_neopixel);
    uint_fast8_t data_len = args[1];
    uint8_t *data = (void*)(size_t)args[2];

    uint_fast8_t retry = 8;
    while (retry--) {
        int ret = send_data(n, data, data_len);
        if (!ret)
            break;
    }
}
#if !CONFIG_MACH_AVR
DECL_COMMAND(command_neopixel_send, "neopixel_send oid=%c data=%*s");
#endif