Ok so i finished a testing sketch which more or less implements how i thought it up initially.
Instead of blocking the main thread using the Filename Queue i decided it was easier to let the Error Queue respond. It isn't the prettiest yet, and some things still need to be modified.
#include "FS.h"
#include "SD.h"
#define HSPI_SCK 17
#define HSPI_MISO 16
#define HSPI_MOSI 4
#define SD_CS 15 // Added 10K pullup for v2 (strapping pin must be HIGH)
#define LED_WHITE 14
#define SPI_FRQ 32000000
#define SERIAL_OUTPUT_BAUD 500000
#define PIXEL_BYTES (680 * 3)
#define PORTS 4
#define FRAMES (40 * 60 * 2) // 40 Hz, 60 Seconds, 2 Minutes
#define FILENAME_LENGTH 30
#define BLOCK_SIZE 512
#define SD_QUEUE_SIZE 32 // maybe only 8
#define SD_ERR_QUEUE_SIZE 16
#define NO_ERROR 0
#define UNEXP_SDQ 1
#define UNEXP_SDQ_EMPTY 2
#define UNEXP_FILENAME_EMPTY 3
#define UNEXP_FILENAME_NULL 4
#define FILE_NOT_CREATED 5
#define NAME_RECEIVED 6
#define EMPTY_QUEUE 7
#define WRITE_ERROR 8
#define HEADER_WRITTEN 9
#define WRITE_STOP 10
#define NAME_NOT_EXP 11
#define Q_NOT_EMPTY 12
#define STACK_SIZE 13
#define WAIT_FOR_NAME 0
#define WAIT_FOR_DATA 1
#define STACK_CHECK //{uint32_t stack = uxTaskGetStackHighWaterMark(NULL); if (stack < maxStack) maxStack = stack;}
typedef struct {
uint16_t len;
uint8_t block[BLOCK_SIZE];
} SdBlock;
QueueHandle_t SdQ;
QueueHandle_t SdFileName;
QueueHandle_t SdError;
SPIClass * hspi = NULL;
uint8_t stripixels[PORTS][PIXEL_BYTES];
uint16_t lengths[PORTS] = {PIXEL_BYTES, PIXEL_BYTES, PIXEL_BYTES, PIXEL_BYTES};
uint16_t totalLengths = 0;
void setup() {
Serial.begin(500000);
delay(1000);
Serial.println();
Serial.println();
Serial.println("SD Task on core 0 test");
pinMode(LED_WHITE, OUTPUT);
SdQ = xQueueCreate(SD_QUEUE_SIZE, sizeof(SdBlock));
SdFileName = xQueueCreate(1, FILENAME_LENGTH + 1); // filename Queue should be only 1 so the main thread can be blocked
uint8_t err = 0;
SdError = xQueueCreate(SD_ERR_QUEUE_SIZE, sizeof(err)); // error codes are only 1 byte
if (SdQ == NULL) {
Serial.println(F("Error creating the SD queue"));
}
if (SdFileName == NULL) {
Serial.println(F("Error creating the finename queue"));
}
if (SdError == NULL) {
Serial.println(F("Error creating the SD Error queue"));
}
if (InitSdCard()) {
Serial.println(F("SD card initialized"));
}
xTaskCreatePinnedToCore(
HandleQueueToSD,
"Task Write To SD", // A name just for humans
3000, // The stack size can be checked by calling `uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);`
NULL, // No parameter is used
2, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
NULL, // Task handle is not used here
0 // pinned to core
);
uint8_t b = 0;
for (uint8_t i = 0; i < PORTS; i++) {
for (uint16_t j = 0; j < PIXEL_BYTES; j++) {
stripixels[i][j] = b;
b++;
}
totalLengths += lengths[i];
}
Serial.println(F("Arrays Filled."));
Serial.print(F("Total Lengths : "));
Serial.println(totalLengths, DEC);
delay(1000);
if (CreateSdFileToTask("TestxTaskSd_2")) {
Serial.println(F("Created SD Ok."));
uint32_t frames = FRAMES;
uint32_t lastFrame = millis();
int pinstate = HIGH;
while (frames) {
digitalWrite(LED_WHITE, pinstate);
pinstate = HIGH - pinstate;
frames--;
while (millis() - lastFrame < 25) { // blocking time limiter
delayMicroseconds(10); // stability
}
lastFrame = millis();
for (uint8_t i = 0; i < PORTS; i++) {
if (!SendDataBlockToSdQueue(stripixels[i], lengths[i])) {
frames = 0;
}
}
} // frames
if (StopSdFileToTask()) {
Serial.println(F("Filename Cleared, Writing Stopped"));
/*uint8_t err = 0;
int ret = xQueueReceive(SdError, &err, portMAX_DELAY);
if (err == STACK_SIZE) {
uint32_t maxStack = 0;
for (uint8_t i = 0; i < 4; i++) {
ret = xQueueReceive(SdError, &err, portMAX_DELAY);
maxStack |= ((uint32_t) err << (8 * i));
}
Serial.print(F("Minimum Free Stack Size : "));
Serial.println(maxStack, DEC);
}*/
}
}// file Created
}
void loop() {
}
void SendBlockToTask(uint8_t* buf, uint16_t len) {
SdBlock qBlock;
memcpy (qBlock.block, buf, len);
qBlock.len = len;
xQueueSend(SdQ, &qBlock, portMAX_DELAY);
}
void PrintAllErrorQueue() {
int ret = pdPASS;
uint8_t err = NO_ERROR;
while (uxQueueMessagesWaiting(SdError)) {
ret = xQueueReceive(SdError, &err, portMAX_DELAY);
Serial.print(F("Error : "));
Serial.println(err, DEC);
}
}
bool StopSdFileToTask() {
PrintAllErrorQueue(); // flush the error Queue
char nameString[FILENAME_LENGTH + 1] = {'\0'};
xQueueSend(SdFileName, &nameString, portMAX_DELAY); // send the noname
while (!uxQueueMessagesWaiting(SdError)) { // wait for response
delayMicroseconds(10); // stability
}
uint8_t err;
int ret = xQueueReceive(SdError, &err, portMAX_DELAY);
if (err != WRITE_STOP) {
Serial.println(F("'Write Stop' not received"));
Serial.print(F("Error : "));
Serial.println(err, DEC);
PrintAllErrorQueue();
return false;
}
return true;
}
bool CreateSdFileToTask(String filename) {
PrintAllErrorQueue(); // flush the error Queue
char nameString[FILENAME_LENGTH + 1] = {'\0'};
filename = "/" + filename;
filename += ".pst"; // adding the extension here is better
uint8_t len = filename.length();
if (len > FILENAME_LENGTH) len = FILENAME_LENGTH;
filename.toCharArray(nameString, len + 1);
xQueueSend(SdFileName, &nameString, portMAX_DELAY); // send the name
while (!uxQueueMessagesWaiting(SdError)) { // wait for response
delay(10); // stability
}
uint8_t err;
int ret = xQueueReceive(SdError, &err, portMAX_DELAY);
if (err != NAME_RECEIVED) {
Serial.println(F("Name not received"));
Serial.print(F("Error : "));
Serial.println(err, DEC);
PrintAllErrorQueue();
return false;
}
char header[] = "DmagiX PixelStream vx.xx\rColorSequence=X\rFrameSize=0000\rDmxSize=000\rID=000000#";
SdBlock qBlock;
memcpy (qBlock.block, header, sizeof(header) - 1);
qBlock.len = sizeof(header) - 1;
xQueueSend(SdQ, &qBlock, portMAX_DELAY); // send the header to the SD Queue
ret = xQueueReceive(SdError, &err, portMAX_DELAY);
if (err != HEADER_WRITTEN) {
Serial.println(F("Header not written"));
Serial.print(F("Error : "));
Serial.println(err, DEC);
PrintAllErrorQueue();
return false;
}
ret = pdPASS;
err = NO_ERROR;
while (uxQueueMessagesWaiting(SdError) && (ret == pdPASS)) {
ret = xQueueReceive(SdError, &err, portMAX_DELAY);
Serial.print(F("Error : "));
Serial.println(err, DEC);
}
if (err != NO_ERROR) return false;
delay(10);
return true; // success
}
bool SendDataBlockToSdQueue(uint8_t * block, uint16_t len) {
uint16_t ndx = 0;
while ((len) && (!uxQueueMessagesWaiting(SdError))) { // break also on error message waiting
uint16_t blockSize = 512;
if (blockSize > len) blockSize = len;
SdBlock qBlock;
memcpy (qBlock.block, block + ndx, blockSize);
qBlock.len = blockSize;
xQueueSend(SdQ, &qBlock, portMAX_DELAY); // send the block to the SD Queue
len -= blockSize;
ndx += blockSize;
}
int ret = pdPASS;
uint8_t err = NO_ERROR;
while (uxQueueMessagesWaiting(SdError) && (ret == pdPASS)) {
ret = xQueueReceive(SdError, &err, portMAX_DELAY);
Serial.print(F("Error : "));
Serial.println(err, DEC);
}
if (err != NO_ERROR) return false;
return true; // success
}
bool InitSdCard() {
if (hspi == NULL) {
hspi = new SPIClass(HSPI);
}
hspi->begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, SD_CS);
hspi->setFrequency(SPI_FRQ);
if (!SD.begin(SD_CS, *hspi, SPI_FRQ)) {
Serial.println(F("No SD card found"));
return false;
}
return true;
}
//-----------------------------------------------------------------------------------
void HandleQueueToSD(void *pvParameters) {
SdBlock taskBlock;
char nameString[FILENAME_LENGTH + 1] = {'\0'};
uint8_t taskState = WAIT_FOR_NAME;
File sFile;
//uint32_t maxStack = uxTaskGetStackHighWaterMark(NULL);
for (;;) {
vTaskDelay(1 / portTICK_PERIOD_MS); // when running a higher priority
if ((SdQ != NULL) && (SdFileName != NULL) && (SdError != NULL)) { // Sanity check
//STACK_CHECK
switch (taskState) {
//STACK_CHECK
case WAIT_FOR_NAME: {
if (nameString[0] == '\0') { // only if we have no name
if (uxQueueMessagesWaiting(SdQ)) { // shouldn't happen, but we need to read them regardless
int ret = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0); // it should be there, if it isn't we can continue
if (ret == pdPASS) { // and send error messages back
uint8_t err = UNEXP_SDQ;
xQueueSend(SdError, &err, (TickType_t) 0); // don't wait
//STACK_CHECK
}
else if (ret == pdFALSE) {
uint8_t err = UNEXP_SDQ_EMPTY;
xQueueSend(SdError, &err, (TickType_t) 0); // don't wait
//STACK_CHECK
}
}
if (uxQueueMessagesWaiting(SdFileName)) {
int ret = xQueueReceive(SdFileName, &nameString, portMAX_DELAY); // read the filename
if (ret == pdPASS) {
//STACK_CHECK
if (nameString[0] == '\0') {
uint8_t err = UNEXP_FILENAME_NULL;
xQueueSend(SdError, &err, (TickType_t) 0); // should not be empty
}
else {
//STACK_CHECK
sFile = SD.open(nameString, FILE_WRITE); // open the file
if (!sFile) {
uint8_t err = FILE_NOT_CREATED;
xQueueSend(SdError, &err, (TickType_t) 0); // error
}
else {
//STACK_CHECK
uint8_t err = NAME_RECEIVED;
xQueueSend(SdError, &err, (TickType_t) 0); // No error !!
while (!uxQueueMessagesWaiting(SdQ)) ;
//STACK_CHECK
int ret = xQueueReceive(SdQ, &taskBlock, portMAX_DELAY);
if (ret == pdFALSE) {
uint8_t err = EMPTY_QUEUE;
xQueueSend(SdError, &err, (TickType_t) 0);
//STACK_CHECK
sFile.close();
nameString[0] = '\0';
}
else {
//STACK_CHECK
uint16_t bytesWritten = sFile.write(taskBlock.block, taskBlock.len);
if (bytesWritten != taskBlock.len) { // error
uint8_t err = WRITE_ERROR;
//STACK_CHECK
xQueueSend(SdError, &err, (TickType_t) 0);
nameString[0] = '\0';
}
else {
uint8_t err = HEADER_WRITTEN; // no error
//STACK_CHECK
xQueueSend(SdError, &err, (TickType_t) 0);
taskState = WAIT_FOR_DATA;
}
//STACK_CHECK
sFile.close(); // close file anyway
}
}
}
}
else if (ret == pdFALSE) {
//STACK_CHECK
uint8_t err = UNEXP_FILENAME_EMPTY;
xQueueSend(SdError, &err, (TickType_t) 0); // don't wait
}
}
}
}
case WAIT_FOR_DATA: {
//STACK_CHECK
if (uxQueueMessagesWaiting(SdQ)) {
sFile = SD.open(nameString, FILE_APPEND); // open the file
int ret = pdPASS;
while (ret == pdPASS) {
//STACK_CHECK
ret = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0); //portMAX_DELAY);
if (ret == pdPASS) {
uint16_t bytesWritten = sFile.write(taskBlock.block, taskBlock.len);
if (bytesWritten != taskBlock.len) { // error
uint8_t err = WRITE_ERROR;
//STACK_CHECK
xQueueSend(SdError, &err, (TickType_t) 0); // send the error
// send specifics
err = bytesWritten >> 8;
xQueueSend(SdError, &err, (TickType_t) 0);
err = bytesWritten & 0xFF;
xQueueSend(SdError, &err, (TickType_t) 0);
err = taskBlock.len >> 8;
xQueueSend(SdError, &err, (TickType_t) 0);
err = taskBlock.len & 0xFF;
xQueueSend(SdError, &err, (TickType_t) 0);
nameString[0] = '\0'; // clear the name
taskState = WAIT_FOR_NAME; // back to the name task
ret = pdFALSE; // Stop write loop
int fl = pdPASS;
while (fl == pdPASS) { // flush the Queue
//STACK_CHECK
fl = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0);
}
}
STACK_CHECK
}
} // read Queue loop
//STACK_CHECK
sFile.close();
//STACK_CHECK
} // if SdQ available
if (uxQueueMessagesWaiting(SdFileName)) { // check if there is a name available
int ret = xQueueReceive(SdFileName, &nameString, portMAX_DELAY); // read the filename
if (ret == pdPASS) {
//STACK_CHECK
if (nameString[0] == '\0') {
uint8_t err = WRITE_STOP;
xQueueSend(SdError, &err, (TickType_t) 0); // should be empty
//STACK_CHECK
}
else { // we were in a stream, so the name shoyuld have been empty
uint8_t err = NAME_NOT_EXP;
xQueueSend(SdError, &err, (TickType_t) 0); // not empty
nameString[0] = '\0';
//STACK_CHECK
}
taskState = WAIT_FOR_NAME; // task ends either way
int q = pdPASS;
bool notEmpty = false;
//STACK_CHECK
while (q == pdPASS) { // flush the Queue just in case (should be empty)
q = xQueueReceive(SdQ, &taskBlock, (TickType_t) 0);
if (q == pdPASS) {
notEmpty = true;
//STACK_CHECK
}
}
if (notEmpty) { // the queue really should be empty
uint8_t err = Q_NOT_EMPTY;
//STACK_CHECK
xQueueSend(SdError, &err, (TickType_t) 0); // should be empty
}
// send the maxStack
/*uint8_t err = STACK_SIZE;
xQueueSend(SdError, &err, (TickType_t) 0);
err = maxStack & 0xFF;
maxStack = maxStack >> 8;
xQueueSend(SdError, &err, (TickType_t) 0);
err = maxStack & 0xFF;
maxStack = maxStack >> 8;
xQueueSend(SdError, &err, (TickType_t) 0);
err = maxStack & 0xFF;
maxStack = maxStack >> 8;
xQueueSend(SdError, &err, (TickType_t) 0);
err = maxStack & 0xFF;
maxStack = maxStack >> 8;
xQueueSend(SdError, &err, (TickType_t) 0);
maxStack = uxTaskGetStackHighWaterMark(NULL);
*/
}
}
} // wait for data
}
} // Sanity check
} // Infinite loop
}
Now when i was trying to work out how much Stack Size to declare, the indication was that about 2800 bytes were required, but when i set it to that, it started crashing until i commented out the measuring of the stack size !?
I don't see how that could require an extra 3000 bytes but that much more was what was required for a stable run.
The idea was anyway to create a buffer which could be flexible.
Other option could be to allocate a buffer for all the pixels on the heap. That way i could do all of the processing for what to store on the SD card on core 0, but that would mean that the buffer isn't flexible anymore. As it is, the buffer is partly available for writing when blocks have already been written to the SD card, which should make the less of an impact on the overall performance.
Anyway. This is the first testing of it, now i guess it's time to implement this is a bigger sketch and see how it works for me. The bigger the SD Queue the smoother i suspect.