ThreadX with SAME70 Xplained!
Article Created on May 08, 2023
In this post, I will write about how I built ThreadX (Azure RTOS) for SAME70 Xplained Evaulation Kit. I am using Windows 11 OS host machine to setup cross-development environment for SAME70 Xplained. Here’s the list of a few things that we will need to get started.
- SAME70 Xplained Kit: https://www.microchip.com/en-us/development-tool/ATSAME70-XPLD
- Microchip Studio: https://www.microchip.com/en-us/tools-resources/develop/microchip-studio
- ThreadX: https://github.com/azure-rtos/threadx
- PuTTY Serial Terminal: https://www.putty.org/
- Git: https://github.com/git-guides/install-git
Setting up the Starting Project
Create a new git repository in github with a README. Clone the repository in local setup. Open up Microchip Studio and import an example project. For this project, we will use lwIP project as the starting project. Go to File->New->Atmel Start Example Project
. Set the filter for SAME70 Xplained Board. Select lwIP example project. Generate the project. Select the git repository we just created as the workspace for the project. Verify that the project works. On running the program, you should see prints in the UART console (baudrate is configured to be 9600 bps). The program prints the statically configured IP address to the console. Try to ping the IP address of the board from the host computer. If ping is successful, the project works. Commit the project at this stage.
Integrating ThreadX
Clone the threadx source code with tag v6.2.1_rel
from git repository.
$ git clone --depth 1 --single-branch --branch v6.2.1_rel https://github.com/azure-rtos/threadx.git
Remove unnecessary source files. We do need the common folder and the ports folder for the time being. In ports folder, retain only the cortex_m7 port folder for GNU toolchain. Add the include directories from threadx common\inc
and ports\cortex_m7_gnu\inc
and build the program. At this point
Define tx_application_define
function to create two threads and two semaphores.
#include "consoleUtils.h"
#include "tx_api.h"
#include "tx_port.h"
TX_BYTE_POOL byte_pool_0;
TX_SEMAPHORE semaphore_0, semaphore_1;
TX_THREAD my_thread, my_thread_2;
void my_thread_entry(ULONG thread_input)
{
UINT status;
UINT thread_counter = 0;
/* Enter into a forever loop. */
while(1)
{
/* Get the semaphore with suspension. */
status = tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);
/* Check status. */
if (status != TX_SUCCESS) break;
/* Increment thread counter. */
thread_counter++;
/* Release the semaphore. */
status = tx_semaphore_put(&semaphore_1);
tx_thread_sleep(500); // corresponding to 5s
/* Check status. */
if (status != TX_SUCCESS) break;
}
}
void my_thread_entry_2(ULONG thread_input)
{
UINT status;
UINT thread_counter = 0;
/* Enter into a forever loop. */
while(1)
{
/* Get the semaphore with suspension. */
status = tx_semaphore_get(&semaphore_1, TX_WAIT_FOREVER);
/* Check status. */
if (status != TX_SUCCESS) break;
/* Increment thread counter. */
thread_counter++;
tx_thread_sleep(500); // corresponding to 5s
/* Release the semaphore. */
status = tx_semaphore_put(&semaphore_0);
/* Check status. */
if (status != TX_SUCCESS) break;
}
}
void tx_application_define(void *first_unused_memory)
{
CHAR *pointer;
/* Create a byte memory pool from which to allocate the thread stacks. */
tx_byte_pool_create(&byte_pool_0, "byte pool 0", first_unused_memory, 8192);
/* Allocate the stack for thread 0. */
tx_byte_allocate(&byte_pool_0, &pointer, 1024, TX_NO_WAIT);
/* Create my_thread! */
tx_thread_create(&my_thread, "My Thread",
my_thread_entry, 0x1234, pointer, 1024,
3, 3, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 1. */
tx_byte_allocate(&byte_pool_0, &pointer, 1024, TX_NO_WAIT);
/* Create my_thread! */
tx_thread_create(&my_thread_2, "My Thread 2",
my_thread_entry_2, 0x1234, pointer, 1024,
3, 3, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Create the semaphore. */
tx_semaphore_create(&semaphore_0, "semaphore 0", 1);
/* Create the semaphore. */
tx_semaphore_create(&semaphore_1, "semaphore 1", 0);
}
Call tx_kernel_enter()
in main()
. Build the project.
At this point, the build process should complain about undefined references to __RAM_segment_used_end__
and _vectors
which are referenced in tx_initialize_low_level.S
file. __RAM_segment_used_end__
should point to the first memory address in RAM which has not been used for stacks or heaps during the startup, and is available for use by kernel. The linker script defines such pointer to be _end
. So, use _end
instead of __RAM_segment_used_end__
. _vectors
should point to the vector table or exception table. In our project, the table is named exception_table
. So, rename _vectors
as exception_table
. Modify the SYSTEM_CLOCK
and SYSTICK_CYCLES
to configure a 10ms systick.
SYSTEM_CLOCK = 300000000 // 300MHz
SYSTICK_CYCLES = ((SYSTEM_CLOCK)/100 -1) // for 10ms systick
_tx_thread_system_stack_ptr
should point to the top of the stack. The stack address is the first member in the exception_table
structure.
The following code in tx_initialize_low_level.S
initializes the vector table offset register with the address of the vector table. This step is already done in the C startup code before main is called, so this code can be removed from tx_initialize_low_level.S
.
/* Setup Vector Table Offset Register. */
MOV r0, #0xE000E000 // Build address of NVIC registers
LDR r1, =_vectors // Pickup address of vector table
STR r1, [r0, #0xD08] // Set vector table address
The threadx source comes with the definition of SysTick_Handler. The SysTick_Handler is also defined in the main.c
file. So, remove the definition in main.c
file.
Build the project. Check if the program runs as expected, i.e. the execution should alterate between the two threads that we have created. Also, check if tx_thread_sleep
causes to sleep for correct amount of time.
Note: printf function does not work after this.
Configure User LED
Since printf
is not working, we will use a LED as a visual indication that the threads are working. SAME70-Xplained board has a user LED at pin PC8. Go to Project->Reconfigure Atmel Start Project
and configure the pin PC8 to be output. Generate the project, which will add necessary driver initialization for the LED.
From the threads that we have created, call the function below:
gpio_toggle_pin_level(LED0);
Build the project and run the program. The LED should toggle every 5 seconds.