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。也许有人会认为,这不就是个高速的I^2^C么。其实不是的。会看到,每一帧都没有ACK和NACK。所以不能替代I^2^C。

四线模式下的波形。红线是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内存卡的驱动。

相关推荐
u0101526581 小时前
STM32F103C8T6学习笔记2--LED流水灯与蜂鸣器
笔记·stm32·学习
王俊山IT2 小时前
C++学习笔记----10、模块、头文件及各种主题(二)---- 预处理指令
开发语言·c++·笔记·学习
慕卿扬2 小时前
基于python的机器学习(二)—— 使用Scikit-learn库
笔记·python·学习·机器学习·scikit-learn
WZF-Sang2 小时前
Linux—进程学习-01
linux·服务器·数据库·学习·操作系统·vim·进程
今天我又学废了3 小时前
scala学习记录,Set,Map
开发语言·学习·scala
Diamond技术流3 小时前
从0开始学习Linux——远程连接工具
linux·学习·centos·ssh·xshell·ftp
LUwantAC3 小时前
Java学习路线:Maven(三)继承关系
java·学习·maven
王俊山IT4 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(1)
开发语言·c++·笔记·学习
w微信150135078124 小时前
小华一级 代理商 HC32F005C6PA-TSSOP20 HC32F005系列
c语言·arm开发·单片机·嵌入式硬件
憧憬一下5 小时前
Pinctrl子系统中Pincontroller和client驱动程序的编写
arm开发·嵌入式·c/c++·linux驱动开发