NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » String manipulation

October 12, 2011
by claesz
claesz's Avatar

This is really embarrassing, but C is not my strong side and I just got my NK the other day and though I'd test it out by making a scrolling text through string manipulation. I know there are many examples of LCD scrollers on this forum that are all much better and more efficient, and this was just meant as a bit of practice for me. The thing works (though I must be overwriting some null str termination somewhere as it from time to time it will mess itself up).

Now, my though was take a text string. Start by adding screen width number of spaces + text string. Only spaces will show. Next step add width-1 number of spaces + text. One letter will show, etc, etc. When number of spaces = 0, just write text+1, text+2 etc. If strlen less than screen width add spaces to fill up.

The principle works of course, all though it is a silly way of making a scroller. However, and finally I get to my question. The line //printline[21] = 'x'; has been commented out. In my mind that should actually add an x just off the screen every time (or next line if you don't cut off the string), since the print function will print from start of array until end. Here I must have misunderstood something in how C works, because in reality it will add an "x" as the first letter of the text-to-show string (the first 20 chrs are spaces) and move it along the whole screen as it scrolls. Originally I wanted to add a "0" to avoid writing a string so long that it continued on to the next line. For testing purposes I have put X there now. What am I misunderstanding?

I know this is not a NK question and I should have found a C forum to post this question in, but you guys seem friendly so I though I'd give it a shot.

/ initialload.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

#include <stdio.h>
#include <string.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

// PIN DEFINITIONS:
//
// PC2 -- RED LCD
// PC3 -- GREEN LCD
// PC4 -- Switch to start stop scrolling

int main() {
    // LED as output
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    lcd_home();

    int maxlen = 20;
    int waittime = 75;
    int startpos = maxlen;
    int i; // a counter
    int appendMe;
    char text[256] = "This is just a scrolling text.  Seems to work somehow."; 
    char outstr[256];
    char printline[21];

    lcd_clear_and_home();

    while(1) {
        i = 0;
        strcpy(outstr, "\0");
        strcpy(printline, "\0");
        while(i <= startpos) {
            strncat( outstr, " ", i); // adds spaces in front
            i++;
        }
        if (startpos<0) {
            strncat(outstr, text+(0-startpos), maxlen); // removes passed chrs from text. 0-x to make pos
            outstr[maxlen+1] = '\0'; // nullzero. If maxlen less than outstr, strncpy will not nullterminate
        } else {
            strncat(outstr, text, maxlen); // adds text to preceeding spaces
            outstr[maxlen+1] = '\0'; // nullzero. If maxlen less than outstr, strncpy will not nullterminate                            
        }
        strncpy(printline, outstr, maxlen);  // puts everything into a nice output array with a max 20 len 
        //printline[21] = 'x'; // was supposed to be nullzero to terminate string. If maxlen less than outstr, strncpy will not nullterminate
        if (strlen(printline) < 1) {        
            startpos = maxlen; // all text used, restart
                lcd_line_two();
                fprintf_P(&lcd_stream, PSTR("Restart"));
        }
        if (strlen(printline) < (maxlen)) {  // if there is still space on the line after printing the remaining text
            appendMe = maxlen - (strlen(printline));
            strncat(printline, "                                         ", appendMe); // basically adds enough space to cover the EOL      
        }

        fputs(printline, &lcd_stream); // prints string             
        delay_ms(waittime);
        startpos--;
        lcd_home();
    }

    return 0;
}
October 12, 2011
by claesz
claesz's Avatar

Ok, I am sure I am missing something very basic here, but I cannot for the life of me figure out what. If I do

printline[3] = 'x';

There will be an x in a fixed spot, 3rd chr from left, not affected by the scrolling. To me that makes sense, you are setting the third letter of the string just before it is printed. The content of the printline array should not matter - it will always be the third chr that is shown and the rest will just appear to scroll under it. However, if I do printline[21] = 'x' the "x" will scroll along with the rest of the text which makes absolutely no sense.

I am beginning to think I am losing my mind, and any advice will be appreciated.

October 12, 2011
by claesz
claesz's Avatar

I know I am talking to myself here, but still wanted to give yet another updated. I suspect the declared length of the array printline was the problem here and basically the x overwrote the array string termination. Still doesn't explain the weird behaviour where the "x" starts scrolling with the text. array[21] should still remain in position 21 and not suddenly become dynamic, scrolling along with the text. Also doesn't explain why replacing "x" with 0 would be a problem. I would only have overwritten a string termination with another.

