当前位置:   article > 正文

STM32讲解

stm32

        STM32,从字面上来理解, ST 是意法半导体, M Microelectronics(微电子学) 的缩写, 32 表示32 位,合起来理解, STM32 就是指 ST 公司开发的 32 位微控制器。

  • 二、STM32命名规则

如STM32F103C8T6:

https://img-blog.csdn.net/20180203142743795?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfNDA4MTg3OTg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

  • 三、STM32硬件系统简介

如图所示:

 Cortex-M3内核:

        Cortex-M3是一款具有32位处理器的内核,拥有独立的指令总线和数据总线,指令总线和数据总线共享一个4GB的储存空间。

其中的六个总线:

ICode 总线:该总线将 M3 内核指令总线和闪存指令接口相连,指令的预取在该总线上 面完成。

DCode 总线:该总线将 M3 内核的 DCode 总线与闪存存储器的数据接口相连接,常量 加载和调试访问在该总线上面完成。

③ 系统总线:该总线连接 M3 内核的系统总线到总线矩阵,总线矩阵协调内核和 DMA 间 访问。

DMA 总线:该总线将 DMA AHB 主控接口与总线矩阵相连,总线矩阵协调 CPU DCode DMA SRAM,闪存和外设的访问。

⑤ 总线矩阵:总线矩阵协调内核系统总线和 DMA 主控总线之间的访问仲裁,仲裁利用 轮换算法。

AHB/APB :这两个桥在 AHB 2 APB 总线间提供同步连接,APB1 操作速度限于 36MHz,APB2 操作速度全速

 关于APB2 和 APB1 总线,上面挂载着 STM32 各种各样的特色外设。我们经常说的 GPIO、串口、 I2C、 SPI等等这些外设就挂载在这两条总线上,这个是我们学习 STM32 的重点。

FLASH储存器:

⑦ FLASH是存储芯片的一种,通过特定的程序可以修改里面的数据。FLASH存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM的优势),使数据不会因为断电而丢失。

STRM储存器:

⑧SRAM是静态随机存取存储器。它是一种具有静止存取功能的内存,不需要刷新电路即能保存它内部存储的数据。STM32F1系列可以通过FSMC外设来拓展SRAM

⑨FSMC控制器:

        FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和16位PC存储器卡连接,STM32的FSMC接口支持包括SRAM、NANDFLASH、NORFLASH和PSRAM等存储器。 AHB设备接口可以使内部CPU和其他主总线外设去访问外部存储器。 AHB事务可以传输外部设备协议。

  • 四、STM32常用模块以及外设

4.1 STM32引脚介绍

       GPIO 是STM32的可控制的引脚,STM32芯片的引脚与外部设备连接起来,可实现外部的通信控制外部硬件或者采集外部设备数据的功能。借助GPIO,微控制器可以实现对外部设备(LED、按键等等)最简单最直接的监控。除此之外,GPIO可以用于串行、并行通信等等。

4.1.1通用输入工作模式简介

1)GPIO浮空输入_IN_FLOATING模式

   I/O端口有电平输入的时候,I/O端口的电平完全由外部输入决定,也就是无信号的时候引脚悬空,该端口电平无法确定。

(2)GPIO上拉输入_IPU 模式

I/O口输入驱动器的上拉开关闭合,I/O端口的的电平信号直接进入了输入数据寄存器,在端口悬空的时候输入端可以保持在高电平,并且在IO口输入为低电平的时候输入端也还是低电平。

(3)GPIO下拉输入_IPD 模式

I/O口输入驱动器的下拉开关闭合,I/O端口的的电平信号直接进入了输入数据寄存器,在端口悬空的时候输入端可以保持在低电平,并且在IO口输入为高电平的时候输入端也还是高电平。

(4)GPIO模拟输入_AIN 模式

该情况下,I/O端口的模拟信号(电压信号,不是电平信号)直接输入到片上的外设。(如ADC等等)。

4.1.2通用输出工作模式简介

(1)GPIO开漏输出_OUT_OD 模式

   开漏输出模式下,当设置输出的值为高电平的时候,N-MOS管处于关闭状态,此时I/O端口的电平就不会由输出的高低电平决定,而是由I/O端口外部的上拉或者下拉决定;当设置输出的值为低电平的时候,N-MOS管处于开启状态,此时I/O端口的电平就是低电平。

(2)GPIO开漏复用输出_AF_OD模式

开漏复用输出模式,与开漏输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

