Wireless communication has not only became major necessity today, rather it has been a "MUST" component in everyone's life. Start from radio, TV remote control, mobile phone, and now the hottest topic on earth "IoT Internet of Things".
IoT is essentially network of physical object embedded in any devices (starts from industry sensors to home's appliances) which enables these "things" to exchange data over Internet connection. In other words, one can monitor the status of these "things" and conversely control them remotely via Internet.
During my final year in engineering degree, I developed wireless light switch which collaborates Sub-GHz and Wi-Fi connectivity. That says, one can control light bulbs over both dedicated remote control and/or smart phone. In this post I'd like to share the part with most fun, how to create a simple web server with micro-controller, specifically I used EK-TM4C123GXL Tiva C Series Launchpad from Texas Instruments.
All of above can be achieved with micro-controller unit (MCU) and a little bit of programming in C language. By knowing how to make an embedded web server, anyone can build their own Wi-Fi light switch and it can be furthermore developed into any other
applications to suit your needs such as Wi-Fi garage door, washing machine
or even your toaster. It may seems a little bit complicated at first glance but once you get used to it, it's relatively simple!
1. Foundation of Wi-Fi
Let's start with basic foundation of Wi-Fi, the popular wireless connectivity with operating frequency 2.4 GHz and 5 GHz base on IEEE 802.11 standard. Founded in 1999 under Wi-Fi Alliance as the trademark holder, this wireless local area network technology is globally used as admission ticket into Internet.
Below are several key elements which allow one device to connect to WLAN access point :
- SSID : Network name of
desired wireless access point or hotspot.
- Encryption : Type of password encryption, eg. WEP/WPA/WPA2.
- Password : If it's an open connection, password is not necessary.
WLAN Connect Scheme |
Wi-Fi only initiates the delivery of message from one device to another via wireless median. While core communication itself relies on TCP/IP, the most commonly used communication protocol worldwide on computer network including LAN, WAN and Internet. It provides end-to-end connectivity specifying how data should be virtually packaged, addressed, transmitted, routed and received. This makes operation in TCP/IP mostly relates to addresses, most important are as below :
- IP Address : This is the main address of
CC3000 in wireless local area network. It will be automatically assigned by
access point/router. A 32-bit binary value that is represented in
combination of 4 decimal value, separated by dot. Each decimal value represents
8-bit binary therefore. For instance, home router generally has IP address of
192.168.0.1.
- Socket : It defines which type of packet
data to be received. There are two types of packet data, UDP (User Datagram
Protocol) and TCP (Transmission Control Protocol). Generally for PC
application, UDP packet data contains network status while TCP packet contains
data/information. Although both can be used in this project, TCP is chosen as
it makes more sense that we're sending data instead of network status.
- Port : This defines at which port number the data shall be received, a 16-bit binary
value, therefore it ranges from 1-65535. All port number can be used, however
port number 80 is chosen in this project, as it is most commonly heard in
real life application (port number 80 is used for HTTP/webpage application in
PC).
TCP Server Setup Scheme |
2. Hardware
We need only two things, a host MCU and a compatible Wi-Fi module.
I used Texas Instruments for both of them, both are in form of development board which is perfect solution for beginner as below :
- Host MCU : Texas Instrument EK-TM4C123GXL Tiva C Launchpad
- Wi-Fi Module : Texas Instrument CC3000 Boosterpack
TI EK-TM4C123GXL Tiva C Launchpad costs US$12.9, can buy it HERE.
Tiva C Launchpad EK-TM4C123GXL |
Wi-Fi module CC3000 Boosterpack is explicitly made to comply with Tiva C Launchpad, you may buy it for US$40 HERE.
CC3000 Boosterpack |
3. Software/Coding
Now we have our hardware in place, get ready for the next BIG thing which is programming. I used "Code Composer Studio" to write my code in C++ and burn it into my Tiva C Launchpad. On top of it, we need "Tivaware for C Series" library to efficiently write program code for Tiva C Launchpad. Function calls and API for CC3000 Wi-Fi module are also well defined in Tivaware package.
Start to write program code by download and install CCS+Tivaware HERE.
If you don't already know how to initiate CCS and Tivaware, get started by following free tutorial in Tiva C Series Workshop HERE.
Flowchart is extremely important when it comes to write program code, it visualizes the entire operation flow hence give clear image on how the code should be efficiently written in achieving most desirable outcomes. Below is my flowchart, you can have your very own flowchart to suit your need.
My Flowchart |
The first piece of code is always started with hardware configuration and protocol stack initialization. There are abundant peripherals to be turned on in Tiva C in order to greatly integrate to system, among all are two SPI modules one to communicate to CC3000 and another one to dsPIC33F, UART for debugging purpose, and output pins for any other peripherals such as alphanumeric display and trigger relays. I used dsPIC33F from Microchip to control sub-GHz peripherals which won't be covered in this post, you can just assume this as PWM to dim LED.
After initialization completes, CC3000 is expected to gain access into WLAN access point. Then port and socket are required to be opened and bound on in
order to establish a communication channel between CC3000 and wireless
router/end-device. Therefore allowing any incoming packet to be routed
by wireless router towards CC3000 under assigned IP address. When a valid HTTP request is received, Tiva C is programmed to load the
HTML code in CC3000 buffer and transmit to destination client. This will
be displayed as a web page on destination's web browser with virtual
button to send command back to Tiva C+CC3000. For instance, when the
user click certain button on webpage, it will transmit TCP packet to
CC3000 and Tiva C will toggle output according to this command.
#include <stdint>
#include <stdbool>
#include <stdlib>
#include "inc/hw_types.h"
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_ssi.h"
#include "inc/hw_memmap.h"
#include "driverlib/timer.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/systick.h"
#include "driverlib/fpu.h"
#include "driverlib/debug.h"
#include "driverlib/ssi.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "utils/ustdlib.h"
#include "utils/uartstdio.h"
#include "utils/cmdline.h"
#include "utils/lcd44780_LP.h"
#include "driverlib/uart.h"
#include "driverlib/ssi.h"
#include "wlan.h"
#include "evnt_handler.h"
#include "nvmem.h"
#include "socket.h"
#include "netapp.h"
#include "spi.h"
#include "hci.h"
#include "cc3000_common.h"
#include "dispatcher.h"
#include "spi_version.h"
#include "board.h"
#include "host_driver_version.h"
#include "security.h"
volatile unsigned int CC3000Connected,CC3000DHCP; //Variables used for identification
volatile unsigned int ServerSocket = 0xFFFFFFFF; //Socket status
unsigned int SocketType = 0; //Socket type
long ClientSocket = -1; //Client Socket Descriptor
int ClientConnected; //Client Status
volatile fd_set ConnectedSockets; //Incoming Client Socket
char ip3[3],ip2[3],ip1[3],ip0[3];
sockaddr SocketAddress; //Socket address (Port number)
unsigned char CC3000_Rx_Buffer[10]; //Receive buffer in CC3000
int RequestPage = 0; //Data received identification
char *sendWLFWPatch(unsigned long *Length) //Patch, return 0 since it's taken from CC3000 internal memory
{
*Length = 0;
return(NULL);
}
char *sendBootLoaderPatch(unsigned long *Length) //Patch, return 0 since it's taken from CC3000 internal memory
{
*Length = 0;
return(NULL);
}
char *sendDriverPatch(unsigned long *Length) //Patch, return 0 since it's taken from CC3000 internal memory
{
*Length = 0;
return(NULL);
}
void CC3000_Interrupt(long EventType, char *ipaddress, unsigned char Length); //Interrupt, unsolicited event from CC3000
int connecting(void); //WLAN connecting function
void opensocket(void); //Open socket function
void portbinding(void); //Port binding function
int acceptclient();
int datareceive(void); //Receiving data function
void datasend();
void closingsocket(void); //Closing socket function
void itoa(long unsigned int value, char* result, int base);
int main(void)
{
pio_init(); //Initialize device pin configuration and the system clock, defined in board.c
init_spi(1000000, SysCtlClockGet()); //Initialize and configure the SPI 1MHz
SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI1);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
GPIOPinConfigure(GPIO_PD0_SSI1CLK);
GPIOPinConfigure(GPIO_PD1_SSI1FSS);
GPIOPinConfigure(GPIO_PD2_SSI1RX);
GPIOPinConfigure(GPIO_PD3_SSI1TX);
GPIOPinTypeSSI(GPIO_PORTD_BASE,GPIO_PIN_3|GPIO_PIN_2|GPIO_PIN_1|GPIO_PIN_0);
SSIConfigSetExpClk(SSI1_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 10000, 8);
SSIEnable(SSI1_BASE);
IntMasterEnable(); //Enable processor interrupts
wlan_init(CC3000_Interrupt,sendWLFWPatch, sendDriverPatch,sendBootLoaderPatch,
ReadWlanInterruptPin, WlanInterruptEnable, WlanInterruptDisable, WriteWlanPin);
//Initialize, patch and enable WiFi on the CC3000
wlan_start(0); //Start up the CC3000
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 2);
//Turn on the the red LED to indicate CC3000 operates, defined in board.c
wlan_set_event_mask(HCI_EVNT_WLAN_KEEPALIVE | HCI_EVNT_WLAN_UNSOL_INIT);
//Mask out all non-required interrupt events from CC3000
DispatcherUARTConfigure(SysCtlClockGet()); //Initialize UART
SysCtlDelay(1000000);
UARTprintf("Welcome to FYP! \n"); //Welcoming note on UART
initLCD(); //Initialize alphanumeric LCD
LCDWriteText(" Hello! ",0,0); //Welcoming note on LCD
LCDWriteText("Wireless Switch",1,0);
InitSysTick(); // Configure and enable the system tick for interrupt handler
while(1)
{
if(CC3000Connected == 0) //If WiFi is not connected
{
connecting(); //Connect function
opensocket(); //Open socket
portbinding(); //Bind to port
}
int x = acceptclient(); //Client Acceptance
if(x==1) //If there is client
{
UARTprintf("New Client \n");
datareceive(); //Receive data
switch(RequestPage) //Decode the received data accordingly
{
case 1 : datasend(); break;
case 2 :
{
datasend();
while(SSIBusy(SSI1_BASE))
{}
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 2); SysCtlDelay(0.1*SysCtlClockGet()/3);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 10);
//Blink red LED, this can be modified to desired output
SSIDataPut(SSI1_BASE,0x01);
LCDWriteText(" NODE 1 ",0,0);
}; break;
case 3 :
{
datasend();
while(SSIBusy(SSI1_BASE))
{}
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 4); SysCtlDelay(0.1*SysCtlClockGet()/3);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 10);
//Blink blue LED
SSIDataPut(SSI1_BASE,0x02);
LCDWriteText(" NODE 2 ",0,0);
}; break;
case 4 :
{
datasend();
while(SSIBusy(SSI1_BASE))
{}
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 8); SysCtlDelay(0.1*SysCtlClockGet()/3);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 10);
//Blink green LED
SSIDataPut(SSI1_BASE,0x03);
LCDWriteText(" NODE 3 ",0,0);
}; break;
case 5 :
{
datasend();
while(SSIBusy(SSI1_BASE))
{}
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 14); SysCtlDelay(0.1*SysCtlClockGet()/3);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 10);
//Blink all LEDs
SSIDataPut(SSI1_BASE,'d');
LCDWriteText(" SERVER D ",0,0);
}; break;
case 6 :
{
datasend();
while(SSIBusy(SSI1_BASE))
{}
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 14); SysCtlDelay(0.1*SysCtlClockGet()/3);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 10);
//Blink all LEDs
SSIDataPut(SSI1_BASE,'b');
LCDWriteText(" SERVER B ",0,0);
}; break;
}
closingsocket(); //Close the socket, to prevent socket address clash
}
SysCtlDelay(100000);
}
}
Several sub-functions shall be defined in more detail such as connecting(), opensocket(), portbinding(), acceptclient(), datareceive(), datasend() and CC3000_Interrupt(). In obtaining initial access, CC3000 would require to connect to wireless
access point by providing SSID network name and password. Tivaware library made most of crucial applications very simple by just calling necessary API function. CC3000 Wi-Fi module spends most time in waiting for incoming client and TCP packet. Once client request is received, TCP packet data is buffered into temporary memory array and processed accrodingly. Tiva C will process the TCP packet and respond accordingly. In general, HTTP request always starts with "HTTP / GET".
int connecting(void)
{
unsigned int x;
wlan_connect (WLAN_SEC_WPA2,"a7nightmare",11,0,"akutidaktahu",12); //Connect to access point with no password
LCDCommand(0x01); //Clear LCD
LCDWriteText("Connecting...",0,0); //Display status on LCD
LCDWriteText("8s timeout...",1,0);
UARTprintf("Attempting to connect (8 second timeout)...\n"); //Display status on UART
for(x = 0; x < 8000; x++) // Wait for 8 seconds, since the delay in loop is 1 ms
{
if(CC3000DHCP == 1) // Check to see if CC3000 is connected to a network
return(0); //Go back to main execution
SysCtlDelay(SysCtlClockGet() / 3000); //Delay 1 ms, one delay take 3 clock cycles
}
UARTprintf("Connection Failed. Please check the network name.\n");
LCDCommand(0x01);
LCDWriteText("Connection Fail!",0,0);
LCDWriteText("Try Again.....",1,0);
return(0);
}
void opensocket(void)
{
ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //Open TCP socket
if(ServerSocket >= 0) //If socket is successfully opened
{
UARTprintf("Server socket is handled by '%d'.\n",ServerSocket);
return; //Jump to main function
}
else
UARTprintf("Fail to open socket. \n");
}
void portbinding()
{
unsigned char x = 0;
SocketAddress.sa_family = AF_INET; //Port family is always AF_INET
SocketAddress.sa_data[0] = (80 & 0xFF00) >> 8; //Set the number of port to be bound
SocketAddress.sa_data[1] = (80 & 0x00FF) >> 0;
memset (&SocketAddress.sa_data[2], 0, 4); //Set dynamic IP address
x = bind(ServerSocket, &SocketAddress, sizeof(sockaddr)); //Bind the port
if(x == 0) //Check if port is successfully bound
{
UARTprintf("Bind Successful to port 80.\n");
}
else
{
UARTprintf("Bind Failed. Bind returned code '%d'\n",x);
CC3000Connected = 0; //Set variable to 0, reconnect needed
}
if (listen (ServerSocket, 1) != 0) //Listen to incoming connection
{
return ;
}
}
int acceptclient()
{
sockaddr clientaddr; //Client socket address
socklen_t addrlen; //Length of address
addrlen = sizeof(addrlen); //Function to calculate length
ClientSocket = accept(ServerSocket, (sockaddr *)&clientaddr, &addrlen); //Accepting client
FD_CLR(ClientSocket, &ConnectedSockets); //Binding the connected client if any
if(ClientSocket<0) return 0; //If no client, return value 0
else return 1; //If there is client, return 1
}
int datareceive(void)
{
RequestPage = 0;
int ByteReceived = 0;
int ret;
int i;
int home =0;
int client1 = 0;
int client2 = 0;
int client3 = 0;
int serverd = 0;
int serverb = 0;
char data[10] = "";
char home_addr[10] = "GET / HTTP"; //Content References
char Client_1[10] = "GET /node1"; //Content References
char Client_2[10] = "GET /node2";
char Client_3[10] = "GET /node3";
char Server_d[10] = "GET /servd";
char Server_b[10] = "GET /servb";
timeval timeout;
memset(&timeout, 0, sizeof(timeval)); //Timeout function
timeout.tv_sec = 0; //Timeout value
timeout.tv_usec = 500000;
fd_set readsds, errorsds; //Read and error variable
FD_ZERO(&readsds); //Clear read
FD_ZERO(&errorsds); //Clear error
FD_SET(ClientSocket, &readsds); //Apply function
FD_SET(ClientSocket, &errorsds);
ret = select(ClientSocket + 1, &readsds, NULL, NULL, &timeout); //Select port
if(!FD_ISSET(ClientSocket, &readsds)) return 0; //If no data, return 0
ByteReceived = recv(ClientSocket, CC3000_Rx_Buffer, 10, 0); //Receiving incoming data
UARTprintf("Data received with size of %d bytes. \n",ByteReceived);
for (i=0; i<ByteReceived; i++) //Checking data content
{
data[i] = CC3000_Rx_Buffer[i];
if(data[i] == home_addr[i])
home++;
if(data[i] == Client_1[i])
client1++;
if(data[i] == Client_2[i])
client2++;
if(data[i] == Client_3[i])
client3++;
if(data[i] == Server_d[i])
serverd++;
if(data[i] == Server_b[i])
serverb++;
}
UARTprintf(data);
if(home == ByteReceived)
{
RequestPage = 1;
UARTprintf("User ask for welcoming home page. \n");
}
if(client1 == ByteReceived)
{
RequestPage = 2;
UARTprintf("Client 1. \n");
}
if(client2 == ByteReceived)
{
RequestPage = 3;
UARTprintf("Client 2. \n");
}
if(client3 == ByteReceived)
{
RequestPage = 4;
UARTprintf("Client 3. \n");
}
if(serverd == ByteReceived)
{
RequestPage = 5;
UARTprintf("Server D. \n");
}
if(serverb == ByteReceived)
{
RequestPage = 6;
UARTprintf("Server B. \n");
}
}
void datasend()
{
int ByteTransmitted;
char newline[] = {'\n'}; //HTML content
char *html = ("HTTP/1.1 200 OKContent-type:text/html\n");
char *html1 = ("<html><head><title>SubGHz Wireless Switch</title></head>\n");
char *html2 = ("<body align=center><h1 align=center><font color=\"blue\">Welcome to William Young's SubGHz-WiFi Light Switch</font></h1>\n");
char *humid = ("<button onclick=\"location.href='/node1.html'\">Node 1</button><button onclick=\"location.href='/node2.html'\">Node 2</button><button onclick=\"location.href='/node3.html'\">Node 3</button> \n");
char *temp = ("Welcome, click on one of buttons below to switch on and off the power socket. Have fun! :) <br><button onclick=\"location.href='/servb.html'\">Server B</button><button onclick=\"location.href='/servd.html'\">Server D</button>");
char *html3 = ("<br><br><br><img src = \"http://www.gifs.net/Animation11/Jobs_and_People/Scientists/Inventor_3.gif\"></img> <br> \n");
char *html4 = ("</body></html>");
ByteTransmitted = send(ClientSocket, html, ustrlen(html), 0); //Sending HTML code line per line
ByteTransmitted = send(ClientSocket, newline, ustrlen(newline), 0);
ByteTransmitted = send(ClientSocket, newline, ustrlen(newline), 0);
ByteTransmitted = send(ClientSocket, html1, ustrlen(html1), 0);
ByteTransmitted = send(ClientSocket, html2, ustrlen(html2), 0);
ByteTransmitted = send(ClientSocket, temp, ustrlen(temp), 0);
ByteTransmitted = send(ClientSocket, humid, ustrlen(humid), 0);
ByteTransmitted = send(ClientSocket, html3, ustrlen(html3), 0);
ByteTransmitted = send(ClientSocket, newline, ustrlen(newline), 0);
ByteTransmitted = send(ClientSocket, html4, ustrlen(html4), 0);
ByteTransmitted = send(ClientSocket, newline, ustrlen(newline), 0);
ByteTransmitted = send(ClientSocket, newline, ustrlen(newline), 0);
}
void closingsocket(void)
{
unsigned int x;
x = closesocket(ClientSocket); //Close client socket
FD_CLR(ClientSocket, &ConnectedSockets);
if(x == 0) //If socket closed successfully
{
UARTprintf("Socket is closed.\n");
ClientSocket = -1; //Set client descriptor back to its initial value
}
else
UARTprintf("Socket close Failed.\n");
}
void CC3000_Interrupt(long EventType, char *ipaddress, unsigned char Length)
{
if(EventType == HCI_EVNT_WLAN_UNSOL_CONNECT) //Interrupt from CC3000-WLAN is connected
{
CC3000Connected = 1; //Set variable to indicate connection
}
if(EventType == HCI_EVNT_WLAN_UNSOL_DISCONNECT) //Interrupt from CC3000-WLAN is disconnected
{
UARTprintf("CC3000 is disconnected from WLAN\n");
CC3000Connected = 0; //Set the variable to indicate the status
CC3000DHCP = 0;
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 2); //Only Red LED is ON
LCDCommand(0x01);
LCDWriteText("WiFi Network",0,0);
LCDWriteText("Disconnected!",1,0);
}
if(EventType == HCI_EVNT_WLAN_UNSOL_DHCP) //Interrupt from CC3000-DHCP WLAN is connected
{
if( *(ipaddress + 20) == 0) //Check if connection is valid
{
UARTprintf("DHCP Connected. IP: %d.%d.%d.%d\n",
ipaddress[3],ipaddress[2],ipaddress[1],ipaddress[0]); //Print IP address on UART
itoa(ipaddress[3],ip3,10);
itoa(ipaddress[2],ip2,10);
itoa(ipaddress[1],ip1,10);
itoa(ipaddress[0],ip0,10);
LCDCommand(0x01);
LCDWriteText("IP Address :",0,0);
LCDWriteText(ip3,1,0);
LCDWriteText(".",1,3);
LCDWriteText(ip2,1,4);
LCDWriteText(".",1,7);
LCDWriteText(ip1,1,8);
LCDWriteText(".",1,11);
LCDWriteText(ip0,1,12);
CC3000DHCP = 1; //Set indication variable
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 10); //Turn on red and green LED
}
else
CC3000DHCP = 0; //If connection is not valid, set variable to 0
}
}
With this, you can basically make anything in your house an IoT. All you need is small tweak to the code to produce desired output. You can also make the website interface more interesting by modifying HTML content or even make an Android app to communicate with this simple web server. I'd love to hear from you if you succeed in making your own mini web server and/or simple IoT.