这似乎是一个有点常见的问题,但我在网上找到的任何解决方案都没有成功。具体来说,我试图通过I2C/DMA和在cubeIDE中生成的HAL将1024字节的缓冲区(完整的128x64PX映像)传输到cubeIDE显示器。我用的是STML432核子板。如果不使用DMA使用HAL_I2C_Mem_Write传输缓冲区,我没有问题。
基于我看到的其他问题,问题在于DMA完成时I2C总线仍然在传输。我只是不知道如何纠正这一点,并且给出的例子通常不使用HAL (不幸的是,尽管我做了很多努力,我还是不太有能力将它们正确地应用到HAL中,我猜)。我尝试过对I2c和DMA使用中断,但没有成功,只传输了前254个字节(在屏幕上只显示了两行)。
下面是我发送缓冲区的代码:
static void ssd1306_WriteMData_DMA(const uint8_t *data, uint16_t size)
{
while(HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);
HAL_I2C_Mem_Write_DMA(&hi2c1, I2C_ADDR, SSD1306_REG_MDAT, 1, (uint8_t*)data, size);
}
以及每个中断处理程序的代码:
void I2C1_EV_IRQHandler(void)
{
/* USER CODE BEGIN I2C1_EV_IRQn 0 */
if(I2C1->ISR & I2C_ISR_TCR){
I2C1->CR2 |= (I2C_CR2_STOP);// stop i2c
I2C1->ICR |= (I2C_ICR_STOPCF);// Reset the ICR flag.
// stop DMA
DMA1->IFCR |= DMA_IFCR_CTCIF6;
// clear flag
DMA1_Channel6->CCR &= ~DMA_CCR_EN;
}
/* USER CODE END I2C1_EV_IRQn 0 */
//HAL_I2C_EV_IRQHandler(&hi2c1);
/* USER CODE BEGIN I2C1_EV_IRQn 1 */
/* USER CODE END I2C1_EV_IRQn 1 */
}
void DMA1_Channel6_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel6_IRQn 0 */
// stop DMA
DMA1->IFCR |= DMA_IFCR_CTCIF6;
// clear flag
DMA1_Channel6->CCR &= ~DMA_CCR_EN;
/* USER CODE END DMA1_Channel6_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_i2c1_tx);
/* USER CODE BEGIN DMA1_Channel6_IRQn 1 */
/* USER CODE END DMA1_Channel6_IRQn 1 */
}
我认为这是所有相关的代码,让我知道,如果有其他的东西,我错过了。外设的所有初始化代码都是通过cubeMX完成的,但如果需要的话,我可以发布这些代码或设置。我觉得这是一件很简单的事情,我错过了,但老实说,这实在是太过分了,所以我不太明白到底发生了什么……
谢谢你的帮助!
发布于 2021-07-30 05:28:36
问题在于您的自定义DMA1_Channel6_IRQHandler
和I2C1_EV_IRQHandler
。这些函数将在I2C传输255个字节之后立即调用,这是NBYTES的MAX_NBYTE_SIZE。HAL已经在stm32l4xx_hal_i2c.c
中拥有所有必需的中断例程。
如果数据大小大于255个字节,则I2C_Master_ISR_DMA
;
I2C_DMAMasterTransmitCplt
;
HAL_DMA_Start_IT()
I2C_TransferConfig()
将I2C DMA完全回调设置为
HAL驱动程序将使用I2C_Master_ISR_DMA
和I2C_DMAMasterTransmitCplt
处理所有的I2C_Master_ISR_DMA
中断。
I2C_DMAMasterTransmitCplt
将为255 (MAX_NBYTE_SIZE)或更少的块重新启动DMA,bytes.I2C_Master_ISR_DMA
将使用I2C_TransferConfig
重置RELOAD/NBYTES寄存器。对于最后一个数据块,使用了I2C_AUTOEND_MODE
.所以你所需要的就是
在functions
DMA1_Channel6_IRQHandler
和I2C1_EV_IRQHandler
I2C1事件中断中的“用户代码”,数据宽度为byte/byteHAL_I2C_Mem_Write_DMA(...)
启动transfer请参阅HAL_I2C_Mem_Write_DMA
、I2C_Master_ISR_DMA
和stm32l4xx_hal_i2c.c
中的I2C_DMAMasterTransmitCplt
源代码,以了解其工作原理。
关于DMA为何在I2C仍在工作时结束:哈尔驱动程序使用255个字节块通过DMA发送I2C数据,停止DMA,启动DMA,清除I2C_CR2 NBYTES/重新加载,启用DMA。DMA可以使用DMA_CIRCULAR模式连续运行,但目前还没有在HAL I2C驱动程序中实现。
// DMA enabled single time
hi2c1.hdmatx->XferCpltCallback = MY_I2C_DMAMasterTransmitCplt;
HAL_DMA_Start_IT(hi2c1.hdmatx, (uint32_t)&i2cBuffer, (uint32_t)&hi2c1.Instance->TXDR, I2C_BUFFER_SIZE);
MY_I2C_TransferConfig(&hi2c1, (uint16_t)DAC_ADDR, 254, I2C_RELOAD_MODE, I2C_GENERATE_START_WRITE); // in first call using I2C_GENERATE_START_WRITE
uint32_t tmpisr = I2C_IT_TCI;
__HAL_I2C_ENABLE_IT(&hi2c1, tmpisr);
hi2c1.Instance->CR1 |= I2C_CR1_TXDMAEN;
仍然需要使用I2C_CR2每254个字节清除MY_I2C_TransferConfig
NBYTES/RELOAD (我不使用255将中断触发与数组中的偶数索引对齐):
static HAL_StatusTypeDef MY_I2C_Master_ISR_DMA(struct __I2C_HandleTypeDef *hi2c, uint32_t ITFlags, uint32_t ITSources)
{
if (__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_TCR) == SET)
{
MY_I2C_TransferConfig(&hi2c1, (uint16_t)DAC_ADDR, 254, I2C_RELOAD_MODE, I2C_NO_STARTSTOP); // in repeated calls using I2C_NO_STARTSTOP
}
return HAL_OK;
}
使用这种方法,DMA循环缓冲区大小不限于255个字节:
#define I2C_BUFFER_SIZE 1024
uint8_t i2cBuffer[I2C_BUFFER_SIZE];
Main.c应该具有MY_I2C_TransferConfig()
函数,它是从stm32l4xx_hal_i2c.c
复制的私有函数HAL_I2C_TransferConfig()
的粘贴版本。在早期的STM32微控制器上,不存在NBYTES/RELOAD字段,也不需要以这种方式更新I2C_CR2
。在循环模式下使用DMA可以实现最高的帧速率,您只需要使用XferHalfCpltCallback
和XferCpltCallback
回调及时填充DMA缓冲区。可以使用memcpy()
或DMA MEMTOMEM传输从较大的缓冲区复制帧。
发布于 2021-03-26 08:30:19
你还没说你用的是哪一种STM32。它们有不同的位定义(因为早期发布的部分中的I2C外围设备是垃圾),但是看起来您使用的是后面的一个。
基本上,您可以在参考手册中的I2C寄存器的位定义中找到所需的内容。如果您正在设置停止之前,它已经完成,您需要寻找一个繁忙的位,被清除,或BTF (字节传输完成)位,被设置时,你是时候发送停止。
https://stackoverflow.com/questions/66812977
复制相似问题