Altinkaya: турецкие корпуса для РЭА

Оптимизированный интерфейс и адаптер для подключения к Arduino ЖК дисплея с 16-битной шиной. Часть 2 - Программная реализация

Часть  1 - Схемотехническое решение

Примечание.
Выборки исходного кода, размещенные в тексте описания, использовать в своих проектах не рекомендуется. В конце описания имеется ссылка на архив с исходными кодами.

Выбираем схему BMS для заряда литий-железофосфатных (LiFePO4) аккумуляторов

Автор проекта разработал программный драйвер для управления TFT модулем и библиотеку графических функций. Программный драйвер, поддерживающий два режима работы и различные TFT модули, компонуется с библиотекой графических функций на этапе компиляции с помощью Си++ классов. Исходные коды имеют подробные комментарии автора, а в статье мы коснемся только основных моментов. В первой части статьи мы упомянули, что важным преимуществом адаптера является совместимость с интерфейсом внешней памяти XMEM. Именно с этого режима работы мы и начнем.

16-битный доступ к TFT модулю через интерфейс XMEM

Интерфейс XMEM предназначен для доступа микроконтроллера к внешней памяти с 16-битной шиной адреса и 8-битной шиной данных. Так случилось, что протокол доступа к внешней памяти достаточно близок к протоколу управления TFT модулями (на контроллерах с 16-разрядным i80-совместимом системным интерфейсом). Стоит заметить, что не со всеми TFT модулями можно работать через интерфейс XMEM, что связано с различным временем доступа и тайм-слотами контроллеров TFT панелей.

Для записи данных во внешнюю память Arduino выполняет следующие действия:

  1. Устанавливает на линии ALE лог. 1.
  2. 16-битный адрес выставляется на шину, где младшие 8 бит адреса мультиплексированы с линиями данных.
  3. Устанавливается лог. 0 на линии ALE.
  4. Запись данных осуществляется по нарастающему фронту на линии WR (из лог. 1 в лог. 0 и обратно в лог. 1).

Для нашего случая мы можем использовать эту последовательность для передачи 16 бит данных в рамках одной операции, включая управление линий RS (выбор регистра). Мы будем использовать младшие 8 бит из 16-битного адреса для передачи младших 8 бит данных для TFT модуля. Адресная линия A8 будет использоваться для удержания сигнала RS, а 8 линий данных используются для передачи старших 8 бит данных для TFT модуля. Адресный бит 15 необходимо установить в лог. 1, чтобы гарантировать отсутствие конфликтов с внутренним ОЗУ. Расположение выводов платы Arduino при работе с дисплеем через интерфейс XMEM изображено в Таблице 1.

Таблица 1. Назначение выводов платы Arduino при использовании интерфейса XMEM.

Arduino Порт Функция
22 PA0 D0/D8
23 PA1 D1/D9
24 PA2 D2/D10
25 PA3 D3/D11
26 PA4 D4/D12
27 PA5 D5/D13
28 PA6 D6/D14
29 PA7 D7/D15
35 PC2 /RESET
37 PC0 RS
39 PG2 /CS
41 PG0 /WR

Инициализация интерфейса XMEM сводится к простой записи корректных значений в два конфигурационных регистра. Выводы интерфейса XMEM 30–34 (порты PC3 – PC7) освобождаются под линии ввода/вывода общего назначения:

inline void Xmem16AccessMode::initialise() 
{
// установка вывода Reset
pinMode(RESET_PIN,OUTPUT);      
digitalWrite(RESET_PIN,HIGH);      
// установка регистров интерфейса XMEM      
// освобождаем порты PC3-PC7      
XMCRB=_BV(XMM1) | _BV(XMM2);      
// включаем XMEM    
XMCRA=_BV(SRE);    
}  

Первостепенным требованием для нас является производительность, поэтому функции записи данных и команд были написаны на ассемблере:

inline void Xmem16AccessMode::writeData(uint8_t lo8,uint8_t hi8) {      
// Представленная запись эквивалентна:      
// *reinterpret_cast <volatile uint8_t *>(0x8100 | lo8)=hi8;      
// Такой метод займет пять циклов тактовой частоты        
__asm volatile(" ldi r27,0x81  nt"
               " mov r26,%0    nt"
               " st X,%1       nt"
               :: "d" (lo8), "d" (hi8)          
               : "r26", "r27");    
}

На ассемблере связка двух регистров r26 и r27 образует 16-разрядный регистр X, который используется в функции инициализации интерфейса XMEM и функции записи 8-битных данных во внешнюю память. Кроме того, во встроенном ассемблерном коде регистры r26 и r27 можно свободно использовать, не опасаясь конфликтов с кодом генерируемым Си-компилятором. Тем не менее, эти регистры объявлены в “clobber list” в конце ассемблерного кода (clobber list – список регистров, которые будут модифицироваться в ассемблерном коде).

