FreeRTOS任务切换过程深层解析

2023-05-16

FreeRTOS 系统的任务切换最终都是在 PendSV 中断服务函数中完成的,uCOS 也是在 PendSV 中断中完成任务切换的。

【为什么用PendSV异常来做任务切换】

PendSV 可以像普通中断一样被 Pending(往 NVIC 的 PendSV 的 Pend 寄存器写 1),常用的场合是 OS 进行上下文切换;它可以手动拉起后,等到比他优先级更高的中断完成后,再执行;

假设,带 OS 系统的 CM3 中有两个就绪的任务,上下文切换可以发生在 SYSTICK 中断中:
在这里插入图片描述
这里展现的是两个任务 A 和 B 轮转调度的过程;但是,如果在产生 SYSTICK 异常时,系统正在响应一个中断,则 SYSTICK 异常会抢占其他 ISR。在这种情况下 OS 是不能执行上下文切换的,否则将使得中断请求被延迟;

而且,如果在 SYSTICK 中做任务切换,那么就会尝试切入线程模式,将导致用法 fault 异常;因为正常来说,即使SYSTICK优先级比IRQ高,当SYSTICK执行完后,也应该是回到低优先级的中断处理函数IRQ里而不是直接切换到任务去运行了,这时候IRQ都没运行完呢,怎么能中断函数都没处理完就去运行主程序呢?自然是不允许这样的。
在这里插入图片描述
为了解决这种问题,早期的 OS 在上下文切换的时候,检查是否有中断需要响应,没有的话,采取切换上下文,然而这种方法的问题在于,可能会将任务切换的动作拖延很久(如果此次的 SYSTICK 无法切换上下文,那么要等到下一次 SYSTICK 再来切换),严重的情况下,如果某 IRQ 来的频率和 SYSTICK 来的频率比较接近的时候,会导致上下文切换迟迟得不到进行;
引入 PendSV 以后,可以将 PendSV 的异常优先级设置为最低,在 PendSV 中去切换上下文,PendSV 会在其他 ISR 得到相应后,立马执行:
在这里插入图片描述
上图的过程可以描述为:

1、任务 A 呼叫 SVC 请求任务切换;

2、OS 收到请求,准备切换上下文,手动 Pending 一个 PendSV;

3、CPU 退出 SVC 的 ISR 后,发现没有其他 IRQ 请求,便立即进入 PendSV 执行上下文切换;

4、正确的切换到任务 B;

5、此刻发生了一个中断,开始执行此中断的 ISR;

6、ISR 执行一半,SYSTICK 来了,抢占了该 IRQ;

7、OS 执行一些逻辑,并手动 Pending PendSV 准备上下文切换;

8、退出 SYSTICK 的 ISR 后,由于之前的 IRQ 优先级高于 PendSV,所以之前的 ISR 继续执行;

9、ISR 执行完毕退出,此刻没有优先级更高的 IRQ,那么执行 PendSV 进行上下文切换;

10、PendSV 执行完毕,顺利切到任务 A,同时进入线程模式;

以上部分摘自:https://www.cnblogs.com/god-of-death/p/14856578.html

【如何设定PendSV优先级】
在这里插入图片描述
往地址为0xE000ED22的寄存器PRI_14写入PendSV优先级

NVIC_SYSPRI14   EQU     0xE000ED22
NVIC_PENDSV_PRI EQU           0xFF
    
    LDR     R1, =NVIC_PENDSV_PRI    
    LDR     R0, =NVIC_SYSPRI14    
    STRB    R1, [R0]  ;将r1 中的 [7:0]存储到 r0 对应的内存
    BX      LR   ;返回

【如何触发PendSV异常】
在这里插入图片描述
往ICSR第28位写1,即可将PendSV异常挂起。若是当前没有高优先级中断产生,那么程序将会进入PendSV handler

NVIC_INT_CTRL   EQU     0xE000ED04                              
NVIC_PENDSVSET  EQU     0x10000000                              

    LDR     R0, =NVIC_INT_CTRL                                 
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

【测试PendSV异常handler实现任务切换】
如何实现任务切换?三个步骤:

