forked from Traumflug/Teacup_Firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
heater-stm32.c
265 lines (224 loc) · 12.6 KB
/
heater-stm32.c
1
2
3
4
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/** \file
\brief Manage heaters, including PID and PWM, ARM specific part.
*/
#if defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__
#include "cmsis-stm32f4xx.h"
#include "pinio.h"
#include "sersendf.h"
#include "debug.h"
/**
Test configuration.
*/
#ifdef EECONFIG
#error EEPROM handling (EECONFIG) not yet supported on ARM.
#endif
#ifdef BANG_BANG
#error BANG_BANG not supported on ARM. You may set PWM frequency of one \
or all heater(s) to zero, which gives similar, better behaviour.
#endif
/** \def PWM_SCALE
G-code standard (if such a thing exists at all) gives a heater setting
range between 0 (off) and 255 (full on), so we let the PWM timers count up
to 255. Doing so allows to set the prescaler for these frequencies (all on
a 96 MHz CPU clock):
prescaler frequency prescaler frequency
0 367.5 kHz 4 75.3 kHz
1 188.2 kHz 5 62.7 kHz
2 125.5 kHz ... ...
3 94.1 kHz 65535 5.74 Hz
As one can see, frequency steps are rather coarse on the high end and
become finer grained the lower it gets.
If this range is generally too high for your purposes, you can set PWM_SCALE
to multiples of 255 to lower the range. Doubling it to 510 moves the
frequency range to 2.9 Hz...183.8 kHz, quadrupling it to 1020 moves the range
to 1.5 Hz...91.9 kHz and so on. The highest allowed number is 65535.
That said, code below calculates the best prescaler value for a configured
frequency, so you should bother about PWM_SCALE only of you need frequencies
below 6 Hz.
*/
#define PWM_SCALE 1020
// some helper macros
#define _EXPANDER(pre, val, post) pre ## val ## post
#define EXPANDER(pre, val, post) _EXPANDER(pre, val, post)
/** \struct heater_definition_t
Holds pinout data to allow changing PWM output after initialisation. Port,
pin, PWM channel if used. After inititalisation we can no longer do the
#include "config_wrapper.h" trick.
*/
typedef struct {
union {
/// Pointer to the capture compare register which changes PWM duty.
__IO uint32_t* ccr;
/// Pointer to the port for non-PWM pins.
__IO uint32_t* bsrr;
};
uint16_t masked_pin;
uint16_t max_value; ///< max value for the heater, for PWM in percent * 256
pwm_type_t pwm_type; ///< saves the pwm-type: NO_PWM, SOFTWARE_PWM, HARDWARE_PWM
uint8_t invert; ///< Wether the heater pin signal needs to be inverted.
} heater_definition_t;
// When pwm >= 2 it's hardware pwm, if the pin has hardware pwm.
// When pwm == 1 it's software pwm.
// pwm == 0 is no pwm at all.
// Use this macro only in DEFINE_HEATER_ACTUAL-macros.
#define PWM_TYPE(pwm, pin) (((pwm) >= HARDWARE_PWM_START) ? ((pin ## _TIMER) ? HARDWARE_PWM : SOFTWARE_PWM) : (pwm))
#undef DEFINE_HEATER_ACTUAL
#define DEFINE_HEATER_ACTUAL(name, pin, invert, pwm, max_value) \
{ \
{ (PWM_TYPE(pwm, pin) == HARDWARE_PWM) ? \
&(pin ## _TIMER-> EXPANDER(CCR, pin ## _CHANNEL,)) : \
&(pin ## _PORT->BSRR) }, \
MASK(pin ## _PIN), \
(PWM_TYPE(pwm, pin) != SOFTWARE_PWM) ? \
((max_value * 64 + 12) / 25) : \
(uint16_t)(255UL * 100 / max_value), \
PWM_TYPE(pwm, pin), \
invert ? 1 : 0 \
},
static const heater_definition_t heaters[NUM_HEATERS] = {
#include "config_wrapper.h"
};
#undef DEFINE_HEATER_ACTUAL
// We test any heater if we need software-pwm
#define DEFINE_HEATER_ACTUAL(name, pin, invert, pwm, ...) \
| (PWM_TYPE(pwm, pin) == SOFTWARE_PWM)
static const uint8_t software_pwm_needed = 0
#include "config_wrapper.h"
;
#undef DEFINE_HEATER_ACTUAL
/** Initialise heater subsystem.
Initialise PWM timers, etc. Inspired by heater-arm_lpc11xx.c (pwm.c in LPC1343CodeBase):
https://github.com/microbuilder/LPC1343CodeBase
Note that PWM is inversed, pins start at Low by chip design. When the pin's
counter is reached, they're set to High. Reaching the timer reset counter is
programmed to reset everything and start over. Thus, having both counter
matches similar gives a low duty, having the pin counter match zero gives
full on.
For simplicity we reset all timer counters always on Match 3 and always
at PWM_SCALE (255 per default), so setting a pin match to PWM_SCALE / 2
gives 50% duty, setting it to PWM_SCALE gives full off. This choice disallows
using a pin connected to a Match 3, working around this would make code much
more complicated (and still not allow to use more than 3 pins per timer).
On ARM we can define a PWM frequency pretty fine grained, so we take the
'pwm' value of DEFINE_HEATER() not only wether to use PWM at all, but also
to define the PWM frequency. Float values are allowed.
If there's more than one pin on a timer, they share the same PWM frequency;
the frequency choosen is the one of the pin defined last.
*/
void heater_init() {
/**
Pins on the STM32F411RE are usable as following, N are negated pin (active low)
some pins are commented out (-) because they are shared. You can change this
in arduino_stm32f4xx. But take care! You could pwm two pins simultanious or disable
other important functions (serial connection).
PWM5 = TIM5 = Stepper timer.
pin timer/channel alt func for PWM other uses
PIOA_0 PWM2/1, 5/1 01, 02 AD0
PIOA_1 PWM2/2, 5/2 01, 02 MOSI4, AD1
- PIOA_2 PWM2/3, 5/3, 9/1 01, 02, 03 TX2, AD2, UART!
- PIOA_3 PWM2/4, 5/4, 9/2 01, 02, 03 RX2, AD3, UART!
- PIOA_5 PWM2/1 01 LED1, SCK1, AD5
- PIOA_6 PWM3/1 02 MISO1, AD6
- PIOA_7 PWM1/1N, 3/2 01, 02 MOSI1, AD7
PIOA_8 PWM1/1 01 SCL1
PIOA_9 PWM1/2 01 TX1
PIOA_10 PWM1/3 01 MOSI5, RX1
PIOA_11 PWM1/4 01 TX6, MISO4
- PIOA_15 PWM2/1 01 NSS1, TX1
PIOB_0 PWM1/2N, 3/3 01, 02 SCK5, CK5, AD8
PIOB_1 PWM1/3N, 3/4 01, 02 NSS4, WS5, AD9
- PIOB_3 PWM2/2 01 SDA2, SCK3
PIOB_4 PWM3/1 02 SDA3, MISO3
PIOB_5 PWM3/2 02 MOSI3
PIOB_6 PWM4/1 02 SCL1, TX1
PIOB_7 PWM4/2 02 SDA1, RX1
PIOB_8 PWM4/3, 10/1 02, 03 SCL1, MOSI5
PIOB_9 PWM4/4, 11/1 02, 03 SDA1, NSS2
PIOB_10 PWM2/3 01 SCL3
- PIOB_13 PWM1/1N 01 SCK2
- PIOB_14 PWM1/2N 01 MISO2
- PIOB_15 PWM1/3N 01 MOSI2
- PIOC_6 PWM3/1 02 MCK2, TX6
- PIOC_7 PWM3/2 02 SCK2, RX6
- PIOC_8 PWM3/3 02 SDA3
- PIOC_9 PWM3/4 02 SDA3
*/
// Auto-generate pin setup.
#undef DEFINE_HEATER_ACTUAL
#define DEFINE_HEATER_ACTUAL(name, pin, invert, pwm, ...) \
if (PWM_TYPE(pwm, pin) == HARDWARE_PWM) { \
uint32_t freq; \
if (pin ## _TIMER == TIM1) { \
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; } /* turn on TIM1 */ \
else if (pin ## _TIMER == TIM2) { \
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; } /* turn on TIM2 */ \
else if (pin ## _TIMER == TIM3) { \
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; } /* turn on TIM3 */ \
else if (pin ## _TIMER == TIM4) { \
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; } /* turn on TIM4 */ \
/* TIM5 is for stepper, TIM9, TIM10 and TIM11 are not used */ \
SET_MODE(pin, 0x2); /* pin mode to AF */ \
SET_AFR(pin, pin ## _AF); \
SET_OSPEED(pin, 0x3); /* high speed */ \
PULL_OFF(pin); /* no pullup/-down */ \
pin ## _TIMER->CR1 |= TIM_CR1_ARPE; /* auto-reload preload */ \
pin ## _TIMER->ARR = PWM_SCALE - 1; /* reset on auto reload at 254 */ \
/* PWM_SCALE - 1, so CCR = 255 is full off. */ \
pin ## _TIMER-> EXPANDER(CCR, pin ## _CHANNEL,) = 0; /* start off */ \
freq = F_CPU / PWM_SCALE / (pwm ? pwm : 1); /* Figure PWM freq. */ \
if (freq > 65535) \
freq = 65535; \
if (freq < 1) \
freq = 1; \
pin ## _TIMER->PSC = freq - 1; /* 1kHz */ \
if (pin ## _CHANNEL <= 2) \
pin ## _TIMER->CCMR1 |= 0x68UL << (8 * (pin ## _CHANNEL && 2)); \
else \
pin ## _TIMER->CCMR2 |= 0x68UL << (8 * (pin ## _CHANNEL && 4)); \
\
pin ## _TIMER->CCER |= EXPANDER(TIM_CCER_CC, pin ## _CHANNEL, E); \
/* output enable */ \
if (pin ## _INVERT ^ invert) \
pin ## _TIMER->CCER |= EXPANDER(TIM_CCER_CC, pin ## _CHANNEL, P); \
else \
pin ## _TIMER->CCER &= ~(EXPANDER(TIM_CCER_CC, pin ## _CHANNEL, P)); \
\
/* invert the signal for negated timers*/ \
/* also with a XOR for inverted heaters */ \
pin ## _TIMER->EGR |= TIM_EGR_UG; /* update generation */ \
pin ## _TIMER->CR1 |= TIM_CR1_CEN; /* enable counters */ \
} \
else { \
SET_OUTPUT(pin); \
WRITE(pin, invert ? 1 : 0); \
}
#include "config_wrapper.h"
#undef DEFINE_HEATER_ACTUAL
pid_init();
}
/** Set PWM output.
\param index The heater we're setting the output for.
\param value The PWM value to write, range 0 (off) to 255 (full on).
This function is called by M106 or, if a temp sensor is connected to the
heater, every few milliseconds by its PID handler. Using M106 on an output
with a sensor changes its setting only for a short moment.
*/
void do_heater(heater_t index, uint8_t value) {
if (index < NUM_HEATERS) {
if (heaters[index].pwm_type == HARDWARE_PWM) {
// Remember, we scale, and duty cycle is inverted.
*heaters[index].ccr = (uint32_t)((heaters[index].max_value * value) * (PWM_SCALE / 255) / 256);
if (DEBUG_PID && (debug_flags & DEBUG_PID))
sersendf_P(PSTR("PWM %su = %lu\n"), index, *heaters[index].ccr);
}
else {
*(heaters[index].bsrr) =
heaters[index].masked_pin <<
((value >= HEATER_THRESHOLD && ! heaters[index].invert) ?
0 : 16);
}
if (value)
power_on();
}
}
#endif /* defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__ */