【玩转APM32的DMA】手把手教你用I2C的DMA实现OLED刷屏

原创 嵌入式ARM 2024-10-30 12:01
一、前言

1.1、关于OLED

OLED屏是一种常见的显示屏,下面以0.96寸OLED模块为例,用IIC的DMA来实现OLED屏幕的刷新。

采用DMA方式不需要程序一个个字节发送,通过启动DMA自动完成整个屏幕的刷新,可以节约大量的CPU时间。

该屏幕分辨率为128x64,每个点占用1bit,于是整个显存占用128x64/8=1024Byte,驱动芯片为SSD1306,支持SPI和IIC接口。

这里采用IIC接口,只需要接三根线SCL、SDA、RES,这里IIC接到I2C1上。RES是复位可以用硬件复位,也可以通过IO控制复位,这里随便接一个IO即可。

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

显存中的第一个字节表示第1列的第1到8行这8个点,也就是坐标为X[0],Y[0-7]的点,整个显存如下图所示:



1.2、关于IIC的DMA通道

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


二、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) ;


数据大小改为按字节传输:
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的传输之前,要配置源数据内存地址,传输长度,然后使能传输,使能传输之后CPU可以做其他事情,也可以等待传输完成:

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的默认是页地址模式,每写入一行都要设置一下坐标。这样的话,传输给OLED的数据就多了很多命令和地址,非常不适合这里的DMA方式刷屏。

而理想的方式是只发一次地址,驱动芯片内部能对地址自增,这样就可以一次性发送所有显存中的数据,这里修改一下OLED的地址模式就可以实现。

官方例程默认是页地址模式,每次换行显示时需要重新发送地址,改为垂直地址模式就不用每次都发送地址,省去了额外的数据。

对于128x64的屏幕来说,只要发送128x64/8=1024字节即可。

查看SSD1306的的数据手册可知,地址模式默认是10b,把地址为0x20的寄存器值写成00b就是水平地址模式。


这样配置驱动芯片SSD1306:

void oled_register_config(){    oled_i2c_wr_byte(0xAE, OLED_CMD);
oled_i2c_wr_byte(0xAE, OLED_CMD); //--turn off oled panel oled_i2c_wr_byte(0x00, OLED_CMD); //---set low column address oled_i2c_wr_byte(0x10, OLED_CMD); //---set high column address oled_i2c_wr_byte(0x40, OLED_CMD); //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) oled_i2c_wr_byte(0x81, OLED_CMD); //--set contrast control register oled_i2c_wr_byte(0xCF, OLED_CMD); // Set SEG Output Current Brightness oled_i2c_wr_byte(0xA0, OLED_CMD); //oled_i2c_wr_byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常 oled_i2c_wr_byte(0xC0, OLED_CMD); //oled_i2c_wr_byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常 oled_i2c_wr_byte(0xA6, OLED_CMD); //--set normal display oled_i2c_wr_byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64) oled_i2c_wr_byte(0x3f, OLED_CMD); //--1/64 duty oled_i2c_wr_byte(0xD3, OLED_CMD); //-set display offset Shift Mapping RAM Counter (0x00~0x3F) oled_i2c_wr_byte(0x00, OLED_CMD); //-not offset oled_i2c_wr_byte(0xd5, OLED_CMD); //--set display clock divide ratio/oscillator frequency oled_i2c_wr_byte(0x80, OLED_CMD); //--set divide ratio, Set Clock as 100 Frames/Sec oled_i2c_wr_byte(0xD9, OLED_CMD); //--set pre-charge period oled_i2c_wr_byte(0xF1, OLED_CMD); //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock oled_i2c_wr_byte(0xDA, OLED_CMD); //--set com pins hardware configuration oled_i2c_wr_byte(0x12, OLED_CMD); oled_i2c_wr_byte(0xDB, OLED_CMD); //--set vcomh oled_i2c_wr_byte(0x40, OLED_CMD); //Set VCOM Deselect Level #if 1 oled_i2c_wr_byte(0x20, OLED_CMD); //-Set Page Addressing Mode (0x00/0x01/0x02) 设置地址模式 oled_i2c_wr_byte(0x00, OLED_CMD); //00b, Horizontal Addressing Mode 水平地址模式 #else oled_i2c_wr_byte(0x20, OLED_CMD); //-Set Page Addressing Mode (0x00/0x01/0x02) oled_i2c_wr_byte(0x02, OLED_CMD); //10b, Page Addressing Mode (RESET) #endif oled_i2c_wr_byte(0x8D, OLED_CMD); //--set Charge Pump enable/disable oled_i2c_wr_byte(0x14, OLED_CMD); //--set(0x10) disable oled_i2c_wr_byte(0xA4, OLED_CMD); // Disable Entire Display On (0xa4/0xa5) oled_i2c_wr_byte(0xA6, OLED_CMD); // Disable Inverse Display On (0xa6/a7) oled_i2c_wr_byte(0xAF, OLED_CMD); //--turn on oled panel
oled_i2c_wr_byte(0xAF, OLED_CMD); /*display ON*/}


