به نام خدا!

سلام عرض میکنم خدمت شما هموطنان و دوستان عزیزم!

امیدوارم که حالتون خوب باشه و سر خوش و سرحال باشید! 

این روز ها حال و هوای انتخابات هست و همه به فکر رئیس جمهور آینده هستن!

توصیه مکنم حتما برید به یه نفر که فکر میکنید شایسته هست و موفق خواهد بود رای بدید!

بریم سراغ اصل مطلب:

خوب دیروز یاد گرفتم که چطور میشه FPGA رو به مانیتور وصل کرد! خیلی ذوق زده شده بودم از اینکه تونستم همچین صحنه ای رو ببینم! امروز رفتم بیشتر یاد گرفتم تا بتونم چیزیایی رو که یاد گرفتم در اختیار شما بزارم و انشالله که شما هم یاد بگیرید!

خوب همونطور که میدونید معمولا صفحه مانیتور در ثانیه 60 بار رفرش میشه! شنیدید میگن فلان دوربین گوشی 24 فریم در ثانیه است؟ یعنی 24 تا عکس تو هر ثانیه ضبط میکنه اینم یه چیزی توی همخون مایه هاست! یعنی توی هر ثانیه 60 بار تصویر رفرش میشه! یا به عبارت دیگه ای 60 Hz هست!

خوب من نمیخوام اینجا بگم که VGA چطوری کار میکنه فقط میخوام نحوه برنامه نویسی اش رو مرور کنیم:

همانطور که میدونید مانیتوز از سمت چپ (ما) شروع میکنه و به سمت راست میره تا تصویر رو رسم کنه و همینطور دوباره بر میگرده و یه سطر میره پایین و دوباره از چپ به راست...

و همونطور که احتمالا میدونید وقتی اون مانیتور های قدیمی CRT که از پرتو کاتدی استفاده میکردند میخواستن یه تصویر رو نشون بدن یه چیز متحرکی بود که الکترون ها رو به سمت صفحه پرت میکرد! یعنی کلا یه چیزی توش بود که جابه جا میشد! حالا بنابراین وقتی جابه جا میشه یه وقتی میگره که باید اون رو محاسبه کنیم (کار اصلا سختی نیست) فقط بدونید بهش میگن FrontPorch و BackPorch اگه جایی دید منظور تعداد سیکلی هست که نیاز هست بگذره تا دوباره مانیتور برای رسم مجدد آماده بشه! (البته همه ی اینها 60 بار در ثانیه رخ میده)

شاید عکس زیر بتونه کمکتون بکنه!

خوب هر چهار سیگنال بالا رو که میبینید باید در برنامه اعمال بشن! (اصلا نترسید خیلی آسونه الان خواهید دید...)

در تصویر بالا از سمت چپ اولی قهوه ای: کلا هرچیزی که توی مانیتور نشون داده میشه در این زمان انجام میشه!

Sync Pulse هم که نشون میده یه فریم کامل نشون داده شده و یه سیگنال تولید میشه به نشونه اتمام نمایش یک فریم! حالا دوباره صبر میکنیم تا مامیتور مجددا آماده نمایش بشه! یعنی همون Back Porch 

ما فقط باید در مدن زمان Active video نمایش بدیم و در بقیه موارد چیزی نشون داده نمیشه!

جدول مربوط به چیزی که گفتم در رزلوشن 640 * 480 رو میتونید اینجا ببینید!

میخوام یه خورده در مورد اعداد و ارقام جدول توضیح بدم! پس جدول رو توی مطلب خودم کپی میکنم!

Screen refresh rate 60 Hz
Vertical refresh 31.46875 kHz
Pixel freq. 25.175 MHz


خوب Screen refresh rate رو که توضیح دادم یعنی 60 بار در ثانیه مانیتور رفرش میشه و عکس مارو نشون میده!

حالا میخوام بفهمیم Vertical refresh از کجا اومده و Pixel freq چطور بدست میاد؟

ببینید اگه به دو جدول زیر نگاه کنید!


Horizontal timing (line)

Polarity of horizontal sync pulse is negative.

Scanline part Pixels Time [µs]
Visible area 640 25.422045680238
Front porch 16 0.63555114200596
Sync pulse 96 3.8133068520357
Back porch 48 1.9066534260179
Whole line 800 31.777557100298

Vertical timing (frame)

Polarity of vertical sync pulse is negative.

Frame part Lines Time [ms]
Visible area 480 15.253227408143
Front porch 10 0.31777557100298
Sync pulse 2 0.063555114200596
Back porch 33 1.0486593843098
Whole frame 525 16.683217477656


خوب اگه به جدول بالایی نگاه کنید نوشته 640 پیکسل افقی داریم! یعنی 800 یعنی 800 سیکل ولی با احتساب Back porch و Sync pulse و Front porch جمعا میشه 800 سیکل!

و همچنین در جدول پایینیش 480 خط داریم یا 480 سیکل داریم ولی با احتساب اونا میشه 525 سیکل!

تا اینجا که مشکلی نیست؟!

حالا میخوایم ببینیم برای نشون دادن یه تصویر 640 * 480 چند سیکل نیاز داریم؟!

خوب میام تعداد کل سیکل ها رو در هم ضرب میکنیم! که میشه 800*525 مساوی میشه با 420000 خوب پس ما 420000 سیکل برای نشون دادن یک فریم نیاز داریم! حالا برای نشون دادن 60 فریم جند سیکل نیاز داریم: آفرین -> معلومه 420000 * 60 که میشه 25200000 حالا اگه برید توی جدول اول نگاه کنید میبینید نوشته Pixel freq = 25.175 MHz که منظورش همونه که ما به دست آوردیم!

حالا میخوام ببینیم Vertical refresh چی هست؟! Vertical refresh در واقع از تقسیم فرکانس پیکسل یا همون 25 مگاهرتز به 800 بدست میاد!

بقیه اعداد هم خیلی واضح هستن! مثلا اگه 640 رو به 25.175 مگاهرتز تقسیم کنید ثانیه اش بدست میاد!

640/25175000=2.54220457e-5

خوب هنوز به برنامه نویسی نرسیدیم!

نکته: چون کلاک برد ما 50 مگاهرتز هست ما باید کلاک رو نصف کنیم تا به 25 مگاهرتز برسیم!

خوب سیگنال ها و ثوابت رو به صورت زیر تعریف میکنیم!


  constant HDisplayArea: integer:= 640; -- horizontal display area
  constant HLimit: integer:= 800; -- maximum horizontal amount (limit)
  constant HFrontPorch: integer:= 16; -- h. front porch
  constant HBackPorch: integer:= 48; -- h. back porch
  constant HSyncWidth: integer:= 96; -- h. pulse width
  
  constant VDisplayArea: integer:= 480; -- vertical display area
  constant VLimit: integer:= 525; -- maximum vertical amount (limit)
  constant VFrontPorch: integer:= 10; -- v. front porch
  constant VBackPorch: integer:= 33; -- v. back porch
  constant VSyncWidth: integer:= 2; -- v. pulse width      
  
  signal Clk_25MHz: std_logic := '0';  
  signal HBlank, VBlank, Blank: std_logic := '0';
    
  signal CurrentHPos: std_logic_vector(10 downto 0) := (others => '0'); -- goes to 1100100000 = 800
  signal CurrentVPos: std_logic_vector(10 downto 0) := (others => '0'); -- goes to 1000001101 = 525
  signal ScanlineX, ScanlineY: std_logic_vector(10 downto 0);
  
  signal ColorOutput: std_logic_vector(7 downto 0);


خوب تا اینجا که چیز خاصی نداشت!

حالا میخوایم یه تقسیم کننده فرکانس بسازیم که از 50 مگاهرتز به 25 مگاهرتز برسیم!

  Generate25MHz: process (CLK_50MHz)
  begin
    if rising_edge(CLK_50MHz) then
  Clk_25MHz <= not Clk_25MHz;
end if;
  end process Generate25MHz;

چون در هر بار اجرای این فرآیند سیگنال not میشه پس با هر دو بار اجرا کردن فرآیند یه سیکل از سیگنال ساخته میشه! یعنی برای ساخت کلاک 25 مگاهرتز باید 50 ملیون بار(مگاهرتز) سیگنال مورد نظر رو not کنیم! که در اینجا نیز همین اتفاق میافته!


حالا میخوام با کلاک 25 مگاهرتز کار کنیم»

