2018年7月25日 星期三

ESP8266 MQTT IoT應用範例介紹

如何把溫濕度sensor讀取到的資料傳送到雲端呢?如何從雲端來控制RGB LED呢?不需要自己架設雲端,使用免費的server及瞭解MQTT協定就可以實現物聯網IoT架構。 先準備硬體零件,電路圖如下圖示,並把電路接好。 1.NodeMCU x 1 pcs 2.DHT11 x 1 pcs 3.RGB LED x 1 pcs nodemcu_dht11.png 安裝ArduinoIDE 與註冊Server帳號 Arduino IDE https://www.arduino.cc/en/Main/Software 免費註冊Server帳號 https://www.adafruit.com/ NodeMCU的範例程式如下,用Arduino IDE去編譯,程式有幾各地方要去修改。 1.WLAN_SSID 請輸入你的wifi SSID 2.WLAN_PASS 請輸入你的wifi 密碼 3.AIO_USERNAME 請輸入註冊Server的帳號 https://www.adafruit.com/ 4.AIO_KEY 請先登入 https://io.adafruit.com ,點選View AIO Key,複製Active Key,如下圖示。 adafruit_1 程式內容就請大家自行研究了,不另外說明了。
關於MQTT協定,可以參考以下路徑的文章。
MQTT教學(一):認識MQTT
在編譯程式之前,請先在Arduino IDE自行匯入以下的程式庫(草稿碼->匯入程式庫->管理程式庫),這樣編譯才不會出現錯誤。 1.Adafruit_ESP8266 2.Adafruit_MQTT_Library 3.DHT_sensor_library
/****************************************************/
#include "ESP8266WiFi.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include "DHT.h"

/************************* LED *********************************************/
#define G_LED    2
#define B_LED    0
#define R_LED    4

/*********************** DHT11 **********************************************/

#define DHTPIN 5 // what digital pin we're connected to
#define DHTTYPE DHT11 // DHT 11
DHT dht(DHTPIN, DHTTYPE);

/************************* WiFi Access Point *********************************/

#define WLAN_SSID  "...your SSID..."
#define WLAN_PASS  "..your password..."

/************************* Adafruit.io Setup *********************************/

#define AIO_SERVER "io.adafruit.com"
#define AIO_SERVERPORT 1883 // use 1883 for SSL
#define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..."
#define AIO_KEY "...your AIO key..."

/************ Global State (you don't need to change this!) ******************/

// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;
// or... use WiFiFlientSecure for SSL
//WiFiClientSecure client;

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);

/****************************** Feeds ***************************************/

// Setup a feed called 'photocell' for publishing.
// Notice MQTT paths for AIO follow the form: /feeds/
Adafruit_MQTT_Publish Humidity = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/Humidity");
Adafruit_MQTT_Publish Temperature = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/Temperature");
// Setup a feed called 'onoff' for subscribing to changes.
Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff");

/*************************** Sketch Code ************************************/

// Bug workaround for Arduino 1.6.6, it seems to need a function declaration
// for some reason (only affects ESP8266, likely an arduino-builder bug).
void MQTT_connect();

