IOT-OS之RT-Thread(十五)--- SDIO设备对象管理 + AP6181(BCM43362) WiFi模块

本文详细介绍了如何在RT-Thread操作系统中管理和驱动SDIO Wi-Fi模块AP6181,该模块基于BCM43362芯片。内容涵盖AP6181硬件接口、驱动层级、SDIO设备对象管理、SDIO总线驱动、SDIO卡设备与驱动的注册和匹配过程。同时,文章还讨论了SDIO硬件驱动的初始化、CubeMX配置、时钟配置和WL_REG_ON引脚的设置。最后,文章提及了RT-Thread的SDIO编译选项配置和WLAN框架的后续内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本章介绍SDIO Wi-Fi模块的驱动实现过程,对SDIO不熟悉的可以先参阅博客:SD/MMC + SDIO,对RT-Thread驱动分层与主从分离思想不熟悉的,可以先参阅博客:驱动分层与主从分离思想

一、AP6181 Wi-Fi模块简介

1.1 AP6181 硬件接口

Pandora开发板为我们提供了一个板载Wi-Fi 芯片 — AP6181,我们不需要再外接扩展模块(比如 ESP8266)即可实现连接 Wi-Fi 并访问外部网络的功能。我们先看下Pandora开发板原理图中 Wi-Fi 模块 AP6181 的硬件接线原理图:
AP6181硬件接线原理图
正基公司的 AP6181 Wi-Fi 模组具有以下特点:

  • 符合IEEE 802.11 b/g/n标准,可以实现单通道高达72.2Mbps 的传输速度(IEEE 802.11n 标准);
  • 支持标准接口SDIO v2.0(时钟频率高速模式可达50MHz,数据线位宽支持4位或1位模式);
  • 集成ARM Cortex-M3 (带有片上存储器)以运行 IEEE802.11 固件(用于Wi-Fi 数据帧的处理);

AP6181 Wi-Fi 模组内部实际封装的是Broadcom 43362 芯片,接下来看看 BCM43362 芯片内部都有哪些模块(图片取自BCM43362_datasheet):
BCM43362系统框图
从上面 BCM43362 系统框图可以看出,内部是集成了ARM处理器及RAM/ROM存储空间的,用于运行 Wi-Fi 固件,管理 Wi-Fi MAC/PHY/RADIO层的无线链路信道资源,完成 Wi-Fi 数据帧 与 以太网数据帧 之间的转换等功能。

AP6181 / BCM43362 模组外接引脚如下:

  • SDIO(Secure Digital Input Output):包括DATA[0:3]、CMD、CLK 共6个引脚,支持SDIO V2.0总线标准;
  • WiFi INT / WL_HOST_WAKE:WLAN wake-up Host,当接收到数据帧后,产生中断信号,唤醒主机Host接收并处理该数据帧;
  • WL_REG_ON:Internal regulators power enable/disable,我觉得跟BCM43362芯片引脚 WL_RST_N / POR(WLAN Reset / Power-On Reset,该引脚持续拉低则BCM 43362进入Power-down状态,给该引脚一个低电平脉冲则BCM 43362进入Reset 状态) 功能相似;
  • Coexistence interface:相近频率的无线设备(比如蓝牙)通过该接口与BCM43362连接,可以共享BCM43362的无线介质(MAC/PHY/RADIO层及其后的ANT等资源),我们暂时不需要蓝牙功能,该引脚可忽略;
  • WL_BT_ANT:向外与板载丝印天线相连,向内通过 T/R Switch 与WLAN Tx/Rx 相连,AP6181把BCM43362与T/R Switch封装到一起了;
  • System Clock / XTAL:外接晶振XTAL_IN / XTAL_OUT,为AP6181或BCM43362提供系统时钟;
  • Sleep Clock / LPO:睡眠时钟或者低功耗时钟输入,Pandora开发板并未使用该引脚;
  • VBAT / VDDIO / LDO:Power supply,为AP6181或BCM43362提供电源支持。

