04-单片机商业项目编程,从零搭建低功耗系统设计

一、本文内容

    上一节《03-单片机商业项目编程,从零搭建低功耗系统设计-CSDN博客》我们确定了设计思路,并如何更有效的保持低功耗,这节我们就准备来做软件框架设计。在AI飞速发展的时代,我们也会利AI来辅助我们完成,让自己也能慢慢学会和AI的合作。

二、软件设计思路

    对于软件代码编写,最重要的还是框架搭建,并不是要一开始就把所有代码一五一十的写出来,而是框架搭建好了,再去填充自己需要的内容,比如单片机的底层驱动之类的;我们软件设计第一步先确定方案构思,第二步开始搭建软件框架,第三步移植到指定芯片。

    当前AI发展十分迅速,很多算法类的代码,都可以不需要自己去编写,而我们更多的是发现问题,修正问题,这样既能够让AI给我们提供思路,又能充分发现AI的思路错误,来理顺自己的思路。我们今天利用Chat GPT来帮我们以STM32F030C8T6构建基础软件框架设计,我们不要求他能够直接能运行,但是要保障他的代码逻辑没有漏洞。这也是我们作为程序员和AI协同的提前练习;当然你也可以选择国内其它AI工具来实现。

三、软件编写过程

    我:能帮我基于STM32F030C8T6,利用systick做一个基于事件触发的低功耗伪时间片调度算法吗?

    GPT:当然可以。以下是一个基于STM32F030C8T6的低功耗伪时间片调度算法的示例。这个例子使用了systick定时器来触发事件。

#include "stm32f0xx.h"

// 定义任务结构体
typedef struct {
    void (*taskFunc)(void); // 任务函数指针
    uint32_t delay; // 任务延迟
    uint32_t period; // 任务周期
    uint8_t run; // 运行标志
} sTask;

#define SCH_MAX_TASKS 5 // 最大任务数

sTask SCH_tasks_G[SCH_MAX_TASKS]; // 任务数组

void SysTick_Handler(void) {
    uint32_t index;
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        if ((SCH_tasks_G[index].taskFunc != 0) && (SCH_tasks_G[index].delay == 0)) {
            SCH_tasks_G[index].run = 1; // 标记任务为可运行
            if (SCH_tasks_G[index].period) {
                SCH_tasks_G[index].delay = SCH_tasks_G[index].period; // 重置任务延迟
            }
        }
        if (SCH_tasks_G[index].delay > 0) {
            SCH_tasks_G[index].delay--; // 任务延迟递减
        }
    }
}

void SCH_Dispatch_Tasks(void) {
    uint32_t index;
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        if (SCH_tasks_G[index].run == 1) {
            (*SCH_tasks_G[index].taskFunc)(); // 运行任务
            SCH_tasks_G[index].run = 0; // 清除运行标志
        }
    }
}

void SCH_Add_Task(void (*fPtr)(void), const uint32_t DELAY, const uint32_t PERIOD) {
    uint32_t Index = 0;
    while ((SCH_tasks_G[Index].taskFunc != 0) && (Index < SCH_MAX_TASKS)) {
        Index++;
    }
    if (Index < SCH_MAX_TASKS) {
        SCH_tasks_G[Index].taskFunc = fPtr;
        SCH_tasks_G[Index].delay = DELAY;
        SCH_tasks_G[Index].period = PERIOD;
        SCH_tasks_G[Index].run = 0;
    }
}

