Passing variables by reference to avoid global variables and extern - c++

I have had written my program but it was relying solely on the use of global variables and extern. I have decided to work on fixing my code and make it as simple as possible. However, It seems to be going in the wrong direction. I have 3 structures that are used pretty much everywhere in my code:
item_inside_struct
device_info
encoder_struct.
I have written a wrapper for the tft display. The tft display must know all the states of the 3 structs since it is displaying everything on the screen. So I pass these structs to my tft functions as a reference. But since the tft functions are being called elsewhere in the code ( it could be main.c or even invoked in other source files), the structures must be passed to other functions that invoke tft methods as well.
For example: I have written sensor.c and sensor.h. It contains all the sensor functions. After the sensor is read, based on the sensor result, it may need to do update tft display hence I must pass all the structure data to the function that performs reading the sensor even though the function itself does not care about these 3 structures apart from the ftf update function. My sensor.c :
void Read_sensor_no_delay(item_inside_struct* item_inside, device_info* device, encoder_struct* encoder){
if(sensor_disable == 0){
if(strncmp(item_inside->state, "PILDYMAS", 8) != 0)
{
sensor = digitalRead(SENSOR);
if(sensor==LOW && sensor_prev_state==HIGH)// SENSOR ON
{
sensor_prev_state=LOW; // set the previous state to LOW
if (strncmp(item_inside->state,"ACTIVE",6)!=0){ // IF NUMBER TO PICK ITEMS IS 0, SET RED LIGHT TO SHOW OPERATOR WRONG BOX
handle_wrong_box(item_inside,device,encoder);
}
}
else if(sensor==HIGH && sensor_prev_state==LOW) //SENSOR DEACTIVATED
{
if(item_inside->num_to_pick > 0 && (strncmp(item_inside->state,"ACTIVE",6)==0) )// if number_to_pick >0, and device is activated, take items
{
handle_active_device(item_inside,device,encoder);
sensor_disable = 1;
lastMsg2 = millis();
}
else if(strncmp(item_inside->state,"ACTIVE",6)!=0)
{
handle_not_active_device(item_inside,device,encoder);
}
sensor_prev_state = HIGH;
}
}
}
else{
if (now - lastMsg2 > 4000)
{
//Serial.println("5 seconds passed");
sensor_disable=0;
}
}
}
void handle_wrong_box(item_inside_struct* item_inside, device_info* device, encoder_struct* encoder){
turn_ON_timer();
LED_strip_ON(red,PixelCount);
update_screen_colour(ST77XX_RED,item_inside,device,encoder);
}
void handle_active_device(item_inside_struct* item_inside, device_info* device, encoder_struct* encoder){
sensor_prev_state = HIGH;
item_inside->num_to_pick--;
item_inside->quantity--;
mb.Hreg(MAIN_REG, 12); //PUT 55 IN REG1
mb.Hreg(NUMBER_TO_PICK_REG, item_inside->quantity); // register 24
update_screen_colour(ST77XX_BLACK,item_inside,device,encoder);
strcpy(item_inside->state,"DONE");
turn_ON_timer2();
if(item_inside->quantity <=10)
{
}
return;
}
void handle_not_active_device(item_inside_struct* item_inside, device_info* device, encoder_struct* encoder){
turn_OFF_timer();
LED_strip_ON(black,PixelCount);
update_screen_colour(ST77XX_BLACK,item_inside,device,encoder);
sensor_prev_state = HIGH;
}
Notice the read_sensor_no_delay() function only needs to know about item_inside_struct, but does not care about device_info or encoder_struct, but I still must pass those to the function. Further down in read_sensor_no_delay() function the other function is calledhandle_wrong_box(). This function will redraw the display and display the new data therefore it must know all 3 structures.
My tft.c:
#include "tft_custom.h"
int screen_colour = ST77XX_BLACK ;
int local_quantity = 0;
int local_number_to_pick = 0;
char local_eeprom_serial[10]="NONE";
char local_current_status[20] = "NONE";
int local_displaycounter = 0;
char local_device_id[10] = "NONE";
char local_device_version[10] = "NONE";
char local_mqtt_server[20] = "NONE";
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
void TFT_setup(){
tft.initR(INITR_BLACKTAB);
tft.setRotation(3);
tft.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
tft.fillScreen(ST77XX_BLACK);
}
void TFT_display_update(item_inside_struct* item_inside,device_info* device, encoder_struct* encoder){
tft.setTextSize(4);
if(local_quantity != item_inside->quantity)
{
tft.setCursor(0, 0);
tft.fillRect(0,0, 80, 32, screen_colour);
local_quantity = item_inside->quantity;
tft.print(local_quantity); //quantity inside
}
if(local_number_to_pick != item_inside->num_to_pick)
{
tft.setCursor(0,32);
tft.fillRect(0,32, 40, 32, screen_colour);
local_number_to_pick = item_inside->num_to_pick;
tft.print(local_number_to_pick); // number2 means number to_pick
}
tft.setTextSize(1);
if(strcmp(local_device_version,device->device_version)!= 0)
{
tft.setCursor(120,0);
tft.fillRect(120,0, 10, 10, screen_colour);
strcpy(local_device_version,device->device_version);
tft.print(local_device_version); // number2 means number to_pick
}
if(strcmp(local_mqtt_server,device->mqtt_server)!= 0)
{
tft.setCursor(80,10);
tft.fillRect(80,10, 40, 5, screen_colour);
strcpy(local_mqtt_server,device->mqtt_server);
tft.print(local_mqtt_server); // number2 means number to_pick
}
tft.setTextSize(2);
if(strcmp(local_device_id,device->DEVICE_ID)!= 0){
tft.setCursor(40,60);
tft.fillRect(40,60, 50, 16, screen_colour);
strcpy(local_device_id,device->DEVICE_ID);
tft.print(local_device_id);
}
if(strcmp(local_current_status,item_inside->state)!= 0){
tft.setCursor(40,32);
tft.fillRect(40,32, 120, 16, screen_colour);
strcpy(local_current_status,item_inside->state);
tft.print(local_current_status);
}
if(strcmp(local_eeprom_serial,item_inside->serial) !=0)
{
tft.setCursor(40,76);
tft.fillRect(40,76, 120, 16, screen_colour);
strcpy(local_eeprom_serial, item_inside->serial);
tft.print(local_eeprom_serial); //print SERIAL id
}
if(local_displaycounter != encoder->displaycounter)
{
tft.setCursor(40,94);
tft.fillRect(40,94, 60, 16, screen_colour);
local_displaycounter = encoder->displaycounter;
tft.print(local_displaycounter/10); //print SERIAL id
}
}
void TFT_display(item_inside_struct* item_inside,device_info* device, encoder_struct* encoder){
//tft.fillRect(0,0, 160, 80, ST77XX_BLACK);
tft.setTextSize(4);
tft.setCursor(0, 0);
tft.print(item_inside->quantity); //quantity inside
tft.setCursor(0,32);
tft.print(item_inside->num_to_pick); // number2 means number to_pick
tft.setTextSize(1);
tft.setCursor(120,0);
tft.print(device->device_version);
tft.setCursor(80,10);
tft.print(device->mqtt_server); // number2 means number to_pick
tft.setTextSize(2);
tft.setCursor(64,32);
tft.print(item_inside->state);
tft.setCursor(40,60);
tft.print(device->DEVICE_ID);
tft.setCursor(40,76);
tft.print(item_inside->serial); //print SERIAL id
tft.setCursor(40,94);
tft.print(encoder->displaycounter/10); //print SERIAL id
}
void update_screen_colour(int background_colour,item_inside_struct* item_inside, device_info* device, encoder_struct* encoder)
{
screen_colour=background_colour;
tft.setTextColor(ST77XX_WHITE, background_colour);
tft.fillScreen(background_colour);
TFT_display(item_inside,device,encoder);
}
Now I pretty much pass these 3 structs to every single function in my code because at some point tft update method will be called. Could someone point me in the right direction how this ugly code could be improved?
Trying to write tft class
I have rewritten my tft.c and tft.h and learned quite a bit in the process.
My tft.c :
include "tft_custom.h"
//class constructor, pass the 3 structures here
TFT::TFT(item_inside_struct &item_inside, device_info &device, encoder_struct &encoder){
Serial.println("calling tft class constructor");
screen_colour = ST77XX_BLACK ;
local_quantity = 0;
local_number_to_pick = 0;
local_displaycounter = 0;
strcpy(local_eeprom_serial,"NONE");
strcpy(local_current_status,"NONE");
strcpy(local_device_id,"NONE");
strcpy(local_device_version,"NONE");
strcpy(local_mqtt_server,"NONE");
class_item_inside = &item_inside;
class_device = &device;
class_encoder = &encoder;
}
void TFT::begin(){
tft.initR(INITR_BLACKTAB);
tft.setRotation(3);
tft.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
tft.fillScreen(ST77XX_BLACK);
}
void TFT::update_display_once(){
tft.setTextSize(4);
tft.setCursor(0, 0);
tft.print(class_item_inside->quantity); //quantity inside
tft.setCursor(0,32);
tft.print(class_item_inside->num_to_pick); // number2 means number to_pick
tft.setTextSize(1);
tft.setCursor(120,0);
tft.print(class_device->device_version);
tft.setCursor(80,10);
tft.print(class_device->mqtt_server); // number2 means number to_pick
tft.setTextSize(2);
tft.setCursor(64,32);
tft.print(class_item_inside->state);
tft.setCursor(40,60);
tft.print(class_device->DEVICE_ID);
tft.setCursor(40,76);
tft.print(class_item_inside->serial); //print SERIAL id
tft.setCursor(40,94);
tft.print(class_encoder->displaycounter/10); //print SERIAL id
}
void TFT::TFT_display_update(){
tft.setTextSize(4);
if(local_quantity != class_item_inside->quantity)
{
tft.setCursor(0, 0);
tft.fillRect(0,0, 80, 32, screen_colour);
local_quantity = class_item_inside->quantity;
tft.print(local_quantity); //quantity inside
}
if(local_number_to_pick != class_item_inside->num_to_pick)
{
tft.setCursor(0,32);
tft.fillRect(0,32, 40, 32, screen_colour);
local_number_to_pick = class_item_inside->num_to_pick;
tft.print(local_number_to_pick); // number2 means number to_pick
}
tft.setTextSize(1);
if(strcmp(local_device_version,class_device->device_version)!= 0)
{
tft.setCursor(120,0);
tft.fillRect(120,0, 10, 10, screen_colour);
strcpy(local_device_version,class_device->device_version);
tft.print(local_device_version); // number2 means number to_pick
}
if(strcmp(local_mqtt_server,class_device->mqtt_server)!= 0)
{
tft.setCursor(80,10);
tft.fillRect(80,10, 40, 5, screen_colour);
strcpy(local_mqtt_server,class_device->mqtt_server);
tft.print(local_mqtt_server); // number2 means number to_pick
}
tft.setTextSize(2);
if(strcmp(local_device_id,class_device->DEVICE_ID)!= 0){
tft.setCursor(40,60);
tft.fillRect(40,60, 50, 16, screen_colour);
strcpy(local_device_id,class_device->DEVICE_ID);
tft.print(local_device_id);
}
if(strcmp(local_current_status,class_item_inside->state)!= 0){
tft.setCursor(40,32);
tft.fillRect(40,32, 120, 16, screen_colour);
strcpy(local_current_status,class_item_inside->state);
tft.print(local_current_status);
}
if(strcmp(local_eeprom_serial,class_item_inside->serial) !=0)
{
tft.setCursor(40,76);
tft.fillRect(40,76, 120, 16, screen_colour);
strcpy(local_eeprom_serial, class_item_inside->serial);
tft.print(local_eeprom_serial); //print SERIAL id
}
if(local_displaycounter != class_encoder->displaycounter)
{
tft.setCursor(40,94);
tft.fillRect(40,94, 60, 16, screen_colour);
local_displaycounter = class_encoder->displaycounter;
tft.print(local_displaycounter/10); //print SERIAL id
}
}
void TFT::update_screen_colour(int background_colour)
{
screen_colour=background_colour;
tft.setTextColor(ST77XX_WHITE, background_colour);
tft.fillScreen(background_colour);
update_display_once();
}
and tft.h:
#ifndef TFT_CUSTOM_H
#define TFT_CUSTOM_H
#include "Adafruit_ST7735.h" // Hardware-specific library for ST7735
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "definitions.h"
#define TFT_CS 5 // Hallowing display control pins: chip select
#define TFT_RST 4 // Display reset
#define TFT_DC 2 // Display data/command select
#define TFT_MOSI 19 // Data out
#define TFT_SCLK 18 // Clock out
class TFT
{
private:
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
//create 3 pointers to the struct
item_inside_struct *class_item_inside;
device_info *class_device;
encoder_struct *class_encoder;
int screen_colour;
int local_quantity;
int local_number_to_pick;
char local_eeprom_serial[10];
char local_current_status[20];
int local_displaycounter;
char local_device_id[10] ;
char local_device_version[10];
char local_mqtt_server[20];
public:
TFT(item_inside_struct &item_inside, device_info &device, encoder_struct &encoder);
void begin();
void update_display_once();
void TFT_display_update();
void update_screen_colour(int background_colour);
};
#endif
In my main.c I create a class constructor and pass the 3 structures. Inside a class constructor, I create a pointers for all 3 structures and I use these 3 pointers throughout the whole class
my main.c:
TFT tft_object(item_inside, device, encoder);
void setup() {
Serial.begin(115200);
pinMode(SENSOR, INPUT); // sets the digital pin 13 as output
Expander_SETUP();
setup_leds();
tft_object.begin();
tft_object.update_display_once();
}
void loop() {
if(strncmp(item_inside.state, "PILDYMAS", 8) != 0)
{
Read_sensor_no_delay(tft_object);
}
Notice in my read_sensor_no_delay function, I pass this the tft object.
And inside the read_sensor function I want to be able to read and write to the class object structures as shown below:
void Read_sensor_no_delay(TFT &tft_object ){
sensor = digitalRead(SENSOR);
if(sensor==LOW && sensor_prev_state==HIGH)// SENSOR ON
{
if (strncmp(tft_object.class_item_inside->state,"ACTIVE",6)!=0){ // IF NUMBER TO PICK ITEMS IS 0, SET RED LIGHT TO SHOW OPERATOR WRONG BOX
handle_activation(tft_object);
}
else if(sensor==HIGH && sensor_prev_state==LOW) //SENSOR DEACTIVATED
{
handle_deactivation(tft_object);
}
}
Notice if (strncmp(tft_object.class_item_inside->state,"ACTIVE",6)!=0) inside read sensor function. However, I am getting an issue due to all the variables in my tft class declared as private. Is the only way to solve this issue declare the 3 pointers to the structure public?

There is no right answer of course. If you try to come up with one, a bunch of people will hop on and tell you why its wrong :-)
But, a few choices:
It's actually ok to have global variables. It makes it harder for people to understand the code and where values are coming from, etc, but it's technically not "wrong" (ie, it compiles so the compiler thinks its ok right?). You can also use goto statements as well, and that's considered "bad" by many as well.
Some code encapsulates local copies of variables they need to know about within static variables in a file. Then you can create an init_sensors() function that takes those three pointers and remembers them.
Another common mechanism, probably preferred by most in raw-C code, is to create a single pointer that encapsulates the three other structures and to pass that pointer around instead as the first argument to pretty much every function. This also future-proofs your code a bit in case you ever need to add a fourth. Something like:
struct foo_context {
item_inside_struct* item_inside;
device_info* device;
encoder_struct* encoder;
}
You're using C++ but much of the code feels more "C" like. Getting a better handle on the object oriented nature might be helpful as well and trying to encapsulate more of the functionality into the objects. IE, if the TFT* functions were wrapped inside a new TFT object, then you could create that object once, pass the pointers in during its constructor and have it memorize them while the class' instance is alive.

This is certainly not a complete answer, just a pointer towards separating responsibilities, which others mentioned already.
Let's see this part:
After the sensor is read, based on the sensor result, it may need to do update tft display hence I must pass all the structure data to the function
This smells bad. The code reading a sensor needs not know about the tft display. There may be multiple objects which would need to give some feedback or other reaction on the sensor's state change – displaying something, blinking some LED, beeping an acoustic signal, logging the event in some file, etc, etc. You just can't know all possible future objects which will need to be updated. And your code must not depend on classes representing those objects.
A solution can be a message broker – an object who knows different participants and transfers messages between them (see Message broker at Wikipedia). This can be simply an object of a main application class: the item_inside_struct can contain a pointer to an App object and that would be responsible for proper handling (i.e. dispatching) events to other devices.
Another approach may be an observer (see Observer pattern at Wikipedia) – all objects of different classes in your application, which may utilize changes of the sensor's state somehow, would register in the sensor class object (i.e. by appending themselves to the list) and will be notified whenever a change is recognized. Then each of them will produce appropriate response without being known in details to the sensor's class.

Related

How to get a input that continuously gives data to output a single digit integer value

To give a short summary of my project, it’s a smart parking system where I let a parking user know whether a parking lot is vacant or not. I’m implementing an XBee network containing 1 coordinator and 2 routers. I have two sensors, 1 sensor at the exit and 1 at the entrance. Those two sensors are the routers and whatever data they gather is being transmitted to the coordinator (output). The two routers have the same code which is:
INPUT CODE (TRANSMITTING):
#include<SoftwareSerial.h>
#define Sensor 8
void setup() {
pinMode(Sensor,INPUT);
Serial.begin(9600);
}
void loop()
{
bool Detection = digitalRead(Sensor);
int carexit=1;
if(Detection == HIGH)
{
Serial.println("Car Exit");
Serial.write(carexit);
}
if(Detection == LOW)
{
Serial.println("Clear");
}
}
It’s a very simple code where it detects a car coming in or out. Since the two routers are the same we’ll use the sensor for the cars exiting. When I open the serial monitor, the word “Clear” will keep on being outputted continuously nonstop until detection is found which will show an output of “Car Exit” for about 3 seconds and then return to “Clear” continuously. This data is being transmitted to the coordinator. What I’m trying to do is take that continuous data and have the serial monitor at the coordinator input a single integer value. For example, at the entrance, the sensor will sense the car and transmit the data to the coordinator where it'll increment. The result will show something like this if there’s only 1 vacant slot available:
Vacant slots: 1
and when the car exits the router at the exit, it will transmit a code to the coordinator decrementing it:
Vacant slots: 0
So, the input(router) would be transmitting continuous data while the output(transmitter) will detect it and just register it for a single-digit value. The output code(receiving) by the way is shown below:
OUTPUT CODE(RECEIVING):
#include "SoftwareSerial.h"
// RX: Arduino pin 2, XBee pin DOUT. TX: Arduino pin 3, XBee pin DIN
void setup()
{
Serial.begin(9600);
}
void loop()
{
if (Serial.available() > 0)
{
Serial.write(Serial.read());
}
}
The output code is pretty simple as well. Let me know if there’s any possible way to implement what I’m trying to do and if there are any other details I left out, let me know as well.
Thank you!
This is a very common problem, best solved at the source, in your sensor transmit code.
//...
bool PreviousDetection = false;
void loop()
{
bool Detection = digitalRead(Sensor);
// do not do anything when state hasn't changed.
if (Detection != PreviousDetection)
{
if (Detection)
Serial.println("Car Exit");
else
Serial.println("Clear");
}
PreviousDetection = Detection;
}
You may want to add some debouncing to reduce the risk of false readings.
//...
// Debounce thresholds the number depends on the frequency of readings,
// speeed of cars, sensitivity and placement of your sensor...
// Dirt, sun exposure, etc... may influence readings in the long term.
const int LowDebounceThreshold = 3;
const int HighDebounceThreshold = 3;
bool PreviousDetection = false;
int DebounceCounter = 0;
bool DebouncedPreviousDetection = false;
void loop()
{
bool Detection = digitalRead(Sensor);
//
if (Detection == PreviousDetection)
++DebounceCounter; // this will rollover, but will not affect
// DebouncedDetection in any meaningfull way.
else
DebounceCounter = 0;
PreviousDetection = Detection;
bool DebouncedDetection = PreviousDebouncedDetection;
if (Detection && DebounceCounter >= HighDebounceThreshold)
DebouncedDetection = true;
else if (!Detection && DebounceCounter >= LowDebounceThreshold)
DebouncedDetection = false;
if (DebouncedDetection != PreviousDebouncedDetection)
{
PreviousDebouncedDetection = DebouncedDetection;
if (DebouncedDetection)
Serial.println("Car Exit");
else
Serial.println("Clear");
}
}

How do I appropriately call constructors and pass objects around?

I have the following code, but something is wrong with it. It compiles, but nothing happens on the NeoPixel matrix. I've loaded up a strand test to verify that the hardware is working properly. Through some manual debugging, I've worked out that the line containing PixelArt art = PixelArt(matrix); triggers the issue. If I comment that out, I get a panel of orange light coming out of the light matrix as expected. Putting it back in simply results in darkness.
What about including that one line is causing issues? I'm new to C++ and this is driving me up the wall.
For some code context, I plan on including more modes, each with a series of submodes. PixelArt would show off different pixel art pictures as its sub modes. Other modes would include different animation patterns. The different modes should all share a reference to the same matrix object.
#include <Adafruit_NeoMatrix.h>
#include <gamma.h>
#define MODE_PIN 2
#define SUB_MODE_PIN 3
#define MATRIX_PIN 9
#define MATRIX_COLUMNS 16
#define MATRIX_ROWS 16
#define MATRIX_NUM_PIXELS (MATRIX_COLUMNS * MATRIX_ROWS)
class Mode {
public:
Mode(Adafruit_NeoMatrix &neomatrix) : matrix{&neomatrix} {}
virtual void toggleSubMode() = 0;
virtual void update(uint64_t timestamp) = 0;
protected:
Adafruit_NeoMatrix * const matrix;
};
class PixelArt : public Mode {
public:
PixelArt(Adafruit_NeoMatrix &neomatrix):Mode(neomatrix) {}
void toggleSubMode();
void update(uint64_t timestamp);
};
void PixelArt::toggleSubMode() {
// Stubbed.
}
// Green: 0x80c000
// Brown: 0xc04000
// Tan: 0xffa040
uint32_t link[16][16] = { [REDACTED FOR BREVITY] };
void PixelArt::update(uint64_t timestamp) {
matrix->clear();
for (uint16_t row = 0; row < 16; row++) {
for (uint16_t column = 0; column < 16; column++) {
matrix->setPassThruColor(link[row][column]);
matrix->drawPixel(column, row, 0);
matrix->setPassThruColor();
}
}
matrix->show();
}
//
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
MATRIX_COLUMNS,
MATRIX_ROWS,
MATRIX_PIN,
NEO_MATRIX_TOP | NEO_MATRIX_LEFT | NEO_MATRIX_COLUMNS | NEO_MATRIX_ZIGZAG,
NEO_GRB | NEO_KHZ800
);
PixelArt art = PixelArt(matrix);
void setup() {
matrix.begin();
matrix.show();
matrix.setBrightness(30); // Max ~80
}
uint8_t mode = 0;
void loop() {
// art.update(millis());
matrix.fillScreen(matrix.Color(255, 127, 0));
matrix.show();
}
My suggestion would be that because the code runs on a microcontroller, it may run out of memory. Especially if the target board is an Arduino Uno or similar, with small dynamic memory. After compilation, the memory usage calculation is done for compile-time variables. The run-time variables and function calls may exceed the available memory.
Compiled for an Arduino Uno, the compiler calculates a 1204 bytes (58%) of dynamic memory usage with PixelArt object. Without it is 168 bytes (8%).
Try reducing the inheritance and function call levels.

