4.17 SPI接口的 SD 卡模块(来自 Yagi 的贡献)
4.17.1 模块来源
采购链接:https://item.taobao.com/item.htm?spm=a1z09.2.0.0.6e7e2e8doYpySd&id=41750115602&_u=gcfflaf6b5
资料下载:http://pan.baidu.com/s/1i3A9gxv
4.17.2 规格参数
工作电压:5V(范围:4.5V 至 5.5V)
工作电流:80mA(范围:0.2mA 至 200mA)
接口电平:3.3V 或 5V
通信方式:SPI 接口
支持卡类型:Micro SD 卡(<=2G),Mirco SDHC 卡(<=32G)
尺寸:42mm X 24mm X 12mm
重量:5g
4.17.3 模块原理
该模块(MicroSD Card Adapter)是 Micro SD 卡读写模块,通过SPI 接口驱动程序,单片机系统即可实现 对 MicroSD 卡的读写等操作。
(一)模块原理
(2)检查卡版本是否为 Ver 1.X
通过发送一个 CMD8 命令,如果 SD 卡响应该命令无效,说明该卡的版本为 Ver1.X。如果有响应,说明卡版本为 Ver2.X 或者更高的版本。 CMD8 的参数一般为 0x000001AA,SD 卡收到响应之后,应该首先检查高 8 位,也就是 R1 令牌部分。 当 R1 的第二位,也就是 R1 & 0x0004 为真时,该命令无效,说明卡版本为 Ver1.X。 当 CMD8 有效时,应该抛弃剩余的 4 字节数据。
(3)检查支持电压范围(可选)
通过发送 ACMD41 命令去初始化 SD 卡。注意,在发送 ACMD41 之前,应该先发送 CMD55 命令,而且 CMD55 命令会响应一个 R1 令牌,此处的 R1 令牌可以直接抛弃。
对于 Ver1.X 版本的卡,ACMD41 的参数应该为 0x00000000; 对于 Ver2.X 以及更高版本的卡,ACMD41 的参数一般使用 0x40000000。
这时需要不断发送 0xFF 同时检查接收 R1 令牌(其实这里的等待接收步骤和 CMD0 一样),直到 R1 为 0,表示 SD 卡初始化成功。这里的循环建议添加超时计数。
(5)获取卡的容量状态
通过 CMD58 命令获取 OCR 寄存器的信息,其中 CCS 位位于 OCR 寄存器的第 30 位。 1 为高容量卡(SDHC) 0 为标准容量卡(SDSC)
(6)设置块大小
SD 卡的所有读写操作,都是以块为单位去读写,因此需要预先设置一个块的大小。 通过 CMD16 命令设置 SD 卡的块大小(字节),后续所有块操作命令(读和写)都是以此设置的大小为准,对于高容量卡,块大小固定为 512 字节。对于低容量的卡,为了统一,一般建议将块大小设置为 512 字节。
SD 卡初始化完成之后,可以将 SPI 总线切换成高速总线。
4.17.4 移植工程
根据 SD 模块数据手册和原理图,实现基于梁山派开发板(GD32F450ZGT6)的驱动代码,通过其 SPI1 接口实现基于该模块读取 MicroSD 卡信息的功能。
4.17.4-2 移植步骤
以下为软件部分,包括系统配置、SPI 接口管理、SD 模块驱动及主程序等部分。
1、主程序(main.c)
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: yagi
* 修改日期: 2023年07月11日
* 功能介绍: 主函数
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "sys_config.h"
#include "systick.h"
#include "stdio.h"
#include "bsp_spi.h"
#include "bsp_sdcard.h"
int main(void)
{
float sd_size;
sys_init(); //系统初始化
printf("Start...\r\n");
gpio_bit_set(GPIO_LED_PORT, GPIO_LED_PIN); //LED01点亮(测试用)
while (sd_init()) //SD卡检测:检测不到SD卡
{
printf("SD Card Error!\r\n"); //串口输出错误提示信息
delay_1ms(500);
printf("Please Check! \r\n");
delay_1ms(500);
gpio_bit_toggle(GPIO_LED_PORT, GPIO_LED_PIN); //LED01闪烁(测试用)
}
// 检测SD卡成功
sd_size = (float)(sd_get_sector_count() * 512 / 1024 / 1024); //得到SD卡容量,转换为MB单位
printf("SD Card OK!\r\n");
printf("SD Card Size : %f MB",sd_size); //串口输出提示信息及卡容量信息
while (1)
{
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2、系统配置模块(sys_config.h)
#ifndef SYS_CONFIG_H
#define SYS_CONFIG_H
#include "gd32f4xx.h"
//******************************************************************
//宏定义
//******************************************************************
//SPI
#define BSP_SPI SPI1
#define RCU_SPI RCU_SPI1
#define GPIO_SPI_PORT GPIOB
#define GPIO_SPI_AF GPIO_AF_5
#define GPIO_SPI_CS_PIN GPIO_PIN_12
#define GPIO_SPI_SCK_PIN GPIO_PIN_13
#define GPIO_SPI_MISO_PIN GPIO_PIN_14
#define GPIO_SPI_MOSI_PIN GPIO_PIN_15
#define BSP_SPI_IRQ SPI1_IRQn
//UART
#define BSP_UART USART0
#define RCU_UART RCU_USART0
#define GPIO_UART_PORT GPIOA
#define GPIO_UART_AF GPIO_AF_7
#define GPIO_UART_TX_PIN GPIO_PIN_9
#define GPIO_UART_RX_PIN GPIO_PIN_10
#define BSP_UART_IRQ USART0_IRQn
#define BANDRATE 9600
//LED
#define GPIO_LED_PORT GPIOE
#define GPIO_LED_PIN GPIO_PIN_3
//******************************************************************
//函数声明
//******************************************************************
void sys_init(void);
void rcc_conf(void);
void gpio_conf(void);
void nvic_conf(void);
void uart_conf(void);
#endif /*SYS_CONFIG_H*/2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
3、系统配置模块(sys_config.c)
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: yagi(八木)
* 修改日期: 2023年07月01日
* 功能介绍: 系统初始化配置模块
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "sys_config.h"
#include "systick.h"
#include <stdio.h>
#include "bsp_spi.h"
#include "bsp_sdcard.h"
/******************************************************************
* 函 数 名 称:system_init
* 函 数 说 明:系统初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void sys_init(void)
{
rcc_conf(); //系统时钟配置
gpio_conf(); //GPIO端口配置
//nvic_conf(); //中断向量及优先级配置
uart_conf(); //串口设备配置
systick_config(); //滴答定时器配置 1us
spi_conf(); //SPI设备初始化
//sd_init(); //SD卡模块初始化
}
/******************************************************************
* 函 数 名 称:rcc_conf
* 函 数 说 明:系统时钟配置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void rcc_conf(void)
{
//rcu_system_clock_source_config(RCU_CKSYSSRC_PLLP); //配置系统时钟源
//systick_clksource_set(SYSTICK_CLKSOURCE_HCLK); //设置SYSTICK时钟源
rcu_periph_clock_enable(RCU_SYSCFG); //系统配置时钟使能
rcu_periph_clock_enable(RCU_GPIOA); //GPIOA口时钟使能
rcu_periph_clock_enable(RCU_GPIOB); //GPIOA口时钟使能
//rcu_periph_clock_enable(RCU_GPIOD); //GPIOD口时钟使能
rcu_periph_clock_enable(RCU_GPIOE); //GPIOE口时钟使能
//rcu_periph_clock_enable(RCU_GPIOG); //GPIOG口时钟使能
rcu_periph_clock_enable(RCU_UART); //设置USART0时钟使能
rcu_periph_clock_enable(RCU_SPI); //设置SPI0时钟使能
}
/******************************************************************
* 函 数 名 称:gpio_conf
* 函 数 说 明:GPIO端口配置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void gpio_conf(void)
{
//【LED1】
//GPIO端口复位
gpio_deinit(GPIO_LED_PORT);
//GPIO模式设置
gpio_mode_set(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_LED_PIN);
//GPIO输出属性设置
gpio_output_options_set(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_LED_PIN);
//【SPI0】
//GPIO端口复位
gpio_deinit(GPIO_SPI_PORT);
//GPIO模式设置
gpio_mode_set(GPIO_SPI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_SPI_SCK_PIN | GPIO_SPI_MISO_PIN | GPIO_SPI_MOSI_PIN);
gpio_mode_set(GPIO_SPI_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_SPI_CS_PIN);
//GPIO输出属性设置
gpio_output_options_set(GPIO_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_SPI_CS_PIN | GPIO_SPI_SCK_PIN | GPIO_SPI_MISO_PIN | GPIO_SPI_MOSI_PIN);
//GPIO附属AF功能设置
gpio_af_set(GPIO_SPI_PORT, GPIO_SPI_AF, GPIO_SPI_SCK_PIN | GPIO_SPI_MISO_PIN | GPIO_SPI_MOSI_PIN);
//【UART0】
//GPIO端口复位
gpio_deinit(GPIO_UART_PORT);
//GPIO模式设置
gpio_mode_set(GPIO_UART_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_UART_TX_PIN | GPIO_UART_RX_PIN);
//GPIO输出属性设置
gpio_output_options_set(GPIO_UART_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_UART_TX_PIN | GPIO_UART_RX_PIN);
//GPIO附属AF功能设置
gpio_af_set(GPIO_UART_PORT, GPIO_UART_AF, GPIO_UART_TX_PIN | GPIO_UART_RX_PIN);
}
/******************************************************************
* 函 数 名 称:nvic_conf
* 函 数 说 明:中断向量及优先级配置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void nvic_conf(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); //优先级分组
nvic_irq_enable(BSP_UART_IRQ, 2U, 2U); //设置UART0中断向量优先级
nvic_irq_enable(BSP_SPI_IRQ, 2U, 2U); //设置SPI0中断向量优先级
}
/******************************************************************
* 函 数 名 称:uart_conf
* 函 数 说 明:串口设备配置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void uart_conf(void)
{
usart_deinit(BSP_UART); //UART设备复位
usart_baudrate_set(BSP_UART, BANDRATE); //设置波特率
usart_parity_config(BSP_UART, USART_PM_NONE); //设置奇偶校验
usart_word_length_set(BSP_UART, USART_WL_8BIT); //设置数据包长度
usart_stop_bit_set(BSP_UART, USART_STB_1BIT); //设置停止位
usart_enable(BSP_UART); //UART设备使能
usart_transmit_config(BSP_UART, USART_TRANSMIT_ENABLE); //UART设备发送使能
//usart_receive_config(BSP_UART, USART_RECEIVE_ENABLE); //UART设备接收使能
}
/******************************************************************
* 函 数 名 称:usart_send_data
* 函 数 说 明:串口发送字符函数
* 函 数 形 参:tmp_data = 发送字符
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void usart_send_data(uint8_t tmp_data)
{
usart_data_transmit(BSP_UART, (uint8_t)tmp_data); //发送字符
while (RESET == usart_flag_get(BSP_UART, USART_FLAG_TBE)); //等待发送完成
}
/******************************************************************
* 函 数 名 称:usart_send_str
* 函 数 说 明:串口发送字符串函数
* 函 数 形 参:*tmp_str = 发送字符串指针
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void usart_send_str(uint8_t *tmp_str)
{
while(tmp_str && *tmp_str)
{
usart_send_data(*tmp_str++); //循环发送字符串
}
}
/******************************************************************
* 函 数 名 称:fputc
* 函 数 说 明:重定向函数(重写)
* 函 数 形 参:ch = 发送字符; *f = 文件指针
* 函 数 返 回:成功 = 写入文件的字符的ASCII码值;失败 = EOF(-1)
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
int fputc(int ch,FILE *f)
{
usart_send_data(ch);
return ch;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
4、SPI 接口管理模块(bsp_spi.h)
#ifndef BSP_SPI_H
#define BSP_SPI_H
#include "gd32f4xx.h"
#include "sys_config.h"
//******************************************************************
//宏定义
//******************************************************************
/* SPI总线速度设置 */
#define SPI_SPEED_2 0
#define SPI_SPEED_4 1
#define SPI_SPEED_8 2
#define SPI_SPEED_16 3
#define SPI_SPEED_32 4
#define SPI_SPEED_64 5
#define SPI_SPEED_128 6
#define SPI_SPEED_256 7
//******************************************************************
//函数声明
//******************************************************************
void spi_conf(void);
void spi_set_speed(uint8_t speed);
uint8_t spi_read_write_byte(uint8_t txdata);
#endif /*BSP_SPI_H*/2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
5、SPI 接口管理模块(bsp_spi.c)
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: yagi(八木)
* 修改日期: 2023年07月01日
* 功能介绍: SD卡设备操作功能文件
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "sys_config.h"
#include "bsp_spi.h"
#include "gd32f4xx_spi.h"
#include <stdio.h>
/******************************************************************
* 函 数 名 称:spi_conf
* 函 数 说 明:SPI设备配置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void spi_conf(void)
{
//SPI初始化结构体声明
spi_parameter_struct spi_initstruct;
//SPI结构体参数赋值
spi_initstruct.device_mode = SPI_MASTER; //SPI设备模式:主机模式
spi_initstruct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; //SPI数据传输模式:双线全双工模式
spi_initstruct.frame_size = SPI_FRAMESIZE_8BIT; //SPI每帧数据大小:8位
spi_initstruct.nss = SPI_NSS_SOFT; //SPI片选NSS引脚控制模式:软件控制
spi_initstruct.endian = SPI_ENDIAN_MSB; //SPI数据优先级设置:高位数据优先
spi_initstruct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; //模式3:SPI时钟空闲状态为高电平;SPI采样时间为偶数边沿
spi_initstruct.prescale = 256U; //SPI时钟分频系数:256分频(APB2总线100MHz,SPI0时钟390.625KHz)
//SPI初始化
spi_init(BSP_SPI, &spi_initstruct);
//SPI使能
spi_enable(BSP_SPI);
}
/******************************************************************
* 函 数 名 称:spi_set_speed
* 函 数 说 明:SPI速度设置函数,SPI0时钟选择来自APB2, 为100Mhz,SPI速度 = CK_APB2 / 2^(speed + 1)
* 函 数 形 参:speed = SPI0时钟分频系数(详见宏定义)
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void spi_set_speed(uint8_t speed)
{
speed &= 0x07; /* 限制范围 */
SPI_CTL0(BSP_SPI) &= ~(1 << 6); /* SPE = 0 , SPI设备失能 */
SPI_CTL0(BSP_SPI) &= ~(7 << 3); /* BR[2:0] = 0 , 先清零 */
SPI_CTL0(BSP_SPI) |= speed << 3; /* BR[2:0] = speed , 设置分频系数 */
SPI_CTL0(BSP_SPI) |= 1 << 6; /* SPE = 1 , SPI设备使能 */
}
/******************************************************************
* 函 数 名 称:spi_read_write_byte
* 函 数 说 明:SPI读写一个字节数据
* 函 数 形 参:txdata = 要发送的数据(单字节)
* 函 数 返 回:接收到的数据(单字节)
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t spi_read_write_byte(uint8_t txdata)
{
while ((SPI_STAT(BSP_SPI) & (1 << 1)) == 0); /* 等待发送区空 */
SPI_DATA(BSP_SPI) = txdata; /* 发送一个byte */
while ((SPI_STAT(BSP_SPI) & (1 << 0)) == 0); /* 等待接收完一个byte */
return SPI_DATA(BSP_SPI); /* 返回收到的数据 */
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
6、SD 驱动模块(bsp_sdcard.h)
#ifndef BSP_SDCARD_H
#define BSP_SDCARD_H
#include "gd32f4xx.h"
#include "sys_config.h"
//******************************************************************
//宏定义
//******************************************************************
/* SD卡 SPI 操作函数 宏定义
* 大家移植的时候, 根据需要实现: spi_read_write_byte 和 spi_set_speed
* 这两个函数即可, SD卡 SPI模式, 会通过这两个函数, 实现对SD卡的操作.
*/
#define SD_SPI_READ_WRITE_BYTE(x) spi_read_write_byte(x) /* SD卡 SPI读写函数 */
#define SD_SPI_SPEED_LOW() spi_set_speed(SPI_SPEED_256) /* SD卡 SPI低速模式 */
#define SD_SPI_SPEED_HIGH() spi_set_speed(SPI_SPEED_2) /* SD卡 SPI高速模式 */
/******************************************************************************************/
/* SD_CS 端口定义 */
#define SD_CS(x) gpio_bit_write(GPIO_SPI_PORT, GPIO_SPI_CS_PIN, x) /* SD_CS */
/******************************************************************************************/
/* SD卡 返回值定义 */
#define SD_OK 0
#define SD_ERROR 1
/* SD卡 类型定义 */
#define SD_TYPE_ERR 0X00
#define SD_TYPE_MMC 0X01
#define SD_TYPE_V1 0X02
#define SD_TYPE_V2 0X04
#define SD_TYPE_V2HC 0X06
/* SD卡 命令定义 */
#define CMD0 (0) /* GO_IDLE_STATE */
#define CMD1 (1) /* SEND_OP_COND (MMC) */
#define ACMD41 (0x80 + 41) /* SEND_OP_COND (SDC) */
#define CMD8 (8) /* SEND_IF_COND */
#define CMD9 (9) /* SEND_CSD */
#define CMD10 (10) /* SEND_CID */
#define CMD12 (12) /* STOP_TRANSMISSION */
#define ACMD13 (0x80 + 13) /* SD_STATUS (SDC) */
#define CMD16 (16) /* SET_BLOCKLEN */
#define CMD17 (17) /* READ_SINGLE_BLOCK */
#define CMD18 (18) /* READ_MULTIPLE_BLOCK */
#define CMD23 (23) /* SET_BLOCK_COUNT (MMC) */
#define ACMD23 (0x80 + 23) /* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24 (24) /* WRITE_BLOCK */
#define CMD25 (25) /* WRITE_MULTIPLE_BLOCK */
#define CMD32 (32) /* ERASE_ER_BLK_START */
#define CMD33 (33) /* ERASE_ER_BLK_END */
#define CMD38 (38) /* ERASE */
#define CMD55 (55) /* APP_CMD */
#define CMD58 (58) /* READ_OCR */
/* SD卡的类型 */
extern uint8_t sd_type;
//******************************************************************
//函数声明
//******************************************************************
static void sd_deselect(void); /* SD卡取消选中 */
static uint8_t sd_select(void); /* SD卡 选中 */
static uint8_t sd_wait_ready(void); /* 等待SD卡准备好 */
static uint8_t sd_get_response(uint8_t response); /* 等待SD卡回应 */
static uint8_t sd_send_cmd(uint8_t cmd, uint32_t arg); /* SD卡发送命令 */
static uint8_t sd_send_block(uint8_t *buf, uint8_t cmd); /* SD卡发送一个数据块 */
static uint8_t sd_receive_data(uint8_t *buf, uint16_t len); /* SD卡接收一次数据 */
uint8_t sd_init(void); /* SD 卡初始化 */
uint32_t sd_get_sector_count(void); /* 获取SD卡的总扇区数(扇区数) */
uint8_t sd_get_status(void); /* 获取SD卡状态 */
uint8_t sd_get_cid(uint8_t *cid_data); /* 获取SD卡的CID信息 */
uint8_t sd_get_csd(uint8_t *csd_data); /* 获取SD卡的CSD信息 */
uint8_t sd_read_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt); /* 读SD卡(fatfs/usb调用) */
uint8_t sd_write_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt); /* 写SD卡(fatfs/usb调用) */
#endif /*BSP_SDCARD_H*/2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
7、SD 驱动模块(bsp_sdcard.c)
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: yagi(八木)
* 修改日期: 2023年07月01日
* 功能介绍: SD卡设备操作功能文件
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "sys_config.h"
#include "bsp_spi.h"
#include "bsp_sdcard.h"
#include "gd32f4xx_spi.h"
#include <stdio.h>
uint8_t sd_type = 0; /* SD卡的类型 */
/******************************************************************
* 函 数 名 称:sd_deselect
* 函 数 说 明:SD卡 取消选择, 释放 SPI总线
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
void sd_deselect(void)
{
SD_CS(SET); /* 取消SD卡片选 */
spi_read_write_byte(0xff); /* 提供额外的8个时钟 */
}
/******************************************************************
* 函 数 名 称:sd_select
* 函 数 说 明:SD卡 选中, 并等待卡准备OK
* 函 数 形 参:无
* 函 数 返 回:返回选中结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_select(void)
{
SD_CS(RESET);
if (sd_wait_ready() == SD_OK)
{
return SD_OK; /* 等待成功 */
}
sd_deselect();
return SD_ERROR; /* 等待失败 */
}
/******************************************************************
* 函 数 名 称:sd_wait_ready
* 函 数 说 明:等待卡准备好
* 函 数 形 参:无
* 函 数 返 回:返回等待结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_wait_ready(void)
{
uint32_t t = 0;
uint8_t tmp = 0; //调试程序使用
do
{
tmp = spi_read_write_byte(0xFF);
//printf("tmp:%d\r\n",tmp);
if (tmp == 0xFF)
{
return SD_OK; /* OK */
}
t++;
} while (t < 0xFFFFFF); /* 等待 */
return SD_ERROR;
}
/******************************************************************
* 函 数 名 称:sd_get_response
* 函 数 说 明:等待SD卡回应
* 函 数 形 参:response = 期待得到的回应值
* 函 数 返 回:返回等待结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_get_response(uint8_t response)
{
uint16_t count = 0xFFFF; /* 等待次数 */
while ((spi_read_write_byte(0xFF) != response) && count)
{
count--; /* 等待得到准确的回应 */
}
if (count == 0) /* 等待超时 */
{
return SD_ERROR;
}
return SD_OK; /* 正确回应 */
}
/******************************************************************
* 函 数 名 称:sd_receive_data
* 函 数 说 明:从SD卡读取一次数据,读取长度不限,由len指定,不过一般为512字节
* 函 数 形 参:buf = 数据缓存区;len = 要读取的数据长度
* 函 数 返 回:返回等待结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_receive_data(uint8_t *buf, uint16_t len)
{
if (sd_get_response(0xFE)) /* 等待SD卡发回数据起始令牌0xFE */
{
return SD_ERROR;
}
while (len--) /* 开始接收数据 */
{
*buf = spi_read_write_byte(0xFF);
buf++;
}
/* 下面是2个伪CRC(dummy CRC) */
spi_read_write_byte(0xFF);
spi_read_write_byte(0xFF);
return SD_OK; /* 读取成功 */
}
/******************************************************************
* 函 数 名 称:sd_send_block
* 函 数 说 明:向SD卡写入一个数据包,写入长度固定为512字节!!
* 函 数 形 参:buf = 数据缓存区;cmd = 指令
* 函 数 返 回:返回等待结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_send_block(uint8_t *buf, uint8_t cmd)
{
uint16_t t;
if (sd_wait_ready()) /* 等待ready */
{
return SD_ERROR;
}
spi_read_write_byte(cmd); /* 发送 CMD */
if (cmd != 0xFD) /* 不是结束指令 */
{
for (t = 0; t < 512; t++)
{
spi_read_write_byte(buf[t]); /* 发送数据 */
}
spi_read_write_byte(0xFF); /* 忽略crc */
spi_read_write_byte(0xFF);
t = spi_read_write_byte(0xFF); /* 接收响应 */
if ((t & 0x1F) != 0x05) /* 数据包没有被接收 */
{
return SD_ERROR;
}
}
return SD_OK; /* 写入成功 */
}
/******************************************************************
* 函 数 名 称:sd_send_cmd
* 函 数 说 明:向SD卡发送一个命令,不同命令的CRC值在该函数内部自动确认
* 函 数 形 参:cmd = 要发送的命令,最高位为1 表示ACMD(应用命令),最高位为0 表示CMD(普通命令);arg = 命令的参数
* 函 数 返 回:SD卡返回的命令响应
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_send_cmd(uint8_t cmd, uint32_t arg)
{
uint8_t res;
uint8_t retry = 0;
uint8_t crc = 0x01; /* 默认 CRC = 忽略CRC + 停止 */
if (cmd & 0x80) /* ACMD发送前, 需要先发送一个 CMD55 命令 */
{
cmd &= 0x7F; /* 清除最高位, 获取ACMD命令 */
res = sd_send_cmd(CMD55, 0); /* 发送CMD55 */
if (res > 1)
{
return res;
}
}
if (cmd != CMD12) /* 当 cmd 不等于 多块读结束命令时(CMD12), 等待卡选中成功 */
{
sd_deselect(); /* 取消上次片选 */
if (sd_select())
{
return 0xFF;/* 选中失败 */
}
}
/* 发送命令包 */
spi_read_write_byte(cmd | 0x40); /* 起始 + 命令索引号 */
spi_read_write_byte(arg >> 24); /* 参数[31 : 24] */
spi_read_write_byte(arg >> 16); /* 参数[23 : 16] */
spi_read_write_byte(arg >> 8); /* 参数[15 : 8] */
spi_read_write_byte(arg); /* 参数[7 : 0] */
if (cmd == CMD0) crc = 0x95; /* CMD0 的CRC值固定为 0X95 */
if (cmd == CMD8) crc = 0x87; /* CMD8 的CRC值固定为 0X87 */
spi_read_write_byte(crc);
if (cmd == CMD12) /* cmd 等于 多块读结束命令(CMD12)时 */
{
spi_read_write_byte(0xFF); /* CMD12 跳过一个字节 */
}
retry = 0x50; /* 重试次数 */
do /* 等待响应,或超时退出 */
{
res = spi_read_write_byte(0xFF);
} while ((res & 0x80) && retry--);
//printf("RES_CMD:%d\n\r",res); //调试使用
return res; /* 返回状态值 */
}
/******************************************************************
* 函 数 名 称:sd_get_status
* 函 数 说 明:获取SD卡的状态
* 函 数 形 参:cmd = 要发送的命令,最高位为1 表示ACMD(应用命令),最高位为0 表示CMD(普通命令);arg = 命令的参数
* 函 数 返 回:读取结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_get_status(void)
{
uint8_t res;
uint8_t retry = 20; /*发送ACMD经常失败, 多尝试几次 */
do
{
res = sd_send_cmd(ACMD13, 0); /* 发ACMD13命令,获取状态 */
}while(res && retry--);
sd_deselect(); /* 取消片选 */
return res;
}
/******************************************************************
* 函 数 名 称:sd_get_cid
* 函 数 说 明:获取SD卡的CID信息, 包括制造商信息等
* 函 数 形 参:cid_data = 存放CID的内存缓冲区(至少16字节)
* 函 数 返 回:读取结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_get_cid(uint8_t *cid_data)
{
uint8_t res;
res = sd_send_cmd(CMD10, 0); /* 发CMD10命令,读CID */
if (res == 0x00)
{
res = sd_receive_data(cid_data, 16); /* 接收16个字节的数据 */
}
sd_deselect(); /* 取消片选 */
return res;
}
/******************************************************************
* 函 数 名 称:sd_get_csd
* 函 数 说 明:获取SD卡的CSD信息,包括容量和速度信息
* 函 数 形 参:csd_data = 存放CSD的内存缓冲区(至少16字节)
* 函 数 返 回:读取结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_get_csd(uint8_t *csd_data)
{
uint8_t res;
res = sd_send_cmd(CMD9, 0); /* 发CMD9命令,读CSD */
if (res == 0)
{
res = sd_receive_data(csd_data, 16); /* 接收16个字节的数据 */
}
sd_deselect(); /* 取消片选 */
return res;
}
/******************************************************************
* 函 数 名 称:sd_get_sector_count
* 函 数 说 明:获取SD卡的总扇区数(扇区数),每扇区的字节数必为512, 如果不是512, 则初始化不能通过
* 函 数 形 参:无
* 函 数 返 回:SD卡容量(扇区数), 换成字节数要 * 512
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint32_t sd_get_sector_count(void)
{
uint8_t csd[16];
uint32_t capacity;
uint8_t n;
uint16_t csize;
if (sd_get_csd(csd) != 0) /* 取CSD信息,如果期间出错,返回0 */
{
return 0; /* 返回0表示获取容量失败 */
}
/* 如果为SDHC卡,按照下面方式计算 */
if ((csd[0] & 0xC0) == 0x40) /* V2.00的卡 */
{
csize = csd[9] + ((uint16_t)csd[8] << 8) + ((uint32_t)(csd[7] & 63) << 16) + 1;
capacity = (uint32_t)csize << 10; /* 得到扇区数 */
}
else /* V1.XX的卡 / MMC V3卡 */
{
n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
capacity = (uint32_t)csize << (n - 9); /* 得到扇区数 */
}
return capacity; /* 注意这里返回的是扇区数量, 不是实际容量的字节数, 换成字节数 得 * 512 */
}
/******************************************************************
* 函 数 名 称:sd_init
* 函 数 说 明:初始化SD卡
* 函 数 形 参:无
* 函 数 返 回:初始化结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_init(void)
{
uint8_t res; /* 存放SD卡的返回值 */
uint16_t retry; /* 用来进行超时计数 */
uint8_t ocr[4];
uint16_t i;
uint8_t cmd;
SD_SPI_SPEED_LOW(); /* 设置到低速模式 */
SD_CS(SET); /* 拉高CS引脚 */
for (i = 0; i < 10; i++)
{
spi_read_write_byte(0xFF); /* 发送最少74个脉冲 */
}
printf("已发送74个脉冲!\r\n");
SD_CS(RESET); /* 拉低CS引脚 */
retry = 80;
do
{
res = sd_send_cmd(CMD0, 0); /* 进入IDLE状态 */
} while ((res != 0x01) && retry--);
if(res == 0x01)
{
printf("已进入IDLE状态!RES:%d,retry:%d\r\n",res,retry);
}
sd_type = 0; /* 默认无卡 */
if (res == 0x01)
{
if (sd_send_cmd(CMD8, 0x1AA) == 1) /* SD V2.0 */
{
//printf("SD Card V2.0!\r\n");
for (i = 0; i < 4; i++)
{
ocr[i] = spi_read_write_byte(0xFF); /* 得到R7的32位响应 */
}
spi_read_write_byte(0xFF); /* 多发送8个时钟 */
// if (ocr[2] == 0x01 && ocr[3] == 0xAA) /* 卡是否支持2.7~3.6V */
// {
retry = 1000;
do
{
res = sd_send_cmd(ACMD41, 1UL << 30); /* 发送ACMD41 */
} while (res && retry--);
if (retry && sd_send_cmd(CMD58, 0) == 0) /* 鉴别SD2.0卡版本开始 */
{
for (i = 0; i < 4; i++)
{
ocr[i] = spi_read_write_byte(0xFF); /* 得到OCR值 */
}
if (ocr[0] & 0x40) /* 检查CCS */
{
sd_type = SD_TYPE_V2HC; /* V2.0 SDHC卡 */
printf("SD Card :V2.0 SDHC!\r\n");
}
else
{
sd_type = SD_TYPE_V2; /* V2.0 卡 */
printf("SD Card :V2.0!\r\n");
}
}
// }
}
else /* SD V1.x / MMC V3 */
{
printf("SD Card :V1.X!\r\n");
res = sd_send_cmd(ACMD41, 0); /* 发送ACMD41 */
retry = 1000;
if (res <= 1)
{
printf("SD Card :SD V1.0!\r\n");
sd_type = SD_TYPE_V1; /* SD V1卡 */
cmd = ACMD41; /* 命令等于 ACMD41 */
}
else /* MMC卡不支持 ACMD41 识别 */
{
printf("SD Card :MMC V3!\r\n");
sd_type = SD_TYPE_MMC; /* MMC V3 */
cmd = CMD1; /* 命令等于 CMD1 */
}
do
{
res = sd_send_cmd(cmd, 0); /* 发送 ACMD41 / CMD1 */
} while (res && retry--); /* 等待退出IDLE模式 */
if (retry == 0 || sd_send_cmd(CMD16, 512) != 0)
{
printf("SD Card :ERROR CARD!\r\n");
sd_type = SD_TYPE_ERR; /* 错误的卡 */
}
}
}
sd_deselect(); /* 取消片选 */
SD_SPI_SPEED_HIGH(); /* 高速模式 */
SD_CS(SET); /* 拉高CS引脚 */
if (sd_type) /* 卡类型有效, 初始化完成 */
{
return SD_OK;
}
printf("RES :%d\r\n",res);
return res;
}
/******************************************************************
* 函 数 名 称:sd_read_disk
* 函 数 说 明:读SD卡(fatfs/usb调用)
* 函 数 形 参:pbuf = 数据缓存区;saddr = 扇区地址;cnt = 扇区个数
* 函 数 返 回:读取结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_read_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt)
{
uint8_t res;
long long lsaddr = saddr;
if (sd_type != SD_TYPE_V2HC)
{
lsaddr <<= 9; /* 转换为字节地址 */
}
if (cnt == 1)
{
res = sd_send_cmd(CMD17, lsaddr); /* 读命令 */
if (res == 0) /* 指令发送成功 */
{
res = sd_receive_data(pbuf, 512); /* 接收512个字节 */
}
}
else
{
res = sd_send_cmd(CMD18, lsaddr); /* 连续读命令 */
do
{
res = sd_receive_data(pbuf, 512); /* 接收512个字节 */
pbuf += 512;
} while (--cnt && res == 0);
sd_send_cmd(CMD12, 0); /* 发送停止命令 */
}
sd_deselect(); /* 取消片选 */
return res;
}
/******************************************************************
* 函 数 名 称:sd_write_disk
* 函 数 说 明:写SD卡(fatfs/usb调用)
* 函 数 形 参:pbuf = 数据缓存区;saddr = 扇区地址;cnt = 扇区个数
* 函 数 返 回:写入结果:SD_OK = 成功;SD_ERROR = 失败
* 作 者:yagi(八木)
* 备 注:无
******************************************************************/
uint8_t sd_write_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt)
{
uint8_t retry = 20; /*发送ACMD经常失败, 多尝试几次 */
uint8_t res;
long long lsaddr = saddr;
if (sd_type != SD_TYPE_V2HC)
{
lsaddr <<= 9; /* 转换为字节地址 */
}
if (cnt == 1)
{
res = sd_send_cmd(CMD24, lsaddr); /* 读命令 */
if (res == 0) /* 指令发送成功 */
{
res = sd_send_block(pbuf, 0xFE);/* 写512个字节 */
}
}
else
{
if (sd_type != SD_TYPE_MMC)
{
do
{
sd_send_cmd(ACMD23, cnt); /* 发送 ACMD23 指令 */
}while(res && retry--);
}
res = sd_send_cmd(CMD25, lsaddr); /* 连续读命令 */
if (res == 0)
{
do
{
res = sd_send_block(pbuf, 0xFC); /* 写入一个block, 512个字节 */
pbuf += 512;
} while (--cnt && res == 0);
res = sd_send_block(0, 0xFD); /* 写入一个block, 512个字节 */
}
}
sd_deselect();/* 取消片选 */
return res;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
4.17.5 移植验证
梁山派开发板通过串口连接到上位机的串口通信助手。
上位机串口通信助手显示的 SD 卡初始化相关信息。
说明:因为 SPI 接口及 SD 卡模块通信时经常会因为某些原因导致错误,可能需要通过更换多张 SD 卡及多次尝试。
完整的示例代码见下方文件 。
通过网盘分享的文件:立创·梁山派GD32F470ZGT6开发板【模块移植手册代码】
链接: https://pan.baidu.com/s/1pp44yjD1Dhh7U9iZ2a11IA 提取码: LCKF