int main(void) {
    // 初始化systick定时器
    SysTick_Config(SystemCoreClock / 1000);
    // 添加任务
    SCH_Add_Task(Task1, 0, 1000);
    SCH_Add_Task(Task2, 0, 2000);
    // 主循环
    while (1) {
        SCH_Dispatch_Tasks();
        __WFI(); // 进入低功耗模式
    }
}

    这个例子中,我们定义了一个任务结构体,包含了任务函数指针、任务延迟、任务周期和运行标志。在systick定时器中断服务程序中,我们检查每个任务,如果任务延迟为0,我们就标记任务为可运行,并重置任务延迟。在主循环中,我们检查每个任务,如果任务被标记为可运行,我们就运行任务,并清除运行标志。

    这个例子只是一个基本的框架,你可能需要根据你的具体需求进行修改。希望这个例子能对你有所帮助!

    我:低功耗对我非常重要,你写的代码需要每1ms就要进行一次中断判断,实际还是做了很多无效判断,我希望是动态的调整systick的定时任务,计算出下一任务还需要多长时间执行,就设定多长时间的中断,这样保障系统尽可能的在休眠状态,提高执行效率

    GPT:我理解你的需求,你希望系统能够动态地调整systick的定时任务,以便在尽可能多的时间里让系统处于休眠状态,从而提高执行效率。以下是一个修改后的示例,它会动态地调整systick的定时任务:

#include "stm32f0xx.h"

typedef struct {
    void (*taskFunc)(void);
    uint32_t delay;
    uint32_t period;
    uint8_t run;
} sTask;

#define SCH_MAX_TASKS 5

sTask SCH_tasks_G[SCH_MAX_TASKS];

void SysTick_Handler(void) {
    uint32_t index;
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        if ((SCH_tasks_G[index].taskFunc != 0) && (SCH_tasks_G[index].delay == 0)) {
            SCH_tasks_G[index].run = 1;
            if (SCH_tasks_G[index].period) {
                SCH_tasks_G[index].delay = SCH_tasks_G[index].period;
            }
        }
        if (SCH_tasks_G[index].delay > 0) {
            SCH_tasks_G[index].delay--;
        }
    }
    // 重新计算下一次中断的时间
    uint32_t nextInterrupt = UINT32_MAX;
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        if (SCH_tasks_G[index].taskFunc != 0 && SCH_tasks_G[index].delay < nextInterrupt) {
            nextInterrupt = SCH_tasks_G[index].delay;
        }
    }
    // 设置下一次中断的时间
    SysTick->LOAD = nextInterrupt;
    SysTick->VAL = 0;
}

void SCH_Dispatch_Tasks(void) {
    uint32_t index;
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        if (SCH_tasks_G[index].run == 1) {
            (*SCH_tasks_G[index].taskFunc)();
            SCH_tasks_G[index].run = 0;
        }
    }
}

void SCH_Add_Task(void (*fPtr)(void), const uint32_t DELAY, const uint32_t PERIOD) {
    uint32_t Index = 0;
    while ((SCH_tasks_G[Index].taskFunc != 0) && (Index < SCH_MAX_TASKS)) {
        Index++;
    }
    if (Index < SCH_MAX_TASKS) {
        SCH_tasks_G[Index].taskFunc = fPtr;
        SCH_tasks_G[Index].delay = DELAY;
        SCH_tasks_G[Index].period = PERIOD;
        SCH_tasks_G[Index].run = 0;
    }
}

int main(void) {
    SysTick_Config(SystemCoreClock / 1000);
    SCH_Add_Task(Task1, 0, 1000);
    SCH_Add_Task(Task2, 0, 2000);
    while (1) {
        SCH_Dispatch_Tasks();
        __WFI();
    }
}

    在这个修改后的示例中,我在SysTick_Handler函数中添加了一段代码,用于计算下一次中断的时间,并设置systick定时器的加载值。这样,systick定时器的中断时间就会根据任务的需求动态地进行调整,从而使系统能够在尽可能多的时间里处于休眠状态,提高执行效率。希望这个修改后的示例能满足你的需求!

    我:SysTick_Handler()函数能够把每句话加上注释吗,有点看不懂

    GPT:当然可以,我会为SysTick_Handler()函数中的每一行代码添加注释,以帮助你理解。以下是带有注释的代码:

