EmbeddedRelated.com
Forums
Memfault Beyond the Launch

Hall sensor Pattern

Started by DebD 5 years ago40 replieslatest reply 4 years ago3167 views

Hello! 

It's me again with another naive doubt. I have a BLDC motor and I need to power it using a 3phase bridge inverter. I plan to use 6 step commutation scheme. But unfortunately I don't have the hall sensor pattern or the PWM switching pattern of the inverter, both of which I believe is given by the motor supplier (at least after going through the BLDC motor control solutions given by ti, microsemi, nxp etc that's what I've understood). Approaching the vendor isn't an option. Given this scenario, can anyone tell me a method to get the hall sensor pattern and the inverter switching pattern required by myself?

P.S. I've attached a MicroSemi BLDC Dev.Kit User's Guide, pg. 10 of which clearly says that the info. I require is provided by the motor manufacturer.

Thank You!

MicroSemi

[ - ]
Reply by mr_banditNovember 6, 2019

I am curious why you cannot talk to the motor vendor.

It sounds like you have been doing your homework. Can I assume this includes googling the motor number/ID? 

Failing anything else, I would try to find the info for similar motors to see what their info is. If it is pretty consistent for different motors, I would try that with the motor you have. Of course, this may damage the motor, so start small && only for a few seconds. Look at the signals on a scope, and measure the current draw on the motor && anything else you can measure.

Or - look on forums for people with the same motor and contact them.

If you can breakout and look at the hall effect sensors, you can turn the motor by hand and watch them change. Put something on the shaft (faking a knob) so you can associate a particular angle with which hall effect sensor. Map out CW and CCW (should be mirrors of each other). Recreate Table 1 & 2 in the MicroSemi datasheet. The datasheet then tells you the pattern for energizing the coils via the H bridge equivalent.

Let us know what you find!

[ - ]
Reply by matthewbarrNovember 6, 2019

OK, I have to call shenanigans on Figure 10 and Tables 1 and 2 in the Microsemi document! If you're trying to learn from this atypical if not just plain incorrect write-up, I think you're doing yourself a disservice.

Hall sensors decode 6 60-degree commutation states in an electrical cycle, Hall sensor transitions define the 60 degree boundaries. There is typically a small number of electrical cycles (2, 3, 4, ...) per mechanical revolution. The lines in a commutation table should represent sequential electrical cycle positions, eg. 0-60, 60-120, etc.

Looking at the voltage applied to any particular phase as a function of these sequential electrical cycle states, you should see "+ + z - - z". A positive voltage is applied for 120 degrees (2 consecutive states), then off for 60 degrees (1 state), then a negative voltage is applied for 120 degrees (2 consecutive states) and then off for 60 degrees (1 state.)

All three phases should follow this pattern, each shifted by 120 degrees relative to another phase. You should NEVER see a "+ -" or "- +" with both the upper and lower drivers switching ON/OFF or OFF/ON at a particular 60 degree commutation state boundary. However, using that nomenclature a "- + + - z z" pattern appears in each phase, shifted 120 degrees relative to another phase. WTF?

uscommtable_91448.jpg

Looking at the Microsemi discussion leading up the the table, they appear to derive a table for 120 degree Hall sensor spacing. Their commutation table has Hall states 5-4-6-2-3-1 which is consistent with the order (reversed) in their "electrical revolution" derivation. According to Figure 10 (above Table 1) showing phases and current directions for each of those Hall states, they have current flowing one direction through a pair of phases in one state with a direction reversal through the same pair of phases in the next state. This is the "+ -" or "- +" sequence, appearing in Figure 10 and in Tables 1 and 2.

This doesn't seem correct, certainly not for typical BLDC motors. I have never encountered a BLDC motor that works this way. That's not the back-EMF you see when you spin them, ergo that's now how you control them.

A typical correctly formed commutation table is found in this TI article:

TI Article With Commutation Table

Here is Table 1 from the TI article:

commtable_41342.jpg

The key point to understand about BLDC commutation logic is that the applied voltages from the bridge follow the line-to-neutral back-EMF waveforms as a function of rotor position. If you spin a BLDC motor CW or CCW and capture the back-EMF response for an electrical cycle (could be 1/2, 1/3, ... of a mechanical revolution depending on pole pair count) you should get waveforms that follow the PH_A/B/C +/- timing as in the TI table above, consistent with the diagrams I posted earlier, and consistent with so many other descriptions you can find on the web.

[ - ]
Reply by DebDNovember 6, 2019

Thanks a ton!! I think I'll be able to get the job done now. This forum is unimaginably helpful!

[ - ]
Reply by DebDNovember 6, 2019

Okay... after going through the following page I think I've understood what I need to do.

https://e2e.ti.com/blogs_/b/industrial_strength/archive/2013/12/20/generate-your-own-commutation-table-part-2

I still have one question:-

- What should be the value of DC supply given to the motor? The rated motor voltage i.e. 48V or something less.

[ - ]
Reply by matthewbarrNovember 6, 2019

I think the voltage for this exercise is not particularly important, the important thing is use a reasonable current level! There's very little phase resistance, and with two phases statically energized and the shaft idle there is nothing other than parasitic resistance to limit current. The TI article mentions limiting current to 10-20% of motor rated current. If you don't know what that is, start very small and work up. Eventually you'll find a current level where the rotor snaps to a stable and consistent position. Don't leave the current on longer than you have to, you may notice the motor getting a little warm but it should not get hot.

You should be able to energize the motor and have it move to a stable position with a lot less than 48V, you'll probably have good luck with 6V or even 3V. Again, the important thing is to limit current so you don't overheat and cook the phase windings. With sufficient current you could damage the motor at even a very low voltage. The power supply voltage should collapse to a very small level as it limits the current through the very low phase winding resistance.

Things are different when you're running the motor, it will produce back-EMF when it is spinning. You have to apply voltage from the bridge that exceeds the generated back-EMF in order to add energy to the system, eg. to make the motor run faster at some torque load condition. You can probably run the motor at low speed with no load from a 24V bus voltage, but you'd need 48V to run at max RPM with a reasonable load.

The other option as has been mentioned is to spin the motor manually and observe line-to-neutral back-EMF and Hall sensor response. Choose values for the resistor "Y" that produce just a few mA of current, eg. 3 4.7k resistors. You won't be able to manually spin it anywhere near fast enough to produce 48V back-EMF, and you should wind up with a result similar to the plots I posted earlier. Hall sensor transitions correspond to phase line-to-neutral back-EMF waveform crossover points, the commutation state boundaries.

