Skip to content

Commit 85d2322

Browse files
committed
grainuum: initial commit
This is the initial commit of the Grainuum USB stack. Signed-off-by: Sean Cross <sean@xobs.io>
0 parents  commit 85d2322

File tree

6 files changed

+2113
-0
lines changed

6 files changed

+2113
-0
lines changed

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Copyright (c) Sean Cross
2+
3+
Permission is hereby granted, free of charge, to any person
4+
obtaining a copy of this software and associated documentation
5+
files (the "Software"), to deal in the Software without
6+
restriction, including without limitation the rights to use,
7+
copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the
9+
Software is furnished to do so, subject to the following
10+
conditions:
11+
12+
The above copyright notice and this permission notice shall be
13+
included in all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
Grainuum USB
2+
============
3+
4+
A software implementation of a USB stack for small CPUs.
5+
6+
Grainuum is designed to run on Cortex-M0+ CPUs running at 48 MHz with
7+
single-cycle IO. In practice, this means it runs well on a wide variety
8+
of Kinetis chips.
9+
10+
11+
Usage
12+
=======
13+
14+
To start with, create a GrainuumUSB object that defines your device's pin layout.
15+
Specify the offsets for setting and clearing pins, sampling pins, changing the
16+
pin direction, and the offsets of the two pins in the various banks.
17+
18+
The structure is defined as such:
19+
20+
static struct USBPHY {
21+
// USB D- line descriptor
22+
uint32_t dpIAddr; // GPIO "sample-whole-bank" address
23+
uint32_t dpSAddr; // GPIO "set-pin-level" address
24+
uint32_t dpCAddr; // GPIO "clear-pin-level" address
25+
uint32_t dpDAddr; // GPIO "pin-direction" address, where 1 = output
26+
uint32_t dpShift; // Shift of GPIO pin in S/C/D/I addresses
27+
28+
// USB D+ line descriptor, as above
29+
uint32_t dnIAddr;
30+
uint32_t dnSAddr;
31+
uint32_t dnCAddr;
32+
uint32_t dnDAddr;
33+
uint32_t dnShift;
34+
35+
// USB masks
36+
uint32_t dpMask; // Mask of GPIO pin in S/C/D/I addresses
37+
uint32_t dnMask;
38+
...
39+
};
40+
41+
42+
For example, if D+ was on pin PTA4 and D- was on PTB0, you might specify the
43+
following layout:
44+
45+
static struct GrainuumUSB myUSB = {
46+
/* PTB0 */
47+
.usbdnIAddr = 0xf8000050, /* FGPIOB_PDIR */
48+
.usbdnSAddr = 0xf8000044, /* FGPIOB_PSOR */
49+
.usbdnCAddr = 0xf8000048, /* FGPIOB_PCOR */
50+
.usbdnDAddr = 0xf8000054, /* FGPIOB_PDDR */
51+
.usbdnMask = (1 << 0),
52+
.usbdnShift = 0,
53+
54+
/* PTA4 */
55+
.usbdpIAddr = 0xf8000010, /* FGPIOA_PDIR */
56+
.usbdpSAddr = 0xf8000004, /* FGPIOA_PSOR */
57+
.usbdpCAddr = 0xf8000008, /* FGPIOA_PCOR */
58+
.usbdpDAddr = 0xf8000014, /* FGPIOA_PDDR */
59+
.usbdpMask = (1 << 4),
60+
.usbdpShift = 4,
61+
};
62+
63+
You should also set up a GrainuumConfig device to handle USB communication:
64+
65+
struct GrainuumConfig {
66+
/* Called by GrainuumUSB to send descriptors to the host */
67+
get_usb_descriptor_t getDescriptor;
68+
69+
/* Called by GrainuumUSB when the host sets the configuration number */
70+
usb_set_config_num_t setConfigNum;
71+
72+
/* Called by GrainuumUSB to get space to store incoming data */
73+
usb_get_buffer_t getReceiveBuffer;
74+
75+
/* Called by GrainuumUSB when data has been received from the host */
76+
usb_data_in_t receiveData;
77+
78+
/* Called by GrainuumUSB after sendData() has queued data, but before it is sent */
79+
usb_data_out_start_t sendDataStarted;
80+
81+
/* Called by GrainuumUSB after sendData() has sent the data to the host */
82+
usb_data_out_finish_t sendDataFinished;
83+
} __attribute__((packed, aligned(4)));
84+
85+
The most important function to fill in is getDescriptor(), which will
86+
allow the USB system to respond to requests from the host. Most other
87+
entries are optional.
88+
89+
Register these two objects with GrainuumUSB:
90+
91+
void grainuumInit(struct GrainuumUSB *usb, struct GrainuumConfig *cfg);
92+
93+
This will initialize the PHY and put it in "Disconnected" mode. To connect, call grainuumConnect();
94+
95+
void grainuumConnect(struct GrainuumUSB *usb);
96+
97+
Now you can hook your interrupt handler. When an ISR hits, call grainuumCaptureI()
98+
with a buffer big enough to hold one USB packet:
99+
100+
void grainuumCaptureI(struct GrainuumUSB *usb, uint8_t packet[12]);
101+
102+
Then, sometime later once the interrupt is finished, pass the same buffer to grainuumProcess():
103+
104+
void grainuumProcess(struct GrainuumUSB *usb,
105+
const uint8_t packet[12]);
106+
107+
*The packet that is passed to grainuumProcess() and grainuumCaptureI() MUST be aligned
108+
such that packet[1] is word-aligned. One way to do this might be to define packet[16]
109+
as being aligned, and pass &packet[3] to these functions. Or you can use Granuum Buffers,
110+
which are described below.*
111+
112+
To send data to the host, use grainuumSendData():
113+
114+
int grainuumSendData(struct GrainuumUSB *usb, int epnum, const void *data, int size);
115+
116+
117+
Grainuum Buffers
118+
----------------
119+
120+
The USB PHY uses a ring buffer to log all incoming data as it enters
121+
the device. This data has special alignment requirements. You can use
122+
Grainuum Buffers to manage this data.
123+
124+
Grainuum Buffers are a set of macros that wrap all of the alignment
125+
magic.
126+
127+
Declare a Grainuum Buffer using the GRAINUUM_BUFFER macro, specifying the
128+
number of complete packets to buffer. To declare a buffer named
129+
*usb_buffer* with four elements, write:
130+
131+
GRAINUUM_BUFFER(usb_buffer, 4);
132+
133+
In your program code, you must initialize the buffer before you use it:
134+
135+
GRAINUUM_BUFFER_INIT(usb_buffer);
136+
137+
To check if the buffer is empty, use is_empty:
138+
139+
if (!GRAINUUM_BUFFER_IS_EMPTY(usb_buffer)) {
140+
... work on the buffer ...
141+
}
142+
143+
You'll generally want to get a pointer to the top of the buffer,
144+
and advance it only if the data is filled. To get a pointer
145+
to the top of the buffer (and pass it to grainuumCaptureI()), type:
146+
147+
grainuumCaptureI(usb, GRAINUUM_BUFFER_ENTRY(usb_buffer));
148+
149+
If the buffer is filled, advance the buffer with advance():
150+
151+
GRAINUUM_BUFFER_ADVANCE(usb_buffer);
152+
153+
To get the oldest item in the queue, use top():
154+
155+
uint8_t *usb_pkt = GRAINUUM_BUFFER_TOP(usb_buffer);
156+
157+
When you're done with the packet and want to advance tne end
158+
of the buffer (i.e. remove the oldest item), use remove():
159+
160+
GRAINUUM_BUFFER_REMOVE(usb_buffer);
161+
162+
Callbacks and Hooks
163+
-------------------
164+
165+
Most of the normal configuration is done through the GrainuumConfig structure.

0 commit comments

Comments
 (0)