Skip to content

Commit 9c22098

Browse files
Arduino Uno R4 Support
Added support for the Arduino Uno R4 and variants. Also improved example code, comments, and linting.
1 parent 4067807 commit 9c22098

26 files changed

Lines changed: 591 additions & 172 deletions

AGENTS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# ShiftRegisterLEDMatrixLib Agent Instructions
2+
This library provides a generalized API to create and drive an image on LED matrix where shift registers, such as the 74HC595 or DM13a, are used to control the rows and columns of the matrix. It is designed to be use with the Arduino framework on a number of microcontroller platforms. A detailed description of this library and its use cases can be found in `README.md`.
3+
4+
## Scope
5+
These instructions apply to the entire repository unless a nested `AGENTS.md` overrides them.
6+
7+
## Code style
8+
- Match the surrounding formatting when editing C++ sources; the project primarily uses tabs for indentation inside functions and spaces for alignment.
9+
- Avoid introducing `try`/`catch` blocks around includes and keep the code portable across supported Arduino cores.
10+
- Prefer existing helper classes (for example `FspTimer`, `TimerThree`, etc.) instead of pulling in new dependencies when adding platform support.
11+
12+
## Documentation
13+
- Update `README.md`, `CHANGELOG.md`, and relevant files under `extras/` whenever you add support for a new board or change user-facing behaviour.
14+
- When documenting commands, use fenced code blocks with a language hint (e.g., ` ```sh `) so they render correctly on GitHub.
15+
16+
## Testing
17+
- There is no automated test suite; note "tests not run" in the final summary when you do not execute hardware tests.

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9+
### Added
10+
- Native scan timer support for the Arduino Uno R4 (Renesas RA4M1) family.
11+
- Fixed several compiler warnings
12+
913
## [2.0.3]
1014
### Fixed
1115
- The row scan timing was too long on ESP32 platforms. Adjusted this timing to remove visible blinking.

README.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ Find at more about this library and hardware that it is designed for at:
99

1010
# Design and Usage
1111
## Hardware Design
12-
The general hardware design of the matrix is to use shift registers to drive the matrix. This library can support either common anode or common cathode RGB LEDs, but default settings assume common anode.
12+
The general hardware design of the matrix is to use shift registers to drive the matrix. This library can support either common anode or common cathode RGB LEDs, but default settings assume common anode.
1313

1414
Consider the following 4x4 matrix using common anode RGB LEDs as an example:
1515

1616
```
1717
Serial Bit Stream
1818
|
1919
RGB--RGB--RGB--RGB- R1 LSB
20-
||| ||| ||| ||| |
20+
||| ||| ||| ||| |
2121
RGB--RGB--RGB--RGB- R2 |
2222
||| ||| ||| ||| |
2323
RGB--RGB--RGB--RGB- R3 |
@@ -37,12 +37,12 @@ Since there are 16 bits needed to control the rows and columns, two 74HC595 shif
3737

3838
In this case, Q7 of the first 74HC595 (U1) would be attached to C01, Q6 to C02, and so on. Since there are 12 column and 4 rows, two 8-bit shift registers are needed. So the second 74HC595 (U2) would have its Q0 through Q3 attached to R1 through R4, and its Q4 through Q7 attached to C12 down through C09. In this configuration, the first bit shifted out in an update cycle is for C01, and the last bit shifted out is for R1.
3939

40-
In this common anode set up, the rows would be "on" when the proper 74HC595 pin is in the `high` state and the column would "on" when its respective pin is in the `low` state. Basically, the shift register is sinking the columns and powering the rows. However, since the 74HC595 cannot source enough power to drive the multiple LEDs in the row, you might use a PNP transistor to drive the row. In this case, the row would be on if the its pin on the 74HC595 is `low`, which turns on the PNP transistor allowing current to flow.
40+
In this common anode set up, the rows would be "on" when the proper 74HC595 pin is in the `high` state and the column would "on" when its respective pin is in the `low` state. Basically, the shift register is sinking the columns and powering the rows. However, since the 74HC595 cannot source enough power to drive the multiple LEDs in the row, you might use a PNP transistor to drive the row. In this case, the row would be on if the its pin on the 74HC595 is `low`, which turns on the PNP transistor allowing current to flow.
4141

4242
Other similar designs can be used with this library. Common variations would be:
4343

4444
1. Using a DM13A sink driver to drive the cathode columns. It is not recommended to use a DM13A to drive the rows for common cathode RGB LEDs due to high current needs to drive the multiple LEDs in a single row. Using DM13A chips for the columns is nice because you can forgo the current limiting resistor for each column and the DM13A does the job of limiting the current.
45-
2. Using common cathode RGB LEDs. In this case NPN transistors would be used to sink the current for a row, and columns are sourced with the current of the high state on a 74HC595 pin.
45+
2. Using common cathode RGB LEDs. In this case NPN transistors would be used to sink the current for a row, and columns are sourced with the current of the high state on a 74HC595 pin.
4646
3. When using a common anode RGB LEDs, you could use a source driver, such as a UDN2981, to drive a row. This would be turned on with a `high` state on the row's shift register pin.
4747
4. Rather than ordering the column bits as alternating through R, G, and B colors, each color can be grouped together. This is convenient when using manufactured LED matrix modules that group the pins by colors rather than by columns. See [Bit Layouts](#bit-layouts).
4848

@@ -55,14 +55,14 @@ All image drawing is handled by the [Adafruit GFX API](https://learn.adafruit.co
5555
For RGB color matrices, there are two color modes supported: 9 bit and 16 bit color. Color for the image is represented by a `RGBColorType` value. When the preprocessor macro `SIXTEEN_BIT_COLOR` is defined to `1`, `RGBColorType` will use following bit layout (notice green gets 6 bits while red and blue each get 5 bits):
5656

5757
```
58-
Bits 0 4 8 12
58+
Bits 0 4 8 12
5959
|---|---|---|---
6060
RRRRRGGGGGGBBBBB
6161
6262
R = Red
6363
G = Green
6464
B = Blue
65-
```
65+
```
6666
Color can easily be set in hexadecimal format. For example, the following colors are defined in `RGBColor.h`:
6767

6868
```
@@ -98,13 +98,13 @@ const RGBColorType YELLOW_COLOR = RED_COLOR_MASK|GREEN_COLOR_MASK;
9898
When the preprocessor macro `SIXTEEN_BIT_COLOR` is defined to `0`, the library will use a subset of bits `RGBColorType` that effectively makes the color range based on 3 bits per red, green, and blue. This is done automatically, and you can still use the 16 bit color definitions illustrated above.
9999

100100
### Matrix Driver
101-
The matrix driver is an object that manages rendering an image on an LED matrix. It does this using a double buffer approach. The first buffer is the image that is desired to be rendered on the LED matrix. The second buffer is the bit sequences that needs to be sent to the LED matrix's shift registers to render the image. The matrix driver object uses SPI to send the bits to the shift register. Since the rows on the matrix are multiplexed when rendering, the matrix driver object will use a system clock interrupt to ensure the multiplexing is consistently timed.
101+
The matrix driver is an object that manages rendering an image on an LED matrix. It does this using a double buffer approach. The first buffer is the image that is desired to be rendered on the LED matrix. The second buffer is the bit sequences that needs to be sent to the LED matrix's shift registers to render the image. The matrix driver object uses SPI to send the bits to the shift register. Since the rows on the matrix are multiplexed when rendering, the matrix driver object will use a system clock interrupt to ensure the multiplexing is consistently timed.
102102

103103
When constructing a matrix driver, you need to tell it a few details:
104104
* The matrix's size in rows and columns
105-
* Whether the shift registers used for controlling columns should be set to `HIGH` or `LOW` to turn on the column.
105+
* Whether the shift registers used for controlling columns should be set to `HIGH` or `LOW` to turn on the column.
106106
* Whether the shift registers used for controlling rows should be set to `HIGH` or `LOW` to turn on the row
107-
* The length of the delay that should be present between turning on each rows while multiplexing. By default, this delay is set to zero (no delay). However, if you are using slow switch for the row's power, such as a UDN2981 which has a 2 microsecond urn off time, introducing a short period of all rows being off in between each row update can eliminate LED ghosting.
107+
* The length of the delay that should be present between turning on each rows while multiplexing. By default, this delay is set to zero (no delay). However, if you are using slow switch for the row's power, such as a UDN2981 which has a 2 microsecond turn off time, introducing a short period of all rows being off in between each row update can eliminate LED ghosting.
108108
* The pin which will be used to send the latch signal.
109109

110110
#### LEDMatrix
@@ -165,7 +165,11 @@ ESP8266 and ESP32 boards are generally 3.3v logic level boards. The default wiri
165165
| **GND** | GND | GND | GND, connect to 5V supply GND | GND | |
166166
| **SER** | D7 | D7 | 23 | D23 | SPI MOSI / HMOSI |
167167
| **CLK** | D5 | D5 | 18 | D18 | SPI SCK / HSCLK |
168-
| **LATCH** | D8 | D8 | 5 | D5 | SS / HCS |
168+
| **LATCH** | D8 | D8 | 5 | D5 | SS / HCS |
169+
170+
### Arduino Uno R4 Boards
171+
172+
The Uno R4 Minima and WiFi variants use Renesas' 48 MHz RA4M1 microcontroller and expose 3.3 V GPIO. The library now ships with a dedicated `FspTimer` backend that keeps the scan cadence identical to the existing AVR implementation, so sketches written for the classic Uno can be recompiled for the Uno R4 without modification. Level-shift the SPI, latch, and optional blanking lines to 5 V when you drive LED matrices that expect the higher voltage, using the same approaches described in the [3.3 V logic level](#33v-logic-level) section.
169173
170174
### 3.3v Logic Level
171175
To use the RGB LED Matrices designed in this project with micro-controller boards that use a 3.3V logic level, you must convert the 3.3V logic signals to 5V levels to work with the shift registers. You can easily use a 74HCT125 buffer/line driver chip to do this transformation. For example, you can wire a Teensy 3.6, which is a 3.3v device, to a 74HCT125 chip in the manner shown in the diagram below to get all power and signal lines required to drive the RGB LED Matrix while the Teensy is connected to USB power:
@@ -179,7 +183,7 @@ An alternative to using this 74HCT125 circuit would be to replace the 74HC595 sh
179183
### Color Modes
180184
This driver can support either 9-bit or 16-bit color. By default, this library uses 9-bit color. The library will default to either 9 bit or 16 bit color based on the type of microcontroller it is being compiled for. You can directly enable 16 bit color in this library by setting the preprocessor macro `SIXTEEN_BIT_COLOR` to a value of 1 (note, not in your `ino` file, but at compile time for all files). You can do this either by editing the `RGBColor.h` file or setting a compiler flag. However, note that 16 bit color requires more RAM than an Arduino Uno or Nano has. Due its memory requirements, 16 bit color should work on most 32 bit boards and the Arduino Mega 2560. 16 bit color has been tested to work on the following boards:
181185
182-
* Teensy 3.6
186+
* Teensy 3.6
183187
* Arduino Mega 2560
184188
* Wemos D1 mini Lite
185189
* NodeMCU
@@ -199,7 +203,7 @@ The second supported bit layout groups all colors together in column order, then
199203
When constructing the the `RGBLEDMatrix` object, the third argument is optional and it take a `RGBLEDBitLayout` enum value indicating which bit layout you are using. This argument defaults to `INDIVIDUAL_LEDS`, which is the first layout described above. The other potential value is `RGB_GROUPS`.
200204
201205
#### Common Power Row Groups
202-
As matrices get large, it becomes more difficult to successfully scan through all the rows within the time needed to create gray scales and not create perceptible blinking. One way to resolve this problem is to design the large matrix with common power row groups. Common power rows groups are a circuit design mechanism for implementing [scan rates](https://www.sparkfun.com/sparkx/blog/2650) in the LED matrix. What common power row groups does is effectively deconstruct a large LED matrix into smaller sub-matrices that all share the same set of rows, and thus the same set of row power control bits and switching transistors. The advantage of this design is that scan rate is implemented in hardware and as a result simplifies that hardware by reducing the row power transistors that are needed. The challenge with this approach is transforming the matrix image's logical layout (e.g., 16x16) to its actual circuit layout (e.g. 32x8). This transformation is handled in software by this library. Another challenge is that the row power transition would need to handle higher levels of current than matrices that don't use row groups. Be sure to select the row transistor appropriately.
206+
As matrices get large, it becomes more difficult to successfully scan through all the rows within the time needed to create gray scales and not create perceptible blinking. One way to resolve this problem is to design the large matrix with common power row groups. Common power rows groups are a circuit design mechanism for implementing [scan rates](https://www.sparkfun.com/sparkx/blog/2650) in the LED matrix. What common power row groups does is effectively deconstruct a large LED matrix into smaller sub-matrices that all share the same set of rows, and thus the same set of row power control bits and switching transistors. The advantage of this design is that scan rate is implemented in hardware and as a result simplifies that hardware by reducing the row power transistors that are needed. The challenge with this approach is transforming the matrix image's logical layout (e.g., 16x16) to its actual circuit layout (e.g. 32x8). This transformation is handled in software by this library. Another challenge is that the row power transition would need to handle higher levels of current than matrices that don't use row groups. Be sure to select the row transistor appropriately.
203207
204208
To illustrate a common power row group works, consider this following 8 row by 4 columns matrix:
205209
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#ifndef DOT_CHASER_RNG_H
2+
#define DOT_CHASER_RNG_H
3+
4+
#include <Arduino.h>
5+
6+
// The Renesas-based Uno R4 implements random() by hitting the on-chip hardware RNG,
7+
// which introduces visible stalls when the dot chaser examples bounce at the screen
8+
// edges. Keep the animation smooth by using a lightweight xorshift32 generator instead.
9+
namespace DotChaserRng {
10+
static uint32_t state = 0x1D2F3B4Cu;
11+
12+
inline void seed(uint32_t value) {
13+
if (value != 0u) {
14+
state = value;
15+
}
16+
}
17+
18+
inline uint32_t nextValue(void) {
19+
uint32_t x = state;
20+
x ^= x << 13;
21+
x ^= x >> 17;
22+
x ^= x << 5;
23+
state = x;
24+
return x;
25+
}
26+
27+
inline unsigned int nextIndex(unsigned int limit) {
28+
if (limit == 0u) {
29+
return 0u;
30+
}
31+
return static_cast<unsigned int>(nextValue() % limit);
32+
}
33+
34+
inline int nextNonZeroStep(void) {
35+
int value = static_cast<int>(nextValue() % 3u) - 1;
36+
if (value == 0) {
37+
value = (nextValue() & 1u) ? 1 : -1;
38+
}
39+
return value;
40+
}
41+
}
42+
43+
#endif // DOT_CHASER_RNG_H

examples/10x10-matrix/dot-chaser-10x10/dot-chaser-10x10.ino

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
1+
#include <Arduino.h>
12
#include <RGBLEDMatrix.h>
23
#include <TimerAction.h>
34

5+
// See notes in the 8x8 dot chaser: the Uno R4's hardware RNG makes random()
6+
// calls expensive, so we switch to a cheap xorshift generator there only.
7+
#if defined(ARDUINO_ARCH_RENESAS)
8+
#include "DotChaserRng.h"
9+
static unsigned int randomIndex(unsigned int limit) {
10+
return DotChaserRng::nextIndex(limit);
11+
}
12+
static int randomStep(void) {
13+
return DotChaserRng::nextNonZeroStep();
14+
}
15+
#else
16+
static unsigned int randomIndex(unsigned int limit) {
17+
if (limit == 0u) {
18+
return 0u;
19+
}
20+
return static_cast<unsigned int>(random(limit));
21+
}
22+
static int randomStep(void) {
23+
int step = 0;
24+
while (step == 0) {
25+
step = random(-1, 2);
26+
}
27+
return step;
28+
}
29+
#endif
30+
431
class Animation : public TimerAction {
5-
private:
32+
private:
633
RGBLEDMatrix* _screen;
734

835
int _xVel;
936
int _yVel;
1037

1138
unsigned int _xStack[5] = {0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF};
1239
unsigned int _yStack[5] = {0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF};
13-
14-
40+
1541
protected:
1642
virtual void action() {
1743
_screen->startDrawing();
@@ -52,21 +78,21 @@ protected:
5278
_yStack[2] = _yStack[1];
5379
_yStack[1] = _yStack[0];
5480

55-
5681
if ( _xStack[0] == 0 && _xVel <= 0 ) {
57-
_xVel = random(1,3) - 1;
58-
_yVel = random(0,3) - 1;
82+
_xVel = 1;
83+
_yVel = randomStep();
5984
} else if ( _xStack[0] == _screen->rows()-1 && _xVel >= 0 ) {
60-
_xVel = random(0,2) - 1;
61-
_yVel = random(0,3) - 1;
85+
_xVel = -1;
86+
_yVel = randomStep();
6287
}
6388

6489
if ( _yStack[0] == 0 && _yVel == -1) {
65-
_yVel = random(1,3) - 1;
90+
_yVel = 1;
91+
_xVel = randomStep();
6692
} else if ( _yStack[0] == _screen->columns()-1 && _yVel == 1) {
67-
_yVel = random(0,2) - 1;
93+
_yVel = -1;
94+
_xVel = randomStep();
6895
}
69-
7096

7197
_xStack[0] += _xVel;
7298
_yStack[0] += _yVel;
@@ -78,25 +104,41 @@ public:
78104
: TimerAction(100000),
79105
_screen(pScreen)
80106
{
81-
_xStack[0] = random(_screen->rows());
82-
_yStack[0] = random(_screen->columns());
83-
_xVel = 1;
84-
_yVel = 0;
85-
107+
this->randomize();
86108
}
87109

88-
110+
void randomize() {
111+
for (unsigned int i = 0; i < 5; ++i) {
112+
_xStack[i] = 0xFFFF;
113+
_yStack[i] = 0xFFFF;
114+
}
115+
_xStack[0] = randomIndex(_screen->rows());
116+
_yStack[0] = randomIndex(_screen->columns());
117+
_xVel = randomStep();
118+
_yVel = randomStep();
119+
}
89120
};
90121

91-
RGBLEDMatrix leds(10,10);
92-
Animation ani(&leds);
122+
RGBLEDMatrix *leds = nullptr;
123+
Animation *ani = nullptr;
93124

94125
void setup() {
95-
leds.setup();
96-
leds.startScanning();
126+
Serial.begin(115200);
127+
Serial.println("---===*** Program Start ***===---");
128+
#if defined(ARDUINO_ARCH_RENESAS)
129+
DotChaserRng::seed(micros());
130+
#else
131+
randomSeed(micros());
132+
#endif
133+
leds = new RGBLEDMatrix(10,10);
134+
ani = new Animation(leds);
135+
ani->randomize();
136+
leds->setup();
137+
leds->startScanning();
138+
Serial.println("Dot Chaser has started!");
97139
}
98140

99-
void loop() {
100-
leds.loop();
101-
ani.loop();
141+
void loop() {
142+
leds->loop();
143+
ani->loop();
102144
}

examples/10x10-matrix/plasma-10x10/plasma-10x10.ino

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
/*
1+
/*
22
* Plasma
3-
*
3+
*
44
* This is a demonstration of the plasma algorithm displayed on an RGB LED matrix.
55
* The COLOR_SCHEME global variable allows you to easily change between some pre-made
66
* color schemes. Using the patterm of the demonstration color schemes, it should be
77
* easy to add others.
8-
*
8+
*
99
* The algorithm for this demo was sourced from here:
10-
*
10+
*
1111
* https://www.bidouille.org/prog/plasma
12-
*
12+
*
1313
*/
1414
#include <RGBLEDMatrix.h>
1515
#include <RGBColor.h>
1616

1717
/* Color Schemes
18-
*
18+
*
1919
* 1 - Full rainbow gradients in RGB
2020
* 2 - Red, Orange, white gradients
2121
* 3 - gray scale
22-
*
22+
*
2323
*/
2424
const int COLOR_SCHEME = 1;
2525

@@ -33,7 +33,6 @@ int mapSineToRange( float sineValue, int rangeMax ) {
3333
}
3434
void drawPlasma( unsigned long counter ) {
3535
float utime = float(counter)/TIME_DILATION;
36-
3736
leds.startDrawing();
3837
for (unsigned int col = 0; col < leds.columns(); col++ ) {
3938
float x = ((float)col/((float)leds.columns()*SPACE_STRETCH_FACTOR)) - 0.5;
@@ -43,11 +42,11 @@ void drawPlasma( unsigned long counter ) {
4342

4443
float v1 = sinf(x*10.0+utime);
4544
float v2 = sinf(10.0*(x*sinf(utime/2.0) + y*cosf(utime/3.0)) + utime);
46-
45+
4746
float cx = x + 0.5*sinf(utime/5.0);
4847
float cy = y + 0.5*cosf(utime/3.0);
4948
float v3 = sinf( sqrtf(100.0*(cx*cx + cy*cy) + 1.0) + utime );
50-
49+
5150
float v = v1+v2+v3;
5251

5352
int r, g, b;
@@ -69,7 +68,7 @@ void drawPlasma( unsigned long counter ) {
6968
}
7069

7170
RGBColorType color = RGBColor::fromRGB(r, g, b);
72-
71+
7372
leds.writePixel(col, row, color);
7473
}
7574
}
@@ -100,7 +99,7 @@ void loop() {
10099
if (loopCounter == loopMod) {
101100
if (timeIncrement) {
102101
timeCount++;
103-
102+
104103
//
105104
// set a maximum to timeCount because floats only have
106105
// a max precision of 5 significant digits. Otherwise, when timeCount

0 commit comments

Comments
 (0)