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. 新建工程¶
我们直接在上一章节的的 “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”。 另外还需要在 “gpt” 文件夹下面新建源文件和头文件 “bsp_gpt_timing.c” 和 “bsp_gpt_timing.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
│ ├─ bsp_gpt_timing.c //新建文件
│ └─ bsp_gpt_timing.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.2. FSP配置¶
下面以野火启明6T2开发板为例来讲解相关的 FSP 配置。
根据原理图所示,需要打开 AN001 以及 AN003 引脚用作电压和电流的采集引脚。 ADC相关知识请参考《瑞萨RA系列FSP库开发实战指南》,这里不过多赘述,主要看虚拟通道AN001以及AN003的配置,如下图:
上图展示了 g_adc_motor 模块的虚拟通道配置界面, 分别设置了 Virtual Channel 0 和 Virtual Channel 1。 Virtual Channel 0 使用 Scan Group 0,并选择通道 AN001, 设置为普通采样模式(Add-time conversion),采样数据格式为 16-bit。
Virtual Channel 1 同样属于 Scan Group 0, 选择通道 AN003,但配置为 Averaging Mode,也就是平均模式,通过多次采样进行平均, 方便进行后续的处理, 数据格式仍为 16-bit。两个虚拟通道均未启用增益或偏移校正功能。
另外还需要新建一个定时器模块,用来定时打印电压电流值。 有关定时器部分也可以看之前的“瑞萨RA系列定时器详解”章节,这里不再赘述。
在本实验中,使用DMAC(直接存储器访问控制器)进行ADC数据的搬移,这样可以有效减少CPU的干预, 从而降低处理器的负载,提高系统的实时性。关于DMAC模块详解也可以参考《瑞萨RA系列FSP库开发实战指南》的 DMAC/DTC——直接存储器访问与数据传输 章节, DMAC的配置如下图:
在DMAC中采用块传输的方式,每次传输4个字节,这是ADC采集后的数据寄存器的大小,每次传输四块, 这是因为 AN000 到 AN003 一共有四位,而我们只需要其中的两位,也就是 AN001 以及 AN003。 传输触发源设置成 ADC0 扫描操作结束后,源地址模式和目标地址模式都设置成递增,这样在组0扫描完成后, DMAC就会把转换后的ADC结果自动搬移到设定好的目标地址,关于源地址和目标地址,需要在代码中设置。
6.4.1.3. ADC以及DMAC初始化函数¶
/**
* @brief 初始化ADC与DMAC模块
*
* 该函数用于初始化ADC模块,包括打开ADC、配置扫描组、校准ADC和启动扫描组,以及配置、初始化DMAC模块。
*
* @param[in] 无
* @return 无
* @note 在调用此函数之前,请确保ADC配置结构体已正确设置。
*/
void ADC_DMAC_Init(void)
{
// 初始化ADC模块
err = R_ADC_B_Open(&g_adc_motor_ctrl, &g_adc_motor_cfg);
//检查是否成功
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_Calibrate(&g_adc_motor_ctrl, NULL);
assert(FSP_SUCCESS == err); // 确保校准初始化成功。
/* 等待校准完成 */
adc_status_t status = {.state = ADC_STATE_CALIBRATION_IN_PROGRESS}; // 初始化状态为“校准中”
while ((ADC_STATE_IDLE != status.state) && // 检查ADC状态是否已空闲
(FSP_SUCCESS == err)) // 确保没有错误发生
{
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); // 延迟1毫秒以避免忙等待
err = R_ADC_B_StatusGet(&g_adc_motor_ctrl, &status); // 获取当前ADC状态
}
// 检查是否成功
assert(FSP_SUCCESS == err);
/*以下配置DMAC*/
// 数据源为R_ADC_B数据寄存器
g_adc_transfer_cfg.p_info->p_src = (void*)&R_ADC_B->ADDR[0];
// 数据目标为结果缓冲区
g_adc_transfer_cfg.p_info->p_dest = &adc_result_buffer[0];
//配置完成后打开DMAC实例
err = R_DMAC_Open(&g_adc_transfer_ctrl, &g_adc_transfer_cfg);
// 检查是否成功
assert(FSP_SUCCESS == err);
//使能DMAC
err = R_DMAC_Enable(&g_adc_transfer_ctrl);
// 检查是否成功
assert(FSP_SUCCESS == err);
//启动ADC扫描组
err = R_ADC_B_ScanGroupStart(&g_adc_motor_ctrl, ADC_GROUP_MASK_0);
assert(FSP_SUCCESS == err);
}
该函数 ADC_DMAC_Init 用于初始化ADC和DMAC模块,确保ADC模块能够正确采集数据并通过DMAC进行数据传输。 在此函数中,DMAC的源地址被配置为ADC的数据寄存器 R_ADC_B->ADDR[0],目标地址为存储ADC结果的缓冲区 adc_result_buffer[0]。 这样配置后,每次扫描结束,DMAC会将ADC数据从寄存器传输到缓冲区,而adc_result_buffer[1]存放的就是电压ADC数据,adc_result_buffer[3]存放的就是电流ADC数据。
6.4.1.4. ADC回调函数¶
/**
* @brief ADC回调函数
*
* 该函数在ADC扫描完成时被调用,用于设置扫描完成标志。
*
* @param[in] p_args 指向ADC回调参数的指针
* @return 无
* @note 此函数由ADC驱动程序自动调用。
*/
void g_adc_motor_callback(adc_callback_args_t * p_args)
{
switch (p_args->event)
{
/*组扫描完成*/
case ADC_EVENT_SCAN_COMPLETE:
/*重置传输源、传输目的地*/
err = R_DMAC_Reset(
&g_adc_transfer_ctrl, // DMA 控制结构体指针
(void*)&R_ADC_B->ADDR[0], // 源地址(ADC 数据寄存器)
&adc_result_buffer[0], // 目标地址(结果缓冲区)
1 // 传输块大小
);
if (FSP_SUCCESS != err)
{
__BKPT(1); // 触发断点,用于调试错误
}
break;
default:
break;
}
}
该函数 g_adc_motor_callback 是一个ADC回调函数,在ADC扫描完成时被自动调用, 在事件触发时,调用 R_DMAC_Reset 函数重置DMAC的源地址和目标地址, 将ADC数据寄存器作为源地址,结果缓冲区作为目标地址,重新准备进行数据传输。
6.4.1.5. 电流滤波函数¶
只采集ADC值还不够,由于电流会随着PWM波形震荡,瞬时电流不代表真正的电流值,所以还要对电流值进行滤波。 另外还要计算电流值的初始偏置值,在之后的采集中,要减去偏置值得到准确的电流值。
/********对电流进行平滑滤波得到准确电流**********/
// 系统启动时电流偏置校准次数
#define OFFSET_SAMPLES 20
// 定义滤波器大小
#define FILTER_SIZE 150
uint32_t current_filter_buffer[FILTER_SIZE]; // 电流数据的滤波缓冲区
uint32_t filter_index = 0; // 当前滤波索引
uint32_t filtered_current = 0; // 滤波后的电流值
uint32_t current_sum = 0; // 用于计算平均值的总和
int i = 0; // 用于计算偏置值
//DMAC中断,每传输完一次数据就进行中断处理
void dmac_callback (dmac_callback_args_t * cb_data)
{
FSP_PARAMETER_NOT_USED(cb_data);
// 等待20次数据,记录最大偏置值
if (i <= OFFSET_SAMPLES) {
// 第一次初始化
if (i == 0) {
current_offset = adc_result_buffer[3];
}
else {
// 比较并保存最大值
if (adc_result_buffer[3] > current_offset) {
current_offset = adc_result_buffer[3];
}
}
i++;
}
//确保偏置值记录完成
else
{
// 将新的电流值存入滤波缓冲区
current_sum -= current_filter_buffer[filter_index]; // 减去旧的值
current_filter_buffer[filter_index] = adc_result_buffer[3]; // 存入新的电流值
current_sum += adc_result_buffer[3]; // 加上新的值
// 更新滤波器索引,达到滤波器大小时回绕
filter_index = (filter_index + 1) % FILTER_SIZE;
// 计算并保存滤波后的电流值(简单的移动平均)
filtered_current = current_sum / FILTER_SIZE;
//减去偏置值并防止负值
if(filtered_current >= current_offset){
filtered_current -= current_offset;
}else
filtered_current = 0;
}
}
6.4.1.6. 定时打印函数¶
// 参考电压,主板跳帽J7部分
#define VREF 3.3f
// 获取测试电压(偏置电压1.24,放大倍数为37)
#define GET_ADC_VDC_VAL(val) ((float)val/(float)65536.0*VREF)
#define GET_VBUS_VAL(val) (((float)val - (float)1.24) * (float)37.0)
// 得到电流值,电压放大8倍,0.02是采样电阻,单位mA
#define GET_ADC_CURR_VAL(val) (((float)val)/(float)8.0/(float)0.02*(float)1000.0)
// GPT中断回调函数
void gpt0_timing_callback(timer_callback_args_t *p_args)
{
// 定时器溢出事件
if (TIMER_EVENT_CYCLE_END == p_args->event)
{
// 先计算电压和电流
float vbus = GET_VBUS_VAL(GET_ADC_VDC_VAL((float)adc_result_buffer[1])); // 电压计算
float current = GET_ADC_CURR_VAL(GET_ADC_VDC_VAL((float)(filtered_current))); // 电流计算
// 打印电压和电流
MOTOR_PRINT("电压 = %.2fV, 电流 = %dmA\r\n", vbus, (int)current);
}
}
该定时打印函数 gpt0_timing_callback 通过GPT定时器溢出事件定期计算并打印电压和电流值:
电压计算:通过 GET_VBUS_VAL 宏,首先读取ADC数据并将其转换为电压值。计算过程包括将ADC读取的原始值转换为测试电压,并根据偏置电压和放大倍数进行校正。
电流计算:通过 GET_ADC_CURR_VAL 宏,利用ADC数据和放大倍数计算电流值。电流的单位为毫安(mA),并且根据采样电阻进行换算。
定时输出:每当定时器溢出时,该回调函数会打印出计算后的电压和电流值,便于实时监控电路的工作状态,例程中设置的是500ms。
随后周期性地获取电压和电流数据,并通过串口输出。
hal_entry入口函数只需要对ADC、DMAC、GPT进行初始化即可,没有多余的新内容,这里不再赘述。
6.4.2. 下载验证¶
编译并下载程序后,复位开发板使程序重新运行,打开串口调试助手,连接好电机以及电机驱动板,启动电机,就可以看到开发板采集的电压值和电流值。
6.5. 在瑞萨RA中实现过压-欠压保护¶
在前面我们通过软件获取了电路的工作状态,现在就可以通过获取到的工作状态来设计一些应用, 例如在实际的使用场景里,我们常常需要关注电路工作时的状态是否正常, 如果发生故障,应当即可进行处理,否则很容以损坏设备, 下面我们通过配置FSP以及编写软件来实现电路的一些保护功能。
6.5.1. 软件设计¶
6.5.1.1. FSP配置¶
在瑞萨RA的ADC中,有一个 Limiter Clipping 模块,也就是限幅功能,主要是在采集的ADC数据超出或者低于限幅值时, 会触发限幅中断,这对于实际应用是一个很重要的功能,接下来就通过这个限幅表来试验 “过压-欠压” 的情况。 打开之前创建的ADC模块属性,关于FSP的 Limiter Clipping 部分配置如图:
首先使能了Table0,也就是限幅表0,随后在限幅表0中配置上限和下限,在本实验中设定的是超过20V以及低于10V的情况。 限幅值是通过反向计算出来的,比如10V就是 ((10/37.0)+1.24) / 3.3) * 65536.0。 实际上就是把代码中由ADC转换成电压值的过程反过来。
随后在扫描组0,也就是 AN001 的配置中选择限幅表0。
6.5.1.2. 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 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 | /**
* @brief ADC回调函数
*
* 该函数在ADC扫描完成时被调用,用于设置扫描完成标志。
*
* @param[in] p_args 指向ADC回调参数的指针
* @return 无
* @note 此函数由ADC驱动程序自动调用。
*/
void g_adc_motor_callback(adc_callback_args_t * p_args)
{
switch (p_args->event)
{
/*组扫描完成*/
case ADC_EVENT_SCAN_COMPLETE:
/*重置传输源、传输目的地*/
err = R_DMAC_Reset(
&g_adc_transfer_ctrl, // DMA 控制结构体指针
(void*)&R_ADC_B->ADDR[0], // 源地址(ADC 数据寄存器)
&adc_result_buffer[0], // 目标地址(结果缓冲区)
1 // 传输块大小
);
if (FSP_SUCCESS != err)
{
__BKPT(1); // 触发断点,用于调试错误
}
break;
case ADC_EVENT_LIMIT_CLIP:
Motor_Control_Stop(); // 停止电机
R_GPT_Stop(&g_timer_gpt0_ctrl); //停止定时器
//蜂鸣器提醒
BUZZER_ON;
R_BSP_SoftwareDelay (200, BSP_DELAY_UNITS_MILLISECONDS);
BUZZER_OFF;
//检查是欠压还是过压
if(adc_result_buffer[1] <= 0x7529){
MOTOR_PRINT("当前欠压,电机电压已不足10V,已停止电机,请复位开发板再试\r\n");
}else{
MOTOR_PRINT("当前过压,电机电压已超过20V,已停止电机,请复位开发板再试\r\n");
}
__BKPT(1); // 触发断点,用于调试错误
break;
default:
break;
}
}
|
在上图中,我们可以看到多了一个ADC_EVENT_LIMIT_CLIP时间选择,也就是限幅触发事件,随后关闭电机和定时器, 并以蜂鸣器响为提醒。 随后检查是欠压事件还是过压事件,并打印提示信息。
6.5.2. 下载验证¶
将对应程序下载到开发板上运行,启动电机后,串口正常打印电压电流信息,随后慢慢调节电压,观察提示信息即可。