❌ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdayJeremy Grosser

Your Amazon Affiliate Account has been closed

29 July 2023 at 00:00

Back in 2015, I wanted to build a NAS for backups at home. I found the shopping experience for hard drives on Amazon extremely frustrating. There is no sort option for Price per $ATTRIBUTE. You can only view 10 search results at a time. The first dozen results are sponsored listings. Many products are available through third party sellers on Amazon’s marketplace at prices below what’s shown in the “Buy Box.” but you’ll never see these prices unless you click through to every product.

I decided to write a Python script to find all of the hard drives on Amazon, figure out their capacity, then sort by price per GB. I found the Amazon Product Advertising API, which allows you to query their product catalog and get results back as XML (later JSON). You need an access key to use the API which is only available through the Amazon Affiliates program. So I signed up.

The idea behind the affiliates program is that you’ll generate links to Amazon with a tracking ID and get people to click on them and buy stuff. I think Amazon’s original expectation of this is that bloggers and journalists would review products and refer people to Amazon to buy them. In an ideal world, this is beneficial for everyone; merchants get more sales, Amazon gets more revenue, and affiliates get a few percent commission off the top.

Amazon won’t give new affiliates access to the Product Advertising API until they’ve referred at least three sales. So my Python script turned into a diskprices.com. I built a list of hard drives in a spreadsheet and sorted by price per GB. A few people clicked my links. Armed with an API key, I was able to programmatically generate the listing and keep it up to date. I built spam filters and moderation tools so I could remove fake products and fix errors in the listings where I found them.

More people started visiting my site and I signed up for Amazon’s affiliate program in other countries. Amazon treats each country as a separate business, so you have to do the three initial sales for each region separately.

The site kept growing in both traffic and commission income. I’ve been running diskprices.com for nearly eight years, reviewed and approved by the affiliate program administrators several times.

The problem

Amazon has automated tools that crawl affiliate sites looking for violations of their Operating Agreement. Sometimes, people will find a good deal on diskprices.com and copy the link to a forum or chat room to share with their friends. If one of Amazon’s bots finds a link with my affiliate tag on a site other than diskprices.com, they assume I’m doing something shady and flag my account. This generates a form email.

The warning email

Created at Fri, Jun 30, 2023 at 9:55 AM
From Amazon Associates <[email protected]>
To [email protected]
Subject ACTION REQUIRED – Your Amazon Affiliate Account - diskprices0a-22

Hello diskprices0a-22, Your Affiliate account is at risk of closure. Why? We reviewed your account as part of our ongoing monitoring of the Amazon Affiliate Programme. During our review, we determined that you are not in compliance with the Operating Agreement, found here: https://affiliate-program.amazon.com.au/help/operating/agreement.

While reviewing your account we noticed traffic coming to Amazon from one or more sites that are currently not included in your Associates account. To understand how your Special Links are used by customers to get to the Amazon site, we request you to provide a detailed description of: • A list of the Sites, Social media handles, Mobile applications, link sharing sites (like linktree.com, campsite.bio) on which your Special Links or banner ads are posted, • Methods you are using to get customers to your website/social media page, • Live links to your Sites, (provide links to Amazon ads/special links displayed on your website/webpage) • Any other ways in which your Special Links are used by customers to get to the Amazon site (like advertising services), Please ensure that the list of sites associated with your account is complete, accurate, and up-to-date. Please do refer to the Operating Agreement for more details.

What’s next? Within five business days please correct the violations and notify us when you are in compliance through our customer service contact page: https://affiliate-program.amazon.com.au/home/contact. Please choose the subject ‘Warning/Information Request Response’ from the drop-down menu, and be sure to reference Issue Code 83441-AU in the comments field. If you do not respond within five business days, we may close your Affiliate account and withhold commissions. More information. For more information about what we’re looking for in your response, see our Warning help content, found here: https://affiliate-program.amazon.com.au/help/node/topic/GPPXH5WSX22AUNKR. We look forward to hearing from you soon. Amazon.com.au

The response

Created at Fri, Jun 30, 2023 at 10:18 AM
From Jeremy Grosser <[email protected]>
To Amazon.com.au Affiliate Program (replied via web form)
Subject Re: ACTION REQUIRED – Your Amazon Affiliate Account - diskprices0a-22

Issue Code 83441-AU

• A list of the Sites, Social media handles, Mobile applications, link sharing sites (like linktree.com, campsite.bio) on which your Special Links or banner ads are posted,

I only post links on diskprices.com. If my links have been posted elsewhere, it’s because people have copy/pasted them from diskprices.com. I have no control over this.

• Methods you are using to get customers to your website/social media page,

I do not promote my site, inbound traffic to diskprices.com is organic. Here’s a list of referrers reported by browsers in the last 24 hours for all of diskprices.com, not limited to the amazon.com.au section. For the social media sites (reddit, fark, etc), I did not create those posts and have no relationship with the users that posted them.

Redacted list of referrer URLs, including forums.overclockers.com.au

• Live links to your Sites, (provide links to Amazon ads/special links displayed on your website/webpage)

All links to amazon.com.au originate from https://diskprices.com/?locale=au

• Any other ways in which your Special Links are used by customers to get to the Amazon site (like advertising services),

I do not pay for advertising, search traffic, or SEO services. I just built a website that some people find useful and share with their friends.

The waiting

I’ve had similar exchanges with the affiliate programs for Amazon.com, Amazon.co.uk, Amazon.ca, Amazon.it, Amazon.de, Amazon.fr, Amazon.es, and Amazon.se. They always wait 10 days after sending the warning email to review your response. After 10 days, the flag on your account either goes away with no further communication, or your account is deleted.

I didn’t hear anything after 10 days, so I thought I was safe and had provided sufficient evidence to prove that I’m not violating the Operating Agreement.

One month later…

The bad news

Created at: Sat, Jul 29, 2023 at 5:20 AM
From: Amazon Associates <[email protected]>
To: [email protected]
Subject: Your Amazon Affiliate Account has been closed - diskprices0a-22

Hello diskprices0a-22, Effective today, Amazon is terminating your Affiliate account as well as the Operating Agreement that governs it (link below). Why? We reviewed your account as part of our ongoing monitoring of the Amazon Affiliate Programme. During our review, we determined that you are not in compliance with the Operating Agreement, found here: https://affiliate-program.amazon.com.au/help/operating/agreement. The violations include the following:

You are referring traffic from a site that is not your Site. An example can be found here: https://forums.overclockers.com.au/ While reviewing your account we noticed traffic coming to Amazon from one or more site/social media handle/mobile application that are currently not included in your Associates account. Please ensure that the list of site/social media handle/mobile application associated with your account is complete, accurate, and up-to-date. Please do refer to the Operating Agreement for more details https://affiliate-program.amazon.com.au/help/operating/policies We previously issued you a non-compliance warning regarding your Associates account. You did not respond or come into compliance within the specified time.

What’s next? You must stop using all Amazon Program Content (for example: Images, trademarks and other information in connection with the Associates Program) and promptly remove all links to the Amazon site. Because you are not in compliance with the Operating Agreement, Amazon will not pay you any outstanding commission income. Please be aware that any other related accounts may be closed without payment of any commissions. Amazon reserves all other rights and claims. In limited cases this closure may be appealable. See Appeals help for more information, found here: https://affiliate-program.amazon.com.au/help/node/topic/GACDBRFKVDTXSPTH. Amazon.com.au

The point

Amazon.com.au approved of my account for the last four years and has paid significant commissions. I don’t even have an account on the forum they claim I’m spamming with affiliate links.

diskprices.com no longer serves data from amazon.com.au

Ada Wireless Driver Progress

25 July 2022 at 07:00

A few weeks ago, Raspberry Pi released the Pico W, an update to the Pico development board with the addition of a wifi chip and trace antenna. As I’ve been using the Pico for many of my projects, I’m excited by the possibility of adding wireless connectivity.

I program in Ada, so I want to add support for the Pico’s wireless controller, CYW43439 to my driver library.

First, a reality check; Raspberry Pi is going to continue producing new microcontrollers and development boards with new features. They’ve put an immense amount of effort into their C/C++ SDK and MicroPython libraries. Do I really want to continue reimplementing their drivers in Ada? Would I be better off just generating a wrapper around their SDK? The Pico SDK is pretty heavily dependent on the CMake build infrastructure right now, so I’d have to figure out how to make that work with Ada. The generated Ada bindings from C code are usually pretty awkward to work with, so I’d have to write a fair bit of code either way. For now I’m going to keep going with my native Ada libraries, but I’m going to keep asking myself if this is a worthwhile effort when good vendor libraries exist.

The wireless chip they’ve chosen, CYW43439, is a beast. Infineon (formerly Cypress, formerly Broadcom) have a public datasheet, which contains information useful to PCB designers and test engineers as well as some rather confusing timing diagrams that contain no actual timing information. The Pico W has an RP2040 connected to the CYW43439 with the "gSPI" interface with both the transmit and receive signals multiplexed onto a single microcontroller pin.