void SysTick_Handler(void) {
    uint32_t index;
    // 遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空且任务延迟为0
        if ((SCH_tasks_G[index].taskFunc != 0) && (SCH_tasks_G[index].delay == 0)) {
            // 标记任务为可运行
            SCH_tasks_G[index].run = 1;
            // 如果任务周期不为0,则重置任务延迟
            if (SCH_tasks_G[index].period) {
                SCH_tasks_G[index].delay = SCH_tasks_G[index].period;
            }
        }
        // 如果任务延迟大于0,则递减任务延迟
        if (SCH_tasks_G[index].delay > 0) {
            SCH_tasks_G[index].delay--;
        }
    }
    // 初始化下一次中断的时间为最大值
    uint32_t nextInterrupt = UINT32_MAX;
    // 再次遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空且任务延迟小于下一次中断的时间
        if (SCH_tasks_G[index].taskFunc != 0 && SCH_tasks_G[index].delay < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = SCH_tasks_G[index].delay;
        }
    }
    // 设置systick定时器的加载值为下一次中断的时间
    SysTick->LOAD = nextInterrupt;
    // 清零systick定时器的当前值
    SysTick->VAL = 0;
}

    这段代码的主要目的是在每次systick定时器中断时更新任务的状态,并计算下一次中断的时间。希望这些注释能帮助你理解这段代码!

    我:我发现这段代码由逻辑错误,因为我们的下一次中断是按最短延时来计算,也就是可能是不定值的毫秒数,而代码这里却每次只是减1操作:// 如果任务延迟大于0,则递减任务延迟

if (SCH_tasks_G[index].delay > 0) {
SCH_tasks_G[index].delay--;
}

    GPT:你的观察非常准确,这确实是一个问题。在这个代码中,我们假设每次中断的时间间隔是1ms,所以每次中断我们只减少1ms的延迟。但是,如果我们动态地改变中断的时间间隔,那么我们就需要减少相应的延迟时间,而不是只减少1ms。我们可以通过修改SysTick_Handler函数来解决这个问题。具体来说,我们可以在计算出下一次中断的时间后,将这个时间间隔从所有任务的延迟中减去。以下是修改后的代码:

void SysTick_Handler(void) {
    uint32_t index;
    // 初始化下一次中断的时间为最大值
    uint32_t nextInterrupt = UINT32_MAX;
    // 遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空且任务延迟小于下一次中断的时间
        if (SCH_tasks_G[index].taskFunc != 0 && SCH_tasks_G[index].delay < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = SCH_tasks_G[index].delay;
        }
    }
    // 遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空
        if (SCH_tasks_G[index].taskFunc != 0) {
            // 如果任务延迟大于下一次中断的时间
            if (SCH_tasks_G[index].delay >= nextInterrupt) {
                // 减去下一次中断的时间
                SCH_tasks_G[index].delay -= nextInterrupt;
                // 如果任务延迟为0
                if (SCH_tasks_G[index].delay == 0) {
                    // 标记任务为可运行
                    SCH_tasks_G[index].run = 1;
                    // 如果任务周期不为0,则重置任务延迟
                    if (SCH_tasks_G[index].period) {
                        SCH_tasks_G[index].delay = SCH_tasks_G[index].period;
                    }
                }
            }
        }
    }
    // 设置systick定时器的加载值为下一次中断的时间
    SysTick->LOAD = nextInterrupt;
    // 清零systick定时器的当前值
    SysTick->VAL = 0;
}

    希望这个修改后的代码能满足你的需求!

    我:感觉还是有点问题,nextInterrupt在前面就获得值了,然后再去遍历任务数组,那不等于本次调用SysTick_Handler中断处理函数,就立即对应的任务标记可运行了?应该是中断下一轮,才到吧,实际上nextInterrupt要定义成static变量,然后更新其值要放后面吧

    GPT:你的观察非常准确,我理解你的担忧。确实,我们应该在下一轮中断时才标记任务为可运行。为了解决这个问题,我们可以将nextInterrupt定义为静态变量,并在每次中断时使用它来更新任务的延迟,然后在中断的最后更新nextInterrupt的值。以下是修改后的代码:

