赞
踩
根据上一篇中介绍的思路,我尝试调试了几次。笔者又发现了以下的问题:
问题 | 原因 | 解决 |
---|---|---|
调试的时候如果一直插着卡,或者不将卡拔出而上电复位开发板再进入调试,会发现卡无法初始化 | 开发板在上电的时候就会执行先前残留的程序。如果这部分程序中包含了初始化卡的代码,则会把卡初始化。当进入DEBUG后,卡已经处于DATA_TRANSFER状态,不再响应CMD41 | 调试的时候就对卡进行重复插拔调试。或者先把里面的程序抹了再插卡进入调试 |
初始化命令序列,及CMD8->CMD41->CMD2->CMD3这个序列,用C语言发就好用,用汇编发就发生RESPx里面的数据无法用LDR语句读出 | 不详。但是仿佛是CPSM在每次发完指令以后关闭一下就好了。可能要再研究一下CPSM的状态转换图。但是目前这个解决方案有效 | 发送指令以后关闭CPSM |
将所有的初始化卡的C代码用汇编实现并通过测试。
在Card Identification Mode中,注意把频率先降下来到400kHz以内。参考《Physical Layer Simplified Specification》第67页
《Physical Layer Simplified Specification》第67页
在进入了Data Transfer Mode以后,可以将时钟调到正常水平。参考《Physical Layer Simplified Specification》第76页
《Physical Layer Simplified Specification》第76页
用STM32F407发送CMD0的时候,明明SDIO_STA中已经提示了CMDSENT,但是可以确认的是,卡并没有初始化。但是由于笔者的示波器坏了,所以无法确认到底是指令没有发还是卡没有响应。所以我认为最好还是硬件上给卡的VDD引脚来个可控的设计。另外,上电复位以后,其实只要从CMD8开始往后操作就可以了。
参考《Physical Layer Simplified Specification》第67页,发送ACMD41的时候你要么就一直在轮询发,要么就等一等。这里我采用的是轮询发。因为笔者在底层没有很好的延时理由。
《Physical Layer Simplified Specification》第67页
这里简单说一下。在用汇编写驱动的时候不像在C的时候。只要有点风吹草动就可以怀疑是需要延时一下,加个HAL_Delay(ms)
或者其他的什么函数延时一下就往往让程序跑通了。但是在汇编这里,所有的延时一般都是轮询某个标志位来的,都必须要有道理。比如这里,如果不发ACOM41,SD卡是不会在成功初始化后第一时间通知MCU的。所以就选择连续发送ACOM41的方法确认初始化完成状态。
很多资料都说,把CPSM一直开着,就发指令就好。但是所有的手册都没有体现这一点。笔者也发现,很多时候都是在C语言下好使,但是同样的逻辑用汇编做出来就会出现各种迷惑问题导致程序不能执行。这个跟前面说的时钟频率还真是没有什么关系。经过测试,笔者发现,每次发完指令都把这个CPSM关一下,这个问题就没了。所以笔者推断,这个CPSM状态机并不是希望你一直开着的。随用随开,用完就关。
在DEBUG的时候往往会把程序改好以后直接接入DEBUG,而忽略了卡目前可能处于的状态。根据《Physical Layer Simplified Specification》第50(76)页。已经被分配了RCA的卡不会响应识别指令,包括ACMD41,CMD2。所以DEBUG的时候,比如是先插卡后上电再调试的,还是调试了一次以后改了程序又调试的,其实卡可能一直处在被分配了RCA的状态,那么是不会响应识别指令的。
《Physical Layer Simplified Specification》第50(76)页
这里就不将peripherals.s里面的代码贴出了。其次,考虑到笔者操作的其实是memory card,不能算是combo card,所以将程序中的原来是combo card的名称改成memory card。
首先是修改了接口。取消了uint32_t (*send_cmd)(uint32_t cmdIndex, uint32_t arg, WaitRspKind waitRsp);
方法,换成简单的void (*card_identification)(void);
方法。
下面是SDIO_Memory_Card.h的源码
#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); void (*card_identification)(void); }SDIO_Memory_Card_Def; extern const SDIO_Memory_Card_Def SDIO_Memory_Card; #endif
这个接口的实现文件是SDIO_Memory_Card.s。
在这个文件中单独把“发送命令”作为一个私有方法写出。因为如果不这么做,代码量很大,结构也会比较复杂。在这个方法中,每次发送完了命令都吧CPSM关掉。下次发的时候再打开。每个命令都由调用这个函数的的函数构造,send_cmd方法只是把把参数从r1装入SDIO_ARG命令从r0装入SDIO_CMD,
get peripherals.s rRCC rn r8 rSDIO rn r9 rGPIOC rn r10 rGPIOD rn r11 SDIO_GPIO_SPEED equ GPIOx_OSPEEDR_VERYHIGH area card_data_area, data align 4 rca space 4 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/CD : PC11 ; ldr rGPIOC, =GPIOC_BaseAddr ldr r0, [rGPIOC, #GPIOx_MODER] bic r0, #2_11:shl:(11*2) orr r0, #GPIOx_MODER_OUTPUT:shl:(11*2) str r0, [rGPIOC, #GPIOx_MODER] ldr r0, [rGPIOC, #GPIOx_PUPDR] orr r0, #GPIOx_PUPDR_PU:shl:(11*2) str r0, [rGPIOC, #GPIOx_PUPDR] mov r0, #1:shl:11 str r0, [rGPIOC, #GPIOx_BSRR] mov r0, #1:shl:(11 + 16) str r0, [rGPIOC, #GPIOx_BSRR] 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] ; 因为HCLK被配置成了144MHz,APB2分频是2分频,所以APB2的时钟是72MHz。 ; 为了达到400kHz的初始时钟输出,这里的APB2分频器的数值应该是72M/400k = 180。 ; 所以如果这里写180,那么实际的分频是182分频,那么就肯定是满足要求的 ldr rSDIO, =SDIO_BaseAddr mov r0, #SDIO_POWER_PWRCTRL_POWERON str r0, [rSDIO, #SDIO_POWER] mov r0, #SDIO_CLKCR_CLKEN:or:SDIO_CLKCR_PWRSAV:or:SDIO_CLKCR_WIDBUS_4WIDE:or:180 str r0, [rSDIO, #SDIO_CLKCR] pop {r4 - r11, lr} bx lr endp card_identification proc push {r4 - r11, lr} ldr rSDIO, =SDIO_BaseAddr mov r0, #SDIO_CMD_CPSMEN str r0, [rSDIO, #SDIO_CMD] mov r0, #0 str r0, [rSDIO, #SDIO_CMD] mov r0, #8:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN mov r1, #1:shl:8 bl send_cmd mov r0, #55:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN mov r1, #0 bl send_cmd mov r0, #41:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN mov r1, #0 bl send_cmd ldr r4, [rSDIO, #SDIO_RESP1] set_voltage mov r0, #55:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN mov r1, #0 bl send_cmd mov r0, #41:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN mov r1, r4 bl send_cmd ldr r0, [rSDIO, #SDIO_RESP1] tst r0, #1:shl:31 beq set_voltage Ask_the_Card_to_Publish_CID mov r0, #2:or:SDIO_CMD_WAITRESP_Long:or:SDIO_CMD_CPSMEN mov r1, #0 bl send_cmd Ask_the_Card_to_Publish_RCA mov r0, #3:or:SDIO_CMD_WAITRESP_Short:or:SDIO_CMD_CPSMEN mov r1, #0 bl send_cmd ldr r0, [rSDIO, #SDIO_RESP1] bfc r0, #0, #16 ldr r4, =rca str r0, [r4] ; 目前已经进入了DATA_TRANSFER_MODE ; 将频率设置到高频。这里设置到36MHz ldr r0, [rSDIO, #SDIO_CLKCR] bfc r0, #0, #8 str r0, [rSDIO, #SDIO_CLKCR] ; 测试DATA_TRANSFER_MODE模式下的指令 mov r0, #9:or:SDIO_CMD_WAITRESP_Long:or:SDIO_CMD_CPSMEN ldr r1, =rca ldr r1, [r1] bl send_cmd mov r0, #10:or:SDIO_CMD_WAITRESP_Long:or:SDIO_CMD_CPSMEN ldr r1, =rca ldr r1, [r1] bl send_cmd pop {r4 - r11, lr} bx lr endp ; Priviate Function Name: Send_cmd ; Args: r0 - cmd, r1 - arg ; 实现这个函数 align 4 send_cmd proc push {r4 - r11, lr} ldr rSDIO, =SDIO_BaseAddr mov r2, #0x7f str r2, [rSDIO, #SDIO_ICR] str r1, [rSDIO, #SDIO_ARG] str r0, [rSDIO, #SDIO_CMD] wait_for_response ldr r0, [rSDIO, #SDIO_STA] tst r0, #SDIO_STA_CMDREND:or:SDIO_STA_DCRCFAIL:or:SDIO_STA_CTIMEOUT beq wait_for_response ; mov r0, #0 ; str r0, [rSDIO, #SDIO_CMD] pop {r4 - r11, lr} bx lr endp align 4 SDIO_Memory_Card export SDIO_Memory_Card dcd init, card_identification end
卡识别过程的流程就是基本按照手册上说的,
注意看一下send_cmd(...)
私有方法。这里面的流程是
测试用例这样就比较简单了,代码如下所示。
#include "cmsis_os2.h" // CMSIS RTOS header file #include "SDIO_TestCase.h" #include "SDIO_Memory_Card.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; (void)argument; // 在这里做一个测试 SDIO_Memory_Card.card_identification(); while (1) { osDelay(101); } }
进DEBUG,插入SD卡,F5启动。在下面这个地方下断。
可以发现程序顺利执行,SD卡进入了DATA_TRANSFER_MODE, 并且读到了RCA。
经过上面的程序源码设计和测试,可以看到程序顺利运行。有以下的体会。
这样初始化的任务就基本完成了。下一步就是测试DATA_TRANSFER_MODE中的指令和功能了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。