STM32裸机I2C的CCR、TRISE配置错误致MMA8452Q通信异常排查
遵循@wovano的建议,我将精简问题表述,问题历史版本可查看编辑记录。
概述
项目采用STM32F407ZGT6作为主控,外接MMA8452Q加速度计实现I2C通信,全程使用裸机开发方式,加速度计功能调用gschorcht库实现,目前手头无示波器、FT232H调试工具。
问题描述
当前无法确认I2C是否正常工作,存在两处核心异常:
- 读取设备ID返回值为0x22,而非芯片手册规定的0x2A*;
- 无法循环读取数据,使用示例代码中
if (mma845x_new_data (sensor) && mma845x_get_float_data (sensor, &data))判断语句时,条件永远无法成立;按如下方式修改代码后,调试时会进入Default_Handler: Infinite_Loop死循环:
void read_data(void) { mma845x_float_data_t data; if (mma845x_new_data (sensor)) { if(mma845x_get_float_data (sensor, &data)) { // max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits printf("[MMA845X (xyz)[g]] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", data.ax, data.ay, data.az); return; } } printf("[not new MMA845X (xyz)[g]] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", data.ax, data.ay, data.az); }
调试选项卡显示触发了跟踪/断点陷阱线程:
我怀疑CCR或TRISE寄存器配置有误(下文附参数计算过程)。当前系统时钟配置为SYSCLK=168MHz,APB1总线时钟为42MHz(APB1PRESC分频系数=4),目前测试I2C标准模式(100kbps)。
脚注:该偏差有可能是因为使用了不同品牌的兼容传感器,但0x2A二进制为0b0010 1010、0x22为0b0010 0010,存在位丢失的嫌疑;更换另一块加速度计测试时读取ID为0x05,怀疑存在位传输丢失问题但暂无法确认。
已确认无误的配置项
- 硬件板卡上I2C线路已正确配置上拉电阻
- GPIO的MODER(模式寄存器)、AFR(复用功能寄存器)配置正确
- SA0引脚电平配置与库中定义的设备地址匹配
- 读取DR(数据寄存器)后已正确发送STOP信号
相关代码
void init_gpio(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Port B GPIOB->MODER |= (2U << 12U); //MMA845_SCL || PB6 GPIOB->AFR[0] |= (4U << 24U); //AF4 GPIOB->OTYPER |= (1U << 6U); //Output Type to Open Drain GPIOB->PUPDR |= (1U << 12U); //Set to Pull-Up GPIOB->MODER |= (2U << 14U); //MMA845_SDA || PB7 GPIOB->AFR[0] |= (4U << 28U); //AF4 GPIOB->OTYPER |= (1U << 7U); //Output Type to Open Drain GPIOB->PUPDR |= (1U << 14U); //Set to Pull-Up } /*i2c_init*/ void i2c_init(I2C_TypeDef *I2Cx) { /*Enable clock access to I2Cs*/ RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //enable I2C1 // RCC->APB1ENR |= RCC_APB1ENR_I2C2EN; //enable I2C2 // RCC->APB1ENR |= RCC_APB1ENR_I2C3EN; //enable I2C3 RCC->APB1ENR; /*Reset I2C*/ I2Cx->CR1 |= I2C_CR1_SWRST; /*!BUSY ON */ I2Cx->CR1 &= ~I2C_CR1_SWRST; /*!BUSY OFF*/ int CCR_VAL; int TRISE_VAL; int MAX_FRQ; /*Set I2C frequency same as APB*/ MAX_FRQ = (SYSCLK/APB1PRESC)/1000000; //= 42 @ max clock speed I2Cx->CR2 &= ~msk(6,0); /*!BUSY ON*/ I2Cx->CR2 |= MAX_FRQ; if(!MASTER_MODE){ /* Standard Mode = 100kb/s*/ /* CCR calculated by half-period of 100k * divided by the period of the PCLK * CCR = ((1/100'000)/2)/(1/42'000'000) * CCR = 5000ns/~24ns = 210 */ CCR_VAL = 210; /* TRISE calculated by PCLK (in MHz) + 1 * TRISE = ((42'000'000/1'000'000) + 1) * TRISE = 42 + 1 = 43 */ TRISE_VAL = 43; } else { /*Fast Mode = 400kb/s*/ if(DUTY_MODE) { /*16:9 ratio*/ /* CCR calculated by half-period of 100k * divided by the sum of the duty ratios * divided by the period of the PCLK * t_high = 9 * CCR * TPCLK * t_low = 16 * CCR * TPCLK * t_high + t_low = 25 * CCR * TPCLK * CCR = ((1/400'000)/2) / (25/42'000'000) * CCR = 1250ns/~595ns = 2.1 ~ 2 */ CCR_VAL = 2; } else { //2:1 ratio /* CCR calculated by half-period of 100k * divided by the sum of the duty ratios * divided by the period of the PCLK * t_high = CCR * TPCLK * t_low = 2 * CCR * TPCLK * t_high + t_low = 3 * CCR * TPCLK * CCR = ((1/400'000)/2) / (3/42'000'000) * CCR = 1250ns/~71ns = 17.5 ~ 17 */ CCR_VAL = 17; } /* TRISE calculated by PCLK (in MHz) * 0.3ms + 1 * TRISE = ((42'000'000/1'000'000)*(300/1000) + 1) * TRISE = 42 * 0.3 + 1 = 13.6 ~ 14 */ TRISE_VAL = 14; } /*Set Clock Control value*/ I2Cx->CCR |= CCR_VAL; //assuming APB1 = 42MHz, SM = True I2Cx->CCR |= (set(DUTY_MODE,I2C_CCR_DUTY_Pos) | set(MASTER_MODE,I2C_CCR_FS_Pos)); I2Cx->TRISE |= TRISE_VAL; //assuming APB1 = 42MHz, SM = True /*Enable I2C*/ I2Cx->CR1 |= I2C_CR1_PE; } void set_addr(I2C_TypeDef *I2Cx, uint8_t sAddr, const uint8_t *mAddr, int read) { volatile int temp; /*Check I2C is not busy*/ while(I2Cx->SR2 & I2C_SR2_BUSY); /*(1) Generate a START, wait for start flag*/ I2Cx->CR1 |= I2C_CR1_START; while (!(I2Cx->SR1 & I2C_SR1_SB)); /*Transmit slave address + 'write'*/ I2Cx->DR = sAddr << 1; /*Wait for the address flag*/ while (!(I2Cx->SR1 & I2C_SR1_ADDR)); temp = I2Cx->SR2; //Clear address flag /*Send memory address*/ //Make sure TXE is emtpy while (!(I2Cx->SR1 & I2C_SR1_TXE)); I2Cx->DR = mAddr; //Wait until transmitter is emtpy while (!(I2Cx->SR1 & I2C_SR1_TXE)); if(read) { /*(2) Generate a START, wait for start flag*/ I2Cx->CR1 |= I2C_CR1_START; while (!(I2Cx->SR1 & I2C_SR1_SB)); /*Transmit slave address + 'read'*/ I2Cx->DR = (sAddr << 1) | 1; /*Wait for the address flag*/ while (!(I2Cx->SR1 & I2C_SR1_ADDR)); temp = I2Cx->SR2; //Clear address flag } } int i2c_burst_read(I2C_TypeDef *I2Cx, uint8_t sAddr, const uint8_t *mAddr, uint8_t *string, uint32_t size) { if(size == 0) { return 0; } uint8_t *pData = string; //points at the first byte (char) of string set_addr(I2Cx, sAddr, mAddr, 1); /*Enable ACK*/ I2Cx->CR1 |= I2C_CR1_ACK; while(size > 0U) { if(size-- == 1) { /*Disable ACK*/ I2Cx->CR1 &= ~I2C_CR1_ACK; /*Wait until the receive flag is set*/ while(!(I2Cx->SR1 & I2C_SR1_RXNE)); //"Generate STOP" was here before /*Read data from the DR*/ *pData++ = I2Cx->DR; break; } else { /*Wait until the receive flag is set*/ while(!(I2Cx->SR1 & I2C_SR1_RXNE)); /*Read data from the DR*/ *pData++ = I2Cx->DR; } } /*Generate a STOP after receiving*/ I2Cx->CR1 |= I2C_CR1_STOP; return 1; } int i2c_burst_write(I2C_TypeDef *I2Cx, uint8_t sAddr, const uint8_t *mAddr, uint8_t *string, uint32_t size) { uint8_t *pData = string; //points at the first byte (char) of string set_addr(I2Cx, sAddr, mAddr, 0); while(size > 0U) { /*Wait for transmitter to be empty*/ while(!(I2Cx->SR1 & I2C_SR1_TXE)); I2Cx->DR = *pData++; size--; } /*Wait for byte transfer finished flag*/ while(!(I2Cx->SR1 & I2C_SR1_BTF)); /*Generate a STOP after writing*/ I2Cx->CR1 |= I2C_CR1_STOP; return 1; }
内容的提问来源于stack exchange,提问作者mgmussi
相关产品推荐
相关产品推荐

