Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55

в 11:31, , рубрики: diy или сделай сам, SPI, stm32vldiscovery, метки: ,

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55

Disclaimer! Авторы не гарантируют истину в последней инстанции, во всяком случае из-за малоопытности в новой для них сфере. Если вы видите грубую техническую ошибку, очень просим вас сообщить о ней как можно скорее!

Свела как-то судьба начинающего программиста и начинающего электронщика вместе. И начали они творить. Сделали они сдесяток небольших игрушек-мишек с записывающими звуковыми модулями и поняли, что использовать однофункциональные платы не так уж и весело. И вот светлыми летними вечерами они собирались и думали, что бы им такого интересного сделать? Судьба помогла им и во второй раз: они нашли объявление о продаже отладочной платы STM32VLDiscovery в России и уже через неделю трясущимися руками распаковали посылку и «поморгали» светодиодами. От ощущения полной власти над крошечным устройством загорелись глаза и заработали мозги. Было решено: сделать «электронный браслет» с каким-нибудь интересным функционалом, используя весь потенциал их новой «игрушки»…

Глаза загорелись ещё больше, когда мы узнали о возможных способах применения STM32. Первым делом мы задумались об подключении всяческой периферии. На руках у нас был семисегментный ЖКИ от советских часов. Подключили, написали таймер, но успокоиться не могли…

На следующий день зашли в небольшой магазинчик при сервисном центре и были ошарашены от огромного количества телефонов на разбор: 3 стенда, полностью заполненных различными «хитами» среди сотовых телефонов прошлых лет. Всего за 80 рублей был куплен Siemens C55, впоследствии ставший нашим донором.
Кое-кав расковыряв корпус, мы достали ЕГО: монохромный LCD-экран LPH7999-4 с разрешением 102 на 64 пикселя. Состоит он из, собственно, ЖК-матрицы и контроллера PCF8812 (ссылка на даташит). Контроллер же состоит из DDRAM, в котором побитово в виде таблицы хранятся состояния пикселей (1), IO-буфера, нескольких генераторов тока и логических элементов. Все действия с LCD происходят непосредственно с помощью контроллера через 8 ног VDD, SCK, MISO, DC, SS, GND, VOUT, RES, из которых две замыкаются через конденсатор, а остальные подключаются к ногам нашего процессора.

Одновременно в наших головах проскочила мысль: «Что же это за непонятные обозначения такие и как этим можно вообще управлять?». С выражением полной печали и безысходности мы начали гуглить, постоянно натыкаясь на полные неизвестных терминов статьи. В итоге после нескольких дней в наших головах немного уложилась новая информация.

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55
Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55

MOSI (или SIMO, SDO, DO, DOUT, SO, MTSR)
Master Output Slave Input
выход Master'а, который должен быть подключен к входам Slave'ов
MISO (или SOMI, SDI, DI, DIN, SI, MRST)
Master Input Slave Output
вход Master'а, в который подаются выходы Slave'ов
SCK (или SCLK, CLK) подаваемая тактовая частота для «парсинга» битов из MISOMOSI
SS (или CS) выбор периферийного устройства, с которым мы будем работать.
Если устройств, больше чем одно, то для работы с конкретным нужно ВЫКЛЮЧИТЬ ногу-перемычку на выбранном и ВКЛЮЧИТЬ на всех остальных устройствах.

Итак, для управления нашим LCD мы должны использовать SPI — стандартизированный интерфейс для общения с периферией. Чтобы им воспользоваться, нужно понимать принцип работы и всю связанную с ним терминологию, а в частности названия и предназначения всех ног.

Интерфейс SPI предполагает, что у нас существует какое-то ОДНО устройство, которое будет всем управлять (Master) и множество управляемых периферийных устройств, таких как датчики, ЖКЖКИ, карты памяти, АЦПЦАП и т.д. (Slave). На нашем Master'е мы должны выбрать 3 ноги для приёмапередачи данных и n ног-перемычек, где n — количество подключаемых периферийных устройств. На Slave-устройствах ноги для приёмапередачи обычно определены заранее (если это, конечно, не ещё один процессор) и описаны в соответствующих даташитах.

