Освоение STM32F103VE + TFT LCD + touch screen (часть 2)

Начали здесь, теперь продолжаем тему.

stm32

Я внёс небольшие изменения в цикл статей. Когда писал первую часть, ещё не знал про контроллер внешней памяти FSMC, который может управлять LCD. Всё управление шло чистым bit-bang'ом, дёргали ножки контроллера и управляли экраном. Когда запустил через FSMC — экран начал просто летать. Разница в скорости на порядки выше. Пришлось даже поменять задержки работы с сенсорным экраном.

FSMC — модуль контроллера, который обеспечивает доступ к внешней памяти, как будто она находится в едином адресном пространстве с регистрами и оперативной памятью. LCD экран — это тоже устройство памяти, только в нём сохранённая информация отображается в виде цветных пикселей. Два регистра (адреса и команды/данных) находятся по адресу 0x60000000 (адрес) и 0x60020000 (команды/данные), а так же включение отдельных ножек, как Reset и подсветка экрана.


#define LCD_DATA ((uint32_t)0x60020000)    
#define LCD_REG  ((uint32_t)0x60000000)

#define LCD_LED_ON (GPIOD->BSRR =GPIO_BSRR_BS13)
#define LCD_LED_OFF (GPIOD->BSRR =GPIO_BSRR_BR13)
#define LCD_RESET_OFF (GPIOE->BSRR =GPIO_BSRR_BS1)
#define LCD_RESET_ON (GPIOE->BSRR =GPIO_BSRR_BR1)
#define LED_2_ON (GPIOD->BSRR =GPIO_BSRR_BS3)
#define LED_2_OFF (GPIOD->BSRR =GPIO_BSRR_BR3)


Конфигурируем порты, на выход и на вход:


void Lcd_Port_Conf (void)
{
// CRH  Port D
//RS	->	PD11
//DB0	->	PD14
//DB1	->	PD15
//DB13	->	PD8
//DB14	->	PD9
//DB15	->	PD10
//Light ->	PD13 - 0010	 - 2 - Output push pull 10 Mhz
GPIOD->CRH	= 0xBB24BBBB;		//8 - Alternative function out	50 Mhz
// CRL  Port D
//WR		->	PD5
//RD		->	PD4
//CS		->	PD7
//DB2		->	PD0
//DB3		->	PD1
GPIOD->CRL	=  0xB4BB44BB;	   //B - Alternative function out	50 Mhz
// CRH  Port E
//DB5		->	PE8
//DB6		->	PE9
//DB8		->	PE11
//DB9		->	PE12
//DB10		->	PE13
//DB11		->	PE14
//DB12		->	PE15
GPIOE->CRH	=  0xBBBBBBBB; 	   //B - Alternative function out	50 Mhz
// CRL  Port E
//DB4		->	PE7
//RESET		->	PE1		- 0010	 - 2 - Output push pull 10 Mhz
GPIOE->CRL	=  0xB4444424;	   //B - Alternative function out	50 Mhz
}


Прикрепляем готовую конфигурацию FSMC (разобраться, как именно всё работает сложно, в таких случаях приходится брать готовый код):


void LCD_FSMCConfig(void)
{
  FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
  FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTimingInitStructure;

  FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
  FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
  FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
  FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
  FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
  FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
  FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
  FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
  FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
  FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
  FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
  FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_NORSRAMTimingInitStructure;
  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); 
 
  FSMC_NORSRAMTimingInitStructure.FSMC_AddressSetupTime = 10;  
  FSMC_NORSRAMTimingInitStructure.FSMC_AddressHoldTime = 3;	   
  FSMC_NORSRAMTimingInitStructure.FSMC_DataSetupTime = 3;	
  FSMC_NORSRAMTimingInitStructure.FSMC_BusTurnAroundDuration = 0x00;
  FSMC_NORSRAMTimingInitStructure.FSMC_CLKDivision = 0x00;
  FSMC_NORSRAMTimingInitStructure.FSMC_DataLatency = 0x00;
  FSMC_NORSRAMTimingInitStructure.FSMC_AccessMode = FSMC_AccessMode_A;	/* FSMC mode */
  FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_NORSRAMTimingInitStructure;	  

  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); 

  /* Enable FSMC Bank1_SRAM Bank */
  FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);
}


Теперь получаем, что общаться с экраном можно простыми функциями:

void Lcd_Reset (void)
{
LCD_RESET_ON;
Delay (300);
LCD_RESET_OFF;
}

