Real-Time 8051 Systems - An Overview
|< back

The microcontroller-based controller card is now an everyday building block for many turnkey products from data loggers to engine management systems. The microcontroller used is typically an 8 bit device such as the 8051 or the 68HC11, equipped with 3 - 5 IO ports and an Eprom for the program. The typical application is invariably real time in nature. This means that software run times are bound by the necessity to process input data derived from real world events - calculations based on values read from on-chip timers is an obvious example; processing data received from a serial port is another.

Generally, devices like the 8051 have an Eprom containing the code and possibly an external RAM, although this is often not the case - frequently, just the 128 or 256 bytes on the internal chip are available. Eproms are normally used for cost reasons; masked ROMs are relatively uncommon because of the masking cost and engineers' reluctance to cast their software in concrete! Eproms give the option to change software after release if bugs are discovered or a change in specification is required.

8051's are also commonly used as communications controllers, either via on chip I2/serial interfaces, CAN or external UART devices. With a fairly decent output compare/input capture system on variants like the C515C and the 80C537, programs are often making timing measurements between events, using, in the case of the 537, the 16-bit timer 2. Calculations based on the time between captures typically follow in the course of freqency/speed measurements etc..Any impact on the processor operation caused by debugging, for instance, is going to render the results invalid.

With any real time system, whether it is during the development phase when the software is first being coded up, or in any final pre-release software quality assessment phase, the system can only properly be tested dynamically in real time.
Overall, the fact that these systems' inputs are dependent on real world events or real interrupts means that any testing or debugging which prejudices the system behaviour must, by definition, invalidate any data so gleaned. In addition, the loss of real time execution inevitably alters the interaction between say, interrupt and background routines so that critical regions may never be found. This is really all part of the more fundamental debate as to whether static analysis of real time systems is actually a valid thing to do at all. Certainly to date, this has been the conventional approach but recent defence standard requirements for software production do actually require some sort of dynamic testing. Hopefully, this will bring the matter into wider discussion.

The Increasing Sophistication Of Microcontroller Software

Traditionally, program development in the particular case of the 8051 processor (the subject of this article), has been done in assembler and thus complicated calculations have tended to be excluded. This is because firstly, the 8051 is not a particularly good number-cruncher, and secondly, the amount of work involved in coding up maths routines in assembler. However, with the increasing use of C, the relatively simple 8051 instruction set becomes rather remote to the programmer and the tendency is to do more complicated things, simply because C makes it so painless to do it. For instance, a 32 by 16 divide in assembler is a very major piece of software, whereas in C it would constitute maybe just one source line.

Naturally, programmers get easily carried away with this and produce more complicated code than before. Most significantly, from the debugging/testing point of view, real time programs can now be viewed as a series of readable C statements, where the logic and function of the program are the main issues, rather than how the instruction set has been coerced into carrying out the program's specification.

Of course, when it comes to testing and debugging, the engineers producing these programs will tend to think only in terms of C statements and real program variables, unlike the assembler programmer, who being so close to the machine, will treat variables as the inhabitants of esoteric and largely anonymous registers.

Pseudo-Static Testing Methods

Sub-routines or even small functional blocks such as a calculation are commonly tested statically; the program may be run to the beginning of the calculation, some input values loaded up, and the program released to run in real time. At the end of the section to be tested, execution is broken and the result(s) extracted and checked for correctness.
This is a perfectly valid method, provided that the input values are totally independent of real world events or time, and that program operation could not possibly be upset by interrupts. Another extension to this problem is that once the program or software is working to a reasonable standard, often the programmer needs to get a feel for what the thing actually does - in simple terms, play around with the software's inputs whilst the program is running. This often reveals unexpected operation when say an input goes to full scale. Again conventionally, because of the way in which the 8051 and probably most other microcontrollers are constructed, the contents of any RAM locations/variables are totally invisible during program execution. Whilst they can be made visible with some advanced development systems by setting breakpoints on write accesses to certain variables and looking at the data, real time operation is inevitably lost in the very act of extracting the data. Even if the emulator can carry on automatically after the breakpoint, at least several hundred microseconds are lost while the emulator actually retrieves the value.

