AUTOSAR OS模块详解(三) Alarm

news2025/1/21 7:51:06

AUTOSAR OS模块详解(三) Alarm

本文主要介绍AUTOSAR OS的Alarm,并对基于英飞凌Aurix TC3XX系列芯片的Vector Microsar代码和配置进行部分讲解。

文章目录

  • AUTOSAR OS模块详解(三) Alarm
    • 1 简介
    • 2 功能介绍
      • 2.1 触发原理
      • 2.2 工作类型
      • 2.3 Alarm启动方式
      • 2.4 Alarm配置
      • 2.5 代码分析
        • 2.5.1 主要数据结构
        • 2.5.2 Alarm初始化阶段
        • 2.5.3 Alarm启动阶段
        • 2.5.4 Alarm运行阶段
      • 3 小结

1 简介

在上一篇Autosar OS模块详解中,我们介绍了OS Counter,其作为OS时间Tick管理的重要元素,为OS提供了重要的时间基准,并驱动OS进行时间调度。

而Counter仅为OS注入时间驱动力,却不负责管理OS中各个任务或者其他定时事件,比如我们要设置某个Task的周期为10ms,或者有一些定时事件,比如100ms之后触发某个event,从概念上我们不能直接让Counter管理这些定时业务,毕竟它的职责仅仅是驱动OS,就像发动机仅仅负责动力,而不负责车辆的档位。

因此我们还需要一个中间层,来管理所有的定时业务。在Autosar OS中,这个元素称为Alarm,它就像车辆的变速器,来管理不同时间频率的定时事件。在Autosar OS中,Counter通过累加来驱动Alarm,Alarm根据自身的设定参数周期地激活Task或者触发事件。

在这里插入图片描述

对于不同的周期事件,我们只需要设置多个Alarm,每个Alarm驱动所关联的业务,这样的方式实现了代码的抽象和业务分层,具备良好的架构体系。

2 功能介绍

2.1 触发原理

在Autosar OS中,Alarm顾名思义,就是闹钟,用于实现定时业务。在前面的Counter模块中我们已经介绍了,Counter在每个Tick会累加所有关联的Alarm,然后判断Alarm的状态,如果Alarm到期,则会执行相应的Job,也就是下文会提到的工作类型。

2.2 工作类型

在Autosar OS中,Alarm有4种工作类型,用户可配置不同的类型,并关联不同的Task、Event等。

Os_AlarmActionSetEvent

该工作类型用来设置某个Task中的一个Event,关于Event我们后面会介绍到,它是扩展任务中的关键元素,Event触发后该任务进入就绪状态,并且不同的Event关联不同的Runable。

Os_AlarmActionActivateTask

该工作类型用于激活某个Task,用于基础任务。基础任务是一种不带Event的普通任务,该任务每次运行都需要被激活,运行完成之后会自动Terminate,也就是挂起。

Os_AlarmActionIncrementCounter

前文提到过,并非每个Counter都需要关联到独立的硬件,也可以使用软件Counter。使用软件Counter的时候就是通过其他硬件Counter中的Alarm进行累加,使软件Couter得以产生连续的Tick。

Os_AlarmActionCallback

该工作类型为调用相应的CallBack函数,需要注意的是,这种工作类型只能在SC1下使用。该工作类型使用不多,一般情况下很少会将业务逻辑的执行与OS内部Counter、Alarm等元素进行直接关联,而是通过Task或者Event激活等进行业务逻辑的设置。

2.3 Alarm启动方式

Alarm的启动有两种方式,一种是绝对启动(SetAbsAlarm),另一种是相对启动(SetRelAlarm)。

绝对启动

如果以Counter的值作为一个时间轴的话,绝对启动就是Alarm的触发在Counter的初始时刻,所有通过绝对启动的Alarm的启动时刻相等。在Vecotr Os中,所有的Alarm启动之后下一次Counter就会立即执行一次,因此所有的Alarm会在第一个Tick统一执行。

当存在1ms、5ms、10ms三个Alarm时,如果使用绝对启动,其在Counter Tick的时间轴上的触发关系如下图所示。可以看到当Alarm的最小公倍数Tick数出现时,所有的Alarm都会同时触发,此时系统的瞬态负载较高。

在这里插入图片描述