void setup() {
pinMode(G_LED, OUTPUT);
pinMode(B_LED, OUTPUT);
pinMode(R_LED, OUTPUT);
digitalWrite(G_LED, LOW);
digitalWrite(B_LED, LOW);
digitalWrite(R_LED, LOW);
Serial.begin(115200);
dht.begin();
delay(10);

Serial.println(F("Adafruit MQTT demo"));

// Connect to WiFi access point.
Serial.println(); Serial.println();
Serial.print("Connecting to ");
Serial.println(WLAN_SSID);

WiFi.begin(WLAN_SSID, WLAN_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();

Serial.println("WiFi connected");
Serial.println("IP address: "); Serial.println(WiFi.localIP());

// Setup MQTT subscription for onoff feed.
mqtt.subscribe(&onoffbutton);
digitalWrite(G_LED, HIGH);
digitalWrite(B_LED, HIGH);
digitalWrite(R_LED, HIGH);

}

//uint32_t x=0;
//uint32_t y=0;
void loop() {
// Ensure the connection to the MQTT server is alive (this will make the first
// connection and automatically reconnect when disconnected). See the MQTT_connect
// function definition further below.
MQTT_connect();

// this is our 'wait for incoming subscription packets' busy subloop
// try to spend your time here

Adafruit_MQTT_Subscribe *subscription;
while ((subscription = mqtt.readSubscription(5000))) {
if (subscription == &onoffbutton) {
Serial.print(F("Got: "));
Serial.println((char *)onoffbutton.lastread);
if( *onoffbutton.lastread == '1')
{
   digitalWrite(G_LED, LOW);
}
else if( *onoffbutton.lastread == '2')
{
   digitalWrite(B_LED, LOW);
}
else if( *onoffbutton.lastread == '3')
{
   digitalWrite(R_LED, LOW);
}
else if( *onoffbutton.lastread == '0')
{
   digitalWrite(G_LED, HIGH);
   digitalWrite(B_LED, HIGH);
   digitalWrite(R_LED, HIGH);
}
}
}

// Now we can publish stuff!
float h = dht.readHumidity();
Serial.print(F("\nHumidity : "));
Serial.print(h);
Serial.print("%");
if (! Humidity.publish(h)) {
Serial.println(F("\nSending Humidty is Failed"));
} else {
Serial.println(F("\nSending Humidty is OK!"));
}

//
float t = dht.readTemperature();
float hic = dht.computeHeatIndex(t, h, false);
Serial.print(F("\nTemperature : "));
Serial.print(hic);
Serial.print("*C");
if (! Temperature.publish(hic)) {
Serial.println(F("\nSending Temperature is Failed"));
} else {
Serial.println(F("\nSending Temperature is OK!"));
}
// ping the server to keep the mqtt connection alive
// NOT required if you are publishing once every KEEPALIVE seconds
/*
if(! mqtt.ping()) {
mqtt.disconnect();
}
*/
}

// Function to connect and reconnect as necessary to the MQTT server.
// Should be called in the loop function and it will take care if connecting.
void MQTT_connect() {
int8_t ret;

// Stop if already connected.
if (mqtt.connected()) {
return;
}

Serial.print("Connecting to MQTT... ");

uint8_t retries = 3;
while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
Serial.println(mqtt.connectErrorString(ret));
Serial.println("Retrying MQTT connection in 5 seconds...");
mqtt.disconnect();
delay(5000); // wait 5 seconds
retries--;
if (retries == 0) {
// basically die and wait for WDT to reset me
while (1);
}
}
Serial.println("MQTT Connected!");
}

影片demo


2018年7月13日 星期五

NodeMCU (ESP8266) Timer and Ticker 範例-Arduino

ESP8266 Timer0和Timer1有兩個計時器,其WiFi功能使用一個計時器。我們只能使用一個計時器。為避免崩潰問題,我建議使用Ticker而不是Timer。Ticker執行與計時器相同的功能。我們以LED閃爍來做示範,電路圖如下。 nodemcu_LED

ESP8266 Ticker範例

Ticker是用於在一定時間內重複調用函數的庫,attach函數有兩種變體:attachattach_ms,第一個以秒為單位,第二個以毫秒為單位。

LED使用ESP8266 Ticker閃爍,每0.5秒閃一次

該程序演示了LED閃爍的範例。此函數啟動類似於附加中斷的定時器blinker.attach(0.5,changeState); 停止計時器使用blinker.detach(); 要使用Ticker os_timer,我們需要Ticker.h Timer Library
#include  <ESP8266WiFi.h>
#include  <Ticker.h>//Ticker Library

Ticker blinker;

#define R_LED 4 //On board LED

//======================================================
void changeState()
{
    digitalWrite(R_LED, !(digitalRead(R_LED))); 
    //Invert Current State of LED
}
//======================================================
// Setup
//======================================================
void setup()
{
    Serial.begin(115200);
    Serial.println("");
    pinMode(R_LED,OUTPUT);

    //Initialize Ticker every 0.5s
    blinker.attach(0.5, changeState);
}
//======================================================
// MAIN LOOP
//======================================================
void loop()
{
}
//======================================================

