Using lib_spdif to i2s with FreeRTOS/C++

Discussions relating to the XK-EVK-XU316
kubaG
Junior Member
Posts: 4
Joined: Fri Jun 07, 2024 12:30 am

Using lib_spdif to i2s with FreeRTOS/C++

Post by kubaG »

Hi,

TL;DR:
I'm trying to implement SPDIF to I2S on FreeRTOS.

I started developing a project based on the XU316 MCU, currently using the XK-AUDIO-316-MC-AB board. My code is primarily written in C++, but I'm encountering issues with lib_spdif, which is written in XC. I used a wrapper to integrate the XC code with my C++/C code, as suggested in this forum, but it only partially resolved my issues. I separated the spdif_rx() function into one task and the spdif_rx_sample() function into another.

Here’s my current pipeline:

spdif_rx_sample() fills buffer[2][192].
When the buffer is full, I use rtos_intertile_tx() to send it to another tile.
On the receiving tile, I have a generic pipeline where:
generic_pipeline_input() uses rtos_intertile_rx()
generic_pipeline_output() uses rtos_i2s_tx()

Main Problem:
Half of the samples incoming from SPDIF are lost. I've verified this using a logic analyzer on both the SPDIF RX and I2S lines. Additionally, print statements in lib_spdif show many missing preambles in the spdif_rx() function, but I don't understand why.

Questions:
Should I move spdif_rx out of the FreeRTOS task scope?
The AN00231 document mentions using ASRC. Is it necessary in my case too?


Any guidance or suggestions would be greatly appreciated.
Thanks!
User avatar
Ross
Verified
XCore Legend
Posts: 1070
Joined: Thu Dec 10, 2009 9:20 pm
Location: Bristol, UK

Post by Ross »

Im not sure about your desired timescales but xmos are just about to publish a new app note (within next couple of weeks) on the spdif -> i2s subject, including SRC. Look out for AN02003.

Personally I wouldn't use an rtos for an audio steaming issue like this unless there is some good reason to do so (and AN02003 doesn't).

On the subject of SRC, you'll need to use it (specifically ASRC), or use lib_sw_pll to generate a master clock from the App PLL synced to the incoming S/PDIF stream, which you then go in to clock your I2S. This is how the USB Audio design handles incoming S/PDIF, for example.
Technical Director @ XMOS. Opinions expressed are my own
kubaG
Junior Member
Posts: 4
Joined: Fri Jun 07, 2024 12:30 am

Post by kubaG »

Thank you for your suggestions. I decided to give XC and the examples from SW_USB_AUDIO and AN00231 another shot. Treating the XU316 more like an FPGA than an MCU has also helped. I initially thought FreeRTOS would be better for handling events to switch active inputs for SPDIF/USB and implementing some signal processing, which I unfortunately cannot share here.

Currently, I have working USB and SPDIF in separate projects. I used loader.xc as suggested in several places here. I am surprised at how quickly it can load an image from flash.

My only question is whether it is possible to reload an image after selecting one. For example, if I select binary no.1 using a button, and during its operation, an interrupt from the button causes binary no.1 to be stopped and binary no.2 (which supports SPDIF) to be loaded.

For me, the easiest solution would probably be to electronically restart the device and somehow remember which binary to load. However, maybe it is possible to have some kind of "sub-loader" in every image?
User avatar
Ross
Verified
XCore Legend
Posts: 1070
Joined: Thu Dec 10, 2009 9:20 pm
Location: Bristol, UK

Post by Ross »

Sounds like you’re making good progress :) agree with your conclusions.

Using a switch would be easier than a button, then you just need to reboot on any change in value from the switch. Then customise the boot loader to load the relevant image based on the switch input on every boot.

If you use a button you’ll have “remember” the boog image, as you suggest. Which isn’t too much extra effort, but is some.

On customising the loader see here: https://www.xmos.com/documentation/XM-0 ... ash-loader

Rebooting each node is done by writing to the pll control register.

There’s some example code here : https://github.com/xmos/lib_xua/blob/de ... /reboot.xc
Technical Director @ XMOS. Opinions expressed are my own
kubaG
Junior Member
Posts: 4
Joined: Fri Jun 07, 2024 12:30 am

Post by kubaG »

A bit off topic. Multi image bootloader works great but for the last few days I've been trying to implement a concept of dynamically starting and closing tasks as an alternative solution. I managed to make a proof of concept for simple functions that print something to the console.

In the main function I create:

tile 0:

- task-A which handles buttons, sends information via the interface which button was pressed to task-B and task-C. can close tasks created by task-B and task-C via channels

- task-B which manages the creation of tasks on tile 0

tile 1:
- task-C which manages the creation of tasks on tile1

Each task created by task-B or task-C gets its own channel so that task-A can close it

Part of the code( sorry that is so chaotic):

Code: Select all

// Define interfaces for each task
interface task_interface
{
    void toggle(unsigned value);
};

