基于正点原子电机实验的pid调试助手代码解析(速度环控制)

news2024/11/24 5:49:51

这里写目录标题

  • 下位机与PID调试助手传输的原理
  • 代码讲解(基于正点原子)
  • 解析数据接受和数据发送的底层函数
    • 数据接受
      • 数据帧格式
      • 环形数组以及怎么找到它的帧头位置
      • crc校验
    • 数据发送
      • 数据上传函数

通过前两节文章,我已经了解了基本的pid算法,现在在完成了电机编码测速,pid控制电机转速的前提,我们来解析一下下位机是如何pid调试助手进行数据传递的.

下位机与PID调试助手传输的原理

首先用c#写一个PID调试助手,然后拟定好传递数据的通信协议,然后下位机配置好串口,下位机使用串口发送指令给上位机解析(按照通信协议),上位机发送数据,下位通过串口接受到上位机传来的指令,进行解析。

代码讲解(基于正点原子)

正点原子的代码使F4系列的,我修改成了stm32F103ZET6的板子代码,
现在我们先来讲一讲函数怎么调用.

首先看到主函数里的程序。

/**
 ****************************************************************************************************
 * @file        main.c
 * @author      正点原子团队(ALIENTEK)
 * @version     V1.0
 * @date        2021-10-16
 * @brief       直流有刷电机速度环PID控制 实验
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:正点原子 F407电机开发板
 * 在线视频:www.yuanzige.com
 * 技术论坛:www.openedv.com/forum.php
 * 公司网址:www.alientek.com
 * 购买地址:openedv.taobao.com
 *
 ****************************************************************************************************
 */

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/DC_MOTOR/dc_motor.h"
#include "./BSP/ADC/adc.h"
#include "./BSP/TIMER/dcmotor_tim.h"
#include "./BSP/PID/pid.h"
#include "./DEBUG/debug.h"

extern uint8_t  g_run_flag;

void lcd_dis(void);

