PSoC 6, DMA & WS2812 LEDs – Modus Toolbox

Summary

One of my favorite readers, who also happens to be my bosses, bosses boss sent me an email the other day asking about the WS2812 LEDs.  So, I sent him a link to my previous article about PSOC 6 and DMA and WS2812.  He said, “That’s cool and everything… but do you have it in Modus Toolbox”.  Well, you wish is my command.

In the original article I wrote directly on the bare metal.  Which is something that I don’t really like, so in this article I will port the original code to use FreeRTOS.  In addition, in the original article I used a CY8CPROTO-062-4343W.  But, look what I found in the mail the other day.  YES! Ronak sent me a prototype of the new Cypress development kit.  Sweet.  Here is a picture.  It has a P6 and a CYW43012 (low power Bluetooth and WiFi).

For this article I will follow these steps:

  1. Make a new project
  2. Add middleware
  3. Configure the retarget i/o, the red LED & Test the configuration
  4. Explain the 2812 Task Architecture
  5. Create ws2812.h
  6. Create ws2812.c
  7. Update main.c to use the public interface of ws2812.h
  8. Rewire to use a level shifter

Finally, I will discuss some other ideas that I have for the project.

Make a New Project

In the quick panel select “New Application”.

Pick out the “CY8CKIT-062-4343W” which has the same PSoC.  In fact any of the CY8C624ABZI-D44 kits will work.

Use the “EmptyPSoC6App” starter project and give it the name “ws2812-mtb”

Select “Finish”

Add the Middleware

For this project I want to use several pieces of middleware.  To add them, right click on the project and select “ModusToolbox Middleware Selector”

Pick out FreeRTOS, Capsense, and Retarget I/O

Press OK, which will bring all of the right libraries into your project.

Configure the retarget i/o, the red LED & Test the configuration

Before I get too far down the road I like to test and make sure that the basic stuff is working.  So, I start by configuring the hardware I need for Retarget I/O and the blinking LED.  To do the hardware configuration, select “Configure Device” from the quick panel.

On this board the Red LED is connected to P1[1].  Here is a picture of the very nice label on the back. (notice the engineering sample sticker)

Go to the pins tab, turn on P1[1], give it the name “red” and select the strong drive mode.

To use the Retarget I/O you need a UART.  Go to the Peripheral tab and turn on “Serial Communication Block (SCB) 5”  Tell it to use P5[0] and P5[1] and the 0th 8-bit clock divider.  Then press save.

Open up studio_user.h and setup the standard i/o to use the correct SCB which we made an alias to called UART_STDIO_HW.  You need to add the include “cycfg.h” so that it can find the alias configuration file.

#include "cy_device_headers.h"
#include "cycfg.h"
/* Must remain uncommented to use this utility */
#define IO_STDOUT_ENABLE
#define IO_STDIN_ENABLE
#define IO_STDOUT_UART      UART_STDIO_HW
#define IO_STDIN_UART       UART_STDIO_HW

and then edit main.c.

  1. Add the include for stdio.h (line 31)
  2. Add the include for FreeRTOS.h (line 32)
  3. Add the include for the task.h (line 33)
  4. Make a context for the UART SCB (line 35)
  5. Write the function for the blinking LED task (line 37-45)
  6. Initialize the SCB as a UART and enable it (lines 53-54)
  7. Print a test message (line 58)
  8. Create the task (line 60)
  9. Start the scheduler (line 61)
#include "cy_device_headers.h"
#include "cycfg.h"
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"

cy_stc_scb_uart_context_t UART_STDIO_context;

void ledTask(void *arg)
{
	(void)arg;
	while(1)
	{
		Cy_GPIO_Inv(red_PORT,red_PIN);
		vTaskDelay(1000);
	}
}


int main(void)
{
    /* Set up the device based on configurator selections */
    init_cycfg_all();

    Cy_SCB_UART_Init(UART_STDIO_HW,&UART_STDIO_config,&UART_STDIO_context);
    Cy_SCB_UART_Enable(UART_STDIO_HW);

    __enable_irq();

    printf("Hello world\n");

    xTaskCreate(ledTask,"LED Task",100,0,5,0);
    vTaskStartScheduler();
}

Once you program it you should have a blinking LED + a serial terminal that says “Hello world”

Now that you having a working test jig we will turn ourselves to fixing up the ws2812 driver.

Configure the SPI and DMA

As I discussed in the previous article the I use the SPI to drive the sequence of 110 (for 1’s) or 100 (for 0’s) out to the string of WS2812B LEDs.  The only difference is that this time I will use  SCB0 and P0[2].  Why?  I wanted to save all of the pins on the Arduino  headers for the display.  This lead me to the row of pins on the outside of the header labeled IO0->IO7

Then I looked at the schematic and found:

OK I know what the pins are, but how do I know which SCB to attach to?  I started up the device configurator, then went through each of the pins, enabled them, then looked at what the digital inout was attached to by clicking on the dropdown menu.   In the picture below you can see that P0[2] is connected to SCB0 SPI.mosi.

Now I know SCB0. You can read about how I chose the SPI configurations values in the previous article, but for today choose:

  • SCB=SCB0
  • master
  • cpha=1 cpol=1
  • oversample=4
  • clk = clk1
  • MOSI = P0[2]
  • Tx trigger = DMA0 Channel 16

The next step is to turn on the DMA block DMA Datawire 0: Channel 16.  I am going to copy the configuration files from the PSoC Creator project, so all I need is the alias for the block

WS2812 Task Architecture

In the original article I have one flat main.c file (actually main_cm4.c)  But, when I look back, I should have used an RTOS (bad Alan).  Basically, I am going to copy the original main_cm4.c and hack it up into a new architecture.  My program will have a task called ws2812Task which will manage the LEDs.  The task will “sit” on a queue that is waiting for the rest of the system to send command messages.  Those messages are in the following format:

typedef enum {
	ws2812_cmd_update,            /* no arguments */
	ws2812_cmd_autoUpdate,        /* data is a binary true for autoupdate false for no update  */
	ws2812_cmd_setRGB,            /* data is pixel number + rgb                                */
	ws2812_cmd_setRange,          /* data is 0xFF00 bits for start and 0x00FF bits for y + rgb */
	ws2812_cmd_initMixColorRGB,   /* no arguments, turns led string to rgbrgbrgb...                */
}ws2812_cmd_t;

typedef struct {
	ws2812_cmd_t cmd;
	uint32_t data;
	uint8_t red;
	uint8_t green;
	uint8_t blue;

} ws2812_msg_t;

In addition I will create some public functions which will setup a message and submit it into the queue.  The last piece of the puzzle is that I will have a software timer which will run every 30ms to update the LEDs (if the timer is running)

Create ws2812.h