It is interesting that Table 1 from the TI article is identical to the Clockwise Rotation plot I posted, the first line in the TI table corresponds to the 120-180 degree position in the plot, B+C-. The bridge torque states and Hall sensor states are the same as you move down the TI table and left to right in the plot.

[ - ]
Reply by DebDNovember 6, 2019

Hi! I'm back again after a day and a half of futile efforts...

I tried to perform the test given in the ti discussionhttps://e2e.ti.com/blogs_/b/industrial_strength/archive/2013/12/20/generate-your-own-commutation-table-part-2

I used two different DC power supplies, one for the motor and the other for the hall sensors (as directed by my instructor). The motor shows erratic behavior- On the first few attempts, the motor span for a second or so (with 2-3 amps of current applied) but nothing showed up on the oscilloscope used to get the hall pattern. So I increased the applied current to 5 amps (again as directed by my instructor. The motor is 48V, 800W btw). This time the motor span appreciably (say quarter of a revolution) and something showed up on the scope (we had connected only 1 hall sensor output to the scope at that time). So we decided to go ahead with this current setting. But from then onward, each time I'm trying to do the test, the Power Supply shows a SHORT CIRCUIT warning message and the motor doesn't spin at all. 

If you're thinking the motor has been damaged, that's not the case. This motor came with a pre-programmed inverter unit (the kind of package normally used in e-rickshaw bldc motors). We ran the motor using that inverter to check if it's working and it's working perfectly. Can anyone suggest a possible solution or point out if I'm going wrong anywhere?


Also my nutcase instructor has asked me to cross-check this method i.e. verify by any means possible whether the switching table I get by the above experiment is correct for this motor. I can put a current probe on each phase and see the current waveforms on the scope (By running the motor with the available inverter)  But I need to have the Hall Pattern simultaneously. When this motor is run with the mentioned inverter, the hall sensor output from the motor goes directly into the inverter box via a 5 pin connection, which renders the hall output pins inaccessible for any other purpose. I've read the basic theory of this commutation method from an nxp article nxp bldc. I can extend this theory for my motor (Which of course has more number of poles than the basic two pole machine, 8 to be exact) but I need to know the positioning of hall sensors inside the motor whether they're 120 electrically apart or not to draw the commutation diagrams. 

I'm desperately in need of some help here and my instructor ain't ready to help even a bit!

[ - ]
Reply by matthewbarrNovember 6, 2019

First a quick sanity check, Hall sensors are typically powered from 5V and the outputs are open drain, requiring pullup resistors to the 5V supply. (There is often a fairly wide DC voltage range over which they can operate, 5V is typical.)

Doing an overly simplistic calculation, 800W at 48V gives you an average current of 16.67A. Using the TI 10-20% suggestion you should be in the 1.7 to 3.3A ballpark, so your 2-3A number sounds reasonable. With nothing attached to the motor shaft (no pulley, etc.) this should be sufficient to make the shaft snap to an energized position. If your motor has eg. 3 pole pairs, there will be three possible mechanical positions, one in each of the three 6-state electrical cycles.

The TI article wants you take 6 static samples of Hall sensor A/B/C state as the motor phases are energized:

A+B-C- A+B+C- A-B+C- A-B+C+ A-B-C+ A+B-C+

The commutation states are 90 degrees from these energized states, so the sample state A+B-C- corresponds to bridge commutation state B+C-, and should correspond to Hall state 110 (config A) or 001 (config B).

None of this should spin the motor such that you get waveforms, you're just collecting static Hall A/B/C data points at each position.

One thing that will happen is that you will get a back-EMF voltage spike when you suddenly turn off the current through the phases, like the ignition coil in an old car. It can be substantial and you don't have protection diodes like you'd normally have in bridge output drivers. Different supplies will handle this differently, it might trigger a fault condition requiring you to reset the supply, hopefully it does not damage the supply.

If your supply has a programmable ramp capability, set it to take a second or two to go from full current back to 0. The turn off case is the one you have to pay attention to, phase inductance will automatically soften turn on.

If your supply does not have a programmable ramp capability, perhaps there is a knob or some other means you can use to bring the current down to 0 more gradually than an instantaneous OFF transition. Hopefully you can: energize phases, record Hall state, bring current gradually back to 0, change phase connections, repeat.

If it is possible to attach some sort of hand crank to the motor shaft, you always have the option of connecting the phases to a resistor "Y" and capturing phase back-EMF and Hall sensor waveforms as you crank the motor shaft. You still have to power the Hall sensors, but you no longer need another power supply. This should give you plots similar to those I posted earlier. The commutation table for the Clockwise Rotation plot would be identical to Table 1 in the TI writeup, the first line in their table corresponds to the 120-180 degree position in my plot.

[ - ]
Reply by matthewbarrNovember 6, 2019

Here's a brief article talking about 60 vs. 120 Hall spacing, you should be able to tell from the posedge relationships relative to a full cycle:

Parker 60 120 Hall

Power the Hall sensors w/ pullups, crank the shaft and observe the waveforms.

There are various ways you might break out those Hall signals with the working controller, even if you don't have a pair of compatible mating connectors and pins. For example, you might open up the controller and tack a few wires on to the Hall connections (and GND) to the PCB.

Sometimes when an instructor or manager doesn't offer any help it is because they want you to do the work yourself and they have a lot of confidence in you. Sometimes it is because they don't want to reveal how little help they are capable of offering!

[ - ]
Reply by DebDNovember 6, 2019

Thank You once again! This back-emf "Y" resistor method that you mentioned of (in an earlier comment as well, which I carelessly skipped) just caught my attention. First thing tomorrow I'm gonna have a go at it.

[ - ]
Reply by CustomSargeNovember 6, 2019

Hi, Silly me, how about finding another mfgr motor of same or similar size and start from their data. Then tinker with small changes to fine tune it. I've never done it, but it's an approach. Good Hunting <<<)))

[ - ]
Reply by matthewbarrNovember 6, 2019

I fundamentally agree with mr_bandit's answer. You really have 3 options:

1. Obtain the Hall sensor wrt. phase timing information for your motor.

2. Reverse engineer your motor phase timing by turning it manually and looking at phase back-EMF and Hall sensor response with a scope.

3. Start with timing information for a similar motor and get yours working by trial and error.

