手把手帶你使用EFR32 -- 土壤溼度感測器變身第二形態,以 ZigBee 形態出擊

靈感桌面發表於2022-03-28

前言

後悔,總之就是非常後悔,我當時到底是為啥才會豬油蒙心,選擇了 EFR32 來學習 ZigBee 使用啊?
在這裡插入圖片描述

EFR32 這玩意看效能確實不錯,但是資料太少了,EmberZnet SDK 也是用得一頭霧水。能找到的教程和例子基本是都是控制一下LED ,配置入網啥的,具體的涉及常用的ADC,I2C什麼的資料太難找了,SDK 裡面也沒有找到類似demo的東西,總之就是非常痛苦。

這裡給大家分享一些好東西!EFR32和EFM32 非常全面的驅動示例 demo 這玩意救我狗命啊!國內不知道為啥都沒有人分享這麼好的玩意,找到了下載居然還要錢!這裡就分享給大家吧。

https://github.com/SiliconLabs/peripheral_examples/tree/master/series2
超級實用的 EFR32 demo !


硬體準備

我使用的是畫時科技的 ZDB-01 是 silicon EFR32MG21 的開發板。
感測器用了以前的 DFRobot 電容式土壤溼度感測器模組
因為第一次接觸 ZigBee 我沒有什麼 ZigBee 的閘道器和上位機啥的,一開始我還蠻頭疼,然後我發現精靈一號就有 ZigBee 閘道器功能,這玩意還真是方便啊,萬萬沒想到之前買的精靈一號還能在這時候幫上忙。

但是笑死,官方又沒有提供開發除錯工具,還得自己寫。

在這裡插入圖片描述

軟體準備

EFR32 入網流程可以參考我上一篇文章《手把手帶你使用ZigBee——通過愛智控制EFR32,以及 Simplicity Studio 使用過程中注意事項》這裡就不贅述了。

土壤溼度感測器 的輸出是模擬量所以需要在 Simplicity Studio 的 Defaultmode Peripherals 中新增並配置 IADC
在這裡插入圖片描述
不知道是我 IDE 問題還是啥,自動生成的 SDK 中生成的 IADC 庫檔案不全,缺少 IADC.c 檔案,而且 IADC.h 有問題。需要我們自己新增一下 IADC.c 和 IADC.h 檔案,這兩個檔案的下載地址:

https://github.com/ryankurte/efm32-base/blob/master/emlib/src/em_iadc.c

將下載下來的 IADC.c 放入專案資料夾的 emlib 資料夾下:
在這裡插入圖片描述
然後在 IDE 中 Refresh 一下:
在這裡插入圖片描述
而 IADC.h 雖然存在,但是有問題,無法通過編譯,需要替換成新的 IADC.h ,網上大部分教程都建議不要修改 SDK
而選擇 Make a Copy
在這裡插入圖片描述
但是經過我親測,在這裡我建議大家選擇 Edit in SDK ,因為選擇 Make a Copy 的話會報錯(雖然不影響編譯),提示某些符號無法解析,可能是出現了重複定義的情況,而且這個 SDK 中的檔案就是有問題的,保留也沒有意義,不如直接替換成新的檔案。

程式碼分析

這個程式碼是基於官方 demo 基礎上修改而來。
為了方便講解邏輯,我會打亂程式碼的順序可能還會進行裁剪,要是想直接拿程式碼跑的朋友可以直接去 靈感桌面的祕密寶庫 獲取程式碼,或者直接 clone:

https://gitee.com/inspiration-desktop/DEV-lib-arduino.git

標頭檔案與初始化配置

#include "app/framework/include/af.h"
#include "em_device.h"
#include "em_chip.h"
#include "em_cmu.h"
#include "em_iadc.h"
#include "em_gpio.h"

// Set CLK_ADC to 10MHz
#define CLK_SRC_ADC_FREQ          20000000 // CLK_SRC_ADC
#define CLK_ADC_FREQ              10000000 // CLK_ADC - 10MHz max in normal mode