相对启动

相对启动在设置Alarm启动时,可以添加一个Offset,这个Offset的分辨率是Os的Tick。我们可以通过设置Alarm的Offset将Alarm的触发错开,相应的Task的执行也会因此错开。

同样是1ms、5ms、10ms三个Alarm,我们将10ms的Alarm添加一个2ms的Offset,将5ms的Alarm添加一个1ms的Offset,则其在Counter Tick时间轴上的触发如下图所示,我们可以看到,此时最多两两之间发生碰撞,系统的瞬态峰值负载得到了降低。

在这里插入图片描述

另外值得一提的是,如果Os Tick的分辨率是0.5ms,则可将这三个Alarm通过设置1.5ms、2.5ms的Offset的方式全部错开。但是分辨率增加也意味着Os运行开销的增大,需要根据项目实际情况进行斟酌。

2.4 Alarm配置

其实在Vector工具中,Alarm是不需要配置的,工具会根据Task内部配置的Rbl的周期,自适应生成对应的Alarm,相对启动的Offset也是通过配置Task中Rbl的Offset来实现的。这里我们以一个Bsw任务中的10ms Alarm为示例,介绍Alarm各配置的含义。

在这里插入图片描述

OsAlarmCounterRef

如上一章所述,Counter是Alarm的驱动源,因此每个Alarm都需要关联一个Counter,一般情况下是Alarm所在核内的Counter。

OsAlarmAction

如前所述,Alarm具有四种Action,一般最常使用的是OsAlarmSetEvent和OsAlarmActivateTask。这里由于Bsw Task分配了多个周期的Rbl,属性为Extended Task,因此需要使用SetEvent的形式进行调度。

OsAlarmTask

每个Alarm的动作都需要对应一个Task。

OsAlarmCallbackName

SC1类型的Os中可以使用Os_AlarmActionCallback类型,因此如果是该类型的Alarm这里可以配置Callback函数。

OsAlarmCounter

当Alarm的工作类型为Os_AlarmActionIncrementCounter时,这里就需要配置一个Counter,以实现软件Counter的激活。

OsAlarmEvent

当Alarm的工作类型为Os_AlarmActionSetEvent时,这里配置对应的需要设置的Event即可。

OsAlarmAlarmTime

Alarm的Offset,但是实际上Offset是根据对应Task中分配的Rbl的Offset来计算的,因此这里不用配置。

OsAlarmAutostartType

启动类型,绝对启动还是相对启动,这个一般由工具计算也不需要配置。

OsAlarmCycleTime

Alarm的周期,如果是一次性的Alarm,这里配置为0,但是这项配置其实也是工具自动计算的,不用配置。

OsAlarmAppModeRef

Os的启动是有启动模式的,这里可以配置为:在哪种启动模式下,该Alarm自动启动,即初始化时启动。这项配置一般不用配置,Rte启动时会统一启动所有Alarm。

OsAlarmAccessingApplication

Alarm的访问权限配置,一般情况下Alarm的激活都是由Os来执行,权限也是由Os自动分配,少数比较特别的Alarm用户或者运行时的RTE会操作,因此Os无法识别所有访问源,因此这里有时候需要手动配置权限。如果执行Os服务时报权限访问的错误,一般是这种权限配置的地方有缺失。

在这里插入图片描述

前文提到Alarm可以配置Offset以错开激活时机,在Vector工具中,这是通过Task中实现的。如上图中将所有5ms的Rbl都配置了1ms的Offset,则该5ms的Alarm就会由工具自动计算出Offset。

2.5 代码分析

此处以Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms为例,分析Vector Os中关于Alarm的代码执行逻辑。

2.5.1 主要数据结构

Alarm中的主要数据结构为Os_AlarmSetEventConfigType、Os_AlarmActivateTaskConfigType、Os_AlarmIncrementCounterConfigType和Os_AlarmCallbackConfigType,分别对应Alarm的四种工作类型,此处我们的Alarm工作类型为设置Event,因此我们重点介绍Os_AlarmSetEventConfigType类型。

在Os_Alarm_Lcfg.c文件中我们可以找到配置生成的每个Alarm的数据类型:

/*! Alarm configuration data: Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms */
CONST(Os_AlarmSetEventConfigType, OS_CONST) OsCfg_Alarm_Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms =
{
  /* .Alarm = */
  {
    /* .Job                   = */
    {
      /* .Dyn      = */ OS_ALARM_CASTDYN_ALARM_2_JOB(OsCfg_Alarm_Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms_Dyn),
      /* .Counter  = */ OS_COUNTER_CASTCONFIG_TIMERPFRT_2_COUNTER(OsCfg_Counter_SystemTimer),
      /* .Callback = */ Os_AlarmActionSetEvent
    },
    /* .Autostart             = */
    {
      /* .AlarmTime        = */ 0uL, /* 0.0 sec */
      /* .Cycle            = */ 0uL, /* 0.0 sec */
      /* .ApplicationModes = */ OS_APPMODE_NONE,
      /* .AlarmMode        = */ OS_ALARMMODE_ABSOLUTE
    },
    /* .AccessingApplications = */ (OS_APPID2MASK(OsApplication_NonTrusted_Core0) | OS_APPID2MASK(OsApplication_NonTrusted_Core1) | OS_APPID2MASK(OsApplication_NonTrusted_Core2) | OS_APPID2MASK(OsApplication_NonTrusted_Core3) | OS_APPID2MASK(SystemApplication_OsCore0) | OS_APPID2MASK(SystemApplication_OsCore1) | OS_APPID2MASK(SystemApplication_OsCore2) | OS_APPID2MASK(SystemApplication_OsCore3)),  /* PRQA S 0410 */ /* MD_MSR_Dir1.1 */
    /* .OwnerApplication      = */ &OsCfg_App_OsApplication_NonTrusted_Core0
  },
  /* .Task  = */ &OsCfg_Task_Default_BSW_Async_Task_Core0,
  /* .Mask  = */ Rte_Ev_Cyclic2_Default_BSW_Async_Task_Core0_1_5ms
};

我们可以看到,配置生成的数据为OsCfg_Alarm_前缀加上Alarm配置名。 这里我们可以看到所有的配置值都在这个结构体里,我们重点关注其中的.Alarm.job.Dyn,这个指针指向的结构体中有一个ExpirationTimestamp成员,是Alarm下一个需要被激活的Counter Tick值,也就是“闹钟”的设定值。

2.5.2 Alarm初始化阶段

关于Os的初始化,在本系列Counter章节中已经介绍过,这里就不赘述了,与Counter相同,Alarm的初始化也是在Os_AppInit中进行的,其调用栈如下图所示。

在这里插入图片描述

在初始化阶段,仅仅是将Alarm的状态切换为OS_ALARMSTATE_CANCELED,并没有其他操作。

2.5.3 Alarm启动阶段

Alarm的启动是在EcuM的StartupTwo阶段完成的,关于EcuM的启动时序,这里不做过多介绍,后续聊到EcuM模块时再做解读。

StartupTwo运行在Default_Init_Task中,这是一个系统自带的初始化Task,优先级较高。在StartupTwo阶段,有两个地方会启动Alarm,SchM_Init和Rte_Start。SchM_Init中主要初始化EcuM等Bsw模块相关联的Alarm,而Rte_Start则主要初始化用户定义的相关的Rbl的Alarm。其时序图如图所示。

在这里插入图片描述

我们从中可以看到,Os_AlarmSetRelAlarm是最终实施的主要服务函数,我们从该函数解读SetRelAlarm的实现,这里忽略掉其中的错误检查。

OS_FUNC_ATTRIBUTE_DEFINITION(OS_LOCAL_INLINE Os_StatusType, OS_CODE, OS_ALWAYS_INLINE, Os_AlarmSetRelAla
(
  P2CONST(Os_AlarmConfigType, AUTOMATIC, OS_CONST) Alarm,
  TickType Increment,
  TickType Cycle
))
{
  Os_StatusType status;
  
  if(ErrorCheck...){}
  else
  {
    P2VAR(Os_AlarmType, AUTOMATIC, OS_VAR_NOINIT) alarmDyn;

    alarmDyn = Os_AlarmGetDyn(Alarm);                                                                   

    /* #20 Set alarm's state to SET. */
    alarmDyn->State = OS_ALARMSTATE_SET;                                                                

    /* #30 Set alarm's cycle time to Cycle. */
    alarmDyn->Cycle = Cycle;                                                                            

    /* #40 Tell counter to execute alarm's job in Increment ticks. */
    Os_JobAddRel(&Alarm->Job, Increment);                                                               

    status = OS_STATUS_OK;
  }
  return status;
}