int main(void)
{
    uint8_t key;
    uint16_t t;
    uint8_t debug_cmd = 0;

    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口1初始化,用于上位机调试 */
    led_init();                             /* 初始化LED */
    lcd_init();                             /* 初始化LCD */
    key_init();                             /* 初始化按键 */
    pid_init();                             /* 初始化PID参数 */
    atim_timx_cplm_pwm_init(8400 - 1 , 0);  /* 168 000 000 / 1 = 168 000 000 168Mhz的计数频率,计数8400次为50us */
    dcmotor_init();                         /* 初始化电机 */
    gtim_timx_encoder_chy_init(0XFFFF, 0);  /* 编码器定时器初始化,不分频直接84M的计数频率 */
    btim_timx_int_init(1000 - 1 , 84 - 1);  /* 基本定时器初始化,1ms计数周期 */

#if DEBUG_ENABLE                            /* 开启调试 */
    
    debug_init();                           /* 初始化调试 */
    debug_send_motorcode(DC_MOTOR);         /* 上传电机类型(直流有刷电机) */
    debug_send_motorstate(IDLE_STATE);      /* 上传电机状态(空闲) */
    
    /* 同步数据(选择第1组PID,目标速度地址,P,I,D参数)到上位机 */
    debug_send_initdata(TYPE_PID1, (float *)(&g_speed_pid.SetPoint), KP, KI, KD);
    
#endif

    g_point_color = WHITE;
    g_back_color  = BLACK;
    lcd_show_string(10, 10, 200, 16, 16, "DcMotor Test", g_point_color);
    lcd_show_string(10, 30, 200, 16, 16, "KEY0:Start forward", g_point_color);
    lcd_show_string(10, 50, 200, 16, 16, "KEY1:Start backward", g_point_color);
    lcd_show_string(10, 70, 200, 16, 16, "KEY2:Stop", g_point_color);

    while (1)
    {
        key = key_scan(0);                                  /* 按键扫描 */
        if(key == KEY0_PRES)                                /* 当key0按下 */
        {
            g_run_flag = 1;                                 /* 标记电机启动 */
            g_speed_pid.SetPoint += 30;
            if (g_speed_pid.SetPoint == 0)
            {
                dcmotor_stop();                             /* 停止则立刻响应 */
                g_motor_data.motor_pwm = 0;
                motor_pwm_set(g_motor_data.motor_pwm);      /* 设置电机方向、转速 */
            }
            else
            {
                dcmotor_start();                            /* 开启电机 */
                if (g_speed_pid.SetPoint >= 300)            /* 限速 */
                {
                    g_speed_pid.SetPoint = 300;
                }
#if DEBUG_ENABLE
                debug_send_motorstate(RUN_STATE);           /* 上传电机状态(运行) */
#endif
            }
        }

        else if(key == KEY1_PRES)                           /* 当key1按下 */
        {
            g_run_flag = 1;                                 /* 标记电机启动 */
            g_speed_pid.SetPoint -= 30;
            if (g_speed_pid.SetPoint == 0)
            {
                dcmotor_stop();                             /* 停止则立刻响应 */
                g_motor_data.motor_pwm = 0;
                motor_pwm_set(g_motor_data.motor_pwm);      /* 设置电机方向、转速 */
            }
            else
            {
                dcmotor_start();                            /* 开启电机 */
                if (g_speed_pid.SetPoint <= -300)           /* 限速 */
                {
                    g_speed_pid.SetPoint = -300;
                }
#if DEBUG_ENABLE
                debug_send_motorstate(RUN_STATE);           /* 上传电机状态(运行) */
#endif
            }
        }

        else if(key == KEY2_PRES)                           /* 当key2按下 */
        {
            dcmotor_stop();                                 /* 停止电机 */
            pid_init();                                     /* 重置pid参数 */
            g_run_flag = 0;                                 /* 标记电机停止 */
            g_motor_data.motor_pwm = 0;
            motor_pwm_set(g_motor_data.motor_pwm);          /* 设置电机转向、速度 */
            
#if DEBUG_ENABLE
            debug_send_motorstate(BREAKED_STATE);           /* 上传电机状态(刹车) */
            debug_send_initdata(TYPE_PID1, (float *)(&g_speed_pid.SetPoint), KP, KI, KD);           /* 同步数据到上位机 */
#endif
        }

#if DEBUG_ENABLE
         /* 查询接收PID助手的PID参数 */
        debug_receive_pid(TYPE_PID1, (float *)&g_speed_pid.Proportion,(float *)&g_speed_pid.Integral, (float *)&g_speed_pid.Derivative);
        
        debug_set_point_range(300, -300, 300);              /* 设置目标速度范围 */
        debug_cmd = debug_receive_ctrl_code();              /* 读取上位机指令 */

        if (debug_cmd == BREAKED)                           /* 电机刹车 */
        {
            dcmotor_stop();                                 /* 停止电机 */
            pid_init();                                     /* 重置pid参数 */
            g_run_flag = 0;                                 /* 标记电机停止 */
            g_motor_data.motor_pwm = 0;
            motor_pwm_set(g_motor_data.motor_pwm);          /* 设置电机转向、速度 */
            debug_send_motorstate(BREAKED_STATE);           /* 上传电机状态(刹车) */
            debug_send_initdata(TYPE_PID1, (float *)(&g_speed_pid.SetPoint), KP, KI, KD);
        } 
        else if (debug_cmd == RUN_CODE)                     /* 电机运行 */
        {  
            dcmotor_start();                                /* 开启电机 */
            g_speed_pid.SetPoint = 30;                      /* 设置目标速度:30 RPM */
            g_run_flag = 1;                                 /* 标记电机启动 */
            debug_send_motorstate(RUN_STATE);               /* 上传电机状态(运行) */
        }
#endif
        t++;
        if(t % 20 == 0)
        {
            lcd_dis();                                      /* 显示数据 */
            LED0_TOGGLE();                                  /*LED0(红灯) 翻转*/
#if DEBUG_ENABLE
            debug_send_speed(g_motor_data.speed);           /* 发送速度 */
#endif
        }
        delay_ms(10);
    }
}

