CW32

例程资料链接如下:

BD网盘链接:

https://pan.baidu.com/s/1B1Fa4GhmScSGR6VQNDsE3w 

提取码:855c

一、简介

1.GY-33是一种基于TCS34725颜色传感器的颜色识别模块。TCS34725是一种高精度光学传感器,能够检测红、绿、蓝三个基本色的光谱信息,从而实现对物体颜色的准确识别,该模块的具体应用场景包括以下几个方面:

(1)电子设备颜色校准:在电子设备制造过程中,颜色一致性非常重要。使用GY-33模块可以帮助制造商检测和校准电子设备的颜色,确保不同设备之间的颜色表现一致。

(2)色彩分析和精确匹配:GY-33模块在颜色分析和匹配方面有广泛应用。例如,在印刷行业中,可以使用该模块来检测和匹配颜色样本,从而确保印刷品的准确颜色表现。

(3)机器人视觉系统:GY-33模块可以用于机器人视觉系统,帮助机器人在环境中对不同颜色的物体进行识别和分类。这在物流、仓储和自动导航系统中非常有用,机器人可以根据物体的颜色属性执行相应的任务。

二、所需物料

本实验使用到了CW32F030C8小蓝板、GY-33颜色识别模块、0.96寸OLED显示屏,RGB全彩LED模块、轻触开关模块及Keil5开发环境。

1.jpg

CW32F030C8小蓝板

2.png

GY-33颜色识别模块

3.png   

RGB全彩LED模块

4.png

实物展示

【GY-33与单片机连线】:VCC<-->+3.3V

   GND<-->GND

   DR<-->PA5

   CT<-->PA4

【LED与单片机连线】:V<-->+3.3V

        R<-->PA0

    G<-->PA1

    B<-->PA2

【轻触开关与单片机连线】:VCC<-->+3.3V

GND<-->GND

OUT<-->PB9

此模块有两种方式读取数据,即串口UART或者 MCU_IIC,本次实验采用MCU_IIC的方式。

5.png

6.png

7.png

有简单的7种颜色识别,单片机不参与数据处理工作,不需要计算RGB值,直接读取吧 数据即可。需要特别注意的是:

8.png

三、核心代码

main.c:
#include "main.h"
#include "RGB.h"
#include "GTIM.h"
#include "Delay.h"
#include "GY_33.h"
#include "OLED.h"
#include "Key.h"
#include "BTIM.h"

#define LENGTH 3   //读取数据的长度
uint8_t press_flag=0; //按键按下标识
uint8_t data[LENGTH]={0}; //存放读取到的RGB数据
uint8_t color[1]={0};  //存放模块识别到的颜色数据
char *str[]={"blue","dblue","green","black","white","pink","yellow","red"};//模块可以识别到的颜色

int main()
{    
    uint8_t i;    
    OLED_Init();      //OLED显示  
    RGB_GPIO_Init();  //RGB灯GPIO初始化  
    GTIM2_Init();     //GTIM2初始化配置为PWM输出模式  
    I2C_GPIO_Init();  //GY-33模块GPIO初始化  
    Key_GPIO_Init();  //按键GPIO初始化  
    BTIM_Init();      //BTIM定时器初始化,定时控制按键扫描周期
    //  WriteData(GY33_ADDR,Config,0x51);  //启动白平衡,等级亮度为5
  
  while(1)  
  {    
      if(press_flag==1)  //若按键标识已打开,代表有按键按下,执行按键功能    
      {      
          OLED_Clear(); //清屏      
          if(ReadData(GY33_ADDR,R,data,LENGTH)) //读取模块检测颜色并进行处理后返回的RGB值      
          {        
              OLED_ShowString(1,1,"RGB:");        
              OLED_ShowNum(1,5,data[0],3);  //R值        
              OLED_ShowNum(2,5,data[1],3);  //G值        
              OLED_ShowNum(3,5,data[2],3);  //B值      
          }      
          RGB_Running(data);  //RGB全彩LED灯根据读取到的RGB进行显示      
          Delay_ms(100);      //数据读取间隔应不小于100ms      
          if(ReadData(GY33_ADDR,Color,color,1))//读取模块检测颜色并进行处理后的颜色信息返回值      
          {        
              for(i=0;i<8;i++) //8-bits数据,逐位判断        
              {          
                  if((color[0]>>i)==1) //判断哪一位为1          
                  {            
                      OLED_ShowString(4,1,"Color:");            
                      OLED_ShowString(4,7,str[7-i]); //显示对应颜色            
                      break;          
                  }        
              }      
          }      
          press_flag=0; //执行完关闭按键标识    
      }    
  }
}

void BTIM1_IRQHandler(void)  //BTIM1中断服务函数
{  
    static unsigned int cnt = 0;   
    
    if(BTIM_GetITStatus(CW_BTIM1,BTIM_IT_OV))    
    {    
        if(++cnt>=20)  //20ms定时,执行一次按键扫描    
        {      
            cnt = 0;      
            if(Key_Scan()==1)  //返回值不为0时        
            press_flag=1;    //打开按键标识    
        }    
        BTIM_ClearITPendingBit(CW_BTIM1,BTIM_IT_OV); //清除标志位  
    }
}

GY-33.c:
#include "main.h"
#include "Delay.h"
#include "GY_33.h"

void I2C_GPIO_Init(void)   //GY-33颜色识别模块GPIO初始化
{  
    __RCC_GPIOA_CLK_ENABLE();   
    
    GPIO_InitTypeDef GPIO_InitStruct;   
    GPIO_InitStruct.IT=GPIO_IT_NONE;  
    GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_OD;      //开漏输出  
    GPIO_InitStruct.Pins=GPIO_PIN_4|GPIO_PIN_5;  
    GPIO_InitStruct.Speed=GPIO_SPEED_HIGH;  
    GPIO_Init(CW_GPIOA, &GPIO_InitStruct);  
     
    SCL(1);  
    SDA(1);
}

void I2C_Delay() //I2C延时函数
{  
    Delay_us(time);
}

uint8_t I2C_Start(void)  //发送起始信号
{  
    SDA(1);  
    SCL(1);  
    I2C_Delay();  
    if(ReadSDA==0) return 0;  
    SDA(0);  
    I2C_Delay();  
    if(ReadSDA==1) return 0;  
    SCL(0);  
    I2C_Delay();   
    
    return 1;
}

void I2C_Stop(void)  //发送停止信号
{  
    SDA(0);  
    SCL(0);  
    I2C_Delay();  
    SCL(1);  
    I2C_Delay();  
    SDA(1);
}

void I2C_SendACK(uint8_t ackbit)  //发送应答
{  
    SDA(ackbit);  
    SCL(1);  
    I2C_Delay();  
    SCL(0);  
    I2C_Delay();
}

void I2C_SendByte(uint8_t Byte)  //发送1字节(8-bit)的数据
{  
    uint8_t i;  
    SCL(0);  
    for (i = 0; i < 8; i++)  
    {    
        if(Byte&0x80) SDA(1);    
        else SDA(0);    
        SCL(1);    
        I2C_Delay();    
        SCL(0);    
        Byte<<=1;    
        I2C_Delay();  
    }
}

uint8_t I2C_ReceiveByte(void)  //接收1字节(8-bit)的数据
{  
    uint8_t data,i;  
    SDA(1);  
    Delay_us(1);  
    for(i=0;i<8;i++)  
    {    
        SCL(1);    
        data<<=1;    
        if(ReadSDA==1) data|=0x01;    
        I2C_Delay();    
        SCL(0);    
        I2C_Delay();  
    }   
    
    return data;
}

uint8_t I2C_WaitAck(void) //等待应答
{  
    uint16_t i;  
    SDA(1);  
    SCL(1);  
    while(ReadSDA==1)  
    {    
        if(++i==500)      
        break;  
    }  
    if(ReadSDA==1)  
    {    
        SCL(0);    
        return 0;  
    }  
    I2C_Delay();  
    SCL(0);  
    I2C_Delay();   
    return 1; 
}

uint8_t WriteData(uint8_t Slave_Addr,uint8_t REG_Addr,uint8_t data)  //写操作
{  
    if(I2C_Start()==0) RETURN   
    
    I2C_SendByte(Slave_Addr);    
    if(I2C_WaitAck()==0) RETURN  
     
    I2C_SendByte(REG_Addr);          
    if(I2C_WaitAck()==0) RETURN                   
    
    I2C_SendByte(data);  
    if(I2C_WaitAck()==0) RETURN                 
    
    I2C_Stop();              //发送停止信号   
    
    return 1;
}

uint8_t ReadData(uint8_t Slave_Addr,uint8_t REG_Addr,uint8_t *data,uint8_t length) //读操作
{    
    if(I2C_Start()==0) RETURN  
             
    I2C_SendByte(Slave_Addr);      
    if(I2C_WaitAck()==0) RETURN           
    
    I2C_SendByte(REG_Addr);              
    if(I2C_WaitAck()==0) RETURN   
    
    if(I2C_Start()==0) RETURN  
     
    I2C_SendByte(Slave_Addr+1);   
    if(I2C_WaitAck()==0) RETURN                   
    
    while(--length)
    {    
        *data++=I2C_ReceiveByte();    
        I2C_SendACK(0);    
        Delay_ms(110);  
    }  
    *data=I2C_ReceiveByte();  
    I2C_SendACK(1);  
    I2C_Stop();  
                      //发送停止信号   
    return 1;              
}

四、实物展示+效果演示

10.png

11.png

12.png

13.png

来源: CW32生态社区

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

围观 81

一、选择目标控制设备

在我们有着理论基础的情况下去开发一款水泵,我们就需要在市面上看一看已有的产品。

1.png

水泵的类型还是很多的,我们也是在网上淘了一款进行拆卸,只需要水泵,不需要它的控制器。我们需要把UVW三个相线引出来。

2.png

3.png

4.png

可以看见这是我们刚拆开水泵,取开壳子的照片,左下角是水泵的控制器,加了很大一块散热片,由此可见,产品功率还是很高的。在我们后面调试的时候,水泵全功率运行是400-500W左右。设备跑了15分钟左右,一桶水的温度都上升了几度,可见热量还是很多,所以对于大功率的设备,散热很重要。

二、芯片选型

在开发这个水泵控制器之前我们肯定要进行芯片选型、画板子以及写程序等工作。

芯片选型,即选取一款合适的芯片用于本方案的电机驱动控制。

为此推荐一款国产芯片--武汉芯源半导体的CW32!

1、CW32芯片选型

武汉芯源半导体官网:www.whxy.com

5.png

武汉芯源半导体有限公司

武汉芯源半导体有限公司,于2018年8月28日成立,为上市公司武汉力源信息技术股份有限公司全资子公司,专业负责自研芯片的研 发、设计、推广、销售及技术服务相关业务。目前已推出微处理器MCU、小容量存储芯片EEPROM、功率器件SJ-MOSFET三大系列 产品,公司致力于成为国产芯片产业的领航者。

一、基本情况

1、2018年成立 为加大自研芯片研发力度, 2018年升为独立的芯片设计公司

2、10年+ 团队成员平均从业时间超10年, 理论扎实、技术过硬

3、背靠上市公司 武汉力源信息技术股份有限公司

4、成功转型 前身为头部技术型分销企业力源信息的IC业务部, 转型为拥有经验丰富的芯片设计团队

5、业务范畴 专注芯片的设计、研发、销售及技术服务, 拥有一支专业高效的市场及销售团队

二、CW32家族

1.通用高性能的CW32F系列MCU

2.安全超低功耗CW32L系列MCU

3.无线射频MCU

4.无线射频蓝牙系列MCU

5.无线射频ChirpIoT系列MCU

6.小容量存储芯片EEPROM产品

所以我们本次选择的是CW32F030C8T6这块芯片,用来开发水泵项目。

内核:ARM(®) Cortex(®)-M0+ 最高主频64MHz

工作温度:-40℃ 至 105℃;工作电压:1.65V 至 5.5V

存储容量: 64K 字节FLASH,数据保持25年@85℃ 8K 字节RAM,支持奇偶校验

对于我们开发人员来说,一款芯片对于项目开发及维护的重要性,就好比心脏和大脑对人的重要性。如果芯片选型和售后、芯片总类和优化、迭代不够好的话,我们是不会考虑采用的。但是本次使用的CW32是一个各方面都很优秀的产品。

三、项目流程

1、设计电路原理图和PCB布局:

首先,根据项目需求,设计无刷水泵电机的电路原理图,并进行PCB布局。确保电路板的布局合理,信号和电源线路分离,减少干扰。

2、选购和连接器件:

根据设计的电路原理图,选购所需的电子器件,并按照原理图进行连接。确保连接正确,信号和电源线路接触良好,以免出现接触不良或者短路的情况。

3、烧录程序:

将编写好的控制程序烧录到cw32主控芯片中。确保程序烧录成功,并进行相应的调试,确保程序可以正确运行。

4、调试和测试:

连接相应的电源和信号源,进行系统的调试和测试。通过调整参数和监测输出,判断无刷水泵电机的运行是否符合预期,并进行必要的调整。

来源:CW32生态社区

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

围观 36

一、前言

无刷水泵被广泛应用于许多领域,如工业生产、农业灌溉以及家庭应用。它具有高效、可靠和节能的特点,因此备受青睐。但是无刷水泵的供电是一个问题。怎么能随时随地的使用无刷水泵呢?总不能拿一个水泵,还要配一台发电机吧。所以本次我们使用电动车供电,可以满足用户骑车到哪,水泵都可以使用的特点。

在户外的各种场景下,肯定要选择一颗稳定的抗干扰的芯片才行。强烈推荐CW32系列芯片。抗干扰能力值得您的信赖!

CW32F030C8T6支持工作温度: -40℃ 至 105℃;

工作电压:1.65V 至 5.5V

存储容量:  64K 字节FLASH,数据保持25年@85℃ 8K 字节RAM,支持奇偶校验,128字节OTP存储器

使用一颗好芯片,为您照亮开发之路!

二、CW-W88水泵通用控制板-方案介绍

1、简介

CW-W88水泵通用控制板适用于水泵电机抽水控制应用。配合水泵电机,可使用48V或60V电池组(电动车电池组)抽水使用。使用前请将电池充满(电池至少在一半电量以上使用)。控制板具有欠压保护、过压保护、过流保护、无水停止保护、堵转保护,速度异常等功能。可广泛应用于各类水泵抽水控制。

2、系统功能

  • 电源接线:红色,黑色接电源,不分正负,建议按使用习惯接:红色接正、黑色接负。

  • 相线接线:UVW随机接入电机的相线,试看电机运行方向。如需要反转,随意调换两根相线,重新上电即可。

  • 电源电压接入不能超过70V。上电2S后,电机启动开始抽水运行(可根据需求缩短上电稳定时间)。速度增到最大功率以后,按设定的最大功率值控制运行。如果用户电机功率达不到设定的功率,控制板则按电机的最大输出能力输出运行。

  • 电机在无水检测或其它任何故障时,会自动停止运行,且以指闪灯的闪烁频率指示故障状态。

3、工作参数

  • 型号:CW-W88

  • 使用环境:48V或60V电池组

  • 工作电压:40V~60V

  • 工作电流:48V电池组13A、60V电池组10A

  • 最大功率:600W

  • 控制方式:电流闭环

  • 尺寸:板子为圆形,直径8CM,高度3CM。建议至少加3MM厚的散热器

4、项目组成部分:

a. CW32主控芯片:

这是整个项目的关键组成部分,它负责控制无刷水泵电机的运行和参数调节。CW32主控芯片具有高性能和低功耗的特点,可以满足无刷水泵电机控制器项目的需求。

b. 无刷水泵电机

无刷水泵电机是项目的核心部分,它可以通过电磁感应实现转子转动的无刷电机效果。无刷水泵电机具有较高的效率和可靠性,适用于各种应用场景。

c. 硬件电路:

包含驱动电路,电流采集电路,母线电压采集电路,反电动势采集电路,电源电路。

d. 软件编写:

包含电机驱动代码,高压,低压保护及自恢复。电流保护,速度异常保护,空载保护,启动堵转保护。

三、项目设计说明

1、硬件设计及PCB制板

此线控器是基于武汉芯源半导体推出的CW32F030C8T6产品设计研发而成的,其主要包括MCU、电源部分、电流采样、电压采样、EG3014驱动、反电动势采集等部分,设计系统原理框图如图1所示:

1.png

  • 该控制器功能包含:

  1. 上电自启动,缓慢加速。

  2. 启动堵转保护

  3. 速度异常保护

  4. 过欠压自恢复保护

  5. 电流保护

  6. 空载保护

2.设计原理图及PCB板实物图

2.png3.png

4.png

5.png

6.png

3.软件部分

软件部分主要分为以下几部分内容,软件构成如图7所示:

7.png

a. 状态机代码

switch(USER_sSysStateErr.sSystemState)//状态机{
case STATE_IDLE_A: MDS_v_charge();break;//充电
case STATE_INIT_B: MDS_v_Init();break;//初始化
case STATE_START_C: MDS_v_Start_the_judgment(); break;//等待启动信号
case STATE_ALIGNMENT_D: MDS_v_The_motor_starts();break;//启动
case STATE_ADCLOOP_E:MDS_v_OPEN_starts();break; // 开环模式
case STATE_BRAKE_F:MDS_v_brake();break; // 刹车
case STATE_FAULT_G:MDS_v_Misjudgment();break; // 错误判断
case STATE_WAIT_L:
MDS_v_Error_recovery(USER_sSysStateErr.sSysTime.u32ReStartTime);
break; // 错误恢复判断
case STATE_STOP_M:Stop_Motor();break; // 停止
default:break;}

b. 电流电压采集计算代码

void V_A_calculation(void)
{
    USER_sVoltageAll.s16Udc=(float)(SampleData[2]*UPDS_resistance_compute);        
    keep_adcvlaue+=SampleData[4];        
    keep_adcvnum++;        
    if(keep_adcvnum>2)        
    {                
        keep_adcvnum=0;        
        keep_adcvlaue=keep_adcvlaue/2;        
        USER_sCurrentAll.s32IdcUse=(SampleData[4]*UPDS_Current_compute)/10;//100mA        
        USER_sCurrentAll.IdcValue=(float)USER_sCurrentAll.s32IdcUse/100.0;        
    }
}

c. 电机启动代码,判断启动是否失败

//(3):电机启动代码,判断启动是否失败。
void Sensorless_MOTOR_START(void)
{        
    unsigned char coun = 0;        
    USER_BLDC.HALLcountS = 0;        
    USER_BLDC.QDPwm = 20; // 启动占空比        
    do        
    {                
        if (Sensorless_START() == 0)                
        {                        
            coun++;                        
            USER_BLDC.QDPwm += 5;                
        }                
        else                        
            break;        
    } while (coun < 5 && ErrorCode == 0);        
    if (coun >= 5 && ErrorCode == 0)        
    {                
        ErrorCode = 3;        
    } // 启动失败
}

d. 电机运行代码

//(4):电机运行代码
void ADCS_chuli(void)
{        
    static unsigned char cou = 0;        
    unsigned char hx = 0;        
    unsigned int thre = 0;        
    if (USER_BLDC.Sta != 2)                
        return;        
    BEMFConvertedValue = SampleData[TAB_BEMFChannel[Forward_rotation][bHallStartStep1]];        
    RisingFalling = TAB_RFling[Forward_rotation][bHallStartStep1];
        
   if (USER_BLDC.Tonoroff == 0)                
       thre = 50; // 30;//248;  //OFF时刻采集。0.2V阀值248        
   else                
       thre = SampleData[2] / 2;        
   if (RisingFalling == FALLING) // 下降沿        
   {                
       if (BEMFConvertedValue < thre)                
       {                        
           cou++;                        
           if (cou >= 2)                        
           {                                
               cou = 0;                                
               USER_BLDC.Sta = 3;                        
               USER_BLDC.StCountComm++; // 正确检测到第三相反电动势                                
               FFlag = 1;                                
               hx = 1;                        
           }                
       }                
       else                
       {                        
           cou = 0;                
       }        
   }        
   else if (RisingFalling == RISING)        
   {                
       if (BEMFConvertedValue > thre)                
       {                        
           cou++;                        
           if (cou >= 2)                        
           {                                
               cou = 0;                                
               USER_BLDC.Sta = 3;                        
               USER_BLDC.StCountComm++; // 正确检测到第三相反电动势                                
               FFlag = 1;                                
               hx = 1;                        
           }                
       }                
       else                
       {                        
           cou = 0;                
       }        
   }        
   if (USER_BLDC.StCountComm >= STCount && USER_BLDC.StOk == 0) // 连续检测到固定数量的过零时,认为启动成功        
   {                
       USER_BLDC.StOk = 1;        
   }        
   if (USER_BLDC.StOk == 1 && hx == 1)        
   {                
       hx = 0;                
       BTIM_SetAutoreload(CW_BTIM3, USER_BLDC.StepTime / 5);                
       BTIM_SetCounter(CW_BTIM3, 0);                
       BTIM_Cmd(CW_BTIM3, ENABLE);        
   }
}

e. 电机报警代码及其自恢复功能

//(5):电机报警代码及其自恢复功能

void US_vSysErrorCheck(void)
{        
    US_vSysCurrentCheck();                  // 电流报警        
    US_vSysVoltageCheck();                  // 电压报警        
    US_vSysStuck_in();                          // 启动堵转保护        
    US_vSysSPEED_ERRORCheck();          // 速度异常保护        
    US_vSyscarrying_idlerCheck(); // 空载保护
}

void MDS_v_Error_recovery(uint32_t WaitTime) // 错误恢复
{        
    static uint32_t lg_u32WaitCount = 0;        
    if (lg_u32WaitCount >= WaitTime)        
    {                
        USER_sSysStateErr.sSystemStatePre = USER_sSysStateErr.sSystemState;                
        USER_sSysStateErr.sSystemState = STATE_INIT_B;                
        USER_sSysStateErr.uSystemError.OverGUdcFlag = false;                
        USER_sSysStateErr.uSystemError.OverLUdcFlag = false;                
        USER_sSysStateErr.uSystemError.overStuck_in = false;                
        USER_sSysStateErr.uSystemError.GlobalErrorFlag = false;                
        ErrorCode = 0;                
        lg_u32WaitCount = 0;        
    }
    else if (((USER_sVoltageAll.s16Udc < UPDS_UDC_PROTECT_MAX_V_recover) && (USER_sSysStateErr.uSystemError.OverGUdcFlag == true)) || ((USER_sVoltageAll.s16Udc > UPDS_UDC_PROTECT_MIN_V_recover) && (USER_sSysStateErr.uSystemError.OverLUdcFlag == true))  || (USER_sSysStateErr.uSystemError.overStuck_in == true))        
    {                
        lg_u32WaitCount++;        
    }        
    else                
        lg_u32WaitCount = 0;
}

f. 电机反电动势及运行代码

反电动势过零检测法基本原理:

忽略电动机电枢反应,无刷直流电动机在稳态运行过程中,通过检测关断相的反电动势过零点获得转子的位置信号,进行对逆变器开关导通顺序切换,控制电机运动。

较为典型的 BLDCM 的无位置传感器控制方法有基于反电势的检测法、磁链估计法、电感检测法等,在该系统中采用反电势过零检测法。

反电动势过零检测法是以直流无刷电机中性点电压为基准进行反电动势过零检测的,属于间接反电动势检测方法。该方法将端电压作分压滤 波处理得到直流无刷电机的位置信号,由于有滤波电路存在,获得的位置信号比真实的反电动势过零点延时了一定的角度。

在理想情况下,电机三相绕组反电势 Ea 、Eb、Ec 的波形为梯形波。无刷电机采用 120°导通方式,在任何时刻只有两相导通,这两相电流的大小 相等、方向相反,另一相电流为零,故三相电流的 总和为零。

8.png

11.png

表: 正向反电动势过零点变化

g. 反电动势部分代码

