adafruit / Adafruit_Protomatter

RGB matrix library for Arduino
64 stars 23 forks source link

Brightness Control #19

Open jepler opened 4 years ago

jepler commented 4 years ago

In https://github.com/adafruit/circuitpython/issues/3409, @riffnshred requests the ability to control brightness:

Hey guys, Sorry if this as been discussed already but I can't find anything using google and search on here.

I've been playing around with the Feather M4 express and an 64x32 RGB matrix. First thing I've been looking for when I started running demos is for a way to reduce the brightness.

I found that you can only turn on or off the LEDs by setting the brightness attribute to 1.0 or 0.0: https://circuitpython.readthedocs.io/en/5.3.x/shared-bindings/rgbmatrix/RGBMatrix.html

Is there any way I can hack my way to adjust the brightness? On all my other raspberry pi projects where I run an rgb matrix, I set them to half brightness because I feel like looking at it 100% brightness is way too intense.

Thank you so much in advance

I have off-the-cuff suggested this might be possible with a fixed off-time between refreshes, but I do not know what the correct implementation is.

riffnshred commented 4 years ago

Im no expert with C, I just know how to get my way around it, but I think there might be an answer in the rpi-rgb-led-matrix repo.

riffnshred commented 4 years ago

So looks like the rpi-rgb-led-matrix play with PWM to tweak the brightness of the LEDs.

Hopefully that can help figure this one out.

daveythacher commented 3 years ago

So looks like the rpi-rgb-led-matrix play with PWM to tweak the brightness of the LEDs.

Hopefully that can help figure this one out.

This project is different from the rpi-rgb-led-matrix in a number of ways. It runs on the raspberry pi from user space, which may not have the most accurate notion of time. It recommends using PWM hardware to control OE signal, which is also why the brightness is actually controlled by software.

It uses the PWM hardware as a timer to only allow the panel to be on for the selected timer interval for BCM. Instead of a time delay causing a small step to be too bright it causes it to be too dim. However this project supports ISR, which removes this problem.

Therefore the OE pin is free to be used it looks like. If you pulse this pin at a frequency of around 1-5MHz with a few bits you could average the current using standard PWM. At least for constant current panels which is what this library targets. There is possibly another approach but that is off topic. Generally speaking the brightness of a LED is linear to current, while voltage is non-linear to current.

rpi-rgb-led-matrix works off luminance using CIE1931, which is non-linear scaling. Human eye brightness is non-linear and therefore there is perceptive brightness. Technically there is a different algorithm for this, but that is getting off topic. The brightness function will scale all incoming 8 bit color values. This value is looked up in a table which will convert it to 11 bits.

rpi-rgb-led-matrix will then shift out this 11 bit value using BCM. Again the PWM hardware forms a timer which is polled by the user space library. Usually running on a reserved core.

This project appears to use RGB 5-6-5 on output which is really 6 bit. (May support more?) The rpi-rgb-led-matrix is RGB 8-8-8 on input and RGB 11-11-11 on output, however this can be configured. Due to CIE1931 and PWM hardware the rpi-rgb-led-matrix exposes a few configuration options which allows some goofy behavior if you know what your doing.

I do not believe you will be able to do much in the way of software scaling without reworking sections of the logic. Increasing PWM steps will compromise performance and increase memory usage. BCM is generally better about this than software PWM. There is more too it than that, but I will try to stay on topic.

You could just divide the RGB values or toggle OE. Likely the OE pin could be defined to just some random unused GPIO pin. Note I only scanned the source briefly.

daveythacher commented 3 years ago

I have off-the-cuff suggested this might be possible with a fixed off-time between refreshes, but I do not know what the correct implementation is.

This may work, but has issues for larger panels. It can consume more memory or cycles. However the most important tradeoff is a loss of bandwidth due to need to increase the refresh rate. This lost bandwidth will cost panel size or image quality.

For this library you will need to use a lot of cycles. How much time you have to draw double the frame rate is unknown. How fast your timer can be is unknown. How fast the GPIO can be is unknown. You would need to figure that out.

Toggling OE would get you something similar. There are maybe some software like tricks you could do here. However this is very expensive.

board707 commented 2 years ago

Toggling OE would get you something similar. There are maybe some software like tricks you could do here. However this is very expensive.

Hi @daveythacher There is a way to control the brightness with the OE toggling, which is fully compatible with the existing BCM model and does not require additional cycles. In this library, with each refreshing we setup the timer period for displaying previous portion of data. The duration of the period is proportional to the binary power of the number of current plane. If we set up another channel of this timer so that the OE turns on exactly at half of this period, we will get a brightness of 50% of the maximum. By changing the ratio of the length of the full period and the on-time OE, we can implement convenient brightness control without additional cycles and memory consumption. In the picture below you can see a real example of the use of such technology: brightness_control2 It could be called synchronous-PWM if the s-PWM abbreviation wasn't already taken :) This can be easily done on timers in STM32 boards. I use this method for control the brightness of RGB panels in my library, DMD_STM32, which is influenced by RGB-matrix-Panel.

riffnshred commented 10 months ago

Toggling OE would get you something similar. There are maybe some software like tricks you could do here. However this is very expensive.