/**
 * @brief       数据显示函数
 * @param       无
 * @retval      无
 */
void lcd_dis(void)
{
    char buf[32];
    
    /* 显示占空比(正负号为电机方向) */
    sprintf(buf, "PWM_Duty:%.1f%c  ", (float)(g_motor_data.motor_pwm * 100 / 8400), '%');
    lcd_show_string(10, 150, 200, 16, 16, buf, g_point_color);

    /* 设置的目标速度 */
    sprintf(buf, "Set Speed :%3d RPM ", (int16_t)g_speed_pid.SetPoint);
    lcd_show_string(10, 180, 200, 16, 16, buf, g_point_color); 
    /* 电机实际速度 */
    sprintf(buf, "Speed :%3d RPM ", (int16_t)g_motor_data.speed);
    lcd_show_string(10, 210, 200, 16, 16, buf, g_point_color);
}



#if DEBUG_ENABLE                            /* 开启调试 */
    
    debug_init();                           /* 初始化调试 */
    debug_send_motorcode(DC_MOTOR);         /* 上传电机类型(直流有刷电机) */
    debug_send_motorstate(IDLE_STATE);      /* 上传电机状态(空闲) */
    
    /* 同步数据(选择第1组PID,目标速度地址,P,I,D参数)到上位机 */
    debug_send_initdata(TYPE_PID1, (float *)(&g_speed_pid.SetPoint), KP, KI, KD);
    
#endif

首先初始化调试,就是将用来存放参数的结构体变量清零。
接着上传下电机类型
上传电机状态,
把此时Kp,Ki,Kd的值发给pid调试助手。
在这里插入图片描述
上传数据的函数。
这里我只上传了电机的速度,差不多200ms上传一次。
请添加图片描述
发送波形函数
这里我上传实际速度,目标速度,占空比值,每50ms上传一次
这里我上传三个通道,
通道1上传实际速度,
通道2上传目标速度
通道3上传占空比

请添加图片描述

接受上位机发送数据函数

这里设定目标范围,已经设定目标的变化上限。
读取上位机指令。
判断指令是否是刹车,然后执行相关操作。
判断指令是否是运行,然后执行相关操作。
请添加图片描述

在主函数的while循环里一直调用这些函数。请添加图片描述

请添加图片描述

解析数据接受和数据发送的底层函数

数据接受

通过串口来接收上位机发来的数据包(中断接收),只要上位机一发送数据,就会产生串口中断,然后在接受中断回调函数里族逐个字节来接收数据,并解析数据。

请添加图片描述

数据帧格式

请添加图片描述

/**
 * @brief       上位机数据解析
 * @param       *data:接收的数据(地址)
 * @note        利用环形缓冲区来接收数据,再存进相应的结构体成员中
 * @retval      无
 */
void debug_handle(uint8_t *data)
{
    uint8_t temp[DEBUG_REV_MAX_LEN];
    uint8_t i;

    if (debug_rev_p >= DEBUG_REV_MAX_LEN)          /* 超过缓冲区(数组)最大长度 */
    {
        debug_rev_p = 0;                           /* 地址偏移量清零 */
    }

    debug_rev_data[debug_rev_p] = *(data);         /* 取出数据,存进数组 */

    if (*data == DEBUG_DATA_END)                   /* 判断是否收到帧尾 */
    {
        if (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 4) % DEBUG_REV_MAX_LEN] == DEBUG_DATA_HEAD)                        /* 数据包长度为5个字节,判断第一个字节是否为帧头 */
        {
            for (i = 0; i < 2; i++)
            {
                temp[i] = debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 4 + i) % DEBUG_REV_MAX_LEN];                         /* 取出帧头、数据类别,5个字节的数据包没有数据域 */
            }

