内容提要
引言
1. S32 SDK的软件分层架构介绍
1.1 外设驱动层(PD--Periperal Driver)
1.2 外设抽象层(PAL--Peripheral Abstract Layer)
1.3 中间件层(Middleware)
1.4 实时操作系统层(RTOS)
1.5 S32 SDK底层驱动软件分层的优点
2. S32 SDK的外设驱动组件全局状态结构体解析
3. S32 SDK外设底层驱动中断处理机制详解
4. S32 SDK的通信外设驱动组件API函数的阻塞与非阻塞发送与接收API函数差别与使用详解;
5. S32 SDK的外设驱动与RTOS之间的交互方式
总结
引言
S32 SDK(Software Development Kit,软件开发套件)借鉴了AutoSAR软件和ARM Cortext M系列MCU软件接口标准(CMSIS--Cortex Microcontroller Software Interface Standard)的编程思想,通过对内核和底层外设硬件驱动的抽象化,对应用层提供统一的API,不但大幅度地降低了MCU用户的软件开发难度和编程门槛,同时也大大提高代码的重用率和可移植性及可读性。使得基于MCU的嵌入式软件编程变得更加容易。
然而,相较于使用传统MCU--比如51单片机、S08和S12(X) MCU的底层软件开发, S32 SDK的软件编程模式和编程思想发生了很大的变化, 以适应日益复杂的MCU内核和外设资源。如果之前没有接触过系统的软件架构和软件编程思想知识体系的学习,也未使用过AutoSAR 软件和CMSIS库,则在使用S32 SDK时,将会发现很多困惑,不知道如何在应用层高效地调用SDK的API、如何通过事件通知(Event Notification)与底层驱动进行交互,以及如何在使用RTOS的系统中调用SDK提供的底层驱动API等?
鉴于此,本文将以S32K SDK为例,详细介绍S32 SDK的软件编程思想,以为大家解开以上疑惑,帮助大家更好的使用S32 SDK并真正理解其编程优势。
1. S32 SDK的软件分层架构介绍
S32 SDK的软件架构如下图所示,从上一篇文章--《S32K SDK使用详解之S32 SDK软件架构详解》解读中(点击文章标题即可快速跳转阅读),
可以看到S32 SDK除了提供底层驱动(LLD--Low Level Drivers)和中间件(Middleware)之外,还提供了不同工具链编译器的启动(Start-up)和链接文件(Linker filers) ,移植好的FreeRTOS和RTOS接口层(OSIF)以及处理器专家(Processor Expert)配置GUI和丰富的demo程序和详细的帮助文档:
具体来看,S32 SDK的软件分层情况如下:
1.1 外设驱动层(PD--Periperal Driver)
PD层组件通过调用对外设寄存器操作的硬件访问层(Hardware Access)API(一般为宏定义或者inline函数,且为静态static代码,仅PD层文件内部使用),读写不同S32系列MCU底层硬件外设IP模块寄存器,从而完成对外设的初始化配置、状态读取--即硬件交互;
在此基础上,根据不同的外设模块功能特性,编写相应的初始化函数、功能配置/使用函数以及中断处理函数(ISR),从而构成完成的外设底层驱动。这里面包含了很多外设初始化配置的时序/顺序要求(配置外设的initialization状态、Freeze状态、STOP、Standby/Shutdown以及nomal work状态)和特定寄存器的操作权限管理(比如管理员模式还是用户模式可以读写权限)、和寄存器操作方式控制(比如清除外设中断标志的方法是写1清零还是读清零)等。
Tips: S32 SDK的PD层包含了所支持MCU的几乎所有片上外设底层驱动,由于其代码直接操作外设IP寄存器,所以其效率是最高的,但不能实现跨MCU平台/系列使用。 如下为S32K SDK的PD层外设驱动组件列表:
1.2 外设抽象层(PAL--Peripheral Abstract Layer)
在PD层的基础上, S32 SDK封装了一个外设抽象层,将能够实现相同功能的外设PD组件封装为一个PAL功能组件,比如实现UART串行通信的外设,在S32K1xx系列上硬件外设为LPUART,在Qorivva MPC57xx和S32V234上则是LINFlex,在PAL就抽象了一个UART_PAL,对应用层提供统一的API函数,从而对应用层软件开发人员屏蔽了底层硬件外设的差别:
Tips: 使用S32 SDK的PAL层能够实现代码的跨平台移植和使用,典型的PAL组件包括CAN、UART、I2C、SPI和I2C等通信功能外设模块,PWM、OC及TIMING等定时器功能模块,以及MPU/MMU存储器保护/管理功能模块以及看门狗和信息安全等系统级功能模块。 如下为S32K SDK的PD层外设驱动组件列表:
1.3 中间件层(Middleware)
S32 SDK的中间件主要包括三大类:①功能静态库--比如电机控制库(AMMCLib)、触控感应库(Touch Sensing)等和②软件协议栈(stack)--比如以太网协议栈(TCP/IP)、LIN协议栈和USB及文件系统等,以及③ 复杂外围芯片驱动程序--比如SBC(UJA1169和UJA113x)系列驱动程序。
Tips: 如下为S32K SDK的Middleware组件列表,其中Touch Sense, ISELED和NFC Middleware需要联系NXP Sale/FAE申请license,在安装相应的SDK版本之后单独下载安装:
1.4 实时操作系统层(RTOS)
为了管理众多的MCU外设资源和存储器资源并进行实时的应用程序任务调度,在S32 SDK中还移植了实时操作系统--FreeRTOS,并未裸写代码提供了操作系统接口服务(Osif组件)。
Tips: 如下为S32K SDK的提供的FreeRTOS,在S32K SDK RTM 2.0.0中是FreeRTOS的版本为v8.2.1,在最新的Beta 2.9.0(及更高版本)中已经update到FreeRTOS v10.0.1了。
1.5 S32 SDK底层驱动软件分层的优点
综上所述,S32 SDK提供的底层外设驱动分层,具有如下优点:
① 分层提高了代码的可读性;
② 使用PD层,对保证对外设模块的操作实时性和代码运行效率;
③ 通过PAL层对底层硬件外设的抽象,对应用层提供了统一的API,保证了底层驱动代码在不同S32 MCU硬件平台上的通用性,提高了底层驱动代码的可移植性和使用效率;
④ 集成丰富的中间件,极大地提高了S32 MCU平台应用层软件的可扩展性和易用性;
⑤ 通过OSIF隔离硬件外设底层驱动程序与RTOS内核,保证底层驱动对不同RTOS的支持;
⑥ 通过提供针对不同软件开发工具链的启动文件(start-up)和链接文件(linker file),支持主流工具链--比如GNU GCC(S32DS IDE自带工具链)、DIAB、GHS和IAR等;
2. S32 SDK的外设驱动组件全局状态结构体解析
在S32 SDK中每一个外设驱动组件都有一个组件的全局状态结构体,以<组件/外设名>_state_t命名,用于存放该外设/组件的工作状态和硬件资源使用信息,主要包含以下信息:
① 外设模块的配置信息;
② 通信外设收发器的工作状态(isTxBbusy/isRxBusy);
③ 通信外设收发器是否为阻塞方式工作(isTxBlocking/isRxBlocking);
④ 中断回调函数指针(*callback)及参数(*callbackParam)定义;
⑤ 外设工作/通信使用的信号(semaphore)(TxComplete/RxComplete)和通信状态(transmitStatus/receiveStatus);
⑥ 通信外设收发数据的缓存(txBuff/rxBuff)和大小(txSize/rxSize);
⑦ 外设使用的DMA通道号;
以下是S32K SDK中LPUART的全局状态结构体lpuart_state_t的定义:
而非通信外设的时钟管理器全局状态结构体clock_manager_state_t如下:
Tips:通常在SDK外设驱动(PD)层定相应硬件外设的全局状态结构体结构体指针数组,而在外设抽象层(PAL)为其定义外设驱动工作时使用的全局状态结构体(如果包含多个相同的外设实体(instance),比如LPUART0/1/2或者FlexCAN0/1/2则为结构体数组,每个成员对应相应的外设实体):
比如S32K SDK中在LPUART的外设驱动层定义其工作时的全局状态结构体指针数组:
而在外设抽象层中,才定义其使用的全局状态结构体数组:
FlexCAN/CAN_PAL的驱动也是如此:
因此,如果使用PD层组件,用户还需组件定义全局状态结构体,而使用PAL层组件时,其将会定义该外设要使用的全局状态结构体,无需用户定义。
S32 SDK的LLD组件外设全局状态结构体在SDK组件的初始化API函数中被初始化,并与其全局状态结构体指针数组关联起来。比如在S32K SDK的LPUART驱动中,其全局状态结构体就是其初始化函数--LPUART_DRV_Init()中完成初始化的:
当执行某一个SDK组件的反初始化函数(Deinit)时,其全局状态结构体也将被清除(指向空指针NULL)。比如在S32K SDK的LPUART驱动中,其反初始化函数--LPUART_DRV_Deinit()实现代码如下:
每一个硬件外设模块工作时,只能有一个全局状态结构体与其对应,其使用生命周期是初始化完成(调用SDK组件Init()函数)到反初始化函数执行(调用SDK组件Deinit()函数)。
Tips:使用S32 SDK的外设底层驱动(LLD)时,推荐大家使用PAL组件,而不是PD组件;
Tips: S32 SDK的LLD组件外设全局状态结构体为定义在MCU SARM静态区(static)的全局变量,其包含对应外设模块的完整工作状态信息,并实时记录外设的工作状态,是调试LLD时最为重要的一个信息源,建议将其添加到变量(variable/expression)窗口查看以跟踪调试问题。
3. S32 SDK外设底层驱动中断处理机制详解
在基于MCU的嵌入式系统软件开发中,外设中断是保证系统软件工作实时性和内核效率最重要的工作机制----在外设事件(比如通信外设的数据接收完成,GPIO状态改变以及定时周期满等),CPU内核将停止当前程序指令的执行,及时响应外设中断请求并做相应的处理(比如搬移数据,填充缓冲,改变程序运行条件/状态、激活新任务等)。
在传统的嵌入式MCU底层外设驱动程序开发中,对于外设中断的处理流程/机制如下:
① POR复位后,在main()函数最开始完成系统时钟初始化后,初始化外设模块时,就配置使能相应的外设中断;
② 编写中断服务函数ISR,在其中处理中断事件,置位全局事件标志通知应用程序做后续处理, 并清除中断标志;
③ 将中断服务函数放到MCU的中断向量表中,这一步可能是通过关键词(interrupt)+中断向量号 + ISR定义的方式实现,工具链的编译器和链接器自动识别并加载到MCU中断向量表中;
④ 使能全局中断;
在整个MCU正常工作期间,除非特殊应用需要,不会关心外设中断的关闭与使能,
而在S32 SDK中,其进行了如下优化:
①外设初始化Init()函数中,仅使能芯片级中断控制器中对应的中断源(比如S32K的ARM Cortex M系列内核的NVIC,和Qorivva MPC57xx/S32R的PowerPC e200系列内核的INTC模块)并不使能相应外设的IP级中断(比如LPUART的TX和RX以及ERROR中断);
在具体使用外设的特定功能(比如LPUART的数据发送和接收)时,才使能该特定功能的中断(比如LPUART的发送中断和接收中断),在中断发生后,若相应的中断已经完全发生(比如发送10字节数据,全部发送完成),则在ISR的最后关闭该特定功能中断;
这样做的好处是,避免意外的中断浪费宝贵的CPU资源,比如由于程序跑飞导致的LPUART意外数据发送和接收;
② 在S32 SDK的PD层为绝大多数硬件外设模块提供了中断ISR函数,(对应S32K SDK来说,仅PD层的LPIT组件和GPIO的IRQ中断,需要用户自己使能NVIC中断并编写自己的中断ISR)无需用户自己编写;
在S32 SDK的组件中,相同外设的不同实体(比如LPUART0/1/2)共用同一个中断ISR,依靠外设实体号(instance)来区分不同的处理,由于相同外设的工作流程和中断处理流程相同,这样的好处是可以提高代码的复用效率:
如下是S32K144的SDK代码,其有LPUART0/1/2三个串口外设,其中断ISR定义如下:
而在真正的LPUART中断处理函数中依靠不同的instance来区别:
③ S32 SDK默认在startup过程中将外设中断向量表拷贝到SARM中,这样运行用户可以在程序运行时动态的修改中断ISR;
④ S32 SDK中为大部分外设都提供了中断处理函数(ISR)—位于外设驱动PD的中断处理文件(<Peripheral_Name>_irq.c/h)中,并在外设初始化时配置了相应的中断,其外设中断ISR属于SDK PD层驱动非常重要的一部分代码,不允许用户修改,因此提供了在外设中断ISR中调用用户中断回调函数的方式给用户提供中断处理接口。
Tips:在用户中断回调函数中,无需用户自己清除外设中断标志,因为在SDK提供的外设中断ISR中已经做了相应的处理。只有需要用户调用SDK中断管理器(interrupt_manager)的API--INT_SYS_InstallHandler()手动注册安装的外设中断ISR,才需要用户自己清外设中断标志,比如典型的GPIO的IRQ中断和PIT定时器中断。
Tips:使用PD层外设驱动组件,需要用户自己调用中断回调函数API自己安装中断Callback:
而若使用PAL层外设驱动组件,则可以在其Processor Expert图形化配置界面中添加中断Callback函数名,然后再用户代码中实现该Callback即可,无需用户自己调用中断回调函数安装API。
4. S32 SDK的通信外设驱动组件API函数的阻塞与非阻塞发送与接收API函数差别与使用详解;
在S32 SDK,通信外设组件都提供了阻塞(Blocking)与非阻塞两类发送与接收API函数,比如:
UART_PAL组件的API函数:
CAN_PAL组件的API函数:
其差别如下:
非阻塞类数据收发API,只是将要发送的数据buffer和长度size设置到对应的通信外设驱动的全局状态结构体中,使能数据发送器(transmiter)和接收器(receiver),打开收发和错误中断/若使用DMA收发,则配置相应的DMA通道TCD,使能DMA中断,安装中断回调函数,启动DMA传输, 然后就立即返回了,因此非阻塞类API的返回值总是成功(STATUS_SUCCESS)。
比如S32K SDK的LPUART非阻塞发送API--LPUART_DRV_SendData() 实现如下:
而阻塞(Blocking)类数据收发API,函数参数多了一个超时timeout(单位为ms)则在完成以上非阻塞API的工作后,还会调用OSIF组件的API函数--OSIF_SemaWait()检查外设驱动全局状态结构体中的数据收发信号量(semaphore),等待数据收发完成,若在设置的timeout时间内未完成数据传输,则返回超时(STATUS_TIMEOUT),若在timeout时间内数据收发完成,则返回成功(STATUS_SUCCESS)。
比如S32K SDK的LPUART阻塞(blocking)发送API--LPUART_DRV_SendDataBlocking()实现如下:
Tips:结合前面S32 SDK外设底层驱动中断处理机制的介绍可知,无论调用阻塞函数非阻塞数据收发函数,在成功接收完指定长度的数据后,都会将通信外设的接收器关闭,若想通信外设一直接收数据,则需要在该驱动组件的中断回调函数Callback中,设置新的数据接收buffer,从而避免在中断ISR最后,驱动程序关闭数据接收器:
比如使用S32K的LPUART接收GPS数据时,相应的数据接收中断回调函数定义如下,其中使用双buffer,在一个buffer接收完成后,调用UART_SetRxBuffer()切换新的buffer继续接收数据:
Tips:由于在阻塞API函数中,调用了OSIF组件的API函数--OSIF_SemaWait()检查外设驱动全局状态结构体中的数据收发信号量(semaphore), 若在应用工程中使用了RTOS,比如FreeRTOS,则调用该阻塞API函数的任务将被阻塞,让出CPU资源运行其他就绪的高优先级认为,直至收据收发完成(信号量释放)或者超时,这样CPU的执行效率将大大提高;而若应用工程中没有使用RTOS,则阻塞API函数将一直占有CPU,造成CPU资源浪费;
综上, 若应用工程使用了RTOS,则推荐使用阻塞API函数;若应用工程未使用RTOS,则推荐使用非阻塞API函数,并配合中断回调函数,判断数据收发状态并做相应处理。
5. S32 SDK的外设驱动与RTOS之间的交互方式
在S32 SDK中,所有的外设驱动(PD和PAL组件)以及中间件(Middleware)的软件代码实现,都使用了OSIF组件提供的操作系统接口API对外设驱动工作中的数据进行同步和事件时间延迟,从而保证了S32 SDK代码在不同RTOS之间的兼容:
其中使用的信号量都在相应的组件全局状态结构体中定义,在组件初始化(组件_Init()函数)时,调用了OSIF组件的API函数--OSIF_SemaCreate()创建,比如S32K SDK的LPUART驱动用于数据收发的信号量rxComplete/txComplete,就在其初始化函数LPUART_DRV_Init()中创建的:
在组件的阻塞(Blocking)API函数中调用OSIF组件的API函数--OSIF_SemaWait()检查外设驱动全局状态结构体中定义的信号量(semaphore)以完成事件同步。比如S32K SDK的LPUART驱动中数据发送完成信号量txComplete,就在其阻塞数据发送API函数LPUART_SendDataBlocking()中使用的:
在组件反初始化(组件_Deinit()函数)时,调用调用了OSIF组件的API函数--OSIF_SemaDestroy()释放。比如S32K SDK的LPUART驱动用于数据收发的信号量rxComplete/txComplete,就在其反初始化函数LPUART_DRV_Deinit()中被销毁的:
从以上分析可知,通过在SDK组件全局状态结构体中引入信号量(Mutex/Semaphore)和使用操作系统接口OSIF组件API,既保证了S32 SDK外设底层驱动程序和中间件软件工作的同步和效率,又提高了SDK软件代码的在不同RTOS之间的可移植性,使用不同RTOS时,只需要关心和移植OSIF组件提供的API函数,即可保证S32 SDK外设底层驱动和中间件软件代码在该RTOS中的正常工作。
总结
虽然我已经力求尽量把它写的深入浅出,通俗易懂,阅读本文介绍的知识要点仍然需要一定的编程基础和嵌入式底层驱动程序开发经验积累,或者使用过一段时间的S32 SDK软件。不过没关系,在后续的《S32K SDK使用详解》系列技术分享文章中,我还将结合S32K1xx系列MCU的内核和外设功能特性,为大家带来更多的S32 SDK使用细节分解,希望大家可以持续关注和学习。
内容提要
引言
1. S32 SDK的软件分层架构介绍
1.1 外设驱动层(PD--Periperal Driver)
1.2 外设抽象层(PAL--Peripheral Abstract Layer)
1.3 中间件层(Middleware)
1.4 实时操作系统层(RTOS)
1.5 S32 SDK底层驱动软件分层的优点
2. S32 SDK的外设驱动组件全局状态结构体解析
3. S32 SDK外设底层驱动中断处理机制详解
4. S32 SDK的通信外设驱动组件API函数的阻塞与非阻塞发送与接收API函数差别与使用详解;
5. S32 SDK的外设驱动与RTOS之间的交互方式
总结
引言
S32 SDK(Software Development Kit,软件开发套件)借鉴了AutoSAR软件和ARM Cortext M系列MCU软件接口标准(CMSIS--Cortex Microcontroller Software Interface Standard)的编程思想,通过对内核和底层外设硬件驱动的抽象化,对应用层提供统一的API,不但大幅度地降低了MCU用户的软件开发难度和编程门槛,同时也大大提高代码的重用率和可移植性及可读性。使得基于MCU的嵌入式软件编程变得更加容易。
然而,相较于使用传统MCU--比如51单片机、S08和S12(X) MCU的底层软件开发, S32 SDK的软件编程模式和编程思想发生了很大的变化, 以适应日益复杂的MCU内核和外设资源。如果之前没有接触过系统的软件架构和软件编程思想知识体系的学习,也未使用过AutoSAR 软件和CMSIS库,则在使用S32 SDK时,将会发现很多困惑,不知道如何在应用层高效地调用SDK的API、如何通过事件通知(Event Notification)与底层驱动进行交互,以及如何在使用RTOS的系统中调用SDK提供的底层驱动API等?
鉴于此,本文将以S32K SDK为例,详细介绍S32 SDK的软件编程思想,以为大家解开以上疑惑,帮助大家更好的使用S32 SDK并真正理解其编程优势。
1. S32 SDK的软件分层架构介绍
S32 SDK的软件架构如下图所示,从上一篇文章--《S32K SDK使用详解之S32 SDK软件架构详解》解读中(点击文章标题即可快速跳转阅读),
可以看到S32 SDK除了提供底层驱动(LLD--Low Level Drivers)和中间件(Middleware)之外,还提供了不同工具链编译器的启动(Start-up)和链接文件(Linker filers) ,移植好的FreeRTOS和RTOS接口层(OSIF)以及处理器专家(Processor Expert)配置GUI和丰富的demo程序和详细的帮助文档:
具体来看,S32 SDK的软件分层情况如下:
1.1 外设驱动层(PD--Periperal Driver)
PD层组件通过调用对外设寄存器操作的硬件访问层(Hardware Access)API(一般为宏定义或者inline函数,且为静态static代码,仅PD层文件内部使用),读写不同S32系列MCU底层硬件外设IP模块寄存器,从而完成对外设的初始化配置、状态读取--即硬件交互;
在此基础上,根据不同的外设模块功能特性,编写相应的初始化函数、功能配置/使用函数以及中断处理函数(ISR),从而构成完成的外设底层驱动。这里面包含了很多外设初始化配置的时序/顺序要求(配置外设的initialization状态、Freeze状态、STOP、Standby/Shutdown以及nomal work状态)和特定寄存器的操作权限管理(比如管理员模式还是用户模式可以读写权限)、和寄存器操作方式控制(比如清除外设中断标志的方法是写1清零还是读清零)等。
Tips: S32 SDK的PD层包含了所支持MCU的几乎所有片上外设底层驱动,由于其代码直接操作外设IP寄存器,所以其效率是最高的,但不能实现跨MCU平台/系列使用。 如下为S32K SDK的PD层外设驱动组件列表:
1.2 外设抽象层(PAL--Peripheral Abstract Layer)
在PD层的基础上, S32 SDK封装了一个外设抽象层,将能够实现相同功能的外设PD组件封装为一个PAL功能组件,比如实现UART串行通信的外设,在S32K1xx系列上硬件外设为LPUART,在Qorivva MPC57xx和S32V234上则是LINFlex,在PAL就抽象了一个UART_PAL,对应用层提供统一的API函数,从而对应用层软件开发人员屏蔽了底层硬件外设的差别:
Tips: 使用S32 SDK的PAL层能够实现代码的跨平台移植和使用,典型的PAL组件包括CAN、UART、I2C、SPI和I2C等通信功能外设模块,PWM、OC及TIMING等定时器功能模块,以及MPU/MMU存储器保护/管理功能模块以及看门狗和信息安全等系统级功能模块。 如下为S32K SDK的PD层外设驱动组件列表:
1.3 中间件层(Middleware)
S32 SDK的中间件主要包括三大类:①功能静态库--比如电机控制库(AMMCLib)、触控感应库(Touch Sensing)等和②软件协议栈(stack)--比如以太网协议栈(TCP/IP)、LIN协议栈和USB及文件系统等,以及③ 复杂外围芯片驱动程序--比如SBC(UJA1169和UJA113x)系列驱动程序。
Tips: 如下为S32K SDK的Middleware组件列表,其中Touch Sense, ISELED和NFC Middleware需要联系NXP Sale/FAE申请license,在安装相应的SDK版本之后单独下载安装:
1.4 实时操作系统层(RTOS)
为了管理众多的MCU外设资源和存储器资源并进行实时的应用程序任务调度,在S32 SDK中还移植了实时操作系统--FreeRTOS,并未裸写代码提供了操作系统接口服务(Osif组件)。
Tips: 如下为S32K SDK的提供的FreeRTOS,在S32K SDK RTM 2.0.0中是FreeRTOS的版本为v8.2.1,在最新的Beta 2.9.0(及更高版本)中已经update到FreeRTOS v10.0.1了。
1.5 S32 SDK底层驱动软件分层的优点
综上所述,S32 SDK提供的底层外设驱动分层,具有如下优点:
① 分层提高了代码的可读性;
② 使用PD层,对保证对外设模块的操作实时性和代码运行效率;
③ 通过PAL层对底层硬件外设的抽象,对应用层提供了统一的API,保证了底层驱动代码在不同S32 MCU硬件平台上的通用性,提高了底层驱动代码的可移植性和使用效率;
④ 集成丰富的中间件,极大地提高了S32 MCU平台应用层软件的可扩展性和易用性;
⑤ 通过OSIF隔离硬件外设底层驱动程序与RTOS内核,保证底层驱动对不同RTOS的支持;
⑥ 通过提供针对不同软件开发工具链的启动文件(start-up)和链接文件(linker file),支持主流工具链--比如GNU GCC(S32DS IDE自带工具链)、DIAB、GHS和IAR等;
2. S32 SDK的外设驱动组件全局状态结构体解析
在S32 SDK中每一个外设驱动组件都有一个组件的全局状态结构体,以<组件/外设名>_state_t命名,用于存放该外设/组件的工作状态和硬件资源使用信息,主要包含以下信息:
① 外设模块的配置信息;
② 通信外设收发器的工作状态(isTxBbusy/isRxBusy);
③ 通信外设收发器是否为阻塞方式工作(isTxBlocking/isRxBlocking);
④ 中断回调函数指针(*callback)及参数(*callbackParam)定义;
⑤ 外设工作/通信使用的信号(semaphore)(TxComplete/RxComplete)和通信状态(transmitStatus/receiveStatus);
⑥ 通信外设收发数据的缓存(txBuff/rxBuff)和大小(txSize/rxSize);
⑦ 外设使用的DMA通道号;
以下是S32K SDK中LPUART的全局状态结构体lpuart_state_t的定义:
而非通信外设的时钟管理器全局状态结构体clock_manager_state_t如下:
Tips:通常在SDK外设驱动(PD)层定相应硬件外设的全局状态结构体结构体指针数组,而在外设抽象层(PAL)为其定义外设驱动工作时使用的全局状态结构体(如果包含多个相同的外设实体(instance),比如LPUART0/1/2或者FlexCAN0/1/2则为结构体数组,每个成员对应相应的外设实体):
比如S32K SDK中在LPUART的外设驱动层定义其工作时的全局状态结构体指针数组:
而在外设抽象层中,才定义其使用的全局状态结构体数组:
FlexCAN/CAN_PAL的驱动也是如此:
因此,如果使用PD层组件,用户还需组件定义全局状态结构体,而使用PAL层组件时,其将会定义该外设要使用的全局状态结构体,无需用户定义。
S32 SDK的LLD组件外设全局状态结构体在SDK组件的初始化API函数中被初始化,并与其全局状态结构体指针数组关联起来。比如在S32K SDK的LPUART驱动中,其全局状态结构体就是其初始化函数--LPUART_DRV_Init()中完成初始化的:
当执行某一个SDK组件的反初始化函数(Deinit)时,其全局状态结构体也将被清除(指向空指针NULL)。比如在S32K SDK的LPUART驱动中,其反初始化函数--LPUART_DRV_Deinit()实现代码如下:
每一个硬件外设模块工作时,只能有一个全局状态结构体与其对应,其使用生命周期是初始化完成(调用SDK组件Init()函数)到反初始化函数执行(调用SDK组件Deinit()函数)。
Tips:使用S32 SDK的外设底层驱动(LLD)时,推荐大家使用PAL组件,而不是PD组件;
Tips: S32 SDK的LLD组件外设全局状态结构体为定义在MCU SARM静态区(static)的全局变量,其包含对应外设模块的完整工作状态信息,并实时记录外设的工作状态,是调试LLD时最为重要的一个信息源,建议将其添加到变量(variable/expression)窗口查看以跟踪调试问题。
3. S32 SDK外设底层驱动中断处理机制详解
在基于MCU的嵌入式系统软件开发中,外设中断是保证系统软件工作实时性和内核效率最重要的工作机制----在外设事件(比如通信外设的数据接收完成,GPIO状态改变以及定时周期满等),CPU内核将停止当前程序指令的执行,及时响应外设中断请求并做相应的处理(比如搬移数据,填充缓冲,改变程序运行条件/状态、激活新任务等)。
在传统的嵌入式MCU底层外设驱动程序开发中,对于外设中断的处理流程/机制如下:
① POR复位后,在main()函数最开始完成系统时钟初始化后,初始化外设模块时,就配置使能相应的外设中断;
② 编写中断服务函数ISR,在其中处理中断事件,置位全局事件标志通知应用程序做后续处理, 并清除中断标志;
③ 将中断服务函数放到MCU的中断向量表中,这一步可能是通过关键词(interrupt)+中断向量号 + ISR定义的方式实现,工具链的编译器和链接器自动识别并加载到MCU中断向量表中;
④ 使能全局中断;
在整个MCU正常工作期间,除非特殊应用需要,不会关心外设中断的关闭与使能,
而在S32 SDK中,其进行了如下优化:
①外设初始化Init()函数中,仅使能芯片级中断控制器中对应的中断源(比如S32K的ARM Cortex M系列内核的NVIC,和Qorivva MPC57xx/S32R的PowerPC e200系列内核的INTC模块)并不使能相应外设的IP级中断(比如LPUART的TX和RX以及ERROR中断);
在具体使用外设的特定功能(比如LPUART的数据发送和接收)时,才使能该特定功能的中断(比如LPUART的发送中断和接收中断),在中断发生后,若相应的中断已经完全发生(比如发送10字节数据,全部发送完成),则在ISR的最后关闭该特定功能中断;
这样做的好处是,避免意外的中断浪费宝贵的CPU资源,比如由于程序跑飞导致的LPUART意外数据发送和接收;
② 在S32 SDK的PD层为绝大多数硬件外设模块提供了中断ISR函数,(对应S32K SDK来说,仅PD层的LPIT组件和GPIO的IRQ中断,需要用户自己使能NVIC中断并编写自己的中断ISR)无需用户自己编写;
在S32 SDK的组件中,相同外设的不同实体(比如LPUART0/1/2)共用同一个中断ISR,依靠外设实体号(instance)来区分不同的处理,由于相同外设的工作流程和中断处理流程相同,这样的好处是可以提高代码的复用效率:
如下是S32K144的SDK代码,其有LPUART0/1/2三个串口外设,其中断ISR定义如下:
而在真正的LPUART中断处理函数中依靠不同的instance来区别:
③ S32 SDK默认在startup过程中将外设中断向量表拷贝到SARM中,这样运行用户可以在程序运行时动态的修改中断ISR;
④ S32 SDK中为大部分外设都提供了中断处理函数(ISR)—位于外设驱动PD的中断处理文件(<Peripheral_Name>_irq.c/h)中,并在外设初始化时配置了相应的中断,其外设中断ISR属于SDK PD层驱动非常重要的一部分代码,不允许用户修改,因此提供了在外设中断ISR中调用用户中断回调函数的方式给用户提供中断处理接口。
Tips:在用户中断回调函数中,无需用户自己清除外设中断标志,因为在SDK提供的外设中断ISR中已经做了相应的处理。只有需要用户调用SDK中断管理器(interrupt_manager)的API--INT_SYS_InstallHandler()手动注册安装的外设中断ISR,才需要用户自己清外设中断标志,比如典型的GPIO的IRQ中断和PIT定时器中断。
Tips:使用PD层外设驱动组件,需要用户自己调用中断回调函数API自己安装中断Callback:
而若使用PAL层外设驱动组件,则可以在其Processor Expert图形化配置界面中添加中断Callback函数名,然后再用户代码中实现该Callback即可,无需用户自己调用中断回调函数安装API。
4. S32 SDK的通信外设驱动组件API函数的阻塞与非阻塞发送与接收API函数差别与使用详解;
在S32 SDK,通信外设组件都提供了阻塞(Blocking)与非阻塞两类发送与接收API函数,比如:
UART_PAL组件的API函数:
CAN_PAL组件的API函数:
其差别如下:
非阻塞类数据收发API,只是将要发送的数据buffer和长度size设置到对应的通信外设驱动的全局状态结构体中,使能数据发送器(transmiter)和接收器(receiver),打开收发和错误中断/若使用DMA收发,则配置相应的DMA通道TCD,使能DMA中断,安装中断回调函数,启动DMA传输, 然后就立即返回了,因此非阻塞类API的返回值总是成功(STATUS_SUCCESS)。
比如S32K SDK的LPUART非阻塞发送API--LPUART_DRV_SendData() 实现如下:
而阻塞(Blocking)类数据收发API,函数参数多了一个超时timeout(单位为ms)则在完成以上非阻塞API的工作后,还会调用OSIF组件的API函数--OSIF_SemaWait()检查外设驱动全局状态结构体中的数据收发信号量(semaphore),等待数据收发完成,若在设置的timeout时间内未完成数据传输,则返回超时(STATUS_TIMEOUT),若在timeout时间内数据收发完成,则返回成功(STATUS_SUCCESS)。
比如S32K SDK的LPUART阻塞(blocking)发送API--LPUART_DRV_SendDataBlocking()实现如下:
Tips:结合前面S32 SDK外设底层驱动中断处理机制的介绍可知,无论调用阻塞函数非阻塞数据收发函数,在成功接收完指定长度的数据后,都会将通信外设的接收器关闭,若想通信外设一直接收数据,则需要在该驱动组件的中断回调函数Callback中,设置新的数据接收buffer,从而避免在中断ISR最后,驱动程序关闭数据接收器:
比如使用S32K的LPUART接收GPS数据时,相应的数据接收中断回调函数定义如下,其中使用双buffer,在一个buffer接收完成后,调用UART_SetRxBuffer()切换新的buffer继续接收数据:
Tips:由于在阻塞API函数中,调用了OSIF组件的API函数--OSIF_SemaWait()检查外设驱动全局状态结构体中的数据收发信号量(semaphore), 若在应用工程中使用了RTOS,比如FreeRTOS,则调用该阻塞API函数的任务将被阻塞,让出CPU资源运行其他就绪的高优先级认为,直至收据收发完成(信号量释放)或者超时,这样CPU的执行效率将大大提高;而若应用工程中没有使用RTOS,则阻塞API函数将一直占有CPU,造成CPU资源浪费;
综上, 若应用工程使用了RTOS,则推荐使用阻塞API函数;若应用工程未使用RTOS,则推荐使用非阻塞API函数,并配合中断回调函数,判断数据收发状态并做相应处理。
5. S32 SDK的外设驱动与RTOS之间的交互方式
在S32 SDK中,所有的外设驱动(PD和PAL组件)以及中间件(Middleware)的软件代码实现,都使用了OSIF组件提供的操作系统接口API对外设驱动工作中的数据进行同步和事件时间延迟,从而保证了S32 SDK代码在不同RTOS之间的兼容:
其中使用的信号量都在相应的组件全局状态结构体中定义,在组件初始化(组件_Init()函数)时,调用了OSIF组件的API函数--OSIF_SemaCreate()创建,比如S32K SDK的LPUART驱动用于数据收发的信号量rxComplete/txComplete,就在其初始化函数LPUART_DRV_Init()中创建的:
在组件的阻塞(Blocking)API函数中调用OSIF组件的API函数--OSIF_SemaWait()检查外设驱动全局状态结构体中定义的信号量(semaphore)以完成事件同步。比如S32K SDK的LPUART驱动中数据发送完成信号量txComplete,就在其阻塞数据发送API函数LPUART_SendDataBlocking()中使用的:
在组件反初始化(组件_Deinit()函数)时,调用调用了OSIF组件的API函数--OSIF_SemaDestroy()释放。比如S32K SDK的LPUART驱动用于数据收发的信号量rxComplete/txComplete,就在其反初始化函数LPUART_DRV_Deinit()中被销毁的:
从以上分析可知,通过在SDK组件全局状态结构体中引入信号量(Mutex/Semaphore)和使用操作系统接口OSIF组件API,既保证了S32 SDK外设底层驱动程序和中间件软件工作的同步和效率,又提高了SDK软件代码的在不同RTOS之间的可移植性,使用不同RTOS时,只需要关心和移植OSIF组件提供的API函数,即可保证S32 SDK外设底层驱动和中间件软件代码在该RTOS中的正常工作。
总结
虽然我已经力求尽量把它写的深入浅出,通俗易懂,阅读本文介绍的知识要点仍然需要一定的编程基础和嵌入式底层驱动程序开发经验积累,或者使用过一段时间的S32 SDK软件。不过没关系,在后续的《S32K SDK使用详解》系列技术分享文章中,我还将结合S32K1xx系列MCU的内核和外设功能特性,为大家带来更多的S32 SDK使用细节分解,希望大家可以持续关注和学习。