步骤一:在进入中断前先设置PSP。

步骤二:将当前寄存器的内容保存到当前任务堆栈中。进入ISR时,cortex-m3会自动保存八个寄存器到PSP中,剩下的几个需要我们手动保存。

步骤三:在Handler中将下一个任务的堆栈中的内容加载到寄存器中,并将PSP指向下一个任务的堆栈。这样就完成了任务切换。

要在PendSV 的ISR中完成这两个步骤,我们先需了解下在进入PendSV ISR时,cortex-M3做了什么?

1,入栈。会有8个寄存器自动入栈。入栈内容及顺序如下:
在这里插入图片描述
在步骤一中,我们已经设置了PSP,那这8个寄存器就会自动入栈到PSP所指地址处。

2,取向量。找到PendSV ISR的入口地址,这样就能跳到ISR了。

3,更新寄存器内容。

做完这三步后,程序就进入ISR了。

进入ISR前,我们已经完成了步骤一,cortex-M3已经帮我们完成了步骤二的一部分,剩下的需要我们手动完成。

在ISR中添加代码如下:

MRS R0, PSP

保存PSP到R0。为什么是PSP而不是MSP。因为在OS启动的时候,我们已经把SP设置为PSP了。这样使得用户程序使用任务堆栈,OS使用主堆栈,不会互相干扰。不会因为用户程序导致OS崩溃。

STMDB R0!,{R4-R11}

保存R4-R11到PSP中。C语言表达是*(–R0)={R4-R11},R0中值先自减1,然后将R4-R11的值保存到该值所指向的地址中,即PSP中。

STMDB Rd!,{寄存器列表} 连续存储多个字到Rd中的地址值所指地址处。每次存储前,Rd先自减一次。

若是ISR是从从task0进来,那么此时task0的堆栈中已经保存了该任务的寄存器参数。保存完成后,当前任务堆栈中的内容如下(假设是task0)
在这里插入图片描述
左边表格是预期值,右边是keil调试的实际值。可以看出,是一致的。在任务初始化时(步骤一),我们将PSP指向任务0的栈顶0x20000080。在进入PendSV之前,cortex-M3自动入栈八个值,此时PSP指向了0x20000060。然后我们再保存R4-R11到0x20000040~0x2000005C。

这样很容易看明白,如果需要下次再切换到task0,只需恢复R4~R11,再将PSP指向0x20000060即可。

测试例程:

#define HW32_REG(ADDRESS)  (*((volatile unsigned long  *)(ADDRESS)))
void USART1_Init(void);
void task0(void) ; 

uint32_t  curr_task=0;     // 当前执行任务
uint32_t  next_task=1;     // 下一个任务
uint32_t task0_stack[17];
uint32_t task1_stack[17];
uint32_t  PSP_array[4];

u8 task0_handle=1;
u8 task1_handle=1;

void task0(void) 
{ 
    while(1)
    {
        if(task0_handle==1)
        {
            printf("task0\n");
            task0_handle=0;
            task1_handle=1;
        }
    }
}

void task1(void)
{
    while(1)
    {
        if(task1_handle==1)
        {
            printf("task1\n");
            task1_handle=0;
            task0_handle=1;
        }
    }
}

__asm void SetPendSVPro(void)
{
NVIC_SYSPRI14   EQU     0xE000ED22
NVIC_PENDSV_PRI EQU           0xFF
    
    LDR     R1, =NVIC_PENDSV_PRI    
    LDR     R0, =NVIC_SYSPRI14    
    STRB    R1, [R0]
    BX      LR
}

__asm void TriggerPendSV(void)
{
NVIC_INT_CTRL   EQU     0xE000ED04                              
NVIC_PENDSVSET  EQU     0x10000000                              

    LDR     R0, =NVIC_INT_CTRL                                 
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR
}