#if EN_CRC                                         /* 进行CRC校验 */
            if (crc16_calc(temp, 2) == ((debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 4 + 2) % DEBUG_REV_MAX_LEN] << 8) | \
                                         debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 4 + 3) % DEBUG_REV_MAX_LEN]))
#endif
            {
                if (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 4 + 1) % DEBUG_REV_MAX_LEN] == CMD_GET_ALL_DATA)           /* 判断数据类别是否为:获取全部参数 */
                {
                    debug_upload_data(&g_debug, TYPE_STATUS);                                                                    /* 发送电机状态 */
                    debug_upload_data(&g_debug, TYPE_SPEED);                                                                     /* 发送速度值 */
                    debug_upload_data(&g_debug, TYPE_HAL_ENC);                                                                   /* 发送霍尔、编码器位置 */
                    debug_upload_data(&g_debug, TYPE_VBUS);                                                                      /* 发送电压 */
                    debug_upload_data(&g_debug, TYPE_AMP);                                                                       /* 发送电流 */
                    debug_upload_data(&g_debug, TYPE_TEMP);                                                                      /* 发送温度 */
                    debug_upload_data(&g_debug, TYPE_SUM_LEN);                                                                   /* 发送总里程 */
                    debug_upload_data(&g_debug, TYPE_BEM);                                                                       /* 发送反电动势 */
                    debug_upload_data(&g_debug, TYPE_MOTOR_CODE);                                                                /* 发送电机类型 */
                    for (i = TYPE_PID1; i < TYPE_PID10; i++)
                    {
                        debug_upload_data(&g_debug, i);                                                                          /* 发送PID参数 */
                    }
                }
            }
        }

        if (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 5) % DEBUG_REV_MAX_LEN] == DEBUG_DATA_HEAD)                        /* 数据包长度为6个字节,判断第一个字节是否为帧头 */
        {
            for (i = 0; i < 3; i++)
            {
                temp[i] = debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 5 + i) % DEBUG_REV_MAX_LEN];                         /* 取出帧头、数据类别、数据域 */
            }
#if EN_CRC                                         /* 进行CRC校验 */
            if (crc16_calc(temp, 3) == ((debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 5 + 3) % DEBUG_REV_MAX_LEN] << 8) | \
                                         debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 5 + 4) % DEBUG_REV_MAX_LEN]))
#endif
            {
                switch (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 5 + 1) % DEBUG_REV_MAX_LEN])                           /* 判断数据类别 */
                {
                    case CMD_SET_CTR_CODE:                                                                                       /* 下发控制指令 */
                        debug_rev.Ctrl_code = debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 5 + 2) % DEBUG_REV_MAX_LEN];
                        break;

                    case CMD_SET_CTR_MODE:                                                                                       /* 下发控制模式 */
                        debug_rev.Ctrl_mode = debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 5 + 2) % DEBUG_REV_MAX_LEN];
                        break;
                }
            }
        }

        if (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6) % DEBUG_REV_MAX_LEN] == DEBUG_DATA_HEAD)                        /* 数据包长度为7个字节,判断第一个字节是否为帧头 */
        {
            for (i = 0; i < 4; i++)
            {
                temp[i] = debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + i) % DEBUG_REV_MAX_LEN];                         /* 取出帧头、数据类别、数据域 */
            }
#if EN_CRC                                         /* 进行CRC校验 */
            if (crc16_calc(temp, 4) == ((debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + 4) % DEBUG_REV_MAX_LEN] << 8) | \
                                         debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + 5) % DEBUG_REV_MAX_LEN]))
