Help with string on avr 4809

Hi, Using a 4809 to model a shaft encoder and I need to use a string, but when I declare it i get the error ‘string was not declared in this scope.’

My project has a header file (encoder .h) which declares all the functions etc in use and all the functions in a library (encoder.cpp)

there’s also main.cpp

Prior to insertion of the string declaration the code compiles.

here’s the .h file, which as can be seen includes the string class type <string.h>

#pragma once;

#define F_CPU 3333333
#define USART1_BAUD_RATE(BAUD_RATE) ((float)(F_CPU * 64 / (16 * (float)BAUD_RATE)) + 0.5)
#define MAX_COMMAND_LEN		8
#define INIT_DELAY			10	// Delay to invalidate the after-reset noise on the PC0 pin (TX) 

#include <avr/io.h>
#include <stdio.h>
#include <string.h>
#include <util/delay.h>
#include <stdlib.h>


#define ASCOMSerial    USART1      // give the USARTS meaningful names
#define MONITORSerial USART0
#define STEPPERSerial USART2

int example (void);
void USART1_init(void);
void USARTx_sendChar(USART_t* usart_x, char c);

void USARTx_sendString(USART_t* usart_x , char *str);

char USARTx_readChar(USART_t* usart);
void LED_on(void);
void LED_off(void);
void LED_init(void);
void executeCommand(char *command);
void pins_disable(void);

and here’s the string declaration in main.cpp which cause the problem

string pkstr = "Hello";

so I’m guessing I’m using a library which doesn’t allow this type of string declaration, but according to various tutorials I’ve read (e.g. C++ Strings ) it should work…
thanks for help,
Paul

This header is for functions regarding C-style strings, which are const char*, pointing to the start of memory where the character data is store (until a 0 byte / \0 is encountered). This header is also known a #include <cstring> in C++.

I think here you’re refering to the std::string from the C++ standard library. That’s included via #include <string>. Not string.h. Also, if you don’t do a using namespace std;, you have to write std::string instead of string. It is however considered not good practice to do that.

But, there’s also another dimension to it: std::string from the C++ standard library are convenient, since you can do addition and resizing and finding substrings etc directly on there, but they work by allocating memory on the heap, which is generally bad for small embedded devices. The Arduino String class works in approximately the same way, and thus also considered bad. (see this and this article). You may also find the first few paragaphs of this article interesting.

So I’d highly recommand to stay with C-style strings for the embedded world. For your code that would mean that you write normal strings into variables of type const char* or const char variablename[].

Also, try and be const correct. For a function like

The given str should be a const char*, since you probably do not modify the string inside the function, thus it stays constant. Same goes for executeCommand(). Setting a paramter to const will enable the compiler to do some optimizations, like putting a string or variables purely in Flash instead of RAM (and flash for init value).

Of course, you may still want to experiment with std::string. One would expect to be able to write

#include <string>

std::string pkstr = "Hello";

And the send function like

void USARTx_sendString(USART_t* usart_x , const std::string& str);

As you can pass a constant standard-string reference. Constant because again the string you’re passing shouldn’t need to be modified during sending. And a refernce because you don’t want to pass the std::string object by value – a refernce is a pointer under the hood here for all intents and purposes, but prettier.

The send function could then look like

void USARTx _sendString(USART_t* usart_x , const std::string& str)
{
    for (char const &c: str) {
        USARTx_sendChar(c);
    }
}

reading as "for each character in the string (as a const reference), send that character. You can also loop through it in other ways.

But: The caveat in the AVR GCC toolchain is, that string is not supported. In general, support of the C++ standard library is poor. Errors out with

avr-g++ -o .pio\build\ATmega4809\src\main.o -c -std=gnu++11 -fno-exceptions -fno-threadsafe-statics -fpermissive -Wno-error=narrowing -Os -w -ffunction-sections -fdata-sections -flto -mmcu=atmega4809 -DF_CPU=16000000L -DPLATFORMIO=50101 -DARDUINO_AVR_ATMEGA4809 -Iinclude -Isrc src\main.cpp
src\main.cpp:7:10: fatal error: string: No such file or directory
    7 | #include <string>
      |          ^~~~~~~~

See also here.

The only known workaround with this is using a supplimentary library like such as GitHub - mike-matera/ArduinoSTL: An STL and iostream implementation based on uClibc++ that supports my CS-11M class. and the Arduino framework. ArduinoSTL provides a ton of header files and implementations for the standard C++ library that is missing in the AVR-GCC toolchain, not only string, but iostream and vector and functional and algorithm, etc.

So with a platformio.ini like

[env:ATmega4809]
platform = atmelmegaavr
board = ATmega4809
lib_deps = 
    mike-matera/ArduinoSTL @ ^1.1.0
framework = arduino

and code

#define F_CPU 3333333
#define USART0_BAUD_RATE(BAUD_RATE) ((float)(F_CPU * 64 / (16 * (float)BAUD_RATE)) + 0.5)

#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <string>

void USART0_init(void);
void USART0_sendChar(char c);
void USART0_sendString(const std::string& str);

void USART0_init(void)
{
  PORTA.DIR &= ~PIN1_bm;
  PORTA.DIR |= PIN0_bm;

  USART0.BAUD = (uint16_t)USART0_BAUD_RATE(9600);

  USART0.CTRLB |= USART_TXEN_bm;
}

void USART0_sendChar(char c)
{
  while (!(USART0.STATUS & USART_DREIF_bm))
  {
    ;
  }
  USART0.TXDATAL = c;
}

void USART0_sendString(const std::string& str)
{
  for (char const &c : str)
  {
    USART0_sendChar(c);
  }
}

int main(void)
{
  USART0_init();

  while (1)
  {
    std::string myString = "Hello World!\r\n";
    USART0_sendString(myString);
    USART0_sendString("ABCD\r\n");
    _delay_ms(500);
  }
}

It compiles :slight_smile:

Dependency Graph
|-- <ArduinoSTL> 1.1.0
Building in release mode
Compiling .pio\build\ATmega4809\src\main.cpp.o
Linking .pio\build\ATmega4809\firmware.elf
Checking size .pio\build\ATmega4809\firmware.elf
Building .pio\build\ATmega4809\firmware.hex
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [          ]   0.2% (used 10 bytes from 6144 bytes)
Flash: [          ]   2.6% (used 1278 bytes from 49152 bytes)
=========================== [SUCCESS] Took 1.74 seconds ===========================

Note that this also uses a neat trick: Although we have declared framework = arduino, and the Arduino framework will be compiled a part of it is needed by ArduinoSTL, we were are still able to override the main() function. Thus we bypass the Arduino entry point and the linker will throw out most of the Arduino framework that is not used.

If I added

#include <Arduino.h>
#include <ArduinoSTL.h>

on top and implemented main() as setup() and an empty loop, the program would grow to

RAM:   [          ]   0.2% (used 14 bytes from 6144 bytes)
Flash: [          ]   3.1% (used 1536 bytes from 49152 bytes)
=========================== [SUCCESS] Took 2.13 seconds ===========================

so we are able to throw quite a bit out if we abuse ArduinoSTL for just the string header while ignoring most of Arduino.

But yeah, this was a rather long expedition. Main take-aways are

  • if you want to be as memory-efficient as possible, only use C strings, aka const char* and string.h
  • using C++ std::string and the string header in C++ is not possible by default with AVR-GCC
    • but, can be supplemented by a library, for both Arduino cases and baremetal cases (if we accept that a tiny bit of Arduino code is in there as ArduinoSTL’s dependency)
    • if you want to stay 100% pure baremetal though, then either use C-strings or copy just the std::string implementation from ArduinoSTL in your project
  • know the dangers of heap-based string implementations like String and std::string
    • they still have their usefullness though, and if correctly used can also be efficient
  • const correctness is good

thanks for this hugely comprehensive reply Max, much appreciated. I walked into arduino coding a few years ago and wrote my shaft encoder project in a few days. I chose it as a baremetal project as I knew the coding requirements and the project has some good requirements such as serial exchanges between mcus and the use of interrupts for the encoder changes. The baremetal stuff is an enjoyable excursion, but much harder as it’s closer to hardware (as the name implies…). I think it will become easier as I get to know the hardware more.

Thanks also for picking up on const correctness, I read the link and it makes sense to be const correct from the outset.

There are some conflicting resources out there (some of which seem credible) and that’s why I ended up posting my string problem.

I’ll stick with C strings, I also looked in the stdlib.h and there are functions to convert string to double and double to string, which is all I need re strings in this project.
thanks again,
Paul