El periférico I2C STM32F446 no hará valer la condición de inicio repetido

2

Estoy escribiendo un firmware de nivel de registro para mi STM32F446 para usar el periférico I2C para hablar con un acelerómetro. La secuencia de eventos que necesito hacer es la siguiente

1) START
2) SLAVE ADDRESS+Write
3) SUBADDRESS (command byte to specify which register address on the peripheral i want to read from)
4) REPEAT START
5) SLAVE ADDRESS+Read
6) Collect data from slave
7) STOP

En un analizador lógico puedo ver que estoy haciendo correctamente los pasos 1-3, pero cuando necesito afirmar el inicio repetido, parece que no lo hago correctamente. Estoy configurando el bit de registro 8 (desde cero) I2C1- > CR1 con esta función:

void repeat_start_I2C1(){
  //set start bit
  I2C1->CR1 |= (0x1 << 0x8); 
}

que parece funcionar para mi primer inicio pero no está generando el inicio repetido, porque no está generando el inicio repetido mi función de lectura (utilizada en el paso número 5) se bloquea en un bucle mientras espera el bit de registro de estado SB para ir alto antes de continuar (SB va alto después de cada inicio / inicio de repetición y es necesario borrar antes de enviar la dirección de esclavo):

void address_read_I2C1(uint8_t addr){
  //wait for SB bit to be set
  while(!(I2C1->SR1 & 0x1));

  //write slave address with LSB set for receive mode
  I2C1->DR = (addr << 1) | 0x1;
}

Aquí hay una imagen de la transmisión completa a medida que se produce hasta el inicio repetido falso que se produce directamente después de 0x2D + ACK (el byte de comando de subdirección, es decir, paso 3).

Aquíhayunprimerplanodeliniciorepetidofalso

Comopuedeverenlamarcadeverificación+1us,laSCL(líneasuperior)ylaSDA(Líneainferior)bajanalmismotiempo(hastalaresolucióndelanalizadorlógico)enlugardeSDA->SCLcomonecesarioenunacondicióndeinicio(quesepuedevercorrectamenteenlaprimeraimagen.

HiceunapausaduranteladepuraciónmientrasestabaatascadoenelbuclewhileynotéqueelbitdeBTFdelEstadodeRegistro2%eraalto,loqueindicaquelatransmisiónhabíafinalizadoyestapuedeserlarazónporlaquenosepuedeafirmarqueelinicioescorrecto.Peronoentiendoelprocedimientoquedeboseguirparasolucionarloparaquepuedarepetirelinicio.

EDITAR:

unusuariosugirióquepodríaomitirunpasoparaborrarelbitdeestadodeladirecciónantesdeenviarunbytedecomando,peromeolvidédemostrarestafunción

//WritebytetoI2C1voidwrite_I2C1_byte(uint8_tcmd){uint8_ttmp;//waitforADDR=1thenclearbyreadingSR1andSR1while(!(I2C1->SR1&&0x02));tmp=I2C1->SR1;tmp=I2C1->SR2;//waitforTxE=1todenotedataregisterreadytoaccepttransmissiondatawhile(!(I2C1->SR1&&0x40));I2C1->DR=cmd;}

Estafunciónrealizaelpaso3enmilistadepasosborrandoADDRleyendolosregistrosSRyluegoenviandounbyteaDR.Compilosinoptimizacionesparaquetmpnosecortedelcódigopornoserutilizado.

EDIT2:

Unusuariopreguntóquéresistenciasdepull-upestánconfiguradasparaelgpioconectadoalperiférico,aquíestálafuncióndeinicioqueconfiguralasresistenciasdepull-upparatirardelaslíneasaltas(líneasespaciadasespaciadas):

inlinevoidinit_i2c1(){//setGPIOtocorrectpinmodesforI2C1//PB_8IC21_SCL//PB_9I2C1_SDA//enableGPIOBclock//RCC->AHB1ENR|=RCC_AHB1ENR_GPIOBEN;RCC->AHB1ENR|=1<<(1);//setGPIOBclockenablebit//enableI2C1Clock//RCC->APB1ENR|=RCC_APB1ENR_I2C1EN;RCC->APB1ENR|=1<<(21);//setI2C1clockenablebit//PB_8IC21_SCL//setthemodetoalternatefuntionGPIOB->MODER&=~(0b11<<(8*2));//clearbitfieldusing2bitmaskGPIOB->MODER|=0b10<<(8*2);//setbitfildsto0b10toenablealternatefunctionmode//setoutputmodetoopendrainsoitcanbepulledlowGPIOB->OTYPER|=(1<<(8));//setGPIOspeedGPIOB->OSPEEDR&=~(0b11<<(8*2));//clearGPIOB->OSPEEDR|=(0b10<<(8*2));//fastspeed-50MHz//******************************************************//SetPullupResistorsGPIOB->PUPDR&=~(0b11<<(2*2));//clearGPIOB->PUPDR|=(0b01<<(2*2));//pullup//******************************************************//SetAlternateFunctionI2C1usingAlternateFunctionRegisterHighGPIOB->AFR[1]&=~(0b1111<<(0*4));//clearfieldGPIOB->AFR[1]|=(0b0100<<(0*4));//setforalternatefunction4(I2C1)//PB_9I2C1_SDAGPIOB->MODER&=~(0b11<<(9*2));//clearbitfieldusing2bitmaskGPIOB->MODER|=0b10<<(9*2);//setbitfildsto0b10toenablealternatefunctionmode//setoutputmodetoopendrainsoitcanbepulledlowGPIOB->OTYPER|=(1<<(9));//setGPIOspeedGPIOB->OSPEEDR&=~(0b11<<(9*2));//clearGPIOB->OSPEEDR|=(0b10<<(9*2));//fastspeed-50MHz//******************************************************//SetPullupResistorsGPIOB->PUPDR&=~(0b11<<(2*2));//clearGPIOB->PUPDR|=(0b01<<(2*2));//pullup//******************************************************//SetAlternateFunctionI2C1usingAlternateFunctionRegisterHighGPIOB->AFR[1]&=~(0b1111<<(1*4));//clearfieldGPIOB->AFR[1]|=(0b0100<<(1*4));//setforalternatefunction4(I2C1)//setupI2C1//disableI2C1tosetupI2C1->CR1&=~(1<<0);//Peripheralenablebit//setclockfrequencyto100kHzI2C1->CR2&=~(0b111111);I2C1->CR2|=(0b001000);//setI2Cperipheralfrequencyto8lMHzI2C1->CCR&=~(1<15);//clearthe15thbittosetittoSmmode//CalculatetheClockControlBits//CCR[11:0]indecimal=((1/2)(1/targetFrequency))/(1/PeripheralFrequency)//itis1/2thetargetperiodbecausetheCRRdefinesThighorTlow(halftheperiod)//CCR=((1/2)(1/100E3))/(1/8E6)decimaltohex//CCR=0x28I2C1->CCR&=~(0b111111111111);//clearthelowest12bitsI2C1->CCR|=(0x28);//clearthelowest12bits//SetTRISE//TRiseisthemaximumrisetimedividedbytheperipheralclockperiod+1//TRISE=floor(Trise_max/Tpclk)+1//ForSmmodethemaxrisetimeis1000ns//thuswehave((1000E-9/(1/8E6))+1)=9I2C1->TRISE=0b001001;//setTRISEto9//ProgramtheI2C_CR1registertoenabletheperipheralI2C1->CR1|=(1<<0);//Peripheralenablebit}

EDIT3:

Deacuerdocon STM32F446 sección de la hoja de errata titulada 2.4.3 Mismatch on the “Setup time for a repeated Start condition” timing parameter Aparentemente hay algunos casos en los que se viola el tiempo de configuración para el inicio repetido en I2C velocidades de modo estándar entre 88-100KHz (que actualmente estoy operando desde que estoy a 100KHz). El tiempo mínimo requerido para la configuración del inicio repetido es 4.7us, pero al mirar la salida de mi analizador lógico, tengo menos de 3us cuando SCL está alto y cuando la línea SDA parece intentar un inicio repetido. Aunque este problema no se supone que ocurra si el esclavo no está estirando el reloj y el tiempo de aumento de SCL es inferior a 300 ns, lo que mis imágenes indican, así que no estoy seguro de por qué se está infringiendo la configuración.

Intentaré reducir la frecuencia por debajo de 88KHz como solución sugerida en la Errata, y le informaré.

EDIT4: reducir SCL a menos de 80KHz no hizo nada, de hecho lo empeoró. Ahora, la primera condición de inicio tiene el mismo comportamiento (fallar hacia abajo exactamente al mismo tiempo que SDA). Pero esto solo ocurre de forma intermitente, a veces funciona y otras no ...

EDIT5: RESUELTO. Los pull-ups externos son muy necesarios ... estúpida placa del núcleo ...

    
pregunta Taako

1 respuesta

3

Acabo de verificar mi código para el F401. Espero que el periférico I2C siga siendo el mismo para el F446.

Su enfoque general es una coincidencia con la mía (que funciona bien después de un gran trabajo de modificación del código desde un enfoque de interrupción, que fue horrible).

Pero tengo un paso adicional entre 2) y 3) que no has mencionado:

1) START
2) SLAVE ADDRESS+Write
--> 2.5) Clear Address Bits (read SR1 and SR2)
3) SUBADDRESS 
4) REPEAT START
5) SLAVE ADDRESS+Read
6) Collect data from slave
7) STOP

Encontré que el periférico I2C es bastante quebradizo y feo de usar. Tenga cuidado de no leer demasiado o demasiado poco de los registros, ya que la máquina de estado interna parece depender en gran medida de la secuencia correcta de la lectura y las escrituras en los registros.

Especialmente la parte sobre la lectura de los registros puede darle un infierno de depuración, ya que la lectura del depurador activará la máquina de estados como una lectura de su código, así que tan pronto como detiene e inspecciona los registros (a veces el depurador sí lo hace). todo automáticamente) la lectura sucede y el periférico hará algo nuevamente.

Si necesita conocer un valor en un cierto punto, almacénelo dentro de una variable, ya que no puede confiar en lo que muestra el depurador si se detiene en ese punto. Pero nuevamente esa lectura podría alterar el comportamiento ya. Lee el manual de referencia con mucho cuidado.

En respuesta a la edición:

//Write byte to I2C1
void write_I2C1_byte(uint8_t cmd){
  uint8_t tmp;

  //wait for ADDR=1 then clear by reading SR1 and SR1
  //// check address sent succeeded, data bytes transmitted, bus busy, and in master mode)
  //while(!( (I2C2->SR1 && 0X02) && (I2C2->SR2 && 0x07) ));
  while(!(I2C1->SR1 && 0x02));
  tmp = I2C1->SR1;
  tmp = I2C1->SR2;

  //wait for TxE=1 to denote data register ready to accept transmission data
  while(!(I2C1->SR1 && 0x40)); 
  I2C1->DR = cmd;
}

Este código contiene errores: la espera de los bits. Accidentalmente usa && en lugar de & para verificar un bit establecido.

Y comentaste que tmp no se está optimizando porque has compilado sin optimizaciones. tmp no se optimizaría ya que los registros (si no hay nada malo) se declararon volátiles, por lo tanto, el compilador no puede optimizar la lectura de SR1 o SR2.

Después de inspeccionar las formas de onda capturadas un poco más cerca, parecía que había un fallo en el SCL en la condición de reinicio. Esto podría ser simplemente una captura incorrecta o una falla real, que podría muy bien desprenderse del periférico ya que está estrechamente relacionado con lo que está sucediendo en la línea SCL.

Después de aclarar, que se utilizaron los pull-ups internos, sugerí usar resistores de pull-up externos de 10k (en un comentario).

La razón por la cual es que me he encontrado con un extraño comportamiento de los pines GPIO bajo el control de un periférico. Es decir, algunos de los pines SPI no estaban en modo de alta impedancia después de una transmisión (lo que causó problemas de EMI). Así que no confío en que el pull-up interno permanecerá siempre activo.

También podría ser que el pull-up interno no sea lo suficientemente fuerte. Normalmente oscilan entre 30k y 60k ohm, por lo que son bastante más débiles.

    
respondido por el Arsenal

Lea otras preguntas en las etiquetas