开发环境:
虚拟I2C
keil
芯片型号:STM32U575
芯片详解:
或
两组端口P00~07和P10~17
1、读写地址
首先搞清楚电路板上A0~2怎么接的,比如笔者使用的电路板全接地,这里地址就是0x40+读写位,要向PCA9535芯片写数据时地址为0x40,读的话为0x41。
2、选择寄存器
PCA9535芯片共有8个读写寄存器,分配给两端的8个IO口,按功能分为4类
寄存器0,2,4,6对应P0端,寄存器1,3,5,7对应P1端
寄存器01为输入端口,用来读IO口的状态;
寄存器23为输出端口,用来写IO口的状态;
寄存器45为极性反转端口,用来反转IO口的极性;
寄存器67为方向端口,用来控制IO口的方向(输入/输出);
这里要说明的是,寄存器01的优先级是大于寄存器67的,也就是无论现在IO口的状态是输入还是输出,寄存器01中总是可以读出现在外部IO口的电平模拟量高低。
有了上述基础内容,我们可以简单的完成读写。
3、写IO口数据
由官方给的时序图
可知,写数据的顺序为:
I2C开始--->写写地址--->等待ACK信号--->写要写入的寄存器地址--->等待ACK信号--->写寄存器--->等待ACK信号--->写入另一边端口的寄存器--->等待ACK信号--->I2C停止
这里写入另一边端口的寄存器的意思是,如果我要写寄存器2,也就是我要写P0端的“写功能”寄存器,那么这个另一端就是寄存器3,也就是P1端口的“写功能"寄存器。如果我还继续写,就会又跳回寄存器2,如此反复。
写入的数据是一次性对P0或P1端8个IO口一起写,本文往下会介绍如何单独写和单独读。
4、读IO口数据
类似于写,我们先看时序图
可以看出,要读数据,首先还是得先写入我要读哪个寄存器,命令顺序为:
I2C开始--->写写地址--->等待ACK信号--->写入要读数据的寄存器--->等待ACK信号--->IC2开始--->写读地址--->等待ACK信号--->读数据--->发出ACK信号--->是否要读另一端口的寄存器--->发出ACK信号--->I2C停止
相同于写数据,读数据也是读一整个端口8个IO口
这里对以上部分代码做个汇总:
#define PCA9535_ADDR_READ 0x41 // 读命令 #define PCA9535_ADDR_WRITE 0x40 // 写命令 // 读有效输入端口,反应外部逻辑电平 #define PCA9535_CMD_IN0 0 #define PCA9535_CMD_IN1 1 // 写有效输出端口 #define PCA9535_CMD_OUT0 2 #define PCA9535_CMD_OUT1 3 // 写有效输出端口,效果是对应位反转 #define PCA9535_CMD_Toggle0 4 #define PCA9535_CMD_Toggle1 5 // 控制输入输出端口,对应位设置 // 1为输入状态 // 0为输出状态 // 重置后所有寄存器为输入态 // 没有即输入又输出状态 #define PCA9535_CMD_DIR0 6 #define PCA9535_CMD_DIR1 7 // 端口0 // IN0、OUT0、Toggle0、DIR0都是这个端口 #define PCA95xx_IO_00 0x0001 #define PCA95xx_IO_01 0x0002 #define PCA95xx_IO_02 0x0004 #define PCA95xx_IO_03 0x0008 #define PCA95xx_IO_04 0x0010 #define PCA95xx_IO_05 0x0020 #define PCA95xx_IO_06 0x0040 #define PCA95xx_IO_07 0x0080 // 端口1 #define PCA95xx_IO_10 0x0101 #define PCA95xx_IO_11 0x0102 #define PCA95xx_IO_12 0x0104 #define PCA95xx_IO_13 0x0108 #define PCA95xx_IO_14 0x0110 #define PCA95xx_IO_15 0x0120 #define PCA95xx_IO_16 0x0140 #define PCA95xx_IO_17 0x0180
这里端口的IO定义为16位是为了方便后面对单个IO口的读写
以下程序中用到的I2C程序这里不做多余解释,以讲懂原理为主
方向选择初始化:
//方向选择,这里笔者需要,将P02设为输入,其余都为输出 void pca9535_dio_init(void) { iic_pca9535.iic_start(iic_pca9535.cb); iic_send_byte(&iic_pca9535, PCA9535_ADDR_WRITE); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_send_byte(&iic_pca9535, PCA9535_CMD_DIR0); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_send_byte(&iic_pca9535, 0x04); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_send_byte(&iic_pca9535, 0x00); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_pca9535.iic_stop(iic_pca9535.cb); delay_xus(10); }
写端口:
// which:0或1 void pca9535_write(uint8_t which, uint8_t DataToWrite) { uint8_t cmd_num; if (which == 0) cmd_num = PCA9535_CMD_OUT0; else cmd_num = PCA9535_CMD_OUT1; iic_pca9535.iic_start(iic_pca9535.cb); iic_send_byte(&iic_pca9535, PCA9535_ADDR_WRITE); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_send_byte(&iic_pca9535, cmd_num); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_send_byte(&iic_pca9535, DataToWrite); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_pca9535.iic_stop(iic_pca9535.cb); delay_xus(10); }
读端口:
// which:0或1 uint8_t pca9535_read(uint8_t which) { uint8_t temp, cmd_num; if (which == 0) cmd_num = PCA9535_CMD_IN0; else cmd_num = PCA9535_CMD_IN1; iic_pca9535.iic_start(iic_pca9535.cb); iic_send_byte(&iic_pca9535, PCA9535_ADDR_WRITE); iic_pca9535.iic_wait_ack(iic_pca9535.cb); iic_send_byte(&iic_pca9535, cmd_num); iic_pca9535.iic_wait_ack(iic_pca9535.cb); delay_xus(10); iic_pca9535.iic_start(iic_pca9535.cb); iic_send_byte(&iic_pca9535, PCA9535_ADDR_READ); iic_pca9535.iic_wait_ack(iic_pca9535.cb); temp = iic_read_byte(&iic_pca9535, 1); iic_pca9535.iic_stop(iic_pca9535.cb); delay_xus(10); return temp; }
以上部分是对整个端口的读写,接下来我们介绍以下对单独端口的读写
5、写单个IO口数据
先上代码:
// 单独修改对应位I/O值 // pin:对应IO // n:IO设置电平高低 void pca9535_set_pin(uint16_t pin, GPIO_PinState n) { uint8_t temp, cmp, which, writedata; which = (uint8_t)(pin >> 8); writedata = (uint8_t)(pin & (0x00ff)); if (n == 0) cmp = 0; else cmp = writedata; writedata = ~writedata; temp = pca9535_read(which); writedata &= temp; writedata |= cmp; pca9535_write(which, writedata); return; }
输入为要设置的单独IO口,以及要设置的电平,1为高,0为低
思路是这样的:
1、确定这个IO是P0上的还是P1上的,所以我们上面定义时定义为了16位,前8位就是为了区分P0还是P1的;
2、把定义的IO口的后8位提取出来,这才是我们真正要使用的寄存器数据;
3、将要写入的数据按位反转,比如我们要改变P12的值,那么我们目前得到的寄存器数据应该是0000 0010,这代表着P12这个IO口,这里我们位反转后得到1111 1101,将这个1111 1101和目前的P1口按位与一下,就可以得到P1端口8个IO口的目前的状态(除了我们要改变的P12),这个时候我们就可以在不改变其他7个IO口的情况下,单独对P12进行设置;
4、单独设置值,如果我们要设置0,那就让上面我们得到的那个数据不变,也就是按位或0,如果我们要设置1,那我们就将上面我们得到的那个数据按位或0000 0010,也就是最初提取出来的后8位。
5、写入数据,这个时候我们就可以得到一个,其他7位不变,而要设置的那一位已经变成哦我们想要的值的一个8位数据,再将这个数据写入对应的寄存器就可以啦。
这里看不懂的话可以自己拿个笔划拉一下,一写就懂了。
6、读单个IO口数据:
GPIO_PinState pca9535_get_pin(uint16_t pin) { uint8_t stat = 0; uint8_t which = (uint8_t)(pin >> 8); uint8_t writedata = (uint8_t)(pin & (0x00ff)); uint8_t port_data; port_data = pca9535_read(which); port_data &= writedata; if (port_data != 0) stat = 1; return stat; }
有了上面的写,这个读就是小儿科了,就是先把寄存器1或0的东西读出来,然后和IO定义的后八位按位与一下,如果结果是0,那原本IO口就是0,如果非0,那原本IO口就是1,再返回这个0或1就可以了。
7、反转单个IO口数据:
void pca9535_toggle_pin(uint16_t pin) { uint8_t port_data; uint8_t which = (uint8_t)(pin >> 8); uint8_t writedata = (uint8_t)(pin & (0x00ff)); port_data = pca9535_read(which); port_data &= writedata; if (port_data != 0) pca9535_set_pin(pin, 0); else pca9535_set_pin(pin, 1); return; }
有了上面的写和读,这个就不再浪费口舌了,先读,再判断,最后写。
差不多就这些,本文主要讲原理,切勿直接复制粘贴然后啥也不改直接编译,要切合自己使用环境做出相应修改。