//反电动势部分代码
void ADCS_chuli(void)
{        
    static unsigned char cou = 0;        
    unsigned char hx = 0;        
    unsigned int thre = 0;
   
   if (USER_BLDC.Sta != 2)                
       return;
   
   BEMFConvertedValue = SampleData[TAB_BEMFChannel[Forward_rotation][bHallStartStep1]];        
   RisingFalling = TAB_RFling[Forward_rotation][bHallStartStep1];
        
   if (USER_BLDC.Tonoroff == 0)                
       thre = 50; // 30;//248;  //OFF时刻采集。0.2V阀值248        
   else                
       thre = SampleData[2] / 2;
        
   if (RisingFalling == FALLING) // 下降沿        
   {                
       if (BEMFConvertedValue < thre)                
       {                        
           cou++;                        
           if (cou >= 2)                        
           {                                
               cou = 0;                                
               USER_BLDC.Sta = 3;
                                
           USER_BLDC.StCountComm++; // 正确检测到第三相反电动势                                
           FFlag = 1;                                
           hx = 1;                        
         }                
      }                
      else                
      {                        
         cou = 0;                
      }        
    }        
    else if (RisingFalling == RISING)        
    {                
        if (BEMFConvertedValue > thre)                
        {                        
            cou++;                        
            if (cou >= 2)                        
            {                                
                cou = 0;                                
                USER_BLDC.Sta = 3;
                                
           USER_BLDC.StCountComm++; // 正确检测到第三相反电动势                                
           FFlag = 1;                                
           hx = 1;                        
        }                
      }                
      else                
      {                        
          cou = 0;                
      }        
   }
        
   if (USER_BLDC.StCountComm >= STCount && USER_BLDC.StOk == 0) // 连续检测到固定数量的过零时,认为启动成功        
   {                
       USER_BLDC.StOk = 1;        
   }
  
   if (USER_BLDC.StOk == 1 && hx == 1)        
   {                
       hx = 0;                
       BTIM_SetAutoreload(CW_BTIM3, USER_BLDC.StepTime / 5);                
       BTIM_SetCounter(CW_BTIM3, 0);                
       BTIM_Cmd(CW_BTIM3, ENABLE);        
   }
}

h. 母线电压监测-分压电路原理及代码

可以看原理图中的分压电路是两个电阻

i. 软件保护

void US_vSysErrorCheck(void)
{        
    US_vSysCurrentCheck();                  // 电流报警        
    US_vSysVoltageCheck();                  // 电压报警        
    US_vSysStuck_in();                          // 启动堵转保护        
    US_vSysSPEED_ERRORCheck();          // 速度异常保护        
    US_vSyscarrying_idlerCheck(); // 空载保护
}

1):电流保护

在无刷电机运行中,有时候堵转很容易烧mos管,所以电流保护就尤为的重要。

在程序中配置了电流触发保护值,是15A,因为我们额定功率是500W。还有电流采集电路没有加入运放,采集不精确,所以需要把电流值设置大一点。之后也要根据实际的调试而改变。

//电流保护
void US_vSysCurrentCheck(void) // 电流保护
{
    #if (UPDS_ISUM_DETECTION == 1)        
    if ((USER_sCurrentAll.IdcValue) >= UPDS_OVERISUM_limit_MAX) // 一级电流保护        
    {                
        USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount++;                
        if (USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount >= UPDS_OVERISUM_limit_MS)                
        {                        
            USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount = UPDS_OVERISUM_limit_MS;                        
            USER_sSysStateErr.uSystemError.OverIdcFirstlevelFlag = true;                        
            USER_sSysStateErr.sSystemStatePre = USER_sSysStateErr.sSystemState;                        
            USER_sSysStateErr.sSystemState = STATE_FAULT_G;                
        }        
    }        
    else        
    {                
        USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount--;                
        if (USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount <= 0)                
        {                        
            USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount = 0;                        
            USER_sSysStateErr.uSystemError.OverIdcFirstlevelFlag = false;                
        }        
    }
#endif
}

/********************限流保护值***********************/ 
#define UPDS_OVERISUM_limit_MAX         (15) //unit: A 限流保护值 
#define UPDS_OVERISUM_limit_MS           (500)//unit: ms  触发保护时间

/**********************报警设置********************/
#define  UPDS_ISUM_DETECTION                         (1)                //电流保护使能

2):电压保护

在高压和低压保护中,我们在程序中加入了自恢复功能,如果电压高于58v就会触发高压保护,电机停止,等待电压下降到55v以下,就会触发自恢复功能,等待自恢复时间结束,电机重新启动。低压也是同理。

//电压保护
void US_vSysVoltageCheck(void) // 电压保护
{
    #if (UPDS_UDC_DETECTION == 1)        if (USER_sVoltageAll.s16Udc > UPDS_UDC_PROTECT_MAX_V)        
    {                
        USER_sSysStateErr.sSysTime.s32OverGUdcCount++;                
        if (USER_sSysStateErr.sSysTime.s32OverGUdcCount >= UPDS_OVERVOLTAGE_VOLTAGE_MS)                
        {                        
            USER_sSysStateErr.sSysTime.s32OverGUdcCount = UPDS_OVERVOLTAGE_VOLTAGE_MS;                        
            USER_sSysStateErr.uSystemError.OverGUdcFlag = true;                        
            USER_sSysStateErr.sSystemStatePre = USER_sSysStateErr.sSystemState;                        
            USER_sSysStateErr.sSystemState = STATE_FAULT_G; // 高压保护                
        }        
    }        
    if (USER_sVoltageAll.s16Udc < UPDS_UDC_PROTECT_MIN_V && USER_sVoltageAll.s16Udc > 5)        
    {                
        USER_sSysStateErr.sSysTime.s32OverLUdcCount++;                
        if (USER_sSysStateErr.sSysTime.s32OverLUdcCount >= UPDS_UNDERVOLTAGE_VOLTAGE_MS)                
        {                        
            USER_sSysStateErr.sSysTime.s32OverLUdcCount = UPDS_UNDERVOLTAGE_VOLTAGE_MS;                        
            USER_sSysStateErr.uSystemError.OverLUdcFlag = true;                        
            USER_sSysStateErr.sSystemStatePre = USER_sSysStateErr.sSystemState;                        
            USER_sSysStateErr.sSystemState = STATE_FAULT_G; // 低压保护                
        }        
    }
#endif
}

/**********************报警设置********************/
#define  UPDS_UDC_DETECTION                           (1)                //电压保护使能

/********************电压保护值***********************/
#define UPDS_UDC_REAL                                         (48.0f) //unit: V;电压源电压
#define UPDS_UDC_PROTECT_MAX_V                        (58.0f)        //unit: V;过压值
#define UPDS_UDC_PROTECT_MAX_V_recover        (54.0f) //unit: V;过压恢复值
#define UPDS_OVERVOLTAGE_VOLTAGE_MS         (10.0)  //Unit: ms 过电压持续时间1ms基准
#define UPDS_UDC_PROTECT_MIN_V                        (30.0f)        //unit: V;欠压值
#define UPDS_UDC_PROTECT_MIN_V_recover        (32.0f)        //unit: V;欠压恢复值
#define UPDS_UNDERVOLTAGE_VOLTAGE_MS         (14.0)  //Unit: ms 欠电压持续时间1ms基准

3):启动堵转保护

在堵转保护之中,我们同样设置了自恢复启动,但是自恢复启动有次数,只有五次。如果5次都没有启动成功,电机将停止转动。不会启动。

/启动堵转保护void US_vSysStuck_in(void) // 堵转保护启动5次
{        
    if ((USER_stuck_in.locked_rotor_flag == 1) || (ErrorCode == 3)) // 检测如果触发了一次堵转保护之后 电流大于额定值都没有启动        
    {                
        USER_stuck_in.locked_rotor_flag = 0;                
        USER_sSysStateErr.uSystemError.overStuck_in = true;           // 启动堵转标志                
        USER_sSysStateErr.uSystemError.GlobalErrorFlag = true; // 全局错误标志位                
        USER_sSysStateErr.sSystemStatePre = USER_sSysStateErr.sSystemState;                
        USER_sSysStateErr.sSystemState = STATE_FAULT_G; // 跳入错误判断                
        USER_sADSampleAll.s16VSPLPF = 0;                                   // 清空一下电位器                
        USER_stuck_in.Phase_Stuck_num++;                                   // 启动堵转报警加1        
    }
}

4):速度异常保护

检测目标速度和实际速度之比,如果较小或者较大就是速度异常

//速度异常保护
float value;
void US_vSysSPEED_ERRORCheck(void) // 速度异常保护
{
    if (USER_sADSampleAll.s16VSPLPF > 2500) // 启动之后        
    {                
        value = (float)USER_sADSampleAll.s16VSPLPF / (float)USER_sADSampleAll.s32SpdUse;                
        if ((value > 1.5) || (value < 0.5))                
        {                        
            USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount++;                        
            if (USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount >= UPDS_SPEED_IDLER_MS)                        
            {                                
                USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount = UPDS_CARRYING_IDLER_MS;                                
                USER_sSysStateErr.uSystemError.over_speed_error = true;                                
                USER_sSysStateErr.sSystemStatePre = USER_sSysStateErr.sSystemState;                                
                USER_sSysStateErr.sSystemState = STATE_FAULT_G;                        
            }                
        }                
        else                
        {                        
            USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount--;                        
            if (USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount <= 0)                        
            {                                
                USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount = 0;                                
                USER_sSysStateErr.uSystemError.over_speed_error = false;                        
            }                
        }        
    }
}