Рассмотрим пример работы передачи и получения данных между абстрактным Master'а и одним абстрактным устройством Slave.
Примечание! SPI устроен следующим образом: при передаче данных от MOSI Master'а к MISO Slave'а происходит одновременная передача данных от MOSI Slave'а к MISO Master'а и наоборот. Таким образом, сигнал SCK один и тот же для MISO и MOSI, соответственно работают они одинаково.

При передаче одного байта задействованы выходы SCK и MOSI. Ны выходе SCK идут тактирующие импульсы (перепады напряжения от логического нуля до логической единицы). При передаче логической единицы на выходе напряжение ~3.3В, при передаче нуля, соответственно ~0В. Длительность состояния логического нуля и логической единицы равны и задаются программно. При передаче одного байта, на каждый бит приходится импульс. Таким образом, на выходе SCK при передаче байта мы можем увидеть восемь одинаковых «горбков». На выходе MOSI передается непосредственно наша информация. Например, если мы передаем 10000001, сигнал будет выглядеть как большая яма, а если 10011001, то как яма с выступом по середине. Как по отдельности работают оба выхода сейчас, думаю, ясно, теперь же расскажем о том, как они между собой согласованы.

В режиме простоя. Тот момент, когда ничего не передается, то есть в промежутке между передачей байтов или же до начала их передачи при включенном SPI. Логично было бы предположить, что при отсутстии каких бы то ни было операций на обоих входах будет 0. Но нет, в режиме простоя на MOSI напряжение логической единицы, на SCK либо логической единицы, либо нуля. Это состояние SCK мы можем выбирать сами.

В режиме передачи. Здесь нам предстоит выбрать, как будут согласованы импульсы портов SCK и MOSI. Для этого придется ввести несколько плохих слов:
Фронт — это переход из одного состояние в другое, то есть скачок напряжения от логической единицы к логическому нулю. На изображении импульса это вертикальные палочки.
Фронт бывает нарастающим и спадающим: нарастающий — переход от логического нуля к логической единице, спадающий — наоборот, от логической единицы к логическому нулю.
Фронт бывает также передний и задний: передний фронт — первый случившийся скачок после режима простоя, задний фронт — второй случившийся скачок после режима простоя.

Разработчик может выбрать для SCK режим простоя (логическая единица или ноль) и режим передачи (по переднему или заднему фронту). Итого, выходит 4 режима работы:

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55
Режим 0 (00):
Режим простоя — логический ноль.
Передача по переднему фронту.
Так как мы выбрали передачу по переднему фронту, во время перехода от напряжения логического нуля до напряжения логической единицы на SCK, на MOSI произойдет передача бита.

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55
Режим 1 (01):
Режим простоя — логический ноль.
Передача по заднему фронту.
Так как мы выбрали передачу по заднему фронту, то сначала идет передний нарастающий фронт, потом некоторое время держится напряжение логического нуля, потом идет задний спадающий фронт. После этого на MOSI произойдет передача бита.

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55
Режим 2 (10):
Режим простоя — логическая единица.
Передача по переднему фронту.
Во время передачи идет импульс на SCK. Но он не нарастающий, в отличие от двух предыдущих режимов, а спадающий. Так как выше напряжения логической единицы напряжения быть не может, первый импульс идет «вниз». Именно во время этого перехода (ведь мы выбрали передний фронт) и происходит передача бита на MOSI.

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55
Режим 3 (11):
Режим простоя — логическая единица.
Передача по заднему фронту.
Во время передачи идет импульс на SCK, сначала спадающий, потом нарастающий. В это время происходит переход на MOSI.

Обычно режим работы не обозначен в даташитах, но его легко получить, если изучить поведение MOSIMISO и SCK на каком-нибудь графике в даташите.

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55

