外观
电压检测与显示实验
电路分析
在简易万用表的电压测量电路上,是通过差分放大电路来进行检测的,除此之外还涉及COM端控制电路以及红表笔切换电路。具体的电路分析可前往第二章-电路原理分析进行查看。
这里重点要看的是各引脚的分布以及对应实现的功能,实际也就是控制对应的高低电平来实现切换,同时通过ADC引脚来获取电压值最后进行转换即可。
功能实现
创建工程
- 点击左上方文件、选择新建,选择新建MounRiver工程;
- 对工程进行修改,修改完成后点击“完成”按键,系统会自动生成好工程;
- 创建自己的工程库文件夹,分别创建头文件夹与源文件夹,将之前的库文件包含进来,包括蜂鸣器驱动文件、屏幕驱动文件、长按开关机文件等,同时新建3个文件,用于电压检测、黑表笔公共端导通控制、继电器控制。
- 对工程库路径进行包含;
代码实现
- 这里涉及的代码还是非常多的,有关继电器、黑表笔控制引脚相关的初始化与之前蜂鸣器或LED灯的初始化一致,这里就直接在相关的文件中编写出来然后声明即可。
初始化相关控制IO
C
/*
* comControl.c
*
* Created on: 2024年5月8日
* Author: lceda
*/
#include "comControl.h"
/*
* 函数内容:初始化公共端控制IO
* 函数参数:无
* 返回值:无
*/
void Init_Com_GPIO(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_WriteBit(GPIOB,GPIO_Pin_9,Bit_RESET);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/*
* 函数内容:使公共端连通GND
* 函数参数:无
* 返回值:无
*/
void Connect_GND(void)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_9, Bit_SET);
}
/*
* 函数内容:使公共端断开GND
* 函数参数:无
* 返回值:无
*/
void DisCon_GND(void)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_9, Bit_RESET);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
C
#include "Res_Vol_Control.h"
/*
* 函数内容:初始化光耦控制GPIO
* 函数参数:无
* 返回值:无
*/
void Init_RES_VOL_Con_GPIO(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_WriteBit(GPIOB,GPIO_Pin_1,Bit_RESET);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/*
* 函数内容:继电器连接到电阻测量端
* 函数参数:无
* 返回值:无
*/
void RES_VOL_Connect_RES(void)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_1,Bit_SET);
}
/*
* 函数内容:继电器连接到电压测量端
* 函数参数:无
* 返回值:无
*/
void RES_VOL_Connect_VOL(void)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_RESET);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- 将这些控制开关初始化完成后,接下来就是初始化电压检测引脚,需要设置为ADC采集,使用单次转换模式。
电压采集
C
#include "Res_Vol_Control.h"
#include "voltage.h"
#include "comControl.h"
#include "tftshow.h"
#include "main.h"
/*
* 函数内容:初始化电压检查ADC引脚
* 函数参数:无
* 返回值;无
*/
void Init_Vol_ADC_GPIO(void)
{
ADC_InitTypeDef ADC_InitStructure = {0};
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div8); //ADC时钟分频,最大14Mhz, 96/8 = 12M
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOC, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC1工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //禁止扫描转换模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //禁止连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //规则通道组转换,不使用外部触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行转换的通道数
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 1, ADC_SampleTime_239Cycles5 );
ADC_Cmd(ADC1, ENABLE); //使能ADC1
ADC_ResetCalibration(ADC1); //重置选择ADC1校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位完成
ADC_StartCalibration(ADC1); //开始ADC1校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准完成
}
/*
* 函数内容:得到任意通道的值
* 函数参数:uint8_t ch--通道值
* 返回值:无
*/
uint16_t Get_ADC_Val(uint8_t ch)
{
uint16_t val;
ADC_RegularChannelConfig( ADC1, ch, 1, ADC_SampleTime_239Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
val = ADC_GetConversionValue(ADC1);
ADC_ClearFlag( ADC1, ADC_FLAG_EOC);
return val;
}
uint16_t Get_ADC_Average(uint8_t ch, uint16_t num)
{
uint16_t i = 0,val = 0;
uint32_t sum_val = 0;
uint16_t max_value = 0,min_value = 9999;
for(i =0;i<num;i++)
{
val = Get_ADC_Val(ch);
if(min_value > val){
min_value = val;
}
if(max_value < val){
max_value = val;
}
sum_val = sum_val +val;
}
sum_val = sum_val - max_value - min_value;
sum_val = sum_val / (num - 2);
return sum_val;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
- 相关初始化完成后,最后编写电压检测任务,在电压检测任务中,会不断对电压进行测量然后校准,最后将数据进行汇总取平均数滤波,这样得到一个较为准确的电压值。考虑到电压测量可以反序,这里在代码编写过程中需要将值进行反转,不管正常接还是反着接,都是一个正常的值。
电压采集
C
static void bubble_sort(float arr[], uint16_t len)
{
uint16_t i = 0, j = 0;
float temp = 0;
for (i = 0; i < len - 1; i++)
{
for (j = 0; j < len - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
/*
* 函数内容:测电压任务
* 函数参数:无
* 返回值:无
*/
static uint8_t first_in = 0;
static float sum_volValue[100] = {0};
static uint16_t num = 0;
void Voltage_Task(void)
{
float TempVolValue = 0, Vol_Value = 0;
uint16_t value_sum = 0,ref_vol_value = 0,i = 0;
if(first_in == 0){ //如果是第一次进入电压测量页面
first_in = 1;
RES_VOL_Connect_VOL(); //继电器切换到电压测量端
LCD_ShowChinese(16, 0, "电压模式", BLACK, WHITE, 32, 0); //显示模式
}
ADC_TempSensorVrefintCmd(DISABLE); //取消内部测量
value_sum = Get_ADC_Average(ADC_Channel_15,200); //测量外部ADC值,取200次平均值
ADC_TempSensorVrefintCmd(ENABLE); //使能内部基准测量
ref_vol_value = Get_ADC_Average(ADC_Channel_Vrefint,100); //测量内部ADC值,取100次平均值
TempVolValue = ((value_sum*1.0f) / ref_vol_value)*1.2f; //换算为电压值
if(TempVolValue<0.01){ //如果是抖动电压
TempVolValue = 0;
}
if(TempVolValue > 1.65){ //如果是红表笔接正极,黑表笔接负极
TempVolValue = (492.0f * TempVolValue)/22.0f - 38.0f;
}
else {
TempVolValue = 38.0f - (492.0f * TempVolValue)/22.0f;
}
sum_volValue[num] = TempVolValue; //记录每一个值
num++;
if(num >= 100) //计满100个
{
num = 0;
bubble_sort(sum_volValue,100); //冒泡排序
for(i=10;i<90;i++) //取中间80个
{
Vol_Value = Vol_Value + sum_volValue[i];
}
Vol_Value = Vol_Value / 80.0f; //取80个的平均值
LCD_ShowFloatNum1(24,48,(Vol_Value*1.0f),4,BLACK,WHITE,32); //显示电压值
LCD_ShowChar(104,48,'V',BLACK,WHITE,32,0);
for(i=0;i<100;i++) //清除计数
{
sum_volValue[i] = 0;
}
Vol_Value = 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
- 最后在主函数中进行调用,当设备开机后开始执行电压测量任务,在main.h中,有一个结构体,虽然有进行声明,但是并没有实际使用,这是为了后续综合案例做准备,大家也可以事先查看或尝试进行优化。
电压采集
C
#include "Res_Vol_Control.h"
#include "debug.h"
#include "beep.h"
#include "comControl.h"
#include "powerswitch.h"
#include "tftinit.h"
#include "tftshow.h"
#include "voltage.h"
#include "ec11.h"
#include "main.h"
/* Global typedef */
/* Global define */
/* Global Variable */
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
struct DEVICE_PARAMETER device_parameter;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
device_parameter.device_page = VOLTAGE_PAGE; //当前页面为电压测量页面
Init_Power_Con_GPIO(); //初始化电源控制引脚
Init_BEEP_GPIO(); //初始化蜂鸣器控制引脚
Init_Com_GPIO(); //初始化黑表笔控制引脚
Init_RES_VOL_Con_GPIO();//初始化固态继电器控制引脚
Init_Vol_ADC_GPIO(); //初始化电压检测引脚
Init_EC11_GPIO(); //初始化EC11引脚
LCD_Init(); //LCD显示初始化
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
while(1)
{
key_task();
if(device_state == DEVICE_TURN_ON) //如果设备此时已经开机
{
Voltage_Task(); //电压测量任务
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
TIP
烧录代码后,接上万用表表笔到电压测量端与COM端,进行测试,看一下实际电压值是否正常。
在实际测试过程中,电压值与真实值肯定会有偏差,此时需要通过增加显示空载的电压值,然后去校准代码中的参数,有可能不是1.65或你的参考电压本身就不准,这些都是需要调整的。
源码&USB下载工具等软件,均在[gitee资料中](https://gitee.com/chen11232/simple-digital-multimeter)