5):空载保护

空载保护是不让水泵电机空转,检测方法是检测电流大小,如果在水里,电流会比较大,只需要在速度达到某一点时,检测电流的大小就可以实现。

//空载保护void US_vSyscarrying_idlerCheck(void) // 空载保护
{        
    if (USER_sADSampleAll.s32SpdCommand > 3000) // 启动之后        
    {                
        if ((USER_sCurrentAll.IdcValue) < UPDS_CARRYING_IDLER_MAX)                
        {                        
            USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount++;                        
            if (USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount >= UPDS_CARRYING_IDLER_MS)                        
            {                                
                USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount = UPDS_CARRYING_IDLER_MS;                                
                USER_sSysStateErr.uSystemError.overload_protection = true;                                
                USER_sSysStateErr.sSystemStatePre = USER_sSysStateErr.sSystemState;                                
                USER_sSysStateErr.sSystemState = STATE_FAULT_G;                        
            }                
        }                
        else                
        {                        
            USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount--;                        
            if (USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount <= 0)                        
            {                                
                USER_sSysStateErr.sSysTime.s32OverIdcFirstlevelCount = 0;                                
                USER_sSysStateErr.uSystemError.overload_protection = false;                        
            }                
        }        
    }

3. 调试部分

下载程序即可上电调试;

接上电机的UVW三相,记得在代码中设置一下分压电阻,电流采样的电阻(如果没有更改就不需要更换)。四线插入正确,按下烧录键即可烧录成功。烧录后电机就3s缓启动正常运行起来了。需要检测电机的UVW三相,如果是如图9这样呈现梯形就可以。

9.png

10.png

4. 小结

以上即为本次无刷电机驱动板设计的全部基本内容。芯源推出的CW32F030系列产品,已全面实现-40℃ 至 105℃超宽温度范围和 1.65V~5.5V 超宽工作电压,面向最广泛的各种基础应用。用户可以根据自己的需求自行更改代码功能。

来源:CW32生态社区

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

围观 33

例程资料链接如下:

BD网盘链接:

https://pan.baidu.com/s/1JUMZH-sJAH-dPB0AtEvE7w

提取码:oe9t

一、简介 

1.薄膜式键盘是一种常见的输入设备,它由一层薄膜电路板和一层触摸膜组成。薄膜电路板上印有导电图案,而触摸膜则具有与之对应的按键区域。这种键盘的应用场景非常广泛,以下是几个典型的应用场景:

(1)电子产品:薄膜式键盘被广泛应用于各种电子产品中,如手机、平板电脑、数码相机等。由于其结构简单、体积小巧,可以很好地满足电子产品的设计需求。

(2)工业控制:在工业自动化领域,薄膜式键盘常用于控制面板和操作界面。它们具有防尘、防水、抗腐蚀等特性,能够适应恶劣的工作环境。

(3)医疗设备:医疗设备通常需要高度卫生和易清洁的特点,薄膜式键盘因其表面光滑、易擦拭的特性而被广泛应用于医疗设备中,如手术台、心电图仪等。

二、所需物料

本实验使用到了CW32-48F大学计划开发板、5*4薄膜式键盘模块、0.96寸OLED显示屏及Keil5开发环境。开发板上留有矩阵键盘接口,可以直接将模块插上使用。

1.png

CW32-48F大学计划板   

2.png

5*4薄膜式键盘

   3.png

键盘内部连线示意图

注:键盘的9根引线从左至右分别与单片机引脚PB15、PB14、PB13、PB12、PA6、PA5、PA4、PA1、PA0相连。

三、核心代码

main.c:
#include "main.h"
#include "OLED.h"
#include "Key.h"
#include "Delay.h"
#include "BTIM.h"

#define NUM_LENGTH 6

uint8_t choose_flag=0;    //选中标识
uint8_t choose_index=0;   //数组下标
uint8_t exert_flag=0;     //执行标识

uint8_t number[NUM_LENGTH]={0};    //存储6位数字
uint8_t num_index=0;      //数组下标    
char temp='.';            //默认值'.' 

int main()
{  
    uint8_t i;  
  uint8_t position=0;     //选中的数字在数组中的位置  
  OLED_Init();            //OLED显示  
  Key_GPIO_Init();        //5*4薄膜键盘GPIO初始化  
  BTIM_Init();            //定时器初始化,控制按键扫描周期  
  while(1)  
  {     
    if(exert_flag==1)     //若执行标识已打开    
    {      
      switch(temp)      
      {        
         case '<':           //选中左移          
         if(choose_flag==0) position=choose_index+1; //向左选中数字          
         if(position!=0)                              //若已有数字输入          
         {            
             choose_flag=1;                            //打开选中标识                    
           OLED_Clear_Row(2);                         //先清除已有标识符号‘^’            
           if(--position==0) position=choose_index;  //选中左移            
           OLED_ShowChar(2,position,'^');            //显示选中标识符号'^'          
         }          
         break;        
         case '>':          
         if(choose_flag==0) position=choose_index;   //向右选中数字          
         if(position!=0)                              //若已有数字输入          
         {            
             choose_flag=1;                            //打开选中标识            
             OLED_Clear_Row(2);                        //先清除已有标识符号'^'            
             if(++position==choose_index+1) position=1;//选中右移            
             OLED_ShowChar(2,position,'^');            //显示选中标识符号'^'          
         }          
         break;        
         case 'E':         
          choose_flag=0;                              //关闭选中标识          
          OLED_Clear_Row(2);                          //清除选中标识符号'^'          
          break;        
          default:          
          if(choose_flag==0)                           //若未打开选中标识          
          {            
              choose_index=num_index+1;                              
              if(num_index==0)                                      
              {              
                  OLED_Clear_Row(1);              
                  for(i=0;i<NUM_LENGTH;i++)                        //溢出清零                
                  number[i]=0;            
              }            
              number[num_index]=temp;            
              OLED_ShowNum(1,num_index+1,number[num_index],1);            
              num_index++;                              //输入右移            
              choose_index=num_index;            
              if(num_index==NUM_LENGTH) num_index=0;          
             }          
             else          
             {            
                  number[position-1]=temp;                      //若已打开选中标识,将获取的键值保存至数组中对应的位置            
                  OLED_ShowNum(1,position,number[position-1],1);//显示屏上覆盖原有数字          
             }          
             break;          
             }        
             exert_flag=0;        //执行后关闭标识    
        }      
     }
}

void BTIM1_IRQHandler(void)     //基本定时器1中断服务函数
{  
    static uint8_t cnt = 0;   
    if(BTIM_GetITStatus(CW_BTIM1,BTIM_IT_OV))    
    {      
        if(++cnt>=18)     
        {      
            cnt=0;      
            temp=Key_Scan(); //每180ms执行一次按键扫描,返回值赋值给temp      
            if(temp!='.')  exert_flag=1;    //打开执行标识    
        }    
        BTIM_ClearITPendingBit(CW_BTIM1,BTIM_IT_OV); //清除标志位  
    }
}
Key.c:
#include "Key.h"
#include "main.h"
#include "Delay.h"
#include "OLED.h"

#define  ROW_PORT CW_GPIOA      //键盘行引脚端口
#define  COL_PORT CW_GPIOB      //键盘列引脚端口

#define  ROW_NUM  4     //4行
#define  COL_NUM  4     //4列

uint16_t row_pins[ROW_NUM]={GPIO_PIN_1,GPIO_PIN_4,GPIO_PIN_5,GPIO_PIN_6};     //每一行所对应的引脚

uint16_t col_pins[COL_NUM]={GPIO_PIN_15,GPIO_PIN_14,GPIO_PIN_13,GPIO_PIN_12}; //每一列所对应的引脚

char key_value[ROW_NUM][COL_NUM]={    //键值  
    1,   2,  3,   '(',   
    4,   5,  6,   ')',  
    7,   8,  9,   'E',  
    '<', 0,  '>', 'Y'
};

void Key_GPIO_Init(void)
{  
    __RCC_GPIOA_CLK_ENABLE();  
    __RCC_GPIOB_CLK_ENABLE();  
     
    //rows-->置行      
    GPIO_InitTypeDef GPIO_InitStruct;   
    GPIO_InitStruct.IT=GPIO_IT_NONE;  
    GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;      //推挽输出  
    GPIO_InitStruct.Pins=row_pins[0]|row_pins[1]|row_pins[2]|row_pins[3];  
    GPIO_InitStruct.Speed=GPIO_SPEED_HIGH;  
    GPIO_Init(ROW_PORT, &GPIO_InitStruct);   
    
    //cols-->检列  
    GPIO_InitStruct.Mode=GPIO_MODE_INPUT_PULLUP;   //上拉输入  
    GPIO_InitStruct.Pins=col_pins[0]|col_pins[1]|col_pins[2]|col_pins[3];  
    GPIO_Init(COL_PORT, &GPIO_InitStruct);
}

char Key_Scan(void)
{  
    uint8_t i,j;  
    char key = '.';  //默认值'.'   
    
    for ( i = 0; i < ROW_NUM; i ++ )   //1-4行依次置低  
    {    
        GPIO_WritePin(ROW_PORT,row_pins[i],GPIO_Pin_RESET);    
        for( j = 0; j < COL_NUM; j ++ )  //依次检测1~4列电平    
        {      
            if( GPIO_ReadPin(COL_PORT,col_pins[j])==RESET )  //如果检测到低电平,则代表有按键按下      
            {        
                key = key_value[i][j];    //获取键值        
                break;            //跳出检列循环      
            }    
        }    
        GPIO_WritePin(ROW_PORT,row_pins[i],GPIO_Pin_SET);  //本行恢复高电平,准备置低下一行    
        if(key != '.') break; //若key不是默认值,则代表已检测到按键按下,退出置行循环,结束本次按键扫描  
    }   
    
    return key;  //返回键值
}

四、效果演示

4.png

5.png

6.png

7.png

来源:CW32生态社区

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

围观 19

目前截止时间不限,只要想参加,联系小助理即可参与!

/ 关于作者/

1694051305885876.png

雷超林,硬件工程师,研究方向嵌入式,电力电子;省级以上竞赛奖项7项,入选湖南省教育厅燎原计划。

一、描述

1.项目说明

使用武汉芯源半导体CW32系列MCU作为主控一款小巧的MP3。采用CW32L系列低功耗芯片。

2.项目相关功能

  1. 锂电池供电,TYPE-C接口充电,电量显示;

  2. 功耗管理;

  3. 3.5mm音频输出接口,可不设计外置扬声器模式;

  4. 屏幕显示:歌词名、歌词、电量等;可设置屏幕使用亮度、时间进行功耗管理;

  5. SD卡插入,并支持读取歌曲信息;

3.项目属性

本项目为首次公开,为本人原创项目。项目未曾在别的比赛中获奖。

4.项目进度 

5.20:开始画原理图

5.23:画PCB

5.27:焊接完毕,开始调程序

6.15第一版完成

6.18第二版开始

7.12全部完成

二、设计原理

设计框架

2.png

上图为整个MP3的主体系统框架,先是由电池提供4.2V电压给电源管理,电源管理再转化为3.3V以及1.8V的电压给整个系统,

主控将内存卡里的信息提取转为二进制输入到音频解码芯片(VS1053B)里,同时在OLED显示屏上显示信息。用独立按键可以控制播放以及暂停等信息。

设计难点

低功耗(解决方案:电源使用业界领先的电荷泵降压芯片,芯片采用CW32L系列低功耗主控)

1.主控

3.png

4.png

主控采用CW32L031C8U6,ARM® Cortex®-M0+ 32 位低功耗微控制器 64K 字节 FLASH,8K 字节 RAM,最高主频 48MHz,支持最多 39 路 I/O 接口。

2.音频解码芯片

5.png

VS1053B是一款高性能音频编解码模块,支持:MP3/WMA/OGG/WAV/FLAC/MIDI/AAC等音频格式的解码,并支持:OGG/WAV音频格式的录音,支持高低音调节以及EarSpeaker空间效果设置,功能十分强大

它包含了一个高性能、有专利的低功耗DSP 处理器内核VS_DSP4、工作数据存储器、供用户应用程序和任何固化解码器一起运行的16 KiB 指令RAM 及0.5KiB 多的数据RAM、串行的控制和输入数据接口、最多8 个可用的通用I/O引脚。

3.独立按键

6.png

7.png

独立按键采用轻触按键以及拨片按键,符合人体工程学,按起来非常舒服

4.电源管理

8.png

电源管理采用电池充电芯片TP4054,TP4054是一个完善的单片锂离子电池恒流/恒压线形电源管理芯片。它薄的尺寸和小的外包装使它便于便携用。更值得一提的是,TP4054专门设计适用于USB的供电规格。得益于内部的MOSFET结构, 在应用上不需要
外部电阻和阻塞二极管。在高能量运行和高外围温度时,热反馈可以控制充电电流以降低芯片温度。 

电源芯片采用TX4310B是一款低噪声,恒定频率(1.2MHz)开关电容器倍压器。
TX4310B从1.8V至5V输入产生稳定的输出电压。外部元件数量较少(VDD和VOUT处有一个快速电容和两个小旁路电容)使得芯片非常适用于电池供电的小型应用。
电荷泵架构可保持恒定的开关频率以实现空载稳压输出,并降低输出和输入波纹。 

5.OLED显示屏

9.png

10.png

OELD采用0.96寸 蓝色 128x64像素分辨率 单色 30PIN,主控芯片    SSD1315

6.外接接口电路

11.png

外接电路有TF卡插槽,下载接口以及3.5mm音频接口,这里我们注意TF卡采用SPI通信,用了10K的电阻进行上拉,保证通信的稳定性。

三、软件说明

12.png

程序较为复杂,采用了状态机以及文件系统,增加了息屏之后进入低功耗模式,得益于我们CW32L031系列优异的低功耗性能,可以连续播放音乐12小时,

我们在sd卡内需要放置字库文件,在我工程的附件中下载,通过读卡器写入到SD卡中才能工作,否则开机会提示

四、实物展示

13.jpg

14.jpg

15.jpg

16.jpg

来源:CW32生态社区

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

围观 24

例程资料链接如下:

BD网盘链接:

链接:https://pan.baidu.com/s/1nkBLRwc6rv2qnBTfgyBdgQ

提取码:1kxe

一、简介 

1. L9110风扇模块是一种常见的电机驱动模块,可以用于控制小型直流风扇的转动,常被用于:

(1) 电子设备散热:将L9110风扇模块连接到需要散热的电子设备上,通过控制风扇的转速来提高设备的散热效果,保持设备的正常工作温度。

(2) DIY电子项目:L9110风扇模块是制作各种DIY电子项目的理想选择。通过将其与微控制器板结合使用,可以构建自己的智能风扇、温度控制系统等。这为爱好者提供了灵活性和创造力的发挥空间。

(3) 模型制作:L9110风扇模块也可以在模型制作领域中找到应用。通过将风扇模块嵌入模型中,并通过控制模块来改变风扇的速度和转向,可以增加模型的真实感和互动性。

2.本实验用到了CW32F030C8T6小蓝板、L9110风扇模块、LED交通信号灯模块、轻触微动立式按键开关及Keil5开发环境。

1.png

2.png

风扇三档转速调节系统

二、风扇三档转速调节系统说明

(1)L9110风扇模块

 3.png

L9110风扇模块,可控制正反转,具有安装孔,可以吹灭20cm外的打火机或蜡烛火焰,经常被用于灭火机器人之上。

【连线】:VCC连5V,GND连GND,INA连PA0,INB连PA1

(2)LED交通信号灯模块

 4.png

【连线】:GND连GND,R连PC13,Y连PC14,G连PC15

(3)轻触微动立式按键开关

 5.png

【连线】:VCC连+3.3V,GND连GND,OUT接PB9

三、核心代码

L9110.c:
#include "L9110.h"
#include "GTIM.h"

void L9110_GPIO_Init()      //INA接PA0,INB接PA1
{  
    __RCC_GPIOA_CLK_ENABLE(); 
      
    GPIO_InitTypeDef GPIO_InitStruct;  
    GPIO_InitStruct.IT = GPIO_IT_NONE;   
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出模式  
    GPIO_InitStruct.Pins = GPIO_PIN_0|GPIO_PIN_1;  
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;  
    GPIO_Init(CW_GPIOA, &GPIO_InitStruct);  
     
    PA00_AFx_GTIM2CH1();  //PA0引脚复用为GTIM通道1  
    PA01_AFx_GTIM2CH2();  //PA1引脚复用为GTIM通道2
}

void Turn_around(int16_t value)  //风扇转动函数
{  
    if(value>0)  
    {    
        GTIM_SetCompare1(CW_GTIM2,value); //设置CRR1的值为value    
        GTIM_SetCompare2(CW_GTIM2,0);  
    }  
    else  
    {    
        GTIM_SetCompare1(CW_GTIM2,0);    
        GTIM_SetCompare2(CW_GTIM2,-value);//设置CRR2的为value  
    }
}

GTIM.c:
#include "GTIM.h"

void GTIM2_Init(void) //输出PWM到INA和INB引脚
{  
    RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_GTIM2,ENABLE);  //使能APB外设时钟   
    
    GTIM_InitTypeDef GTIM_Initstruct;  
    GTIM_Initstruct.Mode = GTIM_MODE_TIME;  //工作模式-->定时器模式  
    GTIM_Initstruct.OneShotMode = GTIM_COUNT_CONTINUE;//连续计数模式  
    GTIM_Initstruct.ToggleOutState = DISABLE;  //电平反转失能  
    GTIM_Initstruct.Prescaler = BTIM_PRS_DIV64;  //预分频  
    GTIM_Initstruct.ReloadValue =1000-1;//计数重载周期,16bit自动重载寄存器ARR,ARR的值最大为65535  
    GTIM_TimeBaseInit(CW_GTIM2,&GTIM_Initstruct);  
    //定时时长=预分频/计数器时钟源频率*(计数重载周期+1),即T=64/64000000*1000s=1ms  
    GTIM_OCInit(CW_GTIM2,GTIM_CHANNEL1,GTIM_OC_OUTPUT_PWM_LOW);//向GTIMx_CCMR寄存器中的CCyM 位写入0xF  
    GTIM_OCInit(CW_GTIM2,GTIM_CHANNEL2,GTIM_OC_OUTPUT_PWM_LOW);  
    //当 GTIM2_CNT <= GTIM2_CCR1(GTIM2_CCR2)时,CH1(CH2)通道输出高电平,否则输出低电平  
    GTIM_Cmd(CW_GTIM2,ENABLE); //GTIM2使能   GTIM_SetCompare1(CW_GTIM2,0);   GTIM_SetCompare2(CW_GTIM2,0);  //GTIM2_CCR1(GTIM2_CCR2)中的比较值设为0,CH1(CH2) 通道输出保持为低电平
}
main.c
#include "main.h"
#include "LED.h"
#include "L9110.h"
#include "GTIM.h"
#include "Key.h"

