ws2812 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ /*
2
+ * ws2811.h
3
+ *
4
+ * Copyright (c) 2014 Jeremy Garff <jer @ jers.net>
5
+ *
6
+ * All rights reserved.
7
+ *
8
+ * Redistribution and use in source and binary forms, with or without modification, are permitted
9
+ * provided that the following conditions are met:
10
+ *
11
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
12
+ * conditions and the following disclaimer.
13
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
14
+ * of conditions and the following disclaimer in the documentation and/or other materials
15
+ * provided with the distribution.
16
+ * 3. Neither the name of the owner nor the names of its contributors may be used to endorse
17
+ * or promote products derived from this software without specific prior written permission.
18
+ *
19
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
20
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
22
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
24
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ *
28
+ */
29
+
30
+
31
+ #ifndef __WS2811_H__
32
+ #define __WS2811_H__
33
+
34
+ #include "pwm.h"
35
+
36
+
37
+ #define WS2811_TARGET_FREQ 800000 // Can go as low as 400000
38
+
39
+ struct ws2811_device;
40
+
41
+ typedef uint32_t ws2811_led_t; //< 0x00RRGGBB
42
+ typedef struct
43
+ {
44
+ int gpionum; //< GPIO Pin with PWM alternate function, 0 if unused
45
+ int invert; //< Invert output signal
46
+ int count; //< Number of LEDs, 0 if channel is unused
47
+ int brightness; //< Brightness value between 0 and 255
48
+ ws2811_led_t *leds; //< LED buffers, allocated by driver based on count
49
+ } ws2811_channel_t;
50
+
51
+ typedef struct
52
+ {
53
+ struct ws2811_device *device; //< Private data for driver use
54
+ uint32_t freq; //< Required output frequency
55
+ int dmanum; //< DMA number _not_ already in use
56
+ ws2811_channel_t channel[RPI_PWM_CHANNELS];
57
+ } ws2811_t;
58
+
59
+
60
+ int ws2811_init(ws2811_t *ws2811); //< Initialize buffers/hardware
61
+ void ws2811_fini(ws2811_t *ws2811); //< Tear it all down
62
+ extern uint8_t ws2811_direct_colors; //< Set to non-zero to bypass "brightness" [gamma correction] altogether (default is 0)
63
+ int ws2811_render(ws2811_t *ws2811); //< Send LEDs off to hardware
64
+ extern uint32_t ws2811_dma_error; //< DMA errors from ws2811_wait go here
65
+ int ws2811_wait(ws2811_t *ws2811); //< Wait for DMA completion
66
+
67
+
68
+ #endif /* __WS2811_H__ */
69
+
data/lib/ws2812.rb ADDED
@@ -0,0 +1,20 @@
1
+ module Ws2812
2
+ VERSION = "0.0.2"
3
+ end
4
+
5
+ # to make it all less confusing
6
+ WS2812 = Ws2812
7
+
8
+ if $__WS2812_SKIP_LL
9
+ module Ws2812
10
+ module Lowlevel
11
+ # XXX: could provide full blown emulation of all calls? :)
12
+ end
13
+ end
14
+ else
15
+ require 'ws2812/lowlevel'
16
+ end
17
+ require 'ws2812/color'
18
+ require 'ws2812/basic'
19
+ require 'ws2812/unicornhat'
20
+ require 'ws2812/gamma_correction'
@@ -0,0 +1,195 @@
1
+ module Ws2812
2
+ ##
3
+ # Provides basic interface for so called NeoPixels (ws2812 RGB LED
4
+ # chips)
5
+ #
6
+ # This library internally uses C (and SWIG) extension from Richard Hirst's
7
+ # version of Jeremy Garff's rpi_ws281x library.
8
+ #
9
+ # And this particular class is heavily inspired by the included
10
+ # <tt>neopixel.py</tt> Python class within these projects.
11
+ #
12
+ # See:
13
+ # [jgarff] https://github.com/jgarff/rpi_ws281x
14
+ # [richardghirst] https://github.com/richardghirst/rpi_ws281x
15
+ # [pimoroni] https://github.com/pimoroni/unicorn-hat/tree/master/python/rpi-ws281x
16
+ #
17
+ #
18
+ class Basic
19
+ include Ws2812::Lowlevel
20
+
21
+ ##
22
+ # Initializes the basic ws2812 driver for +num+ leds at given +pin+,
23
+ # with initial +brightness+ (0..255)
24
+ #
25
+ # The +options+ hash can contain various additional options
26
+ # (all keys as symbols):
27
+ #
28
+ # [freq] frequency (Hz) to communicate at, defaults to 800_000
29
+ # [dma] dma channel to use, defaults to 5
30
+ # [invert] use inverted logic, defaults to false
31
+ # [channel] which channel to use, defaults to 0 (permissible 0, 1)
32
+ def initialize(num, pin, brightness = 50, options = {})
33
+
34
+ freq = options.fetch(:freq) { 800_000 }
35
+ dma = options.fetch(:dma) { 5 }
36
+ invert = options.fetch(:invert) { false }
37
+ channel = options.fetch(:channel) { 0 }
38
+
39
+ @leds = Ws2811_t.new
40
+ @leds.freq = freq
41
+ @leds.dmanum = dma
42
+
43
+ @channel = ws2811_channel_get(@leds, channel)
44
+ @channel.count = num
45
+ @channel.gpionum = pin
46
+ @channel.invert = invert ? 1 : 0
47
+ @channel.brightness = brightness
48
+
49
+ @count = num
50
+
51
+ at_exit { self.close }
52
+
53
+ @open = false
54
+ end
55
+
56
+ ##
57
+ # Is gamma correction (brightness) bypassed?
58
+ def direct?
59
+ !Ws2812::Lowlevel.ws2811_direct_colors.zero?
60
+ end
61
+
62
+ ##
63
+ # Instruct lowlevel driver to bypass gamma correction (brightness)
64
+ # and use the color values straight as they are given
65
+ def direct=(value)
66
+ Ws2812::Lowlevel.ws2811_direct_colors = !!value ? 1 : 0
67
+ end
68
+
69
+ ##
70
+ # Actually opens (initializes) communication with the LED strand
71
+ #
72
+ # Raises an exception when the initialization fails.
73
+ #
74
+ # Failure is usually because you don't have root permissions
75
+ # which are needed to access /dev/mem and to create special
76
+ # devices.
77
+ def open
78
+ return nil if @open
79
+ resp = ws2811_init(@leds)
80
+ fail "init failed with code: " + resp.to_s + ", perhaps you need to run as root?" unless resp.zero?
81
+ @open = true
82
+ self
83
+ end
84
+
85
+ ##
86
+ # Closes (deinitializes) communication with the LED strand
87
+ #
88
+ # Can be called on already closed device just fine
89
+ def close
90
+ if @open && @leds
91
+ @open = false
92
+ ws2811_fini(@leds)
93
+ @channel = nil # will GC the memory
94
+ @leds = nil
95
+ # Note: ws2811_fini will free mem used by led_data internally
96
+ end
97
+ self
98
+ end
99
+
100
+
101
+ ##
102
+ # Apply all changes since last show
103
+ #
104
+ # This method renders all changes (brightness, pixels) done to the
105
+ # strand since last show
106
+ def show
107
+ resp = ws2811_render(@leds)
108
+ fail "show failed with code: " + resp.to_s unless resp.zero?
109
+ end
110
+
111
+ ##
112
+ # Set given pixel identified by +index+ to +color+
113
+ #
114
+ # See +set+ for a method that takes individual +r+, +g+, +b+
115
+ # components
116
+ def []=(index, color)
117
+ if index.respond_to?(:to_a)
118
+ index.to_a.each do |i|
119
+ check_index(i)
120
+ ws2811_led_set(@channel, i, color.to_i)
121
+ end
122
+ else
123
+ check_index(index)
124
+ ws2811_led_set(@channel, index, color.to_i)
125
+ end
126
+ end
127
+
128
+ ##
129
+ # Set given pixel identified by +index+ to +r+, +g+, +b+
130
+ #
131
+ # See <tt>[]=</tt> for a method that takes +Color+ instance instead
132
+ # of individual components
133
+ def set(index, r, g, b)
134
+ check_index(index)
135
+ self[index] = Color.new(r, g, b)
136
+ end
137
+
138
+ ##
139
+ # Set brightness used for all pixels
140
+ #
141
+ # The value is from +0+ to +255+ and is internally used as a scaler
142
+ # for all colors values that are supplied via <tt>[]=</tt>
143
+ def brightness=(val)
144
+ @channel.brightness = val
145
+ end
146
+
147
+ ##
148
+ # Return brightness used for all pixels
149
+ #
150
+ # The value is from +0+ to +255+ and is internally used as a scaler
151
+ # for all colors values that are supplied via <tt>[]=</tt>
152
+ def brightness
153
+ @channel.brightness
154
+ end
155
+
156
+ ##
157
+ # Number of leds it's initialized for
158
+ #
159
+ # Method actually passes to low-level implementation for this
160
+ # value; it doesn't use the parameter passed during construction
161
+ def count
162
+ @channel.count
163
+ end
164
+
165
+ ##
166
+ # Return +Color+ of led located at given index
167
+ #
168
+ # Indexed from 0 upto <tt>#count - 1</tt>
169
+ def [](index)
170
+ if index.respond_to?(:to_a)
171
+ index.to_a.map do |i|
172
+ check_index(i)
173
+ Color.from_i(ws2811_led_get(@channel, i))
174
+ end
175
+ else
176
+ check_index(index)
177
+ Color.from_i(ws2811_led_get(@channel, index))
178
+ end
179
+ end
180
+
181
+ ##
182
+ # Verify supplied index
183
+ #
184
+ # Raises ArgumentError if the supplied index is invalid
185
+ # (doesn't address configured pixel)
186
+ def check_index(index)
187
+ if 0 <= index && index < @count
188
+ true
189
+ else
190
+ fail ArgumentError, "index #{index} outside of permitted range [0..#{count})"
191
+ end
192
+ end
193
+ private :check_index
194
+ end
195
+ end
@@ -0,0 +1,33 @@
1
+ module Ws2812
2
+ ##
3
+ # Simple wrapper class around RGB based color
4
+ class Color
5
+ def initialize(r, g, b)
6
+ @r, @g, @b = r, g, b
7
+ end
8
+ attr_accessor :r, :g, :b
9
+
10
+ ##
11
+ # Converts color to integer by encoding +r+, +g+, +b+
12
+ # as 8bit values (in this order)
13
+ #
14
+ # Thus <tt>Color.new(1,2,3).to_i # => 66051</tt>
15
+ def to_i
16
+ ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)
17
+ end
18
+
19
+ ##
20
+ # Converts color from integer +i+ by taking the least significant
21
+ # 24 bits and using them for r(8), g(8), b(8); in this order.
22
+ def self.from_i(i)
23
+ Color.new((i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff)
24
+ end
25
+
26
+
27
+ # Makes sense to represent color as hex, right?
28
+ def to_hex
29
+ "#%02x%02x%02x" % [r & 0xff, g & 0xff, b & 0xff]
30
+ end
31
+ alias_method :to_s, :to_hex
32
+ end
33
+ end
@@ -0,0 +1,72 @@
1
+ module Ws2812
2
+ ##
3
+ # This class implements (on Ruby level) the gamma correction that is
4
+ # internally used as part of the +brightness+ setup.
5
+ #
6
+ # It's especially useful if you want to mess around with pixel brightness
7
+ # in <em>direct mode</em>. See the <em>digiclock</em> example for more info.
8
+ class GammaCorrection
9
+ # correction table thanks to http://rgb-123.com/ws2812-color-output/
10
+ GAMMA_TABLE = [
11
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
12
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
13
+ 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
14
+ 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11,
15
+ 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18,
16
+ 19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28,
17
+ 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40,
18
+ 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54,
19
+ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
20
+ 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89,
21
+ 90, 91, 93, 94, 95, 96, 98, 99,100,102,103,104,106,107,109,110,
22
+ 111,113,114,116,117,119,120,121,123,124,126,128,129,131,132,134,
23
+ 135,137,138,140,142,143,145,146,148,150,151,153,155,157,158,160,
24
+ 162,163,165,167,169,170,172,174,176,178,179,181,183,185,187,189,
25
+ 191,193,194,196,198,200,202,204,206,208,210,212,214,216,218,220,
26
+ 222,224,227,229,231,233,235,237,239,241,244,246,248,250,252,255
27
+ ]
28
+
29
+ def initialize(brightness)
30
+ self.brightness = brightness
31
+ end
32
+
33
+ def brightness=(value)
34
+ raise ArgumentError, "brightness should be within (0..255)" unless (0..255).include?(value.to_i)
35
+ @brightness = value.to_i
36
+ end
37
+ attr_reader :brightness
38
+
39
+ def correct(value)
40
+ if value.kind_of?(Color)
41
+ Color.new(
42
+ self.correct(value.r),
43
+ self.correct(value.g),
44
+ self.correct(value.b))
45
+ else
46
+ raise ArgumentError, "value should be within (0..255)" unless (0..255).include?(value.to_i)
47
+ (GAMMA_TABLE[value.to_i] * (@brightness + 1)) >> 8
48
+ end
49
+ end
50
+
51
+ def correct_with_min_max(value, min = nil, max = nil)
52
+ trim(correct(value), min, max)
53
+ end
54
+
55
+ def trim(value, min = nil, max = nil)
56
+ if value.kind_of?(Color)
57
+ Color.new(
58
+ self.trim(value.r, min && min.r, max && max.r),
59
+ self.trim(value.g, min && min.g, max && max.g),
60
+ self.trim(value.b, min && min.b, max && max.b))
61
+ else
62
+ if min && value.to_i < min.to_i
63
+ min.to_i
64
+ elsif max && value.to_i > max.to_i
65
+ max.to_i
66
+ else
67
+ value.to_i
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,209 @@
1
+ module Ws2812
2
+ ##
3
+ # Provides interface for *Unicorn HAT* (a 8x8 ws2812 matrix by Pimoroni)
4
+ #
5
+ # This particular class is heavily inspired by the <tt>unicornhat.py</tt>
6
+ # class from UnicornHat's repo.
7
+ #
8
+ # See:
9
+ # [unicornhat] https://github.com/pimoroni/unicorn-hat
10
+ #
11
+ class UnicornHAT
12
+ UHAT_MAP = [
13
+ [7 ,6 ,5 ,4 ,3 ,2 ,1 ,0 ],
14
+ [8 ,9 ,10,11,12,13,14,15],
15
+ [23,22,21,20,19,18,17,16],
16
+ [24,25,26,27,28,29,30,31],
17
+ [39,38,37,36,35,34,33,32],
18
+ [40,41,42,43,44,45,46,47],
19
+ [55,54,53,52,51,50,49,48],
20
+ [56,57,58,59,60,61,62,63]
21
+ ]
22
+ ##
23
+ # Initializes the matrix ws2812 driver at given +pin+,
24
+ # with initial +brightness+ (0..255)
25
+ #
26
+ # The +options+ hash can contain various additional options,
27
+ # see +Basic+ class' description of options for more details.
28
+ def initialize(pin = 18, brightness = 50, options = {})
29
+ @hat = Basic.new(64, pin, brightness, options)
30
+ @hat.open
31
+
32
+ @rotation = 0
33
+ @pixels = Array.new(8) { |x| Array.new(8) { |y| Color.new(0,0,0) } }
34
+ push_all_pixels
35
+ rescue Object
36
+ @hat = nil
37
+ raise
38
+ end
39
+
40
+ # Query direct mode. See <em>Ws2812::Basic</em> for explanation.
41
+ def direct?
42
+ @hat.direct?
43
+ end
44
+
45
+ ##
46
+ # Set direct mode. See <em>Ws2812::Basic</em> for explanation.
47
+ def direct=(value)
48
+ @hat.direct = value
49
+ end
50
+
51
+ ##
52
+ # Closes (deinitializes) communication with the matrix
53
+ #
54
+ # Can be called on already closed device just fine
55
+ def close
56
+ if @hat
57
+ @hat.close
58
+ @hat = nil
59
+ end
60
+ self
61
+ end
62
+
63
+ ##
64
+ # Apply all changes since last show
65
+ #
66
+ # This method renders all changes (brightness, pixels, rotation)
67
+ # done to the strand since last show
68
+ def show
69
+ @hat.show
70
+ end
71
+
72
+ ##
73
+ # Set given pixel identified by +x+, +y+ to +color+
74
+ #
75
+ # See +set+ for a method that takes individual +r+, +g+, +b+
76
+ # components.
77
+ #
78
+ # You still have to call +show+ to make the changes visible.
79
+ def []=(x, y, color)
80
+ check_coords(x, y)
81
+ @pixels[x][y] = color
82
+ @hat[map_coords(x, y)] = color
83
+ end
84
+
85
+ ##
86
+ # Set given pixel identified by +x+, +y+ to +r+, +g+, +b+
87
+ #
88
+ # See <tt>[]=</tt> for a method that takes +Color+ instance instead
89
+ # of individual components.
90
+ #
91
+ # You still have to call +show+ to make the changes visible.
92
+ def set(x, y, r, g, b)
93
+ check_coords(x, y)
94
+ self[x, y] = Color.new(r, g, b)
95
+ end
96
+
97
+ ##
98
+ # Set brightness used for all pixels
99
+ #
100
+ # The value is from +0+ to +255+ and is internally used as a scaler
101
+ # for all colors values that are supplied via <tt>[]=</tt>
102
+ #
103
+ # You still have to call +show+ to make the changes visible.
104
+ def brightness=(val)
105
+ @hat.brightness = val
106
+ end
107
+
108
+ ##
109
+ # Return brightness used for all pixels
110
+ #
111
+ # The value is from +0+ to +255+ and is internally used as a scaler
112
+ # for all colors values that are supplied via <tt>[]=</tt>
113
+ def brightness
114
+ @hat.brightness
115
+ end
116
+
117
+ ##
118
+ # Return +Color+ of led located at given +x+, +y+
119
+ def [](x, y)
120
+ @pixels[x][y]
121
+ end
122
+
123
+ ##
124
+ # Returns current rotation as integer; one of: [0, 90, 180, 270]
125
+ def rotation
126
+ @rotation
127
+ end
128
+
129
+ ##
130
+ # Set rotation of the Unicorn HAT to +val+
131
+ #
132
+ # Permissible values for rotation are [0, 90, 180, 270] (mod 360).
133
+ #
134
+ # You still have to call +show+ to make the changes visible.
135
+ def rotation=(val)
136
+ permissible = [0, 90, 180, 270]
137
+ fail ArgumentError, "invalid rotation, permissible: #{permissible.join(', ')}" unless permissible.include?(val % 360)
138
+ @rotation = val % 360
139
+ push_all_pixels
140
+ end
141
+
142
+ ##
143
+ # Clears all pixels (sets them to black) and calls +show+ if +do_show+
144
+ def clear(do_show = true)
145
+ set_all(Color.new(0, 0, 0))
146
+ show if do_show
147
+ end
148
+
149
+ ##
150
+ # Sets all pixels to +color+
151
+ #
152
+ # You still have to call +show+ to make the changes visible.
153
+ def set_all(color)
154
+ 0.upto(7) do |x|
155
+ 0.upto(7) do |y|
156
+ self[x, y] = color
157
+ end
158
+ end
159
+ end
160
+
161
+ ##
162
+ # Pushes all pixels from buffer to the lower level (physical device)
163
+ #
164
+ # This is internally used when changing rotation but it can be useful
165
+ # when you set several pixels to the same Color instance and then
166
+ # manipulate those pixels' color all at once.
167
+ def push_all_pixels
168
+ 0.upto(7) do |x|
169
+ 0.upto(7) do |y|
170
+ @hat[map_coords(x, y)] = @pixels[x][y]
171
+ end
172
+ end
173
+ end
174
+
175
+
176
+ ##
177
+ # Maps +x+, +y+ coordinates to index on the physical matrix
178
+ # (takes rotation into account)
179
+ def map_coords(x, y)
180
+ check_coords(x, y)
181
+ y = 7 - y
182
+ case rotation
183
+ when 90
184
+ x, y = y, 7 - x
185
+ when 180
186
+ x, y = 7 - x, 7 - y
187
+ when 270
188
+ x, y = 7 - y, x
189
+ end
190
+
191
+ UHAT_MAP[x][y]
192
+ end
193
+ private :map_coords
194
+
195
+ ##
196
+ # Verify supplied coords +x+ and +y+
197
+ #
198
+ # Raises ArgumentError if the supplied coords are invalid
199
+ # (doesn't address configured pixel)
200
+ def check_coords(x, y)
201
+ if 0 <= x && x < 8 && 0 <= y && y < 8
202
+ true
203
+ else
204
+ fail ArgumentError, "coord (#{x},#{y}) outside of permitted range ((0..7), (0..7))"
205
+ end
206
+ end
207
+ private :check_coords
208
+ end
209
+ end