STM32电子实战项目(一)记录:BLDC kitchen prep centre

在这里插入图片描述

产品目的:

解决搅拌机食材粘壁问题。

产品功能及需求分析:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

需求分析及实现可能性:

从项目需求看,该项目要实现的功能并不复杂,控制电机的正反转及对应LED显示即可,同时也没必要控制电机转速,不过由于是锂电产品,所以还要考虑充电和低功耗问题以及产品均需要注意的安全性问题。

1.锂电池:
从电机特性表中得到负载电流也不大,但仍需考虑启动时电流会稍大,所以选择18650锂电池时要对其输出电流能力留有一定余量,一般除了看电池容量外,还需要看其标准充放电,最大充放电参数。
在这里插入图片描述
在这里插入图片描述

2.充电设计:
TYPE-C充电口,本来应设计成兼容各类TYPE-C适配器,但由于项目进度较赶,若设计成TYPE-C标准的话除了需要加协议诱导芯片外,还要MCU对不同适配器所能输出的最大功率进行调整,所以经过商议后决定直接买现成的带TYPE-C口的15V/2A输出适配器;
三串锂电池满电电压约为12.6V,由于考虑使用DC-DC降压方案,所以电池管理芯片选择了如韵公司的CN3763芯片;
在这里插入图片描述

3.电机正反转:
可以使用分立元件搭H桥电路来实现,但是程序方面就要进行死区保护,防止上下管直通烧毁,也可以直接使用电机专用IC,其内部基本都会加入过热保护、过流保护、短路保护及死区保护等,该项目我选择了RZ7886对电机进行控制;
在这里插入图片描述

4.电量显示:
通过一个红蓝双色LED灯显示电量情况,锂电池充电情况可以通过电池管理芯片CN3763的CHRG和DONE脚状态来进行是否充满电的判断;而当产品进行工作时,需要通过MCU的ADC功能来检测电池电压判断电量情况,一般单节锂电池的电量判断低于3V时则为低电量,电池电量检测电路如下:
在这里插入图片描述
分别对3串,2串,单节锂电池的电量进行测量,通过相减便可以分别得出3节锂电池的电量,同时使用三极管在产品处于充电或工作状态时才打开电量检测,可以缩小产品待机休眠时的功耗。

5.锂电池产品的低功耗及安规设计
对于锂电池产品,我们公司的功耗要求为待机时小于20uA,实测产品的待机功耗为11uA:

在这里插入图片描述
低功耗的关键点在于硬件电路耗电的地方在满足正常工作条件的情况下电阻值尽量选大,或者使用三极管、MOS管对其进行控制;在MCU方面在休眠前将无关的IO引脚置为高阻输入。
锂电池产品一般要对电池充放电做双重保护,保护方案包括:电池充电管理芯片(BMS)、电池保护IC、过流保护保险丝、NTC监测锂电池温度等。

开发环境:

硬件电路绘制:立创eda(标准版)
软件程序:STM32CUBEMX、Keil-arm

开发过程:

1.使用立创EDA进行原理图绘制:

在这里插入图片描述

部分功能电路介绍:
在这里插入图片描述
这里的作用是当有充电插入时,通过该电路能实现对IO口过压保护的同时,消除部分触发干扰,也能将外部中断的触发边沿延长使单片机能稳定读取到外部中断源。

在这里插入图片描述
该电路位于电池充电管理芯片之前,用于在充电时若检测到电池异常情况(过热、过充)时,而充电管理芯片不起作用时,可以关断充电,实现双重充电保护的作用。

在这里插入图片描述
该电路用于在电池过放之后,充电时由于锂电池已经不够电量支持单片机工作时使用,此时接入充电,VBUS会直接到达LDO处稳压后供MCU工作,而又不至于直接倒灌入电池正极VBAT+。而在正常工作时,VBAT+又因为D1二极管的存在不会流入VBUS,而是正常的流向LDO稳压后供MCU工作。

在这里插入图片描述
此电路为采用NTC+MCU的ADC功能实现温度的监测,监测时将NTC紧贴锂电池才可测量出锂电池的真实温度,我一般设置在超过60℃则禁止其充放电。

在这里插入图片描述
此电路为使用RZ7886实现的电机正反转驱动电路,同时在芯片的接地端接入100mΩ电流采样电阻作为多一重的堵转过流保护。其原理为当电机工作电流为1A时,会有1A电流通过芯片流入采样电阻到地,此时在电阻上就会产生100mV的压降,通过MCU的ADC读取到100mV的电压则可以反推出来此时电机的工作电流为1A。

在这里插入图片描述
此部分作为调试或维修PCB板时断开锂电池连接使用,由于产品化后拆卸锂电池较为麻烦,而又要对电路进行断电,防止焊接时短路锂电池,一般锂电池产品的电路板上都会画为锯齿状,这样焊接时更容易直接用焊锡连接上跳线处,实物如下:
在这里插入图片描述

2.PCB绘制

PCB的尺寸和固定孔一般由公司的结构部门提供,我们只需让其提供对应的DXF格式文件,我们在将其导入到立创EDA的边框层即可,如下:
在这里插入图片描述
在这里插入图片描述
最后完成元器件的布局及走线等,标注好版本日期丝印等,就可完成PCB的绘制了:在这里插入图片描述
同时立创EDA也支持3D模型的查看,让我们看一下到时候的成品PCBA大致样子吧:
在这里插入图片描述
在这里插入图片描述
此时可以将该3D模型输出为obj格式(立创EDA专业版支持输出step格式)供结构部门进行板子尺寸及元器件位置的比对,看是否合适其制作的产品外壳。

3.PCB板打样下单及元器件购买

在结构部门确认好3D模型没问题之后,这边也就可以检查DRC后就开始PCB板的打样了;PCB打样下单成功后紧接着就要对着原理图查找是否有公司元件仓缺失的元件,有的话就要及时下单购买,防止板子回来之后却元器件影响项目进度。

4.使用STM32CUBEMX搭建程序框架

一般PCB板打样不加急的话时间大概为3天左右,在这段等待的时间内,也不能无所事事,可以先进行程序框架的搭建。

对着原理图将各部分外设配置好,配置好时钟树等等:
在这里插入图片描述

在生成工程的时候,记得勾选Set all free pins as analog,可以将没有配置功能的IO口置为高阻态降低功耗。
在这里插入图片描述

5.样板焊接

等PCB样板及元器件都送到后,就可以焊接样板了,这里没什么好写的,拿起烙铁直接动手就行,成品如下图:
在这里插入图片描述
在这里插入图片描述

6.程序编写

首先新建一个.h头文件,将所有输入输出IO口的操作进行重新定义,这样进行程序工作逻辑编写时才不容易乱:

#ifndef __centre_ctrl_H
#define __centre_ctrl_H

#include "stm32f1xx.h"
#include "main.h"
#include "stdbool.h"

enum mode
{
	stop=0,
	cw_work=1,
	ccw_work=2,
	cw=3,
	ccw=4,
	charing=5,
	re_stop=6
};

enum CE
{
	off=0,
	on=1
};

#define LED_CW_ON          HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET)  
#define LED_CW_OFF         HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET)
#define LED_CCW_ON         HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_RESET)  
#define LED_CCW_OFF        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_SET)
#define LED_BAT_RED_ON     HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_RESET)  
#define LED_BAT_RED_OFF    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_SET)
#define LED_BAT_BLUE_ON    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET)  
#define LED_BAT_BLUE_OFF   HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_SET)


#define motor_BI_ON         HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_SET)  
#define motor_BI_OFF        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_RESET)
#define motor_FI_ON         HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET)  
#define motor_FI_OFF        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET)

#define charge_CE_ON        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET)  
#define charge_CE_OFF       HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET)

