Blinkt bfscΒΆ

An example of driving the Pimoroni Blinkt using wiringPi and modelling colors in floating point.

I’m treating the super bright leds as if they have a high dynamimc range up to 8.0, based on visual comparison to the red of my RPi power led.

I’m using full floating point everywhere; I like the RPi specifically because I can program it like a desktop machine, not an embedded microcontroller.

The user can pass 3 floats, in the [0.0 8.0] range, on the command line to set the RGB values of all the leds.

Or, use -f option to run a fire “simulator”.

Click to dowlnoad bfsc-blinkt.c or view the code:

  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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
// Brute Force Sample Code for the Raspberry Pi
// This code is in the public domain
// Revision 0.1.0
// Calling this a "prerelease version" because it's one of my first public code releases!
//////////////////////////////////////////////////////////////////////////////////////////////////// 
// Blinkt bfsc
//
// An example of driving the Pimoroni Blinkt using wiringPi and modelling colors in floating point.
//
// I'm treating the super bright leds as if they have a high dynamimc range of 8.0, based on
// visual comparison to the red of my RPi power led.
//
// I'm using full floating point everywhere; I like the RPi specifically because I can program it
// like a desktop machine, not an embedded microcontroller.
// 
// The user can pass 3 floats, in the [0.0 8.0] range, on the command line to set the RGB values of
// all the leds.
//
// Or, use the -f option to run a campfire "simulator". Put a sheet of paper in front of the
// Blinkt to diffuse the light from the leds, or otherwise hide direct view of the leds.
//
// To turn off the onboard leds, you can use "sudo util/rpi-led -d" in the bfsc directory
// To turn the onboard leds back on, you can use "sudo util/rpi-led -e"
////////////////////////////////////////////////////////////////////////////////////////////////////
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
#include <wiringPi.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
// Abbreviations I can't live without.
typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef float    f32;
typedef double   f64;
////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct
{
  f32 r;
  f32 g;
  f32 b;
} rgb;
////////////////////////////////////////////////////////////////////////////////////////////////////
// CLOCK_MONOTONIC should never decrease and be pretty smooth.
// It's alos said to be faster than CLOCK_MONOTONIC_RAW (exercise for the reader!)
u64 nanoseconds()
{
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return (u64)(ts.tv_sec) * 1000000000ULL + (u64)(ts.tv_nsec);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Sleeps or spins based on a threshold; imperfect, since it depends on the OS giving back the CPU
// at the right time; sleep_margin is empirically set and imperfect, since OS delays can get large!
void nanowait(u64 delta)
{
  u64 now = nanoseconds();
  u64 end = now + delta;
  do
  {
    delta = end - now;
    const u64 sleep_margin = 1000 * 500;
    if(delta > sleep_margin)
    {
      u64 sleepnano = delta - sleep_margin;
      struct timespec ts, dummy;
      delta = end - now; // First time through this is redundant
      ts.tv_sec = sleepnano / 1000000000ULL;
      ts.tv_nsec = sleepnano % 1000000000ULL;
      nanosleep(&ts,&dummy);
    }
    now = nanoseconds();
  } while(end > now);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
f32 clamp_f32_01(f32 x)
{
  return x < 0.0f ? 0.0f : (x > 1.0f ? 1.0f : x);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
f32 random_f32_01()
{
  return (f32)(rand() / (f64)RAND_MAX);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Pimoroni Blinkt uses 2 pins to control APA102 LEDs
const int data_pin  = 23;
const int clock_pin = 24;
////////////////////////////////////////////////////////////////////////////////////////////////////
void send_blinkt_bit(bool b)
{
  digitalWrite(data_pin, b ? HIGH : LOW);
  nanowait(500);
  digitalWrite(clock_pin,  HIGH) ;
  nanowait(500);
  digitalWrite(clock_pin, LOW);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void send_blinkt_byte(u8 byte)
{
  int i;
  for (int i = 0; i < 8; ++ i)
    send_blinkt_bit((byte >> (7 - i)) & 1);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void send_led_values(rgb c)
{
  // Arbitrarily decide that Blinkt LEDs go from 0 to 8, since they are super bright!

  // Scale and clamp inputs to 0.0f to 1.0f range
  c.r = clamp_f32_01(c.r * (1.0f / 8.0f));
  c.g = clamp_f32_01(c.g * (1.0f / 8.0f));
  c.b = clamp_f32_01(c.b * (1.0f / 8.0f));
  
  // Find max brightness value, which looks like a linear scaling on the output 
  f32 fbrightness = c.r > c.g ? c.r : c.g;
  fbrightness = c.b > fbrightness ? c.b : fbrightness;

  // Pick a useful brightness value; note use of ceiling function
  u8 brightness = (u8)ceilf(fbrightness * 31.0f);
  
  send_blinkt_byte(0b11100000 | brightness);

  u8 ir = 0;
  u8 ig = 0;
  u8 ib = 0;

  if(brightness > 0)
  {
    // Since physical output of LED is scaled by brightness, divide out brightness to "correct".
    f32 scale = (31.0f * 255.0f) / (f32)brightness;
    assert(c.r * scale <= 255.0f && c.g * scale <= 255.0f && c.b * scale <= 255.0f);
    
    ir = (u8)roundf(c.r * scale); 
    ig = (u8)roundf(c.g * scale); 
    ib = (u8)roundf(c.b * scale); 
  }

  send_blinkt_byte(ib);
  send_blinkt_byte(ig);
  send_blinkt_byte(ir);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void send_blinkt_colors(rgb* cs)
{
  // Sync to start sending; from APA102 datasheet
  for(u32 i = 0; i < 32; ++ i)
    send_blinkt_bit(0);
  
  for(u32 i = 0; i < 8; ++ i)
    send_led_values(cs[i]);
                     
  // Sync to finish sending
  for(u32 i = 0; i < 32; ++i)
    send_blinkt_bit(1);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void clear_colors(rgb* cs)
{
  for(u8 i = 0; i < 8; ++ i)
  {
    cs[i].r = 0.0f;
    cs[i].g = 0.0f;
    cs[i].b = 0.0f;
  }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Catch control C and turn off all LEDs on the Blinkt
void sigint_handler(int dummy)
{
  rgb cs[8];
  clear_colors(cs);
  // Not sure why but experimentally it looks like if Control-C interrupts sending,
  // some LEDs may be left on; sending twice has resolved the issue.
  send_blinkt_colors(cs);
  send_blinkt_colors(cs);
  exit(1);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// This function does not return; control-C to exit program
// Skip the fire simulator when first exploring this sample.
// Take advantage of the Pi's capabilities to do this in floating point.
////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct
{
  rgb current;
  rgb target;
  
  f32 current_p;
  f32 target_p;

  u64 target_time;
} flame;
////////////////////////////////////////////////////////////////////////////////////////////////////
// returns true if the target needs to be updated!
bool update_flame(flame* f, u64 now, u64 last)
{
  assert(now >= last);            // time goes forward
  assert(f->target_time >= last); // we have not missed any updates
    
  if(now >= f->target_time)
  {
    f->current = f->target;
    f->current_p = f->target_p;
    return true;
  }
  else
  {
    assert(f->target_time > last);  // f->target_time > now >= last

    // move a fraction toward the target
    f32 delta =  (f32)(now - last) / (f32)(f->target_time - last);
    f->current.r += (f->target.r - f->current.r) * delta;
    f->current.g += (f->target.g - f->current.g) * delta;
    f->current.b += (f->target.b - f->current.b) * delta;

    f->current_p += (f->target_p - f->current_p) * delta;
    return false;
  }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void add_flame_to_colors(flame* f, rgb* cs)
{
  f32 p = f->current_p * 7.0f;
  u8 l0 = (u8)floorf(p);
  u8 l1 = l0 + 1;
  cs[l0].r += f->current.r * (1.0f - (p - l0));
  cs[l0].g += f->current.g * (1.0f - (p - l0));
  cs[l0].b += f->current.b * (1.0f - (p - l0));
  if(l1 < 8)
  {
    cs[l1].r += f->current.r * (p - l0);
    cs[l1].g += f->current.g * (p - l0);
    cs[l1].b += f->current.b * (p - l0);
  }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void fire_simulator()
{
  // Every fire is made of 3 flames, right?
  const u32 n_f = 3;
  flame fs[n_f];
  
  u64 now = nanoseconds(); // track current wall clock time
  u64 last = now;          // last "simulation" step
  u64 next = now;          // target next sim step, for 100hz

  for(u32 i = 0; i < n_f; ++ i)
  {
    fs[i].target.r = 0.0f;
    fs[i].target.g = 0.0f;
    fs[i].target.b = 0.0f;

    fs[i].target_p = 0.5f;

    fs[i].target_time = now;
  }

  while(1)
  {
    assert(now >= last);

    rgb cs[8];
    clear_colors(cs);

    for(u32 i = 0; i < n_f; ++ i)
    {
      assert(fs[i].target_time >= last);

      // If time has passed the target, pick a new target.
      if(update_flame(&fs[i], now, last))
      {
        fs[i].target.r = random_f32_01();
        fs[i].target.g = fs[i].target.r * (random_f32_01() * 0.125f + 0.125f);
        fs[i].target.b = random_f32_01() < 0.1f ? fs[i].target.g * random_f32_01() * 0.5f : 0.0f;
      
        fs[i].target_p = clamp_f32_01(fs[i].target_p + 0.125f * (random_f32_01() - 0.5f));

        fs[i].target_time = now + (u64)((random_f32_01() + 0.5f) * (1000.0f * 1000.0f * 100.0f));
      }

      add_flame_to_colors(&fs[i], cs);
    }
    
    send_blinkt_colors(cs);
    
    last = now;
    next += 1000 * 1000 * 10; // goal is to run 100 times / second
    now = nanoseconds();

    if(next > now)
    {
      nanowait(next - now);
      now = nanoseconds();
    }
  }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
  bool fire = false;
  
  signal(SIGINT, sigint_handler);

  if(argc == 2)
  {
    if(strcmp(argv[1], "-f") == 0)
      fire = true;
  }

  if(!fire && argc != 4)
  {
    fprintf(stderr,"bfsc-blinkt needs either -f option or 3 f32s! Exiting..\n");
    exit(1);
  }

  // Initialize communication with Blinkt. The user is responsible for HW config!
  if(wiringPiSetupGpio() < 0)
  {
    fprintf(stderr,"wiringPi failed to init! Exiting...\n");
    exit(1);
  }

  pinMode (data_pin, OUTPUT) ;
  pinMode (clock_pin, OUTPUT) ;

  if(fire)
    fire_simulator();
  
  // Build an array of floating point colors and send it
  f32 r = atof(argv[1]);
  f32 g = atof(argv[2]);
  f32 b = atof(argv[3]);

  rgb cs[8];
  for(u8 i = 0; i < 8; ++ i)
  {
    cs[i].r = r;
    cs[i].g = g;
    cs[i].b = b;
  }

  send_blinkt_colors(cs);

  return 0 ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////