不用串口,如何打印STM32单片机log

发布时间:2024-04-30  

本文主要介绍在嵌入式开发中用来输出log的方法。

最常用的是通过串口输出uart log,这种方法实现简单,大部分嵌入式芯片都有串口功能。但是这样简单的功能有时候却不是那么好用,比如:


一款新拿到的芯片,没有串口驱动时如何打印log

某些应用下对时序要求比较高,串口输出log占用时间太长怎么办?比如USB枚举。

某些bug正常运行时会出现,当打开串口log时又不再复现怎么办

一些封装中没有串口,或者串口已经被用作其他用途,要如何输出log

下文来讨论这些问题。

1输出log信息到SRAM

准确来说这里并不是输出log,而是以一种方式不使用串口就可以看到log。在芯片开发阶段都可以连接仿真器调试,可以使用打断点的方法调试,但是有些操作如果不能被打断就没法使用断点调试了。 这时候可以考虑将log打印到SRAM中,整个操作结束后再通过仿真器查看SRAM中的log buffer,这样就实现了间接的log输出。

本文使用的测试平台是STM32F407 discovery,基于usb host实验代码,对于其他嵌入式平台原理也是通用的。首先定义一个结构体用于打印log,如下:

ccfa1872-6658-11ed-8abf-dac502259ad0.png

定义一段SRAM空间作为log buffer:



static u8 log_buffer[LOG_MAX_LEN];


log buffer是环形缓冲区,在小的buffer就可以无限打印log,缺点也很明显,如果log没有及时输出就会被新的覆盖。Buffer大小根据SRAM大小分配,这里使用1kB。为了方便输出参数,使用printf函数来格式化输出,需要做如下配置(Keil):

cd0bd724-6658-11ed-8abf-dac502259ad0.png

并包含头文件#include , 在代码中实现函数fputc():

cd298d8c-6658-11ed-8abf-dac502259ad0.png

写入数据到SRAM:

cd3aab44-6658-11ed-8abf-dac502259ad0.png

为了方便控制log打印格式,在头文件中再添加自定义的打印函数。

cd4e81dc-6658-11ed-8abf-dac502259ad0.png

在需要打印log的地方直接调用DEBUG()即可,最终效果如下,从Memory窗口可以看到打印的log:

cd663138-6658-11ed-8abf-dac502259ad0.png

2通过SWO输出log

通过打印log到SRAM的方式可以看到log,但是数据量多的时候可能来不及查看就被覆盖了。为了解决这个问题,可以使用St-link的SWO输出log,这样就不用担心log被覆盖。查看原理图f407 discovery的SWO已经连接了,否则需要自己飞线连接:

cd7a09d8-6658-11ed-8abf-dac502259ad0.png

在log结构体中添加SWO的操作函数集:


typedef struct

{

    u8 (*init)(void* arg);

    u8 (*print)(u8 ch);

    u8 (*print_dma)(u8* buffer, u32 len);

}log_func;



typedef struct

{

volatile u8     type;

    u8*             buffer;

volatile u32    write_idx;

volatile u32    read_idx;

//SWO

    log_func*       swo_log_func;

}log_dev;

SWO只需要print操作函数,实现如下:


u8 swo_print_ch(u8 ch)

{

    ITM_SendChar(ch);

return 0;

}

使用SWO输出log同样先输出到log buffer,然后在系统空闲时再输出,当然也可以直接输出。log延迟输出会影响log的实时性,而直接输出会影响到对时间敏感的代码运行,所以如何取舍取决于需要输出log的情形。


在while循环中调用output_ch()函数,就可以实现在系统空闲时输出log。


/*output log buffer to I/O*/

void output_ch(void)

{   

    u8 ch;

    volatile u32 tmp_write,tmp_read;

    tmp_write = log_dev_ptr->write_idx;

    tmp_read = log_dev_ptr->read_idx;



if(tmp_write != tmp_read)

    {

        ch = log_dev_ptr->buffer[tmp_read++];

//swo

if(log_dev_ptr->swo_log_func)

            log_dev_ptr->swo_log_func->print(ch);

if(tmp_read >= LOG_MAX_LEN)

        {

            log_dev_ptr->read_idx = 0;

        }

else

        {

            log_dev_ptr->read_idx = tmp_read;

        }

    }

}


2.1 通过IDE输出

使用IDE中SWO输出功能需要做如下配置(Keil):

cd9bc870-6658-11ed-8abf-dac502259ad0.png

在窗口可以看到输出的log:

cdc08214-6658-11ed-8abf-dac502259ad0.png

2.2 通过STM32 ST-LINK Utility输出

使用STM32 ST-LINK Utility不需要做特别的设置,直接打开ST-LINK菜单下的Printf via SWO viewer,然后按start:

cdcf8d90-6658-11ed-8abf-dac502259ad0.png

在log结构体中添加SWO的操作函数集:


typedef struct

{

    u8 (*init)(void* arg);

    u8 (*print)(u8 ch);

    u8 (*print_dma)(u8* buffer, u32 len);

}log_func;



typedef struct

{

volatile u8     type;

    u8*             buffer;

volatile u32    write_idx;

volatile u32    read_idx;

//SWO

    log_func*       swo_log_func;

}log_dev;

SWO只需要print操作函数,实现如下:


u8 swo_print_ch(u8 ch)

{

    ITM_SendChar(ch);

return 0;

}

使用SWO输出log同样先输出到log buffer,然后在系统空闲时再输出,当然也可以直接输出。log延迟输出会影响log的实时性,而直接输出会影响到对时间敏感的代码运行,所以如何取舍取决于需要输出log的情形。


在while循环中调用output_ch()函数,就可以实现在系统空闲时输出log。


/*output log buffer to I/O*/

void output_ch(void)

{   

    u8 ch;

    volatile u32 tmp_write,tmp_read;

    tmp_write = log_dev_ptr->write_idx;

    tmp_read = log_dev_ptr->read_idx;



if(tmp_write != tmp_read)

    {

        ch = log_dev_ptr->buffer[tmp_read++];

//swo

if(log_dev_ptr->swo_log_func)

            log_dev_ptr->swo_log_func->print(ch);

if(tmp_read >= LOG_MAX_LEN)

        {

            log_dev_ptr->read_idx = 0;

        }

else

        {

            log_dev_ptr->read_idx = tmp_read;

        }

    }

}

DMA输出到串口的函数如下:

cdfd94e2-6658-11ed-8abf-dac502259ad0.png

这里为了方便直接使用了查询DMA状态寄存器,有需要可以修改为DMA中断方式,查Datasheet可以找到串口2使用DMA1 channel4的stream6:

ce0fab32-6658-11ed-8abf-dac502259ad0.png

最后在PC端串口助手可以看到log输出:

ce2211aa-6658-11ed-8abf-dac502259ad0.png

使用DMA搬运log buffer中数据到串口,同时CPU可以处理其他事情,这种方式对系统影响最小,并且输出log及时,是实际使用中用的最多的方式。并且不仅可以用串口,其他可以用DMA操作的接口(如SPI、USB)都可以使用这种方法来打印log。

4使用IO口模拟串口输出log

最后要讨论的是在一些封装中没有串口,或者串口已经被用作其他用途时如何输出log,这时可以找一个空闲的普通IO,模拟UART协议输出log到上位机的串口工具。常用的UART协议如下:

ce326b5e-6658-11ed-8abf-dac502259ad0.png


只要在确定的时间在IO上输出高低电平就可以模拟出波形,这个确定的时间就是串口波特率。为了得到精确延时,这里使用TIM4定时器产生1us的延时。注意:定时器不能重复用,在测试工程中TIM2、3都被用了,如果重复用就错乱了。初始化函数如下:


u8 simu_log_init(void* arg)

{

    TIM_TimeBaseInitTypeDef TIM_InitStructure;  

    u32* bound = (u32*)arg;

//GPIO端口设置

    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //速度50MHz

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉

    GPIO_Init(GPIOA,&GPIO_InitStructure);

    GPIO_SetBits(GPIOA, GPIO_Pin_2);

//Config TIM

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //使能TIM4时钟

    TIM_DeInit(TIM4);

    TIM_InitStructure.TIM_Prescaler = 1;        //2分频

    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;

    TIM_InitStructure.TIM_Period = 41;          //1us timer

    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;

    TIM_TimeBaseInit(TIM4, &TIM_InitStructure);

    TIM_ClearFlag(TIM4, TIM_FLAG_Update);

    baud_delay = 1000000/(*bound);          //根据波特率计算每个bit延时

return 0;

}

使用定时器的delay函数为:


image.png


最后是模拟输出函数,注意:输出前必须要关闭中断,一个byte输出完再打开,否则会出现乱码:


u8 simu_print_ch(u8 ch)

{

volatile u8 i=8;

    __asm("cpsid i");

//start bit

    GPIO_ResetBits(GPIOA, GPIO_Pin_2);

    simu_delay(baud_delay);

while(i--)

    {

if(ch & 0x01)

        GPIO_SetBits(GPIOA, GPIO_Pin_2);

else

        GPIO_ResetBits(GPIOA, GPIO_Pin_2);

        ch >>= 1;

        simu_delay(baud_delay);

    }

//stop bit

    GPIO_SetBits(GPIOA, GPIO_Pin_2);

    simu_delay(baud_delay);

    simu_delay(baud_delay);

    __asm("cpsie i");

return 0;

}

使用IO模拟可以达到与真实串口类似的效果,并且只需要一个普通IO,在小封装芯片上比较使用。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>