FB pixel

Create a USB Terminal Using ThreadX Services | Final Project | Renesas RA - 27

Published


Hello again! As I promised, after we studied all the main Azure RTOS ThreadX services, we will create a project that will incorporate them. Not all of them, because I don’t want this project to be very large, but the majority of them.

I thought a lot about what this project could be. On one hand it should be simple enough not to use a lot of new MCU modules, and on the other hand it should be complex enough so that using the RTOS in it would be justified. Finally, after discussion with Josh, we decided that this would be the UART terminal. Let’s consider the task in more detail.

The terminal will work in two directions: it will receive information from the PC and show it on an OLED display (we will use the OLED library discussed in tutorial 8), also it will send the characters to the PC using the matrix keypad. The communication between the MCU and the PC will be done using the USB-to-UART converter. We already have considered all the parts that will be used here in former tutorials (PIC series) but to simplify your reading I will just copy the description of them into this tutorial.

Now let’s consider the task from the perspective of the RTOS. The project will have two threads: the OLED Thread which will be responsible for showing the information on the OLED display, and the Keypad Thread which will send the character corresponding to the pressed button to the UART. To receive data from the UART, an interrupt will be used, which will put the received character into the message queue. These messages will be read by the OLED Thread. To operate with the matrix keypads RA MCUs have a dedicated module called KINT (Key INTerrupt) which produces the interrupt when the state of one of the pins is changed. We will use this interrupt to resume the Keypad Thread using the semaphore. Also, we will use the application timer to switch between button rows.

So, in this project we will use the following ThreadX services: semaphores, message queues, and application timers.

Let’s briefly consider the main hardware parts of this project. I will also provide the links to the previous tutorial where we used these modules in case you want to learn more about them.

OLED Display

We will use a monochrome OLED display with a 128x64 pixels resolution. Such displays are very widespread and can be purchased from eBay or Aliexpress for about $1.5 - $2.5 USD. The appearance of the display module is shown in Figure 1.

OLED displays
Figure 1 - OLED displays

The most widespread ones have white, blue or blue/yellow color (don’t delude yourself with the last option: it has 16 yellow lines and 48 blue lines, so it is not a true two-color display). As you can see in Figure 1 the LCD has just four pins to connect:

  • GND - negative power pin
  • VCC - positive power pin. Even though the display driver requires 3.3 - 3.6 V to operate, you can apply up to 5V to this pin, as there is the LDO voltage regulator on board.
  • SCL and SDA - clock and data pins of the I2C interface.

These displays have the SSD1306 driver about which you can read in the following data sheet if you want to know more: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf.

Matrix Keypad

Such keypads are very widespread and quite cheap. They are often included into the Arduino set. They can look different (Figure 2 - 4) but the principle of operation is the same.

Keypad with tactile switches
Figure 2 - Keypad with tactile switches
Keypad with membrane switches
Figure 3 - Keypad with membrane switches
Fancy keypad
Figure 4 - Fancy keypad

All the keypads in Figure 1-3 have the same schematics diagram (Figure 5).

4x4 matrix keypad schematics diagram
Figure 5 - 4x4 matrix keypad schematics diagram

As you can see, the keypad consists of 16 pushbuttons S1 - S16. They are connected into a 4x4 matrix, so each of four buttons in a row have one common pin (R1 - R4), and each of the four buttons in a column also have one common pin (C1 - C4). Such a connection allows us to significantly reduce the number of the pins used for the keypad connection.

USB-to-UART Converter

There are plenty of USB-to-UART converters. The most widespread are based on FT232 (Figure 6), CP2102 (Figure 7), or CH340 (Figure 8) chips.

FT232 based converter
Figure 6 - FT232 based converter
CP2102 based converters
Figure 7 - CP2102 based converters
CH340 G based converters
Figure 8 - CH340G based converters

The first one is the most advanced but also the most expensive, while the CH340 is extremely cheap (you can buy a ready-to-use converter board for less than a buck on Aliexpress) and still quite good.

I strongly recommend using the converter that supports true 3.3V operation. The RA MCU in the development board is supplied with 3.3V, and if you use the converter with the 5V output, this can damage the input pins. Frankly, I used such 5V converters with my RA2A1 board, and everything was fine but it was at my own risk. So again, it is better to use the converter with the 3.3V output. Among the samples shown in figures 6-8 such functionality is only found on the FT232 based board. The others also have the 3.3V pin, but it is just used for powering the external circuit with this voltage while the interface pins are still 5V.

Let’s first consider the UART interface in more detail. I will give just the basics required to understand what and why we made things this way, and if you need more information you can find it on our website in a dedicated lesson about it here. This interface uses up to eight lines to communicate but the minimum required number of lines is just two. The rest are “service lines” and are used for hardware flow control. The mandatory two lines are called Rx and Tx (receive and transmit respectively), and we will only use them in our application. The connection schematic is very simple and is shown in Figure 9.

UART communication
Figure 9 - UART communication

As you can see, only three wires are required - two for the signals, and one for connecting the devices’ ground. Please note that the signal lines are crossed, so the Tx of one device is connected with the Rx of another one and vice versa.

Schematics Diagram of the Device

The schematics diagram of the UART terminal is shown in Figure 10.

Schematics diagram of the device
Figure 10 - Schematics diagram of the device

Even though it seems big, in fact it consists of just four parts: EK-RA2A1 board (J1 and J2 connectors), OLED display (X1), FT232 based USB-to-UART converter (X2), and the matrix keypad with the 4x4 size (S1 - S16).

The OLED display uses the I2C interface, and is connected with just 2 wires (apart from the power pins) - SCL and SDA. Its connection is the same as in tutorial 8.

The USB-to-UART converter is connected using three pins - TX, RX and GND. We have connected it to pins RXD9 (P110) and TXD9 (P109) of the board. In this tutorial, I will not describe the operation of the UART interface in detail considering that you already are familiar with it. If you need more information about it, you can refer to this tutorial.

Connection to the matrix keypad is not random here. The row pins R1 - R4 should be connected to pins P301-P304, respectively. These MCU pins are used by the KINT module about which I will talk later. The column pins C1-C4, though, can be connected to any pins of the MCU. Here, I used the ones that are not automatically configured to work with the peripheral modules by default.

That’s actually all about the schematics diagram. As I said before, there is nothing new in it - we have already used all these parts in other tutorials. Now we can open e2 studio and create the new project.

Creation and configuration of the project

First of all, I want to mention that as of writing this tutorial, FSP 4.0.0 has been released, so I will use it in this and the following tutorials. This FSP release can be found here. You don’t need to download and install e2 studio together with the new FSP, you can just download the standalone FSP pack and install it into the e2 studio folder.

Now, when we have installed the latest FSP release, let’s create the new RA project and call it “threadx_terminal” (Figure 11).

Creation of the new project
Figure 11 - Creation of the new project

In the next step we need to select the EK-RA2A1 board (if you have a different one, select it) and switch from C language to C++ as the OLED display library is written in the latter. Also, make sure that the FSP version for this project is 4.0.0 (Figure 12).

Selection of the board and programming language
Figure 12 - Selection of the board and programming language

In the next step we select the project with the Azure RTOS ThreadX. You can see that the version of it also has been updated (Figure 13).

Selection of the RTOS
Figure 13 - Selection of the RTOS

In the final step we select the “Azure RTOS ThreadX - Minimal” template, as usual (Figure 14).

Selection of the project template
Figure 14 - Selection of the project template

In the opened “FSP Configuration” window let’s first switch to the “Pins” tab and configure the required pins.

Before we proceed, let’s consider what the KINT module is. This module can incorporate up to eight pins (KR00-KR07) and throw an interrupt on a rising or falling edge on any pin in this group. Also this module provides a special register which has the state of all pins within the group which is convenient to use when reading the whole column of the key matrix at a time. And frankly, this is it about this module, let’s return to the configuration.

We need to scroll down to the “Peripherals” list, expand the “Input: KINT” list and select the KINT0 point. Then we need to change the “Operation mode” from “Disabled” to “Custom”, and KRM4-KRM7 pins from “None” to P301-P304, respectively (Figure 15).

Configuration of the KINT module pins
Figure 15 - Configuration of the KINT module pins

As you can see, pins P303 and P304 show errors. This is because they are already configured to work with another module. To fix this issue we need to scroll up to the “Ports” list, expand the “P3” list and select “P303” (Figure 16).

Checking of the P303 using
Figure 16 - Checking of the P303 using

As you can see, this pin is used by the SPI0 module. As we won’t use it in our project let’s disable it. To switch to this module, the fastest way is to press the arrow sign in the “Link” column (Figure 16). When you do this, you will be moved to the SPI0 module configuration (Figure 17).

Initial configuration of the SPI0 module
Figure 17 - Initial configuration of the SPI0 module

We need to change the “Operation Mode” field from “Enabled” to “Disabled”, and the error will disappear (Figure 18).

Disabling of the SPI0 module
Figure 18 - Disabling of the SPI0 module

To complete the configuration of the KIPT module we need to next switch to pins P301-P304 and enable their pull-up resistors (Figure 19).

Enabling the pull up resistors at the keypad row pins
Figure 19 - Enabling the pull-up resistors at the keypad row pins

Also let’s give the symbolic names to these pins as “ROW1” - “ROW4”, respectively. We will use these names in our program.

Now let’s switch to the “Connectivity:IIC” list and select the “IIC0” module. Here, we need to change the “Operation Mode” from “Disabled” to “Enabled” and “Pin Group Selection” from “_A only” to “_C only” (Figure 20).

Configuration of the IIC module
Figure 20 - Configuration of the IIC module

Make sure that the SDA pin is P401, and SCL pin is P000, which corresponds to the schematics diagram (Figure 10).

Finally, we need to configure the UART module. To do this we need to expand the “Connectivity:SCI” list and select the “SCI9” module. There we need to change the “Operation Mode” from “Diabled” to “Asynchronous UART” and “Pin Group Selection” from “Mixed” to “_B only” (Figure 21).

Configuration of the SCI module
Figure 21 - Configuration of the SCI module

Here “SCI” stands for “Serial Communication Interface”. This module can work in several modes which you could see in the “Operation Mode” drop-down list (Figure 22).

Modes of the SCI module operation
Figure 22 - Modes of the SCI module operation

We will not consider this module in detail here, you can refer to the User’s Manual for more information about it. For now we just need to know that this module can work in UART mode, and we can use it in our program.

The only thing left to configure in this tab is keypad column pins (P410, P015, P107, and P106). They all will have the same configuration except for the “Symbolic Name”. For example, the configuration of the P410 pin is shown in Figure 23.

Configuration of the P410 pin
Figure 23 - Configuration of the P410 pin

Please note that the initial output state of the pin is high. We will set the column pins low in our program consequently to read the row states.

Pins P105, P107, and P106 have the same configuration, as I said, just their symbolic name has a different digit at the end instead of 1 (2, 3, and 4, respectively).

Now let’s switch to the “Stacks” tab and add the required stacks. To do this, click the “New Stack >” button, expand the “Input” list and select the “Key Matrix (k_int)” (Figure 24).

Selection of the Key Matrix stack
Figure 24 - Selection of the Key Matrix stack

Then we need to click on the “g_kint0 Key Matrix (r_kint)” block and change its properties according to Figure 25.

