Dec 6, 2015

Meet the Cypress PSoC 4 BLE

WARNING: unfinished blog entry

About half year ago, I went to a seminar hosted by Arrow, where I received this nice development board, and interacted with the Bluetooth feature of the board.
The red baseboard is the generic development motherboard with peripherals (e.g. capacitive touch sensor array) and USB based emulator connection to the PC, and the smaller red daughter board is the CY8C427LQI0BL483 eval board.  The black board is the "BLE dongle" that runs the BLE host emulation FW, for the PC to act as the counter part (usually through the CySmart SW) of the red eval board, like this picture:

Until now, my only ARM experience has been on TI Stellaris (now called Tiva) which is ARM Cortex-M4 based, and  the Zedboard, which packs a dual core ARM Cortex-A9.  But the BLE Pioneer Kit runs on Cortex-M0, so I began to check out the low level details of the processor, by reading through my favorite real-time OS (of a sort): QP, which has an example port (of QP version 4.5.04) to ARM Cortex M0 on the LPCXpresso-1114 board.  In this blog entry, I port QP to the CY8CKIT-042-BLE, and implement Bluetooth "Find Me" device in QP.

Getting started with the PSoC creator--on virtualbox

Setting up PSoC Creator on virtualbox

Similar to the Xilinx Zynq, PSoC is is programmable logic device with a CPU on the same die.  Being configurable means that the SW runs on top of customized HW--even if it is one that has been stable for some time.  So the PSoC Creator's is like the Xilinx Vivado IDE: it creates/configures the HW.  It is a convenient IDE that runs only on Windows, so I need to run in a virtual machine.  Installing the whole (kitchen sink) PSoC BLE toolchain is easy, but takes a long time.  The biggest problem with developing an embedded target in a virtual machine is getting the virtualbox guest to see the development board's USB endpoints.   It took me half a day to figure out the following steps (reinstalling the virtualbox several times along the way): I (the Linux user running the virtualbox) must be in the "vboxuser" and "lp" group:

sudo usermod -aG vboxuser henry
sudo usermod -aG lp henry

Then I had to RESTART the PC, before I could see in the virtualbox USB settings window the following USB devices the on the pioneer baseboard:
If the toolchain has been correctly installed, the Windows device drives will be found automatically when the Windows virtual machine boots up the next time, as you can see here:
These devices should show up in the Windows Device Manager (Start --> right click on Computer --> Manage --> Device Manager):
  • Bluetooth Radios
  • Network adapters: PAN and RFCOMM Protocol TDI
  • Ports: KitProg USB-UART
  • USB controllers: KitProg
When I plug in the "BLE dongle", I get another KitProg USB-UART device (enumerated on COM4)), which the CySmart SW can automatically find, as you can see here:

Blinking an LED

The project will drive an LED with a PWM HW, available from the Cypress Component Catalog tab --> Digital --> Functions --> TCPWM mode (I could also search for this in the textbox).  Supply the clock, and drive a digital out pin from line_n as shown below:
Change the clock frequency to 1 kHz, and then rename the output pin to Red_LED.

Optionally, I can draw the rest of the LED circuit elements on the schematic, as shown below):
These off-chip components are available in the "Off-Chip" catalog tab, and do not have any down stream consequences for the PSoC creator.  [Q: what does "include in Netlist" do?]
To constrain the output pin to an actual pin on the chip, bring up the design wide resource wizard in the project explorer.  Remember that the PWM HW is INSIDE the chip, and constrain the Red_LED pin to P2_6 (pin 43) from the drop-down selection, to satisfy the actual Pioneer base board schematic for the 3-color LED shown below:
Generate the HW through menu --> Build --> Generate Application.  In PSoC Creator 3.x there is a code analysis tool bug that causes crash.  The workaround is to remove ("replace in file" feature) the following string from ALL files:

/* [] END OF FILE */

The menu item name IS appropriate because this generates C code for the HW, similar to how Xilinx BSP generates C stub code for each peripheral.  Since I prefer writing bare metal C code, I don't like this option.  Perhaps I can turn off the code generation in the future in the build settings:

At any rate, the main() simply calls PWM_Start() and loops forever:

#include <project.h>

int main()
{
    PWM_Start();//CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */

    for(;;)
    {
        /* Place your application code here. */
    }
}

When I build and program this to the target, the red LED is linking at 2 Hz, as expected (the clock frequency is 1 kHz, and the PWM period is 500).  So what just happened?

The auto-generated code PWM_Start() is ultimately writing to the register of course.  For example, the very first code run in PWM_Start() is resetting the PWM HW's control register to the default values:

    #if (PWM__PWM_SEL == PWM_CONFIG)
        PWM_CONTROL_REG = PWM_CTRL_PWM_BASE_CONFIG;