void SysTick_Handler(void) {
    static uint32_t nextInterrupt = 1; // 初始化为1,以便在第一次中断时运行
    uint32_t index;
    // 遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空
        if (SCH_tasks_G[index].taskFunc != 0) {
            // 如果任务延迟大于等于下一次中断的时间
            if (SCH_tasks_G[index].delay >= nextInterrupt) {
                // 减去下一次中断的时间
                SCH_tasks_G[index].delay -= nextInterrupt;
                // 如果任务延迟为0
                if (SCH_tasks_G[index].delay == 0) {
                    // 标记任务为可运行
                    SCH_tasks_G[index].run = 1;
                    // 如果任务周期不为0,则重置任务延迟
                    if (SCH_tasks_G[index].period) {
                        SCH_tasks_G[index].delay = SCH_tasks_G[index].period;
                    }
                }
            }
        }
    }
    // 初始化下一次中断的时间为最大值
    nextInterrupt = UINT32_MAX;
    // 再次遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空且任务延迟小于下一次中断的时间
        if (SCH_tasks_G[index].taskFunc != 0 && SCH_tasks_G[index].delay < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = SCH_tasks_G[index].delay;
        }
    }
    // 设置systick定时器的加载值为下一次中断的时间
    SysTick->LOAD = nextInterrupt;
    // 清零systick定时器的当前值
    SysTick->VAL = 0;
}

    这样,我们就可以确保只有在下一轮中断时才会标记任务为可运行。希望这个修改后的代码能满足你的需求!

    我:嗯,太好了,这个中断处理函数目前没问题了,那么对应的SCH_Add_Task函数也要做一些修正,新增了任务后,对应的nextInterrupt参数和SysTick也要更新

    GPT:你的观察非常准确,当我们添加新的任务时,我们确实需要更新nextInterrupt和systick定时器。以下是修改后的SCH_Add_Task函数:

void SCH_Add_Task(void (*fPtr)(void), const uint32_t DELAY, const uint32_t PERIOD) {
    uint32_t Index = 0;
    // 寻找任务数组中的空闲位置
    while ((SCH_tasks_G[Index].taskFunc != 0) && (Index < SCH_MAX_TASKS)) {
        Index++;
    }
    // 如果找到空闲位置
    if (Index < SCH_MAX_TASKS) {
        // 添加新任务
        SCH_tasks_G[Index].taskFunc = fPtr;
        SCH_tasks_G[Index].delay = DELAY;
        SCH_tasks_G[Index].period = PERIOD;
        SCH_tasks_G[Index].run = 0;
        // 如果新任务的延迟小于下一次中断的时间
        if (DELAY < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = DELAY;
            // 设置systick定时器的加载值为下一次中断的时间
            SysTick->LOAD = nextInterrupt;
            // 清零systick定时器的当前值
            SysTick->VAL = 0;
        }
    }
}

    在这个修改后的函数中,当我们添加新的任务时,我们会检查新任务的延迟是否小于下一次中断的时间。如果是,我们就更新下一次中断的时间,并设置systick定时器的加载值为下一次中断的时间。希望这个修改后的函数能满足你的需求!

    我:那再把上面确定的这些,全部写出来,我再梳理下

    GPT:当然,以下是我们讨论并确定的代码:

#include "stm32f0xx.h"

typedef struct {
    void (*taskFunc)(void);
    uint32_t delay;
    uint32_t period;
    uint8_t run;
} sTask;

#define SCH_MAX_TASKS 5

sTask SCH_tasks_G[SCH_MAX_TASKS];
static uint32_t nextInterrupt = 1; // 初始化为1,以便在第一次中断时运行

