[APM32E1]

玩转APM32的DMA-用I2C的DMA实现OLED刷屏

[复制链接]
3520|6
手机看帖
扫描二维码
随时随地手机跟帖
shanyuxiang|  楼主 | 2024-5-3 18:06 | 显示全部楼层 |阅读模式
本帖最后由 shanyuxiang 于 2024-5-3 18:15 编辑

#申请原创# @21小跑堂
玩转APM32的DMA-用I2C的DMA实现OLED刷屏


一、前言

1.1、关于OLED

OLED屏是一种常见的的显示屏,下面以0.96寸OLED模块为例来实现用IIC的DMA来实现OLED屏幕的刷新,
用DMA方式不需要程序一个个字节发送,通过启动DMA自动完成整个屏幕的刷新,可以节约大量的CPU时间。
该屏幕分辨率为128x64,驱动芯片为SSD1306,采用IIC接口,只需要接三根线SCL、SDA、RES,这里IIC接到I2C1上。

时钟 SCL  -- PB6
数据 SDA -- PB7
复位 RES -- PB5

10-2.jpg


1.2、关于IIC的DMA通道

APM32E103的IIC是支持DMA的收发的,通过芯片的用户手册可知I2C1_TX的对应的是 DMA1的通道6。

1.png


二、IIC的DMA发送

2.1 IIC初始化

这里参考SDK中的“I2C\I2C_TwoBoards\I2C_TwoBoards_Master”例程即可:

void oled_i2c_hardware_init(void)
{
    GPIO_Config_T gpioConfigStruct;
    I2C_Config_T i2cConfigStruct;
    /** Enable I2C related Clock */
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB | RCM_APB2_PERIPH_AFIO);
    RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);

    /** Free I2C_SCL and I2C_SDA */
    gpioConfigStruct.mode = GPIO_MODE_AF_OD;
    gpioConfigStruct.speed = GPIO_SPEED_50MHz;
    gpioConfigStruct.pin = GPIO_PIN_6;
    GPIO_Config(GPIOB, &gpioConfigStruct);

    gpioConfigStruct.mode = GPIO_MODE_AF_OD;
    gpioConfigStruct.speed = GPIO_SPEED_50MHz;
    gpioConfigStruct.pin = GPIO_PIN_7;
    GPIO_Config(GPIOB, &gpioConfigStruct);

    /**  Config I2C1 */
    I2C_Reset(I2C1);
    i2cConfigStruct.mode = I2C_MODE_I2C;
    i2cConfigStruct.dutyCycle = I2C_DUTYCYCLE_2;
    i2cConfigStruct.ackAddress = I2C_ACK_ADDRESS_7BIT;
    //i2cConfigStruct.ownAddress1 = 0XA0;
    i2cConfigStruct.ownAddress1 = SSD1306_ADDRESS;
    i2cConfigStruct.ack = I2C_ACK_ENABLE;
    i2cConfigStruct.clockSpeed = 400000;

    I2C_Config(I2C1, &i2cConfigStruct);

    /** Enable I2Cx */
    I2C_Enable(I2C1);


    i2c_dma_init();

}


2.2 DMA初始化

在SDK中“DMA_MemoryToMemory”的基础上进行修改,官方例程中是内存到内存,而这里是从内存到IIC外设,

修改传输方向,以外设作为目的地址:
DMA_ConfigStruct.dir  = DMA_DIR_PERIPHERAL_DST;


外设的地址填 I2C1_DATA 寄存器的地址,查看用户手册可知DATA寄存器的偏移地址是0x10:
DMA_ConfigStruct.peripheralBaseAddr = (uint32_t)(I2C1_BASE + 0x10) ;

3.png

数据大小改为按字节传输:
DMA_ConfigStruct.memoryDataSize     = DMA_MEMORY_DATA_SIZE_BYTE;

完整的IIC的DMA初始化代码如下:
void i2c_dma_init(void)
{
    DMA_Config_T    DMA_ConfigStruct;
    RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);

    DMA_Reset(DMA1_Channel6);

    DMA_ConfigStruct.peripheralBaseAddr = (uint32_t)(I2C1_BASE + 0x10) ;
    DMA_ConfigStruct.memoryBaseAddr     = (uint32_t)NULL;
    DMA_ConfigStruct.dir                = DMA_DIR_PERIPHERAL_DST;
    DMA_ConfigStruct.bufferSize         = 0;
    DMA_ConfigStruct.peripheralInc      = DMA_PERIPHERAL_INC_DISABLE;
    DMA_ConfigStruct.memoryInc          = DMA_MEMORY_INC_ENABLE;
    DMA_ConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE;
    DMA_ConfigStruct.memoryDataSize     = DMA_MEMORY_DATA_SIZE_BYTE;
    DMA_ConfigStruct.loopMode = DMA_MODE_NORMAL;
    DMA_ConfigStruct.priority = DMA_PRIORITY_HIGH;
    DMA_ConfigStruct.M2M      = DMA_M2MEN_DISABLE;

    DMA_Config(DMA1_Channel6, &DMA_ConfigStruct);

    I2C_EnableDMA(I2C1);

}