#endif
            {
                switch (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + 1) % DEBUG_REV_MAX_LEN])                           /* 判断数据类别 */
                {
                    case CMD_SET_SPEED:                                                                                          /* 设定电机速度 */
                        *(debug_rev.speed) = (int16_t)((debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + 2) % DEBUG_REV_MAX_LEN] << 8) | \
                                                        debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + 3) % DEBUG_REV_MAX_LEN]);
                        break;

                    case CMD_SET_TORQUE:                                                                                         /* 设定转矩 */
                        *(debug_rev.torque) = (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + 2) % DEBUG_REV_MAX_LEN] << 8) | \
                                               debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 6 + 3) % DEBUG_REV_MAX_LEN];
                        break;
                }
            }
        }

        if (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 16) % DEBUG_REV_MAX_LEN] == DEBUG_DATA_HEAD)                       /* 数据包长度为17个字节,判断第一个字节是否为帧头 */
        {
            for (i = 0; i < 14; i++)
            {
                temp[i] = debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 16 + i) % DEBUG_REV_MAX_LEN];                        /* 取出帧头、数据类别、数据域 */
            }
#if EN_CRC                                          /* 进行CRC校验 */
            if (crc16_calc(temp, 14) == ((debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 16 + 14) % DEBUG_REV_MAX_LEN] << 8) | \
                                          debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 16 + 15) % DEBUG_REV_MAX_LEN]))
#endif
            {
                switch (debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 16 + 1) % DEBUG_REV_MAX_LEN])                          /* 判断数据类别 */
                {
                    case CMD_SET_PID1:
                    case CMD_SET_PID2:
                    case CMD_SET_PID3:
                    case CMD_SET_PID4:
                    case CMD_SET_PID5:
                    case CMD_SET_PID6:
                    case CMD_SET_PID7:
                    case CMD_SET_PID8:
                    case CMD_SET_PID9:
                    case CMD_SET_PID10:
                    for (i = 0; i < 12; i++)                                                                                     /* 接收设定的PID参数 */
                    {
                        g_debug.pid[debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 16 + 1) % DEBUG_REV_MAX_LEN] - CMD_SET_PID1].pid.pidi8[i] = \
                                    debug_rev_data[(debug_rev_p + DEBUG_REV_MAX_LEN - 16 + 2 + i) % DEBUG_REV_MAX_LEN];
                    }
                    break;
                }
            }
        }
    }

    debug_rev_p ++;
}

首先先来说一下上位机的解析函数
上位机数据的解析函数是用环形数组的形式来存放,这样不会产生内存碎片,通过串口不断接受数组,然后把接受到的数组按照环形的顺序赋予给数值,并判断是否是帧尾,如果是帧尾,则接着判断是几字节的包,并且首字节是否是帧头,这里四种四节的包,分别是5字节,6字节,7字节,17字节。所以可以看到代码里进行了四个if语句判断分别是什么字节,并且首字节是否为帧头,然后分别进行crc校验,如果校验正确,则把表示数组类别和数据域的包给取出来,然后根据通信协议,将参数存放结构体变量进行对应赋值。

四个if语句
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

环形数组以及怎么找到它的帧头位置

例如数组大小为17的数组,当它的地址偏移量到达17的时候,则重新从0开始赋值。
在这里插入图片描述
我们现在已经知道帧尾的位置了,接下来如何找到帧头的位置呢?
这就和上面的四个if语句对应上了,例如我们要数据包长度为5的帧头,
我们就可以用((当前的地址偏移量+数组长度-(数据包长度-1))% 数组长度)来找到帧头的位置。

在这里插入图片描述

crc校验

本例程数据通过crc校验进行验证。
CRC定义:
 CRC即循环冗余校验码,是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
 取出帧头、数据类别、数据域进行crc校验
 代码:
请添加图片描述

数据发送

数据上传函数

构建数组包:
先定义一个数组 upload_data[37],为什么是37呢,因为最长的数据包是37byte。
判断数据类别并进行指令存储:
根据upload_type的值来确定数据类别,然后进行把数据域值赋给upload_data数组。
请添加图片描述
判断是否未数据类别错误,如果不是,则进行crc校验,然后把指令发送给pid调试助手。
请添加图片描述

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

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