(3)GPIO推挽输出_OUT_PP模式

   推挽输出模式下,当设置输出的值为高电平的时候,P-MOS管处于开启状态,N-MOS管处于关闭状态,此时I/O端口的电平就由P-MOS管决定:高电平;当设置输出的值为低电平的时候,P-MOS管处于关闭状态,N-MOS管处于开启状态,此时I/O端口的电平就由N-MOS管决定:低电平。

(4)推挽复用输出模式,与推挽输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

4.2 STM32 中断简介

        中断,即机器运行过程中出现某些意外情况,需机器停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

如图所示:

        换个角度理解这张图,我正在打游戏,我妈喊我出房间去客厅恰饭,并且以不出来就拔网线为威胁。这时候我应该怎么办?只能乖乖存档(挂机),然后去恰饭,恰完再回来继续玩。

我们分析一下上述的场景:

正在打游戏——当前执行的事件

我妈喊你吃饭——中断源

我听到妈妈的喊声(命令)——接受到中断请求

拔我网线威胁——事件优先级别高

存档——保存现场(中断响应)

去恰饭——执行中断事件

恰饭回房间——中断返回

打开存档继续游戏——恢复现场

4.3 STM32 看门狗简介

4.3.1 独立看门狗简介

        IWDG(Independent watchdog)独立看门狗,可以用来检测并解决由于软件错误导致的故障,当计数器到达给定的超时值时,会触发一个中断或产生系统复位。

        独立看门狗的时钟是它专用的低速时钟(LSI),所以它能保持工作及时主时钟出现问题。窗口看门狗的时钟是有APB1时钟分频得到得到,通过可配置的时间窗口来检测应用程序非正常的过迟或过早的操作。

①自由运行递减计数器

②时钟来自通过独立的RC振荡器(可以工作在待机和停止模式下)

③当向下计数器值达到0时,会产生复位(如果看门狗已经激活)

通俗点来讲独立看门狗的工作模式,就类似与那种科幻故事,比如说《开端》。你一直公交车上重复一段经历:你在公交车上醒来作为倒计时的开始,车上老妈妈引爆炸弹作为倒计时结束,一直在重复这种剧情。除非你提前做点什么(比如炸弹爆炸前干掉老妈妈)这就是喂狗,喂了狗就不会爆炸(复位)重启剧情。你就可以继续崭新的生活(程序继续运行)。

4.3.1 窗口看门狗简介

        WWDG窗口看门狗(Window watchdog)通常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。除非递减计数器的值在 T6 位变成 0 前被刷新,看门狗电路在达到预置的时间周期时,会产生一个 MCU 复位。如果在递减计数器达到窗口寄存器值之前刷新控制寄存器中的 7 位递减计数器值,也会产生 MCU 复位。这意味着必须在限定的时间窗口内刷新计数器。

①可编程自由向下计数器

②复位条件,激活看门狗的情况下,当递减计数器值小于 0x40 时复位;在窗口之外重载递减计数器时复位。

③提前唤醒中断 (EWI):当递减计数器等于 0x40 时触发(如果已使能且看门狗已激活)

跟独立看门狗剧情一致,窗口看门狗可讲究多了,你阻止老妈妈引爆炸弹的时机你得考虑起来了。太早了(下窗口值),老妈妈知道你识破了他的计划,太晚了(上窗口值)倒计时结束炸弹直接炸了。这中间有一个时间区间,必须在这个区间内阻止炸弹引爆(喂狗)。

4.4 ADC简介

        ADC是Analog-to-DigitalConverter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。

4.5 DAC简介

        Digital-to-Analog Converter的缩写。指数/模转换器或者数字/模拟转换器。是指将离散的数字信号转换为连续变量的模拟信号的器件。典型的数字模拟转换器将表示一定比例电压值的数字信号转换为模拟信号。STM32的DAC模块是12位数字输入,电压输出型的DAC。

4.6 DMA简介

        DMA(Direct Memory Access)——直接存储器访问,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用CPU,即在传输数据的时候,CPU可以干其他的事情,好像是多线程一样。数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM或者是FLASH。

4.7 STM32的通信

4.7.1 串口通信(USART)

    USART(通用同步异步收发器)是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。有别于 USART 还有一个UART,它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。USART 在 STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一个USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等。

4.7.2 IIC通信

        IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。

I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。

开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。

结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。

        应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

4.7.3 SPI通信

        SPI,是一种高速的,全双工, 同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32 也有 SPI 接口。

