-1

The avr-gcc compiler offers the F() macro as a good way to define strings in my statements and then place the strings in program memory. The strings end up being of type __FlashStringHelper, however, and I will get an error if I try to pass them to functions that expect "const char *".

I can cast each one and the code will function. For example this code will work:

int16_t LT_printf(const char *format, ...);
LT_printf((const char *)F("TESTING!\r\n"));

I can also define an overload function that does nothing but receive the __FlashStringHelper and then turn it into a (const char *). This also works:

int16_t LT_printf(const __FlashStringHelper *format, ...);
LT_printf(F("TESTING!\r\n"));

The second solution executes less efficiently than the first, but at least I don't have hundreds of casts in my code any more.

Is there a way to eliminate the casts inside every function call, but still not need the overload function?

Edited to Add More Examples that build (not that any of the examples are something I'd really do...I'm just interested in the pointer to const __FlashStringHelper):

typedef struct
{
  char test_string[20];
} TEST_STRUCT_TYPE;
const TEST_STRUCT_TYPE PROGMEM test_struct = {"STRUCT TESTING!\r\n"};
const uint8_t PROGMEM test_array[] = {'A', 'R', 'R', 'A', 'Y', ' ', 'T', 'E', 'S', 'T', 'I', 'N', 'G', '!', '\r', '\n', NULL};
const char PROGMEM test_string[] = {"TEST TESTING!\r\n"};

void test()
{
  LT_printf(test_string);                       // doesn't need a cast

  LT_printf((const char *)F("F TESTING!\r\n")); // these need a cast
  LT_printf((const char *)&test_struct);
  LT_printf((const char *)test_array);

  LT_printf((PGM_P)F("F TESTING!\r\n"));        // this is the cleaner cast
  LT_printf((PGM_P)&test_struct);
  LT_printf((PGM_P)test_array);
}

This results in this output:

TEST TESTING!
F TESTING!
STRUCT TESTING!
ARRAY TESTING!
F TESTING!
STRUCT TESTING!
ARRAY TESTING!
BigBobby
  • 423
  • 1
  • 3
  • 17
  • Don't add tags for langauges other than you ask about. – too honest for this site Feb 28 '16 at 23:46
  • Well, I added c because I wouldn't mind a solution that worked in c as well as c++. No problem though...the actual file I'm working with now is c++ in the Arduino environment. – BigBobby Feb 28 '16 at 23:49
  • Should add `arduino` tag, or whatever it is that `__FlashStringHelper` is part of. Also, [this](http://stackoverflow.com/q/16597437/395718) question suggests that common functions are already overloaded to make use of `F()` macro, so if the makers are not worried about efficiency maybe you should go with the flow too. – Dialecticus Feb 28 '16 at 23:52
  • If that is Processing code, you actually should even remove the C++ tag. Because there are some differences. – too honest for this site Feb 28 '16 at 23:54
  • I checked out the link and they are correct that print and println are already overloaded. Many others aren't though, with printf() as an example. In fact, the lack of an overload is part of my reason for having my own printf(). – BigBobby Feb 28 '16 at 23:57
  • Why don't you just define another macro? For instance `define F_CHAR(x) (const char*)F(x)` and then just use it as `LT_printf(F_CHAR("TESTING!\r\n"));` – frarugi87 Feb 29 '16 at 10:32
  • Are you sure that `LT_printf((const char *)F("TESTING!\r\n"));` does not only compile but also yield the correct output? - First, casting an object reference to `char*` is not a good idea; moreover, casting a pointer to flash memory to a pointer to RAM should *not* work at runtime. – JimmyB Feb 29 '16 at 14:15
  • For reference, see e.g. https://github.com/johnmccombs/arduino-libraries/blob/master/Flash/Flash.h#L55 – JimmyB Feb 29 '16 at 14:16
  • @frarugi - I'll probably do this is I can't find a better way. @ Hanno Binder - yes, they build and run. I can modify my question with more examples that show output. And you are correct that it seems strange since the cast doesn't mention PROGMEM, but if I include that qualifier the compiler tells me to take it out. It seems PGM_P is the best cast for me to use, but either way I'd prefer to not need the cast or the overload. – BigBobby Feb 29 '16 at 17:03
  • I had the idea of making LT_print() be a macro itself that always cast what I put into it, but I didn't know how to make a macro that handles variable arguments...until this question showed me how! -> http://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments . I had to make a macro for each number of arguments up to the maximum I expect to support, but that's not too bad since I don't expect to ever have more than 9. – BigBobby Feb 29 '16 at 19:57

1 Answers1

0
  LT_printf(test_string);                       // doesn't need a cast
  LT_printf((const char *)F("F TESTING!\r\n")); // these need a cast
  LT_printf((const char *)&test_struct);

You can't just cast away a different data type and expect it to work. Presuming that test_string works then it expects to print from RAM and not PROGMEM which is in a separate address space.

The simple solution in your case is to derive the class that LT_printf is in (whatever that is) from Print and then print, println etc. will just work.

For example, I have a library that prints to an I2C LCD. I derive it from Print like this:

class I2C_graphical_LCD_display : public Print

Now all your class has to do is implement write in your class (ie. writing a single byte) and the Print class takes care of the rest: both writing from RAM and PROGMEM via the F() macro.

My answer doesn't address how to get printf to work, although you could look at this.

You might be better off looking at the Streaming library which lets you stream output in a C++ style, rather than using printf-style outputting.


Here is a simple way of implementing printf (however from RAM only) by adding a couple of helper functions:

int getChar (FILE *fp)
  {
  while (!(Serial.available()));
  return (Serial.read());
  }  // end of getChar

int putChar (char c, FILE *fp)
  {
  Serial.write (c);
  return c;
  }  // end of putChar

void setup ()
  {
  Serial.begin(115200);
  fdevopen (putChar, getChar);
  }  // end of setup

void loop ()
  {
  int temp = 30;

  printf ("The temperature is %d degrees C.\n", temp);
  delay (100);
  }  // end of loop

fdevopen tells the Standard IO library how to get and put characters. In this particular case I am using Serial, but it could be Serial1 or SoftwareSerial, etc.


If you want to use PROGMEM constants (ie. F()) then I think the easiest is to use the Streaming library. eg.

#include <Streaming.h>

void setup ()
  {
  Serial.begin (115200);
  }  // end of setup

void loop ()
  {
  int temp = 30;    
  Serial << F("The temperature is ") << temp << F(" degrees C") << endl;
  delay (100);
  }  // end of loop

That is even more compact, and saves RAM by using the F() macro. It's pretty readable too.

Nick Gammon
  • 1,173
  • 10
  • 22
  • I understand why you would expect my code not to work, as (const char *) should refer to a constant in RAM and the things that I'm casting are constants in PROGMEM. That came about because I first had (const PROGMEM char *) and the compiler wouldn't build until I removed the keyword. Since I only use pgm_read_byte() on that argument, which doesn't seem to throw an error when it's passed (const char *), the code worked. I have since replaced all of the (const char *) with PGM_P, however in pgmspace.h it seems to just "#define PGM_P const char *" anyway? – BigBobby Feb 29 '16 at 21:45
  • Added more examples to answer. – Nick Gammon Feb 29 '16 at 22:24