I tested the N76E003AT20, which has:
- 16-MHz 8051 core
- 18 KB of flash memory
- 1 K of SRAM
- Dedicated 6-channel PWM module
- Two UARTs, an SPI peripheral, and an I2C peripheral
- 12-bit 8-channel ADC @ 500 ksps
- Programmable slew rate
Note that because the device has more than 128 SFRs, Nuvoton implements a paging system. There’s only two pages — the “normal” one, and the alternative page (“page 1”). It should be simple enough, but they “protect” the SFR page with a “Timed Access Protection” (TA) system that essentially “protects” “critical” SFRs by requiring you to write an unlock sequence to the TA register before modifying the “critical” SFRs. I’m using a lot of passive-aggressive quote marks, because I really don’t understand the validity to this at all — yes, I agree that if you change the SFR page without changing it back, everything is going to get screwy. But you can say that about almost everything on a microcontroller — that’s why we test our code thoroughly and watchdog it. Really, the watchdog is the only thing that should be protected by accidental writes, in my opinion.
Just like many other low-cost 8051s, Nuvoton assumes you’ll be using Keil µVision and Keil C51 as your IDE and compiler, respectively.
as their recommended IDE, like almost all low-cost 8051 MCUs (Silicon Labs is the only 8051 vendor that maintains their own free IDE, to my knowledge).
Since I used Keil µVision for the STC review, I thought I’d test out a text-editor-based workflow (where possible), so I tried using Visual Studio Code instead, and I really enjoyed it.
I created separate tasks to build, flash, and debug each project, and then set up key bindings to allow me to quickly execute these tasks. Keil µVision can be called from the command-line to build, flash, and debug projects (among other things), so this worked out really well, actually.
While it couldn’t auto-complete my #include statements, Visual Studio Code figured out where all the header files I was using came from, scanned through them, and provided basic code completion.
The 8051 is an extremely simple microcontroller, and because many of the most commonly-used peripherals are bit-addressable, there’s barely a reason to do peripheral libraries. Having said that, there’s a lot of standard operations that get a little tedious — especially initialization code. Nuvoton included essentially two unrelated systems that help you be a bit more productive when you’re programming the 8051.
Saying that Function_Define.h is “just a set of preprocessor macros” is like saying Citizen Kane is “that movie with the sled” — this is essentially a collection of 500 tasks you find yourself writing all the time, all put in one file.
Want to capture rising-edge events of P1.2 with CAP0 on IC1? No problem; just dump this line of code in your source file:
This is equivalent to writing:
CAPCON1&=0xFC; CAPCON1|=0x01; CAPCON3&=0xF0; CAPCON0|=SET_BIT4; CAPCON2|=SET_BIT4;
Note that Nuvoton doesn’t bother trying to make it a function — which I don’t mind at all. If it looked like a function, you might start confusing it with one, which can be confusing when you’re trying to squeeze a bit more performance out of your interrupt loop.
Want to set up PWM1 on P1.4? No problem:
Which, under the hood, is equivalent to:
BIT_TMP=EA; // save interrupt state EA=0; // disable interrupts TA=0xAA; // TA unlock TA=0x55; SFRS|=0x01; // switch to the paged SFRs PIOCON1|=0x02; // route P1.4 to PWM TA=0xAA; // TA unlock TA=0x55; SFRS&=0xFE; // restore normal SFR page EA=BIT_TMP; // restore interrupts
On all 8051 MCUs I tested, the Toggle routine gets compiled into:
CPL P0.0 (bit-complement) SJMP -2 (signed-jump)
Each 8051 varies based on how long it takes to execute each of these instructions. This Nuvoton part took a surprisingly long time to execute the bitwise-complement operation (4 cycles), while taking 3 cycles to execute the signed-jump.
The N76 struggled through the 16-bit biquad filter test. Due to the spare variables used in the calculation, there’s no way to fit the 64-byte input and output buffers into normal RAM — they must go in XRAM. This concept is confusing for beginners, but it gives you — the developer — some power to optimize code the way you want, instead of treating all RAM as the same.
With 64-byte buffers in XRAM, the MCU pulled out a 36 kSPS result. When I lowered the buffer size to 25 bytes, it fit perfectly in RAM; that improved performance to 41 kSPS. I measured 3.64 mA run current throughout, which means the N76 pulls 293 nJ/sample with data in RAM, and 309.6 nJ/sample when data is in XRAM.
DMX-512 RGB LED Receiver Project
This project was extremely straightforward to implement on this microcontroller — this is the stuff 8051s excel at. I had the code working in about an 45 minutes worth of work. The whole thing is composed of 43 statements, which compiled into 414 bytes of code, 512 bytes of XRAM (for the DMX buffer), and 12 bytes of other variables.
The UART initialization function provided by Nuvoton was the only thing that burned me — twice. I couldn’t resist the temptation of typing InitialUART0_Timer1(250000) and have it figure everything out for me.
They worked for the most part, but I did find a bug:
TH1 = 256 - (1000000/u32Baudrate+1);
TH1 = 256 - (1000000/u32Baudrate);
(at least by my math)
The second problem is that I was getting an unhandled TX interrupt, even though I wasn’t transmitting anything. Why? Because Nuvoton — to try to make using printf() easier for the user — explicitly sets TI (transmit enable) in the initialization routine for the UART. (Keil — in their infinite wisdom — wrote their printf() library to require the user set TI before calling the initial printf() routine. I understand why they do it, but it’s frustrating if you’re unfamiliar with it). Nuvoton also assumes you’re using the 16 MHz internal oscillator without any division, and provides no support for calculating baud rates under other scenarios. I feel like these routines are more for demonstration purposes than actual use — then again, I’m not sure anyone bothers controlling current consumption of these parts, so there’s really no reason to mess with clocking at all. Full speed ahead!
It’s amazing how fast the interrupts are on these 8051 MCUs. These bytes from the 250kbps UART are coming in every 44 µs; there’s only about a 7.3 µs delay , and even running at 8 MHz, the ISR is taking less than 20% of the byte period of the incoming data, which means you could slow down the clock by another four times and still catch all your data.
The bottleneck isn’t actually the processor — it’s the UART. When I clocked it at 4 MHz (i.e., exactly 16 times my baud rate), it really struggled with the mostly-all-zero data that was coming in; there were lots of framing errors, which forced me to bring the clock rate up to 8 MHz.
However, if I were trying to write this project the “right way” — a pin-interrupt-driven wake-up routine, I bet this chip would be able to get down to the 500 µA range, which isn’t terrible (given the problem specifications).