More thoughts on #RP2040 vs #STM32

In a previous post, I shared my thoughts on what I thought are my two favourite mcus: the Raspberry Pi Pico, and the STM32. The STM32F411 seems a particularly popular choice. I have a blackpill, and I got a Nucleo F411 through the post today. So I have been having a little play with that. So, in my mind, it’s a shoot-out between which one will win my heart: the rp2040 or the stm32.

Here’s what I said in June:

  1. ARM leads
  2. STM32CubeIDE is bloated, slow, and the graphical configuration tool is confusing
  3. I favoured libopencm3 for STM, due to its simplicity
  4. I’d probably flip-flop between trying ideas on the stm32 and the rp2040

Having gained a bit more experience, my preferences have shifted.

I now “like” the stm32 MX. Sure it generates a lot of crud, but I’ve begun to embrace the crud and just ignore it. The MX will warn you of things like conflicts, and I can select the peripheral I’m interested in and just accept what it gives me, absent some juggling.

I’m taking a liking the HAL, too. And, by extension, the CubeIDE too, despite that I still haven’t changed my mind that Java is a pox. libopencm3 isn’t especially well-documented, and configuration is more painful than the HAL.

HAL is more powerful than RP2040 API. This is where STM32 is really starting to curry favour with me. The RP2040 takes a more blocking-oriented approach. This keeps things simple, but it is a drawback when you want to push the envelope.

Using the HAL, I have been able to set up PWM timing and SPI using DMA. On fact, I’ve gotten 3 SPI devices on the go: a SPI master as a “loopback” controller, a SPI slave, which I want to learn how to use for an audio project, and another SPI master to control a seven-segment display.

Although I had some fiddle with pin conflicts and with the way the Chip Select works, I thought that I got to grips with things relatively quickly. The MX tool was a great help here.

So, as you can tell, I think STM32 is in the lead at this stage. I had learned a lot about the RP2040 since it came out, but trying out the Cube helped a lot.

I’ve gained a little more knowledge on mbed, and gotten more into the groove of its workflow. It’s good for simple projects, but it does seem to be lacking when you want to use DMA, Interrupts, etc. and you care about performance. I’ve seen some posts where people have incorporated HAL into mbed, but in my view, I don’t think that’s a great win. Might just as well just use the HAL.

As an aside, I’ve recently downloaded software that implements a Zettelkasten (literally “slip box”). It’s a note-taking system, invented before the days of wikis. I’m hoping that it will help me remember all the stuff I need to develop software. The Zettelkasten was developed by Niklas Luhmann, a German sociologist. The Zettelkasten comprises a series of index-cards and a system of indexing. The cards, which contain “atomic” pieces of information, are then related to other cards in a web weaved by the indexing system. This video will give you a general overview.

I may post some blog posts on information I am building up. I have found a lot of blog posts are too long or irrelevant for my purposes. I think a lot of people could benefit from smaller, more focussed, posts.

That’s for the future, mind.

Posted in Uncategorized | Leave a comment