SPI 接口一般使用 4 条线通信:

MISO 主设备数据输入,从设备数据输出。

MOSI 主设备数据输出,从设备数据输入。

SCLK 时钟信号,由主设备产生。

CS 从设备片选信号,由主设备控制。

4.7.4 CAN通信

        CAN,全称为“Controller Area Network”,即控制器局域网,是一种多主方式的串行通讯总线,是国际上应用最广泛的现场总线之一。

        CAN总线通信系统是串行通信的一种,要优于RS485总线,是目前比较常用的一种工业总线,如汽车的电气部分就采用CAN总线实现通信。与I2C、SPI等具有时钟信号的同步通讯方式不同,CAN通讯并不是以时钟信号来进行同步的,它是一种异步半双工通讯。(同步即在同一个时钟驱动下数据通信,半双工即接受与发送不能同时进行)

 相较于串口等其他通信协议CAN有着如下优势:

        ① 数据传输速度很高,可达到1 Mbit/s。(CAN-FD和CAN-XL分别可以达到2 Mbit/s和10 Mbit/s。)

        ② 采用差分数据线,抗干扰能力强;

        ③多主通信模式,大幅减少单点通信线束成本;

        ④具有错误侦听的自我诊断功能,通信可靠信较高

五、嵌入式系统开发

5.1嵌入式系统的概念

        嵌入式系统( Embedded system) , 是一种 "完全嵌入受控器件内部,为特定应用而设计的专用计算机系统”以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。

5.1.1 嵌入式系统的构成原理

(1)系统概念
        如下图所示,一个典型的嵌入式系统构成可以分为两大部分:一部分是嵌入式系统的核心构成,包括软件和硬件;另一部分是嵌入式系统的接口,包括用户交互,数据输入、数据输出、与其他应用的接口。

(2)嵌入式硬件模块


        硬件是嵌入式系统的基础,大致组成如下图所示,硬件部分由核心处理器与外围硬件组成。外围硬件主要包括输入设备接口/驱动电路、电源模块、时钟模块、串并行接口等等。

5.1.2嵌入式系统分类

如下图所示:

        嵌入式系统分为单片机、实时嵌入式、具备联网功能的嵌入式系统、移动嵌入式,当然这是按照功能划分的,本章我们主要讲32单片机,的暂且忽略。

        单片机本身可以不需要操作系统 独立工作有输入口采集数据,对数据进行加工处理,根据处理结果向输出口输出数据给其它系统或者执行部件。

5.2 嵌入式系统的概述(STM32)

         如上图所示,嵌入式系统架构自底向上共由五个部分组成,它们分别为硬件设备层、官方开发库层、底层驱动层、外设模块层和算法层。

5.2.1硬件设备层

        硬件层(Hardware),硬件设备层是整个嵌入式系统架构的基础,关于这部分的内容我已经文章中进行了讲解,这里就不再重复介绍了。

5.2.2算法层

        算法层位于整个嵌入式架构的最顶端,其实现主要是建立在硬件设备层、底层驱动层和外设模块层三者已有的基础之上。是嵌入式系统的绝对核心,也是开发的重点和难点。我们不做这块,简单了解一下就行。

5.2.3外设模块层

        外设模块层是对具体外设硬件进行操作的函数库,它主要通过调用底层驱动层中已封装好的函数来实现与硬件之间的双向数据通信,对于一些常用外设我已经在上文做出了介绍,这里不多赘述。

5.2.4层驱动

        底层驱动层包含了常用的通信接口(IIC、SPI、USART、IO)和内部核心功能(时钟、延时、嵌套中断、定时器、EEPROM、Flash),它们全部是基于官方开发库层中的FWLib,即STM32官方固件库中的相应外设驱动代码进行编写,以便对系统外设接口或内部核心功能进行进一步的封装,在实现与硬件设备进行数据通信的同时也能满足外设模块对其调用的需要。

5.2.5官方开发库层

官方开发库层主要由 CMSIS、FWLib 和 LinkScript 这三个部分组成。

(1)CMSIS是Cortex Microcontroller Software Interface Standard的简写,即ARM Cortex™微控制器软件接口标准。CMSIS是独立于供应商的Cortex-M处理器系列硬件抽象层,为芯片厂商和中间件供应商提供了简单的处理器软件接口,简化了软件复用工作,降低了Cortex-M上操作系统的移植难度。

