Индикатор загрузки процессора без микроконтроллера

Здравствуйте, уважаемые радиолюбители. После выхода моей статьи: Использование MCP2210 и видеоролика, про микросхему MCP2210, которая является переходником из интерфейса USB в SPI мне часто задавали вопросы по поводу практического применения. Сегодня я решил показать как можно применить данный переходник. Для этого я выбрал довольно известный SPI дисплей, на который я буду выводить график загруженности центрального процессора. К статье прикреплен полноценный видеоролик, можете не читать статью, а просто посмотреть его.

Давайте начнем с «железной» части. Подключим дисплей к переходнику по следующей схеме:

По сути, с помощью вывода D/C выбирается, что будет передаваться команда или данные. Подробное описание работы экранчика есть в даташите, который лежит в архиве к статье. Там все команды и структуры данных. Питание экрана подается напрямую с USB порта 5 В, потому что в нем стоит собственный стабилизатор на 3.3 В, а вот питание подсветки 3.3 В. Подсветку советуют подключать через резистор в 10 Ом, я поставил временно какой попался под руку в 470 Ом.

Как не печально осознавать, но на этом «железная» часть заканчивается и начинается программирование.

Теперь запускаем MS Visual Studio и создаем новый проект консольного приложения на языке C#. Для работы с данным SPI экранчиком есть множество библиотек написанных под различные микроконтроллеры. Чтобы не усложнять себе жизнь я взял подобную библиотеку для написания основных функций для работы с экраном. Я упакую эту библиотеку в архив к статье.

Нам необходимо переписать функции отправки команд и битов данных. Далее проинициализировать экран и реализовать функцию прорисовки одного пикселя.

Приведу сразу весь листинг программы с комментариями.

using mcp2210_dll_m;

