草庐IT

基于STM32的外部电源电压检测

爱抖腿的虎子哥 2023-03-28 原文

STM32测量外部电源的电压

本人在项目中遇到一个需求:使用电池给STM32开发板供电,并需要实时显示当前电源的电量情况。这个需求可以说是很常见了,但是却困扰了我整整一个多月。

在收到这个需求的时候我首先想到的就是上网查找相关的技术贴,其中一条名为《基于STM32F103内部AD测量电池电压》的帖子花费了我大量的时间,最后没能完成测试,偶然的一次尝试我发现了一个新电路

 

图中的电阻可以自行调整,R1和R2电阻的作用是分压,对所测电压进行缩小处理:

   测量所得电压=R2/(R2+R1)×电源电压  

根据该公式可以推出

   电源电压=(R2+R1)/R2×测量所得电压  

R0、R3和R4是对电路起到保护作用的电阻,阻值也可以适当调整。

我在实验中使用了两个相同的电路组合测试两个电压,导致相互之间有影响

 

电路中实测阻值为

R0

213

R5

32.1K

R1

213

R6

19.7K

R2

9.85K

R7

32.1K

R3

8.66K

R8

19.3K

R4

19.7K

R9

8.63K

使用稳压电源测试得到数据并进行拟合得到GPIO口的数值与实际电压的关系:

 

所以得到电压的计算公式:

   电压=数值/4096×2.3583×3.3   

然后根据电路以及该公式,对电池从满电一直到放电结束进行数据采集和分析:

 

采集了近万条数据,拟合出了电压-时间关系以及电量电压关系。

最后运行在开发板上如图:

 

测试代码:

  1 /************************************************
  2  功能:基于STM32单片机正点原子精英板的电池电量检测
  3  输入:特定电路位置的电压值  5  开发者:XAUT—余涛
  6  版本:1.0
  7  最后更新时间:2020/08/16
  8 
  9 ************************************************/
 10 
 11 //初始化GPIO口
 12 void Electricity_Init(void)
 13 {
 14     GPIO_InitTypeDef GPIO_InitStructure;
 15     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PORTA时钟
 16     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5 anolog输入     金属传感器
 17     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;        //模拟输入引脚
 18     GPIO_Init(GPIOA, &GPIO_InitStructure);
 19     Adc2_Init();
 20 }
 21 //求幂的函数
 22 double mypow(float x, int y) {
 23     double res = 1;
 24     int i;
 25     for(i = 0; i < y; i++)
 26     {
 27         res *= x;
 28     }
 29     return res;
 30 }
 31 
 32 int main(void)
 33 {
 34     char writeTemp_val[10];  //用于存储resault转为字符后的值
 35     char ElectricityVal[60] = ""; //用于保存电量数据
 36     float voltage = 0; //定义电压变量
 37     int Electricity = 0;   //定义电量数值
 38     int value = 0;
 39     double ElectricityVOL = 0; //电量余量
 40     float temporary = 0; //临时变量
 41     int i = 0;
 42     int time = 0; //可工作时长
 43     FIL fil, newfil;          //文件指针指向打开的文件
 44     FRESULT fileRes;       //文件打开结果
 45     UINT bww = 32;           //写入文件数据类型
 46     u32 total, free;         //内存SD卡的总容量和剩余容量
 47     u8 res = 0;        //扇区格式化结果
 48     vu8 key = 0;       //定义key接收按键信号
 49     usmart_dev.init(SystemCoreClock / 1000000);    //初始化USMART
 50     delay_init();                                                                //延时函数初始化
 51     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                         //设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
 52     uart_init(115200);                                                           //串口初始化为115200,串口监视显示时,波特率应当设为115200
 53     LED_Init();                                                                      //初始化与LED连接的硬件接口
 54     LCD_Init();                                                                      //初始化LCD
 55     BEEP_Init();             //初始化蜂鸣器端口
 56     KEY_Init();           //初始化与按键连接的硬件接口
 57     Electricity_Init();       //初始化GPIO口
 58     W25QXX_Init();                //初始化W25Q128
 59     RTC_Init();                  //RTC初始化
 60     usmart_dev.init(72);        //初始化USMART
 61     my_mem_init(SRAMIN);        //初始化内部内存池
 62     exfuns_init();                            //为fatfs相关变量申请内存
 63     f_mount(fs[0], "0:", 1);                     //挂载SD卡
 64     res = f_mount(fs[1], "1:", 1);                 //挂载FLASH.
 65 
 66     POINT_COLOR = RED;                                                      //设置字体为红色
 67     //显示提示信息
 68     LCD_ShowString(30, 50, 200, 16, 16, "Elite STM32");
 69     LCD_ShowString(30, 70, 200, 16, 16, "Shell Firing Simulation");
 70     LCD_ShowString(30, 110, 200, 16, 16, "ATOM@ALIENTEK");
 71     LCD_ShowString(30, 110, 200, 16, 16, "2021/5/04");
 72     POINT_COLOR = BLUE;                                                     //设置字体为蓝色
 73     LCD_ShowString(30, 130, 200, 16, 16, "Number of launches: 0");            //已发射炮弹实际数量
 74     LCD_ShowString(30, 110, 200, 16, 16, "System is initializing!");            //系统已经准备就绪
 75     LCD_ShowString(30, 150, 200, 16, 16, "Launched: 0");                      //检测到炮弹发射信号次数
 76     LCD_ShowString(30, 170, 200, 16, 16, "Filled in: 0");                     //检测到炮弹装填信号次数
 77     LCD_Clear(WHITE);//清屏
 78     while(font_init())             //检查字库
 79     {
 80         while(SD_Init())//检测不到SD卡
 81         {
 82             LCD_ShowString(30, 110, 200, 16, 16, "SD Card Error!");
 83             delay_ms(500);
 84             LCD_ShowString(30, 110, 200, 16, 16, "Please Check! ");
 85             delay_ms(500);
 86             LED0 = !LED0; //DS0闪烁
 87         }
 88         LCD_ShowString(30, 70, 110, 16, 16, "SD Card OK");
 89         LCD_ShowString(30, 110, 130, 16, 16, "Font Updating...");
 90         key = update_font(20, 110, 16, "0:"); //更新字库
 91         while(key)//更新失败
 92         {
 93             LCD_ShowString(30, 110, 110, 16, 16, "Font Update Failed!");
 94             delay_ms(200);
 95             LCD_Fill(20, 110, 130, 110 + 16, WHITE);
 96             delay_ms(200);
 97         }
 98         LCD_ShowString(30, 110, 110, 16, 16, "Font Update Success!   ");
 99         delay_ms(100);
100         LCD_Clear(WHITE);//清屏
101     }
102     POINT_COLOR = RED;             //设置字体为红色
103     Show_Str(30, 50, 200, 16,  "电量检测测试代码", 16, 0);
104     Show_Str(30, 70, 200, 16,  "XAUT-HuziGe", 16, 0);
105     POINT_COLOR = BLUE;                                                     //设置字体为蓝色
106     Show_Str(30, 110, 200, 16, "系统正在载入!", 16, 0);          //系统已经准备就绪
107     Show_Str(30, 130, 200, 16, "电量检测数值: 0", 16, 0);                   //检测到串口数值
108     Show_Str(30, 150, 200, 16, "电池电压: 0.000V", 16, 0);                   //根据数值转换的电压
109     Show_Str(30, 170, 200, 16, "剩余电量:  0 %", 16, 0);                   //根据电压得到的电量
110     Show_Str(30, 190, 200, 16, "预计还能工作: 0 h", 16, 0);                   //根据电量计算工作时间
111     Disp_Time(30, 90, 16);  //显示时间
112     //delay_ms(100);
113     // RTC_Set(2021,8,16,9,41,0);//设置时间
114 
115     if(res == 0X0D) //FLASH磁盘,FAT文件系统错误,重新格式化FLASH
116     {
117         Show_Str(30, 110, 200, 16, "格式化FLASH...", 16, 0);;    //格式化FLASH
118         res = f_mkfs("1:", 1, 4096); //格式化FLASH,1,盘符;1,不需要引导区,8个扇区为1个簇
119         if(res == 0)
120         {
121             f_setlabel((const TCHAR *)"1:ALIENTEK");    //设置Flash磁盘的名字为:ALIENTEK
122             Show_Str(30, 110, 200, 16, "格式化完成!", 16, 0);    //格式化完成
123         } else Show_Str(30, 110, 200, 16, "格式化失败!", 16, 0);    //格式化失败
124         delay_ms(1000);
125     }
126     LCD_Fill(30, 110, 240, 110 + 16, WHITE);        //清除显示
127     while(exf_getfree("0", &total, &free))    //得到SD卡的总容量和剩余容量
128     {
129         Show_Str(30, 110, 200, 16, "FATFS文件系统加载失败!", 16, 0);
130         delay_ms(200);
131         LED0 = !LED0; //DS0闪烁
132     }
133     POINT_COLOR = BLUE; //设置字体为蓝色
134     Show_Str(30, 110, 200, 16, "文件系统加载完成!       ", 16, 0);
135     delay_ms(1000);
136     fileRes = f_open(&fil, "0:/Electricity.txt", FA_OPEN_ALWAYS | FA_WRITE); //打开文件Electricity.txt,若没有则新建
137     while(FR_OK != fileRes)      //打开文件Electricity.txt,若没有则新建,检查文件打开是否成功
138     {
139         Show_Str(30, 110, 200, 16, "文件打开失败!", 16, 0);
140         delay_ms(500);
141         Show_Str(30, 110, 200, 16, "正在重试!    ", 16, 0);
142         delay_ms(500);
143         LED0 = !LED0; //DS0闪烁
144         fileRes = f_open(&fil, "0:/Electricity.txt", FA_OPEN_ALWAYS | FA_WRITE); //打开文件Electricity.txt,若没有则新建
145         LCD_ShowxNum(30 , 2110, fileRes, 3, 16, 0);    //显示错误类型
146     }
147     f_lseek(&fil, fil.fsize);   //指针指到文件末尾
148     Show_Str(30, 110, 200, 16, "文件一打开测试完成!", 16, 0);
149     while(FR_OK != f_close(&fil)) {}
150     delay_ms(1000);
151     fileRes = f_open(&newfil, "0:/Metal.txt", FA_OPEN_ALWAYS | FA_WRITE); //打开文件Metal.txt,若没有则新建
152     while(FR_OK != fileRes)      //打开文件Metal.txt,若没有则新建,检查文件打开是否成功
153     {
154         Show_Str(30, 110, 200, 16, "文件打开失败!", 16, 0);
155         delay_ms(500);
156         Show_Str(30, 110, 200, 16, "正在重试!    ", 16, 0);
157         delay_ms(500);
158         LED0 = !LED0; //DS0闪烁
159         fileRes = f_open(&newfil, "0:/Metal.txt", FA_OPEN_ALWAYS | FA_WRITE); //打开文件Metal.txt,若没有则新建
160         LCD_ShowxNum(30 , 2110, fileRes, 3, 16, 0);    //显示错误类型
161     }
162     f_lseek(&newfil, newfil.fsize);   //指针指到文件末尾
163     Show_Str(30, 110, 200, 16, "文件二打开测试完成!", 16, 0);
164     delay_ms(1000);
165     while(FR_OK != f_close(&newfil)) {}
166     Show_Str(30, 110, 200, 16, "系统准备就绪!             ", 16, 0);        //系统已经准备就绪
167     while(1)
168     {
169         Disp_Time(30, 90, 16);  //显示时间
170         //连续读取10次取平均值
171         for(i = 0; i < 10; i++)
172         {
173             printf("in for");
174             Electricity = Get_Adc2(ADC_Channel_6);    //读取ADC值通道6  即PA6
175             value += Electricity;
176             delay_ms(10);
177         }
178         Electricity = value / 10;
179         value = 0;
180         strcpy(ElectricityVal, Res_Time());    //将时间写入字符串  Res_Time()函数是作为自己写的,获得当前时间组成的字符串
183         voltage = (float)(Electricity * (2.3583 * 3.3 / 4096) - 1.6494); //计算电压值(新电路测电源)
184         sprintf(writeTemp_val, "\t%4d\t%.3f\n", Electricity, voltage);
185         strcat(ElectricityVal, writeTemp_val);
186         temporary = voltage;
187         ElectricityVOL = ((-285.53) * mypow(voltage, 6) + 5601.4 * mypow(voltage, 5) - 45377 * mypow(voltage, 4) + 194353 * mypow(voltage, 3) - 464275 * mypow(voltage, 2) + 586596 * mypow(voltage, 1) - 306296);
188         if(ElectricityVOL > 1)
189             time = ElectricityVOL / 100 * 32; //满电情况下测试可连续工作32小时
190         else
191             time = 0;
192 
193         LCD_ShowxNum(30 + 10 * 8, 170, ElectricityVOL, 3, 16, 0);
194         LCD_ShowxNum(30 + 14 * 8, 190, time, 2, 16, 0);
195         voltage = temporary;
196         //显示电压到LCD屏幕上
197         LCD_ShowxNum(30 + 14 * 8, 130, Electricity, 4, 16, 0);
198         Electricity = voltage;
199         voltage -= Electricity;
200         voltage *= 1000;
201         voltage += 10000;
202         LCD_ShowxNum(30 + 10 * 8, 150, Electricity, 1, 16, 0);//整数部分
203         LCD_ShowxNum(30 + 12 * 8, 150, voltage, 3, 16, 0);//小数部分
204 
205         //打开文件Electricity.txt,若没有则新建
206         while(FR_OK != f_open(&fil, "0:/Electricity.txt", FA_OPEN_ALWAYS | FA_WRITE)) {};
207         f_lseek(&fil, f_size(&fil));   //指针指到文件末尾
208         f_write(&fil, ElectricityVal, strlen(ElectricityVal), &bww);   //写入读取到的值到Electricity.txt
209         while(FR_OK != f_close(&fil)) {}
210         //每10秒进行一次检测并记录
211         for(i = 0; i < 10; i++)
212         {
213             delay_ms(1000);
214         }
215         //delay_ms(1000 * 60 * 5); //延时时间为10ms,可以检测按键按下的最佳延时
216     }
217 }

 

