Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: mbed BufferedSerial Servo2 PCT2075 I2CEeprom FastPWM
Revision 0:77803b3ee157, committed 2019-06-28
- Comitter:
- JonFreeman
- Date:
- Fri Jun 28 19:32:51 2019 +0000
- Child:
- 1:450090bdb6f4
- Commit message:
- As at end June 2019
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Alternator.h Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,36 @@
+#include "Servo.h"
+#include "BufferedSerial.h"
+const int eeprom_page = 17; // Determines where in eeprom 'settings' reside
+
+const int lut_seg_size = 60; // steps per thousand RPM
+const int lut_size = lut_seg_size * 8; // 8 segments - 0-1, 1-2, 2-3, 3-4 etc 000 rpm
+
+class eeprom_settings {
+ char settings [36];
+ int max_pwm_lut [lut_size + 4];
+// bool rd_24LC64 (int start_addr, char * dest, int length) ;
+// bool wr_24LC64 (int start_addr, char * dest, int length) ;
+// bool set_24LC64_internal_address (int start_addr) ;
+// bool ack_poll () ;
+ void build_lut () ;
+ public:
+ eeprom_settings (); // Constructor
+ int get_pwm (int) ;
+ char rd (uint32_t) ; // Read one setup char value from private buffer 'settings'
+ bool wr (char, uint32_t) ; // Write one setup char value to private buffer 'settings'
+ bool save () ; // Write 'settings' buffer to EEPROM
+ bool load () ; // Get 'settings' from EEPROM
+ bool set_defaults (); // Put default settings into EEPROM and local buffer
+// uint32_t errs () ; // Return errors
+} ;
+
+enum {RPM0, RPM1, RPM2, RPM3, RPM4, RPM5, RPM6, RPM7, RPM8,
+ PWM_SCALE, FUT1, FUT2, FUT3, FUT4, FUT5} ; //
+
+struct optpar {
+ int min, max, def; // min, max, default
+ const char * t; // description
+} ;
+
+const int PWM_PERIOD_US = 3200 ;
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Servo.lib Fri Jun 28 19:32:51 2019 +0000 @@ -0,0 +1,1 @@ +https://os.mbed.com/users/simon/code/Servo/#4e3d7cb4d0a2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cli.cpp Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,226 @@
+/*
+Command Line Interpreter code module.
+Purpose -
+ Provides easy interface to pc terminal programme for use during programme development, debug etc.
+ Also usable as comms subsystem in finished code for accepting commands, reporting data etc.
+*/
+#include "mbed.h"
+#include "Alternator.h"
+//#include "BufferedSerial.h"
+#include <cctype>
+using namespace std;
+
+extern eeprom_settings mode ;
+//eeprom_settings mode ;
+
+extern int ver, vef, measured_pw_us;
+extern uint32_t ReadEngineRPM () ;
+extern double Read_BatteryVolts () ;
+
+
+
+
+
+const int MAX_PARAMS = 10;
+struct parameters {
+ int32_t times[50];
+ int32_t position_in_list, last_time, numof_dbls;
+ double dbl[MAX_PARAMS];
+} ;
+
+// WithOUT RTOS
+//extern BufferedSerial pc;
+extern Serial pc;
+//extern BufferedSerial pc;
+extern double test_pot; // These used in knifeandfork code testing only
+
+//extern int numof_eeprom_options2 ;
+//extern struct optpar const option_list2[] ;
+extern struct optpar option_list2[] ;
+
+/**void mode_cmd (struct parameters & a) // With no params, reads eeprom contents. With params sets eeprom contents
+* mode_cmd called only from pc comms. No sense calling from Touch Screen Controller
+*
+* Called without parameters - Lists to pc terminal current settings
+*
+*/
+void mode19_cmd (struct parameters & a) // With no params, reads eeprom contents. With params sets eeprom contents
+{
+ char temps[36];
+ int i;
+ pc.printf ("\r\nmode - Set system data in EEPROM - Jan 2019\r\nSyntax 'mode' with no parameters lists current state.\r\n");
+ if (a.numof_dbls) { // If more than 0 parameters supplied
+ for (i = 0; i < a.numof_dbls; i++)
+ temps[i] = (char)a.dbl[i]; // recast doubles to char
+ while (i < 33)
+ temps[i++] = 0;
+ i = (int)a.dbl[0];
+ switch (i) {
+ case 0: case 1: case 2: case 3: case 4:
+ case 5: case 6: case 7: case 8:
+ if (temps[1] >= option_list2[i].min && temps[1] <= option_list2[i].max)
+ mode.wr(temps[1], RPM0 + i);
+ break;
+ case 37: // set pwm scale factor
+ if (temps[1] >= option_list2[PWM_SCALE].min && temps[1] <= option_list2[PWM_SCALE].max)
+ mode.wr(temps[1], PWM_SCALE);
+ break;
+ case 83: // set to defaults
+ mode.set_defaults ();
+ break;
+ case 9: // 9 Save settings
+ mode.save ();
+ pc.printf ("Saving settings to EEPROM\r\n");
+ break;
+ default:
+ break;
+ } // endof switch
+ } // endof // If more than 0 parameters supplied
+ else {
+ pc.printf ("No Changes\r\n");
+ }
+ pc.printf ("mode 0\t%s, [%d]\r\n", option_list2[0].t, mode.rd(RPM0));
+ pc.printf ("mode 1\t%s, [%d]\r\n", option_list2[1].t, mode.rd(RPM1));
+ pc.printf ("mode 2\t%s, [%d]\r\n", option_list2[2].t, mode.rd(RPM2));
+ pc.printf ("mode 3\t%s, [%d]\r\n", option_list2[3].t, mode.rd(RPM3));
+ pc.printf ("mode 4\t%s, [%d]\r\n", option_list2[4].t, mode.rd(RPM4));
+ pc.printf ("mode 5\t%s, [%d]\r\n", option_list2[5].t, mode.rd(RPM5));
+ pc.printf ("mode 6\t%s, [%d]\r\n", option_list2[6].t, mode.rd(RPM6));
+ pc.printf ("mode 7\t%s, [%d]\r\n", option_list2[7].t, mode.rd(RPM7));
+ pc.printf ("mode 8\t%s, [%d]\r\n", option_list2[8].t, mode.rd(RPM8));
+
+ pc.printf ("mode 37\t%s, [%d]\r\n", option_list2[PWM_SCALE].t, mode.rd(PWM_SCALE));
+ pc.printf ("mode 83\tSet to defaults\r\n");
+ pc.printf ("mode 9\tSave settings\r\r\n");
+
+}
+
+void gpcmd (struct parameters & a) {
+ pc.printf ("pwm=%d\r\n", mode.get_pwm ((int)a.dbl[0]));
+}
+
+void rfcmd (struct parameters & a) {
+ pc.printf ("ver = %d, vef = %d, measured_pw_us = %d\r\n", ver, vef, measured_pw_us);
+}
+
+extern double glob_rpm;
+extern void set_RPM_demand (uint32_t d) ;
+
+void set_rpm_cmd (struct parameters & a) {
+ pc.printf ("setting RPM to %d\r\n",(int)a.dbl[0]);
+ set_RPM_demand ((uint32_t)a.dbl[0]);
+}
+
+void speedcmd (struct parameters & a) {
+ int s = ReadEngineRPM ();
+ pc.printf ("speed %d, %.2f, pwm %d\r\n", s, glob_rpm, mode.get_pwm(s));
+}
+
+void vcmd (struct parameters & a) {
+ pc.printf ("volts %.2f\r\n", Read_BatteryVolts());
+}
+
+extern void set_servo (double p) ; // Only for test, called from cli
+
+void set_servo_cmd (struct parameters & a) {
+ double p = a.dbl[0] / 100.0;
+ pc.printf ("servo %.2f\r\n", p);
+ set_servo (p);
+}
+
+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 menucmd (struct parameters & a);
+
+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[] = {
+ {"?", "Lists available commands, same as ls", menucmd},
+ {"ls", "Lists available commands, same as menu", menucmd},
+ {"rf", "Check rise and fall on VEXT", rfcmd},
+ {"s", "Speed, RPM", speedcmd},
+ {"v", "Read Battery volts", vcmd},
+ {"gp","Get pwm from RPM", gpcmd},
+ {"mode", "See or set eeprom values", mode19_cmd},
+ {"nu", "do nothing", null_cmd},
+ {"ser","set throttle servo direct 0 - 99", set_servo_cmd},
+ {"sv","set engine RPM demand 3000 - 6000", set_rpm_cmd},
+};
+
+const int numof_menu_items = sizeof(command_list) / sizeof(kb_command);
+void menucmd (struct parameters & a)
+{
+ pc.printf("\r\nIntelligent Alternator Controller - Jon Freeman 2019\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 ()
+{
+ 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 (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())
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/i2c_bit_banged.cpp Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,374 @@
+#include "mbed.h"
+#include "Alternator.h"
+extern Serial pc;
+DigitalInOut SDA (D4); // Horrible bodge to get i2c working using bit banging.
+DigitalInOut SCL (D5); // DigitalInOut do not work as you might expect. Fine if used only as OpenDrain opuputs though!
+DigitalIn SDA_IN (A4); // That means paralleling up with two other pins as inputs
+DigitalIn SCL_IN (A5); // This works but is a pain. Inbuilt I2C should have worked but never does on small boards with 32 pin cpu.
+
+const int _24LC_rd = 0xa1; // set bit 0 for read, clear bit 0 for write
+const int _24LC_wr = 0xa0; // set bit 0 for read, clear bit 0 for write
+const int ACK = 0; // but acknowledge is 0, NAK is 1
+
+
+/*struct optpar {
+ int min, max, def; // min, max, default
+ const char * t; // description
+} ;*/
+struct optpar option_list2[] = {
+ {0, 100, 10, "max pwm% @ Eng RPM 0, 0 to 100"},
+ {0, 100, 10, "max pwm% @ Eng RPM 1000, 0 to 100"},
+ {0, 100, 20, "max pwm% @ Eng RPM 2000, 0 to 100"},
+ {0, 100, 30, "max pwm% @ Eng RPM 3000, 0 to 100"},
+ {0, 100, 40, "max pwm% @ Eng RPM 4000, 0 to 100"},
+ {0, 100, 50, "max pwm% @ Eng RPM 5000, 0 to 100"},
+ {0, 100, 50, "max pwm% @ Eng RPM 6000, 0 to 100"},
+ {0, 100, 50, "max pwm% @ Eng RPM 7000, 0 to 100"},
+ {0, 100, 50, "max pwm% @ Eng RPM 8000, 0 to 100"},
+ {0, 100, 50, "Set Overall PWM Scale Factor percent"},
+ {0, 100, 0, "Future 2"},
+ {0, 100, 0, "Future 3"},
+ {0, 100, 0, "Future 4"},
+ {0, 100, 0, "Future 5"},
+} ;
+
+const int numof_eeprom_options2 = sizeof(option_list2) / sizeof (struct optpar);
+
+bool wr_24LC64 (int start_addr, char * source, int length) ; // think this works
+bool rd_24LC64 (int start_addr, char * source, int length) ; // think this works
+
+
+
+eeprom_settings mode ;
+
+eeprom_settings::eeprom_settings () {}
+
+bool eeprom_settings::set_defaults () {
+ for (int i = 0; i < numof_eeprom_options2; i++)
+ settings[i] = option_list2[i].def; // Load defaults and 'Save Settings'
+ return save ();
+}
+
+char eeprom_settings::rd (uint32_t i) { // Read one setup char value from private buffer 'settings'
+ if (i > 31) {
+ pc.printf ("ERROR Attempt to read setting %d\r\n", i);
+ return 0;
+ }
+ return settings[i];
+}
+
+bool eeprom_settings::wr (char c, uint32_t i) { // Read one setup char value from private buffer 'settings'
+ if (i > 31)
+ return false;
+ settings[i] = c;
+ return true;
+}
+
+int eeprom_settings::get_pwm (int rpm) {
+ int p = rpm * lut_size;
+ p /= 8000; // 8000 is upper RPM limit
+ if (p < 0) p = 0; // point to first
+ if (p >= lut_size) p = lut_size - 1; // point to last
+// pc.printf ("In get_pwm, rpm = %d, lut entry = %d, pwm = %d\r\n", rpm, p, max_pwm_lut[p]);
+ return max_pwm_lut[p];
+}
+
+void eeprom_settings::build_lut () {
+ int ptr = 0;
+ int range, i;
+ int base = mode.rd(RPM0) * PWM_PERIOD_US;
+ double acc, incr;
+ base /= 100; // got pwm_pulsewidth of 0 RPM
+ acc = (double) base;
+ pc.printf ("pwm_period_us ar 0 RPM = %d\r\n", base);
+ for (i = 0; i < 8; i++) {
+ range = mode.rd(i+1) - mode.rd(i); // range now change in percent between two 'n'000 RPMs
+ range *= mode.rd(PWM_SCALE); // range now 10000 times factor due to percentage twice
+ range *= PWM_PERIOD_US;
+ incr = (double)range;
+ incr /= 10000.0;
+ incr /= lut_seg_size;
+ for(int j = 0; j < lut_seg_size; j++) {
+ max_pwm_lut[ptr++] = (int)acc;
+ acc += incr;
+ }
+ }
+ max_pwm_lut[ptr] = (int)acc;
+ pc.printf ("At end of build_lut ptr=%d\r\n", ptr);
+ range = 0;
+// while (range < ptr) {
+// for (i = 0; i < 10; i++) {
+// pc.printf ("%d\t", max_pwm_lut[range++]);
+// }
+// pc.printf ("\r\n");
+// }
+ pc.printf ("lut_size = %d\r\n", lut_size);
+}
+
+bool eeprom_settings::load () { // Get 'settings' buffer from EEPROM
+ bool rv ;
+ rv = rd_24LC64 (eeprom_page * 32, settings, 32); // Can now build lookup table
+ build_lut ();
+ return rv;
+}
+
+bool eeprom_settings::save () { // Write 'settings' buffer to EEPROM
+ return wr_24LC64 (eeprom_page * 32, settings, 32);
+}
+
+
+
+/**
+* bool i2c_init(void) {
+*
+* Init function. Needs to be called once in the beginning.
+* Returns false if SDA or SCL are low, which probably means
+* a I2C bus lockup or that the lines are not pulled up.
+*/
+bool i2c_init(void) {
+ SDA.output();
+ SCL.output();
+ SDA.mode(OpenDrain);
+ SCL.mode(OpenDrain); // Device may pull clock lo to indicate to master
+ SDA = 0;
+ SCL = 0;
+ wait_us (1);
+ SDA = 1;
+ wait_us (1);
+ SCL = 1;
+ wait_us (1);
+ if (SCL_IN == 0 || SDA_IN == 0) return false;
+ return true;
+}
+
+/**
+* During data transfer, the data line must remain
+* stable whenever the clock line is high. Changes in
+* the data line while the clock line is high will be
+* interpreted as a Start or Stop condition
+*
+* A high-to-low transition of the SDA line while the clock
+* (SCL) is high determines a Start condition. All
+* commands must be preceded by a Start condition.
+*/
+int i2c_start () { // Should be Both hi, start takes SDA low
+ int rv = 0;
+ if (SDA_IN == 0 ) {
+ rv |= 1; // Fault - SDA was lo on entry
+ SDA = 1;
+ wait_us (1);
+ }
+ if (SCL == 0 ) {
+ rv |= 2; // Fault - SCL was lo on entry
+ SCL = 1;
+ wait_us (1);
+ }
+ SDA = 0; // Take SDA lo
+ wait_us (1);
+ SCL = 0;
+ wait_us (1);
+ return rv; // Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault
+}
+
+/**
+* During data transfer, the data line must remain
+* stable whenever the clock line is high. Changes in
+* the data line while the clock line is high will be
+* interpreted as a Start or Stop condition
+*
+* A low-to-high transition of the SDA line while the clock
+* (SCL) is high determines a Stop condition. All
+* operations must be ended with a Stop condition.
+*/
+int i2c_stop () { // Should be SDA=0, SCL=1, start takes SDA hi
+ int rv = 0;
+ SDA = 0; // Pull SDA to 0
+ wait_us (1);
+ if (SCL_IN != 0) {
+ pc.printf ("SCL 1 on entry to stop\r\n");
+ SCL = 0; // pull SCL to 0 if not there already
+ wait_us (1);
+ }
+ SCL = 1;
+ wait_us (1);
+ if (SCL_IN == 0)
+ pc.printf ("SCL stuck lo in stop\r\n");
+ SDA = 1;
+ wait_us (1);
+ if (SDA_IN == 0)
+ pc.printf ("SDA stuck lo in stop\r\n");
+ return rv; // Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault
+}
+
+void jclk (int bit) {
+ SCL = bit;
+ wait_us (1);
+}
+
+void jclkout () {
+ wait_us (1);
+ SCL = 1;
+ wait_us (1);
+ SCL = 0;
+ wait_us (1);
+}
+
+int i2c_write (int d) {
+ int ackbit = 0;
+ if (SCL_IN != 0) {
+ pc.printf ("SCL hi on entry to write\r\n");
+ jclk (0);
+ }
+ for (int i = 0x80; i != 0; i >>= 1) { // bit out msb first
+ if ((d & i) == 0) SDA = 0;
+ else SDA = 1;
+ jclkout (); // SCL ____---____
+ }
+ SDA = 1; // Release data to allow remote device to pull lo for ACK or not
+ jclk (1); // SCL = 1
+ ackbit = SDA_IN; // read in ack bit
+ jclk (0); // SCL = 0
+// pc.printf ("wr 0x%x %s\r\n", d, ackbit == 0 ? "ACK" : "nak");
+ return ackbit; // 0 for acknowledged ACK, 1 for NAK
+}
+
+
+
+
+int i2c_read (int acknak) { // acknak indicates if the byte is to be acknowledged (0 = acknowledge)
+ int result = 0; // SCL should be 1 on entry
+ SDA = 1; // Master released SDA
+ if (SCL_IN != 0) pc.printf ("SCL hi arriving at read\r\n");
+ wait_us (2);
+ for (int i = 0; i < 8; i++) {
+ result <<= 1;
+ jclk (1);
+ if (SDA_IN != 0) result |= 1;
+ jclk (0);
+ }
+ if (acknak != 0 && acknak != 1)
+ pc.printf ("Bad acknak in 12c_read %d\r\n", acknak);
+ if (acknak == 0) SDA = 0;
+ else SDA = 1;
+ jclkout (); // clock out the ACK bit __--__
+// pc.printf ("rd 0x%x %s\r\n", result, acknak == 0 ? "ACK" : "nak");
+ return result; // Always ? nah
+}
+
+int check_24LC64 () { // Call from near top of main() to init i2c bus
+ int last_found = 0, q, e; // 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
+ e = i2c_start();
+ if (e) pc.putc(',');
+ q = i2c_write(i); // may return error code 2 when no start issued
+ if (q == ACK) {
+ pc.printf ("I2C device found at 0x%x\r\n", i);
+ last_found = i;
+ wait_ms (5);
+ }
+ i2c_stop();
+ }
+ return last_found;
+}
+
+
+
+
+
+
+
+
+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(_24LC_wr) == ACK)
+ i2cfree = true;
+ else
+ wait_ms (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) {
+*
+*
+*
+*/
+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;
+ }
+// pc.printf ("GOOD set_24LC64_internal_address %d\r\n", start_addr);
+ return true;
+}
+
+bool wr_24LC64 (int start_addr, char * source, int length) { // think this works
+ 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 += 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(_24LC_rd) != ACK) {
+ pc.printf ("Errors in rd_24LC64 sending 24LC_rd\r\n");
+ return false;
+ }
+ while(length--) {
+ if(length == 0)
+ acknak = 1;
+ *dest++ = i2c_read(acknak);
+ }
+ i2c_stop();
+ return true;
+}
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,446 @@
+#include "mbed.h"
+#include "Alternator.h"
+
+#define MAGNETO_SPEED // Selects engine speed input as magneto coil on engine switch line
+#define SPEED_CONTROL_ENABLE // Includes engine revs servo control loop
+
+/*
+ Alternator Regulator
+ Jon Freeman
+ June 2019
+
+ WHAT THIS PROGRAMME DOES -
+
+ BEGIN
+ Loop forever at 32 Hz {
+ Read engine RPM
+ Adjust Alternator field current max limit according to RPM
+ Measure system voltage (just in case this is ever useful)
+ Respond to any commands arriving at serial port (setup and test link to laptop)
+ Flash LED at 8 Hz as proof of life
+ }
+ END
+
+ INPUTS AnalogIn x 2 - Ammeter chip - current and offset AnalogIns
+ INPUT AnalogIn - System voltage for info only.
+ INPUT AnalogIn - ExtRevDemand
+ INPUT AnalogIn - DriverPot
+ INPUT Pulse engine speed indicator, speed checked against EEPROM data to select max pwm duty ratio for this speed
+ INPUT Final pwm gate drive wired back to InterruptIn ** MAYBE USEFUL OR NOT ** Could read this back via serial to laptop
+ OUTPUT pwm to MCP1630. This is clock to pwm chip. Also limits max duty ratio
+ RS232 serial via USB to setup eeprom data
+*/
+// Uses software bit banged I2C - DONE (because no attempt to get I2C working on these small boards)
+
+/**
+* Jumpers fitted to small mbed Nucleo boards - D5 - A5 and D4 - A4 CHECK - yes
+*/
+/*
+ declared in file i2c_bit_banged.cpp
+DigitalInOut SDA (D4); // Horrible bodge to get i2c working using bit banging.
+DigitalInOut SCL (D5); // DigitalInOut do not work as you might expect. Fine if used only as OpenDrain opuputs though!
+DigitalIn SDA_IN (A4); // That means paralleling up with two other pins as inputs
+DigitalIn SCL_IN (A5); // This works but is a pain. Inbuilt I2C should have worked but never does on small boards with 32 pin cpu.
+*/
+Serial pc (USBTX, USBRX); // Comms port to pc or terminal using USB lead
+BufferedSerial LocalCom (PA_9, PA_10); // New March 2019
+// Above combo of Serial and BufferedSerial is the only one to work !
+
+// INPUTS :
+AnalogIn Ain_SystemVolts (A0); // Brought out to CN8 'AN' A0. Connect 3k3 resistor from A0 to ground.
+AnalogIn Ammeter_In (A1); // Output of ASC709LLFTR ammeter chip (pin 20)
+AnalogIn Ammeter_Ref (A6); // Ref output from ASC709LLFTR used to set ammeter zero (pin 25)
+AnalogIn Ext_Rev_Demand (D3); // Servo determines engine revs, servo out to be higher of Ext_Rev_Demand and internal calc
+AnalogIn Driver_Pot (A3); // If whole control system can be made to fit
+// Connect 33k resistor from A0 to nom 24 Volt system rail.
+
+//#ifdef TARGET_NUCLEO_F303K8 //
+#ifdef TARGET_NUCLEO_L432KC //
+/*
+ MODULE PIN USAGE
+1 PA_9 D1 LocalCom Tx
+2 PA_10 D0 LocalCom Rx
+3 NRST
+4 GND
+5 PA12_D2 NEW June 2019 - Output engine tacho cleaned-up
+6 PB_0 D3 AnalogIn Ext_Rev_Demand
+7 PB_7 D4 SDA i2c to 24LC memory
+8 PB_6 D5 SCL i2c to 24LC memory
+9 PB_12 D6 PwmOut PWM_OSC_IN Timebase for pwm, also determines max duty ratio
+10 N.C.
+11 N.C.
+12 PA_8 D9 InterruptIn pulse_tacho from alternator, used to measure rpm
+13 PA_11 D10
+14 PB_5 D11
+15 PB_4 D12
+16 PB_3 D13 LED Onboard LED
+17 3V3
+18 AREF
+19 PA_0 A0 AnalogIn V_Sample system link voltage
+20 PA_1 A1 AnalogIn Ammeter_In
+21 PA_3 A2 PWM analogue out
+22 PA_4 A3 InterruptIn VEXT PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+23 PA_5 A4 n.c. SDA_IN paralleled to i2c pin, necessary because i2c has to be bit banged
+24 PA_6 A5 n.c. SCL_IN paralleled to i2c pin, necessary because i2c has to be bit banged
+25 PA_7 A6 AnalogIn Ammeter_Ref
+26 PA_2 A7 UART2_TX Throttle Servo out now on D10, can not use D11, can not use D12 for these
+27 5V
+28 NRST
+29 GND
+30 VIN
+*/
+
+InterruptIn pulse_tacho (D9); // Signal from 'W' alternator terminal via low pass filter and Schmitt trigger cleanup
+ // NOTE D7 pin was no good for this
+//InterruptIn VEXT (A3); // PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+InterruptIn VEXT (D11); // PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+// OUTPUTS :
+
+//DigitalOut Scope_probe (D0); // Handy pin to hang scope probe onto while developing code
+DigitalOut myled(LED1); // Green LED on board is PB_3 D13
+//PwmOut PWM_OSC_IN (A6); // Can alter prescaler can not use A5
+PwmOut PWM_OSC_IN (D6); // Can alter prescaler can not use A5
+PwmOut A_OUT (A2); // Can alter prescaler can not use A5
+//Servo Throttle (A2); // Pin A2, PA3
+//Servo Throttle (A7); // Changed from A2, June 2019
+Servo Throttle (D10); // Changed from A2, June 2019
+DigitalOut EngineTachoOut (D2); // New June 2019
+#endif
+Timer microsecs;
+Ticker loop_timer; // Device to cause periodic interrupts, used to sync iterations of main programme loop - slow
+
+extern eeprom_settings mode ;
+// SYSTEM CONSTANTS
+/* Please Do Not Alter these */
+const int MAIN_LOOP_REPEAT_TIME_US = 31250; // 31250 us, with TACHO_TAB_SIZE = 32 means tacho_ticks_per_time is tacho_ticks_per_second
+const int MAIN_LOOP_ITERATION_Hz = 1000000 / MAIN_LOOP_REPEAT_TIME_US; // = 32 Hz
+//const int FAST_INTERRUPT_RATE = 3125;
+/* End of Please Do Not Alter these */
+/* Global variable declarations */
+uint32_t //semaphore = 0,
+ volt_reading = 0, // Global updated by interrupt driven read of Battery Volts
+ amp_reading = 0,
+ amp_offset = 0,
+ ext_rev_req = 0,
+ driver_reading = 0,
+ tacho_count = 0, // Global incremented on each transition of InterruptIn pulse_tacho
+ tacho_ticks_per_time = 0, // Global tacho ticks in most recent (MAIN_LOOP_REPEAT_TIME_US * TACHO_TABLE_SIZE) micro secs
+ sys_timer32Hz = 0; // gets incremented by our Ticker ISR every MAIN_LOOP_REPEAT_TIME_US
+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
+
+
+const double scale = 0.125;
+const double shrink_by = 1.0 - scale;
+double glob_rpm;
+
+/* End of Global variable declarations */
+
+//void ISR_fast_interrupt () { // here at 10 times main loop repeat rate (i.e. 320Hz)
+void ISR_fast_interrupt () { // here at ** 25 ** times main loop repeat rate (1250us, i.e. 800Hz)
+ static int t = 0;
+ switch (t) {
+ case 0:
+ volt_reading >>= 1; // Result = Result / 2
+ volt_reading += Ain_SystemVolts.read_u16 (); // Result = Result + New Reading
+ break;
+ case 1:
+ amp_reading >>= 1; // Result = Result / 2
+ amp_reading = Ammeter_In.read_u16();
+ break;
+ case 2:
+ amp_offset >>= 1; // Result = Result / 2
+ amp_offset = Ammeter_Ref.read_u16();
+ break;
+ case 3:
+ ext_rev_req >>= 1; // Result = Result / 2
+ ext_rev_req = Ext_Rev_Demand.read_u16();
+ break;
+ case 4:
+ driver_reading >>= 1; // Result = Result / 2
+ driver_reading = Driver_Pot.read_u16();
+ break;
+ case 5:
+// semaphore++;
+ const int TACHO_TABLE_SIZE = MAIN_LOOP_ITERATION_Hz; // Ensures table contains exactly one seconds worth of samples
+ static uint32_t h[TACHO_TABLE_SIZE], // circular buffer to contain list of 'tacho_count's
+ i = 0, last_temp = 0;
+ static double rpm_filt = 0.0;
+ double tmp;
+
+ uint32_t temp = tacho_count; // Read very latest total pulse count from global tacho_count
+ tmp = (double) (temp - last_temp);
+ last_temp = temp;
+#ifdef MAGNETO_SPEED
+ tmp *= (scale * 32.0 * 60.0); // ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#else
+ tmp *= (scale * 32.0 * 60.0 / 12.0); // ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#endif
+ rpm_filt *= shrink_by;
+ rpm_filt += tmp;
+ glob_rpm = rpm_filt;
+
+ tacho_ticks_per_time = temp - h[i]; // latest tacho total pulse count - oldest stored tacho total pulse count
+ h[i] = temp; // latest overwrites oldest in table
+ i++; // index to next table position for next time around
+ if (i >= TACHO_TABLE_SIZE)
+ i = 0; // circular buffer
+ loop_flag = true; // set flag to allow main programme loop to proceed
+ sys_timer32Hz++; // Just a handy measure of elapsed time for anything to use
+ if ((sys_timer32Hz & 0x03) == 0)
+ flag_8Hz = true; // flag gets set 8 times per sec. Other code may clear flag and make use of this
+ break;
+ }
+ t++;
+ if (t > 9)
+ t = 0;
+}
+
+
+
+/** 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.
+* Also, updates global int 'tacho_ticks_per_time' to contain total number of transitions from alternator 'W' terminal in
+* time period from exactly one second ago until now.
+* Increments global 'sys_timer32Hz', 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
+{ // Jan 2019 MAIN_LOOP_REPEAT_TIME_US = 31.25 ms
+ const int TACHO_TABLE_SIZE = MAIN_LOOP_ITERATION_Hz; // Ensures table contains exactly one seconds worth of samples
+ static uint32_t h[TACHO_TABLE_SIZE], // circular buffer to contain list of 'tacho_count's
+ i = 0, last_temp = 0;
+ static double rpm_filt = 0.0;
+ double tmp;
+
+ uint32_t temp = tacho_count; // Read very latest total pulse count from global tacho_count
+ tmp = (double) (temp - last_temp);
+ last_temp = temp;
+#ifdef MAGNETO_SPEED
+ tmp *= (scale * 32.0 * 60.0); // ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#else
+ tmp *= (scale * 32.0 * 60.0 / 12.0); // ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#endif
+// tmp *= (scale * 32.0 * 60.0 / 12.0);
+ rpm_filt *= shrink_by;
+ rpm_filt += tmp;
+ glob_rpm = rpm_filt;
+
+ tacho_ticks_per_time = temp - h[i]; // latest tacho total pulse count - oldest stored tacho total pulse count
+ h[i] = temp; // latest overwrites oldest in table
+ i++; // index to next table position for next time around
+ if (i >= TACHO_TABLE_SIZE)
+ i = 0; // circular buffer
+ loop_flag = true; // set flag to allow main programme loop to proceed
+ sys_timer32Hz++; // Just a handy measure of elapsed time for anything to use
+ if ((sys_timer32Hz & 0x03) == 0)
+ flag_8Hz = true; // flag gets set 8 times per sec. Other code may clear flag and make use of this
+}
+
+
+// New stuff June 2019
+//uint32_t magneto_count = 0;
+#ifdef MAGNETO_SPEED
+bool magneto_stretch = false;
+Timeout magneto_timo;
+uint32_t magneto_times[8] = {0,0,0,0,0,0,0,0};
+
+void ISR_magneto_tacho () ; // New June 2019
+ // Engine On/Off switch turns engine off by shorting magneto to ground.
+ // Therefore have pulse signal one pulse per rev (even tghough 4 stroke, spark delivered at 2 stroke rate)
+ // Pulse spacing 20ms @ 3000 RPM, 60ms @ 1000 RPM, 6ms @ 10000 RPM
+
+ // Relies also on a timeout
+void magneto_timeout ()
+{
+ magneto_stretch = false; // Magneto ringing finished by now, re-enable magneto pulse count
+ EngineTachoOut = 0;
+}
+
+void ISR_magneto_tacho () // Here rising or falling edge of magneto output, not both
+{
+ if (!magneto_stretch)
+ {
+ uint32_t new_time = microsecs.read_us();
+ if (new_time < magneto_times[1]) // rollover detection
+ magneto_times[1] = 0;
+ magneto_times[0] = new_time - magneto_times[1]; // microsecs between most recent two sparks
+ magneto_times[1] = new_time; // actual time microsecs of most recent spark
+ magneto_stretch = true;
+ magneto_timo.attach_us (&magneto_timeout, 5000); // To ignore ringing and multiple counts on magneto output, all settled within about 5ms
+ tacho_count++;
+ EngineTachoOut = 1;
+ }
+}
+
+#endif
+// Endof New stuff June 2019
+
+//uint32_t time_diff;
+/** void ISR_pulse_tacho ()
+*
+*/
+void ISR_pulse_tacho () // Interrupt Service Routine - here after each lo to hi and hi to lo transition on pulse_tacho pin
+{
+// static uint64_t ustot = 0;
+// uint64_t new_time = microsecs.read_high_resolution_us();
+ static uint32_t ustot = 0;
+ uint32_t new_time = microsecs.read_us();
+ if (new_time < ustot) // rollover detection
+ ustot = 0;
+//// time_diff = (uint32_t) new_time - ustot;
+// time_diff = new_time - ustot; // always 0 or positive
+ ustot = new_time;
+ tacho_count++;
+}
+
+uint32_t t_on = 0, t_off = 0, measured_pw_us = 0;
+int ver = 0, vef = 0;
+void ISR_VEXT_rise () // InterruptIn interrupt service
+{ // Here is possible to read back how regulator has controlled pwm
+ ver++;
+ t_on = microsecs.read_us();
+}
+void ISR_VEXT_fall () // InterruptIn interrupt service
+{
+ vef++;
+ t_off = microsecs.read_us();
+ measured_pw_us = t_off - t_on;
+}
+// **** End of Interrupt Service Routines ****
+
+
+/** uint32_t ReadEngineRPM ()
+* System timers arranged such that tacho_ticks_per_time contains most up to the moment count of tacho ticks per second.
+* This * 60 / number of alternator poles gives Revs Per Minute
+* Band pass filter alternator phase output - LF rolloff about 50Hz, HF rolloff about 1500Hz
+*/
+uint32_t ReadEngineRPM ()
+{
+#ifdef MAGNETO_SPEED
+ uint32_t time_since_last_spark = microsecs.read_us() - magneto_times[1];
+ if (time_since_last_spark > 50000) // if engine probably stopped, return old method RPM
+ return tacho_ticks_per_time * 60; // 1 pulse per rev from magneto
+ return (60000000 / magneto_times[0]); // 60 million / microsecs between two most recent sparks
+#else
+ return tacho_ticks_per_time * 60 / 12; // Numof alternator poles, 12, factored in.
+#endif
+}
+
+
+double Read_BatteryVolts ()
+{
+ return (double) volt_reading / 1626.0; // divisor fiddled to make voltage reading correct !
+}
+
+void set_servo (double p) { // Only for test, called from cli
+ Throttle = p;
+}
+
+double normalise (double * p) {
+ if (*p > 0.999)
+ *p = 0.999;
+ if (*p < 0.001)
+ *p = 0.001;
+ return * p;
+}
+
+uint32_t RPM_demand = 0; // For test, set from cli
+void set_RPM_demand (uint32_t d) {
+ if (d < 10)
+ d = 10;
+ if (d > 5600)
+ d = 5600;
+ RPM_demand = d;
+}
+
+extern void command_line_interpreter () ; // Comms with optional pc or device using serial port through board USB socket
+extern bool i2c_init () ;
+extern int check_24LC64 () ;
+
+// Programme Entry Point
+int main()
+{
+ // local variable declarations
+ double servo_position = 0.2; // set in speed control loop
+ double revs_error;
+ int irevs_error;
+ int ticks = 0;
+ const double throttle_limit = 0.4;
+
+ loop_timer.attach_us (&ISR_loop_timer, MAIN_LOOP_REPEAT_TIME_US); // Start periodic interrupt generator
+#ifdef MAGNETO_SPEED
+ pc.printf ("Magneto Mode\r\n");
+ pulse_tacho.fall (&ISR_magneto_tacho); // 1 pulse per engine rev
+#else
+ pc.printf ("Alternator W signal Mode\r\n");
+ pulse_tacho.rise (&ISR_pulse_tacho); // Handles - Transition on filtered input version of alternator phase output
+ pulse_tacho.fall (&ISR_pulse_tacho); //
+#endif
+ VEXT.rise (&ISR_VEXT_rise); // Handles - MCP1630 has just turned mosfet on
+ VEXT.fall (&ISR_VEXT_fall); // Handles - MCP1630 has just turned mosfet off
+ microsecs.reset() ; // timer = 0
+ microsecs.start () ; // 64 bit, counts micro seconds and times out in half million years
+ PWM_OSC_IN.period_us (PWM_PERIOD_US); // about 313Hz
+ PWM_OSC_IN.pulsewidth_us (9); // value is int
+ A_OUT.period_us (100);
+ A_OUT.pulsewidth_us (19);
+ Throttle = servo_position;
+ pc.printf ("\r\n\n\n\n\nAlternator Regulator 2019, Jon Freeman, SystemCoreClock=%d\r\n", SystemCoreClock);
+ if (!i2c_init())
+ pc.printf ("i2c bus failed init\r\n");
+ // end of local variable declarations
+ pc.printf ("check_24LC64 returned 0x%x\r\n", check_24LC64());
+ mode.load () ; // Fetch values from eeprom, also builds table of speed -> pwm lookups
+
+ // Setup Complete ! Can now start main control forever loop.
+
+//***** START OF MAIN 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
+ } // Jan 2019 pass here 32 times per sec
+ loop_flag = false; // Clear flag set by ticker interrupt handler
+#ifdef SPEED_CONTROL_ENABLE
+// uint32_t RPM_demand = 0; // For test, set from cli
+// double servo_position = 0.0; // set in speed control loop
+// double revs_error;
+
+// time_since_last_spark = microsecs.read_us() - magneto_times[1];
+ irevs_error = RPM_demand - ReadEngineRPM ();
+ revs_error = (double) irevs_error;
+ if (RPM_demand < 3000)
+ servo_position = Throttle = 0.0;
+ else {
+ servo_position += (revs_error / 75000.0);
+ servo_position = normalise(&servo_position);
+ if (servo_position < 0.0 || servo_position > 1.0)
+ pc.printf ("servo_position error %f\r\n", servo_position);
+ if (servo_position > throttle_limit)
+ servo_position = throttle_limit;
+ Throttle = servo_position;
+ }
+#endif
+
+ PWM_OSC_IN.pulsewidth_us (mode.get_pwm((int)glob_rpm)); // Update field current according to latest measured RPM
+
+// while (LocalCom.readable()) {
+// int q = LocalCom.getc();
+// //q++;
+// pc.putc (q);
+// }
+
+ if (flag_8Hz) { // Do any stuff to be done 8 times per second
+ flag_8Hz = false;
+ myled = !myled;
+ LocalCom.printf ("%d\r\n", volt_reading);
+
+ ticks++;
+ if (ticks > 7) { // once per sec stuff
+ ticks = 0;
+ pc.printf ("RPM %d, err %.1f, s_p %.2f\r\n", ReadEngineRPM (), revs_error, servo_position);
+ } // eo once per second stuff
+ } // End of if(flag_8Hz)
+ } // End of main programme loop
+} // End of main function - end of programme
+//***** END OF MAIN LOOP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed.bld Fri Jun 28 19:32:51 2019 +0000 @@ -0,0 +1,1 @@ +https://os.mbed.com/users/mbed_official/code/mbed/builds/65be27845400 \ No newline at end of file