Как стать автором
Обновить

ESPspectrum прошивка

Уровень сложностиПростой
Время на прочтение10 мин
Количество просмотров11K

Недавно я собрал первую версию своего устройства, но не смотря на небольшой срок программа имеет множество изменений, доработок и улучшений. В предыдущей статье я кратко рассказал о том как собрать ESPspectrum https://habr.com/ru/articles/783778/. В этой как и планировалось, я расскажу и работе программы ( т.е. операционной системе ESPspectrum ).

На данном этапе для работы с файлами, я использую SPIFFS. В следующей версии будет SD карты( уже заказал все необходимое ) .

На момент выхода следующих статьей, устройство перейдет на новый уровень и их содержимое станет поинтересней.

Основной файл скетча

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

void menuOC();
void freereadKeyboard();
void kalculator();
//files
void file_mode();
void file_read();
String filename();
String write_data();
void file_del();
void file_create();
//games
void snake_game(); //можно удалить, работает без этой строчки
void game_menu();
/*==============================================*/
#define CLOCK 16 //D-
#define DATA 17  //D+

const char keymap[] = {
  0, 0,  0,  0,  0,  0,  0,  0,
  0, 0,  0,  0,  0,  0, '`', 0,
  0, 0 , 0 , 0,  0, 'q', '1', 0,
  0, 0, 'z', 's', 'a', 'w', '2', 0,
  0, 'c', 'x', 'd', 'e', '4', '3', 0,
  0, ' ', 'v', 'f', 't', 'r', '5', 0,
  0, 'n', 'b', 'h', 'g', 'y', '6', 0,
  0, 0, 'm', 'j', 'u', '7', '8', 0,
  0, ',', 'k', 'i', 'o', '0', '9', 0,
  0, '.', '/', 'l', ';', 'p', '-', 0,
  0, 0, '\'', 0, '[', '=', 0, 0,
  0, 0, 13, ']', 0, '\\', 0, 0,
  0, 0, 0, 0, 0, 0, 127, 0,
  0, '1', 0, '4', '7', 0, 0, 0,
  '0', '.', '2', '5', '6', '8', 0, 0,
  0, '+', '3', '-', '*', '9', 0, 0,
  0, 0, 0, 0
};

int  lastscan;
String txt_keys;

/*==============================================*/
#include <ESP32Lib.h>
#include <Ressources/Font6x8.h>
#include <Ressources/CodePage437_8x8.h>
#include <Ressources/CodePage437_8x14.h>
#include <Ressources/CodePage437_8x16.h>

// одинаковые резисторы на R G B!!!!!!

//pin configuration
const int redPin = 33;
const int greenPin = 14;
const int bluePin = 12;
const int hsyncPin = 2;
const int vsyncPin = 4;

//VGA Device
VGA3Bit vga;

/*==============================================*/
#include "FS.h"
#include "SPIFFS.h"

// Создаем объект класса «File» для манипуляции с файлом:
File myFile;
/*==============================================*/
void setup()
{
  Serial.begin(115200);
  //initializing vga at the specified pins
  pinMode(CLOCK, INPUT_PULLUP); //пины клавиатуры
  pinMode(DATA, INPUT_PULLUP);

  vga.init(vga.MODE320x240, redPin, greenPin, bluePin, hsyncPin, vsyncPin);

  vga.setFont(CodePage437_8x16);
  vga.setCursor(2, 5);
  vga.println("Starting...");
  vga.setCursor(2, 25);
  vga.println("ESPspectrum V1");
  vga.setFont(CodePage437_8x14);
  vga.setCursor(2, 100);
  vga.println("created on 2023");
  delay(5000);
  vga.clear(vga.RGB(0, 0, 0));
  delay(1000);
  vga.setCursor(0, 0);

   
  if(!SPIFFS.begin(true)){  // Инициализируем / "Ошибка при монтировании SPIFFS"
    Serial.println("Error while mounting SPIFFS"); vga.println("Error while mounting SPIFFS"); 
    delay(2000); vga.clear(vga.RGB(0, 0, 0)); vga.setCursor(0, 0); return; }
    
}

int menu = 0;
void loop() {
  menuOC(); Serial.println(txt_keys);
}

Меню OC

В данной вкладке происходит перемещение по меню: команды, калькулятор, игровое меню, файловое меню и свободная печать. Перемещение происходит при нажатии клавиш 1 - 5.

