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
- Intruction, Table of Contents, Overview of Table of Contets
- Review of Development Board
- Review of Microcontroller
- Review of Wi-FI and BT Module
- Review of Onboard Peripherals
- Review of Onboard Programmer and Debugger
- Review of Development Software
- Review of Documentation
- Project 1 - Serial Interfaces
- Project 2 - Simple Cloud Application
- Project 3 - FRAM
- Project done using serial-flash library
- Project done using HAL library (this article)
- Project 4 - CapSense and Bluetooth
- Project 5 - Dual Core Application
- Project 6 - Profiler
- Summary and Score
Project 3 – FRAM
In this project I will make application that uses onboard FRAM (it is external memory from the perspective of main MCU). Application example will show program that print numbered messages over UART in infinite loop. When power loss (and then restore) occur application will continue with numbering messages at the same number as it was when power loss occurred. Code of this sample is quite easy in when implemented using both libraries. More detailed overview of FRAM’s details is presented in chapter Review of onboard peripherals.
This is second variant of this project. It is implemented using HAL library. First variant is implemented suing serial-flash library.
At the beginning create project based on Empty PSoC6 App template and integrate retarget-io library. Picture based tutorial you can find in Project 1 – low level variant. At the beginning we initialize retarget-io library after initialization system by cybsp_init.
result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, 115200); if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); }
Now we will initialize HAL QSPI library. Initialization requires pointer to context so let’s declare global variable for qpsi context.
cyhal_qspi_t qspi;
Initialization function except context that requires port names which are used for data, clock and slave select. It also requires target frequency of the bus and SPI mode number (0 and 3 is valid). I specify ports manually, but you can use constants instead. Frequency I specify as 50 MHz. Because I do not need some extra high performance I will use only commands in SPI mode for simplicity. This also means that I do not need to configure any wait states.
result = cyhal_qspi_init(&qspi, P11_6, P11_5, P11_4, P11_3, NC, NC, NC, NC, P11_7, P11_0, 50000000, 0); if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); }
And that’s almost all. Now I can write my application. Application will load backuped value from FRAM. I will do that using RFRAM function which I will write later. I check If the value received from FRAM is in valid range 0 - 500. For example, when Application run first time FRAM content is 0xFFFFFFFF so the value is out of range. This should also happen when you ran application that overwrote same block of FRAM memory before. For storing variable in FRAM I will use FRAM location defined using FRAM_I macro.
#define FRAM_I 0x10
I also will need I variable for storing counter So I will define it at top of main.
uint32_t i;
Now I can write code for loading value.
uint32_t initialValue = RFRAM(FRAM_I); if (initialValue > 500) { initialValue = 0; } i = initialValue;
At this moment I will print loaded value and wait for a while.
printf("Restored value counter value to: %lu\r\n", initialValue); cyhal_system_delay_ms(2000);
And now I can write final loop which will print numbered messages and always backups content of I variable to FRAM. For storing data to FRAM I will use WFRAM function which I write later.
while (1) { for (; i <= 500; i++, WFRAM(FRAM_I, i)) { printf("Hello RoadTest! Message no. %lu\r\n", i); cyhal_system_delay_ms(100); } i = 0; }
Before I write RFRAM and WFRAM function I need to define how does commands issued against the memory looks like. I need to define read, write and write enable commands. Read command should looks like as follows.
I am using 0 dummy cycles as mentioned before. As you can see all parts of transfer (command, address and data) is send over only one line so all bus_width properties of definition structure are set to CYHAL_QSPI_CFG_BUS_SINGLE. Instruction is 0x03, address is 24bit wide and the value of address will be updated before every issue of command in RFRAM function. This command does not support XIP (there are more advanced read commands which supports it, but 0x03 do not) so transmission of mode bits is disabled. The definition of cyhal_qspi_command_t should looks as follows.
cyhal_qspi_command_t readCommand = { .instruction.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, .instruction.value = 0x03, .instruction.disabled = false, .address.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, .address.size = CYHAL_QSPI_CFG_SIZE_24, .address.value = 0, .address.disabled = false, .mode_bits.disabled = true, .dummy_count = 0, .data.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, };
The next command I will define is write enable. It is simple command without any address, mode bits, dummy cycles and data. It looks as follows.
The definition is quite simple because most of features are disabled for this command. I do not need to configure properties of parts that are disabled because they are not used by cyhla_qspi functions internally.
cyhal_qspi_command_t writeEnableCommand = { .instruction.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, .instruction.value = 0x06, .instruction.disabled = false, .address.disabled = true, .mode_bits.disabled = true, .dummy_count = 0, .data.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, };
The last command is write command. It looks as follows.
This command is like read and also has no mode bits and dummy cycles. Address will be also set before every execution in WFRAM function.
cyhal_qspi_command_t writeCommand = { .instruction.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, .instruction.value = 0x02, .instruction.disabled = false, .address.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, .address.size = CYHAL_QSPI_CFG_SIZE_24, .address.value = 0, .address.disabled = false, .mode_bits.disabled = true, .dummy_count = 0, .data.bus_width = CYHAL_QSPI_CFG_BUS_SINGLE, };
That’s all for definitions. In variant of this project with serial-flash library there were similar structure but they was generated by QSPI Configurator utility.
Now we can write FRAM and WRAM functions.
RFRAM will call cyhal_qspi_read function which expects context, command definition, buffer for received data and pointer with value how much remains to read. This function also updates remaining count of data to read. That means that we need to store it in variable and pass pointer to that variable. Overall function is following.
uint32_t RFRAM(uint32_t address) { size_t transferSize = sizeof(uint32_t); uint32_t output; cy_rslt_t result; readCommand.address.value = address; result = cyhal_qspi_read(&qspi, &readCommand, &output, &transferSize); if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } return output; }
The WFRAM function is similar but except write command it also issues write enable command. For issuing generic command there are cyhal_qspi_transfer function.
void WFRAM(uint32_t address, uint32_t value) { size_t transferSize = sizeof(uint32_t); cy_rslt_t result; writeCommand.address.value = address; // write enable result = cyhal_qspi_transfer(&qspi, &writeEnableCommand, NULL, 0, NULL, 0); if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } // write result = cyhal_qspi_write(&qspi, &writeCommand, &value, &transferSize); if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } }
Now you can run application. Power loss you can simulate using XRES button or you can disconnect MCU from power using Power button in Cypress Programmer utility. Following video shows how application works.
For the experiment I reconfigured application to lower frequency from 50 MHz to 1 MHz. Now let’s see what happens on the bus using Logic analyzer. I have connected logic analyzer to memory using following connection.
So now I can see what happens on the bus. If you look for overview, It looks as presented on following image. Every 100ms there are some SPI operation on the bus.
Now let’s look at the detail of one operation (others have the same format and differs only in number stored in data).
There are two operations. The first is command 0x06 which is Write Enable command. The second operations consist of transmitted bytes 0x02 0x00 0x00 0x10 0xA8 0x01 0x00 0x00. The operation code 0x02 means Write operation, then follows 3 bytes of address (0x000010) which is the same address as specified in FRAM_I macro, and then follows four bytes of data (0xA8 0x01 0x00 0x00). Because ARM of PSoC6 is little endian it is uint32_t of value 0x000001A8 (424 in decimal). It is the value of message which was printed at the time when this was sampled.
That’s all for now. You have seen that accessing memory using HAL library. If you are looking for easier solution look at variant of this project which use serial-flash library. If you are interesting about details of FRAM, I recommend reading chapter Review of onboard peripherals. In this chapter I describes FRAM’s in more details including details about lifespan of memory when application writes to memory very frequently (which our app created in this tutorial does) and why I used FRAM instead of normal FLASH.