Подключение экрана Nokia 1202 к аппаратному SPI контроллера STM32F0

Для любителей микроконтроллеров экраны от старых телефонов Nokia давно стали одним из основных устройств вывода простой, но уже графической информации.
Вот и у меня возник проект, который уже давно тесно связан с подключением какого-нибудь небольшого LCD экрана к микроконтроллеру.
Изначально проект планировалось делать на процессорах ATMega48. Но когда я уже решил, что хватит заниматься проектированием, надо брать макетки и начинать что-то собирать — мне посоветовали посмотреть в сторону STM32. Довольно приятный сюрприз, что полноценная отладочная плата с JTAG на борту стоит всего 500 рублей. Таки был приобретен STM32F0 Discovery.
На борту у него стоит чип STM32F051R8. Применительно к данной статье у него есть отличный плюс — он позволяет изменять ширину слова, передаваемого по шине SPI.
Убедившись, что экран от Nokia 1202 неплохо работает в программном режиме передачи данных, выдавая вполне неплохие 70 кадров в секунду на перерисовку всего экрана, я все же озадачился использованием аппаратных возможностей микроконтроллера для передачи данных в сторону экрана.

Подключение к SPI2
Так ка SPI1 гораздо мощнее, чем SPI2, а мощностей посленего более чем достаточно для подключения LCD экрана, то будем использовать именно его.
Согласно документации, ноги MOSI и SCK располагаются на выводах PB15 и PB13 соответственно. ПО соседству, на отладочной плате располагаются ноги PC6 и PC7. Их мы будем использовать для передачи сигналов CS и RESET соответственно. Можно было бы, конечно, воспользоваться PB14, т.к. он мультиплексируется с MISO, не используемым в данном случае, но мне очень не хотелось менять порядок ног при напаивании на макетку. Да, грешен, разъемов у меня нет, и обжимать лень.

Настройка ног RESET и CS ничем не отличается от обычного программного режима:

/* Configure GPIO for CS/RST pins */
RCC_AHBPeriphClockCmd(LCD_GPIO_RCC, ENABLE);
GPIO_init_str.GPIO_Pin = LCD_RESET_PIN | LCD_CS_PIN;
GPIO_init_str.GPIO_Mode = GPIO_Mode_OUT;
GPIO_init_str.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LCD_GPIO, &GPIO_init_str);
GPIO_WriteBit(LCD_GPIO, LCD_RESET_PIN, Bit_SET); // Поднимаем ногу RESET в состояние логической единицы
GPIO_WriteBit(LCD_GPIO, LCD_CS_PIN, Bit_RESET); // А ногу CS в противоположность, опускаем

Как вы можете заметить, нога CS изначально опускается в состояние логического нуля. Объясняется это оптимизацией производительности — контроллер LCD будет работать и без постоянного изменения состояния CS, однако, это позволит нам сэкономить время. Опытным путем было выяснено, что после опускания ноги CS при работе аппаратного SPI потребуется довольно внушительная пауза, негативно влияющая на производительность.

Для того, чтобы настроить SPI, в первую очередь следует связать ноги PB13 и PB15 с этим самым SPI:

/* Configure GPIOB for SDA/CLK pins */
RCC_AHBPeriphClockCmd(LCD_SPI_GPIO_RCC, ENABLE);
GPIO_PinAFConfig(LCD_SPI_GPIO, LCD_SDA_PIN_SOURCE, GPIO_AF_0);
GPIO_PinAFConfig(LCD_SPI_GPIO, LCD_SCK_PIN_SOURCE, GPIO_AF_0);

А затем настроить PB13 и PB15 на использование альтернативной функции:

/* Configure GPIO pins for SPI */
GPIO_init_str.GPIO_Pin = LCD_SDA_PIN | LCD_SCK_PIN;
GPIO_init_str.GPIO_Mode = GPIO_Mode_AF;
GPIO_init_str.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_init_str.GPIO_OType = GPIO_OType_PP;
GPIO_init_str.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(LCD_SPI_GPIO, &GPIO_init_str);

Немного расскажу о настройке SPI. Семейство экранов, в которые входит в том числе используемый автором данной статьи экран Nokia 1202 используют полудуплексный 9-битный serial interface. Сигнально он полностью совместим с SPI. Однако, не каждый микроконтроллер умеет хитрость с дополнительным битом, который определяет, что сейчас собираются передать контроллеру экрана — данные, или команду.
Поэтому, основные настройки выглядят следующим образом:

SPI_init_str.SPI_Direction = SPI_Direction_1Line_Tx; // Полудуплексный режим, включена только передача со стороны микроконтроллера
SPI_init_str.SPI_Mode = SPI_Mode_Master; //Микроконтроллер является мастером соединения
SPI_init_str.SPI_DataSize = SPI_DataSize_9b; //Режим слова - 9 бит
SPI_init_str.SPI_CPOL = SPI_CPOL_High; //Сигнал Clock в режиме простоя находится в в состоянии логической единицы
SPI_init_str.SPI_CPHA = SPI_CPHA_2Edge; //Передача данных осуществляется по правому краю сигнала Clock, т.е. в момент подъема из 0 в 1
SPI_init_str.SPI_NSS = SPI_NSS_Soft; //Отключаем аппаратное управление CS
SPI_init_str.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //Устанавливаем скорость передачи данных. SPI тактируется от fPCLK.
SPI_init_str.SPI_FirstBit = SPI_FirstBit_MSB; //При передаче слова первым идет наиболее значимый бит (MSB)
SPI_Init(LCD_SPI, &SPI_init_str);

Далее необходимо сообщить ядру SPI состояние ноги NSS (мы же выключили аппаратное управление):

SPI_NSSInternalSoftwareConfig(LCD_SPI, SPI_NSSInternalSoft_Set);

Если этого не сделать, то SPI будет считать, что шина уже занята и ничего передавать не будет.

Ну и финальный аккорд — включаем SPI:

SPI_Cmd(LCD_SPI, ENABLE);

Далее идут уже знакомые процедуры сброса и инициализации экрана.

Передача данных
Для того, чтобы полностью раскрыть возможности аппаратного блока SPI следует передавать данные пачками.
Иначе говоря, следует отказаться от подхода 1 вызов — один байт, а передавать указатель на последовательность данных.
Основное преимущество в данном случае заключается в том, что мы можем поддерживать буфер FIFO шины SPI полным, выжимая максимум скорости передачи.
Выглядит это следующим образом:

void lcd_write(char cd, const char * data, unsigned short int len, unsigned short int repeat)
{
char i;

while(repeat > 0)
{
for(i = 0; i < len; i++)
{
unsigned short int octet = data[i];
if (cd) octet |= 0x100;
SPI_I2S_SendData16(SPI2, octet);
while (SPI_GetTransmissionFIFOStatus(SPI2) == SPI_TransmissionFIFOStatus_Full); //Wait, if SPI FIFO is full
}
repeat --;
}
while (SPI_GetTransmissionFIFOStatus(SPI2) != SPI_TransmissionFIFOStatus_Empty); //Wait, while SPI FIFO not empty
}

Вы можете заметить также полезный параметр - repeat. Это позволяет однотипно закрашивать области в одной транзакции.

На этом, пожалуй, особенности использования аппаратного блока SPI заканчиваются. Результатом стал десятикратный прирост производительности с 70 кадров в секунду до 700 при закрашивании всего экрана.

Исходные коды библиотеки и демонстрационной программы можно взять здесь: lcd1202.zip

Добавить комментарий

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>