使用内部的MSI振荡器给STM32L476RG单片机提供80MHz的时钟

本文介绍STM32单片机串口通信时出现乱码的问题及两种解决方案:一是通过延迟等待MSI时钟校准;二是将USART2时钟源切换为HSI时钟,彻底解决乱码问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【程序1】

#include <stdio.h>
#include <stm32l4xx.h>

// 1<=nus<=13107
void delay_us(uint16_t nus)
{
	if ((RCC->APB1ENR2 & RCC_APB1ENR2_LPTIM2EN) == 0)
	{
		RCC->APB1ENR2 |= RCC_APB1ENR2_LPTIM2EN;
		LPTIM2->CFGR = 4 << LPTIM_CFGR_PRESC_Pos; // 80MHz/16=5MHz
		LPTIM2->CR = LPTIM_CR_ENABLE;
	}
	LPTIM2->ARR = nus * 5 - 1;
	LPTIM2->CR |= LPTIM_CR_SNGSTRT;
	while ((LPTIM2->ISR & LPTIM_ISR_ARRM) == 0);
	LPTIM2->ICR = LPTIM_ICR_ARRMCF;
}

int fputc(int ch, FILE *fp)
{
	if (fp == stdout)
	{
		if (ch == '\n')
		{
			while ((USART2->ISR & USART_ISR_TXE) == 0);
			USART2->TDR = '\r';
		}
		while ((USART2->ISR & USART_ISR_TXE) == 0);
		USART2->TDR = ch;
	}
	return ch;
}

void set_clock(void)
{
	RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;
	PWR->CR1 |= PWR_CR1_DBP; // 允许访问备用区域寄存器
	
	// 打开LSE
	if ((RCC->BDCR & RCC_BDCR_LSEON) == 0)
	{
		RCC->BDCR |= RCC_BDCR_LSEON;
		while ((RCC->BDCR & RCC_BDCR_LSERDY) == 0); // 等待LSE稳定
	}
	RCC->CR |= RCC_CR_MSIPLLEN; // MSI使用LSE校准MSI
	
	// 配置PLL: MSI/M*N/R=4MHz/1*40/2=80MHz
	RCC->PLLCFGR = RCC_PLLCFGR_PLLREN | (40 << RCC_PLLCFGR_PLLN_Pos) | RCC_PLLCFGR_PLLSRC_MSI;
	RCC->CR |= RCC_CR_PLLON;
	while ((RCC->CR & RCC_CR_PLLRDY) == 0); // 等待PLL稳定
	
	// 根据参考手册3.3.3 Read access latency, Table 11. Number of wait states according to CPU clock (HCLK) frequency
	// CPU时钟要达到64MHz以上, LATENCY必须为4WS
	// 由PWR_CR1_VOS可知当前V_CORE为Range 1 (最高时钟频率80MHz)
	FLASH->ACR |= FLASH_ACR_LATENCY_4WS; // 参阅: Increasing the CPU frequency
	while ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_4WS);
	
	// 将PLL选作系统时钟
	RCC->CFGR |= RCC_CFGR_SW;
	while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS);
	
	// 刚开始MSI还没有校准, 所以使用115200波特率时必须要延时, 否则前四个字符会乱码
	// 延时5个字节的时间, 每个字节69.4us, 共347us
	delay_us(347);
	// 使用9600波特率时, 发送一个字符就要833.3us, 远远大于这个时间, 所以无需延时
}

int main(void)
{
	set_clock();
	
	// LED灯配置
	RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
	GPIOA->MODER &= ~GPIO_MODER_MODE5_1;
	GPIOA->BSRR = GPIO_BSRR_BS5; // LED灯亮
	
	// 配置串口2
	RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN;
	GPIOA->AFR[0] = 7 << GPIO_AFRL_AFSEL2_Pos;
	GPIOA->MODER &= ~GPIO_MODER_MODE2_0;
	//GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED2;
	
	USART2->BRR = 80000000 / 115200; // 被除数为时钟频率, 除数为波特率
	USART2->CR1 = USART_CR1_UE | USART_CR1_TE;
	
	printf("ABCDEFGH\n");
	printf("PWR->CR1=0x%08x\n", PWR->CR1);
	printf("RCC->BDCR=0x%08x\n", RCC->BDCR);
	printf("RCC->CFGR=0x%08x\n", RCC->CFGR);
	printf("RCC->CR=0x%08x\n", RCC->CR);
	printf("RCC->PLLCFGR=0x%08x\n", RCC->PLLCFGR);
	printf("USART2->BRR=%d\n", USART2->BRR);
	
	GPIOA->BRR = GPIO_BRR_BR5; // LED灯灭
	while (1);
}

MSI晶振接到PLL上后会通过外部的LSE晶振进行自动校准。校准期间,不精准的MSI时钟频率被PLL放大之后就会显著地影响串口的时钟,导致串口字符乱码,因此时钟切换后串口发送第一个字符之前必须延时,等待MSI时钟校准。

解决串口乱码问题还有一个更简单的方法,无需延时。单片机内部还带有一个16MHz的HSI时钟,在RCC->CCIPR寄存器中将USART2的时钟由与MSI有关的APB1时钟切换到HSI时钟,就能彻底解决问题。

【程序2】

#include <stdio.h>
#include <stm32l4xx.h>

int fputc(int ch, FILE *fp)
{
	if (fp == stdout)
	{
		if (ch == '\n')
		{
			while ((USART2->ISR & USART_ISR_TXE) == 0);
			USART2->TDR = '\r';
		}
		while ((USART2->ISR & USART_ISR_TXE) == 0);
		USART2->TDR = ch;
	}
	return ch;
}

void set_clock(void)
{
	RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;
	PWR->CR1 |= PWR_CR1_DBP; // 允许访问备用区域寄存器
	
	// 打开LSE
	if ((RCC->BDCR & RCC_BDCR_LSEON) == 0)
	{
		RCC->BDCR |= RCC_BDCR_LSEON;
		while ((RCC->BDCR & RCC_BDCR_LSERDY) == 0); // 等待LSE稳定
	}
	RCC->CR |= RCC_CR_MSIPLLEN; // MSI使用LSE校准MSI
	
	// 配置PLL: MSI/M*N/R=4MHz/1*40/2=80MHz
	RCC->PLLCFGR = RCC_PLLCFGR_PLLREN | (40 << RCC_PLLCFGR_PLLN_Pos) | RCC_PLLCFGR_PLLSRC_MSI;
	RCC->CR |= RCC_CR_PLLON;
	while ((RCC->CR & RCC_CR_PLLRDY) == 0); // 等待PLL稳定
	
	// 根据参考手册3.3.3 Read access latency, Table 11. Number of wait states according to CPU clock (HCLK) frequency
	// CPU时钟要达到64MHz以上, LATENCY必须为4WS
	// 由PWR_CR1_VOS可知当前V_CORE为Range 1 (最高时钟频率80MHz)
	FLASH->ACR |= FLASH_ACR_LATENCY_4WS; // 参阅: Increasing the CPU frequency
	while ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_4WS);
	
	// 将PLL选作系统时钟
	RCC->CFGR |= RCC_CFGR_SW;
	while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS);
}

int main(void)
{
	set_clock();
	
	// LED灯配置
	RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
	GPIOA->MODER &= ~GPIO_MODER_MODE5_1;
	GPIOA->BSRR = GPIO_BSRR_BS5; // LED灯亮
	
	// 配置串口2
	RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN;
	GPIOA->AFR[0] = 7 << GPIO_AFRL_AFSEL2_Pos;
	GPIOA->MODER &= ~GPIO_MODER_MODE2_0;
	//GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED2;
	
	// 使用HSI16作为USART2的时钟可以避免乱码
	RCC->CR |= RCC_CR_HSION;
	while ((RCC->CR & RCC_CR_HSIRDY) == 0);
	RCC->CCIPR = RCC_CCIPR_USART2SEL_1; // HSI16作为USART2的时钟
	USART2->BRR = 16000000 / 115200; // 被除数为时钟频率, 除数为波特率
	USART2->CR1 = USART_CR1_UE | USART_CR1_TE;
	
	printf("ABCDEFGH\n");
	printf("PWR->CR1=0x%08x\n", PWR->CR1);
	printf("RCC->BDCR=0x%08x\n", RCC->BDCR);
	printf("RCC->CFGR=0x%08x\n", RCC->CFGR);
	printf("RCC->CR=0x%08x\n", RCC->CR);
	printf("RCC->PLLCFGR=0x%08x\n", RCC->PLLCFGR);
	printf("USART2->BRR=%d\n", USART2->BRR);
	
	GPIOA->BRR = GPIO_BRR_BR5; // LED灯灭
	while (1);
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值