Using Wire.onRequest by passing a class method?

I have an Arduino sketch that will be working on an Arduino UNO and I am trying to get uno to communicate over the i2c connection with a raspberry pi.
Problem is using wire.h library where method Wire.onRequest is working just fine when I use it like this.
#include <Wire.h>
#define COMM_DELAY 50
#define SLAVE_ADDRESS 0x04
int current_rule = 0;
void initI2c() {
// initialize i2c as slave
Wire.begin(SLAVE_ADDRESS);
// define callbacks for i2c communication
Wire.onReceive(receiveData);
}
// callback for received data
void receiveData(int byteCount) {
while (Wire.available()) {
current_rule = Wire.read();
}
}
but when I try to make this exact result with a class method, I get an error :
invalid use of non-static member function
(with Wire.onRequest(this->receiveData) line gets to be marked red)
Just like this:
void (*funptr)();
typedef void (*Callback)(byte);
class Comm{
public:
int callback_list_size = 0;
bool option_debug;
byte option_address;
int option_comm_delay;
void(*callback_list[256]);
byte *rules;
// function for receiving data. raspberry -> arduino
// Whenever the master sends new data, this method will call the appropriate callback.
void receiveData()
{
byte data;
Serial.println("[INFO] Received new data from master");
while (Wire.available())
{
data = Wire.read();
}
for (int i = 0; i < callback_list_size; i++)
{
if (rules[i] == data){
funptr = callback_list[i];
funptr();
}
}
}
// function for sending data. Called when raspberry request data. arduino -> raspberry
// Whenever the master requests data, this method will be called. For now we don't need this but anyway.
void sendData(int s)
{
if (option_debug)
Serial.println("[INFO] Master requests data!");
}
/* Constructor that takes 3 parameters at max. Only the adress is mandatory others are optional and will be filled with default values
:address - adress of slave(arduino) - Example 0x04
:delay - a delay is needed because I2C clock is quite slow compared to the CPU clock - 50
:debug - for debug purposes if true debug info will be sent to Serial interface - true/false
*/
Comm(byte address, int delay = 50, bool debug = false)
{
option_address = address;
option_comm_delay = delay;
option_debug = debug;
if (debug)
Serial.println("[INFO] Comm Object Created!");
}
// Function needs to be called to initialize the communication channel.
void initI2c()
{
Wire.begin(option_address);
Wire.onReceive(this->sendData);
Wire.onRequest(this->receiveData);
if (option_debug)
Serial.println("[INFO] I2C channel initialized");
}
// Function to add new callback for a rule.
// This function returns id of passed callback
int addCallback(Callback func, byte rule)
{
callback_list_size++;
// Enlarge rules array to keep 1 more byte
byte *temp = new byte[callback_list_size]; // create new bigger array.
for (int i = 0; i + 1 < callback_list_size; i++) // reason fo i+1 is if callback_list_size is 1 than this is the first initializition so we don't need any copying.
{
temp[i] = rules[i]; // copy rules to newer array.
}
delete[] rules; // free old array memory.
rules = temp; // now rules points to new array.
callback_list[callback_list_size - 1] = &func;
rules[callback_list_size - 1] = rule;
return callback_list_size;
}
};
Comm *i2c_comm;
void loop()
{
}
void setup()
{
Serial.begin(9600);
initI2C();
}
void initI2C()
{
i2c_comm = new Comm(0x04, 50, true);
i2c_comm->initI2c();
//Callback Definitions
i2c_comm->addCallback(&rule_1, 0x01);
i2c_comm->addCallback(&rule_2, 0x02);
i2c_comm->addCallback(&rule_3, 0x03);
i2c_comm->addCallback(&rule_4, 0x04);
}
I also tried to make the receiveData method to be static.
But in this case I have an error like this:
invalid use of member Com::callback_list_size in static member function
which makes sense to me as static method won't know which callback_list_size I am talking about.
so I am quite confused about how I can handle such a problem?
You're almost there. Generally speaking in C++ you need to pass a static class method for callback functions.
The error you received after changing your method to static is expected as you're trying to access a member of an instance of the class Comm which cannot be done in a static method in which there is no 'this'.
Here's one of many techniques to consider, but please read over the SO post Using a C++ class member function as a C callback function.
Anyway the approach here is to leverage a static pointer to an instance.
class Comm {
private:
static Comm* pSingletonInstance;
static void OnReceiveHandler() {
if (pSingletonInstance)
pSingletonInstance->receiveData();
}
static void OnSendHandler(int s) {
if (pSingletonInstance)
pSingletonInstance->sendData(s);
}
void initI2c() {
Comm::pSingletonInstance = this; // Assign the static singleton used in the static handlers.
Wire.onReceive(Comm::OnSendHandler);
Wire.onRequest(Comm::OnReceiveHandler);
Wire.begin(option_address);
}
}
// static initializer for the static member.
Comm* Comm::pSingletonInstance = 0;
Again there are many ways to get around this issue but above is an easy one and likely suitable for your project. If you need to manage multiple instances of Comm, you'll have to do something quite different.
Good luck!