从AP6181 模块的接线图可以看出,我们需要重点关注的是SDIO、WiFi INT、WL_REG_ON这三组共8个引脚,其余的引脚Pandora 开发板上已经帮我们接好了。SDIO引脚的定义在博客:SD/MMC + SDIO中已经有过介绍,WiFi INT引脚需要绑定自定义的中断处理函数,WL_REG_ON引脚是内部稳压电源的使能引脚,在WLAN模块正常工作时需要将其拉高,对该引脚的拉高时间有什么要求吗?我们看下AP6181 / BCM43362 WLAN模块的启动时序图:
WLAN Boot-Up Sequence
从上图可以看出,WL_REG_ON引脚需要在 VBAT / VDDIO 上电2个睡眠时钟周期(32.768KHZ)后,1.5ms之内完成电平拉高,我们可以在 AP6181 驱动代码中设置WL_REG_ON引脚的拉高时机(比如0.1ms ~ 1ms)与动作。

1.2 AP6181 驱动层级

AP6181 模块不像 ESP8266 模块那样内部集成了 WLAN驱动与TCP/IP协议栈,甚至AP6181 为了节省成本,模块内可能就没有可供存放WLAN驱动代码的ROM区域。

AP6181 模块内部有ARM处理器和RAM 内存区域,在工作时也需要运行WLAN固件程序以处理WLAN数据帧,这就需要开发者在初始化该模块时,将主控端Flash中保存的WLAN固件代码传送到AP6181 模块内(RAM内存区域)。这样做虽然增加了点主控端的驱动代码和ROM占用空间,但也有三个明显的好处:

  • 省去了大部分ROM空间,降低了模块的成本;
  • 不需要在模块出厂时单独为其烧录固件代码,减少了生产环节;
  • 固件代码便于维护和升级,只需要更新主控端Flash内的固件文件,模块初始化时自动会将新的固件代码传输到模块内。

由于Nand Flash成本比Nor Flash(可以在芯片内执行代码,而不需先拷贝到RAM内存中)更低,且在主控端Flash中更新固件代码更灵活方便,因此这种固件加载方式在设备驱动开发中很常见。

AP6181 模块是基于SDIO 总线协议进行通信的,因此模块与主控端最底层应该分别是SDIO Card controller与Host controller。SDIO Card内有一个CSA(Code Storage Area)可以用来存放WLAN固件代码(由AP6181 芯片供应商提供),SDIO Host controller上层则分别是SDIO Bus Driver和SDIO Card Driver。AP6181 模块提供的是Wi-Fi 网络访问服务,因此这里的SDIO Card Driver 也就是 WLAN Driver(由AP6181 芯片供应商提供公版驱动,开发者再根据需要调整或移植)。当 WLAN Driver 适配完成,接下来的Wi-Fi 管理与网络服务就可以交给操作系统了,Pandora 开发板上的 AP6181 模块WLAN驱动及TCP/IP协议栈的层级关系图如下:
AP6181 驱动层级图
RT-Thread为方便我们管理Wi-Fi 设备,提供了一个WLAN管理框架,相当于WLAN 设备无关层,可以向上提供统一的访问接口。当我们更换 Wi-Fi 模块时,只需要修改相应的适配代码,不需要修改上层的应用程序。

二 SDIO设备对象管理

前篇博客:SD/MMC + SDIO已经简单介绍过SDIO协议的三个部分:SDIO Host controller、SDIO Bus protocol、SDIO Card。上面已经简单介绍过本文需要用到的 SDIO Card — AP6181 模块,这里重点介绍主机端的SDIO Host controller 与 SDIO Bus protocol,包括SDIO Card Driver — WLAN Driver。

前篇博客:驱动分层与主从分离思想介绍了RT-Thread I/O 设备驱动管理的分层思想和一主多从工作模式的主从分离思想,也提到了Linux的总线设备驱动模型。SDIO驱动也可以看出明显的分层结构(只列出了SDIO相关的驱动文件,省去了MMC/SD相关的驱动文件):

// I/O 设备管理层
rt-thread-4.0.1\include\rtdef.h
rt-thread-4.0.1\src\device.c

// 设备驱动框架层
rt-thread-4.0.1\components\drivers\include\drivers\sdio.h
rt-thread-4.0.1\components\drivers\sdio\sdio.c
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_card.h
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_core.h
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_cmd.h
rt-thread-4.0.1\components\drivers\sdio\mmcsd_core.c
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_host.h

// 设备驱动层
libraries\HAL_Drivers\drv_sdio.h
libraries\HAL_Drivers\drv_sdio.c

SDIO的设备驱动框架层还可以细分为SDIO Card Layer、Core Layer、Host Layer,但这种分层没有清晰的展示出Host与Card的主从分离思想。上面并没有展示SDIO Card Driver — WLAN Driver 的相关文件,我也不知道应该将其放到设备驱动框架层还是设备驱动层。

