08 lipca 2018

Port szeregowy

Dawno, dawno temu każdy szanujący się komputer PC był wyposażony z złącze RS-232C. Teraz to już historia, złącze szeregowe zostało zastąpione przez USB, ale w przypadku mikrokontrolerów starsze rozwiązanie nadal ma się całkiem dobrze. Powód jest oczywisty - prostota.
Można używać USB do komunikacji, ale nawet podstawowe przesłanie prostego komunikatu wymaga napisania znacznej ilości kodu. Ewentualnie użycia biblioteki, która na ogół jest droga, albo pełna błędów (czasem jedno i drugie).
Użycie portu szeregowego jest banalnie proste - chociaż komputery, czy laptopy go nie obsługują, ale od czego są konwertery UART-USB.
W przypadku płytki stm32f429-disc konieczny był właśnie taki konwerter, nowsza wersja stm32f429-disc1 ma już odpowiedni konwerter wbudowany.

Niezależnie od wersji trzeba odpowiednio skonfigurować piny przeznaczone do transmisji.


Jak widać chodzi o linie PA9 oraz PA10. Tym razem obie linie mają być połączone z modułem peryferyjnym. Oznacza to ustawienie trybu "10" w rejestrze MODER oraz wybór układu peryferyjnego za pomocą AFR.

W rejestrze MODER wystarczy zapalić wyższe bity dla odpowiednich linii:

GPIOA->MODER |= GPIO_MODER_MODE9_1|GPIO_MODER_MODE10_1;

Z rejestrzem AFR jest nieco trudniej. Piny mogą mieć przypisaną jedną z maksymalnie 16 funkcji - oznacza to 4 bity na każdy pin. Ponieważ pinów jest 16 w każdym porcie, do konfiguracji niezbędne są 64 bity. Oznacza to że jeden rejestr AFR nie wystarczy. Dlatego w dokumentacji znajdziemy rejestry AFRL oraz AFRH. Co ciekawe w pliku stm32f429xx.h te rejestry są zdefinowane jako tablica AFR[2].
Numery dostępnych modułów, które można wybierać za pomocą AFR znajdziemy w dokumentacji mikrokontrolera:


Jak widać chcemy użyć funkcji AF7 zarówno dla PA9 i PA10. Można więc napisać:
GPIOA->AFR[1]  |= GPIO_AFRH_AFSEL9_0 | GPIO_AFRH_AFSEL9_1 | GPIO_AFRH_AFSEL9_2;
        GPIOA->AFR[1]  |= GPIO_AFRH_AFSEL10_0 | GPIO_AFRH_AFSEL10_1 | GPIO_AFRH_AFSEL10_2;


Dalej jest już tylko łatwiej. Jak zwykle musimy pamiętać o uruchomieniu taktowania modułu UART1:

RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

Konfiguracja sprowadza się do ustawienia prędkości komunikacji:

USART1->BRR = 16000000uL / 115200;

A następnie włączenia nadajnika i odbiornika:

USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;

Oraz samego modułu uart:

USART1->CR1 |= USART_CR1_UE;

Tutaj natrafiłem na pewną niespodziankę - jeśli zapalenie bitu UE następuje razem z konfiguracją pozostałych bitów, przed pierwszym wysłanym znakiem pojawiają się "śmieci". Konieczne okazało się dodanie pewnego opóźnienia - niestety nie wiem dlaczego.

Wysyłanie polega na wpisaniu wartości do rejestru UART1->DR, czyli:

USART1->DR = value;

Oczywiście przed zapisem należy upewnić się, że rejestr jest wolny. Rejestr statusu UART1->SR zawiera flagę TXE, która jest ustawiana gdy rejestr nadawania jest pusty. Należy więc przed wysłaniem poczekać na zapalenie tej flagi.
Cały program wysyłający komunikaty przez port szeregowy wygląda następująco:


#include <stdint.h>
#include <stdbool.h>
#include "stm32f429xx.h"

volatile uint32_t ms_counter = 0;

void SysTick_Handler(void)
{
        if (ms_counter)
                ms_counter--;
}

void delay_ms(uint32_t ms)
{
        ms_counter = ms;
        while (ms_counter) ;
}

void usart_send(const char *s)
{
        while (*s) {
                while ((USART1->SR & USART_SR_TXE) == 0);
                USART1->DR = *s++;
        }
}

int main(int argc, char *argv[])
{
        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOGEN;
        RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

        GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;

        GPIOA->MODER |= GPIO_MODER_MODE9_1|GPIO_MODER_MODE10_1;
        GPIOA->AFR[1]  |= GPIO_AFRH_AFSEL9_0 | GPIO_AFRH_AFSEL9_1 | GPIO_AFRH_AFSEL9_2;
        GPIOA->AFR[1]  |= GPIO_AFRH_AFSEL10_0 | GPIO_AFRH_AFSEL10_1 | GPIO_AFRH_AFSEL10_2;

        SysTick->LOAD = 16000-1;
        SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
                        SysTick_CTRL_TICKINT_Msk |
                        SysTick_CTRL_ENABLE_Msk;

        USART1->BRR = 16000000uL / 115200;
        USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;

        delay_ms(1);

        USART1->CR1 |= USART_CR1_UE;

        usart_send("Hello world!\r\n");

        while (1) {
                GPIOG->BSRR = GPIO_BSRR_BS13;
                GPIOG->BSRR = GPIO_BSRR_BR14;
                delay_ms(500);

                GPIOG->BSRR = GPIO_BSRR_BS14;
                GPIOG->BSRR = GPIO_BSRR_BR13;
                delay_ms(500);

                usart_send("stm32f429\r\n");
        }

        return 0;
}


Brak komentarzy:

Prześlij komentarz