写点什么

基于 STM32 设计的智能灌溉控制系统

作者:DS小龙哥
  • 2023-06-15
    重庆
  • 本文字数:5760 字

    阅读完需:约 19 分钟

一、项目介绍

随着现代农业的发展,人们对于水资源的合理利用越来越重视。而传统的灌溉方式往往存在着浪费水资源、劳动力投入大、效率低等问题。因此,设计一款智能灌溉控制系统,可以实现对灌溉水量的精准控制,增加水资源利用率,提高农业生产效率,具有广泛的应用前景。


当前文章介绍一款高性能的智能灌溉控制系统的开发过程,可自动采集电压、电流、累计用水量,并根据用户需要实现自动灌溉、定时灌溉、周期灌溉和手动灌溉等多种模式,同时具备中控室控制、手机短信、现场遥控及现场手动等多种方式控制功能。该系统可以对现场温湿度限值进行设置和修改,并通过控制器或后台监控系统完成灌溉起始时间、停止时间、喷灌时间等参数设置。系统显示功能包括液晶屏以中文菜单方式显示现场采集数据以及后台监控系统配大屏幕显示器,图形、表格等多种形式动态显示整个灌溉区运行情况。同时,在电压、电流或者流量出现异常时,系统可以及时报警。该系统供电为 220VAC,流量计量误差精度为 2 级,使用二维码或卡实现预付费功能,通讯使用 4G 与云平台连接。


二、设计功能

本系统采用 STM32 作为主控芯片,并通过 AD 模块采集电压、电流和流量等数据。同时,通过继电器控制灌溉设备的启停,使用 PWM 控制阀门的开合程度,从而实现精确控制灌溉水量。通信模块则采用 4G 模块与云平台连接,实现远程监控及控制功能。预付费模块则使用二维码或卡实现预付费功能,用户需在充值后才能使用该系统进行灌溉操作。


系统软件设计包括采集程序、控制程序、前端程序和后台程序。其中,采集程序主要负责采集电压、电流、流量等数据,并将采集到的数据上传到云平台;控制程序主要负责控制灌溉设备的启停和阀门的开合程度,从而实现灌溉控制;前端程序主要负责实现中文菜单方式显示现场采集数据,并提供灌溉模式选择、参数设置等功能;后台程序主要负责实现大屏幕显示器、图形、表格等多种形式动态显示整个灌溉区运行情况。

【1】硬件部分

  1. MCU:本系统采用 STM32 作为主控芯片,其具有高性能、低功耗等优点,可满足该系统的高要求。

  2. 数据采集模块:本系统通过 AD 模块采集电压、电流和流量等数据,然后使用 MCU 进行处理,并将采集到的数据存储到 Flash 中。

  3. 控制模块:本系统通过继电器控制灌溉设备的启停,同时使用 PWM 控制阀门的开合程度,从而实现精确控制灌溉水量。

  4. 通信模块:本系统采用 4G 模块与云平台连接,实现远程监控及控制功能。

  5. 预付费模块:本系统使用二维码或卡实现预付费功能,用户需在充值后才能使用该系统进行灌溉操作。

【2】软件部分

  1. 采集程序:本系统的采集程序主要负责采集电压、电流、流量等数据,并将采集到的数据上传到云平台。

  2. 控制程序:本系统的控制程序主要负责控制灌溉设备的启停和阀门的开合程度,从而实现灌溉控制。

  3. 前端程序:本系统的前端程序主要负责实现中文菜单方式显示现场采集数据,并提供灌溉模式选择、参数设置等功能。

  4. 后台程序:本系统的后台程序主要负责实现大屏幕显示器、图形、表格等多种形式动态显示整个灌溉区运行情况。

三、系统实现

具体实现过程如下:


(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 占空比。


最后,可以按照以下步骤将代码封装成子函数调用:


  1. 将以上代码复制到单独的.c 文件中,并包含必要的头文件。

  2. 在该文件中定义一个名为 Motor_Control 的函数,该函数接收三个参数:TIM 句柄结构体,指定的通道,以及电机的转速。

  3. 在 Motor_Control 函数中调用 PWM_Init 和 Set_Motor_Speed 函数,并传递相应的参数。

  4. 在 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 头文件的示例代码:


#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(模数转换器)来测量电压和电流,并通过乘法器计算电功率和电能。


下面是实现方案和实现代码:


  1. 选择合适的传感器: 为了测量电压,可以使用 AC-AC 变压器将 220V 交流电压降至低电平,再使用电阻分压器将电压信号调整在 ADC 的输入范围内。 为了测量电流,可以使用霍尔传感器或者电阻式传感器,将电流信号转换成电压信号,然后通过电阻分压器调整信号范围。

  2. 配置 ADC: 使用 STM32CubeMX 软件选择相应的引脚和配置 ADC 模块,设置采样频率、参考电压等参数。需要注意的是,ADC 模块只能同时转换一路模拟信号,因此需要轮流采样电压和电流信号。

  3. 计算电流、电压、功率和能量: 将电压和电流信号转换成数字值后,可以使用下面的公式计算电流、电压、功率和能量:


电流 = AD值 / 灵敏度电压 = AD值 / 分压比功率 = 电压 * 电流能量 = 功率 * 时间
复制代码


其中,灵敏度是传感器的转换系数,分压比是电阻分压器的比值,时间可以通过定时器计算。


  1. 输出数据: 将测量的电流、电压、功率和能量输出到串口或者 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 V\r\n", voltage); printf("Current: %.2f A\r\n", current); printf("Power: %.2f W\r\n", power); printf("Energy: %.2f J\r\n", 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通道为0sConfig.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通道为1if (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; // 假设定时器时间间隔为100msif (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
复制代码


发布于: 2023-06-15阅读数: 19
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022-01-06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
基于STM32设计的智能灌溉控制系统_6 月优质更文活动_DS小龙哥_InfoQ写作社区