px-fwlib 0.10.0
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers generated with Doxygen 1.9.2
px_spi.c
1/* =============================================================================
2 ____ ___ ____ ___ _ _ ___ __ __ ___ __ __ TM
3 | _ \ |_ _| / ___| / _ \ | \ | | / _ \ | \/ | |_ _| \ \/ /
4 | |_) | | | | | | | | | | \| | | | | | | |\/| | | | \ /
5 | __/ | | | |___ | |_| | | |\ | | |_| | | | | | | | / \
6 |_| |___| \____| \___/ |_| \_| \___/ |_| |_| |___| /_/\_\
7
8 Copyright (c) 2012 Pieter Conradie <https://piconomix.com>
9
10 License: MIT
11 https://github.com/piconomix/px-fwlib/blob/master/LICENSE.md
12
13 Title: px_spi.h : SPI Peripheral Driver
14 Author(s): Pieter Conradie
15 Creation Date: 2012-06-23
16
17============================================================================= */
18
19/* _____STANDARD INCLUDES____________________________________________________ */
20
21/* _____PROJECT INCLUDES_____________________________________________________ */
22#include "px_spi.h"
23#include "px_spi_defs.h"
24#include "px_board.h"
25#include "px_log.h"
26
27// Issue warning that SS pin must be an output or input with a pull-up
28#warning "There is a caveat with the SS pin when using the SPI in master mode! See documentation in 'px_spi.h'"
29
30/* _____LOCAL DEFINITIONS____________________________________________________ */
31PX_LOG_NAME("spi");
32
33/// Internal data for each SPI handle
34typedef struct px_spi_per_s
35{
36 px_spi_nr_t spi_nr; ///< SPI peripheral number
37 uint8_t open_counter; /// Number of open handles referencing peripheral
39
40/* _____MACROS_______________________________________________________________ */
41
42/* _____GLOBAL VARIABLES_____________________________________________________ */
43
44/* _____LOCAL VARIABLES______________________________________________________ */
45/// Allocate data for each enabled SPI peripheral
46#if PX_SPI_CFG_SPI0_EN
47static px_spi_per_t px_spi_per_0;
48#endif
49
50/* _____LOCAL FUNCTION DECLARATIONS__________________________________________ */
51
52/* _____LOCAL FUNCTIONS______________________________________________________ */
53static void px_spi_init_per(px_spi_nr_t spi_nr,
54 uint8_t spcr,
55 bool use_spi2x)
56{
57 switch(spi_nr)
58 {
59#if PX_SPI_CFG_SPI0_EN
60 case PX_SPI_NR_0:
61 // Initialise SPI Control Register
62 SPCR = spcr;
63 // Use 2x SPI Clock rate?
64 if(use_spi2x)
65 {
66 PX_BIT_SET_HI(SPSR, SPI2X);
67 }
68 else
69 {
70 PX_BIT_SET_LO(SPSR, SPI2X);
71 }
72 break;
73#endif
74 default:
75 PX_LOG_E("Invalid peripheral");
76 return;
77 }
78}
79
80static void px_spi_update_cfg(px_spi_nr_t spi_nr,
81 uint8_t spcr,
82 bool use_spi2x)
83{
84 px_spi_init_per(spi_nr, spcr, use_spi2x);
85}
86
87static void px_spi_init_per_data(px_spi_nr_t spi_nr,
88 px_spi_per_t * spi_data)
89{
90 // Set peripheral
91 spi_data->spi_nr = spi_nr;
92 // Clear reference counter
93 spi_data->open_counter = 0;
94}
95
96/* _____GLOBAL FUNCTIONS_____________________________________________________ */
97void px_spi_init(void)
98{
99 // Initialize peripheral data for each enabled peripheral
100#if PX_SPI_CFG_SPI0_EN
101 px_spi_init_per_data(PX_SPI_NR_0, &px_spi_per_0);
102#endif
103}
104
106 px_spi_nr_t spi_nr,
107 uint8_t cs_id)
108{
109 px_spi_per_t * spi_per;
110
111#if PX_LOG
112 // Verify that pointer to handle is not NULL
113 if(handle == NULL)
114 {
115 PX_LOG_ASSERT(false);
116 return false;
117 }
118 if(handle->spi_per != NULL)
119 {
120 PX_LOG_W("Handle already open?");
121 }
122#endif
123
124 // Handle not initialised
125 handle->spi_per = NULL;
126 // Set pointer to peripheral data
127 switch(spi_nr)
128 {
129#if PX_SPI_CFG_SPI0_EN
130 case PX_SPI_NR_0:
131 spi_per = &px_spi_per_0;
132 break;
133#endif
134 default:
135 PX_LOG_E("Invalid peripheral specified");
136 return false;
137 }
138 // Save Chip Select GPIO ID
139 handle->cs_id = cs_id;
140 // Use default value for SPI Control Register
141 handle->spcr = PX_SPI_CFG_SPCR;
142 // Use default value for "Use SPI 2x clock rate"
144 // Set default value for "Master Out dummy byte"
145 handle->mo_dummy_byte = 0x00;
146 // Initialise peripheral
147 px_spi_init_per(spi_nr, handle->spcr, handle->use_spi2x);
148 // Point handle to peripheral
149 handle->spi_per = spi_per;
150
151 // Success
152 PX_LOG_ASSERT(spi_per->open_counter < 255);
153 spi_per->open_counter++;
154 return true;
155}
156
158 px_spi_nr_t spi_nr,
159 uint8_t cs_id,
160 px_spi_baud_t baud,
161 px_spi_mode_t mode,
162 px_spi_dord_t data_order,
163 uint8_t mo_dummy_byte)
164{
165 px_spi_per_t * spi_per;
166 uint8_t spcr = 0;
167 bool use_spi2x;
168
169#if PX_LOG
170 // Verify that pointer to handle is not NULL
171 if(handle == NULL)
172 {
173 PX_LOG_ASSERT(false);
174 return false;
175 }
176 if(handle->spi_per != NULL)
177 {
178 PX_LOG_W("Handle already open?");
179 }
180#endif
181
182 // Handle not initialised
183 handle->spi_per = NULL;
184 // Set pointer to peripheral data
185 switch(spi_nr)
186 {
187#if PX_SPI_CFG_SPI0_EN
188 case PX_SPI_NR_0:
189 spi_per = &px_spi_per_0;
190 break;
191#endif
192 default:
193 PX_LOG_E("Invalid peripheral specified");
194 return false;
195 }
196 // Save Chip Select GPIO ID
197 handle->cs_id = cs_id;
198 // Initial value for SPCR
199 spcr = (0 << SPIE) | (1 << SPE) | (0 << DORD) | (1 << MSTR)
200 | (0 << CPOL) | (0 << CPHA) | (0 << SPR1) | (0 << SPR0);
201 // Set baud
202 use_spi2x = false;
203 switch(baud)
204 {
205 case PX_SPI_BAUD_CLK_DIV_2: spcr |= (0 << SPR1) | (0 << SPR0); use_spi2x = true; break;
206 case PX_SPI_BAUD_CLK_DIV_4: spcr |= (0 << SPR1) | (0 << SPR0); break;
207 case PX_SPI_BAUD_CLK_DIV_8: spcr |= (0 << SPR1) | (1 << SPR0); use_spi2x = true; break;
208 case PX_SPI_BAUD_CLK_DIV_16: spcr |= (0 << SPR1) | (1 << SPR0); break;
209 case PX_SPI_BAUD_CLK_DIV_32: spcr |= (1 << SPR1) | (0 << SPR0); use_spi2x = true; break;
210 case PX_SPI_BAUD_CLK_DIV_64: spcr |= (1 << SPR1) | (0 << SPR0); break;
211 case PX_SPI_BAUD_CLK_DIV_128: spcr |= (1 << SPR1) | (1 << SPR0); break;
212 default: break;
213 }
214 // Set SPI Mode (0,1,2 or 3)
215 spcr |= (((uint8_t)mode) & 3) << CPHA;
216 // Set data order
217 if(data_order == PX_SPI_DATA_ORDER_LSB)
218 {
219 PX_BIT_SET_HI(spcr, DORD);
220 }
221 // Save value for SPI Control Register
222 handle->spcr = spcr;
223 // Save value for "Use SPI 2x clock rate"
224 handle->use_spi2x = use_spi2x;
225 // Set value for "Master Out dummy byte"
226 handle->mo_dummy_byte = mo_dummy_byte;
227 // Initialise peripheral
228 px_spi_init_per(spi_nr, handle->spcr, handle->use_spi2x);
229 // Point handle to peripheral
230 handle->spi_per = spi_per;
231
232 // Success
233 PX_LOG_ASSERT(spi_per->open_counter < 255);
234 spi_per->open_counter++;
235 return true;
236}
237
239{
240 px_spi_per_t * spi_per;
241
242 // Verify that pointer to handle is not NULL
243 PX_LOG_ASSERT(handle != NULL);
244 // Set pointer to peripheral
245 spi_per = handle->spi_per;
246 // Check that handle is open
247 PX_LOG_ASSERT(spi_per != NULL);
248
249 // Decrement open count
250 PX_LOG_ASSERT(spi_per->open_counter > 0);
251 spi_per->open_counter--;
252
253 // More open handles referencing peripheral?
254 if(spi_per->open_counter != 0)
255 {
256 // Close handle
257 handle->spi_per = NULL;
258 // Success
259 return true;
260 }
261 // Disable peripheral
262 switch(spi_per->spi_nr)
263 {
264#if PX_SPI_CFG_SPI0_EN
265 case PX_SPI_NR_0:
266 SPCR = 0x00;
267 break;
268#endif
269 default:
270 break;
271 }
272 // Close handle
273 handle->spi_per = NULL;
274
275 // Success
276 return true;
277}
278
280 const void * data,
281 size_t nr_of_bytes,
282 uint8_t flags)
283{
284 px_spi_per_t * spi_per;
285 const uint8_t * data_u8 = (const uint8_t *)data;
286 uint8_t data_rx;
287
288 // Verify that pointer to handle is not NULL
289 PX_LOG_ASSERT(handle != NULL);
290 // Set pointer to peripheral
291 spi_per = handle->spi_per;
292 // Check that handle is open
293 PX_LOG_ASSERT(spi_per != NULL);
294 PX_LOG_ASSERT(spi_per->open_counter != 0);
295
296 // Update communication parameters (if different)
297 px_spi_update_cfg(spi_per->spi_nr, handle->spcr, handle->use_spi2x);
298
299 // Assert Chip Select?
300 if(flags & PX_SPI_FLAG_START)
301 {
302 // Take Chip Select Low
303 PX_SPI_CFG_CS_LO(handle->cs_id);
304 }
305
306 // Write data
307 while(nr_of_bytes)
308 {
309 // Buffer transmit data
310 SPDR = *data_u8++;
311 // Wait until byte has been transfered
312 PX_WAIT_UNTIL_BIT_IS_HI(SPSR, SPIF);
313 // Read received byte(and discard)
314 data_rx = SPDR;
315 // Avoid compiler warning that variable was set but not used
316 (void)data_rx;
317 // Next byte
318 nr_of_bytes--;
319 }
320
321 // De-assert Chip Select?
322 if(flags & PX_SPI_FLAG_STOP)
323 {
324 // Take Chip Select High
325 PX_SPI_CFG_CS_HI(handle->cs_id);
326 }
327}
328
330 void * data,
331 size_t nr_of_bytes,
332 uint8_t flags)
333{
334 px_spi_per_t * spi_per;
335 uint8_t * data_u8 = (uint8_t *)data;
336
337 // Verify that pointer to handle is not NULL
338 PX_LOG_ASSERT(handle != NULL);
339 // Set pointer to peripheral
340 spi_per = handle->spi_per;
341 // Check that handle is open
342 PX_LOG_ASSERT(spi_per != NULL);
343 PX_LOG_ASSERT(spi_per->open_counter != 0);
344
345 // Update communication parameters (if different)
346 px_spi_update_cfg(spi_per->spi_nr, handle->spcr, handle->use_spi2x);
347
348 // Assert Chip Select?
349 if(flags & PX_SPI_FLAG_START)
350 {
351 // Take Chip Select Low
352 PX_SPI_CFG_CS_LO(handle->cs_id);
353 }
354
355 // Repeat until all of the bytes have been read
356 while(nr_of_bytes)
357 {
358 // Buffer dummy byte to transmit
359 SPDR = handle->mo_dummy_byte;
360 // Wait until byte has been transfered
361 PX_WAIT_UNTIL_BIT_IS_HI(SPSR, SPIF);
362 // Read received byte
363 *data_u8++ = SPDR;
364 // Next byte
365 nr_of_bytes--;
366 }
367
368 // De-assert Chip Select?
369 if(flags & PX_SPI_FLAG_STOP)
370 {
371 // Take Chip Select High
372 PX_SPI_CFG_CS_HI(handle->cs_id);
373 }
374}
375
377 const void * data_wr,
378 void * data_rd,
379 size_t nr_of_bytes,
380 uint8_t flags)
381{
382 px_spi_per_t * spi_per;
383 const uint8_t * data_wr_u8 = (const uint8_t *)data_wr;
384 uint8_t * data_rd_u8 = (uint8_t *)data_rd;
385
386 // Verify that pointer to handle is not NULL
387 PX_LOG_ASSERT(handle != NULL);
388 // Set pointer to peripheral
389 spi_per = handle->spi_per;
390 // Check that handle is open
391 PX_LOG_ASSERT(spi_per != NULL);
392 PX_LOG_ASSERT(spi_per->open_counter != 0);
393
394 // Update communication parameters (if different)
395 px_spi_update_cfg(spi_per->spi_nr, handle->spcr, handle->use_spi2x);
396
397 // Assert Chip Select?
398 if(flags & PX_SPI_FLAG_START)
399 {
400 // Take Chip Select Low
401 PX_SPI_CFG_CS_LO(handle->cs_id);
402 }
403
404 // Repeat until all of the bytes have been exchanged
405 while(nr_of_bytes)
406 {
407 // Buffer byte to transmit
408 SPDR = *data_wr_u8++;
409 // Wait until byte has been transfered
410 PX_WAIT_UNTIL_BIT_IS_HI(SPSR, SPIF);
411 // Read received byte
412 *data_rd_u8++ = SPDR;
413 // Next byte
414 nr_of_bytes--;
415 }
416
417 // De-assert Chip Select?
418 if(flags & PX_SPI_FLAG_STOP)
419 {
420 // Take Chip Select High
421 PX_SPI_CFG_CS_HI(handle->cs_id);
422 }
423}
424
426 px_spi_baud_t baud)
427{
428 px_spi_per_t * spi_per;
429 uint8_t spcr;
430 bool use_spi2x;
431
432 // Verify that pointer to handle is not NULL
433 PX_LOG_ASSERT(handle != NULL);
434 // Set pointer to peripheral
435 spi_per = handle->spi_per;
436 // Check that handle is open
437 PX_LOG_ASSERT(spi_per != NULL);
438 PX_LOG_ASSERT(spi_per->open_counter != 0);
439
440 // Clear SPI Clock divisor bits
441 spcr = handle->spcr & (~((1 << SPR1) | (1 << SPR0)));
442 // Set new clock divisor
443 use_spi2x = false;
444 switch(baud)
445 {
446 case PX_SPI_BAUD_CLK_DIV_2: spcr |= (0 << SPR1) | (0 << SPR0); use_spi2x = true; break;
447 case PX_SPI_BAUD_CLK_DIV_4: spcr |= (0 << SPR1) | (0 << SPR0); break;
448 case PX_SPI_BAUD_CLK_DIV_8: spcr |= (0 << SPR1) | (1 << SPR0); use_spi2x = true; break;
449 case PX_SPI_BAUD_CLK_DIV_16: spcr |= (0 << SPR1) | (1 << SPR0); break;
450 case PX_SPI_BAUD_CLK_DIV_32: spcr |= (1 << SPR1) | (0 << SPR0); use_spi2x = true; break;
451 case PX_SPI_BAUD_CLK_DIV_64: spcr |= (1 << SPR1) | (0 << SPR0); break;
452 case PX_SPI_BAUD_CLK_DIV_128: spcr |= (1 << SPR1) | (1 << SPR0); break;
453 default: break;
454 }
455 // Update value for SPI Control Register
456 handle->spcr = spcr;
457 // Update value for "Use SPI 2x clock rate"
458 handle->use_spi2x = use_spi2x;
459 // Initialise peripheral
460 px_spi_update_cfg(spi_per->spi_nr, handle->spcr, handle->use_spi2x);
461}
462
464{
465 uint32_t actual_bit_rate_hz;
466 px_spi_baud_t baud;
467
468 /*
469 * Calculate clock divisor that generates a bit rate closest to the desired
470 * bit rate (equal or less)
471 */
472 actual_bit_rate_hz = F_CPU >> 1;
474
475 // Repeat until target is reached
476 while(actual_bit_rate_hz > baud_hz)
477 {
478 // Divide actual bit rate by two
479 actual_bit_rate_hz >>= 1;
480 // Stop if divisor reaches maximum
481 if(++baud == PX_SPI_BAUD_CLK_DIV_128)
482 {
483 break;
484 }
485 };
486
487 return baud;
488}
489
491{
492 return (((uint32_t)F_CPU) >> (baud + 1));
493}
#define PX_SPI_CFG_USE_SPI2X
Default value for "Use double clock rate" used in px_spi_open().
#define PX_SPI_CFG_SPCR
Default value for SPI Control Register used in px_spi_open().
@ PX_SPI_DATA_ORDER_LSB
Data order is Least Significant Bit first (D0, D1, ..., D7)
Definition: px_spi.h:97
@ PX_SPI_BAUD_CLK_DIV_128
F_PCLK / 128.
Definition: px_spi.h:109
@ PX_SPI_BAUD_CLK_DIV_8
F_PCLK / 8.
Definition: px_spi.h:105
@ PX_SPI_BAUD_CLK_DIV_4
F_PCLK / 4.
Definition: px_spi.h:104
@ PX_SPI_BAUD_CLK_DIV_64
F_PCLK / 64.
Definition: px_spi.h:108
@ PX_SPI_BAUD_CLK_DIV_16
F_PCLK / 16.
Definition: px_spi.h:106
@ PX_SPI_BAUD_CLK_DIV_2
F_PCLK / 2.
Definition: px_spi.h:103
@ PX_SPI_BAUD_CLK_DIV_32
F_PCLK / 32.
Definition: px_spi.h:107
#define F_CPU
Processor frequency in Hz.
Definition: px_board.h:52
#define NULL
NULL pointer.
Definition: px_defs.h:49
#define PX_WAIT_UNTIL_BIT_IS_HI(var, bit)
Wait until a bit is set.
Definition: px_defs.h:193
#define PX_BIT_SET_HI(var, bit)
Set a bit (1)
Definition: px_defs.h:178
#define PX_BIT_SET_LO(var, bit)
Clear a bit (0)
Definition: px_defs.h:181
#define PX_LOG_NAME(name)
Macro to declare a log name string once for each file to reduce code size.
Definition: px_log.h:349
#define PX_LOG_W(format,...)
Macro to display a formatted WARNING message.
Definition: px_log.h:388
#define PX_LOG_E(format,...)
Macro to display a formatted ERROR message.
Definition: px_log.h:377
#define PX_LOG_ASSERT(expression)
Macro that will test an expression, and block indefinitely if false.
Definition: px_log.h:440
bool use_spi2x
Use 2X clock rate.
Definition: px_spi.h:138
uint8_t cs_id
Chip Select GPIO ID.
Definition: px_spi.h:128
uint8_t mo_dummy_byte
Master Out dummy byte when data is read from Master In.
Definition: px_spi.h:130
uint8_t spcr
SPI Control Register value.
Definition: px_spi.h:137
struct px_spi_per_s * spi_per
SPI peripheral.
Definition: px_spi.h:127
bool px_spi_close(px_spi_handle_t *handle)
Close specified device.
Definition: px_spi.c:338
#define PX_SPI_CFG_CS_HI(cs_id)
Map PX_SPI_CFG_CS_HI() macro to px_board_spi_cs_hi() function.
px_spi_mode_t
Specify SPI Clock polarity / Clock phase.
Definition: px_spi.h:86
void px_spi_init(void)
Initialise SPI driver.
Definition: px_spi.c:162
bool px_spi_open2(px_spi_handle_t *handle, px_spi_nr_t spi_nr, uint8_t cs_id, px_spi_baud_t baud, px_spi_mode_t mode, px_spi_dord_t data_order, uint8_t mo_dummy_byte)
Open SPI peripheral using specified parameters.
Definition: px_spi.c:252
#define PX_SPI_CFG_CS_LO(cs_id)
Map PX_SPI_CFG_CS_LO() macro to px_board_spi_cs_lo() function.
px_spi_dord_t
Specify SPI Data order.
Definition: px_spi.h:95
#define PX_SPI_FLAG_START
Begin SPI transaction (take SPI slave's Chip Select line low)
Definition: px_spi.h:117
void px_spi_xc(px_spi_handle_t *handle, const void *data_wr, void *data_rd, size_t nr_of_bytes, uint8_t flags)
Perform an SPI exchange (write and read) transaction with an SPI slave.
Definition: px_spi.c:568
void px_spi_wr(px_spi_handle_t *handle, const void *data, size_t nr_of_bytes, uint8_t flags)
Perform an SPI write transaction with an SPI slave.
Definition: px_spi.c:396
void px_spi_rd(px_spi_handle_t *handle, void *data, size_t nr_of_bytes, uint8_t flags)
Perform an SPI read transaction with an SPI slave.
Definition: px_spi.c:479
px_spi_nr_t
Specify SPI peripheral number.
Definition: px_spi.h:79
void px_spi_change_baud(px_spi_handle_t *handle, px_spi_baud_t baud)
Change SPI peripheral baud.
Definition: px_spi.c:657
uint32_t px_spi_util_clk_div_to_baud_hz(px_spi_baud_t baud)
Calculate the actual BAUD (in Hz) from the specified division.
Definition: px_spi.c:720
bool px_spi_open(px_spi_handle_t *handle, px_spi_nr_t spi_nr, uint8_t cs_id)
Open SPI peripheral using predefined (default) parameters.
Definition: px_spi.c:173
#define PX_SPI_FLAG_STOP
Finish SPI transaction (take SPI slave's Chip Select line high)
Definition: px_spi.h:119
px_spi_baud_t
Specify SPI baud rate as a ratio of the peripheral clock.
Definition: px_spi.h:102
px_spi_baud_t px_spi_util_baud_hz_to_clk_div(uint32_t baud_hz)
Calculate clock divisor that will yield closest frequency equal to or less than specified baud rate (...
Definition: px_spi.c:698
Define SPI handle.
Definition: px_spi.h:126
Internal data for each SPI peripheral.
Definition: px_spi.c:32
uint8_t open_counter
Number of open handles referencing peripheral.
Definition: px_spi.c:37
px_spi_nr_t spi_nr
Peripheral number.
Definition: px_spi.c:36