This post is part of my Roadtest Review of Cypress PSoC 62S2 Wi-Fi & BT5.0 Pioneer Dev Kit - Review. My review is splitted into multiple reviews and tutorials. Main page of review contains brief description of every chapter. Some projects are implemented in multiple variants. Main page of review contains brief description about every chapter, project and variants and contains some reccomendations for reading.

 

Table of Contents

 

Project 1 – Serial Interfaces

Project 1 is simple project in which I connect I2C temperature sensor HTU21D, reads temperature using them and prints it over UART. All articles and variants of this project are available as tutorials. I have used this to make opinions about Cypress platform and I reused these skills later when writing chapters with “Review” in name. This chapter is designed as tutorial, not a review. I have implemented this project in 4 ways:

  • In C and ModusToolbox IDE without using any library. Just accessing registers. I have done this to look how are some peripherals of MCU designed. If their design makes sense and If it is easy to use them. It shows how to setup clocks of the MCU, setup I2C, UART and Timer (TCPWM) and communicate over it. All just using raw registry access. For definition of registers, I am including PDL but otherwise solution shown in tutorial does not use any library.
  • In C and ModusToolbox IDE using PDL library provided by Cypress. This example features my library from github to control sensor and is based on PDL library provided by Cypress. Otherwise, it does the same as previous two variants, but it is hidden in library calls. This mainly shows how easy is to use library provided by Cypress.
  • In C and ModusToolbox IDE using HAL library provided by Cypress. This variant also does the same but uses HAL library provided by Cypress and shows how easy is to use this library.
  • In C and MBED Studio. Solution is based on MBED OS. This variant shows portable solution using MBED environment.

 

This is second variant of first project and is developed using PDL (Peripheral driver library) library and ModusToolbox environment. It shows one of the most common way to access MCU’s peripherals. PDL is library that enables you access functions of peripherals and control them. It is designed to be simple and fits to peripheral design. There is also higher-level library named HAL (hardware abstraction layer) which internally uses PDL. I have written project using both libraries. This article shows implementation using PDL and next one show implementation using HAL. Project is written as tutorial. All my thoughts about development board, MCU, development environments, libraries and much more are presented in chapters with name starting with “Review of”.

 

I implemented this variant of this project using port of my library for temperature sensor.

 

Creating project in ModusToolbox

 

At the beginning we need to create a new project of template Empty PSoC6 App, assign library retarget-io, import eclipse imports to make code completion working and to project and generate launches using Quick panel. All these steps are described (with images) in the first variant of this project.

 

Copy library to project

 

As mentioned before for this project I will use my library. Library is available at github https://github.com/misaz/HTU21DLibrary. Download library and copy following files to your project.

 

  • HTU21D.c
  • HTU21D.h
  • HTU21D_PlatformSpecificPSoC6.c
  • HTU21D_PlatformSpecificPSoC6.h

 

Rename HTU21D_PlatformSpecificPSoC6.c to HTU21D_PlatformSpecific.c (remove PSoC6 from name) and the same do for .h file.

HTU21D.c is core file of library and includes all the functions to control sensor. It is platform independent and platform specific parts of library (I2C access) is implemented in platform specific files. Library contains implementation for multiple platforms.

 

Writing code

 

Now lets use library from main. At the beginning we will initialize MCU (their clocks, power options and such other things) using cybsp_init function and configure redirection of printf function to UART (using retarget-io library). This is done by following code.

 

cy_rslt_t result;

result = cybsp_init();
if (result != CY_RSLT_SUCCESS) {
 CY_ASSERT(0);
}

result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, 115200);
if (result != CY_RSLT_SUCCESS) {
    CY_ASSERT(0);
}

 

Now call initialization function of sensor and print that initialization was completed.

 

HTU21D_Init();

printf("Initialization completed.\r\n");

 

And now lets measure temperature in infinite and print them. After that we will wait some amount of time.

 

while (1) {
      float temperature;
      if (HTU21D_ReadTemperature(&temperature) == HTU21D_E_OK) {
            printf("Temperature is %.2f°C\r\n", temperature);
      } else {
            printf("Error while reading temperature.\r\n");
      }

      // some basic delay
      for (int i = 0; i < 1000000; i++) {}
}

Connecting sensor

 

Now connect the HTU21DHTU21D sensor to the board by connecting SDA to P9_1 and SCL to P9_0. This choice is not random. Selected ports must have support for I2C which means that there must exists at least one Serial communication block (SCB) which’s signals can be connected to these ports using High speed input output matrix (HSIOM). These mappings are described in the datasheet (not a TRM) because they are device specific (partially they are family specific). In this case on the P9_1 and P9_0 is mapped SCB number 2.

 

This choice is also configurable, you can change them in HTU21D_PlatformSpecificPSoC6.h file.

 

Testing

 

Now you can run the program using run or debug button in quick panel and as result you will see measured temperature on the UART serial monitor.

 

 

Controlling I2C using PDL

Okay, all interesting code for driving I2C using PDL is hidden in library. So, lets look at that. Open the HTU21D_PlatformSpecificPSoC6.c (now it is named to name without PSoC6 at the end).

The measurement itself is done using following I2C operations.

 

  • Start
  • Address (0x40 << 1) + Write (0)
  • Write command 0xE3
  • Repeated start
  • Address (0x40 << 1) + Read (1)
  • Read 3 bytes (2 bytes are encoded temperature and third is CRC)
  • Stop

 

The initialization code looks as follows. It does lot of things. At the beginning there is configuration structure which configure properties of SCB. SCB is initialized by this structure using Cy_SCB_I2C_Init function.

 

cy_rslt_t status;

