Skip to content

Commit 5e3c6d1

Browse files
authored
Merge pull request cnlohr#827 from UserR00T/feature/demo-spi_slave
Added SPI Slave/Peripheral example
2 parents 16c5584 + 2a55f09 commit 5e3c6d1

File tree

4 files changed

+271
-0
lines changed

4 files changed

+271
-0
lines changed

examples/spi_slave/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
all : flash
2+
3+
TARGET:=spi_slave
4+
TARGET_MCU:=CH32V003
5+
include ../../ch32fun/ch32fun.mk
6+
7+
flash : cv_flash
8+
clean : cv_clean

examples/spi_slave/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# SPI Slave/Peripheral Example
2+
3+
This example demonstrates how to configure a microcontroller as an SPI slave (peripheral) device using the SPI protocol. The SPI slave will receive data from an SPI master and can also send data back to the master, using MOSI and MISO lines
4+
respectively. As long as the CH32 microcontroller supports SPI, this example should work with only minimal modifications.
5+
6+
This example does not use DMA for data transfer, and is more so intended to demonstrate the basic SPI peripheral functionality using interrupts and a simple polling loop.
7+
As with most demos, there is still room for optimization and improvements; such as the previously mentioned DMA, handling errors, and more loosely defined buffers instead of fixed-size ones depending on your application needs. This is a
8+
good
9+
base to learn from and build upon.
10+
11+
## Hardware Setup
12+
13+
To test this example, you will need two microcontroller boards: one configured as an SPI master and the other as an SPI slave. The master SPI device can be whatever you like. The wiring follows the default SPI pinout of the specific CH32
14+
board.
15+
For example on a CH32V003F4P6, it'll be:
16+
17+
- MOSI -> MOSI `PC6`
18+
- MISO -> MISO `PC7`
19+
- SCK -> SCK `PC5`
20+
- NSS -> NSS (aka CS) `PC1`
21+
22+
Refer to the datasheet or pinout diagram for the exact SPI pin locations.
23+
24+
## How it works
25+
26+
- First, we initialize the SPI in slave mode with all the desired settings. This demo just picked some sensible defaults e.g. baud rate prescaler. It's important to note that it sets the mode to `SPI_Mode_Slave` instead of
27+
`SPI_Mode_Master`.
28+
- Once initialization is done, we enter the main loop. In there we simply wait for CS to be low. You could do something in the meantime whilst CS is high, but this demo just waits. To keep things simple this doesn't use an interrupt for CS
29+
and thus be careful to not over utilize the CPU in the main loop.
30+
- When CS goes low, this is the signal the SPI master/controller is starting a transaction to this device. We enable the `RXNE` (RX buffer Not Empty) interrupt so we can receive data. We change the state to receiving, and simply wait for
31+
the state to be changed to received from within the interrupt.
32+
- Within the interrupt, we read byte by byte from the SPI data register as long as there is data to read (RXNE flag is set). Once the MOSI buffer is full, we change the state to received, disable the RXNE interrupt, prepare the MISO buffer,
33+
preload the first byte, and finally re-enable to RXNE interrupt.
34+
- Within state sending, we send each byte from the MOSI buffer back to the master based on the clock cycles provided by the master. Once all bytes are sent, we wait for CS to be back high, indicating the end of the transaction. At this
35+
point the full transfer is complete: data was received from MOSI, and sent through MISO back to the master.
36+
- At the end we also log it over serial for debugging purposes as `RX/MOSI(4): 00 00 00 00 | TX/MISO(4): 00 00 00 00` per transaction. Do note this adds about 100us of delay at least each cycle at the end, so if the clock rate is high
37+
enough you logically need to remove the serial logging to keep up. (at which
38+
point you should probably move to DMA and more interrupts where needed.)
39+
40+
## Example Master SPI code
41+
42+
For simplicity sake here is an example SPI master code snippet in CircuitPython you can flash to another microcontroller. You can of course also use ch32fun for the master side as well, that's all your call.
43+
The code below sends 4 bytes to the slave and reads back 4 bytes from the slave. both buffers are printed to the console. CS is set to pin D6.
44+
The four bytes it writes are incrementing numbers from 0 to 255, letters from A to Z, and 41 & 42.
45+
46+
```python
47+
import digitalio
48+
import time
49+
from board import *
50+
51+
spi_bus = SPI()
52+
try:
53+
cs = digitalio.DigitalInOut(D6)
54+
bytes_read = bytearray(4)
55+
bytes_wrote = bytearray(4)
56+
bytes_wrote[2] = 41
57+
bytes_wrote[3] = 42
58+
59+
while True:
60+
bytes_wrote[0] = (bytes_wrote[0] + 1) % 256 # 0 to 255
61+
bytes_wrote[1] = ord('A') + (bytes_wrote[1] - ord('A') + 1) % 26 # A to Z
62+
63+
spi_bus.try_lock()
64+
spi_bus.configure(baudrate=100000, polarity=0, phase=0)
65+
cs.value = False
66+
spi_bus.write(bytes_wrote)
67+
spi_bus.readinto(bytes_read)
68+
cs.value = True
69+
spi_bus.unlock()
70+
71+
print("MOSI(4): " + " ".join(hex(b) for b in bytes_wrote) + " | " +
72+
"MISO(4): " + " ".join(hex(b) for b in bytes_read))
73+
74+
time.sleep(.1)
75+
finally:
76+
spi_bus.deinit()
77+
```

examples/spi_slave/funconfig.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#ifndef _FUNCONFIG_H
2+
#define _FUNCONFIG_H
3+
4+
#define FUNCONF_USE_DEBUGPRINTF 1
5+
#define CH32V003 1
6+
7+
#endif

examples/spi_slave/spi_slave.c

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#include "ch32fun.h"
2+
#include <stdio.h>
3+
4+
#define PIN_CS PC1
5+
// Useful to determine the cycle duration with a scope. Goes high the moment CS goes low, and back to low at the last
6+
// action within the while loop. Can be removed otherwise.
7+
#define PIN_OUT PC3
8+
9+
volatile uint8_t buffer_miso[4];
10+
volatile uint8_t spi_miso_index = 0;
11+
volatile uint8_t buffer_mosi[4];
12+
volatile uint8_t spi_mosi_index = 0;
13+
14+
typedef enum
15+
{
16+
STATE_IDLE = 0,
17+
STATE_RECEIVING = 1,
18+
STATE_RECEIVED = 2,
19+
STATE_SENDING = 3,
20+
STATE_SENT = 4,
21+
} SpiState;
22+
23+
volatile SpiState spi_state = STATE_IDLE;
24+
25+
void wait_for_state( SpiState desired_state )
26+
{
27+
while ( spi_state != desired_state )
28+
{
29+
}
30+
}
31+
32+
static uint8_t SPI_read_8()
33+
{
34+
return SPI1->DATAR;
35+
}
36+
37+
static void SPI_write_8( uint8_t data )
38+
{
39+
SPI1->DATAR = data;
40+
}
41+
42+
void SPI_Configure()
43+
{
44+
// reset control register
45+
SPI1->CTLR1 = 0;
46+
47+
// Enable GPIO Port C and SPI peripheral
48+
RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1;
49+
50+
// PC5 is SCLK
51+
funPinMode( PC5, GPIO_CFGLR_IN_FLOAT );
52+
53+
// PC6 is MOSI
54+
funPinMode( PC6, GPIO_CFGLR_IN_FLOAT );
55+
56+
// PC7 is MISO
57+
funPinMode( PC7, GPIO_Speed_2MHz | GPIO_CNF_OUT_PP_AF );
58+
59+
// Configure SPI
60+
SPI1->CTLR1 |= SPI_CPHA_1Edge | SPI_CPOL_Low | SPI_Mode_Slave | SPI_BaudRatePrescaler_2 | SPI_DataSize_8b;
61+
SPI1->CTLR1 |= SPI_Direction_2Lines_FullDuplex;
62+
SPI1->CTLR1 |= CTLR1_SPE_Set;
63+
}
64+
65+
void SPI1_IRQHandler( void ) __attribute__( ( interrupt ) );
66+
void SPI1_IRQHandler( void )
67+
{
68+
if ( !( SPI1->STATR & SPI_STATR_RXNE ) )
69+
{
70+
return;
71+
}
72+
73+
// Read received data first to clear RXNE. This is important: not reading the data register can leave RXNE set and
74+
// prevent further proper IRQ handling.
75+
uint8_t received = SPI_read_8();
76+
if ( spi_state == STATE_SENDING )
77+
{
78+
// When sending, the master clocks data in, and we should provide the next
79+
// MISO byte for the next clock. Write after reading the incoming byte.
80+
if ( spi_miso_index < sizeof( buffer_miso ) )
81+
{
82+
SPI_write_8( buffer_miso[spi_miso_index++] );
83+
if ( spi_miso_index >= sizeof( buffer_miso ) )
84+
{
85+
spi_state = STATE_SENT;
86+
}
87+
}
88+
else
89+
{
90+
spi_state = STATE_SENT;
91+
}
92+
}
93+
else if ( spi_state == STATE_RECEIVING )
94+
{
95+
// Store byte when in receiving state
96+
buffer_mosi[spi_mosi_index++] = received;
97+
if ( spi_mosi_index > sizeof( buffer_mosi ) )
98+
{
99+
spi_mosi_index = 0;
100+
}
101+
if ( spi_mosi_index == 4 )
102+
{
103+
spi_state = STATE_RECEIVED;
104+
}
105+
}
106+
}
107+
108+
void send()
109+
{
110+
spi_miso_index = 0;
111+
// Transition to sending: briefly disable RXNE interrupt to avoid the ISR
112+
// writing 0x00 while we flip state and preload the response bytes.
113+
SPI1->CTLR2 &= ~SPI_CTLR2_RXNEIE;
114+
spi_state = STATE_SENDING;
115+
// Preload the first MISO byte so the master reads the intended first byte.
116+
if ( spi_miso_index < sizeof( buffer_miso ) )
117+
{
118+
SPI_write_8( buffer_miso[spi_miso_index++] );
119+
}
120+
121+
SPI1->CTLR2 |= SPI_CTLR2_RXNEIE;
122+
wait_for_state( STATE_SENT );
123+
}
124+
125+
126+
int main()
127+
{
128+
SystemInit();
129+
printf( "--- init ---\n" );
130+
131+
funGpioInitAll();
132+
133+
funPinMode( PIN_CS, GPIO_CFGLR_IN_PUPD );
134+
funPinMode( PIN_OUT, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP );
135+
136+
SPI_Configure();
137+
NVIC_EnableIRQ( SPI1_IRQn );
138+
while ( 1 )
139+
{
140+
if ( funDigitalRead( PIN_CS ) )
141+
{
142+
continue;
143+
}
144+
145+
funDigitalWrite( PIN_OUT, FUN_HIGH );
146+
SPI_write_8( 0x00 );
147+
// Enable the RXNE interrupt only when CS is low, and we are ready to receive; to avoid unnecessary IRQs.
148+
// Not exactly necessary, but if the CPU needs to do other things while CS is high, this ensures it will not be,
149+
// well, interrupted.
150+
SPI1->CTLR2 |= SPI_CTLR2_RXNEIE;
151+
spi_mosi_index = 0;
152+
spi_state = STATE_RECEIVING;
153+
wait_for_state( STATE_RECEIVED );
154+
buffer_miso[0] = buffer_mosi[0];
155+
buffer_miso[1] = buffer_mosi[1];
156+
buffer_miso[2] = 0xBE;
157+
buffer_miso[3] = 0xEF;
158+
send();
159+
while ( !funDigitalRead( PIN_CS ) )
160+
{
161+
}
162+
163+
spi_state = STATE_IDLE;
164+
printf( "RX/MOSI(%d):", 4 );
165+
for ( uint8_t i = 0; i < 4; i++ )
166+
{
167+
printf( " %02X", buffer_mosi[i] );
168+
}
169+
170+
printf( " | TX/MISO(%d):", 4 );
171+
for ( uint8_t i = 0; i < 4; i++ )
172+
{
173+
printf( " %02X", buffer_miso[i] );
174+
}
175+
printf( "\n" );
176+
SPI1->CTLR2 &= ~SPI_CTLR2_RXNEIE;
177+
funDigitalWrite( PIN_OUT, FUN_LOW );
178+
}
179+
}

0 commit comments

Comments
 (0)