【正点原子STM32连载】 第二十三章 电容触摸按键实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/10/6 18:21:08

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第二十三章 电容触摸按键实验

上一章,我们介绍了STM32F1的输入捕获功能及其使用。这一章,我们将向大家介绍如何通过输入捕获功能,来做一个电容触摸按键。在本章中,我们将用TIM5的通道2(PA1)来做输入捕获,并实现一个简单的电容触摸按键,通过该按键控制DS1的亮灭。从本章分为如下几个部分:
21.1电容触摸按键简介
21.2硬件设计
21.3软件设计
21.4下载验证

23.1 电容触摸按键简介

前面我们学习过了机械按键,这节我们将介绍另一种人机交互设备:电容触摸按键。
电容式触摸按键已经广泛应用在家用电器、消费电子市场,其主要优势有:无机械装置,使用寿命长;非接触式感应,面板不需要开孔;产品更加美观简洁;防水可以做到很好。
战舰F103上的触摸按键TPAD其实就是一小块覆铜区域,其形状就是正点原子的logo,如图23.1.1所示。
在这里插入图片描述

图23.1.1 电容按键TPAD外观
与机械按键不同,这里我们使用的是检测电容充放电时间的方法来判断是否有触摸,图23.1.2中的A、B分别表示有无人体按下时电容的充放电曲线。其中R是外接的电容充电电阻,Cs是没有触摸按下时TPAD与PCB之间的杂散电容。而Cx则是有手指按下的时候,手指与TPAD之间形成的电容。图中的开关是电容放电开关(实际使用时,由STM32F1的IO代替)。
在这里插入图片描述

图23.1.2 电容按键TPAD外
先用开关将Cs(或Cs+Cx)上的电放尽,然后断开开关,让R给Cs(或Cs+Cx)充电,当没有手指触摸的时候,Cs的充电曲线如图中的A曲线。而当有手指触摸的时候,手指和TPAD之间引入了新的电容Cx,此时Cs+Cx的充电曲线如图中的B曲线。从上图可以看出,A、B两种情况下,Vc达到Vth的时间分别为Tcs和Tcs+Tcx。
其中,除了Cs和Cx我们需要计算,其他都是已知的,根据电容充放电公式:

其中Vc为电容电压,V0为充电电压,R为充电电阻,C为电容容值,e为自然底数,t为充电时间。根据这个公式,我们就可以计算出Cs和Cx。利用这个公式,我们还可以把战舰开发板作为一个简单的电容计,直接可以测电容容量了,有兴趣的朋友可以捣鼓下。
在本章中,其实我们只要能够区分Tcs和Tcs+Tcx,就已经可以实现触摸检测了,当充电时间在Tcs附近,就可以认为没有触摸,而当充电时间大于Tcs+Tx时,就认为有触摸按下(Tx为检测阀值)。
本章,我们使用PA1(TIM5_CH2)来检测TPAD是否有触摸,在每次检测之前,我们先配置PA1为推挽输出,将电容Cs(或Cs+Cx)放电,然后配置PA1为浮空输入,利用外部上拉电阻给电容Cs(Cs+Cx)充电,同时开启TIM5_CH2的输入捕获,检测上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。
在MCU每次复位重启的时候,我们执行一次捕获检测(可以认为没触摸),记录此时的值,记为tpad_default_val,作为判断的依据。在后续的捕获检测,我们就通过与tpad_default_val的对比,来判断是不是有触摸发生。
关于输入捕获的配置,在上一章我们已经有详细介绍了,这里我们就不再介绍。至此,电容触摸按键的原理介绍完毕。
23.2 硬件设计

  1. 例程功能
    LED0用来指示程序运行,150ms变换一次状态,即约300ms一次闪烁。不断扫描按键的状态,如果判定了电容触摸按键按下,我们就把LED1的状态翻转一次。
  2. 硬件资源
    1)LED灯:
    LED0 – PB5
    LED1 – PE5
    2) 定时器 TIM5
    3) GPIO:PA1,用于控制触摸按键TPAD。
  3. 原理图
    触摸按键的原理图TPAD设计如图23.2.1所示
    在这里插入图片描述