Configuration of the Key Matrix stack
Figure 25 - Configuration of the Key Matrix stack

Even though it seems that there are a lot of settings, in fact, you’re right. There are quite few of them:

  • The “Name” field is the normal and represents the name under which this stack will be used in our program. I’ve changed it from “g_kint0” to “g_kint” but this is optional.
  • In the “Key Interrupt Flag Mask” list you can select which channels will produce the KINT interrupt. According to the schematics diagram (Figure 10) and pins configuration (Figure 15) we need to enable channels 4-7.
  • In the “Trigger Type” field we can select the edge at which the interrupt will be generated. In our case the button press corresponds to the falling edge, so we select this option.
  • The “Callback” field is the name of the KINT callback function, I have left it unchanged.
  • The “Key Interrupt Priority” field you can set the interrupt priority of the KINT module. Here I also didn’t change it.
  • The “Pins” list is informational and shows which pins are used by the KINT module. Also, if you click on any pin and then press the arrow near it, you can quickly navigate to the configuration of the current pin.

The configuration of the IIC module has been described in detail in tutorial 8 but again, to simplify your reading, I will repeat it here.

Click on the “New Stack >” button once again, expand the “Connectivity” list and select the “I2C Master (r_iic_master)” line (Figure 26).

Adding the IIC stack
Figure 26 - Adding the IIC stack

The next step is optional and will not affect anything else further. I mean adding the DTC Drivers for data transmission and reception. As I mentioned in tutorial 6, it’s always a good idea to use the DTC where possible to reduce the load of the CPU. So if you want to use it, you need to click on the “Add DTC Driver for Transmission”, you will see the pop-up menu “New >”. Open it and select the “Transfer (r_dtc)” module (Figure 27).

DTC Transmission driver selection
Figure 27 - DTC Transmission driver selection

Then do the same for the “Add DTC driver for Reception” block. After that, you will have the following Stacks view (Figure 28).

Stacks configuration
Figure 28 - Stacks configuration

We don’t need to change the DTC modules settings as they are all already configured and locked. But we need to configure the I2C module. So click on the “g_i2c_master0 I2C Master (r_iic_master)” block and change its properties according to Figure 29.

IIC0 module configuration
Figure 29 - IIC0 module configuration
  • As long as we added the DTC modules to the IIC0 stack, we need to enable it in the “DTC on Transmission and Reception” field.
  • “10-bit slave addressing” enables the 10-bit I2C addressing mode support which we don’t need in the current case, so we leave it as “Disabled”.
  • The “Name” field may be left unchanged or you can change it to your own.
  • The “Channel” field should remain 0, as we use the IIC0 module.
  • The “Rate” field sets the I2C communication speed: “Standard” is 100 kHz, “Fast-mode” is 400 kHz, “Fast-mode plus” is 1 MHz. As we’re not going to display dynamic images, standard speed works well for us.
  • “Rise Time”, “Fall Time” and “Duty Cycle” fields set the parameters of the pulses, we will better not change them unless we know exactly that we have some different values.
  • The “Slave Address” field is the address of the slave device. In our case it’s the OLED display, which according to the data sheet can have the address 0x3C or 0x3D depending on the jumper position on the display module. The default value is 0x3C so we write it here. Some curious readers may notice that on the OLED module board there are other addresses: 0x78 or 0x7A. To understand why it is so, we need to recall that an I2C address consists of 7 bits of the device address, and the LSB is the read/write bit. So some MCUs adjust the device address to the left, leaving the MSB free, the other MCUs adjust the device address to the right, leaving the LSB free. If we convert the values 0x3C and 0x78 into the binary system, we will see the following: 0x3C = 0b00111100, 0x78 = 0b01111000. So it’s the same address but with different adjustments. The same with values 0x3D and 0x7A, you can check this yourself.
  • The “Address Mode” field sets the 7-bit or 10-bit addressing mode. As I mentioned before, we need to leave the 7-bit mode.
  • The “Timeout Mode” field selects the timeout mode to detect bus hanging. It can be short or long.
  • The “Timeout during SCL Low” field enables the timeout event if the SCL line is held low by the slave for the time specified in the “Timeout Mode” field.
  • The “Callback” field is the custom name of the callback function which is invoked from the IIC0 module interrupt subroutine. We need to use the callback to detect the transmission end event, so we need to give it some name instead of NULL. I selected the simple “i2c_callback” name but you may call it whatever you want.
  • The “Interrupt Priority Level” can be left as is because we don’t use any other interrupts here.
  • The “SCL” and “SDA” pins should correspond to real pins to which the I2C bus is connected. In our case there are pins P401 and P000, respectively.

Now the IIC0 module is totally configured. The last stack to add is the UART module. Let’s click the “Nex Stack >” the last time in this application, expand the “Connectivity” list and select the “UART (r_sci_uart) stack (Figure 30).

Selection of the UART stack
Figure 30 - Selection of the UART stack

Here you also see the optional DTC, but if it’s being used for transmission it’s recommended to not use it for reception. So let’s add it only for transmission (Figure 31).

Adding the DTC driver for UART transmission
Figure 31 - Adding the DTC driver for UART transmission

The same as with the IIC module, the DTC driver is already configured and locked, so we don’t need to change its settings. But we need to configure the UART Stack itself. So click on the “g_uart0 UART (r_sci_uart)” block and configure it according to Figure 32.

UART configuration
Figure 32 - UART configuration

