【基于GD32E230的定时器级联M/T法电机测速】

news2024/12/25 8:53:30

前言

在有感电机控制中,获取电机转速是非常重要的步骤,转速获取越准确,控制电机时越方便,抛开霍尔不谈,这里讨论电机编码器。

目前常见的电机编码器按种类分为绝对值编码器和增量编码器,绝对值编码器相对较为方便,编码器直接通过通讯接口输出绝对位置信息,MCU接收角度信息便可。而增量编码器就需要MCU测量编码器输出的脉冲信号,通过计算脉冲信号的频率,从而计算实际转速,同时通过A B两相的相位差获取旋转方向。通过这样的方式测量转速一般有三种方法,下面就这三种方法展开介绍,并分析每个方法的优劣势。

一、测频率法(M法)

M法测速又称“定时测角法”,指在一定周期T内,测量编码器输出的脉冲个数M1,然后计算出转速。
n = 60 ∗ M 1 / ( p T ) n = 60*M_1/(pT) n=60M1/(pT)
在这里插入图片描述
首先已知电机旋转一周,编码器会产生P个脉冲信号,P由编码器线数和倍频数的乘积决定,例如2500线4倍频编码器,则P = 2500*4 = 10000。在一定时间周期T内,测量编码器输出的脉冲个数M1,则编码器输出频率为:
f = M 1 / T f = M_1/T f=M1/T
用频率f除以一圈脉冲数p就可以得到电机转速n:
n = f / p ( r / s ) n = f/p (r/s) n=f/p(r/s)
电机转速单位为r/min,所以还需要再乘以60:
n = f / p ∗ 60 = 60 ∗ M 1 P T n = f/p *60 = \dfrac {60*M_1}{PT} n=f/p60=PT60M1
电机一圈脉冲数P和周期T一般是已知常数,那电机的转速和单位时间内的脉冲计数M1成正比,试想,当单位时间内捕获到M1个脉冲,刚好未捕捉到M1+1个脉冲,此时误差最大,最大误差为一个脉冲,所以误差率为:
δ m a x = 1 M 1 ∗ 100 % \delta _{max} = \dfrac {1}{M_1}*100\% δmax=M11100%
测速误差率与脉冲个数成反比关系,转速越高 M1 值越大,当转速很低时,M1 值很小,误差率会变大,因此 M 法适合高速测量

二、测周期法(T法)

T 法又称“定角测时法”,是测量编码器相邻两个脉冲之间的时间间隔来计算转速,也被称为周期法。实际使用中通过一个高频时钟脉冲的个数 M2 来计算编码器两个脉冲之间的时间间隔。
在这里插入图片描述
另高频脉冲频率为f0,两个相邻脉冲之间的时间间隔Tt = M2/f0,电机的转速可以表示为:
n = 60 p T t = 60 f 0 p M 2 n = \dfrac {60}{pTt} = \dfrac {60f_0}{pM_2} n=pTt60=pM260f0
T法测速误差来源于高频脉冲数量,最多可能产生一个高频脉冲的误差,当产生了一个脉冲误差时,最大误差率为:
δ m a x = 60 f 0 p ( M 2 − 1 ) − 60 f 0 p M 2 60 f 0 p M 2 ∗ 100 % = 1 M 2 − 1 ∗ 100 % \delta _{max} = \dfrac {\dfrac {60f_0}{p(M_2-1)}-\dfrac {60f_0}{pM_2}}{\dfrac {60f_0}{pM_2}}*100\% = \dfrac {1}{M_2-1}*100\% δmax=pM260f0p(M21)60f0pM260f0100%=M211100%
这里可以预料到的是,转速越快,间隔时间Tt越短,高频脉冲个数M2越少,当丢失一个高频脉冲数,就会对转速造成很大影响。当转速越慢,单位时间的M2越多,误差越小。因此T法测速更适合低转速应用

三、M/T法测速

以采样周期为基准,在采样时间T内,同时计算编码器输出的脉冲数量M1和高频脉冲数量M2,同时尽量保持两个计数时间的的严格同步,最大限度减小误差。
在这里插入图片描述
设电机旋转一圈编码器输出脉冲数为p,高频脉冲频率为fc,T时间内,高频脉冲计数为M2,编码器输出脉冲数为M1,则此时T时间内电机旋转圈数:
n = M 1 p n = \dfrac {M_1}{p} n=pM1
一秒电机旋转圈数为:
n = M 1 p T n = \dfrac {M_1}{pT} n=pTM1
每分钟的转速为:
n = 60 ∗ M 1 p T n = 60*\dfrac {M_1}{pT} n=60pTM1
T可通过高频脉冲计数原理求得:
T = M 2 f c T = \dfrac {M_2}{f_c} T=fcM2
此种方式在低速和高速都有很高的分辨率,最适合宽速度检测范围使用。

四、如何在GD32单片机上实现上述过程

这里本人从M法测速开始,发现低转速速度误差太大,于是升级了M/T法测速,这里仅介绍这两种的实现,T法就不做实现,其实M/T能实现,T法就一定不会出问题啦。下文将逐步讲解我的开发历程。

1.试验资料

a.GD32E230C8T6
b.LME2500FE 2500线四倍频磁编码器
c.纸飞机串口助手
d.伺服电机+驱动器

2.M法测速实现及试验结果

在设计初期,使用定时器2的通道0输入捕获来获取编码器的输出脉冲M1,同时使用定时器0作为时间T。
首先将定时器2设置为输入捕获,同时开启通道0的中断,每捕获到一个脉冲则进入一次中断,将标志位+1;定时器0作为普通中断,开启溢出中断,定时检测捕获数量。计算完成存入数组,在主函数转换为速度,通过串口发送到PC进行显示。

void timer2_configuration(void)
{
        gpio_configuration();
        
    timer_ic_parameter_struct timer_icinitpara;
    timer_parameter_struct timer_initpara;
    /* enable the TIMER clock */
    rcu_periph_clock_enable(RCU_TIMER2);

    timer_deinit(TIMER2);
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler         = 0;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 65535;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER2, &timer_initpara);

    timer_channel_input_struct_para_init(&timer_icinitpara);
    timer_icinitpara.icpolarity  = TIMER_IC_POLARITY_BOTH_EDGE;
    timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;
    timer_icinitpara.icfilter    = 0x0;
    timer_input_pwm_capture_config(TIMER2, TIMER_CH_0, &timer_icinitpara);

    timer_input_trigger_source_select(TIMER2, TIMER_SMCFG_TRGSEL_CI0FE0);
    timer_slave_mode_select(TIMER2, TIMER_SLAVE_MODE_RESTART);

    timer_master_slave_mode_config(TIMER2, TIMER_MASTER_SLAVE_MODE_ENABLE);
    timer_auto_reload_shadow_enable(TIMER2);
    timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_CH0);
    timer_interrupt_enable(TIMER2, TIMER_INT_CH0);
    timer_enable(TIMER2);
}

void timer_configuration(void)
{
    timer_ic_parameter_struct timer_icinitpara;
    timer_parameter_struct timer_initpara;


    rcu_periph_clock_enable(RCU_TIMER0);

    timer_deinit(TIMER0);
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler         = 71;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 1999;//1MS
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
        timer_initpara.repetitioncounter = 0;
    timer_init(TIMER0, &timer_initpara);

    timer_auto_reload_shadow_enable(TIMER0);
    timer_interrupt_flag_clear(TIMER0,TIMER_INT_FLAG_UP);
    timer_interrupt_enable(TIMER0,TIMER_INT_FLAG_UP);
    timer_enable(TIMER0);        
}

