Stefan Bormann
Stefan Bormann

Reputation: 693

Concatenate compile time constant strings with characters whose character code comes from a #define

I want to assemble a char array that contains one byte with a compile time constant value and a string, which is also compile time constant. Solution would be:

char packet[] = "\x42" __DATE__;

That works but is not very readable and maintainable, as that 0x42 is a message opcode that is used elsewhere, making this a magic number. Now, I could put a dummy x into the string and follow this definition with an assignment like this:

#define OPCODE 0x42
char packet[] = "x" __DATE__;
packet[0] = OPCODE;

But I have the feeling that could be done in a purely constant string literal, I just can't find how to do it. Any idea?

Upvotes: 3

Views: 577

Answers (2)

ndim
ndim

Reputation: 37847

As the C preprocessor cannot help here, this might be a case for an additional preprocessor to be added to the build system.

Whether you choose a second stage of C preprocessor, m4, a shell or python script or whatever else is up to you.

Or you go another route and change the packet type from char array to a struct with a flexible array member, something like the following:

/* foo.c
 * Compile with something like
 *    avr-gcc -mmcu=atmega328 -Os -Wall -Wextra -Werror \
 *        -save-temps=obj -Wa,-adhlns=foo.lst,-gstabs -c foo.c
 */

#include <stddef.h>
#include <stdint.h>

#include <avr/pgmspace.h>

#define OPCODE_DATE 0x42

struct packet_descr {
  uint8_t  opcode;
  uint16_t size;
};

struct string_packet {
  struct packet_descr descr;
  char                string[];
};

#define STRING_PACKET_P(IDENTIFIER, OPCODE, STRING)   \
  const struct string_packet IDENTIFIER PROGMEM = {   \
    { (OPCODE),                                       \
      sizeof(STRING)-1                                \
    },                                                \
    (STRING)                                          \
  }

STRING_PACKET_P(packet_date_P, OPCODE_DATE, __DATE__);

void uart_send_char(const char ch);
void uart_send_char(const char ch)
{
  UDR0 = ch;
}

/* Send string packet in the following format:
 *      uint8_t  opcode;
 *      uint16_t len;         // in AVR endianness
 *      char     string[len]; // string of "len" characters, unterminated
 */
extern
void string_packet_send_P(const struct string_packet *string_packet_P);
void string_packet_send_P(const struct string_packet *string_packet_P)
{
  const uint8_t opcode = pgm_read_byte(&string_packet_P->descr.opcode);
  uart_send_char(opcode);

  size_t        len    = pgm_read_word(&string_packet_P->descr.size);
  for (PGM_P byte_P = (PGM_P)&string_packet_P->string; len > 0; len--, byte_P++) {
    uart_send_char(pgm_read_byte(byte_P));
  }
}

int main(void)
{
  string_packet_send_P(&packet_date_P);
  return 0;
}

One obvious disadvantage of a struct with a flexible array member is that sizeof(packet) will only yield the size of the non-array part. However, depending on your actual packet format (the receiver of the packet also needs to know when a packet starts and finishes, right?), recording the size separately might be feasible.

Upvotes: 1

KamilCuk
KamilCuk

Reputation: 141613

__DATE__ should mostly have exactly Mmm dd yyyy format, so 11 characters. You can do this:

char packet[] = {
  OPCODE,
  __DATE__[0],
  __DATE__[1],
  __DATE__[2],
  __DATE__[3],
  __DATE__[4],
  __DATE__[5],
  __DATE__[6],
  __DATE__[7],
  __DATE__[8],
  __DATE__[9],
  __DATE__[10],
  __DATE__[11],
  0,
};
   

Upvotes: 4

Related Questions