As you can see, this module has a lot of options, let’s try to figure out what they are for.

  • The “FIFO Support” field allows the use of the 16-level FIFO for receiving and transmitting operations. This option is only available for SCI0, so we can’t use it, as we are using the SCI9 module.
  • The “DTC Support” field enables the DTC for the reception or/and the transmission. As we added the support of the DTC for data transmission, we need to switch this option to “Enabled”.
  • The “Flow Control Support” field enables the hardware control of the data flow using CTR/RTS pins. We will not use this feature, so let’s leave it as “Disabled”.
  • The “RS-485 Support” field enables the RS-485 interface which uses the special DE pin to automatically switch the data direction on the bus. We don’t need this feature here as well.
  • The “Name” field is the name of the UART stack in the program. Let’s change it to “g_uart9” to indicate that we use channel 9.
  • The “Channel” field sets the number of the SCI module channel. As we use SCI9, we need to change this value to 9.
  • The “Data Bits” field sets the length of the transaction. It can be 7, 8, or 9 bits. But we leave it as standard value “8 bits”.
  • The “Parity” field sets the parity control. It can be “None”, “Odd” or “Even”. Let’s also leave the default value “None”.
  • The “Stop Bits” field sets the number of the stop bits. It can be 1 or 2. And again, we leave it at the default value of 1 bit.
  • The “Baud Rate” field sets the communication speed. Let’s also leave this value at the default value of 115200 baud.
  • The “Baud Rate Modulation” enables the modulation of the baud rate making some bits slightly longer which makes the real baud closer to the desired one.
  • The “Max Error (%)” field sets the maximum percent error allowed during baud calculation. This is used by the algorithm to determine whether or not to consider using less accurate alternative register settings.
  • The “CTS/RTS selection” field selects either CTS or RTS function on the CTSn_RTSn pin of SCI channel n or select CTS function on CTSn pin and RTS function on CTSn_RTSn pin of SCI channel n. This option is applicable only when the flow control is enabled, so we can set any value here
  • The “Software RTS Port” field specifies the number of the port where the RTS pin will be located.
  • The “Software RTS Pin” field specifies the number of the pin within the set above port, which acts as the RTS.
  • The “RS-485” list consists of the additional settings used in the RS-485 mode. We will not consider them here now.
  • The “Clock source” field selects the clock source to be used in the baud-rate clock generator. When the internal clock is used the baud rate can be output on the SCK pin. As we don’t use the external clock and don’t need to output the clock, we just leave the default value “Internal Clock”.
  • The “Start Bit Detection” field can be set as either “Falling edge” or “Low Level”. We will leave the first option.
  • The “Noise filter” field enables the digital noise filter on the RXDn pin. The digital noise filter block in SCI consists of two-stage flip flop circuits. We hope that the USB-to-UART converter produces the clear output signal, and leave this option as “Disabled”.
  • The “Receive FIFO Trigger Level” field is applicable only if the FIFO support is enabled. It can be either “One”, which will generate the interrupt after every received byte or “Max”, which will generate the interrupt when the FIFO is full. As we don’t use FIFO, the value of this field doesn’t matter.
  • The “Callback” field sets the name of the UART callback function. As we will use the callback to receive the data, we need to add it. Let’s call it “uart_callback”.
  • The next four fields set the priority of the corresponding interrupts. Let’s leave all of them at the default level of 2.
  • The “Pins” list is informational, and shows the actual pins that will be used by the current module. As you can see, they are P109 and P110 which correspond to the schematics diagram (Figure 5).

This is all about the stacks configuration. Now we need to add the required threads and RTOS services.

As I mentioned before, the application will have two threads: OLED Thread and Keypad Thread. So let’s click on the “New Thread” button to create a new thread and configure it according to Figure 33.

Configuration of OLED Thread
Figure 33 - Configuration of OLED Thread

For the description of all settings please refer to tutorial 21, as here only the settings that are changed are shown.

We need to enable the “Event Trace” to use the TraceX. Also, we need to reduce the “Trace Buffer Size” because the RAM size is not very large.

The thread’s name in the program code is “oled_thread” and the normal name is “OLED Thread”.

Let’s now create one more thread, whose settings will be the same besides the “Symbol” and the “Name” fields (Figure 34).

Configuration of Keypad Thread
Figure 34 - Configuration of Keypad Thread

Now let’s click on the “New Object >” button, create a new semaphore and configure it according to Figure 35 (for more information about semaphores please refer to tutorial 22).

Configuration of the semaphore
Figure 35 - Configuration of the semaphore

And finally, let’s click on the “New Object >” button once again and create the new Queue whose configuration is shown in Figure 36 (for more information about message queues please refer to tutorial 25).

Configuration of the message queue
Figure 36 - Configuration of the message queue

Here, besides the changes to the “Name” and “Symbol” fields, we will increase the queue size to 512 bytes, so now it can store the message of up to 512 / 4 = 128 characters.

Those are all the required changes in the “Stacks” tab. After everything, it should look like this (Figure 37).

Completed stack configuration
Figure 37 - Completed stack configuration

Before switching to the program code, we need to change one more thing - to increase the heap size to 0x1000 because the OLED display memory will be allocated there. To do this, let’s switch to the “BSP” tab. In the “Properties” window in the left bottom corner find the “Heap size (bytes)” line under the “RA Common” list, and change its value to 0x1000 (Figure 38).

Setting the heap size
Figure 38 - Setting the heap size

Now we can switch to the program part of the device

Code of the Program

First of all, we need to add the SSD1306 library to the application. There is a special tutorial devoted to this topic. Here we will use the files that we already have ported to the RA family. You can find them under the tutorial 8, also I will attach them under this tutorial.

This time we will not copy all files into the “src” folder and then exclude the unnecessary ones from build. We will only copy the files that are required, which are:

  • Adafruit_GFX.cpp
  • Adafruit_GFX.h
  • Adafruit_SSD1306.cpp
  • Adafruit_SSD1306.h
  • glcdfont.c
  • gfxfont.h
  • splash.h

The last two files don’t need to be included but this will need slight changes in the source files. You can do this by yourself if you want but I will leave the files as is.

So we need to copy the mentioned files from the folder where tutorial 8 is located into the “src” folder of the current project. After this the “src” folder will look like this (Figure 39).

Content of the src folder of the current project
Figure 39 - Content of the “src” folder of the current project

Now we have everything to write our own code. We will do this in two files: “keypad_thread_entry.cpp” and “oled_thread_entry.cpp”. As you see the auto-generated files now have the extension “cpp” instead of “c” like in previous tutorials. This is because we selected the C++ language when we created the project (Figure 12). But actually this will not make any difference for us.

Let’s first consider the code of the “oled_thread_entry.cpp” file, in which we will process the received data from the UART and display it in the OLED display.

#include "oled_thread.h"

#include "Adafruit_SSD1306.h"

Adafruit_SSD1306 display(128, 64); //Declaration of the display object

volatile uint32_t g_transfer_complete; //Flag indicating that the UART transfer is complete

/* OLED Thread entry function */

void oled_thread_entry(void)

{

uint32_t data; //Character received via UART

CHAR *name; //Pointer to the queue's name

ULONG enqueued; //Number of messages currently in the queue

ULONG available_storage; //Number of messages the queue currently has space for

TX_THREAD *first_suspended; //Thread that is first on the suspension list of this queue

ULONG suspended_count; //Number of threads currently suspended on this queue

TX_QUEUE *next_queue; //Pointer of the next created queue

R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);

display.begin(SSD1306_SWITCHCAPVCC, 0x3C, false, true); //Initialize OLED display with the I2C addr 0x3C (for the 128x64)

display.clearDisplay(); //Clear the display

display.setTextColor(WHITE); //Set the color of the text

display.write('>'); //Write the prompt character

display.display(); //Send the buffer to the display

R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg); //Open the UART stack

while (1)

{

tx_queue_receive(&uart_rx_queue, &data, TX_WAIT_FOREVER); //Receive the message

display.write((uint8_t)data); //Write the received character

if (display.getCursorY() > display.height() - 8) //If the cursor is under the last line

{

for (uint16_t i = 0; i < display.width() * ((display.height() - 1) / 8); i ++) //Shift the first 7 lines up

{

display.getBuffer()[i] = display.getBuffer()[i + display.width()];

}

for (uint16_t i = display.width() * ((display.height() - 1) / 8); i < display.width() * ((display.height() + 7) / 8); i ++) //Clear the last line

{

display.getBuffer()[i] = 0;

}

display.setCursor(0, display.height() - 8); //Set the cursor at the beginning of the last line

if (data != '\n') //If the character is not "Line Feed"

display.write((uint8_t)data); //Write the received character again

}

tx_queue_info_get(&uart_rx_queue, &name, &enqueued, &available_storage, &first_suspended, &suspended_count, &next_queue); //Get the message queue info

if (enqueued == 0) //If there are no more messages in the queue

display.display(); //Send the buffer to the display

}

}

/* Callback function */

void uart_callback(uart_callback_args_t *p_args)

{

/* Handle the UART event */

switch (p_args->event)

{

/* Received a character */

case UART_EVENT_RX_CHAR:

{

tx_queue_send(&uart_rx_queue, &p_args->data, TX_NO_WAIT); //Send the message to the queue

break;

}

/* Transmit complete */

case UART_EVENT_TX_COMPLETE:

{

g_transfer_complete = 1;

break;

}

default:

{

}

}

}

As you can see, the code is not very long. Let’s now consider it in detail.

In line 2 we include the “Adafruit_SSD1306.h” file to use the OLED display functions in this thread.

In line 4 we create the object display of the Adafruit_SSD1306 class with the resolution of 128x64 pixels. We will use it for operating with the OLED display.

In line 5 we declare the variable g_transfer_complete. It will be used to indicate that sending data to the UART is complete. Actually, this variable is needed in the other thread, and we declare it here just because in this file the UART callback function uart_callback is located (lines 51-72).

In lines 8-48 there is the function oled_thread_entry which is the body of the OLED Thread.

In line 10 we declare the variable data into which we will read the messages from the queue and forward to the OLED display.

The next variables in line 11-16 are used by the tx_queue_info_get function about which I will talk a bit later.

In line 18 we perform a delay for 1 second to give the OLED display enough time to start and stabilize.

In line 19 we initialize the OLED display for using the internal boost voltage converter as a power source for the matrix.

In line 20 we clear the display, then set the text color as white (line 21) and type the character “>” (line 22) as a prompt to write the text there.

In line 23 we send the display buffer to the physical OLED display (as you remember, all the drawing first happens in the buffer inside MCU RAM, and only the display method copies it into the display).

In line 24 we open the UART stack with the configuration that we set earlier in the visual FSP configurator.

In lines 26-47 there is the main loop of the OLED Thread.

In line 28 we call the function tx_queue_receive which is familiar to us from tutorial 25. So in this line the thread becomes suspended for infinity (because we set the waiting parameter as TX_WAIT_FOREVER) until a message appears in the queue. This will happen when the data is received via UART and sent into the queue. Then this message is received and copied into the data variable (we are still talking about line 27).

In line 29 we write the received character at the display buffer using the method write. This method can write a single character in the OLED display, start the new line when the previous line is full, and recognize the line feed symbol ‘\n’ to start the new line. After its implementation, it may happen that we moved from the last line out of the display borders. That’s why, when this situation happens, we need to shift the whole text one line up, and write the new text in the last line. This algorithm is implemented in lines 30-43.

