使用硬件 I2C + DMA 操作液晶屏 (STM32)

由于项目不允许阻塞的液晶屏 I/O 操作,因此需要使用硬件 I2C + DMA 来进行。其实中断方式也可以,不过中断的时候依旧要占用 CPU 周期,不如 DMA 来的实在。再说以前也没用过 DMA,正好有个机会不妨试试看。

参考微雪课堂的文章:http://www.waveshare.net/study/article-645-1.html

微雪用的是硬件 USART + DMA,不过道理是一样的嘛,拿来参考就是。

首先把同步阻塞式程序写好,确认液晶屏可以用了。直接上异步非阻塞的方式怕是屏不亮都搞不清是通讯错了还是指令错了……

然后在Cube中开启 I2C2 的 DMA,在 Configuration 中打开 I2C 配置窗口,添加 DMA Request,分为 I2C_TX 和 I2C_RX 两种 Request,这里只需要发送的。参数的话,优先级适当调整一下,其他默认就可以了,具体参数的含义看手册或微雪的那篇文章。

一定记得打开 I2C 的中断,否则你只能进行一次不超过 256 Bytes 的 DMA,之后就得手动去清空标志位,再手动启动一次 DMA 发送。

现在可以重新生成代码了,回到代码,不考虑错误处理的话,把原来的 I2C 函数换成 DMA 版本,就可以直接使用了,是不是很方便(笑——)

另外,还建议开启 I2C 的错误中断。因为是异步操作嘛,启动 DMA 的时候只会告诉你设备是否空闲,然后 CPU 就干别的去了。至于传输是否成功,有没有错误,是不知道的,只有在错误发生之后,通过中断的方式调用你注册的回掉函数来报告错误。

(=======================补充=============================)

在 stm32l4xx_hal_i2c.c 中可以找到如下函数定义:

/**
  * @brief  Memory Tx Transfer completed callback.
  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
  *                the configuration information for the specified I2C.
  * @retval None
  */
__weak void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hi2c);

  /* NOTE : This function should not be modified, when the callback is needed,
            the HAL_I2C_MemTxCpltCallback could be implemented in the user file
   */
}

这个就是 HAL_I2C_Mem_Write_DMA 和 HAL_I2C_Mem_Write_IT 的回调函数,当之前 I2C 上的异步 IO 操作完成后,系统将调用这个函数来告诉你上一次操作已完成。同理,异步操作发生错误时的回掉函数也在此处。

需要注意的是,这里的 HAL_I2C_MemTxCpltCallback 的回调函数和 I2C2_EV_IRQHandler 的中断服务函数意义有所不同,前者在函数封装上要更高一层。

当调用 HAL_I2C_Mem_Write_DMA 这个函数时,是调用的 HAL 的接口,并将 CPU 交给 HAL,然后由 HAL 配置外设,并启动 DMA 传输,然后释放 CPU。一次 DMA 传输结束后,中断触发 I2C2_EV_IRQHandler,这个函数检查具体的中断事件,然后把 CPU 移交给 HAL。HAL 判断本次 DMA 是否传完了用户提交的全部数据,如果未传完,则启动下一次 DMA;如果已传完,则调用 HAL_I2C_MemTxCpltCallback, 将 CPU 交给用户,代表 HAL 已经执行完这次的数据发送请求。

即,一次 HAL_I2C_Mem_Write_DMA (或者_IT)只会触发一次 HAL_I2C_MemTxCpltCallback,但视长度而定,可能会多次触发 I2C 中断和 DMA 中断。

嗯,没好好读 Cube 的手册,这问题头疼了好久。

另外还有一点就是,要注意同步问题。像以下代码是非常危险的:

void sendSomething() {
    char ch[] = "LoveLive!"; // 新建临时变量(懒的打成那样子了,意会意会)
    HAL_I2C_Mem_Write_DMA(&hi2c2, 0x78, 0x80, 1, &ch, 10); // 启动了一次发送
} // 撤销了临时变量

想想看?由于变量已经被释放了,如果非常不幸你的程序恰好在这段内存里写了新的内容,I2C会发出去什么东西?

如果使用指针的话,我该什么时候释放?我该释放哪一个指针?这样做会不会内存泄露?

所以说,还挺折腾的。不过好在暂时还不用考虑这么多,等之后慢慢来吧~

OK,接下来该研究单片机的字库问题了~☆彡

“使用硬件 I2C + DMA 操作液晶屏 (STM32)”的一个回复

发表评论