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

Files at this revision

API Documentation at this revision

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

24LC64_eeprom.cpp Show annotated file Show diff for this revision Revisions of this file
BufferedSerial.lib Show annotated file Show diff for this revision Revisions of this file
DualBLS.h Show annotated file Show diff for this revision Revisions of this file
FastPWM.lib Show annotated file Show diff for this revision Revisions of this file
MPL3115A2b.lib Show annotated file Show diff for this revision Revisions of this file
cli_nortos.cpp Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /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