CW32

一、滤波的概念

滤波是指通过某种方法将信号中的某些频率成分增强或抑制,达到去除噪声、改善信号质量、分离信号等目的的过程。滤波器是实现滤波功能的关键组件,它可以改变信号的频谱特性,对不同频率区域的信号进行处理。

二、常见的滤波算法

1.小波变换滤波

原理:小波变换通过将信号分解成不同尺度的子信号,可以提取出信号中的局部特征。在滤波中,小波变换可以用来去除信号中的噪声或者对信号进行平滑处理。

下面是该算法的波形图:

1.png

使用场景:

(1)图像去噪:小波变换可以有效地去除图像中的噪声,尤其是在多尺度分解中,可以针对不同频率的噪声进行过滤。

(2)特征提取:小波变换能够揭示图像或信号中的局部特征,因此在特征提取方面非常有用,比如在图像识别和分类中。

(3)边缘检测:小波变换在边缘检测方面表现出色,因为它能够捕捉到图像中的尖锐变化。

(4)信号去噪:在信号处理中,小波变换可以用于去除信号中的噪声,尤其是在非平稳信号中。

2.移动平均滤波

原理:首先确定一个包含一定数量样本点的窗口,这个窗口在信号上滑动。在每一个位置,计算窗口内所有样本点的平均值。将计算得到的平均值作为滤波后的输出。将窗口沿信号滑动一定步长,重复上述步骤。

我们有一个离散时间信号 x[n],并且我们使用长度为 M 的窗口进行移动平均滤波,那么滤波后的信号 y[n] 可以通过以下公式计算:则计算公式为:

y[n]=1/M∑k=n−M+1nx[k]

例如:一个包含噪声的信号序列x=[1,4,3,6,6,5,10,9,7,12].

我们可以使用移动平均滤波来平滑信号并减小噪声的影响。假设我们选择一个窗口大小为3,即每次计算三个样本的平均值。

第一个输出样本为:y[0]=(1/3)∗(1+4+3)=2.666

第二个输出样本为:y[1]=(1/3)∗(4+3+6)=4.33

以此类推,我们可以继续计算后续输出样本。

下面是使用这个算法生成的一个波形图

2.png

使用场景:

(1)在金融领域等需要对数据进行趋势分析的场景中,移动平均滤波可以平滑价格波动,帮助分析数据的长期趋势,预测未来走势。

(2)在实时数据处理或传感器数据处理中,移动平均滤波可以利用先前时刻的数据进行滤波处理,对新的数据进行平均,有助于消除数据中的噪声或异常值。

(3)移动平均滤波对周期性噪声和高频噪声的滤波效果比较好,可以很好地降低信号或数据中的高频成分,同时保留信号的基本特征。

3.中值滤波

原理:中值滤波会选取数字图像或数字序列中像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将这些像素值排序,然后将位于中间位置的像素值作为当前像素点的像素值,让周围的像素值接近真实值,从而消除孤立的噪声点。例如,针对下图中第4行第3列的像素点,计算它的中值滤波值。

3.png

将其邻域设置为3×3大小,对其3×3邻域内像素点的像素值进行排序(升序降序均可),按升序排序后得到序列值为:[16,35,46,52,52,62,64,83,85],在该序列中,处于中心位置(也叫中心点或中值点)的值是“52”,因此用该值替换原来的像素值 64,作为当前点的新像素值,新的像素图如下所示。

4.png

下面是使用这个算法对随机噪声滤波后生成的一个波形图:

5.png

使用场景:

(1)孤立噪声点平滑:中值滤波能有效去除图像或信号中的孤立噪声点,而不会对整体图像或信号的平滑区域造成影响。

(2)椒盐噪声去除:中值滤波对椒盐噪声(salt-and-pepper noise)具有很好的滤除效果,这种噪声表现为图像中随机分布的白色和黑色像素点。

(3)实时视频处理:由于中值滤波算法的计算复杂度相对较低,它可以用于实时视频流的噪声去除。

4.算术平均滤波

原理:算术平均滤波是图像处理中的一个常用技术,主要用于降低图像中的随机噪声。算术平均滤波器的基本思想是用像素点邻域内的平均灰度值来代替该像素点的灰度值。

算术平均滤波的计算公式如下:

f'(x, y) = 1/(mn) * ΣΣf(x+k, y+l)

其中,f'(x, y)是滤波后像素点(x, y)的灰度值,f(x+k, y+l)是原像素点(x, y)领域内的灰度值,ΣΣ表示对领域内所有像素点求和,m和n是滤波窗口的大小。

下面是使用这个算法对随机噪声滤波后生成的一个波形图:

6.png

算术平均滤波适用于一些简单的图像或信号处理场景,例如去除较为均匀且较弱的噪声。

5.卡尔曼滤波

原理:

(1)预测步骤(预测状态):通过系统的动态模型,根据已知的系统状态和控制量,预测系统的下一个状态以及状态的协方差。这一步骤得到的是对系统未来状态的预测。

(2)测量更新步骤(更新状态):利用传感器测量数据,根据预测的状态和测量数据的协方差,通过卡尔曼增益计算得到关于系统状态的修正估计。卡尔曼增益会根据预测和测量的不确定性来调整估计值,以平衡两者之间的权重。

下面是使用这个算法对随机噪声滤波后生成的一个波形图:

7.png

使用场景:卡尔曼滤波广泛应用于估计动态系统的状态,例如航天器导航、飞行器控制、传感器数据融合等领域。它具有高效、准确、稳定等优点,能够处理系统模型不确定性、传感器误差等问题,提高状态估计的精度和鲁棒性。

6.均值滤波

原理:连续采样N个数据,去掉一个最大值和一个最小值然后计算N-2个数据的算术平均值N值的选取

下面是使用这个算法对随机噪声滤波后生成的一个波形图:

8.png

使用场景:
(1)工业自动化:在工业自动化和控制系统中,均值滤波用于从传感器数据中去除噪声,提高系统的稳定性和可靠性。

(2)信号平滑:当信号受到高频干扰或随机噪声时,中值平均滤波法可以将突然的干扰降低,使信号变得更加平滑。

(3)环境监测:在环境监测中,均值滤波可以用于处理大气、水质等监测数据,去除测量误差和随机波动。

(4)数字图像处理:在数字图像处理中,中值平均滤波法常用于去除图像中的噪声,例如在数字摄影中去除低光照条件下的图像噪声。

7.快速傅里叶变换(FFT)滤波

原理:FFT滤波将时域信号通过FFT算法转换到频域,得到信号在频域上的频谱信息。在频域上对信号进行滤波处理,可以采用各种滤波器,如低通滤波器、高通滤波器、带通滤波器等,以抑制或增强特定频率成分。对经过滤波处理的频域信号进行逆FFT,将信号恢复回时域。

我使用python生成了一个频率为50 Hz的正弦波信号,并添加了高斯噪声。然后,使用numpy.fft.fft函数计算信号的FFT,并使用numpy.fft.fftfreq生成对应的频率向量。接下来,设计了一个简单的低通滤波器,只允许截止频率以下的频率成分通过。应用滤波器后,使用numpy.fft.ifft计算逆FFT,得到滤波后的信号。

下面是滤波的效果图:

9.png

使用场景:

(1)频域滤波:FFT允许将时域信号转换到频域,然后可以轻松地应用各种滤波器,如低通、高通、带通和带阻滤波器。

(2)信号分析:FFT常用于分析信号的频率成分,识别信号中的周期性成分或检测特定频率的信号。

(3)图像处理:在图像处理中,FFT可以用于频域滤波,如锐化、模糊、边缘检测等。

来源:CW32生态社区,,部分内容来源于网络,如有侵权请联系删除。

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 6

一、概述

CW32循迹、遥控小车具有循迹和遥控两种功能,小车的硬件模块由CW32F030C8T6小蓝板、智能小车控制底板、BT04-E 蓝牙模块、OLED屏幕、TB6612和红外循迹模块组成,电源采用可充电锂电池供电,建议不要使用 1.5V 干电池供电。

1716790334418305.jpg

图1 CW32小车

二、硬件部分

2.1、主控板

小车主控板由小蓝板和控制底板组成,小蓝板通过排母与控制底板相连,控制底板上还预留了按键等功能。主控板的原理图分别如下:

4.png

图2-1 小车控制底板原理图1

5.png

图2-2 小车控制底板原理图2

2.2、蓝牙模块

蓝牙模块采用 BT04-E 模块,为单独小板,通过排母插在小车主控板上:

1716790357484040.png
图2-3 BT04-E 模块

通过模块背面丝印可以确定与主控板的连接线序,主控板上为蓝牙预留的位置如下:

7.jpg

图2-4 主控板蓝牙位置

查找CW32F030的数据手册可知 PA2 和 PA3 为其串口2,调用串口2对其发送信息即可通过串口蓝牙助手接收对应的消息。

2.3、循迹模块

循迹模块通过排线与主控底板相连,参考原理图里的红外对管接口所对应的引脚。

1716790376476724.jpg

图2-5 循迹模块

循迹模块的工作原理:传感器的红外发射二极管不断发射红外线,当发射出的红外线没有被反射回来或被反射回来但强度不够大时,红外接收管一直处于关断状态,此时模块的 CH 端为高电平,通过比较器后输出为低电平,指示 LED 被点亮; 当被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和,此时模块的输出端为低电平,经过比较器后输出为高电平,LED 灯熄灭。

由于黑色会吸收红外线,所以总结为:检测到黑线--灯亮、输出低电平;未检测到黑线--灯灭、输出高电平。

9.png

图2-6 循迹模块原理图

2.4、TB6612芯片

TB6612是一款常用的双路直流电机驱动器芯片,常用于控制小型电动机或机器人的运动。该芯片具有高效、可靠和灵活的特点,适用于各种电气控制应用。

10.jpg

图2-7 TB6612 和控制底板

TB6612芯片具有以下主要特性:

  1. 双路驱动:TB6612可以控制两个直流电机的转动,支持正转、反转和停止功能。因此,它可以同时控制两个电机的运动,实现平稳的双轮驱动或其他双电机配置。
  2. 高电流输出:该芯片能够提供高达1.2A的持续输出电流,并且具有1.5A的瞬时峰值电流能力。这使得TB6612在控制较大功率电机时表现出色,适用于一些对功率要求较高的应用场景。
  3. 低功耗:TB6612在待机模式下的功耗非常低,可以有效延长电池寿命,适用于依赖电池供电的设备和机器人。
  4. 内置保护功能:芯片内部集成了过温保护、过电流保护和欠压锁定等保护功能,可以保护电机和芯片本身免受损坏或过载的风险。
  5. 灵活的控制接口:TB6612支持多种控制接口,包括PWM控制、频率锁定和直接控制模式等,可以根据具体需求选择合适的控制方式。

TB6612 可以控制两路电机,分别由 AIN1、AIN2、PWMA、BIN1、BIN2、PWMB组成,下面是AIN和BIN不同输入时控制电机的转动方向真值表。PWMA 和 PWMB 输入不同占空比的 PWM 波可以控制电机的转速快慢。

IN1IN2电机状态
00制动
01正转
10反转
11制动

三、软件部分

3.1、循迹模块检测判断

循迹模块检测,根据 4 个灯的亮灭情况共有 16 种状态,每种状态对应小车在黑线上的一种情况,根据不同的情况有不同的控制策略。

void IR_Check(void)
{  
    IR_Sensor[0] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_12);  //存放循迹模块输入值  
    IR_Sensor[1] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_13);  
    IR_Sensor[2] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_14);  
    IR_Sensor[3] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_15);
    
    /*********************************只有一个灯亮****************************/  
    if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1)      //略微偏离道路 偏左,需要右转  
    { Road_Error = 10; Flag_BaseSpeed = 10; }  
    else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //略微偏离道路 偏右,需要左转  
    { Road_Error = -10; Flag_BaseSpeed = 10; }  
    else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //较大偏离道路 偏左,需要右转  
    { Road_Error = 20; Flag_BaseSpeed = 20; }  
    else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //较大偏离道路 偏右,需要左转  
    { Road_Error = -20; Flag_BaseSpeed =20; } 
    
    /*********************************两个灯亮****************************/  
    else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //需要左转  
    { Road_Error = -40; Flag_BaseSpeed = 100; }  
    else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1) //直行  
    { Road_Error = 0; Flag_BaseSpeed = 0; }  
    else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //需要右转  
    { Road_Error = 40; Flag_BaseSpeed = 100; }  
    else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //直行  
    { Road_Error = 0; Flag_BaseSpeed = 0; }  
    else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1) //未偏离道路  
    { Road_Error = 0; Flag_BaseSpeed = 0; }  
    else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //保持之前的操作  
    { Road_Error = 0; Flag_BaseSpeed = 0; }  
    
    /*********************************三个灯亮****************************/  
    else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1) //需要左转  
    { Road_Error = -40; Flag_BaseSpeed = 100; }  
    else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //需要右转  
    { Road_Error = 20; Flag_BaseSpeed = 0; }  
    else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //需要左转  
    { Road_Error = -20; Flag_BaseSpeed = 0; }  
    else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //需要右转  
    { Road_Error = 40; Flag_BaseSpeed = 100; } 
    
     /*********************************零、四个灯亮****************************/  
     else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //没有检测到线,保持之前的操作  ;  
     else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //全是线,说明在十字路口,保持之前的操作  ;
}

3.2、PID计算控制

PID 计算控制根据红外循迹模块的亮灭情况,分别控制小车的基速和差速,从而控制小车运动的方向。

/** 
    * @brief       PID基速控制 
    * @param       Encoder:Flag_BaseSpeed ,Target:0 
    * @return      基速 PID 计算值 
*/

int PID_BaseSpeed(int Encoder,int Target)
{  
    float V_Base_Kp = 30,V_Base_Kd = 100; //Kp、Kd  
    static float Bias,PID,Last_Bias;      //本次偏差、PID计算值、上次偏差   
    
    Bias = Encoder - Target;              //计算本次偏差  
    PID = MAXOUTPUT - V_Base_Kp * Bias + V_Base_Kd * (Bias - Last_Bias); //PID计算  
    Last_Bias = Bias;                     //存储偏差  
    return PID;
}
/** 
    * @brief       PID差速控制 
    * @param       Encoder:Road_Error ,Target:0 
    * @return      差速 PID 计算值 
    */
    
int PID_DiffSpeed(int Encoder,int Target)
{  
    float V_Diff_Kp = 80,V_Diff_Ki = 0.08,V_Diff_Kd = 100;//Kp、Ki、Kd  
    static float Bias_D,PID_D,Integral_Bias,Last_Bias_D;  //本次偏差、PID计算值、积分累计值、上次偏差   
    
    Bias_D = Encoder - Target;  //计算本次偏差  
    Integral_Bias += Bias_D;    //积累偏差  
    PID_D = V_Diff_Kp * Bias_D + V_Diff_Ki * Integral_Bias + V_Diff_Kd * (Bias_D - Last_Bias_D);//PID计算  
    Last_Bias_D = Bias_D;       //存储偏差  
    return PID_D;
}
/** 
    * @brief       小车控制 
    * @param       无 
    * @return      无 
    */
