本篇内容为个人笔记,可能存在错误,欢迎指正。
屏幕为正点原子7寸屏幕(1024*600)。SDRAM是W9825G6KH-6芯片,容量32MByte。开发板是自己弄的,接口除了背光引脚,其余跟正点的阿波罗核心板原子一样。
DMA2D下期使用,这期先点灯,原理一笔带过,下期补回来。
以下内容参考正点原子的STM32H743 阿波罗开发指南V1.0
一、驱动SDRAM
1、使用外部高速时钟
2、开启ICche和Dcahe
提高 CPU访问指令/数据速度。开启D-Cache 强制透写功能,在初始化代码中添加SCB->CACR |= 1 << 2;
3、配置时钟树
主频选择480M。FMC外设最大速率是200MHz,W9825G6KH-6最大速率是166MHz,这里为了方便使用PPL2R时钟源,配置时钟200MHz。后面再2分频,使SDRAM时钟为100MHz。
4、配置FMC
SDRAM timing in memory clock cycles要根据配置的FMC时钟而定。我这里配置读写大块内存测试没有问题。
1)使用SRRAM1。
Clock and chip enable 时钟信号使用FMC_SDCKE0,片选信号使用FMC SDME0。
FMC_BA0和FMC_BA1, 2根bank选择线都使用。
13位地址线,16位数据线
2)SDRAM control 配置
Bank:使用bank1。
Number of column address bits:W9825G6KH-6的列地址宽度为9。
Number of row address bits:W9825G6KH-6的行地址宽度为13。
CAS Latency:列地址选通延迟(简称 CL)。 在读命令(同时发送列地址) 发送完之后,需要等待几个时钟周期, DQ 数据线上的数据, 才会有效, 这个延迟时间, 就叫 CL, 一般
设置为 2/3 个时钟周期。(正点原子手册原话)。
Write protection:写保护,不使能。
SDRAM common clock:SDRAM通用时钟,2分频。我们的FMC时钟是200Mhz,分频后,SDRAM 时钟就变成100Mhz。
SDRAM common burst read:SDRAM突发读取,使能。
SDRAM common read pipe delay:SDRAM读管延时,选择1个HCLK时钟周期。
3)配置SDRAM timing in memory clock cycles
Load mode register to active dela:负载模式寄存器到活动延时,选择2。Load mode register to active delay 是FMC_SDTRx寄存器的TMRD位,根据手册配置为2;
Exit self-refresh delay:退出自动刷新延时,72ns。如果是100MHz的时钟,周期就10ns。72ns / 10ns = 7.2 clock cycles。所以选择8。
Self-refresh time:自我刷新时间:42ns,选择5。
SDRAM common row cycle delay:60ns,选择6。
Write recovery time:写入恢复时间,writerrecoverytime必须满足以下约束条件:
1: WriteRecoveryTime >= SelfRefreshTime - RowToColumnDelay,
2: WriteRecoveryTime >= RowCycleDelay - RowToColumnDelay - RowPrechargeDelay.
SelfRefreshTime=5,RowToColumnDelay=2,所以这里填3;
SDRAM common row precharge dela:SDRAM行预充电时间,18ns,选择2。
Row to column delay:行到列延时,18ns ,选择2。
5、开启串口,生成工程。
打开串口
勾选以下,生成.h文件。
6、初始化代码
正点原子也有这个代码,都是差不多的
1)新建一个 "sdram_fmc_drv.h"文件
//以下代码来自网友,作者是 mculover666
/**
*@file sdram_fmc_drv.h
*@brief 使用 FMC 操作 SDRAM
*@author mculover666
*@date 2020-08-27
*@note 此驱动测试 W9825G6KH SDRAM芯片通过
*/
#ifndef _SDRAM_FMC_DRV_H_
#define _SDRAM_FMC_DRV_H_
#include "fmc.h"
#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)
void SDRAM_Init(void);
#endif /* _SDRAM_FMC_DRV_H_ */
2)新建一个 "sdram_fmc_drv.c"文件
#include "sdram_fmc_drv.h"
//以下代码来自网友,作者是 mculover666
/**
* @brief 向SDRAM发送命令
* @param CommandMode : 指令(0,正常模式/1,时钟配置使能/2,预充电所有存储区/3,自动刷新/4,加载模式寄存器/5,自刷新/6,掉电)
* @param Bank : 0,向BANK5上面的SDRAM发送指令
* @arg 1,向BANK6上面的SDRAM发送指令
* @param RefreshNum : 自刷新次数
* @RegVal 模式寄存器的定义
* @retval 返回值:0,正常;1,失败.
*/
static int SDRAM_SendCommand(uint32_t CommandMode, uint32_t Bank, uint32_t RefreshNum, uint32_t RegVal)
{
uint32_t CommandTarget;
FMC_SDRAM_CommandTypeDef Command;
if (Bank == 1) {
CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
} else if (Bank == 2) {
CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
}
Command.CommandMode = CommandMode;
Command.CommandTarget = CommandTarget;
Command.AutoRefreshNumber = RefreshNum;
Command.ModeRegisterDefinition = RegVal;
if (HAL_SDRAM_SendCommand(&hsdram1, &Command, 0x1000) != HAL_OK) {
return -1;
}
return 0;
}
void SDRAM_Init(void)
{
uint32_t temp;
/* 1. 时钟使能命令 */
SDRAM_SendCommand(FMC_SDRAM_CMD_CLK_ENABLE, 1, 1, 0);
/* 2. 延时,至少100us */
HAL_Delay(1);
/* 3. SDRAM全部预充电命令 */
SDRAM_SendCommand(FMC_SDRAM_CMD_PALL, 1, 1, 0);
/* 4. 自动刷新命令 */
SDRAM_SendCommand(FMC_SDRAM_CMD_AUTOREFRESH_MODE, 1, 8, 0);
/* 5. 配置SDRAM模式寄存器 */
temp = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | //设置突发长度:1
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | //设置突发类型:连续
SDRAM_MODEREG_CAS_LATENCY_3 | //设置CL值:3
SDRAM_MODEREG_OPERATING_MODE_STANDARD | //设置操作模式:标准
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; //设置突发写模式:单点访问
SDRAM_SendCommand(FMC_SDRAM_CMD_LOAD_MODE, 1, 1, temp);
/* 6. 设置自刷新频率 */
/**
* 刷新频率计数器(以SDCLK频率计数),计算方法:
* COUNT=SDRAM刷新周期/行数-20=SDRAM刷新周期(us)*SDCLK频率(Mhz)/行数
* 我们使用的SDRAM刷新周期为64ms,SDCLK=200/2=100Mhz,行数为8192(2^13).
* 所以,COUNT=64*1000*100/8192-20=761.25
*/
HAL_SDRAM_ProgramRefreshRate(&hsdram1, 761);
}
HAL_SDRAM_ProgramRefreshRate(&hsdram1, 761);要根据时钟频率计算,如果不想算,可以直接套 refresh = 7.8125*(你的时钟频率)-20,我使用的是100MHz,
所以refresh = 7.8125*100-20=761.25。
3)在main.c中初始化串口和编程测试函数。
在main.c中添加以下代码
//在main.c中添加
#include <stdio.h>
#include "sdram_fmc_drv.h"
#include <string.h>
#define EXT_SDRAM_ADDR ((uint32_t)0xC0000000+0x200000)
#define EXT_SDRAM_SIZE (28 * 1024 * 1024)
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1 ,(uint8_t *) &ch,1,100);
return ch;
}
uint32_t bsp_TestExtSDRAM(void)
{
uint32_t i;
uint32_t *pSRAM;
uint8_t *pBytes;
uint32_t err;
const uint8_t ByteBuf[4] = {0x55, 0xA5, 0x5A, 0xAA};
/* 写SDRAM */
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
*pSRAM++ = i;
}
/* 读SDRAM */
err = 0;
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
if (*pSRAM++ != i)
{
err++;
}
}
if (err > 0)
{
return (4 * err);
}
/* 对SDRAM 的数据求反并写入 */
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
*pSRAM = ~*pSRAM;
pSRAM++;
}
/* 再次比较SDRAM的数据 */
err = 0;
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
if (*pSRAM++ != (~i))
{
err++;
}
}
if (err > 0)
{
return (4 * err);
}
/* 测试按字节方式访问, 目的是验证 FSMC_NBL0 、 FSMC_NBL1 口线 */
pBytes = (uint8_t *)EXT_SDRAM_ADDR;
for (i = 0; i < sizeof(ByteBuf); i++)
{
*pBytes++ = ByteBuf[i];
}
/* 比较SDRAM的数据 */
err = 0;
pBytes = (uint8_t *)EXT_SDRAM_ADDR;
for (i = 0; i < sizeof(ByteBuf); i++)
{
if (*pBytes++ != ByteBuf[i])
{
err++;
}
}
if (err > 0)
{
return err;
}
return 0;
}
在main函数中添加代码
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* Enable the CPU Cache */
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
SCB->CACR |= 1 << 2; /* 强制 D-Cache 透写,如不开启透写,实际使用中可能遇到各种问题 */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_FMC_Init();
MX_USART1_UART_Init();
MX_DMA2D_Init();
MX_LTDC_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
printf("STM32H743 SDRAM Test\r\n");
SDRAM_Init();
printf("SDRAM Init complete\r\n");
if (bsp_TestExtSDRAM() == 0) {
printf("SDRAM Test success\r\n");
} else {
printf("SDRAM Test fail\r\n");
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
7、观察串口输出结果
SDRAM Test success 代表成功了。
如果下载失败,则是SDRAM配置有问题,程序可能跑飞了,按住复位引脚点下载,再松开即可。
二、驱动RGB屏幕
1、配置LTDC
1)Display Type
选择RGB565颜色格式。其实565和888色彩区别不是很大,为了节省一个字节就选择565。
2)Parameter Settings
Parameter Settings的配置比较简单,就不说明了,直接套用。
3)Layer Settings配置
先配置Number of Layers :Number of Layers 这里选择1 layer,LTDC是有前景层和背景层的,后面再出一期讲解。
Windows Position:第0层的窗口的开始和结束,就根据屏幕像素配置,我的是1024x600 pixel的屏幕。
Pixel Parameters:该图层选择RGB565格式。
Blending:图层颜色混合,通透度选择255,前景色不透明,剩下两个图层通透度系数默认选项。
Layer Default Color:图层默认颜色,全是0就行了。
Frame Buffe:
1)Layer 0 - Color Frame Buffer Start Adress:第0层-彩色帧缓冲区起始地址选择0xC0000000,就是我们SDRAM的起始地址。以也可偏移32M字节内。因为我们使用RGB565格式,1024x600 pixel,要预留2x1024x600个字节空间。
2)Layer 0 - Color Frame Buffer Line Length :1024;
3)Layer 0 - Color Frame Buffer Number of Lines :600
4)配置RGB引脚
LTDC的引脚的Maximum output speed全部配置为High或者Very High,不然工程会失败。
5)打开背光灯
背光灯使用推挽上拉输出,默认高电平就行了。
6、配置LTCD时钟
像素时钟就是 RGB LCD 的时钟信号,以 ATK7016 这款屏幕为例,显示一帧图像所需要的
时钟数就是:
= (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
= (3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160)
= 635 * 1344
= 853440。
显示一帧图像需要 853440个时钟数,那么显示 60帧就是:853440 * 60 = 51206400≈51.2M,
所以像素时钟就是 51.2MHz。
2、打开DMA2D
这里打开就行了,不用作任何配置,因为用HAL库操作DMA2D非常浪费资源,频繁进栈和出栈没有达到使用DMA2D的初衷,所以我们直接对寄存器操作。
3、编写测试代码
代码先写一个简单的测试,不使用DMA2D,后面出下一章节再讲。
__ attribute __( at(绝对地址) )的作用是定位到 Flash或 RAM。绝对定位不能在函数中定义,局部变量是定义在栈区,栈区是自动分配、释放,不能定义为绝对地址,只能于函数外定义。
uint16_t LCD_SDRAM_BUFF[1024][600] __attribute__((at(0XC0000000)));
//在main函数的死循环里中添加
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
memset(LCD_SDRAM_BUFF,0x001F,sizeof(LCD_SDRAM_BUFF));
HAL_Delay(2000);
memset(LCD_SDRAM_BUFF,0xF800,sizeof(LCD_SDRAM_BUFF));
HAL_Delay(2000);
memset(LCD_SDRAM_BUFF,0x07E0,sizeof(LCD_SDRAM_BUFF));
HAL_Delay(2000);
}
点灯带师,启动。