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 }
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
文章目录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.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/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
我理解(我认为)Ruby中类变量和类的实例变量之间的区别。我想知道如何从该类外部访问该类的实例变量。从内部(即在类方法中而不是实例方法中),它可以直接访问,但是从外部,有没有办法做MyClass.class.[@$#]variablename?我没有任何具体原因要这样做,只是学习Ruby并想知道是否可行。 最佳答案 classMyClass@my_class_instance_var="foo"class上述yield:>>foo我相信Arkku演示了如何从类外部访问类变量(@@),而不是类实例变量(@)。我从这篇文章中提取了上述内
我想知道我的代码是否在rspec下运行。这可能吗?原因是我正在加载一些错误记录器,这些记录器在测试期间会被故意错误(expect{x}.toraise_error)弄得乱七八糟。我查看了我的ENV变量,没有(明显的)测试环境变量的迹象。 最佳答案 在spec_helper.rb的开头添加:ENV['RACK_ENV']='test'现在您可以在代码中检查RACK_ENV是否经过测试。 关于ruby-检测由RSpec、Ruby运行的代码,我们在StackOverflow上找到一个类似的问题
我想在Windows7上安装带有ruby1.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
是否有可能以某种方式访问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(
我正在使用rubydaemongem。想知道如何向停止操作添加一些额外的步骤?希望我能检测到停止被调用,并向其添加一些额外的代码。任何人都知道我如何才能做到这一点? 最佳答案 查看守护程序gem代码,它似乎没有用于此目的的明显扩展点。但是,我想知道(在守护进程中)您是否可以捕获守护进程在发生“停止”时发送的KILL/TERM信号...?trap("TERM")do#executeyourextracodehereend或者你可以安装一个at_exit钩子(Hook):-at_exitdo#executeyourextracodehe