New Camera Library for Teensy Micromod/4.1

mjs513

Senior Member+
@KurtE and I have been at it again. This time creating a library that supports a number Arducam/Arduino camera modules:
ModelFrameSizesPixel Formats
Omnivision OV2640 FRAMESIZE_VGA //640x480
FRAMESIZE_QQVGA, // 160x120
FRAMESIZE_QVGA, // 320x240
FRAMESIZE_QCIF, // 176x144
FRAMESIZE_CIF, // 352x288
FRAMESIZE_SVGA, //800, 600
FRAMESIZE_UXGA, //1500, 1200
YUV422,
RGB565
BAYER
GRAYSCALE
JPEG
Omnivision OV7670
FRAMESIZE_VGA //640x480
FRAMESIZE_QQVGA, // 160x120
FRAMESIZE_QVGA, // 320x240
FRAMESIZE_QCIF, // 176x144
FRAMESIZE_CIF, // 352x288
YUV422,
RGB565
BAYER
GRAYSCALE
Omnivision OV7675 FRAMESIZE_VGA //640x480
FRAMESIZE_QQVGA, // 160x120
FRAMESIZE_QVGA, // 320x240
FRAMESIZE_QCIF, // 176x144
FRAMESIZE_CIF, // 352x288
YUV422,
RGB565
BAYER
GRAYSCALE
GalaxyCore GC2145 {640, 480}, /* VGA */
{160, 120}, /* QQVGA */
{320, 240}, /* QVGA */
{480, 320}, /* ILI9488 */
{320, 320}, /* 320x320 */
{320, 240}, /* QVGA */
{176, 144}, /* QCIF */
{352, 288}, /* CIF */
{800, 600}, /* SVGA */
{1600, 1200}, /* UXGA */
YUV422,
RGB565
BAYER
GRAYSCALE
Himax HM0B01 FRAMESIZE_320X320
FRAMESIZE_QVGA
FRAMESIZE_QQVGA
FRAMESIZE_QVGA4BIT (same as QVGA)
Grayscale
Himax HM0360 FRAMESIZE_320X320
FRAMESIZE_QVGA
FRAMESIZE_QQVGA
FRAMESIZE_QVGA4BIT (same as QVGA)
Grayscale

Supported commands do vary from camera module to camera module:
OV2640OV767XGC2145HB01B0HM0360
bool begin_omnivision(framesize_t resolution, pixformat_t format, int fps, int camera_name, bool use_gpio);bool begin_omnivision(framesize_t resolution = FRAMESIZE_QVGA, pixformat_t format = RGB565, int fps = 30, int camera_name = OV7670, bool use_gpio = false); // Supported FPS: 1, 5, 10, 15, 30bool begin_omnivision(framesize_t resolution = FRAMESIZE_QVGA, pixformat_t format = RGB565, int fps = 30, int camera_name = OV7670, bool use_gpio = false);bool begin(framesize_t framesize = FRAMESIZE_QVGA, int framerate = 30, bool use_gpio = false);bool begin(framesize_t framesize = FRAMESIZE_QVGA, int framerate = 30, bool use_gpio = false);
void end();void end();void end();void end();
uint16_t getModelid();uint16_t getModelid();uint16_t getModelid();uint16_t getModelid();uint16_t getModelid();
int reset();int reset();int reset();int reset();
void showRegisters();void showRegisters();void showRegisters(void);void showRegisters(void);
int setPixformat(pixformat_t pixformat);int setPixelFormat(pixformat_t pixformat);int setPixformat( pixformat_t pfmt);int setPixformat(pixformat_t pfmt);
uint8_t setFramesize(framesize_t framesize);uint8_t setFramesize(framesize_t framesize);uint8_t setFramesize(framesize_t framesize);uint8_t setFramesize(framesize_t framesize);
uint8_t setFramesize(int w, int h);uint8_t setFramesize(int w, int h);
bool setZoomWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);bool setZoomWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
int setFramerate(int framerate);
int setHmirror(int enable);int setHmirror(int enable);int setHmirror(int enable);int setHmirror(int enable);int setHmirror(int enable);
int setVflip(int enable);int setVflip(int enable);int setVflip(int enable);int setVflip( int enable);int setVflip(int enable);
int setAutoExposure(int enable, int exposure_us);int setAutoExposure(int enable, int exposure_us);int setAutoExposure(int enable, int exposure_us);int setAutoExposure(int enable, int exposure_us);
int setBrightness(int level);int setBrightness(int brightness); // 0 - 255int setBrightness(int level);int setBrightness(int level);
void setSaturation( int level);void setSaturation(int saturation); // 0 - 255
void setContrast(int level);void setContrast(int contrast); // 0 - 127
void setHue(int hue); // -180 - 180
int setWBmode(int mode);
int setSpecialEffect(sde_t sde);
int setGainceiling(gainceiling_t gainceiling);int setGainceiling(gainceiling_t gainceiling);int setGainceiling(gainceiling_t gainceiling);
int setColorbar(int enable);void testPattern(int pattern = 2);int setColorbar(int enable);int setColorbar(int enable);int setColorbar(int enable);
void noTestPattern();
int setAutoGain( int enable, float gain_db, float gain_db_ceiling);void autoGain(int enable, float gain_db, float gain_db_ceiling);int setAutoGain(int enable, float gain_db, float gain_db_ceiling);int setAutoGain(int enable, float gain_db, float gain_db_ceiling);
int setAutoWhitebal(int enable, float r_gain_db, float g_gain_db, float b_gain_db);int setAutoWhitebal(int enable, float r_gain_db, float g_gain_db, float b_gain_db);
void setGain(int gain);void setGain(int gain); // 0 - 255
int getGain_db(float *gain_db);int getGain_db(float *gain_db);int getGain_db(float *gain_db);
int getRGB_Gain_db(float *r_gain_db, float *g_gain_db, float *b_gain_db) ;
void autoExposure(int enable);void autoExposure(int enable);
int getExposure_us(int *exposure_us);void setExposure(int exposure); // 0 - 65535int getExposure_us(int *exposure_us);int getExposure_us(int *exposure_us);
void showRegisters();void showRegisters();void showRegisters(void);void showRegisters(void);void showRegisters();
void printRegisters(bool only_ones_set = true);
int sleep(int enable);int setFramerate(int framerate);
int setQuality(int qs);int get_vt_pix_clk(uint32_t *vt_pix_clk);int get_vt_pix_clk(uint32_t *vt_pix_clk);
int getCameraClock(uint32_t *vt_pix_clk);int getCameraClock(uint32_t *vt_pix_clk);
uint8_t setMode(uint8_t Mode, uint8_t FrameCnt);uint8_t setMode(uint8_t Mode, uint8_t FrameCnt);
uint8_t cmdUpdate();uint8_t cmdUpdate();
uint8_t loadSettings(camera_reg_settings_t settings);uint8_t loadSettings(camera_reg_settings_t settings);
uint8_t getAE( ae_cfg_t *psAECfg);uint8_t getAE(ae_cfg_t *psAECfg);
uint8_t calAE( uint8_t CalFrames, uint8_t* Buffer, uint32_t ui32BufferLen, ae_cfg_t* pAECfg);

