超级精简系列之十五:超级精简的IO模拟UART接收

原创 嵌入式Lee 2024-01-13 08:03

前言

前面一篇我们实现了IO模拟UART发送,我们继续来实现UART接收。对于接收底层资源需要一个输入IO且其可配置位下降沿中断,和一个定时器即可。

实现过程

有了发送的实现,我们依葫芦画瓢,按照发送的模式进行实现,只是一个是发送一个是接收。状态机实现的过程是类似的。

启动接收即使能RX引脚的下降沿中断,用于检测起始位。在中断回调中启动定时器延迟1.5个位宽即可采样第一个bit数据。定时器中断回调中进行状态机处理,配置定时器每隔一个bit采样一次,继续处理后续位。直到处理完所有位,然后调用接收回调,并继续下一个循环。

数据结构

和发送类似,只是TX输出引脚改为RX接收,并且需要提供下降沿中断配置接口。

整个数据结构如下

typedef uint8_t    (*io_uart_rx_rd_pf)(void);                                  /**< RX读接口                                            */typedef void    (*io_uart_rx_set_eint_pf)(uint8_t enable, void(*)(void*));         /**< RX接口设置下降沿回调                     */typedef void    (*io_uart_rx_time_set_period_pf)(uint32_t t, void(*)(void*));      /**< 定时器设置周期接口,单位波特率     */typedef void    (*io_uart_rx_time_ctrl_pf)(uint8_t onoff);                         /**< 定时器启停控制接口                         */typedef void    (*io_uart_rx_cb_pf)(uint8_t val);      /**< 接收到字节回调                            */typedef void    (*io_uart_rx_init_pf)(void);       /**< 底层接口初始化                         */typedef void    (*io_uart_rx_deinit_pf)(void);     /**< 底层接口解除初始化                   */
/** * \struct io_uart_rx_e * 接收状态枚举 */typedef enum{ IO_UART_RX_STATE_DIS = 0, /**< 停止状态 */ IO_UART_RX_STATE_IDLE = 1, /**< 空闲等待RX下降沿阶段 */ IO_UART_RX_STATE_START = 2, /**< 起始位阶段 */ IO_UART_RX_STATE_DATA = 3, /**< 数据位阶段 */ IO_UART_RX_STATE_PARITY = 4, /**< 校验位阶段 */ IO_UART_RX_STATE_STOP = 5, /**< 停止位阶段 */} io_uart_rx_e;
/** * \struct io_uart_rx_patity_e * 校验枚举 */typedef enum{ IO_UART_RX_PARITY_NONE = 0, /**< 无校验 */ IO_UART_RX_PARITY_ODD = 1, /**< 奇校验 */ IO_UART_RX_PARITY_EVEN = 2, /**< 偶校验 */ IO_UART_RX_PARITY_0 = 3, /**< 固定0校验 */ IO_UART_RX_PARITY_1 = 4, /**< 固定1校验 */} io_uart_rx_patity_e;
/** * \struct io_uart_rx_dev_st * 接收状态机结构体 */typedef struct { io_uart_rx_e state; /**< 接收主状态 */ uint8_t state_s; /**< 接收子状态 */ uint8_t data; /**< 待发送的字节数据 */ uint8_t cal_parity; /**< 计算的校验位 */ io_uart_rx_patity_e parity; /**< 校验 */ uint8_t stop; /**< 停止位 */ uint8_t data_len; /**< 数据长 */ uint32_t baud; /**< 波特率 */ io_uart_rx_rd_pf rx_rd; /**< RD读接口 */ io_uart_rx_set_eint_pf set_int; /**< 设置RX下降沿中断接口 */ io_uart_rx_init_pf init; /**< 接口初始化 */ io_uart_rx_deinit_pf deinit; /**< 接口解除初始化 */ io_uart_rx_time_set_period_pf time_set_period; /**< 定时器设置周期接口,单位uS */ io_uart_rx_time_ctrl_pf time_ctrl; /**< 定时器启停控制 */ io_uart_rx_cb_pf rx_cb; /**< 接收到字节回调 */} io_uart_rx_dev_st;

接口

只需要初始化和解除初始化接口

再加一个控制器启停的接口。