void SysTick_Handler(void) {
    uint32_t index;
    // 遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空
        if (SCH_tasks_G[index].taskFunc != 0) {
            // 如果任务延迟大于等于下一次中断的时间
            if (SCH_tasks_G[index].delay >= nextInterrupt) {
                // 减去下一次中断的时间
                SCH_tasks_G[index].delay -= nextInterrupt;
                // 如果任务延迟为0
                if (SCH_tasks_G[index].delay == 0) {
                    // 标记任务为可运行
                    SCH_tasks_G[index].run = 1;
                    // 如果任务周期不为0,则重置任务延迟
                    if (SCH_tasks_G[index].period) {
                        SCH_tasks_G[index].delay = SCH_tasks_G[index].period;
                    }
                }
            }
        }
    }
    // 初始化下一次中断的时间为最大值
    nextInterrupt = UINT32_MAX;
    // 再次遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空且任务延迟小于下一次中断的时间
        if (SCH_tasks_G[index].taskFunc != 0 && SCH_tasks_G[index].delay < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = SCH_tasks_G[index].delay;
        }
    }
    // 设置systick定时器的加载值为下一次中断的时间
    SysTick->LOAD = nextInterrupt;
    // 清零systick定时器的当前值
    SysTick->VAL = 0;
}

void SCH_Add_Task(void (*fPtr)(void), const uint32_t DELAY, const uint32_t PERIOD) {
    uint32_t Index = 0;
    // 寻找任务数组中的空闲位置
    while ((SCH_tasks_G[Index].taskFunc != 0) && (Index < SCH_MAX_TASKS)) {
        Index++;
    }
    // 如果找到空闲位置
    if (Index < SCH_MAX_TASKS) {
        // 添加新任务
        SCH_tasks_G[Index].taskFunc = fPtr;
        SCH_tasks_G[Index].delay = DELAY;
        SCH_tasks_G[Index].period = PERIOD;
        SCH_tasks_G[Index].run = 0;
        // 如果新任务的延迟小于下一次中断的时间
        if (DELAY < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = DELAY;
            // 设置systick定时器的加载值为下一次中断的时间
            SysTick->LOAD = nextInterrupt;
            // 清零systick定时器的当前值
            SysTick->VAL = 0;
        }
    }
}

void SCH_Dispatch_Tasks(void) {
    uint32_t index;
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        if (SCH_tasks_G[index].run == 1) {
            (*SCH_tasks_G[index].taskFunc)();
            SCH_tasks_G[index].run = 0;
        }
    }
}

int main(void) {
    SysTick_Config(SystemCoreClock / 1000);
    SCH_Add_Task(Task1, 0, 1000);
    SCH_Add_Task(Task2, 0, 2000);
    while (1) {
        SCH_Dispatch_Tasks();
        __WFI();
    }
}

    这段代码实现了一个基于STM32F030C8T6的低功耗伪时间片调度算法,使用了systick定时器来触发事件,并且能够动态地调整systick的定时任务,以...(GPT卡壳了,但不重要)

    我:我觉得在SysTick_Handler()函数中if (SCH_tasks_G[index].delay >= nextInterrupt) 的else情况也写一下,这样更健全点儿,当不知名原因导致delay小于nextInterrupt,定时时间肯定也是到了

    GPT:你的建议非常好,确实应该考虑到这种情况。我们可以在if (SCH_tasks_G[index].delay >= nextInterrupt)后面添加一个else语句,处理delay小于nextInterrupt的情况。以下是修改后的代码:

#include "stm32f0xx.h"

typedef struct {
    void (*taskFunc)(void);
    uint32_t delay;
    uint32_t period;
    uint8_t run;
} sTask;

#define SCH_MAX_TASKS 5

sTask SCH_tasks_G[SCH_MAX_TASKS];
static uint32_t nextInterrupt = 1; // 初始化为1,以便在第一次中断时运行

