汽车电子expert成长之路

本博客发布的个人原创精品----嵌入式系统技术文章,欢迎大家参考学习,并转发分享!

浅谈嵌入式软件开发之重定向标准输入输出设备使用printf()函数格式化输出调试信息

0
阅读(422) 评论(0)

浅谈嵌入式软件开发之重定向标准输入输出设备使用printf()函数格式化输出调试信息(基于S32DS IDE和MPC5744P)

内容提要

引言

1. 重定向JTAG调试接口的半主机(semi-hosting)模式为标准输入输出设备

    1.1 创建工程时,选择I/0 Support选择为Debugger Console

    1.2 设置应用工程Library Support为ewl_c_Debugger Console(-specs=ewl_c9x_hosted.specs)

2. 重定MCU的UART模块为标准输入输出设备

    2.1 DEVKIT-MPC5744P评估板的UART资源及SDK中uart_pal组件的配置

    2.2 配置应用工程Library support为ewl_c no I/O(-specs_c9x_noio.specs)

    2.3 在应用工程中添加printf.c、uart_console_io.c、uart.h和uart.c

3. 调用printf()输出格式化调试信息

    3.1  调用printf()输出格式化调试信息的方法和步骤

    3.2 使用JTAG的半主机模式作为EWL库标准输入输出设备的打印结果

    3.3 使用UART作为EWL库标准输入输出设备的打印结果

4. 使用printf()打印调试信息的优缺点

总结


引言


    不知道大家注意过没,在嵌入式Linux程序的开发过程中,在调试内核(Kernel)和板级支持包(BSP, 包含各种片上外设和板载外设模块的驱动程序)以及应用程序代码时,往往不使用调试器(Debugger,比如SWD、JTAG等),而是通过调试器在最开始将MCU/MPU的引导加载程序(bootloader,常见的Linuxbootloader为U-BOOT)烧写到板载启动非易失存储器(典型如Nor Flash或者SD卡)中,再通过bootloader由串口(UART/RS232接口)或者以太网(10/100 Mbit/s Ethernet)以FPT形式加载内核和应用程序,而下载程序的串口往往也是调试开发阶段甚至是量产后维护程序的通信接口,在调试定位问题时,常用到C原因的标准输入输出库的printf()函数打印调试信息/错误信息和应用程序运行接口,板载串口就是嵌入式Linux程序开发时的标准输入输出端口,在调试程序定位软件代码问题时极为重要,使用也极为方便高效。

    其实,任意嵌入式系统,哪怕不使用任何操作系统“裸奔”的嵌入式MCU应用工程,也可以使用printf(),将UART串口或者其他通信接口(比如SPI、USB等),甚至LCD显示屏或者调试接口重定向为C语言标准输入输出库的标准输入输出设备,为printf()使用。

    在NXP/Freescale的32-bit汽车级MCU使用的应用程序开发IDE--CodeWarrior 2.10、CodeWarrior 10.6/7/11.0以及S32DS for ARM/Power/Vision IDE广泛(新建工程默认)使用的EWL(Embedded Warrior Library)嵌入式 C/C++库中就包含了标准输入输出stdio的支持,从而可以轻易实现printf()重定向(redirect)输出。    

    接下来,我就以S32DS for Power v2017.R1 IDE为例,介绍在DEVKIT-MPC5744P评估板上,如何重定向JTAG半主机(semi-hosting)和UART为EWL C语言库的标准输入输出设备,然后使用printf()打印调试信息的具体方法和步骤。


1. 重定向JTAG调试接口的半主机(semi-hosting)模式为标准输入输出设备

    使能调试接口的半主机模式重定向为EWL库的标准输入输出设备的方法有如下两种:

1.1 创建工程时,选择I/0 Support选择为Debugger Console

    第一种方法是,在S32DS for ARM/Power IDE的新应用工程创建向导中,选择I/0 Support选择为Debugger Console:


10.jpg

1.2 设置应用工程Library Support为ewl_c_Debugger Console(-specs=ewl_c9x_hosted.specs)

    另一种方法是,在应用工程属性设置中,选中想要使能该功能的编译目标(比如本例的Debug_Semi-Host),将C/C++ Build-->Settings-->Tool Settings-->Target Processor的Library Support设置为ewl_c_Debugger Console(-specs=ewl_c9x_hosted.specs):

11.jpg

2. 重定MCU的UART模块为标准输入输出设备

    通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),简称UART,是一种低成本的异步全双工通信,其通过PL 2303、CH341等廉价的USB转串口芯片即可与PC的COM互联通信,在PC上使用串口调试助手即可查看和控制(收发)UART的通信数据,在嵌入式系统开发中广泛使用。

    NXP的32-bit Qorivva MPC57xx和S32R系列汽车级MCU使用的LINFlexD/eSCI模块和S32K1xx系列使用的LPUART都可以通过简单的配置支持UART通信。

2.1 DEVKIT-MPC5744P评估板的UART资源及SDK中uart_pal组件的配置

    具体到DEVKIT-MPC5744P评估板,其LINFlexD_1模块通过板载的OpenSDA转换为虚拟串口,从而可以方便地与PC连接:

12.jpg



MPC5744P的PD9/12(FlexLIN0_TXD/RXD)与OpenSDA通过电平转换芯片连接:

13.jpg




    相应地,我们在S32DS应用工程中添加的SDK的uart_pal组件:

14.jpg



    将uart_pal组件的底层实现设备(Device)选择为LINFlexD_1,并对通信波特率和通信格式等进行简单配置,即可:

15.jpg



    与此同时,需要在工程的pin_mux组件中,将相应的LINFlexD_1模块的TXD和RXD引脚分配到PTD9和PTD12上:

16.jpg




    完成以上配置,并生成代码后,就可以在应用程序中调用该组件的API函数完成UART通信的初始化和数据收发了:

/* Initialize UART */

UART_Init(INST_UART_PAL1, &uart_pal1_Config0);

/* Send 8 frames */

UART_SendData(INST_UART_PAL1, tx, 8U);

while(UART_GetTransmitStatus(INST_UART_PAL1, &bytesRemaining) != STATUS_SUCCESS);

/* Receive 8 frames */

UART_ReceiveData(INST_UART_PAL1, rx, 8UL);

/* Wait for transfer to be completed */

while(UART_GetReceiveStatus(INST_UART_PAL1, &bytesRemaining) != STATUS_SUCCESS);

/* De-initialize UART */

UART_Deinit(INST_UART_PAL1);


2.2 配置应用工程Library support为ewl_c no I/O(-specs_c9x_noio.specs)


    为了将MPC5744P的UART重定向为EWL库的标准输入输出设备,需要确保应用工程的Library support为ewl_c no I/O(-specs_c9x_noio.specs):


    方法有如下两种:


    方法①、通过应用工程属性设置



17.jpg

    方法②、在新建应用工程时,选择使用Library:EWL,同时I/O Support为No I/O




18.jpg

    Tips:S32DS IDE还支持另外三种库--EWL Nano、NewLib和NewLib Nano,其中Nano时简配,轻量级的意思,则三个库时不支持printf()标准输入输出的,EWL是官方推荐使用的库。



19.jpg

2.3 在应用工程中添加printf.c、uart_console_io.c、uart.h和uart.c


    为了帮助用户实现UART重定向为EWL库的标准输入输出设备,在EWL库的安装目录下提供了相应的接口API函数和头文件。用户只需要将其添加到应用工程中,并做相应的修改即可完成重定向。具体步骤如下:

    将S32DS for Power v2017.R1 IDE安装目录下的以下文件:

    ①e200_ewl2\EWL_C\src\stdio库目录下的printf.c:



20.jpg

    ②e200_ewl2\EWL_C\src\sys库目录下的uart_console_io.c:



21.jpg

    ③e200_ewl\EWL_C\include\sys库目录下的uart.h:



22.jpg

    Tips:在该文件中包含了重定向UART为EWL库标准输入输出设备必要的数据结构和API函数:



23.jpg

    ④创建uart.c,调用SDK中uart_pal组件的UART初始化、数据收发等API函数实现以上uart.h中的API功能函数:


UARTError InitializeUART(UARTBaudRate baudRate)

{

/*set the communication baudrate*/

uart_pal1_Config0.baudRate = baudRate;

/* Initialize UART PAL over LINFlexD */

UART_Init(INST_UART_PAL1, &uart_pal1_Config0);

return kUARTNoError;

}

UARTError ReadUARTN(void* bytes, unsigned long limit)

{

UARTError err;

/*

* call LINFlexD to receive several byte within 1000 count delay

*/

err = UART_ReceiveDataBlocking(INST_UART_PAL1,bytes,limit,1000);

return err;

}

UARTError ReadUARTPoll(char* c)

{

   UARTError err = kUARTNoError;

    /*

    * call LINFlexD to receive several bytes within 1000 count delay

   */

   err = UART_ReceiveDataBlocking(INST_UART_PAL1,c,1,100);

    return err;

}

UARTError WriteUARTN(const void * bytes, unsigned long length)

{

UARTError err = kUARTNoError;

uint32_t Byte_Left = 0;

/*

* call LINFlexD to send several bytes

*/

err = UART_SendData(INST_UART_PAL1,(const char * const)bytes,length);

/*wait for transmit complete*/

while(STATUS_SUCCESS !=  UART_GetTransmitStatus(INST_UART_PAL1,&Byte_Left));

  return err;

}

    将以上四个文件添加在应用工程的Sources和include目录下:


24.jpg


3. 调用printf()输出格式化调试信息

3.1  调用printf()输出格式化调试信息的方法和步骤

    在完成以上准备工作之后,在应用工程中只需要如下简单步骤即可使用printf()函数了:


    首先,在需要调用printf的C文件中包含标准输入输出库头文件--stdio.h

    #include "stdio.h"

    然后,就可以调用printf函数,格式化打印输出调试信息了。

    比如在我的测试工程中,将一个32-bit的main()函数主循环计数器按照十进制和十六进制打印出来:



25.jpg

    3.2 使用JTAG的半主机模式作为EWL库标准输入输出设备的打印结果

    将本例应用工程的编译目标选择为Debug_Semi-Host编译成功,并启动相应的调试目标将其编译结果下载到DEVKIT-MPC5744P评估板:



    26.jpg

    在S32DS IDE的调试界面半主机控制台(Semihosting Console)中就可以看到printf()函数打印输出的结果:



27.jpg

Tips:如果启动调试运行后,在debugger_console中未发现printf()的打印信息,或者程序进入CPU内核异常IVOR8_Vector:




28.jpg

    此时,请检查应用工程的调试目标(Debug Configuration)-->Satrup配置的Semihosting Settings,确认其半主机模式使能,并进行了相应的正确配置:



29.jpg

    Tips:可以使用条件编译,在量产时去掉printf()定义为一个空函数,以保证应用程序在正常模式下(不连接调试器debug--debug模式)能够正常工作,同时还有省去stdio标准库所占的Flash空间;

    使能__DEBUG_ENABLE宏定义,此时,printf()调用stdio标准输入输出库,通过JTAG的半主机模式实现调试信息打印:

10.jpg

    相应的应用工程代码编译结果如下:



31.jpg





    如果注释掉__DEBUG_ENABLE宏定义,此时,printf()为空定义,所有调用printf()的地方为空函数替代,无需JTAG调试器连接和半主机通信:

32.jpg



    此时的应用工程代码编译结果如下:

33.jpg




    从上面的编译结果可以看到,调用stdio标准输入输出库的printf()将增加(21957-6570)=15397字节的Flash空间(.text代码段)和(7980-7208)=772字节的SRAM空间(.bss未初始化段,作为发送接收缓存)


3.3 使用UART作为EWL库标准输入输出设备的打印结果


    将本例应用工程的编译目标选择为Debug_UART编译成功,并启动相应的调试目标将其编译结果下载到DEVKIT-MPC5744P评估板:



34.jpg

    在打开串口调试助手,配置好(保持与MCU UART模块配置一致)通信波特率和格式,选择OpenSDA模拟的虚拟串口作为通信设备,全速运行或者复位目标MCU即可看到程序从串口输出的调试信息:



35.jpg

    Tips:为了让程序正常工作,此时的调试目标配置不能使能半主机模式:


36.jpg

4. 使用printf()打印调试信息的优缺点


    1. 利用C语言标准库(比如EWL_C库)的printf()函数,可以实现嵌入式应用程序调试信息/数据的多种格式转换,轻松实现调试信息的格式化输出,但需要占用额外的Flash存储空间,因此,不建议在小尺寸Flash的MCU应用工程中使用printf();


    2. 使用调试接口(比如JTAG和ARM Cortex M系列MCU的SWD接口)的半主机通信,不占用MCU外设通信接口资源,但由于复用了正常的调试接口信号线,会占用调试器通信带宽,所以会降低调试速度,进而影响调试效率,建议使用能够支持较高调试速度的调试器(比如Multilink FX,调试速度Multilink FX > Multilink > OSBDM/OpenSDA);另外,此种方法,脱机不能运行,所以量产时必须去掉应用程序中的所有printf()函数或者将其定义为空函数;若将其重映射到串口或者其他外设,则会占用相应的硬件资源,造成浪费,但好处是与调试接口独立,无需连接调试器和打开调试软件,使用串口调试助手等作为上位机软件即可,通信速度快且稳定。


    Tips:不同的C语言库标准输入输出设备重定向实现方法使用的print上位机软件及其优缺点总结如下,供大家参考:

重定向实现方法print上位机软件优点缺点
调试接口半主机通信Debugger_Console配置简单,无需占用外设资源影响调试器调试效率,无法脱机运行
UART外设通信串口调试助手不影响调试接口工作,工作稳定需要占用UART通信外设硬件资源


总结


    当嵌入式MCU连接调试器(debuger)时,虽然可以进行单步调试(可以实现进入/跳出函数,运行至指定断点地址,汇编指令级调试,查看内核和外设寄存器值,以及断点处的存储器和全局变量/堆栈数据以及更高级的指令和数据trace功能),但此时MCU处于调试模式(S08/S12(x)和S12Z系列MCU称作BDM special模式/Freeze冻结模式,Qorivva MPC56/57xx系列称作Debug调试模式),其内核和外设模块的工作状态与正常模式(Normal Mode)下存在一些差异,比如:


    ①S12(X)/MagniV S12Z系列MCU的COP看门狗配置寄存器CPMUCOP,在BDM special模式下可以多次配置(改写),而在正常模式下每次reset之后仅能配置一次(write once):


37.jpg


    ②通过PIT模块的MCR寄存器的FRZ位可以控制Qorivva MPC56/57xx系列MCU的PIT定时器在调试模式下是否继续工作(调试暂停正常计数):


38.jpg


    通过使用重定义C语言库的标准输入输出设备的方式,调用printf()函数格式化打印调试信息的方法,将调试时关心的寄存器和变量数据等输出到调试软件,可以让嵌入式应用程序的在线调试过程也处于正常工作模式下,从而确保了调试结果与最终量产时的程序运行结果的一致性;


    本文介绍的方法虽然基于S32DS for Power v2017.R1和MPC5744P,同样适用于S32DS for ARM IDE和S32K1xx系列MCU以及S32DS for Power IDE和其他Qorvva MPC56/57xx,S32R系列MCU。


    另外,本demo工程使用S32DS for Power v2017.R1和MPC57xx SDK v0.8.2,为了正常使用该工程,请务必确保相应版本的软件IDE和SDK已经正确安装。