RPi Matrix #2: C!

Next step is going to be to find a way to render stuff on our matrix!

Data structures

Register output: io-bits

(For RPi Model B Rev2)

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 union io_bits { struct { bits_t unused : 2; // 0-1 bits_t output_enable_rev2 : 1; // 2 bits_t clock_rev2 : 1; // 3 bits_t strobe : 1; // 4 bits_t unused2 : 2; // 5..6 bits_t row : 4; // 7..10 bits_t unused3 : 6; // 11..16 bits_t r1 : 1; // 17 bits_t g1 : 1; // 18 bits_t unused4 : 3; bits_t b1 : 1; // 22 bits_t r2 : 1; // 23 bits_t g2 : 1; // 24 bits_t b2 : 1; // 25 } bits; uint32_t raw; };

The size of this struct is 26 → less than 32 so we can use a 32-bit integer to define our register output. Source: http://www.pieter-jan.com/images/RPI_LowLevelProgramming/GPSET0_Location.png

The register we’re going to write the struct into is the has the address 0x001C. (Note: You first need to write to the 0x0028 register to clear the GPIO pins first)

Bit-planes

The data is going to be organized a bit-plane of io_bits. The bit-plane contains colums times double-row io_bit structs. (Double-rows is the amount of rows divided by 2.)

This is because if you have a matrix with 32 rows for example you are going to have to address the rows by 4 bits. \(2^4\) is 16, which is \(32 / 2\). For a 16 pixels high matrix you have a 3 bit address: \(16 / 2 = 2^3\). A 32 pixels high matrix is basically a matrix of two 16x32 forged together. If we want to control the upper part we need to set R1,G1,B1. For the lower part: R2,G2,B2. That’s why we only need half the space for our bit-plane. The io_bits struct just contains more information than obvious.

On the raspberry’s hardware we’re going to use 11 bit-planes. More details later.

Filling our bit-planes

We’re going to iterate over all bit-planes we want to fill. Then we’re going to check if we want to turn the R, G or B output on. Now just set the output’s we need and copy the data to each double-row and column.

 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 const uint16_t red = map_color(matrix, rgb->r); const uint16_t green = map_color(matrix, rgb->g); const uint16_t blue = map_color(matrix, rgb->b); //Iterate over bit-planes for (i = MAX_BITPLANES - matrix->pwm_bits; i < MAX_BITPLANES; ++i) { // The bit we check in our color int mask = 1 << i; int r = (red & mask) == mask; // Check if i-th bit in red is set int b = (blue & mask) == mask; // Check if i-th bit in blue is set int g = (green & mask) == mask; // Check if i-th bit in green is set io_bits plane_bits = { 0 }; plane_bits.bits.r1 = plane_bits.bits.r2 = (bits_t) r; plane_bits.bits.g1 = plane_bits.bits.g2 = (bits_t) g; plane_bits.bits.b1 = plane_bits.bits.b2 = (bits_t) b; for (row = 0; row < double_rows; ++row) { // Iterate over all double-rows io_bits *row_data = lm_io_bits_value_at(bitplane, columns, row, 0, i); for (col = 0; col < columns; ++col) { // Iterate over all columns (row_data++)->raw = plane_bits.raw; // Copy data } } }

We’re creating matrix->pwm_bits io_bits. Because the more io_bits we use the more we’re going to PWM our LEDs. More data → greater color-depth.More on this later.

Throw this data at our matrix!

First prepare some masks we’re going to need later on.

We start by iterating over all double-rows.

 1 for (d_row = 0; d_row < double_rows; ++d_row) {

Now we’re setting our current row address which basically is our iteration value d_row. (Apply bit mask as we really only want to send the address which is max 0xF)

Start PWM-ing our LEDs! We start at COLOR_SHIFT, which is MAX_BITPLANES - CHAR_BIT, since the first 3 PWM loops are basically useless as the raspberry can’t time that precisely. Still the wither pm_bits, the more often we need to iterate.

 1 for (b = COLOR_SHIFT + MAX_BITPLANES - pwm_bits; b < MAX_BITPLANES; ++b) {

Get the row data for our current d_row for column 0, iterate over all columns, write R1,G1,B1 and R2,G2,B2 and clock the color in.

 1 2 3 4 5 6 7 io_bits *row_data = lm_io_bits_value_at(bitplane, columns, d_row, 0, b); for (col = 0; col < columns; ++col) { const io_bits out = *row_data++; lm_gpio_set_masked_bits(out.raw, color_clock_mask.raw); lm_gpio_set_bits(clock.raw); }

Clock back to normal.

Strobe in current row.

 1 2 lm_gpio_set_bits(strobe.raw); lm_gpio_clear_bits(strobe.raw);

The last step is to sleep for a specific amount of time.

 1 sleep_nanos(sleep_timings[b]);

One loop is finished now, repeat this now as fast as possible

 1 2 } }

Raspberry: “How long do I need to wait?”

The code to generate the timings is as follows:

 1 2 3 4 5 6 long base_time_nanos = 200; long row_sleep_timings[MAX_BITPLANES]; for (i = 0; i < MAX_BITPLANES; ++i) { row_sleep_timings[i] = (1 << i) * base_time_nanos; }

which will output (Credits go to https://github.com/hzeller/rpi-rgb-led-matrix):

 1 2 3 4 5 6 7 8 9 10 11 row_sleep_timings: (1 * base_time_nanos) row_sleep_timings: (2 * base_time_nanos) row_sleep_timings: (4 * base_time_nanos) row_sleep_timings: (8 * base_time_nanos) row_sleep_timings: (16 * base_time_nanos) row_sleep_timings: (32 * base_time_nanos) row_sleep_timings: (64 * base_time_nanos) row_sleep_timings: (128 * base_time_nanos) row_sleep_timings: (256 * base_time_nanos) row_sleep_timings: (512 * base_time_nanos) row_sleep_timings: (1024 * base_time_nanos)