ws2812 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Single 1x4 pixels binary "digit"
6
+ class BinaryDigit
7
+ def initialize(hat, x, y, color, off_color = nil)
8
+ off_color ||= Ws2812::Color.new(0, 0, 0)
9
+ @hat, @x, @y, @color, @off_color = hat, x, y, color, off_color
10
+ end
11
+ attr_reader :hat
12
+ attr_accessor :x, :y, :color, :off_color
13
+
14
+ def show(value, do_show = false, &pixel_set)
15
+ pixel_set ||= method(:default_pixel_set)
16
+ raise ArgumentError, "invalid value" unless (0..9).include?(value)
17
+ x = 0
18
+ 0.upto(3) do |y|
19
+ if value[3-y].zero?
20
+ pixel_set.call(@hat,@x + x, @y + y, @off_color)
21
+ else
22
+ pixel_set.call(@hat,@x + x, @y + y, @color)
23
+ end
24
+ end
25
+ @hat.show if do_show
26
+ end
27
+
28
+ def default_pixel_set(hat, x, y, color)
29
+ hat[x, y] = color
30
+ end
31
+ private :default_pixel_set
32
+ end
33
+
34
+ # Two BinaryDigits next to each other with no pixel gap
35
+ class TwoBinaryDigits
36
+ def initialize(hat, x, y, color, off_color = nil)
37
+ @tens = BinaryDigit.new(hat, x, y, color, off_color)
38
+ @singles = BinaryDigit.new(hat, x + 1, y, color, off_color)
39
+ @hat, @x, @y, @color = hat, x, y, color
40
+ end
41
+ attr_reader :hat, :x, :y, :color, :off_color
42
+
43
+ def show(value, do_show = false, &pixel_set)
44
+ tens_value = value / 10
45
+ @tens.show(tens_value, false, &pixel_set)
46
+ @singles.show(value % 10, false, &pixel_set)
47
+ @hat.show if do_show
48
+ end
49
+
50
+ def off_color
51
+ @tens.off_color
52
+ end
53
+
54
+ def off_color=(val)
55
+ @tens.off_color = @singles.off_color = val
56
+ end
57
+
58
+ def color=(val)
59
+ @tens.color = @singles.color = val
60
+ end
61
+
62
+ def x=(val)
63
+ @tens.x = val
64
+ @singles.x = val + 1
65
+ end
66
+
67
+ def y=(val)
68
+ @tens.y = @singles.y = val
69
+ end
70
+ end
71
+
72
+ # A 6x4 matrix that represents binary clock
73
+ class BinaryClock
74
+ def initialize(hat, x, y, color)
75
+ @h = TwoBinaryDigits.new(hat, x, y, color)
76
+ @m = TwoBinaryDigits.new(hat, x+2, y, color)
77
+ @s = TwoBinaryDigits.new(hat, x+4, y, color)
78
+ end
79
+
80
+ def show(time)
81
+ @h.show(time.hour, false)
82
+ @m.show(time.min, false)
83
+ @s.show(time.sec, false)
84
+ end
85
+ end
86
+
87
+ # A 8x4 matrix that represents binary calendar (ddmmyyyy)
88
+ class BinaryCalendar
89
+ def initialize(hat, x, y, color)
90
+ @d = TwoBinaryDigits.new(hat, x, y, color)
91
+ @m = TwoBinaryDigits.new(hat, x+2, y, color)
92
+ @y1 = TwoBinaryDigits.new(hat, x+4, y, color)
93
+ @y2 = TwoBinaryDigits.new(hat, x+6, y, color)
94
+ end
95
+
96
+ def show(time)
97
+ @d.show(time.day, false)
98
+ @m.show(time.month, false)
99
+ @y1.show(time.year/100, false)
100
+ @y2.show(time.year%100, false)
101
+ end
102
+ end
103
+
104
+ if __FILE__ == $0
105
+ # Init
106
+ hat = Ws2812::UnicornHAT.new
107
+ hat.rotation = 180
108
+ hat.brightness = 20
109
+
110
+ frame = Ws2812::Color.new(0x00, 0x00, 0x66)
111
+ red = Ws2812::Color.new(0xff, 0, 0)
112
+ green = Ws2812::Color.new(0, 0xff, 0)
113
+ if true
114
+ # Calendar + Clock
115
+ calendar = BinaryCalendar.new(hat, 0, 0, green)
116
+ clock = BinaryClock.new(hat, 1, 4, red)
117
+ show_calendar = true
118
+ frame_proc = proc do
119
+ 4.upto(7) do |y|
120
+ hat[0, y] = hat[7, y] = frame
121
+ end
122
+ end
123
+ else
124
+ # Just clock, with a frame around it
125
+ clock = BinaryClock.new(hat, 1, 2, red)
126
+ show_calendar = false
127
+ frame_proc = proc do
128
+ 0.upto(7) do |x|
129
+ #hat[x, 7] = hat[x, 0] = frame
130
+ hat[x, 6] = hat[x, 1] = frame
131
+ end
132
+ 2.upto(5) do |y|
133
+ hat[0, y] = hat[7, y] = frame
134
+ end
135
+ end
136
+ end
137
+ ot = nil
138
+
139
+ # Show it...
140
+ begin
141
+ loop do
142
+ t = Time.now
143
+
144
+ # set the display anew if hour/minute/sec changed
145
+ if ot.nil? || (ot.hour != t.hour || ot.min != t.min || ot.sec != t.sec)
146
+ ot = t
147
+ hat.clear(false)
148
+ clock.show(t)
149
+ calendar.show(t) if show_calendar
150
+ frame_proc.call
151
+ hat.show
152
+ end
153
+
154
+ sleep 0.1
155
+ end
156
+ rescue Interrupt
157
+ end
158
+
159
+ # cleanup
160
+ hat.clear
161
+ end
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Single 3x5 pixels "digit" (think 7-segment display)
6
+ class Digit
7
+ n = nil # just to have the table below pretty
8
+ VALUES = {
9
+ n => [ 0b000, 0b000, 0b000, 0b000, 0b000, ],
10
+ 0 => [ 0b111, 0b101, 0b101, 0b101, 0b111, ],
11
+ #1 => [ 0b010, 0b110, 0b010, 0b010, 0b010, ], # 1 in the middle
12
+ 1 => [ 0b001, 0b011, 0b001, 0b001, 0b001, ], # 1 at the left
13
+ 2 => [ 0b111, 0b001, 0b111, 0b100, 0b111, ],
14
+ 3 => [ 0b111, 0b001, 0b111, 0b001, 0b111, ],
15
+ 4 => [ 0b101, 0b101, 0b111, 0b001, 0b001, ],
16
+ 5 => [ 0b111, 0b100, 0b111, 0b001, 0b111, ],
17
+ #6 => [ 0b111, 0b100, 0b111, 0b101, 0b111, ], # "full" six
18
+ 6 => [ 0b100, 0b100, 0b111, 0b101, 0b111, ], # "sparse" six
19
+ 7 => [ 0b111, 0b001, 0b001, 0b001, 0b001, ],
20
+ 8 => [ 0b111, 0b101, 0b111, 0b101, 0b111, ],
21
+ #9 => [ 0b111, 0b101, 0b111, 0b001, 0b111, ], # "full" nine
22
+ 9 => [ 0b111, 0b101, 0b111, 0b001, 0b001, ], # "sparse" nine
23
+ }
24
+
25
+ def initialize(hat, x, y, color, off_color = nil)
26
+ off_color ||= Ws2812::Color.new(0, 0, 0)
27
+ @hat, @x, @y, @color, @off_color = hat, x, y, color, off_color
28
+ end
29
+ attr_reader :hat
30
+ attr_accessor :x, :y, :color, :off_color
31
+
32
+ def show(value, do_show = false, &pixel_set)
33
+ pixel_set ||= method(:default_pixel_set)
34
+ raise ArgumentError, "invalid value" unless VALUES[value]
35
+ 0.upto(4) do |y|
36
+ 0.upto(2) do |x|
37
+ if VALUES[value][y][2-x].zero?
38
+ pixel_set.call(@hat,@x + x, @y + y, @off_color)
39
+ else
40
+ pixel_set.call(@hat,@x + x, @y + y, @color)
41
+ end
42
+ end
43
+ end
44
+ @hat.show if do_show
45
+ end
46
+
47
+ def default_pixel_set(hat, x, y, color)
48
+ hat[x, y] = color
49
+ end
50
+ private :default_pixel_set
51
+ end
52
+
53
+ # Two Digits next to each other with one pixel gap
54
+ # (think two 7-segment displays next to each other)
55
+ class TwoDigits
56
+ def initialize(hat, x, y, color, off_color = nil)
57
+ @tens = Digit.new(hat, x, y, color, off_color)
58
+ @singles = Digit.new(hat, x + 4, y, color, off_color)
59
+ @hat, @x, @y, @color = hat, x, y, color
60
+ @leading_zero = false
61
+ end
62
+ attr_reader :hat, :x, :y, :color, :off_color
63
+ attr_accessor :leading_zero
64
+
65
+ def show(value, do_show = false, &pixel_set)
66
+ tens_value = value / 10
67
+ tens_value = nil if !@leading_zero && tens_value.zero?
68
+ @tens.show(tens_value, false, &pixel_set)
69
+ @singles.show(value % 10, false, &pixel_set)
70
+ @hat.show if do_show
71
+ end
72
+
73
+ def off_color
74
+ @tens.off_color
75
+ end
76
+
77
+ def off_color=(val)
78
+ @tens.off_color = @singles.off_color = val
79
+ end
80
+
81
+ def color=(val)
82
+ @tens.color = @singles.color = val
83
+ end
84
+
85
+ def x=(val)
86
+ @tens.x = val
87
+ @singles.x = val + 4
88
+ end
89
+
90
+ def y=(val)
91
+ @tens.y = @singles.y = val
92
+ end
93
+ end
94
+
95
+ if __FILE__ == $0
96
+ # Init
97
+ hat = Ws2812::UnicornHAT.new
98
+ hat.rotation = 180
99
+
100
+ gc = Ws2812::GammaCorrection.new(20) # => "brightness" set to 20
101
+ hat.direct = true
102
+ # Note: I'm using direct mode with custom +gc+ here so I can better
103
+ # "animate" the pixels where hours and minutes overlap (both set)
104
+ #
105
+ # Without direct mode you're flying blind and changing the uncorrected
106
+ # values, while possible, looks fugly in lower brightness (i.e. 20)
107
+ black = gc.correct(Ws2812::Color.new(0, 0, 0))
108
+ red = gc.correct(Ws2812::Color.new(0xff, 0, 0))
109
+ green = gc.correct(Ws2812::Color.new(0, 0xaa, 0))
110
+ both = gc.correct(Ws2812::Color.new(0xff, 0xaa, 0))
111
+ h = TwoDigits.new(hat, 0, 0, red, black)
112
+ m = TwoDigits.new(hat, 1, 3, green, black)
113
+ m.leading_zero = true
114
+
115
+ # The following proc block allows us to merge individual red+green pixels
116
+ # into one aggregate "both" pixel (same Ws2812::Color instance). Thus
117
+ # allowing us to change one value (and then call +#push_all_pixels+) for
118
+ # update of the whole array.
119
+ green_ps = proc do |hat, x, y, color|
120
+ if hat[x, y] == red
121
+ hat[x, y] = both if color == green
122
+ # otherwise stays red
123
+ else
124
+ hat[x, y] = color
125
+ end
126
+ end
127
+
128
+ begin
129
+ ticks = 25
130
+ tick = 0
131
+ direction = 1
132
+ ot = nil
133
+ loop do
134
+ t = Time.now
135
+
136
+ # set the display anew if hour/minute changed
137
+ if ot.nil? || (ot.hour != t.hour || ot.min != t.min)
138
+ ot = t
139
+ hat.clear(false)
140
+ h.show(t.hour, false)
141
+ m.show(t.min, false, &green_ps)
142
+ end
143
+
144
+
145
+ # animate "both" pixels (where hour & minute overlay)
146
+ both.r = (red.r * tick.to_f/ticks).to_i
147
+ both.g = (green.g * ((ticks - tick).to_f/ticks)).to_i
148
+ both.r = both.g = 1 if both.r.zero? && both.g.zero?
149
+ hat.push_all_pixels
150
+
151
+ if direction > 0
152
+ direction = -1 if tick + 1 >= ticks
153
+ tick = tick + 1
154
+ else
155
+ direction = 1 if tick - 1 <= 0
156
+ tick = tick - 1
157
+ end
158
+
159
+ hat.show
160
+ sleep 0.1
161
+ end
162
+ rescue Interrupt
163
+ end
164
+
165
+ # cleanup
166
+ hat.clear
167
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Init
6
+ n = 64 # num leds
7
+ ws = Ws2812::Basic.new(n, 18) # +n+ leds at pin 18, using defaults
8
+ ws.open
9
+ ws.brightness = 255
10
+ if ARGV.first
11
+ puts 'Using direct mode.'
12
+ puts 'Remove all positional parameters to switch to gamma-corrected mode.'
13
+ ws.direct = true
14
+ else
15
+ puts 'Using gamma-corrected mode.'
16
+ puts 'Add (any) positional parameter to switch to direct mode.'
17
+ end
18
+
19
+ # up...
20
+ 0.upto(255) do |i|
21
+ ws[0..63] = Ws2812::Color.new(i, i, i)
22
+ ws.show
23
+ sleep 0.01
24
+ end
25
+
26
+ # and down...
27
+ 255.downto(0) do |i|
28
+ ws[0..63] = Ws2812::Color.new(i, i, i)
29
+ ws.show
30
+ sleep 0.01
31
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'ws2812'
4
+
5
+ # Init
6
+ hat = Ws2812::UnicornHAT.new
7
+
8
+ # first corner set to red
9
+ red = Ws2812::Color.new(0xff, 0, 0)
10
+ hat[0, 0] = red
11
+ hat[0, 1] = red
12
+ hat[1, 0] = red
13
+
14
+ # second to green
15
+ green = Ws2812::Color.new(0, 0xff, 0)
16
+ hat[6, 7] = green
17
+ hat[7, 7] = green
18
+ hat[7, 6] = green
19
+
20
+ # middle part
21
+ hat[3, 3] = red
22
+ hat[4, 4] = green
23
+
24
+
25
+ # show it
26
+ hat.show
27
+
28
+ sleep 0.5
29
+
30
+ # rotate around till ^C
31
+ puts "Spinning... ^C to terminate"
32
+ begin
33
+ loop do
34
+ # rotate around
35
+ for rot in [90, 180, 270, 0]
36
+ hat.rotation = rot
37
+ hat.show
38
+
39
+ sleep 0.5
40
+ end
41
+ end
42
+ rescue Interrupt
43
+ end
44
+
45
+ # Clear the display at the end
46
+ hat.clear
47
+ hat.show
@@ -0,0 +1,4 @@
1
+ # make sure you have swig2.0 if you're planning to regen
2
+ # lowlevel_wrap.c from lowlevel.i
3
+ ruby extconf.rb
4
+ make
@@ -0,0 +1,2 @@
1
+ lowlevel.i is https://github.com/pimoroni/unicorn-hat/blob/master/python/rpi-ws281x/rpi_ws281x.i
2
+ *.[ch] are https://github.com/pimoroni/unicorn-hat/tree/master/python/rpi-ws281x/lib/*.[ch]
@@ -0,0 +1,143 @@
1
+ /*
2
+ * This needs cleaning up and expanding. The MODEL_* defines shoudl be in
3
+ * board_info.h, it currently identifies everything not a B2 as a B, it
4
+ * should have an accessor function for the board model and revision, etc.
5
+ */
6
+
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+ #include <stdarg.h>
10
+ #include <string.h>
11
+
12
+ #include "board_info.h"
13
+
14
+ enum
15
+ {
16
+ MODEL_UNKNOWN,
17
+ MODEL_A,
18
+ MODEL_A_PLUS,
19
+ MODEL_B,
20
+ MODEL_B_PLUS,
21
+ MODEL_B_2,
22
+ };
23
+
24
+ static int board_info_initialised = 0;
25
+ static int board_model = MODEL_UNKNOWN;
26
+ static int board_revision;
27
+
28
+ static void
29
+ fatal(char *fmt, ...)
30
+ {
31
+ va_list ap;
32
+
33
+ va_start(ap, fmt);
34
+ vfprintf(stderr, fmt, ap);
35
+ va_end(ap);
36
+ exit(1);
37
+ }
38
+
39
+ static unsigned get_dt_ranges(const char *filename, unsigned offset)
40
+ {
41
+ unsigned address = ~0;
42
+ FILE *fp = fopen(filename, "rb");
43
+ if (fp)
44
+ {
45
+ unsigned char buf[4];
46
+ fseek(fp, offset, SEEK_SET);
47
+ if (fread(buf, 1, sizeof buf, fp) == sizeof buf)
48
+ address = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0;
49
+ fclose(fp);
50
+ }
51
+ return address;
52
+ }
53
+
54
+ uint32_t board_info_peripheral_base_addr(void)
55
+ {
56
+ unsigned address = get_dt_ranges("/proc/device-tree/soc/ranges", 4);
57
+
58
+ board_info_init();
59
+
60
+ if (address == ~0)
61
+ {
62
+ if (board_model == MODEL_B_2)
63
+ return 0x3f000000;
64
+ else
65
+ return 0x20000000;
66
+ }
67
+ else
68
+ {
69
+ return address;
70
+ }
71
+ }
72
+
73
+ uint32_t board_info_sdram_address(void)
74
+ {
75
+ unsigned address = get_dt_ranges("/proc/device-tree/axi/vc_mem/reg", 8);
76
+
77
+ board_info_init();
78
+
79
+ if (address == ~0)
80
+ {
81
+ if (board_model == MODEL_B_2)
82
+ return 0xc0000000;
83
+ else
84
+ return 0x40000000;
85
+ }
86
+ else
87
+ {
88
+ return address;
89
+ }
90
+ }
91
+
92
+ int board_info_init(void)
93
+ {
94
+ char buf[128], revstr[128], modelstr[128];
95
+ char *ptr, *end, *res;
96
+ FILE *fp;
97
+
98
+ if (board_info_initialised)
99
+ return 0;
100
+
101
+ revstr[0] = modelstr[0] = '\0';
102
+
103
+ fp = fopen("/proc/cpuinfo", "r");
104
+
105
+ if (!fp)
106
+ fatal("Unable to open /proc/cpuinfo: %m\n");
107
+
108
+ while ((res = fgets(buf, 128, fp))) {
109
+ if (!strncasecmp("model name", buf, 8))
110
+ memcpy(modelstr, buf, 128);
111
+ else if (!strncasecmp(buf, "revision", 8))
112
+ memcpy(revstr, buf, 128);
113
+ }
114
+ fclose(fp);
115
+
116
+ if (modelstr[0] == '\0')
117
+ fatal("No 'Model name' record in /proc/cpuinfo\n");
118
+ if (revstr[0] == '\0')
119
+ fatal("No 'Revision' record in /proc/cpuinfo\n");
120
+
121
+ if (strstr(modelstr, "ARMv6"))
122
+ board_model = MODEL_B;
123
+ else if (strstr(modelstr, "ARMv7"))
124
+ board_model = MODEL_B_2;
125
+ else
126
+ fatal("Cannot parse the model name string\n");
127
+
128
+ ptr = revstr + strlen(revstr) - 3;
129
+ board_revision = strtol(ptr, &end, 16);
130
+ if (end != ptr + 2)
131
+ fatal("Failed to parse Revision string\n");
132
+ if (board_revision < 1)
133
+ fatal("Invalid board Revision\n");
134
+ else if (board_revision < 4)
135
+ board_revision = 1;
136
+ else
137
+ board_revision = 2;
138
+
139
+ board_info_initialised = 1;
140
+
141
+ return 0;
142
+ }
143
+