Hi @daveythacher There is a way to control the brightness with the OE toggling, which is fully compatible with the existing BCM model and does not require additional cycles. In this library, with each refreshing we setup the timer period for displaying previous portion of data. The duration of the period is proportional to the binary power of the number of current plane. If we set up another channel of this timer so that the OE turns on exactly at half of this period, we will get a brightness of 50% of the maximum. By changing the ratio of the length of the full period and the on-time OE, we can implement convenient brightness control without additional cycles and memory consumption. In the picture below you can see a real example of the use of such technology: brightness_control2 It could be called synchronous-PWM if the s-PWM abbreviation wasn't already taken :) This can be easily done on timers in STM32 boards. I use this method for control the brightness of RGB panels in my library, DMD_STM32, which is influenced by RGB-matrix-Panel.

Could this be done using circuit python? or does this require to modify the circuit python code it self?

PaintYourDragon commented 10 months ago

I think this was discussed in another issue or in the Protomatter source, forget exactly where…absolutely yes this is possible in theory, but every time I’ve tried to implement it my brain kind of melts down in the details. It requires a second timer interrupt synchronized off the same clock…but every mcu timer peripheral is different, so it’s easy on some (e.g. A/B timer match on some chips) but not on others, so the idea has always fallen apart somewhere along the way. Alternately, using a PWM output for the OE signal, but many chips are inflexible with regard to PWM output assignments, so this has been avoided (so the same shields/feathers can work across many architectures).

If the lowest-level Protomatter code had the luxury of being custom-written to each chip’s strengths (and peripheral idiosyncrasies) that might be one way to address it. But as it is, it’s very simplistic code to run everywhere with minimal differences and with the least pin constraints.

board707 commented 10 months ago

It requires a second timer interrupt synchronized off the same clock…

No, it could be done by controling the OE output by another channel of the same timer. It is not require additional interrupt.

PaintYourDragon commented 10 months ago

Yes…I mean…depending if the architecture has such thing as timer channels. I think that was the hangup, that some don’t, it’s been a while and I don’t even remember which chip(s). I had to walk away from it because it was Just Too Much.

riffnshred commented 10 months ago

So my question right now is, and I'm sorry if it get out of the scope of the matter, but would there be way to apply this on a Matrix Portal S3. I don't even care if I have to solder two pins together and use PWM that way to adjust the ON and OFF time of the LEDs.

board707 commented 10 months ago

every mcu timer peripheral is different, so it’s easy on some (e.g. A/B timer match on some chips) but not on others, so the idea has always fallen apart somewhere along the way.

The idea can be realised on many different MCUs, So far I have implemented this way of brightness control on STM32F1 & F4, Raspberry Pi Pico RP2040, ATmega328 and 2560, Atmega4809 (Nano Every) and WinnerMicro W806

PaintYourDragon commented 10 months ago

@riffnshred Not in CircuitPython at present, since it builds on this library. If working in Arduino, I’d be surprised if there isn’t a different/better HUB75 library that has the luxury of fully exploiting ESP32-S3 features (like totally flexible pin MUX). But as it is, currently limited by some design decisions going way back and having to keep broad chip support.

@board707 One of the “rules” in Protomatter is that OE has to work from any I/O pin (for compatibility with certain existing HUB75 breakouts). The limited I/O MUX capabilities on some chips make this difficult when operating with specific timers/channels. If you‘ve worked out universal handling for this on all supported Protomatter architectures, please submit a PR. I’m no longer the primary maintainer on this repo but I’m sure its keepers would be interested, this is an oft-requested feature.

riffnshred commented 10 months ago

@PaintYourDragon Thanks, I'll look into it. cheers

board707 commented 10 months ago

@PaintYourDragon You are right, this approach imposes restrictions on the choice of pins, sometimes so strict that you only have one strictly defined set of pins for a particular controller. I understand that this may be contrary to the ideas that guide the Protomatter library's developers. I prefer a different path - by adding different controllers to my library, I try to use the architectural advantages of each of them

utiq commented 8 months ago

@riffnshred did you figure it out? I'm trying to accomplish the same than you

CoastalAmusementsFrank commented 8 months ago

@riffnshred Did you ever come to a solution for controlling the brightness of the LEDs?

board707 commented 8 months ago

I showed a way to control brightness in the diagrams in my message (Jul 19, 2022) at the beginning of the discussion.

riffnshred commented 8 months ago

@CoastalAmusementsFrank @utiq

No sadly. I think my next step is to flash arduino on the Matrix S3 and try other libraries (Smartmatrix or fast led, I dont remember which one).

czei commented 6 months ago

I realize this is a challenging problem, but I'm getting complaints about the LED brightness driven by a MatrixPortal S3 blinding people. It's nice it can get that bright for outdoor use, but this is indoors, and it's seriously causing me to reconsider using a MatrixPortal S3 for this project. Even coding the project with the LED on my desk means I have to keep the LED face down on the table because the brightness is so glaring.

tannewt commented 6 months ago

Note that you can get more brightness control by increasing the bit depth (at the cost of RAM). Thanks to the comment here: https://github.com/adafruit/circuitpython/issues/3409#issuecomment-1955756380