Artisinal Digital Audio

The I2S protocol is pretty straightforward — it uses a bit clock and a word clock to transfer raw (stereo or mono) audio data in digital form. The clock speeds determine the sample rate, and the number of bits sent per word clock determines the sample depth. In theory, it’s possible to mathematically define the waveform of the audio you want to create — sample by sample and bit by bit.

And that’s the upside and the curse. You get perfect control over the audio you produce (in digital form, anyway) — but in return, you have to feed the beast. If you’re using CD audio parameters, that’s two, 16-bit audio samples that need to be generated, 44100 times every second.

Quick, what’s 32,000 * sin(440*curSample/SAMPLES_PER_SECOND)?
Too late — you only had about 11.33 microseconds!

This sort of thing is why, when I asked GPT-4o for example I2S code to generate a 440Hz test signal, it used the sinf() function, instead of the usual sin(). I’m still not 100% sure which helpfully-included-for-me-because-Arduino library is being used here, but running benchmarks, it’s something like 6x faster, for a slight loss in accuracy. I think it’s using 6-term Taylor series expansions, if it’s similar to sinf() code I found online.

Could sine computation be made even faster, if some memory were set aside as a lookup table? I coded up a fastSine() function to look up float32 sine values from a table, based on an integer scaling of a float32 parameter. Swapping this in for sinf() and testing it by having each function do a million-sin summation, it worked — and was some 20% faster! At about 1.25 microseconds each, I can afford to crank the sample rate up to an almost reasonable value!

Making sine table...
Done.
Testing sin()...(3791.946274): 92282.847 ops/s (10836.250 ns/op)
Testing sinf()...(3791.948715): 657273.786 ops/s (1521.436 ns/op)
Testing fastSin()...(3787.528053): 807325.347 ops/s (1238.658 ns/op)

Well, it almost worked. After a while, the waveforms started to look somewhat shaky — and this got worse as time progressed. Resetting the ESP32 cleaned things back up, so something was going wrong with the software. Was all this caused by that ~1% error?!?

Sines with float32 precision error

Thinking I had introduced an error with the fastSine() function, I recompiled with the left channel using sinf() and the right channel using fastSine(), to see when they started to differ. Weirdly, both of them acted similarly — so whatever the problem was, it wasn’t the fastSine() code.

After some diagnosis, the problem turned out to be caused by floating point dilution of precision. Floating point numbers are represented in a mantissa-and-exponent format. Oversimplifying, they’re scientific notation numbers in binary — and there is a limited amount of precision available for the mantissa. Larger numbers can be represented, but at the cost of precision. Double the size of the number, and you halve the precision. Once numbers get larger than 2^24 or so, the representation inaccuracy in float32 can be larger than 1.0. And for angles, we need to do better than this.

Capping the sample number at I2S_SAMPLE_RATE*24 seemed to be a good compromise, and the waveforms seem to have noticeably fewer glitches, now.

Moral: float32s have about 23 bits of precision. Choose your scale carefully!

Posted in Arduino, C, Digital, I2S, Math, Troubleshooting | Tagged , , , , , , | Leave a comment

MobiFlight

Flight Simulation is a fun hobby, and has evolved from interesting-but-not-especially-realistic vector graphic depictions of flying something that vaguely resembled a Cessna 182RG around the Chicagoland area, to impressive, raytraced simulations of just about every type of aircraft out there, often with highly accurate physical simulations backing up the aircraft performance.

MS Flight Simulator 1.0, about to overfly KCGX Meigs,
in what we are assured is a Cessna 182RG. (Image: Wikipedia)
On final approach to the virtually-restored Meigs, in an SR-22 in FS2020.

Of course, realism takes a hit when you’re interacting with these glorious models through a computer screen — and manipulating controls by clicking on them and dragging them with a mouse. If real aircrews had to do this to control their planes, they could make it work — but they would declare an emergency due to how difficult (and therefore risky) it makes things. It would be nice to have the same kind of controls for simulation that the actual aircraft use, but this has generally been a hassle, having to program each control to interact with an API such as SimConnect.

MobiFlight (freeware) makes connecting hardware inputs and outputs much simpler. Connect your controls to I/O pins on supported Arduino models, upload the MobiFlight firmware to the board, download and launch the connector software on the PC, and you’re up and running. Make a change on the physical controls, and it will be reflected in the sim.

A quadrature encoder plus SPST switch, used to set the heading bug.
This, plus plugging in the board via USB, is all the hardware setup needed!

