【DIY电子作品】出门提醒设备 基于 Ai-M61-32S 汇总篇

[复制链接]
查看3493 | 回复27 | 2023-11-30 23:11:51 | 显示全部楼层 |阅读模式

本帖最后由 WT_0213 于 2023-12-7 11:45 编辑

一、简介 本项目设计初衷是为了提醒自己 出门不要忘记带东西,“身”、“手”、“钥”、“钱” 身份证,手机,钥匙,钱包等。现在好像都在线支付了。钱包都不带了。后期可以播报天气等,提醒带伞等等。本来想着使用 安信可24G雷达传感器模块人体微动感应检测模组“Rd-03” 来做的。由于手上刚好有一个"HC-SR501红外感应电子模块传感器" 模块,凑合也能用。等买的新雷达模块到了再替换。

硬件: 红外感应模块:HC-SR501 语音合成播报模块:XFS5152 开发板:Ai-M61-32S

GPIO 全称 General Purpose Input Output(通用输入 / 输出),博流系列芯片的 GPIO 外设主要有以下功能。

  • 普通输入输出带上下拉
  • 复用功能带上下拉
  • 模拟功能
  • 外部中断(上升沿、下降沿、高电平、低电平)
  • 硬件消抖
  • 驱动能力控制

bl mcu sdk 的引脚配置方式分为两种。

  • GPIO 复用功能通过专门的 pinmux table ,用户只需要修改 table 中的相关引脚的功能,程序会自动配置这些引脚。pinmux table 位于 bsp/board/xxx_board 目录下 pinmux_config.h 文件。
  • 通过标准的 GPIO 设备接口配置引脚,缺点是只能配置普通的输入输出和中断功能,复用功能建议还是使用 table 进行配置。

*PIR 传感器如何工作?

**绝对零 (0 开尔文/-273.5 *C) 以上的每个物体都会以红外辐射的形式发出热能。物体越热,它发出的辐射就越多。辐射对人眼是不可见的,PIR 传感器专门设计用于检测这种辐射水平。PIR 传感器由两个主要部分组成

*您可以在下图中看到的热释电传感器是圆形的,中间有一个矩形晶体。

  • 一种称为菲涅尔透镜的特殊透镜,可将红外信号聚焦到热释电传感器上。

热释电传感器热释电传感器由一个窗口和两个由涂层硅制成的矩形槽组成,它允许红外线通过并阻挡任何其他辐射。传感器的设计使得一个可以抵消另一个,这样传感器就可以抵消环境辐射并检测辐射模式的变化。

当没有检测到运动时,产生的输出信号为零,因为传感器正在检测背景辐射。但是,当传感器的任何一半截获运动时,都会导致传感器两部分之间的电压电平发生变化,这就是检测运动的方式。

菲涅耳透镜

菲涅耳透镜由一系列刻在塑料上的同心凹槽组成。这些轮廓充当单独的折射表面,在焦点处聚集平行光线。因此,菲涅耳透镜能够像传统光学透镜一样聚焦光线。

实际上,为了增加 PIR 传感器的范围和视野,透镜被分成几个面部分,每个部分都是一个单独的菲涅尔透镜。

HC-SR501 PIR 运动传感器模块引出线

HC-SR501 模块具有三个引脚。模块丝印被菲涅耳透镜遮挡,请参考下面给出的引脚排列。施加 5V – 12V 电源和接地,传感器输出在检测到运动时变为高电平,在空闲时变为低电平(未检测到运动)。

触发器选择跳线

有两种触发模式决定传感器在检测到运动时如何反应。

  • 单触发模式:持续运动将导致单触发。
  • 多重触发模式:不断的运动会引起一系列的触发。

L - 在此设置中,传感器将处于单触发模式,在此模式下,当检测到运动时输出变高。并在延时电位器设定的一定时间内保持高电平。任何其他类型的检测都会被阻止,直到输出变低。

H - 选择这些设置将设置多重触发模式。在这种模式下,当检测到运动时输出变高,高电平周期由设置的电位器决定。但与单触发模式不同的是,进一步检测不会被阻止并且可以连续触发,当未检测到移动时,引脚变为低电平。

灵敏度调整

PIR 传感器背面有一个电位器,用于调节灵敏度。在电位器的帮助下,可以调整设备的灵敏度。顺时针旋转电位器会增加灵敏度,逆时针旋转电位器会降低灵敏度。

延时调整

传感器背面的另一个锅设置输出将保持高电平的时间以及在检测到运动后顺时针转动锅会增加延迟,逆时针转动锅会减少延迟。

3.3V 稳压器

该模块带有一个 3.3V 稳压器,因此它可以由 4.5V 至 12V 电源供电。虽然 5V 是常用的。

保护二极管

该模块带有一个保护二极管,用于保护二极管免受反向电压和电流的影响。

接线示意图:

HC-SR501 PIR 传感器模块故障排除

PIR 传感器无法正常工作可能有多种原因。您需要通过一些测试来找出问题的根本原因。

  • 传感器的工作电压为 4.8V 至 20V,因此无法使用 3.3V 为传感器供电。
  • 在某些情况下,您会看到镜头顶部积聚了灰尘,因此 PIR 传感器可能会停止工作。
  • 在某些情况下,传感器内部可能会积聚灰尘,这可能是传感器无法正常工作的原因。
  • 如果上述方法均无效,请尝试旋转电位器。如果您将电位计的灵敏度设置为最低,那么这可能是您的传感器不工作的原因。
  • 在测试了所有方法后,如果传感器不工作,那么您可以确定您手中的传感器有故障。

部分代码

#include "bflb\_mtimer.h"
#include "board.h"
#include "bflb\_gpio.h"
#include "locale.h"

#define DBG\_TAG "MAIN"
#include "log.h"

struct bflb\_device\_s \*gpio;

