forked from Traumflug/Teacup_Firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
heater-lpc.c
238 lines (197 loc) · 10.1 KB
/
heater-lpc.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
/** \file
\brief Manage heaters, including PID and PWM, ARM specific part.
For test cases see the intro comment in heater.c.
*/
#if defined TEACUP_C_INCLUDE && defined __ARM_LPC1114__
#include "cmsis-lpc11xx.h"
#include <stddef.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 48 MHz CPU clock):
prescaler frequency prescaler frequency
0 188.2 kHz 4 37.6 kHz
1 91.1 kHz 5 31.4 kHz
2 62.7 kHz ... ...
3 47.0 kHz 65535 2.87 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 1.4 Hz...91.1 kHz, quadrupling it to 1020 moves the range
to 0.7 Hz...46.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 3 Hz.
*/
#define PWM_SCALE 1020
/** \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 match register which changes PWM duty.
__IO uint32_t* match;
/// Pointer to the port for non-PWM pins.
__IO uint32_t* masked_port;
};
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->MR[pin ## _MATCH]) : \
&(pin ## _PORT->MASKED_ACCESS[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 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 LPC1114 are usable as following, capture pins not listed:
pin timer/match func for PWM other uses
PIO0_1 CT32B0_MAT2 0x2 ---
PIO0_8 CT16B0_MAT0 0x2 MISO0
PIO0_9 CT16B0_MAT1 0x2 MOSI0
PIO0_10 CT16B0_MAT2 0x3 SCK0 (also on PIO0_6)
PIO0_11 CT32B0_MAT3 0x3 AD0, Step timer
PIO1_1 CT32B1_MAT0 0x3 AD2
PIO1_2 CT32B1_MAT1 0x3 AD3
PIO1_3 CT32B1_MAT2 0x3 AD4
PIO1_4 CT32B1_MAT3 0x2 AD5, PWM reset
PIO1_6 CT32B0_MAT0 0x2 RXD, Step timer
PIO1_7 CT32B0_MAT1 0x2 TXD, Step timer
PIO1_9 CT16B1_MAT0 0x1 ---
*/
// 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 == LPC_TMR16B0) { \
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 7); /* Turn on CT16B0. */ \
} \
else if (pin ## _TIMER == LPC_TMR16B1) { \
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 8); /* Turn on CT16B1. */ \
} \
else if (pin ## _TIMER == LPC_TMR32B1) { \
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 10); /* Turn on CT32B1. */ \
} \
\
LPC_IOCON->pin ## _CMSIS = pin ## _PWM; /* Connect to timer. */ \
/*pin ## _TIMER->IR = 0; ( = reset value) No interrupts. */ \
pin ## _TIMER->TCR = (1 << 0); /* Enable counter. */ \
freq = F_CPU / PWM_SCALE / (pwm ? pwm : 1); /* Figure PWM freq. */ \
if (freq > 65535) \
freq = 65535; \
if (freq < 1) \
freq = 1; \
pin ## _TIMER->PR = freq - 1; /* Prescaler to freq. */ \
pin ## _TIMER->MCR = (1 << 10); /* Reset on Match 3. */ \
/* PWM_SCALE - 1, so match = 255 is full off. */ \
pin ## _TIMER->MR[3] = PWM_SCALE - 1; /* Match 3 at 254. */ \
pin ## _TIMER->MR[pin ## _MATCH] = /* Match pin = duty. */ \
invert ? 0 : PWM_SCALE; \
/*pin ## _TIMER->CCR = 0; ( = reset value) No pin capture. */ \
pin ## _TIMER->EMR |= ((1 << pin ## _MATCH) /* Connect to pin. */ \
| (0x03 << ((pin ## _MATCH * 2) + 4))); /* Toggle pin on match.*/ \
/*pin ## _TIMER->CTCR = 0; ( = reset value) Timer mode. */ \
pin ## _TIMER->PWMC |= ((1 << 3) /* 3 to PWM mode. */ \
| (1 << pin ## _MATCH)); /* Pin to PWM mode. */ \
} \
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) {
uint32_t pwm_value;
// Remember, we scale, and the timer inverts already.
pwm_value = (uint32_t)((heaters[index].max_value * value) * (PWM_SCALE / 255) / 256);
if ( ! heaters[index].invert)
pwm_value = PWM_SCALE - pwm_value;
*heaters[index].match = pwm_value;
if (DEBUG_PID && (debug_flags & DEBUG_PID))
sersendf_P(PSTR("PWM %su = %lu\n"), index, *heaters[index].match);
}
else {
*(heaters[index].masked_port) =
((value >= HEATER_THRESHOLD && ! heaters[index].invert) ||
(value < HEATER_THRESHOLD && heaters[index].invert)) ?
0xFFFF : 0x0000;
}
if (value)
power_on();
}
}
#endif /* defined TEACUP_C_INCLUDE && defined __ARM_LPC1114__ */