README.md 8.94 KB
Newer Older
1
2
# Programming the BLDC Driver

Jake Read's avatar
Jake Read committed
3
While I went through this previously with the [ATSAMS70](atsams70.md), we're going to do it again on the ATSAMD51. I have a v0.3 board here (which will shortly be rev'd to 0.31 due to a few already apparent mistakes, welp) and I can program it. I'm in the process of checking all of the hardware so that I can go forward with a new board order, knowing a bit better that I'll be o-k with that set. 
Jake Read's avatar
Jake Read committed
4

Jake Read's avatar
Jake Read committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
I've got the PWM up, and it's running as expected. Nice.

```C

int main(void)
{
    /* Initialize the SAM system */
    SystemInit();
	
	PORT->Group[1].DIRSET.reg |= (uint32_t)(1 << 9);
	PORT->Group[0].DIRSET.reg |= (uint32_t)(1 << 23);
	
	SysTick_Config(5000000);
	
	/* TCC SETUP */
	// from 49.6.2.1
	// a few registers are protected - and can only be updated when
	// TCCn.CTRLA.ENABLE = 0
	// FCTRLA and FCTRLB, WEXCTRL, DRVCTRL, and EVCTRL
	
	// (4) Configure Output Pin with PORT->Group[n].DIRSET.reg
	// PA8 PA9 PA10 PA12, PB10 PB11
	// 32.9.13
	PORT->Group[0].DIRSET.reg |= (uint32_t)(1 << 8) | (uint32_t)(1 << 9) | (uint32_t)(1 << 10) | (uint32_t)(1 << 12);
	PORT->Group[1].DIRSET.reg |= (uint32_t)(1 << 10) | (uint32_t)(1 << 11);
	
	// 1 lo / hi
	PORT->Group[0].PINCFG[10].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[10>>1].reg |= PORT_PMUX_PMUXE(0x5);  // on peripheral F
	PORT->Group[0].PINCFG[12].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[12>>1].reg |= PORT_PMUX_PMUXE(0x5); 
	
	// 2 lo / hi
	PORT->Group[0].PINCFG[9].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[9>>1].reg |= PORT_PMUX_PMUXO(0x5);  // on peripheral F
	PORT->Group[1].PINCFG[11].bit.PMUXEN = 1;
	PORT->Group[1].PMUX[11>>1].reg |= PORT_PMUX_PMUXO(0x5);
	
	// 3 lo / hi
	PORT->Group[0].PINCFG[8].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[8>>1].reg |= PORT_PMUX_PMUXE(0x5);  // on peripheral F
	PORT->Group[1].PINCFG[10].bit.PMUXEN = 1;
	PORT->Group[1].PMUX[10>>1].reg |= PORT_PMUX_PMUXE(0x5);
	
	// (1) enable the TCC Bus Clock - CLK_TCCn_APB
	// https://www.eevblog.com/forum/microcontrollers/atmel-sam-d-tc-and-tcc-(no-asf)/
	
	TCC0->CTRLA.bit.ENABLE = 0;
	
	MCLK->APBBMASK.reg |= MCLK_APBBMASK_TCC0; // at 15.8.9
	
	GCLK->GENCTRL[5].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL) | GCLK_GENCTRL_GENEN;
	while(GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL5);
	
	GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK5;
	
	TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV8 | TCC_CTRLA_PRESCSYNC_PRESC |TCC_CTRLA_RESOLUTION(0);
	
	// (2) Select Waveform Generation operation in the WAVE register WAVE.WAVEGEN
	// we want dual slope pwm
		
	TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_DSBOTH; // 'dual slope both' - updates on both hi and lo of slope ?
		
	// (3) We want OTMX - Output Matrix Channel Pin Routing Configuration - at 0x0
	
	TCC0->WEXCTRL.reg = TCC_WEXCTRL_DTHS(1) | TCC_WEXCTRL_DTLS(1) | 
						TCC_WEXCTRL_DTIEN1 | TCC_WEXCTRL_DTIEN2 | TCC_WEXCTRL_DTIEN3 | TCC_WEXCTRL_DTIEN0 |
						TCC_WEXCTRL_OTMX(0);
						
	TCC0->PER.reg = TCC_PER_PER(256); // 18 bit
	
	TCC0->COUNT.reg = 0;
	
	TCC0->CC[0].reg = 12; // '3'
	TCC0->CC[1].reg = 24; // '2'
	TCC0->CC[2].reg = 48; // '1'
	TCC0->CC[3].reg = 0;
	
	// (4) Enable with CTRLA.ENABLE
	
	TCC0->CTRLA.bit.ENABLE = 1;
	while(TCC0->SYNCBUSY.bit.ENABLE);

    while (1) 
    {
		PORT->Group[1].OUTTGL.reg = (uint32_t)(1 << 9);
    }
}
```

![atsamd51 pwm](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/raw/master/images/programming-pwm-alive-atsamd51.png)

![atsamd51 pwm](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/raw/master/images/programming-pwm-alive-atsamd51-picture.jpg)

Next up is the SPI wakeup.

Great, this is running as well.