void Lcd_Com (int com)
{
 *(uint16_t *) (LCD_REG) = com;	
}

void Lcd_Data (int data)
{
*(uint16_t *) (LCD_DATA)= data;
}

void Lcd_Com_Data (int addr, int data)
{
Lcd_Com	(addr);
Lcd_Data (data);
Delay (2);
}

int Lcd_Read (void)
{
int tmp1;
*(uint16_t *) (LCD_REG) = 0x0000;
tmp1 = *(uint16_t *) LCD_DATA;        //Read
return tmp1;
}


Всё намного короче и удобнее, чем при прямом обращении через порты.

Когда контроллер готов работать с внешними устройствами, читаем тип контроллера из ячейки 0x00. В зависимости от типа экрана, отправляем конфигурацию:

if  (Lcd_Read () == 0x9325)
    {	
    Lcd_Com_Data (0x00e7,0x0010); 
    Lcd_Com_Data (0x0000,0x0001);       /* start internal osc */
    Lcd_Com_Data (0x0001,0x0100);     
    Lcd_Com_Data (0x0002,0x0700);       /* power on sequence */
    Lcd_Com_Data (0x0003,(1<<12)|(0<<5)|(1<<4)|(0<<3) );        /* importance */
    Lcd_Com_Data (0x0004,0x0000);                                   
    Lcd_Com_Data (0x0008,0x0207);                  
    Lcd_Com_Data (0x0009,0x0000);         
    Lcd_Com_Data (0x000a,0x0000);       /* display setting */        
    Lcd_Com_Data (0x000c,0x0001);       /* display setting */        
    Lcd_Com_Data (0x000d,0x0000);                               
    Lcd_Com_Data (0x000f,0x0000);     /* Power On sequence */
    Lcd_Com_Data (0x0010,0x0000);   
    Lcd_Com_Data (0x0011,0x0007);
    Lcd_Com_Data (0x0012,0x0000);                                                                 
    Lcd_Com_Data (0x0013,0x0000);  Delay (2000);  /* delay 50 ms */             
    Lcd_Com_Data (0x0010,0x1590);   
    Lcd_Com_Data (0x0011,0x0227);  Delay (2000);  /* delay 50 ms */        
    Lcd_Com_Data (0x0012,0x009c);  Delay (2000);  /* delay 50 ms */             
    Lcd_Com_Data (0x0013,0x1900);   
    Lcd_Com_Data (0x0029,0x0023);
    Lcd_Com_Data (0x002b,0x000e);  Delay (2000);  /* delay 50 ms */             
    Lcd_Com_Data (0x0020,0x0000);                                                            
    Lcd_Com_Data (0x0021,0x0000);  Delay (2000);  /* delay 50 ms */             
    Lcd_Com_Data (0x0030,0x0007); 
    Lcd_Com_Data (0x0031,0x0707);   
    Lcd_Com_Data (0x0032,0x0006);
    Lcd_Com_Data (0x0035,0x0704);
    Lcd_Com_Data (0x0036,0x1f04); 
    Lcd_Com_Data (0x0037,0x0004);
    Lcd_Com_Data (0x0038,0x0000);        
    Lcd_Com_Data (0x0039,0x0706);     
    Lcd_Com_Data (0x003c,0x0701);
    Lcd_Com_Data (0x003d,0x000f);  Delay (2000);  /* delay 50 ms */             
    Lcd_Com_Data (0x0050,0x0000);        
    Lcd_Com_Data (0x0051,0x00ef);   
    Lcd_Com_Data (0x0052,0x0000);     
    Lcd_Com_Data (0x0053,0x013f);
    Lcd_Com_Data (0x0060,0xa700);        
    Lcd_Com_Data (0x0061,0x0001); 
    Lcd_Com_Data (0x006a,0x0000);
    Lcd_Com_Data (0x0080,0x0000);
    Lcd_Com_Data (0x0081,0x0000);
    Lcd_Com_Data (0x0082,0x0000);
    Lcd_Com_Data (0x0083,0x0000);
    Lcd_Com_Data (0x0084,0x0000);
    Lcd_Com_Data (0x0085,0x0000);
    Lcd_Com_Data (0x0090,0x0010);     
    Lcd_Com_Data (0x0092,0x0000);  
    Lcd_Com_Data (0x0093,0x0003);
    Lcd_Com_Data (0x0095,0x0110);
    Lcd_Com_Data (0x0097,0x0000);        
    Lcd_Com_Data (0x0098,0x0000);  /* display on sequence */    
    Lcd_Com_Data (0x0007,0x0133);	 //Power On
    Lcd_Com_Data (0x0020,0x0000);                                                            
    Lcd_Com_Data (0x0021,0x0000);
    }
}


