What is a concurrent process? In a few words pseudo parallelism. Take a look at the following diagram that I steal from somewhere else. The first image shows tree tasks (processes) running in a apparent parallel, but since in our microcontroller we only have one CPU this is not possible, in reality is like the second graph ( more or less ) the CPU will provide a given time to each task to run.

To achieve the previous one we need a tick, or a timer that provides and interrupt every millisecond, every time an interrupt happens a global variable increments its value, registering a tick. Using some interface we can read the global variable and easily translate its value to milliseconds.

This is an example of the most basic scenario, we need to blink two leds at a different times, instead of using the classic blocking delay( ms ); function we ask for a certain period of time using a if sentence for each case. Both process are independent from each other and both execute the blink led functionality with a few instruction that does not add interference between them.

if( TickTimer() >= _300ms )
{
    BlinkLedRed(); /*process 1*/
}
if( TickTimer() >= _50ms )
{
    BlinkLedBlue(); /*process 2*/
}

Keep in mind that instead of blinky leds we can place any process we want, like a serial transmission, or a motor control, the only thing we need to take care is that one of the process takes so long that interfere the periodic execution of the some other process.

For instance in the code below, DisplayInfo() takes about 70ms to run, that amount of time does not affect its own periodicity, but it will affect the MotorControl() execution, because now it wont be possible to run this second process every 50ms causing a potential failure on the motor control algorithm. The solution it is to redesign the DisplayInfo() process to broken down into sub-process, each of them shall take less than 50ms

if( TickTimer() >= _100ms )
{
    DisplayInfo(); /*This process block the Mcu for about 70ms*/
}

if( TickTimer() >= _50ms )
{
    MotorControl(); /*This other process only takes a few microseconds*/
}

Lets move on to another example with two process that need communication between them, like a program that every time a button is pressed the led will blink with a different period of time ( only four periods are allowed ). One process will take care of polling the button and determine how many times has been pressed, and a second process takes care the complexity of blink the led at a according time. A global variable is used to communicate both processes, PollButton() will modify its value indicating if the button had been pressed and how many times, while BlinkLed() will only read it and cleared it to blink the led according at the number of times its been pressed.

/*value of 0 is not been preesed, 1, 2, 3 are valid values*/
uint32_t n_press = 0;

...

if( TickTimer() >= _50ms )
{
    PollButton(); /*process 1*/
}
if( TickTimer() >= _100ms )
{
    BlinkLed(); /*process 2*/
}

/*preoccess to poll buton*/
void PollButton( void )
{
    if( Button() == PRESS )
    {
        n_press++ %= 4;
    }
}

/*proccess to toggle led according to n_press * 100ms*/
void BlinkLed( void )
{
    static last_n_press = n_press;
    if( last_n_press-- == 0 )
    {
        ToggleLed();
        last_n_press = n_press;
    }
}

The SMT32CubeG0 library comes with a Tick functionality given by the Systick Timer, It is configured by the HAL_Init function to trigger an interrupt every millisecond, this increment a global variable using the function HAL_IncTick ( look at the file app_ints.c ) and we just need to ask for this tick value using the function HAL_GetTick. For instance our first pseudo code will look like this using these functions:

uint32_t tick1 = HAL_GetTick(); /*get the current tick count for process 1*/
uint32_t tick2 = HAL_GetTick(); /*get the current tick count for process 2*/

...

/*if difference between current tick and last process tick is equal or bigger to 300ms*/
if( (HAL_GetTick() - tick1) >= 300 )
{
    tick1 = HAL_GetTick();/*get the current tick again*/
    BlinkLedRed(); /*run process 1*/
}

/*if difference between current tick and last process tick is equal or bigger to 50ms*/
if( (HAL_GetTick() - tick2) >= 50 )
{
    tick2 = HAL_GetTick();/*get the current tick again*/
    BlinkLedBlue(); /*run process 2*/
}

We can add any number of process we want following the same formula, keep in mind the CPU time that each process consume. We can say this is the most rudimentary way to implement what is called Scheduler, which is basically an algorithm in charge of running tasks (or process) on a given periodicity with a given priority.

This is a very crude example of what a program could look like using this methodology, it can be used as a template

uint32_t tick_50ms;
uint32_t tick_100ms;
uint32_t tick_500ms;

void InitTask_50ms(void);
void InitTask_100ms(void);
void InitTask_500ms(void);

void RunTask_50ms(void);
void RunTask_100ms(void);
void RunTask_500ms(void);

int main( void )
{
    InitTask_50ms();
    InitTask_100ms();
    InitTask_500ms();
    
    tick_50ms = HAL_GetTick(); /*get the current tick count for process 1*/
    tick_100ms = HAL_GetTick(); /*get the current tick count for process 2*/
    tick_500ms = HAL_GetTick(); /*get the current tick count for process 3*/
    
    while(1)
    {
        /*if difference between current tick and last process tick is equal or bigger to 50ms*/
        if( (HAL_GetTick() - tick_50ms) >= 50 )
        {
            tick_50ms = HAL_GetTick();/*get the current tick again*/
            RunTask_50ms(); /*run process 1*/
        }
        /*if difference between current tick and last process tick is equal or bigger to 100ms*/
        if( (HAL_GetTick() - tick_100ms) >= 100 )
        {
            tick_100ms = HAL_GetTick();/*get the current tick again*/
            RunTask_100ms(); /*run process 2*/
        }
        /*if difference between current tick and last process tick is equal or bigger to 500ms*/
        if( (HAL_GetTick() - tick_500ms) >= 500 )
        {
            tick_500ms = HAL_GetTick();/*get the current tick again*/
            RunTask_500ms(); /*run process 3*/
        }
    }
}

void InitTask_50ms(void)
{
    /*Placed here any code to initialize what you need on process 1*/
}

void InitTask_100ms(void)
{
    /*Placed here any code to initialize what you need on process 2*/
}

void InitTask_500ms(void)
{
    /*Placed here any code to initialize what you need on process 3*/
}

void RunTask_50ms(void)
{
    /*Placed here any code of process 1 that will run every 50ms*/
}

void RunTask_100ms(void)
{
    /*Placed here any code of process 2 that will run every 100ms*/
}

void RunTask_500ms(void)
{
    /*Placed here any code of process 3 that will run every 500ms*/
}