CW32

摘要:

芯片超强抗干扰性能加持的角磨机产品方案,全温度范围稳定可靠!无惧各种复杂工况!芯片超宽供电电压带来更多开发可能!真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)。

围观 55

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

围观 21

例程链接:

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

围观 10

例程网盘链接:

https://pan.baidu.com/s/1gkXa-v_cXPgTv1pXPPEmRA?pwd=l54c 

提取码:l54c

飞书链接:https://dwi41yhz703.feishu.cn/docx/HY4Kd4bXToW2HOxD5wucp8zEnWf?from=from_copylink 

立创开源硬件平台链接:

https://oshwhub.com/beauty_light/e_hand_drill 

一、项目介绍

前言

手电钻是一种常见的便携式电动工具,它由一个电动马达和一个可充电电池组成。这个电动马达通过传递旋转动力给机头上的钻头,从而实现钻孔、螺丝拧紧、切割或打磨等任务。一般来说,手电钻通常有这几个重要的部件:

(1)电动马达:它是手电钻的核心部件,负责产生动力并驱动钻头旋转。不同型号的手电钻功率不同。高功率的电动马达可以提供更大的扭矩和更高的转速,适用于处理坚硬材料或需要更快速度的工作。

(2)钻头:钻头是安装在手电钻机头上的可更换配件,用于进行钻孔或切割操作。钻头的尺寸和类型根据不同的工作需求而变化,例如木材钻头、金属钻头、混凝土钻头等。一些手电钻还可以配备其他类型的机头,如螺丝刀头、砂轮切割头等。

(3)电池:手电钻通常使用可充电电池供电,这样可以使其更加便携和灵活。电池容量的大小决定了工作时间的长短,较大容量的电池可以提供更长时间的使用。一些手电钻还支持快速充电功能,在短时间内充满电池以提高工作效率。

手柄和握把:手电钻通常具有一个手柄和一个握把,使操作更加稳定和舒适。手柄是保持平衡和稳定的主要手部支撑,而握把则用于按下触发器按钮和控制手电钻的方向。

(4)手电钻的设计和功能使其成为许多家庭维修、装饰和建筑工作中不可或缺的工具。它的便携性、灵活性和易于使用使得用户可以在各种材料上进行精确的钻孔和切割操作。

1.png

2.CW32手电钻驱动板介绍

1.2.1 简介

在本项目中,我们使用CW32手电钻驱动板驱动手电钻的无刷电机,电池使用18V电池组。建议功率100~200W;该驱动板具有欠压保护、过压保护、过流保护、堵转保护、电量检测、正反转切换,刹车等功能。可广泛应用于各类无刷电机的控制。

1.2.2 项目组成

(1)CW32主控芯片:

控制器的选择对于整个项目来说特别重要。好的控制器能产生事半功倍的效果,这是整个项目的大脑,它负责控制电机的运行和参数调节。而CW32主控芯片的高性能和低功耗的特点,完全能胜任整个项目的要求。

(2)无刷电机:

无刷电机是项目的核心部分,相比传统的有刷直流电机,它寿命更长、稳定性更好、效率更高,能产生的功率更大。

(3)硬件电路:

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

(4)软件编写:

包含电机驱动代码,高压,低压保护。电流保护,速度异常保护,空载保护、电量检测、正反转切换,刹车等。

二、电路设计说明

CW32手电钻驱动板硬件设计包含3个部分:无刷驱动电路,电源控制电路,单片机及外设。

2.png

2.1 无刷驱动部分

无刷电机的驱动又分为反电动势检测、电流采集、电源电压采集,和MOS驱动电路。

2.1.1 反电动势检测

根据法拉第电磁感应定律和楞次定律,转子旋转的时候会在定子绕组中产生感应电势,该感应电势的方向与绕组的电压相反,极性与励磁电压相反,故一般称为反电动势或反电势。

3.png

 本次使用的反电动势采样电路如下,一段接电机的三个相线,另外一端接单片机的三个ADC输入引脚,中间使用电阻对反电动势的电压进行分压。

4.png

2.1.2 电源电压采集

在无刷电机运转过程中,需要在每个控制周期将母线电压与悬空相的端电压进行比较,从而获得反电动势过零点,在检测到反电动势过零之后,延时30°电角度即可进行换相操作。在PWM的关断时间内采集时,此时的理论的中性点电压为0V;在PWM的开通时间内采集时,此时理论的母线电压为电源电压的一半。

5.png

由于在TON时刻采样时需要母线电压的一半与反电动势进行比较,以确定零点的位置,所以在电路设计中,我们将直接将电源电压采集电路的分压电阻设置为200k和5.1k,为上述的反电动势采集电路的分压比的二分之一,这样ADC采集出来的值就不再需要多余的运算,减少了MCU的负担

6.png

2.1.3 电流采集

本项目中使用运放对采样电阻上的电压进行放大,偏置电压为1V左右,放大倍率为11倍。

7.png

2.1.4 MOS驱动电路

本项目中栅极MOS的驱动芯片使用的是FD6288。

FD6288T&Q 是一款集成了三个独立的半桥栅极驱动集成电路芯片,专为高压、高速驱动 MOSFET 设计,可在高达+250V 电压下工作。

FD6288T&Q 内置 VCC/VBS 欠压(UVLO)保护功能,防止功率管在过低的电压下工作。FD6288T&Q 内置直通防止和死区时间,防止被驱动的高低侧 MOSFET 直通,有效保护功率器件。

FD6288T&Q 内置输入信号滤波,防止输入噪声干扰。

8.png

2.2 电源控制电路

2.2.1 上电逻辑

9.png

1、当手电钻上的滑块按下时,上图中的NO_4和NO_5会连在一起,VIN将通过二极管和限流电阻流经三极管的基极,最后返回电源负极。

2、Q3三极管基极正偏,三极管导通。

3、Q1三极管基极接地,三极管导通,

4、VIN经过稳压二极管后电压降为11.5V左右

5、经过一个LDO,11.5V的电压被稳到了5V,用于给单片机供电。

6、当单片机得电后,会将ON-OFF_CONTROL引脚拉高,使Q3三极管一直处于开通状态,整个电源部分完成自锁。

2.3 单片机及外设

本项目使用的是CW32F030系列的MCU,其采用先进的处理器架构和高频率运行的CPU,具备较高的计算能力和响应速度。它可以处理复杂的算法和任务,并且在实时性要求较高的应用中有良好的性能表现。并且CW32通过优化的电源管理技术和低功耗设计,可以在工作时保持较低的功耗。CW32是一种广泛应用的单片机,具有庞大的开发社区和资源支持。开发者能够方便地获取到丰富的官方文档、软件开发工具和例程等,以及来自其他开发者的经验分享和技术支持。

2.3.1 CW32单片机核心系统

因为本项目不需要用到高精度时钟和低速晶振进行长时间计时,因此则省略这部分电路。

10.png

2.3.2 指示灯