int main(void)
{
    SetPendSVPro();
    LED_Init();
    uart_init(115200);
    
    printf("OS test\n");
    
    PSP_array[0] = ((unsigned int) task0_stack) + (sizeof task0_stack) - 16*4;
    //PSP_array中存储的为task0_stack数组的尾地址-16*4, 即task0_stack[1]的地址
    HW32_REG((PSP_array[0] + (14*4))) = (unsigned long) task0; /* PC */
    //task0的PC存储在task0_stack[1]地址+14*4, 即task0_stack[15]的地址中
    HW32_REG((PSP_array[0] + (15*4))) = 0x01000000;            /* xPSR */
  
    PSP_array[1] = ((unsigned int) task1_stack) + (sizeof task1_stack) - 16*4;
    HW32_REG((PSP_array[1] + (14*4))) = (unsigned long) task1; /* PC */
    HW32_REG((PSP_array[1] + (15*4))) = 0x01000000;            /* xPSR */    
    
    /* 任务0先执行 */
    curr_task = 0; 
     
    /* 设置PSP指向任务0堆栈的栈顶 */
    __set_PSP((PSP_array[curr_task] + 16*4)); 
    
    SysTick_Config(9000000);
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//72/8=9MHZ     
    /* 使用堆栈指针,非特权级状态 */
    __set_CONTROL(0x3);
    
    /* 改变CONTROL后执行ISB (architectural recommendation) */
    __ISB();
    
    /* 启动任务0 */
    task0();  
    //LED0=0;
    while(1);
}

