MAX7219/7221 LED drivers

GroupDIY Audio Forum

Help Support GroupDIY Audio Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.
32bit floats on an 8bit micro will slow things down a lot

You could look into some of the dsPIC devices, those are made for digital power supplies, motor controllers, and similar which run control loops. They are primarily fixed point, but I just saw an announcement that there is a new device with floating point DSP instructions.
The floating point devices are shown as in production, but they are new enough that the distributors don't have stock yet, so I don't know how the price compares to the older fixed point devices. The older devices have 32 bit DSP instructions with a 40 bit accumulator, and I see some of those at Digikey in the US $1.50 range.
 
All in software. It's not running as fast as I would like (but looks OK visually) but I have some ideas to optimise it more before giving up and moving to a faster micro. I don't have the PCB space for external rectifier circuitry and I enjoy a bit of a challenge
 
All in software. It's not running as fast as I would like (but looks OK visually) but I have some ideas to optimise it more before giving up and moving to a faster micro. I don't have the PCB space for external rectifier circuitry and I enjoy a bit of a challenge
Excellent, that is the way I would prefer to do it. So, can you achieve an 44Kb/s sample rate and do all the floating point math? I know the Pico has some ARM optimised software floating point routines but I do not know how/if an AVR would cope. Have you looked at Hartley transforms?

Cheers

Ian
 
Excellent, that is the way I would prefer to do it. So, can you achieve an 44Kb/s sample rate and do all the floating point math? I know the Pico has some ARM optimised software floating point routines but I do not know how/if an AVR would cope. Have you looked at Hartley transforms?

Cheers

Ian
I could probably reach 44Kb with 1 or 2 channels but I'm trying to do 5 channels :)
The ADC in the attiny1617 maxes out at 115K. Something I glimpsed over when choosing that device.
The ADC's in the AVRs max out at 375K. I might 'cheat' and go back to a STM32 based micro.

Something I noticed when I did some very early testing with tones from my laptop:
I expected the displayed level to drop as I increased the test tone frequency (due to the poor sample rate) but it didn't
Is this because it was a steady tone and the even with being under-sampled, the average is still correct?
Or does aliasing not matter (too much) if it's just to display a level?

Hartley transforms: Never heard of them. How would they be used in this case?
 
With a single tone, if it is above half the sample frequency will be aliased as you say but the amplitude will remain the same so you get a true indication. How this translates when the signal is wide bandwidth audio I do not know - is there any cancellation with in band frequencies???. On the other hand, there is not much content above 10KHz so it might be possible to get away with a 20Kb/s sample rate.

The Hartley transform is a relative of the Fourier. It has the advantage it only requires real numbers - no complex numbers required. There is also a discrete Hartley transform.

Bottom line is it should be easier/faster to compute than Fourier. The attached readme file is from an Arduino implementation I came across.

Cheers

Ian
 

Attachments

  • fht_read_me.txt
    11.7 KB
Last edited:
25Hz is roughly the number of frames per second to trick our eyes into perceiving motion.
I've "some" experience of analogue and digital CCTV, video recording, MUX, transmission, storage, blah, and 25 IPS* is effectively from the UK broadcast TV standard which gives fairly fluid motion, especially with a bit of afterglow in the phosphor ... although it's less good when you're watching a fast car or tennis on an LED display. In the 'States, unsurprisingly, 30 rather than 25 was adopted as the de facto "real time" standard. Since IP video no one needs to be wedded to these and provided you *don't* capture at the same frequency as (or 2nd harmonic of) the AC lighting power you shouldn't suffer flicker or moiré effect.

The actual frame rate needed to perceive smooth motion is a bit lower ... from memory it's something like geography agnostic 15 /second although you can go as low as 8 before it's really nasty. The thing is, and this feels likely for a desk, those update rates doesn't hold true when a human blinks or rapidly scans across a bank of displays. E.g.: try rapidly blinking when looking at tail lights on a moving car (not while driving, obv!) and you might see they strobe. This is usually for brightness control but the strobe rates used tend to be north of 200Hz, well above those generally touted as "real time".

I’d go a bit further than the excellent earlier points @ruffrecords made about capturing fast transients – clearly critical for this application – because I’d also want to consider operator fatigue which it’s fairly well documented tends to be heightened and or accelerated in control rooms that present lots of flicker.

When consumer grade 100Hz TVs launched in the UK, I did grin at the "flicker free" marketing ... especially now we have 200Hz models!

