Dual Brushless Motor ESC, 10-62V, up to 50A per motor. Motors ganged or independent, multiple control input methods, cycle-by-cycle current limit, speed mode and torque mode control. Motors tiny to kW. Speed limit and other parameters easily set in firmware. As used in 'The Brushless Brutalist' locomotive - www.jons-workshop.com. See also Model Engineer magazine June-October 2019.
Dependencies: mbed BufferedSerial Servo PCT2075 FastPWM
Update 17th August 2020 Radio control inputs completed
Revision 0:435bf84ce48a, committed 2018-03-01
- Comitter:
- JonFreeman
- Date:
- Thu Mar 01 11:29:28 2018 +0000
- Child:
- 1:0fabe6fdb55b
- Commit message:
- Tested so far on Nucleo F401RE.; About to experiment reading pressure sensor MPL3115A2 using library code;
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/24LC64_eeprom.cpp Thu Mar 01 11:29:28 2018 +0000
@@ -0,0 +1,109 @@
+#include "mbed.h"
+//#include "rtos.h"
+#include "BufferedSerial.h"
+extern BufferedSerial pc;
+extern I2C i2c;
+ // Code for 24LC64 eeprom
+ // Code based on earlier work using memory FM24W256, also at i2c address 0xa0;
+
+const int addr_rd = 0xa1; // set bit 0 for read, clear bit 0 for write
+const int addr_wr = 0xa0; // set bit 0 for read, clear bit 0 for write
+const int ACK = 1;
+
+bool ack_poll () { // wait short while for any previous memory operation to complete
+ const int poll_tries = 40;
+ int poll_count = 0;
+ bool i2cfree = false;
+ while (poll_count++ < poll_tries && !i2cfree) {
+ i2c.start ();
+ if (i2c.write(addr_wr) == ACK)
+ i2cfree = true;
+ else
+// Thread::wait(1); // 1ms
+ wait (1);
+ }
+// pc.printf ("ack_poll, count = %d, i2cfree = %s\r\n", poll_count, i2cfree ? "true" : "false");
+ return i2cfree;
+}
+
+bool set_24LC64_internal_address (int start_addr) {
+ if (!ack_poll())
+ {
+ pc.printf ("Err in set_24LC64_internal_address, no ACK writing device address byte\r\n");
+ i2c.stop();
+ return false;
+ }
+ int err = 0;
+ if (i2c.write(start_addr >> 8) != ACK) err++;
+ if (i2c.write(start_addr & 0xff) != ACK) err++;
+ if (err) {
+ pc.printf ("In set_24LC64_internal_address, Believe Device present, failed in writing 2 mem addr bytes %d\r\n", err);
+ i2c.stop();
+ return false;
+ }
+ return true;
+}
+
+bool wr_24LC64 (int start_addr, char * source, int length) {
+ int err = 0;
+ if(length < 1 || length > 32) {
+ pc.printf ("Length out of range %d in wr_24LC64\r\n", length);
+ return false;
+ }
+ if (!set_24LC64_internal_address (start_addr)) {
+ pc.printf ("In wr_24LC64, Believe Device present, failed in writing 2 mem addr bytes %d\r\n", err);
+ return false;
+ }
+ while(length--)
+ err += ACK - i2c.write(*source++);
+ i2c.stop();
+ if (err) {
+ pc.printf ("in wr_24LC64, device thought good, mem addr write worked, failed writing string\r\n");
+ return false;
+ }
+ pc.printf ("In wr_24LC64 No Errors Found!\r\n");
+ return true;
+}
+
+bool rd_24LC64 (int start_addr, char * dest, int length) {
+ int acknak = ACK;
+ if(length < 1)
+ return false;
+ if (!set_24LC64_internal_address (start_addr)) {
+ pc.printf ("In rd_24LC64, failed to set_ramaddr\r\n");
+ return false;
+ }
+ i2c.start();
+ if (i2c.write(addr_rd) != ACK) {
+ pc.printf ("Errors in rd_24LC64 sending addr_rd\r\n");
+ return false;
+ }
+ while(length--) {
+ if(length == 0)
+ acknak = 0;
+ *dest++ = i2c.read(acknak);
+ }
+ i2c.stop();
+ return true;
+}
+
+int check_24LC64 () { // Call from near top of main() to init i2c bus
+ i2c.frequency(400000); // Speed 400000 max.
+ int last_found = 0, q; // Note address bits 3-1 to match addr pins on device
+ for (int i = 0; i < 255; i += 2) { // Search for devices at all possible i2c addresses
+ i2c.start();
+ q = i2c.write(i); // may return error code 2 when no start issued
+ switch (q) {
+ case ACK:
+ pc.printf ("I2C device found at 0x%x\r\n", i);
+ last_found = i;
+ case 2: // Device not seen at this address
+ break;
+ default:
+ pc.printf ("Unknown error %d in check_24LC64\r\n", q);
+ break;
+ }
+ }
+ i2c.stop();
+ return last_found;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BufferedSerial.lib Thu Mar 01 11:29:28 2018 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/sam_grove/code/BufferedSerial/#a0d37088b405
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DualBLS.h Thu Mar 01 11:29:28 2018 +0000 @@ -0,0 +1,2 @@ +#define POWER_OF_TWO 12 // Range is 4 to 13, is log2N +typedef float ffty; // Choice of float or double float is HUGELY FASTER than double
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/FastPWM.lib Thu Mar 01 11:29:28 2018 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/Sissors/code/FastPWM/#c0b2265cff9c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MPL3115A2b.lib Thu Mar 01 11:29:28 2018 +0000 @@ -0,0 +1,1 @@ +https://os.mbed.com/teams/MSS/code/MPL3115A2/#12223b4c88b1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cli_nortos.cpp Thu Mar 01 11:29:28 2018 +0000
@@ -0,0 +1,245 @@
+#include "mbed.h"
+#include "BufferedSerial.h"
+#include <cctype>
+using namespace std;
+
+typedef double fl_typ; //
+
+const int MAX_PARAMS = 10;
+struct parameters {
+// int32_t times[numof_menu_items];
+ int32_t times[50];
+ int32_t position_in_list, last_time, numof_dbls;
+ double dbl[MAX_PARAMS];
+} ;
+
+// WithOUT RTOS
+extern BufferedSerial xb, pc;
+//extern BufferedSerial bt;
+//extern void set_I_limit (double p) ; // Sets max motor current
+//extern void set_V_limit (double p) ; // Sets max motor voltage
+extern void send_test () ;
+//extern void assemble_rx_frame () ;
+//extern void read_pulses (uint32_t * ) ;
+//extern void apply_brake (double b) ;
+//extern uint32_t Watch_Dog ;
+
+void null_cmd (struct parameters & a) {
+ pc.printf ("At null_cmd, parameters : First %.3f, second %.3f\r\n", a.dbl[0], a.dbl[1]);
+}
+
+void rd_cmd (struct parameters & a) { // Reading Hall pulse totals and clock() from bogie
+ uint32_t rd[2];
+ char t[36];
+// read_pulses (rd);
+ sprintf (t, "P0=%d, P1=%d, clock=%d\n", rd[0], rd[1], clock());
+ pc.printf (t);
+}
+
+void fw_cmd (struct parameters & a) {
+ pc.printf ("Forward : First %d, second %d\r\n", a.dbl[0], a.dbl[1]);
+}
+
+void re_cmd (struct parameters & a) {
+ pc.printf ("Reverse : First %d, second %d\r\n", a.dbl[0], a.dbl[1]);
+}
+
+void rb_cmd (struct parameters & a) { // Regen brake
+ double b = a.dbl[0] / 999.0;
+ pc.printf ("Applying brake %.3f\r\n", b);
+// apply_brake (b);
+}
+
+void drive_cmd (struct parameters & a) { // Drive
+ double drive = a.dbl[0] / 999.0;
+
+}
+void coast_cmd (struct parameters & a) { // Coast
+
+}
+void hb_cmd (struct parameters & a) {
+ pc.printf ("numof params = %d\r\n", a.numof_dbls);
+ pc.printf ("Hand Brake : First %.3f, second %.3f\r\n", a.dbl[0], a.dbl[1]);
+}
+
+//void wd_cmd (struct parameters & a) {
+// pc.printf ("Reset Watch Dog timer, was %d\r\n", Watch_Dog);
+// Watch_Dog = 0;
+//}
+
+/*void hall_cmd (struct parameters * a) { // Report back pulse totals from Hall sensors for both motors
+ char buff[30];
+ sprintf (buff, "Halls 0x%08lx 0x%08lx\r\n", Halls_A_accum, Halls_B_accum); // doesn't need to be hex
+ pc.printf (buff);
+
+ Halls_A_accum += 1369;
+ Halls_B_accum += 11369;
+}*/
+
+/*void setpwm_cmd (struct parameters * a) {
+ double pw = ((double)a[1].i) / 1000.0;
+ pc.printf ("Setting Volt limit %.3f, %d\r\n", pw, a[1].i);
+ set_V_limit (pw);
+}
+
+void setvref_cmd (struct parameters * a) {
+ double pw = ((double)a[1].i) / 1000.0;
+ pc.printf ("Setting Current limit %.3f, %d\r\n", pw, a[1].i);
+ set_I_limit (pw);
+}*/
+
+/*
+void setmotpwm_cmd (struct parameters * a) {
+ //a[1].i // first param
+ pc.printf ("First %d, second %d\r\n", a[1].i, a[2].i);
+// set_motor_pwm (a[1].i, a[2].i);
+}
+//extern void set_fwd_rev (int direction) ;
+void set_fwd_cmd (struct parameters * a) {
+// set_fwd_rev (FWD);
+}
+void set_rev_cmd (struct parameters * a) {
+// set_fwd_rev (REV);
+}
+
+void set_speed_cmd (struct parameters * a) {
+ pc.printf ("Speed entered %d\r\n", a[1].i);
+}
+
+void read_current_cmd (struct parameters * a) {
+ pc.printf ("Read current\r\n");
+}
+*/
+void menucmd (struct parameters & a);
+
+//void xb_cmd (struct parameters & a)
+//{
+// send_test();
+//}
+
+extern void set_api_mode (bool mode) ;
+void xm_cmd (struct parameters & a)
+{
+/* if (a[1].i == 2)
+ set_api_mode (true);
+ else
+ set_api_mode (false);
+ pc.printf ("New API_MODE %d\r\n", a[1].i);
+*/
+}
+
+void at_cmd (struct parameters & a)
+{
+ xb.printf ("AT\r");
+// pc.printf ("AT %d\r\n", a[1].i);
+}
+
+struct kb_command {
+ const char * cmd_word; // points to text e.g. "menu"
+ const char * explan;
+ void (*f)(struct parameters &); // points to function
+} ;
+
+struct kb_command const command_list[] = {
+ {"menu", "Lists available commands, same as ls", menucmd},
+ {"ls", "Lists available commands, same as menu", menucmd},
+// {"sv", "set Volts pwm 0 to 999", setpwm_cmd},
+// {"si", "set Amps pwm 0 to 999", setvref_cmd},
+// {"ha", "read Hall pulse totals", hall_cmd},
+ {"fw", "forward", fw_cmd},
+ {"re", "reverse", re_cmd},
+ {"rb", "regen brake", rb_cmd},
+ {"dr", "drive", drive_cmd},
+ {"co", "coast", coast_cmd},
+ {"hb", "hand brake", hb_cmd},
+ {"at", "AT", at_cmd},
+ {"xm", "set api mode, 1 or 2", xm_cmd},
+ {"nu", "do nothing", null_cmd},
+
+// {"rev", "set motors in tother direction", set_rev_cmd},
+// {"s", "set speed", set_speed_cmd},
+// {"i", "Read motor currents", read_current_cmd},
+};
+
+const int numof_menu_items = sizeof(command_list) / sizeof(kb_command);
+void menucmd (struct parameters & a)
+{
+ pc.printf("\r\n\nDouble Brushless Motor Driver 2018\r\nAt menucmd function - listing commands:-\r\n");
+ for(int i = 0; i < numof_menu_items; i++)
+ pc.printf("[%s]\t\t%s\r\n", command_list[i].cmd_word, command_list[i].explan);
+ pc.printf("End of List of Commands\r\n");
+}
+
+//void command_line_interpreter (void const *argument)
+void command_line_interpreter ()
+{
+const int MAX_CMD_LEN = 120;
+static char cmd_line[MAX_CMD_LEN + 4];
+static int cl_index = 0;
+int ch;
+char * pEnd;
+static struct parameters param_block ;
+// while (true) {
+// assemble_rx_frame () ; // check for anything coming from xbee
+// Watch_Dog++;
+/* while (bt.readable()) {
+ ch = bt.getc();
+// bt.putc(ch);
+ pc.printf ("%c", ch);
+ }*/
+ while (pc.readable()) {
+ ch = tolower(pc.getc());
+ // pc.printf("%c", ch);
+ if (cl_index > MAX_CMD_LEN) { // trap out stupidly long command lines
+ pc.printf ("Error!! Stupidly long cmd line\r\n");
+ cl_index = 0;
+ }
+ if (ch == '\r' || ch >= ' ' && ch <= 'z')
+ pc.printf("%c", ch);
+ else { // Using <Ctrl>+ 'F', 'B' for Y, 'L', 'R' for X, 'U', 'D' for Z
+ cl_index = 0; // 6 2 12 18 21 4
+ pc.printf("[%d]", ch);
+ //nudger (ch); // was used on cnc to nudge axes a tad
+ }
+ if(ch != '\r') // was this the 'Enter' key?
+ cmd_line[cl_index++] = ch; // added char to command being assembled
+ else { // key was CR, may or may not be command to lookup
+ cmd_line[cl_index] = 0; // null terminate command string
+ if(cl_index) { // If have got some chars to lookup
+ int i, wrdlen;
+ for (i = 0; i < numof_menu_items; i++) { // Look for input match in command list
+ wrdlen = strlen(command_list[i].cmd_word);
+ if(strncmp(command_list[i].cmd_word, cmd_line, wrdlen) == 0 && !isalpha(cmd_line[wrdlen])) { // If match found
+ for (int k = 0; k < MAX_PARAMS; k++) {
+ param_block.dbl[k] = 0.0;
+ }
+ param_block.position_in_list = i;
+ param_block.last_time = clock ();
+ param_block.numof_dbls = 0;
+ pEnd = cmd_line + wrdlen;
+ while (*pEnd) { // Assemble all numerics as doubles
+ param_block.dbl[param_block.numof_dbls++] = strtod (pEnd, &pEnd);
+ while (*pEnd && !isdigit(*pEnd) && '-' != *pEnd && '+' != *pEnd) {
+ pEnd++;
+ }
+ }
+ pc.printf ("\r\n");
+ for (int k = 0; k < param_block.numof_dbls; k++)
+ pc.printf ("Read %.3f\r\n", param_block.dbl[k]);
+ param_block.times[i] = clock();
+ command_list[i].f(param_block); // execute command
+ i = numof_menu_items + 1; // to exit for loop
+ } // end of match found
+ } // End of for numof_menu_items
+ if(i == numof_menu_items)
+ pc.printf("No Match Found for CMD [%s]\r\n", cmd_line);
+ } // End of If have got some chars to lookup
+ pc.printf("\r\n>");
+ cl_index = 0;
+ } // End of else key was CR, may or may not be command to lookup
+ } // End of while (pc.readable())
+// Thread::wait(20); // Using RTOS on this project
+// }
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp Thu Mar 01 11:29:28 2018 +0000
@@ -0,0 +1,714 @@
+#include "mbed.h"
+#include "DualBLS.h"
+#include "BufferedSerial.h"
+#include "FastPWM.h"
+#include "MPL3115A2.h"
+/* STM32F401RE - compile using NUCLEO-F401RE
+// PROJECT - Dual Brushless Motor Controller - March 2018.
+
+DigitalIn nFault1 (); needs pullup
+DigitalIn nFault2 (); needs pullup
+AnalogIn to read each motor current
+
+____________________________________________________________________________________
+ CONTROL PHILOSOPHY
+This Bogie drive board software should ensure sensible control when commands supplied are not sensible !
+
+That is, smooth transition between Drive, Coast and Brake to be implemented here.
+The remote controller must not be relied upon to deliver sensible command sequences.
+
+So much the better if the remote controller does issue sensible commands, but ...
+
+____________________________________________________________________________________
+
+
+*/
+
+// Hoped to select servo functions from user info stored on EEROM. Too difficult. Do not define servo as in and out
+#define SERVO1_IN
+//#define SERVO1_OUT
+//#define SERVO2_IN
+#define SERVO2_OUT
+
+
+// Port A -> MotorA, Port B -> MotorB
+const uint16_t
+AUL = 1 << 0, // Feb 2018 Now using DGD21032 mosfet drivers via 74HC00 pwm gates (low side)
+AVL = 1 << 6,
+AWL = 1 << 4,
+
+AUH = 1 << 1,
+AVH = 1 << 7,
+AWH = 1 << 8,
+
+AUV = AUH | AVL,
+AVU = AVH | AUL,
+AUW = AUH | AWL,
+AWU = AWH | AUL,
+AVW = AVH | AWL,
+AWV = AWH | AVL,
+
+BRA = AUL | AVL | AWL,
+
+BUL = 1 << 0,
+BVL = 1 << 1,
+BWL = 1 << 2,
+
+BUH = 1 << 10,
+BVH = 1 << 12,
+BWH = 1 << 13,
+
+BUV = BUH | BVL,
+BVU = BVH | BUL,
+BUW = BUH | BWL,
+BWU = BWH | BUL,
+BVW = BVH | BWL,
+BWV = BWH | BVL,
+
+BRB = BUL | BVL | BWL,
+
+PORT_A_MASK = AUL | AVL | AWL | AUH | AVH | AWH, // NEW METHOD FOR DGD21032 MOSFET DRIVERS
+PORT_B_MASK = BUL | BVL | BWL | BUH | BVH | BWH;
+
+PortOut MotA (PortA, PORT_A_MASK);
+PortOut MotB (PortB, PORT_B_MASK);
+
+// Pin 1 VBAT NET +3V3
+DigitalIn J3 (PC_13, PullUp);// Pin 2 Jumper pulls to GND, R floats Hi
+// Pin 3 PC14-OSC32_IN NET O32I
+// Pin 4 PC15-OSC32_OUT NET O32O
+// Pin 5 PH0-OSC_IN NET PH1
+// Pin 6 PH1-OSC_OUT NET PH1
+// Pin 7 NRST NET NRST
+AnalogIn Ain_DriverPot (PC_0); // Pin 8 Spare Analogue in, net SAIN fitted with external pull-down
+AnalogIn Ain_SystemVolts (PC_1); // Pin 9
+AnalogIn Motor_A_Current (PC_2); // Pin 10 might as well use up WSRA stock here
+AnalogIn Motor_B_Current (PC_3); // Pin 11
+// Pin 12 VSSA/VREF- NET GND
+// Pin 13 VDDA/VREF+ NET +3V3
+// Pin 14 Port_A AUL
+// Pin 15 Port_A AUH
+// Pins 16, 17 BufferedSerial pc
+BufferedSerial pc (PA_2, PA_3, 512, 4, NULL); // Pins 16, 17 tx, rx to pc via usb lead
+// Pin 18 VSS NET GND
+// Pin 19 VDD NET +3V3
+// Pin 20 Port_A AWL
+// Pin 21 DigitalOut led1(LED1);
+DigitalOut LED (PA_5); // Pin 21
+// Pin 22 Port_A AVL
+// Pin 23 Port_A AVH
+InterruptIn MBH2 (PC_4); // Pin 24
+InterruptIn MBH3 (PC_5); // Pin 25
+// Pin 26 Port_B BUL
+// Pin 27 Port_B BVL
+// Pin 28 Port_B BWL
+// Pin 29 Port_B BUH
+// Pin 30 VCAP1
+// Pin 31 VSS
+// Pin 32 VDD
+// Pin 33 Port_B BVH
+// Pin 34 Port_B BWH
+DigitalOut T4 (PB_14); // Pin 35
+DigitalOut T3 (PB_15); // Pin 36
+// BufferedSerial xb pins 37 Tx, 38 Rx
+BufferedSerial xb (PC_6, PC_7); // Pins 37, 38 tx, rx to XBee module
+FastPWM A_MAX_V_PWM (PC_8, 1), // Pin 39 pwm3/3
+ A_MAX_I_PWM (PC_9, 1); // pin 40, prescaler value pwm3/4
+//InterruptIn MotB_Hall (PA_8); // Pin 41
+// Pin 41 Port_A AWH
+// BufferedSerial ser3 pins 42 Tx, 43 Rx
+BufferedSerial ser3 (PA_9, PA_10); // Pins 42, 43 tx, rx to any aux module
+
+// Feb 2018 Pins 44 and 45 now liberated, could use for serial or other uses
+//BufferedSerial extra_ser (PA_11, PA_12); // Pins 44, 45 tx, rx to XBee module
+DigitalOut T2 (PA_11); // Pin 44
+DigitalOut T1 (PA_12); // Pin 45
+// Pin 46 SWDIO
+// Pin 47 VSS
+// Pin 48 VDD
+// Pin 49 SWCLK
+DigitalOut T5 (PA_15); // Pin 50
+InterruptIn MAH1 (PC_10); // Pin 51
+InterruptIn MAH2 (PC_11); // Pin 52
+InterruptIn MAH3 (PC_12); // Pin 53
+InterruptIn MBH1 (PD_2); // Pin 54
+DigitalOut T6 (PB_3); // Pin 55
+FastPWM B_MAX_V_PWM (PB_4, 1), // Pin 56 pwm3/3
+ B_MAX_I_PWM (PB_5, 1); // pin 57, prescaler value pwm3/4
+
+I2C i2c (PB_7, PB_6); // Pins 58, 59 For 24LC64 eeprom
+// Pin 60 BOOT0
+
+//MPL3115A2(PinName sda, PinName scl, int addr) ;
+//MPL3115A2 PressureSensor (PB_7, PB_6, 0xa0) ;
+//double PressureSensor.getPressure ();
+
+#ifdef SERVO1_IN
+InterruptIn Servo1_i (PB_8); // Pin 61 to read output from rc rx
+#endif
+#ifdef SERVO1_OUT
+FastPWM Servo1_o (PB_8, 2); //Prescaler 2. This is pwm 4/3
+#endif
+
+#ifdef SERVO2_IN
+InterruptIn Servo2_i (PB_9); // Pin 62 to read output from rc rx
+#endif
+#ifdef SERVO2_OUT
+FastPWM Servo2_o (PB_9, 2); // Prescaler 2. This is pwm 4/4
+#endif
+
+// Pin 63 VSS
+// Pin 64 VDD
+// SYSTEM CONSTANTS
+
+/* Please Do Not Alter these */
+const int VOLTAGE_READ_INTERVAL_US = 50, // Interrupts timed every 50 micro sec, runs around loop performing 1 A-D conversion per pass
+ MAIN_LOOP_REPEAT_TIME_US = 31250, // 31250 us, with TACHO_TAB_SIZE = 32 means tacho_ticks_per_time is tacho_ticks_per_second
+ MAIN_LOOP_ITERATION_Hz = 1000000 / MAIN_LOOP_REPEAT_TIME_US,
+ CURRENT_SAMPLES_AVERAGED = 100, // Current is spikey. Reading smoothed by using average of this many latest current readings
+ HANDBRAKE = 0,
+ FORWARD = 8,
+ REVERSE = 16,
+ REGENBRAKE = 24,
+ PWM_HZ = 16000, // chosen to be above cutoff frequency of average human ear
+ MAX_PWM_TICKS = SystemCoreClock / PWM_HZ;
+
+/* End of Please Do Not Alter these */
+
+/* Global variable declarations */
+uint32_t volt_reading = 0, // Global updated by interrupt driven read of Battery Volts
+ driverpot_reading = 0, // Global updated by interrupt driven read of Drivers Pot
+ sys_timer = 0, // gets incremented by our Ticker ISR every MAIN_LOOP_REPEAT_TIME_US
+ AtoD_Semaphore = 0;
+bool loop_flag = false; // made true in ISR_loop_timer, picked up and made false again in main programme loop
+bool flag_8Hz = false; // As loop_flag but repeats 8 times per sec
+bool sounder_on = false;
+double test_pot = 0.0, test_amps = 0.0; // These used in knifeandfork code testing only
+/* End of Global variable declarations */
+
+Ticker tick_vread; // Device to cause periodic interrupts, used to time voltage readings etc
+Ticker loop_timer; // Device to cause periodic interrupts, used to sync iterations of main programme loop
+
+// Interrupt Service Routines
+
+/** void ISR_loop_timer ()
+* This ISR responds to Ticker interrupts at a rate of (probably) 32 times per second (check from constant declarations above)
+* This ISR sets global flag 'loop_flag' used to synchronise passes around main programme control loop.
+* Increments global 'sys_timer', usable anywhere as general measure of elapsed time
+*/
+void ISR_loop_timer () // This is Ticker Interrupt Service Routine - loop timer - MAIN_LOOP_REPEAT_TIME_US
+{
+ loop_flag = true; // set flag to allow main programme loop to proceed
+ sys_timer++; // Just a handy measure of elapsed time for anything to use
+ if ((sys_timer & 0x03) == 0)
+ flag_8Hz = true;
+}
+
+/** void ISR_voltage_reader ()
+* This ISR responds to Ticker interrupts every 'VOLTAGE_READ_INTERVAL_US' micro seconds
+*
+* AtoD_reader() called from convenient point in code to take readings outside of ISRs
+*/
+
+void ISR_voltage_reader () // This is Ticker Interrupt Service Routine - few us between readings
+{
+ AtoD_Semaphore++;
+}
+
+
+/*
+Servo - mutex uses :
+0. Unused
+1. Input of pwm from model control Rx
+2. Output pwm to drive model control servo
+*/
+//enum {SERVO_UNUSED, SERVO_PWM_IN, SERVO_PWM_OUT} ;
+class Servo
+{
+ FastPWM * out;
+ Timer t;
+
+public:
+
+ bool rx_active;
+ int32_t clock_old, clock_new;
+ int32_t pulse_width_us, period_us;
+ Servo () { // Constructor
+ pulse_width_us = period_us = 0;
+ clock_old = clock_new = 0;
+ out = NULL;
+ rx_active = false;
+ }
+ bool validate_rx () ;
+ void rise () ;
+ void fall () ;
+ void out_pw_set (double);
+ void period_ticks (uint32_t);
+ void pulsewidth_ticks (uint32_t);
+ void set_out (FastPWM *);
+} S1, S2;
+
+bool Servo::validate_rx ()
+{
+ if ((clock() - clock_new) > 4)
+ rx_active = false;
+ else
+ rx_active = true;
+ return rx_active;
+}
+
+void Servo::rise ()
+{
+ t.stop ();
+ period_us = t.read_us ();
+ t.reset ();
+ t.start ();
+}
+void Servo::fall ()
+{
+ pulse_width_us = t.read_us ();
+ clock_old = clock_new;
+ clock_new = clock();
+ if ((clock_new - clock_old) < 4)
+ rx_active = true;
+}
+void Servo::out_pw_set (double outpw)
+{
+ if (outpw > 1.0)
+ outpw = 1.0;
+ if (outpw < 0.0)
+ outpw = 0.0;
+ outpw *= 1.2; // Change range to 1.2 ms to cover out pulse width range 0.9 to 2.1 ms
+ outpw += 0.9; // Bias to nom min servo range
+ pulsewidth_ticks ((uint32_t)(outpw * (SystemCoreClock / 2000)));
+}
+void Servo::period_ticks (uint32_t pt)
+{
+ if (out) out->period_ticks (pt);
+}
+void Servo::pulsewidth_ticks (uint32_t wt)
+{
+ if (out) out->pulsewidth_ticks (wt);
+}
+void Servo::set_out (FastPWM * op)
+{
+ out = op;
+}
+
+void Servo1rise ()
+{
+ S1.rise ();
+}
+void Servo1fall ()
+{
+ S1.fall ();
+}
+void Servo2rise ()
+{
+ S2.rise ();
+}
+void Servo2fall ()
+{
+ S2.fall ();
+}
+//uint32_t HAtot = 0, HBtot = 0, A_Offset = 0, B_Offset = 0;
+/*
+ 5 1 3 2 6 4 SENSOR SEQUENCE
+
+1 1 1 1 0 0 0 ---___---___
+2 0 0 1 1 1 0 __---___---_
+4 1 0 0 0 1 1 -___---___--
+
+ UV WV WU VU VW UW OUTPUT SEQUENCE
+*/
+const uint16_t A_tabl[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, // Handbrake
+ 0, AWV,AVU,AWU,AUW,AUV,AVW, 0, // Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0, // JP, FR, SG, PWM = 1 0 1 1 Forward1
+ 0, AVW,AUV,AUW,AWU,AVU,AWV, 0, // Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0, // JP, FR, SG, PWM = 1 1 0 1 Reverse1
+ 0, BRA,BRA,BRA,BRA,BRA,BRA,0 // Regenerative Braking
+} ;
+const uint16_t B_tabl[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, // Handbrake
+ 0, BWV,BVU,BWU,BUW,BUV,BVW, 0, // Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0, // JP, FR, SG, PWM = 1 0 1 1 Forward1
+ 0, BVW,BUV,BUW,BWU,BVU,BWV, 0, // Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0, // JP, FR, SG, PWM = 1 1 0 1 Reverse1
+ 0, BRB,BRB,BRB,BRB,BRB,BRB,0 // Regenerative Braking
+} ;
+
+
+class motor
+{
+ struct currents {
+ uint32_t max, min, ave;
+ } I;
+ uint32_t Hall_total, mode, edge_count_table[MAIN_LOOP_ITERATION_Hz]; // to contain one seconds worth
+ uint32_t Hall_tab_ptr, latest_pulses_per_sec;
+ const uint16_t * lut;
+ FastPWM * maxV, * maxI;
+ PortOut * Motor_Port;
+public:
+ uint32_t current_samples[CURRENT_SAMPLES_AVERAGED]; // Circular buffer where latest current readings get stored
+ uint32_t index;
+ motor () {} ; // Default constructor
+ motor (PortOut * , FastPWM * , FastPWM * , const uint16_t * ) ;
+ void set_V_limit (double) ; // Sets max motor voltage
+ void set_I_limit (double) ; // Sets max motor current
+ void Hall_change () ; // Called in response to edge on Hall input pin
+ bool set_mode (int);
+ void current_calc () ;
+ uint32_t pulses_per_sec () ; // call this once per main loop pass to keep count = edges per sec
+} ; //MotorA, MotorB;
+
+motor MotorA (&MotA, &A_MAX_V_PWM, &A_MAX_I_PWM, A_tabl);
+motor MotorB (&MotB, &B_MAX_V_PWM, &B_MAX_I_PWM, B_tabl);
+
+motor::motor (PortOut * P , FastPWM * _maxV_ , FastPWM * _maxI_ , const uint16_t * lutptr ) // Constructor
+{
+ maxV = _maxV_;
+ maxI = _maxI_;
+ Hall_total = mode = 0; // mode can be only 0, 8, 16 or 24, lut row select for Handbrake, Forward, Reverse, or Regen Braking
+ index = 0;
+ Hall_tab_ptr = 0;
+ latest_pulses_per_sec = 0;
+ for (int i = 0; i < MAIN_LOOP_ITERATION_Hz; i++)
+ edge_count_table[i] = 0;
+ if (lutptr != A_tabl && lutptr != B_tabl)
+ pc.printf ("Fatal in 'motor' constructor, Invalid lut address\r\n");
+ Motor_Port = P;
+ pc.printf ("In motor constructor, Motor port = %lx\r\n", P);
+ maxV->period_ticks (MAX_PWM_TICKS + 1); // around 18 kHz
+ maxI->period_ticks (MAX_PWM_TICKS + 1);
+ maxV->pulsewidth_ticks (MAX_PWM_TICKS / 20);
+ maxI->pulsewidth_ticks (MAX_PWM_TICKS / 30);
+// if (P != PortA && P != PortB)
+// pc.printf ("Fatal in 'motor' constructor, Invalid Port\r\n");
+// else
+// PortOut Motor_P (P, *mask); // PortA for motor A, PortB for motor B
+ lut = lutptr;
+}
+
+void motor::current_calc ()
+{
+ I.min = 0x0fffffff; // samples are 16 bit
+ I.max = 0;
+ I.ave = 0;
+ uint16_t sample;
+ for (int i = 0; i < CURRENT_SAMPLES_AVERAGED; i++) {
+ sample = current_samples[i];
+ I.ave += sample;
+ if (I.min > sample)
+ I.min = sample;
+ if (I.max < sample)
+ I.max = sample;
+ }
+ I.ave /= CURRENT_SAMPLES_AVERAGED;
+}
+
+void motor::set_V_limit (double p) // Sets max motor voltage
+{
+ if (p < 0.0)
+ p = 0.0;
+ if (p > 1.0)
+ p = 1.0;
+// last_pwm = p;
+ p *= 0.95; // need limit, ffi see MCP1630 data
+ p = 1.0 - p; // because pwm is wrong way up
+ maxV->pulsewidth_ticks ((int)(p * MAX_PWM_TICKS)); // PWM output to MCP1630 inverted motor pwm as MCP1630 inverts
+}
+
+void motor::set_I_limit (double p) // Sets max motor current. pwm integrated to dc ref voltage level
+{
+ int a;
+ if (p < 0.0)
+ p = 0.0;
+ if (p > 1.0)
+ p = 1.0;
+ a = (int)(p * MAX_PWM_TICKS);
+ if (a > MAX_PWM_TICKS)
+ a = MAX_PWM_TICKS;
+ if (a < 0)
+ a = 0;
+ maxI->pulsewidth_ticks (a); // PWM
+}
+
+uint32_t motor::pulses_per_sec () // call this once per main loop pass to keep count = edges per sec
+{
+ uint32_t tmp = Hall_total;
+ latest_pulses_per_sec = tmp - edge_count_table[Hall_tab_ptr];
+ edge_count_table[Hall_tab_ptr] = tmp;
+ Hall_tab_ptr++;
+ if (Hall_tab_ptr >= MAIN_LOOP_ITERATION_Hz)
+ Hall_tab_ptr = 0;
+ return latest_pulses_per_sec;
+}
+
+bool motor::set_mode (int m)
+{
+ if (m != HANDBRAKE && m != FORWARD && m != REVERSE && m !=REGENBRAKE)
+ return false;
+ mode = m;
+ return true;
+}
+
+void motor::Hall_change ()
+{
+ Hall_total++;
+ mode &= 0x038L; // safety
+ *Motor_Port = lut[mode + index];
+}
+void MAH1r ()
+{
+ MotorA.index = 1;
+ if (MAH2 != 0) MotorA.index |= 2;
+ if (MAH3 != 0) MotorA.index |= 4;
+ MotorA.Hall_change ();
+}
+void MAH1f ()
+{
+ MotorA.index = 0;
+ if (MAH2 != 0) MotorA.index |= 2;
+ if (MAH3 != 0) MotorA.index |= 4;
+ MotorA.Hall_change ();
+}
+void MAH2r ()
+{
+ MotorA.index = 2;
+ if (MAH1 != 0) MotorA.index |= 1;
+ if (MAH3 != 0) MotorA.index |= 4;
+ MotorA.Hall_change ();
+}
+void MAH2f ()
+{
+ MotorA.index = 0;
+ if (MAH1 != 0) MotorA.index |= 1;
+ if (MAH3 != 0) MotorA.index |= 4;
+ MotorA.Hall_change ();
+}
+void MAH3r ()
+{
+ MotorA.index = 4;
+ if (MAH1 != 0) MotorA.index |= 1;
+ if (MAH2 != 0) MotorA.index |= 2;
+ MotorA.Hall_change ();
+}
+void MAH3f ()
+{
+ MotorA.index = 0;
+ if (MAH1 != 0) MotorA.index |= 1;
+ if (MAH2 != 0) MotorA.index |= 2;
+ MotorA.Hall_change ();
+}
+
+void MBH1r ()
+{
+ MotorB.index = 1;
+ if (MBH2 != 0) MotorB.index |= 2;
+ if (MBH3 != 0) MotorB.index |= 4;
+ MotorB.Hall_change ();
+}
+void MBH1f ()
+{
+ MotorB.index = 0;
+ if (MBH2 != 0) MotorB.index |= 2;
+ if (MBH3 != 0) MotorB.index |= 4;
+ MotorB.Hall_change ();
+}
+void MBH2r ()
+{
+ MotorB.index = 2;
+ if (MBH1 != 0) MotorB.index |= 1;
+ if (MBH3 != 0) MotorB.index |= 4;
+ MotorB.Hall_change ();
+}
+void MBH2f ()
+{
+ MotorB.index = 0;
+ if (MBH1 != 0) MotorB.index |= 1;
+ if (MBH3 != 0) MotorB.index |= 4;
+ MotorB.Hall_change ();
+}
+void MBH3r ()
+{
+ MotorB.index = 4;
+ if (MBH1 != 0) MotorB.index |= 1;
+ if (MBH2 != 0) MotorB.index |= 2;
+ MotorB.Hall_change ();
+}
+void MBH3f ()
+{
+ MotorB.index = 0;
+ if (MBH1 != 0) MotorB.index |= 1;
+ if (MBH2 != 0) MotorB.index |= 2;
+ MotorB.Hall_change ();
+}
+
+
+// End of Interrupt Service Routines
+
+void buggery_fuck () // simulate hall movement to observe port output bits
+{
+ /* MotorA.index++;
+ if (MotorA.index > 6)
+ MotorA.index = 1;
+ MotorA.Hall_change ();
+ */
+}
+void AtoD_reader ()
+{
+ static uint32_t i = 0, tab_ptr = 0;
+ if (AtoD_Semaphore > 20) {
+ pc.printf ("WARNING - silly semaphore count %d, limiting to sensible\r\n", AtoD_Semaphore);
+ AtoD_Semaphore = 20;
+ }
+ while (AtoD_Semaphore > 0) {
+ AtoD_Semaphore--;
+ // Code here to sequence through reading voltmeter, demand pot, ammeters
+ switch (i) { //
+ case 0:
+ volt_reading += Ain_SystemVolts.read_u16 (); // Result = Result + New Reading
+ volt_reading >>= 1; // Result = Result / 2
+ break; // i.e. Very simple digital low pass filter
+ case 1:
+ driverpot_reading += Ain_DriverPot.read_u16 ();
+ driverpot_reading >>= 1;
+ break;
+ case 2:
+ MotorA.current_samples[tab_ptr] = Motor_A_Current.read_u16 (); //
+ break;
+ case 3:
+ MotorB.current_samples[tab_ptr++] = Motor_B_Current.read_u16 (); //
+ if (tab_ptr >= CURRENT_SAMPLES_AVERAGED) // Current reading will be lumpy and spikey, so put through moving average filter
+ tab_ptr = 0;
+ break;
+ }
+ i++; // prepare to read the next input in response to the next interrupt
+ if (i > 3)
+ i = 0;
+ }
+}
+
+/** double Read_DriverPot ()
+* driverpot_reading is a global 16 bit unsigned int updated in interrupt service routine
+* ISR also filters signal
+* This function returns normalised double (range 0.0 to 1.0) representation of driver pot position
+*/
+double Read_DriverPot ()
+{
+#ifdef KNIFEANDFORK
+ return test_pot; // may be altered using cli
+#else
+ return (double) driverpot_reading / 65535.0; // Normalise 0.0 <= control pot <= 1.0
+#endif
+}
+
+double Read_BatteryVolts ()
+{
+ return (double) volt_reading / 1800.0; // divisor fiddled to make voltage reading correct !
+}
+
+double read_volts () // A test function
+{
+ pc.printf ("pot = %.4f, System Voltage = %.2f\r\n", Read_DriverPot(), Read_BatteryVolts());
+ return Read_BatteryVolts();
+}
+
+
+extern void command_line_interpreter () ;
+extern int check_24LC64 () ; // Call from near top of main() to init i2c bus
+extern bool wr_24LC64 (int mem_start_addr, char * source, int length) ;
+extern bool rd_24LC64 (int mem_start_addr, char * dest, int length) ;
+
+int main()
+{
+ int j = 0;
+ uint32_t Apps, Bpps;
+
+ MotA = 0; // Motor drive ports A and B
+ MotB = 0;
+ MAH1.rise (& MAH1r);
+ MAH1.fall (& MAH1f);
+ MAH2.rise (& MAH2r);
+ MAH2.fall (& MAH2f);
+ MAH3.rise (& MAH3r);
+ MAH3.fall (& MAH3f);
+
+ MBH1.rise (& MBH1r);
+ MBH1.fall (& MBH1f);
+ MBH2.rise (& MBH2r);
+ MBH2.fall (& MBH2f);
+ MBH3.rise (& MBH3r);
+ MBH3.fall (& MBH3f);
+
+ MAH1.mode (PullUp);
+ MAH2.mode (PullUp);
+ MAH3.mode (PullUp);
+ MBH1.mode (PullUp);
+ MBH2.mode (PullUp);
+ MBH3.mode (PullUp);
+ pc.printf ("\tAbandon Hope %d\r\n", LED ? 0 : 1);
+ // Setup system timers to cause periodic interrupts to synchronise and automate volt and current readings, loop repeat rate etc
+ tick_vread.attach_us (&ISR_voltage_reader, VOLTAGE_READ_INTERVAL_US); // Start periodic interrupt generator
+ loop_timer.attach_us (&ISR_loop_timer, MAIN_LOOP_REPEAT_TIME_US); // Start periodic interrupt generator
+ // Done setting up system interrupt timers
+
+ const int TXTBUFSIZ = 36;
+ char buff[TXTBUFSIZ];
+ bool eerom_detected = false;
+ pc.printf ("RAM test - ");
+ if (check_24LC64() != 0xa0) // searches for i2c devices, returns address of highest found
+ pc.printf ("Check for 24LC64 eeprom FAILED\r\n");
+ else // i2c.write returned 0, think this means device responded with 'ACK', found it anyway
+ eerom_detected = true;
+ if (eerom_detected) {
+ bool j, k;
+ pc.printf ("ok\r\n");
+ static const char ramtst[] = "I found the man sir!";
+ j = wr_24LC64 (0x1240, (char*)ramtst, strlen(ramtst));
+ for (int i = 0; i < TXTBUFSIZ; i++) buff[i] = 0; // Clear buffer
+ // need a way to check i2c busy - YES implemented ack_poll
+ k = rd_24LC64 (0x1240, buff, strlen(ramtst));
+ pc.printf("Ram test returned [%s], wr ret'd [%s], rd ret'd [%s]\r\n", buff, j ? "true" : "false", k ? "true" : "false");
+ }
+ T1 = 0; // As yet unused pins
+ T2 = 0;
+ T3 = 0;
+ T4 = 0;
+ T5 = 0;
+ T6 = 0;
+// MotorA.set_mode (HANDBRAKE);
+// MotorB.set_mode (HANDBRAKE);
+ MotorA.set_mode (FORWARD);
+ MotorB.set_mode (FORWARD);
+ MotorA.set_V_limit (0.1);
+ MotorB.set_V_limit (0.0);
+ MotorA.set_I_limit (0.0);
+ MotorB.set_I_limit (0.0);
+
+ // Setup Complete ! Can now start main control forever loop.
+
+ while (1) { // Loop forever, repeats synchroised by waiting for ticker Interrupt Service Routine to set 'loop_flag' true
+ while (!loop_flag) { // Most of the time is spent in this loop, repeatedly re-checking for commands from pc port
+ command_line_interpreter () ; // Proceed beyond here once loop_timer ticker ISR has set loop_flag true
+ AtoD_reader (); // Performs A to D conversions at rate set by ticker interrupts
+ }
+ loop_flag = false; // Clear flag set by ticker interrupt handler
+ Apps = MotorA.pulses_per_sec (); // Needed to keep table updated to give reading in Hall transitions per second
+ Bpps = MotorB.pulses_per_sec ();
+
+ buggery_fuck ();
+
+ // do stuff
+ if (flag_8Hz) { // do slower stuff
+ flag_8Hz = false;
+ LED = !LED; // Toggle LED on board, should be seen to fast flash
+ j++;
+ if (j > 6) { // Send some status info out of serial port every second and a bit or thereabouts
+ j = 0;
+//double pres = PressureSensor.getPressure ();
+//double pres = PressureSensor.getTemperature ();
+ pc.printf ("Apps %d, Bpps %d, sys_timer %d\r\n", Apps, Bpps, sys_timer);
+// pc.printf ("Apps %d, Bpps %d, sys_timer %d, pressure %.2f\r\n", Apps, Bpps, sys_timer, pres);
+ // pc.printf ("V=%+.1f, I=%+.1f, CtrlOut %.3f, RPM %d, %s\r\n", Read_BatteryVolts(), AmpsReading, ControlOut, ReadEngineRPM(), engine_warm ? "Running mode" : "Startup mode");
+ }
+ } // End of if(flag_8Hz)
+ } // End of main programme loop
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed.bld Thu Mar 01 11:29:28 2018 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed/builds/7130f322cb7e \ No newline at end of file