ESP8266 Timer範例

硬件Timer0由WiFi功能使用。我們只能使用Timer1。使用Timer而不是Ticker可以實現精確r計時,並且您可以在微秒內獲得定時器中斷。

LED使用ESP8266 Ticker閃爍,每0.5秒閃一次,每10ms執行Timer中斷副程式

設定每10ms執行ATTR onTimerISR(),副程式內counter計算到50次,也就是0.5秒,執行LED閃爍,程式如下。
#include <ESP8266WiFi.h>

uint16_t counter = 0 ;
#define R_LED 4
//======================================================
void ICACHE_RAM_ATTR onTimerISR(){
counter++;
if(counter >= 50 )
{
   counter = 0 ;
   digitalWrite(R_LED,!(digitalRead(R_LED))); //Toggle LED Pin
}
timer1_write(50000);//10ms
}
//======================================================
// Setup
//======================================================
void setup()
{
   Serial.begin(115200);
   Serial.println("");

   pinMode(R_LED,OUTPUT);
   timer1_attachInterrupt(onTimerISR);
   timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE);
   timer1_write(50000); //10ms
}
//=======================================================
// MAIN LOOP
//=======================================================
void loop()
{

}
//========================================================
示範結果

2018年5月9日 星期三

如何取消Nordic nRF52系列廣播的Timeout功能

如何取消BLE廣播的Timeout功能?讓BLE裝置一直都可以被搜尋到。 以nRF5-SDK-15.0.0的ble_app_uart的範例程式。廣播初時化設定,如下函式。

