how to pass an array to a function as a parameter

how to pass an array to a function?

I am trying to clean up my LED display program so I can use a single function to display 12 LEDs. I use a 2 dimensional array to specify the PWM values for each LED as a row, and then change rows to get a new pattern.

Anyway, this is a transfer from a python program that works, but I want to be able to use case switch to change between patterns.

Here si the code:

// Larson scanner with PWM

#define DEBUG_UART 0


const uint8_t leds[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};  // PWM pins 2 through 13
static uint32_t previousMillis = 0;
static uint32_t loopMillis = 0;
const uint16_t myIntervall = 5;
int totalNoLeds = sizeof(leds);
int i;
int pulse = 0;
uint8_t pattern;

void display_pattern(pattern)
{

  static uint32_t previousMillis = 0;
  const uint16_t myIntervall = 5;
 
uint8_t indexI;
uint8_t indexJ;

  // if it's time
  if (millis() - previousMillis >= myIntervall)
  {
    analogWrite(leds[indexI], pattern[indexJ][indexI]);
    // next indexI
    indexI++;
    // if indexI has reach max value
    if(indexI == sizeof(pattern[0]))
    {
      // reset indexI
      indexI = 0;
      // next indexJ
      indexJ++;

      // if indexJ has reached max value
      if(indexJ == sizeof(pattern))
      {
          // reset indexJ
          indexJ = 0;
      }
    }
    previousMillis = millis();
  }
}


int pattern1[17][12] = {
{255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255}, 
{75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 75},
{50, 75, 255, 0, 0, 0, 0, 0, 0, 255, 75, 50},
{25, 50, 75, 255, 0, 0, 0, 0, 255, 75, 50, 25},
{10, 25, 50, 75, 255, 0, 0, 255, 75, 50, 25, 10},
{5, 10, 25, 50, 75, 255, 255, 75, 50, 25, 10, 5},
{0, 5, 10, 25, 50, 255, 255, 50, 25, 10, 5, 0},
{0, 0, 5, 10, 255, 75, 75, 255, 10, 5, 0, 0},
{0, 0, 0, 255, 75, 50, 50, 75, 255, 0, 0, 0},
{0, 0, 255, 75, 50, 25, 25, 50, 75, 255, 0, 0},
{0, 255, 75, 50, 25, 10, 10, 25, 50, 75, 255, 0},
{255, 75, 50, 25, 10, 5, 5, 10, 25, 50, 75, 255},
{75, 50, 25, 10, 5, 0, 0, 5, 10, 25, 50, 75},
{50, 25, 10, 5, 0, 0, 0, 0, 5, 10, 25, 50},
{25, 10, 5, 0, 0, 0, 0, 0, 0, 5, 10, 25},
{10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10},
{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},  
};

void setup() 
{
for (uint8_t i = 0; i < totalNoLeds; i++) 
  {
  pinMode(i, OUTPUT);
  } 
}

void loop()
{
display_pattern(pattern1);
}

it does not like to passing of pattern1 to display_pattern() it gives various messages on compile depending. I want to keep the pattern array reference generic and use sizeof() to set dimensions of passed array.

Thanks,

Roger

If you're using multidimensional arrays, the array dimensions have to be part of the data type and function signature. Without knowing the number of columns, you cannot compute the position of an element given two indices, you need to know the stride/leading dimension.

You could use a template function:

template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) {

  static uint32_t previousMillis = 0;
  const uint16_t myIntervall = 5;

  uint8_t indexI;
  uint8_t indexJ;

  // if it's time
  if (millis() - previousMillis >= myIntervall)
  {
    analogWrite(leds[indexI], pattern[indexJ][indexI]);
    // next indexI
    indexI++;
    // if indexI has reach max value
    if (indexI == cols)
    {
      // reset indexI
      indexI = 0;
      // next indexJ
      indexJ++;

      // if indexJ has reached max value
      if (indexJ == rows)
      {
        // reset indexJ
        indexJ = 0;
      }
    }
    previousMillis = millis();
  }
}

