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. 编程要点¶
初始化ADC并进行数据的获取
编写函数对采集得到的数据进行处理
编写获取最终温度值、电压值、电流的函数
测试代码
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模块
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. 电流读取函数¶
这几个函数的主要作用是去读取电流值,并通过滤波算法去进行数据清洗
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入口函数¶
此函数可以通过接收串口命令去控制电机,在电机运行时观察数据的变化。
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. 下载验证¶
编译并下载程序后,复位开发板使程序重新运行,打开串口调试助手,连接好电机以及电机驱动板,启动电机,就可以看到开发板采集的电压值和电流值。