图23.2.1 电容按键TPAD连接原理图
由于设计时PA1不直接连接到电容触摸按键,而是引到了插针上,我们需要通过跳线帽把P10上标为ADC的引脚与标号为“TPAD”的标号连接到一起,连接图23.2.2所示。
在这里插入图片描述

图23.2.2 用跳线帽连接电容按键TPAD和PA1
23.3 程序设计
我们在基本定时器一节已经学习过定时器的输入捕获功能,这里我们可以类似地,用定时器5来实现这对TPAD引脚上的电平状态进行捕获的功能。本实验用到的HAL库驱动请回顾基本定时器实验的介绍。下面直接从程序流程图开始介绍。
23.3.1 程序流程图
在这里插入图片描述

图23.2.3.1.1 电容触摸按键实验程序流程图
23.3.2 程序解析
TPAD可以看作《实验9-3 通用定时器输入捕获实验》的一个应用案例,相关的HAL库函数函数在定时器章节我们已经介绍过了,所以这里就不重复介绍了,大家回过头复习一下即可。

  1. TPAD驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,TPAD的驱动主要包括两个文件:tpad.c和tpad.h。
    首先看tpad.h头文件的几个宏定义:
/* TPAD 引脚 及 定时器 定义 */
#define TPAD_GPIO_PORT      			GPIOA
#define TPAD_GPIO_PIN              	GPIO_PIN_1
/* PA口时钟使能 */
#define TPAD_GPIO_CLK_ENABLE()  	do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) 

#define TPAD_TIMX_CAP                       TIM5
#define TPAD_TIMX_CAP_CHY                  TIM_CHANNEL_2 	/* 通道Y,  1<= Y <=4 */
#define TPAD_TIMX_CAP_CHY_CCRX            TIM5->CCR2    	/* 通道Y的捕获/比较寄存器 */
#define TPAD_TIMX_CAP_CHY_CLK_ENABLE()  \
do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0)   	/* TIM5 时钟使能 */

PA1是定时器5的PWM通道2,如果我们使用其它定时器和它们对应的捕获通道的其它IO,我们只需要修改上面的宏即可。
利用前面描述的触摸按键的原理,上电时检测TPAD上的电容的充放电时间,并以此为基准,每次需要重新检测TPAD时,通过比较充放电的时长来检测当前是否有按下,所以我们需要用定时器的输入捕获来监测低TPAD上低电平的时间。编写tpad_timx_cap_init()函数如下:

/**
 * @brief       触摸按键输入捕获设置
 * @param       arr     :自动重装值
 * @param       psc     :时钟预分频数
 * @retval      无
 */
static void tpad_timx_cap_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_IC_InitTypeDef timx_ic_cap_chy;
    TPAD_GPIO_CLK_ENABLE();                      	/* TPAD引脚 时钟使能 */
    TPAD_TIMX_CAP_CHY_CLK_ENABLE();           	/* 定时器 时钟使能 */

    gpio_init_struct.Pin = TPAD_GPIO_PIN;     	/* 输入捕获的GPIO口 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT; 	/* 复用推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLDOWN;               	/* 下拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; 	/* 中速 */
    HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);  	/* TPAD引脚浮空输入 */

    g_timx_cap_chy_handle.Instance = TPAD_TIMX_CAP;     	/* 定时器5 */
    g_timx_cap_chy_handle.Init.Prescaler = psc;            	/* 定时器分频 */
    g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/*向上计数模式 */
    g_timx_cap_chy_handle.Init.Period = arr;              	/* 自动重装载值 */
    g_timx_cap_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/*不分频*/
    HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
    timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; 	/* 上升沿捕获 */
    timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;/*映射TI1*/
    timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; 	/* 配置输入不分频 */
    timx_ic_cap_chy.ICFilter = 0;               		/* 配置输入滤波器,不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy, 
&timx_ic_cap_chy, TPAD_TIMX_CAP_CHY);/* 配置TIM5通道2 */
    HAL_TIM_IC_Start(&g_timx_cap_chy_handle,TPAD_TIMX_CAP_CHY);/* 使能输入捕获 */
}

