【FreeRTOS】从0写简易RTOS实现任务切换

2023-05-16

1. RTOS引入

单片机性能越来越强,很多Linux程序在单片机上也可以运行了:这需要RTOS。

我们要开发的单片机产品,功能也越来越丰富:这也需要RTOS。

就个人技术发展来说,单片机开发的技术提升方向之一就是RTOS。

RTOS已经无处不在:

  • ESP8266 WIFI模块,出厂自带FreeRTOS,可以在上面做二次开发;
  • 4G模块CAT1,出厂自带FreeRTOS,可以在上面做二次开发;
  • 想实现功能比较丰富的设备时,比如加上MQTT功能,就需要RTOS
  • 比如已经被RT-Thread采用的kawaii-mqtt,默认就不支持裸机
  • 你去看所有的智能设备:小度音箱、小爱闹钟、家居摄像头,都使用RTOS。

2. RTOS必需的几个文件

2.1 start.S

开发板上电运行的第一个文件。必需的文件之一。

  1. 首先需要先设置异常向量表。
    • 开发板复位后,找到第一条执行的指令。
    • 作为实时操作系统,肯定要实现任务切换的功能,而任务切换需要依赖于中断,因此,当中断发生时,需要硬件跳转到中断发生的异常地址处
  2. 初始化内存,必需操作之一
  3. 初始化串口
    • 用于调试
    • 用于观察程序的运行情况
  4. 初始化时钟
    • 为了实现任务切换的功能,采用时间片轮转的方式进行任务切换,假定当一个任务运行1ms后,发生时钟中断,俗称tick中断,确保系统的心跳。
    • 由于需要实现,每1ms进行一次任务切换,因此,任务启动的功能以及任务切换的功能就需要在时钟中断的函数中实现
  5. 跳转至main函数

上述功能作为RTOS的基础,必须要实现的

/* 设置异常向量表 */
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler
                DCD     0                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     0          ; MPU Fault Handler
                DCD     0           ; Bus Fault Handler
                DCD     UsageFault_Handler_asm         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     0           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     0             ; PendSV Handler
                DCD     SysTick_Handler_asm            ; SysTick Handler
				AREA    |.text|, CODE, READONLY

/* 上电运行的第一个函数 */
; Reset handler
Reset_Handler   PROC
				EXPORT  Reset_Handler             [WEAK]
                IMPORT  mymain

				IMPORT SystemInit
				IMPORT uart_init
				IMPORT UsageFaultInit
				IMPORT SysTickInit
				IMPORT LedInit
				
				/* 由于会用到C函数,因此必须设置一个栈,在内存的顶部随便找了一个地方 */
				LDR SP, =(0x20000000+0x10000)
				/* 初始化内存,内存中的各个分区 */
				BL SystemInit
				/* 初始化串口 */
				BL uart_init
				/* 初始化中断控制器 */
				BL UsageFaultInit
				
				LDR R0, =0
				LDR R1, =0x11111111
				LDR R2, =0x22222222
				LDR R3, =0x33333333
				LDR R12, =0x44444444
				LDR LR, =0x55555555
				
				DCD 0xffffffff
					
				SVC #1
				
				/* 初始化系统时钟,并设置时钟中断1ms */
				BL SysTickInit
				
				/* 点灯指示 */
				BL LedInit

				/* 跳转到main函数执行,可以看到主函数的名字不一定非要设置为 "main" */
				;BL mymain
				LDR R0, =mymain
				BLX R0

                ENDP

2.2 task.c

在该文件中包含了任务创建、开始任务(开始调度)、获得当前任务栈、任务调度等,是RTOS中关键的主要文件。

2.2.1 创建任务 create_task

所谓的任务环境,指的就是当该任务运行时,寄存器中的值

