Linux I2C 应用编程
设备文件
/dev/i2c-x
x是I2C总线号,即一组SCL和SDA,一条总线上可以挂接多个I2C设备,通信时以设备地址区分,不体现在系统设备文件上。
对于树莓派,启用I2C功能后,有一条总线,即一个设备:
/dev/i2c-1
i2c-0被eeprom占用未导出
树莓派I2C相关PIN定义参考https://pinout.xyz/pinout/i2c#
用户空间设备操作ioctl
相关头文件
#include <linux/i2c-dev.h>
I2C设置:
- 地址位数
- 设备地址
- 时钟频率
功能 | cmd | arg | 参数说明 |
---|---|---|---|
设置I2C寻址重试次数 | I2C_RETRIES | ulong | |
设置超时时间 | I2C_TIMEOUT | ulong | 单位是10ms |
设置I2C设备地址 | I2C_SLAVE | ulong | 低位为设备地址 |
强制使用一个地址 | I2C_SLAVE_FORCE | ulong | 低位为设备地址 |
指定使用10位地址 | I2C_TENBIT | ulong | |
查询主设备功能 | I2C_FUNCS | ulong* | |
读写数据 | I2C_RDWR | i2c_rdwr_ioctl_data[] | 仅发送一次停止信号 |
SMBUS方式下使用PEC | I2C_PEC | ulong | |
使用SMBUS方式读写数据 | I2C_SMBUS | i2c_smbus_ioctl_data[] |
- 使用时如果设备地址为10位,先设置为10位地址模式,再设置设备地址;
- 没有看到设置时钟频率的方法,应该是在驱动中设置,应用不可修改;
数据传输
使用ioctl的I2C_RDWR或者I2C_SMBUS指令
使用read/write
read/wirte一次最多传输不超过8k bytes
I2C_RDWR单消息最大长度不超过8k bytes
应用举例
以树莓派操作PN532 NFC模块为例:
PN532采用微雪的PN532 NFC HAT模块,使用HAT方式连接。
PN532的通信参数:
注意:
- 文档里的地址0x48用的是高7位,而程序中传递的地址通常是参数的低7位,所以我们时间使用的地址是0x24;
- 采用的MSB的位序,树莓派I2C硬件和驱动也是MSB位序,无需做位序转换;
- 最大400kHz的时钟频率,参考硬件手册看是否支持,经过测试可以支持。
PN532 I2C交互流程
注意:
- 每次读都会读到Status,就是说,在读到Satus为1后,再去读ACK或Response的时候还会再次读到Status,需要在结果中移除。
以下程序读取NFC tag的UID:
使用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);
}
使用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+1];
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, 7) < 0)
{
perror("read ack error");
return -1;
}
printf("read ack: ");
print_hex(buf+1, 6);
if (wait_for_ready(fd) != 0)
{
return -1;
}
printf("ready for answer!\n");
int alen = 0;
if ((alen = recv_i2c(fd, buf, answerlen+1)) < 0)
{
perror("read answer error");
return -1;
}
printf("read answer: ");
print_hex(buf+1, alen-1);
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;
}
参考
https://www.waveshare.net/wiki/PN532_NFC_HAT
https://pinout.xyz/pinout/i2c#
https://github.com/nfc-tools/libnfc