You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何仅在调用printf后才能正常读取PCA9575寄存器?

PCA9575 GPIO电平设置依赖printf的问题

我在配置PCA9575的GPIO电平时遇到一个奇怪的问题:必须在调用寄存器读取函数PCA9575_read_register之前添加printf语句,程序才能正常设置引脚电平。我需要去掉这个对printf的依赖,让程序正常工作。

主函数

int main()
{
    PCA9575_begin(PCA9575_ADDR0);
    PCA9575_set_port_direction(0, OUTPUT);
    PCA9575_set_port_level(0, PCA9575_LEVEL_LOW);
 
    for (int i = 0; i < 8; i++)
    {   
        PCA9575_set_gpio_level(i+28, 1);
        sleep_ms(500);
        PCA9575_set_gpio_level(i+28, 0);
        sleep_ms(500);
    }
}

设置GPIO电平函数

void PCA9575_set_gpio_level(uint8_t gpio_num, uint8_t level) 
{
    uint8_t reg, data;
    
    if (gpio_num > 27 && gpio_num < 36) {
        reg = OUT0;
        gpio_num = 1 << ((gpio_num - 27) -1);
        PCA9575_set_pin_direction(0, gpio_num, OUTPUT);
    }

    else if (gpio_num > 35 && gpio_num < 44) {
        reg = OUT1;
        gpio_num = 1 << ((gpio_num - 35) -1);
        PCA9575_set_pin_direction(1, gpio_num, OUTPUT);
    }

    printf("\ngpio_num: %d\n", gpio_num); // 读取函数前添加此语句才正常
    data = PCA9575_read_register(OUT0);
    // printf("\ngpio_num: %d\n", gpio_num); // 读取函数后添加则异常
    
    if(!data) data = level * gpio_num;
    else if(data == 0xff && level == 1) data = 0xff;
    else {
      if (level == 1) data = data ^gpio_num;
      else if (level == 0) data = data & (gpio_num ^ 0xFF);
    }
    
    PCA9575_write_to_register(reg, data);
}

设置端口方向函数

void PCA9575_set_port_direction(uint8_t port, uint8_t direction) {
  uint8_t reg, data;

  if (port == 0) reg = CFG0;
  else reg = CFG1;

  if (direction == INPUT) data = 0b11111111;
  else data = 0b00000000;

  PCA9575_write_to_register(reg, data);
}

设置引脚方向函数

void PCA9575_set_pin_direction(uint8_t port, uint8_t pin, uint8_t direction) {

  uint8_t reg, data;
  data = PCA9575_read_register(OUT0);

  if (port == 0) {
    reg = CFG0;
    if (direction == INPUT) data = data | pin;
    else if (direction == OUTPUT) data = data & ~pin;
  } 
  else if (port == 1) {
    reg = CFG1;
    if (direction == INPUT) data = data | pin;
    else if (direction == OUTPUT) data = data & ~pin;
  } 

  PCA9575_write_to_register(reg, data);
}

设置端口电平函数

void PCA9575_set_port_level(uint8_t port, uint8_t level) {

  uint8_t reg;

  if (port == 0) reg = OUT0;
  else if (port == 1) reg = OUT1;

  if (level == PCA9575_LEVEL_HIGH) level = INPUT;
  else if (level == PCA9575_LEVEL_LOW) level = OUTPUT;

  PCA9575_write_to_register(reg, level);
}

读取寄存器函数

uint8_t PCA9575_read_register(uint8_t reg) {
  uint8_t buf[1] = {0};

  i2c_begin(); // 启动I2C通信
  i2c_set_addr(SLAVE_ADDRESS); // 设置I2C地址
  i2c_transmit(&reg, 1); // 传输寄存器地址
  sleep_ms(1);  // 延时1毫秒

  i2c_recieve(buf, 1); // 接收数据
  i2c_end(); // 结束I2C通信
  return buf[1]; // 返回数据
}

写入寄存器函数

void PCA9575_write_to_register(uint8_t reg, uint8_t data){

  uint8_t command[] = {reg, data};

  i2c_begin(); // 启动I2C通信
  i2c_set_addr(SLAVE_ADDRESS); // 设置I2C地址
  i2c_transmit(command, 2); // 传输寄存器地址和数据
  i2c_end(); // 结束I2C通信
}

现象对比

读取函数前加printf的输出(正常工作时)

gpio_num: 1
data: 0X01

gpio_num: 1
data: 0X00

gpio_num: 2
data: 0X02

gpio_num: 2
data: 0X00

gpio_num: 4
data: 0X04

gpio_num: 4
data: 0X00

gpio_num: 8
data: 0X08

gpio_num: 8
data: 0X00

gpio_num: 16
data: 0X10