Image capture is similar to what was done with the Teensy HM01B0 library:
1. GPIO
2. FLEXIO2 which supports DMA transfer


Double buffers have been tested for Framesizes up to VGA. If using the OV2640 JPEG images can be displayed up to SVGA and UXGA can be saved to SDCARD. Some modules support Windowing function which allows a smaller portion of the image to be displayed. See table above.

Currently the library has only been tested with the Teensy Micromod board but we will be testing with the Teensy 4.1 which supports PSRAM modules.

If anyone is interested they can check the library: https://github.com/mjs513/TeensyMM_Camera

This is still a work in progress so be warned. We have tried documenting as much as we could and a few extensive examples are available. The library is inspired from the Arduino GIGA Camera library, the ESP32 Camera library and the OpenMV project.

This is a quick overview and sure @KurtE will add more info and will be posting additional info in future threads
 
To give you an idea ran the OV2640 camera using the Aducam at SVGA resolution with an antique special effect in jpeg format
Screenshot 2024-04-17 165228.png


Same JPEG image downsampled on a ILI9341:
unnamed.png
 
Thanks @mjs513,

As you mentioned we are having some fun with several of these cameras. I see you showed my quick and dirty (v2) version of a shield for the Sparkfun Micrmod ATP board. Sure is a lot easier than having to deal with lots of different jumper wires and the like. Sort of turned into a reasonably good test board for the Micromods. If anyone is interested, the files are up on github...

As you mentioned, there are several cameras now that Arducam and some others are producing that have the same pin out. So having some fun.
This page on Arducam: https://www.arducam.com/camera-for-arduino-giga/
Shows you a list of the cameras that they are producing for the Arduino GIGA and you can order them there. However, I purchased mine from Arduino USA store:

OV7675: https://store-usa.arduino.cc/products/arducam-camera-module

HM01b0: https://store-usa.arduino.cc/produc...-camera-module-for-arduino-giga-r1-wifi-board
As mentioned, they only hooked up 4 data lines on this board.

HM0360: https://store-usa.arduino.cc/produc...-camera-module-for-arduino-giga-r1-wifi-board

GC2145: https://store-usa.arduino.cc/produc...-camera-module-for-arduino-giga-r1-wifi-board

OV2640
But being glutens for punishment, we also found that Arducam, has/had another 2MP camera with the same pinout (OV2640), which we could not resist trying. Arducam does not list this one any more on their website, but I found them on some others:
I purchased one from Robotshop: I am guessing when the sell the last two the have as of now, they will be gone:

However Waveshare also sells aOV2640 with similar setup which Robotshop also sells:

I purchased another one from Amazon (https://www.amazon.com/dp/B0BXDMKDNC?psc=1&ref=ppx_yo2ov_dt_b_product_details) that does not have a MCLK pin on it and pinout is different. At times was able to make it work...
We been having too much fun with these cameras. Like the GC2145 which is also 2MB, you run into the issue that a quickly run out of memory
trying to read in images. Even with several of the others such as the OV7675(or OV7670) which supports VGA size of 640x480. None of these processors have enough memory on board (in a single region) that can hold a full image: 640*480*2 = 614400 which will not fit into DTCM nor into DMAMEM, so most of our readFrame methods, allow you to pass in two buffers, so you use some of both. The example sketch Camera_display_tft_vga is setup this way and allows you to read in a full VGA frame and display most of it on an ILI9488 display.

But this camera as well as the GC2145, also allows you to define a view port rectangle into the frame size you have selected, and the camera will only return that window of pixels. And you can then move around the origin to show different portions of what the camera is capturing.

This camera also has the ability to capture and compress the data returned in JPEG format. Mike created another example sketch: ov2640_display, that we have been having fun experimenting with this. The sketch has code in it, that allows you to capture the JPEG image and save it out to an SDCard. The sketch is also setup to use MTP. In addition to this, we have code that can receive the jpeg data, and then use the jpeg decoder library to then show the data on the TFT display... There are still lots of things to learn on different ways to do this. For example, how do you setup to do DMA, when you don't know the actual size of the data that will be returned. The data is terminated using a two byte special sequence...

What's next? About to experiment with OV5640 (5MP) https://www.adafruit.com/product/5838

More cleanup. We have been trying to refactor some of this code and remove redundant architecture code we added earlier, to do the flexio, dma, GPIO...

Setup to work on Teensy 4.1 with PSRAM. In most case it will probably need to be done using GPIO as there are not enough CSI pins that are brought out on any production boards. There are enough for 4-bit CSI, which only the two monochrome cameras support.

Try it again on DevBoard - Although I find myself resisting setting up all of those jumper wires. As I spend more time debugging the wiring, than the code. But it would be fun to try it again.

Probably enough for my first post on this thread!
 
None of these processors have enough memory on board (in a single region) that can hold a full image: 640*480*2 = 614400 which will not fit into DTCM nor into DMAMEM, so most of our readFrame methods, allow you to pass in two buffers, so you use some of both.
You can manually change the FlexRAM config (in the linker script) to assign some of the 512KB to OCRAM2, which exists in memory directly after regular OCRAM (DMAMEM)... effectively enlarging it.
 
Thanks Kurt - probably should add some of this to the repo on the examples :)

Anyway for those of you that don't know about the projects I mentioned earlier, they all use single class,
Code:
#include "Camera.h",
to call the different cameras and if a specific method isn't supported it will either return 0 of just not work. As an example in the Camera_display_tft.ino example the camera are selected by uncommenting a
Code:
#define
which

Code:
//#define ARDUCAM_CAMERA_HM01B0
#define ARDUCAM_CAMERA_HM0360
//#define ARDUCAM_CAMERA_OV2640
//#define ARDUCAM_CAMERA_OV7670
//#define ARDUCAM_CAMERA_OV7675
//#define ARDUCAM_CAMERA_GC2145

which will then include the appropriate camera:
Code:
#if defined(ARDUCAM_CAMERA_HM0360)
#include "TMM_HM0360/HM0360.h"
HM0360 himax;
Camera camera(himax);
#define CameraID 0x0360

#elif defined(ARDUCAM_CAMERA_HM01B0)
#include "TMM_HM01B0/HM01B0.h"
HM01B0 himax;
Camera camera(himax);
#define CameraID 0x01B0

#elif defined(ARDUCAM_CAMERA_OV2640)
#include "TMM_OV2640/OV2640.h"
OV2640 omni;
Camera camera(omni);
#define CameraID OV2640a

#elif defined(ARDUCAM_CAMERA_OV7670)
#include "TMM_OV767X/OV767X.h"
OV767X omni;
Camera camera(omni);
#define CameraID OV7670
#elif defined(ARDUCAM_CAMERA_OV7675)

#include "TMM_OV767X/OV767X.h"
OV767X omni;
Camera camera(omni);
#define CameraID OV7675

#elif defined(ARDUCAM_CAMERA_GC2145)
#include "TMM_GC2145/GC2145.h"
GC2145 galaxycore;
Camera camera(galaxycore);
#define CameraID GC2145a
#endif

then all that is needed for any of the sensors is to call a method using the camera constructor you selected:
Code:
camera.setHmirror(true);
  camera.setVflip(true);
 
Thought I would mention, I am currently playing with trying to add CSI support for this library, as the Teensy 4.1 has enough of the CSI pins exposed for 8-bit transfers, and the control signals.

I am starting off with the OV7675 camera, and I do have it now being able to display a QVGA image on an ILI9341 display. That occasionally actually looks OK.
1713891963775.png

Other times it has some issues like:

The code also has issue that each frame alternates between the cameras FB1 and FB2, and I only have the code
setup to use FB1...

It looks like I am not configuring something correctly in the camera. as if I mark when I call the capture function and when it returns, about half way through the hardware frame:
1713892233232.png

Bottom line is my marking, 3rd line is the vertical frame, the line above are the HREF and there are about 480 of them in the frame...

Not sure if I can do the trick I was doing for flexio where I could chain two memory locations into the DMA chain to read a larger image (like VGA) into normal memory. With this board I do have PSRAM, so will see how well it works.

Not sure how to handle JPEG with CSI. Then again on FlexIO we do it by avoiding DMA. Probably could do similar with CSI.

Want to rip out a lot of the setup/startup code in several of these camera and when possible, move to base class, probably with callouts to places where the camera classes need to do camera specific things. and/or they register some init table...

Other than that, not much left to do 😆
 
While @KurtE has been busy with CSI we also been playing with the OV5640 5MB camera which we got from Adrafruit: https://www.adafruit.com/product/5673 for the most part it was compatible with Flexio that we used for the OV2640 but of course lib had to change for common functions such as: contrast, brightness, hue, gain functions, exposure, auto white balance, mirror and flip for instance. Also it has different camera specific functions:


C++:
    /**
     * Sets OV5640 Image Special Effects.
     *
     * Input: Enumerated
     * 0. NOEFFECT.
     * 1. NEGATIVE.
     * 2. BW.
     * 3. REDDISH.
     * 4. GREENISH.
     * 5. BLUEISH.
     * 6. RETRO.
     * 7. OVEREXPOSURE (5640 only).
     * 8. SOLARIZE (5640 only).
     * RETURNS:  Non-zero if it fails.
     */
    int setSpecialEffect(sde_t sde);

    /**
     * Sets Whitebalance mode for OV5640 camera Only.
     *
     * INPUT: integer.
     *   0 - Auto white balance.
     *   1 - Sunny.
     *   2 - Cloudy.
     *   3 - Office.
     *   4 - Home.
     *
     * RETURNS:   Non-zero if it fails.
     */
    int setWBmode(int mode);
    int setAutoBlc(int enable, int *regs);
    int getBlcRegs(int *regs);
    int setLensCorrection(int enable);
    int setNightMode(int enable);
    int setSharpness(int level);
    int setAutoSharpness(int enable);

For the OV2640 I have been using the arducam windows app to test some of these, however, decided to create a new example that you can adust these from command line for the OV5640. Here is a list of command line options for the OV5640 example.

Rich (BB code):
************ Camera Settings **************/
Send the 'C[-3 to +3]' To set Contrast
Send the 'S[-4 to +4]' To set Saturation
Send the 'B[-4 to +4]' To set Brightness
Send the 'E[0-9]' To set Special Effects
Send the 'W[0-4]' To set Light Mode
Send the 'H[-6 to +5]' To set Hue
Send the 'N[on/Off]' To turn on Nightmode
Send the 'A[on/Off]' To turn on AutoSharpness
Send the 'P[0-9]' To set Sharpness
Send the 'L[on/Off]' To turn on LensCorrection
Send the 'R[QVSU]' To set image size QVGA, VGA, SVGA UXGA
************ Single Frame **************/
Send the 'f' character to read a frame using FlexIO (changes hardware setup!)
Send the 'F' to start/stop continuous using FlexIO (changes hardware setup!)
Send the 'n' character to read a frame using FlexIO without DMA (changes hardware setup!)
Send the 'x' send snapshot (JPEG) to display
************ Multi Frame **************/
Send the 'm' character to read and display multiple frames
Send the 'M' character to read and display multiple frames use Frame buffer
Send the 'V' character DMA to TFT async continueous  ...
************ Save Frames **************/
Send the 'b' character to save snapshot (BMP) to SD Card
Send the 'j' character to save snapshot (JPEG) to SD Card
Send the 'z' character to send current screen BMP to SD
************ INFO **************/
Send the 't' character to send Check the display
Send the 'd' character to toggle camera debug on and off
Send the 'r' character to show the current camera registers

Cheers
 
Would love to show this on the website. Any chance for a photo showing the hardware connection with MicroMod and with Teensy 4.1?
 
Note: no promises on these boards, as I am a retired software guy.... I was laying out a complete board as a modified version of the earlier board I did for the Micromod. But decided to do a quick and dirty one that I can easily solder up. So this one simply plugs into a Sparkfun Micromod ATP
Although I have screwed up soldering of one of the SDCard adapters... If I were to do another slight rev, probably would add pinout for Adafruit SDCard adapter. It has connections for the Camera, SDCard, ILI9341/9488 display and second one for some of the ADAFruit displays....
Thie shield area on it, I think I reserved all of the pins that are used by the audio adapter, although I have tried one with it yet.

1713924013397.png

If anyone is interested, the DipTrace Schematic and layout as well as a zip file with the gerbers is up on github.
(dch, dip ) files same directory.


And it is working pretty well. A lot less error prone than the jumper wiring version from my other ATP board:
1713927167761.png
 
As for the Teensy 4.1, I am using a board, I made maybe 4 years ago, and the one I am using is not fully populated. Don't remember if I assembled any others of that batch and if so what parts worked or did not work...

1713960189752.png

The board is more or less same size as the 2.8" ILI9341 display,

As I mentioned this one was only assembled enough to play with the display and the camera. And all of the IO pins. The T4.1 I marked with red tape to remind me, this is one that is locked and it has PSRAM.
1713961309291.png


Note: At the time I was using the OV7670 camera and the ones I had the camera was rotated 90 degrees from the display, So I did a quick
and dirty adapter, which allowed me to rotate the camera 90 degrees, and also allowed me to choose to mount the camera pointing the either the front or the back of the board.


Again the schematic and layouts are up on github: https://github.com/KurtE/Teensy3.1-Breakout-Boards/blob/master/Teensy 3.6 Touch display LoRa/Teensy 4.1 Touch Display LoRa OV7670.dch

Not sure if I uploaded the gerber files, Easy to generate again if desired.
I ordered a new set from PCBWay, where I mostly removed all of the stuff I was not using.
1713979327399.png

Note: can be built like this, or with tft on other side... Shown with 3.5" ILI9488 in the 3d...

Now I should get back to debugging the CSI code.
 
Last edited:
Quick updates on CSI code:

I now have more of it working with T4.1 and CSI running in VGA size, with PSRAM for frame buffer.
1714082407065.png

I have more of it working now. With the VGA sketch, using OV7675 camera PSRAM, where f command is working, as is m and F...

Next up try to setup with one of the higher resolution cameras like OV2640...
 
Wondering has anyone tried using the CSI subsystem without using their built in DMA?

Two different ways, I might find it interesting:

a) Reading in JPEG. Yesterday I was sort of able to get it to maybe read in one JPEG using DMA, I think I have that part broken a bit right now
as was trying to add code to recover. But what happens with the CSI and internal DMA, on normal read of frame:
1714225880626.png


Top line now shows when ISR is called. Which has current status like: CSII:8e334000
Which includes information, like the Frame Buffer 1 or FB2 DMA was completed. So can pass the
data back to sketch. It knows the frame has completed as I asked for 480x320 2 byte pixels... And it transferred
that many...

Now if I try to do a Jpeg image: The capture data looks different:
1714226405920.png


Note the 3rd line (HREF) looks less dense and it is and fewer of them. There is a lot less data recovered, as expected. I believe that
some code bases we have played with, expect at most about 1/5th as many pixels... With this sub-system (or DMA in general) how to tell it to end the capture when you see the following two bytes... However I do get an error interrupt as you see on the top line where the status is
different: CSII:94014000
The 0x10000000 bit says, there was an error where the system sees a new Start of Frame, while it is still expected the end of the previous frame.
Which I had the code then exit the CSI capture. However AFAIK there is no way to ask the CSI for how much data did it put into some DMA buffer and also if there is any way for you to get it flush out any pending data it has to the currently active transfer...

So for this, I might try CSI without having it do DMA in the JPEG case, in the same way as we do with the FLexIO and avoid the DMA...

b) DMA with scatter/gather - currently I am trying these larger images on a T4.1 with PSRAM which is working. But may want to
try without PSRAM, and again not enough memory within either DTCM memory nor DMAMEM for a complete VGA image, but
is enough if you can split it. (Maybe there are tricks one can do with the memory space, but...)

I don't see any way to do chain up DMA Settings using the built-in CSI dma controller, but wonder if you can use the normal
DMA controller with and setup your own DMA Channel?

I do see that that DMAMux Channel 12 is for CSI. So wondering...

Anyway was just curious, if anyone has played with CSI without DMA...
 

Attachments

  • 1714226198370.png
    1714226198370.png
    15 KB · Views: 3
I haven't done it with CSI, I used FlexIO instead with a shifter register in match store mode to detect the ending sequence.
It sounds like the same situation as with LCDIF: the reference doc says you can perform the DMA "manually" but gives no details about how/when the DMA signal is triggered. But at least with CSI you have a source address to read from (RXFIFO)...

The documentation does state this about the FIFO watermark:
RxFIFO Full Level. When the number of data in RxFIFO reaches this level, a RxFIFO full interrupt is
generated, or an RXFIFO DMA request is sent.
But it isn't clear if that DMA request is internal to CSI, or refers to the eDMA request line. So you could try that... disable the CSI internal DMA, set up an eDMA channel to transfer the watermark amount of bytes from CSI_RXFIFO whenever it is triggered. Would also need to set FCC in CSI_CR1 to 0 (asynchronous FIFO clear), and check DRDY in CSI_SR when a start-of-frame interrupt occurs to see if there's any leftover data in the RXFIFO (below the watermark) when a new frame begins, then manually trigger DMA to finish the last frame if so.
If it doesn't trigger the external DMA channel, the only way I see it being do-able is using the RXFIFO watermark interrupt to trigger the DMA manually which means a load of CPU interrupts happening during each frame.

Overall I'd just stick with FlexIO for compressed images... it's a lot less data being transferred so doesn't need the speed of CSI and a lot easier to handle the variable length.
 
Thanks @jmarsh and @mjs513

Quick note on the main CSI pins, (AD_B1_xx) pins, that are used on Teensy 4.1. They are also FlexIO pins but on FlexIO3, which does not have DMA, so we have not tried using it... But could especially, if we do it without DMA anyway...

Was first experimenting today wondering what the CSI was doing in this error case. Again it is unclear how much of the buffer the CSI had written into, so first thing I tried was to enable the interrupt on the RXFIFO full interrupt, So the ISR code was called a lot more times, but by the time the ISR was called and I grabbed the status register, the state was cleared by the DMA code... So I simply counted how many ISR calls...

On normal RGB565 read of the image 480x320 (2 bytes per pixel), The ISR was called 4801 times (2 of them are known SOF end FB completed)
Assume that SOF did not transfer any data: There were 64 bytes transferred for each one...

When I tried it with the jpeg request. It failed as mentioned.
By the time I received the Error state, I had something like 311 ISR calls. In the handling code, I allowed it some time to maybe get a few more ISR calls, to maybe let it flush out the rest of the data that might be queued...

I then put in some quick and dirty debug code to see what I received:
C++:
  if (bytes_read == 0) {
    if (debug_on) {
      Serial.printf("Error: No bytes returned from camera\n");
      MemoryHexDump(Serial, frameBuffer, 256, true, "FB1\n", -1, 0);
      MemoryHexDump(Serial, frameBuffer2, 256, true, "FB2\n", -1, 0);

      // Curious to see if it maybe did retrieve the data...
      uint8_t *pfb = (uint8_t *)frameBuffer;
      if ((pfb[0] == 0xff) && (pfb[1] == 0xd8) && (pfb[2] == 0xff)) {
        // looks like first buffer has start of a JPEG...
        Serial.println("Data in buffer appears to be start with JPEG data");
        for (int i = 3; i < (sizeof_framebuffer - 1); i++) {
          if ((pfb[i] == 0xff) && (pfb[i + 1] == 0xd9)) {
            Serial.println("Data in buffer appears to be JPEG data");
            bytes_read = i + 2;
            break;
          }
        }
      } else {
        pfb = (uint8_t *)frameBuffer2;
        if ((pfb[0] == 0xff) && (pfb[1] == 0xd8) && (pfb[2] == 0xff)) {
          Serial.println("Data in buffer2 appears to be start with JPEG data");
          // looks like first buffer has start of a JPEG...
          for (int i = 3; i < (sizeof_framebuffer2 - 1); i++) {
            if ((pfb[i] == 0xff) && (pfb[i + 1] == 0xd9)) {
              bytes_read = i + 2;
              // hack it wants it in the first fuffer...
              Serial.println("Data in buffer2 appears to be JPEG data");
              memcpy(frameBuffer, frameBuffer2, bytes_read);
              break;
            }
          }
        }
      }
      // If we found start marker but not end marker, lets look at what is at the end of both frames.
      if ((bytes_read == 0) && (pfb[0] == 0xff) && (pfb[1] == 0xd8) && (pfb[2] == 0xff)) {
        pfb = (uint8_t *)frameBuffer;
        int i;
        for (i = sizeof_framebuffer - 1; i > 128; i--) {
          if (pfb[i] != 0) break;
        }
        if(i > 128) {
          i &= 0xfffffff0;
          MemoryHexDump(Serial, &pfb[i], 128, true, "End FB1\n", -1, (uint32_t)i);
        }

        pfb = (uint8_t *)frameBuffer2;
        for (i = sizeof_framebuffer2 - 1; i > 128; i--) {
          if (pfb[i] != 0) break;
        }
        if(i > 128) {
          i &= 0xfffffff0;
          MemoryHexDump(Serial, &pfb[i], 128, true, "End FB2\n", -1, (uint32_t)i);
        }
      }
    }

    if (bytes_read == 0) return false;
  }
The date returned:
Code:
$$Count of RxFifoFull ISRs:311
$$ImageSensor::readFrameCSI exited with Frame Error
    CSI_CSICR1: 401e0912
    CSI_CSICR2: c0000000
    CSI_CSICR3: 41020
    CSI_CSISTATFIFO: 7f598045
    CSI_CSIRFIFO: 101
    CSI_CSIRXCNT: 9600
    CSI_CSISR: 80004001
    CSI_CSIDMASA_STATFIFO: 0
    CSI_CSIDMATS_STATFIFO: 0
    CSI_CSIDMASA_FB1: 700ea600
    CSI_CSIDMASA_FB2: 70000000
    CSI_CSIFBUF_PARA: 0
    CSI_CSIIMAG_PARA: 50001e0
    CSI_CSICR18: 8002d210
    CSI_CSICR19: 10
    CSI_CSIRFIFO: 78777675