Some sawtooth pwm wave mathematics (#stm32f411, dma)

Sawtooth waves are a useful way of experimenting with pwm signals. I prefer them over sine waves for testing purposes, as the sharpness of their maximum and minimums make it easier to spot signal distortions when viewing them on an oscilloscope.

The basic equation for the frequency output, f, is governed by the equation that you’ll find in most datasheets:

f = C / (PSC' ARR') ... [1]

where C is the clock frequency of the PWM peripheral. PSC’ = PSC+1, where PSC is the prescaler, and ARR’ = ARR +1, where ARR is the reload value.

To generate a duty cycle, DC, you set the the mcu’s CC (Compare Counter) to some percentage of ARR’. The relationship is straightforward:

DC = CC / ARR' % ... [2]

Now suppose we don’t want a static duty cycle, but want to generate a varying wave, like a sine or sawtooth wave. What we need to do is vary the duty cycle over time.

One could set the value dynamically using a timer, but here I want to concentrate on using DMA, as I think it is the most reliable, albeit not the easiest or most flexible.

So one approach is to create an array V having L values, which constitutes one period of the wave being generated. The frequency of the wave is then a slightly modified version of [1]:

f = C / (PSC' ARR' L) ... [3]

So the frequency of the output goes down as the size of the array goes up. Intuitvely this should make sense. When L =1, we just have the regular PWM wave as before. If we had L=2, with V[0] = 0, V[1] = ARR, then we would have, in effect, stretched out the wave to be double its period.

We may have several conflicting goals as to what we are trying to achieve. We may want to be able to generate different frequencies, or with different resolutions.

Note an important relationship, however. We can increase the resolution of our signal by increasing ARR’. If we want 8-bit resolution, then we would choose ARR=255 (or equivalently ARR’ = 256).

Be aware of this, though: L also determines the granularity of the signal, so you can’t just ramp up ARR and expect a more finely-resolved signal. Maximum resolution is obtained when ARR’ = L.

Let’s put some numbers to these equations. I have set up an STM32F411 to generate a clock pulse for the peripheral bus of 100MHz. So C = 100e6. If I want to maximise the resolution of my wave, I set PSC’ = 1. Suppose I want to generate a sawtooth wave of frequency f = 500Hz. I set ARR’ = L in [3], and rearrange to obtain:

L^2 = C / (f PSC') = 100e6 / 500 = 200k ... [4]

Taking square roots, we see:

L = sqrt(200,000) = 447.2 = 447 ... [5]

I round down L because both ARR and L must be integer values.

To generate an ascending sawtooth wave, I set the value vector as follows:

V[i] = i for i = 0..(L-1) ... [6]

Reverse sawtooth, sines and triangular waves will be a little trickier.

Does it work? Sure! You can test out the code here, which uses the HAL on an STM32F411. I’m not going to go into details on the Cube setup. The signal is produced on PB6 using timer 4 channel 1, DMA 1 stream 0. You can see the individual setting if you download the code. TIM4 Channel 1 has its Channel1 set to PWM Generation CH1. The DMA request is set to circular mode, because of course we want to loop around continually. We use a memory to peripheral configuration where we increment the memory so that we loop through our vector. I use word data size, although I suspect we could set it up using half-words (i.e. 16 bits). Code-wise, I have declared the array as follows:

#define LEN 447
 __attribute__ ((aligned (32)))
 uint32_t  pwmData[LEN];

initialise it:

for(uint32_t i = 0; i<LEN; i++) pwmData[i] = i;

then I activate the DMA transfer:

HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_1, (uint32_t *)pwmData,  LEN);

You can create a little circuit to smooth out the PWM so that you can view it on an osciiloscope:

It’s a low-pass filter consisting of a 330 ohm resistor and an 0.1uF capacitor. This will reduce the output power, although you can still use a mini-speaker. You may prefer to send it to some computer speakers so as to boost the output.

Alternatively, just dispense with the whole circuit altogether and connect a speaker to pin PB6. The output is good, and you won’t notice any difference. So far, I have found 8-bit PWM signal generation to be adequate for my purposes. I have been toying with DACs such as the MCP4921. If you can get away with PWM, then that would be the simplest option.

I hope you found this post useful.

Posted in Uncategorized | Leave a comment

K.I.S.S. #stm32f4 HAL PWM fixed frequency and duty cycle

Today I’m going to look at producing a PWM pulse of fixed frequency and duty cycle.

You will need to choose a PWM-compatable pin. I chose PA11. The pins will have a timer and channel associated with them, according to the following diagram:

pinout.jpg

Look at the purple boxes. So, for example, PC9 corresponds with timer 1, channel 4. PA11 corresponds to timer 1, channel 4.

You also need to associate the timer with the pin. For that you need the “alternate function”, which you can get from the following table (anything other than the F411 will likely be different):

AF0 system
AF1 TIM1/TIM2
AF2 TIM3..5
AF3 TIM8..11
AF4 I2C1..3
AF5 SPI1/SPI2
AF6 SPI3
AF7 USART1..3
AF8 USART4..6
AF9 CAN1..2, TIM12..14
AF10 OTG_FS, OTG_HS
AF11 ETH
AF12 FSMC, SDIO, OTH_HS
AF13 DCMI
AF14
AF15 EVENTOUT

Choose a frequency. I chose 500Hz. I set a prescaler for timer 1 via the line:

htim1.Init.Prescaler = HAL_RCC_GetSysClockFreq() / 1000000 - 1;

This has the effect of scaling the prescaler so that timer 1 is at 1MHz. We actually want a PWM frequency of 500Hz. This is calculated according to the formula

f_pwm = f_clk / (arr+1)/(psc+1)

where f_clk is the clock that drives the timer module, ARR is the prescaler, and ARR is the Auto Reload Register value. Substitute in the value of the f_clk and the prescaler in the formula above, and rearrange is to to obtain:

arr = 1,000,000/f_pwm -1

it is a period, equivalent to the piece of code that reads:

htim1.Init.Period = 1000000 / freq - 1;

The pulse length, i.e. the value at which the PWM is turned off, will be based on the duty cycle:

sConfigOC.Pulse = 1000000 / freq * duty_cycle_pc/100 - 1;

There’s a bunch of other stuff to do like setting up the pin, enabling the relevant RCCs, and the timer. Putting it altogether, the complete code looks like this:

#include "stm32f4xx_hal.h"
TIM_HandleTypeDef htim1;

static void MX_TIM1_Init(uint32_t freq, uint32_t duty_cycle_pc)
{
	htim1.Instance = TIM1;
	htim1.Init.Prescaler = HAL_RCC_GetSysClockFreq() / 1000000 - 1;
	htim1.Init.Period = 1000000 / freq - 1;
	HAL_TIM_Base_Init(&htim1);
	HAL_TIM_PWM_Init(&htim1);

	TIM_OC_InitTypeDef sConfigOC = {0};
	sConfigOC.OCMode = TIM_OCMODE_PWM1;
	sConfigOC.Pulse = 1000000 / freq * duty_cycle_pc/100 - 1;
	HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4);

	/**TIM1 GPIO Configuration: PA11     ------> TIM1_CH4 */
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	GPIO_InitStruct.Pin = GPIO_PIN_11;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

int main(void)
{
	HAL_Init();
	__HAL_RCC_GPIOA_CLK_ENABLE();
	__HAL_RCC_TIM1_CLK_ENABLE();
	HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);

	MX_TIM1_Init(500, 25);
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
	while (1)
		;
}

