一例離奇的STM32Cube串口數(shù)據(jù)亂碼解決過程分析

使用的開發(fā)環(huán)境是STM32CubeMX+VSCode-PlatformIO,用CubeMX初始化外設(shè),然后導(dǎo)入到PlatformIO中開發(fā)實(shí)際應(yīng)用。


引腳配置圖

中斷配置圖

時鐘配置圖

結(jié)果出師不利,前后折騰將近一整天,始終輸出亂碼。


亂碼

嘗試的方法如下:

  1. 降低波特率
    低波特率一般不容易出錯,容錯率較高。


    image.png

    依舊是亂碼。

  2. 降低系統(tǒng)頻率
    過高的CPU主頻會降低系統(tǒng)穩(wěn)定性,是不是CPU跑飛了?加上LED指示燈代碼,顯示依然是正常的。繼續(xù)修改系統(tǒng)頻率(原本設(shè)置為滿頻率168Mhz,后降為84Mhz)


    亂碼
  3. 嘗試避免使用中斷方式發(fā)送
    用HAL_UART_Transmit()函數(shù)發(fā)送,依然亂碼。

  4. 嘗試換用外接TTL轉(zhuǎn)換器
    去掉PA9、PA10到USB的跳線帽,連接外部USB-TTL,


    image.png

結(jié)果:


亂碼
  1. 懷疑是不是片內(nèi)UART部分硬件燒壞了?
    用示波器檢測PA9腳上波形,測到的結(jié)果:
    image.png

    似乎看不出什么所以然來,后繼續(xù)查閱前人提問資料,翻到這樣一條:
    串口通信,顯示亂碼!-CSDN論壇
image.png

亂碼就一種可能,波特率不對。如果有示波器可以用示波器看一下。收一個最小的脈沖,他的脈寬就是實(shí)際的波特率的倒數(shù)。

讓我們記住這句話。


image.png

放大看一個最小波形寬度,得到的結(jié)果是 27.1us. 按照wackestar老哥的計(jì)算方法,對應(yīng)的波特率就是1/27.1*1e6=36900,似乎并不怎么對勁,我不是設(shè)置的115200???

寫進(jìn)串口監(jiān)視器配置里看看輸出:


image.png
image.png

問題解決。

...

原因呢?
算一下115200和36900之間的倍率,剛好3.12倍。。。好像沒啥規(guī)律呀。


image.png

測試一下附近的標(biāo)準(zhǔn)波特率,38400,同樣可以準(zhǔn)確解碼。而115200和38400之間剛好差了3倍。然而還是沒搞懂哪里用錯了。翻看一下使用手冊:


image.png

也并沒有弄明白為什么。
image.png

查看HAL認(rèn)為的系統(tǒng)頻率,發(fā)現(xiàn)是這個數(shù):262.50Mhz。而設(shè)定值為84Mhz,將這個數(shù)字乘3,為252Mhz。這個值是比較接近輸出值的。會不會是因?yàn)橥獠繒r鐘設(shè)置沒有生效呢?
image.png

查看stm32f4xx_hal_conf.h文件,發(fā)現(xiàn)是有設(shè)置時鐘頻率的。但是沒有導(dǎo)入?


image.png

image.png

這是HAL庫的默認(rèn)配置文件,按道理來說應(yīng)該是采用項(xiàng)目中的配置,但不清楚什么原因始終導(dǎo)入的是庫文件的配置。
image.png

修改為8Mhz
image.png

修改后的輸出:
image.png

這次又出現(xiàn)了亂碼,不過不用慌,因?yàn)檫@時的波特率已經(jīng)發(fā)生變化,修改為115200:
image.png

完美解決!

分析原因

由于stm32f4xx_hal_conf.h文件錯誤導(dǎo)入,導(dǎo)致STM32Cube庫錯誤的計(jì)算了系統(tǒng)時鐘震蕩頻率,進(jìn)而算出了錯誤的串口時鐘,從而在PC端上使用預(yù)定的波特率無法正常接收消息,表現(xiàn)為數(shù)據(jù)亂碼。
修改系統(tǒng)庫內(nèi)配置文件為正確的晶振頻率后,輸出一切正常。

使用printf函數(shù)進(jìn)行串口輸出?

順便提一下(也是本次探索的副產(chǎn)品)如何使用print函數(shù)來實(shí)現(xiàn)串口輸出吧,畢竟printf函數(shù)在調(diào)試當(dāng)中非常方便。
由于PlatformIO IDE使用的編譯器比較特別,是用的arm-eabi-none標(biāo)準(zhǔn)嵌入式設(shè)備GCC編譯器,所以和STM32CubeIDE以及Keil MDK編譯器特性都不相同,所以無論網(wǎng)上多見的fputc()函數(shù),或者__io_putchar()乃至_write()函數(shù)都無法實(shí)現(xiàn)print函數(shù)的串口重定向。順便放出來供參考(親測對PlatformIO附帶的編譯器無效):

#include <stdio.h>
__IO ITStatus USART1Ready = RESET;

/* USER CODE BEGIN PV */
#ifdef __GNUC__
    #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
    #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit_IT(&huart1 , (uint8_t *)&ch, 1);
    while (USART1Ready != SET)
  {
  }
    USART1Ready = RESET;
    return ch;
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
  if(huart->Instance==USART1)
  {
    USART1Ready = SET;
    // HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
  }
}
/* USER CODE END PV */
int _write(int fd, char *ptr, int len)  
{
  UNUSED(fd);
  HAL_UART_Transmit_IT(&huart1 , (uint8_t *)ptr, len);
  while (USART1Ready != SET) {
  }
  USART1Ready = RESET;
  HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
  return 0;
}

這里定義USART1Ready 變量起到防止同步讀寫出現(xiàn)亂碼的作用。
而且printf代碼是包含在lib庫中的,所以無法看到其具體實(shí)現(xiàn),因此也很難追溯其調(diào)用中間步驟。
所以才有了這里的曲線救國方案:

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void print(const char *fmt, ...);

void vprint(const char *fmt, va_list argp)
{
    char string[200];
    if(0 < vsprintf(string, fmt, argp)) // build string
    {
        HAL_UART_Transmit(&huart1, (uint8_t*)string, strlen(string), 0xffffff); // send message via UART
    }
}

void print(const char *fmt, ...) // custom printf() function
{
    va_list argp;
    va_start(argp, fmt);
    vprint(fmt, argp);
    va_end(argp);
}

測試代碼:

while (1)
  {
    /* USER CODE END WHILE */
    HAL_Delay(500);
    HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
    HAL_Delay(250);
    // uint8_t str[] = {32, 33, 34, 35, 36};
    uint8_t str[] = "Hello, 嚶嚶嚶。\n";
    // HAL_UART_Transmit_IT(&huart1, str, sizeof(str));
    print("Hello\n");
    print((char*)str);
    
    print("double: %lf\n", a=a+0.01);
    print("SysClock Freq: %.2f Mhz\n", (float)HAL_RCC_GetSysClockFreq()/(float)1e6);
    print("HCLK1 Freq: %.2f Mhz\n", (float)HAL_RCC_GetHCLKFreq()/(float)1e6);
    print("PCLK1 Freq: %.2f Mhz\n", (float)HAL_RCC_GetPCLK1Freq()/(float)1e6);
    print("PCLK2 Freq: %.2f Mhz\n", (float)HAL_RCC_GetPCLK2Freq()/(float)1e6);

    uint32_t pllm, pllvco, pllp, sysclockfreq;
    pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM;
    if(__HAL_RCC_GET_PLL_OSCSOURCE() != RCC_PLLSOURCE_HSI)
    {
      /* HSE used as PLL clock source */
      pllvco = (uint32_t) ((((uint64_t) HSE_VALUE * ((uint64_t) ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> RCC_PLLCFGR_PLLN_Pos)))) / (uint64_t)pllm);
    }
    else
    {
      /* HSI used as PLL clock source */
      pllvco = (uint32_t) ((((uint64_t) HSI_VALUE * ((uint64_t) ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> RCC_PLLCFGR_PLLN_Pos)))) / (uint64_t)pllm);
    }
    pllp = ((((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >> RCC_PLLCFGR_PLLP_Pos) + 1U) *2U);

    sysclockfreq = pllvco/pllp;
    print("HSE_VALUE, pllm, pllvco, pllp, sysclockfreq = %u \t %u \t %u \t %u \t %u\n", HSI_VALUE, pllm, pllvco, pllp, sysclockfreq);
    print("sysclockfreq: %.2f Mhz\n", (float)sysclockfreq/(float)1e6);

    /* USER CODE BEGIN 3 */
  }

如何print輸出浮點(diǎn)數(shù)float以及double?

這里又有一個坑...因?yàn)檎G闆r你的輸出格式應(yīng)該長這樣:


image.png

也就是無法對浮點(diǎn)數(shù)進(jìn)行格式輸出,這是由于printf庫內(nèi)容很大,所以嵌入式版本默認(rèn)進(jìn)行了縮減,所以直接使用是無法進(jìn)行浮點(diǎn)數(shù)格式化的。
解決辦法是引入一個浮點(diǎn)數(shù)printf庫,而這個庫在編譯器中是自帶的,只是需要選擇性加載進(jìn)來:

build_flags = 
    -Wl,-u_printf_float

platformio.ini文件中添加上面的代碼,就會看到前面的輸出結(jié)果了。實(shí)測可以很好的進(jìn)行浮點(diǎn)數(shù)格式處理,包括截?cái)嗵幚怼⒎柼幚?、補(bǔ)全處理等等,同時也支持雙精度小數(shù)的輸出,到目前為止基本使用起來得心應(yīng)手了。

小彩蛋

測試得到的36900的波特率與設(shè)置的波特率115200剛好差3.12倍。
而測試得到的系統(tǒng)頻率262.50Mhz與設(shè)定的頻率84.00Mhz相差也是3.125倍。
晶振設(shè)置頻率25000000 Hz與實(shí)際的8000000 Hz相差也是 25/8=3.125倍。

如果對你有所幫助,歡迎點(diǎn)贊。?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    余生動聽閱讀 10,896評論 0 11
  • 彩排完,天已黑
    劉凱書法閱讀 4,492評論 1 3
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,752評論 2 7

友情鏈接更多精彩內(nèi)容