In line 30 we check if the current Y position of the cursor is greater than the display height minus 8 pixels. This means that the current line will not fit into the display as the text height is 8 pixels. In this case, we implement the loop (line 32) inside which we shift the whole display buffer 8 pixels up (line 34). If you want more information about how the information is stored in the display buffer, you can refer to the SSD1306 driver data sheet, I won’t get into detail here.

In line 36 we implement another loop to clear the last row of 8 pixels of the LCD (line 38). Then in line 40 we set the cursor to the beginning of the bottom line. And finally, we check if the character that we send to the display is not ‘\n’ (line 41). This means that this character was moved from the end of the previous line, and thus we need to type it again (line 42). If the character is ‘\n’ we don’t need to write it again, as this will lead to one more shift in the display.

In line 44 we invoke the function tx_queue_info_get which allows us to get the information about the message queue. In the current case we need to know only when the queue is empty. This means that the whole message has been received, and we can send the display buffer to the display itself. We could skip this checking and do this after receiving every character but this makes the updating quite slow. So in line 45 we check if the variable enqueued (in which the information about the number of messages remaining in the queue is stored) is 0, and if it is so, we invoke the display method (line 46).

This is everything about the OLED Thread function, let’s now switch to the uart_callback function (lines 51-72) which is invoked every time a UART event happens. Among all possible events we will process just two: UART_EVENT_RX_CHAR which is caused by receiving a new character (lines 57-61), and UART_EVENT_TX_COMPLETE which is caused by finishing the transmission (lines 63-67). All other events are ignored (lines 68-70).

As you see, the uart_callback function has a single argument p_args which consists of the information about the callback event (event field) and the received character (data field). So we check this event field of the p_args parameter to distinguish the events which caused this interrupt (line 54).

If the event is UART_EVENT_RX_CHAR we put the received character (p_args->data) into the message queue using the tx_message_send (line 59). This is the important thing about the RTOS which I didn’t mention before. We can invoke the RTOS-related functions not only from the thread functions but from the regular interrupt callbacks as well. The only restriction in this case is that we must use the waiting option TX_NO_WAIT to prevent hanging of the whole system.

If the event is UART_EVENT_TX_COMPLETE, we just set the g_transfer_complete variable to 1 to indicate that the transmission is done. As I mentioned before, we will need it in the other thread.

This is all about the OLED Thread, let’s now consider the code of the Keypad Thread located inside the “keypad_thread_entry.cpp” file.

#include "keypad_thread.h"

volatile uint32_t column_mask; //The mask of the column when any button is pressed

volatile uint8_t column; //Number of column which is currently active

extern volatile uint32_t g_transfer_complete; //Flag indicating that the UART transfer is complete

const uint8_t matrix[4][4] = {{'1', '2', '3', 'A'}, //Keypad mapping

{'4', '5', '6', 'B'},

{'7', '8', '9', 'C'},

{'*', '0', '#', 'D'}};

TX_TIMER keypad_timer; //Timer to switch the columns

void keypad_timer_callback (ULONG input); //Timer callback declaration

/* Keypad Thread entry function */

void keypad_thread_entry(void)

{

uint8_t row; //Number of row at which the button is pressed

column = 0; //Set the the initial column as 0

R_KINT_Open(&g_kint_ctrl, &g_kint_cfg); //Configure the KINT

R_KINT_Enable(&g_kint_ctrl); //Enable the KINT

tx_timer_create(&keypad_timer, "Keypad Timer", keypad_timer_callback, 0, 3, 3, TX_AUTO_ACTIVATE); //Creation of Keypad Timer

while (1)

{

tx_semaphore_get(&keypad_semaphore, TX_WAIT_FOREVER); //Get the semaphore from the KINT interrupt

R_KINT_Disable(&g_kint_ctrl); //Disable the KINT

tx_timer_deactivate(&keypad_timer); //Deactivate timer while the button is pressed

switch (column_mask & 0xF0) //Switch the column_mask to find the row which caused the interrupt

{

case 0x10: row = 0; while (R_BSP_PinRead(ROW1) == BSP_IO_LEVEL_LOW); break; //ROW1, wait while ROW1 pin is low

case 0x20: row = 1; while (R_BSP_PinRead(ROW2) == BSP_IO_LEVEL_LOW); break; //ROW2, wait while ROW2 pin is low

case 0x40: row = 2; while (R_BSP_PinRead(ROW3) == BSP_IO_LEVEL_LOW); break; //ROW3, wait while ROW3 pin is low

case 0x80: row = 3; while (R_BSP_PinRead(ROW4) == BSP_IO_LEVEL_LOW); break; //ROW4, wait while ROW4 pin is low

}

R_BSP_SoftwareDelay(30, BSP_DELAY_UNITS_MILLISECONDS); //Debounce delay on button releasing

g_transfer_complete = 0; //Clear the flag indicating that the UART transfer is complete

R_SCI_UART_Write(&g_uart9_ctrl, &matrix[column][row], 1); //Write the corresponding character to the UART

while (!g_transfer_complete); //Wait while the UART transfer is done

tx_timer_activate(&keypad_timer); //Reactivate the timer

R_KINT_Enable(&g_kint_ctrl); //Enable the KINT

}

}

/* Callback function */

void kint_callback(keymatrix_callback_args_t *p_args)

{

column_mask = p_args->channel_mask; //Save current interrupt mask

tx_semaphore_put(&keypad_semaphore); //Put the semaphore to unlock the thread

}

void keypad_timer_callback (ULONG input)

