Sunday, 22 February 2026

AVR Command Line Hello World in C

I have planned to write this sort of post several times, but for whatever reason it has never worked out. I would take all the screenshots and code saves etc. and by the time I came to write it all up, I would have lost them, or forgot what I took them for.

This time I wrote it up as I was going, so I finally get to complete it.


I have found I don't get along with the many and varied development environments and frameworks and plugins etc. And have ranted about them at nauseam in the past.

A lot of the time, I just want something nice an simple. Hello World, a flashing LED etc. and it's not always easy to get that without hundreds even thousands of lines of irrelevant code being added to your project.

Here I present the minimalistic approach I take when I want, what I consider, the easy approach. (I appreciate that my idea of "easy" and "painless" is very different to others, but this is what works for me)

I am going back to plain text files and command line compiles, and a simple flashing LED.

Development Board

I am using the AVR64DD32 Curiosity Nano board. This is one of many of this type of board, a microcontroller with it's pins connected to two rows of 0.1" spaced pads, and an integrated programmer / debugger at the end, all powered from a USB connector (microUSB on the older boards, USB C on the newer ones).

You can use these in various ways, in the full development environment there are powerful in system debugging and monitoring, but today I am going to use what they call "drag and drop" programming.

When you plug in the Curiosity Nano, it appears as a composite USB device with many features, including USB serial and a USB storage device.

When you open that drive, you see a few files, mostly information about that board and links to the product page which contains links to example projects on github.

The status.txt files is magically generated and shows info about the current settings. It works the other way, if you copy a .hex file into that folder, it programs that into the device. Very neat.

The .hex files are cached in the file views, although it doesn't really exist, you can't read the .hex file back. But you can overwrite it with a new version as you go.

If you unplug the device and plug it back in, any hex files will disappear as they were never really there.

Occasionally I get an error saying "Read Only Filing System", but that resolves by unplugging and plugging back in. (I think it happens when I am too quick with copying a new version on there)

Writing the Code

Now all I need to do is generate a .hex file.

I will start with a nice simple flashing LED, written in C.

The development board has an LED wired to pin PF5.

// Blinking LED
      
#define F_CPU 4000000UL
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    PORTF_DIR = (1<<5);

    while(1)
    {
        PORTF_OUTTGL = (1<<5);
        _delay_ms(500);
    }
}

That is about as minimal as you can get.

Building

I am going to make a bash script to build this (you can do a makefile if you find that easier)

I first create a build directory to hold the output files away from the source.

mkdir build

At the start of the build script, I change into that folder and then clear any temporary files.

cd build
rm *.o -f
rm *.d -f
rm test.* -f

With older chips, support was built into avr-gcc, so the command line would be something like

avr-gcc -Wall -g -Os -mmcu=atmega1284p -o LEDTest.bin LEDtest.c

However, the newer AVR chips are not included, so you need to reference library files. You can install these packs separately, but if you have MPlabX installed, they are already there.

I found the easiest way to get the appropriate build lines is to copy bits from the build output of one of the development environments. Setup a simple project with just a main.c and look at the build output.

The line I took was as follows. This will vary based on system, paths, versions etc. so don't expect this to work on your system.

/usr/bin/avr-gcc -mmcu=avr64dd32 -I "/opt/microchip/mplabx/v6.25/packs/Microchip/AVR-Dx_DFP/2.7.321/include" -B "/opt/microchip/mplabx/v6.25/packs/Microchip/AVR-Dx_DFP/2.7.321/gcc/dev/avr64dd32" -x c -c -D__AVR64DD32__ -funsigned-char -funsigned-bitfields -O1 -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -Wall -MD -MP -MF main.o.d -MT main.o.d -MT main.o -o main.o ../main.c -DXPRJ_default=default

