Using i2c shared module on XCORE-200 MCA

Technical questions regarding the xTIMEcomposer, xSOFTip Explorer and Programming with XMOS.
radii
New User
Posts: 2
Joined: Thu Aug 12, 2021 12:38 pm

Using i2c shared module on XCORE-200 MCA

Post by radii »

Hi,

I have developed application code for an XMOS based digital (audio) mixer and have encountered a build error (use of ‘r_i2C’ violates parallel usage rules) on XCORE-200 MCA (app_usb_aud_xk_216_mc).

My issue is due to using r_i2c in two parallel tasks; the audioHW driver, and a button matrix reading task that is also on the i2c bus. I understand the XMOS architecture does not support accessing the same memory location from separate tasks, tiles or cores, hence the error, although I am using the i2c shared module which is developed for multiple access, due to use of swlock.

My button matrix code is in my audioHW driver. ADC/DAC calls and button matrix calls are separate tasks from main.xc into my audioHW driver, and from audioHW.xc they call the same i2c shared routine.

I encountered the same issue several years ago while developing a high end audio XMOS based DAC with digital preamp, which had iap running, and the reference design when including iap uses r_i2c in parallel tasks. I noticed a workaround employed having the i2c function in a 'c' driver, and connecting via a channel (c_aud_cfg) from main(). I copied the i2c function to coprocessor.c which was already in use (by the iap task), and I added #include coprocessor.h to the audioHW driver, so I did not have to create a new c driver. At the time I tried the same code in and .xc driver which didn’t work, and not using the c_aud_cfg chanend also didn’t work. This worked very reliably and commercial pressures meant the product was launched without fully understanding the workaround.

I have seen several threads on xcore.com regarding shared i2c which detail setting up i2c calls in custom drivers, although I would love to understand how to use the i2c shared module as intended. The XMOS device is a fantastic processor and understanding how to share resources will be a massive help.

Thanks.


User avatar
CousinItt
Respected Member
Posts: 265
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

I don't have any experience of working with the usb audio app, but the shared i2c module looks a bit clunky, in that you have to pass the port structure to it every time you read or write. I suspect that's where your problem arises. The xmos i2c library is much easier to work with in that regard, but I think the usb audio design uses an approach that predates the library. One solution is to have a single task that manages the i2c interface, and then pass the port information to it once during an initialisation step. Then you can provide local wrappers for the read/write functions that provide the port info via the local copy, so that the client tasks never have to supply the port information directly, hence avoiding the sharing violation.
radii
New User
Posts: 2
Joined: Thu Aug 12, 2021 12:38 pm

Post by radii »

Hi CousinItt thanks for your reply.

I have reviewed my previous project and it confirms my hunch from the time that the c file is a compiler workaround. It seems the only way for the i2c shared module to work shared (using the same structure variable in different parts of a program) is to create an indirection to the memory space using a pointer. To further this assumption (based on not seeing this stated in the manuals) the compiler does not allow address pointers in .xc, although the compiler does allow these in a .c driver within the same XMOS program. So the call for the r_i2c structure to be an argument to the i2c_shared_master_read_reg(...) function must come from a .c driver and the r_i2c must be preceded by &, which makes it work and is a very simple code addition. As far as I am concerned if a swlock is the best tool for the job as part of a well structured system then I am happy to use it over a less structured approach, of coruse it's implementation must be well understood.

I had considered the interface approach although from a hardware perspective the i2c is time critical, the system behaviour depends on it. If there are two tasks waiting for the same i2c interface to send and receive packets that control the feel of the system and avoid possibility of audio glitches then we are moving away from embedded into RTOS territory. I prefer XMOS over an RTOS system as I can have achieve rock solid firmware and product operation consistently.
User avatar
akp
XCore Expert
Posts: 544
Joined: Thu Nov 26, 2015 11:47 pm

Post by akp »

I believe you should be able to share resources (on a single tile) by using an intermediate C file that gets initialized in one task with the port name. This will be cast to an unsigned 32 bit number in the C module as a static unsigned for instance. Then the C module will call the underlying XC module with that value, which is cast back to the port name. That should mean that the actual port is only referenced in one XC task.

The C module would be like:
init(port)
{
local_port = port;
}

c_i2c_write(value)
{
xc_i2c_write(local_port, value);
}

etc for read... it seems to me that should compile without issue. Now, the problem arises in that the port resource is now shared between two tasks so you have to be certain it's never accessed simultaneously by both. There's some guard time of like 3 or 4 clock cycles (maybe due to the pipeline? anyway it's in the ISA manual) where if a resource gets accessed by two different tasks within that interval it will cause an exception. So you can use a hw lock or sw lock or other programming practice to avoid that situation.