I'm working on a small HTTP server. I am building a router and since there could be quite a few routes, I wanted to put them into flash memory so that I don't have to use the valuable SRAM. However either I don't understand something correctly or something weird is happening since I can't seem to be able to read back my stored data from flash.
I have a struct which contains a function pointer and a char pointer. I want to store an array of these structs into flash and read them back. However with a small debug print I can see I can't read back the char pointer correctly. It prints garbish to the serial port.
Here is a small example.
#include <avr/pgmspace.h>
typedef struct {
void (*func)();
const char *URI;
} Route;
void test1() {
Serial.println("Executed testfunc1");
}
void test2() {
Serial.println("Executed testfunc2");
}
const char route1URI[] PROGMEM = "/route1";
const Route route1 PROGMEM = {
test1,
route1URI
};
const char route2URI[] PROGMEM = "/route2";
const Route route2 PROGMEM = {
test2,
route2URI
};
const Route routingTable[] PROGMEM = {
route1,
route2
};
void (*getRoute(char *URI))() {
Route *r = (Route *)pgm_read_word(routingTable + 0);
char *f = (char *)pgm_read_word(r->URI);
Serial.println(f);
return r->func;
}
void setup() {
Serial.begin(9600);
while (!Serial) { }
Serial.println("started setup");
void (*fn)() = getRoute("sometest");
// will cause errors if called
//fn();
Serial.println("ended setup");
}
void loop() {
// put your main code here, to run repeatedly:
}
The PROGMEM is not that easy to use. And it can be little bit simplified:
#include <avr/pgmspace.h>
struct Route {
void (*func)();
const char *URI;
};
void test1() {
Serial.println(F("Executed testfunc1")); // if you are using progmem, why not for string literals?
}
void test2() {
Serial.println(F("Executed testfunc2"));
}
const char route1URI[] PROGMEM = "/route1";
const char route2URI[] PROGMEM = "/route2";
const Route routingTable[] PROGMEM = {
{test1,route1URI},
{test2,route2URI}
};
void (*getRoute(char *URI))() {
Route r;
memcpy_P((void*)&r, routingTable, sizeof(r)); // read flash memory into the r space. (can be done by constructor too)
Serial.println((__FlashStringHelper*)r.URI); // it'll use progmem based print
// for comparing use: strcmp_P( URI, r.URI)
return r.func; // r.func is already pointer to the function
}
void setup() {
Serial.begin(57600);
while (!Serial) { }
Serial.println("started setup");
void (*fn)() = getRoute("sometest");
// will cause errors if called
//fn();
Serial.print((uint16_t)test1, HEX); Serial.print(' ');
Serial.print((uint16_t)test2, HEX); Serial.print(' ');
Serial.println((uint16_t)fn, HEX);
Serial.println("ended setup");
}
void loop() {
// put your main code here, to run repeatedly:
}
I suppose route1 and route2 might cause all the troubles as it was used for copy into the routingTable. If you initialize elements of routingTable as I did, it works much better. And also getRoute was broken a lot.
Anyway, if you have flash string, you can use also String str {(__FlashStringHelper*)r.URI}; and then use compare operator: str == URI:
#include <avr/pgmspace.h>
// get size of array[]
template<typename T, int size> int GetArrLength(T(&)[size]){return size;}
struct Route {
void (*func)();
const char *URI;
};
void test1() {
Serial.println(F("Executed testfunc1")); // if you are using progmem, why not for string literals?
}
void test2() {
Serial.println(F("Executed testfunc2"));
}
void test3() {
Serial.println(F("Executed testfunc3"));
}
const char route1URI[] PROGMEM = "/route1";
const char route2URI[] PROGMEM = "/route2";
const char route3URI[] PROGMEM = "/route3";
const Route routingTable[] PROGMEM = {
{test1,route1URI},
{test2,route2URI},
{test3,route3URI}
};
void (*getRoute(char *URI))() {
for (int8_t i = 0; i < GetArrLength(routingTable); ++i) {
Route r;
memcpy_P((void*)&r, routingTable+i, sizeof(r)); // read flash memory into the r space. (can be done by constructor too)
String uri {(__FlashStringHelper*)r.URI};
if (uri == URI) {
return r.func; // r.func is already pointer to the function
}
}
return nullptr;
}
void setup() {
Serial.begin(57600);
while (!Serial) { }
Serial.println("started setup");
void (*fn)() = getRoute("/route3");
// will cause errors if called
//fn();
Serial.print((uint16_t)test1, HEX); Serial.print(' ');
Serial.print((uint16_t)test2, HEX); Serial.print(' ');
Serial.print((uint16_t)test3, HEX); Serial.print(' ');
Serial.println((uint16_t)fn, HEX);
Serial.println("ended setup");
}
char *f = (char *)pgm_read_word(r->URI);
Serial.println(f);
f is a pointer to a character array in PROGMEM, but Serial.println doesn't know that! It ends up trying to read the string from RAM, where it isn't.
The Arduino Serial library doesn't appear to support strings in PROGMEM. You will need to loop over the string printing one character at a time, use another library, or store the string in RAM.
As pointed by #KIIV, it's better to specify Route directly inside the declaration of routingTable. As an alternative solution, you could redefined the struct Route to
typedef struct {
void (*func)();
char URI[16]; //adjust the size to your need
} Route;
In this way, reading both URI and function address from flash can be done by single call to memcpy_P. The complete codes:
typedef struct {
void (*func)();
char URI[16]; //adjust the size to your need
} Route;
void test1() {
Serial.println("Executed testfunc1");
}
void test2() {
Serial.println("Executed testfunc2");
}
const Route routingTable[] PROGMEM = {
{test1, "/route1"},
{test2, "/route2"}
};
void (*getRoute(char *URI, int idx))() {
Route r;
memcpy_P(&r, &routingTable[idx], sizeof(Route));
Serial.print(idx); Serial.println(". -----------------------------");
Serial.print("Route: "); Serial.println(r.URI);
Serial.print("fn address: "); Serial.println((uint16_t)r.func, HEX);
Serial.print("test1 address: "); Serial.println((uint16_t)test1, HEX);
Serial.print("test2 address: "); Serial.println((uint16_t)test2, HEX);
return r.func;
}
void setup() {
Serial.begin(9600);
while (!Serial) { }
Serial.println("started setup");
void (*fn)();
const int n = sizeof(routingTable) / sizeof(Route);
for (int i = 0; i < n; i++) {
fn = getRoute("sometest", i);
fn();
}
Serial.println("ended setup");
}
void loop() {
// put your main code here, to run repeatedly:
}
Related
I am creating a project using PlatformIO and a Nodemcuv2 micro-controller.
I have written a class for serial communication SerialCommunicationHandler. This class ICommunicationHandler implements a Interface. See the code below.
ICommunicationHandler.h
class ICommunicationHandler {
public:
virtual void sendTemperature(float temp) = 0;
virtual void receiveData() = 0;
virtual void update() = 0;
protected:
virtual void parseData() = 0;
virtual void showParsedData() = 0;
};
SerialCommunicationHandler
headerfile
#include "ICommunicationHandler.h"
class SerialCommunicationHandler : public ICommunicationHandler {
private:
//atributes needed for storing and modifying incoming data.
static char incomingData[6]; //char array to temporarily store incoming data.
static char receivedData[6]; //char array to copy incoming data to.
static unsigned int messagePosition; //index of the incomingData array.
bool receiving;
bool newData;
void parseData() override;
void receiveData() override;
void showParsedData() override;
public:
explicit SerialCommunicationHandler();
void sendTemperature(float temp) override;
void update() override;
};
.cpp
#include <Arduino.h>
#include "SerialCommunicationHandler.h"
SerialCommunicationHandler::SerialCommunicationHandler() {
messagePosition = 0;
receiving = false;
newData = false;
}
void SerialCommunicationHandler::receiveData() {
//check if there are bytes in the serial buffer.
while (Serial.available() > 0){
char inByte = Serial.read();
//check if the byte is a starting or ending character;
switch (inByte) {
case '<':
//start receiving characters
receiving = true;
break;
case '>':
//stop receiving and parse the incoming data.
receiving = false;
newData = true;
strcpy(receivedData, incomingData); //copy incoming data into receivedData for further parsing.
memset(incomingData, 0, sizeof(incomingData)); //resetting incomingData.
messagePosition = 0;
break;
default:
if (receiving) {
incomingData[messagePosition] = inByte; //add incoming byte to array.
messagePosition++;
}
break;
}
}
}
void SerialCommunicationHandler::parseData() {
if (newData) {
showParsedData();
}
newData = false;
}
void SerialCommunicationHandler::showParsedData() {
Serial.println(receivedData);
}
void SerialCommunicationHandler::sendTemperature(float temp) {
Serial.println(temp);
}
void SerialCommunicationHandler::update() {
receiveData();
parseData();
}
When building I get multiple undefined refernce errors:
*/ld.exe: .pio\build\nodemcuv2\src\SerialCommunicationHandler.cpp.o:(.text._ZN26SerialCommunicationHandler14s
howParsedDataEv+0x0): undefined reference to `_ZN26SerialCommunicationHandler12receivedDataE`
*/ld.exe: .pio\build\nodemcuv2\src\SerialCommunicationHandler.cpp.o:(.text._ZN26SerialCommunicationHandlerC2E
v+0x4): undefined reference to `_ZN26SerialCommunicationHandler15messagePositionE'
*/ld.exe: .pio\build\nodemcuv2\src\SerialCommunicationHandler.cpp.o:(.text._ZN26SerialCommunicationHandler11r
eceiveDataEv+0x0): undefined reference to `_ZN26SerialCommunicationHandler12incomingDataE'
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\nodemcuv2\firmware.elf] Error 1
I have checked my code multiple times for syntax errors or misspelling but have found nothing. My IDE doesnt bring up any errors as well. Any information on what might be causing the undefined reference error is welcome.
headerfile
#include "ICommunicationHandler.h"
class SerialCommunicationHandler : public ICommunicationHandler {
private:
//atributes needed for storing and modifying incoming data.
static char incomingData[6]; //char array to temporarily store incoming data.
static char receivedData[6]; //char array to copy incoming data to.
static unsigned int messagePosition; //index of the incomingData array.
// ... etc
These are only declarations of the static member variables. You also have to define them in the cpp file, just like you do with the member functions:
.cpp file:
void SerialCommunicationHandler::showParsedData() {
Serial.println(receivedData);
}
// static data members
char SerialCommunicationHandler::incomingData[6] = {};
char SerialCommunicationHandler::receivedData[6] = {};
unsigned int SerialCommunicationHandler::messagePosition = 0;
I am having some difficulties with a bluetooth/oled display displaying the right string. The goal is to read data from bluetooth, update a class variable(Music.setArtist()), and then read from that variable later(Music.getArtist()) to draw it using a draw text function.
If i only call drawText once, it works fine. More than one calls in a loop to drawText cause some undefined behavior though, which is usually the second pointer getting overwritten. This causes the pointer in drawText to be null, or some random characters, or something.
This is my music object.
#ifndef Music_h
#define Music_h
class Music {
private:
char *track,*artist,*length, *position;
int progressBar;
bool playing;
public:
Music(char* t, char* a, char* l, char* po, bool pl, int p): track(t), artist(a), length(l), position(po), playing(pl), progressBar(p){};
~Music(){};
char* getLength(){return length;}
char* getPosition(){return position;}
char* getArtist(){return artist;}
char* getTrack(){return track;}
bool getPlaying(){return playing;}
int getProgressBar(){return progressBar;}
void setLength(char * l){length = l;}
void setPosition(char *p){position = p;}
void setArtist(char *a){artist = a;}
void setTrack(char *t){track = t;}
void setPlaying(bool p){playing = p;}
void setProgressBar(int p){progressBar = p;}
};
#endif
This is my drawText function
void Display_obj::drawText(double xPos, double yPos,char str[], int stringSize, uint8_t asciiBuff)
{
bitmapLetter fnt_controller(0,0,0x00,0,0);
bitmapLetter sheldon_alph[0x5A];
fnt_controller.createDictionary(sheldon_alph,6,8);
int startX = xPos;
int startY = yPos;
for (int i=0; i < stringSize; i++){
char charAt = str[i];
uint8_t ascii = (uint8_t)charAt;
if (ascii > 0x60)
{
charAt = charAt & ~(0x20);
ascii = ascii & ~(0x20);
}
int width = sheldon_alph[ascii-asciiBuff].getWidth();
int height = sheldon_alph[ascii-asciiBuff].getHeight();
size_t siz = sheldon_alph[ascii-asciiBuff].getSize();
unsigned char* bitmap = sheldon_alph[ascii-asciiBuff].getLetter();
drawBitmap(startX,startY,width,height,bitmap,siz);
startX = startX - 8;
if (startX <= 0)
{
startX = xPos;
startY = startY+8;
}
}
}
and my main loop looks something like this
Music music("", "", "", "", false, 0);
RideTracking ride("", "", "", "", "", false);
Navigation nav("", "", "", "", "", false);
void loop(){
if (bt.getDataUpdated() == false)
{
bt.recvWithEndMarker(&bt,&music);
}
else
{
//Serial.println(music.getTrack());
musicUpdateTrack(music.getTrack());
//Serial.println(music.getArtist()); --> This returns gibberish. If the above update is commented out, it works.
musicUpdateArtist(music.getArtist());
bt.setDataUpdated(false);
}
}
Ive tried everything i can think of, which was a lot of messing around with pointers and addresses to see if it was allocated correctly. This is the closest ive gotten, but it seems like the drawText breaks the rest of the char pointers i call in the future. I dont believe the issue has to do with flash or SRAM, as both seem to be within normal values. Any help would be greatly appreciated.
EDIT: in my bluetooth object, there are two calls. one is receive the data, and the other updates the object. I originally didnt include this as it looks like data is set correctly. if i dont call drawtext, but just call print statements, the data is correct.
void blue::updateData(Music* music) {
if (getDataUpdated() == true) {
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, receivedChars);
else{
char s1[55];
char s2[55];
char s3[15];
char s4[15];
char s5[15];
if(doc["music"]) {
strlcpy(s1, doc["music"]["track"]);
music->setTrack(s1);
strlcpy(s2, doc["music"]["artist"]);
music->setArtist(s2);
strlcpy(s3, doc["music"]["track_length"]);
music->setLength(s3);
strlcpy(s4, doc["music"]["position"]);
music->setPosition(s4);
music->setProgressBar(doc["music"]["progressBar"]);
music->setPlaying(doc["music"]["playing"]);
}
}
void blue::recvWithEndMarker(blue* bt,Music* mu) {
char rc;
while (ble.available() > 0 && getDataUpdated() == false) {
rc = ble.read();;
if (rc != endMarker)
{
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars)
{
ndx = numChars - 1;
}
}
else
{
brackets++;
if(brackets != 2)
{
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars)
{
ndx = numChars - 1;
}
}
else
{
receivedChars[ndx] = rc;
receivedChars[ndx+1] = '\0'; // terminate the string
ndx = 0;
brackets = 0;
setDataUpdated(true);
}
}
}
bt->updateData(mu);
}
Found it! In the music class, i changed the variables such as *track, to a hardset track[50]. Then, in the set method, i changed it to
void setLength(char * l){
strlcpy(length,l,strlen(l)+1);
}
void setPosition(char *p){
strlcpy(position,p,strlen(p)+1);
}
void setArtist(char *a){
strlcpy(artist,a,strlen(a)+1);
}
void setTrack(char *t){
strlcpy(track,t,strlen(t)+1);
}
Along with a few changes in how its passed, it now works. Thanks! Ive been stuck on this for days.
I wrote a class for Aduino, for reading an ads124x, and I am having one major problem, I can't call a function defined by the class. Here is how it is structured:
.ino calls functions using . operator
.h contains register map and class definition
.cpp contains all functions of the class
so far here is what is happening the .ino is successfully calling a function in .cpp, but when that same function calles another in .cpp it fails to call.
.ino:
#include "ADS124X.h"
void setup(){
ADS124X ADS124X(1,2,3,4);
Serial.begin(9600);
ADS124X.setUP(0x20, 0x20);}
.h:
class ADS124X{
public:
void reset(void);
void setUP(unsigned char* mux1, unsigned char* sys0);
private:
void SPI_Write(unsigned char* data, unsigned char size);
}
.cpp:
void ADS124X::setUP(unsigned char * mux1, unsigned char * sys0)
{
Serial.println("hi"); //prints this
delay(1);
reset(); // stops here
Serial.println("hi"); // doesn't print this
delay(1);
stopDataCont();
delay(210);
setREG(MUX1, mux1, 1);
setREG(SYS0, 0x01, 1);
delay(1);
}
void ADS124X::reset(void)
{
unsigned char dataToSend[] = { RESET };
START_HIGH;
CS_LOW;
Serial.println(RESET); // prints this as 0x06 (correct value)
SPI_Write(dataToSend, 1); // Seems to stop here
START_LOW;
CS_HIGH;
}
void ADS124X::SPI_Write(unsigned char * data, unsigned char size)
{
Serial.println("SPI_Write"); //prints this
for (unsigned int i = 0; i < size; i++) {
Serial.println("SPI_Write"); //prints this
Serial.println(* data); //prints this as 126 (if RESET is 3 * data becomes 189...)
SPI.transfer(*data);
Serial.println("SPI_Write");
data++;
}
}
This is not right...
Serial.println(RESET); // println expects a null terminated string
// you are sending a char.
You should define dataToSend as a null-terminated char array.
void ADS124X::reset(void)
{
char dataToSend[] = { RESET, 0 };
// ...
serial.println(dataToSend); // maybe println "reset" would be better?
SPI_Write(dataToSend, 1);
//...
}
Don't expect to see a nice 0x06 on your serial monitor, since that is not a printable character.
I'm writing a class to save data to the EEPROM of an Arduino.
The class is called Memory.
The class contains different functions and variabeles.
char serverdefault[15] = "0032484716340";
int pricedefault = 30;
void Memory::FactoryReset()
{
TotalSold = 0;
TotalCash = 0;
Sold = 0;
Cash = 0;
Items = 0;
EEPROM_writeAnything(10, TotalSold);
EEPROM_writeAnything(20, TotalCash);
EEPROM_writeAnything(30, Sold);
EEPROM_writeAnything(40, Cash);
EEPROM_writeAnything(50, pricedefault);
EEPROM_writeAnything(60, Items);
EEPROM_writeAnything(70, serverdefault);
ReadAll();
}
Annother function allows to change the default server number.
void Memory::ChangeServer(char *number_str)
{
EEPROM_writeAnything(70, number_str);
ReadAll();
}
This function doesn't work.
I call the function in the void setup().
void setup()
{
Serial.begin(9600);
Serial.println("started");
Serial.println("factory reset");
mem.FactoryReset();
Serial.println("change server number");
mem.ChangeServer("1234567890123");
}
The value saved in the EEPROM is replaced by "b32484716340" instead of "1234567890123". What am i doing wrong?
In Memory::ChangeServer you are writing the pointer itself to EEPROM (i.e. the address), rather than the string that the pointer points to. One way to fix this would be:
void Memory::ChangeServer(char *number_str)
{
for (int i = 0; i <= strlen(number_str); ++i)
{
EEPROM_writeAnything(70 + i, number_str[i]);
}
ReadAll();
}
I have created a live continuous mjpeg stream. A crude illustration is like this
....[image (jpeg)]->[text "content-length"]->[image (jpeg)]->[text "content-length"]->....
As you can see I receive data from gstreamer media pipe line which contains image and my own injected text
(Note: Although I am using Gstreamer, my question is only related to C++ principles.)
In order to parse this real-time data, I am trying to receive and push it into the queue. Subsequently I plan to parse the data for the word "content-length" after queue contains a certain number of packets.
My code looks like the following:
void clear( std::queue<char> &q )
{
std::queue<char> empty;
std::swap( q, empty );
}
static GstFlowReturn new_buffer (GstAppSink *app_sink, gpointer user_data)
{
GstBuffer* buffer = gst_app_sink_pull_buffer(app_sink);
//create queue
std::queue<char> q;
g_print("The input buffer contents are\n");
gint i=0;
for(i=0; buffer->data[i];i++)
{
//g_print("\n%d",i);
q.push(buffer->data[i]);
}
//g_print("\nsize of inbuf is %d\n",GST_BUFFER_SIZE(buffer));
g_print("\n");
gst_buffer_unref(buffer);
//#####################
//parsing method here???
//#####################
clear(q);
return GST_FLOW_OK;
}
I have used circular queues/ ring buffer in C/C++ before. Is that the best option? Or is the C++ STL queues would be more appropriate in this scenario like above?
I ended up using ringbuffer class
In header file declare
//queue size
enum { rb_size = 5 }; // ---->element1 -> element2 -> .... -> elementN -> gap ->
// ^ |
// | |
// <--------------------<------------------<-------------V
typedef struct
{
char * data[rb_size];
int head, tail;
} ring_buffer_struct;
namespace myspace{
class ring_buffer{
private:
protected:
public:
//========= constructor ============
ring_buffer()
{
//If necessary initialization can happen here.
}
//========== destructor =============
virtual ~ring_buffer()
{
}
//===================================
virtual void rb_start(ring_buffer_struct *b);
virtual bool rb_empty(ring_buffer_struct const *b);
virtual char * rb_front(ring_buffer_struct const *b);
virtual char * rb_rear(ring_buffer_struct const *b);
virtual void rb_pop_front(ring_buffer_struct *b);
virtual ring_buffer_struct* rb_push_back(ring_buffer_struct *b);
}; //end of class
}
In cpp file
//start
void myspace::ring_buffer::rb_start(ring_buffer_struct *b)
{
b->head = 0; b->tail = 0;
}
//clear
bool myspace::ring_buffer::rb_empty(ring_buffer_struct const *b)
{
return b->head == b->tail;
}
//front element
char * myspace::ring_buffer::rb_front(ring_buffer_struct const *b)
{
return b->data[b->head]; //data gets popped
}
//rear element
char * myspace::ring_buffer::rb_rear(ring_buffer_struct const *b)
{
return b->data[b->tail]; //data gets pushed
}
//pop out front element
void myspace::ring_buffer::rb_pop_front(ring_buffer_struct *b)
{
if(b->head < b->tail)
{
++b->head;
}
if(b->head > b->tail)
{
b->head = 0;
}
}
//push in rear element
ring_buffer_struct* myspace::ring_buffer::rb_push_back(ring_buffer_struct *b)
{
int new_tail = b->tail;
if (++new_tail >= rb_size)
{ //beginning of the queue
new_tail = 0;
}
if (new_tail != b->head)
{
//middle of the queue
b->tail = new_tail;
}
if (new_tail <= b->head)
{
b->tail = 0;
}
return b;
}
And to use in the main()
...
char element1[10] = "abcdefghi";
char element2[10] = "bcdefghij";
char element3[10] = "cdefghijk";
ring_buffer_struct rb;
myspace::ring_buffer q;
q.rb_empty(&rb); //make sure empty
q.rb_start(&rb); //start - initialize
//initialize
uint16_t i;
for(i=0;i<rb_size;i++)
{
rb.data[rb.tail] = (char *)"000000000";
q.rb_push_back(&rb);
}
rb.data[rb.tail] = element1;
q.rb_push_back(&rb);
q.rb_pop_front(&rb); //now parse
rb.data[rb.tail] = element2;
q.rb_push_back(&rb);
q.rb_pop_front(&rb); //now parse
...
For parsing: I looked at this post
Simple string parsing with C++
Off topic suggestion:
When using the swap trick to clear out an STL container, don't call std::swap explicitly, as you may end up not getting a better-optimized version. The better way is:
void clear( std::queue<char> &q )
{
std::queue<char> empty;
using std::swap;
swap( q, empty );
}
This allows the compiler to choose a specialized version of swap that's optimized for the type of container you're using. You could also try q.swap(empty);, but I'm not sure all STL implementations offer that.