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管状态真值表如下表所示。

与门输入输出与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.3.1. MOS管搭建驱动板

电机与MOS管搭建驱动板连接见下表所示。

电机与MOS管搭建驱动板连接

电机

MOS管搭建驱动板

M+

M+

5V

编码器电源:+

GND

编码器电源:-

A

A

B

B

M-

M-

MOS管搭建驱动板与主控板连接见下表所示。

MOS管搭建驱动板与主控板连接

MOS管搭建驱动板

主控板

PWM1

PE10

PWM2

PE11

SD

PB15

Current_ADC

PA01

POWER_ADC

PA03

电源输入:5V

5V

电源输入:GND

GND

推荐使用配套的牛角排线直接连接驱动板和主控板,如使用牛角排线,注意缺口方向。连接开发板的那端,请连接在“直流有/无刷电机驱动接口1”上。

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的配置,如下图:

ADC

上图展示了 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

在DMAC中采用块传输的方式,每次传输4个字节,这是ADC采集后的数据寄存器的大小,每次传输四块, 这是因为 AN000 到 AN003 一共有四位,而我们只需要其中的两位,也就是 AN001 以及 AN003。 传输触发源设置成 ADC0 扫描操作结束后,源地址模式和目标地址模式都设置成递增,这样在组0扫描完成后, DMAC就会把转换后的ADC结果自动搬移到设定好的目标地址,关于源地址和目标地址,需要在代码中设置。

6.4.1.3. ADC以及DMAC初始化函数

motor_v_c_acquisition.c-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回调函数

motor_v_c_acquisition.c-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波形震荡,瞬时电流不代表真正的电流值,所以还要对电流值进行滤波。 另外还要计算电流值的初始偏置值,在之后的采集中,要减去偏置值得到准确的电流值。

motor_v_c_acquisition.c-电流滤波函数
/********对电流进行平滑滤波得到准确电流**********/

// 系统启动时电流偏置校准次数
#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. 定时打印函数

motor_v_c_acquisition.c-定时打印函数
// 参考电压,主板跳帽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 部分配置如图:

限幅表0

首先使能了Table0,也就是限幅表0,随后在限幅表0中配置上限和下限,在本实验中设定的是超过20V以及低于10V的情况。 限幅值是通过反向计算出来的,比如10V就是 ((10/37.0)+1.24) / 3.3) * 65536.0。 实际上就是把代码中由ADC转换成电压值的过程反过来。

随后在扫描组0,也就是 AN001 的配置中选择限幅表0。

限幅表0

6.5.1.2. ADC回调函数

与前面回调函数相比只是多了一个事件选择,来看不同的部分:

motor_v_c_acquisition.c-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. 下载验证

将对应程序下载到开发板上运行,启动电机后,串口正常打印电压电流信息,随后慢慢调节电压,观察提示信息即可。

野火直流有刷电机-过压-欠压保护实验 野火直流有刷电机-过压-欠压保护实验2