精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

不用串口,如何打印STM32單片機log

STM32嵌入式開發 ? 來源:STM32嵌入式開發 ? 作者:STM32嵌入式開發 ? 2022-11-18 11:37 ? 次閱讀

本文主要介紹在嵌入式開發中用來輸出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

3通過串口輸出log

以上都是在串口log暫時無法使用,或者只是臨時用一下的方法,而適合長期使用的還是需要通過串口輸出log,畢竟大部分時候沒法連接仿真器。添加串口輸出log只需要添加串口的操作函數集即可:


typedef struct
{
volatile u8     type;
    u8*             buffer;
volatile u32    write_idx;
volatile u32    read_idx;
volatile u32    dma_read_idx;
//uart
    log_func*       uart_log_func;
//SWO
    log_func*       swo_log_func;
}log_dev;

實現串口驅動函數:

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

添加串口輸出log與通過SWO過程類似,不再多敘述。而下面要討論的問題是,串口的速率較低,輸出數據需要較長時間,嚴重影響系統運行。

雖然可以通過先打印到SRAM再延時輸出的辦法來減輕影響,但是如果系統中斷頻繁,或者需要做耗時運算,則可能會丟失log。要解決這個問題,就是要解決CPU與輸出數據到串口同時進行的問題,嵌入式工程師立馬可以想到DMA正是好的解決途徑。

使用DMA搬運log數據到串口輸出,同時又不影響CPU運行,這樣就可以解決輸出串口log耗時影響系統的問題。串口及DMA初始化函數如下:


u8 uart_log_init(void* arg)
{
    DMA_InitTypeDef DMA_InitStructure;
    u32* bound = (u32*)arg;
//GPIO端口設置
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;


    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA時鐘
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2時鐘
//串口2對應引腳復用映射
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
//USART2端口配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能
    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);
//USART2初始化設置
    USART_InitStructure.USART_BaudRate = *bound;//波特率設置
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制
    USART_InitStructure.USART_Mode = USART_Mode_Tx; //收發模式
    USART_Init(USART2, &USART_InitStructure); //初始化串口1
#ifdef LOG_UART_DMA_EN  
    USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE);
#endif
    USART_Cmd(USART2, ENABLE);  //使能串口1 
    USART_ClearFlag(USART2, USART_FLAG_TC);
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
#ifdef LOG_UART_DMA_EN
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
//Config DMA channel, uart2 TX usb DMA1 Stream6 Channel
    DMA_DeInit(DMA1_Stream6);
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; 
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream6, &DMA_InitStructure);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
#endif
return 0;
}

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函數為:

cf20b214-6658-11ed-8abf-dac502259ad0.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,在小封裝芯片上比較使用。

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 單片機
    +關注

    關注

    6032

    文章

    44514

    瀏覽量

    632951
  • 嵌入式
    +關注

    關注

    5068

    文章

    19014

    瀏覽量

    303231
  • STM32
    +關注

    關注

    2266

    文章

    10871

    瀏覽量

    354789
  • Log
    Log
    +關注

    關注

    0

    文章

    14

    瀏覽量

    11312

原文標題:不用串口,如何打印STM32單片機log

文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    STM32單片機串口接收數據的方法

    串口作為單片機開發的一個常用的外設,應用范圍非常廣。大部分時候,串口需要接收處理的數據長度是不定的。那么怎么才能判斷一幀數據是否結束呢,今天就以STM32
    發表于 09-21 14:39 ?1.2w次閱讀

    單片機串口模塊調試方法

    筆者在調試某Cortex-M3內核單片機時遇到一個問題,此單片機不具備在線仿真功能,因此調試代碼時只能使用UART輸出Log的方式調試。調試過程中發現串口模塊會影響
    發表于 07-28 15:48 ?1487次閱讀
    <b class='flag-5'>單片機</b><b class='flag-5'>串口</b>模塊調試方法

    有哪幾種辦法可實現單片機像在pc終端一樣打印log

    有哪幾種辦法可實現單片機像在pc終端一樣打印log呢?stm32實現printf打印log的辦法
    發表于 12-01 06:39

    單片機是如何實現printf打印串口

    軟件顯示區了! 和電腦端一樣用!串口初始化代碼部分,以STM32為例,其他單片機也一樣,只是修改成對應的單片機寄存器即可,整個邏輯是一樣的若只是實現printf
    發表于 02-16 07:10

    STM32 單片機串口通信仿真測試技術研究_郭勇.pdf下載

    STM32單片機串口通信仿真測試技術
    發表于 04-22 11:02 ?14次下載
    <b class='flag-5'>STM32</b> <b class='flag-5'>單片機</b>多<b class='flag-5'>串口</b>通信仿真測試技術研究_郭勇.pdf下載

    基于STM32單片機串口通信資源

    基于STM32單片機串口通信資源
    發表于 07-05 09:13 ?21次下載

    STM32 LoRa無線數傳模塊 PC通過串口傳輸數據到單片機

    STM32F1單片機,燒錄代碼后,連接LoRa無線數傳模塊,在PC上面使用串口助手,通過串口傳輸數據到單片機
    發表于 11-19 11:51 ?79次下載
    <b class='flag-5'>STM32</b> LoRa無線數傳模塊 PC通過<b class='flag-5'>串口</b>傳輸數據到<b class='flag-5'>單片機</b>

    【轉】STC89C52RC單片機實現串口打印功能

    【轉】STC89C52RC單片機實現串口打印功能
    發表于 11-25 16:06 ?19次下載
    【轉】STC89C52RC<b class='flag-5'>單片機</b>實現<b class='flag-5'>串口</b><b class='flag-5'>打印</b>功能

    單片機實現 printf 打印輸出,和電腦端一樣用

    軟件顯示區了! 和電腦端一樣用!串口初始化代碼部分,以STM32為例,其他單片機也一樣,只是修改成對應的單片機寄存器即可,整個邏輯是一樣的若只是實現printf
    發表于 12-17 18:32 ?1次下載
    <b class='flag-5'>單片機</b>實現 printf <b class='flag-5'>打印</b>輸出,和電腦端一樣用

    stm32單片機串口使用printf及u3_printf

    無論是在51單片機還是在stm32,默認printf串口都是串口一。使用printf的時候頭文件為&amp;quot;stdio.h&amp;quot;,但是一些
    發表于 12-27 19:24 ?1次下載
    <b class='flag-5'>stm32</b><b class='flag-5'>單片機</b><b class='flag-5'>串口</b>使用printf及u3_printf

    STM32F103ZET6單片機串口互發程序設計與實現

    STM32庫函數開發系列文章目錄第一篇:STM32F103ZET6單片機串口互發程序設計與實現文章目錄STM32庫函數開發系列文章目錄前言
    發表于 12-28 19:03 ?18次下載
    <b class='flag-5'>STM32</b>F103ZET6<b class='flag-5'>單片機</b>雙<b class='flag-5'>串口</b>互發程序設計與實現

    不用串口,如何打印STM32單片機log?

    本文主要介紹在嵌入式開發中用來輸出log的方法。
    發表于 02-08 15:42 ?3次下載
    <b class='flag-5'>不用</b><b class='flag-5'>串口</b>,如何<b class='flag-5'>打印</b><b class='flag-5'>STM32</b><b class='flag-5'>單片機</b><b class='flag-5'>log</b>?

    STM32與51單片機原理圖及串口通信實例

    分別編寫STM32與51單片機程序,通過串口通信,實現STM32按鍵控制51單片機LED的實驗效果。
    發表于 12-28 14:27 ?7597次閱讀

    stm32f103zet6單片機串口互發程序

    為什么用51單片機調試串口藍牙模塊或者是串口wifi模塊很困難呢?因為串口只有一個,串口一旦用于與模塊通信之后,就沒有辦法進行調試信息的
    發表于 01-05 15:44 ?5次下載

    51單片機串口配置方法

    串口,作為單片機程序開發中最常用、最方便,也是應用最廣泛的程序調試方法;無論是作為調試工具,打印出調試信息,還是對功能模塊進行通信,串口是每個單片機
    的頭像 發表于 04-14 14:58 ?4863次閱讀
    51<b class='flag-5'>單片機</b><b class='flag-5'>串口</b>配置方法