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