Метод записи команды аналогичен записи данных, за исключением сброса бита A9 (лог. 0):

inline void Xmem16AccessMode::writeData(uint8_t lo8,uint8_t hi8) {      
// Представленная запись эквивалентна:      
// *reinterpret_cast <volatile uint8_t *>(0x8000 | lo8)=hi8;      
// Такой метод займет пять циклов тактовой частоты        
__asm volatile(" ldi r27,0x80  nt"        
               " mov r26,%0    nt"
               " st X,%1       nt"               
               :: "d" (lo8), "d" (hi8)
               : "r26", "r27");    
}

Чтобы убедиться в работоспособности этого режима работы потребуется логический анализатор. Автор использовал анализатор Ant18e (Рисунок 8).

Диаграмма сигналов интерфейса XMEM: младшие 8 бит данных, сигналы RS и WR.
Рисунок 8. Диаграмма сигналов интерфейса XMEM: младшие 8 бит данных, сигналы RS и WR.

На диаграмме сигналов между двумя маркерами времени отчетливо видно, что микроконтроллер AVR генерирует импульс на линии WR ровно за один машинный цикл (за один такт).

Некоторые TFT модули, с которыми проводились эксперименты, не работали корректно через интерфейс XMEM ввиду отсутствия или очень коротких задержек в тайм-слотах, даже после введения в код циклов задержки передачи данных. Поэтому было решено разработать еще один режим работы, эмулирующий автоматическую работу интерфейса XMEM, и, как выяснилось после тестирования, не зря.

Режим 16-битного доступа к TFT модулю через линии ввода/вывода общего назначения (GPIO access mode)

Чтобы развеять мнение о низкой производительности такого интерфейса, сразу нужно заметить, что программный код был оптимизирован, что в некоторых случая привело к огромному увеличению производительности. В этом режиме мы программно эмулируем автоматическую работу интерфейса XMEM с помощью обычных портов ввода/вывода. Метод имеет свои преимущества и недостатки. Однократная операция записи данных становится несколько более медленной по сравнению с XMEM интерфейсом, однако при записи множества одинаковых данных алгоритм можно оптимизировать, создав соответствующую процедуру для TFT модулей, поддерживающих 16-битный режим. Другим преимуществом является то, что мы не ограничены выводами интерфейса XMEM, и TFT модуль можно подключить к обычной плате Arduino на микроконтроллере с 32 Кбайт встроенной памяти.

Рассматривать инициализацию интерфейса мы не будем, ввиду того, что код, который просто настраивает порты и направление передачи данных, не представляет большого интереса.

Определение выводов микроконтроллера для подключения адаптера осуществляется в шаблоне класса Gpio16LatchAccessModeXmemMapping, содержащего в качестве параметров имя порта и номера выводов, к которым подключен адаптер. Например, для использования тех же выводов, которые использовались при доступе через интерфейс XMEM, необходимо определить следующий тип:

struct Gpio16LatchAccessModeXmemMapping {       
enum {            
// Индексы портов, не путать с физическим адресом          
PORT_DATA  = 0x02,    // PORTA          
PORT_WR    = 0x14,    // PORTG          
PORT_RS    = 0x08,    // PORTC          
PORT_ALE   = 0x14,    // PORTG          
PORT_RESET = 0x08,    // PORTC            
// индексы выводов портов, не путать с выводами Arduino          
PIN_WR    = PIN0,          
PIN_RS    = PIN0,          
PIN_ALE   = PIN2,          
PIN_RESET = PIN2        
  };      
};

Шаблон класса Gpio16LatchAccessMode представлен ниже.

template<typename TPinMappings>
 class Gpio16LatchAccessMode {        
protected:          
   static uint8_t _streamIndex;          
   static void initOutputHigh(uint8_t port,uint8_t pin);        
public:          
   static void initialise();          
   static void hardReset();
          
   static void writeCommand(uint8_t lo8,uint8_t hi8=0);          
   static void writeCommandData(uint8_t cmd,uint8_t data);          
   static void writeData(uint8_t lo8,uint8_t hi8=0);          
   static void writeMultiData(uint32_t howMuch,uint8_t lo8,uint8_t hi8=0);          
   static void writeStreamedData(uint8_t data);    
};      
typedef Gpio16LatchAccessMode<Gpio16LatchAccessModeXmemMapping> DefaultMegaGpio16LatchAccessMode;;

Кроме того, вы можете видеть, что для простоты использования конкретно определяется псевдоним типа с помощью ключевого слова typedef.