October 13, 2011
by hevans
(NerdKits Staff)

hevans's Avatar

Hi claesz,

I have a theory as to what is happening here, its a bit of a long shot but it might explain what you are seeing.

I believe that your thinking about where the null termination is on a char array is off by 1. If I declared a char myStr[5], it would give me an array of 5 characters to work with, this means I could safely address myStr[0] through myStr[4]. If I want to this to be a happy null terminated string I have to make sure myStr[4] is a null 0. Addressing myStr[5] is a buffer overrun, and depending on where on the stack your buffer is living you are quite possibly blowing away the first character in another array. I think you had this in mind when you made your printline array 21 characters wide, but you didn't remember you can only safely address length-1, so setting printline[20] to a null 0 would still give you a 20 character string.

It is fairly bizarre that the x ended up at the beginning of your output string, I can't really explain that.

Humberto

October 13, 2011
by claesz
claesz's Avatar

Thanks Humberto!

I believe you are right about my mistake regarding the max array length. Rather a silly mistake. Hopefully I would have noticed it earlier myself if it wasn't for the fact that the "x" started to scroll. Through me off a bit on my attempt to track down the problem Very strange behaviour, but I guess that is what can happen when you overrun the buffer.

Thanks a lot! C

October 13, 2011
by claesz
claesz's Avatar

Just to close off the thread properly, I am adding the entire code, which now seems to work fine for me. (There is a switch on PC4 to stop/start the scroll and a red diode on PC2 and green on PC3 to indicate scroll status (scrolling or not) none of which are needed of course).

The code is messy and there are many much better ways of making a scrolling text. This was just for me to practice a bit.

// initialload.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

#include <stdio.h>
#include <string.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

// PIN DEFINITIONS:
//
// PC2 -- RED LCD
// PC3 -- GREEN LCD
// PC4 -- Switch to start stop scrolling

int main() {
    // LED as output
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    lcd_home();

    int maxlen = 20;
    int waittime = 75;
    int startpos = maxlen;
    int i; // a counter
    int appendMe;
    char text[256] = "Hi there! You can stop this scrolling text by flipping the switch on the top to get a red light."; 
    char outstr[256];
    char printline[21];

    DDRC &= ~(1<<PC4); // Set PC4 for input
    DDRC |= (1<<PC3);   // Set PC3 for output
    DDRC |= (1<<PC2);   // Set PC2 for output

    lcd_clear_and_home();

    while(1) {
        if (PINC & (1<<PC4)) { // is switch on PC4 on? if not skip all
            PORTC |= (1<<PC3);  // Turn on green LCD
            PORTC &= ~(1<<PC2); // Turn off red LCD
            i = 0;
            strcpy(outstr, "\0");
            memset(printline, '\0', sizeof( printline ));
            while(i <= startpos) {
                strncat( outstr, " ", i); // adds spaces in front
                i++;
            }
            if (startpos<0) {
                strncat(outstr, text+(0-startpos), maxlen); // removes passed chrs from text. 0-x to make pos
                outstr[maxlen+1] = '\0'; // NULL terminator. If maxlen less than outstr, strncpy will not null terminate
            } else {
                strncat(outstr, text, maxlen); // adds text to preceeding spaces
                outstr[maxlen+1] = '\0'; // NULL terminator. If maxlen less than outstr, strncpy will not null terminate                            
            }
            strncpy(printline, outstr, maxlen);  // puts everything into a nice output array with a max 20 len 
            if (strlen(printline) < 1) {        
                startpos = maxlen; // all text used, restart
            }
            if (strlen(printline) < (maxlen)) {  // if there is still space on the line after printing the remaining text
                appendMe = maxlen - (strlen(printline));
                strncat(printline, "                                         ", appendMe); // basically adds enough space to cover the EOL      
            }
            printline[maxlen] = '\0'; // NULL terminator. If maxlen less than outstr, strncpy will not null terminate
            fputs(printline, &lcd_stream); // prints string             
            delay_ms(waittime);
            startpos--;
            lcd_home();
        } else {
            PORTC |= (1<<PC2);  // Turn off green LCD
            PORTC &= ~(1<<PC3); // Turn on red LCD
        }
    }

    return 0;
}

Post a Reply

Please log in to post a reply.

Did you know that the microcontroller's crystal oscillator can be used to keep accurate time? Learn more...