CMSIS包含以下组件:

  • CMSIS-CORE:提供与 Cortex-M0、Cortex-M3、Cortex-M4、SC000 和 SC300 处理器与外围寄存器之间的接口。
  • CMSIS-DSP包含以定点(分数 q7、q15、q31)和单精度浮点(32 位)实现的 60 多种函数的 DSP 库。
  • CMSIS-RTOS API:用于线程控制、资源和时间管理的实时操作系统的标准化编程接口
  • CMSIS-SVD:包含完整微控制器系统(包括外设)的程序员视图的系统视图描述 XML 文件。


CMSIS框架如下图所示:

CMSIS 分为 3 个基本功能层:

1) 核内外设访问层:ARM 公司提供的访问,定义处理器内部寄存器地址以及功能函数。

2) 中间件访问层:定义访问中间件的通用 API,也是 ARM 公司提供。

3) 外设访问层:定义硬件寄存器的地址以及外设的访问函数。

        从图中可以看出,CMSIS 层在整个系统中是处于中间层,向下负责与内核和各个外设直接打交 道,向上提供实时操作系统用户程序调用的函数接口。如果没有 CMSIS 标准,那么各个芯片公 司就会设计自己喜欢的风格的库函数,而 CMSIS 标准就是要强制规定,芯片生产公司设计的库 函数必须按照 CMSIS 这套规范来设计。

(2)FWLib是STM32官方提供的固件库源码,它基于STM32F1系列芯片的内部寄存器架构并根据CMSIS命名规范封装了一套完整的底层操作函数,方便用户进行应用开发。FWLib下的inc目录下存放的是stm32f10x_xxx.h形式的头文件,src目录下存放的是stm32f10x_xxx.c形式的固件库源码文件,每一个.c文件和一个.h文件相对应,用于实现命名中由xxx所指定的功能。注意:在开发的时候,不要修改这些源码文件,直接调用其中的函数即可。

(3)LinkScript即链接脚本,它的主要功能是描述如何把输入文件中的节(sections)映射到输出文件中,并控制输出文件的存储布局。当然,在大多数情况下我们都不会注意到链接脚本的存在,主要原因在于链接器在我们没有指定特定链接脚本的时候,会使用一个默认缺省的脚本。

  • 六、STM32CubeF1概念讲解

STM32Cube固件解决方案是围绕三个独立的级别构建的,它们可以很容易地相互交互,如上图所示:

首先是Level 0这一层又分为三个子层:单板支持包(BSP)硬件抽象层(HAL)基本外设使用示例。

6.1 STM32CubeF1体系LEVEL 0

6.1.1单板支持包(BSP)

        这一层提供了一组相对于硬件板中的硬件组件的API(LCD驱动,MicroSD,…)它由两部分组成:

         ①组件是相对于单板上的外部设备的驱动,而不是相对于STM32的驱动。组件驱动程序为BSP驱动程序外部组件提供特定的API,并且可以移植到任何其他板上。

        ②BSP驱动,它允许将组件驱动连接到特定的板,并提供一组用户友好的API。API命名规则是BSP_FUNCT_Action()。

        例如:BSP_LED_Init (), BSP_LED_On ()。

        BSP基于模块化架构,只需实现底层例程,就可以在任何硬件上轻松移植。

6.1.2硬件抽象层(HAL)和底层(LL)

STM32CubeF1 HAL和LL是互补的,涵盖了广泛的应用需求:

        HAL驱动程序提供面向高级功能的高可移植性API。他们隐藏MCU和外围设备对终端用户的复杂性。

        HAL驱动提供了通用的面向特征的多实例API,通过提供可使用的进程来简化用户应用程序的实现。例如,对于通信外设(I2S、UART…),它提供了允许初始化和配置外设的API,基于轮询、中断或DMA进程管理数据传输,以及处理通信过程中可能产生的通信错误。HAL驱动程序的API分为两类:首先是通用API,为所有STM32系列提供通用和通用功能;扩展API,它为特定的族或特定的部件号提供特定的和自定义的功能。

        底层API提供寄存器对于级别的底层API有更好的优化,但是的可移植性不高。他们需要对MCU和外围设备规范有深入的了解。该LL驱动程序被设计为提供一个快速轻量级的面向专家层比HAL更接近硬件。与HAL相反,没有提供LL API

        优化的访问不是关键功能的外围设备,或许是些需要的重要软件配置或是复杂的上层堆栈(如FSMC、USB或SDMMC)。