首先,我们能想到的

  • 新创建出来的任务无非是为了运行一个新的程序,而且这个新程序不需要返回
  • 创建的任务也是一个C函数,因此需要有地方来存放自己的环境,比如局部变量,任务传入参数以及多个寄存器
  • 任务的现场就是运行该任务时的寄存器,因此,当任务切换时,为了下次还能接着运行,需要将切换任务时寄存器的状态保存起来,需要保存到自己的任务栈里面,当下次调度到自己时,直接将栈里的环境弹出,恢复环境就可以接着运行了。
  • 为了可以让任务参与调度,当创建任务时,任务的寄存器还没有使用,因此,我们需要替他伪造一个环境,让其可以参与调度,这样我们的任务就可以跟上其他的任务一起参与调度了。

所以,创建任务的关键就是伪造任务的环境

void create_task(task_function f, void *param, char *stack, int stack_len)
{
	int *top = (int *)(stack + stack_len);
	
	/* 伪造现场 */
	top -= 16;
	
    /* 新创建任务的栈的结构如下图所示 */
	/* r4~r11 */
	top[0] = 0; /* r4 */
	top[1] = 0; /* r5 */
	top[2] = 0; /* r6 */
	top[3] = 0; /* r7 */
	top[4] = 0; /* r8 */
	top[5] = 0; /* r9 */
	top[6] = 0; /* r10 */
	top[7] = 0; /* r11 */
	
	/* r0~r3 */
	top[8]  = (int)param; /* r0 */
	top[9]  = 0; /* r1 */
	top[10] = 0; /* r2 */
	top[11] = 0; /* r3 */
	
	/* r12,lr */
	top[12] = 0; /* r12 */
	top[13] = 0; /* lr */
	
	/* 返回地址 */
	top[14] = (int)f; /* 任务入口 */
	
	/* PSR */
	top[15] = (1<<24); /* psr 使用thumb指令集 */	
	
	/* 记录栈的位置 */
	task_stacks[task_count++] = (int)top;
}

在这里插入图片描述

2.2.2 开始调度 start_task

开始调度就相当于调度器的作用,当执行了start_task后,所有的任务开始运行,调度器是一个死循环,之后等待时钟中断的发生,对任务进行调度

void start_task(void)
{
    /* 设置一个标志位 */
	task_running = 1;
	while (1);
}

2.3 main.c

在main函数中,创建了三个任务,用于模拟任务的调度。

为这三个任务分别分配了1024字节的空间,三个任务的栈并不会有交集,因此,三个任务各有自己的环境

void task_a(void *param)
{
	char c = (char)param;
	while (1)
	{
		putchar(c);
	}
}

void task_c(void *param)
{
	int i;
	int sum = 0;
	
	for (i = 0; i <= 100; i++)
		sum += i;
	
	while (1)
	{
		put_s_hex("sum = ", sum);
	}
}

int mymain()
{
	create_task(task_a, 'a', stack_a, 1024);
	create_task(task_a, 'b', stack_b, 1024);
	create_task(task_c, 0, stack_c, 1024);
	start_task();
	return 0;
}

任务a和任务b虽然使用的是同一个函数,但是传入的参数是不一样的,而且栈也是不同的,因此是属于不同的任务。

任务a:一直打印字符 ‘a’

任务b:一直打印字符 ‘b’

任务c:计算0-100的和

此时的栈分布如下图

3. 启动任务

在main函数中,我们已经创建了三个任务,并伪造了他们的环境,是时候启动任务了。

根据前面我们知道,在执行了start_task后,程序陷入了死循环中,此时只有中断能拯救,也就是时钟中断,即系统滴答。

在时钟设置时,设置了每1ms触发一次中断,当中断发生时,将会跳转到异常向量表的位置执行中断,

__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler
                DCD     0                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     0          ; MPU Fault Handler
                DCD     0           ; Bus Fault Handler
                DCD     UsageFault_Handler_asm         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     0           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     0             ; PendSV Handler
                DCD     SysTick_Handler_asm            ; SysTick Handler
				AREA    |.text|, CODE, READONLY

就是SysTick_Handler_asm这个函数

