使用的开发环境是STM32CubeMX+VSCode-PlatformIO,用CubeMX初始化外设,然后导入到PlatformIO中开发实际应用。
结果出师不利,前后折腾将近一整天,始终输出乱码。
尝试的方法如下:
-
降低波特率
低波特率一般不容易出错,容错率较高。
image.png
依旧是乱码。
-
降低系统频率
过高的CPU主频会降低系统稳定性,是不是CPU跑飞了?加上LED指示灯代码,显示依然是正常的。继续修改系统频率(原本设置为满频率168Mhz,后降为84Mhz)
乱码 尝试避免使用中断方式发送
用HAL_UART_Transmit()函数发送,依然乱码。-
尝试换用外接TTL转换器
去掉PA9、PA10到USB的跳线帽,连接外部USB-TTL,
image.png
结果:
- 怀疑是不是片内UART部分硬件烧坏了?
用示波器检测PA9脚上波形,测到的结果:
image.png
似乎看不出什么所以然来,后继续查阅前人提问资料,翻到这样一条:
串口通信,显示乱码!-CSDN论坛
乱码就一种可能,波特率不对。如果有示波器可以用示波器看一下。收一个最小的脉冲,他的脉宽就是实际的波特率的倒数。
让我们记住这句话。
放大看一个最小波形宽度,得到的结果是 27.1us. 按照wackestar老哥的计算方法,对应的波特率就是1/27.1*1e6=36900,似乎并不怎么对劲,我不是设置的115200🐴?
写进串口监视器配置里看看输出:
问题解决。
...
原因呢?
算一下115200和36900之间的倍率,刚好3.12倍。。。好像没啥规律呀。
测试一下附近的标准波特率,38400,同样可以准确解码。而115200和38400之间刚好差了3倍。然而还是没搞懂哪里用错了。翻看一下使用手册:
也并没有弄明白为什么。
查看HAL认为的系统频率,发现是这个数:262.50Mhz。而设定值为84Mhz,将这个数字乘3,为252Mhz。这个值是比较接近输出值的。会不会是因为外部时钟设置没有生效呢?
查看stm32f4xx_hal_conf.h文件,发现是有设置时钟频率的。但是没有导入?
这是HAL库的默认配置文件,按道理来说应该是采用项目中的配置,但不清楚什么原因始终导入的是库文件的配置。
修改为8Mhz
修改后的输出:
这次又出现了乱码,不过不用慌,因为这时的波特率已经发生变化,修改为115200:
完美解决!
分析原因
由于stm32f4xx_hal_conf.h
文件错误导入,导致STM32Cube库错误的计算了系统时钟震荡频率,进而算出了错误的串口时钟,从而在PC端上使用预定的波特率无法正常接收消息,表现为数据乱码。
修改系统库内配置文件为正确的晶振频率后,输出一切正常。
使用printf函数进行串口输出?
顺便提一下(也是本次探索的副产品)如何使用print函数来实现串口输出吧,毕竟printf函数在调试当中非常方便。
由于PlatformIO IDE使用的编译器比较特别,是用的arm-eabi-none标准嵌入式设备GCC编译器,所以和STM32CubeIDE以及Keil MDK编译器特性都不相同,所以无论网上多见的fputc()
函数,或者__io_putchar()
乃至_write()
函数都无法实现print函数的串口重定向。顺便放出来供参考(亲测对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
变量起到防止同步读写出现乱码的作用。
而且printf代码是包含在lib库中的,所以无法看到其具体实现,因此也很难追溯其调用中间步骤。
所以才有了这里的曲线救国方案:
#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输出浮点数float以及double?
这里又有一个坑...因为正常情况你的输出格式应该长这样:
也就是无法对浮点数进行格式输出,这是由于printf库内容很大,所以嵌入式版本默认进行了缩减,所以直接使用是无法进行浮点数格式化的。
解决办法是引入一个浮点数printf库,而这个库在编译器中是自带的,只是需要选择性加载进来:
build_flags =
-Wl,-u_printf_float
在platformio.ini
文件中添加上面的代码,就会看到前面的输出结果了。实测可以很好的进行浮点数格式处理,包括截断处理、符号处理、补全处理等等,同时也支持双精度小数的输出,到目前为止基本使用起来得心应手了。
小彩蛋
测试得到的36900的波特率与设置的波特率115200刚好差3.12倍。
而测试得到的系统频率262.50Mhz与设定的频率84.00Mhz相差也是3.125倍。
晶振设置频率25000000 Hz与实际的8000000 Hz相差也是 25/8=3.125倍。
如果对你有所帮助,欢迎点赞。❤