FMC总线应用控制32路高速IO扩展
仅供个人学习,来源于armfly。
为什么要做 IO 扩展,不是已经用了 240 脚的 H743XIH6 吗?因为开发板使用了 32 位 SDRAM 和RGB888 硬件接口,消耗 IO 巨大,所以必须得扩展了。扩展的 32 路高速 IO 非常实用,且使用简单,只需初始下 FMC,32 路 IO 就可以随意使用了。当前的扩展方式只支持高速输出。
FMC 总线扩展 32 路高速 IO 理解成 GPIO 的 ODR 寄存器就很简单了,其实就是一个东西。
FMC 扩展 IO 是对地址 0x60001000 的 32bit 数据空间的 0 和 1 的操作。GPIOA 的 ODR 寄存器是对地址 0x40000000 + 0x18020000 + 0x14 空间的操作。但只能操作 16 个引脚。
使用总线的优势就在这里了,相当于在 GPIOA 到 GPIOK 的基础上,又扩展出 GPIOL 和 GPIOM。
FMC的块区分配
FMC 总线可操作的地址范围 0x60000000 到 0xDFFFFFFF,具体的框图如下:
从上面的框图可以看出,NOR/PSRAM/SRAM 块区有 4 个片选 NE1,NE2,NE3 和 NE4,但由于引脚复用,部分片选对应的引脚要用于其他功能,而且要控制的总线外设较多,导致片选不够用。因此需要增
加译码器。
SN74LVC1G139APWR 是双 2-4 线地址译码器,也就是带了两个译码器。原理图上仅用了一个。下面是
139 的真值表和引脚功能:
通过上面的原理图和真值表就比较好理解了,真值表的输出是由片选 FMC_NE1 和地址线 FMC_A10、
FMC_A11 控制。
FMC_NE1 输出低电平:
◆ FMC_A11(B),FMC_A10(A) = 00 时,1Y0 输出的低电平,选择的是 OLED。
◆ FMC_A11(B),FMC_A10(A) = 01 时,1Y1 输出的低电平,选择的是 74HC574。
◆ FMC_A11(B),FMC_A10(A) = 10 时,1Y2 输出的低电平,选择的是 DM9000。
◆ FMC_A11(B),FMC_A10(A) = 11 时,1Y3 输出的低电平,选择的是 AD7606。
然后我们再计算译码器的地址,注意,这里地址的计算都是按照 FMC 的 32bit 访问模式计算的,因为我们的 V7 程序中是将 NE1 对应的 FMC 配置为 32bit 模式了。
32bit 模式下,我们计算 A10 和 A11 的时候,实际上需要按 HADDR12 和 HADDR13 计算的。
如果来算 NE1 + HADDR12 + HADDR13 的四种组合地址就是如下:
NE1 + HADDR13 + HADDR12 = 0x60000000 + 0<<13 + 0<<12 = 0x60000000
NE1 + HADDR13 + HADDR12 = 0x60000000 + 0<<13 + 1<<12 = 0x60001000
NE1 + HADDR13 + HADDR12 = 0x60000000 + 1<<13 + 0<<12 = 0x60002000
NE1 + HADDR13 + HADDR12 = 0x60000000 + 1<<13 + 1<<12 = 0x60003000
这样一来,原理图里面给的地址就对应上了。同理如果配置为 16 位模式和 8 位模式,大家应该也都会计
算了。
IO扩展电路
有了这个原理图,首先要做的就是了解 74HC574 和 SN74HC02 的功能。
74HC574 是一款 8 位三态 D 触发器,起到锁存的功能,上升沿触发,对应的真值表如下(L 表示低电平,
H 表示高电平,Z 表示高阻):
SN74HC02 是一款 2 输入或非门,一个芯片带了四组或非门,对应的真值表如下(L 表示低电平,H 表示
高电平):
举例扩展IO驱动LED应用
操作LED的亮灭,就是操作FMC数据引脚D8、D9、D10和D11
FMC驱动
配置MPU
实际测试发现,使能 FMC_NE1 所管理的存储区的 Cache 功能后,会出现扩展 IO 的 NE 片选和 NWE信号输出 2 次的问题。经过各种 Cache 方式配置、FMC 带宽配置、操作 FMC 时的数据位宽设置,发现禁止了 Cache 功能就正常了,也就是说,设置 FMC_NE1 所管理的存储区 MPU 属性为 Device 或者Strongly Ordered 即可。
/* 配置 FMC 扩展 IO 的 MPU 属性为 Device 或者 Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
配置扩展IO
static void HC574_ConfigGPIO(void)
{
/*
安富莱 STM32-H7 开发板接线方法:4 片 74HC574 挂在 FMC 32 位总线上。1 个地址端口可以扩展出 32 个 IO
PD0/FMC_D2
PD1/FMC_D3
PD4/FMC_NOE ---- 读控制信号,OE = Output Enable , N 表示低有效
PD5/FMC_NWE -XX- 写控制信号,AD7606 只有读,无写信号
PD8/FMC_D13
PD9/FMC_D14
PD10/FMC_D15
PD14/FMC_D0
PD15/FMC_D1
PE7/FMC_D4
PE8/FMC_D5
PE9/FMC_D6
PE10/FMC_D7
PE11/FMC_D8
PE12/FMC_D9
PE13/FMC_D10
PE14/FMC_D11
PE15/FMC_D12
PG0/FMC_A10 --- 和主片选 FMC_NE2 一起译码
PG1/FMC_A11 --- 和主片选 FMC_NE2 一起译码
XX --- PG9/FMC_NE2 --- 主片选(OLED, 74HC574, DM9000, AD7606)
--- PD7/FMC_NE1 --- 主片选(OLED, 74HC574, DM9000, AD7606)
+-------------------+------------------+
+ 32-bits Mode: D31-D16 +
+-------------------+------------------+
| PH8 <-> FMC_D16 | PI0 <-> FMC_D24 |
| PH9 <-> FMC_D17 | PI1 <-> FMC_D25 |
| PH10 <-> FMC_D18 | PI2 <-> FMC_D26 |
| PH11 <-> FMC_D19 | PI3 <-> FMC_D27 |
| PH12 <-> FMC_D20 | PI6 <-> FMC_D28 |
| PH13 <-> FMC_D21 | PI7 <-> FMC_D29 |
| PH14 <-> FMC_D22 | PI9 <-> FMC_D30 |
| PH15 <-> FMC_D23 | PI10 <-> FMC_D31 |
+------------------+-------------------+
*/
GPIO_InitTypeDef gpio_init_structure;
/* 使能 GPIO 时钟 */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOI_CLK_ENABLE();
/* 使能 FMC 时钟 */
__HAL_RCC_FMC_CLK_ENABLE();
/* 设置 GPIOD 相关的 IO 为复用推挽输出 */
gpio_init_structure.Mode = GPIO_MODE_AF_PP;
gpio_init_structure.Pull = GPIO_PULLUP;
gpio_init_structure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
gpio_init_structure.Alternate = GPIO_AF12_FMC;
/* 配置 GPIOD */
gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7 |
GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 |
GPIO_PIN_15;
HAL_GPIO_Init(GPIOD, &gpio_init_structure);
/* 配置 GPIOE */
gpio_init_structure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 |
GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |
GPIO_PIN_15;
HAL_GPIO_Init(GPIOE, &gpio_init_structure);
/* 配置 GPIOG */
gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1;
HAL_GPIO_Init(GPIOG, &gpio_init_structure);
/* 配置 GPIOH */
gpio_init_structure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12
| GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOH, &gpio_init_structure);
/* 配置 GPIOI */
gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_6
| GPIO_PIN_7 | GPIO_PIN_9 | GPIO_PIN_10;
HAL_GPIO_Init(GPIOI, &gpio_init_structure);
}
配置FMC并口访问时序
static void HC574_ConfigFMC(void)
{
SRAM_HandleTypeDef hsram = {0};
FMC_NORSRAM_TimingTypeDef SRAM_Timing = {0};
hsram.Instance = FMC_NORSRAM_DEVICE;
hsram.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
/* FMC使用的HCLK3,主频200MHz,1个FMC时钟周期就是5ns */
/* SRAM 总线时序配置 4-1-2-1-2-2 不稳定,5-2-2-1-2-2 稳定 */
SRAM_Timing.AddressSetupTime = 5; /* 5*5ns=25ns,地址建立时间,范围0 -15个FMC时钟周期个数 */
SRAM_Timing.AddressHoldTime = 2; /* 地址保持时间,配置为模式A时,用不到此参数 范围1 -15个时钟周期个数 */
SRAM_Timing.DataSetupTime = 2; /* 2*5ns=10ns,数据保持时间,范围1 -255个时钟周期个数 */
SRAM_Timing.BusTurnAroundDuration = 1; /* 此配置用不到这个参数 */
SRAM_Timing.CLKDivision = 2; /* 此配置用不到这个参数 */
SRAM_Timing.DataLatency = 2; /* 此配置用不到这个参数 */
SRAM_Timing.AccessMode = FMC_ACCESS_MODE_A; /* 配置为模式A */
hsram.Init.NSBank = FMC_NORSRAM_BANK1; /* 使用的BANK1,即使用的片选FMC_NE1 */
hsram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE; /* 禁止地址数据复用 */
hsram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM; /* 存储器类型SRAM */
hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_32; /* 32位总线宽度 */
hsram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE; /* 关闭突发模式 */
hsram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW; /* 用于设置等待信号的极性,关闭突发模式,此参数无效 */
hsram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS; /* 关闭突发模式,此参数无效 */
hsram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE; /* 用于使能或者禁止写保护 */
hsram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE; /* 关闭突发模式,此参数无效 */
hsram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE; /* 禁止扩展模式 */
hsram.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE; /* 用于异步传输期间,使能或者禁止等待信号,这里选择关闭 */
hsram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE; /* 禁止写突发 */
hsram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY; /* 仅同步模式才做时钟输出 */
hsram.Init.WriteFifo = FMC_WRITE_FIFO_ENABLE; /* 使能写FIFO */
/* 初始化SRAM控制器 */
if (HAL_SRAM_Init(&hsram, &SRAM_Timing, &SRAM_Timing) != HAL_OK)
{
/* 初始化错误 */
Error_Handler(__FILE__, __LINE__);
}
}
应用
#define HC574_PORT *(uint32_t *)0x60001000
__IO uint32_t g_HC574; /* 保存74HC574端口状态 */
static void HC574_ConfigGPIO(void);
static void HC574_ConfigFMC(void);
/*
*********************************************************************************************************
* 函 数 名: bsp_InitExtIO
* 功能说明: 配置扩展IO相关的GPIO. 上电只能执行一次。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitExtIO(void)
{
HC574_ConfigGPIO();
HC574_ConfigFMC();
/* 将开发板一些片选,LED口设置为高 */
g_HC574 = (NRF24L01_CE | VS1053_XDCS | LED1 | LED2 | LED3 | LED4);
HC574_PORT = g_HC574; /* 写硬件端口,更改IO状态 */
}
/*
*********************************************************************************************************
* 函 数 名: HC574_SetPin
* 功能说明: 设置74HC574端口值
* 形 参: _pin : 管脚号, 0-31; 只能选1个,不能多选
* _value : 设定的值,0或1
* 返 回 值: 无
*********************************************************************************************************
*/
void HC574_SetPin(uint32_t _pin, uint8_t _value)
{
if (_value == 0)
{
g_HC574 &= (~_pin);
}
else
{
g_HC574 |= _pin;
}
HC574_PORT = g_HC574;
}
/*
*********************************************************************************************************
* 函 数 名: HC574_TogglePin
* 功能说明: 饭庄74HC574端口值
* 形 参: _pin : 管脚号, 0-31; 只能选1个,不能多选
* 返 回 值: 无
*********************************************************************************************************
*/
void HC574_TogglePin(uint32_t _pin)
{
if (g_HC574 & _pin)
{
g_HC574 &= (~_pin);
}
else
{
g_HC574 |= _pin;
}
HC574_PORT = g_HC574;
}
/*
*********************************************************************************************************
* 函 数 名: HC574_GetPin
* 功能说明: 判断指定的管脚输出是1还是0
* 形 参: _pin : 管脚号, 0-31; 只能选1个,不能多选
* 返 回 值: 0或1
*********************************************************************************************************
*/
uint8_t HC574_GetPin(uint32_t _pin)
{
if (g_HC574 & _pin)
{
return 1;
}
else
{
return 0;
}
}