KEIL 5.38 ARM-CM3/4 ARM汇编设计学习笔记9 – STM32 SDIO学习入门
KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记9 – STM32的SDIO学习1
一、概述和回顾
相比笔者目前接触过的接口,包括UART、SPI、I2C(硬件)、CAN、FSMC,我感觉这些总线中只有CAN好像可以真正意义上一行代码都不写,直接进DEBUG里对相应的寄存器一通捣鼓就可以成功看到数据帧或扩展帧的收发。其他的总线多多少少会因为有一些时效性的要求,就算是时序对了,不能满足时效的话就会发生异常。所以虽然CAN的文档也不少,但是学习曲线其实就还好。但是对于这种有时效性要求的接口,如果文档还比较多,文档结构还不太对你的阅读思路习惯,那就可能要辛苦一些了。
关于学习SDIO、ETH和USB在Cortex-M3/4上的开发,一般都是劝退的。网上总有人说,STM32的SDIO比较复杂,或者就是SDIO本身就比较复杂,驱动肯定做不了,要用第三方的。或者你买了书或者看了视频,到了学这个的时候了,人家来一句“这个东西比较复杂,就不展开讲了”,然后给你贴出一堆巨大的C语言源文件,或者就不贴叫你自己去看工程文件,确保你其实是看不懂的或者是也不会去看的。但是!笔者就是要做一个自己的驱动出来。
1.1、 一点体会
所有的能存在到今天的广泛应用的接口,一定是因为这个接口的门槛还是没有那么高的。否则就早被淘汰了。我十分看好SDIO的应用。主要有两点:
- 比着其他的基本的通讯总线的设备,SDIO设备的性能参数一般都比较强。比如串口wifi,SPI的W25Q128,SDIO WIFI和SDIO Memory(学过协议便知,SDIO Card指的往往是SDIO I/O Card)。
- 比着那些性能比较好的,它引脚有比较少。比如NAND。虽然FSMC可以挂NAND,但是对比一下,笔者感觉还是SDIO内存卡比较简单。
这个接口的学习曲线相比CAN来讲,其实我认为稍微陡一点。关于协议的介绍和寄存器的介绍网上有很多很多了,我就不赘述了。主要有以下的难点。
1.1.1 文档比较复杂
主要的文档一共有两3个,分别是
- 《RM0090 Reference manual – STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs》 这里注意一个问题,ST的网站有的时候打不开可能是因为你的浏览器不被支持。可以试下Windows Edge。这个笔者试过可以打开。
- 《Physical Layer Simplified Specification》 和下面的3一样,都在 https://www.sdcard.org/downloads/pls/ 这个网站下载。打开有点卡。其实就内容而言,我认为这个文档会更全面一点。
- 《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页)说明的上电初始化的指令顺序。要严格执行这个启动初始化的图,否则大概率会失败。这个并不是领会精神就可以的。为了方便理解,大概讲一下要做的事情的:
- COM0全部复位
- COM8检查电平
- 对于SDIO Memory Card有ACMD41先不挂参数广播查询OCR,再挂参数设置并启动初始化。
- 如果切电平,就先发CMD11,再控制硬件或固件切电平
- 发CMD2让卡发送CID值。
- 发CMD3让卡发送新的RCA值
- 启动初始化结束
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完成初始化。
目标:
- 初始化时钟
- 初始化相关引脚
- 初始化SDIO外设
- 发送指令初始化存储卡
三、 实现
实现这个固件一共需要做下面的三件事:
- 实现KEIL RTX下的一个主线程和测试用例线程
- 定义C接口
- 实现接口
那笔者就依次叙述。由于这种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的初始化了。后面要尝试进行读写操作。
六、参考文献
- 《RM0090 Reference manual – STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs》
- 《Physical Layer Simplified Specification》
- 《SDIO Simplified Specification》
作者:超级喵窝窝