The RP2040’s PIO should be perfect for implementing this sort of odd serial protocol, but I found it to be quite frustrating in this case. The CS, CLK, and DAT signals are not accessible from any test pad or trace that I can find on the Pico W, so I can’t get a logic analyzer connected while the RP2040 is communicating with the CYW43439. I considered buying a standalone module that uses this chip so that I could connect it to some pins I can probe, but I figured I’d try working blind first. I configured the PIO to use some of the Pico’s exposed pins and connected a logic analyzer. At the very least, I can get the CS, CLK and DAT transmit timing to look correct before trying to talk to the wireless controller.

The datasheet says that the device needs up to 50ms after pulling REG_ON high before it’ll respond to gSPI commands. The host should poll the registers beginning at address 0x14 for the value 0xFEEDBEAD. Once that value is read correctly, a test write/readback at address 0x18 verifies that the gSPI bus is working and the CYW43439 is powered up and ready to accept commands.

That 0xFEEDBEAD value led to some interesting search results; this protocol has been used in Broadcom wireless chipsets as far back as the venerable WRT54G router. There are many open source drivers of varying quality for this family of chips and it seems that Broadcom/Cypress/Infineon kept the host interface mostly the same over the years, only adding new commands when necessary. I found a very detailed writeup about reverse engineering the firmware for these chips, which was quite interesting, if not immediately useful.

The Pico SDK has a PIO driver for gSPI, so I spent a lot of time reading that code and trying to understand it. They use a single PIO SM to control the DAT signal, with CLK configured as a side-set. To begin a transfer, the host CPU resets the SM and executes set pindirs, 1 to configure DAT as an output, then sets the x register to the number of bits to write and y to the number of bits to read. These instructions are not included in the main PIO program as they’re not timing critical and executing them from the CPU saves on PIO memory, which is only 32 instructions. The PIO program shifts one bit out on the DAT pin, toggles the CLK sideset, then decrements the x register. Once x reaches zero, DAT is reconfigured as an input and the SM starts shifting in bits, decrementing y as it goes.

This seems fairly reasonable. We can control the number of bits per word by changing the x and y registers before beginning a transaction. The host CPU needs to get involved after each TX/RX cycle to reset the pindirs, x, and y registers and pull CS high if the transaction has completed. The driver code uses DMA to send data to the PIO, which doesn’t seem like a huge benefit when the CPU is going to intervene after every word transferred. The DMA controller can do byte swapping, which may be useful for big endian network data, but I’m not sure if it’s worth the trouble in this case.

I decided that I’d rather use up a bit more PIO memory and have separate programs for read and write that do their own setting of pindirs and the x register. The CPU still needs to control CS though, which is inconvenient. I really wish the PIO had just a few more Delay/Side-Set bits so that we could have longer delays in PIO programs for timing the CS "back porch."

The CYW43439 datasheet also illustrates a "Read-Write" transaction, where CS stays low between the Write and Read commands. The datasheet says there should be a "fixed delay" in between read and write in this mode. The diagram shows four clock edges during this delay time and there is a bus configuration register for delay timing, but I wasn’t able to get this working. As far as I know, this is just an optimization, not a required bus mode.

Speaking of optimizations, the CYW43439 defaults to 16-bit words on the gSPI interface, but the command to switch to 32-bit mode is 2 16-bit words long, so we can send that as the first command and never have to deal with changing word sizes. As far as I can tell, this is what every driver does.

After spending far too long trying to make my program block until the SM finished clocking bits and wondering if the idle polarity of the clock really matters, I got a logic analyzer trace that looked reasonably close to what the datasheet specifies. I changed my pin config to connect the PIO to the wireless controller and got some signs of life! Polling the 0x14 register returned 0xED 0xFE, which was half of my expected 0xFEEDBEAD with swapped bytes. I played with the PIO SHIFTCTRL register a bit and got a successful read of 0xFEEDBEAD. Surprisingly, the test write/readback worked too! I guess that means I have a working gSPI implementation on PIO! I noticed that the Pico SDK runs the PIO at 33 MHz for this interface, but my test program fails at anything faster than 20 MHz. I guess I still need to get a probe on those DAT, CLK, and CS traces to get the timing right. The datasheet says this interface can run at 50 MHz, but we can worry about that later.

Now that I can communicate with the CYW43439, I started looking at cyw43-driver more seriously. The datasheet makes no mention of most of the CYW43439’s internal registers and this is not a trivial driver, so I’m going to try to interoperate with this C driver from Ada, rather than trying to rewrite it. It looks like this driver calls a bunch of symbols like cyw43_hal_pin_config that we need to provide. Luckily, exporting Ada procedures as symbols that conform to the C ABI is easy.

procedure HAL_Pin_Config
  (Pin, Mode, Pull, Alt : Interfaces.C.int)
with Export, Convention => C, External_Name => "cyw43_hal_pin_config";

On the more complex side of things, we need a TCP/IP stack. cyw43 only contains bindings for LwIP and that’s what Pico SDK uses, so I went searching for Ada bindings for LwIP. Surely someone else has tried to do embedded networking in Ada before, right?

Right! I found a directory called ipstack buried in the SPARK unit tests. If the README is to be believed, this is a formally verified rewrite of a large chunk of LwIP in SPARK. Excellent! This code predates the Alire package manager by many years, so I spent some time understanding the existing Makefiles and hierarchy of GPRbuild projects and got the whole mess built as an Alire crate I can use as a dependency. There’s a lot of room for improvement here, but I want to get some packets flowing before I start trying to improve the IP implementation. If this works at all, I’ll be impressed.

The ipstack library only has two hardware drivers: a minimal TUN/TAP implementation for testing on a Linux host and a driver for the ancient NE2000 ISA card. I spent many hours learning about firewalls with OpenBSD on a 486 with a couple of NE2K cards in my formative years, so this brings back fond memories. Back in the day, the NE2000 was not the fastest network card you could buy, but it was cheap, reliable, and rock solid under Linux, which was far from a guarantee for any peripheral back then.

So now I have three bits of code to string together: my Ada/PIO gSPI implementation, the SPARK ipstack, and cyw43-driver.

Stay tuned for my next post, where I might actually get something working.

Announcing rp2040_hal 1.0

28 December 2021 at 08:00

rp2040_hal is a library of drivers for the Raspberry Pi RP2040 microcontroller written in Ada.

It’s been 11 months since the first Ada code running on an RP2040 blinked an LED. In that time, rp2040_hal has grown considerably, with support for a veritable alphabet soup of peripherals; UART, SPI, I2C, USB, DMA, PWM, PIO, RTC, Timer, and ADC.

I’m pleased with the state of things and feel like rp2040_hal is a solid foundation for other projects to build upon. Up to this point, each release came with exciting new features and functionality. This release marks a new stage of development, where the focus is on providing bug fixes, performance improvements, and a stable API. In the words of Linus, "We do not break userspace."

Documentation

If you have questions about Ada, the RP2040, or just microcontrollers in general, come join the conversation at ada-lang/raspberrypi-pico on gitter.im. Gitter.im is available over Matrix if you’d prefer to bring your own chat client.

Demos

I often share what I’m working on via on Twitter @JeremyGrosser. I’ve embedded some tweets about my RP2040 Ada projects below.

I needed to charge some NiMH batteries but I was worried they'd overheat. Only took an hour to build a nice little temperature monitor! pic.twitter.com/pcJXoAt8yk

— Jeremy Grosser (@JeremyGrosser) October 5, 2021

I've got working bitmap tiles on the Picosystem now. I went with 2-bit 8x8 bitmaps, with switchable color palettes, similar to the Game Boy. pic.twitter.com/Gv68XG2t4N

— Jeremy Grosser (@JeremyGrosser) November 10, 2021

pic.twitter.com/agOMV7A3pM

— Jeremy Grosser (@JeremyGrosser) December 7, 2021

Finally remembered why I started messing with microcontrollers to begin with... Behold, my three timezone NTP clock. pic.twitter.com/IHoCbUhm1n

— Jeremy Grosser (@JeremyGrosser) October 19, 2021

Lookin pretty good! pic.twitter.com/HKvWuzVCGr

— Jeremy Grosser (@JeremyGrosser) December 23, 2021

Pretty happy with how my portable Pico dev kit is turning out. Still need to 3d print a mount for the battery and make some shorter wires. pic.twitter.com/a3LFRChFwn

— Jeremy Grosser (@JeremyGrosser) June 22, 2021

SPI debugging means I get to use *all the channels* pic.twitter.com/ntIMNSBKRd

— Jeremy Grosser (@JeremyGrosser) June 25, 2021

Adding a serial command line to the #RaspberryPi #Pico in #Adahttps://t.co/jxJ9FGGTjZ

— Jeremy Grosser (@JeremyGrosser) July 18, 2021

Update: I blew up my microcontroller.

— Jeremy Grosser (@JeremyGrosser) July 5, 2021

I built it on a breadboard. This is gonna work. pic.twitter.com/y3WteQgFAH

— Jeremy Grosser (@JeremyGrosser) September 26, 2021

Changelog 1.0.0

New features

DMA IRQ management

RP.DMA can now configure interrupt masks for each DMA channel. If DMA_Configuration.Quiet = False, the interrupt will fire when a transfer is completed.

Unit tests

We’ve begun writing tests for rp2040_hal with the AUnit Testing Framework. Currently, there are tests for Clock, UART, SPI, GPIO, and DMA. These tests have already led to several bug fixes and we will continue to work toward more complete unit test coverage.

