|
|
@@ -108,19 +108,36 @@ MISO 逐位输入 ← 移位寄存器(8位) ← RX 缓冲区 → 读 DR
|
|
|
#define __SPI_H
|
|
|
|
|
|
#include "stm32f10x.h"
|
|
|
+#include "delay.h"
|
|
|
|
|
|
-// GPIO 引脚宏定义
|
|
|
-// PA5=SCK, PA6=MISO, PA7=MOSI, PA4=CS(片选)
|
|
|
-#define SPI_SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5) // |= SCK置1
|
|
|
-#define SPI_SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5) // &=~ SCK清0
|
|
|
-#define SPI_MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7) // |= MOSI置1
|
|
|
-#define SPI_MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7) // &=~ MOSI清0
|
|
|
-#define SPI_MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6) // & 读取MISO电平
|
|
|
-#define SPI_CS_HIGH (GPIOA->ODR |= GPIO_ODR_ODR4) // |= CS置1
|
|
|
-#define SPI_CS_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR4) // &=~ CS清0
|
|
|
+// 宏定义:SPI总线的操作
|
|
|
+// CS - PC13
|
|
|
+#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
|
|
|
+#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
|
|
|
|
|
|
+// SCK - PA5
|
|
|
+#define SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5)
|
|
|
+#define SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5)
|
|
|
+
|
|
|
+// MOSI - PA7
|
|
|
+#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
|
|
|
+#define MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7)
|
|
|
+
|
|
|
+// MISO - PA6,读取输入
|
|
|
+#define MISO_READ ( GPIOA->IDR & GPIO_IDR_IDR6 )
|
|
|
+
|
|
|
+// 产生标准的延迟时间
|
|
|
+#define SPI_DELAY Delay_us(5)
|
|
|
+
|
|
|
+// 初始化
|
|
|
void SPI_Init(void);
|
|
|
-uint8_t SPI_SendByte(uint8_t byte);
|
|
|
+
|
|
|
+// SPI通信的开启和关闭
|
|
|
+void SPI_Start(void);
|
|
|
+void SPI_Stop(void);
|
|
|
+
|
|
|
+// 一个时钟周期内,主机交换一个字节数据
|
|
|
+uint8_t SPI_SwapByte(uint8_t byte);
|
|
|
|
|
|
#endif
|
|
|
```
|
|
|
@@ -130,102 +147,256 @@ uint8_t SPI_SendByte(uint8_t byte);
|
|
|
```c
|
|
|
#include "spi.h"
|
|
|
|
|
|
-// SPI 初始化(模式0: CPOL=0, CPHA=0)
|
|
|
+// 初始化
|
|
|
void SPI_Init(void)
|
|
|
{
|
|
|
- // 1. 开启 GPIOA 时钟
|
|
|
+ // 1. 开启时钟
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
|
|
|
|
|
|
- // 2. 配置 PA4(CS), PA5(SCK), PA7(MOSI) 为推挽输出 50MHz
|
|
|
- // PA4: CRL 控制,Pin4 的 MODE 在 CRL 的第 18~19 位
|
|
|
- GPIOA->CRL |= GPIO_CRL_MODE4; // MODE4=11: 50MHz输出
|
|
|
- GPIOA->CRL &= ~GPIO_CRL_CNF4; // CNF4=00: 通用推挽
|
|
|
+ // 2. GPIO配置模式
|
|
|
+ // 2.1 CS - PC13,通用推挽输出,CNF = 00,MODE = 11
|
|
|
+ GPIOC->CRH |= GPIO_CRH_MODE13;
|
|
|
+ GPIOC->CRH &= ~GPIO_CRH_CNF13;
|
|
|
|
|
|
- // PA5(SCK)
|
|
|
+ // 2.2 SCK - PA5,通用推挽输出,CNF = 00,MODE = 11
|
|
|
GPIOA->CRL |= GPIO_CRL_MODE5;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF5;
|
|
|
|
|
|
- // PA7(MOSI)
|
|
|
+ // 2.3 MOSI - PA7,通用推挽输出,CNF = 00,MODE = 11
|
|
|
GPIOA->CRL |= GPIO_CRL_MODE7;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF7;
|
|
|
|
|
|
- // 3. 配置 PA6(MISO) 为浮空输入
|
|
|
- GPIOA->CRL &= ~GPIO_CRL_MODE6; // MODE6=00: 输入
|
|
|
- GPIOA->CRL &= ~GPIO_CRL_CNF6_1; // CNF6[1]=0
|
|
|
- GPIOA->CRL |= GPIO_CRL_CNF6_0; // CNF6[0]=1 → 浮空输入
|
|
|
+ // 2.4 MISO - PA6,浮空输入,CNF = 01,MODE = 00
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_MODE6;
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
|
|
|
+ GPIOA->CRL |= GPIO_CRL_CNF6_0;
|
|
|
+
|
|
|
+ // 3. SCK 保持空闲状态(模式0 - 低电平空闲)
|
|
|
+ SCK_LOW;
|
|
|
|
|
|
- // 4. 初始状态: SCK=低(CPOL=0), CS=高(不选中)
|
|
|
- SPI_SCK_LOW;
|
|
|
- SPI_CS_HIGH;
|
|
|
+ // 4. 片选初始为未选中
|
|
|
+ CS_HIGH;
|
|
|
+
|
|
|
+ // 5. 延时
|
|
|
+ SPI_DELAY;
|
|
|
+}
|
|
|
+
|
|
|
+// SPI通信的开启和关闭(通过片选信号控制)
|
|
|
+void SPI_Start(void)
|
|
|
+{
|
|
|
+ CS_LOW;
|
|
|
+}
|
|
|
+void SPI_Stop(void)
|
|
|
+{
|
|
|
+ CS_HIGH;
|
|
|
}
|
|
|
|
|
|
-// SPI 模式0: CPOL=0(空闲SCK低), CPHA=0(上升沿采集)
|
|
|
-// 发送 1 字节的同时接收 1 字节
|
|
|
-uint8_t SPI_SendByte(uint8_t byte)
|
|
|
+// 一个时钟周期内,主机交换一个字节数据
|
|
|
+uint8_t SPI_SwapByte(uint8_t byte)
|
|
|
{
|
|
|
- uint8_t i;
|
|
|
+ // 交换字节并返回接收数据
|
|
|
+ uint8_t rByte = 0x00;
|
|
|
|
|
|
- // 循环 8 次,每次处理 1 位
|
|
|
- for (i = 0; i < 8; i++)
|
|
|
+ // 通过循环每次读写一位
|
|
|
+ for (uint8_t i = 0; i < 8; i++)
|
|
|
{
|
|
|
- // 第 1 步: 在 SCK 低电平时改变 MOSI 数据
|
|
|
- // MSB first (先发最高位)
|
|
|
- if (byte & 0x80) // &测试: 最高位=1?
|
|
|
- SPI_MOSI_HIGH; // 输出高
|
|
|
+ // 1. 判断当前最高位,向MOSI输出相应电平
|
|
|
+ if ( byte & 0x80 )
|
|
|
+ {
|
|
|
+ MOSI_HIGH;
|
|
|
+ }
|
|
|
else
|
|
|
- SPI_MOSI_LOW; // 输出低
|
|
|
+ {
|
|
|
+ MOSI_LOW;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 移位
|
|
|
+ byte <<= 1;
|
|
|
+
|
|
|
+ // 3. 输出时钟,在第一个时钟沿产生上升沿
|
|
|
+ SCK_HIGH;
|
|
|
+ SPI_DELAY;
|
|
|
+
|
|
|
+ // 4. 移位,腾出最低位用来接收数据
|
|
|
+ rByte <<= 1;
|
|
|
+
|
|
|
+ // 5. 根据 MISO 电平来接收
|
|
|
+ if ( MISO_READ )
|
|
|
+ {
|
|
|
+ rByte |= 0x01; // 高电平则最低位置1
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 下降沿,为下次传输准备
|
|
|
+ SCK_LOW;
|
|
|
+ SPI_DELAY;
|
|
|
+ }
|
|
|
+
|
|
|
+ return rByte;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**文件:`stm32_base/36_spi_software_register/User/main.c`**
|
|
|
+
|
|
|
+```c
|
|
|
+#include "usart.h"
|
|
|
+#include "w25q32.h"
|
|
|
+
|
|
|
+int main(void)
|
|
|
+{
|
|
|
+ // 1. 初始化
|
|
|
+ USART_Init();
|
|
|
+ W25Q32_Init();
|
|
|
|
|
|
- byte <<= 1; // 左移1位,准备下一位
|
|
|
+ printf("中国芯SPI通信模块实验...\n");
|
|
|
|
|
|
- // 第 2 步: SCK 上升沿 → 从机采集 MOSI
|
|
|
- SPI_SCK_HIGH; // SCK上升沿
|
|
|
+ // 2. 获取JEDEC ID
|
|
|
+ uint8_t mID = 0;
|
|
|
+ uint16_t dID = 0;
|
|
|
|
|
|
- // 同时,主机在上升沿采集 MISO(从机的数据)
|
|
|
- if (SPI_MISO_READ) // 读取MISO电平
|
|
|
- byte |= 1; // 如果MISO=高,将当前位(已左移后的bit0)置1
|
|
|
+ W25Q32_ReadID(&mID, &dID);
|
|
|
+ printf("mid = %#x, did = %#x\n", mID, dID);
|
|
|
|
|
|
- // 第 3 步: SCK 下降沿 → 从机改变 MISO 数据
|
|
|
- SPI_SCK_LOW; // SCK下降沿
|
|
|
- }
|
|
|
+ // 3. 写数据前需要先擦除一个扇区
|
|
|
+ W25Q32_EraseSector(0, 0);
|
|
|
+
|
|
|
+ // 4. 页写入一页数据
|
|
|
+ W25Q32_WritePage(0, 0, 0, "12345678", 8);
|
|
|
+
|
|
|
+ // 5. 读取
|
|
|
+ uint8_t buff[10] = {0};
|
|
|
+ W25Q32_Read(0x0, buff, 8);
|
|
|
+
|
|
|
+ printf("buff: %s\n", buff);
|
|
|
|
|
|
- return byte; // 返回接收到的数据
|
|
|
+ while (1)
|
|
|
+ {
|
|
|
+ }
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+> **注意**:W25Q32 是 4MB(32Mbit)Flash,Page=256B,Sector=4KB,Block=64KB。与 W25Q64(8MB)指令集完全兼容,仅容量减半。
|
|
|
+
|
|
|
---
|
|
|
|
|
|
## 硬件 SPI(stm32_base/37_spi_hardware_register)
|
|
|
|
|
|
-硬件 SPI 使用 STM32 片内外设,更高效(硬件自动移位):
|
|
|
+硬件 SPI 使用 STM32 片内外设,更高效(硬件自动移位)。实际项目中 CS 片选仍用 GPIO(PC13)独立控制,SCK/MOSI 配置为复用推挽输出:
|
|
|
+
|
|
|
+**文件:`stm32_base/37_spi_hardware_register/Hardware/SPI/spi.h`**
|
|
|
+
|
|
|
+```c
|
|
|
+#ifndef __SPI_H
|
|
|
+#define __SPI_H
|
|
|
+
|
|
|
+#include "stm32f10x.h"
|
|
|
+
|
|
|
+// 宏定义:SPI总线的操作
|
|
|
+// CS - PC13
|
|
|
+#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
|
|
|
+#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
|
|
|
+
|
|
|
+// 初始化
|
|
|
+void SPI_Init(void);
|
|
|
+
|
|
|
+// SPI通信的开启和关闭
|
|
|
+void SPI_Start(void);
|
|
|
+void SPI_Stop(void);
|
|
|
+
|
|
|
+// 一个时钟周期内,主机交换一个字节数据
|
|
|
+uint8_t SPI_SwapByte(uint8_t byte);
|
|
|
+
|
|
|
+#endif
|
|
|
+```
|
|
|
+
|
|
|
+**文件:`stm32_base/37_spi_hardware_register/Hardware/SPI/spi.c`**
|
|
|
|
|
|
```c
|
|
|
-void SPI1_Init(void)
|
|
|
+#include "spi.h"
|
|
|
+
|
|
|
+// 初始化
|
|
|
+void SPI_Init(void)
|
|
|
{
|
|
|
- // 1. 开启 SPI1 + GPIOA 时钟
|
|
|
- RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // SPI1在APB2
|
|
|
+ // 1. 开启时钟
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
|
|
|
+
|
|
|
+ // 2. GPIO配置模式
|
|
|
+ // 2.1 CS - PC13,通用推挽输出,CNF = 00,MODE = 11
|
|
|
+ GPIOC->CRH |= GPIO_CRH_MODE13;
|
|
|
+ GPIOC->CRH &= ~GPIO_CRH_CNF13;
|
|
|
+
|
|
|
+ // 2.2 SCK - PA5,复用推挽输出,CNF = 10,MODE = 11
|
|
|
+ GPIOA->CRL |= GPIO_CRL_MODE5;
|
|
|
+ GPIOA->CRL |= GPIO_CRL_CNF5_1;
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
|
|
|
+
|
|
|
+ // 2.3 MOSI - PA7,复用推挽输出,CNF = 10,MODE = 11
|
|
|
+ GPIOA->CRL |= GPIO_CRL_MODE7;
|
|
|
+ GPIOA->CRL |= GPIO_CRL_CNF7_1;
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF7_0;
|
|
|
+
|
|
|
+ // 2.4 MISO - PA6,浮空输入,CNF = 01,MODE = 00
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_MODE6;
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
|
|
|
+ GPIOA->CRL |= GPIO_CRL_CNF6_0;
|
|
|
+
|
|
|
+ // 3. SPI模块参数配置
|
|
|
+ // 3.1 通信模式配置
|
|
|
+ // 3.1.1 设为主机模式
|
|
|
+ SPI1->CR1 |= SPI_CR1_MSTR;
|
|
|
+
|
|
|
+ // 3.1.2 选择软件控制片选,NSS电平为高电平
|
|
|
+ SPI1->CR1 |= SPI_CR1_SSM;
|
|
|
+ SPI1->CR1 |= SPI_CR1_SSI;
|
|
|
|
|
|
- // 2. PA5(SCK) 复用推挽, PA6(MISO) 浮空输入, PA7(MOSI) 复用推挽
|
|
|
-
|
|
|
- // 3. 配置 SPI1
|
|
|
- // CR1: CPOL=0, CPHA=0, BR=011(72/16=4.5MHz), MSTR=1(主机), SPE=1(使能)
|
|
|
- SPI1->CR1 = 0; // 先清0
|
|
|
- SPI1->CR1 |= SPI_CR1_CPOL & 0; // CPOL=0
|
|
|
- SPI1->CR1 |= SPI_CR1_CPHA & 0; // CPHA=0
|
|
|
- SPI1->CR1 |= SPI_CR1_BR_1 | SPI_CR1_BR_0; // BR=011: fPCLK/16
|
|
|
- SPI1->CR1 |= SPI_CR1_MSTR; // MSTR=1: 主机模式
|
|
|
- SPI1->CR1 |= SPI_CR1_SSI; // SSI=1: 软件NSS高电平
|
|
|
- SPI1->CR1 |= SPI_CR1_SSM; // SSM=1: 软件NSS管理
|
|
|
- SPI1->CR1 |= SPI_CR1_SPE; // SPE=1: SPI使能
|
|
|
+ // 3.1.2 SPI通信模式设为模式0:CPOL = 0,CPHA = 0
|
|
|
+ SPI1->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);
|
|
|
+
|
|
|
+ // 3.2 设置波特率,时钟分频系数:001 - 4分频,18MHz
|
|
|
+ SPI1->CR1 &= ~SPI_CR1_BR;
|
|
|
+ SPI1->CR1 |= SPI_CR1_BR_0;
|
|
|
+
|
|
|
+ // 3.3 帧格式配置
|
|
|
+ // 3.3.1 帧长度:0 - 8位
|
|
|
+ SPI1->CR1 &= ~SPI_CR1_DFF;
|
|
|
+
|
|
|
+ // 3.3.2 数据传输顺序:0 - MSB在前
|
|
|
+ SPI1->CR1 &= ~SPI_CR1_LSBFIRST;
|
|
|
+
|
|
|
+ // 3.4 使能SPI
|
|
|
+ SPI1->CR1 |= SPI_CR1_SPE;
|
|
|
+}
|
|
|
+
|
|
|
+// SPI通信的开启和关闭(通过片选信号控制)
|
|
|
+void SPI_Start(void)
|
|
|
+{
|
|
|
+ CS_LOW;
|
|
|
+}
|
|
|
+void SPI_Stop(void)
|
|
|
+{
|
|
|
+ CS_HIGH;
|
|
|
}
|
|
|
|
|
|
-// 硬件 SPI 收发
|
|
|
-uint8_t SPI1_SendByte(uint8_t byte)
|
|
|
+// 一个时钟周期内,主机交换一个字节数据
|
|
|
+uint8_t SPI_SwapByte(uint8_t byte)
|
|
|
{
|
|
|
- while (!(SPI1->SR & SPI_SR_TXE)) {} // 等待 TX 缓冲区空
|
|
|
- SPI1->DR = byte; // 写入数据 → 开始发送
|
|
|
- while (!(SPI1->SR & SPI_SR_RXNE)) {} // 等待 RX 缓冲区非空
|
|
|
- return SPI1->DR; // 读取接收数据
|
|
|
+ // 1. 将要发送的数据写入发送缓冲区
|
|
|
+ // 1.1 等待发送缓冲区为空(TXE = 1)
|
|
|
+ while ((SPI1->SR & SPI_SR_TXE) == 0 )
|
|
|
+ {}
|
|
|
+
|
|
|
+ // 1.2 将数据写入DR
|
|
|
+ SPI1->DR = byte;
|
|
|
+
|
|
|
+ // 2. 获取接收到的数据并返回
|
|
|
+ // 2.1 等待接收缓冲区非空(RXNE = 1)
|
|
|
+ while ( (SPI1->SR & SPI_SR_RXNE) == 0 )
|
|
|
+ {}
|
|
|
+
|
|
|
+ // 2.2 将DR中的数据返回
|
|
|
+ return (uint8_t)(SPI1->DR & 0xff);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
@@ -258,7 +429,9 @@ uint8_t SPI1_SendByte(uint8_t byte)
|
|
|
| CHIP_ERASE | 0xC7 | 全片擦除 | — |
|
|
|
| RDID | 0x9F | 读芯片 ID | 3字节返回 |
|
|
|
|
|
|
-### W25Q64 擦写(读写前必须擦除)
|
|
|
+### W25Q64/W25Q32 擦写(读写前必须擦除)
|
|
|
+
|
|
|
+> **注意**:实际项目中使用 **W25Q32**(4MB/32Mbit),与 W25Q64 指令集完全兼容,容量减半。以下代码使用 `SPI_SwapByte` 函数(软件/硬件 SPI 驱动均为此函数名)。
|
|
|
|
|
|
```c
|
|
|
// W25Q64 写入流程:
|
|
|
@@ -272,8 +445,8 @@ uint8_t W25Q64_ReadSR(void)
|
|
|
{
|
|
|
uint8_t sr;
|
|
|
CS_LOW;
|
|
|
- SPI_SendByte(RDSR); // 发指令 0x05
|
|
|
- sr = SPI_SendByte(0xFF); // 发占住位,收状态
|
|
|
+ SPI_SwapByte(RDSR); // 发指令 0x05
|
|
|
+ sr = SPI_SwapByte(0xFF); // 发占位字节,收状态
|
|
|
CS_HIGH;
|
|
|
return sr;
|
|
|
}
|
|
|
@@ -287,23 +460,23 @@ void W25Q64_WritePage(uint32_t addr, uint8_t *data, uint16_t len)
|
|
|
{
|
|
|
W25Q64_WaitBusy(); // 等上次完成
|
|
|
CS_LOW;
|
|
|
- SPI_SendByte(WREN); // 写使能
|
|
|
+ SPI_SwapByte(WREN); // 写使能
|
|
|
CS_HIGH;
|
|
|
|
|
|
CS_LOW;
|
|
|
- SPI_SendByte(PAGE_PROG); // 页编程指令
|
|
|
- SPI_SendByte(addr >> 16); // 地址高8位
|
|
|
- SPI_SendByte(addr >> 8); // 地址中8位
|
|
|
- SPI_SendByte(addr); // 地址低8位
|
|
|
+ SPI_SwapByte(PAGE_PROG); // 页编程指令
|
|
|
+ SPI_SwapByte(addr >> 16); // 地址高8位
|
|
|
+ SPI_SwapByte(addr >> 8); // 地址中8位
|
|
|
+ SPI_SwapByte(addr); // 地址低8位
|
|
|
for (uint16_t i = 0; i < len; i++) {
|
|
|
- SPI_SendByte(data[i]); // 发送数据
|
|
|
+ SPI_SwapByte(data[i]); // 发送数据
|
|
|
}
|
|
|
CS_HIGH;
|
|
|
W25Q64_WaitBusy(); // 等写入完成
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-> **注意**:W25Q64 不支持"写覆盖"——必须先擦除再写。最小擦除单位是扇区(4KB)。
|
|
|
+> **注意**:W25Q64/W25Q32 不支持"写覆盖"——必须先擦除再写。最小擦除单位是扇区(4KB)。
|
|
|
> 页编程不能跨页(256 字节边界)。如果数据跨页,需要分多次写入。
|
|
|
|
|
|
---
|
|
|
@@ -364,6 +537,176 @@ Bank1 分为 4 个子区(片选 NE1~NE4),各 64MB:
|
|
|
| 8:15 | DATAST | 数据保持时间(1~255个HCLK周期) |
|
|
|
| 16:19 | BUSTURN | 总线周转时间 |
|
|
|
|
|
|
+### 实验:FSMC 扩展 SRAM(寄存器版)
|
|
|
+
|
|
|
+**项目路径**:`stm32_base/39_fsmc_sram_register`
|
|
|
+
|
|
|
+使用 FSMC 的 Bank1 子区3(NE3,地址范围 0x68000000~0x6BFFFFFF),16 位数据总线连接外部 SRAM。
|
|
|
+
|
|
|
+**文件:`stm32_base/39_fsmc_sram_register/Hardware/FSMC/fsmc.h`**
|
|
|
+
|
|
|
+```c
|
|
|
+#ifndef __FSMC_H
|
|
|
+#define __FSMC_H
|
|
|
+
|
|
|
+#include "stm32f10x.h"
|
|
|
+
|
|
|
+// 初始化
|
|
|
+void FSMC_Init(void);
|
|
|
+
|
|
|
+#endif
|
|
|
+```
|
|
|
+
|
|
|
+**文件:`stm32_base/39_fsmc_sram_register/Hardware/FSMC/fsmc.c`**
|
|
|
+
|
|
|
+```c
|
|
|
+#include "fsmc.h"
|
|
|
+
|
|
|
+void FSMC_GPIO_Init(void);
|
|
|
+
|
|
|
+// 初始化
|
|
|
+void FSMC_Init(void)
|
|
|
+{
|
|
|
+ // 1. 开启时钟
|
|
|
+ RCC->AHBENR |= RCC_AHBENR_FSMCEN;
|
|
|
+
|
|
|
+ RCC->APB2ENR |= (RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPEEN |
|
|
|
+ RCC_APB2ENR_IOPFEN | RCC_APB2ENR_IOPGEN);
|
|
|
+
|
|
|
+ // 2. GPIO模式配置
|
|
|
+ FSMC_GPIO_Init();
|
|
|
+
|
|
|
+ // 3. FSMC寄存器配置
|
|
|
+ // 3.1 BCR3 - BTCR[4]
|
|
|
+ // 3.1.1 存储区使能
|
|
|
+ FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MBKEN;
|
|
|
+
|
|
|
+ // 3.1.2 设置存储器类型:MTYP = 00,SRAM/ROM
|
|
|
+ FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MTYP;
|
|
|
+
|
|
|
+ // 3.1.3 禁止Flash访问
|
|
|
+ FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_FACCEN;
|
|
|
+
|
|
|
+ // 3.1.4 地址数据复用功能,不使能
|
|
|
+ FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MUXEN;
|
|
|
+
|
|
|
+ // 3.1.5 设置总线宽度:MWID = 01,16位
|
|
|
+ FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MWID_1;
|
|
|
+ FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MWID_0;
|
|
|
+
|
|
|
+ // 3.1.6 使能写操作
|
|
|
+ FSMC_Bank1->BTCR[4] |= FSMC_BCR3_WREN;
|
|
|
+
|
|
|
+ // 3.2 BTR - BTCR[5]
|
|
|
+ // 3.2.1 地址建立时间 ADDSET
|
|
|
+ FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_ADDSET;
|
|
|
+
|
|
|
+ // 3.2.2 数据建立时间 DATAST
|
|
|
+ FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_DATAST;
|
|
|
+ FSMC_Bank1->BTCR[5] |= (71 << 8);
|
|
|
+}
|
|
|
+
|
|
|
+// 配置GPIO引脚,均为复用推挽输出(CNF = 10,MODE = 11)
|
|
|
+void FSMC_GPIO_Init(void)
|
|
|
+{
|
|
|
+ // 1. 地址线 A0 ~ A18
|
|
|
+ // MODE = 11
|
|
|
+ GPIOF->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2 |
|
|
|
+ GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
|
+ GPIOF->CRH |= (GPIO_CRH_MODE12 | GPIO_CRH_MODE13 |
|
|
|
+ GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
|
|
|
+ GPIOG->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2 |
|
|
|
+ GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
|
+ GPIOD->CRH |= (GPIO_CRH_MODE11 | GPIO_CRH_MODE12 | GPIO_CRH_MODE13);
|
|
|
+
|
|
|
+ // CNF = 10(复用推挽输出)
|
|
|
+ GPIOF->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1 | GPIO_CRL_CNF2_1 |
|
|
|
+ GPIO_CRL_CNF3_1 | GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1);
|
|
|
+ GPIOF->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0 | GPIO_CRL_CNF2_0 |
|
|
|
+ GPIO_CRL_CNF3_0 | GPIO_CRL_CNF4_0 | GPIO_CRL_CNF5_0);
|
|
|
+ // ...(其余地址线引脚配置类似)
|
|
|
+
|
|
|
+ // 2. 数据线 D0 ~ D15(MODE = 11,CNF = 10)
|
|
|
+ GPIOD->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
|
|
|
+ GPIOD->CRH |= (GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_MODE10 |
|
|
|
+ GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
|
|
|
+ GPIOE->CRL |= GPIO_CRL_MODE7;
|
|
|
+ GPIOE->CRH |= (GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_MODE10 |
|
|
|
+ GPIO_CRH_MODE11 | GPIO_CRH_MODE12 | GPIO_CRH_MODE13 |
|
|
|
+ GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
|
|
|
+ // CNF = 10
|
|
|
+ GPIOD->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1);
|
|
|
+ GPIOD->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
|
|
|
+ // ...
|
|
|
+
|
|
|
+ // 3. 控制线
|
|
|
+ // PD4 - NOE(读使能),PD5 - NWE(写使能)
|
|
|
+ GPIOD->CRL |= (GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
|
+ GPIOD->CRL |= (GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1);
|
|
|
+ GPIOD->CRL &= ~(GPIO_CRL_CNF4_0 | GPIO_CRL_CNF5_0);
|
|
|
+
|
|
|
+ // PG10 - NE3(片选)
|
|
|
+ GPIOG->CRH |= GPIO_CRH_MODE10;
|
|
|
+ GPIOG->CRH |= GPIO_CRH_CNF10_1;
|
|
|
+ GPIOG->CRH &= ~GPIO_CRH_CNF10_0;
|
|
|
+
|
|
|
+ // PE0, PE1 - NBL(字节掩码,16位SRAM的高低位使能)
|
|
|
+ GPIOE->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
|
|
|
+ GPIOE->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1);
|
|
|
+ GPIOE->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
|
|
|
+}
|
|
|
+
|
|
|
+// 配置完成后,FSMC 区域3的内存映射地址为 0x68000000
|
|
|
+// 写: *(uint16_t *)0x68000000 = data;
|
|
|
+// 读: data = *(uint16_t *)0x68000000;
|
|
|
+```
|
|
|
+
|
|
|
+**文件:`stm32_base/39_fsmc_sram_register/User/main.c`**
|
|
|
+
|
|
|
+```c
|
|
|
+#include "usart.h"
|
|
|
+#include "fsmc.h"
|
|
|
+
|
|
|
+// 方法1:使用关键字attribute指定全局变量的地址
|
|
|
+uint8_t v1 __attribute__((at(0x68000000)));
|
|
|
+uint8_t v2 __attribute__((at(0x68000004)));
|
|
|
+
|
|
|
+uint16_t v3 = 30;
|
|
|
+
|
|
|
+int main(void)
|
|
|
+{
|
|
|
+ // 1. 初始化
|
|
|
+ USART_Init();
|
|
|
+ FSMC_Init();
|
|
|
+
|
|
|
+ printf("中国芯FSMC扩展SRAM实验...\n");
|
|
|
+
|
|
|
+ v1 = 10;
|
|
|
+ v2 = 20;
|
|
|
+
|
|
|
+ // 测试局部变量指定地址
|
|
|
+ uint8_t v4 __attribute__((at(0x68000008)));
|
|
|
+ v4 = 40;
|
|
|
+ uint8_t v5 = 50;
|
|
|
+
|
|
|
+ // 打印地址验证
|
|
|
+ printf("v1 = %d, @%p\n", v1, &v1);
|
|
|
+ printf("v2 = %d, @%p\n", v2, &v2);
|
|
|
+ printf("v3 = %d, @%p\n", v3, &v3);
|
|
|
+ printf("v4 = %d, @%p\n", v4, &v4);
|
|
|
+ printf("v5 = %d, @%p\n", v5, &v5);
|
|
|
+
|
|
|
+ // 方法2:指针直接访问
|
|
|
+ uint8_t *p = (uint8_t *)0x68000FFF;
|
|
|
+ *p = 100;
|
|
|
+ printf("*p = %d, @%p\n", *p, p);
|
|
|
+
|
|
|
+ while (1)
|
|
|
+ {
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
### FSMC + LCD 应用
|
|
|
|
|
|
将 LCD 连接到 FSMC,利用地址线 A0 区分命令和数据:
|