DMA I2C 非阻塞方式驱动 OLED
芯片:AI8051U。OLED:SSD1306 0.96 寸 OLED。昨日调试一天的程序,仔细看了例程,终于成功驱动 OLED。
DMA触发发送逻辑固定,但手册里没有写明
其他 DMA,包括串口的 DMA 发送都是直接设置发送触发位就好,而 IIC DMA 触发发送有一个逆天前摇 I2C_StartSendDataRecvACK()
(可以在 ai8051u_def.h 中看到这个宏的定义,作用是 产生起始信号+发送+接收ACK ),而且,更重要的是,手册里不写!!手册里不写!!手册里竟然不写!!! 老天,这是要我算命算出来吗?不看例程根本不知道。其他命令无法触发。
触发 DMA 发送需要设置传输数量、开启和关闭 DMA 等等,这些语句的顺序建议严格按照例程来,一句也不要少,哪怕你认为你自己是对的。尤其是 DMA 的 TxAmount 寄存器和 ST 寄存器要写成相同的数量,我之前一直以为多此一举,结果不写还真失败了。而且全过程要保持 I2C 主机中断开着。
非阻塞式驱动 OLED 例程
楼主用以下思路驱动 OLED:
- 开启 I2C主机中断,开启 DMA 及其中断,接下来 1024 + 2 个数据字节通过 DMA 来发送(加的两个分别是从机地址和控制字节);
- 当 DMA 发送完成时应进入中断,在中断里失能 DMA 及其中断,并发送一个停止信号。
- 在停止信号的中断里调用整个发送过程完成的回调函数。
如果 OLED_ADDR
,OLED_CMD
和那 1024 个数据字节必须存放在同一个数组里,看上去比较麻烦,因为可能要修改 OLED 显示的算法。这里我想了一个办法解决这个问题:将 OLED_DispGraph[1024]
换成宏定义,用 pu8I2CDMATxBuffer + 2
代替之,这样就不用改动处理 OLED_DispGraph
的算法了。
代码 1:OLED.h 中的代码
#define OLED_DispGraph (pu8I2CDMATxBuffer + 2)
#define OLED_ADDR ((u8)0x78)
// OLED I2C sending operation.
#define OLED_I2C_Write(cmd, pdat, size) I2C_MemWrite_Start(&oled_i2c, OLED_ADDR, cmd, NULL, size)
#define OLED_I2C_BlockWrite(cmd, pdat, size) \
{ \
while (I2C_MemWrite_Start(&oled_i2c, OLED_ADDR, cmd, pdat, size) == FALSE); \
while (I2C_IsSending(&oled_i2c)); \
}
代码 2:user_i2c.c 中的代码
#include "user_i2c.h"
#define I2C_State_IDLE 0
#define I2C_State_DATA 1
#define I2C_State_STOP 2
#define I2C_Mode_MEMWRITE 1
#define I2C_Mode_SEND 2
#define I2CMSAUX_WDTA_MSK BIT0
#define I2C_EnableAutoSend() (SET_REG_BIT(I2CMSAUX, I2CMSAUX_WDTA_MSK))
I2C_Send_Cplt_Callback_t i2c_send_cplt_callback = NULL;
void I2C_Handle_Init(I2C_Handle_t *hi2c, u8 *buf, u16 bufSize)
{
hi2c->Buf = buf;
hi2c->BufSize = bufSize;
hi2c->State = I2C_State_IDLE;
}
void I2C_Set_CpltCallback(I2C_Send_Cplt_Callback_t callback)
{
i2c_send_cplt_callback = callback;
}
BOOL I2C_IsSending(I2C_Handle_t *hi2c)
{
return (BOOL)(hi2c->State);
}
static void I2C_SendStart(I2C_Handle_t *hi2c)
{
// I2C_DisableMasterInt(); // The interrupt must be open
// I2C_DisableAutoSend(); // this is irrelational
DMA_I2C_ClearTxFlag();
hi2c->State = I2C_State_DATA;
I2C_StartSendDataRecvACK(); // Only this cmd can start the transmission
DMA_I2C_EnableTxInt();
DMA_I2C_EnableDMA(); // Set data amount is allowed when dma is off or on
DMA_I2C_EnableTx(); // Set data amount is allowed only when dma tx is off, either tx off or dma off
DMA_I2C_TriggerTx();
}
/**
* @brief I2C sending function.
*
* @param hi2c the handle
* @param slvAddr address of slave device
* @param pdat if translate data form other places (not the buffer), then put the addr;
* if translate data of the buffer of the handle, put NULL.
* @param len length tend to transfer
* @return BOOL if success
*/
BOOL I2C_Send_Start(I2C_Handle_t *hi2c, u8 slvAddr, u8 *pdat, u16 len)
{
if (hi2c->State != I2C_State_IDLE || len == 0) return FALSE;
if (pdat != NULL) memcpy(hi2c->Buf + 1, pdat, min(len, hi2c->BufSize));
hi2c->Buf[0] = slvAddr;
DMA_I2C_DisableDMA();
DMA_I2C_SetTxAmount(len);
DMA_I2C_SetDMAAmount(len); // must set the same with TxAmount
I2C_SendStart(hi2c);
return TRUE;
}
/**
* @brief I2C memory write function.
*
* @param hi2c the handle
* @param slvAddr address of slave device
* @param memAddr memory address
* @param pdat if translate data form other places (not the buffer), then put the addr;
* if translate data of the buffer of the handle, put NULL.
* @param len length tend to transfer
* @return BOOL BOOL if success
*/
BOOL I2C_MemWrite_Start(I2C_Handle_t *hi2c, u8 slvAddr, u8 memAddr, u8 *pdat, u16 len)
{
if (hi2c->State != I2C_State_IDLE || len == 0) return FALSE;
if (pdat != NULL) memcpy(hi2c->Buf + 2, pdat, min(len, hi2c->BufSize));
hi2c->Buf[0] = slvAddr;
hi2c->Buf[1] = memAddr;
DMA_I2C_DisableDMA();
DMA_I2C_SetTxAmount(len + 1);
DMA_I2C_SetDMAAmount(len + 1); // must set the same with TxAmount
I2C_SendStart(hi2c);
return TRUE;
}
void I2C_IT_Handler(I2C_Handle_t *hi2c)
{
if (hi2c->State == I2C_State_STOP)
{
hi2c->State = I2C_State_IDLE;
if (i2c_send_cplt_callback) i2c_send_cplt_callback(hi2c);
}
}
void I2C_DMA_IT_Handler(I2C_Handle_t *hi2c)
{
if (hi2c->State == I2C_State_DATA)
{
// The DMA transmission was just over.
// Disable DMA and enable master interrupt, then generate a stop condition.
hi2c->State = I2C_State_STOP;
DMA_I2C_DisableDMA();
DMA_I2C_DisableTxInt();
I2C_Stop();
}
}
整个工程放这里了:
附件:01_learn.zip