Если требуется то можно легко добавить еще вкладки.

 void menuOC(){
   vga.setCursor(2, 2);
   vga.println("1 - Work with files");   
   vga.println("2 - Calculator");  
   vga.println("3 - Free printing");
   vga.println("4 - View the commands");
   vga.println("5 - Games");
/*==================================================*/
  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
  if(scanval != 0x5A and scanval != 0xE0)
    {
   txt_keys = txt_keys + keymap[scanval];   
   Serial.println(keymap[scanval]);
     }  
     //системные клавиши, чтобы не было напечатано непонятных символов   
          if(scanval == 0x76){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);}          
          if(scanval == 0x5A){vga.clear(vga.RGB(0,0,0)); menu = 0;}
          if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); menu = 0;}
          if(scanval == 0x16){menu = 1;}
          if(scanval == 0x1E){menu = 2;}
          if(scanval == 0x26){menu = 3;}
          if(scanval == 0x25){menu = 4;}
          if(scanval == 0x2E){menu = 5;} 
        //если адрес == "" то переменная меню равна ""
  }
  lastscan = scanval;
/*========================================================*/

   
   if(menu == 0){ txt_keys = ""; }
   //если пересенная меню равна "" то открываем ""
   if(menu == 1){ txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); file_mode();}
   if(menu == 2){ txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); kalculator();}
   if(menu == 3){ txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); freereadKeyboard(); }
   if(menu == 4)
   {
    txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); 
    vga.print("instructions for use can be found in the README file");  //инструкция в файле README
    delay(5000); vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); 
   }                                                                                                                                                                                                                                                                                                                                                                                                                         
   if(menu == 5){ txt_keys = "";vga.clear(vga.RGB(0,0,0));  game_menu();}
   
   Serial.print("menu = ");Serial.println(menu);
   }


   

Свободная печать

Режим очень полезен если требуется узнать адрес клавиши или проверить клавиатуру

/*==================================*/
void freereadKeyboard(){   
while(true){
  
  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
  if(scanval != 0x5A and scanval != 0xE0)
    {
   txt_keys = txt_keys + keymap[scanval];   
   Serial.println(keymap[scanval]);
   vga.print(keymap[scanval]);
     }  
     //системные клавиши, чтобы не было напечатано непонятных символов   
          if(scanval == 0x76){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);}          
          if(scanval == 0x5A){vga.println(""); }
          if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); menu = 0; return;}
      
  }
  lastscan = scanval;
 }

}

Калькулятор

Первая версия не имеет много возможностей, но простые операции выполняет. Возможна работа с дробными числами.

Работает функция сложения, умножения, вычитания, деления и сравнивания(123,22225 = 123,22 ?)

void kalculator() {  
String sing = "=";
String s1 = "";
String s2 = "";   
int s12 = 1; 
vga.println("Calculator");


 /*==================================================*/
 while(true){   

  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
  if(scanval != 0xE0){
  
  if( keymap[scanval] == '='){sing = "=";}
  if( keymap[scanval] == '+'){sing = "+";}
  if( keymap[scanval] == '-'){sing = "-";}
  if( keymap[scanval] == '*'){sing = "*";} 
  if( keymap[scanval] == '/'){sing = "/";}
  
  if( keymap[scanval] == '=' or keymap[scanval] == '+' or keymap[scanval] == '-' or keymap[scanval] == '*' or keymap[scanval] == '/' or scanval == 0x5A ){
    if(s12 == 1){ s1 = txt_keys; txt_keys = ""; s12 = 2;}
    else{s2 = txt_keys; txt_keys = ""; s12 = 1;}   
  }
  else{txt_keys = txt_keys + keymap[scanval];}

  if(scanval != 0xE0 and scanval != 0x5A){ Serial.println(keymap[scanval]); vga.print(keymap[scanval]); }
  
  }  
          //системные клавиши, чтобы не было напечатано непонятных символов   
          if(scanval == 0x76){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); vga.println("Kalculator"); s1 = ""; s2 = ""; txt_keys = "";}  
          //ответ        
          if(scanval == 0x5A){vga.println("");
            if(sing == "="){vga.println(s1.toFloat()  == s2.toFloat());}
            if(sing == "+"){vga.println(s1.toFloat() + s2.toFloat() );}
            if(sing == "-"){vga.println(s1.toFloat() - s2.toFloat() );}
            if(sing == "*"){vga.println(s1.toFloat() * s2.toFloat() );}
            if(sing == "/"){vga.println(s1.toFloat() / s2.toFloat() );}  
            s1 = ""; s2 = ""; txt_keys = "";
                  
          }
          if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); menu = 0; return;}
  }
  lastscan = scanval;
/*========================================================*/   
 Serial.print("s1 = ");Serial.println(s1);
 Serial.print("s2 = ");Serial.println(s2);
 } 
}

Работа с файлами

В этой вкладке есть свое меню действий при работе с файлами: чтение, создание/перезапись, удаление.

Чтобы прочитать файл, надо ввести имя нужного файла. Формат указывать не требуется( пример test1). Для удаления потребуется тоже ввести имя нужного файла. Но чтобы создать или изменить по мимо ввода имени файла требуется ввести содержимое.

String write_data(){
  String str = " <no data> ";
  
while(true){  
  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
  if(scanval != 0x5A and scanval != 0xE0)
    {
   txt_keys = txt_keys + keymap[scanval];   
   Serial.println(keymap[scanval]);
   vga.print(keymap[scanval]);
     }  
     //системные клавиши, чтобы не было напечатано непонятных символов   
          if(scanval == 0x76){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);  txt_keys = ""; }
                    
          if(scanval == 0x5A){
            str = txt_keys; 
            vga.setCursor(0, 0); 
            vga.clear(vga.RGB(0,0,0)); 
            txt_keys = ""; 
            return(str);
            }
          
          if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);  txt_keys = ""; }
      
  }
  lastscan = scanval;
 }
}


String filename(){
  String str;
while(true){  
  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
  if(scanval != 0x5A and scanval != 0xE0)
    {
   txt_keys = txt_keys + keymap[scanval];   
   Serial.println(keymap[scanval]);
   vga.print(keymap[scanval]);
     }  
     //системные клавиши, чтобы не было напечатано непонятных символов   
          if(scanval == 0x76){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);  txt_keys = ""; str = "";}
                    
          if(scanval == 0x5A){
            str = "/" + txt_keys + ".txt"; 
            vga.setCursor(0, 0); 
            vga.clear(vga.RGB(0,0,0)); 
            txt_keys = "";  
            return(str);
            }
          
          if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);  txt_keys = ""; str = "";}
      
  }
  lastscan = scanval;
 }
  
}
/*==================== mode =========================*/
void file_mode(){
  int filemode = 0;
  
while(true){    
   vga.setCursor(2, 2);
   vga.println("1 - Read");     
   vga.println("2 - Create new / owerwrite");
   vga.println("3 - Delete");


  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
  if(scanval != 0x5A and scanval != 0xE0){ txt_keys = txt_keys + keymap[scanval]; Serial.println(keymap[scanval]);}  
          //системные клавиши  
     
          if(scanval == 0x76){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);}          
          if(scanval == 0x5A){vga.clear(vga.RGB(0,0,0)); filemode = 0;}
          if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); menu = 0; return;}
          if(scanval == 0x16){filemode = 1;}
          if(scanval == 0x1E){filemode = 2;}
          if(scanval == 0x26){filemode = 3;}
          if(scanval == 0x25){filemode = 4;}
          if(scanval == 0x2E){filemode = 5;}
  }
  lastscan = scanval;

   
   if(filemode == 1){ txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); file_read(); }
   if(filemode == 2){ txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); file_create();}
   if(filemode == 3){ txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); file_del(); }
   
   
   Serial.print("filemode = ");Serial.println(filemode);
}
   }



/*===================== read files ==========================*/ 
void file_read() // Читаем содержимое файла:
{
int del = 20;  
char inByte  = 0;
String n = filename(); 

  myFile = SPIFFS.open(n, FILE_READ);  
  
  vga.print("File size:");  
  vga.println(myFile.size()); delay(1000); vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); //size 
  if(myFile.size() == 0){vga.print("an error is likely, press end"); }
       
  while(myFile.available()) {   
   inByte = (char)myFile.read();  
   vga.print((char)inByte); // ascii converter
   delay(del);
  }
  
 delay(500);
 while(true){

  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
  {     
     if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); menu = 0; return;}
  }
  lastscan = scanval;

 delay(100);
 }

  myFile.close();
  vga.clear(vga.RGB(0,0,0));
  vga.setCursor(0, 0);
}
/*================= delete =====================================*/
void file_del()
{
  String n = filename();     
  if(SPIFFS.remove(n)){
    Serial.println("File successfully deleted");
    vga.println("File successfully deleted");          
  }
  else{
    Serial.print("Deleting file failed!");
    vga.print("Deleting file failed!");
             //  "Не удалось удалить файл!"
  }
  delay(2000);  
 
  vga.clear(vga.RGB(0,0,0));
  vga.setCursor(0, 0);
}

/*================= create =====================================*/
void file_create()
{
  String n = filename();    
  String dataf = write_data();
  
  myFile = SPIFFS.open(n, FILE_WRITE);
  
  if (myFile.print(dataf))
  {               
    Serial.println("Message successfully written");  
    vga.print("Message successfully written");           
  }
  else{Serial.print("Writing message failed!!"); vga.print("Writing message failed!!");}
  delay(2500);
  myFile.close();
  
  vga.clear(vga.RGB(0,0,0));
  vga.setCursor(0, 0);
}
 

Игры

Самое сложное в данном компьютере, но при этом очень интересное. Переписать или создать игру это довольно долгое занятие, но оно развивает творчество. Как свою первую игру на это устройство я переделал змейку.

В начале данной вкладки добавляется игра - #include "games/SnakeGame.h"

В меню выбора нужно добавить название новой игры, которая находится по адресу Documents\Arduino\ESPspectrum\ESPspectrum_V1\games. Туда копируется игра.

#include "games/SnakeGame.h"
//#include "games/'название игры'.h"
void game_menu()
{
  int gamemode = 0;
  
while(true){    
   vga.setCursor(2, 2);
   vga.println("1 - Snake");     
   vga.println("2 - Add");
   //печать названия и номера игр


  int scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
  if(scanval != 0x5A and scanval != 0xE0){ txt_keys = txt_keys + keymap[scanval]; Serial.println(keymap[scanval]);}  
          //системные клавиши  
     
          if(scanval == 0x76){vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0);}          
          if(scanval == 0x5A){vga.clear(vga.RGB(0,0,0)); gamemode = 0;}
          if(scanval == 0xE0){vga.clear(vga.RGB(0,0,0)); menu = 0; return;}
          if(scanval == 0x16){gamemode = 1;}
          if(scanval == 0x1E){gamemode = 2;}
          if(scanval == 0x26){gamemode = 3;}
          if(scanval == 0x25){gamemode = 4;}
          if(scanval == 0x2E){gamemode = 5;}
  }
  lastscan = scanval;

   //если переменная = "" то открываем игру ""
   if(gamemode == 1){ txt_keys = ""; vga.clear(vga.RGB(0,0,0)); vga.setCursor(0, 0); snake_game(); }
   
    
   Serial.print("gamemode = ");Serial.println(gamemode);
}
   }

USB

Я нашел вариант с подключением USB а не PC/2 клавиатуры. Код выглядит так

#define CLOCK 16 //D-
#define DATA 17  //D+


const char keymap[] = {
  0, 0,  0,  0,  0,  0,  0,  0,
  0, 0,  0,  0,  0,  0, '`', 0,
  0, 0 , 0 , 0,  0, 'q','1', 0,
  0, 0, 'z','s','a','w','2', 0,
  0,'c','x','d','e','4','3', 0,
  0,' ','v','f','t','r','5', 0,
  0,'n','b','h','g','y','6', 0,
  0, 0, 'm','j','u','7','8', 0,
  0,',','k','i','o','0','9', 0,
  0,'.','/','l',';','p','-', 0,
  0, 0,'\'', 0,'[', '=', 0, 0,
  0, 0,13, ']', 0, '\\', 0, 0,
  0, 0, 0, 0, 0, 0, 127, 0,
  0,'1', 0,'4','7', 0, 0, 0,
  '0','.','2','5','6','8', 0, 0,
  0,'+','3','-','*','9', 0, 0,
  0, 0, 0, 0 };



void setup()
{
  Serial.begin(115200);
  pinMode(CLOCK, INPUT_PULLUP); //пины клавиатуры
  pinMode(DATA, INPUT_PULLUP);
 }

uint8_t lastscan;
uint8_t line = 0, col = 0;


void loop()
{
   uint16_t scanval = 0;
  for(int i = 0; i<11; i++)
  {
    while(digitalRead(CLOCK));
    scanval |= digitalRead(DATA) << i;
    while(!digitalRead(CLOCK));
  }
  scanval >>= 1;
  scanval &= 0xFF;
  Serial.println(scanval, HEX);
  if(lastscan != 0xF0 && scanval != 0xF0)
 {
   Serial.println(keymap[scanval]);
      
  }
  lastscan = scanval;
  
}

Самым лучшим вариантом увидеть как это работает - это собрать самому или связаться со мной на сервере дискорд. А в данной статье я решил показать код программы.

Архив с прошивкой и схемой: https://disk.yandex.ru/d/22eXr-gerhFaXA

Почему esp32 https://www.alexcube.ru/blog/2021/11/53-dnya-do-2022-goda-zx-spectrum-iz-arduino/


Вопросы, помощь проекту: https://discord.gg/nd6K7sbR

Теги:
Хабы:
Всего голосов 8: ↑7 и ↓1+9
Комментарии64

Публикации

Истории

Работа

QT разработчик
12 вакансий
Программист C++
109 вакансий

Ближайшие события

Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область