Bugs fixed

GPIO.Mode returned incorrect values

GPIO.Mode was returning the mode of the wrong pin.

PWM divider edge cases

The minimum and maximum PWM divider values were calculated incorrectly. The calculation and constraints on RP.PWM.Divider have been fixed.

RP.DMA.Status returned incorrect Transfers_Remaining

The DMA alias register layouts were incorrect. The only visible effect of this error was that RP.DMA.Status returned an incorrect value for Transfers_Remaining.

Some DMA triggers didn’t work

The DREQ register values did not have a representation clause specified, which caused triggers internal to the DMA peripheral (pacing timers and permanent triggers) to be nonfunctional.

Pico Ada Libraries Release 0.7.0

27 October 2021 at 07:00

This is another big release, with a USB device controller driver, ADC DMA support, and boot2 rewritten in Ada. Many other small bug fixes and usability improvements are included. The detailed changelog can be found on the documentation site. Thanks to Daniel King, Fabien Chouteau, and Holger Rodriguez for patches, testing, advice, and feedback during this release cycle.

I’ve begun work on a board support package called picosystem_bsp for the Pimoroni Picosystem, which is a handheld game console that includes an RP2040, 240x240 screen, piezo speaker, buttons, and a battery. I’ll be building games on this platform soon!

Pico Ada Libraries Release 0.6.0

13 September 2021 at 07:00

I’ve tagged release 0.6.0 of the rp2040_hal, pico_bsp, and pico_examples libraries for the Raspberry Pi Pico. Release notes are at the end of this page.

This is a relatively small release as I’ve found that these drivers are working well for my projects. If things keep going this way, I’ll be comfortable tagging a stable 1.0 release before the end of the year and promising no breaking changes without a major version bump.

Work needed

While we have simple drivers for most of the RP2040’s internal peripherals, there are a few tricky bits that still need work. I’d appreciate help with these.

Ravenscar Runtime

To take advantage of some of the more interesting features of Ada (tasking, protected types, etc) RP2040 support needs to be merged into bb-runtimes. On the rpi-pico-2021 branch I’ve rebased damaki’s branch on the community-2021 release of bb-runtimes. This branch cannot be merged upstream as it still uses a stage 2 boot (boot2) loader assembled from the upstream pico-sdk, which is licensed as BSD-3-Clause and copyright Raspberry Pi Foundation. AdaCore prefers that all contributions to bb-runtimes be licensed as GPL-3 and their copyright assigned to AdaCore. Therefore, we need to write new boot2 code that can be licensed as such.

I’ve made an attempt to reimplement the generic_03h version of boot2 in Ada, but this is broken. The ROM expects a .boot2 section at the beginning of flash to be padded to 256 bytes, with the last four bytes containing a CRC32. As far as I know, there’s no easy way to do the padding and checksum within the bb-runtimes build system. I’d be comfortable with having an out-of-band build process that generates a .S with the boot2 binary that can be committed to the bb-runtimes repository. In addition to the missing checksum, I believe my boot2 implementation does not save the link register and jump to the correct entry point after executing. I don’t know how to do that without adding inline assembly, which seems inelegant.

I’d appreciate help with getting the Ada boot2 implementation working so that the rpi-pico-2021 branch can be merged to bb-runtimes.

USB

I’ve made some progress on a USB driver, but it’s far from complete and too disorganized to share right now. While it’s certainly possible to build a USB driver within the ZFP runtime restrictions, the implementation would be much easier and cleaner if tasking and storage pools were available. To that end, I’m prioritizing work on the Ravenscar runtime ahead of the USB stack. If anybody does want to work on USB, most of the device stack is already in usb_embedded, but that library requires hardware atomics that the Cortex-M0+ does not have. I have a branch with the atomics stuff removed that kinda works, but this feels like playing with fire.

Dual CPU

The bb-runtimes branch already has support for multiprocessing so I’m hesitant to duplicate that work in rp2040_hal. Even if I were to add SMP support to rp2040_hal, doing tasking without using Ada’s tasking features just seems wrong. Once again, this reinforces the importance of the Ravenscar work.

Integer divider

There’s a hardware integer divider in the SIO block that would significantly improve the performance of many programs. The assembly code used to hook this up to gcc’s EABI in pico-sdk is intimidating and more complicated than you’d expect.

Watchdog

I wrote a driver for it, but I’m not convinced that it works. Watchdogs are hard to test.

rp2040_hal 0.6.0 Release Notes

New features

Clocks can be disabled

To save power, peripheral clocks can be disabled with RP.Clock.Disable. Some peripherals may exhibit unexpected behavior if their clocks are disabled. Use at your own risk.

RTC can be paused

The RP.RTC.Pause and RP.RTC.Resume procedures stop and start the RTC. This is useful if you want the RTC to stop ticking while a user is setting the time. Preconditions requiring the clock to be running have been removed from the RTC procedures. RP.RTC.Initialize still needs to be called at least once, but can be skipped if RP.RTC.Running returns True, implying that the RTC is already Initialized.

Breaking changes

Delay_Microseconds no longer uses interrupts

RP.Timer.Delay_Microseconds now polls the timer registers in a busy loop, rather than setting up an alarm interrupt. This should make shorter (< 10 microsecond) delays more accurate as interrupt latency is no longer a factor. RP.Timer.Delay_Until can still be used to perform interrupt-based delays with microsecond precision.

Bugs fixed

16-bit RP.SPI.Transmit did not respect the Blocking configuration option

Issue #3

If Blocking was set in the SPI_Configuration and the 16-bit version of the Transmit procedure was used, Transmit would return before all data was clocked out. Thanks to @hgrodriguez for discovering this

RP.PWM did not check that Initialize was called first

If RP.PWM.Initialize was not called before configuring PWM slices, the configuration would succeed but would generate no output. An Initialized variable has been added to RP.PWM along with a precondition on all procedures that modify PWM slices to ensure that Initialized is True. If you forget to call RP.PWM.Initialize, your program will crash on the first run.

RP.ADC.Temperature could return incorrect data

If RP.ADC.Configure (Temperature_Sensor) was not called before RP.ADC.Temperature, incorrect temperature readings would be returned. RP.ADC.Temperature now ensures the temperature sensor is configured on every call, eliminating the need to call Configure for the temperature sensor.

Pico Ada Libraries Release 0.5.0

20 July 2021 at 03:58

I’ve tagged release 0.5.0 of the rp2040_hal, pico_bsp, and pico_examples libraries for the Raspberry Pi Pico.

In addition to this month’s library updates, I’ve begun making YouTube videos about developing Ada on the Raspberry Pi Pico, starting with a debug setup and serial console library.

rp2040_hal 0.5.0

New features

UART enhancements

RP.UART now allows configuration of baud, word size, parity, and stop bits via the UART_Configuration record. The default values for the UART_Configuration record represent the typical 115200 8n1 setup.

The UART now has a Send_Break procedure, which holds TX in an active state (usually low) for at least two frame periods. Some protocols use the UART break condition to indicate the start of a new packet.

RP.UART.Receive now sets Status = Busy and returns immediately if a break condition is detected.

UART Transmit and Receive procedures now return as soon as all words have been delivered to the FIFO. FIFO status is exposed by the Transmit_Status and Receive_Status functions. This interface is the same as the I2C and SPI drivers.

The uart_echo example has been updated to demonstrate these new features.

RTC driver

The real time clock is now exposed by the RP.RTC package. It implements the HAL.Real_Time_Clock interface for getting and setting the date and time. An example project demonstrates use of the RTC. RTC alarm interrupts are not yet implemented.

Interpolator driver

The RP2040 has two interpolators per core embedded in the SIO peripheral. The RP.Interpolator package make their registers available. Some of the registers in this block support single-cycle operation, so it would be counter productive to wrap them up in procedures that may not be inlined by the compiler. There are examples in the datasheet for working with the interpolators, but I’m still trying to wrap my head around it, so there is no example here yet.

Breaking changes

UART.Enable is replaced with UART.Configure

To match the nomenclature of the other serial drivers (SPI, I2C), RP.UART now has a Configure procedure instead of Enable.

I2C addresses should include the R/W bit

The RP.I2C driver was expecting 7-bit I2C addresses to not include the R/W bit in the LSB. This was inconsistent with the other HAL.I2C implementations and would result in incorrect I2C addressing. Now, 7-bit I2C addresses should be represented as a UInt8 with the LSB set to 0. If this breaks your code, shift your I2C address left by one bit.

Bugs fixed

Improper use of the Pack clause

The Pack clause was used to enforce the memory layout of some records.

It is important to realize that pragma Pack must not be used to specify the exact representation of a data type, but to help the compiler to improve the efficiency of the generated code. Source

The Pack clause has been replaced with Component_Size and Size clauses where necessary. Thanks to @onox for pointing this out!

Use of access PIO_Device as a type discriminant

Projects depending on pico_bsp failed gnatprove in SPARK mode as the Pico.Audio_I2S package was using not null access PIO_Device as a discriminant. PIO_Device is now tagged and Pico.Audio_I2S uses not null access PIO_Device'Class, which is valid under SPARK. gnatprove still throws many warnings about side effects in the rp2040_hal drivers, but no fatal errors.