Получилось много страниц непонятного кода.

Ещё нам нужна простая функция для заполнения экрана каким-либо цветом, и чтобы засветить пиксель по координатам x и y.


void Lcd_Go_XY (int x, int y)
{
Lcd_Com_Data (0x0020,x);    //пишем X 
Lcd_Com_Data (0x0021,y);    //пишем Y 
}

void Lcd_Fill_Screen (int color)
{
uint32_t j= LCD_MAX_X * LCD_MAX_Y; //240*320 
Lcd_Go_XY (0, 0);
Lcd_Com (0x0022);
while(j)
	{
	Lcd_Data (color);
	j--;
	}
}

void Lcd_Put_Pix (int x, int y, int col)
{
Lcd_Go_XY (x,y);
Lcd_Com_Data (0x0022,col);  //col - цвет пикселя
}


В следующей статье будем разбираться с подключением сенсорного экрана через SPI.
  • +8
  • 14 ноября 2013, 17:08
  • ilus

Комментарии (20)

RSS свернуть / развернуть
два вопроса. почему сначала команда, а потом данные? не атомарно же. не может исполниться команда со старыми данными в случае задержки?
командой feel вы пытаетесь что то прочувствовать?
+1
  • avatar
  • xar
  • 14 ноября 2013, 18:07
почему сначала команда, а потом данные?
Отправляется либо команда (под командой подразумевается то, что записывается в нужный регистр), либо данные (цвет пикселя, который пишется в 0x0022 регистр).
Сначала пишем адрес, потом содержание (команда или данные).
командой feel вы пытаетесь что то прочувствовать
поправил.
0
командой feel вы пытаетесь что то прочувствовать?
xD
у меня чуть истерика не началась
-ты не сможешь этого увидеть, ты должен это почувствовать…
-ok, uint8_t Feel (void)…
-ты че так скудно чувствуешь??????!!!
-ok, uint16…
-ух е…
-тише, тише, uint32_t Feel…
-ты че такой однополярный???????????
-все, я понял int Feel (void)

меня понесло, продолжать не буду… %)
+3
Похоже тут ошибка:
uint32_t j= LCD_MAX_X * LCD_MAX_X;

Ну и Lcd_Feel_Screen наверное лучше переименовать в Lcd_Fill_Screen.
А вообще — спасибо, жду продолжения.
0
спасибо… поправил
0
Товарищ ilus добавьте тег пожалуйста!
0
cut
0
добавил, забыл..)
0
Два регистра (адреса и команды/данных) находятся по адресу 0x60000000 (адрес) и 0x60020000 (команды/данные)
Это не совсем так. На самом деле физический адрес один и тот же (точнее это целая область не дешифрированных адресов FSMC_A15..A0, а 0x60000000 берется для определенности, по сути можно было бы обращаться к любому адресу в этой области), а вот бит FSMC_A16 адресной шины (помним, что FSMC работает с дисплеем по 16-битной шине данных) подключен не к адресной шине внешнего устройства (микросхемы SRAM например), а на вход RS дисплея. Когда для обращения к регистрам дисплея необходимо RS установить в «1», мы обращаемся по адресу 0x60020000 (FSMC_A16 эквивалентен A17 для 8-битной шины данных).
А сам сигнал RS является селектором адрес_регистра/данные. При обращении к дисплею с RS = 1 по шине данных в дисплей пишется адрес одного из регистров дисплея, а при RS = 0 идет чтение/запись данных соответственно в/из ранее определенного регистра. Особенность регистра дисплея с адресом 0х22 — это то, что для нас его, как регистра, по сути нет (!!), а при обращении к нему открывается доступ к памяти пикселей дисплея (причем потоковой, с автоинкрементом адреса, блин, ну не буду сейчас усложнять...). Что и делается в Lcd_Fill_Screen, а к какой именно точке на экране — устанавливается с помощью предыдущих команд, таких как Lcd_Go_XY (0, 0);
+2
Очень важное дополнение. Поэтому, думаю, стоит сначала самому написать функцию «общения» с экраном с помощью портов ввода-вывода. Помогает лучше понимать работу через fsmc.
Еще хорошо бы разобраться с самим fsmc, чтобы не обращаться с ним «в слепую».
0
Спасибо за статью.

