一、前言
上回FastBond2阶段1的文档中,展示了使用Scheme-it绘制的原理图,介绍了几种检测土壤湿度的方法以及原理,也说明了项目中所使用到的活动规定厂家的芯片。
想了解阶段1的具体内容可以通过链接跳转:FastBond2阶段1-基于土壤湿度检测的原理图设计
本文之后的内容将展示使用kicad继续修缮的原理图及PCB,成品的功能测试和主要程序的说明。
二、原理图及PCB介绍
流程图及原理图初步设计都是由Scheme-it网页绘制,简单的方框图和引脚图在这上面自制还挺方便的,使用了FastBond活动中要求的两家厂商的芯片,分别是ADI公司的ADP160供电芯片以及TI公司的LM358运放芯片。
这里是分享链接:https://www.digikey.cn/schemeit/project/土壤湿度检测-6b07a1c6125240f198038bc2ecdb99c3
如框图所示,由MCU核心板的TYPE-C接口提供5V电源,分别为LDO和运算放大器供电。
主控的5v供电经ADP160降为3.3v供电输出。ADP160有150mA的输出电流,只是给晶振供电的话是足够的,其它并没有什么注意的地方。ADP160/ADP161 均为超低静态电流、低压差线性调节器,工作电压为2.2 V至5.5 V,输出电流最高可达150 mA。在150 mA负载下仅有195 mV的低压差,有助于提高效率,使器件能在较宽的输入电压范围内工作。
LDO稳压至3.3v后为晶振供电,晶振提供高频振荡波通过探针入土,传出的信号经整流滤波成直流后通过运放放大。MCU使能ADC读取运放放大后的电压值以判断土壤干湿程度。流程图展示的是FDR法,电阻法则只需将探针的电压值经过运放放大即可,省去了LDO、晶振和整流滤波电路。
电阻法的原理很简单,众所周知水导电,探针之间塞满了土壤介质,土壤水分越多越导电。将探针理解为可变电阻,电阻越小越导电,而探针的电阻越小则电压越小。故检测探针的电压越小则土壤湿度越高,此为电阻法,方便实现的同时干湿两态的电压差值足以应付日常使用。
使用Scheme-it设计的原理图导出到KiCad后,有一些地方并不匹配,比如网络标签并不识别,用户自定义的符号引脚未分配等等,因此需要继续更改加以完善。经过完善的原理图如下,与阶段1中并没有什么改动,只是增加了几个切换开关便于调试。
其中LM358芯片提供十倍左右增益,电阻阻值选择如下图所示。
之后绘制PCB如下,能用就算胜利。
三、成品功能测试
首先是板卡焊接之后装配完成的样子:
探针模块暂时“裸奔”,只进行了与同轴线之间的连接工作,性能必然没有增加固定之后的稳定:
首先使用万用表检测ADP160芯片输出电压3.3v,正常工作:
探针插入已经浇过水的花盆里,观察上位机收到的ADC示数:
探针插入未浇水的花盆中,观察上位机收到的ADC示数:
经过几次简单试验后发现,在湿润土壤中的ADC示数在500以下,在干燥土壤中ADC示数在1000以上,两者状态差距明显,因此可以实现干浇湿停的基本功能。
四、程序功能说明
程序中首先初始化ADC、GPIO、UART三种功能。其中UART只用于前期测试用,ADC用于采集电压值,GPIO用于使能ADP160芯片以及后续控制抽水泵的关断。
PLATFORM_Init();
ADC_Configure();
GPIO_Configure();
USART_Configure(115200);
GPIO_SetBits(GPIOA, GPIO_Pin_7);
void ADC_Configure(void)
{
ADC_InitTypeDef ADC_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1ENR_ADC1, ENABLE);
ADC_StructInit(&ADC_InitStruct);
ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStruct.ADC_PRESCARE = ADC_PCLK2_PRESCARE_16;
ADC_InitStruct.ADC_Mode = ADC_Mode_Scan;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_Init(ADC1, &ADC_InitStruct);
ADC_SampleTimeConfig(ADC1, ADC_Samctl_240_5);
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOB, &GPIO_InitStruct);
ADC_Cmd(ADC1, ENABLE);
}
void GPIO_Configure(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_13 | GPIO_Pin_7 ;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_WriteBit(GPIOA, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_13 | GPIO_Pin_7, Bit_SET);
}
void USART_Configure(uint32_t Baudrate)
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1ENR_USART2, ENABLE);
USART_StructInit(&USART_InitStruct);
USART_InitStruct.USART_BaudRate = Baudrate;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART2, &USART_InitStruct);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource1, GPIO_AF_2);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_Init(GPIOB, &GPIO_InitStruct);
USART_Cmd(USART2, ENABLE);
}
初始化完成之后,循环读取ADC值,并增加判断条件,暂定若ADC值大于1000则浇水,若小于500则停止浇水。
while (1)
{
int i=0;
sprintf(num_str1, "%f", ADC_GetChannelVoltage(ADC_Channel_0));
USART_ClearFlag(USART2, USART_FLAG_TC);
while(num_str1[i] !=0)
{
USART_SendData(USART2,num_str1[i] );
while (RESET == USART_GetFlagStatus(USART2, USART_FLAG_TC))
{
}
i++;
}
if(ADC_GetChannelVoltage(ADC_Channel_0)<500)
{
GPIO_SetBits(GPIOA, GPIO_Pin_8);
}
if(ADC_GetChannelVoltage(ADC_Channel_0)>1000)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_8);
}
PLATFORM_DelayMS(500);
}
以上就是主要程序的说明,实现的一个简单的判断自动开关的功能。
四、活动总结
很感谢硬禾学堂举办的FastBond2活动,让我有机会通过这个活动使用一些未曾尝试过的芯片来做一些不大但很有意义的制作。这之中虽然遇到了很多难题,比如原理部分、第一次使用灵动作为主控芯片,用ADI的ADP160作为稳压器,TI的LM358作为运算放大器等,但跨过之后尽是收获。项目之中所做的成品确实很粗糙,稳定性实用性还有所改进空间,我将继续完善,在我家花园中用起来,更好监测我养的花。