SysTick_Handler_asm PROC

				; 在这里保存R4~R11
				STMDB sp!, { r4 - r11 }
				STMDB sp!, { lr }

				MOV R0, LR ; LR是一个特殊值
				ADD R1, SP, #4
				BL SysTick_Handler  ; 这个C函数保证不破坏R4~R11
				
				LDMIA sp!, { r0 }
				
				LDMIA sp!, { r4 - r11 }
				
				BX R0
				
				ENDP

                END

在这个函数中,上来先保存了R4~R11中的寄存器,此时要注意一个问题!

在硬件上,会自动帮我们先保存下面的寄存器即,R0~R3、R12、LR、返回地址、PSR,因此,在上面的函数中我们只保存了R4~R11,而且由于硬件帮我们自动保存了一部分东西,我们就要考虑怎么才能让他恢复呢?这时,就需要将LR设置为一个特殊值,当我们利用LR返回时,如果发现是一个特殊值,我们就需要恢复硬件保存的寄存器。

  • 硬件帮我们自动保存**R0~R3、R12、LR、返回地址、PSR**
  • 硬件保存后,LR寄存器被设置为一个特殊值

因此,基于上面两点,我们在汇编程序中,只保存了R4~R11以及LR寄存器
在这里插入图片描述

; 在这里保存R4~R11
				STMDB sp!, { r4 - r11 }
				STMDB sp!, { lr }

				MOV R0, LR ; LR是一个特殊值
				ADD R1, SP, #4
				BL SysTick_Handler  ; 这个C函数保证不破坏R4~R11

仔细理解一下上面的指令,在保存完寄存器后,后面接着需要运行一个C函数,传入的C函数根据AAPCS规范,汇编语言向C语言传入参数时,第一个参数保存在R0,第二个参数保存在R1,第三个参数保存在R2,第四个参数保存在R3。当多于四个时,就需要用栈来传递参数了。

因此上面的指令中,将LR以及SP传给了C函数,栈的分布如下
在这里插入图片描述
跳转到C函数中继续执行,下面分析下C函数的作用

int is_task_running(void)
{
	return task_running;
}

void SysTick_Handler(int lr_bak, int old_sp)
{
	int stack;
	int pre_task;
	int new_task;
	
	SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
		
	/* clear exception status */
	SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;

	/* 如果还没有创建好任务, 直接返回 */
	if (!is_task_running())
	{
		return;  // 表示无需切换
	}
	
	/* 启动第1个任务或者切换任务 */
	if (cur_task == -1)
	{
		/* 启动第1个任务 */
		cur_task = 0;
		
		/* 从栈里恢复寄存器 */
		/* 写汇编 */
		stack = get_stack(cur_task);
		StartTask_asm(stack, lr_bak);
		
		return ; /* 绝对不会运行到这里 */
	}
    
    ······
}
  1. 因为,时钟中断可能随时发生,也可能在任务没有创建就已经发生了,因此,需要先判断是否已经创建好了任务,才能继续向下运行。
  2. cur_task = -1时,就意味着,因为刚刚创建好,还没有执行任务,因此接下来的事情就是将任务栈中的寄存器,全部恢复
    • 首先需要获取到该任务的栈
    • 开始任务
#define TASK_COUNT 32

static int task_stacks[TASK_COUNT];
static int task_count;

int get_stack(int task_index)
{
	return task_stacks[task_index];
}

可以看到,分配了一个数组,在这个数组中,我们在创建任务时,将任务栈顶保存在了这个数组中,因此,我们只需要获取到栈顶位置就行了

注意上面的StartTask_asm函数,需要传入两个参数,第一个参数是栈顶的位置,另一个参数是LR的值,此时的LR是一个特殊值

/* 从栈里恢复寄存器 */
/* 写汇编 */
stack = get_stack(cur_task);
StartTask_asm(stack, lr_bak);

在启动任务中,StartTask_asm是一个关键函数,下面按照流程看下这个函数

