Some simple extensions to a Pascal to 8051 Cross Compiler

Derek Kennedy, April 1998
Last Update 29th July 1998
e-mail: minerva9@hotmail.com

Who might be interested in this stuff?

1. If you experiment with the Intel 8051 series of microcontrollers and want to avoid having to write all of your code in 8051 assembly language, this may be of interest.

2. If point 1. applies and you prefer writing Pascal to writing C, this really should be of interest.

This page describes some enhancements which I made to an existing Pascal to 8051 cross-compiler.
The compiler supports bit, byte, word, integer and real variables. Enumeration types, records, functions and procedures are catered for.
Variables may be declared as residing at a user defined address. All of the standard Pascal flow control structures are supported (repeat..until, while do, case, if then else, labels). Sadly, char & strings were not supported.

This freeware Pascal to 8051 cross compiler is called the ElProg Pascal51 compiler and can be obtained from ftp://sistudio/pub/atmel/pascal51

If you want to download my compiler enhancer right now, use this link .... prepro.zip

As there appears to be no source code available for the Pascal51 compiler, it is difficult to add new features to the system (if anybody knows where the source can be downloaded pls. let me know!). Overall, the code generated by this compiler is not very ‘tight’. However, for those of us who don’t like to work with C, it is a worthwhile tool.

I missed being able to work with strings and also wanted to be able to mix 8051 assembler with Pascal51 code, so decided to look at how these features might be added.

The approach taken was to write a pre-processor program which takes a '.p80' textual source program (which supports some new features) and outputs a .p51 source file which will compile with the existing Pascal51 compiler. A post-processor was also needed to modify the generated .a51 assembler source file, the modified assembler file is given a '.a80' file extension.

The new features described here are :

- String support : A limited ability to declare strings for use in a Pascal51 program. e.g. myString := ‘hello’;
- Single Character support : Single character constants are allowed. e.g. MyByte := ‘B’; PutChar(‘x’);
- Inline Assembler support : Allows the creation of Pascal51 procedures which are implemented inline with 8051 assembler code.

      e.g.
      Procedure ReadTimer; {*Assembler}
      begin
      RDTIME:
        mov A,TH0
        mov R0,TL0
        cjne A,TH0,RDTIME
        mov R1,A
      end;

- External Procedure support : Allows Pascal51 to make calls to assembler routines which are implemented as external 8051 assembler routines.

      e.g.
      Procedure ClearOut; {*External}
      begin;
      end;

String support

A new type of global declaration block is supported.

e.g.

    Strings
     Start at $4000;
     mystring := 'abcde';
     mystring2 := 'eric';

This block must appear just after the program global var declarations.
Only one such block is allowed (so strings are ‘global’).
The block ends with a blank line.

Note that the start address should be chosen to place the generated strings in a free area of ROM.
When the pre-compiler finds a string, two actions are taken.

First, a declaration is generated -

e.g.
mystring at $4000 : string; {‘abcde’}
mystring at $4006 : string; {‘eric’}

then a .asi (ASsembler Include) file is written to hold the necessary definitions for the strings.
This file is merged with the .a51 file (to produce a .a80 file) later on.

e.g.
org $4000
db 5
ds ‘abcde’
db 4
ds ‘eric’

Notice that the strings are of type ‘string’, which needs to be declared thus :

    type
    string =
     record
       len : byte;
       chars : array[1..2] of byte;
     end;

As you can see, each string starts with a byte which holds the length of the whole string (like Borland Turbo Pascal), strings have a maximum length of 255 bytes. The actual string is held in the array of chars. Since the upper bound of the chars array is not checked by the compiler, it does not really matter which upper bound value you use.When using strings as parametes to procedures & functions you will almost always want to declare these as 'var' parameters.

If you want to have RAM based strings which can be manipulated at runtime,
just declare your RAM based string :

    var
      ramstr at $2000 : string;

