一、PSU-3300电源
1、功能说明
主要通过此电源的主路电压给算力板供电,侧路电压接口给控制板供电,可以通过控制板的i2c接口去动态调节算力板供电电压。
2、参数说明
-
IIC设备地址为0x2c。
-
PSU version为0x04.
-
通讯速度为50khz.
-
数据传输指令格式为N byte data + 1Byte CRC8校验码。
-
控制板通过发送指令调整主路电压输出。
3、协议有无说明。
4、调压指令说明
5、错误码说明
6、i2c时序说明
由时序可知,主机写数据给PSU的时序为:
-
发送开始信号。
-
发设备地址(7位地址+1个读写位)。
-
从机应答ack.
-
发要读的寄存器地址。
-
从机应答。
-
发送数据(先发高位,再发地位)。
-
发stop信号。
读时序:
-
发送开始信号。
-
发送要读的设备地址(7位地址+W)
-
回ACK。
-
发送要读的寄存器地址。
-
回ACK.
-
再发送设备地址 (7为地址+R)
-
ACK
-
读取PSU发过来的数据。
-
读取CRC8(经示波器实验,并没有收到CRC8校验码)。
-
stop.
二、主要函数的说明。
由PSU的时序可知是标准的IIC通讯时序。
cgminer 中有以下几个接口进行i2c设备的读写。
/* common i2c context */
struct i2c_ctx {
/* destructor */
void (*exit)(struct i2c_ctx *ctx);
/* write one byte to given register */
bool (*write)(struct i2c_ctx *ctx, uint8_t reg, uint8_t val);
/* read one byte from given register */
bool (*read)(struct i2c_ctx *ctx, uint8_t reg, uint8_t *val);
/* read one word from given register */
bool (*read_word)(struct i2c_ctx *ctx, uint8_t reg, uint16_t *val);
/* write multiple bytes to addr */
bool (*write_raw)(struct i2c_ctx *ctx, uint8_t *buf, uint32_t len);
/* read multiple bytes from addr */
bool (*read_raw)(struct i2c_ctx *ctx, uint8_t *buf, uint32_t len);
/* common data */
uint8_t addr;
int file;
};
static bool i2c_slave_read(struct i2c_ctx *ctx, uint8_t reg, uint8_t *val);
static bool i2c_slave_write(struct i2c_ctx *ctx, uint8_t reg, uint8_t val)
static bool i2c_slave_read_word(struct i2c_ctx *ctx, uint8_t reg, uint16_t *val);
static bool i2c_slave_write_raw(struct i2c_ctx *ctx, uint8_t *buf, uint32_t len);
static bool i2c_slave_read_raw(struct i2c_ctx *ctx, uint8_t *buf, uint32_t len);
1、i2c_slave_read函数说明。
功能:读取一个字节的数据。
参数说明:
-
ctx :结构体对象,可通过指针去访问上面几个read,write函数
-
reg:要读的寄存器的地址。
-
val:获取到一个字节的数据
static bool i2c_slave_read(struct i2c_ctx *ctx, uint8_t reg, uint8_t *val)
{
union i2c_smbus_data data;
struct i2c_smbus_ioctl_data args;
args.read_write = I2C_SMBUS_READ;
args.command = reg;
args.size = I2C_SMBUS_BYTE_DATA; //读取一个字节的数据
args.data = &data;
if (ioctl(ctx->file, I2C_SMBUS, &args) == -1) {
applog(LOG_INFO, "i2c 0x%02x: failed to read from fdesc %d: %s",
ctx->addr, ctx->file, strerror(errno));
return false;
}
*val = data.byte;
applog(LOG_DEBUG, "I2C-R(0x%02x/0x%02x)=0x%02x", ctx->addr, reg, *val);
return true;
}
2、i2c_slave_write
功能:写一个字节的数据。
参数说明:
-
ctx :结构体对象,可通过指针去访问上面几个read,write函数
-
reg:要读的寄存器的地址。
-
val:要写入的一个字节的数据。
3、i2c_slave_read_word
由psu规格书可知,在读电流、电压等数据时要发送两个字节的数据。根据i2c_slave_read函数,可知args.size可以设置读取的字节数。根据i2c驱动代码可知,有以下三种类型选择,byte:一个字节;word:两个字节;block:最多可以写32个字节。
union i2c_smbus_data {
__u8 byte;
__u16 word;
__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
/* and one more for user-space compatibility */
};
static bool i2c_slave_read_word(struct i2c_ctx *ctx, uint8_t reg, uint16_t *val)
{
union i2c_smbus_data data;
struct i2c_smbus_ioctl_data args;
args.read_write = I2C_SMBUS_READ;
args.command = reg;
args.size =I2C_SMBUS_WORD_DATA;
args.data = &data;
if (ioctl(ctx->file, I2C_SMBUS, &args) == -1) {
applog(LOG_INFO, "i2c 0x%02x: failed to read from fdesc %d: %s",
ctx->addr, ctx->file, strerror(errno));
return false;
}
*val = data.word;
//printf("data.word=%x",data.word);
applog(LOG_DEBUG, "I2C-R(0x%02x/0x%02x)=0x%02x", ctx->addr, reg, *val);
return true;
}
4、i2c_slave_write_raw。
功能:写多个字节的数据。
参数说明:
-
ctx:结构体对象,可通过指针去访问上面几个read,write函数。
-
buf:写入数据的buf
-
len:buf的长度。
static bool i2c_slave_write_raw(struct i2c_ctx *ctx, uint8_t *buf, uint32_t len)
{
/* SMBus cann't support write bytes > 32, use plain i2c write */
if (len != write(ctx->file, buf, len)) {
applog(LOG_INFO, "i2c 0x%02x: failed to write raw to fdesc %d: %s",
ctx->addr, ctx->file, strerror(errno));
return false;
}
applog(LOG_DEBUG, "I2C-W-RAW(0x%02x)", ctx->addr);
return true;
}
5、i2c_slave_read_raw
此函数可读取多个字节的数据。但是在read之前要进行write操作,不然不知道读取哪个地址的数据。
static bool i2c_slave_read_raw(struct i2c_ctx *ctx, uint8_t *buf, uint32_t len)
{
/* SMBus cann't support read bytes > 32, use plain i2c read */
if (len != read(ctx->file, buf, len)) {
applog(LOG_INFO, "i2c 0x%02x: failed to read raw from fdesc %d: %s",
ctx->addr, ctx->file, strerror(errno));
return false;
}
printf("ctx->addr=%x
",ctx->addr);
applog(LOG_DEBUG, "I2C-R-RAW(0x%02x)", ctx->addr);
return true;
}
6、i2c_slave_open函数
功能:open i2c进行读写,并进行从设备地址的设置。
extern struct i2c_ctx *i2c_slave_open(char *i2c_bus, uint8_t slave_addr)
{
int file = open(i2c_bus, O_RDWR);
if (file < 0) {
applog(LOG_INFO, "Failed to open %s: %s", i2c_bus, strerror(errno));
return NULL;
}
if (ioctl(file, I2C_SLAVE, slave_addr) < 0) {
close(file);
return NULL;
}
struct i2c_ctx *ctx = malloc(sizeof(*ctx));
assert(ctx != NULL);
ctx->addr = slave_addr;
ctx->file = file;
ctx->exit = i2c_slave_exit;
ctx->read = i2c_slave_read;
ctx->read_word = i2c_slave_read_word;
ctx->write = i2c_slave_write;
ctx->read_raw = i2c_slave_read_raw;
ctx->write_raw = i2c_slave_write_raw;
return ctx;
}
7、i2c_slave_exit
功能:关闭文件句柄,释放内存空间。
static void i2c_slave_exit(struct i2c_ctx *ctx)
{
if (ctx->file == -1)
return;
close(ctx->file);
free(ctx);
}
三、功能实现。
根据PSU-3300规格书主要分为以下功能:
-
读PSU Version。
-
主路电压进行开关操作,并读取开关状态。
-
读PSU的error code.
-
读侧路电压(接控制板)。
-
读主路电压(接算力板)。
-
读主路电流。
-
主路输出功率。
-
读SR温度(后级散热器温度)。
-
读PFC温度(前级散热器温度)。
-
读PSU fan1的转速。
-
设置主路输出电压。
1、代码实现。
读写函数实现:
//写多个字节数据
uint8_t avalon9_write_PSU(uint8_t* buff,uint8_t len)
{
uint8_t ret;
struct i2c_ctx * fd;
fd = i2c_slave_open("/dev/i2c-3", 0x2c);
if(fd == NULL)
{
printf("open dev error!");
return 0;
}
printf("write %d byte
",len);
ret= fd ->write_raw(fd, buff , len) ;
if(ret != true)
{
printf("write data error!
");
return 0;
}
if(fd !=NULL)
{
fd->exit(fd);
}
return 1;
}
//读2个字节
uint8_t avalon9_Read_Word_from_PSU(uint8_t reg, uint16_t *val)
{
uint8_t ret;
struct i2c_ctx * fd;
fd = i2c_slave_open("/dev/i2c-3", 0x2c);
if(fd == NULL)
{
printf("open dev error!");
return 0;
}
ret = fd->read_word(fd, reg , val);
if(ret != true)
{
printf("read data error!
");
return 0;
}
printf("read word data:val=%x
",*val);
if(fd !=NULL)
{
fd->exit(fd);
}
return 1;
}
//读1个字节
uint8_t avalon9_Read_Single_byte_from_PSU(uint8_t reg, uint8_t *val)
{
uint8_t ret;
struct i2c_ctx * fd;
fd = i2c_slave_open("/dev/i2c-3", 0x2c);
if(fd == NULL)
{
printf("open dev error!");
return 0;
}
ret= fd ->read(fd, reg , val) ;
if(ret != true)
{
printf("read data error!
");
return 0;
}
printf("read single byte data:val=%x
",*val);
if(fd !=NULL)
{
fd->exit(fd);
}
return 1;
}
(1)读PSU version.
由规格书可知,psu version的寄存器地址位0x00,version只占一个字节。
uint8_t avalon9_Get_PSU_Version()
{
uint8_t psuVersionReg = 0x00;
uint8_t psuVersion=0,ret = 0;
ret = avalon9_Read_Single_byte_from_PSU(psuVersionReg,&psuVersion);
if(ret == 0)
{
printf("read psu Version data error!
");
return 0;
}
return psuVersion;
}
(2)设置主路电压输出开关
设置主路电压输出状态的寄存器地址为0x02,写0x01 enable,写0x00 disable,目前PSU默认时关闭主路电压输出的。
由规格书可知,控制板写数据到PSU的写设备地址为0x58,读设备地址为0x59.
CRC8校验码的计算方式是,CRC(设备地址,寄存器地址,要写入的数据)。
CRC8的算法代码:
char crc8(const void* vptr, int len)
{
const char *data = vptr;
unsigned crc = 0;
int i, j;
printf("
len=%ld
",sizeof(vptr)/sizeof(vptr[0]));
for (j = len; j; j--, data++) {
crc ^= (*data << 8);
for(i = 8; i; i--) {
if (crc & 0x8000)
crc ^= (0x1070 << 3);
crc <<= 1;
}
}
return (char)(crc >> 8);
}
将 {0x58,0x02,0x01}代入会计算出CRC值为0x58,也可以只用CRC8计算器计算.
发送{0x02,0x01,0x58}给PSU就打开主路电压输出。
void avalon9_Set_PowrOn()
{
uint8_t ret = 0;
//RegAddr+data+CRC
uint8_t powerOnCode[] = {0x02, 0x01,0x58};
printf("Set Power On !
");
ret = avalon9_write_PSU(powerOnCode,sizeof(powerOnCode)/sizeof(powerOnCode[0]));
if(ret == 0)
{
printf("Set Power On failed!
");
}
}
关闭也是同理,这里不再赘述。
(3)读取主路电压状态(1个字节).
uint8_t avalon9_Get_PowrOnOff()
{
uint8_t psuPowerstausReg = 0x02;
uint8_t psuPowerStatus=0xff,ret=0;
ret = avalon9_Read_Single_byte_from_PSU(psuPowerstausReg,&psuPowerStatus);
if(ret == 0)
{
printf("read psu Power Staus Failed!
");
return 2;
}
return psuPowerStatus;
}
(4)读取主路电压
寄存器地址为0x07,读取2个字节。先读到高位,再读低位。读取到16bit的数据转为十进制然后乘0.01就是实际的电压值。
float avalon9_Get_PSUSideRoad_Voltage()
{
uint8_t psuVSBoutReg = 0x06,ret=0; //侧路电压
uint16_t psuVSBout = 0;
ret = avalon9_Read_Word_from_PSU(psuVSBoutReg,&psuVSBout);
if(ret == 0)
{
printf("Read PSU Error Code Failed!
");
return 0;
}
return psuVSBout*0.01;
}
(5)设置主路电压
设置主路电压12.5V波形
void avalon9_Set_PSUMainRoad_Voltage(float val)
{
uint8_t ret = 0;
uint8_t Voutcode[11][4] = {
{0x12,0xb0,0x04,0x6b}, //12.00
{0x12,0xba,0x04,0xe9}, //12.10
{0x12,0xc4,0x04,0x9d}, //12.20
{0x12,0xce,0x04,0x1f}, //12.30
{0x12,0xd8,0x04,0x36}, //12.40
{0x12,0xe2,0x04,0x4d}, //12.50
{0x12,0xec,0x04,0x9b}, //12.60
{0x12,0xf6,0x04,0x4e}, //12.70
{0x12,0x00,0x05,0x23}, //12.80
{0x12,0x0a,0x05,0xa1}, //12.90
{0x12,0x14,0x05,0x20} //13.00
};
printf("set MainRoad Voltage=%f
",val);
switch((uint8_t)((val)*10)) //switch参数必须为整数。
{
case 121:
ret = avalon9_write_PSU(Voutcode[1],4);
break;
case 122:
ret = avalon9_write_PSU(Voutcode[2],4);
break;
case 123:
ret = avalon9_write_PSU(Voutcode[3],4);
break;
case 124:
ret = avalon9_write_PSU(Voutcode[4],4);
break;
case 125:
printf("12.5
");
ret = avalon9_write_PSU(Voutcode[5],4);
break;
case 126:
ret = avalon9_write_PSU(Voutcode[6],4);
break;
case 127:
ret = avalon9_write_PSU(Voutcode[7],4);
break;
case 128:
ret = avalon9_write_PSU(Voutcode[8],4);
break;
case 129:
ret = avalon9_write_PSU(Voutcode[9],4);
break;
case 130:
ret = avalon9_write_PSU(Voutcode[10],4);
break;
default:
ret = avalon9_write_PSU(Voutcode[0],4);
break;
}
if(ret == 0)
{
printf("Set mainRoad Voltage Failed!
");
}
}
其他的功能类似就不再赘述了。
6、设置第四路i2c通讯速度