If you're going to call the function with many different sizes of arrays, it's best to create a proxy to a function that takes a pointer and two dimensions to prevent code duplication.

By the way, your indices indexI and indexJ are uninitialized and should probably be static.

void display_pattern(const int *pattern, size_t rows, size_t cols) {
  static uint32_t previousMillis = 0;
  const uint16_t myInterval = 5;

  static uint8_t indexI = 0;
  static uint8_t indexJ = 0;

  // if it's time
  if (millis() - previousMillis >= myInterval) {
    uint16_t linearIndex = indexJ * cols + indexI;
    analogWrite(leds[indexI], pattern[linearIndex]);
    // next indexI
    indexI++;
    // if indexI has reach max value
    if (indexI == cols) {
      // reset indexI
      indexI = 0;
      // next indexJ
      indexJ++;

      // if indexJ has reached max value
      if (indexJ == rows) {
        // reset indexJ
        indexJ = 0;
      }
    }
    previousMillis += myInterval;
  }
}

template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) {
  display_pattern(&pattern[0][0], rows, cols);
}

In your setup, you probably want:

  for (uint8_t i = 0; i < totalNoLeds; i++)
  {
    pinMode(leds[i], OUTPUT);
  }

Or even better, use a range-based for-loop, so you don't have to worry about indices:

  for (auto ledPin : leds)
    pinMode(ledPin, OUTPUT);

Pieter

I think:

static uint8_t indexI = 0;
static uint8_t indexJ = 0;

PieterP:
If you're using multidimensional arrays, the array dimensions have to be part of the data type and function signature. Without knowing the number of columns, you cannot compute the position of an element given two indices, you need to know the stride/leading dimension.

why can't you pass a pointer to the first array element along with the # rows and # columns?

to index thru a particular row, locate the first element of the row (row * # cols) and then index thru the row as if a single dimension array. nothing you couldn't do in C

gcjr:
why can't you pass a pointer to the first array element along with the # rows and # columns?

to index thru a particular row, locate the first element of the row (row * # cols) and then index thru the row as if a single dimension array.

I never said you couldn't do that, in fact, it's exactly what I did in the second code snippet I posted. However, then you're no longer passing it as a 2D array, which is what the original question was about.

By using a template, you can't the dimensions wrong, because the compiler determines them for you, no need for confusing sizeof operators (which the OP used incorrectly).

gcjr:
nothing you couldn't do in C

I understand you like C, but how is that relevant here?

OK.

Template functions are a whole new concept for me. It will take me a while to assimilate what you have shown me.

A question.

In the first example you use teh template one time:
template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) {

in the second example it is used twice, once, similar to your first example:
void display_pattern(const int *pattern, size_t rows, size_t cols) {

and again:

template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) {
display_pattern(&pattern[0][0], rows, cols);

not sure teh reasoning or difference. Also, I see something using linnearindex, which is computed from rows and cols. Why necessary? That transformation is perhaps useful, but not necessary is it? Does it save time? Speed? Memory?

It is difficult for me, a somewhat beginner, to use some of these more advanced tools. I will endeavor to try to use and understand the template function examples, but if there is a simpler way to do this, I would like additional suggestions.

I understand the compiler will not know the size memory to allocate if the dimension s of the array are not know at compile.

Roger

rcayot:
I understand you like C, but how is that relevant here?

rcayot:
It is difficult for me, a somewhat beginner, to use some of these more advanced tools. I will endeavor to try to use and understand the template function examples, but if there is a simpler way to do this, I would like additional suggestions.

even the question was already answered, i would like to add

int pattern1[17][12] = {
[code]

a)
You have defined your pattern in global scope anyway. So why do need to hand over it to your function?
As pattern is global, you can access it in your function.

b)
none of your values is larger than 255. so you don't need an int. Safe SRAM and make it byte

c)
[code]
static uint32_t previousMillis = 0;
static uint32_t loopMillis = 0;

no need for static keyword if you define them in global scope.

d)