C++ referencing instances created within a function's scope

Context
The context of the problem is that I am currently writing a small library for use with the Arduino in order to act as a game controller. The problem I am encountering has more to do with C++ than anything Arduino specific however.
I've included the libraries' header and source code below, followed by the Arduino code. I've truncated it where possible.
Problem
In short, only the last switch / action I define actually gets properly handles.
These actions get defined in the Arduino setup function. For example:
controller.addSwitchContinuous(10, 0); // Pin 10; btn index 0
means that pin 10 gets mapped to button 0. When pin 10 is switched closed this is treated as the button being pressed. This works fine for a single action but when I start adding more only the last action actually works. So in the following example only pin 9 is recognized:
controller.addSwitchContinuous(10, 0); // <-- Doesn't work
controller.addSwitchContinuous(9, 1); // <-- Works
This goes for any arbitrary number of actions:
controller.addSwitchContinuous(10, 0); // <-- Doesn't work
controller.addSwitchContinuous(9, 1); // <-- Doesn't work
controller.addSwitchContinuous(8, 2); // <-- Doesn't work
controller.addSwitchContinuous(7, 3); // <-- Works
Potential causes
I am fairly novice with C++ so this I suspect I'm doing something wrong with pointers. More specifically, something seems wrong with how the Joystick_ instance gets passed around.
I have been fiddling with the constructor and trying to use references instead of pointers but I couldn't get it to work properly.
I can confirm the iteration in JFSF::loop does iterate over all actions, if I modify it with:
void JFSF::loop()
{
for (int n = 0; n < _nextActionIndex; n++)
{
if (_actions[n])
{
_actions[n]->loop();
_joystick->setButton(n, PRESSED); // Debug: Set button pressed, regardless of switch.
}
}
if (_doSendState)
{
_joystick->sendState();
}
}
then buttons 0 through n get pressed as expected. It is possible that loop() isn't properly being called, but I would expect it to fail for the N = 1 case as well in that case. Furthermore the fact the last action always succeeds would suggest the iteration is ok.
Full code
// JFSF.h
#ifndef JFSF_h
#define JFSF_h
// ... include for Arduino.h and Joystick.h; bunch of defines
namespace JFSF_PRIV
{
class AbstractAction
{
public:
virtual void loop();
};
/* A Switch that essentially acts as a push button. */
class SwitchContinuousAction : public AbstractAction
{
public:
SwitchContinuousAction(Joystick_ *joystick, int pin, int btnIndex);
void loop();
private:
Joystick_ *_joystick;
int _pin;
int _btnIndex;
};
} // namespace JFSF_PRIV
class JFSF
{
public:
JFSF(Joystick_ *joystick, bool doSendState); // doSendState should be true if Joystick_ does not auto send state.
void loop();
void addSwitchContinuous(int inputPin, int btnIndex);
private:
Joystick_ *_joystick;
JFSF_PRIV::AbstractAction *_actions[MAX_ACTIONS];
int _nextActionIndex;
bool _doSendState;
};
#endif
Source file (trimmed):
// JFSF.cpp
#include "Arduino.h"
#include "Joystick.h"
#include "JFSF.h"
#define PRESSED 1
#define RELEASED 0
// Private classes
namespace JFSF_PRIV
{
SwitchContinuousAction::SwitchContinuousAction(Joystick_ *joystick, int pin, int btnIndex)
{
_joystick = joystick;
_pin = pin;
_btnIndex = btnIndex;
pinMode(_pin, INPUT_PULLUP);
}
void SwitchContinuousAction::loop()
{
int _state = digitalRead(_pin) == LOW ? PRESSED : RELEASED;
_joystick->setButton(_btnIndex, _state);
}
} // namespace JFSF_PRIV
JFSF::JFSF(Joystick_ *joystick, bool doSendState)
{
_joystick = joystick;
_nextActionIndex = 0;
_doSendState = doSendState;
}
void JFSF::addSwitchContinuous(int inputPin, int btnIndex)
{
JFSF_PRIV::SwitchContinuousAction newBtnAction(_joystick, inputPin, btnIndex);
_actions[_nextActionIndex++] = &newBtnAction;
}
void JFSF::loop()
{
for (int n = 0; n < _nextActionIndex; n++)
{
if (_actions[n])
{
_actions[n]->loop();
}
}
if (_doSendState)
{
_joystick->sendState();
}
}
For completeness sake, this is the code for the Arduino, but it is pretty much just declarations:
#include <JFSF.h>
// ... A bunch of const declarations used below. These are pretty self explanatory.
// See: https://github.com/MHeironimus/ArduinoJoystickLibrary#joystick-library-api
Joystick_ joystick(HID_REPORT_ID,
JOYSTICK_TYPE_JOYSTICK, // _JOYSTICK, _GAMEPAD or _MULTI_AXIS
BTN_COUNT, HAT_SWITCH_COUNT,
INCLUDE_X_AXIS, INCLUDE_Y_AXIS, INCLUDE_Z_AXIS,
INCLUDE_RX_AXIS, INCLUDE_RY_AXIS, INCLUDE_RZ_AXIS,
INCLUDE_RUDDER, INCLUDE_THROTTLE,
INCLUDE_ACCELERATOR, INCLUDE_BRAKE, INCLUDE_STEERING);
JFSF controller(&joystick, !DO_AUTO_SEND_STATE);
void setup() {
joystick.begin(DO_AUTO_SEND_STATE);
controller.addSwitchContinuous(10, 0); // <-- Doesn't work
controller.addSwitchContinuous(9, 1); // <-- Works
}
void loop() {
controller.loop();
}
References
ArduinoJoystickLibrary (Source for Joystick_) can be found here: https://github.com/MHeironimus/ArduinoJoystickLibrary#joystick-library-api
I dont really understand your code. Please read How to create a Minimal, Complete and Verifiable example. Anyhow, the following is certainly wrong and likely the cause of your problem:
void JFSF::addSwitchContinuous(int inputPin, int btnIndex)
{
JFSF_PRIV::SwitchContinuousAction newBtnAction(_joystick, inputPin, btnIndex);
_actions[_nextActionIndex++] = &newBtnAction;
}
Lets rewrite it a bit for clarity:
void foo(){
T bar;
container[index] = &bar;
}
What happens here is that bar gets destroyed when it goes out of scope, hence the pointer you put into the container, points to garbage. Presumably somewhere else in your code you are dereferencing those pointers, which is undefined behaviour (aka anything can happen).
Long story short: It is a common pattern among c++ beginners to overuse pointers. Most likely you should make container a container of objects rather than pointers and make use of automatic memory managment instead of trying to fight it.
Thanks to #user463035818 and #drescherjm for identifiying the actual problem.
So in the end I fixed it by simply moving the Action object creation up to the Arduino code (where it's essentially global) and passing references to those objects to the controller.
In code this translates to:
JFSF.cpp
void JFSF::addAction(JFSF_PRIV::AbstractAction *action){
_actions[_nextActionIndex++] = action;
}
Arduino code (ino)
// See code in original post
JFSF controller(&joystick, !DO_AUTO_SEND_STATE);
JFSF_PRIV::SwitchContinuousAction btnOne(&joystick, 10, 0);
JFSF_PRIV::SwitchContinuousAction btnTwo(&joystick, 9, 1);
void setup() {
joystick.begin(DO_AUTO_SEND_STATE);
// controller.addSwitchContinuous(10, 0); // Pin 10; btn index 0
// controller.addSwitchContinuous(9, 1); // Pin 9 ; btn index 1
controller.addAction(&btnOne);
controller.addAction(&btnTwo);
}
// loop() is unchanged

How to write function for multiple analog pins? (arduino)

So I'm writing this little function for some pot pins. The pot sends a value only when its being turned, at rest, it sends nothing. Which is how I want it to function.
It works fine with one pin.
I've gotten it to a point where it half works with multiple pins. So if I call it twice in the loop with two pins, I get back the right values on both those pins. But I loose the functionality of the if statement. Basically I can't figure out the last half of this. Arrays have been suggested I'm just unsure of how to proceed.
Suggestions? Thank you.
byte pots[2] = {A0, A2};
int lastPotVal = 0;
void setup(){
Serial.begin(9600);
}
void loop(){
// get the pin out of the array
rePot(pots[0]);
rePot(pots[1]);
delay(10);
}
void rePot(const int potPin){
// there is probably an issue around here somewhere...
int potThresh = 2;
int potFinal = 0;
int potVal = 0;
// set and map potVal
potVal = (analogRead(potPin));
potVal = map(potVal, 0, 664, 0, 200);
if(abs(potVal - lastPotVal) >= potThresh){
potFinal = (potVal/2);
Serial.println(potFinal);
lastPotVal = potVal;
} // end of if statement
} // end of rePot
This uses a struct to mange a pot and the data associated with it (the pin it's on, the last reading, threshold, etc). Then, the rePot() function is changed to take one of those structs as input, instead of just the pin number.
struct Pot {
byte pin;
int threshold;
int lastReading;
int currentReading;
};
// defining an array of 2 Pots, one with pin A0 and threshold 2, the
// other with pin A2 and threshold 3. Everything else is automatically
// initialized to 0 (i.e. lastReading, currentReading). The order that
// the fields are entered determines which variable they initialize, so
// {A1, 4, 5} would be pin = A1, threshold = 4 and lastReading = 5
struct Pot pots[] = { {A0, 2}, {A2, 3} };
void rePot(struct Pot * pot) {
int reading = map(analogRead(pot->pin), 0, 664, 0, 200);
if(abs(reading - pot->lastReading) >= pot->threshold) {
pot->currentReading = (reading/2);
Serial.println(pot->currentReading);
pot->lastReading = reading;
}
}
void setup(){
Serial.begin(9600);
}
void loop() {
rePot(&pots[0]);
rePot(&pots[1]);
delay(10);
}
A slightly different take on this is to change rePot() into a function that takes the whole array as input, and then just updates the whole thing. Like this:
void readAllThePots(struct Pot * pot, int potCount) {
for(int i = 0; i < potCount; i++) {
int reading = map(analogRead(pot[i].pin), 0, 664, 0, 200);
if(abs(reading - pot[i].lastReading) >= pot[i].threshold) {
pot[i].currentReading = (reading/2);
Serial.println(pot[i].currentReading);
pot[i].lastReading = reading;
}
}
}
void loop() {
readAllThePots(pots, 2);
delay(10);
}