void Car_Control(void)
{  
    OUTPUT_Left = PID_BaseSpeed(Flag_BaseSpeed,0) + PID_DiffSpeed(Road_Error,0);    //左轮占空比计算  
    OUTPUT_Right = PID_BaseSpeed(Flag_BaseSpeed,0) - PID_DiffSpeed(Road_Error,0);   //右轮占空比计算   
    
    if(OUTPUT_Left > MAXOUTPUT)OUTPUT_Left = MAXOUTPUT;    //限制大小  
    else if(OUTPUT_Left < 0)OUTPUT_Left = 0;  
    if(OUTPUT_Right > MAXOUTPUT)OUTPUT_Right = MAXOUTPUT;  
    else if(OUTPUT_Right < 0)OUTPUT_Right = 0;   
    
    GTIM_SetCompare3(CW_GTIM1,OUTPUT_Left);                //左轮  
    GTIM_SetCompare4(CW_GTIM1,OUTPUT_Right);               //右轮
}

3.3、遥控部分

遥控部分其实就是,通过串口蓝牙接收信息并向对应的方向运动,下面是蓝牙串口的中断服务程序:

// 串口2中断处理函数
void UART2_IRQHandler(void)
{  
    unsigned char TxRxBuffer; 
    if (USART_GetITStatus(CW_UART2, USART_IT_RC) != RESET)  
    {    
        USART_ClearITPendingBit(CW_UART2, USART_IT_RC); // 清除中断标志位    
        TxRxBuffer = USART_ReceiveData_8bit(CW_UART2);  // 将接收到的数据放入TxRxBuffer    
        USART2_RX_BUF[rx2Index] = TxRxBuffer; // 将接收到的数据放入缓冲区    
        if (rx2Index < USART2_REC_LEN - 1)    // 做数据长度的限制,留一个字节用于结束字符或者溢出检测    
        {      
            // 接收到的字符包含 \n 或者 \r 结束接收      
            if (USART2_RX_BUF[rx2Index - 1] == '\n' || USART2_RX_BUF[rx2Index - 1] == '\r')      
            {        
                USART2_RX_BUF[rx2Index] = '\0'; // 在最后一个字节加上空字符,表示字符串结束      
            }      
            else rx2Index++;    
        }     
        
        if(USART2_RX_BUF[0] == 't')Flag_Mode = 1 - Flag_Mode;  //发送字符 ‘t’来切换模式     
        
        if(Flag_Mode == 0)    
        {      
            if(USART2_RX_BUF[0] == '1')Flag_Start = 1;      
            else Flag_Start = 0;    
        }     
        
        else if(Flag_Mode == 1)    
        {      
            if(USART2_RX_BUF[0] == 'w')Flag_Direction = 1;     
            else if(USART2_RX_BUF[0] == 's')Flag_Direction = 2;      
            else if(USART2_RX_BUF[0] == 'a')Flag_Direction = 3;      
            else if(USART2_RX_BUF[0] == 'd')Flag_Direction = 4;      
            else Flag_Direction = 9;    
        }      
            rx2Index = 0; // 清除数据标志  
    }
}

四、调试

4.1、调试场地

调试场地对小车的要求包括直角、交叉点、弯道等,具体如下图所示:

11.jpg

图4-1 智能小车巡线赛道

4.2、调试提示

  • 小车运动主要由基速环和差速环的 PID 控制,可以先将差速环的 PID 参数整定下来,再调基速环的参数。
  • 循迹模块受到环境光的影响较大,最好在光线均匀和充足的环境下调试。
  • 电池电压同样会影响循迹模块的性能,建议不要使用 1.5V 干电池调试,而是使用锂电池。在电池接近没电时循迹模块不能正常工作。
  • 在弯道处如果小车不能及时转向,可以适当降低速度和增大差速环的 Kp 值。
  • 小车在交叉点处的循迹受到速度影响较大,较低的速度可能会使小车无法按照规定路线循迹,可以提高车速或者更换具有编码器的电机做轮式里程计来对该点做预判。

五、视频演示

例程链接:

https://pan.baidu.com/s/14KtIh6EcJYq_kQCItm9tTw?pwd=8mhq 

提取码:8mhq

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 28

2024年4月24日上午,武汉芯源半导体有限公司与上海科学技术职业学院共同举办的“CW32嵌入式创新实验室揭牌仪式”在上海科学技术职业学院第二会议室隆重举行。

1.png

活动现场,武汉芯源半导体与上海科技职业学院的领导及嘉宾齐聚一堂,共同见证了这一历史时刻。武汉芯源半导体北中区销售总监孙秀艳、CW32产业应用专家李家庆、CW32生态社区李工,以及上海科学技术职业学院教务处处长邵汝军、继续教育学院院长张华、通信与电子工程学院院长褚结等领导出席了揭牌仪式。

2.png

作为中国本土的MCU厂商,武汉芯源半导体始终坚持以创新驱动发展,专注32位MCU芯片设计,致力于提供本土化、工业级、高品质、低成本的集成电路产品。而上海科学技术职业学院作为培养高素质技术技能人才的摇篮,对嵌入式技术领域的教育和培训同样倾注了极大的心血。此次揭牌仪式的成功举办,体现了双方在推动嵌入式技术创新和人才培养上的共同理念和坚定决心,为培养集成电路创新型应用人才、推动国产芯片产业发展注入新动力。

3.png

此次合作,“CW32嵌入式创新实验室”将成为企业和学校之间的桥梁和纽带,双方也将对各自优势资源进行有机结合,为双方共同探索集成电路领域的前沿技术、推动产学研一体化进程提供有力支持。

未来,武汉芯源半导体将与更多高校进行合作,不断完善嵌入式创新实验室的建设和管理,为培养更多优秀的集成电路创新型应用人才、推动国产芯片产业的繁荣和发展做出贡献!

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 8

01、概述

空心杯电机(Hollow-Cup Motor)是一种特殊类型的微型无刷直流电机,具有空心的旋转部分。它通常由外部固定的外壳和内部旋转的空心杯组成。空心杯电机具有较高的功率密度和扭矩输出,适用于一些特定的应用场景,如精密仪器、机器人、医疗设备等。

空心杯电机的工作原理是基于无刷直流电机的原理。它采用无刷电机的结构,包括定子(固定部分)和转子(旋转部分)。定子包含一组永磁体,而转子则包含一组线圈。通过电流在线圈中的流动和永磁体之间的相互作用,产生电磁力,从而使转子旋转。

1.jpg

图1-1 空心杯电机结构

1.1空心杯电机的特点和优势

  • 空心结构:空心杯设计使得电机的旋转部分中心为空,可以通过空心轴传递其他信号、光线或气体,并且由于绕组无铁芯,转矩分布均匀。

  • 高功率密度:由于其紧凑的设计和高效的电机结构,空心杯电机具有较高的功率密度,可以在有限的空间内提供更大的扭矩输出。

  • 平滑运行:空心杯电机通常具有平滑的运行特性,可以提供稳定的转速和低噪音。

  • 高精度和可控性:空心杯电机的设计使得其具有较高的精度和可控性,适用于需要精确位置控制的应用。

  • 快速响应:由于其转动惯量小,空心杯电机能够快速响应控制信号,机械时间常数可以达到ms级,适用于需要高速动态响应的应用场景。

需要注意的是,空心杯电机由于结构紧凑的设计导致散热困难,并且其要实现高速和高精度的响应,因此空心杯电机的功率和扭矩都有一定的限制,需要根据具体工程问题选择合适的电机类型和配套的控制系统。

1.2应用场景

  • 机器人技术:空心杯电机广泛应用于机器人的关节驱动器中,能够提供高精度的运动控制和力矩输出。机器人的关节通常需要快速而准确地执行各种动作,而空心杯电机可以满足这些要求。

  • 自动化设备:在自动化设备中,如自动装配线、自动化仪器等,空心杯电机可用于驱动各种传送带、传送装置和旋转平台,以实现工件的快速、精确定位和搬运。

  • 医疗器械:空心杯电机在医疗器械中的应用广泛,例如手术机器人、医疗影像装置、药物输送系统等。这些应用需要高度精确的运动控制和定位,而空心杯电机能够提供稳定的力矩输出和高精度的位置控制。

  • 光学设备:在需要进行旋转、调焦、变焦等精密光学操作的设备中,如摄像机、望远镜、激光器等,空心杯电机可用于驱动相应的部件,实现精确的光路控制和图像稳定。

  • 回转平台:空心杯电机常被用于回转平台或转台,例如航天器的天线转动、摄影设备的平稳旋转等。通过空心杯电机的驱动,可以实现平稳、高速的旋转,并且减小了传动装置的尺寸和重量。

02、控制原理

2.1 霍尔传感器

霍尔传感器是一种基于霍尔效应原理的传感器,用于检测磁场的存在和变化。它通常由霍尔元件、信号调理电路和输出接口组成。霍尔元件是一种半导体材料,当其受到外部磁场的作用时,会产生一个电压信号。这个电压信号经过信号调理电路处理后,就可以输出给控制系统进行相应的处理。

霍尔传感器的工作原理基于霍尔效应,即当电流通过某些材料时,受到垂直于电流方向的磁场的影响,会在材料两侧产生一种电势差。这个电势差被称为霍尔电压,其大小与外部磁场的强度成正比。

霍尔传感器具有以下特点和优势:

  • 非接触式检测:霍尔传感器通过检测磁场,而无需与被检测物直接接触,从而避免了物理接触可能带来的摩擦和磨损。

  • 快速响应:由于霍尔传感器是基于半导体材料的电子器件,其响应速度非常快,可以实时检测和响应磁场变化。

  • 高精度:霍尔传感器能够提供精确的磁场测量和检测,可用于测量磁场的强度、方向和变化。

  • 宽工作温度范围:霍尔传感器具有较宽的工作温度范围,可以在高温或低温环境下正常工作。

  • 可靠性和耐用性:霍尔传感器不受机械磨损的影响,具有较长的使用寿命和可靠性。

  • 低功耗:霍尔传感器通常具有低功耗特性,适用于电池供电或对能源消耗敏感的应用。

在本次实验中,我们使用霍尔传感器对转子位置进行检测。通过将三个霍尔传感器相隔120°安装在电机定子的不同位置,即可根据霍尔传感器的电平信号确定电机转子的位置。下图是本次实验用到的霍尔真值表,理解真值表后对程序的编写有着重要作用:

2.png

图2-1 120°霍尔真值表

上图左为正转,右为反转。从上图可以看出,A、B、C三相霍尔传感器分别在空间上间隔120°放置,当转子的磁极运动到对应的霍尔传感器位置时,对应的相产生高电平,高电平的持续角度为180°(电角度,当电机极对数为1时也等于机械角度)。所以我们根据上面的真值表可以写出电机运行时的六种状态,以C相为高位:101、001、011、010、110、100;用十六进制的表示方式为:5、1、3、2、6、4,也就是说电机在正转时,霍尔传感器的信号只会按照513264的大小依次出现,在程序里读取对应霍尔引脚的电平状态即可判断此时电机转子的位置,这对于后续的方波控制尤为重要。

2.2 方波控制

方波控制是通过改变电机的输入电压信号来控制电机的转速和方向,这里的方波是指在电机运行过程中定子电流的波形近似方波。

3.png

图2-1 120°霍尔真值表

如果我们采用二二导通的方式,即同一时刻电机的绕组只有两相导通,本次实验的空心杯电机的内部为三角形连接,极对数为1。在一个电周期(360°)内,由上面提到的霍尔六种不同的状态来切换控制MOSFET的开通关断,使得定子电流也有六种状态,即定子绕组的合成磁动势有六种状态——所以,方波控制又被称为六步换相。

以上文的霍尔状态举例,当霍尔传感器传出信号为5时,控制VT1和VT6开通,其余关断,所以A相电流为正,B相电流为负;电机旋转至霍尔信号为1时,控制VT1和VT2开通,其余关断;以此类推,完整地经历过六个状态后,电机也就旋转完了一圈,再次进行上述步骤就可以使得电机连续运行。

那么电机为什么会这样运行呢,我们在这里用较为通俗的语言解释,如果读者有兴趣可以自行查阅相关资料。电机的定子通电后也具有磁性,根据“异性相吸”的原理,电机转子会向着通电的定子相运动直至二者“吸住”,如果在转子运动到对应“相吸”定子前的一瞬间,断掉该定子的供电而对顺着转子运动方向相隔120°的下一相定子供电,则转子又会与下一相定子“相吸”,如此往复即可使转子不断转动,上文的电路等效图对应相关定子相的供电。

4.png

图2-3 直流无刷电机定转子运动示意图

一个完整系统的方波控制步骤如下:

01)设置控制系统

确定控制系统的输入和输出接口,选择适当的控制器(如微控制器)和驱动电路。

02)确定转子位置

根据霍尔传感器信号的真值表,确定电机转子此时的位置。

03)确定换相顺序

根据转子的位置情况,确定电机定子的换相顺序,即图2-2中VT1-6的通断顺序。

04)控制电机转速

通过对MOS管VT输入PWM信号,该变占空比来控制平均电压的大小即可控制电机的转速。

05)控制电机方向

通过改变换相顺序的运行方向,可以控制电机的运动方向。

06)反馈控制(可选)

如果需要更精确的控制,可以使用更加灵敏的传感器,如编码器,来进一步监测电机的位置,在程序里使用对应算法(如PID)精确控制电机的位置和速度。

注意事项:

控制器的选择应考虑到方波控制的要求,如频率范围、引脚采样速度和分辨率等。驱动电路的设计应与电机的额定电压和电流匹配,并具备过流、过压等保护功能。在实际应用中,应注意电机的负载特性、惯性等因素对控制的影响,可能需要进行参数调整和系统优化。

04、CW32性能特点

本次实验采用的MCU为CW32F030C8T6,其性能特点如下:

  • 架构和处理能力:CW32F030C8T6采用了ARM Cortex-M0+处理器核心,具有高性能和低功耗的特点。Cortex-M0+是ARM架构中的一种32位处理器核心,适用于对功耗要求较高的应用场景。

  • 主频和存储器:CW32F030C8T6的主频可以高达48MHz,提供了较高的处理速度。它具有8KB的SRAM(静态随机存储器)和32KB的闪存(用于存储程序代码和数据),可用于存储应用程序和数据。

  • 低功耗特性:CW32F030C8T6在低功耗方面表现出色,具有多种省电模式和功耗管理功能,可实现对系统功耗的有效控制。这对于需要长时间运行的电池供电设备或对功耗敏感的应用非常重要。

  • 外设和接口:CW32F030C8T6提供了丰富的外设和接口,包括多个通用输入输出引脚(GPIO)、SPI(串行外设接口)、I2C(串行通信接口)、UART(通用异步收发器)等。这些接口可用于与外部传感器、存储器、通信模块等设备进行通信和连接。

  • 定时器和中断控制:CW32F030C8T6配备了多个定时器和中断控制功能,可用于实现精确的定时和事件触发。定时器可以用于生成精确的时间延迟、PWM(脉冲宽度调制)输出等应用,而中断控制则可以实现对外部事件的快速响应。

  • 安全性和保护机制:CW32F030C8T6提供了多种安全性和保护机制,包括存储器保护单元、访问控制等。这些机制可以帮助保护系统免受潜在的安全威胁和未授权访问。