Apologies because I did go a very long way to make that point but the experience of using analogue PPMs made by Sifam is so lovely and many, many times better than nearly all later alternatives which may have been lower cost or met the technical criteria for dwell time, etc., blah, but missed the fact that a human needs to be able to use them, for long periods and preferably without throwing up all over the desk.

I'd aim for an absolute minimum display update speed of 100 /second, 200 would be far better and be prepared to increase that in larger banks or if flicker is perceived.

Cheers, Will.



* somewhat confusingly to old people like me because I still sometimes think “tape speed”!
 
Has anyone used the MAX 7219/7221 LED drivers for audio meters? In theory one chip could do 8 bars graphs of 8 LEDs each or 4 of`16 each. Ideal for bus meters? Ans still available in a DIP package!

Cheers

Ian
I used that chip. Worked great.

Here's some Arduino code:

mled32.h
--
#ifdef __cplusplus
extern "C" {
#endif


#define BIT_MASK(n) (1ul << (n))
#define BIT_ISSET(p,n) (((p) & (1ul << (n))) ? 1 : 0)
#define BIT_SET(p,n) ((p) |= (1ul << (n)))
#define BIT_UNSET(p,n) ((p) &= ~(1ul << (n)))
#define BIT_FLIP(p,n) ((p) ^= (1ul << (n)))


#define MLED32_DIG0 0
#define MLED32_DIG1 1
#define MLED32_DIG2 2
#define MLED32_DIG3 3
#define MLED32_DIG4 4
#define MLED32_DIG5 5
#define MLED32_DIG6 6
#define MLED32_DIG7 7


#define MLED32_SEG0 0
#define MLED32_SEG1 1
#define MLED32_SEG2 2
#define MLED32_SEG3 3
#define MLED32_SEG4 4
#define MLED32_SEG5 5
#define MLED32_SEG6 6
#define MLED32_SEG7 7


#define MLED32_SEGG 0
#define MLED32_SEGF 1
#define MLED32_SEGE 2
#define MLED32_SEGD 3
#define MLED32_SEGC 4
#define MLED32_SEGB 5
#define MLED32_SEGA 6
#define MLED32_SEGDP 7


typedef uint8_t mled32_shared_t[8];

struct mled32_led {
uint8_t bit;
uint8_t dig;
uint8_t seg;
};
struct mled32 {
mled32_shared_t *shared;
struct mled32_led *leds;
uint8_t nleds;
uint8_t pin_cs;
uint32_t mask;
uint32_t curr;
};


int max7221_reset(uint8_t pin_cs,
uint8_t intensity,
uint8_t scanlimit);
int max7221_test(uint8_t pin_cs, int op);
int mled32_init(struct mled32 *ml,
mled32_shared_t *shared,
uint8_t pin_cs,
struct mled32_led *leds,
uint8_t nleds);
int mled32_write(struct mled32 *ml, uint32_t bval);


#ifdef __cplusplus
}
#endif
mled32.cpp
--
#include <SPI.h>


#include "mled32.h"

SPISettings max7221(10000000, MSBFIRST, SPI_MODE0);

int
max7221_reset(uint8_t pin_cs,
uint8_t intensity,
uint8_t scanlimit)
{
uint8_t data[][2] = {
{ 0x01, 0x00 },
{ 0x02, 0x00 },
{ 0x03, 0x00 },
{ 0x04, 0x00 },
{ 0x05, 0x00 },
{ 0x06, 0x00 },
{ 0x07, 0x00 },
{ 0x08, 0x00 },
{ 0x0a, intensity }, // intensity
{ 0x0b, scanlimit }, // scanlimit
{ 0x0c, 0x01 }, // shutdown
};
int di, dn;


dn = sizeof(data) / sizeof(data[0]);

SPI.beginTransaction(max7221);
for (di = 0; di < dn; di++) {
digitalWrite(pin_cs, LOW);
SPI.transfer(data[di][0]);
SPI.transfer(data[di][1]);
digitalWrite(pin_cs, HIGH);
}
SPI.endTransaction();


return 0;
}


int
max7221_test(uint8_t pin_cs, int op)
{
if (op < 2) {
int di;


SPI.beginTransaction(max7221);
for (di = 0; di < 8; di++) {
digitalWrite(pin_cs, LOW);
SPI.transfer(0x01 + di);
SPI.transfer(op ? 0xff : 0x00);
digitalWrite(pin_cs, HIGH);
}
SPI.endTransaction();
} else if (op == 2) {
static int dii = 0;
static int sii = 0;
int di;


if (sii == 8) {
sii = 0;
dii++;
}
if (dii == 8)
dii = 0;


SPI.beginTransaction(max7221);
for (di = 0; di < 8; di++) {
digitalWrite(pin_cs, LOW);
SPI.transfer(0x01 + di);
SPI.transfer(di == dii ? BIT_MASK(sii) : 0x00);
digitalWrite(pin_cs, HIGH);
}
SPI.endTransaction();


sii++;
} else if (op == 3) {
static int intensity = 0;
static int updown = 1;


SPI.beginTransaction(max7221);
digitalWrite(pin_cs, LOW);
SPI.transfer(0x0a);
SPI.transfer(intensity & 0xF);
digitalWrite(pin_cs, HIGH);
SPI.endTransaction();


//Serial.println(intensity, HEX);

intensity += updown;

if (intensity == 0 || intensity == 15)
updown *= -1;
}


return 0;
}


int
mled32_write(struct mled32 *ml, uint32_t bval)
{
uint32_t trans;
int li, di;
struct mled32_led *ld;
uint8_t dig_updated = 0;


trans = ml->curr ^ bval & ml->mask;

for (li = 0; li < ml->nleds; li++) {
ld = &ml->leds[li];


if (BIT_ISSET(trans, ld->bit) == 0) {
continue;
}


BIT_FLIP(ml->curr, ld->bit);
BIT_FLIP((*ml->shared)[ld->dig], ld->seg);
BIT_SET(dig_updated, ld->dig);
}


SPI.beginTransaction(max7221);
for (di = 0; dig_updated; di++) {
if (dig_updated & 1) {
digitalWrite(ml->pin_cs, LOW);
SPI.transfer(0x01 + di);
SPI.transfer((*ml->shared)[di]);
digitalWrite(ml->pin_cs, HIGH);
}
dig_updated >>= 1;
}
SPI.endTransaction();


return 0;
}


int
mled32_init(struct mled32 *ml,
mled32_shared_t *shared,
uint8_t pin_cs,
struct mled32_led *leds,
uint8_t nleds)
{
int li;
struct mled32_led *ld;
uint32_t mask = 0;


memset(ml, 0, sizeof(*ml));
memset(shared, 0, sizeof(*shared));


ml->shared = shared;
ml->pin_cs = pin_cs;
ml->leds = leds;
ml->nleds = nleds;


for (li = 0; li < ml->nleds; li++) {
ld = &ml->leds[li];


BIT_SET(mask, ld->bit);
}


ml->mask = mask;

pinMode(pin_cs, OUTPUT);

return 0;
}


Maybe it will give you some ideas.
 
Last edited:
EDIT: did not realize there was a full thread about this from last year lol my bad: https://groupdiy.com/threads/lm339-and-a-simple-4-segmented-vu-meter.83137/

Unrelated to programmed code for LED drivers, but just something I thought about now.

dbx used the same LED meter scheme for all of the 900 series units on a sub board, and it stayed pretty much unchanged since the 1980s until they stopped making the units in the early 2000s.

They used several LM339 comparators, which are still available and very cheap. There are other schemes using the LM339 but this one looks particularly simple and is well laid out.

Here are two revisions of the LED board schematic from the late 90s / early 2000s versions when dbx was owned by Harman, plus a PCB overlay diagram:
https://drive.google.com/drive/folders/1hy9qSaKFTsDR-mTdvKOdvK14O5Ia1Thp


And here’s the full manual + schematics for the 903 Compressor from the early 1990s when AKG owned them which I think is the same other than some value changes:

Obviously they had it set up to show reduction
 
Last edited:
"dbx used the same LED meter scheme for all of the 900 series units on a sub board"

- suspect this is the same as used on the dbx 160X / XT / A. On the X and XT it was a piggy-back board. When a driver chip fails a block of LEDs goes out and a little sleuthing pinpoints the relevant one
 
"dbx used the same LED meter scheme for all of the 900 series units on a sub board"

- suspect this is the same as used on the dbx 160X / XT / A. On the X and XT it was a piggy-back board. When a driver chip fails a block of LEDs goes out and a little sleuthing pinpoints the relevant one
Yeah same idea here with the simplified 263X
 

Attachments

  • IMG_5072.png
    IMG_5072.png
    452 KB

Latest posts

Back
Top