这和我们《实验9-3 通用定时器输入捕获实验》的代码基本一样,原理就不重复解释了,接下我们通过控制变量法,每次先给TPAD放电(STM32输出低电平)相同时间,然后释放,监测VCC每次给TPAD的充电时间,由此可以得到一个充电时间,操作的代码如下:

/**
 * @brief       复位TPAD
 *   @note      我们将TPAD按键看做是一个电容, 当手指按下/不按下时容值有变化
 *               该函数将GPIO设置成推挽输出, 然后输出0, 进行放电, 然后再设置
 *               GPIO为浮空输入, 等待外部大电阻慢慢充电
 * @param       无
 * @retval      无
 */
static void tpad_reset(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    gpio_init_struct.Pin = TPAD_GPIO_PIN;          		/* 输入捕获的GPIO口 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;  		/* 复用推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                  	/* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;   	/* 中速 */
    HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
/* TPAD引脚输出0, 放电 */
    HAL_GPIO_WritePin(TPAD_GPIO_PORT, TPAD_GPIO_PIN, GPIO_PIN_RESET);   
    delay_ms(5);
    g_timx_cap_chy_handle.Instance->SR = 0;    		/* 清除标记 */
    g_timx_cap_chy_handle.Instance->CNT = 0;      	/* 归零 */

    gpio_init_struct.Pin = TPAD_GPIO_PIN;         	/* 输入捕获的GPIO口 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;      	/* 复用推挽输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;          	/* 浮空 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;  	/* 中速 */
    HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); 	/* TPAD引脚浮空输入 */
}

/**
 * @brief    	得到定时器捕获值
 *   @note    	如果超时, 则直接返回定时器的计数值
 *              	我们定义超时时间为: TPAD_ARR_MAX_VAL - 500
 * @param       	无
 * @retval      	捕获值/计数值(超时的情况下返回)
 */
static uint16_t tpad_get_val(void)
{
    uint32_t flag = (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_1)?TIM_FLAG_CC1:\
                    (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_2)?TIM_FLAG_CC2:\
                    (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_3)?TIM_FLAG_CC3:TIM_FLAG_CC4;
    
    tpad_reset();
    while (__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handle ,flag) == RESET) 
    {    /* 等待通道CHY捕获上升沿 */
        if (g_timx_cap_chy_handler.Instance->CNT > TPAD_ARR_MAX_VAL - 500)
        {
           return g_timx_cap_chy_handle.Instance->CNT; /* 超时了,直接返回CNT的值 */
        }
    }

    return TPAD_TIMX_CAP_CHY_CCRX;      /* 返回捕获/比较值 */
}

/**
 * @brief   		读取n次, 取最大值
 * @param    	n       :连续获取的次数
 * @retval    	n次读数里面读到的最大读数值
 */
static uint16_t tpad_get_maxval(uint8_t n)
{
    uint16_t temp = 0;
    uint16_t maxval = 0;

    while (n--)
    {
        temp = tpad_get_val();  /* 得到一次值 */
        if (temp > maxval)maxval = temp;
    }

    return maxval;
}

得到充电时间后,接下来我们要做的就是获取没有按下TPAD时的充电时间,并把它作为基准来确认后续有无按下操作,我们定义全局变量g_tpad_default_val来保存这个值,通过多次平均的滤波算法来减小误差,编写的初始化函数tpad_init代码如下。

/**
 * @brief   		初始化触摸按键
 * @param   		psc     : 分频系数(值越小, 越灵敏, 最小值为: 1)
 * @retval    	0, 初始化成功; 1, 初始化失败;
 */