/*
 * Specify the IADC input using the IADC_PosInput_t typedef.  This
 * must be paired with a corresponding macro definition that allocates
 * the corresponding ABUS to the IADC.  These are...
 *
 * GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AEVEN0_ADC0
 * GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AODD0_ADC0
 * GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BEVEN0_ADC0
 * GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BODD0_ADC0
 * GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDEVEN0_ADC0
 * GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDODD0_ADC0
 *
 * ...for port A, port B, and port C/D pins, even and odd, respectively.
 */
#define IADC_INPUT_0_PORT_PIN     iadcPosInputPortBPin0;     //  配置輸入引腳
#define IADC_INPUT_1_PORT_PIN     iadcNegInputPortBPin1;     

#define IADC_INPUT_0_BUS          BBUSALLOC                  //  配置匯流排
#define IADC_INPUT_0_BUSALLOC     GPIO_BBUSALLOC_BEVEN0_ADC0
#define IADC_INPUT_1_BUS          BBUSALLOC
#define IADC_INPUT_1_BUSALLOC     GPIO_BBUSALLOC_BODD0_ADC0

/*******************************************************************************
 ***************************   GLOBAL VARIABLES   *******************************
 ******************************************************************************/

static volatile uint32_t sample;
const float AirValue = 465;                       // 初始化最大幹燥 (感測器在空中的情況)這個資料每個感測器不一樣,需要自己測試
const float WaterValue = 1177;                    // 初始化最大溼度 (感測器放入水中的情況)

EmberEventControl AcoinfoAioReportEventControl;   // 宣告事件

設定上電列印與上電初始化 IADC

void emberAfMainInitCallback(void)
{
    emberAfCorePrintln("---------------靈感桌面---------------");
    // 初始化 IADC
    initIADC();
}

設定按按鈕入網

void emberAfHalButtonIsrCallback(uint8_t button, uint8_t state)
{
  if (state == BUTTON_RELEASED) {
      emberAfPluginNetworkSteeringStart();
  }
}

初始化 IADC ,我比較奇怪的一點,在上面 Defaultmode Peripherals 的時候就已經配置過 IADC 了,為什麼在這裡還需要配置?之前嘗試 LED 的時候就不需要。(我試過了,不重新初始化 IADC 是不能用的)

void initIADC (void)
{
	  // 初始化結構體宣告
	  IADC_Init_t init = IADC_INIT_DEFAULT;
	  IADC_AllConfigs_t initAllConfigs = IADC_ALLCONFIGS_DEFAULT;
	  IADC_InitSingle_t initSingle = IADC_INITSINGLE_DEFAULT;
	  IADC_SingleInput_t initSingleInput = IADC_SINGLEINPUT_DEFAULT;

	  // 重置IADC以重置配置,以防它已被其他程式碼修改
	  IADC_reset(IADC0);

	  // 為IADC選擇時鐘
	  CMU_ClockSelectSet(cmuClock_IADCCLK, cmuSelect_FSRCO);  // FSRCO - 20MHz

	  // 修改init結構體並初始化此處設定HFSCLK預設值
	  init.srcClkPrescale = IADC_calcSrcClkPrescale(IADC0, CLK_SRC_ADC_FREQ, 0);
//
//	  // 預設情況下,掃描和單個轉換都使用配置0。使用無緩衝AVDD(供電電壓為mV)作為參考
	  initAllConfigs.configs[0].reference = iadcCfgReferenceVddx;
	  initAllConfigs.configs[0].vRef = 3300;
//
//	  // 除以CLK_SRC_ADC,設定CLK_ADC頻率
	  initAllConfigs.configs[0].adcClkPrescale = IADC_calcAdcClkPrescale(IADC0,
	                                             CLK_ADC_FREQ,
	                                             0,
	                                             iadcCfgModeNormal,
	                                             init.srcClkPrescale);
//
//	  // 將引腳分配到差分模式下的正輸入
	  initSingleInput.posInput   = IADC_INPUT_0_PORT_PIN;
	  // 負輸入
	  initSingleInput.negInput   = IADC_INPUT_1_PORT_PIN;
//
//	  // 初始化 IADC
	  IADC_init(IADC0, &init, &initAllConfigs);
//
//	  // 初始化Single轉換輸入
	  IADC_initSingle(IADC0, &initSingle, &initSingleInput);

	  // 為ADC0輸入分配模擬匯流排
	  GPIO->IADC_INPUT_0_BUS |= IADC_INPUT_0_BUSALLOC;
	  GPIO->IADC_INPUT_1_BUS |= IADC_INPUT_1_BUSALLOC;
}