StartTask_asm PROC
				
				; 从任务的栈里把R4~R11读出来写入寄存器
				; r0 : 保存有任务的栈				
				; r1 : 保存有LR(特殊值)
				LDMIA r0!, { r4 - r11 }
				
				; 更新SP
				MSR MSP, R0
				;MOV SP, R0
				
				; 触发硬件中断返回: 它会把栈里其他值读出来写入寄存器(R0,R1,R2,R3,R12,PSR)
				BX R1
				
				ENDP

程序中已经进行了注释,LDMIA指令,将栈中的寄存器一个个恢复。**R0~R3、R12、LR、返回地址、PSR**由硬件恢复,当执行完之后,该任务的环境就全部恢复了,此时的CPU就会运行这个任务
在这里插入图片描述
任务启动完毕!
在这里插入图片描述
由于没有切换任务,此时只有任务a在疯狂运行。

4. 任务切换

任务切换是重点了,但是并没有想象中的复杂

任务切换的流程与任务启动的流程很类似,任务启动是由于cur_task == -1才会启动任务,而任务切换cur_task已经设置了,因此走的是另外一个分支

void SysTick_Handler(int lr_bak, int old_sp)
{
	int stack;
	int pre_task;
	int new_task;
	
	SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
		
	/* clear exception status */
	SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;

	/* 如果还没有创建好任务, 直接返回 */
	if (!is_task_running())
	{
		return;  // 表示无需切换
	}
	
	/* 启动第1个任务或者切换任务 */
	if (cur_task == -1)
	{
		/* 启动第1个任务 */
		cur_task = 0;
		
		/* 从栈里恢复寄存器 */
		/* 写汇编 */
		stack = get_stack(cur_task);
		StartTask_asm(stack, lr_bak);
		
		return ; /* 绝对不会运行到这里 */
	}
	else
	{
		/* 切换任务 */
		// 取出下一个任务
		pre_task = cur_task;
		new_task = get_next_task();
		
		if (pre_task != new_task)
		{			
			/* 保存 pre_task: 在汇编里已经保存了 */
			/* 更新sp */
			set_task_stack(pre_task, old_sp);
			
			/* 切换 new_task */
			stack = get_stack(new_task);
			cur_task = new_task;
			StartTask_asm(stack, lr_bak);
		}
	}
	
}

可以看到任务启动和任务切换走的是不同的分支,在任务切换分支,由于需要切换任务,因此需要找到下一个任务的栈是什么,我们才能恢复下一个任务的环境。

int get_next_task(void)
{
	int index = cur_task;
	index++;
	if (index >= task_count)
		index = 0;
	return index;
}

为了简化过程,我们依次取出任务

if (pre_task != new_task)
{
    ····
}

判断一下,之前任务的栈与新任务栈是否一致,不一致就意味着需要切换任务了。切换任务的本质就是保存当前栈与恢复新任务栈而已

在切换之前,我们需要先保存一下当前栈的位置,下次切换到时可以直接获得

void set_task_stack(int task, int sp)
{
	task_stacks[task] = sp;
}

前面讲过,在这个数组中我们保存的就是栈顶的位置。

栈保存完了,后面就是易主

获取新任务的栈

int get_stack(int task_index)
{
	return task_stacks[task_index];
}

仔细想一下,之前的栈已经保存起来了,新任务的栈也准备好了,这个状态是不是与任务启动时栈的状态一样了!

因此很巧妙,我们可以直接调用任务启动的函数了!
在这里插入图片描述
当再次切换到这个任务时,保持之前切换前的寄存器,程序继续向下运行,恢复时钟中断发生前寄存器的状态,此时一个时钟中断就处理完了,要知道这个整个过程1ms运行一次,因此在CPU运行时,1ms就反复进行任务切换了。

可以看到,三个任务在飞速切换中…

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

