赞
踩
相比笔者目前接触过的接口,包括UART、SPI、I2C(硬件)、CAN、FSMC,我感觉这些总线中只有CAN好像可以真正意义上一行代码都不写,直接进DEBUG里对相应的寄存器一通捣鼓就可以成功看到数据帧或扩展帧的收发。其他的总线多多少少会因为有一些时效性的要求,就算是时序对了,不能满足时效的话就会发生异常。所以虽然CAN的文档也不少,但是学习曲线其实就还好。但是对于这种有时效性要求的接口,如果文档还比较多,文档结构还不太对你的阅读思路习惯,那就可能要辛苦一些了。
关于学习SDIO、ETH和USB在Cortex-M3/4上的开发,一般都是劝退的。网上总有人说,STM32的SDIO比较复杂,或者就是SDIO本身就比较复杂,驱动肯定做不了,要用第三方的。或者你买了书或者看了视频,到了学这个的时候了,人家来一句“这个东西比较复杂,就不展开讲了”,然后给你贴出一堆巨大的C语言源文件,或者就不贴叫你自己去看工程文件,确保你其实是看不懂的或者是也不会去看的。但是!笔者就是要做一个自己的驱动出来。
所有的能存在到今天的广泛应用的接口,一定是因为这个接口的门槛还是没有那么高的。否则就早被淘汰了。我十分看好SDIO的应用。主要有两点:
这个接口的学习曲线相比CAN来讲,其实我认为稍微陡一点。关于协议的介绍和寄存器的介绍网上有很多很多了,我就不赘述了。主要有以下的难点。
主要的文档一共有两3个,分别是
有的指令有这样的要求。有的要求前后要有其他的语句铺垫,那么如果手册上没有注意到相关的内容就可能走不通;有的要求在指定的时间内完成,那么就不能在DEBUG里像CAN那样鼓捣出来。
指令有两种: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。
下面的图是在 《Physical Layer Simplified Specification》的第42页(全局第68页)说明的上电初始化的指令顺序。要严格执行这个启动初始化的图,否则大概率会失败。这个并不是领会精神就可以的。为了方便理解,大概讲一下要做的事情的:
首先,作为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查询。总之就是严格执行那个流程图就对了。木的感情,木的节外生枝。
发指令前要清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,你不清后面这个口就不好用了。
测试中难免会一遍一遍的复位并重跑启动程序。但是当程序跑ACMD41出现问题的时候想复位MCU重来的时候,必须把内存卡弹出再重新插入。否则会出现响应超时且不响应。
说了这么多,可以看出,笔者还是做了一点作业才能开展这个工作的。那么下面就制定一下本期的目标。并验证。
目的:实现发送CMD3完成初始化。
目标:
实现这个固件一共需要做下面的三件事:
那笔者就依次叙述。由于这种SDIO接口的卡也被叫做Combo Card,所以我在程序里用的是Combo Card命名。当然,如果是我后面发现理解文档有误再进行修改。
首先用系统模板创建一个主函数。在主函数中初始化硬件(所有外设的时钟和引脚功能定义)。之所以我倾向在主函数中初始化硬件,是因为我们在硬件初始化过程中一不小心可能会用到一些需要在特权模式才方便操作的指令。而在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 (;;) {} }
利用模板创建一个线程,起名叫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就是有这个问题。动不动给你来个大惊小怪。等我后面全给你弄汇编里,都给我安静!
创建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
把之前的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返回。
插卡,上电并启动调试。每一步的运行的情况都在下面的调试窗口中有显示。返回值在右侧的寄存器里。
将卡复位。
卡回应0x100。查一下R7的意思,是说普通卡,电压2.7 - 3.6V。
对的,就是这步,当时卡住了很久。就是手册没有看好。要先发CMD55。卡对CMD55有回应,但是这里貌似是用不上的。
在这里插入图片描述
第一次发ACMD41是查询,第二次发就是设置了。
话说CMD2应该是回应R2长响应,但是看RSP里面好像也不长。CRC
细心的你可能已经看出来了,左边的内核时间有点不太对。笔者这些图是在多次调试中截取的,所以时间不一定能对上。但是程序绝对没有问题。
根据对手册和SDIO协议的学习,我们可以简单快速的实现SDIO Memory Card的初始化了。后面要尝试进行读写操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。