BLE核心模塊FS-QN9021模塊開發-linux版

這段時間又參與了一個新的小項目,簡單概括爲藍牙、智能、家居吧,雖然時間有點緊,還是希望能把這一些東西記錄下來。


####BLE 什麼是BLE?參考這篇文章做如下總結。 
中文名稱爲藍牙低功耗。主要特點爲低成本、超低功耗、短距離、標準接口和可互操作性強,並且工作在免許可的2.4GHz ISM射頻段,需要支持藍牙4.0(系統爲Android4.3及以上)的主機設備才能與其連接。

目前生產BLE芯片的廠家主要有CSR、TI、Nodic和NXP(QN902x),各個廠家芯片對比如下圖

s

從如上圖對比可以看出,NXP的QN902x在功耗方面比CSR和TI更省電,在接收靈敏度和模式方面比Nodic的勝一籌,它的從設備相比其它幾家可以連接的更多,共有8個,這也算是藍牙4.0的一大特色吧,並且NXP的芯片已經過了MFI認證,直接能與蘋果設備相連接,因爲這種認證也是挺貴的。

因爲BLE的低功耗、低成本及強大的處理能力,並且隨着iPhone的設備支持藍牙4.0,BLE的終端設備在我們的生活當中將會越來越多,在未來將會有爆發式增長。

QN902X是一款內核爲M0的藍牙BLE SOC芯片,其SDK對藍牙BLE的profile都有實現,並提供源碼,SDK也提供很多工具以便使用芯片,比如引腳配置,NVDS讀寫,串口USB Dongle(配合上位機可以調試各profile,沒有手機也可以調試),ISP下載等,但QN902X不提供數據手冊,所有的外設操作都以庫的方式提供,SDK說明比較全面但全是英文的。

與NODRDIC的51822和TI的CC2540不同QN902X的架構是M0+ROM+FLASH+SRAM的方式,其中ROM放的是藍牙協議和內部一個小的調度核,FLASH放的是用戶程序和數據,RAM用於跑程序。其中ROM:96K,SRAM:64K,FLASH:64K/128K。因爲QN902X程序是跑到SRAM中,所以它的深度睡眠電流比較大些。


####環境搭建 老生長談,開發一款產品,第一步當然是搭建開發環境咯。

#####wine安裝 由這裏可知,keil不支持linux環境,所以必須自己想辦法了。這是在linux環境下運行windows的程序,本身使用windows的請自動忽略這一步。具體請看官網/博文

~ $ sudo add-apt-repository ppa:ubuntu-wine/ppa
~ $ sudo apt-get update
# => 我的是64爲的系統,所有安裝64位版
~ $ sudo apt-get install wine1.8-amd64
# => 配置
~ $ winecfg
# => 中文路徑:~/ .wine/drive_c/windows/Fonts/
~ $ winetricks corefonts

#####keil安裝 首先得下載keil,可以自己去官網下載最新的,也可以直接點擊mdk5.17下載地址,參考這篇博文並實踐做如下記錄。

# => 進入下載目錄安裝
~ $ wine mdk517.exe

提示:卸載程序可以用wine uninstaller 
如編譯有限制,下載破解註冊機Keygen:http://pan.baidu.com/s/1hqGSRqs

安裝完成後,會彈出來一個安裝器件(pack installer)如下的界面,也就是說,你要用它來開發哪個芯片(此項目可以忽略,後面步驟導入DB)。 packin


或者打開keil界面會看到如下圖標 packicon


要開發哪一款芯片,點擊install即可,或者先網頁端下載好在導入,具體參考上面提供的博文地址。

#####MCU DB庫安裝 下載Quintic最新的SDKQBlue1.3.7,安裝:

~ $ wine QBlue-1.3.7.exe

會彈出窗口如下,點擊安裝即可,或者打開桌面QBlue裏的QN9020DevDBforIDE工具安裝。

sdk

#####keil使用 首先獲取開源代碼

~ $ cd
~ $ git clone [email protected]:T-Firefly/fireble.git