int main()
{  
    LED_Init();       //三个LED灯用来指示风扇转动状态  
    L9110_GPIO_Init();//L9110风扇模块引脚初始化配置  
    Key_GPIO_Init();  //轻触微动立式按键开关用来进行三档转速调节  
    GTIM2_Init();     //输出PWM到INA,INB引脚  
    while(1)  
    {    
        Key_Scan();     //扫描按键并执行相应功能    
        LED_Indicator();//指示灯  
    }
}

四、效果演示+说明

(1)系统上电处于0档,风扇不转,红色LED灯点亮

 6.png

(2)第一次按下按键开关,系统设置为正向一档,风扇满占空比旋转,风力达到最大,同时红灯熄灭,黄灯点亮,代表风扇顺时针旋转。此后第二次、第三次按下开关,转速依次下降,第四次按下开关,系统回到0档

7.png

(3)第五次按下按键开关,系统设置为反向一档,风扇满占空比旋转,风力达到最大,同时红灯熄灭,绿灯点亮,代表风扇逆时针旋转。此后第六次、第七次按下开关,转速依次下降,第八次按下开关,系统回到0档

8.png

来源:CW32生态社区

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

围观 13

例程资料链接如下:

BD网盘链接:

链接:https://pan.baidu.com/s/1nkBLRwc6rv2qnBTfgyBdgQ
提取码:1kxe