That’s 36 lines of code, so not too bad. To reiterate, this will produce a 500Hz PWM signal on pin PA11 with a duty cycle of 25%.

Update 1: If you’re using CubeMX, the configuration steps are (IIRC):

Pinout View: PA11 : TIM1_CH4

Timers: TIM 1:

  • Timers: TIM1:
    • Mode:
      • Clock source: Internal clock
      • Channel 4: PWM Generation CH4
    • Parameter settings:
      • Counter settings:
        • Prescaler: HAL_RCC_GetSysClockFreq()/1000000 -1 (No check)
        • Counter Period: 1000000/500-1
        • auto-reload preload: enable
      • PWM Generation Channel 4:
        • Mode: PWM mode 1
        • Pulse: 1000000/500/4 -1
    • NVIC Settings: TIM1 capture compare interrupt: Enabled

Posted in Uncategorized | Leave a comment

K.I.S.S. #stm32 HAL blinky LED.

The HAL seems to be the most popular library when developing for the STM32. I hate the CubeIDE with a vengeance, though. It’s slow and bloated. It’s based on Java, so no wonder.

I have set up VSCode on Linux, and added PlatformIO. I am not going to go through the set-up procedure here. There’s megabytes of stuff to download, naturally, which I’m not particularly happy with, but there we go.

Create a project for your MCU using PlatformIO. That is easy enough. I am using a blackpill, with the inbuilt LED on PC13. If you are using a discovery board or some-such, the pin is likely different.

I used CubeMX to generate the configuration. I just used it to give me clues as to what I needed to do. Once you have the general idea of how to set things up, you can dispense with MX and roll your own.

So here it is, a complete blinky sketch for the black pill:

#include "stm32f4xx_hal.h"

void SysTick_Handler(void)
{
	HAL_IncTick();
}

void setup_gpio_pc13_as_output(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};	
	__HAL_RCC_GPIOC_CLK_ENABLE(); /* GPIO Ports Clock Enable */
	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); 	/*Configure GPIO pin Output Level */

	/*Configure GPIO pin : PC13 */
	GPIO_InitStruct.Pin = GPIO_PIN_13;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}

int main()
{
	HAL_Init();	      // required
	setup_gpio_pc13_as_output();

	while (1)
	{
		HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
		HAL_Delay(100*1);
	}
}

As you can see, it’s very simple. You don’t need any of the other goofy stuff that MX seems to generate. No extra boilerplate or files are required, at least if you are using VSCode as I described above. It should work as-is.

You do need to call HAL_Init(), which is an inbuilt function.

If you are going to use HAL_delay, you need to setup Systick_Handler(). This function is called as an ISR, but you don’t need any extra setup. I did see that the handler existed as a weak function in the library somewhere. I therefore suspect that you can dispense with the function somehow, but that did not seem to work for me.

Systick_Handler() is called once every millisecond, and as you can see, in this case it just increment a tick, which is 32-bits wide.

I am in two minds about using the handler. If HAL_Delay() was eliminated, then I would not need it. I like to avoid setting up interrupts as much as possible. This avoids potential bottlenecks that I may not have accounted for, and keeps the design as flexible as possible.

Contrariwise, you can at least set up a tick, which is likely to be useful in many places. I also see it as a kind of pre-emptive scheduler, for which I can do things like button debouncing. For a debouncer, I don’t need to check button state every tick. One can also stagger other routines that you want to service. This will ensure that you’re not locking up the CPU for longer than you wanted.

An alternative is to use polling, of course. It’s the simplest approach, but here again, you may want to check on timing.

I’m also tempted to see if I can add my own abstractions on top of the HAL. One neat trick I saw in the rt-thread project is to define pins like so:

#define PC13 GPIOC, GPIO_PIN_13

Maybe that’s not everyone’s cup of tea, but it would enable to write a wrapper function which you can call like:

gpio_toggle(PC13);

That is more convenient.

You could also use it for creating a function like gpio_out(), which does the same thing as setup_gpio_pc13_as_output(); but is more generalised. I am also tempted enable all the RCC clocks in a oner in a separate initialisation function. You probably do not want to do that if you are looking to minimise power, but if that’s not your priority, you can reduce your code size. It’s a design decision, but I’m in favour of trying to avoid gobs and gobs of boilerplate.

This brings me onto my seeming obsession with performance. For many projects it may not be necessary. I like to experiment with audio. It soon becomes apparent when you do this that you need to pay attention to performance. You need to be efficient and give attention to how things are timed, what things can lock you up, and so on.

It is in this respect that I think something like mbed falls a bit short of the mark. I couldn’t see a way of writing an interrupt for when a PWM carry flag is set. The threading is great, and it has nice, interrupt and thread-safe classes for things like circular buffers. But the mcu seemed to baulk when I had a timer that was fired every 8us. Even without that, there seemed to be some problems when I had a couple of threads that seemed incompatible somehow.

This seems to be a general problem with RTOS’s. Critics often say that they’re great, but now how do we get them to work?

Mbed did seem good for some rapid prototyping where there isn’t a lot of time-critical stuff, but there’s only so far you can push it. Mbed is perhaps a place to look if you want to expand your horizons beyond MicroPython, or where MicroPython doesn’t quite cut it.

There is some nice stuff in mbed, so maybe it can work for you. I’m warming to it, but I don’t think it’s the hammer for every nail. Sometimes you just need to get your hands dirty and understand the hardware in greater depth.

Incidentally, I took the briefest of skims through some of the files in the rt-thread project. They seem to make extensive use of interrupts and DMA for things like SPI. So I imagine that one could make very responsive apps without having to sweat much of these issues by oneself.

