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



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


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

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

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

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




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)
подаваемая тактовая частота для «парсинга» битов из MISO\MOSI
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 режима работы:


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


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


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


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

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



Так чем же мы можем управлять на нашем контроллере?
Во-первых, у нас есть память, которую контроллер отображает на ЖК-матрице.
Во-вторых, у нас есть каретка памяти с координатами 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 каретки () 1 X6 X5 X4 X3 X2 X1 X0
установить координату Y каретки () 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 () 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В.



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

Код писать и отлаживать будем в CooCox IDE:
  1. Запустим CoIDE и создадим новый проект

  2. Выберем нужные нам модули GPIO (для управления ногами), SPI (для управления SPI), RCC

  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



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

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

Некоторые иллюстрации были взяты с http://easystm32.ru/interfaces/43-spi-interface-part-1

Поделиться публикацией
Похожие публикации
Никаких подозрительных скриптов, только релевантные баннеры. Не релевантные? Пиши на: adv@tmtm.ru с темой «Полундра»

Зачем оно вам?
Реклама
Комментарии 32
  • +1
    Отличная статья, все просто и доступно. Пишите еще! :)
    • +1
      Спасибо! Если статья окажется востребованной, то постараемся описать все этапы нашей разработки в похожем ключе.
      • 0
        А что ещё прикручивали к дискавери?
        С SD картами не пробовали, такого туториала не планируете?
        • +1
          Следующее, что планируем сделать — подключить SD-карточку и аппаратный MP3-декодер для создания простейшего MP3-плеера. К сожалению, в провинции достать тот же MP3-декодер не предоставляется возможности, поэтому всё зависит от сроков работы Почты России и нашего свободного времени.
          • 0
            Если я не ошибаюсь, то дискавери тянет софтварный декодинг с слабым битрейтом. (кажется на том же easyelectronics обсуждалось).
            Я звук в speex из буфера играл как-то.
            Хотел с карточки, но что-то у меня не завелось с карточкой, а я в этом совсем чайник и на этом застрял пол года тому назад.
            С радостью бы почитал туториал на эту тему.
            • 0
              Что тянет VL — не знаю, проц там слабенький, но на STM32F103 mp3-шки даже с приличным битрейтом воспроизводились, а на F4 вообще видео воспроизводится.
              • 0
                Для распаковки MP3 нужно больше памяти, ~24-32кБ. Скорости процессора хватает с запасом.
      • 0
        Смею предположить, что если вы захотите пустить свое изделие в производство, то найти такие дисплеи будет проблематично. Как вариант можно использовать экранчики от Nokia 1202 (сейчас в руках держу такой, датирован мартом 2013). Но у него есть особенность — 9-битный SPI.
        • 0
          Промышленный масштаб мы не планировали, скорее наоборот — хотелось сделать какой-нибудь уникальный гаджет для себя. Пока всё на уровне изучения, составления примерного плана работы и выкройки свободного времени. К слову, китайские запчасти есть в свободном доступе даже в провинции, в том самом сервисном центре, о котором мы писали в статье, по небольшой цене (100 рублей за ту же самую модель)
        • +4
          Оригинальное оформление)
          Пара моментов:
          — Называть переменные капсом: GPIO_InitTypeDef PORT; — дурной тон. Оставьте капс для макрсов.
          — Вот так: VDD_Pin_Port->ODR |= VDD_Pin; писать стоит очень аккуратно. Это типичная проблема Read-Modify-Write, если на середине эта операция прервется прерыванием, которое тоже что-то запишет в порт — получим редкий и трудноуловимый баг. В GPIO портах STM-ок для этого предусмотрены регистры BSR и BRR, обеспечивающие атомарную запись бита в порт.
          В остальном неплохая статья для начинающих
          • 0
            Вот так: VDD_Pin_Port->ODR |= VDD_Pin; писать стоит очень аккуратно. Это типичная проблема Read-Modify-Write, если на середине эта операция прервется прерыванием, которое тоже что-то запишет в порт — получим редкий и трудноуловимый баг. В GPIO портах STM-ок для этого предусмотрены регистры BSR и BRR, обеспечивающие атомарную запись бита в порт.

            На AVR есть аналогичный механизм для атомарного выставления битов в регистрах. И как минимум gcc умеет распознавать такие языковые конструкции и использует для них правильные последовательности команд.
            • 0
              И как минимум gcc умеет распознавать такие языковые конструкции и использует для них правильные последовательности команд.

              Пример можно? А то вот разглядываю ассемблерный листинг gcc, например для модификации бита там всё просто, три команды: IN из порта, модификация, OUT в порт. Вполне можно глюк словить, если не знать.
              • +1
                Поэкспериментировал немного. На stm32 действительно компиляторной магии не получается, надо использовать аппаратную магию в виде BSRRL и BSRRH. А вот на avr оптимизация есть, но она срабатывает не во всех случаях:
                void test_or() {
                    PORTB |= 1 << 5;
                }
                
                void test_and() {
                    PORTB &= ~(1 << 5);
                }
                
                void test_xor() {
                    PORTB ^= 1 << 5;
                }
                
                void test_xor2() {
                    PORTB ^= ~(1 << 5);
                }
                
                void test_or_arg(uint8_t bit) {
                    PORTB |= 1 << bit;
                }
                

                результат:
                00000000 <test_or>:
                   0:	2d 9a       	sbi	0x05, 5	; 5
                   2:	08 95       	ret
                
                Disassembly of section .text.test_and:
                
                00000000 <test_and>:
                   0:	2d 98       	cbi	0x05, 5	; 5
                   2:	08 95       	ret
                
                Disassembly of section .text.test_xor:
                
                00000000 <test_xor>:
                   0:	85 b1       	in	r24, 0x05	; 5
                   2:	90 e2       	ldi	r25, 0x20	; 32
                   4:	89 27       	eor	r24, r25
                   6:	85 b9       	out	0x05, r24	; 5
                   8:	08 95       	ret
                
                Disassembly of section .text.test_xor2:
                
                00000000 <test_xor2>:
                   0:	85 b1       	in	r24, 0x05	; 5
                   2:	9f ed       	ldi	r25, 0xDF	; 223
                   4:	89 27       	eor	r24, r25
                   6:	85 b9       	out	0x05, r24	; 5
                   8:	08 95       	ret
                
                Disassembly of section .text.test_or_arg:
                
                00000000 <test_or_arg>:
                   0:	95 b1       	in	r25, 0x05	; 5
                   2:	21 e0       	ldi	r18, 0x01	; 1
                   4:	30 e0       	ldi	r19, 0x00	; 0
                   6:	08 2e       	mov	r0, r24
                   8:	00 c0       	rjmp	.+0      	; 0xa <test_or_arg+0xa>
                   a:	22 0f       	add	r18, r18
                   c:	0a 94       	dec	r0
                   e:	02 f4       	brpl	.+0      	; 0x10 <test_or_arg+0x10>
                  10:	92 2b       	or	r25, r18
                  12:	95 b9       	out	0x05, r25	; 5
                  14:	08 95       	ret
                

                Видно, что в случае, если компилятор видит |= или &= и на этапе компиляции знает, что в маске только один бит, то он применяет специальные атомарные команды. Для ^= он такую оптимизацию применить уже не догадывается. Если операция производится не над константой, то тоже не догадывается и делает неатомарно и неэффективно.

                Пожалуй это делает возможный баг еще менее предсказуемым.
                • 0
                  Для ^= он такую оптимизацию применить уже не догадывается.

                  Так для xor вроде бы нету атомарной операции изменения бита?

                  А так вполне ожидаемо, что на stm32 компилятор не самовольничает. Если в AVR для атомарной установки/сброса бита есть инструкция, то у stm-ок это отдельный регистр. И обращаться по другому адресу компилятор не в праве.
                  Есть еще такая штука как bit-band-регионы, области памяти, в которых обращение к определенному 32-битному слову эквивалентно обращению к соответствующему биту в другом регионе.
                  • 0
                    Так для xor вроде бы нету атомарной операции изменения бита?

                    Там сделано похоже на stm32, для каждого регистра PORTxn есть регистр PINxn. Если туда записать 1, то соответсвующий бит в PORTxn изменится.
                    И обращаться по другому адресу компилятор не в праве.

                    Возможно. Хотя это не обычные адреса и запись в BSRRL можно рассматривать как способ обращения к ODR. Но я не готов спорить о таких тонких моментах.
                    • +1
                      Там сделано похоже на stm32, для каждого регистра PORTxn есть регистр PINxn. Если туда записать 1, то соответсвующий бит в PORTxn изменится

                      На новых сериях. Если память не изменяет, даже на классической 8й меге этого ещё не было.
                  • 0
                    Видно, что в случае, если компилятор видит |= или &= и на этапе компиляции знает, что в маске только один бит, то он применяет специальные атомарные команды. Для ^= он такую оптимизацию применить уже не догадывается.

                    Я именно такой тест и проводил, когда писал коммент. Скорее всего при модификации одного бита порта используются sbi/cbi не из-за соображений атомарности/безопасности, а тупо из-за экономии времени и программной памяти (что логично, раз есть такая инструкция). Достаточно написать PORTB |= (1<<5)|(1<<6); — опять чтение-модификация-запись (по другому ведь нельзя, если не считать, что программист написал бы два sbi, т.к. это выгоднее, но так же небезопасно).

                    Вот если бы компилятор, например, отключал прерывания при модификации переменных, это можно было бы назвать какой-то защитой, но за такие выкрутасы я бы первый его выкинул.
            • 0
              Вот тут всё замечательно разжевано про SPI.
              Студентов учу общаться с памятью типа 25AA010A — у неё замечательная документация, в которой все времена хорошо прописаны и графики красиво нарисованы.

              И еще. Мне представляется, что не следует вводить в заблуждение людей, говоря что вы «включаете» или «выключаете» #SS для выбора конкретного слейва. Вы его таки включаете. Просто для #SS активный уровень сигнала — физический ноль, а не единица.
              • 0
                Напишите в следующий раз про реализацию USB :)
              • 0
                А не подскажете в Siemens C55 и Siemens A57 один контроллер дисплея стоит? (единственное отличие в разрешении, 102*64 против 101*64 соответственно)
                • +1
                  И даже в этом различий нет. Одна строка является «сервисной» (как мы поняли) и не отображается, поэтому в тех. характеристиках телефонов написано 101х64, а у контроллеров 102х64. Аналогичные дисплеи можно найти в A52\A55\A57
                • 0
                  Порадовали! Особенно внимательностью к разъяснению деталей для чайников! Давно примеряюсь к stm32, но пока не начал их использовать. (Фоток бы побольше.)
                  • +1
                    Расскажите кто-нибудь «для чайников» и по шагам, как это всё компилировать и загружать на устройство.
                    Допустим, у меня завалялась отладочная плата с мк и готовый дистриб «иде+компилятор+отладчик» под вин скачаный у производителя борды. И в этой связке оно даже работает как нужно, но хочется романтики и писать из под линукса и своей привычной ИДЕшки, компилируя и загружая на устройство чем-либо, что можно поставить из пакетов и поковырять вручную.
                    • +2
                      Да никаких там проблем. Даже отладка в железе работает. вот например цикл статеек. А вот так я даже всё это на Sublime Text прикручивал.
                      • +2
                        Моя статья, на которую вы любезно сослались, несколько устарела. Сейчас вместо утилиты stlink я бы порекомендовал использовать OpenOCD, в котором есть отличная поддержка отладчиков ST-Link 1 и 2, которые стоят на платах STM32***Discovery. Про его настройку я читал тут:

                        we.easyelectronics.ru/CADSoft/ubuntu-eclipse-code-sourcery-openocd-j-link-arm-ili-bystryy-start-dlya-somnevayuschihsya.html

                        # Только я использую немного другую последовательность команд для OpenOCD:

                        file Debug/stm32vld_nrf_rx.elf
                        target extended-remote :3333
                        monitor reset halt
                        load
                        monitor reset

                        # Сам OpenOCD запускаю так:

                        sudo openocd -f <config>

                        # Конфиг для STM32VLDiscovery:

                        telnet_port 4444
                        gdb_port 3333

                        source [find interface/stlink-v1.cfg]
                        source [find target/stm32f1x_stlink.cfg]

                        set WORKAREASIZE 0x2000
                        set CHIPNAME «STM32F100RBT6»

                        init
                        reset halt

                        # Последний раз я использовал OpenOCD 0.6.1 в Ubuntu 12.10

                        P.S. Если гражданину gaelpa покажется мало приключений, описанных в обеих статьях, он может также попытаться собрать GCC toolchain вручную *громкий дьявольский смех*
                        • 0
                          Да, эта статья у меня давно в избранном, вот только руки не дойдут испытать.
                      • 0
                        Уровень «чайников» тоже разный)

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.