太阳能心率智能骑行仪(STM32)

光guan是用本文章讲解一下:太阳能心率智能骑行仪的部分软硬件。

主控使用:STM32C8T6RC

下面是要求实现的功能:

制作一款能实现太阳能充电的智能骑行仪,完成行驶过程中的即时速度/平均速度显示,总里程数显示,时间显示,实时心率显示,夜间行车自动开灯等。

主要研究内容和工作如下:

(1)显示电量不足时,通过光照自动充电;有光线,即充电;

(2)显示屏上能显示即时速度,平均速度,总里程数;

(3)显示当前时间,断电后系统时间保持持续更新;

(4)增加照明功能,当行驶环境变暗后,自动开启照明灯;

(5)自行车把手增加心率检测电极,用于实时监测心率。当设定目标心率达到后进行声音报警;(可采用按键或实现蓝牙连接设置目标心率)

太阳能充电部分:

使用CN3791 MPPT模块,选择对应规格的太阳能板,以及对应的锂电池。

在检测到光照部分时候,有光照则CN3791 MPPT模块则能够通过LED充电指示灯,来显示是否进行了充电。在断电期间,则使用锂电池通过BAT和GND引脚输出对应电压。如下为模块图:

 接口部分解释:SOLAR IN接入太阳能板,作者使用了6V的太阳能充电板,BAT引脚接入PH2.0接口的锂电池,之后通过OK指示灯进行充电的判断。BAT和GND引脚用来给其他外设和硬件电路进行供电。

速度测量:

使用3144霍尔传感器。模块图如下:

接口:VCC GND A0 D0 ,供电3.3-5V,A0引脚采集模拟电压,D0采集数字信号。

使用要在轮子上或者被测对象上,粘上一个磁铁,通过霍尔传感器在轮子转动一圈,则能够在磁铁部分收到一个脉冲信号,进而单片机内部进行计数。通过定时器进行卡好对应阈值,在一个阈值中检测到的脉冲数量。阈值/脉冲数量,则为轮子在对应时间内的转动次数。

转速计算公式: 𝑠𝑝𝑒𝑒𝑑 = (𝑡𝑖𝑚𝑒1 ∗ 2 ∗ 𝛱 ∗ 𝑑)/𝑇1

里程数计算公式: 𝑚𝑖𝑙𝑒𝑎𝑔𝑒 = 𝑡𝑖𝑚𝑒2 ∗ 2 ∗ 𝛱 ∗ d

其中 time1 为一个周期内检测到的车轮转动的圈数;d 为车轮的半径;time2 是一个周 期内检测到的车轮旋转的圈数;T1 为单片机定时器定时的时间;speed 为车轮旋转的瞬时 速度;mileage 为总共行驶的里程数;Tim_prescaler 为时钟预分频数;TIM_period 为时 钟的技术次数;systick 是主控的时钟频率,因为主晶振为 8Mhz 通过了倍频到 72Mhz,因此 systick 数值为 72Mhz。

代码部分:

外部中断初始化:3144传感器,接在了PB12口上。

void speed_Init(void)
{
	EXTI_InitTypeDef EXTI_InitStructure;
 	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12);
  EXTI_InitStructure.EXTI_Line=EXTI_Line12;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
 	EXTI_Init(&EXTI_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;			
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	//抢占优先级2, 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;					//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能外部中断通道
	NVIC_Init(&NVIC_InitStructure); 
}

 定时器初始化:(设置阈值时间)

void TIM1_Int_Init(u16 arr,u16 psc)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	GPIO_InitTypeDef GPIO_InitStructure;
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

	//配置引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//定时器TIM3初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
	TIM_ClearFlag(TIM1,TIM_FLAG_Update);
	TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断

	// 
	NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器

	TIM_Cmd(TIM1, ENABLE);  //使能TIMx					 
}

定时器中断计算:

//定时器3中断服务程序
void TIM1_UP_IRQHandler(void)   //TIM3中断
{
	static u8 time3=0;
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
		{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);  //清除TIMx更新中断标志 
		speed=3.6*time*(6.28*0.0325/1000);
		mileage=3.6*time2*(6.28*0.0325/1000);
		speed_val = mileage/time3;
		time=0;
		}
		time3++;
}

//B15
void EXTI15_10_IRQHandler(void)
{	 
	time++;
  time2++;
	EXTI_ClearITPendingBit(EXTI_Line12);  //清除LINE0上的中断标志位  
}

实时时钟:

使用DS3231模块,通过IIC协议进行数据的读写。同时内部集成了AT24C32作为内部的存储芯片。最大速度是400Khz。

      使用该实时时钟需要考虑的是初始数据的设置,默认的实时时钟为系统的出厂设置, 因此需要在第一次烧录的时候要将设计初始时间,通过软件 IIC 写入 AT24C32 中,更新 系统时间,之后 DS3231 在此基础上通过内部计时来完成时间的记录,并且将其数据实 时更新到 AT24C32 中,通过查询该模块的芯片手册能够获得 DS3231 的数据写地址为 1101 0000,数据读地址为 1101 0001,因此通过 IIC 来对该地址进行读写操作,则能够实 现实时时钟的功能。

初次使用,写入一次数据,之后通过读取即可,匹配IIC协议就行。代码部分不放了,太多了,淘宝买模块,都有的。

