PID Motor Position Control using ESP8266 WiFi Module, US Digital E4P-100-079, LMD18200 H-Bridge Break Out, and HN-GH12-1634T 30:1 200RPM Motor
Dependencies: 4DGL-uLCD-SE PID QEI SDFileSystem mbed ESP8266_pid_mtrPos_webserver_SDcard_v2
Dependents: ESP8266_pid_mtrPos_webserver_SDcard_v2
main.cpp
- Committer:
- electromotivated
- Date:
- 2015-11-25
- Revision:
- 1:26a13ee574e9
- Parent:
- 0:a2a238159653
- Child:
- 2:af4befcd02d6
File content as of revision 1:26a13ee574e9:
/*
Uses the ESP8266 WiFi Chip to set up a WiFi Webserver used to control
the position of a motor using a PID controller.
NOTE: Webpage Handling in this program is specific to a CUSTOM
WEBPAGE. Program must be modified to handle specfically a new
webpage. A copy of the webpage for this program can be found at
the end of this program page. Simply copy and past text into a
html file and save as the given name.
TODO: ESP8366 has a max packet send size. Make sure we implement
a method to send webpages that exceed this value. The max size is
listed in the official ESP8266 AT Commands Documentation, I think
it is 2048 bytes/chars
TODO: CREATE CONFIG FUNCTION TO SET SSID, PASSWORD, BAUDRATE ETC.
TODO: CREATE FUCNTIONS FOR WIFI AND SERVER!!! GET RID OF "MAGIC NUMBER"
MAKE THIS AWESOME!!!!
TODO: COMMENT AND DOCUMENT THE HELL OUT OF THIS PROGRAM!!!
*/
#include "mbed.h"
#include "SDFileSystem.h"
#include "PID.h"
#include "QEI.h"
#include <algorithm>
/**********WEB SERVER SPECIFIC DECLARTATIONS**********************************/
/*****************************************************************************/
SDFileSystem sd(p5,p6,p7,p8,"sd"); // MOSI, MISO, SCLK, CS,
// Virtual File System Name
Serial esp(p13, p14); // tx, rx
DigitalOut espRstPin(p26); // ESP Reset
DigitalOut led(LED4);
Timer t1;
Timer t2;
void init(char* buffer, int size);
void getreply(int timeout_ms, char* buffer, int size, int numBytes);
void startserver(char* buffer, int size);
void update_webpage(char* webpage, float setpoint, float kp, float ki, float kd);
void parse_input(char* webpage_user_data, float* setpoint, float* kp, float* ki, float* kd);
int port =80; // set server port
int serverTimeout_secs =5; // set server timeout in seconds in case
// link breaks.
/*****************************************************************************/
/*****************************************************************************/
/*********PID CONTROLLER SPECIFIC DECLARATIONS********************************/
/*****************************************************************************/
static float setpoint, feedback, output;
const float output_lower_limit = -1.0;
const float output_upper_limit = 1.0;
const float FEEDBACK_SCALE = 1.0/3000.0; // Scale feedback to 1rev/3000cnts
// this is encoder specific.
const float setpoint_init = 0.0;
const float kp_init = 2.5;
const float ki_init = 5.0;
const float kd_init = 0.5;
const float Ts_init = 0.04; // 25Hz Sample Freq (40ms Sample Time)
PID pid(&setpoint, &feedback, &output, output_lower_limit, output_upper_limit,
kp_init, ki_init, kd_init, Ts_init);
QEI encoder(p15, p16);
PwmOut mtr_pwm(p25);
DigitalOut mtr_dir(p24);
void pid_callback(); // Updates encoder feedback and motor output
Ticker motor;
/*****************************************************************************/
/*****************************************************************************/
// Common Application Declarations
Serial pc(USBTX, USBRX);
float clip(float value, float lower, float upper);
int main()
{
printf("Starting\n");
/****************** Load Webpage from SD Card***************************************/
/***********************************************************************************/
char file[] = "/sd/pid_pos.html";
// Get file size so we can dynamically allocate buffer size
int num_chars = 0;
FILE *fp = fopen(file, "r");
while(!feof(fp)){
fgetc(fp);
num_chars++;
}
rewind(fp); // Go to beginning of file
// printf("Webpage Data Size: %d byte\r\n", num_chars);
const int WEBPAGE_SIZE = num_chars;
char webpage[WEBPAGE_SIZE];
webpage[0] = NULL; // Init our array so that element zero contains a null
// This is important, ensures strings are placed into
// buffer starting at element 0... not some random
// elment
// Read in and buffer file to memory
if(fp == NULL){
printf("Error: No Such File or something :(");
return 1;
}
else{
while(!feof(fp)){
fgets(webpage + strlen(webpage), WEBPAGE_SIZE, fp); // Get a string from stream, add to buffer
}
}
fclose(fp);
printf("Webpage Buffer Size: %d bytes\r\n", sizeof(webpage));
update_webpage(webpage, setpoint, kp_init, ki_init, kd_init);
/***********************************************************************************/
/***********************************************************************************/
/***************BRING UP SERVER*****************************************************/
/***********************************************************************************/
char buff[5000]; // Working buffer
init(buff, sizeof(buff)); // Init ESP8266
esp.baud(115200); // ESP8266 baudrate. Maximum on KLxx' is 115200, 230400 works on K20 and K22F
startserver(buff, sizeof(buff)); // Configure the ESP8266 and Setup as Server
printf(buff); // If start successful buff contains IP address...
// if not if contains an error.
led =1;
/***********************************************************************************/
/***********************************************************************************/
/************Initialize the PID*****************************************************/
/***********************************************************************************/
// Working paramater variables
setpoint = setpoint_init;
encoder.reset();
feedback = encoder.read();
float kp = kp_init;
float ki = ki_init;
float kd = kd_init;
pid.set_parameters(kp, ki, kd, Ts_init);
// Init the motor
mtr_dir = 0; // Can be 0 or 1, sets the direction
mtr_pwm = 0.0;
// Clear encoder count
encoder.reset();
// Update sensors and feedback twice as fast as PID sample time
// this makes pid react in real-time avoiding errors due to
// missing counts etc.
motor.attach(&pid_callback, Ts_init/2.0);
// Start PID sampling
pid.start();
/***********************************************************************************/
/***********************************************************************************/
while(1){
/**************SERVICE WEBPAGE******************************************************/
/***********************************************************************************/
if(esp.readable()){
getreply(500, buff, sizeof(buff), sizeof(buff) -1); // Get full buff, leave last element for null char
// printf("\r\n*************WORKING BUFFER******************************\r\n");
printf(buff); printf("\n");
// printf("\r\n**************END WORKING BUFFER**************************\r\n");
// If Recieved Data get ID, Length, and Data
char* rqstPnt = strstr(buff, "+IPD");
if(rqstPnt != NULL){
int id, len;
char type[10]; memset(type, '\0', sizeof(type)); // Create and null out data buff
sscanf(rqstPnt, "+IPD,%d,%d:%s ", &id, &len, type);
// printf("ID: %i\nLen: %i\nType: %s\n", id, len, type);
// If GET or POST request "type" parse and update user input then send webpage
if(strstr(type, "GET") != NULL || strstr(type, "POST") != NULL){
// printf("I got web request\n");
/* Read Webpage <Form> data sent using "method=POST"...
Note: Input elements in the <Form> need a set name attribute to
appear in the returned HTML body. Thus to "POST" data ensure:
<Form method="POST"> <input type="xxx" name="xxx" value="xxx">
<input type="xxx" value="xxx"> </Form>
Only the input with name="xxx" will appear in body of HTML
*/
printf("\r\n*************USER INPUT**********************************\r\n");
parse_input(buff, &setpoint, &kp, &ki, &kd);
setpoint = clip(setpoint, -999.99, 999.99);
kp = clip(kp, 0.00, 999.99);
ki = clip(ki, 0.00, 999.99);
kd = clip(kd, 0.00, 999.99);
printf("User Entered: \nSetpoint: %7.2f\nKp: %6.2f\nKi: %6.2f\nKd: %6.2f\n", setpoint, kp, ki, kd);
pid.set_parameters(kp, ki, kd, Ts_init); // Updata PID params
printf("Updated to Kp: %1.2f Ki: %1.2f Kd: %1.2f Ts: %1.2f\r\n",
pid.getKp(), pid.getKi(), pid.getKd(), pid.getTs());
printf("Setpoint: %1.2f\r\n", setpoint);
printf("Output: %1.2f\r\n", output);
printf("\r\n*************END USER INPUT******************************\r\n");
// Update Webpage to reflect new values POSTED by client
static bool isFirstRequest = true;
if(!isFirstRequest) update_webpage(webpage, setpoint, kp, ki, kd);
else isFirstRequest = false; // First Request just send page with initial values
printf(webpage); // DEBUGGING ONLY!!! REMOVE FOR RELEASE!!!
// Command TCP/IP Data Tx
esp.printf("AT+CIPSEND=%d,%d\r\n", id, strlen(webpage));
getreply(200, buff, sizeof(buff), 15); /*TODO: Wait for "OK\r\n>"*/
// printf(buff); printf("\n");
// Send webpage
// while(!esp.writeable()); // Wait until esp ready to send data
int idx = 0;
while(webpage[idx] != '\0'){
esp.putc(webpage[idx]);
idx++;
}
// Check status - Success: close channel and update PID controller, Error: reconnect
bool weberror = true;
t2.reset(); t2.start();
while(weberror ==1 && t2.read_ms() < 5000){
getreply(500, buff, sizeof(buff), 24);
if(strstr(buff, "SEND OK") != NULL) weberror = false;
}
if(weberror){
esp.printf("AT+CIPMUX=1\r\n");
getreply(500, buff, sizeof(buff), 10);
// printf(buff); printf("\n");
esp.printf("AT+CIPSERVER=1,%d\r\n", port);
getreply(500, buff, sizeof(buff), 10);
// printf(buff); printf("\n");
}
else{
esp.printf("AT+CIPCLOSE=%d\r\n", id); // Notice id is an int formatted to string
getreply(500, buff, sizeof(buff), 24);
// printf(buff); printf("\n");
}
}
}
}
/*********************************************************************/
/*********************************************************************/
}
}
// Initialize ESP8266
void init(char* buffer, int size){
// Hardware Reset ESP
espRstPin=0;
wait(0.5);
espRstPin=1;
// Get start up junk from ESP8266
getreply(6000, buffer, size, 500);
}
// Get Command and ESP status replies
void getreply(int timeout_ms, char* buffer, int size, int numBytes)
{
memset(buffer, '\0', size); // Null out buffer
t1.reset();
t1.start();
int idx = 0;
while(t1.read_ms()< timeout_ms && idx < numBytes) {
if(esp.readable()) {
buffer[idx] = esp.getc();
idx++;
}
}
t1.stop();
}
// Starts and restarts webserver if errors detected.
void startserver(char* buffer, int size)
{
esp.printf("AT+RST\r\n"); // BWW: Reset the ESP8266
getreply(8000, buffer, size, 1000);
if (strstr(buffer, "OK") != NULL) {
// BWW: Set ESP8266 for multiple connections
esp.printf("AT+CIPMUX=1\r\n");
getreply(500, buffer, size, 20);
// BWW: Set ESP8266 as Server on given port
esp.printf("AT+CIPSERVER=1,%d\r\n", port);
getreply(500, buffer, size, 20); // BWW: Wait for reply
// wait(1);
// BWW: Set ESP8266 Server Timeout
esp.printf("AT+CIPSTO=%d\r\n", serverTimeout_secs);
getreply(500, buffer, size, 50); // BWW: Wait for reply
// wait(5);
// BWW: Request IP Address from router for ESP8266
int weberror = 0;
while(weberror==0) {
esp.printf("AT+CIFSR\r\n");
getreply(2500, buffer, size, 200);
if(strstr(buffer, "0.0.0.0") == NULL) {
weberror=1; // wait for valid IP
}
}
}
// else ESP8266 did not reply "OK" something is messed up
else {
strcpy(buffer, "ESP8266 Error\n");
}
}
/*
update_webpage() updates output fields based on webpage user inputs "POSTED"
Preconditions: webpage[] must have the following elements
"kp_output" value="xxx.xx"
"ki_output" value="xxx.xx"
"kp_output" value="xxx.xx"
@param webpage Pointer to webpage char[]
@param kp New kp value posted by user
@param ki New ki value posted by user
@param kd New kd value posted by user
NOTE: THIS IS WEBPAGE SPECIFIC!!!! CHANGE THE CODE IN HERE TO SUITE THE
SPECIFIC APPLICATION WEBPAGE!!! ALSO USED TO REFLECT THE CUSTOM
IMPLEMENTATION OF THE parse_intput() function. MAKE SURE THESE TWO FUNCTIONS
INTEGRATE PROPERLY!!!
*/
void update_webpage(char* webpage, float setpoint, float kp, float ki, float kd){
// Change output value to reflect new setpoint kp, ki, kd values
char* begin;
// char* end;
char temp[8];
int idx;
memset(temp, '\0', sizeof(temp));
idx = 0;
begin = strstr(webpage, "name=\"kp_input\" value=\"") +
sizeof("name=\"kp_input\" value="); // Points to start of kp_output field
// end = begin + 5; // Points to end of kp_output value
// Determine precision of float such temp string has no empty spaces;
// i.e. each space must have a value or a decimal point, other wise webbrowser may not recognize value
if(kp >= 100) sprintf(temp, "%6.2f", kp); // xxx.00
else if(10 <= kp && kp < 100) sprintf(temp, "%6.3f", kp); // xx.000
else sprintf(temp, "%6.4f", kp); // x.0000
while(temp[idx] != '\0'){ // Overwrite old digits with new digits
begin[idx] = temp[idx];
idx++;
}
memset(temp, '\0', sizeof(temp));
idx = 0;
begin = strstr(webpage, "name=\"ki_input\" value=\"") +
sizeof("name=\"ki_input\" value="); // Points to start of ki_output field
// end = begin + 5; // Points to end of ki_output value
if(ki >= 100) sprintf(temp, "%6.2f", ki); // xxx.00
else if(10 <= ki && ki < 100) sprintf(temp, "%6.3f", ki); // xx.000
else sprintf(temp, "%6.4f", ki); // x.0000
while(temp[idx] != '\0'){ // Overwrite old digits with new digits
begin[idx] = temp[idx];
idx++;
}
memset(temp, '\0', sizeof(temp));
idx = 0;
begin = strstr(webpage, "name=\"kd_input\" value=\"")+
sizeof("name=\"kd_input\" value="); // Points to start of kd_output field
// end = begin + 5; // Points to end of kd_output value
if(kd >= 100) sprintf(temp, "%6.2f", kd); // xxx.00
else if(10 <= kd && kd < 100) sprintf(temp, "%6.3f", kd); // xx.000
else sprintf(temp, "%6.4f", kd); // x.0000
while(temp[idx] != '\0'){ // Overwrite old digits with new digits
begin[idx] = temp[idx];
idx++;
}
// Determine precision of float such temp string has no empty spaces;
// i.e. each space must have a value or a decimal point or neg sign,
// other wise webbrowser may not recognize value
memset(temp, '\0', sizeof(temp));
idx = 0;
begin = strstr(webpage, "name=\"setpoint_input\" value=\"")+
sizeof("name=\"setpoint_input\" value="); // Points to start of kp_output field
// end = begin + 6; // Points to end of kp_output value. +6 to accomadate negative sign
if(setpoint >= 0.00){
if(setpoint >= 100) sprintf(temp, "%6.3f", setpoint); // xxx.000
else if(10 <= setpoint && setpoint < 100) sprintf(temp, "%7.4f", setpoint); // xx.0000
else sprintf(temp, "%6.5f", setpoint); // x.00000
}
else{
if(setpoint <= -100) sprintf(temp, "%6.2f", setpoint); // -xxx.00
else if(-100 < setpoint && setpoint <= -10) sprintf(temp, "%6.3f", setpoint); // -xx.000
else sprintf(temp, "%6.4f", setpoint); // -x.0000
}
while(temp[idx] != '\0'){ // Overwrite old digits with new digits
begin[idx] = temp[idx];
idx++;
}
}
/*
parse_input() take a char*, in particular a pointer to Webpage User
Input Data, for example:
char str[] = "+IPD,0,44:kp_input=0.12&ki_input=14.25&kd_input=125.42";
and parses out the Setpoint Kp, Ki, Kd values that the user entered
and posted in the webpage. Values are converted to floats and
assigned to the given argurments.
NOTE: THIS IS WEBPAGE SPECIFIC!!!! CHANGE THE CODE IN HERE TO SUITE THE
SPECIFIC APPLICATION WEBPAGE!!! THESE EXTRACTED VALUES WILL BE USED IN
THE update_webpage() function. MAKE SURE THESE TWO FUNCTIONS INTEGRATE
PROPERLY!!!
*/
void parse_input(char* webpage_user_data,float *setpoint, float* kp, float* ki, float* kd){
char keys[] = {'&', '\0'};
// Parse out user input values
char input_buff[50];
char* begin;
char* end;
printf("**************Parsing**************\n");
memset(input_buff, '\0', sizeof(input_buff)); // Null out input buff
begin = strstr(webpage_user_data, "setpoint_input=") +
sizeof("setpoint_input"); // Points to start of setpoint_input value
end = begin + strcspn(begin, keys); // Points to end of setpoint_input value
for(long i = 0; i < end - begin; i++){ // Parse out the value one char at a time
input_buff[i] = begin[i];
}
printf("Setpoint Parsed Data: %s\n", input_buff);
*setpoint = atof(input_buff);
memset(input_buff, '\0', sizeof(input_buff)); // Null out input buff
begin = strstr(webpage_user_data, "kp_input=") +
sizeof("kp_input"); // Points to start of kp_input value
end = begin + strcspn(begin, keys); // Points to end of kp_input value
for(long i = 0; i < end - begin; i++){ // Parse out the value one char at a time
input_buff[i] = begin[i];
}
printf("Kp Parsed Data: %s\n", input_buff);
*kp = atof(input_buff);
memset(input_buff, '\0', sizeof(input_buff)); // Null out input buff
begin = strstr(webpage_user_data, "ki_input=") +
sizeof("ki_input"); // Points to start of ki_input value
end = begin + strcspn(begin, keys); // Points to end of ki_input value
for(long i = 0; i < end - begin; i++){ // Parse out the value one char at a time
input_buff[i] = begin[i];
}
printf("Ki Parsed Data: %s\n", input_buff);
*ki = atof(input_buff);
memset(input_buff, '\0', sizeof(input_buff)); // Null out input buff
begin = strstr(webpage_user_data, "kd_input=") +
sizeof("kd_input"); // Points to start of kd_input value
end = begin + strcspn(begin, keys); // Points to end of kd_input value
for(long i = 0; i < end - begin; i++){ // Parse out the value one char at a time
input_buff[i] = begin[i];
}
printf("Kd Parsed Data: %s\n", input_buff);
*kd = atof(input_buff);
printf("**********End Parsing***************\n");
}
void pid_callback(){
// Update motor
if(output >= 0.0) mtr_dir = 1; // Set direction to sign of output
else mtr_dir = 0;
mtr_pwm = abs(output); // Apply motor output
// Update feedback
feedback = encoder.read()*FEEDBACK_SCALE;// Scale feedback to num wheel revs
}
/*
Clips value to lower/ uppper
@param value The value to clip
@param lower The mininum allowable value
@param upper The maximum allowable value
@return The resulting clipped value
*/
float clip(float value, float lower, float upper){
return std::max(lower, std::min(value, upper));
}
/**************************WEB PAGE TEXT**************************************/
/*****************************************************************************
Copy and past text below into a html file and save as the given file name to
your SD card.
file name: pid_pos.html
html text:
<!DOCTYPE html>
<html>
<head>
<title>PID Motor Position Control</title>
</head>
<body>
<h1>PID Motor Position Control</h1>
<h2>Motor Status</h2>
<p>
<form title="Motor Status">
<input type="text" value="Some user information" size="25" readonly /><br>
Current Setpoint:
<input type="number" name="current_setpoint" value="0000.00" readonly /><br>
Current Position:
<input type="number" name="current_position" value="0000.00" readonly /><br>
</form>
</p>
<h2>PID Status</h2>
<form title="User Input" method="post">
PID Controls: <br>
Setpoint:
<input type="number" name="setpoint_input" value="0000.00" step="0.01" size="6" /><br>
Proportional Gain:
<input type="number" name="kp_input" value="002.50" step="0.01" size="6" /><br>
Integral Gain:
<input type="number" name="ki_input" value="005.00" step="0.01" size="6" /><br>
Derivative Gain:
<input type="number" name="kd_input" value="000.25" step="0.01" size="6" /><br>
<br>
<input type="submit" value="Submit" />
<input type="submit" name="update" value="Update">
<input type="submit" name="estop" value="STOP">
</form>
</body>
</html>
*****************************************************************************/
/*****************************************************************************/