namespace TFT
{
class Program
{
static byte[] Tx = new byte[1];
static byte[] Rx = new byte[1];
//скорость передачи данных
static uint rate = 10000000;
//количество байт передачи/приема
static uint numberbyte = 1;
// открываем модуль
static StringBuilder Path = null;
static IntPtr module = MCP2210.M_Mcp2210_OpenByIndex(0x4D8, 0xDE, 0, Path);
//создаем переменную, в которой хранится состояние GPIO
static uint resvet = 0;

static void TFT_sendCMD(byte index)//функция отправляет на экран команду
{
MCP2210.M_Mcp2210_SetGpioPinVal(module, 3, ref resvet);//TFT_DC_LOW;
MCP2210.M_Mcp2210_SetGpioPinVal(module, 2, ref resvet);//TFT_CS_LOW;
Tx[0] = index;
MCP2210.M_Mcp2210_xferSpiData(module, Tx, Rx, ref rate, ref numberbyte, 0x02);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 3, ref resvet);//TFT_CS_HIGH;
}

static void TFT_sendDATA(byte data)//функция отправляет на экран байт данных
{
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_DC_hign;
MCP2210.M_Mcp2210_SetGpioPinVal(module, 6, ref resvet);//TFT_CS_LOW;
Tx[0] = data;
MCP2210.M_Mcp2210_xferSpiData(module, Tx, Rx, ref rate, ref numberbyte, 0x06);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_CS_HIGH;
}

static void TFT_sendWord(int data)//отправка 2-хбайтовых чисел
{
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_DC_hign;
MCP2210.M_Mcp2210_SetGpioPinVal(module, 6, ref resvet);//TFT_CS_LOW;
Tx[0] = (byte)(data>>8);
MCP2210.M_Mcp2210_xferSpiData(module, Tx, Rx, ref rate, ref numberbyte, 0x06);
Tx[0] = (byte)(data & 0x00ff);
MCP2210.M_Mcp2210_xferSpiData(module, Tx, Rx, ref rate, ref numberbyte, 0x06);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_CS_HIGH;
}

static void TFT_setCol(int StartCol, int EndCol)//установка колонки
{
TFT_sendCMD(0x2A);
TFT_sendWord(StartCol);
TFT_sendWord(EndCol);
}

static void TFT_setPage(int StartPage, int EndPage)// установка строчки
{
TFT_sendCMD(0x2B);
TFT_sendWord(StartPage);
TFT_sendWord(EndPage);
}

static void TFT_setXY(int poX, int poY)//установка координат точки
{
TFT_setCol(poX, poX);
TFT_setPage(poY, poY);
TFT_sendCMD(0x2c);
}

static void TFT_setPixel(int poX, int poY, int color)//функция прорисовки пикселя
{
TFT_setXY(poX, poY);
TFT_sendWord(color);
}

static void Main(string[] args)
{
//проверяем подключен ли модуль
int error = MCP2210.M_Mcp2210_GetConnectedDevCount(0x4D8, 0xDE); //стандартные VID и PID у модуля MCP2210
if (error == 0)
{
Console.WriteLine(«Ошибка! Модуль не подключен!»);
Console.ReadKey();
}
else
{
Console.WriteLine(«Модуль открыт!»);
//настройка выводов CS
uint cs_idle = 0xFFFFFF;
uint cs_activ = 0xFFFFFE;
//задержки всякие
uint cs_to_data_dly = 0;
uint data_to_data_dly = 0;
uint data_to_cs_dly = 0;

//режим SPI
byte spi_mode = (byte)MCP2210.M_MCP2210_SPI_MODE0;

//настраиваем GIPO все выводы GPIO, кроме GPIO0, который CS
byte[] gpio = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };

//настраиваем GPIO
MCP2210.M_Mcp2210_SetGpioConfig(module, (byte)MCP2210.M_MCP2210_VM_CONFIG, gpio, 0, 0, (byte)MCP2210.M_MCP2210_REMOTE_WAKEUP_DISABLED, (byte)MCP2210.M_MCP2210_INT_MD_CNT_NONE, (byte)MCP2210.M_MCP2210_SPI_BUS_RELEASE_DISABLED);
MCP2210.M_Mcp2210_SetSpiConfig(module, (byte)MCP2210.M_MCP2210_VM_CONFIG, ref rate, ref cs_idle, ref cs_activ, ref cs_to_data_dly, ref data_to_cs_dly, ref data_to_data_dly, ref numberbyte, ref spi_mode);

//запуск инициализации дисплея
MCP2210.M_Mcp2210_SetGpioPinVal(module, 2, ref resvet);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 3, ref resvet);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 5, ref resvet);
System.Threading.Thread.Sleep(10);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);
System.Threading.Thread.Sleep(50);
TFT_sendCMD(0xCB);
TFT_sendDATA(0x39);
TFT_sendDATA(0x2C);
TFT_sendDATA(0x00);
TFT_sendDATA(0x34);
TFT_sendDATA(0x02);
TFT_sendCMD(0xCF);
TFT_sendDATA(0x00);
TFT_sendDATA(0xC1);
TFT_sendDATA(0x30);
TFT_sendCMD(0xE8);
TFT_sendDATA(0x85);
TFT_sendDATA(0x00);
TFT_sendDATA(0x78);
TFT_sendCMD(0xEA);
TFT_sendDATA(0x00);
TFT_sendDATA(0x00);
TFT_sendCMD(0xED);
TFT_sendDATA(0x64);
TFT_sendDATA(0x03);
TFT_sendDATA(0x12);
TFT_sendDATA(0x81);
TFT_sendCMD(0xF7);
TFT_sendDATA(0x20);
TFT_sendCMD(0xC0); //Power control
TFT_sendDATA(0x23); //VRH[5:0]
TFT_sendCMD(0xC1); //Power control
TFT_sendDATA(0x10); //SAP[2:0];BT[3:0]
TFT_sendCMD(0xC5); //VCM control
TFT_sendDATA(0x3e); //Contrast
TFT_sendDATA(0x28);
TFT_sendCMD(0xC7); //VCM control2
TFT_sendDATA(0x86); //—
TFT_sendCMD(0x36); // Memory Access Control
TFT_sendDATA(0x48); //C8 //48 68绔栧睆//28 E8 妯睆
TFT_sendCMD(0x3A);
TFT_sendDATA(0x55);
TFT_sendCMD(0xB1);
TFT_sendDATA(0x00);
TFT_sendDATA(0x18);
TFT_sendCMD(0xB6); // Display Function Control
TFT_sendDATA(0x08);
TFT_sendDATA(0x82);
TFT_sendDATA(0x27);
TFT_sendCMD(0xF2); // 3Gamma Function Disable
TFT_sendDATA(0x00);
TFT_sendCMD(0x26); //Gamma curve selected
TFT_sendDATA(0x01);
TFT_sendCMD(0xE0); //Set Gamma
TFT_sendDATA(0x0F);
TFT_sendDATA(0x31);
TFT_sendDATA(0x2B);
TFT_sendDATA(0x0C);
TFT_sendDATA(0x0E);
TFT_sendDATA(0x08);
TFT_sendDATA(0x4E);
TFT_sendDATA(0xF1);
TFT_sendDATA(0x37);
TFT_sendDATA(0x07);
TFT_sendDATA(0x10);
TFT_sendDATA(0x03);
TFT_sendDATA(0x0E);
TFT_sendDATA(0x09);
TFT_sendDATA(0x00);
TFT_sendCMD(0xE1); //Set Gamma
TFT_sendDATA(0x00);
TFT_sendDATA(0x0E);
TFT_sendDATA(0x14);
TFT_sendDATA(0x03);
TFT_sendDATA(0x11);
TFT_sendDATA(0x07);
TFT_sendDATA(0x31);
TFT_sendDATA(0xC1);
TFT_sendDATA(0x48);
TFT_sendDATA(0x08);
TFT_sendDATA(0x0F);
TFT_sendDATA(0x0C);
TFT_sendDATA(0x31);
TFT_sendDATA(0x36);
TFT_sendDATA(0x0F);
TFT_sendCMD(0x11); //Exit Sleep
System.Threading.Thread.Sleep(150);
TFT_sendCMD(0x29); //Display on
TFT_sendCMD(0x2c);
Console.WriteLine(«Завершение инициализации дисплея»);

