|
@@ -95,13 +95,15 @@ MISO 逐位输入 ← 移位寄存器(8位) ← RX 缓冲区 → 读 DR
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 软件 SPI 实现
|
|
|
|
|
|
|
+## 项目 36:软件 SPI(寄存器版)
|
|
|
|
|
|
|
|
-### 软件设计(寄存器版 — 软件模拟 SPI)
|
|
|
|
|
|
|
+> 使用 GPIO 位操作模拟 SPI 时序,纯软件实现,不依赖硬件 SPI 外设。
|
|
|
|
|
|
|
|
-**项目路径**:`stm32/36_spi_software_register`
|
|
|
|
|
|
|
+**项目路径**:`上部-基础篇\03_代码\stm32\36_spi_software_register`
|
|
|
|
|
|
|
|
-**文件:`stm32/36_spi_software_register/Hardware/SPI/spi.h`**
|
|
|
|
|
|
|
+### spi.h
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Hardware/SPI/spi.h`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#ifndef __SPI_H
|
|
#ifndef __SPI_H
|
|
@@ -110,44 +112,40 @@ MISO 逐位输入 ← 移位寄存器(8位) ← RX 缓冲区 → 读 DR
|
|
|
#include "stm32f10x.h"
|
|
#include "stm32f10x.h"
|
|
|
#include "delay.h"
|
|
#include "delay.h"
|
|
|
|
|
|
|
|
-// 宏定义:SPI总线的操作
|
|
|
|
|
|
|
+// 宏定义:控制各信号线高低电平
|
|
|
// CS - PC13
|
|
// CS - PC13
|
|
|
#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
|
|
#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
|
|
|
-#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
|
|
|
|
|
|
|
+#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
|
|
|
|
|
|
|
|
// SCK - PA5
|
|
// SCK - PA5
|
|
|
#define SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5)
|
|
#define SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5)
|
|
|
-#define SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5)
|
|
|
|
|
|
|
+#define SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5)
|
|
|
|
|
|
|
|
// MOSI - PA7
|
|
// MOSI - PA7
|
|
|
#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
|
|
#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
|
|
|
-#define MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7)
|
|
|
|
|
|
|
+#define MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7)
|
|
|
|
|
|
|
|
// MISO - PA6,读取输入
|
|
// MISO - PA6,读取输入
|
|
|
-#define MISO_READ ( GPIOA->IDR & GPIO_IDR_IDR6 )
|
|
|
|
|
|
|
+#define MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6)
|
|
|
|
|
|
|
|
// 产生标准的延迟时间
|
|
// 产生标准的延迟时间
|
|
|
#define SPI_DELAY Delay_us(5)
|
|
#define SPI_DELAY Delay_us(5)
|
|
|
|
|
|
|
|
-// 初始化
|
|
|
|
|
void SPI_Init(void);
|
|
void SPI_Init(void);
|
|
|
-
|
|
|
|
|
-// SPI通信的开启和关闭
|
|
|
|
|
void SPI_Start(void);
|
|
void SPI_Start(void);
|
|
|
void SPI_Stop(void);
|
|
void SPI_Stop(void);
|
|
|
-
|
|
|
|
|
-// 一个时钟周期内,主机交换一个字节数据
|
|
|
|
|
uint8_t SPI_SwapByte(uint8_t byte);
|
|
uint8_t SPI_SwapByte(uint8_t byte);
|
|
|
|
|
|
|
|
#endif
|
|
#endif
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**文件:`stm32/36_spi_software_register/Hardware/SPI/spi.c`**
|
|
|
|
|
|
|
+### spi.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Hardware/SPI/spi.c`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#include "spi.h"
|
|
#include "spi.h"
|
|
|
|
|
|
|
|
-// 初始化
|
|
|
|
|
void SPI_Init(void)
|
|
void SPI_Init(void)
|
|
|
{
|
|
{
|
|
|
// 1. 开启时钟
|
|
// 1. 开启时钟
|
|
@@ -155,19 +153,19 @@ void SPI_Init(void)
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
|
|
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
|
|
|
|
|
|
|
|
// 2. GPIO配置模式
|
|
// 2. GPIO配置模式
|
|
|
- // 2.1 CS - PC13,通用推挽输出,CNF = 00,MODE = 11
|
|
|
|
|
|
|
+ // CS - PC13,通用推挽输出,CNF = 00,MODE = 11
|
|
|
GPIOC->CRH |= GPIO_CRH_MODE13;
|
|
GPIOC->CRH |= GPIO_CRH_MODE13;
|
|
|
GPIOC->CRH &= ~GPIO_CRH_CNF13;
|
|
GPIOC->CRH &= ~GPIO_CRH_CNF13;
|
|
|
|
|
|
|
|
- // 2.2 SCK - PA5,通用推挽输出,CNF = 00,MODE = 11
|
|
|
|
|
|
|
+ // SCK - PA5,通用推挽输出,CNF = 00,MODE = 11
|
|
|
GPIOA->CRL |= GPIO_CRL_MODE5;
|
|
GPIOA->CRL |= GPIO_CRL_MODE5;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF5;
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF5;
|
|
|
|
|
|
|
|
- // 2.3 MOSI - PA7,通用推挽输出,CNF = 00,MODE = 11
|
|
|
|
|
|
|
+ // MOSI - PA7,通用推挽输出,CNF = 00,MODE = 11
|
|
|
GPIOA->CRL |= GPIO_CRL_MODE7;
|
|
GPIOA->CRL |= GPIO_CRL_MODE7;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF7;
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF7;
|
|
|
|
|
|
|
|
- // 2.4 MISO - PA6,浮空输入,CNF = 01,MODE = 00
|
|
|
|
|
|
|
+ // MISO - PA6,浮空输入,CNF = 01,MODE = 00
|
|
|
GPIOA->CRL &= ~GPIO_CRL_MODE6;
|
|
GPIOA->CRL &= ~GPIO_CRL_MODE6;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
|
|
|
GPIOA->CRL |= GPIO_CRL_CNF6_0;
|
|
GPIOA->CRL |= GPIO_CRL_CNF6_0;
|
|
@@ -182,108 +180,89 @@ void SPI_Init(void)
|
|
|
SPI_DELAY;
|
|
SPI_DELAY;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SPI通信的开启和关闭(通过片选信号控制)
|
|
|
|
|
-void SPI_Start(void)
|
|
|
|
|
-{
|
|
|
|
|
- CS_LOW;
|
|
|
|
|
-}
|
|
|
|
|
-void SPI_Stop(void)
|
|
|
|
|
-{
|
|
|
|
|
- CS_HIGH;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+void SPI_Start(void) { CS_LOW; }
|
|
|
|
|
+void SPI_Stop(void) { CS_HIGH; }
|
|
|
|
|
|
|
|
-// 一个时钟周期内,主机交换一个字节数据
|
|
|
|
|
uint8_t SPI_SwapByte(uint8_t byte)
|
|
uint8_t SPI_SwapByte(uint8_t byte)
|
|
|
{
|
|
{
|
|
|
- // 交换字节并返回接收数据
|
|
|
|
|
uint8_t rByte = 0x00;
|
|
uint8_t rByte = 0x00;
|
|
|
|
|
|
|
|
- // 通过循环每次读写一位
|
|
|
|
|
for (uint8_t i = 0; i < 8; i++)
|
|
for (uint8_t i = 0; i < 8; i++)
|
|
|
{
|
|
{
|
|
|
// 1. 判断当前最高位,向MOSI输出相应电平
|
|
// 1. 判断当前最高位,向MOSI输出相应电平
|
|
|
- if ( byte & 0x80 )
|
|
|
|
|
- {
|
|
|
|
|
- MOSI_HIGH;
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- MOSI_LOW;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (byte & 0x80) MOSI_HIGH;
|
|
|
|
|
+ else MOSI_LOW;
|
|
|
|
|
|
|
|
- // 2. 移位
|
|
|
|
|
byte <<= 1;
|
|
byte <<= 1;
|
|
|
-
|
|
|
|
|
- // 3. 输出时钟,在第一个时钟沿产生上升沿
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 输出时钟上升沿
|
|
|
SCK_HIGH;
|
|
SCK_HIGH;
|
|
|
SPI_DELAY;
|
|
SPI_DELAY;
|
|
|
|
|
|
|
|
- // 4. 移位,腾出最低位用来接收数据
|
|
|
|
|
|
|
+ // 3. 移位,腾出最低位用来接收
|
|
|
rByte <<= 1;
|
|
rByte <<= 1;
|
|
|
|
|
+ if (MISO_READ) rByte |= 0x01;
|
|
|
|
|
|
|
|
- // 5. 根据 MISO 电平来接收
|
|
|
|
|
- if ( MISO_READ )
|
|
|
|
|
- {
|
|
|
|
|
- rByte |= 0x01; // 高电平则最低位置1
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 6. 下降沿,为下次传输准备
|
|
|
|
|
|
|
+ // 4. 下降沿,为下次传输准备
|
|
|
SCK_LOW;
|
|
SCK_LOW;
|
|
|
SPI_DELAY;
|
|
SPI_DELAY;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return rByte;
|
|
return rByte;
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**文件:`stm32/36_spi_software_register/User/main.c`**
|
|
|
|
|
|
|
+### main.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`User/main.c`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#include "usart.h"
|
|
#include "usart.h"
|
|
|
#include "w25q32.h"
|
|
#include "w25q32.h"
|
|
|
|
|
+#include <string.h>
|
|
|
|
|
|
|
|
int main(void)
|
|
int main(void)
|
|
|
{
|
|
{
|
|
|
- // 1. 初始化
|
|
|
|
|
- USART_Init();
|
|
|
|
|
- W25Q32_Init();
|
|
|
|
|
-
|
|
|
|
|
- printf("中国芯SPI通信模块实验...\n");
|
|
|
|
|
|
|
+ USART_Init();
|
|
|
|
|
+ W25Q32_Init();
|
|
|
|
|
|
|
|
- // 2. 获取JEDEC ID
|
|
|
|
|
- uint8_t mID = 0;
|
|
|
|
|
- uint16_t dID = 0;
|
|
|
|
|
|
|
+ printf("中国芯SPI通信模块实验开始...\n");
|
|
|
|
|
|
|
|
- W25Q32_ReadID(&mID, &dID);
|
|
|
|
|
- printf("mid = %#x, did = %#x\n", mID, dID);
|
|
|
|
|
|
|
+ // 读取ID进行检验
|
|
|
|
|
+ uint8_t mid = 0;
|
|
|
|
|
+ uint16_t did = 0;
|
|
|
|
|
+ W25Q32_ReadID(&mid, &did);
|
|
|
|
|
+ printf("mid = %#x, did = %#x\n", mid, did);
|
|
|
|
|
|
|
|
- // 3. 写数据前需要先擦除一个扇区
|
|
|
|
|
- W25Q32_EraseSector(0, 0);
|
|
|
|
|
|
|
+ // 扇区擦除
|
|
|
|
|
+ W25Q32_EraseSector(0, 0);
|
|
|
|
|
|
|
|
- // 4. 页写入一页数据
|
|
|
|
|
- W25Q32_WritePage(0, 0, 0, "12345678", 8);
|
|
|
|
|
|
|
+ // 页写入
|
|
|
|
|
+ W25Q32_PageWrite(0, 0, 0, "12345678", 8);
|
|
|
|
|
|
|
|
- // 5. 读取
|
|
|
|
|
- uint8_t buff[10] = {0};
|
|
|
|
|
- W25Q32_Read(0x0, buff, 8);
|
|
|
|
|
|
|
+ // 读取
|
|
|
|
|
+ uint8_t buffer[10] = {0};
|
|
|
|
|
+ W25Q32_Read(0, 0, 0, 2, buffer, 6);
|
|
|
|
|
|
|
|
- printf("buff: %s\n", buff);
|
|
|
|
|
|
|
+ printf("buffer = %s\n", buffer);
|
|
|
|
|
|
|
|
- while (1)
|
|
|
|
|
- {
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ while (1) {}
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-> **注意**:W25Q32 是 4MB(32Mbit)Flash,Page=256B,Sector=4KB,Block=64KB。与 W25Q64(8MB)指令集完全兼容,仅容量减半。
|
|
|
|
|
|
|
+> **软件 SPI vs 硬件 SPI**:软件 SPI 不依赖片内外设,任意 GPIO 均可模拟,但速度受限(约 2~4MHz)。适合低速设备或硬件 SPI 引脚被占用的场景。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 硬件 SPI(stm32/37_spi_hardware_register)
|
|
|
|
|
|
|
+## 项目 37:硬件 SPI(寄存器版)
|
|
|
|
|
+
|
|
|
|
|
+> 使用 STM32 片内 SPI 外设,硬件自动移位,效率更高。CS 片选仍用 GPIO(PC13)独立控制,SCK/MOSI 配置为复用推挽输出。
|
|
|
|
|
+
|
|
|
|
|
+**项目路径**:`上部-基础篇\03_代码\stm32\37_spi_hardware_register`
|
|
|
|
|
|
|
|
-硬件 SPI 使用 STM32 片内外设,更高效(硬件自动移位)。实际项目中 CS 片选仍用 GPIO(PC13)独立控制,SCK/MOSI 配置为复用推挽输出:
|
|
|
|
|
|
|
+### spi.h
|
|
|
|
|
|
|
|
-**文件:`stm32/37_spi_hardware_register/Hardware/SPI/spi.h`**
|
|
|
|
|
|
|
+**文件**:`Hardware/SPI/spi.h`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#ifndef __SPI_H
|
|
#ifndef __SPI_H
|
|
@@ -291,30 +270,25 @@ int main(void)
|
|
|
|
|
|
|
|
#include "stm32f10x.h"
|
|
#include "stm32f10x.h"
|
|
|
|
|
|
|
|
-// 宏定义:SPI总线的操作
|
|
|
|
|
-// CS - PC13
|
|
|
|
|
|
|
+// CS - PC13(片选仍用GPIO独立控制)
|
|
|
#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
|
|
#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
|
|
|
-#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
|
|
|
|
|
|
|
+#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
|
|
|
|
|
|
|
|
-// 初始化
|
|
|
|
|
void SPI_Init(void);
|
|
void SPI_Init(void);
|
|
|
-
|
|
|
|
|
-// SPI通信的开启和关闭
|
|
|
|
|
void SPI_Start(void);
|
|
void SPI_Start(void);
|
|
|
void SPI_Stop(void);
|
|
void SPI_Stop(void);
|
|
|
-
|
|
|
|
|
-// 一个时钟周期内,主机交换一个字节数据
|
|
|
|
|
uint8_t SPI_SwapByte(uint8_t byte);
|
|
uint8_t SPI_SwapByte(uint8_t byte);
|
|
|
|
|
|
|
|
#endif
|
|
#endif
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**文件:`stm32/37_spi_hardware_register/Hardware/SPI/spi.c`**
|
|
|
|
|
|
|
+### spi.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Hardware/SPI/spi.c`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#include "spi.h"
|
|
#include "spi.h"
|
|
|
|
|
|
|
|
-// 初始化
|
|
|
|
|
void SPI_Init(void)
|
|
void SPI_Init(void)
|
|
|
{
|
|
{
|
|
|
// 1. 开启时钟
|
|
// 1. 开启时钟
|
|
@@ -323,95 +297,235 @@ void SPI_Init(void)
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
|
|
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
|
|
|
|
|
|
|
|
// 2. GPIO配置模式
|
|
// 2. GPIO配置模式
|
|
|
- // 2.1 CS - PC13,通用推挽输出,CNF = 00,MODE = 11
|
|
|
|
|
|
|
+ // PC13:通用推挽输出(CS)
|
|
|
GPIOC->CRH |= GPIO_CRH_MODE13;
|
|
GPIOC->CRH |= GPIO_CRH_MODE13;
|
|
|
GPIOC->CRH &= ~GPIO_CRH_CNF13;
|
|
GPIOC->CRH &= ~GPIO_CRH_CNF13;
|
|
|
|
|
|
|
|
- // 2.2 SCK - PA5,复用推挽输出,CNF = 10,MODE = 11
|
|
|
|
|
|
|
+ // PA5(SCK)、PA7(MOSI):复用推挽输出,CNF = 10
|
|
|
GPIOA->CRL |= GPIO_CRL_MODE5;
|
|
GPIOA->CRL |= GPIO_CRL_MODE5;
|
|
|
GPIOA->CRL |= GPIO_CRL_CNF5_1;
|
|
GPIOA->CRL |= GPIO_CRL_CNF5_1;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
|
|
|
|
|
|
|
|
- // 2.3 MOSI - PA7,复用推挽输出,CNF = 10,MODE = 11
|
|
|
|
|
GPIOA->CRL |= GPIO_CRL_MODE7;
|
|
GPIOA->CRL |= GPIO_CRL_MODE7;
|
|
|
GPIOA->CRL |= GPIO_CRL_CNF7_1;
|
|
GPIOA->CRL |= GPIO_CRL_CNF7_1;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF7_0;
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF7_0;
|
|
|
|
|
|
|
|
- // 2.4 MISO - PA6,浮空输入,CNF = 01,MODE = 00
|
|
|
|
|
|
|
+ // PA6(MISO):浮空输入
|
|
|
GPIOA->CRL &= ~GPIO_CRL_MODE6;
|
|
GPIOA->CRL &= ~GPIO_CRL_MODE6;
|
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
|
|
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
|
|
|
GPIOA->CRL |= GPIO_CRL_CNF6_0;
|
|
GPIOA->CRL |= GPIO_CRL_CNF6_0;
|
|
|
|
|
|
|
|
// 3. SPI模块参数配置
|
|
// 3. SPI模块参数配置
|
|
|
- // 3.1 通信模式配置
|
|
|
|
|
- // 3.1.1 设为主机模式
|
|
|
|
|
|
|
+ // 3.1 设为主机模式
|
|
|
SPI1->CR1 |= SPI_CR1_MSTR;
|
|
SPI1->CR1 |= SPI_CR1_MSTR;
|
|
|
|
|
|
|
|
- // 3.1.2 选择软件控制片选,NSS电平为高电平
|
|
|
|
|
|
|
+ // 3.2 软件控制片选,NSS电平为高
|
|
|
SPI1->CR1 |= SPI_CR1_SSM;
|
|
SPI1->CR1 |= SPI_CR1_SSM;
|
|
|
SPI1->CR1 |= SPI_CR1_SSI;
|
|
SPI1->CR1 |= SPI_CR1_SSI;
|
|
|
|
|
|
|
|
- // 3.1.2 SPI通信模式设为模式0:CPOL = 0,CPHA = 0
|
|
|
|
|
- SPI1->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);
|
|
|
|
|
|
|
+ // 3.3 模式0:CPOL = 0,CPHA = 0
|
|
|
|
|
+ SPI1->CR1 &= ~SPI_CR1_CPOL;
|
|
|
|
|
+ SPI1->CR1 &= ~SPI_CR1_CPHA;
|
|
|
|
|
|
|
|
- // 3.2 设置波特率,时钟分频系数:001 - 4分频,18MHz
|
|
|
|
|
|
|
+ // 3.4 波特率:4分频(18MHz,当APB2=72MHz时)
|
|
|
SPI1->CR1 &= ~SPI_CR1_BR;
|
|
SPI1->CR1 &= ~SPI_CR1_BR;
|
|
|
SPI1->CR1 |= SPI_CR1_BR_0;
|
|
SPI1->CR1 |= SPI_CR1_BR_0;
|
|
|
|
|
|
|
|
- // 3.3 帧格式配置
|
|
|
|
|
- // 3.3.1 帧长度:0 - 8位
|
|
|
|
|
|
|
+ // 3.5 帧格式:8位,MSB在前
|
|
|
SPI1->CR1 &= ~SPI_CR1_DFF;
|
|
SPI1->CR1 &= ~SPI_CR1_DFF;
|
|
|
-
|
|
|
|
|
- // 3.3.2 数据传输顺序:0 - MSB在前
|
|
|
|
|
SPI1->CR1 &= ~SPI_CR1_LSBFIRST;
|
|
SPI1->CR1 &= ~SPI_CR1_LSBFIRST;
|
|
|
|
|
|
|
|
- // 3.4 使能SPI
|
|
|
|
|
|
|
+ // 3.6 使能SPI
|
|
|
SPI1->CR1 |= SPI_CR1_SPE;
|
|
SPI1->CR1 |= SPI_CR1_SPE;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SPI通信的开启和关闭(通过片选信号控制)
|
|
|
|
|
|
|
+void SPI_Start(void) { CS_LOW; }
|
|
|
|
|
+void SPI_Stop(void) { CS_HIGH; }
|
|
|
|
|
+
|
|
|
|
|
+uint8_t SPI_SwapByte(uint8_t byte)
|
|
|
|
|
+{
|
|
|
|
|
+ // 1. 等待发送缓冲区为空(TXE = 1)
|
|
|
|
|
+ while ((SPI1->SR & SPI_SR_TXE) == 0) {}
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 将数据写入DR
|
|
|
|
|
+ SPI1->DR = byte;
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 等待接收缓冲区非空(RXNE = 1)
|
|
|
|
|
+ while ((SPI1->SR & SPI_SR_RXNE) == 0) {}
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 从DR读取接收到的数据
|
|
|
|
|
+ return (uint8_t)(SPI1->DR & 0xff);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **关键差异**:SCK/MOSI 配置为 `复用推挽输出(CNF=10)`,由 SPI 外设自动控制电平,CPU 只需读写 DR 寄存器。CS 仍为通用推挽输出(GPIO 控制)。
|
|
|
|
|
+
|
|
|
|
|
+### main.c
|
|
|
|
|
+
|
|
|
|
|
+与项目 36 的 main.c 完全相同(`W25Q32_*` 函数适配 `SPI_SwapByte` 接口即可)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 项目 38:硬件 SPI(HAL 库版)
|
|
|
|
|
+
|
|
|
|
|
+> 使用 STM32CubeMX 生成初始化代码,HAL 库封装 SPI 外设。CS 片选仍由 GPIO 独立控制,CubeMX 中配置 PC13 为普通输出。
|
|
|
|
|
+
|
|
|
|
|
+**项目路径**:`上部-基础篇\03_代码\stm32\38_spi_hardware_hal`
|
|
|
|
|
+
|
|
|
|
|
+### CubeMX 生成:spi.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Core/Src/spi.c`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+SPI_HandleTypeDef hspi1;
|
|
|
|
|
+
|
|
|
|
|
+void MX_SPI1_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ hspi1.Instance = SPI1;
|
|
|
|
|
+ hspi1.Init.Mode = SPI_MODE_MASTER;
|
|
|
|
|
+ hspi1.Init.Direction = SPI_DIRECTION_2LINES;
|
|
|
|
|
+ hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
|
|
|
|
|
+ hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
|
|
|
|
|
+ hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
|
|
|
|
|
+ hspi1.Init.NSS = SPI_NSS_SOFT;
|
|
|
|
|
+ hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
|
|
|
|
|
+ hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
|
|
|
|
|
+ hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
|
|
|
|
|
+ hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
|
|
|
|
|
+ hspi1.Init.CRCPolynomial = 10;
|
|
|
|
|
+ if (HAL_SPI_Init(&hspi1) != HAL_OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ Error_Handler();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
|
|
|
+ if (spiHandle->Instance == SPI1)
|
|
|
|
|
+ {
|
|
|
|
|
+ __HAL_RCC_SPI1_CLK_ENABLE();
|
|
|
|
|
+ __HAL_RCC_GPIOA_CLK_ENABLE();
|
|
|
|
|
+
|
|
|
|
|
+ // PA5(SCK), PA7(MOSI) — 复用推挽输出
|
|
|
|
|
+ GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ // PA6(MISO) — 浮空输入
|
|
|
|
|
+ GPIO_InitStruct.Pin = GPIO_PIN_6;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
|
|
|
|
+ GPIO_InitStruct.Pull = GPIO_NOPULL;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### CS 引脚定义(CubeMX 生成)
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Core/Inc/main.h`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#define CS_Pin GPIO_PIN_13
|
|
|
|
|
+#define CS_GPIO_Port GPIOC
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Core/Src/gpio.c`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+void MX_GPIO_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
|
|
|
+
|
|
|
|
|
+ __HAL_RCC_GPIOC_CLK_ENABLE();
|
|
|
|
|
+ __HAL_RCC_GPIOA_CLK_ENABLE();
|
|
|
|
|
+
|
|
|
|
|
+ HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
|
|
|
|
|
+
|
|
|
|
|
+ GPIO_InitStruct.Pin = CS_Pin;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
|
|
|
|
+ GPIO_InitStruct.Pull = GPIO_NOPULL;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(CS_GPIO_Port, &GPIO_InitStruct);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 用户添加代码(spi.c USER CODE 段)
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
void SPI_Start(void)
|
|
void SPI_Start(void)
|
|
|
{
|
|
{
|
|
|
- CS_LOW;
|
|
|
|
|
|
|
+ HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
void SPI_Stop(void)
|
|
void SPI_Stop(void)
|
|
|
{
|
|
{
|
|
|
- CS_HIGH;
|
|
|
|
|
|
|
+ HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 一个时钟周期内,主机交换一个字节数据
|
|
|
|
|
uint8_t SPI_SwapByte(uint8_t byte)
|
|
uint8_t SPI_SwapByte(uint8_t byte)
|
|
|
{
|
|
{
|
|
|
- // 1. 将要发送的数据写入发送缓冲区
|
|
|
|
|
- // 1.1 等待发送缓冲区为空(TXE = 1)
|
|
|
|
|
- while ((SPI1->SR & SPI_SR_TXE) == 0 )
|
|
|
|
|
- {}
|
|
|
|
|
-
|
|
|
|
|
- // 1.2 将数据写入DR
|
|
|
|
|
- SPI1->DR = byte;
|
|
|
|
|
|
|
+ uint8_t rByte;
|
|
|
|
|
+ HAL_SPI_TransmitReceive(&hspi1, &byte, &rByte, 1, 1000);
|
|
|
|
|
+ return rByte;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
- // 2. 获取接收到的数据并返回
|
|
|
|
|
- // 2.1 等待接收缓冲区非空(RXNE = 1)
|
|
|
|
|
- while ( (SPI1->SR & SPI_SR_RXNE) == 0 )
|
|
|
|
|
- {}
|
|
|
|
|
-
|
|
|
|
|
- // 2.2 将DR中的数据返回
|
|
|
|
|
- return (uint8_t)(SPI1->DR & 0xff);
|
|
|
|
|
|
|
+### main.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Core/Src/main.c`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#include "main.h"
|
|
|
|
|
+#include "spi.h"
|
|
|
|
|
+#include "usart.h"
|
|
|
|
|
+#include "gpio.h"
|
|
|
|
|
+#include "w25q32.h"
|
|
|
|
|
+
|
|
|
|
|
+int main(void)
|
|
|
|
|
+{
|
|
|
|
|
+ HAL_Init();
|
|
|
|
|
+ SystemClock_Config();
|
|
|
|
|
+
|
|
|
|
|
+ MX_GPIO_Init();
|
|
|
|
|
+ MX_SPI1_Init();
|
|
|
|
|
+ MX_USART1_UART_Init();
|
|
|
|
|
+
|
|
|
|
|
+ printf("中国芯SPI通信模块实验开始...\n");
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t mid = 0;
|
|
|
|
|
+ uint16_t did = 0;
|
|
|
|
|
+ W25Q32_ReadID(&mid, &did);
|
|
|
|
|
+ printf("mid = %#x, did = %#x\n", mid, did);
|
|
|
|
|
+
|
|
|
|
|
+ W25Q32_EraseSector(0, 0);
|
|
|
|
|
+ W25Q32_PageWrite(0, 0, 0, "12345678", 8);
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t buffer[10] = {0};
|
|
|
|
|
+ W25Q32_Read(0, 0, 0, 2, buffer, 6);
|
|
|
|
|
+ printf("buffer = %s\n", buffer);
|
|
|
|
|
+
|
|
|
|
|
+ while (1) {}
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+> HAL 库优势:初始化代码由 CubeMX 生成,跨芯片移植方便;`HAL_SPI_TransmitReceive` 封装了 TXE/RXNE 轮询逻辑。但仍有轮询等待开销,大批量数据建议使用 DMA 方式。
|
|
|
|
|
+
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## W25Q64 Flash 驱动
|
|
|
|
|
|
|
+## W25Q64 / W25Q32 Flash 驱动
|
|
|
|
|
+
|
|
|
|
|
+| 参数 | W25Q64 | W25Q32 |
|
|
|
|
|
+|------|--------|--------|
|
|
|
|
|
+| 容量 | 8MB (64Mbit) | 4MB (32Mbit) |
|
|
|
|
|
+| 页(Page) | 256 字节 | 256 字节 |
|
|
|
|
|
+| 扇区(Sector) | 4KB (16 页) | 4KB (16 页) |
|
|
|
|
|
+| 块(Block) | 64KB (16 扇区) | 64KB (16 扇区) |
|
|
|
|
|
+| 擦除时间(扇区) | 典型 45ms | 典型 45ms |
|
|
|
|
|
+| 写寿命 | 100,000 次 | 100,000 次 |
|
|
|
|
|
|
|
|
-| 参数 | 值 |
|
|
|
|
|
-|------|-----|
|
|
|
|
|
-| 容量 | 8MB (64Mbit) |
|
|
|
|
|
-| 页(Page) | 256 字节 |
|
|
|
|
|
-| 扇区(Sector) | 4KB (16 页) |
|
|
|
|
|
-| 块(Block) | 64KB (16 扇区) |
|
|
|
|
|
-| 擦除时间(扇区) | 典型 45ms |
|
|
|
|
|
-| 写寿命 | 100,000 次 |
|
|
|
|
|
|
|
+两者指令集完全兼容,容量减半。以下驱动使用 `SPI_SwapByte` 接口,三个项目(36/37/38)均可直接复用。
|
|
|
|
|
|
|
|
### 常用指令集
|
|
### 常用指令集
|
|
|
|
|
|
|
@@ -429,18 +543,10 @@ uint8_t SPI_SwapByte(uint8_t byte)
|
|
|
| CHIP_ERASE | 0xC7 | 全片擦除 | — |
|
|
| CHIP_ERASE | 0xC7 | 全片擦除 | — |
|
|
|
| RDID | 0x9F | 读芯片 ID | 3字节返回 |
|
|
| RDID | 0x9F | 读芯片 ID | 3字节返回 |
|
|
|
|
|
|
|
|
-### W25Q64/W25Q32 擦写(读写前必须擦除)
|
|
|
|
|
-
|
|
|
|
|
-> **注意**:实际项目中使用 **W25Q32**(4MB/32Mbit),与 W25Q64 指令集完全兼容,容量减半。以下代码使用 `SPI_SwapByte` 函数(软件/硬件 SPI 驱动均为此函数名)。
|
|
|
|
|
|
|
+### 核心驱动逻辑
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
-// W25Q64 写入流程:
|
|
|
|
|
-// 1. 读状态寄存器,检查 BUSY 位(正在擦除/写入?)
|
|
|
|
|
-// 2. 发送 WREN (0x06) 使能写
|
|
|
|
|
-// 3. 发送 SECTOR_ERASE (0xD8) + 3字节地址 → 等待 BUSY=0
|
|
|
|
|
-// 4. 再次 WREN → PAGE_PROG (0x02) + 3字节地址 + 数据(≤256字节)
|
|
|
|
|
-// 5. 等待 BUSY=0
|
|
|
|
|
-
|
|
|
|
|
|
|
+// 读取状态寄存器
|
|
|
uint8_t W25Q64_ReadSR(void)
|
|
uint8_t W25Q64_ReadSR(void)
|
|
|
{
|
|
{
|
|
|
uint8_t sr;
|
|
uint8_t sr;
|
|
@@ -458,26 +564,25 @@ void W25Q64_WaitBusy(void)
|
|
|
|
|
|
|
|
void W25Q64_WritePage(uint32_t addr, uint8_t *data, uint16_t len)
|
|
void W25Q64_WritePage(uint32_t addr, uint8_t *data, uint16_t len)
|
|
|
{
|
|
{
|
|
|
- W25Q64_WaitBusy(); // 等上次完成
|
|
|
|
|
|
|
+ W25Q64_WaitBusy();
|
|
|
CS_LOW;
|
|
CS_LOW;
|
|
|
- SPI_SwapByte(WREN); // 写使能
|
|
|
|
|
|
|
+ SPI_SwapByte(WREN); // 写使能
|
|
|
CS_HIGH;
|
|
CS_HIGH;
|
|
|
|
|
|
|
|
CS_LOW;
|
|
CS_LOW;
|
|
|
- SPI_SwapByte(PAGE_PROG); // 页编程指令
|
|
|
|
|
- SPI_SwapByte(addr >> 16); // 地址高8位
|
|
|
|
|
- SPI_SwapByte(addr >> 8); // 地址中8位
|
|
|
|
|
- SPI_SwapByte(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++) {
|
|
for (uint16_t i = 0; i < len; i++) {
|
|
|
- SPI_SwapByte(data[i]); // 发送数据
|
|
|
|
|
|
|
+ SPI_SwapByte(data[i]);
|
|
|
}
|
|
}
|
|
|
CS_HIGH;
|
|
CS_HIGH;
|
|
|
- W25Q64_WaitBusy(); // 等写入完成
|
|
|
|
|
|
|
+ W25Q64_WaitBusy();
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-> **注意**:W25Q64/W25Q32 不支持"写覆盖"——必须先擦除再写。最小擦除单位是扇区(4KB)。
|
|
|
|
|
-> 页编程不能跨页(256 字节边界)。如果数据跨页,需要分多次写入。
|
|
|
|
|
|
|
+> **注意**:Flash 不能写覆盖——必须先擦除再写。最小擦除单位是扇区(4KB)。页编程不能跨页(256 字节边界)。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -500,8 +605,8 @@ Bank1 分为 4 个子区(片选 NE1~NE4),各 64MB:
|
|
|
|------|---------|---------|
|
|
|------|---------|---------|
|
|
|
| NE1 | FSMC_NE1 | 0x60000000~0x63FFFFFF(最常用) |
|
|
| NE1 | FSMC_NE1 | 0x60000000~0x63FFFFFF(最常用) |
|
|
|
| NE2 | FSMC_NE2 | 0x64000000~0x67FFFFFF |
|
|
| NE2 | FSMC_NE2 | 0x64000000~0x67FFFFFF |
|
|
|
-| NE3 | FSMC_NE3 | 0x68000000~0x6BFFFFFF |
|
|
|
|
|
-| NE4 | FSMC_NE4 | 0x6C000000~0x6FFFFFFF |
|
|
|
|
|
|
|
+| NE3 | FSMC_NE3 | 0x68000000~0x6BFFFFFF(SRAM 实验用) |
|
|
|
|
|
+| NE4 | FSMC_NE4 | 0x6C000000~0x6FFFFFFF(LCD 实验用) |
|
|
|
|
|
|
|
|
### FSMC 信号线
|
|
### FSMC 信号线
|
|
|
|
|
|
|
@@ -537,13 +642,17 @@ Bank1 分为 4 个子区(片选 NE1~NE4),各 64MB:
|
|
|
| 8:15 | DATAST | 数据保持时间(1~255个HCLK周期) |
|
|
| 8:15 | DATAST | 数据保持时间(1~255个HCLK周期) |
|
|
|
| 16:19 | BUSTURN | 总线周转时间 |
|
|
| 16:19 | BUSTURN | 总线周转时间 |
|
|
|
|
|
|
|
|
-### 实验:FSMC 扩展 SRAM(寄存器版)
|
|
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 项目 39:FSMC 扩展 SRAM(寄存器版)
|
|
|
|
|
|
|
|
-**项目路径**:`stm32/39_fsmc_sram_register`
|
|
|
|
|
|
|
+> 使用 FSMC Bank1 子区 3(NE3,片选 PG10),地址范围 0x68000000~0x6BFFFFFF,16 位数据总线连接外部 SRAM。
|
|
|
|
|
|
|
|
-使用 FSMC 的 Bank1 子区3(NE3,地址范围 0x68000000~0x6BFFFFFF),16 位数据总线连接外部 SRAM。
|
|
|
|
|
|
|
+**项目路径**:`上部-基础篇\03_代码\stm32\39_fsmc_sram_register`
|
|
|
|
|
|
|
|
-**文件:`stm32/39_fsmc_sram_register/Hardware/FSMC/fsmc.h`**
|
|
|
|
|
|
|
+### fsmc.h
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Hardware/FSMC/fsmc.h`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#ifndef __FSMC_H
|
|
#ifndef __FSMC_H
|
|
@@ -551,66 +660,55 @@ Bank1 分为 4 个子区(片选 NE1~NE4),各 64MB:
|
|
|
|
|
|
|
|
#include "stm32f10x.h"
|
|
#include "stm32f10x.h"
|
|
|
|
|
|
|
|
-// 初始化
|
|
|
|
|
void FSMC_Init(void);
|
|
void FSMC_Init(void);
|
|
|
|
|
|
|
|
#endif
|
|
#endif
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**文件:`stm32/39_fsmc_sram_register/Hardware/FSMC/fsmc.c`**
|
|
|
|
|
|
|
+### fsmc.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Hardware/FSMC/fsmc.c`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#include "fsmc.h"
|
|
#include "fsmc.h"
|
|
|
|
|
|
|
|
void FSMC_GPIO_Init(void);
|
|
void FSMC_GPIO_Init(void);
|
|
|
|
|
|
|
|
-// 初始化
|
|
|
|
|
void FSMC_Init(void)
|
|
void FSMC_Init(void)
|
|
|
{
|
|
{
|
|
|
// 1. 开启时钟
|
|
// 1. 开启时钟
|
|
|
RCC->AHBENR |= RCC_AHBENR_FSMCEN;
|
|
RCC->AHBENR |= RCC_AHBENR_FSMCEN;
|
|
|
-
|
|
|
|
|
RCC->APB2ENR |= (RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPEEN |
|
|
RCC->APB2ENR |= (RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPEEN |
|
|
|
RCC_APB2ENR_IOPFEN | RCC_APB2ENR_IOPGEN);
|
|
RCC_APB2ENR_IOPFEN | RCC_APB2ENR_IOPGEN);
|
|
|
|
|
|
|
|
// 2. GPIO模式配置
|
|
// 2. GPIO模式配置
|
|
|
FSMC_GPIO_Init();
|
|
FSMC_GPIO_Init();
|
|
|
|
|
|
|
|
- // 3. FSMC寄存器配置
|
|
|
|
|
- // 3.1 BCR3 - BTCR[4]
|
|
|
|
|
- // 3.1.1 存储区使能
|
|
|
|
|
|
|
+ // 3. FSMC寄存器配置 — BCR3(BTCR[4])
|
|
|
|
|
+ // 存储区使能
|
|
|
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MBKEN;
|
|
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MBKEN;
|
|
|
-
|
|
|
|
|
- // 3.1.2 设置存储器类型:MTYP = 00,SRAM/ROM
|
|
|
|
|
|
|
+ // 存储器类型:SRAM(MTYP = 00)
|
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MTYP;
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MTYP;
|
|
|
-
|
|
|
|
|
- // 3.1.3 禁止Flash访问
|
|
|
|
|
|
|
+ // 禁止Flash访问
|
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_FACCEN;
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_FACCEN;
|
|
|
-
|
|
|
|
|
- // 3.1.4 地址数据复用功能,不使能
|
|
|
|
|
|
|
+ // 禁止地址数据复用
|
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MUXEN;
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MUXEN;
|
|
|
-
|
|
|
|
|
- // 3.1.5 设置总线宽度:MWID = 01,16位
|
|
|
|
|
|
|
+ // 总线宽度:16位(MWID = 01)
|
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MWID_1;
|
|
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MWID_1;
|
|
|
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MWID_0;
|
|
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MWID_0;
|
|
|
-
|
|
|
|
|
- // 3.1.6 使能写操作
|
|
|
|
|
|
|
+ // 使能写操作
|
|
|
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_WREN;
|
|
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_WREN;
|
|
|
|
|
|
|
|
- // 3.2 BTR - BTCR[5]
|
|
|
|
|
- // 3.2.1 地址建立时间 ADDSET
|
|
|
|
|
|
|
+ // 4. 时序配置 — BTR3(BTCR[5])
|
|
|
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_ADDSET;
|
|
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_ADDSET;
|
|
|
-
|
|
|
|
|
- // 3.2.2 数据建立时间 DATAST
|
|
|
|
|
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_DATAST;
|
|
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_DATAST;
|
|
|
- FSMC_Bank1->BTCR[5] |= (71 << 8);
|
|
|
|
|
|
|
+ FSMC_Bank1->BTCR[5] |= (71 << 8); // DATAST = 71
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 配置GPIO引脚,均为复用推挽输出(CNF = 10,MODE = 11)
|
|
|
|
|
void FSMC_GPIO_Init(void)
|
|
void FSMC_GPIO_Init(void)
|
|
|
{
|
|
{
|
|
|
- // 1. 地址线 A0 ~ A18
|
|
|
|
|
- // MODE = 11
|
|
|
|
|
|
|
+ // 地址线 A0~A18:PF0~PF5, PF12~PF15, PG0~PG5, PD11~PD13
|
|
|
|
|
+ // MODE = 11(50MHz),CNF = 10(复用推挽输出)
|
|
|
GPIOF->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2 |
|
|
GPIOF->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2 |
|
|
|
GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
|
GPIOF->CRH |= (GPIO_CRH_MODE12 | GPIO_CRH_MODE13 |
|
|
GPIOF->CRH |= (GPIO_CRH_MODE12 | GPIO_CRH_MODE13 |
|
|
@@ -619,14 +717,13 @@ void FSMC_GPIO_Init(void)
|
|
|
GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
|
GPIOD->CRH |= (GPIO_CRH_MODE11 | GPIO_CRH_MODE12 | GPIO_CRH_MODE13);
|
|
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 |
|
|
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);
|
|
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 |
|
|
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);
|
|
GPIO_CRL_CNF3_0 | GPIO_CRL_CNF4_0 | GPIO_CRL_CNF5_0);
|
|
|
- // ...(其余地址线引脚配置类似)
|
|
|
|
|
|
|
+ // ...(其余地址线引脚配置类似,CNF=10,MODE=11)
|
|
|
|
|
|
|
|
- // 2. 数据线 D0 ~ D15(MODE = 11,CNF = 10)
|
|
|
|
|
|
|
+ // 数据线 D0~D15:PD0~PD1, PD8~PD10, PD14~PD15, PE7~PE15
|
|
|
GPIOD->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
|
|
GPIOD->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
|
|
|
GPIOD->CRH |= (GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_MODE10 |
|
|
GPIOD->CRH |= (GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_MODE10 |
|
|
|
GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
|
|
GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
|
|
@@ -639,7 +736,7 @@ void FSMC_GPIO_Init(void)
|
|
|
GPIOD->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
|
|
GPIOD->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
|
|
|
// ...
|
|
// ...
|
|
|
|
|
|
|
|
- // 3. 控制线
|
|
|
|
|
|
|
+ // 控制线
|
|
|
// PD4 - NOE(读使能),PD5 - NWE(写使能)
|
|
// PD4 - NOE(读使能),PD5 - NWE(写使能)
|
|
|
GPIOD->CRL |= (GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
GPIOD->CRL |= (GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
|
|
|
GPIOD->CRL |= (GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1);
|
|
GPIOD->CRL |= (GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1);
|
|
@@ -650,125 +747,903 @@ void FSMC_GPIO_Init(void)
|
|
|
GPIOG->CRH |= GPIO_CRH_CNF10_1;
|
|
GPIOG->CRH |= GPIO_CRH_CNF10_1;
|
|
|
GPIOG->CRH &= ~GPIO_CRH_CNF10_0;
|
|
GPIOG->CRH &= ~GPIO_CRH_CNF10_0;
|
|
|
|
|
|
|
|
- // PE0, PE1 - NBL(字节掩码,16位SRAM的高低位使能)
|
|
|
|
|
|
|
+ // PE0, PE1 - NBL0, NBL1(字节掩码)
|
|
|
GPIOE->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
|
|
GPIOE->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
|
|
|
GPIOE->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1);
|
|
GPIOE->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1);
|
|
|
GPIOE->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
|
|
GPIOE->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-// 配置完成后,FSMC 区域3的内存映射地址为 0x68000000
|
|
|
|
|
-// 写: *(uint16_t *)0x68000000 = data;
|
|
|
|
|
-// 读: data = *(uint16_t *)0x68000000;
|
|
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**文件:`stm32/39_fsmc_sram_register/User/main.c`**
|
|
|
|
|
|
|
+### main.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`User/main.c`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
#include "usart.h"
|
|
#include "usart.h"
|
|
|
#include "fsmc.h"
|
|
#include "fsmc.h"
|
|
|
|
|
|
|
|
-// 方法1:使用关键字attribute指定全局变量的地址
|
|
|
|
|
|
|
+// 方法1:使用 __attribute__((at())) 指定全局变量的地址
|
|
|
uint8_t v1 __attribute__((at(0x68000000)));
|
|
uint8_t v1 __attribute__((at(0x68000000)));
|
|
|
uint8_t v2 __attribute__((at(0x68000004)));
|
|
uint8_t v2 __attribute__((at(0x68000004)));
|
|
|
-
|
|
|
|
|
uint16_t v3 = 30;
|
|
uint16_t v3 = 30;
|
|
|
|
|
|
|
|
int main(void)
|
|
int main(void)
|
|
|
{
|
|
{
|
|
|
- // 1. 初始化
|
|
|
|
|
- USART_Init();
|
|
|
|
|
- FSMC_Init();
|
|
|
|
|
|
|
+ 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 *)0x68000001;
|
|
|
|
|
+ *p = 100;
|
|
|
|
|
+ printf("*p = %d, @%p\n", *p, p);
|
|
|
|
|
+
|
|
|
|
|
+ while (1) {}
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **配置完成后,FSMC 区域3 的地址为 0x68000000。写:`*(uint16_t *)0x68000000 = data;` 读:`data = *(uint16_t *)0x68000000;`**
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
- printf("中国芯FSMC扩展SRAM实验...\n");
|
|
|
|
|
|
|
+## 项目 40:FSMC 扩展 SRAM(HAL 库版)
|
|
|
|
|
|
|
|
- v1 = 10;
|
|
|
|
|
- v2 = 20;
|
|
|
|
|
|
|
+> 使用 CubeMX 生成 FSMC 初始化代码,HAL 库管理 SRAM 配置。同样使用 Bank3(NE3),16 位模式。
|
|
|
|
|
|
|
|
- // 测试局部变量指定地址
|
|
|
|
|
- uint8_t v4 __attribute__((at(0x68000008)));
|
|
|
|
|
- v4 = 40;
|
|
|
|
|
- uint8_t v5 = 50;
|
|
|
|
|
|
|
+**项目路径**:`上部-基础篇\03_代码\stm32\40_fsmc_sram_hal`
|
|
|
|
|
|
|
|
- // 打印地址验证
|
|
|
|
|
- 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);
|
|
|
|
|
|
|
+### CubeMX 生成:fsmc.c
|
|
|
|
|
|
|
|
- // 方法2:指针直接访问
|
|
|
|
|
- uint8_t *p = (uint8_t *)0x68000FFF;
|
|
|
|
|
- *p = 100;
|
|
|
|
|
- printf("*p = %d, @%p\n", *p, p);
|
|
|
|
|
|
|
+**文件**:`Core/Src/fsmc.c`
|
|
|
|
|
|
|
|
- while (1)
|
|
|
|
|
- {
|
|
|
|
|
- }
|
|
|
|
|
|
|
+```c
|
|
|
|
|
+SRAM_HandleTypeDef hsram1;
|
|
|
|
|
+
|
|
|
|
|
+void MX_FSMC_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ FSMC_NORSRAM_TimingTypeDef Timing = {0};
|
|
|
|
|
+
|
|
|
|
|
+ hsram1.Instance = FSMC_NORSRAM_DEVICE;
|
|
|
|
|
+ hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
|
|
|
|
|
+ hsram1.Init.NSBank = FSMC_NORSRAM_BANK3;
|
|
|
|
|
+ hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
|
|
|
|
|
+ hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
|
|
|
|
|
+ hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
|
|
|
|
|
+ hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
|
|
|
|
|
+ hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
|
|
|
|
|
+ hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
|
|
|
|
|
+ hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
|
|
|
|
|
+ hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
|
|
|
|
|
+ hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
|
|
|
|
|
+ hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
|
|
|
|
|
+ hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
|
|
|
|
|
+ hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
|
|
|
|
|
+
|
|
|
|
|
+ Timing.AddressSetupTime = 15;
|
|
|
|
|
+ Timing.AddressHoldTime = 15;
|
|
|
|
|
+ Timing.DataSetupTime = 71;
|
|
|
|
|
+ Timing.BusTurnAroundDuration = 15;
|
|
|
|
|
+ Timing.CLKDivision = 16;
|
|
|
|
|
+ Timing.DataLatency = 17;
|
|
|
|
|
+ Timing.AccessMode = FSMC_ACCESS_MODE_A;
|
|
|
|
|
+
|
|
|
|
|
+ if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ Error_Handler();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ __HAL_AFIO_FSMCNADV_DISCONNECTED();
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### FSMC + LCD 应用
|
|
|
|
|
|
|
+### HAL_FSMC_MspInit(GPIO 配置,由 CubeMX 生成)
|
|
|
|
|
|
|
|
-将 LCD 连接到 FSMC,利用地址线 A0 区分命令和数据:
|
|
|
|
|
|
|
+```c
|
|
|
|
|
+static void HAL_FSMC_MspInit(void)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
|
|
|
+ if (FSMC_Initialized) return;
|
|
|
|
|
+ FSMC_Initialized = 1;
|
|
|
|
|
+
|
|
|
|
|
+ __HAL_RCC_FSMC_CLK_ENABLE();
|
|
|
|
|
+
|
|
|
|
|
+ // PF0~PF5, PF12~PF15 → FSMC_A0~A9
|
|
|
|
|
+ GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|
|
|
|
|
+ |GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_12|GPIO_PIN_13
|
|
|
|
|
+ |GPIO_PIN_14|GPIO_PIN_15;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ // PG0~PG5, PG10 → FSMC_A10~A15, NE3
|
|
|
|
|
+ GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|
|
|
|
|
+ |GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ // PE0~PE1(NBL), PE7~PE15(D4~D12)
|
|
|
|
|
+ GPIO_InitStruct.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|GPIO_PIN_0|GPIO_PIN_1;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ // PD0~PD1(D2~D3), PD4(NOE), PD5(NWE), PD8~PD10(D13~D15),
|
|
|
|
|
+ // PD11~PD13(A16~A18), PD14~PD15(D0~D1)
|
|
|
|
|
+ GPIO_InitStruct.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
|
|
|
|
|
+ |GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
|
|
|
|
|
+}
|
|
|
```
|
|
```
|
|
|
-写 (uint16_t *)0x60000000 = cmd → RS=0 → 写命令
|
|
|
|
|
-写 (uint16_t *)0x60020000 = data → RS=1(A0=1) → 写数据
|
|
|
|
|
|
|
+
|
|
|
|
|
+### main.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Core/Src/main.c`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#include "main.h"
|
|
|
|
|
+#include "usart.h"
|
|
|
|
|
+#include "gpio.h"
|
|
|
|
|
+#include "fsmc.h"
|
|
|
|
|
+
|
|
|
|
|
+// 方法1:__attribute__((at())) 指定全局变量地址
|
|
|
|
|
+uint8_t v1 __attribute__((at(0x68000000)));
|
|
|
|
|
+uint8_t v2 __attribute__((at(0x68000004)));
|
|
|
|
|
+uint16_t v3 = 30;
|
|
|
|
|
+
|
|
|
|
|
+int main(void)
|
|
|
|
|
+{
|
|
|
|
|
+ HAL_Init();
|
|
|
|
|
+ SystemClock_Config();
|
|
|
|
|
+
|
|
|
|
|
+ MX_GPIO_Init();
|
|
|
|
|
+ MX_FSMC_Init();
|
|
|
|
|
+ MX_USART1_UART_Init();
|
|
|
|
|
+
|
|
|
|
|
+ printf("中国芯FSMC实验...\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);
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t *p = (uint8_t *)0x68000001;
|
|
|
|
|
+ *p = 100;
|
|
|
|
|
+ printf("*p = %d, @%p\n", *p, p);
|
|
|
|
|
+
|
|
|
|
|
+ while (1) {}
|
|
|
|
|
+}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
|
|
+## 项目 41:FSMC + LCD(寄存器版)
|
|
|
|
|
+
|
|
|
|
|
+> 将 LCD 连接到 FSMC,利用地址线 A10 区分命令和数据:
|
|
|
|
|
+> - `*LCD_ADDR_CMD = cmd` → A10=0 → RS=0 → 写命令
|
|
|
|
|
+> - `*LCD_ADDR_DATA = data` → A10=1 → RS=1 → 写数据
|
|
|
|
|
+>
|
|
|
|
|
+> 使用 Bank1 子区 4(NE4,片选 PG12),地址基址 0x6C000000。
|
|
|
|
|
+
|
|
|
|
|
+**项目路径**:`上部-基础篇\03_代码\stm32\41_lcd_register`
|
|
|
|
|
+
|
|
|
|
|
+> 该项目的 FSMC 驱动在 `Hardware/FSMC/`,LCD 驱动在 `Interface/LCD/`(不同于其他项目的 `Hardware/` 目录结构)。
|
|
|
|
|
+
|
|
|
|
|
+### fsmc.c(LCD 专用 FSMC 配置)
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Hardware/FSMC/fsmc.c`(区别于 SRAM 版本,使用 BCR4/BTCR[6] 和 NE4)
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#include "fsmc.h"
|
|
|
|
|
+
|
|
|
|
|
+void FSMC_GPIO_Init(void);
|
|
|
|
|
+
|
|
|
|
|
+void FSMC_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ // 1. 开启时钟(增加 GPIOB 用于背光)
|
|
|
|
|
+ RCC->AHBENR |= RCC_AHBENR_FSMCEN;
|
|
|
|
|
+ RCC->APB2ENR |= (RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPDEN |
|
|
|
|
|
+ RCC_APB2ENR_IOPEEN | RCC_APB2ENR_IOPFEN |
|
|
|
|
|
+ RCC_APB2ENR_IOPGEN);
|
|
|
|
|
+
|
|
|
|
|
+ FSMC_GPIO_Init();
|
|
|
|
|
+
|
|
|
|
|
+ // 3. FSMC BCR4 — BTCR[6]
|
|
|
|
|
+ FSMC_Bank1->BTCR[6] |= FSMC_BCR4_MBKEN;
|
|
|
|
|
+ FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_MTYP; // SRAM类型
|
|
|
|
|
+ FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_FACCEN; // 禁止Flash访问
|
|
|
|
|
+ FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_MWID_1;
|
|
|
|
|
+ FSMC_Bank1->BTCR[6] |= FSMC_BCR4_MWID_0; // 16位
|
|
|
|
|
+ FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_MUXEN; // 非复用
|
|
|
|
|
+ FSMC_Bank1->BTCR[6] |= FSMC_BCR4_WREN; // 写使能
|
|
|
|
|
+
|
|
|
|
|
+ // 4. FSMC BTR4 — BTCR[7]
|
|
|
|
|
+ FSMC_Bank1->BTCR[7] &= ~FSMC_BTR4_ADDSET;
|
|
|
|
|
+ FSMC_Bank1->BTCR[7] &= ~FSMC_BTR4_DATAST;
|
|
|
|
|
+ FSMC_Bank1->BTCR[7] |= (71 << 8); // DATAST = 71
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void FSMC_GPIO_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ // 地址线:只用了 A10(PG0)
|
|
|
|
|
+ GPIOG->CRL |= GPIO_CRL_MODE0;
|
|
|
|
|
+ GPIOG->CRL |= GPIO_CRL_CNF0_1;
|
|
|
|
|
+ GPIOG->CRL &= ~GPIO_CRL_CNF0_0;
|
|
|
|
|
+
|
|
|
|
|
+ // 数据线 D0~D15:PD0~PD1, PD8~PD10, PD14~PD15, PE7~PE15
|
|
|
|
|
+ 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);
|
|
|
|
|
+ // ...
|
|
|
|
|
+
|
|
|
|
|
+ // 控制线: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);
|
|
|
|
|
+
|
|
|
|
|
+ // PG12 — NE4(片选)
|
|
|
|
|
+ GPIOG->CRH |= GPIO_CRH_MODE12;
|
|
|
|
|
+ GPIOG->CRH |= GPIO_CRH_CNF12_1;
|
|
|
|
|
+ GPIOG->CRH &= ~GPIO_CRH_CNF12_0;
|
|
|
|
|
+
|
|
|
|
|
+ // PG15 — LCD复位(通用推挽输出)
|
|
|
|
|
+ GPIOG->CRH |= GPIO_CRH_MODE15;
|
|
|
|
|
+ GPIOG->CRH &= ~GPIO_CRH_CNF15;
|
|
|
|
|
+
|
|
|
|
|
+ // PB0 — 背光控制(通用推挽输出)
|
|
|
|
|
+ GPIOB->CRL |= GPIO_CRL_MODE0;
|
|
|
|
|
+ GPIOB->CRL &= ~GPIO_CRL_CNF0;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### lcd.h
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Interface/LCD/lcd.h`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#ifndef __LCD_H
|
|
|
|
|
+#define __LCD_H
|
|
|
|
|
+
|
|
|
|
|
+#include "fsmc.h"
|
|
|
|
|
+#include "delay.h"
|
|
|
|
|
+#include <math.h>
|
|
|
|
|
+
|
|
|
|
|
+// 命令/数据地址(A10 区分)
|
|
|
|
|
+#define SRAM_BANK1_4 0x6C000000
|
|
|
|
|
+#define LCD_ADDR_CMD (uint16_t *)SRAM_BANK1_4
|
|
|
|
|
+#define LCD_ADDR_DATA (uint16_t *)(SRAM_BANK1_4 + (1 << 11))
|
|
|
|
|
+// A10=0 → 命令,A10=1 → 数据(地址偏移 0x800)
|
|
|
|
|
+
|
|
|
|
|
+#define LCD_W 320
|
|
|
|
|
+#define LCD_H 480
|
|
|
|
|
+
|
|
|
|
|
+/* 常用颜色 */
|
|
|
|
|
+#define WHITE 0xFFFF
|
|
|
|
|
+#define BLACK 0x0000
|
|
|
|
|
+#define BLUE 0x001F
|
|
|
|
|
+#define RED 0xF800
|
|
|
|
|
+#define GREEN 0x07E0
|
|
|
|
|
+#define YELLOW 0xFFE0
|
|
|
|
|
+#define GRAY 0x8430
|
|
|
|
|
+// ... 其他颜色
|
|
|
|
|
+
|
|
|
|
|
+// 基础操作
|
|
|
|
|
+void LCD_Init(void);
|
|
|
|
|
+void LCD_Reset(void);
|
|
|
|
|
+void LCD_BGOn(void);
|
|
|
|
|
+void LCD_BGOff(void);
|
|
|
|
|
+void LCD_RegConfig(void);
|
|
|
|
|
+void LCD_WriteCmd(uint16_t cmd);
|
|
|
|
|
+void LCD_WriteData(uint16_t data);
|
|
|
|
|
+uint16_t LCD_ReadData(void);
|
|
|
|
|
+
|
|
|
|
|
+// 绘图功能
|
|
|
|
|
+uint32_t LCD_ReadID(void);
|
|
|
|
|
+void LCD_ClearAll(uint16_t color);
|
|
|
|
|
+void LCD_SetArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
|
|
|
|
+void LCD_WriteAsciiChar(uint16_t x, uint16_t y, uint16_t height,
|
|
|
|
|
+ uint8_t c, uint16_t fColor, uint16_t bColor);
|
|
|
|
|
+void LCD_WriteAsciiString(uint16_t x, uint16_t y, uint16_t height,
|
|
|
|
|
+ uint8_t *str, uint16_t fColor, uint16_t bColor);
|
|
|
|
|
+void LCD_WriteChineseChar(uint16_t x, uint16_t y, uint16_t height,
|
|
|
|
|
+ uint8_t index, uint16_t fColor, uint16_t bColor);
|
|
|
|
|
+void LCD_DisplayAtguiguLogo(uint16_t x, uint16_t y);
|
|
|
|
|
+void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t w, uint16_t color);
|
|
|
|
|
+void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
|
|
|
|
|
+ uint16_t w, uint16_t color);
|
|
|
|
|
+void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
|
|
|
|
|
+ uint16_t w, uint16_t color);
|
|
|
|
|
+void LCD_DrawCircle(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t color);
|
|
|
|
|
+void LCD_DrawCircle_Pro(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t color);
|
|
|
|
|
+void LCD_DrawFilledCircle(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t bColor, uint16_t fColor);
|
|
|
|
|
+void LCD_DrawFilledCircle_Pro(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t bColor, uint16_t fColor);
|
|
|
|
|
+
|
|
|
|
|
+#endif
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### lcd.c(核心函数)
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Interface/LCD/lcd.c`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#include "lcd.h"
|
|
|
|
|
+#include "lcd_font.h"
|
|
|
|
|
+
|
|
|
|
|
+// 初始化调用 FSMC_Init,然后复位、亮背光、写寄存器
|
|
|
|
|
+void LCD_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ FSMC_Init();
|
|
|
|
|
+ LCD_Reset();
|
|
|
|
|
+ LCD_BGOn();
|
|
|
|
|
+ LCD_RegConfig();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 复位:PG15 拉低100ms再拉高
|
|
|
|
|
+void LCD_Reset(void)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIOG->ODR &= ~GPIO_ODR_ODR15;
|
|
|
|
|
+ Delay_ms(100);
|
|
|
|
|
+ GPIOG->ODR |= GPIO_ODR_ODR15;
|
|
|
|
|
+ Delay_ms(100);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 背光:PB0
|
|
|
|
|
+void LCD_BGOn(void) { GPIOB->ODR |= GPIO_ODR_ODR0; }
|
|
|
|
|
+void LCD_BGOff(void) { GPIOB->ODR &= ~GPIO_ODR_ODR0; }
|
|
|
|
|
+
|
|
|
|
|
+// 寄存器初始化序列(ILI9341 兼容)
|
|
|
|
|
+void LCD_RegConfig(void)
|
|
|
|
|
+{
|
|
|
|
|
+ /* 1. 正极伽马校正 */
|
|
|
|
|
+ LCD_WriteCmd(0xE0);
|
|
|
|
|
+ LCD_WriteData(0x00); LCD_WriteData(0x07); LCD_WriteData(0x10);
|
|
|
|
|
+ LCD_WriteData(0x09); LCD_WriteData(0x17); LCD_WriteData(0x0B);
|
|
|
|
|
+ LCD_WriteData(0x41); LCD_WriteData(0x89); LCD_WriteData(0x4B);
|
|
|
|
|
+ LCD_WriteData(0x0A); LCD_WriteData(0x0C); LCD_WriteData(0x0E);
|
|
|
|
|
+ LCD_WriteData(0x18); LCD_WriteData(0x1B); LCD_WriteData(0x0F);
|
|
|
|
|
+
|
|
|
|
|
+ /* 2. 负极伽马校正 */
|
|
|
|
|
+ LCD_WriteCmd(0XE1);
|
|
|
|
|
+ LCD_WriteData(0x00); LCD_WriteData(0x17); LCD_WriteData(0x1A);
|
|
|
|
|
+ LCD_WriteData(0x04); LCD_WriteData(0x0E); LCD_WriteData(0x06);
|
|
|
|
|
+ LCD_WriteData(0x2F); LCD_WriteData(0x45); LCD_WriteData(0x43);
|
|
|
|
|
+ LCD_WriteData(0x02); LCD_WriteData(0x0A); LCD_WriteData(0x09);
|
|
|
|
|
+ LCD_WriteData(0x32); LCD_WriteData(0x36); LCD_WriteData(0x0F);
|
|
|
|
|
+
|
|
|
|
|
+ /* 4. 电源控制1 */
|
|
|
|
|
+ LCD_WriteCmd(0xC0);
|
|
|
|
|
+ LCD_WriteData(0x11); LCD_WriteData(0x09);
|
|
|
|
|
+
|
|
|
|
|
+ /* 5. 电源控制2 */
|
|
|
|
|
+ LCD_WriteCmd(0xC1);
|
|
|
|
|
+ LCD_WriteData(0x02); LCD_WriteData(0x03);
|
|
|
|
|
+
|
|
|
|
|
+ /* 6. VCOM控制 */
|
|
|
|
|
+ LCD_WriteCmd(0XC5);
|
|
|
|
|
+ LCD_WriteData(0x00); LCD_WriteData(0x0A); LCD_WriteData(0x80);
|
|
|
|
|
+
|
|
|
|
|
+ /* 7. 帧率控制 */
|
|
|
|
|
+ LCD_WriteCmd(0xB1);
|
|
|
|
|
+ LCD_WriteData(0xB0); LCD_WriteData(0x11);
|
|
|
|
|
+
|
|
|
|
|
+ /* 12. 像素格式:16位 */
|
|
|
|
|
+ LCD_WriteCmd(0x3A);
|
|
|
|
|
+ LCD_WriteData(0x55);
|
|
|
|
|
+
|
|
|
|
|
+ /* 13. 退出睡眠 */
|
|
|
|
|
+ LCD_WriteCmd(0x11);
|
|
|
|
|
+ Delay_ms(120);
|
|
|
|
|
+
|
|
|
|
|
+ /* 14. 显示开启 */
|
|
|
|
|
+ LCD_WriteCmd(0x29);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 命令/数据访问
|
|
|
|
|
+void LCD_WriteCmd(uint16_t cmd) { *LCD_ADDR_CMD = cmd; }
|
|
|
|
|
+void LCD_WriteData(uint16_t data) { *LCD_ADDR_DATA = data; }
|
|
|
|
|
+uint16_t LCD_ReadData(void) { return *LCD_ADDR_DATA; }
|
|
|
|
|
+
|
|
|
|
|
+// 读 LCD 控制器 ID
|
|
|
|
|
+uint32_t LCD_ReadID(void)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_WriteCmd(0x04);
|
|
|
|
|
+ LCD_ReadData(); // 丢弃第一个无效字节
|
|
|
|
|
+ uint32_t id = 0;
|
|
|
|
|
+ id |= (LCD_ReadData() & 0xff) << 16;
|
|
|
|
|
+ id |= (LCD_ReadData() & 0xff) << 8;
|
|
|
|
|
+ id |= (LCD_ReadData() & 0xff);
|
|
|
|
|
+ return id;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 设置读写窗口
|
|
|
|
|
+void LCD_SetArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_WriteCmd(0x2a);
|
|
|
|
|
+ LCD_WriteData(x >> 8 & 0xff); LCD_WriteData(x & 0xff);
|
|
|
|
|
+ LCD_WriteData((x + w - 1) >> 8 & 0xff);
|
|
|
|
|
+ LCD_WriteData((x + w - 1) & 0xff);
|
|
|
|
|
+
|
|
|
|
|
+ LCD_WriteCmd(0x2b);
|
|
|
|
|
+ LCD_WriteData(y >> 8 & 0xff); LCD_WriteData(y & 0xff);
|
|
|
|
|
+ LCD_WriteData((y + h - 1) >> 8 & 0xff);
|
|
|
|
|
+ LCD_WriteData((y + h - 1) & 0xff);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 全屏清空为指定颜色
|
|
|
|
|
+void LCD_ClearAll(uint16_t color)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_SetArea(0, 0, LCD_W, LCD_H);
|
|
|
|
|
+ LCD_WriteCmd(0x2c);
|
|
|
|
|
+ for (uint32_t i = 0; i < LCD_W * LCD_H; i++) {
|
|
|
|
|
+ LCD_WriteData(color);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ASCII 字符显示(支持 12/16/24/32 高度)
|
|
|
|
|
+void LCD_WriteAsciiChar(uint16_t x, uint16_t y, uint16_t height,
|
|
|
|
|
+ uint8_t c, uint16_t fColor, uint16_t bColor)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_SetArea(x, y, height / 2, height);
|
|
|
|
|
+ LCD_WriteCmd(0x2C);
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t index = c - ' ';
|
|
|
|
|
+
|
|
|
|
|
+ if (height == 16 || height == 12) {
|
|
|
|
|
+ for (uint8_t i = 0; i < height; i++) {
|
|
|
|
|
+ uint8_t tempByte = (height == 16)
|
|
|
|
|
+ ? ascii_1608[index][i] : ascii_1206[index][i];
|
|
|
|
|
+ for (uint8_t j = 0; j < height / 2; j++) {
|
|
|
|
|
+ LCD_WriteData((tempByte & 0x01) ? fColor : bColor);
|
|
|
|
|
+ tempByte >>= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (height == 24) {
|
|
|
|
|
+ for (uint8_t i = 0; i < height * 2; i++) {
|
|
|
|
|
+ uint8_t tempByte = ascii_2412[index][i];
|
|
|
|
|
+ uint8_t jCount = (i % 2) ? 4 : 8;
|
|
|
|
|
+ for (uint8_t j = 0; j < jCount; j++) {
|
|
|
|
|
+ LCD_WriteData((tempByte & 0x01) ? fColor : bColor);
|
|
|
|
|
+ tempByte >>= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (height == 32) {
|
|
|
|
|
+ for (uint8_t i = 0; i < height * 2; i++) {
|
|
|
|
|
+ uint8_t tempByte = ascii_3216[index][i];
|
|
|
|
|
+ for (uint8_t j = 0; j < 8; j++) {
|
|
|
|
|
+ LCD_WriteData((tempByte & 0x01) ? fColor : bColor);
|
|
|
|
|
+ tempByte >>= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 字符串(支持 \n 换行和自动换行)
|
|
|
|
|
+void LCD_WriteAsciiString(uint16_t x, uint16_t y, uint16_t height,
|
|
|
|
|
+ uint8_t *str, uint16_t fColor, uint16_t bColor)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t i = 0;
|
|
|
|
|
+ while (str[i] != '\0') {
|
|
|
|
|
+ if (str[i] != '\n') {
|
|
|
|
|
+ if (x + height / 2 > LCD_W) { x = 0; y += height; }
|
|
|
|
|
+ LCD_WriteAsciiChar(x, y, height, str[i], fColor, bColor);
|
|
|
|
|
+ x += height / 2;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ x = 0; y += height;
|
|
|
|
|
+ }
|
|
|
|
|
+ i++;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 显示汉字(使用 chinese[][128] 字库)
|
|
|
|
|
+void LCD_WriteChineseChar(uint16_t x, uint16_t y, uint16_t height,
|
|
|
|
|
+ uint8_t index, uint16_t fColor, uint16_t bColor)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_SetArea(x, y, height, height);
|
|
|
|
|
+ LCD_WriteCmd(0x2C);
|
|
|
|
|
+ for (uint8_t i = 0; i < 128; i++) {
|
|
|
|
|
+ uint8_t tempByte = chinese[index][i];
|
|
|
|
|
+ for (uint8_t j = 0; j < 8; j++) {
|
|
|
|
|
+ LCD_WriteData((tempByte & 0x01) ? fColor : bColor);
|
|
|
|
|
+ tempByte >>= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 显示 Logo(使用 gImage_logo 数组)
|
|
|
|
|
+void LCD_DisplayAtguiguLogo(uint16_t x, uint16_t y)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_SetArea(x, y, 227, 68);
|
|
|
|
|
+ LCD_WriteCmd(0x2C);
|
|
|
|
|
+ uint16_t len = sizeof(gImage_logo);
|
|
|
|
|
+ for (uint16_t i = 0; i < len; i += 2) {
|
|
|
|
|
+ uint16_t p = gImage_logo[i] + (gImage_logo[i + 1] << 8);
|
|
|
|
|
+ LCD_WriteData(p);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 画点
|
|
|
|
|
+void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t w, uint16_t color)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_SetArea(x, y, w, w);
|
|
|
|
|
+ LCD_WriteCmd(0x2C);
|
|
|
|
|
+ for (uint16_t i = 0; i < w * w; i++) LCD_WriteData(color);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 画线(Bresenham 近似:y = kx + b)
|
|
|
|
|
+void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
|
|
|
|
|
+ uint16_t w, uint16_t color)
|
|
|
|
|
+{
|
|
|
|
|
+ if (x1 == x2) {
|
|
|
|
|
+ for (uint16_t y = y1; y <= y2; y++) LCD_DrawPoint(x1, y, w, color);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ double k = 1.0 * (y1 - y2) / (x1 - x2);
|
|
|
|
|
+ double b = y1 - k * x1;
|
|
|
|
|
+ for (uint16_t x = x1; x <= x2; x++) {
|
|
|
|
|
+ uint16_t y = (uint16_t)(k * x + b);
|
|
|
|
|
+ LCD_DrawPoint(x, y, w, color);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 矩形
|
|
|
|
|
+void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
|
|
|
|
|
+ uint16_t w, uint16_t color)
|
|
|
|
|
+{
|
|
|
|
|
+ LCD_DrawLine(x1, y1, x2, y1, w, color);
|
|
|
|
|
+ LCD_DrawLine(x2, y1, x2, y2, w, color);
|
|
|
|
|
+ LCD_DrawLine(x1, y1, x1, y2, w, color);
|
|
|
|
|
+ LCD_DrawLine(x1, y2, x2, y2, w, color);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 画圆(参数方程,逐点)
|
|
|
|
|
+void LCD_DrawCircle(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t color)
|
|
|
|
|
+{
|
|
|
|
|
+ for (uint16_t theta = 0; theta < 360; theta++) {
|
|
|
|
|
+ uint16_t x = xCenter + r * cos(3.14 * theta / 180);
|
|
|
|
|
+ uint16_t y = yCenter + r * sin(3.14 * theta / 180);
|
|
|
|
|
+ LCD_DrawPoint(x, y, w, color);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 画圆优化版(同时画 4 象限)
|
|
|
|
|
+void LCD_DrawCircle_Pro(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t color)
|
|
|
|
|
+{
|
|
|
|
|
+ for (uint16_t theta = 0; theta <= 90; theta++) {
|
|
|
|
|
+ uint16_t dx = r * cos(3.14 * theta / 180);
|
|
|
|
|
+ uint16_t dy = r * sin(3.14 * theta / 180);
|
|
|
|
|
+ LCD_DrawPoint(xCenter + dx, yCenter + dy, w, color);
|
|
|
|
|
+ LCD_DrawPoint(xCenter - dx, yCenter + dy, w, color);
|
|
|
|
|
+ LCD_DrawPoint(xCenter - dx, yCenter - dy, w, color);
|
|
|
|
|
+ LCD_DrawPoint(xCenter + dx, yCenter - dy, w, color);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 实心圆
|
|
|
|
|
+void LCD_DrawFilledCircle(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t bColor, uint16_t fColor)
|
|
|
|
|
+{
|
|
|
|
|
+ for (uint16_t i = 0; i <= r; i++) {
|
|
|
|
|
+ for (uint16_t theta = 0; theta < 360; theta++) {
|
|
|
|
|
+ uint16_t x = xCenter + i * cos(3.14 * theta / 180);
|
|
|
|
|
+ uint16_t y = yCenter + i * sin(3.14 * theta / 180);
|
|
|
|
|
+ LCD_DrawPoint(x, y, w, (i == r) ? fColor : bColor);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 实心圆优化版(画填充直线代替逐点扫描)
|
|
|
|
|
+void LCD_DrawFilledCircle_Pro(uint16_t xCenter, uint16_t yCenter, uint16_t r,
|
|
|
|
|
+ uint16_t w, uint16_t bColor, uint16_t fColor)
|
|
|
|
|
+{
|
|
|
|
|
+ for (uint16_t theta = 0; theta <= 90; theta++) {
|
|
|
|
|
+ uint16_t dx = r * cos(3.14 * theta / 180);
|
|
|
|
|
+ uint16_t dy = r * sin(3.14 * theta / 180);
|
|
|
|
|
+
|
|
|
|
|
+ uint16_t x1 = xCenter + dx, y1 = yCenter + dy;
|
|
|
|
|
+ uint16_t x2 = xCenter - dx, y2 = yCenter + dy;
|
|
|
|
|
+ LCD_DrawPoint(x1, y1, w, fColor);
|
|
|
|
|
+ LCD_DrawPoint(x2, y2, w, fColor);
|
|
|
|
|
+ LCD_DrawLine(x2 + w, y2, x1 - w, y1, w, bColor);
|
|
|
|
|
+
|
|
|
|
|
+ x2 = xCenter - dx; y2 = yCenter - dy;
|
|
|
|
|
+ x1 = xCenter + dx; y1 = yCenter - dy;
|
|
|
|
|
+ LCD_DrawPoint(x2, y2, w, fColor);
|
|
|
|
|
+ LCD_DrawPoint(x1, y1, w, fColor);
|
|
|
|
|
+ LCD_DrawLine(x2 + w, y2, x1 - w, y1, w, bColor);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### main.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`User/main.c`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#include "usart.h"
|
|
|
|
|
+#include "lcd.h"
|
|
|
|
|
+
|
|
|
|
|
+int main(void)
|
|
|
|
|
+{
|
|
|
|
|
+ USART_Init();
|
|
|
|
|
+ LCD_Init();
|
|
|
|
|
+
|
|
|
|
|
+ printf("中国芯LCD实验开始...\n");
|
|
|
|
|
+
|
|
|
|
|
+ uint32_t id = LCD_ReadID();
|
|
|
|
|
+ printf("id = %#x\n", id);
|
|
|
|
|
+
|
|
|
|
|
+ // 全屏白色
|
|
|
|
|
+ LCD_ClearAll(WHITE);
|
|
|
|
|
+
|
|
|
|
|
+ // 显示 ASCII 字符
|
|
|
|
|
+ LCD_WriteAsciiChar(10, 10, 16, 'A', WHITE, RED);
|
|
|
|
|
+ LCD_WriteAsciiChar(10, 30, 24, 'A', WHITE, RED);
|
|
|
|
|
+ LCD_WriteAsciiChar(20, 60, 32, 'A', BLUE, WHITE);
|
|
|
|
|
+ LCD_WriteAsciiChar(20, 100, 12, 'B', BLUE, YELLOW);
|
|
|
|
|
+
|
|
|
|
|
+ // 显示字符串
|
|
|
|
|
+ LCD_WriteAsciiString(200, 200, 24,
|
|
|
|
|
+ "Hello\natguigu!\nHello, world!at\nguigu", BLACK, WHITE);
|
|
|
|
|
+
|
|
|
|
|
+ // 显示汉字
|
|
|
|
|
+ LCD_WriteChineseChar(20, 330, 32, 0, RED, BLUE);
|
|
|
|
|
+ LCD_WriteChineseChar(20, 362, 32, 1, BLUE, RED);
|
|
|
|
|
+ LCD_WriteChineseChar(20, 394, 32, 2, GRAY, RED);
|
|
|
|
|
+
|
|
|
|
|
+ // 显示 Logo
|
|
|
|
|
+ LCD_DisplayAtguiguLogo(57, 100);
|
|
|
|
|
+
|
|
|
|
|
+ // 几何图形
|
|
|
|
|
+ LCD_DrawPoint(300, 300, 5, RED);
|
|
|
|
|
+ LCD_DrawLine(10, 10, 10, 300, 5, RED);
|
|
|
|
|
+ LCD_DrawLine(10, 10, 300, 180, 3, BLUE);
|
|
|
|
|
+ LCD_DrawRectangle(20, 20, 300, 300, 5, RED);
|
|
|
|
|
+ LCD_DrawCircle_Pro(160, 240, 100, 5, BLUE);
|
|
|
|
|
+ LCD_DrawFilledCircle_Pro(240, 400, 50, 3, BLUE, RED);
|
|
|
|
|
+
|
|
|
|
|
+ while (1) {}
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **LCD 地址映射原理**:NE4 基址 = 0x6C000000,A10 连接 LCD 的 RS 引脚。
|
|
|
|
|
+> 当 CPU 访问 `0x6C000000` 时,A10=0 → RS=0 → 命令模式。
|
|
|
|
|
+> 当 CPU 访问 `0x6C000000 + 0x800`(即 A10=1)时 → RS=1 → 数据模式。
|
|
|
|
|
+
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## HAL 库版 SPI
|
|
|
|
|
|
|
+## 项目 42:FSMC + LCD(HAL 库版)
|
|
|
|
|
|
|
|
-HAL 库使用 `SPI_HandleTypeDef` 管理 SPI,提供 `Transmit/Receive` 函数。
|
|
|
|
|
|
|
+> CubeMX 生成 FSMC 初始化代码,使用 Bank4(NE4),LCD 驱动逻辑与寄存器版相同。
|
|
|
|
|
+
|
|
|
|
|
+**项目路径**:`上部-基础篇\03_代码\stm32\42_lcd_hal`
|
|
|
|
|
+
|
|
|
|
|
+### CubeMX 生成:fsmc.c
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Core/Src/fsmc.c`
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
-// CubeMX 生成: MX_SPI1_Init()
|
|
|
|
|
-SPI_HandleTypeDef hspi1;
|
|
|
|
|
|
|
+SRAM_HandleTypeDef hsram1;
|
|
|
|
|
+
|
|
|
|
|
+void MX_FSMC_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ FSMC_NORSRAM_TimingTypeDef Timing = {0};
|
|
|
|
|
+
|
|
|
|
|
+ hsram1.Instance = FSMC_NORSRAM_DEVICE;
|
|
|
|
|
+ hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
|
|
|
|
|
+ hsram1.Init.NSBank = FSMC_NORSRAM_BANK4; // NE4
|
|
|
|
|
+ hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
|
|
|
|
|
+ hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
|
|
|
|
|
+ hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
|
|
|
|
|
+ hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
|
|
|
|
|
+ hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
|
|
|
|
|
+ hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
|
|
|
|
|
+ hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
|
|
|
|
|
+ hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
|
|
|
|
|
+ hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
|
|
|
|
|
+ hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
|
|
|
|
|
+ hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
|
|
|
|
|
+ hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
|
|
|
|
|
+
|
|
|
|
|
+ Timing.AddressSetupTime = 15;
|
|
|
|
|
+ Timing.AddressHoldTime = 15;
|
|
|
|
|
+ Timing.DataSetupTime = 71;
|
|
|
|
|
+ Timing.BusTurnAroundDuration = 15;
|
|
|
|
|
+ Timing.CLKDivision = 16;
|
|
|
|
|
+ Timing.DataLatency = 17;
|
|
|
|
|
+ Timing.AccessMode = FSMC_ACCESS_MODE_A;
|
|
|
|
|
+
|
|
|
|
|
+ if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
|
|
|
|
|
+ Error_Handler();
|
|
|
|
|
+
|
|
|
|
|
+ __HAL_AFIO_FSMCNADV_DISCONNECTED();
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-// HAL 库发送接收
|
|
|
|
|
-uint8_t tx_data = 0x06; // WREN 指令
|
|
|
|
|
-uint8_t rx_data;
|
|
|
|
|
|
|
+### HAL_FSMC_MspInit(LCD 版,NE4)
|
|
|
|
|
|
|
|
-HAL_SPI_Transmit(&hspi1, &tx_data, 1, 100); // 阻塞发送 1 字节
|
|
|
|
|
-HAL_SPI_Receive(&hspi1, &rx_data, 1, 100); // 阻塞接收 1 字节
|
|
|
|
|
-HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, 100); // 同时收发
|
|
|
|
|
|
|
+```c
|
|
|
|
|
+static void HAL_FSMC_MspInit(void)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
|
|
|
+ if (FSMC_Initialized) return;
|
|
|
|
|
+ FSMC_Initialized = 1;
|
|
|
|
|
+
|
|
|
|
|
+ __HAL_RCC_FSMC_CLK_ENABLE();
|
|
|
|
|
+
|
|
|
|
|
+ // PG0(A10), PG12(NE4)
|
|
|
|
|
+ GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_12;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ // PE7~PE15(D4~D12)
|
|
|
|
|
+ GPIO_InitStruct.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;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ // PD0(D2), PD1(D3), PD4(NOE), PD5(NWE), PD8~PD10(D13~D15),
|
|
|
|
|
+ // PD14(D0), PD15(D1)
|
|
|
|
|
+ GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14
|
|
|
|
|
+ |GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4
|
|
|
|
|
+ |GPIO_PIN_5;
|
|
|
|
|
+ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
|
|
|
|
+ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
|
|
|
+ HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### lcd.h 与 lcd.c
|
|
|
|
|
+
|
|
|
|
|
+与项目 41 的 LCD 驱动代码**几乎相同**,关键差异:
|
|
|
|
|
+- `LCD_Init()` 调用 `MX_FSMC_Init()` 而非 `FSMC_Init()`
|
|
|
|
|
+- `LCD_Reset()` 使用 `HAL_Delay()` 而非 `Delay_ms()`
|
|
|
|
|
+- `LCD_RegConfig()` 使用 `HAL_Delay(120)` 而非 `Delay_ms(120)`
|
|
|
|
|
|
|
|
-// HAL 库中断方式
|
|
|
|
|
-HAL_SPI_Transmit_IT(&hspi1, buffer, size); // 中断发送
|
|
|
|
|
-HAL_SPI_Receive_DMA(&hspi1, buffer, size); // DMA 接收
|
|
|
|
|
|
|
+### main.c
|
|
|
|
|
|
|
|
-// HAL 库写 W25Q64 示例
|
|
|
|
|
-uint8_t cmd[] = {0x06}; // WREN
|
|
|
|
|
-HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
|
|
|
|
|
-HAL_SPI_Transmit(&hspi1, cmd, 1, 100);
|
|
|
|
|
-HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
|
|
|
|
|
|
|
+**文件**:`Core/Src/main.c`
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+#include "main.h"
|
|
|
|
|
+#include "usart.h"
|
|
|
|
|
+#include "gpio.h"
|
|
|
|
|
+#include "fsmc.h"
|
|
|
|
|
+#include "lcd.h"
|
|
|
|
|
+
|
|
|
|
|
+int main(void)
|
|
|
|
|
+{
|
|
|
|
|
+ HAL_Init();
|
|
|
|
|
+ SystemClock_Config();
|
|
|
|
|
+
|
|
|
|
|
+ MX_GPIO_Init();
|
|
|
|
|
+ MX_FSMC_Init();
|
|
|
|
|
+ MX_USART1_UART_Init();
|
|
|
|
|
+
|
|
|
|
|
+ LCD_Init();
|
|
|
|
|
+
|
|
|
|
|
+ printf("中国芯LCD实验开始...\n");
|
|
|
|
|
+
|
|
|
|
|
+ uint32_t id = LCD_ReadID();
|
|
|
|
|
+ printf("id = %#x\n", id);
|
|
|
|
|
+
|
|
|
|
|
+ LCD_ClearAll(WHITE);
|
|
|
|
|
+
|
|
|
|
|
+ LCD_WriteAsciiChar(10, 10, 16, 'A', WHITE, RED);
|
|
|
|
|
+ LCD_WriteAsciiChar(10, 30, 24, 'B', RED, WHITE);
|
|
|
|
|
+ LCD_WriteAsciiChar(10, 60, 32, 'R', BLUE, YELLOW);
|
|
|
|
|
+
|
|
|
|
|
+ LCD_WriteAsciiString(200, 200, 24,
|
|
|
|
|
+ "Hello, Atguigu! Hello, hello! At\nguigu! Hello, wolrd!",
|
|
|
|
|
+ BLACK, WHITE);
|
|
|
|
|
+
|
|
|
|
|
+ LCD_WriteChineseChar(20, 330, 32, 0, RED, BLUE);
|
|
|
|
|
+ LCD_WriteChineseChar(20, 362, 32, 1, BLUE, RED);
|
|
|
|
|
+ LCD_WriteChineseChar(20, 394, 32, 2, GRAY, RED);
|
|
|
|
|
+
|
|
|
|
|
+ LCD_DisplayAtguiguLogo(50, 100);
|
|
|
|
|
+
|
|
|
|
|
+ LCD_DrawPoint(300, 300, 5, RED);
|
|
|
|
|
+ LCD_DrawLine(10, 10, 10, 300, 5, RED);
|
|
|
|
|
+ LCD_DrawLine(10, 20, 300, 100, 3, BLUE);
|
|
|
|
|
+ LCD_DrawRectangle(15, 15, 280, 55, 3, RED);
|
|
|
|
|
+ LCD_DrawCircle(150, 400, 50, 3, BLUE);
|
|
|
|
|
+ LCD_DrawFilledCircle_Pro(260, 400, 50, 3, BLUE, RED);
|
|
|
|
|
+
|
|
|
|
|
+ while (1) {}
|
|
|
|
|
+}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## 核心速查表
|
|
## 核心速查表
|
|
|
|
|
|
|
|
-| SPI 操作 | 软件模拟 | 硬件寄存器 | HAL 库 |
|
|
|
|
|
-|---------|---------|-----------|--------|
|
|
|
|
|
-| 初始化 | GPIO 开漏/推挽 | `CR1 |= MSTR + SPE` + GPIO 复用 | `MX_SPI1_Init()` |
|
|
|
|
|
-| 发 1 字节 | 8 次 GPIO 位操作 | `DR = byte; while(!RXNE); val = DR` | `HAL_SPI_Transmit()` |
|
|
|
|
|
-| 收 1 字节 | 同发送(发 0xFF 占位) | 同发送(发占位字节) | `HAL_SPI_Receive()` |
|
|
|
|
|
-| 同时收发 | 循环 8 次读写 | `DR = tx; val = DR` | `HAL_SPI_TransmitReceive()` |
|
|
|
|
|
-| 中断方式 | — | CR2.TXEIE/RXNEIE | `_IT()` 后缀 |
|
|
|
|
|
-| DMA 方式 | — | CR2.TXDMAEN/RXDMAEN | `_DMA()` 后缀 |
|
|
|
|
|
-| 片选 | GPIO 位操作 | GPIO 位操作 | `HAL_GPIO_WritePin()` |
|
|
|
|
|
-
|
|
|
|
|
-| FSMC 操作 | 代码 |
|
|
|
|
|
-|----------|------|
|
|
|
|
|
-| 写 SRAM | `*(uint16_t *)0x60000000 = data` |
|
|
|
|
|
-| 读 SRAM | `data = *(uint16_t *)0x60000000` |
|
|
|
|
|
-| 写 LCD 命令 | `*(uint16_t *)0x60000000 = cmd` |
|
|
|
|
|
-| 写 LCD 数据 | `*(uint16_t *)0x60020000 = data` |
|
|
|
|
|
|
|
+| SPI 操作 | 软件模拟(36) | 硬件寄存器(37) | HAL 库(38) |
|
|
|
|
|
+|---------|--------------|----------------|------------|
|
|
|
|
|
+| SCK/MOSI 配置 | 通用推挽输出 | 复用推挽输出 | `GPIO_MODE_AF_PP` |
|
|
|
|
|
+| MISO 配置 | 浮空输入 | 浮空输入 | `GPIO_MODE_INPUT` |
|
|
|
|
|
+| 初始化 | GPIO 位操作 + 延时 | `CR1 \|= MSTR+SSM+SSI+SPE` | `MX_SPI1_Init()` |
|
|
|
|
|
+| 发 1 字节 | 8 次 GPIO 位操作 | `DR=byte; while(!RXNE); val=DR` | `HAL_SPI_Transmit()` |
|
|
|
|
|
+| 同时收发 | 循环 8 次读写 | `DR=byte; val=DR` | `HAL_SPI_TransmitReceive()` |
|
|
|
|
|
+| 片选 CS | GPIO 位操作 | GPIO 位操作 | `HAL_GPIO_WritePin()` |
|
|
|
|
|
+| 适用场景 | 任意引脚/低速 | 固定引脚/高速 | 跨平台/快速开发 |
|
|
|
|
|
+
|
|
|
|
|
+| FSMC 操作 | 寄存器版(39/41) | HAL 库版(40/42) |
|
|
|
|
|
+|----------|----------------|-----------------|
|
|
|
|
|
+| SRAM 片选 | Bank3 (NE3, PG10) | `FSMC_NORSRAM_BANK3` |
|
|
|
|
|
+| LCD 片选 | Bank4 (NE4, PG12) | `FSMC_NORSRAM_BANK4` |
|
|
|
|
|
+| 命令/数据区分 | A10 地址线:`偏移 0x800` | 同左 |
|
|
|
|
|
+| 写 SRAM | `*(uint16_t *)0x68000000 = data` | 同左 |
|
|
|
|
|
+| 读 SRAM | `data = *(uint16_t *)0x68000000` | 同左 |
|
|
|
|
|
+| 写 LCD 命令 | `*LCD_ADDR_CMD = cmd` | 同左 |
|
|
|
|
|
+| 写 LCD 数据 | `*LCD_ADDR_DATA = data` | 同左 |
|
|
|
|
|
+| 初始化 | 手动配置寄存器 | `MX_FSMC_Init()` + `HAL_SRAM_Init()` |
|
|
|
|
|
+
|
|
|
|
|
+### 各项目引脚分配
|
|
|
|
|
+
|
|
|
|
|
+| 信号 | 36/37/38 (SPI) | 39/40 (SRAM) | 41/42 (LCD) |
|
|
|
|
|
+|------|---------------|-------------|------------|
|
|
|
|
|
+| SPI_SCK | PA5 | — | — |
|
|
|
|
|
+| SPI_MOSI | PA7 | — | — |
|
|
|
|
|
+| SPI_MISO | PA6 | — | — |
|
|
|
|
|
+| SPI_CS | PC13 | — | — |
|
|
|
|
|
+| FSMC_NE | — | PG10 (NE3) | PG12 (NE4) |
|
|
|
|
|
+| FSMC_A10 | — | — | PG0 (RS) |
|
|
|
|
|
+| LCD_RST | — | — | PG15 |
|
|
|
|
|
+| LCD_BL | — | — | PB0 |
|
|
|
|
|
+| FSMC_NOE | — | PD4 | PD4 |
|
|
|
|
|
+| FSMC_NWE | — | PD5 | PD5 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
## 常见问题与避坑
|
|
## 常见问题与避坑
|
|
|
|
|
|
|
|
1. **SPI 接收数据为 0xFF** → MISO 连接断开、从机未选中(CS 拉低后才有输出)、从机忙于内部操作
|
|
1. **SPI 接收数据为 0xFF** → MISO 连接断开、从机未选中(CS 拉低后才有输出)、从机忙于内部操作
|
|
|
2. **W25Q64 写入失败** → 每次写操作前必须发 WREN(0x06);扇区必须事先擦除(Flash 不能写覆盖)
|
|
2. **W25Q64 写入失败** → 每次写操作前必须发 WREN(0x06);扇区必须事先擦除(Flash 不能写覆盖)
|
|
|
-3. **FSMC 读写时序不对** → 查 SRAM/LCD 数据手册的时序参数(ADDSET 和 DATAST),STM32 的 HCLK 对应关系
|
|
|
|
|
|
|
+3. **FSMC 读写时序不对** → 查 SRAM/LCD 数据手册的时序参数(ADDSET 和 DATAST),STM32 的 HCLK 对应关系。DATAST 的计算:`DATAST = (T_access / T_HCLK) - ADDSET`。LCD 通常比 SRAM 慢,可适当增大 DATAST
|
|
|
4. **软件 SPI 速度太慢** → 软件 SPI 受限于 GPIO 翻转速度(约 2~4MHz),大批量数据建议用硬件 SPI
|
|
4. **软件 SPI 速度太慢** → 软件 SPI 受限于 GPIO 翻转速度(约 2~4MHz),大批量数据建议用硬件 SPI
|
|
|
5. **NSS 软件管理模式** → 多从机时必须用 SSM=1 软件管理 NSS,否则硬件自动管理可能产生冲突
|
|
5. **NSS 软件管理模式** → 多从机时必须用 SSM=1 软件管理 NSS,否则硬件自动管理可能产生冲突
|
|
|
|
|
+6. **LCD 显示异常(花屏/白屏)** → 检查 FSMC 时序(LCD 控制器通常需要更长的 DATAST);检查复位引脚 PG15 和背光引脚 PB0 的 GPIO 配置;确认 LCD_ADDR_DATA 的地址偏移量(A10 连接 RS,偏移量 = 1<<10 = 0x400,但实际使用 1<<11 = 0x800,需核对硬件连接)
|
|
|
|
|
+7. **HAL 库 FSMC 初始化失败** → 确认 CubeMX 中 FSMC 的 Bank 选择与硬件一致(SRAM 选 NE3,LCD 选 NE4);NADV 必须断开(`__HAL_AFIO_FSMCNADV_DISCONNECTED()`)
|