【FreeRTOS】从0写简易RTOS实现任务切换 的相关文章

  • 虚拟串口模拟器和串口调试助手使用教程

    虚拟串口 xff08 虚拟 COM 端口 xff09 xff0c 应该很多人都知道 xff0c 也就是一种模拟物理串行接口的 软件 它完全复制了硬件 COM 接口的功能 xff0c 并且将被操作系统和串行应用程序识别为真实端口 以前的电脑
  • 在Ubuntu上安装Boost的五种方法(全网最全,建议收藏)

    问题描述 我在Ubuntu上 xff0c 并且想安装Boost 我尝试过 sudo apt get install boost 但是没有这样的软件包 在Ubuntu上安装Boost的最佳方法是什么 xff1f 最佳方法 您可以使用apt g
  • 编程入门指南:零基础如何自学编程?

    注明一下 xff1a 本文适用于零基础 xff0c 想自学编程的伙伴 xff0c 重点在于盘清底层逻辑以及整理一些学习途径 编程学习如何入门 xff1f 第一步 xff1a 做好心理建设 首先要告诉你一个坏消息 xff1a 自学编程的弯路多
  • Clion中控制台中文出现乱码怎么解决?

    为什么会出现乱码 xff0c 肯定是字符的编码方式不匹配导致的 我们知道 xff0c 现在比较新一点的编辑器和IDE基本都是默认UTF 8编码了 xff0c 但是在Windows下 xff0c 控制台中的中文编码格式是GBK xff0c 因
  • Pandas教程(非常详细)

    文章目录 教程特点阅读条件 Pandas是什么Pandas主要特点Pandas主要优势Pandas内置数据结构 Pandas库下载和安装Windows系统安装Linux系统安装1 Ubuntu用户2 Fedora用户 MacOSX系统安装
  • Pygame教程(非常详细)

    文章目录 教程特点阅读条件 Pygame是什么扩展知识 Pygame下载和安装1 pip包管理器安装2 二进制安装包安装 第一个Pygame程序初始化程序创建Surface对象事件监听游戏循环 Pygame Display显示模块详解Pyg
  • 如何系统地入门学习stm32?

    来自 xff1a https www zhihu com question 46616925 心得 xff1a 本人当初学习STM32的时候有一些跟风的因素 xff0c 自以为学的芯片越多就越厉害 61 61 其实 xff0c 学习嵌入式重
  • 一口气从零读懂CAN总线以及应用

    在各种总线通信中 xff0c 很多都是点对点的通信方式 xff0c CAN总线是一种去中心化的多主控通信方式 xff0c 在汽车领域应用很多 xff0c 因此有必要了解一下 概要 上世纪八十年代以来 xff0c 汽车ECU越来越多 xff0
  • 开源免费录屏和直播软件OBS Studio教程

    转载于 xff1a https zhuanlan zhihu com p 107720665 OBS Studio是目前比较主流的免费开源录屏和直播软件 xff0c 它提供了丰富的功能特性 xff0c 可以媲美一些受欢迎的同类商业软件 如果
  • KY-RTI分布仿真技术教程

    已剪辑自 https blog csdn net sillysunny article details 84197424 第一章 简介 高层体系结构 xff08 High Level Architecture xff0c HLA xff09
  • 自动化测试框架知识,读这一篇就够了!怎么构建属于自己的自动化测试框架

    已剪辑自 https segmentfault com a 1190000022693251 自动化测试因其节约成本 提高效率 减少手动干预等优势已经日渐成为测试人员的 潮流 xff0c 从业人员日益清楚地明白实现自动化框架是软件自动化项目
  • 代理模型优化算法

    已剪辑自 https zhuanlan zhihu com p 99609634 微信公号 xff1a Mat物语科研数据分析 阅读本文最好需要提前了解 xff08 一点点 xff09 的一些知识点 xff08 不懂也可以阅读 xff09
  • Maven常用知识梳理总结

    一 xff0e Maven简介 Maven的本质是一个项目管理工具 xff0c 将项目开发和管理过程抽象成一个项目对象模型 POM 将java项目看成是一个对象来进行管理 POM Project Object Model xff1a 项目对
  • 让数据变得更直观:10款常用的可视化工具(解决99%的可视化大屏需求)

    不管是跟上司汇报工作 xff0c 还是向客户介绍项目 xff0c 在工作中各种与 汇报 有关的事情 xff0c 都离不开图表 漂亮而有视觉冲击力的图表可以给你的工作大大加分 xff0c 让对方直观理解你想表达的内容 xff0c 而大屏能将图
  • CMake 编译选项设置

    在CMakeLists txt中可以通过修改CMake内置的环境变量来改变C或C 43 43 的编译选项 编译选项相关的CMake 变量如下 xff1a CMAKE C FLAGS span class token operator 61
  • 阿里云服务器盘镜像备份恢复到本地VMware

    步骤如下 xff1a 1 阿里云创建镜像并下载镜像文件到本地 xff1b 2 使用qemu img工具转换镜像文件为vmdk 格式 3 将转换后的文件挂载到VMware上然后运行 xff1b 4 运行后修改账号密码 IP等信息 xff1b
  • 树莓派系统入门教程(一)—— 烧录系统镜像,配置系统信息

    烧录系统镜像 一 准备工作二 烧录镜像2 1 格式化内存卡2 2 烧录镜像 xff08 Raspberry Pi Imager xff09 2 3 烧录镜像 xff08 Win32DiskImager xff09 一 准备工作 1 材料准备
  • LCD1602按下复位后乱码的问题

    1602按下后会有乱码的问题 xff0c 其实根本上是硬件引起的 某些最小系统板的复位电路没做好 xff0c 按下复位键后 xff0c 就会产生乱码 但是出现这种现象 xff0c 是可以用软件后期修正的 由于LCD复位后默认是8线输入 xf
  • Linux VNC server 安装配置

    一 服务端操作 0 打开终端 xff0c 切换到目标账户 su xie 1 打开终端使用yum命令安装vnc sever yum install tigervnc server y 2 设置 vnc server 开机启动 chkconfi
  • IDEA出现 java.lang.NoSuchMethodError 错误的原因及解决方法

    出现 java lang NoSuchMethodError 错误的原因及解决方法 问题分析 xff1a 出现这种情况 xff0c 一般是存在jar冲突 xff0c 简单的说就是导入了两个相同名称的jar xff0c 系统不知道用哪个 解决

