这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【Let'sdo第1期-DIY功率检测与控制系统】成果贴-记录第一次完成的作品

共3条 1/1 1 跳转至

【Let'sdo第1期-DIY功率检测与控制系统】成果贴-记录第一次完成的作品

菜鸟
2025-06-01 23:35:41     打赏

首先来张硬件全家福和软件项目构成图。主要用了几个模块:

1.F411主控板 2.正点原子便携电源 3.INA219模块 4. OLED模块 5.不知道从哪遗留的电机模块(直流电机通过皮带带动旋转平台,上面放了一卷电工胶带当负载) 5.USB转mini usb数据线(和stlink相连) 6.串口通信模块

总体图.jpg

image.png

系统框图

电路中主要用了2个模拟IIC,分别与OLEDINA219通信,一个串口和2个GPIO,F411IO资源分配如下。

F411 IO分配

信号

IO方向

IO功能

PA5

GPIO_LED

O

LED控制

PC13

GPIO_KEY

I

按键控制

PC10

SCL1

0

OLED模拟IIC 时钟

PC12

SDA1

IO

OLED模拟IIC 数据

PB8

SCL2

O

INA模拟IIC时钟

PB9

SDA2

IO

INA模拟IIC 数据

PA9

UART_TX

AF

串口发送

PA10

UART_RX

AF

串口接收

image.png

实现的任务:

1. led定时翻转

2. OLED切换显示

3. INA219测量电压电流功率,并经过滑动平均滤波,电阻值10mR,电流检测范围0-4A,总线电压32V.

4. 串口不定长命令超时检测机制和解析,并存储电流阈值和校验

5. 不同电流模式的切换。

下面是成果。

第一张图是5V电压时电机负载的测量值。


第二张图是12V电压时电机负载的测量值。


第三张图是电机带负载转动的画面。负载是一个电工胶带。电机通过皮带带动旋转平台转动。


第四张图是过流切换。


第五张图是发送命令和解析命令。


第六张图是将收到的阈值数据保存在flash中,flash地址为0x8060000,在扇区7。收到的阈值电流为1000mA,对应16进制数据为0x3e8


第七张图是VOFA上位机读取的滑动平均滤波后的电压,电流和功率值。看起来还不错。

下面是主要的功能代码。

在数据结构上,定义了PowerInfoDef(功率信息)、CommandInfoDef(命令信息)、HiccupInfoDef(打嗝模式信息)、SelfLockingInfoDef(自锁模式信息)、OverCurrentInfoDef(过流信息)五个结构体。PowerInfoDef(功率信息)结构体用来记录电压、电流、功率、过流阈值等属性,CommandInfoDef(命令信息)结构体用来记录串口发送的各种命令号和过流阈值,HiccupInfoDef(打嗝模式信息)结构体用来记录打嗝模式状态和计数值,SelfLockingInfoDef(自锁模式信息)结构体用来记录自锁模式状态和计数值,OverCurrentInfoDef(过流信息)结构体用来记录过流标志、过流恢复标志和过流次数。

头文件如下。

#ifndef __APP_H
#define __APP_H

#include "sys.h"

typedef struct
{
	int Current;
	uint32_t Voltage;
	uint32_t CurThreshold;
	uint32_t Power;
}	PowerInfoDef;


typedef struct 
{
	u8 CmdNum;
	u32 CurrentThreshold;
}CommandInfoDef;

typedef enum {CurrentThresholdCMD = 1,\
							RecoveryControlCMD = 2,\
							CanRunCMD =3,\
							StopRunCMD = 4,\
							CurrentMode1CMD = 5,\
							CurrentMode2CMD =6	
}Command;

//自锁模式:当过流保护产生时,必须使用按键或者串口指令恢复控制;
//打嗝模式:当过流保护产生时,停止负载,过流消失1s(可自行设定)后自行恢复控制
typedef enum {
							SelfLockingMode = 1,\
							HiccupMode =2
}ControlMode;

typedef struct
{
	u8 HiccupStatus;
	u32 HiccupCount;
}HiccupInfoDef;//打嗝模式

typedef struct
{
	u8 SelfLockingStatus;
	u32 SelfLockingCount;
}SelfLockingInfoDef;//自锁模式
typedef struct
{
	u8 OverCurrentFlag;
	u8 OverCurrentRecoveryFlag;
	u32 OverCurrentCount;
}OverCurrentInfoDef;

#define HICCUP_START 0
#define HICCUP_FINISH 1
#define HICCUP_NUM 5
#define OVER_CURRENT_NUM  5//过流次数,预防启动瞬间可能过流


ErrorStatus ParseCMD(u8* buf,CommandInfoDef* command);
void ControlProcess(u8* ControlMode);
#endif

C文件如下

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "oled.h"
#include "ina219.h"
#include "stmflash.h"
#include "app.h"
#include "string.h"
#include "filter.h"
#include "key.h"

