• 基于STM32的花卉温室控温系统设计


    一、前言

    随着人们对花卉养殖的需求不断增长,花卉温室的建设和管理成为了一个重要的课题。在花卉温室中,温度是一个至关重要的环境参数,对花卉的生长和发展有着直接的影响。为了提供一个稳定的生长环境,控制温室的温度变得非常重要。

    本项目设计一个基于STM32微控制器的花卉温室控温系统。该系统利用STM32F103C8T6作为主控芯片,通过与DS18B20温度传感器和0.96寸OLED显示屏等硬件模块的连接,实现对温室内温度的监测和控制。同时,系统还配备了两个独立按键,用于设置温度阀值。

    温度传感器采用DS18B20,能够准确地监测温室内的温度。通过与STM32微控制器的通信,可以实时获取温度数据。显示屏采用SPI协议的0.96寸OLED显示屏,用于显示当前环境的温度以及温度阀值。用户可以通过按键设置温度阀值,以便系统能够根据设定的阀值进行温度控制。

    当温度低于设定的温度阀值时,系统将通过继电器控制热风机进行加热,吹出热风来控制室温。通过实时监测温度并根据设定的阀值进行控制,系统能够保持温室内的温度在一个适宜的范围,为花卉提供一个稳定的生长环境。

    项目的设计用于提高花卉温室的自动化程度,减轻人工管理的负担,同时提供一个稳定的温度控制方案,以促进花卉的生长和发展。通过使用STM32微控制器和相关硬件模块,该系统能够实现温度的实时监测和自动控制,为花卉温室管理者提供了一种方便、高效的解决方案。

    image-20230802154655957

    加上远程控制之后的最终系统模型图:

    image-20230802154537086

    二、硬件选型介绍

    以下是基于STM32的花卉温室控温系统的硬件选型:

    【1】主控芯片:STM32F103C8T6

    • STM32F103系列具有良好的性能和丰富的外设,适合嵌入式应用。
    • STM32F103C8T6是一款32位ARM Cortex-M3内核的微控制器,具有64KB的Flash存储器和20KB的RAM。

    【2】温度传感器:DS18B20

    • DS18B20是一款数字温度传感器,采用单总线接口进行通信。
    • 具有高精度、防水防尘等特点,非常适合测量温室内的温度。
    • 通过引脚连接到STM32的GPIO口,并使用OneWire协议进行数据通信。

    【3】显示屏:0.96寸OLED显示屏

    • 选择支持SPI协议的0.96寸OLED显示屏作为显示设备,可以方便地显示环境温度和温度阀值。
    • OLED显示屏具有低功耗、高对比度、视角广等优点,适合嵌入式应用。

    【4】按键:两个独立按键

    • 选择两个独立按键用于设置温度阀值,可以通过按下按钮增加或减小温度阀值。

    【5】继电器:用于控制热风机加热

    • 根据温度阀值和实时温度数据,通过STM32的GPIO口控制继电器的开关,从而控制热风机的加热。
    • 继电器的选型要根据热风机的额定电流和电压来确定,确保能够正常工作。

    三、设计思路

    软件逻辑设计思路:

    【1】初始化STM32外设,包括GPIO、SPI、USART等。

    【2】设置温度阀值的初始值,并通过按键调节阀值。

    【3】循环读取DS18B20温度传感器的数据,并将读取到的温度值与阀值进行比较。

    【4】如果当前温度低于阀值,则控制继电器闭合,热风机开始加热;否则,打开继电器,停止加热。

    【5】将温度值和阀值显示在OLED屏幕上,通过USART串口输出给用户。

    【6】不断循环执行以上步骤,实现温室的自动控温功能。

    伪代码:

    // 定义变量
    float temperature;  // 当前温度值
    float threshold;    // 温度阀值
    
    // 初始化硬件和外设
    void initialize() {
        initialize_GPIO();     // 初始化GPIO
        initialize_SPI();      // 初始化SPI
        initialize_USART();    // 初始化USART
        initialize_DS18B20();  // 初始化DS18B20
        initialize_OLED();     // 初始化OLED显示屏
        initialize_Button();   // 初始化按键
        initialize_Relay();    // 初始化继电器
    }
    
    // 读取温度值
    float readTemperature() {
        // 通过DS18B20读取温度值
        // 返回温度值
    }
    
    // 读取阀值
    float readThreshold() {
        // 读取按键的状态,并调节阀值
        // 返回阀值
    }
    
    // 控制加热器
    void controlHeater(float currTemperature, float currThreshold) {
        if (currTemperature < currThreshold) {
            // 温度低于阀值,控制继电器闭合,热风机加热
        } else {
            // 温度高于或等于阀值,打开继电器,停止加热
        }
    }
    
    // 显示温度和阀值
    void displayTemperature(float currTemperature, float currThreshold) {
        // 在OLED屏幕上显示温度值和阀值
        // 通过USART串口输出温度值和阀值
    }
    
    // 主函数
    int main() {
        initialize();  // 初始化
        
        while (1) {
            temperature = readTemperature();          // 读取温度值
            threshold = readThreshold();              // 读取阀值
            controlHeater(temperature, threshold);     // 控制加热器
            displayTemperature(temperature, threshold);// 显示温度和阀值
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    以上是基本的软件逻辑设计思路和伪代码。

    四、代码实现

    4.1 读取温度显示

    下面是使用STM32F103C8T6读取DS18B20温度传感器数据,并将温度显示到OLED显示屏上的实现代码:

    #include "stm32f10x.h"
    #include "delay.h"
    #include "onewire.h"
    #include "ds18b20.h"
    #include "ssd1306.h"
    
    int main(void)
    {
        // 初始化延迟函数
        delay_init();
        
        // 初始化OLED显示屏
        SSD1306_Init();
        
        // 初始化DS18B20温度传感器
        DS18B20_Init();
        
        float temperature = 0.0;
        char tempStr[10];
        
        while (1)
        {
            // 读取DS18B20温度传感器数据
            temperature = DS18B20_GetTemp();
            
            // 将温度转换为字符串
            sprintf(tempStr, "%.2f C", temperature);
            
            // 清空OLED显示屏
            SSD1306_Clear();
            
            // 在OLED显示屏上显示温度
            SSD1306_GotoXY(0, 0);
            SSD1306_Puts("Temperature:", &Font_7x10, SSD1306_COLOR_WHITE);
            SSD1306_GotoXY(0, 20);
            SSD1306_Puts(tempStr, &Font_11x18, SSD1306_COLOR_WHITE);
            
            // 刷新OLED显示屏
            SSD1306_UpdateScreen();
            
            // 延时一段时间
            delay_ms(1000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    代码中,使用了封装好库文件,包括延迟函数(delay.h)、OneWire总线(onewire.h)、DS18B20温度传感器(ds18b20.h)和SSD1306 OLED显示屏(ssd1306.h)的库文件。

    在主函数中,初始化延迟函数和OLED显示屏,初始化DS18B20温度传感器。然后进入无限循环,在循环中读取DS18B20温度传感器的温度数据,将温度显示到OLED显示屏上。温度数据通过sprintf函数转换为字符串,使用SSD1306库函数在OLED显示屏上进行显示。通过延时函数延时一段时间,实现温度的定时更新。

    4.2 DS18B20的代码

    头文件代码:

    #ifndef DS18B20_H
    #define DS18B20_H
    
    #include "stm32f10x.h"
    
    // DS18B20引脚定义
    #define DS18B20_GPIO_PORT   GPIOA
    #define DS18B20_GPIO_PIN    GPIO_Pin_0
    
    // DS18B20函数声明
    void DS18B20_Init(void);
    void DS18B20_WriteByte(uint8_t data);
    uint8_t DS18B20_ReadByte(void);
    float DS18B20_GetTemp(void);
    
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    源文件代码:

    #include "ds18b20.h"
    #include "delay.h"
    
    // 初始化DS18B20温度传感器
    void DS18B20_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        
        // 使能GPIOA时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        
        // 配置GPIOA引脚为推挽输出
        GPIO_InitStructure.GPIO_Pin = DS18B20_GPIO_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure);
        
        // 将引脚拉低一段时间
        GPIO_ResetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN);
        delay_us(500);
        
        // 将引脚拉高一段时间
        GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN);
        delay_us(80);
        
        // 等待DS18B20的响应
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
        GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure);
        delay_us(80);
    }
    
    // 向DS18B20写入一个字节的数据
    void DS18B20_WriteByte(uint8_t data)
    {
        uint8_t i;
        
        // 将引脚设置为推挽输出
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Pin = DS18B20_GPIO_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure);
        
        // 写入数据
        for (i = 0; i < 8; i++)
        {
            GPIO_ResetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN);
            delay_us(2);
            if (data & 0x01)
            {
                GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN);
            }
            delay_us(60);
            GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN);
            delay_us(2);
            data >>= 1;
        }
    }
    
    // 从DS18B20读取一个字节的数据
    uint8_t DS18B20_ReadByte(void)
    {
        uint8_t i, data = 0;
        
        // 将引脚设置为推挽输出
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Pin = DS18B20_GPIO_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure);
        
        // 读取数据
        for (i = 0; i < 8; i++)
        {
            GPIO_ResetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN);
            delay_us(2);
            GPIO_SetBits(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN);
            delay_us(2);
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
            GPIO_Init(DS18B20_GPIO_PORT, &GPIO_InitStructure);
            delay_us(2);
            data >>= 1;
            if (GPIO_ReadInputDataBit(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN))
            {
                data |= 0x80;
            }
            delay_us(60);
        }
        
        return data;
    }
    
    // 获取DS18B20温度数据
    float DS18B20_GetTemp(void)
    {
        uint8_t tempLSB, tempMSB;
        int16_t tempData;
        float temperature;
        
        // 发送温度转换命令
        DS18B20_WriteByte(0xCC);     // 跳过ROM操作
        DS18B20_WriteByte(0x44);     // 发送温度转换命令
        
        // 等待温度转换完成
        while (!GPIO_ReadInputDataBit(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN));
        
        // 发送读取温度命令
        DS18B20_WriteByte(0xCC);     // 跳过ROM操作
        DS18B20_WriteByte(0xBE);     // 发送读取温度命令
        
        // 读取温度数据
        tempLSB = DS18B20_ReadByte();
        tempMSB = DS18B20_ReadByte();
        
        // 计算温度值
        tempData = (tempMSB << 8) | tempLSB;
        if (tempData & 0x8000)      // 温度为负数
        {
            tempData = ~tempData + 1;
            temperature = -((float)tempData / 16.0);
        }
        else                        // 温度为正数
        {
            temperature = (float)tempData / 16.0;
        }
        
        return temperature;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    4.3 OLED显示屏代码

    头文件:

    #ifndef SSD1306_H
    #define SSD1306_H
    
    #include "stm32f10x.h"
    #include "fonts.h"
    
    // SSD1306显示屏参数定义
    #define SSD1306_I2C_ADDR      0x78    // I2C地址
    #define SSD1306_WIDTH         128     // 显示屏宽度
    #define SSD1306_HEIGHT        64      // 显示屏高度
    
    // SSD1306函数声明
    void SSD1306_Init(void);
    void SSD1306_Clear(void);
    void SSD1306_UpdateScreen(void);
    void SSD1306_GotoXY(uint16_t x, uint16_t y);
    void SSD1306_Puts(const char* str, FontDef_t* font, uint8_t color);
    
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    源文件:

    #include "ssd1306.h"
    #include "i2c.h"
    
    static uint8_t SSD1306_Buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8];
    
    void SSD1306_Init(void)
    {
        // 初始化I2C总线
        I2C_Init();
        
        // 向SSD1306发送初始化命令
        uint8_t initCommands[] = {
            0xAE,           // 关闭显示
            0xD5, 0x80,     // 设置时钟分频因子
            0xA8, 0x3F,     // 设置驱动路数
            0xD3, 0x00,     // 设置显示偏移
            0x40,           // 设置显示开始行
            0x8D, 0x14,     // 设置电荷泵
            0x20, 0x00,     // 设置内存地址模式
            0xA1,           // 设置段重定义
            0xC8,           // 设置COM扫描方向
            0xDA, 0x12,     // 设置COM引脚配置
            0x81, 0xCF,     // 设置对比度控制
            0xD9, 0xF1,     // 设置预充电周期
            0xDB, 0x40,     // 设置VCOMH电压倍率
            0xA4,           // 全局显示开启
            0xA6,           // 设置显示方式
            0xAF            // 开启显示
        };
        
        for (uint8_t i = 0; i < sizeof(initCommands); i++)
        {
            I2C_WriteByte(SSD1306_I2C_ADDR, 0x00, initCommands[i]);
        }
        
        // 清空缓冲区
        SSD1306_Clear();
        
        // 更新显示屏
        SSD1306_UpdateScreen();
    }
    
    void SSD1306_Clear(void)
    {
        memset(SSD1306_Buffer, 0x00, sizeof(SSD1306_Buffer));
    }
    
    void SSD1306_UpdateScreen(void)
    {
        for (uint8_t i = 0; i < 8; i++)
        {
            I2C_WriteBuffer(SSD1306_I2C_ADDR, 0x40, &SSD1306_Buffer[SSD1306_WIDTH * i], SSD1306_WIDTH);
        }
    }
    
    void SSD1306_GotoXY(uint16_t x, uint16_t y)
    {
        if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT)
            return;
        
        SSD1306_Buffer[(x + (y / 8) * SSD1306_WIDTH)] |= (1 << (y % 8));
    }
    
    void SSD1306_Puts(const char* str, FontDef_t* font, uint8_t color)
    {
        while (*str)
        {
            for (uint8_t i = 0; i < font->FontWidth; i++)
            {
                uint8_t temp = font->data[(*str - 32) * font->FontWidth + i];
                for (uint8_t j = 0; j < font->FontHeight; j++)
                {
                    if (temp & (1 << j))
                    {
                        SSD1306_GotoXY(font->FontWidth * i + j, font->FontHeight * i + j);
                        SSD1306_Buffer[(font->FontWidth * i + j + (font->FontHeight * i + j) / 8 * SSD1306_WIDTH)] |= (1 << ((font->FontHeight * i + j) % 8));
                    }
                    else
                    {
                        SSD1306_GotoXY(font->FontWidth * i + j, font->FontHeight * i + j);
                        SSD1306_Buffer[(font->FontWidth * i + j + (font->FontHeight * i + j) / 8 * SSD1306_WIDTH)] &= ~(1 << ((font->FontHeight * i + j) % 8));
                    }
                }
            }
            
            str++;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    五、总结

    本项目设计了基于STM32的花卉温室控温系统,通过使用DS18B20温度传感器、OLED显示屏和继电器等硬件模块,实现了对温室内温度的监测和控制。该系统能够根据预设的温度阀值,自动控制热风机的加热,以维持温室内的适宜温度,从而保证花卉的生长环境。

    在软件逻辑设计方面,采用了STM32的外设和中断机制,结合合适的算法和状态判断,实现了温度数据的获取和比较,并根据结果控制继电器的开关。通过OLED显示屏和USART串口,能够及时地将温度值和阀值反馈给用户,方便用户了解当前环境并进行调节。

    本项目的设计和实现为温室控温系统提供了一个具体的解决方案,通过合理的硬件选型和软件逻辑设计,能够满足花卉种植对温度控制的需求。在未来的发展中,系统将在农业领域发挥重要作用,并为人们创造更舒适、高效的温控环境。

  • 相关阅读:
    <C++> 反向迭代器
    全面进化!Apache Doris 1.2.0 Release 版本正式发布|版本通告
    汽车屏类产品(三):抬头显示Head-Up Display(HUD)
    k8s集群调度,亲和性,污点,容忍,排障
    应用配置文件 student-system.yaml 生成 Deployment报错,有没有厉害的解决一下的
    java计算机毕业设计税务缴纳管理系统源程序+mysql+系统+lw文档+远程调试
    『忘了再学』Shell基础 — 23、其他环境变量配置文件
    SpringMVC(四)REST风格
    Linux 小技巧1
    安卓性能优化,UI优化漫谈
  • 原文地址:https://blog.csdn.net/xiaolong1126626497/article/details/133266452