ucos作业

ucosii-1(必做)

  1. 下载附件,尝试在vc6.0 中编译运行ucos
  2. 下载附件,尝试在vs2017中编译运行ucos,给出你遇到的问题和解决方式

1.在vc6.0中编译运行
编译成功

运行后得到命令

2.在vs2017中编译运行
编译成功

运行后得到截图

ucosii-2(选做)

阅读附件中的代码,回答:

  1. ucos是如何分层的?
  2. HAL都有哪些代码?
  3. 分析任务是如何切换的。

1.ucos是如何分层的:
共分四层,硬件相关层,驱动接口层,应用接口层,应用层。好的分层会让软件开发相对独立化,分工同步进行。
ucos结构示意图

所有的硬件被抽象化,应用层的程序,基本上不用再考虑硬件的问题,也就是说,在硬件完全更换的情况下,只要硬件相关层被更新,完全可以等同原先的所实现的功能。这样就极大成度上方便了移植。要做到完全的划分,下面逐步对这几层做一个说明。

硬件相关层:

在这层中,要尽量所有硬件相关都囊括在其中。不管是GPIO还是定时器,或串行接口。只要提供标准统一的接口,就可以让上层会因此而变的很潇洒。这其中有三个最为重要的接口Open,Close,Ctrl。 Open主要来完成对应硬件初始化,形参中包括了些,初始化的相关参数。Close失能硬件。Ctrl来实现一些控制的修改如:优先级,中断回调函数等等,硬件的不同,内容也大为不同

驱动接口层:

其实在上一层也算是驱动层,只不过因为硬件相关,而把他分离。这层中会用到一个或多个硬件层的接口,进行组合来实现特定功能的程序。这部分程序可举例进行说明。以Flash为列,它这里主要调用硬件层的SPI函数接口,但是主要的写,读指令都是在这里函数中完成的。在这层中需要提供5个标准统一的接口函数:

XXXOpen
XXXClose
XXXWrite
XXXRead
XXXIoCtl

没有被用到的函数,可以为空。本来还需要Install函数来进行动态加载和删除,因为stm32内存一般都很有限,所以舍弃动态分配。而把这5个函数用常量的形式直接编译到ROM中。在驱动的抽象接口层中可以做选择,哪些驱动要加载到内核,哪些不需要。不要的驱动不参与编译。这样有限的资源 可以得到合理的应用。这一层大部分工作可以说属于一次性投入。

应用接口层:

主要连接驱动和应用。又是连接应用层模块与模块之间的一层, 这一块有很强的特殊性,第一包括了驱动抽象接口层,第二包括了模块与模块的接口层。第三又与应用层密不可分。
先说下驱动抽象接口,在驱动层时也有提过,这个接口 其实就是通过ID去访问那ID对应的那五个函数。抽象接口也是一次性投入的函数,在设计时对其可靠性要很重视。
模块与模块的接口层,包括模块的接口头文件,这些头文件要求是非常独立的,不能加载模块内的内部头文件,应该包括接口函数的函数声明,在接口中尽量少用到全局变量。如果非要用到可以使用函数的方式进行传递,或ucos消息队列方式。最好用ucos进行传递,因为有很好的互斥保护功能。

应用层:

这里所有模块都算是应用层,在前面以经提到过,在模块内所有变量,或函数(接口除处)应该都本地化。在模块内可以有本模块化共用的主头文件,来方便本模块的维护。对硬件的访问其实直接调用应用接口就可完成。
2. HAL都有哪些代码:
stm32f2xx.h主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件

#if defined(STM32F205xx)
#include "stm32f205xx.h"
#elif defined(STM32F215xx)
#include "stm32f215xx.h"
#elif defined(STM32F207xx)
#include "stm32f207xx.h"
#elif defined(STM32F217xx)
#include "stm32f217xx.h"
#else
#error "Please select first the target STM32F2xx device used in your application (in stm32f2xx.h file)"
#endif

紧接着,其会包含stm32f2xx_hal.h
stm32f2xx_hal.h:stm32f2xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置stm32f2xx_hal_conf.h

该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。
接下来对于HAL库的源码文件进行一下说明,HAL库文件名均以stm32f2xx_hal开头,后面加上_外设或者模块名(如:stm32f2xx_hal_adc.c):
库文件:

    stm32f2xx_hal_ppp.c/.h          // 主要的外设或者模块的驱动源文件,包含了该外设的通用API
    stm32f2xx_hal_ppp_ex.c/.h       // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API。
    stm32f2xx_hal.c/.h              // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API
    其他库文件
用户级别文件:
    stm32f2xx_hal_msp_template.c    // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。
    stm32f2xx_hal_conf_template.h   // 用户级别的库配置文件模板。使用者复制到自己目录下使用
    system_stm32f2xx.c              // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。 **它不在启动时配置系统时钟(与标准库相反)**。 时钟的配置在用户文件中使用HAL API来完成。
    startup_stm32f2xx.s             // 芯片启动文件,主要包含堆栈定义,终端向量表等
    stm32f2xx_it.c/.h               // 中断处理函数的相关实现
    main.c/.h                       //

根据HAL库的命名规则,其API可以分为以下三大类:

初始化/反初始化函数: HAL_PPP_Init(), HAL_PPP_DeInit()
IO 操作函数: HAL_PPP_Read(), HAL_PPP_Write(),HAL_PPP_Transmit(),
HAL_PPP_Receive()
控制函数: HAL_PPP_Set (), HAL_PPP_Get ().
状态和错误: HAL_PPP_GetState (), HAL_PPP_GetError ().
HAL库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分:

处理外设句柄(实现用户功能)
处理MSP
处理各种回调函数
用户代码的第一大部分:对于外设句柄的处理。 HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。*所有的函数都是工作在ppp_HandleTypeDef指针之下。
多实例支持:每个外设/模块实例都有自己的句柄。 因此,实例资源是独立的
外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。
下面,以ADC为例:

/** 
 * @brief  ADC handle Structure definition
 */ 
typedef struct
{
    ADC_TypeDef                   *Instance;                   /*!< Register base address */
    ADC_InitTypeDef               Init;                        /*!< ADC required parameters */
  __IO uint32_t                 NbrOfCurrentConversionRank;  /*!< ADC number of current conversion rank */
    DMA_HandleTypeDef             *DMA_Handle;                 /*!< Pointer DMA Handler */
    HAL_LockTypeDef               Lock;                        /*!< ADC locking object */
    __IO uint32_t                 State;                       /*!< ADC communication state */
    __IO uint32_t                 ErrorCode;                   /*!< ADC Error code */
}ADC_HandleTypeDef;

从上面的定义可以看出,ADC_HandleTypeDef中包含了ADC可能出现的所有定义,对于用户想要使用ADC只要定义一个ADC_HandleTypeDef的变量,给每个变量赋好值,对应的外设就抽象完了。接下来就是具体使用了。
当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。例如以下外设:

GPIO
SYSTICK
-NVIC
RCC
FLASH

以GPIO为例,对于HAL_GPIO_Init() 函数,其只需要GPIO 地址以及其初始化参数即可。
HAL库对所有的函数模型也进行了统一。在HAL库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):

HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);

其中,带_IT的表示工作在中断模式下;带_DMA的工作在DMA模式下(注意:DMA模式下也是开中断的);什么都没带的就是轮询模式(没有开启中断的)。至于使用者使用何种方式,就看自己的选择了。
此外,新的HAL库架构下统一采用宏的形式对各种中断等进行配置(原来标准外设库一般都是各种函数)。针对每种外设主要由以下宏:

__HAL_PPP_ENABLE_IT(__HANDLE__, __INTERRUPT__): 使能一个指定的外设中断
__HAL_PPP_DISABLE_IT(__HANDLE__, __INTERRUPT__):失能一个指定的外设中断
__HAL_PPP_GET_IT (__HANDLE__, __ INTERRUPT __):获得一个指定的外设中断状态
__HAL_PPP_CLEAR_IT (__HANDLE__, __ INTERRUPT __):清除一个指定的外设的中断状态
__HAL_PPP_GET_FLAG (__HANDLE__, __FLAG__):获取一个指定的外设的标志状态
__HAL_PPP_CLEAR_FLAG (__HANDLE__, __FLAG__):清除一个指定的外设的标志状态
__HAL_PPP_ENABLE(__HANDLE__) :使能外设
__HAL_PPP_DISABLE(__HANDLE__) :失能外设
__HAL_PPP_XXXX (__HANDLE__, __PARAM__) :指定外设的宏定义
__HAL_PPP_GET_ IT_SOURCE (__HANDLE__, __ INTERRUPT __):检查中断源

在HAL库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    /*Configure the SysTick to have interrupt in 1ms time basis*/
    HAL_SYSTICK_Config(SystemCoreClock/1000U);
    /*Configure the SysTick IRQ priority */
    HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
    /* Return function status */
    return HAL_OK;
}

有些则没有被实现,例如:

__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hspi);
  /* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file
  */
}

所有带有__weak关键字的函数表示,就可以由用户自己来实现。如果出现了同名函数,且不带__weak关键字,那么连接器就会采用外部实现的同名函数。通常来说,HAL库负责整个处理和MCU外设的处理逻辑,并将必要部分以回调函数的形式给出到用户,用户只需要在对应的回调函数中做修改即可。 HAL库包含如下三种用户级别回调函数(PPP为外设名):

外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理):HAL_PPP_MspInit()和
HAL_PPP_MspDeInit** 例如:__weak void HAL_SPI_MspInit(SPI_HandleTypeDef
*hspi)。在HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt)
处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),例如:__weak
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef
*hspi)。当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用
错误处理回调函数:HAL_PPP_ErrorCallback例如:__weak void
HAL_SPI_ErrorCallback(SPI_HandleTypeDef
*hspi)**。当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用
  1. 分析任务是如何切换的。
    根据老师PPT上的图

    时钟节拍中断服务函数OSTickISR()进行切换。
    任务中调用时间延迟函数OSTimeDly()进行切换。
    延迟函数OSTimeDly()进行切换:
    复制代码
`y = OSTCBCur->OSTCBY;
OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;//清除准备优先级中的X标记量
if (OSRdyTbl[y] == 0) {
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;//如果对应的Y优先级中没有X的优先级标记了,则把Y优先级也清除
}
OSTCBCur->OSTCBDly = ticks;//把对应的延时时间赋值给任务控制块,在系统滴答中断中会自动减
OS_SchedNew();//从准备好的任务中找到优先级最高的,赋值给OSPrioHighRdy,然后通过OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];从任务的链表中找出对应的任务控制块, OS_TASK_SW();实际切换任务的函数,一般由汇编代码完成。

复制代码
中断服务函数进行切换:
OSTimeTick()函数的主要内容,if (ptcb->OSTCBDly != 0)if (--ptcb->OSTCBDly == 0) 判断该任务块中的设定的延时是否到了,OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;延时时间到后将该任务的优先级重新加入到优先级准备变量中。
OSIntExit()该函数和上面的内容差不多,是中断函数中真正切换任务的地方
下图表明了任务状态之前切换的关系,调用哪种函数可以进行任务切换。uCOS-II就是通过这些直接或间接调用的系统函数进行任务切换的。

参考链接
ucos软件结构
一步一步教你使用uCOS-II
ucos2学习方法
STM32 之二 HAL库详解 及 手动移植

posted @ 2019-12-29 21:34  20175206ldc  阅读(237)  评论(0编辑  收藏  举报