6. 直流有刷驱动板电流电压采集¶
野火使用MOS管搭建的直流有刷驱动板做到了信号完全隔离,其他驱动板基本都只是使用光耦隔离了控制信号, 并没有对ADC采样电路进行隔离,野火不仅使用光耦对控制信号进行了隔离, 还使用AMC1200SDUBR隔离运放对ADC采样电路进行了隔离。
6.1. 电流采样电路¶
如下图所示是电流采样电路,在电机驱动电路中串入一个0.02Ω、2W的采样电阻,将电流信号转换成电压信号, 再经过隔离运放放大8倍后差分输出,使用普通运放将差分输出转换成单端输出给瑞萨RA的ADC采样通道。
从上图中我们可以知道是一个负反馈电路,那么根据虚短和虚断可以知道Up=Un, p点和n点没有电流到运放的5脚和6脚,可以得:
R61与后面的电容组成RC滤波电路,R61上流过的电流很小,压降也小,可以忽略不计,Vo等于Vcurrent_adc。
将(1)式和(2)式整理可得:
因为Up=Un,所以有:
其中R52=R51=R53=R50=10KΩ, 将R52、R51、R53和R50阻值带入上式化简可得:
因为隔离运放将Vi放大8倍后输出,所以有U7-U6=8*Vi, 带入上式可得:
在下图中使用电压比较器LMV331SE实现10A过流保护电路,电流采样电路中Vi经过隔离运放和普通运放后变成Vcurrent_adc输入到下图比较器的IN-, 当IN-的电压超过IN+时比较器的OUT将输出低电平到锁存器中,锁存器对输入信号进行锁存并输出到与门。
光耦隔离部分电路图如下图所示。
与门输入输出与MOS管状态真值表如下表所示。
A |
B |
Y |
MOS |
---|---|---|---|
H |
H |
H |
可导通 |
H |
L |
L |
关断(过流保护) |
L |
H |
L |
关断(单片机控制关断) |
L |
L |
L |
关断(单片机控制关断,过流保护) |
6.2. 电压采样电路¶
如下图所示是电源电压采样电路,在电源电压上并联R18和R19的串联电阻,R19两端的电压作为隔离运放的输入, 再经过隔离运放放大8倍后差分输出,使用普通运放将差分输出转换成单端输出,连接到瑞萨RA的ADC采样通道。 隔离运放的输入电压为Vi,则有:Vi/R19=POWER/(R18+R59+R19),带入电阻值可得:Vi=POWER/37, 通过上一节中电流采样电流的计算方法可以计算得到POWER_ADC=POWER/37+1.24,不同的是,电压检测部分的隔离运放是没有放大的。
6.3. 硬件连接¶
本章实验需要连接开发板和驱动板,这里给出接线表。
6.4. 在瑞萨RA中实现电流电压采集¶
从第一节电流采样电路中,我们可以知道,想要对电流进行采集,需要将电流信号转换为电压信号。 我们通过硬件部分完成了对该信号转换、放大处理,这样一来就可以很方便的在瑞萨RA中使用ADC外设对该信号进行采集。 在瑞萨RA中采集到了数据,最终再通过的一些数据的处理,我们就可以得到所需的电流值。 当然同理可得,我对电压信号的采集也是类似地,下面我们看代码如何进行这部分的处理。
6.4.1. 软件设计¶
6.4.1.1. 编程要点¶
初始化ADC并进行数据的获取
编写函数对采集得到的数据进行处理
编写获取最终电流值的函数
测试代码
6.4.1.2. 新建工程¶
我们直接在上一章节的的 “200_Motor_BDC_PWM_Control” 例程的基础上修改程序。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “200_Motor_BDC_PWM_Control”, 然后将工程文件夹重命名为 “201_Motor_BDC_Current_Voltage_Acquisition”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “200_Motor_BDC_PWM_Control”, 然后将工程文件夹重命名为 “201_Motor_BDC_Current_Voltage_Acquisition”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “adc” 文件夹, 再进入 “adc” 文件夹里面新建源文件和头文件:“motor_v_c_acquisition.c” 和 “motor_v_c_acquisition.h”。 工程文件结构如下。
201_Motor_BDC_Current_Voltage_Acquisition
├─ ......
└─ src
├─ adc
│ ├─ motor_v_c_acquisition.c //新建文件
│ └─ motor_v_c_acquisition.h //新建文件
├─ beep
│ ├─ bsp_beep.c
│ └─ bsp_beep.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ gpt
│ ├─ bsp_gpt_pwm_output.c
│ └─ bsp_gpt_pwm_output.h
├─ key
│ ├─ bsp_key_irq.c
│ └─ bsp_key_irq.h
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ motor_controls
│ ├─ bsp_motor_control.c
│ └─ bsp_motor_control.h
└─ hal_entry.c
6.4.1.3. FSP配置¶
下面以野火启明6T2开发板为例来讲解相关的 FSP 配置。
根据原理图所示,需要打开 AN001 以及 AN003 引脚用作电压和电流的采集引脚。 ADC相关知识请参考《瑞萨RA系列FSP库开发实战指南》,这里不过多赘述。 这里来讲解使用FSP库进行电压电流采集时如何更便捷的使用偏置值计算,如下图:
在图中,除了设置ADC模块的名称以及回调函数,还有两个特殊的表,分别代表 “用户偏移功能” 以及 “用户增益功能”, 用户偏移调整功能可在 A/D 转换数据中添加或减去一个常量值。Virtual channels 从用户指定的值表中选择一个偏移量。 用户增益调整功能将 A/D 转换数据乘以任意系数值。与偏移一样,虚拟通道可以从用户指定的表中选择一个增益值。
提示
当转换数据的数据长度选择14位/12位/10位时,偏移值的低位将根据数据格式的选择进行切割。
所以,使用常用的12位精度接收数据时,应该注意此处的偏移量为16位,要将偏移量切换成二进制,再统一切换成16位数值。 例如本实验中,电压的偏移值为1.24V,计算得到的偏移量为: 1.24V / 3.3V(参考电压) × 65535 ≈ 24625 ,由于是多的偏置值,要减去, 所以FSP偏移表第一项填写 -24625 。
随后在虚拟通道中从设定的表中选择一个偏移量,如下图所示:
这样就省去了我们在软件中的计算量,另外还需要新建一个定时器模块,用来定时打印电压电流值。 有关定时器部分也可以看之前的“瑞萨RA系列定时器详解”章节,这里不再赘述。
6.4.1.4. ADC初始化函数¶
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 | /**
* @brief 初始化ADC模块
*
* 该函数用于初始化ADC模块,包括打开ADC、配置扫描组和启动扫描组。
* @note 在调用此函数之前,请确保ADC配置结构体已正确设置。
*/
void adc_Init(void)
{
fsp_err_t err = FSP_SUCCESS; // 定义错误码,初始化为成功状态
// 初始化ADC模块
err = R_ADC_B_Open(&g_adc_motor_ctrl, &g_adc_motor_cfg);
// 检查是否成功打开ADC,失败则断言
assert(FSP_SUCCESS == err);
// 配置ADC扫描组
err = R_ADC_B_ScanCfg(&g_adc_motor_ctrl, &g_adc_motor_scan_cfg);
// 检查扫描配置是否成功,失败则断言
assert(FSP_SUCCESS == err);
// 启动ADC扫描组
err = R_ADC_B_ScanGroupStart(&g_adc_motor_ctrl, ADC_GROUP_MASK_ALL);
// 检查扫描组启动是否成功,失败则断言
assert(FSP_SUCCESS == err);
}
|
6.4.1.5. 电压电流值读取函数¶
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 | /**
* @brief 读取ADC电压值
*
* 该函数从ADC通道1读取电压值,并将其转换为实际电压值。
*
* @param[in] 无
* @return 返回读取到的电压值
* @note 此函数在调用前需确保ADC扫描已完成。
*/
double Read_ADC_Voltage_Value(void)
{
uint16_t adc1_data; // 存储ADC读取到的数据
double c0; // 存储转换后的电压值
// 等待扫描完成
while (!scan_complete_flag)
{
; // 空循环,直到扫描完成标志为真
}
// 从ADC通道1读取数据
R_ADC_B_Read(&g_adc_motor_ctrl, ADC_CHANNEL_1, &adc1_data);
// 将ADC值转换为电压值
c0 = GET_ADC_VDC_VAL(adc1_data); // 获取直流电压值
c0 = GET_VBUS_VAL(c0); // 进一步获取总线电压值
// 返回电压值
return c0;
}
/**
* @brief 读取ADC电流值
*
* 该函数从ADC通道3读取电流值,并将其转换为实际电流值。
*
* @param[in] 无
* @return 返回读取到的电流值
* @note 此函数在调用前需确保ADC扫描已完成。
*/
double Read_ADC_Current_Value(void)
{
uint16_t adc2_data; // 存储ADC读取到的数据
double c1; // 存储转换后的电流值
// 等待扫描完成
while (!scan_complete_flag)
{
; // 空循环,直到扫描完成标志为真
}
// 从ADC通道3读取数据
R_ADC_B_Read(&g_adc_motor_ctrl, ADC_CHANNEL_3, &adc2_data);
// 计算电流
c1 = GET_ADC_VDC_VAL(adc2_data); // 获取直流电压值作为电流数据
c1 = GET_ADC_CURR_VAL(c1); // 进一步获取电流值
// 置扫描完成标志为假,以便进行下一次扫描
scan_complete_flag = false;
// 返回电流值
return c1;
}
|
6.4.1.6. 定时打印函数¶
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 | // GPT中断回调函数
void gpt0_timing_callback(timer_callback_args_t *p_args)
{
// 定时器溢出事件
if (TIMER_EVENT_CYCLE_END == p_args->event)
{
Time += 5;
// 读取当前电流值并加入移动平均滤波器
double current_value = Read_ADC_Current_Value();
current_sum -= current_samples[current_index]; // 减去旧值
current_samples[current_index] = current_value; // 更新当前值
current_sum += current_value; // 加上新值
// 更新索引
current_index = (uint16_t)((current_index + 1) % FILTER_WINDOW_SIZE);
// 计算采样次数
if (sample_count < FILTER_WINDOW_SIZE)
{
sample_count++;
}
// 每秒计算一次平均值并打印
if (Time == 500)
{
// 翻转 LED1
LED1_TOGGLE; // 每秒翻转一次
// 计算当前平均值
double current_avg = current_sum / sample_count;
printf("电压 = %.2fV, 电流 = %.2fmA\r\n", Read_ADC_Voltage_Value(), (current_avg > current_offset) ? (current_avg - current_offset) : 0);
// 重置时间
Time = 0;
}
}
}
|
6.4.1.7. hal_entry入口函数¶
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | void hal_entry(void)
{
/* TODO: add your own code here */
LED_Init(); // LED 初始化
Debug_UART9_Init(); // SCI4 UART 调试串口初始化
/* GPT和电机初始化 */
Motor_GPT_PWM_Init();
Motor_Control_Init();
adc_Init();
printf("这是一个有刷电机基础控制示例\r\n");
printf("打开串口助手发送以下指令,可控制电机运行状态:\r\n");
printf("s----------------电机开始旋转\r\n");
printf("p----------------电机停止旋转\r\n");
printf("u----------------电机加速旋转[PWM+10%%]\r\n");
printf("d----------------电机减速旋转[PWM-10%%]\r\n");
printf("r----------------电机反向旋转\r\n");
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒
while(1)
{
if(motor_start_flag == true) // 如果电机启动标志为真
{
motor_start_flag = false; // 将电机启动标志设为假
Motor_Control_Start(); // 启动电机
GPT_Timing_Init(); //启动定时器
printf("电机启动,当前PWM占空比 = %d%%\r\n", motor_pwm_duty);
}
if(motor_stop_flag == true) // 如果电机停止标志为真
{
motor_stop_flag = false; // 将电机停止标志设为假
Motor_Control_Stop(); // 停止电机
R_GPT_Stop(&g_timer_gpt0_ctrl); //停止定时器
printf("************电机关闭************\r\n");
}
if(motor_speedup_flag == true) // 如果电机加速标志为真
{
motor_speedup_flag = false; // 将电机加速标志设为假
motor_pwm_duty += 10; // 增加电机的PWM占空比
if (motor_pwm_duty > 100) {// 如果PWM占空比超过100
motor_pwm_duty = 100; // 将PWM占空比限制为100
printf("已到达最大占空比,当前电机PWM占空比 = %d%%\r\n", motor_pwm_duty);
}else{
Motor_Control_SetDirAndDuty(motor_dir, (uint8_t)motor_pwm_duty); // 设置电机方向和占空比
printf("电机加速,新的电机PWM占空比 = %d%%\r\n", motor_pwm_duty); // 打印当前的PWM占空比
}
}
if(motor_slowdown_flag == true) // 如果电机减速标志为真
{
motor_slowdown_flag = false; // 将电机减速标志设为假
motor_pwm_duty -= 10; // 减少电机的PWM占空比
if (motor_pwm_duty < 0){// 如果PWM占空比小于0
motor_pwm_duty = 0; // 将PWM占空比限制为0
printf("已到达最小占空比,当前电机PWM占空比 = %d%%\r\n", motor_pwm_duty);
}else{
Motor_Control_SetDirAndDuty(motor_dir, (uint8_t)motor_pwm_duty); // 设置电机方向和占空比
printf("电机减速,新的电机PWM占空比 = %d%%\r\n", motor_pwm_duty); // 打印当前的PWM占空比
}
}
if(motor_reverse_flag == true) // 如果电机反转标志为真
{
motor_reverse_flag = false; // 将电机反转标志设为假
Motor_Control_Reverse(); // 反转电机
if (motor_dir == 0) {
printf("电机方向翻转,当前旋转方向:逆时针\n");
}
else {
printf("电机方向翻转,当前旋转方向:顺时针\n");
}
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
|
6.4.2. 下载验证¶
编译并下载程序后,复位开发板使程序重新运行,打开串口调试助手,连接好电机以及电机驱动板,启动电机,就可以看到开发板采集的电压值和电流值。