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



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

嘗試的方法如下:
-
降低波特率
低波特率一般不容易出錯,容錯率較高。
image.png
依舊是亂碼。
-
降低系統(tǒng)頻率
過高的CPU主頻會降低系統(tǒng)穩(wěn)定性,是不是CPU跑飛了?加上LED指示燈代碼,顯示依然是正常的。繼續(xù)修改系統(tǒng)頻率(原本設(shè)置為滿頻率168Mhz,后降為84Mhz)
亂碼 嘗試避免使用中斷方式發(fā)送
用HAL_UART_Transmit()函數(shù)發(fā)送,依然亂碼。-
嘗試換用外接TTL轉(zhuǎn)換器
去掉PA9、PA10到USB的跳線帽,連接外部USB-TTL,
image.png
結(jié)果:

- 懷疑是不是片內(nèi)UART部分硬件燒壞了?
用示波器檢測PA9腳上波形,測到的結(jié)果:
image.png
似乎看不出什么所以然來,后繼續(xù)查閱前人提問資料,翻到這樣一條:
串口通信,顯示亂碼!-CSDN論壇

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

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


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

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

也并沒有弄明白為什么。

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

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


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

修改為8Mhz

修改后的輸出:

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

完美解決!
分析原因
由于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)該長這樣:

也就是無法對浮點(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)贊。?


