Code:
/****************************************************
Collect camera data into two different sets of
buffers and set up PXP rotations then print PXP_Next
data for cut and paste into another program
M Borgerson 12/5/2020
******************************************************** */
#include <OV7670.h>
#include <ILI9341_t3n.h>
// need to include PXP definitions if not using the
// latest imxrt.h from GitHub (as of 12/4/2020).
#ifndef PXP_CTRL_SET
#include "PXP_Defs.h"
#endif
//Specify the pins used for Non-SPI functions of display
#define TFT_CS 10 // AD_B0_02
#define TFT_DC 9 // AD_B0_03
#define TFT_RST 8
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST);
#define OUTBUFIDX 3
#define PSBUFIDX 12
const uint16_t bwidth = 320;
const uint16_t bheight = 240;
// PXP_Next structis 32 register setings, but we just save as an array
uint32_t PXP_Next_0[32];
// define memory buffers in different locations for speed testing
uint16_t srcdma[320l * 240l]__attribute__ ((aligned (64))) DMAMEM;
uint16_t dstdma[320l * 240l]__attribute__ ((aligned (64))) DMAMEM;
uint16_t srcext[320l * 240l]__attribute__ ((aligned (64))) EXTMEM;
uint16_t dstext[320l * 240l]__attribute__ ((aligned (64))) EXTMEM;
// CSI frame buffer 2 isn't used by PXP rotations
uint16_t fb2[320l * 240l];
uint16_t *srcptr = (uint16_t *)&srcdma;
uint16_t *dstptr = (uint16_t *)&dstdma;
const char compileTime [] = " Compiled on " __DATE__ " " __TIME__;
const int pinCamReset = 14;
void setup() {
Serial.begin(9600);
delay(200);
Wire.begin();
pinMode(pinCamReset, OUTPUT);
digitalWriteFast(pinCamReset, LOW);
delay(10);
digitalWriteFast(pinCamReset, HIGH); // subsequent resets via SCB
if (OV7670.begin(QVGA, (uint8_t *)srcptr, (uint8_t *)&fb2)) {
Serial.println("OV7670 camera initialized.");
} else {
Serial.println("Error initializing OV7670");
}
// 12 MHz gives 15FPS. 16MHz will do 20FPS, but leaves little time
// for anything but video display.
OV7670.SetCamClock(12);
// Start ILI9341
tft.begin();
tft.setRotation(0); // testing external rotation
CMSI();
Serial.println("Initializing PXP");
delay(50);
PXP_Init(srcptr, dstptr);
delay(10);
Serial.println("Ready");
}
void loop() {
// Only 3 choices: 's' System Info 'f' capture single frame 't' run rotate tests
char ch;
if (Serial.available()) {
ch = Serial.read();
if (ch == 's') CMSI();
if (ch == 'f') CMGF();
if (ch == 't') TestSpeeds();
}
}
elapsedMicros rutime;
void CMSI(void) {
Serial.printf("\n\nOV7670 Camera and ILI9341 QVGA Test 3 %s\n", compileTime);
OV7670.ShowCamConfig();
}
void ShowPXP(void) {
Serial.printf("PXP_OUT_BUF:%08X \n", PXP_OUT_BUF);
Serial.printf("PXP_PS_BUF:%08X \n", PXP_PS_BUF);
}
// save the PXP registers to the PXP_Next array
void SavePXPNext(uint32_t pxnptr[]) {
uint16_t i;
volatile uint32_t *pxptr = &PXP_CTRL; // set first address
uint32_t *nxptr = &pxnptr[0];
for (i = 0; i < 29; i++) { // first 29 are at 16-byte intervals
*nxptr++ = *pxptr;
pxptr += 4; // skips ahead 16 bytes at input
}
// the last three entries are oddly spaced
*nxptr++ = PXP_POWER;
*nxptr++ = PXP_NEXT;
*nxptr = PXP_PORTER_DUFF_CTRL;
}
// print out a PXP_Next array in a format that can be pasted into
// source code to get the same PXP behavior
void PrintPXPNext(const char *arrayname, uint32_t pxnptr[]) {
uint16_t i;
Serial.printf("\nuint32_t %s[32] = {", arrayname);
for (i = 0; i < 31; i++) {
if ((i % 6) == 0) Serial.println();
Serial.printf("0x%08X, ", *pxnptr++);
}
// last one can't have a comma and needs bracket amd semicolon
Serial.printf("0x%08X };", *pxnptr);
Serial.println();
}
bool PXP_Done(void) {
return PXP_STAT & PXP_STAT_IRQ;
}
// updated with PXP definitions from new imxrt.h
// and using constants bwidth = 320, bheight = 240 for QVGA
void PXP_Init(uint16_t *inbuff, uint16_t *outbuff) {
// turn on the PXP Clock
CCM_CCGR2 |= CCM_CCGR2_PXP(CCM_CCGR_ON);
PXP_CTRL_SET = PXP_CTRL_SFTRST; //Reset the PXP
PXP_CTRL_CLR = PXP_CTRL_SFTRST | PXP_CTRL_CLKGATE; //Clear reset and gate
delay(10);
PXP_CTRL_SET = PXP_CTRL_ROTATE(3) | PXP_CTRL_BLOCK_SIZE; // Set Rotation 3 block size 16x16
PXP_CSC1_COEF0 |= PXP_COEF0_BYPASS;
PXP_OUT_CTRL_SET = 0x0E; // specify RGB565 output
PXP_OUT_BUF = (volatile void *)outbuff;
PXP_OUT_PITCH = bheight * 2; // output is 240 pixels by 2 bytes after rotation
PXP_OUT_LRC = 0;
PXP_OUT_LRC = ((bwidth) << 16) | (bheight);
PXP_OUT_AS_ULC = 0xFFFFFFFF; // not using the alpha surface
PXP_OUT_AS_LRC = 0;
PXP_OUT_PS_ULC = 0; // start processing at upper left 0,0
PXP_OUT_PS_LRC = ((bwidth) << 16) | (bheight); // same as output
PXP_PS_CTRL_SET = 0x0E; // PS buffer format is RGB565
PXP_PS_BUF = (volatile void *)inbuff;
PXP_PS_UBUF = 0; // not using YUV
PXP_PS_VBUF = 0; // not using YUV
PXP_PS_PITCH = 640; // input is 320 pixels by 2 bytes wide before rotation
PXP_PS_SCALE = 0x10001000; // 1:1 scaling (0x1.000)
PXP_PS_CLRKEYLOW_0 = 0xFFFFFF; // this disables color keying
PXP_PS_CLRKEYHIGH_0 = 0x0; // this disables color keying
PXP_CTRL_SET = PXP_CTRL_IRQ_ENABLE;
// we don't actually use the interrupt but need to enable the bits
// in the PXP_STAT register
}
void PXP_Rotate(void) {
uint32_t etime;
SavePXPNext((uint32_t*)&PXP_Next_0);
PXP_STAT_CLR = PXP_STAT; // clears all flags
PXP_CTRL_SET = PXP_CTRL_ENABLE; // start the PXP
rutime = 0;
// wait until rotation finished
while (!PXP_Done()) {};
PXP_CTRL_CLR = PXP_CTRL_ENABLE; // stop the PXP
etime = rutime;
Serial.printf("PXP Rotation took %lu microseconds\n", etime);
}
// Capture, rotate and display a single frame from OV7670
void CMGF(void) {
uint16_t readyframe;
uint32_t imagesize;
imagesize = OV7670.ImageSize();
OV7670.begin(QVGA, (uint8_t *)PXP_PS_BUF, (uint8_t *)&fb2);
OV7670.ClearFrameReady();
do {
readyframe = OV7670.FrameReady();
} while (readyframe != 1 ); // wait until frame 1 just completed
if ((uint32_t)PXP_PS_BUF > 0x2020000) { // makes camera dma data visible
arm_dcache_delete((void *)PXP_PS_BUF, imagesize);
}
if ((uint32_t)PXP_OUT_BUF > 0x2020000) { // needed when doing DMA into memory
arm_dcache_delete((void *)PXP_OUT_BUF, imagesize);
}
PXP_Rotate();
Serial.printf("Output buffer at %p\n", PXP_OUT_BUF);
if ((uint32_t)PXP_OUT_BUF > 0x2020000) {
arm_dcache_flush((void *)PXP_OUT_BUF, imagesize); // needed when doing DMA out of memory
}
tft.writeRect(0, 0, tft.width(), tft.height(), (uint16_t *)PXP_OUT_BUF);
}
void TestFrame(uint16_t *psrc, uint16_t *pdst) {
// set up frame 1 to store in psrc, frame 2 to fb2
uint32_t imagesize;
imagesize = OV7670.ImageSize();
PXP_PS_BUF = (void *)psrc;
PXP_OUT_BUF = (void *)pdst; // set the PXP OUT buffer pointer
if ((uint32_t)psrc > 0x20200000) arm_dcache_flush((void*)psrc, imagesize);
if ((uint32_t)pdst > 0x20200000) arm_dcache_flush((void*)pdst, imagesize);
CMGF();
}
// try various combinations of source and destination memory
// to compare the rotation speeds
void TestSpeeds(void) {
Serial.println("\nDMAMEM to DMAMEM");
TestFrame((uint16_t *)&srcdma, (uint16_t *)&dstdma);
PrintPXPNext("Rot_DMA_DMA ", (uint32_t*)&PXP_Next_0);
Serial.println("\nDMAMEM to EXTMEM");
TestFrame((uint16_t *)&srcdma, (uint16_t *)&dstext);
PrintPXPNext("Rot_DMA_EXT ", (uint32_t*)&PXP_Next_0);
Serial.println("\nEXTMEM to DMAMEM");
TestFrame((uint16_t *)&srcext, (uint16_t *)&dstdma);
PrintPXPNext("Rot_EXT_DMA ", (uint32_t*)&PXP_Next_0);
Serial.println("\nEXTMEM to EXTMEM");
TestFrame((uint16_t *)&srcext, (uint16_t *)&dstext);
PrintPXPNext("Rot_EXT_EXT ", (uint32_t*)&PXP_Next_0);
}
The second program allows you to use the same PXP setup without going through setup code or modifying any registers to switch between two setups. The program does require that you specify the input and output buffers for the rotation--in case the new program should have the arrays in different places than the original program.
Code:
/****************************************************
Collect camera data and rotate using restored PXP
setups copied from output of SavePXP program
m. borgerson 12/5/2020
******************************************************** */
#include <OV7670.h>
#include <ILI9341_t3n.h>
// need to include PXP definitions if not using the
// latest imxrt.h from GitHub (as of 12/4/2020).
#ifndef PXP_CTRL_SET
#include "PXP_Defs.h"
#endif
//Specify the pins used display for Non-SPI functions
#define TFT_CS 10 // AD_B0_02
#define TFT_DC 9 // AD_B0_03
#define TFT_RST 8
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST);
// we are using QVGA settings for camera
const uint16_t bwidth = 320;
const uint16_t bheight = 240;
// define memory buffers in different locations for speed testing
uint16_t srcdma[320l * 240l]__attribute__ ((aligned (64))) DMAMEM;
uint16_t dstdma[320l * 240l]__attribute__ ((aligned (64))) DMAMEM;
uint16_t srcext[320l * 240l]__attribute__ ((aligned (64))) EXTMEM;
uint16_t dstext[320l * 240l]__attribute__ ((aligned (64))) EXTMEM;
#define OUTBUFIDX 3
#define PSBUFIDX 12
// PXP_Next struct is 32 register setings, but we just save as an array
uint32_t Rot_EXT_EXT [32] = {
0x00800302, 0x00000000, 0x0000000E, 0x70000000, 0x00000000, 0x000001E0,
0x014000F0, 0x00000000, 0x014000F0, 0x3FFF3FFF, 0x00000000, 0x0000000E,
0x70025800, 0x00000000, 0x00000000, 0x00000280, 0x00000000, 0x10001000,
0x00000000, 0x00FFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00FFFFFF, 0x00000000, 0x44000000, 0x01230208, 0x079B076C, 0x00000000,
0x00000000, 0x00000000
};
uint32_t Rot_DMA_DMA [32] = {
0x00800302, 0x00000000, 0x0000000E, 0x20200000, 0x00000000, 0x000001E0,
0x014000F0, 0x00000000, 0x014000F0, 0x3FFF3FFF, 0x00000000, 0x0000000E,
0x20225800, 0x00000000, 0x00000000, 0x00000280, 0x00000000, 0x10001000,
0x00000000, 0x00FFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00FFFFFF, 0x00000000, 0x44000000, 0x01230208, 0x079B076C, 0x00000000,
0x00000000, 0x00000000
};
#define OUTBUFIDX 3
#define PSBUFIDX 12
// CSI frame buffer 2 isn't used by PXP rotations
uint16_t fb2[320l * 240l];
const char compileTime [] = " Compiled on " __DATE__ " " __TIME__;
const int pinCamReset = 14;
void setup() {
uint8_t *srcptr = (uint8_t *)Rot_DMA_DMA [PSBUFIDX];
Serial.begin(9600);
delay(200);
Wire.begin();
pinMode(pinCamReset, OUTPUT);
digitalWriteFast(pinCamReset, LOW);
delay(10);
digitalWriteFast(pinCamReset, HIGH); // subsequent resets via SCB
if (OV7670.begin(QVGA, (uint8_t *)srcptr, (uint8_t *)&fb2)) {
Serial.println("OV7670 camera initialized.");
} else {
Serial.println("Error initializing OV7670");
}
// 12 MHz gives 15FPS. 16MHz will do 20FPS, but leaves little time
// for anything but video display.
OV7670.SetCamClock(12);
// Start ILI9341
tft.begin();
tft.setRotation(0); // testing external rotation
CMSI();
Serial.println("Adjusting PXP buffer addresses.");
delay(10);
SetNextBuffers(Rot_EXT_EXT, srcext, dstext);
SetNextBuffers(Rot_DMA_DMA, srcdma, dstdma);
Serial.println("Initializing PXP");
delay(50);
PXP_Start((uint32_t)&Rot_DMA_DMA);
delay(10);
Serial.println("Ready");
}
void loop() {
// Only 3 choices: 's' System Info 'f' capture single frame 't' run rotate tests
char ch;
if (Serial.available()) {
ch = Serial.read();
if (ch == 's') CMSI();
if (ch == 'f') CMGF();
if (ch == 't') TestSpeeds();
}
}
// adjust the source and destination buffers in the saved PXP settings to match
// the variables declared in this program
void SetNextBuffers(uint32_t pxpnxt[], uint16_t src[], uint16_t dst[]) {
pxpnxt[PSBUFIDX] = (uint32_t)src;
pxpnxt[OUTBUFIDX] = (uint32_t)dst;
}
void CMSI(void) {
Serial.printf("\n\nOV7670 Camera and ILI9341 QVGA Test 3 %s\n", compileTime);
OV7670.ShowCamConfig();
}
// Capture, rotate and display a single frame from OV7670
void CMGF(void) {
uint16_t readyframe;
uint32_t imagesize;
imagesize = OV7670.ImageSize();
OV7670.begin(QVGA, (uint8_t *)PXP_PS_BUF, (uint8_t *)&fb2);
OV7670.ClearFrameReady();
do {
readyframe = OV7670.FrameReady();
} while (readyframe != 1 ); // wait until frame 1 just completed
if ((uint32_t)PXP_PS_BUF > 0x2020000) { // makes camera dma data visible
arm_dcache_delete((void *)PXP_PS_BUF, imagesize);
}
if ((uint32_t)PXP_OUT_BUF > 0x2020000) { // needed when doing DMA into memory
arm_dcache_delete((void *)PXP_OUT_BUF, imagesize);
}
PXP_Rotate();
Serial.printf("Output buffer at %p\n", PXP_OUT_BUF);
if ((uint32_t)PXP_OUT_BUF > 0x2020000) {
arm_dcache_flush((void *)PXP_OUT_BUF, imagesize); // needed when doing DMA out of memory
}
tft.writeRect(0, 0, tft.width(), tft.height(), (uint16_t *)PXP_OUT_BUF);
}
// Use two different PXP_NEXT settings to compare the rotation speeds
void TestSpeeds(void) {
Serial.println("\nDMAMEM to DMAMEM");
PXP_NEXT = (uint32_t)&Rot_DMA_DMA;
PXP_CTRL_CLR = PXP_CTRL_ENABLE; // stop automatic execution on PXP_NEXT write
CMGF();// get, rotate and display a frame
delay(1000);
Serial.println("\nEXTMEM to EXTMEM");
PXP_NEXT = (uint32_t)&Rot_EXT_EXT;
PXP_CTRL_CLR = PXP_CTRL_ENABLE; // stop automatic execution on PXP_NEXT write
CMGF(); // get, rotate and display a frame
}
bool PXP_Done(void) {
return PXP_STAT & PXP_STAT_IRQ;
}
// Restart PXP with settings from a PXP_Next array
void PXP_Start(uint32_t pxnptr) {
// turn on clock to PXP
CCM_CCGR2 |= CCM_CCGR2_PXP(CCM_CCGR_ON);
PXP_CTRL_SET = PXP_CTRL_SFTRST; //Reset
PXP_CTRL_CLR = PXP_CTRL_SFTRST | PXP_CTRL_CLKGATE; //Clear reset and gate
delay(10);
// storing pointer in PXP_NEXT causes PXP to restore settings
PXP_NEXT = pxnptr;
}
elapsedMicros rutime;
void PXP_Rotate(void) {
uint32_t etime;
PXP_STAT_CLR = PXP_STAT; // clears all flags
PXP_CTRL_SET = PXP_CTRL_ENABLE; // start the PXP
rutime = 0; // reset the timing counter
// wait until rotation finished
while (!PXP_Done()) { };
PXP_CTRL_CLR = PXP_CTRL_ENABLE; // stop the PXP
etime = rutime;
Serial.printf("PXP Rotation took %lu microseconds\n", etime);
}
The primary advantage of using the PXP_Next arrays for setup is that you can switch from one PXP setup to another with minimal code. These rotation example are excessively simple in that the PXP rotation can be bypassed by just setting the TFT display rotation to 3 instead of zero. In my case, this example code was a first step toward simplifying setup and restore for more complex operations like scaling and color space conversions.