If you pursue option 2, you can construct a 3-resistor "Y" to which phases can be connected. Attach your scope ground to the center point and motor phases to the resistor ends. Spin the motor, probe the phases and Hall sensor outputs. You now have the line-to-neutral voltage and Hall sensor timing relationships. Line-to-line voltages are easily computed from the difference of two line-to-neutral voltages.

Option 3 is probably the easiest. In a perfect world everyone would have the same motor phase A/B/C (or U/V/W) wrt. Hall A/B/C timing relationships, but that is not our world. It shouldn't take too many trials swapping motor phase connections and/or swapping/inverting Hall sensor input connections before you get the motor spinning in one direction or another. Be sure to work at low power levels so that nothing destructive happens when the motor doesn't run properly. Once you get it running in one direction, swapping any pair of motor phases should create a direction reversal.

[ - ]
Reply by matthewbarrNovember 6, 2019

Here are a couple plots showing the relationships between line-to-neutral back-EMF, Hall sensor inputs and the related bridge driver states for a particular motor. Note that the power applied from the bridge follows line-to-neutral back-EMF, "A+B-" denotes the bridge state where a positive voltage is applied to motor phase A and a negative voltage is applied to motor phase B. This is true whether you're using simple block commutation or a complex modulation technique that produces sinusoidal waveforms.

cw_42252.jpg

ccw_32000.jpg

[ - ]
Reply by DebDNovember 6, 2019

Can I know the source from where you've got these plots? Actually I found a commutation table on this website https://www.lucidar.me/en/actuators/commutation-fo...

The switching pattern for clockwise rotation is exactly the same as that of your plot. I mean can we infer that the commutation sequence vs hall pattern thing is universal for all BLDC motors?

[ - ]
Reply by matthewbarrNovember 6, 2019

With motor phases attached to a resistor "Y" and Hall sensors powered, I manually cranked a motor in both directions and captured back-EMF and Hall sensor waveforms with a scope. Based on that information I used gnuplot to generate those plots.

It is not correct to assume all BLDC motors with Hall sensors work this way. It may be true for many and perhaps even for most, but not for all. There is at least the 60 vs 120 degree Hall sensor placement in the motor to consider, and there can be other differences.

The key thing to understand about controlling a 3 phase BLDC motor is that the applied voltage from the bridge follows the back-EMF waveforms. Looking at the CW rotation plot, the 0-60 degree position shows the A phase positive and the B phase negative. At this rotor position the bridge should apply a positive voltage to the A phase and a negative voltage to the B phase (A+B-) to apply CW torque. At the 60 degree boundary a positive voltage is still applied to A but the negative voltage switches to the C phase (A+C-), and so on.

Looking at just the A phase, it is positive from 0-120, crosses over from 120 to 180, is negative from 180 to 300, and crosses over from 300 to 360. This gives the familiar + + z - - z pattern that you would see for the A phase half bridge driver state in the commutation table. All three phases have this same pattern, they're just shifted 120 degrees from one another.

You'll get these line-to-neutral back-EMF waveforms if you spin the motor manually. If you capture the Hall sensor state at the same time, you now know based on Hall sensor state how to apply voltage to the motor phases to make the motor spin in that direction. Then repeat the process to get a similar table for operation in the other direction.

[ - ]
Reply by DebDNovember 6, 2019

Post deleted by author

[ - ]
Reply by matthewbarrNovember 6, 2019

Yes on the electrical cycles, 8 poles (4 pole pairs) should give you 4 electrical cycles per mechanical revolution. You'll see one complete cycle on each Hall sensor output and on each line to neutral back-EMF waveform per electrical cycle, four complete cycles per mechanical revolution.

You may or may not know which motor phases and Hall outputs are A/B/C, it doesn't really matter for this exercise. I believe the typical convention for clockwise vs. counter clockwise is for an observer looking into the motor drive shaft, which means the normal drilling direction would be counter clockwise.

I think you might be making this a little too hard, here's one way you might approach it. The first thing to do is to make your resistor "Y". Connect all three motor phases to the open ends of the resistors. Connect three scope channels to the motor phase connections, and (important) connect scope ground to the center point of the resistor "Y". Power and pull up the Hall sensor outputs, connect your fourth channel to a Hall sensor output, scope ground to Hall ground.

Spin the motor clockwise and convince yourself you can capture three reasonable looking sine waves separated by 120 degrees along with your Hall sensor output. I've found it useful to trigger on a rising voltage on one of the Hall phases, set at some reasonable voltage level. The back-EMF amplitude depends on angular velocity, this prevents you from triggering on "runt" waveforms as rotation starts and stops. There's no need to worry about restricting the rotation to some number of electrical cycles, just capture reasonable looking waveforms.

You should see that the Hall sensor output transitions correspond to a crossover point for two of the phases. You now have three phases that fire in a known order and you know how this Hall sensor corresponds to them. At this point you can leave your scope trigger channel connected to the motor phase, and move the other two scope channels to the remaining two Hall sensor outputs. Spin the motor clockwise again and capture the motor phase and Hall sensor outputs.

Now you have all the information you need to generate a clockwise rotation back-EMF and Hall sensor plot. The only question is which phases are A/B/C and which Hall sensors are A/B/C. In some sense it doesn't matter, particularly if you don't have a mechanical 0 position you care about and you don't care about the relationship between mechanical 0 and electrical cycle 0. The Hall sensor state gives you electrical cycle position, eg. commutation state. Each state corresponds to one of the motor phases high, another low, and the third in transition. As long as your motor controller logic controls the bridge such that you apply voltage to the motor phases consistent with the back-EMF waveforms as a function of Hall sensor state, the motor should run in the same direction. This is regardless of which phases you're calling A/B/C, which sensors you're calling A/B/C and even how many pole pairs you have. You'll likely be able to map the phases and sensors back to the "typical" phase and sensor timing relationship.

[ - ]
Reply by DebDNovember 6, 2019