/** * \fn io_uart_rx_ctrl * 启动或停止接收 * \param[in] dev \ref io_uart_rx_dev_st * \param[in] onoff 使能或者停止接收 * \retval 0 成功 * \retval 其他值 失败 */int io_uart_rx_ctrl(io_uart_rx_dev_st* dev, uint8_t onoff);
/** * \fn io_uart_rx_init * 初始化 * \param[in] dev \ref io_uart_rx_dev_st*/void io_uart_rx_init(io_uart_rx_dev_st* dev);
/** * \fn io_uart_rx_deinit * 解除初始化 * \param[in] dev \ref io_uart_rx_dev_st*/void io_uart_rx_deinit(io_uart_rx_dev_st* dev);

实现

初始化解除初始化

void io_uart_rx_init(io_uart_rx_dev_st* dev){    if(dev == 0)    {        return;    }    if(dev->init == 0)    {        return;    }    dev->init();}
void io_uart_rx_deinit(io_uart_rx_dev_st* dev){ if(dev == 0) { return; } if(dev->deinit == 0) { return; } dev->deinit();}

启动接收,即使能下降沿中检测起始位,检测到下降沿回调io_uart_rx_start并停止外部中断检测,交给定时采样处理。

static void io_uart_rx_start(void* p){    io_uart_rx_dev_st* dev = (io_uart_rx_dev_st*)p;    /* 检测到下降沿中断时调用 */    dev->set_int(0,0);                                 /* 关闭外部中间检测 */    dev->time_set_period(dev->baud*2/3,io_uart_rx_handle); /* 设置1.5位宽的周期,周期到了回调io_uart_tx_handle 采样第一个数据 */    dev->time_ctrl(1);    dev->state = IO_UART_RX_STATE_DATA;                /* 更新状态到数据阶段,直接延迟跳过1个周期的开始位,并且延迟0.5个周期到位中间采样  */    dev->cal_parity = 0;                               /*  初始化信息         */    dev->state_s = 0;    dev->data = 0;}
int io_uart_rx_ctrl(io_uart_rx_dev_st* dev, uint8_t onoff){ if(dev == 0) { return -1; } if(onoff != 0) { dev->set_int(1,io_uart_rx_start); /* 使能外部中间检测 */ dev->state = IO_UART_RX_STATE_IDLE; /* 更新状态 */ } else { dev->set_int(0,0); /* 关闭外部中间检测 */ dev->time_ctrl(0); /* 停止定时器 */ dev->state = IO_UART_RX_STATE_DIS; /* 设置停止状态 */ } return 0;}

状态机处理

这个函数是核心,对照着注释和字节的阶段即可,不再赘述

和发送流程差不多

void io_uart_rx_handle(void* p){    uint8_t bit = 0;    uint8_t parity = 0;    io_uart_rx_dev_st* dev = (io_uart_rx_dev_st*)p;    /* 参数检查 */    if(dev == 0)    {      return;    }    switch(dev->state)    {      case IO_UART_RX_STATE_DATA:        dev->time_set_period(dev->baud,io_uart_rx_handle); /* 设置一个位宽的周期,周期到了回调io_uart_rx_handle */        dev->time_ctrl(1);
/* 更新一位,低位在前,所以先放在高位然后往低位移动 */ bit = dev->rx_rd(); dev->data >>= 1; /* 这里要先移动后更新数据,因为本次最后更新的是有效数据 */ if(bit != 0) { dev->data |= 0x80; } dev->cal_parity ^= bit; dev->state_s++; if(dev->state_s >= dev->data_len) /* state_s先加,因为进来一次就是收到一个bit数据 */ { if(dev->parity == IO_UART_RX_PARITY_NONE) { /* 这里就开始等待停止位 */ dev->state = IO_UART_RX_STATE_STOP; dev->state_s = 0; /**< 停止位要用到state_s计数停止位数需要清0 */ } else { /* 这里就需要开始等待校验位 */ dev->state = IO_UART_RX_STATE_PARITY; } } else { /* 不改变状态继续下一位 */ } break; case IO_UART_RX_STATE_PARITY: /* 校验位时间到,进入停止位阶段 这里就需要开始发送停止位 */ dev->time_set_period(dev->baud,io_uart_rx_handle); /* 设置一个位宽的周期,周期到了回调io_uart_rx_handle */ dev->time_ctrl(1);
switch (dev->parity) { case IO_UART_RX_PARITY_ODD: parity = dev->cal_parity ^ 0x01; break; case IO_UART_RX_PARITY_EVEN: parity = dev->cal_parity; break; case IO_UART_RX_PARITY_0: parity = 0; break; case IO_UART_RX_PARITY_1: parity = 1; break; default: break; }
bit = dev->rx_rd(); if(parity != bit) { /* 校验错误处理 */ }
dev->state = IO_UART_RX_STATE_STOP; dev->state_s = 0; /**< 停止位要用到sstate_s计数停止位数需要清0 */ break; case IO_UART_RX_STATE_STOP: dev->state_s++; /* 进入这里实际就已经接收了一个停止位了,所以是先加 */ if(dev->state_s >= dev->stop) { /* 完成一个字节的接收 */ dev->rx_cb(dev->data); dev->set_int(1,io_uart_rx_start); /* 继续下一个字节接收处理 */ dev->state = IO_UART_RX_STATE_IDLE; } else { dev->time_set_period(dev->baud,io_uart_rx_handle); /* 设置一个位宽的周期,周期到了回调io_uart_tx_handle */ /* 不改变状态继续下个停止位 */ dev->time_ctrl(1); } break; }}