LL驱动的特点:

  • 一组根据数据结构中指定的参数初始化外围主要特征的函数。
  • 一组函数,用于用对应于每个字段的重置值填充初始化数据结构。
  • 外围设备去初始化函数(外围设备寄存器恢复到默认值)。
  • 一组用于直接和原子寄存器访问的内联函数。
  • 完全独立于HAL,能够在独立模式下使用。

6.2 STM32CubeF1体系LEVEL 1

        这一层分为两个子层:中间件组件、基于中间件组件的示例。

6.2.1中间组件

        中间件是一组库,包括USB主机和设备库,STemWin,FreeRTOS, FatFS和LwIP。该层组件之间的水平交互是通过调用特性API直接完成的,而与底层驱动程序的垂直交互是通过库系统调用接口中实现的特定回调和静态宏完成的。例如,fatf实现磁盘I/O驱动程序访问microSD驱动器或USB海量存储类。

6.2 STM32CubeF1体系LEVEL 2

        该层由一个基于中间件服务层的全局实时和图形化演示层、底层抽象层和基于板的功能的基本外围使用应用程序组成。

6.3 STM32CubeF1 库结构(HALLL

        利用一个例子来讲,例如串口初始化,从主函数开始分析,如下面两张图所示:前者是工程目录,后者是工程的主函数。


首先是HAL_Init(),对该函数使用搜索功能发现它串口工程中Drivers/STM32F1xx_HAL_Driver目录下一个名为stm32f1xx_hal.c的文件中。如下图所示:

        第一步:配置Flash预取功能,这个功能宏在Drivers/CMSIS目录下的stm32f0xx_hal_conf.h文件中定义。主要功能是让Flash通过AHB协议执行指令存取和数据存取,它以预存取缓冲的方式,加速CPU代码的执行。

        第二步:HAL_NVIC_SetPriorityGrouping()配置中断优先级,该函数在目录Drivers/CMSIS下的stm32f1xx_hal_cortex.c文件中。

第三步:HAL_InitTick()使用systick作为时间基源,并配置1ms tick (Reset后的默认时钟为HSI)

        第四步:HAL_MspInit(void)初始化底层硬件该函数在目录Application/User/Core下的stm32f1xx_hal_msp.c文件中,是一个回调函数,由用户自己编写。

          第五步:返回一个结果,告诉main函数是否初始化成功。

  1. SystemClock_Config(void)函数,初始化系统时钟,函数在目录Drivers/CMSIS下的stm32f1xx_hal_cortex.c文件中。
  2. MX_GPIO_Init()函数,初始化所有配置的外设。
  3. MX_USART1_UART_Init()该函数由用户自己设置,是对串口的初始化。
  4. 该函数是为RX接收数据处理的函数。

6.3.1 工程目录

        (1)Application/MDK-ARM目录,其中只有一个名为startup_ stm32f103x6.s的文件,startup_stm32f103x6.s是工程的启动文件,以汇编语言编写其作用是:

          初始化堆栈指针 SP;初始化程序计数器指针 PC;设置堆、栈的大小;

设置异常向量表的入口地址;配置外部 SRAM作为数据存储器(这个由用户配置,一般的开发板可没有外部 SRAM);设置 C库的分支入口__main(最终用来调用 main函数)。虽然在上文分析函数时没有提到,但它显而易见时程序运行的根本。

        (2)Drivers/STM32F1xx_HAL_Driver目录,里面有大量的类似stm32f1xx_hal _gpio_ex.c、stm32f1xx_hal_gpio.c、stm32f1xx_hal_uart.c等等,这里面存放的是一些驱动文件,串口驱动,SPI驱动等等。控制各个外设的引脚输入输出,可以说是汽车的发动机。

        (3)Application/User/Core目录,其中有一个名为stm32f1xx_ hal_msp.c 的文件,这个文件的作用就是根据用户所提供的具体的 MCU 型号以及硬件配置,对 HAL 库进行初始化设置操作。 所以这个文件是就 HAL 库与 MCU 结合的纽带。还有名为stm32f1xx_ it.c的文件,是存放工程中所有中断函数的地方,也是用户自定义,其他main.c、gpio.c等等都是用户自己配置外设、接口而自定义的文件。

        (4)Drivers/CMSIS目录中存放了一个名为system_stm32f1xx.c的文件,由上文可见这个文件提供了两个函数以及一个全局变量以便被从用户程序调用

系统初始化SystemInit();还有系统滴答初始化,变量SystemCoreClock variable的设置。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/280921
推荐阅读
相关标签
  

闽ICP备14008679号