RP.ADC.Read_Microvolts was rounding incorrectly

Read_Microvolts was using Integer arithmetic to calculate VREF / Analog_Value'Last, which does not divide evenly for common VREF values. When that value was multiplied by an ADC reading, Read_Microvolts would return lower than expected results. Read_Microvolts now uses floating point to multiply ADC counts before converting the return value to Integer.

UART Transmit and Receive did not respect Timeout

The UART driver has been modified to use RP.Timer to implement timeouts and monitor FIFO status, similar to RP.SPI and RP.I2C.

SPI Transmit was nonblocking

The SPI Transmit procedure would return immediately after the last byte was written to the FIFO, but before the FIFO became empty. This behavior breaks some drivers that depend on all bytes being clocked out before proceeding. A configuration flag for Blocking behavior has been added and defaults to True.

Pico Ada Libraries Release 0.4.0

11 June 2021 at 06:35

I’ve tagged release 0.4.0 of the rp2040_hal, pico_bsp, and pico_examples libraries for the Raspberry Pi Pico. Lots of testing and bug fixes on SPI and I2C in this release and both interfaces should be much more stable now. DMA to PIO works well and opens up a lot of possibilities for high speed signaling. Examples have been added for a rigorous SPI loopback test and a custom BSP implementation for the Adafruit Feather RP2040, which should serve as a template for running on any board that uses the RP2040. The Pimoroni Audio Pack example has been updated to use the ROM floating point library to generate a sampled sine output.

rp2040_hal 0.4.0

New features

DMA driver

The RP.DMA package allows out of band copies between a source and target System.Address and may be triggered by a variety of events. The PIO and SPI drivers have been tested with DMA and have new functions that return their FIFO addresses.

I/O Schmitt triggers

The RP.GPIO.Configure procedure now takes optional Schmitt and Slew_Fast boolean parameters that control the behavior of I/O pads. The RP2040 documentation recommends enabling the Schmitt trigger for I2C operation.

RP.ROM.Floating_Point

The ROM floating point library is now exposed in the RP.ROM.Floating_Point package. GNAT will use gcc’s soft float implementation by default, but you may call the optimized versions in the ROM directly. The Ravenscar runtimes will replace the gcc functions with these ROM calls automatically.

I2C and SPI Timeouts

Previously, the I2C and SPI drivers did not use the Timeout argument. They now use RP.Timer to implement a timeout for all blocking operations and set Status to Err_Timeout if it expires before the blocking operation completes. The I2C peripheral may require a reset after a timeout as the bus may be in an unknown state.

SPI FIFO status is exposed with Transmit_Status and Receive_Status

You can use these functions to determine if the Transmit or Receive procedures would block. See the new spi_loopback example.

Breaking changes

PWM Set_Duty_Cycle and Set_Invert no longer use PWM_Point

These procedures have changed to take a PWM_Slice as the first argument to make them more consistent with the rest of the driver. These procedures now set both channels of a slice nearly simultaneously.

PWM Initialize must be called before any other PWM configuration

This procedure was added to fix the corruption bug discussed below.

SPI.Enable is replaced with SPI.Configure

The Configure procedure takes a SPI_Configuration record as an argument for easy static configuration.

Bugs fixed

PWM configuration is corrupted after power cycle

RP.PWM.Enable is called after configuring a PWM slice to enable it. This procedure was incorrectly resetting the PWM peripheral before enabling the slice. RP.PWM.Initialize now performs the reset and all peripheral resets have been moved to RP.Reset to avoid this mistake in the future.

PWM dividers can have a value of zero

The documentation is unclear on what this means, but my testing shows that it acts like a divider of 1, which outputs the clk_sys frequency.

Fast I2C writes would result in dropped bytes

The RP.I2C_Master driver has been modified to wait for the TX FIFO to be empty before writing a byte. This effectively reduces the FIFO depth to 1 byte. This is the same behavior as the upstream SDK.

Known issues

I2C clock is slower than expected

In 400 KHz (fast mode) operation, the I2C master generates SCL at approximately 380 KHz. I believe this is due to clock stretching caused by the new TX FIFO blocking behavior. The upstream SDK has the same behavior. According to the I2C specification, a fast mode clock may be up to 400 KHz, but specifies no minimum frequency. It may be possible to workaround this by using DMA to write to the I2C FIFO, but this is untested.

Should I learn Ada for embedded development?

18 March 2021 at 01:21

Originally posted in response to this thread on Reddit.

I started learning Ada a few years ago and have been using it for the type of hobby projects where most people would reach for an Arduino or Teensy compatible thing. Like you, I was also seeking a safer alternative to C.

Learning Ada is not easy. It’s a big language and the reference manual uses quite a bit of vocabulary that’s specific to Ada, making it even harder for a beginner to comprehend. There are some excellent textbooks, but it’s 2021 and most people learn by Googling things, not thumbing paper. Ada resources tend to be heavily weighted towards explaining with words rather than examples, which is again complicated by the vocabulary. Personally, I feel like I learned the most when I did the Advent of Code 2019 and 2020 puzzles in Ada. There’s just no substitute for bashing your head against lots of different problems until it makes sense.

If you’ve ever skimmed any of the popular C coding standards, you’ll find a lot of rules that boil down to “allocate on the stack and avoid pointers”. When you do need a pointer, Ada has access types, which are fat pointers that associate a type and size with the underlying pointer and make it much harder to do unsafe things by accident. One of my favorite things is the concept of a not null access type, which is a pointer that must be assigned a value at instantiation and can never be assigned null. This effectively makes null pointer exceptions impossible.

“Safe” code is an overloaded term that means different things depending on what industry you work in. There is a subset of the Ada language called SPARK that allows you to specify enough constraints to be able to generate formal proofs of certain properties of your implementation. If you’re building a machine that can kill people unintentionally, your code is probably subject to a standard like DO-178C or ISO 26262 that requires this level of verification. These days, most of those systems get modeled and verified in a program like Matlab which generates C or C++ code that conforms to the model. Having a toolchain that can verify that the model’s constraints still hold for the actual implementation code is where Ada/SPARK shines.

If you’re just trying to avoid buffer overflows, Ada makes it difficult to do dumb things. In most cases, your code won’t compile if you write past the bounds of an array. Worst case, it’ll raise an exception at runtime. If you really believe you know better than the compiler, you can disable the runtime checks and use an Unchecked_Conversion to change the size of the array. But if you’re reaching for these switches, you probably need to rethink your decisions.

Record types are like C structs, with a very rich syntax for defining the memory layout of the struct members. These “record representation clauses” are probably the most useful construct for embedded programming as you can define bit fields in registers and buffers without ever needing to shift and mask bits, eliminating a large class of potential bugs.

Ada has namespaces, generics, objects, interfaces, polymorphism, collections, and iterators, just like you’d find in C++ or Java, but with more verbose syntax. “Annex A” of the language spec is effectively the standard library and is quite comprehensive. The language predates unicode, so it has the same issues as C conflating character encoding with size. There are several libraries that add new unicode string types, but the community hasn’t centered around a single implementation yet.

There are many arguments against Ada that have been discussed ad nauseum for the last 20 years. One of the most persistent is that Ada compilers are expensive. GCC has had a GPL licensed Ada frontend called GNAT since the mid-90s. A company called AdaCore contributes the majority of patches to GNAT and sells a commercial version that provides better tooling for people that need to certify their code. Most applications will be just fine using the open source GPL licensed GNAT. It’s already in most Linux distros, on Debian you can apt-get install gnat.

“It’s hard to hire Ada programmers.” This is true, but in my opinion points to a larger issue with organizations wanting to hire a candidate with every conceivable skill they will ever need, rather than investing time into training and education. Plenty of people would be happy to learn a new programming language given the opportunity. That said, there are several Ada developers lurking on reddit, freenode, and github that would be delighted to take a job writing Ada full time.

I know I didn’t directly answer your question about Ada in embedded contexts specifically, but that’s because I believe that you can write good embedded software in any language. The development process is more important than the language.

The Department of Defense put a bunch of experts in a room together to come up with requirements for a systems language. They iterated on these requirements a few times, then called for proposals for a language to fulfil those requirements. Three languages were proposed, discussed, and voted upon. The winning proposal was renamed to Ada. This same requirements -> proposals -> deliberation -> implementation flow is more familiar today as the waterfall process or V model. Process drives good software, the language is just a tool.

Using drivers

3 March 2021 at 02:38

Twiddling bits in registers doesn’t make the most intuitive, readable, or portable code. This is why we write drivers. I’ve created three Alire packages, rp2040_hal, pico_bsp, and pico_examples. rp2040_hal contains all of the drivers for the chip’s internal peripherals, pico_bsp contains some details about the Pico board and drivers for the Pimoroni Pico addons, and pico_examples contains, you guessed it, example code. At the moment, pico_bsp cannot be used with the Ravenscar runtime without modification, so I’ll ignore that and focus on rp2040_hal for right now. The examples repository contains lots of code that uses the pico_bsp if you’d like to see how that works.

