USART на STM32L

Почти год назад я описывал процесс сборки минимального набора программ для сборки прошивок под STM32. Пришло время рассказать, как же можно реализовать работу с периферией на примере USART.

Если посмотреть в даташит, например на RM0038, то можно заметить что USART там далеко не один. Наряду с тем работать он может в куче режимов, но в пределах данного поста нас будет интересовать старый добрый USART. Прежде чем начать подготовку, сначала необходимо определиться с тактированием. Перед тем как попасть на нашу периферию, частота несколько раз умножается и делится. Рассмотрим этот процесс на основе того микропроцессора, что стоит на STM32L-Discovery (STM32L152RB)

На первоночальной блок-схеме можно выделить следующие пути

Как видно, на схеме все порты ввода/вывода находятся на AHB, а что касательно USART, так USART1 лежит на APB2, а остальные на APB1. Теперь попробуем выяснить какая именно частота попадает на USART-ы. Эмпирическим путем я просмотрел дампы памяти и получил такую схему

На моем STM32L-Discovery в качестве внешнего кварца был впаян кварц на 8МГц, дальше он умножается в 12 раз, что делает частоту в 96Мгц. Для нормальной работы USB, если он будет нужен, на этом этапе частота должна быть равна 48Мгц, так что хорошо бы поменять PLLMUL с x12 на x6. Но пока USB не нужен, можно оставить все не тронутым. Дальше частота делится на 3 и получается 32Мгц. _Прошу обратить внимание, если вы будете изменять параметры PLL, то на это время его необходимо отключить. _

Далее SYSCLK проходит через 3 делителя. Но все они равны 1, так что в финале на все подается 32Мгц.

Теперь можно подойти к самой настройке USART. Начнем с подсчета регистра BRR. Именно он у нас будет отвечать за скорость передачи. В даташите можете найти подобные формулы

Пользоваться будем верхней формулой и возьмем OVER8 за 0. Частота у нас 32Мгц, скорость 9600:(32 000 000 / 9 600) / 16 = 208.(3)
Выделяем целую часть: 208 = 0xD0
Дробную же часть необходимо умножить на 16 и округлить до целого 16*0.(3) = 5.(3) ~ 5 = 0x5
В результате получаем, что USART_BRR = 0xD05

Теперь, когда все что необходимо подсчитано, можно написать код. Сначала нужно подвести тактирование к модулю USART1:

RCC->APB2ENR|= RCC_APB2ENR_USART1EN;

Теперь настраиваем сам USART. По сравнению с STM8L кода тут как то поменьше

USART1->BRR = 0xD05;
USART1->CR1  |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // USART1 ON, TX ON, RX ON

Но вот с настройкой GPIO совсем все по другому. Для начала подведем тактирование к группе портов A.

RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBLPENR_GPIOALPEN;

А теперь еще одна очень важная вещь. Нужно указать в каком режиме какая нога должна работать. За это отвечают регистры AFRL и AFRH

AFRL отвечает за порты от 0 до 7, а AFRH - от 8 до 15. Наш USART1 находится на портах PA9 и PA10, следовательно нам нужен AFRH. Чтобы понять какую цифру вписывать в регистр, нужно посмотреть чуть выше по документации:

Как видно, по нижней части картинки USART1..3 находиться под AF7. AF7 - это 0111, следовательно в регистре должно быть что то вроде 0111 0111 00000x770. Чтож, так и поступим:

GPIOA->AFR[1] |= 0x770; //AF7(USART1..3) to pins 9,10

Порт вывода работает в альтернативном режиме push-pull без подтяжек и на максимальной скорости:

GPIOA->OTYPER &= ~GPIO_OTYPER_ODR_9; // Output push-pull (reset state)
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR9; // No pull-up, pull-down
GPIOA->MODER |= GPIO_MODER_MODER9_1; // Alternate function mode
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9_1; // 40 MHz High speed

И порт входа:

GPIOA->MODER |= GPIO_MODER_MODER10_1; // Alternate function mode
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR10_1; // 40 MHz High speed

Теперь можно попробовать запустить USART. Для сего можно написать такой код (Первый цикл нужен для того чтобы дождаться готовности передачи):

while(!(USART1->SR & USART_SR_TC));
USART1->DR = 0xFF;

Для полноценного USART-а нам необходимо реализовать хотябы прерывание по приему. В STM32 для USART1 есть только одно прерывание. Нужно лишь переопределить функцию USART1_IRQHandler и вписать туда свой код:

void USART1_IRQHandler(void) {
	if (USART1->SR & USART_SR_RXNE) {
		while(!(USART1->SR & USART_SR_TC));
		USART1->DR = USART1->DR;
	}
}

Если регистр говорит, что была принята какая либо информация, то ждем готовности передачи и возращаем то, что приняли.

Теперь остается только лишь резрешить прерывания:

USART1->CR1 |= USART_CR1_RXNEIE; // RXNE Int ON
NVIC_EnableIRQ (USART1_IRQn);
__enable_irq ();

Вот и все готово. Результат:

#include "stm32l1xx.h"

int main(void)
{
	RCC->CR |= RCC_CR_HSEON;
	while(!(RCC->CR & RCC_CR_HSERDY));

	RCC->APB2ENR	|= RCC_APB2ENR_USART1EN;	//USART1 Clock ON
	USART1->BRR = 0xD05;		// Bodrate for 9600 on 32Mhz
	USART1->CR1  |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // USART1 ON, TX ON, RX ON

	RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBLPENR_GPIOALPEN;
	GPIOA->AFR[1] |= 0x770; //AF7(USART1..3) to pins 9,10

	GPIOA->OTYPER &= ~GPIO_OTYPER_ODR_9; // Output push-pull (reset state)
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR9; // No pull-up, pull-down
	GPIOA->MODER |= GPIO_MODER_MODER9_1; // Alternate function mode
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9_1; // 40 MHz High speed

	GPIOA->MODER |= GPIO_MODER_MODER10_1; // Alternate function mode
	GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR10_1; // 40 MHz High speed

	USART1->CR1 |= USART_CR1_RXNEIE; // RXNE Int ON
	NVIC_EnableIRQ (USART1_IRQn);
	__enable_irq ();

	while (1) {
	}
}

void USART1_IRQHandler(void) {
	if (USART1->SR & USART_SR_RXNE) {
		while(!(USART1->SR & USART_SR_TC));
		USART1->DR = USART1->DR;
	}
}

Так же его можно взять в качестве Gist на моем гитхабе: https://gist.github.com/ftp27/10454616