当前位置: 首页 > article >正文

【GD32】外部存储器控制器(EXMC)驱动16位8080时序并口屏(GD32F470ZGT6)

1. 简介

        GD32F4系列中的EXMC外设可以用来驱动外部的储存器,如SDRAM、SRAM等等,在之前的文章中有介绍其用法。

        但EXMC还有一种比较特别的玩法就是用来驱动8080并口屏;之前讲过,EXMC的工作原理就是把外部的储存器地址映射到芯片内部的一段地址中。而显示屏的驱动其实也是把数据写到显示芯片的RAM中,这个RAM本质还是储存器的一种,因此我们也可以用EXMC外设把显示芯片的RAM映射到单片机中,这样可以大大提高通讯速度。

2. 硬件设计

        下面的例程是基于我之前的一个小项目,项目的介绍已经开源在立创开源社区中了,跳转链接。

        开发板使用的是立创梁山派,开发板上带有一个8080并口屏接口;屏幕买的是一块IPS的并口屏,屏幕驱动芯片是ST7796U

        因为屏幕是支持触摸的,所以忽略掉I2C部分的管脚。LCD_D0~16为显示屏的16根数据管脚,LCD_CS为片选线,LCD_DC脚控制数据或命令传输,LCD_WR为写使能管脚,LCD_RD为读使能管脚,LCD_BLC为显示屏背光使能。

3. 原理

        驱动屏幕使用的是EXMC的NOR Flash模式,因此使用的是EXMC的Bank0,总共256MB;Bank0又分为了4个Region,每个Region有64MB。梁山派的这个8080并口引出的是Region3的片选线(EXMC_NE3)

         由参考手册可以查看Region3的基地址映射,从0x6C000000开始。接下来我们要确认命令地址和数据地址,LCD_DC线是连接到EXMC_A10上的;屏幕驱动芯片的RAM是16位宽度,根据参考手册,使用16位宽度时,EXMC的HADDR[25:1]与EXMC_A[24:0]连接,另外LCD_DC高电平表示数据传输,低电平表示数据传输;综上可以得出数据地址为0x6C000800,命令地址为0x6C000000

        针对NOR Flash,EXMC提供了非常多的驱动时序,这块屏幕使用的是模式B来驱动,它的读时序和写时序如下面。

        芯片读过程,EXMC先拉低片选(EXMC_NEx);拉低输出使能(EXMC_NOE),其实就是读使能;拉高写使能(EXMC_NWE);在经过地址建立时间后拉高地址有效线(我们没有用到);再经过数据建立时间后就可以读取到稳定的数据了。

        芯片写过程差不多, 先拉低片选(EXMC_NEx);拉高输出使能(EXMC_NOE);拉低写使能(EXMC_NWE);在经过地址建立时间后拉高地址有效线(我们没有用到);经过地址建立时间后EXMC开始通过数据线(EXMC_Dx)向芯片输出数据;经过数据建立时间后拉高写使能;再经过一个HCLK时钟周期再恢复管脚状态。

         对于模式B的时序,我们要设置其地址建立时间(WASET)和数据建立时间(WDSET),这两个时间可以在屏幕驱动芯片的数据手册中找到。

         上面的T_{AST}T_{DST}就是地址建立时间和数据建立时间,最小值分别为0ns和10ns

4. 代码

        先看GPIO的初始化。代码有省略,因为都大差不差。主要注意的是设置复用模式,EXMC的复用组为GPIO_AF_12,EXMC的所有管脚最好上拉,速度设置为最高速。

static void ST7796_GPIOInit(void)
{
	/* config GPIO */
	rcu_periph_clock_enable(ST7796_D0_CLK);
    ...

	gpio_mode_set(ST7796_D0_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, ST7796_D0_PIN);
	gpio_output_options_set(ST7796_D0_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, ST7796_D0_PIN);
	gpio_af_set(ST7796_D0_PORT, EXMC_AF, ST7796_D0_PIN);
    ...
}

        比较关键的是EXMC的初始化。

