📜 ⬆️ ⬇️

Strings in the AVR code memory

In our company, we write programs for AVR series controllers. In this article I want to describe how we create strings located in code memory.

Initially, it was required that the following code should not give errors, but in the end get a much more powerful tool than expected.

const char *pStr = PSTR("Hello"); //    . // error: statement-expressions are not allowed outside functions nor in template-argument lists int main() {…} 

Those who are not aware of the problem of working with memory in AVR microcontrollers can watch the spoiler
AVR controllers use two independent address spaces:
')
  • for code
  • for RAM and registers.

The GCC compiler uses a two-byte pointer that provides access to the first 64K code memory (the rest can only be used for instructions) or to the entire RAM.

But to find out by the pointer in which memory the variable is located is not possible. Because of this, separate functions appeared in the avr-gcc library for working with the code memory and the strings located in it. They are marked with the “_P” suffix at the end of the function name. For example, strcpy_P is an analogue of the function strcpy, which takes a pointer to a string in code memory.

Unfortunately, the compiler cannot check where the variable will be used and place it there, and therefore the programmer needs to take care of it himself. Accordingly, it has become necessary to label the variables located in the code memory with the PROGMEM keyword in order to clearly indicate that we intend to work with the code.

This, however, does not negate the need for the programmer to monitor the correct use of variables.

Most of the inconvenience we delivered the line. String literals are objects located in RAM, which means they occupy both RAM and code memory (you need to take values ​​for initialization from somewhere). Again, they are not suitable for working with functions that work with code memory. For example:

 int main() { char dest[20]; strcpy_P(dest, "Hello world!"); } 

This code will lead to undefined consequences, since it will take data from code memory located at the same address as the string “Hello world!” In RAM.

For these cases, the PSTR (text) macro was provided in the avr library, which returns a pointer to a string located in the code memory.

 int main() { char dest[20]; strcpy_P(dest, PSTR("Hello world!")); } 

Now this code works and does not even take up RAM. But it is necessary to bring this macro beyond the limits of any function, it stops working.

 const char *pStr = PSTR("Hello"); //    . // error: statement-expressions are not allowed outside functions nor in template-argument lists int main() {…} 

I had to write something like this code:

 extern const char PROGMEM caption1[]; const char caption1[] = "Hello"; const char *pStr = caption1; 

This is a contrived example, but imagine that instead of pStr, we are initializing some kind of user structure that is waiting for a pointer to a string.

First of all, it was necessary to initialize the menu structure. All initialization had to be done statically, at compile time.

Therefore, we began to look for a reliable way to get a pointer to a string in the code memory. This helped us to template classes. For a template class, you can create a static variable located in the code memory and get a pointer to it.

 template <char value> struct ProgmemChar { static const char PROGMEM v; }; template <char value> const char ProgmemChar<value>::v = value; const char *pChar = &(ProgmemChar<'a'>::v); 

But do not pass the string parameter in the template. Therefore, we decided to break the string into characters. As we break the string into characters, I will show further, but for now I will show a simple example of a string in code memory:

 template <char ch1, char ch2, char ch3, char ch4, char ch5> struct ProgmemString { static const char PROGMEM v[5]; }; template <char ch1, char ch2, char ch3, char ch4, char ch5> const char ProgmemString<ch1, ch2, ch3, ch4, ch5>::v[5] = {ch1, ch2, ch3, ch4, ch5}; const char *pStr = ProgmemString<'a', 'b', 'c', 'd', 0>::v; 

This example works for strings that are exactly 4 characters in length and ends with 0 at the end. And the string ProgmemString <'a', 0, 0, 0, 0> will also occupy 5 bytes.

To solve this problem, we used a partial specialization of the template class, adding the size of the string to the template. Here is the basic template class:

 template<size_t S, char... L>struct _Pstr; 

Now back to the problem of breaking a string into characters. To be honest, this is still a problem for us, since we could not come up with anything better than to write a macro that N takes the i-th (from 0 to N-1) character from the source string.

 #define SPLIT_TO_CHAR_4(STR) STR[0], STR[1], STR[2], STR[3] 

This macro breaks the string, which must be at least four characters, into characters. In this case, N = 4.

If we look at the code after the preprocessor, we would see the following code:

 "Hello world!"[0], "Hello world!"[1], "Hello world!"[2], "Hello world!"[3] 

I agree that this is a long text, but we have come to terms with it. Moreover, the compiler itself already produces only four characters.

A more important problem was taking a character with a large index. For a large N (and we want all our lines to be shorter than N), there will definitely be a case when we want to take a character outside of the line, which will result in a compilation error.

The first working option was the following way:

  1. Add to the source line a string consisting of the character '\ 0' and having a length of N characters. Adding was carried out as follows: #define ADD_STR (STR) STR "\ 0 \ 0 \ ... \ 0".
  2. We perform an SPLIT_TO_CHAR operation on the resulting string.

This method works, but is guaranteed to increase the code after the preprocessor by N * N characters. As a result, we quickly get the compiler limit.

Fortunately, with the arrival of c ++ 11 and constexpr functions, we managed to get rid of the extra characters using the character selector class. For brevity, it is called _CS (Char Selector).

 struct _CS { template<size_t n> constexpr _CS(const char (&s)[n]) :s(s), l(n){} constexpr char operator [](size_t i){return i < l ?s[i] :0;} const char *s = 0; const size_t l = 0; }; 