// Larson scanner with PWM

wtf. I remember that line of code.
https://forum.arduino.cc/index.php?topic=675980.15

would have saved my some time, if you could have linked your old thread!

rcayot:
Template functions are a whole new concept for me. It will take me a while to assimilate what you have shown me.

Think of template parameters as extra parameters to functions that are evaluated at compile time.
They are very powerful, because they allow you to make data types variable. Keep this in mind while reading the following example:
(If you don't know what a data type is, I'd recommend looking it up first.)

As a simple example, let's say you wanted to write a function to print out the values of an array.
The following code demonstrates how you would write that function, and how you can pass the array to the function:

// Function that prints the elements of an array
void print_array(int (&array)[3]) {
  for (int element : array) // For each element in the array
    Serial.println(element);
}

void setup() {
  Serial.begin(115200);
  int array[3] = {1, 2, 3};
  print_array(array);
}
void loop() {}

The type of the array parameter is int (&)[3], i.e. "a reference to an array of integers of length 3". The length of the array is part of the data type of this parameter.

Let's say you want to extend the print_array function, so it can print arrays of any length, not just 3. Now you have to change the type of the array argument to "a reference to an array of integers of length N", where "N" can be any value.
By using a template, you can make the type of the array argument variable. As the length of the array N changes, the data type of the argument changes as well.

template <size_t N> void print_array(int (&array)[N]) {
  ...
}

N is the template parameter that determines the length of the array. Its type is size_t, which is a normal data type like int.
You can now call the function with arrays of arbitrary length, the compiler will automatically deduce the value of N for you when you call the function.

template <size_t N> void print_array(int (&array)[N]) {
  for (int element : array)
    Serial.println(element);
}

void setup() {
  Serial.begin(115200);
  int array1[3] = {1, 2, 3};
  int array2[4] = {1, 2, 3, 4};
  print_array(array1); // Compiler deduces that N=3
  print_array(array2); // Compiler deduces that N=4
}
void loop() {}

rcayot:
A question.