I tried the exact same exercise that you've mentioned, yesterday. I've also identified the H1, H2 and H3 correctly by the phase shift in their waveforms.(It's come out as 120 deg. elec.) The only catch is that my motor doesn't have a cranking arrangement (neither is my instructor able to provide me with one >.<) which is why I'm not getting a smooth movement when I rotate the motor shaft with my hands. I thought maybe if I had all three hall outputs and l-l vlgs (one by one) in front of me simultaneously, it'd make my interpretations easier.

Anyway... all members of my team are fairly satisfied with the results and thus I've got the green light to go ahead with the coding part now. I've been assigned an ST-Microelectronics 8 bit MCU and given the STM8S Discovery launchpad (STM8S105C6T6 MCU on board) for coding. I don't need to build an inverter right now, a prototype has been ordered for me. I just need to program the micro-controller to drive the inverter in order to run the motor.

So could you tell me where exactly should I start off as far as coding goes? Like I've browsed the ST-Microelectronics website exhaustively. They have a Motor Control Firmware Library for 8 bit MCUs which has loads of code examples (dedicated to BLDC and AC Induction Motors). I've also assembled all the softwares necessary to code the MCU, on my PC. But I don't know the exact point from where I should start (again as usual, my instructor is even more clueless here) so yeah once again I'm calling out for help!

[ - ]
Reply by matthewbarrNovember 6, 2019

If they've got code examples for a basic BLDC controller I'd start there.

The very first thing you should do is get a dead simple demo project to build, load and run on your launchpad board. Convince yourself you can build and run a very simple program that can sense port input state and control port output state. Assuming you have some sort of IDE, convince yourself that you can set and hit code breakpoints, examine varaiable and memory state. This is about getting the development tool chain working so you can then focus on developing your software.

From there you should be able to add the BLDC control logic to your demo project, turning it into a motor controller. I'm not sure exactly what kind of a control solution you're targeting, but you'll likely have the following pieces:

  • bridge driver output state decoded from Hall input state
  • PWM duty cycle control applied to bridge driver outputs
  • PI (or PID) controller to adjust PWM duty cycle based on target/actual velocity
  • actual velocity determination, probably based on Hall sensor input transitions
  • target velocity "throttle" command input
  • actual velocity status output

If you're lucky you will find a BLDC demo project based on your MCU with most or all of these pieces. You'll have to adapt them to your particular board.

You might also need some demo application to communicate with the launchpad board, probably USB serial, to request target velocity and report actual velocity. Again, if you're lucky you may find something like this. The most simple thing you could do is have a single periodic message, say 5 or 10 times a second, that sends a throttle (target velocity) command from the demo program. The response to this message could contain the actual velocity. The controller should have a watchdog timeout function so that it forces the throttle to 0 after maybe 5 seconds if it is not receiving throttle commands.

Assuming you have only a simple velocity loop with no current sensing, you will want to avoid sudden changes in phase current. An easy way to do this is to limit the rate of change of PWM duty cycle wrt. time. Each time you run your velocity control loop you'll compute an adjustment to PWM duty cycle based on velocity error. If you run the loop, say, once every 20 ms, you might limit your PWM duty cycle updates to 2% per update. This means you'll need 50 updates, 1 second, to go between 0 and 100% PWM duty cycle, which will prevent large current surges as you accelerate and large back-EMF events as you decelerate.

One other thing I'll point out is that you don't have to apply PWM to both the upper and lower drivers, you can leave the upper drivers on and apply PWM to just the lower drivers, or vice-versa. With this approach you are never turning the upper/lower drivers for a particular phase on/off or off/on at the same time, so you don't have to worry about dead time on your bridge driver outputs.

[ - ]
Reply by DebDNovember 6, 2019
olc_42803.jpg


Above is a block diag. of what I'm supposed to do with my motor as the first task.

I've got a few questions regarding this :-

- How to obtain duty cycle from speed? I've seen in a matlab simulation file given by nxp that Duty cycle = Speed Ref./Max Speed of Motor. Is this correct?

- What is purpose of finding out the duty cycle? I've gone through open loop motor control solution given by nxp and infineon. I see them applying PWM to one switch and constant pulse to another (Eg. 60 deg commutation, Hall Pattern: 110, Phases: U- off, V-, W+. While nxp has taken PWM for W+ and constant pulse for V-, infineon has done the reverse) Is this entirely upto me i.e. I can choose either switch to have PWM and the other to have constant pulse? 

Also, please correct me if my understanding is wrong here: The duty cycle will only be used to generate PWM pulse train for one switch and has got nothing to do with the other switch to which constant pulse has to be applied 

[ - ]
Reply by matthewbarrNovember 6, 2019

I think there's a problem in your understanding here. Duty cycle is not computed or derived from anything in this open loop operation case, it is an input to the system, varying from 0 to 100% for a particular direction of rotation. There is no velocity feedback loop.

Duty cycle is your throttle, similar to the gas pedal on a car. You apply more throttle to increase power and make the car go faster. By increasing the PWM duty cycle you are increasing the average voltage applied to the motor phase windings. This is basically a torque throttle, more voltage means more current which is proportional to torque.

If you engage the cruise control on a car you now have a velocity loop, closed loop operation. If/when you add a velocity control loop to your motor control, you'll likely have a PI or PID controller that computes a PWM duty cycle adjustment from angular velocity error (commanded velocity vs. actual velocity.)

You can apply PWM to just the upper or just the lower drivers. You don't need to worry about dead time in this case because you're never switching the upper/lower drivers for a particular phase on/off or off/on at the same time. PWM will generate switching noise and it may be more of a problem on V+ or V- depending on your circuitry. (There are PWM schemes that involve switching both drivers, but I don't think you need to worry about that with the related dead time concerns for your exercise.)

I've mentioned this before, and it bears repeating: Avoid large instantaneous changes in PWM duty cycle! If you switch the PWM duty cycle from 0 to 100% you'll get a large current spike. Conversely, if you switch it from 100% to 0 you'll get a large back-EMF voltage spike. Both of these can create problems for your lab supply. When you're getting started, vary the PWM duty cycle gradually until you get a feel for how your motor responds and what your equipment will tolerate. Consider limiting dPWM/dt in software to make sure you never see excessive current spikes or back-EMF events.

[ - ]
Reply by DebDNovember 6, 2019

So let me get this straight... There's no need for a reference speed in open loop operation right? I'll have to make arrangements in software so that a variable PWM duty cycle (inc./dec. in steps of 10% say) is generated at the appropriate output pins of the MCU, which in turn will be fed to the upper/lower drivers as per my choice?

[ - ]
Reply by matthewbarrNovember 6, 2019

Short answer is yes!

I'm guessing someone wants you to get this working, then extend to closed loop operation. The commutation block in your diagram should perform the same function whether open loop or closed loop. It should generate output state to the bridge gate drivers as a function of current rotor position (Hall sensor state) and current PWM duty cycle.

In closed loop operation PWM duty cycle is adjusted by a PI or PID controller based on velocity error. In open loop operation it comes from some form of throttle command.

Your diagram refers to this input as a "speed reference" which is a bit misleading. With a fixed PWM duty cycle the rotor speed will vary with mechanical load.

By the way, a 10% PWM granularity is likely too coarse, 5% is better but you really want something like 1% or less. If/when you move to closed loop operation, your ability to run smoothly at an arbitrary velocity will be poor if your only option to adjust PWM duty is up or down by 10% or even 5%. It will be hard to distinguish normal duty cycle adjustments from oscillations due to a poorly tuned control loop.

[ - ]
Reply by DebDNovember 6, 2019

Post deleted by author

[ - ]
Reply by DebDNovember 6, 2019

Hi! I'm back after a couple of months of untiring efforts. Glad to say I've been able to run the BLDC using the STMicroelectronics 8 bit microcontroller :) Here's the link to a video captured on my project setup running https://drive.google.com/open?id=1rAi5UxFPSlqqfiBE... The motor is rated for 3000 rpm. With 48V applied to the DC Bus of the inverter, I've been able to take the motor upto 2500 rpm, the corresponding PWM duty cycle being 0.8. I've used the switching scheme suggested by you: Top Side Switch: PWM, Bottom Side Switch: Constant High. PWM switching frequency is kept at 5 kHz currently.

I was wondering if you're conversant with ti's f28335 dsp. Or atleast tell me where to post a doubt regarding coding this dsp on Code Composer Studio. A colleague of mine has to run the same setup with this dsp, with the same switching strategy as used by me. Every time he burns the code on his board, it so happens that the motor gives a momentary jerk and then comes to rest immediately. Hall state change is detected correctly. But the corresponding pair of switches don't conduct accordingly. The issue in particular concerns with the top side switches i.e. PWM transition. The bottom side switches i.e. Constant High show proper transition on hall state change. We've ascertained that much by simulating hall state change using one of the lab power supplies and the o-scope. Given below is the link to his doubt posted on ti's support forum. Hope it'll help you understand the situation better. 

https://e2e.ti.com/support/tools/ccs/f/81/t/851906

The reason I'm rushing into posting the same doubt here is because we're working with the deadline day looming large and need a quick fix! Can't wait for ti's staffs to take their own time to reply. Also we haven't been able to find sufficient documentation/user manuals/relevant sample codes for this dsp (unlike my STMicroelectronics Launchpad, which I must say was extremely well documented!)This isn't helping us either...

Any suggestion/help would be greatly appreciated!

Thanks and Regards!


[ - ]
Reply by matthewbarrNovember 6, 2019

I'm sorry, I don't have any familiarity with that TI DSP platform. It sounds like there is a problem driving the top side switches based on the current Hall state and target PWM duty cycle. This doesn't seem like it should be too difficult to debug. Perhaps you can disconnect the motor phases, hack the control firmware to override the control loop and fix the PWM duty cycle at maybe 50%, and observe the top side and bottom side outputs from controller to power bridge gate drivers for each Hall input state.

Congrats on getting your STM controller running, good stuff!

[ - ]
Reply by DebDNovember 6, 2019

Hi! I'm back with another doubt... So right now I'm in a position to run the motor at discrete fixed speeds by changing the PWM duty cycle manually inside my code every time. Now I want to achieve this speed control using an analog pot. i.e. speed input would be given using the pot. and then an ADC conversion would be done inside the code. The result of ADC conversion would be fed as the "uint16_t TIM*_Pulsevalue that changes the PWM duty cycle in my microcontroller and consequently the speed of the motor. Given below is the main function of my code, with the PWM duty cycle being set at 0.5, by writing the uint16_t TIM*_Pulse value as 400 (or 320) inside the various switch case statements.

My question is: Where do I put the ADC conversion part inside main()? Will it work if I write the ADC conversion code inside the for(;;) prior to reading the Hall Sensor Inputs, and then replace 400 (or 320) inside the switch case statements by the ADC conversion result? I've also provided a sample ADC Conversion code for my microcontroller.

Please suggest changes if needed!

Thanks and Regards

Continuous ADC Conversion Code Snippet.

ADC1_StartConversion();       
while(ADC1_GetFlagStatus(ADC1_FLAG_EOC) == FALSE);                                       
A0 = ADC1_GetConversionValue();   //A0 is of uint16_t type. A0 to be fed as Tim*_Pulse instead of 400 (or 320) as done in main() below currently.
ADC1_ClearFlag(ADC1_FLAG_EOC);
BLDC Motor main function

void main(void)
{
 clock_setup();
 GPIO_setup();
 
 for(;;) 
 {
        /* Read Hall States. H1 = B7, H2 = A6, H3 = G0. "state" is defined as decimal eq of binary H3H2H1 */
        
        if(GPIO_ReadInputPin(GPIOG, GPIO_PIN_0) == FALSE)
        {
            H3 = 0;
        }
        else
        {
            H3 = 1;
        }
        if(GPIO_ReadInputPin(GPIOA, GPIO_PIN_6) == FALSE)
        {
            H2 = 0;
        }
        else
        {
            H2 = 1;
        }
        if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == FALSE)
        {
            H1 = 0;
        }
        else
        {
            H1 = 1;
        }


state = 4*H3 + 2*H2 + 1*H1;
        
 if ((state != p_state) && (state >= 1 && state <= 6))
 {
        p_state = state;
        switch(state)
        {
            
                            case 1: //C- & A+ ON
                            x = 1;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                          
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_2); //C- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800); //HSI Clock is at 16 MHz. (16*10^6)/(4*800) = 5 kHz PWM switching frequency.
                            TIM3_OC1Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 400, TIM3_OCPOLARITY_HIGH); //A+ ON. TIM3_Pulse = 400. Therefore duty cycle = 400/800 = 0.5
                            TIM3_Cmd(ENABLE);
                            
                            break; 


                            case 2: //B- & C+ ON
                            x = 2;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_3, DISABLE); //C+ OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_1); //B- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800);
                            TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 400, TIM3_OCPOLARITY_HIGH); //C+ ON
                            TIM3_Cmd(ENABLE);
                         
                            break; 


                          case 3: //B- & A+ ON
                            x = 3;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_1); //B- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800);
                            TIM3_OC1Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 400, TIM3_OCPOLARITY_HIGH); //A+ ON
                            TIM3_Cmd(ENABLE);
                            
                            break;


                            case 4: //A- & B+ ON
                            x = 4;
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ Substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ Substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_0); //A- constant high pulse
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE);
                            TIM1_TimeBaseInit(4, TIM1_COUNTERMODE_UP, 640, 1);
                            TIM1_OC2Init(TIM1_OCMODE_PWM1, 
                                     TIM1_OUTPUTSTATE_ENABLE, // B+ ON 
                                     TIM1_OUTPUTNSTATE_DISABLE, 
                                     320, TIM1_OCPOLARITY_HIGH, 
                                     TIM1_OCNPOLARITY_LOW, 
                                     TIM1_OCIDLESTATE_RESET, 
                                     TIM1_OCNIDLESTATE_RESET); 
                                                     
                            TIM1_CtrlPWMOutputs(ENABLE);
                            TIM1_Cmd(ENABLE);
                            
                            break;
            
                            case 5: //C- & B+ ON
                            x = 5;
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_2); //C- constant high pulse
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE);
                            TIM1_TimeBaseInit(4, TIM1_COUNTERMODE_UP, 640, 1);
                            TIM1_OC2Init(TIM1_OCMODE_PWM1, 
                                     TIM1_OUTPUTSTATE_ENABLE, // B+ ON 
                                     TIM1_OUTPUTNSTATE_DISABLE, 
                                     320, TIM1_OCPOLARITY_HIGH, 
                                     TIM1_OCNPOLARITY_LOW, 
                                     TIM1_OCIDLESTATE_RESET, 
                                         TIM1_OCNIDLESTATE_RESET); 
                                                     
                            TIM1_CtrlPWMOutputs(ENABLE);
                            TIM1_Cmd(ENABLE);
                            break;




                            case 6: // A- & C+ ON
                            x = 6;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_0); //A- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800);
                            TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 400, TIM3_OCPOLARITY_HIGH); //C+ ON
                            TIM3_Cmd(ENABLE);
                            
                            break;        
        }
 }
 if (state == 0 || state == 7)
 {
        //Disable all gate pulse pins if either 000 or 111 i.e. unexpected hall state occurs.
                            
        TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
        GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
        TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
        GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
        TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
        GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                          
        TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
        GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
        TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
        GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
        TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
        GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
 }
}
}