2.3 用IIC的DMA发送

在启动DMA的传输之前,要配置源数据内存地址,传输长度,然后使能传输:
void i2c_dma_transmit_buffer(unsigned char *buffer, unsigned int length)
{

    DMA_Disable(DMA1_Channel6);
    DMA1_Channel6->CHMADDR = (uint32_t)buffer;
    DMA1_Channel6->CHNDATA = length;
    DMA_Enable(DMA1_Channel6);
    while (DMA_ReadStatusFlag(DMA1_FLAG_TC6) == RESET);

}


三、OLED的驱动

3.1修改OLED地址模式

OLED的默认是页地址模式,每写入一行都要设置一下坐标,非常不适合这里的DMA方式刷屏,

4.png


改为水平地址模式好些,这样就只用发送显示的内容,不用发送额外的数据,对于128x64的屏幕来说只要发送128x64/8=1024字节即可。

查看SSD1306DE 的数据手册可知,把0x20地址的值写成0x00就是水平地址模式。

2.png

    oled_i2c_wr_byte(0x20, OLED_CMD); //-Set Page Addressing Mode
    oled_i2c_wr_byte(0x00, OLED_CMD); //00b, Horizontal Addressing Mode


3.2 实现OLED刷全屏

在发送显示数据之前还是先发一个从机设备地址0x78,再发一个写数据指令0x40,

接着以DMA方式发送1024字节的显示数据,这样就完成了整个屏幕的刷新:

void oled_i2c_write_buffer(unsigned char *buffer, unsigned int length)
{

    I2C_EnableGenerateStart(I2C1);
    while (!I2C_ReadEventStatus(I2C1, I2C_EVENT_MASTER_MODE_SELECT));  //EV5

    I2C_Tx7BitAddress(I2C1, SSD1306_ADDRESS, I2C_DIRECTION_TX);
    while (!I2C_ReadEventStatus(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //EV6

    I2C_TxData(I2C1, 0x40);
    while (!I2C_ReadEventStatus(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));  //EV8

    i2c_dma_transmit_buffer(buffer, length);  //DMA Transmit

}



四、效果演示

为了测试IIC的DMA刷屏,用GIF转了个12帧的位图,在循环中依次调用全屏刷新函数,可以看到动画效果。
int main(void)
{

        oled_init();

        while(1)
        {

      oled_refresh(&mario1[BMP_OFFSET]);
            oled_refresh(&mario2[BMP_OFFSET]);
            oled_refresh(&mario3[BMP_OFFSET]);
                  oled_refresh(&mario4[BMP_OFFSET]);
                  oled_refresh(&mario5[BMP_OFFSET]);
                  oled_refresh(&mario6[BMP_OFFSET]);
                  oled_refresh(&mario7[BMP_OFFSET]);
                  oled_refresh(&mario8[BMP_OFFSET]);
                  oled_refresh(&mario9[BMP_OFFSET]);
                  oled_refresh(&mario10[BMP_OFFSET]);
                  oled_refresh(&mario11[BMP_OFFSET]);
                  oled_refresh(&mario12[BMP_OFFSET]);
   
        }
}


位图的取模可以用工具 PCtoLCD2003,取模方式按如下设置:

5.png


最后是OLED刷屏的实际效果(加载的图片较大,需要稍等一会...):

11.gif


APM32E10x_SDK_V1.1_oled_i2c_dma.rar (251.89 KB)

使用特权

评论回复
caigang13| | 2024-5-4 08:43 | 显示全部楼层
用DMA可以提高CPU利用效率,本来IIC通信效率就低了。

使用特权

评论回复
shanyuxiang|  楼主 | 2024-5-4 15:19 | 显示全部楼层
caigang13 发表于 2024-5-4 08:43
用DMA可以提高CPU利用效率,本来IIC通信效率就低了。

IIC速度确实不高 用DMA的话在传输过程中CPU可以做其他事

使用特权

评论回复
chenjun89| | 2024-5-5 20:11 | 显示全部楼层
用DMA可以弥补一下IIC的劣势

使用特权

评论回复
21小跑堂| | 2024-5-13 14:28 | 显示全部楼层
halo 大佬,根据我们的审核标准,您目前的文章还没满800字哦~您可以补充内容后再次@21小跑堂进行审核~

使用特权

评论回复
shanyuxiang|  楼主 | 2024-5-15 09:19 | 显示全部楼层
21小跑堂 发表于 2024-5-13 14:28
halo 大佬,根据我们的审核标准,您目前的文章还没满800字哦~您可以补充内容后再次@21小跑堂进行审核~ ...

可以自己查看字数吗?

使用特权

评论回复
WoodData| | 2024-5-16 15:40 | 显示全部楼层
学习学习

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

16

帖子

1

粉丝