一、简介 

1. L9110风扇模块是一种常见的电机驱动模块,可以用于控制小型直流风扇的转动,常被用于:(1) 电子设备散热:将L9110风扇模块连接到需要散热的电子设备上,通过控制风扇的转速来提高设备的散热效果,保持设备的正常工作温度。(2) DIY电子项目:L9110风扇模块是制作各种DIY电子项目的理想选择。通过将其与微控制器板结合使用,可以构建自己的智能风扇、温度控制系统等。这为爱好者提供了灵活性和创造力的发挥空间。(3) 模型制作:L9110风扇模块也可以在模型制作领域中找到应用。通过将风扇模块嵌入模型中,并通过控制模块来改变风扇的速度和转向,可以增加模型的真实感和互动性。

2.本实验用到了CW32F030C8T6小蓝板、L9110风扇模块、LED交通信号灯模块、轻触微动立式按键开关及Keil5开发环境。

1.png

2.png

风扇三档转速调节系统

二、风扇三档转速调节系统说明

(1)L9110风扇模块 

3.png

L9110风扇模块,可控制正反转,具有安装孔,可以吹灭20cm外的打火机或蜡烛火焰,经常被用于灭火机器人之上。

【连线】:VCC连5V,GND连GND,INA连PA0,INB连PA1

(2)LED交通信号灯模块 

4.png

【连线】:GND连GND,R连PC13,Y连PC14,G连PC15

(3)轻触微动立式按键开关 

5.png

【连线】:VCC连+3.3V,GND连GND,OUT接PB9

三、核心代码

L9110.c:
#include "L9110.h"
#include "GTIM.h"

void L9110_GPIO_Init()      //INA接PA0,INB接PA1
{  
    __RCC_GPIOA_CLK_ENABLE();   
    GPIO_InitTypeDef GPIO_InitStruct;  
    GPIO_InitStruct.IT = GPIO_IT_NONE;   
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出模式  
    GPIO_InitStruct.Pins = GPIO_PIN_0|GPIO_PIN_1;  
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;  
    GPIO_Init(CW_GPIOA, &GPIO_InitStruct);   
    
    PA00_AFx_GTIM2CH1();  //PA0引脚复用为GTIM通道1  
    PA01_AFx_GTIM2CH2();  //PA1引脚复用为GTIM通道2
}

void Turn_around(int16_t value)  //风扇转动函数
{  
    if(value>0)  
    {    
        GTIM_SetCompare1(CW_GTIM2,value); //设置CRR1的值为value    
        GTIM_SetCompare2(CW_GTIM2,0);  
    }  
    else  
    {    
        GTIM_SetCompare1(CW_GTIM2,0);    
        GTIM_SetCompare2(CW_GTIM2,-value);//设置CRR2的为value  
    }
}
GTIM.c:
#include "GTIM.h"

void GTIM2_Init(void) //输出PWM到INA和INB引脚
{  
    RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_GTIM2,ENABLE);  //使能APB外设时钟   
    
    GTIM_InitTypeDef GTIM_Initstruct;  
    GTIM_Initstruct.Mode = GTIM_MODE_TIME;  //工作模式-->定时器模式  
    GTIM_Initstruct.OneShotMode = GTIM_COUNT_CONTINUE;//连续计数模式  
    GTIM_Initstruct.ToggleOutState = DISABLE;  //电平反转失能  
    GTIM_Initstruct.Prescaler = BTIM_PRS_DIV64;  //预分频  
    GTIM_Initstruct.ReloadValue =1000-1;//计数重载周期,16bit自动重载寄存器ARR,ARR的值最大为65535  
    GTIM_TimeBaseInit(CW_GTIM2,&GTIM_Initstruct);  
    //定时时长=预分频/计数器时钟源频率*(计数重载周期+1),即T=64/64000000*1000s=1ms  
    GTIM_OCInit(CW_GTIM2,GTIM_CHANNEL1,GTIM_OC_OUTPUT_PWM_LOW);//向GTIMx_CCMR寄存器中的 CCyM 位写入0xF  
    GTIM_OCInit(CW_GTIM2,GTIM_CHANNEL2,GTIM_OC_OUTPUT_PWM_LOW);  
    //当 GTIM2_CNT <= GTIM2_CCR1(GTIM2_CCR2)时,CH1(CH2)通道输出高电平,否则输出低电平  
    GTIM_Cmd(CW_GTIM2,ENABLE); //GTIM2使能   
    
    GTIM_SetCompare1(CW_GTIM2,0);   
    GTIM_SetCompare2(CW_GTIM2,0);  
    //GTIM2_CCR1(GTIM2_CCR2)中的比较值设为0,CH1(CH2) 通道输出保持为低电平
}
    
