一、簡(jiǎn)介
二、紅外感應(yīng)篇
三、XFS5152CE語(yǔ)音合成篇
四、外殼篇
01
簡(jiǎn)介
本項(xiàng)目設(shè)計(jì)初衷是為了提醒自己出門(mén)不要忘記帶東西——“身”、“手”、“鑰”、“錢(qián)” (身份證,手機(jī),鑰匙,錢(qián)包)等。不過(guò)現(xiàn)在好像都在線(xiàn)支付了,錢(qián)包都不帶了。筆者覺(jué)得后期可以改造成播報(bào)天氣等,提醒帶傘等等。
項(xiàng)目最初設(shè)計(jì)是使用安信可24G雷達(dá)傳感器模塊人體微動(dòng)感應(yīng)檢測(cè)模組“Rd-03” 來(lái)做。由于手上剛好有一個(gè)"HC-SR501 紅外感應(yīng)電子模塊傳感器" 模塊,所以這次先用這個(gè)試試。
02
紅外感應(yīng)篇
硬件相關(guān)
紅外感應(yīng)模塊:HC-SR501
語(yǔ)音合成播報(bào)模塊:XFS5152
開(kāi)發(fā)板:Ai-M61-32S
GPIO 全稱(chēng) General Purpose Input Output(通用輸入 / 輸出),博流系列芯片的 GPIO 外設(shè)主要有以下功能。
普通輸入輸出帶上下拉
復(fù)用功能帶上下拉
模擬功能
外部中斷(上升沿、下降沿、高電平、低電平)
硬件消抖
驅(qū)動(dòng)能力控制
GPIO 復(fù)用功能通過(guò)專(zhuān)門(mén)的 pinmux table ,用戶(hù)只需要修改 table 中的相關(guān)引腳的功能,程序會(huì)自動(dòng)配置這些引腳。pinmux table 位于 bsp/board/xxx_board 目錄下 pinmux_config.h 文件。
通過(guò)標(biāo)準(zhǔn)的 GPIO 設(shè)備接口配置引腳,缺點(diǎn)是只能配置普通的輸入輸出和中斷功能,復(fù)用功能建議還是使用 table 進(jìn)行配置。
PIR 傳感器如何工作?
**絕對(duì)零 (0 開(kāi)爾文/-273.5 *C) 以上的每個(gè)物體都會(huì)以紅外輻射的形式發(fā)出熱能。物體越熱,它發(fā)出的輻射就越多。輻射對(duì)人眼是不可見(jiàn)的,PIR傳感器專(zhuān)門(mén)設(shè)計(jì)用于檢測(cè)這種輻射水平。
PIR 傳感器由兩個(gè)主要部分組成,可以看到的熱釋電傳感器是圓形的,中間有一個(gè)矩形晶體。
一種稱(chēng)為菲涅爾透鏡的特殊透鏡,可將紅外信號(hào)聚焦到熱釋電傳感器上。
熱釋電傳感器熱釋電傳感器由一個(gè)窗口和兩個(gè)由涂層硅制成的矩形槽組成,它允許紅外線(xiàn)通過(guò)并阻擋任何其他輻射。傳感器的設(shè)計(jì)使得一個(gè)可以抵消另一個(gè),這樣傳感器就可以抵消環(huán)境輻射并檢測(cè)輻射模式的變化。
當(dāng)沒(méi)有檢測(cè)到運(yùn)動(dòng)時(shí),產(chǎn)生的輸出信號(hào)為零,因?yàn)閭鞲衅髡跈z測(cè)背景輻射。但是,當(dāng)傳感器的任何一半截獲運(yùn)動(dòng)時(shí),都會(huì)導(dǎo)致傳感器兩部分之間的電壓電平發(fā)生變化,這就是檢測(cè)運(yùn)動(dòng)的方式。
菲涅耳透鏡
菲涅耳透鏡由一系列刻在塑料上的同心凹槽組成。這些輪廓充當(dāng)單獨(dú)的折射表面,在焦點(diǎn)處聚集平行光線(xiàn)。因此,菲涅耳透鏡能夠像傳統(tǒng)光學(xué)透鏡一樣聚焦光線(xiàn)。
實(shí)際上,為了增加 PIR 傳感器的范圍和視野,透鏡被分成幾個(gè)面部分,每個(gè)部分都是一個(gè)單獨(dú)的菲涅爾透鏡。
HC-SR501 PIR 運(yùn)動(dòng)傳感器模塊引出線(xiàn)
HC-SR501 模塊具有三個(gè)引腳。模塊絲印被菲涅耳透鏡遮擋,請(qǐng)參考下面給出的引腳排列。施加 5V – 12V 電源和接地,傳感器輸出在檢測(cè)到運(yùn)動(dòng)時(shí)變?yōu)楦唠娖?,在空閑時(shí)變?yōu)榈碗娖剑ㄎ礄z測(cè)到運(yùn)動(dòng))。
觸發(fā)器選擇跳線(xiàn)
有兩種觸發(fā)模式?jīng)Q定傳感器在檢測(cè)到運(yùn)動(dòng)時(shí)如何反應(yīng)。
單觸發(fā)模式:持續(xù)運(yùn)動(dòng)將導(dǎo)致單觸發(fā)。
多重觸發(fā)模式:不斷的運(yùn)動(dòng)會(huì)引起一系列的觸發(fā)。
L - 在此設(shè)置中,傳感器將處于單觸發(fā)模式,在此模式下,當(dāng)檢測(cè)到運(yùn)動(dòng)時(shí)輸出變高。并在延時(shí)電位器設(shè)定的一定時(shí)間內(nèi)保持高電平。任何其他類(lèi)型的檢測(cè)都會(huì)被阻止,直到輸出變低。
H - 選擇這些設(shè)置將設(shè)置多重觸發(fā)模式。在這種模式下,當(dāng)檢測(cè)到運(yùn)動(dòng)時(shí)輸出變高,高電平周期由設(shè)置的電位器決定。但與單觸發(fā)模式不同的是,進(jìn)一步檢測(cè)不會(huì)被阻止并且可以連續(xù)觸發(fā),當(dāng)未檢測(cè)到移動(dòng)時(shí),引腳變?yōu)榈碗娖健?/p>
靈敏度調(diào)整
PIR 傳感器背面有一個(gè)電位器,用于調(diào)節(jié)靈敏度。在電位器的幫助下,可以調(diào)整設(shè)備的靈敏度。順時(shí)針旋轉(zhuǎn)電位器會(huì)增加靈敏度,逆時(shí)針旋轉(zhuǎn)電位器會(huì)降低靈敏度。
延時(shí)調(diào)整
傳感器背面的另一個(gè)鍋設(shè)置輸出將保持高電平的時(shí)間以及在檢測(cè)到運(yùn)動(dòng)后順時(shí)針轉(zhuǎn)動(dòng)鍋會(huì)增加延遲,逆時(shí)針轉(zhuǎn)動(dòng)鍋會(huì)減少延遲。
3.3V 穩(wěn)壓器
該模塊帶有一個(gè) 3.3V 穩(wěn)壓器,因此它可以由 4.5V 至 12V 電源供電。雖然 5V 是常用的。
保護(hù)二極管
該模塊帶有一個(gè)保護(hù)二極管,用于保護(hù)二極管免受反向電壓和電流的影響。
HC-SR501 PIR 傳感器模塊故障排除
PIR 傳感器無(wú)法正常工作可能有多種原因。需要通過(guò)一些測(cè)試來(lái)找出問(wèn)題的根本原因。
傳感器的工作電壓為 4.8V 至 20V,因此無(wú)法使用 3.3V 為傳感器供電。
在某些情況下會(huì)看到鏡頭頂部積聚了灰塵,因此 PIR 傳感器可能會(huì)停止工作。
如果上述方法均無(wú)效,請(qǐng)嘗試旋轉(zhuǎn)電位器。如果將電位計(jì)的靈敏度設(shè)置為最低,那么這可能是傳感器不工作的原因。
在測(cè)試了所有方法后,如果傳感器不工作,那么您可以確定您手中的傳感器有故障。
部分代碼
#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:構(gòu)建項(xiàng)目并實(shí)現(xiàn) Ai-M61-32S 與 人體紅外感應(yīng)模塊 HC-SR501 連接,并獲取狀態(tài)值。
默認(rèn)燈光是關(guān)閉的:
檢測(cè)有人經(jīng)過(guò)時(shí),紅燈亮起。
03
XFS5152CE語(yǔ)音合成篇
TTS 語(yǔ)音模塊:XFS5152CE 語(yǔ)音合成模塊
TTS 是 Text To Speech 的縮寫(xiě),即“從文本到語(yǔ)音”,是人機(jī)對(duì)話(huà)的一部分,讓機(jī)器能夠說(shuō)話(huà)。
語(yǔ)音播報(bào)功能的實(shí)現(xiàn)方式* TTS 語(yǔ)音模塊,比如 XFS5152、SYN6288 等
ISD4000 系列語(yǔ)音錄放芯片分段輸出
可以按鍵、UART 控制的 mp3 解碼芯片模塊
OTP(One Time Programable)語(yǔ)音芯片[定制]
其中 TTS 語(yǔ)音模塊使用起來(lái)最方便靈活,OTP 語(yǔ)音芯片最簡(jiǎn)單。
由于項(xiàng)目使用的是 XFS5152CE,所以簡(jiǎn)單介紹一下科大訊飛的 XFS5152CE 語(yǔ)音合成模塊。
性能描述
1 采用 XFS5152CE 語(yǔ)音合成芯片,支持任意中文文本、英文文本合成及中英混讀。
2 支持文本控制標(biāo)記設(shè)置,使用便捷,同時(shí)提升了文本處理的正確率。
3具有文本智能分析處理功能,對(duì)常見(jiàn)的數(shù)字、號(hào)碼、時(shí)間、日期、度量衡符號(hào)等能正確的識(shí)別和處理。
4.具有很強(qiáng)的多音字和中文姓氏處理能力。
5.支持內(nèi)置多款播音人聲可供選擇。
6.支持 10 級(jí)語(yǔ)速調(diào)節(jié)。
7.支持 10 級(jí)音調(diào)調(diào)節(jié)。
8.支持 10 級(jí)音量調(diào)節(jié)。
9.支持 GB2312、GBK、BIG5 和 UNICODE 四種編碼方式。
10.每次合成的文本量多達(dá) 4K 字節(jié)。
11.集成 80 種常用提示音效,適用于不同場(chǎng)合的信息提示、鈴聲、警報(bào)
等功能。
12.支持多種控制命令,如合成文本、停止合成、狀態(tài)查詢(xún)等。
13.板載揚(yáng)聲器。
14.支持三種連接方式:杜邦線(xiàn)接口、鱷魚(yú)夾接口、PH2.0 防呆接口。
15.通信方式:IIC 通信。
16.12C 地址:0x50[新版本 0x30]。
由于協(xié)議使用的是 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 中的方法,主要是獲取高位和低位數(shù)據(jù)
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
};
默認(rèn)是 GLB_GPIO_PIN_14、GLB_GPIO_PIN_15.然后封裝.
XFS5152CE 模塊功能代碼
XFS.h
#ifndef __XFS_H #define __XFS_H #include#include #include #include #include #include #include //Added for uint_t #include #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)| 數(shù)據(jù)區(qū)長(zhǎng)度(2Byte)| 數(shù)據(jù)區(qū)(<4KByte) | *| | 高字節(jié) | 低字節(jié) | 命令字 | 文本編碼格式 | 待合成文本 | *| 0xFD | 0xHH | 0xLL | 0x01 | 0x00~0x03 | ... ... | */ typedef enum { CMD_StartSynthesis = 0x01,//語(yǔ)音合成命令 CMD_StopSynthesis = 0x02,//停止合成命令,沒(méi)有參數(shù) CMD_PauseSynthesis = 0x03,//暫停合成命令,沒(méi)有參數(shù) CMD_RecoverySynthesis = 0x04,//恢復(fù)合成命令,沒(méi)有參數(shù) CMD_CheckChipStatus = 0x21,//芯片狀態(tài)查詢(xún)命令 CMD_PowerSavingMode = 0x88,//芯片進(jìn)入省電模式 CMD_NormalMode = 0xFF//芯片從省電模式返回正常工作模式 } CMD_Type;//命令字 void StartSynthesis(const char* str);//開(kāi)始合成 // void StartSynthesis(String str);//開(kāi)始合成 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();//恢復(fù)合成 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,//收到不能識(shí)別命令幀回傳 ChipStatus_Busy = 0x4E,//芯片忙碌狀態(tài)回傳 ChipStatus_Idle = 0x4F//芯片空閑狀態(tài)回傳 } ChipStatus_Type;//芯片回傳 typedef enum { Style_Single,//?為 0,一字一頓的風(fēng)格 Style_Continue//?為 1,正常合成 } Style_Type; //合成風(fēng)格設(shè)置 [f?] void SetStyle(Style_Type style); typedef enum { Language_Auto,//? 為 0,自動(dòng)判斷語(yǔ)種 Language_Chinese,//? 為 1,阿拉伯?dāng)?shù)字、度量單位、特殊符號(hào)等合成為中文 Language_English//? 為 2,阿拉伯?dāng)?shù)字、度量單位、特殊符號(hào)等合成為英文 } Language_Type; //合成語(yǔ)種設(shè)置 [g?] void SetLanguage(Language_Type language); typedef enum { Articulation_Auto,//? 為 0,自動(dòng)判斷單詞發(fā)音方式 Articulation_Letter,//? 為 1,字母發(fā)音方式 Articulation_Word//? 為 2,單詞發(fā)音方式 } Articulation_Type; //設(shè)置單詞的發(fā)音方式 [h?] void SetArticulation(Articulation_Type articulation); typedef enum { Spell_Disable,//? 為 0,不識(shí)別漢語(yǔ)拼音 Spell_Enable//? 為 1,將“拼音+1 位數(shù)字(聲調(diào))”識(shí)別為漢語(yǔ)拼音,例如:hao3 } Spell_Type; //設(shè)置對(duì)漢語(yǔ)拼音的識(shí)別 [i?] void SetSpell(Spell_Type spell); typedef enum { Reader_XiaoYan = 3,//? 為 3,設(shè)置發(fā)音人為小燕(女聲, 推薦發(fā)音人) Reader_XuJiu = 51,//? 為 51,設(shè)置發(fā)音人為許久(男聲, 推薦發(fā)音人) Reader_XuDuo = 52,//? 為 52,設(shè)置發(fā)音人為許多(男聲) Reader_XiaoPing = 53,//? 為 53,設(shè)置發(fā)音人為小萍(女聲) Reader_DonaldDuck = 54,//? 為 54,設(shè)置發(fā)音人為唐老鴨(效果器) Reader_XuXiaoBao = 55//? 為 55,設(shè)置發(fā)音人為許小寶(女童聲) } Reader_Type;//選擇發(fā)音人 [m?] void SetReader(Reader_Type reader); typedef enum { NumberHandle_Auto,//? 為 0,自動(dòng)判斷 NumberHandle_Number,//? 為 1,數(shù)字作號(hào)碼處理 NumberHandle_Value//? 為 2,數(shù)字作數(shù)值處理 } NumberHandle_Type; //設(shè)置數(shù)字處理策略 [n?] void SetNumberHandle(NumberHandle_Type numberHandle); typedef enum { ZeroPronunciation_Zero,//? 為 0,讀成“zero ZeroPronunciation_O//? 為 1,讀成“歐”音 } ZeroPronunciation_Type; //數(shù)字“0”在讀 作英文、號(hào)碼時(shí) 的讀法 [o?] void SetZeroPronunciation(ZeroPronunciation_Type zeroPronunciation); typedef enum { NamePronunciation_Auto,//? 為 0,自動(dòng)判斷姓氏讀音 NamePronunciation_Constraint//? 為 1,強(qiáng)制使用姓氏讀音規(guī)則 } NamePronunciation_Type; //設(shè)置姓名讀音 策略 [r?] void SetNamePronunciation(NamePronunciation_Type namePronunciation); void SetSpeed(int speed);//設(shè)置語(yǔ)速 [s?] ? 為語(yǔ)速值,取值:0~10 void SetIntonation(int intonation);//設(shè)置語(yǔ)調(diào) [t?] ? 為語(yǔ)調(diào)值,取值:0~10 void SetVolume(int volume);//設(shè)置音量 [v?] ? 為音量值,取值:0~10 typedef enum { PromptTone_Disable,//? 為 0,不使用提示音 PromptTone_Enable//? 為 1,使用提示音 } PromptTone_Type; //設(shè)置提示音處理策略 [x?] void SetPromptTone(PromptTone_Type promptTone); typedef enum { OnePronunciation_Yao,//? 為 0,合成號(hào)碼“1”時(shí)讀成“幺 OnePronunciation_Yi//? 為 1,合成號(hào)碼“1”時(shí)讀成“一” } OnePronunciation_Type; //設(shè)置號(hào)碼中“1”的讀法 [y?] void SetOnePronunciation(OnePronunciation_Type onePronunciation); typedef enum { Rhythm_Diasble,//? 為 0,“ *”和“#”讀出符號(hào) Rhythm_Enable//? 為 1,處理成韻律,“*”用于斷詞,“#”用于停頓 } Rhythm_Type; //是否使用韻律 標(biāo)記“*”和“#” [z?] void SetRhythm(Rhythm_Type rhythm); void SetRestoreDefault();//恢復(fù)默認(rèn)的合成參數(shù) [d] 所有設(shè)置(除發(fā)音人設(shè)置、語(yǔ)種設(shè)置外)恢復(fù)為默認(rèn)值 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#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 ", ChipStatus); return ChipStatus; } bool IIC_WriteByte(uint8_t data) { beginTransmission(I2C_Addr); write_char(data); endTransmission(); // if(endTransmission()!=0) //發(fā)送結(jié)束信號(hào) // { // 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); } }
語(yǔ)音播報(bào)內(nèi)容 文件頭 speak.h,該文件是為了方式播報(bào)內(nèi)容亂碼錯(cuò)亂。文件需要保存為 ANSI
char* hello[] = {"主人", "出門(mén)記得帶手機(jī)和鑰匙"};
主程序代碼 :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" /*超時(shí)設(shè)置,示例為30S*/ static uint32_t LastSpeakTime = 0; #define SpeakTimeOut 10000 /** @brief 初始化語(yǔ)音合成 @param 無(wú) @retval 無(wú) */ uint8_t n = 1; static void XFS_Init() { Begin(0x30);//設(shè)備i2c地址,地址為0x50 bflb_mtimer_delay_ms(n); SetReader(Reader_XuXiaoBao); //設(shè)置發(fā)音人 bflb_mtimer_delay_ms(n); SetEncodingFormat(GB2312); //文本的編碼格式 bflb_mtimer_delay_ms(n); SetLanguage(Language_Auto); //語(yǔ)種判斷 bflb_mtimer_delay_ms(n); SetStyle(Style_Continue); //合成風(fēng)格設(shè)置 bflb_mtimer_delay_ms(n); SetArticulation(Articulation_Letter); //設(shè)置單詞的發(fā)音方式 bflb_mtimer_delay_ms(n); SetSpeed(6); //設(shè)置語(yǔ)速1~10 bflb_mtimer_delay_ms(n); SetIntonation(5); //設(shè)置語(yǔ)調(diào)1~10 bflb_mtimer_delay_ms(n); SetVolume(10); //設(shè)置音量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); //設(shè)置發(fā)音人; 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 sdk_add_compile_definitions(-DCONFIG_CLI_CMD_ENABLE) #sdk_add_compile_definitions(-DBL616_DHCP_DEBUG) target_sources(app PRIVATE Wire.c XFS.c) sdk_add_include_directories(.) sdk_set_main_file(main.c) project(helloworld)
開(kāi)發(fā)板:Ai-M61-32S
相關(guān)功能模塊封裝完成,下一步硬件連線(xiàn)。
Ai-M61-32S GPIO 接口定義。
Ai-M61-32S | XFS5152CE |
3.3v | VCC |
GLB_GPIO_PIN_30 | SCL |
GLB_GPIO_PIN_31 | SDA |
GND | GND |
實(shí)物圖:
連接部分:
紅外感應(yīng):
完整 組件結(jié)構(gòu):
到這里基于 Ai-M61-32S “出門(mén)提醒設(shè)備 ”軟硬件已完成。下一步就是外殼了。
04
外殼
外殼選材小插曲
刷 B 站的時(shí)候曾經(jīng)看過(guò)一個(gè)防水盒做音箱的視頻,就跟著做了一個(gè)感覺(jué)很有趣。就買(mǎi)了一些防水盒。
某寶簽到紅包1.5-3塊錢(qián),直接抵下來(lái)幾毛錢(qián)一個(gè)比 3D 打印什么的性?xún)r(jià)比高很多。唯一缺點(diǎn)就是外觀比較固定。
這個(gè)是 DIY 的小音箱。用的是“藍(lán)牙驅(qū)動(dòng)板 m38 藍(lán)牙模塊” 4 快多買(mǎi)的。可以藍(lán)牙連接,也可以當(dāng) USB 聲卡使用還是不錯(cuò)的。側(cè)面改裝成了 Type-C 接口,內(nèi)置了 18650 電池。插著電源開(kāi)關(guān)關(guān)掉就可以了。開(kāi)著開(kāi)關(guān)也沒(méi)關(guān)系。用 mos 管做了隔離。插著線(xiàn)的情況下電池不給設(shè)備供電。
拔掉 USB 線(xiàn),電池供電。基本可以無(wú)縫銜接。
下面言歸正傳,選用的是 835833mm 尺寸的防水盒,用了給的紅包抵扣后價(jià)格 0.17 元。還可以。
擺了擺感覺(jué)尺寸剛剛好。
新鉛筆,用小孩兒的卷筆刀削一下。
拿出尺子、圓規(guī)。
畫(huà)上圈圈,定好位置。
然后拿把剪刀,搞就完了。
最終效果。下面“ XFS5152CE” 語(yǔ)音合成模塊的那個(gè)孔開(kāi)始想用其他方法開(kāi)孔結(jié)果給弄壞了。
最后用剪刀固定一點(diǎn)一直轉(zhuǎn),圓孔就出來(lái)了,也算是漲經(jīng)驗(yàn)了。
開(kāi)孔后接線(xiàn)。
最終效果展示,快來(lái)圍觀!
-
傳感器
+關(guān)注
關(guān)注
2552文章
51353瀏覽量
755562 -
PIR
+關(guān)注
關(guān)注
0文章
75瀏覽量
17894 -
HC-SR501
+關(guān)注
關(guān)注
9文章
16瀏覽量
41980
原文標(biāo)題:【DIY電子作品】基于 Ai-M61-32S做一個(gè)出門(mén)提醒設(shè)備(匯總篇)
文章出處:【微信號(hào):安信可科技,微信公眾號(hào):安信可科技】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論