uint8_t tpad_init(uint16_t psc)
{
    uint16_t buf[10];
    uint16_t temp;
    uint8_t j, i;
    tpad_timx_cap_init(TPAD_ARR_MAX_VAL, psc - 1);/* 以72/(psc-1)Mhz的频率计数 */

    for (i = 0; i < 10; i++)    		/* 连续读取10次 */
    {
        buf[i] = tpad_get_val();
        delay_ms(10);
    }

    for (i = 0; i < 9; i++)     		/* 排序 */
    {
        for (j = i + 1; j < 10; j++)
        {
            if (buf[i] > buf[j])		/* 升序排列 */
            {
                temp = buf[i];
                buf[i] = buf[j];
                buf[j] = temp;
            }
        }
    }

    temp = 0;

    for (i = 2; i < 8; i++)     		/* 取中间的6个数据进行平均 */
    {
        temp += buf[i];
    }
    
    g_tpad_default_val = temp / 6;
    printf("g_tpad_default_val:%d\r\n", g_tpad_default_val);

    if (g_tpad_default_val > TPAD_ARR_MAX_VAL / 2)
    {
        return 1;   /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */
    }

    return 0;
}
得到初始值后,我们需编写一个按键扫描函数,以方便在需要监控TPAD的地方调用,代码如下:
/**
 * @brief    	扫描触摸按键
 * @param      	 mode    :扫描模式
 *   @arg       	0, 不支持连续触发(按下一次必须松开才能按下一次);
 *   @arg       	1, 支持连续触发(可以一直按下)
 * @retval      	0, 没有按下; 1, 有按下;
 */
uint8_t tpad_scan(uint8_t mode)
{
    static uint8_t keyen = 0; 	/* 0, 可以开始检测;  >0, 还不能开始检测; */
    uint8_t res = 0;
    uint8_t sample = 3;         	/* 默认采样次数为3次 */
    uint16_t rval;

    if (mode)
    {
        sample = 6;     /* 支持连按的时候,设置采样次数为6次 */
        keyen = 0;      /* 支持连按, 每次调用该函数都可以检测 */
    }
    rval = tpad_get_maxval(sample);
    if (rval > (g_tpad_default_val + TPAD_GATE_VAL))
    {   /* 大于tpad_default_val+TPAD_GATE_VAL,有效 */
        if (keyen == 0)
        {
            res = 1;    /* keyen==0, 有效 */
        }

        //printf("r:%d\r\n", rval);   /* 输出计数值, 调试的时候才用到 */
        keyen = 3;      /* 至少要再过3次之后才能按键有效 */
    }
    if (keyen)keyen--;
    return res;
}

TPAD函数到此就编写完了,接下来我们通过main函数编写测试代码来验证一下TPAD的逻辑是否正确。
2. main.c代码
在main.c里面编写如下代码:

int main(void)
{
uint8_t t = 0;

    HAL_Init();                  			/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);	/* 设置时钟, 72Mhz */
    delay_init(72);                      	/* 延时初始化 */
    usart_init(115200);                 	/* 串口初始化为115200 */
    led_init();                           	/* 初始化LED */
    tpad_init(6);                        	/* 初始化触摸按键 */

    while (1)
    {
        if (tpad_scan(0)) /* 成功捕获到了一次上升沿(此函数执行时间至少15ms) */
        {
            LED1_TOGGLE(); 				/* LED1取反 */
        }

        t++;
        if (t == 15)
        {
            t = 0;
            LED0_TOGGLE(); 				/* LED0取反 */
        }
        delay_ms(10);
    }
}

初始化了必要的外设后,我们通过循环来实现我们的代码操作。我们在扫描函数中定义了电容按触摸发生后的状态,通过判断返回值来判断是否符合按下的条件,如果按下我们就翻转一次LED1。LED0通过累计延时次数的方法,既能保证扫描的频率,又能达到定时翻转的目的。
23.4 下载验证
下载代码后,可以看到LED0不停闪烁(每300ms闪烁一次),用手指按下电容按键时,LED1的状态发生改变(亮灭交替一次)。这里记得TPAD引脚和PA1都是连接到开发板上的排针上的,开始测试前需要连接好,否则测试就不准了,如果下载代码前没有连接好,请连接后复位重新测试即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/598416.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java网络开发(Tomcat)—— 登陆 和 注册功能 的实现 和 迭代升级

