前言

自己做的答案,仅供参考,欢迎指正。

第一题

(1)复位的原理:根据本地ROM的存储内容对必要的寄存器、I/O接口等资源的值和状态进行初始化。

复位的目的:完成硬件初始化操作,为系统正常运行做好准备。

上电复位、看门狗复位、软件复位的异同:

相同点:都会将硬件设置为一个已知的、稳定的状态,使系统从头开始正常运行。

不同点:

① 触发条件不同:上电复位的触发条件为电源电压达到可以正常工作的阈值电压;看门狗复位的触发条件为监控到程序异常或操作错误或系统"跑飞",即没有及时"喂狗"导致的看门狗溢出;软件复位的触发条件为执行跳转到程序入口的指令或软件指交置位。

② 应用场景不同:上电复位用于系统启动的初始化,看门狗复位用于程序异常或操作错误或系统"跑飞",软件复位用于错误恢复或用户请求重启。

③ 控制系统不同:上电复位完全由硬件控制,看门狗复位由软件和硬件共同控制("喂狗"操作为软件),软件复位完全由软件控制。

STC-B学习板有7种复位方式:外部RST引脚复位,软件复位,上电复位,内部低压检测复位,MAX810专用复位电路复位,看门狗复位以及程序地址非法复位。

STM32:外部NRST引脚低电平复位、看门狗复位、软件复位、低功耗管理复位、电源复位(上电复位+退出待机模式时复位)、备份域复位。

(2)STC-B板使用了消抖设计,如下左图红框处为一个RC消抖电路。可以用施密特触发器来修改相关设计,如下右图红框处。

STM32板同样使用了消抖设计,由于STM32的按键部分与STC-B 相同,因此不再赘述。

第二题

(1)最小系统是指一个仅具有进入正确执行模式所需最少资源的系统。最小系统硬件包括了嵌入式处理器、片上/片外存储器以及电源供电、复位、时钟等外围辅助电路。

思想方法:在设计和构建系统时,去除不必要的复杂性,只保留对于实现目标功能至关重要的部分。

价值:通过设计和验证最小系统,可以掌握以特定型号处理器为核心的嵌入式硬件设计方法,并为进一步的功能、接口、总线扩展奠定基础。

(2)计算机网络课程中将网络分为应用层、运输层、网络层、链路层、物理层,每层具有不同的功能和实现,也具有不同的协议。使用分层思想,可以对每个层次进行独立的开发和测试,当对某一层做出改变时,不用更改其他层。例如IPv4协议升级为IPv6协议,不需要对其他层的协议做出更改。

而本课程中STM32相关实验的HAL库也使用了分层思想,将硬件层、I/O操作层与应用层软件进行模块化分离,我们只需要调用相关函数而不需要直接对硬件进行操作。

第三题

(1)GPIO允许用户根据不同的功能需求进行动态配置和管理,而专用接口/引脚,如SPI、I2C、LART等,有特定的协议和用途,不能像GPIO一样灵活配置。

GPIO的特点:①有多种输入输出模式。②初始化过程简单。③速度较慢。

GPIO的使用:

①设置GPIO的模式为输入或输出。

②根据模式配置上拉电阻和下拉电阻

③进行数据读取或写入操作。

(2)

第四题

(1)裸机软件启动过程:

STM32的Boot Loader位于系统内存中。上电复位后,Boot Loader代码执行一系列基本的硬件初始化工作,然后根据BOOT [1:0]引脚的值选择从主闪存、系统内存还是SRAM启动主程序。主程序的入口点通常是main函数。(好像不对)

嵌入式OS启动过程:

上电复位后,Boot Loader代码执行一系列基本的硬件初始化工作,然后将外部存储器中的操作系统内核映像及根文件系统映像复制到内存中的代码和数据空间,设置内核启动参数。最后,跳转至内核入口地址开始执行。

(2)BSP的功能:封装硬件初始化及不同模块基本功能代码。

BSP基本组成:每个程序都必须包含的sys模块,提供系统硬件初始化代码接口的MySTC_init函数、系统调度函数MySTC_OS函数以及各种用户回调函数;根据实现功能进行选择的各个模块,均提供硬件设置接口和其他具体功能接口。

开发新的BSP:可能需要在sys模块中增加可设置和触发回调函数的事件。

(3)CMSIS与STM32 HAL的关系:

CMSIS 是 HAL的基础,HAL 在 CMSIS 的基础上进一步抽象,进行扩展和定制。CMSIS提供了更底层的硬件访问,而HAL提供了更易于使用的接口。

在实验中使用过HAL。例如在配置LED灯亮灭时,通过HAL_GPIO_WritePin 函数来设置对应引脚的输出,从而控制LED灯的亮灭。

第五题

 (1)微内核结构:微内核中仅实现基本的、必要的操作系统功能,如任务管理、任务间通信、存储管理、中断响应与分发等功能,而设备驱动、网络通信、文件系统、图形界面等作为外围扩展组件运行在用户模式中。

宏内核结构:宏内核将所有系统服务,如文件系统、设备驱动等,都集成在内核空间中。

微内核,如MINIX和QNX,优点是①可靠性高:一个模块出现错误时,不会影响整个系统;②可扩展性好:添加新的功能只需要增加新的模块,不需要修改内核代码。缺点是①性能低,需要在用户态和内核态之间切换,处理速度较慢;②设计相对复杂,实现和维护难度较大。

