首先来张硬件全家福和软件项目构成图。主要用了几个模块:
1.F411主控板 2.正点原子便携电源 3.INA219模块 4. OLED模块 5.不知道从哪遗留的电机模块(直流电机通过皮带带动旋转平台,上面放了一卷电工胶带当负载) 5.USB转mini usb数据线(和stlink相连) 6.串口通信模块
系统框图
电路中主要用了2个模拟IIC,分别与OLED和INA219通信,一个串口和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 | 串口接收 |
实现的任务:
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; } }