int main(void)
{
board\_init();

gpio = bflb\_device\_get\_by\_name("gpio");
bflb\_gpio\_init(gpio, GPIO\_PIN\_13, GPIO\_INPUT | GPIO\_PULLDOWN | GPIO\_SMT\_EN | GPIO\_DRV\_0);
bflb\_gpio\_init(gpio, GPIO\_PIN\_12, GPIO\_OUTPUT | GPIO\_PULLUP | GPIO\_SMT\_EN | GPIO\_DRV\_0);
while (1) {
bool isH = bflb\_gpio\_read(gpio, GPIO\_PIN\_13);
if(isH){
bflb\_gpio\_set(gpio, GPIO\_PIN\_12);
}else{
bflb\_gpio\_reset(gpio, GPIO\_PIN\_12);
}
LOG\_F("是否有人=%d\\r\\n", isH);
bflb\_mtimer\_delay\_ms(500);
}
}

Step1: 构建项目并实现 Ai-M61-32S 与 人体红外感应模块 HC-SR501 连接,并获取状态值。 默认灯光是关闭的。 默认状态

检测有人经过时,红灯亮起。

TTS语音模块:XFS5152CE语音合成模块 TTS是Text To Speech的缩写,即“从文本到语音”,是人机对话的一部分,让机器能够说话。

**语音播报功能的实现方式*** TTS语音模块,比如XFS5152、SYN6288等

  • ISD4000系列语音录放芯片分段输出
  • 可以按键、UART控制的mp3解码芯片模块
  • OTP(One Time Programable)语音芯片[定制]

其中TTS语音模块使用起来最方便灵活,OTP语音芯片最简单。

由于项目使用的是XFS5152CE,所以简单介绍一下科大讯飞的XFS5152CE语音合成模块。

性能描述

1采用XFS5152CE语音合成芯片,支持任意中文文本、英文文本合成及

中英混读。

2支持文本控制标记设置,使用便捷,同时提升了文本处理的正确率。

3.具有文本智能分析处理功能,对常见的数字、号码、时间、日期、度

量衡符号等能正确的识别和处理。

4.具有很强的多音字和中文姓氏处理能力。

5.支持内置多款播音人声可供选择。

6.支持10级语速调节。

7.支持10级音调调节。

8.支持10级音量调节。

9.支持GB2312、GBK、BIG5和UNICODE四种编码方式。

10.每次合成的文本量多达4K字节。

11.集成80种常用提示音效,适用于不同场合的信息提示、铃声、警报

等功能。

12.支持多种控制命令,如合成文本、停止合成、状态查询等。

13.板载扬声器。

14.支持三种连接方式:杜邦线接口、鳄鱼夹接口、PH2.0防呆接口。

15.通信方式:IIC通信。

16.12C地址:0x50[新版本0x30]。

由于协议使用的是I2C,为了方便使用封装I2C功能。

Wire.h

#pragma once

// #include "bouffalo\_sdk.h"
#include "bflb\_gpio.h"
#include "bl616\_gpio.h"
#include "bl616\_glb.h"
#include "bl616\_glb\_gpio.h"
#include "../../drivers/lhal/include/hardware/i2c\_reg.h"
#include "bflb\_i2c.h"
#define lowByte(w) ((uint8\_t) ((w) & 0xff))
#define highByte(w) ((uint8\_t) ((w) >> 8))

bool getWireTimeoutFlag();
bool clearWireTimeoutFlag();
void setWireTimeout(int timeout, bool reset\_on\_timeout);
void onRequest(void (\*callback)());
void onReceive(void (\*callback)(int));
void setClock(int clockFrequency);
int readI2c();
int available();
int write\_len(uint8\_t \*str, int len);
int write\_str(uint8\_t \*str);
int write\_char(unsigned char value);
void endTransmission\_stop(bool stop);
void endTransmission();
void beginTransmission(unsigned char addr);
int requestFrom\_stop(unsigned char addr, int quantity, bool stop);
int requestFrom(unsigned char addr, int quantity);
void end();
void begin\_addr(unsigned char addr);
void begin();

其中

define lowByte(w) ((uint8_t) ((w) & 0xff))

define highByte(w) ((uint8_t) ((w) >> 8))

是arduino中的方法,主要是获取高位和低位数据

Wire.c

#include "Wire.h"

#define PUT_UINT32_LE(field, value)            \
    do {                                       \
        (field)[0] = (uint8_t)((value) >> 0);  \
        (field)[1] = (uint8_t)((value) >> 8);  \
        (field)[2] = (uint8_t)((value) >> 16); \
        (field)[3] = (uint8_t)((value) >> 24); \
    } while (0)



struct bflb_device_s *i2c0;
uint8_t rbuf[128];
int available_count;
int indexi2c;
int wire_timeout;
bool wire_timeout_flag;

void board_i2c_pinmux_init(void)
{
    GLB_GPIO_Type pinlist[] = {
        GLB_GPIO_PIN_30,
        GLB_GPIO_PIN_31
    };
    GLB_GPIO_Func_Init(GPIO_FUN_I2C0, pinlist, 2);
}

bool bflb_i2c_isend(struct bflb_device_s *dev)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_INT_STS_OFFSET);

    if (regval & I2C_END_INT) {
        return true;
    }

    return false;
}

bool bflb_i2c_isnak(struct bflb_device_s *dev)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_INT_STS_OFFSET);

    if (regval & I2C_NAK_INT) {
        return true;
    }

    return false;
}

bool bflb_i2c_isbusy(struct bflb_device_s *dev)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_BUS_BUSY_OFFSET);

    if (regval & I2C_STS_I2C_BUS_BUSY) {
        return true;
    }

    return false;
}

void bflb_i2c_enable(struct bflb_device_s *dev)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_CONFIG_OFFSET);
    regval |= I2C_CR_I2C_M_EN;
    putreg32(regval, reg_base + I2C_CONFIG_OFFSET);
}

bool bflb_i2c_isenable(struct bflb_device_s *dev)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_CONFIG_OFFSET);
    if (regval & I2C_CR_I2C_M_EN) {
        return true;
    }

    return false;
}

void bflb_i2c_disable(struct bflb_device_s *dev)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_CONFIG_OFFSET);
    regval &= ~I2C_CR_I2C_M_EN;
    putreg32(regval, reg_base + I2C_CONFIG_OFFSET);
    /* Clear I2C fifo */
    regval = getreg32(reg_base + I2C_FIFO_CONFIG_0_OFFSET);
    regval |= I2C_TX_FIFO_CLR;
    regval |= I2C_RX_FIFO_CLR;
    putreg32(regval, reg_base + I2C_FIFO_CONFIG_0_OFFSET);
    /* Clear I2C interrupt status */
    regval = getreg32(reg_base + I2C_INT_STS_OFFSET);
    regval |= I2C_CR_I2C_END_CLR;
    regval |= I2C_CR_I2C_NAK_CLR;
    regval |= I2C_CR_I2C_ARB_CLR;
    putreg32(regval, reg_base + I2C_INT_STS_OFFSET);
}


void bflb_i2c_addr_config(struct bflb_device_s *dev, uint16_t slaveaddr, uint16_t subaddr, uint8_t subaddr_size, bool is_addr_10bit)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_CONFIG_OFFSET);

    if (subaddr_size > 0) {
        regval |= I2C_CR_I2C_SUB_ADDR_EN;
        regval &= ~I2C_CR_I2C_SUB_ADDR_BC_MASK;
        regval |= ((subaddr_size - 1) << I2C_CR_I2C_SUB_ADDR_BC_SHIFT);
    } else {
        regval &= ~I2C_CR_I2C_SUB_ADDR_EN;
    }

    regval &= ~I2C_CR_I2C_SLV_ADDR_MASK;
    regval |= (slaveaddr << I2C_CR_I2C_SLV_ADDR_SHIFT);
#if !defined(BL602) && !defined(BL702)
    if (is_addr_10bit) {
        regval |= I2C_CR_I2C_10B_ADDR_EN;
    } else {
        regval &= ~I2C_CR_I2C_10B_ADDR_EN;
    }
#endif
    putreg32(subaddr, reg_base + I2C_SUB_ADDR_OFFSET);
    putreg32(regval, reg_base + I2C_CONFIG_OFFSET);
}

void bflb_i2c_set_datalen(struct bflb_device_s *dev, uint16_t data_len)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_CONFIG_OFFSET);
    regval &= ~I2C_CR_I2C_PKT_LEN_MASK;
    regval |= ((data_len - 1) << I2C_CR_I2C_PKT_LEN_SHIFT) & I2C_CR_I2C_PKT_LEN_MASK;
    putreg32(regval, reg_base + I2C_CONFIG_OFFSET);
}


void bflb_i2c_set_dir(struct bflb_device_s *dev, bool is_in)
{
    uint32_t regval;
    uint32_t reg_base;

    reg_base = dev->reg_base;

    regval = getreg32(reg_base + I2C_CONFIG_OFFSET);

    if (is_in) {
        regval |= I2C_CR_I2C_PKT_DIR;
    } else {
        regval &= ~I2C_CR_I2C_PKT_DIR;
    }
    putreg32(regval, reg_base + I2C_CONFIG_OFFSET);
}


int bflb_i2c_write_bytes(struct bflb_device_s *dev, uint8_t *data, uint32_t len, uint32_t timeout)
{
    uint32_t reg_base;
    uint32_t temp = 0;
    uint8_t *tmp_buf;
    uint64_t start_time;

    reg_base = dev->reg_base;
    tmp_buf = data;
    while (len >= 4) {
        for (uint8_t i = 0; i < 4; i++) {
            temp += (tmp_buf[i] << ((i % 4) * 8));
        }
        tmp_buf += 4;
        len -= 4;
        start_time = bflb_mtimer_get_time_ms();
        while ((getreg32(reg_base + I2C_FIFO_CONFIG_1_OFFSET) & I2C_TX_FIFO_CNT_MASK) == 0) {
            if ((bflb_mtimer_get_time_ms() - start_time) > timeout) {
                return -ETIMEDOUT;
            }
        }
        putreg32(temp, reg_base + I2C_FIFO_WDATA_OFFSET);
        if (!bflb_i2c_isenable(dev)) {
            bflb_i2c_enable(dev);
        }
        temp = 0;
    }

    if (len > 0) {
        for (uint8_t i = 0; i < len; i++) {
            temp += (tmp_buf[i] << ((i % 4) * 8));
        }
        start_time = bflb_mtimer_get_time_ms();
        while ((getreg32(reg_base + I2C_FIFO_CONFIG_1_OFFSET) & I2C_TX_FIFO_CNT_MASK) == 0) {
            if ((bflb_mtimer_get_time_ms() - start_time) > timeout) {
                return -ETIMEDOUT;
            }
        }
        putreg32(temp, reg_base + I2C_FIFO_WDATA_OFFSET);
        if (!bflb_i2c_isenable(dev)) {
            bflb_i2c_enable(dev);
        }
    }

    start_time = bflb_mtimer_get_time_ms();
    while (bflb_i2c_isbusy(dev) || !bflb_i2c_isend(dev) || bflb_i2c_isnak(dev)) {
        if ((bflb_mtimer_get_time_ms() - start_time) > timeout) {
            return -ETIMEDOUT;
        }
    }
    bflb_i2c_disable(dev);

    return 0;
}


int bflb_i2c_read_bytes(struct bflb_device_s *dev, uint8_t *data, uint32_t len, uint32_t timeout)
{
    uint32_t reg_base;
    uint32_t temp = 0;
    uint8_t *tmp_buf;
    uint64_t start_time;

    reg_base = dev->reg_base;
    tmp_buf = data;

    bflb_i2c_enable(dev);

    while (len >= 4) {
        start_time = bflb_mtimer_get_time_ms();
        while ((getreg32(reg_base + I2C_FIFO_CONFIG_1_OFFSET) & I2C_RX_FIFO_CNT_MASK) == 0) {
            if ((bflb_mtimer_get_time_ms() - start_time) > timeout) {
                return -ETIMEDOUT;
            }
        }
        temp = getreg32(reg_base + I2C_FIFO_RDATA_OFFSET);
        PUT_UINT32_LE(tmp_buf, temp);
        tmp_buf += 4;
        len -= 4;
    }

    if (len > 0) {
        start_time = bflb_mtimer_get_time_ms();
        while ((getreg32(reg_base + I2C_FIFO_CONFIG_1_OFFSET) & I2C_RX_FIFO_CNT_MASK) == 0) {
            if ((bflb_mtimer_get_time_ms() - start_time) > timeout) {
                return -ETIMEDOUT;
            }
        }
        temp = getreg32(reg_base + I2C_FIFO_RDATA_OFFSET);

        for (uint8_t i = 0; i < len; i++) {
            tmp_buf[i] = (temp >> (i * 8)) & 0xff;
        }
    }

    start_time = bflb_mtimer_get_time_ms();
    while (bflb_i2c_isbusy(dev) || !bflb_i2c_isend(dev)) {
        if ((bflb_mtimer_get_time_ms() - start_time) > timeout) {
            return -ETIMEDOUT;
        }
    }
    bflb_i2c_disable(dev);

    return 0;
}



