Welcome to mirror list, hosted at ThFree Co, Russian Federation.

neopixel.c « src - github.com/Klipper3d/klipper.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: bbea09f841e45817f2c5033a601786303604f795 (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// 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 <string.h> // memcpy
#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
#include "sched.h" // sched_shutdown

// The WS2812 uses a bit-banging protocol where each bit is
// transmitted as a gpio high pulse of variable length.  The various
// specs are unclear, but it is believed the timing requirements are:
// - A zero bit must have a high pulse less than 500ns.
// - A one bit must have a high pulse longer than 650ns.
// - The total bit time (gpio high to following gpio high) must not
//   exceed ~5000ns. The average bit time must be at least 1250ns.
// - The specs generally indicate a minimum high pulse and low pulse
//   of 200ns, but the actual requirement might be smaller.



/****************************************************************
 * Timing
 ****************************************************************/

typedef unsigned int neopixel_time_t;

static neopixel_time_t
nsecs_to_ticks(uint32_t ns)
{
    return timer_from_us(ns * 1000) / 1000000;
}

static inline int
neopixel_check_elapsed(neopixel_time_t t1, neopixel_time_t t2
                       , neopixel_time_t ticks)
{
    return t2 - t1 >= ticks;
}

// The AVR micro-controllers require specialized timing
#if CONFIG_MACH_AVR

#include <avr/interrupt.h> // TCNT1

static neopixel_time_t
neopixel_get_time(void)
{
    return TCNT1;
}

#define neopixel_delay(start, ticks) (void)(ticks)

#else

static neopixel_time_t
neopixel_get_time(void)
{
    return timer_read_time();
}

static inline void
neopixel_delay(neopixel_time_t start, neopixel_time_t ticks)
{
    while (!neopixel_check_elapsed(start, neopixel_get_time(), ticks))
        ;
}

#endif

#define PULSE_LONG_TICKS  nsecs_to_ticks(650)
#define PULSE_SHORT_TICKS nsecs_to_ticks(200)
#define BIT_MIN_TICKS     nsecs_to_ticks(1250)


/****************************************************************
 * Neopixel interface
 ****************************************************************/

struct neopixel_s {
    struct gpio_out pin;
    neopixel_time_t bit_max_ticks;
    uint32_t last_req_time, reset_min_ticks;
    uint16_t data_size;
    uint8_t data[0];
};

void
command_config_neopixel(uint32_t *args)
{
    struct gpio_out pin = gpio_out_setup(args[1], 0);
    uint16_t data_size = args[2];
    if (data_size & 0x8000)
        shutdown("Invalid neopixel data_size");
    struct neopixel_s *n = oid_alloc(args[0], command_config_neopixel
                                     , sizeof(*n) + data_size);
    n->pin = pin;
    n->data_size = data_size;
    n->bit_max_ticks = args[3];
    n->reset_min_ticks = args[4];
}
DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u"
             " data_size=%hu bit_max_ticks=%u reset_min_ticks=%u");

static int
send_data(struct neopixel_s *n)
{
    // Make sure the reset time has elapsed since last request
    uint32_t last_req_time = n->last_req_time, rmt = n->reset_min_ticks;
    uint32_t cur = timer_read_time();
    while (cur - last_req_time < rmt) {
        irq_poll();
        cur = timer_read_time();
    }

    // Transmit data
    uint8_t *data = n->data;
    uint_fast16_t data_len = n->data_size;
    struct gpio_out pin = n->pin;
    neopixel_time_t last_start = neopixel_get_time();
    neopixel_time_t bit_max_ticks = n->bit_max_ticks;
    while (data_len--) {
        uint_fast8_t byte = *data++;
        uint_fast8_t bits = 8;
        while (bits--) {
            if (byte & 0x80) {
                // Long pulse
                neopixel_delay(last_start, BIT_MIN_TICKS);
                irq_disable();
                neopixel_time_t start = neopixel_get_time();
                gpio_out_toggle_noirq(pin);
                irq_enable();

                if (neopixel_check_elapsed(last_start, start, bit_max_ticks))
                    goto fail;
                last_start = start;
                byte <<= 1;

                neopixel_delay(start, PULSE_LONG_TICKS);
                irq_disable();
                gpio_out_toggle_noirq(pin);
                irq_enable();

                neopixel_delay(neopixel_get_time(), PULSE_SHORT_TICKS);
            } else {
                // Short pulse
                neopixel_delay(last_start, BIT_MIN_TICKS);
                irq_disable();
                neopixel_time_t start = neopixel_get_time();
                gpio_out_toggle_noirq(pin);
                neopixel_delay(start, PULSE_SHORT_TICKS);
                gpio_out_toggle_noirq(pin);
                irq_enable();

                if (neopixel_check_elapsed(last_start, start, bit_max_ticks))
                    goto fail;
                last_start = start;
                byte <<= 1;
            }
        }
    }
    n->last_req_time = timer_read_time();
    return 0;
fail:
    // A hardware irq messed up the transmission - report a failure
    gpio_out_write(pin, 0);
    n->last_req_time = timer_read_time();
    return -1;
}

void
command_neopixel_update(uint32_t *args)
{
    uint8_t oid = args[0];
    struct neopixel_s *n = oid_lookup(oid, command_config_neopixel);
    uint_fast16_t pos = args[1];
    uint_fast8_t data_len = args[2];
    uint8_t *data = command_decode_ptr(args[3]);
    if (pos & 0x8000 || pos + data_len > n->data_size)
        shutdown("Invalid neopixel update command");
    memcpy(&n->data[pos], data, data_len);
}
DECL_COMMAND(command_neopixel_update,
             "neopixel_update oid=%c pos=%hu data=%*s");

void
command_neopixel_send(uint32_t *args)
{
    uint8_t oid = args[0];
    struct neopixel_s *n = oid_lookup(oid, command_config_neopixel);
    int ret = send_data(n);
    sendf("neopixel_result oid=%c success=%c", oid, ret ? 0 : 1);
}
DECL_COMMAND(command_neopixel_send, "neopixel_send oid=%c");