宏内核,如Linux,优点是①性能高,处理速度快;②设计相对简单,易于实现和维护。缺点是①可靠性低,内核出现错误会导致系统崩溃;②可扩展性差,添加新的功能需要修改内核代码,容易引入新的错误。

(2)RTOS常用的任务调度有单调速率调度(RMS)、最早截止期调度(EDF)、轮转调度(RR)等。

单调速率调度(RMS)根据任务的周期分配优先级。周期越短,优先级越高。C代码:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

// 定义任务结构体
typedef struct {
    int taskId;        // 任务ID
    int period;        // 任务周期
    int executionTime; // 任务执行时间
    int remainTime;    // 任务剩余执行时间 
    bool isfinish;     // 任务是否执行完毕 
} Task;

// 比较函数,用于排序
int compareTasks(const void *a, const void *b) {
    Task *taskA = (Task *)a;
    Task *taskB = (Task *)b;
    //任务完成时优先级最低
    if(taskA->isfinish && taskB->isfinish) return 0;
    else if (taskA->isfinish) return 1;
    else if (taskB->isfinish) return -1;
    else  return taskA->period - taskB->period;
}

// 调度函数
int scheduleTasks(int t, Task tasks[], int numTasks) {
	for(int i = 0; i < numTasks; i++){
		if(tasks[i].remainTime == 0)
        	tasks[i].isfinish = true;
		if(t % tasks[i].period == 0){
			tasks[i].isfinish = false;
			tasks[i].remainTime = tasks[i].executionTime;
		}
	}
    // 根据任务周期对任务进行排序
    qsort(tasks, numTasks, sizeof(Task), compareTasks);
    
	tasks[0].remainTime --;
    return tasks[0].taskId;
}

int main() {
    // 初始化任务
    Task tasks[] = {
        {1, 4, 1, 1, false},
        {2, 5, 2, 2, false},
        {3, 20, 5, 5, false}
    };
    int numTasks = sizeof(tasks) / sizeof(Task);

    // 打印调度结果
    for (int i = 0; i < 20; i++) {
    	int id = scheduleTasks(i, tasks, numTasks);
        printf("Task %d is running\n", id);
    }
    return 0;
}

 最早截止期调度(EDF)根据每个时刻任务的绝对截止期限分配优先级,每个时刻任务的优先级可能变化。C代码:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

// 定义任务结构体
typedef struct {
    int taskId;        // 任务ID
    int period;        // 任务周期
    int executionTime; // 任务执行时间
    int remainTime;    // 任务剩余执行时间 
    int deadline;      // 任务绝对截止期限 
    bool isfinish;     // 任务是否执行完毕 
} Task;

// 比较函数,用于排序
int compareTasks(const void *a, const void *b) {
    Task *taskA = (Task *)a;
    Task *taskB = (Task *)b;
    if(taskA->isfinish && taskB->isfinish) return 0;
    else if (taskA->isfinish) return 1;
    else if (taskB->isfinish) return -1;
    else  return taskA->deadline - taskB->deadline;
}

// 调度函数
int scheduleTasks(int t, Task tasks[], int numTasks) {
	for(int i = 0; i < numTasks; i++){
		if(t % tasks[i].period == 0){
			tasks[i].isfinish = false;
			tasks[i].remainTime = tasks[i].executionTime;
		}
	}
    // 根据任务周期对任务进行排序
    qsort(tasks, numTasks, sizeof(Task), compareTasks);
    
	tasks[0].remainTime --;
	if(tasks[0].remainTime == 0){
			tasks[0].isfinish = true;
			tasks[0].deadline = tasks[0].deadline + tasks[0].period;
		}
    return tasks[0].taskId;
}

int main() {
    // 初始化任务
    Task tasks[] = {
        {1, 2, 1, 1, 2, false},
        {2, 5, 3, 3, 5, false}
    };
    int numTasks = sizeof(tasks) / sizeof(Task);

    // 打印调度结果
    for (int i = 0; i < 10; i++) {
    	int id = scheduleTasks(i, tasks, numTasks);
        printf("Task %d is running\n", id);
    }
    return 0;
}

(代码都还有些缺陷,但对于老师课件上的调度可以正常输出结果)

比较:RMS算法和EDF算法都是抢占式算法,每个时间片根据任务优先级决定运行哪个任务。但RM算法的优先级是静态的,不会改变;EDF算法为动态优先级,每个时刻任务的优先级可能变化。

(3)优先级翻转问题产生原因:低优先级任务持有高优先级任务所需的临界资源,从而导致高优先级任务被延迟执行。

解决方法:

① 优先级继承协议(PIP):优先级翻转问题发生时,让持有共享资源的低优先级任务获取被阻塞高优先级任务的优先级,以尽快执行并释放共享资源,进而使高优先级任务能得到快速响应。

② 优先级天花板协议(PCP):每个临界资源有一个优先级天花板,取决于需要该资源的所有任务的最高优先级。当一个任务尝试获取这个资源时,如果任务的优先级严格大于该资源的优先级天花板,就能获得这个临界资源;否则,任务被阻塞,而持有资源的任务继承该任务的优先级。

相同点:都能解决优先级翻转问题,并且任务的优先级都会发生改变。

不同点:① PCP需要额外维护资源的优先级,而PIP不需要,PCP比PIP有更多的资源消耗。

② PIP可能会造成"死锁现象"和"阻塞链",而PCP不会。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