【电源】通过I2c控制瀚强PSU-3300电源
一、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设备的读写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* 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:获取到一个字节的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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个字节。
1 2 3 4 5 6 | 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 */ }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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的长度。
1 2 3 4 5 6 7 8 9 10 11 | 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操作,不然不知道读取哪个地址的数据。
1 2 3 4 5 6 7 8 9 10 11 12 | 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\n" ,ctx->addr); applog(LOG_DEBUG, "I2C-R-RAW(0x%02x)" , ctx->addr); return true ; } |
6、i2c_slave_open函数
功能:open i2c进行读写,并进行从设备地址的设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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
功能:关闭文件句柄,释放内存空间。
1 2 3 4 5 6 7 | 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、代码实现。
读写函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | //写多个字节数据 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\n" ,len); ret= fd ->write_raw(fd, buff , len) ; if (ret != true ) { printf ( "write data error!\n" ); 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!\n" ); return 0; } printf ( "read word data:val=%x\n" ,*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!\n" ); return 0; } printf ( "read single byte data:val=%x\n" ,*val); if (fd !=NULL) { fd-> exit (fd); } return 1; } |
(1)读PSU version.
由规格书可知,psu version的寄存器地址位0x00,version只占一个字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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!\n" ); return 0; } return psuVersion; } |
(2)设置主路电压输出开关
设置主路电压输出状态的寄存器地址为0x02,写0x01 enable,写0x00 disable,目前PSU默认时关闭主路电压输出的。
由规格书可知,控制板写数据到PSU的写设备地址为0x58,读设备地址为0x59.
CRC8校验码的计算方式是,CRC(设备地址,寄存器地址,要写入的数据)。
CRC8的算法代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | char crc8( const void * vptr, int len) { const char *data = vptr; unsigned crc = 0; int i, j; printf ( "\n len=%ld\n" , 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就打开主路电压输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 | void avalon9_Set_PowrOn() { uint8_t ret = 0; //RegAddr+data+CRC uint8_t powerOnCode[] = {0x02, 0x01,0x58}; printf ( "Set Power On !\n" ); ret = avalon9_write_PSU(powerOnCode, sizeof (powerOnCode)/ sizeof (powerOnCode[0])); if (ret == 0) { printf ( "Set Power On failed!\n" ); } } |
关闭也是同理,这里不再赘述。
(3)读取主路电压状态(1个字节).
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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!\n" ); return 2; } return psuPowerStatus; } |
(4)读取主路电压
寄存器地址为0x07,读取2个字节。先读到高位,再读低位。读取到16bit的数据转为十进制然后乘0.01就是实际的电压值。
1 2 3 4 5 6 7 8 9 10 11 12 | 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!\n" ); return 0; } return psuVSBout*0.01; } |
(5)设置主路电压
设置主路电压12.5V波形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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\n" ,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\n" ); 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!\n" ); } } |
其他的功能类似就不再赘述了。
6、设置第四路i2c通讯速度
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)