static void ST7796_EXMCInit(void)
{
	exmc_norsram_parameter_struct exmc_init_struct = {0};
	exmc_norsram_timing_parameter_struct exmc_timing_struct = {0};
	
	/* config FMC clock */
	rcu_periph_clock_enable(EXMC_CLK);
	
	exmc_timing_struct.asyn_access_mode = EXMC_ACCESS_MODE_B;
	exmc_timing_struct.asyn_address_setuptime = 5;  // 5 / 240MHz = 20ns
	exmc_timing_struct.asyn_data_setuptime = 4;  // (4 + 1) / 240MHz = 20ns

	exmc_init_struct.address_data_mux = DISABLE;
	exmc_init_struct.asyn_wait = DISABLE;
	exmc_init_struct.burst_mode = DISABLE;
	exmc_init_struct.databus_width = EXMC_NOR_DATABUS_WIDTH_16B;
	exmc_init_struct.extended_mode = DISABLE;
	exmc_init_struct.memory_type = EXMC_MEMORY_TYPE_NOR;
	exmc_init_struct.memory_write = ENABLE;
	exmc_init_struct.norsram_region = EXMC_BANK0_NORSRAM_REGION3;
	exmc_init_struct.nwait_signal = DISABLE;
	exmc_init_struct.wrap_burst_mode = DISABLE;
	exmc_init_struct.write_mode = EXMC_ASYN_WRITE;
	exmc_init_struct.read_write_timing = &exmc_timing_struct;
	exmc_init_struct.write_timing = &exmc_timing_struct;
	
	exmc_norsram_init(&exmc_init_struct);
	exmc_norsram_enable(EXMC_BANK0_REGIONx);
}

         先初始化exmc_norsram_timing_parameter_struct结构体配置时序;asyn_access_mode成员设置异步模式,这里为模式B;asyn_address_setuptime设置地址建立时间,这里设置为5,即20ns;asyn_data_setuptime设置数据建立时间,这里设置为4,也是20ns;这两个时间的话可以适当设得大点,因为硬件的体质是有差异的,设得离理论值很接近的话有可能会通讯失败

        再初始化exmc_norsram_parameter_struct结构体配置EXMC; address_data_mux设置地址线复用,这里不复用;aync_wait设置异步等待,这里不等待;burst_mode设置突发模式,这里不使用;databus_width设置数据宽度,这里为16位;extended_mode设置扩展模式,这里不使用;memory_type设置储存器类型,这里为NOR Flash;memory_write设置储存器写,这里使能;norsram_region设置使用的Region;这里设置为Region3;nwait_signal设置NWAIT信号,这里不使用;wrap_burst_mode设置突发模式中的数据包裹,这里不使用;write_mode设置写模式,这里使用异步模式;read_write_timing和write_timing填之前初始化好的时序结构体。

        最后初始化并使能EXMC外设就可以使用了。对屏幕芯片读和写操作只需要像操作指针一样即可。

#define EXMC_Addr_ST7796_CMD		((uint32_t)0x6C000000)
#define EXMC_Addr_ST7796_DATA		((uint32_t)0x6C000800)  // 0x6C000000 | (1 << (1 + Ax))

__inline void ST7796_WriteCmd(uint16_t cmd)
{
	*( __IO uint16_t*)(EXMC_Addr_ST7796_CMD) = cmd;
}


__inline void ST7796_WriteData(uint16_t data)
{
	*( __IO uint16_t*)(EXMC_Addr_ST7796_DATA) = data;
}

        当然我们还需要对屏幕芯片寄存器进行初始化后才能,执行其他的操作,但这里就不介绍了,因为每个芯片的寄存器都不同,只需要根据芯片手册进行编写即可。


http://www.kler.cn/a/294206.html

相关文章:

  • 使用 Vision 插件让 GitHub Copilot 识图问答
  • Serverless架构在实时数据处理中的应用
  • sealos部署K8s,安装docker时master节点突然NotReady
  • CentOS网络配置
  • 2024 年(第 7 届)“泰迪杯”数据分析技能赛B 题 特殊医学用途配方食品数据分析 完整代码 结果 可视化分享
  • 如何在CentOS 7上搭建SMB服务
  • 企业级WEB应用服务器---TOMACT
  • LeetCode --- 413周赛
  • Spring + ActiveMQ 整合实现发布/订阅(publish-subscribe)消息发送案例
  • OPenCV结构分析与形状描述符(2)计算轮廓周长的函数arcLength()的使用
  • 机器学习面试:SVM为什么使用对偶函数求解?
  • 力扣1049-最后一块石头的重量II(Java详细题解)
  • 3个恢复方法详解:iPhone手机快速找回备忘录
  • 数据血缘系列(19)—— 数据血缘技术实现之数据血缘可视化
  • 高德地图SDK Android版开发 10 InfoWindow
  • 【Redis】Windows平台编译调试运行Redis,并附编译问题解决方案
  • 用python fastapi写一个http接口,使ros2机器人开始slam toolbox建图
  • @import导入样式以及scss变量应用与static目录
  • 4. GIS前端工程师岗位职责、技术要求和常见面试题
  • Windows 11的新游戏手柄键盘现在可让玩家使用Xbox手柄打字
  • UE引擎工具链
  • vue3+ant design vue实现表格导出(后端返回文件流类型导出)
  • 多线程的实现和成员方法
  • 2 php8.0 中开发一个websocket 聊天 表设计
  • 启动第一个docker容器
  • Vue——day11之生命周期