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

2024-06-06 08:44

本文主要是介绍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; //单位mVcase 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);//mAprintf("\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-DMAADC_collect();//HAL_ADC_Stop_DMA(&hadc1);//关闭ADC-DMAADC_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颗电池的电量是否异常,异常则会进入STOPuart_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使能IOcharge_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);//1msmotor_FI_OFF;motor_BI_ON;machine_mode=cw_work;}if(machine_mode==ccw){while(!CCW_key);//HAL_TIM_Base_Start_IT(&htim3);//1msmotor_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值*1000uint8_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-DMAHAL_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.最后装机交货

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

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

在这里插入图片描述

这篇关于STM32电子实战项目(一)记录:BLDC kitchen prep centre的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1035623

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2