[ - ]
Reply by matthewbarrNovember 6, 2019

I think you're on the right track, but with a super-loop you generally want to keep everything non-blocking, particularly when you care about real time behavior. This means you should avoid test loops that stick, polling a status flag, waiting for something to happen. You need to instead make this logic stateful, test and update each time through the loop.

I'm not going to get in the business of writing code or recommending specific C source code changes, but in pseudo-code you probably want to do something like this:

main()
{
  system initialization
  PWM_duty_cycle=0      // replaces 400 or 320
  ADC_state=IDLE        // IDLE or ACTIVE
  while (1)
  {
    // Non-blocking ADC handling
    if (ADC_state == IDLE)
    {
      start ADC conversion
      ADC_state=ACTIVE
    }
    else
    {
      if (ADC conversion done)
      {
        update PWM_duty_cycle from ADC result
        ADC_state=IDLE
      }
    }
    // Existing commutation logic
  }
}

There is no spin loop waiting for ADC completion, instead the ADC handling is stateful and non-blocking. With respect to ADC and commutation, the main loop can walk and chew gum at the same time!

One source code suggestion I will make is to write your Hall sensor decode logic this way:

H3 = (GPIO_ReadInputPin(GPIOG, GPIO_PIN_0) == FALSE) ? 0 : 1;
H2 = (GPIO_ReadInputPin(GPIOA, GPIO_PIN_6) == FALSE) ? 0 : 1;
H1 = (GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == FALSE) ? 0 : 1;
state = (H3 << 2) + (H2 << 1) + H1;