如果按照主从分离思想看,SDIO协议可以分为主机Host 与卡设备Card,I/O Card可能支持的功能模块比较多(1个I/O Card最多可以包含7个功能模块),驱动种类与数量自然也比较多。如果我们把 I/O Card和 function device driver 看作一体,驱动代码的复用性就比较低。如果我们我们借鉴Linux的总线设备驱动模型(如下图所示),将 I/O Card和 function device driver 也分开,就可以根据需要灵活匹配 Driver 与 Function Device,不仅实现了Host — Card 的主从分离,也实现了 Function Device — Function Driver 的设备驱动分离,符合编写代码的高内聚、低耦合原则。
Linux总线设备驱动模型
一个总线Bus 分别管理一个设备链表device_list 和一个驱动链表 driver_list,当新注册一个I/O Card/Device 或 Function device Driver 时,探测已有的 driver_list 或 device_list 并尝试与自己匹配(调用Bus提供的match接口函数)。当Driver 与 Device 完成匹配后,就可以调用Driver提供的probe接口函数完成Device设备的初始化,后面就可以通过Driver提供的API 来访问Device设备,实现相应功能的扩展服务。

2.1 SDIO Bus Driver

2.1.1 Host 数据结构描述

SDIO Bus要给出Command、Response、Data三部分分别是如何描述、配置和传输的,而这三部分的传输是受主机Host 控制的,在介绍Command/Response与Data的描述结构前,先介绍下主机Host 是如何描述的:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_host.h

struct rt_mmcsd_host {
   
	struct rt_mmcsd_card *card;
	const struct rt_mmcsd_host_ops *ops;
	rt_uint32_t  freq_min;
	rt_uint32_t  freq_max;
	struct rt_mmcsd_io_cfg io_cfg;
	rt_uint32_t  valid_ocr;	/* current valid OCR */
#define VDD_165_195		(1 << 7)	/* VDD voltage 1.65 - 1.95 */
#define VDD_20_21		(1 << 8)	/* VDD voltage 2.0 ~ 2.1 */
#define VDD_21_22		(1 << 9)	/* VDD voltage 2.1 ~ 2.2 */
#define VDD_22_23		(1 << 10)	/* VDD voltage 2.2 ~ 2.3 */
#define VDD_23_24		(1 << 11)	/* VDD voltage 2.3 ~ 2.4 */
#define VDD_24_25		(1 << 12)	/* VDD voltage 2.4 ~ 2.5 */
#define VDD_25_26		(1 << 13)	/* VDD voltage 2.5 ~ 2.6 */
#define VDD_26_27		(1 << 14)	/* VDD voltage 2.6 ~ 2.7 */
#define VDD_27_28		(1 << 15)	/* VDD voltage 2.7 ~ 2.8 */
#define VDD_28_29		(1 << 16)	/* VDD voltage 2.8 ~ 2.9 */
#define VDD_29_30		(1 << 17)	/* VDD voltage 2.9 ~ 3.0 */
#define VDD_30_31		(1 << 18)	/* VDD voltage 3.0 ~ 3.1 */
#define VDD_31_32		(1 << 19)	/* VDD voltage 3.1 ~ 3.2 */
#define VDD_32_33		(1 << 20)	/* VDD voltage 3.2 ~ 3.3 */
#define VDD_33_34		(1 << 21)	/* VDD voltage 3.3 ~ 3.4 */
#define VDD_34_35		(1 << 22)	/* VDD voltage 3.4 ~ 3.5 */
#define VDD_35_36		(1 << 23)	/* VDD voltage 3.5 ~ 3.6 */
	rt_uint32_t  flags; /* define device capabilities */
#define MMCSD_BUSWIDTH_4	(1 << 0)
#define MMCSD_BUSWIDTH_8	(1 << 1)
#define MMCSD_MUTBLKWRITE	(1 << 2)
#define MMCSD_HOST_IS_SPI	(1 << 3)
#define controller_is_spi(host)	(host->flags & MMCSD_HOST_IS_SPI)
#define MMCSD_SUP_SDIO_IRQ	(1 << 4)	/* support signal pending SDIO IRQs */
#define MMCSD_SUP_HIGHSPEED	(1 << 5)	/* support high speed */

	rt_uint32_t	max_seg_size;	/* maximum size of one dma segment */
	rt_uint32_t	max_dma_segs;	/* maximum number of dma segments in one request */
	rt_uint32_t	max_blk_size;   /* maximum block size */
	rt_uint32_t	max_blk_count;  /* maximum block count */

