Extend photo-diode detector to support multiple sensors - c++

I am quite new to C++, so I was hoping someone can help me answer my question. I am building an embedded system using Arduino.
I have a number of photo-diodes that generate an event when an physical object passes the sensor. The object passes the sensor too quickly, so the signal is short-lived and on non deterministic length. So I wish to implement a timer that holds the state to a period following detection.
The following code is my attempt to implement this for a single photo-diode sensor, (Note: I have not tried to compile it yet, there might be some mistakes). However how can I extend this to support multiple sensors.
Here is code:
struct DiodeResult {
bool diodePassed;
unsigned long timeOfSet;
}
void loop() {
static DiodeResult diodeResult;
bool diodePassed = readDiode();
if (diodePassed) {
diodeResult.diodePassed = true;
diodeResult.timeOfSet = millis();
} else {
if ((millis() - diodeResult.timeOfSet) > (5*1000) {
diodeResult.diodePassed = false;
}
}
}

Your existing solution can be extended to handle multiple photo-diodes simply by using an array of DiodeResult instances:
void loop()
{
static DiodeResult diodeResult[DIODE_COUNT];
for( int i = 0; i < DIODE_COUNT; i++ )
{
bool diodePassed = readDiode( i ) ;
if( diodePassed )
{
diodeResult[i].diodePassed = true;
diodeResult[i].timeOfSet = millis();
}
else
{
if( (millis() - diodeResult[i].timeOfSet) > (5 * 1000)
{
diodeResult[i].diodePassed = false;
}
}
}
}
Or perhaps more "Arduino-like":
void loop()
{
static DiodeResult diodeResult[DIODE_COUNT];
static int i= 0 ;
bool diodePassed = readDiode( i ) ;
if( diodePassed )
{
diodeResult[i].diodePassed = true;
diodeResult[i].timeOfSet = millis();
}
else
{
if( (millis() - diodeResult[i].timeOfSet) > (5 * 1000)
{
diodeResult[i].diodePassed = false;
}
}
// Next diode on next loop iteration
i = (i + 1) % DIODE_COUNT ;
}
However it relies on the photo-diode state remaining active for at least as long as it takes to iterate all ten sensors. This may be the case, but it highly dependent on your application and possibly the implementation of readDiode(). It may be necessary if polling is not fast enough to use interrupts.
Moreover the solution could arguably be improved by a little OOP:
class cMonoStable
{
public :
cMonoStable( unsigned long period_ms ) : m_time_ms(time_ms)
{
}
void trigger()
{
m_start_ms = millis() ;
}
bool getState()
{
return m_start_ms - millis() < m_time_ms ;
}
private :
unsigned long m_time_ms;
unsigned long m_start_ms
};
class cPhotoDiodeTriggeredMonostable : public cMonoStable
{
public :
cPhotoDiodeTriggeredMonostable( unsigned long period_ms, int diode = 0 ) : cMonoStable( period_ms ) : m_diode(diode)
{
}
void update()
{
if( readDiode( m_diode ) )
{
trigger() ;
}
}
private:
int m_diode ;
} ;
static const int DIODE_COUNT = 10 ;
static cPhotoDiodeTriggeredMonostable* photo_diode_state[DIODE_COUNT];
void setup()
{
for( int i = 0; i < DIODE_COUNT; i++ )
{
photo_diode_state[i] = new cPhotoDiodeTriggeredMonostable( 5000, i ) ;
}
}
void loop()
{
static int i = 0 ;
photo_diode_state[i]->update() ;
if( photo_diode_state[i].getState() )
{
// do whatever - state remains true for 5 seconds after diode was last active.
}
}
It looks a little long winded, but not how simple the individual elements are, and the cMonoStable class can be applied to other sensor types with similar characteristics, just be implementing a super-class with a different update() implementation.

Related

C++ (Arduino), Can't update object when accessing it as attribute of another object

I have recently bought an Arduino which uses C++ to code on. I am familiar with Java and as C++ allows OO programming I didn't think it would have been that difficult. But...
CODE:
// LEDCLOCK CLASS /////////////////////////////////////////////////////////////////////////
class LedClock{
private:
int hours;
int minutes;
int seconds;
static const long secondInterval = 1000;
unsigned long previousMilliseconds;
unsigned long currentMilliseconds;
public:
LedClock(){
hours = 0;
minutes = 0;
seconds = 0;
previousMilliseconds = 0;
currentMilliseconds = 0;
};
int getHours(){
return hours;
};
int getMinutes(){
return minutes;
};
int getSeconds(){
return seconds;
};
long getSecondInterval(){
return secondInterval;
};
unsigned long getPreviousMilliseconds(){
return previousMilliseconds;
};
void setHours(int h){
if(h < 24 && h >= 0){
hours = h;
}else{
hours = 0;
}
};
void setMinutes(int m){
if(m < 60 && m > 0){
minutes = m;
}else{
minutes = 0;
}
};
void setSeconds(int s){
if(s < 60 && s > 0){
seconds = s;
}else{
seconds = 0;
}
};
void setPreviousMilliseconds(unsigned long ms){
previousMilliseconds = ms;
};
void increaseOneHour(){
// as there is no day counter to increment atm this if-else statement is a bit useless.
// setHour(getHours() + 1) would have sufficed here with the current setter
if(getHours()==23){
setHours(0);
}else{
setHours(getHours() + 1);
}
};
void increaseOneMinute(){
if(getMinutes() == 59){
increaseOneHour();
setMinutes(0);
}else{
setMinutes(getMinutes() + 1);
}
};
void increaseOneSecond(){
if(getSeconds() == 59){
increaseOneMinute();
setSeconds(0);
}else{
setSeconds(getSeconds() + 1);
}
};
void tick(){
currentMilliseconds = millis();
if(currentMilliseconds - getPreviousMilliseconds() >= getSecondInterval()){
setPreviousMilliseconds(currentMilliseconds);
increaseOneSecond();
}
};
};
// LEDCLOCKCONTROLLER CLASS ////////////////////////////////////////////////////////////////
class LedClockController{
private:
LedClock ledClock;
int mode = 2;
public:
LedClockController(LedClock lc){
ledClock = lc;
};
LedClock getLedClock(){
return ledClock;
};
int getMode(){
return mode;
};
void setMode(int newMode){
mode = newMode;
};
};
// ARDUINO CODE /////////////////////////////////////////////////////////////////////////
LedClock lc = LedClock();
LedClockController lcc(lc);
void setup() {
Serial.begin(9600); //Begin serializer to print out value
}
void loop() {
//doesn't give me updated values
if(lcc.getLedClock().getPreviousMilliseconds()<63000){
Serial.println(lcc.getLedClock().getSeconds());
lcc.getLedClock().tick();
}
//does give me updated values
//commented out for now
/*
if(lc.getPreviousMilliseconds()<63000){
Serial.println(lc.getSeconds());
lc.tick();
}
*/
}
Q1:
I have difficulties to update attributes of my LedClock attribute in my Controller class.
When I target the LedClock by itself, everything runs fine but when I would update it via the Controller, then it wouldn't. In short, when getting the seconds in the first case, I could see the increment in values in the output. When I did the same thing but via the controller, the seconds stayed 0.
So I am missing something vital here. Can someone help/explain what I am missing?
Q2: Ideally I would want to create the LedClock object inside the constructor but didn't seem to find how to.
I tried things that could make sense but with the issues I have been having, I was holding off on this:
LedClockController lcc(LedClock lc());
LedClockController lcc(LedClock);
//would make sense to me, I noticed C++ doesn't use the 'new' keyword so have no idea how to do that then
LedClockController lcc(LedClock());
All of those ran into compilation issues so probably another important C++ thing that I haven't taken into account.
P.S. I have been noticing that there are some different views on getter and setters (accessing the attributes directly vs actual functions). I have been using the method I am used to (and were mentioned on W3schools) because I will rely on setting logic in 1 place.

The attributes of my child class are lazily initialized

I have created a parent class in C++ and one child class which I have two attributes: _trigger and _echo. In order to use my child class I declare it and I assign its address to a pointer of the parent class. Hence, I use the methods of my parent class.
My problem: When I use the method cyclePulse without parameters (int trigger, int echo) and the attributes from my class (_trigger and _echo), the method does not work properly. I guess it is because the attributes _trigger and _echo are lazily initialized, or because I am not using new keyword when I am creating my object.
class ISensor {
...
public:
ISensor();
virtual ~ISensor();
...
virtual int connect() = 0;
virtual char * readRequest() = 0;
virtual int disconnect() = 0;
};
class HCSR04: public ISensor {
private:
int _trigger;
int _echo;
public:
HCSR04();
HCSR04(int trigger, int echo);
...
int connect();
char * readRequest();
uint64_t cyclePulse(int trigger, int echo);
float distanceCentimeters();
};
Here is the implementation of HCSR04.cpp. Edited: I forgot the constructors.
HCSR04::HCSR04() {
_echo = RPI_V2_GPIO_P1_13;
_trigger = RPI_V2_GPIO_P1_15;
}
HCSR04::HCSR04(int trigger, int echo) {
_echo = echo;
_trigger = trigger;
}
char * HCSR04::readRequest() {
float preCent = distanceCentimeters();
char* buf = new char[20];
sprintf(buf, "%.10f", preCent);
return buf;
}
float HCSR04::distanceCentimeters() {
return (float) cyclePulse(_trigger, _echo) / 55.5;
}
uint64_t HCSR04::cyclePulse(int trigger, int echo) {
uint64_t width, begin, start, end;
int max = 80, check;
begin = bcm2835_st_read();
// Emit pulse for 10 microseconds
bcm2835_gpio_write(_trigger, HIGH); // Set trigger state HIGH
bcm2835_delayMicroseconds(10); // Wait 10 microseconds
bcm2835_gpio_write(_trigger, LOW); // Set trigger state LOW
while (bcm2835_gpio_lev(_echo) == LOW && check < max) {
start = bcm2835_st_read();
check = (int) begin - start;
}
while (bcm2835_gpio_lev(_echo) == HIGH) {
bcm2835_delayMicroseconds(1);
}
end = bcm2835_st_read();
width = end - start;
return width;
}
I am using the class like this:
HCSR04 deviceUltrasonic;
ISensor * sensorUltrasonic = &deviceUltrasonic;
char* readRequestArray = sensorUltrasonic->readRequest();
Giving my previous comment as a possible answer here, because I assume that the uninitialized variables are causing your fault here:
uint64_t HCSR04::cyclePulse(int trigger, int echo) {
uint64_t width, begin, start, end;
int max = 80, check = 0; // <<< init check to 0.
// Btw: size_t or any other unsigned type matches
// better the purpose of what you want to achieve.
begin = bcm2835_st_read();
// begin is not used afterwards. Did you mean to initialize "start" here?
start = begin;
// Emit pulse for 10 microseconds
bcm2835_gpio_write(_trigger, HIGH); // Set trigger state HIGH
bcm2835_delayMicroseconds(10); // Wait 10 microseconds
bcm2835_gpio_write(_trigger, LOW); // Set trigger state LOW
while (bcm2835_gpio_lev(_echo) == LOW && check < max) {
start = bcm2835_st_read();
check = (int) begin - start;
}
while (bcm2835_gpio_lev(_echo) == HIGH) {
bcm2835_delayMicroseconds(1);
}
end = bcm2835_st_read();
width = end - start;
return width;
}

arduino if statement giving error

I am working on some arduino code and my program keeps on giving me this error,
ISO C++ forbids comparison between pointer and integer [-fpermissive]
I've tried searching on the internet to solve this issue but, either the solution is incorrect, or irrelevant. here is where the arduino software is saying the problem is,
if((millis - incLastDebounce) > debounceDelay) {
and if you need the rest of the code here it is,
#include <LiquidCrystal.h>
int freq = 0;
int change = 0;
const int incPin = 3;
const int setPin = 2;
int incButtonState;
int setButtonState;
int incPreviousState;
int setPreviousState;
int incLastDebounce;
int setLastDebounce;
const int debounceDelay = 50;
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() {
// put your setup code here, to run once:
lcd.begin(16, 2);
pinMode(setPin, INPUT);
pinMode(incPin, INPUT);
lcd.print("Frequency: " + freq);
}
void loop() {
// put your main code here, to run repeatedly:
int incReading = digitalRead(incPin);
int setReading = digitalRead(setPin);
if(setReading != setPreviousState) {
setLastDebounce = millis();
}
if(incReading != incPreviousState) {
incLastDebounce = millis();
}
if((millis - setLastDebounce) > debounceDelay) {
if(setReading != setButtonState) {
setButtonState = setReading;
}
if(setButtonState == HIGH) {
//Okay so here you will do your set lcd voodoo
}
}
if((millis - incLastDebounce) > debounceDelay) {
if(incReading != buttonState) {
incButtonState = incReading;
}
if(buttonState == HIGH) {
// here you can put the lcd code
change = change + 500;
if(change == 10500){
change = 0;
}
}
}
incPreviousState = incReading;
setPreviousState = setReading;
}
hopefully you can find the problem and help.
Looks like you're missing parentheses after millis, so instead of calling the function, you're trying to do arithmetic with its memory address.
This will probably work better:
if ((millis() - incLastDebounce) > debounceDelay) {

How to limit a decrement?

There is a initial game difficulty which is
game_difficulty=5 //Initial
Every 3 times if you get it right, your difficulty goes up to infinity but every 3 times you get it wrong, your difficulty goes down but not below 5. So, in this code for ex:
if(user_words==words) win_count+=1;
else() incorrect_count+=1;
if(win_count%3==0) /*increase diff*/;
if(incorrect_count%3==0) /*decrease difficulty*/;
How should I go about doing this?
Simple answer:
if(incorrect_count%3==0) difficulty = max(difficulty-1, 5);
But personally I would wrap it up in a small class then you can contain all the logic and expand it as you go along, something such as:
class Difficulty
{
public:
Difficulty() {};
void AddWin()
{
m_IncorrectCount = 0; // reset because we got one right?
if (++m_WinCount % 3)
{
m_WinCount = 0;
++m_CurrentDifficulty;
}
}
void AddIncorrect()
{
m_WinCount = 0; // reset because we got one wrong?
if (++m_IncorrectCount >= 3 && m_CurrentDifficulty > 5)
{
m_IncorrectCount = 0;
--m_CurrentDifficulty;
}
}
int GetDifficulty()
{
return m_CurrentDifficulty;
}
private:
int m_CurrentDifficulty = 5;
int m_WinCount = 0;
int m_IncorrectCount = 0;
};
You could just add this as a condition:
if (user words==words) {
win_count += 1;
if (win_count %3 == 0) {
++diff;
}
} else {
incorrect_count += 1;
if (incorrect_count % 3 == 0 && diff > 5) {
--diff
}
}
For example:
if(win_count%3==0) difficulty++;
if(incorrect_count%3==0 && difficulty > 5) difficulty--;
This can be turned into a motivating example for custom data types.
Create a class which wraps the difficulty int as a private member variable, and in the public member functions make sure that the so-called contract is met. You will end up with a value which is always guaranteed to meet your specifications. Here is an example:
class Difficulty
{
public:
// initial values for a new Difficulty object:
Difficulty() :
right_answer_count(0),
wrong_answer_count(0),
value(5)
{}
// called when a right answer should be taken into account:
void GotItRight()
{
++right_answer_count;
if (right_answer_count == 3)
{
right_answer_count = 0;
++value;
}
}
// called when a wrong answer should be taken into account:
void GotItWrong()
{
++wrong_answer_count;
if (wrong_answer_count == 3)
{
wrong_answer_count = 0;
--value;
if (value < 5)
{
value = 5;
}
}
}
// returns the value itself
int Value() const
{
return value;
}
private:
int right_answer_count;
int wrong_answer_count;
int value;
};
And here is how you would use the class:
Difficulty game_difficulty;
// six right answers:
for (int count = 0; count < 6; ++count)
{
game_difficulty.GotItRight();
}
// check wrapped value:
std::cout << game_difficulty.Value() << "\n";
// three wrong answers:
for (int count = 0; count < 3; ++count)
{
game_difficulty.GotItWrong();
}
// check wrapped value:
std::cout << game_difficulty.Value() << "\n";
// one hundred wrong answers:
for (int count = 0; count < 100; ++count)
{
game_difficulty.GotItWrong();
}
// check wrapped value:
std::cout << game_difficulty.Value() << "\n";
Output:
7
6
5
Once you have a firm grasp on how such types are created and used, you can start to look into operator overloading so that the type can be used more like a real int, i.e. with +, - and so on.
How should I go about doing this?
You have marked this question as C++. IMHO the c++ way is to create a class encapsulating all your issues.
Perhaps something like:
class GameDifficulty
{
public:
GameDifficulty () :
game_difficulty (5), win_count(0), incorrect_count(0)
{}
~GameDifficulty () {}
void update(const T& words)
{
if(user words==words) win_count+=1;
else incorrect_count+=1;
// modify game_difficulty as you desire
if(win_count%3 == 0)
game_difficulty += 1 ; // increase diff no upper limit
if((incorrect_count%3 == 0) && (game_difficulty > 5))
game_difficulty -= 1; //decrease diff;
}
inline int gameDifficulty() { return (game_difficulty); }
// and any other access per needs of your game
private:
int game_difficulty;
int win_count;
int incorrect_count;
}
// note - not compiled or tested
usage would be:
// instantiate
GameDiffculty gameDifficulty;
// ...
// use update()
gameDifficulty.update(word);
// ...
// use access
gameDifficulty.gameDifficulty();
Advantage: encapsulation
This code is in one place, not polluting elsewhere in your code.
You can change these policies in this one place, with no impact to the rest of your code.

C Simple RingBuffer - Multithreading - Finding Critical Sections [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
so I wrote a simple C Ring Buffer that I'm now testing using multiple threads and I'm having a hard time trying to get the code to fail so that I can identify critical sections.
Note: The code is in C, but i'm testing it in C++ files because its easier to create threads mutexes etc.
Header File:
#ifndef _C_TEST_H_
#define _C_TEST_H_
#include <stdio.h>
#include <mutex>
///////////////////////////////////////////////////////////////////////////////
// Defines and macros
///////////////////////////////////////////////////////////////////////////////
#ifndef __cplusplus
typedef enum { false, true } bool;
#endif
#define RING_BUFFER_SIZE 2000
///////////////////////////////////////////////////////////////////////////////
// Structures, Enumerations, Typedefs
///////////////////////////////////////////////////////////////////////////////
typedef struct Node
{
int val;
struct Node *next;
struct Node *previous;
} Node_T;
typedef enum RB_ERC
{
RB_ERC_NO_ERROR,
RB_ERC_NULL_PTR,
RB_ERC_UNDERFLOW,
RB_ERC_OVERFLOW
} RB_ERC_T;
typedef enum RB_HANDLE_OVERFLOW
{
RB_DECIMATE,
RB_IGNORE_AND_RETURN_ERROR
} RB_HANDLE_OVERFLOW_T;
typedef enum RB_READ_MODE
{
RB_FIFO,
RB_LIFO
} RB_READ_MODE_T;
typedef struct RingBuffer
{
int curSize;
RB_HANDLE_OVERFLOW_T handleOverflow;
struct Node *Write;
struct Node *Read;
Node_T buffer[RING_BUFFER_SIZE];
} RING_BUFFER_T;
///////////////////////////////////////////////////////////////////////////////
// Prototypes
///////////////////////////////////////////////////////////////////////////////
#ifdef __cplusplus
extern "C" {
#endif
RB_ERC_T RB_InitRingBuffer(RING_BUFFER_T *rb_, RB_HANDLE_OVERFLOW_T ifOverflow_);
//Return true if the queue has no elements; false if there are elements on the queue
bool RB_IsEmpty(RING_BUFFER_T *rb_);
//Return true if the queue is full; false if there are seats available
bool RB_IsFull(RING_BUFFER_T *rb_);
//Write N elements (length of the array) to the queue
//Note: array values will be read from element 0 to array length
RB_ERC_T RB_WriteArray(RING_BUFFER_T *rb_, int values_[], int length_);
//Write 1 element
RB_ERC_T RB_Write(RING_BUFFER_T *rb_, int val_);
//Dequeue and read N elements (length of the array) into an array
RB_ERC_T RB_ReadArray(RING_BUFFER_T *rb_, int values_[], int length_, RB_READ_MODE_T readMode_);
//Dequeue and read 1 element
RB_ERC_T RB_Read(RING_BUFFER_T *rb_, int *readVal_, RB_READ_MODE_T readMode_);
#ifdef __cplusplus
}
#endif
#endif //_C_TEST_H_
Source:
#include "CTest.h"
static std::mutex m;
RB_ERC_T RB_InitRingBuffer(RING_BUFFER_T *rb_, RB_HANDLE_OVERFLOW_T handleOverflow_)
{
//m.lock();
RB_ERC_T erc = RB_ERC_NO_ERROR;
int i;
if(rb_ == 0)
{
return RB_ERC_NULL_PTR;
}
//Initialize this instance of the ring buffer
//Both the read/write pointers should start at the same location
rb_->curSize = 0;
rb_->Read = &rb_->buffer[0];
rb_->Write = &rb_->buffer[0];
rb_->handleOverflow = handleOverflow_;
//Build the circular doubly-linked list
for(i = 0; i < RING_BUFFER_SIZE; i++)
{
rb_->buffer[i].val = 0;
if(i == 0)
{
//Sentinal Node found. Point the first node to the last element of the array
rb_->buffer[i].previous = &rb_->buffer[(RING_BUFFER_SIZE - 1)];
rb_->buffer[i].next = &rb_->buffer[i + 1];
}
else if(i < (RING_BUFFER_SIZE - 1) )
{
rb_->buffer[i].next = &rb_->buffer[i + 1];
rb_->buffer[i].previous = &rb_->buffer[i - 1];
}
else
{
//Sentinal node found. Reached the last element in the array; Point the sentinal
//node to the first element in the array to create a circular linked list.
rb_->buffer[i].next = &rb_->buffer[0];
rb_->buffer[i].previous = &rb_->buffer[i - 1];
}
}
//m.unlock();
return erc;
}
bool RB_IsEmpty(RING_BUFFER_T *rb_)
{
//m.lock();
//Note: assume rb is valid.
if(rb_->curSize == 0)
{
return true;
}
else
{
return false;
}
//m.unlock();
}
bool RB_IsFull(RING_BUFFER_T *rb_)
{
//m.lock();
//Note: assume rb is valid.
if(rb_->curSize == RING_BUFFER_SIZE)
{
return true;
}
else
{
return false;
}
//m.unlock();
}
RB_ERC_T RB_WriteArray(RING_BUFFER_T *rb_, int values_[], int length_)
{
//m.lock();
RB_ERC_T erc = RB_ERC_NO_ERROR;
int i;
if(rb_ == 0 || values_ == 0 || length_ == 0)
{
return RB_ERC_NULL_PTR;
}
switch(rb_->handleOverflow)
{
//Increment through the array and enqueue
//If attempting to write more elements than are available on the queue
//Decimate - overwrite old data
//Ignore and return error - Don't write any data and throw an error
case RB_DECIMATE:
for(i = 0; i < length_; i++)
{
RB_Write(rb_, values_[i] );
}
break;
default:
case RB_IGNORE_AND_RETURN_ERROR:
{
int numSeatsAvailable = (RING_BUFFER_SIZE - rb_->curSize);
if( length_ <= numSeatsAvailable )
{
//Increment through the array and enqueue
for(i = 0; i < length_; i++)
{
RB_Write(rb_, values_[i] );
}
}
else
{
//Attempted to write more elements than are avaialable on the queue
erc = RB_ERC_OVERFLOW;
}
}
break;
}
//m.unlock();
return erc;
}
RB_ERC_T RB_Write(RING_BUFFER_T *rb_, int val_)
{
//m.lock();
RB_ERC_T erc = RB_ERC_NO_ERROR;
if(rb_ == 0)
{
return RB_ERC_NULL_PTR;
}
if( !RB_IsFull(rb_) )
{
//Write the value to the current location, then increment the write pointer
//so that the write pointer is always pointing 1 element ahead of the queue
rb_->Write->val = val_;
rb_->Write = rb_->Write->next;
rb_->curSize++;
}
else
{
//Overflow
switch(rb_->handleOverflow)
{
case RB_DECIMATE:
//Set the value and increment both the read/write pointers
rb_->Write->val = val_;
rb_->Write = rb_->Write->next;
rb_->Read = rb_->Read->next;
break;
default:
case RB_IGNORE_AND_RETURN_ERROR:
erc = RB_ERC_OVERFLOW;
break;
}
}
//m.unlock();
return erc;
}
RB_ERC_T RB_ReadArray(RING_BUFFER_T *rb_, int values_[], int length_, RB_READ_MODE_T readMode_)
{
//m.lock();
RB_ERC_T erc = RB_ERC_NO_ERROR;
if(values_ == 0)
{
return RB_ERC_NULL_PTR;
}
//Verify that the amount of data to be read is actually available on the queue
if( length_ <= rb_->curSize )
{
//Increment through the array and dequeue
int i;
for(i = 0; i < length_; i++)
{
//Note: Error conditions have already been checked. Skip the ERC check
(void) RB_Read(rb_, &values_[i], readMode_);
}
}
else
{
//Attempted to read more data than is available on the queue
erc = RB_ERC_UNDERFLOW;
}
//m.unlock();
return erc;
}
RB_ERC_T RB_Read(RING_BUFFER_T *rb_, int *readVal_, RB_READ_MODE_T readMode_)
{
//m.lock();
RB_ERC_T erc = RB_ERC_NO_ERROR;
if(rb_ == 0 || readVal_ == 0)
{
return RB_ERC_NULL_PTR;
}
if( !RB_IsEmpty(rb_) )
{
switch(readMode_)
{
case RB_LIFO:
//Use the head (Write) to read the most recently written value (newest data)
//Note: The write pointer is always pointing 1 position ahead of the current queue.
rb_->Write = rb_->Write->previous; //Decrement write pointer
//Read the data
*readVal_ = rb_->Write->val;
rb_->Write->val = 0; //Reset read values to 0
break;
default:
case RB_FIFO:
*readVal_ = rb_->Read->val;
rb_->Read->val = 0; //Reset read values to 0
rb_->Read = rb_->Read->next; //Increment read pointer
break;
}
rb_->curSize--;
}
else
{
//Attempted to read more data but there is no data available on the queue
erc = RB_ERC_UNDERFLOW;
}
//m.unlock();
return erc;
}
Main CPP using for tests:
#include "CTest.h"
#include <iostream>
#include "windows.h"
#include <thread>
using namespace std;
static RING_BUFFER_T test1;
const int dataSize = 300;
const int dataSizeout = 1000;
int sharedValue = 0;
static std::mutex m;
void function1()
{
int data[dataSize];
RB_ERC_T erc = RB_ERC_NO_ERROR;
for (int i = 0; i < dataSizeout; i++)
{
erc = RB_Write(&test1, i);
if (erc != RB_ERC_NO_ERROR)
{
printf("Count down errrror %d\n", erc);
}
}
//RB_WriteArray(&test1, data, dataSize);
}
void function2()
{
RB_ERC_T erc = RB_ERC_NO_ERROR;
for (int i = 0; i > -dataSizeout; i--)
{
erc = RB_Write(&test1, i);
if (erc != RB_ERC_NO_ERROR)
{
printf("Count down errrror %d\n", erc);
}
}
}
int main()
{
RB_InitRingBuffer(&test1, RB_DECIMATE);
thread p1(function1);
//Sleep(1000);
thread p2(function2);
p1.join();
p2.join();
//Read out 5 at a time
int out;
int cnt = 0;
while(cnt < (2 * dataSizeout) )
{
if (RB_Read(&test1, &out, RB_LIFO) == RB_ERC_NO_ERROR)
{
printf("out[%d] = %d\n", cnt, out);
cnt += 1;
}
}
system("Pause");
return 0;
}
I'm thinking that everything in the main RING_BUFFER_T instance would be shared variables, so everywhere they are used, which is pretty much everywhere, they would have to be enclosed in mutexes.
typedef struct RingBuffer
{
int curSize;
RB_HANDLE_OVERFLOW_T handleOverflow;
struct Node *Write;
struct Node *Read;
Node_T buffer[RING_BUFFER_SIZE];
} RING_BUFFER_T;
I suppose NODE_T would be as well, but only for initialization. Am I wrong or shouldn't the elements being stuffed in the ring buffer be placed out of order, since there is no mutex being used right now?
For a state-of-the-art C implementation of a lock-free ring buffer, look in the Linux kernel source code. That should give you some idea of how the experts do it, and it is battle-proven code. See linux/kfifo.h and corresponding C file(s).
design description of Linux ring buffer, dunno how up-to-date it is
For ideas of how to do it in C++, you can look at
Linux Journal article about C++ lock-free queue
or maybe look at boost::lockfree::queue. Using C++ of course enables you to use generic types (templates) and e.g. replace function pointers with compile-time bound calls, thus enabling even better performance than C. And you can avoid those pesky void* pointers.
Thou Shalt Not expose the functions RB_IsEmpty and RB_IsFull as the return values may be invalid immediately. If you only call them from within read/write there is no need to do protection within that functions.
Typically you must protect your struct within the externally exposed read and write functions from the first access to the last access. There is no need to protect parameter checking.
You shall not double lock. Do not call RB_Read from RB_ReadArray. Provide an internal read function used by both. Same for the write functions.