void TIMER2_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_CH0)){
        timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_CH0);
                                readvalue2++;
    }
}
void TIMER0_BRK_UP_TRG_COM_IRQHandler(void)
{
        if(SET == timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP))
        {
                timer_interrupt_flag_clear(TIMER0, TIMER_FLAG_UP);
                if(readvalue2 > readvalue1){
                                count = (readvalue2 - readvalue1); 
                }else{
                                count = ((0xFFFFU - readvalue1) + readvalue2); 
                }
                TIM_NUM++;
                readvalue1=readvalue2;
                if(TIM_NUM>999&TIM_NUM<1999)
                {
                        Speed_num[TIM_NUM-1000] = count+1;                        
                }
                if(TIM_NUM>1999)
                {
                        timer_disable(TIMER0);
                }
        }
}

在定时器0的中断响应函数中,对TIMER0的当前捕获值和上一次捕捉只进行运算,得出单位时间T内的脉冲数量,并将值保存进Speed_num数组,保存到1000个TIMER0失能中断,主函数开始处理数据并发送到上位机。

    while(1){
                if(TIM_NUM>1999)
                {                        
                        for(uint16_t p=0;p<999;p++)
                        {
                                kalman_height = Speed_num[p]*60/10000*0.02;
        //                                                kalman_height=kalmanFilter(&KFP_height,kalman_height);
                                printf("{Speed:%f}\n",kalman_height);                                        
                        }
                        TIM_NUM = 0;
                        timer_enable(TIMER0);        
                }
                        
    }

其中,M1 = Speed_num[p],P = 2500*4 = 10000,T = 20ms =0.02s。

开启伺服电机驱动器,设置速度3000转,先使用驱动器读取一下速度波动,如下图一所示:
图一:伺服驱动读取速度波动-3000rp/m
图一:伺服驱动读取速度波动-3000rp/m
再通过串口助手获取单片机M法测速速度波动,如下图二所示:
在这里插入图片描述
图二:M法测速测得速度波动-3000rp/m
因为两张图的采样频率不同,显示的效果也会不同,而且驱动器会进行数据滤波,但是还是可以看出速度分布在3000附近,下面我们将速度数据卡尔曼滤波(后文所有的图均默认进行滤波处理),如下图三所示。
在这里插入图片描述
图三:M法卡尔曼滤波速度-3000rp/m
由上图三和图一进行比较,可以发现我们使用GD32的M法测速在3000转时表现较好,基本和驱动器测得数据较为吻合,说明方法是没问题的,于是我们降低转速,继续对比。
在这里插入图片描述
图四:伺服驱动读取速度波动-2000rp/m

在这里插入图片描述
图五:M法卡尔曼滤波速度-2000rp/m
在这里插入图片描述

图六:伺服驱动读取速度波动-1000rp/m
在这里插入图片描述
图七:M法卡尔曼滤波速度-1000rp/m
从图四-图七,对比就会发现,在2000rp/m时,速度已经发生了偏移,速度点以2002rp/m为中心分布,在1000rp/m时,速度点以2004rp/m为中心分布,速度越低,产生的速度误差越大。

3. M/T法测速实现及试验结果

初期试验:

一开始我使用三个定时器,定时器2继续使用M法的捕获功能,捕获M1的值,使用定时器14产生高频脉冲,每当计数器到达就触发中断,获取M2的值,最后在定时器0中定时获取M1和M2的值,在主函数打印。此方法在3500rp/m以下均无问题,但是速度再提高,编码器输出的频率增高,导致中断处理不过来了,M1不再随着转速的提高而提高,速度显示也就无法提高了。后来想到这块MCU的定时器是有正交译码器的。如此便不会受中断的影响。

GD32E230之正交译码器的M/T法测速。

介于本篇文章篇幅已经很长了, 就不介绍定时器的正交译码器功能了,该功能网络上资料很多,这里不赘述,我们从代码入手:

int rmp;
int m1,m2;
void gpio_configuration(void)
{
    /* enable the GPIOA clock */
    rcu_periph_clock_enable(RCU_GPIOA);
    /*configure PA6(TIMER2 CH0) as alternate function*/
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6);
    gpio_af_set(GPIOA, GPIO_AF_1, GPIO_PIN_6);        
        gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
         gpio_af_set(GPIOA, GPIO_AF_1, GPIO_PIN_7);