本次实验我们使用了CW32的ATIM、GTIM、BTIM、ADC和DMA外设,下面分别简要介绍这五种外设。

3.1 高级定时器(ATIM)

高级定时器 (ATIM) 由一个 16 位的自动重载计数器和 7 个比较单元组成,并由一个可编程的预分频器驱动。ATIM 支持 6 个独立的捕获 / 比较通道,可实现 6 路独立 PWM 输出或 3 对互补 PWM 输出或对 6 路输入进行捕获。可 用于基本的定时 / 计数、测量输入信号的脉冲宽度和周期、产生输出波形(PWM、单脉冲、插入死区时间的互补 PWM 等)。在本次实验中,我们使用 ATIM 来产生PWM波驱动上桥。

5.png

图3-1 ATIM 功能框图

3.2 通用定时器(GTIM)

CW32F030 内部集成 4 个通用定时器 (GTIM),每个 GTIM 完全独立且功能完全相同,各包含一个 16bit 自动重装载计数器并由一个可编程预分频器驱动。GTIM 支持定时器模式、计数器模式、触发启动模式和门控模式 4 种基本工作模式,每组带 4 路独立的捕获 / 比较通道,可以测量输入信号的脉冲宽度(输入捕获)或者产生输出波形(输出比较和 PWM)。本次实验使用 GTIM 的输入捕获功能来触发获取霍尔传感器的数据。

6.png

图3-2 GTIM功能框图

3.3 基本定时器(BTIM)

CW32F030 内部集成 3 个基本定时器 (BTIM),每个 BTIM 完全独立且功能完全相同,各包含一个 16bit 自动重装载计数器并由一个可编程预分频器驱动。BTIM 支持定时器模式、计数器模式、触发启动模式和门控模式 4 种工作模式,支持溢出事件触发中断请求和 DMA 请求。得益于对触发信号的精细处理设计,使得 BTIM 可以由硬件自动执行触发信号的滤波操作,还能令触发事件产生中断和 DMA 请求。本次实验使用BTIM的定时器中断功能,10ms进入一次定时器中断,在中断中修改相关功能的标志位,在主函数的 while 循环里根据标志位判断相关功能本次是否执行。

7.png

图3-3 BTIM功能框图

3.4 模数转换器(ADC)

CW32F030 内部集成一个 12 位精度、最高 1M SPS 转换速度的逐次逼近型模数转换器 (SAR ADC),最多可将 16 路模拟信号转换为数字信号。现实世界中的绝大多数信号都是模拟量,如光、电、声、图像信号等,都要由 ADC 转换成数字信号,才能由 MCU 进行数字化处理。本次实验使用 ADC 采集电位器的电压值,根据电位器的电压大小控制目标值的设定。

8.png

图3-4 ADC 功能框图

3.5 直接内存访问(DMA)

CW32F030 支持直接内存访问(DMA),无需 CPU 干预,即可实现外设和存储器之间、外设和外设之间、存储器和存储器之间的高速数据传输。DMA 控制器内部的优先级仲裁器,可实现 DMA 和 CPU 对外设总线控制权的仲裁,以及多 DMA 通道之间的调度执行。本次实验使用 DMA 将 ADC 采集的数据写入内存,DMA 传输由 ADC 转换完成信号触发。

9.png

图3-5 DMA 功能框图

04、实验设备

4.1 CW32-BLDC电机驱动板

本次实验我们使用的无刷电机驱动板为CW32_BLDC_EVA V5开发板,其配置如下:

10.png

图4-1 CW32_BLCD_EVA 评估板资源配置图

4.2 空心杯电机与连接

本次实验使用的空心杯电机如下图:

11.jpg

图4-2 空心杯电机实物图

连接示意图如下:

12.jpg

图4-3 电机与驱动板连接示意图

下面展示电机驱动板的原理图:

13.png

图4-4 电机驱动板原理图1

14.png

图4-5 电机驱动板原理图2

15.png

图4-6 电机驱动板原理图3

05、程序编写

5.1 有霍尔方波开环控制程序

下面会将控制程序按照不同的功能模块向读者展示。

首先是与霍尔传感器相关的模块,存放在HALL.c文件中,先展示HALL.h文件的内容:

#ifndef _HALL_H_
#define _HALL_H_
#include "cw32f030_rcc.h"
#include "cw32f030_gpio.h"
#include "cw32f030_gtim.h"
#define HALLA_PORT         (CW_GPIOA)
#define HALLB_PORT         (CW_GPIOB)
#define HALLC_PORT         (CW_GPIOA)
#define HALLA_PIN          (GPIO_PIN_15)
#define HALLB_PIN          (GPIO_PIN_3)
#define HALLC_PIN          (GPIO_PIN_2)

extern void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag);
extern void GTIM2_IRQHandler(void); 

void HALL_Init(void);
unsigned char  HALL_Check(void);
#endif

HALL.c文件

#include "HALL.h"uint8_t ErrorCode;                                           //电机运行错误代码
extern uint8_t Motor_Start_F;                                //电机启动运行标志
extern uint8_t Cur_Step;                                     //当前HALL状态
extern uint8_t Direction;                                    //电机方向,0为正转,1为反转
const uint8_t STEP_TAB[2][6] = {{4,0,5,2,3,1},{1,3,2,5,0,4}};//电机换相序号
extern uint32_t HALLcount;                                   //霍尔脉冲计数
extern uint32_t OutPwm;                                      //输出PWM值
//初始化霍尔传感器要用到的GPIO和定时器
void HALL_Init(void)
{  
    __RCC_GTIM2_CLK_ENABLE();                    //先打开对应时钟  
    __RCC_GPIOA_CLK_ENABLE();  
    __RCC_GPIOB_CLK_ENABLE();   
    
    GPIO_InitTypeDef GPIO_InitStruct;            //再配置对应接口  
    GPIO_InitStruct.IT = GPIO_IT_NONE;  
    GPIO_InitStruct.Mode =GPIO_MODE_INPUT_PULLUP;//霍尔输入配置;  
    GPIO_InitStruct.Pins = HALLA_PIN | HALLC_PIN;//PA15和PA2  
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;  
    GPIO_Init(HALLA_PORT, &GPIO_InitStruct);   
    
    GPIO_InitStruct.IT = GPIO_IT_NONE;  
    GPIO_InitStruct.Mode =GPIO_MODE_INPUT_PULLUP;// 霍尔输入配置;  
    GPIO_InitStruct.Pins = HALLB_PIN;            //PB3  
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;  
    GPIO_Init(HALLB_PORT, &GPIO_InitStruct);   
    
    PA15_AFx_GTIM2CH1();                         //GTIM2CH1();  
    PB03_AFx_GTIM2CH2();                         //GTIM2CH2();  
    PA02_AFx_GTIM2CH3();                         //GTIM2CH3();   
    
    __disable_irq();   
    NVIC_EnableIRQ(GTIM2_IRQn);                  //配置GTIM2输入捕获中断  
    __enable_irq();   GTIM_InitTypeDef GTIM_InitStruct;            //这里使用GTIM2的输入捕获功能  
    
    GTIM_ICInitTypeDef GTIM_ICInitStruct;   
    GTIM_InitStruct.Mode = GTIM_MODE_TIME;  
    
    GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;  
    GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV1;  
    GTIM_InitStruct.ReloadValue = 0xFFFF;  
    GTIM_InitStruct.ToggleOutState = DISABLE;  
    GTIM_TimeBaseInit(CW_GTIM2, &GTIM_InitStruct);  
    GTIM_ICInitStruct.CHx = GTIM_CHANNEL1;       //GTIM2捕获通道配置  
    GTIM_ICInitStruct.ICFilter = GTIM_CHx_FILTER_PCLK_N2;  
    GTIM_ICInitStruct.ICInvert = GTIM_CHx_INVERT_OFF;  
    GTIM_ICInitStruct.ICPolarity = GTIM_ICPolarity_BothEdge;  
    GTIM_ICInit(CW_GTIM2, &GTIM_ICInitStruct);  
    GTIM_ICInitStruct.CHx = GTIM_CHANNEL2;  
    GTIM_ICInit(CW_GTIM2, &GTIM_ICInitStruct); 
      
    GTIM_ICInitStruct.CHx = GTIM_CHANNEL3;  
    GTIM_ICInit(CW_GTIM2, &GTIM_ICInitStruct);   
    
    GTIM_ITConfig(CW_GTIM2, GTIM_IT_CC1 | GTIM_IT_CC2 | GTIM_IT_CC3, ENABLE);  
    GTIM_Cmd(CW_GTIM2, ENABLE);
}
unsigned char  HALL_Check(void)                 //读取霍尔状态,确定换相顺序
{  
    static unsigned char hallerrnum=0;   
    unsigned char Hall_State=0;   
    
    if(PA15_GETVALUE()!=0)Hall_State=0x1;         //对每个引脚状态分别判断,所以三个if而不是else if  
    if(PB03_GETVALUE()!=0)Hall_State|=0x2;        //或运算 010  
    if(PA02_GETVALUE()!=0)Hall_State|=0x4;        //或运算 100  
    if(Hall_State==0||Hall_State==7)              //000或者111都是异常状态    
    {      
        hallerrnum++;      
        if(hallerrnum>=10)      
        {
            hallerrnum=10;
            ErrorCode=2;
        }              //持续异常状态说明霍尔传感器有问题    
    }  
    else hallerrnum=0;  
    return Hall_State;
}
void GTIM2_IRQHandler(void)   //在GTIM2的中断服务程序里对霍尔脉冲计数、霍尔状态确定、换相确定
{          
    uint32_t Hall_State;         
    /* USER CODE BEGIN */  
    if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC1))        //捕获输入变化就产生中断标志  
    {    
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC1);    //清除中断标志  
    }  
    else if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC2))  
    {        
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC2);  
    }  
     
    else if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC3))  
    {        
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC3);  
    }   
    
    HALLcount++;                                       //霍尔脉冲计数           
    Hall_State=HALL_Check();                           //读取霍尔状态   
    Cur_Step=STEP_TAB[Direction][Hall_State-1];        //获取换相序位,例如霍尔变化为513264,则Cur_Step变化为345012   
    if(Motor_Start_F==1&&ErrorCode==0)                 //根据启停状态 换相     
     Commutation(Cur_Step,OutPwm,Motor_Start_F);             
    /* USER CODE END */
}

与电机相关的BLDC模块:

BLDC.h

#include "main.h"
/***********************                PWM        definition   *************************/
#define PWM_HN_PORT                 (CW_GPIOA)      //上管引脚
#define PWM_LN_PORT                 (CW_GPIOB)      //下管引脚
#define PWM_AH_PIN                  (GPIO_PIN_8)
#define PWM_BH_PIN                  (GPIO_PIN_9)
#define PWM_CH_PIN                  (GPIO_PIN_10)
#define PWM_AL_PIN                  (GPIO_PIN_13)
#define PWM_BL_PIN                  (GPIO_PIN_14)
#define PWM_CL_PIN                  (GPIO_PIN_15)
//上管PWM调制控制,下管GPIO开关控制, 上管高电平开关管导通,下管反相
#define PWM_AL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_AL_PIN,GPIO_Pin_SET)
#define PWM_BL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_BL_PIN,GPIO_Pin_SET)
#define PWM_CL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_CL_PIN,GPIO_Pin_SET)
#define PWM_AL_ON GPIO_WritePin(PWM_LN_PORT,PWM_AL_PIN,GPIO_Pin_RESET)
#define PWM_BL_ON GPIO_WritePin(PWM_LN_PORT,PWM_BL_PIN,GPIO_Pin_RESET)
#define PWM_CL_ON GPIO_WritePin(PWM_LN_PORT,PWM_CL_PIN,GPIO_Pin_RESET)
#define PWM_FRQ                        (20000)       //PWM频率(HZ)
#define PWM_TS                         3200//20K 

#define OUTMAXPWM  PWM_TS*0.25
#define OUTMINPWM  PWM_TS*0.005 
void BLDC_Init(void);
void BLDC_Motor_Start(uint8_t Dir);
void BLDC_Motor_Stop(void);
void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag);
void UPPWM(void);         //更新PWM占空比
/////////////////////////

BLDC.c

