Работа по учебнику Discovering the STM32 Microcontroller by Geoffrey Brown. главы 7 + 8 + 12.
STM32 + ST7735 + SD card. Интерфейс SPI. DMA
При нажатии кнопки "на stm32" на дисплее последовательно отображаются: текстовая строка, изображения из bitmap файла(считываются с SD карты), графические примитивы (линии, окружности), меняется расположение осей X и Y.
Для работы с SD картой использован модуль FatFs версии R0.09 .
Цветной TFT LCD с контроллером ST7735R, со слотом для MicroSD карты (это клон Adafruit Mini TFT - 0.96" 160x80; https://www.adafruit.com/product/3533 (почему-то только через TOR) ).
Недостатки:
1. Реализовано мало функций
2. Использование дополнительной оперативной памяти не предполагалось. Своей памяти у stm32F100RBT6 недостаточно, чтобы формировать массив данных для всего экрана сразу. Для данного дисплея нужно 25 КБ (160*80*2), а если использовать 24-битные bmp , то ещё 37,5 КБ (если не объединять). Поэтому скорость вывода bmp(на видео картинки с буквой "Ё" и словом "mirossa") заметно медленнее, чем просто заливка экрана. С доп памятью было бы одинаково.
Ещё:
У этого дисплея начальная координата - (24,0), а не (0,0). Если поменять оси X и Y местами (функции дисплея), то (0,24). Добавил этот нюанс в функции, чтобы на уровне приложения задавать привычные (0,0).
некоторые модули:
/* main.c */#include <stm32f10x.h>#include <stm32f10x_rcc.h>#include <stm32f10x_gpio.h>#include <stm32f10x_spi.h>#include "lcd_st7735.h"#include <string.h>#include "ff.h"externuint8_t ST7735_width_curr;
externuint8_t ST7735_height_curr;
UINT bw, br;
FIL Fil1;
FILINFO fno; /* FILINFO structure */
FATFS Fatfs;
FRESULT frez;
// bmp24 b// https://studassistent.ru/c/otobrazhenie-kartinki-bmp-c-sistruct
{
short signature;
int size;
int adress;
int lengthHead;
int length;
int heigh;
short numColPlosk;
short bitPerPix;
int comprMet;
int arrLength;
int gorResolution;
int vertResolution;
int numColors;
int numMainColors;
} headBMP;
char pic[480]; // BGR BGR BGR ... // 1 line = 160 px * 3 byte; для ширины в 160 пикселейuint8_t readPic(char *path);
uint8_t writePic(char *path);
void verticalReflectC(void);
uint8_t readAndPrintBMP();
// bmp24 evoid fillScreen(uint16_t color);
void otherInit(void);
//void Delay(uint32_t nTime);uint8_t debounce(uint8_t last);
#define rlen 80int main(void)
{
uint8_t currentButton;
uint8_t lastButton;
uint8_t rej = 0;
char Buff[70];
otherInit(); // Initialize user button
ST7735_init();
ST7735_backLight(1);
lastButton = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
currentButton = lastButton;
// Configure SysTick Timer//if (SysTick_Config(SystemCoreClock / 1000))//while (1);//fillScreen(ST7735_GREEN); //медленно
fillRect(0, 0, ST7735_width_curr, ST7735_height_curr, ST7735_GREEN);
while (1) {
currentButton = debounce(lastButton);
if ( lastButton == Bit_RESET && currentButton == Bit_SET ){
if ( rej == 0 ){
rej++;
//outChar('X', ST7735_YELLOW, ST7735_BLACK);
printStr("Let's get started. Here are print of string, read of bitmaps from SD card and ""display of them, draw primitive figures.", -1, -1, ST7735_YELLOW, ST7735_BLACK);
}
elseif ( rej == 1 ){
rej++;
if( f_mount(0, &Fatfs) == FR_OK) {
if ( ( frez = f_open (&Fil1 , "1_40x40.bmp", FA_READ) ) == FR_OK) {
readAndPrintBMP();
frez = f_close(&Fil1);
}else{
printStr("File is not opened for read.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
frez = f_mount(0, NULL);
} else{
printStr("Volume was not mounted.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
} elseif ( rej == 2 ){
rej++;
fillRect(0, 0, 159, 79, ST7735_WHITE);
fillRect(10, 13, 140, 40, ST7735_MAGENTA);
DrawRectangle(3,30, 80, 75, 2, ST7735_CYAN);
DrawLine(2,3, 149, 50, ST7735_BLACK);
DrawLine(2,50, 149, 3, ST7735_BLACK);
DrawCircleBresenham(80, 40, 35, ST7735_RED);
}elseif ( rej == 3 ){
rej++;
if( f_mount(0, &Fatfs) == FR_OK) { // Register volume work area// READif ( ( frez = f_open (&Fil1 , "2.bmp", FA_READ) ) == FR_OK) {
readAndPrintBMP();
frez = f_close(&Fil1);
}else{
printStr("File is not opened for read.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
// MAKE DIR, SET CURRENT DIR
frez = f_stat ( "/files", &fno);
if ( frez == FR_OK ){
frez = f_chdir("/files");
}elseif ( frez == FR_NO_PATH ){
frez = f_mkdir("/files");
if ( frez == FR_OK )
frez = f_chdir("/files");
}else{
}
// WRITEif ( ( frez = f_open (&Fil1 , "HELLO.TXT", FA_WRITE | FA_CREATE_ALWAYS) ) == FR_OK) {
if ( ( frez = f_write (&Fil1 , "Hello world !\r\n", 14, &bw) ) == FR_OK ){
frez = f_close (&Fil1);
}else{
printStr("Error of recording to file.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
}else{
printStr("File is not opened for write.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
frez = f_mount(0, NULL);
} else{
printStr("Volume was not mounted.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
} elseif ( rej == 4 ){
rej++;
ST7735_setMadctl(MADCTLGRAPHICS_normal);
//fillScreen(ST7735_CYAN); // медленно
fillRect(0, 0, ST7735_width_curr, ST7735_height_curr, ST7735_CYAN);
//outChar('O', ST7735_YELLOW, ST7735_BLACK);
} else {
rej = 0;
if( f_mount(0, &Fatfs) == FR_OK) {
if ( ( frez = f_open (&Fil1 , "1_40x40.bmp", FA_READ) ) == FR_OK) {
readAndPrintBMP();
frez = f_close(&Fil1);
}else{
printStr("File is not opened for read.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
frez = f_mount(0, NULL);
} else{
printStr("Volume was not mounted.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
}
}
}
lastButton = currentButton;
}//whilereturn 0;
}
void otherInit(void){
GPIO_InitTypeDef GPIO_InitStructure;
// user button A0
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/*static __IO uint32_t TimingDelay;void Delay(uint32_t nTime){ TimingDelay = nTime; while(TimingDelay != 0);}void SysTick_Handler(void){ if (TimingDelay != 0x00) TimingDelay--;}*/// обработка дребега контакта кнопкиuint8_t debounce(uint8_t last){
uint8_t current = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
if ( last != current ){
Delay(5);
current = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
}
return current;
}
//вместо этой функции(медленная) лучше использовать fillRect()void fillScreen(uint16_t color){
uint8_t x, y;
ST7735_setAddrWindow(0, 0, ST7735_width_curr-1, ST7735_height_curr-1);
for ( x=0; x < ST7735_width_curr; x++ ) {
for ( y=0; y < ST7735_height_curr; y++ ) {
ST7735_pushColor(&color, 1);
}
}
}
#ifdef USE_FULL_ASSERTvoid assert_failed(uint8_t *file, uint32_t line)
{
/* Infinite loop *//* Use GDB to find out why we're here */while (1);
}
#endif// работа с 24-битным bitmap.// оперативной памяти под целый bmp файл ( > 38Kb для 160x80 пикселей ) не хватит.// поэтому из файла (на SD карте) считывается, и выводится на экран, по одной "пиксельной"// строке за раз. Bitmap выводится в "верхний левый" угол экрана. Если ширина/длина дисплея// больше чем у bmp, то изображение будет занимать часть экрана.// Если ширина/длина дисплея меньше, чем размер bmp, то из файла будут считываться// пиксельные строки по ширине экрана, часть выходящая за экран отбрасывается(не считывается).// Если по какой-то причине(редкой) из файла будет прочитано меньше, чем запрошено, то строка// будет выведена не полностью, а управление перейдёт на вывод следующей строки.uint8_t readAndPrintBMP() {
longint i, y;
uint8_t d, n;
uint16_t cr, bytePerLine, cc;
frez = f_read (&Fil1 , &headBMP.signature , sizeof(short), &br);
if (frez || !br){
printStr("read error.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
return 1;
}
if(headBMP.signature != (short)0x4d42){
printStr("File is not bmp.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
return 1;
}
else
{
f_read (&Fil1 , &headBMP.size , sizeof(int), &br);
f_lseek(&Fil1, f_tell(&Fil1) + 2*sizeof(short));
f_read (&Fil1 , &headBMP.adress , sizeof(int), &br);
f_read (&Fil1 , &headBMP.lengthHead , sizeof(int), &br);
f_read (&Fil1 , &headBMP.length , sizeof(int), &br);
f_read (&Fil1 , &headBMP.heigh , sizeof(int), &br);
f_read (&Fil1 , &headBMP.numColPlosk , sizeof(short), &br);
f_read (&Fil1 , &headBMP.bitPerPix , sizeof(short), &br);
if (headBMP.bitPerPix != (short)24) {
printStr("Bmp is not 24.", -1, -1, ST7735_MAGENTA, ST7735_WHITE);
return 1;
}
else {
f_read (&Fil1 , &headBMP.comprMet , sizeof(int), &br);
f_read (&Fil1 , &headBMP.arrLength , sizeof(int), &br);
f_read (&Fil1 , &headBMP.gorResolution , sizeof(int), &br);
f_read (&Fil1 , &headBMP.vertResolution , sizeof(int), &br);
f_read (&Fil1 , &headBMP.numColors , sizeof(int), &br);
f_read (&Fil1 , &headBMP.numMainColors , sizeof(int), &br);
}
}
frez = f_lseek(&Fil1, 54*sizeof(char));
// что меньше ширина/высота в файле или текущая ширина/высота экрана
cr = ( headBMP.heigh > ST7735_height_curr) ? ST7735_height_curr: headBMP.heigh; //row
cc = ( headBMP.length > ST7735_width_curr) ? ST7735_width_curr: headBMP.length; //col
bytePerLine = cc * 3;
// (командой MADCTL (36h) можно выбрать расшифровку значения цветов - RGB или BGR. сейчас - RGB)// в bmp файле каналы цвета располагаются в GBR порядке.// преобразую BGR 24-бит в RGB 16-бит с учётом того, что формирую последовательность// байтов цветов в массиве CHAR, а затем функция lcd_printPixelLine работает с ним// как с массивом uint16_t (порядок байт не "mb lb", а "lb mb")for (i = 0; i < cr; i++){
frez = f_read (&Fil1, pic , bytePerLine, &br);
if ( frez != FR_OK)
break;
n = br/3; // count of pixelsfor ( y = 0; y < n; y++){
pic[y*2] = pic[y*3] >> 3; // B сдвигается вправо
pic[y*2] |= (pic[y*3+1] & 0x1C) << 3; //слева от B добавляюся 3 старших бита G
pic[y*2+1] = pic[y*3+1] >> 5; // R сдвигается вправо
pic[y*2+1] |= (pic[y*3+2] & 0xF8) ; //к R присоединяются 3 средних бита G
}
// строки в bmp располагаются в обратном порядке в направлении заголовков// считываю строки в ЭТОМ порядке, а вывожу снизу вверх.
lcd_printPixelLine(cr - 1 - i, (uint16_t *)pic, n);
if ( bytePerLine != br || cc < headBMP.length )
frez = f_lseek(&Fil1, 54*sizeof(char) + 3*headBMP.length* (i+1) );
}
return 0;
}
/* lcd_st7735.h*/#ifndef _LCD_ST7735_#define _LCD_ST7735_//#define MADCTLGRAPHICS 0x6 // X-Mirror Y-Mirror#define MADCTLGRAPHICS 0x3 // X-Y Exchange X-Mirror#define MADCTLGRAPHICS_normal 0x0 // normal 10.1.27 9.11.3#define MADCTLBMP 0x2// начальная координата смещена. вместо (0,0) брать (24,0)//#define ST7735_width 80//#define ST7735_height 160// Color definitions#define ST7735_BLACK 0x0000#define ST7735_BLUE 0x001F#define ST7735_RED 0xF800#define ST7735_GREEN 0x07E0#define ST7735_CYAN 0x07FF#define ST7735_MAGENTA 0xF81F#define ST7735_YELLOW 0xFFE0#define ST7735_WHITE 0xFFFF#define ST7735_ORANGE 0xFBE4//Установка порядка записи (полученных от МК) данных в массив памяти контроллера.//(Одна и та же последовательность данных, отправляемых из МК на контроллер дисплея,// будет отображаться по вертикали, по горизонтали, снизу вверх или сверху вниз...)// для madctl используются: MADCTLGRAPHICS, MADCTLGRAPHICS_normal, MADCTLBMPvoid ST7735_setMadctl(uint8_t madctl);
//Установка начальной и конечной координат окна(выбранного участка дисплея), куда будут//выводиться последующие данные.void ST7735_setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
//вывод пикселей цвета color в количестве cnt от текущей позиции в массиве памяти контроллера.void ST7735_pushColor(uint16_t * color, int cnt);
//Инициализацияvoid ST7735_init();
// вкл/выкл подсветку дисплеяvoid ST7735_backLight(uint8_t on);
// сделать задержку nTime мс.void Delay(uint32_t nTime);
//вывести символ с текущей позиции (после посленего вывода)void outChar(uint8_t ch, uint16_t foreground, uint16_t background);
//вывести строку с указанной позицииvoid printStr(char * str, int16_t X, int16_t Y, uint16_t foreground, uint16_t background);
// заполнить прямоугольную область одним цветомvoid fillRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color); // horiz, vert lines// нарисовать прямоугольник нужной толщиныvoid DrawRectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t thickness, uint16_t color);
// нарисовать линию из точки (x1,y1) в точку (x2, y2)void DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color);
// нарисовать линию из точки (x1,y1) в точку (x2, y2) методом Брезенхамаvoid DrawLineBresenham(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color);
// нарисовать круг методом Брезенхамаvoid DrawCircleBresenham(uint8_t x0, uint8_t y0, uint8_t r, uint16_t color);
// вывести линиюvoid lcd_printPixelLine(uint8_t y, uint16_t * colors, uint8_t n);
#endif
/* spi.h */#ifndef SPI_H_#define SPI_H_enum spiSpeed { SPI_SLOW, SPI_MEDIUM, SPI_FAST };
// Основная настройка SPI. Работает только с SPI2.void spiInit(SPI_TypeDef* SPIx);
// Передача/получение данных по 8 бит// SPIx - только SPI2// rbuf - указатель на буфер, куда будут загружены данные// tbuf - указатель на отправляемые данные// cnt - количество (по 8 бит)//// Если в tbuf передан указатель, а в rbuf - 0, то будут отправлены данные, а получаемые// данные никуда записываться не будут.// Если наоборот - будут получены данные, отправляться будет "пустой" сигнал (0xff).// Если оба указателя не нулевые, то данные будут переданы и получены.int spiReadWrite(SPI_TypeDef* SPIx, uint8_t * rbuf, constuint8_t * tbuf, int cnt, enum spiSpeed speed);
// Передача/получение данных по 16 бит// см. описание функции spiReadWrite() (8 бит -> 16 бит)int spiReadWrite16(SPI_TypeDef* SPIx, uint16_t * rbuf, constuint16_t * tbuf, int cnt, enum spiSpeed speed);
#endif
/* spi_dma.h */#ifndef SPI_DMA_H_#define SPI_DMA_H_#include <stm32f10x.h>// Инициализация. Включение тактированияvoid spi_dma_Init(void);
//Передача/Получение данных по SPI c DMA//(основные настройки SPI должны быть уже выполнены (spi.h.c) )// SPIx - SPI1 или SPI2// half: 0 - 8 бит, 1 - 16 бит// tbuf - указатель на отправляемые данные// rbuf - указатель на буфер, куда будут загружены данные// count - количество порций данных размером по параметру half//// добавлил параметр dontIncMem для увеличения скорости заливки ( lcd_st7735.c функция fillRect() )// - инкрементировать или нет указатель на блок памяти при отправке данных// (включается в функциях заливки, где одним цветом заливается участок экрана)//// Если в tbuf передан указатель, а в rbuf - 0, то будет запущена фукнция отправки.// Если наоборот - будет запущена функция получения.// Если оба указателя не нулевые, то будет запущено копирование из памяти в памятьint xchng_datablock(SPI_TypeDef *SPIx , uint8_t half , constvoid *tbuf , void *rbuf , unsigned count, uint8_t dontIncMem);
#endif
Комментарии