1

I'm trying to change a library for STM32F407 to include DMA transfers when using I2C. I'm using it do drive an OLED screen. In its original form it is working w/o problems. In the comments, somebody added DMA, but also ported it to STM32F10 and I'm trying to port it back to F407.

My problem is, after enabling DMA transfer, debugger stops working (at exactly that line) - debugger activity LED stops / turns off and debugger stays at next statement. After some more testing (blinking a led at certain events to see if they happen) I found out that code actually continues to a certain point (specifically, next time when DMA transfer is needed - in second call to update screen). After that, program doesn't continue (LED doesn't turn ON if set ON after that statement).

The weird thing is, I know the transfer is working because the screen gets a few characters written on it. That only happens if I don't debug step by step because CPU writes new data to screen buffer in the mean time and changes content of it before it is entirely sent to the screen by DMA (I will figure out how to fix that later - probably dual buffer, but it shouldn't interfere with DMA transfer anyway). However if I debug step by step, DMA finishes before CPU writes new content to screen buffer and screen is black (as it should be as buffer is first cleared). For testing, I removed the first call to DMA (after the clearing of buffer) and let the program write the text intended into buffer. It displays without any anomalies, so that means DMA must have finished, but something happened after. I simply can't explain why debugger stops working if DMA finishes the transfer. I tried blinking a led in transfer finished interrupt handler of DMA but it never blinks, that means it is never fired. I would appreciate any help as I'm at a loss (been debugging for a few days now). Thank you!

Here is relevant part of code (I have omitted rest of the code because there is a lot of it, but if required I can post). The code works without DMA (with ordinary I2C transfers), it only breaks with DMA.

// TM_STM32F4_I2C.h

typedef struct DMA_Data
{
    DMA_Stream_TypeDef* DMAy_Streamx;
    uint32_t feif;
    uint32_t dmeif;
    uint32_t teif;
    uint32_t htif;
    uint32_t tcif;
} DMA_Data;

//...

// TM_STM32F4_I2C.c

void TM_I2C_Init(I2C_TypeDef* I2Cx, uint32_t clockSpeed) {
    I2C_InitTypeDef I2C_InitStruct;

    /* Enable clock */
    RCC->APB1ENR |= RCC_APB1ENR_I2C3EN;

    /* Enable pins */
    TM_GPIO_InitAlternate(GPIOA, GPIO_PIN_8, TM_GPIO_OType_OD, TM_GPIO_PuPd_UP, TM_GPIO_Speed_Medium, GPIO_AF_I2C3);
    TM_GPIO_InitAlternate(GPIOC, GPIO_PIN_9, TM_GPIO_OType_OD, TM_GPIO_PuPd_UP, TM_GPIO_Speed_Medium, GPIO_AF_I2C3);

    /* Check clock, set the lowest clock your devices support on the same I2C bus */
    if (clockSpeed < TM_I2C_INT_Clocks[2]) {
        TM_I2C_INT_Clocks[2] = clockSpeed;
    }

    /* Set values */
    I2C_InitStruct.I2C_ClockSpeed = TM_I2C_INT_Clocks[2];
    I2C_InitStruct.I2C_AcknowledgedAddress = TM_I2C3_ACKNOWLEDGED_ADDRESS;
    I2C_InitStruct.I2C_Mode = TM_I2C3_MODE;
    I2C_InitStruct.I2C_OwnAddress1 = TM_I2C3_OWN_ADDRESS;
    I2C_InitStruct.I2C_Ack = TM_I2C3_ACK;
    I2C_InitStruct.I2C_DutyCycle = TM_I2C3_DUTY_CYCLE;

    /* Disable I2C first */
    I2Cx->CR1 &= ~I2C_CR1_PE;

    /* Initialize I2C */
    I2C_Init(I2Cx, &I2C_InitStruct);

    /* Enable I2C */
    I2Cx->CR1 |= I2C_CR1_PE;
}

int16_t TM_I2C_WriteMultiDMA(DMA_Data* dmaData, I2C_TypeDef* I2Cx, uint8_t address, uint8_t reg, uint16_t len)
{
    int16_t ok = 0;
    // If DMA is already enabled, wait for it to complete first.
    // Interrupt will disable this after transmission is complete.
    TM_I2C_Timeout = 10000000;
    // TODO: Is this I2C check ok?
    while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) && !I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE) && DMA_GetCmdStatus(dmaData->DMAy_Streamx) && TM_I2C_Timeout)
    {
        if (--TM_I2C_Timeout == 0)
        {
            return -1;
        }
    }
    //Set amount of bytes to transfer
    DMA_Cmd(dmaData->DMAy_Streamx, DISABLE); //should already be disabled at this point
    DMA_SetCurrDataCounter(dmaData->DMAy_Streamx, len);
    DMA_ClearFlag(dmaData->DMAy_Streamx, dmaData->feif | dmaData->dmeif | dmaData->teif | dmaData->htif | dmaData->tcif);   // Clear dma flags
    DMA_Cmd(dmaData->DMAy_Streamx, ENABLE); // enable DMA
    //Send I2C start
    ok = TM_I2C_Start(I2Cx, address, I2C_TRANSMITTER_MODE, I2C_ACK_DISABLE);
    //Send register to write to
    TM_I2C_WriteData(I2Cx, reg);
    //Start DMA transmission, interrupt will handle transmit complete.
    I2C_DMACmd(I2Cx, ENABLE);
    return ok;
}

//...

// TM_STM32F4_SSD1306.h