/*
 * address: the 7-bit slave address (optional); if not specified, join the bus as a controller device.
 */
void begin_addr(unsigned char addr) {
    wire_timeout = 100;
    wire_timeout_flag = false;
    board_i2c_pinmux_init();
    i2c0 = bflb_device_get_by_name("i2c0");
    bflb_i2c_init(i2c0, 50000);
}

void begin() {
    wire_timeout = 100;
    wire_timeout_flag = false;
    board_i2c_pinmux_init();
    i2c0 = bflb_device_get_by_name("i2c0");
    bflb_i2c_init(i2c0, 50000);
}

void end() {
    bflb_i2c_deinit(i2c0);
}
/*
 * address: the 7-bit slave address of the device to request bytes from.
 *
 * quantity: the number of bytes to request.
 *
 * stop: true or false. true will send a stop message after the request, releasing the bus.
 * False will continually send a restart after the request, keeping the connection active.
 */

int requestFrom_stop(unsigned char addr, int quantity, bool stop) {
    indexi2c = 0;
    bflb_i2c_disable(i2c0);
    bflb_i2c_enable(i2c0);
    bflb_i2c_addr_config(i2c0, addr, 0, 0, false);
    bflb_i2c_set_datalen(i2c0,quantity);
    bflb_i2c_set_dir(i2c0, 1);
    bflb_i2c_read_bytes(i2c0, rbuf,quantity,wire_timeout);
    available_count = quantity;
    if(true == stop){
        bflb_i2c_disable(i2c0);
    }
    return 0;
}

int requestFrom(unsigned char addr, int quantity) {
    indexi2c = 0;
    bflb_i2c_disable(i2c0);
    bflb_i2c_enable(i2c0);
    bflb_i2c_addr_config(i2c0, addr, 0, 0, false);
    bflb_i2c_set_datalen(i2c0,quantity);
    bflb_i2c_set_dir(i2c0, 1);
    bflb_i2c_read_bytes(i2c0, rbuf,quantity,wire_timeout);
    available_count = quantity;
    return 0;
}

/*
 * address: the 7-bit address of the device to transmit to.
 */
void beginTransmission(unsigned char addr) {
    //bflb_i2c_enable(i2c0);
    bflb_i2c_addr_config(i2c0, addr, 0, 0, false);
    bflb_i2c_set_dir(i2c0, 0);
}

/*
 * stop: true or false. True will send a stop message, releasing the bus after transmission.
 * False will send a restart, keeping the connection active.
 *
 * Returns
 * 0: success.
 * 1: data too long to fit in transmit buffer.
 * 2: received NACK on transmit of address.
 * 3: received NACK on transmit of data.
 * 4: other error.
 * 5: timeout
 */

void endTransmission_stop(bool stop) {
    bflb_i2c_disable(i2c0);
}

void endTransmission() {
    bflb_i2c_disable(i2c0);

}

/*
 * Description
 * This function writes data from a peripheral device in response to a request from
 * a controller device, or queues bytes for transmission from a controller to
 * peripheral device (in-between calls to beginTransmission() and endTransmission()).
 * Syntax
 * Wire.write(value) Wire.write(string) Wire.write(data, length)
 * Parameters
 * value: a value to send as a single byte.
 * string: a string to send as a series of bytes.
 * data: an array of data to send as bytes.
 * length: the number of bytes to transmit.
 * Returns
 * The number of bytes written (reading this number is optional).
 */
int write_char(unsigned char value) {
    bflb_i2c_set_datalen(i2c0, 1);
    bflb_i2c_write_bytes(i2c0, &value, 1,wire_timeout);
  return 0;
}
int write_str(uint8_t *str) {
    bflb_i2c_set_datalen(i2c0, strlen((const char*)str));
    bflb_i2c_write_bytes(i2c0, str, strlen((const char*)str),wire_timeout);
  return 0;
}
int write_len(uint8_t *str, int len) {
    bflb_i2c_set_datalen(i2c0, len);
    int ret = bflb_i2c_write_bytes(i2c0, str, len,wire_timeout);
  return ret;
}

/*
 * Description
 * This function returns the number of bytes available for retrieval with read().
 * This function should be called on a controller device after a call to
 * requestFrom() or on a peripheral inside the onReceive() handler.
 * available() inherits from the Stream utility class.
 */

int available() {
    return available_count;
}

/*
 * Description
 * This function reads a byte that was transmitted from a peripheral device to
 * a controller device after a call to requestFrom() or was transmitted from a
 * controller device to a peripheral device. read() inherits from the Stream utility class.
 * Syntax
 * Wire.read()
 * Parameters
 * None.
 * Returns
 * The next byte received.
 */

int readI2c() {
    unsigned char ret;
    if(available_count){
        available_count--;
        ret = rbuf[indexi2c];
        indexi2c++;
        return ret;
    }
    return 0;
}

/*
 * Description
 * This function modifies the clock frequency for I2C communication.
 * I2C peripheral devices have no minimum working clock frequency,
 * however 100KHz is usually the baseline.
 * Syntax
 * Wire.setClock(clockFrequency)
 * Parameters
 * clockFrequency: the value (in Hertz) of the desired communication clock.
 * Accepted values are 100000 (standard mode) and 400000 (fast mode).
 * Some processors also support 10000 (low speed mode), 1000000 (fast mode plus)
 * and 3400000 (high speed mode). Please refer to the specific processor documentation
 * to make sure the desired mode is supported.
 * Returns
 * None.
 */
