Thread: PIC16F684 Interfacing with Hitachi 44780

  1. #31
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    So I Googled for that error message and found a few things of interest:
    HI-TECH Software Frequently Asked Questions
    HI-TECH Software Forums: error in MPLAB
    HI-TECH Software Forums: Error [1347] ; 0. can't find 0x1 words (0x1 withtotal) for psect

    Read through those. One suggests the printf family of functions may be the culprit. More on optimizing that later.

    My first suggestion would be to look at compiler settings. Embedded compilers usually let you specify code, data, stack and heap sizes, so maybe you could adjust that.

    You also have a few quirks in your code. First, you're putting an extra null character at the end of some of your sprintf commands (sprintf puts a null for you). Also, I don't know why you're using goto, you could just put the code below LOCATION1: inside the while loop, like so:
    Code:
    do {
        strcpy(TopMessage, "Input Stride");
        sprintf(BotMessage, "%d cm \0", stridelength);
        LCDWrite(0b00000001, 0);
    
        for (i = 0; i < Delay; i++);
        for (i = 0; TopMessage[i] != 0; i++)
            LCDWrite(TopMessage[i], 1);
    
        LCDWrite(0b11000000, 0);
    
        for (i = 0; BotMessage[i] != 0; i++)
            LCDWrite(BotMessage[i], 1);
    
        if (UP == 1)
        ...
    } while (FORWARD != 1);
    It might help if you get rid of more global variables by moving them into functions so they only exist on the stack when the function is active (i, j, k, n, TopMessage and BottomMessage). You could also try making your const ints for your delay loop #defines instead. This means they will be worked into the assembly instructions in immediate mode if possible instead of having to read the data from memory in one instruction then use it in a second.

    Also, a good practice in general (not just for shrinking code) is to find any section of repeated code and turn it into a function. That might not work for every case because if you have only 2 simple lines of code repeated, the overhead of a function call may make your code bigger. One obvious instance of this I see is the code for writing TopMessage and BotMessage to the screen. Put the following into a function, and try passing in TopMessage and BotMessage (after making them local to main):
    Code:
        LCDWrite(0b00000001, 0);
        for (i = 0; i < Delay; i++);
        for (i = 0; TopMessage[i] != 0; i++)
            LCDWrite(TopMessage[i], 1);
    
        LCDWrite(0b11000000, 0);
    
        for (i = 0; BotMessage[i] != 0; i++)
            LCDWrite(BotMessage[i], 1);
    The last thing is something I actually suggested early on, making an array out of the display screens, with a top message, bottom message/format string, and address of data to print, then you can eliminate your switch statement and have just one instance of the code to print a particular screen, like so (this is untested, but gives you the idea):
    Code:
    struct screen {
        char *top;
        char *bot;
        int  *data
    } screen_data[NUM_SCREENS] = {
        {"You have taken", "%d steps", &noofsteps},
        ...
    };
    ...
    while (1) {
        ...
        // these two lines replace your entire switch statement
        sprintf(buf, screen_data[menulevel].bot, *(screen_data[menu_level].data));
        output_message(screen_data[menulevel].top, buf);    // this is the function I had you make above.
    }
    I was going to mention writing your own "printf" type routine, but I wouldn't worry about that yet. The 3 code changes I would make are the output_message function, dropping the switch statement in main and removing global variables.

  2. #32
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    printf/sprintf are probably BY FAR the worst offenders. They are HUGE functions!

    Since you are only printing numbers and simple strings, you probably want to write your own printing routines. For printing strings you can use puts(), which is way lighter than printf.

    This is going to get dirty, but I don't think there's an alternative (other than getting a bigger chip). You have a very tiny chip (2KB). The smallest chip I've used is 4KB, and you can get 16KB ones for <$5. Why 2KB?

  3. #33
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    Oh and I'm assuming you already set your compiler to optimize for size? It will probably take care of the things anduril462 mentioned.

    The PIC architecture is very bad for C compilers (only 1 GP register), so rewriting some things in assembly MAY help. It's really designed to be used in assembly. Compared to something like AVR (32 GP registers).

  4. #34
    Deleted Account
    Join Date
    Nov 2010
    Posts
    16

    Puts

    Hi guys, from doing a bit of reading around, puts can only be used to display simple text strings, and wouldn't work for displaying any of our variables? I could be wrong on this, if so please correct me.

    Having tried the code with and without the sprintf functionality, it's clear that sprintf is the main problem. It accounts for around half of the whole code, and takes up about a third of the available data space. If there's a way of removing this, it'd be great!

    Have found another point of contact at the uni and he may possibly be able to put together some assembly code that carries out the functionality of sprintf in a more efficient way. However, if anybody can think of a simpler way of fixing this, I'm all ears!

    JT

  5. #35
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    You can take your numbers and print them out digit by digit, using a loop and the division and modulo operators. This is easy since you know you wont ever print more than 3 or 4 digits. It goes something like this (this is untested):
    Code:
    // buf is the string to print into, you can pass BotMessage[8] to start printing at the 9th column
    // num is the number you want to print
    void print_num(char *buf, int num)
    {
        int d = 1000;  // this will give 4 spaces/digits per number, add or drop zeros to adjust, and could be a parameter to print_num if you want
    
        while (d > 0) {
            if num divided by d is more than zero
                print the ASCII digit for num divided by d
            }
            num = num modulo d
            d = d divided by 10
        }
    }

  6. #36
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    Well since the only format you seem interested in is %d, check to see if you have something like itoa()
    Though it's easy enough to roll your own if you haven't.

    That, with strcat for whatever constant suffix you append, is all you need to replace all your existing sprintf calls.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  7. #37
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    The reason why sprintf is so big is because it can do half a million things.

    If you try to implement sprintf yourself, even in assembly, chances are it won't be any smaller than the standard library's version.

    But you don't actually need most of its functionalities. You just need to print fixed strings, and integers in base 10.

    You can build the strings yourself (using strcat and itoa, which you may have to write yourself), then use puts() to print it out.

Popular pages Recent additions subscribe to a feed