前言
後悔,總之就是非常後悔,我當時到底是為啥才會豬油蒙心,選擇了 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:
標頭檔案與初始化配置
#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 板子和精靈一號的相性極差,裝置非常容易掉線,而且重連很慢,板子斷電後想要重新連上也是非常困難的事情。不知道是什麼情況。但是好歹是成功了