前 言
这是我在嵌入式学习的第二阶段——M4理论的学习过程中完成的一个练习,如果对您有帮助的话,可以点赞,收藏,关注哦,有其他问题可以私聊交流。
项目还有需要改进优化的地方,也可以添加其他有趣的功能,期待各位大佬的指点和交流。
一、项目简介
项目名:模拟毒气检测系统(STM32F407)
功 能: 1.光敏电阻每100ms检测光照强度调节LED的亮度(光照强度越强,灯的亮度越亮)
2.毒气检测每100ms检测毒气浓度,超过20%浓度,通风(电机)启动同时关闭阀门(舵机转180度), 毒气越浓电机越快,(电机用LED4灯的亮度代替)
3.每100ms通过串口显示芯片内部温度值
4.在毒气泄露的情况下,100cm内不能有人靠近,如果低于100cm,蜂鸣器报警
5.通过按键控制RGB彩灯,KEY1控制红灯亮,KEY2控制绿灯亮,KEY3控制蓝灯亮,KEY4控制彩灯随机变色
6.PC端发送open/close给串口,控制阀门开关
说 明:在学习完ADC,定时器等原理和使用后,我便接触到了这个综合练习,这个项目主要是用于检测毒气浓度,并且附加了一些额外的功能。其中,大部分功能是在之前的的学习中已经完成了的,例如按键扫描函数,串口中断以及各种初始化配置函数。所以,这次项目主要是写ADC和定时器的一些函数,但在文章中我会发整个代码及思路。
二、功能介绍
1.光敏电阻每100ms检测光照强度调节LED的亮度(光照强度越强,灯的亮度越亮)
我通过ADC控制器将光照传感器的模拟量改为数字量,然后用输出比较根据这个数字量改变定时器的比较寄存器的值,从而控制LED灯的亮度。同时通过定时中断完成每100ms转化一次。
2.毒气检测每100ms检测毒气浓度,超过20%浓度,通风(电机)启动同时关闭阀门(舵机转180度), 毒气越浓电机越快,(电机用LED4灯的亮度代替)
我通过ADC控制器将毒气传感器的模拟量改为数字量,当这个数字量大于20%时,阀门(舵机)对应定时器通道的比较寄存器会赋值2500(舵机旋转角度对应比较寄存器的值 0°---500 180°---2500),阀门关闭(舵机旋转180°),电机(LED4)根据数字量改变TIM8-CH2的比较寄存器的值,从而改变转速(LED4亮度)。同时通过定时中断完成每100ms转化一次。
tips:因为电机模块坏了,所以用LED4的亮度代表电机的转速
3.每100ms通过串口显示芯片内部温度值
通过定时中断完成每100ms转化一次。ADC将温度传感器的模拟量转化为数字量,通过公式计算温度值,并用printf函数将温度值在串口软件打印。
4.在毒气泄露的情况下,100cm内不能有人靠近,如果低于100cm,蜂鸣器报警
我通过用超声波模块和定时器的输入捕获测距,当毒气浓度大于20%且100cm内有人时,蜂鸣器会报警,若100cm内没人,则蜂鸣器关闭。
5.通过按键控制RGB彩灯,KEY1控制红灯亮,KEY2控制绿灯亮,KEY3控制蓝灯亮,KEY4控制彩灯随机变色
通过一个定时器的三个通道控制RGB彩灯,按键按下后通过改变对应通道的比较寄存器的值控制不同的灯。
6.PC端发送open/close给串口,控制阀门开关
当在PC端通过串口发送字符串时,会进入串口中断服务函数,在函数中与open/close比较,如果比较结果为相同,则会打开/关闭阀门。
三、程序框架
根据功能需求,我们需要先完成一些简单的初始化函数:
我们需要先配置串口初始化,蜂鸣器初始化,定时器3通道1输出PWM控制LED3初始化,定时器8通道2输出PWM控制电机(LED4)初始化,RGB初始化,舵机初始化,超声波初始化,按键初始化,ADC初始化。接着配置另一个定时器用作定时中断。然后将每个功能的事件写入对应的中断服务函数中。
主函数框架
int main(void)
{
//串口初始化
//蜂鸣器初始化
//定时器3通道1输出PWM控制LED3
//定时器8通道2输出PWM控制电机(LED4)
//舵机初始化
//超声波初始化
//RGB初始化
//按键初始化
//ADC初始化
while(1)
{
//RGB彩灯控制
}
}
四、代码设计
1.串口初始化
/**************************************************************************
* 函数名 :Usart1_Init
* 函数功能 :对串口1初始化配置
* 函数参数 :U32 bps
* 函数返回值:void
* 函数说明 :
* PA9 ---- USART1_Tx
* PA10 ---- USART1_Rx
*************************************************************************/
void Usart1_Init(u32 bps)
{
/*IO口控制寄存器*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 0);
//端口模式寄存器---复用模式
GPIOA->MODER &= ~((3 << 18) | (3 << 20));
GPIOA->MODER |= ((2 << 18) | (2 << 20));
//端口输出类型寄存器---推挽
GPIOA->OTYPER &= ~(1 << 9);
//端口输出速度寄存器---2MHZ
GPIOA->OSPEEDR &= ~(3 << 18);
//上拉/下拉寄存器---浮空
GPIOA->PUPDR &= ~((3 << 18) |(3 << 20));
//复用功能寄存器
GPIOA->AFR[1] &= ~((0xf << 4) | (0xf << 8));
GPIOA->AFR[1] |= ((7 << 4) | (7 << 8));
/*串口控制寄存器*/
//串口时钟使能---usart1
RCC->APB2ENR |= (1 << 4);
//BRR
USART1->BRR = 84000000 / bps;
//CR1
USART1->CR1 &= ~(1 << 15);
USART1->CR1 &= ~(1 << 12);
USART1->CR1 |= (1 << 3);
USART1->CR1 |= (1 << 2);
//CR2
USART1->CR2 &= ~(3 << 12);
//优先级分组 --- 主函数
//优先级编码值
u32 pri = NVIC_EncodePriority (5,1,2);
//确定具体中断源
NVIC_SetPriority(USART1_IRQn,pri);
//使能NAIC响应通道
NVIC_EnableIRQ(USART1_IRQn);
//接收中断使能
USART1->CR1 |= (1 << 5);
//空闲中断使能
USART1->CR1 |= (1 << 4);
//USART1使能
USART1->CR1 |= (1 << 13);
}
2.蜂鸣器初始化
/**************************************************************************
* 函数名 :Beep_Init
* 函数功能 :对蜂鸣器初始化配置
* 函数参数 :void
* 函数返回值:void
* 函数说明 :
* BEEP --- PE0 --- 通用输出,输出推挽,速度为2MHZ,无上拉/下拉
*************************************************************************/
void Beep_Init(void)
{
//打开GPIOE时钟
RCC->AHB1ENR |= (1 << 4);
//配置PE0
GPIOE->MODER &= ~(3 << 0);
GPIOE->MODER |= (1 << 0);
GPIOE->OTYPER &= ~(1 << 0);
GPIOE->OSPEEDR &= ~(3 << 0);
GPIOE->PUPDR &= ~(3 << 0);
//关闭蜂鸣器
BEEP_OFF;
}
3.定时器3通道1输出PWM控制LED3初始化
/***********************************************
*函数名 :Tim3_ch1_pwm_Init
*函数功能 :定时器3通道1输出PWM控制LED3
*函数参数 :无
*函数返回值:无
*函数描述 :84M 84分频 1/us T:1ms
* LED3 --- PC6 --- TIM3_CH1
************************************************/
void Tim3_ch1_pwm_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 2);
//端口模式寄存器 --- 复用
GPIOC->MODER &= ~(3 << 12);
GPIOC->MODER |= (2 << 12);
//端口输出类型寄存器
GPIOC->OTYPER &= ~(1 << 6);
//端口输出速度寄存器
GPIOC->OSPEEDR &= ~(3 << 12);
//端口上拉下拉寄存器
GPIOC->PUPDR &= ~(3 << 12);
//端口复用功能寄存器
GPIOC->AFR[0] &= ~(0xf << 24);
GPIOC->AFR[0] |= (2 << 24);
/*定时器对应通道配置*/
// 定时器时钟使能
RCC->APB1ENR |= (1 << 1);
// CR1
TIM3->CR1 |= (1 << 7); //打开重载影子寄存器
TIM3->CR1 &= ~(3 << 5); //配置边沿对齐模式
TIM3->CR1 &= ~(1 << 4); //计数器递增计数
TIM3->CR1 &= ~(1 << 3); //计数器在发生更新事件时不会停止计数
TIM3->CR1 &= ~(1 << 1); //使能更新事件
// SMRC
TIM3->SMCR &= ~(7 << 0); //选择内部时钟
// CCMRx
TIM3->CCMR1 &= ~(3 << 0); //CC1 通道配置为输出
TIM3->CCMR1 |= (1 << 3); //影子寄存器使能
TIM3->CCMR1 &= ~(7 << 4); //PWM 模式 1
TIM3->CCMR1 |= (6 << 4);
// CCER
TIM3->CCER |= (1 << 0); //开启––在相应输出引脚上输出 OC1 信号
TIM3->CCER |= (1 << 1); //OC1 低电平有效
// PSC
TIM3->PSC = 84 - 1;
// ARR
TIM3->ARR = 1000 - 1;
// CCRx
TIM3->CCR1 = 1000;
// EGR
TIM3->EGR |= (1 << 0); //重新初始化计数器并生成寄存器更新事件
//计数器使能
TIM3->CR1 |= (1 << 0);
}
4.定时器8通道2输出PWM控制电机(LED4)初始化
/***********************************************
*函数名 :Tim8_ch2_pwm_Init
*函数功能 :定时器8通道2输出PWM控制电机(LED4)
*函数参数 :无
*函数返回值:无
*函数描述 :168M 168分频 1/us T:1ms
* LED4 --- PC7 --- TIM8_CH2
************************************************/
void Tim8_ch2_pwm_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 2);
//端口模式寄存器 --- 复用
GPIOC->MODER &= ~(3 << 14);
GPIOC->MODER |= (2 << 14);
//端口输出类型寄存器
GPIOC->OTYPER &= ~(1 << 7);
//端口输出速度寄存器
GPIOC->OSPEEDR &= ~(3 << 14);
//端口上拉下拉寄存器
GPIOC->PUPDR &= ~(3 << 14);
//端口复用功能寄存器
GPIOC->AFR[0] &= ~(0xf << 28);
GPIOC->AFR[0] |= (3 << 28);
/*定时器对应通道配置*/
// 定时器时钟使能
RCC->APB2ENR |= (1 << 1);
// CR1
TIM8->CR1 |= (1 << 7); //打开重载影子寄存器
TIM8->CR1 &= ~(3 << 5); //配置边沿对齐模式
TIM8->CR1 &= ~(1 << 4); //计数器递增计数
TIM8->CR1 &= ~(1 << 3); //计数器在发生更新事件时不会停止计数
TIM8->CR1 &= ~(1 << 1); //使能更新事件
// SMCR
TIM8->SMCR &= ~(7 << 0); //选择内部时钟
// CCMRx
TIM8->CCMR1 &= ~(3 << 8); //CC2 通道配置为输出
TIM8->CCMR1 |= (1 << 11); //影子寄存器使能
TIM8->CCMR1 &= ~(7 << 12); //PWM 模式 1
TIM8->CCMR1 |= (6 << 12);
// CCER
TIM8->CCER |= (1 << 5); //OC2 低电平有效
// PSC
TIM8->PSC = 168 - 1;
// ARR
TIM8->ARR = 1000 - 1;
// CCRx
TIM8->CCR2 = 0;
//BDTR
TIM8->BDTR |= (1 << 15); //允许通道输出PWM
// EGR
TIM8->EGR |= (1 << 0); //重新初始化计数器并生成寄存器更新事件
//开启––在相应输出引脚上输出 OC2 信号
TIM8->CCER |= (1 << 4);
//计数器使能
TIM8->CR1 |= (1 << 0);
}
5.RGB初始化
/***********************************************
*函数名 :RGB_Init
*函数功能 :RGB彩灯初始化函数
*函数参数 :无
*函数返回值:无
*函数描述 :
* R --- Tim5_ch2_pwm_Init()
* G --- Tim5_ch3_pwm_Init()
* B --- Tim5_ch4_pwm_Init()
************************************************/
void RGB_Init(void)
{
Tim5_ch2_pwm_Init();
Tim5_ch3_pwm_Init();
Tim5_ch4_pwm_Init();
}
/***********************************************
*函数名 :Tim5_ch2_pwm_Init
*函数功能 :定时器5通道2输出PWM控制彩灯R
*函数参数 :无
*函数返回值:无
*函数描述 :84M 84分频 1/us T:1ms
* R--------PA1-------TIM5_CH2
************************************************/
void Tim5_ch2_pwm_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 0);
//端口模式寄存器 --- 复用
GPIOA->MODER &= ~(3 << 2);
GPIOA->MODER |= (2 << 2);
//端口输出类型寄存器
GPIOA->OTYPER &= ~(1 << 1);
//端口输出速度寄存器
GPIOA->OSPEEDR &= ~(3 << 2);
//端口上拉下拉寄存器
GPIOA->PUPDR &= ~(3 << 2);
//端口复用功能寄存器
GPIOA->AFR[0] &= ~(0xf << 4);
GPIOA->AFR[0] |= (2 << 4);
/*定时器对应通道配置*/
// 定时器时钟使能
RCC->APB1ENR |= (1 << 3 );
// CR1
TIM5->CR1 |= (1 << 7); //打开重载影子寄存器
TIM5->CR1 &= ~(3 << 5); //配置边沿对齐模式
TIM5->CR1 &= ~(1 << 4); //计数器递增计数
TIM5->CR1 &= ~(1 << 3); //计数器在发生更新事件时不会停止计数
TIM5->CR1 &= ~(1 << 1); //使能更新事件
// SMRC
TIM5->SMCR &= ~(7 << 0); //选择内部时钟
// CCMRx
TIM5->CCMR1 &= ~(3 << 8); //CC2 通道配置为输出
TIM5->CCMR1 |= (1 << 11); //影子寄存器使能
TIM5->CCMR1 &= ~(7 << 12); //PWM 模式 1
TIM5->CCMR1 |= (6 << 12);
// CCER
TIM5->CCER |= (1 << 4); //开启––在相应输出引脚上输出 OC2+ 信号
TIM5->CCER |= (1 << 5); //OC2 低电平有效
// PSC
TIM5->PSC = 84 - 1;
// ARR
TIM5->ARR = 1000 - 1;
// CCRx
TIM5->CCR2 = 0;
// EGR
TIM5->EGR |= (1 << 0); //重新初始化计数器并生成寄存器更新事件
//计数器使能
TIM5->CR1 |= (1 << 0);
}
/***********************************************
*函数名 :Tim5_ch3_pwm_Init
*函数功能 :定时器5通道3输出PWM控制彩灯G
*函数参数 :无
*函数返回值:无
*函数描述 :84M 84分频 1/us T:1ms
* G--------PA2-------TIM5_CH3
************************************************/
void Tim5_ch3_pwm_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 0);
//端口模式寄存器 --- 复用
GPIOA->MODER &= ~(3 << 4);
GPIOA->MODER |= (2 << 4);
//端口输出类型寄存器
GPIOA->OTYPER &= ~(1 << 2);
//端口输出速度寄存器
GPIOA->OSPEEDR &= ~(3 << 4);
//端口上拉下拉寄存器
GPIOA->PUPDR &= ~(3 << 4);
//端口复用功能寄存器
GPIOA->AFR[0] &= ~(0xf << 8);
GPIOA->AFR[0] |= (2 << 8);
/*定时器对应通道配置*/
// 定时器时钟使能
RCC->APB1ENR |= (1 << 3);
// CR1
TIM5->CR1 |= (1 << 7); //打开重载影子寄存器
TIM5->CR1 &= ~(3 << 5); //配置边沿对齐模式
TIM5->CR1 &= ~(1 << 4); //计数器递增计数
TIM5->CR1 &= ~(1 << 3); //计数器在发生更新事件时不会停止计数
TIM5->CR1 &= ~(1 << 1); //使能更新事件
// SMRC
TIM5->SMCR &= ~(7 << 0); //选择内部时钟
// CCMRx
TIM5->CCMR2 &= ~(3 << 0); //CC3 通道配置为输出
TIM5->CCMR2 |= (1 << 3); //影子寄存器使能
TIM5->CCMR2 &= ~(7 << 4); //PWM 模式 1
TIM5->CCMR2 |= (6 << 4);
// CCER
TIM5->CCER |= (1 << 8); //开启––在相应输出引脚上输出 OC3 信号
TIM5->CCER |= (1 << 9); //OC3 低电平有效
// PSC
TIM5->PSC = 84 - 1;
// ARR
TIM5->ARR = 1000 - 1;
// CCRx
TIM5->CCR3 = 0;
// EGR
TIM5->EGR |= (1 << 0); //重新初始化计数器并生成寄存器更新事件
//计数器使能
TIM5->CR1 |= (1 << 0);
}
/***********************************************
*函数名 :Tim5_ch4_pwm_Init
*函数功能 :定时器5通道4输出PWM控制彩灯B
*函数参数 :无
*函数返回值:无
*函数描述 :84M 84分频 1/us T:1ms
* B--------PA3-------TIM5_CH4
************************************************/
void Tim5_ch4_pwm_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 0);
//端口模式寄存器 --- 复用
GPIOA->MODER &= ~(3 << 6);
GPIOA->MODER |= (2 << 6);
//端口输出类型寄存器
GPIOA->OTYPER &= ~(1 << 3);
//端口输出速度寄存器
GPIOA->OSPEEDR &= ~(3 << 6);
//端口上拉下拉寄存器
GPIOA->PUPDR &= ~(3 << 6);
//端口复用功能寄存器
GPIOA->AFR[0] &= ~(0xf << 12);
GPIOA->AFR[0] |= (2 << 12);
/*定时器对应通道配置*/
// 定时器时钟使能
RCC->APB1ENR |= (1 << 3);
// CR1
TIM5->CR1 |= (1 << 7); //打开重载影子寄存器
TIM5->CR1 &= ~(3 << 5); //配置边沿对齐模式
TIM5->CR1 &= ~(1 << 4); //计数器递增计数
TIM5->CR1 &= ~(1 << 3); //计数器在发生更新事件时不会停止计数
TIM5->CR1 &= ~(1 << 1); //使能更新事件
// SMRC
TIM5->SMCR &= ~(7 << 0); //选择内部时钟
// CCMRx
TIM5->CCMR2 &= ~(3 << 8); //CC4 通道配置为输出
TIM5->CCMR2 |= (1 << 11); //影子寄存器使能
TIM5->CCMR2 &= ~(7 << 12); //PWM 模式 1
TIM5->CCMR2 |= (6 << 12);
// CCER
TIM5->CCER |= (1 << 12); //开启––在相应输出引脚上输出 OC2+ 信号
TIM5->CCER |= (1 << 13); //OC2 低电平有效
// PSC
TIM5->PSC = 84 - 1;
// ARR
TIM5->ARR = 1000 - 1;
// CCRx
TIM5->CCR4 = 0;
// EGR
TIM5->EGR |= (1 << 0); //重新初始化计数器并生成寄存器更新事件
//计数器使能
TIM5->CR1 |= (1 << 0);
}
6.舵机初始化
/***********************************************
*函数名 :SG90_Init
*函数功能 :舵机初始化函数
*函数参数 :无
*函数返回值:无
*函数描述 :
************************************************/
void SG90_Init(void)
{
Tim14_ch1_pwm_Init();
}
/***********************************************
*函数名 :Tim14_ch1_pwm_Init
*函数功能 :定时器13通道1输出PWM控制舵机
*函数参数 :无
*函数返回值:无
*函数描述 :84M 84分频 1/us T:20ms
* SG90 --- PA7 --- TIM14_CH1
************************************************/
void Tim14_ch1_pwm_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 0);
//端口模式寄存器 --- 复用
GPIOA->MODER &= ~(3 << 14);
GPIOA->MODER |= (2 << 14);
//端口输出类型寄存器
GPIOA->OTYPER &= ~(1 << 7);
//端口输出速度寄存器
GPIOA->OSPEEDR &= ~(3 << 14);
//端口上拉下拉寄存器
GPIOA->PUPDR &= ~(3 << 14);
//端口复用功能寄存器
GPIOA->AFR[0] &= ~(0xf << 28);
GPIOA->AFR[0] |= (9 << 28);
/*定时器对应通道配置*/
// 定时器时钟使能
RCC->APB1ENR |= (1 << 8);
// CR1
TIM14->CR1 |= (1 << 7); //打开重载影子寄存器
TIM14->CR1 &= ~(1 << 1); //更新中断事件
//CCMRx
TIM14->CCMR1 &= ~(3 << 0); //CC1 通道配置为输出
TIM14->CCMR1 |= (1 << 3); //影子寄存器使能
TIM14->CCMR1 &= ~(7 << 4); //PWM 模式 1
TIM14->CCMR1 |= (6 << 4);
// CCER
TIM14->CCER &= ~(1 << 1); //OC1 高电平有效
// PSC
TIM14->PSC = 84 - 1;
// ARR
TIM14->ARR = 20000 - 1;
// CCRx
TIM14->CCR1 = 500;
//EGR
TIM14->EGR |= (1 << 0);
//通道一使能
TIM14->CCER |= (1 <<0);
//计数器使能
TIM14->CR1 |= (1 << 0);
}
7.超声波初始化
/***********************************************
*函数名 :HC_SR04_Init
*函数功能 :HC-SR04超声波测距模块初始化函数
*函数参数 :无
*函数返回值:无
*函数描述 :
************************************************/
void HC_SR04_Init(void)
{
//Trig---PB8
RCC->AHB1ENR |= (1 << 1);
GPIOB->MODER &= ~(3 << 16);
GPIOB->MODER |= (1 << 16);
GPIOB->ODR &= ~(1 << 8);
//Echo
Tim4_ch1_in_Init();
}
/***********************************************
*函数名 :Tim4_ch1_in_Init
*函数功能 :定时器4通道1输入捕获ECHO超声波电平
*函数参数 :无
*函数返回值:无
*函数描述 :84M 84分频 1/us ARR:65535
* HC-SR04 --- PB6 --- Tim4_ch1
************************************************/
void Tim4_ch1_in_Init(void)
{
/*IO口控制器配置*/
RCC->AHB1ENR |= (1 << 1);
GPIOB->MODER &= ~(3 << 12);
GPIOB->MODER |= (2 << 12);
//上拉/下拉寄存器
GPIOB->PUPDR &= ~(3 << 6);
//复用功能寄存器
GPIOB->AFR[0] &= ~(0xf << 24);
GPIOB->AFR[0] |= (2 << 24);
/*定时器控制器配置*/
RCC->APB1ENR |= (1 << 2);
TIM4->CR1 &= ~(3 << 8);
TIM4->CR1 |= (1 << 7);
TIM4->CR1 &= ~(3 << 5);
TIM4->CR1 &= ~(1 << 4);
TIM4->CR1 &= ~(1 << 3);
TIM4->CR1 &= ~(1 << 2);
TIM4->CR1 &= ~(1 << 1);
TIM4->SMCR &= ~(7 << 0);
TIM4->DIER |= (1 << 1);
TIM4->DIER |= (1 << 1);
TIM4->CCMR1 &= ~(3 << 0);
TIM4->CCMR1 |= (1 << 0);
TIM4->CCMR1 &= ~(3 << 2);
TIM4->CCMR1 &= ~(0xf << 4);
TIM4->CCMR1 |= (0xf << 4);
TIM4->CCER &= ~(1 << 1);
TIM4->CCER &= ~(1 << 3);
TIM4->PSC = 84-1;
TIM4->ARR = 65535;
//EGR
TIM4->EGR |= (1 << 0);
/*NVIC控制器配置*/
//优先级分组 --- 主函数
//优先级编码值
u32 pri = NVIC_EncodePriority (5,0,0);
//确定具体中断源
NVIC_SetPriority(TIM4_IRQn,pri);
//使能NAIC响应通道
NVIC_EnableIRQ(TIM4_IRQn);
//通道使能
TIM4->CCER |= (1 << 0);
//计数器使能
TIM4->CR1 |= (1 << 0);
}
8.按键初始化
/**************************************************************************
* 函数名 :Key_Init
* 函数功能 :对按键初始化配置
* 函数参数 :void
* 函数返回值:void
* 函数说明 :
* KEY1 --- PA0 --- 通用输入,不用上拉/下拉电阻
* KEY2 --- PE2 --- 通用输入,不用上拉/下拉电阻
* KEY3 --- PE3 --- 通用输入,不用上拉/下拉电阻
* KEY4 --- PE4 --- 通用输入,不用上拉/下拉电阻
*************************************************************************/
void Key_Init(void)
{
//打开时钟
RCC->AHB1ENR |= (1 << 0);
RCC->AHB1ENR |= (1 << 4);
//配置GPIO口
//PA0
GPIOA->MODER &= ~(3 << 0);
GPIOA->PUPDR &= ~(3 << 0);
//PE2
GPIOE->MODER &= ~(3 << 4);
GPIOE->PUPDR &= ~(3 << 4);
//PE3
GPIOE->MODER &= ~(3 << 6);
GPIOE->PUPDR &= ~(3 << 6);
//PE4
GPIOE->MODER &= ~(3 << 8);
GPIOE->PUPDR &= ~(3 << 8);
}
9.ADC初始化
/***********************************************
*函数名 :adc1_ch10_12_16_init
*函数功能 :ADC1通道10,12和16初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :PC0-----ADC1_CH10---毒气
* PC2-----ADC1_CH12---光敏
* ADC1_CH16---温度
************************************************/
void adc1_ch10_12_16_interrupt_init(void)
{
/*io控制器配置*/
//时钟使能
RCC->AHB1ENR |= (1 << 2);
GPIOC->MODER &= ~(3 << 4);//PC2模拟模式
GPIOC->MODER |= (3 << 4);
GPIOC->MODER &= ~(3 << 0);//PC0模拟模式
GPIOC->MODER |= (3 << 0);
/*ADC控制器配置*/
//时钟使能
RCC->APB2ENR |= (1 << 8);
//CR1
ADC1->CR1 &= ~(3 << 24);//分辨率:12位 --- 4096份
ADC1->CR1 |= (1 << 8);//扫描
//CR2
//CR2-SWSTART
ADC1->CR2 &= ~(1 << 11);//右对齐
ADC1->CR2 |= (1 << 10);//在每个规则转换结束时将 EOC 位置 1
ADC1->CR2 &= ~(1 << 1);//单次转换
//SMPR1
ADC1->SMPR1 &= ~(7 << 6);
ADC1->SMPR1 |= (7 << 6);//通道12采样时间480个周期
ADC1->SMPR1 &= ~(7 << 0);
ADC1->SMPR1 |= (7 << 0);//通道10采样时间480个周期
ADC1->SMPR1 &= ~(7 << 18);
ADC1->SMPR1 |= (7 << 18);//通道16采样时间480个周期
//SQR
ADC1->SQR1 &= ~(0xf << 20);
ADC1->SQR1 |= (2<<20);//3次转换
ADC1->SQR3 &= ~(0x1f << 0);
ADC1->SQR3 |= (10 << 0); //通道10第一个转换
ADC1->SQR3 &= ~(0x1f << 5);
ADC1->SQR3 |= (12 << 5); //通道12第二个转换
ADC1->SQR3 &= ~(0x1f << 10);
ADC1->SQR3 |= (16 << 10); //通道16第二个转换
//CCR
ADC->CCR &= ~(3 << 16);//2分频
ADC->CCR |= (1 << 23);
//优先级分组 --- 主函数
//优先级编码值
u32 pri = NVIC_EncodePriority (5,1,1);
//确定具体中断源
NVIC_SetPriority(ADC_IRQn,pri);
//使能NAIC响应通道
NVIC_EnableIRQ(ADC_IRQn);
//EOC中断使能
ADC1->CR1 |= (1<<5);
//使能ADC
ADC1->CR2 |= (1 << 0);
}
10.基本定时器7毫秒级定时中断
/***********************************************
*函数名 :Timer7_interrupt_ms
*函数功能 :基本定时器7毫秒级定时中断
*函数参数 :u16 ms
*函数返回值:无
*函数描述 :84M 8400分频 10/ms Max:6553ms
************************************************/
void Timer7_interrupt_ms(u16 ms)
{
/*定时器控制器配置*/
//时钟使能
RCC->APB1ENR |= (1 << 5);
//CR1
TIM7->CR1 |= (1 << 7); //影子寄存器
TIM7->CR1 &= ~(1 << 3); //连续计数
TIM7->CR1 &= ~(1 << 2); //产生中断条件
TIM7->CR1 &= ~(1 << 1); //产生更新事件条件
//PSC
TIM7->PSC = 8400 - 1; //分频
//ARR
TIM7->ARR = ms * 10 -1; //重载值
//人为产生更新事件UG
TIM7->EGR |= (1 << 0); //生成寄存器更新事件
//清除计数完成标志位
TIM7->SR &= ~(1 << 0);
//优先级编码值
u32 pri = NVIC_EncodePriority (5,2,3);
//确定具体中断源
NVIC_SetPriority(TIM7_IRQn,pri);
//使能NAIC响应通道
NVIC_EnableIRQ(TIM7_IRQn);
//中断使能
TIM7->DIER |= (1 << 0);
//使能计数器
TIM7->CR1 |= (1 << 0);
}
11.定义存储传感器数据的结构体
typedef struct adc
{
u16 mq2_val; //毒气数据
u16 light_val; //光照数据
float tem_val; //温度数据
float sr; //超声波测距距离
}ADC1_T;
12.获取毒气数据,光照数据,温度数据
/***********************************************
*函数名 :get_adc1_ch10_12_16_data
*函数功能 :获取ADC1的通道10,12和16的转换数据
*函数参数 :无
*函数返回值:ADC1_T
*函数描述 :
************************************************/
ADC1_T get_adc1_ch10_12_16_data(void)
{
ADC1_T adc_data;
ADC1->CR2 |= (1 << 30);//开始转换
//毒气数据
while(!(ADC1->SR & (1 << 1)));
adc_data.mq2_val = ADC1->DR / 4095.0 * 100;
//光照数据
while(!(ADC1->SR & (1 << 1)));
adc_data.light_val = (4095 - ADC1->DR) / 4095.0 * 100;
//温度数据
while(!(ADC1->SR & (1 << 1)));
adc_data.tem_val = ((ADC1->DR * 0.805-760)/2.5)+25;
return adc_data;
}
13.ADC中断服务函数处理数据
/**************************************************************************
* 函数名 : ADC_IRQHandler
* 函数功能 :ADC中断服务函数配置
* 函数参数 :void
* 函数返回值:void
* 函数说明 :
*************************************************************************/
ADC1_T adc1_irqdata;
void ADC_IRQHandler(void)
{
//清除中断标志位
//紧急事件
static u8 adc_count=0;
if(ADC1->SR & (1<<1))
{
adc_count++;
//毒气
if(adc_count == 1)
{
adc1_irqdata.mq2_val = ADC1->DR / 4095.0 * 100;
//毒气数据控制舵机控制阀门
if(adc1_irqdata.mq2_val>20)
{
SG90_val = 2500;
TIM8->CCR2 = 200+adc1_irqdata.mq2_val * 10;
}
else
{
TIM8->CCR2 = 0;
}
}
//光照
else if(adc_count == 2)
{
adc1_irqdata.light_val = (4095 - ADC1->DR) / 4095.0 * 100;
//光照强度越强,LED越亮
TIM3->CCR1 = adc1_irqdata.light_val*10;
}
//温度
else if(adc_count == 3)
{
adc1_irqdata.tem_val = ((ADC1->DR * 0.805-760)/2.5)+25;
adc_count=0;
}
}
}
14.超声波开始测距函数
/***********************************************
*函数名 :HC_SR04_Star
*函数功能 :HC-SR04超声波开始测距函数
*函数参数 :无
*函数返回值:无
*函数描述 :
************************************************/
void HC_SR04_Star(void)
{
GPIOB->ODR |= (1 << 8);
Timer6_Delay_Us(10);
GPIOB->ODR &= ~(1 << 8);
}
15.通过定时器4完成超声波测距及报警功能
/**************************************************************************
* 函数名 :TIM4_IRQHandler
* 函数功能 :通用定时器TIM4中断服务函数配置
* 函数参数 :void
* 函数返回值:void
* 函数说明 :
*************************************************************************/
void TIM4_IRQHandler(void)
{
static u16 TIM4_count = 0;
static u16 val_1;
static u32 val_2;
static u32 val;
float csb_val;
//如果是更新中断
if(TIM4->SR & (1 << 0))
{
//清除中断标志位
TIM4->SR &= ~(1 << 0);
//紧急事件
TIM4_count++;
}
//如果是捕获中断
if(TIM4->SR & (1 << 1))
{
//清除中断标志位
TIM4->SR &= ~(1 << 1);
//紧急事件
if(!(TIM4->CCER & (1 << 1)))
{
TIM4_count = 0;
val_1 = TIM4->CCR1;
TIM4->CCER |= (1 << 1);
}
else if(TIM4->CCER & (1 << 1))
{
val_2 = TIM4->CCR1;
val = 65535 * TIM4_count - val_1 +val_2;
adc1_irqdata.sr = 34 * (val/1000.0) / 2;
if(adc1_irqdata.mq2_val>20 && adc1_irqdata.sr<=100)
{
BEEP_ON;
}
else
{
BEEP_OFF;
}
TIM4->CCER &= ~(1 << 1);
}
}
}
16.按键扫描函数
/**************************************************************************
* 函数名 :Key_Scanf
* 函数功能 :对按键扫描函数
* 函数参数 :void
* 函数返回值:u8
* 函数说明 :
* 标志位:用变量的数值控制程序的执行次数
*************************************************************************/
u8 Key_Scanf(void)
{
u8 key = 0xff;
static u8 key_flag = 1;
if((KEY1 || !KEY2 || !KEY3 || !KEY4) && key_flag)
{
Delay_Ms(15);
if(KEY1)
{
key = 1;
key_flag = 0;
}
if(!KEY2)
{
key = 2;
key_flag = 0;
}
if(!KEY3)
{
key = 3;
key_flag = 0;
}
if(!KEY4)
{
key = 4;
key_flag = 0;
}
}
if(!KEY1 && KEY2 && KEY3 && KEY4)
{
key_flag = 1;
}
return key;
}
17.在主函数中完成RGB彩灯指定控制
u8 key;
key = Key_Scanf();
if(key==1)
{
RGB_R=1000;
RGB_G=0;
RGB_B=0;
rgb_flag = 0; //彩灯随机变化标志位
}
else if(key==2)
{
RGB_R=0;
RGB_G=1000;
RGB_B=0;
rgb_flag = 0;
}
else if(key==3)
{
RGB_R=0;
RGB_G=0;
RGB_B=1000;
rgb_flag = 0;
}
else if(key==4)
{
rgb_flag=1; //彩灯随机变化标志位
}
18.通过串口中断发送字符串控制阀门开关
/**************************************************************************
* 函数名 :USART1_IRQHandler
* 函数功能 :对串口1中断服务函数配置
* 函数参数 :void
* 函数返回值:void
* 函数说明 :
* 接收中断信号触发
* 空闲中断信号触发
*************************************************************************/
u8 usart1_buff[30];
u8 usart1_flag = 0;
void USART1_IRQHandler(void)
{
static u8 i = 0;
//判断接收中断信号触发
if(USART1->SR & (1 << 5))
{
//清除中断标志位
//紧急事件
usart1_flag = 0;
usart1_buff[i] = USART1->DR;
i++;
}
//判断空闲中断信号触发
if(USART1->SR & (1 << 4))
{
//清除中断标志位
USART1->SR;
USART1->DR;
//紧急事件
usart1_buff[i] = '\0';
i=0;
usart1_flag = 1;
if(strcmp((char*)usart1_buff,"open")==0)
{
SG90_val=500;
}
if(strcmp((char*)usart1_buff,"close")==0)
{
SG90_val=2500;
}
}
}
19.通过串口打印数据
/***********************************************************
*函数名 :printf_val
*函数功能 :数据打印函数
*函数参数 :无
*函数返回值:无
*函数描述 :
*************************************************************/
void printf_val(void)
{
printf("毒气数据:%d%%\r\n",adc1_irqdata.mq2_val);
printf("光照数据:%d%%\r\n",adc1_irqdata.light_val);
printf("温度数据:%0.1f°C\r\n",adc1_irqdata.tem_val);
printf("检测距离:%0.1f\r\n",adc1_irqdata.sr);
}
20.通过定时器7完成定时中断
/**************************************************************************
* 函数名 :TIM7_IRQHandler
* 函数功能 :基本定时器TIM7中断服务函数配置
* 函数参数 :void
* 函数返回值:void
* 函数说明 :
*************************************************************************/
u32 timer7_count[10];
u8 rgb_flag = 0;
void TIM7_IRQHandler(void)
{
//清除标志位
TIM7->SR &= ~(1 << 0);
//紧急事件
timer7_count[1]++;
timer7_count[2]++;
timer7_count[6]++;
timer7_count[7]++;
//RGB彩灯随机变化
if(timer7_count[1] == 1000)
{
timer7_count[1] = 0;
if(rgb_flag == 1)
{
RGB_R += 123;
RGB_G += 456;
RGB_B += 789;
if(RGB_R >= 1000)
{
RGB_R = 0;
}
if(RGB_G >= 1000)
{
RGB_G = 0;
}
if(RGB_B >= 1000)
{
RGB_B = 0;
}
}
}
//超声波开始测距
if(timer7_count[2]==100)
{
timer7_count[2] = 0;
HC_SR04_Star();
}
//ADC开始转换
if(timer7_count[6]==100)
{
timer7_count[6] = 0;
ADC1->CR2 |= (1<<30);//开始转换
}
//将获取到的数据通过串口打印出来
if(timer7_count[7]==105) //等转换完成再打印
{
timer7_count[7] = 0;
printf_val();
}
}
五、整体代码
可以在PC端打开该文章,可以下载该项目压缩包。(在这个项目之前的练习也在这个程序中,所以会显得有些乱)
六、总结
这个模拟毒气检测系统项目是在他人带领下完成的,只是作为一个练手的实践项目,这里面用到了单片机的中断,GPIO,UART,ADC,定时器等基础知识。这并不是一个已经完全完善了的代码,需要补足的地方还有很多也可以通过代码写入其他功能,也有可能会遗漏部分代码的分析,你也可以根据自己需要写入合适的功能。
你们的支持和反馈对我来说非常重要。如果你有任何问题或建议,请通过私聊随时与我联系。别忘了点赞,收藏,关注哦!