This PWM_CONTROL_REG is defined in PWM.h, cyfitter.h, and cydevice_trm.h: PWM_CONTROL_REG -->  PWM_cy_m0s8_tcpwm_1__CTRL pointer --> CYREG_TCPWM_CNT0_CTRL --> 0x40200100u

Note that the same cydevice_trm.h which defines the CYDEV_TCPWM_BASE and all other register definitions, and is also auto-generated.  The "trm" in the header file name is of course referring to the PSoC TRMs (the technical reference manuals), which are available here, where I downloaded 3 documents:
  • PSoC 4 BLE Architecture TRM 
  • PSoC 4 BLE Registers TRM.pdf 
  • CY8C41XX, CY8C42XX Programming Specifications.pdf 
As a proof that I am reading the right manuals for the part, the PSoC 4 BLE Register TRM section 6.1.15, TCPWM_CNT1_CTRL register's address is 0x40200140.

While everything seems to work, this is not a good FW demo, because the HW (clock and PWM) does all the work.  Production FW needs system tick and multiple threads, which any real-time OS supplies.

Understanding the PSoC BSP (auto-generated code)

Just like the auto-generated BSP code for the Zynq platform, this PSoC BSP code is an excellent way to learn the FW-HW interface for a given PSoC HW, by drilling down into source in PSoC Creator IDE (Ctrl F12).  But the most direct way to get at the boot code is from the linker script, which is in the auto-generated cy_boot/cm0gcc.ld, which specifies the first instruction to execute in a program:

ENTRY(Reset)
SEARCH_DIR(.)
GROUP(-lgcc -lc -lnosys)

Like the files listed in INPUT, the libraries listed in GROUP are linked into the linker target, so the executable will be pulling some initialization routines in libc.  The linker file also specifies the stack and heap locations:

MEMORY
{
  rom (rx) : ORIGIN = 0x0, LENGTH = 131072
  ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16384
}

PROVIDE(__cy_stack = ORIGIN(ram) + LENGTH(ram));
PROVIDE(__cy_heap_end = __cy_stack - 0x0800);

In this case, half of the 16 KB RAM is given to the stack.  The reset code gets placed after the ISRs:

