KEIL 5.38 ARM-CM3/4 ARM汇编设计学习笔记9 – STM32 SDIO学习入门

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记9 – STM32的SDIO学习1

  • 一、概述和回顾
  • 1.1、 一点体会
  • 1.1.1 文档比较复杂
  • 1.1.2 指令有时序和时效的要求
  • 1.2 一些基础但是要强调的内容
  • 1.2.1 指令和返回值
  • 1.2.2 启动时序
  • 1.2.3 关于ACMD41的注意事项
  • 1.2.4 MCU的一点问题
  • 1.2.5 SDIO Memory Card的一点问题
  • 二、目的与目标
  • 三、 实现
  • 3.1 实现KEIL RTX下的一个主线程和测试用例线程
  • 3.1.1 创建主函数
  • 3.1.2 创建测试用例
  • 3.2 定义C接口
  • 3.3 实现接口
  • 四、调试
  • 4.1 发送CMD0
  • 4.2 发送CMD8
  • 4.3 发送ACMD41
  • 4.4 广播CMD2获得CID
  • 4.5 发送CMD3设置RCA
  • 五、 结论
  • 六、参考文献
  • 一、概述和回顾

    相比笔者目前接触过的接口,包括UART、SPI、I2C(硬件)、CAN、FSMC,我感觉这些总线中只有CAN好像可以真正意义上一行代码都不写,直接进DEBUG里对相应的寄存器一通捣鼓就可以成功看到数据帧或扩展帧的收发。其他的总线多多少少会因为有一些时效性的要求,就算是时序对了,不能满足时效的话就会发生异常。所以虽然CAN的文档也不少,但是学习曲线其实就还好。但是对于这种有时效性要求的接口,如果文档还比较多,文档结构还不太对你的阅读思路习惯,那就可能要辛苦一些了。

    关于学习SDIO、ETH和USB在Cortex-M3/4上的开发,一般都是劝退的。网上总有人说,STM32的SDIO比较复杂,或者就是SDIO本身就比较复杂,驱动肯定做不了,要用第三方的。或者你买了书或者看了视频,到了学这个的时候了,人家来一句“这个东西比较复杂,就不展开讲了”,然后给你贴出一堆巨大的C语言源文件,或者就不贴叫你自己去看工程文件,确保你其实是看不懂的或者是也不会去看的。但是!笔者就是要做一个自己的驱动出来。

    1.1、 一点体会

    所有的能存在到今天的广泛应用的接口,一定是因为这个接口的门槛还是没有那么高的。否则就早被淘汰了。我十分看好SDIO的应用。主要有两点:

    1. 比着其他的基本的通讯总线的设备,SDIO设备的性能参数一般都比较强。比如串口wifi,SPI的W25Q128,SDIO WIFI和SDIO Memory(学过协议便知,SDIO Card指的往往是SDIO I/O Card)。
    2. 比着那些性能比较好的,它引脚有比较少。比如NAND。虽然FSMC可以挂NAND,但是对比一下,笔者感觉还是SDIO内存卡比较简单。

    这个接口的学习曲线相比CAN来讲,其实我认为稍微陡一点。关于协议的介绍和寄存器的介绍网上有很多很多了,我就不赘述了。主要有以下的难点。

    1.1.1 文档比较复杂

    主要的文档一共有两3个,分别是

    1. 《RM0090 Reference manual – STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs》 这里注意一个问题,ST的网站有的时候打不开可能是因为你的浏览器不被支持。可以试下Windows Edge。这个笔者试过可以打开。
    2. 《Physical Layer Simplified Specification》 和下面的3一样,都在 https://www.sdcard.org/downloads/pls/ 这个网站下载。打开有点卡。其实就内容而言,我认为这个文档会更全面一点。
    3. 《SDIO Simplified Specification》 也是讲SDIO这个接口的,文档长度短一些,但是不如上面那个全面和准确。

    1.1.2 指令有时序和时效的要求

    有的指令有这样的要求。有的要求前后要有其他的语句铺垫,那么如果手册上没有注意到相关的内容就可能走不通;有的要求在指定的时间内完成,那么就不能在DEBUG里像CAN那样鼓捣出来。

    1.2 一些基础但是要强调的内容

    1.2.1 指令和返回值

    指令有两种:Standard Command (CMD)和Application Specific Command (ACMD) 。在发ACMD之前要先发CMD55。具体的文字来源如下

    Note that ACMD41 is application specific command; therefore APP_CMD (CMD55) shall always precede ACMD41. The RCA to be used for CMD55 in idle_state shall be the card’s default RCA =0x0000.
    —— *《Physical Layer Simplified Specification》*第41(67)页倒数第二段最后一句

    所有的指令都在《Physical Layer Simplified Specification》 第102(128)页(4.7.4 Detailed Command Description)中有列表进行详细说明。

    返回值一共有7种,但是我们这里只关心4中,分别是R1、R2、R3、R6和R7具体内容在 《Physical Layer Simplified Specification》 第117(149)页(4.9 Responses)讲到。网上也有很多人罗列,这里不在赘述。只是提一下,只有R2是Long Response,其他的都是Short Response。

    1.2.2 启动时序

    下面的图是在 《Physical Layer Simplified Specification》的第42页(全局第68页)说明的上电初始化的指令顺序。要严格执行这个启动初始化的图,否则大概率会失败。这个并不是领会精神就可以的。为了方便理解,大概讲一下要做的事情的:

    1. COM0全部复位
    2. COM8检查电平
    3. 对于SDIO Memory Card有ACMD41先不挂参数广播查询OCR,再挂参数设置并启动初始化。
    4. 如果切电平,就先发CMD11,再控制硬件或固件切电平
    5. 发CMD2让卡发送CID值。
    6. 发CMD3让卡发送新的RCA值
    7. 启动初始化结束

    1.2.3 关于ACMD41的注意事项

    首先,作为ACMD,发送前要先发CMD55
    其次,ACMD41广播查询之后必须在1秒内进行ACMD41设置,并且在这1秒内要持续重复发送。仔细读一下 《Physical Layer Simplified Specification》第43(69)页的前2段。然而这里有个歧义。就是在第45(71)页有关于AMD41的说明。英文术语‘first ACMD41’其实指的是第一次发送设置。但是经测试我认为是第一次发送ACMD41查询和第一次发送ACMD41设置之间的时间间隔超过1秒或者按照手册解读,都会出现不稳定的情况。所以就都快点就好。

    这里是引用
    《Physical Layer Simplified Specification》第43(69)页的前2段


    第45(71)页有关于AMD41的说明

    最后,发送了CMD8以后就必须紧接着发ACMD41查询。总之就是严格执行那个流程图就对了。木的感情,木的节外生枝。

    1.2.4 MCU的一点问题

    发指令前要清STA,尤其是那些带FAIL的。 笔者尚不清楚哪些FAIL会影响后面的发送,但是至少CRCFAIL是不行的。STM32在指令响应这边的描述是 《RM0090 Reference manual – STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs》 第1026页的状态机图描述。可以看到只要是CRCFail或者是Response received都可以从Receive状态回到Idle状态。笔者猜测这是因为外设上不对Response进行检查。由于所有的Response要么是Response received,要么是CRC Failed。R3是不进行CRC校验的,CRC域是2_1111111,无法通过CRC校验。所以才这样设计。

    时钟配置 SDIO Adapter有关的一共有3路时钟,分别是PCLK2、SDIOCLK和SDIO_CLK。 这里SDIOCLK的来源是主锁相环的第2路输出(如第7章所示如下)。指的是输入到SDIO adapter的时钟。

    Name Source Usage
    PCLK2 APB2 The adapter registers and FIFO
    SDIOCLK main PLL The control unit, command path and data path
    SDIO_CLK SDIOCLK分频或直连 The clock to the card

    这里是引用《RM0090 Reference manual – STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs》 第219页有关PLL Configuration的阐述

    《RM0090 Reference manual – STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs》 第1022页有关SDIO的时钟的阐述

    但是在配置SDIO的外设时钟的时候,会发现只有APB2上的一个时钟。初始化外设时钟的顺序就是先先使能APB2上的SDIO,再去SDIO的寄存器里使能SDIO_CLK。

    发送指令 只要启动了CPSM,不断往SDIO_CMD里面写指令就可以了,当然SDIO_CMD_CPSMEN要保持是1。这里建议写之前吧SDIO_STA清一清。不然会出幺蛾子。比如前面来了个R3导致CRCFAIL,你不清后面这个口就不好用了。

    1.2.5 SDIO Memory Card的一点问题

    测试中难免会一遍一遍的复位并重跑启动程序。但是当程序跑ACMD41出现问题的时候想复位MCU重来的时候,必须把内存卡弹出再重新插入。否则会出现响应超时且不响应。

    说了这么多,可以看出,笔者还是做了一点作业才能开展这个工作的。那么下面就制定一下本期的目标。并验证。

    二、目的与目标

    目的:实现发送CMD3完成初始化。
    目标:

    1. 初始化时钟
    2. 初始化相关引脚
    3. 初始化SDIO外设
    4. 发送指令初始化存储卡

    三、 实现

    实现这个固件一共需要做下面的三件事:

    1. 实现KEIL RTX下的一个主线程和测试用例线程
    2. 定义C接口
    3. 实现接口

    那笔者就依次叙述。由于这种SDIO接口的卡也被叫做Combo Card,所以我在程序里用的是Combo Card命名。当然,如果是我后面发现理解文档有误再进行修改。

    3.1 实现KEIL RTX下的一个主线程和测试用例线程

    3.1.1 创建主函数

    首先用系统模板创建一个主函数。在主函数中初始化硬件(所有外设的时钟和引脚功能定义)。之所以我倾向在主函数中初始化硬件,是因为我们在硬件初始化过程中一不小心可能会用到一些需要在特权模式才方便操作的指令。而在main()函数中系统是处于特权模式下的。所以我习惯将所有的初始化都放在这里。

    /*----------------------------------------------------------------------------
     * CMSIS-RTOS 'main' function template
     *---------------------------------------------------------------------------*/
     
    #include "RTE_Components.h"
    #include  CMSIS_device_header
    #include "cmsis_os2.h"
    #include "hardware_setup.h"
    #include "SDIO_TestCase.h"
    /*----------------------------------------------------------------------------
     * Application main thread
     *---------------------------------------------------------------------------*/
    __NO_RETURN static void app_main (void *argument) {
      (void)argument;
      // ...
    	Init_SDIO_Testcase();
      for (;;) {
    		osDelay(100);
    	}
    }
     
    int main (void) {
     
      // System Initialization
      SystemCoreClockUpdate();
      hardware_setup();
    	
      // ...
     
      osKernelInitialize();                 // Initialize CMSIS-RTOS
      osThreadNew(app_main, NULL, NULL);    // Create application main thread
      osKernelStart();                      // Start thread execution
      for (;;) {}
    }
    
    

    3.1.2 创建测试用例

    利用模板创建一个线程,起名叫SDIO_Testcase,文件名叫SDIO_Testcase.c和SDIO_Testcase.h。都是模板套路。毕竟这个程序的主要目的是为了测试接口和SD内存卡,所以没有在线程设计上花费太多设计。就是严格执行那个流程图,没有什么好说的。

    #ifndef _SDIO_TESTCASE_H_
    #define _SDIO_TESTCASE_H_
    
    int Init_SDIO_Testcase (void);
    
    #endif
    
    

    目前是为了测试一下手册上说的启动方法。所以只是在流程上用C语言调用指令一个个走下去。

    #include "cmsis_os2.h"                          // CMSIS RTOS header file
    #include "SDIO_TestCase.h"
    #include "SDIO_Combo_Card.h"
    /*----------------------------------------------------------------------------
     *      Thread 1 'SDIO_Testcase': 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;
    	(void)argument;
    	SDIO_Combo_Card.send_cmd(0, 0, waitRsp_noRsp);
    	SDIO_Combo_Card.send_cmd(8, 1<<8, waitRsp_shortRsp);
    	SDIO_Combo_Card.send_cmd(55, 0, waitRsp_shortRsp);
    	resp = SDIO_Combo_Card.send_cmd(41, 0, waitRsp_shortRsp);
    	do{
    		SDIO_Combo_Card.send_cmd(55, 0, waitRsp_shortRsp);
    		resp = SDIO_Combo_Card.send_cmd(41, 0xff0000, waitRsp_shortRsp);
    		if(resp & (0x80000000)){
    			break;
    		}
    		
    	}while(1);
    	SDIO_Combo_Card.send_cmd(2,0, waitRsp_shortRsp);
    	resp = SDIO_Combo_Card.send_cmd(3,0, waitRsp_shortRsp);
      while (1) {
        osDelay(101);
      }
    }
    
    

    注意那个do{...}while(1)循环。就是用这个写法实现了循环发41号设置指令直到设置完成。0x80000000指的是R3返回值的busy位,也就是最高位。为什么不写1<<31的原因是,写了以后老是警告说我在将符号数强转成符号数。C就是有这个问题。动不动给你来个大惊小怪。等我后面全给你弄汇编里,都给我安静!

    3.2 定义C接口

    创建SDIO_Combo_Card.h,源文件如下所示。

    #ifndef _SDIO_COMBO_CARD_H_
    #define _SDIO_COMBO_CARD_H_
    
    #include "stdint.h"
    typedef enum {
    	waitRsp_noRsp    = 0,
    	waitRsp_shortRsp = 1,
    	waitRsp_longRsp  = 3,
    }WaitRspKind;
    
    typedef struct {
    	void (*init)(void);
    	uint32_t (*send_cmd)(uint32_t cmdIndex, uint32_t arg, WaitRspKind waitRsp);
    }SDIO_Combo_Card_Def;
    
    extern const SDIO_Combo_Card_Def SDIO_Combo_Card;
    #endif
    
    

    3.3 实现接口

    把之前的CAN相关的符号定义给删除掉,添加上SDIO相关的符号定义,就有了下面这个外设符号定义清单。各位地主请过目。

    	if :def: _PERIPHERALS_H_
    	else
    		gbla _PERIPHERALS_H_
    RCC_BaseAddr 	equ		0x40023800	
    
    RCC_CR			equ		0x00
    RCC_PLLCFGR		equ		0x04
    RCC_CFGR		equ		0x08
    RCC_CIR			equ		0x0C
    RCC_APB2RSTR	equ  	0x24
    RCC_AHB1ENR		equ		0x30
    RCC_AHB2ENR		equ		0x34
    RCC_AHB3ENR		equ		0x38
    RCC_APB1ENR		equ		0x40
    RCC_APB2ENR		equ		0x44
    
    RCC_CR_HSEON		equ		1:shl:16
    RCC_CR_PLLON		equ		1:shl:24	
    RCC_PLLCFGR_PLLSRC	equ		1:shl:22
    RCC_PLLCFGR_PLLP_DIV_2		equ	 	2_00:shl:16
    RCC_PLLCFGR_PLLP_DIV_4		equ	 	2_01:shl:16
    RCC_PLLCFGR_PLLP_DIV_6		equ	 	2_10:shl:16
    RCC_PLLCFGR_PLLP_DIV_8		equ	 	2_11:shl:16
    
    RCC_CFGR_PPRE2_APB2_DIV_2	equ		2_100:shl:13
    RCC_CFGR_PPRE2_APB2_DIV_4	equ		2_101:shl:13	
    RCC_CFGR_PPRE2_APB2_DIV_8	equ		2_110:shl:13		
    RCC_CFGR_PPRE2_APB2_DIV_16	equ		2_111:shl:13			
    RCC_CFGR_PPRE1_APB1_DIV_2	equ		2_100:shl:10
    RCC_CFGR_PPRE1_APB1_DIV_4	equ		2_101:shl:10	
    RCC_CFGR_PPRE1_APB1_DIV_8	equ		2_110:shl:10		
    RCC_CFGR_PPRE1_APB1_DIV_16	equ		2_111:shl:10
    RCC_CFGR_SW_HSE		equ		2_01
    RCC_CFGR_SW_PLL		equ		2_10
    RCC_AHB1ENR_GPIOBEN	equ		1:shl:1
    RCC_AHB1ENR_GPIOCEN	equ		1:shl:2
    RCC_AHB1ENR_GPIODEN	equ		1:shl:3
    RCC_APB1ENR_CAN1EN	equ		1:shl:25
    RCC_APB2ENR_SDIOEN	equ		1:shl:11
    
    
    FLASH_BaseAddr	equ		0x40023C00 
    FLASH_ACR		equ		0x00	
    FLASH_ACR_DCEN		equ		1:shl:10
    FLASH_ACR_ICEN		equ		1:shl:9
    FLASH_ACR_PRFTEN	equ		1:shl:8
    	
    GPIOB_BaseAddr	equ		0x40020400
    GPIOC_BaseAddr	equ		0x40020800
    GPIOD_BaseAddr	equ		0x40020C00	
    GPIOx_MODER		equ		0x00
    GPIOx_OTYPER	equ		0x04
    GPIOx_OSPEEDR	equ		0x08
    GPIOx_PUPDR		equ		0x0C	
    GPIOx_IDR		equ		0x10
    GPIOx_ODR		equ		0x14
    GPIOx_BSRR		equ		0x18	
    GPIOx_LCKR		equ		0x1C
    GPIOx_AFRL		equ		0x20
    GPIOx_AFRH		equ		0x24	
    
    GPIOx_MODER_INPUT			equ		2_00
    GPIOx_MODER_OUTPUT			equ		2_01
    GPIOx_MODER_AFIO			equ		2_10
    GPIOx_MODER_ANALOG			equ		2_11
    GPIOx_OTYPER_PP				equ		0	
    GPIOx_OTYPER_OD				equ		1	
    GPIOx_OSPEEDR_LOW			equ		2_00
    GPIOx_OSPEEDR_MEDIUM		equ		2_01
    GPIOx_OSPEEDR_HIGH			equ		2_10
    GPIOx_OSPEEDR_VERYHIGH		equ		2_11
    GPIOx_PUPDR_NONE			equ		2_00
    GPIOx_PUPDR_PU				equ		2_01
    GPIOx_PUPDR_PD				equ		2_10
    
    SDIO_BaseAddr	equ		0x40012C00
    SDIO_POWER		equ		0x00
    SDIO_CLKCR		equ		0x04	
    SDIO_ARG		equ		0x08
    SDIO_CMD		equ		0x0C	
    SDIO_RESPCMD	equ		0x10
    SDIO_RESP1		equ		0x10 + 1 * 4
    SDIO_RESP2		equ		0x10 + 2 * 4
    SDIO_RESP3		equ		0x10 + 3 * 4
    SDIO_RESP4		equ		0x10 + 4 * 4	
    SDIO_DTIMER		equ		0x24
    SDIO_DLEN		equ		0x28
    SDIO_DCTRL 		equ		0x2C
    SDIO_DCOUNT		equ		0x30
    SDIO_STA		equ		0x34
    SDIO_ICR		equ		0x38	
    SDIO_MASK		equ		0x3C
    SDIO_FIFOCNT	equ		0x48
    SDIO_FIFO		equ		0x80	
    
    
    SDIO_POWER_PWRCTRL_POWEROFF	equ		2_00	
    SDIO_POWER_PWRCTRL_POWERON	equ		2_11
    SDIO_CLKCR_WIDBUS_1WIDE		equ		2_00:shl:11
    SDIO_CLKCR_WIDBUS_4WIDE		equ		2_01:shl:11
    SDIO_CLKCR_WIDBUS_8WIDE		equ		2_10:shl:11
    SDIO_CLKCR_CLKEN			equ		1:shl:8
    SDIO_CMD_ENCMDcompl			equ		1:shl:12
    SDIO_CMD_CPSMEN				equ		1:shl:10
    SDIO_CMD_WAITRESP_Bits		equ		2_11:shl:6
    SDIO_CMD_WAITRESP_Short 	equ		2_01:shl:6
    SDIO_CMD_WAITRESP_Long  	equ		2_11:shl:6
    SDIO_CMD_CMDINDEX_Bits		equ		2_111111:shl:0		
    SDIO_STA_CMDREND 			equ		1:shl:6
    SDIO_STA_DCRCFAIL			equ		1:shl:0
    	
    	
    			endif
    			end
    

    在这个驱动源文件中,在init()函数中初始化相应的时钟和GPIO,这个玩意目前看来也没有很严格的波特率这一说。就是把SDIO_POWER和SDIO_CLK的打开就可以了。看着长度吓人,其实没有多少。

    	get peripherals.s
    	
    rRCC   rn r8
    rSDIO  rn r9
    rGPIOC rn r10
    rGPIOD rn r11
    
    SDIO_GPIO_SPEED	equ	GPIOx_OSPEEDR_VERYHIGH
    	area text, code
    	align 4
    init proc
    	 push {r4 - r11, lr}
    	 ldr  r8, =RCC_BaseAddr
    	 ldr  r0, [rRCC, #RCC_APB2ENR]
    	 orr  r0, #RCC_APB2ENR_SDIOEN
    	 str  r0, [rRCC, #RCC_APB2ENR]
    	 ldr  r0, [rRCC, #RCC_AHB1ENR]
    	 orr  r0, #RCC_AHB1ENR_GPIOCEN :or: RCC_AHB1ENR_GPIODEN
    	 str  r0, [rRCC, #RCC_AHB1ENR]
    	 ; CLK  :  PC12
    	 ; CMD  :  PD2
    	 ; DAT_0:  PC8
    	 ; DAT_1:  PC9
    	 ; DAT_2:  PC10
    	 ; DAT_3:  PC11
    	 ;
    	 ldr  rGPIOC, =GPIOC_BaseAddr
    	 ldr  r0, [rGPIOC, #GPIOx_MODER]
    GPIOC_MODER_BITs equ (2_11:shl:(12 * 2)) :or: \
    					 (2_11:shl:(11 * 2)) :or: \
    					 (2_11:shl:(10 * 2)) :or: \
    					 (2_11:shl:(9  * 2)) :or: \
    					 (2_11:shl:(8  * 2)) 
    GPIOC_MODER_VAL	 equ (GPIOx_MODER_AFIO:shl:(12 * 2)) :or: \
    					 (GPIOx_MODER_AFIO:shl:(11 * 2)) :or: \
    					 (GPIOx_MODER_AFIO:shl:(10 * 2)) :or: \
    					 (GPIOx_MODER_AFIO:shl:(9  * 2)) :or: \
    					 (GPIOx_MODER_AFIO:shl:(8  * 2)) 					
    	 ldr  r1, =GPIOC_MODER_BITs
    	 bic  r0, r1
    	 ldr  r1, =GPIOC_MODER_VAL
    	 orr  r0, r1
    	 str  r0, [rGPIOC, #GPIOx_MODER]	 
    	 ldr  r0, [rGPIOC, #GPIOx_OTYPER]
    GPIOC_OTYPER_BITs	 equ	2_11111:shl:12
    	 bic  r0, #GPIOC_OTYPER_BITs
    	 str  r0, [rGPIOC, #GPIOx_OTYPER]
    	 
    	 ldr  r0, [rGPIOC, #GPIOx_OSPEEDR]	 
    GPIOC_OSPEEDR_BITs  equ (2_11:shl:(12 * 2)) :or: \
    					    (2_11:shl:(11 * 2)) :or: \
    					    (2_11:shl:(10 * 2)) :or: \
    					    (2_11:shl:(9  * 2)) :or: \
    					    (2_11:shl:(8  * 2)) 
    GPIOC_OSPEEDR_VAL	equ (SDIO_GPIO_SPEED:shl:(12 * 2)) :or: \
    					    (SDIO_GPIO_SPEED:shl:(11 * 2)) :or: \
    					    (SDIO_GPIO_SPEED:shl:(10 * 2)) :or: \
    					    (SDIO_GPIO_SPEED:shl:(9  * 2)) :or: \
    					    (SDIO_GPIO_SPEED:shl:(8  * 2)) 	 
    	 ldr  r1, =GPIOC_OSPEEDR_BITs
    	 bic  r0, r1
    	 ldr  r1, =GPIOC_OSPEEDR_VAL
    	 orr  r0, r1
    	 str  r0, [rGPIOC, #GPIOx_OSPEEDR]
    	 
    	 ldr  r0, [rGPIOC, #GPIOx_PUPDR]
    GPIOC_PUPDR_BITs  equ   (2_11:shl:(12 * 2)) :or: \
    					    (2_11:shl:(11 * 2)) :or: \
    					    (2_11:shl:(10 * 2)) :or: \
    					    (2_11:shl:(9  * 2)) :or: \
    					    (2_11:shl:(8  * 2)) 
    GPIOC_PUPDR_VAL	equ     (GPIOx_PUPDR_PU:shl:(12 * 2)) :or: \
    					    (GPIOx_PUPDR_PU:shl:(11 * 2)) :or: \
    					    (GPIOx_PUPDR_PU:shl:(10 * 2)) :or: \
    					    (GPIOx_PUPDR_PU:shl:(9  * 2)) :or: \
    					    (GPIOx_PUPDR_PU:shl:(8  * 2))	 
    	 ldr  r1, =GPIOC_PUPDR_BITs
    	 bic  r0, r1
    	 ldr  r1, =GPIOC_PUPDR_VAL
    	 orr  r0, r1
    	 str  r0, [rGPIOC, #GPIOx_PUPDR]
    GPIOC_AFIO_BITs		equ		(2_1111:shl:16) :or: \
    							(2_1111:shl:12) :or: \
    							(2_1111:shl:8 ) :or: \
    							(2_1111:shl:4 ) :or: \
    							(2_1111:shl:0 ) 
    GPIOC_AFIO_VAL		equ		(12:shl:16) :or: \
    							(12:shl:12) :or: \
    							(12:shl:8 ) :or: \
    							(12:shl:4 ) :or: \
    							(12:shl:0 ) 							
    	 ldr  r0, [rGPIOC, #GPIOx_AFRH]
    	 ldr  r1, =GPIOC_AFIO_BITs
    	 bic  r0, r1
    	 ldr  r1, =GPIOC_AFIO_VAL
    	 orr  r0, r1
    	 str  r0, [rGPIOC, #GPIOx_AFRH]
    	 
    	 ldr  rGPIOD, =GPIOD_BaseAddr
    	 ldr  r0, [rGPIOD, #GPIOx_MODER]
    	 bic  r0, #2_11 :shl:(2 * 2)
    	 orr  r0, #GPIOx_MODER_AFIO:shl:(2 * 2)
    	 str  r0, [rGPIOD, #GPIOx_MODER]
    	 ldr  r0, [rGPIOD, #GPIOx_OTYPER]
    	 bic  r0, #2_1 :shl: 2
    	 orr  r0, #GPIOx_OTYPER_PP:shl:2
    	 str  r0, [rGPIOD, #GPIOx_OTYPER]
    	 ldr  r0, [rGPIOD, #GPIOx_OSPEEDR]
    	 bic  r0, #2_11:shl:(2*2)
    	 orr  r0, #SDIO_GPIO_SPEED:shl:(2*2)
    	 str  r0, [rGPIOD, #GPIOx_OSPEEDR]
    	 ldr  r0, [rGPIOD, #GPIOx_PUPDR]
    	 bic  r0, #2_11:shl:(2 * 2)
    	 orr  r0, #GPIOx_PUPDR_PU:shl:(2 * 2)
    	 str  r0, [rGPIOD, #GPIOx_PUPDR]
    	 ldr  r0, [rGPIOD, #GPIOx_AFRL]
    	 bic  r0, #2_1111:shl:8
    	 orr  r0, #12:shl:8
    	 str  r0, [rGPIOD, #GPIOx_AFRL]
    	 
    	 ldr  rSDIO, =SDIO_BaseAddr
    	 mov  r0, #SDIO_POWER_PWRCTRL_POWERON
    	 str  r0, [rSDIO, #SDIO_POWER]
    	 ldr  r0, [rSDIO, #SDIO_CLKCR]
    	 bic  r0, #2_11:shl:11
    	 orr  r0, #SDIO_CLKCR_WIDBUS_4WIDE:or:SDIO_CLKCR_CLKEN
    	 str  r0, [rSDIO, #SDIO_CLKCR]
    	 pop  {r4 - r11, lr}
    	 bx   lr
    	 endp
    		 
    ; 	 void (*send_cmd)(uint32_t cmdIndex, uint32_t arg, uint32_t waitRsp);	 
    ;	 实现这个函数
    
    	 align 4		 
    send_cmd	proc
    	push {r4 - r11, lr}
    	ldr  rSDIO, =SDIO_BaseAddr	
    	mov  r4, #0x7ff
    	str  r4, [rSDIO, #SDIO_ICR]
    	cbnz r2, form_cmd_return
    form_cmd_no_return
    	orr  r4, r0, #SDIO_CMD_CPSMEN
    	b	 send_completed
    form_cmd_return	
    	orr  r4, r0, r2, lsl #6
    	orr	 r4, #SDIO_CMD_CPSMEN
    	str  r1, [rSDIO, #SDIO_ARG]
    	str  r4, [rSDIO, #SDIO_CMD]
    wait_for_response
    	ldr  r4, [rSDIO, #SDIO_STA]
    	tst  r4, #SDIO_STA_CMDREND :or: SDIO_STA_DCRCFAIL
    	beq  wait_for_response
    send_completed
    	ldr  r0, [rSDIO, #SDIO_RESP1]
    	pop  {r4 - r11, lr}	
    	bx   lr
    	endp
    	
    	align 4
    SDIO_Combo_Card	
    	 export SDIO_Combo_Card
    	 dcd  init, send_cmd
    	 end
    	
    

    在发送函数(一共就23行汇编。干这点活长吗?不长!)中,根据前面说的,先清一下STA。注意ICR和STA的位不是一一对应的。所以就直接给ICR来个0x7FF,简单高效。然后根据要不要RESPCMD做了个分支。如果是要的话,还在里面做了个忙等。最后把RESPCMD的参数塞进r0返回。

    四、调试

    插卡,上电并启动调试。每一步的运行的情况都在下面的调试窗口中有显示。返回值在右侧的寄存器里。

    4.1 发送CMD0

    将卡复位。

    4.2 发送CMD8

    卡回应0x100。查一下R7的意思,是说普通卡,电压2.7 – 3.6V。

    4.3 发送ACMD41

    对的,就是这步,当时卡住了很久。就是手册没有看好。要先发CMD55。卡对CMD55有回应,但是这里貌似是用不上的。
    在这里插入图片描述

    第一次发ACMD41是查询,第二次发就是设置了。

    4.4 广播CMD2获得CID

    话说CMD2应该是回应R2长响应,但是看RSP里面好像也不长。CRC

    4.5 发送CMD3设置RCA


    细心的你可能已经看出来了,左边的内核时间有点不太对。笔者这些图是在多次调试中截取的,所以时间不一定能对上。但是程序绝对没有问题。

    五、 结论

    根据对手册和SDIO协议的学习,我们可以简单快速的实现SDIO Memory Card的初始化了。后面要尝试进行读写操作。

    六、参考文献

    1. 《RM0090 Reference manual – STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs》
    2. 《Physical Layer Simplified Specification》
    3. 《SDIO Simplified Specification》

    作者:超级喵窝窝

    物联沃分享整理
    物联沃-IOTWORD物联网 » KEIL 5.38 ARM-CM3/4 ARM汇编设计学习笔记9 – STM32 SDIO学习入门

    发表评论