الان باید مقدار Horizontal رو بشماریم تا به 800 برسه! وقتی به 800 رسید یعنی یک خط رو چاپ کرده و هنوز 479 خط دیگه مونده پس بعد از اینکه شمارنده ی Horizontal به 800 رسید باید 1 واحد به Vertical اضافه کنیم تا Vertical به 480 برسه! اونوقت هر دو رو صفر میکنیم! یعنی یک فریم به طور کامل نمایش داده شد! و دوباره از اول شروع میکنیم!

اینم کدش:

  VGAPosition: process (Clk_25MHz)
  begin
    if rising_edge(Clk_25MHz) then
  if CurrentHPos < HLimit-1 then -- if Horizontal Counter < 800-1
 CurrentHPos <= CurrentHPos + 1; -- add 1 to Horizontal Counter
else -- Horizontal Counter = 800-1
 if CurrentVPos < VLimit-1 then -- if Horizontal Counter = 800-1 and Vertical Counter < 525-1
   CurrentVPos <= CurrentVPos + 1;-- add 1 to Vertical
 else -- if Horizontal Counter = 800-1 and Vertical Counter = 525-1
   CurrentVPos <= (others => '0');-- reset Vertical Counter
 end if;
 CurrentHPos <= (others => '0'); -- if Horizontal Counter = 800-1 then reset Horizontal Counter
end if;
end if;
  end process VGAPosition;


حالا باید به سیگنال HS مقدار بدیم تا بتونیم به VGA کار کنیم! پس باید بگیم تا زمانی که شمارنده Horizontal به پهنای پالس (یعنی 96) نرسیده اونو 0 قرار بده در غیر اینصورت 1:

    HS <= '0' when CurrentHPos < HSyncWidth else
       '1';

و همینطور برای شمارنده ی Vertical داریم:

	  VS <= '0' when CurrentVPos < VSyncWidth else
       '1';

حالا باید اون زمانی رو بدست بیاریم که نمایشگر چیزی نشون نمیده (همون زمان هایی که طول میکشه دستگاه اشعه پرتو کاتدی برگرده به جای قبلی تا برای نمایش شروع بشه) یعنی دقیقا همون لحظه ای که توی تصویر بالا نوشته بود Active video که درواقع بین HSyncWidth + HFrontPorch و Back Porch هست یعنی یه چیزی مثل

HCounter      >= 96 + 16 & HCounter      < 96 + 16  +  640

که در فرمول بالا سمت راست زمانی رو نشون میده که در واقع کل پالس (یعنی 800) منهای Back Porch هست! یعنی 800 منهای 48 که میشه 752 و واگه فرمول بالا رو هم باهم جمع کنید میشه 752 و فرمول سمت چپ زمانی رو نشون میده که هنوز سیگنال ویدئو آماده نشده!

HBlank <= '0' when (CurrentHPos >= HSyncWidth + HFrontPorch) and (CurrentHPos < HSyncWidth + HFrontPorch + HDisplayArea) else
          '1';


و برای Vertical هم داریم!

VBlank <= '0' when (CurrentVPos >= VSyncWidth + VFrontPorch) and (CurrentVPos < VSyncWidth + VFrontPorch + VDisplayArea) else
          '1';

که در مجموع میشه:

	  Blank <= '1' when HBlank = '1' or VBlank = '1' else
          '0';

دیگه تموم شد! حالا برای بدست آوردن مقدار x و y میتونیم مقدار شمارنده Horizontal رو (که حداکثر 800 هست) رو منهای HSyncWidth و HFrontPorch کنیم تا مقدار x خالص بدست باید (یعنی مقدار Horizontal برای Active video) و البته باید بگیم که وقتی که Blank = 1 هست اصلا مقدار X برابر صفر هست!

همچنین برای VSyncWidthیا همون y ها داریم! که باید مقدار شمارنده ی VSyncWidth رو منهای VSyncWidth و VFrontPorch کنیم تا مقدار خالص y بدست بیاد!

	  ScanlineY <= CurrentVPos - VSyncWidth - VFrontPorch when Blank = '0' else
              (others => '0');

حالا میتونیم صفحه رو به سه قسمت سبز و آبی و قرمز تبدیل کنیم!

  ColorOutput <= "11100000" when ScanlineX < 213 else
                 "00011100" when ScanlineX >= 213 AND ScanlineX < 213+213 else
                 "00000011" when ScanlineX >= 213+213 AND ScanlineX < 213+213+213 else
                 "00000000";

سه بیت پر ارزش نشون دهنده ی قرمز و سه بیت وسط برای سبز و دو بیت کم ارزش برای آبی هست!

خودم فقط از یک بیت استفاده کردم تا همه رو نشون بدم!

شما هم میتونید از بیت 7 برای قرمز، و از بیت 2 برای سبز و از بیت 0 برای آبی استفاده کنید! لازم به تغییرات نیست!

حالا باید رنگ ها رو به پایه ها اعمال کنید:

     RED <= ColorOutput(7 downto 5) when Blank = '0' else
            "000";  
     GREEN <= ColorOutput(4 downto 2) when Blank = '0' else
            "000";
     BLUE <= ColorOutput(1 downto 0) when Blank = '0' else
            "00";

حالا اگه FPGA خودتون رو به مانیتور وصل کنید باید صفحه معروف سه رنگ رو مشاهده کنید!

کد بالا رو به صورت کامل اینجا میزارم:


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity MyVGA is
  port ( CLK_50MHz: in std_logic;
         VS: out std_logic;
HS: out std_logic;
RED: out std_logic_vector(2 downto 0);
GREEN: out std_logic_vector(2 downto 0);
BLUE: out std_logic_vector(2 downto 1)
  );
end MyVGA;
architecture Behavioral of MyVGA is
  -- VGA Definitions starts
  constant HDisplayArea: integer:= 640; -- horizontal display area
  constant HLimit: integer:= 800; -- maximum horizontal amount (limit)
  constant HFrontPorch: integer:= 16; -- h. front porch
  constant HBackPorch: integer:= 48; -- h. back porch
  constant HSyncWidth: integer:= 96; -- h. pulse width
  
  constant VDisplayArea: integer:= 480; -- vertical display area
  constant VLimit: integer:= 525; -- maximum vertical amount (limit)
  constant VFrontPorch: integer:= 10; -- v. front porch
  constant VBackPorch: integer:= 33; -- v. back porch
  constant VSyncWidth: integer:= 2; -- v. pulse width      
  
  signal Clk_25MHz: std_logic := '0';  
  signal HBlank, VBlank, Blank: std_logic := '0';
    
  signal CurrentHPos: std_logic_vector(10 downto 0) := (others => '0'); -- goes to 1100100000 = 800
  signal CurrentVPos: std_logic_vector(10 downto 0) := (others => '0'); -- goes to 1000001101 = 525
  signal ScanlineX, ScanlineY: std_logic_vector(10 downto 0);
  
  signal ColorOutput: std_logic_vector(7 downto 0);
  -- VGA Definitions end
  --- information
-- Visible area = (1/25175000)*640 = 25.422045680238
-- Visible area = (1/25175000)*800*480 = 15.253227408143
-- Vertical refresh = 25.175 MHz / 800 (Whole line) = 31.46875 KHz
begin
  Generate25MHz: process (CLK_50MHz)
  begin
    if rising_edge(CLK_50MHz) then
  Clk_25MHz <= not Clk_25MHz;
end if;
  end process Generate25MHz;
   
  VGAPosition: process (Clk_25MHz)
  begin
    if rising_edge(Clk_25MHz) then
  if CurrentHPos < HLimit-1 then
 CurrentHPos <= CurrentHPos + 1;
else
 if CurrentVPos < VLimit-1 then
   CurrentVPos <= CurrentVPos + 1;
 else
   CurrentVPos <= (others => '0'); -- reset Vertical Position
 end if;
 CurrentHPos <= (others => '0'); -- reset Horizontal Position
end if;
end if;
  end process VGAPosition;
--  840000/800/2
  -- Timing definition for HSync, VSync and Blank (http://tinyvga.com/vga-timing/640x480@60Hz)
     HS <= '0' when CurrentHPos < HSyncWidth else -- 800 x < 96 = 0
       '1';
 
 VS <= '0' when CurrentVPos < VSyncWidth else -- 525 x < 2 = 0
       '1';
 
 HBlank <= '0' when (CurrentHPos >= HSyncWidth + HFrontPorch) and (CurrentHPos < HSyncWidth + HFrontPorch + HDisplayArea) else
          '1';
 
 VBlank <= '0' when (CurrentVPos >= VSyncWidth + VFrontPorch) and (CurrentVPos < VSyncWidth + VFrontPorch + VDisplayArea) else
          '1';  
 
 Blank <= '1' when HBlank = '1' or VBlank = '1' else
          '0';
 
 ScanlineX <= CurrentHPos - HSyncWidth - HFrontPorch when Blank = '0' else
              (others => '0');
 ScanlineY <= CurrentVPos - VSyncWidth - VFrontPorch when Blank = '0' else
              (others => '0');
     RED <= ColorOutput(7 downto 5) when Blank = '0' else
            "000";  
     GREEN <= ColorOutput(4 downto 2) when Blank = '0' else
            "000";
     BLUE <= ColorOutput(1 downto 0) when Blank = '0' else
            "00";
 

  
 
  ColorOutput <= "11100000" when ScanlineX < 213 else
                 "00011100" when ScanlineX >= 213 AND ScanlineX < 213+213 else
                 "00000011" when ScanlineX >= 213+213 AND ScanlineX < 213+213+213 else
                 "00000000";

end Behavioral;


کد بالا رو از این سایت کپی کردم! برید و پروژه های بهتر رو میتونید اونجا دانلود کنید!

این یه کد که یه مربع رو نشون میده که جا به جا میشه و رنگش تغییر میکنه!

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity MyVGA is
  port ( CLK_50MHz: in std_logic;
         VS: out std_logic;
HS: out std_logic;
RED: out std_logic_vector(2 downto 0);
GREEN: out std_logic_vector(2 downto 0);
BLUE: out std_logic_vector(2 downto 1)
  );
end MyVGA;
architecture Behavioral of MyVGA is
  -- VGA Definitions starts
  constant HDisplayArea: integer:= 640; -- horizontal display area
  constant HLimit: integer:= 800; -- maximum horizontal amount (limit)
  constant HFrontPorch: integer:= 16; -- h. front porch
  constant HBackPorch: integer:= 48; -- h. back porch
  constant HSyncWidth: integer:= 96; -- h. pulse width
  
  constant VDisplayArea: integer:= 480; -- vertical display area
  constant VLimit: integer:= 525; -- maximum vertical amount (limit)
  constant VFrontPorch: integer:= 10; -- v. front porch
  constant VBackPorch: integer:= 33; -- v. back porch
  constant VSyncWidth: integer:= 2; -- v. pulse width  
    
  signal Clk_25MHz: std_logic := '0';  
  signal HBlank, VBlank, Blank: std_logic := '0';
    
  signal CurrentHPos: std_logic_vector(10 downto 0) := (others => '0'); -- goes to 1100100000 = 800
  signal CurrentVPos: std_logic_vector(10 downto 0) := (others => '0'); -- goes to 1000001101 = 525
  signal ScanlineX, ScanlineY: std_logic_vector(10 downto 0);
  
  signal ColorOutput: std_logic_vector(7 downto 0);
  -- VGA Definitions end
  
    signal CounterX: std_logic_vector(9 downto 0) := (others => '0'); -- goes to 1100100000 = 680
    signal CounterY: std_logic_vector(8 downto 0) := (others => '0'); -- goes to 1100100000 = 512
 
    signal CounterPulse: std_logic_vector(16 downto 0) := (others => '0'); -- goes to 1100100000 = 680
    signal SetColor: std_logic_vector(1 downto 0) := (others => '0'); -- goes to 1100100000 = 680
signal SelectX,SelectY : std_logic;

 
begin
  Generate25MHz: process (CLK_50MHz)
  begin
    if rising_edge(CLK_50MHz) then
  Clk_25MHz <= not Clk_25MHz; end if;
  end process Generate25MHz;
   
  VGAPosition: process (Clk_25MHz)
  begin
    if rising_edge(Clk_25MHz) then
  if CurrentHPos < HLimit-1 then
 CurrentHPos <= CurrentHPos + 1;
else
 if CurrentVPos < VLimit-1 then
   CurrentVPos <= CurrentVPos + 1;
 else
   CurrentVPos <= (others => '0'); -- reset Vertical Position
 end if;
 CurrentHPos <= (others => '0'); -- reset Horizontal Position
end if;
end if;
  end process VGAPosition;
  -- Timing definition for HSync, VSync and Blank (http://tinyvga.com/vga-timing/640x480@60Hz)
     HS <= '0' when CurrentHPos < HSyncWidth else
       '1';
 
 VS <= '0' when CurrentVPos < VSyncWidth else
       '1';
 
 HBlank <= '0' when (CurrentHPos >= HSyncWidth + HFrontPorch) and (CurrentHPos < HSyncWidth + HFrontPorch + HDisplayArea) else
          '1';
 
 VBlank <= '0' when (CurrentVPos >= VSyncWidth + VFrontPorch) and (CurrentVPos < VSyncWidth + VFrontPorch + VDisplayArea) else
          '1';  
 
 Blank <= '1' when HBlank = '1' or VBlank = '1' else
          '0';
 
 ScanlineX <= CurrentHPos - HSyncWidth - HFrontPorch when Blank = '0' else
              (others => '0');
 ScanlineY <= CurrentVPos - VSyncWidth - VFrontPorch when Blank = '0' else
              (others => '0');
     RED <= ColorOutput(7 downto 5) when Blank = '0' else
            "000";  
     GREEN <= ColorOutput(4 downto 2) when Blank = '0' else
            "000";
     BLUE <= ColorOutput(1 downto 0) when Blank = '0' else
            "00";
 

  
 
  ColorOutput <= "11100011" when SetColor = "00" AND ScanlineX > CounterX AND  ScanlineX<CounterX+20 AND ScanlineY > CounterY AND  ScanlineY < CounterY+20 else
 "11100000" when SetColor = "01" AND ScanlineX > CounterX AND  ScanlineX<CounterX+20 AND ScanlineY > CounterY AND  ScanlineY < CounterY+20 else
 "00011100" when SetColor = "10" AND ScanlineX > CounterX AND  ScanlineX<CounterX+20 AND ScanlineY > CounterY AND  ScanlineY < CounterY+20 else
 "00000011" when SetColor = "11" AND ScanlineX > CounterX AND  ScanlineX<CounterX+20 AND ScanlineY > CounterY AND  ScanlineY < CounterY+20 else
                 "11111111";


 process (CLK_50MHz)
  begin
    if rising_edge(CLK_50MHz) then
 
CounterPulse <= CounterPulse +1;
if (CounterPulse = 0) then

if (SelectX='0') then
  CounterX <= CounterX +1;
if (CounterX >= 640 - 20) then SelectX <= '1'; SetColor <= SetColor + 1; end if;
else
  CounterX <= CounterX -1;
if (CounterX <= 0 + 20) then SelectX <= '0'; SetColor <= SetColor + 1; end if;
end if;

if (SelectY='0') then
CounterY <= CounterY + 1;
if (CounterY >= 480 - 20) then SelectY <= '1'; SetColor <= SetColor + 1; end if;
else
CounterY <= CounterY - 1;
if (CounterY <= 0 + 20) then SelectY <= '0'; SetColor <= SetColor + 1; end if;
end if;




end if;
end if;
  end process;
  
  
end Behavioral;


امیدوارم مطلب مفیدی بوده باشه!

موفق باشید

یا علی مدد...!


نمایش کاراکتر روی مانیتور با VGA

خوب یه چند وقتی بود این پروژه رو ساخته بودم و اومدم بزارم که برق قطع شد و هرچی نوشته بودم پاک شد! 

ولی امروز اومدم دوباره بزارم انشالله که به دردتون بخوره! 

این پروژه 96 کاراکتر (از 32 اسکی تا 128 اسکی) رو میتونه نشون بده! میتونید کاراکتر و مکان رو مشخص کنید هر کاراکتر 8 در 16 هست پس توی صفحه 80 کاراکتر در طول و 16 کاراکتر در عرض میتونید بنویسید!

نکته دیگه اینه که فونت کاراکتر ها توی یه فایلی و توی رام داخل FPGA ذخیره میشه! (فایل فونت هم توی سورس هست و اگه خواستید عوضش کنید باید از نرم افزار BitFontCreator Pro 3.5 استفاده کنید

همچنین برای نمایش کاراکتر ها یه رم در نظر گرفتم که کاراکتر ها توی اون رم ذخیره میشه و از اون خونده میشه و روی صفحه نشون داده میشه! و چون مقدار رم  خیلی زیاد نبود من عرض رو نصف کردم و الان اگه برید ببینید میبینید صفحه نصف شده و هرچی مینویسه یک خط بالا مینویسه و یک خط پایین مینویسه! (مشکلی نداره و درسته)

سوالی بود بپرسید تا جایی که بلد باشم در خدمتتون هستم!

دریافت
حجم: 8.04 مگابایت