#define SSD1306_I2C             I2C3
#define SSD1306_I2Cx            3
#define SSD1306_DMA_STREAM      DMA1_Stream4
#define SSD1306_DMA_FEIF        DMA_FLAG_FEIF4
#define SSD1306_DMA_DMEIF       DMA_FLAG_DMEIF4
#define SSD1306_DMA_TEIF        DMA_FLAG_TEIF4
#define SSD1306_DMA_HTIF        DMA_FLAG_HTIF4
#define SSD1306_DMA_TCIF        DMA_FLAG_TCIF4

static DMA_Data ssd1306_dma_data = { SSD1306_DMA_STREAM, SSD1306_DMA_FEIF, SSD1306_DMA_DMEIF, SSD1306_DMA_TEIF, SSD1306_DMA_HTIF, SSD1306_DMA_TCIF };

#define SSD1306_I2C_ADDR         0x78

//...

// TM_STM32F4_SSD1306.c

void TM_SSD1306_initDMA(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Stream4);
    DMA_Cmd(DMA1_Stream4, DISABLE);

    //Configure DMA controller channel 3, I2C TX channel.
    DMA_StructInit(&DMA_InitStructure);                                 // Load defaults
    DMA_InitStructure.DMA_Channel = DMA_Channel_3;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(I2C3->DR)); // I2C3 data register address
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)SSD1306_Buffer;   // Display buffer address
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;             // DMA from mem to periph
    DMA_InitStructure.DMA_BufferSize = 1024;                            // Is set later in transmit function
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;    // Do not increment peripheral address
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;             // Do increment memory address
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                       // DMA one shot, no circular.
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;               // Tweak if interfering with other dma actions
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream4, &DMA_InitStructure);
    DMA_ITConfig(DMA1_Stream4, DMA_IT_TC, ENABLE);                      // Enable transmit complete interrupt
    DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TC);

    // Set interrupt controller for DMA
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;             // I2C3 TX connect to stream 4 of DMA1
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x05;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x05;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // Set interrupt controller for I2C
    NVIC_InitStructure.NVIC_IRQChannel = I2C3_EV_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    I2C_ITConfig(I2C3, I2C_IT_BTF, ENABLE);
}

extern void DMA1_Channel3_IRQHandler(void)
{
    //I2C3 DMA transmit completed
    if (DMA_GetITStatus(DMA1_Stream4, DMA_IT_TC) != RESET)
    {
        // Stop DMA, clear interrupt
        DMA_Cmd(DMA1_Stream4, DISABLE);
        DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TC);
        I2C_DMACmd(SSD1306_I2C, DISABLE);
    }
}
// Sending stop condition to I2C in separate handler necessary
// because DMA can finish before I2C finishes
// transmitting and last byte is not sent
extern void I2C3_EV_IRQHandler(void)
{
    if (I2C_GetITStatus(I2C3, I2C_IT_BTF) != RESET)
    {
        TM_I2C_Stop(SSD1306_I2C); // send i2c stop
        I2C_ClearITPendingBit(I2C3, I2C_IT_BTF);
    }
}

// ...

void TM_SSD1306_UpdateScreen(void) {
    TM_I2C_WriteMultiDMA(&ssd1306_dma_data, SSD1306_I2C, SSD1306_I2C_ADDR, 0x40, 1024); // Use DMA
}

edit: i noticed the wrong condition checking at initializing a new transfer, but fixing it doesn't fix the main problem

while ((I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) || !I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE) || DMA_GetCmdStatus(dmaData->DMAy_Streamx)) && TM_I2C_Timeout)
Terraviper-5
  • 61
  • 2
  • 7
  • As an experiment, try adjusting the number of bytes transferred per block by the DMA. The DMA may be blocking the data bus so that the debugger can't fetch data (during the transfer). You may want to transfer in smaller blocks to allow the CPU and other entities to share the data bus. – Thomas Matthews Aug 29 '16 at 23:46
  • BTW, your issue probably has nothing to do with the C++ language. I fairly certain the issue can be created using other languages. – Thomas Matthews Aug 29 '16 at 23:47
  • @ThomasMatthews I'm sorry for my lack of knowledge but which setting do you mean? I think though that DMA must wait until I2C can swallow next byte and I2C is only working at 400kHz. Also, I read here http://www.embedds.com/using-direct-memory-access-dma-in-stm23-projects/ "DMA doesn’t occupy 100% of bus time. DMA takes 5 AHB bus cycles for single word transfer between memory – three of them are still left for CPU access. This means that DMA only takes maximum 40% of buss time. So even if DMA is doing intense data transfer CPU can access any memory area, peripheral at any time. " – Terraviper-5 Aug 30 '16 at 00:14
  • On some DMA chips, you can control how many bytes are transferred in a "block". For example, if the setting is 64, the DMA will transfer 64 bytes before releasing the data bus. It will release the bus then transfer another 64 bytes. We've had issues where an LCD driver's DMA was causing delays through the system because of the bus control issue. – Thomas Matthews Aug 30 '16 at 15:55
  • Did you get this resolved? I'm trying to get an STM32F427 to perform I2C3 DMA transfers using routines that previously worked for STM32F407 with I2C1 ... My I2C works manually, but hangs with DMA. – Tut Dec 05 '16 at 16:00
  • @Tut Yes, be very very careful when rewriting code for other board as many things in ST library can be different, i.e. F10 used channels directly while F407 used them through streams, and interrupt handlers have different names, settings for structs are different, names of interrupt flags and how you reset them, ... If you write something wrong there is very high chance there will be no error/warning but program won't work correctly (exactly what happened to me). When I found documentation for F407 ST library I went through my code and fixed many mistakes I made but didn't realize it before. – Terraviper-5 Dec 05 '16 at 19:17

0 Answers0