Sponsor

A Method for PIC Assembly Code Integration and Reuse

Remarks and macros for writing code for Microchip microcontrollers


This document contains a series of postings I put in the PICLIST (Usenet list regarding Microchip microcontrollers) in 1997. The advice and macros listed here were very useful to me, and still are. Nevertheless, I have to make a few remarks.

First of all, have in consideration that this was written several years ago. The new tools that were launched since might be offering more advantageous methods for linking and such. I never bothered to check it out because I write few but very demanding programs, and the macros listed here are very satisfactory for that kind of work. If you write many programs that do not occupy much RAM memory, maybe you would like linkers better, or high level languages.

Speaking of languages: I learned, after writing these postings, that several compilers use this approach. They have termed it "pseudo-stack".

Another thing that the knowledgeable people on the PICLIST taught me (sorry but I don't recall your names right now folks!) is that it is better to do:

MOVF variable ^ H'80',f

...instead of:

MOVF variable & H'7F',f

...to strip the high-bit when variables are placed in page 1, because this way you will get an error if you have mistakenly placed them in the other page.

The text that follows are the 9 postings (intro + 8 parts) from 1997. I hope you find them useful. In case you wish to email me, my email is Oh, before we end, the usual disclaimer: Although the code posted here was verified, I take no responsibility for any damages that might occur with its use.

Good luck with your PIC code!

Andrés Djordjalian
Indicart Carteles Electrónicos
http://www.indicart.com.ar/


Hi everybody!

I'm working on a project that's quite long (about 10K lines of code) for a moving-message-LED-display controlled by a 16C73 that has a lot of features. It's working nicely and it's almost finished.

While working on it I developed an incredibly-simple-but-suprisingly- useful method for the allocation of registers. Without it I would have had a hard work integrating all its components. It consists of several macros of almost unreadable definition comprised in a <200 line include file I call "ALOCATOR.MAC". When you use them the rest of the code is very readable and the registers are allocated and overlapped almost optimally. And the idea is *very* simple. So much that I don't know how I didn't think of it before! (...and I don't believe nobody else has..)

I've been thinking of trying to write an article but I desperately need to finish this project cause I am having some trouble with my finances. On the other hand you might have noticed that I am not a native English-speaker and I don't have much confidence in the quality of a work of mine written in English...

Instead, I like the idea of explaining it on this list. I see no point in keeping it to myself! I'm starting on a separate message, and if you find it interesting I would very much appreciate your feedback.

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part I

As you may guess from the fancy title, the method I'm suggesting deals with modularization of programs, which means to view a system (the program) as a structured collection of components (routines or sets of routines).

Most of the structure is defined by a client-server relation between components. If for a given a component there is another that uses it we say that the first is the server and the second the client in this relationship. A component may have several clients and several servers too.

It is illustrative to graph this structure in an orderly manner, putting the clients on top of the servers. For example:

                            TEXT_DISPLAY
                              |      |
                ---------------      ------------------
                |                                     |
                |                                     |
      FILLING_A_TEXT_BUFFER-------------    READING_A_TEXT_BUFFER
            |         |                |              |
            |         -------------    ------------   |
            |                     |               |   |
    KEYBOARD_INTERFACE     SERIAL_INTERFACE     EEPROM_SUPPORT

A critical issue in programming a system like this is the allocation of variables. By that I mean giving an address to each variable each component uses.

If you use no strategy at all and allocate everything by hand using "EQU"s you will have to do some work every time you create a new variable. Lets say you are working with the keyboard interface module in the previous example and need a new variable for it. What address will you use? You would have to run through all the other source code to find an unused one. Or you could do the same thing with less work if you allocate the registers in order and write down the next free one. But what happens when you want to use the keyboard interface in another project, maybe with other components you had also already programmed? It's a lot of work, and it's a routine that requires none of your creativity. Hey! What do you have your PC for?

The same scheme can be automated in several ways. One is what Microchip suggests with the use of unseeded "CBLOCK"s. With this methods the assembler will linearly assign addresses to all variables at compile-time. That solves the problems as long as you don't run out of registers.

If you do run out of registers the solution will surely be overlapping (assigning the same address to) variables that are never "active" at the same time. That's where the method I'm suggesting comes to play. It is a way of automating this job without sacrificing any modularity.

The implementation of the method consists of several macros that give a high-level-language look to the declaration of variables in your code and make most of the job for you.

I'll give you just a tip here and I'll continue in other messages. The question is: What variables can you overlap? Recall the previous graph. You can't be sure if you can overlap variables belonging to "FILLING_A_TEXT_BUFFER" with others from "KEYBOARD_INTERFACE" because both components might be active at the same time. I mean, "FILLING..." could be active waiting for "KEYBOARD..." to perform a service, and "KEYBOARD..." would then be active performing it.

But notice that surely "KEYBOARD..." and "SERIAL_INTERFACE" will never be active at the same time! So you may overlap their variables. This has some exceptions to which I will refer to later.

That is the heart of the method, I recognize it is an almost stupid idea and that I'm not discovering America but it took quite a while for me to sit down, think and develop it, and its conclusions turned out being very useful to me.

If I don't receive complaints for using so much of this space I'll continue later. I will be glad to receive feedback from you. Do you have any other strategies for doing this? Can you see something wrong in what I'm saying? Did you think of this too?

Regards!

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 2

First of all I'd like to publicly thank again for all the positive words many of you had towards my first message.

In my last posting I ended by giving an example of a case in which you may overlap variables. We had the following structure as an example:

                            TEXT_DISPLAY
                              |      |
                ---------------      ------------------
                |                                     |
                |                                     |
      FILLING_A_TEXT_BUFFER-------------    READING_A_TEXT_BUFFER
            |         |                |              |
            |         -------------    ------------   |
            |                     |               |   |
    KEYBOARD_INTERFACE     SERIAL_INTERFACE     EEPROM_SUPPORT

I said that you shouldn't overlap the variables belonging to "FILLING_A_TEXT_BUFFER" with those from "KEYBOARD_INTERFACE" because both components might be active at the same time.

By "active" I mean that the component is performing a service for a client. For example, if in this structure the program flow is in the "KEYBOARD_INTERFACE" that component will be active and "FILLING_A_TEXT_ BUFFER" too because it's waiting for "KEYBOARD..." to perform the service it requested (no other component could have requested a service from "KEYBOARD..." with this structure).

The rule is to overlap variables that belong to components that are never active at the same time, with some exceptions to which I'll soon refer.

To check for the validity of this assertion lets make an analogy with an ALGOL-like-high-level-language (one that makes use of a data stack) running on a language-oriented-machine (one that HAS a data stack!).

If a routine is active it has a stack frame belonging to it where it keeps its local variables. When this routine calls a server its frame is kept on the stack and the server's frame is placed on top. So when the program flow is in the server both routines are active, as in the example, and the memory for their local variables is not overlapped.

What happens when the server ends its job, the flow returns to the original routine and it calls another server? The first server's stack frame is wiped out and the newer server's is put on top of the original routine's. So you may have some memory space being shared by both servers.

But there was no problem about it because both routines were not active at the same time, and if one never calls the other, and never calls another routine that calls the other, and never calls another routine that calls another routine... etc. that calls the other, then you can assure that you will *never* need separate memory locations for these routines' variables.

Our example shows this behavior for {"KEYBOARD...", "SERIAL..." and "EEPROM..."} and for {"FILLING..." and "READING..."}. But there is no need to inspect the client-server structure of a given system to apply the method, I'm explaining this only to introduce its logic but its application is much simpler.

I'll again begin with an example and generalize later on my next posting. Lets say each component has the following quantity of local variables:

"KEYBOARD_INTERFACE": 5
"SERIAL_INTERFACE": 3
"EEPROM_SUPPORT": 3
"FILLING_A_TEXT_BUFFER": 7
"READING_A_TEXT_BUFFER": 4
"TEXT_DISPLAY": 3

I'll start from the bottom of the structure. Where will I put the variables belonging to the first three components? I already said I could overlap them.

Recall the method to which I referred on my last posting that assigned consecutive addresses to variables and could be implemented with "CBLOCK"s. I said that it was OK as long as you did not run out of registers, because it did not overlap any. So lets add this feature to that method.

But first lets agree on something, when you use consecutive addresses it's the same to build from the bottom to the top incrementing the addresses or to do it from top to bottom decrementing them. This time I'll choose the second option, but for no particular reason.

So, if the maximum register address is 127, the variables belonging to "KEYBOARD..." will occupy positions 123 to 127. And the other two will also begin in 127 as I have no problem in overlapping them! "SERIAL..." will use 125 to 127 and "EEPROM..." too.

Now the responsibility of not overlapping that allocated space is passed to the components on top. This makes sense if you think that the definition of a component includes information of its servers but not of its clients.

Where can I put "FILLING..."'s variables? They shouldn't overlap the first three allocated spaces, so they could start at 122 being the next free position that meets this condition. The variables will go from 116 to 122.

"READING..."'s variables must not overlap only "EEPROM..."'s, so they can start at 124, occupying from 121 to 124. "TEXT..."'s variables shouldn't overlap any of the others' so they may start at 115 and run down to 113.

The general rule then is (if you're using a "decremental model" as in this case): allocate beginning from the least of the routine's servers' allocated positions minus one. This is a job for a macro.

I'll continue later, but I guess you can see much of the point already. I also owe you the explanation of the exceptions for this scheme. Once again, I'll appreciate any opinions.

Regards!

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 3

I ended my last message by giving a golden rule for the allocation of variables: begin to allocate from the least of the routine's servers' allocated positions minus one (if you are using a "decremental model").

So, what do we need to define a component's variables? We need:

  1. Their names (for their sizes I use only bytes but you could make a scheme in which you could fix the variable's sizes)
  2. Someway of knowing which variables belong to each component, this can be done with some sort of "frame delimiters".
  3. We need to know what servers the component has.

A way of doing this could be, for "FILLING..." in my previous posting's example:

FRAME FILLING
USES KEYBOARD
USES SERIAL
USES EEPROM
BYTE fillingsVarNo1
BYTE fillingsVarNo2
...etc
FEND FILLING

I think it looks pretty good. "FRAME", "USES", "BYTE" and "FEND" are macros that implement the method I've been describing on my previous echoes.

Before showing the macros I'll talk about the exeptions for this allocation scheme. I started the reasoning that ended with it by saying that you could overlap variables as long as the components to which they belong are never active at the same time. This is not true with some variables.

Lets say you have a counter of some sort. It consists of two routines "COUNT" and "INFORM". The first, when called, increments the count, and the latter informs its current value. There's a piece of information (the count value) that must be kept even when the routines are not active. So you shouldn't overlap this variable with others although their components might not share activity.

This fact is not a result of the method or a derivative of Microchip's architecture, it is inherent to the type of variable and, for that reason, high-level languages provide support for them. In my opinion, the cleanest way to deal with them is what Ada and OOP languages do with instance variables. See how they have to do with a certain state the component has that outlives the execution of its routines. The C language, on the other hand, supports this kind of variables with the "static" modifier.

In compiler theory a variable is "static" if its destination and size is already defined at compile-time. It's very probable that an implementation of C will deal with variables declared as static by placing them on a location defined at compile time, differently from automatic variables (those which were not defined as static) that are placed dynamically on the stack as I briefly explained on my last posting.

In my opinion the architects of the C language shouldn't have used a term that relies on a particular implementation and refers to a low-level issue, and has not much to do with the real behavior the programmer is asking for. But this is just another criticism to C to be added to the long list...

I started using "STATIC" too as a name for the macro to support these variables, but I don't want to imitate what I think is wrong so I'm switching to "PERSISTENT". I shouldn't use "STATIC" also because, strictly speaking, all the variables in the method are static!

So when the byte to declare should outlive the component's execution I put "PERSISTENT variable" instead of "BYTE variable". As you may expect, "PERSISTENT" is another macro.

Where to put these persistent variables? We shouldn't overlap them with anything, so the best we can do is assign consecutive addresses for them that are apart from those we used for the automatic variables.

I said before that it was the same to assign upwards from the bottom than downwards from the top as I eventually did, so to pile the persistent variables we could use the first option this time.

For example, if both "KEYBOARD..." and "EEPROM..." have also got a persistent variable each, and the first available location is number 32, we could assign one in register 32 and the other in 33. Persistent allocation consumes more RAM resources as it won't be overlapped.

Most of the times the life of persistent data, although not bounded by the execution of the routines to which they belong, can be bounded to the execution of a certain component. For example in OOP instance variables overlive the execution of the methods that make use of them but are created and destroyed in conjunction with the objects they belong to.

This sort of thing is not implemented in this method, persistent variables live through the whole execution, and as we could save RAM there seems to be room for improvement here.

Before ending, notice that a "memory full" condition will occur if, during compilation, the incrementing count of positions for persistent variables collides with the minimum decrementing count for automatic ones.

Now I'm ready to list the most important macros, "FRAME", "USES", "BYTE", "PERSISTENT" and "FEND". I'll be doing it in a separate message.

I'll be back soon!

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 4

----------------------- cut here ----------------------------------------

; ****************************************************************
; * MACROS FOR THE ALLOCATION OF VARIABLES *
; ****************************************************************
;
; Andres Djordjalian
;

NOEXPAND ; I strongly suggest it as listings could get rather
; long and unreadable with macro expansions

; *************************************
; * FRAME *
; *************************************

FRAME MACRO rutina

IFDEF alocTemp
#undefine alocTemp
ENDIF
#define alocTemp rutina#v(CERO)#v(CLAVE)
VOLATILES0 SET H'7F'
VOLATILES1 SET H'FF'
alocTemp SET 0

ENDM


; *************************************
; * USES *
; *************************************

USES MACRO rutina

#undefine alocTemp
#define alocTemp rutina#v(CERO)#v(CLAVE)
IF VOLATILES0 > alocTemp
VOLATILES0 SET alocTemp
ENDIF
#undefine alocTemp
#define alocTemp rutina#v(UNO)#v(CLAVE)
IF VOLATILES1 > alocTemp
VOLATILES1 SET alocTemp
ENDIF

ENDM


; *************************************
; * BYTE variable *
; *************************************

BYTE MACRO variable

IF VOLATILES#v(RAM_PAGE) > PERSISTENTES#v(RAM_PAGE)
variable EQU VOLATILES#v(RAM_PAGE)
IF VOLATILES#v(RAM_PAGE)<MIN_VOLATILES#v(RAM_PAGE)
MIN_VOLATILES#v(RAM_PAGE) SET VOLATILES#v(RAM_PAGE)
ENDIF
VOLATILES#v(RAM_PAGE) SET VOLATILES#v(RAM_PAGE)-1
ELSE
ERROR("Memory full, can't allocate automatic data")
ENDIF

ENDM


; *************************************
; * PERSISTENT variable *
; *************************************

PERSISTENT MACRO variable

IF PERSISTENTES#v(RAM_PAGE) < MIN_VOLATILES#v(RAM_PAGE)
variable EQU PERSISTENTES#v(RAM_PAGE)
PERSISTENTES#v(RAM_PAGE) SET PERSISTENTES#v(RAM_PAGE)+1
ELSE
ERROR("Memory full, can't allocate persistent data")
ENDIF

ENDM


; As you may expect, this needs initializing:

; *************************************
; * INITIALIZING ALLOCATOR MODULE *
; *************************************

PERSISTENTES0 SET H'20' ; Reconfigure this initial
PERSISTENTES1 SET H'A0' ; values to the target machine.
VOLATILES0 SET H'7F' ; The values given here are for
VOLATILES1 SET H'FF' ; a 16C73 or 16C65
MIN_VOLATILES0 SET H'7F'
MIN_VOLATILES1 SET H'FF'
RAM_PAGE SET 0
CERO EQU 0
UNO EQU 1
CLAVE EQU 86

----------------------- cut here ----------------------------------------

Well, these are the routines I'm using. There is much more to say about the application of these macros, and there is another macro I find quite useful for the allocation of flags, so I'll be continuing with this postings if there's interest.

As always, I'll appreciate any opinions. Regards!

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 5

===Erratas for my last posting============================================

I forgot to list the "FEND" macro!

----------------------------- cut here ----------------------------------

; *************************************
; * FEND *
; *************************************

FEND MACRO rutina

#undefine alocTemp
#define alocTemp rutina#v(CERO)#v(CLAVE)
alocTemp SET VOLATILES0
#undefine alocTemp
#define alocTemp rutina#v(UNO)#v(CLAVE)
alocTemp EQU VOLATILES1

ENDM

----------------------------- cut here ----------------------------------

Another one: the initialization of "VOLATILES0" and "VOLATILES1" is not necessary. Instead, you can assign those values to "INIT_VOLATILES0" and "INIT_VOLATILES1":

----------------------------- cut here ----------------------------------
INIT_VOLATILES0 EQU H'7F'
INIT_VOLATILES1 EQU H'FF'
----------------------------- cut here ----------------------------------

...and use these constants in the "FRAME" macro:

----------------------------- cut here ----------------------------------

; *************************************
; * FRAME *
; *************************************

FRAME MACRO rutina

IFDEF alocTemp
#undefine alocTemp
ENDIF
#define alocTemp rutina#v(CERO)#v(CLAVE)
VOLATILES0 SET INIT_VOLATILES0
VOLATILES1 SET INIT_VOLATILES1
alocTemp SET 0

ENDM

----------------------------- cut here ----------------------------------

...so as to have all the machine-dependent constants in the initialization part.

One more. The initialization of "MIN_VOLATILESx" must be incremented by one to:

----------------------------- cut here ----------------------------------
MIN_VOLATILES0 SET H'80'
MON_VOLATILES1 SET H'100'
----------------------------- cut here ----------------------------------

...or otherwise if there are no volatile variables you wouldn't be able to assign the last memory position to a persistent one.

===End of the errata for my last posting=================================

Since I began with the analysis of the problem I'm talking about "components" but I haven't specified accurately what I mean by such. I'll do it now and start with some practical matters.

When I said "a component" I was simply referring to a part of a system. It could be an large include file or just a short routine. All that I've written about doesn't depend on the level of granularity the system representation has. And the links between these components can be one subroutine call or several.

Of course, using the method with a system representation of a higher granularity (or to put it in other words, simply to build more frames, for every single routine for example) will result in an better use of RAM, but you'll have to write more.

The options seem to be what I've just mentioned, to build a frame for each routine, or making a frame for a whole include file. By the way, I suggest to divide your program into reusable include files for all this to make better sense. Having an include file with a global frame and one or more routine frames for particular routines seems to me hard to follow, and as there is no error checking for missing "USES ..." I think it would surely cause more than one headache.

This is getting a bit long, I'll continue on another message. I'll be back soon!

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 6

On my last posting I wrote about two ways in which frames could be built. One is to build a frame for a whole include file, for example:

------ cut here (beginning of an include file)---------------------------

; ***********************************************************************
; * MODULE THAT STRUCTURES AN I2C EEPROM IN BLOCKS AND CELLS (KEY EEP) *
; ***********************************************************************

IFNDEF I2C_INTERFACE
INCLUDE "I2C.INC"
ENDIF

RAM_PAGE SET 1
FRAME EEP
USES I2C
BYTE EEPblock
BYTE EEPcell
BYTE EEPregister
FEND EEP

; **************************************************
; * SAVE EEPregister IN [EEPblock,EEPcell] *
; **************************************************

EEPSaveBC: MOVF EEPblock & H'7F',W ; ...etc
------------------------------- cut here --------------------------------

Here there's only one frame named "EEP" for all the routines in the file. The routines use the component "I2C.INC", which also has only one frame, named "I2C". You can deduct this from the "USES..." line.

Similarly, if there's another component that uses this one, the frames where the variables belonging to a routine that uses any of these are defined must have a "USES EEP".

The other option looks like this:

---- cut here (the middle of another include file)------------------------

; **************************************************
; * FORMAT *
; **************************************************

FRAME BI2Format
USES EEP
BYTE BI2J
BYTE BI2I6
FEND BI2Format

BI2Format: CLRF EEPregister & H'7F'
MOVLW 1
MOVWF EEPblock & H'7F'
CLRF EEPcell & H'7F'
CALL EEPSaveBC ; etc...
------------------------------- cut here --------------------------------

This is the beginning of a routine that's part of a component (from now on I'll be calling a "component" an include file that adds a particular feature to a system, like an interface, a data structure, etc, to differentiate it from a routine)

I was saying that this routine is part of a component that uses the previous component. But in this case the frames are declared for each routine. The routine "BI2Format" uses the previous component (look at the last line listed) and that's declared on the beginning of its frame. Of course (didn't I mention this?) "USES..." clauses must precede "BYTE..." declarations!

BI2Format uses EEP's variables as an interface. That's the way to do it but, as these variables are not persistent, you should be careful. You mustn't call another subroutine and pretend that their values remain unaltered. For example, you shouldn't code something like...

------------------------------- cut here --------------------------------
MOVLW 1
MOVWF EEPblock & H'7F'
CALL KEYWhatever
CALL EEPSaveBC
------------------------------- cut here --------------------------------

This may not work as KEYWhatever might alter EEPblock's contents! It's not hard to get used to avoiding this practice. If you need to maintain the contents of an interface variable between other subroutine calls you should use another variable (for example, BI2saveEEPblock in this case). Changing EEPblock to a persistent variable would work but it would be hard to follow and might increase RAM usage.

There are two things I want to say about this posting's code that remain pending. One is the scheme I'm using to name variables, the other the "& H'7F'"s. I'll continue later.

Is my explanation on where to build frames clear? As always I appreciate your opinions. Regards,

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 7

Names for variables

If you automate the task of allocating registers you'll very probably have more variables than if you don't. That's because you'll be able to create more variables and also because this scheme is intended to facilitate code reuse and for that you shouldn't share variables.

So variable naming becomes and issue. You've already automated the allocation of registers and facilitated reuse, now you don't want to be dealing with the names of variables, checking to see that they don't repeat. And the same thing happens with routines' names.

The solution I'm using is giving every component a three capital letter keycode. For example, "KEY" for a keyboard interface, "MDI" for a multiplexed-display driver, etc. I document this keycode at the beginning of the source file, and when I do a new one I care that it is unique.

Every label belonging to a source code uses its keycode as a prefix. For example "MDIdigit", "KEYauxi", "KEYReadKey", etc.

For the rest of the names I use a common convention: start with a capital letter if it's a routine or with a small letter if it's a variable. To join several words into a label, instead of using an ugly underscore, start the new word with a capital letter.

I use this rule only because of a personal preference. What I do want to recommend is the use of the keycode to deal with the fact that all variables have a global scope. If you can think of another way please let me know.

I think this solution is pretty neat, and it has another advantage. If you are browsing through a symbol list looking for the values given to a component's labels you just have to search for the keycode and there you'll have all that component's symbols together.

Dealing with RAM paging

Ups, I haven't got much to say about this. The macros, as I'm using them now, give more-than-7-bit addresses. What I mean is that a variable allocated on page 1 could have, for example, address A7 (not 27). So, if you compile something like "MOVF thatVariable,W" you will get a message.

Surely the macros could be modified to give 7-bit-addresses, but I don't like it because symbol lists would give less information and I have the idea that source code could get rather confusing... I really didn't think much about it.

What I do is write "MOVF thatVariable & H'7F',W" to avoid the messages for those lines where I had taken care of ram paging. Typing those "& H'7F'" looks like a lot of nonsense work but with a reasonable editor you can do text substitutions or macros and save much of it. And on the other hand, on the source code you can distinguish between page 0 and page 1 variables and you still have messages alerting you of those cases to which you didn't pay attention.

But I think there could be something better, perhaps using "#define"s. By the way, I have never used four-ram-page processors, and this would not be very neat with them I guess...

Dealing with ROM paging

The ROM pages where components are placed can be documented on a client-server structure diagram like the one I sketched on my first posting.

As the reasonable thing is, given a client-server relationship, to try to place both components on the same page so as to save changes to PCLATH, the whole structure would have only a few clusters of components belonging to the same page. So with a quick look you can tell where you need to change PCLATH and where each component is placed.

I'm not using any automated scheme to change PCLATH, mostly because many times I do several sequential long calls and changing PCLATH for every call there would be a waste of ROM, a resource I always run short of.

Problems could arise when you want to reuse code and you find that if you maintain the original page assignments the code won't fit, or if you want to use a piece of code made for page 1 of a two-page processor in a one-page chip.

For those cases I guess that I would do second versions of those components that need to be changed. For example, if I did a display driver for page 1 and I want to change it for page 0 I'd copy that source and its clients and servers naming them differently (changing the key-code would not be necessary) and take out and add the PCLATH assignments that would be needed. Then, if I have a third project needing this driver, I'd have two versions from where to choose.

This message might have more questions than answers but I already told you that was my objective. I have been refining my ideas as I used them and I would like this process to continue here with yours!

On my next posting I'll list a macro I use to allocate flags. If you have any questions or ideas about the variable-allocation scheme please write.

Regards!

Andres

A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 8

Allocating flags

If you are programming for code reuse it doesn't look correct to assign flags belonging to different components with something like:

PERSISTENT flags
#define MDIerror flags,0
#define KEYcapsLock flags,1
#define EEPerror flags,2 ...etc.

...because for a different project you might need only some of these components along with others, and that would force you to reassign flags by hand, changing them in duplicate source files or doing all allocations in the main source file. None of these look very neat nor practical.

On the other hand, placing each component's flags on a local non-persistent variable would be a waste of RAM if you are to assign only two or three flags for each component, which I consider normal.

A macro that allocates flags would be useful, and making it is no big deal.

As for now I'm not differentiating persistent flags from non-persistent because I don't see much to win. The reason to allocate non-persistent data is to save RAM by permitting overlapping. Flags, being only one bit long, don't add much unless a component uses a lot of flags, and in that case those flags could be placed on local non-persistent variables instead of using the macro. Surely, if you are desperate for RAM and could profit from it, this macro could be expanded to handle non-persistent flags.

The algorithm I use to allocate the flags is simply the following:

  1. If there is no persistent byte available for flags allocate a new one
  2. Allocate the flag on the next bit available on that byte

This is handled by the following macro:

----------------------------- cut here ---------------------------------

; *****************************************
; * RESERVE_FLAG nameByte, nameBit *
; *****************************************

RESERVE_FLAG MACRO nombreByte, nombreBit

IF BYTE_FLAGS#v(RAM_PAGE)==H'FF'
BIT_FLAGS#v(RAM_PAGE) SET 8
BYTE_FLAGS#v(RAM_PAGE) SET 0
ELSE
BIT_FLAGS#v(RAM_PAGE) SET BIT_FLAGS#v(RAM_PAGE)+1
ENDIF
IF BIT_FLAGS#v(RAM_PAGE)==8
BYTE_FLAGS#v(RAM_PAGE) SET BYTE_FLAGS#v(RAM_PAGE)+1
BIT_FLAGS#v(RAM_PAGE) SET 0
PERSISTENT FLAGS#v(BYTE_FLAGS#v(RAM_PAGE))
ENDIF
nameByte EQU BYTE_FLAGS#v(RAM_PAGE)
nameBit EQU BIT_FLAGS#v(RAM_PAGE)

ENDM

----------------------------- cut here ---------------------------------

Which needs the following initialization:

----------------------------- cut here ---------------------------------

BYTE_FLAGS0 SET H'FF'
BYTE_FLAGS1 SET H'FF'

----------------------------- cut here ---------------------------------

This, again, is for a two-RAM-page processor. I guess that if you only add "BYTE_FLAGS2" and "...3" it would work on a four-page.

The macro returns a reference to a register and a bit number for you to allocate the flag with something like:

RAM_PAGE SET 0
RESERVE_FLAG XXXmyFlagByte,XXXmyFlagBit
#define XXXmyFlag FLAGS#v(XXXmyFlagByte),#v(XXXmyFlagBit)

Or, for a 1-page flag and to avoid messages:

RAM_PAGE SET 1
RESERVE_FLAG XXXmyFlagByte,XXXmyFlagBit
#define XXXmyFlag FLAGS#v(XXXmyFlagByte) & H'7F',#v(XXXmyFlagBit)

The ideal thing would had been to be able to do something like:

FLAG XXXmyFlag

...but it is not possible, as far as I know, because the assembler won't perform substitutions in a "#define". It's a pity, not only it would save some cut and pasting and typing, it would also be much clearer to read. I hope I'm missing something or, if I'm not, that future versions of MPASM include something to handle this.

As always, I appreciate your feedback. Regards!

Andres