SECTIONS
{
  /* The bootloader location */
  .cybootloader 0x0 : { KEEP(*(.cybootloader)) } >rom
...
  .text appl_start :
  {
    CREATE_OBJECT_SYMBOLS
    PROVIDE(__cy_interrupt_vector = RomVectors);

    *(.romvectors)

    /* Make sure we pulled in an interrupt vector.  */
    ASSERT (. != __cy_interrupt_vector, "No interrupt vector");

    ASSERT (CY_APPL_ORIGIN ? (SIZEOF(.cybootloader) <= CY_APPL_ORIGIN) : 1, "Wrong image location");

    PROVIDE(__cy_reset = Reset);
    *(.text.Reset)
    /* Make sure we pulled in some reset code.  */
    ASSERT (. != __cy_reset, "No reset code");
...

As in any firmware, after PoR, PSoC 4 firmware needs to initialize many configuration registers before the main FW code can run properly; the clock and peripheral initialization is the best such example.  As explained in AN60616 PSoC ® 3 and PSoC 5LP Startup Procedure, the auto-generated cyfitter function performs a lot of such initialization.  For PSoC Creator 3.2 autogenerated code (for PSoC 4 BLE at least), that happens in initialize_psoc() function, in Cm0Start.c.  The confusing part is that the entry point of the auto-generated code for GCC toolchain is NOT initialize_psoc(), but rather Reset() vector--which is in the same file--that does NOT call initialize_psoc function explicitly, but calls Start_c(), which will in turn call the application main, as shown in the code snippets from Cm0Start.c below:

    CY_SECTION(".romvectors")

    const cyisraddress RomVectors[CY_NUM_ROM_VECTORS] =
{
    INITIAL_STACK_POINTER,   /* The initial stack pointer  0 */
    (cyisraddress)&Reset,
    &IntDefaultHandler,      /* The NMI handler            2 */
    &IntDefaultHandler,      /* The hard fault handler     3 */
};

void Reset(void)
{
#if (CYDEV_PROJ_TYPE == CYDEV_PROJ_TYPE_LOADABLE || CYDEV_PROJ_TYPE == CYDEV_PROJ_TYPE_LOADABLEANDBOOTLOADER)
        __asm volatile ("MSR msp, %0\n" : : "r" ((uint32)&__cy_stack) : "sp");
    #endif /* CYDEV_PROJ_TYPE_LOADABLE */
...


    Start_c();
}

Thus, we have a situation where the FW application seems to be called BEFORE the configuration registers are initialized--which cannot be right.  In contrast, other toolchains (like IAR) have an explicit call path to initialize_psoc() function.

__attribute__ ((constructor(101)))
void initialize_psoc(void) {
  CY_CPUSS_CONFIG_REG &= (uint32) ~CY_CPUSS_CONFIG_VECT_IN_RAM;
  for (indexInit = 0u; indexInit < CY_NUM_VECTORS; indexInit++) {
    CyRamVectors[indexInit] = (indexInit < CY_NUM_ROM_VECTORS) ?
      RomVectors[indexInit] : &IntDefaultHandler;
  }
  cyfitter_cfg(); /* Initialize configuration registers. */
  cySysNoInitDataValid = 0u;
  ...
  CY_CPUSS_CONFIG_REG |= CY_CPUSS_CONFIG_VECT_IN_RAM;
}

For the GNU toolchain case, initialize_psoc() IS being called IMPLICITLY, because initialize_psoc() has the "constructor(101)" attribute, which is explained in this link (in paraphrased summary, the function is put into the .ctor section of the text).   Start_c() (called from Reset vector, as stated earlier) will in turn call __libc_init_array(), which is supplied in libc.  Inside, it will call all static constructors--which are in the .ctor section of the text.  Therefore, initialize_psoc() will be called implicitly right before the application main() is called, as shown in this stack trace:

0 initialize_psoc() .\Generated_Source\PSoC4\Cm0Start.c 509 0x0000026A (All) 
1 __libc_init_array() ?????? ?????? 0x000006AA (All) 
2 Start_c() .\Generated_Source\PSoC4\Cm0Start.c 346 0x00000258 (All) 
3 Reset(int nbytes = <optimized out>) .\Generated_Source\PSoC4\Cm0Start.c 390 0x00000016 (All) 
...

Resuming the discussion about the initialize_psoc() action itself, note that the last line tells the PSoC chip that the vector table has now been relocated to the SRAM.

OTA (over the air) FW update

Field upgradeability of a FW is critical because it takes a long time for the SW to mature to match the sophistication of typical HW.  To replace a FW, one of course needs another FW (called bootloader) that will do that job, and on many chips (like MSP430), the bootloader is burned into ROM.  But on  PSoC, even the bootloader is on the flash, and a bootloader project is created just like a regular FW--with configurable HW and FW features.  The only difference is that a bootloader project has a top level bootloader "component" in the TopDesign.cysch (the counterpart FW to a bootloader--a bootloadable FW--has the top level bootloadable component in TopDesign.cysch) as in the snapshot of the Cypress BLE OTA example project (from the 100 projects in 100 days website):
Note that there are only a few non-auto-generated files for the project:
  • TopDesign.cysch
  • BLE_External_Memory_Bootloader.cydwr (design wide resource)
  • Header/source files
    • CustomInterface.[ch]
    • debug.[ch]
    • Encryption.[ch]
    • ExternalMemoryInterface.[ch]
    • Options.h
    • main.c
    • WriteUserSFlash.[ch] 
Options.h just has a few #defines that control the bootloader FW features, which currently seem to be the same for both the debug and release mode:

#define ENCRYPT_ENABLED         (NO)
#define DEBUG_UART_ENABLED      (NO)
#define CI_PACKET_CHECKSUM_CRC  (NO)
#define KEY_ROW_NUM             (0u)

#define ENCRYPTION_ENABLED      (ENCRYPT_ENABLED || CYDEV_BOOTLOADER_ENABLE)

I2C 2.0 (now there is v 3.0 available) component is from the component catalog, and has been configured as the master, at 1000 KHz (actual 941 KHz).  It is used to control the FMEM that happens to be on the pioneer kit board (U3), to download the new (bootloadable) FW to.  SW transmit UART (makes sense to avoid eating up the precious serial HW--since there are only 2 on this chip) component is version 1.30 (now there is v 1.40), and configured for 115200 baud rate and static pin assignment.  This component is only necessary to print debug messages to the development host serial terminal.  The I2C SCL/SDA pins and UART TX pins are constrained in the "Pins" tab of the design wider resource screen, as you can see below. 
The Clocks tab of the design wide resource shows that the I2C clock is HFCLK/2 = 16 MHz nominal, since the HFCLK ticks at 32 MHz in this design.

Interrupts tab shows there are 2 unassigned interrupts in this design: EMI_I2CM_SCB_IRQ, and BLE_bless_isr.  System tab shows that the heap and stack are 512 B and 2 KB, respectively.

A bootloader FW reset vector is similar to a normal FW studied above, except for a check before C startup:

    #if (CYDEV_BOOTLOADER_ENABLE)
        CyBtldr_CheckLaunch();
    #endif /* CYDEV_BOOTLOADER_ENABLE */
    Start_c();

CyBtldr_CheckLaunch (in Generated_Source\Bootloader\Bootloader.c) checks whether to run the application or the bootloader--only if soft reset--using the Bootloader_runtype global variable.  If hard reset or there is no loadable app, the boot loader will continue onto Start_c(), then to initialize_psoc as discussed above, and finally the application main, which starts the SW UART TX debug print library:

    #if (DEBUG_UART_ENABLED == YES)
        UART_Start();
    #endif /* (DEBUG_UART_ENABLED == YES) */

    DBG_PRINT_TEXT("\r\n");
...

FreeRTOS on PSoC 4

FreeRTOS is the most popular real-time OS--because of its feature/cost ratio.  Cypress already ported FreeRTOS 8 to PSoC 4 (pioneer kit) for both the ARM (MDK) and GNU compiler a year and a half ago.  Let's give the gcc version of the demo a whirl.  The download contains the FreeRTOS 8.0.0 source, and 2 examples for the cy8ckit pioneer board: Demo.cydsn and NewDesign.cydsn, contained in a workspace file cy9ckit-042-gcc.cywrk.  Both projects pull in the included FreeRTOS source just discussed, as you can see in the screenshot of the NewDesign project:
The header files in these folders are pulled in through the compiler include path additions:
  • ..\..\FreeRTOS\Source\include
  • ..\..\FreeRTOS\Source\portable\GCC\ARM_CM0
The linker file is similar to the blink example I just went through, except for the difference in ROM, RAM, stack, and heap sizes:
  • ROM: 128 KB vs. 32 KB for NewDesign example
  • RAM: 16 KB vs. 4 KB for NewDesign example
  • Heap: 64 B vs. 0 B for NewDesign example
  • Stack: 2 KB vs. 120 B for NewDesign example
It seems strange that FreeRTOS, which provides malloc() would use so little heap.  The FreeRTOSConfig.h shows the heap as 3000 B:

    #define configMAX_PRIORITIES ( 4 )
    #define configTOTAL_HEAP_SIZE ( 3000 )

But it is corroborated by the "Design Wide Resource" page:
This linker file will then invoke the Reset handler just like the case I just went through, which calls into main(), where FreeRTOS setup happens:

There must be something else going on?


 I think the firmware can be improved as an explicit state machine.  A more interesting way to blink the LED is with a state machine driven by a HW timer.

An HSM (hierarchical state machine) based PSoC 4 FW architecture 

Understanding the QP ARM Cortex M0 port


Making the HW Bluetooth capable

Configuring the PSoC Bluetooth HW for the "Find Me" target profile

Now I am ready to reattempt the BLE Lab #1: implementing a smart keychain device, controlled by a Bluetooth client on the PC (I will change the remote attribute of the target through the CySmart SW):
To add some excitement to the otherwise boring activity of changing the alert level of a Bluetooth peripheral, the peripheral displays a different light pattern.

In PSoC Creator, create a new project for the PSoC 4100 BLE / PSoC 4200 BLE Design template, but use an empty schematic, as shown below:
The project must target a specific device, shown in the Project --> Device Selector window.  The choice is easy for the eval board: CY8C4247LQI-BL483.  The peripherals already packed into the die are interesting.

To create a new HW, the drag-and-drop the desired components onto the schematic, as shown below:
I would guess that all Bluetooth configurations are SW modifiable at a later time, but for a simple exercise, the configurations are specified while configuring the HW (double click the component).  The BT peripheral gets the "Find Me" standard profile collection setting, showing the profile and GAP roles:
The next tab (Profiles), shows the BT-Sig defined attributes for the chosen profile, but this is NOT where I edit these attributes; I do that in the next tab: GAP Settings.
BT device is highly configurable.  In "Advertisement settings", I turn off timeout on the fast advertising interval and "Slow advertising interval".  The advertisement packet itself is also configurable; I turned on the Service UUID --> Immediate Alert.  When a collector scans the target, the target responds with the packet defined in the "Scan response packet definition: Local Name and Appearance in this case.  Looking at nodes like "Peripheral preferred connection interval", I understand that BT expertise may have less to do with understanding the BT API (which is rather simple) but knowing as many of these possibly esoteric attributes.  The whole seminar skipped security discussion altogether and used the following insecure setting:
The BT peripheral is now configured.

2 comments:

  1. Did you manage to run both the BLE stack and the FreeRTOS at the same time? If so, it would be awesome to write a tutorial about it. I would assume that one would have to be very careful with where the BLE stack is saved in the flash and drawing a line in the SRAM in order to avoid RAM overlapping between the BLE stack and FreeRTOS.

    ReplyDelete
    Replies
    1. I can't remember; I might have blinked the LED but no more. I am not going to pursue PSoC4, because for wearable applications, the custom HW that PSoC can run sucks too much current--one of my preconceptions shattered when I learned more about the constraints of wearable devices. And to be honest, I hate OSes, and would much rather use QP, which I managed to run on nRF and wrote about recently.
      http://henryomd.blogspot.com/2016/05/binary-message-protocol-for-custom-ble.html

      Delete