Now that I have drivers, I’ve removed all of the interfaces generated from SVD from our project. These still live in the rp2040_hal package but as long as the drivers implement all of the right interfaces, we shouldn’t need them. If you find something you can’t do with the HAL drivers that seems important, please send a pull request to the rp2040_hal repo. I’ve refrained from tagging a 1.0 release because I’m still not satisfied with all of the interfaces and retain the right to break the API.

with Ada.Real_Time; use Ada.Real_Time;
with RP.GPIO; use RP.GPIO;

procedure Main is
    LED        : GPIO_Point := (Pin => 25);
    Next_Blink : Time := Clock;
begin
    LED.Configure (Output);
    loop
       LED.Toggle;
       delay until Next_Blink;
       Next_Blink := Next_Blink + Seconds (1);
    end loop;
end Main;

You can see that the Main procedure is much more compact and readable now. The PADS_BANK and IO_BANK stuff is wrapped up in a lovely Configure interface and the GPIO is abstracted into an object with a very convenient Toggle method.

Building the code is done using Alire now, rather than calling gprbuild directly. Alire is analogous to Rust’s Cargo or Python’s pip. Alire keeps track of all of the dependencies and can pull in new ones with the alr with command.

git clone https://github.com/JeremyGrosser/rp2040_hal
cd 04-hal-blink
alr build

Source code

Abstractions and Runtimes

3 March 2021 at 02:37

A month later, much has happened! I’ve implemented drivers for nearly all of the RP2040’s peripherals, Fabien Chouteau contributed an I2C driver, and Daniel King has ported bb-runtimes, including multiprocessing support. Let’s get that blink example updated!

There are lots of pieces that can be abstracted away and made more flexible. For example, Ticks shouldn’t be a public variable that can be modified from anywhere. I should also define a new Time type to differentiate it from other Integers. If you follow these changes to their logical conclusion, you get something that looks like Ada.Real_Time which already exists in the Ravenscar runtimes.

The Ada language provides some fairly high level constructs related to tasking, memory management, and timing that aren’t easy or practical to implement on every platform and the use of some of those features may violate a project’s certification requirements. For this reason, there several runtime profiles with varying levels of functionality. So far, I’ve been using a Zero Footprint (ZFP) runtime, which provides only the bare minimum to allocate some stack space, pass arguments, and call a procedure. No batteries included. The next step up would be the Ravenscar profile, which allows a broader set of builtin functionality, including tasking and synchronization constructs you’d expect to find in an RTOS. An open source implementation of Ravenscar is available in the bb-runtimes repository, though porting it to a new chip is not a small task. There are other runtime implementations that wrap existing RTOS libraries like FreeRTOS and RTEMS. GNAT GCC also includes runtimes for Linux, FreeBSD, Solaris, HP-UX, VxWorks, LynxOS, QNX, and Windows that implement the full set of libraries in the Ada language specification.

I’ll port our blink example to a Ravenscar RTS (Run-Time System) from bb-runtimes. As the RP2040 is still a new platform, its runtimes haven’t been merged yet and aren’t distributed in the GNAT Community 2020 bundle so I’ll need to build it from source.

02-ravenscar-blink

git clone -b rpi-pico https://github.com/damaki/bb-runtimes
cd bb-runtimes
./build_rts.py --build rpi-pico-mp
cd ../02-ravenscar-blink
gprbuild -P rpsimple.gpr

You’ll notice that the boot2, crt0, and linker script aren’t needed anymore as they’re included with the runtime. I’ve replaced all of the SysTick stuff with a single delay 1.0; statement. The RTS has configured the PLLs and the TIMER peripheral, so I now have microsecond resolution tickless operation. We can still do better! The single-cycle loop still takes time to execute, and waking from sleep takes a few cycles too, so the delay between blinks is still not precisely one second.

In 03-realtime-blink I’ve imported Ada.Real_Time and declared a Next_Blink variable with type Time. Time is a private type as the storage representation of time is a non-portable implementation detail. Initializing Next_Blink with a call to the Clock function means that time doesn’t even have to start at 0!

Next_Blink : Time := Clock;

Now that I know when the next blink should happen, I can use a delay until statement for precision waiting.

loop
   SIO_Periph.GPIO_OUT_XOR.GPIO_OUT_XOR := Pin_Mask;
   delay until Next_Blink;
   Next_Blink := Next_Blink + Seconds (1);
end loop;

Source code

From Zero to Blinky in Ada

3 March 2021 at 02:36

Recently, the Raspberry Pi Foundation launched their new RP2040 SoC on a $4 development board called Pico. As the foundation’s goal is primarily education, they’ve provided lots of high quality documentation, libraries, and examples for the new chip. Naturally, I’m going to ignore most of that and roll my own. Why? Because this is my idea of fun and I’m trapped inside during a pandemic.

If you’d like to build the examples and follow along, you’ll need GNAT Community 2020 ARM ELF installed in your PATH. I’ve only tested this on x86_64 Debian, if you’re on another platform I can’t help you. You’ll also want an SWD debugger that works with the RP2040. I use openocd on a Raspberry Pi, I hear Segger J-Link support is coming soon. Technically you could use elf2uf2 and load binaries over USB, but that’s gonna get tedious for any nontrivial debugging.

The RP2040 has no internal flash. The boot ROM loads 256 bytes of code from an external SPI flash and executes it. This “second stage bootloader” is expected to configure the XIP (eXecute In Place) peripheral with clock and timing details specific to the flash chip in use, then jump to the start of the user code in the memory mapped to the flash.

I tried to rewrite the boot2.S bootloader from pico-sdk in Ada, but I couldn’t get it to fit inside 256 bytes. It might be possible, but not today. I ran gcc’s preprocessor on boot2.S to generate a single assembly file I could link. I copied the linker script and crt0.S from pico-sdk too.

The zfp-cortex-m0p Ada runtime does most of the boilerplate Cortex-M0+ setup and implements Ada.Text_IO with semihosting over the debug interface, so I wrote a hello world and copypasta’d a GPR project file to build it.

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
begin
   Put_Line ("Hello, RP2040!");
end Main;

It took me a few hours to figure out the right incantation to get the linker to put the .boot2 section at the start of the flash chip along with crt0.S and the Ada runtime’s init code. The result is an ugly mess, which is why I’m omitting it here. If you want to see how to do things the quick and dirty way, my initial attempt is on github.

I soldered some headers for the Pico’s SWD port, wired it to a Raspberry Pi 4, and compiled the raspberrypi branch of openocd. As far as I can tell, nobody distributes a toolchain that can cross-compile Ada arm-eabi binaries on aarch64 and I don’t want to spend my time building binutils and gcc right now, so I setup an ssh tunnel from my x86_64 workstation to the Pi. I run arm-eabi-gdb locally and connect to openocd over the tunnel. Most of this will go in a .gdbinit script later.

Raspberry Pi SWD wiring

ssh [email protected] -L3333:localhost:3333
openocd -f interface/raspberrypi-swd.cfg -f target/rp2040.cfg

arm-eabi-gdb obj/main
target extended-remote localhost:3333
monitor arm semihosting enable
load
run

Let’s write some code! For this example, I’m not going to do anything particularly complicated or idiomatic to Ada in order to keep things as simple as possible. This is going to be a very imperative program assigning values to registers and not much more.

ARM’s tooling for silicon vendors generates an SVD file, which is a pile of XML that lists all of the peripheral addresses and register offsets with vaguely human readable names. The svd2ada program translates this into Ada spec files with record types and representation clauses. Unfortunately, it crashed with the RP2040 SVD file. svd2ada expects a <size> to be specified on every register but the RP2040 SVD defines <size> at the peripheral level. The SVD format says this should then be inherited by the registers in the peripheral, but svd2ada doesn’t do that. I couldn’t figure out how to fix svd2ada so I wrote a quick Python script to modify the SVD file by copying the <size> field to every register within a peripheral. Now svd2ada works and generates a .ads file for each peripheral on the RP2040.

Normally the next step in bringing up a new chip would be to get all of the clocks configured, which is usually pretty boring code to write. At startup, the RP2040’s system clock runs from an internal ring oscillator with a not at all predictable or stable frequency between 1.8 and 12 MHz, which is good enough for some blinking. I skipped clock configuration and went straight for the GPIO.

There are four peripherals that need to be configured to toggle a pin: RESETS, PADS_BANK, IO_BANK, and SIO. RESETS, as you might expect, controls the reset state of the other peripherals. I pull the IO_BANK and PADS_BANK out of reset and wait for any initialization these peripherals might need to do in a busy loop.

RESETS_Periph.RESET.io_bank0 := False;
RESETS_Periph.RESET.pads_bank0 := False;
while not RESETS_Periph.RESET_DONE.io_bank0 or else not RESETS_Periph.RESET_DONE.pads_bank0 loop
  null;
end loop;

Next, PADS_BANK enables the output driver on GPIO25, which is connected to the LED on the Pico board.

--  output disable off
PADS_BANK0_Periph.GPIO25.OD := False;
--  input enable off
PADS_BANK0_Periph.GPIO25.IE := False;

IO_BANK selects a peripheral to connect the pad to.

--  function select
IO_BANK0_Periph.GPIO25_CTRL.FUNCSEL := sio_25;