In the first example you use teh template one time:
template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) {

in the second example it is used twice, once, similar to your first example:
void display_pattern(const int *pattern, size_t rows, size_t cols) {

and again:

template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) {
display_pattern(&pattern[0][0], rows, cols);

not sure teh reasoning or difference.

In the second example, there is only one template function. The first display_pattern function is a normal function. The second display_pattern function is a template function.

You can have multiple functions with the same name, as long as they have different arguments. The compiler will select the right function depending on the type of arguments you pass to the function when calling it. This is called overloading.

The reason why there are two functions in the second example is to save flash memory. When using templates, the compiler creates a separate function for each value of the template parameter used in the program. In the print_array example above, the compiler creates two functions for you, for each value of N you use. The code in the setup uses arrays of length 3 and 4, so the compiler creates the functions print_array(int (&array)[3]) and print_array(int (&array)[4]). Each of these functions takes up program storage, and you can imagine that the more different lengths of arrays are used, the more different functions you need, so the more storage it'll use.

That's why it's better to keep these template functions short. To continue with the print_array example, you could create a normal function with the implementation, with the length as a normal parameter, and then call this implementation function from the template function:

void print_array_implementation(int *array, size_t length) {
  for (size_t i = 0; i < length; ++i)
    Serial.println(array[i]);
}

template <size_t N> void print_array(int (&array)[N]) {
  print_array_implementation(&array[0], N);
}

By passing the array as a pointer, we loose the size information, so we have to pass the length as a separate parameter.

The template function is now very short, it just calls the implementation function, so using many different lengths doesn't increase the code size by much.
All the template does for us now, is determining the length of the array (N) and passing it on to the print_array_implementation function as a runtime parameter. It also converts the array parameter (of type int(&)[N]) to a pointer to the first element of the array.

Because of overloading, there's no need to use a different name for the implementation function, the compiler can easily see which one we want, because one has a single array parameter, and the other has two parameters (a pointer and a length).

rcayot:
Also, I see something using linnearindex, which is computed from rows and cols. Why necessary? That transformation is perhaps useful, but not necessary is it? Does it save time? Speed? Memory?

It is necessary.
When you convert a multidimensional array to a pointer, you loose all size information. You need to know the size if you want to know the memory location of an element in a multidimensional array.

Consider a 2D array:

00  01  02
10  11  12

Its layout in memory is:

0   1   2   3   4   5   ← linear index
00  01  02  10  11  12

Memory is always one dimensional.

How would you know where the element array[1][2] is in memory? You know that it's in the second row, so you have to skip over all of the elements in the first row, and you know it's in the third column, so you have to skip over two more elements once you're in the right row.
The linear index of array[1][2] is 5, because you have to skip 3 elements to get to the second row, and 2 more elements to get to the third column of that row.
In general, linear index = row index × number of columns + column index.

If you pass the array as a pointer, the compiler only sees it as a flat list of elements in memory, it doesn't know how many columns it has, so indexing it by row and column is impossible, you can only use a linear index. If you want to keep using row and column indices, you have to convert them to the linear index yourself.

noiasca,

re: Larson scanner with PWM.

This is an entirely new program, well mostly. While your example you gave me was fantastic, I could not do with it what I wanted. In addition, the re-arrange type of program I worked with you on was not as flexible. Some patterns were just next to impossible to achieve that way. I decided to use an array for a couple of reasons:

  1. it alowed nearly complete control over the patterns.
    2, while not elegant, it was possible to use switch case to change between patterns on a schedule.

So, after achieving success, I went to CircuitPython hoping to use my experience with C++ to help with Python. After achieving a good condensed program in Python, where, because it is dynamically typed, I could just pass the arra to teh function and it worked. What I could not do so easily in Python, though was use a weitch case to change between patterns, so I went back to C++ an Arduino, where I ran into this problem.

I know I could use the rows and cols numbers from teh array, but I want to have a large number of arrays, of different sizes. In addition, I was able to (in Python) pass a parameter to tell the function if the array was 'reversible' and save array space.

Coming back to C++ and Arduino because the documentation, and the help is much better, and I was getting pretty stuck.

So, I do not know if referencing the original question was a waste of your time, I sincerely thought that this was a very different programming question.

Thanks for all your past (and hopefully future) assistance. Now back to work!

Roger

PieterP,

Thanks, your explanation is helping. I do not understand the last example:

void print_array_implementation(int array, size_t length) {
for (size_t i = 0; i < length; ++i)
Serial.println(array
);*
}
template <size_t N> void print_array(int (&array)[N]) {

  • print_array_implementation(&array[0], N);*
    }
    Where does 'length' come from? I see it used, but I do not see its assignment.
    Roger

rcayot:
Where does 'length' come from? I see it used, but I do not see its assignment.

'length' is the second argument to the 'print_array_implementation' function. It is assigned when the 'print_array' template function calls 'print_array_implementation' (it assigns the value 'N' to it).

PieterP,

Thanks, I tried the code incorporated into my sketch. At first I had a few errors, but it finally compiled, but nothing (no leds light) happened. Without the loop() I was getting an error about a loop reference, so I added a blank loop(). If I included the function call in teh loop() it also threw an error.

// Larson scanner with PWM

#define DEBUG_UART 0


const uint8_t leds[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};  // PWM pins 2 through 13
static uint32_t previousMillis = 0;
const uint16_t myIntervall = 5;
int totalNoLeds = sizeof(leds);
int i;
int j;
int pulse = 0;
uint8_t pattern;

void display_pattern(const int *pattern, size_t rows, size_t cols) {
  static uint32_t previousMillis = 0;
  const uint16_t myInterval = 5;

  static uint8_t indexI = 0;
  static uint8_t indexJ = 0;

  // if it's time
  if (millis() - previousMillis >= myInterval) {
    uint16_t linearIndex = indexJ * cols + indexI;
    analogWrite(leds[indexI], pattern[linearIndex]);
    // next indexI
    indexI++;
    // if indexI has reach max value
    if (indexI == cols) {
      // reset indexI
      indexI = 0;
      // next indexJ
      indexJ++;

      // if indexJ has reached max value
      if (indexJ == rows) {
        // reset indexJ
        indexJ = 0;
      }
    }
    previousMillis += myInterval;
  }
}

int pattern1[17][12] = {
{255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255}, 
{75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 75},
{50, 75, 255, 0, 0, 0, 0, 0, 0, 255, 75, 50},
{25, 50, 75, 255, 0, 0, 0, 0, 255, 75, 50, 25},
{10, 25, 50, 75, 255, 0, 0, 255, 75, 50, 25, 10},
{5, 10, 25, 50, 75, 255, 255, 75, 50, 25, 10, 5},
{0, 5, 10, 25, 50, 255, 255, 50, 25, 10, 5, 0},
{0, 0, 5, 10, 255, 75, 75, 255, 10, 5, 0, 0},
{0, 0, 0, 255, 75, 50, 50, 75, 255, 0, 0, 0},
{0, 0, 255, 75, 50, 25, 25, 50, 75, 255, 0, 0},
{0, 255, 75, 50, 25, 10, 10, 25, 50, 75, 255, 0},
{255, 75, 50, 25, 10, 5, 5, 10, 25, 50, 75, 255},
{75, 50, 25, 10, 5, 0, 0, 5, 10, 25, 50, 75},
{50, 25, 10, 5, 0, 0, 0, 0, 5, 10, 25, 50},
{25, 10, 5, 0, 0, 0, 0, 0, 0, 5, 10, 25},
{10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10},
{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},  
};

void setup() 
{
for (uint8_t i = 0; i < totalNoLeds; i++) 
  {
  pinMode(leds[i], OUTPUT);
  } 
}

template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) 
{
display_pattern(&pattern1[0][0], rows, cols);
}
void loop(){}

You never call display_pattern.

PieterP,

Of course, works perfectly. I will try to add a few more patters of different sizes.

Thanks,

Roger

okay, well, mostly complete success! I have been able to pass an array to the function using a template function. The arrays are PWM values to set LEDs and light in patterns (aka larson etc.).

Then I went ahead and used switch case to switch between five patterns every five seconds and then restart the first pattern.

Trouble happens after about two or so passes through the cycle, the LEDs go crazy! Then apparently come back to 'normal'.

I used a few techniques to see where things went wrong. Evidently the linearIndex (converted from array) would go beyond the end point and continue to increment. To help fix, I added a 10ms buffer to the if millis() - looptime and it helped, but did not completely remove the occasional loss of synchrony.

If anyone can help find out why this happens randomly, well not completely randomly, only one the forth or fifth 'case' and usually when reset to first case, usually resets to proper pattern.

Here is code. I am calling this about 95% succedssful, and really appreciate teh help.

// Larson scanner with PWM

#define DEBUG_UART 0

const uint8_t leds[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};  // PWM pins 2 through 13
int totalNoLeds = sizeof(leds);
int pulse = 0;
static uint32_t loopMillis = 0;

void display_pattern(const int *pattern, size_t rows, size_t cols) 
{
  static uint32_t previousMillis = 0;
  const uint16_t myInterval = 50;
  static uint8_t indexI = 0;
  static uint8_t indexJ = 0;

    for (indexI = 0; indexI <= cols; indexI++)
    {
    uint16_t linearIndex = indexJ * cols + indexI;
    analogWrite(leds[indexI], pattern[linearIndex]);
    #if DEBUG_UART
Serial.println(linearIndex);
//Serial.println(millis() - loopMillis);
//Serial.println(patternd[indexI]);
delay(10);    
#endif
    }
    if (millis() - previousMillis >= myInterval)
    {
    indexJ++;
   // if indexJ has reached max value
      if (indexJ == rows) 
      {
        // reset indexJ
      indexJ = 0;
      }
      previousMillis = millis();
    }
}

int pattern1[17][12] = {
{255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255}, 
{75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 75},
{50, 75, 255, 0, 0, 0, 0, 0, 0, 255, 75, 50},
{25, 50, 75, 255, 0, 0, 0, 0, 255, 75, 50, 25},
{10, 25, 50, 75, 255, 0, 0, 255, 75, 50, 25, 10},
{5, 10, 25, 50, 75, 255, 255, 75, 50, 25, 10, 5},
{0, 5, 10, 25, 50, 255, 255, 50, 25, 10, 5, 0},
{0, 0, 5, 10, 255, 75, 75, 255, 10, 5, 0, 0},
{0, 0, 0, 255, 75, 50, 50, 75, 255, 0, 0, 0},
{0, 0, 255, 75, 50, 25, 25, 50, 75, 255, 0, 0},
{0, 255, 75, 50, 25, 10, 10, 25, 50, 75, 255, 0},
{255, 75, 50, 25, 10, 5, 5, 10, 25, 50, 75, 255},
{75, 50, 25, 10, 5, 0, 0, 5, 10, 25, 50, 75},
{50, 25, 10, 5, 0, 0, 0, 0, 5, 10, 25, 50},
{25, 10, 5, 0, 0, 0, 0, 0, 0, 5, 10, 25},
{10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10},
{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},  
};

int pattern2[17][12] = {
{255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 
{75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{50, 75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{25, 50, 75, 255, 0, 0, 0, 0, 0, 0, 0, 0},
{10, 25, 50, 75, 255, 0, 0, 0, 0, 0, 0, 0},
{5, 10, 25, 50, 75, 255, 0, 0, 0, 0, 0, 0},
{0, 5, 10, 25, 50, 75, 255, 0, 0, 0, 0, 0},
{0, 0, 5, 10, 25, 50, 75, 255, 10, 5, 0, 0},
{0, 0, 0, 5, 10, 25, 50, 75, 255, 0, 0, 0},
{0, 0, 0, 0, 5, 10, 25, 50, 75, 255, 0, 0},
{0, 0, 0, 0, 0, 5, 10, 25, 50, 75, 255, 0},
{0, 0, 0, 0, 0, 0, 5, 10, 25, 50, 75, 255},
{0, 0, 0, 0, 0, 0, 0, 5, 10, 25, 50, 75},
{0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 25, 50},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 25},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},
};

int pattern3[16][12] = {
{16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2},
{8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4},
{4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8},
{2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16},
{0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32},
{2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64},
{4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128},
{8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255},
{16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128},
{32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64},
{64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32},
{128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16},
{255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8},
{128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4},
{64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2},
{32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0},
};

int pattern4[10][12] = {
{2, 0, 2, 8, 32, 128, 255, 128, 32, 8, 2, 0},
{8, 2, 0, 2, 8, 32, 128, 255, 128, 32, 8, 2},
{32, 8, 2, 0, 2, 8, 32, 128, 255, 128, 32, 8},
{128, 32, 8, 2, 0, 2, 8, 32, 128, 255, 128, 32},
{255, 128, 32, 8, 2, 0, 2, 8, 32, 128, 255, 128},
{128, 255, 128, 32, 8, 2, 0, 2, 8, 32, 128, 255},
{32, 128, 255, 128, 32, 8, 2, 0, 2, 8, 32, 128},
{8, 32, 128, 255, 128, 32, 8, 2, 0, 2, 8, 32},
{2, 8, 32, 128, 255, 128, 32, 8, 2, 0, 2, 8},
{0, 2, 8, 32, 128, 255, 128, 32, 8, 2, 0, 2},
};

int pattern5[16][12] = {
{16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2},
{8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4},
{4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8},
{2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16},
{0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32},
{2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64},
{4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128},
{8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255},
{16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128},
{32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64},
{64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32},
{128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16},
{255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8},
{128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4},
{64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2},
{32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0},
};


void setup() 
{
for (uint8_t i = 0; i < totalNoLeds; i++) 
  {
  pinMode(leds[i], OUTPUT);
  } 
}

template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols]) 
{
display_pattern(&pattern[0][0], rows, cols);
}

