From: Justin Kruger <20767264@student.uwa.edu.au> Date: Fri, 13 Sep 2013 14:56:40 +0000 (+0800) Subject: Cleaned up Beaglebone sensors/actuators/PWM code X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=19050f4340b649460080c5a3c4cd2e3a2de22ecc;hp=--cc;p=matches%2FMCTX3420.git Cleaned up Beaglebone sensors/actuators/PWM code Moved some tasks into separate methods --- 19050f4340b649460080c5a3c4cd2e3a2de22ecc diff --git a/BBB code/ActuatorHandler_real.c b/BBB code/ActuatorHandler_real.c deleted file mode 100644 index 783bd41..0000000 --- a/BBB code/ActuatorHandler_real.c +++ /dev/null @@ -1,60 +0,0 @@ -void ActuatorHandler(FCGIContext *context, ActuatorId id, const char *set_value) { - char *ptr; - - switch(id) { //Add new actuators here - case ACT_PRESSURE: //Suppose is pressure regulator. 0-700 input (kPa) - //requires PWM control - //TBA, currently in a separate file - //pwm_convert_duty(value); - convert input to required duty cycle - //pwm_set_frequency(PWM_id, freq); - can be done during PWM setup, frequency won't change (50Hz?) - //pwm_set_duty_cycle(PWM_id, duty_cycle); - this is the low/high percentage, will vary with input - //may also need to set polarity? - //if pwm is not setup, run setup functions first - { - int value = strtol(set_value, &ptr, 10); - if (*ptr == '\0' && value >= 0 && value <= 700) { - FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONKey("description"); - FCGI_JSONValue("\"Set pressure to %d kPa!\"", value); - FCGI_EndJSON(); - } else { - FCGI_RejectJSONEx(context, - STATUS_ERROR, "Invalid pressure specified."); - } - } break; - case ACT_SOLENOID1: - //requires digital control - { - int value = strtol(set_value, &ptr, 10); - if (*ptr == '\0') { - //code to set pin to value - //move this to separate function, will need to be repeated - int fd; - char buffer[10]; - if ((fd = open("/sys/class/gpio/gpio50/value", O_WRONLY)) < 0) //randomly chose pin 50 (each pin can be mapped to a certain switch case as required) - perror("Failed to open pin"); - if (value) { - strncpy(buffer, "1", ARRAY_SIZE(buffer) - 1); //copy value to buffer - } else { - strncpy(buffer, "0", ARRAY_SIZE(buffer) - 1); - } - int write = write(fd, buffer, strlen(buffer)); //write buffer to pin - if (write < 0) perror("Failed to write to pin"); - close(fd); - //code for server - const char *state = "off"; - if (value) - state = "on"; - FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONKey("description"); - FCGI_JSONValue("\"Solenoid 1 turned %s!\"", state); - FCGI_EndJSON(); - } else { - FCGI_RejectJSON(context, "Invalid actuator value specified"); - } - } break; - default: - FCGI_RejectJSONEx(context, - STATUS_ERROR, "Invalid actuator id specified."); - } -} \ No newline at end of file diff --git a/BBB code/Actuator_SetValue_real.c b/BBB code/Actuator_SetValue_real.c new file mode 100644 index 0000000..e83020a --- /dev/null +++ b/BBB code/Actuator_SetValue_real.c @@ -0,0 +1,68 @@ +#include "pwm.h" + +char pin_dir = "/sys/class/gpio/gpio"; //move these +int pwm_active = 0; + +/** Sets a GPIO pin to the desired value +* @param value - the value (1 or 0) to write to the pin +* @param pin_num - the number of the pin (refer to electronics team) +*/ + +void SetPin(int value, int pin_num) { + int pin; + char buffer[10]; + char pin_path[40]; + snprintf(pin_path, sizeof(pin_path), "%s%d%s", pin_dir, pin_num, "/value"); //create pin path + pin = open(pin_path, O_WRONLY); + if (pin < 0) perror("Failed to open pin"); + if (value) { + strncpy(buffer, "1", ARRAY_SIZE(buffer) - 1); + } + else { + strncpy(buffer, "0", ARRAY_SIZE(buffer) - 1); + } + int write = write(pin, buffer, strlen(buffer)); + if (write < 0) perror ("Failed to write to pin"); + close(pin); +} + +//TODO: Be able to write to multiple PWM modules - easy to change +// but current unsure about how many PWM signals we need + +/** + * Set an Actuator value + * @param a - The Actuator + * @param value - The value to set + */ +void Actuator_SetValue(Actuator * a, double value) +{ + // Set time stamp + struct timeval t; + gettimeofday(&t, NULL); + + DataPoint d = {TIMEVAL_DIFF(t, g_options.start_time), value}; + switch (a->id) + { + case ACTUATOR_TEST0: //Pressure regulator example - requires PWM, 0-1000kPa input + { + if (pwm_active == 0) { //if inactive, start the pwm module + pwm_init(); + pwm_start(); + pwm_set_period(FREQ); //50Hz defined in pwm header file + } + if(value >= 0 && value <= 700) { + double duty = value/1000 * 100; //convert pressure to duty percentage + pwm_set_duty((int)duty); //set duty percentage for actuator + } + } break; + case ACTUATOR_TEST1: + { + SetPin(value, 1); //Digital switch example - "1" is the pin number, to be specified by electronics team + } break; + } + + Log(LOGDEBUG, "Actuator %s set to %f", g_actuator_names[a->id], value); + + // Record the value + Data_Save(&(a->data_file), &d, 1); +} \ No newline at end of file diff --git a/BBB code/GetData_real.c b/BBB code/GetData_real.c index 2192772..f52427c 100644 --- a/BBB code/GetData_real.c +++ b/BBB code/GetData_real.c @@ -1,93 +1,121 @@ -DataPoint * GetData(int sensor_id, DataPoint * d) -{ - //TODO: We should ensure the time is *never* allowed to change on the server if we use gettimeofday - // Another way people might think of getting the time is to count CPU cycles with clock() - // But this will not work because a) CPU clock speed may change on some devices (RPi?) and b) It counts cycles used by all threads - gettimeofday(&(d->time_stamp), NULL); +char adc_dir = "/sys/devices/platform/tsc/ain"; +char pin_dir = "/sys/class/gpio/gpio"; + +/** Open an ADC and return the voltage value from it +* @param adc_num - ADC number, ranges from 0 to 7 on the Beaglebone + @return the converted voltage value if successful +*/ + +//TODO: create a function to lookup the ADC or pin number instead of manually +// specifying it here (so we can keep all the numbers in one place) + +int OpenAnalog(int adc_num) +{ + char adc_path[40]; + snprintf(adc_path, sizeof(adc_path), "%s%d", adc_dir, adc_num); //construct ADC path + int sensor = open(adc_path, O_RDONLY); + char buffer[128]; //I think ADCs are only 12 bits (0-4096) + int read = read(sensor, buffer, sizeof(buffer); //buffer can probably be smaller + if (read != -1) { + buffer[read] = NULL; + int value = atoi(buffer); + double convert = (value/4096) * 1000; //random conversion factor, will be different for each sensor + //lseek(sensor, 0, 0); + close(sensor); + return convert; + } + else { + perror("Failed to get value from sensor"); + close(sensor); + return -1; + } +} + +/** Open a digital pin and return the value from it +* @param pin_num - pin number, specified by electronics team + @return 1 or 0 if reading was successful +*/ + +int OpenDigital(int pin_num) +{ + char pin_path[40]; + snprintf(pin_path, sizeof(pin_path), "%s%d%s", pin_dir, pin_num, "/value"); //construct pin path + int pin = open(pin_path, O_RDONLY); + char ch; + lseek(fd, 0, SEEK_SET); + int read = read(pin, &ch, sizeof(ch); + if (read != -1) { + if (ch != '0') { + close(pin); + return 1; + } + else { + close(pin); + return 0; + } + else { + perror("Failed to get value from pin"); + close(pin); + return -1; + } +} + +/** + * Read a DataPoint from a Sensor; block until value is read + * @param id - The ID of the sensor + * @param d - DataPoint to set + * @returns True if the DataPoint was different from the most recently recorded. + */ +bool Sensor_Read(Sensor * s, DataPoint * d) +{ - switch (sensor_id) + // Set time stamp + struct timeval t; + gettimeofday(&t, NULL); + d->time_stamp = TIMEVAL_DIFF(t, g_options.start_time); + + // Read value based on Sensor Id + switch (s->id) { - //TODO: test buffer size on actual hardware - // maybe map the sensor path to an array/structure that can be looked up via sensor_id? - // not sure about the best place to do unit conversions, especially for nonlinear sensors - case SENSOR_TEST0: + case ANALOG_TEST0: + d->value = OpenAnalog(0); //ADC #0 on the Beaglebone + break; + case ANALOG_TEST1: { - int sensor = open("/sys/devices/platform/tsc/ain0", O_RDONLY); //need unique path for each sensor ADC, check path in documentation - char buffer[128]; //ADCs on Beaglebone are 12 bits? - int read = read(sensor, buffer, sizeof(buffer); - if (read != -1) { - buffer[read] = NULL; //string returned by read is not null terminated - int value = atoi(buffer); - double convert = (value/4096) * 1800; //sample conversion from ADC input to 'true value' - d->value = convert; - lseek(sensor, 0, 0); //after read string must be rewound to start of file using lseek - } - else { - perror("Failed to get value from sensor"); - } - close(sensor); + d->value = OpenAnalog(1); break; } - case SENSOR_TEST1: - int sensor = open("/sys/devices/platform/tsc/ain1", O_RDONLY); - char buffer[128]; - int read = read(sensor, buffer, sizeof(buffer); - if (read != -1) { - buffer[read] = NULL; - int value = atoi(buffer); - double convert = (value/4096) * 1800; - d->value = convert; - lseek(sensor, 0, 0); - } - else { - perror("Failed to get value from sensor"); - } - close(sensor); + case ANALOG_FAIL0: + d->value = (double)(rand() % 6) * -( rand() % 2) / ( rand() % 100 + 1); + //Gives a value between -5 and 5 break; - break; - } - //TODO: think about a better way to address individual pins - // i.e. pass an int to a separate function that builds the correct filename - // doesn't really matter if the pins we're using are fixed though case DIGITAL_TEST0: - int fd = open("/sys/class/gpio/gpio17/value", O_RDONLY) //again need the right addresses for each pin - char ch; //just one character for binary info - lseek(fd, 0, SEEK_SET); - int read = read(fd, &ch, sizeof(ch); - if (read != -1) { - if (ch != '0') { - d->value = 1; - } - else { - d->value = 0; - } - } - else { - perror("Failed to get value from pin"); - } + d->value = openDigital(0); //replace 0 with correct pin number break; case DIGITAL_TEST1: - int fd = open("/sys/class/gpio/gpio23/value", O_RDONLY) - char ch; - lseek(fd, 0, SEEK_SET); - int read = read(fd, &ch, sizeof(ch); - if (read != -1) { - if (ch != '0') { - d->value = 1; - } - else { - d->value = 0; - } - } - else { - perror("Failed to get value from pin"); - } + d->value = openDigital(1); //replace 1 with correct pin number + break; + case DIGITAL_FAIL0: + if( rand() % 100 > 98) + d->value = 2; + d->value = rand() % 2; + //Gives 0 or 1 or a 2 every 1/100 times break; default: - Fatal("Unknown sensor id: %d", sensor_id); + Fatal("Unknown sensor id: %d", s->id); break; } usleep(100000); // simulate delay in sensor polling - return d; + // Perform sanity check based on Sensor's ID and the DataPoint + Sensor_CheckData(s->id, d->value); + + // Update latest DataPoint if necessary + bool result = (d->value != s->newest_data.value); + if (result) + { + s->newest_data.time_stamp = d->time_stamp; + s->newest_data.value = d->value; + } + return result; } \ No newline at end of file diff --git a/BBB code/PWM_real/pwm.c b/BBB code/PWM_real/pwm.c deleted file mode 100644 index b58873e..0000000 --- a/BBB code/PWM_real/pwm.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "pwm.h" - -/* Initialize PWM : -1/ mmap /dev/mem to have write access to system clock -2/ enable system clock (0x0 = disabled, 0x02 = enabled) -3/ set correct pin MUX - -can modify pwm variables through virtual filesystem: -"/sys/class/pwm/ehrpwm.1:0/..." - -pwm drivers reference: -http://processors.wiki.ti.com/index.php/AM335x_PWM_Driver%27s_Guide */ - -void pwm_init(void) -{ - FILE *pwm_mux; - int i; - volatile uint32_t *epwmss1; - - int fd = open("/dev/mem", O_RDWR); - - if(fd < 0) - { - printf("Can't open /dev/mem\n"); - exit(1); - } - - epwmss1 = (volatile uint32_t *) mmap(NULL, LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, START); - if(epwmss1 == NULL) - { - printf("Can't mmap\n"); - exit(1); - } - else - { - epwmss1[OFFSET_1 / sizeof(uint32_t)] = 0x2; - } - close(fd); - - pwm_mux = fopen("/sys/kernel/debug/omap_mux/gpmc_a2", "w"); - fprintf(pwm_mux, "6"); // pwm is mux mode 6 - fclose(pwm_mux); -} - -//can change filepath of pwm module "/ehrpwm.%d:0/" by passing %d as argument -//depends how many pwm modules we have to run -//TODO: - -void pwm_start(void) -{ - FILE *pwm_run; - pwm_run = fopen("/sys/class/pwm/ehrpwm.1:0/run", "w"); - fprintf(pwm_run, "1"); - fclose(pwm_run); -} - -void pwm_stop(void) -{ - FILE *pwm_run; - - pwm_run = fopen("/sys/class/pwm/ehrpwm.1:0/run", "w"); - fprintf(pwm_run, "0"); - fclose(pwm_run); -} - -//duty_percent is just a regular percentage (i.e. 50 = 50%) - -void pwm_set_duty(int duty_percent) -{ - FILE *pwm_duty; - - pwm_duty = fopen("/sys/class/pwm/ehrpwm.1:0/duty_percent", "w"); - fprintf(pwm_duty, "%d", duty_percent); - fclose(pwm_duty); -} - -//freq is just normal frequency (i.e. 100 = 100Hz) - -void pwm_set_period(int freq) -{ - FILE *pwm_period; - - pwm_period = fopen("/sys/class/pwm/ehrpwm.1:0/period_freq", "w"); - fprintf(pwm_period, "%d", freq); - fclose(pwm_period); -} \ No newline at end of file diff --git a/BBB code/PWM_real/pwm.h b/BBB code/PWM_real/pwm.h deleted file mode 100644 index 9c95373..0000000 --- a/BBB code/PWM_real/pwm.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define START 0x44e00000 // see datasheet of AM335x -#define LENGTH 1024 -#define OFFSET_1 0xcc // offset of PWM1 clock (see datasheet of AM335x p.1018) - -void pwm_init(void); -void pwm_start(void); -void pwm_stop(void); -void pwm_set_duty(int duty_percent); -void pwm_set_period(int freq); \ No newline at end of file diff --git a/BBB code/old/ActuatorHandler_real.c b/BBB code/old/ActuatorHandler_real.c new file mode 100644 index 0000000..7e7e0ec --- /dev/null +++ b/BBB code/old/ActuatorHandler_real.c @@ -0,0 +1,82 @@ +#include "pwm.h" + +char pin_dir = "/sys/class/gpio/gpio"; +int pwm_active = 0; + +/** Sets a GPIO pin to the desired value +* @param value - the value (1 or 0) to write to the pin +* @param pin_num - the number of the pin (refer to electronics team) +*/ + +void SetPin(int value, int pin_num) { + int pin; + char buffer[10]; + char pin_path[40]; + snprintf(pin_path, sizeof(pin_path), "%s%d%s", pin_dir, pin_num, "/value"); //create pin path + pin = open(pin_path, O_WRONLY); + if (pin < 0) perror("Failed to open pin"); + if (value) { + strncpy(buffer, "1", ARRAY_SIZE(buffer) - 1); + } + else { + strncpy(buffer, "0", ARRAY_SIZE(buffer) - 1); + } + int write = write(pin, buffer, strlen(buffer)); + if (write < 0) perror ("Failed to write to pin"); + close(pin); +} + +//TODO: Be able to write to multiple PWM modules - easy to change +// but current unsure about how many PWM signals we need + +void ActuatorHandler(FCGIContext *context, ActuatorId id, const char *set_value) { + char *ptr; + + switch(id) { //Add new actuators here + case ACT_PRESSURE: //Suppose is pressure regulator. 0-700 input (kPa) + { + if (pwm_active == 0) { //if inactive, start the pwm module + pwm_init(); + pwm_start(); + pwm_set_period(FREQ); //50Hz defined in pwm header file + } + int value = strtol(set_value, &ptr, 10); + //Beaglebone code + if(value >= 0 && value <= 700) { + double duty = value/700 * 100; //convert pressure to duty percentage + pwm_set_duty((int)duty); //set duty percentage for actuator + } + //server code + if (*ptr == '\0' && value >= 0 && value <= 700) { + FCGI_BeginJSON(context, STATUS_OK); + FCGI_JSONKey("description"); + FCGI_JSONValue("\"Set pressure to %d kPa!\"", value); + FCGI_EndJSON(); + } else { + FCGI_RejectJSONEx(context, + STATUS_ERROR, "Invalid pressure specified."); + } + } break; + case ACT_SOLENOID1: + { + int value = strtol(set_value, &ptr, 10); + if (*ptr == '\0') { + //code to set pin to value + SetPin(value, 1); //"1" will need to be changed to pin numbers decided by electronics team + //code for server + const char *state = "off"; + if (value) + state = "on"; + FCGI_BeginJSON(context, STATUS_OK); + FCGI_JSONKey("description"); + FCGI_JSONValue("\"Solenoid 1 turned %s!\"", state); + FCGI_EndJSON(); + } else { + FCGI_RejectJSON(context, "Invalid actuator value specified"); + } + } break; + default: + FCGI_RejectJSONEx(context, + STATUS_ERROR, "Invalid actuator id specified."); + } +} \ No newline at end of file diff --git a/BBB code/pwm.c b/BBB code/pwm.c new file mode 100644 index 0000000..b58873e --- /dev/null +++ b/BBB code/pwm.c @@ -0,0 +1,86 @@ +#include "pwm.h" + +/* Initialize PWM : +1/ mmap /dev/mem to have write access to system clock +2/ enable system clock (0x0 = disabled, 0x02 = enabled) +3/ set correct pin MUX + +can modify pwm variables through virtual filesystem: +"/sys/class/pwm/ehrpwm.1:0/..." + +pwm drivers reference: +http://processors.wiki.ti.com/index.php/AM335x_PWM_Driver%27s_Guide */ + +void pwm_init(void) +{ + FILE *pwm_mux; + int i; + volatile uint32_t *epwmss1; + + int fd = open("/dev/mem", O_RDWR); + + if(fd < 0) + { + printf("Can't open /dev/mem\n"); + exit(1); + } + + epwmss1 = (volatile uint32_t *) mmap(NULL, LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, START); + if(epwmss1 == NULL) + { + printf("Can't mmap\n"); + exit(1); + } + else + { + epwmss1[OFFSET_1 / sizeof(uint32_t)] = 0x2; + } + close(fd); + + pwm_mux = fopen("/sys/kernel/debug/omap_mux/gpmc_a2", "w"); + fprintf(pwm_mux, "6"); // pwm is mux mode 6 + fclose(pwm_mux); +} + +//can change filepath of pwm module "/ehrpwm.%d:0/" by passing %d as argument +//depends how many pwm modules we have to run +//TODO: + +void pwm_start(void) +{ + FILE *pwm_run; + pwm_run = fopen("/sys/class/pwm/ehrpwm.1:0/run", "w"); + fprintf(pwm_run, "1"); + fclose(pwm_run); +} + +void pwm_stop(void) +{ + FILE *pwm_run; + + pwm_run = fopen("/sys/class/pwm/ehrpwm.1:0/run", "w"); + fprintf(pwm_run, "0"); + fclose(pwm_run); +} + +//duty_percent is just a regular percentage (i.e. 50 = 50%) + +void pwm_set_duty(int duty_percent) +{ + FILE *pwm_duty; + + pwm_duty = fopen("/sys/class/pwm/ehrpwm.1:0/duty_percent", "w"); + fprintf(pwm_duty, "%d", duty_percent); + fclose(pwm_duty); +} + +//freq is just normal frequency (i.e. 100 = 100Hz) + +void pwm_set_period(int freq) +{ + FILE *pwm_period; + + pwm_period = fopen("/sys/class/pwm/ehrpwm.1:0/period_freq", "w"); + fprintf(pwm_period, "%d", freq); + fclose(pwm_period); +} \ No newline at end of file diff --git a/BBB code/pwm.h b/BBB code/pwm.h new file mode 100644 index 0000000..605095f --- /dev/null +++ b/BBB code/pwm.h @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define START 0x44e00000 // see datasheet of AM335x +#define LENGTH 1024 +#define OFFSET_1 0xcc // offset of PWM1 clock (see datasheet of AM335x p.1018) +#define FREQ 50 //50Hz pwm frequency for pressure regulator + +void pwm_init(void); +void pwm_start(void); +void pwm_stop(void); +void pwm_set_duty(int duty_percent); +void pwm_set_period(int freq); \ No newline at end of file diff --git a/server/actuator.c b/server/actuator.c index b3cb361..9c21eb7 100644 --- a/server/actuator.c +++ b/server/actuator.c @@ -147,7 +147,20 @@ void Actuator_SetValue(Actuator * a, double value) //TODO: Set actuator switch (a->id) { - case ACTUATOR_TEST0: + case ACTUATOR_TEST0: //LED actuator test code, should blink onboard LED next to Ethernet port + FILE *LEDHandle = NULL; //code reference: http://learnbuildshare.wordpress.com/2013/05/19/beaglebone-black-controlling-user-leds-using-c/ + char *LEDBrightness = "/sys/class/leds/beaglebone\:green\:usr0/brightness"; + if(value == 1) { + if((LEDHandle = fopen(LEDBrightness, "r+")) != NULL) { + fwrite("1", sizeof(char), 1, LEDHandle); + fclose(LEDHandle); + } + else if(value == 0) { + if((LEDHandle = fopen(LEDBrightness, "r+")) != NULL) { + fwrite("0", sizeof(char), 1, LEDHandle); + fclose(LEDHandle); + } + else perror("Pin value should be 1 or 0"); break; case ACTUATOR_TEST1: break;