#define NTC_VC_ON         HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET)  
#define NTC_VC_OFF        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET)
#define VC3_ON        		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET)  
#define VC3_OFF       		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET)
#define VC2_ON        		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET)  
#define VC2_OFF       		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET)
#define VC1_ON        		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_SET)  
#define VC1_OFF       		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_RESET)


#define CW_key            HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)
#define CCW_key           HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)

#define charing_io           HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9)
#define CHRG              HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3)
#define DONE              HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15)

void motolr_LED_display(void);
void charing_read(void);//识别是否充电,用于在工作时接入充电,可用来执行充电时是否允许继续工作
void ADC_CE(bool CE);//使能ADC,会增大耗电量
void work_battery_indicator(void);
void charing_battery_indicator(uint16_t blink_time);
void ADC_analog(void);//进入STOP时将ADC脚置为高阻态,省电; 唤醒时需要初始化ADC

#endif

定义好各种IO口操作后便可以创建C文件编写各种功能程序了,这里我主要将程序分开为3个C文件,一个进行电池电量和温度的ADC读取,一个做低功耗的各种操作,还有一个则进行按键和LED灯的操作:

centre_adc.c内容:

#include <stm32f1xx.h>
#include <centre_ctrl.h>
#include <centre_adc.h>
#include "stdio.h"
#include "centre_low_power.h"

uint16_t ADC_time;//ADC采样时间间隔
uint8_t uart_printf_time;//串口输出时间间隔

uint16_t ADC_SUM[100];//
uint16_t vol_NTC;

float ADC_NTC,ADC_BAT3,ADC_BAT2,ADC_BAT1,ADC_OCP;
uint16_t D_ADC_NTC,D_ADC_BAT3,D_ADC_BAT2,D_ADC_BAT1,D_ADC_current;//ADC值取整,即ADC值*1000
uint16_t I_current;
uint8_t ocp_sum;//过流计数值,多次过流后再进行保护,防止ADC一次错误读取就保护
uint8_t ot_sum=5;//NTC过温计数值
uint8_t bat1_error_sum,bat2_error_sum,bat3_error_sum;//电池异常计数值

extern ADC_HandleTypeDef hadc1;
extern DMA_HandleTypeDef hdma_adc1;
extern TIM_HandleTypeDef htim3;

extern uint8_t machine_mode;


uint8_t battery_level_read_sum;
uint8_t power_work_level_check;
uint8_t power_work_level=6;//工作时的电量档位数,用来避免电量档位过度时在两档之间不稳定显示的问题

uint8_t charing_battery_level_read_sum;
uint8_t power_charing_level_check;
uint8_t power_charing_level=7;//充电时的电量档位数


uint16_t NTC_voltage_celsius(uint8_t temp)//输入摄氏度,自动匹配出对应采集电压  线路为3.3V--100K--100K B3950 NTC--GND
{
	switch(temp)
	{
		case 0:   vol_NTC=2530;break; //单位mV
		case 10:	vol_NTC=2200;break;
		case 20:  vol_NTC=1833;break;
		case 30:	vol_NTC=1472;break;
		case 40:	vol_NTC=1143;break;
		case 50:	vol_NTC=874;break;
		case 60:	vol_NTC=660;break;
		case 70:	vol_NTC=479;break;
		case 80:	vol_NTC=367;break;
		case 90:	vol_NTC=272;break;
		case 100: vol_NTC=207;break;
	}
	
	return vol_NTC;
}

void BAT_OT_protect(uint8_t temp)//形参为设置温度
{
	if(D_ADC_NTC<(NTC_voltage_celsius(temp)))//温度比temp高
	{
		ot_sum++;
	}
	if(D_ADC_NTC>=(NTC_voltage_celsius(temp)))//温度比temp低,未超温
	{
		ot_sum--;
	}
	
	if(ot_sum>=10)//连续检测到超温,禁止充电
	{
		ot_sum=5;
		charge_CE_OFF;//关闭
	}
	
	if(ot_sum==0)//连续检测到未超温,恢复充电
	{
		ot_sum=10;
		charge_CE_ON;//开启
	}
}

void OC_protect(uint16_t sum)//电机工作过流保护,形参为过流保护值,单位:mA
{
	if(machine_mode==cw_work||machine_mode==ccw_work)
	{
		if(I_current>=sum)
		{
			ocp_sum++;
		}
		else if(I_current<sum)
		{
			ocp_sum=0;
		}
		
		if(ocp_sum>=5)//检测到超过5次过流,就停止电机工作,根据要求直接STOP系统
		{
			ocp_sum=0;
			
			motor_BI_OFF;
			motor_FI_OFF;
			LED_CCW_OFF;
			LED_CW_OFF;
			LED_BAT_BLUE_OFF;
			
			LED_BAT_RED_ON;
			HAL_Delay(500);
			LED_BAT_RED_OFF;
			HAL_Delay(500);
			LED_BAT_RED_ON;
			HAL_Delay(500);
			LED_BAT_RED_OFF;
			HAL_Delay(500);
			LED_BAT_RED_ON;
			HAL_Delay(500);
			LED_BAT_RED_OFF;
			
			machine_mode=re_stop;
			//enter_stop_mode();
		}
	}
}


void three_bat_check()//分别检测3颗电池的电量是否异常
{
	if(D_ADC_BAT1<2500||D_ADC_BAT1>4400)//第一节电池的电量如果低于2500mV或者超过4400mV,证明异常
	{
		bat1_error_sum++;
	}
	else bat1_error_sum=0;
	
	if(D_ADC_BAT2-D_ADC_BAT1<2500||D_ADC_BAT2-D_ADC_BAT1>4400)//第二节电池的电量如果低于2500mV或者超过4400mV,或者电池间电量偏差较大,证明异常
	{
		bat2_error_sum++;
	}
	else bat2_error_sum=0;
	
	if(D_ADC_BAT3-D_ADC_BAT2<2500||D_ADC_BAT3-D_ADC_BAT2>4400)//第三节电池的电量如果低于2500mV或者超过4400mV,或者电池间电量偏差较大,证明异常
	{
		bat3_error_sum++;
	}
	else bat3_error_sum=0;
	
	
	if(bat1_error_sum>5||bat2_error_sum>5||bat3_error_sum>5)//只要有一颗电池出现异常,则进入STOP
	{
		bat1_error_sum=0;
		bat2_error_sum=0;
		bat3_error_sum=0;
		machine_mode=re_stop;
	}
}


void ADC_printf()
{
		printf("ADC_NTC=%1.3f\r\n",ADC_NTC);
	  printf("D_ADC_BAT3=%d\r\n",D_ADC_BAT3);
	  printf("D_ADC_BAT2=%d\r\n",D_ADC_BAT2);
		printf("D_ADC_BAT1=%d\r\n",D_ADC_BAT1);
	
		printf("I_current=%d\r\n",I_current);//mA
	
	  printf("\r\n");
}

void ADC_rounding()//ADC值*1000
{
	D_ADC_NTC=ADC_NTC*1000;
	D_ADC_BAT3=ADC_BAT3*1000;
	D_ADC_BAT2=ADC_BAT2*1000;
	D_ADC_BAT1=ADC_BAT1*1000;
	D_ADC_current=ADC_OCP*1000;
	I_current=D_ADC_current*10;//采样电阻为0.1Ω,单位为mA
}

void ADC_collect()//读出ADC的电压采样值
{
	uint8_t j;
	for(j=0;j<100;)
	{
		ADC_NTC+=ADC_SUM[j++];
		ADC_BAT3+=ADC_SUM[j++];//参考电压3.3V,12位精度	
		ADC_BAT2+=ADC_SUM[j++];//参考电压3.3V,12位精度	
		ADC_BAT1+=ADC_SUM[j++];//参考电压3.3V,12位精度	
		ADC_OCP+=ADC_SUM[j++];//参考电压3.3V,12位精度
	}
	ADC_NTC/=20;	
	ADC_BAT3/=20;	
	ADC_BAT2/=20;	
	ADC_BAT1/=20;	
  ADC_OCP/=20;
	
	ADC_NTC=ADC_NTC*3.3f/4096;
	ADC_BAT3=(ADC_BAT3*3.3f/4096)*7.5;//BAT的值由680K和100K分压得到,则应乘以7.8才为实际电压
  ADC_BAT2=(ADC_BAT2*3.3f/4096)*7.5;//BAT的值由680K和100K分压得到,则应乘以7.8才为实际电压
	ADC_BAT1=(ADC_BAT1*3.3f/4096)*7.5;//BAT的值由680K和100K分压得到,则应乘以7.8才为实际电压
	ADC_OCP=ADC_OCP*3.3f/4096;
	
	ADC_rounding();//ADC值*1000
	
	//ADC_printf();
	
}

void ADC_work(uint16_t temp,uint8_t printf_time)//采样时间间隔,串口输出时间间隔
{
	//ADC_time++;
	if(ADC_time>=temp)//temp毫秒读取一次ADC数据
		{
			HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC_SUM,100);//开启ADC-DMA
			ADC_collect();//
			HAL_ADC_Stop_DMA(&hadc1);//关闭ADC-DMA
			ADC_time=0;
			
			if(machine_mode==cw_work||machine_mode==ccw_work)
			{
				
				OC_protect(2500);//电机工作过流保护,形参为过流保护值,单位:mA
			
			}
			if(machine_mode==charing)
			{
				BAT_OT_protect(50);//形参为设置温度
			}
			
			three_bat_check();//分别检测3颗电池的电量是否异常,异常则会进入STOP
			
			uart_printf_time++;
			if(uart_printf_time==printf_time)
			{
				ADC_printf();
				uart_printf_time=0;
			}
		}			
}



centre_low_power.c内容:

#include <stm32f1xx.h>
#include <centre_low_power.h>
#include <centre_ctrl.h>
#include <stdbool.h>
#include "main.h"

extern TIM_HandleTypeDef htim3;
extern ADC_HandleTypeDef hadc1;

uint8_t machine_mode=0;


uint16_t wakeup_read_time;//设定系统唤醒后一段时间内进行模式判断
uint16_t wakeup_CW_key_sum;
uint16_t wakeup_CCW_key_sum;
uint16_t wakeup_charge_sum;


uint16_t key_stop_time;//检测从工作模式进入待机模式的消抖时间


void enter_stop_mode(void)//进入stop模式
{
	HAL_TIM_Base_Stop_IT(&htim3);//1ms
	//关闭所有耗电控制
	motor_BI_OFF;
	motor_FI_OFF;
	LED_BAT_BLUE_OFF;
	LED_BAT_RED_OFF;
	LED_CCW_OFF;
	LED_CW_OFF;
	ADC_CE(off);//关闭ADC使能IO
	charge_CE_OFF;
	//关闭所有耗电控制
	
	//置标志位
	machine_mode=stop;//将mode置为stop后下次唤醒后才可进行唤醒模式判别
	wakeup_read_time=0;
	wakeup_CCW_key_sum=0;
	wakeup_CW_key_sum=0;
	wakeup_charge_sum=0;
	
	HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
	HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
	HAL_ADC_Stop_DMA(&hadc1);//关闭ADC-DMA
	//ADC_analog();//进入STOP时将ADC脚置为高阻态,省电; 唤醒时需要初始化ADC
	__HAL_RCC_PWR_CLK_ENABLE();
	__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
	HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);
	HAL_Delay(50);
	//MX_ADC1_Init();
	//HAL_TIM_Base_Start_IT(&htim3);//1ms
}


void wakeup_mode_check(uint16_t time,uint16_t check_sum)//形参分别为总检测时间,判定为成功的检测次数
{ //只要系统被唤醒,就检测是按键唤醒,还是充电唤醒
	if(machine_mode==stop)
	{
		if(wakeup_read_time<time)
		{
			wakeup_read_time++;
			if(!CW_key&&CCW_key&&!charing_io)//如果为正转按键按下唤醒
			{
				wakeup_CW_key_sum++;
				wakeup_CCW_key_sum=0;
				wakeup_charge_sum=0;
			}
			if(CW_key&&!CCW_key&&!charing_io)//如果为反转按键按下唤醒
			{
				wakeup_CCW_key_sum++;
				wakeup_CW_key_sum=0;
				wakeup_charge_sum=0;
			}
			
			if(charing_io)//如果为充电唤醒
			{
				wakeup_charge_sum++;
				wakeup_CW_key_sum=0;
				wakeup_CCW_key_sum=0;
			}
			
			if(CW_key&&!charing_io&&CCW_key)//为按键或充电唤醒消抖
			{
				wakeup_CW_key_sum=0;
				wakeup_CCW_key_sum=0;
				wakeup_charge_sum=0;
			}
		}
		
		if(wakeup_read_time>=time)//检测时间到达后
		{
			if(wakeup_charge_sum<check_sum&&wakeup_CW_key_sum<check_sum&&wakeup_CCW_key_sum<check_sum)//如果判断为干扰误触发,则重新进入停机模式
			{
				machine_mode=re_stop;
			}
			
			if(wakeup_CW_key_sum>=check_sum&&wakeup_CCW_key_sum<check_sum&&wakeup_charge_sum<check_sum)//为正转按键唤醒
			{
				wakeup_CW_key_sum=0;
				machine_mode=cw;
				//HAL_TIM_Base_Stop_IT(&htim3);//1ms  在这里停止定时器是为了防止识别到按键按下后,在等待松手时定时器中断又执行wakeup_mode_check()导致的异常
			}
			if(wakeup_CCW_key_sum>=check_sum&&wakeup_CW_key_sum<check_sum&&wakeup_charge_sum<check_sum)//为反转按键唤醒
			{
				wakeup_CCW_key_sum=0;
				machine_mode=ccw;
				//HAL_TIM_Base_Stop_IT(&htim3);//1ms
			}
			
			if(wakeup_charge_sum>=check_sum&&wakeup_CCW_key_sum<check_sum&&wakeup_CW_key_sum<check_sum)//为充电唤醒
			{
				wakeup_charge_sum=0;
				machine_mode=charing;
				charge_CE_ON;
			}
			
			wakeup_read_time=0;
		}
	}
	
	
	if(machine_mode==charing)//如果进入充电模式,判断什么时候退出了充电模式,则进入STOP
	{
		if(!charing_io)
		{
			wakeup_charge_sum++;
		}
		if(charing_io)//消抖
		{
			wakeup_charge_sum=0;
		}
		
		if(wakeup_charge_sum>=30)//判断为断开充电后进入待机模式
		{
			wakeup_charge_sum=0;
			machine_mode=re_stop;
		}
	}
	
}

void charing_stop()//拔掉充电后进入STOP
{
	if(machine_mode==charing)//如果进入充电模式,判断什么时候退出了充电模式,则进入STOP
	{
		if(!charing_io)
		{
			wakeup_charge_sum++;
		}
		if(charing_io)//消抖
		{
			wakeup_charge_sum=0;
		}
		
		if(wakeup_charge_sum>=30)//判断为断开充电后进入待机模式
		{
			wakeup_charge_sum=0;
			machine_mode=re_stop;
		}
	}
}

void  key_motor_work()//按下CW_key或CWW_key后,machine唤醒等待松手后电机开始运转
{
	if(machine_mode==cw)
	{
		while(!CW_key);
		//HAL_TIM_Base_Start_IT(&htim3);//1ms
		motor_FI_OFF;
		motor_BI_ON;
		machine_mode=cw_work;
	}
	
	if(machine_mode==ccw)
	{
		while(!CCW_key);
		//HAL_TIM_Base_Start_IT(&htim3);//1ms
		motor_BI_OFF;
		motor_FI_ON;
		machine_mode=ccw_work;
	}
}