{

(void)input; //To prevent the "unused parameter" warning

column ++; //Increment the column variable

if (column == 4) //If it is 4

column = 0; //Then set is as 0

switch (column) //Switch the column variable to activate the corresponding column

{

case 0:

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(COLUMN4, BSP_IO_LEVEL_HIGH); //Deactivate column 4

R_BSP_PinWrite(COLUMN1, BSP_IO_LEVEL_LOW); //Activate column 1

R_BSP_PinAccessDisable(); //Disable access to the IO registers

break;

case 1:

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(COLUMN1, BSP_IO_LEVEL_HIGH); //Deactivate column 1

R_BSP_PinWrite(COLUMN2, BSP_IO_LEVEL_LOW); //Activate column 2

R_BSP_PinAccessDisable(); //Disable access to the IO registers

break;

case 2:

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(COLUMN2, BSP_IO_LEVEL_HIGH); //Deactivate column 2

R_BSP_PinWrite(COLUMN3, BSP_IO_LEVEL_LOW); //Activate column 3

R_BSP_PinAccessDisable(); //Disable access to the IO registers

break;

case 3:

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(COLUMN3, BSP_IO_LEVEL_HIGH); //Deactivate column 3

R_BSP_PinWrite(COLUMN4, BSP_IO_LEVEL_LOW); //Activate column 4

R_BSP_PinAccessDisable(); //Disable access to the IO registers

break;

}

}

This thread, even being a bit longer, is still quite simple.

In line 3 we declare the variable column_mask which represents the value returned by the KINT module. If any button within the specified column is pressed, then the corresponding column_mask bit will be 1. Please note this not to be confused: even though the input pin level is low when the button is pressed, the KINT sets the corresponding bit as 1.

In line 4 we declare the variable column, which is the number of a column which is currently active. By “active” I mean that we set the corresponding column pin low while others are high.

In line 5 we declare the variable g_transfer_complete which is already defined in the “oled_thread_entry.cpp” file in the same line 5. To indicate that we will use exactly that variable, we use the extern modifier.

In lines 6-9 we declare the constant 2-dimensional array matrix of size 4x4. This array contains the characters which correspond to the buttons of the keypad. You can see that this array has the same characters as the keypads shown in Figure 3 and 4. We will use this array to send the character to the UART which corresponds to the pressed button under the same coordinates.

In line 10 we declare the variable keypad_timer which is the application timer’s control block. This timer will be used to switch between columns while scanning the keypad.

In line 11 we declare the keypad_timer_callback function which is the callback of the keypad_timer timer. The body of this callback function is located in lines 49-82.

In lines 14-40 there is the function keypad_thread_entry which is the main function of the Keypad Thread.

In line 16 we declare the local variable row which represents the number of the row at which the button has been pressed. This variable along with the column defines the number of the button which is currently pressed.

In line 17 we initialize the column variable with the 0 value to start scanning with the first column.

In line 18 we invoke the R_KINT_Open function which like the other FSP R_xxxx_Open functions initializes the mentioned module and applies the settings from the FSP visual configurator to it.

In line 19 we invoke the R_KINT_Enable function which enables the KINT module and allows generating the interrupts when any of the pins P301-P304 change their state.

In line 20 we create the application timer using the function tx_timer_create. For more information about application timers please refer to tutorial 26. Here, as you can see, we create the timer called “Keypad Timer” with the keypad_timer_callback callback function which will periodically throw an interrupt every 3 ticks (which is 30 ms in the current case). The timer starts immediately as the TX_AUTO_ACTIVATE parameter is chosen.

Before further proceeding let’s consider the algorithm of operation for this part. Keypad Timer activates the next column of the keypad every 30 ms and deactivates others (which gives the whole scanning cycle time of 120 ms). At the same time the KINT module checks if there is a falling edge on any of the row pins of the keypad. If this happens (which means that some button has been pressed) the interrupt is generated. When this happens, in the callback function of the KINT module the semaphore is put, which resumes the Keypad Thread. After that, inside this thread both KINT module and Keypad Timer are deactivated to prevent further scanning while the button is pressed. When the button is released we send the character to UART which corresponds to the pressed button, then reactivate the KINT module and Keypad Timer, and suspend the Keypad Thread waiting while the semaphore becomes available again. Now let’s return to the code and see how it’s all done there.

In lines 21-39 there is the main loop of Keypad Thread.

In line 23 we invoke the function tx_semaphore_get which suspends the thread waiting for the semaphore from the KINT module (for more information about semaphores please refer to tutorial 18). When the semaphore becomes available (which means that some button has been pressed) we disable the KINT module by invoking the R_KINT_Disable function (line 24) and stop the application timer by calling the tx_timer_deactivate function (line 25).

After that we check the upper four bits of the column_mask variable (line 26) which has 1 in the bit which corresponds to the row in which the button has been pressed. Bit #4 corresponds to row 0, bit #5 corresponds to row 1 etc (see the configuration of the KINT module, Figure 15 and 25).

If bit #4 of column_mask is set (line 28), we set the variable row as 0 and wait while the ROW1 pin is low. We make the same checks for bits #5-#7 (lines 29-31).

After a button has been released we perform the debounce delay for 30 ms (line 33) to make sure that we really have released it.

After that we set the g_transfer_complete variable as 0 (line 34) and send the character via UART using the R_SCI_UART_Write function (line 35). This function has 3 arguments:

  • the pointer to the SCI module control block;

  • the pointer to the data (or array) to be transmitted;

  • number of bytes to be transmitted.

As you see in line 35, we transmit the single matrix[column][row] character. It’s quite simple to make a correspondence between the pressed button and the character to be sent using the 2-dimensional array.

Next, we wait while the g_transfer_complete variable becomes 1 (line 36). As you remember this happens when the UART_EVENT_TX_COMPLETE event occurs (lines 63-67 of the “oled_thread_entry.cpp” file). After that we start the Keypad Timer with the tx_timer_activate function (line 37) and reenable the KINT module using the R_KINT_Enable function (line 38).