//Прорисовка красного пикселя
TFT_setPixel(100, 100, 0xf800);
}
}
}
}

Запускаем, в нужном месте прорисовывается пиксель. Теперь попробуем зарисовать весь экран по одному пикселю. Для этого просто добавим двойной цикл, прорисовывая по одному пикселю, строчку за строчкой.

А вот тут появляется проблема. Дело в том, что MCP2210 довольно долго готовится к передаче данных по SPI. Выходит что передача одного пикселя занимает 0,3 секунды, а значит на прорисовку всего экрана таким образом уйдет (320*240)*0,3 = 23040 секунд или 6,4 часа… Согласитесь это как то медленно. Кстати, настройка изменения вывода GPIO занимает около 0,001 секунды, значит если мы реализуем программный SPI (дерганьем GPIO) до прорисовка одного пикселя займет около 0,001. Но это как забивать гвозди микроскопом…

Любую проблему можно решить! MCP2210 позволяет отправлять пакеты по 64 байта и если заливать такими большими пакетами, то на зарисовку всего экрана уходит 53 секунды. Вот это уже меня полностью устраивает!

Так как ширина экрана 240 пикселей я решил заливать на экранчик данные пакетами по 60 байт. При этом получается, что для прорисовки одной строчки пикселей мне необходимо отправить 8 пакетов (на 1 пиксель уходить 2 байта). Потому я придумал следующий алгоритм работы.

Пусть одна строчка (или столбик) в 240 пикселей будет равна 100 % загрузке процессора. Значит один процент это 2.4 пикселя. Теперь измеряем значение загруженности процессора и формируем массив из 480 байт: до значения загруженности процессора один цвет, после второй. И выводим на экранчик.

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

Вот это я понимаю! Красота! Давайте к коду.

using System.Diagnostics;
using System.Threading;
using mcp2210_dll_m;

namespace TFT
{
class Program
{
 //создаем системный счетчик
static PerformanceCounter pc = new PerformanceCounter(«Processor», «% Processor Time», «_Total»);//(«Процессор», «% загруженности процессора», «0»);

static byte[] procents = new byte[480];
static byte[] Tx = new byte[1];
static byte[] Tf = new byte[60];
static byte[] Rx = new byte[1];
static byte[] Rf = new byte[60];
//скорость передачи данных
static uint rate = 10000000;
//количество байт передачи/приема
static uint numberbyte = 1;
// открываем модуль
static StringBuilder Path = null;
static IntPtr module = MCP2210.M_Mcp2210_OpenByIndex(0x4D8, 0xDE, 0, Path);
//создаем переменную, в которой хранится состояние GPIO
static uint resvet = 0;

static void TFT_sendCMD(byte index)
{
MCP2210.M_Mcp2210_SetGpioPinVal(module, 3, ref resvet);//TFT_DC_LOW;
MCP2210.M_Mcp2210_SetGpioPinVal(module, 2, ref resvet);//TFT_CS_LOW;
Tx[0] = index;
MCP2210.M_Mcp2210_xferSpiData(module, Tx, Rx, ref rate, ref numberbyte, 0x02);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 3, ref resvet);//TFT_CS_HIGH;
}

static void TFT_sendDATA(byte data)
{
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_DC_hign;
MCP2210.M_Mcp2210_SetGpioPinVal(module, 6, ref resvet);//TFT_CS_LOW;
Tx[0] = data;
MCP2210.M_Mcp2210_xferSpiData(module, Tx, Rx, ref rate, ref numberbyte, 0x06);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_CS_HIGH;
}

static void measurement()//функция измерение загрузки ЦП
{

int pro = (int)pc.NextValue();//получение загрузки ЦП

for (int i = 0; i < 480; i=i+2)
{
procents[i] = 0;
procents[i + 1] = 0;

if (i > (pro * 4.8))
{
procents[i] = 0x00;
procents[i + 1] = 0x00;
}
else
{
procents[i] = 0x00;
procents[i + 1] = (byte)(i/16);//это для поучения градиента синего цвета
//можно вписать сюда любой цвет экспериментируйте!
}
}
}

static void indicator()//вывод на экран
{
TFT_sendCMD(0x2c);
for (int k = 0; k < 8; k++)//цикл прорисовки одного столбца (строчки)
{
for (int i = 0; i < 60; i++)//формирование пакета на отправку
{
Tf[i] = procents[i+60*k];
}
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_CS_HIGH;
MCP2210.M_Mcp2210_SetGpioPinVal(module, 6, ref resvet);//TFT_CS_LOW;
MCP2210.M_Mcp2210_xferSpiData(module, Tf, Rf, ref rate, ref numberbyte, 0x06);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);//TFT_CS_HIGH;
}
}

