HID and audio - strange behaviour

Technical questions regarding the XTC tools and programming with XMOS.
satov
Junior Member
Posts: 7
Joined: Wed Apr 30, 2025 3:34 pm

HID and audio - strange behaviour

Post by satov »

Hello! My goal is to run a minimal USB to I2S application along with the addition of a custom bidirectional communication channel between the host (PC) and the device. By reading here and there in the forum, I came to the conclusion that the easiest way would be by using HID. To get a bit familiar with the platform and USB HID, I took the hid_mouse example from lib_xud, I modified it by adding custom IN and OUT endpoints and I got it working quite nicely, I think.

This is the task that deals with the endpoints. LED3 (I'm using XK-AUDIO-316-MC-AB) blinks every ~200 ms or so, that is every time an IN report is sent. LED0-2 also blink according to the data received on the OUT endpoint. Wireshark shows consistent transactions.

Code: Select all

/* Task that handles the HID endpoint IN and endpoint OUT */
DECLARE_JOB(hid_inout_task, (chanend_t, chanend_t));
void hid_inout_task(chanend c_ep_out, chanend c_ep_in)
{
    unsigned char rxBuffer[64];
    unsigned length;
    XUD_Result_t result;
    unsigned int counter = 0;
    int led_state = 0;

    debug_printf("Launching hid_inout_task...\n");

    XUD_ep ep_out = XUD_InitEp(c_ep_out);
    XUD_ep ep_in = XUD_InitEp(c_ep_in);

    /* Mark OUT and IN endpoints as ready to receive/send */
    XUD_SetReady_Out(ep_out, rxBuffer);
    XUD_SetReady_In(ep_in, g_reportBuffer, sizeof(g_reportBuffer));

    SELECT_RES(
        CASE_THEN(c_ep_out, ep_out_label),
        CASE_THEN(c_ep_in, ep_in_label))
    {
        ep_out_label:
            XUD_GetData_Select(c_ep_out, ep_out, &length, &result);

            /* Process data from host (length bytes) */
            led_state = (led_state & 0xFFFFFFF8) | (1 << (rxBuffer[0] % 4));
            port_out(led_port, led_state);

            /* Mark EP as ready again */
            XUD_SetReady_Out(ep_out, rxBuffer);
            continue;

        ep_in_label:
            if ((counter++ | 0xFFF00000) == 0xFFF00000) {
                led_state = led_state ^ 0x8;
                port_out(led_port, led_state);

                XUD_SetData_Select(c_ep_in, ep_in, &result);

                /* Packet successfully sent to host, create next byte */
                g_reportBuffer[0] = (char)((counter & 0x0FF00000) >> 20);

                /* Mark EP as ready again */
                XUD_SetReady_In(ep_in, g_reportBuffer, sizeof(g_reportBuffer));
            }
            continue;
    }
}
Quite happy with this result, I took a basic app in sw_usb_audio-_sw_9_0_0 and made all the changes I think are needed to add my custom IN and OUT HID endpoint to lib_xua. I got the OUT endpoint working as expected, but the IN endpoint is not working as expected. The code below is the XUA_Buffer_Ep task after the pre-processor did its job (compiled with --save-temps). The `case' statement marked with the comment // [COMMENT 1] is executed only once. To get the IN report working as expected I had to implement the blinking code in the `default' statement (see // [COMMENT 2]).

Even more strange is the fact that in the case marked with // [COMMENT 1], if I remove the if statement (if ((hidInCounter++ | 0xFFF00000) == 0xFFF00000)), then I see input reports every 4ms! So it's like `if' statement is never true. I tried different conditions for the if statement, even a simple if (hidInCounter++ == 10) with hidInCounter reset to 0 inside the if doesn't work.

Why is that? I would never realized by myself that I need to handle the IN endpoint in the 'default' statement, but I noticed that original lib_xua does the same (see here) to handle its own input HID for playback control (that is not enabled here, and not present in the descriptors).

Code: Select all

unsafe{volatile unsigned * unsafe masterClockFreq_ptr;}
void XUA_Buffer_Ep(
    register chanend c_aud_out,
    register chanend c_aud_in,
    chanend c_aud_fb,
    chanend c_sof,
    chanend c_aud_ctl,
    in port p_off_mclk,
    chanend c_hid,
    chanend c_hid_out
    )
{

    XUD_ep ep_aud_out = XUD_InitEp(c_aud_out);
    XUD_ep ep_aud_in = XUD_InitEp(c_aud_in);
    XUD_ep ep_aud_fb = XUD_InitEp(c_aud_fb);
    XUD_ep ep_hid = XUD_InitEp(c_hid);
    
    unsigned int hidInCounter = 0;
    int led_state = 0;
    XUD_ep ep_hid_out = XUD_InitEp(c_hid_out);

    unsigned u_tmp;
    unsigned char cmd;
    unsigned sampleFreq =  48000 ;
    unsigned masterClockFreq =  (512*48000) ;
    unsigned lastClock = 0;
    unsigned freqChange = 0;
    unsafe{masterClockFreq_ptr = &masterClockFreq;}
    unsigned clocks = 0;
    long long clockcounter = 0;
    unsigned bufferIn = 1;
    int sofCount = 0;
    unsigned mod_from_last_time = 0;
    xc_ptr aud_from_host_buffer = 0;
    unsigned char hid_from_host_buffer[64];

    asm("stw %0, dp[aud_from_host_usb_ep]"::"r"(ep_aud_out));
    asm("stw %0, dp[aud_to_host_usb_ep]"::"r"(ep_aud_in));
    asm("stw %0, dp[buffer_aud_ctl_chan]"::"r"(c_aud_ctl));
    asm volatile("stw %0, dp[" "g_aud_from_host_flag" "]"::"r"(1):"memory") ;
    asm volatile("stw %0, dp[" "g_aud_to_host_flag" "]"::"r"(1):"memory") ;
    
    fb_clocks[0] = 0;
    
    XUD_SetReady_In(ep_hid, g_customHidData, sizeof(g_customHidData));
    XUD_SetReady_Out(ep_hid_out, hid_from_host_buffer);

    while(1)
    {
        XUD_Result_t result;
        unsigned length;
        select
        {
            case  __builtin_inct_byref(c_aud_ctl, cmd) :
            {
                if(cmd ==  4 )
                {
                    unsigned receivedSampleFreq =  __builtin_in_uint(c_aud_ctl) ;
                    asm volatile("stw %0, dp[" "g_freqChange_sampFreq" "]"::"r"(receivedSampleFreq):"memory") ;
                }

                else if(cmd ==  9 )
                {
                    unsigned formatChange_DataFormat =  __builtin_in_uint(c_aud_ctl) ;
                    unsigned formatChange_NumChans =  __builtin_in_uint(c_aud_ctl) ;
                    unsigned formatChange_SubSlot =  __builtin_in_uint(c_aud_ctl) ;
                    unsigned formatChange_SampRes =  __builtin_in_uint(c_aud_ctl) ;

                    asm volatile("stw %0, dp[" "g_formatChange_NumChans" "]"::"r"(formatChange_NumChans):"memory") ;
                    asm volatile("stw %0, dp[" "g_formatChange_SubSlot" "]"::"r"(formatChange_SubSlot):"memory") ;
                    asm volatile("stw %0, dp[" "g_formatChange_DataFormat" "]"::"r"(formatChange_DataFormat):"memory") ;
                    asm volatile("stw %0, dp[" "g_formatChange_SampRes" "]"::"r"(formatChange_SampRes):"memory") ;
                }

                else if (cmd ==  8 )
                {

                    XUD_BusSpeed_t busSpeed;
                    unsigned formatChange_DataFormat =  __builtin_in_uint(c_aud_ctl) ;
                    unsigned formatChange_NumChans =  __builtin_in_uint(c_aud_ctl) ;
                    unsigned formatChange_SubSlot =  __builtin_in_uint(c_aud_ctl) ;
                    unsigned formatChange_SampRes =  __builtin_in_uint(c_aud_ctl) ;

                    asm volatile("stw %0, dp[" "g_formatChange_NumChans" "]"::"r"(formatChange_NumChans):"memory") ;
                    asm volatile("stw %0, dp[" "g_formatChange_SubSlot" "]"::"r"(formatChange_SubSlot):"memory") ;
                    asm volatile("stw %0, dp[" "g_formatChange_DataFormat" "]"::"r"(formatChange_DataFormat):"memory") ;
                    asm volatile("stw %0, dp[" "g_formatChange_SampRes" "]"::"r"(formatChange_SampRes):"memory") ;
                    asm volatile("ldw %0, dp[" "g_curUsbSpeed" "]":"=r"(busSpeed)::"memory") ;

                    if (busSpeed == XUD_SPEED_HS)
                    {
                        XUD_SetReady_In(ep_aud_fb, (fb_clocks, unsigned char[]), 4);
                    }
                    else
                    {
                        XUD_SetReady_In(ep_aud_fb, (fb_clocks, unsigned char[]), 3);
                    }
                }

                asm volatile("stw %0, dp[" "g_freqChange" "]"::"r"(cmd):"memory") ;
                asm volatile("stw %0, dp[" "g_freqChange_flag" "]"::"r"(cmd):"memory") ;
                break;
            }
            case  __builtin_in_uint_byref(c_sof, u_tmp) :
                asm volatile(" getts %0, res[%1]" : "=r" (u_tmp) : "r" (p_off_mclk));
                asm volatile("ldw %0, dp[" "g_freqChange" "]":"=r"(freqChange)::"memory") ;
                if((freqChange ==  4 ) || !feedbackValid)
                {

                    lastClock = u_tmp;
                    feedbackValid = 1;
                }
                else
                {
                    unsigned usb_speed;
                    asm volatile("ldw %0, dp[" "g_curUsbSpeed" "]":"=r"(usb_speed)::"memory") ;
                    unsigned long long feedbackMul = 64ULL;

                    if(usb_speed != XUD_SPEED_HS)
                        feedbackMul = 8ULL;
                    int count = (int) ((short)(u_tmp - lastClock));
                    unsigned long long full_result = count * feedbackMul * sampleFreq;
                    clockcounter += full_result;
                    lastClock = u_tmp;
                    if(sofCount == 128)
                    {
                        sofCount = 0;

                        clockcounter += mod_from_last_time;
                        clocks = clockcounter / masterClockFreq;
                        mod_from_last_time = clockcounter % masterClockFreq;

                        if(usb_speed == XUD_SPEED_HS)
                        {
                            clocks <<= 3;
                        }
                        else
                        {
                            clocks <<= 6;
                        }
                        {
                            asm volatile("stw %0, dp[g_speed]"::"r"(clocks));
                            asm volatile("ldw %0, dp[" "g_curUsbSpeed" "]":"=r"(usb_speed)::"memory") ;
                            if (usb_speed == XUD_SPEED_HS)
                            {
                                fb_clocks[0] = clocks;
                            }
                            else
                            {
                                fb_clocks[0] = clocks >> 2;
                            }
                        }
                        clockcounter = 0;
                    }
                    sofCount++;
                }
            break;

            case XUD_SetData_Select(c_aud_in, ep_aud_in, result):
            {
                asm volatile("stw %0, dp[" "g_aud_to_host_flag" "]"::"r"(bufferIn+1):"memory") ;
                break;
            }

            case XUD_SetData_Select(c_aud_fb, ep_aud_fb, result):
            {
                XUD_BusSpeed_t busSpeed;
                asm volatile("ldw %0, dp[" "g_curUsbSpeed" "]":"=r"(busSpeed)::"memory") ;
                if (busSpeed == XUD_SPEED_HS)
                {
                    XUD_SetReady_In(ep_aud_fb, (fb_clocks, unsigned char[]), 4);
                }
                else
                {
                    XUD_SetReady_In(ep_aud_fb, (fb_clocks, unsigned char[]), 3);
                }
                break;
            }

            case XUD_GetData_Select(c_aud_out, ep_aud_out, length, result):
            {
                asm volatile("ldw %0, dp[" "g_aud_from_host_buffer" "]":"=r"(aud_from_host_buffer)::"memory") ;
                asm volatile("stw %0, %1[0]"::"r"(length),"r"(aud_from_host_buffer)) ;
                asm volatile("stw %0, dp[" "g_aud_from_host_flag" "]"::"r"(1):"memory") ;
                break;
            }
            
            case XUD_SetData_Select(c_hid, ep_hid, result):
            	// [COMMENT 1]
                if ((hidInCounter++ | 0xFFF00000) == 0xFFF00000) {
                    led_state = led_state ^ 0x8;
                    led_port <: led_state;
                    g_customHidData[0] = (char)((hidInCounter & 0x0FF00000) >> 20);
                    XUD_SetReady_In(ep_hid, g_customHidData, sizeof(g_customHidData));
                }
                break;

            case XUD_GetData_Select(c_hid_out, ep_hid_out, length, result):
                debug_printf("HID data from host, result %d, len %d\n", result, length) ;
                led_state = (led_state & 0xFFFFFFF8) | (1 << (hid_from_host_buffer[0] % 4));
                led_port <: led_state;
                XUD_SetReady_Out(ep_hid_out, hid_from_host_buffer);
                break;
                
            default:
            	// [COMMENT 2]
                if ((hidInCounter++ | 0xFFF00000) == 0xFFF00000) {
                        led_state = led_state ^ 0x8;
                        led_port <: led_state;
                        g_customHidData[0] = (char)((hidInCounter & 0x0FF00000) >> 20);
                        XUD_SetReady_In(ep_hid, g_customHidData, sizeof(g_customHidData));
                }
                break;
        }
    }
}