I looked at the code of this class quite a while on Habré, but I cannot now find exactly where (thanks to the author).
The code for the character splitting macro has become simpler:

 #define SPLIT_TO_CHAR(STR) _CS(STR)[0], _CS(STR)[1], …, _CS(STR)[N-1] 

It now remains to put everything together:

 //    template<size_t S, char... L>struct _PStr; //  ,    .     10  #define ARGS01(P, S) P##00 S #define ARGS02(P, S) ARGS01(P, S),P##01 S #define ARGS03(P, S) ARGS02(P, S),P##02 S #define ARGS04(P, S) ARGS03(P, S),P##03 S #define ARGS05(P, S) ARGS04(P, S),P##04 S #define ARGS06(P, S) ARGS05(P, S),P##05 S #define ARGS07(P, S) ARGS06(P, S),P##06 S #define ARGS08(P, S) ARGS07(P, S),P##07 S #define ARGS09(P, S) ARGS08(P, S),P##08 S #define ARGS0A(P, S) ARGS09(P, S),P##09 S //       ( 0  10 ).     0. template<char... L>struct _PStr<0x00, L...>{static const char PROGMEM v[];}; template<char... L>const char _PStr<0x00, L...>::v[] = {0}; template<ARGS01(char _,), char... L>struct _PStr<0x01, ARGS01(_,), L...>{static const char PROGMEM v[];}; template<ARGS01(char _,), char... L>const char _PStr<0x01, ARGS01(_,), L...>::v[] = {ARGS01(_,), 0}; template<ARGS02(char _,), char... L>struct _PStr<0x02, ARGS02(_,), L...>{static const char PROGMEM v[];}; template<ARGS02(char _,), char... L>const char _PStr<0x02, ARGS02(_,), L...>::v[] = {ARGS02(_,), 0}; template<ARGS03(char _,), char... L>struct _PStr<0x03, ARGS03(_,), L...>{static const char PROGMEM v[];}; template<ARGS03(char _,), char... L>const char _PStr<0x03, ARGS03(_,), L...>::v[] = {ARGS03(_,), 0}; template<ARGS04(char _,), char... L>struct _PStr<0x04, ARGS04(_,), L...>{static const char PROGMEM v[];}; template<ARGS04(char _,), char... L>const char _PStr<0x04, ARGS04(_,), L...>::v[] = {ARGS04(_,), 0}; template<ARGS05(char _,), char... L>struct _PStr<0x05, ARGS05(_,), L...>{static const char PROGMEM v[];}; template<ARGS05(char _,), char... L>const char _PStr<0x05, ARGS05(_,), L...>::v[] = {ARGS05(_,), 0}; template<ARGS06(char _,), char... L>struct _PStr<0x06, ARGS06(_,), L...>{static const char PROGMEM v[];}; template<ARGS06(char _,), char... L>const char _PStr<0x06, ARGS06(_,), L...>::v[] = {ARGS06(_,), 0}; template<ARGS07(char _,), char... L>struct _PStr<0x07, ARGS07(_,), L...>{static const char PROGMEM v[];}; template<ARGS07(char _,), char... L>const char _PStr<0x07, ARGS07(_,), L...>::v[] = {ARGS07(_,), 0}; template<ARGS08(char _,), char... L>struct _PStr<0x08, ARGS08(_,), L...>{static const char PROGMEM v[];}; template<ARGS08(char _,), char... L>const char _PStr<0x08, ARGS08(_,), L...>::v[] = {ARGS08(_,), 0}; template<ARGS09(char _,), char... L>struct _PStr<0x09, ARGS09(_,), L...>{static const char PROGMEM v[];}; template<ARGS09(char _,), char... L>const char _PStr<0x09, ARGS09(_,), L...>::v[] = {ARGS09(_,), 0}; template<ARGS0A(char _,), char... L>struct _PStr<0x0A, ARGS0A(_,), L...>{static const char PROGMEM v[];}; template<ARGS0A(char _,), char... L>const char _PStr<0x0A, ARGS0A(_,), L...>::v[] = {ARGS0A(_,), 0}; //   struct _CS { template<size_t n> constexpr _CS(const char (&s)[n]) :s(s), l(n){} constexpr char operator [](size_t i){return i < l ?s[i] :0;} const char *s = 0; const size_t l = 0; }; //      #define STR_UNION(...) __VA_ARGS__ //  ,    ,    . SPS = StaticProgramString. #define SPS(T) STR_UNION(_PStr<_CS(T).l - 1, ARGS0A(_CS(T)[0x, ])>::v) 

Let us analyze the main macro by elements:


For each line will be selected its own specialization of the template, suitable for the length of the line.



Summing up, I would like to say that with the help of this macro we managed to implement not only getting a pointer to a string in the code, no matter where this macro is applied, but also two more distinct advantages over the PSTR:


 template <class T, const char *name> struct NamedType { T value; static const char *getName() { return name; } }; NamedType<int, SPS("")> var1 = {3}; 

These template classes allowed us to collect metadata about variables in the project, which allowed us to simplify development by an order of magnitude, while at the same time improving the user interface and customization flexibility. But that's another story.

Source: https://habr.com/ru/post/311874/


All Articles