	rt_uint32_t   spi_use_crc;
	struct rt_mutex  bus_lock;
	struct rt_semaphore  sem_ack;

	rt_uint32_t       sdio_irq_num;
	struct rt_semaphore    *sdio_irq_sem;
	struct rt_thread     *sdio_irq_thread;

	void *private_data;
};

struct rt_mmcsd_io_cfg {
   
	rt_uint32_t	clock;			/* clock rate */
	rt_uint16_t	vdd;			/* vdd stores the bit number of the selected voltage range from below. */

	rt_uint8_t	bus_mode;		/* command output mode */
#define MMCSD_BUSMODE_OPENDRAIN	1
#define MMCSD_BUSMODE_PUSHPULL	2

	rt_uint8_t	chip_select;		/* SPI chip select */
#define MMCSD_CS_IGNORE		0
#define MMCSD_CS_HIGH		1
#define MMCSD_CS_LOW		2

	rt_uint8_t	power_mode;		/* power supply mode */
#define MMCSD_POWER_OFF		0
#define MMCSD_POWER_UP		1
#define MMCSD_POWER_ON		2

	rt_uint8_t	bus_width;		/* data bus width */
#define MMCSD_BUS_WIDTH_1		0
#define MMCSD_BUS_WIDTH_4		2
#define MMCSD_BUS_WIDTH_8		3
};

结构体rt_mmcsd_host 描述了SDIO主机Host 应具有的属性和方法(SDIO这个名字出现较晚,很多地方仍用旧名MMC、SDMMC或MMCSD等),包括要访问的卡设备指针 *card、Host驱动层应实现的操作函数集合指针 *ops、Input/Output配置结构io_cfg(包括时钟频率、电源模式、总线模式及位宽等)、主机Host支持的有效工作电压valid_ocr、传输数据的DMA/Block配置、同步命令/响应的mutex/semaphore对象等。

最后几个成员变量是用于管理卡设备的中断处理过程的,sdio_irq_num为绑定到Host 的中断处理函数的数量,sdio_irq_sem 则用于通知线程 sdio_irq_thread 有中断被触发,sdio_irq_thread 是执行注册到Host 的中断处理函数的线程,当获得信号量sdio_irq_thread 后开始执行用户绑定的中断处理函数。

卡设备描述rt_mmcsd_card我们在下文介绍,这里先看主机Host 需要下驱动层实现并向其注册的操作函数有哪些:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_host.h

struct rt_mmcsd_host_ops {
   
	void (*request)(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req);
	void (*set_iocfg)(struct rt_mmcsd_host *host, struct rt_mmcsd_io_cfg *io_cfg);
	rt_int32_t (*get_card_status)(struct rt_mmcsd_host *host);
	void (*enable_sdio_irq)(struct rt_mmcsd_host *host, rt_int32_t en);
};

操作函数集合rt_mmcsd_host_ops中包含的接口函数功能介绍如下:

  • request 方法:用于Host 向 Card 发送Command / Data Request;
  • set_iocfg 方法:用于配置Input/Output 结构io_cfg,包括时钟频率、电源模式、总线模式及位宽等;
  • get_card_status 方法:获取卡设备的状态信息,尝试探测卡设备;
  • enable_sdio_irq 方法:使能/禁用 SDIO 的中断请求功能。

2.1.2 rt_mmcsd_req 数据结构描述

接口函数request 用于Host 向 Card 发送Command / Data Request,参数rt_mmcsd_req 是如何描述的呢?

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_core.h

struct rt_mmcsd_req {
   
	struct rt_mmcsd_data  *data;
	struct rt_mmcsd_cmd   *cmd;
	struct rt_mmcsd_cmd   *stop;
};

struct rt_mmcsd_data {
   
	rt_uint32_t  blksize;
	rt_uint32_t  blks;
	rt_uint32_t  *buf;
	rt_int32_t  err;
	rt_uint32_t  flags;
#define DATA_DIR_WRITE	(1 << 0)
#define DATA_DIR_READ	(1 << 1)
#define DATA_STREAM	(1 << 2)

	unsigned int		bytes_xfered;

	struct rt_mmcsd_cmd	*stop;		/* stop command */
	struct rt_mmcsd_req	*mrq;		/* associated request */

	rt_uint32_t  timeout_ns;
	rt_uint32_t  timeout_clks;
};

