Skip to content

Commit bdea294

Browse files
committed
Tutorials: Add BLE Servo article
1 parent 6d5768b commit bdea294

File tree

2 files changed

+245
-0
lines changed

2 files changed

+245
-0
lines changed

docs/tutorials/ble/bleservo.rst

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
BLE Servo Project
2+
--------------------------
3+
This tutorial will give you an insight on how ``bleservo`` sample application works.
4+
5+
.. contents::
6+
:local:
7+
:depth: 2
8+
9+
Prerequisites
10+
~~~~~~~~~~~~~
11+
- Complete other tutorials like: :doc:`Project Blinky <../blinky/blinky>` and :doc:`BLE peripheral project tutorial <bleprph/bleprph>` to famliarize yourself with MyNewt and BLE
12+
- Nordic nRF52 Development board - PCA 10056
13+
- Any simple servomotor to test the application
14+
15+
GATT Server
16+
~~~~~~~~~~~
17+
18+
GATT in this application contains only one custom service, which contains two characteristics with read, write and notify permissions.
19+
Both characteristics contain information about PWM pulse width, however one of them stores this information in the form of servo's tilt angle [degrees] and the other one in pulse duration [microseconds].
20+
This allows the user to control servo in two ways, either with angle or with PWM pulse duration.
21+
22+
Notifications are used to update user's information on both values if one of them has changed. The other corresponding value is calculated before sending a notification, what will be described below, in section PWM Configuration.
23+
24+
.. code:: c
25+
26+
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
27+
{
28+
/* Service: Servo */
29+
.type = BLE_GATT_SVC_TYPE_PRIMARY,
30+
.uuid = &gatt_svr_svc_servo_uuid.u,
31+
.characteristics = (struct ble_gatt_chr_def[]) { {
32+
/* Characteristic: servo angle*/
33+
.uuid = &gatt_svr_chr_servo_angle_uuid.u,
34+
.val_handle = &gatt_angle_val_handle,
35+
.access_cb = gatt_svr_chr_access_servo_angle,
36+
.flags = BLE_GATT_CHR_F_READ |
37+
BLE_GATT_CHR_F_WRITE |
38+
BLE_GATT_CHR_F_NOTIFY
39+
}, {
40+
/* Characteristic: servo PWM pulse duration*/
41+
.uuid = &gatt_svr_chr_servo_pulse_duration_uuid.u,
42+
.val_handle = &gatt_pulse_duration_val_handle,
43+
.access_cb = gatt_svr_chr_access_servo_pulse_duration,
44+
.flags = BLE_GATT_CHR_F_READ |
45+
BLE_GATT_CHR_F_WRITE |
46+
BLE_GATT_CHR_F_NOTIFY
47+
}, {
48+
0, /* No more characteristics in this service */
49+
}, }
50+
},
51+
52+
{
53+
0, /* No more services */
54+
},
55+
};
56+
57+
static int
58+
gatt_svr_chr_access_servo_angle(uint16_t conn_handle, uint16_t attr_handle,
59+
struct ble_gatt_access_ctxt *ctxt, void *arg)
60+
{
61+
int rc;
62+
switch (ctxt->op) {
63+
case BLE_GATT_ACCESS_OP_READ_CHR:
64+
gatt_angle_val = get_servo_angle();
65+
rc = os_mbuf_append(ctxt->om, &gatt_angle_val,
66+
sizeof gatt_angle_val);
67+
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
68+
69+
case BLE_GATT_ACCESS_OP_WRITE_CHR:
70+
rc = gatt_svr_chr_write(ctxt->om,
71+
sizeof gatt_angle_val,
72+
sizeof gatt_angle_val,
73+
&gatt_angle_val, NULL);
74+
servo_angle_setter(gatt_angle_val);
75+
76+
return rc;
77+
default:
78+
assert(0);
79+
return BLE_ATT_ERR_UNLIKELY;
80+
}
81+
}
82+
83+
static int
84+
gatt_svr_chr_access_servo_pulse_duration(uint16_t conn_handle, uint16_t attr_handle,
85+
struct ble_gatt_access_ctxt *ctxt, void *arg)
86+
{
87+
int rc;
88+
switch (ctxt->op) {
89+
case BLE_GATT_ACCESS_OP_READ_CHR:
90+
gatt_pulse_duration_val = get_servo_pwm_pulse_duration();
91+
rc = os_mbuf_append(ctxt->om, &gatt_pulse_duration_val,
92+
sizeof gatt_pulse_duration_val);
93+
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
94+
95+
case BLE_GATT_ACCESS_OP_WRITE_CHR:
96+
rc = gatt_svr_chr_write(ctxt->om,
97+
sizeof gatt_pulse_duration_val,
98+
sizeof gatt_pulse_duration_val,
99+
&gatt_pulse_duration_val, NULL);
100+
servo_pwm_pulse_duration_setter(gatt_pulse_duration_val);
101+
102+
return rc;
103+
default:
104+
assert(0);
105+
return BLE_ATT_ERR_UNLIKELY;
106+
}
107+
}
108+
[...]
109+
110+
111+
Write callbacks of each characteristic call angle or pulse duration setter function. Both files ``gatt_svr.c`` and ``main.c`` need the access to this variables and thanks to this solution, the extern keyword wasn't used in the application. In addition, the boundary conditions of the updated value are checked in this functions and in the end, PWM fracture update event is put to the queue. ``was_angle_passed`` variable is used to keep track on which of the variables has been passed and changed.
112+
113+
.. code:: c
114+
115+
/* Servo angle setter. Used in gatt_svr.c after receiving new angle value. */
116+
void
117+
servo_angle_setter(uint16_t gatt_value)
118+
{
119+
if (gatt_value > SERVO_MAX_ANGLE_VAL) {
120+
servo_angle = SERVO_MAX_ANGLE_VAL;
121+
} else if (gatt_value < SERVO_MIN_ANGLE_VAL) {
122+
servo_angle = SERVO_MIN_ANGLE_VAL;
123+
} else {
124+
servo_angle = gatt_value;
125+
}
126+
127+
was_angle_passed = 1;
128+
129+
/* After changing servo angle value an event to update PWM fraction is put to the queue. */
130+
os_eventq_put(os_eventq_dflt_get(), &pwm_frac_update_ev);
131+
}
132+
133+
/* Servo PWM pulse duration setter. Called in gatt_svr.c after receiving new pulse duration value. */
134+
void
135+
servo_pwm_pulse_duration_setter(uint16_t gatt_value)
136+
{
137+
if (gatt_value > SERVO_MAX_PULSE_DURATION_US) {
138+
servo_pwm_pulse_duration = SERVO_MAX_PULSE_DURATION_US;
139+
} else if (gatt_value < SERVO_MIN_PULSE_DURATION_US) {
140+
servo_pwm_pulse_duration = SERVO_MIN_PULSE_DURATION_US;
141+
} else {
142+
servo_pwm_pulse_duration = gatt_value;
143+
}
144+
145+
was_angle_passed = 0;
146+
147+
/* After changing servo PWM pulse duration value an event to update PWM fraction is put to the queue. */
148+
os_eventq_put(os_eventq_dflt_get(), &pwm_frac_update_ev);
149+
}
150+
151+
PWM configuration
152+
~~~~~~~~~~~~~~~~~
153+
154+
PWM is configured using two structures: ``pwm_chan_cfg`` and ``pwm_dev_cfg``. Fields initialized with ``NULL`` are related to additional PWM features, which we won't use.
155+
156+
In MyNewt PWM fracture setting is based on number of clock cycles, but the application's characteristics contain variables expressed in more human-friendly units: tilt angle [degrees] or pulse duration [us].
157+
The conversion of this variables is necessary, so the application provides four conversion functions. Each of them transforms one variable into the corresponding value of other variable.
158+
In the ``servo_pwm_init()`` function, after configuration of the PWM, top value of mentioned clock cycles is being assigned to the variable ``pwm_top_val``.
159+
This variable is later used as reference in calculating corresponding values of variables in different units.
160+
161+
.. code:: c
162+
163+
int
164+
servo_pwm_init(void)
165+
{
166+
struct pwm_chan_cfg chan_conf = {
167+
.pin = PWM_CH_CFG_PIN,
168+
.inverted = PWM_CH_CFG_INV,
169+
.data = NULL,
170+
};
171+
struct pwm_dev_cfg dev_conf = {
172+
.n_cycles = 0,
173+
.int_prio = PWM_IRQ_PRIO,
174+
.cycle_handler = NULL,
175+
.seq_end_handler = NULL,
176+
.cycle_data = NULL,
177+
.seq_end_data = NULL,
178+
.data = NULL
179+
};
180+
181+
int rc;
182+
183+
servo = (struct pwm_dev *)os_dev_open("pwm0", 0, NULL);
184+
if (!servo) {
185+
console_printf("Device pwm0 not available\n");
186+
return 0;
187+
}
188+
189+
pwm_configure_device(servo, &dev_conf);
190+
191+
rc = pwm_set_frequency(servo, SERVO_PWM_FREQ);
192+
assert(rc > 0);
193+
rc = pwm_configure_channel(servo, PWM_CH_NUM, &chan_conf);
194+
assert(rc == 0);
195+
196+
/* Calculate minimum fracture value */
197+
pwm_top_val = (uint16_t) pwm_get_top_value(servo);
198+
servo_frac_max_val = us_to_frac(SERVO_MAX_PULSE_DURATION_US);
199+
servo_frac_min_val = us_to_frac(SERVO_MIN_PULSE_DURATION_US);
200+
frac = servo_frac_min_val;
201+
202+
/* At the beginning of working of the app PWM fracture is set to minimum */
203+
rc = pwm_set_duty_cycle(servo, PWM_CH_NUM, frac);
204+
rc = pwm_enable(servo);
205+
assert(rc == 0);
206+
207+
return rc;
208+
}
209+
210+
[...]
211+
212+
/* Conversion functions */
213+
uint16_t
214+
angle_to_frac(uint16_t angle)
215+
{
216+
return servo_frac_min_val + (angle * (servo_frac_max_val - servo_frac_min_val) / SERVO_MAX_ANGLE_VAL);
217+
}
218+
219+
uint16_t
220+
us_to_frac(uint16_t us)
221+
{
222+
return (us * pwm_top_val) / SERVO_PWM_FULL_CYCLE_DURATION;
223+
}
224+
225+
uint16_t
226+
frac_to_angle(uint16_t frac_)
227+
{
228+
return (SERVO_MAX_ANGLE_VAL * frac_ - SERVO_MAX_ANGLE_VAL * servo_frac_min_val) /
229+
(servo_frac_max_val - servo_frac_min_val);
230+
}
231+
232+
uint16_t
233+
frac_to_us(uint16_t frac_)
234+
{
235+
return (SERVO_PWM_FULL_CYCLE_DURATION * frac_) / pwm_top_val;
236+
}
237+
238+
Servo wiring
239+
~~~~~~~~~~~~
240+
241+
Servo's PWM signal wire should be connected to the P0.31 pin of the nRF52840 board. You should also remember about providing the common ground for the nRF52 board and servo's power source.
242+
243+
.. figure:: ../pics/servo_conn.jpg
244+
:alt: Servo wiring
245+

docs/tutorials/pics/servo_conn.jpg

2.99 MB
Loading

0 commit comments

Comments
 (0)