void SysTick_Handler(void) {
    uint32_t index;
    // 遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空
        if (SCH_tasks_G[index].taskFunc != 0) {
            // 如果任务延迟大于等于下一次中断的时间
            if (SCH_tasks_G[index].delay >= nextInterrupt) {
                // 减去下一次中断的时间
                SCH_tasks_G[index].delay -= nextInterrupt;
                // 如果任务延迟为0
                if (SCH_tasks_G[index].delay == 0) {
                    // 标记任务为可运行
                    SCH_tasks_G[index].run = 1;
                    // 如果任务周期不为0,则重置任务延迟
                    if (SCH_tasks_G[index].period) {
                        SCH_tasks_G[index].delay = SCH_tasks_G[index].period;
                    }
                }
            } else {
                // 如果任务延迟小于下一次中断的时间
                // 标记任务为可运行
                SCH_tasks_G[index].run = 1;
                SCH_tasks_G[index].delay = 0;
                // 如果任务周期不为0,则重置任务延迟
                if (SCH_tasks_G[index].period) {
                    SCH_tasks_G[index].delay = SCH_tasks_G[index].period;
                }
            }
        }
    }
    // 初始化下一次中断的时间为最大值
    nextInterrupt = UINT32_MAX;
    // 再次遍历任务数组
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        // 如果任务函数指针不为空且任务延迟小于下一次中断的时间
        if (SCH_tasks_G[index].taskFunc != 0 && SCH_tasks_G[index].delay < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = SCH_tasks_G[index].delay;
        }
    }
    // 设置systick定时器的加载值为下一次中断的时间
    SysTick->LOAD = nextInterrupt;
    // 清零systick定时器的当前值
    SysTick->VAL = 0;
}

void SCH_Add_Task(void (*fPtr)(void), const uint32_t DELAY, const uint32_t PERIOD) {
    uint32_t Index = 0;
    // 寻找任务数组中的空闲位置
    while ((SCH_tasks_G[Index].taskFunc != 0) && (Index < SCH_MAX_TASKS)) {
        Index++;
    }
    // 如果找到空闲位置
    if (Index < SCH_MAX_TASKS) {
        // 添加新任务
        SCH_tasks_G[Index].taskFunc = fPtr;
        SCH_tasks_G[Index].delay = DELAY;
        SCH_tasks_G[Index].period = PERIOD;
        SCH_tasks_G[Index].run = 0;
        // 如果新任务的延迟小于下一次中断的时间
        if (DELAY < nextInterrupt) {
            // 更新下一次中断的时间
            nextInterrupt = DELAY;
            // 设置systick定时器的加载值为下一次中断的时间
            SysTick->LOAD = nextInterrupt;
            // 清零systick定时器的当前值
            SysTick->VAL = 0;
        }
    }
}

void SCH_Dispatch_Tasks(void) {
    uint32_t index;
    for (index = 0; index < SCH_MAX_TASKS; index++) {
        if (SCH_tasks_G[index].run == 1) {
            (*SCH_tasks_G[index].taskFunc)();
            SCH_tasks_G[index].run = 0;
        }
    }
}

void Task1(void)
{
	
}

void Task2(void)
{
	
}

int main(void) {
    SysTick_Config(SystemCoreClock / 1000);
    SCH_Add_Task(Task1, 0, 1000);
    SCH_Add_Task(Task2, 0, 2000);
    while (1) {
        SCH_Dispatch_Tasks();
        __WFI();
    }
}

    在这个修改后的代码中,如果任务延迟小于下一次中断的时间,我们会立即标记任务为可运行,并重置任务延迟。这样,我们就可以确保在任何情况下,只...(GPT又卡壳了,不重要)

四、总结

    是不是和我们平时单片机编程很不一样,实际上AI编程它总结了很多前任的编程经验,以及足够的框架规范,所以它的整体框架还是不错的,但是它并不会很好的完成每一个细节,甚至有一些bug,这反而正是我们自己已经梳理好的,所以我们给它指导出来即可;这样就和AI协同完成了一个基本框架,接下来自己要做的就是移植到指定的芯片上,完成基本的底层驱动开发即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8s v1.20二进制部署 部署 CNI 网络组件 部署 Calico