(ah, it used to be so much simpler in the olden days says the old man yelling at clouds...... I should have that gif on speeddial, ah but then the youth of today probably don't know what speeddial was...... insert second copy of old man yells at clouds gif)

There is a lot going on, I tidied it slightly by having all the object files in the current directory and using ../main.c for the single reference to the source file.

The next step is to link it

/usr/bin/avr-gcc  -mmcu=avr64dd32 -B "/opt/microchip/mplabx/v6.25/packs/Microchip/AVR-Dx_DFP/2.7.321/gcc/dev/avr64dd32"  -D__AVR64DD32__  -Wl,-Map="hello.map" -o hello.elf main.o  -DXPRJ_default=default    -Wl,--defsym=__MPLAB_BUILD=1 -Wl,--gc-sections -Wl,--start-group  -Wl,-lm -Wl,--end-group

Again, taken from the build output with the paths tidied up. I am not sure how many of those options are actually necessary, but I have left them all in for the moment. Maybe one day I will look at streamlining that?

That generates an .elf file. The final step is to convert the .elf file into the .hex file we need.

/usr/bin/avr-objcopy -O ihex hello.elf hello.hex

And an optional step generates a listing file.

/usr/bin/avr-objdump -h -S hello.elf > hello.lss

I Finally clean up the build files I don't need to keep, the full script is as follows:

#!/bin/bash
cd build
rm *.o -f 
rm *.d -f 
rm hello.* -f
      
/usr/bin/avr-gcc -mmcu=avr64dd32 -I
"/opt/microchip/mplabx/v6.25/packs/Microchip/AVR-Dx_DFP/2.7.321/include"
-B "/opt/microchip/mplabx/v6.25/packs/Microchip/AVR-Dx_DFP/2.7.321/gcc/dev/avr64dd32" 
-x c -c -D__AVR64DD32__; -funsigned-char -funsigned-bitfields -O1
-ffunction-sections -fdata-sections -fpack-struct -fshort-enums -Wall -MD
-MP -MF main.o.d -MT main.o.d -MT main.o -o main.o ../main.c
-DXPRJ_default=default 

/usr/bin/avr-gcc -mmcu=avr64dd32 -B
"/opt/microchip/mplabx/v6.25/packs/Microchip/AVR-Dx_DFP/2.7.321/gcc/dev/avr64dd32"
-D__AVR64DD32__ -Wl,-Map="hello.map" -o hello.elf main.o
-DXPRJ_default=default -Wl,--defsym=__MPLAB_BUILD=1
-Wl,--gc-sections -Wl,--start-group -Wl,-lm -Wl,--end-group
      
/usr/bin/avr-objcopy -O ihex hello.elf hello.hex 
/usr/bin/avr-objdump -h -S hello.elf > hello.lss 

rm *.o -f 
rm *.d -f

The build.sh file is saved and and made it executable with

chmod +x build.sh

Now I just run ./build.sh and hopefully get no errors.

./build.sh

Then just copy the .hex file to the mounted folder to program it. Your paths will vary unless your username is also Dave. Hello Dave.

cp build/hello.hex /media/dave/CURIOSITY/

All being well, the LED will start flashing away at about 1 Hz.

Checking your work.

You can double check with a logic analyser. The Curiosity Nano comes with a set of pin headers, I don't know what they get up to locked in those red boxes, but they always seem to be stuck together and a little difficult to separate without breaking them.

The holes in the PCB are a little offset, which is a neat feature. That holds the pins in place without soldering. Handy for some quick testing, but I would still solder for anything permanent.

And there we have a square wave, approximately 500ms high, 500ms low. It is slightly longer due to the time taken to toggle the LED and jump back to the start of the loop.

That was a trivial exercise, but a useful one to get started.

Sometimes you just need a sanity check. Can I still get the LED to flash? OK, so the code is actually being updated and is running.

Adding More Functionality

Now it is over to you to add in your code.

If you are just doing "if this input, then do that output" etc., away you go. If you need timers or ADCs or glue logic or events or whatever, you will need to set those up.

Depending on what you are doing, it is sometimes easier to read the datasheet and work out which registers you need to setup for your application. It is often only a few.

But getting started, it can be easier to use things like Atmel Start or MCC Melody (or whatever the next flavour of the month is).

You can use those to create a dummy project and generate a load of code, and then pick and choose the bits you need to add to your simple code.

Atmel Start

I generally prefer Atmel Start as that has a web front end, so you don't need to install or update anything, although unfortunately it has stopped being updated itself. It support things up to the AVR DA and DB series, but has not been updated to include the DD or Ex series for example.

It has a graphical view for linking things together which is helpful for clocks and lookup tables and events.

The code it generates it a good starting point. Commented out code for bits you don't need to change (most registers default to 0 at power on, so no point in overwriting 0 with 0.

Take the bits you need to change, they show all the defines and most of them have comments.

MCC Melody

The current version is MCC Melody in MPlabX. This is a Java based platform which I always struggle with. It seems to be the user interface is somehow fragile, and it locks up too easily. Like it is a picture of a user interface that checks for mouse clicks when it feels like it.

The code these generate does not use defines, just gives you the calculated value. e.g.

Some modules have the option to comment out the = 0 lines, but not all do, so by default you get a load of unnecessary code.

I am also not a fan of they way they remove the leading zeroes. Instead of 0x00 they use 0x0, which just looks wrong to me. The same with 16 bit numbers, 0x02ff is written as 0x2ff.

However you choose to generate it, you can add the bits you need to your code.

Most of these peripherals do their own thing, so once you have them setup the processor is free to do other things, so I often just leave it looping around flashing the LED. It's reassuring to see the heartbeat. I also occasionally change the period of the timer to make sure it has been updated.

A Bit of Assembler?

I was going to stick with C for this, but there is one bit which always needs assembler.

Unlike previous Atmel chips, where the clock settings are programmed fuses, the Microchip chips always start with the internal oscillator enabled. That can be quite useful if you accidentally set the fuses to use an external crystal and you don't have one connected (ask me how I know).

The default clock on these devices is 24MHz, with a divide by 6 to get 4MHz.

There is a line at the start of the main.c file is used to tell the delay function about that so it know how many cycles it will need to waste to get to the required 500ms.

#define F_CPU 4000000UL

In order to change that, you need to write to a register, however it is not as easy as a simple assignment as that register is protected, so you need a wrapper function to unlock it first.

The register is only unlocked for a few cycles, so this need to be written in assembler as C compilers have a habit of moving things around to optimise the code, so the order of instructions can change.

I am surprised this is not a library function, like _delay_ms as used above, but it does not seem to be.

The code generated by both Atmel Start and Microchip MCC has a lot going on to deal with lots of different conditionals and various macros to deal with formatting it as a C callable assembler function.

I usually distil that down to a much simpler function.

#include <avr/io.h>

.global		protected_write_io
.section	.text_protected_write_io, "ax", @progbits
.type		protected_write_io, @function

protected_write_io :
	movw    r30, r24                // Load addr into Z
	out     CCP, r22                // Start CCP handshake
	st      Z, r20                  // Write value to I/O register
	ret                             // Return to caller

.size		protected_write_io, . - protected_write_io

What it is doing is setting the Z register to the address of the register to be written (Z is a 16 bit register made out of two 8 bit registers, r30 and r31).

Once the Z register is preloaded with the address at r24 and r25, the magic unlock code is written to the CCP (Code Configuration Protection) port. That unlocks the protected registers for a few cycles and then the value in r20 is written to the register pointed to be Z.

The registers are sort of like the zero page in a 6502, so in this case ST Z, r20 is a bit like a zero page indirect LDA, r20 STA (r30,X).

That does mean I have a separate protected_io.s file and also a header file with the function definition for the generic function, and a second function wrapper for the more specific version. Which is not ideal, it just means extra files and some extra lines in the build script.

extern void protected_write_io(void *addr, uint8_t magic, uint8_t value);

static inline void ccp_write_io(void *addr, uint8_t value)
{
	protected_write_io(addr, CCP_IOREG_gc, value);
}

Then you just need the actual call in your code to change the value to 16MHz or 24MHz etc as required.

ccp_write_io((void *)&(CLKCTRL.OSCHFCTRLA), CLKCTRL_FRQSEL_24M_gc);

N.B. watch out for CLKCTRL_FRQSEL_24M_gc, it seems to be CLKCTRL_FREQSEL_24M_gc in the code generated by Atmel Start but the current libraries seem to have lost the E in FREQ.

Inline Assembler

I have been meaning to look at rewriting that with inline assembly. This seems to be a good time.

static void ccp_write_io(void *addr, uint8_t value)
{
    uint8_t magic = CCP_IOREG_gc;
    asm volatile (
        "out %0, %1 \n\t"
        "st %a2, %3 \n\t"
        : 
        : "I" (_SFR_IO_ADDR(CCP)), "r" (magic), "z" (addr), "r" (value)
    );
}

I have made it a single function. It took me a while to work out the best way to use the inline assembler syntax. It's a little convoluted, but you write the assembly in a similar style to printf statements, with %0 etc. placeholders for variables.

I would point you at the official documentation, but for some reason all the code on that page is missing

See this version instead which still has the code intact.

The four parameters are passed as %0 through to %3, and those refer to the items that appear after the colon (the values after the string in a printf statement). The first section is for return values, in this case empty. The second is for parameters. There is also a third for any registers you need to tell the compiler that you have used, which I am not using here as I am only using the ones already in the parameter list.

The type is in quotes, "I" is a 6-bit positive integer constant (as is required by the out statement). "r" is a register, so this means that the variable magic is copied into a register, and the name of that register is inserted into the code in place of the %1. The compiler chooses an appropriate register for you.

"z" tells the compiler not to choose one, and forces it to use the Z register (r30 and r31 as previously mention). %2 would have been replaced with the name of the first register, r30, but %a2 gives the Z as required for the st Z.

Looking at the code which is generated (in the .lss file)

ldi r24, 0xD8
ldi r25, 0x24
ldi r30, 0x68
ldi r31, 0x00
out 0x34, r24
st Z, r25

The first four lines are loading the registers and then the last two lines are the unlock and the register write.

That is a few lines shorter than the one generated by the code in the separate file as the parameters are passed in different registers and have to be copied into r30 and r31. Here I can specify the values are placed directly into the registers I need to save the unnecessary step.

You can of course just cut and paste the code above rather than having to understand it. That is what I intend to do next time.

If you upload that now, the LED will flash faster as the clock is now running faster.

In order to get it back to the correct time, you also need to change the #define F_CPU the at the start.

#define F_CPU 24000000UL

Rebuild and re-copy and it should be back to 1Hz.

(this is a slightly earlier version where I hard coded the CCP port before I worked out how to pass that properly, and also went for 16MHz rather than 24MHz)

More Assembler

This next section also goes into the assembler weeds a bit, but I think it is important to see how seemingly insignificant changes to the C code can make big differences to the size and speed of the assembly generated.

Normally when you use the code builders or something like the Arduino, you get nice wrapper functions, e.g.

LED_Toggle();

That isolates you from a lot of the underlying implementation, which in many cases is what you want.

In the early days of Arduino, I remember the IO instructions used to generate a load a code, I think they are a bit more streamlined these days.

The AVR ones usually map down to single instructions, in this case wrapped in a do-while loop to avoid compiler optimisation.

#define LED_Toggle() do { PORTF_OUTTGL = 0x20; } while(0)

You can duplicate things like that if you find it helpful, but all you need is the important bit.

PORTF_OUTTGL = (1<<5);

I have used (1<<5) as it is pin PF5, but the autogenerated code just goes for 0x20. Up to you, you could also use %0010000, or 32 (or even 040 if you are perverse). Whatever works for you, they all compile down to the same thing.

ldi r24, 0x20
sts 0x04A7, r24

Some of the code uses a dot rather than an underscore. They are pretty much interchangeable, but subtly different in the code which is generated.

PORTF.OUTTGL = (1<<5);

This generates a less efficient implementation using a redirector. That can be more efficient if you are running a series of this sort of instruction, but is worse by a few lines for a one off.

ldi r30, 0xA0
ldi r31, 0x04
ldi r24, 0x20
std Z+7, r24

With some registers, you can also access shortcut "virtual ports" which produce even more efficient code.

VPORTF_DIR = (1<<5);

The code is 1 word and 1 cycle shorter. That can make a difference when you are doing low level pin toggling. Especially if you preload registers with the things you want to write, you IO calls can come down to a single cycle out instruction.

ldi r24, 0x20
out 0x14, r24

If that isn't enough, you can also use the "set" and "clear" versions which will only change certain bits. Useful if you want to turn on one output without knowing what the rest are set as, or having to do a ready, modify, write sequence.

So

PORTF_DIR |= (1<<5)

Could be replaced with the more efficient version.

PORTF_DIRSET = (1<<5);

One of the purposes of a simple test build like this is you can try out the different options in isolation, and test them out and also look at the code generated. (well, I say simple, I suppose I should prefix that with "relatively")

How did it go?

In this case, I had been using the MCC generated code as a starting point, but it just wasn't working. Going back to the minimal code and adding a few lines to set the appropriate registers, I was able to see the correct code was being generated and nothing else was interfering with it. That actually turned out to be a silicon bug in the chip I was using, and when I changed to a different one, it started working as designed.

There was a longer, rantier version of that conclusion in the original version of this on Patreon as I had wasted a lot of time on that and another problem, but I will keep things civil on here.


Adverts

My Tindie store contains all sort of kits, test gear and upgrades for the ZX80, ZX81, Jupiter ACE, and Commodore PET.


Patreon

You can support me via Patreon, and get access to advance previews of blog posts, and progress updates on new projects like the Mini PET II and Mini VIC and other behind the scenes updates. This also includes access to my Patreon only Discord server for even more regular updates.

Sunday, 15 February 2026

Diagnosing a PET Video Fault from One Photograph

I get a lot of emails asking for help with computer repairs. I do what I can to help where I can.

This one was quite an interesting challenge, so lets go through the process I used to deduce the fault.

The owner had initially contacted me saying they had a PET 2001, and there was no video output, and they wondered if bad ROM or RAM could cause that and would a PET ROM/RAM fix the issue?

On a later PET like a 12" 40xx or 8xxx machine, that could be the case, as the CRTC chip needs to be programmed during the boot sequence. If it does not get far enough in, the chip does not get configured to generate the appropriate video sync frequencies, and so the monitor does not show anything.

However, in the 2001 and other 9" PETs, the video circuitry is all hard wired to 60Hz and the appropriate style sync signals for the 9" monitors.

The video circuitry is pretty much stand alone. You could remove the CPU and you should still get a display of random characters, and that is actually a useful troubleshooting step. In this case then, a PET ROM/RAM board won't help until the video is working.

The owner had probed around and found the correct 15.625KHz / 64us HSync signal was present, but 60Hz / 16.67ms VSync signal was missing.

There are a series of counters and decoding logic which generates the sync signals and clock through the video RAM.

It sounds like there was a problem with the VSync generation part of that, so I suggested they trace back through that and see where it stopped.

After some probing around, they found the 74177 at D6 was at fault, and replacing that give video sync and a screen full or random characters.

Time for a ROM/RAM board. One was order and fitted.

Normally that would cure the issue, but in this case, it did not. 

I was sent this photo of the screen.

It is sort of working, you can pick out bits of the correct "COMMODORE BASIC" and "31743 BYTES FREE" and "READY" that you would expect to see, but not in the right place, and surrounded by random characters.

The owner was rightly dubious of the video RAM chips. However, substituting any of the system RAM chips made it worse, and swapping the two video RAM chips around made no difference. Given that I was not sure they were at fault, I was not recommending one of my video RAM replacement boards at this stage.

I started thinking through what the problem would could be.

I am not sure about that being the video RAM. Those patterns of 8 character blocks suggest otherwise, particularly if you still see the same patterns with different RAM chips.

I think it is more likely down to the video address generation circuitry.

The RAM chips can develop similar faults, but it is unlikely both chips would fail identically. Each chip is 4 bits, so to correctly display bits of COMMODORE BASIC in the same (albeit wrong) place, both halves of each characters need to have failed identically.

I have straightened up the picture, you can see good runs of "COMMODORE BASIC" and "31743 BYTES FREE" and "READY" and blank spaces. That would suggest the data path (and quite possibly the RAM) is fine.

It is not WHAT it is reading and writing, it is WHERE.

I think at this point it would be good to have a look at how the PET video RAM is used.

The PET video RAM is memory mapped. There is a 1K block at $8000 in RAM which represents 1000 characters, a 40x25 text display (and 24 free non-visible bytes at the end you could theoretically use for your own purposes).

Each of these 1000 characters can be any of 256 fixed characters (there are no user defined characters).

It is actually only 128 characters followed by inverted versions of the same.

There are two different character sets. The PET 2001 BASIC 1.0 has a different arrangement of the characters set, which makes more sense than the later ones to me. Not sure why they changed.

The first set is uppercase letters and graphic characters.

The second set is uppercase and lowercase letters.

The uppercase characters in this version are in the same place for both sets (which seems logical to me).

On BASIC 2 and BASIC 4 machines, the second set has lowercase and uppercase swapped.

Which makes less sense to me, and also leads to incompatibility if you use BASIC 1 software on a BASIC 2 or later machines, or vice versa, you end up with text displayed in the wrong case.

How it works

In an ideal world, the PET would use dual port RAM, with the 6502 memory mapped $8xxx on one side and the video circuitry on the other.

I have used the naming convention of the PET schematics:

  • BA = Buffered Address Bus
  • BD = Buffered Data Bus
  • SD = Shared Data Bus
  • VA = Video Address Bus (not named in the PET schematics)

Dual port 1K RAM chips were not available in the late 1970s when the PET was designed. There were available later on, but have now been discontinued again.

The PET gets around that by having one bank of RAM and switching access between the two sides.

When memory at $8xxx is accessed by the 6502, an address multiplexor is switched to connect the 6502's buffered address bus to the video RAM's shared address bus.

The data buffers connect the video RAM's data bus to the 6502s data bus for read and write operations.

When the 6502 is doing other things, the address mux connects the video RAM shared address bus to the video address bus.

The video circuity only ever reads from the video RAM, and is always connected.

When the 6502 is accessing the video RAM, whatever data it is reading or writing is displayed to the screen, resulting in "snow", more or less depending on how much access there is.

You can get this screen by setting the DIP switches on the ROM/RAM board to 1 and 2 on, 3, 4 and 5 off, that should run a test cycle. You should see a screen full of "G", then a character set, then back to the "G"'s.

The "G" screen should be clean, but there will be snow noise on the character set. If you press and hold the reset button whilst on that screen, the noise will go away until you release the button if you want a better view.

Later model PETs got around this by allowing the video circuitry to read the video RAM during the low part of the clock cycle, when the 6502 was thinking about what it wants to do in the high part of the cycle, and so was not reading or writing from the bus.

Video Circuity Block Diagram

Expanding things a bit, shows more of the implementation.

In video mode, the display starts at the top left of the screen and the video address counters (not shown here) start to count up from $0000 in the video RAM (which corresponds to $8000 in the PET memory map).

The character code of the first character is placed on the shared data bus.

Bit 7 controls if this character is to be inverted. It is latched (twice) and the stored value used to switch the video output between the non-inverted and inverted pixel data from the 74LS165 shift register.

Bits 0-6 forms bits 4-9 of the address fed into the character ROM. Bits 0-2 are from a line counter. Bit 10 is the character set switch.

Each character is 8 pixels wide, and 8 pixels high. The patterns are stored for each of the 8 lines of each character.

The output of the character ROM is fed to the 74LS165 shift register where the character pixels are clocked out at a rate of 8MHz. 8 pixels per microsecond. 

1 character per microsecond. 40 characters per row, 40 microseconds of characters for every 64 microsecond line on the display.

And so it continues until all 8 lines of the first row of characters have been drawn. It then goes onto the second row and the line counter is back at 0.

8 lines per character, 25 rows of characters, 200 lines in total, 12.8ms in the middle of the 16.67ms video frame.

I hope you are writing that all down in your copybook.

Which Schematic?

Schematics wise, there are four to choose from for the 2001 PET:

  • 320008 has 6550 video RAM and 6540-010 character ROM (the original, and most common)
  • 320081 has 6550 video RAM and 2316 character ROM (I have never seen one of these)
  • 320123 has 2114 video RAM and 6540-010 character ROM (I have seen a few of these)
  • 320137 has 2114 video RAM and 2316 character ROM (I have never seen one of these either)

I have never seen any PET 2001s with either board version that has 2316 ROMs (standard 24 pin mask ROMs found in later Commodore machines). I don't even think I have seen photos.

The fact that all the PET 2001s I have seen have 6540 ROMs could mean a few things. Either they never made many of the 2316 versions, or they made loads and they all still work perfectly, so have never needed to be repaired because they don't have those awful 6540 ROMs in them.

Or they were so bad they all failed in the late seventies and few survived. That is less likely given that all later PETs used that same combination of 2114 video RAM and 2316 character ROMs (apart from the 8296 and CBM-II series which used shared main RAM). The 6540 and 6550 were never seen again, which is a good thing. Nice idea, but they didn't work in practice.

The owner has quite sensibly removed and safely stored away all the system ROM and RAM at the front and fitted a PET ROM/RAM.

That should make the system more reliable, allow it to be upgraded to 32K of RAM and BASIC 4.0.

It will also reduce global warming significantly once the 6540s and 6555s are removed.

Note, I said system ROM and RAM. The 6540 character ROM at the back, and the 6550 video RAM a bit further in can stay if they are working. I have replacements if they are not working, or you want something more long term reliable.

In those positions, the chips are permanently enabled, so even when they fail, the usual failure mode is to be enabled all the time, so everything is OK. You can sometimes use 6550 RAM chips as video RAM even if they fail as main RAM because of the enable arrangements for video RAM.

The 6540-010 replacement also includes a switch on the side to select between the BASIC 1 character set (with the logical placement of uppercase letters) and the BASIC 2 / BASIC 4 versions (with the inexplicably worse later version).

The Full Schematic

The schematics for all four boards are hand drawn, and none of the scans of those are particularly clear, so I have redrawn the relevant sections.

Well, almost full. I have left off the video address counters and the clock logic, but that shows most of the relevant sections.

Diagnosing the Fault

I tidied the image up a bit further to make the text easier to read.

There are several important things to get from that photograph:

  • All characters are correctly formed
  • Everything is in 8 character blocks
  • Some of those contain valid text, but in the wrong place
  • Many of them contain random data, indicating they are uninitialised

Looking through that, I can see three possibilities for the video fault in the photograph:

  1. The 6502 is writing the screen correctly to the video RAM, but the video circuitry is looking in the wrong places when generating the screen.
  2. The video circuitry is perfectly representing what is in the video RAM, but the 6502 is writing to the wrong places in memory.
  3. The 6502 is writing to the correct place, and the video RAM is reading from the right place, but somehow the video RAM chips are writing to one place and reading from another

Writing it out like that, I think #3 is not impossible (especially with 6550s), but seems unlikely, so the video RAM is probably not the cause. The characters all being correct also rule out things like the character ROM and the shift register and line counters etc.

One down, two to go

If the video circuitry was not reading from the video RAM correctly, it would all be either the @ symbol ($00) or the chequerboard character ($FF) in those spaces. Random data indicates it is reading RAM correctly, but the RAM has not been initialised. So probably not #1.

When you see parts of the screen still with random characters, that is usually the address mux side of it not letting the 6502 write to those bits.

Therefore I would say #2, the address the 6502 wants to write to is getting messed up, and that would be down to the address mux chips. It seems every time it counts to 8 it jumps somewhere else, which suggests a problem with video address line SA3, which would point to the address mux chips. SA3 is switched by the 74LS157 at position D3 (next to the 6502). That would be my first thought, and if that is bad, I would consider replacing D2 and D4 at the same time as there are also apparently faults with higher address lines as well.

I have seen that sort of thing quite a few times, I found lots of photos of boards where I had replaced one or more 74LS157s.

They seem a common point of failure in machines of these era.

The only one I can find that I have written up was this 8032 board where one of the many faults was down to a mux chip with a similar patters on blocks, runs of 32 characters this time.

Condensed Version

I wrote quite a lot of that in the email response to the owner, with a short preface at the start saying it was most likely the 74LS157s at D2-D4 and they could skip the rest of the email.

They ordered some 74LS157 and replaced D2, D3 and D4 and the PET is now up and running.

Thank you to the owner for the photos of their now working PET 2001.

Including the obligatory shot of invaders, apparently this is a version they modified back in the day to work with the BASIC version 1 this PET originally had. They bought it from someone "upgrading" to an Apple when it was only a year or so old.

One very nice, and very early PET 2001, note the modified domestic cassette recorder and the blue labels.

Oh, OK, that's very early. A serial number with a lot of zeroes and a one at the end?

No reason to doubt it is genuine, looks the same as the one on my PET 2001 (which if anything looks more like a fake as it is much cleaner).

Mine is apparently 8,410 PETs later, with the same slightly bolder number 1 a bit out of place on the left (which Jori on my Patreon found meant 240V).


I enjoy a bit of problem solving like that. I try to help where I can, if I can.

The only time I had to decline recently was when I was in the middle of writing a big long blog post (aren't they all?). I was emailed by someone said they had built a Vicky Twenty (nothing to do with me) and that it didn't work when they plugged in a Final Expansion 3 (also nothing to do with me), although they did note it worked fine with Commodore diagnostics and with a Penultimate Cartridge (oooh, I did that one).

If it had been a Mini VIC and the Penultimate Cartridge, then I would do my best to help.

I wouldn't know where to start with the other things though I am afraid as I don't know either of the products involved, so had to suggest they contract the designers, sellers or community forums.

The only other time I have to decline to help is when people are trying to get a set of 6540 ROM chips to work. I have been doing this an awfully long time, and I have been through it many times. It's just not worth it. It will never last. They will all fail sooner rather than later if you actually use the PET. With the best of intentions, you can go through very expensive used ones from ebay or individual adapters, but you will end up replacing them all, at least once as they just burn out other chips when they fail. A nice idea that didn't work out, and Commodore dropped after the PET and never used again for good reason.

(that is one of the 6540 ROM and 2114 RAM boards I mentioned about 6 foot further up the screen)


Sponsorship

I think I might have arranged some sponsorship for the blog.

Don't worry, it's not VPNs or mattresses or craft kits or mental health or learning or microwave meals or health drinks or free games with heavy in-game purchases or phone networks or earbuds or yet another VPN or anything like that.

It is the PCB manufacturer that I actually use, and have been using for about 8 years I think and that I am happy to recommend.

I kept getting emails from a different PCB company, one I don't use, and it didn't seem right to do accept sponsorship from them, so I contacted my guys and asked if they would sponsor the blog.

I am still sorting out the details, but it seems like it will help, so there is now a logo on the side of the page (under the Patreon and Tindie ones) with my affiliate link, and there will be the occasional mention in a relevant post.

I hope no one will have a problem with that.

Shortly after, I received an email from Patreon suggesting I send my members a Valentine's Day surprise. Not sure if they have read my blog, that's not really on theme.

Anyway, I thought I would have a go and this is what I sent to Patreon a couple of days ago (on the end of a very long Mini PET II development log, so few people will have actually got to the end to see it).

JLCPCBs are Red

JLCPCBs are Blue

Here is my affiliate link

So you can get some too


Tindie

My Tindie store contains all sort of kits, test gear and upgrades for the ZX80, ZX81, Jupiter ACE, and Commodore PET, including the PET ROM/RAM, character ROM and video RAM replacements mentioned above, as well as complete PET replacement boards.


Patreon

You can support me via Patreon, and get access to advance previews of blog posts, and progress updates on new projects like the Mini PET II and Mini VIC and other behind the scenes updates. This also includes access to my Patreon only Discord server for even more regular updates.

Sunday, 8 February 2026

10 Years with my Second Nissan Leaf

This week my 2016 Nissan Leaf had it's MOT, and is still going strong after 10 years.

That is actually my second Leaf. I had a 2013 first generation Leaf before that, but it was on a 3 year lease with no option to keep the car, otherwise I would probably still have that one. It looks to have been MOTed up to 2021, having covered 20,000 miles. It may have been exported, many were. If not, it's quite likely the drive train and/or batteries live on in other uses.

I wrote several posts about that car when new, one year on, three years on etc. It was quite unusual to be daily driving an EV 13 years ago. I have not written about it's replacement so far as it is just normal common sense these days.

I did try a few times to arrange to buy out the lease, but it wasn't an option.

Instead, I replaced that with a brand new 2016 Leaf with a 30 kWh battery instead of the 24 kWh of the 2013 version.

The new one is essentially the same car, with nicer wheels, a better stereo, a better heater, a 6.6 kW charger (all good) and a mechanical left foot parking break instead of the electronic parking break (less good).

It seemed to me a step backwards from a neat little electronically controlled lever to a clunky mechanical foot operated version, and a very lazy change. They removed the lever and just left the hole for no particular reason, just an oddly shaped small storage recess.

At the time I was still doing a 32 mile round trip commute every day, and plugging it in to charge at work and occasionally at home as required.

These days I work from home, so it is just doing lots of short local runs and it is doing great.

The MOT this week failed first time around due to the "Nearside Rear Service brake excessively fluctuating fluctuating through pedal".

The inboard breaks were actually fine, it was the brake discs all around that were corroded and causing that issue. They had been advisories for the last couple of years, but they were the original 10 years old parts, so about time they were done, and after that it passed the retest.

Living close to the sea (about 100 yards) is not the best for corroding brake discs, and the Leaf does not use them enough to keep them clean as most of the time the regenerative braking slows the cars by recharging the battery.

Slamming the brakes on uses the actual breaks (or switching into neutral whilst moving and applying the breaks), which is usually enough the clear off the worst of the rust.

That is the most I have spent on the car over the last ten years. New brakes this year, new tyres a couple of years ago and a new 12V battery a few years before that. A couple of wiper blades and some screen wash. That's about the total parts cost over 10 years ~ £1,000.

Economy

The dash shows the current average economy of 4.1 miles per kWh, that's quite efficient even as far as modern EVs go.

With the 30 kWh battery, that is a theoretical range of 123 miles. In practice it was more like 100-120 when new.

The battery state of health is displayed on the dash by the 12 thin bars around the right hand side of the range gauge on the right.

This year that finally dropped from 12 bars to 11 bars. I think that indicates the battery is down to 80-85% of it's original range. Not bad for 10 years and 10,000 miles.

That means the range these days is 80-100 miles, and that is never an issue with the short trips, just top it up when convenient at home a few times a month or at the supermarket or car parks when convenient.

I have only used the rapid charger a handful of times, I don't live life at a speed where that is necessary.

Rounding lots of things, 10,000 miles and 4 miles per kWh is 2,500 kWh total over 10 years.

Using today's prices of 26.75 pence per kWh, those 10,000 miles would have cost £668.75.

That is worse case, using the cheap rate over night, it drops to 7p per kWh, so £175.

In fact it is probably less than that, as I did not pay to charge at work in those days, so it may be as little as £100 for £10,000 miles.

Diesel Comparison

My good old VW Golf (which I traded in for the first leaf after 11 years) would do about 50 miles per gallon on a good day. The Golf looks to have been MOTed up to 2018, with 170,000 miles on the clock. (I doubt the fuel tank or 1.8TDI engine live on)

Using today's prices, 10,000 miles would be 200 gallons of diesel. 900 litres. Google tells me the supermarket average price this days is about £1.38 per litre (I wouldn't know, it's been 13 years since I had to buy diesel).

900 litres of diesel at £1.38 per litre is £1,242, so about double the worse case of the EV and seven times more than the best case (twelve times more if I take into account the free charging!).

I am sure I would have needed more than 1 set of tires and 1 set of brakes over 10 years with the golf. I think I went through at least 3 clutches.

Conclusion

I don't like the styling of the current range of Leafs, and Nissan seems to have dropped the ball to their competition in terms of the underlying technology as well.

I am very happy with my 2016 model. I also don't fancy all the bings and bongs of the mandatory speed warnings and lane keep assist etc. in modern cars.

Lots of real buttons to easily access all the functions, none of this digging through touch screen menus to turn the demist on.

The car is still going strong 10 years on, and I have no plans to change.

It always looks nice and shiny after the rain, and luckily we have no shortage of rain these days.

It still looks smart and drives like a breeze. Cheap to run in terms of parts and energy costs.

What's not to like?

I know I happen to be a good use case, and not everyone is, so please don't feel the need to tell me why you have to drive 300 miles each day without stopping more than 5 minutes.


Something else celebrating a 10 year anniversary is the VIC20 Penultimate Cartridge, in honour of which there is a limited edition reissue of the original 2016 version. (I wonder if Nissan would consider that?)

My Tindie store also contains all sort of kits, test gear and upgrades for the ZX80, ZX81, Jupiter ACE, and Commodore PET.


Patreon

You can support me via Patreon, and get access to advance previews of blog posts, and progress updates on new projects like the Mini PET II and Mini VIC and other behind the scenes updates. This also includes access to my Patreon only Discord server for even more regular updates.