随机推荐

  • VNC 桌面没有图标解决办法

    VNC 桌面没有图标解决办法 安装 sudo apt get install gnome core xfce4 打开xstartup文件 xff08 前提已安装VNC xff09 命令 xff1a vim vnc xstartup 操作 x
  • DELL R730服务器安装centos7.3教程

    服务器安装centos7 3系统分为两步 第一步 xff0c 对磁盘进行分组并部署raid 第二步 xff0c 通过系统安装程序安装操作系统 xff0c 并进行系统安装配置 第一步 xff0c RADI的分组部署 将服务器开机 xff0c
  • 访问控制ACL

    ACL访问控制列表可以对具体的用户或组设置权限 root 64 localhost useradd tony tom root 64 localhost passwd tony root 64 localhost passwd tom ro
  • NFS与自动挂载

    1 NFS共享文件server端的配置 xff08 server ip xff1a 172 16 8 11 xff09 root 64 localhost getenforce Enforcing root 64 localhost set
  • SAP主题改变方法

    背景 xff1a SAP7 6有很多主题 xff0c 然后按以下的方法可以改变 xff08 我比较习惯blue这个主题目 xff0c 7 6版本现在默认不是这个主题目 xff09 文章目录 方法一 通过登录后设置步骤1步骤2 方法二 通过程
  • SMB文件共享

    1 windows server端配置 C Users wll gt net share 共享名 资源 注解 C C 默认共享 D D 默认共享 IPC
  • swap空间扩容

    1 查看现有swap空间大小 root 64 centos7 free total used free shared buff cache available Mem 2028116 389108 1266584 12644 372424
  • Linux 卸载及删除磁盘分区

    1 卸载分区 root 64 centos7 df Th Filesystem Type Size Used Avail Use Mounted on dev mapper centos root xfs 17G 4 0G 14G 24 d
  • 磁盘阵列raid5的创建及管理

    1 在虚拟机上添加4个磁盘 xff0c 其中主用盘3个 xff0c 热备盘1个 root 64 centos7 fdisk l Disk dev sda 21 5 GB 21474836480 bytes 41943040 sectors
  • Keil关于.axf文件报错

    项目场景 xff1a 熟悉Keil C51的同学在使用Keil MDK编译STM32系列单片机时会更容易上手 Keil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统 xff0c 与汇编相比 xff0c
  • 《java核心技术卷1》部分章节读书笔记

    目录 3 java基本的程序设计结构3 1 命名规范3 2 数据类型3 3 运算符3 4 枚举类型 3 5 字符串3 6 大数值 BigInteger和BigDecimal3 7 数组 4 对象与类4 1 识别类4 2 类之间的关系4 3
  • linux 问题-——退出vi编辑器 wq失效

    退出linux的vi编辑器时 xff0c 先按左上角的esc按键 xff0c 再输出输入命令 保存不退出w强制保存但不退出w 保存并退出wq强制保存并退出wq xff01 不保存退出q不保存并强制退出q 出现以下问题 xff1a esc退出
  • SUMO中车辆类型的定义及路由文件的写法

    车辆类型的定义 在SUMO中 xff0c 通过vType标签来定义车辆的类型 xff0c 一般可以写成如下形式 xff1a span class token operator lt span vType id span class toke
  • 为什么c语言允许直接访问物理地址?

    C语言允许直接访问物理地址 xff0c 是因为它是一种面向底层的语言 xff0c 提供了底层的硬件操作和系统调用的能力 在C语言中 xff0c 可以通过指针变量直接访问内存地址 xff0c 从而对硬件进行底层控制和操作 在一些应用场景下 x
  • TortoiseGit 安装和使用

    TortoiseGit是一款适用于Windows系统的开源的git版本控制客户端 xff0c 其优点是使用图像化界面操作 xff0c 而不需要记住或使用Git命令 xff0c 从而增加开发效率 xff0c 对不熟悉命令的使用者比较友善 下面
  • 解放拖动屏幕的双手——用xrandr配置多屏显示

    操作系统 xff1a Arch Linux 桌面系统 xff1a Xfce 又是一次会议的结束 xff0c 待我回办公室把大屏幕接上 xff0c 准备继续干 等等 xff0c 这是怎么回事 xff1f 打开系统的Display设置一看 xf
  • 深入浅出BGP

    文章目录 深入浅出BGP说明一 BGP的产生1 1 动态路由的分类1 2 BGP概述 二 与IGP的区别三 BGP核心3 1 属性3 1 1 属性特点 3 2 选路规则3 3 对等体 深入浅出BGP 说明 此篇主要对BGP的产生 与IGP的
  • LVM跨主机迁移

    LVM跨主机迁移 虚拟机 实验环境 两台Centos 7 8虚拟机 xff0c IP xff1a 192 168 221 199 200 查看lv test中的数据 使用命令lsblk xff0c 确认逻辑卷vg下挂有两个分区均为sdb磁盘
  • CodeBlocks 20.03安装&汉语化&找不到编译器

    一 CodeBlocks下载 下载地址 xff1a https www codeblocks org downloads binaries 到如下界面选择下载codeblocks 20 03mingw setup exe xff0c 这个里
  • 【FreeRTOS】从0写简易RTOS实现任务切换

    1 RTOS引入 单片机性能越来越强 xff0c 很多Linux程序在单片机上也可以运行了 xff1a 这需要RTOS 我们要开发的单片机产品 xff0c 功能也越来越丰富 xff1a 这也需要RTOS 就个人技术发展来说 xff0c 单片