一、部署 flannel 1.1.K8S 中 Pod 网络通信 ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器&#xff08;Pod 内的容器是不会跨宿主机的&#xff09;共享同一个网络命名空间&#xff0c;相当于它们在同一台机器上一样&#xff0c;可以用 localhost 地址访问彼此的端口。…

【MATLAB源码-第59期】基于matlab的QPSK,16QAM164QAM等调制方式误码率对比,调制解调函数均是手动实现未调用内置函数。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 正交幅度调制&#xff08;QAM&#xff0c;Quadrature Amplitude Modulation&#xff09;是一种在两个正交载波上进行幅度调制的调制方式。这两个载波通常是相位差为90度&#xff08;π/2&#xff09;的正弦波&#xff0c;因此…

【R语言与统计】SEM结构方程、生物群落、多元统计分析、回归及混合效应模型、贝叶斯、极值统计学、meta分析、copula、分位数回归、文献计量学

统计模型的七大类&#xff1a;一&#xff1a;多元回归 在研究变量之间的相互影响关系模型时候&#xff0c;用到这类方法&#xff0c;具体地说&#xff1a;其可以定量地描述某一现象和某些因素之间的函数关系&#xff0c;将各变量的已知值带入回归方程可以求出因变量的估计值&…

[Algorithm][回溯][组合][目标和][组合总和]详细讲解

目录 1.组合1.题目链接2.算法原理详解3.代码实现 2.目标和1.题目链接2.算法原理详解3.代码实现 3.组合总和1.题目链接2.算法原理详解3.代码实现 1.组合 1.题目链接 组合 2.算法原理详解 思路&#xff1a;每次都只选一个数&#xff0c;此后只能选它后面的数函数设计&#xff…

RK3568平台开发系列讲解(SPI篇)spi_dev 驱动分析

🚀返回专栏总目录 文章目录 一、结构体二、API三、spidev驱动分析3.1、init3.2、probe3.3、spidev_write3.4、spidev_read3.5、spidev_open四、spi_register_driver分析五、spi_dev缺点沉淀、分享、成长

通过java将数据导出为PDF,包扣合并单元格操作

最近项目中需要将查询出来的表格数据以PDF形式导出&#xff0c;并且表格的形式包含横向行与纵向列的单元格合并操作&#xff0c;导出的最终效果如图所示&#xff1a; 首先引入操作依赖 <!--导出pdf所需包--><dependency><groupId>com.itextpdf</groupId&…

项目管理-案例重点知识(风险管理)

项目管理 : 每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 二、风险管理 案例重点 重点内容&#xff1a; &#xff08;1&#xff09;风险划分 &#xff08;2&#xff09;SWOT 分析&#xff0c;提示清单 …

Golang RPC实现-day01

导航 Golang RPC实现一、主体逻辑设计二、服务设计1、监听和接收请求2、处理请求(1)服务结构体定义(2)确认请求方和服务方编解码格式(3)循环读取请求(4)解析请求的内容(5)响应请求 三、读取和发送数据到连接中代码 Golang RPC实现 先来一个最简单的版本&#xff0c;后续更新。…

BakedSDF: Meshing Neural SDFs for Real-Time View Synthesis 论文阅读

&#xff08;水一篇博客&#xff09; 项目主页 BakedSDF: Meshing Neural SDFs for Real-Time View Synthesis 作者介绍 是 Mildenhall 和 Barron 参与的工作&#xff08;都是谷歌的&#xff09;&#xff0c;同时一作是 Lipman 的学生&#xff0c;VolSDF 的一作。本文引用…

使用Caché管理工具

Cach通过一个web工具来对其进行系统管理和完成管理任务,该方法的一个好处是不必将Cach安装到用于管理的系统上。目前,通过网络远程管理和控制对站点的访问,这些都比较容易。因为数据及其格式信息都直接来自被管理的系统,因此,这也可以最小化跨版本的兼容问题。 本文将描述…

