embedded software boot camp

The N_ELEMENTS macro

Friday, March 18th, 2011 by Nigel Jones

Many years ago I came across a simple macro that has proven to be quite useful. Its usual definition looks something like this:

#define N_ELEMENTS(X)           (sizeof(X)/sizeof(*(X)))

Its nominal use is to determine the number of elements in an incomplete array declaration. For example

void foo(void)
{
 uint8_t bar[] = {0, 1, 2, 3, 4};
 uint8_t    i;

 /* Transmit each byte in bar[] */
 for (i = 0; i < N_ELEMENTS(bar); ++i)
 {
  txc(bar[i]);
 }
}

Clearly this is quite useful. However, once you have this macro in your arsenal you will eventually run into a conundrum. To illustrate what I mean consider the following code:

#define BUF_SIZE    (5)
void foo(void)
{
 uint8_t bar[BUF_SIZE] = {0, 1, 2, 3, 4};
 uint8_t    i;

 /* Transmit each byte in bar[] */
 for (i = 0; i < BUF_SIZE; ++i)
 {
  txc(bar[i]);
 }
}

This uses the classic approach of  defining a manifest constant  (BUF_SIZE) and then using it to define the array size and also as the loop limit. The conundrum is this: is one better off using N_ELEMENTS in this case as well. In other words, is the following better code?

#define BUF_SIZE    (5)
void foo(void)
{
 uint8_t bar[BUF_SIZE] = {0, 1, 2, 3, 4};
 uint8_t    i;

 /* Transmit each byte in bar[] */
 for (i = 0; i < N_ELEMENTS(bar); ++i)
 {
  txc(bar[i]);
 }
}

This code is guaranteed to operate on every element of the array bar[] regardless of what is done to the array declaration. For example:

#define BUF_SIZE    (5)
void foo(void)
{
 uint8_t bar[BUF_SIZE + 1] = {0, 1, 2, 3, 4, 5};
 uint8_t    i;

 /* Transmit each byte in bar[] */
 for (i = 0; i < N_ELEMENTS(bar); ++i)
 {
  txc(bar[i]);
 }
}

In this case I have changed the array declaration. The code that uses N_ELEMENTS would still work while the code that used BUF_SIZE would have failed. So from this perspective the N_ELEMENTS code is more robust. However I don’t think the N_ELEMENTS based code is as easy to read. As a result I have oscillated back and fore over the years as to which approach is better. My current view is that the N_ELEMENTS approach is indeed the better way. I’d be interested in your opinion.

44 Responses to “The N_ELEMENTS macro”

  1. Ryan says:

    Thanks for posting this, Nigel! I’ve been reading this blog for a while and this post really struck home with me as one of those things I’ve thought about every time I see the construct.

    I tend to agree that the N_ELEMENTS approach is best. It also causes me to question whether it’s at all necessary to have the BUF_SIZE macro. It would seem that if you use N_ELEMENTS for all array traversals then the BUF_SIZE macro serves only to satisfy one of the many standard “no magic numbers” coding rules/guidelines. I personally think that at this point, the two declarations

    uint8_t foo[BUF_SIZE]
    and
    uint8_t foo[427]

    are the same. One might argue that the macro definition allows the opportunity to write a descriptive comment as to why that value is chosen for the array size, but that comment could also simply live above the array declaration. You may as well just

    #define BUF_SIZE N_ELEMENTS(foo)

    On the topic of array declarations, what do you feel about specifying the size of arrays that are initialized at compile time and should always be exactly the size of the initialized list? Often times this is done for initializing a table of function pointers or other values that correlate to an enumerated list, such as

    enum {BLUE=0, RED, GREEN, NUM_COLORS};
    uint8_t colorToNumberMap[NUM_COLORS] = {0, 1, 2};

    I would argue that such arrays should be initialized and checked like this:
    uint8_t myArray[] = {0, 1, 2};
    COMPILEASSERT(N_ELEMENTS(myArray) == NUM_COLORS);

    The advantage of this approach is that it protects against the case where the developer adds another item to the enumeration but does not add an entry to myArray. The uninitialized entry in myArray could be the source of a hard to find bug.


    Ryan

    • Nigel Jones says:

      It’s been a very long week, so I’m a bit punch drunk – so if my answer is nonsensical then please forgive me! What I tend to do is use a complete array declaration using NUM_COLORS. Then if I forget to supply sufficient initializers the compiler should tell me. For example:
      enum {BLUE=0, RED, GREEN, BLACK, NUM_COLORS};
      uint8_t myArray[NUM_COLORS] = {0, 1, 2}; <— Should generate a compilation error.

      Having said that I am a huge fan of catching problems at compilation time, so your approach seems very good to me. BTW what do you use for COMPILEASSERT?

      • Ryan says:

        Most compilers that I have seen do not generate an error for the case you’ve shown. I believe it’s valid to specify an initializer list that’s less than the total size of the data structure. Unfortunately I don’t have a copy of the C specification to reference, so my evidence is strictly anecdotal.

        My COMPILEASSERT macro looks like:

        #ifdef __cplusplus
        #define C_STORAGECLASS “C”
        #else
        #define C_STORAGECLASS
        #endif

        #define COMPILEASSERT(x) extern C_STORAGECLASS char CompileAssert_##__FILE__##__LINE__[(x) ? 1 : -1]

        The error message you get when the assert occurs is usually something like “negative subscripts are not allowed”. The C_STORAGECLASS modifier exists due to the problems I ran into with name mangling when I first tried using it in a C++ project. The __FILE__ and __LINE__ inclusions exist to prevent namespace clashes if you have more than one COMPILEASSERT in a source file.

        • Nigel Jones says:

          That’s highly likely that not all compilers generate an error. Thanks for the information on how you implement COMIPLEASSERT – it looks like a very clean implementation.

        • Ashleigh says:

          What you say is correct – incomplete initialisers are permitted and the bit on the end will get set to 0’s.

          An initialiser that is too big will cause a compile error.

          • David Brown says:

            Too many initialisers in a list will give not give an error – it is correct C, with the compiler required to ignore the extra initialiser. Most compilers will generate a warning, however.

      • Lundin says:

        A standard C compiler shall not generate a compilation error for an incomplete initializer list. The C standard states that if an initializer list to an “aggregate” (ie an array, struct or union) doesn’t set all members explicitly, the rest of them shall be set as if they had static storage duration.

        Plainly: if you don’t init all items of your array, the ones you don’t set will be set to zero. This is guaranteed by the C standard, and that’s why we can write code like array [N] = {0}; rather than array[N] = {0,0,0,0,…

        • Nigel Jones says:

          Thanks for the comment Daniel. I use Lint so regularly that I sometimes get confused between the compiler and Lint. If you have insufficient initializers, Lint will generate: “Info 785: Too few initializers for aggregate ‘foo’ of type ‘int [5]'”

    • David Brown says:

      Although it’s a common idiom to add an extra “number of entries” tags in enums, it’s not good typing. BLUE, RED and GREEN are colours, while NUM_COLORS is an integer. The C type system is pretty weak already – it doesn’t help to make it weaker.

      In particular, if you use static error checking that makes a distinction between enumerated types and the underlying integer representation, then you’ll get errors or warnings. For example, if you write this:

      typedef enum { blue, red, green, noOfColours } colours;
      const char* colourName(colours c) {
      switch (c) {
      case blue : return “Blue”;
      case red : return “Red”;
      case green : return “Green”;
      }
      return “Bad colour”;
      }

      Compile it with gcc with the -Wswitch flag (part of the common “-Wall” warnings), and gcc will tell you you’ve forgotten “noOfColours” in the switch.

      Unfortunately, C has no good way of expressing “the last enum constant plus one”. The best you can do is something like this:

      typedef enum { blue, red, green, black, } colours;
      enum { num_colours = black + 1 };

      It would be very nice if C had “attributes” like Ada does, where you can write something like :

      type colours is (blue, red, green, black);
      noOfColours : constant := colours’pos(colours’last) + 1;

  2. Chris Knight says:

    I’m a big fan of the N_ELEMENTS macro. It’s similar to the foreach construct in C# which I find an elegant way of iterating through an array.

    I tend to use macros like BUF_SIZE for array declarations since it can also be used for bounds checking. About the only time I declare arrays like bar[] without an explicit array size is for strings or arrays of strings. In that case N_ELEMENTS really is the only way to tell when you’re done looping through an array.

  3. Rosly says:

    In our company we use similar construct to N_ELEMENTS (we name it table_size), we cannot imagine the other coding style. Also in most cases we do not define the size of the table. It is not required since you specify the initializer list. For instance:

    enum { red = 0, blue, green, yellow };
    char* conv_vab[] = { “red”, “blue”, “green”, “yellow” };

    printf(“foreach collor\n”);
    for(i = 0; table_size(conv_tab); i++)
    {
    printf(“%i = %s”, i, conv_tab[i]);
    }

  4. Les Beckham says:

    Can anyone comment on the pros and cons of this implementation (below) vs. the one suggested by Nigel? I’ve been using this approach for years. Is it basically the same? I would be concerned about pointer indirection vs. a specific reference to the first element of the array.

    #define array_length(arr) ((int) (sizeof((arr)) / sizeof((arr)[0])))

    BTW: the default font for this forum makes a zero look like a lower case letter ‘o’. You might want to investigate whether that could be changed since this is a forum that has a very code-based slant. Maybe there are markups that can be applied when we want certain portions of a forum comment to look like code?

    • Rosly says:

      You do not have to cast the result into int, since int may be 32bit while size_t (default type of sizeof) can be 64bit (for 64 bit machine), also the size_t is unsigned while int is signed (such casting is error prone).

      • Les Beckham says:

        What I was really asking was about the sizeof((p[0])) vs sizeof(*(p)) difference — I should have changed only the part about which I was asking, I guess. The names changed as I just pasted in some existing code.

        I understand your point about the cast to int. Perhaps this doesn’t belong in the macro. However, I have had some compilers warn about array indices not being int — probably a long time ago. And, since this macro is usually used in a comparison against a variable used as an array index, this long-used macro had the cast.

        Comments, anyone?

  5. Lundin says:

    First, I don’t think the array declaration change is a valid argument. Anyone who hardcode raw numbers in the middle of their code get what they deserve. A proper change to that program would be to change BUF_SIZE by 1.

    I think the N_ELEMENTS macros is worse practice than the BUF_SIZE method, for the following reasons:

    – It doesn’t work with arrays that have “decayed” into pointers, ie arrays passed to functions. This would be the major argument against it. (Nor does it work with dynamically allocated arrays)

    – It is slightly less readable, since BUF_SIZE could be named to something reminding of the array variable name, so that the reader understands that they belong together.

    – The macro may be slightly less efficient on some systems. size_t is guaranteed to be a large integer type >= unsigned int, perhaps far larger than needed for a small array. Since the iterator is declared uint8_t in these examples, that is indeed the case here.

    – Function-like macros are generally considered very poor practice, and by using them you will get in trouble with every decent test tool / static analyser out there. They also violate advisory rules of MISRA-C.

    – Regarding catching problems at compilation time: accidentally calling N_ELEMENTS() without a parameter specified results in undefined behaviour, while any slip when typing BUF_SIZE most likely results in a compiler error.

    • Travis says:

      I agree on all counts.

    • Lundin says:

      Yet another major argument supporting the use of BUF_SIZE:

      If you have a large array, that is initialized with a lot of data, you really do want compiler-time checking to ensure that you didn’t accidently leave out one item:

      const uint8 alphabet [BUF_SIZE] = {‘A’, ‘B’, ‘C’ …

      If I add too many letters, I will get a compiler error, so I definitely want a “BUF_SIZE” for this. And since it is already there to enable the item counting by the compiler, why not use it instead of invent some macro doing the same thing?

      I also frown at the unspecified array size [] version so many seem to use. In embedded systems, everything must be deterministic, especially memory sizes. So there is really no reason to let arrays get their size through number of items entered in the initialization list, that syntax smells of PC programming.

    • Kraig says:

      What about defining “BUF_SIZE” using Nigel’s method:
      const uint32 u32SupportedBaudRates[] =
      {
      4800,
      9600,
      19200,
      38400,
      57600,
      76800,
      115200
      };
      #define NUM_SUPPORTED_BAUD_RATES (sizeof(u32SupportedBaudRates) / sizeof(u32SupportedBaudRates[0]))

    • ryu says:

      One problem I’ve seen mentioned in this thread a few times is this macro doesn’t work correctly on arrays that have “decayed” into pointers. If you happen to be using gcc, you can use builtin’s to catch this:

      From the linux kernel:

      #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))

      conditionally compiled elsewhere depending on arch/compiler:
      #define __must_be_array(a) BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(typeof(a), typeof(&a[0])))

  6. Bruce Jones says:

    Since I don’t work as a design engineer this is a comment from the “peanut gallery.”

    Declaring an array to be [BUF_SIZE + 1] strikes me as not a good coding practice since it seems to break the contract implicit in the BUF_SIZE constant; you no longer can trust that any array you’re examining is of an expected length. It may be better to define multiple array length constants, and have them referenced in the documentation of the software architecture along with any oddball arrays that are of a unique length. That should assist debugging and maintenance of the software.

    I think that the N_ELEMENTS macro is easier to read as it refers back to the array being iterated over, rather than to a boundary constant that may be defined at the top of the file or even in another module. I’m not sure either approach affects code reuse as you would still have to modify the boilerplate code with either the array you’re looping through or the boundary you need to stop at.

  7. Jan says:

    Thanks for the article and discussion. My 2¢:

    * Personally, I consider N_ELEMENTS() macro partially usefull only, not a panacea. Lundin mentioned the reasons, “array-to-pointer decay” and possible inefficiency.

    * IMHO the “BUF_SIZE+1” example is rather inappropriate for comparison. The loops have distinct semantics (traverse thru all elements of an array vs. loop specified times and do something with each array element visited), ie. we’re comparing apples and oranges.

    Anyway, am I the only blind not seeing anything wrong in the Ryan’s enum example? Why should plain C compiler/lint complain? NUM_COLORS shoud be treated as integer constant with value of 3, and there are exactly 3 initializer items for 3 element array. It does not matter how many members make enum type containing NUM_COLORS. Even my splint says nothing about insuffictient initializers in strict mode (there’re 7 warnings: unused enums [3], inits with incompatible size [2], unused variable, export w/o declaration in .h file). (His static assetion with “unbounded” myArray[] is a nice solution to C implicit zero initializers, thanks for the point.)

    If you’re interested in compile-time asserts, maybe “verify.h” (eg. http://www.opensource.apple.com/source/text_cmds/text_cmds-71/sort/verify.h) and its rationale inside may be useful.

    H.

  8. Ashleigh says:

    I have also oscillated between the fully static size value as named number (used everywhere) and the N_ELEMENTS approach.

    I eventually settled on the use-a-named-number-everywhere approach, because it tends to force everything everywhere to change with a single constant updated, and in reading the code you get an implicit understanding of what the thing is as well as its size, based on the immediate context (eg in a for loop as you iterate through something, knowing a bit more about the thing than just it has an upper bound gives more information and makes long term code maintenance easier).

    Admittedly it could go either way, however i’ve spent a lot of time playing in communication protocols and for these you really want to avoid the N_ELEMENTS approach because anything that plays with sizes, offsets, pointers into buffers, or otherwise messes with your protocol stack causes havoc. Similarly declaring things as [X + 1] is a practice that should get a rap over the knuckles because that “+1” is an unexplained magic number.

    My conclusion is that which way to go depends a lot on the context – what problem you are solving determines the approach to take.

  9. David Collier says:

    I think that the fact that C doesn’t provide a function to do this job is a missing feature of the language.

    if you had elementsof as well as sizeof, we wouldn’t be having this discussion 🙂

  10. Bill Emshoff says:

    My #1 concern with the macro is, as Lundin indicated, that it will accept a pointer as a parameter without even a compiler warning but produce the wrong result, as in:
    char s[80]; // N_ELEMENTS(s)=80
    char *p = s; // N_ELEMENTS(p)=4 (or whatever sizeof (char*) is on your CPU)
    On the other hand, the alternative approach using a named constant BUF_SIZE can lead to the following trap (thankfully tools such as PC-Lint can catch this):
    char s[STRING_BUF_SIZE];
    for (i=0; i<BUF_SIZE; ++i)
    // oops

    However, if we can use C++ as a better C, there's a cleaner solution:
    template size_t inline lengthof(Element (&)[Length]) { return Length; }
    // lengthof(s)=80
    // lengthof(p) fails with a compile error

    Of course, if we’re in C++, we’ve also got full-fledged template container classes in our toolbox as well.

  11. vijay says:

    Hi Nigel Jones,

    I had gone through your post it is very helpful, i had used the same approach “enum colors” without assert, my compiler didnt generate an error but its my PRQA (Programming reasearch Quality analysis ) tool given me level 7 warning that is “array declarion can be out of bonds ” i wondered about the warning didnt get any inputs on net but while going through u r comments i realize why it generated warning. Thanks for the post and i will include the assert in my code.

    Thanks
    Vijay

  12. tomkawal says:

    Definitely, I opt for simple, non-obfuscated code, so
    #define BUF_SIZE NUMBER is the nice and reliable way.

    The code is simple to maintain with
    some several defines at the top level of includes,
    Line of comment is enough then to raise attention.

  13. Hi,

    I seem to incline towards Jan H.

    Since we don’t work on the arrays directly as we pass them across functions as pointers, we would end up with multiple styles of array bounds usage.
    I feel that by hacking through the way C works, we are just trying to bring in the fan-folk from scripting language to work with C – bringing in constructs from other languages though, there is no inherent support.

  14. Ian says:

    To me the most glaring problem is specifying an array size twice. I would *never* define BUF_SIZE for an array that I was going to initialise in this way. My preferred solution is this:

    uint8_t bar[] = {0, 1, 2, 3, 4};
    static const uint8_t barSize = sizeof(bar)/sizeof(bar[0]);

    This resolves the problem with arrays decaying to pointers, it is efficient, there can be no mistake in the intended size of the array versus the actual size, it avoids the type mismatch with enum, and it doesn’t even use a macro.

    The cost is the storage associated with barSize. You can optimise this to a smaller type easily enough.

    • Benjamin says:

      For many compilers, the storage associated with barSize isn’t an issue. If you don’t take the address of the variable, the compiler can optimize the variable away and replace all instances of it with a copy of the associated value (essentially, the same result as if you used a macro).

  15. Ian says:

    Apologies – finding a smaller type than uint8_t might be problematic on many architectures!

    If you don’t want to define a static const uint8_t for barSize, you can still #define BAR_SIZE sizeof(bar)/sizeof(bar[0]).

    If you want to double-check your array initialisation, you can write:

    uint8_t bar[] = {0, 1, 2, 3, 4};
    #define BAR_SIZE sizeof(bar)/sizeof(bar[0])
    COMPILEASSERT(BAR_SIZE == WHAT_IT_SHOULD_BE);

    I think that covers all bases.

    • Steve says:

      Having read through all the comments, and based on my own experiences; I have to say that I agree with this approach above all the others. It’s clean, it’s clear, and doesn’t require you to count how many items are in your array just so you can create a non-magic number.

    • Lundin says:

      What’s the point with that? You’d have

      #define WHAT_IT_SHOULD_BE 5
      #define BAR_SIZE sizeof(bar)/sizeof(bar[0])

      You have just added a duplicate definition of the very same thing. Why couldn’t you use WHAT_IT_SHOULD_BE for the array size in the first place?

      By the same logic you can continue on recursively:

      #define WHAT_IT_REALLY_SHOULD_BE 5
      assert(WHAT_IT_SHOULD_BE == WHAT_IT_REALLY_SHOULD_BE);

      …and so on. 🙂

      • Ian says:

        Those aren’t duplicate definitions.

        There are a couple of mistakes we’re trying to avoid here. The first is the actual array size being wrong. The second is the array size being right but the number of elements initialised being wrong.

        You can force the array size by explicitly specifying it in the definition (uint8_t foo[BUF_SIZE]), but then it is hard to detect the mistake of specifying too few initialisers.

        So I suggest it is better to avoid forcing the array size, so that if the number of initialisers is wrong then the array size will also be wrong, which is something you can easily catch with a compiler assert macro. If we take this approach, then the two definitions, WHAT_IT_SHOULD_BE, and BAR_SIZE are not the same thing. One is the definition of how big the array *should* be and the other is how big the array *actually* is. If you have made a mistake then they will indeed be different – something that is easy to detect.

        • Lundin says:

          You can still use a fixed size BUF_SIZE macro and assert it versus sizeof(array)/sizeof(element).

          You are really arguing for the use of assert here (which is indeed good practice), and not for BUF_SIZE vs N_ELEMENTS. Either macro form does not rule out assert. And as mentioned earlier, any decent static analyser will find that bug as well.

          • Ian says:

            Here’s my counter-example:

            #define BAR_SIZE 5
            uint8_t bar[] = {0, 1, 2, 3}; /* Mistake – should have been {0, 1, 2, 3, 4} */
            COMPILEASSERT(sizeof(bar)/sizeof(bar[0]) == BAR_SIZE); /* No error */

            I agree though that you might be able to find this bug with a static analyser.

          • Ian says:

            Sorry, ignore my other comment. This is the counter-example I meant to give:

            #define BAR_SIZE 5
            uint8_t bar[BAR_SIZE] = {0, 1, 2, 3}; /* Mistake – should have been {0, 1, 2, 3, 4} */
            COMPILEASSERT(sizeof(bar)/sizeof(bar[0]) == BAR_SIZE); /* No error */

            The ‘counter-example’ in my other comment was in fact the example where the error *would* be caught by the compiler!

          • Lundin says:

            Ok, you would have to change the assert statement.

            #define BAR_SIZE 5
            uint8_t bar[BAR_SIZE] = {0, 1, 2, 3}; /* Mistake – should have been {0, 1, 2, 3, 4} */

            #define UNINITIALIZED 0
            assert(bar[BAR_SIZE-1] =! UNINITIALIZED);

            If any number of elements at the end of the initializer list are forgotten, then the final element will be one of them. The C standard guarantees that the last element will then be initialized implicitly to zero.

            So you can assert() no matter method used.

        • Eric Miller says:

          Lundlin,

          The presence of a zero may or may not indicate that the corresponding location in the array was uninitialized.

          Consider:

          #define BAR_SIZE 5
          uint8_t bar[BAR_SIZE] = {0, 1, 0, 1, 0};

          #define UNINITIALIZED 0
          assert(bar[BAR_SIZE-1] != UNINITIALIZED);

          The assertion would fire in this case even though bar[4] was initialized.

          Ian’s example does not suffer from this drawback.

  16. Mike says:

    Hello all,
    I am desperately trying to replace bitmap? “MM logo” in text Arduino 1.0.6 – working with Teensy3.1 i.e:
    #ifndef SPLASHSCREEN_h
    #define SPLASHSCREEN_h

    #include “ILI9341_t3.h”
    #include “Adafruit_STMPE610.h”
    #include “Configuration.h”

    // Array of points which comprise the MM logo
    static const uint8_t fj[] = {
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x00,

    };

    class SplashScreen {
    public:
    SplashScreen(int, int, ILI9341_t3*, Adafruit_STMPE610*);
    void drawMe(void);

    private:
    int xLoc;
    int yLoc;
    ILI9341_t3* ptr_tft;
    Adafruit_STMPE610* ptr_ctp;
    };

    #endif // SPLASHSCREEN_h

    and here the .cpp
    // START screen settings /

    #ifndef SPLASHSCREEN_cpp
    #define SPLASHSCREEN_cpp

    #include “SplashScreen.h”
    SplashScreen::SplashScreen(int xLoc, int yLoc, ILI9341_t3 *ptr_tft,
    Adafruit_STMPE610 *ptr_ctp) {
    this->xLoc = xLoc;
    this->yLoc = yLoc;
    this->ptr_tft = ptr_tft;
    this->ptr_ctp = ptr_ctp;
    }

    void SplashScreen::drawMe(void){
    ptr_tft->fillScreen(ILI9341_BLUE);

    int16_t color0 = ptr_tft->color565(140, 143, 138);
    int16_t color1 = ptr_tft->color565(204, 204, 204);

    ptr_tft->setTextColor(ILI9341_YELLOW); // Mike Oven text//
    ptr_tft->setTextSize(3);
    ptr_tft->setCursor(15, 15);
    ptr_tft->println(F(“Mike’s OVEN”));
    ptr_tft->setTextSize(2);
    ptr_tft->setCursor(40, 48);
    ptr_tft->println(F(“REFOW CONTROL”));

    for (int i = 0; i < 48; i+=8){
    int16_t color; // select the color according to the face dark grey /
    if (i/8 fillTriangle(fj[i+0]+xLoc, fj[i+1]+yLoc, fj[i+2]+xLoc,
    fj[i+3]+yLoc, fj[i+4]+xLoc, fj[i+5]+yLoc, color);
    ptr_tft->fillTriangle(fj[i+0]+xLoc, fj[i+1]+yLoc, fj[i+4]+xLoc,
    fj[i+5]+yLoc, fj[i+6]+xLoc, fj[i+7]+yLoc, color);
    }

    ptr_tft->setTextColor(ILI9341_YELLOW);
    ptr_tft->setTextSize(2);
    ptr_tft->setCursor(xLoc+4, yLoc+200);
    ptr_tft->println(F(“START–>>>”));

    ptr_tft->setTextColor(ILI9341_GREEN);
    ptr_tft->setTextSize(2);
    ptr_tft->setCursor(xLoc+13, yLoc+220);
    ptr_tft->println(F(“TO SETTINGS”));

    while (!ptr_ctp->touched());

    }

    #endif // SPLASHSCREEN_cpp

    I can not figure out what kind of file this is and how to replace the Bitmap

  17. Mike says:

    I did it !
    Thanks,
    mike

Leave a Reply to Ashleigh

You must be logged in to post a comment.