void setClock(int clockFrequency) {
    bflb_i2c_deinit(i2c0);
    bflb_i2c_init(i2c0, clockFrequency);
}

/*
 * Description
 * This function registers a function to be called when a peripheral device receives
 * a transmission from a controller device.
 * Syntax
 * Wire.onReceive(handler)
 * Parameters
 * handler: the function to be called when the peripheral device receives data;
 * this should take a single int parameter (the number of bytes read from the controller
 * device) and return nothing.
 * Returns
 * None.
 */
void onReceive(void (*callback)(int)) {
    //we not support slave mode yet
}

/*
 * Description
 * This function registers a function to be called when a controller device requests data from a peripheral device.
 * Syntax
 * Wire.onRequest(handler)
 * Parameters
 * handler: the function to be called, takes no parameters and returns nothing.
 * Returns
 * None.
 */
void onRequest(void (*callback)()) {
    //we not support slave mode yet
}

/*
 * Description
 * Sets the timeout for Wire transmissions in master mode.
 * Syntax
 * Wire.setWireTimeout(timeout, reset_on_timeout)
 * Wire.setWireTimeout()
 * Parameters
 * timeout a timeout: timeout in microseconds, if zero then timeout checking is disabled
 * reset_on_timeout: if true then Wire hardware will be automatically reset on timeout
 * When this function is called without parameters, a default timeout is configured that
 * should be sufficient to prevent lockups in a typical single-master configuration.
 * Returns
 * None.
 */
void setWireTimeout(int timeout, bool reset_on_timeout) {
    wire_timeout = timeout;
    wire_timeout_flag = true;
}

/* Description
 * Clears the timeout flag.
 * Timeouts might not be enabled by default. See the documentation for Wire.setWireTimeout()
 * for more information on how to configure timeouts and how they work.
 * Syntax
 * Wire.clearTimeout()
 * Parameters
 * None.
 * Returns
 * bool: The current value of the flag
 */
bool clearWireTimeoutFlag() {
    wire_timeout_flag = false;
  return true;
}

/*
 * Description
 * Checks whether a timeout has occured since the last time the flag was cleared.
 * This flag is set is set whenever a timeout occurs and cleared when Wire.clearWireTimeoutFlag()
 * is called, or when the timeout is changed using Wire.setWireTimeout().
 * Syntax
 * Wire.getWireTimeoutFlag()
 * Parameters
 * None.
 * Returns
 * bool: The current value of the flag
 */

bool getWireTimeoutFlag() {
  return wire_timeout_flag;
}

这里修改了I2C引脚;

GLB_GPIO_Type pinlist[] = { GLB_GPIO_PIN_30, GLB_GPIO_PIN_31 };

默认是GLB_GPIO_PIN_14、GLB_GPIO_PIN_15.

然后封装,XFS5152CE模块功能代码;

XFS.h


#ifndef __XFS_H
#define __XFS_H

#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <math.h>
#include <stdint.h>        //Added for uint_t
#include <stdio.h>
#include "bflb_mtimer.h"

struct XFS_Protocol_TypeDef
{
    uint8_t DataHead;
    uint8_t Length_HH;
    uint8_t Length_LL;
    uint8_t Commond;
    uint8_t EncodingFormat;
    const char* Text;
};

/*
    *| 帧头(1Byte)| 数据区长度(2Byte)|           数据区(<4KByte)          |
    *|            |  高字节 | 低字节 | 命令字 | 文本编码格式 | 待合成文本 |
    *|    0xFD    |  0xHH   |  0xLL  |  0x01  |   0x00~0x03  |  ... ...   |
    */

typedef enum
{
    CMD_StartSynthesis = 0x01,//语音合成命令
    CMD_StopSynthesis = 0x02,//停止合成命令,没有参数
    CMD_PauseSynthesis = 0x03,//暂停合成命令,没有参数
    CMD_RecoverySynthesis = 0x04,//恢复合成命令,没有参数
    CMD_CheckChipStatus = 0x21,//芯片状态查询命令
    CMD_PowerSavingMode = 0x88,//芯片进入省电模式
    CMD_NormalMode = 0xFF//芯片从省电模式返回正常工作模式
} CMD_Type;//命令字
void StartSynthesis(const char* str);//开始合成
// void StartSynthesis(String str);//开始合成

bool IIC_WriteByte(uint8_t data);
void IIC_WriteByteSize(uint8_t* buff, uint32_t size);
void SendCommond(CMD_Type cmd);
void StopSynthesis();//停止合成
void PauseSynthesis();//暂停合成
void RecoverySynthesis();//恢复合成

typedef enum
{
    GB2312 = 0x00,
    GBK = 0x01,
    BIG5 = 0x02,
    UNICODE = 0x03
} EncodingFormat_Type;//文本的编码格式
void SetEncodingFormat(EncodingFormat_Type encodingFormat);

typedef enum
{
    ChipStatus_InitSuccessful = 0x4A,//初始化成功回传
    ChipStatus_CorrectCommand = 0x41,//收到正确的命令帧回传
    ChipStatus_ErrorCommand = 0x45,//收到不能识别命令帧回传
    ChipStatus_Busy = 0x4E,//芯片忙碌状态回传
    ChipStatus_Idle = 0x4F//芯片空闲状态回传
} ChipStatus_Type;//芯片回传

typedef enum
{
    Style_Single,//?为 0,一字一顿的风格
    Style_Continue//?为 1,正常合成
} Style_Type; //合成风格设置 [f?]
void SetStyle(Style_Type style);

typedef enum
{
    Language_Auto,//? 为 0,自动判断语种
    Language_Chinese,//? 为 1,阿拉伯数字、度量单位、特殊符号等合成为中文
    Language_English//? 为 2,阿拉伯数字、度量单位、特殊符号等合成为英文
} Language_Type; //合成语种设置 [g?]
void SetLanguage(Language_Type language);