I think the Hn update is just as readable and much more concise.

Using shift instead of multiply is a choice. Your compiler probably optimizes the multiply by power of 2, or your processor may give you a single-cycle integer multiply in hardware. But if neither of those are true you get a performance penalty with the multiply, wasting time in your main loop.

You could even eliminate H1/H2/H3 and related shift or multiply:

state  = (GPIO_ReadInputPin(GPIOG, GPIO_PIN_0)) ? 4 : 0; // H3
state += (GPIO_ReadInputPin(GPIOA, GPIO_PIN_6)) ? 2 : 0; // H2
state += (GPIO_ReadInputPin(GPIOB, GPIO_PIN_7)) ? 1 : 0; // H1
[ - ]
Reply by DebDNovember 6, 2019

Thanks... a pseudo code was exactly what I was looking for. I'll try to implement this! Thanks again for suggesting the ternary operator to read the Hall States... 

I owe you a lot for getting my hardware working indeed!

[ - ]
Reply by DebDNovember 6, 2019

Few questions...

1. ADC_state = IDLE/ACTIVE. Has this got something to do with reading the status of some ADC register bit in C code? If not, what then?

2. "//existing commutation logic" is written after the ADC if else structure. This means reading the hall sensor inputs and entering into the switch case structure, with of course the PWM duty cycle taken as that obtained from the ADC conversion right?

3. If my understanding of points 1 and 2 are correct, isn't the ADC conversion interfering with the commutation? Because every time the infinite loop runs from the top, I am checking the ADC state and then proceeding to read the Hall Inputs? Or is that the way it's done? i.e. I have to cater to the ADC conversion on every execution of the infinite loop.

[ - ]
Reply by matthewbarrNovember 6, 2019

1. IDLE/ACTIVE is used to keep track of whether or not you have a conversion running. You may be able to discern this from ADC status registers, in which case you don't need this additional software state. When ADC conversion is IDLE you have two sub-cases, one where there is a conversion result available, and another when there is not (eg. coming out of reset.) So really there are three states, ACTIVE, IDLE w/ result and IDLE w/o result. You may be able to discern all of these states from ADC status registers, or you may need additional software state to keep things straight.

The way I set this up in pseudo-code, IDLE/ACTIVE is software state. If IDLE you start a conversion. When ACTIVE I'm assuming that you can tell from ADC status registers when the conversion has completed (your EOC test), in which case you go IDLE, read the result and convert it to a PWM duty cycle. The action on IDLE is to immediately start a conversion, so you don't ever hit the IDLE w/o result case.

2. Yes.

3. I'm not sure why you think this interferes with commutation. What would interfere is a CPU-hogging spin loop like the EOC test in your ADC snippet:

ADC1_StartConversion();       
while(ADC1_GetFlagStatus(ADC1_FLAG_EOC) == FALSE); // hog CPU

Any other processing stops while you wait for EOC. This interferes with handling things like Hall state changes in real time.

