diff --git a/libi2c/LICENSE b/libi2c/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ddd1fc0be0c71c8be33b2aa2257a50e4890d089d --- /dev/null +++ b/libi2c/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Amaork + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/libi2c/Makefile b/libi2c/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f0d043b3b0b7403f194802ffa999f133dd5e56b6 --- /dev/null +++ b/libi2c/Makefile @@ -0,0 +1,53 @@ +PYTHON = python +CC = $(CROSS)gcc +AR = $(CROSS)ar +VERSION=$(shell head -n 1 VERSION) +INCDIR = include +CFLAGS = -Wall -I$(INCDIR) -Wextra -g -fPIC -DLIBI2C_VERSION="$(VERSION)" +LDSHFLAGS = -rdynamic -shared +ARFLAGS = rcv +CODE_STYLE = astyle --align-pointer=name --align-reference=name --suffix=none --break-blocks --pad-oper --pad-header --break-blocks --keep-one-line-blocks --indent-switches --indent=spaces + +SOURCES=$(filter-out src/pyi2c.c, $(wildcard src/*.c)) +HEADERS=$(wildcard $(INCDIR)/i2c/*.h) +OBJECTS=$(SOURCES:.c=.o) +TARGETS = libi2c.a libi2c.so pylibi2c.so + +.PHONY:all clean example test install help style +.SILENT: clean + +all:$(TARGETS) example + +clean: + make -C example clean + find . -name "*.o" | xargs rm -f + $(RM) *.o *.so *~ a.out depend $(TARGETS) build -rf + +help: + $(PYTHON) help.py + +test: + $(PYTHON) -m unittest discover tests + +style: + @find -regex '.*/.*\.\(c\|cpp\|h\)$$' | xargs $(CODE_STYLE) + +install: + $(PYTHON) setup.py install + +example:$(TARGETS) + make -C $@ + +libi2c.a:$(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +libi2c.so:$(OBJECTS) + $(CC) $(LDSHFLAGS) -o $@ $^ + +pylibi2c.so:$(OBJECTS) + $(PYTHON) setup.py build_ext --inplace + +depend:$(SOURCES) $(HEADERS) src/pyi2c.c + $(CC) $(CFLAGS) -MM $^ > $@ + +-include depend diff --git a/libi2c/README.md b/libi2c/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d9752d5b051138ea83460cdf37051e45eae57094 --- /dev/null +++ b/libi2c/README.md @@ -0,0 +1,159 @@ +libi2c +======= + +Linux userspace i2c library. + + +## Features + +- Support C/C++ and Python. + +- Support Python2+, Python3+ + +- Support multiple bus and devices. + +- Support 7-bit and 10-bit i2c slave address. + +- Support 1 - 4 byte internal address, auto convert. + +- Provide read/write/ioctl functions to operate i2c device. + +- Support 8/16/32/64/128/256 bytes page aligned write, read/write length are unlimited. + +- Using ioctl functions operate i2c can ignore i2c device ack signal and internal address. + + +## Installation + + pip install pylibi2c + + or + + sudo python setup.py install + + or + + sudo make install + + or + + sudo make install PYTHON=pythonX.X + +## Interface + + i2c_ioctl_write (once max 16 bytes) are more efficient than i2c_write (once max 4 bytes). + + /* Close i2c bus */ + void i2c_close(int bus); + + /* Open i2c bus, return i2c bus fd */ + int i2c_open(const char *bus_name); + + /* I2C file I/O read, write */ + ssize_t i2c_read(const I2CDevice *device, unsigned int iaddr, void *buf, size_t len); + ssize_t i2c_write(const I2CDevice *device, unsigned int iaddr, const void *buf, size_t len); + + /* I2c ioctl read, write can set i2c flags */ + ssize_t i2c_ioctl_read(const I2CDevice *device, unsigned int iaddr, void *buf, size_t len); + ssize_t i2c_ioctl_write(const I2CDevice *device, unsigned int iaddr, const void *buf, size_t len); + +## Data structure + +**C/C++** + + typedef struct i2c_device { + int bus; /* I2C Bus fd, return from i2c_open */ + unsigned short addr; /* I2C device(slave) address */ + unsigned char tenbit; /* I2C is 10 bit device address */ + unsigned char delay; /* I2C internal operate delay, unit millisecond */ + unsigned short flags; /* I2C i2c_ioctl_read/write flags */ + unsigned int page_bytes; /* I2C max number of bytes per page, 1K/2K 8, 4K/8K/16K 16, 32K/64K 32 etc */ + unsigned int iaddr_bytes; /* I2C device internal(word) address bytes, such as: 24C04 1 byte, 24C64 2 bytes */ + }I2CDevice; + +**Python** + + I2CDevice object + I2CDevice(bus, addr, tenbit=False, iaddr_bytes=1, page_bytes=8, delay=1, flags=0) + tenbit, delay, flags, page_bytes, iaddr_bytes are attributes can setter/getter after init + + required args: bus, addr. + optional args: tenbit(defult False, 7-bit), delay(defualt 1ms), flags(defualt 0), iaddr_bytes(defualt 1 byte internal address), page_bytes(default 8 bytes per page). + + +## C/C++ Usage + +**1. First call `i2c_open` open i2c bus.** + + int bus; + + /* Open i2c bus /dev/i2c-0 */ + if ((bus = i2c_open("/dev/i2c-0")) == -1) { + + /* Error process */ + } + +**2. Second fill `I2CDevice` struct, prepare read or write.** + + I2CDevice device; + memset(&device, 0, sizeof(device)); + + /* 24C04 */ + device.bus = bus; /* Bus 0 */ + device.addr = 0x50; /* Slave address is 0x50, 7-bit */ + device.iaddr_bytes = 1; /* Device internal address is 1 byte */ + device.page_bytes = 16; /* Device are capable of 16 bytes per page */ + +**3. Call `i2c_read/write` or `i2c_ioctl_read/write` read or write i2c device.** + + unsigned char buffer[256]; + ssize_t size = sizeof(buffer); + memset(buffer, 0, sizeof(buffer)); + + /* From i2c 0x0 address read 256 bytes data to buffer */ + if ((i2c_read(&device, 0x0, buffer, size)) != size) { + + /* Error process */ + } + +**4. Close i2c bus `i2c_close(bus)`.** + + i2c_close(bus); + +## Python Usage + + import ctypes + import pylibi2c + + # Open i2c device @/dev/i2c-0, addr 0x50. + i2c = pylibi2c.I2CDevice('/dev/i2c-0', 0x50) + + # Open i2c device @/dev/i2c-0, addr 0x50, 16bits internal address + i2c = pylibi2c.I2CDevice('/dev/i2c-0', 0x50, iaddr_bytes=2) + + # Set delay + i2c.delay = 10 + + # Set page_bytes + i2c.page_bytes = 16 + + # Set flags + i2c.flags = pylibi2c.I2C_M_IGNORE_NAK + + # Python2 + buf = bytes(bytearray(256)) + + # Python3 + buf = bytes(256) + + # Write data to i2c, buf must be read-only type + size = i2c.write(0x0, buf) + + # From i2c 0x0(internal address) read 256 bytes data, using ioctl_read. + data = i2c.ioctl_read(0x0, 256) + +## Notice + +1. If i2c device do not have internal address, please use `i2c_ioctl_read/write` function for read/write, set`'iaddr_bytes=0`. + +2. If want ignore i2c device nak signal, please use `i2c_ioctl_read/write` function, set I2CDevice.falgs as `I2C_M_IGNORE_NAK`. diff --git a/libi2c/VERSION b/libi2c/VERSION new file mode 100644 index 0000000000000000000000000000000000000000..7dff5b8921122a487162febe3c8e32effb7acb35 --- /dev/null +++ b/libi2c/VERSION @@ -0,0 +1 @@ +0.2.1 \ No newline at end of file diff --git a/libi2c/example/Makefile b/libi2c/example/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d835bc346a533e13e16258dff42a90741f18d012 --- /dev/null +++ b/libi2c/example/Makefile @@ -0,0 +1,33 @@ +CC = $(CROSS)gcc +AR = $(CROSS)ar +CFLAGS = -Wall -g +LDSHFLAGS = -rdynamic -shared +ARFLAGS = rcv +CFLAGS += -I../include +LDFLAGS = -L.. -li2c -Wl,-R -Wl,.. + +OBJDIR=../objs +SOURCES = $(wildcard *.c) $(wildcard *.cpp) +TARGETS = $(foreach src, $(SOURCES), $(basename $(src))) + +.PHONY:all clean objdir +.SILENT:clean + +all:objdir $(TARGETS) + +objdir: + @mkdir -p $(OBJDIR) + +clean: + $(RM) *.o a.out depend $(TARGETS) $(OBJDIR) -rf + +i2c_tools: i2c_tools.o + $(CC) $(CFLAGS) -o $(OBJDIR)/$@ $^ $(LDFLAGS) + +i2c_without_internal_address: i2c_without_internal_address.o + $(CC) $(CFLAGS) -o $(OBJDIR)/$@ $^ $(LDFLAGS) + +depend:$(wildcard *.h *.c) + $(CC) $(CFLAGS) -MM $^ > $@ + +-include depend diff --git a/libi2c/example/i2c_tools.c b/libi2c/example/i2c_tools.c new file mode 100644 index 0000000000000000000000000000000000000000..e67bd5aab665f6f33f7db8eda0f39d64481756c6 --- /dev/null +++ b/libi2c/example/i2c_tools.c @@ -0,0 +1,171 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include "i2c/i2c.h" + +void print_i2c_data(const unsigned char *data, size_t len) +{ + size_t i = 0; + + for (i = 0; i < len; i++) { + + if (i % 16 == 0) { + + fprintf(stdout, "\n"); + } + + fprintf(stdout, "%02x ", data[i]); + } + + fprintf(stdout, "\n"); +} + + +int main(int argc, char **argv) +{ + char i2c_dev_desc[128]; + I2C_READ_HANDLE i2c_read_handle = i2c_read; + I2C_WRITE_HANDLE i2c_write_handle = i2c_write; + unsigned int addr = 0, iaddr_bytes = 0, page_bytes = 0, bus_num = -1; + + if (argc < 5) { + + fprintf(stdout, "Usage:%s <bus_num> <dev_addr> <iaddr_bytes> <page_bytes> [ioctl]\n" + "Such as:\n" + "\t24c02 i2c_test 1 0x50 1 8\n" + "\t24c04 i2c_test 1 0x50 1 16\n" + "\t24c64 i2c_test 1 0x50 2 32\n" + "\t24c64 i2c_test 1 0x50 2 ioctl\n", argv[0]); + exit(0); + } + + /* Get i2c bus number */ + if (sscanf(argv[1], "%u", &bus_num) != 1) { + + fprintf(stderr, "Can't parse i2c 'bus_num' [%s]\n", argv[1]); + exit(-1); + } + + /* Get i2c device address */ + if (sscanf(argv[2], "0x%x", &addr) != 1) { + + fprintf(stderr, "Can't parse i2c 'dev_addr' [%s]\n", argv[2]); + exit(-1); + } + + /* Get i2c internal address bytes */ + if (sscanf(argv[3], "%u", &iaddr_bytes) != 1) { + + fprintf(stderr, "Can't parse i2c 'iaddr_bytes' [%s]\n", argv[3]); + exit(-2); + } + + /* Get i2c page bytes number */ + if (sscanf(argv[4], "%u", &page_bytes) != 1) { + + fprintf(stderr, "Can't parse i2c 'page_bytes' [%s]\n", argv[4]); + exit(-2); + } + + + /* If specify ioctl using ioctl r/w i2c */ + if (argc == 6 && (memcmp(argv[5], "ioctl", strlen("ioctl")) == 0)) { + + i2c_read_handle = i2c_ioctl_read; + i2c_write_handle = i2c_ioctl_write; + fprintf(stdout, "Using i2c_ioctl_oper r/w data\n"); + } + else { + + fprintf(stdout, "Using i2c_oper r/w data\n"); + } + + /* Open i2c bus */ + int bus; + char bus_name[32]; + memset(bus_name, 0, sizeof(bus_name)); + + if (snprintf(bus_name, sizeof(bus_name), "/dev/i2c-%u", bus_num) < 0) { + + fprintf(stderr, "Format i2c bus name error!\n"); + exit(-3); + } + + if ((bus = i2c_open(bus_name)) == -1) { + + fprintf(stderr, "Open i2c bus:%s error!\n", bus_name); + exit(-3); + } + + /* Init i2c device */ + I2CDevice device; + memset(&device, 0, sizeof(device)); + i2c_init_device(&device); + + device.bus = bus; + device.addr = addr & 0x3ff; + device.page_bytes = page_bytes; + device.iaddr_bytes = iaddr_bytes; + + /* Print i2c device description */ + fprintf(stdout, "%s\n", i2c_get_device_desc(&device, i2c_dev_desc, sizeof(i2c_dev_desc))); + + size_t i = 0; + ssize_t ret = 0; + unsigned char buf[256]; + size_t buf_size = sizeof(buf); + memset(buf, 0, buf_size); + + /* I/O r/w 0x00 - 0xff */ + if (i2c_read_handle == i2c_read) { + + for (i = 0; i < buf_size; i++) { + + buf[i] = i; + } + } + /* ioctl r/w 0xff - 0x0 */ + else { + + for (i = 0; i < buf_size; i++) { + + buf[i] = 0xff - i; + } + } + + /* Print before write */ + fprintf(stdout, "Write data:\n"); + print_i2c_data(buf, buf_size); + + ret = i2c_write_handle(&device, 0x0, buf, buf_size); + if (ret != -1 || (size_t)ret != buf_size) + { + + fprintf(stderr, "Write i2c error!\n"); + exit(-4); + } + + fprintf(stdout, "\nWrite success, prepare read....\n"); + + /* Read */ + usleep(100000); + memset(buf, 0, buf_size); + + ret = i2c_read_handle(&device, 0x0, buf, buf_size); + if (ret == -1 || (size_t)ret != buf_size) + { + + fprintf(stderr, "Read i2c error!\n"); + exit(-5); + } + + /* Print read result */ + fprintf(stdout, "Read data:\n"); + print_i2c_data(buf, buf_size); + + i2c_close(bus); + return 0; +} + + diff --git a/libi2c/example/i2c_without_internal_address.c b/libi2c/example/i2c_without_internal_address.c new file mode 100644 index 0000000000000000000000000000000000000000..bd4306e0a6d2c1fae4d41f26e6d5fd09e412f477 --- /dev/null +++ b/libi2c/example/i2c_without_internal_address.c @@ -0,0 +1,40 @@ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include "i2c/i2c.h" + +int main() +{ + int fd; + I2CDevice device; + const char *data = "9876543"; + ssize_t ret; + + /* First open i2c bus */ + if ((fd = i2c_open("/dev/i2c-0")) == -1) { + + perror("Open i2c bus error"); + return -1; + } + + /* Fill i2c device struct */ + device.bus = fd; + device.addr = 0x12; + device.tenbit = 0; + device.delay = 10; + device.flags = 0; + device.page_bytes = 8; + device.iaddr_bytes = 0; /* Set this to zero, and using i2c_ioctl_xxxx API will ignore chip internal address */ + + /* Write data to i2c */ + ret = i2c_ioctl_write(&device, 0x0, data, strlen(data)); + if (ret == -1 || (size_t)ret != strlen(data)) + { + /* Error process */ + } + + i2c_close(fd); + return 0; + +} + diff --git a/libi2c/example/meson.build b/libi2c/example/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..c44338f0001d9a5077a3cb644a7a23c770df34e9 --- /dev/null +++ b/libi2c/example/meson.build @@ -0,0 +1,15 @@ +examples = [ + 'i2c_tools', + 'i2c_without_internal_address', +] + +foreach example: examples + exe = executable(example, example + '.c', + link_with: libi2c, + include_directories: i2c_incdir, + c_args: [ + cflags, + '-D_DEFAULT_SOURCE', + ], + ) +endforeach \ No newline at end of file diff --git a/libi2c/example/pylibi2c_tools.py b/libi2c/example/pylibi2c_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..1d2d9e388581dc0ef1b41aa449fb4799c3441824 --- /dev/null +++ b/libi2c/example/pylibi2c_tools.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +import argparse +import pylibi2c + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + + # Required args + parser.add_argument('-b', '--bus', help='i2c bus, such as /dev/i2c-1', type=str, required=True) + parser.add_argument('-d', '--device', help='i2c device address, such as 0x56', type=str, required=True) + + # Device option args + parser.add_argument('--iaddr', help='i2c internal address', type=str, default="0x0") + parser.add_argument('--delay', help='i2c r/w delay, unit is msec', type=int, default=1) + parser.add_argument('--iaddr_bytes', help='i2c internal address bytes', type=int, default=1) + parser.add_argument('--page_bytes', help='i2c per page max number of bytes', type=int, default=8) + + # Read/write options + parser.add_argument('--data', help='write data', type=str) + parser.add_argument('--size', help='read data size', type=int) + parser.add_argument('--ioctl', help='using ioctl r/w i2c', type=bool, default=False) + args = vars(parser.parse_args()) + + try: + + bus = args.get('bus') + device = int(args.get('device'), 16) + + delay = args.get('delay') + iaddr = int(args.get('iaddr'), 16) + page_bytes = args.get('page_bytes') + iaddr_bytes = args.get('iaddr_bytes') + + data = args.get('data') + size = args.get('size') + ioctl = args.get('ioctl') + + if data is None and size is None: + raise RuntimeError("'data' or 'size' must specified one, 'data' for write, 'size' for read") + + # Create a i2c device + i2c = pylibi2c.I2CDevice(bus=bus, addr=device, page_bytes=page_bytes, iaddr_bytes=iaddr_bytes, delay=delay) + + if data: + write_handle = i2c.ioctl_write if ioctl else i2c.write + ret = write_handle(iaddr, bytes(data.encode("ascii"))) + print("Write: '{0:s}' to address: 0x{1:x}".format(data, iaddr)) + print("Result:{}".format(ret)) + else: + read_handle = i2c.ioctl_read if ioctl else i2c.read + data = read_handle(iaddr, size) + print("Read: {0:d} bytes data from address: 0x{1:x}".format(size, iaddr)) + print("Result:'{}'".format(data.decode("ascii"))) + except (TypeError, IOError, ValueError, RuntimeError) as err: + print("I2C R/W error:{}".format(err)) + diff --git a/libi2c/help.py b/libi2c/help.py new file mode 100644 index 0000000000000000000000000000000000000000..596a03e5b6d055676ef0afcf90b1cce588ef86b8 --- /dev/null +++ b/libi2c/help.py @@ -0,0 +1,3 @@ +import pylibi2c +help(pylibi2c) +help(pylibi2c.I2CDevice) diff --git a/libi2c/include/i2c/i2c.h b/libi2c/include/i2c/i2c.h new file mode 100644 index 0000000000000000000000000000000000000000..b57f35eee65ec49f9a854dad3bf4640f7d03f78a --- /dev/null +++ b/libi2c/include/i2c/i2c.h @@ -0,0 +1,58 @@ +#ifndef _LIB_I2C_H_ +#define _LIB_I2C_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +/* I2c device */ +typedef struct i2c_device { + int bus; /* I2C Bus fd, return from i2c_open */ + unsigned short addr; /* I2C device(slave) address */ + unsigned char tenbit; /* I2C is 10 bit device address */ + unsigned char delay; /* I2C internal operation delay, unit millisecond */ + unsigned short flags; /* I2C i2c_ioctl_read/write flags */ + unsigned int page_bytes; /* I2C max number of bytes per page, 1K/2K 8, 4K/8K/16K 16, 32K/64K 32 etc */ + unsigned int iaddr_bytes; /* I2C device internal(word) address bytes, such as: 24C04 1 byte, 24C64 2 bytes */ +} I2CDevice; + +/* Close i2c bus */ +void i2c_close(int bus); + +/* Open i2c bus, return i2c bus fd */ +int i2c_open(const char *bus_name); + +/* Initialize I2CDevice with default value */ +void i2c_init_device(I2CDevice *device); + +/* Get i2c device description */ +char *i2c_get_device_desc(const I2CDevice *device, char *buf, size_t size); + +/* Select i2c device on i2c bus */ +int i2c_select(int bus, unsigned long dev_addr, unsigned long tenbit); + +/* I2C internal(word) address convert */ +void i2c_iaddr_convert(unsigned int int_addr, unsigned int iaddr_bytes, unsigned char *addr); + +/* I2C file I/O read, write */ +ssize_t i2c_read(const I2CDevice *device, unsigned int iaddr, void *buf, size_t len); +ssize_t i2c_write(const I2CDevice *device, unsigned int iaddr, const void *buf, size_t len); + +/* I2c ioctl read, write can set i2c flags */ +ssize_t i2c_ioctl_read(const I2CDevice *device, unsigned int iaddr, void *buf, size_t len); +ssize_t i2c_ioctl_write(const I2CDevice *device, unsigned int iaddr, const void *buf, size_t len); + +/* I2C read / write handle function */ +typedef ssize_t (*I2C_READ_HANDLE)(const I2CDevice *dev, unsigned int iaddr, void *buf, size_t len); +typedef ssize_t (*I2C_WRITE_HANDLE)(const I2CDevice *dev, unsigned int iaddr, const void *buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/libi2c/include/meson.build b/libi2c/include/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..d258ebd8def61947a63ff32f62da1c456bb7c71c --- /dev/null +++ b/libi2c/include/meson.build @@ -0,0 +1,7 @@ +i2c_incdir = include_directories('.') + +# public headers +install_headers(['i2c/i2c.h'], + # i2c.h can clash with linux/i2c.h, so we put it in a subdir + subdir: meson.project_name(), +) \ No newline at end of file diff --git a/libi2c/libi2c.wrap b/libi2c/libi2c.wrap new file mode 100644 index 0000000000000000000000000000000000000000..b611c4f44bc67b2cb0e96da5511061de23dd3cb1 --- /dev/null +++ b/libi2c/libi2c.wrap @@ -0,0 +1,6 @@ +[wrap-git] +url = https://github.com/mdegans/libi2c.git +revision = master + +[provide] +i2c=i2c_dep \ No newline at end of file diff --git a/libi2c/meson.build b/libi2c/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..c6979f15298884f5ec8f722f807f5b322fad703e --- /dev/null +++ b/libi2c/meson.build @@ -0,0 +1,44 @@ +project('i2c', ['c'], + version: run_command( + 'head', '-n', '1', files('VERSION'), + ).stdout().strip(), + meson_version: '>= 0.56.0', + license: 'MIT', + default_options: [ + 'c_std=c11', + 'warning_level=2', # -Wall, -Wextra + 'werror=true', # -Werror + ], +) + +# the c++ compiler object +cc = meson.get_compiler('c') + +# global cflags (public, included in subprojects and pkg-config) +cflags = ['-DLIBI2C_VERSION="' + meson.project_version() + '"'] + +# per compiler cflags, if desired +if cc.get_id() == 'clang' + cflags += [] +else + cflags += [] +endif + +# split the project version +proj_ver = meson.project_version().split('.') +# version minus patch revision +proj_ver_short = proj_ver[0] + '.' + proj_ver[1] +proj_ver_major = proj_ver[0] +proj_ver_minor = proj_ver[1] +proj_ver_patch = proj_ver[2] + +# all of this is used by configure_file on i2c_config.h.in +proj_description = 'Linux userspace i2c operation library' +proj_url = 'https://github.com/amaork/libi2c' +binary_package = 'libi2c' +origin = 'github' + +subdir('include') +subdir('src') +subdir('example') +subdir('tests') \ No newline at end of file diff --git a/libi2c/pyproject.toml b/libi2c/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..46e825ca2f5dc6024d3b272549e8d671216a3d82 --- /dev/null +++ b/libi2c/pyproject.toml @@ -0,0 +1,34 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "pylibi2c" +authors = [ + { name="amaork", email="amaork@gmail.com" }, +] + +description = "Linux userspace i2c operation library" +keywords = ["i2c", "raspberry-pi"] + +readme = "README.md" +requires-python = ">=2.7" +license = { file = "LICENSE" } + +dynamic = ["version"] + +classifiers = [ + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX :: Linux", + "Programming Language :: C", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", +] + +[tool.setuptools.dynamic] +version = { file = "VERSION" } + +[project.urls] +"Homepage" = "https://github.com/amaork/libi2c" +"Bug Tracker" = "https://github.com/amaork/libi2c/issues" \ No newline at end of file diff --git a/libi2c/setup.py b/libi2c/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..ab61535db3b141f624e669b3151f33a1fe29ae0a --- /dev/null +++ b/libi2c/setup.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +""" +setup.py file for pylibi2c +""" + +import os +from distutils.core import setup, Extension + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +INC_DIR = os.path.join(THIS_DIR, 'include') +VERSION = open('VERSION').read().strip() + +pylibi2c_module = Extension('pylibi2c', + sources=['src/i2c.c', 'src/pyi2c.c'], + extra_compile_args=['-DLIBI2C_VERSION="' + VERSION + '"'], + include_dirs=[INC_DIR], +) + +setup( + name='pylibi2c', + version=VERSION, + license='MIT', + author='Amaork', + author_email="amaork@gmail.com", + url='https://github.com/amaork/libi2c', + description="Linux userspace i2c operation library", + long_description=open('README.md').read(), + ext_modules=[pylibi2c_module], + py_modules=["pylibi2c"], + classifiers=[ + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + ], +) + + diff --git a/libi2c/src/i2c.c b/libi2c/src/i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..c04efd8ebd907be6f47be5c2668e1196134f15e1 --- /dev/null +++ b/libi2c/src/i2c.c @@ -0,0 +1,371 @@ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include "i2c/i2c.h" + +/* I2C default delay */ +#define I2C_DEFAULT_DELAY 1 + +/* I2C internal address max length */ +#define INT_ADDR_MAX_BYTES 4 + +/* I2C page max bytes */ +#define PAGE_MAX_BYTES 4096 + +#define GET_I2C_DELAY(delay) ((delay) == 0 ? I2C_DEFAULT_DELAY : (delay)) +#define GET_I2C_FLAGS(tenbit, flags) ((tenbit) ? ((flags) | I2C_M_TEN) : (flags)) +#define GET_WRITE_SIZE(addr, remain, page_bytes) ((addr) + (remain) > (page_bytes) ? (page_bytes) - (addr) : remain) + +static void i2c_delay(unsigned char delay); + +/* +** @brief : Open i2c bus +** #bus_name : i2c bus name such as: /dev/i2c-1 +** @return : failed return -1, success return i2c bus fd +*/ +int i2c_open(const char *bus_name) +{ + int fd; + + /* Open i2c-bus devcice */ + if ((fd = open(bus_name, O_RDWR)) == -1) { + + return -1; + } + + return fd; +} + + +void i2c_close(int bus) +{ + close(bus); +} + + +/* +** @brief : Initialize I2CDevice with defualt value +** #device : I2CDevice struct +*/ +void i2c_init_device(I2CDevice *device) +{ + /* 7 bit device address */ + device->tenbit = 0; + + /* 1ms delay */ + device->delay = 1; + + /* 8 bytes per page */ + device->page_bytes = 8; + + /* 1 byte internal(word) address */ + device->iaddr_bytes = 1; +} + + +/* +** @brief : Get I2CDevice struct desc +** #device : I2CDevice struct +** #buf : Description message buffer +** #size : #buf size +** @return : return i2c device desc +*/ +char *i2c_get_device_desc(const I2CDevice *device, char *buf, size_t size) +{ + memset(buf, 0, size); + snprintf(buf, size, "Device address: 0x%x, tenbit: %s, internal(word) address: %d bytes, page max %d bytes, delay: %dms", + device->addr, device->tenbit ? "True" : "False", device->iaddr_bytes, device->page_bytes, device->delay); + + return buf; +} + + +/* +** i2c_ioctl_read/write +** I2C bus top layer interface to operation different i2c devide +** This function will call XXX:ioctl system call and will be related +** i2c-dev.c i2cdev_ioctl to operation i2c device. +** 1. it can choice ignore or not ignore i2c bus ack signal (flags set I2C_M_IGNORE_NAK) +** 2. it can choice ignore or not ignore i2c internal address +** +*/ +ssize_t i2c_ioctl_read(const I2CDevice *device, unsigned int iaddr, void *buf, size_t len) +{ + struct i2c_msg ioctl_msg[2]; + struct i2c_rdwr_ioctl_data ioctl_data; + unsigned char addr[INT_ADDR_MAX_BYTES]; + unsigned short flags = GET_I2C_FLAGS(device->tenbit, device->flags); + + memset(addr, 0, sizeof(addr)); + memset(ioctl_msg, 0, sizeof(ioctl_msg)); + memset(&ioctl_data, 0, sizeof(ioctl_data)); + + /* Target have internal address */ + if (device->iaddr_bytes) { + + i2c_iaddr_convert(iaddr, device->iaddr_bytes, addr); + + /* First message is write internal address */ + ioctl_msg[0].len = device->iaddr_bytes; + ioctl_msg[0].addr = device->addr; + ioctl_msg[0].buf = addr; + ioctl_msg[0].flags = flags; + + /* Second message is read data */ + ioctl_msg[1].len = len; + ioctl_msg[1].addr = device->addr; + ioctl_msg[1].buf = buf; + ioctl_msg[1].flags = flags | I2C_M_RD; + + /* Package to i2c message to operation i2c device */ + ioctl_data.nmsgs = 2; + ioctl_data.msgs = ioctl_msg; + } + /* Target did not have internal address */ + else { + + /* Direct send read data message */ + ioctl_msg[0].len = len; + ioctl_msg[0].addr = device->addr; + ioctl_msg[0].buf = buf; + ioctl_msg[0].flags = flags | I2C_M_RD; + + /* Package to i2c message to operation i2c device */ + ioctl_data.nmsgs = 1; + ioctl_data.msgs = ioctl_msg; + } + + /* Using ioctl interface operation i2c device */ + if (ioctl(device->bus, I2C_RDWR, (unsigned long)&ioctl_data) == -1) { + +// perror("Ioctl read i2c error:"); + return -1; + } + + return len; +} + + +ssize_t i2c_ioctl_write(const I2CDevice *device, unsigned int iaddr, const void *buf, size_t len) +{ + ssize_t remain = len; + size_t size = 0, cnt = 0; + const unsigned char *buffer = buf; + unsigned char delay = GET_I2C_DELAY(device->delay); + unsigned short flags = GET_I2C_FLAGS(device->tenbit, device->flags); + + struct i2c_msg ioctl_msg; + struct i2c_rdwr_ioctl_data ioctl_data; + unsigned char tmp_buf[PAGE_MAX_BYTES + INT_ADDR_MAX_BYTES]; + + while (remain > 0) { + + size = GET_WRITE_SIZE(iaddr % device->page_bytes, remain, device->page_bytes); + + /* Convert i2c internal address */ + memset(tmp_buf, 0, sizeof(tmp_buf)); + i2c_iaddr_convert(iaddr, device->iaddr_bytes, tmp_buf); + + /* Connect write data after device internal address */ + memcpy(tmp_buf + device->iaddr_bytes, buffer, size); + + /* Fill kernel ioctl i2c_msg */ + memset(&ioctl_msg, 0, sizeof(ioctl_msg)); + memset(&ioctl_data, 0, sizeof(ioctl_data)); + + ioctl_msg.len = device->iaddr_bytes + size; + ioctl_msg.addr = device->addr; + ioctl_msg.buf = tmp_buf; + ioctl_msg.flags = flags; + + ioctl_data.nmsgs = 1; + ioctl_data.msgs = &ioctl_msg; + + if (ioctl(device->bus, I2C_RDWR, (unsigned long)&ioctl_data) == -1) { + +// perror("Ioctl write i2c error:"); + return -1; + } + + /* XXX: Must have a little time delay */ + i2c_delay(delay); + + cnt += size; + iaddr += size; + buffer += size; + remain -= size; + } + + return cnt; +} + + +/* +** @brief : read #len bytes data from #device #iaddr to #buf +** #device : I2CDevice struct, must call i2c_device_init first +** #iaddr : i2c_device internal address will read data from this address, no address set zero +** #buf : i2c data will read to here +** #len : how many data to read, lenght must less than or equal to buf size +** @return : success return read data length, failed -1 +*/ +ssize_t i2c_read(const I2CDevice *device, unsigned int iaddr, void *buf, size_t len) +{ + ssize_t cnt; + unsigned char addr[INT_ADDR_MAX_BYTES]; + unsigned char delay = GET_I2C_DELAY(device->delay); + + /* Set i2c slave address */ + if (i2c_select(device->bus, device->addr, device->tenbit) == -1) { + + return -1; + } + + /* Convert i2c internal address */ + memset(addr, 0, sizeof(addr)); + i2c_iaddr_convert(iaddr, device->iaddr_bytes, addr); + + /* Write internal address to devide */ + if (write(device->bus, addr, device->iaddr_bytes) != device->iaddr_bytes) { + + perror("Write i2c internal address error"); + return -1; + } + + /* Wait a while */ + i2c_delay(delay); + + /* Read count bytes data from int_addr specify address */ + if ((cnt = read(device->bus, buf, len)) == -1) { + + perror("Read i2c data error"); + return -1; + } + + return cnt; +} + + +/* +** @brief : write #buf data to i2c #device #iaddr address +** #device : I2CDevice struct, must call i2c_device_init first +** #iaddr : i2c_device internal address, no address set zero +** #buf : data will write to i2c device +** #len : buf data length without '/0' +** @return : success return write data length, failed -1 +*/ +ssize_t i2c_write(const I2CDevice *device, unsigned int iaddr, const void *buf, size_t len) +{ + ssize_t remain = len; + ssize_t ret; + size_t cnt = 0, size = 0; + const unsigned char *buffer = buf; + unsigned char delay = GET_I2C_DELAY(device->delay); + unsigned char tmp_buf[PAGE_MAX_BYTES + INT_ADDR_MAX_BYTES]; + + /* Set i2c slave address */ + if (i2c_select(device->bus, device->addr, device->tenbit) == -1) { + + return -1; + } + + /* Once only can write less than 4 byte */ + while (remain > 0) { + + size = GET_WRITE_SIZE(iaddr % device->page_bytes, remain, device->page_bytes); + + /* Convert i2c internal address */ + memset(tmp_buf, 0, sizeof(tmp_buf)); + i2c_iaddr_convert(iaddr, device->iaddr_bytes, tmp_buf); + + /* Copy data to tmp_buf */ + memcpy(tmp_buf + device->iaddr_bytes, buffer, size); + + /* Write to buf content to i2c device length is address length and + write buffer length */ + ret = write(device->bus, tmp_buf, device->iaddr_bytes + size); + if (ret == -1 || (size_t)ret != device->iaddr_bytes + size) + { + perror("I2C write error:"); + return -1; + } + + /* XXX: Must have a little time delay */ + i2c_delay(delay); + + /* Move to next #size bytes */ + cnt += size; + iaddr += size; + buffer += size; + remain -= size; + } + + return cnt; +} + + +/* +** @brief : i2c internal address convert +** #iaddr : i2c device internal address +** #len : i2c device internal address length +** #addr : save convert address +*/ +void i2c_iaddr_convert(unsigned int iaddr, unsigned int len, unsigned char *addr) +{ + union { + unsigned int iaddr; + unsigned char caddr[INT_ADDR_MAX_BYTES]; + } convert; + + /* I2C internal address order is big-endian, same with network order */ + convert.iaddr = htonl(iaddr); + + /* Copy address to addr buffer */ + int i = len - 1; + int j = INT_ADDR_MAX_BYTES - 1; + + while (i >= 0 && j >= 0) { + + addr[i--] = convert.caddr[j--]; + } +} + + +/* +** @brief : Select i2c address @i2c bus +** #bus : i2c bus fd +** #dev_addr : i2c device address +** #tenbit : i2c device address is tenbit +** #return : success return 0, failed return -1 +*/ +int i2c_select(int bus, unsigned long dev_addr, unsigned long tenbit) +{ + /* Set i2c device address bit */ + if (ioctl(bus, I2C_TENBIT, tenbit)) { + + perror("Set I2C_TENBIT failed"); + return -1; + } + + /* Set i2c device as slave ans set it address */ + if (ioctl(bus, I2C_SLAVE, dev_addr)) { + + perror("Set i2c device address failed"); + return -1; + } + + return 0; +} + +/* +** @brief : i2c delay +** #msec : milliscond to be delay +*/ +static void i2c_delay(unsigned char msec) +{ + usleep(msec * 1e3); +} + diff --git a/libi2c/src/meson.build b/libi2c/src/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..85b242834e9124e9dea688c7989967b76a3b4205 --- /dev/null +++ b/libi2c/src/meson.build @@ -0,0 +1,54 @@ +# source for core library +i2c_src = [ + 'i2c.c', +] + +# shared and/or static library +libi2c = library(meson.project_name(), i2c_src, + c_args: [ + cflags, + '-D_DEFAULT_SOURCE', + ], + include_directories: i2c_incdir, + install: true, +) + +# dependency for use in subprojects (only) +i2c_dep = declare_dependency( + compile_args: cflags, + include_directories: i2c_incdir, + link_with: libi2c, + version: meson.project_version(), +) + +# pkg-config support +pkg = import('pkgconfig') +pkg.generate(libi2c, + extra_cflags: cflags, +) + +# python module build follows +pymod = import('python') +python_versions = ['python2', 'python3'] +python_testme = [] # python execs and built libraries go in here for testing +python_src = ['pyi2c.c'] +python_path = meson.current_build_dir() # used for tests + +foreach python_version : python_versions + python = pymod.find_installation(python_version, required: false) + if python.found() + # we still need the python-dev package + python_dep = dependency(python_version) + message(python_version + ' module enabled') + # same arguments as `shared_module`, more or less + # https://mesonbuild.com/Python-module.html + pylib = python.extension_module('pylibi2c', python_src, + dependencies: python_dep, + include_directories: i2c_incdir, + link_with: libi2c, + install: true, + c_args: cflags, + ) + python_testme += {'python': python, 'lib': pylib} + endif +endforeach \ No newline at end of file diff --git a/libi2c/src/pyi2c.c b/libi2c/src/pyi2c.c new file mode 100644 index 0000000000000000000000000000000000000000..2ad71f036e76877b652f773e9f278291d306a78a --- /dev/null +++ b/libi2c/src/pyi2c.c @@ -0,0 +1,557 @@ +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include "i2c/i2c.h" + +#define _VERSION_ LIBI2C_VERSION +#define _NAME_ "pylibi2c" +#define _I2CDEV_MAX_SIZE_ 4096 +#define _I2CDEV_MAX_IADDR_BYTES_SIZE 4 +#define _I2CDEV_MAX_PAGE_BYTES_SIZE 1024 +PyDoc_STRVAR(I2CDevice_name, "I2CDevice"); +PyDoc_STRVAR(pylibi2c_doc, "Linux userspace i2c library.\n"); + + +/* Macros needed for Python 3 */ +#ifndef PyInt_Check +#define PyInt_Check PyLong_Check +#define PyInt_FromLong PyLong_FromLong +#define PyInt_AsLong PyLong_AsLong +#define PyInt_Type PyLong_Type +#endif + + +PyDoc_STRVAR(I2CDeviceObject_type_doc, "I2CDevice(bus, address, tenbit=False, iaddr_bytes=1, page_bytes=8, delay=1, flags=0) -> I2CDevice object.\n"); +typedef struct { + PyObject_HEAD; + I2CDevice dev; +} I2CDeviceObject; + + +static PyObject *I2CDevice_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + (void)args; + (void)kwds; + + I2CDeviceObject *self; + + if ((self = (I2CDeviceObject *)type->tp_alloc(type, 0)) == NULL) { + + return NULL; + } + + memset(&self->dev, 0, sizeof(self->dev)); + i2c_init_device(&self->dev); + + Py_INCREF(self); + return (PyObject *)self; +} + + +PyDoc_STRVAR(I2CDevice_close_doc, "close()\n\nClose i2c device.\n"); +static PyObject *I2CDevice_close(I2CDeviceObject *self) { + + /* Close i2c bus */ + if (self->dev.bus >= 0) { + + i2c_close(self->dev.bus); + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static void I2CDevice_free(I2CDeviceObject *self) { + + PyObject *ref = I2CDevice_close(self); + Py_XDECREF(ref); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +/* I2CDevice(bus, addr, tenbit=0, iaddr_bytes=1, page_bytes=8, delay=1, flags=0) */ +static int I2CDevice_init(I2CDeviceObject *self, PyObject *args, PyObject *kwds) { + + char *bus_name = NULL; + static char *kwlist[] = {"bus", "addr", "tenbit", "iaddr_bytes", "page_bytes", "delay", "flags", NULL}; + + /* Bus name and device address is required */ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sH|BBHBH:__init__", kwlist, + &bus_name, &self->dev.addr, + &self->dev.tenbit, &self->dev.iaddr_bytes, &self->dev.page_bytes, &self->dev.delay, &self->dev.flags)) { + + return -1; + } + + /* Open i2c bus */ + if ((self->dev.bus = i2c_open(bus_name)) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return -1; + } + + return 0; +} + + +static PyObject *I2CDevice_enter(PyObject *self, PyObject *args) { + + if (!PyArg_ParseTuple(args, "")) + return NULL; + + Py_INCREF(self); + return self; +} + + +static PyObject *I2CDevice_exit(I2CDeviceObject *self, PyObject *args) { + + PyObject *exc_type = 0; + PyObject *exc_value = 0; + PyObject *traceback = 0; + + if (!PyArg_UnpackTuple(args, "__exit__", 3, 3, &exc_type, &exc_value, &traceback)) { + + return 0; + } + + /* Close i2c bus */ + I2CDevice_close(self); + Py_RETURN_FALSE; +} + + +/* str */ +static PyObject *I2CDevice_str(PyObject *object) { + + char desc[128]; + PyObject *dev_desc = NULL; + I2CDeviceObject *self = (I2CDeviceObject *)object; + i2c_get_device_desc(&self->dev, desc, sizeof(desc)); + +#if PY_MAJOR_VERSION >= 3 + dev_desc = PyUnicode_FromString(desc); +#else + dev_desc = PyString_FromString(desc); +#endif + + Py_INCREF(dev_desc); + return dev_desc; +} + + +/* i2c read device */ +static PyObject *i2c_read_device(I2CDeviceObject *self, PyObject *args, int ioctl) { + + int result; + unsigned int len = 0; + unsigned int iaddr = 0; + PyObject *bytearray = NULL; + char buf[_I2CDEV_MAX_SIZE_]; + memset(buf, 0, sizeof(buf)); + I2C_READ_HANDLE read_handle = NULL; + + if (!PyArg_ParseTuple(args, "II:read", &iaddr, &len)) { + + return NULL; + } + + len = len > sizeof(buf) ? sizeof(buf) : len; + read_handle = ioctl ? i2c_ioctl_read : i2c_read; + result = read_handle(&self->dev, iaddr, buf, len); + + if (result < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + if ((uint)result != len) + { + perror("short read"); + return NULL; + } + + /* Copy data to bytearray and return */ + bytearray = PyByteArray_FromStringAndSize(buf, len); + Py_INCREF(bytearray); + return bytearray; +} + + +/* i2c write device */ +static PyObject *i2c_write_device(I2CDeviceObject *self, PyObject *args, int ioctl) { + + char *buf = NULL; + Py_ssize_t size = 0; + unsigned int iaddr = 0; + PyObject *result = NULL; + I2C_WRITE_HANDLE write_handle = NULL; + + if (!PyArg_ParseTuple(args, "Is#::write", &iaddr, &buf, &size)) { + return NULL; + } + + write_handle = ioctl ? i2c_ioctl_write : i2c_write; + result = Py_BuildValue("i", write_handle(&self->dev, iaddr, buf, size)); + + Py_INCREF(result); + return result; +} + + +/* file read */ +PyDoc_STRVAR(I2CDevice_read_doc, "read(iaddr, buf, size)\n\nRead #size bytes data from device #iaddress to #buf.\n"); +static PyObject *I2CDevice_read(I2CDeviceObject *self, PyObject *args) { + + return i2c_read_device(self, args, 0); +} + + +/* file write */ +PyDoc_STRVAR(I2CDevice_write_doc, "write(iaddr, buf, size)\n\nWrite #size bytes data from #buf to device #iaddress.\n"); +static PyObject *I2CDevice_write(I2CDeviceObject *self, PyObject *args) { + + return i2c_write_device(self, args, 0); +} + + +/* ioctl read */ +PyDoc_STRVAR(I2CDevice_ioctl_read_doc, "ioctl_read(iaddr, buf, size)\n\nIoctl read #size bytes data from device #iaddress to #buf.\n"); +static PyObject *I2CDevice_ioctl_read(I2CDeviceObject *self, PyObject *args) { + + return i2c_read_device(self, args, 1); +} + + +/* ioctl write */ +PyDoc_STRVAR(I2CDevice_ioctl_write_doc, "ioctl_write(iaddr, buf, size)\n\nIoctl write #size bytes data from #buf to device #iaddress.\n"); +static PyObject *I2CDevice_ioctl_write(I2CDeviceObject *self, PyObject *args) { + + return i2c_write_device(self, args, 1); +} + + +/* pylibi2c module methods */ +static PyMethodDef I2CDevice_methods[] = { + + {"read", (PyCFunction)I2CDevice_read, METH_VARARGS, I2CDevice_read_doc}, + {"write", (PyCFunction)I2CDevice_write, METH_VARARGS, I2CDevice_write_doc}, + {"close", (PyCFunction)I2CDevice_close, METH_NOARGS, I2CDevice_close_doc}, + {"ioctl_read", (PyCFunction)I2CDevice_ioctl_read, METH_VARARGS, I2CDevice_ioctl_read_doc}, + {"ioctl_write", (PyCFunction)I2CDevice_ioctl_write, METH_VARARGS, I2CDevice_ioctl_write_doc}, + {"__enter__", (PyCFunction)I2CDevice_enter, METH_NOARGS, NULL}, + {"__exit__", (PyCFunction)I2CDevice_exit, METH_NOARGS, NULL}, + {NULL}, +}; + + +/* Check */ +static int check_user_input(const char *name, PyObject *input, int min, int max) { + + int value; + + if (input == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute"); + return -1; + } + + /* tenbit attribute, boolean type */ + if (memcmp(name, "tenbit", strlen(name)) == 0) { + if (!PyBool_Check(input)) { + PyErr_SetString(PyExc_TypeError, "The last attribute value must be boolean"); + return -1; + } + + return 0; + } + + /* Other attribute, integer type */ + if (!PyInt_Check(input)) { + PyErr_SetString(PyExc_TypeError, "The last attribute value must be an integer"); + return -1; + } + + /* Convert value to int */ + value = PyLong_AsLong(input); + + /* Check input value range */ + if (value < min || value > max) { + PyErr_Format(PyExc_ValueError, "invalid ' %s'(%d - %d)", name, min, max); + return -1; + } + + return 0; +} + +/* flags */ +PyDoc_STRVAR(I2CDevice_flags_doc, "ioctl_read/write i2c_msg flags\n\n" + "I_2C_M_NOSTART\n" + "In a combined transaction, no 'S Addr Wr/Rd [A]' is generated at some point." + "For example, setting I2C_M_NOSTART on the second partial message generates something like:\n\n" + "\t\t\tS Addr Rd [A] [Data] NA Data [A] P\n\n" + "If you set the I2C_M_NOSTART variable for the first partial message, we do not generate Addr," + "but we do generate the startbit S. This will probably confuse all other clients on your bus, so don't try this.\n\n" + "I2C_M_IGNORE_NAK\n" + "Normally message is interrupted immediately if there is [NA] from the" + "client. Setting this flag treats any [NA] as [A], and all of message is sent.These messages may still fail to SCL lo->hi timeout.\n\n" + "I2C_M_NO_RD_ACK\n" + "In a read message, master A/NA bit is skipped.\n\n"); +static PyObject *I2CDevice_get_flags(I2CDeviceObject *self, void *closure) { + (void)closure; + + PyObject *result = Py_BuildValue("H", self->dev.flags); + Py_INCREF(result); + return result; +} + +static int I2CDevice_set_flags(I2CDeviceObject *self, PyObject *value, void *closure) +{ + (void)closure; + + if (check_user_input("falgs", value, 0, I2C_M_NOSTART) != 0) { + + return -1; + } + + self->dev.flags = PyLong_AsLong(value); + return 0; +} + +/* delay */ +PyDoc_STRVAR(I2CDevice_delay_doc, "i2c internal operate delay, unit milliscond.\n\n"); +static PyObject *I2CDevice_get_delay(I2CDeviceObject *self, void *closure) { + (void)closure; + + PyObject *result = Py_BuildValue("b", self->dev.delay); + Py_INCREF(result); + return result; +} + +static int I2CDevice_set_delay(I2CDeviceObject *self, PyObject *value, void *closure) +{ + (void)closure; + + if (check_user_input("delay", value, 0, 100) != 0) { + + return -1; + } + + self->dev.delay = PyLong_AsLong(value); + return 0; +} + +/* tenbit */ +PyDoc_STRVAR(I2CDevice_tenbit_doc, "True, Enable 10 bit addressing.\n\nFalse, 7 bit addressing(default).\n"); +static PyObject *I2CDevice_get_tenbit(I2CDeviceObject *self, void *closure) { + (void)closure; + + PyObject *result = self->dev.tenbit ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +static int I2CDevice_set_tenbit(I2CDeviceObject *self, PyObject *value, void *closure) +{ + (void)closure; + + if (check_user_input("tenbit", value, 0, 1) != 0) { + + return -1; + } + + self->dev.tenbit = PyLong_AsLong(value); + return 0; +} + +/* iaddr_bytes */ +PyDoc_STRVAR(I2CDevice_page_bytes_doc, "i2c EEPROM max number of bytes per page (must be divisible by 8)\n\n" + "8, 8 bytes per page, such as 24C01/24C02\n\n" + "16, 16 bytes per page, such as 24C04/24C08/24C16\n\n" + "32, 32 bytes per page, such as 24C32/24C64\n\n"); +static PyObject *I2CDevice_get_page_bytes(I2CDeviceObject *self, void *closure) { + (void)closure; + + PyObject *result = Py_BuildValue("I", self->dev.page_bytes); + Py_INCREF(result); + return result; +} + +static int I2CDevice_set_page_bytes(I2CDeviceObject *self, PyObject *value, void *closure) +{ + (void)closure; + + int page_bytes = 0; + + if (check_user_input("page_bytes", value, 8, _I2CDEV_MAX_PAGE_BYTES_SIZE) != 0) { + + return -1; + } + + page_bytes = PyLong_AsLong(value); + + /* Divisible by 8 */ + if (page_bytes % 8) { + PyErr_SetString(PyExc_ValueError, "The 'page_bytes' must be divisible by 8"); + return -1; + } + + self->dev.page_bytes = page_bytes; + return 0; +} + +/* iaddr_bytes */ +PyDoc_STRVAR(I2CDevice_iaddr_bytes_doc, "I2C device internal(word) address bytes.\n\n" + "0, special device, without internal address\n\n" + "1, 1 byte internal address, such as 24C04\n\n" + "2, 2 byte internal address, such as 24C64\n\n" + "2, 3 byte internal address, such as 24C1024\n\n"); +static PyObject *I2CDevice_get_iaddr_bytes(I2CDeviceObject *self, void *closure) { + (void)closure; + + PyObject *result = Py_BuildValue("b", self->dev.iaddr_bytes); + Py_INCREF(result); + return result; +} + +static int I2CDevice_set_iaddr_bytes(I2CDeviceObject *self, PyObject *value, void *closure) +{ + (void)closure; + + if (check_user_input("iaddr_bytes", value, 0, _I2CDEV_MAX_IADDR_BYTES_SIZE) != 0) { + + return -1; + } + + self->dev.iaddr_bytes = PyLong_AsLong(value); + return 0; +} + +static PyGetSetDef I2CDevice_getseters[] = { + + {"flags", (getter)I2CDevice_get_flags, (setter)I2CDevice_set_flags, I2CDevice_flags_doc, NULL}, + {"delay", (getter)I2CDevice_get_delay, (setter)I2CDevice_set_delay, I2CDevice_delay_doc, NULL}, + {"tenbit", (getter)I2CDevice_get_tenbit, (setter)I2CDevice_set_tenbit, I2CDevice_tenbit_doc, NULL}, + {"page_bytes", (getter)I2CDevice_get_page_bytes, (setter)I2CDevice_set_page_bytes, I2CDevice_page_bytes_doc, NULL}, + {"iaddr_bytes", (getter)I2CDevice_get_iaddr_bytes, (setter)I2CDevice_set_iaddr_bytes, I2CDevice_iaddr_bytes_doc, NULL}, + {NULL}, +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + +static PyTypeObject I2CDeviceObjectType = { +#if PY_MAJOR_VERSION >= 3 + PyVarObject_HEAD_INIT(NULL, 0) +#else + PyObject_HEAD_INIT(NULL) 0, /* ob_size */ +#endif + I2CDevice_name, /* tp_name */ + sizeof(I2CDeviceObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)I2CDevice_free,/* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + I2CDevice_str, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + I2CDeviceObject_type_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + I2CDevice_methods, /* tp_methods */ + 0, /* tp_members */ + I2CDevice_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)I2CDevice_init, /* tp_init */ + 0, /* tp_alloc */ + I2CDevice_new, /* tp_new */ +}; + +#pragma GCC diagnostic pop + +static PyMethodDef pylibi2c_methods[] = { + {NULL} +}; + + +void define_constants(PyObject *module) { + + PyModule_AddObject(module, "I2C_M_NOSTART", Py_BuildValue("H", I2C_M_NOSTART)); + PyModule_AddObject(module, "I2C_M_NO_RD_ACK", Py_BuildValue("H", I2C_M_NO_RD_ACK)); + PyModule_AddObject(module, "I2C_M_IGNORE_NAK", Py_BuildValue("H", I2C_M_IGNORE_NAK)); +} + + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef pylibi2cmodule = { + PyModuleDef_HEAD_INIT, + _NAME_, /* Module name */ + pylibi2c_doc, /* Module pylibi2cMethods */ + -1, /* size of per-interpreter state of the module, size of per-interpreter state of the module,*/ + pylibi2c_methods, + NULL, + 0, + 0, + 0, +}; +#endif + + +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC PyInit_pylibi2c(void) +#else +PyMODINIT_FUNC initpylibi2c(void) +#endif +{ + + PyObject *module; + + if (PyType_Ready(&I2CDeviceObjectType) < 0) { +#if PY_MAJOR_VERSION >= 3 + return NULL; +#else + return; +#endif + } + +#if PY_MAJOR_VERSION >= 3 + module = PyModule_Create(&pylibi2cmodule); + PyObject *version = PyUnicode_FromString(_VERSION_); +#else + module = Py_InitModule3(_NAME_, pylibi2c_methods, pylibi2c_doc); + PyObject *version = PyString_FromString(_VERSION_); +#endif + + /* Constants */ + define_constants(module); + + /* Set module version */ + PyObject *dict = PyModule_GetDict(module); + PyDict_SetItemString(dict, "__version__", version); + Py_DECREF(version); + + /* Register I2CDeviceObject */ + Py_INCREF(&I2CDeviceObjectType); + PyModule_AddObject(module, I2CDevice_name, (PyObject *)&I2CDeviceObjectType); + +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} + diff --git a/libi2c/tests/meson.build b/libi2c/tests/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..3840021c4f7cd032c16072c2fa2817809367fce6 --- /dev/null +++ b/libi2c/tests/meson.build @@ -0,0 +1,14 @@ +foreach d : python_testme + python = d['python'] + testname = 'python ' + python.language_version() + ' module' + test(testname, python, # testname, executable + args: files('test_pylibi2c.py'), # full path to test.py + depends: d['lib'], # the library that must be built before + workdir: python_path, + protocol: 'exitcode', + env: [ + 'PYTHONPATH=' + python_path, + ], + is_parallel: false, + ) +endforeach \ No newline at end of file diff --git a/libi2c/tests/test_pylibi2c.py b/libi2c/tests/test_pylibi2c.py new file mode 100644 index 0000000000000000000000000000000000000000..8eb07e4607e16a43d46a9ff8b56e4c83ea2d6f2e --- /dev/null +++ b/libi2c/tests/test_pylibi2c.py @@ -0,0 +1,235 @@ +import random +import unittest +import pylibi2c + + +class Pylibi2cTest(unittest.TestCase): + def setUp(self): + self.i2c_size = 256 + # 24C04 E2PROM test + self.i2c = pylibi2c.I2CDevice(bus="/dev/i2c-1", addr=0x56, page_bytes=16) + + def test_init(self): + with self.assertRaises(TypeError): + pylibi2c.I2CDevice() + + with self.assertRaises(TypeError): + pylibi2c.I2CDevice(1, 2) + + with self.assertRaises(TypeError): + pylibi2c.I2CDevice("1", "2") + + with self.assertRaises(TypeError): + pylibi2c.I2CDevice("/dev/i2c-1") + + with self.assertRaises(IOError): + pylibi2c.I2CDevice("/dev/i2c-100", 0x56) + + def test_getattr(self): + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56) + + with self.assertRaises(AttributeError): + i2c.bus + + with self.assertRaises(AttributeError): + i2c.addr + + def test_setattr(self): + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56) + + with self.assertRaises(AttributeError): + i2c.bus = "" + + with self.assertRaises(AttributeError): + i2c.addr = "" + + def test_flags(self): + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56) + self.assertEqual(i2c.flags, 0) + + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56, flags=1) + self.assertEqual(i2c.flags, 1) + + with self.assertRaises(TypeError): + i2c.flags = "100" + + with self.assertRaises(TypeError): + i2c.flags = 0.1 + + with self.assertRaises(ValueError): + i2c.flags = -1 + + i2c.flags = 0 + self.assertEqual(i2c.flags, 0) + + i2c.flags = pylibi2c.I2C_M_NOSTART + self.assertEqual(i2c.flags, pylibi2c.I2C_M_NOSTART) + + i2c.flags = pylibi2c.I2C_M_IGNORE_NAK + self.assertEqual(i2c.flags, pylibi2c.I2C_M_IGNORE_NAK) + + def test_delay(self): + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56) + self.assertEqual(i2c.delay, 1) + + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56, delay=0) + self.assertEqual(i2c.delay, 0) + + with self.assertRaises(TypeError): + i2c.delay = "100" + + with self.assertRaises(TypeError): + i2c.delay = 0.1 + + with self.assertRaises(ValueError): + i2c.delay = -1 + + with self.assertRaises(ValueError): + i2c.delay = 101 + + i2c.delay = 10 + self.assertEqual(i2c.delay, 10) + + i2c.delay = 100 + self.assertEqual(i2c.delay, 100) + + def test_tenbit(self): + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56) + self.assertEqual(i2c.tenbit, False) + + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56, tenbit=1) + self.assertEqual(i2c.tenbit, True) + + with self.assertRaises(TypeError): + i2c.tenbit = 0 + + with self.assertRaises(TypeError): + i2c.tenbit = 100 + + with self.assertRaises(TypeError): + i2c.tenbit = "True" + + i2c.tenbit = False + self.assertEqual(i2c.tenbit, False) + + i2c.tenbit = True + self.assertEqual(i2c.tenbit, True) + + def test_page_bytes(self): + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56) + self.assertEqual(i2c.page_bytes, 8) + + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56, page_bytes=16) + self.assertEqual(i2c.page_bytes, 16) + + with self.assertRaises(TypeError): + i2c.page_bytes = "1" + + with self.assertRaises(ValueError): + i2c.page_bytes = -1 + + with self.assertRaises(ValueError): + i2c.page_bytes = 0 + + with self.assertRaises(ValueError): + i2c.page_bytes = 4 + + with self.assertRaises(ValueError): + i2c.page_bytes = 10 + + with self.assertRaises(ValueError): + i2c.page_bytes = 2048 + + i2c.page_bytes = 32 + self.assertEqual(i2c.page_bytes, 32) + + i2c.page_bytes = 64 + self.assertEqual(i2c.page_bytes, 64) + + def test_iaddr_bytes(self): + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56) + self.assertEqual(i2c.iaddr_bytes, 1) + + i2c = pylibi2c.I2CDevice("/dev/i2c-1", 0x56, iaddr_bytes=2) + self.assertEqual(i2c.iaddr_bytes, 2) + + with self.assertRaises(TypeError): + i2c.iaddr_bytes = "1" + + with self.assertRaises(ValueError): + i2c.iaddr_bytes = -1 + + with self.assertRaises(ValueError): + i2c.iaddr_bytes = 5 + + i2c.iaddr_bytes = 0 + self.assertEqual(i2c.iaddr_bytes, 0) + + i2c.iaddr_bytes = 1 + self.assertEqual(i2c.iaddr_bytes, 1) + + i2c.iaddr_bytes = 3 + self.assertEqual(i2c.iaddr_bytes, 3) + + def test_read(self): + self.assertEqual(len(self.i2c.read(0, self.i2c_size)), self.i2c_size) + self.assertEqual(len(self.i2c.read(0, 100)), 100) + self.assertEqual(len(self.i2c.read(13, 13)), 13) + self.assertEqual(len(self.i2c.read(13, 1)), 1) + + def test_write(self): + + # 0 - 0xff + w_buf = bytearray(range(self.i2c_size)) + self.assertEqual(self.i2c.write(0, bytes(w_buf)), self.i2c_size) + r_buf = self.i2c.read(0, self.i2c_size) + self.assertEqual(len(r_buf), self.i2c_size) + self.assertSequenceEqual(w_buf, r_buf) + + # Random data + w_buf = bytearray(self.i2c_size) + for i in range(self.i2c_size): + w_buf[i] = random.randint(0, 255) + self.assertEqual(self.i2c.write(0, bytes(w_buf)), self.i2c_size) + r_buf = self.i2c.read(0, self.i2c_size) + self.assertEqual(len(r_buf), self.i2c_size) + self.assertSequenceEqual(w_buf, r_buf) + + # Not aligned write + data = "a212131edada123qdadaeqeqdsadskfljfjfj" + for addr in range(1, 200, 2): + self.assertEqual(self.i2c.write(addr, data), len(data)) + self.assertEqual(self.i2c.read(addr, len(data)).decode("ascii"), data) + + def test_ioctl_read(self): + self.assertEqual(len(self.i2c.ioctl_read(0, self.i2c_size)), self.i2c_size) + self.assertEqual(len(self.i2c.ioctl_read(0, 100)), 100) + self.assertEqual(len(self.i2c.ioctl_read(13, 13)), 13) + self.assertEqual(len(self.i2c.ioctl_read(13, 1)), 1) + + def test_ioctl_ioctl_write(self): + # 0 - 0xff + w_buf = bytearray(range(self.i2c_size)) + self.assertEqual(self.i2c.ioctl_write(0, bytes(w_buf)), self.i2c_size) + r_buf = self.i2c.ioctl_read(0, self.i2c_size) + self.assertEqual(len(r_buf), self.i2c_size) + self.assertSequenceEqual(w_buf, r_buf) + + # Random data + w_buf = bytearray(self.i2c_size) + for i in range(self.i2c_size): + w_buf[i] = random.randint(0, 255) + self.assertEqual(self.i2c.ioctl_write(0, bytes(w_buf)), self.i2c_size) + r_buf = self.i2c.ioctl_read(0, self.i2c_size) + self.assertEqual(len(r_buf), self.i2c_size) + self.assertSequenceEqual(w_buf, r_buf) + + # Not aligned write + data = "a212131edada123qdadaeqeqdsadskfljfjfj" + for addr in range(1, 200, 2): + self.assertEqual(self.i2c.ioctl_write(addr, data), len(data)) + self.assertEqual(self.i2c.ioctl_read(addr, len(data)).decode("ascii"), data) + + +if __name__ == '__main__': + unittest.main()