__asm void PendSV_Handler(void)
{ 
    // 保存当前任务的寄存器内容
    MRS    R0, PSP     // 得到PSP  R0 = PSP
                       // xPSR, PC, LR, R12, R0-R3已自动保存
    STMDB  R0!,{R4-R11}// 保存R4-R11共8个寄存器得到当前任务堆栈
    
    // 加载下一个任务的内容
    LDR    R1,=__cpp(&curr_task)
    LDR    R3,=__cpp(&PSP_array)
    LDR    R4,=__cpp(&next_task)
    LDR    R4,[R4]     // 得到下一个任务的ID
    STR    R4,[R1]     // 设置 curr_task = next_task
    LDR    R0,[R3, R4, LSL #2] // 从PSP_array中获取PSP的值
    LDMIA  R0!,{R4-R11}// 将任务堆栈中的数值加载到R4-R11中
  //ADDS   R0, R0, #0x20
    MSR    PSP, R0     // 设置PSP指向此任务
 // ORR     LR, LR, #0x04   
    BX     LR          // 返回
                       // xPSR, PC, LR, R12, R0-R3会自动的恢复
    ALIGN  4
}

void SysTick_Handler(void)
{
    LED0=!LED0;//位带操作
    if(curr_task==0)
        next_task=1;
    else
        next_task=0;
    TriggerPendSV();
}

串口输出:
在这里插入图片描述
可以看到在任务0和任务1之间来回切换。

【FreeRTOS任务切换源码】

上下文(任务)切换被触发的场合大致分为:
● 可以执行一个系统调用
● 系统滴答定时器(SysTick)中断。

执行系统调用就是执行 FreeRTOS 系统提供的相关 API 函数,比如任务切换函数taskYIELD(),这些 API 函数和任务切换函数
taskYIELD()都统称为系统调用。

函数 taskYIELD()其实就是个宏,在文件 task.h 中有如下定义:
#define taskYIELD() portYIELD()

函数 portYIELD()也是个宏,在文件 portmacro.h 中有如下定义

#define portYIELD()  \
{  \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \   //通过向中断控制和壮态寄存器 ICSR 的 bit28 写入 1 挂起 PendSV 来启动 PendSV 中断。这样就可以在 PendSV 中断服务函数中进行任务切换了。
\
__dsb( portSY_FULL_READ_WRITE );  \
__isb( portSY_FULL_READ_WRITE );  \
}

中断级的任务切换函数为 portYIELD_FROM_ISR(),定义如下:

#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )

#define portEND_SWITCHING_ISR( xSwitchRequired ) \
	if( xSwitchRequired != pdFALSE ) portYIELD()   //可以看出 portYIELD_FROM_ISR()最终也是通过调用函数 portYIELD()来完成任务切换的。

系统滴答定时器(SysTick)中断

void SysTick_Handler(void)
{
	if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
	{
		xPortSysTickHandler();
	}
}

xPortSysTickHandler()源码如下:

void xPortSysTickHandler( void )
{
	vPortRaiseBASEPRI(); //关闭中断
	{
		if( xTaskIncrementTick() != pdFALSE )  //增加时钟计数器 xTickCount 的值
		{
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; //通过向中断控制和壮态寄存器 ICSR 的 bit28 写入 1 挂起 PendSV 来启动 PendSV 中断。这样就可以在 PendSV 中断服务函数中进行任务切换了。
		}
	}
	vPortClearBASEPRIFromISR(); //打开中断
}

真正的任务切换代码在PendSV中断函数中,
FreeRTOS做了如下函数重定义
#define xPortPendSVHandler PendSV_Handler

xPortPendSVHandler函数如下 (汇编 port.c)

__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;
	
	PRESERVE8
	
	mrs r0, psp  //读取进程栈指针,保存在寄存器 R0 里面。
	isb
	
	ldr r3, =pxCurrentTCB //获取当前任务的任务控制块
	ldr r2, [r3]  //接上,并将任务控制块的地址保存在寄存器 R2 里面
	
	tst r14, #0x10 //判断任务是否使用了 FPU,如果任务使用了 FPU 的话在进行任务切换的时候就
	it eq          //需要将 FPU 寄存器 s16~s31 手动保存到任务堆栈中,其中 s0~s15 和 FPSCR 是自动保存的
	
	vstmdbeq r0!, {s16-s31} //保存 s16~s31 这 16 个 FPU 寄存器
	stmdb r0!, {r4-r11, r14} //保存 r4~r11 和 R14 这几个寄存器的值
	str r0, [r2]  //将寄存器 R0 的值写入到寄存器 R2 所保存的地址中去,也就是将新的栈顶保存在任务控制块的第一个字段中。
	stmdb sp!, {r3} //将寄存器 R3 的值临时压栈,寄存器 R3 中保存了当前任务的任务控制块
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY //关闭中断,进入临界区
	msr basepri, r0  							  //关闭中断,进入临界区
	dsb
	isb
	bl vTaskSwitchContext //调用函数 vTaskSwitchContext(),此函数用来获取下一个要运行的任务,并将pxCurrentTCB 更新为这个要运行的任务
	mov r0, #0  //打开中断,退出临界区。
	msr basepri, r0  //打开中断,退出临界区。
	ldmia sp!, {r3} //刚刚保存的寄存器 R3 的值出栈,恢复寄存器 R3 的值
	ldr r1, [r3]  //获取新的要运行的任务的任务堆栈栈顶,
	ldr r0, [r1]  //接上,并将栈顶保存在寄存器 R0 中
	ldmia r0!, {r4-r11, r14} //R4~R11,R14 出栈,也就是即将运行的任务的现场
	tst r14, #0x10 //判断即将运行的任务是否有使用到 FPU,如果有的话还需要手工恢复 FPU的 s16~s31 寄存器。
	it eq          //同上
	vldmiaeq r0!, {s16-s31} //同上
	msr psp, r0 //更新进程栈指针 PSP 的值
	isb
	bx r14  //执行此行代码以后硬件自动恢复寄存器 R0~R3、R12、LR、PC 和 xPSR 的值,确定
			//异常返回以后应该进入处理器模式还是进程模式,使用主栈指针(MSP)还是进程栈指针(PSP)。
			//很明显这里会进入进程模式,并且使用进程栈指针(PSP),寄存器 PC 值会被恢复为即将运行的
			//任务的任务函数,新的任务开始运行!至此,任务切换成功。
}

参考:
https://www.cnblogs.com/WeyneChen/p/4891885.html

