Inital commit
Very much work in progress. Terrible code. Aim is to make this Adafruit compatable.
This commit is contained in:
parent
23963f791a
commit
3245a37e3e
26 changed files with 7902 additions and 0 deletions
202
LICENSE
Normal file
202
LICENSE
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
10
Makefile
Normal file
10
Makefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := led-display-controller
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
Currently WIP. Dirty demo code only.
|
||||
|
||||
Seems to work extremly well and drive a RGB panel with 24bit color at very high refresh rates.
|
||||
|
||||
Based off 'SmartMatrix' code.
|
||||
|
||||
Credits:
|
||||
https://www.esp32.com/viewtopic.php?f=17&t=3188
|
||||
https://www.esp32.com/viewtopic.php?f=13&t=3256
|
||||
|
13
README.rst
Normal file
13
README.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
EXAMPLE TO USE I2S TO DRIVE A LED DISPLAY
|
||||
=========================================
|
||||
|
||||
This is example code to drive one of the common 64x32-pixel RGB LED
|
||||
screen. It illustrates the parallel output mode of the I2S peripheral.
|
||||
|
||||
See main/app_main.c for more information and how to hook up a display.
|
||||
|
||||
This is PRELIMINARY CODE and Espressif gives no support on it.
|
||||
|
||||
See this forum thread for the original source, and discussion:
|
||||
|
||||
https://www.esp32.com/viewtopic.php?t=3188
|
1
anim.h
Normal file
1
anim.h
Normal file
|
@ -0,0 +1 @@
|
|||
extern const unsigned char *anim;
|
BIN
anim/lenna.png
Normal file
BIN
anim/lenna.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
1
anim/lenna.rgb
Normal file
1
anim/lenna.rgb
Normal file
File diff suppressed because one or more lines are too long
21
anim/mkanimfile.sh
Normal file
21
anim/mkanimfile.sh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
#Simple and stupid script to (re)generate image data. Needs an Unix-ish environment with
|
||||
#ImageMagick and xxd installed.
|
||||
|
||||
convert nyan_64x32.gif nyan_64x32-f%02d.rgb
|
||||
convert lenna.png lenna.rgb
|
||||
|
||||
OUTF="../anim.c"
|
||||
|
||||
echo '//Auto-generated' > $OUTF
|
||||
echo 'static const unsigned char myanim[]={' >> $OUTF
|
||||
{
|
||||
for x in nyan_64x32-f*.rgb; do
|
||||
echo $x >&2
|
||||
cat $x
|
||||
done
|
||||
cat lenna.rgb
|
||||
} | xxd -i >> $OUTF
|
||||
echo "};" >> $OUTF
|
||||
echo 'const unsigned char *anim=&myanim[0];' >> $OUTF
|
BIN
anim/nyan_64x32-f00.rgb
Normal file
BIN
anim/nyan_64x32-f00.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f01.rgb
Normal file
BIN
anim/nyan_64x32-f01.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f02.rgb
Normal file
BIN
anim/nyan_64x32-f02.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f03.rgb
Normal file
BIN
anim/nyan_64x32-f03.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f04.rgb
Normal file
BIN
anim/nyan_64x32-f04.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f05.rgb
Normal file
BIN
anim/nyan_64x32-f05.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f06.rgb
Normal file
BIN
anim/nyan_64x32-f06.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f07.rgb
Normal file
BIN
anim/nyan_64x32-f07.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f08.rgb
Normal file
BIN
anim/nyan_64x32-f08.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f09.rgb
Normal file
BIN
anim/nyan_64x32-f09.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f10.rgb
Normal file
BIN
anim/nyan_64x32-f10.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32-f11.rgb
Normal file
BIN
anim/nyan_64x32-f11.rgb
Normal file
Binary file not shown.
BIN
anim/nyan_64x32.gif
Normal file
BIN
anim/nyan_64x32.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
9
component.mk
Normal file
9
component.mk
Normal file
|
@ -0,0 +1,9 @@
|
|||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
|
637
esp32_I2sParallelDmaLedMatrix-row-based-refresh-16bitv2.ino
Normal file
637
esp32_I2sParallelDmaLedMatrix-row-based-refresh-16bitv2.ino
Normal file
|
@ -0,0 +1,637 @@
|
|||
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_heap_caps.h"
|
||||
#include "anim.h"
|
||||
#include "val2pwm.h"
|
||||
#include "esp32_i2s_parallel.h"
|
||||
#include "CircularBuffer.h"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed
|
||||
continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable
|
||||
input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate
|
||||
RGB pixel input, the rest of the inputs are shared.
|
||||
|
||||
Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins
|
||||
to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel,
|
||||
giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high,
|
||||
the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just
|
||||
clocked in.
|
||||
|
||||
The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data
|
||||
will immediately appear on this line. Note: While clocking in data for a line, the *previous* line is still displayed, and these lines should
|
||||
be set to the value to reflect the position the *previous* line is supposed to be on.
|
||||
|
||||
Finally, the screen has an OE input, which is used to disable the LEDs when latching new data and changing the state of the line select inputs:
|
||||
doing so hides any artifacts that appear at this time. The OE line is also used to dim the display by only turning it on for a limited time every
|
||||
line.
|
||||
|
||||
All in all, an image can be displayed by 'scanning' the display, say, 100 times per second. The slowness of the human eye hides the fact that
|
||||
only one line is showed at a time, and the display looks like every pixel is driven at the same time.
|
||||
|
||||
Now, the RGB inputs for these types of displays are digital, meaning each red, green and blue subpixel can only be on or off. This leads to a
|
||||
color palette of 8 pixels, not enough to display nice pictures. To get around this, we use binary code modulation.
|
||||
|
||||
Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without
|
||||
binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length.
|
||||
|
||||
We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a
|
||||
normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7
|
||||
to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set,
|
||||
we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0.
|
||||
|
||||
Now, in order to (on average within a frame) turn a LED on for the time specified in the pixel value in the input data, we need to weigh the
|
||||
subframes. We have 15 pixels: if we show subframe 3 for 8 of them, subframe 2 for 4 of them, subframe 1 for 2 of them and subframe 1 for 1 of
|
||||
them, this 'automatically' happens. (We also distribute the subframes evenly over the ticks, which reduces flicker.)
|
||||
|
||||
|
||||
In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory
|
||||
contains a sequence of all the signals (2xRGB, line select, latch enable, output enable) that need to be sent to the display for that subframe.
|
||||
Then we ask the I2S-parallel driver to set up a DMA chain so the subframes are sent out in a sequence that satisfies the requirement that
|
||||
subframe x has to be sent out for (2^x) ticks. Finally, we fill the subframes with image data.
|
||||
|
||||
We use a frontbuffer/backbuffer technique here to make sure the display is refreshed in one go and drawing artifacts do not reach the display.
|
||||
In practice, for small displays this is not really necessarily.
|
||||
|
||||
Finally, the binary code modulated intensity of a LED does not correspond to the intensity as seen by human eyes. To correct for that, a
|
||||
luminance correction is used. See val2pwm.c for more info.
|
||||
|
||||
Note: Because every subframe contains one bit of grayscale information, they are also referred to as 'bitplanes' by the code below.
|
||||
*/
|
||||
|
||||
#define matrixHeight 32
|
||||
#define matrixWidth 64
|
||||
#define matrixRowsInParallel 2
|
||||
|
||||
|
||||
#define ESP32_NUM_FRAME_BUFFERS 2 // from SmartMatrixMultiPlexedRefresESP32.h
|
||||
#define ESP32_OE_OFF_CLKS_AFTER_LATCH 1
|
||||
#define ESP32_I2S_CLOCK_SPEED (20000000UL)
|
||||
|
||||
|
||||
#define COLOR_DEPTH 24 // known working: 24, 48 - If the sketch uses type `rgb24` directly, COLOR_DEPTH must be 24
|
||||
const uint8_t kMatrixWidth = 64; // known working: 32, 64, 96, 128
|
||||
const uint8_t kMatrixHeight = 32; // known working: 16, 32, 48, 64
|
||||
const uint8_t kRefreshDepth = 24; // known working: 24, 36, 48
|
||||
const uint8_t kDmaBufferRows = 2; // known working: 2-4, use 2 to save memory, more to keep from dropping frames and automatically lowering refresh rate
|
||||
|
||||
//This is the bit depth, per RGB subpixel, of the data that is sent to the display.
|
||||
//The effective bit depth (in computer pixel terms) is less because of the PWM correction. With
|
||||
//a bitplane count of 7, you should be able to reproduce an 16-bit image more or less faithfully, though.
|
||||
#define BITPLANE_CNT 7
|
||||
|
||||
// LSBMSB_TRANSITION_BIT defines the color bit that is refreshed once per frame, with the same brightness as the bits above it
|
||||
// when LSBMSB_TRANSITION_BIT is non-zero, all color bits below it will be be refreshed only once, with fractional brightness, saving RAM and speeding up refresh
|
||||
// LSBMSB_TRANSITION_BIT must be < BITPLANE_CNT
|
||||
#define LSBMSB_TRANSITION_BIT 1
|
||||
|
||||
//64*32 RGB leds, 2 pixels per 16-bit value...
|
||||
#define BITPLANE_SZ (matrixWidth*matrixHeight/matrixRowsInParallel)
|
||||
|
||||
// From MatrixHardware_ESP32_V0
|
||||
// ADDX is output directly using GPIO
|
||||
#define CLKS_DURING_LATCH 0
|
||||
#define MATRIX_I2S_MODE I2S_PARALLEL_BITS_16
|
||||
#define MATRIX_DATA_STORAGE_TYPE uint16_t
|
||||
|
||||
//#define CLKS_DURING_LATCH 4
|
||||
//#define MATRIX_I2S_MODE I2S_PARALLEL_BITS_8
|
||||
//#define MATRIX_DATA_STORAGE_TYPE uint8_t
|
||||
|
||||
|
||||
|
||||
//Upper half RGB
|
||||
#define BIT_R1 (1<<0)
|
||||
#define BIT_G1 (1<<1)
|
||||
#define BIT_B1 (1<<2)
|
||||
//Lower half RGB
|
||||
#define BIT_R2 (1<<3)
|
||||
#define BIT_G2 (1<<4)
|
||||
#define BIT_B2 (1<<5)
|
||||
|
||||
// Control Signals
|
||||
#define BIT_LAT (1<<6)
|
||||
#define BIT_OE (1<<7)
|
||||
|
||||
#define BIT_A (1<<8)
|
||||
#define BIT_B (1<<9)
|
||||
#define BIT_C (1<<10)
|
||||
#define BIT_D (1<<11)
|
||||
#define BIT_E (1<<12)
|
||||
|
||||
// Pin Definitions
|
||||
/*
|
||||
#define R1_PIN 2
|
||||
#define G1_PIN 15
|
||||
#define B1_PIN 4
|
||||
#define R2_PIN 16
|
||||
#define G2_PIN 27
|
||||
#define B2_PIN 17
|
||||
|
||||
#define A_PIN 5
|
||||
#define B_PIN 18
|
||||
#define C_PIN 19
|
||||
#define D_PIN 21
|
||||
#define LAT_PIN 26
|
||||
#define OE_PIN 25
|
||||
|
||||
#define CLK_PIN 22
|
||||
*/
|
||||
|
||||
#define R1_PIN 25
|
||||
#define G1_PIN 26
|
||||
#define B1_PIN 27
|
||||
#define R2_PIN 14
|
||||
#define G2_PIN 12
|
||||
#define B2_PIN 13
|
||||
|
||||
#define A_PIN 23
|
||||
#define B_PIN 22
|
||||
#define C_PIN 5
|
||||
#define D_PIN 17
|
||||
#define LAT_PIN 4
|
||||
#define OE_PIN 15
|
||||
|
||||
#define CLK_PIN 16
|
||||
|
||||
|
||||
#define MATRIX_PANEL_HEIGHT 32
|
||||
#define MATRIX_STACK_HEIGHT (matrixHeight / MATRIX_PANEL_HEIGHT)
|
||||
|
||||
#define PIXELS_PER_LATCH ((matrixWidth * matrixHeight) / MATRIX_PANEL_HEIGHT) // = 64
|
||||
#define ROW_PAIR_OFFSET 16
|
||||
|
||||
#define COLOR_CHANNELS_PER_PIXEL 3
|
||||
#define LATCHES_PER_ROW (kRefreshDepth/COLOR_CHANNELS_PER_PIXEL)
|
||||
#define COLOR_DEPTH_BITS (kRefreshDepth/COLOR_CHANNELS_PER_PIXEL)
|
||||
#define ROWS_PER_FRAME 16
|
||||
|
||||
|
||||
|
||||
|
||||
// note: sizeof(data) must be multiple of 32 bits, as DMA linked list buffer address pointer must be word-aligned.
|
||||
struct rowBitStruct {
|
||||
MATRIX_DATA_STORAGE_TYPE data[((matrixWidth * matrixHeight) / 32) + CLKS_DURING_LATCH];
|
||||
};
|
||||
|
||||
struct rowDataStruct {
|
||||
rowBitStruct rowbits[COLOR_DEPTH_BITS];
|
||||
};
|
||||
|
||||
struct frameStruct {
|
||||
rowDataStruct rowdata[ROWS_PER_FRAME];
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//Get a pixel from the image at pix, assuming the image is a 64x32 8R8G8B image
|
||||
//Returns it as an uint32 with the lower 24 bits containing the RGB values.
|
||||
static uint32_t getpixel(const unsigned char *pix, int x, int y) {
|
||||
const unsigned char *p=pix+((x+y*64)*3);
|
||||
return (p[0]<<16)|(p[1]<<8)|(p[2]);
|
||||
}
|
||||
|
||||
int brightness=28; //Change to set the global brightness of the display, range 1-matrixWidth
|
||||
//Warning when set too high: Do not look into LEDs with remaining eye.
|
||||
|
||||
|
||||
|
||||
|
||||
// pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel (two rows of pixels are refreshed in parallel)
|
||||
frameStruct *matrixUpdateFrames;
|
||||
|
||||
// other variables
|
||||
uint8_t lsbMsbTransitionBit;
|
||||
|
||||
CircularBuffer dmaBuffer;
|
||||
uint16_t refreshRate;
|
||||
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
// cbInit(&dmaBuffer, ESP32_NUM_FRAME_BUFFERS);
|
||||
|
||||
printf("Starting SmartMatrix DMA Mallocs\r\n");
|
||||
|
||||
matrixUpdateFrames = (frameStruct *)heap_caps_malloc(sizeof(frameStruct) * ESP32_NUM_FRAME_BUFFERS, MALLOC_CAP_DMA);
|
||||
assert("can't allocate SmartMatrix frameStructs");
|
||||
|
||||
printf("Allocating refresh buffer:\r\nDMA Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
|
||||
|
||||
// setupTimer();
|
||||
|
||||
// calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory
|
||||
int numDescriptorsPerRow;
|
||||
lsbMsbTransitionBit = 0;
|
||||
while(1) {
|
||||
numDescriptorsPerRow = 1;
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++) {
|
||||
numDescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1);
|
||||
}
|
||||
|
||||
int ramrequired = numDescriptorsPerRow * ROWS_PER_FRAME * ESP32_NUM_FRAME_BUFFERS * sizeof(lldesc_t);
|
||||
int largestblockfree = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);
|
||||
|
||||
printf("lsbMsbTransitionBit of %d requires %d RAM, %d available, leaving %d free: \r\n", lsbMsbTransitionBit, ramrequired, largestblockfree, largestblockfree - ramrequired);
|
||||
|
||||
if(ramrequired < (largestblockfree))
|
||||
break;
|
||||
|
||||
if(lsbMsbTransitionBit < COLOR_DEPTH_BITS - 1)
|
||||
lsbMsbTransitionBit++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(numDescriptorsPerRow * ROWS_PER_FRAME * ESP32_NUM_FRAME_BUFFERS * sizeof(lldesc_t) > heap_caps_get_largest_free_block(MALLOC_CAP_DMA)){
|
||||
assert("not enough RAM for SmartMatrix descriptors");
|
||||
printf("not enough RAM for SmartMatrix descriptors\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Raised lsbMsbTransitionBit to %d/%d to fit in RAM\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1);
|
||||
|
||||
// calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate
|
||||
while(1) {
|
||||
int psPerClock = 1000000000000UL/ESP32_I2S_CLOCK_SPEED;
|
||||
int nsPerLatch = ((PIXELS_PER_LATCH + CLKS_DURING_LATCH) * psPerClock) / 1000;
|
||||
//printf("ns per latch: %d: \r\n", nsPerLatch);
|
||||
|
||||
// add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions...
|
||||
int nsPerRow = COLOR_DEPTH_BITS * nsPerLatch;
|
||||
|
||||
// add time to shift out MSBs
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++)
|
||||
nsPerRow += (1<<(i - lsbMsbTransitionBit - 1)) * (COLOR_DEPTH_BITS - i) * nsPerLatch;
|
||||
|
||||
//printf("nsPerRow: %d: \r\n", nsPerRow);
|
||||
|
||||
int nsPerFrame = nsPerRow * ROWS_PER_FRAME;
|
||||
//printf("nsPerFrame: %d: \r\n", nsPerFrame);
|
||||
|
||||
int actualRefreshRate = 1000000000UL/(nsPerFrame);
|
||||
|
||||
refreshRate = actualRefreshRate;
|
||||
|
||||
printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate);
|
||||
|
||||
if(lsbMsbTransitionBit < COLOR_DEPTH_BITS - 1)
|
||||
lsbMsbTransitionBit++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1);
|
||||
|
||||
// TODO: completely fill buffer with data before enabling DMA - can't do this now, lsbMsbTransition bit isn't set in the calc class - also this call will probably have no effect as matrixCalcDivider will skip the first call
|
||||
//matrixCalcCallback();
|
||||
|
||||
// lsbMsbTransition Bit is now finalized - redo descriptor count in case it changed to hit min refresh rate
|
||||
numDescriptorsPerRow = 1;
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++) {
|
||||
numDescriptorsPerRow += 1<<(i - lsbMsbTransitionBit - 1);
|
||||
}
|
||||
|
||||
printf("Descriptors for lsbMsbTransitionBit %d/%d with %d rows require %d bytes of DMA RAM\r\n", lsbMsbTransitionBit, COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, 2 * numDescriptorsPerRow * ROWS_PER_FRAME * sizeof(lldesc_t));
|
||||
|
||||
// malloc the DMA linked list descriptors that i2s_parallel will need
|
||||
int desccount = numDescriptorsPerRow * ROWS_PER_FRAME;
|
||||
lldesc_t * dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
assert("Can't allocate descriptor buffer a");
|
||||
if(!dmadesc_a) {
|
||||
printf("can't malloc");
|
||||
return;
|
||||
}
|
||||
lldesc_t * dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||
assert("Can't allocate descriptor buffer b");
|
||||
if(!dmadesc_b) {
|
||||
printf("can't malloc");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("SmartMatrix Mallocs Complete\r\n");
|
||||
printf("Heap Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0));
|
||||
printf("8-bit Accessible Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_8BIT), heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
|
||||
printf("32-bit Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_32BIT), heap_caps_get_largest_free_block(MALLOC_CAP_32BIT));
|
||||
printf("DMA Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
|
||||
lldesc_t *prevdmadesca = 0;
|
||||
lldesc_t *prevdmadescb = 0;
|
||||
int currentDescOffset = 0;
|
||||
|
||||
// fill DMA linked lists for both frames
|
||||
for(int j=0; j<ROWS_PER_FRAME; j++) {
|
||||
// first set of data is LSB through MSB, single pass - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT
|
||||
// TODO: size must be less than DMA_MAX - worst case for SmartMatrix Library: 16-bpp with 256 pixels per row would exceed this, need to break into two
|
||||
link_dma_desc(&dmadesc_a[currentDescOffset], prevdmadesca, &(matrixUpdateFrames[0].rowdata[j].rowbits[0].data), sizeof(rowBitStruct) * COLOR_DEPTH_BITS);
|
||||
prevdmadesca = &dmadesc_a[currentDescOffset];
|
||||
link_dma_desc(&dmadesc_b[currentDescOffset], prevdmadescb, &(matrixUpdateFrames[1].rowdata[j].rowbits[0].data), sizeof(rowBitStruct) * COLOR_DEPTH_BITS);
|
||||
prevdmadescb = &dmadesc_b[currentDescOffset];
|
||||
currentDescOffset++;
|
||||
//printf("row %d: \r\n", j);
|
||||
|
||||
for(int i=lsbMsbTransitionBit + 1; i<COLOR_DEPTH_BITS; i++) {
|
||||
// binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc
|
||||
// because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM)
|
||||
// we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB
|
||||
//printf("buffer %d: repeat %d times, size: %d, from %d - %d\r\n", nextBufdescIndex, 1<<(i - LSBMSB_TRANSITION_BIT - 1), (COLOR_DEPTH_BITS - i), i, COLOR_DEPTH_BITS-1);
|
||||
for(int k=0; k < 1<<(i - lsbMsbTransitionBit - 1); k++) {
|
||||
link_dma_desc(&dmadesc_a[currentDescOffset], prevdmadesca, &(matrixUpdateFrames[0].rowdata[j].rowbits[i].data), sizeof(rowBitStruct) * (COLOR_DEPTH_BITS - i));
|
||||
prevdmadesca = &dmadesc_a[currentDescOffset];
|
||||
link_dma_desc(&dmadesc_b[currentDescOffset], prevdmadescb, &(matrixUpdateFrames[1].rowdata[j].rowbits[i].data), sizeof(rowBitStruct) * (COLOR_DEPTH_BITS - i));
|
||||
prevdmadescb = &dmadesc_b[currentDescOffset];
|
||||
|
||||
currentDescOffset++;
|
||||
//printf("i %d, j %d, k %d\r\n", i, j, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//End markers
|
||||
dmadesc_a[desccount-1].eof = 1;
|
||||
dmadesc_b[desccount-1].eof = 1;
|
||||
dmadesc_a[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_a[0];
|
||||
dmadesc_b[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_b[0];
|
||||
|
||||
//printf("\n");
|
||||
|
||||
i2s_parallel_config_t cfg={
|
||||
.gpio_bus={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, LAT_PIN, OE_PIN, A_PIN, B_PIN, C_PIN, D_PIN, -1, -1, -1, -1},
|
||||
.gpio_clk=CLK_PIN,
|
||||
.clkspeed_hz=ESP32_I2S_CLOCK_SPEED, //ESP32_I2S_CLOCK_SPEED, // formula used is 80000000L/(cfg->clkspeed_hz + 1), must result in >=2. Acceptable values 26.67MHz, 20MHz, 16MHz, 13.34MHz...
|
||||
.bits=MATRIX_I2S_MODE, //MATRIX_I2S_MODE,
|
||||
.bufa=0,
|
||||
.bufb=0,
|
||||
desccount,
|
||||
desccount,
|
||||
dmadesc_a,
|
||||
dmadesc_b
|
||||
};
|
||||
|
||||
//Setup I2S
|
||||
i2s_parallel_setup_without_malloc(&I2S1, &cfg);
|
||||
|
||||
printf("I2S setup done.\n");
|
||||
|
||||
|
||||
/*
|
||||
tempRow0Ptr = malloc(sizeof(rgb24) * PIXELS_PER_LATCH);
|
||||
tempRow1Ptr = malloc(sizeof(rgb24) * PIXELS_PER_LATCH);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
struct rgb24;
|
||||
|
||||
typedef struct rgb24 {
|
||||
rgb24() : rgb24(0,0,0) {}
|
||||
rgb24(uint8_t r, uint8_t g, uint8_t b) {
|
||||
red = r; green = g; blue = b;
|
||||
}
|
||||
rgb24& operator=(const rgb24& col);
|
||||
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
} rgb24;
|
||||
|
||||
/*
|
||||
#if defined(ESP32)
|
||||
// use buffers malloc'd previously
|
||||
rgb24 * tempRow0 = (rgb24*)tempRow0Ptr;
|
||||
rgb24 * tempRow1 = (rgb24*)tempRow1Ptr;
|
||||
#else
|
||||
// static to avoid putting large buffer on the stack
|
||||
static rgb24 tempRow0[PIXELS_PER_LATCH];
|
||||
static rgb24 tempRow1[PIXELS_PER_LATCH];
|
||||
#endif
|
||||
|
||||
*/
|
||||
|
||||
void loop() {
|
||||
static int apos=0; //which frame in the animation we're on
|
||||
static int backbuf_id=0; //which buffer is the backbuffer, as in, which one is not active so we can write to it
|
||||
unsigned char currentRow;
|
||||
|
||||
printf("\r\nStarting SmartMatrix Mallocs\r\n");
|
||||
printf("Heap Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0));
|
||||
printf("8-bit Accessible Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_8BIT), heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
|
||||
printf("32-bit Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_32BIT), heap_caps_get_largest_free_block(MALLOC_CAP_32BIT));
|
||||
printf("DMA Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
|
||||
// tempRow0Ptr = malloc(sizeof(rgb24) * PIXELS_PER_LATCH);
|
||||
// tempRow1Ptr = malloc(sizeof(rgb24) * PIXELS_PER_LATCH);
|
||||
|
||||
|
||||
while(1) {
|
||||
//Fill bitplanes with the data for the current image
|
||||
const uint8_t *pix=&anim[apos*64*32*3]; //pixel data for this animation frame
|
||||
|
||||
|
||||
for (unsigned int y=0; y<matrixHeight/matrixRowsInParallel; y++) // half height - 16 iterations
|
||||
{
|
||||
currentRow = y;
|
||||
/*
|
||||
// use buffers malloc'd previously
|
||||
rgb24 * tempRow0 = (rgb24*)tempRow0Ptr;
|
||||
rgb24 * tempRow1 = (rgb24*)tempRow1Ptr;
|
||||
|
||||
// clear buffer to prevent garbage data showing through transparent layers
|
||||
memset(tempRow0, 0x00, sizeof(rgb24) * PIXELS_PER_LATCH);
|
||||
memset(tempRow1, 0x00, sizeof(rgb24) * PIXELS_PER_LATCH);
|
||||
*/
|
||||
for(int j=0; j<COLOR_DEPTH_BITS; j++) // color depth - 8 iterations
|
||||
{
|
||||
int maskoffset = 0;
|
||||
/*
|
||||
if(COLOR_DEPTH_BITS == 12) // 36-bit color
|
||||
maskoffset = 4;
|
||||
else if (COLOR_DEPTH_BITS == 16) // 48-bit color
|
||||
maskoffset = 0;
|
||||
else if (COLOR_DEPTH_BITS == 8) // 24-bit color
|
||||
maskoffset = 0;
|
||||
*/
|
||||
uint16_t mask = (1 << (j + maskoffset));
|
||||
|
||||
// SmartMatrix3<refreshDepth, matrixWidth, matrixHeight, panelType, optionFlags>::rowBitStruct *p=&(frameBuffer->rowdata[currentRow].rowbits[j]); //bitplane location to write to
|
||||
//MATRIX_DATA_STORAGE_TYPE *p=matrixUpdateFrames[backbuf_id].rowdata[y].rowbits[pl].data; //matrixUpdateFrames
|
||||
rowBitStruct *p=&matrixUpdateFrames[backbuf_id].rowdata[currentRow].rowbits[j]; //matrixUpdateFrames location to write to
|
||||
|
||||
int i=0;
|
||||
while(i < PIXELS_PER_LATCH) // row pixels (64) iterations
|
||||
{
|
||||
|
||||
// parse through matrixWith block of pixels, from left to right, or right to left, depending on C_SHAPE_STACKING options
|
||||
for(int k=0; k < matrixWidth; k++) // row pixel width 64 iterations
|
||||
{
|
||||
int v=0;
|
||||
|
||||
#if (CLKS_DURING_LATCH == 0)
|
||||
// if there is no latch to hold address, output ADDX lines directly to GPIO and latch data at end of cycle
|
||||
int gpioRowAddress = currentRow;
|
||||
|
||||
// normally output current rows ADDX, special case for LSB, output previous row's ADDX (as previous row is being displayed for one latch cycle)
|
||||
if(j == 0)
|
||||
gpioRowAddress = currentRow-1;
|
||||
|
||||
if (gpioRowAddress & 0x01) v|=BIT_A;
|
||||
if (gpioRowAddress & 0x02) v|=BIT_B;
|
||||
if (gpioRowAddress & 0x04) v|=BIT_C;
|
||||
if (gpioRowAddress & 0x08) v|=BIT_D;
|
||||
// if (gpioRowAddress & 0x10) v|=BIT_E;
|
||||
|
||||
// need to disable OE after latch to hide row transition
|
||||
if((i+k) == 0) v|=BIT_OE;
|
||||
|
||||
// drive latch while shifting out last bit of RGB data
|
||||
if((i+k) == PIXELS_PER_LATCH-1) v|=BIT_LAT;
|
||||
#endif
|
||||
|
||||
// turn off OE after brightness value is reached when displaying MSBs
|
||||
// MSBs always output normal brightness
|
||||
// LSB (!j) outputs normal brightness as MSB from previous row is being displayed
|
||||
if((j > lsbMsbTransitionBit || !j) && ((i+k) >= brightness)) v|=BIT_OE;
|
||||
|
||||
// special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness
|
||||
if(j && j <= lsbMsbTransitionBit) {
|
||||
// divide brightness in half for each bit below lsbMsbTransitionBit
|
||||
int lsbBrightness = brightness >> (lsbMsbTransitionBit - j + 1);
|
||||
if((i+k) >= lsbBrightness) v|=BIT_OE;
|
||||
}
|
||||
|
||||
// need to turn off OE one clock before latch, otherwise can get ghosting
|
||||
if((i+k)==PIXELS_PER_LATCH-1) v|=BIT_OE;
|
||||
|
||||
|
||||
|
||||
//c2 = {0,0,0};
|
||||
|
||||
int c1, c2; // 32 bit int
|
||||
#if 1
|
||||
/*
|
||||
//uint32_t testpixel = 0xFFFFFFFF;
|
||||
uint32_t testpixel = 0x7F7F7F7F;
|
||||
//uint32_t testpixel = 0x80808080;
|
||||
|
||||
if((31 - i) == y)
|
||||
c1=testpixel;
|
||||
else
|
||||
c1 = 0x00;
|
||||
if((31 - i) == y+16)
|
||||
c2=testpixel;
|
||||
else
|
||||
c2 = 0x00;
|
||||
|
||||
c1 = 0xFFFFFFFF;
|
||||
*/
|
||||
|
||||
c1=getpixel(pix, k, y);
|
||||
c2=getpixel(pix, k, y+(matrixHeight/2));
|
||||
|
||||
if (c1 & (mask<<16)) v|=BIT_R1;
|
||||
if (c1 & (mask<<8)) v|=BIT_G1;
|
||||
if (c1 & (mask<<0)) v|=BIT_B1;
|
||||
if (c2 & (mask<<16)) v|=BIT_R2;
|
||||
if ( c2 & (mask<<8)) v|=BIT_G2;
|
||||
if (c2 & (mask<<0)) v|=BIT_B2;
|
||||
|
||||
#else
|
||||
|
||||
struct rgb24 c1( 255,0,0);
|
||||
struct rgb24 c2 = { 0,0,255 };
|
||||
|
||||
|
||||
if (c1.red & mask)
|
||||
v|=BIT_R1;
|
||||
if (c1.green & mask)
|
||||
v|=BIT_G1;
|
||||
if (c1.blue & mask)
|
||||
v|=BIT_B1;
|
||||
if (c2.red & mask)
|
||||
v|=BIT_R2;
|
||||
if (c2.green & mask)
|
||||
v|=BIT_G2;
|
||||
if (c2.blue & mask)
|
||||
v|=BIT_B2;
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
// 8 bit parallel mode
|
||||
//Save the calculated value to the bitplane memory in 16-bit reversed order to account for I2S Tx FIFO mode1 ordering
|
||||
if(k%4 == 0){
|
||||
p->data[(i+k)+2] = v;
|
||||
} else if(k%4 == 1) {
|
||||
p->data[(i+k)+2] = v;
|
||||
} else if(k%4 == 2) {
|
||||
p->data[(i+k)-2] = v;
|
||||
} else { //if(k%4 == 3)
|
||||
p->data[(i+k)-2] = v;
|
||||
}
|
||||
*/
|
||||
|
||||
// 16 bit parallel mode
|
||||
//Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
|
||||
if(k%2){
|
||||
p->data[(i+k)-1] = v;
|
||||
} else {
|
||||
p->data[(i+k)+1] = v;
|
||||
} // end reordering
|
||||
|
||||
} // end for matrixwidth
|
||||
|
||||
i += matrixWidth;
|
||||
|
||||
} // end pixels per latch loop (64)
|
||||
|
||||
} // color depth loop (8)
|
||||
|
||||
// printf("Processing row %d \n", y) ;
|
||||
//delay(50);
|
||||
|
||||
|
||||
} // half matrix height (16)
|
||||
|
||||
|
||||
//Show our work!
|
||||
i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
|
||||
backbuf_id^=1;
|
||||
//Bitplanes are updated, new image shows now.
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS); //animation has an 100ms interval
|
||||
|
||||
if (true) {
|
||||
//show next frame of Nyancat animation
|
||||
apos++;
|
||||
if (apos>=12) apos=0;
|
||||
} else {
|
||||
//show Lena
|
||||
apos=12;
|
||||
}
|
||||
} // end while(1)
|
||||
} // end loop
|
284
esp32_i2s_parallel.c
Normal file
284
esp32_i2s_parallel.c
Normal file
|
@ -0,0 +1,284 @@
|
|||
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if defined(ESP32)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "soc/i2s_struct.h"
|
||||
#include "soc/i2s_reg.h"
|
||||
#include "driver/periph_ctrl.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "rom/lldesc.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp32_i2s_parallel.h"
|
||||
|
||||
typedef struct {
|
||||
volatile lldesc_t *dmadesc_a, *dmadesc_b;
|
||||
int desccount_a, desccount_b;
|
||||
} i2s_parallel_state_t;
|
||||
|
||||
static i2s_parallel_state_t *i2s_state[2]={NULL, NULL};
|
||||
|
||||
callback shiftCompleteCallback;
|
||||
|
||||
void setShiftCompleteCallback(callback f) {
|
||||
shiftCompleteCallback = f;
|
||||
}
|
||||
|
||||
volatile bool previousBufferFree = true;
|
||||
|
||||
static int i2snum(i2s_dev_t *dev) {
|
||||
return (dev==&I2S0)?0:1;
|
||||
}
|
||||
|
||||
// Todo: handle IS20? (this is hard coded for I2S1 only)
|
||||
static void IRAM_ATTR i2s_isr(void* arg) {
|
||||
REG_WRITE(I2S_INT_CLR_REG(1), (REG_READ(I2S_INT_RAW_REG(1)) & 0xffffffc0) | 0x3f);
|
||||
|
||||
// at this point, the previously active buffer is free, go ahead and write to it
|
||||
previousBufferFree = true;
|
||||
|
||||
if(shiftCompleteCallback)
|
||||
shiftCompleteCallback();
|
||||
}
|
||||
|
||||
#define DMA_MAX (4096-4)
|
||||
|
||||
//Calculate the amount of dma descs needed for a buffer desc
|
||||
static int calc_needed_dma_descs_for(i2s_parallel_buffer_desc_t *desc) {
|
||||
int ret=0;
|
||||
for (int i=0; desc[i].memory!=NULL; i++) {
|
||||
ret+=(desc[i].size+DMA_MAX-1)/DMA_MAX;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fill_dma_desc(volatile lldesc_t *dmadesc, i2s_parallel_buffer_desc_t *bufdesc) {
|
||||
int n=0;
|
||||
for (int i=0; bufdesc[i].memory!=NULL; i++) {
|
||||
int len=bufdesc[i].size;
|
||||
uint8_t *data=(uint8_t*)bufdesc[i].memory;
|
||||
while(len) {
|
||||
int dmalen=len;
|
||||
if (dmalen>DMA_MAX) dmalen=DMA_MAX;
|
||||
dmadesc[n].size=dmalen;
|
||||
dmadesc[n].length=dmalen;
|
||||
dmadesc[n].buf=data;
|
||||
dmadesc[n].eof=0;
|
||||
dmadesc[n].sosf=0;
|
||||
dmadesc[n].owner=1;
|
||||
dmadesc[n].qe.stqe_next=(lldesc_t*)&dmadesc[n+1];
|
||||
dmadesc[n].offset=0;
|
||||
len-=dmalen;
|
||||
data+=dmalen;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
// set EOF bit in last dma descriptor
|
||||
dmadesc[n-1].eof=1;
|
||||
// link end of list back to beginning so current frame will be refreshed continously
|
||||
dmadesc[n-1].qe.stqe_next=(lldesc_t*)&dmadesc[0];
|
||||
|
||||
printf("fill_dma_desc: filled %d descriptors\n", n);
|
||||
}
|
||||
|
||||
// size must be less than DMA_MAX - need to handle breaking long transfer into two descriptors before call
|
||||
void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size) {
|
||||
if(size > DMA_MAX) size = DMA_MAX;
|
||||
|
||||
dmadesc->size = size;
|
||||
dmadesc->length = size;
|
||||
dmadesc->buf = memory;
|
||||
dmadesc->eof = 0;
|
||||
dmadesc->sosf = 0;
|
||||
dmadesc->owner = 1;
|
||||
dmadesc->qe.stqe_next = 0; // will need to set this elsewhere
|
||||
dmadesc->offset = 0;
|
||||
|
||||
// link previous to current
|
||||
if(prevdmadesc)
|
||||
prevdmadesc->qe.stqe_next = (lldesc_t*)dmadesc;
|
||||
}
|
||||
|
||||
static void gpio_setup_out(int gpio, int sig) {
|
||||
if (gpio==-1) return;
|
||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
|
||||
gpio_set_direction(gpio, GPIO_MODE_DEF_OUTPUT);
|
||||
gpio_matrix_out(gpio, sig, false, false);
|
||||
}
|
||||
|
||||
|
||||
static void dma_reset(i2s_dev_t *dev) {
|
||||
dev->lc_conf.in_rst=1; dev->lc_conf.in_rst=0;
|
||||
dev->lc_conf.out_rst=1; dev->lc_conf.out_rst=0;
|
||||
}
|
||||
|
||||
static void fifo_reset(i2s_dev_t *dev) {
|
||||
dev->conf.rx_fifo_reset=1; dev->conf.rx_fifo_reset=0;
|
||||
dev->conf.tx_fifo_reset=1; dev->conf.tx_fifo_reset=0;
|
||||
}
|
||||
|
||||
void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config_t *cfg) {
|
||||
//Figure out which signal numbers to use for routing
|
||||
printf("Setting up parallel I2S bus at I2S%d\n", i2snum(dev));
|
||||
int sig_data_base, sig_clk;
|
||||
if (dev==&I2S0) {
|
||||
sig_data_base=I2S0O_DATA_OUT0_IDX;
|
||||
sig_clk=I2S0O_WS_OUT_IDX;
|
||||
} else {
|
||||
if (cfg->bits==I2S_PARALLEL_BITS_32) {
|
||||
sig_data_base=I2S1O_DATA_OUT0_IDX;
|
||||
} else if (cfg->bits==I2S_PARALLEL_BITS_16) {
|
||||
//Because of... reasons... the 16-bit values for i2s1 appear on d8...d23
|
||||
printf("Setting up i2s parallel mode in 16 bit mode!");
|
||||
sig_data_base=I2S1O_DATA_OUT8_IDX;
|
||||
} else { // I2S_PARALLEL_BITS_8
|
||||
printf("Setting up i2s parallel mode in 8 bit mode -> https://www.esp32.com/viewtopic.php?f=17&t=3188 | https://www.esp32.com/viewtopic.php?f=13&t=3256");
|
||||
sig_data_base=I2S1O_DATA_OUT0_IDX;
|
||||
}
|
||||
sig_clk=I2S1O_WS_OUT_IDX;
|
||||
}
|
||||
|
||||
//Route the signals
|
||||
for (int x=0; x<cfg->bits; x++) {
|
||||
gpio_setup_out(cfg->gpio_bus[x], sig_data_base+x);
|
||||
}
|
||||
//ToDo: Clk/WS may need inversion?
|
||||
gpio_setup_out(cfg->gpio_clk, sig_clk);
|
||||
|
||||
//Power on dev
|
||||
if (dev==&I2S0) {
|
||||
periph_module_enable(PERIPH_I2S0_MODULE);
|
||||
} else {
|
||||
periph_module_enable(PERIPH_I2S1_MODULE);
|
||||
}
|
||||
//Initialize I2S dev
|
||||
dev->conf.rx_reset=1; dev->conf.rx_reset=0;
|
||||
dev->conf.tx_reset=1; dev->conf.tx_reset=0;
|
||||
dma_reset(dev);
|
||||
fifo_reset(dev);
|
||||
|
||||
//Enable LCD mode
|
||||
dev->conf2.val=0;
|
||||
dev->conf2.lcd_en=1;
|
||||
|
||||
// Enable "One datum will be written twice in LCD mode" - for some reason, if we don't do this in 8-bit mode, data is updated on half-clocks not clocks
|
||||
if(cfg->bits == I2S_PARALLEL_BITS_8)
|
||||
dev->conf2.lcd_tx_wrx2_en=1;
|
||||
|
||||
dev->sample_rate_conf.val=0;
|
||||
dev->sample_rate_conf.rx_bits_mod=cfg->bits;
|
||||
dev->sample_rate_conf.tx_bits_mod=cfg->bits;
|
||||
dev->sample_rate_conf.rx_bck_div_num=4; //ToDo: Unsure about what this does...
|
||||
|
||||
// because conf2.lcd_tx_wrx2_en is set for 8-bit mode, the clock speed is doubled, drop it in half here
|
||||
if(cfg->bits == I2S_PARALLEL_BITS_8)
|
||||
dev->sample_rate_conf.tx_bck_div_num=2;
|
||||
else
|
||||
dev->sample_rate_conf.tx_bck_div_num=1; // datasheet says this must be 2 or greater (but 1 seems to work)
|
||||
|
||||
dev->clkm_conf.val=0;
|
||||
dev->clkm_conf.clka_en=0;
|
||||
dev->clkm_conf.clkm_div_a=63;
|
||||
dev->clkm_conf.clkm_div_b=63;
|
||||
//We ignore the possibility for fractional division here, clkspeed_hz must round up for a fractional clock speed, must result in >= 2
|
||||
dev->clkm_conf.clkm_div_num=80000000L/(cfg->clkspeed_hz + 1);
|
||||
|
||||
dev->fifo_conf.val=0;
|
||||
dev->fifo_conf.rx_fifo_mod_force_en=1;
|
||||
dev->fifo_conf.tx_fifo_mod_force_en=1;
|
||||
//dev->fifo_conf.tx_fifo_mod=1;
|
||||
dev->fifo_conf.tx_fifo_mod=1;
|
||||
dev->fifo_conf.rx_data_num=32; //Thresholds.
|
||||
dev->fifo_conf.tx_data_num=32;
|
||||
dev->fifo_conf.dscr_en=1;
|
||||
|
||||
dev->conf1.val=0;
|
||||
dev->conf1.tx_stop_en=0;
|
||||
dev->conf1.tx_pcm_bypass=1;
|
||||
|
||||
dev->conf_chan.val=0;
|
||||
dev->conf_chan.tx_chan_mod=1;
|
||||
dev->conf_chan.rx_chan_mod=1;
|
||||
|
||||
//Invert ws to be active-low... ToDo: make this configurable
|
||||
//dev->conf.tx_right_first=1;
|
||||
dev->conf.tx_right_first=0;
|
||||
//dev->conf.rx_right_first=1;
|
||||
dev->conf.rx_right_first=0;
|
||||
|
||||
dev->timing.val=0;
|
||||
|
||||
//Allocate DMA descriptors
|
||||
i2s_state[i2snum(dev)]=malloc(sizeof(i2s_parallel_state_t));
|
||||
assert(i2s_state[i2snum(dev)] != NULL);
|
||||
i2s_parallel_state_t *st=i2s_state[i2snum(dev)];
|
||||
|
||||
st->desccount_a = cfg->desccount_a;
|
||||
st->desccount_b = cfg->desccount_b;
|
||||
st->dmadesc_a = cfg->lldesc_a;
|
||||
st->dmadesc_b = cfg->lldesc_b;
|
||||
|
||||
//Reset FIFO/DMA -> needed? Doesn't dma_reset/fifo_reset do this?
|
||||
dev->lc_conf.in_rst=1; dev->lc_conf.out_rst=1; dev->lc_conf.ahbm_rst=1; dev->lc_conf.ahbm_fifo_rst=1;
|
||||
dev->lc_conf.in_rst=0; dev->lc_conf.out_rst=0; dev->lc_conf.ahbm_rst=0; dev->lc_conf.ahbm_fifo_rst=0;
|
||||
dev->conf.tx_reset=1; dev->conf.tx_fifo_reset=1; dev->conf.rx_fifo_reset=1;
|
||||
dev->conf.tx_reset=0; dev->conf.tx_fifo_reset=0; dev->conf.rx_fifo_reset=0;
|
||||
|
||||
// setup I2S Interrupt
|
||||
SET_PERI_REG_BITS(I2S_INT_ENA_REG(1), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S);
|
||||
// allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete
|
||||
esp_intr_alloc(ETS_I2S1_INTR_SOURCE, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL);
|
||||
|
||||
//Start dma on front buffer
|
||||
dev->lc_conf.val=I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN | I2S_OUT_DATA_BURST_EN;
|
||||
dev->out_link.addr=((uint32_t)(&st->dmadesc_a[0]));
|
||||
dev->out_link.start=1;
|
||||
dev->conf.tx_start=1;
|
||||
}
|
||||
|
||||
//Flip to a buffer: 0 for bufa, 1 for bufb
|
||||
void i2s_parallel_flip_to_buffer(i2s_dev_t *dev, int bufid) {
|
||||
int no=i2snum(dev);
|
||||
if (i2s_state[no]==NULL) return;
|
||||
lldesc_t *active_dma_chain;
|
||||
if (bufid==0) {
|
||||
active_dma_chain=(lldesc_t*)&i2s_state[no]->dmadesc_a[0];
|
||||
} else {
|
||||
active_dma_chain=(lldesc_t*)&i2s_state[no]->dmadesc_b[0];
|
||||
}
|
||||
|
||||
// setup linked list to refresh from new buffer (continuously) when the end of the current list has been reached
|
||||
i2s_state[no]->dmadesc_a[i2s_state[no]->desccount_a-1].qe.stqe_next=active_dma_chain;
|
||||
i2s_state[no]->dmadesc_b[i2s_state[no]->desccount_b-1].qe.stqe_next=active_dma_chain;
|
||||
|
||||
// we're still refreshing the previously buffer, so it shouldn't be written to yet
|
||||
previousBufferFree = false;
|
||||
}
|
||||
|
||||
bool i2s_parallel_is_previous_buffer_free() {
|
||||
return previousBufferFree;
|
||||
}
|
||||
|
||||
#endif
|
54
esp32_i2s_parallel.h
Normal file
54
esp32_i2s_parallel.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
#ifndef I2S_PARALLEL_H
|
||||
#define I2S_PARALLEL_H
|
||||
|
||||
#if defined(ESP32)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "soc/i2s_struct.h"
|
||||
#include "rom/lldesc.h"
|
||||
|
||||
typedef enum {
|
||||
I2S_PARALLEL_BITS_8=8,
|
||||
I2S_PARALLEL_BITS_16=16,
|
||||
I2S_PARALLEL_BITS_32=32,
|
||||
} i2s_parallel_cfg_bits_t;
|
||||
|
||||
typedef struct {
|
||||
void *memory;
|
||||
size_t size;
|
||||
} i2s_parallel_buffer_desc_t;
|
||||
|
||||
typedef struct {
|
||||
int gpio_bus[24];
|
||||
int gpio_clk;
|
||||
int clkspeed_hz;
|
||||
i2s_parallel_cfg_bits_t bits;
|
||||
i2s_parallel_buffer_desc_t *bufa;
|
||||
i2s_parallel_buffer_desc_t *bufb;
|
||||
int desccount_a;
|
||||
int desccount_b;
|
||||
lldesc_t * lldesc_a;
|
||||
lldesc_t * lldesc_b;
|
||||
} i2s_parallel_config_t;
|
||||
|
||||
void i2s_parallel_setup(i2s_dev_t *dev, const i2s_parallel_config_t *cfg);
|
||||
void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config_t *cfg);
|
||||
void i2s_parallel_flip_to_buffer(i2s_dev_t *dev, int bufid);
|
||||
bool i2s_parallel_is_previous_buffer_free();
|
||||
void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size);
|
||||
|
||||
typedef void (*callback)(void);
|
||||
void setShiftCompleteCallback(callback f);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue