XC8: .asm to C ( i.e. Lowering One's Expectations)

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Well sounds like you have stuff covered, but do test your setup, be sure that if a catastrophe does strike you you can indeed recover from the backups and stuff. Much of the time this goes untested and then...
Most of what I described has been running continuously for years. Everything produces reports and alarms when things aren't working.

I've had a few opportunities to need to restore my main servers from backups (which are only ever a few hours behind). It only takes a few minutes and has -- thus far -- been flawless.

The hardest part is keeping my PHP based document control system running. Every major version update of PHP invariably breaks something, so I have to do my OS upgrades carefully -- usually on a Friday night before a weekend on which I know I will have time to fix things before Monday morning. I hate software guys...
 

ApacheKid

Joined Jan 12, 2015
1,762
Most of what I described has been running continuously for years. Everything produces reports and alarms when things aren't working.

I've had a few opportunities to need to restore my main servers from backups (which are only ever a few hours behind). It only takes a few minutes and has -- thus far -- been flawless.

The hardest part is keeping my PHP based document control system running. Every major version update of PHP invariably breaks something, so I have to do my OS upgrades carefully -- usually on a Friday night before a weekend on which I know I will have time to fix things before Monday morning. I hate software guys...
I used to work with Stratus fault tolerant minis in the 1980s and 1990s in London. This was an innovative technology, the company grew nicely and made serious money for the founders too. They were a nemesis of Tandem who used software to provide fault tolerance which was far from simple, whereas Stratus did it all in hardware.

Stratus provided (they still exist actually) machines that had 5 nines availability, like the machine was unavailable six minutes per year, they sold well to telecoms, financial trading firms, nuclear power stations and so on.

They did it by having duplexed boards each of which had duplicate components. So a single logical CPU was in fact two boards, each of which had two 68020 CPUs in lockstep with hardware comparators. So four microprocessors running the same code, same instructions at any point in time.

If two CPU's generated different outputs at any point, the entire board went "out of service" and a message was sent to their support center. Meanwhile the remaining board continued. An hour or so later a courier would arrive with a package, the replacement board.

The customer would open the cabinet, see the board with a flashing red light, remove it, insert new board and go back to their desk.

The OS log would record this as something like:

12:30:45 Board 2 in cabinet 3 has been taken out of service.
12:30:47 Board 3 in cabinet 3 is running alone.
12:30:55 CPU 2 is now running simplexed.
13:40:17 Board 2 in cabinet 3 has been removed.
13:40:42 Board 2 in cabinet 3 serial 12345678 has been inserted.
13:41:31 Board 2 in cabinet 3 has been put into service.
13:41:55 CPU 2 is now running duplexed.

All the while the operating system and the applications were all totally undisturbed, the OS knew and logged messages, but otherwise continued, there was not a hint of a problem, unlike Tandem, the Stratus developer never had to write code to support fault tolerance, basically the platform was always there.

I used to run courses on their OS for developers, and I would demo this to the class, it was impressive, they really did have this down and Tandem looked like a joke compared.

The fault tolerant duplicate design was carried over too into memory, IO control boards, disk controllers and hard disks as well and the power supplies.
 
Last edited:

nsaspook

Joined Aug 27, 2009
16,330
Most of what I described has been running continuously for years. Everything produces reports and alarms when things aren't working.

I've had a few opportunities to need to restore my main servers from backups (which are only ever a few hours behind). It only takes a few minutes and has -- thus far -- been flawless.

The hardest part is keeping my PHP based document control system running. Every major version update of PHP invariably breaks something, so I have to do my OS upgrades carefully -- usually on a Friday night before a weekend on which I know I will have time to fix things before Monday morning. I hate software guys...
+1

I've got server class work systems that are long in the tooth that are still operational as backup servers.
1668127630490.png
I hate to put a guy down that's still on the job.
 

nsaspook

Joined Aug 27, 2009
16,330
The boot hard-drive in that machine needs a pension. 140232 hours of run-time.
1668134110037.png
1668133868041.png

This is the replacement machine.
1668134464259.png
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
@nsaspook, any way I can get a switch statement to compile more efficiently for sequential cases?

A switch with 13 cases and a default compiles like this:

Code:
423E  0A00     XORLW 0x0
4240  A4D8     BTFSS 0xFD8, 2, ACCESS
4242  D04D     BRA 0x42DE
4244  503D     MOVF message, W, ACCESS
4246  0A00     XORLW 0x0
4248  B4D8     BTFSC 0xFD8, 2, ACCESS
424A  D7C8     BRA 0x41DC
424C  0A01     XORLW 0x1
424E  B4D8     BTFSC 0xFD8, 2, ACCESS
4250  D7C9     BRA 0x41E4
4252  0A03     XORLW 0x3
4254  B4D8     BTFSC 0xFD8, 2, ACCESS
4256  D7C8     BRA 0x41E8
4258  0A01     XORLW 0x1
425A  B4D8     BTFSC 0xFD8, 2, ACCESS
425C  D7C7     BRA 0x41EC
425E  0A07     XORLW 0x7
4260  B4D8     BTFSC 0xFD8, 2, ACCESS
4262  D7C6     BRA 0x41F0
4264  0A01     XORLW 0x1
4266  B4D8     BTFSC 0xFD8, 2, ACCESS
4268  D7C5     BRA 0x41F4
426A  0A03     XORLW 0x3
426C  B4D8     BTFSC 0xFD8, 2, ACCESS
426E  D7C6     BRA 0x41FC
4270  0A01     XORLW 0x1
4272  B4D8     BTFSC 0xFD8, 2, ACCESS
4274  D7C7     BRA 0x4204
4276  0A0F     XORLW 0xF
4278  B4D8     BTFSC 0xFD8, 2, ACCESS
427A  D7C8     BRA 0x420C
427C  0A01     XORLW 0x1
427E  B4D8     BTFSC 0xFD8, 2, ACCESS
4280  D7C9     BRA 0x4214
4282  0A03     XORLW 0x3
4284  B4D8     BTFSC 0xFD8, 2, ACCESS
4286  D7CA     BRA 0x421C
4288  0A01     XORLW 0x1
428A  B4D8     BTFSC 0xFD8, 2, ACCESS
428C  D7C9     BRA 0x4220
428E  0A07     XORLW 0x7
4290  B4D8     BTFSC 0xFD8, 2, ACCESS
4292  D7C8     BRA 0x4224
4294  0A01     XORLW 0x1
4296  B4D8     BTFSC 0xFD8, 2, ACCESS
4298  D7C7     BRA 0x4228
429A  0A03     XORLW 0x3
429C  B4D8     BTFSC 0xFD8, 2, ACCESS
429E  D7C6     BRA 0x422C
42A0  0A01     XORLW 0x1
42A2  B4D8     BTFSC 0xFD8, 2, ACCESS
42A4  D7C5     BRA 0x4230
42A6  D01B     BRA 0x42DE
This is just a bunch of serialized tests, skips, and jumps, uses way too much code, and consumes far too many cycles for the larger valued cases.

In asm, I can do the entire structure in just a few instructions, and each case requires the same number of instruction cycles to select.

My framework is heavily dependent on efficient case switching. I need a solution or an alternative.

Here is the way I do it in .asm:

Code:
    switch    lcdst,liendst    :index to state action

    goto    lres0            ;assert reset
    goto    lres1            ;deassert reset and start delay
    goto    list0a            ;function set 8 bit
    goto    list0b            ;function set 8 bit
    goto    list0c            ;function set 8 bit
    goto    list1            ;function set 4 bit
    goto    list2            ;complete initializstion

...

liendst                             ;all functions return here
switch is a macro:

Code:
switch    macro    idxreg,retaddr

    push
    movlw    high retaddr
    movwf    tosh
    movlw    low retaddr
    movwf    tosl

    movf    idxreg,w
    call    _cjump2

    endm
which calls this:

Code:
;****************************************************************
;** CJUMP2 -- Preform a computed jump over 2 word instructions **
;****************************************************************
;**   w = index (0 to 63)                                      **
;****************************************************************

_cjump2    rlncf    wreg,f

;********************************************************************
;** CJUMP -- Preform a computed jump over single word instructions **
;********************************************************************
;**   w = index (0 to 127)                                         **
;********************************************************************

_cjump    rlncf    wreg,f

    addwf    tosl,f
    skpnc
    incf    tosh,f

    return
 

ApacheKid

Joined Jan 12, 2015
1,762
@nsaspook, any way I can get a switch statement to compile more efficiently for sequential cases?
My framework is heavily dependent on efficient case switching. I need a solution or an alternative.
Well, why even use a switch at all, if the pattern is what it seems to be from your example, this is better:

Code:
execute_action[index](args...); // index into the array of function pointers.
Also can we see the C source code that led to those 53 lines of instructions? Did you not specify any optimizations when you compiled it?

I don't know your problem space but if it's any kind of state machine, then the ideal way to design that in C (and many other languages) is as a loop that invokes a handler indexed from a 2D array of function pointers.

One subscript represents the "event" that occurred and the other represents the current "state". Once the static table of function pointers is initialized the code is tiny:

Code:
while (1)
{
    event = get_event();
    // Invoke the function that specifically process this event when in this state and goto new state.
    state = handle_event[state][event](args...);
}
I once wrote a C language lexical analyzer using that exact pattern, it could consume arbitrarily complex C source and generate tokens from that, there was a lot of functions (I'm guessing like it was 10*10 table or something, but many entries in the matrix were NOPs so to speak, "do nothing") but there were a lot of states too, but the ease with which this can be debugged and updated is huge. There were no switch statements at all in that logic either, I tend to steer away from them except in trivial cases, there was always, always odd scenarios that caused problems, it was to all intents and purposes impossible to reason about the code.

The reason I avoid switch/case is that over time, code can get inserted into the various cases that can lead to complex, hard to debug behaviors. I once rewrote a large piece of code at the Financial Times, that was a huge switch were most of the cases also contained further switches, needless to say that code never reached a point of being bug free despite being a decade old.

In this example problem, the "event" was the next character read from the input.
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Also can we see the C source code that led to those 53 lines of instructions? Did you not specify any optimizations when you compiled it?
Optimization level 2. Just a switch statement with sequentially ordered cases and a default. Nothing complicated or specific to my project.

I really don't want to use an array of functions. That will significantly complicate the source code (and the ability of one to follow the logic) while simultaneously bloating the object code.

Just take a gander at how elegant the .asm code looks. That's what I want!
 

ApacheKid

Joined Jan 12, 2015
1,762
Optimization level 2. Just a switch statement with sequentially ordered cases and a default. Nothing complicated or specific to my project.

I really don't want to use an array of functions. That will significantly complicate the source code (and the ability of one to follow the logic) while simultaneously bloating the object code.

Just take a gander at how elegant the .asm code looks. That's what I want!
Perhaps you're using a poor compiler, does the job but had little time invested in producing tight code. Now you did say in an earlier post above:

My goal is to provide extreme abstraction between the various modules, to the extent that they are truly independent of each other and literally have no knowledge that they exist or are operating (or not).
An array of functions satisfies that need I think, I can't see anything complicated there. Can I see the source code that is generating all those instructions? Do the case statements end in "break" or allow "fall thru"? It's conceivable that you know something the compiler doesn't and that allows you to reduce the code as you have.
 

ApacheKid

Joined Jan 12, 2015
1,762
This might be a good justification for you too evaluate other compilers for the PIC. Try several of them, compare their generated code, as a PIC assembler guru you'd be well positioned to make such a comparison. I've written code generators and the code generation and optimization is often an ongoing labor of love, there are always opportunities to improve the output but it can be involved.

Also one can often optimize for execution speed or memory size, one can select very fast code but big or very small code but slow.
 

nsaspook

Joined Aug 27, 2009
16,330
My advice basically concurs with @ApacheKid with the exception of evaluate other compilers as IMO you will see the same types of behaviors with C on 8-bit machines on many types of architectures. I try not to use switch/case where speed and efficiency are paramount but they are very useful in clarify state-machine flow. Maybe there is a magic foo switch combo that generates code like yours but it's unlikely to stay stable.

https://embeddedgurus.com/stack-overflow/2010/04/efficient-c-tip-12-be-wary-of-switch-statements/
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
An array of functions satisfies that need [of extreme abstraction] I think...
Nope. The desired abstraction is between the modules, not within the modules themselves.

Non-blocking code requires state machines to keep track of things.

The individual states of a state machine are intimately interrelated. There is no benefit to abstracting the states from each other.

Efficient non-blocking code requires an efficiently decoded state machine.

This might be a good justification for you too evaluate other compilers for the PIC.
I really am too old of a dog to learn new tricks. What I am doing right now is about at my threshold of frustration. I just want to learn the best constructs with the tool I've chosen that will create code that is the best approximation of that which I've already written in .asm.

If Microchip still supported mpasm, we would never even have been here to discuss this.
 

ApacheKid

Joined Jan 12, 2015
1,762
Well I can't say much more since I know little of your problem domain. Perhaps though it's an opportunity to move up to a higher spec MCU with better compilers etc? All you'd need to do is port your code over and...oh wait, its all written in PIC assembler, darn...
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
I went ahead and created a jump table of function pointers as described here:

https://rmbconsulting.us/Publications/jump-Tables.pdf

Yes, the C text is now much messier and difficult to follow, and the resulting object code is bloated over what I can do in assembly...

...but the execution times are consistent and predictable regardless of the index value. That is probably the most important quality of all -- at least in the case of processing large state machines.

Thanks, @ApacheKid and @nsaspook.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Perhaps though it's an opportunity to move up to a higher spec MCU with better compilers etc?
Why? Why rewrite 30 years worth of libraries that are fully-functioning, tested, and bug free?

Because a second-party vendor decides to end support for the most basic tool one can utilize for a CPU?

In my youth, I assume I could always assemble assembly code regardless of whatever else changed or progressed in the world.

Boy, was I wrong.
 

ApacheKid

Joined Jan 12, 2015
1,762
Why? Why rewrite 30 years worth of libraries that are fully-functioning, tested, and bug free?

Because a second-party vendor decides to end support for the most basic tool one can utilize for a CPU?

In my youth, I assume I could always assemble assembly code regardless of whatever else changed or progressed in the world.

Boy, was I wrong.
Well that's not quote the full picture Joe. If one invests in machine specific assembler then they pretty much abandon any prospect of portability hence the need to rewrite. This is the price one pays, if it was absolutely essential to use assembler for a system, if there were no alternatives whatsoever then of course one will do as you did.

But why has the question come up? If you've invested in, built and tested all that code, what's the issue? Is the issue maintenance? are you seeking to change some aspect of what you have? It can't be bug fixing because you've already described your system as "bug free" no known defects. Isn't this system by now, basically, finished? completed?

This comes up a lot, this is why sometimes we retire certain products, processes and technologies. Its a cost benefit calculation. If the cost of maintaining something start to get uncompetitive then it's time to review everything, plan to retire the product or at least freeze all further development of it because of the expense.

Look at say Microsoft and Windows 3.1 16 Bit. They invested a HUGE amount of time and money in that yet reached a point where they realized that starting afresh, despite the costs in doing so, offered better long term potential than continuing with that old platform.

Microsoft do no further work, fixes or development for example on the .Net Framework, it is now all .Net Core. They pass that cost (the cost of freezing .Net Framework) onto the customer. They do no more fixes or anything, so it's the customer's problem. The customer could pay Microsoft 10 million dollars to fix this or that and perhaps Microsoft would do it too, but basically its the customer's problem not Microsoft's, customers either migrate, rewrite or whatever for .Net Core or they don't,
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
One last *major* gripe:

MPLabX is slow as freakin' molasses compared to MPLab.

And I'm on a Core I7. MPLab on a 20 year old XP machine runs circles around MPLabX.

Same with mpasm on a much older DOS machine.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
New question -- with a simple example:

Let's say I want to skip a few instruction cycles, say a series of 10 NOPs. I can write in my code Nop(); 10 times, obviously.

In .asm, I can also write 10 NOPs in succession, but I can write a macro like:

Code:
nopn    macro   numn
 variable count=0
 while (count<numn)
        nop
 count=count+1
        endw
and invoke this as:

Code:
        nopn    10
And the macro expands at assembly time.

How do I do similar in C?
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
I'll let you guys critique my as-of-yet incomplete but working LCD code. This is for a Newhaven NHD-C0216CU-FSW-GBW-3V3.

Completely non-blocking from startup and initialization to shutdown. The main application loop simply needs to call LCD_Poll() each iteration. Aside from that, the main app has no idea of its existence.

So far it only prints static messages and message swaps between two set of line buffers. It doesn't look like much, but there will be a lot more features when I am done!

This is the easiest of the drivers I need to write, so I am using it to cut my teeth on XC8.


charlcd.h:
/*
 * File:   charlcd.h
 * Author: JoeyD999
 *
 * Created on October 14, 2021, 1:02 PM
 */

#ifndef CHARLCD_H
#define    CHARLCD_H

#ifdef    __cplusplus
extern "C" {
#endif

#include <xc.h>
#include <stdint.h>
#include <string.h>
#include "system_timer_definitions.h"
#include "messages.h"
#include "custom_types.h"

//                                   Port F
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//Bit      |   7   |   6   |   5   |   4   |   3   |   2   |   1   |   0   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//Device   |           LCD DATA            |          LCD CONTROL          |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//Function |  DB7  |  DB6  |  DB5  |  DB4  | RESET |  RS   |  RW   |  EN   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//LATF     |   0   |   0   |   0   |   0   |   0   |   0   |   0   |   0   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//TRISF    |   0   |   0   |   0   |   0   |   0   |   0   |   0   |   0   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//ANSELF   |   0   |   0   |   0   |   0   |   0   |   0   |   0   |   0   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//WPUF     |   0   |   0   |   0   |   0   |   0   |   0   |   0   |   0   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//ODCONF   |   0   |   0   |   0   |   0   |   0   |   0   |   0   |   0   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
//SLRCONF  |   1   |   1   |   1   |   1   |   1   |   1   |   1   |   0   |
//         +-------+-------+-------+-------+-------+-------+-------+-------+
    
#define LCDHIGH                 //uncomment if data pins on high nibble of port

#define LCDPORT    PORTF            //lcd port assignment
#define LCDLAT    LATF            //lcd latch assignment
#define LCDTRIS    TRISF            //lcd tris assignment
#define LCDANSEL ANSELF

#define LCDE    LATF0           //enable pin falling edge triggered
#define LCDRW   LATF1           //RW pin 0=Write   / 1=Read
#define LCDRS   LATF2           //RS pin 0=Command / 1=Data
#define LCDRES  LATF3           //Reset pin 0=Reset / 1=Run
#define LCDDB4  LATF4
#define LCDDB5  LATF5
#define LCDDB6  LATF6
#define LCDDB7  LATF7
#define LCDBUSY RF7             //busy port bit

#define LCDBL   LATB4           //Backlight pin
    
#define LCDBLT  TRISB4          //Backlight tris

#ifdef LCDHIGH
#define LCDDMSK 0b11110000      //Mask for data pins
#else
#define LCDDMSK 0b00001111
#endif

#define LCD_STATE_RUN   99      //indicates LCD is fully initized and running   
#define LCD_DATA        1
#define LCD_COMMAND     0
#define LCD_NUM_LINES   2
#define LCD_NUM_CHARS   16      //number of characters per line

//LCD Constants

#define LCD_LINE1       0
#define LCD_LINE2       1

#define LCD_MAIN_LINE1  0
#define LCD_ALT_LINE1   1
#define LCD_MAIN_LINE2  2
#define LCD_ALT_LINE2   3

#define LCD_SWAP_LINE1  0b00000001
#define LCD_SWAP_LINE2  0b00000010
#define LCD_SWAP_FAST   0b00000100
#define LCD_SWAP_TEMP1  0b00011001
#define LCD_SWAP_TEMP2  0b00101010
 
#define LCD_NUM_LANGUAGES 1           //number of languages

#define LCD_INIT_TIME   clctim(42,T2ms)     //40+2 ms initial wait for reset
#define LCD_SWAP_TIME   clctim(2000,T32ms)    //2 second display swap timing
#define LCD_FSWAP_TIME  clctim(500,T32ms)        //500 mS fast swap timin

#define ENGLISH     0
#define INTL        1

//Commands for S6A0069 Driver

#define DFSET       0b00100000   
#define DFSET8      0b00101000
#define DFSET8i     0b00101001   
#define CLDSP       0b00000001
#define EMODE       0b00000110
#define DISPON      0b00001100
#define DISPOFF     0b00001000
#define HOME        0b00000010
#define SETPOS      0b10000000
#define POS_LINE1   0b10000000
#define POS_LINE2   0b11000000
#define SETCG       0b01000000
#define CONTRAST    0b01110000
#define IOSF        0b00010100
#define PWRCTL      0b01010100
#define FOLCTL      0b01101101

#define lcd_bfvalid_f               lcd_flags.bitv.b0
#define lcd_update_f                lcd_flags.bitv.b1
#define lcd_lineout_in_process_f    lcd_flags.bitv.b2

#define lcd_swap1_f                 lcd_swap_flags.bitv.b0  //swapping line 1
#define lcd_swap2_f                 lcd_swap_flags.bitv.b1  //swapping line 2
#define lcd_fswap_f                 lcd_swap_flags.bitv.b2  //fast swap
#define lcd_tswap_f                 lcd_swap_flags.bitv.b3  //temporary swap
#define lcd_dalt1_f                 lcd_swap_flags.bitv.b4  //alternate line 1
#define lcd_dalt2_f                 lcd_swap_flags.bitv.b5  //alternate line 2
    
extern ct_bitmap8 lcd_flags, lcd_swap_flags;

extern uint_fast8_t lcd_contrast; //contrast setting
extern uint_fast8_t lcd_state;
extern uint_fast8_t lcd_timer;

uint_fast8_t lcd_swap_timer;
uint_fast8_t lcd_line_in_process;

message_e lcd_line_message[4];    //LCD 2 line messages/2 alternates


char * lcd_outchar_p;
char lcd_line_buffer[LCD_NUM_CHARS+1];                   //LCD line buffer

//char lcd_fill_buffer[LCD_NUM_CHARS+1];                   //LCD fill buffer
//uint_fast8_t lcd_bcd_buffer[4];                 //LCD 8 digit max BCD buffer
//uint_fast8_t lcd_language;                      //LCD language

void LCD_poll(void);
void LCD_update(void);
void LCD_print(message_e message, uint_fast8_t line);
void LCD_swapOn(uint_fast8_t flags);
void LCD_swapOff(void);
void LCD_backlightOn(void);
void LCD_backlightOff(void);

#ifdef    __cplusplus
}
#endif

#endif    /* CHARLCD_H */
charlcd.c:
/*
 * File:   charlcd.c
 * Author: JoeyD999
 *
 * Created on October 14, 2021, 1:02 PM
 */

#include "charlcd.h"
#include "charlcd_callbacks.h"
#include "power.h"

//Power on Initialization

ct_bitmap8 lcd_flags = {0};
ct_bitmap8 lcd_swap_flags = {0};
uint_fast8_t lcd_contrast = 24;     //contrast setting
uint_fast8_t lcd_state = 0;
uint_fast8_t lcd_timer = 0;

//**********************************************
//** LCD_isBusy -- return true if LCD is busy **
//**********************************************

_Bool LCD_isBusy(void)
{
    _Bool isbusy;
    
    LCDTRIS = LCDDMSK;
    LCDRS   = 0;
    LCDRW   = 1;

    //tAW6 -- 20ns Max
    Nop();
    
    LCDE=1;

    //tACC6 -- 500ns Max
    Nop(); Nop(); Nop(); Nop(); Nop(); Nop(); Nop(); Nop();
  
    isbusy=LCDBUSY;
    
    LCDE=0;

    //tEWL -- 150ns Max
    Nop(); Nop(); Nop();

    LCDE=1;
    
    //tEWH -- 200ns Max
    Nop(); Nop(); Nop(); Nop();
    
    LCDE=0;
    
    //tOH6 -- 300ns Max
    Nop(); Nop(); Nop(); Nop(); Nop();

    LCDTRIS = 0b00000000;
    LCDRW = 0;

    return isbusy;
}

//**********************************************
//** LCD_outNibble -- Send nibble in w to LCD **
//**********************************************

void LCD_outNibble(uint_fast8_t nibble, _Bool RS)
{
    LCDRS=RS; //preset RS Bit
    LCDLAT = (LCDLAT & ~LCDDMSK) | (nibble & LCDDMSK);
    LCDE = 1;

    //tEWH -- 200ns Max
    Nop(); Nop(); Nop(); Nop();
    
    LCDE = 0;
}

//************************************************************
//** LCD_outByte -- Send command or data byte in w to LCD   **
//**                Ensure no Busy before calling           **
//************************************************************

void LCD_outByte(uint_fast8_t value, _Bool RS)
{
#ifdef LCDHIGH
    LCD_outNibble(value, RS);                       //send high nibble
    LCD_outNibble((uint_fast8_t) (value << 4), RS);    //send low nibble
#else
    LCD_outNibble(value >> 4, RS);                  //send high nibble
    LCD_outNibble(value, RS);                       //send low nibble
#endif
}

//*****************************************************
//** LCD_render -- Render a line to the LCD hardware **
//*****************************************************

void LCD_render(uint_fast8_t line)
{
    uint_fast8_t message;
    
    if (!line)
       lcd_line_in_process = (lcd_dalt1_f)?LCD_ALT_LINE1:LCD_MAIN_LINE1;
    else
       lcd_line_in_process = (lcd_dalt2_f)?LCD_ALT_LINE2:LCD_MAIN_LINE2;

    message = LCD_CB_message(line, lcd_line_message[lcd_line_in_process]);

    strcpy(lcd_line_buffer,messages[message]);
    
    lcd_outchar_p = lcd_line_buffer;
    
    LCD_outByte((line)?POS_LINE2:POS_LINE1,LCD_COMMAND);
    
    lcd_lineout_in_process_f = TRUE;
    
}


//******************************
//** LCD State Machine States **
//******************************

//State 0 -- Idle with LCD Off

void LCDstate0(void)
{
    if (PPCTRL_LCD)
    {
        LCDRES=1;                       //Unreset LCD
        lcd_timer=LCD_INIT_TIME;        //wait 40ms after reset
 
        lcd_flags.bytev = 0;            //reset all flags
        lcd_swap_timer = 0;             //clear swap timer
        lcd_swap_flags.bytev = 0;       //and swap flags

        // set all lines to blank

        lcd_line_message[0] = m_blank;
        lcd_line_message[1] = m_blank;
        lcd_line_message[2] = m_blank;
        lcd_line_message[3] = m_blank;

        lcd_state++;                    //next state
    }
}

//States 1-4 -- after 40ms, set to 4 bit mode -- BF not valid

void LCDstate1(void)
{
    LCD_outNibble(DFSET|0x10,LCD_COMMAND);
    lcd_timer=1;
    lcd_state++;
}

void LCDstate2(void)
{
    LCD_outNibble(DFSET|0x10,LCD_COMMAND);
    lcd_timer=1;
    lcd_state++;
}

void LCDstate3(void)
{
    LCD_outNibble(DFSET|0x10,LCD_COMMAND);
    lcd_timer=1;
    lcd_state++;
}

void LCDstate4(void)
{
    LCD_outNibble(DFSET,LCD_COMMAND);
    lcd_bfvalid_f = TRUE;              //indicate busy flag is valid
    lcd_state++;
}

//States 5-14 -- complete initialization of LCD

void LCDstate5(void)
{
    LCD_outByte(DFSET8i,LCD_COMMAND);
    lcd_state++;
}

void LCDstate6(void)
{
    LCD_outByte(IOSF,LCD_COMMAND);
    lcd_state++;
}

void LCDstate7(void)
{
    LCD_outByte(CONTRAST | (lcd_contrast & 0x0f),LCD_COMMAND);
    lcd_state++;
}

void LCDstate8(void)
{
    LCD_outByte(FOLCTL,LCD_COMMAND);
    lcd_state++;
}

void LCDstate9(void)
{
    LCD_outByte(PWRCTL | ((lcd_contrast >> 4) & 0x03) ,LCD_COMMAND);
    lcd_state++;
}

void LCDstate10(void)
{
    LCD_outByte(DISPON,LCD_COMMAND);
    lcd_state++;
}

void LCDstate11(void)
{
    LCD_outByte(EMODE,LCD_COMMAND);
    lcd_state++;
}

void LCDstate12(void)
{
    LCD_outByte(CLDSP,LCD_COMMAND);
    lcd_state++;
}

void LCDstate13(void)
{
    LCD_outByte(HOME,LCD_COMMAND);
    lcd_state++;
}

void LCDstate14(void)
{
    PPSTAT_LCD = 1;                                               //indicate driver is active
    LCD_CB_init();                                                //callback to main application
    lcd_state++;
}

// Normal run state

void LCDstate15(void)
{
    if (!PPCTRL_LCD)    //stop LCD?
    {
        LCD_outByte(CLDSP,LCD_COMMAND);                //start shutdown
        lcd_state++;
        return;
    }
 
    if (lcd_lineout_in_process_f)
    {
        LCD_outByte(*(lcd_outchar_p++),LCD_DATA);

        if (!(*lcd_outchar_p))
        {
            lcd_lineout_in_process_f = FALSE;

            if (!(lcd_line_in_process & 0b10))
                LCD_render(LCD_LINE2);
        }
        return;
    }

    if (lcd_update_f)
    {
        lcd_update_f = FALSE;
        LCD_CB_startUpdate();
        LCD_render(LCD_LINE1);
    }
}

//finish shutdown

void LCDstate16(void)
{
        LCD_outByte(DISPOFF,LCD_COMMAND);                //turn off
        lcd_state++;
} 

void LCDstate17(void)
{
     PPSTAT_LCD=0;                                   //indicate driver is off
     lcd_bfvalid_f = FALSE;                          //signal busy flag is no longer valid
     lcd_state = 0;                                  //reset LCD init state
     LCD_backlightOff();                             //turn off backlight
}

//*****************************************************
//** LCD_poll -- Poll the LCD Display                **
//*****************************************************
//** Call each loop of main program                  **
//**   Handles initialization and message display    **
//**   Updates display when lcd_update_f flag is set **
//*****************************************************

void LCD_poll(void)
{
    static void (*state_function[])(void)=
        {LCDstate0, LCDstate1, LCDstate2, LCDstate3,
         LCDstate4, LCDstate5, LCDstate6, LCDstate7,
         LCDstate8, LCDstate9, LCDstate10,LCDstate11,
         LCDstate12,LCDstate13,LCDstate14,LCDstate15,
         LCDstate16,LCDstate17};

    if (lcd_timer)
    {
        if (TC2ms) lcd_timer--;
        return;
    }
    
    if (lcd_swap_timer && TC32ms)
    {
        if (!(--lcd_swap_timer))
        {
            if (lcd_swap1_f)
                lcd_dalt1_f^=1;

            if (lcd_swap2_f)
                lcd_dalt2_f^=1;

            if (!lcd_tswap_f)
                lcd_swap_timer=(lcd_fswap_f)?LCD_FSWAP_TIME:LCD_SWAP_TIME;
            else
                LCD_swapOff();

            lcd_update_f=TRUE;
        }
    }
 
    if ((lcd_bfvalid_f && !LCD_isBusy()) || !lcd_bfvalid_f)
        state_function[lcd_state]();
}

//****************************************
//** LCD_update -- Force repaint of LCD **
//****************************************

void LCD_update(void)
{
    lcd_update_f = TRUE;
}

//***********************************************
//** LCD_print -- put a message on an LCD line **
//***********************************************

void LCD_print(message_e message, uint_fast8_t line)
{
    lcd_line_message[line] = message;
    lcd_update_f = TRUE;
}


//*******************************************************
//** LCD_swapOn -- Turn on message swapping for a line **
//*******************************************************

void LCD_swapOn(uint_fast8_t flags)
{
    lcd_swap_flags.bytev = flags;
    lcd_swap_timer=(lcd_fswap_f)?LCD_FSWAP_TIME:LCD_SWAP_TIME;
    lcd_update_f = TRUE;
}

//**********************************************
//** LCD_swapOff -- Turn off message swapping **
//**********************************************

void LCD_swapOff(void)
{
    lcd_swap_flags.bytev = 0;
    lcd_swap_timer = 0;
    lcd_update_f = TRUE;
}

//**********************************************
//** LCD_backlightOn -- Turn LCD backlight on **
//**********************************************

void LCD_backlightOn(void)
{
    LCDBL=1;
}

//************************************************
//** LCD_backlightOff -- Turn LCD backlight off **
//************************************************

void LCD_backlightOff(void)
{
    LCDBL=0;
}
Application specific messages to be displayed:

messages.h:
/*
 * File:   messages.h
 * Author: JoeyD999
 *
 * Created on October 16, 2021, 11:26 AM
 */

#ifndef MESSAGES_H
#define    MESSAGES_H

#ifdef    __cplusplus
extern "C" {
#endif
    
typedef enum
{
    m_blank,
    m_init1,
    m_init2,
    m_am1,
    m_am2
} message_e;

extern const char * messages[];

#ifdef    __cplusplus
}
#endif

#endif    /* MESSAGES_H */
messages.c:
/*
 * File:   messages.c
 * Author: JoeyD999
 *
 * Created on October 16, 2021, 11:26 AM
 */

#include "messages.h"


const char * messages[] =
{"                ",   //m_blank
     "Initial Line 1 M",   //m_init1
     "Initial Line 2 M",   //m_init2
     "Another Message ",   //m_am1
     "And Another One "    //m_am2 
};
And communication with the main app is done with callback functions (definitely far from complete):

charlcd_callbacks.h:
/*
 * File:   charlcd_callbacks.h
 * Author: JoeyD999
 *
 * Created on October 15, 2021, 3:49 PM
 */

#ifndef CHARLCD_CALLBACKS_H
#define    CHARLCD_CALLBACKS_H

#ifdef    __cplusplus
extern "C" {
#endif

#include <xc.h>   
#include <stdint.h>
#include "charlcd.h"
#include "messages.h"

void LCD_CB_init(void);
void LCD_CB_startUpdate(void);
uint_fast8_t LCD_CB_message(uint_fast8_t line ,uint_fast8_t  message);
    
#ifdef    __cplusplus
}
#endif

#endif    /* CHARLCD_CALLBACKS_H */
charlcd_callbacks.c:
/*
 * File:   charlcd_callbacks.c
 * Author: JoeyD999
 *
 * Created on October 15, 2021, 3:49 PM
 */

#include "charlcd_callbacks.h"
#include "charlcd.h"

void LCD_CB_init(void)
{
    //This is just test code right now
    
    LCD_print(m_init1,LCD_MAIN_LINE1);
    LCD_print(m_init2,LCD_MAIN_LINE2);
    LCD_print(m_am1,LCD_ALT_LINE1);
    LCD_print(m_am2,LCD_ALT_LINE2);
    LCD_swapOn(LCD_SWAP_LINE1 | LCD_SWAP_LINE2 | LCD_SWAP_FAST);
    
    LCD_backlightOn();
}

void LCD_CB_startUpdate(void)
{

}

uint_fast8_t LCD_CB_message(uint_fast8_t line,uint_fast8_t  message)
{
   return message;
}
 
Top