void key_stop()//在machine_mode处于cw或ccw状态下,按CW_key或CCW_key使其进入stop,这里要看需求是否CW状态下只能通过CW_key关机还是均可以关机
{
	if(machine_mode==cw_work||machine_mode==ccw_work)
	{
		if(!CW_key||!CCW_key)
		{
			key_stop_time++;
		}
		if(CW_key&&CCW_key)
		{
			key_stop_time=0;
		}
	}
	
	if(key_stop_time>=20)
	{
		while(!CW_key||!CCW_key);
		key_stop_time=0;
		machine_mode=re_stop;	
	}
}


centre_ctrl.c内容:

#include <stm32f1xx.h>
#include <centre_ctrl.h>
#include <stdbool.h>
#include "centre_low_power.h"
#include "centre_adc.h"

extern uint8_t machine_mode;
extern uint16_t D_ADC_NTC,D_ADC_BAT3,D_ADC_BAT2,D_ADC_BAT1,D_ADC_current;//ADC值取整,即ADC值*1000


uint8_t charing_read_sum;
uint16_t led_blink_time;

uint16_t chrg_sum;
uint16_t done_sum;

void motolr_LED_display()
{
	if(machine_mode==cw_work)
	{
		LED_CCW_OFF;
		LED_CW_ON;
	}
	
	if(machine_mode==ccw_work)
	{
		LED_CW_OFF;
		LED_CCW_ON;
	}
}

void charing_read()//识别是否充电,用于在工作时接入充电,可用来执行充电时是否允许继续工作
{
	if(charing_io)
	{
		charing_read_sum++;
	}
	if(!charing_io)
	{
		charing_read_sum=0;
	}
	
	if(charing_read_sum>=10)
	{
		machine_mode=charing;
		charing_read_sum=0;
		motor_BI_OFF;
		motor_FI_OFF;
		LED_CCW_OFF;
		LED_CW_OFF;
	}
}


void ADC_CE(bool CE)//使能ADC读取,会增大耗电量
{
	if(CE==on)
	{	
		NTC_VC_ON;
		VC3_ON;
		VC2_ON;
		VC1_ON;
	}
	
	if(CE==off)
	{
		NTC_VC_OFF;
		VC3_OFF;
		VC2_OFF;
		VC1_OFF;
	}
}

void work_battery_indicator()
{
	if(D_ADC_BAT3<=10000)//小于10V
	{
		LED_BAT_BLUE_OFF;
		LED_BAT_RED_ON;
	}
	
	if(D_ADC_BAT3>10000)//大于10V
	{
		LED_BAT_RED_OFF;
		LED_BAT_BLUE_ON;
	}
}

void charing_battery_indicator(uint16_t blink_time)
{
	
	if(!CHRG&&DONE)//处于充电状态
	{
		//led_blink_time++;
		//LED_BAT_BLUE_OFF;
		chrg_sum++;
		done_sum=0;
	}
	
	if(!DONE&&CHRG)//充电完成
	{
		chrg_sum=0;
		done_sum++;
		led_blink_time=0;
	}
	
	if(chrg_sum>=50)
	{
		chrg_sum=50;//防止其持续累加增大
		LED_BAT_BLUE_OFF;
		if(led_blink_time<=blink_time)
		{
			LED_BAT_RED_ON;
		}
		if(led_blink_time>blink_time&&led_blink_time<=blink_time*2)
		{
			LED_BAT_RED_OFF;
		}
		if(led_blink_time>blink_time*2)
		{
			led_blink_time=0;
		}
	}
		
	
	if(done_sum>=50)//充电完成
	{
		done_sum=50;
		LED_BAT_RED_OFF;
		LED_BAT_BLUE_ON;
		led_blink_time=0;
	}
}