Many simpler emulators will not even do this basic task. For much of the time with this sort of software development, the programmer has to simply keep going through the listings, doing a "what if" exercise on every variable, possibly extending to a '"what if" variable = such and such, and interrupt occurs at a certain point' etc. All the potential permutations of variable values, program paths and interrupt times makes the testing of this sort of real time software almost impossible.

The Link Between Hardware And Coding Approach.

With applications coded in assembler, the memory model that is used is really very much embedded into the software and reflects fully the physical memory configuration of the hardware. For instance, if the programmer is going to use an external RAM chip, a conscious effort must be made to use MOVX @DPTR instructions when moving data around. Thus if the program is written using solely the internal memory, to convert it to use external RAM (should more space be required) would be a very major undertaking - all the move indirect/direct instructions would have to be converted to include the loading of DPTR and the use of MOVX @DPTR.

Such an exercise is almost impossible given a program of any size, or at the very least extremely laborious. Really in this sense, the software is as rigid and inflexible as the hardware design. If the software is designed to use only internal RAM, it is almost as difficult to convert it to use external RAM as it is to change the PCB design to add the RAM chip to the hardware in the first place.

Coding For A Variety Of Memory Models

A new possibility has been opened up in this area for engineers using C compilers such as the Keil or IAR C51's. These compilers will support many different memory models - the memory model being determined by just a software switch at the compilation stage. It now becomes extremely simple to change the memory model assumed by the software during development. The memory model used is no longer implicit in the nature of the program instructions used by the programmer.

An example of how this memory model switch works is as follows: if the SMALL model is selected, the compiler will assume that no external RAM exists and all function parameters, variables etc will be located within the internal directly or indirectly addressable RAM segments of the microcontroller's memory. The large model, if selected as a global model, will cause all variables and parameter passing areas to be forced into external RAM, where they will be addressed by the MOVX @DPTR instruction.

The stack, as has to be the case with the 8051, always remains in internal memory. Typically in expanded 8051 systems, the external RAM will be used for parameter passing and variables, constituting the LARGE model for the C compiler. The stack now has a free run of the internal memory. There is, unfortunately, a penalty in going to the large model, as within the basic 8031 there is only one DPTR that can be used for moving data in and out of external memory devices, so any addressing of an external variable will require the loading of the DPTR and then the moving of the data to the target location, via the accumulator. There is a COMPACT model which will actually allow paged memory to be used. This is actually slightly better in that external memory movements are made with respect to one of the eight general purpose R0 - R7 registers. This, in actual fact, does not require the constant loading and unloading of the DPTR and so probably represents the best way of using external RAM.
Variable Characteristics In Assembler And C Programs

The upshot of all this is that a program which is initially written for the SMALL memory model can at the flick of a software switch be recompiled to produce a totally different set of 8051 instructions that can now make use of external RAM. This, as has been said, has not been possible with assembler - assembler programs carry their memory model with them at all times and it cannot change. With the C compiler route, the memory model may be changed at will. This ease of conversion opens up an interesting new debugging/ testing possibility that has been well explored by the author.

An New Approach To Testing Real Time 8051/68HC11 C Systems

With the previously stated testing problems and memory model points in mind, here is a solution to the real-time debugging problem of 8051/68HC11 systems.

When a module or function is first being coded up, it will actually be compiled down using the large model, regardless of the final model to be used - normally determined by the hardware design. This has the effect of pushing all the variables into external RAM. During the debugging phase, the variables will remain in external RAM. Assuming that the compiler will produce code of identical functionality, regardless of model used, the programmer is left in the happy position of having all the variables and parameters of the new code sitting in external RAM rather than buried within the on-chip RAM.

The debugging via an in-circuit emulator is done at C source level, so that the user will single-step C lines or trace lines of C and will basically only view the program in terms of the C source lines originally created using his text editor. Essentially, just the logic of the program is actually being tested here, not the way that the 8051 is being driven by the compiler's opcodes.

As has been said, many 8031 systems have only an external Eprom, with all RAM existing on-chip. The use of an Eprom however, does mean that the designer has already opted to lose ports 0 and 2, choosing to bring the bus out instead for the Eprom.

