I’ve been focusing back on the 68000 SBC for the last few weeks.
I’m my previous post I mentioned that I’d had no problems with the MAX232 (PDF) portion of the board. As it turned out, this was not strictly true.
It seemed that after the board was powered on for a few hours the XR88C681 (PDF) DUART would send and receive nonsense data on the RS232 console port. I also noticed that the MAX232 IC got rather warm when this was so. To rule out any problems with the DUART itself I removed the MAX232 and attached a USB TTL-level serial port to the headers I added just for this kind of purpose. The result was the UART ports were both working perfectly, but not when the MAX232 level shifters were in play.
I then swapped out the MAX232, thinking it might be faulty, and tried again. Same result: after a few hours the IC got hot and passed nonsense characters. At this point I thought it might be the circuit or the caps used.
After swapping the IC for a spare a second time, success. The serial interface worked fine even after many days and the IC remained cool to the touch. I’ve somehow accumulated about 10 of these ICs over the years. It seems I might have, along the way, bought some fakes.
For interest sake here’s a picture of the working part, top and bottom:
And a suspected fake:
It’s not difficult to draw any conclusions from these pictures. These ICs have been in production for three decades now. Maxim likely change the branding laser engraved on the ICs to match tweaks to their logo. But if you look closely at the fake you can clearly see that, particularly, the last M in MAXIM is printed at a jaunty angle. I can’t see a big semiconductor manufacturer making this mistake. It’s also interesting that the fake has no writing on the back-side. Reading around it is clear that there MAX232 is a common victim of IC fakery, and the fact that these ICs “half work” points squarely at them being fakes.
Actually this is not the first fake IC I believe I’ve come across. MINI000 uses two AT28C256 (PDF) 32KByte EEPROMs and I had to refill my supply as part of building the board. I bought two batches of five from two different random Chinese eBay sellers. After receiving them I set about testing them using my trusty old home made programmer.
The first set of five all would not program at all and always read back 0xff. It is this batch which I suspect as being fakes. The other batch were almost certainly chip-pulls; ICs that had been removed from old equipment: they came programmed with data, what that data actually is I’m not yet sure but will at some point try to determine, just for curiosity’s sake. Only one EEPROM was actually programmable. It was this IC which found it’s way into the MINI000 board.
At this point I should point out that the AT28C256 posses a “software write protect” mode. To lock and unlock the contents of the IC a magic sequence of bytes to a magic sequence of addresses, following specified timing rules, must be written to. I did attempt to extend my programmer firmware with this ability but it appears the limitations with how the address bus is formed – via a pair of 8 bit counters – prevent the EEPROM from recognising the special sequences.
So it’s not completely clear that the un-programmable and loaded with 0xff EEPROMs are fake. They could just be write protected without ever being programmed. However a key point is these EEPROMs do not ship from the factory write protected. And also there are quite a few reports of fake AT28C256s out there and in each case the fakes read back 0xff. So my conclusion is the 4 loaded with data just need unlocking to be useable, but the five that read back with 0xff almost certainly are fakes. Luckily one of the ones loaded with data was already unlocked and I only needed the one.
One thing I could do, to allow me to use the four locked but probably good parts, is to buy a proper programmer. They are only around £50. One downside is the software to drive the programmer is almost certainly Windows only. It is tempting though as they support thousands of different parts: parallel EEPROMs like the AT28C256, serial EEPROMs, and certain MCUs. Another idea, just to make use of the four good but unusable EEPROMs, would be to build a programming rig on breadboard and drive it from the MINI000 board. Generating the unlock sequence in 68K code would be trivial.
I think in future I will try to buy parts from established component suppliers in the UK, if said part is current, as the AT28C256 still is, even if it costs me a bit more money. If the part isn’t current I will try sticking to more reputable Chinese suppliers like UTSource, who have been terrific in the past.
So, to the software side.
The machine-code monitor is coming along, slowly. I’ve written a command line parser and am currently debugging it. It uses the previously described asciitoint subroutine to extract data from a command line buffer, which will be in the following form:
<command> <param1> <param2> <...>
Params are bytes, words or longs in hex. Unlike with my 6809 monitor it will be possible to specify hexadecimal values that automatically “expand” to the needed data size. This is pretty much essential because specifying a 32 bit address every time one wants to dump out some memory would get extremely tedious, especially as the upper 8 bits are unused on the 68000. Therefore a particular command will accept a value up to a maximum data width. For example a possible command “writewords” will take a value up to the width of a long (expanding it to a long), followed by a sequence of values up to the with of a word (expanding them to words) writing the words to the specified address. For example, the command:
> writewords 0 1 2
Would write 0x0001 followed by 0x0002 to the address 0x00000000.
Inside the parser, the command line text will be passed in via a0, and the command name will be copied into the buffer pointed to by a1. The parameters will be written into a buffer at a2. The format of this buffer will be a sequence of type and value. Type will be the smallest size value width that will hold the value parsed: 1 for byte, 2 for word, and 3 for long. A type of 0 will mark the end of the list. The type field will be written into a 16 bit word, to keep the values in the buffer aligned. For convince the value will always be expanded to a zero-extended long.
Hopefully I will have a useable monitor in a few weeks. The first use of it will be to try out some peripheral ICs, like a 6522, on breadboard and see if the 68000 can successfully address its registers. I also want to experiment with a SC2681 (PDF) and try to ascertain why one did not work in the MINI000, as I found during board bringup.
Of course a machine code monitor, as I previously found with the MAXI09, is the perfect step towards, and a component of, more useable system software, ie. an Operating System.
As a break from writing (and learning as I go) 68K assembly, I’ve been playing about with running C code on my 68000 SBC. And I’ve had some success, which I’m rather pleased with.
The following is a loose description of how I went about implementing a simple LED flasher and buzzy noise maker in C.
Unlike when I experimented with running C programs on the MAXI09 there is no underlying, written in assembly, OS to call down to. Instead the C code runs “on the metal”, albeit after the existing written in assembly boot-loader, with no routines available for lower level access. Instead the C code itself must manipulate the peripheral IC registers in order to, for example, transmit a character on a serial port.
Here’s the test program in all it’s glory:
#define PORT_IO(port) (*(volatile unsigned char *)(port)) #define LED 0x100001 #define BUZZER 0x100003 unsigned char tone = 50; unsigned short delay; const unsigned char led_on = 0xff; const unsigned char led_off = 0x00; void do_delay(void); int main(void) { unsigned char silence = 0; while(1) { PORT_IO(LED) = led_on; do_delay(); PORT_IO(LED) = led_off; do_delay(); PORT_IO(BUZZER) = tone; do_delay(); PORT_IO(BUZZER) = silence; tone += 10; } return 0; } void do_delay(void) { for (delay = 0; delay < 0xffff; delay++) __asm__ __volatile__(""); }
Writing to the LED IO port is accomplished by poking into a constant pointer, not something you usually see in C programs. This is done through a macro, PORT_IO. This macro casts the parameter, the port address, into a byte pointer and then dereferences it. This can be used for both reading and writing to a port.
The volatile keyword was added only after I had significant problems with running code built under optimisation (-O1 or -O2 on GCC’s command-line). The compiler was optimising out the access until it was marked with volatile. This is discussed in some detail at a Wikipedia article on the topic.
Another problem I encountered with optimised builds was with the do_delay() subroutine. Under -O1 and -O2 the delay loop was being optimised away, since it did no “useful computation”. Adding the __asm__ __volatile__(“”) is one way to instruct the compiler that it must keep the loop in. Alternatively I could have put that subroutine in it’s own .c file and built it with an explicit -O0 to disable optimisations.
To make things interesting the program uses a number of C language features, specifically around data storage:
- It uses read only global variables (the unsigned chars marked const).
- It uses a global with no initial value (delay).
- It uses a global which does have an initial value (tone).
- It uses a stacked variable (silence), albeit one that is never changed.
Each of these variable “classes” (I’m not aware of the correct terminology here) has a different process to go through in order to be available to the running program.
But before I could compile any code, the first problem to overcome was the fact that the GCC cross compiler published on Debian’s repositories is geared towards building code on the 68020 and later. Whilst the compiler itself can generate 68000 compatible code, the libgcc supplied uses 68020 “extensions” which do not work on a 68000. I noticed this pretty quickly when disassembling some compiled code (not the program above, which can be built without it) and noticed it used long branches, something the 68000 does not do. Libgcc contains support routines that program code will call, indirectly, to carry out particular operations. For example if a processor does not have a particular width division in it’s ISA, the operation can be implemented in software in that particular professor’s implementation of libgcc, with those calls being completely transparent to the programmer.
As it happens GCC has a configure-time option to build a libgcc for each of the MPU variants within a particular architecture but the Debian build does not make use of this. I was faced with the prospect of doing my own cross-compiler build, after thinking I could make use of the Debian-maintained one.
Fortunately it wasn’t too difficult. I started with building up only the minimum: binutils and gcc itself. One if the nice things about building my own cross-compiler is I can build the very latest versions, which is nice.
It’s actually pretty amazing that you can compile code using such a new compiler, which in turn supports the very latest language features, and target a now 40 year old MPU, albeit an awesome one like the 68000.
For my own record, and anyone faced with building their own 68000 cross compiler on Linux, here are the configure commands used for both packages:
binutils:
../binutils-2.32/configure --target=m68k-elf --prefix=/usr/local/m68k \ --disable-nls -v
gcc:
../gcc-8.3.0/configure --target=m68k-elf --prefix=/usr/local/m68k/ --without-headers \ --with-newlib -v --enable-languages=c --with-multilib-list=rmprofile \ --disable-libssp
You’ll notice that this will not build a C++ compiler. I have some build errors to work through before I can think about building C++ code. The interesting thing about using gcc as a cross compiler is that it includes compilers for other languages, for example Go. It’ll be fun to see if I could some day run programs written in Go on my 68000 SBC!
Building the LED flasher was done in two steps; compile and link. Fist up the compile step, which is trivial enough:
$(CC) -mcpu=68000 -Wall -O2 -c $< -o $@
Linking, however, require some special options as the image being run is not running on top of an OS. The command-line specified:
$(CC) -mcpu=68000 -T linker.scr -nostdlib -nostartfiles $^ -lgcc -o $@
Briefly:
- -mcpu=68000 : Link against 68000-compatible libraries.
- -T linker.scr : Use a custom linker script, described below.
- -nostdlib : Don’t link to the standard libraries.
-
-nostartfiles : Do not insert the standard startup code.
I’m using gcc, the compiler front-end, for the linking step, rather then ld. The -mcpu option takes care of selecting the right libraries, something you’d have to do explicitly if ld was invoked directly.
Several actions need to be performed before the main() function can be run. These tasks are normally performed by the startup code which is a component of the compiler and targeted at the particular MPU and OS in use, but since I have no OS I have to write my own. Startup code overlaps with the linker script, since it is the linker script which describes the locations for the various sections (code and data areas) in the program. It does this by setting variables for the locations and sizes of the various sections. These variables are read by the startup code. The startup code must carry out the following actions:
- Global data in the .data section that has an initial value needs to be copied out of its load time address (LMA) in the EEPROM to it’s runtime address (VMA) in RAM.
- The .bss section needs to be cleared. This section holds uninitialised global variables, which the C standard mandates must have a zero value when main() is entered. This is in contrast to uninitialised variables on the stack, which can and do have random values.
- The main() sub is ran. There are different options here, but since there is no caller of the program the start-up code simply loops back to running the main() subroutine if it should return. Needless to say, there is no way to pass in the traditional argc and argv arguments.
Whilst it could probably have been written in C, I chose to write my startup code in assembly. It uses addresses defined in the linker script for the start addresses and sizes of the various sizes and either copies (.data) or clears (.bss) them, before running main() via jsr.
The linker script is getting pretty complex, and you can view it on github if you are interested. Briefly, RAM starts at location 0x000000 and contains the .data and .bss sections. ROM starts at 0xf00000 and contains the .text (confusingly this is where code lives) and .rodata sections. The only real complexity is that the ROM also contains the initialised global data values, which are copied into RAM (in the .data section) by the startup code.
An illustration of the memory arrangement can be had by looking at the generated code with objdump:
test.elf: file format elf32-m68k Sections: Idx Name Size VMA LMA File off Algn 0 .data 00000001 00000000 00f008c2 00002000 2**2 CONTENTS, ALLOC, LOAD, DATA 1 .bss 00000002 00000004 00f008c6 00002001 2**2 ALLOC 2 .text 000000be 00f00800 00f00800 00000800 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 3 .rodata 00000002 00f008c0 00f008c0 000008c0 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .comment 00000011 00000000 00000000 00002001 2**0 CONTENTS, READONL
The input for the program is test.elf, which is the linked – but not yet writeable to EEPROM – program. Producing a flashable image is done subsequently, using objcopy an dd to pad the file to the next 64byte EEPROM page. Going through the sections:
- .data is 1 byte (which holds the tone variable). It has an initial value at 0xf008c2 in the EEPROM and resides at 0x0 in RAM at runtime.
- .bss is 2 bytes (the delay global) and resides start at 0x2 in RAM. Why it requires an address in the EEPROM is interesting, since those addresses are never used. I will have to investigate.
- .text holds code (startup.s and test.c, given above). It’s runtime memory address is 0xf00800, 2KBytes in, and just after the bootloader.
- .rodata is 2 bytes. The led_on and led_off constant variables. in EEPROM at 00f008c0, reside here.
- .comment is something the compiler/linker supplies. It contains the compiler name and version number (GCC: (GNU) 8.3.0 in this case). This is not copied into the runable image.
Objdump is a terrific tool. As well as dumping out data on sections it can also disassemble, and if code is compiled with debug information (-g on the GCC command-line) then it will show inline C source-code. It was using the disassembler that highlighted the problems with performing optimised compiles, which I briefly touched on earlier.
The summary of all this is I can run C programs on my MINI000 board. Further, I’m pretty happy with my work-flow too. I can:
- Modify code.
- Rebuild it with “make”.
- Start the programming step with “make flash”.
- Reset the MINI00 by hitting the reset button.
- Wait a few seconds for the code to copy across and verify.
- Run the new code.
All this takes seconds. So far, the flashing process has not failed me either, which is great since – unlike the MAXI09 board – if it ever did I’d have to pull out the EEPROMs and reprogram them in my trusty old programmer.
This is all raises the question of where I will use C and where I will use assembly in my 68000 exploration. I suspect the answer is I will use both: assembly for the lower-level code, and C for the “applications”.
Running bare C programs is all very well, but what is really needed is a C library, something I will talk about in my next post…