STM32G474RE的I2C向MCP4728发送数据异常问题问询
问题描述
我正在使用STM32G474RE的I2C接口与MCP4728 DAC芯片通信。预期发送的字节序列为{0xFF, 0x58, 0x04, 0x56},调试显示TX寄存器已写入这些字节,但实际传输时示波器捕获到的是{0xFF, 0x58, 0xFF, 0xFF},而且修改第一个字节时,第三、第四个字节也会跟着变化。
代码片段
int main(void){ SystemClock_Config(); //16MHz HSI16 Clock I2C2_Init(); while (1){ MCP4728_Transmit(SINGLE_WRITE, CHANNEL_A, 0x456); } } void MCP4728_Transmit(uint8_t tx_mode, uint8_t channel, uint16_t data){ /*If data larger than 12 bits (0xFFF) return*/ if (data > 0xFFF) return; int tx_buffer_index = 0; uint8_t tx_buffer[4]; tx_buffer[0] = 0xFF; tx_buffer[1] = tx_mode | (channel << 1); tx_buffer[2] = (uint8_t) (data >> 8) & 0x0F; tx_buffer[3] = (uint8_t) data & 0xFF; I2C2->CR2 |= 0x04 << I2C_CR2_NBYTES_Pos; I2C2->CR2 &= ~I2C_CR2_AUTOEND; I2C2->CR2 |= ((0x60<<1)<< I2C_CR2_SADD_Pos); I2C2->CR2 |= I2C_CR2_START; while(!(I2C2->ISR & I2C_ISR_TC)){ if (I2C2->ISR & I2C_ISR_NACKF) return; if (!(I2C2->ISR & I2C_ISR_TXIS)) return; else{ I2C2->TXDR = tx_buffer[tx_buffer_index]; tx_buffer_index++; } } I2C2->CR2 |= I2C_CR2_STOP; } void I2C2_Init(void){ /*Enable I2C and GPIOA clock*/ RCC->APB1ENR1 |= RCC_APB1ENR1_I2C2EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; /*Config PA8 and PA9*/ //PA8 and PA9 Alternate Function (AF4) GPIOA->MODER &= ~(GPIO_MODER_MODE8 | GPIO_MODER_MODE9); GPIOA->MODER |= (0x02 << GPIO_MODER_MODE8_Pos | 0x02 << GPIO_MODER_MODE9_Pos); GPIOA->AFR[1] |= (0x04 << GPIO_AFRH_AFSEL8_Pos | 0x04 <<GPIO_AFRH_AFSEL9_Pos); //Output type Open Drain GPIOA->OTYPER |= (GPIO_OTYPER_OT8 | GPIO_OTYPER_OT9); //High Speed GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED8 | GPIO_OSPEEDR_OSPEED9); GPIOA->OSPEEDR |= (0x02 << GPIO_OSPEEDR_OSPEED8_Pos | 0x02 << GPIO_OSPEEDR_OSPEED9_Pos); //Enable Pull Up GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD8 | GPIO_PUPDR_PUPD9); GPIOA->PUPDR |= (0x01 << GPIO_PUPDR_PUPD8_Pos | 0x01 << GPIO_PUPDR_PUPD9_Pos); /*Configure I2C2*/ //Disable I2C I2C2->CR1 &= ~I2C_CR1_PE; //Default filter I2C2->CR1 &= ~I2C_CR1_ANFOFF; I2C2->CR1 &= ~I2C_CR1_DNF; //TIMINGR value for 16MHz taken from table in Reference Manual (page xxx) I2C2->TIMINGR |= (0x03 << I2C_TIMINGR_PRESC_Pos | 0x04 << I2C_TIMINGR_SCLDEL_Pos | \ 0x02 << I2C_TIMINGR_SDADEL_Pos | 0x0F << I2C_TIMINGR_SCLH_Pos | \ 0x13 << I2C_TIMINGR_SCLL_Pos); //Enable stretching I2C2->CR1 &= ~I2C_CR1_NOSTRETCH; //Enable I2C I2C2->CR1 |= I2C_CR1_PE; }
问题分析与修复方案
核心问题点
- I2C_CR2寄存器未清零旧值:每次传输直接对NBYTES、SADD位做或操作,残留的旧配置会导致字节计数、地址识别错误,进而干扰后续字节传输。
- TXIS检查逻辑缺陷:仅单次检查TXIS位,若未及时置位就直接返回,导致后续字节无法写入TXDR,最终传输寄存器默认值0xFF。
- NACK处理不完整:收到NACK后未发送停止信号,可能导致总线处于异常挂起状态。
修改后的传输函数
void MCP4728_Transmit(uint8_t tx_mode, uint8_t channel, uint16_t data){ /*If data larger than 12 bits (0xFFF) return*/ if (data > 0xFFF) return; int tx_buffer_index = 0; uint8_t tx_buffer[4]; tx_buffer[0] = 0xFF; tx_buffer[1] = tx_mode | (channel << 1); tx_buffer[2] = (uint8_t) (data >> 8) & 0x0F; tx_buffer[3] = (uint8_t) data & 0xFF; // 先清除CR2中NBYTES、SADD、读写方向位,避免旧配置干扰 I2C2->CR2 &= ~(I2C_CR2_NBYTES_Msk | I2C_CR2_SADD_Msk | I2C_CR2_RD_WRN); // 配置发送字节数 I2C2->CR2 |= 0x04 << I2C_CR2_NBYTES_Pos; // 禁用自动结束模式 I2C2->CR2 &= ~I2C_CR2_AUTOEND; // 配置MCP4728的7位从机地址(左移1位后写入SADD字段) I2C2->CR2 |= (0x60 << 1) << I2C_CR2_SADD_Pos; // 发起起始信号 I2C2->CR2 |= I2C_CR2_START; while(!(I2C2->ISR & I2C_ISR_TC)){ // 处理NACK:发送停止信号后退出 if (I2C2->ISR & I2C_ISR_NACKF){ I2C2->CR2 |= I2C_CR2_STOP; return; } // 循环等待TXIS置位,确保可以写入下一个字节 while(!(I2C2->ISR & I2C_ISR_TXIS)){ // 可选:添加超时检测,防止死循环 } I2C2->TXDR = tx_buffer[tx_buffer_index]; tx_buffer_index++; } // 传输完成后发送停止信号 I2C2->CR2 |= I2C_CR2_STOP; }
修复说明
- CR2寄存器先清零再配置:每次传输前清除相关控制位,确保新配置完全生效,避免旧值叠加导致的参数错误。
- 循环等待TXIS:改为持续等待TXIS位置位,保证每个字节都能在合适时机写入TXDR,解决部分字节未发送的问题。
- 完善NACK处理:收到NACK后主动发送停止信号,避免总线挂起,提升通信稳定性。
内容的提问来源于stack exchange,提问作者th3virtuos0
相关产品推荐
相关产品推荐

