基于STM32的PCA9535 IO拓展板针对单个IO口的读写

文章详细介绍了如何在STM32U575开发环境中利用虚拟I2C与PCA9535芯片通信,包括地址设定、寄存器选择、单个IO口读写和极性反转操作,强调了理论与实际应用的结合

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发环境:

虚拟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;
}

有了上面的写和读,这个就不再浪费口舌了,先读,再判断,最后写。

差不多就这些,本文主要讲原理,切勿直接复制粘贴然后啥也不改直接编译,要切合自己使用环境做出相应修改

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值