Using IIO to manage an ADC from Petalinux.

Manage an ADC is something that any engineer dedicated to signal processing has to do very often. This task, in general, use to be no much complicated, unless you are using a high-speed ADC with LVDS interface or something similar, but that kind of ADCs are not the scope of this post. On this post I will show you how we can manage an ADC from Petalinux in less than 10 minutes. Actually, I said that I will show you to manage an ADC, but this process will be valid to manage ADC, DAC, gyroscopes, accelerometers… and a large list of different devices. I am talking about compatible industrial IO (IIO) devices, that has native support from Linux kernel. 

According the definition of Industrial IO on https://www.kernel.org   

The main purpose of the Industrial I/O subsystem (IIO) is to provide support for devices that in some sense perform either analog-to-digital conversion (ADC) or digital-to-analog conversion (DAC) or both (…) Usually these sensors are connected via SPI or I2C. A common use case of the sensors devices is to have combined functionality (e.g. light plus proximity sensor). 

Drivers to manage these devices can be manually added to the Linux kernel, or in case that the device we are using are one of devices included on the kernel by default, we only need to add the corresponding entry to the device tree, and include the drivers on the kernel configuration. In this case, I will use a controlpaths board based on an ADC of Texas Instruments which is natively supported by the kernel, the ADC122s021. If we search this ADC on the supported ADCs on the kernel.org web page, we notice that the ADC apparently is not supported, but we can find an equivalent ADC, in terms of protocol. In this case, we can find that ADC128s052 is supported, and this ADC is part of the same family that the ADC122s021, and that means the SPI protocol to manage one is the same to the other. Also, if we open the .yaml file, we can see a list of supported ADC, in which it appears the ADC122s021. 

In order to develop this project, we can use any board where we can install Linux. In my case I am going to use the Picozed 7015 board with the FMC Carrier Gen2. This board is based on xc7z015 SOC device.

To start this development, first we have to create a new project on Vivado, and create a new block design. On this block design we have to add only a Zynq Processing System. According the board you will use, you will have access to the SPI pins of the PS or not. In my case, SPI pins are used by other peripherals, so if I want to use the SPI0 bus of the PS, I have to use EMIO to pull the pins out through the PL. On the tab MIO Configuration,we will search the peripheral SPI 0 (SPI 1 can used as well), and configure the IO to EMIO. 

Done that, on the Zynq PS block on block design, we will have available all the pins of the SPI0 peripheral. Next, we have to connect these pins to external ports, and later, on the xdc file, we will assign the corresponding pins of our board. As you can see, the block design in this case is pretty simple.

When we develop a driver for and ADC, we choose the frequency of the SPI, and is easy to synthesize accurately a desired frequency. In this case, we are limited by the PS clocking system. In order to manage the clock of the SPI, we have to select the frecuency of the clock that is connected to the SPI peripheral. Output SCK frequency will the frequency of the clock of the SPI peripheral, divided by a power of 2 from 1 to 128. That is, for a clock of 166.666 MHz, if we want an SCK frequency of 2 MHz, the best approximation we can achieve will be 166.66 MHz/128 = 1.3MHz. You can find more information about clocking on UG585, pg698. In this case, I will select a frequency of 166.666 Mhz.

Next, as always, verify the block design, generate the wrapper, generate the bitstream and export the hardware to use it on the Petalinux build process.

Once the Petalinux project has been created, next we will import the hardware exported on Vivado.

petalinux-config --get-hw-description ../../../xilinx/picozed7015_petalinux/

Now, we will modify the device tree to tell the kernel that there is an ADC122s021 connected to the SPI0 (or SPI1), and connected to the SS 0. This has to be configured on project-spec/meta-user/recipes-bsp/device-tree/files, on the file system-conf.dtsi, by adding the next lines.

/include/ "system-conf.dtsi"
/ {
};
 
&spi0 {
status = "okay";
 
        adc@0 {
            compatible = "ti,adc122s021";
            reg = <0>;
            spi-max-frequency = <2000000>;
        };
    };

Notice that the SPI frequency is set to 2 MHz, but this is an unreachable clock frequency with our configuration, so the clock will run at 1.3 MHz.

The next step is add the corresponding drivers to the kernel. To do that we have to make this.

petalinux-config –c kernel

After a while, a new terminal window will be open, and we will navigate to

Device Drivers > Industrial I/O support > Analog to digital converters

And select the line corresponding with the ADC we are going to use, in my case Texas Instruments ADC122S021.

Once we have the kernel configured and the device tree modified, last is build our Petalinux distribution.

petalinux-build

Once Petalinux is built, I use to package all the files and copy them to an SD, but in this case we will run Petalinux in a different way, through the JTAG. This method is very useful if we are debugging our system and we want avoid the process of extract SD, replace files, and insert SD again. Also, as the JTAG is connected, we can use an ILA to debug our design. To boot Petalinux through the JTAG, first we have to configure the PL.

petalinux-boot --jtag –fpga

Next we have to fill all the corresponding memory locations with the u-boot and the Linux kernel itself.

petalinux-boot --jtag –kernel

Done that, Petalinux must boot without issues. Once inside Petalinux, we will navigate to

/sys/bus/iio/devices

On this folder we can find 2 devices. Device number 1 is corresponding with the XADC integrate on the Zynq part, and the device0 will be our ADC. To check that, we will enter to iio:device0 folder, and check the content of name

>> cat name
adc122s021

If we check all the content of the folder we can see several files and folders. Interesting files to us are in_voltagex_raw, whose return us the value read on each channel. In this case I have connected a 2v source on the channel 0, and my supply voltage is 3v, so the result will be 2/3*2^12 = 2730.

Notice that the driver is compatible with the ADC128s052, that is an 8 channels ADC, but the ADC I use has only 2 channels, so if I try to read channels from 2 to 7, the value returned by the ADC will be 0.

Industrial IO is very interesting bus which we can manage different kind of devices in a very simple way. There are a lot of devices supported natively by the kernel, from ADC, to PMICs, that make very easy develop application to manage them. If you think this bus is powerful, imagine to develop your own drivers mixing the configuration of a device like an ADC from Petalinux, and besides generate a high speed interface on the PL, dumping readings to DDR via AXI Stream. Additional to this, from a host PC, MATLAB reading data from DDR and use it as input to Simulink algorithm.

Although I never had talk in the blog about IIO, we have use it to manage the USRP B205 Mini on this post, where the IIO bus is managed by the host PC through a USB 3.0 interface.

Files of this development are available on Github.

Leave a Reply