The public interface to my ws2812Task will reside in a new file called “ws2812.h”.  It is pretty simple

  • Define the number of LEDs
  • Define the enumerated list of legal commands
  • Define the Queue structure ws2812_msg_t (lines
  • 5 helper functions which create a command message and submit it into the queue (lines 15-19)
  • the function prototype for the ws2812Task (line 19)
/*
 * ws2812.h
 *
 *  Created on: Jun 15, 2019
 *      Author: arh
 */

#ifndef WS2812_H_
#define WS2812_H_

#include "stdbool.h"
#include "FreeRTOS.h"
#include "queue.h"

#define ws2812_NUM_PIXELS (144)

extern QueueHandle_t ws2812QueueHandle;


typedef enum {
	ws2812_cmd_update,            /* no arguments */
	ws2812_cmd_autoUpdate,        /* data is a binary true for autoupdate false for no update  */
	ws2812_cmd_setRGB,            /* data is pixel number + rgb                                */
	ws2812_cmd_setRange,          /* data is 0xFF00 bits for start and 0x00FF bits for y + rgb */
	ws2812_cmd_initMixColorRGB,   /* no arguments, turns string to rgbrgbrgb...                */
}ws2812_cmd_t;

typedef struct {
	ws2812_cmd_t cmd;
	uint32_t data;
	uint8_t red;
	uint8_t green;
	uint8_t blue;

} ws2812_msg_t;

extern QueueHandle_t ws2812QueueHandle;

void ws2812_update(void);
void ws2812_autoUpdate(bool option);
void ws2812_setRGB(int led,uint8_t red, uint8_t green, uint8_t blue);
void ws2812_setRange(int start, int end, uint8_t red,uint8_t green ,uint8_t blue);
void ws2812_initMixColorRGB(void);

void ws2812Task(void *arg);

#endif /* WS2812_H_ */

Create ws2812.c

To build the ws2812.c I start by opening the main_cm4.c from the original project and copying it into the ws2812.c.  At the top I add the includes for ws2812.h and the includes for FreeRTOS.  Next I declare the handle for the Queue and the Timer.  I wanted to have a variable which kept track of the autoUpdate timer being turned on, so I declare a bool.  The rest of the code is from the original program.

#include "ws2812.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "timers.h"

QueueHandle_t ws2812QueueHandle;
TimerHandle_t ws2812TimerHandle;

bool wsAutoUpdateState = false;


#define WS_ZOFFSET (1)
#define WS_ONE3  (0b110<<24)
#define WS_ZERO3 (0b100<<24)
#define WS_SPI_BIT_PER_BIT (3)
#define WS_COLOR_PER_PIXEL (3)
#define WS_BYTES_PER_PIXEL (WS_SPI_BIT_PER_BIT * WS_COLOR_PER_PIXEL)

static uint8_t WS_frameBuffer[ws2812_NUM_PIXELS*WS_BYTES_PER_PIXEL+WS_ZOFFSET];

Next I build the 5 helper functions.  These functions all have exactly the same form,

  • declare a ws2812_msg_t
  • fill it up
  • send it to the queue

Notice that I wait 0 time to try to add to the queue.  What that means is if the queue is full the message will get tossed away.

// These functions are helpers to create the message to send to the ws2812 task.

void ws2812_update(void)
{
	ws2812_msg_t msg;
	msg.cmd = ws2812_cmd_update;
	xQueueSend(ws2812QueueHandle,&msg,0);
}

void ws2812_autoUpdate(bool option)
{
	ws2812_msg_t msg;
	msg.cmd = ws2812_cmd_autoUpdate;
	msg.data = option;
	xQueueSend(ws2812QueueHandle,&msg,0);
}
void ws2812_setRGB(int led,uint8_t red, uint8_t green, uint8_t blue)
{
	ws2812_msg_t msg;
	msg.cmd = ws2812_cmd_setRGB;
	msg.red = red;
	msg.blue = blue;
	msg.green = green;
	msg.data = led;
	xQueueSend(ws2812QueueHandle,&msg,0);

}
void ws2812_setRange(int start, int end, uint8_t red,uint8_t green ,uint8_t blue)
{

	ws2812_msg_t msg;
	msg.cmd = ws2812_cmd_setRange;
	msg.red = red;
	msg.blue = blue;
	msg.green = green;
	msg.data = start << 16 | end;
	xQueueSend(ws2812QueueHandle,&msg,0);

}
void ws2812_initMixColorRGB(void)
{
	ws2812_msg_t msg;
	msg.cmd = ws2812_cmd_initMixColorRGB;
	xQueueSend(ws2812QueueHandle,&msg,0);
}

The next block of code is largely unchanged from the original program, except where I fixed some small differences between the PSoC Creator generated code and the ModusToolbox generated code.

// Function WS_DMAConfiguration
// This function sets up the DMA and the descriptors

#define WS_NUM_DESCRIPTORS (sizeof(WS_frameBuffer) / 256 + 1)
static cy_stc_dma_descriptor_t WSDescriptors[WS_NUM_DESCRIPTORS];
static void WS_DMAConfigure(void)
{
    // I copies this structure from the PSoC Creator Component configuration
    // in generated source
    const cy_stc_dma_descriptor_config_t WS_DMA_Descriptors_config =
    {
    .retrigger       = CY_DMA_RETRIG_IM,
    .interruptType   = CY_DMA_DESCR_CHAIN,
    .triggerOutType  = CY_DMA_1ELEMENT,
    .channelState    = CY_DMA_CHANNEL_ENABLED,
    .triggerInType   = CY_DMA_1ELEMENT,
    .dataSize        = CY_DMA_BYTE,
    .srcTransferSize = CY_DMA_TRANSFER_SIZE_DATA,
    .dstTransferSize = CY_DMA_TRANSFER_SIZE_WORD,
    .descriptorType  = CY_DMA_1D_TRANSFER,
    .srcAddress      = NULL,
    .dstAddress      = NULL,
    .srcXincrement   = 1L,
    .dstXincrement   = 0L,
    .xCount          = 256UL,
    .srcYincrement   = 0L,
    .dstYincrement   = 0L,
    .yCount          = 1UL,
    .nextDescriptor  = 0
    };

    for(unsigned int i=0;i<WS_NUM_DESCRIPTORS;i++)
    {
        Cy_DMA_Descriptor_Init(&WSDescriptors[i], &WS_DMA_Descriptors_config);
        Cy_DMA_Descriptor_SetSrcAddress(&WSDescriptors[i], (uint8_t *)&WS_frameBuffer[i*256]);
        Cy_DMA_Descriptor_SetDstAddress(&WSDescriptors[i], (void *)&WS_SPI_HW->TX_FIFO_WR);
        Cy_DMA_Descriptor_SetXloopDataCount(&WSDescriptors[i],256); // the last
        Cy_DMA_Descriptor_SetNextDescriptor(&WSDescriptors[i],&WSDescriptors[i+1]);
    }

    // The last one needs a bit of change
    Cy_DMA_Descriptor_SetXloopDataCount(&WSDescriptors[WS_NUM_DESCRIPTORS-1],sizeof(WS_frameBuffer)-256*(WS_NUM_DESCRIPTORS-1)); // the last
    Cy_DMA_Descriptor_SetNextDescriptor(&WSDescriptors[WS_NUM_DESCRIPTORS-1],0);
    Cy_DMA_Descriptor_SetChannelState(&WSDescriptors[WS_NUM_DESCRIPTORS-1],CY_DMA_CHANNEL_DISABLED);

    Cy_DMA_Enable(WS_DMA_HW);
}

// Function: WS_DMATrigger
// This function sets up the channel... then enables it to dump the frameBuffer to pixels
void WS_DMATrigger()
{

    cy_stc_dma_channel_config_t channelConfig;
    channelConfig.descriptor  = &WSDescriptors[0];
    channelConfig.preemptable = false;
    channelConfig.priority    = 3;
    channelConfig.enable      = false;
    Cy_DMA_Channel_Init(WS_DMA_HW, WS_DMA_CHANNEL, &channelConfig);
    Cy_DMA_Channel_Enable(WS_DMA_HW,WS_DMA_CHANNEL);
}

The next block of code is just a function which the autoupdate timer can call to trigger the DMA to update the stripe of LEDs.

// This function is called by the software timer which is used to autoupdate the LEDs
// It checks to make sure that the DMA is done... if not it doesnt do anything
void ws2812CallbackFunction( TimerHandle_t xTimer )
{
    if((Cy_DMA_Channel_GetStatus(WS_DMA_HW,WS_DMA_CHANNEL) & CY_DMA_INTR_CAUSE_COMPLETION))
    {
        WS_DMATrigger();
    }
}

From lines 156-372 I use the original functions to implement the frame buffer for WS2812 (you can read about that in the original article).  I am not including these functions here.

The final block of code is the actual task which manages the ws2812 led string.  On lines 379->395 it sets up the SPI, DMA, Queue and Timer.   Then it goes into the infinite loop waiting for command messages.  The message loop just looks at the command, the calls the correct helper function.

void ws2812Task(void *arg)
{
	ws2812_msg_t msg;
	cy_stc_scb_spi_context_t WS_SPI_context;

	vTaskDelay(100);

	printf("Starting ws2812 task\n");
	WS_runTest();
    WS_frameBuffer[0] = 0x00;
    WS_setRange(0,ws2812_NUM_PIXELS-1,0,0,0); // Initialize everything OFF
    Cy_SCB_SPI_Init(WS_SPI_HW, &WS_SPI_config, &WS_SPI_context);
    Cy_SCB_SPI_Enable(WS_SPI_HW);
    WS_DMAConfigure();

    // This queue handles messages from the keyboard
    ws2812QueueHandle = xQueueCreate( 10,sizeof(ws2812_msg_t));
    // This timer calls the update function every 30ms if it is turned on.
    ws2812TimerHandle = xTimerCreate("ws2812 timer",pdMS_TO_TICKS(30),pdTRUE,0,ws2812CallbackFunction );

    while(1)
    {
    		xQueueReceive(ws2812QueueHandle,&msg,0xFFFFFFFF);
    		switch(msg.cmd)
    		{
    		case ws2812_cmd_update:
    			if(!wsAutoUpdateState)
    			{
    				WS_DMATrigger();
    			}
    			break;
    		case ws2812_cmd_autoUpdate:
    			if(wsAutoUpdateState && msg.data == false)
    			{
    				xTimerStop(ws2812TimerHandle,0);
    			}
    			else if(!wsAutoUpdateState && msg.data == true)
    			{
    				xTimerStart(ws2812TimerHandle,0);
    			}
    			wsAutoUpdateState = msg.data;

    			break;
    		case ws2812_cmd_setRGB:
    			WS_setRGB( msg.data,msg.red,msg.green ,msg.blue);
    			break;
    		case ws2812_cmd_setRange:
    			WS_setRange(msg.data>>16 & 0xFFFF, msg.data&0xFFFF, msg.red,msg.green ,msg.blue);
    			break;
    		case ws2812_cmd_initMixColorRGB:
    			WS_initMixColorRGB();
    			break;
    		}
    }
}

Update main.c to use the Public Interface of ws2812.h

Initially when I did this, I just updated main.c.  But, after thinking about it a little bit I decided that it was better to create a uartTask.h and uartTask.c to make the keyboard processing a bit more self contained.  Starting with the public interface to uartTask.h.  This file simply declares the function prototype for the uartTask.

/*
 * uartTask.h
 *
 *  Created on: Jun 16, 2019
 *      Author: arh
 */

#ifndef SOURCE_UARTTASK_H_
#define SOURCE_UARTTASK_H_

void uartTask(void *arg);


#endif /* SOURCE_UARTTASK_H_ */

I do not like to poll!  Ever!  That is the point of an RTOS.  Don’t poll if you can at all get away from it.  To avoid polling I set up the SCB UART to give an interrupt when it receives a character.  In the ISR I then turn off the interrupts and increment a semaphore.  In the main body of the task I “sit” on the semaphore and wait for it to be incremented.  Once it is incremented, I read and process characters until there are no more.  Then turn the interrupts back on.

The uartTask.c has three sections

  • The header where I do all of the includes and define the semaphore
  • The ISR where I turn off the interrupts and set the semaphore
  • The main task.

First, the beginning of the file just does the normal includes.  It also declares a context for the UART and it declares a handle for the semaphore.

/*
 * uartTask.c
 *
 *  Created on: Jun 16, 2019
 *      Author: arh
 */

#include <stdio.h>
#include "ws2812.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "cy_device_headers.h"
#include "cycfg.h"
#include "cy_pdl.h"

cy_stc_scb_uart_context_t UART_STDIO_context;
SemaphoreHandle_t UART_STDIO_SemaphoreHandle;

The ISR simply turns off the interrupt mask so that no interrupts happen until the Rx fifo is clear (line 24).  Then clears the interrupt source (meaning tells the SCB to turn off the interrupt) so that it doesn’t just re-pend the interrupt (line 25).  Then it increments the semaphore and does the normal FreeRTOS context switch if needed.

void UART_Isr(void)
{

	// Disable & clear the interrupt
	Cy_SCB_SetRxInterruptMask(UART_STDIO_HW,0);
	Cy_SCB_ClearRxInterrupt(UART_STDIO_HW, CY_SCB_RX_INTR_NOT_EMPTY);

	static BaseType_t xHigherPriorityTaskWoken;
	xHigherPriorityTaskWoken = pdFALSE;
	xSemaphoreGiveFromISR( UART_STDIO_SemaphoreHandle, &xHigherPriorityTaskWoken );
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

The uartTask function has several parts

  • Initialize the semaphore (line 36)
  • Initialize the SCB and Interrupt (lines 38-49)
  • Waits for the semaphore to be set (line 55)
  • Loops until the Rx FIFO is empty (line 57)
  • Reads a character and does correct operation with a giant switch (59-127)
  • When all the characters are done being read (aka the Rx FIFO is empty) turn back on the interrupt (line 129)
void uartTask(void *arg)
{

	UART_STDIO_SemaphoreHandle = xSemaphoreCreateCounting( 0xFFFF,0); // Semaphore counts unprocessed key presses

	Cy_SCB_UART_Init(UART_STDIO_HW,&UART_STDIO_config,&UART_STDIO_context);
	cy_stc_sysint_t uartIntrConfig =
	{
			.intrSrc      = UART_STDIO_IRQ,
			.intrPriority = 7,
	};

	(void) Cy_SysInt_Init(&uartIntrConfig, &UART_Isr);
	NVIC_EnableIRQ(UART_STDIO_IRQ);
    Cy_SCB_SetRxInterruptMask(UART_STDIO_HW,CY_SCB_RX_INTR_NOT_EMPTY);
    setvbuf( stdin, NULL, _IONBF, 0 ); // Turn off Input buffering on STDIO
	Cy_SCB_UART_Enable(UART_STDIO_HW);

	printf("Starting UART Task\n");

	for(;;)
	{
		xSemaphoreTake( UART_STDIO_SemaphoreHandle, 0xFFFFFFFF); // Wait for a semaphore

		while(Cy_SCB_UART_GetNumInRxFifo(UART_STDIO_HW))
		{
			char c=getchar();
			switch(c)
			{
			case 'u':
				printf("Enable auto DMA updating\n");
				ws2812_autoUpdate(true);
				break;
			case 'U':
				printf("Disable auto DMA updating\n");

				ws2812_autoUpdate(false);
				break;
			case 't':
				printf("Update LEDs\n");
				ws2812_update();
				break;
			case 'r':
				ws2812_setRGB(0,0xFF,0,0);
				printf("Set LED0 Red\n");
				break;
			case 'g':
				ws2812_setRGB(0,0,0xFF,0);
				printf("Set LED0 Green\n");
				break;
			case 'O':
				ws2812_setRange(0,ws2812_NUM_PIXELS-1,0,0,0);
				printf("Turn off all LEDs\n");
				break;
			case 'o':
				ws2812_setRange(0,ws2812_NUM_PIXELS-1,0xFF,0xFF,0xFF);
				printf("Turn on all LEDs\n");
				break;
			case 'b':
				ws2812_setRGB(0,0,0,0xFF);
				printf("Set LED0 Blue\n");
				break;
			case 'R':
				ws2812_setRange(0,ws2812_NUM_PIXELS-1,0x80,0,0);
				printf("Turn on all LEDs RED\n");
				break;
			case 'G':
				ws2812_setRange(0,ws2812_NUM_PIXELS-1,0,0x80,0);
				printf("Turn on all LEDs Green\n");
				break;
			case 'B':
				ws2812_setRange(0,ws2812_NUM_PIXELS-1,0,0,0x80);
				printf("Turn on all LEDs Blue\n");
				break;
			case 'a':
				ws2812_initMixColorRGB();
				printf("Turn on all LEDs RGB Pattern\n");
				break;
			case '?':
				printf("u\tEnable Auto Update of LEDs\n");
				printf("U\tDisable Auto Update of LEDs\n");
				printf("t\tTrigger the DMA\n");
				printf("r\tSet the first pixel Red\n");
				printf("g\tSet the first pixel Green\n");
				printf("b\tSet the first pixel Blue\n");
				printf("O\tTurn off all of the pixels\n");
				printf("o\tSet the pixels to white full on\n");
				printf("R\tSet all of the pixels to Red\n");
				printf("G\tSet all of the pixels to Green\n");
				printf("B\tSet all of the pixels to Blue\n");
				printf("a\tSet pixels to repeating RGBRGB\n");
				printf("?\tHelp\n");
				break;
			}
		}
		// turn the rx fifo interrupt back on
        Cy_SCB_SetRxInterruptMask(UART_STDIO_HW,CY_SCB_RX_INTR_NOT_EMPTY); // Turn on interrupts for Rx buffer
	}
}

Rewire to Use a Level Shifter

At the end of the previous article I said “I’m Lucky it Works. The last thing to observe in all of this is that I am driving the LED string with a 5V wall wart. And according to the datasheet VIH is 0x7 * VDD = 3.5V … and I am driving it with a PSoC 6 with 3.3V. Oh well.”  This time I am not so lucky.  I am not totally sure why (probably because I used a different power supply) but it doesn’t work.  So I put my lab assistant to work putting together a level shifter that I got from SparkFun.  For those of you long time readers, you will say, “Hey that isn’t Nicholas”.  Well, it is my other lab assistant, Anna.  And she is just as good at soldering!

Now when I try it, everything works!

What is next?

As I was working on the project, I thought of several things that I would like to add to the project including:

  • A random color / blinking mode
  • A CapSense button and slider
  • The TFT display
  • Ability to handle multiple strips of LEDs

But for now, all that stuff is for another day.

You can find all of this code at my github site. git@github.com:iotexpert/WS2812-MTB.git

FreeRTOS Command Line Interface (CLI)

Summary

In a previous article, I gnashed my teeth about my implementation of the FreeRTOS FAT SL.  Specifically, I wasn’t very happy about my command line interface.  What I did was cheap and easy, which sometimes is a good thing, but given that the FreeRTOS Command Line Interface comes as part of the FreeRTOS installation, it sure seemed like that would have been a better way to go.  Now that I have finished the FreeRTOS Command Line Interface implementation, I am not totally in love with how it works.  But, it was worth the time to figure it out. In this article In this article I will make a copy of the PSoC Real Time Clock Project, add the FreeRTOS Command Line Interface and explain how the CLI works.  In the next article I will give the project the ability to set and read the time from the RTC component in the PSoC.

While I was working on the implementation I discovered that the example code used FreeRTOS_write and FreeRTOS_read which depend on a Peripheral Driver Library (don’t confuse that with the Cypress Peripheral Driver Library) and are part of FreeRTOS+IO.  I did very a very simple implementation of those commands to make the FreeRTOS Command Line Interface work, and I will talk more about the FreeRTOS+IO in a future article.

FreeRTOS Command Line Interface

The FreeRTOS Command Line Interface is pretty straightforward to use.  Basically you:

  1. Integrate FreeRTOS_CLI.h and .c into your project
  2. Create one or more command functions and register them for use by the CLI by calling FreeRTOS_CLIRegisterCommand
  3. Read input from the terminal UART into a buffer
  4. Call the CLI (when the users presses enter) and display the output

Integrate FreeRTOS Command Line Interface C Files

I start this whole thing by copying the project “RTC-Example” from the previous article into a new project called “RTC-Example-CLI”.  Inside of the directory FreeRTOSv9.0.0/FreeRTOS-Plus/Source/FreeRTOS-Plus-CLI you will find FreeRTOS_CLI.h and FreeRTOS_CLI.c.  These two files have all of the FreeRTOS Command Line implementation.  To make this work I copied them into my project, and then did “Add –> Existing Item” to get PSoC Creator to make them part of the project.

Integrate FreeRTOS Command Line Interface C Files

Create a Command

To use the FreeRTOS Command Line Interface you need to create one or more functions which the CLI will then callback when it detects that the user has typed that command.   Each command takes three input parameters

  1. A pointer to a character buffer where the command can store text to be printed on the screen
  2. The size of that buffer
  3. A pointer to a character buffer that holds the whole string that the user typed

The function is then responsible for

  1. Doing the command
  2. Writing some output into the output buffer (up to the length of the buffer)
  3. Returning pdFALSE (if the command has completed writing the output) or pdTRUE (if it needs to do some more processing or more output)

For example if you want to create a command to clear the screen you would create a function called “clearCommand”  That command would

  1. Ignore the pcCommandString (there are no parameters)
  2. Keep track of the number of characters written into the buffer using a static int
  3. Copy the VT100 Clear String (AKA “\033[2J\033[H”) into the output buffer
  4. Null terminate the output string
  5. If you wrote less characters than the string then return pdTrue
  6. Otherwise return false
/*****************************************************************************\
 * Function:    clearCommand
 * Input:       char *pcWriteBufer,size_t xWriteBufferLen,const char *pcCommandString
 * Returns:     BaseType_t
 * Description: 
 *     This function clears the screen. It is run by the CLI interpreter
\*****************************************************************************/

BaseType_t clearCommand( char *pcWriteBuffer,size_t xWriteBufferLen, const char *pcCommandString )
{
    (void)pcCommandString;
    static int processed = 0;
    
    char *clearScreen = VT100_CLEARSCREEN;
    // Only allowed to write up top xWriteBufferLen bytes ... 
    strncpy(pcWriteBuffer,&clearScreen[processed],xWriteBufferLen-1);
    pcWriteBuffer[xWriteBufferLen-1]=0;

    processed = processed + xWriteBufferLen-1;
    if(processed < (int)strlen(clearScreen))
        return pdTRUE;
    
    processed = 0;
    return pdFALSE;
}

Once you have created the command function you then need to register it with the FreeRTOS Command Line Interface.  To do that you create a structure of type “CLI_Command_Definition_t” which has 4 members.

  1. The ascii string which the user can type to trigger the command
  2. The help message
  3. A function pointer to the command function
  4. The number of arguments that the CLI should accept
    static const CLI_Command_Definition_t clearCommandStruct =
    {
        "clear",
        "clear: Clear Screen by sending VT100 Escape Code\n",
        clearCommand,
        0
    };

Finally you need to register the command.  You should be aware that this function uses malloc.

FreeRTOS_CLIRegisterCommand( &clearCommandStruct );

Call the CLI and Display

On the FreeRTOS Command Line Interface webpage they provided a sample task to handle the command line interpreter.  I copied this task into my project and then made a few little changes.

  1. My Mac sends a ‘\r’ when you press the return key so I changed line 164 to reflect that
  2. My Mac sends 0x7F when you press the “Delete” key.  So I changed the ‘\b’ (aka backspace) to be 0x7F aka “del”
  3. I fixed the stupid ‘\r\n’ stuff

One of the great annoyances in the world is the way that line breaks in text files and terminals are handled.  It is typical in “unix” to use just a newline “\n”.  However, in “DOS” it is typical to use a carriage return/newline “\r\n”.  All over FreeRTOS it uses DOS mode.  I typically like to use unix mode… so you will find that I made that changes in the FreeRTOS CLI code.

The cliTask function does a number of things.

  1. Initializes the IO System, RTC and Command Line Interface (lines 154-163)
  2. Gets 1 character from the UART using FreeRTOS_read
  3. If the character is a carriage return (\r) then it calls the CLI until there is no more output (lines 167-196).   Remember that when you implement a command function for the CLI, if you return pdFALSE it means that you have no more output, and if you return pdTRUE then you have more output.
  4. If the character is the delete key (0x7F) then erase the character from the input buffer)
  5. Otherwise add it to the input buffer… assuming that you still have room.  (lines 227-232)
/*****************************************************************************\
 * Function:    cliTask
 * Input:       void *arg  ... unused
 * Returns:     void
 * Description: 
 *     This function is the inifite loop for the command line intepreter.. it
 *     reads characters using the FreeRTOS_read function then sends them to the
 *     cli when there is a \r 
\*****************************************************************************/
void cliTask(void *arg)
{
    (void)arg;

    char pcOutputString[ MAX_OUTPUT_LENGTH ], pcInputString[ MAX_INPUT_LENGTH ];
    int8_t cRxedChar, cInputIndex = 0;
    BaseType_t xMoreDataToFollow;    

    FreeRTOS_open( (const int8_t *)"/uart",0 );
    clearScreen();
    #define INTRO_STRING "Command Line & RTC Demo\n"
    FreeRTOS_write(0,INTRO_STRING,strlen(INTRO_STRING));

    RTC_Start();

    FreeRTOS_CLIRegisterCommand( &clearCommandStruct );	
    FreeRTOS_CLIRegisterCommand( &setTimeCommandStruct );	
    FreeRTOS_CLIRegisterCommand( &timeCommandStruct );	
    while(1)
    {
        FreeRTOS_read( 0, &cRxedChar, sizeof( cRxedChar ) );
        if( cRxedChar == '\r' )
        {
            /* A newline character was received, so the input command string is
            complete and can be processed.  Transmit a line separator, just to
            make the output easier to read. */

            FreeRTOS_write(0,&cRxedChar,1);

            /* The command interpreter is called repeatedly until it returns
            pdFALSE.  See the "Implementing a command" documentation for an
            exaplanation of why this is. */
            do
            {
                /* Send the command string to the command interpreter.  Any
                output generated by the command interpreter will be placed in the
                pcOutputString buffer. */
                
                xMoreDataToFollow = FreeRTOS_CLIProcessCommand
                              (
                                  pcInputString,   /* The command string.*/
                                  pcOutputString,  /* The output buffer. */
                                  MAX_OUTPUT_LENGTH/* The size of the output buffer. */
                              );

                /* Write the output generated by the command interpreter to the
                console. */
                    
                FreeRTOS_write( 0, pcOutputString, strlen( pcOutputString ) );

            } while( xMoreDataToFollow != pdFALSE );

            /* All the strings generated by the input command have been sent.
            Processing of the command is complete.  Clear the input string ready
            to receive the next command. */
            cInputIndex = 0;
            memset( pcInputString, 0x00, MAX_INPUT_LENGTH );
        }
        else
        {
            /* The if() clause performs the processing after a newline character
            is received.  This else clause performs the processing if any other
            character is received. */

            if( cRxedChar == 127 ) // delete character
            {
                FreeRTOS_write(0,&cRxedChar,1);
                /* Backspace was pressed.  Erase the last character in the input
                buffer - if there are any. */
                if( cInputIndex > 0 )
                {
                    cInputIndex--;
                    pcInputString[ cInputIndex ] = '
/*****************************************************************************\
* Function:    cliTask
* Input:       void *arg  ... unused
* Returns:     void
* Description: 
*     This function is the inifite loop for the command line intepreter.. it
*     reads characters using the FreeRTOS_read function then sends them to the
*     cli when there is a \r 
\*****************************************************************************/
void cliTask(void *arg)
{
(void)arg;
char pcOutputString[ MAX_OUTPUT_LENGTH ], pcInputString[ MAX_INPUT_LENGTH ];
int8_t cRxedChar, cInputIndex = 0;
BaseType_t xMoreDataToFollow;    
FreeRTOS_open( (const int8_t *)"/uart",0 );
clearScreen();
#define INTRO_STRING "Command Line & RTC Demo\n"
FreeRTOS_write(0,INTRO_STRING,strlen(INTRO_STRING));
RTC_Start();
FreeRTOS_CLIRegisterCommand( &clearCommandStruct );	
FreeRTOS_CLIRegisterCommand( &setTimeCommandStruct );	
FreeRTOS_CLIRegisterCommand( &timeCommandStruct );	
while(1)
{
FreeRTOS_read( 0, &cRxedChar, sizeof( cRxedChar ) );
if( cRxedChar == '\r' )
{
/* A newline character was received, so the input command string is
complete and can be processed.  Transmit a line separator, just to
make the output easier to read. */
FreeRTOS_write(0,&cRxedChar,1);
/* The command interpreter is called repeatedly until it returns
pdFALSE.  See the "Implementing a command" documentation for an
exaplanation of why this is. */
do
{
/* Send the command string to the command interpreter.  Any
output generated by the command interpreter will be placed in the
pcOutputString buffer. */
xMoreDataToFollow = FreeRTOS_CLIProcessCommand
(
pcInputString,   /* The command string.*/
pcOutputString,  /* The output buffer. */
MAX_OUTPUT_LENGTH/* The size of the output buffer. */
);
/* Write the output generated by the command interpreter to the
console. */
FreeRTOS_write( 0, pcOutputString, strlen( pcOutputString ) );
} while( xMoreDataToFollow != pdFALSE );
/* All the strings generated by the input command have been sent.
Processing of the command is complete.  Clear the input string ready
to receive the next command. */
cInputIndex = 0;
memset( pcInputString, 0x00, MAX_INPUT_LENGTH );
}
else
{
/* The if() clause performs the processing after a newline character
is received.  This else clause performs the processing if any other
character is received. */
if( cRxedChar == 127 ) // delete character
{
FreeRTOS_write(0,&cRxedChar,1);
/* Backspace was pressed.  Erase the last character in the input
buffer - if there are any. */
if( cInputIndex > 0 )
{
cInputIndex--;
pcInputString[ cInputIndex ] = '\0';
}
}
else
{
/* A character was entered.  It was not a new line, backspace
or carriage return, so it is accepted as part of the input and
placed into the input buffer.  When a \n is entered the complete
string will be passed to the command interpreter. */
if( cInputIndex < MAX_INPUT_LENGTH )
{
FreeRTOS_write(0,&cRxedChar,1);
pcInputString[ cInputIndex ] = cRxedChar;
cInputIndex++;
}
}
}
}
}
'; } } else { /* A character was entered. It was not a new line, backspace or carriage return, so it is accepted as part of the input and placed into the input buffer. When a \n is entered the complete string will be passed to the command interpreter. */ if( cInputIndex < MAX_INPUT_LENGTH ) { FreeRTOS_write(0,&cRxedChar,1); pcInputString[ cInputIndex ] = cRxedChar; cInputIndex++; } } } } }

As always you can find all of this code on the IoT Expert GitHub website or your can git clone git@github.com:iotexpert/PSoC-FileSystem.git

CY8CKIT-044 PSoC4200M Real Time Clock

Summary

In my article entitled “FreeRTOS FAT SL – Musing on my Implementation“, I lamented that I was supposed to implement a Real Time Clock so that the reads and writes of files could be timestamped, but didnt.  I knew that the PSoC4200M Real Time Clock is actually really easy to use.  But, I didn’t because I didn’t want to fix the command line interface that I was using.  In this article I will show you how to implement a PSoC4200M Real Time Clock in FreeRTOS.  Then in the next article Ill show you how to implement the FreeRTOS Command Line Interface to set the time and turn on the FreeRTOS FAT SL Clock Driver.

PSoC4200M Real Time Clock Schematic

The first thing to do is copy the FreeRTOS Template project.  Then add the UART and RTC components to the project schematic from the PSoC Creator Component Catalog.

PSoC4200M Real Time Clock Schematic

The PSoC4200M Real Time Clock will have the default settings for the component.

PSoC4200M Real Time Clock Configuration

To make an accurate clock, I want to turn on the Watch Crystal Oscillator which drives the Watch Dog Timers.  To do this, click on the “Clocks” tab of the Design Wide Resources.  Then press edit clock.

PSoC4200M Real Time Clock Configuration DWR

Once you are on the clock editor page, turn on the WCO (by pressing the checkmark in the upper left hand corner).  Then turn on the WDT Timer 0 in the Periodic Timer Mode.  Set the divider to 32768 so that the RTC will get 1 interrupt per second.  You also need to select the source of the RTC to be Timer 0.

PSoC4200M Real Time Clock Low Frequency Clock Configuration

Firmware

In the firmware I create a new task called “uartTask” which just starts the RTC, then prints out the time when the user presses ‘t’.  Obviously I didnt set the clock, so it starts from 12:00:00.  Ill fix that in the next post after I get the FreeRTOS Command Line Interpreter working.

void uartTask(void *arg)
{
    (void)arg;
    UART_Start();
    clearScreen();
    UART_UartPutString("Start Real Time Clock Demo\n");
    UART_SetCustomInterruptHandler(uartISR);
    uint32 timeBCD;
    char buff[32];
    
    RTC_Start();

    
    while(1)
    {
        ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
        
        while(UART_SpiUartGetRxBufferSize()) // if there is data then read and process
        {
            char c;
            
            c= UART_UartGetChar();
			switch(c)
			{
                case 't':
                    
                    timeBCD = RTC_GetTime();
                    sprintf(buff,"%d:%d:%d\n",(int)RTC_GetHours(timeBCD),(int)RTC_GetMinutes(timeBCD),(int)RTC_GetSecond(timeBCD));
                    UART_UartPutString(buff);
                    
                break;

You can find this project called RTC-Example in the workspace PSoC-Filesystem on the IoT Expert GitHub site or git@github.com:iotexpert/PSoC-FileSystem.git

FreeRTOS FAT SL – F_FS_THREAD_AWARE

Summary

If you have been following this series of articles about the FreeRTOS FAT SL filesystem, you might have read my post called “FreeRTOS FAT SL – Musings on my Implementation”.  In that post I asked myself “Why do I get the error message ‘undefined reference to xQueueCreateMutex'”.  Here is a screen shot from PSoC Creator

FreeRTOS FAT SL Error Message

When I traced through the problem I noticed that it comes from this block of code:

#if F_FS_THREAD_AWARE == 1
	{
		extern SemaphoreHandle_t fs_lock_semaphore;

		if( fs_lock_semaphore == NULL )
		{
			fs_lock_semaphore = xSemaphoreCreateMutex();
            
			if( fs_lock_semaphore == NULL )
			{
				return F_ERR_OS;
			}
		}
	}
#endif /* F_FS_THREAD_AWARE */

Which gave me the hint that it had something to do with F_FS_THREAD_AWARE being turned on.  When I got the error message I assumed that it had something to do with the tangled mess of include files that I had created.  This flag is set in config_fat_sl.h

/**************************************************************************
**
**  FAT SL user settings
**
**************************************************************************/
#define F_SECTOR_SIZE           512  /* Disk sector size. */
#define F_FS_THREAD_AWARE       1     /* Set to one if the file system will be access from more than one task. */
#define F_MAXPATH               16    /* Maximum length a file name (including its full path) can be. */
#define F_MAX_LOCK_WAIT_TICKS   20

So, to “solve” the problem I just turned it off, knowing that I would need to come back to it to figure out because for sure if you are using the FreeRTOS FAT SL filesystem in an RTOS, which I am, you had better have the thread awareness turned on.  Specifically what the flag does is create and use a mutex around the actual “disk” to prevent re-entrant code from hosing you.

This morning I sat down and figured it out.  The answer is that I am an idiot.  Almost all of the features of FreeRTOS that you might want to use need to be turned on in the FreeRTOSConfig.h file.  This is true of mutex.  Here is the section of the file with the issue… look at line 19

#define configUSE_PREEMPTION                    1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_TICKLESS_IDLE                 0
#define configCPU_CLOCK_HZ                      ( ( unsigned long ) CYDEV_BCLK__SYSCLK__HZ )
#define configTICK_RATE_HZ                      1000
#define configMAX_PRIORITIES                    5
#define configMINIMAL_STACK_SIZE                128
#define configMAX_TASK_NAME_LEN                 16
#define configUSE_16_BIT_TICKS                  0
#define configIDLE_SHOULD_YIELD                 1
#define configUSE_TASK_NOTIFICATIONS            1
#define configUSE_MUTEXES                       0 // <=== here is the problem line
#define configUSE_RECURSIVE_MUTEXES             0
#define configUSE_COUNTING_SEMAPHORES           0
#define configUSE_ALTERNATIVE_API               0 /* Deprecated! */
#define configQUEUE_REGISTRY_SIZE               10
#define configUSE_QUEUE_SETS                    0
#define configUSE_TIME_SLICING                  0
#define configUSE_NEWLIB_REENTRANT              0
#define configENABLE_BACKWARD_COMPATIBILITY     0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5

When I turn on the configUSE_MUTEXS things compile just fine and I can get on with my life.

FreeRTOS FAT SL – Musings on my Implementation

Summary

I just finished writing the “last” article about building a FreeRTOS FAT SL filesystem into the Cypress FM24V10 FRAM using a CY8CKIT-044.  My implementation works pretty well…. but I am not really that happy with it.  As I sit here and write this article I am not totally sure what I should do next.

I suppose the first thing to do is talk about the things that I don’t like in what I did.

  1. Real Time Clock
  2. Command Line Interpreter
  3. DMA Media Driver
  4. Include files
  5. F_FS_THREAD_AWARE 0
  6. Template project
  7. Performance Metrics
  8. Wear Leveling
  9. Performance Metrics
  10. Discussion of FAT Filesystems

Real Time Clock

As part of the port, you are supposed to provide psp_rtc.c which has one function, psp_getcurrentimedate.  This function is used to get the time to use as a timestamp on files.  I left it default, which means every transaction is timestamped the same, probably not good.  Moreover, there is an RTC in the PSoC4200M that is on the CY8CKIT-044.  The board also has the watch crystal which drives the RTC populated so there is really no good reason not to turn it on.

FreeRTOS FAT SL PSoC RTC

However, when I wrote the original example the command line interpreter that I build only took one character at a time, so there was no good way to set the clock.   Which brings me to the next problem.

Command Line Interpreter (CLI)

When I originally build the example project my command interpreter just had an infinite loop that waited for a character from the keyboard, then did one command based on that character.  It looks like this:

   while(1)
    {
        ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
        
        while(UART_SpiUartGetRxBufferSize()) // if there is data then read and process
        {
            char c;
            
            c= UART_UartGetChar();
			switch(c)
			{
				case 'i': 
				break;
				
			
                case '?': // Print out the list of commands
                    
                break;
                    
                default:
                break;
			}
        }
        // Turn the interrupts back on
        UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY); 
        UART_SetRxInterruptMode(UART_INTR_RX_NOT_EMPTY);
    }

What I did was cheap and easy… but, FreeRTOS has a CLI built in, so I suppose that I should have used.

DMA Media Driver

When I read and write from the FRAM I put in code that is blocking.  Meaning that it essentially hangs the entire system until they return.  Not really good given that this is an RTOS.  This is what all of the I2C_ functions below do.

  status = I2C_I2CMasterSendStart( calcI2CAddress(sector),I2C_I2C_WRITE_XFER_MODE);
    if(status != I2C_I2C_MSTR_NO_ERROR)
    {
        UART_UartPutString("I2C Error\n");
        return MDRIVER_RAM_ERR_SECTOR;
    }
    int i;
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); //

There is no reason that I shouldn’t have used the DMA engine to read and write the FRAM which would have freed up the processor.

Include files

I absolutely hate the scheme that I used to name and use the includes in the FreeRTOS FAT SL port.  I should fix this for sure.

F_FS_THREAD_AWARE 0

When I originally tried to compile this project I marked this #define from “config_fat_sl.h” as “1”.  However when I do that, I end up with this error which I should for sure fix as the code is not reentrant and this #define protects you.

An RTOS bug in the FreeRTOS FAT SL Implementation

Template project

My good friend Mark Saunders pointed out PSoC Creator has a new feature which you can use to make template projects.. which I didnt know about until he told me.  Obviously this would be better than what I am doing.

Performance Metrics

I did not collect any performance metrics when I build this project.  How much RAM? Flash?  How long does it take to read and write files?  I don’t know.  Moreover, I put in debugging information into the media driver which was counter productive to good memory usage.  For instance in this snip from the readsector function I define a big ass buffer of 128 bytes on line 134, then I printout a message to the uart each time this function is called.

static int fram_readsector ( F_DRIVER * driver, void * data, unsigned long sector )
{
    char buff[128]; // A scratch buffer for UART Printing
    (void)driver;
    uint16 address;
    uint32_t status;
    
    sprintf(buff,"Read sector %d\n",(int)sector);
    UART_UartPutString(buff);

Error Checking

There are a bunch of places where I could have put in much better error checking, and I didnt.  For instance in this section of the readsector function if the I2C_I2CMasterWriteByte function fails, it probably hangs the I2C bus until the chip is reset… this is bad.  Even when an error occurs, printing a message probably isn’t a good idea (line 146).

    if(status != I2C_I2C_MSTR_NO_ERROR)
    {
        UART_UartPutString("I2C Error\n");
        return MDRIVER_RAM_ERR_SECTOR;
    }
    int i;
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); // 
    
    
    I2C_I2CMasterSendRestart(calcI2CAddress(sector),I2C_I2C_READ_XFER_MODE);

Wear Leveling

Many nonvolatile memory chip will wear out if you write them too many time.  Even 100K cycles can easily happen on a key sector of the filesystem for instance sector 0.  One convient thing about the FRAM is that it doesnt wear out.  But, when I started this journey I was originally going to use the PSoC6 development kit which uses a NOR Flash.  The NOR Flash will for sure wear out.  To combat this problem, people have developed wear leveling schemes.  But I don’t address this issue at all with my media driver.

Discussion of FAT Filesystems

As I wrote about the FreeRTOS FAT SL Filesystem I was originally planning a tutorial on file systems.  But as I dug a little bit a whole bunch of issues came up which felt a little bit overwhelming to address.  The issues that were left unaddressed are:

  1. Copywrite of the FAT File System
  2. Efficiency of FAT File Systems
  3. The licensing of the FreeRTOS FAT SL
  4. Other FAT implementations

I suppose that at some point I should come back and look at those issues.

FreeRTOS FAT SL – PSoC Example Project (Part 2)

Summary – Examples using FreeRTOS FAT SL

In the previous several articles I have written about the FreeRTOS FAT SL Filesystem.  This included using the Cypress FM24V10 FRAM, creating an FRAM media driver and building an example project.  This article will show you the C-functions in “extestfs.c” that are used to actually test the filesystem.  Notice that I named all of the functions starting with “ex” which I adopted from the FreeRTOS FAT SL examples.  The functions in this article are all called by the command line interpreter that I built in the previous post.  They include

  • exInit – Initializing the Filesystem
  • exCreateFile – Creating a file in the FileSystem
  • exReadFile – Reading the data from a file
  • exFormat – Formatting the disk
  • exDriveInfo – Printing the drive information
  • exDirectory – Print an “ls” or “dir”

exInit

Before the FreeRTOS FAT SL Filesystem can do anything, you must initialize it.  All this function does is call the function that I created to turn on the file system in the media port.  One thing that is interesting is that it returns an error code that tells you if the file system is formatted or  not.   It figured this out by looking at the data in the first sector of the FRAM.

// This function initalize the filesystem
void exInit(void)
{
    unsigned char ucStatus;
    
	/* First create the volume. */
	ucStatus = f_initvolume( fram_initfunc );
    UART_UartPutString("Initialized sucessfully\n");
	/* It is expected that the volume is not formatted. */
	if( ucStatus == F_ERR_NOTFORMATTED )
	{
        UART_UartPutString("Filesystem Unformatted\n");
	}
    
    else
    {
        UART_UartPutString("Filesystem Formatted\n");
    }
}

exCreateFile

This functiom creates a file called “afile.txt” in the FreeRTOS FAT SL filesystem with the ASCII characters for 0-9.  I did something that was probably not helpful in that I made a loop (on line 43) that started a ‘0’ which is also known as 49 (go look at the ASCII table).

This function calls f_open which will create a new file when it is passed the argument “w”.  I also use the function f_putc to output characters to the file.

As I am looking at the code to write this article I realized that I didnt f_close the file (which I have now fixed).

#define FILENAME "afile.txt"
// exCreateFile - this function creates a file called "afile.txt" an
void exCreateFile(void)
{
    F_FILE *pxFile;
    UART_UartPutString("Attempting Create & Write File\n");
    pxFile = f_open(FILENAME, "w" );
  
    if(pxFile)
    {
        for(int i='0' ; i< '9' ; i++)
        {
            f_putc(i,pxFile);
        }
        f_close(pxFile);
    }
    else
    {
        UART_UartPutString("Failed File Create\n");
    }
}

exReadFile

The exReadFile functions reads all of the characters out of the “afile.txt” and prints them to the screen.  It uses the function f_getc which reads one character from the file.  Notice that I open the file in read mode by using the “r” option.

void exReadFile(void)
{
    UART_UartPutString("Attempting Read\n");
    F_FILE *pxFile;
    pxFile = f_open( FILENAME, "r" );
    if(pxFile)
    {
        while(!f_eof(pxFile))
        {
            UART_UartPutChar(f_getc(pxFile));
        }
        f_close(pxFile);
    }
    else
    {
        UART_UartPutString("File not found\n");
    }
}

exFormat

The exFormat function calles f_format with the FAT12 option.  It is impossible to use FAT16 and FAT32 which require a much larger media to use.  FAT12 was originally created for use on smallish floppy disk.

void exFormat(void)
{
    char buff[128];
	/* Format the created volume. */
    unsigned char ucStatus;
	ucStatus = f_format( F_FAT12_MEDIA );
	if( ucStatus == F_NO_ERROR )
	{
        UART_UartPutString("Formatted sucessfully\n");
    }
    else
    {
        sprintf(buff,"Error = %d\n",ucStatus);
        UART_UartPutString(buff);   
        UART_UartPutString("Format Error\n");
    }   
}

exDriveInfo

The exDriveInfo function calls the f_getfreespace function to find out how much space is free for use on the FRAM FreeRTOS FAT SL Filesystem.

void exDriveInfo( void )
{
    char buff[128];
    F_SPACE xSpace;
    unsigned char ucReturned;

    /* Get space information on current embedded FAT file system drive. */
    ucReturned = f_getfreespace( &xSpace );
    if( ucReturned == F_NO_ERROR )
    {
        /* xSpace.total holds the total drive size, xSpace.free holds the
        free space on the drive, xSpace.used holds the size of the used space
        on the drive, xSpace.bad holds the size of unusable space on the
        drive. */
        sprintf(buff,"Free Space = %lu\nUsed Space = %lu\nTotal = %lu\n",xSpace.free,xSpace.used,xSpace.total);
        UART_UartPutString(buff);
    }
    else
    {
        /* xSpace could not be completed.  ucReturned holds the error code. */
        UART_UartPutString("Free space failed\n");
    }
}

exDirectory

The exDirectory function calls the f_findfirst and f_findnext functions to iterate all of the files and directories on the top level of the file system.  The f_findfirst function uses a wildcard regular expression to match filenames. When it finds a file it prints information about that file.

void exDirectory( void )
{
    char buff[128];
    F_FIND xFindStruct;

    /* Print out information on every file in the subdirectory "subdir". */
    if( f_findfirst( "*.*", &xFindStruct ) == F_NO_ERROR )
    {
        do
        {
            sprintf(buff,"filename:%s ", xFindStruct.filename );
            UART_UartPutString(buff);

            if( ( xFindStruct.attr & F_ATTR_DIR ) != 0 )
            {
                UART_UartPutString ( "is a directory directory\n" );
            }
            else
            {
                sprintf ( buff, "is a file of size %lu\n", xFindStruct.filesize );
                UART_UartPutString(buff);
            }

        } while( f_findnext( &xFindStruct ) == F_NO_ERROR );
    }
}

 

FreeRTOS FAT SL – PSoC Example Project (Part 1)

Summary

In the previous articles I discussed using the FRAM, and making a FreeRTOS FAT SL media driver.  In this article I will discuss building an example project that uses that work to make a real FreeRTOS FAT SL system example.  I was originally planning on making just one article explaining the example, but my example code turned out to be a little bit sprawling, so I decided to break it into two pieces.  In part 1 (this article) I will

  1. Build the FreeRTOS Template
  2. Import the FreeRTOS FAT SL FileSystem & FRAM Media Driver
  3. Build a command line interpreter
  4. Nuke the data in the FRAM

In the next article I will show you the details of using the FreeRTOS FAT SL filesystem.

Build the FreeRTOS Template & Schematic

I start the project from my FreeRTOS template project.  You can read all about that here.  Then I add in the components required to make things work.  The schematic is simple:

  • A UART component to run the Command Line Interpreter
  • An I2C to control the FRAM

FreeRTOS FAT SL Example Schematic

The I2C is setup as a master at it highest speed setting.

FreeRTOS FAT SL I2C Configuration

On the CY8CKIT-044 the pins need to be configured as so:

FreeRTOS FAT SL - Pin Configuration

Import the FreeRTOS FAT SL FileSystem & FRAM Media Driver

The first step in importing the FreeRTOS FAT SL FileSystem and and media driver is to copy the FreeRTOS-Plus-FAT-SL directory into my project.  Once that is done, my folder structure looks like this:

FreeRTOS FAT SL Code Layout

The next step is to fix the include paths to include the config, api and psp include directories.  This can be done on the build settings menu.

FreeRTOS FAT SL Include Files

Finally I import the .h and .c files into my project so that it looks like this:

FreeRTOS FAT SL Project Directory Structure

Build a Command Line Interpreter (CLI)

I think that it is convenient to have a command line utility (one that connects to the UART port) to send commands to my program to test it.  To build the CLI, I create a task called uartTask which takes keys from the terminal and then processes them in a giant switch statement.  While I was building the FreeRTOS FAT SL system, I noticed that FreeRTOS also has a scheme for CLIs which I will try out in the future.

In the UART configuration I turn on the “RX FIFO not empty” interrupt (meaning that there is data in the fifo that needs to be processed)

FreeRTOS FAT SL UART Configuration

I connect the interrupt to the interrupt service routine on line 64.  In the ISR I use the FreeRTOS notification scheme to send notifications to the uartTask to wakeup and process data (line 50).

My CLI has two different things that it does:

  • It calls functions in the file extestfs.c that start with “ex” , meaning example, to format, initalize etc.
  • It keeps a “currentSector” variable that lets me print out the raw data in a sector.  The currentSector variable is incremented (with the keyboard +) and decremented (with the keyboard -) and set to 0 with the (keyboard 0)

Once all of the keys have been processed (the Rx FIFO buffer is empty – line 71), it turns back on the Rx FIFO interrupt (lines 150-151), then waits for another notification from the ISR (line 69)

TaskHandle_t uartTaskHandle;
void uartISR()
{
    
    BaseType_t xHigherPriorityTaskWoken;
    // disable the interrupt
    UART_SetRxInterruptMode(0);
    vTaskNotifyGiveFromISR(uartTaskHandle,&xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    
}

// This is the main task which processes commands from the UART and prints the results
// on the screen.
void uartTask(void *arg)
{
    (void)arg;
    UART_Start();
    I2C_Start();
    clearScreen();
    UART_UartPutString("Start Filesystem Demo\n");
    UART_SetCustomInterruptHandler(uartISR);
    
    int currentSector=0;    
    while(1)
    {
        ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
        
        while(UART_SpiUartGetRxBufferSize()) // if there is data then read and process
        {
            char c;
            
            c= UART_UartGetChar();
			switch(c)
			{
				case 'i': // Initialize the RTOS
					exInit();
				break;
				case 'f': // Format the FRAM
					exFormat();
				break;
                    
				case 'q': // Return the drive information
					exDriveInfo();
				break;
			
                case 'w': // Create a file
				    exCreateFile();
				break;
					
				case 'r': // Read the file that was created (if it exists)
				    exReadFile();
				break;
					
				case '0': // Goto to sector 0 and print the data
				    currentSector = 0;
					exPrintSector(0,0,0);
				break;
				
			    case '+': // Print the "current" sector and go to the next
				    exPrintSector(0,0,currentSector);
				    currentSector += 1;
			    break;
					
			    case '-': // print the current sector and go to the previous
				    exPrintSector(0,0,currentSector);
				    currentSector -= 1;
				    if(currentSector<0)
					    currentSector =0;
			    break;
                    
    			case 'd': // Do a directory
	    			exDirectory();
		    	break;
                    
                case 'c':
                    clearScreen();
                break;
                    
                case 'b': // Erase the FRAM (write all 0's)
                    blankFRAM();
                break;
			
                case '?': // Print out the list of commands
                    UART_UartPutString("b - Blank FRAM - write all 0's\n");
                    UART_UartPutString("i - Initalize Filesystem\n");
                    UART_UartPutString("f - Format FRAM\n");
                    UART_UartPutString("q - Print FileSystem Information\n");
                    UART_UartPutString("w - Create a file called \"afile.bin\"\n");
                    UART_UartPutString("r - Read the file called \"afile.bin\" and print contents\n");
                    UART_UartPutString("0 - Goto sector 0 and print contents\n");
                    UART_UartPutString("+ - Goto next sector and print contents\n");
                    UART_UartPutString("- - Goto previous sector and print contents\n");
                    UART_UartPutString("d - Print Directory\n");
                    UART_UartPutString("c - Clear Screen\n");
                    UART_UartPutString("? - Print help\n");        
                    
                break;
                    
			    default:
				    UART_UartPutString("Unknown :");
				    UART_UartPutChar(c);
				    UART_UartPutChar('\n');
			    break;
			}
        }
        // Turn the interrupts back on
        UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY); 
        UART_SetRxInterruptMode(UART_INTR_RX_NOT_EMPTY);
    }
}

Nuke the FRAM Data

I thought that it would be a good idea to have a function to put 0’s in all of the locations in the FRAM, to prove that I could start with a clean slate.  This function does just that.  Specifically it write 0’s to the 64K locations in I2C address 0x50 (the first bank) and to I2C address 0x51 (the second bank)

// This function erases - write 0's into the FRAM... also known as NUKE the FRAM
void blankFRAM(void)
{
    int i;
    UART_UartPutString("Starting Erase 1st Block\n");
    I2C_I2CMasterSendStart(0x50,I2C_I2C_WRITE_XFER_MODE);
    I2C_I2CMasterWriteByte(0);
    I2C_I2CMasterWriteByte(0);
    for(i=0;i<0xFFFF;i++) // Write 64K of 0's to erase first bank
    {
        I2C_I2CMasterWriteByte(0);
    }
    I2C_I2CMasterSendStop();
    UART_UartPutString("Starting Erase 2nd Block\n");
    I2C_I2CMasterSendStart(0x51,I2C_I2C_WRITE_XFER_MODE);
    I2C_I2CMasterWriteByte(0);
    I2C_I2CMasterWriteByte(0);
    for(i=0;i<0xFFFF;i++) // write 64k of 0's to erase 2nd bank
    {
        I2C_I2CMasterWriteByte(0);
    }
    I2C_I2CMasterSendStop();
    
    UART_UartPutString("Erase Complete\n");

}

As always you can find this project on the IoT Expert GitHub site git@github.com:iotexpert/PSoC-FileSystem.git

FreeRTOS FAT SL FileSystem Porting to PSoC4M

CY8CKIT-044 for FreeRTOS FAT FL FileSystem

Summary of FreeRTOS FAT SL FileSystem Port

In the previous article I discussed the Cypress 24V10 FRAM which I am going to use to store nonvolatile data for the FreeRTOS FAT SL FileSystem.  The “SL” in the name stands for “super light” and was built by HCC Embedded for, get this, embedded applications.

In this article I am going to show you how to build a media driver to make the FreeRTOS FAT SL FileSystem work.  In the next article I will talk about how to actually use the FreeRTOS FAT SL FileSystem on the CY8CKIT-044 using the Cypress FMV24V10 FRAM.

The media driver is pretty simple,  you just need to provide the following functions:

And two structures

  • F_DRIVER – Function pointers to the media driver functions
  • F_PHY – Information about the FRAM

To make all of this work I will start by copying the “ram” driver that was provided as an example (ill call mine framdrv_f.c).  The file that I started with resides in FreeRTOS-Plus/Source/FreeRTOS-Plus-FAT-SL/media-drv/ram/ramdrv_f.c

F_DRIVER Structure

The F_DRIVER structure mostly contains function pointers to the driver functions (which you create).  All throughout the FreeRTOS FAT SL FileSystem code it will do things like :

status = mdrv->getstatus( mdrv );

which calls the function in the media driver structure named “getstatus”.

Here is the exact definition of the F_DRIVER STRUCTURE (found in FreeRTOS-Plus-FAT-SL/api/api_mdriver.h) :

typedef struct F_DRIVER  F_DRIVER;

typedef int           ( *F_WRITESECTOR )( F_DRIVER * driver, void * data, unsigned long sector );
typedef int           ( *F_READSECTOR )( F_DRIVER * driver, void * data, unsigned long sector );
typedef int           ( *F_GETPHY )( F_DRIVER * driver, F_PHY * phy );
typedef long          ( *F_GETSTATUS )( F_DRIVER * driver );
typedef void          ( *F_RELEASE )( F_DRIVER * driver );

typedef struct F_DRIVER
{
  unsigned long  user_data;     /* user defined data */
  void         * user_ptr;      /* user define pointer */

  /* driver functions */
  F_WRITESECTOR          writesector;
  F_READSECTOR           readsector;
  F_GETPHY               getphy;
  F_GETSTATUS            getstatus;
  F_RELEASE              release;
} _F_DRIVER;

typedef F_DRIVER *( *F_DRIVERINIT )( unsigned long driver_param );

The user_data and user_ptr do not appear to be used anywhere in the FreeRTOS FAT SL FileSystem code, so I am not sure what they had in mind for those variables.

The F_PHY Structure

The other structure that is needed (sort of) is F_PHY.  This structure contains parameters that were originally meant for disk drives and are no longer used.  For the FRAM I will divide up the 128k array into “sectors” of “512” bytes.  For some reason which I don’t understand the sector size must be fixed to 512 bytes.  The history of FAT filesystems is incredibly messy, and I started to dig through it so that I understood the number, but I am not sure that there are enough hours in my life.

typedef struct
{
  unsigned short  number_of_cylinders;
  unsigned short  sector_per_track;
  unsigned short  number_of_heads;
  unsigned long   number_of_sectors;
  unsigned char   media_descriptor;

  unsigned short  bytes_per_sector;
} F_PHY;

As I wrote this article I didnt remember setting up the F_PHY structure… but things still worked (ill address this later in the article).

F_DRVERINIT()

This function sets up the function pointers and marks the in_use variable.

/****************************************************************************
 *
 * fram_initfunc
 *
 * this init function has to be passed for highlevel to initiate the
 * driver functions
 *
 * INPUTS
 *
 * driver_param - driver parameter
 *
 * RETURNS
 *
 * driver structure pointer
 *
 ***************************************************************************/
F_DRIVER * fram_initfunc ( unsigned long driver_param )
{
  ( void ) driver_param;

    UART_UartPutString("Calling init\n");

  if( in_use )
    return NULL;

  (void)psp_memset( &t_driver, 0, sizeof( F_DRIVER ) );

  t_driver.readsector = fram_readsector;
  t_driver.writesector = fram_writesector;
  t_driver.getphy = fram_getphy;
  t_driver.release = fram_release;

  in_use = 1;

  return &t_driver;
} /* fram_initfunc */

F_GETPHY()

This function returns information about the FRAM including the size (in sectors) and the size of the sectors (in bytes)

/****************************************************************************
 *
 * ram_getphy
 *
 * determinate ramdrive physicals
 *
 * INPUTS
 *
 * driver - driver structure
 * phy - this structure has to be filled with physical information
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
static int fram_getphy ( F_DRIVER * driver, F_PHY * phy )
{
  /* Not used. */
  ( void ) driver;

  phy->number_of_sectors = maxsector;
  phy->bytes_per_sector = F_SECTOR_SIZE;

  return MDRIVER_RAM_NO_ERROR;
}

FRAM Helper Functions

In order to map sectors to the correct I2C address (remember the FRAM has two banks of memory in two different I2C addresses) and to the correct bank address, I created two function which map “sector” into address and I2C address.

static const unsigned long maxsector = FDRIVER_VOLUME0_SIZE / F_SECTOR_SIZE;
static const unsigned long halfsector = FDRIVER_VOLUME0_SIZE / F_SECTOR_SIZE / 2;

/****************************************************************************
 *
 * calcAddress
 *
 * This function takes a sector and returns the address in the bank for the
 * start of the secor
 *
 * INPUTS
 *
 * unsigned long sector - which logical sector in the FRAM
 * 
 * RETURNS
 *
 * The FRAM Address of the sector
 *
 ***************************************************************************/
static inline uint32_t calcAddress(unsigned long sector)
{
    if(sector < halfsector)
        return sector * F_SECTOR_SIZE;
    else
        return (sector-halfsector) * F_SECTOR_SIZE;
}

/****************************************************************************
 *
 * calcI2CAddress
 *
 * This function takes a sector from 0 --> maxsector and figures out which bank
 * the address exists.
 *
 * INPUTS
 *
 * unsigned long sector - which logical sector in the FRAM to write to
 * 
 * RETURNS
 *
 * The I2C Address of Bank 0 or Bank 1
 *
 ***************************************************************************/
static inline uint32_t calcI2CAddress(unsigned long sector)
{
    if(sector < halfsector)
        return 0x50; // I2C Bank 0 Address from the datasheet
    else
        return 0x51; // I2C Bank 0 Address - From the datasheet  
}

F_READSECTOR()

This function reads 512 bytes that are located in the sector into the RAM buffer pointed to by data.  Notice on lines 26 & 27 I put in debugging information.  In order to read you need to

  • Send a start (line 30)
  • Send the I2C address and the write bit
  • Set the address you want to read from (line 37-38)
  • Send a restart (line 41)
  • read the 512 bytes (lines 44-54)
  • On the last byte NAK (line 47-50)
/****************************************************************************
 *
 * fram_readsector
 *
 * This function reads 512 bytes into the SRAM at the pointer data
 *
 * INPUTS
 *
 * driver - driver structure
 * void *data - a pointer to the SRAM where the 512 bytes of data will be written
 * unsigned long sector - which logical sector in the FRAM to read from
 * 
 * RETURNS
 *
 * error code or MDRIVER_RAM_NO_ERROR if successful
 *
 ***************************************************************************/

static int fram_readsector ( F_DRIVER * driver, void * data, unsigned long sector )
{
    char buff[128]; // A scratch buffer for UART Printing
    (void)driver;
    uint16 address;
    uint32_t status;
    
    sprintf(buff,"Read sector %d\n",(int)sector);
    UART_UartPutString(buff);
    
    address = calcAddress(sector);
    status = I2C_I2CMasterSendStart( calcI2CAddress(sector),I2C_I2C_WRITE_XFER_MODE);
    if(status != I2C_I2C_MSTR_NO_ERROR)
    {
        UART_UartPutString("I2C Error\n");
        return MDRIVER_RAM_ERR_SECTOR;
    }
    int i;
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); // 
    
    
    I2C_I2CMasterSendRestart(calcI2CAddress(sector),I2C_I2C_READ_XFER_MODE);
    
    uint8_t d;
    for(i=0;i<F_SECTOR_SIZE;i++)
    {
        
        if(i != F_SECTOR_SIZE - 1)
            d = I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA);
        else
            d = I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA);
         
        *((uint8_t *)data + i) = d;
        
    }
    
    I2C_I2CMasterSendStop();
    
  return MDRIVER_RAM_NO_ERROR;
}

F_WRITESECTOR()

The write sector is very similar to the read.  To write the data you need to

  • Send a start (line 204)
  • Write the address you want to write to (line 205-206)
  • Write the 512 bytes (lines 208-214)
  • Send a stop (line 215)
/****************************************************************************
 *
 * fram_writesector
 *
 * This function takes 512 bytes of user input and writes to the FRAM in the
 *
 * INPUTS
 *
 * driver - driver structure
 * void *data - a pointer to the SRAM where the 512 bytes of data exists
 * unsigned long sector - which logical sector in the FRAM to write to
 * 
 * RETURNS
 *
 * error code or MDRIVER_RAM_NO_ERROR if successful
 *
 ***************************************************************************/

static int fram_writesector ( F_DRIVER * driver, void * data, unsigned long sector )
{
    (void)driver;
    char buff[128]; // A scratch buffer for UART Printing
    uint16 address;
    int i;
        
    sprintf(buff,"Wrote sector %d\n",(int)sector);
    UART_UartPutString(buff);
        
    address = calcAddress(sector);
    I2C_I2CMasterSendStart(calcI2CAddress(sector),I2C_I2C_WRITE_XFER_MODE);
    I2C_I2CMasterWriteByte((address>>8)&0xFF);
    I2C_I2CMasterWriteByte(address & 0xFF); // 
    
    for(i=0;i<F_SECTOR_SIZE;i++)
    {
        uint8_t d = *((uint8_t *)data +i);
       
        I2C_I2CMasterWriteByte(d); 
        
    }
    I2C_I2CMasterSendStop();
    
  return MDRIVER_RAM_NO_ERROR;
}

F_GETSTATUS()

As I wrote the article I noticed in the documentation that I needed to provide the “F_GETSTATUS” function.  The reason that I had not noticed before was that it was not in the media driver provided in the distribution.  That being said, my implementation always returns a 0 indicating OK.

/****************************************************************************
 *
 * fram_getstatus
 *
 * This function must return the status of the drive... F_ST_MISSING, F_ST_CHANGED
 * or F_ST_WRPROECT or 0 (for OK)
 * INPUTS
 *
 * driver_param - driver parameter
 *
 *
 * For the FRAM I dont support any of these other status's
 ***************************************************************************/

static long fram_getstatus(F_DRIVER *driver)
{
    (void) driver;
    // F_ST_MISSING	The media is not present (it has been removed or was never inserted).
    //F_ST_CHANGED	Since F_GETSTATUS() was last called the media has either been removed and re-inserted, or a different media has been inserted.
    //F_ST_WRPROTECT	The media is write protected.
    
    return 0;
}

F_RELEASE()

This function simply set the in_use to 0;

/****************************************************************************
 *
 * fram_release
 *
 * Releases a drive
 *
 * INPUTS
 *
 * driver_param - driver parameter
 *
 ***************************************************************************/
static void fram_release ( F_DRIVER * driver )
{
  /* Not used. */
  ( void ) driver;

  /* Disk no longer in use. */
  in_use = 0;
}

In the next article I will show you the source code for an example project using the FreeRTOS FAT SL Filesystem.

FreeRTOS FAT SL FileSystem – Using the FM24V10 FRAM

Summary

Last week while working on a PSoC FreeRTOS Project, I looked at the CY8CKIT-044 Development kit that I was using and remembered that right under the Ambient Light Sensor there is a Cypress FM24V10 FRAM.  Way back, I started pushing our Development Kit team to put other Cypress parts onto the dev kits…. and because they are awesome, they aggressively started doing that.  One of those chips is the FM24V10 FRAM.  As I looked at the chip, I realized again that I had never really written anything into the FM24V10 FRAM on any of our dev kits, which seems silly after I pushed the dev kit team for them.  I realized that the reason I had never used the FM24V10 FRAM was I didn’t have a nifty way to write files.  But, while working on the other FreeRTOS projects, I noticed the FreeRTOS FAT SL Filesystem is built into FreeRTOS.  The next couple (3?) articles I will talk about making the FreeRTOS FAT SL Filesystem work on a PSoC4200M with a FM24V10 I2C FRAM.

CY8CKIT-044 with FM24V10 FRAM

The Cypress FM24V10 FRAM

The “F” in FRAM stands for ferroelectric.  What that means is each of the memory cells is a little magnet.  So what you say?  Well the “what” is that FRAMs don’t wear out (unlike Flash), you can write individual cells (unlike Flash), and it is super low power, it is plenty fast (I2C = 3.4MBS), and it retains data for a long long time (151 years).

That list sounds great.  Actually really great.  But, what is the but?  There are two answers to that question.  The first answer is there is a density penalty, the biggest I2C FRAM is 1Mb where you can buy a 1Gb NOR Flash or even bigger NAND Flash.  The next issue is price.  The digikey pricing for a 128Kb FRAM is $12.76 or 9.9c/kb and a 1GB NOR Flash is $14.97 or 0.0014c/kb.  That means an FRAM is 6724 x as expensive per bit.

How do I use it?  The FRAM is attached to the I2C pins on the CY8CKIT-044.  That means that I can talk to it with either the Bridge Control Panel, or with the PSoC.  To start with I will show you the Bridge Control Panel.  First, I probe the I2C bus, and I see that there are three I2C devices.

  • 0x0F = The I2C Accelerometer (which I wrote about here)
  • 0x50= The first 64K of FRAM
  • 0x51 = The second 64K of FRAM

The 128K FRAM is divided into two banks which are accessed on separate I2C addresses.  By splitting 128K into two blocks of 64K the chip designers optimized the addressing of the memory.  In order to access 64K you need 16-bits, which means to access 128K you need 17-bits.  This would have required sending a whole extra byte of address when you really only needed 1 of those 8 bits.  They avoid this problem by having two addresses.

The FM24V10 FRAM is easy to use as it implements an EzI2C (like) protocol.  EZI2C keeps an internal address pointer.  The pointer is automatically incremented each time you do an I2C transaction.  When you do a write transaction, the first two bytes you write are placed into the address pointer.  I say “EZI2C like” because the pointer is not retained between transactions.  Some examples:

If I want to write 0x56 into location 0x0123 I would issue

  • I2C Start
  • I2C Write to I2C address 0x50
  • Send 0x01, 0x02 (which write the address pointer)
  • Send 0x56
  • I2C Stop

I can do that sequence with the Bridge Control Panel command “W 50 01 23 56 P;”  The “W” stands for “send a start and send the 7-bit address 0x50 (left shifted 1) and send a ‘0’ for write”.  For some reason the engineers at Philips decided that the I2C protocol would have 7-bit addressing and then 1 bit to represent write or read.  When you talk about I2C there is always confusion about “8-bit address” versus “7-bit address”.  When they talk about 8-bit address they are talking about the 7-bit address shifted left 1 bit and then or-ed with the read or write bit.  Here is a picture from the bridge control panel:

And the same thing from the logic analyzer.

Once I have written 0x56 into address 0x0123, I can read it back by a “w 50 01 23 r 50 x p;” which sends:

  • I2C Start
  • I2C Write to I2C address 0x50
  • Send 0x01, 0x02 (which write the address pointer)
  • I2C Restart
  • Sends 0x50 (shifted left) or-ed with “read”
  • Reads the byte
  • I2C Stop

In the transaction output window below, you can see that the “x” is replaced with “56” meaning that it read the previously written value back.

The only other interesting thing about the protocol is that once you have sent and address, you can keep reading, or writing without sending the address again.  For instance to write 01, 02, 03, 04 to address 23, 23,25 and 26 you can send “W 50 00 23 01 02 03 04 p”, then you can read it back with “W 50 00 23 r 50 x x x x p;”

In the next Article I will start the process of using the Free RTOS File System to write and read from the I2C FRAM.

FreeRTOS PSoC Template Project

Summary

In the last article I showed you clever FreeRTOS PSoC Component… and the talked about some of the issues that I had with it.  In this article I will talk about a self-contained FreeRTOS PSoC Template project that includes everything for FreeRTOS and Tracealyzer all in one project.  My idea was that when I start a new project I will just make a copy of the template project, rename it, then move on.

My FreeRTOS PSoC Template has

  • FreeRTOS with the files in in the same directory (i.e. not referenced externally)
  • Tracealyzer with the files in the same directory & all of the streamports including RTT and the UART DMA
  • All of the files organized into PSoC Creator folders
  • A template main.c
  • All of the build settings configured

This project is checked into GitHub and you can find it git@github.com:iotexpert/PSoC-FreeRTOS-Template.git

Building the FreeRTOS PSoC Template

I started the process by creating a blank PSoC project called “FreeRTOS_Template”.  I then went into the Windows Explorer and copied the entire FreeRTOS source directory into the FreeRTOS PSoC Template Project directory.  Then I copied the Percepio TraceRecorder library into the FreeRTOS PSoC Template project.

FreeRTOS PSoC Template Directory Structure

By having both of the source directories in my project it isolated this project from changes in the FreeRTOS or the TraceRecorder.  Obviously that is a double edged sword.  The next thing that I did was use the Windows Explorer to copy FreeRTOSConfig.h, trcConfig.h, trcSnapshotConfig.h and trcStreamingConfig.h into the project.   Once all of the files were in my project directory is was time to fix up the PSoC Creator Project.

To do this I created a folder called “FreeRTOS_Source” in the “Header Files” and “Source Files” folders in my template project.  You can do this by right clicking and selecting “Add->New Folder”.  Then I added all of the appropriate .h and .c files from FreeRTOS source directory.  This gives me the following project view:

FreeRTOS PSoC Template PSoC Creator Header Files

And Source Files (notice that I also added heap_4.c which I generally use for memory management)

FreeRTOS PSoC Template PSoC Creator Source Files

Then I add the configuration files FreeRTOSConfig.h, trcConfig.h, trcSnapshotConfig.h, trcStreamingConfig.h and trcStreamingport.h.  Next I do the same thing for the TraceRecorder library which makes my project look like this:

Then I modify the build settings to add the include directories:

FreeRTOS PSoC Template Build Settings

FreeRTOS PSoC Template PSoC Creator Include Path

Now you can modify the FreeRTOSConfig.h to include all of the Tracealyzer stuff:

/* A header file that defines trace macro can be included here. */
#if ( configUSE_TRACE_FACILITY == 1 )
#include "trcRecorder.h"
#endif

#endif /* FREERTOS_CONFIG_H */

Then I setup a new tab in the schematic to contain the DMA UART Streamport.  You can read all about the UART DMA Streamport in this article.

FreeRTOS PSoC Template PSoC Creator Streamport Schematic

By putting that part of the stream port on a separate schematic page I can now do a right click and disable the page when I am not using the Tracealyzer streamport.  Disabling a page of the schematic completely removes everything from the build.

PSoC Creator Schematic Disable Settings

Next I create files called FreeRTOS_Start.h/.c to put in the startup code:

#include <project.h>
#include "FreeRTOS.h"

extern void xPortPendSVHandler(void);
extern void xPortSysTickHandler(void);
extern void vPortSVCHandler(void);

#define CORTEX_INTERRUPT_BASE          (16)
void FreeRTOS_Start()
{
    /* Handler for Cortex Supervisor Call (SVC, formerly SWI) - address 11 */
    CyIntSetSysVector( CORTEX_INTERRUPT_BASE + SVCall_IRQn,
        (cyisraddress)vPortSVCHandler );
    
    /* Handler for Cortex PendSV Call - address 14 */
	CyIntSetSysVector( CORTEX_INTERRUPT_BASE + PendSV_IRQn,
        (cyisraddress)xPortPendSVHandler );    
    
    /* Handler for Cortex SYSTICK - address 15 */
	CyIntSetSysVector( CORTEX_INTERRUPT_BASE + SysTick_IRQn,
        (cyisraddress)xPortSysTickHandler );
}

Finally I make a template main.c that starts everything and has a simple ledTask and instructions for changing Memory Management scheme and the TraceRecorder.

// This template has heap_4.c included in the project.  If you want a 
// different heap scheme then remove heap_4 from the project and add 
// the other scheme from FreeRTOS/Source/portable/memmag

// TraceRecorder
// There are methods
// 1. Snapshot mode
// 2. Streaming UART/DMA
// 3. Streaming JLINK RTT
//
// To use method 1: 
// - in FreeRTOSConfig.h make this line be 1 (currently line 45)
//#define configUSE_TRACE_FACILITY                1
// - in trcConfig.h configure the TRC_CFG_RECORDER 
// #define TRC_CFG_RECORDER_MODE TRC_RECORDER_MODE_SNAPSHOT
//
// To use method 2:
// - in FreeRTOSConfig.h make this line be 1 (currently line 45)
//#define configUSE_TRACE_FACILITY                1
// - in trcConfig.h configure the TRC_CFG_RECORDER
//#define TRC_CFG_RECORDER_MODE TRC_RECORDER_MODE_STREAMING
//
// This project currently has the PSoC UART Streaming Port
// add the TraceRecorder/streamports/PSoC_Serial/trcStreamingPort.c to the project
// add the TraceRecorder/streamports/PSoC_Serial/include/trcStreamingPort.h
// add the TraceRecorder/streamports/PSoC_Serial/include to the compiler include directories
// Enable the Trace_DMA schematic sheet and make sure UART pins are assigned correctly
// This port depends on the UART being named UART and the DMA being named "DMA"
//
// To use method 3: JLINK RTT
// Remove the previous streamport files from the project
// Remove the Streamport include path
// add the TraceRecorder/streamports/JLink_RTT/Segger_RTT.c to the project
// add the TraceRecorder/streamports/JLink_RTT/Segger_RTT_Printf.c to the project
// add the TraceRecorder/streamports/JLink_RTT/include/trcStreamingPort.h
// add the TraceRecorder/streamports/JLink_RTT/include/Segger_RTT.h
// add the TraceRecorder/streamports/JLink_RTT/include/Segger_RTT_Conf.h
// add the TraceRecorder/streamports/JLink_RTT/include to the compiler include directories


#include "project.h"
#include "FreeRTOS.h"
#include "timers.h"

// An example Task
void ledTask(void *arg)
{
    (void)arg;
    while(1)
    {
        RED_Write(~RED_Read());
        vTaskDelay(500);  
    }
}

int main(void)
{
    CyGlobalIntEnable;
    FreeRTOS_Start();
    
    #if ( configUSE_TRACE_FACILITY == 1 )
    vTraceEnable(TRC_START);
    #endif
    
    xTaskCreate(ledTask,"LED Task",configMINIMAL_STACK_SIZE,0,1,0);
    vTaskStartScheduler();  // Will never return
    while(1);               // Eliminate compiler warning
}

 

Topic Description
FreeRTOS: A PSoC4 FreeRTOS Port An introduction to making FreeRTOS work on PSoC 4
FreeRTOS PSoC Examples Using multiple tasks in FreeRTOS
FreeRTOS Queue Example Using a queue to communicate between tasks
PSoC 6 FreeRTOS - The First Example Booting FreeRTOS on PSoC 6
FreeRTOS Binary Semaphore An first example of a binary semaphore
FreeRTOS Binary Semaphore (Part 2) Removing polling in the UART Task
FreeRTOS Counting Semaphore An example using a counting semaphore
PSoC FreeRTOS Reading I2C Sensors with a shared I2C Bus
PSoC FreeRTOS Task Notify A light weight scheme to replace Semaphores
PSoC FreeRTOS Task Notification Values A very light weight method to transfer one word of information into a task

FreeRTOS PSoC Component

Summary

This weekend I found myself down a rabbit hole, beating my head on the wall trying to make a USB driver work properly inside of Parallels & Windows 10 on my Mac (which I still don’t have quite right).  While going through that excruciating process I ended up creating 3-4-5 different FreeRTOS PSoC projects, which although not difficult, is still a bit of a pain as it requires 1/2 dozen step.  After reflecting on it a while I decided that I needed something easier.  The obvious thing to do was to build a FreeRTOS PSoC Component, which I did, but eventually abandoned (Ill talk about that process in the next section).  After the component idea was abandoned I decided to just make a template project that could be used to start a FreeRTOS PSoC Project (the next Article).

In this article I will show you:

  • An example project using a clever FreeRTOS PSoC Component that I found on GitHub built by E2ForLife
  • A discussion of the implementation details of that FreeRTOS PSoC Component
  • A discussion of problems with that implementation which lead me to build a template project.

FreeRTOS PSoC Component Example Project

When I first started looking at FreeRTOS I wasn’t totally sure where to start.  When I googled FreeRTOS PSoC, one of the first hits was this repo which contains a PSoC Component that was built by “E2ForLife”.  I really liked the idea of a component based FreeRTOS implementation, as all you would need to do is place the components… and then away you go.  The more that I looked at the component the more that I liked what E2ForLife had done.  When I cloned his repo, there was “nothing” in it, but it turns out there is another branch called “Implement-PSoC5” which makes me think that he was partially done (he hasn’t been changed since August 2015)

First, his example project.  When you look at the schematic you see a nice looking FreeRTOS PSoC Symbol (obviously E2ForLife draws way better than me).

FreeRTOS PSoC Component

When you double click the component you see all of the stuff that is normally in FreeRTOSConfig.h which you can set using the GUI instead of the FreeRTOSConfig.h

FreeRTOS PSoC Component Configuration

When you “Generate Application” you can see that he setup all of the FreeRTOS files to come into the Generated Source automatically (though with different names)

FreeRTOS PSoC Component API

And his FreeRTOS PSoC Component example project is straight forward

#include <project.h>

xTaskHandle redTask;
xTaskHandle blueTask;
xTaskHandle greenTask;

void vRedTask( void* pvParameters);
void vGreenTask( void* pvParameters);
void vBlueTask( void* pvParameters);

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

	xTaskCreate(vRedTask,"red",80,NULL,3,&redTask);
	xTaskCreate(vGreenTask,"green",80,NULL,3,&greenTask);

	FreeRTOS_Start();
	
    for(;;);
}

void vRedTask( void* pvParameters)
{
	for(;;) {
		vTaskDelay( 125/portTICK_RATE_MS );
		RED_Write( ~RED_Read() );
	}
}

void vGreenTask( void* pvParameters)
{
	for(;;) {
		vTaskDelay( 219/portTICK_RATE_MS );
		GREEN_Write( ~GREEN_Read() );
	}
}

He built a function called “FreeRTOS_Start()” which installs the interrupt vectors then starts up the scheduler.

uint8 FreeRTOS_initVar = 0;

/* ------------------------------------------------------------------------ */
void FreeRTOS_Init( void )
{
    /* Handler for Cortex Supervisor Call (SVC, formerly SWI) - address 11 */
    CyIntSetSysVector( CORTEX_INTERRUPT_BASE + SVCall_IRQn,
        (cyisraddress)vPortSVCHandler );
    
    /* Handler for Cortex PendSV Call - address 14 */
	CyIntSetSysVector( CORTEX_INTERRUPT_BASE + PendSV_IRQn,
        (cyisraddress)xPortPendSVHandler );    
    
    /* Handler for Cortex SYSTICK - address 15 */
	CyIntSetSysVector( CORTEX_INTERRUPT_BASE + SysTick_IRQn,
        (cyisraddress)xPortSysTickHandler );
	
	FreeRTOS_initVar = 1;
}
/* ------------------------------------------------------------------------ */
void FreeRTOS_Enable( void )
{
	/* start the scheduler so the tasks will start executing */
	vTaskStartScheduler();	
}
/* ------------------------------------------------------------------------ */
void FreeRTOS_Start( void )
{
	if (FreeRTOS_initVar == 0) {
		FreeRTOS_Init();
	}
	FreeRTOS_Enable();
	
	/*
	 * After the scheduler starts in Enable(), the code should never get to
	 * this location.
	 */
	for (;;);
}

And when you run the FreeRTOS PSoC Component project you get the nice blinking LED (Red and Green)

CY8CKIT-042

FreeRTOS PSoC Component

After downloading the project, then opening it I first wanted to look at the components.  You can do this by clicking the “Components” tab in the workspace explorer.  It looks like he created (or was planning to create) several other components in addition to the “FreeRTOS”.

FreeRTOS PSoC Component

When you double click on the “FreeRTOS_v8_2.cysym” you will get an editable view of the symbol.

FreeRTOS PSoC Component Symbol

When you right click on the blank part of the canvas, PSoC Creator will bring up this menu.

PSoC Creator Configuration

On the properties menu you can configure which tab the component belongs to in the Component Catalog.  In this case he created a tab called “Community” which looks like this in the actual Component Catalog.

PSoC Creator Component Catalog

To make that happen he setup the “Doc.CatalogPlacement” to be “Community/Operating System/FreeRTOS” (on the properties menu)

FreeRTOS PSoC Component Parameter Configuration

The next thing that he did was add a whole bunch of Symbol Parameters which will let the user change the setup of FreeRTOS. Each  of these parameters show up as an editable field on the component customers (in the schematic).

FreeRTOS PSoC Parameter

In the picture above you can see that he created two new “enumerated” datatypes called “FreeRTOS_CheckStackOverFlowType” “FreeRTOS_MemMangType”.  In the picture below you can see the legal values for that type.  This lets him restrict the things that the user of the component can select when he places it in his schematic.

FreeRTOS PSoC Component Configuration

Here is what the component looks like when the user actually uses it.  You can see the legal fields (from above) and the legal values for those fields (from above)

FreeRTOS PSoC Component Configuration

The next thing that he did was copy all of the FreeRTOS files into component API.

FreeRTOS PSoC Component API

If you remember each FreeRTOS project needs to have a file called “FreeRTOSConfig.h”.  But, that file doesn’t appear to exist in the list above.  How is that?  Remember that when PSoC Creator builds a project it copies the API into your generated source directory, but it renames each of the files to be INSTANCE_NAME_filename.  In this example, “Config.h” will become “FreeRTOS_Config.h” (FreeRTOS is the instance name in the schematic)

In FreeRTOS each of the features of the RTOS turn into a #define that is 0 or 1.  When the component was created he modified “Config.h” so that each of the parameters on the component set values for the #defines in the “Config.h” file.  And there is a bunch of them so that must have taken a good bit of work.  Here is an example of a section of his “Config.h”.  Each place you see the back-tick $parameter back-tick PSoC Creator will substitute the value of the parameter (I am typing back-tick because WordPress interprets the actual symbol to mean something special and I don’t know how to get it into the text)

#if CY_PSOC4
    
    /* Port-dependent settings - not user-editable */
    #define configCPU_CLOCK_HZ          ( ( unsigned long ) CYDEV_BCLK__SYSCLK__HZ )
        
    /* Application settings - user editable */
    #define configMAX_PRIORITIES		( `$MAX_PRIORITIES` )
    #define configTOTAL_HEAP_SIZE		( `$TOTAL_HEAP_SIZE` )
    
#elif CY_PSOC5
    
    /* Device settings - not user-editable */
    #define configCPU_CLOCK_HZ			( ( unsigned long ) BCLK__BUS_CLK__HZ )
        
    /* Application settings - user editable */
    #define configMAX_PRIORITIES        ( `$MAX_PRIORITIES` )
    #define configTOTAL_HEAP_SIZE		( `$TOTAL_HEAP_SIZE` ) 
    
#else
    
    #error "This FreeRTOSConfig.h file is for PSoC 4 and PSoC 5LP devices only"
    
#endif

#define configUSE_PREEMPTION			`$USE_PREEMPTION`
#define configUSE_IDLE_HOOK				`$USE_IDLE_HOOK`
#define configUSE_TICK_HOOK				`$USE_TICK_HOOK`
#define configTICK_RATE_HZ				( ( portTickType ) `$TICK_RATE_HZ` )

Because each filename changes name, he had to go fix all of the #includes to look like this example from croutine.c (this was a bunch of work)

/* PSoC Component Customizations */

#include "`$INSTANCE_NAME`.h"
#include "`$INSTANCE_NAME`_task.h"
#include "`$INSTANCE_NAME`_croutine.h"

He also wanted to be able to have all of the FreeRTOS memory management schemes included in the project at one time.  To solve this problem he put an #if around all of the code in heap_1, heap_2 … that adds and removes the appropriate memory manager.

#if (`$MemManager` == 2)

.
.
.
.
.

#endif

A FreeRTOS PSoC Template Project

When I started working on FreeRTOS I knew that I wanted to use V9.0.  But the component was setup for V8.2.  So I launched into the process of converting the component.  This turned out to be a colossal pain in the ass because of the massive number of “small” changes.  In addition I didn’t really like having to change the names of key files (even the stupidly named semphr.h).  Moreover, the source code as a component debate was raging wildly inside of Cypress and in general is on the way out.  So I decided to implement this as a template project instead of a component.

All that being said, what E2ForLife did I continue to think was really clever.

Topic Description
FreeRTOS: A PSoC4 FreeRTOS Port An introduction to making FreeRTOS work on PSoC 4
FreeRTOS PSoC Examples Using multiple tasks in FreeRTOS
FreeRTOS Queue Example Using a queue to communicate between tasks
PSoC 6 FreeRTOS - The First Example Booting FreeRTOS on PSoC 6
FreeRTOS Binary Semaphore An first example of a binary semaphore
FreeRTOS Binary Semaphore (Part 2) Removing polling in the UART Task
FreeRTOS Counting Semaphore An example using a counting semaphore
PSoC FreeRTOS Reading I2C Sensors with a shared I2C Bus
PSoC FreeRTOS Task Notify A light weight scheme to replace Semaphores
PSoC FreeRTOS Task Notification Values A very light weight method to transfer one word of information into a task

PSoC FreeRTOS Task Notification Value

Summary

In the previous article I showed you how to use the FreeRTOS task notification mechanism to replace a binary semaphore.  In this article I will build PSoC FreeRTOS Task Notification Value firmware.  Every FreeRTOS task has one built-in “uint32_t” which you can use to pass information between the task that sends the notification and the task that receives the notification.  In this article, instead of using the task notification as a binary semaphore, I will use it as a Queue of depth 1 that can hold one 32-bit word.  I will also show you a lesson that I learned about the interrupt registers in the SCB UART.

PSoC FreeRTOS Task Notification Value

I start by copying the project 9-TaskNotify and calling it 10-TaskNotifyValue.  I thought that it would be interesting to pass the cause of the UART interrupt to the UART Task, so that it could be told to the user.  To do this I call “xTaskNotifyFromISR” instead of “vTaskGiveFromISR”.   This function lets me set the value of the FreeRTOS Task notification value, is case to the bit mask of the cause of the UART interrupt.  The function also lets you specific if you want to

  • eNoAction – don’t do anything (this is what the xTaskNotifyFromISR does)
  • eSetBits – or the current notification value with the value you send
  • eIncrement – increment the notification value by 1 (in which case it ignore the value you send)
  • eSetValueWithOverwrite – replace the current notification value with the value passed in this function
  • eSetValueWithoutOverwrite – if there is no pending write, then write the value from this function into the notification value

In the UART_Task I take the value and then clear all of the bit (aka set it to 0) when I exit.

CY_ISR(uartHandler)
{
    uint32_t intrBits = UART_GetRxInterruptSourceMasked();
    UART_SetRxInterruptMode(0); // Turn off the Rx interrupt
    BaseType_t xHigherPriorityTaskWoken;
    xTaskNotifyFromISR( uartTaskHandle,intrBits,eSetValueWithOverwrite,&xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
void UART_Task( void *arg)
{
    (void)arg;
    char c;
    UART_Start();
    UART_SetCustomInterruptHandler(uartHandler);
    while(1)
    {
        uint32_t intrBits;
              
        xTaskNotifyWait( 0x00,         /* Don't clear any bits on entry. */
                         ULONG_MAX,          /* Clear all bits on exit. */
                         &intrBits, 
                         portMAX_DELAY );    /* Block indefinitely. */
      
        switch(intrBits)
        {
            case UART_INTR_RX_NOT_EMPTY:
                UART_UartPutString("Interrupt: FIFO Not Empty\n");
            break;
            
            case UART_INTR_RX_ERR:
                UART_UartPutString("Interrupt: Error\n");
            break;
                
            case UART_INTR_RX_FULL:
                UART_UartPutString("Interrupt: FIFO Full\n");
            break;
            
            default:
                UART_UartPutString("Interrupt: Unknown\n");
            break;
        }
        
        while(UART_SpiUartGetRxBufferSize())
        {
            c = UART_UartGetChar();
            UART_UartPutString("Char = ");
            UART_UartPutChar(c);
            UART_UartPutString("\n");
        }
        // re-enable the interrupt
        UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY);
        UART_SetRxInterruptMode(UART_INTR_RX_NOT_EMPTY);
    }
}

New Learning

When I first wrote the program I had something “weird” happening.  Specifically it looked like I was getting the interrupt service routine called twice:

I originally wrote the code like this:

CY_ISR(uartHandler)
{
    uint32_t intrBits = UART_GetRxInterruptSourceMasked();
    UART_SetRxInterruptMode(0); // Turn off the Rx interrupt
    UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY);
    BaseType_t xHigherPriorityTaskWoken;
    xTaskNotifyFromISR( uartTaskHandle,intrBits,eSetValueWithOverwrite,&xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

But, what happens is:

  1. Turn off interrupts
  2. Clear the interrupt source (meaning turn off the flag in the SCB that says there is a character in the Rx Buffer)
  3. One or so UART clock cycles later the flag is reset (because there is still something in the UART Rx Buffer)
  4. Send the notification to the UART Task
  5. The UART Task wakes up and processes the UART Rx Buffer
  6. The UART Task turns back on the interrupts
  7. The interrupt is called because the RX_NOT_EMPTY flag is still set (it is set until it is clear)
  8. The interrupt handler clears the flag (which isn’t reset this time because there is nothing in the Rx buffer)
  9. The interrupt handler sends the notification
  10. The UART Task wakes up again… prints out the Flag..
  11. The UART Task tries to read out of the Rx Buffer… but there isn’t anything in it.

Each time I start thinking that I know what I am doing, I find something else to learn.  I suppose that is what makes this whole thing fun though.

Topic Description
FreeRTOS: A PSoC4 FreeRTOS Port An introduction to making FreeRTOS work on PSoC 4
FreeRTOS PSoC Examples Using multiple tasks in FreeRTOS
FreeRTOS Queue Example Using a queue to communicate between tasks
PSoC 6 FreeRTOS - The First Example Booting FreeRTOS on PSoC 6
FreeRTOS Binary Semaphore An first example of a binary semaphore
FreeRTOS Binary Semaphore (Part 2) Removing polling in the UART Task
FreeRTOS Counting Semaphore An example using a counting semaphore
PSoC FreeRTOS Reading I2C Sensors with a shared I2C Bus
PSoC FreeRTOS Task Notify A light weight scheme to replace Semaphores
PSoC FreeRTOS Task Notification Values A very light weight method to transfer one word of information into a task

PSoC FreeRTOS Task Notify

Summary

I have been writing a bunch of articles about implementing PSoC FreeRTOS so, this morning I was reading the FreeRTOS manual (yes I am one of those…) and I noticed a section in the API guide that I hadn’t see before… Task Notifications.  Every task in the FreeRTOS has a built in 32-bit integer notification value.  This value is super light weight and can be used like a task specific counting semaphore, or a signaling bit mask, or binary semaphore.  The API includes:

It seems like this API is good for the situations when your Semaphore has a specific task target in mind.  I thought that this would be a perfect scheme to have a PSoC FreeRTOS UART ISR signal the UART Handling task that there is data available to do something with.

Setup the PSoC FreeRTOS Project

I start this process by making a copy of “1-BlinkingLED” (which already has all of the FreeRTOS stuff in it) and naming it “9-TaskNotify”.  Then I add a UART to the schematic and name it “UART”

PSoC FreeRTOS Schematic

I attach the UART to the right pins on the CY8CKIT-044 kit.

PSoC Creator Pin Assignment
Next I turn on the interrupt which will be called when there is data in the receive FIFO.

PSoC UART Configuration

PSoC FreeRTOS UART Code

Now that the schematic is all configured I update my firmware.  The function “uartHandler” is called when there is data in the UART RX FIFO.  It turns of the interrupts for the UART (which I will turn back on after I have cleared the data in the input buffer), clears the interrupt  (so that it will stop pending) and then sends the notification to the UART_Task.

The UART Task just registers the handler… then while(1)’s until the end of time.  It waits for a notification, then reads data out of the RX fifo and puts out,  then re-enables the interrupts.

CY_ISR(uartHandler)
{
    BaseType_t xHigherPriorityTaskWoken;

    // disable the interrupt
    UART_SetRxInterruptMode(0);
    vTaskNotifyGiveFromISR(uartTaskHandle,&xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

void UART_Task( void *arg)
{
    (void)arg;
    char c;
    UART_Start();
    UART_SetCustomInterruptHandler(uartHandler);
    while(1)
    {
        ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
        while(UART_SpiUartGetRxBufferSize())
        {
            c = UART_UartGetChar();
            UART_UartPutChar(c);
        }
        // clear & re-enable the interrupt
        UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY);
        UART_SetRxInterruptMode(UART_INTR_RX_NOT_EMPTY);
    }
}

Topic Description
FreeRTOS: A PSoC4 FreeRTOS Port An introduction to making FreeRTOS work on PSoC 4
FreeRTOS PSoC Examples Using multiple tasks in FreeRTOS
FreeRTOS Queue Example Using a queue to communicate between tasks
PSoC 6 FreeRTOS - The First Example Booting FreeRTOS on PSoC 6
FreeRTOS Binary Semaphore An first example of a binary semaphore
FreeRTOS Binary Semaphore (Part 2) Removing polling in the UART Task
FreeRTOS Counting Semaphore An example using a counting semaphore
PSoC FreeRTOS Reading I2C Sensors with a shared I2C Bus
PSoC FreeRTOS Task Notify A light weight scheme to replace Semaphores
PSoC FreeRTOS Task Notification Values A very light weight method to transfer one word of information into a task

Percepio Tracealyzer – Analyzing the PSoC Tracealyzer Streamport

Summary

In the previous article I showed you how to make a PSoC Tracealyzer Streamport using the SCB based UART on a PSoC 4200M.  It didn’t take long for me to realize that 115200 baud was not going to cut it.   That realization lead me to figure out that the KitProg on the CY8CKIT-044 was limited to 115200, which I worked around by using the Cypress USB Serial Bridge that I got from the CY8CKIT-049.  But when I look at the CPU graph I found out that it took 50% of the CPU to support streaming the data.  So now what?  In this Article lets analyze the data, both old school with a logic analyzer, and new school with PSoC Tracealyzer.

Analyzing the PSoC Tracealyzer Data

While I was capturing the streaming data from PSoC Tracealyzer I could see that the CPU was burning about 50% of the time just running the TzCntrl and blinking LED thread.  That isn’t good.

PSoC Tracelyzer

When you look at the PSoC Tracealyzer trace viewing you can see the problem is the TzCtrl task.  This task is running for 83 milliseconds and using 57.6% of the CPU.

PSoC Tracelyzer - analyzing the CPU Usage

If you look at the data transmit function called “PSoC_Transmit”, there isn’t much going on.  Just a call to UART_SpiUartPutArray.  But if you look at the UART documentation you will find that SpiUartPutArray is a blocking function, meaning it doesn’t return until it is done.  If you calculate the time to transfer a block of 1024 bytes at 8bits/byte and 230400 baud you will find that just sending the data takes 35ms.

int32_t PSoC_Transmit(void* data, uint32_t size, int32_t *numOfBytesSent )
{
    timing_Write(1);
    UART_SpiUartPutArray((uint8_t *)data,size);
    *numOfBytesSent=size;
    timing_Write(0);
    return 0; // Doesnt matter what you return... i dont think that it is checked
}

I was not very sure why the task was taking 83 milliseconds to run when the data transfer was only taking 40ms.  To figure this out I added a toggle pin to the data write routine, just to make sure.  What I found is the data write routine is getting called about every 70ms takes ~30ms to run.

Old school with Salae Logic

How is that possible, the calculation says that it should take 35ms.  It turns out that I configured the UART to have a 64 byte buffer.

PSoC Creator SCB Configuration

When you remove the 64 bytes from the calculation you end up with 32ish milliseconds.  The other thing that this chart shows is that the TzCntrl task is calling the PSoC_Transmit function more frequently that I thought, at least twice per cycle.  You can also see from the plot that function is taking 30/70=42.8% of the CPU by itself.  Not good, but we are starting to understand what is going on.

As I typed this part of the Article I realized that toggling a GPIO probably wasn’t the best way to figure out what was happening.  In fact, that is the whole point of Tracealyzer, that is analyzing the performance of your program.  When I looked at the Tracealyzer documentation (I always hate doing that), I found a nice function called vTracePrintf.  I added a “toggle” aka printing a 0 and printing a 1 into trace channel 0.

int32_t PSoC_Transmit(void* data, uint32_t size, int32_t *numOfBytesSent )
{
    timing_Write(1);
    vTracePrintF(0,"1");
    UART_SpiUartPutArray((uint8_t *)data,size);
    vTracePrintF(0,"0");
    timing_Write(0);
    *numOfBytesSent=size;
    
    return 0; // Doesnt matter what you return... i dont think that it is checked
}

After running another trace, look what I get.  You can see the “1” printing at the start and the 0 printing at the end.

PSoC Tracelyzer - Analyzing the CPU Usage with vTaskPrintF

Even better, when I double clicked on the “[Default Channel] 0” it took me to this nice screen which shows when the events occurred.  The one that I clicked on started at 8.136.039 and ended at 8.176.176, in other words it took about 40ms

PSoC Tracelyzer - User Events from Tracelyzer

After hand calculating the time, I realized that once again I should have done something even more obvious, let the ARM calculate the time.  the vTracePrintf will let you specify the output format, in this case a %d

int32_t PSoC_Transmit(void* data, uint32_t size, int32_t *numOfBytesSent )
{
    TickType_t time;
    
    timing_Write(1);
    
    time = xTaskGetTickCount();
    UART_SpiUartPutArray((uint8_t *)data,size);
    time = xTaskGetTickCount() - time;
    vTracePrintF(0,"%d",time);
    timing_Write(0);
    *numOfBytesSent=size;
    
    return 0; // Doesnt matter what you return... i dont think that it is checked
}

When you look at the trace, you need to click on the “User Events” to see the print outs from the trace.

PSoC Tracelyzer - CPU Usage

Now that we have a pretty good feel for what is going on, how do we fix it?  Simple, use the PSoC DMA take the CPU mostly out of the UART transmission path, which is the topic of the next Article.

As always you can find all of these projects on the IotExpert GitHub site or git@github.com:iotexpert/PSoC-Tracelyzer.git

Article Description
Percepio Tracealyzer & PSoC An Introduction to Percepio Tracealyzer on the Cypress PSoC
Percepio Tracealyzer RTT Streamport - PSoC4200M Make the JLINK RTT Library work with Tracealyzer
Percepio Tracealyzer PSoC UART Streamport Creating a UART Streamport
Percepio Tracealyzer - Analyzing the PSoC Tracealyzer Streamport Figure out what is broken in the UART Streamport
Percepio Tracealyzer - Using PSoC DMA to Fix the UART Streamport Implementing PSoC DMA to improve the CPU Utilization
Percepio Tracealyzer - Running on PSoC6 Porting the Percepio Tracealyzer to PSoC6

PSoC FreeRTOS & Kionix KXTJ2 Accelerometer

Summary

In the previous PSoC FreeRTOS article I showed you a scheme for sharing the I2C between multiple tasks by sending “I2C Transactions” to a single I2C Master thread.  In this article I will modify the Accelerometer task to use the hardware interrupt pin from the KXTJ2-1009 to only read data when there is new data to be read (i.e. stop polling).  I will also modify the project to use “kxtj2.h” a header file which I downloaded from Rohm’s website with all of the register definitions.

Updates to the PSoC FreeRTOS Project Schematics

When I was looking at the schematics for the CY8CKIT-044 I noticed that the KXTJ2-1009 had one of the pins attached to the PSoC P1[6].  Even better that pin was labeled “INT”.  In the original PSoC FreeRTOS project I polled the values from the accelerometer, which is almost always a bad idea.

 

CY8CKIT-044 Schematic

Then when you look in the data sheet you see that you ask for an interrupt when the data is ready “DRDYE”

KTXJ2-1009 Datasheet

And you can program the INT pin to behave how you want.  I chose:

  • IEN=1 (Enable interrupts)
  • IEA=1 (Make the interrupt active high)
  • IEL=1 (Pulse the pin)

KTXJ2-1009 Datasheet

After I figured all of that out, I modify the PSoC FreeRTOS Creator schematic to have the accel_int_pin (aka P1[6]) attached to an interrupt.

PSoC FreeRTOS Schematic

Then I write an ISR to handle the interrupt, which just resets the pin interrupt and sets a semaphore.

CY_ISR(accelIntHandler)
{
    accel_int_pin_ClearInterrupt();
    xSemaphoreGiveFromISR(accelIntSemaphore,NULL);
}

Finally register the interrupt and the start the semaphore

   accelIntSemaphore = xSemaphoreCreateBinary(); // Signal from the Accel that it is ready
   accel_int_StartEx(accelIntHandler);

Using the “kxtj2.h” Header File

When I looked on the Kionix website for the KXTJ2-1009 datasheet I noticed that there was a download link… which I assumed would take me to a place where I could download the datasheet.

KTXJ2-1009 Datasheet

But when I go to the download page I got the  pleasant surprise of finding a bunch of driver files… but even better there was a “Register Definitions .h/.py”

KTXJ2-1009 Datasheet Download

This file had a bunch of #defines for the registers in the chip.

/* registers */
// output register x
#define KXTJ2_OUTX_L 0x06
#define KXTJ2_OUTX_H 0x07
// output register y
#define KXTJ2_OUTY_L 0x08
#define KXTJ2_OUTY_H 0x09
// output register z
#define KXTJ2_OUTZ_L 0x0A
#define KXTJ2_OUTZ_H 0x0B
// This register can be used to verify proper integrated circuit functionality
#define KXTJ2_DCST_RESP 0x0C
// This register can be used for supplier recognition, as it can be factory written to a known byte value.
#define KXTJ2_WHO_AM_I 0x0F
// This register reports which function caused an interrupt.
#define KXTJ2_INT_SOURCE1 0x16
// This register reports the axis and direction of detected motion
#define KXTJ2_INT_SOURCE2 0x17
// This register reports the status of the interrupt

In addition it has the the bit fields for the registers

// 12.5Hz
#define KXTJ2_DATA_CTRL_REG_OSA_12P5 (0x00 << 0)
// 25Hz
#define KXTJ2_DATA_CTRL_REG_OSA_25 (0x01 << 0)
// 50Hz
#define KXTJ2_DATA_CTRL_REG_OSA_50 (0x02 << 0)
// 100Hz
#define KXTJ2_DATA_CTRL_REG_OSA_100 (0x03 << 0)
// 200Hz
#define KXTJ2_DATA_CTRL_REG_OSA_200 (0x04 << 0)
// 400Hz
#define KXTJ2_DATA_CTRL_REG_OSA_400 (0x05 << 0)
// 800Hz
#define KXTJ2_DATA_CTRL_REG_OSA_800 (0x06 << 0)
// 1600Hz
#define KXTJ2_DATA_CTRL_REG_OSA_1600 (0x07 << 0)
// 0.78Hz
#define KXTJ2_DATA_CTRL_REG_OSA_0P781 (0x08 << 0)
// 1.563Hz
#define KXTJ2_DATA_CTRL_REG_OSA_1P563 (0x09 << 0)
// 3.125Hz
#define KXTJ2_DATA_CTRL_REG_OSA_3P125 (0x0A << 0)
// 6.25Hz
#define KXTJ2_DATA_CTRL_REG_OSA_6P25 (0x0B << 0)

To use it all I did was add it to my project by right clicking on Header Files and selecting “Add–>Existing Item”

Add header file to PSoC FreeRTOS Project

Then I add the include “ktxk2.h” to the includes

#include "kxtj2.h"

Finally I can use the #define names instead of the hardcoded values for example to reset the chip you can write to the KXTJ2_CTRL_REG with the value  KXTJ2_CTRL_REG _SRST

    setupData[0] = KXTJ2_CTRL_REG2_SRST; // Software reset
    mytransaction.i2cm_register = KXTJ2_CTRL_REG2; 

Updates to the Accelerometer Task

With all of the updates in place I can now make the modifications to the PSoC FreeRTOS accel_Task to use the new interrupt structure.  The first thing to do is write into the reset register to force a software reboot.  The datasheet says that you need to:

  • Write 0x00 into the CTRL_REG1
  • Write KXTJ2_CTRL_REG2_SRST into KXTJ2_CTRL_REG2
  • Then WAIT until the value in KXTJ2_CTRL_REG2 is 0x0
    // Setup for initialization
    uint8_t setupData[1];
    mytransaction.i2cm_method = I2CM_WRITE;
    mytransaction.i2cm_byteNum = 1;
    mytransaction.i2cm_doneSemaphore = mySemaphore;
    mytransaction.i2cm_bytes = setupData;
    
    // reset the accelerometer
    setupData[0] = 0b00000000; 
    mytransaction.i2cm_register = KXTJ2_CTRL_REG1; 
    i2cm_runTransaction(&mytransaction);
  
    setupData[0] = KXTJ2_CTRL_REG2_SRST; // Software reset
    mytransaction.i2cm_register = KXTJ2_CTRL_REG2; 
    i2cm_runTransaction(&mytransaction);

    vTaskDelay(10); // From the datasheet power up time

    while(1) // delay until the accelerometer is reset
    {
        mytransaction.i2cm_method = I2CM_READ;  
        mytransaction.i2cm_register = KXTJ2_CTRL_REG2;
        i2cm_runTransaction(&mytransaction);
        
        if(setupData[0] & KXTJ2_CTRL_REG2_SRST)
        {
            vTaskDelay(10); // backup 10 ms
        }
        else
            break;  
    }
    

After the accelerometer chip is reset, then initialize the chip

    // initialize the interrupt 
    setupData[0] = KXTJ2_INT_CTRL_REG1_IEN | KXTJ2_INT_CTRL_REG1_IEA | KXTJ2_INT_CTRL_REG1_IEL;
    mytransaction.i2cm_register = KXTJ2_INT_CTRL_REG1; 
    i2cm_runTransaction(&mytransaction);
  
    // initialize the  DATA_CTRL_REG
    setupData[0] = KXTJ2_DATA_CTRL_REG_OSA_12P5;      
    mytransaction.i2cm_register = KXTJ2_DATA_CTRL_REG; 
    i2cm_runTransaction(&mytransaction);
 
    // Initalize the acceleromter CTRL_REG_1
    setupData[0] =   KXTJ2_CTRL_REG1_PC | KXTJ2_CTRL_REG1_RES | KXTJ2_CTRL_REG1_DRDYE | KXTJ2_CTRL_REG1_GSEL_2G  ; 
    mytransaction.i2cm_register = KXTJ2_CTRL_REG1; 
    i2cm_runTransaction(&mytransaction);
    
    // Setup for repeated reads
    mytransaction.i2cm_method = I2CM_READ;
    mytransaction.i2cm_register = 0x06;
    mytransaction.i2cm_byteNum = sizeof(myData);
    mytransaction.i2cm_doneSemaphore = mySemaphore;
    mytransaction.i2cm_bytes = (uint8_t *)&myData;

After that you can wait for the semaphore, then get the data

    while(1)  // Run the same transaction over and over
    {
        xSemaphoreTake(accelIntSemaphore,portMAX_DELAY);
        
        i2cm_runTransaction(&mytransaction);
        myData.x /= 16;  // The KXTJ2 Datasheet say lower 4 bits are X
        myData.y /= 16;  // The KXTJ2 Datasheet say lower 4 bits are X
        myData.z /= 16;  // The KXTJ2 Datasheet say lower 4 bits are X
        xs = (myData.x < 0? -1:1);  // Find the XSign
        ys = (myData.y < 0? -1:1);  // Find the YSign
        zs = (myData.z < 0? -1:1);  // Find the ZSign
            
        // The 1024 is a hardcode based on the range of the acceleromter
        x = (int)((float)myData.x / 1024.0 * 10.0) * xs; // Turn it into integer G * 10
        y = (int)((float)myData.y / 1024.0 * 10.0) * ys; // Turn it into integer G * 10
        z = (int)((float)myData.z / 1024.0 * 10.0) * zs; // Turn it into integer G * 10
       
        // Format data in x.x  
        sprintf(buff,"x=%s%d.%dg\ty=%s%d.%dg\tz=%s%d.%dg\n",(xs<0?"-":""),x/10,x%10,(ys<0?"-":""),y/10,y%10,(zs<0?"-":""),z/10,z%10);
        UART_UartPutString(buff); 
    }

As always you can git this PSoC FreeRTOS project at the IoT Expert GitHub site or git@github.com:iotexpert/PSoC-FreeRTOS-Examples.git The project name is “8-Shared-I2C_Interrupt”

Topic Description
FreeRTOS: A PSoC4 FreeRTOS Port An introduction to making FreeRTOS work on PSoC 4
FreeRTOS PSoC Examples Using multiple tasks in FreeRTOS
FreeRTOS Queue Example Using a queue to communicate between tasks
PSoC 6 FreeRTOS - The First Example Booting FreeRTOS on PSoC 6
FreeRTOS Binary Semaphore An first example of a binary semaphore
FreeRTOS Binary Semaphore (Part 2) Removing polling in the UART Task
FreeRTOS Counting Semaphore An example using a counting semaphore
PSoC FreeRTOS Reading I2C Sensors with a shared I2C Bus
PSoC FreeRTOS Task Notify A light weight scheme to replace Semaphores
PSoC FreeRTOS Task Notification Values A very light weight method to transfer one word of information into a task