Last time I blogged (is that really a word? what a strange world this is) I wrote about the ModusToolbox device configurator. We set up a pair of pins, one called "LED" and the other "SW" (because, it turns out that I am too lazy to write "Switch" or "Button"). Anyway, I mentioned that the configurator generates the setup code for the pins and that I would explain how that works. So here goes...

At the top of main() there is a function call to "init_cycfg_all()" and that's where the pins get initialized. That function lives in a file called cycfg.c which you can find in the GeneratedSource folder.

You can see here that this folder is actually a link (see the tiny arrow on the folder icon) to the configuration project. That project is the one that contains the design.modus file, which contains all the selections and information from the configurators, and the source files generated when I saved my configuration.

Also in this folder are the configuration files for connectivity, which means the signals that are connected inside the PSoC, platform, which is the power, debug and clock setup code, and pins, which we are most interested in.


First, here is the meat of cycfg.c, showing the init_cycfg_all() function.


#include "cycfg.h"

void init_cycfg_all()


I am sure you'll be flabbergasted to learn that init_cycfg_pins() is in cycfg_pins.c - this is pretty simple stuff (by design - so you can dig in and really understand what the tools are doing for you). What does that function look like? Well, this...


void init_cycfg_pins(void)
    Cy_GPIO_Pin_Init(SW_PORT, SW_PIN, &SW_config);
    Cy_GPIO_Pin_Init(LED_PORT, LED_PIN, &LED_config);
    Cy_GPIO_Pin_Init(SWO_PORT, SWO_PIN, &SWO_config);
    Cy_GPIO_Pin_Init(SWDIO_PORT, SWDIO_PIN, &SWDIO_config);
    Cy_GPIO_Pin_Init(SWCLK_PORT, SWCLK_PIN, &SWCLK_config);


The pins are set up with a call to a PDL function that accepts the port address, pin number within the port, and a pointer to a configuration struct. There are five calls - one for LED, one for SW, and three for debugging - SWCLK, SWDIO, and SWO. It's all simple enough, but where do those arguments come from? The ports and pins are defines and they come from cycfg_pins.h. Here is a snippet for SW, the input pin.


#define SW_PIN 4U
#define SW_NUM 4U
#ifndef ioss_0_port_0_pin_4_HSIOM
#define ioss_0_port_0_pin_4_HSIOM HSIOM_SEL_GPIO
#define SW_HSIOM ioss_0_port_0_pin_4_HSIOM
#define SW_IRQ ioss_interrupts_gpio_0_IRQn



I'll go through all of those defines in order. SW_PORT is the crucial macro, it maps to a pointer to the port 0 hardware:

#define GPIO_PRT0                               ((GPIO_PRT_Type*) &GPIO->PRT[0])

I am glad that's generated for me!

Next there is SW_PIN, the pin number. SW_NUM is always the same as the PIN macro - the "NUM" style is used for lots of peripherals (which I'll show you next time) and so we generate two macros, in case you are lazy like me and always want to use one style. it's your choice.

After that we have the drive mode - PULLUP creates an active low button. DRIVESTATE is the initial pin value (so pin reads get the right result before the button is pressed). Then there are some definitions of internal connections, which are unused for software-driven GPIOs. Lastly, there is the IRQ definition, which is useful when you trigger interrupts from the pin and REALLY useful when you trigger interrupts on multiple pins in the same port (another subject for another day).

All that explains where the first two arguments come from. The last one is a C variable and it is back in the cycfg_pins.c file. Here is the configuration struct for SW.


const cy_stc_gpio_pin_config_t SW_config =
    .outVal = 1,
    .driveMode = CY_GPIO_DM_PULLUP,
    .hsiom = SW_HSIOM,
    .intEdge = CY_GPIO_INTR_DISABLE,
    .intMask = 0UL,
    .vtrip = CY_GPIO_VTRIP_CMOS,
    .slewRate = CY_GPIO_SLEW_FAST,
    .driveSel = CY_GPIO_DRIVE_FULL,
    .vregEn = 0UL,
    .ibufMode = 0UL,
    .vtripSel = 0UL,
    .vrefSel = 0UL,
    .vohSel = 0UL,


I will not bore you explaining all of that but a few things are intreesting. Firstly, the struct is const. That means it is stored in flash and not copied into RAM on startup. This saves memory but, if you want to change the struct at run-time, you can turn off the const from the device configurator, by un-checking "Store Config in Flash". Note that this option saves RAM, but not flash, because the INITDATA always has to be kept in non-volatile memory.



Back in the C code you can see that the pin initialization, like the PULLUP and interrupt settings, is all contained in the struct. The end result of all this code is that device initialization is an automated function with PSoC in ModusToolbox but, if you need to over-ride these choices for any reason, it is really easy to make a copy of the generated code, edit it and simply call your new function and not the generated one!

Next time, I'll take you a little deeper into the configurators and set up some more interesting peripherals, and then, if all goes well, I'll introduce you to the wonders of the CapSense configurator and its pal, the CapSense tuner!