typedef enum
{
    Articulation_Auto,//? 为 0,自动判断单词发音方式
    Articulation_Letter,//? 为 1,字母发音方式
    Articulation_Word//? 为 2,单词发音方式
} Articulation_Type; //设置单词的发音方式 [h?]
void SetArticulation(Articulation_Type articulation);

typedef enum
{
    Spell_Disable,//? 为 0,不识别汉语拼音
    Spell_Enable//? 为 1,将“拼音+1 位数字(声调)”识别为汉语拼音,例如: hao3
} Spell_Type; //设置对汉语拼音的识别 [i?]
void SetSpell(Spell_Type spell);

typedef enum
{
    Reader_XiaoYan = 3,//? 为 3,设置发音人为小燕(女声, 推荐发音人)
    Reader_XuJiu = 51,//? 为 51,设置发音人为许久(男声, 推荐发音人)
    Reader_XuDuo = 52,//? 为 52,设置发音人为许多(男声)
    Reader_XiaoPing = 53,//? 为 53,设置发音人为小萍(女声)
    Reader_DonaldDuck = 54,//? 为 54,设置发音人为唐老鸭(效果器)
    Reader_XuXiaoBao = 55//? 为 55,设置发音人为许小宝(女童声)
} Reader_Type;//选择发音人 [m?]
void SetReader(Reader_Type reader);

typedef enum
{
    NumberHandle_Auto,//? 为 0,自动判断
    NumberHandle_Number,//? 为 1,数字作号码处理
    NumberHandle_Value//? 为 2,数字作数值处理
} NumberHandle_Type; //设置数字处理策略 [n?]
void SetNumberHandle(NumberHandle_Type numberHandle);

typedef enum
{
    ZeroPronunciation_Zero,//? 为 0,读成“zero
    ZeroPronunciation_O//? 为 1,读成“欧”音
} ZeroPronunciation_Type; //数字“0”在读 作英文、号码时 的读法 [o?]
void SetZeroPronunciation(ZeroPronunciation_Type zeroPronunciation);


typedef enum
{
    NamePronunciation_Auto,//? 为 0,自动判断姓氏读音
    NamePronunciation_Constraint//? 为 1,强制使用姓氏读音规则
} NamePronunciation_Type; //设置姓名读音 策略 [r?]
void SetNamePronunciation(NamePronunciation_Type namePronunciation);

void SetSpeed(int speed);//设置语速 [s?] ? 为语速值,取值:0~10
void SetIntonation(int intonation);//设置语调 [t?] ? 为语调值,取值:0~10
void SetVolume(int volume);//设置音量 [v?] ? 为音量值,取值:0~10

typedef enum
{
    PromptTone_Disable,//? 为 0,不使用提示音
    PromptTone_Enable//? 为 1,使用提示音
} PromptTone_Type; //设置提示音处理策略 [x?]
void SetPromptTone(PromptTone_Type promptTone);

typedef enum
{
    OnePronunciation_Yao,//? 为 0,合成号码“1”时读成“幺
    OnePronunciation_Yi//? 为 1,合成号码“1”时读成“一”
} OnePronunciation_Type; //设置号码中“1”的读法 [y?]
void SetOnePronunciation(OnePronunciation_Type onePronunciation);

typedef enum
{
    Rhythm_Diasble,//? 为 0,“ *”和“#”读出符号
    Rhythm_Enable//? 为 1,处理成韵律,“*”用于断词,“#”用于停顿
} Rhythm_Type; //是否使用韵律 标记“*”和“#” [z?]
void SetRhythm(Rhythm_Type rhythm);


void SetRestoreDefault();//恢复默认的合成参数 [d] 所有设置(除发音人设置、语种设置外)恢复为默认值

void XFS5152CE(uint8_t encodingFormat);
void Begin(uint8_t addr);
uint8_t GetChipStatus();
void TextCtrl(char c, int d);

#endif

XFS.c

#include "XFS.h"
#include <Wire.h>
#define DBG_TAG "MAIN"
#include "log.h"

#define XFS_DataHead (uint8_t)0xFD

uint8_t I2C_Addr;
uint8_t ChipStatus;
struct XFS_Protocol_TypeDef DataPack;

size_t foo( const char* restrict src, uint8_t* restrict dst, size_t dst_maxlen )
{
    size_t idx = 0;
    for( ; src[idx] && dst_maxlen; ++idx )
    {
        if( idx%8 == 0 )
            *dst = 0;
        if( src[idx] != '0' )
            *dst |= 1<<(7-idx%8);
        if( idx%8 == 7 )
            ++dst, --dst_maxlen;
    }
    return (idx+7)/8;
}

void XFS5152CE(uint8_t encodingFormat)
{
  DataPack.DataHead = XFS_DataHead;
  DataPack.Length_HH = 0x00;
  DataPack.Length_LL = 0x00;

  DataPack.Commond = 0x00;
  DataPack.EncodingFormat = encodingFormat;

  ChipStatus = 0x00;
}


void Begin(uint8_t addr)
{
  I2C_Addr = addr;
  XFS5152CE(GB2312);
  begin();
}

uint8_t GetChipStatus()
{
  uint8_t AskState[4] = {0xFD,0x00,0x01,0x21};
  beginTransmission(I2C_Addr);
  write_len(AskState,4);
  endTransmission();
  bflb_mtimer_delay_ms(100);
  requestFrom(I2C_Addr, 1);
  while (available())
  {
    ChipStatus = readI2c();
  }

  LOG_F("ChipStatus=%x\r\n", ChipStatus);
  return ChipStatus;
}


bool IIC_WriteByte(uint8_t data)
{
  beginTransmission(I2C_Addr);
  write_char(data);
  endTransmission();
  // if(endTransmission()!=0)            //发送结束信号
  //  {
  //         bflb_mtimer_delay_ms(10);
  //         return false;
  //   }
    bflb_mtimer_delay_ms(10);
    return true;  
}

void IIC_WriteBytes(uint8_t* buff, uint32_t size)
{
  for (uint32_t i = 0; i < size; i++)
  {
    IIC_WriteByte(buff[i]);
  }
}