我嘗試通過 aio 命令觸發 aio 回撥函式從而獲取 aio 輸出,但是失敗了,不知道為什麼我報文發過去,板子也收到了,但是就是沒辦法觸發 aio 的回撥函式,但是 dio 命令的回撥卻是正常的,於是我在這取了個巧,通過 EFR32 的事件機制規避了這個問題。

通過傳送 dio 命令觸發 dio 函式的回撥函式,然後在dio 回撥函式中啟用事件,呼叫事件函式獲取 感測器資料然後通過 aio通道傳送給精靈一號。

這是 dio 函式的回撥函式,在這啟用事件


void emberAfOnOffClusterServerAttributeChangedCallback(int8u endpoint,
                                                       EmberAfAttributeId attributeId)
{
    EmberAfStatus status;
    uint8_t data[1];
    emberAfCorePrintln("---------------LED---------------");
    emberAfCorePrintln("attributeId:%x",attributeId);


      status = emAfReadAttribute(endpoint,
                                 ZCL_ON_OFF_CLUSTER_ID,
                                 attributeId,
                                 0x40,
                                 0x0000,
                                 data,
                                 1,
                                 NULL);
      if (status == EMBER_ZCL_STATUS_SUCCESS) {
          if(attributeId == ZCL_ACOINFO_ZB_DIO_ATTR_1_ATTRIBUTE_ID){
               //啟用事件
               emberEventControlSetActive(AcoinfoAioReportEventControl);
           }
      }
}

這是事件處理函式,在這裡獲取到 IADC 資料,並且傳送到精靈一號

void AcoinfoAioReportEventHandler(void)
{
    // 在下次使用之前禁用該事件
    emberEventControlSetInactive(AcoinfoAioReportEventControl);
//
//    // 開始轉換 IADC
    IADC_command(IADC0,iadcCmdStartSingle);

    // Wait for conversion to be complete
    while((IADC0->STATUS & (_IADC_STATUS_CONVERTING_MASK
                | _IADC_STATUS_SINGLEFIFODV_MASK)) != IADC_STATUS_SINGLEFIFODV); //while combined status bits 8 & 6 don't equal 1 and 0 respectively

    sample = IADC_pullSingleFifoResult(IADC0).data;

    emberAfCorePrintln("sample:%d",sample);
    float data = 100 - (((sample - AirValue)/(WaterValue - AirValue))*100);
    if(data > 100)
    {
    	data = 100;
    } else if(data < 0)
    {
    	data = 0;
    }
    emberAfCorePrintln("data:%d",data);

    uint8_t * p_data = (uint8_t *)&data;
    uint8_t buf[7] = {0};
    buf[0] = ZCL_ACOINFO_ZB_AIO_ATTR_1_ATTRIBUTE_ID && 0xFF;
    buf[1] = ZCL_ACOINFO_ZB_AIO_ATTR_1_ATTRIBUTE_ID >> 8;
    buf[2] = ZCL_FLOAT_SINGLE_ATTRIBUTE_TYPE;
    for(int i=0;i<4;i++){
        buf[3+i] = *p_data++;
    }
    emberAfFillCommandGlobalServerToClientReportAttributes(ZCL_ACOINFO_ZB_AIO_CLUSTER_ID,
                                                           (uint8_t *)buf, 7);
    emberAfSetCommandEndpoints(1, 1);
    emberAfSendCommandUnicast(EMBER_OUTGOING_DIRECT, 0x0000);

    // 延遲 5 秒後重新觸發事件
//    emberEventControlSetDelayMS(AcoinfoAioReportEventControl, 5000);
//    // 結尾處重置回未啟用狀態
    emberEventControlSetInactive(AcoinfoAioReportEventControl);
}

總結

土壤溼度感測器的 ZigBee 版本就完成了,不過不知道什麼原因,這塊 EFR32 板子和精靈一號的相性極差,裝置非常容易掉線,而且重連很慢,板子斷電後想要重新連上也是非常困難的事情。不知道是什麼情況。但是好歹是成功了
在這裡插入圖片描述

相關文章