在电路板上,有三颗LED用于指示当前电池电量。三颗LED与MCU的连接均为灌电流模式

11.png

2.4 PCB实物图

12.jpg

三、程序编写说明

直流无刷电机由于定子绕组的反电动势与电机的转速成正比,所以电机在静止时反电动势为零或低速时反电动势很小,此时无法根据反电动势信号确定转子磁极的位置。因此,反电动势法需要采用特殊启动技术,从静止开始加速,直至转速足够大。通过反电势能检测到过零时,再切换至直流无刷电机运行状态。这个过程称为“三段式”启动,主要包括转子预定位、加速和运行状态切换三个阶段。这样既可以使电机转向可控,又可以保证电机达到一定转速后再进行切换,保证了启动的可靠性。

1、电机转子预定位

若要保证直流无刷电机能够正常启动,首先要确定转子在静止时的位置。

在小型轻载条件下,对于具有梯形反电势波形的直流无刷电机来说,一般采用磁制动转子定位方式。系统启动时,任意给定一组触发脉冲,在气隙中形成一个幅值恒定、方向不变的磁通。只要保证其幅值足够大,那么这一磁通就能在一定时间内将电机转子强行定位在这个方向上。

在应用中,可以在任意一组绕组上通电一定时间,其中预定位的PWM占空比和预定位时间的长短设定值可由具体电机特性和负载决定,在实际应用中调试而得。

在预定位成功后,转子在启动前可达到预定的位置,为电机启动做好准备。

2、电机的外同步加速

确定了电机转子的初始位置后,由于此时定子绕组中的反电动势仍为零,所以须人为改变电机的外施电压和换相信号,使电机由静止逐步加速运动。这一过程称为外同步加速。对于不同的外施电压调整方法和换相信号调整方法,外同步加速可划分为三类:

  • 换相信号频率不变,逐步增大外施电压使电机加速,称为恒频升压法。

  • 保持外施电压不变,逐渐增高换相信号的频率,使电机逐步加速,称为恒压升频法。

  • 在逐步增大外施电压的同时,增高换相的频率,称为升频升压法。

各个方法都有其优点和缺点。如升频升压法是人为地给电机施加一个由低频到高频不断加速的他控同步切换信号,而且电压也在不断地增加。通过调整电机换相频率,即可调整电机启动的速度。调整方法比较简单。但是这个过程较难实现。切换信号的频率的选择要根据电机的极对数和其他参数来确定。太低,电机无法加速;太高,电机转速达不到,会有噪声甚至无法启动,算法比较困难。

无论哪种方法,该过程都是在未检测到反电动势信号时进行。因此对于控制系统来说此段电机控制是盲区。参数在调试好的时候,可以快速切换至正常运行状态;而参数不理想时,电流可能不稳甚至电机会抖动。因此,在应用中,应根据电机及负载特性设定合理的升速曲线,并在尽可能短的时间内完成切换。

3、电机运行状态的转换

当电机通过外同步加速到一定的转速,反电势信号可以准确检测时,即可由外同步向自同步切换。可以通过试验观察反电势信号能够被准确检测的转速。在进行切换有两种方法:一种是测速模块可以测出电机的转速,当达到这一转速时即可进行切换;另一种,通过试验检测出达到预定切换转速的时间,通过软件定时器设置切换时间。

这一步是关键也是比较难实现的一步。有时软件或者硬件设计的不合理都可能导致启动失败。通常是采用估算的方式来选择切换速度。

通过上面的分析可知,无位置传感器直流无刷电机控制系统的难点就是加速及切换阶段。当电机顺利启动后,就可以对电机进行调速操作。其中,无位置传感器直流无刷电机和有位置传感器直流无刷电机调速原理一致。但,由于无感三段式启动过程,转子位置检测无效。因此,对电机进行的速度PID闭环控制,须在电机启动顺利完成后进行。

本项目的程序框图如下:在程序上电后,首先进行各种外设的初始化,完成后进入while循环里轮询执行代码。

13.png

免责声明:项目仅供学习和开发评估参考。实际开发项目请根据需求合理选型MCU。

来源:CW32生态社区

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

围观 37

本章以CW32通用定时器为例介绍单片机定时器的用法。

定时器是单片机中一个非常传统且重要的外设,定时器的本质其实就是一个计数器,只不过被计数的对象是定时器的时钟源。定时器要正常工作,需要这几个要点步骤:有时钟源输入、计数器工作、有一个可以比较的值(自动重装载值)。其工作流程是这样的:计数器会随着时钟源对时钟源提供的脉冲进行计数,计数值不断上涨(或下降),如果计数值和自动重装载值一样,那么计数器的值就会被硬件清零重新计数,这个清零重新计数被称为定时器计数溢出,这个事情会触发一个中断,被叫做定时器溢出中断,也就是说,定时器依靠对稳定的时钟源定次数计数来实现定时,并且每一个定时周期完成都会产生溢出中断。

上面讲述的就是任何一个定时器都会具备的功能,只要是个定时器就会有,程序上为了方便,关于上述功能的配置项都被以”base”命名,比如这样: 

1.png

2.png

3.png

看图中的结构体,这个结构体的成员同样对应了定时器相关的寄存器,由于定时器本质是一个计数器,所以根据时钟源的选择,定时器会有不同的工作模式,如外部计数模式、编码器模式等。这里选择定时器模式(就是开头介绍的那种工作过程),之后定时器会自动选择单片机自己工作使用的时钟作为时钟源,连续计数模式下,定时器会自动重复执行上述溢出中断的过程,预分频系数根据用户需要进行配置,它和下面的重装载值共同决定定时器的溢出周期。

以图中的配置为例,该定时器的时钟源是48MHz,现在需要一个50Hz(也就是周期为20ms)的定时器,该定时器会每20ms触发一次定时器中断。在不进行干涉的情况下,定时器每秒会计数48M次,预分频系数设置成32之后,定时器每秒计数150万次,将重装载值设置为30000,定时器在每计数30000次之后触发一次中断,1秒触发50次中断,正好是需要的50Hz频率。但是填入的时候不能直接填入30000,因为30000是人类从1开始计算第一个数字得出的结果,计算机的第一个数字是0,因此需要在最后减1。图中的代码直接列出了上述文字表达的公式,其中的50就是频率。

随后设置好中断,完成必要的初始化,定时器的基本功能就可以使用了,中断服务函数可以在函数列表中找到。

好的,你已经掌握了所有单片机定时器的基本用法,不过细心的小伙伴肯定想过:为什么CW32的定时器叫ATIM、GTIM和BTIM呢?TIM就是timer,也就是定时器,A是advanced的缩写,ATIM就是高级定时器,GTIM是通用定时器,BTIM自然是基本定时器。这是根据功能对定时器资源进行划分的,这么划分的好处是不需要查手册就能通过代码直接看出来某个定时器具备什么功能,基本定时器只具备上述基本功能,通用定时器额外拥有捕获/比较功能,高级定时器包含通用定时器所有的功能,而且还有更多其他功能。理论上来说这些附带的功能都可以通过代码来实现,但由于很多工业场景需要用到,所以做到硬件层面会更加稳定,也更方便。

本章使用的是通用定时器,下面介绍高级定时器的捕获/比较功能,因为这个功能很常用。

首先需要着重声明的一点是,捕获比较功能大概率拥有多个通道,但是定时器,也就是上述的基础功能只有一个,所以即使使用很多个捕获比较通道,其所属定时器的定时周期也是相同的。

下面就来看看通用定时器的结构框图,初看这个图可能会不知所措,我们可以先进行简单的划分,框图上半部分的右侧有一个16位计数器,计数器可以从左侧选择输入的时钟源,可以对输入进行分频。框图下半部分展示了定时器的4个捕获比较通道channel1~channel4,通道可以用来输出也可以用来输入,但同一时间只能使用输入|输出中的一个功能。笔者刚学习单片机的时候,不知道通道是什么,总是稀里糊涂的,通道就是让信号走的路,放到这里就是说,这个定时器拥有4个可以用来输出|输入的电信号道路。那这个通道输出的是什么东西呢?

4.png

我们都知道,对电平进行周期反转就可以制造方波,而定时器基本功能就可以实现这个效果,只需要在中断中反转IO电平即可。但是这样很不方便,比如我想要在不调整周期的情况下去控制方波的占空比,这种原始的办法就会略显麻烦,需要在中断内修改定时器的设置来实现。为了避免这种麻烦,出现了一种带输出比较功能的定时器。理念也很简单,定时器自己有一个在有限区间内周期性增长归零的计数器,那我直接设置一个新的门限值:当这个自增的计数值小于门限时,输出高电平;计数值大于门限时,输出低电平。这就是定时器的输出比较功能,对应上图下半部分右侧的输出功能。这种方式可以便捷快速地输出一个可轻松修改占空比的方波,而这种对信号的处理方式,也叫做脉宽调制(Pulse-width modulation),简称PWM,用这种方式输出的方波也叫做PWM波。

现在来看使用PWM功能需要进行哪些操作。先思考,除去基本的定时器配置之外,PWM需要用到捕获比较通道,那必然会有对比较捕获功能相关寄存器的配置,它需要输出一个波,那必定会有引脚相关的初始化。

下面看代码:首先当然是对IO的初始化,相信经过对前几章的阅读,读者必定是能轻松配置GPIO了,这里着重介绍对PWM输出功能的配置。第一步当然是找到输出比较功能的函数,输出比较的英文是output compare,简写是OC,所以直接找到函数“通用定时器_输出比较初始化”。这个函数有3个参数,按顺序分别表示要初始化的定时器是哪个、要初始化的通道是哪个、以及这个通道的输出模式。输出模式就是设定:当计数值大于|小于门限值的时候,是该输出高电平还是低电平。这里设定的是计数值小于门限时输出高电平。第二步就是设置这个关键的门限值,我们可以直接找到“通用定时器_设置比较1”来设置门限值,这里我把门限值设定为重装载值的一半,最后的效果就是输出一个占空比50%的方波。

5.png

6.png

对占空比的修改不一定需要用到这个设置占空比的函数,我们可以直接修改寄存器来实现。单片机中,存储这个门限值的是一个叫做CCR的寄存器,所以为什么叫CCR?没错,他原名叫Capture Comparison Register,所以就简写为CCR。定时器的每一个通道都有一个自己的捕获比较寄存器,所以CCR一共有四个,故而上图那个设置门限值的函数也有4个,但是由于整个寄存器都只用来装这一个值,所以我们修改的时候可以直接操作寄存器修改,就像这样CW_GTIM1->CCR1=0,我们也可以直接对这个赋值号左侧的部分进行自增操作或是别的什么操作都可以,但是写入操作仅限于作为输出模式时使用。

下面就是紧张刺激的验证环节了,笔者手上没有可以接的用来发光的灯泡,所以直接用万用表测量输出引脚的电压来验证PWM功能,万用表在测量方波时,会显示该方波的平均值,所以如果PWM正常,万用表的直流档会显示1.65V左右的电压,交流档会显示3.3V的电压,这里我为了使现象更明显,在中断中对PWM波的占空比进行周期性修改。

7.png

经过测量,占空比50%时,PA6输出电压为1.62V,算上误差这个在预期结果内。而加入中断的代码后,万用表示数会周期性跳变,符合预期结果,可以认定该配置下,PWM功能正常工作。

来源:CW32生态社区

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

围观 15

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

BD网盘链接:

https://pan.baidu.com/s/1N7MQY7ETFAbKXCm9ZKGYHA?pwd=pqyq 

提取码:pqyq

一、实验简介

本智能家居系统是一款功能丰富、易于使用的智能家居解决方案,可以通过检测温湿度、光照强度和空气质量等参数,为我们提供更加舒适、健康、安全的居住环境,让用户享受更加智能的生活体验。

该系统采用CW32F030单片机作为核心控制器,通过各种传感器实时监测室内环境参数,并根据预设的阈值进行相应控制。例如,当室内光照不足时,系统会自动打开灯光,确保室内光线充足。室内温湿度数据和空气质量通过屏幕显示,并实时上传到云平台,以实现远程监控。

二、实验器材

本实验使用到了CW32-48F大学计划开发板、ESP8266WIFI模块、DHT11温湿度模块、MQ-135空气检测传感器、光敏电阻模块、热释电传感器模块、LED交通灯模块及Keil5开发环境。

1.png

CW32-48F大学计划板 

2.png

ESP8266WIFI模块

3.png

DHT11温湿度模块

4.png

MQ-135空气传感器模块

5.png 

光敏电阻模块

6.png


热释电人体感应模块

7.png

实物图

开发板上预留了ESP8266WIFI模块接口,通过串口与ESP8266通信。

8.png

开发板上预留了DHT11模块接口

9.png

【MQ-135空气传感器模块与单片机接线】:

VCC -- 5V

GND -- GND

DO  -- ×AO  -- PA0

【光敏电阻模块与单片机接线】:

VCC -- 3.3V

GND -- GND

DO  -- ×AO  -- PA4

【热释电人体感应模块与单片机接线】:

VCC -- 3.3V

GND -- GND

OUT -- PB10

【LED交通灯模块与单片机接线】:

R -- PA6

Y -- PA5

G -- PB11

GND -- GND

三、核心代码

