STM32L011K4 with DMA using I2C Start Condition not Occurring

0

I'm attempting to use the STM32L011K4's DMA controller to communicate with slave devices over I2C. Currently, I have no slave devices and am just trying to get the microcontroller to send the start condition out onto the I2C bus, but that is not happening.

When I run this code in debugging mode through the STM32CubeIDE, I notice that the start bit is set, but it never clears even though the reference manual says it should be cleared by hardware once the start condition occurs (page 656 for I2C_CR2).

Monitoring the SDA and SCL lines on my oscilloscope also show that they are a logical 1. Note: I'm using the NUCLEO-L011K4 on a breadboard, so the IO pins are tied to Vref through 1k resistors. All configuration registers appear to contain the desired value when the code is stuck sending the start condition, so I don't believe they are getting clobbered by a random line of code.

I'm not sure what's preventing the start condition from being sent, so any help would be greatly appreciated.

STM32L011K4 Datasheet: https://www.st.com/content/ccc/resource/technical/document/datasheet/42/c0/ab/e5/71/7a/47/0b/DM00206508.pdf/files/DM00206508.pdf/jcr:content/translations/en.DM00206508.pdf

STM32L011K4 Reference Manual: https://www.st.com/resource/en/reference_manual/dm00108282-ultralowpower-stm32l0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

Initialization code:

void Init_I2C1_DMA() {
    /*  Basic I2C Initialization for 100 kHz I2C, 24 MHz SYSCLK, /1 APB1 scaler  */
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Enable peripheral clock for I2C1

    I2C1->CR1 &= ~(I2C_CR1_PE);         // Disable I2C1
    I2C1->CR1 = 0;                      // Reset CR1

    I2C1->TIMINGR = 0;                  // Reset timer settings
    /* APB1 clock (I2C1 clock) is set in RCC_CFGR reg -- keep at divide by 1 -- 24 MHz SYSCLK
     * Refer to table 103 for timing value source.  t_presc was found to be 250 ns for 100 kHz I2C, so PRESC was set to match that for SYSCLK = 24 MHz
     * All subsequent settings are copied from table 103 from STM32L011K4 reference manual
     */
    I2C1->TIMINGR |= (0x5 << 28)|(0x4 << 20)|(0x2 << 16)|(0x0F << 8)|(0x13 << 0);

    /* Desired settings:
     * RXDMAEN enable, ANF enable.
     */
    I2C1->CR1 |= (0x8 << I2C_CR1_DNF_Pos)|I2C_CR1_ERRIE;
    I2C1->CR2 = 0;                      // Reset contents (ACKs are enabled by default)
    NVIC_EnableIRQ(I2C1_IRQn);
    NVIC_SetPriority(I2C1_IRQn, 0);

    /* DMA initialization */

    /* Since this is peripheral to memory, we use I2C1_RX, which is available on DMA channels 3,7.  We used channel 3, but 7 would work the same. */
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;                           // Enable peripheral clock for DMA1
    DMA1_Channel3->CCR &= ~(0x00000001);                        // Disable Channel 3 DMA

    // Configure DMA channel mapping
    DMA1_CSELR->CSELR &= ~0x00000F00; // Channel 3 re-mapping mask
    DMA1_CSELR->CSELR |=  0x00000600; // Channel 3 re-mapped to I2C1_RX

    /* Configure NVIC for DMA */
    NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
    NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);

    return;
}

void I2C1_DMA_Start_Read(uint8_t SlaveAddress, uint8_t RegisterAddress, int* MemoryBaseAddress, int BufferSize) {
    // We need to put the device address out on the serial line before we can hand it over to the DMA

    I2C1->CR1 &= ~(I2C_CR1_PE);                                 // Disable I2C1
    DMA1_Channel3->CCR &= ~DMA_CCR_EN;                          // Disable Channel 3 DMA

    DMA1_Channel3->CCR &= ~(0x00007FFF);                        // Channel 3 DMA mask
    // Configure DMA Channel 3 for 16 bit memory and peripheral, and other aliased settings (reference manual page 249, 10.4.3)
    DMA1_Channel3->CCR |=  (0b01 << 10)|(0b01 << 8)|DMA_CCR_MINC|DMA_CCR_TEIE|DMA_CCR_TCIE;

    DMA1_Channel3->CPAR =  (uint32_t) RegisterAddress;
    DMA1_Channel3->CMAR =  (uint32_t) MemoryBaseAddress;
    DMA1_Channel3->CNDTR = (uint16_t) BufferSize;

    I2C1->CR1 &= (~I2C_CR1_TXDMAEN);                            // Disable TX DMA for I2C1
    I2C1->CR1 |= I2C_CR1_RXDMAEN;                               // Enable RX DMA for I2C1
    // I2C1->CR2 |= ((uint8_t) (SlaveAddress << 1));            // Set up the slave address for DMA read
    while(!(I2C1->ISR & I2C_ISR_TXE));
    I2C1->TXDR |= ((uint8_t) (SlaveAddress << 1));              // Set up the slave address for DMA read
    I2C1->CR2 |= I2C_CR2_RD_WRN;

    DMA1_Channel3->CCR |= DMA_CCR_EN;                           // Activate DMA channel 3
    I2C1->CR1 |= I2C_CR1_PE;                                    // Enable I2C1

    I2C1->CR2 |= I2C_CR2_START;                                 // Generate start condition
    while(I2C1->CR2 & I2C_CR2_START);                           // Wait until hardware clears the start bit

    // ???

    return;
}
c
stm32
i2c
dma
asked on Stack Overflow Nov 2, 2020 by cassini-huygens • edited Nov 2, 2020 by cassini-huygens

1 Answer

0

According with reference manual(p. 604) you need uncomment I2C1->CR2 |= ((uint8_t) (SlaveAddress << 1)); and comment I2C1->TXDR |= ((uint8_t) (SlaveAddress << 1)); for set slave address, and you need set the number of bytes to be transferred.

I can't see your initialisation of GPIO. Check is GPIO settings right (Alternative function and open-drain mode).

Also in reference manual written this

PE must be kept low during at least 3 APB clock cycles in order to perform the software reset. This is ensured by writing the following software sequence: - Write PE=0 - Check PE=0 - Write PE=1.

I think you should try to do so.

Also I advise using 4.7k resistor for pulling to VDD.

answered on Stack Overflow Nov 9, 2020 by Sergey Melnik

User contributions licensed under CC BY-SA 3.0