PowerInfoDef PowerInfo = {0,0,0,0};
HiccupInfoDef HiccupInfo= {0,0};
SelfLockingInfoDef SelfLockingInfo= {0,0};
OverCurrentInfoDef OverCurrentInfo= {0,0,0};
CommandInfoDef CommandInfo = {0,0};
u8 CurrentControlMode = SelfLockingMode;//默认自锁模式
SlidAvgFilterDef SlidAvgFilterInfo_Votage;
SlidAvgFilterDef SlidAvgFilterInfo_Current;
SlidAvgFilterDef SlidAvgFilterInfo_Power;
bool OLED_page = 0;
//将电流阈值保存在扇区7 0x8060000地址
//扇区 0 0x0800 0000 - 0x0800 3FFF 16 KB
//扇区 1 0x0800 4000 - 0x0800 7FFF 16 KB
//扇区 2 0x0800 8000 - 0x0800 BFFF 16 KB
//扇区 3 0x0800 C000 - 0x0800 FFFF 16 KB
//扇区 4 0x0801 0000 - 0x0801 FFFF 64 KB
//扇区 5 0x0802 0000 - 0x0803 FFFF 128 KB
//扇区 6 0x0804 0000 - 0x0805 FFFF 128 KB
//扇区 7 0x0806 0000 - 0x0807 FFFF 128 KB

#define CURRENT_THRESHOLD_POS 0x8060000 //扇区7的起始地址
u32* pCurrentThreshold = (u32*)CURRENT_THRESHOLD_POS;

int main(void)
{ 
	static u8 INA_Init_Status = 0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(100);  //初始化延时函数
	LED_Init();				//初始化LED端口
	uart_init(115200);
	KEY_Init();
	
 	TIM3_Int_Init(10-1,10000-1);	//定时器时钟100M,分频系数10000,所以100M/10000=10Khz的计数频率,计数10次为1ms   
	INA_Init_Status = INA_Init(); 
	if(INA_Init_Status == 1)
		printf("INA219_init complete\r\n");
	else
		printf("INA219_init fail\r\n");
	OLED_Init();

	printf("uart_init complete\r\n");
	
	SlidAvgFilterInit(&SlidAvgFilterInfo_Votage);
	SlidAvgFilterInit(&SlidAvgFilterInfo_Current);	
	SlidAvgFilterInit(&SlidAvgFilterInfo_Power);	
	
	PowerInfo.CurThreshold = *pCurrentThreshold;//将flash存的数据取出来复制
	OLED_Show_Power_Info();
	while(1)
	{
		if(GetTickCount() >= 1000)
		{
			LED0=!LED0;//DS0翻转
			TickReset();
		}
		
		//直接采集电流电压功率信息,不滤波	
    //PowerInfo.Voltage = INA_GET_BusVoltage_MV();
		//PowerInfo.Current = INA_GET_Current_MA();
		//PowerInfo.Power = INA_GET_Power_MW();
		//采集电流电压功率信息并滑动平均滤波	
    PowerInfo.Voltage = (u32)SlidAvgFilter(&SlidAvgFilterInfo_Votage,(float)INA_GET_BusVoltage_MV());
		PowerInfo.Current = (u32)SlidAvgFilter(&SlidAvgFilterInfo_Current,(float)INA_GET_Current_MA());
		PowerInfo.Power = (u32)SlidAvgFilter(&SlidAvgFilterInfo_Power,(float)INA_GET_Power_MW());
		
		if(KEY_Scan(0) != 0){
				OLED_page = !OLED_page;//如果按下,切换页面
		}				
		if(OLED_page == 0)
			OLED_Show_Power_Info();
		else
			OLED_Show_Threshold_Info();	
		
		printf("%4d,%4d,%5d\r\n",PowerInfo.Voltage,PowerInfo.Current,PowerInfo.Power);
				
		//查询串口数据是否接收完成,如果超时未收到新数据,则认为接收完成。
		if(REC_PACK_FINSH == UsartRecTime())
		{
			if(ParseCMD(ext_usart_rx_buf,&CommandInfo) != ERROR)
			{
				switch(CommandInfo.CmdNum)
				{
					case CurrentThresholdCMD :
					{
						printf("CurrentThresholdCMD\r\n");
						//存阈值数据
						STMFLASH_Write(CURRENT_THRESHOLD_POS,&CommandInfo.CurrentThreshold,1);
					  //校验阈值数据,校验通过,将阈值数据赋值给power_info
						if(CommandInfo.CurrentThreshold == *pCurrentThreshold)
						{
							PowerInfo.CurThreshold = *pCurrentThreshold;
						}
						printf("CurrentThreshold verify success\r\n");
						break;
					}
					case RecoveryControlCMD://过流恢复命令
					{
						printf("RecoveryControlCMD\r\n");
						OverCurrentInfo.OverCurrentRecoveryFlag = 1;
						break;
					}
					case CanRunCMD://电机启动
					{
						printf("CanRunCMD\r\n");
						break;
					}
					case StopRunCMD://电机停止
					{
						printf("StopRunCMD\r\n");
						break;
					}
					case CurrentMode1CMD://切换自锁模式
					{
						printf("CurrentMode1CMD\r\n");
						CurrentControlMode = SelfLockingMode;
						break;	
					}
					case CurrentMode2CMD://切换打嗝模式
					{
						printf("CurrentMode2CMD\r\n");
						CurrentControlMode = HiccupMode;
						break;
					}
					default:
					{
						printf("NoneCMD\r\n");
						;
						break;
					}
				}
						
			}
		}
		
		ControlProcess(&CurrentControlMode);
		
			
	};
}