移植

移植即实现数据结构中的接口依赖,即下面的接口函数

static io_uart_rx_dev_st uart_rx_dev={    .parity = IO_UART_RX_PARITY_ODD,    .stop = 2,    .data_len=8,    .baud = 115200,
.rx_rd = port_rx_rd, .init = port_rx_init, .deinit = port_rx_deinit, .time_set_period = port_time1_set_period, .rx_cb = rx_done_cb, .time_ctrl = port_timer1_ctrl, .set_int = port_set_int,};

这里基于HPM53xx开发板实现,代码如下

Uart_rx_port.c

#include #include #include "rtt_board.h"#include #include #include "io_uart_rx.h"
static void port_timer1_cb(void);static void port_time1_set_period(uint32_t period, void(*cb)(void*));static void port_set_int(uint8_t enable, void(*)(void*));static void port_rx_init(void);static void port_rx_deinit(void);static uint8_t port_rx_rd(void);static void port_timer1_ctrl(uint8_t onoff);static void(*t1_cb)(void*) = 0;static void(*int_cb)(void*) = 0;static void rx_done_cb(uint8_t val);static void (*rx_cb)(uint8_t val) = 0;static io_uart_rx_dev_st uart_rx_dev={ .parity = IO_UART_RX_PARITY_ODD, .stop = 2, .data_len=8, .baud = 115200,
.rx_rd = port_rx_rd, .init = port_rx_init, .deinit = port_rx_deinit, .time_set_period = port_time1_set_period, .rx_cb = rx_done_cb, .time_ctrl = port_timer1_ctrl, .set_int = port_set_int,};

static void port_set_int(uint8_t enable, void(*cb)(void*)){ int_cb = cb; if(enable != 0) { HPM_GPIO0->PL[GPIO_DI_GPIOA].SET = (1u << 26); /* 低电平或者下降沿中断 */ HPM_GPIO0->TP[GPIO_DI_GPIOA].SET = (1u << 26); /* 边沿中断 */ HPM_GPIO0->IF[GPIO_DI_GPIOA].VALUE = (1u << 26); /* 写1清除标志 */ HPM_GPIO0->IE[GPIO_DI_GPIOA].SET = (1u << 26); /* 写1使能中断 */ } else { HPM_GPIO0->IE[GPIO_DI_GPIOA].CLEAR = (1u << 26); /* 写1不使能中断 */ }}

static void port_timer1_cb(void){ HPM_GPTMR1->SR = 0x1; /* 写1清除标志 */ if(t1_cb != 0) { t1_cb(&uart_rx_dev); }}
static void port_int_cb(void){ HPM_GPIO0->IF[GPIO_DI_GPIOA].VALUE = (1u << 26); /* 写1清除标志 */ if(int_cb) { int_cb(&uart_rx_dev); }}