struct rt_mmcsd_cmd {
   
	rt_uint32_t  cmd_code;
	rt_uint32_t  arg;
	rt_uint32_t  resp[4];
	rt_uint32_t  flags;
/*rsponse types 
 *bits:0~3
 */
#define RESP_MASK	(0xF)
#define RESP_NONE	(0)
#define RESP_R1		(1 << 0)
#define RESP_R1B	(2 << 0)
#define RESP_R2		(3 << 0)
#define RESP_R3		(4 << 0)
#define RESP_R4		(5 << 0)
#define RESP_R6		(6 << 0)
#define RESP_R7		(7 << 0)
#define RESP_R5		(8 << 0)	/*SDIO command response type*/
/*command types 
 *bits:4~5
 */
#define CMD_MASK	(3 << 4)		/* command type */
#define CMD_AC		(0 << 4)
#define CMD_ADTC	(1 << 4)
#define CMD_BC		(2 << 4)
#define CMD_BCR		(3 << 4)

#define resp_type(cmd)	((cmd)->flags & RESP_MASK)

/*spi rsponse types 
 *bits:6~8
 */
#define RESP_SPI_MASK	(0x7 << 6)
#define RESP_SPI_R1	(1 << 6)
#define RESP_SPI_R1B	(2 << 6)
#define RESP_SPI_R2	(3 << 6)
#define RESP_SPI_R3	(4 << 6)
#define RESP_SPI_R4	(5 << 6)
#define RESP_SPI_R5	(6 << 6)
#define RESP_SPI_R7	(7 << 6)

#define spi_resp_type(cmd)	((cmd)->flags & RESP_SPI_MASK)
/*
 * These are the command types.
 */
#define cmd_type(cmd)	((cmd)->flags & CMD_MASK)
	
	rt_int32_t  retries;	/* max number of retries */
	rt_int32_t  err;

	struct rt_mmcsd_data *data;
	struct rt_mmcsd_req	*mrq;		/* associated request */
};

结构体rt_mmcsd_req包含了数据指针 *data、命令指针 *cmd、停止命令指针 *stop三部分,SDIO总线上传输的主要就是Data、Command/Response两部分,附加一个停止命令方便判断数据流或多个数据块的传输结束。

结构体rt_mmcsd_data包括了数据块大小与数量、缓冲区首地址与字节数、读写标志(包括数据流还是数据块的标志)、停止传输命令、与该数据对象相关的请求、超时等信息。

结构体rt_mmcsd_cmd包括了命令编码(比如CMD5)、命令参数、响应数据、命令类型与响应类型标志(命令/响应在博客:SD/MMC + SDIO中有详细介绍)、允许该命令尝试发送的最大次数、与该命令对象相关的数据、与该命令对象相关的请求等信息。

2.1.3 SDIO Bus 接口函数及初始化过程

SDIO Bus有了上面主机Host 和请求 Request 的数据结构描述,再加上 Host 驱动层提供的方法集合 rt_mmcsd_host_ops (下文介绍Host 驱动层时,会介绍其如何实现并注册给 Host 的),就可以对外提供一些访问接口,方便开发者配置管理这些结构体,并通过 SDIO Bus 访问 Card device。SDIO Core Layer为我们提供的访问接口如下:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_core.h

int mmcsd_wait_cd_changed(rt_int32_t timeout);
void mmcsd_host_lock(struct rt_mmcsd_host *host);
void mmcsd_host_unlock(struct rt_mmcsd_host *host);
void mmcsd_req_complete(struct rt_mmcsd_host *host);
void mmcsd_send_request(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req);
rt_int32_t mmcsd_send_cmd(struct rt_mmcsd_host *host, struct rt_mmcsd_cmd *cmd, int retries);
rt_int32_t mmcsd_go_idle(struct rt_mmcsd_host *host);
rt_int32_t mmcsd_spi_read_ocr(struct rt_mmcsd_host *host, rt_int32_t high_capacity, rt_uint32_t *ocr);
rt_int32_t mmcsd_all_get_cid(struct rt_mmcsd_host *host, rt_uint32_t *cid);
rt_int32_t mmcsd_get_cid(struct rt_mmcsd_host *host, rt_uint32_t *cid);
rt_int32_t mmcsd_get_csd(struct rt_mmcsd_card *card, rt_uint32_t *csd);
rt_int32_t mmcsd_select_card(struct rt_mmcsd_card *card);
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值