The SIO (single-cycle IO) peripheral can toggle pins. I added a Pin_Mask constant in the declare block with bit 25 set. svd2ada generates nice subtype definitions for each register field so that I don’t need to know that GPIO_OUT is 30 bits wide. The immediate value here is just Shift_Left (1, 25), but using Shift_Left here would require some type conversion that I want to avoid in this example.

Pin_Mask : constant GPIO_OUT_GPIO_OUT_Field := 16#0200_0000#

Enable the output in the SIO peripheral

-- output enable
SIO_Periph.GPIO_OE_SET.GPIO_OE_SET := Pin_Mask;

Now we blink!

loop
   SIO_Periph.GPIO_OUT_SET.GPIO_OUT_SET := Pin_Mask;
   SIO_Periph.GPIO_OUT_CLR.GPIO_OUT_CLR := Pin_Mask;
end loop;

Assuming those calls actually get compiled into single cycle writes, that’s gonna be blinking at half the system clock frequency, far faster than the human eye can see, but good enough for an oscilloscope. The SIO peripheral has an XOR register that allows us to make this code even shorter.

loop
   SIO_Periph.GPIO_OUT_XOR.GPIO_OUT_XOR := Pin_Mask;
end loop;

This is where the narrative diverges from reality. After I got to this point, I spent a few days refactoring the code into a package with well defined types and interfaces that conform to the Ada HAL package. For the sake of this example, I’m going to gloss over some of those organizational details and pretend things are still mostly happening in a single Main procedure.

Next I need a delay in that loop to blink at a rate that’s perceptible to humans. I could call a bunch of nop instructions and waste cycles, but that’s just silly. The RP2040 has a very nice 64-bit timer peripheral that I completely ignored because the chip also has the standard ARM SysTick peripheral that I’m already familiar with. Exported symbols need to be defined at the package level, so I’ve put this code into a SysTick package. I’ve only reproduced the implementation bits here, just know that this is happening in a new file.

--  Reload every 1ms
PPB_Periph.SYST_RVR.RELOAD := SYST_RVR_RELOAD_Field (12_000_000 / 1_000);

PPB_Periph.SYST_CSR :=
    (CLKSOURCE => True, --  cpu clock
     TICKINT   => True,
     ENABLE    => True,
     others    => <>);

I need an interrupt handler to increment a counter for every tick. crt0.S exports a weak isr_systick symbol that I can implement.

Ticks : Natural := 0;

procedure SysTick_Handler
    with Export        => True,
         Convention    => C,
         External_Name => "isr_systick";

procedure SysTick_Handler is
begin
    Ticks := Ticks + 1;
end SysTick_Handler;

I’ll also add a wrapper around the wfi (wait for interrupt) assembly instruction in the same package

procedure Wait is
begin
    System.Machine_Code.Asm ("wfi", Volatile => True);
end Wait;

Back in the blink loop, I call the wait for interrupt instruction and check the value of Ticks.

loop
   if SysTick.Ticks >= Next_Blink then
      SIO_Periph.GPIO_OUT_XOR.GPIO_OUT_XOR := Pin_Mask;
      Next_Blink := Next_Blink + 1_000;
   end if;
   SysTick.Wait;
end loop;

IT BLINKS! Not precisely at 1 Hz, as the ring oscillator isn’t accurate, but close enough for this demo.

Pico Ada Libraries Release 0.5.0

21 July 2021 at 03:58

I’ve tagged release 0.5.0 of the rp2040_hal, pico_bsp, and pico_examples libraries for the Raspberry Pi Pico.

In addition to this month’s library updates, I’ve begun making YouTube videos about developing Ada on the Raspberry Pi Pico, starting with a debug setup and serial console library.

rp2040_hal 0.5.0

New features

UART enhancements

RP.UART now allows configuration of baud, word size, parity, and stop bits via the UART_Configuration record. The default values for the UART_Configuration record represent the typical 115200 8n1 setup.

The UART now has a Send_Break procedure, which holds TX in an active state (usually low) for at least two frame periods. Some protocols use the UART break condition to indicate the start of a new packet.

RP.UART.Receive now sets Status = Busy and returns immediately if a break condition is detected.

UART Transmit and Receive procedures now return as soon as all words have been delivered to the FIFO. FIFO status is exposed by the Transmit_Status and Receive_Status functions. This interface is the same as the I2C and SPI drivers.

The uart_echo example has been updated to demonstrate these new features.

RTC driver

The real time clock is now exposed by the RP.RTC package. It implements the HAL.Real_Time_Clock interface for getting and setting the date and time. An example project demonstrates use of the RTC. RTC alarm interrupts are not yet implemented.

Interpolator driver

The RP2040 has two interpolators per core embedded in the SIO peripheral. The RP.Interpolator package make their registers available. Some of the registers in this block support single-cycle operation, so it would be counter productive to wrap them up in procedures that may not be inlined by the compiler. There are examples in the datasheet for working with the interpolators, but I’m still trying to wrap my head around it, so there is no example here yet.

Breaking changes

UART.Enable is replaced with UART.Configure

To match the nomenclature of the other serial drivers (SPI, I2C), RP.UART now has a Configure procedure instead of Enable.

I2C addresses should include the R/W bit

The RP.I2C driver was expecting 7-bit I2C addresses to not include the R/W bit in the LSB. This was inconsistent with the other HAL.I2C implementations and would result in incorrect I2C addressing. Now, 7-bit I2C addresses should be represented as a UInt8 with the LSB set to 0. If this breaks your code, shift your I2C address left by one bit.

Bugs fixed

Improper use of the Pack clause

The Pack clause was used to enforce the memory layout of some records.

It is important to realize that pragma Pack must not be used to specify the exact representation of a data type, but to help the compiler to improve the efficiency of the generated code. Source

The Pack clause has been replaced with Component_Size and Size clauses where necessary. Thanks to @onox for pointing this out!

Use of access PIO_Device as a type discriminant

Projects depending on pico_bsp failed gnatprove in SPARK mode as the Pico.Audio_I2S package was using not null access PIO_Device as a discriminant. PIO_Device is now tagged and Pico.Audio_I2S uses not null access PIO_Device'Class, which is valid under SPARK. gnatprove still throws many warnings about side effects in the rp2040_hal drivers, but no fatal errors.

RP.ADC.Read_Microvolts was rounding incorrectly

Read_Microvolts was using Integer arithmetic to calculate VREF / Analog_Value'Last, which does not divide evenly for common VREF values. When that value was multiplied by an ADC reading, Read_Microvolts would return lower than expected results. Read_Microvolts now uses floating point to multiply ADC counts before converting the return value to Integer.

UART Transmit and Receive did not respect Timeout

The UART driver has been modified to use RP.Timer to implement timeouts and monitor FIFO status, similar to RP.SPI and RP.I2C.

SPI Transmit was nonblocking

The SPI Transmit procedure would return immediately after the last byte was written to the FIFO, but before the FIFO became empty. This behavior breaks some drivers that depend on all bytes being clocked out before proceeding. A configuration flag for Blocking behavior has been added and defaults to True.

Pico Ada Libraries Release 0.5.0

20 July 2021 at 03:58

I’ve tagged release 0.5.0 of the rp2040_hal, pico_bsp, and pico_examples libraries for the Raspberry Pi Pico.

In addition to this month’s library updates, I’ve begun making YouTube videos about developing Ada on the Raspberry Pi Pico, starting with a debug setup and serial console library.

rp2040_hal 0.5.0

New features

UART enhancements

RP.UART now allows configuration of baud, word size, parity, and stop bits via the UART_Configuration record. The default values for the UART_Configuration record represent the typical 115200 8n1 setup.

The UART now has a Send_Break procedure, which holds TX in an active state (usually low) for at least two frame periods. Some protocols use the UART break condition to indicate the start of a new packet.

RP.UART.Receive now sets Status = Busy and returns immediately if a break condition is detected.

UART Transmit and Receive procedures now return as soon as all words have been delivered to the FIFO. FIFO status is exposed by the Transmit_Status and Receive_Status functions. This interface is the same as the I2C and SPI drivers.

The uart_echo example has been updated to demonstrate these new features.

RTC driver

The real time clock is now exposed by the RP.RTC package. It implements the HAL.Real_Time_Clock interface for getting and setting the date and time. An example project demonstrates use of the RTC. RTC alarm interrupts are not yet implemented.

Interpolator driver

The RP2040 has two interpolators per core embedded in the SIO peripheral. The RP.Interpolator package make their registers available. Some of the registers in this block support single-cycle operation, so it would be counter productive to wrap them up in procedures that may not be inlined by the compiler. There are examples in the datasheet for working with the interpolators, but I’m still trying to wrap my head around it, so there is no example here yet.

Breaking changes

UART.Enable is replaced with UART.Configure

To match the nomenclature of the other serial drivers (SPI, I2C), RP.UART now has a Configure procedure instead of Enable.

I2C addresses should include the R/W bit

The RP.I2C driver was expecting 7-bit I2C addresses to not include the R/W bit in the LSB. This was inconsistent with the other HAL.I2C implementations and would result in incorrect I2C addressing. Now, 7-bit I2C addresses should be represented as a UInt8 with the LSB set to 0. If this breaks your code, shift your I2C address left by one bit.

Bugs fixed

Improper use of the Pack clause

The Pack clause was used to enforce the memory layout of some records.

It is important to realize that pragma Pack must not be used to specify the exact representation of a data type, but to help the compiler to improve the efficiency of the generated code. Source

The Pack clause has been replaced with Component_Size and Size clauses where necessary. Thanks to @onox for pointing this out!

Use of access PIO_Device as a type discriminant

Projects depending on pico_bsp failed gnatprove in SPARK mode as the Pico.Audio_I2S package was using not null access PIO_Device as a discriminant. PIO_Device is now tagged and Pico.Audio_I2S uses not null access PIO_Device'Class, which is valid under SPARK. gnatprove still throws many warnings about side effects in the rp2040_hal drivers, but no fatal errors.

RP.ADC.Read_Microvolts was rounding incorrectly

Read_Microvolts was using Integer arithmetic to calculate VREF / Analog_Value'Last, which does not divide evenly for common VREF values. When that value was multiplied by an ADC reading, Read_Microvolts would return lower than expected results. Read_Microvolts now uses floating point to multiply ADC counts before converting the return value to Integer.

UART Transmit and Receive did not respect Timeout

The UART driver has been modified to use RP.Timer to implement timeouts and monitor FIFO status, similar to RP.SPI and RP.I2C.

SPI Transmit was nonblocking

The SPI Transmit procedure would return immediately after the last byte was written to the FIFO, but before the FIFO became empty. This behavior breaks some drivers that depend on all bytes being clocked out before proceeding. A configuration flag for Blocking behavior has been added and defaults to True.

Pico Ada Libraries Release 0.4.0

11 June 2021 at 06:35

I’ve tagged release 0.4.0 of the rp2040_hal, pico_bsp, and pico_examples libraries for the Raspberry Pi Pico. Lots of testing and bug fixes on SPI and I2C in this release and both interfaces should be much more stable now. DMA to PIO works well and opens up a lot of possibilities for high speed signaling. Examples have been added for a rigorous SPI loopback test and a custom BSP implementation for the Adafruit Feather RP2040, which should serve as a template for running on any board that uses the RP2040. The Pimoroni Audio Pack example has been updated to use the ROM floating point library to generate a sampled sine output.

rp2040_hal 0.4.0

New features

DMA driver

The RP.DMA package allows out of band copies between a source and target System.Address and may be triggered by a variety of events. The PIO and SPI drivers have been tested with DMA and have new functions that return their FIFO addresses.

I/O Schmitt triggers

The RP.GPIO.Configure procedure now takes optional Schmitt and Slew_Fast boolean parameters that control the behavior of I/O pads. The RP2040 documentation recommends enabling the Schmitt trigger for I2C operation.

RP.ROM.Floating_Point

The ROM floating point library is now exposed in the RP.ROM.Floating_Point package. GNAT will use gcc’s soft float implementation by default, but you may call the optimized versions in the ROM directly. The Ravenscar runtimes will replace the gcc functions with these ROM calls automatically.

I2C and SPI Timeouts

Previously, the I2C and SPI drivers did not use the Timeout argument. They now use RP.Timer to implement a timeout for all blocking operations and set Status to Err_Timeout if it expires before the blocking operation completes. The I2C peripheral may require a reset after a timeout as the bus may be in an unknown state.

SPI FIFO status is exposed with Transmit_Status and Receive_Status

You can use these functions to determine if the Transmit or Receive procedures would block. See the new spi_loopback example.

Breaking changes

PWM Set_Duty_Cycle and Set_Invert no longer use PWM_Point

These procedures have changed to take a PWM_Slice as the first argument to make them more consistent with the rest of the driver. These procedures now set both channels of a slice nearly simultaneously.

PWM Initialize must be called before any other PWM configuration

This procedure was added to fix the corruption bug discussed below.

SPI.Enable is replaced with SPI.Configure

The Configure procedure takes a SPI_Configuration record as an argument for easy static configuration.

Bugs fixed

PWM configuration is corrupted after power cycle

RP.PWM.Enable is called after configuring a PWM slice to enable it. This procedure was incorrectly resetting the PWM peripheral before enabling the slice. RP.PWM.Initialize now performs the reset and all peripheral resets have been moved to RP.Reset to avoid this mistake in the future.

PWM dividers can have a value of zero

The documentation is unclear on what this means, but my testing shows that it acts like a divider of 1, which outputs the clk_sys frequency.

Fast I2C writes would result in dropped bytes

The RP.I2C_Master driver has been modified to wait for the TX FIFO to be empty before writing a byte. This effectively reduces the FIFO depth to 1 byte. This is the same behavior as the upstream SDK.

Known issues

I2C clock is slower than expected

In 400 KHz (fast mode) operation, the I2C master generates SCL at approximately 380 KHz. I believe this is due to clock stretching caused by the new TX FIFO blocking behavior. The upstream SDK has the same behavior. According to the I2C specification, a fast mode clock may be up to 400 KHz, but specifies no minimum frequency. It may be possible to workaround this by using DMA to write to the I2C FIFO, but this is untested.

Making Noise with Ada

2 April 2021 at 22:00

I’m building an audio synthesizer with Ada on the Raspberry Pi Pico’s RP2040 chip. See my earlier posts about board bring up. This chip has 256 KB of memory, no floating point unit, and no DAC or I2S peripheral. But, it does have some very flexible I/O state machines and a well optimized soft float library in ROM.

There’s a PIO program in pico-extras called audio_i2s.pio that handles the timing sensitive clocking and data bits of I2S. All I need to do is push 16-bit two’s complement signed integers into the PIO FIFO and the state machine handles the rest!

The PIO program includes a block of C code that calls pico-sdk to configure the state machine’s pin muxing and jump to the program’s entry point. I stumbled a bit trying to rewrite this in Ada, but things went much smoother after I refactored my RP.PIO driver to more closely match pico-sdk’s interfaces. This code populates a Configuration record with a bunch of settings, then applies them to the PIO both by setting registers directly and executing a few instructions on the state machine.

pico-audio_i2s.adb

   procedure Program_Init
      (This   : in out I2S_Device;
       Offset : PIO_Address)
   is
   begin
      This.Config := Default_SM_Config;
      Set_Out_Pins (This.Config, This.Data.Pin, 1);
      Set_Sideset_Pins (This.Config, This.BCLK.Pin);
      Set_Sideset (This.Config, 2, False, False);
      Set_Out_Shift (This.Config, False, True, 32);
      Set_Wrap (This.Config,
          Wrap        => Offset + Pico.Audio_I2S_PIO.Audio_I2s_Wrap,
          Wrap_Target => Offset + Pico.Audio_I2S_PIO.Audio_I2s_Wrap_Target);

      Set_Config (This.PIO.all, This.SM, This.Config);
      SM_Initialize (This.PIO.all, This.SM, Offset, This.Config);

      Set_Pin_Direction (This.PIO.all, This.SM, This.Data.Pin, Output);
      Set_Pin_Direction (This.PIO.all, This.SM, This.BCLK.Pin, Output);
      Set_Pin_Direction (This.PIO.all, This.SM, This.LRCLK.Pin, Output);

      Execute (This.PIO.all, This.SM, PIO_Instruction (Offset + Pico.Audio_I2S_PIO.Offset_entry_point));
   end Program_Init;

The rest of the clock and GPIO configuration for the PIO is pretty straightforward and I was able to get things up and running pretty easily… Except for my Pico’s broken internal pull down resistor, which took me a while to debug and ended with a silly looking 0603 resistor pulling the I2S data pin to GND.

Pico pull down resistor fix

The RP.PIO driver only supports blocking writes to the FIFO, which wastes a lot of cycles that could be used to generate more interesting audio. I had never implemented a DMA driver before and was expecting a challenge, but it was suprisingly simple! Load some configuration registers, set a source and destination address and buffer size, and pull the trigger. The RP2040’s DMA channels have a CTRL register that is aliased four times at different memory addresses and the triggering behavior is slightly different depending on which one you write to. This was a little difficult to understand from the docs, but I eventually figured it out.

rp-dma.adb

   procedure Configure
      (Channel : DMA_Channel_Id;
       Config  : DMA_Configuration)
   is
   begin
      DMA_Periph.CH (Channel).AL1_CTRL :=
         (EN            => True,
          HIGH_PRIORITY => Config.High_Priority,
          DATA_SIZE     => Config.Data_Size,
          INCR_READ     => Config.Increment_Read,
          INCR_WRITE    => Config.Increment_Write,
          RING_SIZE     => Config.Ring_Size,
          RING_SEL      => Config.Ring_Wrap,
          CHAIN_TO      => Config.Chain_To,
          TREQ_SEL      => Config.Trigger,
          IRQ_QUIET     => Config.Quiet,
          BSWAP         => Config.Byte_Swap,
          SNIFF_EN      => Config.Sniff,
          others        => <>);
   end Configure;

   procedure Start
      (Channel  : DMA_Channel_Id;
       From, To : System.Address;
       Count    : HAL.UInt32)
   is
   begin
      DMA_Periph.CH (Channel).READ_ADDR := From;
      DMA_Periph.CH (Channel).WRITE_ADDR := To;
      DMA_Periph.CH (Channel).AL1_TRANS_COUNT_TRIG := Count;
   end Start;

