diff --git a/examples/Makefile.rules b/examples/Makefile.rules index 9cd41f5..15011a1 100644 --- a/examples/Makefile.rules +++ b/examples/Makefile.rules @@ -236,4 +236,4 @@ endif .PHONY: images clean stylecheck styleclean elf bin hex srec list --include $(OBJS:.o=.d) \ No newline at end of file +-include $(OBJS:.o=.d) diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/Makefile b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/Makefile new file mode 100644 index 0000000..4f84e13 --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/Makefile @@ -0,0 +1,10 @@ +OBJS = sdram.o clock.o console.o lcd-spi.o + +BINARY = lcd-dma + +# we use sin/cos from the library +LDLIBS += -lm + +LDSCRIPT = ../stm32f429i-discovery.ld + +include ../../Makefile.include diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/clock.c b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/clock.c new file mode 100644 index 0000000..f5af810 --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/clock.c @@ -0,0 +1,75 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Chuck McManis + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* + * Now this is just the clock setup code from systick-blink as it is the + * transferrable part. + */ + +#include +#include +#include + +/* Common function descriptions */ +#include "clock.h" + +/* milliseconds since boot */ +static volatile uint32_t system_millis; + +/* Called when systick fires */ +void sys_tick_handler(void) +{ + system_millis++; +} + +/* simple sleep for delay milliseconds */ +void milli_sleep(uint32_t delay) +{ + uint32_t wake = system_millis + delay; + while (wake > system_millis) { + continue; + } +} + +/* Getter function for the current time */ +uint32_t mtime(void) +{ + return system_millis; +} + +/* + * clock_setup(void) + * + * This function sets up both the base board clock rate + * and a 1khz "system tick" count. The SYSTICK counter is + * a standard feature of the Cortex-M series. + */ +void clock_setup(void) +{ + /* Base board frequency, set to 168Mhz */ + rcc_clock_setup_hse_3v3(&hse_8mhz_3v3[CLOCK_3V3_168MHZ]); + + /* clock rate / 168000 to get 1mS interrupt rate */ + systick_set_reload(168000); + systick_set_clocksource(STK_CSR_CLKSOURCE_AHB); + systick_counter_enable(); + + /* this done last */ + systick_interrupt_enable(); +} diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/clock.h b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/clock.h new file mode 100644 index 0000000..7ce5ed8 --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/clock.h @@ -0,0 +1,17 @@ +/* + * This include file describes the functions exported by clock.c + */ +#ifndef __CLOCK_H +#define __CLOCK_H + +#include + +/* + * Definitions for functions being abstracted out + */ +void milli_sleep(uint32_t); +uint32_t mtime(void); +void clock_setup(void); + +#endif /* generic header protector */ + diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/console.c b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/console.c new file mode 100644 index 0000000..e6bc91e --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/console.c @@ -0,0 +1,274 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Chuck McManis + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* + * Interrupt drive Console code (extracted from the usart-irq example) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "clock.h" +#include "console.h" + + +/* This is a ring buffer to holding characters as they are typed + * it maintains both the place to put the next character received + * from the UART, and the place where the last character was + * read by the program. See the README file for a discussion of + * the failure semantics. + */ +#define RECV_BUF_SIZE 128 /* Arbitrary buffer size */ +char recv_buf[RECV_BUF_SIZE]; +static volatile int recv_ndx_nxt; /* Next place to store */ +static volatile int recv_ndx_cur; /* Next place to read */ + +/* For interrupt handling we add a new function which is called + * when receive interrupts happen. The name (usart1_isr) is created + * by the irq.json file in libopencm3 calling this interrupt for + * USART1 'usart1', adding the suffix '_isr', and then weakly binding + * it to the 'do nothing' interrupt function in vec.c. + * + * By defining it in this file the linker will override that weak + * binding and instead bind it here, but you have to get the name + * right or it won't work. And you'll wonder where your interrupts + * are going. + */ +void usart1_isr(void) +{ + uint32_t reg; + int i; + + do { + reg = USART_SR(CONSOLE_UART); + if (reg & USART_SR_RXNE) { + recv_buf[recv_ndx_nxt] = USART_DR(CONSOLE_UART); +#ifdef RESET_ON_CTRLC + /* Check for "reset" */ + if (recv_buf[recv_ndx_nxt] == '\003') { + scb_reset_system(); + } +#endif + /* Check for "overrun" */ + i = (recv_ndx_nxt + 1) % RECV_BUF_SIZE; + if (i != recv_ndx_cur) { + recv_ndx_nxt = i; + } + } + } while ((reg & USART_SR_RXNE) != 0); + /* can read back-to-back interrupts */ +} + +/* + * console_putc(char c) + * + * Send the character 'c' to the USART, wait for the USART + * transmit buffer to be empty first. + */ +void console_putc(char c) +{ + uint32_t reg; + do { + reg = USART_SR(CONSOLE_UART); + } while ((reg & USART_SR_TXE) == 0); + USART_DR(CONSOLE_UART) = (uint16_t) c & 0xff; +} + +/* + * char = console_getc(int wait) + * + * Check the console for a character. If the wait flag is + * non-zero. Continue checking until a character is received + * otherwise return 0 if called and no character was available. + * + * The implementation is a bit different however, now it looks + * in the ring buffer to see if a character has arrived. + */ +char console_getc(int wait) +{ + char c = 0; + + while ((wait != 0) && (recv_ndx_cur == recv_ndx_nxt)); + if (recv_ndx_cur != recv_ndx_nxt) { + c = recv_buf[recv_ndx_cur]; + recv_ndx_cur = (recv_ndx_cur + 1) % RECV_BUF_SIZE; + } + return c; +} + +/* + * void console_puts(char *s) + * + * Send a string to the console, one character at a time, return + * after the last character, as indicated by a NUL character, is + * reached. + */ +void console_puts(char *s) +{ + while (*s != '\000') { + console_putc(*s); + /* Add in a carraige return, after sending line feed */ + if (*s == '\n') { + console_putc('\r'); + } + s++; + } +} + +/* + * int console_gets(char *s, int len) + * + * Wait for a string to be entered on the console, limited + * support for editing characters (back space and delete) + * end when a character is received. + */ +int console_gets(char *s, int len) +{ + char *t = s; + char c; + + *t = '\000'; + /* read until a is received */ + while ((c = console_getc(1)) != '\r') { + if ((c == '\010') || (c == '\127')) { + if (t > s) { + /* send ^H ^H to erase previous character */ + console_puts("\010 \010"); + t--; + } + } else { + *t = c; + console_putc(c); + if ((t - s) < len) { + t++; + } + } + /* update end of string with NUL */ + *t = '\000'; + } + return t - s; +} + +/* + * console_setup(int baudrate) + * + * Set the pins and clocks to create a console that we can + * use for serial messages and getting text from the user. + */ +void console_setup(int baud) +{ + /* MUST enable the GPIO clock in ADDITION to the USART clock */ + rcc_periph_clock_enable(RCC_GPIOA); + + /* This example uses PD5 and PD6 for Tx and Rx respectively + * but other pins are available for this role on USART1 (our chosen + * USART) as well, such as PA2 and PA3. You can also split them + * so PA2 for Tx, PD6 for Rx but you would have to enable both + * the GPIOA and GPIOD clocks in that case + */ + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO9 | GPIO10); + + /* Actual Alternate function number (in this case 7) is part + * depenedent, CHECK THE DATA SHEET for the right number to + * use. + */ + gpio_set_af(GPIOA, GPIO_AF7, GPIO9 | GPIO10); + + + /* This then enables the clock to the USART1 peripheral which is + * attached inside the chip to the APB2 bus. Different peripherals + * attach to different buses, and even some UARTS are attached to + * APB1 and some to APB2, again the data sheet is useful here. + */ + rcc_periph_clock_enable(RCC_USART1); + + /* Set up USART/UART parameters using the libopencm3 helper functions */ + usart_set_baudrate(CONSOLE_UART, baud); + usart_set_databits(CONSOLE_UART, 8); + usart_set_stopbits(CONSOLE_UART, USART_STOPBITS_1); + usart_set_mode(CONSOLE_UART, USART_MODE_TX_RX); + usart_set_parity(CONSOLE_UART, USART_PARITY_NONE); + usart_set_flow_control(CONSOLE_UART, USART_FLOWCONTROL_NONE); + usart_enable(CONSOLE_UART); + + /* Enable interrupts from the USART */ + nvic_enable_irq(NVIC_USART1_IRQ); + + /* Specifically enable receive interrupts */ + usart_enable_rx_interrupt(CONSOLE_UART); +} + +static ssize_t console_read(void *cookie, char *buf, size_t size) +{ + cookie = cookie; /* -Wunused-parameter */ + size_t i; + for (i = 0; i < size; i++) { + char c = console_getc(1); + buf[i] = c; + if (c == '\r') { + buf[i] = '\n'; + i++; + break; + } + } + return i; +} + +static ssize_t console_write(void *cookie, const char *buf, size_t size) +{ + cookie = cookie; /* -Wunused-parameter */ + size_t i; + for (i = 0; i < size; i++) { + char c = buf[i]; + if (c == '\n') { + console_putc('\r'); + } + console_putc(c); + } + return size; +} + +void console_stdio_setup() +{ + cookie_io_functions_t console_input_fns = { + .read = console_read, + .write = NULL, + .seek = NULL, + .close = NULL + }; + cookie_io_functions_t console_output_fns = { + .read = NULL, + .write = console_write, + .seek = NULL, + .close = NULL + }; + stdin = fopencookie(NULL, "r", console_input_fns); + stdout = fopencookie(NULL, "w", console_output_fns); + stderr = fopencookie(NULL, "w", console_output_fns); + setlinebuf(stdout); + setbuf(stderr, NULL); +} diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/console.h b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/console.h new file mode 100644 index 0000000..ae61c4a --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/console.h @@ -0,0 +1,57 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Chuck McManis + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __CONSOLE_H +#define __CONSOLE_H + +/* + * Some definitions of our console "functions" attached to the + * USART. + * + * These define sort of the minimum "library" of functions which + * we can use on a serial port. If you wish to use a different + * USART there are several things to change: + * - CONSOLE_UART change this + * - Change the peripheral enable clock + * - add usartx_isr for interrupts + * - nvic_enable_interrupt(your choice of USART/UART) + * - GPIO pins for transmit/receive + * (may be on different alternate functions as well) + */ + + +#define CONSOLE_UART USART1 + +/* + * Our simple console definitions + */ + +void console_putc(char c); +char console_getc(int wait); +void console_puts(char *s); +int console_gets(char *s, int len); +void console_setup(int baudrate); + +/* Connect stdin, stdout, stderr to the console. */ +extern void console_stdio_setup(void); + +/* this is for fun, if you type ^C to this example it will reset */ +#define RESET_ON_CTRLC + +#endif diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-dma.c b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-dma.c new file mode 100644 index 0000000..0b82d08 --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-dma.c @@ -0,0 +1,495 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "clock.h" +#include "console.h" +#include "lcd-spi.h" +#include "sdram.h" + +#define LCD_WIDTH 240 +#define LCD_HEIGHT 320 +#define REFRESH_RATE 70 /* Hz */ + +#define HSYNC 10 +#define HBP 20 +#define HFP 10 + +#define VSYNC 2 +#define VBP 2 +#define VFP 4 + +/* Layer 1 (bottom layer) is ARGB8888 format, full screen. */ + +typedef uint32_t layer1_pixel; +#define LCD_LAYER1_PIXFORMAT LTDC_LxPFCR_ARGB8888 + +layer1_pixel *const lcd_layer1_frame_buffer = (void *)SDRAM_BASE_ADDRESS; +#define LCD_LAYER1_PIXEL_SIZE (sizeof(layer1_pixel)) +#define LCD_LAYER1_WIDTH LCD_WIDTH +#define LCD_LAYER1_HEIGHT LCD_HEIGHT +#define LCD_LAYER1_PIXELS (LCD_LAYER1_WIDTH * LCD_LAYER1_HEIGHT) +#define LCD_LAYER1_BYTES (LCD_LAYER1_PIXELS * LCD_LAYER1_PIXEL_SIZE) + +/* Layer 2 (top layer) is ARGB4444, a 128x128 square. */ + +typedef uint16_t layer2_pixel; +#define LCD_LAYER2_PIXFORMAT LTDC_LxPFCR_ARGB4444 +layer2_pixel *const lcd_layer2_frame_buffer = + (void *)SDRAM_BASE_ADDRESS + LCD_LAYER1_BYTES; +#define LCD_LAYER2_PIXEL_SIZE (sizeof(layer2_pixel)) +#define LCD_LAYER2_WIDTH 128 +#define LCD_LAYER2_HEIGHT 128 +#define LCD_LAYER2_PIXELS (LCD_LAYER2_WIDTH * LCD_LAYER2_HEIGH) +#define LCD_LAYER2_BYTES (LCD_LAYER2_PIXELS * LCD_LAYER2_PIXEL_SIZE) + +/* + * Pin assignments + * R2 = PC10, AF14 + * R3 = PB0, AF09 + * R4 = PA11, AF14 + * R5 = PA12, AF14 + * R6 = PB1, AF09 + * R7 = PG6, AF14 + * + * G2 = PA6, AF14 + * G3 = PG10, AF09 + * G4 = PB10, AF14 + * G5 = PB11, AF14 + * G6 = PC7, AF14 + * G7 = PD3, AF14 + * + * B2 = PD6, AF14 + * B3 = PG11, AF11 + * B4 = PG12, AF09 + * B5 = PA3, AF14 + * B6 = PB8, AF14 + * B7 = PB9, AF14 + * + * More pins... + * ENABLE = PF10, AF14 + * DOTCLK = PG7, AF14 + * HSYNC = PC6, AF14 + * VSYNC = PA4, AF14 + * CSX = PC2 used in lcd-spi + * RDX = PD12 not used: read SPI + * TE = PD11 not used: tearing effect interrupt + * WRX_DCX = PD13 used in lcd-spi + * DCX_SCL = PF7 used in lcd-spi + * SDA = PF9 used in lcd-spi + * NRST = NRST + */ + +static void lcd_dma_init(void) +{ + /* init GPIO clocks */ + rcc_periph_clock_enable(RCC_GPIOA | RCC_GPIOB | RCC_GPIOC | + RCC_GPIOD | RCC_GPIOF | RCC_GPIOG); + + /* set GPIO pin modes */ + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, + GPIO3 | GPIO4 | GPIO6 | GPIO11 | GPIO12); + gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, + GPIO3 | GPIO4 | GPIO6 | GPIO11 | GPIO12); + gpio_set_af(GPIOA, GPIO_AF14, GPIO3 | GPIO4 | GPIO6 | GPIO11 | GPIO12); + + gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, + GPIO0 | GPIO1 | GPIO8 | GPIO9 | GPIO10 | GPIO11); + gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, + GPIO0 | GPIO1 | GPIO8 | + GPIO9 | GPIO10 | GPIO11); + gpio_set_af(GPIOB, GPIO_AF9, GPIO0 | GPIO1); + gpio_set_af(GPIOB, GPIO_AF14, GPIO8 | GPIO9 | GPIO10 | GPIO11); + + gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, + GPIO6 | GPIO7 | GPIO10); + gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, + GPIO6 | GPIO7 | GPIO10); + gpio_set_af(GPIOC, GPIO_AF14, GPIO6 | GPIO7 | GPIO10); + + gpio_mode_setup(GPIOD, GPIO_MODE_AF, GPIO_PUPD_NONE, + GPIO3 | GPIO6); + gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, + GPIO3 | GPIO6); + gpio_set_af(GPIOD, GPIO_AF14, GPIO3 | GPIO6); + + gpio_mode_setup(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, + GPIO10); + gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, + GPIO10); + gpio_set_af(GPIOF, GPIO_AF14, GPIO10); + + gpio_mode_setup(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, + GPIO6 | GPIO7 | GPIO10 | GPIO11 | GPIO12); + gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, + GPIO6 | GPIO7 | GPIO10 | GPIO11 | GPIO12); + gpio_set_af(GPIOG, GPIO_AF9, GPIO10 | GPIO12); + gpio_set_af(GPIOG, GPIO_AF14, GPIO6 | GPIO7 | GPIO11); + + /* + * The datasheet says (Figure 16, page 151): + * The LCD-TFT clock comes from PLLSAI. + * PLLSRC selects either HSI or HSE. + * PLLSAI's input clock is either HSI or HSE divided by PLLM. + * PLLSAI's PLLLCDCLK output is the input * PLLSAIN / PLLSAIR. + * LCD-TFT clock is PLLLCDCLK divided by PLLSAIDIVR. + * + * PLLSRC and PLLM are in the RCC_PLLCFGR register. + * PLLSAIN and PLLSAIR are in RCC_PLLSAICFGR. + * PLLSAIDIVR is in RCC_DCKCFGR; + * + * In our case, + * PLLSRC already selected HSE, which is 8 MHz. + * PLLM is already set to 8. 8 MHz / 8 = 1 MHz. + * We set PLLSAIN = 192 and PLLSAIR = 4. 1 MHz * 192 / 4 = 48 MHz. + * We set PLLSAIDIVR to 8. 48 MHz / 8 = 6 MHz. + * So the LCD-TFT pixel clock is 6 MHz. + * + * The number of clocks per frame is + * (VSYNC + VBP + LCD_HEIGHT + VFP) * (HSYNC + HBP + LCD_WIDTH + HFP) = + * (2 + 2 + 320 + 4) * (10 + 20 + 240 + 10) = 91840. + * + * So the refresh frequency is 6 MHz / 91840 ~= 65.6 Hz. + */ + + uint32_t sain = 192; + uint32_t saiq = (RCC_PLLSAICFGR >> RCC_PLLSAICFGR_PLLSAIQ_SHIFT) & + RCC_PLLSAICFGR_PLLSAIQ_MASK; + uint32_t sair = 4; + RCC_PLLSAICFGR = (sain << RCC_PLLSAICFGR_PLLSAIN_SHIFT | + saiq << RCC_PLLSAICFGR_PLLSAIQ_SHIFT | + sair << RCC_PLLSAICFGR_PLLSAIR_SHIFT); + RCC_DCKCFGR |= RCC_DCKCFGR_PLLSAIDIVR_DIVR_8; + RCC_CR |= RCC_CR_PLLSAION; + while ((RCC_CR & RCC_CR_PLLSAIRDY) == 0) { + continue; + } + RCC_APB2ENR |= RCC_APB2ENR_LTDCEN; + + /* + * Configure the Synchronous timings: VSYNC, HSNC, + * Vertical and Horizontal back porch, active data area, and + * the front porch timings. + */ + LTDC_SSCR = (HSYNC - 1) << LTDC_SSCR_HSW_SHIFT | + (VSYNC - 1) << LTDC_SSCR_VSH_SHIFT; + LTDC_BPCR = (HSYNC + HBP - 1) << LTDC_BPCR_AHBP_SHIFT | + (VSYNC + VBP - 1) << LTDC_BPCR_AVBP_SHIFT; + LTDC_AWCR = (HSYNC + HBP + LCD_WIDTH - 1) << LTDC_AWCR_AAW_SHIFT | + (VSYNC + VBP + LCD_HEIGHT - 1) << LTDC_AWCR_AAH_SHIFT; + LTDC_TWCR = + (HSYNC + HBP + LCD_WIDTH + HFP - 1) << LTDC_TWCR_TOTALW_SHIFT | + (VSYNC + VBP + LCD_HEIGHT + VFP - 1) << LTDC_TWCR_TOTALH_SHIFT; + + /* Configure the synchronous signals and clock polarity. */ + LTDC_GCR |= LTDC_GCR_PCPOL_ACTIVE_HIGH; + + /* If needed, configure the background color. */ + LTDC_BCCR = 0x00000000; + + /* Configure the needed interrupts. */ + LTDC_IER = LTDC_IER_RRIE; + nvic_enable_irq(NVIC_LCD_TFT_IRQ); + + /* Configure the Layer 1 parameters. + * (Layer 1 is the bottom layer.) */ + { + /* The Layer window horizontal and vertical position */ + uint32_t h_start = HSYNC + HBP + 0; + uint32_t h_stop = HSYNC + HBP + LCD_LAYER1_WIDTH - 1; + LTDC_L1WHPCR = h_stop << LTDC_LxWHPCR_WHSPPOS_SHIFT | + h_start << LTDC_LxWHPCR_WHSTPOS_SHIFT; + uint32_t v_start = VSYNC + VBP + 0; + uint32_t v_stop = VSYNC + VBP + LCD_LAYER1_HEIGHT - 1; + LTDC_L1WVPCR = v_stop << LTDC_LxWVPCR_WVSPPOS_SHIFT | + v_start << LTDC_LxWVPCR_WVSTPOS_SHIFT; + + /* The pixel input format */ + LTDC_L1PFCR = LCD_LAYER1_PIXFORMAT; + + /* The color frame buffer start address */ + LTDC_L1CFBAR = (uint32_t)lcd_layer1_frame_buffer; + + /* The line length and pitch of the color frame buffer */ + uint32_t pitch = LCD_LAYER1_WIDTH * LCD_LAYER1_PIXEL_SIZE; + uint32_t length = LCD_LAYER1_WIDTH * LCD_LAYER1_PIXEL_SIZE + 3; + LTDC_L1CFBLR = pitch << LTDC_LxCFBLR_CFBP_SHIFT | + length << LTDC_LxCFBLR_CFBLL_SHIFT; + + /* The number of lines of the color frame buffer */ + LTDC_L1CFBLNR = LCD_LAYER1_HEIGHT; + + /* If needed, load the CLUT */ + /* (not using CLUT) */ + + /* If needed, configure the default color and blending + * factors + */ + LTDC_L1CACR = 0x000000FF; + LTDC_L1BFCR = LTDC_LxBFCR_BF1_PIXEL_ALPHA_x_CONST_ALPHA | + LTDC_LxBFCR_BF2_PIXEL_ALPHA_x_CONST_ALPHA; + } + + /* Configure the Layer 2 parameters. */ + { + /* The Layer window horizontal and vertical position */ + uint32_t h_start = HSYNC + HBP + 0; + uint32_t h_stop = HSYNC + HBP + LCD_LAYER2_WIDTH - 1; + LTDC_L2WHPCR = h_stop << LTDC_LxWHPCR_WHSPPOS_SHIFT | + h_start << LTDC_LxWHPCR_WHSTPOS_SHIFT; + uint32_t v_start = VSYNC + VBP + 0; + uint32_t v_stop = VSYNC + VBP + LCD_LAYER2_HEIGHT - 1; + LTDC_L2WVPCR = v_stop << LTDC_LxWVPCR_WVSPPOS_SHIFT | + v_start << LTDC_LxWVPCR_WVSTPOS_SHIFT; + + /* The pixel input format */ + LTDC_L2PFCR = LCD_LAYER2_PIXFORMAT; + + /* The color frame buffer start address */ + LTDC_L2CFBAR = (uint32_t)lcd_layer2_frame_buffer; + + /* The line length and pitch of the color frame buffer */ + uint32_t pitch = LCD_LAYER2_WIDTH * LCD_LAYER2_PIXEL_SIZE; + uint32_t length = LCD_LAYER2_WIDTH * LCD_LAYER2_PIXEL_SIZE + 3; + LTDC_L2CFBLR = pitch << LTDC_LxCFBLR_CFBP_SHIFT | + length << LTDC_LxCFBLR_CFBLL_SHIFT; + + /* The number of lines of the color frame buffer */ + LTDC_L2CFBLNR = LCD_LAYER2_HEIGHT; + + /* If needed, load the CLUT */ + /* (not using CLUT) */ + + /* If needed, configure the default color and blending + * factors + */ + LTDC_L2CACR = 0x000000FF; + LTDC_L2BFCR = LTDC_LxBFCR_BF1_PIXEL_ALPHA_x_CONST_ALPHA | + LTDC_LxBFCR_BF2_PIXEL_ALPHA_x_CONST_ALPHA; + } + + /* Enable Layer1 and if needed the CLUT */ + LTDC_L1CR |= LTDC_LxCR_LAYER_ENABLE; + + /* Enable Layer2 and if needed the CLUT */ + LTDC_L2CR |= LTDC_LxCR_LAYER_ENABLE; + + /* If needed, enable dithering and/or color keying. */ + /* (Not needed) */ + + /* Reload the shadow registers to active registers. */ + LTDC_SRCR |= LTDC_SRCR_VBR; + + /* Enable the LCD-TFT controller. */ + LTDC_GCR |= LTDC_GCR_LTDC_ENABLE; +} + +static void mutate_background_color(void) +{ + static uint32_t ints; + ints += 3; + uint32_t shift = ints >> 9; + if (shift >= 3) { + ints = shift = 0; + } + uint32_t component = ints & 0xFF; + if (ints & 0x100) { + component = 0xff - component; + } + + LTDC_BCCR = component << 8 * shift; +} + +/* + * The sprite bounce algorithm works surprisingly well for a first + * guess. Whenever the sprite touches a wall, we reverse its velocity + * normal to the wall, and pick a random velocity from -3 to +3 + * parallel to the wall. (e.g., if it touches a side, reverse the X + * velocity and pick a random Y velocity.) That gives enough + * unpredictability to make it interesting. + * + * The random numbers come from rand(), and we do not call srand(). + * That means the sprite makes exactly the same moves every time the + * demo is run. (Repeatability is a feature.) + */ + +static void move_sprite(void) +{ + static int8_t dx = 1, dy = 1; + static int16_t x, y; + static int16_t age; + x += dx; + y += dy; + if (x < 0) { + dy = rand() % 7 - 3; + dx = -dx; + x = 0; + age = 0; + } else if (x >= LCD_WIDTH - LCD_LAYER2_WIDTH) { + dy = rand() % 7 - 3; + dx = -dx; + x = LCD_WIDTH - LCD_LAYER2_WIDTH - 1; + age = 0; + } + if (y < 0) { + dx = rand() % 7 - 3; + dy = -dy; + y = 0; + age = 0; + } else if (y >= LCD_HEIGHT - LCD_LAYER2_HEIGHT) { + dx = rand() % 7 - 3; + dy = -dy; + y = LCD_HEIGHT - LCD_LAYER2_HEIGHT - 1; + age = 0; + } + if (dy == 0 && dx == 0) { + dy = y ? -1 : +1; + } + uint32_t h_start = HSYNC + HBP + x; + uint32_t h_stop = h_start + LCD_LAYER2_WIDTH - 1; + LTDC_L2WHPCR = h_stop << LTDC_LxWHPCR_WHSPPOS_SHIFT | + h_start << LTDC_LxWHPCR_WHSTPOS_SHIFT; + uint32_t v_start = VSYNC + VBP + y; + uint32_t v_stop = v_start + LCD_LAYER2_HEIGHT - 1; + LTDC_L2WVPCR = v_stop << LTDC_LxWVPCR_WVSPPOS_SHIFT | + v_start << LTDC_LxWVPCR_WVSTPOS_SHIFT; + + /* The sprite fades away as it ages. */ + age += 2; + if (age > 0xFF) { + age = 0xFF; + } + LTDC_L2CACR = 0x000000FF - age; +} + +/* + * Here is where all the work is done. We poke a total of 6 registers + * for each frame. + */ + +void lcd_tft_isr(void) +{ + LTDC_ICR |= LTDC_ICR_CRRIF; + + mutate_background_color(); + move_sprite(); + + LTDC_SRCR |= LTDC_SRCR_VBR; +} + +/* + * Checkerboard pattern. Odd squares are transparent; even squares are + * all different colors. + */ + +static void draw_layer_1(void) +{ + int row, col; + int cel_count = (LCD_LAYER1_WIDTH >> 5) + (LCD_LAYER1_HEIGHT >> 5); + + for (row = 0; row < LCD_LAYER1_HEIGHT; row++) { + for (col = 0; col < LCD_LAYER1_WIDTH; col++) { + size_t i = row * LCD_LAYER1_WIDTH + col; + uint32_t cel = (row >> 5) + (col >> 5); + uint8_t a = cel & 1 ? 0 : 0xFF; + uint8_t r = row * 0xFF / LCD_LAYER1_HEIGHT; + uint8_t g = col * 0xFF / LCD_LAYER1_WIDTH; + uint8_t b = 0xFF * (cel_count - cel - 1) / cel_count; + if (!(cel & 3)) { + b = 0; + } + + /* Put black and white borders around the squares. */ + if (row % 32 == 0 || col % 32 == 0) { + r = g = b = a ? 0xFF : 0; + a = 0xFF; + } + layer1_pixel pix = a << 24 | r << 16 | g << 8 | b << 0; + + /* + * Outline the screen in white. Put a black + * dot at the origin. + * + * (The origin is in the lower left!) + */ + if (row == 0 || col == 0 || row == 319 || col == 239) { + pix = 0xFFFFFFFF; + } else if (row < 20 && col < 20) { + pix = 0xFF000000; + } + lcd_layer1_frame_buffer[i] = pix; + } + } +} + +/* + * Layer 2 holds the sprite. The sprite is a semitransparent + * magenta/cyan diamond outlined in black. + */ + +static void draw_layer_2(void) +{ + int row, col; + const uint8_t hw = LCD_LAYER2_WIDTH / 2; + const uint8_t hh = LCD_LAYER2_HEIGHT / 2; + const uint8_t sz = (hw + hh) / 2; + + for (row = 0; row < LCD_LAYER2_HEIGHT; row++) { + for (col = 0; col < LCD_LAYER2_WIDTH; col++) { + size_t i = row * LCD_LAYER2_WIDTH + col; + uint8_t dx = abs(col - hw); + uint8_t dy = abs(row - hh); + uint8_t dxy = dx + dy; + uint8_t a = dxy <= sz ? 0xF * dxy / (sz / 2) : 0x0; + if (a > 0xF) { + if (a < 0x14) { + a = 0xF; + } else { + a &= 0xF; + } + } + uint8_t r = dx >= dy ? 0xF : 0x0; + uint8_t g = dy >= dx ? 0xF : 0x0; + uint8_t b = 0xF; + if (dx + dy >= sz - 2 || dx == dy) { + r = g = b = 0; + } + layer2_pixel pix = a << 12 | r << 8 | g << 4 | b << 0; + lcd_layer2_frame_buffer[i] = pix; + } + } +} + +int main(void) +{ + /* init timers. */ + clock_setup(); + + /* set up USART 1. */ + console_setup(115200); + console_stdio_setup(); + + /* set up SDRAM. */ + sdram_init(); + + printf("Preloading frame buffers\n"); + + draw_layer_1(); + draw_layer_2(); + + printf("Initializing LCD\n"); + + lcd_dma_init(); + lcd_spi_init(); + + printf("Initialized.\n"); + + while (1) { + continue; + } +} diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-spi.c b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-spi.c new file mode 100644 index 0000000..92a7d44 --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-spi.c @@ -0,0 +1,383 @@ +#include "lcd-spi.h" + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Chuck McManis + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* + * Initialize the ST Micro TFT Display for DMA video using the SPI port + */ +#include +#include + +#include +#include +#include +#include + +#include "clock.h" + +#define LCD_SPI SPI5 + +/* + * This is an ungainly workaround (aka hack) basically I want to know + * when the SPI port is 'done' sending all of the bits out, and it is + * done when it has clocked enough bits that it would have received a + * byte. Since we're using the SPI port in write_only mode I am not + * collecting the "received" bytes into a buffer, but one could of + * course. I keep track of how many bytes should have been returned + * by decrementing the 'rx_pend' volatile. When it reaches 0 we know + * we are done. + */ + +static volatile int rx_pend; +static volatile uint16_t spi_rx_buf; + +/* + * This is the ISR we use. Note that the name is based on the name + * in the irq.json file of libopencm3 plus the "_isr" extension. + */ +void +spi5_isr(void) +{ + spi_rx_buf = SPI_DR(SPI5); + --rx_pend; +} + +/* + * For the STM32-DISCO board, SPI pins in use: + * N/C - RESET + * PC2 - CS (could be NSS but won't be) + * PF7 - SCLK (AF5) SPI5 + * PD13 - DATA / CMD* + * PF9 - MOSI (AF5) SPI5 + */ + +/* + * void lcd_command(cmd, delay, args, arg_ptr) + * + * All singing all dancing 'do a command' feature. Basically it + * sends a command, and if args are present it sets 'data' and + * sends those along too. + */ +static void +lcd_command(uint8_t cmd, int delay, int n_args, const uint8_t *args) +{ + uint32_t timeout; + int i; + + gpio_clear(GPIOC, GPIO2); /* Select the LCD */ + rx_pend++; + spi_send(SPI5, cmd); + /* We need to wait until it is sent, if we turn on the Data + * line too soon, it ends up confusing the display to thinking + * its a data transfer, as it samples the D/CX line on the last + * bit sent. + */ + for (timeout = 0; (timeout < 1000) && (rx_pend); timeout++) { + continue; + } + rx_pend = 0; /* sometimes, at 10Mhz we miss this */ + if (n_args) { + gpio_set(GPIOD, GPIO13); /* Set the D/CX pin */ + for (i = 0; i < n_args; i++) { + rx_pend++; + spi_send(SPI5, *(args+i)); + } + /* This wait so that we don't pull CS too soon after + * sending the last byte of data. + */ + for (timeout = 0; (timeout < 1000) && (rx_pend); timeout++) { + continue; + } + } + gpio_set(GPIOC, GPIO2); /* Turn off chip select */ + gpio_clear(GPIOD, GPIO13); /* always reset D/CX */ + if (delay) { + milli_sleep(delay); /* wait, if called for */ + } +} + +/* Notes on the less obvious ILI9341 commands: */ + +/* + * ILI9341 datasheet, pp 46-49: + * + * RCM[1:0} = 0b10 command 0xb0 + * DPI[2:0] = 0b110 command 0x3a + * RIM = 0 command 0xf6 + * PCDIV = ???? command 0xB6 + * + * Pp 239-240: + * external fosc = DOTCLK / (2 * (PCDIV + 1)) + * + * ("Cube" is how the STM32F4Cube demo software sets the register. + * "Chuck" is ChuckM's lcd-serial demo, first revision.) + * + * Command 0x3A: COLMOD: Pixel Format Set LCD_PIXEL_FORMAT + * Reset Cube Chuck + * DPI[2:0] 110 (18 bit/pix) 110 101 (16 bit/pix) + * DBI[2:0] 110 (18 bit/pix) 110 101 (16 bit/pix) + * + * Command 0xB0: RGB Interface Signal LCD_RGB_INTERFACE + * Reset Cube + * Bypass: 0 (direct) 1 (memory) + * RCM[1:0] 10 10 + * VSPL 0 (low) 0 + * HSPL 0 (low) 0 + * DPL 0 (rising) 1 (falling) + * EPL 1 (low) 0 (high) + * + * Command 0xB6: Display Function Control LCD_DFC + * Reset Cube 0A A7 27 04 + * PTG[1:0] 10 10 + * PT[1:0] 10 10 + * REV 1 1 + * GS 0 0 + * SS 0 (S1->S720) 1 (S720->S1) + * SM 0 0 + * ISC[3:0] 0010 (5 frames) 0111 (15 frames) + * NL[5:0] 100111 100111 + * PCDIV[5:0] ? 000100 + * S720->S1 moves the origin from the lower left corner to lower right + * (viewing the board so the silkscreen is upright) + * + * Command 0xF6: Interface Control LCD_INTERFACE + * Reset Cube 01 00 06 + * MY_EOR 0 0 + * MX_EOR 0 0 + * MV_EOR 0 0 + * BGR_EOR 0 0 + * WEMODE 1 (wrap) 1 + * EPF[1:0] 00 00 + * MDT[1:0] 00 00 + * ENDIAN 0 (MSB first) 0 + * DM[1:0] 00 (int clk) 01 (RGB ifc) + * RM 0 (sys ifc) 1 (RGB ifc) + * RIM 0 (1 xfr/pix) 0 + */ + +/* ILI9341 command definitions */ + +/* Regulative[sic] Command Set */ +#define ILI_NOP 0x00 +#define ILI_RESET 0x01 +#define ILI_RD_DID 0x04 +#define ILI_RD_STS 0x09 +#define ILI_RD_PWR_MODE 0x0a +#define ILI_RD_MADCTL 0x0b +#define ILI_RD_PXL_FMT 0x0c +#define ILI_PD_IMG_FMT 0x0d +#define ILI_RD_SIG_MODE 0x0e +#define ILI_RD_DIAG_RSLT 0x0f +#define ILI_ENTER_SLEEP 0x10 +#define ILI_SLEEP_OUT 0x11 +#define ILI_PARTIAL_ON 0x12 +#define ILI_NORMAL_MODE_ON 0x13 +#define ILI_INVERSE_ON 0x20 +#define ILI_INVERSE_OFF 0x21 +#define ILI_GAMMA_SET 0x26 +#define ILI_DISP_OFF 0x28 +#define ILI_DISP_ON 0x29 +#define ILI_CAS 0x2a +#define ILI_PAS 0x2b +#define ILI_MEM_WRITE 0x2c +#define ILI_COLOR_SET 0x2d +#define ILI_MEM_READ 0x2e +#define ILI_PARTIAL_AREA 0x30 +#define ILI_VERT_SCROLL_DEF 0x33 +#define ILI_TEAR_EFF_OFF 0x34 +#define ILI_TEAR_EFF_ON 0x35 +#define ILI_MEM_ACC_CTL 0x36 +#define ILI_V_SCROLL_START 0x37 +#define ILI_IDLE_OFF 0x38 +#define ILI_IDLE_ON 0x39 +#define ILI_PIX_FMT_SET 0x3a +#define ILI_WR_MEM_CONT 0x3c +#define ILI_RD_MEM_CONT 0x3e +#define ILI_SET_TEAR_LINE 0x44 +#define ILI_GET_SCANLINE 0x45 +#define ILI_WR_BRIGHTNESS 0x51 +#define ILI_RD_BRIGHTNESS 0x52 +#define ILI_WR_CTRL 0x53 +#define ILI_RD_CTRL 0x54 +#define ILI_WR_CABC 0x55 +#define ILI_RD_CABC 0x56 +#define ILI_WR_CABC_MIN 0x5e +#define ILI_RD_CABC_MAX 0x5f +#define ILI_RD_ID1 0xda +#define ILI_RD_ID2 0xdb +#define ILI_RD_ID3 0xdc + +/* Extended Command Set */ +#define ILI_RGB_IFC_CTL 0xb0 +#define ILI_FRM_CTL_NORM 0xb1 +#define ILI_FRM_CTL_IDLE 0xb2 +#define ILI_FRM_CTL_PART 0xb3 +#define ILI_INVERSE_CTL 0xb4 +#define ILI_PORCH_CTL 0xb5 +#define ILI_FUNC_CTL 0xb6 +#define ILI_ENTRY_MODE_SET 0xb7 +#define ILI_BL_CTL_1 0xb8 +#define ILI_BL_CTL_2 0xb9 +#define ILI_BL_CTL_3 0xba +#define ILI_BL_CTL_4 0xbb +#define ILI_BL_CTL_5 0xbc +#define ILI_BL_CTL_7 0xbe +#define ILI_BL_CTL_8 0xbf +#define ILI_PWR_CTL_1 0xc0 +#define ILI_PWR_CTL_2 0xc1 +#define ILI_VCOM_CTL_1 0xc5 +#define ILI_VCOM_CTL_2 0xc7 +#define ILI_NV_MEM_WR 0xd0 +#define ILI_NV_MEM_PROT_KEY 0xd1 +#define ILI_NV_MEM_STATUS_RD 0xd2 +#define ILI_RD_ID4 0xd3 +#define ILI_POS_GAMMA 0xe0 +#define ILI_NEG_GAMMA 0xe1 +#define ILI_GAMMA_CTL_1 0xe2 +#define ILI_GAMMA_CTL_2 0xe3 +#define ILI_IFC_CTL 0xf6 + +/* + * This structure defines the sequence of commands to send + * to the Display in order to initialize it. The AdaFruit + * folks do something similar, it helps when debugging the + * initialization sequence for the display. + */ + +#define MAX_INLINE_ARGS (sizeof(uint8_t *)) +struct tft_command { + uint16_t delay; /* If you need a delay after */ + uint8_t cmd; /* command to send */ + uint8_t n_args; /* How many arguments it has */ + union { + uint8_t args[MAX_INLINE_ARGS]; /* The first four arguments */ + const uint8_t *aptr; /* More than four arguemnts */ + }; +}; + +static const uint8_t pos_gamma_args[] = { 0x0F, 0x29, 0x24, 0x0C, 0x0E, + 0x09, 0x4E, 0x78, 0x3C, 0x09, + 0x13, 0x05, 0x17, 0x11, 0x00 }; +static const uint8_t neg_gamma_args[] = { 0x00, 0x16, 0x1B, 0x04, 0x11, + 0x07, 0x31, 0x33, 0x42, 0x05, + 0x0C, 0x0A, 0x28, 0x2F, 0x0F }; + +/* + * These are the commands we're going to send to the + * display to initialize it. We send them all, in sequence + * with occasional delays. Commands that require data bytes + * as arguments, indicate how many bytes to pull out the + * above array to include. + * + * The sequence was pieced together from the ST Micro demo + * code, the data sheet, and other sources on the web. + */ +#define EXPERIMENT 1 +const struct tft_command initialization[] = { + { 0, ILI_PWR_CTL_1, 1, .args = { 0x10 } }, + { 0, ILI_PWR_CTL_2, 1, .args = { 0x10 } }, + { 0, ILI_VCOM_CTL_1, 2, .args = { 0x45, 0x15 } }, + { 0, ILI_VCOM_CTL_2, 1, .args = { 0x90 } }, + { 0, ILI_MEM_ACC_CTL, 1, .args = { 0x08 } }, + { 0, ILI_RGB_IFC_CTL, 1, .args = { 0xc0 } }, + { 0, ILI_IFC_CTL, 3, .args = { 0x01, 0x00, 0x06 } }, + { 0, ILI_GAMMA_SET, 1, .args = { 0x01 } }, + { 0, ILI_POS_GAMMA, 15, .aptr = pos_gamma_args }, + { 0, ILI_NEG_GAMMA, 15, .aptr = neg_gamma_args }, + { +5, ILI_SLEEP_OUT, 0, .args = {} }, + { 0, ILI_DISP_ON, 0, .args = {} }, +}; + +static void +initialize_display(const struct tft_command cmds[], size_t cmd_count) +{ + size_t i; + + for (i = 0; i < cmd_count; i++) { + uint8_t arg_count = cmds[i].n_args; + const uint8_t *args = cmds[i].args; + if (arg_count > MAX_INLINE_ARGS) { + args = cmds[i].aptr; + } + lcd_command(cmds[i].cmd, cmds[i].delay, arg_count, args); + } +} + +/* + * void lcd_spi_init(void) + * + * Initialize the SPI port, and the through that port + * initialize the LCD controller. Note that this code + * will expect to be able to draw into the SDRAM on +> * the board, so the sdram much be initialized before + * calling this function. + * + * SPI Port and GPIO Defined - for STM32F4-Disco + * + * LCD_CS PC2 + * LCD_SCK PF7 + * LCD_DC PD13 + * LCD_MOSI PF9 + * LCD_SPI SPI5 + * LCD_WIDTH 240 + * LCD_HEIGHT 320 + */ +void +lcd_spi_init(void) +{ + uint32_t tmp; + + /* + * Set up the GPIO lines for the SPI port and + * control lines on the display. + */ + rcc_periph_clock_enable(RCC_GPIOC | RCC_GPIOD | RCC_GPIOF); + + gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO2); + gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO13); + + gpio_mode_setup(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO7 | GPIO9); + gpio_set_af(GPIOF, GPIO_AF5, GPIO7 | GPIO9); + + rx_pend = 0; + /* Implement state management hack */ + nvic_enable_irq(NVIC_SPI5_IRQ); + + rcc_periph_clock_enable(RCC_SPI5); + /* This should configure SPI5 as we need it configured */ + tmp = SPI_SR(LCD_SPI); + SPI_CR2(LCD_SPI) |= (SPI_CR2_SSOE | SPI_CR2_RXNEIE); + + /* device clocks on the rising edge of SCK with MSB first */ + tmp = SPI_CR1_BAUDRATE_FPCLK_DIV_4 | /* 10.25Mhz SPI Clock (42M/4) */ + SPI_CR1_MSTR | /* Master Mode */ + SPI_CR1_BIDIOE | /* Write Only */ + SPI_CR1_SPE; /* Enable SPI */ + + SPI_CR1(LCD_SPI) = tmp; /* Do it. */ + if (SPI_SR(LCD_SPI) & SPI_SR_MODF) { + SPI_CR1(LCD_SPI) = tmp; /* Re-writing will reset MODF */ + fprintf(stderr, "Initial mode fault.\n"); + } + + /* Set up the display */ + initialize_display(initialization, + sizeof(initialization) / sizeof(initialization[0])); +} diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-spi.h b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-spi.h new file mode 100644 index 0000000..9956ffd --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/lcd-spi.h @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Chuck McManis + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef LCD_SPI_H +#define LCD_SPI_H + +/* + * prototypes for the LCD example + * + * This is a very basic API, initialize. + */ + +extern void lcd_spi_init(void); + +#endif /* !LCD_SPI_H */ diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/sdram.c b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/sdram.c new file mode 100644 index 0000000..26527df --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/sdram.c @@ -0,0 +1,139 @@ +#include "sdram.h" +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Chuck McManis + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* + * This then is the initialization code extracted from the + * sdram example. + */ +#include +#include +#include +#include +#include "clock.h" + +/* + * This is just syntactic sugar but it helps, all of these + * GPIO pins get configured in exactly the same way. + */ +static struct { + uint32_t gpio; + uint16_t pins; +} sdram_pins[6] = { + {GPIOB, GPIO5 | GPIO6 }, + {GPIOC, GPIO0 }, + {GPIOD, GPIO0 | GPIO1 | GPIO8 | GPIO9 | GPIO10 | GPIO14 | GPIO15}, + {GPIOE, GPIO0 | GPIO1 | GPIO7 | GPIO8 | GPIO9 | GPIO10 | + GPIO11 | GPIO12 | GPIO13 | GPIO14 | GPIO15 }, + {GPIOF, GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | GPIO5 | GPIO11 | + GPIO12 | GPIO13 | GPIO14 | GPIO15 }, + {GPIOG, GPIO0 | GPIO1 | GPIO4 | GPIO5 | GPIO8 | GPIO15} +}; + +static struct sdram_timing timing = { + .trcd = 2, /* RCD Delay */ + .trp = 2, /* RP Delay */ + .twr = 2, /* Write Recovery Time */ + .trc = 7, /* Row Cycle Delay */ + .tras = 4, /* Self Refresh Time */ + .txsr = 7, /* Exit Self Refresh Time */ + .tmrd = 2, /* Load to Active Delay */ +}; + +/* + * Initialize the SD RAM controller. + */ +void +sdram_init(void) { + int i; + uint32_t cr_tmp, tr_tmp; /* control, timing registers */ + + /* + * First all the GPIO pins that end up as SDRAM pins + */ + rcc_periph_clock_enable(RCC_GPIOB); + rcc_periph_clock_enable(RCC_GPIOC); + rcc_periph_clock_enable(RCC_GPIOD); + rcc_periph_clock_enable(RCC_GPIOE); + rcc_periph_clock_enable(RCC_GPIOF); + rcc_periph_clock_enable(RCC_GPIOG); + + for (i = 0; i < 6; i++) { + gpio_mode_setup(sdram_pins[i].gpio, + GPIO_MODE_AF, GPIO_PUPD_NONE, + sdram_pins[i].pins); + gpio_set_output_options(sdram_pins[i].gpio, GPIO_OTYPE_PP, + GPIO_OSPEED_50MHZ, sdram_pins[i].pins); + gpio_set_af(sdram_pins[i].gpio, GPIO_AF12, sdram_pins[i].pins); + } + + /* Enable the SDRAM Controller */ +#if 1 + rcc_periph_clock_enable(RCC_FSMC); +#else + rcc_peripheral_enable_clock(&RCC_AHB3ENR, RCC_AHB3ENR_FMCEN); +#endif + + /* Note the STM32F429-DISCO board has the ram attached to bank 2 */ + /* Timing parameters computed for a 168Mhz clock */ + /* These parameters are specific to the SDRAM chip on the board */ + + cr_tmp = FMC_SDCR_RPIPE_1CLK; + cr_tmp |= FMC_SDCR_SDCLK_2HCLK; + cr_tmp |= FMC_SDCR_CAS_3CYC; + cr_tmp |= FMC_SDCR_NB4; + cr_tmp |= FMC_SDCR_MWID_16b; + cr_tmp |= FMC_SDCR_NR_12; + cr_tmp |= FMC_SDCR_NC_8; + + /* We're programming BANK 2, but per the manual some of the parameters + * only work in CR1 and TR1 so we pull those off and put them in the + * right place. + */ + FMC_SDCR1 |= (cr_tmp & FMC_SDCR_DNC_MASK); + FMC_SDCR2 = cr_tmp; + + tr_tmp = sdram_timing(&timing); + FMC_SDTR1 |= (tr_tmp & FMC_SDTR_DNC_MASK); + FMC_SDTR2 = tr_tmp; + + /* Now start up the Controller per the manual + * - Clock config enable + * - PALL state + * - set auto refresh + * - Load the Mode Register + */ + sdram_command(SDRAM_BANK2, SDRAM_CLK_CONF, 1, 0); + milli_sleep(1); /* sleep at least 100uS */ + sdram_command(SDRAM_BANK2, SDRAM_PALL, 1, 0); + sdram_command(SDRAM_BANK2, SDRAM_AUTO_REFRESH, 4, 0); + tr_tmp = SDRAM_MODE_BURST_LENGTH_2 | + SDRAM_MODE_BURST_TYPE_SEQUENTIAL | + SDRAM_MODE_CAS_LATENCY_3 | + SDRAM_MODE_OPERATING_MODE_STANDARD | + SDRAM_MODE_WRITEBURST_MODE_SINGLE; + sdram_command(SDRAM_BANK2, SDRAM_LOAD_MODE, 1, tr_tmp); + + /* + * set the refresh counter to insure we kick off an + * auto refresh often enough to prevent data loss. + */ + FMC_SDRTR = 683; + /* and Poof! a 8 megabytes of ram shows up in the address space */ +} diff --git a/examples/stm32/f4/stm32f429i-discovery/lcd-dma/sdram.h b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/sdram.h new file mode 100644 index 0000000..157f30a --- /dev/null +++ b/examples/stm32/f4/stm32f429i-discovery/lcd-dma/sdram.h @@ -0,0 +1,32 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Chuck McManis + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __SDRAM_H +#define __SDRAM_H + +#define SDRAM_BASE_ADDRESS ((uint8_t *)(0xd0000000)) + +/* Initialize the SDRAM chip on the board */ +void sdram_init(void); + +#ifndef NULL +#define NULL (void *)(0) +#endif + +#endif