// Function to monitor buttons and signal task controllers
void monitorButtons(interface task_interface client monitorBtn0, interface task_interface client monitorBtn1,chanend cM,chanend cM2)
{
    unsigned valueS;
    p_ctrl <: 0b00100000;

    while (1) {
        printf("select button\n");
        select
        {
            case p_button when pinsneq(0b00000111) :> valueS:
                if (valueS == 0b00000110) {
                    printf("btn1\n");
                    monitorBtn0.toggle(valueS); // Signal taskM 1 to toggle
                }
                else if (valueS == 0b00000101) {
                    printf("btn2\n");
                                        cM <:5;
                                                                                cM2 <:5;
                }
                else if(0b00000011)
                {
                                        printf("btn3\n");

                                                            monitorBtn1.toggle(valueS); // Signal taskM 2 to toggle
                }
                else {
                    printf("button unknown\n");
                }
                break;
            }
                                printf("buttons active\n");

            delay_milliseconds(50);
    }
}

// Task 1 function
void task1(chanend cT1)
{
    unsigned running = 0;

    while (1) {
        select
        {
            case cT1 :> int chnl_input_a:
            {
                printf("cT1: %d\n",chnl_input_a);
                if(running == 0 )
                {
                running= !running;
                }
                else
                {
                return;
                }
                break;
            }
            default:
            {
                break;
            }
        }



        if (running) {
            printf("Task 1 running\n");
            delay_milliseconds(500);
        }
    }
}
// Task 2 function
void task2(chanend cT2)
{
    unsigned running = 0;

    while (1) {
        select
        {
            case cT2 :> int chnl_input_a:
            {
                printf("cT2: %d\n",chnl_input_a);
                if(running == 0 )
                {
                running= !running;
                }
                else
                {
                return;
                }
                break;
            }
            default:
            {
                break;
            }
        }



        if (running) {
            printf("Task 2 running\n");
            delay_milliseconds(500);
        }
    }
}

// Task control function
void task_control0(interface task_interface server monitorBtn,chanend cT)
{
    unsigned tmpValue            = 0;
    unsigned state_machine_task1 = 0; // 0 init state = task off => start task , 1 stop task

    while (1) {
                    printf("state machine : %d\n",state_machine_task1);
        select
        {
        case monitorBtn.toggle(unsigned value):
            printf("task_control0 received value:%d\n", value);
            tmpValue = value;
            break;
        }
        switch (tmpValue) {
        case (0b00000110): {
            printf("1 case:\n");
            printf("state machine : %d\n",state_machine_task1);
            switch (state_machine_task1) {
            case 0: {
                state_machine_task1 = 1;
                par {
                    task1(cT);
                }
                                                state_machine_task1 = 0;

                printf("task1 end\n" );
                break;
            }

            }
            break;
        }
        case (0b00000101): {
            printf("2 case:\n");
            break;
        }
        default: {
            printf("default case:\n");
            break;
        }
        }
    }

}

// Task control function
void task_control1(interface task_interface server monitorBtn,chanend cT)
{
    unsigned tmpValue            = 0;
    unsigned state_machine_task2 = 0; // 0 init state = task off , 1 start task , 2 stop task

    while (1) {
        printf("state machine Tile1 : %d\n",state_machine_task2);
        select
        {
        case monitorBtn.toggle(unsigned value):
            printf("task_control1 received value:%d\n", value);
            tmpValue = value;
            break;
        }
        switch (tmpValue) {
        case (0b00000011): {
            printf("1 case:\n");
            printf("state machine : %d\n",state_machine_task2);
            switch (state_machine_task2) {
            case 0: {
                state_machine_task2 = 1;
                par {
                    task2(cT);
                }
                                state_machine_task2 = 0;
                printf("task2 end\n" );
                break;
            }

            }
            break;
        }
        case (0b00000101): {
            printf("2 case:\n");
            break;
        }
        default: {
            printf("default case:\n");
            break;
        }
        }
    }
}

int main()
{
    interface task_interface task[2];
    chan cX,cX2;

    par
    {
        on tile[0]:
        {
            par
            {
                monitorButtons(task[0], task[1],cX,cX2);
                task_control0(task[0],cX);
            }
        }
        on tile[1]:
        {
            par
            {
                task_control1(task[1],cX2);
            }
        }
    }
    return 0;
}
However, using this solution is difficult for me when I want to do the same for spdif(AN00231 ) and usb xua (AN00246_xua_example). Even the part of just running these tasks outside of main function (without dynamic on/off via buttons).
Both examples share some channels / interfaces between functions that are run on different tiles. And I have the impression that the problem is passing interfaces from the main function down.
Does my approach make any sense at all or can something be improved?
User avatar
Ross
Verified
XCore Legend
Posts: 1070
Joined: Thu Dec 10, 2009 9:20 pm
Location: Bristol, UK

Post by Ross »

Ross wrote: Mon Jul 22, 2024 9:22 pm Im not sure about your desired timescales but xmos are just about to publish a new app note (within next couple of weeks) on the spdif -> i2s subject, including SRC. Look out for AN02003.

Personally I wouldn't use an rtos for an audio steaming issue like this unless there is some good reason to do so (and AN02003 doesn't).

On the subject of SRC, you'll need to use it (specifically ASRC), or use lib_sw_pll to generate a master clock from the App PLL synced to the incoming S/PDIF stream, which you then go in to clock your I2S. This is how the USB Audio design handles incoming S/PDIF, for example.
Sorry for the delay but the original app note I mentioned is now here: https://www.xmos.com/file/an02003-spdif ... with-asrc/
Technical Director @ XMOS. Opinions expressed are my own