Having program variables located in external RAM therefore may appear unwise. However, using the in-circuit emulator's own memory, this absent RAM can be easily created. The program will therefore run using RAM within the emulator. Even though this external area is not actually part of the final design, providing the emulator is attached, the RAM is available and any LARGE/
COMPACT model C programs can make use of it and run correctly.

Ultimately, the whole point of doing this is that the better in-circuit emulators will actually let you recover the values of variables located in external RAM, in real time. This is certainly true for the Hitex teletest XX emulators, where the RAM mapped into the data space is actually dual-ported. This means that the 8031 can access the RAM, and in between cycles, the emulator's own hardware can get in and recover specific variables. Once extracted from the 8031 system, their values can either be reported to the user on the controlling PC screen, or directed into the emulator's trace buffer - it doesn't really matter which.

In effect what we have got now is the ability to recover C variables or program variables in real time "on the fly", using the real 8031 hardware. The only respect in which it differs from the proper hardware is that it has got the in-circuit emulator supplying the external RAM, where no RAM chip position actually exists on the board.

A Simple Example

The value of this technique is best illustrated by the following real-time testing example:

The speed of a rotating shaft is to be measured in real time as a scaled value between 0 and 10000 rpm and from this a variety of other calculations performed. The problem here is a typical 8051 task; an interrupt is generated by an input capture pin every 180 degrees of shaft rotation. Every time a negative transition occurs on the capture input 0, the TIMER2 counter value is latched into an input capture register. This is done every 180 degrees.

The input capture service routine picks up this value, subtracts it from the last value and works out the time in TIMER2 counts between successive interrupts or 180 degree markers on the shaft. Knowing the time between interrupts, it is possible to work out the rotational speed.
The calculation to be performed in the service routine every 180 degrees of shaft rotation is:

time_for_180 = abs(time_this_180 - time_last_180) ;
speed = 382500 / (time_for_180) ;

Where 0 to 255 represents 0 to 10000 rpm and the numerator is a constant that assumes a 2us clock period.
With the emulator configured correctly, the values of time_for_180 and speed are displayed on the screen with the program running in real time. Clearly, with the input values being dependant on real time events, any interference would cause an error in the measure times. By the same token, any corruption of the result by other interrupt routines will now be obvious - the result on the screen will be wrong. Now, with the interrupt rate constant, the output from the calculation will be constant. The calculation can now be checked from the screen values by using a pocket calculator...

Conventionally, this calculation could only be tested statically - influence from other interrupts could only be done by a "what if" exercise on the source listings and logical bugs might ultimately only show up as faulty operation at a port pin.

In addition, being able to see the results of the C calculation change in real time as, in this case, the output of a signal generator is altered, is very reassuring to the programmer.

Having verified the correct operation of the function, it can now be recompiled using the small model. Assuming that the compiler produces functionally identical code regardless of model, the function can be considered debugged.

Another problem which can be partially solved by this method is, for instance, buffering timer values and incoming serial port buffer contents into a visible external RAM location. Thus the mystery of a timer's value on entry to an interrupt routine can now be solved.

Some Practical Points

The following points should be noted:

(i) For situations where the emulator supplies the external RAM chip, it should be noted that #RD and #WR should be reserved and not used as IO port pins.

(ii) If the output data being observed changes too quickly for a satisfactory display on the emulator's host PC screen, it could be channelled into the trace buffer for later examination. Real time operation is again not prejudiced.

(iii) There is only one drawback with this method - in very high speed, very time critical systems, the extra runtime caused by using external variables might prevent the required operating speed from being reached. A partial solution to this is to compile using the final SMALL model but force only specific variables into the accessible external RAM by declaring them as XDATA. Here they can be observed for out of range values etc. during real time operation.

Summary

To summarise then, with the need to test real time systems in real time to get valid results, program variables will have to be made visible so that they can be checked. Being pragmatic about the side effects of this, unless the silicon manufacturer allows the internal memory/register file to be accessed, there is always going to be some overhead involved in retrieving program data on the fly. However, the method outlined goes as far towards the goal of real time dynamic software testing as is likely to be achieved for 8051 family microcontrollers.