and if necessary, initialise it using a procedure such as :

    procedure InitString(var ramStr : string; var romStr : string);
    {note the use of var parameters to ensure the passing of addresses}

    var
      ii : byte;

    begin
      ramStr.len := romStr.len;
      for ii := 1 to romStr.len do ramStr.chars[ii] := romStr.chars[ii];
    end;

Some sample procedures which use these strings are :

    procedure Wait_TX_Ready;
    begin
      Repeat Until TI=1;
    end;

    procedure WriteCh(ch:byte);
    {write a character to the serial port}
    begin
      TI := 0;
      SBUF := ch;
      Wait_TX_Ready;
    end;

    procedure WriteString(var astring : string);
    {write out a string to the serial port}
    var
      ii: byte; {Note : Using a global ‘internal byte’ as a loop index is much more efficient}

    begin
      with astring do
       for ii := 1 to len do WriteCh(chars[ii]);
    end;

    function Pos(var aString:string; TargetChar:byte):byte;
    {returns the index at which a character is located within a string
       or 0 if the char is not found in the string}

    var
       ii : byte;
       found : boolean;

    begin
      Pos := 0;
      found := false;
      ii := 1;
      while (ii <= aString.len) and (not found) do
      begin
        if aString.chars[ii] = TargetChar then
        begin
          Pos := ii;
          found := true;
        end;
        ii := ii + 1;
      end;
    end;

    procedure LeftString(var aRamString : string; var aString : string; CharCount:byte);
    {set aRamString to the leftmost CharCount characters of aString}
    var
      ii, jj : byte;
    begin
      jj := CharCount;
      if jj > aString.len then jj := aString.len;
      aRamString.len := jj;
      for ii := 1 to jj do aRamString.chars[ii] := aString.chars[ii];
    end;

    procedure RightString(var aRamString : string, var aString : string, CharCount:byte);
    {set aRamString to the rightmost CharCount characters of aString}
    var
      ii, jj : integer;
    begin
      jj := CharCount;
      if jj > aString.len then jj := aString.len;
      ii := aString.len;
      aRamString.len := jj;

      while jj > 0 do
      begin
        aRamString.chars[jj] := aString.chars[ii];
        jj := jj-1;
        ii := ii-1;
     end;
    end;

Single Character support

This is basically a way of avoiding having to calculate the hex value of single characters which are held as byte values.

Instead of writing

    case ch of
       $41: WriteString(messageA);
       $42: WriteString(messageB);

…..

you can write

    case ch of
       'A' : WriteString(messageA);
       'B' : WriteString(messageB);

…..

You can use this technique anywhere a single byte constant value is expected.
The pre-compiler simply performs a string replacement.

Inline Assembler Functions

This allows you to write asm code directly into your Pascal source file.
The flag {*Assembler} is required.

The procedure implemented in asm code is visible to the Pascal compiler.
Note that it is the responsibility of the user to ensure that any registers used are suitably restored.

    Procedure mystore; {*Assembler};
    begin
       mov a,#$66
       mov dpl,#$45
       mov dph,#$60
       movex @dptr,a
       ret
    end;

Ensure that instructions don’t start in column 1 of the source line (or they will look like labels to the assembler).
Note that labels can be used - just ensure that the label name is unique and that the label starts in column 1.

External assembler functions

This feature is handy when you have an existing assembler routine which you wish to call from Pascal, but don’t want to include the assembler code inline.

    procedure ExtTest; {*External}
    begin
    end;

The pre-compiler builds a list of all of the procedures which are implemented as ‘External’.
This list is used to scan the generated .a51 file and remove the stubs of assembler which the compiler produces. The modified output is written to the .a80 file.

How to use the pre-preprocessor program

First, get a copy of the Pascal51 compiler (see above) & experiment with it.

Next, download my program & sample files for the pre-processor. prepro.zip