//        gpio_mode_set(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
}
/**定时器2  用于捕获编码器输出脉冲数*/
void timer2_configuration(void)
{
        gpio_configuration();
        
    timer_ic_parameter_struct timer_icinitpara;
    timer_parameter_struct timer_initpara;
    /* enable the TIMER clock */
    rcu_periph_clock_enable(RCU_TIMER2);

    /* deinit a TIMER */
    timer_deinit(TIMER2);
    /* initialize TIMER init parameter struct */
    timer_struct_para_init(&timer_initpara);
    /* TIMER2 configuration */
    timer_initpara.prescaler         = 0;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 65535;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER2, &timer_initpara);

    /* TIMER2 configuration */
    /* initialize TIMER channel input parameter struct */
    timer_channel_input_struct_para_init(&timer_icinitpara);
    /* TIMER2 CH0 PWM input capture configuration */
    timer_icinitpara.icpolarity  = TIMER_IC_POLARITY_RISING;
    timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;
    timer_icinitpara.icfilter    = 0x0;
    timer_input_capture_config(TIMER2, TIMER_CH_0, &timer_icinitpara);
        timer_input_capture_config(TIMER2, TIMER_CH_1, &timer_icinitpara);
                
         timer_quadrature_decoder_mode_config(TIMER2, TIMER_ENCODER_MODE2, TIMER_IC_POLARITY_BOTH_EDGE, TIMER_IC_POLARITY_BOTH_EDGE);

    timer_slave_mode_select(TIMER2, TIMER_ENCODER_MODE2);
    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER2);
    /* clear channel 0 interrupt bit */
    timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP);
//    /* channel 0 interrupt enable */
    timer_interrupt_enable(TIMER2, TIMER_INT_UP);
    /* TIMER2 counter enable */
    timer_enable(TIMER2);
}
/**定时器0  用于周期获取脉冲数和高频时钟脉冲***/
void timer_configuration(void)
{
    timer_ic_parameter_struct timer_icinitpara;
    timer_parameter_struct timer_initpara;

    /* enable the TIMER clock */
    rcu_periph_clock_enable(RCU_TIMER0);
    /* disable a TIMER */
    timer_deinit(TIMER0);
    /* initialize TIMER init parameter struct */
    timer_struct_para_init(&timer_initpara);
    /* TIMER2 configuration */
    timer_initpara.prescaler         = 7199;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 9;//1S
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
        timer_initpara.repetitioncounter = 0;
    timer_init(TIMER0, &timer_initpara);

    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER0);
    /* clear channel 0 interrupt bit */
    timer_interrupt_flag_clear(TIMER0,TIMER_INT_FLAG_UP);
    /* channel 0 interrupt enable */
    timer_interrupt_enable(TIMER0,TIMER_INT_FLAG_UP);
    /* TIMER2 counter enable */
    timer_enable(TIMER0);        
}

/****定时器14  用于产生高频时钟脉冲****/
void timer14_configuration(void)
{
    timer_ic_parameter_struct timer_icinitpara;
    timer_parameter_struct timer_initpara;

    /* enable the TIMER clock */
    rcu_periph_clock_enable(RCU_TIMER14);
    /* disable a TIMER */
    timer_deinit(TIMER14);
    /* initialize TIMER init parameter struct */
    timer_struct_para_init(&timer_initpara);
    /* TIMER2 configuration */
    timer_initpara.prescaler         = 1;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 359;//624us采样一次
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
        timer_initpara.repetitioncounter = 0;
    timer_init(TIMER14, &timer_initpara);

    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER14);
    /* clear channel 0 interrupt bit */
    timer_interrupt_flag_clear(TIMER14,TIMER_INT_FLAG_UP);
    /* channel 0 interrupt enable */
    timer_interrupt_enable(TIMER14,TIMER_INT_FLAG_UP);
    /* TIMER2 counter enable */
    timer_enable(TIMER14);        
}

void TIMER2_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_UP)){
        timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP);
    }
}
void TIMER14_IRQHandler(void)
{
        if(SET == timer_interrupt_flag_get(TIMER14, TIMER_INT_FLAG_UP))
        {
                timer_interrupt_flag_clear(TIMER14, TIMER_FLAG_UP);
                m2++;
        }
}
int m1_data[point_num],m2_data[point_num];
void TIMER0_BRK_UP_TRG_COM_IRQHandler(void)
{
        if(SET == timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP))
        {
                timer_interrupt_flag_clear(TIMER0, TIMER_FLAG_UP);
                m1 = timer_counter_read(TIMER2);
                if(TIM_NUM<point_num)
                {
                        m1_data[TIM_NUM] = m1;
                        m2_data[TIM_NUM] = m2;
                        TIM_NUM++;
                                        m1 = 0;
                                m2 = 0;
                }else
                {
                        timer_disable(TIMER0);        
                        timer_disable(TIMER2);        
                        timer_disable(TIMER14);                
                }

        }
}

TIMER2作为高级定时器,拥有正交译码器的功能,我们将其通道0和通道1接到编码器的A和B相,初始化TIMER2为译码器模式。

  timer_quadrature_decoder_mode_config(TIMER2, TIMER_ENCODER_MODE2, TIMER_IC_POLARITY_BOTH_EDGE, TIMER_IC_POLARITY_BOTH_EDGE);

    timer_slave_mode_select(TIMER2, TIMER_ENCODER_MODE2);

主要就是这两个函数,其他的几乎和普通定时器设置差不多。

TIMER14用于产生一个100K的高频时钟脉冲,并使能溢出中断,每产生一个脉冲则将M2的值累加1;

定时器0 用于周期获取脉冲数和高频时钟脉冲数,并使能溢出中断,中断到达后读取TIMER2的计数值,该值的变化量与编码器输出脉冲相同,计算两次之间的差值即为每次采样脉冲间隔的编码器脉冲数。中断中将M1和M2的值存入数组,在主函数进行速度计算并输出:

    while(1){
        if(TIM_NUM>point_num-2)
        {                        
                for(uint16_t p=5;p<point_num-2;p++)
                {
                        m1calc = m1_data[p]-m1_data[p+1];
                        if(m1calc<0)
                        {
                                m1calc = m1calc+65535;                                                
                        }
                        Speed_num[p] = (float)(60*m1calc*100000)/(m2_data[p]*10000);
                        kalman_height = kalmanFilter(&KFP_height,Speed_num[p]);
//                                                printf("{Speed:%d,%d,%d}\n",(int)(Speed_num[p]+0.5),m1calc,m2_data[p]);
                        printf("{Speed:%d}\n",(int)(kalman_height+0.5));                                        
                }
                TIM_NUM = 0;
                start_flag = 0;
                timer_enable(TIMER0);        
                timer_enable(TIMER2);        
                timer_enable(TIMER14);        
        }
                        
    }

案例中:m1calc为M1,100000为fc,即编码器输出脉冲数。m2_data[p]为M2,即高频脉冲计数。kalmanFilter()函数为卡尔曼滤波器,此处不便公开。

最终效果如下:

在3000RP/M表现基本无差异:
在这里插入图片描述
图八:M/T法卡尔曼滤波速度-3000rp/m
在低速表现优异
在这里插入图片描述
图九:M/T法卡尔曼滤波速度-1000rp/m
在这里插入图片描述
图十:伺服驱动读取速度波动-100rp/m

在这里插入图片描述
图十一:M/T法卡尔曼滤波速度-100rp/m
高速表现同样没问题
在这里插入图片描述
图十二:伺服驱动读取速度波动-5000rp/m
在这里插入图片描述
图十三:M/T法卡尔曼滤波速度-5000rp/m

五、总结

M/T法测速在增量型编码器中还是很常用的测速方式,再结合相应的滤波算法,可以得到非常精准的速度信息,同时MCU的译码器模式可同时获取到电机的旋转方向,可自行探索。上文中的编码器未经校准,精度表现一般,于本次实验结果无关。

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

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

相关文章

厚积薄发,AR光学方案商光舟半导体于AWE 2023首次公开亮相

青亭网6月2日报道&#xff0c;国内AR光波导技术厂商“光舟半导体”&#xff08;深圳市光舟半导体技术有限公司&#xff09;在AWE 2023首次公开亮相&#xff0c;并展示了最新的AR光波导模组、光机/光引擎、汽车HUD三大产品。 光舟半导体成立于2020年1月&#xff0c;公司由AR光学…

5.8 几个常见JavaScript图表库

几个常见JavaScript图表库 目录1、 Chart.js2、 Chartist.js3、 Highcharts.js4、 D3.js5、 Plotly.js6、 ECharts.js7、 Google Charts8、Other Charts 目录 1、 Chart.js 官方网站&#xff1a; www.chartjs.org Chart.js 是一个基于 HTML5 Canvas 的 JavaScript 图表库&…

Day59【单调栈】503.下一个更大元素II、42.接雨水

503.下一个更大元素II 力扣题目链接/文章讲解 视频讲解 本题和739.每日温度很相似&#xff0c;只不过是循环数组 一种处理循环的方式是&#xff0c;直接把两个数组拼接在一起&#xff0c;然后使用单调栈求下一个最大值 class Solution { public:vector<int> nextGre…

【JUC基础】13. 线程池(二)

目录 1、前言 2、Java实现线程池 2.1、Executors框架 2.2、newFixedThreadPool 2.3、newCachedThreadPool 2.4、newSingleThreadExecutor 2.5、newScheduledThreadPool 2.5.1、scheduleAtFixedRate 2.5.2、scheduleWithFixedDelay 2.5.3、异常中断 3、execute()和sub…

大数据挖掘企业服务平台(TipDM大数据挖掘建模平台)-快速构建数据挖掘工程

“TipDM大数据挖掘建模平台”&#xff08;以下简称平台&#xff09;是由广东泰迪智能科技股份有限公司自主研发&#xff0c;基于Python引擎的数据挖掘建模平台。使用平台配置的开箱即用的算法组件&#xff0c;用户可在没有编程基础的情况下&#xff0c;通过拖拽的方式进行操作&…

蓝牙规范系列--经典蓝牙概述(第一篇)

一、目的 从本篇开始介绍经典蓝牙的基础知识&#xff0c;内容较多故会分成多篇进行介绍。 经典蓝牙&#xff08;BR/EBR&#xff09;射频&#xff08;物理层PHY&#xff09;工作在免授权的2.4G ISM频段&#xff08;2400 - 2483.5 MHz&#xff09;&#xff0c;使用跳频技术来对抗…

基于java的班级管理系统的设计与实现

一:需求分析 1.功能需求 1).能够实现对班级学生基本资料的录入,包括学生的学号,姓名,性别,所学专业,家庭住址以及出生年月等。 2).能够实现对学生基本资料的修改。 3).根据学号对学生资料进行查询。 4).能够删除某些学生的资料。 二:总体设计 本班级管理系统共有6…

静态Web服务器搭建

文章目录 一&#xff0c;安装Apache软件&#xff08;一&#xff09;Apache软件安装&#xff08;二&#xff09;Apache软件管理&#xff08;三&#xff09;Apache软件基本设置&#xff08;四&#xff09;测试Apache服务器 二&#xff0c;Apache服务器的配置文件&#xff08;一&a…

【C++】结构体 - 定义和使用,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,结构体 const

文章目录 1. 定义和使用2. 结构体数组3. 结构体指针4. 结构体嵌套结构体5. 结构体做函数参数6. 结构体 const 1. 定义和使用 结构体属于用户自定义的数据类型&#xff0c;允许用户存储不同的数据类型。 struct 结构体 {结构体成员列表}; 通过结构体创建变量的方法有三种&…

2023,智能硬件的AIGC“又一春”

​ 文|智能相对论 作者|佘凯文 消费电子产品风光不再&#xff0c;特别是自去年以来&#xff0c;电子消费市场经历了一整年的寒潮袭击&#xff0c;智能手机等产品达到10年消费谷底&#xff0c;PC出货量整体下降16%&#xff0c;不仅如此&#xff0c;包括平板、可穿戴设备也一改…