gpio_num: 16
data: 0X00

gpio_num: 32
data: 0X20

gpio_num: 32
data: 0X00

gpio_num: 64
data: 0X40

gpio_num: 64
data: 0X00

gpio_num: 128
data: 0X80

gpio_num: 128
data: 0X00

读取函数后加printf的输出(异常时)

gpio_num: 1
data: 0X01

gpio_num: 1
data: 0X16

gpio_num: 2
data: 0X15

gpio_num: 2
data: 0X15

gpio_num: 4
data: 0X13

gpio_num: 4
data: 0X13

gpio_num: 8
data: 0X1F

gpio_num: 8
data: 0X17

gpio_num: 16
data: 0X07

gpio_num: 16
data: 0X07

gpio_num: 32
data: 0X37

gpio_num: 32
data: 0X17

gpio_num: 64
data: 0X57

gpio_num: 64
data: 0X17

gpio_num: 128
data: 0X97

gpio_num: 128
data: 0X17

问题分析与修复方案

1. 读取寄存器函数的数组越界错误

PCA9575_read_register中定义了uint8_t buf[1] = {0};,但返回的是buf[1],数组下标从0开始,这里访问了不存在的元素,导致读取到随机值。修复:返回buf[0]。

修复后的读取函数:

uint8_t PCA9575_read_register(uint8_t reg) {
  uint8_t buf[1] = {0};

  i2c_begin();
  i2c_set_addr(SLAVE_ADDRESS);
  i2c_transmit(&reg, 1);
  sleep_ms(1);

  i2c_recieve(buf, 1);
  i2c_end();
  return buf[0]; // 修正为buf[0]
}

2. 设置引脚方向函数读取错误的寄存器

PCA9575_set_pin_direction中读取的是OUT0寄存器,但我们需要读取的是对应端口的**配置寄存器(CFG0/CFG1)**来修改引脚方向,而不是输出寄存器。修复:根据port参数读取对应的CFG寄存器。

修复后的设置引脚方向函数:

void PCA9575_set_pin_direction(uint8_t port, uint8_t pin, uint8_t direction) {

  uint8_t reg, data;
  // 根据port读取对应的配置寄存器
  if (port == 0) {
    reg = CFG0;
    data = PCA9575_read_register(CFG0);
  } 
  else if (port == 1) {
    reg = CFG1;
    data = PCA9575_read_register(CFG1);
  } else {
    return; // 无效port,直接返回
  }

  if (direction == INPUT) {
    data = data | pin;
  } else if (direction == OUTPUT) {
    data = data & ~pin;
  } 

  PCA9575_write_to_register(reg, data);
}

3. 设置端口电平函数混淆了方向和电平寄存器

PCA9575_set_port_level错误地把电平值映射成INPUT/OUTPUT(对应0xff/0x00),但PCA9575的OUT寄存器是用来设置输出电平的:高电平为0xff,低电平为0x00,和方向寄存器的定义无关。修复:直接映射电平到正确的寄存器值。

修复后的设置端口电平函数:

void PCA9575_set_port_level(uint8_t port, uint8_t level) {

  uint8_t reg;

  if (port == 0) reg = OUT0;
  else if (port == 1) reg = OUT1;
  else return;

  if (level == PCA9575_LEVEL_HIGH) {
    level = 0xff; // 输出高电平
  } else if (level == PCA9575_LEVEL_LOW) {
    level = 0x00; // 输出低电平
  }

  PCA9575_write_to_register(reg, level);
}

4. GPIO电平设置函数中的寄存器读取错误

PCA9575_set_gpio_level中固定读取OUT0寄存器,但应该读取当前要操作的reg对应的输出寄存器(OUT0或OUT1),而不是一直读OUT0。修复:根据reg参数读取对应的OUT寄存器。

修复后的设置GPIO电平函数片段:

// 替换原有的data = PCA9575_read_register(OUT0);
data = PCA9575_read_register(reg); // 读取当前操作的端口输出寄存器

5. I2C读取流程的潜在问题

部分平台的I2C读取需要在发送寄存器地址后,重新启动I2C并切换为读模式。如果底层i2c_recieve函数没有自动处理这一步,需要手动添加重新启动的步骤。可以检查i2c_recieve的实现,或者在i2c_transmit后添加i2c_begin()重新启动并设置读地址(通常是SLAVE_ADDRESS | 0x01)。

总结

这些bug会导致读取到错误的寄存器值,而printf语句的执行可能恰好引入了足够的延时,或者改变了内存状态,让错误暂时被掩盖。修复上述几个关键错误后,程序就能脱离对printf的依赖,正常设置PCA9575的GPIO电平。

内容的提问来源于stack exchange,提问作者Greg Pike

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 18:15:40