Skip to content

Commit 13b7707

Browse files
committed
Fix issue introduced by correction to #591
1 parent fe065c2 commit 13b7707

3 files changed

Lines changed: 58 additions & 29 deletions

File tree

megaavr/libraries/SPI/README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ Notes:
3030

3131
When `SPI.swap()` or `SPI.pins()` is called, assuming it was called with a valid option, the pin mapping requested is saved. When `SPI.begin()` is called, this stored value will specify changes to `PORTMUX.SPIROUTEA` and `PORTx` registers. Thus, if a non-default pin mapping is required, you must set it before before calling `SPI.begin()`. To change the pin mapping after `SPI.begin()` you must turn off SPI with`SPI.end()`, call `SPI.swap()` or `SPI.pins()` and then `SPI.begin()` again.
3232

33+
34+
## Note on standard SPI.setClockDivider() method
35+
As always, constants of the form `SPI_CLOCK_DIVn` are provided. These are what should be passed to setClockDivider().
36+
37+
In the event that an invalid value is passed, if the value is known at compile time, an error will be produced telling you that you're passing an invalid value. However, this only catches the most common, obvious cases. Since there are no runtime exceptions, invalid values that are not known at compile time will get through. When an invalid value is passed that can't be picked out at compile time, we are left with a connundrum:
38+
39+
What should we set the clock divider to? We can't just have the function return and do nothing, because then it will appear that the problem is with the library. My first thought was to just have it set to the minimum speed, which will likely function, if possibly with poor performance, but I realized the SPI peripheral's odd design gives us a way to signalize an error condition here, because there are only 7 supported speeds, but the 2+1 bits are structured as a single-bit field that doubles the clock speed, ahd a 2-bit field that selects thee base between /4, /16, /64, and /128. Hence, /128 with CLK2x and /64 without both generate the same clock frequency.
40+
41+
`SPI_CLOCK_DIV2`, `SPI_CLOCK_DIV4`, `SPI_CLOCK_DIV8`, `SPI_CLOCK_DIV16`, `SPI_CLOCK_DIV32`, `SPI_CLOCK_DIV64`, `SPI_CLOCK_DIV128`
42+
43+
3344
## Two SPI ports
3445
The AVR DA/DB-series parts have two hardware SPI ports. On parts with more pins, they can be pin-swapped to different sets of pins (up to three sets of pins per SPI peripheral). The AVR DD-series has only a single SPI port - but it has a far more pin options than the DA/DB-series parts do. Originally, it was expected that two libraries could be created like is done for the few classic AVRs with multiple SPI ports (eg, ATmega328PB) and the many 32-bit architectures with multiple SPI ports; however, it was discovered in 1.2.0 (which attempted to implement this) that the existing libraries with which we desire compatibility (an SPI library that you need to modify everything you use with it is hardly satisfactory) were more challenging to work with than expected. In order to work with existing libraries, we need only guarantee that our instances of SPI_class have names matching the convention; that sounds like a low bar - and indeed, it is: the only way it could be a problem is if one of those key names happened to already be used for something, and not just any something, but something which had a greater authority to be naming things than anything the core or core libraries did.
3546

@@ -50,7 +61,7 @@ As of 1.3.0, the version of SPI.h included with DxCore allows all SPI0 and SPI1
5061
Unless the old attachInterrupt implementation is selected, `SPI.usingInterrupt(number)` will cause it to globally disable interrupts while in a transaction. This is not ideal, however *you should not have interrupts performing SPI transactions, period* - SPI transactions are not fast; interrupts should be fast. This is here for compatibility ONLY. The standard implementation of this does not map cleanly onto the new attachInterrupt(). Furthermore, the standard implementation has several logic gaps and race conditions which, together, could still result in the same sort of behavior it was aimed at preventing.
5162
Similarly, `SPI.notUsingInterrupt(number)` will set it back to the normal mode. This is also not ideal, but we needed a timely fix and as I said, this functionality only, as supplied, approximated correct behavior. I'll come back to this at some point, but it is no longer an urgent - not least because you should never use these functions, because you should not be using SPI from interrupts! The one exception to this is the case where the number passed is NOT_AN_INTERRUPT (-1 or 255), in which case we match the old behavior and simply return.
5263

53-
## SPI.attachInterrupt and SPI.detachInterrupt
64+
## SPI.attachInterrupt() and SPI.detachInterrupt
5465
These methods, as far as I can tell were never supported for the official or third party AVR boards, only third party extensions where some other API was used to support SPI slave mode functionality. They were marked as something that should never be called, and there was no sign of code that made use of them within the core. In fact, there was no sign of use of them on the wider internet - except for slave mode extensions for different architectures; hence, I feel safe ditching these.
5566

5667
## SS (Slave Select) pin

megaavr/libraries/SPI/src/SPI.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
#define SPI_MODULE SPI0
4141
#endif
4242

43+
44+
4345
const SPISettings DEFAULT_SPI_SETTINGS = SPISettings();
4446