As to Zephyr, I think I got an example to compile once, but the magic faded, and I ran out of chickens to sacrifice. OK, not chickens, patience. I’ve got a few toolkits that I investigate by rotation, when the mood takes me. There’s generally a lot of infrastructure surrounding them. So there’s a steep learning curve just to blink an LED, with the prospect of more hardship to follow.

So one is always trying to weigh up if the game is worth the candle without knowing what the rules of the game exactly are.

So I’m trying to learn more about the HAL, because it’s what the majority seem to be using, and I’m hoping that it provides a gain over libopencm3. I hope to add my own twist to more suit my sense of aesthetics. Which is a polite way of saying “what is all this crap doing, anyway?” I know I’m not the only one.

So, that’s me done for today. I hope you found the post useful, and are having lots of fun with your own microcontrollers.

Posted in Uncategorized | Leave a comment

Kicking the tyres of ZephyrOS

Brief note: I decided to give ZephyrOS a try on my STM32 blackpill. I’m pleased to report that it works. ZephyrOS was most definitely not a breeze to install. Pun intended.

I think installation is something they’re working on. Yes, they should do. It’s a bit wasteful to download stuff for boards that I’m not interested in.

A minor annoyance is that I had to upgrade my Cmake. I’m on Debian Stable. I don’t know if it was strictly necessary, but I did it to avoid investigating the matter further.

The download is hefty.

Then you need to download their “SDK”. That’s 1G in size. Crikey, there are Linux distros that are smaller than this. What the SDK contains are compiler binaries for supported architectures: arm, riscv, xtensa, etc.. I really don’t like that approach. Everybody’s Linux distro already has cross-compilers, they should just use those.

So I was a bit peeved about the sheer scale of stuff I had to install. I’d hope that they’d be able to do better.

So anyway, I got all this infrastructural stuff sorted, and was able to compile a blink sketch. That’s sledgehammer to crack a nut, of course. I’m hoping that I can now dive in and see what goodies they’ve got to offer. They also support the ESP32, I see, so that should be an interesting thing for me to try out, too.

So although my post seems critical, I’m just pointing out the annoyances I faced. I’m actually hoping that now I’ve managed to get the thing installed and build a basic example, that they’ll be all sorts of things to keep me amused.

And one extra gripe, whilst I’m here. I do wish people wouldn’t keep hyping up “IoT” and “cloud” so much. The first words on their website are “The Zephyr ProjectTM strives to deliver the best-in-class RTOS for connected resource-constrained devices, built to be secure and safe.”

“Best-in-class” is an entirely subjective term. Everyone has their own ideas. there is no “best”. The fact that it’s built to be secure and safe is of little interest to me. If you want to change my devices, then you’ll need to break into my house. In which case reprogramming my microcontroller is the least of my worries.

Also on the front page is the fact that the emphasise that Zephyr is “secure” and “connected”. They’re referring to bluetooth, wifi, and a TCP stack. A lot of the microcontrollers they support don’t even have bluetooth or wifi, so I find it irritating that they emphasise it so much. (Having said that, I am interested in LoWPAN, which is on my TODO list).

I mention all this because I hate buzzword-driven sales pitches. What I really see it as is a RTOS for microcontrollers with a lot of device support and a consistent HAL. There doesn’t appear to be any official support for the Raspberry Pi Pico, but I’m guessing that’s just a question of time.

What would be really cool is if it supported the Raspberry Pi 0. I could really go for that. The Pi 0 seems to attract little attention amongst projects, which is a great shame.

Zephyr also targets x86, which is actually quite interesting. Sounds like one could make a nice little operating system for commodity computers, without all the Windows/Linux guff.

Posted in Uncategorized | Leave a comment

“CrunkyOS”: sound from SD card now works on #RaspberryPi0

I’m putting together a very crude unikernel-type system for the Raspberry Pi 0. I’m really pleased with my progress: I can read a song stored on an SD card into memory and play it via PWM.

Here’s the code for the project:

#include <assert.h>
#include <stdio.h>
#include <bcm2835.h>
#include <../pico/fat32.h>

// PWM output on RPi Plug P1 pin 12 (which is GPIO pin 18)
// in alt fun 5.
// Note that this is the _only_ PWM pin available on the RPi IO headers
#define PIN RPI_GPIO_P1_12
// and it is controlled by PWM channel 0
#define PWM_CHANNEL 0
// This controls the max range of the PWM signal
#define RANGE 256


uint8_t* song;
uint32_t len;

void kernel_main(void)
{
	puts("\nPlaying song with PWM audio");

	fat32_init();
	file32_t file;
	char cooked_name[12];
	canfile(cooked_name, "song.raw");
	file32_init(&file, cooked_name);
	printf("Song %s\n", file32_found(&file) ? "found" : "unfound");
	len = file32_size(&file);
	song = malloc(len);
	assert(song);



	bcm2835_init();

	// Set the output pin to Alt Fun 5, to allow PWM channel 0 to be output there
	bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_ALT5);

	// PWM base frequency is 19.2MHz
	// freq = 19'200'000/DIVIDER/RANGE = 19'200'000/4/256 = 18750Hz
	bcm2835_pwm_set_clock(BCM2835_PWM_CLOCK_DIVIDER_16);
	bcm2835_pwm_set_clock(BCM2835_PWM_CLOCK_DIVIDER_4);
	bcm2835_pwm_set_mode(PWM_CHANNEL, 1, 1);
	bcm2835_pwm_set_range(PWM_CHANNEL, RANGE);


	uint32_t offset = 0;
	while(file32_read(&file, song + offset))
		offset += 512;
	puts("Song finished reading.");

	// Vary the PWM m/s ratio between 1/RANGE and (RANGE-1)/RANGE
	int idx = 0;
	//int data = 1;
	while (1)
	{
		unsigned char data = song[idx++];
		if(idx == len) idx = 0;

		bcm2835_pwm_set_data(PWM_CHANNEL, data);
		bcm2835_delayMicroseconds(1000000/16000);
	}

	bcm2835_close();
}

The song plays a little slowly because the delay is not properly calibrated. Ideally I’d use interrupts anyway, which is also available for the unikernel.

There’s a lot of cobbled-together code that makes up the kernel. The BCM2835 was adapted from the Linux version. The SD card library is by “bzt”. It seems rather slower than I would expect (by at least an order of magnitude, I would say), which is unfortunate. It prints debug messages as it loads in the file. For some bizarre reason I have to leave the debugging in, or the module won’t work. I’ve had a look at the debugging routines, and I cannot see anything at all that suggests that they cannot be dispensed with.

I have written the FAT32 driver myself. It is read-only, with some limitations on the directory size. It uses 8.3 file format only.

Crunky, so far, uses the simplest thing that could possibly work. I kid you not. malloc() just takes a hunk of heap and moves the bottom of the heap up. There is no free().

Starting from a few days ago, I decided to abandon the idea of making Crunky programmable in C++. I had it working before – except for exceptions – but it required some infrastructure that was just too fiddly to maintain when I returned to the project after some absence.

I also abandoned the idea of using a third-party C library like nano or newlib. Integration was a faff the last time I tried it. When I upgraded my Debian system I found that newlib no longer compiled in with it properly. Rather than spend time trying to reintegrate it, I decided just to throw the baby out with the bathwater. I don’t need the whole C library. Many functions aren’t that difficult to write, anyway.

I’ve used “tinyprint” to implement things like printf. It doesn’t handle the printing of floats, but I can live with that for now. Maybe I’ll have a go at implementing it if I feel the urge. There is also other code that I’ve integrated from other sites, so it’s not as though I have insisted on writing everything from scratch.

The framebuffer works, which is quite nice. You can print and draw to it. I actually communicate via the serial port, chiefly because I can’t get a keyboard working. USB is a whole complex ball of wax, and I’ve always ended up abandoning any attempts at getting it working. I’m thinking of cheating anyway. Maybe I could set up an ESP32 to log into the Pi 0 over Wifi, or maybe try getting tinyusb working on a Pico and using it to interface with the serial port on the Pi 0.

Talking of the Pico: it has been frustrating for me trying to get real-time audio on it. I had written an SD card reader using SPI. The sound works, but I noticed some fluttering on the audio. I am pretty sure that the SPI is causing some problems for the audio interrupt. I know this because I wrote a project to transfer files from an SD card to the Pico’s flash. When I play audio from the flash, the fluttering goes away.

Problems, problems, problems.

I was thinking about evaluating some real-time systems for the Pi 0. The ones that are on my radar are: ChibiOS, RT-Thread, RTEMS, and maybe ZephyrOS or RIOT. RT-Thread recently announced that they were putting out a real-time system, which seemed like Linux but real-time. I noticed that RT-Thread also included support for the Raspberry Pi 2. This is pretty exciting, because if it supports the Pi 2, it should be fairly easy to support the Pi 0. Theoretically. I had reported on a previous occasion that RT-Thread didn’t seem ready for primetime. “It compiles for me” seems to be the level of quality assurance that goes on in that project.

I do face dipping into OS projects with some trepidation. There’s usually a steep learning curve, all sorts of pitfalls and traps in setting the thing up, and there are far from any guarantees that the game is worth the candle.

But anyhoo, I’m excited about getting my song player working on the Pi. Onwards and upwards!

Posted in Uncategorized | Leave a comment

#rp2040: Using DMA to set SPI DAC (MCP4921)

Good news: I managed to get DMA working on an MCP4921 DAC, which uses SPI. It’s my first time trying to get DMA working on the Raspberry Pi Pico, so I was a little bit daunted.

Here the code which generates a 500Hz sawtooth wave:

#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/gpio.h"
#include <math.h>

#include "pi.h"


using u8 = uint8_t;
using u16 = uint16_t;


#define LED 25


double framerate = 44100, saw_freq = 500;
uint64_t period = 1'000'000/framerate;


void mcp4921_init(void)
{
#define	PIN_SCK		2
#define	PIN_MOSI	3
#define PIN_MISO 	4
#define	PIN_CS 		5
	int spi_speed = 18'000'000;
	spi_init(spi0, spi_speed);
	spi_set_format(spi0, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
	gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
	gpio_set_function(PIN_CS,   GPIO_FUNC_SPI);
	gpio_set_function(PIN_SCK,  GPIO_FUNC_SPI);
	gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
}

void mcp4921_put(uint16_t vol)
{
	if(vol>4095) vol = 4095;
	vol |= (0b11 <<12);
	spi_write16_blocking(spi0, &vol, 1);
}

volatile float y = 0, dy;

static void alarm_0_irq() 
{
	pi_alarm_rearm(0, period);
	mcp4921_put(y);
	y += dy;
	if(y>4095) y=0;
	pi_gpio_toggle(LED);
}

int main() 
{
	stdio_init_all();
	pi_gpio_init(LED, OUTPUT);
	dy = 4095 * saw_freq / framerate;
	mcp4921_init();
	pi_alarm_init(0, alarm_0_irq, period);

	while(1);
	return 0;
}

You can get the full project on my repo.

I hate the idea of having bottlenecks in my code, especially in light of the fact that I’m interested in audio work, probably with some DSP thrown in. DMA is a way of offloading the burden from the CPU.

I have a number of ways that I’d like to progress. One would be to see if I could convert my OLED driver to exploit DMA too. Hopefully that should create interest in my own OLED project compared to the other offerings. It ought to be great for people wanting to write games. I saw a Doom port for the Pico using OLED. I could see that my library might be used to make the game more sophisticated.

It would be nice if I could speed up my SD card library, too. Using DMA might be difficult/impossible for that, though, due to the way that the RP2040’s SPI works. Maybe using interrupts is a better bet. Or perhaps PIO. But I’m still at a loss when it comes to PIO.

The official pico-extras has audio work for I2S and PWM, so that’s something that I could possible investigate. I should also check out its SD Card stuff, which uses DMA and PIO. So it’s likely to be a way better implementation than mind. It does say that it’s a hacked prototype, mind.

I’ll add another plug for my SD card library; available here. It still has significant limitations, like being read-only.

I’m thinking that my next move would be to try to take a look at the Amiga MOD audio file format. This would advance towards making a drum machine. A range of different drums would be pretty cool. So I could advance, in baby steps, towards a drum sequencer. A drum sequencer typically costs over 100 quid, so imagine putting together a little piece of kit out of a Pico which you could make for considerably less than 20 quid.

Posted in Uncategorized | Leave a comment

#RP2040 SD card with limited FAT32 support

I’ve updated my sdcard project to include some FAT32 reading. Make sure a FAT32 partition is on the first partition of the card. So far, the driver is read-only. Drop a file called song.raw into the partition and it will play the song using PWM on pin GPIO 19. The song should be in a raw format with a sample rate of 16000Hz, one byte per sample.

The implementation is small. fat32.cc is 260 lines long, which is likely to be smaller than most other implementations. sdcard.cc is 576 lines long. It implements the SD card block device over SPI. Another compact implementation.

Posted in Uncategorized | Leave a comment

Upgraded to Debian Bullseye

I decided to jump the gun and update my Debian Stable from Buster to Bullseye. The upgrade was smooth enough, and I’m pleased to report no problems. The download was large, though, over 4G. I shouldn’t have been surprised, I guess, seeings as it’s basically a completely new system.

The login screen looks a lot nicer.

I decided to remove LibreOffice. That frees up 400M of disk space. Mental. Over the coming weeks I’ll undoubtedly see what else can be removed from my system.

I’m keen to check out the latest version of C++, as it will have a lot more C++20 goodness.

The mail reader Evolution now has a nice touch: it aerosnaps!

Firefox seems to be soaking up the memory: over 20% of it. It seems to cause my fans to do a bit of spinning, too. Could try harder. I took an opportunity to play with FreeDOS the other day. One thing that really sticks out is just how fast it is. On my old machine it took over a minute to load up and old version of Kubuntu. I can’t remember how long FreeDOS took, but it was effectively instantaneous. There might have been a boot screen with a timeout of a few seconds.

It reminds me of when I was working as an accountant back in the mid-80’s for a small firm. They installed a computer network running 286s. I used an accounts package called IRIS. What took the time is marking up the ledger with accounts codes manually. I then entered the values onto the accounting system. Data entry was pretty quick. There probably wouldn’t have been much time saved by entering values directly into the computer as opposed to coding them beforehand. A set of accounts was produced. It took, what, about a second or something. Makes you think, right. We could effectively do everything then that we could now, and could probably get it done quicker.

Upgrading my system was a bit of a punt. Although I have backups of my important data, there was a whole lot of data that I had downloaded from the internet. Rather than assiduously backing all this stuff up, I decided to take a flier and just upgrade my system. If the system b0rked then, well, I’d have had the job of reconstructing all the tools that I used. That’s not so bad, as I would have had the latest ones anyway. So it was a calculated risk of going through the chore of backing things up as opposed to just sucking it and seeing. Fortunately it all went without a hitch, so in the end, I actually saved myself the hassle of doing a fresh install.

Posted in Uncategorized | Leave a comment

OLED #rp2040 C++ library gets a blocking boost

I’m continuing to refine my OLED I2C library. I sped up the library 10X by the simple expedient of increasing the baudrate of the I2C transfer. Transferring a whole 128×64 bitmap to OLED still took about 12ms.

I wanted more speed! So introduced a function called ssd1306_display_cell(), which transfers a “cell” at a time: basically an 8×8 grid of pixels. A cell transfer takes 117us, which is a vast improvement over the 100ms that my original version was using.

Now, admittedly, the original transferred the whole screen in a oner, whereas the new function would need several rounds in the loop for a whole screen. But it has the advantage that you can round-robin other tasks in your main polling loop without hideous blocking by the screen function.

I hope that someone finds it of use. I certainly plan on using it for my synth project for the Pico. Perhaps things can be sped up even more by using DMA, but I’m somewhat fearful of opening that particular can of worms. I’ve managed to get memory-to-memory DMA working on the Pico, so I’m hoping that communicating with a peripheral won’t be too much harder.

Posted in Uncategorized | Leave a comment