Linux I2C application programming
Equipment file
/dev/i2c-x
x is the I2C bus number, that is, a group of SCL and SDA. Multiple I2C devices can be connected to one bus. During communication, they are distinguished by device address and are not reflected in the system device file.
For raspberry pie, when I2C function is enabled, there is a bus, that is, a device:
/dev/i2c-1
i2c-0 is occupied by eeprom and not exported
Raspberry pie I2C related PIN definition reference https://pinout.xyz/pinout/i2c#
User space device operation ioctl
Related header file
#include <linux/i2c-dev.h> #include <i2c/smbus.h>
I2C settings:
- Address bits
- Device address
- clock frequency
function | cmd | arg | Parameter description |
---|---|---|---|
Sets the number of I2C addressing retries | I2C_RETRIES | ulong | |
Set timeout | I2C_TIMEOUT | ulong | The unit is 10ms |
Set I2C device address | I2C_SLAVE | ulong | The low bit is the device address |
Force an address | I2C_SLAVE_FORCE | ulong | The low bit is the device address |
Specifies to use a 10 bit address | I2C_TENBIT | ulong | |
Query main equipment function | I2C_FUNCS | ulong* | |
Read and write data | I2C_RDWR | i2c_rdwr_ioctl_data[] | Send stop signal only once |
PEC is used in SMBUS mode | I2C_PEC | ulong | |
Use SMBUS to read and write data | I2C_SMBUS | i2c_smbus_ioctl_data[] |
- When in use, if the device address is 10 bits, set it to the 10 bit address mode first, and then set the device address;
- The method of setting the clock frequency is not seen. It should be set in the driver and cannot be modified by the application;
data transmission
I2C using ioctl_ Rdwr or I2C_SMBUS command
Using read/write
read/wirte can transmit no more than 8k bytes at a time
I2C_ The maximum length of rdwr single message shall not exceed 8k bytes
Application examples
Take the raspberry pie operation PN532 NFC module as an example:
PN532 adopts PN532 NFC HAT module with light snow and is connected in HAT mode.
Communication parameters of PN532:
be careful:
- The address 0x48 in the document uses the upper 7 bits, while the address passed in the program is usually the lower 7 bits of the parameter, so the address we use in time is 0x24;
- The adopted MSB bit sequence, raspberry I2C hardware and driver are also MSB bit sequence, without bit sequence conversion;
- The maximum clock frequency of 400kHz is supported by referring to the hardware manual. It can be supported after testing.
The following procedure reads the UID of NFC tag:
Using libnfc
// To compile this simple example: // $ gcc -o quick_start_example1 quick_start_example1.c -lnfc #include <stdlib.h> #include <nfc/nfc.h> static void print_hex(const uint8_t *pbtData, const size_t szBytes) { size_t szPos; for (szPos = 0; szPos < szBytes; szPos++) { printf("%02x ", pbtData[szPos]); } printf("\n"); } int main(int argc, const char *argv[]) { nfc_device *pnd; nfc_target nt; // Allocate only a pointer to nfc_context nfc_context *context; // Initialize libnfc and set the nfc_context nfc_init(&context); if (context == NULL) { printf("Unable to init libnfc (malloc)\n"); exit(EXIT_FAILURE); } // Display libnfc version const char *acLibnfcVersion = nfc_version(); (void)argc; printf("%s uses libnfc %s\n", argv[0], acLibnfcVersion); // Open, using the first available NFC device which can be in order of selection: // - default device specified using environment variable or // - first specified device in libnfc.conf (/etc/nfc) or // - first specified device in device-configuration directory (/etc/nfc/devices.d) or // - first auto-detected (if feature is not disabled in libnfc.conf) device pnd = nfc_open(context, "pn532_i2c:/dev/i2c-1"); if (pnd == NULL) { printf("ERROR: %s\n", "Unable to open NFC device."); exit(EXIT_FAILURE); } // Set opened NFC device to initiator mode if (nfc_initiator_init(pnd) < 0) { nfc_perror(pnd, "nfc_initiator_init"); exit(EXIT_FAILURE); } printf("NFC reader: %s opened\n", nfc_device_get_name(pnd)); // Poll for a ISO14443A (MIFARE) tag const nfc_modulation nmMifare = { .nmt = NMT_ISO14443A, .nbr = NBR_106, }; if (nfc_initiator_select_passive_target(pnd, nmMifare, NULL, 0, &nt) > 0) { printf("The following (NFC) ISO14443A tag was found:\n"); printf(" ATQA (SENS_RES): "); print_hex(nt.nti.nai.abtAtqa, 2); printf(" UID (NFCID%c): ", (nt.nti.nai.abtUid[0] == 0x08 ? '3' : '1')); print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen); printf(" SAK (SEL_RES): "); print_hex(&nt.nti.nai.btSak, 1); if (nt.nti.nai.szAtsLen) { printf(" ATS (ATR): "); print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen); } } // Close NFC device nfc_close(pnd); // Release the context nfc_exit(context); exit(EXIT_SUCCESS); }
Use ioctl/read/write
pn532.h
#pragma once #include <memory> namespace pn532 { const uint8_t TFI_H2P = 0xD4; const uint8_t TFI_P2H = 0xD5; #pragma pack(1) struct ni_frame_header { uint8_t preamble; uint8_t start_code[2]; uint8_t len; uint8_t lcs; uint8_t tfi; }; struct frame_tailer { uint8_t dcs; uint8_t postamble; }; #pragma pack() const uint8_t ACK_FRAME[6] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00}; const uint8_t NACK_FRAME[6] = {0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00}; const uint8_t ERROR_FRAME[8] = {0x00, 0x00, 0xFF, 0x01, 0xFF, 0x7F, 0x81, 0x00}; typedef std::shared_ptr<uint8_t[]> byte_ptr; typedef byte_ptr data_ptr; typedef byte_ptr frame_ptr; uint8_t checksum(const uint8_t * data, int data_len); uint8_t checksum(data_ptr data, int data_len); bool validate_checksum(const uint8_t * data, int data_len); bool validate_checksum(data_ptr data, int data_len); int cal_frame_len(int data_len); frame_ptr new_frame(int frame_len); int make_frame(uint8_t* buf, int buf_len, uint8_t tfi, const uint8_t* data, int data_len); int make_frame(frame_ptr& frame, uint8_t tfi, const uint8_t* data, int data_len); int make_frame(frame_ptr& frame, uint8_t tfi, data_ptr data, int data_len); }
pn532.cpp
#include "pn532.h" #include <cassert> namespace pn532 { uint8_t checksum(const uint8_t * data, int data_len) { assert(data_len >= 0); uint8_t cs = 0; const uint8_t* p = data; for (int i = 0; i < data_len; ++i) { cs += *p++; } cs = (~cs) + 1; return cs; } uint8_t checksum(data_ptr data, int data_len) { return checksum(data.get(), data_len); } bool validate_checksum(const uint8_t* data, int data_len) { assert(data_len >= 0); uint8_t cs = 0; const uint8_t* p = data; for (int i = 0; i < data_len; ++i) { cs += *p++; } return (cs == 0); } bool validate_checksum(data_ptr data, int data_len) { return validate_checksum(data.get(), data_len); } int cal_frame_len(int data_len) { assert(data_len >= 0); return sizeof(ni_frame_header) + data_len + sizeof(frame_tailer); } frame_ptr new_frame(int frame_len) { assert(frame_len >= 0); frame_ptr frame(new uint8_t[frame_len]); return frame; } int make_frame(uint8_t* buf, int buf_len, uint8_t tfi, const uint8_t* data, int data_len) { assert(data_len >= 0); int frame_len = cal_frame_len(data_len); if (buf == NULL || buf_len == 0) { return frame_len; } if (buf_len < frame_len) { return -1; } uint8_t* f = buf; ni_frame_header* h = (ni_frame_header*)f; uint8_t* d = f + sizeof(ni_frame_header); frame_tailer* t = (frame_tailer*)(d + data_len); h->preamble = 0x00; h->start_code[0] = 0x00; h->start_code[1] = 0xFF; h->len = data_len + 1; h->lcs = checksum(&h->len, 1); h->tfi = tfi; uint8_t dcs = tfi; const uint8_t* dd = data; for (int i = 0; i < data_len; ++i) { *d++ = *dd; dcs += *dd++; } dcs = (~dcs) + 1; t->dcs = dcs; t->postamble = 0x00; return frame_len; } int make_frame(frame_ptr & frame, uint8_t tfi, data_ptr data, int data_len) { assert(data_len >= 0); int frame_len = cal_frame_len(data_len); frame = new_frame(frame_len); return make_frame(frame.get(), frame_len, tfi, data.get(), data_len); } }
get_uid_i2c.cpp
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/i2c-dev.h> #include <string.h> #include "pn532.h" using namespace pn532; #define IOCTL(fd, cmd, arg) \ do\ {\ if (ioctl(fd, cmd, arg) == -1)\ {\ perror(#cmd);\ close(fd);\ return -1;\ }\ }\ while(0) int init_i2c(const char * dev) { static const ulong addr = 0x24; int fd = open(dev, O_RDWR); if (fd == -1) { perror("open device:"); return -1; } IOCTL(fd, I2C_SLAVE, addr); return fd; } int send_i2c(int fd, const uint8_t* sendbuf, int sendlen) { usleep(5000); return write(fd, sendbuf, sendlen); } int recv_i2c(int fd, uint8_t* recvbuf, int recvlen) { usleep(5000); return read(fd, recvbuf, recvlen); } void print_hex(const uint8_t * buf, int len) { const uint8_t * p = buf; for (int i = 0; i < len; ++i) { printf("%02X ", *p++); if ((i+1) % 16 == 0) { printf("\n"); } } if (len % 16 != 0) { printf("\n"); } } int wait_for_ready(int fd) { uint8_t buf[1]; while (1) { usleep(10000); if (recv_i2c(fd, buf, 1) != 1) { printf("recv status error!\n"); return -1; } if (buf[0] == 0x01) return 0; } return -1; } #define BUF_SIZE 264 int do_cmd(int fd, const uint8_t* cmd, int cmdlen, const char* cmdname, int answerlen) { if (cmdname != NULL) { printf("do command: %s\n", cmdname); } if (answerlen < 0) { answerlen = BUF_SIZE; } uint8_t buf[BUF_SIZE]; frame_ptr cmd_frame; int cmd_frame_len = make_frame(cmd_frame, TFI_H2P, cmd, cmdlen); print_hex(cmd_frame.get(), cmd_frame_len); if (send_i2c(fd, cmd_frame.get(), cmd_frame_len) != cmd_frame_len) { perror("write cmd"); return -1; } if (wait_for_ready(fd) != 0) { return -1; } printf("ready for ack!\n"); if (recv_i2c(fd, buf, 6) < 0) { perror("read ack error"); return -1; } printf("read ack: "); print_hex(buf, 6); if (wait_for_ready(fd) != 0) { return -1; } printf("ready for answer!\n"); int alen = 0; if ((alen = recv_i2c(fd, buf, answerlen)) < 0) { perror("read answer error"); return -1; } printf("read answer: "); print_hex(buf, alen); return 0; } int main(int argc, char *argv[]) { int fd = init_i2c("/dev/i2c-1"); if (fd < 0) { fprintf(stderr, "init device error!\n"); return -1; } #if 0 uint8_t gfv_data[] = {0x02}; if (do_cmd(fd, gfv_data, sizeof(gfv_data), "get fireware version", 13) < 0) { return -1; } #endif #if 1 uint8_t set_normal_mode_data[] = {0x14, 0x01, 0x00, 0x00}; if (do_cmd(fd, set_normal_mode_data, sizeof(set_normal_mode_data), "set normal mode", 9) < 0) { return -1; } #endif #if 1 uint8_t autopoll_data[] = {0x60, 0xff, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x10, 0x11, 0x12, 0x20, 0x23}; if (do_cmd(fd, autopoll_data, sizeof(autopoll_data), "auto poll", 32) < 0) { return -1; } #endif #if 0 uint8_t lpt_data[] = {0x4A, 0x01, 0x00}; if (do_cmd(fd, lpt_data, sizeof(lpt_data), "list passive target", 32) < 0) { return -1; } #endif close(fd); return 0; }
reference resources
https://www.waveshare.net/wiki/PN532_NFC_HAT
https://pinout.xyz/pinout/i2c#
https://github.com/nfc-tools/libnfc