4547
SPIClass::SPIClass() {
@@ -588,12 +590,14 @@ void SPIClass::setDataMode(uint8_t mode) {
588590

589591

590592
void SPIClass::setClockDivider(uint8_t div) {
591-
mode_gm = ~(SPI_MODE_gm | SPI_CLK2X_bm);
592-
if (div & mode_gm)
593-
if (__builtin_constant_p(div))
594-
uint8_t tem = SPI_MODULE.CTRLA;
595-
tem &= ~SPI_PRESC_gm;
596-
tem |= div;
593+
_check_valid_spi(div); // error if the user has passed something that's not a valid constant to it.
594+
uint8_t tem = ~(SPI_PRESC_gm | SPI_CLK2X_bm);
595+
if (div & tem) { //it has bits set not in the bitfield - this can only be true if div is determined at runtime *and* invalid.
596+
div = SPI_CLOCK_ERROR; //since we can't fulfill their request, set the divisor to an error
597+
}
598+
tem &= SPI_MODULE.CTRLA; // now we have the current value of the register with the bits masked off, reusing the temp variable as a mask.
599+
tem |= div;
600+
SPI_MODULE.CTRLA = div;
597601
}
598602

599603
uint8_t SPIClass::transfer(uint8_t data) {
@@ -643,3 +647,6 @@ void SPIClass::transfer(void *buf, size_t count) {
643647
#if SPI_INTERFACES_COUNT > 0
644648
SPIClass SPI;
645649
#endif
650+
651+
652+

megaavr/libraries/SPI/src/SPI.h

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,31 @@
2222
#define _SPI_H_INCLUDED
2323

2424
#include <Arduino.h>
25+
#ifndef SPI_CLOCK_DIV2
26+
#define SPI_CLOCK_DIV2 (SPI_PRESC_DIV4_gc | SPI_CLK2X_bm )
27+
#endif
28+
#ifndef SPI_CLOCK_DIV4
29+
#define SPI_CLOCK_DIV4 (SPI_PRESC_DIV4_gc )
30+
#endif
31+
#ifndef SPI_CLOCK_DIV8
32+
#define SPI_CLOCK_DIV8 (SPI_PRESC_DIV16_gc | SPI_CLK2X_bm )
33+
#endif
34+
#ifndef SPI_CLOCK_DIV16
35+
#define SPI_CLOCK_DIV16 (SPI_PRESC_DIV16_gc )
36+
#endif
37+
#ifndef SPI_CLOCK_DIV32
38+
#define SPI_CLOCK_DIV32 (SPI_PRESC_DIV64_gc | SPI_CLK2X_bm )
39+
#endif
40+
#ifndef SPI_CLOCK_DIV64
41+
#define SPI_CLOCK_DIV64 (SPI_PRESC_DIV64_gc )
42+
#endif
43+
#ifndef SPI_CLOCK_ERROR
44+
#define SPI_CLOCK_ERROR (SPI_PRESC_DIV128_gc | SPI_CLK2X_bm ) // There are two ways to get DIV64 prescaling on the SPI clock. The /128 x 2 route, which is functionally identical to /64. This is what the clock gets set to if you pass garbage to setClockDivider()
45+
46+
#endif
47+
#ifndef SPI_CLOCK_DIV128
48+
#define SPI_CLOCK_DIV128 (SPI_PRESC_DIV128_gc )
49+
#endif
2550

2651
#if defined(SPI_MUX)
2752
#define SPI0_SWAP_DEFAULT 0x00
@@ -177,7 +202,14 @@
177202

178203

179204
//#define EXTERNAL_NUM_INTERRUPTS NUM_TOTAL_PINS
180-
205+
inline __attribute__((always_inline)) void _check_valid_spi(uint8_t div) {
206+
if (__builtin_constant_p(div)) {
207+
if (!(SPI_CLOCK_DIV2 == div || SPI_CLOCK_DIV4 == div || SPI_CLOCK_DIV8 == div || SPI_CLOCK_DIV16 == div || SPI_CLOCK_DIV32 == div || SPI_CLOCK_DIV64 == div || SPI_CLOCK_DIV128 == div)) {
208+
badArg("setClockDivider called with argument that is not an SPI_CLOCK_DIVn constant");
209+
// don't let them pass garbage to it
210+
}
211+
}
212+
}
181213

182214
class SPISettings {
183215
public:
@@ -346,26 +378,5 @@ class SPIClass {
346378
extern SPIClass SPI;
347379
#endif
348380

349-
#ifndef SPI_CLOCK_DIV2
350-
#define SPI_CLOCK_DIV2 (SPI_PRESC_DIV4_gc | SPI_CLK2X_bm )
351-
#endif
352-
#ifndef SPI_CLOCK_DIV4
353-
#define SPI_CLOCK_DIV4 (SPI_PRESC_DIV4_gc )
354-
#endif
355-
#ifndef SPI_CLOCK_DIV8
356-
#define SPI_CLOCK_DIV8 (SPI_PRESC_DIV16_gc | SPI_CLK2X_bm )
357-
#endif
358-
#ifndef SPI_CLOCK_DIV16
359-
#define SPI_CLOCK_DIV16 (SPI_PRESC_DIV16_gc )
360-
#endif
361-
#ifndef SPI_CLOCK_DIV32
362-
#define SPI_CLOCK_DIV32 (SPI_PRESC_DIV64_gc | SPI_CLK2X_bm )
363-
#endif
364-
#ifndef SPI_CLOCK_DIV64
365-
#define SPI_CLOCK_DIV64 (SPI_PRESC_DIV64_gc )
366-
#endif
367-
#ifndef SPI_CLOCK_DIV128
368-
#define SPI_CLOCK_DIV128 (SPI_PRESC_DIV128_gc )
369381
#endif
370382

371-
#endif

0 commit comments

Comments
 (0)