10. 无刷有刷驱动板温度电压三相电流采集

野火使用MOS管搭建的直流无刷驱动板做到了信号完全隔离,其他驱动板基本都只是使用光耦隔离了控制信号, 并没有对ADC采样电路进行隔离,野火不仅使用光耦对控制信号进行了隔离, 还使用AMC1200SDUBR隔离运放对ADC采样电路进行了隔离。

10.1. 电流采样电路

如下图所示是电流采样电路,在电机驱动电路中串入一个0.02Ω、2W的采样电阻,将电流信号转换成电压信号, 再经过隔离运放放大8倍后差分输出,使用普通运放将差分输出转换成单端输出给RA6T2的ADC采样通道。

电流采样电路

从上图中我们可以知道是一个负反馈电路,那么根据虚短和虚断可以知道Up=Un, p点和n点没有电流到运放的6脚和7脚,可以得:

差分转单端输出

R108与后面的电容组成RC滤波电路,R108上流过的电流很小,压降也小,可以忽略不计,Vo等于Vcurrent_adc

将(1)式和(2)式整理可得:

差分转单端输出整理

因为Up=Un,所以有:

差分转单端输出合并

其中R67=R66=R69=R68=10KΩ, 将R67、R66、R69和R68阻值带入上式化简可得:

差分转单端输出化简

因为隔离运放将Vi放大8倍后输出,所以有U7-U6=8*Vi, 带入上式可得:

差分转单端输出结果

在下图中使用电压比较器LMV331SE实现10A过流保护电路

比较器逻辑:IN+电压大于IN-,则输出高电平。反之则相反。

正常情况下:Motor_IU/V/W_ADC采集的电压低于R86和R87分压值2V84_IN,通过比较器U19/U20/U21输出高电平3.3V,U33引脚3处于高电平3.3V,与R142和R143分压值(3V3_IN/2)比较输出低电平0V,(或者短时间内Motor_IU/V/W_ADC电压高于分压值2V84_IN,通过比较器U19/U20/U21输出低电平0V,通过R133、R134、C33器件RC充放电作用,始终U33引脚3电压高于R142和R143分压值(3V3_IN/2),比较器输出低电平0V),则Q8处于关闭状态,C99两端为3.3V,U34比较器引脚1电压高于引脚3,则输出高电平3.3V,Motor_SD_B为3.3V,表示正常工作,SS8550是PNP管,过流指示灯也不亮。

过流保护过程:Motor_IU/V/W_ADC采集的电压高于R86和R87分压值2V84_IN,通过比较器U19/U20/U21输出低电平0V,通过R133、R134、C33器件RC充放电(触发时间与阻容值有关),使U33引脚3电压低于R142和R143分压值(3V3_IN/2)比较输出高电平3.3V,则Q8处于导通状态,U34引脚1电压迅速变为0V,输出低电平,Motor_SD_B为0V,表示电路处于过流状态,SS8550是PNP管,过流指示灯亮起。

过流保护恢复正常:Motor_IU/V/W_ADC采集的电压低于R86和R87分压值2V84_IN,通过比较器U19/U20/U21输出高电平3.3V,通过R133、R134、C33器件RC充放电(触发时间与阻容值有关),U33引脚3始终高于R142和R143分压值(3V3_IN/2)比较输出低电平0V,则Q8处于关闭状态,R145和C99阻容RC充电,充电时间长短反应到单次保护时间,当R145和C99阻容RC充电电压高于R146和R147分压,则U34比较器引脚1电压高于引脚3,则输出高电平3.3V,Motor_SD_B为3.3V,表示正常工作,SS8550是PNP管,过流指示灯也不亮。

R144起到触发电压缓冲作用:

正常时:U33引脚3处于高电平3.3V,U33输出0V,则U33分压值为:U=((R143//R144)*3.3V)/(R142+R143//R144)≈1.571V,触发保护电压为1.571V。

过流触发:当U33引脚3低于1.571V,则已触发保护,U33输出3.3V,则U33分压值为:U=(R143*3.3V)/(R142//R144+R143)≈1.729V,触发恢复电压为1.729V。

电压比较器

10.1.1. 编程要点

  1. 初始化ADC并进行数据的获取

  2. 编写函数对采集得到的数据进行处理

  3. 编写获取最终温度值、电压值、电流的函数

  4. 测试代码

10.1.2. 新建工程

我们直接在上一章节的的 “402_Motor_BLDC_Temp_Voltage_Collect” 例程的基础上修改程序。

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程 “402_Motor_BLDC_Temp_Voltage_Collect”, 然后将工程文件夹重命名为 “404_Motor_BLDC_Temp_Voltage_Current_Collect”,最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程 “402_Motor_BLDC_Temp_Voltage_Collect”, 然后将工程文件夹重命名为 “404_Motor_BLDC_Temp_Voltage_Current_Collect”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程文件结构如下。

文件结构
404_Motor_BLDC_Temp_Voltage_Current_Collect
├─ ......
└─ src
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ adc
   │  ├─ bsp_adc_vr.c
   │  └─ bsp_adc_vr.h
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ motor_control
   │  ├─ bsp_motor_control.c   //新建文件
   │  └─ bsp_motor_control.h   //新建文件
   ├─ motor_GPT
   │  ├─ bsp_motor_gpt.c       //新建文件
   │  └─ bsp_motor_gpt.h       //新建文件
   └─ hal_entry.c

10.1.3. FSP配置

下面以野火启明6T2开发板为例来讲解相关的 FSP 配置。

在上一节课,我们讲解AN001 以及 AN003 引脚用作电压和电流的采集引脚, 这一节课,我们主要是在此基础上,添加电流采集,电流采集引脚是AN006、 AN008、AN0010。下面我们来看一下发fsp的配置。

偏置表 偏置表

由于电流采集引脚所以的是ADC1,电压、温度是ADC0,所以我们把他们分成两个组来采集, 其他的设置都是一样的,上一章节讲过,这一章节就不继续讲解了。

10.1.4. ADC初始化函数

此函数主要是初始化ADC模块

ADC_Init()函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @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);
   assert(FSP_SUCCESS == err);

   err = R_ADC_B_ScanCfg(&g_adc_motor_ctrl, &g_adc_motor_scan_cfg);
   assert(FSP_SUCCESS == err);

   err = R_ADC_B_ScanGroupStart(&g_adc_motor_ctrl, ADC_GROUP_MASK_ALL);
   assert(FSP_SUCCESS == err);

}

10.1.5. 电流读取函数

这几个函数的主要作用是去读取电流值,并通过滤波算法去进行数据清洗

ADC_GPIO_Config()函数
 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
/**
* @brief 读取ADC电流值
*
* 该函数从ADC通道1读取电流电压值。
*
* @param[in] 无
* @return 返回读取到的电流电压值
* @note 此函数在调用前需确保ADC扫描已完成。
*/
double Read_ADC_Current_U_Value(void)
{
   uint16_t adc2_data;
   double c1;

   while (!scan_complete_flag)        //等待扫描完成
   {
      ;
   }

   R_ADC_B_Read(&g_adc_motor_ctrl,ADC_CHANNEL_6, &adc2_data);

   c1 = GET_ADC_VDC_VAL(adc2_data);//计算电流

   c1 = GET_ADC_CURR_VAL(c1) ;


   return c1;
}

/**
* @brief 电流偏置计算函数
* @param[in] 无
* @return
*/
void Calculate_Current_Offset(void)
{
   double u_sum = 0;
   double v_sum = 0;
   double w_sum = 0;

   // 采集多次电流值
   for (int i = 0; i < 100; i++)
   {
      u_sum += Read_ADC_Current_U_Value();
      v_sum += Read_ADC_Current_V_Value();
      w_sum += Read_ADC_Current_W_Value();
   }

   // 计算平均值作为偏置值
   u_current_offset = (double)u_sum / 100;
   v_current_offset = (double)v_sum / 100;
   w_current_offset = (double)w_sum / 100;

   printf("初始u偏置值为: %.2f\n", u_current_offset);
   printf("初始v偏置值为: %.2f\n", v_current_offset);
   printf("初始w偏置值为: %.2f\n", w_current_offset);
}


/**
* @brief  电流值处理函数
* @param  无
* @retval
*/
// 全局变量
double u_current_sum = 0.0;                         // 采样值的总和
uint16_t u_sample_count = 0;                        // 已采样的次数
void Deal_Current_U_Data(void)
{
   static double u_current_samples[FILTER_WINDOW_SIZE] = {0.0}; // 用于存储最近的100次采样值
   static uint16_t u_current_index = 0;                       // 当前采样值的索引

   // 读取当前电流值并加入移动平均滤波器
   double u_current_value = Read_ADC_Current_U_Value();
   u_current_sum -= u_current_samples[u_current_index]; // 减去旧值
   u_current_samples[u_current_index] = u_current_value; // 更新当前值
   u_current_sum += u_current_value; // 加上新值

   // 更新索引
   u_current_index = (uint16_t)((u_current_index + 1) % FILTER_WINDOW_SIZE);

   // 计算采样次数
   if (u_sample_count < FILTER_WINDOW_SIZE)
   {
      u_sample_count++;
   }
}

10.1.6. 定时打印函数

此函数1s打印一次电压、温度、电流数据。

定时打印
 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
void gpt0_timing_callback(timer_callback_args_t *p_args)
{
   // 定时器溢出事件
   if (TIMER_EVENT_CYCLE_END == p_args->event)
   {
      Time += 5;

      Deal_Current_U_Data();
      Deal_Current_V_Data();
      Deal_Current_W_Data();

      // 每秒计算一次平均值并打印
      if (Time == 100)
      {
            double u_current_avg = (u_current_sum / u_sample_count - u_current_offset);
            double v_current_avg = (v_current_sum / v_sample_count - v_current_offset);
            double w_current_avg = (w_current_sum / w_sample_count - w_current_offset);

            printf("V = %.2fV  Temp = %.1f  u = %.0fmA  v = %.0fmA  w = %.0fmA\r\n",Read_ADC_Voltage_Value(),get_ntc_t_val(),u_current_avg,v_current_avg,w_current_avg);

            // 重置时间
            Time = 0;
      }
   }
}

10.1.7. hal_entry入口函数

此函数可以通过接收串口命令去控制电机,在电机运行时观察数据的变化。

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
98
void hal_entry(void)
{
   /* TODO: add your own code here */
   /* LED初始化 */
   LED_Init();

   /* 串口初始化 */
   Debug_UART9_Init();

   adc_Init();

//    /* 电机初始化 */
   bldcm_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秒

   Calculate_Current_Offset();



   while(1)
   {
      if(uart_recv_motor_enable == true)
      {
         uart_recv_motor_enable = false;

         /* 使能电机 */
         ChannelPulse = 10;
         set_bldcm_speed(ChannelPulse);
         set_bldcm_enable();
         GPT_Timing_Init();      //启动定时器
      }
      if(uart_recv_motor_disenable == true)
      {
            uart_recv_motor_disenable = false;

            /* 停止电机 */
            set_bldcm_disable();
      }
      if(uart_recv_motor_speed_up == true)
      {
            uart_recv_motor_speed_up = false;

            static int is_run_flag;

            if(ChannelPulse==0)//占空比从零增加后 重新使能一次
            {
               is_run_flag=1;
            }

            /* 增大占空比 */
            ChannelPulse +=10;

            if(ChannelPulse >= 90)
               ChannelPulse = 100;

            set_bldcm_speed(ChannelPulse);

            if(is_run_flag==1)
            {
               set_bldcm_enable();
               is_run_flag=0;
            }
      }
      if(uart_recv_motor_speed_down == true)
      {
            uart_recv_motor_speed_down = false;

            /* 减小占空比 */
            if(ChannelPulse <= 10)
               ChannelPulse = 0;
            else
               ChannelPulse -= 10;

            set_bldcm_speed(ChannelPulse);
      }
      if(uart_recv_motor_reverse == true)
      {
            uart_recv_motor_reverse = false;

            Motor_Control_Reverse();
      }
   }


#if BSP_TZ_SECURE_BUILD
   /* Enter non-secure code */
   R_BSP_NonSecureEnter();
#endif
}

10.1.7.1. 下载验证

编译并下载程序后,复位开发板使程序重新运行,打开串口调试助手,连接好电机以及电机驱动板,启动电机,就可以看到开发板采集的电压值和电流值。

读取结果