Так чем же мы можем управлять на нашем контроллере?
Во-первых, у нас есть память, которую контроллер отображает на ЖК-матрице.
Во-вторых, у нас есть каретка памяти с координатами X и Y
В-третьих, у нас есть сдесяток различных битов:

	Бит PD - если 0, то контроллер включен, если 1 - то контроллер в спящем режиме
	Бит V  - если 0, то после записи данных происходит сдвиг каретки по Х на единицу, иначе сдвиг по Y на 9 (т.е. сразу после записанного столбика)
	Бит H -  если 0, то включен режим для работы с обычным набором инструкций, если 1 - то с расширенным
	Биты D и E отвечают за режим работы дисплея:
		00 - все пиксели не горят
		01 - все пиксели горят
		10 - если состояние пикселя в памяти 1, то он горит, если 0, то не горит (нормальный режим)
		11 - если состояние пикселя в памяти 1, то он НЕ горит, если 0, то горит (инверсия)
	Биты TC1 и TC0 отвечают за коэффициент температуры LCD
		00 - коэффициент 0
		01 - коэффициент 1
		10 - коэффициент 2
		11 - коэффициент 3
	Биты S1 и S0 отвечают за множитель внутреннего питания, т.е. теоретически во сколько раз питание, 
		поданное на VDD, будет отличаться от внутреннего питания
		00 - в два раза больше
		01 - в три раза больше
		10 - в четыре раза больше
		11 - в пять раз ботльше
	Биты Vop6-Vop0 отвечают за величину исходного внутреннего напряжения
	Биты BS2-BS0 отвечают за смещение системы

Теперь приведём возможные команды для управления. Каждая из них формируется ровно из 8 бит:

(в любом режиме инструкций)
установить регистры PD, V, H 0 0 1 0 0 PD V H
(в режиме обычного набор инструкций)
установить регистры D, E 0 0 0 0 1 D 0 E
установить координату X каретки (Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55) 1 X6 X5 X4 X3 X2 X1 X0
установить координату Y каретки (Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55) 0 1 0 0 Y3 Y2 Y1 Y0
(в расширенном наборе инструкций)
установить регистр TC 0 0 0 0 0 1 TC1 TC0
установить регистр S 0 0 0 0 1 0 S1 S0
установить регистр BS 0 0 0 1 0 BS2 BS1 BS0
установить регистр V (Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55) 1Vop5 Vop6 Vop5 Vop4 Vop3 Vop2 Vop1 Vop0

Чтобы корректно инициализировать LCD, мы должны подать напряжение на VDD, отключить на RES, подождать 100 мкс и подать на RES снова. При ОТКЛЮЧЕНИИ питания на RES контроллер переходит в спящий режим, RAM гарантировованно не очищается, множество регистров получают своё дефолтное значение. Подробнее можно почитать на стр. 14 в даташите на контроллер.
После этого мы должны выключить SS (т.е. «выбрать» устройство для работы) и выключить DC (т.е. начать передачу команд) и передать с помощью SPI несколько инициализирующих команд:

  1. включить питание, а заодно выставить V=0 и H=1
  2. выбрать коэффициент температуры TC=11
  3. установить максимальное внутренне питание Vop=1111111
  4. включить множитель внутреннего питания S=01
  5. установить смещение системы BS=011
  6. включить режим обычного набора инструкций H=0, V=0, PD=0
  7. выбрать обычный режим работы дисплея D=1, E=0

После этого наш дисплей загорится и покажет нам рандомные пиксели, взятые из необнулённой RAM.
К сожалению, на контроллере нет MOSI (т.е. нет никакой обратной связи), поэтому если дисплей ничего не показывает, то чтобы убедится, работает ли хотя бы SPI, после вышеописанных команд нужно померить напряжение на 7 ноге LCD.
Опытным (и долгим) путём выяснено следующее: если дисплей ничего не показывает, а напряжение на 7 ноге есть, то это означает, что SPI работает и что дисплею недостаточно внутреннего напряжения и его нужно увеличить с помощью регистров Vop и S (поставить на максимумы, например). В нашем случае дисплей загорался при ~6В.

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55

Теорию мы изучили и теперь перейдём к реализации. Есть два способа реализовать работу с SPI: сделать всё ручками при помощи управления ногами процессором (software spi) или же использовать «железную» реализацию (hardware spi), которая есть в нашей STM32. Я, например, не вижу смысла реализовывать интерфейс с помощью мощностей процессора, поэтому использую hardware spi.