桌面打開keil,假如我們希望開始proxr工程:

  1. 在keil的Project菜單中選擇Open Project…
  2. 彈出文件選擇框中,打開/home/xxx/fireble/BLE/prj_proxr/keil/proxr.uvproj工程文件(linux可能在z磁盤裏)
  3. 配置DB如下,如果沒發現庫,請重啓軟件

    dbset

  4. 編譯代碼,成功後如下,具體配置選項請參考這裏

    compile

  5. 下載程序,下載的時候需要按復位鍵,如下載出錯,可先下載到SRAM。

    dl

Tip:在ubuntu 上串口識別爲ttyS0或ttyUSB0之類,在wine上識別不到,可用:

# =>將其該爲小寫的com1,如果不行,將其改爲大寫的COM1
~ $ sudo ln -s /dev/ttyUSB0 ~/.wine/dosdevices/com1
~ $ sudo usermod -a -G dialout $USER
~ $ sudo chmod 777 ~/.wine/dosdevices/com1

即可在wine的應用程序使用串口


####項目實踐 #####點亮LED 對於新的芯片與開發板,從LED實驗開始。首先下載該開發板的原理圖,對該LED部分的電路進行分析。

led

本程序非常簡單,複製gpio demo代碼

gpio

實現讓開發板D1燈閃爍如下:

/* Set pin D1/P2_7 */
gpio_set_direction_field(GPIO_P27, (uint32_t)GPIO_OUTPUT);
while(1)
{
	gpio_write_pin_field(GPIO_P27, GPIO_LOW);
	delay(100000);
	gpio_write_pin_field(GPIO_P27, GPIO_HIGH);
	delay(100000);
}

程序流程:系統初始化–>GPIO配置–>各驅動模塊初始化–>主循環實現功能 
效果如下:

led

#####UART實驗 串口通信可以用來打印數據,調試程序,有必要實驗一下。 
同樣複製uart demo代碼 
改程序如下:

//Print out "Hello NXP!\n" thought uart.
uart_printf(QN_UART0, (uint8_t *)"Hello NXP!\n");
uart_printf(QN_UART0, (uint8_t *)"This is nephen's test!\n");

波特率設置爲115200,現象如下: uart_tst

#####PWM實驗 由於這個項目會控制到電機什麼的,所以pwm少不了,這個實驗是通過pwm控制陶瓷蜂鳴器報警和呼吸燈。通過PWM方式調節脈衝頻率和佔空比,變換LED亮度,漸變亮度實現呼吸燈效果。FireBLE板載的貼片蜂鳴器是壓電式陶瓷蜂鳴器,壓電陶瓷蜂鳴器要想響起來,需要滿足四個條件:多諧振盪器、壓電蜂鳴片、阻抗匹配器及共鳴箱,壓電蜂鳴片由鋯鈦酸鉛或鈮鎂酸鉛壓電陶瓷材料製成,在陶瓷片的兩面鍍上銀電極,經極化和老化處理後,再與黃銅片或不鏽鋼片粘在一起。板載的蜂鳴器缺少的只是多謝振盪器,這裏我們用PWM來代替,輸出1.5KHz-2.5KHz的方波信號推動壓電蜂鳴片發聲,頻率在1.5KHz-2.5KHz纔會響,太高太低都不響,直接加3.3V更不會響,直接加3.3V響的那是電磁式蜂鳴器,有源和無源。 
首先看下電路連接

buzz

同樣複製pwm代碼,改寫如下:

int main (void)
{
    SystemInit();

    pwm_init(PWM_CH0);
    pwm_io_config();
    //P2.7 will output pwm wave with period for 1000us and pulse for 400us
    pwm_config(PWM_CH0, PWM_PSCAL_DIV, PWM_COUNT_US(1000, PWM_PSCAL_DIV), PWM_COUNT_US(500, PWM_PSCAL_DIV));
    pwm_enable(PWM_CH0, MASK_ENABLE);
    pwm_io_dis_config();

    pwm_init(PWM_CH1);
    pwm_io_config();
    //P2.6 will output pwm wave with period for 1000us and pulse for 500us
    pwm_config(PWM_CH1, 119, PWM_COUNT_US(1000, 119), PWM_COUNT_US(500, 119));
    pwm_enable(PWM_CH1, MASK_ENABLE);

    while (1)                                /* Loop forever */
    {
			int i;
			for(i=0;i<=1000;i++)
			{
				pwm_config(PWM_CH1, 119, PWM_COUNT_US(1000-i, 119), PWM_COUNT_US(500, 119));
				if(i%2)
					pwm_config(PWM_CH0, PWM_PSCAL_DIV, PWM_COUNT_US(1000-i, PWM_PSCAL_DIV), PWM_COUNT_US(500, PWM_PSCAL_DIV));
				else
					pwm_config(PWM_CH0, PWM_PSCAL_DIV, PWM_COUNT_US(i, PWM_PSCAL_DIV), PWM_COUNT_US(500, PWM_PSCAL_DIV));
				delay(2000);
			}
    }
}

