CW32

例程资料链接如下:

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

围观 15

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

/ 关于作者/

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

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

围观 11

例程资料链接如下:

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

围观 18

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

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

围观 23

例程资料链接如下:

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

围观 30

本章针对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)。

围观 33

前言 

电池备份(VBAT)功能的实现方法,一般是使用 MCU 自带的 VBAT 引脚,通过在该引脚连接钮扣电池,当系统电源因故掉电时,保持 MCU 内部备份寄存器内容和 RTC 时间信息不会丢失。 

本文档介绍了如何基于 CW32 系列 MCU,通过增加简单的外部电路配合软件实现 VBAT 功能,在系统电源掉 电后仍能保持 RTC 时钟正常计时,以及如何降低系统功耗,从而延长后备电池的使用寿命。

1 、电路设计 

对于自带 VBAT 引脚的 MCU,MCU 内部有对 VBAT 电源和系统电源的管理单元,保证在系统电源掉电后,及 时切换 VBAT 引脚电源给备份域供电,保证 RTC 正常工作。 

对于没有 VBAT引脚的 CW32,要实现类似的功能,可以在外部进行后备带电池和系统电源的切换,如下图所示:

1.png

后备电池(B1)提供的备用电源 VBAT 和系统电源 VDDIN 通过 2 个肖特基二极管(D1)合路,合路后的 电源 VDD 给 MCU 的数字域 DVCC 和模拟域 AVCC 进行供电。系统电源 VDDIN 通过 R3、R4 电阻分压得到 WAKEIO 信号,连接到 MCU 的 IO 引脚。注意遵循如下规则:

 1. Vwakeio 要大于 MCU IO 口的 Vih;

 2. VDDIN 必须高于 Vb1 在 0.4V 以上,否则如果 VDDIN 和 Vb1 相等,在系统电源正常时,后备电池也会有一定 的泄放电流,不利于节省后备电池电量。

2 、程序设计 

程序启动后正常初始化时钟、IO、RTC 以及 OELD,循环中检测系统电源是否存在,如存在则读取 RTC 时间 并显示。 

当系统电源 VDDIN 因故掉电,则关闭 OLED 电源,并进入 DeepSleep 低功耗睡眠模式。 

当系统电源 VDDIN 恢复供电时,产生高电平中断,唤醒 MCU,退出 DeepSleep 低功耗睡眠模式。

3 、参考代码

int32_t main(void)
{
    RCC_Configuration();     // 时钟配置
    GPIO_Configuration();     //GPIO配置
    OLED_Init();          //OLED显示屏初始化配置
    dis_err("RTC_TestBoard");   // 显示
    FirmwareDelay(5000000);   // 增加延时防止过早休眠影响程序烧写
    RTC_init();          //RTC时钟初始化
    //DeepSleep 唤醒时,保持原系统时钟来源
    RCC_WAKEUPCLK_Config(RCC_SYSCTRL_WAKEUPCLKDIS);
    ShowTime();       // 获取时间数据
    displaydatetime();    // 显示当前时间
    while(1)
    {
        if( 0==PB05_GETVALUE() )  // 循环检测是否掉电
        {
            PA05_SETHIGH();   // 关 OLED 电源
            SCB->SCR = 0X04;  //DeepSleep
            __WFI();       //MCU 进入DeepSleep模式以节省功耗
            OLED_Init();     // 外部电源接入后唤醒,重新初始化 OLED
        }
        else
        {
            ShowTime();      // 获取时间数据
            displaydatetime();   // 显示当前时间
        }
    }
}

void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStruct= {0};
    __RCC_GPIOB_CLK_ENABLE();         // 开 
    GPIOB 时钟GPIO_InitStruct.IT  = GPIO_IT_RISING;    // 使能上升沿中断
    GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;  // 输入模式
    GPIO_InitStruct.Pins = GPIO_PIN_5 ;    //PB05,连接 WAKEIO 网络
    GPIO_Init(CW_GPIOB, &GPIO_InitStruct);  // 初始化 IO
    GPIOB_INTFLAG_CLR(bv5);        // 清除 PB05 中断标志
    NVIC_EnableIRQ(GPIOB_IRQn);      // 使能 PB05 中断
}

4 、实际测试

使用 CW32L031C8T6 设计了用于测试后备电池功能的评估板,实物如下图所示:

2.png

使用 3V 的 CR2032 钮扣电池,实测电池电压为 3.14V;VDDIN 使用可调节数字电源,设置为 3.54V,保证 VDDIN >= Vb1 + 0.4V;D1 实测合路后的电源电压为 3.21V。

3.png

4.1 测试数据 

实际测试时,断开 J4 跳线接入万用表,设置万用表为电流测试档位。 

1. 关闭 VDDIN 电源输入,MCU 检测到无外电输入,关闭 OLED 显示,进入 DeepSleep 模式,实测此时 B1 电流为 +0.95μA。 

2. 打开 VDDIN电源输入,MCU被高电平中断从 DeepSleep状态唤醒到正常状态,OLED正常显示当前时间, 实测此时 B1 电流为 -75nA(负电流是因为 D1 处于反向偏置状态,有小的反向漏电流)。 

测试结果符合电路设计预期,以 CR2032 电池容量为 200mAH 计算,则电池可用时间为 210526 小时,合计 24 年(不考虑电池和产品寿命),可实现超长待机时间,完全满足各种低功耗产品对 RTC 后备电池容量需求。

5 、附件 

5.1 RTC_TestBoard 单板原理图

4.png

5.png

来源:武汉芯源半导体

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

围观 42

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

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

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

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

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

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


1.png

CW32-48大学计划开发板OK   

2.png

温控实验模块   

3.png 

4.png

温控模块电路原理图

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

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

2)https://zhuanlan.zhihu.com/p/3473726243)https://zhuanlan.zhihu.com/p/41962512
利用热敏电阻采集温度的原理及方法可参考往期文章及视频。

4.核心代码

mian.c:

#include "config.h"

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

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

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

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

pwm.c:

#include "pwm.h"

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

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

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

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

pid.c:

#include "pid.h"

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

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

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

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

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

5.实验最终现象

5.png

来源:CW32生态社区

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

围观 29

描述

简介:基于武汉芯源半导体CW32F030C8T6和启英泰伦CI-C22GS02制作的智能语音分类垃圾桶,能够实现识别语音来判断垃圾种类,同时打开相应的垃圾桶,可外接普通按键或者触摸按键,也可以外接红外接近传感器来打开对应的垃圾桶。

项目说明

1.jpg

2.jpg

3.jpg

本次设计采用武汉芯源半导体的CW32F030C8T6作为主控,主要负责和离线语音模块的串口通讯,负责输出4路PWM信号、驱动IPS显示屏、控制2路MOS驱动电路(控制风扇和灯光)、驱动4个WS2812B灯。语音识模块采用启英泰伦CI-C22GS02模块(主芯片采用Cl1122,支持本地200条命令词以下的离线语音识别),主要负责语音识别和播报语音,可通过串口与MCU通讯。

项目相关功能介绍介绍

1.离线语音模块的命令词可自行定制,最多可录入200个命令词,目前已录入了168条垃圾名词和一些控制指令。当语音模块识别到正确的命令词后,串口输出规定协议的数据。语音模块也可以接收规定协议的串口数据来播报指定的语音。离线语音模块的串口1和MCU主控的串口2使用跳线连接,方便分别开发调试。

4.jpg

5.jpg

2.主控输出4路PWM信号(周期20us,频率50Hz),可直接驱动4路舵机。

6.jpg

3.四个WS2812B灯作为4类垃圾的指示灯,语音模块在识别到正确的语音后,由CW32主控来控制指定的灯点亮对应的颜色。

7.jpg

4.电路采用直流12V供电,经DC-DC降压后输出5.2V,5.2V再经LDO降压后输出3.3V。其中CI-C22GS02模块、舵机、WS2812B、外部灯光和风扇供电均采用5.2V供电,MCU主控、IPS显示屏采用3.3V供电。

8.jpg

5.电路设计了2路MOS驱动电路,可外接5V的小风扇和5V供电的灯。另外预留了2种屏幕接口,支持8针的SPI接口的屏幕或者4针的I2C接口的屏幕。预留3组扩展接口,将剩余的IO口全部引出,方便扩展。

9.jpg

10.jpg

硬件部分介绍

智能语音分类垃圾桶主要包含主控板、显示屏、独立按键模块、加装舵机的垃圾桶模型、12V电源适配器。

软件部分介绍

软件分两部分,一部分是针对CW32F030C8T6的程序开发,另一部分是针对CI-C22GS02离线语音模块的命令词、语言模型、声学模型以及固件的制作。

1. CW32F030C8T6的程序开发。采用Keil5开发,借鉴了CW32官方例程和开源平台上一些大佬的程序。程序中涉及GPIO、DMA、定时器、ADC、UART、PWM、SPI等功能的实现。

因本人水平有限,写的程序只能说能用,但不保证好用。里面使用了大量的全局变量,还有很多改进的地方。

下面展示了部分程序。

(1)主程序

11.jpg

12.jpg

(2)串口发送程序

13.jpg

(3)串口接收程序(有BUG,接收两遍才能接收完整)

14.jpg15.jpg

(4)舵机执行动作的程序

采用定时器和标志位,使舵机缓慢打开,一定时间后缓慢自动关闭,4路舵机相互不受影响。

16.jpg

2. CI-C22GS02离线语音模块的命令词、语言模型、声学模型以及固件的制作。具体制作教程建议参考启英泰伦文档中心

(1)命令词列表(根据自己需要定制)

17.jpg

(2)生成的语言模型相应的文件放在这里

18.jpg

19.jpg

(3)生成的声学模型相应的文件放在这里

20.jpg

(4)生成固件之前需要手动修改这里,这里按照数字顺序(16进制)来修改。

21.jpg

(5)合并烧录固件

22.jpg

(6)打包固件

23.jpg

 24.jpg

(7)烧录固件。使用CH340串口工具,CH340的TX接语音模块串口0的RXD,CH340的RX接语音模块串口0的TXD,GNG接GND。CH340先插入电脑,烧录软件中出现对应的串口后,在右边的方框中打勾,然后按住UPDATE 按钮,CH340D的5V接语音模块+5V,此时烧录软件会显示开始烧录,直到烧录完成。

25.jpg

26.jpg

固件烧录成功后,喇叭应该会播报事先录入的欢迎词,这就说明烧录成功。

视频地址:https://www.bilibili.com/video/BV1oh411w7x2/?vd_source=2462ee6bfbc931195...

设计图

27.jpg

原理图_V1.0

28.jpg

原理图_V2.0

29.jpg

PCB_V2.0

工程源文件链接:https://oshwhub.com/myself1820/zhi-neng-yu-yin-fen-lei-de-la-ji-tong

来源:CW32生态社区

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

围观 19

页面

订阅 RSS - CW32