PSoC4 Boot Sequence (Part 5) – Initializing the PSoC with initialize_psoc()

Summary – Initializing the PSoC

Well, I have finally reached the end of the booting the PSoC4 series.  Actually I am contemplating one more article after this one, but for now I am done.  In this article I talk about the function “initialize_psoc()” which is responsible for initializing the PSoC; it turns the PSoC into a PSoC.  The function does two basic things:

  • It moves the exception table out of the flash (at location 0x0000) and puts it into the SRAM
  • It calls cyfitter_cfg() which calls a bunch of functions to setup all of the configuration registers inside of the PSoC

Here is the actual function:

Move the Exception Table

In a previous article I talked about setting up the exception table and programming it into the flash.  This leaves me with a previously unstated question: How do I change the address of an interrupt vector service routine? Does it have to remain static and known a priori when you write the firmware?  In the Cortex-M0 architecture the answer to that question is fascinating (in my opinion).  If you use the ARM CM0 “straight out of the box” the answer is you can’t.  However, in the PSoC implementation of the CM0’s our system architect created a register to allow you to move the vector table.

Before I explain how this happens I would like to give a nod to the Cypress “CPUSS Architect”.  He is a former NXP engineer from the Netherlands and is one of the most amazing individual talents I have ever known.  He understands how to balance the white-tower-purity of the CTO office and the overall system architecture with the roll-up-your-sleeves realities of making chips.

Anyway.  Maybe it is a common thing to do with the CM0 (I actually haven’t looked at other people’s implementations) but the register “CPUSS_CONFIG” changes the address logic in the CM0 to read from two different places when the vector table addresses are issues by the CPU.  What this means is when the bit “CPUSS_VECS_IN_RAM” is 0, the addresses 0x0 are read from the flash, and when the bit is set to 1, the address 0x0 are read from 0x20000000 (which is the RAM).

Here is a clip from the PSoC Technical Reference Manual:

PSoC4 TRM

Once you see this, it is easy to understand lines 496–>517.  They basically:

  • Set the CPUSS_VECS_IN_RAM bit to 0
  • Then copy the vector table from the flash to the RAM

Finally on lines 537–>542 it sets the bit to 1 so that the vectors are read from the flash.

The only other trick here is that the CM0+ (and CM3/CM4) have a register called “VTOR” which allows you the accomplish exactly the same thing with out having to modify the address logic in the core.

Initializing the PSoC: Call cyfitter_cfg()

As part of building a PSoC project, PSoC Creator runs a “fitter”.  The fitter is responsible for

  1. Configuring the Digital Signal Interconnect  (DSI) matrix
  2. Configuring the UDBs
  3. Configuring the Analog Routing
  4. Configuring the Clocks
  5. Assigning the blocks of the PSoC to pins and components

All of the fitter tasks get turned into C-code which are either #defines (in the case of the components) or actual c-functions and data (as in the case of 1-4).  The last step in the function “start_c()” is calling the “cyfitter_cfg()” function”.  The “cyfitter_cfg()” will grow and shrink depending on how you configure the UDBs and the DSI.  For example, the version below has a blank schematic.  In it you can see that:

  • Lines 247-251 write 0s to the configuration registers… basically turning everything off
  • Lines 254-265 enable the UDBs and routing
  • Line 274 turns on the clocks
  • Line 277 turns on the analog

However, if you had a more complicated schematic, like this one which uses a UDB and the DSI to implement a LUT

LUT Example

Then you will end up with a block of code that configures the DSI and UDB registers required to implement the LUT and route it to the pins.  You can see on lines 274-308 there is a table of register values which are copied into the architectural registers by the function call on line 333.  I started looking through the meaning of all of these register values in the TRM but realized that it didn’t matter.  PSoC Creator does a perfectly good job of setting up the UDB and getting it routed to the right place.

The “ClockSetup()” function does exactly what its name says.  It configures all of the clocks (and starts them) based on how you setup things in the Clocks Tab of the DWR.

The AnalogSetDefault() function does exactly what its name says…. with a blank design is a whole lot of nothing.  As you add things that use the analog resources on the chip, say for instance the OpAmps, it configures the registers require to implement those features in the chip.

OK, I am done with this set of articles.  I hope that they were useful.  It has certainly been an adventure digging through all of this code.

Article Description
PSoC4 Boot Sequence (Part 1) - Debugging to the Reset Vector An introduction to the PSoC4 Boot Sequence
PSoC4 Boot Sequence (Part 2) - Creating the Exception Table using the Linker Building the exception vector table
PSoC4 Boot Sequence (Part 3) - Preinitializing Variables before main() Initializing BSS and Data
PSoC4 Boot Sequence (Part 4) - Linker trickery with __attribute_((constructor(101))) Running initialize_psoc()
PSoC4 Boot Sequence (Part 5) - Initialize PSoC