ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

STM32驱动LCD1602与TFT彩屏:从GPIO模拟到SPI+DMA的完整实践

STM32驱动LCD1602与TFT彩屏:从GPIO模拟到SPI+DMA的完整实践 简介STM32F103C8T6配套程序源码是一份以LCD1602字符液晶显示为核心的嵌入式开发例程附带C/C环境下的彩屏驱动思路适合刚接触STM32的入门者也适合需要复用显示代码的进阶开发者。压缩包共121个文件以29个h头文件、28个c源文件为主另含uvproj工程文件、hex/axf编译结果、备份调试文件及辅助配置整体仅1.3MB结构紧凑、便于快速定位。目前已有2671人学习下载具备较高的参考热度。源码覆盖GPIO推挽输出配置、RS/E/D0-D7控制时序、液晶初始化序列、字符编码映射等LCD1602驱动全流程彩屏部分则涉及SPI/I2C接口选择、帧缓冲组织与DMA刷新可帮助学习者系统建立显示类外设的开发框架。配套工程可直接打开编译烧录适合按步骤对照学习能有效缩短从原理到落地的调试周期。1. 一块蓝板子从字符屏到彩屏的完整驱动链路常见做法拿到STM32F103C8T6最小系统板第一步不外乎点个灯、跑个串口再往前就是驱动屏。LCD1602是字符屏里的入门钉子户TFT彩屏则是图形界面的第一个台阶。标题里的这套配套源码本质上是把两种屏幕的驱动路径都铺好了底层用C操作寄存器与管脚时序应用层用C封装成可复用的类。适合刚踩进嵌入式门槛、手上有蓝板子但不知道代码怎么组织的开发者也适合想从裸机过渡到FreeRTOS的人——两个屏都跑通之后任务的划分、锁的保护、中断的归属就都有载体可以练了。这套代码最值得拆的不是“能显示”而是C与C如何在同一工程里分工驱动层必须贴着硬件、用C表达时序和寄存器操作应用层则可以用C的类把“清屏、写字符串、画矩形”这些动作收拢。本文按显示驱动的最小原理、LCD1602的GPIO时序实现、TFT彩屏的SPI与DMA路径、工程组织与FreeRTOS整合、最后是调试技巧的顺序展开每一段都带可抄的参数表和代码。2. LCD1602驱动GPIO模拟时序与HD44780协议2.1 为什么LCD1602还在用 GPIO 模拟而不是走总线LCD1602内部的HD44780控制器是1970年代末的设计接口是8位或4位并行数据总线加RS寄存器选择、RW读/写、E使能三条控制线。STM32F103C8T6没有专门的LCD并行总线外设最可靠的办法就是拿GPIO去模拟时序。这也是配套源码里最底层的部分——它不依赖任何库函数直接操作GPIO的ODR和IDR寄存器。模拟时序的原理并不复杂把数据放到并行引脚上拉高E保持一个最小宽度再拉低E。芯片在E的下降沿锁存数据。难点在于HD44780对最小脉宽有硬性要求E的高电平持续时间不能低于450nsVDD5V时而STM32F103C8T6跑在72MHz一条普通IO操作大约十几纳秒如果代码不做点空转一个周期会压到几百纳秒以内部分LCD模块会偶发漏字。#define LCD_D4_PORT GPIOB #define LCD_D4_PIN GPIO_Pin_0 #define LCD_D5_PIN GPIO_Pin_1 #define LCD_D6_PIN GPIO_Pin_10 #define LCD_D7_PIN GPIO_Pin_11 #define LCD_RS_PIN GPIO_Pin_12 #define LCD_E_PIN GPIO_Pin_13 void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin LCD_D4_PIN | LCD_D5_PIN | LCD_D6_PIN | LCD_D7_PIN | LCD_RS_PIN | LCD_E_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); }逻辑说明这是4位模式的GPIO初始化只用PB0、PB1、PB10、PB11做数据PB12做RSPB13做E。为什么选4位模式因为8位模式要占8根数据线加3根控制线共11个引脚F103C8T6虽然引脚够但留给其他外设的空间就少了。4位模式只需7个引脚代价是每次传输要拆成高4位、低4位两次发送速度降一半——对于1602这种刷新率只有几十赫兹的字符屏完全够用。参数方面GPIO_Mode_Out_PP是推挽输出GPIO_Speed_50MHz保证翻转速率足够两条数据线复用了PB0、PB1这两个默认的JTAG引脚如果下载调试异常方案是换到PB3~PB7或者直接在代码里关掉JTAG复用。2.2 写命令与写数据的时序实现HD44780的两条核心操作是写命令和写数据。区别只在于RS的电平RS0写命令清屏、光标归位、设置模式RS1写数据要显示的字符ASCII码。写命令/数据的关键步骤一致先把RS拉到目标电平把数据送到D4~D7然后拉高E延时拉低E。旁边配一个RW引脚永远接低让它始终处于写状态省掉读忙标志的逻辑。void LCD_WriteNibble(uint8_t nibble) { GPIO_WriteBit(LCD_D4_PORT, LCD_D4_PIN, (nibble 0) 0x01); GPIO_WriteBit(LCD_D4_PORT, LCD_D5_PIN, (nibble 1) 0x01); GPIO_WriteBit(LCD_D4_PORT, LCD_D6_PIN, (nibble 2) 0x01); GPIO_WriteBit(LCD_D4_PORT, LCD_D7_PIN, (nibble 3) 0x01); GPIO_SetBits(LCD_D4_PORT, LCD_E_PIN); delay_us(1); GPIO_ResetBits(LCD_D4_PORT, LCD_E_PIN); } void LCD_WriteByte(uint8_t rs_level, uint8_t data) { GPIO_WriteBit(LCD_D4_PORT, LCD_RS_PIN, rs_level); LCD_WriteNibble(data 4); LCD_WriteNibble(data 0x0F); }逻辑说明LCD_WriteNibble先把4位数据摆到数据引脚上然后拉高E并延时1微秒最后拉低E。这个1微秒的延时远大于HD44780要求的450ns最小高电平脉宽多出来的部分是给模块内部电平转换电路留的余量特别是当LCD模块是5V供电而STM32是3.3V时电平转换芯片会引入额外延迟。LCD_WriteByte先写高4位再写低4位完成一个字节传输。rs_level参数为0表示写命令为1表示写数据这个参数控制RS引脚的电平状态。延时函数delay_us(1)用SysTick实现注意如果使用FreeRTOS这里要换成vTaskDelay或者关掉调度器否则SysTick中断优先级低于LCD时序时会出现毛刺。2.3 LCD1602初始化序列的执行顺序HD44780的初始化是一个多步序列顺序不能颠倒。上电后芯片可能处于8位或4位未知状态第一步要发三次0x03让芯片确认进入4位模式再发0x02正式切到4位模式之后才能设置显示参数。配套源码里的初始化序列是标准做法完整复制即可用但每步之间的延时参数值得留意。void LCD_Init(void) { delay_ms(50); LCD_WriteNibble(0x03); delay_ms(5); LCD_WriteNibble(0x03); delay_us(150); LCD_WriteNibble(0x03); delay_ms(5); LCD_WriteNibble(0x02); // 进入4位模式 LCD_WriteByte(0, 0x28); // 2行、5x7点阵、4位模式 LCD_WriteByte(0, 0x0C); // 显示开、光标关、闪烁关 LCD_WriteByte(0, 0x06); // 写入后地址自动加1 LCD_WriteByte(0, 0x01); // 清屏 delay_ms(5); }初始化参数表命令二进制作用备注0x280010 10004位模式、2行、5x7点阵必须在0x02之后发送0x0C0000 1100显示开光标关闪烁关改成0x0F可显示光标0x060000 0110写入后地址自动加1整体不移动适合连续字符串输出0x010000 0001清屏光标回原点清屏后需等待1.52ms逻辑说明0x28的每一位都有实际对应关系——bit5是4位/8位模式选择0为4位bit4是行数选择1为2行bit3是字形大小0为5x8。初始化序列中前三次发送0x03是HD44780规定必须存在的“唤醒”步骤跳过会导致屏幕显示乱码或者完全不亮。上电后的延时50毫秒是给LCD模块内部的DC-DC升压电路稳定电压用的如果用5V供电、模块自带负压生成这个时间可缩短到20毫秒。清屏命令的5毫秒延时不是拍脑袋写的HD44780数据手册上清屏指令的典型执行时间是1.52毫秒这里给3倍余量。3. TFT彩屏驱动SPI通信、初始化序列与DMA优化3.1 从LCD1602到TFT彩屏的架构变化LCD1602只能显示ASCII字符和少量日本假名且需要手动维护坐标而TFT彩屏以像素为单位刷新。ST7735和ILI9341是入门彩屏最常见的两种控制芯片ST7735常见于1.8寸128x160的小屏ILI9341则对应2.8寸和3.2寸的320x240大屏。两者的驱动接口都是SPI数据位宽等于像素色深——16位RGB565时一个像素就是两字节。这套配套源码最值得关注的设计是把LCD1602的GPIO模拟思路直接切到了SPI外设上。STM32F103C8T6的SPI1支持18Mbit/s的时钟比GPIO模拟快一个数量级根本原因是SPI外设的移位寄存器在硬件层面工作CPU只需往DR寄存器里写数据就能触发发送不需要逐位翻转引脚。结构上TFT屏有5条控制线SCLK时钟、MOSI主出从入、CS片选、DC数据/命令、RST复位。void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOA, GPIO_InitStructure); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }逻辑说明PA5SCLK和PA7MOSI配置为复用推挽交给SPI1外设控制PA4CS和PA6DC配置为普通输出由代码手动控制。SPI_Direction_2Lines_FullDuplex意味着同时使用MOSI和MISO但很多TFT屏的MISO引脚悬空或者没有引出实际只用到了发送方向。SPI_CPOL_Low和SPI_CPHA_1Edge组合成SPI Mode 0这是ST7735和ILI9341数据手册里标注的时序要求SPI模式不匹配的典型症状是显示花屏或者颜色错乱。SPI_BaudRatePrescaler_4把72MHz的APB2时钟降到18MHz这是ST7735能接受的极限时钟频率。如果配到2分频36MHz一部分屏会丢像素尤其是供电不稳时。3.2 彩屏初始化序列的“三板斧”TFT彩屏的驱动比LCD1602复杂在于上电后芯片默认处于Sleep模式要经过一串寄存器配置序列才能正常显示。这个序列通常有几十条指令源码里提供的初始化序列可以直接使用但需要理解三条最关键的指令。void LCD_Init(void) { LCD_Reset(); LCD_WriteCmd(0x11); // 退出睡眠模式 delay_ms(120); LCD_WriteCmd(0x36); // 设置扫描方向和RGB顺序 LCD_WriteData(0x00); LCD_WriteCmd(0x3A); // 设置像素格式 LCD_WriteData(0x05); // 16位色 LCD_WriteCmd(0x29); // 开启显示 delay_ms(20); } void LCD_Reset(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_3); delay_ms(20); GPIO_SetBits(GPIOA, GPIO_Pin_3); delay_ms(120); }逻辑说明0x11退出睡眠模式是最关键的一步上电后不执行它屏幕就是黑的这个指令后的120毫秒延时是芯片内部震荡器启动时间缩短到50毫秒以下大概率出现显示异常。0x36的bit6和bit7控制扫描方向0x00对应默认的从左到右、从上到下如果想翻转屏幕改成0xC0。0x3A设置像素格式0x05是16位RGB5650x06是18位RGB666注意如果选择18位后续写像素时的数据组织和RGB565完全不同。LCD_Reset在硬件复位引脚上拉低至少20毫秒再拉高这是芯片数据手册规定的复位脉宽下限。3.3 像素写入与DMA搬运彩屏的日常操作中写像素占了大头。一个320x240的ILI9341屏幕刷一帧要写入3202402 153600字节。如果用阻塞式SPI发送按18MHz时钟、每字节8个时钟周期算理论极限是每秒2.25MB但每发一个字节都要判断SPI发送完成标志实际吞吐量会掉一半以上。解决办法是用DMA搬数据。void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { LCD_SetWindow(x, y, x, y); LCD_CS_LOW(); LCD_WriteCmd(0x2C); LCD_DC_HIGH(); SPI_I2S_SendData(SPI1, color 8); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, color 0xFF); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); LCD_CS_HIGH(); } void LCD_DrawBitmap_DMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *bitmap) { uint32_t pixel_count (uint32_t)w * h; LCD_SetWindow(x, y, x w - 1, y h - 1); LCD_CS_LOW(); LCD_WriteCmd(0x2C); LCD_DC_HIGH(); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); DMA_Cmd(DMA1_Channel3, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel3, pixel_count * 2); DMA_Cmd(DMA1_Channel3, ENABLE); }逻辑说明单点写像素时先设置窗口地址然后发送0x2C内存写命令最后分两次发送高字节和低字节。注意RGB565的字节序——先发高字节这里不是随便定的取决于芯片内部的像素格式顺序反了会导致红蓝颜色互换。LCD_DrawBitmap_DMA把整个位图交给DMA1通道3搬运DMA在内存和SPI外设之间直接传输不经过CPUCPU可以腾出来做其他事情。pixel_count * 2是因为一个像素两个字节。使用时需要先初始化DMA1 Channel3源地址是bitmap指针目的地址是SPI1-DR。DMA传输完成后标志位在DMA ISR里处理。SPI与GPIO模拟两种方案对比这个挂在第2章和第3章之间衔接更自然对比项LCD1602 GPIO模拟TFT彩屏 SPI数据宽度4位并行串行8位控制线数量6~7根5根含CS、DC刷新一屏~50ms16字符320x240全屏约68msCPU占用全程占用单点低DMA下近零适用场景字符菜单、调试信息图形界面、图像显示4. C与C的工程组织给裸机代码做分层的正确姿势4.1 为什么LED秒闪的代码用CUI逻辑用C标题里写了C和C说明这套配套源码不是纯C裸机工程而是C与C混编。常见做法的边界划分是硬件驱动层用C文件实现对外暴露C接口函数上层应用用C的类封装把多个驱动组合成带状态的对象。分层的基本原理是——C能精确控制内存布局适合处理寄存器映射、中断回调、硬件时序C的类、继承、重载能把UI逻辑写得可维护尤其是多个屏幕之间切换状态机时用C做状态机得写一堆switch-case嵌套用C直接抽象成状态类。class LCD1602 { public: LCD1602(GPIO_TypeDef *port, uint16_t rs, uint16_t e, uint16_t d4, uint16_t d5, uint16_t d6, uint16_t d7); void Init(); void Print(const char *str, uint8_t row, uint8_t col); void Clear(); private: void WriteCmd(uint8_t cmd); void WriteData(uint8_t data); void WriteNibble(uint8_t nibble); TIM_TypeDef *delay_timer; GPIO_TypeDef *gpio_port; uint16_t rs_pin, e_pin; uint16_t d4_pin, d5_pin, d6_pin, d7_pin; };逻辑说明这个类的成员变量保存了LCD的GPIO配置构造函数传入的端口和引脚地址在初始化时赋值给类内私有成员。WriteCmd和WriteData是私有方法对应上一章的C接口Print是公开方法内部按行列地址换算成HD44780的DDRAM地址再逐个写字符。C类的好处是复用性——同一套代码实例化两个对象时只需要传入不同的GPIO配置不会像C版本那样全局变量互相污染。混编工程里C文件如system_stm32f10x.c、stm32f10x_gpio.c不能直接编译成C必须用extern C包裹头文件否则链接阶段会报未定义符号错误。4.2 FreeRTOS移植与C析构函数的坑配套源码如果放到FreeRTOS环境下有C和C的内存管理冲突问题。FreeRTOS的pvPortMalloc默认实现不提供free函数而C的new/delete依赖标准库的堆管理。如果有C对象在动态创建和销毁常见做法是重载类的operator new和operator delete让它们走FreeRTOS的堆分配器#include FreeRTOS.h #include task.h void* operator new(size_t size) { return pvPortMalloc(size); } void operator delete(void* ptr) { vPortFree(ptr); } void vTaskLCDTask(void *pvParameters) { LCD1602 *lcd new LCD1602(GPIOB, GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_10, GPIO_Pin_11); lcd-Init(); for (;;) { lcd-Print(FreeRTOS OK!, 0, 0); vTaskDelay(pdMS_TO_TICKS(1000)); } }逻辑说明全局重载operator new和operator delete后任务里new LCD1602的分配动作走FreeRTOS的pvPortMalloc好处是内存申请进入临界区避免两个任务同时new对象时数据竞争。注意这里的delete不需要——FreeRTOS任务函数结束后任务本身会被删除对象留在堆上没问题。另一个关键点是任务栈大小。C对象构造时若有虚函数表会在堆上额外分配vptr这不算栈开销但如果任务栈默认给128字512字节加上LCD操作时层层调用很容易溢出。F103C8T6被视为国产替代热门芯片的原因是同封装下有20KB SRAM任务栈可以给到256字甚至512字一个LCD显示任务实际占用约200字栈空间。4.3 Keil/IAR双环境的工程文件组织源码里应同时保留KeilMDK-ARM和IAR两种工程文件。两个IDE处理C混编的方式有细微差别。Keil的AC5编译器需要手动把源文件扩展名改成.cpp并且在Options - C/C里勾选--cpp11或--cppIAR则在Project - Options - C/C Compiler里把语言模式切换到C。两种环境下都建议把C文件的头文件包含路径用extern C包裹一次#ifdef __cplusplus extern C { #endif #include lcd1602.h #include tft_st7735.h #include spi_dma.h #ifdef __cplusplus } #endif逻辑说明这段代码放在C头文件的末尾或者C文件包含它们的地方。__cplusplus宏在C编译器下自动定义C编译器不定义所以C文件包含时不变C文件包含时自动套上extern C确保链接器按C符号规则找函数名。这样设计的目的是让C代码能直接调用LCD_Init()这种C函数而不用在C源文件里每个函数都重写一遍声明。工程文件路径上Keil的uvprojx和IAR的ewp放在不同目录避免IDE互相覆盖配置。F103C8T6的Flash只有64KBC的标准库如果全量链接会超过这个数解决方案是使用--no_exceptions或--no_rtti关闭C的异常和运行时类型识别——嵌入式上的C一般不启用这两个特性。// lcd1602.h 核心C接口 void LCD_Init(void); void LCD_Clear(void); void LCD_PrintChar(char c); void LCD_PrintString(char *str); void LCD_SetCursor(uint8_t row, uint8_t col); void LCD1602_WriteCmd(uint8_t cmd); void LCD1602_WriteData(uint8_t data);逻辑说明C接口的设计原则是只暴露操作必需的最小集合——LCD_Init做初始化LCD_Clear清屏LCD_PrintString输出字符串LCD_SetCursor设置光标位置。LCD1602_WriteCmd和LCD1602_WriteData虽然是对外的但主要给C类内部调用。每个函数都加LCD1602_前缀防止多个驱动文件的符号在链接时冲突。5. LCD1602与TFT彩屏共存源码复用的边界与调试手法5.1 两个屏同时接的系统IO资源怎么分配冲突在哪当LCD1602和TFT彩屏同时接入F103C8T6最小系统板时引脚分配的优先级是先SPI彩屏的时钟线不能换、再LCD1602的数据线、最后才是控制线。标准接法是SPI1用PA5/PA6/PA7LCD1602用PB0~PB13缺口处的一组但PB2BOOT1默认是启动配置脚不能拿来当普通IO所以LCD1602的6根线要避开PB2。如果发现TFT屏幕花屏、LCD1602无显示同时发生先查两个屏的CS片选是否接有独立GPIO是否存在共用引脚导致片选信号交叉——这个问题在一个屏单独跑的时候永远不会暴露。5.2 逻辑分析仪抓时序站位比猜EFASTE脉冲靠谱LCD1602驱动里最隐蔽的坑是E脉冲的宽度不够。逻辑分析仪接在E引脚上抓一条写命令波形查E的高电平时间是否大于450ns。如果小于这个值很可能是编译器O3优化把delay_us里的空循环删掉了——一个没有副作用的延时函数优化掉了等于没有延时。对策是把延时函数声明为volatile或者用__NOP()指令填满循环体这也是为什么源码里要留一个看起来多余的for(i0; i10; i);而不是直接调官方库函数void delay_us(uint32_t us) { volatile uint32_t i; for (i 0; i us * 8; i) { __NOP(); } }参数说明us * 8不是随手写的。F103C8T6主频72MHz时一条__NOP()加一条循环分支判断大约需要8个时钟周期所以延时时间约等于8 * 循环次数 / 72MHz单位正好是微秒。如果用volatile uint32_t声明循环变量编译器不会把循环优化掉。这个方法不如SysTick精确但胜在轻量适合在GPIO模拟时序里用。5.3 电平不匹配5V的LCD1602和3.3V的STM32怎么共存LCD1602的典型供电是5V而STM32F103C8T6的GPIO输出是3.3V。直接把3.3V信号接到5V的LCD模块IO上逻辑高电平阈值是2.0V3.3V确实高于阈值大部分模块能正常工作。但反向问题更大LCD模块的上拉电阻可能会往STM32的GPIO灌入超过数据手册限值的电流长期跑下来PA引脚有可能会损坏。源码里的标准配置是LCD1602的VDD接5V而数据线和控制线串330欧姆限流电阻再连到STM32。TFT彩屏则完全没有这个问题ST7735和ILI9341都支持3.3V供电和3.3V逻辑电平直接共用电源轨即可。5.4 用OLED替代彩屏时的驱动节省量验证初始化的抽象正确性如果这套源码已经跑通LCD1602和TFT彩屏再换一个SSD1306的OLED时会发现接口层可以复用——OLED是I2C接口但同样有命令/数据区分SPI版本的TFT驱动代码里把写命令和写数据抽成两个函数以后I2C驱动只需实现同样的WriteCmd和WriteData函数签名上层OLED_ShowString、OLED_DrawLine这些API就能原封不动搬过来。验证这套代码抽象质量的快速方法是只改驱动层不动应用层调用代码看看还能不能显示完整画面。如果改完一处就要动上层三处说明分层边界划错了。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表