static void advertising_init(void)
{
uint32_t err_code;
ble_advertising_init_t init;

memset(&init, 0, sizeof(init));

init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = false;
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;

init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;

init.config.ble_adv_fast_enabled = true ;
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = APP_ADV_DURATION;
init.evt_handler = on_adv_evt;

err_code = ble_advertising_init(&m_advertising, &init);
APP_ERROR_CHECK(err_code);

ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
只要修改兩個地方,廣播Timeout的功能就可以拿掉。如下
 init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; init.config.ble_adv_fast_timeout = 0 ;
 完整advertising_init()如下,

static void advertising_init(void)
{
uint32_t err_code;
ble_advertising_init_t init;

memset(&init, 0, sizeof(init));

init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = false;
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;

init.config.ble_adv_fast_enabled = true ;
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = 0 ; //APP_ADV_DURATION;
init.evt_handler = on_adv_evt;

err_code = ble_advertising_init(&m_advertising, &init);
APP_ERROR_CHECK(err_code);

ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
#CSR #CSR8635 #藍芽 #藍芽立體聲喇叭 #電子外包 #PCB LAYOUT #電子設計 #學生專題製作#電路設計 #數位線路 #類比線路 #單晶片程式設計

2018年4月20日 星期五

如何用ESPlorer來撰寫NodeMCU ESP8266的LED閃爍程式

ESP8266 程式編輯環境主要分成兩種,Arduino IDE 與ESPlorer。ESPlorer是使用Lua語言撰寫程式。Lua是個非常間易的語法,很容易學習。
NodeMCU使用ESPlorer來編輯,有兩個軟體先安裝。
1.先下載ESPlorer軟體,下載路徑 http://esp8266.ru/esplorer-latest/?f=ESPlorer.zip
ESPlorer
2.NodeMCU機板需要更新NodeMCU最新的Firmware(nodemcu_latest.bin),利用ESP8266 Flasher(NODEMCU FIRMWARE PROGRAMMER)軟體來更新Firmware,這樣NodeMCU機板才可以使用ESPlorer撰寫Lua程式,之前因為機板沒有燒錄NodeMCU Firmware,ESPlorer編譯好的程式是無法燒錄到機板上,這點要特別注意。
ESP8266 Flasher下載路徑:https://github.com/nodemcu/nodemcu-flasher
NodeMCU Flasher_Config
完成前兩步驟就可以開始用Lua編輯程式了。ESPlorer Baud rate 要選擇9600 ,才不會出現亂碼。按Open就可以與NodeMCU連接,按NodeMCU機板的RST按鍵,就會出現如下圖。
ESPlorer_1_
接著來寫個簡單的程式來試試看,讓機板上的LED閃爍,程式如下。
ESP_LED = 4                       -- NodeMCU pin no. (D4)
gpio.mode( ESP_LED, gpio.OUTPUT ) --設定D4為輸出
-- turn on
tmr.alarm(0,500,1,function()      --每500ms執行一次
   if gpio.read(ESP_LED)==1 then
   gpio.write( ESP_LED, gpio.LOW )
   else
   gpio.write( ESP_LED, gpio.HIGH )
   end
end)

led_lua.png
按下Save to ESP存成led.lua ,按NodeMCU機板上的RST按鍵,讓後Upload led.lua到NodeMCU機板上,在輸入 dofile(“led.lua")按Send看到NodeMCU的LED每0.5秒閃爍一次。

#CSR #CSR8635 #藍芽 #藍芽立體聲喇叭 #電子外包 #PCB LAYOUT #電子設計 #學生專題製作#電路設計 #NodeMCU #ESP8266 #單晶片程式設計

2018年4月18日 星期三

NodeMCU開發板ESP8266 WiFi模組介紹

NodeMCU是安信可科技(Ai-Thinker)公司(http://www.ai-thinker.com)做的開發板。搭配樂鑫(Espressif)公司(https://espressif.com)的ESP8266 ESP12的WiFi模組,如下圖。
nodemcu-lua-wifi-v3-esp8266-wifi
ESP8266晶片由上海樂鑫公司所開發出來,是顆32-bit單晶片處理器,整合了支援IEEE802.11 b/g/n的WiFi晶片。與Arduino週邊接腳相容,所以可以支援Arduino的感應器,價格十分便宜且操作簡單,也容易取得,已成為物連網應用的最熱門的選擇。

ESP8266硬體規格如下
  • CPU選用32-bit Tensilica Xtensa LX3,處理速度80MHz。
  • ROM/RAM方面提供64K Boot ROM,64K Instruction RAM與64K Data RAM。
  • 額外Flash擴充到4MB,可以儲存即時作業系統與應用程式。
  • 具有WiFi 802.11b/g/n 2.4G Radio ,可以設定為AP、Station或AP+Station等各種網路應用模式。
  • 有13支GPIO,支援PWM、I2C、UART、SPI及10-bit ADC等週邊功能,沒有DAC功能。

ESP8266程式編輯環境

ESP8266晶片系列模組
ESP8266系列
NodeMCU是支援ESP8266軟硬件開發非常好用的開發板,NodeMCU PIN腳定義,如下圖。
NodeMCU1

NodeMCU驅動程式,目前NodeMCU板子USB轉UART的晶片有兩種,CH341與CP2102這兩種,驅動程式下載路經分別如下。

NodeMCU韌體燒錄程式(NodeMCU Flasher),下載路徑 https://github.com/nodemcu/nodemcu-flasher
NodeMCU Firmware下載路徑 https://github.com/sleemanj/ESP8266_Simple/raw/master/firmware/ai-thinker-0.9.5.2-115200.bin
NodeMCU Flasher
#CSR #CSR8635 #藍芽 #藍芽立體聲喇叭 #電子外包 #PCB LAYOUT #電子設計 #學生專題製作#電路設計 #NodeMCU #ESP8266 #單晶片程式設計

2018年4月16日 星期一

Nordic nRF52832 DFU介紹

什麼是DFU?就是Device Firmware Update 。Nordic提供nRF Toolbox APP可以在Play商店下載。可以經由over-the-air(OTA)方式來更新程式。
Screenshot_20180222-131341
如何讓你裝置有DFU功能?可以分成三個部份。
1.先修改Bootloader程式,因為有專屬private key
2.燒錄檔.hex轉成.zip
3.利用nRF Toolbox的DFU來更新程式
Bootloader程式,請參考nRF5-SDK-zip 的examples/duf/bootloader_secure_ble的範例。我是用Keil去編譯,出現錯誤,如下。
dfu-1
顯示錯誤訊息如下
#error “Debug public key not valid for production. Please see https://github.com/NordicSemiconductor/pc-nrfutil/blob/master/README.md to generate it"
點選以上網址,根據網頁內容步驟去執行,步驟如下。
1.下載pc-nrfutil 的檔案。
3.安裝pip
4.在Windows系統,新增環境變數內系統變數的Path路徑如下。
C:\Python27\Scripts;C:\Python27\;
5.接下來的指令都是在DOS畫面下執行,請在開始的搜尋檔案處,打上cmd,就會出現如下圖示。
dos
6.Installing from PyPi ,請在DOS畫面輸入以下指令
pip install nrfutil
7.PyInstall,請在DOS畫面輸入以下指令
pip install pyinstaller
8.在DOS畫面,跳到下載pc-nrfutil檔案的pc-nrfutil-master資料夾下,執行以下的指令
pip install -r requirements.txt
python nordicsemi/__main__.py
python setup.py install
pyinstaller nrfutil.spec
9.產生key.pem,請在DOS畫面輸入以下指令,請把key.pem存到pc-nrfutil-master\nordicsemi\dfu\tests下。
nrfutil keys generate key.pem
10.把key.pem轉換成code ,請在DOS畫面輸入以下指令。把code如下圖紅色框框,複蓋到SDK的dfu_public_key.c
nrfutil keys display --key pk --format code key.pem
keypem
11.Keil編譯應該就可以成功了。
12.利用nRFgo Studio軟體把.hex燒錄到板子上,先燒錄SoftDevice,再燒錄Application,最後才燒錄Bootlader。這樣機板就有DFU功能。
13.把.hex轉成.zip檔案,請在DOS畫面輸入以下指令,選用nRF52xxx系列–hw-version 就填52,--sd-req 請參考下圖,--application填寫要燒錄的檔案名稱,--key-file key.pem就是dfu_public_key.c,不同key.pem就無法DFU,這一點要特別注意。app_dfu_package.zip是你要轉出的.zip檔案,名稱可以自訂。
nrfutil pkg generate --hw-version 52 --sd-req 0x9D --application-version 4 --application app.hex --key-file key.pem app_dfu_package.zip
SoftDevice
14.打開手機的nRF Toolbox APP,選擇DFU,把app_dfu_package.zip上傳到手機,選擇藍芽裝置,就可以更新程式了。
PS.  藍芽裝置要先進入Bootloader才可以DFU,藍芽裝置按住按鍵再上電就可以進入Bootloader。
#CSR #CSR8635 #藍芽 #藍芽立體聲喇叭 #電子外包 #PCB LAYOUT #電子設計 #學生專題製作#電路設計 #數位線路 #類比線路 #單晶片程式設計

2018年4月13日 星期五

Nordic nRF52832 Timer程式介紹

#include <stdbool.h>
#include <stdint.h>
#include "nrf.h"
#include "nrf_drv_timer.h"
#include "bsp.h"
#include "app_error.h"

const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSTANCE(0);

/**
 * @brief Handler for timer events.
 */
void timer_led_event_handler(nrf_timer_event_t event_type, void* p_context)
{
    static uint32_t i;
    uint32_t led_to_invert = ((i++) % LEDS_NUMBER);

    switch (event_type)
    {
        case NRF_TIMER_EVENT_COMPARE0:
            bsp_board_led_invert(led_to_invert);  
            break;

        default:
            //Do nothing.
            break;
    }
}

/**
 * @brief Function for main application entry.
 */
int main(void)
{
    uint32_t time_ms = 500; //Time(in miliseconds) between consecutive compare events.
    uint32_t time_ticks;
    uint32_t err_code = NRF_SUCCESS;

    //Configure all leds on board.
    bsp_board_leds_init();
 
    //Configure TIMER_LED for generating simple light effect - leds on board will invert his state one after the other.
    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    err_code = nrf_drv_timer_init(&TIMER_LED, &timer_cfg, timer_led_event_handler);
    APP_ERROR_CHECK(err_code);

    time_ticks = nrf_drv_timer_ms_to_ticks(&TIMER_LED, time_ms);

    nrf_drv_timer_extended_compare(
         &TIMER_LED, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);

    nrf_drv_timer_enable(&TIMER_LED);

    while (1)
    {
        __WFI();
    }
}
const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSTANCE(0);設定為Timer0。
void timer_led_event_handler(nrf_timer_event_t event_type, void* p_context);Timer中斷副程式。
nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;設定為default config。
nrf_drv_timer_init(&TIMER_LED, &timer_cfg, timer_led_event_handler);Timer初時化設定及中斷副程式。
nrf_drv_timer_ms_to_ticks(&TIMER_LED, time_ms);中斷時間轉為ticks.
nrf_drv_timer_extended_compare( &TIMER_LED, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
nrf_drv_timer_enable(&TIMER_LED);Timer啟動
此程式每500ms產生中斷,程式會跳到void timer_led_event_handler(nrf_timer_event_t event_type, void* p_context)去執行LED控制。




#CSR #CSR8635 #藍芽 #藍芽立體聲喇叭 #電子外包 #PCB LAYOUT #電子設計 #學生專題製作#電路設計

2018年4月11日 星期三

Nordic nRF52832 PWM Library介紹

#include "nrf.h"
#include "app_error.h"
#include "bsp.h"
#include "app_pwm.h"
#include "nrf_delay.h"

APP_PWM_INSTANCE(PWM0,0); // Create the instance "PWM0" using TIMER0.
void pwm_ready_callback(uint32_t pwm_id)    // PWM callback function
{
    ready_flag = true;
}
int main(void)
{
 ret_code_t err_code;
 
 /* 2-channel PWM, 1000Hz, output on p0.17 & p0.18 */
        app_pwm_config_t pwm0_cfg = APP_PWM_DEFAULT_CONFIG_2CH(1000L, 17, 18);
 
 /* Switch the polarity of the second channel. */
 pwm0_cfg.pin_polarity[0] = APP_PWM_POLARITY_ACTIVE_HIGH;
 pwm0_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_LOW;
 
 app_pwm_init(&PWM0,&pwm0_cfg,pwm_ready_callback);
 app_pwm_enable(&PWM0);
 
 ready_flag = false;
 while (app_pwm_channel_duty_set(&PWM0, 0, 0) == NRF_ERROR_BUSY);
 while (!ready_flag);
 ready_flag = false;
 while (app_pwm_channel_duty_set(&PWM0, 1, 0) == NRF_ERROR_BUSY);
 while (!ready_flag);
 
 uint32_t value;
        while (true)
        {
            for (uint8_t i = 0; i < 40; ++i)
            {
            value = (i < 20) ? (i * 5) : (100 - (i - 20) * 5);

            ready_flag = false;
            // Set the duty cycle - keep trying until PWM is ready... 
            while (app_pwm_channel_duty_set(&PWM0, 0, value) == NRF_ERROR_BUSY);

            // ... or wait for callback. 
            while (!ready_flag);
            APP_ERROR_CHECK(app_pwm_channel_duty_set(&PWM0, 1, value));
            nrf_delay_ms(25);
            }
        }
}
函式APP_PWM_INSTANCE(PWM0,0) ;是指PWM0模組時序是TIMER0。
函式APP_PWM_DEFAULT_CONFIG_2CH(1000L, 17, 18);PWM模組有兩個頻道輸出,為channel0為P0.17,channel1為P0.18,頻率為1000Hz。
pwm0_cfg.pin_polarity[0] = APP_PWM_POLARITY_ACTIVE_HIGH;
channel0 的PWM POLARITY為High。
pwm0_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_LOW;
channel1 的PWM POLARITY為Low。
函式app_pwm_init(&PWM0,&pwm0_cfg,pwm_ready_callback);PWM0初時化設定及設定callback函式。
函式app_pwm_enable(&PWM0);啟動PWM0
函式app_pwm_channel_duty_set(&PWM0, 0, value);channel0 pwm duty設定,value為0~100。