Bytes returned from camera: 0
Error: No bytes returned from camera
FB1
00000000 - 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  : ........ ........
...     8 duplicate line(s) removed.
00000090 - 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  : ........ ........
000000A0 - 00 1F 00 00 01 05 01 01  01 01 01 01 00 00 00 00  : ........ ........
000000B0 - 00 00 00 00 01 02 03 04  05 06 07 08 09 0A 0B FF  : ........ ........
000000C0 - C4 00 B5 10 00 02 01 03  03 02 04 03 05 05 04 04  : ........ ........
000000D0 - 00 00 01 7D 01 02 03 00  04 11 05 12 21 31 41 06  : ...}.... ....!1A.
000000E0 - 13 51 61 07 22 71 14 32  81 91 A1 08 23 42 B1 C1  : .Qa."q.2 ....#B..
000000F0 - 15 52 D1 F0 24 33 62 72  82 09 0A 16 17 18 19 1A  : .R..$3br ........
FB2
00000000 - FF D8 FF E0 00 10 4A 46  49 46 00 01 01 01 00 00  : ......JF IF......
00000010 - 00 00 00 00 FF DB 00 43  00 0C 08 09 0B 09 08 0C  : .......C ........
00000020 - 0B 0A 0B 0E 0D 0C 0E 12  1E 14 12 11 11 12 25 1A  : ........ ......%.
00000030 - 1C 16 1E 2C 26 2E 2D 2B  26 2A 29 30 36 45 3B 30  : ...,&.-+ &*)06E;0
00000040 - 33 41 34 29 2A 3C 52 3D  41 47 4A 4D 4E 4D 2F 3A  : 3A4)*<R= AGJMNM/:
00000050 - 55 5B 54 4B 5A 45 4C 4D  4A FF DB 00 43 01 0D 0E  : U[TKZELM J...C...
00000060 - 0E 12 10 12 23 14 14 23  4A 32 2A 32 4A 4A 4A 4A  : ....#..# J2*2JJJJ
00000070 - 4A 4A 4A 4A 4A 4A 4A 4A  4A 4A 4A 4A 4A 4A 4A 4A  : JJJJJJJJ JJJJJJJJ
00000080 - 4A 4A 4A 4A 4A 4A 4A 4A  4A 4A 4A 4A 4A 4A 4A 4A  : JJJJJJJJ JJJJJJJJ
00000090 - 4A 4A 4A 4A 4A 4A 4A 4A  4A 4A 4A 4A 4A 4A FF C4  : JJJJJJJJ JJJJJJ..
000000A0 - 00 1F 00 00 01 05 01 01  01 01 01 01 00 00 00 00  : ........ ........
000000B0 - 00 00 00 00 01 02 03 04  05 06 07 08 09 0A 0B FF  : ........ ........
000000C0 - C4 00 B5 10 00 02 01 03  03 02 04 03 05 05 04 04  : ........ ........
000000D0 - 00 00 01 7D 01 02 03 00  04 11 05 12 21 31 41 06  : ...}.... ....!1A.
000000E0 - 13 51 61 07 22 71 14 32  81 91 A1 08 23 42 B1 C1  : .Qa."q.2 ....#B..
000000F0 - 15 52 D1 F0 24 33 62 72  82 09 0A 16 17 18 19 1A  : .R..$3br ........
Data in buffer2 appears to be start with JPEG data
End FB1
00009730 - D0 8E EE 39 3B D2 33 68  FF D9 00 00 00 00 00 00  : ...9;.3h ........
00009740 - 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  : ........ ........
...     5 duplicate line(s) removed.
000097A0 - 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  : ........ ........
End FB2
0007CF50 - 8A 3A 49 32 29 32 29 32  69 3A 49 3A 88 3A 06 2A  : .:I2)2)2 i:I:.:.*
0007CF60 - 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  : ........ ........
...     5 duplicate line(s) removed.
0007CFC0 - 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  : ........ ........
Sort of interesting that after it hit the SOF on the 2nd frame, it then stored the data into the first buffer,
which included the end of jpeg data.. But it also started with a bunch of 0's ...
So hard to say how to combine the two...

Now to see about turning off the DMA and doing it myself...
 
There is always a chance that an MJPG frame could end up larger than an uncompressed frame, if the quality is turned up and the frame has lots of detail that can't be compressed (things like ocean waves and grassy lawns are typical causes).
 
Quick update on using the CSI setup to try to read in Jpeg images in from OV2640 and later OV5640 on Teensy 4.1.

I tried several different ways to make it work within CSI subsystem. I was tried several different ways to turn off using their DMA and interrupts. I would believe that you should be able to check the status register and when it says that the DRTY (data ready) bit is set, that you could read the out the stuff from the FIFO using the CSI_CSIRFIFO register, but at most I could get it to read out two words that kept repeating... None of the different ways worked.

Side note: I did some different searches on the web for example tyring to find if anyone has had any success using the CSI_CSIRFIFO register... But about the only place I found it mentioned was in a thread I was doing maybe 4 years ago and the only reference to it was me printing out the contents of that register as part of the debug code.

So right now I punted. When we say go into the non-DMA, I switch the pins into FlexIO3 mode as all of these CSI pins are also on FlexIO3. And then I configure up FlexIO3. There were a few hickups, like the CSI pins map opposite direction: That is CSID2 -> FlexIO3:F and CSID9->Flexio3:8
So needed to pass in the D9 pins (8) into the FlexIO setup... It then was returning the bits in the wrong direction,
So used the special shift buffer register: SHIFTBUFBBS
Which reverses the bits within a byte.
1714437436025.png
The sketch was able to read the JPG from the camera, save it to the SD Card, then opened it up on my PC using MTP...

Not the greatest picture, but it worked. Still lots to clean up. So far I have only added CSI to OV767x and OV2640... Just glad I have some of it working
 
Question/Wondering: Currently with our JPEG input code, we are using some pretty primitive code to detect the
end of the JPEG stream (0xff followed by 0xd9). That is we are working without DMA, and manually scan the new data
coming in for this, like:
C++:
        *p++ = _pflexio->SHIFTBUFBBS[_fshifter];
        if ((_format == 8) && (frame_bytes_received > 0)) {
            // jpeg check for
            for (int i = 0; i < 4; i++) {
                if ((pu8[i - 1] == 0xff) && (pu8[i] == 0xd9)) {
                    if (_debug)
                        Serial.printf("JPEG - found end marker at %u\n",
                                      frame_bytes_received + i);
                    DBGdigitalWriteFast(DBG_TIMING_PIN, LOW);
                    return frame_bytes_received + i + 1;
                }
            }
        }
