2021-06-21 11:47 AM
Hi gang. My application uses from 0 to 3 UARTs, or maybe USB CDC instead ... LOTS of end-user settable options. Even before I get to that, I'm not happy with my queue implementation that's to be used with UART (or CDC) Tx and Rx. I would love for the enqueue function below to be part of queue structure itself, but really any ideas or pointers would help.
// Queue type (lost about using functions in it):
typedef struct
{
uint8_t *buffer;
uint32_t size;
uint32_t head; // Enqueue data using the head index.
uint32_t tail; // Dequeue data using the tail index.
uint32_t count;
//bool (*enqueue)(uint8_t *buffer, const uint8_t entry); // Fail
//bool (*enqueue)(Queue_T *queue, const uint8_t entry); // Fail
}
Queue_T;
// Test data
uint8_t TestBuffer[16];
Queue_T MyQueue;
// IN USE: Initialize a queue...
MyQueue.buffer = TestBuffer;
KeyQueue.size = 16;
QueueInit(&MyQueue);
// IN USE: enqueue a byte:
QueueEnqueue(&TestBuffer, 47);
// Init function
void QueueInit(Queue_T *queue)
{
queue->head = queue->size - 1;
queue->tail = queue->size - 1;
queue->count = 0;
}
// Enqueue Function (also dequeue, peek, etc needed)
bool QueueEnqueue(Queue_T *queue, const uint8_t entry)
{
uint32_t index;
index = queue->head; // Queue points to last entry, so incr
index++; // pointer for new entry.
if(index >= queue->size) // If past end of queue area, wrap to
index = 0; // start.
if(index == queue->tail) // If head==tail, then the queue is full.
return 1; // Exit failure.
queue->buffer[index] = entry; // Append the byte or word value.
queue->head = index; // Save the new head pointer.
queue->count++; // Update the count.
return 0; // Exit success.
}
Any pointers on any of this would be great. It seems like such a waste to initialize the queue data type then have to spell out each function. It would be nice to be able to do something like:
MyQueue.enqueue(47);
instead of
QueueEnqueue(&TestBuffer, 47);
since MyQueue already knows about TestBuffer.
This is the closest thing I could find and I could not beat it into submission:
Solved! Go to Solution.
2021-06-21 12:51 PM
#include <stdint.h>
typedef struct Queue_S Queue_T;
#if(0) // some compilers complain about redefinition
typedef struct Queue_S {
uint8_t * buffer;
uint32_t size;
uint32_t head; // Enqueue data using the head index.
uint32_t tail; // Dequeue data using the tail index.
uint32_t count;
_Bool (* enqueue)(uint8_t * buffer, const uint8_t entry);
_Bool (* enqueue2)(Queue_T * queue, const uint8_t entry);
} Queue_T;
#else // this appears to be more widely accepted
struct Queue_S {
uint8_t * buffer;
uint32_t size;
uint32_t head; // Enqueue data using the head index.
uint32_t tail; // Dequeue data using the tail index.
uint32_t count;
_Bool (* enqueue)(uint8_t * buffer, const uint8_t entry);
_Bool (* enqueue2)(Queue_T * queue, const uint8_t entry);
};
#endif
Queue_T queue1, queue2;
uint8_t buf1[10];
volatile uint8_t data;
_Bool foo(Queue_T * queue, const uint8_t entry) {
}
_Bool bar(uint8_t * buffer, const uint8_t entry) {
}
int main(void) {
queue1.buffer = buf1;
queue1.size = 10;
queue1.head = queue1.tail = 0;
queue1.enqueue2 = foo;
queue1.enqueue = bar;
queue2 = queue1;
queue2.enqueue2(&queue1, data);
}
Using the standard C99 _Bool type instead of bool resolved the first "Fail"; forward declaration typedef struct Queue_S Queue_T; resolved the second.
JW
2021-06-21 12:51 PM
#include <stdint.h>
typedef struct Queue_S Queue_T;
#if(0) // some compilers complain about redefinition
typedef struct Queue_S {
uint8_t * buffer;
uint32_t size;
uint32_t head; // Enqueue data using the head index.
uint32_t tail; // Dequeue data using the tail index.
uint32_t count;
_Bool (* enqueue)(uint8_t * buffer, const uint8_t entry);
_Bool (* enqueue2)(Queue_T * queue, const uint8_t entry);
} Queue_T;
#else // this appears to be more widely accepted
struct Queue_S {
uint8_t * buffer;
uint32_t size;
uint32_t head; // Enqueue data using the head index.
uint32_t tail; // Dequeue data using the tail index.
uint32_t count;
_Bool (* enqueue)(uint8_t * buffer, const uint8_t entry);
_Bool (* enqueue2)(Queue_T * queue, const uint8_t entry);
};
#endif
Queue_T queue1, queue2;
uint8_t buf1[10];
volatile uint8_t data;
_Bool foo(Queue_T * queue, const uint8_t entry) {
}
_Bool bar(uint8_t * buffer, const uint8_t entry) {
}
int main(void) {
queue1.buffer = buf1;
queue1.size = 10;
queue1.head = queue1.tail = 0;
queue1.enqueue2 = foo;
queue1.enqueue = bar;
queue2 = queue1;
queue2.enqueue2(&queue1, data);
}
Using the standard C99 _Bool type instead of bool resolved the first "Fail"; forward declaration typedef struct Queue_S Queue_T; resolved the second.
JW
2021-06-21 02:42 PM
> It would be nice to be able to do something like:
> instead of
And that is why a lot of people prefer C++ over C, because you can embed information into the object without needing to pass it around with this extra awkward pointer. This sort of exercise would be a perfect homework problem for C++ 101.
2021-06-21 04:06 PM
Fabulous! I was getting closer, but was just skipping the typedef until everything else was OK. Thanks, this is super helpful!! I had no issues with the bool (using Keil so far).
I can see, if I continue to do this from scratch (with help!), that there will be ISRs using this in some cases. Is calling an enqueue() or dequeue() like this from within an ISR too crazy? There's only 5 or 10 lines of code to the function, plus the stacking/unstacking for the function call. And for the main non-interrupt level code, turning off interrupts for all or part of the these function calls?
I imagine I am (with help!) reinventing the wheel here. This is the kind of thing you'd get with the STM HAL or LL or SPL or the CMSIS Driver or RTOS? I'm wondering if I need to make a small project using all the "frameworks" available and see what is usable or not. Being able to set these up at run-time to some degree is paramount and most of the example I've seen just hard-code a UART # and that's that. I (will) love being able to switch UARTs just by changing a pointer.
2021-06-21 04:21 PM
Yeah, no doubt. I think it's more of a mind bender (for me at least), but there isn't really a lot of code in this case. It's not a cute as I'd hoped, but it's not bad. There might be a way to macro it further, but I'm OK with this. The queue needs it's internal head and tail pointers updated during enqueue/dequeue, so I pass the address to the whole shebang. Main never plays with the head and tail at all.
uint16_t ABuffer[QUEUE_SIZE];
:
MyQueue.buffer = ABuffer;
MyQueue.size = QUEUE_SIZE;
QueueInit(&MyQueue);
// After this is just calls to enqueue/dequeue/etc functions.
In use:
MyQueue.enqueue(&MyQueue, 47); // This is the "enqueue2 variation.
The functions themselves are super short, as you'd expect with a queue. But there is the pointer that's getting passed around and getting dereferenced.
2021-06-21 05:43 PM
> Is calling an enqueue() or dequeue() like this from within an ISR too crazy?
You can call these within an ISR as long as the main loop (or another ISR) isn't doing so at the same time, so you would need to disable interrupts temporarily to ensure it's the case.
Note that if you're modifying stuff within the ISR and the main loop you'll need to define them as volatile. If they're only accessed in one or the other, volatile is not needed.
> This is the kind of thing you'd get with the STM HAL or LL or SPL or the CMSIS Driver or RTOS?
None of those implement a FIFO buffer (maybe RTOS does, not as familiar with it). They're more for accessing the peripherals.
2021-06-22 07:26 PM
> Being able to set these up at run-time to some degree is paramount
That's for you. I've never felt the urge for this. I'm typically standing at the "controller" end of "microcontroller", and the requirements just keep to be too diverse to allow any benefit from any "abstraction", whatever that may be.
This, together with what TDK said, also explain why I consider C++ to be an elegant solution to a problem I (and arguably most mcu users) don't have.
YMMV of course.
JW