Instead, test EOC once each time through the loop while commutation proceeds. When you hit EOC, process result and go IDLE. Next time through the loop start another conversion and go ACTIVE. You never sit waiting for something to happen, and you never run more than a few lines of code in any path through the ADC handling.

Nothing changes about the way you read hall sensors and update driver state, except that you now have a variable PWM duty cycle instead of a constant. If you've written your PWM logic such that it cannot be controlled easily from a variable value and depends on a constant value, you're going to have to make some changes. These would be good changes, necessary in order to control the motor from a torque or velocity loop.

Frequently things like your ADC handling are done in an interrupt service routine (ISR). It's similar but a little more complicated, you need to worry about volatile state and pay close attention to the fact that the ISR may may run at any time, changing certain values at any point in your code. I suggested the non-blocking polled solution because it's a little easier to get your arms around. You have some additional overhead each time through the loop relative to an ISR, but it isn't very much overhead, typically just test ADC status and move on. When there is useful work to do (handle result or start a new conversion) you're doing the same work you'd be doing in an ISR.

[ - ]
Reply by DebDNovember 6, 2019

Post deleted by author

[ - ]
Reply by DebDNovember 6, 2019

I implemented the ADC part following the pseudo code. Could you please check if I'm on the right track!

#include "STM8S.h"
uint8_t x = 0;
uint8_t H1 = 0;
uint8_t H2 = 0;
uint8_t H3 = 0;
uint8_t state = 0;
uint8_t p_state = 0;
unsigned int ADC_state;
unsigned int A0 = 0x0000;
unsigned int C1 = 0;
unsigned int C2 = 0;
void clock_setup(void);
void GPIO_setup(void);


void main(void)
{
 clock_setup();
 GPIO_setup();
 ADC_state = 1; /* 1 --> ADC is in IDLE State
                   2 --> ADC is in ACTIVE State */
 
         for(;;) 
       {
                //PWM Duty Cycle update via ADC Conversion

                if (ADC_state == 1)
                {
                    ADC1_StartConversion();
                    ADC_state = 2;
                }


                else
                {
                    if (ADC1_GetFlagStatus(ADC1_FLAG_EOC) != FALSE) //ADC Conversion Done
                    {
                      A0 = ADC1_GetConversionValue(); //Fetch ADC Conversion Result
                      ADC1_ClearFlag(ADC1_FLAG_EOC);
                      ADC_state = 1;
                      C1 = ((float)A0/1024)*800; //A+ & C+ TIM Pulse Generator 
                      C2 = ((float)A0/1024)*640; //B+ TIM Pulse Generator
                    }
                }        


        /* Read Hall States. H1 = B7, H2 = A6, H3 = G0. "state" is defined as decimal eq of binary H3H2H1 */
        
        H3 = (GPIO_ReadInputPin(GPIOG, GPIO_PIN_0) == FALSE) ? 0 : 1;
        H2 = (GPIO_ReadInputPin(GPIOA, GPIO_PIN_6) == FALSE) ? 0 : 1;
        H1 = (GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == FALSE) ? 0 : 1;
        state = (H3 << 2) + (H2 << 1) + H1;
        
 if ((state != p_state) && (state >= 1 && state <= 6))
 {
        p_state = state;
        switch(state)
        {
                            case 1: //C- & A+ ON
                            x = 1;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                          
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_2); //C- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800);
                            TIM3_OC1Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, C1, TIM3_OCPOLARITY_HIGH); //A+ ON
                            TIM3_Cmd(ENABLE);
                            
                            break; 

                            case 2: //B- & C+ ON
                            x = 2;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_3, DISABLE); //C+ OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_1); //B- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800);
                            TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, C1, TIM3_OCPOLARITY_HIGH); //C+ ON
                            TIM3_Cmd(ENABLE);
                         
                            break; 

                            case 3: //B- & A+ ON
                            x = 3;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_1); //B- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800);
                            TIM3_OC1Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, C1, TIM3_OCPOLARITY_HIGH); //A+ ON
                            TIM3_Cmd(ENABLE);
                            
                            break;


                            case 4: //A- & B+ ON
                            x = 4;
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ Substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ Substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_0); //A- constant high pulse
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE);
                            TIM1_TimeBaseInit(4, TIM1_COUNTERMODE_UP, 640, 1);
                            TIM1_OC2Init(TIM1_OCMODE_PWM1, 
                                     TIM1_OUTPUTSTATE_ENABLE, // B+ ON 
                                     TIM1_OUTPUTNSTATE_DISABLE, 
                                     C2, TIM1_OCPOLARITY_HIGH, 
                                     TIM1_OCNPOLARITY_LOW, 
                                     TIM1_OCIDLESTATE_RESET, 
                                     TIM1_OCNIDLESTATE_RESET); 
                                                     
                            TIM1_CtrlPWMOutputs(ENABLE);
                            TIM1_Cmd(ENABLE);
                            
                            break;
            
                            case 5: //C- & B+ ON
                            x = 5;
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_2); //C- constant high pulse
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE);
                            TIM1_TimeBaseInit(4, TIM1_COUNTERMODE_UP, 640, 1);
                            TIM1_OC2Init(TIM1_OCMODE_PWM1, 
                                     TIM1_OUTPUTSTATE_ENABLE, // B+ ON 
                                     TIM1_OUTPUTNSTATE_DISABLE, 
                                     C2, TIM1_OCPOLARITY_HIGH, 
                                     TIM1_OCNPOLARITY_LOW, 
                                     TIM1_OCIDLESTATE_RESET, 
                                         TIM1_OCNIDLESTATE_RESET); 
                                                     
                            TIM1_CtrlPWMOutputs(ENABLE);
                            TIM1_Cmd(ENABLE);
                            break;

                            case 6: // A- & C+ ON
                            x = 6;
                            
                            TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
                            GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
                            GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
                            GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
                            TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE);
                            GPIO_WriteHigh(GPIOB, GPIO_PIN_0); //A- constant high pulse
                            
                            TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE);
                            TIM3_TimeBaseInit(TIM3_PRESCALER_4, 800);
                            TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, C1, TIM3_OCPOLARITY_HIGH); //C+ ON
                            TIM3_Cmd(ENABLE);
                            
                            break;        
        }
 }
 if (state == 0 || state == 7)
 {
        //Disable all gate pulse pins if either 000 or 111 i.e. unexpected hall state occurs.
                            
        TIM3_CCxCmd(TIM3_CHANNEL_1, DISABLE); //A+ substitute OFF Step 1
        GPIO_WriteLow(GPIOD, GPIO_PIN_2); //A+ substitute OFF Step 2
                            
        TIM1_CCxNCmd(TIM1_CHANNEL_1, DISABLE); //A- OFF Step 1
        GPIO_WriteLow(GPIOB, GPIO_PIN_0); //A- OFF Step 2
                            
        TIM1_CCxCmd(TIM1_CHANNEL_2, DISABLE); //B+ OFF Step 1
        GPIO_WriteLow(GPIOC, GPIO_PIN_2); //B+ OFF Step 2
                          
        TIM1_CCxNCmd(TIM1_CHANNEL_2, DISABLE); //B- OFF Step 1
        GPIO_WriteLow(GPIOB, GPIO_PIN_1); //B- OFF Step 2
                            
        TIM3_CCxCmd(TIM3_CHANNEL_2, DISABLE); //C+ substitute OFF Step 1
        GPIO_WriteLow(GPIOD, GPIO_PIN_0); //C+ substitute OFF Step 2
                            
        TIM1_CCxNCmd(TIM1_CHANNEL_3, DISABLE); //C- OFF Step 1
        GPIO_WriteLow(GPIOB, GPIO_PIN_2); //C- OFF Step 2
 }
}
}
[ - ]
Reply by matthewbarrNovember 6, 2019