static void Main(string[] args)
{
//проверяем подключен ли модуль
int error = MCP2210.M_Mcp2210_GetConnectedDevCount(0x4D8, 0xDE); //стандартные VID и PID у модуля MCP2210
if (error == 0)
{
Console.WriteLine(«Ошибка! Модуль не подключен!»);
Console.ReadKey();
}
else
{
Console.WriteLine(«Модуль открыт!»);

//настройка выводов CS
uint cs_idle = 0xFFFFFF;
uint cs_activ = 0xFFFFFE;
//задержки всякие
uint cs_to_data_dly = 0;
uint data_to_data_dly = 0;
uint data_to_cs_dly = 0;

//режим SPI
byte spi_mode = (byte)MCP2210.M_MCP2210_SPI_MODE0;

//настраиваем GIPO все выводы GPIO, кроме GPIO0, который CS
byte[] gpio = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };

//настраиваем GPIO
MCP2210.M_Mcp2210_SetGpioConfig(module, (byte)MCP2210.M_MCP2210_VM_CONFIG, gpio, 0, 0, (byte)MCP2210.M_MCP2210_REMOTE_WAKEUP_DISABLED, (byte)MCP2210.M_MCP2210_INT_MD_CNT_NONE, (byte)MCP2210.M_MCP2210_SPI_BUS_RELEASE_DISABLED);
MCP2210.M_Mcp2210_SetSpiConfig(module, (byte)MCP2210.M_MCP2210_VM_CONFIG, ref rate, ref cs_idle, ref cs_activ, ref cs_to_data_dly, ref data_to_cs_dly, ref data_to_data_dly, ref numberbyte, ref spi_mode);

MCP2210.M_Mcp2210_SetGpioPinVal(module, 2, ref resvet);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 3, ref resvet);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 5, ref resvet);
System.Threading.Thread.Sleep(10);
MCP2210.M_Mcp2210_SetGpioPinVal(module, 7, ref resvet);
System.Threading.Thread.Sleep(50);
TFT_sendCMD(0xCB);
TFT_sendDATA(0x39);
TFT_sendDATA(0x2C);
TFT_sendDATA(0x00);
TFT_sendDATA(0x34);
TFT_sendDATA(0x02);
TFT_sendCMD(0xCF);
TFT_sendDATA(0x00);
TFT_sendDATA(0xC1);
TFT_sendDATA(0x30);
TFT_sendCMD(0xE8);
TFT_sendDATA(0x85);
TFT_sendDATA(0x00);
TFT_sendDATA(0x78);
TFT_sendCMD(0xEA);
TFT_sendDATA(0x00);
TFT_sendDATA(0x00);
TFT_sendCMD(0xED);
TFT_sendDATA(0x64);
TFT_sendDATA(0x03);
TFT_sendDATA(0x12);
TFT_sendDATA(0x81);
TFT_sendCMD(0xF7);
TFT_sendDATA(0x20);
TFT_sendCMD(0xC0); //Power control
TFT_sendDATA(0x23); //VRH[5:0]
TFT_sendCMD(0xC1); //Power control
TFT_sendDATA(0x10); //SAP[2:0];BT[3:0]
TFT_sendCMD(0xC5); //VCM control
TFT_sendDATA(0x3e); //Contrast
TFT_sendDATA(0x28);
TFT_sendCMD(0xC7); //VCM control2
TFT_sendDATA(0x86); //—
TFT_sendCMD(0x36); // Memory Access Control
TFT_sendDATA(0x48); //C8 //48 68绔栧睆//28 E8 妯睆
TFT_sendCMD(0x3A);
TFT_sendDATA(0x55);
TFT_sendCMD(0xB1);
TFT_sendDATA(0x00);
TFT_sendDATA(0x18);
TFT_sendCMD(0xB6); // Display Function Control
TFT_sendDATA(0x08);
TFT_sendDATA(0x82);
TFT_sendDATA(0x27);
TFT_sendCMD(0xF2); // 3Gamma Function Disable
TFT_sendDATA(0x00);
TFT_sendCMD(0x26); //Gamma curve selected
TFT_sendDATA(0x01);
TFT_sendCMD(0xE0); //Set Gamma
TFT_sendDATA(0x0F);
TFT_sendDATA(0x31);
TFT_sendDATA(0x2B);
TFT_sendDATA(0x0C);
TFT_sendDATA(0x0E);
TFT_sendDATA(0x08);
TFT_sendDATA(0x4E);
TFT_sendDATA(0xF1);
TFT_sendDATA(0x37);
TFT_sendDATA(0x07);
TFT_sendDATA(0x10);
TFT_sendDATA(0x03);
TFT_sendDATA(0x0E);
TFT_sendDATA(0x09);
TFT_sendDATA(0x00);
TFT_sendCMD(0xE1); //Set Gamma
TFT_sendDATA(0x00);
TFT_sendDATA(0x0E);
TFT_sendDATA(0x14);
TFT_sendDATA(0x03);
TFT_sendDATA(0x11);
TFT_sendDATA(0x07);
TFT_sendDATA(0x31);
TFT_sendDATA(0xC1);
TFT_sendDATA(0x48);
TFT_sendDATA(0x08);
TFT_sendDATA(0x0F);
TFT_sendDATA(0x0C);
TFT_sendDATA(0x31);
TFT_sendDATA(0x36);
TFT_sendDATA(0x0F);
TFT_sendCMD(0x11); //Exit Sleep
System.Threading.Thread.Sleep(150);
TFT_sendCMD(0x29); //Display on
TFT_sendCMD(0x2c);