#include "BLDC.h"
extern const uint8_t STEP_TAB[2][6];//电机换相序号
uint8_t Cur_Step;                   //当前HALL状态
uint8_t STEP_last;                  //上次HALL状态
extern uint8_t Direction;           //电机方向,0为正转,1为反转
extern uint8_t Motor_Start_F;       //电机启动运行标志
uint32_t OutPwm;                    //PWM占空比//初始化电机要用到的GPIO和定时器,上桥为PWM,下桥为引脚电平控制
void BLDC_Init(void)
{  
    __RCC_ATIM_CLK_ENABLE();            
    __RCC_GPIOA_CLK_ENABLE();  
    __RCC_GPIOB_CLK_ENABLE();   
    
    //初始化下管GPIO  
    GPIO_InitTypeDef GPIO_InitStruct;  
    GPIO_InitStruct.IT = GPIO_IT_NONE;  
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  G
    PIO_InitStruct.Pins = PWM_AL_PIN | PWM_BL_PIN | PWM_CL_PIN;  
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;  
    GPIO_Init(PWM_LN_PORT,&GPIO_InitStruct);  
    
    //初始化上管
    GPIO  GPIO_InitStruct.Pins = PWM_AH_PIN | PWM_BH_PIN | PWM_CH_PIN;  
    GPIO_Init(PWM_HN_PORT,&GPIO_InitStruct);  
     
    PWM_AL_OFF; PWM_BL_OFF;PWM_CL_OFF;   //初始化先关闭下管   
    
    //初始化ATIM的PWM通道  
    ATIM_InitTypeDef ATIM_InitStruct;  
    ATIM_OCInitTypeDef ATIM_OCInitStruct;   
    
    PA08_AFx_ATIMCH1A();               //上管ABC三相  
    PA09_AFx_ATIMCH2A();  
    PA10_AFx_ATIMCH3A();   
    
    ATIM_InitStruct.BufferState = DISABLE;  
    ATIM_InitStruct.ClockSelect = ATIM_CLOCK_PCLK;  
    ATIM_InitStruct.CounterAlignedMode = ATIM_COUNT_MODE_EDGE_ALIGN;  
    ATIM_InitStruct.CounterDirection = ATIM_COUNTING_UP;  
    ATIM_InitStruct.CounterOPMode = ATIM_OP_MODE_REPETITIVE;  
    ATIM_InitStruct.OverFlowMask = DISABLE;  
    ATIM_InitStruct.Prescaler = ATIM_Prescaler_DIV1;    // 计算时钟1MHz  
    ATIM_InitStruct.ReloadValue = PWM_TS - 1;           // 20K   
    ATIM_InitStruct.RepetitionCounter = 0;  
    ATIM_InitStruct.UnderFlowMask = DISABLE;  
    ATIM_Init(&ATIM_InitStruct);  
    //初始化PWM通道  
    ATIM_OCInitStruct.BufferState = DISABLE;  
    ATIM_OCInitStruct.OCDMAState = DISABLE;  
    ATIM_OCInitStruct.OCInterruptSelect = ATIM_OC_IT_UP_COUNTER;  
    ATIM_OCInitStruct.OCInterruptState = ENABLE;  
    ATIM_OCInitStruct.OCMode = ATIM_OCMODE_PWM1;  
    ATIM_OCInitStruct.OCPolarity = ATIM_OCPOLARITY_NONINVERT;  
    ATIM_OC1AInit(&ATIM_OCInitStruct);  
    ATIM_OC2AInit(&ATIM_OCInitStruct);  
    ATIM_OC3AInit(&ATIM_OCInitStruct);  
    ATIM_SetCompare1A(0);        //初始化先关闭上管  
    ATIM_SetCompare2A(0);  
    ATIM_SetCompare3A(0);  
    ATIM_PWMOutputConfig(OCREFA_TYPE_SINGLE, OUTPUT_TYPE_COMP, 0);  
    ATIM_CtrlPWMOutputs(ENABLE);  
    ATIM_Cmd(ENABLE);}void ATIM_IRQHandler(void)
    {  
        if (ATIM_GetITStatus(ATIM_IT_OVF))  
        {    
            ATIM_ClearITPendingBit(ATIM_IT_OVF);                  
        }
    }

//step,为当前换相序号,OutPwmValue 输出PWM值,PWM_ON_flag=1时启动PWM输出
void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag)
{ 
    if(PWM_ON_flag==0) //不启动则关闭输出   
    {     
        CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0;            
        ATIM_CtrlPWMOutputs(DISABLE);     
        PWM_AL_OFF;PWM_BL_OFF;PWM_CL_OFF;     
        return;   
    }     
    PWM_AL_OFF;PWM_BL_OFF;PWM_CL_OFF;           //先关闭输出,避免意外     
    //输出上桥     
    if(step==0||step==1){   CW_ATIM->CH1CCRA=OutPwmValue;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0;        } //0:AB; 1:AC    
    if(step==2||step==3){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=OutPwmValue;CW_ATIM->CH3CCRA=0;        } //2:BC; 3:BA     
    if(step==4||step==5){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=OutPwmValue;        } //4:CA; 5:CB      
    
    //输出下桥     
    if(step==0||step==5){PWM_AL_OFF;PWM_CL_OFF;PWM_BL_ON;}       //AB CB ; B下桥导通     
    else if(step==1||step==2){PWM_AL_OFF;PWM_BL_OFF;PWM_CL_ON;}//AC BC; C下桥导通     
    else if(step==3||step==4){PWM_BL_OFF;PWM_CL_OFF;PWM_AL_ON;}//BA CA; A下桥导通      
    
    ATIM_CtrlPWMOutputs(ENABLE);         //输出有效     
    STEP_last = step;
}
void UPPWM(void)         //更新PWM占空比
{          
    if(STEP_last==0||STEP_last==1){         CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0; CW_ATIM->CH1CCRA=OutPwm;        }  
    if(STEP_last==2||STEP_last==3){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH3CCRA=0;CW_ATIM->CH2CCRA=OutPwm;        }  
    if(STEP_last==4||STEP_last==5){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=OutPwm;        }
}
void BLDC_Motor_Start(uint8_t Dir)  //启动电机
{           
    uint32_t x;     
    
    x=HALL_Check();  
    if(x==0||x==7) {x=1;}           //如果霍尔异常,输出一项,使电机先转起来  
    Cur_Step=STEP_TAB[Direction][x-1];  
    Motor_Start_F = 1;  
    OutPwm = OUTMINPWM;  
    Commutation(Cur_Step,OutPwm,Motor_Start_F);
}
void BLDC_Motor_Stop(void)         //停止电机
{  
    Motor_Start_F = 0;  
    Commutation(Cur_Step,OutPwm,Motor_Start_F);;
}

与测速(BTIM1)相关的文件:Speed_Measure.h

#ifndef _SPEED_MEASURE_H_
#define _SPEED_MEASURE_H_
#include "cw32f030_btim.h"
#include "cw32f030_rcc.h"
void Speed_Measure_Init(void);
#endif

Speed_Measure.c

#include "Speed_Measure.h"
extern uint32_t HALLcount;           //霍尔脉冲计数
extern uint16_t ADC_TimeCount;       //电位器ADC采样计算计数
extern uint16_t Hall_TimeCount;      //计数,进了2次BTIM1中断,即20ms对转速计算一次
extern uint16_t OLED_FRESH_TimeCount;//计数,500ms刷新一次OLED显示
void Speed_Measure_Init(void)        //BTIM1 10ms进一次中断,在中断里改变标志位           
{  
    __RCC_BTIM_CLK_ENABLE();  
    __disable_irq();   
    NVIC_EnableIRQ(BTIM1_IRQn);   
    __enable_irq();   
    
    BTIM_TimeBaseInitTypeDef BTIM_InitStruct;  
    BTIM_InitStruct.BTIM_Mode = BTIM_Mode_TIMER;  
    BTIM_InitStruct.BTIM_OPMode = BTIM_OPMode_Repetitive;  
    BTIM_InitStruct.BTIM_Prescaler = BTIM_PRS_DIV64;  
    BTIM_InitStruct.BTIM_Period = 10000;             
    BTIM_TimeBaseInit(CW_BTIM1, &BTIM_InitStruct);  
    BTIM_ITConfig(CW_BTIM1, BTIM_IT_OV, ENABLE);  
    BTIM_Cmd(CW_BTIM1, ENABLE);
}        
void BTIM1_IRQHandler(void)
{  
    /* USER CODE BEGIN */  
    if(BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))  
    {      
        BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);    
        Hall_TimeCount++;        //计数,进了2次BTIM1中断,即20ms对转速计算一次    
        ADC_TimeCount++;         //计数,100ms检查一次电位器的电压大小,确定目标速度    
        OLED_FRESH_TimeCount++;  //计数,500ms刷新一次OLED显示  
    }  
    /* USER CODE END */
}

与电位器输入有关的文件:ADC_BLDC_Ctrl.h

#ifndef _ADC_BLDC_CTRL_H_
#define _ADC_BLDC_CTRL_H_
#include "cw32f030_rcc.h"
#include "cw32f030_gpio.h"
#include "cw32f030_adc.h"
#include "cw32f030_dma.h"
void ADC_Configuration(void);
void ADC_DMA_Trans(void);
uint32_t ADC_SampleTarget(void);
#endif

ADC_BLDC_Ctrl.c

#include "ADC_BLDC_Ctrl.h"
uint32_t ADC_Result_Array;//ADC采集电位器的值,使用了DMA传输
void ADC_Configuration(void)
{  
    RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_DMA | RCC_AHB_PERIPH_GPIOB, ENABLE);  //开启DMA和ADC使用GPIO引脚的时钟  
    RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_ADC, ENABLE);    //开启ADC时钟  
    PB00_ANALOG_ENABLE();  //配置ADC测试IO口  电位器接口   
    
    //ADC初始化  
    ADC_InitTypeDef   ADC_InitStruct;           
    ADC_InitStruct.ADC_OpMode = ADC_SingleChContinuousMode;   
    ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div8;          //PCLK 8MHz  
    ADC_InitStruct.ADC_SampleTime = ADC_SampTime10Clk; //10个ADC时钟周期  
    ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;        //外部3.3V参考电压  
    ADC_InitStruct.ADC_InBufEn = ADC_BufDisable;       //开启跟随器  
    ADC_InitStruct.ADC_TsEn = ADC_TsDisable;           //内置温度传感器禁用  
    ADC_InitStruct.ADC_DMAEn = ADC_DmaEnable;          //ADC转换完成触发DMA传输  
    ADC_InitStruct.ADC_Align = ADC_AlignRight;         //ADC转换结果右对齐  
    ADC_InitStruct.ADC_AccEn = ADC_AccDisable;         //转换结果累加不使能  
    ADC_Init(&ADC_InitStruct);                         //初始化ADC配置  
    CW_ADC->CR1_f.DISCARD = FALSE;                     //配置数据更新策略,覆盖未被读取的旧数据,保留新数据  
    CW_ADC->CR1_f.CHMUX = ADC_ExInputCH8;              //配置ADC输入通道   
    
    //ADC使能  
    ADC_Enable();      
    ADC_SoftwareStartConvCmd(ENABLE);   
    
    //配置DMA  
    DMA_InitTypeDef   DMA_InitStruct;  
    DMA_StructInit( &DMA_InitStruct );  
    DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;        //该模式在传输过程中会被更高级的响应打断  
    DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT;//传输32位  
    DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix;  //源地址增量方式固定  
    DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix;  //目的地址增量方式固定  
    DMA_InitStruct.DMA_TransferCnt =60000;             
    DMA_InitStruct.DMA_SrcAddress = (uint32_t) &(CW_ADC->RESULT0);//(0x00000020) RESULT0  
    DMA_InitStruct.DMA_DstAddress = (uint32_t)&ADC_Result_Array;  
    DMA_InitStruct.TrigMode = DMA_HardTrig;          //硬件触发  
    DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE; //ADC采集完成触发  
    DMA_Init(CW_DMACHANNEL3,&DMA_InitStruct);  
    DMA_ClearITPendingBit(DMA_IT_ALL);  
    DMA_ITConfig(CW_DMACHANNEL3, DMA_IT_TC|DMA_IT_TE , ENABLE);     //使能DMA_CHANNEL3中断  
    DMA_Cmd(CW_DMACHANNEL3, ENABLE);                 //使能DMA
}
void ADC_DMA_Trans(void)
{  
    if (CW_DMA->ISR_f.TC3)  
    { //AD DMA 启动   
      CW_DMA->ICR_f.TC3 = 0;            
      CW_DMACHANNEL3->CNT=bv16|60000;         //MUST RET AGAIN BEFORE 
      CW_DMACHANNEL1->CNT=0    CW_DMACHANNEL3->CSR_f.EN = 1;                   
    }
}
uint32_t ADC_SampleTarget(void)            //采集电压
{  
    uint32_t Target = 0;   
    
    if(ADC_Result_Array >= 4000)Target = 4000;//限制大小,12位ADC采集值为:0-4096  
    else if(ADC_Result_Array < 3)Target = 0;  
    else Target = ADC_Result_Array;  
    return Target;
}

与显示有关的驱动函数由于篇幅原因不在此展示,下面展示main.c的内容:

#include "main.h"
uint8_t Direction;                //电机方向,0为正转,1为反转
uint8_t Motor_Start_F=0;          //电机启动运行标志
uint16_t ADC_TimeCount=0;         //电位器ADC采样计时计数
uint16_t Hall_TimeCount=0;        //霍尔计时计数
uint16_t OLED_FRESH_TimeCount=0;  //OLED刷新显示计时计数
uint32_t HALLcount=0;             //霍尔脉冲计数
uint32_t Motor_Speed = 0;         //电机实际转速,rpm 
extern uint32_t OutPwm;
char Buffer1[48],Buffer2[48];
uint32_t Pwm_Buffer;
int main()
{  
    RCC_Configuration();            //时钟树初始化  
    I2C_init();                                        //OLED初始化  
    I2C_OLED_Init();                //I2C初始化  
    BLDC_Init();                    //电机初始化  
    HALL_Init();                    //霍尔传感器初始化  
    Speed_Measure_Init();           //BTIM1初始化  
    ADC_Configuration();            //ADC初始化  
    I2C_OLED_Clear(1);      
            //清屏   
    Direction = 1;     //电机方向   
    
    sprintf(Buffer1,"Speed:%d rpm   ",Motor_Speed); //显示电机转速  
    sprintf(Buffer2,"PWM:%d %%   ",Pwm_Buffer);     //显示PWM占空比  
    I2C_OLED_ShowString(0,0,Buffer1);          
    I2C_OLED_ShowString(0,15,Buffer2);  
    I2C_OLED_UPdata();    
    
    while(1)  
    {    
        ADC_DMA_Trans();          //DMA传输完毕则允许下一次传输     
        
        if(ADC_TimeCount > 10)    //100ms检查一次目标速度    
        {      
            ADC_TimeCount = 0;      
            OutPwm = ADC_SampleTarget() / 5;                   //设置占空比      
            if(OutPwm > 0 && Motor_Start_F == 0)BLDC_Motor_Start(Direction);//转速大于1000rpm才启动电机      
            else if(OutPwm > 0 && Motor_Start_F == 1)UPPWM();  //更新占空比      
            else BLDC_Motor_Stop(); //停止电机    
        }     
        if(Hall_TimeCount > 1)   //20ms测一次速    
        {      
            Hall_TimeCount = 0;      
            Motor_Speed = HALLcount * 500 / MotorPoles;  //转速计算,rpm      
            HALLcount = 0;    
        }     
        
        if(OLED_FRESH_TimeCount > 50)  //500ms OLED显示刷新一次    
        {      
            OLED_FRESH_TimeCount = 0;      
            sprintf(Buffer1,"Speed:%d rpm   ",Motor_Speed);    //显示电机转速      
            I2C_OLED_ShowString(0,0,Buffer1);       
            
            Pwm_Buffer = OutPwm/32;     //最大25%对应OutPwm的值:800      
            sprintf(Buffer2,"PWM:%d %%   ",Pwm_Buffer);        //显示占空比      
            I2C_OLED_ShowString(0,15,Buffer2);      
            I2C_OLED_UPdata();    
        }  
    }
}
void RCC_Configuration(void)
{  
    RCC_HSI_Enable(RCC_HSIOSC_DIV6);  
    /* 1. 设置HCLK和PCLK的分频系数 */  
    RCC_HCLKPRS_Config(RCC_HCLK_DIV1);  
    RCC_PCLKPRS_Config(RCC_PCLK_DIV1);  
    /* 2. 使能PLL,通过HSI倍频到64MHz */  
    RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8);       
    // PLL输出频率64MHz  
    /*< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle  
    < 当使用的时钟源HCLK大于48M,小于等于72MHz:设置FLASH 读等待周期为3 cycle */      
    __RCC_FLASH_CLK_ENABLE();  
    FLASH_SetLatency(FLASH_Latency_3);  
    /* 3. 时钟切换到PLL */  
    RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);  
    RCC_SystemCoreClockUpdate(64000000);
}