Yes! I would write == TRUE instead of != FALSE (or just omit the != FALSE) but same logic in any case. I believe you can better optimize your C1 and C2 computations:

C1 = ((float)A0/1024)*800; //A+ & C+ TIM Pulse Generator 
C2 = ((float)A0/1024)*640; //B+ TIM Pulse Generator

You're scaling two values by a 10-bit ADC result and I think your code gives you the right answer, but you convert uint to float, float divide by 1024, float multiply by 800 and convert float to int.

You probably don't have floating point hardware, so you'll pull in IEEE floating point software libs and chew up clock cycles computing C1 and C2. You might instead consider:

C1 = (800*(uint32_t)A0) >> 10; //A+ & C+ TIM Pulse Generator 
C2 = (640*(uint32_t)A0) >> 10; //B+ TIM Pulse Generator

This will give you generally the same result with a single 32-bit integer multiply and shift. There may be rounding differences, but for your purpose (analog pot voltage as throttle) this shouldn't matter. You could always round up based on the lower 10 bits before shift, but I don't think you need to worry about it.

A0 is an unsigned int which could be 16 or 32 bits. If it's 16 bits, the multiply will overflow if A0 > 81. The A0 cast to uint32_t prevents this by forcing a 32-bit multiply and shift.

It's good practice to use uint16_t or uint32_t instead of unsigned int, especially for embedded projects. This eliminates platform dependent word size ambiguity, important for portability and also helps prevent bugs.

[ - ]
Reply by DebDNovember 6, 2019

Strangely enough my IDE doesn't recognize "TRUE", which is why I had to write "! = FALSE"

I've forgotten to call the ADC setup function inside the main(). Which is perhaps why the setup didn't run today with this code I posted...

I've made the changes in the code you suggested. I shall update you about the status of the hardware...

Thanks and Regards!

[ - ]
Reply by DebDNovember 6, 2019

Hi could you please check if I'm thinking correctly here- In one complete rotor rotation, there'll be 4 electrical cycles (8 pole motor) i.e. there should be 4 intervals where A+ B- switches conduct together. The line voltage waveform AB should look similar at these intervals. 

What I'm saying is I'll give the rotor half a rotation, so that two electrical cycles are generated. I'll put l-l vlg AB on one channel of scope, the 3 hall outputs on the other 3 channels (H1 H2 H3 properly ordered) and observe the A+B- conduction zone and corresponding hall pattern (two electrical cycles would allow me to verify my work easily)

Then I'll repeat this exercise for B+C- and C+A- The rest 3 can be obtained simply by binary complementing.

[ - ]
Reply by DebDNovember 6, 2019

I was able to draw up a Hall Pattern vs Switching State table by observing the l-n generated vlgs. and hall patterns on scope (approximations involved ofcourse) Quite interestingly, I got similar ++zero--zero sequences for the phases as you had mentioned. Here's my table (I actually observed three of these from the available waveforms, the ones in bold face in the table below and deduced the other 3 by binary complementing)


Switching StateH1H2H3
C+A-001
A+C-110
B+C-100
C+B-011
A+B-010
B+A-101
[ - ]
Reply by matthewbarrNovember 6, 2019

Hey, that looks reasonable! I'd re-order your table this way, this should be the electrical cycle commutation state sequence from top top bottom:

Switching StateH1H2H3
A+B-010
A+C-110
B+C-100
B+A-101
C+A-001
C+B-011

You pretty much have to get the ++z--z sequences shifted by 120 degrees, that's the nature of the 3 phase back-EMF from a current day BLDC motor. It's then a matter of determining how the Hall states correspond.


[ - ]
Reply by DebDNovember 6, 2019

When this table of mine is rearranged as per the hall sequence given in ti's table https://e2e.ti.com/blogs_/b/industrial_strength/ar...

I get the ++z--z etc sequences as you mentioned

Ph. A: ++z--z

Ph. B: z--z++

Ph. C: -z++z-

H1H2H3Switching State
110A+C-
010A+B-
011C+B-
001C+A-
101B+A-
100B+C-
[ - ]
Reply by matthewbarrNovember 6, 2019

Yes, very nice! Note that your back-EMF phase sequence top-to-bottom is A-C-B rather than A-B-C. Again, the important thing is to apply power from the bridge consistently with back-EMF as a function of commutation state.

[ - ]
Reply by rtomkinsNovember 6, 2019

Interestingly, I got the mail today, and the latest edition of Circuit Cellar was in there.

Part 1 of a multipart article about creating a VFD (Variable Frequency Drive) is in this issue.

Go to your favorite magazine stand and get yours now.

This is cool stuff.

I wrote documentation years ago for an industrial compressor test system and it used a very high power Siemens VFD and motor to drive the compressors through a two speed gear box. It was enormous and hundreds of horsepower.

The circuit cellar article is concerns re-purposing VFD and motor from a scrapped washing machine. There are some good cations in the article, the supply voltage for the drive is +/- 160 VDC and logic ground is referenced to -160 VDC, so you don't want to connect the USB of your PC nor your oscilloscope or any other ground (neutral) referenced electronics to the circuit, or you'll let all the magic smoke out of your devices, worse, you could let your own magic smoke out too.

Memfault Beyond the Launch