这里alarmDyn的数据指向的是OsCfg_Alarm_Rte_Al_TE2_Default_BSW_Async_Task_Core0_1_5ms_Dyn变量,大家能够很容易注意到其命名是有规律的。

首先,该函数将Alarm的状态切换到OS_ALARMSTATE_SET,表示该Alarm已经启动。

然后设置该Alarm的周期为Cycle,这里我们是5ms的Alarm,周期为5。

然后通过Os_JobAddRel将Alarm添加到其关联的Counter中,这里的Increment就是Offset,如果没有偏移的话Increment为1,表示第一次Counter Tick就会激活该Alarm,这里我们设置了1ms的Offset,因此Increment=2,表示第二次Counter Tick才会激活该Alarm。(Counter的逻辑是先累加再判断,因此第一次工作时Counter Tick值为1)

这里的Os_JobAddRel,也就是Os_CounterAddRelJob,是Counter内部的实现函数,我们观察其代码。

FUNC(void, OS_CODE) Os_CounterAddRelJob
(
  P2CONST(Os_CounterConfigType, AUTOMATIC, OS_CONST) Counter,
  P2CONST(Os_JobConfigType, AUTOMATIC, OS_CONST) Job,
  Os_TickType Offset
)
{
  Os_TickType now;
  Os_TickType newExpTime;
  P2CONST(Os_PriorityQueueConfigType, AUTOMATIC, OS_CONST) jobQueue;

  jobQueue = &Counter->JobQueue;

  /* #10 Get the current counter value (now). */
  now = Os_CounterGetPhysicalValue(Counter);                            

  /* #20 Set job's expiration time to mod(now + Offset). */
  newExpTime = Os_TimerAdd(
      Counter->Characteristics.MaxAllowedValue,
      Counter->Characteristics.MaxCountingValue,
      now,
      Offset);

  Os_JobSetExpirationTimestamp(Job, newExpTime);                        

  /* #30 Enqueue the given job in counter's job queue. */
  Os_PriorityQueueInsert(jobQueue, Job);                                

  /* #40 If the top element of the queue has changed: */
  if(Job == Os_PriorityQueueTopGet(jobQueue))                           
  {
    /* #50 Update timer's compare value. */
    Os_CounterSetCompareValue(Counter, newExpTime);                     
  }
}

首先通过Os_CounterGetPhysicalValue计算出Counter的当前值,一般启动时该值都是0。然后通过Os_TimerAdd结合当前值和Offset,计算“闹钟”的定时时刻ExpirationTimestamp,也就是时间轴上的下一个触发点。这里需要最大值来判断溢出。这里now=0,Offset=2,计算出结果为ExpirationTimestamp=2。

然后通过Os_JobSetExpirationTimestamp函数,将计算出的ExpirationTimestamp设置为超时值,在Counter的中断中,就是通过比较Counter的Tick值和ExpirationTimestamp,来判断该Alarm是否需要激活。

然后通过Os_PriorityQueueInsert来将Alarm插入到Counter的Job队列中,这里使用了完全二叉树进行优先级排序,时间轴上未来最近的一个Alarm具有最高优先级,感兴趣的读者可以自行研究,这里不进行算法层面的探讨。

最后一步是判断是否更新了待激活的Alarm,比如当前Counter待激活的时刻为Tick=5,现在插入的Alarm需要在Tick=2时激活,那么就需要更新Counter的Compare值,因为Counter并不是每一次Timer中断都会工作,而是按需执行。

到这里就完成了Alarm的启动,只需要等待Counter中断去执行了。

2.5.4 Alarm运行阶段

前篇我们提到,在Counter中的Os_CounterWorkJobs函数,会轮询任务队列中的所有Alarm,如果该Alarm的ExpirationTimestamp与Counter Tick的值吻合,则执行该Alarm的Callback,同时将该Alarm从Job队列中取出。这部分逻辑不再赘述,读者如果有疑问可以阅读前篇Counter详解。

这里我们在运行阶段重点介绍Alarm在的Callback中的动作,这是Counter激活Alarm之后由Alarm实施的主要业务。我们的Alarm选取的是5ms的SetEvent,因此其Callback是Os_AlarmActionSetEvent。

在这里插入图片描述

如果是周期Alarm,在Os_CounterReloadJob中,该Alarm会被重载,会根据Counter的当前Tick值加上周期,算出最新的ExpirationTimestamp,以设置下一个周期的触发;如果是一次性Alarm,则在Os_AlarmCancelOrReload中Alarm的状态会被设置为OS_ALARMSTATE_CANCELED,以表示关闭该Alarm。

然后执行Os_EventSetInternal以进行Event的设置,来触发对应Task中的Rbl执行。该部分我们在后面的Event文章内容中进行详细介绍,这里就一概而过。

3 小结

本文承接上篇Counter,介绍了Autosar Os中的Alarm机制,解释了Vector工具中的Alarm配置项,并根据Vector代码详细解读了其初始化、启动、运行等阶段,结合详细的时序图,介绍了其由Counter激活后的执行逻辑。

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

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

相关文章

「免填邀请码」赋能各类APP,提升转化率与用户体验

在当前移动互联网的高速发展下,用户获取和留存已成为各类APP成功的关键。传统的注册流程虽然能够有效识别用户来源并进行用户管理,但随着市场竞争的激烈,复杂的注册和绑定步骤往往会成为用户流失的瓶颈。免填邀请码技术,结合自研的…

Linux:expect spawn简介与用法

一、背景 大家在使用linux系统的很多时候,都用linux指令来实现一些操作,执行特定的job,有时一些场景中需要执行交互指令来完成任务,比如ssh登录这个命令大家一定很熟悉: ssh-keygen -t rsa # 以及 ssh-copy-id -i /hom…

Express的接口

目录 接口的跨域问题域问题 request接口代码 const express require(express) const app express() //在路由之前,配置cors中间件,解决接口跨域问题 const cors require(cors) app.use(cors())const router require(./apiRouter)app.use(/api,rout…

【PCIe 总线及设备入门学习专栏 6.2 -- PCIe VDM (Vendor Defined Messages)】

文章目录 OverviewPCIe VDM (Vendor Defined Messages) 概述PCIe VDM Header 的各个字段及作用VDM 的工作方式例子:一个简化的 VDM 示例注意事项Overview 本文将详细介绍 PCIe VDM 及 PCIe VDM Header 各个域的作用。 PCIe VDM (Vendor Defined Messages) 概述 在 PCIe 协议…

微服务学习:基础理论

一、微服务和应用现代化 1、时代的浪潮,企业的机遇和挑战 在互联网化数字化智能化全球化的当今社会,IT行业也面临新的挑战: 【快】业务需求如“滔滔江水连绵不绝”,企业需要更快的交付【变】林子大了,百色用户&…

实战演示:利用ChatGPT高效撰写论文

在当今学术界,撰写论文是一项必不可少的技能。然而,许多研究人员和学生在写作过程中常常感到困惑和压力。幸运的是,人工智能的快速发展为我们提供了新的工具,其中ChatGPT便是一个优秀的选择。本文将通过易创AI创作平台&#xff0c…

【PCIe 总线及设备入门学习专栏 6 -- PCIe Inbound and Outbound】

文章目录 PCIe Outbound 和 Inbound 概念详解Outbound 与 Inbound 的基础定义基于角色的详细分析关于“上游”和“下游”方向应用举例小结PCIe Outbound 和 Inbound 概念详解 PCIe(Peripheral Component Interconnect Express)是一种高速串行通信协议,用于连接主机与外部设…

24年总结 -- 共赴心中所向往的未来

一、前言 我又回来了,前阵子忙着期末考试的东西,也是快半个月没更新了,刚好前几天报名了博客之星的评选,也很幸运的入围了,也借此机会来回顾一下关于2024年的个人成长、创作经历等。 二、个人 本人是一个双非学校的软…

嵌入式产品级-超小尺寸热成像相机(从0到1 硬件-软件-外壳)

Thermal_Imaging_Camera This is a small thermal imaging camera that includes everything from hardware and software. 小尺寸热成像相机-Pico-LVGL-RTOS 基于RP2040 Pico主控与RTOS,榨干双核性能实现LVGL和成图任务并行。ST7789驱动240280屏,CST8…

网络协议入门:OSI模型与TCP/IP栈

在网络通信的世界中,数据从一台设备传输到另一台设备,需要遵循一系列规则,这些规则统称为网络协议。OSI模型和TCP/IP协议栈作为网络通信的基石,帮助我们理解数据传输的全流程。这篇文章将深入解析它们的结构、功能和实际应用&…

HackMyVM-Klim靶机的测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、信息搜集 2、Getshell 3、提权 CVE-2008-0166 四、结论 一、测试环境 1、系统环境 渗透机:kali2021.1(192.168.159.127) 靶 机:debian(192.168.159.27) 注意事…

Mysql InnoDB B+Tree是什么?

“mysql中常用的数据库搜索引擎InnoDB,其索引通过BTree的方式进行构建。” 实在想不起来BTree是怎么一回事了。以点带线,将涉及到的数据结构一起复习一下。 文章目录 数据结构定义红黑树定义使命 BTree定义使命 BTree定义 InnoDB BTree 旋转与调整二叉排序树插入删…

docker 使用远程镜像启动一个容器

使用前提: 首先你得安装docker,其次你得拥有一个远程镜像 docker run --name io_11281009 --rm -it -p 2233:22 -v .:/root/py -e ed25519_rootAAAAC3NzaC1lZDI1********Oy7zR7l7aUniR2rul ghcr.lizzie.fun/fj0r/io srv对上述命令解释: 1.docker run:…

吴恩达深度学习——神经网络介绍

文章内容来自BV11H4y1F7uH,仅为个人学习所用。 文章目录 什么是神经网络引入神经网络神经元激活函数ReLU隐藏单元 用神经网络进行监督学习监督学习与无监督学习举例 什么是神经网络 引入 已经有六个房子的数据集,横轴为房子大小,纵轴为房子…

基于GRU实现股价多变量时间序列预测(PyTorch版)

前言 系列专栏:【深度学习:算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对抗网络、门控循环单元、长短期记…

Linux -- HTTP 请求 与 响应 报文

目录 请求报文: 请求方法 响应报文: 状态码 与 状态码描述 共性 常见的报头 请求报文: 请求方法 方法说明GET获取资源POST传输实体主体PUT传输文件HEAD获得报文首部DELETE删除文件OPTIONS询问支持的方法TRACE追踪路径CONNECT要求用…

小米平板pad6工程固件界面预览 修复tee损坏 修复底层分区 开diag端口

💝💝💝小米平板pad6 机型代码pipa。采用一块分辨率为 2880*1800p,支持 120/144Hz 高刷新率的国产屏,并且屏幕支持 HDR10 + 以及杜比视界。分别搭载 SM8250AC(骁龙 870)、SM8475(骁龙 8+)处理器。也适用于以下型号的小米机型:23043RP34G, 23043RP34I, 23043RP34C等…

day03_开发前准备和匹配类标签

文章目录 day03_开发前准备和匹配类标签一、标签体系(了解)二、数据导入(操作)1、背景介绍(重要)2、创建Hive表2.1 dwm_sold_goods_sold_dtl_i2.2 dwm_sell_o2o_order_i**2.3 dwd_mem_member_union_i**2.4 dwm_mem_member_behavior_day_i**2.5 dwm_mem_first_buy_i**3、数…

STM32之FreeRTOS开发介绍(十九)

STM32F407 系列文章 - freertos(十九) 目录 前言 一、简述 二、开源网址 三、原理及功能特性 1.原理 2.功能 3.特点 4.优缺点 四、参考书籍 五、实现方式 总结 前言 FreeRTOS是一个免费的、开源的实时操作系统,专为微控制器和嵌入…

第十三章:数据库技术

文章目录: 一:基础 1.概念 2.特点 3.常见数据库品牌 4.数据库应⽤系统 4.1 C/S 4.2 B/S 5.数据模型的分类 6.名词解析 7.关系运算 二:Access 1.基础 2.操作 2.1 建立表 2.2 维护表 2.3 创建查询 2.4 创建窗体 2.5 创建报表…