void loop()
{
if (millis() - loopMillis < 5000)
{pulse = 1;}
if ((millis() - loopMillis) >= 5010 && (millis() - loopMillis)  <10000)
{pulse = 2;}
if (millis() - loopMillis >= 10010 && (millis() - loopMillis) < 15000)
{pulse = 3;}
if (millis() - loopMillis >= 15010 && (millis() - loopMillis) < 20000)
{pulse = 4;}
if (millis() - loopMillis >= 20010 && (millis() - loopMillis) < 25000)
{pulse = 5;}



switch (pulse) {
  case 1:
    display_pattern(pattern1);
    return;
  case 2:
    display_pattern(pattern2);
    return;
  case 3:
    display_pattern(pattern3);
    return;
    case 4:
    display_pattern(pattern4);
    return;
  case 5:
    display_pattern(pattern5);
if (millis() - loopMillis > 25010)
loopMillis = millis();    
    return;

}
}

Your column index is going one too far due to the '<=' comparison. You should also get in the habit of computing the size of an array properly. (divide by the size of 1 element) In this case, it doesn't matter, but in general, it does.

The loop got cleaned up a bit to keep track of time rather than an ongoing time count.

Since you are using pin 1, you can never use Serial for debugging, fyi.

// Larson scanner with PWM

