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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
|
.. highlight:: c
.. _libmaple-overview:
Overview
========
This page is a general overview of :ref:`libmaple proper
<libmaple-vs-wirish>`. It describes libmaple's design, and names
implementation patterns to look for when using it. Examples are given
from the sources.
.. contents:: Contents
:local:
Design Goals
------------
The central goal for libmaple proper is to provide a pleasant,
portable, and consistent set of interfaces for dealing with the
various series of STM32 microcontrollers. We want to make it easy to
write portable STM32 code. To enable that, we've abstracted away many
hardware details behind portable interfaces. We also want to make it
easy for you to get your hands dirty when need or desire arises. To
that end, libmaple makes as few assumptions as possible, and does its
best to get out of your way when you want it to leave.
Let's start with the basics. If you're interested in low-level details
on the STM32, then you're going to spend a lot of quality time wading
through `ST RM0008
<http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/CD00171190.pdf>`_.
That document is the single most important tool in your toolbox. It
is the authoritative documentation for the capabilities and register
interfaces of the STM32 line.
Perhaps you haven't read it in detail, but maybe you've at least
thumbed through a few of the sections, trying to gain some
understanding of what's going on. If you've done that (and if you
haven't, just take our word for it), then you know that underneath the
covers, *everything* is controlled by messing with bits in the
seemingly endless collections of registers specific to every
peripheral. The :ref:`USARTs <usart>` have data registers; (some of
the) the :ref:`timers <timers>` have capture/compare registers, the
:ref:`GPIOs <gpio>` have output data registers, etc.
For the most part, Wirish does everything it can to hide this truth
from you. That's because when you really just want to get your robot
to fly, your LEDs to blink, or your `FM synthesizer
<https://github.com/Ixox/preen>`_ to, well, `synthesize
<http://xhosxe.free.fr/IxoxFMSynth.mp3>`_, you probably couldn't care
less about messing with registers.
That's fine! In fact, it's our explicit goal for Wirish to be good
enough that most people never need to know libmaple proper even
exists. We want to make programming our boards as easy as possible,
after all. But the day may come when you want to add a library for an
as-yet unsupported peripheral, or you want to do something we didn't
anticipate, or you'd like to squeeze a little more speed out of a
critical section in your program. Or maybe you're just curious!
If anything in the above paragraph describes you, then you'll find
that you need a way to translate your knowledge of RM0008 into
software. We imagine (if you're anything like us) you want to spend
the least amount of time you possibly can doing that
translation. Ideally, once you've finished your design, you want some
way to start reading and writing code right away, without having to
bushwhack your way through a thicket of clunky APIs.
The central abstractions we've chosen to accomplish the above goals
are *register maps* and *devices*. Register maps are just structs
which encapsulate the layout of the IO-mapped memory regions
corresponding to a peripheral's registers. Devices encapsulate a
peripheral's register map as well as any other necessary information
needed to operate on it. Peripheral support routines generally
operate on devices rather than register maps.
Devices
-------
At the highest level, you'll be dealing with *devices*, where a
"device" is a general term for any particular piece of hardware you
might encounter. So, for example, an analog to digital converter is a
device. So is a USART. So is a GPIO port. In this section, we'll
consider some hypothetical "xxx" device.
The first thing you need to know is that the header file for dealing
with xxx devices is, naturally enough, called ``xxx.h``. So if you
want to interface with the :ref:`ADCs <adc>`, just ``#include
"adc.h"``.
Inside of ``xxx.h``, there will be a declaration for a ``struct
xxx_dev`` type. This type encapsulates all of the information we keep
track of for that xxx. So, for example, in ``adc.h``, there's a
``struct adc_dev``::
/** ADC device type. */
typedef struct adc_dev {
adc_reg_map *regs; /**< Register map */
rcc_clk_id clk_id; /**< RCC clock information */
} adc_dev;
The ADCs aren't particularly complicated. All we keep track of for an
ADC device is a pointer to its register map (which keeps track of all
of its registers' bits; see :ref:`below <libmaple-overview-regmaps>`
for more details), and an identifying piece of information which tells
the RCC (reset and clock control) interface how to turn the ADC on and
reset its registers to their default values.
The timers on the STM32 line are more involved than the ADCs, so a
``timer_dev`` has to keep track of a bit more information::
/** Timer device type */
typedef struct timer_dev {
timer_reg_map regs; /**< Register map */
rcc_clk_id clk_id; /**< RCC clock information */
timer_type type; /**< Timer's type */
voidFuncPtr handlers[]; /**< User IRQ handlers */
} timer_dev;
However, as you can see, both ADC and timer devices are named
according to a single scheme, and store similar information.
``xxx.h`` will also declare pointers to the actual devices you need to
deal with, called ``XXX1``, ``XXX2``, etc. (or just ``XXX``, if
there's only one) [#fgpio]_. For instance, on the Maple's
microcontroller (the STM32F103RBT6), there are two ADCs.
Consequently, in ``adc.h``, there are declarations for dealing with
ADC devices one and two::
extern const adc_dev *ADC1;
extern const adc_dev *ADC2;
In general, each device needs to be initialized before it can be used.
libmaple provides this initialization routine for each peripheral
``xxx``; its name is ``xxx_init()``. These initialization routines
turn on the clock to a device, and restore its register values to
their default settings. Here are a few examples::
/* From dma.h */
void dma_init(dma_dev *dev);
/* From gpio.h */
void gpio_init(gpio_dev *dev);
void gpio_init_all(void);
Note that, sometimes, there will be an additional initialization
routine for all available peripherals of a certain kind.
Many peripherals also need additional configuration before they can be
used. These functions are usually called something along the lines of
``xxx_enable()``, and often take additional arguments which specify a
particular configuration for the peripheral. Some examples::
/* From usart.h */
void usart_enable(usart_dev *dev);
/* From i2c.h */
void i2c_master_enable(i2c_dev *dev, uint32 flags);
After you've initialized, and potentially enabled, your peripheral, it
is now time to begin using it. The file ``xxx.h`` contains other
convenience functions for dealing with xxx devices. For instance,
here are a few from ``adc.h``::
void adc_set_sample_rate(const adc_dev *dev, adc_smp_rate smp_rate);
uint32 adc_read(const adc_dev *dev, uint8 channel);
We aim to enable libmaple's users to interact with peripherals through
devices as much as possible, rather than having to break the
abstraction and consider individual registers. However, there will
always be a need for low-level access. To allow for that, libmaple
provides *register maps* as a consistent set of names and abstractions
for dealing with registers and their bits.
.. _libmaple-overview-regmaps:
Register Maps
-------------
A *register map* is just a C struct which names and provides access to
a peripheral's registers. These registers are usually mapped to
contiguous regions of memory (though at times unusable or reserved
regions exist between a peripheral's registers). Here's an example
register map, from ``dac.h`` (``__io`` is just libmaple's way of
saying ``volatile`` when referring to register values)::
/** DAC register map. */
typedef struct dac_reg_map {
__io uint32 CR; /**< Control register */
__io uint32 SWTRIGR; /**< Software trigger register */
__io uint32 DHR12R1; /**< Channel 1 12-bit right-aligned data
holding register */
__io uint32 DHR12L1; /**< Channel 1 12-bit left-aligned data
holding register */
__io uint32 DHR8R1; /**< Channel 1 8-bit left-aligned data
holding register */
__io uint32 DHR12R2; /**< Channel 2 12-bit right-aligned data
holding register */
__io uint32 DHR12L2; /**< Channel 2 12-bit left-aligned data
holding register */
__io uint32 DHR8R2; /**< Channel 2 8-bit left-aligned data
holding register */
__io uint32 DHR12RD; /**< Dual DAC 12-bit right-aligned data
holding register */
__io uint32 DHR12LD; /**< Dual DAC 12-bit left-aligned data
holding register */
__io uint32 DHR8RD; /**< Dual DAC 8-bit right-aligned data holding
register */
__io uint32 DOR1; /**< Channel 1 data output register */
__io uint32 DOR2; /**< Channel 2 data output register */
} dac_reg_map;
There are two things to notice here. First, if RM0008 names a
register ``DAC_FOO``, then ``dac_reg_map`` has a field named ``FOO``.
So, the Channel 1 12-bit right-aligned data register (RM0008:
DAC_DHR12R1) is the ``DHR12R1`` field in a ``dac_reg_map``. Second,
if RM0008 describes a register as "Foo bar register", the
documentation for the corresponding field has the same description.
This consistency makes it easy to search for a particular register,
and, if you see one used in a source file, to feel sure about what's
going on just based on its name.
So let's say you've included ``xxx.h``, and you want to mess with some
particular register. What's the name of the ``xxx_reg_map`` variable
you want? That depends on if there's more than one xxx or not. If
there's only one xxx, then libmaple guarantees there will be a
``#define`` that looks like like this::
#define XXX_BASE ((struct xxx_reg_map*)0xDEADBEEF)
That is, you're guaranteed there will be a pointer to the (only)
``xxx_reg_map`` you want, and it will be called
``XXX_BASE``. (``0xDEADBEEF`` is the register map's *base address*, or
the fixed location in memory where the register map begins). Here's a
concrete example from ``dac.h``::
#define DAC_BASE ((struct dac_reg_map*)0x40007400)
How can you use these? This is perhaps best explained by example.
* In order to write 2048 to the channel 1 12-bit left-aligned data
holding register (RM0008: DAC_DHR12L1), you could write::
DAC_BASE->DHR12L1 = 2048;
* In order to read the DAC control register, you could write::
uint32 cr = DAC_BASE->CR;
The microcontroller takes care of converting reads and writes from a
register's IO-mapped memory regions into reads and writes to the
corresponding hardware registers.
That covers the case where there's a single xxx peripheral. If
there's more than one (say, if there are *n*), then ``xxx.h`` provides
the following::
#define XXX1_BASE ((struct xxx_reg_map*)0xDEADBEEF)
#define XXX2_BASE ((struct xxx_reg_map*)0xF00DF00D)
...
#define XXXn_BASE ((struct xxx_reg_map*)0x13AF1AB5)
Here are some examples from ``adc.h``::
#define ADC1_BASE ((struct adc_reg_map*)0x40012400)
#define ADC2_BASE ((struct adc_reg_map*)0x40012800)
In order to read from the ADC1's regular data register (where the
results of ADC conversion are stored), you might write::
uint32 converted_result = ADC1_BASE->DR;
Register Bit Definitions
------------------------
In ``xxx.h``, there will also be a variety of #defines for dealing
with interesting bits in the xxx registers, called *register bit
definitions*. These are named according to the scheme
``XXX_REG_FIELD``, where "``REG``" refers to the register, and
"``FIELD``" refers to the bit or bits in ``REG`` that are special.
.. TODO image of the bit layout of a DMA_CCR register
Again, this is probably best explained by example. Each Direct Memory
Access (DMA) controller's register map has a certain number of channel
configuration registers (RM0008: DMA_CCRx). In each of these channel
configuration registers, bit 14 is called the ``MEM2MEM`` bit, and
bits 13 and 12 are the priority level (``PL``) bits. Here are the
register bit definitions for those fields::
/* From dma.h */
#define DMA_CCR_MEM2MEM_BIT 14
#define DMA_CCR_MEM2MEM BIT(DMA_CCR_MEM2MEM_BIT)
#define DMA_CCR_PL (0x3 << 12)
#define DMA_CCR_PL_LOW (0x0 << 12)
#define DMA_CCR_PL_MEDIUM (0x1 << 12)
#define DMA_CCR_PL_HIGH (0x2 << 12)
#define DMA_CCR_PL_VERY_HIGH (0x3 << 12)
Thus, to check if the ``MEM2MEM`` bit is set in DMA controller 1's
channel configuration register 2 (RM0008: DMA_CCR2), you can write::
if (DMA1_BASE->CCR2 & DMA_CCR_MEM2MEM) {
/* MEM2MEM is set */
}
Certain register values occupy multiple bits. For example, the
priority level (PL) of a DMA channel is determined by bits 13 and 12
of the corresponding channel configuration register. As shown above,
libmaple provides several register bit definitions for masking out the
individual PL bits and determining their meaning. For example, to
check the priority level of a DMA transfer, you can write::
switch (DMA1_BASE->CCR2 & DMA_CCR_PL) {
case DMA_CCR_PL_LOW:
/* handle low priority case */
case DMA_CCR_PL_MEDIUM:
/* handle medium priority case */
case DMA_CCR_PL_HIGH:
/* handle high priority case */
case DMA_CCR_PL_VERY_HIGH:
/* handle very high priority case */
}
Of course, before doing that, you should check to make sure there's
not already a device-level function for performing the same task!
What Next?
----------
After you've read this page, you can proceed to the :ref:`libmaple API
listing <libmaple-apis>`. From there, you can read documentation and
follow links to the current source code for those files on `libmaple's
GitHub page <https://github.com/leaflabs/libmaple>`_.
.. rubric:: Footnotes
.. [#fgpio] For consistency with RM0008, GPIO ports are given letters
instead of numbers (``GPIOA`` and ``GPIOB`` instead of
``GPIO1`` and ``GPIO2``, etc.).
|