最终的实验结果如下:

16.jpg

图5-1 有霍尔方波开环控制无刷直流空心杯电机

5.2 有霍尔方波闭环控制程序

闭环程序与开环程序相比,分别在main.c、Speed_Measure.c、ADC_BLDC_Ctrl.c文件中略有变化,同时新增了PID.c文件用于控制。

首先是main.c文件中的变化,新增了变量Flag_PID_TimeCount、Target_Speed,函数修改如下:

int main()
{  
    RCC_Configuration();            //时钟树初始化  
    I2C_init();                     //OLED初始化  
    I2C_OLED_Init();                //I2C初始化  
    BLDC_Init();                    //电机初始化  
    HALL_Init();                    //霍尔传感器初始化  
    Speed_Measure_Init();           //BTIM1初始化  
    PID_Init();                     //PID初始化  
    ADC_Configuration();            //ADC初始化  
    I2C_OLED_Clear(1);              //清屏  
     
    Direction = 1;     //电机方向   
    
    sprintf(Buffer1,"Target:%d rpm",Target_Speed);  //显示目标速度  
    sprintf(Buffer2,"Speed:%d rpm   ",Motor_Speed); //显示电机转速  
    I2C_OLED_ShowString(0,0,Buffer1);  
    I2C_OLED_ShowString(0,15,Buffer2);          
    I2C_OLED_UPdata();    
    while(1)  
    {    
        ADC_DMA_Trans();          //DMA传输完毕则允许下一次传输    
        if(ADC_TimeCount > 10)    //100ms检查一次目标速度    
        {      
            ADC_TimeCount = 0;       
            Target_Speed = ADC_SampleTarget();  //采集目标速度  
                
            if(Target_Speed > 1000 && Motor_Start_F == 0)BLDC_Motor_Start(Direction);//转速大于1000rpm才启动电机      
            else if(Target_Speed > 1000 && Motor_Start_F == 1);  //没有操作,避免重复启动      
            else BLDC_Motor_Stop(); //停止电机    
        }     
        
        if(Hall_TimeCount > 1)   //20ms测一次速    
        {      
            Hall_TimeCount = 0;      
            Motor_Speed = HALLcount * 500 / MotorPoles;  //转速计算,HALLcount * 50 * 60 / 6 ,单位rpm      
            HALLcount = 0;    
        }     
        if(Flag_PID_TimeCount > 1) //20ms PID控制一次    
        {      
            Flag_PID_TimeCount = 0;      
            PID_Ctrl(Target_Speed);    
        }     
        
        if(OLED_FRESH_TimeCount > 50)  //500ms OLED显示刷新一次    
        {      
            OLED_FRESH_TimeCount = 0;      
            sprintf(Buffer1,"Target:%d rpm   ",Target_Speed);  //显示目标速度      
            sprintf(Buffer2,"Speed:%d rpm   ",Motor_Speed);    //显示电机转速      
            I2C_OLED_ShowString(0,0,Buffer1);      
            I2C_OLED_ShowString(0,15,Buffer2);              
            I2C_OLED_UPdata();    
        }  
    }      
}

Speed_Measure.c中对BTIM1的中断服务程序修改:

void BTIM1_IRQHandler(void)
{  
    /* USER CODE BEGIN */  
    if(BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))  
    {      
        BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);     
        Hall_TimeCount++;        //计数,进了2次BTIM1中断,即20ms对转速计算一次    
        Flag_PID_TimeCount++;    //计数,进了2次BTIM1中断,即20ms对PID计算一次    
        ADC_TimeCount++;         //计数,100ms检查一次电位器的电压大小,确定目标速度    
        OLED_FRESH_TimeCount++;  //计数,500ms刷新一次OLED显示  
    }  
    /* USER CODE END */
}

ADC_BLDC_Ctrl.c中对电压采集函数作修改:

uint32_t ADC_SampleTarget(void)        //采集电压
{  
    uint32_t Target = 0;   
    
    if(ADC_Result_Array >= 4000)Target = 4000;//限制大小,12位ADC采集值为:0-4096  
    else if(ADC_Result_Array < 3)Target = 0;  
    else Target = ADC_Result_Array;   
  
    Target = Target * 5;                 //目标速度为采集值的5被则设置最大速度20000rpm,可自行修改   
    
    return Target;
}

新增PID文件如下:

#include "PID.h"
extern uint8_t Motor_Start_F;  //电机启动运行标志
extern uint32_t OutPwm;        //输出PWM值,PID最终计算要得到一个确定的PWM占空比输出值
extern uint32_t Motor_Speed;   //电机实际转速,rpm 
float V_Kp,V_Ki,V_Kd;
void PID_Init(void)
{  
    V_Kp = 25;  
    V_Ki = 5;  
    V_Kd = 0;
}
void PID_Ctrl(uint32_t Target)
{  
    static int Error,LastError;  
    int PID=0;   
    
    Error = Target - Motor_Speed;   
    
    PID = (V_Kp/1000) * (Error - LastError) + (V_Ki/1000) * Error;   
    
    if(PID>10)PID=10;         //牺牲响应速度换取稳定性,避免占空比从0突增到25   
    else if(PID<-10)PID=-10;   
    
    OutPwm += PID;   
    
    if(OutPwm > OUTMAXPWM)OutPwm = OUTMAXPWM;  //占空比输出限制  
    else if(OutPwm < OUTMINPWM)  
    {    
        if(Target > 100)OutPwm = OUTMINPWM;    
        else OutPwm = 0;  
    }   
    
    if(Motor_Start_F == 0)OutPwm = 0;         //启停判断  
    else if(Motor_Start_F == 1);  
    else OutPwm = 0;   
    
    UPPWM();                                  //更新占空比  
    LastError = Error;
}

最终实验结果如下:

17.jpg

图5-2 有霍尔方波闭环控制无刷直流空心杯电机

06、调试过程中的问题与小提示

在调试过程中,作者曾烧板四次,前面两次烧毁MOS管,后两次烧毁PCB供电,针对此种问题有三条注意事项:

程序在KEIL5在进入和退出调试窗口的过程中存在未知的运行情况,所有烧毁都在进入和退出调试窗口时发生,推荐使用性能较好的线性电源限流0.2A进行供电,开关电源限流响应较慢,也会导致板子或电机烧毁。

如果缺乏对应电源,可以在进入和退出调试窗口前断掉PCB供电,待进入调试后再对PCB上电。

如果发生烧毁情况,首先检查PCB供电的芯片XL7005是否损坏,如若正常则依次检查EG3013的15V供电,板上的5V和3.3V供电。如果电机仍然无法转动,再使用万用表测量MOS管是否烧毁。

小提示

对于BLDC.c文件中的程序,需要确认逻辑是否严密,切记不可发生上下桥同时导通的情况。

在 ADC_SampleTarget 函数可以自行修改 Target 的值来规定目标速度上限。

本程序的方向切换在程序里手动设置,如果读者想要通过按键等控制方向,需要在方向改变前先停止电机,再切换方向,不可在电机运行时直接改变方向和换相顺序。

对于转速的测量要及时,虽然由于硬件原因,测量转速时间间隔越小,转速变化的梯度越大,但是未获得实时速度会导致PID控制效果不佳。先测速再进行PID运算。

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 61

主要功能:

1、对5-99V的电压与0.1-3A的电流进行测量与显示

2、通过蓝牙发送测得的数据

3、作为一款CW32+数码管的迷你开发板

设计要点:

1、使用CW32F003E4P7设计,使用其内置电压跟随器的功能简化外围电路

2、使用了和市面电压电流表的同款接口(XH2.54+CH3.96),方便通用

3、最大40V的表头供电电压,覆盖大部分常用电压范围

4、板载低成本蓝牙通信电路,使用单芯片+晶振便可实现BLE通信

5、模块使用的所有0603器件使用了更加方便手工焊接的0603L封装

电路设计与电路原理:

本项目电路图采用模块化绘制,在电路图中对不同模块使用线条进行了分区以便于读图,下面将分模块对电路图进行分析。

1、供电电路

本项目使用LDO作为电源,考虑到电压表头可能在24V或36V供电的工业场景中使用,本项目选择了最高输入电压高达40V的SE8533K2作为电源。本项目没有使用DCDC降压电路来应对大压差的主要原因为减少PCB面积占用,次要原因为降低表头成本。

考虑到高电压反接将会给模块带来不可逆的损坏,电压表头供电电路采用了串联二极管的方案进行防反接

1.png

注:本项目使用串联二极管进行防反接考虑到了本设备供电电压通常高于5V的使用场景,二极管的0.7V压降将不会供电造成影响。在常规的电路设计中不推荐该方案,而是建议使用反向并联二极管+串接保险丝的方案

本项目额外使用了串联小电阻(10Ω)来进行分压操作,从而减少在高电压情况下LDO由于较大的压差导致发热严重的问题,如果实际使用场景电源电压小于12V,可以将电阻替换为0Ω电阻来提升电源效率

2.png

SOT-89封装LDO的2号焊盘为散热焊盘,由于LDO会因为较大的压差导致发热严重,因此需要扩大与散热焊盘连接的铜箔的面积,表头在2号焊盘下设置了单独的铜箔,即上图中灰色半透明区域(正面也有设置独立的散热铜箔区域),同时增加了过孔,以便于将热量通过铜箔散发出去

2、主控芯片

本项目使用CW32F003E4P7作为主控芯片

3.png

本项目使用了CW32F003的最小系统,既主控芯片+复位电路,而不需要晶振等其余外围电路,其中芯片的PA05和PA02分别为SWD接口的CLK和DIO引脚,表头模块通过2.54标准间距的排针引出了相关引脚.

考虑到模块的尺寸问题,本模块并没有设置复位按键,而是在PCB上设置了一组短接触点,可以使用镊子等工具短接该组触点实现CW32芯片的复位.

4.png

3、电压采集电路

本项目采用分压电路实现高电压采集

5.png

本项目设计分压电阻为680K+10K,因此分压比例为69:1(约等于0.0145)

分压电阻选型主要需要参考以下几个方面:

1、设计测量电压的最大值,本项目中为100V(实际最大显示99.9V);

2、ADC参考电压,本项目中为1.5V,该参考电压可以通过程序进行配置;

3、功耗,为了降低采样电路的功耗,通常根据经验值将低侧电阻选择为10K;

随后便可以通过以上参数计算出分压电阻的高侧电阻:

1、计算所需的分压比例:即ADC参考电压:设计输入电压,通过已知参数可以计算出1.5V/100V≈0.015

2、计算高侧电阻:即低侧电阻/分压比例,通过已知参数可以计算出10K/0.015≈666.666K

3、选择标准电阻:选择一颗等于或略高于计算值的电阻,计算值约为666K,通常我们选择E24系列电阻,因此本项目中选择大于666K且最接近的680K

如果在实际使用中,需要测量的电压低于2/3的模块设计电压66V,则可以根据实际情况更换分压电阻并修改程序从而提升测量的精度,下面将进行案例说明:

1、假设被测电压不高于24V,其他参数不变

2、通过计算可以得到1.5V/24V=0.0625,10K/0.0625=160K,160K为标准E24电阻可以直接选用,或适当留出冗余量选择更高阻值的180K。

如果在实际使用中,需要测量的电压或高于模块99V的设计电压,可以选择更换分压电阻或通过修改基准电压来实现更大量程的电压测量范围,下面将进行案例说明:

1、假设被测电压为160V,选择提升电压基准的方案扩大量程

2、已知选用电阻的分压比例为0.0145,通过公式反推,我们可以计算出160V*0.0145=2.32V,因此我们可以选择2.5V的电压基准来实现量程的提升(扩大量程将会降低精度)

考虑到被测电源可能存在波动,在电路设计时,在低侧分压电阻上并联了0.1uF的滤波电容提高测量稳定性

6.png

在PCB进行Layout需要特别注意,由于需要采样的电压可能较高,因此需要在线路与铺铜之间设置更大的间距已保证安全性,在上图中,我使用了“铺铜禁止区域”来避免铺铜靠近网络的线路,另外也可以使用“约束区域”对需要注意的部分设置独立的铺铜规则来增加间距。

4、电流采集电路

本项目采用低侧电流采样电路进行电流检测,采样电路的低侧与表头供地

7.png

本项目设计的采样电流为3A,选择的采样电阻为100mΩ

采样选型主要需要参考以下几个方面:

1、设计测量电流的最大值,本项目中为3A

2、检流电阻带来的压差,一般不建议超过0.5V

3、检流电阻的功耗,应当根据该参数选择合适的封装,本项目考虑到PCB尺寸,选择了2512封装

4、检流电阻上电压的放大倍数:本项目中没有使用放大电路,因此倍率为1

随后便可以通过以上参数计算出检流的阻值选择:

1、由于本项目没有使用放大电路,因此需要选择更大的采样电阻获得更高的被测电压以便于进行测量

2、考虑到更大的电阻会带来更大的压差、更高的功耗,因此也不能无限制的选择更大的电阻

3、本项目选用了2512封装的电阻,对应的温升功率为1W

综合以上数据,本项目选择了100mΩ的检流电阻,根据公式可以计算出3A*100mΩ=300mV,900mW

表头在设计时考虑到了贴片采样电阻不能够应对不同的使用环境,尤其是电流较大的场景,因此预留了10mm间距的康铜丝直插焊盘,可以更具实际使用场景,使用康铜丝替换贴片采样电阻

下图中红色方框框选出的即是康铜丝焊接焊盘

8.png

在PCB进行Layout也需要特别注意,虽然I-网络与GND网络在电气上为同一网络,但是需要注意的是I-会有大电流通过,属于“功率地”,即使该点已经接地也会因为电流的波动造成网络电平变化,因此我们可以将该网络视为一个“干扰源”;而GND网络为表头电源负极,即“信号地”,同时,由于单片机的AGND与表头GND并未进行隔离,那此时可以将表头GND视为“敏感地”,因此需要避免被干扰,因此在Layout时选择在I-网络附近设置了铺铜禁止区,再使用导线将I-网络与GND网络相连接,并且连接点紧靠RC滤波网络的电容负极,进一步减少干扰对GND网络的影响。

在上图中,黄色箭头标注的即为大电流流通路径,通过接口的I+流入、流经采样电阻、通过接口的I-流出,因此从相对远离大电流路径的左下角(黄色圆圈处)引线将I-网络与GND网络进行电气连接,该点也紧靠采样电路的RC滤波网络的C6电容负极。

5、蓝牙通信电路

本项目使用KT6368A作为蓝牙主控芯片

9.png

本项目只需要通过蓝牙进行数据透传,也就是通过蓝牙把数据发送出去,便于用户通过手机或电脑对被测电压电流进行无线监控,不需要其他复杂功能,因此本项目中选择了外围电路极其简单的KT6368A,只需要使用单芯片+晶振便可实现BLE通信,同时该芯片为双模芯片,还可以支持SPP通信

为了降低项目成本,模块采用了PCB板载天线替代外接天线或陶瓷天线,在室内环境依旧可以保持良好的通信效果,若实际使用场景对通信距离有要求,可根据实际情况改为不同的天线类型

6、数码管

本项目采用了数码管作为显示单元

10.png

在本项目中使用了两颗0.28寸的三位共阴数码管作为显示器件,相较于显示屏,数码管在复杂环境中拥有更好的识别度,可以根据实际使用环境的需求,改为更小的限流电阻实现更高的数码管亮度;在另一方面,数码管拥有较好的机械性能,不会像显示屏一样容易被外力损坏

在本项目中,经过实际测试,数码管的限流电阻被配置为300Ω,对应的亮度无论是红色还是蓝色数码管,均具有较好的识别度,且亮度柔和不刺眼

7、按键

本项目预留有一颗按键与配套电路

11.png

考虑到用户可能需要对表头进行二次开发,本项目预留有一颗按键,按键io默认上拉,按下后则拉低,用户可以根据需求修改程序代码,使用按键实现不同的功能。

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 20

BD网盘链接:

链接:https://pan.baidu.com/s/1L22HHHCdJ5PvelaUS_tlxQ?pwd=vtik 

提取码:vtik

1.PID温控系统是一种常用的控制系统,用于实现对温度变量的精确控制。PID算法根据当前的温度误差以及误差的变化率,计算一个控制信号,用于调节加热器的输出。以下是PID算法的三个主要组成部分:

①比例(Proportional)控制:比例控制是根据当前的温度误差来计算控制信号。具体而言,通过将设定温度与实际温度之间的差异称为误差,然后将误差乘以一个比例增益参数,得到一个修正值。这个修正值与控制设备的输出信号相加,以调整温度控制。

②积分(Integral)控制:积分控制用于处理长期的温度误差。它通过对温度误差进行积分来计算一个积分误差。积分误差乘以积分增益参数,并且在一段时间内进行积累,得到一个修正值。积分控制可以帮助消除持续的稳态误差,使系统更快地达到设定温度。

③微分(Derivative)控制:微分控制用于处理温度变化的速率。它通过计算温度误差的变化率,即误差的导数,得到一个微分值。微分值乘以一个微分增益参数,用于调整修正值。微分控制可以帮助系统更快地响应温度变化,以防止过冲。

通过结合比例、积分和微分部分的修正值,PID控制算法可以计算出最终的控制信号。这个控制信号会被传递给加热器,以控制温度的变化。

2.本实验用到了CW32-48大学计划开发板OK、温控实验模块及Keil5开发环境。

 1.png

CW32-48大学计划开发板OK

    2.png

温控实验模块

3.png

 4.png

温控模块电路原理图   

3.PID控制算法的具体原理可参考以下链接中的文章

1)https://zhuanlan.zhihu.com/p/39573490 

2)https://zhuanlan.zhihu.com/p/347372624

3)https://zhuanlan.zhihu.com/p/41962512 

利用热敏电阻采集温度的原理及方法可参考往期文章及视频。

4.核心代码

mian.c:

#include "config.h"

unsigned char face = 0;       //界面变量
unsigned char face_brush = 0; //界面刷新频率控制

void InitSystem(void)  
{  
    RCC_Configuration();        //时钟配置  
    ADC_Configuration();        //ADC采集通道配置,采集NTC热敏电阻电压  
    PID_Configuration();        //PID参数配置  
    GPIO_KEYS_Configuration(); //按键GPIO配置  
    PWM_Init();                  //两路PWM输出初始化  
    Lcd_Init();                 //TFT屏幕初始化  
    BTIM_Init();                //定时器初始化
}

void Interface(void)  //人机交互界面
{  
    if ( face_brush > 200 )  //200ms刷新一次  
    {    
        face_brush = 0;    
        switch(face)    
        {      
            case 0://显示PV和SV,该界面下,可以设定SV        
                TFTSHOW_STRING_HEADLINE(0,0,"  PID  Control  ");        
                TFTSHOW_STRING(2,0,"REAL_Temper(℃):");        
                TFTSHOW_STRING(4,0,"   P V:       ");        
                TFTSHOW_FLOAT_NUMBER(4,8,pid.Pv);        
                TFTSHOW_STRING(6,0,"SET_Temper(℃):");        
                TFTSHOW_STRING(8,0,"   S V:       ");        
                TFTSHOW_FLOAT_NUMBER(8,8,pid.set_Sv);        
                break;      
            case 1://该界面下,可以设定P参数        
                TFTSHOW_STRING_HEADLINE(0,0,"  PID  Control  ");        
                TFTSHOW_STRING(2,0,"SET PID Control:");        
                TFTSHOW_STRING(4,0,"    P :       ");        
                TFTSHOW_INT_NUMBER(4,8,pid.set_Kp);        
                break;      
            case 2://该界面下,可以设定I参数        
                TFTSHOW_STRING_HEADLINE(0,0,"  PID  Control  ");        
                TFTSHOW_STRING(2,0,"SET PID Control:");        
                TFTSHOW_STRING(4,0,"    I :       ");        
                TFTSHOW_FLOAT_NUMBER(4,8,pid.set_Ki);        
                break;      
            case 3://该界面下,可以设定D参数        
                TFTSHOW_STRING_HEADLINE(0,0,"  PID  Control  ");        
                TFTSHOW_STRING(2,0,"SET PID Control:");        
                TFTSHOW_STRING(4,0,"    D :       ");        
                TFTSHOW_INT_NUMBER(4,8,pid.set_Kd);        
                break;      
            case 4://该界面下,可以设定Out0,即修正值        
                TFTSHOW_STRING_HEADLINE(0,0,"  PID  Control  ");        
                TFTSHOW_STRING(2,0,"SET PID Control:");        
                TFTSHOW_STRING(4,0,"   OUT0 :      ");        
                TFTSHOW_INT_NUMBER(4,10,pid.set_Out0);        
                break;    
        }  
    }
}

int main()           //主函数
{  
    InitSystem();      //系统初始化  
    while(1)  
    {    
        PID_Calc();      //PID运算    
        Interface();     //人机交互界面    
        Keys_Function(); //按键控制      
    }
}

pwm.c:

#include "pwm.h"

void PWM_Init(void)
{  
    RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_GTIM2,ENABLE); //使能GTIM2时钟
   
   __RCC_GPIOA_CLK_ENABLE();   //使能GPIOA时钟
   
   PA01_AFx_GTIM2CH2();        //打开PWM输出通道  
   PA02_AFx_GTIM2CH3();
   
   GPIO_InitTypeDef GPIO_InitStruct;
   
   GPIO_InitStruct.IT = GPIO_IT_NONE;   
   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  //推挽输出模式  
   GPIO_InitStruct.Pins = GPIO_PIN_1|GPIO_PIN_2;  
   GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;  
   GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
   
   GTIM_InitTypeDef GTIM_Initstructure;     //通用定时器  
   GTIM_Initstructure.Mode=GTIM_MODE_TIME;  //计数模式  
   GTIM_Initstructure.OneShotMode=GTIM_COUNT_CONTINUE; //连续计数  
   GTIM_Initstructure.Prescaler=GTIM_PRESCALER_DIV64; //预分频  
   GTIM_Initstructure.ReloadValue=2000-1; //ARR,计数重载周期2000  
   GTIM_Initstructure.ToggleOutState=DISABLE;  
   GTIM_TimeBaseInit(CW_GTIM2,&GTIM_Initstructure);
   
   GTIM_OCInit(CW_GTIM2,GTIM_CHANNEL3,GTIM_OC_OUTPUT_PWM_LOW); //GTIM2输出比较,CH3、CH2  
   GTIM_OCInit(CW_GTIM2,GTIM_CHANNEL2,GTIM_OC_OUTPUT_PWM_LOW); //有效占空比为低电平  
   GTIM_Cmd(CW_GTIM2,ENABLE); //使能GTIM2
}

void PWM1_Output(uint32_t value)
{  
    GTIM_SetCompare3(CW_GTIM2,value);  //设置GTIM2通道3的CCR
}

void PWM2_Output(uint32_t value)
{  
    GTIM_SetCompare2(CW_GTIM2,value);  //设置GTIM2通道2的CCR
}

void PWM_ALL_Output(uint32_t value)  //PWM1、2同步输出
{  
    PWM1_Output(value);  
    PWM2_Output(value);
}

pid.c:

#include "pid.h"

PID pid;    //定义PID结构体变量pid

void PID_Configuration(void)   //PID参数初始化配置 
{  
    pid.Sv     = 55;
   
   pid.Kp     = 350;  //比例系数  
   pid.Ki     = 10;   //积分系数  
   pid.Kd     = 38;   //微分系数
   
   pid.Ek_1   = 0;    //上一次偏差  
   pid.T      = 400;  //PID计算周期
   
   pid.cnt    = 0;      
   pid.cycle  = 2000; //PWM周期  
   pid.Out0   = 500;  //PID修正值
   
   pid.set_Sv = pid.Sv;  
   pid.set_Kp = pid.Kp;  
   pid.set_Ki = pid.Ki;  
   pid.set_Kd = pid.Kd;  
   pid.set_Out0 = pid.Out0;
}

float Get_Pv(void)  //Pv意为当前测量值,即当前温度
{  
    return Get_Temperture();
}

void PID_Calc(void)    //PID算法
{  
    float Pout,Iout,Dout;  
    float out;
   
   if ( pid.cnt > pid.T )   //控制计算周期  
   {    
       pid.cnt = 0;
     
      pid.Pv = Get_Pv();           
      pid.Ek = pid.Sv - pid.Pv; //计算偏差    
      pid.SumEk += pid.Ek;  //偏差累积

      Pout = pid.Kp * pid.Ek;  //比例控制
      
      Dout = pid.Kd * (pid.Ek - pid.Ek_1);  //微分控制
   
       if(pid.Pv>(pid.Sv-1))   //当测量值非常接近目标值的时候加入积分控制    
       {      
           Iout = pid.Ki * pid.SumEk;           //积分控制      
           out = Pout + Iout + Dout + pid.Out0;     
       }    
       else   out  = Pout + Dout + pid.Out0;  //测量值距离目标值较远时只使用PD控制
      
      if ( out > pid.cycle ) pid.Out = pid.cycle; //限幅    
      else if ( out < 0 )    pid.Out = 0;    
      else                     pid.Out = out;    
      PWM_ALL_Output(pid.Out);  //控制PWM输出    
      pid.Ek_1 = pid.Ek;  //进行下一次PID运算之前,将本次偏差变为上次偏差  
    }
}

5.实验最终现象

5.png

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 14

B站链接:

【【CW32】基于CW32的超声波模块的应用】 

https://www.bilibili.com/video/BV1sP411Y72M/ 

例程资料链接如下(群文件也可下载):

BD网盘链接:

链接:https://pan.baidu.com/s/1jORpJd1SIAiFWJjgomZhBA?pwd=0pcl  

提取码:0pcl

一、简介

1. C-SR04超声波模块是一种常用的测距模块,其通过发射超声波并接收其反射信号来实现测距功能。因其成本低、精度高、使用简便等特点,被广泛应用于以下场景:

(1)避障机器人:HC-SR04超声波模块可以用于避障机器人的距离测量,通过检测障碍物与机器人的距离,实现避障控制。

(2)智能家居:HC-SR04超声波模块可用于智能家居中的人体检测和距离测量。例如,在门口安装超声波模块,可以检测人的接近并触发开门动作,或者用于室内的距离测量和触发自动照明等。

2. 本实验用到了CW32F030C8T6小蓝板、0.96寸OLED显示屏、HC-SR04超声波模块及Keil5开发环境。

1.png

超声波测距系统

二、超声波模块测距的使用方法

使用流程

连接电源将VCC引脚连接到+5V,GND引脚连接到GND。

连接触发引脚

将Trig引脚连接到单片机的数字输出引脚。

连接回波引脚

将Echo引脚连接到单片机的数字输入引脚。

发送信号

通过向Trig引脚发送一个至少10微秒的高电平触发信号来启动测距过程

接收信号

模块发送触发信号后,自动发射超声波,并等待接收反射信号。当接收到反射信号时,Echo引脚会输出一个高电平信号,持续时间与超声波的往返时间成正比。

计算距离

通过测量Echo引脚输出高电平信号的持续时间,可以计算得到距离,一般使用以下公式计算:

距离 = 高电平持续时间 × 声波在空气中传播的速度 / 2。

重复测量

根据需要可定时测量距离,以实现连续的距离监测。

需要注意的是,HC-SR04超声波模块的测距精度受到多种因素的影响,如温度、超声波传播介质等。在使用过程中,需要结合具体的应用场景和需求进行参数调整和校准,以获得准确的距离测量结果。

三、核心代码

HC_SR04.c:
#include "HC_SR04.h"

extern unsigned int time;

void HC_GPIO_Init(void)  
{  
    __RCC_GPIOB_CLK_ENABLE();   
    
    GPIO_InitTypeDef GPIO_InitStruct;   
    GPIO_InitStruct.IT=GPIO_IT_NONE;  
    GPIO_InitStruct.Mode=GPIO_MODE_INPUT_PULLDOWN;//下拉输入  
    GPIO_InitStruct.Pins=GPIO_PIN_8;               //Echo  
    GPIO_Init(CW_GPIOB,&GPIO_InitStruct);   
    
    GPIO_InitStruct.Speed=GPIO_SPEED_HIGH;  
    GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;     //推挽输出  
    GPIO_InitStruct.Pins=GPIO_PIN_9;               //Trig   
    GPIO_Init(CW_GPIOB,&GPIO_InitStruct);   
    
    PB09_SETLOW();   //Trig拉低,为输出脉冲触发信号做准备
}

unsigned int Measure_Distance(void) //测距
{  
    unsigned int distance=0;   
    
    SetTrig();     //10us的脉冲触发信号  
    Delay_us(10);  
    ResetTrig();  
    while(ReadEcho()==0); //等待Echo输出高电平  
    time=0;               //开始记录回波信号脉宽  
    while(ReadEcho()==1); //等待Echo输出低电平       
    distance=time*1.7;//根据声速和时间计算距离,即distance=time*340/2/100  
    
    /*      
        关于分辨力(mm):          
        定时器每次对time加1是10us,10us=0.01ms,340m/s=340mm/ms          
        计算距离时,最小分辨力为:0.01(ms) * 340(mm/ms) / 2 = 1.7(mm)          
        小于模块标准精度3mm,故测距结果十分精准  
    */  
    return distance; //返回距离,单位mm
}

main.c:
#include "HC_SR04.h"

extern unsigned int time;

void HC_GPIO_Init(void)  
{  
    __RCC_GPIOB_CLK_ENABLE();   
    GPIO_InitTypeDef GPIO_InitStruct;   
    GPIO_InitStruct.IT=GPIO_IT_NONE;  
    GPIO_InitStruct.Mode=GPIO_MODE_INPUT_PULLDOWN;//下拉输入  
    GPIO_InitStruct.Pins=GPIO_PIN_8;               //Echo  
    GPIO_Init(CW_GPIOB,&GPIO_InitStruct);   
    
    GPIO_InitStruct.Speed=GPIO_SPEED_HIGH;  
    GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;     //推挽输出  
    GPIO_InitStruct.Pins=GPIO_PIN_9;               //Trig   
    GPIO_Init(CW_GPIOB,&GPIO_InitStruct);   
    
    PB09_SETLOW();   //Trig拉低,为输出脉冲触发信号做准备
}

unsigned int Measure_Distance(void) //测距
{  
    unsigned int distance=0;   
    
    SetTrig();     //10us的脉冲触发信号  
    Delay_us(10);  
    ResetTrig();  
    while(ReadEcho()==0); //等待Echo输出高电平  
    time=0;               //开始记录回波信号脉宽  
    while(ReadEcho()==1); //等待Echo输出低电平       
    distance=time*1.7;//根据声速和时间计算距离,即distance=time*340/2/100  
    
    /*      
        关于分辨力(mm):          
        定时器每次对time加1是10us,10us=0.01ms,340m/s=340mm/ms          
        计算距离时,最小分辨力为:0.01(ms) * 340(mm/ms) / 2 = 1.7(mm)          
        小于模块标准精度3mm,故测距结果十分精准  
    */  
    return distance; //返回距离,单位mm
}

四、实验最终现象

3.png

4.png

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 10

摘要:

芯片超强抗干扰性能加持的角磨机产品方案,全温度范围稳定可靠!无惧各种复杂工况!芯片超宽供电电压带来更多开发可能!真12位ADC精准可靠!

采用CW32MCU+驱动+MOS的方案架构,比一般集成预驱动的方案更加可靠、可控; 自研国产芯片与优秀产业链的赋能,带来更具优势的方案成本和稳定可靠的芯片供应,因而为企业提供更具性价比的选择。

01、产品概述

1.jpg

什么是角磨机?

角磨机是一种高效、多用途的磨削工具,广泛应用于建筑、装修、金属加工等领域。本方案将介绍该角磨机控制器的功能特点、参数列表、组成框图、软硬件设计、操作方法、安全使用注意事项等,旨在帮助用户更好地了解和使用。

02、功能特点

1. 多功能性:可配备多种砂轮,适用于锯割、磨削、抛光等多种作业。

2. 高效能:角磨机转速快,切削力强,工作效率高。

3. 轻便易携:锂电池供电,操作灵活,方便携带。

4. 安全可靠:采用优质材料制造,安全性能高,保障用户安全。

03、参数列表

工作电压:13~24V

额定电流:50A

工作温度:-20度~95度,湿度:最大85%

BLDC电机最大转速:1对极6W转。

启动时间 :0至最大速<2S。

启动过压保护:24V

启动欠压保护:15V

运行欠压保护 :13.5V

三级过流保护功能:60A、70A、80A

短路保护功能:120A

04、功能描述

控制器通过开关按键实现启动。

按键切换三档调速功能,档速参数掉电存储功能。

三档速度指示、电量指示功能(低中高),电量低于最低档时最低电量指示灯闪。

延时停机功能:松开主开关,电机自由停转,10S后进入待机模式。10S内,可随时开关启动电机运转。

电机顺风接转功能、无反转启动现象。

多种故障保护功能:欠压、过压、过温、启动失败、过流、失步、堵转、防飞车等。

防飞车功能:电池先上电,才能打开开关,启动电机运转。在打开开关状态下,接上电池时,进入防飞车保护,10S后进入待机状态。

05、方案主控芯片

该方案基于CW32F003E4P7主控

• 芯片内核:ARM® Cortex®-M0+、

• 最高主频:48MHz

• 工作温度:-40℃ 至 105℃ 全温度范围稳定可靠!

• 工作电压:宽电压!1.65V至5.5V、最大20K字节 FLASH,数据保持25年@85℃、 最大 3K 字节 RAM。

• 支持最多 21 路 I/O 接口;

• 模数转换器:12 位高精度ADC,±1.0 LSB,11.3 ENOB(有效位数),最高 1M SPS 转换速度;

• 超强抗干扰,无惧复杂工况!HBM ESD、MMESD、CDM ESD、Latch up@105℃ 全面达到 JEDEC最高等级!

• 定时器

- 16 位高级控制定时器,支持 6 路捕获 / 比较通道和 3 对互补 PWM 输出,死区时间和灵活的同步功能

- 一组 16 位通用定时器

- 三组 16 位基本定时器

• 推荐理由

采用CW32MCU+驱动+MOS的方案架构,比一般集成预驱动的方案更加可靠、可控; 自研国产芯片与优秀产业链的赋能,带来更具优势的方案成本和稳定可靠的芯片供应,因而为企业提供更具性价比的选择。

06、方案组成框图

2.png

该方案使用32位单片机CW32F003E4P7作为主控,使用AD软件比较过零点方式进行电机换相控制,采用六步方波控制算法。采用顺风接转+静态脉冲注入的启动算法,最小输出10%占空比,最小换相时间可达180uS,失步响应时间为30MS。

07、方案实测

#产品实物图:

3.png

4.png

#最高速端电压波形:

5.png

#高速顺风接转端电压波形(40000多转速度接转):

6.png

#低速顺风接转端电压波形(3000多转速度接转):

7.png

#静止启动端电压波形:

8.png

08、故障保护

该方案具有多种故障保护措施:短路保护、三级过流保护、防飞车保护、堵转保护、失步保护、欠压保护、过压保护、过温保护等功能。

在发生故障时,速度档最低位指示灯闪烁,闪烁频率表示了故障代码。不同的故障代码表示了不同的故障状态发生。

故障代码定义如下:

闪1次:>120A短路保护

闪2次:>80A过流保护

闪3次:>70A过流保护

闪4次:>60A过流保护

闪5次:速度低过度保护(300RPM)

闪6次:欠压保护,运行中小于13.5V,启动时小于15V

闪7次:启动过压保护,高于24V

闪8次:过温保护,运行中高于95度,启动时高于85度

闪9次:防飞车保护。电池先供电,才能打开开关。

闪10次:启动失败保护。

闪11次:失步保护。

发生故障时,需要关闭启动开关,且等待10S进入待机模式后,重新启动运行。

09、操作方法

1. 使用前检查:检查角磨机砂轮片是否完好无损,安装是否牢固,确认无误后方可使用。

2. 操作步骤:将角磨机握持稳定,确保砂轮片与被加工材料保持适当角度和力度。

3. 使用后清理:使用完毕后,及时清理角磨机内部的灰尘和杂物,保持清洁

10、安全使用注意事项

1. 使用角磨机时应佩戴防护眼镜和手套,防止砂尘飞溅伤及眼睛和手部。

2. 严禁在潮湿或光滑的表面上使用角磨机,以免滑倒或损坏机器。

3. 切勿使用角磨机进行超出其设计范围的作业,以免造成机器损坏或人员伤亡。

4. 长时间不使用角磨机时,应将其存放在干燥通风的地方,并定期进行维护检查。

5. 在使用过程中如发现异常声音或发热严重等情况,应立即停止使用,进行检查维修。

6. 对于不熟悉操作或无经验的人员,应在专业人员的指导下进行使用,以免发生意外事故。

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 32

B站链接:

【【CW32】基于CW32的AS608指纹模块的应用-哔哩哔哩】 https://b23.tv/PE2yjOR 

例程资料链接如下(也可在群文件下载):

BD网盘链接:

https://pan.baidu.com/s/1U6q0oEb6dO7iB3T_bU9jHw?pwd=8cq8  
提取码:8cq8

一、简介

AS608是一款集成了指纹图像采集和指纹比对算法的指纹识别模块。它采用了高质量的光学传感器,能够实时采集人体指纹图像,并通过指纹比对算法进行指纹识别。AS608具有高精度、快速、可靠的特点,广泛应用于各种指纹识别场景,如门禁系统、安防系统、考勤系统等。

本实验用到了CW32-48F大学计划板、0.96寸OLED显示屏模块、AS608指纹模块及Keil5开发环境。

1.png

CW32-48F大学计划板

2.png

AS608指纹模块

二、AS608指纹模块使用说明

(1) 引脚说明(该模块共8个引脚,只使用前6个引脚)

3.png

(2) 通讯方法

通过给AS608串口发送特定的指令调用模块内置的算法进行相应的操作。

常用的指令有:

4.png

5.png

指令格式有三种:命令包格式、数据包格式和结束包格式。

6.png

以命令包格式为例,包头是2个字节的数据,固定为0xEF01;芯片地址4个字节,默认是0xFFFFFFFF;包识别用来区分指令类型,如命令包固定为0x01,数据包格式固定为0x02;包长度指这一条指令有多少个重要的数据,包长度=包长度至校验和(指令、参数或数据)的总字节数,包含校验和,但不包含包长度本身的字节数;指令就是需要AS608执行的操作,参数和具体的指令有关,不同的指令,参数的长度和数值都有所不同;校验和是为了确保串口通讯正确。

模块接收到命令后会产生应答,将有关命令执行情况与结果上报给上位机,上位机只有在收到模块的应答包后才能确认模块收包情况与指令执行情况。模块应答包中包含一个参数:确认码。确认码表示执行指令完毕的情况。

7.png

确认码定义:

00H:表示指令执行完毕或 OK;

01H:表示数据包接收错误;

02H:表示传感器上没有手指;

03H:表示录入指纹图像失败;

...

(3) 指纹录入实现流程
录入指纹图像

当手指放在光学指纹窗口时,模块自动拍下指纹图像。

生成特征

记录图像中的指纹特征,并将该特征存入缓存区。

再次录入图像

当手指放在光学指纹窗口时,再次拍下指纹图像。

再次生成特征

记录二次图像中的指纹特征,并将该特征存入另一缓存区。

精确比对两枚指纹特征

对录入的两个指纹特征进行比对。

合并特征(生成模板)

比对成功无误后,将两个特征合并成一个指纹模板。

储存模板

将生成的模板存储到到AS608内部Flash,存储时需指定存储地址,即指纹ID。

上面以指纹录入功能为例,简要介绍了功能实现的基本流程,更多详情请参考AS60x指纹识别SOC用户手册和ATK-AS608指纹识别模块用户手册。
三、核心代码
main.c:
#include  "main.h"
#include  "Uart.h"
#include  "LED.h"
#include  "AS608.h"
#include  "OLED.h"
#include  "Delay.h"
#include  "BTIM.h"
#include  "Key.h"

uint8_t flag_add=0;          //指纹录制标识位
uint8_t flag_passport=0;     //密码输入标识位
uint8_t flag_key_function=0; //按键功能标识位
const uint16_t passpot=123;  //管理员密码

/*-----------------------刷指纹-----------------------*/
void AS608_MatchFingerPrints(void)
{  
    if(Is_Press==1)       //有手指按下  
    {    
        SearchResult seach;   
        uint8_t result;    
        result=PS_GetImage();    
        if(result==0x00)//获取图像成功     
        {        
            result=PS_GenChar(CharBuffer1);      
            OLED_Clear();      
            if(result==0x00) //生成特征成功      
            {          
                result=PS_HighSpeedSearch(CharBuffer1,0,6,&seach);  //自定义检索ID为0~6的指纹库,最大范围为0~300        if(result==0x00)//搜索成功        {                OLED_ShowString(2,1,"      ID=      ");  //指纹识别成功后自定义显示内容          
                OLED_ShowNum(2,10,seach.pageID,1);          
                OLED_ShowString(3,1,"    Welcome!    ");          
                /*                    
                    指纹识别成功后的自定义功能                                                  
                                                    */       
            }        
            else         
            {          
                OLED_ShowString(1,1,"    Refuse!    ");  //指纹识别失败,遭到系统拒绝        
            }              
        }      
        else      
        {        
            OLED_ShowString(1,1,"    Error!     ");  //指纹识别过程出现错误,3秒后重启识别功能可再次识别        
            OLED_ShowString(2,1,"      3       ");        
            Delay_s(1);        
            OLED_ShowString(2,1,"      2       ");        
            Delay_s(1);        
            OLED_ShowString(2,1,"      1       ");      
        }      
        Delay_s(1);    
     }    
     Display_Meum();     
     flag_passport=0;  
    }  
}

/*-----------------------录指纹-----------------------*/
void AS608_AddFingerPrints(void)
{ 
    uint8_t result,steps=0;  
    flag_add=1;  
    OLED_Clear(); 
    OLED_ShowString(1,1,"AddFingerPrints");  
    while(1)  //在指纹录制环节中循环  
    {    
        switch (steps)    
        {      
            case 0:          //录制环节0        
            flag_key_function=1;        
            OLED_ShowString(2,1,"Choose ID:");  //指纹ID选择界面        
            OLED_ShowNum(2,11,Finger_ID,1);        
            OLED_ShowString(3,1,"               ");        
            OLED_ShowString(4,1,"back");        
            OLED_ShowString(4,8,"+");        
            OLED_ShowString(4,13,"ok");        
            switch(Key_Scan())        
            {          
                case 1:         //退出指纹录制,返回主界面,            
                Display_Meum();            
                return ;          
                case 2:      //选择录入的指纹对应的ID            
                if(Finger_ID!=6)  Finger_ID++;            
                else              Finger_ID=1;            
                break;          
                case 3:              //进入下一个环节            
                steps=1;            
                break;        
            }      
            break;       
            
            case 1:          //录制环节1        
            if(Key_Scan()!=0){Display_Meum();return ;};  //按任意键退出        
            OLED_ShowString(2,1,"               ");       
            OLED_ShowString(3,1,"Please press...");     //操作提示        
            OLED_ShowString(4,1,"               ");        
            result=PS_GetImage();        //首次获取指纹图像        
            if(result==0x00)         
            {          
                result=PS_GenChar(CharBuffer1);//生成特征1          
                if(result==0x00)          
                {            
                    OLED_ShowString(3,1," OK! Get it... ");            
                    Delay_s(1);            
                    OLED_ShowString(2,1,"               ");           
                    steps=2;        //进入下一个环节                      
                }                
            }              
            break;       
            
            case 2:          //录制环节2        
            if(Key_Scan()!=0){Display_Meum();return ;};  //按任意键退出        
            OLED_ShowString(3,1,"Press Again...");      //操作提示        
            result=PS_GetImage();         //再次获取指纹图像        
            if(result==0x00)         
            {          
                result=PS_GenChar(CharBuffer2);//生成特征2          
                if(result==0x00)          
                {            
                    OLED_ShowString(3,1," OK! Get it... ");            
                    Delay_s(1);            
                    steps=3;        //进入下一个环节            
                }            
            }            
            break;
      
            case 3:          //录制环节3        
            result=PS_Match();    //特征1与特征2进行匹配        
            if(result==0x00)      //匹配成功        
            {          
                steps=4;          //进入下一个环节          
                OLED_ShowString(3,1,"  Matching...  ");        
            }        
            else                  //匹配失败        
            {          
                OLED_ShowString(2,1,"     Fail!     ");          
                OLED_ShowString(3,1,"       3       ");          
                Delay_s(1);          
                OLED_ShowString(3,1,"       2       ");          
                Delay_s(1);          
                OLED_ShowString(3,1,"       1       ");          
                steps=0;          //回到录制环节0          
            }        
            Delay_s(1);        
            break;
            
            case 4:          //录制环节4        
            result=PS_RegModel();  //合并特征,生成指纹模板        
            if(result==0x00)           
            {          
                steps=5;          //进入下一个环节          
                OLED_ShowString(3,1,"      OK!      ");        
            }        
            else                       
            {          
                OLED_ShowString(2,1,"     Fail!     ");          
                OLED_ShowString(3,1,"       3       ");          
                Delay_s(1);          
                OLED_ShowString(3,1,"       2       ");          
                Delay_s(1);          
                OLED_ShowString(3,1,"       1       ");          
                steps=0;        
            }        
            Delay_s(1);        
            break;       
            
            case 5:          
            result=PS_StoreChar(CharBuffer2,Finger_ID);   //储存指纹        
            if(result==0x00)         
            {                    
                flag_key_function=0;          
                OLED_ShowString(3,1,"AddSuccessfully");          
                Delay_s(1);          
                Display_Meum();        //回到主页面          
                return ;        
            }                  
            break;            
        }      
    }
}

/*-----------------------主页面-----------------------*/
void Display_Meum()   
{  
    OLED_Clear();  
    flag_add = 0;  
    flag_key_function = 0;  
    if(PS_ValidTempleteNum(&validFinger_num)==0xff)  //读取指纹模块当下信息参数    
        OLED_ShowString(2,1,"  ERROR!!!  ");     //提示读取失败,原因可能是线没接好或者模块损坏  
    else   
    {    
        OLED_ShowString(1,1,"----Welcome----");      
        OLED_ShowString(2,1,"ValidNum:");    
        OLED_ShowNum(2,10,validFinger_num,1);    
        OLED_ShowString(4,1,"add   delet   ");    
        if(delet_num==0) OLED_ShowString(4,15,"+");    
        else OLED_ShowNum(4,15,delet_num,1);  
    }
}

/*-----------------------密码输入-----------------------*/
void PassportInput()    
{  
    static uint8_t i=0;  
    flag_key_function=1;  
    OLED_ShowString(1,1,"No access!");      //密码输入提示   
    OLED_ShowString(2,1,"Input Passport:");  
    OLED_ShowNum(3,6,passport_num1,1);  
    OLED_ShowNum(3,8,passport_num2,1);  
    OLED_ShowNum(3,10,passport_num3,1);  
    OLED_ShowString(4,1,"+");  
    if(i==0)  OLED_ShowString(4,6,"^");  
    OLED_ShowString(4,12,"back");  
    switch(Key_Scan())  
    {    
        case 1://加      
        if(i==0) {passport_num1++;if(passport_num1==10) passport_num1=0;}      
        if(i==1) {passport_num2++;if(passport_num2==10) passport_num2=0;}      
        if(i==2) {passport_num3++;if(passport_num3==10) passport_num3=0;}      
        if((passport_num1*100+passport_num2*10+passport_num3)==passpot)      
        {        
            OLED_ShowNum(3,10,passport_num3,1);        
            Delay_ms(500);        
            OLED_Clear();        
            OLED_ShowString(2,1,"    Access!    ");        
            access=1;       //打开指纹录制/删除权限        
            PA07_SETLOW();  //LED全部点亮,指示权限全开        
            PA08_SETLOW();        
            PC13_SETLOW();        
            Delay_s(2);        
            Display_Meum();        
            flag_passport=0;          
            passport_num1=0;        
            passport_num2=0;        
            passport_num3=0;        
            i=0;      
        }      
        break;    
        case 2://  切换数字      
        if(++i==3) i=0;      
        if(i==1)      OLED_ShowString(4,6,"  ^  ");       
        else if(i==2) OLED_ShowString(4,6,"    ^");       
        else if(i==0) OLED_ShowString(4,6,"^    ");       
        break;    
        case 3://退出      passport_num1=0;      
        passport_num2=0;      
        passport_num3=0;      
        Display_Meum();      
        flag_passport=0;      
        i=0;      
        break ;    
    }
}

/*------------------指纹录制/删除权限管理-------------------*/
void Permission_Management(void)
{  
    if(Key_Scan()==20)   //若返回键值为20,则打开密码输入标识  
    {    
        OLED_Clear();    
        flag_passport=1;   
    }  
    if(flag_passport==1)//若密码标识打开,则进入密码输入界面    
    PassportInput();
}

/*--------------------------主函数--------------------------*/
int main()
{  
    LED_Init();             //3个LED指示灯  
    OLED_Init();           //OLED屏幕显示  
    BTIM_Init();           //启动定时器配合应答包接收  
    UART2_Init();        //与AS608进行串口通信,波特率为57600  
    Key_GPIO_Init();       //三个按键  
    AS608_WAK_GPIO_Init(); //WAK引脚,感应手指按下  
    Display_Meum();        //屏幕主界面  
    while(1)  
    {      
        AS608_MatchFingerPrints();  //指纹识别    
        Permission_Management();    //权限管理  
    }
}

四、最终实验现象

8.png
录入指纹
9.png
识别指纹
10.png
删除指纹

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 11

例程链接:

https://pan.baidu.com/s/1s1XwqDFkO8fK4SRSTKsNhA?pwd=mshk 

提取码:mshk

本章将介绍CW32的IIC接口,并最终点亮一块OLED屏幕,如果你对如何编写各种模块的驱动代码束手无策,那本系列教程的IIC章节或许能让你受益匪浅。

Inter-Integrated Circuit Bus,集成电路总线,简称IIC总线。这是一种半双工同步总线协议,这个分类很好地概括了IIC总线的特点:

1.作为总线协议,可以在一根“信息主干道”上接入多个通信节点(也就是通信设备);

2.同一时间只能有一个通信节点能够在总线上讲话;

3.同步传输意味着这种传输方式至少需要一根时钟线;

IIC总线使用2根线来传输信号:时钟线和数据线,相比于其他的总线协议,这种传输方式更节省IO资源,由于多数情况下IIC仅用于同一块集成电路板上不同模块之间的通信,所以它并不能传输很长的距离,速度也不是那么快,但硬件布线简单,且同一块电路板都会使用同一个电源的正负极,因此IIC总线相当实用。

本文不会详细介绍IIC总线的时序,这里只对其通信流程进行概括,并着重介绍数据链路层的相关内容。从流程上看,IIC总线协议的通信过程大致如下。

假设有A、B两个设备,他们使用IIC总线协议来传递信息,协议规定至少要有一个设备作为主机,其他设备作为从机,IIC是同步通信,协议规定主机来提供时钟,因此只有主机可以主动发起一次通信。假设现在A需要向B传递一个信息DATA,那过程就是这样:

1.设备A发出消息“全体目光向我看齐,我宣布个事,我要开始讲话了”;

2.设备A发出的“公告”会被B看到,B作为从机就会听A接下来要说什么,从A发出“公告”开始,A就会占用总线,此时其他设备都无法在数据线上发出消息。

3.设备A需要继续喊话,这第二次喊话,A就需要告知总线上的设备自己到底要找谁发消息,是广播给所有人(就像全校演讲那样),还是逮住某个设备“私聊”,IIC总线使用从机地址来区分广播和“私聊”,如果第二次喊话的内容是广播模式专有的“地址码”(0x00),总线上的所有从机都会接收后续发出的数据;如果第二次喊话的内容是设备B专有的“地址码”(一个7bit大小的数字),那这次喊话就是针对设备B的,其他的设备会发现点名私聊没找到自己,也就会放弃对后续数据的接收。

4.假设设备A用二次喊话找到设备B进行私聊,待B回应一个应答信号(ACK)之后,A就可以开始数据的传输,每当B接收到A传输的数据,B就会给A发出一个应答信号,这个过程会持续到A完成所有数据的传输并发出停止信号为止——“我的话讲完了,你可以挂电话了“。

如果在A不断向B传输数据的过程中,B觉得自己脑子要炸了,数据太多了,需要时间消化,B可以在回发应答信号的时候发一个“我收不了了!“(NACK),A在接收到这个信号之后,就知道B已经接收到极限了,A就不会再发数据。之后A可以选择开始新一轮的数据传输(回到过程1发起一个新的”喊话“)或者发出停止信号来直接关闭这次通信。

我们会发现上述过程存在很多分支选项,整个过程就像上课一样,“老师讲课学生都听着“、”老师点名某个学生回答问题“,下面我们就从具体的格式上来把上述的抽象过程给对应上。

格式上看,任何一次完整的IIC通信需要传输的数据都是如下的结构:

“起始信号+从机地址|读写类型+ACK+数据0+ACK+数据1+ACK+数据2+……+数据N+ACK+停止信号”

起始信号=我要开始喊话了;

从机地址|读写类型=我要找谁讲话|我要传达or索要信息;

ACK=收到!;

数据N=需要传达or索要的信息;

停止信号:我的话讲完了;

肯定有小伙伴想问:“那IIC总线通过什么方式来表示这些信号呢?“,这些内容属于物理层,感兴趣的小伙伴可以自行百度。

前半部分主要讲解了IIC总线的过程,下面介绍具体到代码上,单片机的IIC接口应该如何去使用。

首先登场的还是喜闻乐见的IO和时钟配置,需要注意的是,这里的IO输出模式需要配置为开漏输出,因为IIC接口需要从IO口收发数据,读写都在这一个IO上,开漏输出就能满足同时读写的需求。

1.png

2.png

对于IIC的配置其实相对来说比较简单,配置好波特率(公式在结构体的注释里面写好了),使能外设,设置好应答规则,最后开启IIC外设即可。

接着就是比较重要的部分了,IIC接口的收发并不是全自动的,因为一个完整的通信不仅包括发数据(地址、数据什么的),还包含收数据(啥也不干也得接收ACK信号),所以IIC通信的每个部分基本上都是收发易位的过程,IIC外设并不会自动完成这个复杂的过程,每个部分的信号是否发送、以及发送的情况都需要开发者自己去查看(开发者:改为手动操作,全部让我来!)。

编者写了几个带自检功能的IIC函数,这几个简单的函数可以满足驱动OLED的需求。

 3.png

编写这四个函数可以方便我们后续对OLED驱动的开发,现在我要详细说明一下这四个函数的内在逻辑。

4.png

首先是起始信号,我们可以看到发起始信号的函数显示打开了一个开关,等待某个标志成立之后又关掉了那个开关,这里结合手册进行说明。IIC协议中,起始信号是“SCL维持高电平,SDA线电平拉低”这一现象,手册中也有详细描述:

5.png

又由于CW32的IIC接口并不会在起始信号发出之后自动停发起始信号,因此如果不在监测到起始信号发出之后关闭起始信号的发送,那么数据传输就无法开始,IIC设备会一直发送RESTART信号来占用总线,通信就会失败。

对于总线的状态——“我发的信号到底成功发出去没有呢?”,CW32提供了IIC状态码来指示总线状态,根据IIC设备不同的工作模式,一共会有26种总线状态,我们并不会用到全部的状态,但可能用到的状态都可以放到枚举类型里面,就像这样: 

6.png

以起始信号发送函数为例,其返回值就是已发送起始信号的状态码(0x08),如果起始信号发送失败,死循环就无法跳出,程序死机(虽然实际上不应该这么写,此处只做演示,编者就小小地偷一下懒)。

视线来到发送从机地址和读写指令的函数,就像本文前半部分讲的一样,喊话宣言之后需要指名道姓自己需要私聊的对象。从机地址本身只有7bit,占据整个字节的高7位,0号bit位表示这一次通信是为了传达信息还是索取信息,0位为0则传达(也就是写),为1则索取(也就是读)。当成功发送从机地址这一个字节之后,IIC状态码也会改变,对比状态码之后即可确认从机地址字节发送成功并收到了从机的ACK信号,这表示从机确认收到了这个字节的消息。

7.png

8.png

发送数据的函数和发送从机地址的函数很相似,只不过整个字节都表示数据,并没有什么独特的含义。

最后就是发送停止信号的函数,与起始信号不同,停止信号成功发出之后,总线会进入空闲状态,并且停止信号使能位会被硬件自动清零。

9.png

捋清逻辑之后,我就要说明一个非常重要的细节,仔细观察会发现,所有的IIC信号发送函数都有一个清除中断标志的操作,这里明明不是中断,为什么要写这个语句呢?因为CW32的IIC接口,其发送数据的触发条件,就是中断标志位被清除。根据手册的描述,只要IIC状态码改变,中断标志位就会被硬件置位,在开启中断的情况下,程序会进入中断服务函数,如果不开启中断,程序的执行顺序不会改变,这个标志位也就只是一个发送开关。

这个发送逻辑某种程度上很反直觉,因为大部分的通信接口,都是拿“数据缓冲区被写入数据”来触发发送行为的,而此处的send函数,均不具备发送功能,与其叫send_data,不如叫set_data更合适,他们的作用只是把数据装载到IIC的数据寄存器,因此如果想要发送,就需要在清除中断标志位之前将数据写入数据寄存器。手册上也详细描述了这一点:

10.png

这样一来,IIC通信就具备基本的发送功能了,对于常见的EEPROM读写,CW32的IIC库提供了连续读写的函数,开发者可以直接使用:

11.png

个人评价:大部分人在需要使用IIC的时候,都会直接移植软件模拟的IIC接口,但是在更多的地方,我还是推荐使用硬件IIC,尤其是需要使用IIC大量读写数据的场合。而CW32的IIC接口,在不考虑发送触发与中断绑定这一反直觉因素的情况下,其内部的处理逻辑相比其他MCU的IIC接口,还是颇具优势的(读者可以自行对比STM32的IIC接口,STM32的IIC读写逻辑不能完全手动操作,效率不够高),尤其是每次发送之后,不必要立刻进行下一个字节的发送,只要IIC总线还保持在建立状态,开发者可以在之后一段时间内的任意时刻发送下一个字节,这直接省去了等待发送完成的时间(当然本文并没有采取这种写法),提高了程序整体的运行效率。

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 6

页面

订阅 RSS - CW32