也可以使用STM32的时钟RTC进行实时时钟,我这边没有使用。RTC代码正点原子那部分有讲解。B站都有视频。

照明功能

该部分,使用光敏电路部分,通过光敏电阻和电压比较器进行输出。单片机负责检测电压比较器出来的结果,如果是0则开启灯光,如果是1则关闭灯光,基本的模拟电路就可以实现。电路图如下:

如果要是驱动大功率灯,需要外部供电和通过继电器的方式来进行控制。

这边写了一个简单的控制:(控制普通的LED灯作为了模拟)。

代码:

配置对应的光敏信号接口初始化。

	GPIO_InitTypeDef GPIO_InitStructure; 
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 						 
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
		GPIO_Init(GPIOB,&GPIO_InitStructure);
	  GPIO_SetBits(GPIOB,GPIO_Pin_6);

 光敏控制部分:B6是对应的光敏接口。通过这个口进行读取光敏信号。PA6接LED灯.

void light_page()
{
			 OLED_8x16Str(0,0,"light menu");
		   OLED_8x16Str(0,12,"now light:");
	     gm_flag=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6);
			 delay_ms(200);
		   if (gm_flag==0)  
        {
				OLED_8x16Str(96,12,"off");	
        GPIO_ResetBits(GPIOA,GPIO_Pin_6);					
				//USART_SendData(USART3,2);
			
				}
		   else
			{ 
				//OLED_8x16Str(96,12,"on ");
				//USART_SendData(USART3,1);
			 	GPIO_SetBits(GPIOA,GPIO_Pin_6);
			}
				 
}

电压测量:

使用ADC内部的12位高精度的ADC进行电池电压的测量和采集。

考虑到 STM32 上的内部基准电压位 3.3V,最大只可 以测量到 3.3V 的电压,18650 充满电电压达到 4. 2V,因此本设计中使用两个分压电阻 作为分压,在之后程序中根据分压比例乘以对应的分压系数,从而得到对应的电压数值。

为了确保数据转换的稳定性,本设计中使用 ADC 采样时间 239.5 个周期。

其中 SamplTime 是 ADC 采样周期;𝑉௥௘௙是 ADC 采样的内部基准电压为 3.3V,Value 是单片机 ADC 转换后的数值;n 为单片机 ADC 的位数。 之后将其获得的电压值,通过硬件电路上的分压电阻,获得 ADC 计算电压的分压 系数,通过软件算法将获得 ADC 数值计算之后的电压数值乘以分压系数,最后能够获 得当前电池的实际电压,从而实现了 ADC 电压测量范围的扩大。

代码部分:

#include "includes.h"

void  Dianya_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1	, ENABLE );	  //使能ADC1通道时钟
 

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

	//PA1 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	

	ADC_DeInit(ADC1);  //复位ADC1 

	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

  
	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
	
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
 
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能

}				  
//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)   
{
  	//设置指定ADC的规则组通道,一个序列,采样时间
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC1,ADC通道,采样时间为239.5周期	  			    
  
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
	 
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
}

u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);
		delay_ms(5);
	}
	return temp_val/times;
} 	 

心率检测部分:

使用了pulse sensor

原理:心率指的是人在一分钟以内心跳的次数,得到的心率数值最简单的方法就是计数一 个人在一分钟之内检测到了多少次的脉搏数。如果本设计采用这种方法,那么将无法及 时的得到心率数值,而且这种方法采集心率效率特别低。因此本设计采用了另外一种方 式来实时的采集心率数值:测量两次脉搏的时间间隔,之后用一分钟除以两次脉冲信号 之间的时间差,间接得到个体的心率实时数据。 首先计算实时心率数值要引入 IBI 和 BPM 两个概念,其中 IBI 指代两次脉搏之间 特征点的时间间隔数值,BPM 指代的是一个人在一分钟之内的心跳次数。并且两者之间 的换算关系为:BPM = 60/IBI。

脉搏信号波形图

 脉搏信号阈值波形图

通过检测脉搏信号获得到的则是一个波形图,但是计算则需要的是一个 IBI 的数值 点,因此需要通过从一段一段的波形中选取一个点,称之为该脉搏信号上的特征点。这 一个脉搏信号特征点代表了一个有效的脉搏信号,通过检测两个相邻的特征点并且做差, 则能够获得到对应的 IBI 数值,之后通过 IBI 和 BPM 两者之间的换算关系从而能够实 时的获得到当前心率值。在本设计中则是选取当信号上升到振幅的一半时,将该点作为 了这一个脉冲信号的特征点。 

代码部分: 通过ADC来进行脉冲之间的信号采集,之后做动态阈值的检测,之后通过以上算法来对数据进行处理。(这里一般处理出来的信号很飘的,需要滤波,并且波形图容易收到很大的影响)。

为啥不用MAX30102,我试过那个官方算法,数据完全不稳定的。可能是我实力有限,个人问题把。

Pulse sensor结果:

没有检测到信号时:

结果展示:

 

 

 上位机部分,以后再讲咋做。图我先不放了,因为有我个人的信息在上面。emmm以后在更新。以下是各个功能的结果图。如果有问题的,可以问我,我有空会解答的。最近摸的嵌入式内容比较少,大部分时间在做深度学习和机器学习部分的。

代码部分:我不提供完成的全套代码, 因为代码中,涉及到我的许多个人信息,不太方便,提供大家一个思路。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

防空洞的仓鼠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值