[STM32]串口疑难杂症解决[无法实现、printf失败]

前言

这里是解决问题的版块,默认你已经有相关知识了。

我们使能串口怎么使能?

无非是开启时钟、配置GPIO、配置USART、配置中断、写中断函数。

但如果你书写的代码都没有问题,而串口时而可以发送,时而又是乱码,时而半乱不乱,怎么回事呢?

几乎可以肯定,是因为时钟频率的问题。

而接下来就是要详细的说明如何调试我们的时钟频率,以达到我们期望的效果。

首先,你得会Keil5的调试功能,因为串口的失能意味着你无法通过串口助手来锁定代码的错误,所以你需要调试功能查询变量的数值。

正篇

修改HSE_VALUE

HSE是高速外部时钟,你得确认你手上板子的晶振,HSE晶振是8MHz还是25MHz,这一点可能会被初学者忽略,因为就算没有配置好,程序也是可以运行的,但是会出现串口传输的时候会乱码的问题。

stm32f4xx.h中STM32F407芯片定义的HSE时钟频率是25M,而我板子上的HSE的频率是8MHz,所以需要针对性的更改,更改为8MHz。

#define HSE_VALUE    ((uint32_t)8000000)

修改PLL

注意:USB OTG FS、SDIO 和 RNG 等外设需要 48 MHz 的时钟,相差较大可能导致 USB、SDIO 等外设工作不正常。

(注意这里是Cortex-M4的解析)

HSE进入PLL锁相环进行分频,HSE先通过PLL_M进行8分频,变成1MHz的信号。

这1MHz的信号就是Vco的输入频率,输入频率再通过PLL_N,放大336倍,变成336MHz,变成Vco的输出频率。

对Vco输出频率进行分频,可以得到系统时钟(PLL_P),168MHz。

USB OTG FS、SDIO 、随机数发生器的时钟(PLL_Q),48MHz。

#define PLL_M      8
#define PLL_N      336
#define PLL_P      2
#define PLL_Q      7

当然,如果你想要超频到180也是没问题的,只需要更改一下参数就行了。

 

修改SystemCoreClock

千万不要忘记更改系统时钟,我们这里的系统时钟本身只是一个变量,用于存储和反映当前系统时钟频率的参考值。它不会实际更改系统时钟的设置。

但是他供其他函数使用,以便计算定时器、延时等需要知道系统时钟频率的功能。

在更改系统时钟配置后,需要手动调用SystemCoreClockUpdate()函数来更新SystemCoreClock的值,以确保其反映当前的实际时钟频率。

uint32_t SystemCoreClock = 168000000;

 

现在,你会发现串口已经可以正常使用了,接下来我们再说说printf的问题吧。

其实printf你可以直接参考下面的代码,放在自己的串口文件中,就可以使用printf了:

#if 1
#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}

/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART1->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    USART1->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif