为什么这个DMA I2C传输会锁定执行?

时间:2016-08-29 23:41:10

标签: debugging i2c dma stm32f4discovery

我正在尝试将STM32F407的library更改为在使用I2C时包含DMA传输。我正在使用它来驱动OLED屏幕。它的原始形式是没有问题的。在评论中,有人添加了DMA,但也将其移植到STM32F10,我正试图将其移植回F407。

我的问题是,在启用DMA传输后,调试器停止工作(正好在该行) - 调试器活动LED停止/关闭,调试器停留在下一个语句。 经过一些更多的测试(在特定事件中闪烁led以查看它们是否发生)我发现代码实际上持续到某一点(具体来说,下次需要DMA传输时 - 在第二次调用更新屏幕时)。之后,程序不会继续(如果在该语句后设置为ON,则LED不会打开)。

奇怪的是,我知道转移是有效的,因为屏幕上写了几个字符。只有当我不逐步调试时才会发生这种情况,因为CPU会同时将新数据写入屏幕缓冲区并在通过DMA将其完全发送到屏幕之前更改其内容(我将在以后找出解决方法 - 可能是双缓冲区,但它不应该干扰DMA传输)。但是,如果我逐步调试,DMA会在CPU将新内容写入屏幕缓冲区并且屏幕为黑色之前完成(因为它应该是首先清除缓冲区)。为了测试,我删除了对DMA的第一次调用(在清除缓冲区之后)并让程序将预期的文本写入缓冲区。它显示没有任何异常,因此这意味着DMA必须已经完成,但之后发生了一些事情。我无法解释为什么调试器在DMA完成传输时停止工作。 我尝试在DMA的传输完成中断处理程序中闪烁,但它从不闪烁,这意味着它永远不会被触发。我很感激任何帮助,因为我不知所措(现在已经调试了几天)。 谢谢!

这是代码的相关部分(我已经省略了其余代码,因为它有很多,但如果需要我可以发布)。代码在没有DMA的情况下工作(使用普通的I2C传输),它只能在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
}

编辑:我注意到在初始化新转移时检查错误的条件,但修复它并不能解决主要问题

while ((I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) || !I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE) || DMA_GetCmdStatus(dmaData->DMAy_Streamx)) && TM_I2C_Timeout)

0 个答案:

没有答案