When you run the pre-processor program for the 1st time, you should double-click on the compiler and assembler label text to setup your environment & let my program know where to find the Pascal51 compiler & your asm51 assembler.

Next, create a new directory for your new project. Write your Pascal project source file & save it with a .p80 file extension. e.g. test.p80

Run the Pascal51_preprocess.exe, double click on the Project label & select the test.p80 file.

Press the ‘Pre-Process’ button. This should scan the test.p80 file & produce a test.p51 file.

Now Press the ‘Compile’ button. This should run the Pascal51 compiler and generate test.a51.

Now click on the ‘Post-process’ button to generate a ‘test.a80’ file.

Then click on the 'Assemble' button to assemble the test.a80 file.

The 'Link etc.' button runs a batch file (<projectname>.bat) which should reside in the project directory.
This is handy for running a linker & hex to binary program and anything else which you use. You need to create the .bat file yourself.

The 'View Listing' buttons allow you to view the listing files & find any errors – you can use Windows file association to associate your favourite code editor with the .a51 and .lst file extensions.

Pressing the ‘Edit Project’ button allows you to edit the project (.p80) source file.

The pre/post processor tool is written in Borland’s Delphi. It should run OK on Windows ’95 or NT 4. It takes a very simplistic approach to parsing the input files - if you don’t get the syntax correct, the program might hangup without warning (so, keep backups of your source files). If you want the Delphi source code, use this link ... delphisrc.zip

(Interesting) things to note about the ElProg Pascal51 compiler

The compiler always seems to use the accumulator to hold the return value of functions whose return type is ‘byte’.

Watch out! The compiler generates incorrect code for functions whose return type is 'word'. Thanks to Mike Conboy for bringing this to my attention. I'll eventually fix up my assembler post procesor to sort this problem.

‘For’ loops can use bytes, words and enumerated types as loop counters, but can’t use integers.

Functions can’t return a user defined ‘type’.

Variables of type 'bit' can be set to 1 or 0 by assigning the variable to 'true' or 'false' respectively.
Variables of type 'boolean' are held as a single byte and can be set to 1 or 0 by assigning the variable to 'true' or 'false' respectively. Note however, that bit and boolean variables are not assignment compatible.

Sadly, the 8051 code generated by this compiler is not very efficient. However, it is free!

Futures

I hope to made a (very simplistic) attempt to optimise some of the code generated by the compiler. Certain jump and call codes could be optimised. (e.g. where possible, some LJMPs could be converted to SJMPs or AJMPs).

If you use this package, please drop me an e-mail & let me know how you get on.

Other Stuff

The original documentation for the Pascal51 compiler mentions a Mr. Vladimir Gladyshev, working out of Moscow, Russia. I reckon that he deserves a mention here.

Thanks to the good people at Silicon Studio Ltd. http://www.sistudio.com for making the Pascal51 compiler available for download.

Just out! A 'pre-release beta' of an excellent, low cost, Pascal to 805x development system. This features an integrated development environment, compiler, assembler and simulator. Code can be developed to be compatible with Inprise Delphi. Code generation from the beta looks to be very tight and in a different league from ElProg Pascal - the demo system now allows code sizes up to 1Kb - yes, this compiler can pack a non-trivial program into this small space! Another (longer established) version supports Z80/Z180. More details (and demo 8052 & Z80 system downloads) at http://users.iafrica.com/r/ra/rainier

If you are looking for a good 8051 simulator, try http://www.vaultbbs.com/sim8052

I've just found a long-standing (since 1984) Pascal cross-compiler which targets to 680x0, Z80 and 8051. Known as the MXPascal Multi-Target X-Compiler, details and a download can be found at http://www.pds.com.au/mxpascal/. I hope to have a closer look at this soon & will report on my findings here.

If you have lots of money to spend & want a supported Pascal to 8051 family toolset, look at http://www.ksc-softsys.com

This page hosted by Get your own Free Home Page