17 lipca 2018

Komunikacja przez SPI

Ten wpis będzie krótki - właściwie nie planowałem zajmować się SPI, to w końcu taki prosty interfejs. Z drugiej strony okazało się, że jego obsługa konieczna jest do uruchomienia LCD. Więc tym razem krótko i prosto - komunikacja przez SPI.

Na początek małe sprzątanie kodu. Do przetestowania SPI wygodnie będzie wysłać otrzymane dane przez UART, więc utworzyłem odpowiedni moduł. Plik nagłówkowy uart.h:

#ifndef __UART__
#define __UART__

#include <stdint.h>

void uart_init(void);

void usart_putc(char c);

void usart_print_hex(uint8_t value);

void usart_send(const char *s);

#endif // __UART__

Oraz sam kod uart.c:

#include "uart.h"
#include "pll.h"
#include "stm32f429xx.h"

void uart_init(void)
{
        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;

        RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
        USART1->BRR = APB2_FREQ / 115200;
        USART1->CR3 = 0;
        USART1->CR2 = 0;
        USART1->CR1 = USART_CR1_TE | USART_CR1_RE;
        for (volatile uint32_t dly = 0; dly < 1000; dly++);
        USART1->CR1 |= USART_CR1_UE;
}

void usart_putc(char c)
{
        while ((USART1->SR & USART_SR_TXE) == 0);
        USART1->DR = (int)c;
}

void usart_print_hex(uint8_t value)
{
        const char hex2char[] = "0123456789abcdef";

        usart_putc(hex2char[value >> 4]);
        usart_putc(hex2char[value & 0x0f]);
}

void usart_send(const char *s)
{
        while (*s)
                usart_putc(*s++);
}

Do komunikacji przez SPI oczywiście przydałby się jakiś moduł. Na płytce discovery znajdziemy trzyosiowy żyroskop L3GD20.

 Jak widać jest podłączony do interfejsu SPI5 - tego samego co wyświetlacz.
Obsługa SPI jest bardzo prosta. Jak zwykle musimy najpierw uruchomić zegar modułu i  skonfigurować odpowiednie piny - podobnie jak poprzednio użyję funkcji pin_config, chociaż przyznam się do lenistwa i jej skopiowania. Kiedyś trzeba będzie to poprawić:

        RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
 

        config_pin(GPIOF, 7, 5);
        config_pin(GPIOF, 8, 5);
        config_pin(GPIOF, 9, 5);


Linią wyboru układu CS będziemy sterować programowo, więc jest to zwykły pin GPIO:

        GPIOC->MODER |= GPIO_MODER_MODE1_0;
        GPIOC->BSRR = GPIO_BSRR_BS1;


Na koniec sama konfiguracja i uruchomienie modułu SPI:

        SPI5->CR1 = SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_MSTR |
                    SPI_CR1_SSM | SPI_CR1_SSI;
        SPI5->CR1 |= SPI_CR1_SPE;


BR2 i BR0 ustawiają dzielnik zegara dla SPI, pozostałe piny uruchamiają interfejs w trybie master. Plik nagłówkowy spi.h:


#ifndef __SPI__
#define __SPI__

#include <stdint.h>

void spi_init(void);

void spi_start(void);

void spi_stop(void);

uint8_t spi_sendrecv(uint8_t tx);

#endif // __SPI__

Implementacja spi.c:

#include "spi.h"
#include "stm32f429xx.h"

static void config_pin(GPIO_TypeDef* port, uint8_t pin, uint32_t func)
{
        if (pin < 8) {
                port->AFR[0] |=  func << (pin * 4);
                port->AFR[0] |=  func << (pin * 4);
        } else {
                port->AFR[1] |=  func << ((pin - 8) * 4);
                port->AFR[1] |=  func << ((pin - 8) * 4);
        }
        port->MODER |= 2 << (pin * 2);
        port->OSPEEDR |= 2 << (pin * 2);
}

void spi_init(void)
{
        RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;

        config_pin(GPIOF, 7, 5);
        config_pin(GPIOF, 8, 5);
        config_pin(GPIOF, 9, 5);

        GPIOC->MODER |= GPIO_MODER_MODE1_0;
        GPIOC->BSRR = GPIO_BSRR_BS1;

        SPI5->CR1 = SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_MSTR |
                    SPI_CR1_SSM | SPI_CR1_SSI;
        SPI5->CR1 |= SPI_CR1_SPE;
}

uint8_t spi_sendrecv(uint8_t tx)
{
        while ((SPI5->SR & SPI_SR_TXE) == 0);
        SPI5->DR = tx;
        while ((SPI5->SR & SPI_SR_RXNE) == 0);

        return SPI5->DR;
}

void spi_start(void)
{
        GPIOC->BSRR = GPIO_BSRR_BR1;
}

void spi_stop(void)
{
        while ((SPI5->SR & SPI_SR_TXE) == 0);
        while (SPI5->SR & SPI_SR_BSY);

        GPIOC->BSRR = GPIO_BSRR_BS1;
}

Teraz wystarczy napisać program główny i przetestować komunikację. Do testów najłatwiej będzie odczytać rejestr WHO_AM_I układu L3GD20:


Jak widać poprawna wartość to 0xd4. Przy okazji małe ułatwienie - dodałem przekierowanie printf() na uart. To trochę rozrzutność jak chodzi o kod, ale stm32f429 ma ogromną jak na mikrokontroler pamięć Flash, więc warto chociaż zapamiętać że taka opcja istnieje. Kod main.c:

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "stm32f429xx.h"
#include "pll.h"
#include "uart.h"
#include "spi.h"

int _write(int fd, char *str, int len)
{
        int i;
        for (i = 0; i < len; i++)
                usart_putc(str[i]);
        return len;
}

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

        pll_init();
        uart_init();
        spi_init();

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

        spi_init();

        while (1) {
                printf("Starting SPI communication...\r\n");

                spi_start();

                uint8_t r = spi_sendrecv(0x8f);
                printf("Received: %02x\r\n", r);

                r = spi_sendrecv(0x00);
                printf("Received: %02x\r\n", r);

                spi_stop();

                printf("done.\r\n");
                for (volatile uint32_t dly = 0; dly < 10000000; dly++);
        }

        return 0;
}

Po uruchomieniu program wypisuje oczekiwaną wartość, czyli 0xd4 a analizator logiczny pokazuję piękny przebieg. Skoro SPI już działa, czas zająć się sterownikiem wyświetlacza.

Brak komentarzy:

Prześlij komentarz