呼吸燈效果:

breath

#####按鍵廣播 看到有些人對買的fireBLE按鍵廣播不懂,其實剛開始我也是這樣的,摸不着頭腦,現在至少不會太迷糊,就給大家記錄下吧。 
首先按鍵按下屬於gpio中斷,找到中斷初始化函數:SystemInit(); ——》 
gpio_init(gpio_interrupt_callback); ——》 
void gpio_interrupt_callback(enum gpio_pin pin) ——》 
void usr_button1_cb(void) ——》 
ke_evt_set(1UL « EVENT_BUTTON1_PRESS_ID); 找到EVENT_BUTTON1_PRESS_ID對應的事件,搜索 EVENT_BUTTON1_PRESS_ID,找到void usr_init(void)裏的if(KE_EVENT_OK != ke_evt_callback_set(EVENT_BUTTON1_PRESS_ID, app_event_button1_press_handler))可知爲app_event_button1_press_handler ——》 
ke_timer_set(APP_KEY_SCAN_TIMER,TASK_APP,2); 同樣通過搜索APP_KEY_SCAN_TIMER可知定時器事件爲app_key_scan_timer_handler ——》 
app_key_scan_timer_handler,這裏進行adc採集,完成後由adc_read(&read_cfg, adc_key_value, KEY_SAMPLE_NUMBER, adc_test_cb);可知進入adc_test_cb,在這裏設置了EVENT_ADC_KEY_SAMPLE_CMP_ID,搜索找到對應事件app_event_adc_key_sample_cmp_handler ——》 
app_event_adc_key_sample_cmp_handler,判斷按鍵朝哪個方向按,定時回調 ——》 
app_key_process_timer_handler,如下:

int app_key_process_timer_handler(ke_msg_id_t const msgid, void const *param,
                                  ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
	ke_evt_clear(1UL << EVENT_ADC_KEY_SAMPLE_CMP_ID);
	switch (key_value0)
	{

	case	key_up:
		if (APP_IDLE == ke_state_get(TASK_APP))
		{

			// start adv
			//QPRINTF("you press up key\r\n!");
			app_gap_adv_start_req(GAP_GEN_DISCOVERABLE | GAP_UND_CONNECTABLE,
			                      app_env.adv_data, app_set_adv_data(GAP_GEN_DISCOVERABLE),
			                      app_env.scanrsp_data, app_set_scan_rsp_data(app_get_local_service_flag()),
			                      GAP_ADV_FAST_INTV1, GAP_ADV_FAST_INTV2);

這裏一看就明白,向上按時,進入廣播。其實在系統上電的時候,已經進行了一系列的gap建立連接的初始話,就差廣播了,所以這裏只要廣播出去就能和別的藍牙建立連接。

#####QTool使用及藍牙瞭解 使用QBlueStudio中QTool工具進行藍牙開發分析,可以方便的對各個藍牙操作的過程進行細緻的研究,並結合具體的源代碼進行查看,能夠更加深入的瞭解到藍牙協議的實現過程,大部分的API接口在GAP和GATT。具體的使用文檔請查看QBlueStudio裏的Document。Linux裏可以採取這種方法查看:地址見/home/username/.wine/drive_c/QBlue/QN9020/QBlue-1.3.7/Documents,然後使用evince命令打開。

在這之前,還需對藍牙的一些術語做一個大概的瞭解,比如什麼是master,slave,主機。客戶端與服務器又是什麼。GAP與GATT有什麼區邊。藍牙各個協議層都有哪些分工。下面參考做一些歸納:

  1. BLE規範中定義了GAP(Generic Access Profile)和GATT(Generic Attribute)兩個基本配置文件。 
    a.協議中的GAP層負責設備訪問模式和進程,包括設備發現,建立連接,終止連接。初始化安全特性和設備配置。 
    b.GATT層用於已連接的藍牙設備之間的數據通信。GATT通俗理解爲用於主從機之間的客戶端和服務器端的數據交互,以Attribute Table來體現。

  2. BLE低功耗藍牙中有四種設備類型,Central主機,Peripheral從機,Observer觀察者,Broadcaster廣播者。通常Central主機,Peripheral從機一起使用,Observer觀察者,Broadcaster廣播者一起使用。Central和Peripheral連接交換數據,平時我們使用到的基本上是這種模式。而像多溫度採集器,通常使用Observer和Broadcaster這種無連接形式。 
    主機和從機是這樣開機工作的:從機開始廣播,然後主機掃描廣播的從機,當從機收到主機的掃描請求後,會向主機發送掃描迴應數據。然後主機發起連接,然後開始通訊。所以從機需要設置廣播內容和掃描迴應內容。這方面的代碼可以查看App_gap.c和App_gap_task.c。
    • profile:可以理解爲一種規範,一個標準的通信協議,profile存在於從機中。藍牙組織規定了一系列的標準profile,如防丟計、心率計。每個profile中包含多個service,每個service代表從機的一種能力。
    • Service:可以理解爲一種服務,在BLE從機裏,通過有多個服務,例如電量信息服務、系統信息服務等。每個service又包含多個characteristic特徵值。每個具體的characteristic特徵值纔是BLE通訊的主體,比如當前電量是80%。所以會通過電量的characteristic特徵值特徵值保存在從機的profile裏,這樣主機就可以通過這個characteristic來讀取80%這個數。
    • characteristic:characteristic特徵值,BLE主從機均是通過characteristic來實現,可以理解爲一個標籤,通過這個標籤可以獲取或者寫入想要的內容。
    • UUID:統一標識嗎,我們剛提到的characteristic和service,都需要一個唯一的UUID來標識。

    每個從機都會有個叫做profile的東西存在,不管自定義的還是標準的profile,他們都是由一系列的Service組成,然後每個service又包含多個characteristic,主機和從機之間的通信,均是通過characteristic來實現。 
    BLE協議棧中傳輸數據分爲兩方面,一個GATT的client主動向service發送數據。另一個是GATT的service主動向client發送數據。即主從之前相互傳數據。 
    主機向從機發送數據,使用GATT_Write 
    從機向主機發送數據,使用GATT_Notification

  3. GATT有Service和Client,Service作爲服務器端,對GATT Client提供read/write接口,一般情況下,Central作爲Client,Peripheral作爲Service。所以主機會調用read/write來和作爲Service端的Peripheral從機通訊。而Peripheral則通過notify的方式即調用GATT_Notifycation發起和主機通訊。
  4. 特徵值聲明值可以有五種屬性:Read(可讀) Write(可靠的可寫,帶響應) Write without resp(不可靠的可寫,不帶響應) Indicate(可靠通知,帶響應) Notify(不可靠通知,不帶響應)

更多請查看藍牙設計問與答 /Android 藍牙4.0 BLE 理解

BLE中主從機建立連接,到配對和綁定的過程如下圖。 

#####QPPS工程 在此之前,建議先了解一下Firefly的QPPS介紹/對QPPS profile中服務和特徵實現的分析理解/對profile QPPS的分析理解。關於藍牙的數據收發部分可以看Firefly的協議棧介紹。數據幀格式如下:

Tip:技術案例中串口透傳案例,實現的是將藍牙模組串口所接收到的數據透傳到app,並且把app的數據通過藍牙透傳到串口輸出,案例本身的實現是針對串口通信和藍牙透傳的結合。

  1. 其實BLE完成初始化的條件並不是進入main函數的while(1)中(系統在調度了幾個消息之後才完成的初始化,前幾次進入while(1)時初始化都是沒完成的),真正完成初始化並且可以運行是在打印BLE is Ready這句話的地方。那個地方你可以看到只要開啓QN_DEMO_AUTO宏定義就可以上電廣播了。
  2. 設備名默認從NVDS中寫入,但是軟件也可以修改,可以用app_gap_set_devname_req修改設備名。
  3. 如果你非要用軟件指定,參考廣播內容設定,取消NVDS讀取,直接利用app_gap_set_devname_req函數指定設備名,然後將QN_LOACL_NAME廣播出去,那麼設備名和廣播中的設備名都會是QN_LOCAL_NAME了。

    具體實施如下

    修改自動廣播,無需向上撥動按鍵:

    c #=> App_config.h中,取消如下的註釋即可 #define QN_DEMO_AUTO 1 在廣播之前改變設備的名字,注意app_set_adv_data函數

    c //Set remote device name app_gap_set_devname_req("nephen",6); // Created DB should has been finished by each profile service, // Start Adv mode automatically here app_gap_adv_start_req(GAP_GEN_DISCOVERABLE|GAP_UND_CONNECTABLE, app_env.adv_data, app_set_adv_data(GAP_GEN_DISCOVERABLE), app_env.scanrsp_data, app_set_scan_rsp_data(app_get_local_service_flag()), GAP_ADV_FAST_INTV1, GAP_ADV_FAST_INTV2);

  4. 接收手機app中9600可寫屬性發送的數據。 
    由下面Qpps_task.c可知,當有數據到達藍牙時,會觸發gatt_write_cmd_ind_handler,同理,發送數據觸發qpps_data_send_req_handler,得到通知觸發gatt_notify_cmp_evt_handler,app層對應的api是app_qpps_data_send_cfm_handlerapp_qpps_data_ind_handlerapp_qpps_cfg_indntf_ind_handler,下文會提到,只會對這些做修改

    c /// Connected State handler definition. const struct ke_msg_handler qpps_connected[] = { {QPPS_DATA_SEND_REQ, (ke_msg_func_t) qpps_data_send_req_handler}, {GATT_WRITE_CMD_IND, (ke_msg_func_t) gatt_write_cmd_ind_handler}, {GATT_NOTIFY_CMP_EVT, (ke_msg_func_t) gatt_notify_cmp_evt_handler}, }; 而在gatt_write_cmd_ind_handler函數裏,會對QPPS_IDX_RX_DATA_VAL屬性進行處理

    ```c else if (param->handle == (qpps_env.shdl + QPPS_IDX_RX_DATA_VAL)) { if (param->length <= QPP_DATA_MAX_LEN) { //inform APP of configuration change struct qpps_data_val_ind * ind = KE_MSG_ALLOC_DYN(QPPS_DAVA_VAL_IND, qpps_env.appid, TASK_QPPS, qpps_data_val_ind, param->length);

                 memcpy(&ind->conhdl, &(qpps_env.conhdl), sizeof(uint16_t));
                 //Send received data to app value
                 ind->length = param->length;
                 memcpy(ind->data, param->value, param->length);
    
                 ke_msg_send(ind);
             }
             else
             {
                 status = QPPS_ERR_RX_DATA_EXCEED_MAX_LENGTH;
             }
         }  ```
    

    再由上面的QPPS_DAVA_VAL_IND可知會跳到下面這個函數,在這裏對接收到的數據進行處理

    ```c int app_qpps_data_ind_handler(ke_msg_id_t const msgid, struct qpps_data_val_ind *param, ke_task_id_t const dest_id, ke_task_id_t const src_id) { uint8_t i; if (param->length > 0) { QPRINTF(“len=%d, I%02X”, param->length, param->data[0]); QPRINTF(“\r\n”); QPRINTF(“the receive data is :”); for(i=0;ilength;i++) { QPRINTF("%x",param->data[i]); } } QPRINTF("\r\n");

     return (KE_MSG_CONSUMED);  }  ```  當發送數據爲29時,CuteCom藍牙接收數據爲
    

    QN BLE is ready. Set device name complete Advertising start. Connection with 1FDA7E9F8E30 result is 0x0. LTK request indication idx is 0, auth_req is 0. Start encryption complete, idx 0, status 0, key_size 0, sec_prop 1, bonded 0. Slave update success. Update parameter complete, interval: 0xc, latency: 0x0, sup to: 0x12c. len=1, I29 the receive data is :29

  5. App收取藍牙通知。通過點擊app上5個特徵的通知,能獲取藍牙模塊的實時通知。Gatt層與通知相關的函數是qpps_data_send_req_handler,但我們只需要修改app開頭的api即可完成相應的開發,現象及分析如下: 
    當點擊app上的通知時,會進入如下的函數,爲了調試將其調整爲如下:

    ```c int app_qpps_cfg_indntf_ind_handler(ke_msg_id_t const msgid, struct qpps_cfg_indntf_ind *param, ke_task_id_t const dest_id, ke_task_id_t const src_id) { if (app_qpps_env->conhdl == param->conhdl) { QPRINTF(“enter app_qpps_cfg_indntf_ind_handler”); QPRINTF(“\r\n”); if (param->cfg_val == PRF_CLI_START_NTF) { QPRINTF(“enter PRF_CLI_START_NTF”); QPRINTF(“\r\n”); app_qpps_env->features |= (QPPS_VALUE_NTF_CFG « param->char_index); QPRINTF(“app_qpps_env->features is %d, param->char_index is %d”,app_qpps_env->features,param->char_index); QPRINTF(“\r\n”); // App send data if all of characteristic have been configured if (get_bit_num(app_qpps_env->features) == app_qpps_env->tx_char_num)//num is 5 { QPRINTF(“enter app_qpps_env->features”); QPRINTF(“\r\n”); app_qpps_env->char_status = app_qpps_env->features; app_test_send_data(app_qpps_env->tx_char_num - 1); } } else { app_qpps_env->features &= ~(QPPS_VALUE_NTF_CFG « param->char_index); app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG « param->char_index); } }

     return (KE_MSG_CONSUMED);  }  ```  其中,param->char_index代表第幾個可通知特徵,app_qpps_env->tx_char_num爲特徵的數量5,只有當所有特徵都進入通知狀態時藍牙模塊纔會發數據到手機app。     
    

    數據發送的過程見如下函數,這個函數中的數據val可以改爲項目中的需求,如IO口狀態

    ```c static void app_test_send_data(uint8_t max) { uint8_t cnt;

     QPRINTF("enter app_test_send_data");
     QPRINTF("\r\n");
     for (cnt = 0; (max != 0) && cnt < app_qpps_env->tx_char_num; cnt++)
     {
         if ((app_qpps_env->char_status >> cnt) & QPPS_VALUE_NTF_CFG)
         {
             static uint8_t val[] = {0, '0', '1', '2','3','4','5','6','7','8','9','8','7','6','5','4','3','2','1','0'};
    
             // Increment the first byte for test 
             val[0]++;
    
             max--;
             // Allow next notify until confirmation received in this characteristic
             app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG << cnt);
             app_qpps_data_send(app_qpps_env->conhdl, cnt, sizeof(val), val);
         }
     }  }  ```  數據發送後,有一個發送確認函數app_qpps_data_send_cfm_handler,發送成功後手機上看到的現象是這樣的
    

至此完成的功能爲,實時反應藍牙通知信息,app發數據控制藍牙模塊。


####QTool使用 QTool是一個運行在PC端的應用軟件,允許用戶啓動兩個BLE設備之間的連接,它能幫助用戶分析BLE藍牙。它是通過串口與BLE設備進行通信,通過ACI命令扮演網絡處理器的作用。

打開pdf使用文檔

~ $ cd /home/nephne/.wine/drive_c/QBlue/QN9020/QBlue-1.3.7/Documents
~ $ evince QBlue\ ISP\ Studio\ Manual\ v1.0.pdf

插入USB連接藍牙模塊,下載np_controller_B2.bin,然後打開QTool進入設備連接。關於界面說明見系統文檔。

例如:點擊Setting-Server-QPPS裏的Create DB,然後進入Setting-Mode裏點擊Advertising,即可實現如上QPPS工程的效果。而在旁邊的Local Device Traces窗口可以看到程序的大概運行過程。


####參考文檔 - FireBLE-wiki - BLE編程API參考手冊 - BLE軟件開發手冊 - 數據手冊 - QN9020快速入門 - QN902x周立功版 - 防丟器項目 - MDK-ARM PRO SET - 總有“一道菜”適合你——BLE - 【FireBLE試用體驗】體驗報告彙總 -FireBLE 開發板試用匯總貼(2015.11.29更新 共收錄145篇) [板塊置頂] [精華] - FireBLE驅動第一篇:呼吸燈 -FireBLE低功耗藍牙開發板評測 > 可穿戴手環+藍牙防丟器 - esp8266

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章