目录 引出登陆功能---从html到jsp1.登陆--用post请求2.用html文件的form表单登陆&#xff08;1&#xff09;index.html页面&#xff08;2&#xff09;login.html登陆的页面&#xff08;3&#xff09;LoginServlet.java处理输入信息的代码&#xff08;4&#xff09;登陆成功&…

分布式事务一 事物以及分布式事物介绍

一 事务简介 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中&#xff0c;一个事务由一组SQL语句组成。事务应该具有4个属性&#xff1a;原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。 原子性&#xff08;at…

MyBatis - Spring Boot 集成 MyBatis

文章目录 1.版本要求2.导入依赖3.自动配置2.可配置项 MyBatis-Spring-Boot-Starter 可以帮助你更快地在 Spring Boot 之上构建 MyBatis 应用。通过使用该模块我们能够快速实现以下目的&#xff1a; 构建单体应用程序将几乎不需要样板配置使用更少的 XML 配置 1.版本要求 MyB…

Apache Kafka - 构建数据管道 Kafka Connect

文章目录 概述主要概念ConnectorTasksWorkesConvertersTransformsDead Letter Queue 主要使用场景主要价值Kafka Connect API vs Producer 和 Consumer API构建数据管道时需要考虑的主要问题ETL VS ELT数据整合方式的不同ETL 和 ELT 各有优缺点: 概述 Kafka Connect 是一个工具…

Linux二——Web基础与HTTP协议

Web基础与HTTP协议 一、Web基础1. 域名的概念2.域名空间结构3.域名注册4.网页的概念5. HTML概念6.网页基本标签1 二、HTTP协议1.HTTP概念2.HTML的基本标签3.头标签中常用标签4.内容标签中常用的标签 三、动态网页与静态网页1.静态网页2.动态网页3.动态网页和静态网页的区别4.动…

软件测试目的是什么?软件测试公司可提供哪些测试服务类型?

随着科技的不断发展&#xff0c;软件行业的发展也越来越迅速。然而&#xff0c;随着软件的增多和复杂性的提高&#xff0c;开发者们需要更多的手段来确保软件质量。软件测试就是通过一系列的测试来发现软件的问题&#xff0c;从而提高软件的质量。 一、软件测试目的是什么? …

Unity中的UniTask如何取消指定的任务或所有的任务

今天儿童节&#xff0c;犬子已经9个多月了&#xff0c;今天是他的第一个儿童节。中年得子&#xff0c;其乐无穷无尽啊… 〇、 示例效果 一连创建5个异步任务[id 从0~4]&#xff0c;先停止其中的第id 4的任务&#xff0c;再停止所有的任务 一、CancellationTokenSource有什…

利用矩阵分解实现图像压缩(实验)

机器学习的课程&#xff0c;老师布置了一个实验报告&#xff0c;当我看到实验内容&#xff0c;傻眼了&#xff0c;手写计算矩阵特征值和特征向量的函数&#xff0c;这给我整无语了&#xff0c;直接调用已有的不好吗&#xff0c; 我直接摆烂。 实验报告放这了&#…

【C#图解教程】 第六章 方法(上)

方法的结构 方法是一块具有名称的代码&#xff0c;在类和结构中都经常用到 局部变量 局部变量位于方法内部&#xff0c;在方法中声明时产生&#xff0c;在方法执行结束时结束 类型推断与var关键字 某些情况下&#xff0c;在声明的开始部分包含类型名是多余的&#xff0c;因为…

Python-python判断语句:布尔类型和比较运算符、if语句的基本格式、if else语句、if el if else语句、判断语句的嵌套、实战案例

版本说明 当前版本号[20230601]。 版本修改说明20230601初版 知识总览图 目录 文章目录 版本说明知识总览图目录Python判断语句布尔类型和比较运算符布尔类型布尔类型的定义 比较运算符 if语句的基本格式if判断语句if语句的注意点 if else 语句语句注意点 if elif else语句注…

7min 到 40s:SpringBoot 优化居然可以玩出这么多花样!