Основное внимание необходимо уделить алгоритму, выполнящему однократную запись данных WriteData() и, особенно, алгоритму записи последовательности данных writeMultiData (). Сперва рассмотрим метод записи одного значения:

template<class TPinMappings>
inline void Gpio16LatchAccessMode<TPinMappings>::writeData(uint8_t lo8,uint8_t hi8) {        
__asm volatile(      
          "  sbi %1, %5   nt"     // ALE   = HIGH
          "  out %3, %7   nt"     // PORTA = lo8
          "  sbi %2, %6   nt"     // RS    = HIGH
          "  cbi %1, %5   nt"     // ALE   = LOW
          "  out %3, %8   nt"     // PORTA = hi8
          "  cbi %0, %6   nt"     // /WR   = LOW
          "  sbi %0, %6   nt"     // /WR   = HIGH
          :: "I" (TPinMappings::PORT_WR),     // %0
             "I" (TPinMappings::PORT_ALE),    // %1
             "I" (TPinMappings::PORT_RS),     // %2
             "I" (TPinMappings::PORT_DATA),   // %3
             "I" (TPinMappings::PIN_WR),      // %4
             "I" (TPinMappings::PIN_ALE),     // %5
             "I" (TPinMappings::PIN_RS),      // %6
             "d" (lo8),                       // %7
             "d" (hi8)                        // %8
            );
    }

Простой метод. Это так называемая программная реализация протокола (bit-bang) контроллера TFT панели (с i80-совместимой системной шиной) с дополнительным выполнением задачи программирования регистра-защелки. Такой подход очень распространен в графических библиотеках для прорисовки цветных блоков. При выполнении очистки экрана, рисовании прямоугольников или просто прямых линий все сводится к установке «выходного окна» на дисплее, а затем выполняется прорисовка точек.

Метод записи множества данных WriteMultiData () обеспечивает определенное преимущество в том, что вам необходимо только один раз установить данные и линию RS, а затем изменять состояние линии WR столько раз, сколько нужно для прорисовки графического блока точек. Посмотреть авторский вариант этого метода можно в Листинге 1.

Работает данный код следующим образом:

  1. Мы устанавливаем 16 бит данных и линию управления RS.
  2. Если при входе в процедуру были включены прерывания, то мы их отключаем, чтобы обработчик прерывания не изменил состояние портов.
  3. Читаем состояние порта, с которого осуществляется управление линией WR, и сохраняем два значения в памяти: одно с установленным битом WR, другое – со сброшенным битом WR.
  4. Если количество точек, которое нужно записать, менее 40, то переходим к пункту 7.
  5. С помощью последовательных операций out передаем данные о 40 точках (пикселях). Скорость записи достигает 8 млн. точек в секунду.
  6. Вычитаем значение 40 из счетчика точек и, если их все еще больше 40, то возвращаемся к пункту 5.
  7. Рассчитываем косвенный переход на выполнение оставшихся 39 операций вывода данных, которые заполнят оставшиеся пиксели, и используем для перехода команду ijmp.
  8. Если мы отключали прерывания, то восстанавливаем их.

Значение 40 было выбрано опытным путем, как компромисс между используемой Flash-памятью и производительностью. Работоспособность кода и недостаток программной реализации алгоритма наглядно демонстрируется на Рисунке 9 и 10.

Диаграмма сигналов шины данных TFT модуля при работе через общий интерфейс ввода/вывода Arduino
Рисунок 9. Эффект оптимизации операций записи данных в TFT модуль. Скорость заполнения 8 млн пикселей в секунду, длительность импульса записи 120 нс.
едостаток программной реализации алгоритма - временнáя задержка длительностью 1 мкс,
Рисунок 10. Недостаток программной реализации алгоритма – появление временнóй задержки длительностью 1 мкс, связанной с разделением общего количества пикселей на блоки по 40 пикселей и переход к следующему блоку.

Безусловно, требуется дальнейшая ручная оптимизация, и в следующей части статьи мы познакомимся с некоторыми аспектами алгоритма оптимизации прорисовки линий, рассмотрим варианты конфигурирования программного драйвера для различных контроллеров TFT панелей.

Продолжение следует...

Часть 3 - Оптимизация алгоритма прорисовки линий и варианты конфигурирования драйвера

andybrown.me.uk

Перевод: Vadim по заказу РадиоЛоцман

На английском языке: A Generic Optimized 16-bit LCD Adaptor for the Arduino. Part 2 - Software

Электронные компоненты. Бесплатная доставка по России
Для комментирования материалов с сайта и получения полного доступа к нашему форуму Вам необходимо зарегистрироваться.
Имя