最近有一个小项目,测量运动自行车速度,上传给上位机软件,处理VR视频播放。正好公司有现成的stm32f1系列单片机开发板,所以我就想到了使用它来实现这个小功能。
1. 硬件配置:
1.1. 运动自行车;
1.2. 磁感应开关与专用磁铁;
1.3. 基于Stm32f103zet6芯片的开发板(七星虫),如下图;
1.4. 连接线若干;
1.5. miniusb线缆,用于给开发板供电及串口通信。
2. 系统描述与框图:
运动自行车车轮上安装5只磁铁,通过磁感应开关检测磁铁产生信号,接入stm32开发板PE0引脚。测量出的速度值通过串口发送给PC上位机软件(mini usb线缆连接)。硬件框图如下:
3. 软件实现
3.1.设定开发板PE0引脚下降沿中断,在引脚中断服务函数里累计中断次数(即磁感应开关感应到磁铁的次数),同时每累计10次LED2交换一次状态。外部初始化代码及中断服务函数如下:
void EXTIX_Init(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 引脚端口初始化 PE0 GPIO_InitStructure.GPIO_Pin = DEF_BIT_00; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOE, &GPIO_InitStructure); // 启 AFIO 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //GPIOE.0 中断线以及中断初始化配置,下降沿触发 GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource0);// 配置中断线为0 EXTI_InitStructure.EXTI_Line=EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); // 初始化中断线参数 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键外部中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2, NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道 NVIC_Init(&NVIC_InitStructure); // 初始化 NVIC } // 外部中断0服务程序 long long lSpeedCnt = 0; void EXTI0_IRQHandler(void) { OSIntEnter(); // 告诉ucosii系统进入中断 if(GPIO_ReadInputDataBit(GPIOE, DEF_BIT_00)==0) // PE0检测到下降沿 { // 累计中断次数,每隔10次改变led1状态 if(!((lSpeedCnt++))) { BSP_LED_Toggle(2); } } EXTI_ClearITPendingBit(EXTI_Line0); // 清除LINE0上的中断标志位 OSIntExit // 告诉ucosii系统退出中断 }3.2.在启用一个定时器中断,周期为1s,在定时器中断服务函数里计算自行车的速度。计算方式如下:v = p / μ * C,其中:v是速度:m/s,p是磁感应开关感应频率, μ为车轮上安装磁铁个数:5,C为自行车车轮周长:1.38m。定时器中断初始化代码及中断服务函数如下:
/******************************************************************************* * Function Name : BSP_TIM2_Init * Description : Compute return latest speed measurement * Input : None * Output : s16 * Return : Return the speed in 0.1 Hz resolution. *******************************************************************************/ static void BSP_TIM2_Init(u16 arr, u16 psc) { TIM_TimeBaseInitTypeDef bsp_tim2_init; //使能TIM2时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_DeInit(TIM2); TIM_TimeBaseStructInit(&bsp_tim2_init); //TIM2初始化 bsp_tim2_init.TIM_Prescaler = psc; //时钟预分频 定时器每隔 (psc+1)/72 us计数一次 bsp_tim2_init.TIM_CounterMode = TIM_CounterMode_Up; //向上计数 bsp_tim2_init.TIM_Period = arr; //计数满(arr+1)次更新重装载寄存器数据 bsp_tim2_init.TIM_ClockDivision = TIM_CKD_DIV1; //时钟不分频 // bsp_tim2_init.TIM_RepetitionCounter = ; //高级定时器用,这里不需设置 TIM_TimeBaseInit(TIM2, &bsp_tim2_init); //初始化定时器 //设置定时器TIM2中断 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //设置定时器更新中断 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除中断标志,防止刚上电时进一次中断 //初始化中断 BSP_NVIC_Init(TIM2_IRQn, 3, 3); //使能定时器TIM2 TIM_Cmd(TIM2, ENABLE); } long long lvalCur; long long lValPrev; float fSpeedVal; void TIM2_IRQHandler(void) { OSIntEnter(); // 告诉ucosii系统进入中断 if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { extern long long lSpeedCnt; lvalCur = lSpeedCnt; fSpeedVal = (float)lvalCur - (float)lValPrev; fSpeedVal /= 5.0; fSpeedVal *= 1.38; lValPrev = lvalCur; TIM_ClearFlag(TIM2, TIM_IT_Update); } OSIntExit(); // 告诉ucosii系统退出中断 }3.3.系统共有两个任务,其中一个任务每50ms发送一次速度值给PC机,另一个控制LED1闪烁,周期100ms,用于指示系统正常运行。
第一个任务中运行代码如下:
while(DEF_TRUE) { extern float fSpeedVal; if((int)(fSpeedVal*100) > 9999) { printf("9999"); } else if((int)(fSpeedVal*100) > 999) { printf("%d",(int)(fSpeedVal*100)); } else if((int)(fSpeedVal*100) > 99) { printf("0%d",(int)(fSpeedVal*100)); } else if((int)(fSpeedVal*100) > 9) { printf("00%d",(int)(fSpeedVal*100)); } else { printf("000%d",(int)(fSpeedVal*100)); } OSTimeDlyHMSM(0, 0, 0, 80); }第二个任务中代码如下:
while (DEF_TRUE) { BSP_LED_Toggle(1); OSTimeDlyHMSM(0, 0, 0, 100); }整体运行稳定,满足项目需求。