2

I'm implementing a voice synthesizer chip. To build a phrase, I create a list of phonemes like this:

static const uint8_t PROGMEM heybuddy[] = {
  pPA5, pHH1, pEY, pPA5,
  pBB2, pAX, pDD2, pIY, pPA5,pPA5,pPA5,
};

And to play them I do this:

 for (size_t i=0; i<sizeof(heybuddy); i++){
    say(pgm_read_byte(heybuddy+i));
 }

So what I'd like to do is create a function where I can just pass the array to a function that can take care of the loop and do some other maintenance stuff.

//what I've tried: 
void sayPhrase(const uint8_t *phrase){
  int open = 0;
  for (size_t i=0; i<sizeof(*phrase); i++){

      if(pgm_read_byte(*phrase) != 4 && open == 0){
          Serial.println("mouth open");
          open = 1;
      } else if(pgm_read_byte(*phrase) == 4 && open == 1){
         Serial.println("mouth closed");
         open = 0;
      }
      say(pgm_read_byte(*phrase+i));
    } 
}

But when it comes to passing a static const I'm lost I've tried passing it as a reference with * but nothing I do seems to work - I just don't know the proper way to do this in C.

What I have compiles, but it seems to only get one phoneme and repeat it over and over. The rest are lost.

4 Answers 4

1

The problem is, when you call sizeof(*phrase), what is returned is 1, the size of the uint8_t array element, the result of your pointer de-reference. On the other hand, if you call sizeof(phrase) inside the function, you get the size of the pointer itself and not the size of the array, as you might expect. When you pass the array to the function via a pointer, the function has no idea if you passed the start of an array to it or merely a pointer to some uint8_t variable, so it can't know what the size is; context has been lost.

Instead, you should call sizeof(phrase) in the same scope where the array was declared and initialized and pass the result to the function, which can then use this value in the for loop.

static const uint8_t PROGMEM heybuddy[] = {
  pPA5, pHH1, pEY, pPA5,
  pBB2, pAX, pDD2, pIY, pPA5,pPA5,pPA5,
};

size_t SIZE = sizeof(heybuddy);  // this is ok since each element = 1 byte

sayPhrase(phrase, SIZE);

The function definition should be:

void sayPhrase(const uint8_t *phrase, size_t size){
  int open = 0;
  for (size_t i=0; i < size; i++){

      if(pgm_read_byte(phrase) != 4 && open == 0){
          Serial.println("mouth open");
          open = 1;
      } else if(pgm_read_byte(phrase) == 4 && open == 1){
         Serial.println("mouth closed");
         open = 0;
      }
      say(pgm_read_byte(phrase+i));
    } 
}
2
  • Thanks! It didn't work using say(pgm_read_byte(*phrase+i)); But it worked using: say(pgm_read_byte(phrase+i)); I guess I'm still not clear on the difference between phrase and *phrase inside the function Jun 26, 2016 at 1:50
  • @RampantCreativeGroup Oh, I didnt notice that in the body of the function. Copy and paste. phrase is the pointer, the address of the first element of the array. *phrase is the element pointed to by phrase. If you want to use pgm_read_byte(), it means you want to read a byte from some address in flash, so it makes sense to pass the pointer, phrase, but not the first array element, *phrase. Jun 26, 2016 at 11:52
1

If you're not averse to a bit of C++:

void sayPhrase(const uint8_t * phrase, size_t size)
{
    bool open = false;
    for (size_t i = 0; i < size; ++i)
    {
        if(pgm_read_byte(phrase) != 4 && !open)
        {
            Serial.println("mouth open");
            open = true;
        }
        else if(pgm_read_byte(phrase) == 4 && open)
        {
            Serial.println("mouth closed");
            open = false;
        }
        say(pgm_read_byte(&phrase[i]));
    } 
}

template< size_t size >
void sayPhrase(const uint8_t (&phrase)[size])
{
    sayPhrase(phrase, size);
}

Which can then be called directly on the array:

sayPhrase(heybuddy);

And the template will infer the array size and call the non-template function.

1

I have some extensive examples of accessing PROGMEM here.

I've adapted one of the examples to show how you might do something similar to what you want:

const int NUMBER_OF_ELEMENTS = 10;

const char Message0000 [] PROGMEM = "Twas bryllyg, and ye slythy toves"; 
const char Message0001 [] PROGMEM = "Did gyre and gymble"; 
const char Message0002 [] PROGMEM = "in ye wabe:"; 
const char Message0003 [] PROGMEM = "All mimsy were ye borogoves; And ye mome raths outgrabe."; 
const char Message0004 [] PROGMEM = "\"Beware the Jabberwock, my son! \n The jaws that bite, the claws that catch!"; 
const char Message0005 [] PROGMEM = "Beware the Jubjub bird, and shun\n The frumious Bandersnatch!\""; 
const char Message0006 [] PROGMEM = "He took his "; 
const char Message0007 [] PROGMEM = "vorpal sword in hand:"; 
const char Message0008 [] PROGMEM = "Long time the manxome foe he sought - "; 
const char Message0009 [] PROGMEM = "So rested he by the Tumtum tree, \n And stood awhile in thought."; 

const char * const messages[NUMBER_OF_ELEMENTS] PROGMEM = 
   { 
   Message0000, 
   Message0001, 
   Message0002, 
   Message0003, 
   Message0004, 
   Message0005, 
   Message0006, 
   Message0007, 
   Message0008, 
   Message0009, 
   };

void sayPhrase (const char * const * message)
  {
  const char * ptr = reinterpret_cast<const char *>(pgm_read_ptr (message));  // pointer to message
  Serial.println(reinterpret_cast<const __FlashStringHelper *>(ptr));    // and print it
  } // end of sayPhrase

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();

  for (int i = 0; i < NUMBER_OF_ELEMENTS; i++)
    sayPhrase (&messages [i]);

  }  // end of setup

void loop () { } 
2
  • It's better to use pgm_read_ptr rather than pgm_read_word because it expresses the intent better and doesn't rely on the assumption that a pointer is the same size as a word. (It usually is the same size as a word, but that may not be true for all boards.) Additionally, rather than performing an unncessary copy, you could take advantage of Arduino's __FlashStringHelper type.
    – Pharap
    May 22, 2018 at 13:38
  • 1
    Sounds sensible enough. The original (which makes a copy) might be more general (you might need the string in a context where the __FlashStringHelper type doesn't apply), but I accept your suggested changes.
    – Nick Gammon
    May 22, 2018 at 20:49
0

As others have pointed out, your problem is sizeof, which is calculated at compile time, not when the code is run.

You need to pass the size of the array to the function. This can be done in many ways, depending on what else you are doing.

In c the most common method is to use a null-terminated string, but I suspect this may not work.

You can store the size of each phrase in a separate array, and pass as a separate parameter or Pascal style as a count byte in the first element of the phrase.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.