1240-基于STM32控制的智能窗帘控制系统仿真(仿真加程序) 功能如下: 1.光敏电阻可以实时检测环境中的亮度情况 2.OLED显示屏可以实时显示测量环境亮度、阈值亮度、控制模式 3.两个设置按键可以实时修改环境亮度报警阈值 模式切换按键可以切换自动和手动模式 5.自动模式下,当环境亮度大于阈值时,步进电机转动,控制窗帘关闭当环境亮度小于阈值时,控制窗帘打开 6.手动模式下,开关按键可以手动开启窗帘和关闭窗帘

整个项目用到的东西其实挺常见的:STM32F103C8T6最小系统板、光敏电阻测亮度、12864的OLED屏显示数据、四个按键(两个调阈值、一个切模式、一个手动开关),还有28BYJ-48步进电机加ULN2003驱动板,仿真用Proteus就行,不用真的买硬件,搭个原理图就能跑。

首先是亮度检测这块,用的是PA0接光敏电阻的分压电路,毕竟PA0是ADC1_IN0,懒得改通道,直接用默认的就行。这里我踩过坑,一开始没给ADC校准,采出来的数值飘得厉害,晒个台灯都能跳几十个数,后来加上校准步骤才稳。贴个简化的初始化代码:

// ADC初始化,用的STM32标准库
void ADC1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
    
    // PA0设为模拟输入,别开上下拉,光敏本身已经分压了
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换,不用每次手动触发
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
    ADC_Cmd(ADC1, ENABLE);
    // 校准步骤,必须加,不然数值不准
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

每次读数据的时候直接调用ADC_GetConversionValue(ADC1)就能拿到当前的ADC值,我偷懒直接把这个值当亮度显示了,其实可以套个分压公式转成实际照度,不过仿真里凑活看就行,逻辑对就ok。

接下来是OLED屏,用来显示当前亮度、阈值还有当前工作模式,用的是SSD1306驱动的12864屏,接的I2C引脚。写了个显示当前系统状态的函数,三行刚好打完所有信息:

void OLED_ShowSysStatus(u16 light, u16 threshold, u8 mode)
{
    char buf[32];
    OLED_Clear();
    // 第一行打当前亮度
    sprintf(buf, "Light: %d", light);
    OLED_ShowString(0,0,(u8*)buf,16);
    // 第二行打阈值
    sprintf(buf, "Thresh: %d", threshold);
    OLED_ShowString(0,20,(u8*)buf,16);
    // 第三行打模式
    mode ? OLED_ShowString(0,40,(u8*)"Mode: Manual",16) : OLED_ShowString(0,40,(u8*)"Mode: Auto",16);
    OLED_Update(); // 这里一定要加!不然显存没刷新,啥都不显示,我之前踩过这个坑,烧进去半天没反应
}

这里要注意,每次显示完必须调用刷新函数,不然OLED的显存没更新,不会出东西,而且字体选16号刚好三行占满整个屏幕,不会挤。

1240-基于STM32控制的智能窗帘控制系统仿真(仿真加程序) 功能如下: 1.光敏电阻可以实时检测环境中的亮度情况 2.OLED显示屏可以实时显示测量环境亮度、阈值亮度、控制模式 3.两个设置按键可以实时修改环境亮度报警阈值 模式切换按键可以切换自动和手动模式 5.自动模式下,当环境亮度大于阈值时,步进电机转动,控制窗帘关闭当环境亮度小于阈值时,控制窗帘打开 6.手动模式下,开关按键可以手动开启窗帘和关闭窗帘

按键这块我用了四个,分别是阈值加、阈值减、模式切换、手动开关,都是接在PB口上,用的是最笨的轮询加延时消抖,毕竟按键不多,没必要搞外部中断。代码大概是这样:

void Key_Scan(void)
{
    // PB0:阈值+,PB1:阈值-,PB2:模式切换,PB3:手动开关
    if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
    {
        delay_ms(10); // 消抖,防止按一下触发好几次
        if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
        {
            threshold += 50;
            if(threshold > 4095) threshold = 0; // ADC是12位,最大值4095
        }
        while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0); // 等待松手,不然按住一直加
    }
    // 阈值减的逻辑和加的差不多,就不重复贴了
    // 模式切换键,按一下就切换自动/手动
    if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == 0)
    {
        delay_ms(10);
        if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == 0)
        {
            work_mode = !work_mode;
        }
        while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == 0);
    }
    // 手动开关只有在手动模式下才生效,不然自动模式按了也白按
    if(work_mode == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) ==0)
    {
        delay_ms(10);
        if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) ==0)
        {
            curtain_status = !curtain_status;
            Motor_Run(curtain_status);
        }
        while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == 0);
    }
}

这里消抖用的是10ms延时,足够滤掉按键的抖动了,要是用外部中断的话还要写中断服务函数,反而麻烦,轮询就够了。

最后是核心的电机控制,用的是28BYJ-48五线四相步进电机,减速比挺大的,转一圈要好久,刚好适合窗帘用,驱动用ULN2003,毕竟STM32的IO口带不动电机的电流。我把电机的四相接到PC0到PC3上,写了个转动函数:

// dir=0开窗帘,dir=1关窗帘
void Motor_Run(u8 dir)
{
    u8 i;
    // 四相八拍的驱动序列,扭矩比单相的大一点
    u8 motor_seq[8] = {0x01,0x03,0x02,0x06,0x04,0x0c,0x08,0x09};
    if(dir == 1)
    {
        // 关窗帘的话倒着走序列
        for(i=7; i>=0; i--)
        {
            GPIO_Write(GPIOC, motor_seq[i]);
            delay_ms(2); // 转速,太慢的话窗帘动得慢,太快会丢步
        }
    }
    else
    {
        for(i=0; i<8; i++)
        {
            GPIO_Write(GPIOC, motor_seq[i]);
            delay_ms(2);
        }
    }
}

这里要注意电机的供电,必须外接5V电源,不能用STM32的5V引脚,不然要么转不动,要么烧IO口,我第一次试的时候差点把开发板烧了,吓一跳。

最后把所有东西串到主函数里,自动模式的逻辑是:如果当前亮度高于阈值,就关窗帘;低于阈值就开窗帘,这里我加了个50的死区,防止亮度在阈值附近来回跳,不然电机一直启停,吵得要死。主循环大概是这样:

int main(void)
{
    delay_init();
    OLED_Init();
    ADC1_Init();
    GPIO_Init(); // 初始化按键和电机的GPIO
    u16 threshold = 2000; // 默认阈值
    u8 work_mode = 0; // 默认自动模式
    u8 curtain_status = 0; // 默认窗帘开着
    while(1)
    {
        u16 light_val = ADC_GetConversionValue(ADC1);
        OLED_ShowSysStatus(light_val, threshold, work_mode);
        Key_Scan();
        
        if(work_mode == 0) // 自动模式
        {
            if(light_val > threshold)
            {
                Motor_Run(1);
                curtain_status = 1;
            }
            else if(light_val < threshold -50) // 死区,防止来回跳
            {
                Motor_Run(0);
                curtain_status = 0;
            }
        }
        delay_ms(100); // 主循环加个延时,不然CPU占满
    }
}

仿真的话直接在Proteus里搭个原理图就行:光敏电阻接PA0,OLED接I2C的PB6和PB7,四个按键接PB0到PB3,电机通过ULN2003接PC0到PC3,然后把编译好的hex文件烧进去,就能跑了。调一下光敏电阻的亮度,屏幕上的数值会跟着变,按模式切换键可以改模式,按阈值按键可以调阈值,自动模式下亮度超过阈值就关窗帘,手动模式下按开关按键就能控制窗帘开和关。

其实整个项目没啥难的,就是把各个模块拼起来,逻辑理清楚就行,中间踩了不少坑:ADC没校准、OLED忘了刷新、电机供电不够这些,都是小问题改过来就好了。要是想加功能的话,还可以加个蓝牙模块用手机遥控,或者加个红外接收用遥控器控制,反正随便造就行。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