Код писать и отлаживать будем в CooCox IDE:

  1. Запустим CoIDE и создадим новый проект
    Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55
  2. Выберем нужные нам модули GPIO (для управления ногами), SPI (для управления SPI), RCC
    Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55
  3. Напишем небольшой каркас для нашей будущей программы
    #include "stm32f10x_gpio.h"
    #include "stm32f10x_rcc.h"
    #include "stm32f10x_spi.h"
    
    void SPIInit(void) {
    
    }
    
    void GPIOInit(void) {
    
    }
    
    void LCDInit(void) {
    
    }
    
    int main() {
    	SystemInit();
    	GPIOInit();
    	SPIInit();
    	LCDInit();
    	return 0;
    }
    

  4. Начнём с заполнения GPIO. Для этого с помощью даташита на STM32 мы должны узнать, где находятся ноги «железного» SPI. У нашей модели это PA5 (SCK), PA6 (MISO), PA7 (MOSI). Эти ноги могут быть как обычными ногами, так и ногами SPI, поэтому нам нужно явно указать предполагаемое назначение и задействовать их.
  5. Рассмотрим оставшиеся ноги:
    VDD нога для подключения питания, подключается к произвольной ноге на STM32 (в нашем случае на LPH7999-4 предел подключения до 6.5V, а STM32 выдаёт 3.3V на каждую свою ногу)
    VOUT вывод внутреннего питания, подключается к земле экрана через конденсатор на х мФ.
    GND земля, см. VOUT
    RES нога для управления сбросом контроллера, подключается к произвольной ноге на STM32
    DC нога, отвечающая за режим передачи данных в контроллер, подключается к произвольной ноге на STM32. Если на ноге нет напряжения, то контроллер LCD интерпретирует полученные данные как команду, а если есть, то как набор из 8 пикселей, которые запишутся столбиком в DDRAM относительно местонахождения каретки.
    SS см. выше, подключается к произвольной ноге на STM32

  6. Припаиваем SCK, MOSI к PA5 и PA7, а DC, VDD, RES и SS к произвольным ногам. У нас это PB0, PB1, PB2, PB3 соответственно.
  7. Пишем код:
    #define SCK_Pin  GPIO_Pin_5
    #define SCK_Pin_Port GPIOA
    
    #define MOSI_Pin GPIO_Pin_7
    #define MOSI_Pin_Port GPIOA
    
    #define DC_Pin  GPIO_Pin_0
    #define DC_Pin_Port GPIOB
    
    #define VDD_Pin GPIO_Pin_1
    #define VDD_Pin_Port GPIOB
    
    #define RST_Pin GPIO_Pin_2
    #define RST_Pin_Port GPIOB
    
    #define SS_Pin  GPIO_Pin_3
    #define SS_Pin_Port GPIOB
    
    void GPIOInit(void)
    {
    	// включаем тактирование (=питание) на порты A, B и железный SPI1
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_SPI1, ENABLE); 
    	GPIO_InitTypeDef PORT;
    	// выбрали ноги для настройки
    	PORT.GPIO_Pin   = SCK_Pin | MOSI_Pin;					
    	// установили наименьшую скорость (максимальная скорость контроллера 4 Мбита в секунду)
    	PORT.GPIO_Speed = GPIO_Speed_2MHz;						
    	// (важно!) определяем предназначение ног. здесь - выбор "альтернативной функции" ног
    	PORT.GPIO_Mode  = GPIO_Mode_AF_PP;						
    	// настроили ноги в порту А
    	GPIO_Init(GPIOA, &PORT);								
    
    	// выбрали ноги для настройки
    	PORT.GPIO_Pin   = DC_Pin | VDD_Pin | RST_Pin | SS_Pin;	
    	// установили скорость (тут - без разницы)
    	PORT.GPIO_Speed = GPIO_Speed_2MHz;						
    	// предназначение - общее, выход
    	PORT.GPIO_Mode  = GPIO_Mode_Out_PP;						
    	// настроили ноги в порту B
    	GPIO_Init(GPIOB, &PORT);								
    }
    

    Напишем вспомогательные процедуры для читабельности кода:

  8. void PowerOn() {
    	VDD_Pin_Port->ODR |= VDD_Pin;
    }
    
    void PowerOff() {
    	VDD_Pin_Port->ODR &= ~VDD_Pin;
    }
    
    void ResetOn() {
    	RST_Pin_Port->ODR |= RST_Pin;
    }
    
    void ResetOff() {
    	RST_Pin_Port->ODR &= ~RST_Pin;
    }
    
    void DCOn() {
    	DC_Pin_Port->ODR |= DC_Pin;
    }
    
    void DCOff() {
    	DC_Pin_Port->ODR &= ~DC_Pin;
    }
    
    void SSOff() {
    	SS_Pin_Port->ODR &= ~SS_Pin;
    }
    
    void SSOn() {
    	SS_Pin_Port->ODR |= SS_Pin;
    }
    

  9. Теперь настроим SPI:
    void SPIInit(void) {
        SPI_InitTypeDef SPIConf;
        // указываем, что используем мы только передачу данных
        SPIConf.SPI_Direction = SPI_Direction_1Line_Tx;
        // указываем, что наше устройство - Master
        SPIConf.SPI_Mode = SPI_Mode_Master;
        // передавать будем по 8 бит (=1 байт)
        SPIConf.SPI_DataSize = SPI_DataSize_8b;
        // режим 00
        SPIConf.SPI_CPOL = SPI_CPOL_Low;
        SPIConf.SPI_CPHA = SPI_CPHA_1Edge;
        SPIConf.SPI_NSS = SPI_NSS_Soft;
        // установим скорость передачи (опытным путём выяснили, что разницы от изменения этого параметра нет)
        SPIConf.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
        // передаём данные старшим битом вперёд (т.е. слева направо)
        SPIConf.SPI_FirstBit = SPI_FirstBit_MSB;
        // внесём настройки в SPI
        SPI_Init(SPI1, &SPIConf);
        // включим  SPI1
        SPI_Cmd(SPI1, ENABLE);
        // SS = 1
        SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set);
    }
    

  10. Напишем функцию отправки данных по SPI
    void SPISend(uint16_t data) {
    	SPI_I2S_SendData(SPI1, data);  // отправили данные
    	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // ждём, пока данные не отправятся
    }
    

  11. Допишем инициализацию в соответствии с теорией
    void LCDInit(void) {
        SSOff();
        DCOff();
        PowerOn();
        ResetOff();
        ResetOn();
        SPISend(0x21);   // включаем питание, устанавливаем сдвиг каретки, включаем режим расш. инстр.
        SPISend(0b1001); // устанавливаем трёхкратный множитель внутреннего питания
        SPISend(0xFF);   // включаем максимальное внутреннее питание
        SPISend(0x06);   // устанавлиаем температуру
        SPISend(0x13);   // устанавливаем bias (смещение системы)
        SPISend(0x20);   // ..., включаем режим обычных инструкций
        SPISend(0b1100); // включаем нормальный режим дисплея
    }
    

  12. Project — Build (или F7)
  13. Flash — Program Download
  14. Смотрим и радуемся :3

Здесь можно скачать готовый проект для CooCox

Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55

Конечно, удивить в 2013 году подключенным дисплеем к ARM-процессору сложно. Для нас, как для начинающих разработчиков, это — первый шаг к реализации своего проекта уникального «электронного браслета».
Мы не мечтаем выйти на рынок, мы просто хотим получить опыт, сделать функциональный, стильный и долгоработающий гаджет для себя, а заодно и рассказать о наших успехах и неудачах здесь.

Сейчас наше устройство умеет рисовать картинки на экране, предварительно переделанные в массив 8-битных «столбиков» с помощью этого скрипта на Python'е, требующего Python 2.7 и PIL.
Использование: photo.py file24bit.bmp > bytes.c

Некоторые иллюстрации были взяты с http://easystm32.ru/interfaces/43-spi-interface-part-1
Изучаем STM32 на практике. Часть 1. Подключение экрана от Siemens C55

Автор: 3ap

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js