URP Shader FrameBuffer Fetch Mali Crash

1&#xff09;URP Shader FrameBuffer Fetch Mali Crash ​2&#xff09;Unity模型Lightmap UV相关的疑问 3&#xff09;动画上下半身融合问题 4&#xff09;AnimatorControllerPlayable.PrepareFrame函数在什么情况下调用 这是第338篇UWA技术知识分享的推送&#xff0c;精选了…

如何将 HTML 字符串转换成 DOM 对象:用 DOMParser

如何将 HTML 字符串转换成 DOM 对象&#xff1a;用 DOMParser 一、问题描述 有的时候我们需要处理一些 HTML 字符串&#xff0c;比如我需要从下方 HTML 字符串中提取每个 <a> 标签的内容和属性。 <pre><a href"cc1245.jpg">cc1245.jpg</a>…

将ipa文件上架苹果应用商店详细教程

使用windows电脑打包好uniapp的ios应用之后&#xff0c;还有一个麻烦事&#xff0c;就是需要将这个打包好的ipa格式的文件&#xff0c;上架到苹果的应用商店。用户才能安装。 而苹果提供的上传工具&#xff0c;比如xcode或transports&#xff0c;只能安装在mac电脑&#xff0c…

国产AIGC大模型汇总

“ 随着ChatGPT和GPT-4的出现&#xff0c;直接引爆了全球的AIGC大模型市场&#xff01;为了赶上这一波热潮&#xff0c;国内的大厂和创业公司也纷纷内卷起来&#xff0c;相继发布了自己的大模型。但是到目前为止&#xff0c;没有一个大模型能与ChatGPT相提并论&#xff0c;更比…

【算法系列之二叉树III】leetcode236. 二叉树的最近公共祖先

617.合并二叉树 力扣题目链接 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新二叉树。合并的规则是&…

MySQL 三万字精华总结 + 面试100 问,和面试官扯皮绰绰有余

MySQL 三万字精华总结 面试100 问&#xff0c;和面试官扯皮绰绰有余 写在之前&#xff1a;不建议那种上来就是各种面试题罗列&#xff0c;然后背书式的去记忆&#xff0c;对技术的提升帮助很小&#xff0c;对正经面试也没什么帮助&#xff0c;有点东西的面试官深挖下就懵逼了。…

linuxOPS基础_linux文本文件查看

vi/vim vim文档编辑操作太多了,可以看这篇单独介绍vim的文章>https://blog.csdn.net/weixin_44368963/article/details/130963920 cat查看文件 命令&#xff1a;cat 作用&#xff1a;查看文件内容 语法&#xff1a;#cat 文件名称 ​ #cat 文件1 文件2 > 文件3 **特别注…

如何利用CiteSpace快速锁定领域内最新研究热点并制作精美的可视化专题图?

【基于Citespace和vosviewer文献计量学相关论文 】 ​ 01 文献计量学方法与应用 1. 文献计量学方法基本介绍 2. 与其他综述方法区别联系 3. 各学科领域应用趋势近况 4. 主流分析软件优缺点对比 5. 经典高分10SCI思路复盘 6. 软件安装与Java环境配置 02 主题确定、数据检…

Vue+springboot校园跳蚤二手市场管理系统

摘 要 本毕业设计的内容是设计并且实现一个基于Springboot框架的校园跳蚤市场管理系统。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;Tomcat网络信息服务作为应用服务器。校园跳蚤市场管理系统的功能已基本实现&#xff0c;主要包括用户、卖家、商品分类…

中国存储竞争新格局:曙光掌舵分布式存储市场

近日&#xff0c;赛迪顾问发布了《中国分布式存储市场研究报告&#xff08;2023&#xff09;》。 作为数字经济的底座&#xff0c;数据存储的重要性日益凸显。 近年来&#xff0c;凭借高性能、高可靠性、高可扩展性等优势&#xff0c;基于分布式架构的分布式存储迎来了蓬勃发…