使用的開發(fā)環(huán)境是STM32CubeMX+VSCode-PlatformIO误债,用CubeMX初始化外設(shè)诗茎,然后導(dǎo)入到PlatformIO中開發(fā)實(shí)際應(yīng)用霜第。
結(jié)果出師不利渐溶,前后折騰將近一整天雹锣,始終輸出亂碼赡麦。
嘗試的方法如下:
-
降低波特率
低波特率一般不容易出錯(cuò)堪遂,容錯(cuò)率較高秩冈。
依舊是亂碼蚂子。
-
降低系統(tǒng)頻率
過高的CPU主頻會(huì)降低系統(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喇伯,
結(jié)果:
- 懷疑是不是片內(nèi)UART部分硬件燒壞了?
用示波器檢測PA9腳上波形拨与,測到的結(jié)果:
似乎看不出什么所以然來稻据,后繼續(xù)查閱前人提問資料,翻到這樣一條:
串口通信买喧,顯示亂碼捻悯!-CSDN論壇
亂碼就一種可能,波特率不對(duì)淤毛。如果有示波器可以用示波器看一下今缚。收一個(gè)最小的脈沖,他的脈寬就是實(shí)際的波特率的倒數(shù)低淡。
讓我們記住這句話姓言。
放大看一個(gè)最小波形寬度,得到的結(jié)果是 27.1us. 按照wackestar老哥的計(jì)算方法查牌,對(duì)應(yīng)的波特率就是1/27.1*1e6=36900事期,似乎并不怎么對(duì)勁,我不是設(shè)置的115200??纸颜?
寫進(jìn)串口監(jiān)視器配置里看看輸出:
問題解決。
...
原因呢绎橘?
算一下115200和36900之間的倍率胁孙,剛好3.12倍唠倦。。涮较。好像沒啥規(guī)律呀稠鼻。
測試一下附近的標(biāo)準(zhǔn)波特率,38400狂票,同樣可以準(zhǔn)確解碼候齿。而115200和38400之間剛好差了3倍。然而還是沒搞懂哪里用錯(cuò)了闺属。翻看一下使用手冊:
也并沒有弄明白為什么慌盯。
查看HAL認(rèn)為的系統(tǒng)頻率,發(fā)現(xiàn)是這個(gè)數(shù):262.50Mhz掂器。而設(shè)定值為84Mhz亚皂,將這個(gè)數(shù)字乘3,為252Mhz国瓮。這個(gè)值是比較接近輸出值的灭必。會(huì)不會(huì)是因?yàn)橥獠繒r(shí)鐘設(shè)置沒有生效呢?
查看stm32f4xx_hal_conf.h文件乃摹,發(fā)現(xiàn)是有設(shè)置時(shí)鐘頻率的禁漓。但是沒有導(dǎo)入?
這是HAL庫的默認(rèn)配置文件孵睬,按道理來說應(yīng)該是采用項(xiàng)目中的配置播歼,但不清楚什么原因始終導(dǎo)入的是庫文件的配置。
修改為8Mhz
修改后的輸出:
這次又出現(xiàn)了亂碼肪康,不過不用慌荚恶,因?yàn)檫@時(shí)的波特率已經(jīng)發(fā)生變化,修改為115200:
完美解決磷支!
分析原因
由于stm32f4xx_hal_conf.h
文件錯(cuò)誤導(dǎo)入谒撼,導(dǎo)致STM32Cube庫錯(cuò)誤的計(jì)算了系統(tǒng)時(shí)鐘震蕩頻率,進(jìn)而算出了錯(cuò)誤的串口時(shí)鐘雾狈,從而在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ù)的串口重定向。順便放出來供參考(親測對(duì)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?
這里又有一個(gè)坑...因?yàn)檎G闆r你的輸出格式應(yīng)該長這樣:
也就是無法對(duì)浮點(diǎn)數(shù)進(jìn)行格式輸出佛致,這是由于printf庫內(nèi)容很大贮缕,所以嵌入式版本默認(rèn)進(jìn)行了縮減,所以直接使用是無法進(jìn)行浮點(diǎn)數(shù)格式化的晌杰。
解決辦法是引入一個(gè)浮點(diǎn)數(shù)printf庫跷睦,而這個(gè)庫在編譯器中是自帶的,只是需要選擇性加載進(jìn)來:
build_flags =
-Wl,-u_printf_float
在platformio.ini
文件中添加上面的代碼肋演,就會(huì)看到前面的輸出結(jié)果了抑诸。實(shí)測可以很好的進(jìn)行浮點(diǎn)數(shù)格式處理,包括截?cái)嗵幚淼狻⒎?hào)處理蜕乡、補(bǔ)全處理等等,同時(shí)也支持雙精度小數(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倍辛块。
如果對(duì)你有所幫助,歡迎點(diǎn)贊铅碍。?