RSL10-002GEVB植物管家节点设计
本帖最后由 dql2016 于 2021-7-17 17:18 编辑<p>本次项目中有一个植物管家功能节点,顾名思义,植物管家就是可以帮你自动照料植物花卉盆景,自动浇水(也可以手动控制浇水),随时掌控植物生长状态。先上实物图如下:</p>
<p> </p>
<p>基于RSL10-002GEVB设计,扩展板板载了DHT22温湿度传感器、5V直流小水泵、AD接口电容式土壤湿度传感器、光敏电阻。系统框图如下:</p>
<p></p>
<p>光敏电阻用于感知光照,可扩展补光灯模块对植物进行补充光照,温湿度传感器用于感知周围环境温湿度,土壤湿度传感器用于测量土壤湿度,太干的话就自动浇水,小水泵则用于将水从储水瓶抽到花盆中。微信小程序目前提供土壤湿度值显示、光照值显示,水泵启停控制按钮。</p>
<p> </p>
<p></p>
<p> </p>
<p>本设计中,使用RSL10-002GEVB的2路ADC口A0、A1分别测量光敏电阻端电压值、土壤湿度传感器模拟输出,进而感知光照和土壤湿度,使用RSL10-002GEVB的DIO11作为控制小水泵启停,驱动电路原本设计使用继电器,但发现RSL10的IO驱动能力还是太弱,常用的继电器模块无法驱动,只好采用了十分简单的三极管扩流方法驱动5V直流小水泵。</p>
<p>RSL10-002GEVB端程序依旧基于peripheral_server例程实现,RSL10-002GEVB提供一个具有通知权限的特征值用于将采集的传感器数据发送给微信小程序,一个具有可写权限的特征值用于水泵控制。程序没什么新意,在调试驱动DHT22的时候,奈何总是失败,遂放弃,看来RSL10的IO还是需要深入研究才行。</p>
<p>在app_init.c中初始化代码如下:</p>
<pre>
<code class="language-cpp">void App_Initialize(void)
{
/* Mask all interrupts */
__set_PRIMASK(PRIMASK_DISABLE_INTERRUPTS);
/* Disable all interrupts and clear any pending interrupts */
Sys_NVIC_DisableAllInt();
Sys_NVIC_ClearAllPendingInt();
/* Test DIO12 to pause the program to make it easy to re-flash */
DIO->CFG = DIO_MODE_INPUT| DIO_WEAK_PULL_UP |
DIO_LPF_DISABLE | DIO_6X_DRIVE;
while (DIO_DATA->ALIAS == 0);
/* Configure the current trim settings for VCC, VDDA */
ACS_VCC_CTRL->ICH_TRIM_BYTE = VCC_ICHTRIM_16MA_BYTE;
ACS_VDDA_CP_CTRL->PTRIM_BYTE = VDDA_PTRIM_16MA_BYTE;
/* Start 48 MHz XTAL oscillator */
ACS_VDDRF_CTRL->ENABLE_ALIAS = VDDRF_ENABLE_BITBAND;
ACS_VDDRF_CTRL->CLAMP_ALIAS = VDDRF_DISABLE_HIZ_BITBAND;
/* Wait until VDDRF supply has powered up */
while (ACS_VDDRF_CTRL->READY_ALIAS != VDDRF_READY_BITBAND);
/* Disable RF TX power amplifier supply voltage and
* connect the switched output to VDDRF regulator */
ACS_VDDPA_CTRL->ENABLE_ALIAS = VDDPA_DISABLE_BITBAND;
ACS_VDDPA_CTRL->VDDPA_SW_CTRL_ALIAS = VDDPA_SW_VDDRF_BITBAND;
/* Enable RF power switches */
SYSCTRL_RF_POWER_CFG->RF_POWER_ALIAS = RF_POWER_ENABLE_BITBAND;
/* Remove RF isolation */
SYSCTRL_RF_ACCESS_CFG->RF_ACCESS_ALIAS = RF_ACCESS_ENABLE_BITBAND;
/* Start the 48 MHz oscillator without changing the other register bits */
RF->XTAL_CTRL = ((RF->XTAL_CTRL & ~XTAL_CTRL_DISABLE_OSCILLATOR) |
XTAL_CTRL_REG_VALUE_SEL_INTERNAL);
/* Enable the 48 MHz oscillator divider using the desired prescale value */
RF_REG2F->CK_DIV_1_6_CK_DIV_1_6_BYTE = CK_DIV_1_6_PRESCALE_6_BYTE;
/* Wait until 48 MHz oscillator is started */
while (RF_REG39->ANALOG_INFO_CLK_DIG_READY_ALIAS !=
ANALOG_INFO_CLK_DIG_READY_BITBAND);
/* Switch to (divided 48 MHz) oscillator clock */
Sys_Clocks_SystemClkConfig(JTCK_PRESCALE_1 |
EXTCLK_PRESCALE_1 |
SYSCLK_CLKSRC_RFCLK);
/* Configure clock dividers */
CLK->DIV_CFG0 = (SLOWCLK_PRESCALE_8 | BBCLK_PRESCALE_1 |
USRCLK_PRESCALE_1);
CLK->DIV_CFG2 = (CPCLK_PRESCALE_8 | DCCLK_PRESCALE_2);
BBIF->CTRL = (BB_CLK_ENABLE | BBCLK_DIVIDER_8 | BB_WAKEUP);
/* Configure ADC channel 7 to measure VBAT/2 */
//Sys_ADC_Set_Config(ADC_VBAT_DIV2_NORMAL | ADC_NORMAL | ADC_PRESCALE_6400);
//Sys_ADC_InputSelectConfig(7,(ADC_NEG_INPUT_GND |ADC_POS_INPUT_VBAT_DIV2));
/* Configure DIOs */
Sys_DIO_Config(LED_DIO_NUM, DIO_MODE_GPIO_OUT_0);
Sys_DIO_Config(11, DIO_MODE_GPIO_OUT_1|DIO_LPF_DISABLE | DIO_6X_DRIVE);
Sys_DIO_Config(10, DIO_MODE_GPIO_OUT_0);
//add by dql begin
Sys_DIO_Config(0, DIO_MODE_INPUT);
Sys_DIO_Config(1, DIO_MODE_INPUT);
Sys_DIO_Config(2, DIO_MODE_INPUT);
Sys_DIO_Config(3, DIO_MODE_INPUT);
/* Set the ADC configuration */
Sys_ADC_Set_Config(ADC_VBAT_DIV2_NORMAL | ADC_NORMAL | ADC_PRESCALE_320H);
/* Set the battery monitor interrupt configuration */
Sys_ADC_Set_BATMONIntConfig(INT_EBL_ADC | ADC_INT_CH0 | ADC_INT_CH1| ADC_INT_CH2| ADC_INT_CH3| ADC_INT_CH4| ADC_INT_CH5 | ADC_INT_CH6 | ADC_INT_CH7 | INT_EBL_BATMON_ALARM);
/* Configure both input selection for an ADC channel to GND so the OFFSET is subtracted automatically to result. */
Sys_ADC_InputSelectConfig(0, ADC_POS_INPUT_DIO0 | ADC_NEG_INPUT_GND);
Sys_ADC_InputSelectConfig(1, ADC_POS_INPUT_DIO1 | ADC_NEG_INPUT_GND);
Sys_ADC_InputSelectConfig(2, ADC_POS_INPUT_DIO2 | ADC_NEG_INPUT_GND);
Sys_ADC_InputSelectConfig(3, ADC_POS_INPUT_DIO3 | ADC_NEG_INPUT_GND);
Sys_ADC_InputSelectConfig(4, ADC_POS_INPUT_AOUT | ADC_NEG_INPUT_GND);
Sys_ADC_InputSelectConfig(5, ADC_POS_INPUT_VDDC | ADC_NEG_INPUT_GND);
Sys_ADC_InputSelectConfig(6, ADC_POS_INPUT_VBAT_DIV2 | ADC_NEG_INPUT_GND);
Sys_ADC_InputSelectConfig(7, ADC_POS_INPUT_GND | ADC_NEG_INPUT_GND);
NVIC_EnableIRQ(ADC_BATMON_IRQn);
//add by dql end
/* Initialize the baseband and BLE stack */
BLE_Initialize();
/* Set radio output power of RF */
//Sys_RFFE_SetTXPower(OUTPUT_POWER_DBM);
/* Initialize environment */
App_Env_Initialize();
/* Stop masking interrupts */
__set_PRIMASK(PRIMASK_ENABLE_INTERRUPTS);
__set_FAULTMASK(FAULTMASK_ENABLE_INTERRUPTS);
}
</code></pre>
<p>主要代码如下:</p>
<pre>
<code class="language-cpp">#include "app.h"
#include <stdio.h>
#include <stdint.h>
void delay_us(uint32_t us)
{
uint32_t n=15;
while(n--);
}
#define THRESHOLD 1.6
#define THRESHOLD_CFG ((uint32_t)(THRESHOLD / (2 * 0.0078)))
#define ADC_FILTER_COUNTS 100
volatile uint8_t data_ready_flag;
volatile float adc_value;
uint8_t buffer={0};
int cnt=0;
uint32_t timestamp = 0;
int t,h;
/* ----------------------------------------------------------------------------
* Function : void ADC_BATMON_IRQHandler(void)
* ----------------------------------------------------------------------------
* Description : Handle ADC and BATMON interrupts. When the interrupt is from
* ADC add the ADC value to the accumulator adc_value and
* increment the counter. When counter reach 100 calculate the
* average and set data_ready.
* When the interrupt is from battery monitor set bat_error.
* Inputs : None
* Outputs : None
* Assumptions : None
* ------------------------------------------------------------------------- */
void ADC_BATMON_IRQHandler(void)
{
static uint32_t adc_samples_count = 0;
static uint32_t adc_filter_sum = {0.0f};
uint32_t adc_status = Sys_ADC_Get_BATMONStatus();
if ((adc_status & (1 << ADC_BATMON_STATUS_BATMON_ALARM_STAT_Pos)) == BATMON_ALARM_TRUE)
{
/* Battery monitor alarm status is set */
/* Clear the battery monitor status and counter */
Sys_ADC_Clear_BATMONStatus();
uint32_t dummy = ADC->BATMON_COUNT_VAL;
}
else
{
adc_filter_sum += ADC->DATA_TRIM_CH;
adc_filter_sum += ADC->DATA_TRIM_CH;
adc_filter_sum += ADC->DATA_TRIM_CH;
adc_filter_sum += ADC->DATA_TRIM_CH;
adc_filter_sum += ADC->DATA_TRIM_CH;
adc_filter_sum += ADC->DATA_TRIM_CH;
adc_filter_sum = adc_filter_sum + ADC->DATA_TRIM_CH;
adc_filter_sum = adc_filter_sum + ADC->DATA_TRIM_CH;
adc_samples_count++;
if (adc_samples_count == ADC_FILTER_COUNTS)
{
adc_samples_count = 0;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
adc_filter_sum = 0;
adc_filter_sum = 0;
adc_filter_sum = 0;
adc_filter_sum = 0;
adc_filter_sum = 0;
adc_filter_sum = 0;
adc_filter_sum = 0;
adc_filter_sum = 0;
data_ready_flag = 1;
}
}
}
uint16_t adc_in_volts_mv;
void Send_ADC_Value(void)
{
uint8_t size;
float adc_in_volts;
int32_t v;
for(int i=0;i<8;i++)
{
if(i<4)
{
adc_in_volts<i> = 2*(adc_value<i> * 2.0f) / (float)16384;//2^14=16384 Vref=2.0V ad端口使用了10K:10K分压电阻所以乘以2
}
else
{
adc_in_volts<i> = (adc_value<i> * 2.0f) / (float)16384;//2^14=16384 Vref=2.0V
}
/* Multiply by 2 as we measure VBAT/2 and divide by a gain factor to convert the value from ADC. */
if(i==6)
{
adc_in_volts<i> = 2*(adc_value<i> * 2.0f) / (float)16384;
}
adc_in_volts_mv<i>=(int32_t)(adc_in_volts<i> * 1000);
//PRINTF("ADC[%d] input value = %d mV\n",i,adc_in_volts_mv<i>);
}
}
int main(void)
{
int moto_status=0;
App_Initialize();
Sys_GPIO_Set_Low(11);
PRINTF("SystemCoreClock = %dMHz\n", SystemCoreClock/1000000);
PRINTF("=== PlantKeeper based on peripheral_server has started ===\n");
data_ready_flag = 0;
adc_value = 0.0f;
adc_value = 0.0f;
adc_value = 0.0f;
adc_value = 0.0f;
adc_value = 0.0f;
adc_value = 0.0f;
adc_value = 0.0f;
adc_value = 0.0f;
//Main application loop:
// - Run the kernel scheduler
// - Send notifications for the battery voltage and RSSI values
// - Refresh the watchdog and wait for an interrupt before continuing
while (1)
{
Kernel_Schedule();
if (data_ready_flag == 1)
{
data_ready_flag = 0;
Send_ADC_Value();
}
for (int i = 0; i < NUM_MASTERS; i++)
{
if (ble_env<i>.state == APPM_CONNECTED)
{
/* Send battery level if battery service is enabled */
if (app_env.send_batt_ntf<i> && bass_support_env<i>.enable)
{
PRINTF("__SEND BATTERY LEVEL %d\n",app_env.batt_lvl);
app_env.send_batt_ntf<i> = false;
Batt_LevelUpdateSend(ble_env<i>.conidx,app_env.batt_lvl, 0);
}
//填充需要发送给手机的通知数据
buffer=((adc_in_volts_mv & 0xff00) >> 8 );//A0 高8位
buffer=(adc_in_volts_mv & 0x00ff);//A0 低8位
buffer=((adc_in_volts_mv & 0xff00) >> 8 );//A1 高8位
buffer=(adc_in_volts_mv & 0x00ff);//A1 低8位
cs_env<i>.tx_len=4;
/* Update custom service characteristics, send notifications if notification is enabled */
if (cs_env<i>.tx_value_changed && cs_env<i>.sent_success)
{
cs_env<i>.tx_value_changed = false;
(cs_env<i>.val_notif)++;
if (cs_env<i>.tx_cccd_value & ATT_CCC_START_NTF)
{
//PRINTF("[%s:%d %s]==== notifications is enabled ====\n",__FILE__,__LINE__,__FUNCTION__);
//memset(cs_env<i>.tx_value, cs_env<i>.val_notif,CS_TX_VALUE_MAX_LENGTH);
//CustomService_SendNotification(ble_env<i>.conidx,CS_IDX_TX_VALUE_VAL,&cs_env<i>.tx_value,CS_TX_VALUE_MAX_LENGTH);
//memset(cs_env<i>.tx_value, cs_env<i>.val_notif,1);
//CustomService_SendNotification(ble_env<i>.conidx,CS_IDX_TX_VALUE_VAL,&cs_env<i>.tx_value,1);
CustomService_SendNotification(ble_env<i>.conidx,CS_IDX_TX_VALUE_VAL,buffer,cs_env<i>.tx_len);
}
}
if (cs_env<i>.rx_value_changed)
{
PRINTF("[%s:%d %s]==== get data from phone start_hdl:0x%04x ====\n",__FILE__,__LINE__,__FUNCTION__,cs_env<i>.start_hdl);
for (int j=0;j<cs_env<i>.rx_len;j++)
{
PRINTF("[%s:%d %s]==== get data from phone %d:0x%02x ====\n",__FILE__,__LINE__,__FUNCTION__,j,cs_env<i>.rx_value);
}
if(cs_env<i>.rx_value==0x31)
{
if(moto_status==0)
{
PRINTF("=== turn on water===");
moto_status=1;
Sys_GPIO_Set_High(11);
}
}
else if(cs_env<i>.rx_value==0x66)
{
if(moto_status==1)
{
PRINTF("=== turn off water===");
moto_status=0;
Sys_GPIO_Set_Low(11);
}
}
cs_env<i>.rx_value_changed=false;
}
}
}
/* Refresh the watchdog timer */
Sys_Watchdog_Refresh();
/* Wait for an event before executing the scheduler again */
//SYS_WAIT_FOR_EVENT;
}
}
</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></code></pre>
<p><i><i><i>将采集的两路AD数据用4个字节发送出去;接收到小程序发来的0x31则打开水泵,0x66则关闭水泵。</i></i></i></p>
<p><i><i><i>微信小程序逻辑十分简单,主要就是启动后加载页面,图标,收到蓝牙通知数据后触发回调进行数据显示,按钮按下后触发相应的回调函数并改变图标状态,发送数据给RSL10-002GEVB。</i></i></i></p>
<pre>
<i><i><i>
<code class="language-javascript">// index.js
// 获取应用实例
const app = getApp()
var event = require('../../utils/event.js')
Page({
data: {
device_id:123,
device_status:"在线",// 显示是否在线的字符串,默认离线
plant_status:"良好",
checked: false,//led的状态。默认led关闭
shidu:0,//湿度值,默认为空
guangzhao:0,//光照值,默认为空
icon:"/utils/img/waterOff.png",//显示图标的状态,默认是关闭状态图标
},
//屏幕打开时执行的函数
onLoad: function () {
//接收别的页面传过来的数据
event.on('plantKeeperDataChanged', this, function(data) {
//另外一个页面传过来的data是16进制字符串形式 前面2个字节是A0端口采集的(光照)、后面2个字节是A1端口采集的(土壤湿度)
console.log("接收到蓝牙设备发来的数据:"+data)
var a=parseInt(data+data,16);
var b=parseInt(data+data,16);
var c=a*256+b;
var d=parseInt(data+data,16);
var e=parseInt(data+data,16);
var f=d*256+e;
//湿度小于认为是干旱
var g="良好";
if(f<500)
{
g="干旱";
}
else
{
g="良好";
}
this.setData({
shidu:f,
guangzhao:c,
plant_status:g,
});
})
},
onUnload: function() {
event.remove('plantKeeperDataChanged', this);
},
//点击图片执行的函数
onChange(){
var that = this
//如果点击前是打开状态,现在更换为关闭状态,并更换图标,完成状态切换
if( that.data.checked == true){
//发送'fefe'给蓝牙设备 关
event.emit('EnvMonitorSendData2Device','f');
this.setData({
icon: "/utils/img/waterOff.png",
checked:false //设置状态为false
});
}else{
//发送'1234'给蓝牙设备 开
event.emit('EnvMonitorSendData2Device','1');
that.setData({
icon: "/utils/img/waterOn.png",
checked:true//设置状态为true
});
}
},
})
</code></i></i></i></pre>
<p><i><i><i> </i></i></i></p>
<p><i><i><i> </i></i></i></p>
<p>楼主的植物管家还行吧,很不错</p>
<p>RSL10的IO驱动确实不行,比较常用的继电器模块无法驱动是个小不足</p>
<p> </p>
Jacktang 发表于 2021-7-17 20:47
楼主的植物管家还行吧,很不错
RSL10的IO驱动确实不行,比较常用的继电器模块无法驱动是个小不足
&nb ...
<p>是的,这个IO还是太少了,arduino接口上好几个空的</p>
页:
[1]