#define DEBUG_UART 0

const uint8_t leds[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};  // PWM pins 2 through 13
const int totalNoLeds = sizeof(leds) / sizeof(leds[0]);
int pulse = 0;
const int maxPulse = 5;
const uint32_t loopDelay = 5000;

uint32_t loopMillis = 0;

void display_pattern(const int *pattern, size_t rows, size_t cols)
{
  static uint32_t previousMillis = 0;
  const uint16_t myInterval = 50;
  static uint8_t rowIndex = 0;

  for (size_t i = 0; i < cols; i++)
  {
    uint16_t linearIndex = rowIndex * cols + i;
    analogWrite(leds[i], pattern[linearIndex]);
#if DEBUG_UART
    Serial.println(linearIndex);
    //Serial.println(millis() - loopMillis);
    //Serial.println(patternd[indexI]);
    delay(10);
#endif
  }
  if (millis() - previousMillis >= myInterval)
  {
    rowIndex = ( rowIndex + 1) % rows;
    previousMillis = millis();
  }
}

int pattern1[17][12] = {
  {255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255},
  {75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 75},
  {50, 75, 255, 0, 0, 0, 0, 0, 0, 255, 75, 50},
  {25, 50, 75, 255, 0, 0, 0, 0, 255, 75, 50, 25},
  {10, 25, 50, 75, 255, 0, 0, 255, 75, 50, 25, 10},
  {5, 10, 25, 50, 75, 255, 255, 75, 50, 25, 10, 5},
  {0, 5, 10, 25, 50, 255, 255, 50, 25, 10, 5, 0},
  {0, 0, 5, 10, 255, 75, 75, 255, 10, 5, 0, 0},
  {0, 0, 0, 255, 75, 50, 50, 75, 255, 0, 0, 0},
  {0, 0, 255, 75, 50, 25, 25, 50, 75, 255, 0, 0},
  {0, 255, 75, 50, 25, 10, 10, 25, 50, 75, 255, 0},
  {255, 75, 50, 25, 10, 5, 5, 10, 25, 50, 75, 255},
  {75, 50, 25, 10, 5, 0, 0, 5, 10, 25, 50, 75},
  {50, 25, 10, 5, 0, 0, 0, 0, 5, 10, 25, 50},
  {25, 10, 5, 0, 0, 0, 0, 0, 0, 5, 10, 25},
  {10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10},
  {5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},
};