static void port_time1_set_period(uint32_t baud, void(*cb)(void*)){ t1_cb = cb; HPM_GPTMR1->CHANNEL[0].CR &= ~(1u << 10); /* 停止 */ HPM_GPTMR1->CHANNEL[0].CR |= (1u << 14); /* 复位 */ HPM_GPTMR1->CHANNEL[0].CR &= ~(1u << 14); /* 释放复位 */
HPM_GPTMR1->CHANNEL[0].CR |= (1u << 3); /* 调试时定时器停止 */
HPM_GPTMR1->CHANNEL[0].RLD = (81000000/baud); /* 重载值 计数器从0开始运行到重载值恢复到0重新计数 1nS对应1/10个计数值 由于软件执行需要时间所以这里的值要小一点 */
HPM_GPTMR1->SR = 0x1; /* 写1清除标志 */
HPM_GPTMR1->IRQEN |= 0x01; /* 通道0重载中断使能 */
}
static void port_rx_init(void){ HPM_IOC->PAD[IOC_PAD_PA26].FUNC_CTL = IOC_PA26_FUNC_CTL_GPIO_A_26; HPM_GPIO0->OE[GPIO_DI_GPIOA].CLEAR = 1u << 26;
install_isr(IRQn_GPTMR1,(uint32_t)port_timer1_cb); intc_m_enable_irq(IRQn_GPTMR1);
install_isr(IRQn_GPIO0_A,(uint32_t)port_int_cb); intc_m_enable_irq(IRQn_GPIO0_A);}
static void port_rx_deinit(void){ //uninstall_isr(IRQn_GPTMR1); intc_m_disable_irq(IRQn_GPTMR1); HPM_GPIO0->IE[GPIO_DI_GPIOA].CLEAR = (1u << 26); /* 写1不使能中断 */}
static uint8_t port_rx_rd(void){ if((HPM_GPIO0->DI[GPIO_DI_GPIOA].VALUE & (1u << 26)) != 0) { return 1; } else { return 0; }}
static void port_timer1_ctrl(uint8_t onoff){ if(onoff) { HPM_GPTMR1->CHANNEL[0].CR |= (1u << 10); /* 使能 */ } else { HPM_GPTMR1->CHANNEL[0].CR &= ~(1u << 10); /* 停止 */ }}
static void rx_done_cb(uint8_t val){ rx_cb(val);}
void sw_uart_rx_init(void){ io_uart_rx_init(&uart_rx_dev);}
void sw_uart_rx_deinit(void){ io_uart_rx_deinit(&uart_rx_dev);}
void sw_uart_rx_start(uint8_t ctrl,void(*cb)(uint8_t val)){ rx_cb = cb; io_uart_rx_ctrl(&uart_rx_dev,ctrl);}

Uart_rx_port.h

#ifndef UART_RX_PORT_H#define UART_RX_PORT_H
#ifdef __cplusplus extern "C"{#endif
#include
void sw_uart_rx_init(void);void sw_uart_rx_deinit(void);void sw_uart_rx_start(uint8_t ctrl,void(*cb)(uint8_t val));
#ifdef __cplusplus }#endif
#endif

测试

配置参数为115200-8-ODD-2

static io_uart_tx_dev_st uart_tx_dev={    .parity = IO_UART_TX_PARITY_ODD,    .stop = 2,    .data_len=8,    .baud = 115200,
.tx_wr = port_tx_wr, .init = port_tx_init, .deinit = port_tx_deinit, .time_set_period = port_time0_set_period, .tx_cb =tx_done_cb, .time_ctrl = port_timer0_ctrl,};
static io_uart_rx_dev_st uart_rx_dev={ .parity = IO_UART_RX_PARITY_ODD, .stop = 2, .data_len=8, .baud = 115200,
.rx_rd = port_rx_rd, .init = port_rx_init, .deinit = port_rx_deinit, .time_set_period = port_time1_set_period, .rx_cb = rx_done_cb, .time_ctrl = port_timer1_ctrl, .set_int = port_set_int,};

测试代码如下,收到数据原样返回。

uint8_t str[]={0xAA,0xAA};
volatile int rx_flag = 0;void rx_cb(uint8_t val){ str[0]= val; rx_flag = 1;}void thread_entry(void *arg){ sw_uart_tx_init(); sw_uart_rx_init(); sw_uart_rx_start(1,rx_cb); while(1){ if(rx_flag) { rx_flag = 0; sw_uart_tx_byte(str[0]); } } sw_uart_tx_deinit(); sw_uart_rx_deinit();}

使用串口调试助手发送,开发板返回,看到完全正确。使用示波器产看波形也完全正确。

总结

以上超级简单的代码实现了串口数据的接收,具备良好的可移植性,这样我们就完成了IO模拟串口的全部实现。但是这还不够,以上示例代码仅作测试,并不适合实际应用。在实际应用中,我们还可以继续基于上面一篇的FIFO实现,实现基于环形缓冲区的串口收发驱动,这样才能实现高效方便应用调用的接口,见下一篇。

评论 (0)
  • 提到“质量”这两个字,我们不会忘记那些奠定基础的大师们:休哈特、戴明、朱兰、克劳士比、费根堡姆、石川馨、田口玄一……正是他们的思想和实践,构筑了现代质量管理的核心体系,也深远影响了无数企业和管理者。今天,就让我们一同致敬这些质量管理的先驱!(最近流行『吉卜力风格』AI插图,我们也来玩玩用『吉卜力风格』重绘质量大师画象)1. 休哈特:统计质量控制的奠基者沃尔特·A·休哈特,美国工程师、统计学家,被誉为“统计质量控制之父”。1924年,他提出世界上第一张控制图,并于1931年出版《产品制造质量的经济
    优思学院 2025-04-01 14:02 156浏览
  • 文/郭楚妤编辑/cc孙聪颖‍不久前,中国发展高层论坛 2025 年年会(CDF)刚刚落下帷幕。本次年会围绕 “全面释放发展动能,共促全球经济稳定增长” 这一主题,吸引了全球各界目光,众多重磅嘉宾的出席与发言成为舆论焦点。其中,韩国三星集团会长李在镕时隔两年的访华之行,更是引发广泛热议。一直以来,李在镕给外界的印象是不苟言笑。然而,在论坛开幕前一天,李在镕却意外打破固有形象。3 月 22 日,李在镕与高通公司总裁安蒙一同现身北京小米汽车工厂。小米方面极为重视此次会面,CEO 雷军亲自接待,小米副董
    华尔街科技眼 2025-04-01 19:39 240浏览
  • 文/Leon编辑/cc孙聪颖‍步入 2025 年,国家进一步加大促消费、扩内需的政策力度,家电国补政策将持续贯穿全年。这一利好举措,为行业发展注入强劲的增长动力。(详情见:2025:消费提振要靠国补还是“看不见的手”?)但与此同时,也对家电企业在战略规划、产品打造以及市场营销等多个维度,提出了更为严苛的要求。在刚刚落幕的中国家电及消费电子博览会(AWE)上,家电行业的竞争呈现出胶着的态势,各大品牌为在激烈的市场竞争中脱颖而出,纷纷加大产品研发投入,积极推出新产品,试图提升产品附加值与市场竞争力。
    华尔街科技眼 2025-04-01 19:49 231浏览
  • 退火炉,作为热处理设备的一种,广泛应用于各种金属材料的退火处理。那么,退火炉究竟是干嘛用的呢?一、退火炉的主要用途退火炉主要用于金属材料(如钢、铁、铜等)的热处理,通过退火工艺改善材料的机械性能,消除内应力和组织缺陷,提高材料的塑性和韧性。退火过程中,材料被加热到一定温度后保持一段时间,然后以适当的速度冷却,以达到改善材料性能的目的。二、退火炉的工作原理退火炉通过电热元件(如电阻丝、硅碳棒等)或燃气燃烧器加热炉膛,使炉内温度达到所需的退火温度。在退火过程中,炉内的温度、加热速度和冷却速度都可以根
    锦正茂科技 2025-04-02 10:13 98浏览
  • 职场之路并非一帆风顺,从初入职场的新人成长为团队中不可或缺的骨干,背后需要经历一系列内在的蜕变。许多人误以为只需努力工作便能顺利晋升,其实核心在于思维方式的更新。走出舒适区、打破旧有框架,正是让自己与众不同的重要法宝。在这条道路上,你不只需要扎实的技能,更需要敏锐的观察力、不断自省的精神和前瞻的格局。今天,就来聊聊那改变命运的三大思维转变,让你在职场上稳步前行。工作初期,总会遇到各式各样的难题。最初,我们习惯于围绕手头任务来制定计划,专注于眼前的目标。然而,职场的竞争从来不是单打独斗,而是团队协
    优思学院 2025-04-01 17:29 232浏览
  • 据先科电子官方信息,其产品包装标签将于2024年5月1日进行全面升级。作为电子元器件行业资讯平台,大鱼芯城为您梳理本次变更的核心内容及影响:一、标签变更核心要点标签整合与环保优化变更前:卷盘、内盒及外箱需分别粘贴2张标签(含独立环保标识)。变更后:环保标识(RoHS/HAF/PbF)整合至单张标签,减少重复贴标流程。标签尺寸调整卷盘/内盒标签:尺寸由5030mm升级至**8040mm**,信息展示更清晰。外箱标签:尺寸统一为8040mm(原7040mm),提升一致性。关键信息新增新增LOT批次编
    大鱼芯城 2025-04-01 15:02 222浏览
  • 随着汽车向智能化、场景化加速演进,智能座舱已成为人车交互的核心承载。从驾驶员注意力监测到儿童遗留检测,从乘员识别到安全带状态判断,座舱内的每一次行为都蕴含着巨大的安全与体验价值。然而,这些感知系统要在多样驾驶行为、复杂座舱布局和极端光照条件下持续稳定运行,传统的真实数据采集方式已难以支撑其开发迭代需求。智能座舱的技术演进,正由“采集驱动”转向“仿真驱动”。一、智能座舱仿真的挑战与突破图1:座舱实例图智能座舱中的AI系统,不仅需要理解驾驶员的行为和状态,还要同时感知乘员、儿童、宠物乃至环境中的潜在
    康谋 2025-04-02 10:23 158浏览
  • 引言在语音芯片设计中,输出电路的设计直接影响音频质量与系统稳定性。WT588系列语音芯片(如WT588F02B、WT588F02A/04A/08A等),因其高集成度与灵活性被广泛应用于智能设备。然而,不同型号在硬件设计上存在关键差异,尤其是DAC加功放输出电路的配置要求。本文将从硬件架构、电路设计要点及选型建议三方面,解析WT588F02B与F02A/04A/08A的核心区别,帮助开发者高效完成产品设计。一、核心硬件差异对比WT588F02B与F02A/04A/08A系列芯片均支持PWM直推喇叭
    广州唯创电子 2025-04-01 08:53 219浏览
  • 北京贞光科技有限公司作为紫光同芯授权代理商,专注于为客户提供车规级安全芯片的硬件供应与软件SDK一站式解决方案,同时配备专业技术团队,为选型及定制需求提供现场指导与支持。随着新能源汽车渗透率突破40%(中汽协2024数据),智能驾驶向L3+快速演进,车规级MCU正迎来技术范式变革。作为汽车电子系统的"神经中枢",通过AEC-Q100 Grade 1认证的MCU芯片需在-40℃~150℃极端温度下保持μs级响应精度,同时满足ISO 26262 ASIL-D功能安全要求。在集中式
    贞光科技 2025-04-02 14:50 192浏览
  • 探针本身不需要对焦。探针的工作原理是通过接触被测物体表面来传递电信号,其精度和使用效果取决于探针的材质、形状以及与检测设备的匹配度,而非对焦操作。一、探针的工作原理探针是检测设备中的重要部件,常用于电子显微镜、坐标测量机等精密仪器中。其工作原理主要是通过接触被测物体的表面,将接触点的位置信息或电信号传递给检测设备,从而实现对物体表面形貌、尺寸或电性能等参数的测量。在这个过程中,探针的精度和稳定性对测量结果具有至关重要的影响。二、探针的操作要求在使用探针进行测量时,需要确保探针与被测物体表面的良好
    锦正茂科技 2025-04-02 10:41 106浏览
  • 在智能交互设备快速发展的今天,语音芯片作为人机交互的核心组件,其性能直接影响用户体验与产品竞争力。WT588F02B-8S语音芯片,凭借其静态功耗<5μA的卓越低功耗特性,成为物联网、智能家居、工业自动化等领域的理想选择,为设备赋予“听得懂、说得清”的智能化能力。一、核心优势:低功耗与高性能的完美结合超低待机功耗WT588F02B-8S在休眠模式下待机电流仅为5μA以下,显著延长了电池供电设备的续航能力。例如,在电子锁、气体检测仪等需长期待机的场景中,用户无需频繁更换电池,降低了维护成本。灵活的
    广州唯创电子 2025-04-02 08:34 168浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