Console.WriteLine(«Завершение инициализации дисплея»);

//количество байтов в пакетах SPI
numberbyte = 60;

MCP2210.M_Mcp2210_SetSpiConfig(module, (byte)MCP2210.M_MCP2210_VM_CONFIG, ref rate, ref cs_idle, ref cs_activ, ref cs_to_data_dly, ref data_to_cs_dly, ref data_to_data_dly, ref numberbyte, ref spi_mode);

int interval = 0;

while (true)
{
if (interval == 0)
{
measurement();//измерение ЦП
interval = 5;//размер столбцов на экране
}
indicator();//вывод на экранчик
interval—;
}
}
}
}
}

В цикле формирования строчки — показателя загруженности в качестве цвета я применил индекс i/16. Это дает плавную градацию синего цвета, от черного при нулевой загрузке до ярко голубого при 100% загрузке. Мне показалось так красиво. Поэкспериментируйте!  У каждого свой вкус. 

Вот и все! Получился довольно интересный индикатор загрузки ЦП, при этом в любой момент можно переписать программу и изменить все что хочется. Можно выводить на экран, например, время, интернет трафик, изображения какие-то и т.п. Все что вам надо, это сформировать массив пикселей (по 2 байта на 1 пиксель), а потом делить его на пакеты по 64 байта и слать на экран.

Давайте подведем итог, для чего удобно использовать подобные переходники как MCP2210. 

Во-первых, использование данного переходника позволяет людям, далеким от радиотехники, но разбирающиеся в программировании легко собирать свои собственные устройства. Как было показано в данной статье. Весь программный код запускается и отлаживается на компьютере, что так же удобно.

Во-вторых, использование данных переходников хорошо подходит для тестирования новых устройств, когда вы лишь купили микросхему или экранчик, что угодно, раз-раз подключили, запустили — работает! В этом плане полезно иметь полный набор подобных переходников на SPI, I2C, UART и т.д.

Но как было выяснено в данной статье, микросхема MCP2210 довольно долго думает перед отправкой пакета. Эти 0,3 секунды надо запомнить и учитывать в следующих проектах.

Ну а я попробую найти где можно напечатать корпус и соберу данный индикатор. Повешу его над или под монитором, пускай работает. В темноте выглядит особенно классно!

Всем спасибо, что дочитали статью до конца! Демонстрация работы индикатора приведена в видео ниже.


Прикрепленные файлы:

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

Ваш адрес email не будет опубликован.