main.c
#include "main.h"
#include "LED.h"
#include "L9110.h"
#include "GTIM.h"
#include "Key.h"

int main()
{  
    LED_Init();       //三个LED灯用来指示风扇转动状态  
    L9110_GPIO_Init();//L9110风扇模块引脚初始化配置  
    Key_GPIO_Init();  //轻触微动立式按键开关用来进行三档转速调节  
    GTIM2_Init();     //输出PWM到INA,INB引脚  
    while(1)  
    {    
        Key_Scan();     //扫描按键并执行相应功能    
        LED_Indicator();//指示灯  
    }
}

四、效果演示+说明

(1)系统上电处于0档,风扇不转,红色LED灯点亮

 6.png

(2)第一次按下按键开关,系统设置为正向一档,风扇满占空比旋转,风力达到最大,同时红灯熄灭,黄灯点亮,代表风扇顺时针旋转。此后第二次、第三次按下开关,转速依次下降,第四次按下开关,系统回到0档

7.png

(3)第五次按下按键开关,系统设置为反向一档,风扇满占空比旋转,风力达到最大,同时红灯熄灭,绿灯点亮,代表风扇逆时针旋转。此后第六次、第七次按下开关,转速依次下降,第八次按下开关,系统回到0档

8.png

来源:CW32生态社区

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

围观 20

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

BD网盘链接:

链接:https://pan.baidu.com/s/1g_fN3YCBw8RINsl9Pe9c6Q

提取码:knxt

一、简介

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

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

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

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

1.png

2.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)。

围观 31

例程资料链接如下:

BD网盘链接:

https://pan.baidu.com/s/1q7b0_AOx_rUxi_XPcJL7zw 

提取码:vp02

一、简介

 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)。

围观 32

本章针对CW32F030C8T6的时钟树进行详细解析,续第一章启动文件的相关讲解。

上一章讲到,单片机的启动文件会使用单片机内部的RC振荡器作为单片机的启动时钟,并且该时钟频率被设置为48MHz。但讲解有疏漏,根据编程手册的描述,该48MHz仅为HSI频率,而非输送到时钟总线上的频率,在进入时钟总线之前,该时钟会被分频(也就是降低频率),在不进行任何配置的情况下,这个48M的信号会被6分频。

1.png

这个图包含很多信息,但是图中的彩色字信息并非全部来自此图,更多的信息需要结合代码和寄存器描述来获得:

1.HSI被配置为48M是通过启动文件中的SystemInit函数得知的。

2.系统选择HSI作为启动时钟是通过编程手册“时钟与复位”一章得知的。

3.HSI之后的预分频器被设置为6分频,是通过查看指示该分频器的寄存器得知的。

下面以信息3为例,讲解如何查看此图中的相关寄存器:

  • 在经过SYSCTRL_CR0.SYSCLK寄存器选择之后,系统时钟SysClk会在经过分频后送入内核与各个外设,这一信息流程图与粉色箭头已经清晰展示。

  • 在手写箭头指向的方框中,有1、...、16的字样,表示该预分频器可以进行这些倍数的分频。所谓分频比如48M,6分频,其分频结果就是48M/6 = 8M,2分频就是24M,简单的除法。

  • 方框下方是该分频器对应的控制寄存器,寄存器的名字并不一定完全叫这个,但是这个名字可以很容易就被理解为预分频器控制相关的寄存器,该寄存器名字拆解版本是:SYS(系统)CTRL(控制)HSI.DIV(分频),这套命名系统是通用的,即使使用的是别的单片机,也可以根据这套命名规则快速确认寄存器的功能。

  • 通过查看编程手册时钟与复位章节的寄存器描述,此寄存器DIV位的值默认为6。而HSI频率的设置则是该寄存[10:0]位的TRIM位决定的。

2.png

只需要沿着紫色箭头的方向配置相关的寄存器,单片机就能够正常启动,但这一步并不需要开发者亲自去做,芯片厂家提供的启动文件和库可以自动完成这一步。但不论怎么说RC振荡器的精度有限,且8M的速度放在48MHz主频的内核上也确实不够看,因此大部分时候,都需要使用外部晶振提供的时钟,通过锁相环倍频之后达到48M,最后通过时钟线送入内核和外设。

下面讲解怎么配置才能得到48MHz的高精度高速时钟信号:

3.png

首先,电路板上需要有一个在范围内的晶体,晶体的两个引脚需要在外围电路的配合下连接到单片机的晶体输入引脚,同时IO需要工作在正确的工作模式。

4.png 

硬件部分准备完成了,接下来就是配置HSE和PLL相关的寄存器了。下面是相关的配置代码:

5.png

  • 首先需要打开HSE功能,允许单片机接收HSE提供的震荡信号。

  • 然后配置PLL的分频系数,这将决定锁相环的输出频率,此处设置为1分频,也就是不分频。

  • 使能PLL功能,并告知用到的时钟源、时钟频率、倍频系数,这对应三个入口参数。

  • 将flash的等待周期设置为3个时钟周期,部分单片机需要进行这一步操作,原因会在后面细说。

  • 进行时钟切换,按照注释完成准备工作之后即可切换。

先查看HSE使能函数: 

6.png

仅展示主体部分,函数注释未列出,但CW32的时钟配置库函数注释相对来说很详细,推荐配置都写在函数注释里面,不懂得寄存器配置的小伙伴可以直接根据推荐进行配置,如果有更深入的需求,直接查看芯片手册对应的寄存器描述即可。

后续的参数配置直接根据注释进行推荐配置即可,在PLL与HSE相关的寄存器配置完成之后,48M的时钟信号就已经产生了,只不过系统的时钟源还不是这个(记得那个梯形的选择器吗?),下一步就是切换时钟源,让系统工作在48M的频率下。

切换时钟?我知道你很急,但是先别急,虽然一般情况下确实可以直接切换了,但是CW32有个需要注意的地方,那就是flash的配置。

为什么CW32需要进行flash配置之后才能切换时钟源?

我们都知道,写的程序都存储在flash中,等到需要执行程序的时候,CPU会和flash进行通信,取出flash中的指令然后执行。问题来了,既然程序需要通过某种通信方式传输到CPU,那这个通信传输的速度一定有个上限,这个上限就是flash的读写速度上限,当flash的实际读写速度与其允许的读写速度不匹配时,flash与CPU之间的通信就会出现问题,之后就可以理解为CPU有高速取指令需求,但是flash无法在这个速度下跟上CPU的请求速度,这个通信就断了,程序就不动了。因此我们需要设置flash,让他多等几个时钟周期再响应,这样flash就能适应更高频率的读取请求,并且在此程序的配置中,需要使能预取指令和缓存功能,这两个功能可以让flash与CPU更好地配合以实现单片机的高速运行。障碍都解决了,接下来真的只需要切换时钟即可。

对比部分:这一部分会列出stm32f103c8t6单片机的标准库上电时钟设置代码进行对比。

7.png

8.png

这里的条件编译,我选择最后一条,如果需要上电设置为别的频率,只需要在同一个文件中把宏定义注释取消即可。

9.png

下面列出设置时钟到72M函数的一部分,此函数与CW32的时钟初始化顺序几乎是一模一样,首先需要将HSE使能并等待时钟稳定,之后设置PLL的参数,配置flash为2个等待周期,再使能PLL输出,等待PLL时钟稳定再切换时钟。

10.png11.png

12.png

可以看出,即使是不同的芯片,他们在大部分地方的操作也是一样的,只是一些细节上有些许不同。

总结:

1.本章简单展示了如何查找手册来配置寄存器,且再次强调库函数的本质就是操作寄存器。

2.单片机都会有一个时钟树,时钟树的图可以在编程手册(不是数据表和内核手册)中找到。

3.部分单片机想要工作在高工作频率下,需要设置flash等待时间并打开缓存和预取指令使能。

来源:CW32生态社区

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

围观 40

页面

订阅 RSS - CW32