一、项目介绍
随着现代农业的发展,人们对于水资源的合理利用越来越重视。而传统的灌溉方式往往存在着浪费水资源、劳动力投入大、效率低等问题。因此,设计一款智能灌溉控制系统,可以实现对灌溉水量的精准控制,增加水资源利用率,提高农业生产效率,具有广泛的应用前景。
当前文章介绍一款高性能的智能灌溉控制系统的开发过程,可自动采集电压、电流、累计用水量,并根据用户需要实现自动灌溉、定时灌溉、周期灌溉和手动灌溉等多种模式,同时具备中控室控制、手机短信、现场遥控及现场手动等多种方式控制功能。该系统可以对现场温湿度限值进行设置和修改,并通过控制器或后台监控系统完成灌溉起始时间、停止时间、喷灌时间等参数设置。系统显示功能包括液晶屏以中文菜单方式显示现场采集数据以及后台监控系统配大屏幕显示器,图形、表格等多种形式动态显示整个灌溉区运行情况。同时,在电压、电流或者流量出现异常时,系统可以及时报警。该系统供电为220VAC,流量计量误差精度为2级,使用二维码或卡实现预付费功能,通讯使用4G与云平台连接。
二、设计功能
本系统采用STM32作为主控芯片,并通过AD模块采集电压、电流和流量等数据。同时,通过继电器控制灌溉设备的启停,使用PWM控制阀门的开合程度,从而实现精确控制灌溉水量。通信模块则采用4G模块与云平台连接,实现远程监控及控制功能。预付费模块则使用二维码或卡实现预付费功能,用户需在充值后才能使用该系统进行灌溉操作。
系统软件设计包括采集程序、控制程序、前端程序和后台程序。其中,采集程序主要负责采集电压、电流、流量等数据,并将采集到的数据上传到云平台;控制程序主要负责控制灌溉设备的启停和阀门的开合程度,从而实现灌溉控制;前端程序主要负责实现中文菜单方式显示现场采集数据,并提供灌溉模式选择、参数设置等功能;后台程序主要负责实现大屏幕显示器、图形、表格等多种形式动态显示整个灌溉区运行情况。
【1】硬件部分
MCU:本系统采用STM32作为主控芯片,其具有高性能、低功耗等优点,可满足该系统的高要求。
数据采集模块:本系统通过AD模块采集电压、电流和流量等数据,然后使用MCU进行处理,并将采集到的数据存储到Flash中。
控制模块:本系统通过继电器控制灌溉设备的启停,同时使用PWM控制阀门的开合程度,从而实现精确控制灌溉水量。
通信模块:本系统采用4G模块与云平台连接,实现远程监控及控制功能。
预付费模块:本系统使用二维码或卡实现预付费功能,用户需在充值后才能使用该系统进行灌溉操作。
【2】软件部分
采集程序:本系统的采集程序主要负责采集电压、电流、流量等数据,并将采集到的数据上传到云平台。
控制程序:本系统的控制程序主要负责控制灌溉设备的启停和阀门的开合程度,从而实现灌溉控制。
前端程序:本系统的前端程序主要负责实现中文菜单方式显示现场采集数据,并提供灌溉模式选择、参数设置等功能。
后台程序:本系统的后台程序主要负责实现大屏幕显示器、图形、表格等多种形式动态显示整个灌溉区运行情况。
三、系统实现
具体实现过程如下:
(1)采集程序
采集程序主要由AD模块和STM32芯片完成。AD模块采集电压、电流和流量等数据,经过滤波和放大处理后,传输到STM32芯片上。STM32芯片通过串口将采集到的数据上传到云平台,并存储在Flash中。
(2)控制程序
控制程序主要由继电器和PWM模块完成。继电器用于控制灌溉设备的启停,PWM模块则用于控制阀门的开合程度,从而实现精确控制灌溉水量。控制程序通过读取Flash中存储的参数,确定灌溉起始时间、停止时间、喷灌时间等操作流程,并根据实时采集到的数据进行动态调整,保证灌溉操作的准确性和稳定性。
(3)前端程序
前端程序主要是通过液晶屏以中文菜单方式显示现场采集数据,并提供灌溉模式选择、参数设置等功能。用户可以通过按键或触摸屏来进行操作,并实时查看灌溉操作的运行情况。此外,用户还可以通过手机短信、现场遥控或现场手动等方式对灌溉操作进行控制。
(4)后台程序
后台程序主要负责实现大屏幕显示器、图形、表格等多种形式动态显示整个灌溉区运行情况,同时还能够将采集到的数据进行分析和统计,为灌溉管理提供决策参考。
四、核心代码
【1】电机控制代码
以下是STM32F103ZET6通过PWM控制直流电机转速的代码,并封装成子函数调用的示例:
首先,需要在STM32CubeMX中配置TIM定时器和GPIO引脚,以及将PWM模式设置为嵌套边沿对齐模式,然后生成代码,并在main.c文件中添加以下代码:
#include 'main.h'
#include 'stm32f1xx_hal.h'
/* TIM handle structure */
TIM_HandleTypeDef htim;
/* Function prototypes */
void PWM_Init(TIM_HandleTypeDef *htim, uint32_t channel);
void Set_Motor_Speed(TIM_HandleTypeDef *htim, uint32_t channel, uint16_t speed);
int main(void)
{
/* Initialize the HAL Library */
HAL_Init();
/* Initialize TIM2 PWM with a frequency of 10 kHz */
PWM_Init(&htim2, TIM_CHANNEL_1);
/* Set the motor speed to 50% */
Set_Motor_Speed(&htim2, TIM_CHANNEL_1, 5000);
while (1)
{
/* Infinite loop */
}
}
/**
* @brief Initializes PWM output on specified TIM channel.
* @param htim: TIM handle structure.
* @param channel: TIM channel to be used for PWM output.
* @retval None
*/
void PWM_Init(TIM_HandleTypeDef *htim, uint32_t channel)
{
TIM_OC_InitTypeDef sConfigOC = {0};
/* Configure PWM output on specified TIM channel */
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(htim, &sConfigOC, channel);
/* Start PWM output */
HAL_TIM_PWM_Start(htim, channel);
}
/**
* @brief Sets the motor speed on specified TIM channel.
* @param htim: TIM handle structure.
* @param channel: TIM channel to be used for PWM output.
* @param speed: Motor speed in units of 1/10,000th of the maximum speed.
* For example, a speed of 5000 would set the motor speed to 50%.
* @retval None
*/
void Set_Motor_Speed(TIM_HandleTypeDef *htim, uint32_t channel, uint16_t speed)
{
uint16_t max_speed = htim->Init.Period;
/* Ensure that speed is within range */
if (speed > max_speed)
speed = max_speed;
/* Update PWM duty cycle */
__HAL_TIM_SET_COMPARE(htim, channel, speed);
}
在以上代码中,定义了两个函数:PWM_Init和Set_Motor_Speed。PWM_Init用于初始化TIM定时器的PWM输出,并设置指定通道的PWM模式和默认占空比为0。Set_Motor_Speed用于设置电机的转速,其接收三个参数:TIM句柄结构体,指定的通道,以及电机的转速(单位为1/10,000最大速度)。该函数会将电机的转速转换为PWM占空比,并通过__HAL_TIM_SET_COMPARE函数更新PWM占空比。
最后,可以按照以下步骤将代码封装成子函数调用:
将以上代码复制到单独的.c文件中,并包含必要的头文件。
在该文件中定义一个名为Motor_Control的函数,该函数接收三个参数:TIM句柄结构体,指定的通道,以及电机的转速。
在Motor_Control函数中调用PWM_Init和Set_Motor_Speed函数,并传递相应的参数。
在main函数中调用Motor_Control函数,传递相应的参数。
以下是Motor_Control函数的示例代码:
#include 'motor_control.h'
void Motor_Control(TIM_HandleTypeDef *htim, uint32_t channel, uint16_t speed)
{
/* Initialize PWM output on specified TIM channel */
PWM_Init(htim, channel);
/* Set the motor speed */
Set_Motor_Speed(htim, channel, speed);
}
在以上示例中,将PWM的初始化和设置电机转速的函数封装成了一个名为Motor_Control的函数。可以在需要控制电机转速的其他地方调用Motor_Control函数即可。
注意,在调用Motor_Control函数之前,需要先定义并初始化TIM句柄结构体,并确保GPIO引脚已经正确配置为TIM模式。此外,如果需要控制多个电机,可以在Motor_Control函数中增加参数以区分不同的电机通道。
以下是motor_control.h头文件的示例代码:
cCopy Code#ifndef __MOTOR_CONTROL_H__
#define __MOTOR_CONTROL_H__
#include 'stm32f1xx_hal.h'
/* Function prototypes */
void PWM_Init(TIM_HandleTypeDef *htim, uint32_t channel);
void Set_Motor_Speed(TIM_HandleTypeDef *htim, uint32_t channel, uint16_t speed);
void Motor_Control(TIM_HandleTypeDef *htim, uint32_t channel, uint16_t speed);
#endif /* __MOTOR_CONTROL_H__ */
在以上头文件中,声明了三个函数:PWM_Init,Set_Motor_Speed和Motor_Control,并包含必要的头文件。
【2】电压、电流采集
为了采集220V抽水电机的用电量和当前电压,当前使用STM32F103ZET6的ADC(模数转换器)来测量电压和电流,并通过乘法器计算电功率和电能。
下面是实现方案和实现代码:
选择合适的传感器: 为了测量电压,可以使用AC-AC变压器将220V交流电压降至低电平,再使用电阻分压器将电压信号调整在ADC的输入范围内。 为了测量电流,可以使用霍尔传感器或者电阻式传感器,将电流信号转换成电压信号,然后通过电阻分压器调整信号范围。
配置ADC: 使用STM32CubeMX软件选择相应的引脚和配置ADC模块,设置采样频率、参考电压等参数。需要注意的是,ADC模块只能同时转换一路模拟信号,因此需要轮流采样电压和电流信号。
计算电流、电压、功率和能量: 将电压和电流信号转换成数字值后,可以使用下面的公式计算电流、电压、功率和能量:
Copy Code电流 = AD值 / 灵敏度
电压 = AD值 / 分压比
功率 = 电压 * 电流
能量 = 功率 * 时间
其中,灵敏度是传感器的转换系数,分压比是电阻分压器的比值,时间可以通过定时器计算。
输出数据: 将测量的电流、电压、功率和能量输出到串口或者LCD显示屏上。可以设置一个定时器,在一定时间间隔内输出一次数据。
实现代码:
#include 'stm32f1xx_hal.h'
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim2;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM2_Init(void);
uint16_t ad_val_ch1, ad_val_ch2;
float voltage, current, power, energy;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_TIM2_Init();
while (1)
{
// ADC采样电压信号
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
ad_val_ch1 = HAL_ADC_GetValue(&hadc1);
voltage = ad_val_ch1 * 3.3 / 4096 * 10; // 假设分压比为10
// ADC采样电流信号
HAL_TIM_Base_Start(&htim2);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
ad_val_ch2 = HAL_ADC_GetValue(&hadc1);
current = ad_val_ch2 * 3.3 / 4096 * 50; // 假设灵敏度为50mV/A
// 计算功率和能量
power = voltage * current;
energy += power * 0.1; // 假设定时器时间间隔为100ms
// 输出测量结果
printf('Voltage: %.2f Vrn', voltage);
printf('Current: %.2f Arn', current);
printf('Power: %.2f Wrn', power);
printf('Energy: %.2f Jrn', energy);
HAL_Delay(1000); // 假设数据输出间隔为1s
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCCRCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
__HAL_RCC_ADC1_CLK_ENABLE();
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_0; // 假设测量电压的ADC通道为0
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_1; // 假设测量电流的ADC通道为1
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM2_Init(void)
{
__HAL_RCC_TIM2_CLK_ENABLE();
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7200 - 1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 10000 - 1; // 假设定时器时间间隔为100ms
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(char *file, uint32_t line)
{
}
#endif