企业微信hook接口协议,ipad协议http,获取群成员列表简洁版

获取群成员列表简洁版 参数名必选类型说明uuid是String每个实例的唯一标识&#xff0c;根据uuid操作具体企业微信 请求示例 {"uuid":"3240fde0-45e2-48c0-90e8-cb098d0ebe43","roomid":10696052955016166 } 返回示例 {"data": {&q…

K8S内容

K8S介绍 1、故障迁移:当某一个node节点关机或挂掉后&#xff0c;node节点上的服务会自动转移到另一个node节点上&#xff0c;这个过程所有服务不中断。这是docker或普通云主机是不能做到的 2、资源调度:当node节点上的cpu、内存不够用的时候&#xff0c;可以扩充node节点&…

基于SSM的“口腔护理网站”的设计与实现(源码+数据库+文档)

基于SSM的“口腔护理网站”的设计与实现&#xff08;源码数据库文档) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 首页 用户注册页面 医生信息查看模块 口腔护理预约模块 后台首页面…

零基础10 天入门 Web3之第3天

10 天入门 Web3之第3天 什么是以太坊&#xff0c;以太坊能做什么&#xff1f;Web3 是互联网的下一代&#xff0c;它将使人们拥有自己的数据并控制自己的在线体验。Web3 基于区块链技术&#xff0c;该技术为安全、透明和可信的交易提供支持。我准备做一个 10 天的学习计划&…

Anaconda下载安装

看到这篇文章的同学们&#xff0c;说明你们是要下载Anaconda&#xff0c;这篇文章讲的就是下载安装教程。 Anaconda下载网址&#xff1a; Download Now | Anaconda 根据我们需要的系统版本下载&#xff0c;我的电脑是window&#xff0c;所以选择第一个&#xff0c;如下图&am…

苍穹外卖-day01(SpringBoot+SSM的企业级Java项目实战)

苍穹外卖-day01 课程内容 软件开发整体介绍 苍穹外卖项目介绍 开发环境搭建 导入接口文档 Swagger 项目整体效果展示&#xff1a; 管理端-外卖商家使用 用户端-点餐用户使用 当我们完成该项目的学习&#xff0c;可以培养以下能力&#xff1a; 1. 软件开发整体介绍 作为…

Power query与Excel的区别,优势?

Power Query是Microsoft Excel的一个强大数据导入、转换和自动化的插件工具&#xff0c;它在Excel 2010之后的版本中被发布出来&#xff0c;随着时间的发展&#xff0c;功能不断增强。 以下是Power Query的一些优势以及它与Excel传统数据处理方式的区别和一些令人印象深刻的功…

鸿蒙内核源码分析 (TLFS 算法篇) | 图表解读 TLFS 原理

动态分配 本篇开始说一个耳朵听起老茧的概念 动态分配&#xff0c;将分成上下两篇&#xff0c;本篇为上篇&#xff0c;看完能快速理解下篇鸿蒙内核源码对动态内存的具体实现。 鸿蒙内核源码分析(TLFS算法) 结合图表从理论视角说清楚 TLFS 算法鸿蒙内核源码分析(内存池管理) 结…

分析 vs2019 cpp20 规范的 STL 库模板 function ,源码注释并探讨几个问题

&#xff08;1 探讨一&#xff09;第一个尝试弄清的问题是父类模板与子类模板的模板参数的对应关系&#xff0c;如下图&#xff1a; 我们要弄清的问题是创建 function 对象时&#xff0c;传递的模板参数 _Fty , 传递到其父类 _Func_class 中时 &#xff0c;父类的模板参数 _Ret…

面试集中营—rocketmq架构篇

一、基本定义 Apache RocketMQ 是一款低延迟、高并发、高可用、高可靠的分布式消息中间件。消息队列 RocketMQ 可为分布式应用系统提供异步解耦和削峰填谷的能力&#xff0c;同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。 Topic&#xff1a;消息主题&…