https://www.cnblogs.com/god-of-death/p/14856578.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS任务切换过程深层解析 的相关文章

  • 【FreeRtos学习笔记】STM32 CubeMx——Timers(定时器)

    目录 1 软件定时器 2 示例程序 2 1 例程功能 2 2 步骤 2 3 实验结果 2 4 函数讲解 1 软件定时器 定时器是MCU常用的外设 我们在学习各种单片机时必然会学习它的硬件定时器 但是 MCU自带的硬件定时器资源是有限的 而且
  • FreeRTOS内核配置说明---FreeRTOS Kernel V10.2.1

    FreeRTOS内核是高度可定制的 使用配置文件FreeRTOSConfig h进行定制 每个FreeRTOS应用都必须包含这个头文件 用户根据实际应用来裁剪定制FreeRTOS内核 这个配置文件是针对用户程序的 而非内核 因此配置文件一般
  • FreeRTOS config开始的宏

    FreeRTOSConfig h系统配置文件中可以自定义 FreeRTOS h中定义默认值 configAPPLICATION ALLOCATED HEAP 默认情况下FreeRTOS的堆内存是由编译器来分配的 将宏configAPPLIC
  • freertos---软定时器

    一 软件定时器介绍 freeRTOS软件定时器的时基是基于系统时钟节拍实现的 可以创建很多个 在硬件定时器资源不充足的情况下非常有用 软件定时器一般用作周期性地执行函数 在创建软件定时器时指定软件定时器的回调函数 在回调函数中实现相应的功能
  • FreeRTOS ------- 任务(task)

    在学习RTOS的时候 个人觉得带着问题去学习 会了解到更多 1 什么是任务 在FreeRTOS中 每个执行线程都被称为 任务 每个任务都是在自己权限范围内的一个小程序 其具有程序入口每个任务都是在自己权限范围内的一个小程序 其具有程序入口通
  • FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)

    记录一下一个实际项目由裸机程序改成FreeRTOS 以前产品的平台还是C8051单片机上面的程序 硬件平台改成了STM32L051 同时使用STM32CubeMX生成的工程 使用FreeRTOS系统 EEPROM数据存储读取函数修改更新 2
  • FreeRTOS_中断

    传送门 博客汇总帖 传送门 Cortex M3 中断 异常 传送门 Cortex M3笔记 基础 笔记内容参考 正点原子的FreeRTOS开发手册 cortex m3权威指南 Cortex M3和Cortex M4权威指南等 文中stm32
  • FreeRTOS临界段和开关中断

    http blog sina com cn s blog 98ee3a930102wg5u html 本章教程为大家讲解两个重要的概念 FreeRTOS的临界段和开关中断 本章教程配套的例子含Cortex M3内核的STM32F103和Co
  • FreeRTOS,串口中断接收中使用xQueueOverwriteFromISR()函数,程序卡死在configASSERT

    原因 UART的中断优先级设置的太高 高于了configMAX SYSCALL INTERRUPT PRIORITY宏定义的安全中断等级 UART的中断等级小于等于宏定义的优先等级即可
  • FreeRTOS之事件

    FreeRTOS之事件 声明 本人按照正点原子的FreeRTOS例程进行学习的 欢迎各位大佬指责和批评 谢谢 一 事件定义 事件 事件集 与高数上的集合意义差不多 事件啊 其实是实现任务间通信的机制 主要用于实现多任务间的同步 但是事件类型
  • 啊哈C的简单使用

    打开啊哈C 新建一个程序输出hello world include
  • freeRTOS出现任务卡死的情况。

    最近在做一个产品二代升级的项目 代码是上一任工程师留下的 很多BUG 而且融合了HAL库和LL库 以及github上下载的GSM源码 很不好用 我这边是将2G模块换成了4G 且添加了单独的BLE模块 因此只在源码的基础上 去除2G和BLE代
  • FreeRTOS笔记(二)

    FreeRTOS笔记 二 静态任务 文章目录 FreeRTOS笔记 二 静态任务 一 任务定义 二 任务创建 2 1 定义任务栈 2 2 定义任务函数 2 3 定义任务控制块 2 4 实现任务创建函数 三 实现就绪列表 3 1 定义就绪列表
  • STM32 Freertos 添加 外部sram heap_5.c

    1 添加外部SRAM 初始化 2 添加heap 5 c 3 初始化heap 5 c 外部堆栈 Define the start address and size of the two RAM regions not used by the
  • FreeRTOS实时操作系统(三)任务挂起与恢复

    系列文章 FreeRTOS实时操作系统 一 RTOS的基本概念 FreeRTOS实时操作系统 二 任务创建与任务删除 HAL库 FreeRTOS实时操作系统 三 任务挂起与恢复 FreeRTOS实时操作系统 四 中断任务管理 FreeRTO
  • FreeRTOS 配置TICK_RATE_HZ

    我使用的是带有 5 4 版 FreeRTOS 的 MSP430f5438 我有一个有趣的问题 我无法弄清楚 基本上 当我将 configTICK RATE HZ 设置为不同的值时 LED 闪烁得更快或更慢 它应该保持相同的速率 我将 con
  • 如何更改 FreeRTOS 中任务的最大可用堆大小?

    我通过以下方式在任务中创建元素列表 l dllist pvPortMalloc sizeof dllist dlllist 有 32 字节大 我的嵌入式系统有 60kB SRAM 所以我希望系统可以轻松处理我的 200 个元素列表 我发现在
  • FreeRTOS 匈牙利表示法 [重复]

    这个问题在这里已经有答案了 我是 RTOS 和 C 编程的新手 而且我仍在习惯 C 的良好实践 因此 我打开了一个使用 FreeRTOS 的项目 我注意到操作系统文件使用匈牙利表示法 我知道一点符号 但面临一些新的 标准 FreeRTOS
  • 有可用的 FreeRTOS 解释语言库吗?

    我在一家公司工作 该公司使用 FreeRTOS 为多个设备创建固件 最近 我们对新功能的要求已经超出了我们固件工程师的工作能力 但我们现在也无力雇用任何新人 即使进行微小的更改 也需要固件人员在非常低的级别上进行修改 我一直在为 FreeR
  • C++ freeRTOS任务,非静态成员函数的无效使用

    哪里有问题 void MyClass task void pvParameter while 1 this gt update void MyClass startTask xTaskCreate this gt task Task 204

随机推荐

  • 【SLAM】VINS-fusion,VINS-Mono 编译,使用T265测试

    安装 VINS fusion VINS Mono 将vins mono 下载到ros工作空间catkin ws里面 span class token builtin class name cd span home jiangz catkin
  • 总结使用layui-laydate日历控件遇到的问题

    一 利用layui laydate日历控件选择月份 1 问题 选择月份时不能实现鼠标直接点击选择 xff0c 选择之后需要按 确定 原来的代码 html文件 xff1a span class token operator lt span d
  • 【pytorch】torch1.7.1安装、查看torch版本、GPU是否可用

    在conda 虚拟环境下安装torch 61 61 1 7 1 43 GPU版本 本机环境 CUDA 11 0Python 3 7 安装torch1 7 1 官网搜索确认需要下载的对应本机cuda的torch版本 xff0c 使用在线下载即
  • S.BUS协议

    内容 本篇文章主要是S BUS协议原理介绍 xff0c 并实现了arduino输出S BUS数据 S BUS简介 SBUS是一个接收机串行总线输出 xff0c 通过这根总线 xff0c 可以获得遥控器上所有通道的数据 目前很多模型及无人机电
  • window10+TensorRT-8.2.5.1+yolov5 v6.2 c++部署

    一 准备工具 1 1 visual studio下载安装 参考 xff1a vs2019社区版下载教程 xff08 详细 xff09 Redamancy 06的博客 CSDN博客 vs2019社区版 1 2 显卡驱动 43 cuda 43
  • openstack创造实例报:找不到有效有效主机,没有足够主机,而且点击实例还报500

    第一次接触openstack时候 xff0c 再创建实例时候右上方就弹出创建失败 实例 xxx 执行所请求操作失败 xff0c 实例处于错误状态 请稍后再试 错误 找不到有效主机 xff0c 原因是 没有足够的主机可用 这个问题是因为你的o
  • Kubernetes v1.21 发布,新版本主要特性

    美国时间 4 月 8 日 xff0c Kubernetes v1 21 正式发布 xff0c 这是 Kubernetes 在 2021 年发布的第一个版本 此版本包含 51 项增强功能 xff1a 13 项增强功能已进入稳定阶段 xff0c
  • 基于51单片机的火焰报警器

    基于51单片机的火焰报警器 一 模块描述 1 可以检测火焰或者波长在760纳米心1100纳米范围内的光源 2 探测角度60度左右 xff0c 对火焰光谱特别灵敏3灵敏度可调 图中蓝色数字电位器调节 3 对火焰的探测距离 跟灵敏度和火焰强度有
  • STM32的大小端模式——什么是大小端模式?

    目录 1 什么是大小端模式2 为什么会有大小端模式之分3 什么情况需要考虑大小端模式4 常见的设备的大小端模式5 测试大小端模式例程6 大小端转化例程 1 什么是大小端模式 大端模式Big Endian xff1a 高字节存于内存低地址 x
  • MDK KEIL 烧录STM32下载错误:Flash Timeout.Reset the Target and try it again.解决办法(芯片解锁 解除读报护)

    使用keil开发STM32点下载时出现下面的报错 xff1a 点确定后 xff1a 出现如上情况很可能是该芯片锁死 xff0c 即设置了读写保护 解决方法是想办法解锁芯片 xff0c 可以使用ST Link配合stlink utility软
  • Visual Studio 如何创建C/C++项目

    这里不说Visual Studio安装过程 xff0c 默认已经安装好软件 对Visual Studio安装有疑问的可以参考 xff1a Visual Studio安装教程 1 打开软件Visual Studio xff0c 点击创建新项目
  • Visual Studio安装教程

    本文章主要记录Visual Studio2019的安装过程 xff0c 由于只用于开发C C 43 43 xff0c 因此关于其它语言支持将不安装 xff0c 仅供参考 一 下载 1 下载地址在微软官网 xff1a 微软官网 2 选择菜单栏
  • CAN波形解析实例(1)

    这里的CAN数据波形抓取的是两个STM32F103设备通过CAN通信一方发送另一方接收 xff0c CAN收发器使用的是TJA1051 xff08 扩展帧发送数据ID 61 0x18DAF110 Data 61 0x06 0x08 xff0
  • GPIO推挽输出和开漏输出模式区别详解

    以STM32参考手册中的GPIO输出配置图为例 xff1a 看到输出驱动器虚线框中的内容 xff0c 输出驱动器中的P MOS和N MOS两个MOS管就是实现推挽输出和开漏输出的关键 推挽输出模式下 xff0c P MOS和N MOS都正常
  • 树莓派新手入门教程

    截至目前 20210405 xff0c 树莓派最新版本为4B xff0c 如下图所示 xff1a 树莓派3B 43 的主要的部件位置 xff1a 下载最新Raspbian系统镜像 1 首先进入树莓派官网 xff1a https www ra
  • 公司研发工具链体系化建设,帮助公司从混乱走向正规

    一 软件发布平台 没办法对外提供二进制库下载便捷方式不方便部署 xff0c 不同版本之间预览和说明性欠缺问题 可以参考的解决方案 xff1a https www cnblogs com djlsunshine p 11164770 html
  • 关于写代码的几个看法

    最近在新公司负责bug的修复 xff0c 发现很多的代码逻辑理解起来有些困难 现在将其中观察到的现象列出来 xff0c 谈谈自己的看法 1 类过大 对于代码来说 xff0c 我们在编写的时候最好做到SRP Single Responsibi
  • 树莓派VNC server设置开机自启动

    目前已测试OK的几个方法 xff1a 方法1 xff08 作为服务自启动 xff09 xff1a 在 etc init d 中创建一个文件 例如tightvncserver xff1a span class token function s
  • 一个结构体 = 另一个结构体(同类型结构体之间可直接赋值操作)

    两个同类型结构体变量可以直接赋值 xff0c 不同类型结构体不能直接赋值 span class token macro property span class token directive hash span span class tok
  • FreeRTOS任务切换过程深层解析

    FreeRTOS 系统的任务切换最终都是在 PendSV 中断服务函数中完成的 xff0c uCOS 也是在 PendSV 中断中完成任务切换的 为什么用PendSV异常来做任务切换 PendSV 可以像普通中断一样被 Pending xf