Есть вопрос:

Вы заполнили NORSRAMTimingInitStructure первый раз, затем вызвали
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_NORSRAMTimingInitStructure;
  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);


Затем поменяли тримминги и вызвали

FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_NORSRAMTimingInitStructure;
  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);   


Но ведь на момент второго вызова и
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct и FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct ссылаются на одну и туже структуру, и второй вызов переопределит и ReadWriteTiming и WriteTiming …

Это ошибка, или какая-то неочевидная особенность?

И еще (хоть это и не очень принципиально) – почему у вас в коде все переменные int? ИМХО, логичнее, в данном случае, использовать uint16_t.
0
Я тоже увидел, что код повторяется. Но именно эту область я перекопировал без каких либо изменений.

Как-то написалось так. Вообще, интересно было бы знать, как контроллер хранит 8-ми или 16-ти битные переменные. Занимают они все 32 бита?
Насколько понимаю, uint16_t или uint8_t это не сами ключевые слова, а define какого-нибудь unsigned int?
0
Насколько понимаю, uint16_t или uint8_t это не сами ключевые слова, а define какого-нибудь unsigned int?
Да, это просто синонимы, но они не зависят от платформы. Грубо говоря, uint8_t — на любой платформе, это беззнаковая переменная размером 8 бит. uint16_t, по аналогии, это беззнаковая переменная, 16 бит. А вот размер int может изменятся от платформы к платформе, битовая размерность int стандартом языка не определена. Ровно как и битовая размерность других типов (char, short, long)
Для данной платформы, uint16_t это синоним unsigned short

Я тоже увидел, что код повторяется. 

Хм, а попробуйте, в порядке эксперимента, закоментровать первый вызов
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);

и проверить работоспособность.

По логике, от этого ничего изменится не должно. Либо, действительно, есть неочевидные моменты.
+1
Это взято из китайского кода к STM32 Mini… второй вызов действительно переопределит времена во FSMC_BTR1 и это даже хорошо, потому что времена будут установлены более быстрыми.
Но и если даже попытаться использовать две структуры для определения времен, то все равно запишется только одна, та которая определялась для FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct, т.к. FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct используется только для случая работы FSMC в ExtendedMode, а здесь этого нет:
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;

Так что можно смело убрать первое определение времен:
//FSMC_NORSRAMTimingInitStructure.FSMC_AddressSetupTime = 10;  
  //FSMC_NORSRAMTimingInitStructure.FSMC_AddressHoldTime = 0;        
  //FSMC_NORSRAMTimingInitStructure.FSMC_DataSetupTime = 10;      
  //FSMC_NORSRAMTimingInitStructure.FSMC_BusTurnAroundDuration = 0x00;
  //FSMC_NORSRAMTimingInitStructure.FSMC_CLKDivision = 0x00;
  //FSMC_NORSRAMTimingInitStructure.FSMC_DataLatency = 0x00;
  //FSMC_NORSRAMTimingInitStructure.FSMC_AccessMode = FSMC_AccessMode_A;
а также первый вызов
//FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
оставив только второй.
+1
Первый блок убрал, всё работает, полёт нормальный. Глюков вроде нет. В статье тоже уберу.
0
Как уже писал выше, убери и первый вызов инициализации FSMC:
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
он не полный — времена остаются заданными по умолчанию, второй вызов дополняет инициализацию в части таймирования, а в остальном просто дублирует его.
+1
времена остаются заданными по умолчанию

Все верно, но не факт, что при первом вызове тайминги остаются по умолчанию, ибо не факт, что FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct равна NULL. Сама структура выделяется в стеке, не инициализируется компилятором. Часть полей мы инициализируем явно, остальные, потенциально, содержат мусор.

На момент первого вызова инициализации FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct может содержать любое значение, которое будет интерпретировано как указатель на FSMC_NORSRAMTimingInitTypeDef. Если повезет и указатель будет NULL – останутся дефолтные тайминги. В противном случае – установятся значения вычитанные из «левого» адреса памяти.
0
Да, согласен, Вы абсолютно правы, в тайминги набьется мусор при первом вызове, он в исправленном тексте вообще ни к чему. Нижняя строка инициализации приведет все в чувство.
0
А оставшиеся выводы адреса FSMC A17-A23 можно использовать в своих целях, например аналоговые входы, или они заняты FSMC?
0
можно. Работа выводов зависит не от модуля, который их использует, а от параметров в регистрах GPIO.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.