/**************************************************
*简介:解析命令
*参数:
*****参数1:数据缓冲区
*****参数2:命令结构体指针,在函数内部被更改
*返回值:无
**************************************************/
ErrorStatus ParseCMD(u8 *buf,CommandInfoDef *command)
{
	char* endptr = NULL;
	char* pCommand = NULL;
	u32 threshold=0;
	
	command->CmdNum = 0;
	command->CurrentThreshold = 0;
	
	//查找字符串
	pCommand = strstr(buf,"CurrentThreshold:");//找到电流阈值并返回所在位置指针
	if(pCommand != NULL)
	{
			pCommand += strlen("CurrentThreshold:");//指向mA
		
			//提取电流值大小
			command->CurrentThreshold = strtol(pCommand,&endptr,10); 
			if(*endptr != 'm' && *(endptr++) != 'A')
				return ERROR;
			command->CmdNum = CurrentThresholdCMD;
			return SUCCESS;
	}
	
	pCommand = strstr(buf,"RecoveryControl");//找到恢复控制命令并返回所在位置指针
	if(pCommand != NULL)
	{
		command->CmdNum = RecoveryControlCMD;
		return SUCCESS;
	}
	
	pCommand = strstr(buf,"CanRun");//找到运行并返回所在位置指针
	if(pCommand != NULL)
	{
		command->CmdNum = CanRunCMD;
		return SUCCESS;
	}
	
	pCommand = strstr(buf,"StopRun");//找到停止运行命令并返回所在位置指针
	if(pCommand != NULL)
	{
		command->CmdNum = StopRunCMD;
		return SUCCESS;
	}
	
	pCommand = strstr(buf,"CurrentMode1");//找到电流模式1并返回所在位置指针
	if(pCommand != NULL)
	{
		command->CmdNum = CurrentMode1CMD;
		return SUCCESS;
	}
	
	pCommand = strstr(buf,"CurrentMode2");//找到电流模式2并返回所在位置指针
	if(pCommand != NULL)
	{
		command->CmdNum = CurrentMode2CMD;
		return SUCCESS;
	}
	
	return ERROR;
}

/**************************************************
*简介:电流控制模式处理
*参数:
*****参数1:电流控制模式指针
*返回值:无
**************************************************/
void ControlProcess(u8* ControlMode)
{
	u8* pControlMode = ControlMode;
	
		//控制处理
		switch(*pControlMode)
		{
			case SelfLockingMode:
			{
				if(PowerInfo.Current > PowerInfo.CurThreshold)
				{
					OverCurrentInfo.OverCurrentCount++;
					if(OverCurrentInfo.OverCurrentCount >= OVER_CURRENT_NUM)
					{
						OverCurrentInfo.OverCurrentFlag = 1;
						printf("负载过流\r\n");
					}				
				}else
				{
					OverCurrentInfo.OverCurrentCount = 0;
				}
				
				if(OverCurrentInfo.OverCurrentRecoveryFlag == 1 && OverCurrentInfo.OverCurrentFlag == 1)
				{
					OverCurrentInfo.OverCurrentRecoveryFlag = 0;
					OverCurrentInfo.OverCurrentFlag = 0;
					printf("过流解除\r\n");
				}
				
				break;
			}
			case HiccupMode:
			{
				if(PowerInfo.Current > PowerInfo.CurThreshold)
				{
					OverCurrentInfo.OverCurrentCount++;
					if(OverCurrentInfo.OverCurrentCount >= OVER_CURRENT_NUM)
					{
						OverCurrentInfo.OverCurrentFlag = 1;
						HiccupInfo.HiccupStatus = HICCUP_START;
						printf("负载过流\r\n");
					}	
				}else
				{
					OverCurrentInfo.OverCurrentCount = 0;
				}
				
				if(HiccupInfo.HiccupStatus == HICCUP_FINISH && OverCurrentInfo.OverCurrentFlag ==1)
				{
					HiccupInfo.HiccupStatus = HICCUP_START;
					OverCurrentInfo.OverCurrentCount = 0;
					printf("过流解除\r\n");
				}
				break;
			}
			default:
				break;				
		}
}

    专家
    2025-06-02 11:43:14     打赏
    2楼

    感谢分享


    专家
    2025-06-02 11:45:54     打赏
    3楼

    感谢分享


    共3条 1/1 1 跳转至

    回复

    匿名不能发帖!请先 [ 登陆 注册 ]