Koneksi Mikrokontroler ATmega8535 Dengan Modul GSM Fastrack M1306B
January 3, 2012 55 Comments
Spesifikasi
Konfigurasi RS232 yang saya gunakan seperti biasanya TX ke RX device dan RX ke TX device, tapi ada tambahan yaitu pin 7 dan 8 disambungkan. USART Baud Rate: 115200. Berikut keterangan pin konektor DB9.
Pin No. | Name | Dir | Notes/Description |
1 | DCD | IN | Data Carrier Detect. Raised by DCE when modem synchronized. |
2 | RD | IN | Receive Data (a.k.a RxD, Rx). Arriving data from DCE. |
3 | TD | OUT | Transmit Data (a.k.a TxD, Tx). Sending data from DTE. |
4 | DTR | OUT | Data Terminal Ready. Raised by DTE when powered on. In auto-answer mode raised only when RI arrives from DCE. |
5 | SGND | – | Ground |
6 | DSR | IN | Data Set Ready. Raised by DCE to indicate ready. |
7 | RTS | OUT | Request To Send. Raised by DTE when it wishes to send. Expects CTS from DCE. |
8 | CTS | IN | Clear To Send. Raised by DCE in response to RTS from DTE. |
9 | RI | IN | Ring Indicator. Set when incoming ring detected – used for auto-answer application. DTE raised DTR to answer. |
Berikut adalah contoh kode koneksi menggunakan serial RS232 antara modem dan mikrokontroler atmega8535 dengan tambahan LCD 2×16 menggunakan compiler CodeVisionAVR. Perintah menggunakan AT Command.
Inisialisasi
void sms_init() { printf("ATE0"); //set command tanpa echo putchar(0x0D);//ENTER while(getchar()!='K'){}; //menunggu OK while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); }
Kirim SMS ukuran 1 byte
void send_sms(char flash *fmtstr1, char fmtstr2) { printf("AT+CMGF=1"); //set sebagai pesan text putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGS=\""); //perintah kirim sms tanpa memasukan ke memory pesan printf(fmtstr1); //kirim nomor tujuan printf("\""); putchar(0x0D);//ENTER while(getchar()!=0x20){}; //menunggu spasi delay_ms(500); putchar(fmtstr2); //pesan kode ukuran 1 byte putchar(0x1A); //kirim Ctrl+z while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGD=1"); //hapus memori pesan urutan satu putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGD=2"); //hapus memori pesan urutan dua putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGD=3"); //hapus memori pesan urutan tiga putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); }
Kirim Text SMS
void send_sms_txt(char flash *fmtstr1, char flash *fmtstr2) { printf("AT+CMGF=1"); //set sebagai pesan text putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGS=\""); //perintah kirim sms tanpa memasukan ke memory pesan printf(fmtstr1); //kirim nomor tujuan printf("\""); putchar(0x0D);//ENTER while(getchar()!=0x20){}; //menunggu spasi delay_ms(500); printf(fmtstr2); //pesan text putchar(0x1A); //kirim Ctrl+z while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGD=1"); //hapus memori pesan urutan satu putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGD=2"); //hapus memori pesan urutan dua putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGD=3"); //hapus memori pesan urutan tiga putchar(0x0D);//ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); }
Menerima pesan ukuran 1 byte
char receive_sms() { char data; printf("AT+CMGR=1"); //cek memori pesan datang urutan 1 putchar(0x0D); //ENTER while(getchar()!=0x0A){}; //menunggu new line while(getchar()!=0x0A){}; //menunggu new line data=getchar(); //menerima data while(getchar()!='K'){}; //menunggu OK while(getchar()!=0x0A){}; //menunggu new line delay_ms(500); printf("AT+CMGD=1"); //hapus memori pesan urutan satu putchar(0x0D);//ENTER while(getchar()!='K'){};//menunggu OK while(getchar()!=0x0A){};//menunggu new line delay_ms(500); printf("AT+CMGD=2"); //hapus memori pesan urutan dua putchar(0x0D);//ENTER while(getchar()!='K'){};//menunggu OK while(getchar()!=0x0A){};//menunggu new line delay_ms(500); printf("AT+CMGD=3"); //hapus memori pesan urutan tiga putchar(0x0D);//ENTER while(getchar()!='K'){};//menunggu OK while(getchar()!=0x0A){};//menunggu new line delay_ms(500); return(data);//mengembalikan nilai data }
Mengecek sms masuk
void cek_sms() { while(getchar()!=','){}; //menunggu tanda ',' koma while(getchar()!=0x0A){};//menunggu new line }
________________________________________________________
Berikut kode lengkap yang saya gunakan untuk mendeteksi ketinggian air di alat pengukur curah hujan berbasis sms.
/***************************************************** Chip type : ATmega8535 Program type : Application AVR Core Clock frequency: 11.059200 MHz Memory model : Small External RAM size : 0 Data Stack size : 128 *****************************************************/ #include #include #include // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x15 ;PORTC #endasm #include #ifndef RXB8 #define RXB8 1 #endif #ifndef TXB8 #define TXB8 0 #endif #ifndef UPE #define UPE 2 #endif #ifndef DOR #define DOR 3 #endif #ifndef FE #define FE 4 #endif #ifndef UDRE #define UDRE 5 #endif #ifndef RXC #define RXC 7 #endif #define FRAMING_ERROR (1<<FE) #define PARITY_ERROR (1<<UPE) #define DATA_OVERRUN (1<<DOR) #define DATA_REGISTER_EMPTY (1<<UDRE) #define RX_COMPLETE (1<<RXC) // USART Receiver buffer #define RX_BUFFER_SIZE 8 char rx_buffer[RX_BUFFER_SIZE]; #if RX_BUFFER_SIZE<256 unsigned char rx_wr_index,rx_rd_index,rx_counter; #else unsigned int rx_wr_index,rx_rd_index,rx_counter; #endif // This flag is set on USART Receiver buffer overflow bit rx_buffer_overflow; // USART Receiver interrupt service routine interrupt [USART_RXC] void usart_rx_isr(void) { char status,data; status=UCSRA; data=UDR; if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0) { rx_buffer[rx_wr_index]=data; if (++rx_wr_index == RX_BUFFER_SIZE) rx_wr_index=0; if (++rx_counter == RX_BUFFER_SIZE) { rx_counter=0; rx_buffer_overflow=1; }; }; } #ifndef _DEBUG_TERMINAL_IO_ // Get a character from the USART Receiver buffer #define _ALTERNATE_GETCHAR_ #pragma used+ char getchar(void) { char data; while (rx_counter==0); data=rx_buffer[rx_rd_index]; if (++rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0; #asm("cli") --rx_counter; #asm("sei") return data; } #pragma used- #endif // USART Transmitter buffer #define TX_BUFFER_SIZE 8 char tx_buffer[TX_BUFFER_SIZE]; #if TX_BUFFER_SIZE<256 unsigned char tx_wr_index,tx_rd_index,tx_counter; #else unsigned int tx_wr_index,tx_rd_index,tx_counter; #endif // USART Transmitter interrupt service routine interrupt [USART_TXC] void usart_tx_isr(void) { if (tx_counter) { --tx_counter; UDR=tx_buffer[tx_rd_index]; if (++tx_rd_index == TX_BUFFER_SIZE) tx_rd_index=0; }; } #ifndef _DEBUG_TERMINAL_IO_ // Write a character to the USART Transmitter buffer #define _ALTERNATE_PUTCHAR_ #pragma used+ void putchar(char c) { while (tx_counter == TX_BUFFER_SIZE); #asm("cli") if (tx_counter || ((UCSRA & DATA_REGISTER_EMPTY)==0)) { tx_buffer[tx_wr_index]=c; if (++tx_wr_index == TX_BUFFER_SIZE) tx_wr_index=0; ++tx_counter; } else UDR=c; #asm("sei") } #pragma used- #endif // Standard Input/Output functions #include // Declare your global variables here void sms_init() { printf("ATE0"); putchar(0x0D);//ENTER while(getchar()!='K'){}; while(getchar()!=0x0A){}; delay_ms(500); } void send_sms(char flash *fmtstr1, char fmtstr2) { printf("AT+CMGF=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGS=\""); printf(fmtstr1); printf("\""); putchar(0x0D);//ENTER while(getchar()!=0x20){}; delay_ms(500); putchar(fmtstr2); putchar(0x1A); while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=2"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=3"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); } void send_sms_txt(char flash *fmtstr1, char flash *fmtstr2) { printf("AT+CMGF=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGS=\""); printf(fmtstr1); printf("\""); putchar(0x0D);//ENTER while(getchar()!=0x20){}; delay_ms(500); printf(fmtstr2); putchar(0x1A); while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=2"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=3"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); } char receive_sms() { char data; printf("AT+CMGR=1"); putchar(0x0D); while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; data=getchar(); while(getchar()!='K'){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=1"); putchar(0x0D);//ENTER while(getchar()!='K'){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=2"); putchar(0x0D);//ENTER while(getchar()!='K'){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=3"); putchar(0x0D);//ENTER while(getchar()!='K'){}; while(getchar()!=0x0A){}; delay_ms(500); return(data); } void cek_sms() { while(getchar()!=','){}; while(getchar()!=0x0A){}; } // Declare your global variables here #define PULSE PORTA.1 #define ECHO PINA.1 #define ARAH DDRA.1 unsigned char kata[16]; unsigned int ultra() { unsigned int count=0,jarak=0; ARAH = 1; PULSE = 1; delay_us(5); PULSE = 0; ARAH = 0; PULSE = 1; while(ECHO == 0){}; while(ECHO == 1){count++;}; jarak = (float)count/2.5; delay_ms(10); return(jarak); } int sensor_in_t0, //nilai sensor sebelum sensor_in_t1, //nilai sensor sekarang del_sensor; //delta sensor void baca_sensor_init() { sensor_in_t0 = (int)ultra(); //setting awal delay_ms(100); } unsigned char baca_curah_hujan() { sensor_in_t1 = (int)ultra(); //baca sensor sekarang del_sensor = sensor_in_t0 - sensor_in_t1; //hitung selisih sensor sebelum dikurangi sensor sekarang if(del_sensor 300) //300 mm/menit { return((unsigned char)3); }else if(del_sensor>200) //200 mm/menit { return((unsigned char)2); }else if(del_sensor>100) //100 mm/menit { return((unsigned char)1); }else //dibawah 100 mm/menit { return((unsigned char)0); } } char kirim_pesan(unsigned char kode) { switch(kode) { case 0: lcd_gotoxy(0,1); lcd_putsf("AMAN"); return('0'); break; case 1: lcd_gotoxy(0,1); lcd_putsf("SIAGA I"); return('1'); break;//jika kode 1 kirim pesan siaga I case 2: lcd_gotoxy(0,1); lcd_putsf("SIAGA II"); return('2'); break;//jika kode 1 kirim pesan siaga II case 3: lcd_gotoxy(0,1); lcd_putsf("SIAGA III"); return('3'); break;//jika kode 1 kirim pesan siaga III } } void delta_waktu(unsigned int del) { delay_ms(del*1000); } void main(void) { // Declare your local variables here char data_dev1, data_dev2, data_hasil; // Input/Output Ports initialization // Port A initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTA=0x00; DDRA=0x00; // Port B initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTB=0x00; DDRB=0x00; // Port C initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTC=0x00; DDRC=0x00; // Port D initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTD=0x00; DDRD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped // Mode: Normal top=FFh // OC0 output: Disconnected TCCR0=0x00; TCNT0=0x00; OCR0=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: Timer1 Stopped // Mode: Normal top=FFFFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0x00; TCCR1B=0x00; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // Timer/Counter 2 initialization // Clock source: System Clock // Clock value: Timer2 Stopped // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x00; TCCR2=0x00; TCNT2=0x00; OCR2=0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off // INT2: Off MCUCR=0x00; MCUCSR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x00; // USART initialization // Communication Parameters: 8 Data, 1 Stop, No Parity // USART Receiver: On // USART Transmitter: On // USART Mode: Asynchronous // USART Baud Rate: 115200 UCSRA=0x00; UCSRB=0xD8; UCSRC=0x86; UBRRH=0x00; UBRRL=0x05; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR=0x00; // LCD module initialization lcd_init(16); // Global enable interrupts #asm("sei") lcd_clear(); delay_ms(1000); lcd_gotoxy(0,0); lcd_putsf("inisialisasi program"); baca_sensor_init(); sms_init(); while (1) { // Place your code here lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("siap menerima kode"); cek_sms(); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("ada sms, siap cek kode"); data_dev1 = receive_sms() - 48; //normalisasi bilangan, ascii to desimal data_dev2 = kirim_pesan(baca_curah_hujan()) - 48; //normalisasi bilangan, ascii to desimal data_hasil = data_dev1 + data_dev2; switch(data_hasil) { case 0: lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("AMAN"); delay_ms(2000); break; case 1: lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("AMAN"); delay_ms(2000); break; case 2: lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("AMAN"); delay_ms(2000); break; case 3: send_sms("082121962044",'1'); send_sms_txt("082121962033","BADAN PENANGGULANGAN BANJIR: WARNING I"); //OPERATOR lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("WARNING I"); delay_ms(2000); break; case 4: send_sms("082121962044",'2'); send_sms_txt("082121962033","BADAN PENANGGULANGAN BANJIR: WARNING II"); //OPERATOR lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("WARNING II"); delay_ms(2000); break; case 5: send_sms("082121962044",'2'); send_sms_txt("082121962033","BADAN PENANGGULANGAN BANJIR: WARNING II"); //OPERATOR lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("WARNING II"); delay_ms(2000); break; case 6: send_sms("082121962044",'3'); send_sms_txt("082121962033","BADAN PENANGGULANGAN BANJIR: WARNING III"); //OPERATOR lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("WARNING II"); delay_ms(2000); break; } }; }
___________________________________________________
UPDATE
Karena versi yg atas masih ada kelemahannya, yaitu pada fungsi send sms nya. Ketika modem memberi feedback error, program mikrokontroler akan terjebak pada looping. Maka, berikut versi perbaikannya dari fungsi send sms yang akan mencoba mengirim sms kembali jika modem memberikan feedback error pada pengiriman sebelumnya.
void send_sms(char flash *fmtstr1, char fmtstr2) { char dat; awal_sms: delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Kirim SMS"); printf("AT+CMGF=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGS=\""); printf(fmtstr1); printf("\""); putchar(0x0D);//ENTER while(getchar()!=0x20){}; //spasi delay_ms(500); putchar(fmtstr2); putchar(0x1A); while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS"); goto awal_sms; } } }; while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS"); goto awal_sms; } } }; while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS"); goto awal_sms; } } }; while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS"); goto awal_sms; } } }; delay_ms(500); printf("AT+CMGD=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=2"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=3"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); } void send_sms_txt(char flash *fmtstr1, char flash *fmtstr2) { char dat; awal_sms_txt: delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Kirim SMS ke-2"); printf("AT+CMGF=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGS=\""); printf(fmtstr1); printf("\""); putchar(0x0D);//ENTER while(getchar()!=0x20){}; delay_ms(500); printf(fmtstr2); putchar(0x1A); while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS ke-2"); goto awal_sms_txt; } } }; while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS ke-2"); goto awal_sms_txt; } } }; while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS ke-2"); goto awal_sms_txt; } } }; while((dat=getchar())!=0x0A) { if (dat=='O') { if(getchar()=='R') { getchar(); delay_ms(500); lcd_clear(); lcd_gotoxy(0,0); lcd_putsf("Gagal kirim SMS ke-2"); goto awal_sms_txt; } } }; delay_ms(500); printf("AT+CMGD=1"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=2"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); printf("AT+CMGD=3"); putchar(0x0D);//ENTER while(getchar()!=0x0A){}; while(getchar()!=0x0A){}; delay_ms(500); }
____________________________________
Daftar Pustaka
Click to access P_Fastrack_Modem_M1306B_User_Guide.pdf
http://www.developershome.com/sms/sendSmsByAtCommands.asp
http://www.bbdsoft.com/ascii.html