This is all about the Keypad Thread body. Now let’s consider the auxiliary callbacks.

In lines 43-47 kint_callback is located which is invoked when the key interrupt occurs. In this interrupt we first save the p_args->channel_mask variable value into the column_mask variable (line 45) to be able to use this value outside this function. And then we put the semaphore using the tx_semaphore_put function (lins 46). As you see, here we also operate with the RTOS service inside the interrupt callback, not inside the thread.

And finally, in lines 49-82 keypad_timer_callback is located.

In this function we first increment the column variable to set the next one (line 52). If the column number exceeds the maximum value (line 53) we set it as 0 (line 54).

In line 55 we check the column number. If it is 0 (line 57) we set the COLUMN4 pin high (line 59) because this is the last column which was set low, and set the COLUMN1 low (line 60) to make it active and check the buttons S1, S5, S9, and S13 (Figure 10). The other values of the column variable are processed in the similar way (lines 63-68, 69-74, and 75-80) - we set the previous column high and the current column low.

And this is all about the program code of the device. Let’s now build the project, connect the board, and start the debugging process to see how it all works. Also, don’t forget to connect the USB-to-UART converter to another USB port.

Testing the Application

As in the previous ThreadX-related tutorials we will use TraceX to see what happens with the program from the RTOS perspective. You can read in detail how to set up and configure it in tutorial 17. And again I warn you to check the real address of the g_tx_trace_buffer variable to get the correct result.

Just after the MCU reset, the running application looks like Figure 40.

Running of the program after resetting the MCU
Figure 40 - Running of the program after resetting the MCU

As you can see, OLED Thread creates the message queue (QC), gets, creates and puts a system semaphore and keeps running. This happens because in line 18 of the “oled_thread_entry.cpp” file we implement the 1 second delay during which the thread is still active.

Keypad Thread creates the application timer (CR) and suspends (IS) after an attempt to get the semaphore. OLED Thread in its turn also becomes suspended in 1 second after attemption of receive the message from the queue (QR) which is shown in Figure. 41.

Suspension of OLED Thread while attempting to receive the message
Figure 41 - Suspension of OLED Thread while attempting to receive the message

Now let’s open a terminal application on your PC. You can use whichever you like or are used to, there are hundreds of them. I prefer to use Termite which can be downloaded from this page. After opening the application you need to configure the COM port as shown in Figure 42.

Configuration of the COM port
Figure 42 - Configuration of the COM-port

The number of the COM port in your case most likely will be different from mine, you can find it in the device manager, or just in the drop-down list of the terminal application if you have only one active COM port. The baud rate, data bits, stop bits, parity and flow control should be the same as we set during configuration of the SCI module (Figure 32).

In Termite there is a useful option - you can append some character(s) at the end of the transmitting text. Here I selected “Append LF” which will add the ‘\n’ symbol automatically and start every new message from the new line. Now let’s press the “OK” button and return to the main screen of the application.

First let’s check how sending the message works. In the bottom line write some text and press the button to the right of it (Figure 43).

Sending the message to the device
Figure 43 - Sending the message to the device

You will see that this text appears in the OLED display (Figure 44).

Text in the OLED display
Figure 44 - Text in the OLED display

Now let’s see how this looks from the RTOS perspective (Figure 45).

Receiving the message via UART
Figure 45 - Receiving the message via UART

As you can see, the Interrupt line now becomes active. First, it enters the interrupt (IE) then sends the message to the queue (QS) and finally exits the interrupt (IX). As you see, this pattern repeats several times.

Receiving messages by the OLED Thread is more interesting. The first message is conveyed directly to the OLED Thread because it was waiting for it. The next three messages are received via the queue (QR). After receiving the message the thread checks if the queue is empty (IG). It turns out that receiving from the queue is faster than sending to it, so after the fourth message the queue becomes empty which causes sending the buffer to the display. As you can see, during this time no messages are received, and the queue is filled (events #594-#652). OLED thread is busy for some time (till event #680) and then receives all the messages without any delay (from event #681).

You can send several other messages to the device and see how the OLED behaves when it becomes fully filled.

Now let’s try how the keypad works. Press all buttons on it from the top left to bottom right consequently and see how the characters appear in the terminal application (Figure 46).

Receiving of the characters from the device
Figure 46 - Receiving of the characters from the device

As you can see, all characters correspond to the pressed buttons. If they are not, check the connection and swap the wires if needed.

From the RTOS perspective sending the messages looks like this (Figure 47).

Sending the message via UART
Figure 47 - Sending the message via UART

When the KINT interrupt occurs (event #91, IE), it puts the semaphore (SP) which resumes the Keypad Thread (IR). In this thread the application timer is stopped (TD) and the KINT interrupts are disabled. Then inside the thread, we wait all the time while the button is pressed then send the character via UART which causes interrupts (events #126-127 and #129-130). It is interesting that there are two interrupts instead of one. After the character is sent, the timer is started again (event #131, TA), then the thread tries to get the semaphore (SG) and suspends again (IS).

When another button is pressed, the process repeats.

This is how the whole project works. As you can see, there is nothing difficult in it. Obviously, we could do the same functionality without using the RTOS. In this case, the RTOS just splits the different tasks between threads which makes the code more readable.

With this final project, our tutorial series on exploring Renesas RA comes to an end. We hope these tutorials helped you establish a good understanding of microcontrollers, what they can do, and how to make use of the wonderful features they offer. There are no further tutorials planned in this series at the moment, but who knows? Maybe we'll change our minds in the future. Until then, get ready for another cool series on FPGAs! Stay tuned! :)

Make Bread with our CircuitBread Toaster!

Get the latest tools and tutorials, fresh from the toaster.

What are you looking for?