赞
踩
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
本章教程为大家讲解利用STM32H7的FMC总线扩展出32路高速IO,且使用简单,实际项目中也比较有实用价值。
目录
第48章 STM32H7的FMC总线应用之是32路高速IO扩展
48.4 FMC扩展IO板级支持包(bsp_fmc_io.c)
FMC扩展IO是对地址0x60001000的32bit数据空间的0和1的操作。GPIOA的ODR寄存器是对地址 0x40000000 + 0x18020000 + 0x14 空间的操作。但只能操作16个引脚。
使用总线的优势就在这里了,相当于在GPIOA到GPIOK的基础上,又扩展出GPIOL和GPIOM。
#define PERIPH_BASE ((uint32_t)0x40000000) #define D3_AHB1PERIPH_BASE (PERIPH_BASE + 0x18020000) #define GPIOA_BASE (D3_AHB1PERIPH_BASE + 0x0000) #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) typedef struct { __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */ __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */ __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */ __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */ __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */ __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */ __IO uint16_t BSRRL; /*!< GPIO port bit set/reset low register, Address offset: 0x18 */ __IO uint16_t BSRRH; /*!< GPIO port bit set/reset high register, Address offset: 0x1A */ __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */ __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */ } GPIO_TypeDef;
扩展IO涉及到的知识点稍多,下面逐一为大家做个说明。
注,这个知识点在前面第47章的2.3小节有详细说明。
FMC总线可操作的地址范围0x60000000到0xDFFFFFFF,具体的框图如下:
从上面的框图可以看出,NOR/PSRAM/SRAM块区有4个片选NE1,NE2,NE3和NE4,但由于引脚复用,部分片选对应的引脚要用于其他功能,而且要控制的总线外设较多,导致片选不够用。因此需要增加译码器。
有了前面的认识之后再来看下面的译码器电路:
SN74LVC1G139APWR是双2-4线地址译码器,也就是带了两个译码器。原理图上仅用了一个。下面是139的真值表和引脚功能:
通过上面的原理图和真值表就比较好理解了,真值表的输出是由片选FMC_NE1和地址线FMC_A10、FMC_A11控制。
FMC_NE1 输出低电平:
然后我们再计算译码器的地址,注意,这里地址的计算都是按照FMC的32bit访问模式计算的,因为我们的V7程序中是将NE1对应的FMC配置为32bit模式了。
具体FMC的32bit访问模式,16bit访问模式和8bit访问模式的区别在第47章的2.4小节有详细讲解。
32bit模式下,我们计算A10和A11的时候,实际上需要按HADDR12和HADDR13计算的。
如果来算NE1 + HADDR12 + HADDR13的四种组合地址就是如下:
NE1 + HADDR13 + HADDR12 = 0x6000000 + 0<<13 + 0<<12 = 0x60000000
NE1 + HADDR13 + HADDR12 = 0x6000000 + 0<<13 + 1<<12 = 0x60001000
NE1 + HADDR13 + HADDR12 = 0x6000000 + 1<<13 + 0<<12 = 0x60002000
NE1 + HADDR13 + HADDR12 = 0x6000000 + 1<<13 + 1<<12 = 0x60003000
这样一来,原理图里面给的地址就对应上了。同理如果配置为16位模式和8位模式,大家应该也都会计算了。
先来看下IO扩展的原理图实现,如果不太了解FMC的通信时序和数字逻辑芯片的使用,可能会比较懵,下面逐一为大家说明。
有了这个原理图,首先要做的就是了解74HC574和SN74HC02的功能。
74HC574是一款8位三态D触发器,起到锁存的功能,上升沿触发,对应的真值表如下(L表示低电平,H表示高电平,Z表示高阻):
SN74HC02是一款2输入或非门,一个芯片带了四组或非门,对应的真值表如下(L表示低电平,H表示高电平):
有了这个认识后,我们再来看FMC的配置,V7开发板的BSP驱动包里面专门做了一个IO扩展的FMC配置,即文件bsp_fmc_io.c,配置方式是FMC_AccessMode_A,这种模式对应的写时序是:
那么问题来了,我们要实现的功能是通过FMC输出的数据要锁存在扩展IO的输出端,否则FMC时序信号消失了,扩展IO的输出数据也消失了,就起不到控制作用了。所以就用到74HC574的锁存功能,而锁存的实现需要一个上升沿触发,这个上升沿就是通过74HC02输出的。
再结合上面FMC写时序图,在NE片选为低电平,NWE写使能信号为高电平期间,即地址建立时间段ADDSET内,74HC02是输出的低电平。
进入到DATAST数据建立阶段,在NE片选为低电平,NWE写使能信号也为低电平时,74HC02输出高电平,正好是实现1个上升沿的变化,将数据总线上的数据锁存到74HC574的输出端了。这里隐含了一个知识点,数据还没有完全建立起来就锁存是不是会有问题。在下面的3.3小节配置具体时序参数时再为大家说明。
进行到这里,再回过头来看LED驱动就比较好理解了。操作LED的亮灭就是操作FMC的数据引脚D8,D9,D10和D11。
对地址0x64001000发送数据就可以了,但是如何对这个地址发送数据呢? 反映到C语言的实现上就是通过固定地址的指针变量(跟我们操作寄存器是一样的),即
#define HC574_PORT *(uint32_t *)0x64001000
如果要点亮LED1(低电平点亮),就是 HC574_PORT = 0x0000 0000。
如果要熄灭LED1就是HC574_PORT = 0x0000 0100,即操作FMC_D8的高低电平即可。
下面将程序设计中的相关问题逐一为大家做个说明。
这里仅需把用到的GPIO时钟、FMC时钟、GPIO引脚和复用配置好即可:
/* ********************************************************************************************************* * 函 数 名: HC574_ConfigGPIO * 功能说明: 配置GPIO,FMC管脚设置为复用功能 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ 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可以选择如下几种时钟源HCLK3,PLL1Q,PLL2R和PER_CK:
我们这里直接使用HCLK3,配置STM32H7的主频为400MHz的时候,HCLK3输出的200MHz,这个速度是FMC支持的最高时钟,正好用于这里:
这里要补充两个重要的知识点,74HC574的CP端接收到上升沿触发到Qn输出的时间参数:
通过时序图和对应的参数要了解到以下几点:
了解了74HC574,再来看SN74HC02:
通过时序图和对应的参数要了解到以下几点:
对应74HC574和74HC02的时序参数有个了解后,再来看本章2.3小节末尾的问题:
当写使能信号NWE出现下降沿后,74H02或非门就会输出一个上升沿,然后触发74HC574做锁存。此时我们要考虑到一个重要的知识点,就是使用的数字逻辑芯片有个传输延迟问题,也就是要我们要保证74HC02的tpd传输延迟时间 + 74HC02的tr传输延迟时间 + 74HC574的tpd传输延迟时间的这段时间内,数据总线上要有数据,所以保证DATAST数据建立时间够大就行。实际测试FMC频率在200MHz的情况下,2-3个FMC时钟周期就已经可以正常使用。
有了这些认识后,再来看FMC的时序配置就比较好理解了:
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: HC574_ConfigFMC 4. * 功能说明: 配置FMC并口访问时序 5. * 形 参: 无 6. * 返 回 值: 无 7. ****************************************************************************************************** 8. */ 9. static void HC574_ConfigFMC(void) 10. { 11. SRAM_HandleTypeDef hsram = {0}; 12. FMC_NORSRAM_TimingTypeDef SRAM_Timing = {0}; 13. 14. hsram.Instance = FMC_NORSRAM_DEVICE; 15. hsram.Extended = FMC_NORSRAM_EXTENDED_DEVICE; 16. 17. /* FMC使用的HCLK3,主频200MHz,1个FMC时钟周期就是5ns */ 18. /* SRAM 总线时序配置 4-1-2-1-2-2 不稳定,5-2-2-1-2-2 稳定 */ 19. SRAM_Timing.AddressSetupTime = 5; /* 5*5ns=25ns,地址建立时间,范围0 -15个FMC时钟周期个数 */ 20. SRAM_Timing.AddressHoldTime = 2; /* 地址保持时间,配置为模式A时,用不到此参数 范围1 -15个时 21. 钟周期个数 */ 22. SRAM_Timing.DataSetupTime = 2; /* 2*5ns=10ns,数据保持时间,范围1 -255个时钟周期个数 */ 23. SRAM_Timing.BusTurnAroundDuration = 1; /* 此配置用不到这个参数 */ 24. SRAM_Timing.CLKDivision = 2; /* 此配置用不到这个参数 */ 25. SRAM_Timing.DataLatency = 2; /* 此配置用不到这个参数 */ 26. SRAM_Timing.AccessMode = FMC_ACCESS_MODE_A; /* 配置为模式A */ 27. 28. hsram.Init.NSBank = FMC_NORSRAM_BANK1; /* 使用的BANK1,即使用的片选FMC_NE1 */ 29. hsram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE; /* 禁止地址数据复用 */ 30. hsram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM; /* 存储器类型SRAM */ 31. hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_32; /* 32位总线宽度 */ 32. hsram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE; /* 关闭突发模式 */ 33. hsram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW; /* 用于设置等待信号的极性,关闭突 34. 发模式,此参数无效 */ 35. hsram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS; /* 关闭突发模式,此参数无效 */ 36. hsram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE; /* 用于使能或者禁止写保护 */ 37. hsram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE; /* 关闭突发模式,此参数无效 */ 38. hsram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE; /* 禁止扩展模式 */ 39. hsram.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE; /* 用于异步传输期间,使能或者禁止 40. 等待信号,这里选择关闭 */ 41. hsram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE; /* 禁止写突发 */ 42. hsram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY; /* 仅同步模式才做时钟输出 */ 43. hsram.Init.WriteFifo = FMC_WRITE_FIFO_ENABLE; /* 使能写FIFO */ 44. 45. /* 初始化SRAM控制器 */ 46. if (HAL_SRAM_Init(&hsram, &SRAM_Timing, &SRAM_Timing) != HAL_OK) 47. { 48. /* 初始化错误 */ 49. Error_Handler(__FILE__, __LINE__); 50. } 51. }
这里把几个关键的地方阐释下:
实际测试发现,使能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);
MPU配置中直接从FMC_NE1的首地址开始配置,设置了64KB空间的属性。将FMC_NE1通过译码器所管理的所有设备地址全部设置为此配置:
在bsp_fmc_io.c文件开头有个宏定义#define HC574_PORT *(uint32_t *)0x60001000。特别注意,这里是要操作地址0x60001000上的32位数据空间,即做了一个强制转换uint32_t *,要跟FMC配置时设置的位宽一致。这样做的原因,在第47章的2.6小节有说明。
驱动文件bsp_fmc_io.c提供了如下几个函数供用户调用:
函数原型:
/* ********************************************************************************************************* * 函 数 名: 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状态 */ }
函数描述:
此函数用于初始化FMC扩展IO所用到的GPIO和FMC的参数配置。
使用举例:
作为初始化函数,直接在在bsp.c文件的bsp_Init函数里面调用即可。
函数原型:
/* ********************************************************************************************************* * 函 数 名: 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; }
函数描述:
此函数用于设置扩展IO的输出状态。调用此函数前,要保证调用了函数bsp_InitExtIO进行了初始化。
函数参数:
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */ #define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */ #define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */ #define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */ #define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */ #define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */ #define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */ #define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */ #define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */ #define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */ #define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */ #define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */ #define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */ #define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */ #define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */ #define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */ #define GPIO_PIN_16 ((uint32_t)0x00010000) /* Pin 16 selected */ #define GPIO_PIN_17 ((uint32_t)0x00020000) /* Pin 17 selected */ #define GPIO_PIN_18 ((uint32_t)0x00040000) /* Pin 18 selected */ #define GPIO_PIN_19 ((uint32_t)0x00080000) /* Pin 19 selected */ #define GPIO_PIN_20 ((uint32_t)0x00100000) /* Pin 20 selected */ #define GPIO_PIN_21 ((uint32_t)0x00200000) /* Pin 21 selected */ #define GPIO_PIN_22 ((uint32_t)0x00400000) /* Pin 22 selected */ #define GPIO_PIN_23 ((uint32_t)0x00800000) /* Pin 23 selected */ #define GPIO_PIN_24 ((uint32_t)0x01000000) /* Pin 24 selected */ #define GPIO_PIN_25 ((uint32_t)0x02000000) /* Pin 25 selected */ #define GPIO_PIN_26 ((uint32_t)0x04000000) /* Pin 26 selected */ #define GPIO_PIN_27 ((uint32_t)0x08000000) /* Pin 27 selected */ #define GPIO_PIN_28 ((uint32_t)0x10000000) /* Pin 28 selected */ #define GPIO_PIN_29 ((uint32_t)0x20000000) /* Pin 29 selected */ #define GPIO_PIN_30 ((uint32_t)0x40000000) /* Pin 30 selected */ #define GPIO_PIN_31 ((uint32_t)0x80000000) /* Pin 31 selected */
使用举例:
比如设置扩展IO引脚GPIO_PIN_23为高电平:HC574_SetPin(GPIO_PIN_23, 1)。
函数原型:
/* ********************************************************************************************************* * 函 数 名: 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; }
函数描述:
此函数用于FMC扩展IO的翻转。调用此函数前,要保证调用了函数bsp_InitExtIO进行了初始化。
函数参数:
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */ #define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */ #define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */ #define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */ #define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */ #define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */ #define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */ #define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */ #define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */ #define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */ #define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */ #define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */ #define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */ #define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */ #define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */ #define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */ #define GPIO_PIN_16 ((uint32_t)0x00010000) /* Pin 16 selected */ #define GPIO_PIN_17 ((uint32_t)0x00020000) /* Pin 17 selected */ #define GPIO_PIN_18 ((uint32_t)0x00040000) /* Pin 18 selected */ #define GPIO_PIN_19 ((uint32_t)0x00080000) /* Pin 19 selected */ #define GPIO_PIN_20 ((uint32_t)0x00100000) /* Pin 20 selected */ #define GPIO_PIN_21 ((uint32_t)0x00200000) /* Pin 21 selected */ #define GPIO_PIN_22 ((uint32_t)0x00400000) /* Pin 22 selected */ #define GPIO_PIN_23 ((uint32_t)0x00800000) /* Pin 23 selected */ #define GPIO_PIN_24 ((uint32_t)0x01000000) /* Pin 24 selected */ #define GPIO_PIN_25 ((uint32_t)0x02000000) /* Pin 25 selected */ #define GPIO_PIN_26 ((uint32_t)0x04000000) /* Pin 26 selected */ #define GPIO_PIN_27 ((uint32_t)0x08000000) /* Pin 27 selected */ #define GPIO_PIN_28 ((uint32_t)0x10000000) /* Pin 28 selected */ #define GPIO_PIN_29 ((uint32_t)0x20000000) /* Pin 29 selected */ #define GPIO_PIN_30 ((uint32_t)0x40000000) /* Pin 30 selected */ #define GPIO_PIN_31 ((uint32_t)0x80000000) /* Pin 31 selected */
使用举例:
比如翻转扩展IO引脚GPIO_PIN_23为高电平:HC574_TogglePin(GPIO_PIN_23)。
函数原型:
/* ********************************************************************************************************* * 函 数 名: 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; } }
函数描述:
此函数用于读取FMC扩展IO的状态。调用此函数前,要保证调用了函数bsp_InitExtIO进行了初始化。
函数参数:
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */ #define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */ #define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */ #define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */ #define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */ #define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */ #define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */ #define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */ #define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */ #define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */ #define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */ #define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */ #define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */ #define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */ #define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */ #define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */ #define GPIO_PIN_16 ((uint32_t)0x00010000) /* Pin 16 selected */ #define GPIO_PIN_17 ((uint32_t)0x00020000) /* Pin 17 selected */ #define GPIO_PIN_18 ((uint32_t)0x00040000) /* Pin 18 selected */ #define GPIO_PIN_19 ((uint32_t)0x00080000) /* Pin 19 selected */ #define GPIO_PIN_20 ((uint32_t)0x00100000) /* Pin 20 selected */ #define GPIO_PIN_21 ((uint32_t)0x00200000) /* Pin 21 selected */ #define GPIO_PIN_22 ((uint32_t)0x00400000) /* Pin 22 selected */ #define GPIO_PIN_23 ((uint32_t)0x00800000) /* Pin 23 selected */ #define GPIO_PIN_24 ((uint32_t)0x01000000) /* Pin 24 selected */ #define GPIO_PIN_25 ((uint32_t)0x02000000) /* Pin 25 selected */ #define GPIO_PIN_26 ((uint32_t)0x04000000) /* Pin 26 selected */ #define GPIO_PIN_27 ((uint32_t)0x08000000) /* Pin 27 selected */ #define GPIO_PIN_28 ((uint32_t)0x10000000) /* Pin 28 selected */ #define GPIO_PIN_29 ((uint32_t)0x20000000) /* Pin 29 selected */ #define GPIO_PIN_30 ((uint32_t)0x40000000) /* Pin 30 selected */ #define GPIO_PIN_31 ((uint32_t)0x80000000) /* Pin 31 selected */
使用举例:
比如获取扩展IO的GPIO_PIN_23高低电平状态,调用函数HC574_GetPin(GPIO_PIN_23)获取即可。
扩展IO的移植比较方便:
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
第1阶段,上电启动阶段:
第2阶段,进入main函数:
配套例子:
V7-027-FMC总线扩展32路高速IO
实验目的:
实验内容:
实验操作:
FMC扩展引脚20和23的位置:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ bsp_InitLed(); /* 初始化LED */ }
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置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); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 数 名: CPU_CACHE_Enable * 功能说明: 使能L1 Cache * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主程序实现如下操作:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按键代码 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */ bsp_SetTIMforInt(TIM6, 10000, 2, 0); /* 设置为10KHz频率定时器中断*/ /* 进入主程序循环体 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(2); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,开启TIM6的周期性中断*/ TIM6->DIER |= TIM_IT_UPDATE; break; case KEY_DOWN_K2: /* K2键按下,关闭TIM6的周期性中断*/ TIM6->DIER &= ~TIM_IT_UPDATE; break; default: /* 其它的键值不处理 */ break; } } } }
定时器6中断服务程序:
/* ********************************************************************************************************* * 函 数 名: TIM6_DAC_IRQHandler * 功能说明: TIM6定时中断服务程序 * 返 回 值: 无 ********************************************************************************************************* */ void TIM6_DAC_IRQHandler(void) { if((TIM6->SR & TIM_FLAG_UPDATE) != RESET) { /* 清除更新标志 */ TIM6->SR = ~ TIM_FLAG_UPDATE; /* 翻转FMC扩展引脚20和23脚 */ HC574_TogglePin(GPIO_PIN_23); HC574_TogglePin(GPIO_PIN_20); } }
配套例子:
V7-027-FMC总线扩展32路高速IO
实验目的:
实验内容:
实验操作:
FMC扩展引脚20和23的位置:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ bsp_InitLed(); /* 初始化LED */ }
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置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); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 数 名: CPU_CACHE_Enable * 功能说明: 使能L1 Cache * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主程序实现如下操作:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按键代码 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */ bsp_SetTIMforInt(TIM6, 10000, 2, 0); /* 设置为10KHz频率定时器中断*/ /* 进入主程序循环体 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(2); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,开启TIM6的周期性中断*/ TIM6->DIER |= TIM_IT_UPDATE; break; case KEY_DOWN_K2: /* K2键按下,关闭TIM6的周期性中断*/ TIM6->DIER &= ~TIM_IT_UPDATE; break; default: /* 其它的键值不处理 */ break; } } } }
定时器6中断服务程序:
/* ********************************************************************************************************* * 函 数 名: TIM6_DAC_IRQHandler * 功能说明: TIM6定时中断服务程序 * 返 回 值: 无 ********************************************************************************************************* */ void TIM6_DAC_IRQHandler(void) { if((TIM6->SR & TIM_FLAG_UPDATE) != RESET) { /* 清除更新标志 */ TIM6->SR = ~ TIM_FLAG_UPDATE; /* 翻转FMC扩展引脚20和23脚 */ HC574_TogglePin(GPIO_PIN_23); HC574_TogglePin(GPIO_PIN_20); } }
本章节就为大家讲解这么多,由于FMC总线可以扩展出32路高速IO且使用简单,所以实际项目中也比较有实用价值,望初学者熟练掌握。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。