项目描述:
基于STM32控制的多功能小灯,有红外遥控、光敏自动调节亮度、以及超声波检测人是否在位功能。然后用OLED实现人机交互,多级菜单,功能选择。
硬件:
STM32C8T6
红外接收器HS0038
光敏传感器
0.96 4引脚OLED屏幕
HCSR04超声波测距
若干杜邦线以及按键
LED灯
模块 | IO | |
红外控制 | A1 | TIM2CH2 |
按键控制 | B12/13/14 | |
超声波控制 | A9\A10 | Trig\Echo |
光敏控制 | B1 | ADC12IN9 |
LED | PB0 | TIM3CH3 PWM |
项目步骤以及功能实现
一.编写各模块驱动
1.PWM
2.红外接受模块
3.ADC光敏传感器
4.超声波模块
5.移植U8g2图象图
6.编写多级菜单
二.代码编写
PWM
void PWM_Init()
{
/*启动时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
/*初始化引脚*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
/*TIM3选择内部时钟*/
TIM_InternalClockConfig(TIM3);
/*时基配置*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1 ;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 10-1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 72-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
/********中断用在超声波模块用了计算返回时间********/
TIM_ClearFlag(TIM3, TIM_FLAG_Update); //清除更新中断标志位
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //开启更新中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组
/*中断配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体,配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //指定中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //中断使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //设置响应优先级
NVIC_Init(&NVIC_InitStructure); // https://blog.zeruns.tech
/************************************************/
/*OC输出比较通道初始化化*/
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable ;
TIM_OCInitStruct.TIM_Pulse = 0;
TIM_OC3Init(TIM3,&TIM_OCInitStruct);
TIM_Cmd(TIM3,ENABLE);
}
/**
* @brief 设置PWM的比较值
* @param
* @param
* @retval None
*/
void PWM_SetCompare(uint16_t Compare)
{
TIM_SetCompare3(TIM3, Compare);
}
红外接受
#include "Remote.h"
#include "stm32f10x.h" // Device header
#include "OLED.h"
void Remote_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);//将时钟源选择内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 20000-1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0x03;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM2,&TIM_ICInitStructure);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE);
}
/**********************************红外遥控***********************************/
/*
通过检测下捕捉下降沿进入中断间隔时间来判断红外接收数据
*/
unsigned int IR_Time;//进中断的间隔时间
unsigned char IR_State;//当前状态
unsigned char IR_Data[4];//读取到的数据分别是地址码 反码 数据码 反码
unsigned char IR_pData;//当前读取到的数据位
unsigned char IR_DataFlag;//数据读取完毕标志
unsigned char IR_RepeatFlag;//重复按键标志
unsigned char IR_Address;//地址码
unsigned char IR_Command;//数据之类码
void TIM2_IRQHandler()
{
if(TIM_GetITStatus(TIM2,TIM_IT_CC2)==SET)
{
if(IR_State==0)//红外产生下降沿第一次进出中断
{
TIM_SetCounter(TIM2,0);//清楚计时器
TIM_Cmd(TIM2,ENABLE);//启动计时器
IR_State=1;
}
else if(IR_State==1)
{
IR_Time = TIM_GetCounter(TIM2);//获取计时器的值
TIM_SetCounter(TIM2,0);//计时器清零
if(IR_Time>13500-500 && IR_Time<13500+500)//
{
IR_State=2;
}
else if(IR_Time>11370-500 && IR_Time<11370+500)
{
IR_RepeatFlag = 1;
TIM_Cmd(TIM2,DISABLE);
IR_State=0;
}
else
{
IR_State = 1;
}
}
else if(IR_State == 2)
{
IR_Time = TIM_GetCounter(TIM2);
TIM_SetCounter(TIM2,0);
if(IR_Time>1120-500 && IR_Time<1120+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
IR_pData++; //数据位置指针自增
}
else if(IR_Time>2250-500 && IR_Time<2250+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1
IR_pData++; //数据位置指针自增
}
else
{
IR_pData = 0;
IR_State = 1;
}
if(IR_pData==32)
{
IR_pData = 0;
IR_Address=IR_Data[0]; //转存数据
IR_Command=IR_Data[2];
IR_DataFlag=1; //置收到连发帧标志位为1
TIM_SetCounter(TIM2,0);
IR_State = 0;
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
}
}
/**
* @brief 红外遥控获取收到数据帧标志位
* @param 无
* @retval 是否收到数据帧,1为收到,0为未收到
*/
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到连发帧标志位
* @param 无
* @retval 是否收到连发帧,1为收到,0为未收到
*/
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到的地址数据
* @param 无
* @retval 收到的地址数据
*/
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
/**
* @brief 红外遥控获取收到的命令数据
* @param 无
* @retval 收到的命令数据
*/
unsigned char IR_GetCommand(void)
{
return IR_Command;
}
/**
* @brief 放回按键功能,有增加,减少,开关
* @param
* @retval None
*/
uint8_t IR_Num;
uint8_t IR_GetNum()
{
static uint8_t Mode;
if(IR_GetDataFlag())
{
if(IR_GetCommand()==IR_VOL_ADD)
{
IR_Num += 20;
if(IR_Num>=100) IR_Num=100;
}
if(IR_GetCommand()==IR_VOL_MINUS)
{
IR_Num -= 20;
if(IR_Num>100||IR_Num==0) IR_Num=0;
}
if(IR_GetCommand()==IR_POWER)
{
if(Mode==0)
{
Mode=1;
IR_Num = 50;
}
else if(Mode==1)
{
Mode = 0;
IR_Num = 0;
}
}
}
return IR_Num/10;
}
光敏
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
/*启动时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//配置时钟总线使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
/*初始化引脚*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//引脚配置成模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADC时钟
ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 1, ADC_SampleTime_55Cycles5);//配置ADC通道采样排名
/*ADC初始化*/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_None ;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE);
/*ADC校准*/
ADC_ResetCalibration(ADC1);//复位校准寄存器
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//在复位校准过程中Status一直被置Set
ADC_StartCalibration(ADC1);//启动校准寄存器
while (ADC_GetCalibrationStatus(ADC1) == SET);//在启动校准过程中Status一直被置Set
}
uint16_t ADC_GetValue()
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件触发开始转变
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//如果转变成功了就为Set,RESET说明还在转变过程中
return ADC_GetConversionValue(ADC1);//转变成功后放回
}
uint16_t ADC_GetCompare()
{
uint16_t Value;
Value = ADC_GetValue();
if(Value<3500&&Value>2000) return 10;
if(Value<2000&&Value>1000) return 5;
if(Value<1000&&Value>0) return 0;
return Value;
}
超声波
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#define Trig GPIO_Pin_9
#define Echo GPIO_Pin_10
uint64_t time=0; //声明变量,用来计时
uint64_t time_end=0; //声明变量,存储回波信号时间
/**
* @brief HCSR04初始化
* @param
* @param
* @retval None
*/
void HCSRO4_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitTypeDef GPIO_InitStructure2;
GPIO_InitStructure2.GPIO_Mode = GPIO_Mode_IPD; //设置GPIO口为下拉输入模式
GPIO_InitStructure2.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure2.GPIO_Speed = GPIO_Speed_50MHz; //设置GPIO口10
GPIO_Init(GPIOA,&GPIO_InitStructure2); //初始化GPIOA
GPIO_WriteBit(GPIOA,Trig,0);
Delay_us(15);
}
/**
* @brief 返回测试的距离mm为单位
* @param 无
* @param 无
* @retval Distance_mm:返回测距结果
*/
uint16_t Get_sonar_mm()
{
uint32_t Distance,Distance_mm = 0;
GPIO_WriteBit(GPIOA,Trig,1); //输出高电平
Delay_us(15); //延时15微秒
GPIO_WriteBit(GPIOA,Trig,0); //输出低电平
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_10)==0); //等待低电平结束
time=0; //计时清零
while(GPIO_ReadInputDataBit(GPIOA,Echo)==1); //等待高电平结束
time_end=time; //记录结束时的时间
if(time_end/100<38) //判断是否小于38毫秒,大于38毫秒的就是超时,直接调到下面返回0
{
Distance=(time_end*346)/2; //计算距离,25°C空气中的音速为346m/s
Distance_mm=Distance/100; //因为上面的time_end的单位是10微秒,所以要得出单位为毫米的距离结果,还得除以100
}
return Distance_mm; //返回测距结果
}
/**
* @brief 返回PWM的比较器的值
* @param
* @param
* @retval 0 or 5
*/
uint8_t Get_Sonar_Num()
{
int Sonar_Num=Get_sonar_mm();
if(Sonar_Num <= 300)
{
return 5;
}
else
{
return 0;
}
}
void TIM3_IRQHandler(void) //更新中断函数,用来计时,每10微秒变量time加1
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) //获取TIM3定时器的更新中断标志位
{
time++;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除更新中断标志位
}
}
多级菜单
多级菜单主要是使用数组索引的方法来实现
#include "stm32f10x.h" // Device header
#include "menu.h"
#include "Key.h"
#include "OLED.h"
#include "Remote.h"
#include "PWM.h"
#include "ADC.h"
#include "HCSR04.h"
#include "u8g2.h"
#include "U8G2_int.h"
#include <Stdio.h>
#define KEY_UP 1 //向上
#define KEY_DOWN 2 //向下
#define KEY_ENTER 3 //确定
uint8_t fresh_flag; //进入第2层函数标志位
/*********************************第0层************************************/
void Main_UI(u8g2_t *u8g2)
{
draw(u8g2);
}
/**************************************************************************/
/*********************************第1层************************************/
/*******************************************************************************
* 函数名 : fun1
* 功能 : 主要在第一层显示列表,每个函数只是>的位置指向该选项而已
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Main_Remote_fun1(u8g2_t *u8g2)//红外选项
{
u8g2_SetFontMode(u8g2, 1);//
u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols);//字库选择
u8g2_ClearBuffer(u8g2);
u8g2_DrawStr(u8g2,0,16,">");
u8g2_DrawStr(u8g2,16,16,"[1]Remote");
u8g2_DrawStr(u8g2,16,32,"[2]Photosen");
u8g2_DrawStr(u8g2,16,48,"[3]Hcsr04");
u8g2_DrawStr(u8g2,16,64,"<--");
u8g2_SendBuffer(u8g2);
}
void Main_Photosen_fun1(u8g2_t *u8g2)//光敏电阻选项
{
u8g2_SetFontMode(u8g2, 1);//字体模式
u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols);//字库选择
u8g2_ClearBuffer(u8g2);
/*************写显示数据进缓存区中***************/
u8g2_DrawStr(u8g2,0,32,">");
u8g2_DrawStr(u8g2,16,16,"[1]Remote");
u8g2_DrawStr(u8g2,16,32,"[2]Photosen");
u8g2_DrawStr(u8g2,16,48,"[3]Hcsr04");
u8g2_DrawStr(u8g2,16,64,"<--");
/*************发送缓存区中的数据到显示屏幕***************/
u8g2_SendBuffer(u8g2);
}
void Main_Hcsr04_fun1(u8g2_t *u8g2)//超声波选项
{
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols);//字库选择
u8g2_ClearBuffer(u8g2);
u8g2_DrawStr(u8g2,0,48,">");
u8g2_DrawStr(u8g2,16,16,"[1]Remote");
u8g2_DrawStr(u8g2,16,32,"[2]Photosen");
u8g2_DrawStr(u8g2,16,48,"[3]Hcsr04");
u8g2_DrawStr(u8g2,16,64,"<--");
u8g2_SendBuffer(u8g2);
}
void Main_Return_fun1(u8g2_t *u8g2)//放回上一层
{
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols);//字库选择
u8g2_ClearBuffer(u8g2);
u8g2_DrawStr(u8g2,0,64,">");
u8g2_DrawStr(u8g2,16,16,"[1]Remote");
u8g2_DrawStr(u8g2,16,32,"[2]Photosen");
u8g2_DrawStr(u8g2,16,48,"[3]Hcsr04");
u8g2_DrawStr(u8g2,16,64,"<--");
u8g2_SendBuffer(u8g2);
}
/**************************************************************************/
/*********************************第2层************************************/
/*******************************************************************************
* 函数名 : fun2
* 功能 : 执行改功能函数
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Main_Remote_fun2(u8g2_t *u8g2)
{
Remote_Init();
uint8_t Value;
Value = IR_GetNum();
PWM_SetCompare(Value);
char str[20];
sprintf(str,"%d",Value);//将整型初始化为字符串类型
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols);
u8g2_ClearBuffer(u8g2);
u8g2_DrawStr(u8g2,0,48,"Value:");
u8g2_DrawStr(u8g2,48,48,str);
u8g2_SetFont(u8g2, u8g2_font_crox4tb_tf);//字库选择
u8g2_DrawStr(u8g2,30,13,"Remote");
u8g2_SendBuffer(u8g2);
fresh_flag = 1;
}
void Main_Photosen_fun2(u8g2_t *u8g2)
{
AD_Init();
uint8_t Value = ADC_GetCompare();
char str[20];
sprintf(str,"%d",Value);
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols);
u8g2_ClearBuffer(u8g2);
u8g2_DrawStr(u8g2,0,48,"Value:");
u8g2_DrawStr(u8g2,48,48,str);
u8g2_SetFont(u8g2, u8g2_font_crox4tb_tf);//字库选择
u8g2_DrawStr(u8g2,30,13,"Photosen");
u8g2_SendBuffer(u8g2);
PWM_SetCompare(Value);
fresh_flag = 1;
}
void Main_Hcsr04_fun2(u8g2_t *u8g2)
{
HCSRO4_Init();
uint8_t Value = Get_Sonar_Num();
char str[20];
sprintf(str,"%d",Value);
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols);
u8g2_ClearBuffer(u8g2);
u8g2_DrawStr(u8g2,0,48,"Value:");
u8g2_DrawStr(u8g2,48,48,str);
u8g2_SetFont(u8g2, u8g2_font_crox4tb_tf);//字库选择
u8g2_DrawStr(u8g2,30,13,"Hcsr04");
u8g2_SendBuffer(u8g2);
PWM_SetCompare(Value);
fresh_flag = 1;
}
/**************************************************************************/
/*********************************数组索引表************************************/
key_table table[20] =
{
//{自身索引号,上一页的索引号,下一页的索引号,子菜单的索引号,自身执行的函数}
{0,0,0,1,Main_UI},
{1,4,2,5,Main_Remote_fun1},
{2,1,3,6,Main_Photosen_fun1},
{3,2,4,7,Main_Hcsr04_fun1},
{4,3,1,8,Main_Return_fun1},
{5,1,1,1,Main_Remote_fun2},
{6,1,1,1,Main_Photosen_fun2},
{7,1,1,1,Main_Hcsr04_fun2},
{8,0,0,1,Main_UI}
};
/*******************************************************************************/
void Menu_Koop(u8g2_t *u8g2)
{
static u8 func_index=0;//当前页面索引值
static u8 last_index=127;//上一个界面索引值
static void (*current_operation_func)(u8g2_t *u8g2);//定义一个函数指针
static uint8_t KeyNum;
KeyNum = Key_GetKeyNum();
if(KeyNum != 0)
{
fresh_flag=1;
last_index=func_index;
switch(KeyNum)
{
case KEY_UP: func_index = table[func_index].up;break ;//更新索引号
case KEY_DOWN: func_index = table[func_index].down;break ;
case KEY_ENTER: func_index = table[func_index].enter;break ;
}
}
if(fresh_flag ==1)//按键按下就刷新执行函数
{
fresh_flag=0;
current_operation_func = table[func_index].current_operation;
u8g2_ClearBuffer(u8g2);
(*current_operation_func)(u8g2);//执行当前操作函数
u8g2_SendBuffer(u8g2);
last_index = func_index;
}
if(func_index==0)
{
u8g2_ClearBuffer(u8g2);
Main_UI(u8g2);
u8g2_SendBuffer(u8g2);
}
}