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

news2025/1/16 12:28:09

一、本文内容

    上一节《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协同完成了一个基本框架,接下来自己要做的就是移植到指定的芯片上,完成基本的底层驱动开发即可。

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

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

相关文章

【Image captioning】In Defense of Grid Features for Visual Question Answering实现流程

In Defense of Grid Features for Visual Question Answering实现流程 网格特征预训练代码 这是该论文的特征预训练代码发布: @InProceedings{jiang2020defense,title={In Defense of Grid Features for Visual Question Answering},author={Jiang, Huaizu and Misra, Ishan…

机器学习实战宝典:用scikit-learn打造智能应用

书接上文——《数据探险家的终极指南&#xff1a;用Python挖掘机器学习的奥秘》 前文我们在这段精彩的机器学习探险之旅中&#xff0c;从基础概念出发&#xff0c;深入探索了使用Python和scikit-learn库进行数据分析和模型构建的全过程。 我们首先了解了机器学习的基本原理&am…

通义千问 1.5 -7B fine-tune验证

尝试对对中文数据进行finetune验证&#xff0c;测试模型的可优化方向。下面是代码的详细情况 代码实现 from datasets import load_dataset from transformers import (AutoModelForCausalLM,AutoTokenizer,BitsAndBytesConfig,HfArgumentParser,AutoTokenizer,TrainingArgum…

hadoop学习---基于Hive的数据仓库相关函数机制及其优化方案

Hive相关函数&#xff08;部分&#xff09;&#xff1a; if函数: 作用: 用于进行逻辑判断操作 语法: if(条件, true返回信息,false返回信息) 注意: if函数支持嵌套使用 select if(aa,’bbbb’,111) fromlxw_dual; bbbb select if(1<2,100,200) fromlxw_dual; 200nvl函数:…

【Python】理解WOE(Weight of Evidence)和IV(Information Value)

忠孝东路走九遍 脚底下踏著曾经你我的点点 我从日走到夜 心从灰跳到黑 我多想跳上车子离开伤心的台北 忠孝东路走九遍 穿过陌生人潮搜寻你的脸 有人走的匆忙 有人爱的甜美 谁会在意擦肩而过的心碎 &#x1f3b5; 动力火车《忠孝东路走九遍》 在信用评分和…

教育型内容的制胜秘诀:Kompas.ai如何结合知识与营销

在数字化营销的浪潮中&#xff0c;教育型内容已经成为品牌建立权威性和提供价值的重要手段。通过分享专业知识和见解&#xff0c;品牌不仅能够吸引目标受众&#xff0c;还能够在潜在客户心中建立起专业和可信赖的形象。本文将深入分析教育型内容的重要性&#xff0c;详细介绍Ko…

VALSE 2024合合信息 | 文档解析与向量化技术加速多模态大模型训练与应用

第十四届视觉与学习青年学者研讨会&#xff08;VALSE 2024&#xff09;近期在重庆悦来国际会议中心圆满举行&#xff0c;由中国人工智能学会&#xff08;CAAI&#xff09;、中国图象图形学会&#xff08;CSIG&#xff09;、中国民族贸易促进会主办&#xff0c;重庆邮电大学承办…

数据库系统概论(个人笔记)(第二部分)

数据库系统概论&#xff08;个人笔记&#xff09; 文章目录 数据库系统概论&#xff08;个人笔记&#xff09;2、关系模型简介2.1 关系数据库的结构2.2 数据库模式2.3 键2.4 模式图2.5 关系查询语言2.6 关系代数 2、关系模型简介 2.1 关系数据库的结构 Structure of Relational…

【目标检测】YOLOv5|YOLOv8模型QT界面可视化部署

YOLO-Deploy-QT_Interface 最近笔者做了YOLO系列算法的部署工作,现做一个总结。主要工作是做了用于部署YOLOv5和YOLOv8的可视化QT界面,可实现图片、文件夹、视频、摄像头的ONNX与OpenVino部署,具体效果如下: 代码链接:https://github.com/Zency-Sun/YOLO-Deploy-QT_Inte…

开源之夏又有新项目发布!快来认领!¥12,000 奖金等你来!

又有新项目加入开源之夏啦&#xff01;KubeBlocks 的亲兄弟 WeScale 也加入开源之夏啦&#xff01;一起来了解下 WeScale 社区及它带来的项目吧&#xff01; WeScale 是什么&#xff1f; WeScale 社区源自对更好的数据库访问体验的追求。在现代应用程序开发中&#xff0c;数据…

一文扫盲(13):电商管理系统的功能模块和设计要点

电商管理系统是一种用于管理和运营电子商务平台的软件系统。它提供了一系列功能模块&#xff0c;帮助企业进行商品管理、订单管理、会员管理、营销推广、数据分析等工作。本文将从以下四个方面介绍电商管理系统。 一、什么是电商管理系统 电商管理系统是一种集成了各种功能模块…

Android Q - 音频通路调试

对于当前模块不是很清楚&#xff0c;刚好有个项目这方面有点问题&#xff0c;根据展锐支持文档一步步检查就可以了。首先得先弄清楚硬件具体是怎么连接的&#xff0c;比如文档提到的案例&#xff1a;sprd codec speaker output 连接外部 PA。 耳机接的是什么&#xff0c;speake…

国产化开源鸿蒙系统智能终端RK3568主板在电子班牌项目的应用

国产化开源鸿蒙系统智能终端主板AIoT-3568A、人脸识别算法的的电子班牌方案可支持校园信息发布、人脸识别考勤、考场管理、查询互动等多项功能&#xff0c;助力学校在硬件上实现信息化、网络化、数字化&#xff0c;构建“学校、教师、学生”三个维度的智慧教育空间。 方案优势 …

mysql5和mysql8同时存在

Win10安装两个不同版本MySQL数据库&#xff08;一个5.7&#xff0c;一个8.0.17&#xff09;_两个数据库的版本不同(我本地的是mysql5.7,线上是mysql8),怎么进行数据的同步?-CSDN博客 安装两个版本的mysql出现的问题和解决_mysql安装两个版本其中一个不适用-CSDN博客 一台电脑…

增程SUV价格即将崩盘?买车一定要再等等!

文 | AUTO芯球 作者 | 雷歌​ 真是“离谱”啊&#xff0c;车圈真是逗比欢乐多&#xff0c; 我这两天看一个博主连续40多小时开车直播&#xff0c;充电口、油箱盖全部封死&#xff0c;全程视频直播没断过&#xff0c; 就为了测试这两天刚上市的星际元ET续航有多远。 另一个…

drippingblues 靶机实战

信息收集&#xff1a; Nmap: 存活&#xff1a; 靶机ip&#xff1a;192.168.10.110 端口&#xff1a; 服务&#xff1a; 发现ftp服务可以匿名登录。且用户名是FTP。 发现一个压缩包&#xff0c;下载并爆破。 得到密码 072528035。发现关键字 drip。里面还有一个 secret.zip(…

C语言写扫雷游戏(数组和函数实践)

目录 最后是代码啦&#xff01; 手把手教你用C语言写一个扫雷游戏&#xff01; 1.我们搭建一下这个多文件形式的扫雷游戏文件结构 2.在主函数里面设置一个包含游戏框架的菜单 菜单可以方便游戏玩家选择要进行的动作和不断地进行下一局。 3.switch语句连接不同的结果 菜单可…

49.乐理基础-拍号的类型-单拍子、复拍子

当前写的东西&#xff0c;如果只是想要看懂乐谱的话&#xff0c;它是没什么意义的&#xff0c;就像我们要把 0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5。。。称为自然数&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5称为正整…

【提示学习论文】TCP:Textual-based Class-aware Prompt tuning for Visual-Language Model

TCP:Textual-based Class-aware Prompt tuning for Visual-Language Model&#xff08;CVPR2024&#xff09; 基于文本的类感知提示调优的VLMKgCoOp为baseline&#xff0c;进行改进&#xff0c;把 w c l i p w_{clip} wclip​进行投影&#xff0c;然后与Learnable prompts进行…

考研数学|强化《660》+《880》这样刷,太丝滑了❗️

660题880题需要大概两个月才能做完 660题和880题都是很高质量的题集&#xff0c;所以做起来一点也不轻松。 每年都会有学生暑假两个月只做了一本660题的情况&#xff0c;因为题目实在是太难&#xff0c;有点做不下去的感觉。 不过不要担心&#xff0c;暑假就是刷题发现问题的…