I then rewrote the Pico.Audio_I2S.Transmit method to use DMA.

pico-audio_i2s.adb

   overriding
   procedure Transmit
      (This : in out I2S_Device;
       Data : HAL.Audio.Audio_Buffer)
   is
      Count : HAL.UInt32 := Data'Length;
   begin
      --  Wait for previous DMA transfer to finish before modifying the buffer.
      while Busy (This.DMA_Channel) loop
         null;
      end loop;

      This.Buffer (1 .. Data'Length) := Data;

      if This.Channels > 1 then
         Count := Data'Length / 2;
      end if;

      RP.DMA.Start
         (Channel => This.DMA_Channel,
          From    => This.Buffer'Address,
          To      => TX_FIFO_Address (This.PIO.all, This.SM),
          Count   => Count);
   end Transmit;

HAL.Audio.Audio_Buffer is an array of 16-bit integers. If stereo audio is used, the samples are interleaved. The PIO FIFO buffer is 32 bits wide and the PIO program expects two 16-bit samples in each FIFO write, one for each channel. For mono audio, the top 16 bits are zeroed. The DMA channel is configured for 16 or 32 bit writes depending on This.Channels. As long as Data is 32 bit aligned, this works out perfectly.

After I got DMA working, I spent a few days on audio synthesis. I’d never written any software to generate audio before, so I had a bit of domain knowledge to catch up on but now that I understand it, it’s pretty straightforward. The only real complication is the need to call the RP2040’s ROM floating point routines if performance or code size is a concern. Daniel King already did most of the hard work in making those library symbols available in Ada and my RP.ROM.Floating_Point mostly follows the same pattern.

Neither bb-runtimes nor rp2040_hal override gcc’s trigonometry functions (sinf, cosf, tanf, etc) with the ROM’s implementations as the ROM’s floating point library isn’t strictly compatible with what gcc expects. It would likely work for our use case, but I don’t want to make that decision for other people. Instead, if you need the speed of the ROM functions, you can call them directly. Note that this is different from pico-sdk’s behavior, which uses the ROM functions by default.

Now I have a working wavetable oscillator, ADSR envelope filter, and IIR low pass filter on the Pico. I’m starting to think about what kind of user interface I want on this synthesizer and think it would be neat to use the Pimoroni RGB Keypad as a sort of crossbar selector for connecting four oscillators to four filters. Unfortunately, the way the Pico Audio Pack is designed, I can’t have it plugged into a Pico at the same time as the keypad, so I’m probably going to need to design a PCB to get it all wired up.

The PCM5100A I2S DAC that the audio pack uses looks to be in short supply at the moment, but its older sibling, PCM1754 is cheap and widely available. The most significant difference between the two is that the PCM1754 needs a MCLK signal running at a multiple of BCLK, whereas the PCM5100A generates MCLK internally by doing clock recovery from BCLK with a PLL. I think the easiest way to generate MCLK from the RP2040 would be to run a second PIO state machine at the higher clock speed and start it in sync with the I2S state machine. The MCLK PIO program should be very simple, just toggling MCLK on each cycle.

Hopefully next month I’ll have some fun sounds and pretty PCB layouts to show you!

Should I learn Ada for embedded development?

18 March 2021 at 01:21

Originally posted in response to this thread on Reddit.

I started learning Ada a few years ago and have been using it for the type of hobby projects where most people would reach for an Arduino or Teensy compatible thing. Like you, I was also seeking a safer alternative to C.

Learning Ada is not easy. It’s a big language and the reference manual uses quite a bit of vocabulary that’s specific to Ada, making it even harder for a beginner to comprehend. There are some excellent textbooks, but it’s 2021 and most people learn by Googling things, not thumbing paper. Ada resources tend to be heavily weighted towards explaining with words rather than examples, which is again complicated by the vocabulary. Personally, I feel like I learned the most when I did the Advent of Code 2019 and 2020 puzzles in Ada. There’s just no substitute for bashing your head against lots of different problems until it makes sense.

If you’ve ever skimmed any of the popular C coding standards, you’ll find a lot of rules that boil down to “allocate on the stack and avoid pointers”. When you do need a pointer, Ada has access types, which are fat pointers that associate a type and size with the underlying pointer and make it much harder to do unsafe things by accident. One of my favorite things is the concept of a not null access type, which is a pointer that must be assigned a value at instantiation and can never be assigned null. This effectively makes null pointer exceptions impossible.

“Safe” code is an overloaded term that means different things depending on what industry you work in. There is a subset of the Ada language called SPARK that allows you to specify enough constraints to be able to generate formal proofs of certain properties of your implementation. If you’re building a machine that can kill people unintentionally, your code is probably subject to a standard like DO-178C or ISO 26262 that requires this level of verification. These days, most of those systems get modeled and verified in a program like Matlab which generates C or C++ code that conforms to the model. Having a toolchain that can verify that the model’s constraints still hold for the actual implementation code is where Ada/SPARK shines.

If you’re just trying to avoid buffer overflows, Ada makes it difficult to do dumb things. In most cases, your code won’t compile if you write past the bounds of an array. Worst case, it’ll raise an exception at runtime. If you really believe you know better than the compiler, you can disable the runtime checks and use an Unchecked_Conversion to change the size of the array. But if you’re reaching for these switches, you probably need to rethink your decisions.

Record types are like C structs, with a very rich syntax for defining the memory layout of the struct members. These “record representation clauses” are probably the most useful construct for embedded programming as you can define bit fields in registers and buffers without ever needing to shift and mask bits, eliminating a large class of potential bugs.

Ada has namespaces, generics, objects, interfaces, polymorphism, collections, and iterators, just like you’d find in C++ or Java, but with more verbose syntax. “Annex A” of the language spec is effectively the standard library and is quite comprehensive. The language predates unicode, so it has the same issues as C conflating character encoding with size. There are several libraries that add new unicode string types, but the community hasn’t centered around a single implementation yet.

There are many arguments against Ada that have been discussed ad nauseum for the last 20 years. One of the most persistent is that Ada compilers are expensive. GCC has had a GPL licensed Ada frontend called GNAT since the mid-90s. A company called AdaCore contributes the majority of patches to GNAT and sells a commercial version that provides better tooling for people that need to certify their code. Most applications will be just fine using the open source GPL licensed GNAT. It’s already in most Linux distros, on Debian you can apt-get install gnat.

“It’s hard to hire Ada programmers.” This is true, but in my opinion points to a larger issue with organizations wanting to hire a candidate with every conceivable skill they will ever need, rather than investing time into training and education. Plenty of people would be happy to learn a new programming language given the opportunity. That said, there are several Ada developers lurking on reddit, freenode, and github that would be delighted to take a job writing Ada full time.

I know I didn’t directly answer your question about Ada in embedded contexts specifically, but that’s because I believe that you can write good embedded software in any language. The development process is more important than the language.

The Department of Defense put a bunch of experts in a room together to come up with requirements for a systems language. They iterated on these requirements a few times, then called for proposals for a language to fulfil those requirements. Three languages were proposed, discussed, and voted upon. The winning proposal was renamed to Ada. This same requirements -> proposals -> deliberation -> implementation flow is more familiar today as the waterfall process or V model. Process drives good software, the language is just a tool.

Using drivers

3 March 2021 at 02:38

Twiddling bits in registers doesn’t make the most intuitive, readable, or portable code. This is why we write drivers. I’ve created three Alire packages, rp2040_hal, pico_bsp, and pico_examples. rp2040_hal contains all of the drivers for the chip’s internal peripherals, pico_bsp contains some details about the Pico board and drivers for the Pimoroni Pico addons, and pico_examples contains, you guessed it, example code. At the moment, pico_bsp cannot be used with the Ravenscar runtime without modification, so I’ll ignore that and focus on rp2040_hal for right now. The examples repository contains lots of code that uses the pico_bsp if you’d like to see how that works.

Now that I have drivers, I’ve removed all of the interfaces generated from SVD from our project. These still live in the rp2040_hal package but as long as the drivers implement all of the right interfaces, we shouldn’t need them. If you find something you can’t do with the HAL drivers that seems important, please send a pull request to the rp2040_hal repo. I’ve refrained from tagging a 1.0 release because I’m still not satisfied with all of the interfaces and retain the right to break the API.

with Ada.Real_Time; use Ada.Real_Time;
with RP.GPIO; use RP.GPIO;

procedure Main is
    LED        : GPIO_Point := (Pin => 25);
    Next_Blink : Time := Clock;
begin
    LED.Configure (Output);
    loop
       LED.Toggle;
       delay until Next_Blink;
       Next_Blink := Next_Blink + Seconds (1);
    end loop;
end Main;

You can see that the Main procedure is much more compact and readable now. The PADS_BANK and IO_BANK stuff is wrapped up in a lovely Configure interface and the GPIO is abstracted into an object with a very convenient Toggle method.

Building the code is done using Alire now, rather than calling gprbuild directly. Alire is analogous to Rust’s Cargo or Python’s pip. Alire keeps track of all of the dependencies and can pull in new ones with the alr with command.

git clone https://github.com/JeremyGrosser/rp2040_hal
cd 04-hal-blink
alr build

Source code

❌
❌