3.2、实现OLED刷全屏

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

接着,以DMA方式发送1024字节的显示数据,这样就完成了整个屏幕的刷新,刷新过程中不需要CPU的干预:

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
}


//刷新整个屏幕void oled_refresh(unsigned char mem[]){    oled_i2c_write_buffer(mem, OLED_MEM_SIZE);
oled_delay_ms(200);}


四、效果演示

为了测试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,取模方式按如下设置。这里需要注意选择行列式,低位在前,输出格式调成数组即可:


在while(1)循环中依次将每张图片作为显存发送给OLED,每张图片之间配合一定的延时,这样就形成了动画效果:

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]);           }


最后是OLED显示马里奥跑步的动画效果:


以上就是今天分享的所有内容了,如果有需要查看原图、代码的小伙伴,请点击底部“阅读原文”进行下载。

END

作者:shanyuxiang
来源:21ic论坛

版权归原作者所有,如需转载,请注明出处

推荐阅读
遇到一位被国产MCU伤透了心的老板
稚晖君的机器人开源了,含全套图纸+代码
美国民兵III型核导弹制导系统和计算机内部欣赏

→点关注,不迷路←

嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论
  • 在科技飞速发展的今天,5G 通信技术无疑是最耀眼的明星之一。它如同一场数字革命的风暴,以其前所未有的速度、极低的延迟和强大的连接能力,为我们的生活、经济和社会带来了翻天覆地的变化,开启了一个万物互联的崭新时代。5G 技术的卓越特性5G,即第五代移动通信技术,相比其前辈们,有着诸多令人瞩目的特性。首先是超高速率。5G 网络的理论峰值下载速度可达 10Gbps,这意味着下载一部高清电影只需短短几秒钟,而 4G 网络可能需要几分钟甚至更长时间。这种高速率让高清视频流、云游戏等对带宽要求极高的应用变得流
    Jeffreyzhang123 2025-01-02 14:18 57浏览
  • 随着全球汽车行业向更加清洁、可持续的能源转型,燃料电池汽车(FCV)作为一种具有广阔前景的技术,正逐渐受到业界的广泛关注。这类氢能源车辆通过燃料电池中的电化学反应,将氢气转化为电能,并仅产生水作为副产品,展现出显著的环境效益。然而,氢气的易燃性也为燃料电池汽车的安全带来了挑战,因此,高效的氢气泄漏检测系统和残氢排放监控技术对于确保车辆的安全性和可靠性至关重要。 ‌一、氢能源车氢气泄漏检测技术‌为了确保燃料电池汽车的安全性,氢气传感器被广泛应用于氢气泄漏检测中。这些传感器能够集成到车辆的
    锂电小助手 2025-01-02 10:05 49浏览
  • 常见通信标准无线通信标准蜂窝移动通信标准:如 2G(GSM)、3G(WCDMA、CDMA2000、TD - SCDMA)、4G(LTE)以及 5G 等。以 5G 为例,其具有高速率、低时延、大容量等特点,为智能交通、工业互联网和物联网等领域提供支持。无线局域网标准:主要是 IEEE802.11 标准,也就是我们常说的 Wi - Fi。例如 IEEE802.11ac 和 IEEE802.11ax(Wi-Fi 6)等标准,不断提升无线局域网的传输速度和稳定性。短距离无线通信标准:包括蓝牙(Bluet
    Jeffreyzhang123 2025-01-02 14:33 43浏览
  • 2层PCB设计时候回路的寄生电感计算方式。由两个平面构成电流路径的回路电感,取决于每个平面路径的局部自感和它们之间的局部互感。平面越宽,电流分布就越扩散开,平面的局部自感就越小,从而回路电感也就越小。平面越长,局部自感就越大,从而回路电感也就越大。平面间距越小,平面之间的互感就越大,从而回路电感也就越小。当该区域为正方形,即长度等于宽度时,无论边长是多少,长和宽之比始终等于1。令人惊奇的是,一对平面上的边长为100mil的正方形区域和边长为1in的正方形区域的回路电感相同。平面对上的任一正方形区
    tao180539_524066311 2025-01-02 13:51 38浏览
  • 很荣幸收到富芮坤 FR3068x-C低功耗开发板,参加测评。断断续续看了一些资料,今天有时间把开发环境初步搭建好了,能下载程序了。记录一下,给大家分享一下。1、找不到ARMCM33_DSP_FP。安装Keil。我之前安装的Keil版本低,下载了重新安装了5.41,安装完成后,在工程配置页面的Device页面没有找到官方说的ARMCM33_DSP_FP,下图为官方文档内的截图,没有关系,注意右边涂黄色部分,Pack 的信息。打开keil的Pack Installer,找到ARM::CMSIS,找到
    王晓明 2025-01-01 15:38 87浏览
  • 2024年之前国际国内大品牌芯片,是电子产品生产主流供应产品,国际和国内大品牌电子芯片,产品质量有保证,质量过硬,芯片各项参数满足电子产品需求,但价格高,交货慢,工厂接了急单,国内外大品牌产品短期内交不出来,交出芯片,价格太高。目前来看,以美国为首国外势力,对我们国家一些高端芯片,不出货,不卖给我们,我们要生产电器产品,一定要自己研发芯片,替代进口芯片,低价格芯片替代高价格芯片。国外买家对电子产品,价格压的太低,用国内外知名品牌电子芯片,那电子产品成本高过了卖价,工厂没有利润,工厂无法生存,芯片
    开发工匠 2025-01-01 11:49 94浏览
  •  在这个日新月异的科技时代,智能家居正以前所未有的速度融入我们的日常生活,从智能灯光到温控系统,从安防监控到语音助手,每一处细节都透露着科技的温度与智慧。而在这场智能化浪潮中,一个看似不起眼却至关重要的组件——晶体管光耦,正扮演着连接物理世界与数字世界的隐形桥梁角色,默默推动着智能家居行业的发展与革新。 晶体管光耦——智能家居的“神经递质”晶体管光耦,作为一种能够将电信号转换为光信号,再通过光信号控制另一侧电路开关的电子元器件,其独特的工作原理使得它在隔离传输、抗干扰及保护电
    晶台光耦 2025-01-02 16:19 59浏览
  • 在科技飞速发展的今天,机器人已经逐渐深入到我们生活和工作的各个领域。从工业生产线上不知疲倦的机械臂,到探索未知环境的智能探测机器人,再到贴心陪伴的家用服务机器人,它们的身影无处不在。而在这些机器人的背后,C 语言作为一种强大且高效的编程语言,发挥着至关重要的作用。C 语言为何适合机器人编程C 语言诞生于 20 世纪 70 年代,凭借其简洁高效、可移植性强以及对硬件的直接操控能力,成为机器人编程领域的宠儿。机器人的运行环境往往对资源有着严格的限制,需要程序占用较少的内存和运行空间。C 语言具有出色
    Jeffreyzhang123 2025-01-02 16:26 76浏览
  • 国际标准IPC 标准:IPC-A-600:规定了印刷电路板制造过程中的质量要求和验收标准,涵盖材料、外观、尺寸、焊接、表面处理等方面。IPC-2221/2222:IPC-2221 提供了用于设计印刷电路板的一般原则和要求,IPC-2222 则针对高可靠性电子产品的设计提供了进一步的指导。IPC-6012:详细定义了刚性基板和柔性基板的要求,包括材料、工艺、尺寸、层次结构、特征等。IPC-4101:定义了印刷电路板的基板材料的物理和电气特性。IPC-7351:提供了元件封装的设计规范,包括封装尺寸
    Jeffreyzhang123 2025-01-02 16:50 95浏览
  • 早期概念与探索阶段(19 世纪以前):在古代,人类就对自动机械充满了想象,如古希腊时期的希罗发明的自动门、水钟等自动装置,中国古代的指南车、木牛流马等,虽然这些装置不能称之为真正的机器人,但为后来机器人的发展奠定了思想基础。从概念走向实践阶段(19 世纪~20 世纪初):随着工业革命的到来,自动机概念开始与实际机械设计结合,出现了具有实际功能的自动机械,例如雅卡尔提花机等,可通过穿孔卡片控制编织图案,为后续可编程控制的机器人发展提供了灵感。现代机器人产业萌芽期(1920 年代~1950 年代):
    Jeffreyzhang123 2025-01-02 14:53 80浏览
  • 从无到有:智能手机的早期探索无线电话装置的诞生:1902 年,美国人内森・斯塔布菲尔德在肯塔基州制成了第一个无线电话装置,这是人类对 “手机” 技术最早的探索。第一部移动手机问世:1938 年,美国贝尔实验室为美国军方制成了世界上第一部 “移动” 手机。民用手机的出现:1973 年 4 月 3 日,摩托罗拉工程师马丁・库珀在纽约曼哈顿街头手持世界上第一台民用手机摩托罗拉 DynaTAC 8000X 的原型机,给竞争对手 AT&T 公司的朋友打了一个电话。这款手机重 2 磅,通话时间仅能支持半小时
    Jeffreyzhang123 2025-01-02 16:41 85浏览
  • 前言近年来,随着汽车工业的快速发展,尤其是新能源汽车与智能汽车领域的崛起,汽车安全标准和认证要求日益严格,应用范围愈加广泛。ISO 26262和ISO 21448作为两个重要的汽车安全标准,它们在“系统安全”中扮演的角色各自不同,但又有一定交集。在智能网联汽车的高级辅助驾驶系统(ADAS)应用中,理解这两个标准的区别及其相互关系,对于保障车辆的安全性至关重要。ISO 26262:汽车功能安全的基石如图2.1所示,ISO 26262对“功能安全”的定义解释为:不存在由于电子/电气系统失效引起的危害
    广电计量 2025-01-02 17:18 84浏览
  • 起源与诞生:AI 技术的起源可以追溯到 20 世纪 40 年代,随着计算机技术的兴起,科学家们开始思考如何让机器具备类似人类的智能。1950 年,英国数学家艾伦・图灵提出了著名的 “图灵测试”,为 AI 技术的发展奠定了理论基础。1956 年,美国达特茅斯学院举行了一次人工智能研讨会,标志着 AI 作为一门独立学科的诞生。符号主义阶段(20 世纪 50 年代 - 70 年代):研究人员主要关注如何使用符号逻辑和推理规则来模拟人类思维,试图通过构建复杂的逻辑系统来解决各种问题。然而,由于这种方法的
    Jeffreyzhang123 2025-01-02 15:15 78浏览
  •  近年来,消费电子行业难言景气,长期处于萎靡不振的状态。其中,作为明星品类的智能手机同样被寒意所笼罩,出货量持续下跌。据IDC发布的报告显示,2023年全年,中国智能手机市场出货量约2.71亿台,同比下降5.0%,创近10年以来最低出货量。不过,在智能手机行业整体低迷之际,折叠屏手机却表现亮眼,成为智能手机市场唯一实现增长的品类。据IDC发布的跟踪报告显示,2023年,中国折叠屏手机市场出货量约700.7万台,同比增长114.5%。而这也是自2019年首款产品上市以来,出货量连续4年同
    刘旷 2025-01-02 11:27 37浏览
  • 【工程师故事】+半年的经历依然忧伤,带着焦虑和绝望  对于一个企业来说,赚钱才是第一位的,对于一个人来说,赚钱也是第一位的。因为企业要活下去,因为个人也要活下去。企业打不了倒闭。个人还是要吃饭的。企业倒闭了,打不了从头再来。个人失业了,面对的不仅是房贷车贷和教育,还有找工作的焦虑。企业说,一个公司倒闭了,说明不了什么,这是正常的一个现象。个人说,一个中年男人失业了,面对的压力太大了,焦虑会摧毁你的一切。企业说,是个公司倒闭了,也不是什么大的问题,只不过是这些公司经营有问题吧。
    curton 2025-01-02 23:08 60浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