void StartSynthesis(const char* str)
{
  uint16_t size = strlen(str) + 2;
  DataPack.Length_HH = highByte(size);
  DataPack.Length_LL = lowByte(size);
  DataPack.Commond = CMD_StartSynthesis;
  DataPack.Text = str;

  uint8_t dst[(strlen(DataPack.Text)-1+7)/8];
  size_t len = foo(DataPack.Text, dst, sizeof(dst)/sizeof(*dst) );
  IIC_WriteBytes((uint8_t*)&DataPack,5);
  IIC_WriteBytes(DataPack.Text, strlen(str));

}


// void StartSynthesis(String str)
// {
//   StartSynthesis((const char*)str.c_str());
// }

void SendCommond(CMD_Type cmd)
{
  DataPack.Length_HH = 0x00;
  DataPack.Length_LL = 0x01;
  DataPack.Commond = cmd;

  beginTransmission(I2C_Addr);
  write_len((uint8_t*)&DataPack, 4);
  endTransmission();
}

void StopSynthesis()
{
  SendCommond(CMD_StopSynthesis);
}

void PauseSynthesis()
{
  SendCommond(CMD_PauseSynthesis);
}

void RecoverySynthesis()
{
  SendCommond(CMD_RecoverySynthesis);
}

void TextCtrl(char c, int d)
{
  char str[10];
  if (d != -1)
    sprintf(str, "[%c%d]", c, d);
  else
    sprintf(str, "[%c]", c);
  StartSynthesis(str);
}

void SetEncodingFormat(EncodingFormat_Type encodingFormat)
{
  DataPack.EncodingFormat = encodingFormat;
}