int pattern2[17][12] = {
  {255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {50, 75, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {25, 50, 75, 255, 0, 0, 0, 0, 0, 0, 0, 0},
  {10, 25, 50, 75, 255, 0, 0, 0, 0, 0, 0, 0},
  {5, 10, 25, 50, 75, 255, 0, 0, 0, 0, 0, 0},
  {0, 5, 10, 25, 50, 75, 255, 0, 0, 0, 0, 0},
  {0, 0, 5, 10, 25, 50, 75, 255, 10, 5, 0, 0},
  {0, 0, 0, 5, 10, 25, 50, 75, 255, 0, 0, 0},
  {0, 0, 0, 0, 5, 10, 25, 50, 75, 255, 0, 0},
  {0, 0, 0, 0, 0, 5, 10, 25, 50, 75, 255, 0},
  {0, 0, 0, 0, 0, 0, 5, 10, 25, 50, 75, 255},
  {0, 0, 0, 0, 0, 0, 0, 5, 10, 25, 50, 75},
  {0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 25, 50},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 25},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},
};

int pattern3[16][12] = {
  {16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2},
  {8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4},
  {4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8},
  {2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16},
  {0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32},
  {2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64},
  {4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128},
  {8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255},
  {16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128},
  {32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64},
  {64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32},
  {128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16},
  {255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8},
  {128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4},
  {64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2},
  {32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0},
};

