Incrementing single character to output to OLED display, but it increments by 2 rather than 1

StackOverflow https://stackoverflow.com/questions/16912343

  •  31-05-2022
  •  | 
  •  

Question

Having a strange problem. I finally figured out how to turn a variable "i" that increments inside a loop into a string that I can pass to a function which turns it into a character that is output to a display.

The trouble is, the value increments by 2 rather than by 1! I have tried a few different things but I'm not sure where the problem lies.

Initially, I thought that perhaps, if you have a function that calls another function, and they both use the same variable to increment (i.e. for(int i=0; i<10; ++i)), then the "outer" function would have "i" increment by two because it is incremented once in the "outer" loop and once in the "inner" loop. However I think this is not the case. If it were so "i" would get incremented by more than two in my case, and I tried changing all the counter variables to different names with no change. It would be a silly way for the language to work, anyway. Unless of course it IS the case, I'd love to be enlightened.

Here is the code block giving me trouble:

for (int i=0; i<100; i++){
    char c[1]={0};                    // Create variable to hold character
    sprintf(c,"%d", i);               // Copy value of "i" as string to variable
    writeText(c,0,0,WHITE,BLACK,3);   // Write the character "c" at position 0,0. Size 3
    OLED_buffer();                    // Send display buffer
    delay_ms(500);                    // Delay before next increment
}

Here is writeText():

void writeText(unsigned char *string, int16_t x, int16_t y, uint16_t color, uint16_t bgcolor, uint8_t size){
    unsigned char letter;
    for (int i=0; i<strlen(string); ++i){
        letter = string[i];
        if (letter != NULL){
            drawChar(x+(i*6*size),y,letter,color,bgcolor,size);
        }
    }
}

Here is drawChar, called by writeText:

void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size) {

    if((x >= _width)            ||  // Clip right
    (y >= _height)              ||  // Clip bottom
    ((x + 5 * size - 1) < 0)    ||  // Clip left
    ((y + 8 * size - 1) < 0))       // Clip top
    return;

    for (int8_t i=0; i<6; i++ ) {
        uint8_t line;
        if (i == 5)
        line = 0x0;
        else
        line = font[(c*5)+i];
        for (int8_t j = 0; j<8; j++) {
            if (line & 0x1) {
                if (size == 1) // default size
                drawPixel(x+i, y+j, color);
                else {  // big size
                    fillRect(x+(i*size), y+(j*size), size, size, color);
                }
                } else if (bg != color) {
                if (size == 1) // default size
                drawPixel(x+i, y+j, bg);
                else {  // big size
                    fillRect(x+i*size, y+j*size, size, size, bg);
                }
            }
            line >>= 1;
        }
    }
}

And finally drawPixel, called by drawChar (though I sincerely doubt the problem goes this deep):

void drawPixel(int16_t x, int16_t y, uint16_t color) {
    if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
    return;

    // check rotation, move pixel around if necessary
    switch (getRotation()) {
        case 1:
        swap(x, y);
        x = WIDTH - x - 1;
        break;
        case 2:
        x = WIDTH - x - 1;
        y = HEIGHT - y - 1;
        break;
        case 3:
        swap(x, y);
        y = HEIGHT - y - 1;
        break;
    }

    // x is which column
    if (color == WHITE)
    buffer[x+(y/8)*SSD1306_LCDWIDTH] |= _BV((y%8));
    else
    buffer[x+(y/8)*SSD1306_LCDWIDTH] &= ~_BV((y%8));
}

The result of all this is that the display shows a number that increments by twice the length of my delay. So for instance the delay here is 500ms, so it updates every 1 second. Rather than going

1, 2, 3, 4, 5...

as it should, it goes

1, 3, 5, 7, 9...

Does anyone have any advice to offer? I'm sure that it is some stupid simple problem in my initial loop, but I just can't see it right now.

I am using Atmel Studio 6 to program an xmega32a4u. The library functions shown are part of the Adafruit graphics library for the SSD1306 128x32 OLED that I ported into Atmel Studio.

Thanks so much for the help!

UPDATE: Although my code did have some problems, the real issue was actually with the way the OLED was addressed. Apparently Adafruit forgot to set the correct page address in their libraries for the display. As the controller on the display can support a 128x64 as well as a 128x32 display, the "end" address for the display must be set correctly so that the controller knows which parts of the display RAM to access. That function was missing. Because of how the display writes the data ram and because it didn't "know" that the display was only 32 pixels tall, every other frame sent to the display was actually being written to the "bottom" part of the display ram (i.e. the part that WOULD appear if the display was 128x64, twice as tall). So now everything works great!

A big thanks to unwind, if not for his suggestion about the display timing getting me thinking about that side of the issue, it might have taken me a long time to figure out the problem.

Était-ce utile?

La solution

You have a buffer overrun.

This:

char c[1]={0};                    // Create variable to hold character
sprintf(c,"%d", i);   

is not allocating enough room in the string buffer c to hold a single-digit string. Remember that strings in C are 0-terminated, so a 1-digit string requires 2 characters. Since your loop goes to 100, you will eventually write 3 + 1 characters to the buffer, overwriting even more. Not sure how you imagined this to work.

It's likely that sprintf() is overwriting your loop index variable, although anything could happen since you're hitting undefined behavior.

Change those two lines to:

char c[8];
sprintf(c, "%d, i);

or, if you have it, use snprintf():

snprintf(c, sizeof c, "%d", i);

to get protection against buffer overrun.

If you just want the least significant digit of i, do something like this:

snprintf(c, sizeof c, "%d", i % 10);

This uses the modulo (% in C) operator to compute the remainder when dividing by 10, which is the "ones" digit.

UPDATE After reading your comments, I'm inclined to believe that your problem is one of timing, maybe the display contents aren't refreshed when you expect them to be, so that you only see every second "frame" you build. You should be able to use a debugger to pretty easily see that you do indeed build and display each numeric value, by breaking after the sprintf() in the root loop.

UPDATE 2: Just since it bothered me, your writeText() function can be simplified quite a lot, the comparison of a character against NULL is weird (NULL is a pointer, not a character) and pointless since you've already checked with strlen():

void writeText(const unsigned char *string, int16_t x, int16_t y, uint16_t color,
               uint16_t bgcolor, uint8_t size)
{
  while(*string != '\0')
  {
    drawChar(x + (i * 6 * size), y, *string, color, bgcolor, size);
    ++string;
  }
}

Also note the const; functions that take pointers to data that the functions only read should always be declared const.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top