void SetStyle(Style_Type style)
{
  TextCtrl('f', style);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetLanguage(Language_Type language)
{
  TextCtrl('g', language);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetArticulation(Articulation_Type articulation)
{
  TextCtrl('h', articulation);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetSpell(Spell_Type spell)
{
  TextCtrl('i', spell);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  } 
}

void SetReader(Reader_Type reader)
{
  TextCtrl('m', reader);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetNumberHandle(NumberHandle_Type numberHandle)
{
  TextCtrl('n', numberHandle);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetZeroPronunciation(ZeroPronunciation_Type zeroPronunciation)
{
  TextCtrl('o', zeroPronunciation);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}


void SetNamePronunciation(NamePronunciation_Type namePronunciation)
{
  TextCtrl('r', namePronunciation);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetSpeed(int speed)
{
  // speed = constrain(speed, 0, 10);
  TextCtrl('s', speed);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetIntonation(int intonation)
{
  // intonation = constrain(intonation, 0, 10);
  TextCtrl('t', intonation);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetVolume(int volume)
{
  // volume = constrain(volume, 0, 10);
  TextCtrl('v', volume);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetPromptTone(PromptTone_Type promptTone)
{
  TextCtrl('x', promptTone);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetOnePronunciation(OnePronunciation_Type onePronunciation)
{
  TextCtrl('y', onePronunciation);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

void SetRhythm(Rhythm_Type rhythm)
{
  TextCtrl('z', rhythm);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}


void SetRestoreDefault()
{
  TextCtrl('d', -1);
  while(GetChipStatus() != ChipStatus_Idle)
  {
     bflb_mtimer_delay_ms(30);
  }
}

语音播报内容 文件头 speak.h,

该文件是为了方式播报内容乱码错乱。文件需要保存为 ANSI;

char\* hello[] = {"主人", "出门记得带手机和钥匙"};

主程序代码 :main.c

#include "bflb_mtimer.h"
#include "board.h"
#include "bflb_gpio.h"
#include "Wire.h"
#include "XFS.h"
#include "speak.h"
#define DBG_TAG "MAIN"
#include "log.h"

/*超时设置,示例为30S*/
static uint32_t LastSpeakTime = 0;
#define SpeakTimeOut 10000


/**
    @brief  初始化语音合成
    @param  无
    @retval 无
*/

uint8_t n = 1;
static void XFS_Init()
{
    Begin(0x30);//设备i2c地址,地址为0x50
    bflb_mtimer_delay_ms(n);
    SetReader(Reader_XuXiaoBao);        //设置发音人
    bflb_mtimer_delay_ms(n);
    SetEncodingFormat(GB2312);           //文本的编码格式
    bflb_mtimer_delay_ms(n);
    SetLanguage(Language_Auto);                 //语种判断
    bflb_mtimer_delay_ms(n);
    SetStyle(Style_Continue);            //合成风格设置
    bflb_mtimer_delay_ms(n);
    SetArticulation(Articulation_Letter);  //设置单词的发音方式
    bflb_mtimer_delay_ms(n);
    SetSpeed(6);                         //设置语速1~10
    bflb_mtimer_delay_ms(n);
    SetIntonation(5);                    //设置语调1~10
    bflb_mtimer_delay_ms(n);
    SetVolume(10);                        //设置音量1~10
    bflb_mtimer_delay_ms(n);
}

unsigned char result = 0xFF;

struct bflb_device_s *gpio;

void gpio_isr() {
    bool isH = bflb_gpio_read(gpio, GPIO_PIN_13);
    if(isH){
        bflb_gpio_set(gpio, GPIO_PIN_12);
        if(GetChipStatus() == ChipStatus_Idle){
            StartSynthesis(hello[1]);
        }
        bflb_mtimer_delay_ms(3000);
    }else{
        bflb_gpio_reset(gpio, GPIO_PIN_12);
    }
}

int main(void)
{
    board_init();

    gpio = bflb_device_get_by_name("gpio");

    bflb_gpio_init(gpio, GPIO_PIN_12, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);

    bflb_gpio_int_init(gpio, GPIO_PIN_13, GPIO_INT_TRIG_MODE_SYNC_LOW_LEVEL);
    bflb_gpio_int_mask(gpio, GPIO_PIN_0, false);
    bflb_irq_attach(gpio->irq_num, gpio_isr, gpio);
    bflb_irq_enable(gpio->irq_num);

    XFS_Init();
    StartSynthesis(hello[0]);
    while(GetChipStatus() != ChipStatus_Idle)
    {
        bflb_mtimer_delay_ms(30);
    }


    // SetReader(Reader_XuJiu);        //设置发音人;   
    StartSynthesis(hello[1]);
    while(GetChipStatus() != ChipStatus_Idle)
    {
        bflb_mtimer_delay_ms(30);
    }

    while (1) {
        bflb_mtimer_delay_ms(500);
    }
}

修改CMakeLists.txt

cmake_minimum_required(VERSION 3.15)

include(proj.conf)

find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE})
# User
<font color="#ff0000">sdk_add_compile_definitions(-DCONFIG_CLI_CMD_ENABLE)</font>
#sdk_add_compile_definitions(-DBL616_DHCP_DEBUG)

<font color="#ff0000">target_sources(app PRIVATE
                   Wire.c
                   XFS.c)</font>

sdk_add_include_directories(.)

sdk_set_main_file(main.c)

project(helloworld)

相关功能模块封装完成,下一步硬件连线。

Ai-M61-32S GPIO 接口定义。

Ai-M61-32S XFS5152CE
3.3v VCC
GLB_GPIO_PIN_30 SCL
GLB_GPIO_PIN_31 SDA
GND GND

实物图:

连接部分:

红外感应:

完整 组件结构:

到这里基于 Ai-M61-32S “出门提醒设备 ”软硬件已完成。下一步就是外壳了。

【DIY电子作品】出门提醒设备 基于 Ai-M61-32S -- 出门提醒设备 最终章。

外壳选材小插曲

刷B站的时候曾经看过一个防水盒做音箱的视频,就跟着做了一个感觉很有趣。就买了一些防水盒。

某宝签到 红包1.5-3块钱,直接抵下来几毛钱一个比3D打印什么的性价比高很多。唯一缺点就是外观比较固定。

这个是DIY的小音箱。用的是“蓝牙驱动板m38蓝牙模块” 4快多买的。可以蓝牙连接,也可以当USB声卡使用还是不错的。

侧面改装成了Type-C接口,内置了18650电池。插着电源开关关掉就可以了。开着开关也没关系。用mos管做了隔离。插着线的情况下电池不给设备供电。

拔掉USB线,电池供电。基本可以无缝衔接。

总体感觉还可以,主要是DIY的乐趣。

【【总是人生愁】最新视频上线,求关注!】 https://www.bilibili.com/video/BV1WN41177gC/?share\_source=copy\_web&vd\_source=95f092a8edb82d1fb04a2d285b5f28a7

下面言归正传,选用的是 835833mm 尺寸的防水盒,用了给的红包抵扣后价格0.17元。还可以。

a31a5f6f-2900-4091-a30c-8c04d94232b2.jpg

摆了摆感觉尺寸刚刚好。

db9f0359-47b9-4de5-8e9d-211e7281eda8.jpg

3405a5e6-dc42-476a-bedd-3a300493a423.jpg

新铅笔,用小孩儿的卷笔刀削一下。

872ac1d5-2f30-4468-8a7b-55bff2e65c6c.jpg

拿出尺子、圆规开搞。

4adc084c-f32c-44a7-8daa-092cd442e9fa.jpg

画上圈圈,定好位置。

a413e0e1-5dcb-49a7-aea3-ea536fdaebb1.jpg

然后拿把剪刀,搞就完了。

aa176e50-cad7-4658-ab08-239db7f54064.jpg

最终效果。下面“ XFS5152CE” 语音合成模块的那个孔开始想用其他方法开孔结果给弄坏了。

最后还是剪刀好使,固定一点一直转呀转。圆孔就出来了。也算是涨经验了。

b0bad0cc-9cfb-493f-b79e-d1a36164fbb6.jpg

开孔后接线。

7fee60e7-d089-4277-a5e2-ea8f4f123cc4.jpg

3108e610-83ef-44b8-a282-540d99b4a10f.jpg

最终效果展示。

【【总是人生愁】最新视频已上线,快来围观!】

https://www.bilibili.com/video/BV1aH4y1171H/?share\_source=copy\_web&vd\_source=95f092a8edb82d1fb04a2d285b5f28a7

如果觉得还不错的话,可以 给个 企业微信截图_20231207112020.png+ 1 哦。

本帖被以下淘专辑推荐:

回复

使用道具 举报

WT_0213 | 2023-11-30 23:20:59 | 显示全部楼层
【DIY电子作品】出门提醒设备 基于 Ai-M61-32S 之 红外感应篇 Step1
http://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=43541
(出处: 物联网开发者社区-安信可论坛)

【DIY电子作品】出门提醒设备 基于 Ai-M61-32S 之 XFS5152CE语音合成篇 Step2
http://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=43657
(出处: 物联网开发者社区-安信可论坛)

【DIY电子作品】出门提醒设备 基于 Ai-M61-32S 之外壳篇 Step3
http://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=43687
(出处: 物联网开发者社区-安信可论坛)
回复 支持 1 反对 0

使用道具 举报

泠泠 | 2023-11-30 23:21:21 | 显示全部楼层
想做
回复

使用道具 举报

iiv | 2023-11-30 23:25:58 | 显示全部楼层
666
回复

使用道具 举报

bzhou830 | 2023-12-1 08:32:11 | 显示全部楼层
有趣
选择去发光,而不是被照亮
回复

使用道具 举报

爱笑 | 2023-12-1 09:05:06 | 显示全部楼层
优秀~
用心做好保姆工作
回复

使用道具 举报

粉色小风扇 | 2023-12-1 09:08:00 | 显示全部楼层
有点意思,过程也写的好详细
回复 支持 反对

使用道具 举报

Mr.Tail | 2023-12-1 09:40:42 | 显示全部楼层
优秀!这帖子太详细了
回复 支持 反对

使用道具 举报

曹县 | 2023-12-1 09:49:42 | 显示全部楼层
优秀!这帖子太详细了
回复 支持 反对

使用道具 举报

90956 | 2023-12-1 10:04:37 | 显示全部楼层
太强了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则