We cannot depend on both bytes being read in the same position in the SHIFTBUF nor that both bytes will be in the same one,
(i.e. could be 0xff as last byte of one and the 0xd9 in the first byte of the next)...

So far this has worked. But wondering if anyone has tried using the FLEXIO compare code/modes to do this automatically?
Example code?

I am guessing it would take a second shifter. Probably has to be either #3 or #7 (like the main) one in order to read in the same
8 input pins? Probably fed by the same clock? Then can it keep just the last two bytes and do compares...

If this might work, could maybe do while in DMA mode and get interrupt on match, kill the DMA capture for that frame...

Just wondering...
 
@KurtE
Been playing with your latest changes and testing on with the 2640 and 5640.

With the 2640 jpeg seems to be problematic maybe will get a good image 1 in 10 times or so. With the Adafrut 5640 seems to be working like a charm - even able to change resolution with the sketch and still get the a good jpeg image:
Image_20240430_142326.png
 
So far this has worked. But wondering if anyone has tried using the FLEXIO compare code/modes to do this automatically?
Example code?

I am guessing it would take a second shifter. Probably has to be either #3 or #7 (like the main) one in order to read in the same
8 input pins? Probably fed by the same clock? Then can it keep just the last two bytes and do compares...
You don't need to use one of the parallel shifters, just use the one below your receive shifter (so likely either shifter 6 or 2) and configure it for match continuous mode, the same timer as the other shifter, PWIDTH=15 (16 bits) and INSRC=1. So every time the receive shifter gets a new byte, the match shifter will grab the top 2 bytes from it and compare them to the value set in its SHIFTBUF register.
When you initialize the compare value in the matcher shiftbuf don't forget to bitswap each byte to account for the backwards wiring...

You might have a problem resetting the status for the match shifter when it's used to trigger an interrupt. If you run into that (shifter status stays active after a match and keeps re-triggering the FlexIO interrupt) use the shifter error flag instead as it also triggers on a match and can be manually cleared.
 
You don't need to use one of the parallel shifters, just use the one below your receive shifter (so likely either shifter 6 or 2) and configure it for match continuous mode, the same timer as the other shifter, PWIDTH=15 (16 bits) and INSRC=1. So every time the receive shifter gets a new byte, the match shifter will grab the top 2 bytes from it and compare them to the value set in its SHIFTBUF register.
When you initialize the compare value in the matcher shiftbuf don't forget to bitswap each byte to account for the backwards wiring...
First again thanks for the input.

So far I am not having much luck. probably something stupid I am doing and even more likely probably in some of the things I don't understand properly.

When I setup the initial FlexIO, the main shifter is configured like:
Code:
TIMCTL: 18400003 PINSEL: 0 THSYNC: c
 FLEXIO:1 Shifter:3 Timer:0
     SHIFTCFG =  00070000
     SHIFTCTL =  00000401
     TIMCMP = 00000007
     TIMCFG = 01206600
     TIMCTL = 18400003

When I setup to try to capture the JPEG images, I setup the matching Shifter... Current settings are:
FLEXIO: JPEG Shifter:2
Code:
SHIFTCFG =  000F0100
     SHIFTCTL =  00000005
     SHIFTBUF =  D9FF0000
frame size: 614400, cb1:448000 cnt dma: 4 CB per: 112000
frame size left: 166400, cb2:230400 cnt dma: 2 CB per: 83200
 CH: 20045650 400e9020: SA:401b020c SO:0 AT:202 (SM:0 SS:2 DM:0 DS:2) NB:4 SL:0 DA:20200020 DO: 4 CI:6d60 DL:200093c0 CS:10 BI:6d60
 0: 20009360 20009380: SA:401b020c SO:0 AT:202 (SM:0 SS:2 DM:0 DS:2) NB:4 SL:0 DA:20200020 DO: 4 CI:6d60 DL:200093c0 CS:10 BI:6d60
 1: 200093a0 200093c0: SA:401b020c SO:0 AT:202 (SM:0 SS:2 DM:0 DS:2) NB:4 SL:0 DA:2021b5a0 DO: 4 CI:6d60 DL:20009400 CS:10 BI:6d60
 2: 200093e0 20009400: SA:401b020c SO:0 AT:202 (SM:0 SS:2 DM:0 DS:2) NB:4 SL:0 DA:20236b20 DO: 4 CI:6d60 DL:20009440 CS:10 BI:6d60
 3: 20009420 20009440: SA:401b020c SO:0 AT:202 (SM:0 SS:2 DM:0 DS:2) NB:4 SL:0 DA:202520a0 DO: 4 CI:6d60 DL:20009480 CS:12 BI:6d60
 4: 20009460 20009480: SA:401b020c SO:0 AT:202 (SM:0 SS:2 DM:0 DS:2) NB:4 SL:0 DA:2000c620 DO: 4 CI:5140 DL:200094c0 CS:10 BI:5140
 5: 200094a0 200094c0: SA:401b020c SO:0 AT:202 (SM:0 SS:2 DM:0 DS:2) NB:4 SL:0 DA:20020b20 DO: 4 CI:5140 DL:20009380 CS:1a BI:5140
Flexio DMA: length: 614400

* continuous mode started
I setup for a callback on the shifter error. Note: in this case the pins are in the right order so don't need the bit swap.

So far callback has not happened. Also not sure if SHIFTBUF = D9FF0000
or should be FFD9 I tried both ways.

Next thing I will do is double check the settings again.

But my main confusion is how these shifters work together.

With the main shifter here (3), we are setup to receive 8 bits at a time from IO pins, and when we receive 4 bytes and the shifter is full, the shifter contents are shifted out in this case through DMA...

First side question to self and... When the DMA or me reading the SHIFTBUF directly clears the SHIFTSTAT bit. But does it clear the SHIFTBUF?
or does it simply shift the bits out as new bits come in... That is if we had received the bytes: 01 02 03 04 and the shiftbuf is:
0x04030201 we read it and then bytes 05 06 come in and we read SHIFTBUF is it 06050403 or are the bottom two nibbles 0?
Guessing the 6 5 4 3...

