Читатель с ником RandoMan, которого зовут Сергей, любезно поделился своим опытом подключения LCD дисплея от Nokia 1202 к микроконтроллеру STM32F0 с использованием аппаратного SPI. Статью Сергей опубликовал на своем сайте, но разрешил мне перепечатку здесь, чем я и воспользовался. Спасибо ему за это. Я думаю, многим пригодится его опыт подключения этого дисплея к микроконтроллерам STM. Тем более, что скорость работы с дисплеем возросла до 700fps!!! Итак, привожу статью Сергея в оригинале:
Для любителей микроконтроллеров экраны от старых телефонов 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 при закрашивании всего экрана.
Файлы библиотеки для STM32 и демопример — lcd1202.zip
Вливайтесь в обсуждение
  5 комментариев
70ruRandoMan .
11 лет назадНебольшая ошибочка — скорость возросла до 700fps 🙂
70 было в программном режиме.
Chiper
11 лет назад🙂 хм…
Щас поправлю…
Алик Зелимханов
9 лет назадЗдравствуйте.скажите какое IDE использовали?пытаюсь использовать ваши исходники,вылезают ошибки и варниги,
типа
timer.c(9): error: #20: identifier «NVIC_InitTypeDef» is undefined
TIM_TimeBaseInitTypeDef
RCC_ClocksTypeDef и так далее.
Может в исходниках какие то файлы не хватает? Кстати пользуюсь Кейлом.
Andrej
8 лет назадKEIL НЕ имеет по умолчанию SPL.Надо как-то подключать библиотеку SPL к проекту в KEIL вручную.Либо как вариант писать на CMSIS код будет намного шустрее и места займет меньше…
Гость_Случайный
7 лет назадЭтот исходник как пример я использовал, большое спасибо тем кто участвовал в распространении. Использовал STM32F0K6T6 и дисплей Nokia 1110i, Nokia 1200, порт SPI в режиме NSS тоже. Привожу настройки регистров порта, так как они есть в работающей схеме:
Биты регистра CR1:
CPHA=0 (Clock Phase)
CPOL=0 (Clock Polarity)
MSTR=1 (Master Selection)
BR=1 (BR[2:0] bits (Baud Rate Control))
SPE=1 (SPI Enable)
LSBFIRST=0 (Frame Format (мл.бит.первым))
SSI=1 (Internal slave select)
SSM=0 (Software slave management)
RXONLY=0 (Receive only)
BIDIOE=1 (Output enable in bidirectional mode)
BIDIMODE=1 (Bidirectional data mode enable)
Биты регистра CR2:
SSOE=1 (SS Output Enable)
NSSP=1 (NSS pulse management Enable)
FRF=0 (Frame Format Enable(TI-mode)
DS=8 (DS[3:0] Data Size)
Повышал тактовую до 16 мгц, — работает. Код инициализации иногда брал другой, не на всех дисплеях (они от неизвестного производителя) работал оригинальный.