SkylarkOS I2C 使用指南
优质
小牛编辑
133浏览
2023-12-01
I²C 控制器支持下列功能︰
- 支持主从模式
- 支持 7 位和 10 位寻址模式
- 中断或轮询驱动多个字节数据传输
- Three speeds: Standard mode (100 Kb/s), Fast mode (400 Kb/s), High-speed mode (3.4 Mb/s)
I2C使用说明
以8009为例,支持4路I2C接口,两路挂在CK域,两路挂在ARM域。CK域无法访问ARM域,ARM域的可以访问CK域的。
kernel下的设备树描述在:/kernel/arch/arm/boot/dtc/leo.dtsi
i2c0: i2c@02202000:arm域第一组
i2c1: i2c@02203000:arm域第二组
ck_i2c0: i2c@0205000:ck域第一组
ck_i2c1: i2c@0206000:ck域第二组
使用的时候,在Linux设备树下,新加的I2C设备根据硬件连接信息挂在适合的设备树下面
如何新加一个I2C设备
我们以一个apt的I2C设备为例子进行讲解
该设备的7位设备地址为:0x2d
设备挂在在ck_i2c1上,那么我们修改设备树如下:/kernel/arch/arm/boot/dts/leo_gx8009b_ssd_lp_v1_4.dts
加上:
+&ck_i2c1{
+ status = "okay";
+ mcu: apt32f101@2d{
+ compatible = "apt,apt32f101";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x2d>;
+ };
+};
+
然后配置管脚复用,配置为I2C的复用,参考:SkylarkOS GPIO使用指南
下面给出个linux驱动的例子
/**************************************************************************************************************************************
*
* File Name : apt32f101.c
* Date : 2018-09-25
* function : APT32F101 MCU driver.
*
**************************************************************************************************************************************/
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/types.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/input-polldev.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/freezer.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/pm_runtime.h>
#include <linux/atomic.h>
#include <linux/of_device.h>
#include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/dmi.h>
#include <linux/idr.h>
#include <linux/mutex.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/kdev_t.h>
#include <linux/devcoredump.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/kthread.h>
#include "apt32f101.h"
#define SYSTEM_STATUS_CHECK_TIME (3 * 100)
#define D_SUPPORT_KTHREAD
#define DEBUGE_LOG 0
#if DEBUGE_LOG
#define LOG_DBG printk
#else
#define LOG_DBG
#endif
#define D_SYSCMD_CODE_MOTOR 0x11
#define D_SYSCMD_CODE_EYELED 0x12
#define D_SYSCMD_CODE_EARLED 0x13
#define D_SYSCMD_CODE_STATUS 0x14
#define D_SYSSTATUS_GET_BAT (D_SYSCMD_CODE_STATUS << 8 | 0x01)
#define D_SYSSTATUS_GET_CHARGE (D_SYSCMD_CODE_STATUS << 8 | 0x02)
#define D_SYSSTATUS_DO_POWEROFF (D_SYSCMD_CODE_STATUS << 8 | 0x06)
#if 0
static const keymap_t keymap[] =
{
{KEYCODE_KEY1, 111},
{KEYCODE_KEY2, 112},
{KEYCODE_KEY3, 113},
{KEYCODE_KEY4, 254},
};
#else
static const keymap_t keymap[] =
{
{111, 111},
{112, 112},
{113, 113},
{117, 117},
{118, 118},
{254, 254},
};
#endif
struct apt32f101_priv *priv_p;
struct apt32f101_priv *priv;
struct apt32f101_priv {
int irq;
const struct apt32f101_chipdef *cdef;
struct i2c_client *client;
struct input_dev *input_dev;
#ifdef D_SUPPORT_KTHREAD
struct task_struct *pthread;
#endif
};
#if 0
struct apt32f101_chipdef {
u8 key_reg;
u8 battery_reg;
u8 motor_cmd_reg;
u8 sys_status_reg;
};
#else
struct apt32f101_chipdef {
u8 power_key_reg;
u8 battery_reg;
u8 function_key_reg;
u8 sys_status_reg;
};
#endif
static const struct apt32f101_chipdef apt32f101_cdef = {
.power_key_reg = 0x00,
.battery_reg = 0x01,
.function_key_reg = 0x02,
.sys_status_reg = 0x03,
};
struct timer_list systimer;
static int write_d8(void *client, u8 val)
{
return i2c_smbus_write_byte(client, val);
}
static int write_r8d8(void *client, u8 reg, u8 val)
{
return i2c_smbus_write_byte_data(client, reg, val);
}
static int write_r8d16(void *client, u8 reg, u16 val)
{
return i2c_smbus_write_word_data(client, reg, val);
}
static int read_d8(void *client)
{
return i2c_smbus_read_byte(client);
}
static int read_r8d8(void *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
static int read_r8d16(void *client, u8 reg)
{
return i2c_smbus_read_word_data(client, reg);
}
static int apt32f101_smbus_read(struct apt32f101_priv *priv, u8 reg)
{
int ret;
//LOG_DBG( KERN_INFO &priv->client->dev, "read register 0x%02X=", reg);
//LOG_DBG( KERN_INFO "read register 0x%02X=", reg);
ret = i2c_smbus_read_byte_data(priv->client, reg);
if (ret) {
dev_err(&priv->client->dev,
"register read to 0x%02X failed (error %d)",
reg, ret);
}
return ret;
}
static int apt32f101_smbus_write(struct apt32f101_priv *priv, u8 reg, u8 val)
{
int ret;
//LOG_DBG(KERN_INFO &priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
//LOG_DBG(KERN_INFO"writing register 0x%02X=0x%02X", reg, val);
ret = i2c_smbus_write_byte_data(priv->client, reg, val);
if (ret) {
dev_err(&priv->client->dev,
"register write to 0x%02X failed (error %d)",
reg, ret);
}
return ret;
}
static long apt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
u8 cmd_buf[8] = {0};
cmd_buf[0] = (char)((cmd >> 8) & 0xFF);
cmd_buf[1] = (char)(cmd & 0xFF);
switch(cmd_buf[0]) {
case D_SYSCMD_CODE_STATUS:
{
if (cmd == D_SYSSTATUS_GET_BAT) {
u16 bat = 0;
//bat = (read_r8d8(priv_p->client, SYSREG_BAT_ADC_H) << 8) & 0x0f00;
//bat |= read_r8d8(priv_p->client, SYSREG_BAT_ADC_L);
bat = (read_r8d8(priv_p->client, 0x01) << 8) &0xf00; //楂樹綅
bat |= read_r8d8(priv_p->client, 0x02);
LOG_DBG(KERN_INFO"apt_ioctl bat=%d\n", bat);
return bat;
}
if (cmd == D_SYSSTATUS_GET_CHARGE) {
return read_r8d8(priv_p->client, SYSREG_ACIN);
}
if (cmd == D_SYSSTATUS_DO_POWEROFF) {
write_r8d8(priv_p->client, 0x00, 0x02);
}
}
break;
default:
LOG_DBG("APTF32101 IOCTL UNKNOW CMD\n");
break;
}
return 0;
}
int apt_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
int ret = 0;
return ret;
}
int apt_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
{
int ret = 0;
return ret;
}
static int apt_open(struct inode *inode, struct file *file)
{
int ret;
LOG_DBG(KERN_INFO"%s:%d\n", __FUNCTION__, __LINE__);
ret = nonseekable_open(inode, file);
if( ret < 0 )
return ret;
file->private_data = priv_p;
return 0;
}
static int apt_release(struct inode *inode, struct file *file)
{
LOG_DBG(KERN_INFO"%s:%d\n", __FUNCTION__, __LINE__);
return 0;
}
static struct file_operations apt_fops = {
.owner = THIS_MODULE,
.open = apt_open,
.read = apt_read,
.write = apt_write,
.release = apt_release,
.unlocked_ioctl = apt_ioctl,
};
static struct miscdevice apt_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "apt32f101xx",
.fops = &apt_fops,
};
unsigned int get_keycode_keymap(unsigned char key)
{
int i=0;
for(i=0; i < sizeof(keymap)/sizeof(keymap_t); i++)
{
if(keymap[i].key_val == key)
return keymap[i].key_code;
}
return KEY_RESERVED;
}
static void apt_timer_func(unsigned long arg)
{
systimer.expires = jiffies + SYSTEM_STATUS_CHECK_TIME;
add_timer(&systimer);
}
#ifdef D_SUPPORT_KTHREAD
int apt_thread_func(void *data)
{
u8 code, pressed, change = 0;
u8 key_value_mcu = 0;
u8 buf[2] ={0};
//u8 report_falg =0;
do{
//key_value_mcu = read_r8d8(priv_p->client, 0x00);//0x00 1涓猙yte
//msleep(5);
key_value_mcu = read_r8d8(priv_p->client, 0x00);//0x00 1涓猙yte
if(key_value_mcu == 3){
msleep(400);
key_value_mcu = read_r8d8(priv_p->client, 0x00);
}else if(key_value_mcu == 5){
msleep(500);
key_value_mcu = read_r8d8(priv_p->client, 0x00);
}
if(key_value_mcu == 3){
code = 111;
}else if(key_value_mcu ==4){
code = 117;
}else if(key_value_mcu == 5){
code = 113;
}else if(key_value_mcu == 6){
code = 118;
}else{
code = 255;
}
buf[0] = code;
if(code != KEYCODE_DUMP) {
// key pressed
pressed = 1;
change = code;
LOG_DBG(KERN_INFO" key pressed apt_thread_func key_value_mcu =%d change =%d\n",key_value_mcu,change);
input_report_key(priv_p->input_dev, get_keycode_keymap(change), 1);
input_sync(priv_p->input_dev);
write_r8d8(priv_p->client, 0x00, 0x00);
}else if (pressed) {
// key release
//msleep(5);//delay 8ms
pressed = 0;
LOG_DBG(KERN_INFO"key release apt_thread_func key_value_mcu =%d change =%d\n",key_value_mcu,change);
input_report_key(priv_p->input_dev, get_keycode_keymap(change), 0);
input_sync(priv_p->input_dev);
} else {
// no event
}
msleep(100);
} while(!kthread_should_stop());
return 0;
}
#endif
static const struct of_device_id of_apt32f101_match[] = {
{.compatible = "apt,apt32f101" , 0}, //.data = &apt32f101_cdef
{ }
};
MODULE_DEVICE_TABLE(of, of_apt32f101_match);
static int aptf32101_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
int i=0;
LOG_DBG(KERN_INFO"1aptf32101_probe\n");
const struct apt32f101_chipdef *cdef;
const struct of_device_id *of_dev_id;
struct device *dev = &client->dev;
struct input_dev *input_dev;
if (!client->dev.of_node)
{
LOG_DBG(KERN_INFO"apt32f101 device tree node not found.\n");
return -ENODEV;
}
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
{
return -ENXIO;
}
of_dev_id = of_match_device(of_apt32f101_match, dev);
if (!of_dev_id)
return -EINVAL;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->client = client;
priv->cdef = cdef;
i2c_set_clientdata(client, priv);
input_dev = devm_input_allocate_device(&client->dev);
if (!input_dev) {
dev_err(&client->dev, "failed to allocate the input device\n");
return -ENOMEM;
}
__set_bit(EV_KEY, input_dev->evbit);
if (of_property_read_bool(client->dev.of_node, "autorepeat"))
__set_bit(EV_REP, input_dev->evbit);
input_dev->name = "apt32f101-key";
input_dev->dev.parent = &client->dev;
input_dev->id.bustype = BUS_I2C;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0002;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
//input_set_drvdata(input_dev, priv);
//input_set_capability(input_dev, EV_KEY, KEY_HOME);
for(i=0; i < sizeof(keymap)/sizeof(keymap_t); i++)
{
input_set_capability(input_dev, EV_KEY, keymap[i].key_code);
}
priv->input_dev = input_dev;
priv_p = priv;
ret = input_register_device(input_dev);
if (ret) {
dev_err(&client->dev, "failed to register input device\n");
return ret;
}
// miscdevie init
ret = misc_register(&apt_device);
if(ret)
{
LOG_DBG(KERN_ERR"miscdevice register error.\n");
return -1;
}
// timer init
init_timer(&systimer);
systimer.function = &apt_timer_func;
systimer.data = (unsigned long)&apt_device;;
systimer.expires = jiffies + SYSTEM_STATUS_CHECK_TIME;
add_timer(&systimer);
#ifdef D_SUPPORT_KTHREAD
priv_p->pthread = kthread_run(apt_thread_func, NULL, "apt32_thread-%d", 1);
if (IS_ERR(priv_p->pthread)) {
priv_p->pthread = NULL;
}
#endif
//write_r8d8(priv_p->client, SYSREG_LINUX_STARTED, D_ARM_LINUX_OS_STARTED_MAGIC);
return 0;
}
static int aptf32101_remove(struct i2c_client *client)
{
LOG_DBG(KERN_INFO"aptf32101_remove\n");
#ifdef D_SUPPORT_KTHREAD
if (!IS_ERR(priv_p->pthread)) {
kthread_stop(priv_p->pthread);
}
#endif
input_unregister_device(priv_p->input_dev);
misc_deregister(&apt_device);
del_timer(&systimer);
return 0;
}
static const struct i2c_device_id aptf32101_id[] = {
{ "apt32f101"},
{ }
};
MODULE_DEVICE_TABLE(i2c, aptf32101_id);
static struct i2c_driver apt32f101_driver = {
.driver = {
.name = "apt32f101xx",
.of_match_table = of_match_ptr(of_apt32f101_match),
},
.probe = aptf32101_probe,
.remove = aptf32101_remove,
.id_table = aptf32101_id,
};
static int __init apt32f101_init(void)
{
int ret;
LOG_DBG( KERN_INFO "apt32f101_init\n");
ret = i2c_add_driver(&apt32f101_driver);
return ret;
}
static void __exit apt32f101_exit(void)
{
LOG_DBG( KERN_INFO "apt32f101_exit\n");
return i2c_del_driver(&apt32f101_driver);
}
module_init(apt32f101_init);
module_exit(apt32f101_exit);
应用层操作的例子:
#include "voltameter.h"
unsigned short get_battery_val(int fd)
{
unsigned short ret;
ret = ioctl(fd, D_SYSSTATUS_GET_BAT, 0);
if (ret < 0) {
perror("ioctl gpt_hal_get_battery_val error.\n");
ret = 0;
}
return ret;
}
int send_mcu_poweroff(int fd)
{
int ret;
ret = ioctl(fd, D_SYSSTATUS_DO_POWEROFF, 0);
if (ret < 0) {
perror("ioctl send_mcu_poweroff error.\n");
}
return ret;
}
int get_average(int fd)
{
int buf_val[5]={0};
int i = 0;
int averrave_val =0;
for(i= 0;i<5;i++)
{
buf_val[i] =get_battery_val(fd);
}
averrave_val = ( buf_val[0]+buf_val[1]+buf_val[2]+buf_val[3]+buf_val[4])/5;
return averrave_val;
}
int val_class(int val)
{
int battery_level = 0;
if (val <= E_LEVEL_0) { // 3.35V 低电量关机
battery_level = E_BATTERY_LEVEL_0;
} else if (val <= E_LEVEL_1 && val > E_LEVEL_0) {
battery_level = E_BATTERY_LEVEL_1;
} else if (val <= E_LEVEL_2 && val >E_LEVEL_1 ) { // 3.65V
battery_level = E_BATTERY_LEVEL_2;
} else if (val <= E_LEVEL_3 && val > E_LEVEL_2 ) { // 3.73V
battery_level = E_BATTERY_LEVEL_3;
} else if (val <= E_LEVEL_4 && val > E_LEVEL_3) { // 3.87V
battery_level = E_BATTERY_LEVEL_4;
} else if (val <= E_LEVEL_5 && val > E_LEVEL_4) { // 4.20V
battery_level = E_BATTERY_LEVEL_5;
} else {
battery_level = E_BATTERY_LEVEL_5;
}
return battery_level;
}
int main()
{
int ret = -1;
int val = 0;
int buf_val[5]={0};
int i= 0;
int level = 0;
//int motor_fd = 0;
motor_fd = open(MOTOR_DEVICE_PATH, O_RDWR);
if(motor_fd < 0)
{
printf("open /dev/motor error.\n");
return -1;
} else {
printf("open %s success!\n", MOTOR_DEVICE_PATH);
}
while(1){
for (i= 0;i< 5;i++){
buf_val[i]= get_battery_val(motor_fd);
val =val + buf_val[i];
printf("this val[%d] =%d!\n", i,buf_val[i]);
}
val =( buf_val[0]+buf_val[1]+buf_val[2]+buf_val[3]+buf_val[4])/5;
printf("average val=%d!\n", val);
level=val_class(val);
printf("batterylevel=%d!\n", level);
}
return 0;
}
常见问题:
1 当有这种 log:"timeout, ipd: 0x00, state: 1"出现时,请检查硬件上拉是否给电;
2 如果调用 i2c_transfer 返回值为-6 时候,表示为 NACK 错误,即对方设备无应答响应,这种情况一般为外设的问题,常见的有以下几种情况
- I2C 地址错误,解决方法是测量 I²C 波形,确认是否 I²C 设备地址错误
- I2C slave 设备不处于正常工作状态,比如未给电,错误的上电时序等
- 时序不符合 I2C slave 设备所要求也会产生 Nack 信号,比如下面的第三点
3 当外设对于读时序要求中间是 stop 信号不是 repeat start 信号的时候,需要调用两次 i2c_transfer, I²C read 拆分成两次