1 /*
2 * Copyright (c) 2019 Vestas Wind Systems A/S
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/kernel.h>
8 #include <zephyr/drivers/gpio.h>
9 #include <zephyr/sys/reboot.h>
10 #include <zephyr/settings/settings.h>
11 #include <canopennode.h>
12
13 #define LOG_LEVEL CONFIG_CANOPEN_LOG_LEVEL
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(app);
16
17 #define CAN_INTERFACE DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus))
18 #define CAN_BITRATE (DT_PROP_OR(DT_CHOSEN(zephyr_canbus), bitrate, \
19 DT_PROP_OR(DT_CHOSEN(zephyr_canbus), bus_speed, \
20 CONFIG_CAN_DEFAULT_BITRATE)) / 1000)
21
22 static struct gpio_dt_spec led_green_gpio = GPIO_DT_SPEC_GET_OR(
23 DT_ALIAS(green_led), gpios, {0});
24 static struct gpio_dt_spec led_red_gpio = GPIO_DT_SPEC_GET_OR(
25 DT_ALIAS(red_led), gpios, {0});
26
27 static struct gpio_dt_spec button_gpio = GPIO_DT_SPEC_GET_OR(
28 DT_ALIAS(sw0), gpios, {0});
29 static struct gpio_callback button_callback;
30
31 struct led_indicator {
32 const struct device *dev;
33 gpio_pin_t pin;
34 };
35
36 static uint32_t counter;
37
38 /**
39 * @brief Callback for setting LED indicator state.
40 *
41 * @param value true if the LED indicator shall be turned on, false otherwise.
42 * @param arg argument that was passed when LEDs were initialized.
43 */
led_callback(bool value,void * arg)44 static void led_callback(bool value, void *arg)
45 {
46 struct gpio_dt_spec *led_gpio = arg;
47
48 if (!led_gpio || !led_gpio->port) {
49 return;
50 }
51
52 gpio_pin_set_dt(led_gpio, value);
53 }
54
55 /**
56 * @brief Configure LED indicators pins and callbacks.
57 *
58 * This routine configures the GPIOs for the red and green LEDs (if
59 * available).
60 *
61 * @param nmt CANopenNode NMT object.
62 */
config_leds(CO_NMT_t * nmt)63 static void config_leds(CO_NMT_t *nmt)
64 {
65 int err;
66
67 if (!led_green_gpio.port) {
68 LOG_INF("Green LED not available");
69 } else if (!gpio_is_ready_dt(&led_green_gpio)) {
70 LOG_ERR("Green LED device not ready");
71 led_green_gpio.port = NULL;
72 } else {
73 err = gpio_pin_configure_dt(&led_green_gpio,
74 GPIO_OUTPUT_INACTIVE);
75 if (err) {
76 LOG_ERR("failed to configure Green LED gpio: %d", err);
77 led_green_gpio.port = NULL;
78 }
79 }
80
81 if (!led_red_gpio.port) {
82 LOG_INF("Red LED not available");
83 } else if (!gpio_is_ready_dt(&led_red_gpio)) {
84 LOG_ERR("Red LED device not ready");
85 led_red_gpio.port = NULL;
86 } else {
87 err = gpio_pin_configure_dt(&led_red_gpio,
88 GPIO_OUTPUT_INACTIVE);
89 if (err) {
90 LOG_ERR("failed to configure Red LED gpio: %d", err);
91 led_red_gpio.port = NULL;
92 }
93 }
94
95 canopen_leds_init(nmt,
96 led_callback, &led_green_gpio,
97 led_callback, &led_red_gpio);
98 }
99
100 /**
101 * @brief Button press counter object dictionary handler function.
102 *
103 * This function is called upon SDO access to the button press counter
104 * object (index 0x2102) in the object dictionary.
105 *
106 * @param odf_arg object dictionary function argument.
107 *
108 * @return SDO abort code.
109 */
odf_2102(CO_ODF_arg_t * odf_arg)110 static CO_SDO_abortCode_t odf_2102(CO_ODF_arg_t *odf_arg)
111 {
112 uint32_t value;
113
114 value = CO_getUint32(odf_arg->data);
115
116 if (odf_arg->reading) {
117 return CO_SDO_AB_NONE;
118 }
119
120 if (odf_arg->subIndex != 0U) {
121 return CO_SDO_AB_NONE;
122 }
123
124 if (value != 0) {
125 /* Preserve old value */
126 memcpy(odf_arg->data, odf_arg->ODdataStorage, sizeof(uint32_t));
127 return CO_SDO_AB_DATA_TRANSF;
128 }
129
130 LOG_INF("Resetting button press counter");
131 counter = 0;
132
133 return CO_SDO_AB_NONE;
134 }
135
136 /**
137 * @brief Button press interrupt callback.
138 *
139 * @param port GPIO device struct.
140 * @param cb GPIO callback struct.
141 * @param pins GPIO pin mask that triggered the interrupt.
142 */
button_isr_callback(const struct device * port,struct gpio_callback * cb,uint32_t pins)143 static void button_isr_callback(const struct device *port,
144 struct gpio_callback *cb,
145 uint32_t pins)
146 {
147 counter++;
148 }
149
150 /**
151 * @brief Configure button GPIO pin and callback.
152 *
153 * This routine configures the GPIO for the button (if available).
154 */
config_button(void)155 static void config_button(void)
156 {
157 int err;
158
159 if (button_gpio.port == NULL) {
160 LOG_INF("Button not available");
161 return;
162 }
163
164 if (!gpio_is_ready_dt(&button_gpio)) {
165 LOG_ERR("Button device not ready");
166 return;
167 }
168
169 err = gpio_pin_configure_dt(&button_gpio, GPIO_INPUT);
170 if (err) {
171 LOG_ERR("failed to configure button gpio: %d", err);
172 return;
173 }
174
175 gpio_init_callback(&button_callback, button_isr_callback,
176 BIT(button_gpio.pin));
177
178 err = gpio_add_callback(button_gpio.port, &button_callback);
179 if (err) {
180 LOG_ERR("failed to add button callback: %d", err);
181 return;
182 }
183
184 err = gpio_pin_interrupt_configure_dt(&button_gpio,
185 GPIO_INT_EDGE_TO_ACTIVE);
186 if (err) {
187 LOG_ERR("failed to enable button callback: %d", err);
188 return;
189 }
190 }
191
192 /**
193 * @brief Main application entry point.
194 *
195 * The main application thread is responsible for initializing the
196 * CANopen stack and doing the non real-time processing.
197 */
main(void)198 int main(void)
199 {
200 CO_NMT_reset_cmd_t reset = CO_RESET_NOT;
201 CO_ReturnError_t err;
202 struct canopen_context can;
203 uint16_t timeout;
204 uint32_t elapsed;
205 int64_t timestamp;
206 #ifdef CONFIG_CANOPENNODE_STORAGE
207 int ret;
208 #endif /* CONFIG_CANOPENNODE_STORAGE */
209
210 can.dev = CAN_INTERFACE;
211 if (!device_is_ready(can.dev)) {
212 LOG_ERR("CAN interface not ready");
213 return 0;
214 }
215
216 #ifdef CONFIG_CANOPENNODE_STORAGE
217 ret = settings_subsys_init();
218 if (ret) {
219 LOG_ERR("failed to initialize settings subsystem (err = %d)",
220 ret);
221 return 0;
222 }
223
224 ret = settings_load();
225 if (ret) {
226 LOG_ERR("failed to load settings (err = %d)", ret);
227 return 0;
228 }
229 #endif /* CONFIG_CANOPENNODE_STORAGE */
230
231 OD_powerOnCounter++;
232
233 config_button();
234
235 while (reset != CO_RESET_APP) {
236 elapsed = 0U; /* milliseconds */
237
238 err = CO_init(&can, CONFIG_CANOPEN_NODE_ID, CAN_BITRATE);
239 if (err != CO_ERROR_NO) {
240 LOG_ERR("CO_init failed (err = %d)", err);
241 return 0;
242 }
243
244 LOG_INF("CANopen stack initialized");
245
246 #ifdef CONFIG_CANOPENNODE_STORAGE
247 canopen_storage_attach(CO->SDO[0], CO->em);
248 #endif /* CONFIG_CANOPENNODE_STORAGE */
249
250 config_leds(CO->NMT);
251 CO_OD_configure(CO->SDO[0], OD_2102_buttonPressCounter,
252 odf_2102, NULL, 0U, 0U);
253
254 if (IS_ENABLED(CONFIG_CANOPENNODE_PROGRAM_DOWNLOAD)) {
255 canopen_program_download_attach(CO->NMT, CO->SDO[0],
256 CO->em);
257 }
258
259 CO_CANsetNormalMode(CO->CANmodule[0]);
260
261 while (true) {
262 timeout = 1U; /* default timeout in milliseconds */
263 timestamp = k_uptime_get();
264 reset = CO_process(CO, (uint16_t)elapsed, &timeout);
265
266 if (reset != CO_RESET_NOT) {
267 break;
268 }
269
270 if (timeout > 0) {
271 CO_LOCK_OD();
272 OD_buttonPressCounter = counter;
273 CO_UNLOCK_OD();
274
275 #ifdef CONFIG_CANOPENNODE_STORAGE
276 ret = canopen_storage_save(
277 CANOPEN_STORAGE_EEPROM);
278 if (ret) {
279 LOG_ERR("failed to save EEPROM");
280 }
281 #endif /* CONFIG_CANOPENNODE_STORAGE */
282 /*
283 * Try to sleep for as long as the
284 * stack requested and calculate the
285 * exact time elapsed.
286 */
287 k_sleep(K_MSEC(timeout));
288 elapsed = (uint32_t)k_uptime_delta(×tamp);
289 } else {
290 /*
291 * Do not sleep, more processing to be
292 * done by the stack.
293 */
294 elapsed = 0U;
295 }
296 }
297
298 if (reset == CO_RESET_COMM) {
299 LOG_INF("Resetting communication");
300 }
301 }
302
303 LOG_INF("Resetting device");
304
305 CO_delete(&can);
306 sys_reboot(SYS_REBOOT_COLD);
307 }
308