基于用户空间操作IIC接口调试云台电机
在云台机的产品中,会用到电机,电机也有很多不同控制方式的种类,比如基于GPIO的,两个电平控制来控制去,来回倒腾控制,完成你想要的控制目标,这种是最简单的,但是需要更多的IO引脚;也有基于SPI接口的,还有基于IIC接口的,从引脚数量来说,基于IIC的是最少的,在IO引脚紧张的情况下,选用IIC的是最合适的。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/edsam49原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
IIC设备我们都知道要写一个IIC的设备驱动,传统的方式我们把设备驱动编译成一个KO,或者buildin到内核里面去。本文笔者将介绍基于用户空间操作IIC的接口来完成做云台电机的驱动控制;先看看IIC用户层接口吧!
#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);
接口源码:
#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.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);
// for(int i =0 ;i<INT_ADDR_MAX_BYTES;i++)
// {
// printf("fsq_debug:%s-%d:addr[%d]:%d\n",__func__,__LINE__,i,addr[i]);
// }/* 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 andwrite 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);//printf("fsq_debug:%s-%d:convert_iaddr:%x iaddr:%x\n",__func__,__LINE__,convert.iaddr,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);
}
那么怎么使用呢?
int fd;I2CDevice device;
// const char *data = "9876543";ssize_t ret;
unsigned char data[2];unsigned char buf[2];/* 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 = 0x30;device.tenbit = 0;device.delay = 10;device.flags = 0;device.page_bytes = 8;device.iaddr_bytes = 2; /* Set this to zero, and using i2c_ioctl_xxxx API will ignore chip internal address *//* Read */usleep(100000);memset(buf, 0, 2);ret = i2c_ioctl_read(&device, 0x3107, buf, 1);if (ret == -1){fprintf(stderr, "Read i2c error!\n");}printf("i2c_read_handle 0x3107 ret = %d\n", ret);printf("i2c_read_handle buf[0] = %#x\n", buf[0]);memset(buf, 0, 2);ret = i2c_ioctl_read(&device, 0x3108, buf, 1);if (ret == -1){fprintf(stderr, "Read i2c error!\n");}printf("i2c_read_handle 0x3108 ret = %d\n", ret);printf("i2c_read_handle buf[0] = %#x\n", buf[0]);
这个例子是通过接口获取一个sensor的ID。跟通过 i2ctransfer工具来读ID的功效是一样的,如下:
电机控制参考原厂提供的初始驱动,瑞萌的32008N,其实关键是把IIC接口用起来,电机具体驱动不是我们讨论的重点。
static int i2c_fd = -1;
static int motor_iccurt_open_flag = 0; //bit0:motor;bit1: ircut;int open_motor_i2c_device(void){if ((i2c_fd = i2c_open("/dev/i2c-0")) == -1) {printf("Open i2c bus error\n");return -1;}printf("Open i2c bus success!\n");return 0;
}int MS32008_open_motor_device(void)
{if(-1 == i2c_fd){i2c_fd = i2c_open("/dev/i2c-0");if(-1 == i2c_fd){printf("Open i2c bus error\n");return -1;}}motor_iccurt_open_flag |= (1 << 0);printf("Open i2c bus success!\n");return 0;
}int MS32008_close_motor_device(void)
{if(1 == (motor_iccurt_open_flag & (1 << 0))){motor_iccurt_open_flag &= ~(1 << 0);}if(0 == motor_iccurt_open_flag){if(-1 != i2c_fd){i2c_close(i2c_fd);}}return 0;
}
再做初始化电机:
void Init_MS32008(void)
{if(initedFlag)return;open_motor_i2c_device();uint8_t u8Reg[1];u16CHApps = 500;u16CHBpps = 500;u16CHApulse = u16CHApps / 10;u16CHBpulse = u16CHBpps / 10;u8CHAcurrent = 130;u8CHBcurrent = 130;MS32008_SoftReset(); //软件复位
// u8Reg[0] = cmd_nRST_DISABLE | standby_DISABLE | stm_fsw_MUL2|useExtClk_ENABLE|oscOFF_DISABLE;u8Reg[0] = cmd_nRST_DISABLE | standby_DISABLE | stm_fsw_MUL2|oscOFF_DISABLE;
#if (COMM_MODE == IIC_MODE)IIC_WriteReg(0x00,1,&u8Reg[0]);
#endifMS32008_DC_CTRL(DCMotor_Hiz);MS32008_EventClr(uvloClr|otsClr);u8Reg[0] = CHx_PowDri_ENABLE|CHx_recordRev_DISABLE | 0x90;u8Reg[1] = CHx_msModeFull | CHx_ForceStopPosDiv2|CHx_Dir_CW;
#if (COMM_MODE == IIC_MODE)IIC_WriteReg(0x10,2,&u8Reg[0]); //从地址10H开始写入2个数据 chaIIC_WriteReg(0x20,2,&u8Reg[0]); //从地址20H开始写入2个数据 chb//printf("write reg 0x10/0x20 u8Reg[0] = %d\n", u8Reg[0]);
#endifMS32008_SetCHxPPS(CHA,u16CHApps);MS32008_SetCHxPPS(CHB,u16CHBpps);
// MS32008_SetCHxStepNum(CHA,4000);
// MS32008_SetCHxStepNum(CHB,4000);MS32008_SetCHxCurrent(CHA,u8CHAcurrent);MS32008_SetCHxCurrent(CHB,u8CHBcurrent);MS32008_CHxConfLoad(ACH_confLoad|BCH_confLoad);initedFlag = 1;}
Key_Function(uint32_t u32KeyValue)是电机小功能的测试代码;
在测试case里,这样处理:
int functionNum = -1;int runTimes = 1;Init_MS32008();while(functionNum != 100){Key_Function(99);printf("1://步进电机电流增(无此功能)、DC_Motor正转\n");printf("2://步进电机电流减(无此功能)、DC_Motor反转\n");printf("3://步进电机转速增、DC_Motor刹车\n");printf("4://步进电机转速减、DC_Motor高阻\n");printf("7://通道使能/禁止\n");printf("8: //换向\n");printf("9://停机/启动\n");printf(">10 //通道选择11: A, 12:B, 13:E, 14:Z转\n");printf("请输入一个功能号整数: ");scanf("%d", &functionNum);printf("你输入的功能号整数是: %d\n", functionNum);printf("请输入一个运行次数整数: ");scanf("%d", &runTimes);printf("你输入的运行次数是: %d\n", runTimes);if(functionNum < 100){for(int i=0; i<runTimes;i++){Key_Function(functionNum);usleep(500*1000);}}}
demo运行情况:
刚开始调的时候,IIC不通,仔细看了一下原理图,数据和时钟都接反了,这客户硬件设计水平真是差劲,大家引以为戒,前面看起来还正常,不一条一条线看还看不大出来.哎,硬件挖大坑, 软件来填坑.最终通过跳线来完成调试.