int pattern4[10][12] = {
  {2, 0, 2, 8, 32, 128, 255, 128, 32, 8, 2, 0},
  {8, 2, 0, 2, 8, 32, 128, 255, 128, 32, 8, 2},
  {32, 8, 2, 0, 2, 8, 32, 128, 255, 128, 32, 8},
  {128, 32, 8, 2, 0, 2, 8, 32, 128, 255, 128, 32},
  {255, 128, 32, 8, 2, 0, 2, 8, 32, 128, 255, 128},
  {128, 255, 128, 32, 8, 2, 0, 2, 8, 32, 128, 255},
  {32, 128, 255, 128, 32, 8, 2, 0, 2, 8, 32, 128},
  {8, 32, 128, 255, 128, 32, 8, 2, 0, 2, 8, 32},
  {2, 8, 32, 128, 255, 128, 32, 8, 2, 0, 2, 8},
  {0, 2, 8, 32, 128, 255, 128, 32, 8, 2, 0, 2},
};

int pattern5[16][12] = {
  {16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2},
  {8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8, 4},
  {4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16, 8},
  {2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32, 16},
  {0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64, 32},
  {2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128, 64},
  {4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255, 128},
  {8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128, 255},
  {16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64, 128},
  {32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32, 64},
  {64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16, 32},
  {128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8, 16},
  {255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4, 8},
  {128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2, 4},
  {64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0, 2},
  {32, 64, 128, 255, 128, 64, 32, 16, 8, 4, 2, 0},
};


void setup()
{
  for (uint8_t i = 0; i < totalNoLeds; i++)
  {
    pinMode(leds[i], OUTPUT);
  }
  loopMillis = millis() - loopDelay;
  pulse = -1;
}

template <size_t rows, size_t cols> void display_pattern(const int (&pattern)[rows][cols])
{
  display_pattern(&pattern[0][0], rows, cols);
}

void loop()
{
  if ( millis() - loopMillis >= loopDelay ) {
    pulse = ( pulse + 1) % maxPulse;
    loopMillis += loopDelay;

    switch (pulse) {
      case 0:
        display_pattern(pattern1);
        break;
      case 1:
        display_pattern(pattern2);
        break;
      case 2:
        display_pattern(pattern3);
        break;
      case 3:
        display_pattern(pattern4);
        break;
      case 4:
        display_pattern(pattern5);
        break;
    }
  }
}

BLH64,

Thank you. The change to < cols helps, but as written, your modified sketch compiles, but does not work. However, with the change to the for loop, my sketch works BETTER, in that it goes longer without losing itself. Not sure why that is.

Roger