0 背景 公司 SpringBoot 项目在日常开发过程中发现服务启动过程异常缓慢&#xff0c;常常需要6-7分钟才能暴露端口&#xff0c;严重降低开发效率。通过 SpringBoot 的 SpringApplicationRunListener 、BeanPostProcessor 原理和源码调试等手段排查发现&#xff0c;在 Bean 扫描…

“灵巧小工具”一个将图片处理成打印纸尺寸的丰富功能完全免费无水印的图片处理工具

今天推荐一款微信小程序“灵巧小工具”&#xff0c;经常有打印图片需求的用户赶紧收藏了。 可以先扫码体验一番&#xff1a; 下面介绍一下它的主要功能&#xff1a; 1.照片&#xff08;1寸、2寸、5寸、6寸&#xff09; 支持1寸&#xff0c;2寸照片自动排版&#xff0c;生成相纸…

JavaSE】Java基础语法(四十):UDP通信程序

文章目录 1. UDP发送数据2. UDP接收数据【应用】3. UDP通信程序练习【应用】4. UDP三种通讯方式 1. UDP发送数据 Java中的UDP通信 UDP协议是一种不可靠的网络协议&#xff0c;它在通信的两端各建立一个Socket对象&#xff0c;但是这两个 Socket只是发送&#xff0c;接收数据的对…

IPv6 6to4隧道配置和验证实验

IPv6 6to4隧道配置和验证实验 【实验目的】 熟悉IPv6 6to4隧道的概念。 掌握IPv6和IPv4共存的实现方法。 掌握IPv6 6to4地址编址规则。 掌握IPv6 6to4隧道的配置。 验证配置。 【实验拓扑】 设备参数如下表所示。 设备 接口 IP地址 子网掩码 默认网关 R1 S0/0 19…

cuda编程学习——CUDA共享内存性能优化(九)

前言 参考资料&#xff1a; 高升博客 《CUDA C编程权威指南》 以及 CUDA官方文档 CUDA编程&#xff1a;基础与实践 樊哲勇 文章所有代码可在我的GitHub获得&#xff0c;后续会慢慢更新 文章、讲解视频同步更新公众《AI知识物语》&#xff0c;B站&#xff1a;出门吃三碗饭 …

绝不可错过!R语言与ggplot2实现SCI论文数据分析神器

一、介绍 1.1 R语言和ggplot2 语言是一种强大的数据分析和统计建模工具&#xff0c;具有广泛的应用领域。 ggplot2是基于R语言的数据可视化工具&#xff0c;具有强大的绘图功能和灵活性。 1.2 数据分析中的重要性 R语言和ggplot2在数据分析中具有广泛的应用&#xff0c;能够帮助…

有些香港云主机为啥更容易遭遇停机风险?

​对于搭建过外贸网站的站长们来说&#xff0c;在面对香港云主机的选择时&#xff0c;往往遇到且出现较为频繁的两个词便是&#xff1a;免费香港云主机和付费香港云主机。其中&#xff0c;一些所谓的免费香港云主机&#xff0c;尤其是长久免费使用&#xff0c;恐怕用户们就要承…

ES6中函数新增了哪些扩展?

参数 &#x1f355;&#x1f355;&#x1f355;ES6允许为函数的参数设置默认值 函数的形参是默认声明的&#xff0c;不能使用let或const再次声明 function foo(x5){let x 1;//errconst x 2;//err }参数默认值可以与解构赋值的默认值结合起来使用 function foo({x,y 5}){co…

华为OD机试真题B卷 Java 实现【分奖金】,附详细解题思路

一、题目描述 公司老板做了一笔大生意&#xff0c;想要给每位员工分配一些奖金&#xff0c;想通过游戏的方式来决定每个人分多少钱。按照员工的工号顺序&#xff0c;每个人随机抽取一个数字。按照工号的顺序往后排列&#xff0c;遇到第一个数字比自己数字大的&#xff0c;那么…

OpenGL超级宝典第八章学习笔记:基元处理之曲面细分

前言 本篇在讲什么 OpenGL蓝宝书第八章学习笔记之曲面细分 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻…