给我你的心好吗 发表于 2025-10-8 18:49:05

DMA I2C 非阻塞方式驱动 OLED

<h1>DMA I2C 非阻塞方式驱动 OLED</h1>
<hr />
<p>芯片:AI8051U。OLED:SSD1306 0.96 寸 OLED。昨日调试一天的程序,仔细看了例程,终于成功驱动 OLED。</p>
<h2>DMA触发发送逻辑固定,但手册里没有写明</h2>
<p>其他 DMA,包括串口的 DMA 发送都是直接设置发送触发位就好,而 IIC DMA 触发发送有一个<strong>逆天前摇</strong> <code>I2C_StartSendDataRecvACK()</code>(可以在 ai8051u_def.h 中看到这个宏的定义,作用是 <em>产生起始信号+发送+接收ACK</em> ),而且,更重要的是,<strong>手册里不写!!手册里不写!!手册里竟然不写!!!</strong> 老天,这是要我算命算出来吗?不看例程根本不知道。其他命令无法触发。</p>
<p>触发 DMA 发送需要设置传输数量、开启和关闭 DMA 等等,这些语句的顺序建议严格按照例程来,一句也不要少,哪怕你认为你自己是对的。尤其是 DMA 的 TxAmount 寄存器和 ST 寄存器要写成相同的数量,我之前一直以为多此一举,结果不写还真失败了。而且全过程要保持 I2C 主机中断开着。</p>
<h2>非阻塞式驱动 OLED 例程</h2>
<p>楼主用以下思路驱动 OLED:</p>
<ol>
<li>开启 I2C主机中断,开启 DMA 及其中断,接下来 1024 + 2 个数据字节通过 DMA 来发送(加的两个分别是从机地址和控制字节);</li>
<li>当 DMA 发送完成时应进入中断,在中断里失能 DMA 及其中断,并发送一个停止信号。</li>
<li>在停止信号的中断里调用整个发送过程完成的回调函数。</li>
</ol>
<p>如果 <code>OLED_ADDR</code>,<code>OLED_CMD</code> 和那 1024 个数据字节必须存放在同一个数组里,看上去比较麻烦,因为可能要修改 OLED 显示的算法。这里我想了一个办法解决这个问题:将 <code>OLED_DispGraph</code> 换成宏定义,用 <code>pu8I2CDMATxBuffer + 2</code> 代替之,这样就不用改动处理 <code>OLED_DispGraph</code> 的算法了。</p>
<blockquote>
<p>代码 1:OLED.h 中的代码</p>
</blockquote>
<pre><code class="language-C">#define OLED_DispGraph                  (pu8I2CDMATxBuffer + 2)

#define OLED_ADDR                     ((u8)0x78)

// OLED I2C sending operation.
#define OLED_I2C_Write(cmd, pdat, size)         I2C_MemWrite_Start(&amp;oled_i2c, OLED_ADDR, cmd, NULL, size)
#define OLED_I2C_BlockWrite(cmd, pdat, size)    \
{   \
    while (I2C_MemWrite_Start(&amp;oled_i2c, OLED_ADDR, cmd, pdat, size) == FALSE);   \
    while (I2C_IsSending(&amp;oled_i2c));      \
}
</code></pre>
<blockquote>
<p>代码 2:user_i2c.c 中的代码</p>
</blockquote>
<pre><code class="language-C">
#include &quot;user_i2c.h&quot;

#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-&gt;Buf = buf;
    hi2c-&gt;BufSize = bufSize;
    hi2c-&gt;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-&gt;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-&gt;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-&gt;State != I2C_State_IDLE || len == 0) return FALSE;

    if (pdat != NULL) memcpy(hi2c-&gt;Buf + 1, pdat, min(len, hi2c-&gt;BufSize));
        hi2c-&gt;Buf = 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-&gt;State != I2C_State_IDLE || len == 0) return FALSE;

    if (pdat != NULL) memcpy(hi2c-&gt;Buf + 2, pdat, min(len, hi2c-&gt;BufSize));
        hi2c-&gt;Buf = slvAddr;
        hi2c-&gt;Buf = 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-&gt;State == I2C_State_STOP)
        {
                hi2c-&gt;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-&gt;State == I2C_State_DATA)
        {
                // The DMA transmission was just over.
                // Disable DMA and enable master interrupt, then generate a stop condition.
                hi2c-&gt;State = I2C_State_STOP;
                DMA_I2C_DisableDMA();
                DMA_I2C_DisableTxInt();
                I2C_Stop();
        }
}


</code></pre>
<p>整个工程放这里了:<a href="forum.php?mod=attachment&amp;aid=116965" title="attachment"><img src="/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg" alt="upload" /> 附件:01_learn.zip</a></p>

DebugLab 发表于 7 天前

手册里怎么没写





页: [1]
查看完整版本: DMA I2C 非阻塞方式驱动 OLED