相关文章

MatrixGate 5.0 性能再升级,加载速度提升三倍!

前言 数据的加载速度在数据处理和分析领域一直是一个挑战&#xff0c;为应对这一挑战&#xff0c;YMatrix 数据库开发了 mxgate 高速数据加载工具。最近&#xff0c;随着 YMatrix 5.0 的 GA&#xff0c;新版 mxgate 与上一版本&#xff08;4.8.1&#xff09;相比&#xff0c;速…

Linux 虚拟机 磁盘扩容

概述 在单台虚拟机上部署了过多服务&#xff0c;导致磁盘使用过度达到98%。 现在扩容提高磁盘容量&#xff0c;增加10G。 现象 df -h df -ih du -s具体步骤 VMware 扩容 关闭虚拟机的情况下执行&#xff0c;类似于生产环境下需要关闭服务器&#xff0c;从而添加硬盘等相关操作…

Liunx基础命令 - rm删除命令

rm命令 – 删除文件或目录 rm命令来自英文单词“remove”的缩写&#xff0c;中文译为“消除”&#xff0c;其功能是用于删除文件或目录&#xff0c;一次可以删除多个文件&#xff0c;或递归删除目录及其内的所有子文件。 rm也是一个很危险的命令&#xff0c;使用的时候要特别…

从BinDiff到0day 在IE中利用CVE-2019-1208

前言 如上所述&#xff0c;CVE-2019-1208是UAF漏洞&#xff0c;这类安全漏洞可以破坏有效数据、引发进程crash、并且可以精心利用最终导致任意代码执行。而对于本文介绍的CVE-2019-1208而言&#xff0c;成功利用此漏洞的攻击者可以获得系统当前用户权限。如果当前用户具有admi…

理解Java虚拟机——JVM

目录 一、初识JVM 二、JVM执行流程 三、内存区域划分&#xff08;JVM运行时数据区&#xff09; 3.1 本地方法栈&#xff08;线程私有&#xff09; 3.2 程序计数器&#xff08;线程私有&#xff0c;无并发问题&#xff09; 3.3 JVM虚拟机栈&#xff08;线程私有&#xff0…

最全的分布式事务详情,它来啰~

我们首先得理解什么是分布式事务呢&#xff1f;分布式事务是指在分布式系统中&#xff0c;涉及多个计算机或服务器的操作序列&#xff0c;这些操作需要满足一致性和可靠性的要求。每个操作要么全部成功执行&#xff0c;要么全部回滚&#xff0c;以保持数据的一致性和完整性。 …

嵌入式通信协议【Modbus】Modbus TCP的帧格式

一、请求帧格式 Client request:例&#xff1a; 19 B2 00 00 00 06 06 03 00 27 00 02 1、头字节 上面是modbus客户端发出的报文内容&#xff0c;为modbus tcp/ip协议格式&#xff0c;其前面的六个字节为头字节( header handle)&#xff1b; 19 B2 00 00 00 06 19 B2 00 00…

3D点云之语义分割(相关官方示例介绍)

之前在博客中提到&#xff0c;会考虑用深度学习来对3D点云进行处理&#xff0c;接下来迈出脚步&#xff0c;先整几个例子来熟悉它。例子原型来源于官网&#xff0c;博主在其基础上做了一些代码修改。 一. 例子参考 1. Keras中的资源 Code examples 2.openvinotoolkit open_…

20230515在亚马逊Amazon扣费之后的申诉

20230515在亚马逊Amazon扣费之后的申诉 2023/5/15 22:56 缘起&#xff1a;使用Amazon的12个月的免费存储桶&#xff0c;然后调用S3功能翻译&#xff01; 但是&#xff0c;被扣费了&#xff01; 由于绑定的信用卡是工行的&#xff0c;要求和亚马逊解除绑定&#xff0c;工行给了一…

