Some simple extensions to a Pascal to 8051 Cross Compiler

Derek Kennedy, April 1998
email: 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 dont 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 to 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' paramters.

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 dont 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 dont 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 Borlands Delphi. It should run OK on Windows 95 or NT. It takes a very simplistic approach to parsing the input files - if you dont get the syntax correct, the program might hangup without warning (so, keep backups of your source files).



(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.

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

Functions cant return a user defined type.

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).



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.

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

