KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记13 - STM32的SDIO学习5 - 卡的轮询读写擦

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记13 - STM32的SDIO学习5 - 卡的轮询读写擦

  • 一、前情提要
  • 二、目标
  • 三、技术方案
    • [3.1 读写擦的操作](#3.1 读写擦的操作)
      • [3.1.1 读卡操作](#3.1.1 读卡操作)
      • [3.1.2 写卡操作](#3.1.2 写卡操作)
      • [3.1.3 擦除操作](#3.1.3 擦除操作)
    • [3.2 一些技术点](#3.2 一些技术点)
      • [3.2.1 轮询标志位的选择不唯一](#3.2.1 轮询标志位的选择不唯一)
      • [3.2.2 写和擦的卡状态查询](#3.2.2 写和擦的卡状态查询)
      • [3.2.3 写的速度](#3.2.3 写的速度)
  • 四、代码实现
    • [4.1 接口定义](#4.1 接口定义)
    • [4.2 `read_block`接口的实现](#4.2 read_block接口的实现)
    • [4.3 `write_block`接口的实现](#4.3 write_block接口的实现)
    • [4.4 `erase_block`接口的实现](#4.4 erase_block接口的实现)
    • [4.5 `send_cmd`内部函数](#4.5 send_cmd内部函数)
  • 五、测试与结论
    • [5.1 测试用例](#5.1 测试用例)
    • [5.2 运行结果](#5.2 运行结果)
    • [5.3 其他测试结果](#5.3 其他测试结果)
  • 六、总结

一、前情提要

在上一篇提到了要实现SDIO内存卡的读写擦,但是由于程序在调试的时候出现了一些bug,所以一直没有把这个坑填上。最近由于做了一些测试,把读写擦实现了。所以特此来把这个坑填上。

二、目标

  1. 实现读写擦的函数。
  2. 编写测试用例,将一个块擦除,写入内容,再读出来。

三、技术方案

3.1 读写擦的操作

读写擦的具体的指令其实很多帖子都有介绍,笔者就是参考的手册本身。但是具体的流程还是要说一下。

3.1.1 读卡操作

  1. 设置SDIO_DLEN和SDIO_DCTRL两个寄存器。在SDIO_DLEN设置块大小,一般是512字节;在SDIO_DCTRL里设置块大小是9(代表512)、数据传输方向是卡到MCU,暂不启动。必要的话CMD16设置卡的块大小。
  2. CMD7+RCA选中卡。
  3. 置位SDIO_DCTRL的Dten位,使能MCU的传输。
  4. CMD17+块地址要求卡发送数据
  5. 轮询SDIO_STA的SDIO_STA_RXFIFOHF位看看有没有数据到位;轮询SDIO_STA的SDIO_STA_DATAEND位检查卡是否已经发送完成。
  6. 轮询SDIO_STA的SDIO_STA_RXDAVL把管子里的数据都收拾出来。
  7. 通过复位SDIO_DCTRL的Dten位以关闭DPSM。
  8. 通过SDIO_ICR清了SDIO_STA。
  9. 收工

3.1.2 写卡操作

  1. 设置SDIO_DLEN和SDIO_DCTRL两个寄存器。在SDIO_DLEN设置块大小,一般是512字节;在SDIO_DCTRL里设置块大小是9(代表512)、数据传输方向是MCU到卡,暂不启动。必要的话CMD16设置卡的块大小。
  2. CMD7+RCA选中卡。
  3. 置位SDIO_DCTRL的Dten位,使能MCU的传输。
  4. CMD24+块地址通知卡MCU将要发送数据
  5. 轮询SDIO_STA的SDIO_STA_TXFIFOHE位看看是否管子里有空间发数据;轮询SDIO_STA的SDIO_STA_DATAEND位检查MCU是否已经发送完成。
  6. 轮询SDIO_STA的SDIO_STA_DBCKEND直至发送结束
  7. 通过复位SDIO_DCTRL的Dten位以关闭DPSM。
  8. CMD13轮询卡的状态。直至状态离开PROG状态。
  9. 收工

3.1.3 擦除操作

  1. CMD7+RCA选中卡。
  2. CMD32设置起始块、CMD33设置终止块和CMD38执行擦除。
  3. CMD13轮询卡的状态。直至离开PROG状态。
  4. 收工

3.2 一些技术点

3.2.1 轮询标志位的选择不唯一

由于没有采用DMA,所以读写的时候都必须手动轮询SDIO_STA的某些标志位来保证读写操作的顺利完成。但是参考手册会发现对于读写操作并没有规定操作规范。有的时候对于同一个目的可以有多个标志位可以使用。这里确实是有多个标志位可以采用。但是要通过测试去验证。

3.2.2 写和擦的卡状态查询

卡在执行写和擦的操作时候会处于PROG状态。这个时候必须要轮询至状态转移才可以保证操作的成功。

3.2.3 写的速度

按照卡的SDIO_STATUS,很多时候卡的速度都是24MBit/s或50MBit/s,但是在实际操作的时候会发现速度似乎只能开到6.7MBit/s左右,不论是单线模式还是4线模式都不行。看时钟信号发现是非常连续的,所以排除了发送数据的时候有延迟的原因。这个问题目前没有解决,如果未来找到了原因再记录。

从上面的这个图中可以看到,时钟信号SDIO_CLK(红色)其实是非常连贯的。但是频率只能达到6.8,也就是5+2分频。所以笔者认为其实换成DMA传输也不太可能改善这个。但是未来如果有机会试试再测试吧。

四、代码实现

4.1 接口定义

SDIO内存卡的接口定义如下所示:

c 复制代码
#ifndef _SDIO_Memory_CARD_H_
#define _SDIO_Memory_CARD_H_

#include "stdint.h"
typedef enum {
	waitRsp_noRsp     = 0,
	waitRsp_shortRsp = 1,
	waitRsp_longRsp  = 3,
}WaitRspKind;

typedef struct {
	void  (*init)(void);
	uint32_t (*card_identification)(void);
	uint32_t (*read_SD_status)(void);
	uint32_t (*erase_block)(uint32_t, uint32_t);
	uint32_t (*read_block)(uint32_t, uint32_t*);
	uint32_t (*write_block)(uint32_t, uint32_t*);
}SDIO_Memory_Card_Def;

extern const SDIO_Memory_Card_Def SDIO_Memory_Card;
#endif

添加了3个函数接口,分别是

c 复制代码
	uint32_t (*erase_block)(uint32_t, uint32_t);
	uint32_t (*read_block)(uint32_t, uint32_t*);
	uint32_t (*write_block)(uint32_t, uint32_t*);

这三个接口的实现如下所示。但是由于前面说了逻辑,具体的每行的解释这里就不说了。

4.2 read_block接口的实现

c 复制代码
; uint32_t read_block(uint32_t, uint32_t*);
; r0 is the address of the block,
; r1 is the read buffer.
     align  4
read_block	proc
	 push   {r4 - r11, lr}
	 ldr    rSDIO, =SDIO_BaseAddr
	 ldr	rCard_Info, =card_info_data
	 sub    sp, #4 * 2
	 ldrb	r4, card_isSDSC
	 lsl	r0, r4
	 str	r0, [sp, #0]
	 str	r1, [sp, #4]

; Select the Card.	 	 
	 mov	r0, #7:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, card_rca
	 bl		send_cmd
; Set the block size in case of SDSC.	 	 
	 mov	r0, #16:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 mov    r1, #512	 	 
	 bl     send_cmd
	 
	 ldr	r4, [sp, #0] ;  The address of the block in the SD Memory Card
	 ldr	r5, [sp, #4] ;  The address of the buffer	 
; Send CMD17 to inform the card to start a data sending.
	 mov    r0, #17:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 mov    r1, r4
	 bl     send_cmd 	
	 
	 mov	r0, #0
	 str	r0, [rSDIO, #SDIO_DCTRL]
	 mov 	r0, #512
	 str	r0, [rSDIO, #SDIO_DLEN]
	 mov	r0, #(9 :shl: 4):or:SDIO_DCTRL_DTDIR_Card2Controller \
					:or: SDIO_DCTRL_Dten
	 str	r0, [rSDIO, #SDIO_DCTRL] 
	 
reading_data_start	 
	 ldr	r0, [rSDIO, #SDIO_STA]	
	 tst	r0, #SDIO_STA_RXACT
	 beq	reading_data_start
reading_data
	 ldr	r0, [rSDIO, #SDIO_STA]	
	 tst	r0, #SDIO_STA_DATAEND
	 bne	reading_clear_the_FIFO
	 tst	r0, #SDIO_STA_RXFIFOHF
	 beq	reading_data
	 ldr	r1, [rSDIO, #SDIO_FIFO]
	 str	r1, [r5]
	 add	r5, #4
	 b		reading_data
reading_clear_the_FIFO
	 ldr	r0, [rSDIO, #SDIO_STA]
	 tst	r0, #SDIO_STA_RXDAVL
	 beq	reading_completed
	 ldr	r1, [rSDIO, #SDIO_FIFO]
	 str	r1, [r5]
	 add	r5, #4
	 b		reading_clear_the_FIFO
reading_completed	
	 mov	r0, #0
	 str	r0, [rSDIO, #SDIO_DCTRL]
	 mov	r0, #0x7ff
	 str	r0, [rSDIO, #SDIO_ICR]
	 mov	r0, #7:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 mov	r1, #0
	 bl		send_cmd
	 mov	r0, #13:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, card_rca
	 bl		send_cmd
	 ldr 	r0, [rSDIO, #SDIO_RESP1]
	 ubfx	r0, r0, #9, #4
	 add	sp, #4 * 2
	 pop    {r4 - r11, lr}
	 bx     lr
	 ltorg
     endp

4.3 write_block接口的实现

c 复制代码
; uint32_t write_block(uint32_t, uint32_t*);
	 align	4
write_block	proc
	 push   {r4 - r11, lr}	
	 ldr    rSDIO, =SDIO_BaseAddr
	 ldr	rCard_Info, =card_info_data
	 ldrb	r4, card_isSDSC
	 lsl	r0, r4
	 sub	sp, #4 * 2
	 str	r0, [sp, #0]
	 str	r1, [sp, #4]
	 
	 mov	r0, #7:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, card_rca
	 bl		send_cmd
	 	 
	 mov	r0, #16:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 mov    r1, #512	 	 
	 bl     send_cmd
	 
	 ldr	r4, [sp, #0] ;  The address of the block in the SD Memory Card
	 ldr	r5, [sp, #4] ;  The address of the buffer
	 mov	r6, #0;
	 
	 mov    r0, #24:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 mov    r1, r4
	 bl     send_cmd 
	 
	 mov	r0, #0
	 str	r0, [rSDIO, #SDIO_DCTRL]
	 mov 	r0, #512
	 str	r0, [rSDIO, #SDIO_DLEN]
	 mov	r0, #(9:shl:4):or:SDIO_DCTRL_Dten
	 str	r0, [rSDIO, #SDIO_DCTRL]
writing_data_start
	 ldr	r0, [rSDIO, #SDIO_STA]
	 tst	r0, #SDIO_STA_TXACT
	 beq	writing_data_start
	 mov	r1, #SDIO_STA_DBCKEND:or:SDIO_STA_DCRCFAIL
writing_data
	 ldr	r0, [rSDIO, #SDIO_STA]
	 tst	r0, r1
	 bne	writing_completed
	 tst	r0, #SDIO_STA_TXFIFOE
	 beq	writing_data
	 ldr	r0, [r5]
	 str	r0, [rSDIO, #SDIO_FIFO]
	 add	r5, #4
	 add	r6, #1
	 b		writing_data
writing_completed
	 mov	r0, #0
	 str	r0, [rSDIO, #SDIO_DCTRL]
;	 mov    r0, #12:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
;	 ldr    r1, card_rca	 	 
;	 bl     send_cmd
write_block_the_card_is_programming	 
	 mov    r0, #13:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr    r1, card_rca	 	 
	 bl     send_cmd
	 ldr	r0, [rSDIO, #SDIO_RESP1]
	 ubfx	r1, r0, #9, #4
	 cmp	r1, #7
	 beq	write_block_the_card_is_programming
	 
	 mov	r0, #0x7ff
	 str	r0, [rSDIO, #SDIO_ICR]
	 	 
	 mov    r0, #13:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr    r1, card_rca	 	 
	 bl     send_cmd
	 ldr	r0, [rSDIO, #SDIO_RESP1]
	 
	 add	sp, #4 * 2
	 pop    {r4 - r11, lr}
	 bx		lr
	 endp

4.4 erase_block接口的实现

c 复制代码
; uint32_t erase_block(uint32_t, uint32_t);		 
; r0: Start block
; r1: End block
     align  4
erase_block proc
	 push   {r4 - r11, lr}
	 ldr    rSDIO, =SDIO_BaseAddr
	 ldr	rCard_Info, =card_info_data
	 sub	sp, #4 * 2
	 ldrb	r4, card_isSDSC
	 lsl	r0, r4
	 lsl	r1, r4
	 str	r0, [sp, #0]; [sp, #0] is the start block 
	 str	r1, [sp, #4]; [sp, #4] is the end block
	 
	 mov	r0, #7:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, card_rca
	 bl		send_cmd 
	 mov	r0, #13:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, card_rca
	 bl		send_cmd
	 
	 mov	r0, #32:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, [sp, #0];
	 bl	 	send_cmd
	 mov	r0, #33:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, [sp, #4];
	 bl	 	send_cmd
	 mov	r0, #38:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 mov	r1, #0
	 bl	 	send_cmd

erase_block_the_card_is_programming
	 mov    r0, #13:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr    r1, card_rca	 	 
	 bl     send_cmd
	 ldr	r0, [rSDIO, #SDIO_RESP1]
	 ubfx	r1, r0, #9, #4
	 cmp	r1, #7
	 beq	erase_block_the_card_is_programming
	 
	 mov	r0, #7:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, card_rca
	 bl		send_cmd 
	 
	 mov	r0, #13:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN
	 ldr	r1, card_rca
	 bl		send_cmd
	 ldr 	r0, [rSDIO, #SDIO_RESP1]
	 ubfx	r0, r0, #9, #4
	 
	 add	sp, #4 * 2
	 pop    {r4 - r11, lr}
	 bx     lr
	 ltorg
     endp

4.5 send_cmd内部函数

虽然前面的几个帖子中也讲到了这个内部函数的实现。但是为了方便阅读,这里还是把它粘过来了。

c 复制代码
; 	 Priviate Function Name: Send_cmd
;	 Args:  r0 - cmd, r1 - arg
;	 实现这个函数

	 align  4		 
send_cmd	proc
	 push   {r4 - r11, lr}
	 ldr    rSDIO, =SDIO_BaseAddr
	 ldr    rCard_Info, =card_info_data
	 mov    r2, #0x7f
	 str    r2, [rSDIO, #SDIO_ICR]
	 str    r1, [rSDIO, #SDIO_ARG]
	 str    r0, [rSDIO, #SDIO_CMD]
wait_for_response
	 ldr    r1, [rSDIO, #SDIO_STA]
	 tst    r1, #SDIO_STA_CMDREND:or:SDIO_STA_CCRCFAIL:or:SDIO_STA_CTIMEOUT
	 beq    wait_for_response
	 bic	r0, #SDIO_CMD_CPSMEN
	 str    r0, [rSDIO, #SDIO_CMD]
	 pop    {r4 - r11, lr}	
	 bx     lr
	 endp

五、测试与结论

5.1 测试用例

c 复制代码
#include "cmsis_os2.h"                          // CMSIS RTOS header file
#include "SDIO_TestCase.h"
#include "SDIO_Memory_Card.h"
#include "stdio.h"
/*----------------------------------------------------------------------------
 *      Thread 1 'Thread_Name': Sample thread
 *---------------------------------------------------------------------------*/
 
static osThreadId_t tid_SDIO_Testcase;                        // thread id
 
void SDIO_Testcase (void *argument);                   // thread function
 
int Init_SDIO_Testcase (void) {
 
  tid_SDIO_Testcase = osThreadNew(SDIO_Testcase, NULL, NULL);
  if (tid_SDIO_Testcase == NULL) {
    return(-1);
  }
 
  return(0);
}
 
__NO_RETURN void SDIO_Testcase (void *argument) {
	static uint32_t resp = 0;
	static union{
		uint32_t buf32[512 / 4];
		char buf8[512];
	}rBuf, wBuf;
	(void)argument;
	sprintf(wBuf.buf8, "I love Miao! I love you, Da Miao");
	SDIO_Memory_Card.card_identification();
	SDIO_Memory_Card.read_SD_status();
	
//	SDIO_Memory_Card.erase_block(0,0);
	SDIO_Memory_Card.read_block(0, rBuf.buf32);
	
	SDIO_Memory_Card.erase_block(10,15);
	SDIO_Memory_Card.read_block(10, rBuf.buf32);
	SDIO_Memory_Card.write_block(11, wBuf.buf32);
	SDIO_Memory_Card.read_block(11, rBuf.buf32);
	
  while (1) {
		resp = (resp + 1)%100;
   		osDelay(101);
  }
}

5.2 运行结果

可以看到,笔者成功地向第11块中写入了一行英文,"I love you, Da Miao, Kitty and Andy!"。

5.3 其他测试结果

对于这种SDIO内存卡,用单线还是四线数据总线模式其实都是成立的。只要MCU和卡的设置都协调好就可以了。

我把程序换到了另一个pin都引出来的板子上,把波形打出来。

单线模式下的波形。红线是SDIO_CLK,黄线是D0。也许有人会认为,这不就是个高速的I2C么。其实不是的。会看到,每一帧都没有ACK和NACK。所以不能替代I2C。

四线模式下的波形。红线是SDIO_CLK,其他是D0、D1和D2。由于这个示波器是借的,没有配数字探头,所以看不全所有的信号。

但是从调试上可以看到都能实现操作。

这里注明一下,因为HX32F4的开发板没有将SDIO有关的引脚引出,所以无法测量。我用另一个板子做了测试,但是用的IDE是Segger Embedded Studio做的,也就是上面的这个界面。

六、总结

这样,用轮询的方式实现读写擦SD卡的驱动就实现了。有若干的技术点:

  1. 读的速率可以根据卡的额定速度来,但是写入的速度不能高于6.8MBit/s。
  2. 写入和擦除之后要轮询卡的状态,确认卡已经从PROG状态中转出。
  3. 读写擦的轮询的标志位其实可以有多个选择。但是要测试确认。
  4. 管子的数据是32位的,但是发送的是按照8位的。所以可以认为是一次发4个字节。读写缓冲区都必须是字对齐(4字节对齐)。
  5. 要查询SDIO_STATUS,根据版本号确认如何读写。这里还是点一下,2GB以下的卡的寻址是以字节的,而以上的都是以块寻址的。当然,读写的时候都要以块为单位。比如,1GB的卡以字节寻址到第10块,但是还是要一次读1个整块。除非中途用CMD12打断。

这样就可以实现一套SDIO内存卡的驱动。

相关推荐
西岸行者9 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
我在人间贩卖青春9 天前
汇编之伪指令
汇编·伪指令
悠哉悠哉愿意9 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码9 天前
嵌入式学习路线
学习
毛小茛9 天前
计算机系统概论——校验码
学习
babe小鑫9 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms10 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下10 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。10 天前
2026.2.25监控学习
学习
im_AMBER10 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode