32 x IO/Servo Hat – rev 1.5

This is a new version of my 32 x IO/Servo Hat. I have always been a bit unhappy with pin-header connectors since they can be disconnected easily, so I decided to try how a surface mounted 2-pin JST worked out and voila – 32 2 pin connectors with signal and ground. This means I drop the +Voltage signal and expect that to be fed from somwhere else – this is actually good as it makes it easy to mix 5V, 12V and 24V servo solutions + these connectors are good as they will not drop out due to vibration.

This is one of my simplest, but yet most usable Hat’s and this connector solution makes a great addition and enable it to be used as part of an automobile stack without being worried about connectors falling off.

New Motor Driver – DRV8353RS

I am currently using DRV8301 from TI and is very happy with that one, but I want to add a 3rd Current Sensor so I can detect sensor errors and my eyes fell on DRV8353 and DRV8323 series. DRV8323 is 60V, while DRV8353 is 100V. I don’t mind the increase in voltage. The numbering is a bit confusing at first, but I copncluded on the following:

DRV8053S or DRV8353H have Current amplifiers, but lack Buck Converter.

DRV8053R have both current and buck.

DRV8353RH have Hardware Interface

DRV8353RS have SPI Interface

I will be using DRV8353RS (I think). The SPI opens for a bit more config options and use less pins that the Hardware version. Below is an anotated block diagram of the new driver.

23 external components (MOSFET’s excluded) and a smaller 7x7mm package will be interesting. I need to study this and find reference schematics before I start. Cost of this is ca 4ich USD.

This might be on Revision 1.4 of MC3P60V50A – which actually become MC3P100V… to be accurate.

 

ISO1042 Galvanic CANbus

I recently discovered this chip (ISO1042) and wanted to share a circuit I found. This circuit is excellent, but it has two flaws:

Firstly you need a small isolation coil 10-20uH between ISO 1042 and VCC1. This act as a filter so that higher frequencies used by the buck converter in ISO1042 do not disturb the MCU. I need to check frequencies, but my previous experience indicate that a 10uH coil will do the job.

Secondly I have marked a red cross over D1 – This is a common way of protecting non-isolated CAN, but I have always been against it. Usually the tranceiver circuits can handle very hard pulses and are designed to deal with CANH and CANL beeing floating, differential signals. If you put D1 on you will be forced to include GND2 as the 3rd wire, but without D1 you only need CANH/CANL. In this case we use a galvanic isolated circuit that is far more capable that D1 and can handle pulses in KV area.

As for K1 (TLP175A) this is a nice way of making the terminator switchable through software. I have never bothered much about these, but it can be nice if you connect several devices in a network and need to select whom is switched on/off.

ISO1042 speaks for itself – it is 5 x 7mm, so this is a SOIC8, but notice that it has no external components except two standard filter caps on voltage. I am curious to test this and see how much heat it generate. I previously used chips from a different vendor and was a bit surpriced by the heat and noise from the buck converter.

Another feature on ISO1042 is that it is CAN FD (5Mbps) capable.

Motor Algorithm – Part 4 – PWM Output

My previous entry show how to pre-calculate a Sinus table so we avoid doing this full speed because the next step is to convert this into a PWM pulse. A PWM pulse is measured in time – length – so we need to know the max length of a pulse. That is decided by the frequency we use. 4000Hz is really a minimum, thought if you drive a slow motor you can get away with a slower algorithm. This is the frequency of the timer interrupt we will use to re-calculate PWM output, so a pulse of 1 is 1/4000 in length.

The second is that we need to apply torque where “1” will be 100% torque, 0.5 will be 50% torque etc.

The third element is to scale to a length number matching the timer we use. To sum this up I pre-calculate a pwmFactor as follows:

pwmFactor = 1/4000*timerScale*torqueFactor;

I only need to update this if I change torque. This now gives me a factor I can multiply with the vector to calculate the length of the PWM pulse for each phase.

aPWM = vector[x].a * pwmFactor;
bPWM = vector[x].b * pwmFactor;
cPWM = vector[x].c * pwmFactor;

The final step is to output this pulse by switching pins on/off. Assuming I did not have a Hardware timer I might need to create a much faster interrupt that only switched pins on/off, but luckily the motor timers on MCU’s like STM32F405 will do this for us – I will be using Timer 1 that is a specialized motor timer that will do a lot of the work that otherwise would be hard to achieve – hard, not impossible. We run motors using slow AVR’s and PIC’s, but using a modern MCU with a motor timer is just so much easier.

As I am driving blind-folded with no knowledge of my current rotor position I just do exactly the same as in my Trapezoidal example and iterate through the table. The speed I iterate  (change sinus entry) is now the motor speed. Assuming you use 4000 Hertz and have a motor with only 3 coils you can routhly acieve 10 rotations per sec without skipping vector entryies. This is ca 600 RPM, so if you use a faster motor you really should increase freuency, but increased frequency means more CPU used for math and more loss in the MOSFET’s. You also have an upper limit of what driver, MOSFET and motor will support. A common range is 4000 to 20,000 Hertz.

At this point I don’t know the current rotor position so I just use the vector table knowing that as I iterate the motor will be moving most efficient on a 90 degree vector as illustrated below:

If I had known the current rotor position I would have looked up the vector table 90 degrees before or after the current rotor position. But, to do so I need to use BEMF, Hall or current sensors to calculate my position. In theory we could calculate this every time we create a PWM output, but we face two challenges (1) CPU hungry math and (2) inaccurate input.

Phase currents can in theory be measured calculated for every PWM output, but you usually have so much noise that you end up filtering – meaning you will not have ADC measured currents as often as you output PWM. Hall have a lower accuracy. So the real algorithm usually use a trick where we use sensors to correct rotor position.

By adding rotor vector calculations I basically are doing FOC (Field Oriented Control). I will be using both Current- and Hall sensors. My cutter motor have no Hall and it will be driving fast so this is excellent for current sensors. The wheel drivers do however have Hall sensors and will be driving slow – I do not expect any valid input from current sensors on the wheels, but we will see – so I will be driving based on Hall only.

One advice – before starting putting on PWM on a motor you need to activate temperature- and current- damage thresholds. I have four temperature sensors and two of them are located in between MOSFET’s. If the temperature raise fast or we ever achieve a selected threshold we simply cut the motor to avoid that electronics get damaged.

I also need to do this on phase currents – the MOSFET I use have a maximum of 100A, so if we ever reach – lets say 75A – we cut the motor. Maximum pulse is 400A. This is MOSFET specific data and I have used a wide SOP8 with padding underneath – a package used by several MOSFET’s so I can adapt MOSFET to application – I have 60V MOSFET’s, but I am using IRFP5300 since I had a bunch of them – this have a RDS=1.1mOhm, 30V, 100A etc – excellent for my current applications since I will be using 18V batteries from a local DIY shop.

Two numbers on a MOSFET are very important – (1) RDS that needs to be as low as possible and (2) switching time that needs to be as fast (short) as possible. As we switch we move into an area where the MOSFET will consume more heat – a low frequence is good as we switch more seldom, but a low frequency is no good for faster motors – this is a tradeoff you need to make knowing that higher freuencies will increasingly heat up your MOSFETs. For a SOP8 style package I assume max 1W dissipation without heatsink – meaning that if we burn more than 1W on the MOSFET temperature starts to raise fast. This imporves with heatsink that I have on each driver – but we are now into the discussions about my boards limitations – my target was 50A, but at some point I will destroy boards to learn these numbers.

I just tested PWM outputs on my board and is happy to see they work, so I only need to get temperature- and current- sensors working and I will be spinning the larger motors.

Motor Algorithm – Part 3 – Vector Table

As mentioned in part2 I want to create a 360 degree vector table with PWM duties for A,B and C. This allows me to drive sinusoidal by simply stepping 0 to 359. To do this I create a small C application as follows:

#include "stdio.h"
#include "stdlib.h"
#include "math.h"
int main(int argc, char* argv)
{
	FILE* fd = fopen("vectors.cpp", "w");
	if (fd != NULL)
	{
		fprintf(fd, "alSinusVector _vector[AL3P_VECTOR_SIZE]=\n");
		fprintf(fd, "{\n");
		for (int i = 0; i < 360; i++)
		{
			double vA, vB, vC;
			
			vA = sin(i*3.14/180.0);
			vB = sin((i + 120)*3.14/180.0);
			vC = sin((i + 120 + 120)*3.14/180.0);
			fprintf(fd, "    %f, %f, %f,		// %d\n",vA,vB,vC, i);
		}
		fprintf(fd, "};\n");
 
		fclose(fd);
	}
}

The math is vA = sin (radians(degrees)), while vB is +120 degrees etc. This will generate a table with values -1 to +1. I use the – kow if I should swith on High or Low MOSFET and the value to compute a duty. The only thing I need to do is actually to multiply with torque + I control speed with how fast I step this. The generated table is below – I have to test this, but looking at the values I think it should work. Note that I generate all 3 vectors here, but you can actually manage with only one since B is A+120 etc.

alSinusVector al3PhaseMotor::m_vector[AL3P_VECTOR_SIZE]=
{
0.000000, 0.866556, -0.864962, // 0
0.017444, 0.857718, -0.873584, // 1
0.034882, 0.848620, -0.881940, // 2
0.052309, 0.839263, -0.890028, // 3
0.069721, 0.829651, -0.897846, // 4
0.087112, 0.819786, -0.905390, // 5
0.104476, 0.809672, -0.912658, // 6
0.121808, 0.799311, -0.919649, // 7
0.139103, 0.788708, -0.926360, // 8
0.156356, 0.777864, -0.932789, // 9
0.173561, 0.766783, -0.938934, // 10
0.190713, 0.755470, -0.944793, // 11
0.207808, 0.743926, -0.950365, // 12
0.224839, 0.732156, -0.955648, // 13
0.241802, 0.720163, -0.960640, // 14
0.258691, 0.707951, -0.965339, // 15
0.275501, 0.695523, -0.969745, // 16
0.292228, 0.682884, -0.973856, // 17
0.308866, 0.670038, -0.977670, // 18
0.325409, 0.656987, -0.981187, // 19
0.341854, 0.643736, -0.984406, // 20
0.358194, 0.630289, -0.987324, // 21
0.374426, 0.616651, -0.989943, // 22
0.390544, 0.602825, -0.992260, // 23
0.406543, 0.588816, -0.994275, // 24
0.422418, 0.574627, -0.995988, // 25
0.438164, 0.560263, -0.997397, // 26
0.453778, 0.545729, -0.998503, // 27
0.469253, 0.531029, -0.999305, // 28
0.484585, 0.516168, -0.999803, // 29
0.499770, 0.501149, -0.999997, // 30
0.514803, 0.485978, -0.999887, // 31
0.529679, 0.470659, -0.999472, // 32
0.544394, 0.455196, -0.998753, // 33
0.558943, 0.439595, -0.997730, // 34
0.573323, 0.423861, -0.996404, // 35
0.587528, 0.407997, -0.994774, // 36
0.601554, 0.392009, -0.992842, // 37
0.615396, 0.375902, -0.990607, // 38
0.629052, 0.359681, -0.988072, // 39
0.642516, 0.343350, -0.985235, // 40
0.655785, 0.326915, -0.982099, // 41
0.668854, 0.310380, -0.978663, // 42
0.681720, 0.293751, -0.974930, // 43
0.694378, 0.277032, -0.970901, // 44
0.706825, 0.260229, -0.966575, // 45
0.719057, 0.243347, -0.961956, // 46
0.731070, 0.226391, -0.957044, // 47
0.742861, 0.209365, -0.951841, // 48
0.754425, 0.192277, -0.946348, // 49
0.765760, 0.175129, -0.940567, // 50
0.776862, 0.157929, -0.934500, // 51
0.787727, 0.140680, -0.928149, // 52
0.798353, 0.123389, -0.921515, // 53
0.808736, 0.106059, -0.914600, // 54
0.818873, 0.088698, -0.907408, // 55
0.828760, 0.071310, -0.899939, // 56
0.838396, 0.053900, -0.892196, // 57
0.847776, 0.036473, -0.884182, // 58
0.856898, 0.019036, -0.875899, // 59
0.865760, 0.001593, -0.867350, // 60
0.874358, -0.015851, -0.858536, // 61
0.882690, -0.033290, -0.849461, // 62
0.890753, -0.050719, -0.840128, // 63
0.898546, -0.068132, -0.830539, // 64
0.906065, -0.085525, -0.820697, // 65
0.913308, -0.102892, -0.810605, // 66
0.920273, -0.120227, -0.800267, // 67
0.926958, -0.137526, -0.789686, // 68
0.933361, -0.154783, -0.778864, // 69
0.939481, -0.171992, -0.767805, // 70
0.945314, -0.189150, -0.756512, // 71
0.950859, -0.206250, -0.744989, // 72
0.956116, -0.223287, -0.733240, // 73
0.961081, -0.240256, -0.721267, // 74
0.965754, -0.257152, -0.709075, // 75
0.970133, -0.273970, -0.696667, // 76
0.974217, -0.290704, -0.684047, // 77
0.978004, -0.307350, -0.671219, // 78
0.981494, -0.323903, -0.658187, // 79
0.984685, -0.340357, -0.644954, // 80
0.987576, -0.356707, -0.631525, // 81
0.990167, -0.372949, -0.617904, // 82
0.992456, -0.389077, -0.604095, // 83
0.994444, -0.405087, -0.590102, // 84
0.996129, -0.420974, -0.575930, // 85
0.997511, -0.436732, -0.561582, // 86
0.998589, -0.452358, -0.547063, // 87
0.999363, -0.467846, -0.532378, // 88
0.999834, -0.483191, -0.517531, // 89
1.000000, -0.498390, -0.502527, // 90
0.999861, -0.513437, -0.487369, // 91
0.999419, -0.528328, -0.472063, // 92
0.998672, -0.543057, -0.456614, // 93
0.997622, -0.557622, -0.441025, // 94
0.996268, -0.572017, -0.425303, // 95
0.994610, -0.586238, -0.409451, // 96
0.992650, -0.600281, -0.393474, // 97
0.990388, -0.614140, -0.377378, // 98
0.987825, -0.627813, -0.361167, // 99
0.984961, -0.641295, -0.344846, // 100
0.981797, -0.654582, -0.328419, // 101
0.978335, -0.667670, -0.311894, // 102
0.974575, -0.680554, -0.295273, // 103
0.970518, -0.693231, -0.278562, // 104
0.966166, -0.705698, -0.261766, // 105
0.961520, -0.717949, -0.244891, // 106
0.956581, -0.729982, -0.227942, // 107
0.951351, -0.741793, -0.210923, // 108
0.945832, -0.753379, -0.193839, // 109
0.940025, -0.764735, -0.176697, // 110
0.933932, -0.775858, -0.159501, // 111
0.927555, -0.786745, -0.142257, // 112
0.920895, -0.797393, -0.124969, // 113
0.913955, -0.807798, -0.107643, // 114
0.906737, -0.817958, -0.090284, // 115
0.899244, -0.827868, -0.072898, // 116
0.891476, -0.837527, -0.055490, // 117
0.883437, -0.846930, -0.038065, // 118
0.875130, -0.856076, -0.020628, // 119
0.866556, -0.864962, -0.003185, // 120
0.857718, -0.873584, 0.014259, // 121
0.848620, -0.881940, 0.031698, // 122
0.839263, -0.890028, 0.049128, // 123
0.829651, -0.897846, 0.066543, // 124
0.819786, -0.905390, 0.083938, // 125
0.809672, -0.912658, 0.101307, // 126
0.799311, -0.919649, 0.118646, // 127
0.788708, -0.926360, 0.135948, // 128
0.777864, -0.932789, 0.153209, // 129
0.766783, -0.938934, 0.170423, // 130
0.755470, -0.944793, 0.187586, // 131
0.743926, -0.950365, 0.204691, // 132
0.732156, -0.955648, 0.221734, // 133
0.720163, -0.960640, 0.238710, // 134
0.707951, -0.965339, 0.255613, // 135
0.695523, -0.969745, 0.272438, // 136
0.682884, -0.973856, 0.289180, // 137
0.670038, -0.977670, 0.305834, // 138
0.656987, -0.981187, 0.322396, // 139
0.643736, -0.984406, 0.338859, // 140
0.630289, -0.987324, 0.355219, // 141
0.616651, -0.989943, 0.371471, // 142
0.602825, -0.992260, 0.387609, // 143
0.588816, -0.994275, 0.403630, // 144
0.574627, -0.995988, 0.419528, // 145
0.560263, -0.997397, 0.435299, // 146
0.545729, -0.998503, 0.450937, // 147
0.531029, -0.999305, 0.466438, // 148
0.516168, -0.999803, 0.481796, // 149
0.501149, -0.999997, 0.497009, // 150
0.485978, -0.999887, 0.512070, // 151
0.470659, -0.999472, 0.526975, // 152
0.455196, -0.998753, 0.541719, // 153
0.439595, -0.997730, 0.556299, // 154
0.423861, -0.996404, 0.570710, // 155
0.407997, -0.994774, 0.584947, // 156
0.392009, -0.992842, 0.599006, // 157
0.375902, -0.990607, 0.612883, // 158
0.359681, -0.988072, 0.626573, // 159
0.343350, -0.985235, 0.640072, // 160
0.326915, -0.982099, 0.653377, // 161
0.310380, -0.978663, 0.666483, // 162
0.293751, -0.974930, 0.679386, // 163
0.277032, -0.970901, 0.692083, // 164
0.260229, -0.966575, 0.704568, // 165
0.243347, -0.961956, 0.716840, // 166
0.226391, -0.957044, 0.728893, // 167
0.209365, -0.951841, 0.740724, // 168
0.192277, -0.946348, 0.752330, // 169
0.175129, -0.940567, 0.763708, // 170
0.157929, -0.934500, 0.774852, // 171
0.140680, -0.928149, 0.785761, // 172
0.123389, -0.921515, 0.796431, // 173
0.106059, -0.914600, 0.806858, // 174
0.088698, -0.907408, 0.817040, // 175
0.071310, -0.899939, 0.826974, // 176
0.053900, -0.892196, 0.836655, // 177
0.036473, -0.884182, 0.846082, // 178
0.019036, -0.875899, 0.855252, // 179
0.001593, -0.867350, 0.864161, // 180
-0.015851, -0.858536, 0.872808, // 181
-0.033290, -0.849461, 0.881188, // 182
-0.050719, -0.840128, 0.889301, // 183
-0.068132, -0.830539, 0.897143, // 184
-0.085525, -0.820697, 0.904712, // 185
-0.102892, -0.810605, 0.912006, // 186
-0.120227, -0.800267, 0.919022, // 187
-0.137526, -0.789686, 0.925759, // 188
-0.154783, -0.778864, 0.932213, // 189
-0.171992, -0.767805, 0.938385, // 190
-0.189150, -0.756512, 0.944270, // 191
-0.206250, -0.744989, 0.949868, // 192
-0.223287, -0.733240, 0.955178, // 193
-0.240256, -0.721267, 0.960196, // 194
-0.257152, -0.709075, 0.964923, // 195
-0.273970, -0.696667, 0.969355, // 196
-0.290704, -0.684047, 0.973493, // 197
-0.307350, -0.671219, 0.977335, // 198
-0.323903, -0.658187, 0.980879, // 199
-0.340357, -0.644954, 0.984124, // 200
-0.356707, -0.631525, 0.987070, // 201
-0.372949, -0.617904, 0.989716, // 202
-0.389077, -0.604095, 0.992061, // 203
-0.405087, -0.590102, 0.994104, // 204
-0.420974, -0.575930, 0.995844, // 205
-0.436732, -0.561582, 0.997281, // 206
-0.452358, -0.547063, 0.998415, // 207
-0.467846, -0.532378, 0.999245, // 208
-0.483191, -0.517531, 0.999770, // 209
-0.498390, -0.502527, 0.999992, // 210
-0.513437, -0.487369, 0.999909, // 211
-0.528328, -0.472063, 0.999522, // 212
-0.543057, -0.456614, 0.998831, // 213
-0.557622, -0.441025, 0.997836, // 214
-0.572017, -0.425303, 0.996538, // 215
-0.586238, -0.409451, 0.994936, // 216
-0.600281, -0.393474, 0.993031, // 217
-0.614140, -0.377378, 0.990824, // 218
-0.627813, -0.361167, 0.988316, // 219
-0.641295, -0.344846, 0.985506, // 220
-0.654582, -0.328419, 0.982397, // 221
-0.667670, -0.311894, 0.978989, // 222
-0.680554, -0.295273, 0.975283, // 223
-0.693231, -0.278562, 0.971281, // 224
-0.705698, -0.261766, 0.966983, // 225
-0.717949, -0.244891, 0.962390, // 226
-0.729982, -0.227942, 0.957505, // 227
-0.741793, -0.210923, 0.952328, // 228
-0.753379, -0.193839, 0.946861, // 229
-0.764735, -0.176697, 0.941107, // 230
-0.775858, -0.159501, 0.935066, // 231
-0.786745, -0.142257, 0.928740, // 232
-0.797393, -0.124969, 0.922132, // 233
-0.807798, -0.107643, 0.915243, // 234
-0.817958, -0.090284, 0.908076, // 235
-0.827868, -0.072898, 0.900632, // 236
-0.837527, -0.055490, 0.892915, // 237
-0.846930, -0.038065, 0.884925, // 238
-0.856076, -0.020628, 0.876667, // 239
-0.864962, -0.003185, 0.868141, // 240
-0.873584, 0.014259, 0.859351, // 241
-0.881940, 0.031698, 0.850300, // 242
-0.890028, 0.049128, 0.840990, // 243
-0.897846, 0.066543, 0.831425, // 244
-0.905390, 0.083938, 0.821606, // 245
-0.912658, 0.101307, 0.811537, // 246
-0.919649, 0.118646, 0.801221, // 247
-0.926360, 0.135948, 0.790662, // 248
-0.932789, 0.153209, 0.779862, // 249
-0.938934, 0.170423, 0.768824, // 250
-0.944793, 0.187586, 0.757553, // 251
-0.950365, 0.204691, 0.746051, // 252
-0.955648, 0.221734, 0.734322, // 253
-0.960640, 0.238710, 0.722369, // 254
-0.965339, 0.255613, 0.710197, // 255
-0.969745, 0.272438, 0.697809, // 256
-0.973856, 0.289180, 0.685208, // 257
-0.977670, 0.305834, 0.672399, // 258
-0.981187, 0.322396, 0.659385, // 259
-0.984406, 0.338859, 0.646170, // 260
-0.987324, 0.355219, 0.632759, // 261
-0.989943, 0.371471, 0.619156, // 262
-0.992260, 0.387609, 0.605363, // 263
-0.994275, 0.403630, 0.591387, // 264
-0.995988, 0.419528, 0.577231, // 265
-0.997397, 0.435299, 0.562899, // 266
-0.998503, 0.450937, 0.548396, // 267
-0.999305, 0.466438, 0.533726, // 268
-0.999803, 0.481796, 0.518893, // 269
-0.999997, 0.497009, 0.503903, // 270
-0.999887, 0.512070, 0.488759, // 271
-0.999472, 0.526975, 0.473467, // 272
-0.998753, 0.541719, 0.458030, // 273
-0.997730, 0.556299, 0.442454, // 274
-0.996404, 0.570710, 0.426744, // 275
-0.994774, 0.584947, 0.410903, // 276
-0.992842, 0.599006, 0.394938, // 277
-0.990607, 0.612883, 0.378852, // 278
-0.988072, 0.626573, 0.362651, // 279
-0.985235, 0.640072, 0.346340, // 280
-0.982099, 0.653377, 0.329923, // 281
-0.978663, 0.666483, 0.313406, // 282
-0.974930, 0.679386, 0.296794, // 283
-0.970901, 0.692083, 0.280091, // 284
-0.966575, 0.704568, 0.263303, // 285
-0.961956, 0.716840, 0.246435, // 286
-0.957044, 0.728893, 0.229492, // 287
-0.951841, 0.740724, 0.212479, // 288
-0.946348, 0.752330, 0.195402, // 289
-0.940567, 0.763708, 0.178264, // 290
-0.934500, 0.774852, 0.161073, // 291
-0.928149, 0.785761, 0.143833, // 292
-0.921515, 0.796431, 0.126549, // 293
-0.914600, 0.806858, 0.109226, // 294
-0.907408, 0.817040, 0.091870, // 295
-0.899939, 0.826974, 0.074487, // 296
-0.892196, 0.836655, 0.057080, // 297
-0.884182, 0.846082, 0.039656, // 298
-0.875899, 0.855252, 0.022221, // 299
-0.867350, 0.864161, 0.004778, // 300
-0.858536, 0.872808, -0.012666, // 301
-0.849461, 0.881188, -0.030106, // 302
-0.840128, 0.889301, -0.047537, // 303
-0.830539, 0.897143, -0.064954, // 304
-0.820697, 0.904712, -0.082351, // 305
-0.810605, 0.912006, -0.099723, // 306
-0.800267, 0.919022, -0.117064, // 307
-0.789686, 0.925759, -0.134370, // 308
-0.778864, 0.932213, -0.151635, // 309
-0.767805, 0.938385, -0.168854, // 310
-0.756512, 0.944270, -0.186021, // 311
-0.744989, 0.949868, -0.203132, // 312
-0.733240, 0.955178, -0.220181, // 313
-0.721267, 0.960196, -0.237163, // 314
-0.709075, 0.964923, -0.254073, // 315
-0.696667, 0.969355, -0.270905, // 316
-0.684047, 0.973493, -0.287655, // 317
-0.671219, 0.977335, -0.304318, // 318
-0.658187, 0.980879, -0.320888, // 319
-0.644954, 0.984124, -0.337360, // 320
-0.631525, 0.987070, -0.353729, // 321
-0.617904, 0.989716, -0.369991, // 322
-0.604095, 0.992061, -0.386141, // 323
-0.590102, 0.994104, -0.402173, // 324
-0.575930, 0.995844, -0.418082, // 325
-0.561582, 0.997281, -0.433864, // 326
-0.547063, 0.998415, -0.449515, // 327
-0.532378, 0.999245, -0.465028, // 328
-0.517531, 0.999770, -0.480400, // 329
-0.502527, 0.999992, -0.495626, // 330
-0.487369, 0.999909, -0.510701, // 331
-0.472063, 0.999522, -0.525620, // 332
-0.456614, 0.998831, -0.540380, // 333
-0.441025, 0.997836, -0.554975, // 334
-0.425303, 0.996538, -0.569401, // 335
-0.409451, 0.994936, -0.583654, // 336
-0.393474, 0.993031, -0.597730, // 337
-0.377378, 0.990824, -0.611623, // 338
-0.361167, 0.988316, -0.625331, // 339
-0.344846, 0.985506, -0.638848, // 340
-0.328419, 0.982397, -0.652171, // 341
-0.311894, 0.978989, -0.665295, // 342
-0.295273, 0.975283, -0.678217, // 343
-0.278562, 0.971281, -0.690932, // 344
-0.261766, 0.966983, -0.703437, // 345
-0.244891, 0.962390, -0.715728, // 346
-0.227942, 0.957505, -0.727802, // 347
-0.210923, 0.952328, -0.739654, // 348
-0.193839, 0.946861, -0.751280, // 349
-0.176697, 0.941107, -0.762678, // 350
-0.159501, 0.935066, -0.773845, // 351
-0.142257, 0.928740, -0.784775, // 352
-0.124969, 0.922132, -0.795467, // 353
-0.107643, 0.915243, -0.805917, // 354
-0.090284, 0.908076, -0.816121, // 355
-0.072898, 0.900632, -0.826077, // 356
-0.055490, 0.892915, -0.835782, // 357
-0.038065, 0.884925, -0.845232, // 358
-0.020628, 0.876667, -0.854426, // 359
};

Motor Algorithm – Part 2 – Sinusoidal

In the Trapezoidal algorithm we drove the field using six steps which works, but it is very inaccurate. I would like more steps and to do that I need to use a Sinusoidal algorithm.

Sinusoidal means we create a sinus wave using PWM duty, in fact we create 3 sinus waves 120 degrees apart to rotate the field with more steps. To illustrate this I will build on the Trapezoidal algorithm and expand the number of steps it uses:

  • A+ (100% duty), B- (100% duty) C (off)
  • A+ (100% duty), (B-  off), C- (100% duty)

This is out starting point. In the Trapezoidal example we applied the next step A+(100%/C-(100), but what we now will do is to move more gracefuly between B- to C- by adding 50% duty steps.

  • A+ (100% duty), B- (100% duty) C (off)
  • A+(100% duty), (B- 50% duty), C- (50% duty)
  • A+(100% duty), (B-  off), C- (100% duty)

By doing this we have basically modified a 6 step Trapezoidal to be a 12 step Sinusoidal and illustrated how we can use PWM duty to create a full sinusoidal algorithm.

The illustrations above illustrate the original Trapezoidal algorithm with the steps A+/B- and A+/C-. Vectors will in this case jump 45 degrees.

The allistration above show the difference and what we achieve by introducing a new 50% duty step as we now have 22,5 degree jumps. We can now build on this and create a full 360 degree sinusoidal algorithm. Some Sinusoidal algorithms pre-calculate a 360 entry vector table with PWM out duty for A.B and C using index as the input vector. Assuming we use a 4 byte duty number (float) and 360 entries we end up with a 4320 byte lookup table. This is a decent tradeoff to avoid doing all the math real-time.

Just to remind everyone – the picture above is a common propeller motor and while it is still 3-phase it have something like 36 coils which will be A,B and C repeated over and over. This means that a 360 degree Sinusoidal and even a 6 step Trapezoidal might be far more accurate than you expect based on the theoretical 3-phase drawings. You need to know the number of coils to know your speed.

Motor Algorithm – Part 1 – Trapezoidal

I will try to annotate the motor driver algorithms starting with a simple, brute force Trapezoidal algorithm. If you try to read papers on motor drivers you will see a lot of advanced math, but you will find very few papers explaining how simple it actually is, so I will try to do that here.

I borrowed the excellent drawing below that illustrate the 3-phase motors with windiings A,B and C. Actual motors have more windigs. You will find 6, 9, 12 windings and more on actual motors, but the concept is the same. To drive this we need to apply a pulse on A, B and C in sequence.

If you look at the windings you will see that A alone can’t drive anything, so to actually have a coil you will need to apply + on A and – on B or C. This leads us to the simplest of the algorithms where you just apply pulses in sequences over and over again.

  1. A+ B- (C is off)
  2. A+ C- (B is off)
  3. B+ C- (A is off)
  4. B+ A- (C is off)
  5. C+ A- (B is off)
  6. C+ B- (A is off)

A simple Trapezoidal will apply the pulse in sufficient length so the motor is garanteed to step one step. But, as you don’t know the current position you might have 5 steps before your motor starts. As we drive blindfolded we increase speed by making the steps faster. To drive the other direction we just reverse the sequence.

Trapezoldal is excellent to drive a motor very slowly and it is easy to code a working example. As we in this example drive without any sensors whatsoever we just have to assume that the motor follow our directions. This can be a bit tricky as we will not detect if the motor stalls and as the sequence goes wrong we just add the the problem. To cope with this we can add sensors.

BEMF basically measure the voltage on the phase we don’t use as this can tell us the actual position.

Phase Current is the current in/out of each coil that can be used to compute the rotor position. The challenge with this is that it needs a bit of speed before the currents become notifyable + it can be very sensitive for noise situations.

Hall sensors are magnetic delectors that will create a sinus as the motor rotates. This can be measured and used to compute rotor position.

Encoders can be put on shafts to accurate measure position.

Lawn Mover – Motor Algorithm

Running a BLDC (3-phase motor) you need to insert and tune some parameters with regards to size of motor, number of winding, how much current do you output etc. But, more important is the algorithm and technique you chose matching the job at hand. You have three different algorithms (FOC, Sinusoidal, Trapezoidal) and a variety of input sensors techiques (BEMF, Hall, Current and position encoders).

FOC (Field Oriented Vector) is excellent for running fast motors, but the algorithm requires heavy math and current sensors that don’t work that well on slow speeds giving me a problem on the lawn mower wheels. Classic Trapezoidal or Sinusoidal combined with Hall sensors do actually have an advantage at very low speeds. Both are also table driven, meaning we can act more or less as a stepper motor. The accuracy of Hall sensors are not even close to that of a stepper, but we have a belt that introduce a gear ratio that in effect will increase the accuracy.

Sinusoidal can be calculated, but a neat trick is to pre-calculate x number of vectors in a table.

Vector driven means we use current, hall, bemf or a position encoder to detect rotator position and calculate an output vector that is 90 degrees – this is where FOC is good as it is more efficient than the other algorithms assuming it has accurate current sensors. All methods can be used without any sensor input – running a BLDC with Trapezoidal with no sensors is very easy as you just rotate the field based on timing outputting one PWM combination at the time. It work decently well assuming the motor follow your output, but as you are blind for the actual position you get a glitch at start or if the motor is stuck. It is also very difficult to get up in higher speeds, but this work excellent at low speeds.

I have current sensors on the drivers, but as I will be driving slow I expect there to be more noise than input on the wheel drivers. Hall Sensors are far more reliable as they work even at stand-still.

In my case I will just use Trapezoidal on the wheels for now since it is dead easy to code up and fits well with what I need to do. Trapezoidal combined with Hall sensors should work just fine. Having three Hall sensors I will get an encoder that givers me 0 to 7 as input – 8 positions. These will give me the ca position of the rotor and should be sufficient to index the next step in a Trapezoidal algorithm that easily can be adjusted to this.

Changing subject to the grass cutter I basically need a different approach as I in this case is interested in running at 80% possible speed (80% speed is ca 50% efficiency) on a 1,5KW motor. In this case I don’t have hall sensors, but as I run faster I should have working current sensors.

One challenge is however that as I start the cutter I don’t know the position, so I do a trick and start running the motor Trapezoidal until I get readings and from there I run FOC (or sinusoidal). By doing this I take advantage of the fact that slow Trapezoidal will more or less force the motor from stand-still up in a minimum speed where I can start accelerating based on phase current readings.

At this point I am only interested in spinning the motors – I will implement more optimized algorithm’s later. My decition is basically not to use time on this at precent because motor algorithms and optimization can be very time consuming.

BasicPI Firmware Stack – Abstraction Layer

This is the block diagram of AL (Abstraction Layer) modules I drew som time ago. I need to review this as the list is far longer, but you get the idea. I actually started on the AL a year ago and managed to destroy my work due to a bug in STM32CubeIDE at the time, so I need to start from scratch more or less – yes I do feel the pain! But, I have myself to blame for bad backup procedures.

alOS Overview

alOS embed a RTOS (Real Time Operating System) so that the rest of the code can be independent of what OS we use. The terminology Thread and Task is used to distinguish between actual Threads that need a stack and Tasks running within a thread or main.

alOS provides four bulks of functionality that is important in any system. These are static functions so they can be called anywhere in code and guarantee portability of code. Their actual implementation is different from OS to OS.

Embedded will typically use FreeRTOS (or similar) to create a threading OS, while we use a linear scheduler for tasks. Timers are a combination of HS and SW timers.

Windows will use WinAPI for threads and the same linear scheduler for Tasks and Timers.

Static Member Description
alOS::sleep() Sleep in ms.
alOS::millis() Get time in ms.
alOS::micros() Get time in ys.
alOS::FIFOCreate()

Create a one way byte or message FIFO. The array used must be created before calling this function. typically a uint8_t array should be declared and used as FIFO buffer.

alOS::FIFOSend()

Send bytes or a message. Will also signal the Receive Task (if any) to execute. Will either insert all bytes in the fifo or none. The caller must handle full fifo signals (returning false).

alOS::FIFOReceive()

Receive bytes or a message. Can be used polling in which case it will return 0 if no bytes/messages was found.

alOS::FIFOReceiveTask() Set Task to receive a signal for each call to Send.
alOS::AddThread()

Add a thread. A thread execute in parallel on a timer interrupt and need a separate stack.

alOS::StartThread() Start thread. This enables the thread to be called.
alOS::StopThread() Stop a thread. This stops the thread from executing.
alOS::SignalThread() This signals the thread to execute once.
alOS::AddTask()

Add a function callback that can be executed on time or signals.  A Task need to execute and return so the next task can execute.

alOS::StartTask() Start a task.
alOS::StopTask(); Stop a Task.
alOS::SignalTask(); Signal a task to execute.
alOS::AddTimer()

Start a timer. alOS will run itself as a task checking timers and signals ca 1000 times per second.

alOS::StartTimer() Start a timer that will call a task in n ms. This is excellent for timeout style functionality.
alOS::StartLongTimer() Start a long timer lasting more than a day.
alOS::StopTimer() Stop a timer.

FIFO, Timer, Task and Thread reference numbers are unique.

Time

All systems will as a minimum have an elapsed timer counting uS from MCU start. The accuracy of this depends on crystals used and what source is used to maintain the clocks.

alOS guarantee a set of functions related to elapsed timers with uS accuracy. See alRTC for Real Time Clock options.

Threads and Tasks

alOS uses Thread and Task as described here.

A thread need a separate stack and is executed on a system interrupt. It needs to run in a loop and can use techniques like delay() since this allows the OS to execute another thread. Basically threads execute in “parallel” with the RTOS using a time interrupt to switch content usually 1000 times a sec.

A Task is a single function that must do its job and exit before the scheduler can start the next Task. Tasks are lists of functions that are called on timers or signals within a thread. The difference is that they run in a loop executing in sequence and must be written different from a thread. Tasks are however far more scalable than Threads since you only use a single stack. Usually you will have multiple Tasks running in a Thread.

Timers

alOS support 3 types of timers:

  • Hardware timers supported by the MCU.
  • RTOS Timers supported by FreeRTOS.
  • SW Task Timers supported by the linear scheduler.

Using RTOS timers are not recommended, but FreeRTOS (as an example) have their own proprietary timers that can be used if needed.

Hardware timers are  subject to the MCU involved, but STM32F405RG (as an example) have 14 hardware timers. Keep in mind that these are called on actual HW interrupts, so they need an ISR type of function.

Task Timers are basically tasks called on time intervals. A normal task will execute once per ms or each 10th ms depending on what you set, but a timer will execute once the timeout event is raised. Task timers can also exist in much higher numbers and it is not any real difference between a task and a task timer. A timer task can be signalled etc.

Hardware timers should however be used for things that require exact timing. Servo pulse control is an example. A SW timer will have some variance in accuracy causing the pulse to vary from second to second. On a servo this will be observed as the servo making small, unexpected moves. A hardware timer is more exact and capable of giving the same pulse from second to second making the servo stable – this is just one example. But, keep in mind that a HW timer is far more expensive to use than a SW timer, so it is not recommended to use a HW timer to blink a led etc.

Task signals

alOS support a scheme with signal counters, meaning that a task is executed once for each signal you send. This was designed with message queues in mind there you need to process once per message received. Since the timer function will prevent other tasks in the same thread it is healthy to process in bulks – hence the signal counter scheme.

Queues

The main queuing mechanism in alOS is easyIPC, meaning you can create a queue between local tasks, threads or to a different device.

PScript – Part 9 – Streams

Both Ethernet, UART, USART, USB, SPI, I2C, I2S and CAN might be used as a serial 2-way, buffered byte stream. This is an important abstraction as it allow for an easy, uniform usage of byte streams regardless of underlying technology. To support this I create the same interface on all technologies and an object “stream” that contain the buffers.

open Open a stream.
close Close a stream.
send Send bytes through the stream.
receive Receive Bytes from the stream.
Event onreceive Receive bytes through event.

The open function dictate if the stream is continuous bytes or packages. CAN (as an example) have max 8 byte packages on the messages, but it is easy enough to create virtual byte streams or larger packages if that is what you want.

CAN also have IDE, RTR, Standard and extended ID that is part of layer 1 header. I need to allow the user to control those and at the same time abstract away from CAN. This is what stream does. If you want to access raw CAN you access the CAN1 or CAN2 objects, but declaring a stream on CAN1 convert that to a buffered, 2-way byte stream where the details of the underlying CANbus is hidden from us.

Examples

Stream1.Open(CAN1, 115200)
Stream1.Send("Hello World")
Stream1.Close()

In this case I connect Stream1 to CAN1, but I could have connected it to any object capable of serial stream support. Receiving bytes can be done in two ways. You can call “receive” as follows:

uint8 buf[40]
Stream1.Open(CAN1, 115200)
int x = Stream1.Receive(buf)
Stream1.Close()

This will receive a maximum of 40 bytes from Stream1. But, a more elegant way of receiving is using the event as follows:

Event Stream1.OnReceive(uint8 bytes[])
   ...
End

I sneaked in empty brackets here to indicate that this is an array of uint8 passed as a parameter. In effect we will only pass a hidden pointer to the location in the buffer. This location will be destroyed as the event finish, so we need to copy the bytes to a local storage that survive the scope of the event.

uint8 myCopy[20]
Event Stream1.OnReceive(reference uint8 bytes[])
      myCopy = bytes
End

The empty brackets are used to indicate that we receive an array of bytes. The keyword reference indicate that this is a reference to an array, not the array itself. I do however still have one issue here – I need to tell stream1 how many bytes I accepted.

As I end the event, Stream1 will by default mark all bytes that was in the reference as received. This is ok if you receive packets or want to receive everything, but it is not ok if you need to examine the stream and receive only some of the bytes now, waiting for more to arrive later.

uint8 myCopy[20]
Event Stream1.OnReceive(reference uint8 bytes[]) : uint16 bytesReceived
     myCopy = bytes
     bytesReceived = myCopy.length;
End

The change above solves that. I add a return value to the event that is bytesReceived and set this default to the same number as inserted allowing the event to specify a different number if needed.

myCopy = bytes will in effect transfer max 20 bytes, but it might transfer less.

Again, I need to experiment a little with syntax, but I think this starts to look good.