As I’ve discussed before, I’ve started a project to implement support for the Cypress PSoC 4 series, so they can be used without PSoC Creator, which only runs on Microsoft Windows. I am doing this so I can develop for the PSoC 4 on a linux machine using only an open source toolchain. As the libopencm3 project is a project that already targets several Cortex-M microcontrollers (note: even though it’s called libopencm3, it support M0 as well)

Libopencm3 is LGPL licensed. However, in an embedded enviorement, it would effectivly be GPL, as on a microcontroller, everything is usually statically linked. This might make libopencm3 unsuitable for some purposes. However, this is not a concern at this moment. Even when I contribute to this project, I am still the copytight owner, so I can create a version of my drivers that does not integrate with libopencm3 under a MIT license. When I do so, it would be a major rewrite anyways, as if I release such, I would use a different design and code style. But that’s something to discuss at a later point in time.

Let’s focus on the task at hand. Implementing PSoC 4 support. I own two prototype boards, the CY8CKIT-049 featuring a CY8C4245AXI-483, and a CY8CKIT-043 featuring a CY8C4247-M485. These will be my main targets for this development, but I will add support for other family with shared peripherals, but note running on those targets will remain untested for the time being.

Now, let’s have a look at the targets. The CY8C4245AXI-483 is a member of the “4100/4200 family”. The CY8C4247-M485 is a member of the “4100M/4200M family”. I’m not entirely sure how Cypress’ naming convention works, but from what I can tell, it is something like this

CY8C 4 [0,1,2,7,A] [2,4] [3,4,5,6,7,8] [“”,M,L,S,BL,…] nnn
PSoC4 Speed Memory Features
Series, Family Family

Cypress provides Technical Reference Manuals (TRM) for Family, where 4100-x and 4200-x are considered the same family, but 4000-x is considered a different family.

The “Hello World” program for a microcontroller is traditionally a blinking LED. Now, in order to make a LED blink, I need to implement GPIO. Let’s head over to the TRMs for the families I have at hand. There are Architecture TRMs and Register TRMs. To implement GPIO, I’ll look at the Register TRMs.

When we look at the GPIO registers, we notice they’re the same. So, we can use the same code for both families.

So, let’s head over to libopencm3. How to add a new microcontroller to libopencm3? We start at the ld/devices.data file. This is where the supported devices are defined, it defines the amount of ROM and RAM, as well as the core. The file is based on string matches.

# PSoC 4M Series
cy8c4??5???-m*			psoc4m	ROM=32k RAM=4k
cy8c4??6???-m*			psoc4m	ROM=64k RAM=8k
cy8c4??7???-m*			psoc4m	ROM=128k RAM=16k

# PSoc 4100/4200 Series
# We need to do this series last, when no other rules matched
cy8c4[12]?4* 			psoc41 ROM=16K RAM=4K 
cy8c4[12]?5* 			psoc41 ROM=32K RAM=4K

psoc4m	 	END ROM_OFF=0x00000000 RAM_OFF=0x20000000 CPU=cortex-m0 FPU=soft
psoc41	 	END ROM_OFF=0x00000000 RAM_OFF=0x20000000 CPU=cortex-m0 FPU=soft

Here we match parts of the part number to the family and memory amounts. We put the rules for the 4100m/4100m series first, as the family identifier for the 4100/4200 series is empty. Finally, for each family, we set the ROM and RAM offsets and the core.

We have now configured the linker to link in the, yet to be created, library at the correct memory location and with the correct linker flags. We need to add some code to make our library work. We need to define the interrupts for our microcontrollers.

For this, we create the files /include/libopencm3/psoc/41/irq.json and /include/libopencm3/psoc/4m/irq.json. As suggested by the file name, these files are in the JSON file format. To see what interrupts are implemented on a certain family, we look at the PSoCĀ® 4 Interrupts document. I must say, the documentation for the Cypress microcontrollers is spread amongst a number of documents. This document covers most but not all families of PSoC4. There is more to say about the documentation later. The content of this file is rather trivial, just list them all. So I’ll just show a snipset.

{
    "irqs": [
		"dsi0_gpio0",
		"dsi1_gpio1",

Now we have this file in place, the build system will generate interrupt handlers based on this list. The thing we have to do next, is to make it include these files in the library it generates. We need to do this for both header and code files. For this we adjust two files
include/libopencm3/dispatch/nvic.h

#elif defined(PSOC41)
#	include "../psoc/41/vector_nvic.c"
#elif  defined(PSOC4M)
#	include"../psoc/4m/vector_nvic.c"

and lib/dispatch/vector_nvic.c

#elif defined(PSOC41)
#	include <libopencm3/psoc/41/nvic.h>
#elif  defined(PSOC4M)
#	include <libopencm3/psoc/4m/nvic.h>

Now we have configured this, we can head over to the actual implementation. We start with defining a memory map for each device. For this purpose we create two files
include/libopencm3/psoc/41/memorymap.h and include/libopencm3/psoc/4m/memorymap.h.

#define FLASH_BASE			(0x00000000U)
#define PERIPH_BASE			(0x40000000U)

#define GPIO_BASE			(PERIPH_BASE + 0x040000)

#define GPIO_PORT_0_BASE		(GPIO_BASE + 0x000)
#define GPIO_PORT_1_BASE		(GPIO_BASE + 0x100)
#define GPIO_PORT_2_BASE		(GPIO_BASE + 0x200)
#define GPIO_PORT_3_BASE		(GPIO_BASE + 0x300)
#define GPIO_PORT_4_BASE		(GPIO_BASE + 0x400)

At this point, the content of both files is still the same, however, other peripherals differ between the families and when implementing them, the files will diverge.

As the GPIO peripherals are the same on both families, we create a file include/libopencm3/psoc/common/gpio.h where we define the registers of the GPIO peripheral

#include <libopencm3/cm3/common.h>

#define GPIO_DR(port)			MMIO32((port) + 0x00)
#define GPIO_PS(port)			MMIO32((port) + 0x04)
#define GPIO_PC(port)			MMIO32((port) + 0x08)
#define GPIO_INTCFG(port)		MMIO32((port) + 0x0C)
#define GPIO_INTSTAT(port)		MMIO32((port) + 0x10)
#define GPIO_PC2(port)			MMIO32((port) + 0x18)

Here we include the libopencm3/cm3/common.h which defines the MMIO32 macro, which dereferences the pointer to the register. Now we have this, we can say GPIO_PC(GPIO_PORT_0_BASE) = value;

But of course, that is not a convinient way to address the GPIO, therefore I declare some more functions

void gpio_set_mode(uint32_t gpioport, uint8_t dm, uint8_t gpios);
void gpio_set(uint32_t gpioport, uint8_t gpios);
void gpio_clear(uint32_t gpioport, uint8_t gpios);
uint8_t gpio_get(uint32_t gpioport, uint8_t gpios);
void gpio_toggle(uint32_t gpioport, uint8_t gpios);
uint8_t gpio_port_read(uint32_t gpioport);
void gpio_port_write(uint32_t gpioport, uint8_t data);

The implementation of these functions is in lib/psoc/common/gpio.c. The implementation is trivial. set writes the given value to GPIO_DR, etc.

Then, just copy over a makefile from an existing target, adjust the library name, replace the files to build with gpio.c, and the target to the main makefile, and we’re done!

Now, we can take the libopencm3-template, check out our branch in the submodule, set the target part number, and we can build our blinky program.

I hope to have covered this is enough details to show how to add a new target to libopencm3. I would like to share my working git repo https://github.com/a-v-s/libopencm3/tree/psoc4.