const cy_stc_scb_i2c_config_t i2cConfig =
{
 .i2cMode   = CY_SCB_I2C_MASTER,
    .useRxFifo = false,
 .useTxFifo = true,
 .slaveAddress     = 0U,
 .slaveAddressMask = 0U,
 .acceptAddrInFifo = false,
 .ackGeneralAddr   = false,
 .enableWakeFromSleep = false,
 .enableDigitalFilter = false,
 .lowPhaseDutyCycle = 8U,
    .highPhaseDutyCycle = 8U,
};

status = Cy_SCB_I2C_Init(HTU21D_SCB, &i2cConfig, &i2cContext);

 

All PDL functions begins with prefix Cy_ in name.

 

Then P9_1 and P9_0 are configured to be connected to 7th peripheral and do not act as standard GPIO. This mappins is done using High speed input output matrix (HSIOM) peripheral which could be managed by Cy_GPIO_SetHSIOM function.

 

Cy_GPIO_SetHSIOM(HTU21D_SCL_PORT, HTU21D_SCL_NUM, HTU21D_SCL_ALT);
Cy_GPIO_SetHSIOM(HTU21D_SDA_PORT, HTU21D_SDA_NUM, HTU21D_SDA_ALT);

 

The next operation is configuring GPIO ports as open drain with pull-up resistor (so they are driven low when driven). This is done using Cy_GPIO_SetDrivemode function. Please note that CY_GPIO_DM_OD_DRIVESLOW does not mean DRIVE SLOW, but DRIVES LOW. :-).

 

Cy_GPIO_SetDrivemode(HTU21D_SCL_PORT, HTU21D_SCL_NUM, CY_GPIO_DM_OD_DRIVESLOW);
Cy_GPIO_SetDrivemode(HTU21D_SDA_PORT, HTU21D_SDA_NUM, CY_GPIO_DM_OD_DRIVESLOW);

 

All SCB, Timers and some other peripherals requires clock routed from clock subsystem. You always must use some clock divider, even if they would be configured in divide by 1 (no divide). You can share selected divider with other peripherals. Configuration is done using 3 functions - Cy_SysClk_PeriphAssignDivider, Cy_SysClk_PeriphSetDivider and Cy_SysClk_PeriphEnableDivider. First of them will assign specified divider (there are multiple dividers of multiple types and you can choose whatever you want) to the configured peripheral. Second function set the division rate for that divider and third function enable specified divider.

 

status = Cy_SysClk_PeriphAssignDivider(HTU21D_SCB_CLOCK, HTU21D_SCB_CLOCK_DIVIDER_TYPE, HTU21D_SCB_CLOCK_DIVIDER_NUM);
if (status != CY_RSLT_SUCCESS) {
      CY_ASSERT(0);
}

status = Cy_SysClk_PeriphSetDivider(HTU21D_SCB_CLOCK_DIVIDER_TYPE, HTU21D_SCB_CLOCK_DIVIDER_NUM, HTU21D_SCB_CLOCK_DIVIDER_VALUE - 1);
if (status != CY_RSLT_SUCCESS) {
      CY_ASSERT(0);
}

status = Cy_SysClk_PeriphEnableDivider(HTU21D_SCB_CLOCK_DIVIDER_TYPE, HTU21D_SCB_CLOCK_DIVIDER_NUM);
if (status != CY_RSLT_SUCCESS) {
      CY_ASSERT(0);
}

 

Clock from the divider is not the actual clock on I2C bus. Peripheral does oversampling so we need to configure it. PDL contains function Cy_SCB_I2C_SetDataRate which will try to configure it for nearest possible value to specified one. You must provide How fast input clock from configured divider are and what frequency you want on I2C bus.

 

uint32_t inputClockFrequency = Cy_SysClk_PeriphGetFrequency(HTU21D_SCB_CLOCK_DIVIDER_TYPE, HTU21D_SCB_CLOCK_DIVIDER_NUM);

uint32_t currentDataRate = Cy_SCB_I2C_SetDataRate(HTU21D_SCB, 100000, inputClockFrequency);
if ((currentDataRate > 100000) || (currentDataRate == 0)) {
    CY_ASSERT(0);
}

 

At last we will enable SCB block using Cy_SCB_I2C_Enable function.

 

Cy_SCB_I2C_Enable(HTU21D_SCB);

 

That is all for initialization of SCB. Library splits logic for starting transaction and data transfer. Generating START conditions is done using following call. It start WRITE transaction. For read transaction exchange CY_SCB_I2C_WRITE_XFER by CY_SCB_I2C_READ_XFER.

 

Cy_SCB_I2C_MasterSendStart(HTU21D_SCB, HTU21D_I2C_ADDRESS, CY_SCB_I2C_WRITE_XFER, 1000, &i2cContext);

 

Generation of repeated start is done using function Cy_SCB_I2C_MasterSendReStart with the same arguments. Writing data is done using Cy_SCB_I2C_MasterWriteByte function. 1000 is timeout in milisecoonds.

 

Cy_SCB_I2C_MasterWriteByte(HTU21D_SCB, *buffer++, 1000, &i2cContext);

 

Reading data is done using Cy_SCB_I2C_MasterReadByte function. It requires one more argument for specifying if received data should be ACKed or NACKed.

 

Finally, STOP condition si generated using Cy_SCB_I2C_MasterSendStop function.

 

Cy_SCB_I2C_MasterSendStop(HTU21D_SCB, 1000, &i2cContext);

 

That is all for the project. You have seen that is not so complicated to develop using PDL. If you are looking for something more abstract and easier to understand then HAL or MBED variant of the project is designed for you.