```C
int main(void)
{
    /* Initialize the SAM system */
    SystemInit();
	
	PORT->Group[1].DIRSET.reg |= (uint32_t)(1 << 9);
	PORT->Group[0].DIRSET.reg |= (uint32_t)(1 << 23);
	
	SysTick_Config(5000000);
	
	/* BEGIN SPI SETUP */
	
	// PA04, SER0-0, SPI_MISO
	// PA05, SER0-1, SPI_SCK
	// PA06, SER0-2, SPI_CSN
	// PA07, SER0-3, SPI_MOSI
	PORT->Group[0].DIRCLR.reg |= (uint32_t)(1 << 4); 
	PORT->Group[0].DIRSET.reg |= (uint32_t)(1 << 5) | (uint32_t)(1 << 6) | (uint32_t)(1 << 7);
	
	PORT->Group[0].PINCFG[4].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[4>>1].reg |= PORT_PMUX_PMUXE(0x3);  // on peripheral D
	PORT->Group[0].PINCFG[5].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[5>>1].reg |= PORT_PMUX_PMUXO(0x3);  // on peripheral D
	PORT->Group[0].PINCFG[6].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[6>>1].reg |= PORT_PMUX_PMUXE(0x3);  // on peripheral D
	PORT->Group[0].PINCFG[7].bit.PMUXEN = 1;
	PORT->Group[0].PMUX[7>>1].reg |= PORT_PMUX_PMUXO(0x3);  // on peripheral D
	
	// setup clocks to sercom
	
	MCLK->APBAMASK.reg |= MCLK_APBAMASK_SERCOM0; // at 15.8.9
	
	GCLK->GENCTRL[6].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL) | GCLK_GENCTRL_GENEN;
	while(GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL6);
	
	GCLK->PCHCTRL[SERCOM0_GCLK_ID_CORE].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK6;
	
	// TCC0_GCLK_ID
	
	// Some registers can't be written unless CTRL.ENABLE = 0:
	// CTRLA, CTRLB, BAD and ADDR
	
	// (1) set to master
	
	SERCOM0->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_MODE(0x3); // 0x2 or 0x3, slave or master
	
	// SERCOM0->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_CPHA | SERCOM_SPI_CTRLA_CPOL; // clock phase and polarity
	
	// (2) set pin configurations
	
	SERCOM0->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_DIPO(0x0) | SERCOM_SPI_CTRLA_DOPO(0x2); // pin selections, see 35.8.1 bits 21:20 and 17:16, pg. 910
	
	// (3) set character size, data direction
	
	//SERCOM0->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_DORD; // 0 MSB, 1 LSB
	//SERCOM0->SPI.CTRLB.reg |= SERCOM_SPI_CTRLB_CHSIZE(0x0); // 8 bits character - 0x0, so no need to set
	
	// (4) setup baud rate
	// f_baud = f_ref / (2 * (BAUD +1)) so BAUD = f_ref / (2 * f_baud) - 1
	
	SERCOM0->SPI.BAUD.reg |= SERCOM_SPI_BAUD_BAUD(126);
	SERCOM0->SPI.CTRLB.reg |= SERCOM_SPI_CTRLB_MSSEN | SERCOM_SPI_CTRLB_RXEN; // slave select hardware yes
	
	SERCOM0->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_ENABLE;
	
    while (1) 
    {
		while(!(SERCOM0->SPI.INTFLAG.bit.DRE));
		SERCOM0->SPI.DATA.reg = SERCOM_SPI_DATA_DATA(80);
		PORT->Group[1].OUTTGL.reg = (uint32_t)(1 << 9); // i-v, to check we made it thru setup
    }
}
```

![atsamd51 spi](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/raw/master/images/programming-spi-alive-atsamd51.png)

Jake Read's avatar
Jake Read committed
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
Now we do v0.31 board, new step board, etc. Go team, big day.

# Waking up v0.31

![pwm-one-channel](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/raw/master/images/programming-spi-atsamd51-as5147.png)

Have this SPI running with the encoder in-line and PWM still setup.

I integrated UART / Ringbuffers etc from the mkstepper project. So I have everything I need to start commutating. 

OK! 

## Commutating

Properly, I should do this on a timer. I'm going to do it in the while() loop for now, just to check that I'm having the output on the PWMs that I want.

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
Great - I have this running in the open while() loop. It commutates! I suppose I shouldn't be surprised at this stuff anymore.

I set up my logic analyzer so that I can start on this commutation debug cycle. I have three pins on the lo-sides of the PWMs, three on the voltage sense pinse, and two on the current sense pins. 

I'm hearing this 'tick' every so often as the motor commutates. Here's what it looks like on the analyzer:

![pwm-tick](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/raw/master/images/programming-pwm-ticking.png)

So I think that one of the PWM registers is occasionally being written to 100% in error. My suspicion is that this has something to do with my fast-and-loose commutation scheme, which I'm about to improve.

While I did notice that this was only occuring while the motor driver's gates where enabled (so, 'stuff was happening'), I tried using the ATSAMD's PWM Capture-Compare Buffer (capture-compare is the value the pwm timer checks to switch-or-not-switch the output). The buffer let's me write into the PWM registers when I'm sure they're not being read by the peripheral. This eliminated the problem. I also pushed the PWM frequency to 22kHZ and it's all silky smooth sounding now.

OK, some current / voltage waveforms:

Channels: Fault, PWM Hi U, Pwm Hi V, Pwm Hi W, Current V, Current W, Voltage V, Voltage W.

![currents](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/raw/master/images/programming-currents-olcommutate-1.png)

![currents](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/raw/master/images/programming-currents-olcommutate-2.png)

I set this up to accept a commanded 'torque' (just PWM duty cycle) and direction, so next step here is doing some network integration as well.

Jake Read's avatar
Jake Read committed
218
[Video](https://gitlab.cba.mit.edu/jakeread/mkbldcdriver/blob/master/video/20khz-commutate.mp4)
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234

Then, a longer list of development:

# Next:

## Firmware
 - Closed-Loop Speed Control (maybe using simple 6-step commutation?)
 - Search for Encoder Offset
 - Closed-Loop Position Control
  - Probably just Sinusoid PWM Commutation
 - The Big Bite: FOC

## Hardware
 - I have a list of incremental improvements... mostly:
 - Go to 2oz copper so that I dont' blow up any traces when the power hits
 - Discrete LEDs and more indication (there's an overcurrent / overtemp warning I want to break out)