前言
我们使能串口怎么使能?
无非是开启时钟、配置GPIO、配置USART、配置中断、写中断函数。
但如果你书写的代码都没有问题,而串口时而可以发送,时而又是乱码,时而半乱不乱,怎么回事呢?
几乎可以肯定,是因为时钟频率的问题。
而接下来就是要详细的说明如何调试我们的时钟频率,以达到我们期望的效果。
首先,你得会Keil5的调试功能,因为串口的失能意味着你无法通过串口助手来锁定代码的错误,所以你需要调试功能查询变量的数值。
正篇
修改HSE_VALUE
HSE是高速外部时钟,你得确认你手上板子的晶振,HSE晶振是8MHz还是25MHz,这一点可能会被初学者忽略,因为就算没有配置好,程序也是可以运行的,但是会出现串口传输的时候会乱码的问题。
stm32f4xx.h中STM32F407芯片定义的HSE时钟频率是25M,而我板子上的HSE的频率是8MHz,所以需要针对性的更改,更改为8MHz。
#define HSE_VALUE ((uint32_t)8000000)
修改PLL
(注意这里是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