用戶在使用 nRF connect SDK 的時候經(jīng)常會操作的外設(shè)有GPIO,I2C,SPI,UART。我們就以 nRF connect SDK 2.7.0 中的例程代碼 nrfsamplesbluetoothperipheral_lbs 為基礎(chǔ),來演示上述外設(shè)的簡單使用。使用的硬件是開發(fā)板 nRF52840 DK.
準備工作
首先我們在原本的工程目錄的 boards 文件夾里,添加文件 nrf52840dk_nrf52840.overlay。通過這個文件我們可以修改 devicetree。 編譯完成后,我們可以查看 buildzephyrzephyr.dts,以確認devicetree 的更改是否生效。
我們還可以通過修改 prj.conf 來修改 Kconfig。編譯完成后,我們可以查看 buildzephyr.config 以確認 Kconfig 的更改是否生效。
GPIO 控制
首先我們演示如何刪除原有的按鍵和LED的 node 。按照下面的代碼,來修改 devicetree,就可以刪除 button3 和 led3。
/ { aliases { /delete-property/ sw3; /delete-property/ led3; }; }; /delete-node/ &button3; /delete-node/ &led3;
接著我們來更改控制 led2 的管腳。這里我們用 P0.04 控制 led2。
&led2 { gpios = &gpio0 4 GPIO_ACTIVE_LOW?>; };
最后我們添加一個用戶 GPIO 。這里添加了一個名為 user_gpios 的 node。然后又定義了 user_io0,它是 user_gpios 的 subnode。
/ { user_gpios { compatible = "gpio-leds"; user_io0: user_io0 { gpios = &gpio0 16 GPIO_ACTIVE_LOW?>; label = "user gpio 0"; }; }; };
我們不僅在 devicetree 里添加這個 GPIO ,還要在 main.c 里添加代碼使用這個GPIO。下面這句代碼中,我們聲明了結(jié)構(gòu)體變量 user_gpio0,并用宏 GPIO_DT_SPEC_GET 根據(jù) devicetree 里的定義初始化它。
const struct gpio_dt_spec user_gpio_0 = GPIO_DT_SPEC_GET(DT_NODELABEL(user_io0),gpios);
下面這段代碼中 gpio_is_ready_dt 是用來檢查 GPIO 的狀態(tài)是否是就緒。用函數(shù) gpio_pin_configure_dt 把 user_gpio_0 配置成輸出。gpio_pin_toggle_dt 用來翻轉(zhuǎn) GPIO。
if (!gpio_is_ready_dt(&user_gpio_0)) { printk("%s: device not ready.n", user_gpio_0.port->name); return 0; } gpio_pin_configure_dt(&user_gpio_0, GPIO_OUTPUT_ACTIVE); for (index = 0; index < 100; index++) { gpio_pin_toggle_dt(&user_gpio_0); k_sleep(K_MSEC(100)); }
從下面的代碼可以看出翻轉(zhuǎn) GPIO 這個操作有兩種 API 可以調(diào)用。二者的主要區(qū)別是 gpio_pin_toggle_dt 不需要指明引腳 。
/** * @brief Toggle pin level from a @p gpio_dt_spec. * * This is equivalent to: * * gpio_pin_toggle(spec->port, spec->pin); * * @param spec GPIO specification from devicetree * @return a value from gpio_pin_toggle() */ static inline int gpio_pin_toggle_dt(const struct gpio_dt_spec *spec) { return gpio_pin_toggle(spec->port, spec->pin); }
I2C 設(shè)備控制
Nordic 的芯片中 I2C 接口是由外設(shè) TWI 來實現(xiàn)的,I2C master 由 TWIM 實現(xiàn), I2C master 由 TWIS 實現(xiàn)。這里將演示如何用一個 TWIM 來連接兩個 I2C slave 設(shè)備。
首先我們還是先修改 devicetree。我們使用 i2c1 這個 node。 一方面按照應(yīng)用的要求修改這個 node 的 propertise,另一方面在這個 node 里創(chuàng)建兩個 sub-node。
i2c 的時鐘頻率通過 clock-frequency 來定義。
i2c 的引腳通過 pinctrl-0 和 pinctrl-1 定義。我們將在后面分析 i2c1_default 和 i2c1_sleep 的定義。
這兩個 sub-node 一個是 user_i2c_sensor,另一個是 user_i2c_eeprom。這兩個 sub-node 通過 propertise reg 來定義各自的 I2C 地址。
&i2c1 { status = "ok"; clock-frequency = ; pinctrl-0 = < &i2c1_default >; pinctrl-1 = < &i2c1_sleep >; pinctrl-names = "default", "sleep"; user_i2c_sensor: user_i2c_sensor@0 { compatible = "i2c-user-define"; reg = 0xA?>; }; user_i2c_eeprom: user_i2c_eeprom@0 { compatible = "i2c-user-define"; reg = 0x5?>; }; };
i2c1_default 和 i2c1_sleep的定義如下。TWIM_SDA 信號使用的是引腳 P0.04,TWIM_SCL 信號使用的是引腳 P0.03。
&pinctrl { i2c1_default: i2c1_default { group1 { psels = , ; }; }; i2c1_sleep: i2c1_sleep { group1 { psels = , ; low-power-enable; }; }; };
修改 prj.conf 添加 CONFIG_I2C=y
修改完 devicetree 我們在來添加操作 i2c 的代碼。分別定義 i2c1_sensor 和 i2c1_eeprom,它們對應(yīng)剛才 i2c1 的兩個子節(jié)點。
const struct i2c_dt_spec i2c1_sensor = I2C_DT_SPEC_GET(DT_NODELABEL(user_i2c_sensor)); const struct i2c_dt_spec i2c1_eeprom= I2C_DT_SPEC_GET(DT_NODELABEL(user_i2c_eeprom));
i2c 設(shè)備在讀寫操作前無需調(diào)用 API 來配置 ,直接調(diào)用下面的寫函數(shù)。
err = i2c_write_dt(&i2c1_sensor, buf, 1); err = i2c_write_dt(&i2c1_eeprom, buf, 1);
通過邏輯分析儀我們可以看到如下的總線數(shù)據(jù),操作的目標地址分別是我們在 devicetree 里設(shè)置的數(shù)值 0x05 和 0x0A 。
SPI 設(shè)備控制
Nordic 的芯片中 SPI 接口的 master 端通過 SPIM 實現(xiàn), slave 端通過 SPIS 實現(xiàn)。這里將演示如何用一個 SPIM 來連接兩個 SPI slave 設(shè)備。
首先修改 devicetree。
這里我們使用 spi2, 并且關(guān)閉 spi1。在 nordic 的nRF52 系列芯片中,相同數(shù)字編號的 TWIM, TWIS, SPIM, SPIS 是共用一組硬件模塊的。在上面 i2c 中我們已經(jīng)使用 i2c1, 所以這里我們就不能同時使用 spi1了。
cs-gpios 定義了 P0.26 和 P0.27 兩 個CS 信號。 SPI 用不同的片選信號,區(qū)分不同的 slave 設(shè)備。
devicetree node spi2 下定義了兩個 sub-node 分別是 user_spi_adc 和 user_spi_flash。 sub-node 里定義了三個 propertise。propertise compatible 的取值來自于我們在工程里新添加的文件 dtsbindingsspi-user-define.yaml。 propertise reg 的取值和前面的 propertise cs-gpios 呼應(yīng),reg = <0> 的 sub-node 使用 cs-gpios 里面定義的第一個 CS 引腳。reg = <1> 的 sub-node 使用 cs-gpios 里面定義的第二個 CS 引腳。propertise spi-max-frequency 定義 SPI 的時鐘頻率。兩個不同的 SPI 設(shè)備可以使用不同的時鐘頻率驅(qū)動。
&spi1 { status = "disabled"; }; &spi2 { status = "okay"; cs-gpios = &gpio0 26 GPIO_ACTIVE_LOW?>, &gpio0 27 GPIO_ACTIVE_LOW?>; pinctrl-0 = < &spi2_default >; pinctrl-1 = < &spi2_sleep >; pinctrl-names = "default", "sleep"; user_spi_adc: user_spi_adc@0 { compatible = "spi-user-define"; reg = 0?>; spi-max-frequency = ; }; user_spi_flash: user_spi_flash@0 { compatible = "spi-user-define"; reg = 1?>; spi-max-frequency = ; }; };
來看一下我們新添加的 dtsbindingsspi-user-define.yaml 里面的內(nèi)容。如下圖 spi-user-define.yaml 里面包含了 spi-device.yaml 文件,這個文件的位置在目錄 zephyrdtsbindingsspi 。
compatible: "spi-user-define" include: [spi-device.yaml]
spi-device.yaml 文件里面定義了 spi 節(jié)點需要的一些 propertise。 比如我們在 sub-node 里定義的 propertise spi-max-frequency。
# Copyright (c) 2018, I-SENSE group of ICCS # SPDX-License-Identifier: Apache-2.0 # Common fields for SPI devices include: [base.yaml, power.yaml] on-bus: spi properties: reg: required: true spi-max-frequency: type: int required: true description: Maximum clock frequency of device's SPI interface in Hz duplex: type: int default: 0 description: | Duplex mode, full or half. By default it's always full duplex thus 0 as this is, by far, the most common mode. Use the macros not the actual enum value, here is the concordance list (see dt-bindings/spi/spi.h) 0 SPI_FULL_DUPLEX 2048 SPI_HALF_DUPLEX enum: - 0 - 2048 frame-format: type: int default: 0 description: | Motorola or TI frame format. By default it's always Motorola's, thus 0 as this is, by far, the most common format. Use the macros not the actual enum value, here is the concordance list (see dt-bindings/spi/spi.h) 0 SPI_FRAME_FORMAT_MOTOROLA 32768 SPI_FRAME_FORMAT_TI enum: - 0 - 32768 spi-cpol:
SPI 引腳定義如下 CLK P0.28, MISO P0.29, MOSI P0.30。
spi2_default: spi2_default { group1 { psels = , , ; }; }; spi2_sleep: spi2_sleep { group1 { psels = , , ; low-power-enable; }; };
修改 prj.conf 添加 CONFIG_SPI=y CONFIG_SPI_ASYNC=y。
在 main.c 里添加 SPI 的應(yīng)用代碼。下面這段代碼定義了兩個結(jié)構(gòu)體變量,并通過宏 SPI_DT_SPEC_GET 用 devicetree 里的參數(shù)初始化了這兩個結(jié)構(gòu)體變量。
#define SPI_OP SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_WORD_SET(8) | SPI_LINES_SINGLE static struct spi_dt_spec spim2_adc = SPI_DT_SPEC_GET(DT_NODELABEL(user_spi_adc), SPI_OP, 0); static struct spi_dt_spec spim2_flash = SPI_DT_SPEC_GET(DT_NODELABEL(user_spi_flash), SPI_OP, 0);
spi 驅(qū)動支持多 buffer 所以要定義 buffer 個數(shù),和每個 buffer 的長度。同樣 spi 在讀寫之前無需調(diào)用配置函數(shù),直接調(diào)用讀寫函數(shù)就行。
struct spi_buf_set tx_bufs; struct spi_buf spi_tx_buf; tx_bufs.buffers = &spi_tx_buf; tx_bufs.count = 1; spi_tx_buf.buf = buf; spi_tx_buf.len = 2; err = spi_write_dt(&spim2_adc, &tx_bufs); err = spi_write_dt(&spim2_flash, &tx_bufs);
下面是SPI的波形??梢钥吹胶筒煌?spi slave 設(shè)備通訊的時候, spi master 會拉低不同的 CS 引腳。
UART 控制
Nordic 的芯片中 UART 接口叫做 UARTE。這里的 E 是指 EasyDMA , UART 可以使用 DMA 來連續(xù)收發(fā)。
修改 Devicetree。這里使用 uart1。propertise current-speed 設(shè)置 uart 的波特率。
&uart1 { status = "okay"; current-speed = 115200?>; pinctrl-0 = < &uart1_default >; pinctrl-1 = < &uart1_sleep >; pinctrl-names = "default", "sleep"; };
TXD pin 為 P1.02, RXD pin 為 P1.01。
uart1_default: uart1_default { group1 { psels = ; bias-pull-up; }; group2 { psels = ; }; }; uart1_sleep: uart1_sleep { group1 { psels = , ; low-power-enable; }; };
修改 prj.conf 在里面添加 CONFIG_UART_ASYNC_API=y CONFIG_UART_ASYNC_RX_HELPER=y。
修改 main.c 添加 uart 收發(fā)代碼。 uart_callback_set 設(shè)置 callback 函數(shù) uart_cb。因為這里采用的是異步收發(fā)的模式,所以設(shè)置callback 函數(shù)是必備的。uart_rx_enable 使能接收。uart_tx 發(fā)送數(shù)據(jù)。
err = uart_callback_set(uart1, uart_cb, NULL); //printk("uart_callback_set return %dn", err); err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS); //printk("uart_rx_enable return %dn", err); err = uart_tx(uart1, uart_tx_buf, 6, SYS_FOREVER_MS); //printk("uart_tx return %dn", err);
callback 函數(shù) uart_cb 可能由多種事件觸發(fā)。比如當接收到數(shù)據(jù)后會觸發(fā)回調(diào),并在參數(shù) EVT 傳遞 UART_RX_RDY 和接收到的數(shù)據(jù)和長度。
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data) { ARG_UNUSED(dev); //LOG_INF("uart_cb evt->type:%d", evt->type); switch (evt->type) { case UART_TX_DONE: printk("UART_TX_DONEn"); break; case UART_RX_RDY: printk("UART_RX_RDYn"); printk("received %d bytesn", evt->data.rx.len); break; case UART_RX_DISABLED: printk("UART_RX_DISABLEDn"); break; case UART_RX_BUF_REQUEST: printk("UART_RX_BUF_REQUESTn"); uart_rx_buf_rsp(uart1, uart_rx_buf2, MAX_UART_BUF_LEN); break; case UART_RX_BUF_RELEASED: printk("UART_RX_BUF_RELEASEDn"); break; case UART_TX_ABORTED: printk("UART_TX_ABORTEDn"); break; default: break; } }
我們在 DK 上把 TXD 引腳和 RXD 引腳短接來測試 UART 的收發(fā),可以看到如下的 log 信息。UART 收到了自己發(fā)送的6字節(jié)的數(shù)據(jù)。
UART 應(yīng)用代碼的優(yōu)化
上面的 uart 演示代碼中,我們只實現(xiàn)了簡單的收發(fā)。下面我們將進一步在此基礎(chǔ)上優(yōu)化 UART 的收發(fā)代碼。這一部分的修改都在 main.c 里,主要涉及下面幾個部分:
Thread 線程
Semaphore 信號量
線程間通訊 Message queue
線程 下面的代碼中通過 K_THREAD_DEFINE 定義了 一個獨立的線程來處理 uart 相關(guān)的代碼。線程處理函數(shù) uart_thread_task 中:也是先用 uart_callback_set 設(shè)置了回調(diào)函數(shù);再用 uart_rx_enable 使能了接收;然后是一個 for 循環(huán),在里面不斷的接收消息,根據(jù)消息中的指令發(fā)送數(shù)據(jù),或者處理接收到的數(shù)據(jù)。
#define UART_THREAD_STACK_SIZE 512 #define UART_THREAD_PRIORITY -1 void uart_thread_task(void) { int err; struct uart_data_item_type uart_msgq; k_sem_take(&uart_thread_start, K_FOREVER); printk("uart_thread_taskn"); err = uart_callback_set(uart1, uart_cb, NULL); err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS); for (;;) { k_msgq_get(&uart_data_msgq, &uart_msgq, K_FOREVER); printk("received uart data itemn"); switch(uart_msgq.cmd) { case UART_CMD_TX: memcpy(uart_tx_buf,&uart_msgq.data, sizeof(uint32_t)); err = uart_tx(uart1, uart_tx_buf, sizeof(uint32_t), SYS_FOREVER_MS); break; case UART_CMD_DATA_PROCESS: break; default: break; } } } K_THREAD_DEFINE(uart_thread_id, UART_THREAD_STACK_SIZE, uart_thread_task, NULL, NULL, NULL, UART_THREAD_PRIORITY, 0, 0);
上面的代碼中用 K_THREAD_DEFINE 定義線程的時候,需要指定此線程的優(yōu)先級 UART_THREAD_PRIORITY。UART_THREAD_PRIORITY 的數(shù)據(jù)類型是 integer,可以是正數(shù)也可以是負數(shù)。優(yōu)先級的數(shù)字越小,優(yōu)先級越高,負數(shù)的優(yōu)先級比正數(shù)高。thread 的優(yōu)先級取值為負數(shù)時,此 thread 為協(xié)同線程 cooperative thread 。當這種線程正在執(zhí)行的時候,其它更高優(yōu)先級的線程不能打斷它,必須等它執(zhí)行完再執(zhí)行下一個線程。當 thread 的優(yōu)先級取值為正數(shù),此 thread 為搶占線程 preemptible thread。當這種線程正在執(zhí)行的時候,其它更高優(yōu)先級的線程可以打斷它,跳轉(zhuǎn)到高優(yōu)先級的任務(wù)。等高優(yōu)先級的線程執(zhí)行完才返回原 thread 繼續(xù)執(zhí)行?;氐嚼檀a,從應(yīng)用的角度出發(fā),我們希望 uart_thread_task 的執(zhí)行優(yōu)先級大于 main 函數(shù)。通過查詢文件 buildzephyr.config 我們得知 CONFIG_MAIN_THREAD_PRIORITY 的取值為 0,也就是說 main thread 當前的優(yōu)先級為 0, 所以我們定義了 UART_THREAD_PRIORITY 為 -1。這樣 uart thread 的優(yōu)先級就高于 main thread, 而且 uart thread 的執(zhí)行不會被其它更高優(yōu)先級的 thread 打斷。需要注意的是這里的不能被打斷只是對 thread 而言,中斷是可以打斷 cooperative thread 的。
信號量 函數(shù) uart_thread_task 的優(yōu)先級比 main 函數(shù)高,所以會先于main 函數(shù)執(zhí)行。如果之前的函數(shù) uart_thread_task 里沒有 k_sem_take(&uart_thread_start, K_FOREVER),就會出現(xiàn)如下圖的現(xiàn)象。我們看到 uart thread 的 log 是先于 main thread 被打印出來的。
從應(yīng)用的角度,我們希望 uart_thread_task 在 main 函數(shù)啟動完廣播之后再執(zhí)行。這就引入了一個不同線程之間的同步問題。Zephyr RTOS 中可以通過 semaphore 解決不同 thread 間的同步問題。下面的代碼中通過 K_SEM_DEFINE 定義了一個為 uart_thread_start 的 semaphore 。 函數(shù) uart_thread_task 執(zhí)行到函數(shù) k_sem_take 時,如果 uart_thread_start 沒有被釋放,當前 thread 會被掛起等待,直到 semaphore 被釋放。
static K_SEM_DEFINE(uart_thread_start, 0, 1); #define UART_THREAD_STACK_SIZE 512 #define UART_THREAD_PRIORITY -1 void uart_thread_task(void) { int err; struct uart_data_item_type uart_msgq; k_sem_take(&uart_thread_start, K_FOREVER); printk("uart_thread_taskn"); err = uart_callback_set(uart1, uart_cb, NULL); err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS); for (;;) {
在 main 里通過 k_sem_give 釋放 uart_thread_start。uart 線程會打斷當前的 main thread 從 k_sem_take 繼續(xù)執(zhí)行。
err = spi_write_dt(&spim2_adc, &tx_bufs); err = spi_write_dt(&spim2_flash, &tx_bufs); k_sem_give(&uart_thread_start); struct uart_data_item_type main_msgq; main_msgq.cmd = UART_CMD_TX; main_msgq.data = 0; for (;;) { while (k_msgq_put(&uart_data_msgq, &main_msgq, K_NO_WAIT) != 0) { /* message queue is full: purge old data & try again */ k_msgq_purge(&uart_data_msgq); } main_msgq.data++; dk_set_led(RUN_STATUS_LED, (++blink_status) % 2); k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL)); }
線程間通訊 演示代碼中 main thread 會把要發(fā)送的數(shù)據(jù)通過線程通訊發(fā)送到 uart thread, uart thread 調(diào)用驅(qū)動函數(shù)發(fā)送。zephyr 中提供了多種線程間通訊方式,具體如下圖,這里使用的是 message queue。
下面的代碼中 K_MSGQ_DEFINE 定義了一個名為 uart_data_msgq 的 message queue。uart_data_msgq 的緩沖區(qū)里最多可以容納 8 個消息。
struct uart_data_item_type { uint8_t cmd; uint32_t data; }; K_MSGQ_DEFINE(uart_data_msgq, sizeof(struct uart_data_item_type), 8, 4);
下面這段代碼來自于 main thread 的 main 函數(shù)。代碼會定時循環(huán)把待發(fā)送的數(shù)據(jù)打包成一個 message,然后推送到 message queue 里面。
struct uart_data_item_type main_msgq; main_msgq.cmd = UART_CMD_TX; main_msgq.data = 0; for (;;) { while (k_msgq_put(&uart_data_msgq, &main_msgq, K_NO_WAIT) != 0) { /* message queue is full: purge old data & try again */ k_msgq_purge(&uart_data_msgq); } main_msgq.data++; dk_set_led(RUN_STATUS_LED, (++blink_status) % 2); k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL)); }
下面的代碼來自 uart thread 的 uart_thread_task 函數(shù)。 函數(shù)等待 message queue 里推送來的 message。得到 message 后,根據(jù)里面的 cmd 字段來處理發(fā)送或者接收數(shù)據(jù)。
void uart_thread_task(void) { int err; struct uart_data_item_type uart_msgq; k_sem_take(&uart_thread_start, K_FOREVER); printk("uart_thread_taskn"); err = uart_callback_set(uart1, uart_cb, NULL); err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS); for (;;) { k_msgq_get(&uart_data_msgq, &uart_msgq, K_FOREVER); printk("received uart data itemn"); switch(uart_msgq.cmd) { case UART_CMD_TX: memcpy(uart_tx_buf,&uart_msgq.data, sizeof(uint32_t)); err = uart_tx(uart1, uart_tx_buf, sizeof(uint32_t), SYS_FOREVER_MS); break; case UART_CMD_DATA_PROCESS: break; default: break; } } }
下面是加入線程間通訊的代碼后得到的 log,當我們把 TX 和 RX 引腳短接后可以看出 uart thread 不斷的發(fā)送從 main thread 傳輸?shù)臄?shù)據(jù)。
總結(jié)
本文從實際操作出發(fā),介紹了用戶最常用的一些外設(shè)如 GPIO, I2C, SPI, UART 的配置和使用方法。并介紹了一些簡單 RTOS 組件的應(yīng)用如 thread, semaphore, message queue。希望能幫助 Nordic 用戶加快 nRF Connect SDK 的開發(fā)速度。
審核編輯 黃宇
-
NRF
+關(guān)注
關(guān)注
0文章
50瀏覽量
38496 -
SDK
+關(guān)注
關(guān)注
3文章
1091瀏覽量
50624
發(fā)布評論請先 登錄
Nordic 推出nRF Connect for Cloud 的無線物聯(lián)網(wǎng)設(shè)計方案
nRF Connect SDK(NCS)/Zephyr固件升級詳解 – 重點講述MCUboot和藍牙空中升級

Nordic nRF Connect SDK 官方開發(fā)文檔、學(xué)習(xí)資料下載鏈接
如何調(diào)試nRF5 SDK
nRF52840-DK和nRF21540-EK上FEM的支持事宜
esp32連接nrf-connect報錯是何原因?如何解決?
講述Nordic nRF5 SDK的主要調(diào)試手段,以幫助大家快速定位問題

nRF52開發(fā)工具包用戶指南免費下載

DFU協(xié)議簡介 NCS DFU升級步驟說明
Memfault基于云的自助設(shè)備可觀察性平臺

基于XIAO nRF52840的鑰匙尋找器

nRF5 SDK軟件架構(gòu)及softdevice工作原理

如何調(diào)試nRF5 SDK

nRF Connect SDK 使用 nPM2100 評估套件 (PCA10170) 為 nPM2100 電源管理 IC (PMIC) 的開發(fā)

評論