有关基于STM32的外部电源电压检测的更多相关文章

  1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  2. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  5. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  6. ruby - 从外部访问类的实例变量 - 2

    我理解(我认为)Ruby中类变量和类的实例变量之间的区别。我想知道如何从该类外部访问该类的实例变量。从内部(即在类方法中而不是实例方法中),它可以直接访问,但是从外部,有没有办法做MyClass.class.[@$#]variablename?我没有任何具体原因要这样做,只是学习Ruby并想知道是否可行。 最佳答案 classMyClass@my_class_instance_var="foo"class上述yield:>>foo我相信Arkku演示了如何从类外部访问类变量(@@),而不是类实例变量(@)。我从这篇文章中提取了上述内

  7. ruby - 检测由 RSpec、Ruby 运行的代码 - 2

    我想知道我的代码是否在rspec下运行。这可能吗?原因是我正在加载一些错误记录器,这些记录器在测试期间会被故意错误(expect{x}.toraise_error)弄得乱七八糟。我查看了我的ENV变量,没有(明显的)测试环境变量的迹象。 最佳答案 在spec_helper.rb的开头添加:ENV['RACK_ENV']='test'现在您可以在代码中检查RACK_ENV是否经过测试。 关于ruby-检测由RSpec、Ruby运行的代码,我们在StackOverflow上找到一个类似的问题

  8. ruby - 无法安装 gem - make 未被识别为内部或外部命令可运行程序或批处理文件 - 2

    我想在Windows7上安装带有ruby​​1.9.3的rspec-railsgem。我收到一些错误消息,提示无法安装某些json库。所以,我使用下面的说明来解决它。来源=The'json'nativegemrequiresinstalledbuildtools从[rubyinstaller.org][3]下载[Ruby1.9.3][2]从[rubyinstaller.org][3]下载DevKit文件对于Ruby1.9.3,使用[DevKit-tdm-32-4.5.2-20110712-1620-sfx.exe][4]将DevKit解压到路径C:\Ruby193\DevKit运行cd

  9. ruby - 使用 Class.new 时访问外部范围 - 2

    是否有可能以某种方式访问​​Class.new范围内的a?a=5Class.new{defb;aend}.new.b#NameError:undefinedlocalvariableormethod`a'for#:0x007fa8b15e9af0>#:in`b' 最佳答案 即使@MarekLipka的回答是正确的——改变变量范围总是有风险的。这是可行的,因为每个block都带有创建它的上下文,因此您的局部变量a突然变得不那么局部了——它变成了一个“隐藏的”全局变量:a=5object=Class.new{define_method(

  10. ruby - 使用 Ruby Daemons gem 检测停止 - 2

    我正在使用rubydaemongem。想知道如何向停止操作添加一些额外的步骤?希望我能检测到停止被调用,并向其添加一些额外的代码。任何人都知道我如何才能做到这一点? 最佳答案 查看守护程序gem代码,它似乎没有用于此目的的明显扩展点。但是,我想知道(在守护进程中)您是否可以捕获守护进程在发生“停止”时发送的KILL/TERM信号...?trap("TERM")do#executeyourextracodehereend或者你可以安装一个at_exit钩子(Hook):-at_exitdo#executeyourextracodehe

随机推荐