第十章创建模式—外观模式

文章目录 外观模式解决的问题概念结构 案例优缺点使用场景Tomcat 源码 结构型模式描述如何将类或对象按某种布局组成更大的结构&#xff0c;有以下两种&#xff1a; 类结构型模式&#xff1a;采用继承机制来组织接口和类。对象结构型模式&#xff1a;釆用组合或聚合来组合对象。…

GEE:无监督聚类算法(wekaKMeans)

作者:CSDN @ _养乐多_ 本文将介绍如何使用 Google Earth Engine(GEE)进行卫星图像 K-means 聚类分析的基本步骤,并提供相应的示例代码。 结果如下图所示, 文章目录 一、K-means 原理二、代码详解三、代码链接一、K-means 原理 二、代码详解 var roi = table; Map.cent…

【前后端异常】axios post请求 返回415错误状态码的解决方法

错误描述&#xff1a; 进行有文件的表单提交时出现415错误&#xff0c;以前没遇到过记录一下 415错误的解释是说&#xff0c;服务器无法处理请求附带的媒体格式。以下是HTTP的状态码关于415返回码的说明&#xff1a; 415Unsupported Media Type服务器无法处理请求附带的媒体格…

awk的应用

awk的基本应用 一、awk的工作原理二、实际应用按行输出文本按字段输出文本时间的使用查看各个功能 一、awk的工作原理 awk与sed和grep共成为文本三剑客&#xff0c;都是针对文本非常实用的工具。 awk的工作原理&#xff1a; 遂行读取文本&#xff0c;默认以空格或者tab&#x…

NodeJs模块化之上半部分

模块化 什么是模块化 模块化是指解决一个复杂问题时&#xff0c;自顶向下 逐层把系统 拆解 成若干 模块 的过程。 对于整个系统来说&#xff0c;模块是可组合、分解和更换的单元。 编程领域中的模块化 编程领域中的模块化&#xff0c;就是遵守固定的规则&#xff0c;把一个…

( 位运算 ) 231. 2 的幂 / 342. 4的幂 ——【Leetcode每日一题】

❓题目一 231. 2 的幂 难度&#xff1a;简单 给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2 x n 2^x n2x &#xff0c;则认为 n 是 2 的幂次方。 …

10款必装IDEA开发神器

那些IDEA开发神器 1.Material Theme Ul 安装步骤 打开IDEA,点击File -> Settings。在Settings窗口中&#xff0c;选择Appearance & Behavior -> Appearance。在Appearance选项卡下&#xff0c;找到Theme并选择Material Theme Ul。点击Apply按钮&#xff0c;然后点…

Liunx基础命令 - mv剪切命令

mv命令 – 移动或改名文件 mv命令来自英文单词”move“的缩写&#xff0c;中文译为”移动“&#xff0c;其功能与英文含义相同&#xff0c;能够用于对文件进行剪切和重命名操作。这是一个被高频使用的文件管理命令&#xff0c;我们需要留意它与复制命令的区别。cp命令是用于文…

C++标准库中的锁

C标准库中的锁 std::mutex.lock是我们在C中比较常见的锁&#xff0c;我们使用std::mutex.lock方法时&#xff0c;同时需要考虑何时使用std:mutex.unlock方法去解锁。如果在复杂的多线程情况下&#xff0c;加锁、解锁的时机很难把握&#xff0c;也不好实现。 RAII原则是所有的…

c++中this指针的使用

首先&#xff0c;我们都知道类的成员函数可以访问类的数据&#xff0c;那么成员函数如何知道哪个对象的数据成员要被操作呢&#xff1f; 原因在于每个对象都拥有一个指针&#xff1a;this指针&#xff0c;每一个对象都能通过this指针来访问自己的地址。 this 指针是所有成员函…

NOSQL——redis的安装,配置与简单操作

1.缓存的相关知识 1.1 缓存的概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且硬…