main.c:/*传感器数据*/uint16_t ppm=0;            //空气中甲苯浓度uint8_t humidity=0;        //湿度uint8_t temperture=0;      //温度uint8_t Light_intensity=0; //光照强度(0~100)uint16_t adc_result[2]={0}; //保存ADC序列转换结果
/*标识控制位*/_Bool Flag_5s=0;       //5s计时uint16_t time5scnt=0;  //5s计数uint16_t time2scnt=0;  //2s计数_Bool averyflag=0;    //ADC序列采集完成标志位_Bool send_flag=0;    //数据上云控制位
/*自定义函数*/void PIR_Proc(void);    //检测人是否存在void DHT11_Proc(void);  //采集温湿度void MQ135_Proc(void);  //检测空气质量void Photo_Proce(void); //检测亮度void System_Init(void); //系统初始化void Send2OneNet(void); //数据上云void Interface(void);   //数据显示界面void LED_Proc(uint8_t led,uint8_t state); //LED灯
/*主程序*/int main(void){  System_Init();  //系统初始化  Interface();    //界面显示  while(1)  {      PIR_Proc();     //人    DHT11_Proc();  //温湿度    MQ135_Proc();  //空气质量    Photo_Proce(); //光强    Send2OneNet(); //数据上云    ADC_SoftwareStartConvCmd(ENABLE); //启动ADC转换    Delay_ms(500);  }}
/*子程序*/void System_Init(void)  //系统初始化函数{      Lcd_Init();    TFT_Welcome(); //开机界面  LED_Init();      PIR_Init();     //热释电传感器初始化  DHT11_Init();  //温湿度传感器初始化  Buzzer_Init(); //蜂鸣器初始化  while(DHT11_Check()); //检测DHT11是否已连接  BTIM_Init();  //  Usart1_Init(115200);  //调试串口  Usart2_Init(115200);  //ESP8266串口  ESP8266_Init();       //ESP8266初始化  ADC_Configuration();  //ADC序列多次转换模式配置}
void Interface(void)    //界面显示函数{  Lcd_Clear(BLUE);  Gui_DrawFont_GBK16(0,8,WHITE,BLUE,"  智能家居系统 ");  Gui_DrawLine(0,32,128,32,GRAY1);                       //分割线  Gui_DrawFont_GBK16(0,48,WHITE,BLUE," 当前温度:");  Gui_DrawFont_GBK16(0,80,WHITE,BLUE," 当前湿度:");  Gui_DrawFont_GBK16(0,112,WHITE,BLUE," 空气质量:");}
void PIR_Proc(void)   //人体检测函数{  if(ReadPIR())    //有人在  {    Flag_5s=1;   //在中断中开始5s计时    LED_Proc(LED_Yellow,1); //打开黄灯  }  else             //没有人  {    Flag_5s=0;   //清除计时标识    time5scnt=0; //清零计数值    Buzzer(0);   //关闭蜂鸣器    LED_Proc(LED_Red,0); //关闭红灯    LED_Proc(LED_Yellow,0); //关闭红灯  }  if(time5scnt>=500) //计数值超过500,5秒计时到  {    time5scnt=0;  //清零计数值    Buzzer(1);    //打开蜂鸣器    LED_Proc(LED_Red,1); //打开红灯  }  }
void DHT11_Proc(void)  //温湿度采集函数{  char display[8]=" ";  DHT11_Read_Data(&temperture,&humidity);  //采集温湿度  sprintf(display,"%d ℃",temperture);  TFTShowString(3,11,display);  sprintf(display,"%d %%",humidity);  TFTShowString(5,11,display);}
void MQ135_Proc(void)   //空气质量检测函数{  double v_dat=0;  if(averyflag)    //ADC转换完成  {    adc_result[0]/=10;  //采集10次求平均值    v_dat=(double)adc_result[0]*3.3/4960.0;  //AD值传换成电压    ppm = pow((3.4880*10*v_dat)/(5-v_dat),(1.0/0.3203)); //计算甲苯浓度(参数因环境而异)    if(v_dat<0.3)      Gui_DrawFont_GBK16(90,112,WHITE,BLUE,"优");  //依据电压值人为划分空气质量等级    else if(v_dat<0.5) Gui_DrawFont_GBK16(90,112,WHITE,BLUE,"良");    else         Gui_DrawFont_GBK16(90,112,WHITE,BLUE,"差");  }}
void Photo_Proce(void)   //光照强度采集函数{  double v_dat=0;    if(averyflag)  //ADC转换完毕  {    adc_result[1]/=10;  //求平均值    Light_intensity=(1.0-(float)adc_result[1]/4096.0)*100.0;  //转换成光照强度(仅供参考)    v_dat=(double)adc_result[1]*3.3/4960.0; //转换成电压值    if(v_dat>1.5) LED_Proc(LED_Green,1);  //自定义阈值,光照强度过低打开绿灯    else      LED_Proc(LED_Green,0);      }}
void LED_Proc(uint8_t led,uint8_t state)  //LED控制函数{  if(led==LED_Red)    GPIO_WritePin(CW_GPIOA,GPIO_PIN_6,(GPIO_PinState)(state));  else if(led==LED_Yellow)    GPIO_WritePin(CW_GPIOA,GPIO_PIN_5,(GPIO_PinState)(state));  else if(led==LED_Green)    GPIO_WritePin(CW_GPIOB,GPIO_PIN_11,(GPIO_PinState)(state));}
void Send2OneNet(void)  //数据上传函数{  if(send_flag)  {        OneNet_SendData();  //数据上传到OneNET    ESP8266_Clear();    //清除缓存    send_flag=0;    }}
void BTIM1_IRQHandler(void)   //基本定时器1中断{  if(BTIM_GetITStatus(CW_BTIM1,BTIM_IT_OV))    {    if(++time2scnt>200) {send_flag=1;time2scnt=0;} //2s计时    if(Flag_5s) time5scnt++;                       //5s计时    BTIM_ClearITPendingBit(CW_BTIM1,BTIM_IT_OV); //清除标志位  }

四、效果演示

10.png

来源:武汉芯源半导体

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

围观 28

智能科技的迅速发展使得我们的日常生活变得更加便捷和舒适。智能马桶作为其中一种智能家居产品,通过单片机接受和处理来自传感器的数据,然后通过控制模块对智能马桶的各项功能进行控制,实现对智能马桶的全面控制和调节。本文将介绍CW32单片机在智能马桶的详细应用。

1.png

图:CW32的智能马桶控制板

CW32单片机在智能马桶的应用介绍

1.温度感应与控制

智能马桶内设有温度传感器,通过CW32单片机的处理,可以实时感知到用户所需的座圈温度并根据用户的设定进行调节。当温度过高或过低时,单片机将发出信号,通过控制模块对座圈的加热或制冷进行调节,提供更加舒适的使用体验。

2.png

图:CW32在智能马桶控制板的应用框图

2.智能冲洗功能控制

CW32单片机通过接收用户的操作指令,进行流量检测,根据用户所需的水流大小和冲洗时间,精确地控制水泵的工作和停止。

3.智能电量监控与节能功能

智能马桶内置电池或电源模块,CW32单片机可以实时监测电池的电量,并根据电量情况进行智能的调节。当电量不足时,单片机会通过控制模块对电源进行调节,以延长电池的使用寿命。例如,降低加热功率或减少冲洗时间等,通过智能的电量管理,实现了对电能的高效利用和节约。

这款智能马桶采用的是武汉芯源半导体32位低功耗CW32L083系列,该系列产品集成了主频高达64MHz的ARM® Cortex®-M0+ 内核、最多256KB FLASH 、最多 24KBRAM、最多87路GPIO,以及一系列增强型外设。

CW32L083系列产品优势介绍

1、系统特性

● ARM® Cortex®-M0+ 内核;

● 最高主频64MHz;

● 温度范围:-40℃ 至 85℃;

● 宽压供电:1.65V 至 5.5V;

2、高可靠性

● HBM ESD 8KV;MM ESD、CDM ESD、Latch up@105℃全面达到JEDEC较高等级;

● EFT 4KV,IEC61000-4-4 Class:4(Power)/4(IO);

● 支持奇偶校验;

● 符合IEC60730,IEC61508 功能安全设计规范;

3、超低功耗

● 支持0.6uA@3.3V深度休眠模式;支持1.7uA@3.3V深度休眠模式+IWDT工作;

● 深度休眠模式下电流0.6uA,超低功耗唤醒时间仅需4us;

● 支持灵活的中断唤醒及事件唤醒;

4、内部资源

● 最多87路GPIO;

● 1路12位ADC;

● 六路低功耗 UART,支持小数波特率;

● 两路 SPI 接口 12Mbit/s;

● 两路 I2C 接口 1Mbit/s;

● 16 位高级控制定时器;四组 16 位通用定时器;三组 16 位基本定时器;一组 16 位超低功耗定时器;窗口看门狗定时器;独立看门狗定时器;

● 1路低功耗定时器(LPTIM)

● 4×56、6×54 或 8×52 LCD 段码液晶驱动器

● TRNG 真随机数发生器

● AES 高级加密标准模块

CW32L083系列产品适用于对FLASH、RAM、GPIO等资源需求较大,且有LCD显示、低功耗要求的应用场合,该系列目前可提供LQFP64、LQFP80、LQFP100三种封装形式。

3.png

图:CW32L083系列封装展示

CW32L083系列产品非常适合各种小、中型电子产品的应用领域,比如医疗和手持设备、PC外围设备、游戏设备、运动装备、报警系统、智能门锁、有线和无线传感器模块、表计等产品。

来源: 武汉芯源半导体

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

围观 30

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

BD网盘链接:

https://pan.baidu.com/s/1lJbXZsBnEBjsz5bCfKRoQA?pwd=kq5a 

提取码:kq5a

一、实验简介

基于CW32单片机的门禁综合系统是一款功能强大的智能门禁解决方案,它提供了三种开锁方式:密码开锁、指纹开锁和刷卡开锁,为用户提供方便、安全的门禁控制。该系统的核心控制器采用了CW32单片机,具备高性能和可靠性,能够稳定地运行门禁系统的各项功能。

密码开锁是最常见的开锁方式之一。用户可以通过系统提供的密码输入界面,输入正确的密码来解锁门禁系统。这种方式简单明了,适用于多种场景。

指纹开锁是一种更加安全、个性化的开锁方式。系统可以保存多个用户的指纹信息,用户只需将手指放在指纹识别器上进行验证,合法的指纹即可解锁门禁系统。这种方式便于用户快速、准确地开锁,并且具有很高的安全性,因为指纹信息是独一无二的。

刷卡开锁是另一种常见的开锁方式。系统配备了IC卡读卡器,用户只需通过刷卡即可进行开锁。IC卡可以根据实际需求进行添加和删除,非常方便管理和控制用户的权限。

除了这三种开锁方式,该系统还提供了自主修改密码和添加删除指纹、IC卡的功能。管理员可以通过系统设置界面轻松地修改密码,添加或删除用户的指纹和IC卡信息,确保安全性。

二、实验器材

本实验使用到了CW32-48F大学计划开发板、RC522刷卡模块、AS608指纹模块、继电器、薄膜键盘及Keil5开发环境。

1.png

CW32-48F大学计划板    

2.png

RC522刷卡模块

3.png

AS608指纹模块

4.png

继电器模块

5.png  

薄膜键盘

6.png

实物图

三、接线

开发板上预留了矩阵键盘接口

 7.png

【RC522刷卡模块与单片机接线】:

VCC  --  3.3V

GND  --  GND

RST --  PB9

SCK --  PA11

SDA --  PA10

IRQ --  ×

MOSI -- PA12

MISO -- PA15

【AS608指纹模块与单片机接线】:

红线 -- 3.3V

黄线 -- PA3

黑线 -- GND

绿线 -- 3.3V

蓝线 -- PC15

【继电器模块与单片机接线】:

VCC -- 3.3V

GND -- GND

OUT -- PB1

四、核心代码

main.c:
/*函数声明*/
void KEY_Proc(void);                  //按键处理函数
void RC522_Proc(void);                //刷卡处理函数
void AS608_Proc(void);                //指纹处理函数
void System_Init(void);               //系统初始化函数
void Passport_Proc(void);             //密码处理函数
void System_Reload(void);             //系统重置函数
void Mode_Interface(void);            //模式切换界面
void Work_Interface(void);            //工作界面
void Buzzer_Ring(uint8_t ms);         //蜂鸣器函数
_Bool Indentity_Verify(void);         //身份验证函数
void Init_MyStruct(id* mystructure);  //结构体初始化函数

/*主程序*/
int main()
{  
    System_Init();        //系统初始化  
    Mode_Interface();     //上电进入模式选择界面
   
   while(1)  
   {    
       KEY_Proc();       //按键处理    
       RC522_Proc();     //刷卡处理    
       AS608_Proc();     //指纹处理    
       Passport_Proc();  //密码处理  
   }
}

/*子程序*/
void System_Init(void)                //系统初始化函数
{  
    __NVIC_SetPriority(BTIM2_IRQn,0); //BTIM2高优先级  
    __NVIC_SetPriority(BTIM1_IRQn,1); //BTIM1低优先级    
      Lcd_Init();                       //屏幕初始化    
      BTIM1_Init();                     //定时器初始化  
    BTIM2_Init();    
      Relay_Init();                     //继电器初始化  
    RC522_Init();                     //刷卡模块初始化  
    Matrix_Init();                    //薄膜键盘初始化  
    Buzzer_Init();                    //蜂鸣器初始化  
    Lcd_Clear(BLACK);                 //清屏    
      UART2_Init(57600);                //AS608指纹模块串口初始化    
      Init_MyStruct(&card);             //卡片结构体初始化  
    AS608_WAK_GPIO_Init();            //AS608手指感应初始化
}

void Mode_Interface(void)            //用户\管理模式界面
{      
    Lcd_Clear(BLACK);  
    TFT_User();                      //用户模式界面  
    while(start==0)  
    {      
        if(Key_Scan()!='.')    
        {      
            KEY_Proc();      
            if(Mode==0)                      
                TFT_User();          //用户模式界面      
            else if(Mode==1)                 
                TFT_Administrator(); //管理模式界面    
        }  
    }    
    method=0;                        //解锁模式0:密码解锁  
    TFT_Passport(Mode);              //进入用户模式的密码界面
}

void Work_Interface()                //工作界面
{  
    if(method==3)                    //三种解锁模式循环显示    
        method=0;  
    else if(method>3)    
        method=2;
        
   if(method==0)    
       TFT_Passport(Mode);          //密码解锁  
   else if(method==1)    
       TFT_Fingerprint(Mode);       //指纹解锁  
   else if(method==2)    
       TFT_Card(Mode);              //刷卡解锁
}

void KEY_Proc(void)                  //按键处理函数
{  
    if(!exert_flag)                       
        return;  
    temp=Key_Scan();                 //读取键值  
    exert_flag=0;  
    if(temp=='.')      
        return;
    
    switch(temp)  
    {    
        case 'F':  if(start) return; Mode=0; break;  //用户模式                                                
        case 'f':  if(start) return; Mode=1; break;  //管理模式        
            case '#':  if(Mode==0) start=1; else {if(Indentity_Verify()==0) start=1;} break; //开始工作                    
        case '*':  if(!start) return; start=0; System_Reload();    break;  //系统重置                
        case '(':  if(!start) return; method-=1; Work_Interface(); break;  //切换解锁方式    
        case ')':  if(!start) return; method+=1; Work_Interface(); break;  //切换解锁方式                     
        case '+':  if(start==0) Tips_Open=1; break;  //打开信息提示                 
        case '-':  if(start==0) Tips_Open=0; break;  //关闭信息提示      
    }
}

void Passport_Proc(void)               //密码处理
{      
    uint8_t i=0;  
    uint8_t result=0;  
    char uc_temp='.';
   
   if(method!=0) {idx=0; return;}              //解锁模式0:密码解锁  
   if(Passport_Input(uc_passport,PASSPORT_LENGTH)==0) return;  //判断密码输入是否完成   
 
   if(Mode==0)                                 //用户模式  
   {    
       for(i=0;i<PASSPORT_LENGTH;i++)      
           result+=uc_passport[i]^passport[i]; //使用异或判断输入的密码与设置的密码是否一致    
       if(result==0)                           //密码正确    
       {      
           Relay_Flag=1;                       //继电器标识打开      
           PB01_SETHIGH();                     //开锁      
           TFTShowString(7,0,"               ");      
           TFTShowString(8,4,"Unlocked");    
       }    
       else                                    //密码错误    
       {      
           TFTShowString(7,0,"                ");      
           Gui_DrawFont_GBK16(5,128,WHITE,BLACK,"     Error     ");      
           Buzzer_Ring(60);                    //错误提示音      
           Delay_ms(500);      
           TFTShowString(8,0,"                ");     
       }  
   }  
   else                                        //管理模式  
   {    
       TFTShowString(5,2,"Esc: concel ");    
       TFTShowString(7,2,"Ent: comfirm");    
       while(1)                                //等待按键输入(确认/取消)    
       {      
           uc_temp=Key_Scan();      
           exert_flag2=0;      
           if(uc_temp=='R')                    //键入:取消      
           {        
               Buzzer_Ring(120);               //操作提示音        
               TFTShowString(5,2,"              ");        
               TFTShowString(7,2,"              ");        
               TFTShowString(6,2," set concel   ");        
               Delay_ms(500);        
               TFTShowString(6,2,"              ");        
               break;      
           }      
           else if(uc_temp=='E')               //键入:确认      
           {                
               Buzzer_Ring(120);               //操作提示音        
               TFTShowString(5,2,"              ");        
               TFTShowString(7,2,"              ");        
               memcpy(passport,uc_passport,sizeof(uc_passport)); //覆盖开锁密码        
               TFTShowString(6,2,"set  succeed  ");        
               Delay_ms(500);        
               TFTShowString(6,2,"              ");                
               break;      
           }    
       }    
   }
}

void AS608_Proc(void)                     //指纹处理
{    
    char uc_temp='.';   
    
    if(method!=1)  return;                //解锁模式1:指纹解锁       
    
    if(Mode==0)                           //用户模式  
    {    
        if(AS608_PressTest()==0)          //按下    
        {      
            if(AS608_MatchTest()==0)      //匹配      
            {        
                PB01_SETHIGH();           //开锁        
                Relay_Flag=1;        
                TFTShowString(8,4,"Unlocked");      
            }      
            else if(!Relay_Flag)         
            {        
                TFTShowString(7,0,"                ");        
                Gui_DrawFont_GBK16(3,128,WHITE,BLACK,"          Error     ");        
                Buzzer_Ring(60);          //错误提示音        
                Delay_ms(500);        
                TFTShowString(8,0,"                ");      
            }    
        }    
    }  
    else  
    {      
        TFTShowString(7,3,"Esc: Delet ");    
        if(!exert_flag2) return;    
        uc_temp=Key_Scan();    
        exert_flag2=0;        
        if(AS608_AddTest(Finger_ID)==0)       //添加指纹    
        {      
            Buzzer_Ring(120);                 //执行成功提示音      
            TFTShowString(6,1," Add succeeded");      
            Delay_ms(500);      
            TFTShowString(6,0,"                ");    
        }     
        
        if(uc_temp<=validFinger_num)          //键入:id       
        {      
            Finger_ID=uc_temp;                      
            TFT_Fingerprint(MANAGE_MODE);    
        }    
        else if(uc_temp=='R')                 //键入:删除    
        {      
            if(PS_DeletOneChar(Finger_ID)==0) //删除      
            {        
                Buzzer_Ring(120);             //执行成功提示音        
                TFTShowString(6,1,"Delet succeeded");        
                Delay_ms(500);        
                TFTShowString(6,0,"                ");      
            }    
        }  
    }   
}

void RC522_Proc(void)                         //刷卡处理
{    
    char uc_temp='.';  
    uint8_t uc_uid[4]={0};  
             
   if(method!=2)  return;                    //解锁模式2:刷卡解锁     
   
   if(Mode==0)                               //用户模式  
   {    
       if(RC522_ReadIDTest(uc_uid)==0)       //识别到卡    
       {      
           if((memcmp(card.uid0,uc_uid,sizeof(uc_uid))==0)  ||  //检索ic序列号           
           (memcmp(card.uid1,uc_uid,sizeof(uc_uid))==0)  ||         
           (memcmp(card.uid2,uc_uid,sizeof(uc_uid))==0)        
           )      
           {        
               PB01_SETHIGH();               //开锁        
               Relay_Flag=1;               
               TFTShowString(8,4,"Unlocked");      
           }      
           else       
           {        
               TFTShowString(7,0,"                ");        
               Gui_DrawFont_GBK16(0,128,WHITE,BLACK,"      Error     ");        
               Buzzer_Ring(60);              //错误提示音        
               Delay_ms(500);        TFTShowString(8,0,"                ");      
           }    
       }  
    }  
    else  
    {    
        TFTShowString(7,3,"Esc: Delet ");    
        if(!exert_flag2) return;        
        uc_temp=Key_Scan();    
        exert_flag2=0;    
        if(uc_temp<validCard_num)            //键入:id    
        {      
            Card_ID=uc_temp;          
            TFT_Card(MANAGE_MODE);    
        }    
        if(Card_ID==0)                         
        {      
            if(uc_temp=='R') memset(card.uid0,0,sizeof(card.uid0)); //删除ic卡0序列号      
            else if(RC522_ReadIDTest(card.uid0)!=MI_OK) return;     //添加ic卡0序列号     
        }        
        else if(Card_ID==1)    
        {      
            if(uc_temp=='R') memset(card.uid1,0,sizeof(card.uid1)); //删除ic卡1序列号            
            else if(RC522_ReadIDTest(card.uid1)!=MI_OK) return;      //添加ic卡1序列号        
        }        
        else if(Card_ID==2)    
        {      
            if(uc_temp=='R') memset(card.uid2,0,sizeof(card.uid2)); //删除ic卡2序列号            
            else if(RC522_ReadIDTest(card.uid2)!=MI_OK) return;     //添加ic卡2序列号        
        }     
    
        if(uc_temp=='R') TFTShowString(6,1,"Delet succeeded");    
        else        TFTShowString(6,1," Add succeeded");    
        Buzzer_Ring(120);    
        Delay_ms(500);                       //执行成功提示音    
        TFTShowString(6,0,"                ");     
    }
}

void System_Reload(void)   //重置系统,恢复到上电默认的状态
{  
    idx=0;   
    Mode=0;                   
    start=0;  
    method=0;  
    while(Relay_Flag);   
    Lcd_Clear(BLACK);  
    Mode_Interface();
}

来源:CW32生态社区

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

围观 15

前言

人们户外露营、手机终端等方面的用电需求日益明显,便携式储能设备成为理想的选择。伴随着便携式储能市场的持续增长,便携锂电BMS应用空间也会逐渐扩大。虽然目前便携锂电BMS在研发设计中面临着诸多挑战,但正因为这些挑战,让其成为企业快速抢占市场的重要条件。便携式储能设备需求增大,那么便携锂电BMS市场发展可期可待,轻量化、定制化、安全快充的市场需求也会成为未来的发展方向。

现在,市场上各类需要配备锂电池、且要求快速充电的电子产品越来越多。例如:便携类仪器仪表(配置2~6节锂电池)、便携式产品锂电池能量包、支持快充快放的移动电源充电宝等。

现在外面很多充电宝都是千篇一律的四个电量指示灯和一个唤醒按键,显示的信息不全,电量指示显示得也不准。所以我们设计了一款带屏幕的充电宝。在屏幕上能精确的显示充电宝的电量、充电状态、输出电压、输入电流等信息。这样的设计可以提供更直观、方便的使用体验。并且我们开源这款充电宝,让各位电子爱好者能DIY出符合自己个性的充电宝。

方案综述

本项目以CW32F003F4P7为核心,配合英集芯的充电管理芯IP5389,设计了一个完整的移动电源系统,具有彩屏显示充放电的电压、电流、功率等状态,具有过温保护、超温自动降额、过流、过压等完善保护措施。支持各种协议的快速充电,以及大电流快速放电。支持充电/放电电压5~22V,最大充放电功率100W(最终充放电指标由充电设备及用电设备的标准而确定)。支持2~6节串联锂电池组的快速充放电。

此方案不但适合高端移动电源设计参考,而且对所有便携式产品锂电池能量包(BMS)方案的用户,都有非常直接的帮助作用,可以为用户节省1~3月的开发时间。

方案优势:方案基于CW32的20 PIN的MCU设计,具有5V系统、强健抗干扰能力、超低功耗、宽温度(105度)等特性,相比ST或其他国产方案,价格更有优势,性能更稳定。CW32 MCU与IP5389的配合,让方案应用特别灵活多变,特别适合新兴的便携锂电BMS市场的方案需求。

适合行业:移动电源、户外电源控制、新能源、大功率智能电动工具电池包、便携式产品电池包BMS、便携锂电BMS市场(新兴市场)。特别适合用于要求有快速充放电、兼容各种充放电协议的新兴便携锂电BMS方案的应用:DC输入充电(5~20V)、TYPE输入充电(PD协议,5~20V,MAX 100W),输出USB、TYPE或普通供电输出要求(可配合诱骗芯片)。

硬件方案

本方案采用英集芯IP5389作为协议和21700电芯管理芯片,选择市面上便于采购的公模外壳进行安装。同时因为体积考虑,将充放电功率限制在了45W及以下。

1.png

2.png

BOM

3.png

软件编写

首先我们需要根据本充电宝的功能来确定我们所需要使用到的外设。需要与IP5839通讯来读取寄存器,将读取的电压电流信息以及电池温度、MCU内部温度显示在LCD屏幕上,同时为了降低充电宝的待机功耗,我们还需要在待机时将MCU设置为低功耗模式。

屏幕显示 spi外设

读取IP5839的寄存器 I2C

低功耗

读取电池温度与MCU温度 ADC模块

按键唤醒充电宝 GPIO

使用定时器作为系统软件时基单元。

底层驱动部分

开源链接

详细项目文档及资料获取请阅览飞书文档:

https://dwi41yhz703.feishu.cn/docx/VxMGdmu9jo9D5txVdPocJwlrney?from=from_copylink

硬件开源链接(立创):

https://oshwhub.com/beauty_light/cw32-intelligent-charging-treasure-scheme

来源:CW32生态社区

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

围观 188

上回说到单片机的Uart发送,我们编写了一个发送函数循环发送固定的字符串,这回我们讲Uart的中断接收功能。

说一下中断是什么,大概就是说,单片机只有一个核,就是只有一个大脑,他无法一核二用地做事,但有时候迫不得已需要去响应一些紧急的命令,就好比你打游戏开团了,你妈喊你去倒开水,倒开水就会触发咱们人类的“中断”功能。放在单片机上,进行中断操作需要以下几个条件和步骤:拥有中断源、中断控制器正常工作、触发中断、保护现场、响应中断、恢复现场。

看名字可能会比较抽象,我来具体解释一下。

中断源,单片机上会有很多的中断源,也就是有很多办法、或者说“渠道”去触发中断,Uart外设就有很多触发中断的办法,而我们本文涉及到的就是接收信息会触发的中断,具体怎么触发的,后文会详细解释。

中断控制器,这个东西是一个物理存在于单片机内核里面的一块数字电路,这一块电路的功能就是用来管理中断的。对于一些老旧型号的单片机,比如C51单片机,他内部也有这个东西,只不过其中断优先级是固定的,这个控制器只扮演了“总闸”这样的角色。再看CW32这种32位单片机,使用cortex-M0+内核,拥有可编程的中断控制器,单片机上会有很多个中断源,但这是内核可以使用和管理的部分,芯片制造厂使用这一款内核制造单片机,并不会用到所有的中断资源,不只是搭载的功能有限,还受限于封装,很多中断资源会被闲置。但是只要使用芯片的中断,都必须正确配置内核里面的中断控制器,否则中断是无法工作的,因为不论单片机外设设计的如何天花乱坠,外设只负责触发中断,而响应中断的一定是内核。

中断的触发,前面提到了中断源,一个指定的中断只能由特定的、与其绑定的中断源触发,一个中断可能绑定多个中断源,但是只会有一个与中断绑定的中断服务函数,至于什么是中断服务函数,后文会解释。那这个时候肯定会有读者问了“那单片机如何在一个中断里面区分不同的中断源呢?”,单片机对不同的中断源,都设计了中断标志位,假设有ABC三个中断源,那他们就对应了3个标志位(3比特位),没触发中断的时候,ABC的中断标志位就是默认值0,如果触发中断,电路硬件会对其对应的标志比特位进行置位操作,也叫置1操作,该比特位会变成1。这个置位行为会直接反馈到内核的中断控制器,随后内核会对中断信号进行响应。

保护现场,看名字似乎和编程关系不大,这个名词在教科书上的中断章节会高频出现。我们无法预测中断会在什么时候到来,CPU也不能一直傻傻地等中断到来,所以不需要响应中断的时候,CPU还是照常工作的。想象现在CPU正在执行一个函数function(),倘若函数还未执行完成,中断被触发,CPU应该怎么做?是放下function函数不管不顾直接去响应,抑或是先做点什么?显而易见,后者更好更合理,需要做的,正是保护现场,函数执行到哪一步,CPU就会把执行到这一步的CPU数据(不只是我们要看的数据,还包括了程序执行的情况)存放到堆栈中,在中断响应完成之前,这些数据都会被封存,以避免响应完成后数据的丢失。

响应中断,这个是大部分人最关心的部分,因为这个部分直接涉及到中断服务函数的编写。在一切准备就绪后,CPU会放弃下一条需要执行的语句并直接进入中断服务函数,这里需要理解“中断服务函数”它仍然是个函数,初学者可能会认为,C语言的函数需要调用才会被执行,这里没被调用却被执行了,那肯定不是函数。实际上看过单片机原理或者了解过计算机原理的小伙伴会告诉你,CPU内部会有一个程序指针,程序指针会按照代码编译之后的逻辑去依次指向需要被执行的函数,单片机进入中断服务函数的原理就是直接设置这个指针指向中断服务函数,之后CPU就能执行中断代码响应中断了。

恢复现场,对应于保护现场,CPU必须在响应中断之后回到之前被中断打断的语句那里继续执行,取出原路堆栈中的数据就完成了恢复。

掌握中断相关的知识后,我们就可以自己编写和中断相关的代码了,编写程序时,基本上只需要注意中断标志位、中断服务函数、中断控制器就可以,保护现场什么的单片机会自己完成。

在包含了必要的头文件之后,在初始化函数中加入下图的代码即可完成对中断控制器的设置:

1.png

第一行和第二行的函数均是对内核里的中断控制器进行寄存器操作。

解释一下第二行的设置中断优先级,这里涉及到一个中断嵌套的概念,中断不会只有一个,并且很有可能下一个中断触发的时候,上一个中断还没有执行完,此时就需要严格设置中断优先级,在单片机中,根据内核用户手册,优先级从0开始递增,优先级数字越低,其优先级越高,高优先级中断可以直接打断低优先级中断的响应,立刻响应高优先级中断,形成中断嵌套,这里设置为1是因为这个回发功能不算很重要的功能,相比之下嘀嗒定时器会为单片机程序提供时基信号,其优先级应该更高。关于优先级的具体解释,可以进行网上搜索或是查看《cortex-M0+内核手册》。

关于最后一行代码,CW_UART1这个外设拥有很多个中断源,这些中断源的使用是独立的,这里只使用了接收中断这一个中断源,芯片手册的通用异步收发器章节展示了Uart中断包含的中断源。

2.png

当有数据进入单片机的Uart1接收缓冲区时,接收中断会触发,中断标志位置1,程序跳转至Uart1的中断服务函数。单片机几乎所有的中断服务函数都会由一个单独的文件收录,名为interrupt_xxxx.c或者xxxx_it.c。这里贴一张简易的中断服务函数代码,其功能是在尽量不破坏单片机实时性的情况下把数据放入一个既有的数组。

 3.png

前文有提到,硬件会根据中断标志位决定是否进入中断服务函数,如果不在中断服务函数中清除中断标志位,单片机就会反复进入中断,导致程序死在中断里。

说一下代码的思路,len是一个变量,是缓冲区内非空数据的个数;data_rx是一个字符数组,作为接收缓冲区,缓冲区大小为200;进入中断之后首先判断缓冲区是否还有位置,也就是len是否超出缓冲区数组下标上限,超出则判定为缓冲区已满,丢掉后续所有的数据直到缓冲区有空位;变量 Rx_Flag是一个8位无符号数,作为缓冲区有数据&缓冲区满的标志位使用;对于接收的所有数据,均会判断是否是“\r\n”,这个字符串在编码中是换行符,只要判断到最近接收的两个字节数据是连续的0X0D和0X0A,就认定接收到换行符,本次数据接收完毕,Rx_Flag置1表示完成一次完整的数据接收。

需要注意的是,中断的响应并非一个非常可靠的函数调用,一些编译器会试图优化掉代码对某些变量的修改操作(他们可能察觉不到中断函数的存在而认为变量不需要被修改),因此需要在中断中修改的变量需要加上“volatile”关键字以防止对变量的操作被编译器优化。 

4.png

到目前位置,数据其实已经被保存在数组data_rx里面了,但这段数据我们从外部是看不到的,也看不到是否是我们设想的功能完成的接收,所以我编写了如下函数,此函数可以在Uart1完成了一次完整的数据接收(Rx_Flag置1)后立刻回发接收的数据,并清空接收缓冲区,允许进行下一次接收。

5.png

因为函数包含发送功能,所以保留了超时跳出的保险措施。这里解释一下time_ms这个变量的作用,该变量定义在嘀嗒定时器文件中,并在嘀嗒定时器中断服务函数中递增1,即每1ms该变量都会增加1,作为毫秒计数值使用,本系列教程大部分实时性较弱的功能都会依赖此功能进行定时。如有疑问可以移步《内核外设-嘀嗒定时器》章节学习。

在轮询中加入这个回发函数,最大发送容忍时间100ms,并设置间隔1000ms发送一次“success”+“换行符”。随后在串口助手中发送不超过200字节的文本数据,即可验证接收是否成功。

6.png

7.png

看来单片机顺利接收了数据并进行了回发操作,本节完。

总结:

1.注意理解中断的概念;

2.同一个中断可能会有多个中断源;

3.中断的执行不可靠,中断内涉及到修改的变量需要加上volatile防止优化;

4.串口的每一次发送携带很少的数据量,因此非常建议使用缓冲区来接收数据,待需要时再主动读取;

来源: CW32生态社区

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

围观 22

页面

订阅 RSS - CW32