As a first test, I implemented a heading bug selector, and verified that it works in the Cessna 172, Cirrus Vision Jet, and the PMDG 737-700. From here, I’m planning to start to recreate the 737 controls, panel by panel. The eventual goal is to have at least the main control panel in the correct position, so I can look over in VR and find each control where it should be, without having to see the physical device.

Posted in Arduino, Aviation, Flight Simulator, User Interface Design | Tagged , , | Leave a comment

Z80 End-Of-Life

I guess it had to happen sometime — and close to fifty years is one hell of a good run for an 8-bit microprocessor from 1975. This week, Zilog announced that it would be moving the storied Z80 microprocessor family to EOL status, and called for last-time-buy orders through June 24.

I took the opportunity to place a small symbolic order for a single chip for the Mini Museum — a processor that 1975 would have drooled over. The Z84C0020PEG is a power-efficient, DC-clockable, 20MHz CMOS version of the venerable Z80 microprocessor. In car terms, it’s a Z80 with a quad barrel carb, dual chrome exhaust, and a sweet paint job.

A 20MHz CMOS, DC-clockable Z80.
Even this one is five years old, bought “new” from Mouser this month.

The Z80, itself a software-compatible improvement on Intel’s popular 8080 microprocessor, is probably in several billion devices, at this point. It has been used to control everything from robots to the Nintendo Game Boy to microwave ovens, as well as its usual use in 8-bit computers, typically running the CP/M operating system. It anticipated the past few decades of PC upgradability by providing a faster, more capable version of the 8080 that could run on the same software as the 8080, if needed, just like later generations of PCs can (usually) run software from at least the previous generation or two.

While the eZ80 will continue to be produced for the foreseeable future, it’s not quite the same for hobbyists and educators like me who grew up with the Z80 (it powered the Timex-Sinclair 1000, which was my first computer). I understand the business case for the decision, though — we’ve long since switched to 32-bit processors even for our Microcontrollers class, and last year, we replaced the Z80 Microprocessors course content with a Verilog-based approach, where students design their own processors. (I do still use the Z80 for opcode examples.)

Although I wouldn’t use a Z80 in a new design, it’s still bittersweet to see it replaced by more modern tech. But I guess for nostalgia, we’ll always have the good old 16F84A. (That’s so deeply entrenched in engineering curricula that Microchip will be stuck making it until the heat death of the Universe!)

Posted in Components, Current Events, EET325, Electronics, Lore, Nostalgia | Tagged , , , , , , , | Leave a comment

Chicken Breeder Reactor

Minecraft chickens (or maybe they’re ducks?) are strange birds. They’re functionally unisex, for one: all adults lay eggs which can hatch into chicks. Oddly, there doesn’t even need to be another chicken present for this to happen, so presumably they’re all clones.

They drop chicken eggs, which can be used for a few things like making cakes. If your gameplay style allows killing passive mobs (I don’t), they can also be a source of chicken meat and feathers, both of which are also useful.

Oh, yeah. And chickens can more or less bring about the end of a Minecraft world.

I saw the idea online somewhere, years ago, that you can make an automatic chicken breeder machine in vanilla Minecraft. It’s even possible to make a version where no chickens are harmed. The idea is, you corral one or more chickens (any chicken will do; they’re all identical) in a small area with a floor made of Hoppers (a single hopper will do.) These hoppers should all feed into a Dispenser powered by a Redstone oscillator and pointed at a wall not far away. When the chicken lays an egg, it is fed into the Dispenser, which shoots it at the wall, breaking it. Some percentage of these become baby chickens.

A Chicken Breeder Reactor.
This is a Really Bad Idea (but it isn’t in my main world, so why not?)

The neat part is, baby chickens pathfind to any adults nearby. Since you have one or more adults in the corral, the baby will try to find its way there. Give it a one-way stairs (with a drop of two or more blocks at the end), and it will end up alongside its parent.

Now, a few minutes later (they grow up so fast!), you’ll have two adults laying eggs. Soon, you’ll have a lot of chickens. And the more you have, the more eggs per hour they lay. The more eggs per hour that they lay, the more egg-laying chickens you have. The process feeds on itself exponentially, and can eventually render a Minecraft world unplayable. And you don’t even kill any chickens (or anything else) to do it.

It should be fun to see it speed up and go wild.

Posted in Games, Minecraft | Tagged , , , | Leave a comment