Then with the matching Shifter, where you are wanting to match 16 bits, when does it actually receive data from the main shifter? On each byte or on each time it has received 16 bits... And does it matter if the two bytes you are looking for align on even or odd bytes?

Still need to experiment more... Maybe I will try changing to just checking for one byte and see if at least that works...

Sorry just sometimes hard to read between the lines of the RM...
 
Starting to have some success with the match :D
I changed:
_pflexio->SHIFTCFG[_fshifter_jpeg] = FLEXIO_SHIFTCFG_PWIDTH(31) | FLEXIO_SHIFTCFG_INSRC;

To PMOD to transfer 32 bits instead of 16 and things started to work.
1715105160131.png

Although it is not catching it on every frame. the little blips on the bottom line are where I do digital writes within the callback
 
First side question to self and... When the DMA or me reading the SHIFTBUF directly clears the SHIFTSTAT bit. But does it clear the SHIFTBUF?
or does it simply shift the bits out as new bits come in... That is if we had received the bytes: 01 02 03 04 and the shiftbuf is:
0x04030201 we read it and then bytes 05 06 come in and we read SHIFTBUF is it 06050403 or are the bottom two nibbles 0?
Guessing the 6 5 4 3...
The way I understand it, the SHIFTBUF register isn't where the actual work gets done. Each shifter has an internal state where the shifting happens, and it's only when the timer "store" event occurs that the internal state gets copied to/from SHIFTBUF.
Then with the matching Shifter, where you are wanting to match 16 bits, when does it actually receive data from the main shifter? On each byte or on each time it has received 16 bits... And does it matter if the two bytes you are looking for align on even or odd bytes?
The "internal" matching shifter register should be grabbing 16 bits from the receiving register, each time a full byte finishes being received.
If the receiving register contains: 01020304
The matching register would be: XXXX0102
Then the receiving register receives another byte, 05: 02030405
The matching register now contains: 01020203
And when the next byte arrives, the match register will be: 02030304
So alignment isn't an issue... since it's updating after each input byte, it will always attempt a match of every pair of bytes.

(I'm trying to think now, why I did it like this instead of just shifting in 8-bits at a time since it would achieve the same thing... I'm sure there was a reason but I can't remember what it was.)
 
Starting to have some success with the match :D
I changed:
_pflexio->SHIFTCFG[_fshifter_jpeg] = FLEXIO_SHIFTCFG_PWIDTH(31) | FLEXIO_SHIFTCFG_INSRC;

To PMOD to transfer 32 bits instead of 16 and things started to work.

Although it is not catching it on every frame. the little blips on the bottom line are where I do digital writes within the callback
Maybe the ENABLE signal deasserts as soon as the ending sequence is sent, so the timer stops and the final values never get moved into the matching shifter (since its contents are one "tick" behind the receive register's contents) ? Try using TIMDIS=3, to ensure the final bytes are compared when the ENABLE pin goes low. You may get up to four bytes extra at the tail of the output but it should all stay in sync.

(This assumes the camera keeps the pixel clock output running when ENABLE is low...)
 
Thanks,

Have not had much time today to play... Too nice of a day to be inside.

Right now, I am trying to figure out what to do, now that I can detect the JPG end. Mostly mumbling to myself 😆

That is, in continuous capture mode, in normal RGB565 mode we setup the DMA operation to receive Width * Height * 2
bytes of data from the FlexIO (4 bytes at a time), and we only receive bytes clocked in by the pixelclk when the HREF is high...
When we receive that number of bytes, the DMA is stopped, we process the DMA completion interrupt, which calls back to the client code to let them know they have a new frame. We also start up another pin interrupt, which waits for the next VSYNC signal to startup DMA again. In many cases this may not be 100% needed, but it helps recover when things get out of sync.

Now with JPG input, we do not know the length of the data. Suppose we are reading in VGA format; 640*480*2= 614400
Which as mentioned before is hard to fit in any 512K region Lets simplify it slightly and assume I setup a window on these
cameras for 480*32*2= 307200 Which does fit, but not very many of them... Now if we assume that the JPEG will be maybe 1/5th the
size we can fit more of them... Yes there are other bad cases... Sorry sort of a digression... But lets assume I start up the DMA operation
with a buffer of 128K.. or...

First question to self, is suppose the compare shifter finds the end marker at about 100K. How do I cleanly stop the DMA operation?
Could potentially do a disable on the DMA channel? But what about the data that is currently in the shift register that part of it matched.
How do I make sure that that data is output into the output buffer? One thought was when I get the callback for the match, I could enable the
interrupt for the main shifter... And after it receives the next data, then disable it? Secondary question if I have both Interrupts and does DMA on the shifter which happens first? That is if I stop the DMA. will that last write complete. If not should it wait for a second interrupt to cancel...

Or is there a way to tell the DMA, that once you do the next transfer stop and interrupt. Like setting the counter to 1?

Setting up for the next frame. Currently I have both buffers setup in the DMA chain so I simply re enable the DMA to continue once I see the next VSYNC... But guessing above the DMA state may not necessarily know the state. So probably need to re update the DMAChannel to the the appropriate setting.

Then the fun will be once I receive one of these, what to do in the user sketch. My guess is that updating the display from the JPEG data won't be fast enough to be done before the next frame completes... So probably instead simply copy the last buffer to another buffer to be displayed...

Sorry, just thinking out laud....
 
Quick update, I am now actually capturing some of the JPG images using DMA :D
More or less along the line I mentioned. A few wrong turns here and there but the
MATCH shifter finds the match, and it's callback enables the main shifters Interrupt for data store...
I let it interrupt twice and then I disable DMA and do the callbacks to the sketch...

The one sketch then sees these callbacks, knows I am in JPEG mode and then calls decoder. It ignores the new images that come in, until it finishes processing the JPG and the updateScreenAsync completes. Probably will teak some of this later, but especially with the 5840 it does pretty well
some of the time...

Pushed up current stuff up will play more later. Today is nice day outside so need to do some yard work!
 
Back
Top