1.1.Khởi tạo,đọc ADC
Thông thường chúng ta sử dụng GPIO cho chức năng nút bấm,tuy nhiên trong project này chúng ta sử dụng 1 ADC để nhận nhiều nút bấm thông qua phân áp khác nhau chu từng nút.
Trước hết cần download bộ thư viện ADC.h tại đây .Các bạn nên đọc lại Tutorial ADC10 của MSP430.Bắt đầu là khởi tạo Module ADC10.Chúng ta sẽ dùng điện áp tham chiếu VCC, nên khai báo hàm ADC10_Init(VCC); .Vì trong mạch không có điện trở treo trên chân P1.4 và P1.5 nên cần cài đặt điện trở nội kéo lên trên các chân này.
Viết tiếp vào hàm Initial() để khởi tạo module ADC10.
//Khoi tao ADC ADC10_Init(VCC); // Chinh dien tro keo len tren chan P1.4 P1.5 (Nut bam) P1DIR &=~(BIT3+BIT4+BIT5); //Chon nhap du lieu P1OUT |= (BIT4+BIT5); P1REN |= (BIT4+BIT5); //Cho dien tro keo len P1OUT &= ~BIT3; //Doc ADC cua quang tro khong can dien tro keo le P1REN &= ~BIT3;Có thể sửa lại hàm main như sau để xem thông số các ADC khi bấm nút.Đọc 2 ADC 4 và 5 tại chân P1.4 và P1.5 .
void main(void) { unsigned int abc=0; Initial(); while(1) { Hien_thi_gio(); abc = ADC10_Read_Channel(4); //Khoi tao ngat ngoai #ifdef USE_UART UART_Write_Char(10); //Ky tu xuong dong sprintf(buff,"ADC4:%d",abc); UART_Write_String(buff); #endif abc = ADC10_Read_Channel(5); //Khoi tao ngat ngoai #ifdef USE_UART sprintf(buff," ADC5:%d",abc); UART_Write_String(buff); UART_Write_Char(10); //Ky tu xuong dong #endif _delay_cycles(500000); } }
Kết quả hiển thị trên hercules dạng như sau. Chú ý là vì dùng ADC nên nếu muốn nhận nhiều nút cùng lúc thì bạn phải viết thêm các phép if … else để bắt các giá trị ADC trung gian khi 2 nút được nhấn.
ADC4:1020 ADC5:519 //Nhấn nút trên cùng ADC4:1019 ADC5:0 //Nhấn nút thứ 2 ADC4:520 ADC5:1021 //Nhấn nút thứ 3 ADC4:2 ADC5:1021 //Nhấn nút thứ 4 ADC4:1021 ADC5:1022 //Không có nút nào được nhấn
1.2. Đọc bàn phím
Từ dữ liệu thu được như trên chúng ta sẽ viết hàm nhận phím bấm bằng cách đọc ADC.Các bạn nên tạo một thư viện Switch.h riêng trong thư mục Library.Trong thư viện Switch chúng ta khai báo biến toàn cục và 2 macro sbi và cbi.
#define sbi(REG,BIT) REG |= (1<<BIT) // set BIT trong thanh ghi REG #define cbi(REG,BIT) REG &= ~(1<<BIT)// clear BIT trong thanh ghi REG unsigned char press=0;//trang thai phim bam unsigned char press_quakhu=0; unsigned int SW1=0,SW2=0;
Viết hàm SWITCH_CheckKey() .Đây là hàm kiểm tra tại thời điểm gọi hàm có nút nào đang được nhấn không.Đầu tiên là đọc giá trị ADC trên 2 chân P1.4 và P1.5 .Khi không có nút nhấn thì giá trị các ADC trong khoảng lớn hơn 1000.Khi nhấn nút trên cùng,giá trị SW1 giảm xuống trong khoảng 510 ->530 ( Chúng ta đo được khoảng 520).Nếu SW1 trong khoảng 510->530 thì là nút 1 được nhấn,kéo bit0 của press lên 1,nếu không thì giữ mức 0,nếu SW1<10 thì tức là nút 2 được nhấn,kéo bit 1 của press lên 1.Tương tự vậy với các nút khác.
void SWITCH_CheckKey() { press=0; SW1=(int)ADC10_Read_Channel(5); SW2=(int)ADC10_Read_Channel(4); if(SW1<530) // neu co bat ky nut nao o PORT duoc bam { if(SW1>510){sbi(press,0);cbi(press,1);} //SW1 else if(SW1<10){sbi(press,1);cbi(press,0);} //SW2 } if(SW2<530) { if(SW2>510){sbi(press,2);cbi(press,3);} //SW3 else if(SW2<10) {sbi(press,3);cbi(press,2);} //SW4 } }
Kết quả trên Hercules:
press:0 //Không có nút nào được nhấn
press:1 //Nút 1 được nhấn
press:2 //Nút 2 được nhấn
press:4 //Nút 3 được nhấn
press:8 //Nút 4 được nhấn
Chúng ta viết tiếp hàm SWITCH_Key() . Đây là hàm nhận cạnh xuống của nút nhấn,tức là chỉ báo nút được nhấn đúng 1 lần khi nút từ trạng thái không nhấn -> nhấn . Khi nút đang được nhấn thì giá trị press vẫn bằng 0 . Hàm này dùng trong menu để tránh tình trạng dính nút . Cụ thể là biến press_quakhu sẽ lưu giá trị nút nhấn trong lần kiểm tra nút bấm trước đó.Khi chưa nhấn gì,cả press và press_quakhu đều bằng 0. Khi nhấn 1 nút,press kháo press_quakhu ,nên giá trị press được gán bằng sự khác nhau giữa press và press_quakhu & press ,tức là chỉ nút nào được nhấn hiện tại mới được kéo lên 1 trong press.press_quakhu = press nên trong thời gian nhấn sau đó press=0.Cuối cùng khi nhả nút ra,có sự khác nhau giữa press và press_quakhu,nhưng bit khác nhau của press =0 nên kết quả cuối cùng press=0.
2.Tạo menu chương trình
//Ham quet phim chi nhan tung lan 1 nhung cpu ko chay trong vong while void SWITCH_Key() { unsigned char temp; SWITCH_CheckKey(); temp=press; if(press == press_quakhu)press=0; else { press =(press^press_quakhu)&press; press_quakhu=temp; } }
2.Tạo menu chương trình
Cấu trúc menu như sau:
2.1. Tạo timer định thời gian
Bạn tưởng tượng thế này,sau khi nhấn nút 2 để hiển thị ngày,sau khi nhả ra,chúng ta muốn hiển thị thêm 2s nữa rồi hiển thị năm rồi mới thoát về hiển thị giờ.Và trong rất nhiều trường hợp khác nữa.Để giải quyết việc này,chúng ta có thể tạo timer định thời 1 khoảng thời gian (0.5s chẳng hạn).Tạo Cờ Enable_TimerA0 và biến Dem_TimerA0 .Trong ngắt TimerA0 , khi cờ Enable_TimerA0=1 thì mỗi lần ngắt Timer thì tăng biến Dem_TimerA0 lên.Khi muốn định 1 khoảng thời gian nào đó chỉ cần bật cờ Enabled_TimerA0 và chờ đến khi Dem_TimerA0 bằng thời gian định trước thì thực hiện lệnh cần làm.
- Nhấn nút 2 để xem ngày
- Nhấn nút 3 để xem nhiệt độ
- Giữ nút 1 để vào cài đặt.Trong cài đặt, chức năng các phím như sau .Phím 1- Enter ; Phím 2 – Lên ; Phím 3 – Xuống ; Phím 4 – Thoát.Trong setup sẽ chia ra các mục từ 1 đến 5.Enter ở mục nào thì vào cài đặt của mục đó.Muốn thoát thì nhấn phím 4 .
- Nếu trong menu mà trong 1 phút không có nút nhấn nào thì thoát .
2.1. Tạo timer định thời gian
Bạn tưởng tượng thế này,sau khi nhấn nút 2 để hiển thị ngày,sau khi nhả ra,chúng ta muốn hiển thị thêm 2s nữa rồi hiển thị năm rồi mới thoát về hiển thị giờ.Và trong rất nhiều trường hợp khác nữa.Để giải quyết việc này,chúng ta có thể tạo timer định thời 1 khoảng thời gian (0.5s chẳng hạn).Tạo Cờ Enable_TimerA0 và biến Dem_TimerA0 .Trong ngắt TimerA0 , khi cờ Enable_TimerA0=1 thì mỗi lần ngắt Timer thì tăng biến Dem_TimerA0 lên.Khi muốn định 1 khoảng thời gian nào đó chỉ cần bật cờ Enabled_TimerA0 và chờ đến khi Dem_TimerA0 bằng thời gian định trước thì thực hiện lệnh cần làm.
//Co su dung Timer volatile unsigned int Dem_timerA0=0; volatile unsigned char Enable_timerA0=0;
Viết tiếp hàm định thời Timer 0.5s vào hàm Initial().Luôn để lệnh cho phép ngắt ở dưới cùng của hàm khởi tạo,nhưng phải đảm bảo nằm trên của các lệnh có dùng đến ngắt để tránh gây lỗi chương trình.
// Khoi tao Ngat timer // Tao timer 0,5 s TACCR0 = 62499; TACCTL0 = CCIE; TACTL = TASSEL_2 + MC_1 + ID0 + ID1; // SMCLK/8, Up mode __bis_SR_register(GIE); //Cho phep ngat hoat dong
Khai báo ngắt TimerA0.
// Timer A0 interrupt service routine #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A0 (void) { if((Dem_timerA0<65535)&&(Enable_timerA0==1))Dem_timerA0++; }
2.2. Tạo menu
Chúng ta sẽ viết chương trình cho 2 nút bấm 2 và 3 trước vì nó đơn giản hơn.
while(1) { Hien_thi_gio(); if(counter<20)counter++; else counter=0; if(counter==0) { SWITCH_CheckKey(); if(press==0x02) { #ifdef USE_UART UART_Write_Char(10); //Ki tu xuong dong UART_Write_String("Dang nhan nut 1"); #endif } if(press==0x04) { #ifdef USE_UART UART_Write_Char(10); //Ki tu xuong dong UART_Write_String("Dang nhan nut 2"); #endif } // Neu nut 1 khong duoc nhan thi reset timer , // va khong cho timer hoat dong if(press!=0x01) { Enable_timerA0=0; // Ngung dem timer Dem_timerA0=0; // Reset bien Dem_timerA0 } // Neu nut 1 duoc nhan,cho bien Dem_timerA0 chay else if((press==0x01)&&(Enable_timerA0==0)) { Enable_timerA0=1; Dem_timerA0=0; #ifdef USE_UART UART_Write_Char(10); //Ki tu xuong dong UART_Write_String("Dang nhan enter"); #endif } // Sau 1,5s ke tu luc giu phim 1 thi nhay chuong trình vao Menu Setup if((Dem_timerA0>=3)&&(Enable_timerA0==1)) { press=0; press_quakhu=1; Dem_timerA0=0; Enable_timerA0=1; #ifdef USE_UART UART_Write_Char(10); //Ki tu xuong dong UART_Write_String("Vao Menu setup"); #endif PHUT_ON; //Menu setup while(1) { //Chuong trinh trong menu se duoc viet vao day Enable_timerA0=1; //Cho phep chuc nang thoat khi khong nhan nut nao sau 1 phut Write_74HC595_8bit(soled[setup]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); } #ifdef USE_UART UART_Write_Char(10); //Ki tu xuong dong UART_Write_String("Thoat menu"); #endif } } }
Thay hàm SWITCH_Key() bằng hàm SWITCH_CheckKey() vì bây giờ chúng ta cần tính thời gian nhấn nút của phím 1.sau này chúng ta sẽ thêm các hàm chức năng vào trong lệnh if,tạm thời bây giờ thì chỉ hiển thị kết quả qua UART.Nhấn nút 2 và 3 sẽ có thông báo qua UART.Nhấn nút 1 sẽ có thông báo ,sau 1,5s (3 lần ngắt timerA0) thì chương trình sẽ vào menu setup.Vòng while(1) dưới cùng sẽ hiển thị giá trị kênh trong menu đang được trỏ tới, ví dụ 1 là chỉnh giờ,khi nhấn enter thì sẽ nhảy vào chương trình chỉnh giờ.
Chúng ta tạo thêm 1 biến “ unsigned char setup “để giữ giá trị kênh trong menu hiện tại.Tiếp tục code trong vòng while(1).
//Menu setup while(1) { Enable_timerA0=1; //Cho phep chuc nang thoat khi khong nhan nut nao sau 1 phut Write_74HC595_8bit(soled[setup]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); //Hien thi gia tri bien setup SWITCH_Key(); if(press==2) {if(setup<5)setup++;Dem_timerA0=0;} if(press==4) {if(setup>1)setup--;Dem_timerA0=0;} if(press==8) { Enable_timerA0=0; Dem_timerA0=0; break; } if(press==1) //Nhan ENTER { if(setup==1);//Chinh_gio(); else if(setup==2);//Chinh_ngay(); else if(setup==3);//Chinh_do_tuong_phan(); else if(setup==4);//Hen_gio(); else if(setup==5);//Dong_ho_dem_nguoc(); } //Neu qua 1 phut ma khong co nut nao nhan thi thoat if(Dem_timerA0>=120) { setup=1; press=0; press_quakhu=0; Enable_timerA0=0; Dem_timerA0=0; Doc_thoi_gian(); break; } }
Nếu nhấn nút 1 thì sẽ chuyển vào kênh thứ “setup”,trong này chúng ta gọi các hàm chức năng ứng với từng kênh trong setup ra.Đoạn hàm if cuối cùng để thoát khỏi Menu khi không nhấn nút gì trong 1 phút,vì vậy mỗi khi nhấn 1 nút gì,chúng ta phải đưa biến Dem_timerA0 về 0 để reset quá trình tự thoát.
Như vậy là các bạn đã viết xong menu của chương trình rồi.Trong hàm if(press==1) {…} các bạn có thể dùng if … else hoặc switch đều được.
2.3. Tạo hàm xem ngày
Như vậy là các bạn đã viết xong menu của chương trình rồi.Trong hàm if(press==1) {…} các bạn có thể dùng if … else hoặc switch đều được.
2.3. Tạo hàm xem ngày
Bây giờ,khi muốn hiển thị ngày khi nhấn nút 2,ta tạo hàm Hien_thi_ngay() và gọi hàm này ở đoạn lệnh quét phím như phần menu ở trên.
Chúng ta sẽ không đọc bàn phím liên tục mà cứ quét được khoảng 20 lần quét led thì mới đọc bàn phím 1 lần.Vì thời gian đọc ADC khá lâu và nếu đọc liên tục thì sẽ ảnh hưởng đến hiển thị.Dùng biến counter để làm việc này.
Chúng ta sẽ không đọc bàn phím liên tục mà cứ quét được khoảng 20 lần quét led thì mới đọc bàn phím 1 lần.Vì thời gian đọc ADC khá lâu và nếu đọc liên tục thì sẽ ảnh hưởng đến hiển thị.Dùng biến counter để làm việc này.
Hàm Hien_thi_ngay() như sau.
2.4. Tạo hàm xem nhiệt độ
void Hien_thi_ngay() { Enable_timerA0=1; // Cho phep dem timer Dem_timerA0=0; // Reset bien dem timer ve 0 Doc_thoi_gian(); while(press==0x02) { if(Dem_timerA0==0) // 2.5s mois quet ban phim 1 lan { #ifdef USE_UART UART_Write_Char(10); //Ky tu xuong dong sprintf(buff,"Date:%d:%d:20%d",ngay,thang,nam); UART_Write_String(buff); #endif Dem_timerA0++; SWITCH_CheckKey(); } if(Dem_timerA0>=5)Dem_timerA0=0; //Sau moi 2.5s thi quet lai nut bam //Doan nay hien thi ngay GIO10_ON; Write_74HC595_8bit(soled[ngay10BCD]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); GIO_ON; Write_74HC595_8bit(soled[ngayBCD]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT10_ON; Write_74HC595_8bit(soled[thang10BCD]&~BIT5);//hien thi dau cham _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT_ON; Write_74HC595_8bit(soled[thangBCD]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); } //Sau 2.5s ke tu luc nha nut bam ra -> doan nay hien thi ngay Dem_timerA0=0; //Hien thi nam while(Dem_timerA0<4) { GIO10_ON; Write_74HC595_8bit(soled[2]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); GIO_ON; Write_74HC595_8bit(soled[0]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT10_ON; Write_74HC595_8bit(soled[nam/10]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT_ON; Write_74HC595_8bit(soled[nam%10]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); } Enable_timerA0=0; Dem_timerA0=0; }
2.4. Tạo hàm xem nhiệt độ
Tương tự chúng ta viết hàm đọc nhiệt độ khi nhấn nút 3,gọi hàm này trong menu chương trình.Chúng ta đọc cảm biến nhiệt độ trong MSP430.Thực chất cảm biến này là một trở nhiệt được mắc nối tiếp với 1 điện trở thường.Đọc giá trị cảm biến nhiệt độ ở cổng 10 của ADC.Các bạn có thể đọc lại bài ADC trong loạt bài MSP430 Tutorial để hiểu chi tiết.
Một điểm cần chú ý là cảm biến nhiệt độ trong MSP430 có sai số tĩnh khá lớn và mỗi chip vi điều khiển có sai số riêng cần phải căn chỉnh bằng tay.Vì vậy công thức tính nhiệt độ (biến Temperature ) cần chỉnh lại cho phù hợp.
Nhiệt độ sẽ được cập nhật trong ngắt nhận của BQ32000,tức là mỗi giây đo nhiệt độ 1 lần,cho qua 1 bộ lọc cửa sổ ( Temp[0] -> Temp[7]) để giảm nhiễu .Các bạn có thể làm nhiều cách khác tùy ý,miễn sao phải đo nhiệt độ thường xuyên và cách đều nhau.Đoạn code này đặt trong ngắt của chân P1.0 ,đặt ngay trước lệnh :
P1IFG &=~BIT0;
[code] if(giay%2==0) { Temp[7]=Temp[6]; Temp[6]=Temp[5]; Temp[5]=Temp[4]; Temp[4]=Temp[3]; Temp[3]=Temp[2]; Temp[2]=Temp[1]; Temp[1]=Temp[0]; Temp[0] = ADC10_Read_Channel(10); }[/code]
Các bạn nhớ để ý xem đã khai báo ngắt cho ADC chưa.
Khi nhấn nút 3,hàm nhiệt độ sẽ được gọi,thêm đoạn code này trong timer để nhận nút 3 .
if(counter==0) { SWITCH_Key(); if(press==0x02)Hien_thi_ngay(); // Nhan nut 2 if(press==0x04)Doc_nhiet_do(); // Nhan nut 3 }
Viết hàm đọc thời gian.
void Doc_nhiet_do() { // Luu y gia tri Temperature duoc tinh dua tren trung binh // cua 8 lan do cuoi cung,vi vay can doan code cap nhat gia tri // nhiet do thuong xuyen trong ngat 1s nhan tu BQ32000 long Temperature=0; Enable_timerA0=1; //Cho phep dem timer Dem_timerA0=0; //Reset timer ve 0 while(press==0x04) //Neu giu nut 3 thi van hien thi nhiet do //Nha ra thi 2.5s sau se thoat { if(Dem_timerA0==0) //Tuong tu tren, cu 2.5s moi quet ban phim 1 lan { Temperature= (Temp[7]+Temp[6]+Temp[5]+Temp[4]+ Temp[3]+Temp[2]+Temp[1]+Temp[0])>>3; // Tao bo tre,lay trung binh gia tri 8 lan do cuoi cung , de chong nhieu Temperature = (Temperature*4437/128-10180)/25; // Tinh theo cong thuc mau trong datasheet,gia tri offset cua cam bien // Co the chinh bang TLV #ifdef USE_UART UART_Write_Char(10); //Ky tu xuong dong sprintf(buff,"Temp:%d *C",Temperature); UART_Write_String(buff); #endif SWITCH_CheckKey(); Dem_timerA0++; } if(Dem_timerA0>=5)Dem_timerA0=0; // Neu nhiet do duong thi hien nhiet do o ky tu thu 2 va 3, // ky tu *C o ben phai if(Temperature>0) { GIO_ON; Write_74HC595_8bit(soled[Temperature/10]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT10_ON; Write_74HC595_8bit(soled[Temperature%10]); } // Neu nhiet do am thi hien dau tru o ben trai else { GIO10_ON; Write_74HC595_8bit(soled[11]); // Dau - _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); GIO_ON; Write_74HC595_8bit(soled[-Temperature/10]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT10_ON; Write_74HC595_8bit(soled[-Temperature%10]); } _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); //Hien *C PHUT_ON; Write_74HC595_8bit(soled[12]); //*C _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); } Enable_timerA0=0; Dem_timerA0=0; }
2.5. Viết hàm chỉnh giờ trong Menu Setup
Ở đây mình sẽ không giải thích chi tiết thuật toán nữa vì nó không khác nhiều với các hàm bên trên.
void Chinh_gio() { unsigned char temp=0; //Phan chuc nang phim 2 va 3 chinh gio va phut while(1) { GIO10_ON; P2OUT&=~BIT7; //Bat dau hai cham if((temp!=0)||(giay%2==0))Write_74HC595_8bit(soled[gio10BCD]); _delay_us(delay_led_on); P2OUT|=BIT7; //Tat dau hai cham Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); GIO_ON; if((temp!=0)||(giay%2==0))Write_74HC595_8bit(soled[gioBCD]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT10_ON; if((temp!=1)||(giay%2==0))Write_74HC595_8bit(soled[phut10BCD]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); PHUT_ON; if((temp!=1)||(giay%2==0))Write_74HC595_8bit(soled[phutBCD]); _delay_us(delay_led_on); Write_74HC595_8bit(0xFF); _delay_us(delay_led_off); SWITCH_Key(); if(press!=0) { Enable_timerA0=1; Dem_timerA0=0; //__bic_SR_register(GIE); switch(press) { case 1: { if(temp==1)temp=0; else temp=1; break; } case 2: //Tang phut/gio { if(temp==0) //Gio { if(gio==23)gio=0; else gio++; I2C_USCI_Write_Byte(0x02,((gio/10)<<4)+gio%10); } else //Phut { if(phut==59)phut=0; else phut++; I2C_USCI_Write_Byte(0x01,((phut/10)<<4)+phut%10); } break; } case 4: //Giam phut/gio { if(temp==0) //Gio { if(gio==0)gio=23; else gio--; I2C_USCI_Write_Byte(0x02,((gio/10)<<4)+gio%10); } else //Giam phut { if(phut==0)phut=59; else phut--; I2C_USCI_Write_Byte(0x01,((phut/10)<<4)+phut%10); } break; } case 8: { Dem_timerA0=120; break; } } //__bis_SR_register(GIE); } if(Dem_timerA0>=120)break; } press=0; Dem_timerA0=0; Enable_timerA0=0; }
Các hàm còn lại các bạn có thể tự viết hoặc tham khảo trong project mẫu.
Download toàn bộ Project tại đây
No comments:
Post a Comment