The Peripheral Driver Library v3, for PSoC 6, is more than a driver library. It’s a complete software development kit. I talked about what the PDL is in Software Enablement for PSoC 6 – What to Expect. In this article I want to give you some insight into the very high level design of the PDL drivers. I’ve seen a lot of code in my time, and the PDL is really well designed.
Although this article is about the PDL, just as a teaser, in a coming post or two I'll talk about how PSoC Creator and PDL are integrated. They work together seamlessly. But I get ahead of myself. Back to PSoC 6 and PDL.
The underlying hardware capabilities of the PSoC 6 present unique challenges. For one thing (and it’s a big thing), PSoC 6 is a dual-core architecture, with both a Cortex® M4 and a Cortex M0+ device. Among the many complications you might have to deal with are:
The PDL makes a lot of the complexity go away, from a software perspective. We’re doing our best to make this as easy as possible.
The first big design choice is that the cores use the same register and memory maps for all peripherals. The PDL source code takes advantage of that choice. With very few exceptions, either core can use any peripheral driver. For example, if you want to implement a real-time clock in your design, you create a single RTC driver, not separate CM4 and CM0+ RTC drivers. Write once, use for either core.
It gets better.
The set of available peripherals varies per device. In some cases there are multiple instances of the same peripheral; for example, there are multiple Serial Communication Blocks (SCB). On top of that, each peripheral instance may itself operate on multiple instances of user data.
Imagine if you will (because this is in fact how it works), a platform where you create a peripheral driver used by either or both cores. Where you configure and customize each peripheral. Where you have multiple instances of peripherals, each configured differently. Where each of those peripherals can operate on multiple instances of data. Imagine multiple SCBs, each operating on multiple data buffers, with status and indices into each buffer. All of this happening in a dual-core environment with all the corresponding issues involving potential resource and timing conflicts.
There are many, many ways to make this complicated. The PDL is designed to make it easy. The PDL implements a simple, consistent design based on three fundamental concepts.
Base Hardware Address: At the hardware level, peripheral features and behavior are controlled by registers. Each peripheral instance has its own base hardware address that points to the registers for that instance. The PDL uses this base address to access the necessary registers. Constants for each base address are defined in device-specific header files. It’s easy to find what you need.
Configuration Structure: Each peripheral instance is configurable. You modify values in a PDL configuration structure to change behavior to fit your requirements. When you initialize, enable and use a peripheral, the PDL manages register access using the base hardware address.
Context Structure: A single peripheral instance (however configured) may work on multiple instances of data. For example, an SCB configured as a UART operates on data buffers, and maintains status information about those buffers. The peripheral does not allocate this memory. The PDL defines the necessary data structure on which the peripheral operates. You allocate the structure in memory and provide it to the peripheral.
Many PDL API function calls require a parameter representing one or more of these three concepts. The precise details vary per peripheral, but the design is consistent throughout the PDL. When you look at the actual wording of function prototypes, you will discover that not only is the design consistent, so are the parameter names. The base hardware address is always called base. The configuration structure is always called config.
It’s a little thing, name consistency. Someone new to PDL 3.0 may not even notice. But it has a big impact. Once you get it, you’ve got it! There is no need to figure out, ok, so how is this driver put together? Of course each driver is functionally different, with its own API. But each uses the same parameters for the same things. And that will make your life as a developer a lot easier.