Introduction
This article shows how the I²C bus functionality of the SAMA5D2 Series ARM® Cortex®-A5 Microprocessor Unit (MPU) is enabled in the Linux® kernel and how to access the I²C bus in user space.
Usually, I²C devices are controlled by a kernel driver. But it is also possible to access all devices on an adapter from user space through the I²C dev interface. I²C dev is a character device node file. It can be accessed by read(), write(), and ioctl(). The interface of each I²C bus can be exported to user space through its own I²C dev device node.
Prerequisites
This application is developed for the ATSAMA5D27-SOM1-EK1 development platform:
This application is developed using the Buildroot build system.
Hardware
For this application, you will be controlling the I²C bus of the mikroBUS 1 expansion socket of the ATSAMA5D27-SOM1-EK1. The figure below shows the expansion capability of the SOM1-EK1.
The ATSAMA5D27 SOM1 contains five Flexible Serial Communications Controller (FLEXCOM) peripherals to provide serial communications protocols: USART, SPI, and TWI.
You will control pin PD23 and PA24 from the ATSAMA5D27 SOM1 which connects to J25 pins 5 and 6 of the mikroBUS 1 connector (labeled TWCK_mBUS1 and TWD_mBUS1 on the schematic).
mikroBUS pin | Schematic Name | FLEXCOM I/O | Package Pin |
---|---|---|---|
J25 pin 5 | TWCK_mBUS1 | FLEXCOM1_IO1 | PA23 |
J25 pin 6 | TWD_mBUS1 | FLEXCOM1_IO0 | PA24 |
For more details of the Package and Pinout of the SAMA5D2, refer to Table 6-2. Pinouts in SAMA5D2 Series Data Sheet.
THERMO 5 click
For this demonstration, we will use the Mikroe THERMO 5 click board. The THERMO 5 click measures temperature in default range of 0°C to 127°C and in an extended range of -64°C to 191°C with ±1°C accuracy. The THERMO 5 click features the Microchip EMC1414 temperature sensor.
Insert the THERMO 5 click into the mikroBUS 1 slot on the ATSAMA5D27-SOM1-EK1 as shown in the figure below:
Buildroot Configuration
Objective: Using Buildroot, build a bootable image and FLASH onto an SD Memory Card for the ATSAMA5D27-SOM1-EK1 development board.
Follow the steps for building the image in the article Create Project with Default Configuration. You will use the default configuration file: atmel_sama5d27_som1_ek_mmc_dev_defconfig.
Device Tree
Objective: Observe how the FLEXCOM1 peripheral was configured for I²C in the device tree. No changes are required.
Once Buildroot has completed its build, the I²C definitions for the ATSAMA5D27-SOM1-EK1 were configured by a device tree. The device tree source includes files (*.dtsi and *.dts) located in the Buildroot output directory: /output/build/linux-linux4sam_6.0/arch/arm/boot/dts/.
1
Examine the sama5d2.dtsi file and observe the FLEXCOM1 device assignments:
679 flx1_clk: flx1_clk {
680 #clock-cells = <0>;
681 reg = <20>;
682 atmel,clk-output-range = <0 83000000>;
683 };
.
.
1255 flx1: flexcom@f8038000 {
1256 compatible = "atmel,sama5d2-flexcom";
1257 reg = <0xf8038000 0x200>;
1258 clocks = <&flx1_clk>;
1259 #address-cells = <1>;
1260 #size-cells = <1>;
1261 ranges = <0x0 0xf8038000 0x800>;
1262 status = "disabled";
1263 };
Line 681 the PID for FLEXCOM1 is 20, this definition of the offset will be used to enable FLEXCOM1 clock in PMC.
Line 682 the FLEXCOM1 input clock, max frequency is 83 MHz.
Line 1256 specifies which driver will be used for the FLEXCOM peripheral.
Line 1257 the FLEXCOM1 base address is 0xf8038000, size is 0x200.
Line 1258 is the definition for the FLEXCOM1 clock source.
Line 1262 shows the default is “disabled” and will be changed to “okay” in line 195 of the at91-sama5d27_som1_ek.dts file below.
2
Examine the at91-sama5d27_som1_ek.dts file and observe the I²C device assignments:
56 aliases {
57 serial0 = &uart1; /* DBGU */
58 serial1 = &uart4; /* mikro BUS 1 */
59 serial2 = &uart2; /* mikro BUS 2 */
60 i2c1 = &i2c1;
61 i2c2 = &i2c2;
62 };
.
.
179 flx1: flexcom@f8038000 {
180 atmel,flexcom-mode = <ATMEL_FLEXCOM_MODE_TWI>;
181 status = "okay";
182
183 i2c2: i2c@600 {
184 compatible = "atmel,sama5d2-i2c";
185 reg = <0x600 0x200>;
186 interrupts = <20 IRQ_TYPE_LEVEL_HIGH 7>;
187 dmas = <0>, <0>;
188 dma-names = "tx", "rx";
189 #address-cells = <1>;
190 #size-cells = <0>;
191 clocks = <&flx1_clk>;
192 pinctrl-names = "default";
193 pinctrl-0 = <&pinctrl_mikrobus_i2c>;
194 atmel,fifo-size = <16>;
195 status = "okay";
196 };
197 };
.
.
522 pinctrl_mikrobus_i2c: mikrobus1_i2c {
523 pinmux = <PIN_PA24__FLEXCOM1_IO0>,
524 <PIN_PA23__FLEXCOM1_IO1>;
525 bias-disable;
526 };
Line 61, the aliases of FLEXCOM1 I²C is i2c2. FLEXCOM1 I²C will be registered as I²C adapter 2.
Line 180 specifies the I²C mode for the FLEXCOM1 peripheral.
Line 181 enables the device.
Line 184 specifies which driver will be used for this I²C device.
Line 185 is the register offset address for I²C in FLEXCOM1 is 0x600, size is 0x200.
Line 186 the PID for FLEXCOM4 is 23, high level triggered, priority is 7. It is used to configure FLEXCOM4 interrupt in AIC.
Line 187 shows that the DMA feature is not enabled.
Line 191 is the definition for the FLEXCOM1 clock source
Line 193 is the pin definition for I²C.
Line 194 specifies the size of the FIFO as 16.
Line 195 enables the device.
Line 523 is the mux of PA24 to be switched to FLEXCOM1_IO0.
Line 524 is the mux of PA23 to be switched to FLEXCOM1_IO1
Line 525 the pull up/down feature is disabled.
Kernel
Objective: Observe how the I²C functionality was configured in the Linux kernel. No changes are required.
Device Driver
4
Observe <*> I²C device interface is selected.
This will select the driver for the I²C_CHARDEV device.
6
Observe <*> Atmel AT91 I²C Two-Wire interface (TWI) is selected.
This supports the use of the I²C interface on Microchip (formerly Atmel) AT91 processors.
Rootfs
There are two methods to access I²C bus driver:
Kernel Space:
Register your own I²C driver via i2c_add_driver() interface, then access I²C bus driver via struct i2c_client handle.
User Space:
As discussed in the "Device Tree" section above, the FLEXCOM1 peripheral is registered as I²C adapter 2. Enabling the I²C_CHARDEV kernel feature (default) you can access the I²C bus driver via /dev/i2c-2 device node.
I²C_CHARDEV is a good choice because all codes are running in user space (it’s easier for developing).
Application
The following is a C-Language demonstration program (i2c_dev.c) for reading temperature from the THERMO 5 click board via the /dev/i2c-2 node.
To compile:
$ buildroot/output/host/bin/arm-buildroot-linux-uclibcgnueabihf-gcc i2c_dev.c -o i2c_test
Be sure to type in the location of the cross-compiler on your host computer.
Source code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#define DEV_I²C "/dev/i2c-2"
#define SLAVE_ADDR 0x4C /* EMC1414 I2C slave address */
//#define COMBINED_TRANSCTION
int main(int argc, char *argv[])
{
int fd;
int ret;
unsigned char buf[2];
// open device node
fd = open(DEV_I2C, O_RDWR);
if (fd < 0) {
printf("ERROR open %s ret=%d\n", DEV_I2C, fd);
return -1;
}
if (ioctl(fd, I2C_SLAVE, SLAVE_ADDR) < 0) {
printf("ERROR ioctl() set slave address\n");
return -1;
}
#ifdef COMBINED_TRANSCTION
struct i2c_rdwr_ioctl_data data;
struct i2c_msg messages[2];
// Set conversion rate
buf[0] = 0x04; // Conversion rate register address
buf[1] = 0x04; // Set conversion rate to 1 second
messages[0].addr = SLAVE_ADDR; //device address
messages[0].flags = 0; //write
messages[0].len = 2;
messages[0].buf = buf; //data address
data.msgs = &messages[0];
data.nmsgs = 1;
if (ioctl(fd, I2C_RDWR, &data) < 0) {
printf("ERROR ioctl() conversion rate\n");
return -1;
}
// Read temperature
buf[0] = 0x00; // Internal Diode High Byte register address
buf[1] = 0; // clear receive buffer
messages[0].addr = SLAVE_ADDR; //device address
messages[0].flags = 0; //write
messages[0].len = 1;
messages[0].buf = &buf[0]; //data address
messages[1].addr = SLAVE_ADDR; //device address
messages[1].flags = I2C_M_RD; //read
messages[1].len = 1;
messages[1].buf = &buf[1];
data.msgs = messages;
data.nmsgs = 2;
while (1) {
if (ioctl(fd, I2C_RDWR, &data) < 0) {
printf("ERROR ioctl() read data\n");
return -1;
}
printf("Temperature is %d\n", buf[1]);
sleep(1);
}
#else
// Set conversion rate
buf[0] = 0x04; // Conversion rate register address
buf[1] = 0x04; // Set conversion rate to 1 second
ret = write(fd, buf, 2);
if (ret != 2) {
printf("ERROR write() conversion rate\n");
return -1;
}
// Read temperature
// Set internal address register pointer
buf[0] = 0x00; // Internal Diode High Byte register address
ret = write(fd, &buf[0], 1);
if (ret != 1) {
printf("ERROR write() register address\n");
return -1;
}
while (1) {
// Read temperature
// Read data
buf[1] = 0; // clear receive buffer
ret = read(fd, &buf[1], 1);
if (ret != 1) {
printf("ERROR read() data\n");
return -1;
}
printf("Temperature is %d\n", buf[1]);
sleep(1);
}
#endif
// close device node
close(fd);
return 0;
}
Hands-On
Copy the i2c_test program to the target and execute. The temperature data will be printed out.
# chmod +x i2c_test
# ./i2c_test
Temperature is 26
Temperature is 26
Temperature is 26
Temperature is 26
Temperature is 26
Temperature is 26
Tools and Utilities
I2C Tools is a tool for I2C bus testing. It is included in the default configuration of Buildroot. You can view the selection by performing the following:
1
From the Buildroot directory, start menuconfig:
$ make menuconfig
2
Select Target packages ——>
3
Select Hardware handling ——>
i2c-tools Commands
There are several commands in i2c-tools:
# i2cdetect
Error: No i2c-bus specified!
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
i2cdetect -F I2CBUS
i2cdetect -l
I2CBUS is an integer or an I2C bus name
If provided, FIRST and LAST limit the probing range.
# i2cdetect -y 3
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: UU 51 52 53 54 55 56 57 -- -- -- -- -- -- -- --
60: 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
# i2cdump
Error: No i2c-bus specified!
Usage: i2cdump [-f] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (byte, default)
w (word)
W (word on even register addresses)
s (SMBus block)
i (I2C block)
c (consecutive byte)
Append p for SMBus PEC
# i2cdump -f -y 3 0x50
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
10: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
30: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
60: 20 4d 43 48 49 50 20 52 46 4f 12 02 42 42 31 ff MCHIP RFO??BB1.
70: 00 00 44 32 37 2d 53 4f 4d 31 2d 45 4b 31 44 f7 ..D27-SOM1-EK1D?
80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
# i2cget
Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (read byte data, default)
w (read word data)
c (write byte/read byte)
Append p for SMBus PEC
# i2cget -f -y 3 0x50 0x60
0x20
# i2cget -f -y 3 0x50 0x61
0x4d
WARNING: for the i2cset command, DO NOT modify any data in the EEPROM of the ATSAMA5D27-SOM1-EK1.
# i2cset
Usage: i2cset [-f] [-y] [-m MASK] [-r] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
c (byte, no value)
b (byte data, default)
w (word data)
i (I2C block data)
s (SMBus block data)
Append p for SMBus PEC
# i2ctransfer
Usage: i2ctransfer [-f] [-y] [-v] [-V] I2CBUS DESC [DATA] [DESC [DATA]]...
I2CBUS is an integer or an I2C bus name
DESC describes the transfer in the form: {r|w}LENGTH[@address]
1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omitted)
DATA are LENGTH bytes for a write message. They can be shortened by a suffix:
= (keep value constant until LENGTH)
+ (increase value by 1 until LENGTH)
- (decrease value by 1 until LENGTH)
p (use pseudo random generator until LENGTH with value as seed)
Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer 0 w1@0x50 0x64 r8
Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):
# i2ctransfer 0 w17@0x50 0x42 0xff-
Summary
You used Buildroot to build an image with I²C bus support for the ATSAMA5D2 Series MPU. You accessed the I²C bus via two different methods: kernel space using i2c_add_driver() interface and user space by the /dev/i2c-X device. You walked through the device tree and kernel to observe how the embedded Linux system configures the source code for building.