通过此种封装函数的方式进行程序编写的好处是不仅函数可以通过输入不同形参输入在不同模式下进行调用,也可以使主程序看起来简洁,主程序如下:

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  //MX_IWDG_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
	HAL_Delay(500);
	HAL_ADCEx_Calibration_Start(&hadc1);//DMA自校准
	///HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC_SUM,100);//开启ADC-DMA
	HAL_TIM_Base_Start_IT(&htim3);//1ms
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		HAL_Delay(3);
		//HAL_IWDG_Refresh(&hiwdg);
		//printf("machine_mode=%d\r\n",machine_mode);
		
		if(machine_mode==re_stop)
		{
			enter_stop_mode();
		}
		
		if(machine_mode==cw||machine_mode==ccw)//识别到为正转或者反转后,开启工作模式
		{
			key_motor_work();
		}
		
		if(machine_mode==cw_work||machine_mode==ccw_work)//处于工作模式中
		{
			ADC_CE(on);//使能ADC,会增大耗电量
			ADC_work(10,100);//采样时间间隔,串口输出时间间隔
			
			motolr_LED_display();//正反转指示灯
			work_battery_indicator();//电池电量指示灯
			key_stop();//按下按键,进入stop		
			charing_read();//识别是否充电

		}
		
		if(machine_mode==charing)//由于充电指示灯有闪烁时间要求,所以将其显示放定时器中断中执行
		{
			ADC_CE(on);//使能ADC,会增大耗电量
			ADC_work(30,33);//采样时间间隔,串口输出时间间隔
			
			charing_battery_indicator(1000);
			//charing_stop();
		}
		
  }
  /* USER CODE END 3 */
}

可以看到主程序内容浅显易懂,通过判断产品处于什么模式下,比如充电模式,工作模式等,则调用相应需执行或判断的函数进行操作即可。

7.硬件+软件联调

对于锂电产品,接入电池前要对电路的充电和保护功能进行测试,正常后方可接入锂电池:
在这里插入图片描述
可以采用DC稳压电源+电子负载的方式进行测试,输入DC15V电压模拟TYPE-C充电,电子负载调恒压模式,电压范围在三串锂电池并联的正常电压范围内,可以看到充电电流为1A,当将恒压电压调整到超过12.6V后电流变为0,证明充电管理芯片正常工作了,此时便可将电池接入电路了。电池接入前要保证3节电池的电压接近,若有偏差可以对电池分别进行充放电使3节电池电压相等再进行串联。

同时程序不可能一次性就编写无误,一般都是各个功能模块编完之后进行仿真、烧入MCU配合硬件联调、调用串口读取数据等;
此时掏出自制的隔离型ST-LINK V2.1对样板的数据进行串口读取(在未引入外部电源时,无须使用带隔离的模块,但在引入外部电源后,如接入电池,或者使用市电进行供电的产品,最好使用带隔离的模块,否则容易因为地电压不一致导致大电流流动烧毁电脑或样板)


在这里插入图片描述

串口线路连接好后,按下按键唤醒样板工作,看到比克大魔王手发红光,证明正常接到串口数据了,此时我们通过串口助手查看一下数据:

在这里插入图片描述
通过串口可以读出此时三串锂电池总电压为10.9V左右,而电机工作电流在370mA,都处于正常范围,证明数据是正确的。

8.最后装机交货

装机交货没什么好写的,讲讲为什么要写这么繁琐的文章吧。对实际项目开发过程的完整记录旨在让每一位刚入行电子或者对电子感兴趣的人可以了解到一个完整的项目开发过程。相信每一位入门电子行业的工程师一开始肯定都是很渴望自己能独立开发项目的,但并不是每个人都能一入行就得到公司信任获得开发项目的机会,像我刚毕业后在第一家公司虽然挂名电子工程师,但实际却一直做着打杂测试的工作,当时十分想了解一个完整的项目开发需要经历哪些过程但却一直没有机会 。一次完整的项目经验不仅可以令自己获得成就感,也可以考验你硬件软件各方面的能力,在项目中也会遇到各种问题,锻炼你分析问题原因的能力。

当然此篇文章还有很多写得不够严谨仔细的地方,后期如果有较为有趣的电子开发项目,我也会尽量更为仔细把开发过程记录下来,分享给大家。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值