parent
1078a71fca
commit
56731419b0
@ -0,0 +1,73 @@ |
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
||||
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage"> |
||||
<storageModule moduleId="org.eclipse.cdt.core.settings"> |
||||
<cconfiguration id="0.1493500689"> |
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="0.1493500689" moduleId="org.eclipse.cdt.core.settings" name="Default"> |
||||
<externalSettings/> |
||||
<extensions> |
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> |
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> |
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> |
||||
<extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> |
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/> |
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> |
||||
</extensions> |
||||
</storageModule> |
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0"> |
||||
<configuration buildProperties="" description="" id="0.1493500689" name="Default" parent="org.eclipse.cdt.build.core.prefbase.cfg"> |
||||
<folderInfo id="0.1493500689." name="/" resourcePath=""> |
||||
<toolChain id="org.eclipse.cdt.build.core.prefbase.toolchain.1870777726" name="No ToolChain" resourceTypeBasedDiscovery="false" superClass="org.eclipse.cdt.build.core.prefbase.toolchain"> |
||||
<targetPlatform id="org.eclipse.cdt.build.core.prefbase.toolchain.1870777726.204743302" name=""/> |
||||
<builder id="org.eclipse.cdt.build.core.settings.default.builder.466787059" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="org.eclipse.cdt.build.core.settings.default.builder"/> |
||||
<tool id="org.eclipse.cdt.build.core.settings.holder.libs.1222824041" name="holder for library settings" superClass="org.eclipse.cdt.build.core.settings.holder.libs"/> |
||||
<tool id="org.eclipse.cdt.build.core.settings.holder.1644638329" name="Assembly" superClass="org.eclipse.cdt.build.core.settings.holder"> |
||||
<option id="org.eclipse.cdt.build.core.settings.holder.undef.symbols.113137200" superClass="org.eclipse.cdt.build.core.settings.holder.undef.symbols"/> |
||||
<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.776008699" languageId="org.eclipse.cdt.core.assembly" languageName="Assembly" sourceContentType="org.eclipse.cdt.core.asmSource" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/> |
||||
</tool> |
||||
<tool id="org.eclipse.cdt.build.core.settings.holder.2049577207" name="Assembly" superClass="org.eclipse.cdt.build.core.settings.holder"> |
||||
<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.982490919" languageId="com.crt.advproject.com.crt.mcu.assembly" languageName="Assembly" sourceContentType="com.crt.advproject.asmSource" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/> |
||||
</tool> |
||||
<tool id="org.eclipse.cdt.build.core.settings.holder.1959993364" name="GNU C++" superClass="org.eclipse.cdt.build.core.settings.holder"> |
||||
<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.1727849852" languageId="org.eclipse.cdt.core.g++" languageName="GNU C++" sourceContentType="org.eclipse.cdt.core.cxxSource,org.eclipse.cdt.core.cxxHeader" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/> |
||||
</tool> |
||||
<tool id="org.eclipse.cdt.build.core.settings.holder.783534498" name="GNU C" superClass="org.eclipse.cdt.build.core.settings.holder"> |
||||
<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.524594272" languageId="org.eclipse.cdt.core.gcc" languageName="GNU C" sourceContentType="org.eclipse.cdt.core.cSource,org.eclipse.cdt.core.cHeader" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/> |
||||
</tool> |
||||
</toolChain> |
||||
</folderInfo> |
||||
</configuration> |
||||
</storageModule> |
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/> |
||||
</cconfiguration> |
||||
</storageModule> |
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0"> |
||||
<project id="ADIS_Raspi_UDP.null.2080177565" name="ADIS_Raspi_UDP"/> |
||||
</storageModule> |
||||
<storageModule moduleId="scannerConfiguration"> |
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/> |
||||
<scannerConfigBuildInfo instanceId="0.1493500689"> |
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/> |
||||
</scannerConfigBuildInfo> |
||||
</storageModule> |
||||
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"> |
||||
<buildTargets> |
||||
<target name="all" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder"> |
||||
<buildCommand>make</buildCommand> |
||||
<buildArguments/> |
||||
<buildTarget>all</buildTarget> |
||||
<stopOnError>true</stopOnError> |
||||
<useDefaultCommand>true</useDefaultCommand> |
||||
<runAllBuilders>true</runAllBuilders> |
||||
</target> |
||||
<target name="clean" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder"> |
||||
<buildCommand>make</buildCommand> |
||||
<buildArguments/> |
||||
<buildTarget>clean</buildTarget> |
||||
<stopOnError>true</stopOnError> |
||||
<useDefaultCommand>true</useDefaultCommand> |
||||
<runAllBuilders>true</runAllBuilders> |
||||
</target> |
||||
</buildTargets> |
||||
</storageModule> |
||||
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/> |
||||
</cproject> |
||||
@ -0,0 +1,27 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<projectDescription> |
||||
<name>ADIS_Raspi_UDP</name> |
||||
<comment></comment> |
||||
<projects> |
||||
</projects> |
||||
<buildSpec> |
||||
<buildCommand> |
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name> |
||||
<triggers>clean,full,incremental,</triggers> |
||||
<arguments> |
||||
</arguments> |
||||
</buildCommand> |
||||
<buildCommand> |
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name> |
||||
<triggers>full,incremental,</triggers> |
||||
<arguments> |
||||
</arguments> |
||||
</buildCommand> |
||||
</buildSpec> |
||||
<natures> |
||||
<nature>org.eclipse.cdt.core.cnature</nature> |
||||
<nature>org.eclipse.cdt.core.ccnature</nature> |
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature> |
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature> |
||||
</natures> |
||||
</projectDescription> |
||||
@ -0,0 +1,3 @@ |
||||
*.o |
||||
*.d |
||||
udp_app |
||||
@ -0,0 +1,7 @@ |
||||
readme.txt |
||||
---------- |
||||
This project implements a command line client for UDP on the Raspberry Pi. |
||||
|
||||
Usage example: |
||||
./udp_app --udp tx ESP32DEVKIT02 1234 "cmd rs status" |
||||
|
||||
@ -0,0 +1,21 @@ |
||||
/*
|
||||
* delay.c |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#include "delay.h" |
||||
#include <unistd.h> /* interface to sleep */ |
||||
|
||||
void delay_ms(unsigned int ms) { |
||||
//sleep(1); /* sleep for 1 second */
|
||||
usleep(ms*1000); /* sleep for number of microseconds */ |
||||
#if 0 |
||||
volatile long i; |
||||
for(i=0; i<50000000; i++) { |
||||
/* burning time */ |
||||
} |
||||
#endif |
||||
} |
||||
|
||||
@ -0,0 +1,13 @@ |
||||
/*
|
||||
* delay.h |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#ifndef SRC_DELAY_H_ |
||||
#define SRC_DELAY_H_ |
||||
|
||||
void delay_ms(unsigned int ms); |
||||
|
||||
#endif /* SRC_DELAY_H_ */ |
||||
@ -0,0 +1,345 @@ |
||||
/*
|
||||
* Copyright (c) 2022, Erich Styger |
||||
* |
||||
* SPDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
/* see
|
||||
* https://blog.lxsang.me/post/id/33
|
||||
* https://embeddedbits.org/new-linux-kernel-gpio-user-space-interface/
|
||||
* https://www.ics.com/blog/gpio-programming-exploring-libgpiod-library
|
||||
* https://lloydrochester.com/post/hardware/libgpiod-intro-rpi/
|
||||
* */ |
||||
|
||||
#include "gpio.h" |
||||
|
||||
/* include API header for the new interface */ |
||||
#include <linux/gpio.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <sys/ioctl.h> |
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <getopt.h> |
||||
#include <stdlib.h> |
||||
#include <sys/poll.h> |
||||
|
||||
#define GPIO_DEV_NAME "/dev/gpiochip0" |
||||
|
||||
/* HAT v7 requires pull-ups enabled. In case if using older headers, define locally the new flags */ |
||||
#ifndef GPIOHANDLE_REQUEST_BIAS_PULL_UP /* supported with latest libs: if not defined, maybe using older header files? Let's assume the library is a recent one */ |
||||
#define GPIOHANDLE_REQUEST_BIAS_PULL_UP (1UL << 5) |
||||
#define GPIOHANDLE_REQUEST_BIAS_PULL_DOWN (1UL << 6) |
||||
#define GPIOHANDLE_REQUEST_BIAS_DISABLE (1UL << 7) |
||||
#endif |
||||
|
||||
typedef struct { |
||||
struct gpiohandle_request rq; /* request data structure */ |
||||
struct gpiohandle_data data; |
||||
} Gpio_Desc_t; |
||||
|
||||
static Gpio_Desc_t yellowLED, redLED, greenLED; |
||||
#if PL_HAT_VERSION<=6 |
||||
static Gpio_Desc_t blueLED; |
||||
#endif |
||||
static Gpio_Desc_t upBTN, downBTN, leftBTN, rightBTN, centerBTN; |
||||
|
||||
static void gpio_list(const char *dev_name) { |
||||
struct gpiochip_info info; |
||||
struct gpioline_info line_info; |
||||
int fd, ret; |
||||
|
||||
fd = open(dev_name, O_RDONLY); |
||||
if (fd < 0) { |
||||
printf("Unabled to open %s: %s", dev_name, strerror(errno)); |
||||
return; |
||||
} |
||||
ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); |
||||
if (ret == -1) { |
||||
printf("Unable to get chip info from ioctl: %s", strerror(errno)); |
||||
close(fd); |
||||
return; |
||||
} |
||||
printf("Chip name: %s\n", info.name); |
||||
printf("Chip label: %s\n", info.label); |
||||
printf("Number of lines: %d\n", info.lines); |
||||
for (int i = 0; i < info.lines; i++) { |
||||
line_info.line_offset = i; |
||||
ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &line_info); |
||||
if (ret == -1) { |
||||
printf("Unable to get line info from offset %d: %s", i, strerror(errno)); |
||||
} else { |
||||
printf("offset: %d, name: %s, consumer: %s. Flags:\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]\n", |
||||
i, |
||||
line_info.name, |
||||
line_info.consumer, |
||||
(line_info.flags & GPIOLINE_FLAG_IS_OUT) ? "OUTPUT" : "INPUT", |
||||
(line_info.flags & GPIOLINE_FLAG_ACTIVE_LOW) ? "ACTIVE_LOW" : "ACTIVE_HIGHT", |
||||
(line_info.flags & GPIOLINE_FLAG_OPEN_DRAIN) ? "OPEN_DRAIN" : "...", |
||||
(line_info.flags & GPIOHANDLE_REQUEST_BIAS_PULL_UP) ? "PULL-UP" : "...", |
||||
(line_info.flags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN) ? "PULL-DOWN" : "...", |
||||
(line_info.flags & GPIOHANDLE_REQUEST_BIAS_DISABLE) ? "PULL-DISABLE" : "...", |
||||
(line_info.flags & GPIOLINE_FLAG_OPEN_SOURCE) ? "OPENSOURCE" : "...", |
||||
(line_info.flags & GPIOLINE_FLAG_KERNEL) ? "KERNEL" : ""); |
||||
} |
||||
} |
||||
close(fd); |
||||
} |
||||
|
||||
static void gpio_write(const char *dev_name, int offset, uint8_t value) { |
||||
struct gpiohandle_request rq; |
||||
struct gpiohandle_data data; |
||||
int fd, ret; |
||||
|
||||
printf("Write value %d to GPIO at offset %d (OUTPUT mode) on chip %s\n", value, offset, dev_name); |
||||
fd = open(dev_name, O_RDONLY); |
||||
if (fd < 0) { |
||||
printf("Unabled to open %s: %s", dev_name, strerror(errno)); |
||||
return; |
||||
} |
||||
rq.lineoffsets[0] = offset; |
||||
rq.flags = GPIOHANDLE_REQUEST_OUTPUT; |
||||
rq.lines = 1; |
||||
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq); |
||||
close(fd); |
||||
if (ret == -1) { |
||||
printf("Unable to line handle from ioctl : %s", strerror(errno)); |
||||
return; |
||||
} |
||||
data.values[0] = value; |
||||
ret = ioctl(rq.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); |
||||
if (ret == -1) { |
||||
printf("Unable to set line value using ioctl : %s", strerror(errno)); |
||||
} else { |
||||
usleep(2000000); /* wait 2 sec */ |
||||
} |
||||
close(rq.fd); |
||||
} |
||||
|
||||
static void writePin(Gpio_Desc_t *io, uint8_t value) { |
||||
int ret; |
||||
|
||||
io->data.values[0] = value; |
||||
ret = ioctl(io->rq.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &io->data); |
||||
if (ret == -1) { |
||||
printf("Unable to set line value using ioctl : %s", strerror(errno)); |
||||
} |
||||
} |
||||
|
||||
static bool readPin(Gpio_Desc_t *io) { |
||||
int ret; |
||||
bool result = false; |
||||
|
||||
ret = ioctl(io->rq.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &io->data); |
||||
if (ret == -1) { |
||||
printf("Unable to get line value using ioctl : %s", strerror(errno)); |
||||
result = false; |
||||
} else { |
||||
result = io->data.values[0]!=0; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
static int gpio_read(const char *dev_name, int offset) |
||||
{ |
||||
struct gpiohandle_request rq; |
||||
struct gpiohandle_data data; |
||||
int fd, ret; |
||||
int result = -1; |
||||
|
||||
fd = open(dev_name, O_RDONLY); |
||||
if (fd < 0) |
||||
{ |
||||
printf("Unabled to open %s: %s", dev_name, strerror(errno)); |
||||
return -1; |
||||
} |
||||
rq.lineoffsets[0] = offset; |
||||
rq.flags = GPIOHANDLE_REQUEST_INPUT; |
||||
rq.lines = 1; |
||||
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq); |
||||
close(fd); |
||||
if (ret == -1) |
||||
{ |
||||
printf("Unable to get line handle from ioctl : %s", strerror(errno)); |
||||
return -1; |
||||
} |
||||
ret = ioctl(rq.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); |
||||
if (ret == -1) |
||||
{ |
||||
printf("Unable to get line value using ioctl : %s", strerror(errno)); |
||||
result = -1; |
||||
} |
||||
else |
||||
{ |
||||
printf("Value of GPIO at offset %d (INPUT mode) on chip %s: %d\n", offset, dev_name, data.values[0]); |
||||
result = data.values[0]; |
||||
} |
||||
close(rq.fd); |
||||
return result; |
||||
} |
||||
|
||||
static void gpio_poll(const char *dev_name, int offset) |
||||
{ |
||||
struct gpioevent_request rq; |
||||
struct pollfd pfd; |
||||
int fd, ret; |
||||
|
||||
fd = open(dev_name, O_RDONLY); |
||||
if (fd < 0) |
||||
{ |
||||
printf("Unabled to open %s: %s", dev_name, strerror(errno)); |
||||
return; |
||||
} |
||||
rq.lineoffset = offset; |
||||
rq.eventflags = GPIOEVENT_EVENT_RISING_EDGE; |
||||
ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &rq); |
||||
close(fd); |
||||
if (ret == -1) |
||||
{ |
||||
printf("Unable to get line event from ioctl : %s", strerror(errno)); |
||||
close(fd); |
||||
return; |
||||
} |
||||
pfd.fd = rq.fd; |
||||
pfd.events = POLLIN; |
||||
ret = poll(&pfd, 1, -1); |
||||
if (ret == -1) |
||||
{ |
||||
printf("Error while polling event from GPIO: %s", strerror(errno)); |
||||
} |
||||
else if (pfd.revents & POLLIN) |
||||
{ |
||||
printf("Rising edge event on GPIO offset: %d, of %s\n", offset, dev_name); |
||||
} |
||||
close(rq.fd); |
||||
} |
||||
|
||||
bool Gpio_ReadPin(Gpio_Pins_e pin) { |
||||
bool val; |
||||
//return gpio_read(GPIO_DEV_NAME, pin);
|
||||
switch(pin) { |
||||
case Gpio_Pins_BTN_UP_BCM: |
||||
val = readPin(&upBTN); |
||||
break; |
||||
case Gpio_Pins_BTN_DOWN_BCM: |
||||
val = readPin(&downBTN); |
||||
break; |
||||
case Gpio_Pins_BTN_LEFT_BCM: |
||||
val = readPin(&leftBTN); |
||||
break; |
||||
case Gpio_Pins_BTN_RIGHT_BCM: |
||||
val = readPin(&rightBTN); |
||||
break; |
||||
case Gpio_Pins_BTN_CENTER_BCM: |
||||
val = readPin(¢erBTN); |
||||
break; |
||||
default: |
||||
val = false; |
||||
break; |
||||
} |
||||
return val; |
||||
} |
||||
|
||||
void Gpio_WritePin(Gpio_Pins_e pin, bool val) { |
||||
switch(pin) { |
||||
#if PL_HAT_VERSION<=6 |
||||
case Gpio_Pins_LED_BLUE_PIN_BCM: |
||||
writePin(&blueLED, val); |
||||
break; |
||||
#endif |
||||
case Gpio_Pins_LED_RED_PIN_BCM: |
||||
writePin(&redLED, val); |
||||
break; |
||||
case Gpio_Pins_LED_GREEN_PIN_BCM: |
||||
writePin(&greenLED, val); |
||||
break; |
||||
case Gpio_Pins_LED_YELLOW_PIN_BCM: |
||||
writePin(&yellowLED, val); |
||||
break; |
||||
|
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
int Gpio_Deinit(void) { |
||||
#if PL_HAT_VERSION<=6 |
||||
close(blueLED.rq.fd); |
||||
#endif |
||||
close(redLED.rq.fd); |
||||
close(greenLED.rq.fd); |
||||
close(yellowLED.rq.fd); |
||||
|
||||
close(upBTN.rq.fd); |
||||
close(downBTN.rq.fd); |
||||
close(leftBTN.rq.fd); |
||||
close(rightBTN.rq.fd); |
||||
close(centerBTN.rq.fd); |
||||
return 0; |
||||
} |
||||
|
||||
static int RequestPin(Gpio_Desc_t *desc, Gpio_Pins_e pin, int lineRequestFlags, const char *label) { |
||||
int fd, ret; |
||||
|
||||
fd = open(GPIO_DEV_NAME, O_RDONLY); |
||||
if (fd < 0) { |
||||
printf("Unabled to open %s: %s", GPIO_DEV_NAME, strerror(errno)); |
||||
return -1; |
||||
} |
||||
desc->rq.lineoffsets[0] = pin; |
||||
desc->rq.flags = lineRequestFlags; |
||||
memcpy(desc->rq.default_values, &desc->data, sizeof(desc->rq.default_values)); |
||||
strcpy(desc->rq.consumer_label, label); |
||||
desc->rq.lines = 1; |
||||
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &desc->rq); |
||||
close(fd); |
||||
if (ret == -1) { |
||||
printf("Unable to get line handle from ioctl : %s", strerror(errno)); |
||||
return -1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int Gpio_Init(void) { |
||||
#if GPIO_HAT_VERSION<=6 |
||||
if (RequestPin(&blueLED, Gpio_Pins_LED_BLUE_PIN_BCM, GPIOHANDLE_REQUEST_OUTPUT, "led_blue")!=0) { |
||||
return -1; |
||||
} |
||||
#endif |
||||
if (RequestPin(&redLED, Gpio_Pins_LED_RED_PIN_BCM, GPIOHANDLE_REQUEST_OUTPUT, "led_red")!=0) { |
||||
return -1; |
||||
} |
||||
if (RequestPin(&greenLED, Gpio_Pins_LED_GREEN_PIN_BCM, GPIOHANDLE_REQUEST_OUTPUT, "led_green")!=0) { |
||||
return -1; |
||||
} |
||||
|
||||
if (RequestPin(&yellowLED, Gpio_Pins_LED_YELLOW_PIN_BCM, GPIOHANDLE_REQUEST_OUTPUT, "led_yellow")!=0) { |
||||
return -1; |
||||
} |
||||
|
||||
int inputPinFlags = GPIOHANDLE_REQUEST_INPUT; |
||||
#if PL_HAT_VERSION>6 |
||||
inputPinFlags |= GPIOHANDLE_REQUEST_BIAS_PULL_UP; |
||||
#endif |
||||
if (RequestPin(&upBTN, Gpio_Pins_BTN_UP_BCM, inputPinFlags, "button_up")!=0) { |
||||
return -1; |
||||
} |
||||
if (RequestPin(&downBTN, Gpio_Pins_BTN_DOWN_BCM, inputPinFlags, "button_down")!=0) { |
||||
return -1; |
||||
} |
||||
if (RequestPin(&leftBTN, Gpio_Pins_BTN_LEFT_BCM, inputPinFlags, "button_left")!=0) { |
||||
return -1; |
||||
} |
||||
if (RequestPin(&rightBTN, Gpio_Pins_BTN_RIGHT_BCM, inputPinFlags, "button_right")!=0) { |
||||
return -1; |
||||
} |
||||
if (RequestPin(¢erBTN, Gpio_Pins_BTN_CENTER_BCM, inputPinFlags, "button_center")!=0) { |
||||
return -1; |
||||
} |
||||
#if 0 /* testing only */
|
||||
gpio_list(GPIO_DEV_NAME); |
||||
#endif |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
/*
|
||||
* Copyright (c) 2022, Erich Styger |
||||
* |
||||
* SPDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#ifndef SRC_GPIO_H_ |
||||
#define SRC_GPIO_H_ |
||||
|
||||
#include <stdbool.h> |
||||
#include "platform.h" |
||||
|
||||
/* Pins with the BCM/Broadcom numbers */ |
||||
typedef enum { |
||||
#if PL_HAT_VERSION<=6 |
||||
Gpio_Pins_LED_BLUE_PIN_BCM = 12, /* wPi 26 */ |
||||
Gpio_Pins_LED_GREEN_PIN_BCM = 16, /* wPi 27 */ |
||||
Gpio_Pins_LED_YELLOW_PIN_BCM = 20, /* wPi 28 */ |
||||
Gpio_Pins_LED_RED_PIN_BCM = 21, /* wPi 29 */ |
||||
#else |
||||
Gpio_Pins_LED_RED_PIN_BCM = 16, /* wPi 27 */ |
||||
Gpio_Pins_LED_GREEN_PIN_BCM = 21, /* wPi 29 */ |
||||
Gpio_Pins_LED_YELLOW_PIN_BCM = 20, /* wPi 28 */ |
||||
#endif |
||||
|
||||
Gpio_Pins_BTN_UP_BCM = 5, /* wPi 21 */ |
||||
Gpio_Pins_BTN_DOWN_BCM = 6, /* wPi 22 */ |
||||
Gpio_Pins_BTN_RIGHT_BCM = 13, /* wPi 23 */ |
||||
Gpio_Pins_BTN_LEFT_BCM = 19, /* wPi 24 */ |
||||
Gpio_Pins_BTN_CENTER_BCM = 26, /* wPi 25 */ |
||||
} Gpio_Pins_e; |
||||
|
||||
bool Gpio_ReadPin(Gpio_Pins_e pin); |
||||
void Gpio_WritePin(Gpio_Pins_e pin, bool val); |
||||
|
||||
int Gpio_Init(void); |
||||
int Gpio_Deinit(void); |
||||
|
||||
#endif /* SRC_GPIO_H_ */ |
||||
@ -0,0 +1,36 @@ |
||||
/*
|
||||
* Copyright (c) 2021, Erich Styger |
||||
* |
||||
* SPDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
/* taken from https://www.binarytides.com/hostname-to-ip-address-c-sockets-linux/ */ |
||||
#include "hostname.h" /* own interface */ |
||||
#include <stdio.h> /* printf */ |
||||
#include <string.h> /* memset */ |
||||
#include <stdlib.h> /* for exit(0); */ |
||||
#include <sys/socket.h> |
||||
#include <errno.h> /* For errno - the error number */ |
||||
#include <netdb.h> /* hostent */ |
||||
#include <arpa/inet.h> |
||||
|
||||
/* Get ip from domain name */ |
||||
int hostname_to_ip(const char *hostname, char *ipBuf, size_t ipBufLen) { |
||||
struct hostent *he; |
||||
struct in_addr **addr_list; |
||||
int i; |
||||
|
||||
if ((he = gethostbyname(hostname)) == NULL) { |
||||
/* get the host info */ |
||||
herror("gethostbyname"); |
||||
return -1; |
||||
} |
||||
addr_list = (struct in_addr **) he->h_addr_list; |
||||
for(i = 0; addr_list[i]!=NULL; i++) { |
||||
/* Return the first one */ |
||||
strncpy(ipBuf, inet_ntoa(*addr_list[i]), ipBufLen); |
||||
ipBuf[ipBufLen-1] = '\0'; /* terminate string */ |
||||
return 0; |
||||
} |
||||
return -1; |
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
/*
|
||||
* Copyright (c) 2021, Erich Styger |
||||
* |
||||
* SPDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#ifndef SRC_HOSTNAME_H_ |
||||
#define SRC_HOSTNAME_H_ |
||||
|
||||
#include <stddef.h> |
||||
|
||||
/* return for a given hostname the IP address */ |
||||
int hostname_to_ip(const char *hostname, char *ipBuf, size_t ipBufLen); |
||||
|
||||
#endif /* SRC_HOSTNAME_H_ */ |
||||
@ -0,0 +1,67 @@ |
||||
/*
|
||||
* main.c |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#include "main.h" |
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include "delay.h" |
||||
#include "udp.h" |
||||
#include "robotNav.h" |
||||
#include "platform.h" |
||||
|
||||
#define VERSION_INFO "v1.2" |
||||
|
||||
static void printHelp(void) { |
||||
printf("Usage:\n"); |
||||
printf(" --help : prints this list\n"); |
||||
printf(" --udp tx <server> <port> <txt> : send UDP datagram\n"); |
||||
printf(" --test : test led and navigation switch\n"); |
||||
printf(" --nav <server> <port> : navigation with UDP server using joystick\n"); |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
int res; |
||||
|
||||
printf("UDP application, %s, use --help for a list of options\n", VERSION_INFO); |
||||
if (argc > 1) { |
||||
#if 0 /* debugging only */
|
||||
int count; |
||||
|
||||
printf ("This program was called with \"%s\".\n",argv[0]); |
||||
printf("nof arguments: %d\n", argc); |
||||
for (count = 1; count < argc; count++) { |
||||
printf("nof: %d, argv[%d] = %s\n", argc, count, argv[count]); |
||||
} |
||||
#endif |
||||
} else { |
||||
printHelp(); |
||||
return 0; |
||||
} |
||||
|
||||
PL_Init(); |
||||
if (argc==2 && strcmp(argv[1], "--help")==0) { |
||||
printHelp(); |
||||
} else if (argc==6 && strcmp(argv[1], "--udp")==0 && strcmp(argv[2], "tx")==0) { |
||||
udp_send(argv[3], atoi(argv[4]), argv[5]); |
||||
} else if (argc==2 && strcmp(argv[1], "--test")==0) { |
||||
res = ROBONAV_Init(); |
||||
if (res==0) { |
||||
ROBONAV_test(); |
||||
ROBONAV_Deinit(); |
||||
} |
||||
} else if (argc==4 && strcmp(argv[1], "--nav")==0) { |
||||
res = ROBONAV_Init(); |
||||
if (res==0) { |
||||
ROBONAV_navigate(argv[2], atoi(argv[3])); /* pass server and port */ |
||||
ROBONAV_Deinit(); |
||||
} |
||||
} else { /* unknown command */ |
||||
printHelp(); |
||||
} |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
/*
|
||||
* main.h |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#ifndef SRC_MAIN_H_ |
||||
#define SRC_MAIN_H_ |
||||
|
||||
int main(int argc, char *argv[]); |
||||
|
||||
#endif /* SRC_MAIN_H_ */ |
||||
@ -0,0 +1,21 @@ |
||||
/*
|
||||
* Copyright (c) 2022, Erich Styger |
||||
* |
||||
* SPDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include "platform.h" |
||||
#include "gpio.h" |
||||
|
||||
void PL_Deinit(void) { |
||||
Gpio_Deinit(); |
||||
} |
||||
|
||||
void PL_Init(void) { |
||||
printf("using libgpiod with HAT version %d\n", PL_HAT_VERSION); |
||||
if (Gpio_Init()<0) { |
||||
printf("failed Gpio_Init()!\n"); |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,15 @@ |
||||
/*
|
||||
* Copyright (c) 2022, Erich Styger |
||||
* |
||||
* SPDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#ifndef SRC_PLATFORM_H_ |
||||
#define SRC_PLATFORM_H_ |
||||
|
||||
#define PL_HAT_VERSION (6) /* HAT version used, e.g. 4, 5, 6 or 7 */ |
||||
|
||||
void PL_Deinit(void); |
||||
void PL_Init(void); |
||||
|
||||
#endif /* SRC_PLATFORM_H_ */ |
||||
@ -0,0 +1,138 @@ |
||||
/*
|
||||
* robotNav.c |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#include "robotNav.h" |
||||
#include "gpio.h" |
||||
#include "udp.h" |
||||
#include <stdio.h> |
||||
#include <unistd.h> /* interface to sleep */ |
||||
|
||||
static void BlinkLed(Gpio_Pins_e led) { |
||||
Gpio_WritePin(led, true); /* on */ |
||||
usleep(100*1000); |
||||
Gpio_WritePin(led, false); /* off */ |
||||
} |
||||
|
||||
int ROBONAV_test(void) { |
||||
bool buttonPressed; |
||||
|
||||
printf("Use NAV switch on HAT, <CENTER> for > 1 sec to exit.\n"); |
||||
for(;;) { |
||||
buttonPressed = false; |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_LEFT_BCM)==0) { |
||||
printf("left\n"); |
||||
printf("green\n"); BlinkLed(Gpio_Pins_LED_GREEN_PIN_BCM); |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_RIGHT_BCM)==0) { |
||||
printf("right\n"); |
||||
printf("red\n"); BlinkLed(Gpio_Pins_LED_RED_PIN_BCM); |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_UP_BCM)==0) { |
||||
printf("up\n"); |
||||
printf("blue\n"); BlinkLed(Gpio_Pins_LED_BLUE_PIN_BCM); |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_DOWN_BCM)==0) { |
||||
printf("down\n"); |
||||
printf("yellow\n"); BlinkLed(Gpio_Pins_LED_YELLOW_PIN_BCM); |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_CENTER_BCM)==0) { |
||||
printf("center\n"); |
||||
|
||||
int timeMs = 0; |
||||
while(Gpio_ReadPin(Gpio_Pins_BTN_CENTER_BCM)==0 && timeMs <= 1000) { |
||||
usleep(100*1000); |
||||
timeMs += 100; |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_CENTER_BCM)==0) { /* still pressed? */ |
||||
printf("exit program\n"); |
||||
break; /* break for loop and return */ |
||||
} |
||||
} |
||||
usleep(100*1000); |
||||
} /* for */ |
||||
return 0; /* ok */ |
||||
} |
||||
|
||||
int ROBONAV_navigate(const char *server, int port) { |
||||
unsigned int buttons; /* mask of buttons pressed */ |
||||
#define BTN_LEFT_MASK (1<<0) |
||||
#define BTN_RIGHT_MASK (1<<1) |
||||
#define BTN_UP_MASK (1<<2) |
||||
#define BTN_DOWN_MASK (1<<3) |
||||
#define BTN_CENTER_MASK (1<<4) |
||||
#define ALL_BUTTONS_MASK (BTN_LEFT_MASK|BTN_RIGHT_MASK|BTN_UP_MASK|BTN_DOWN_MASK|BTN_CENTER_MASK) |
||||
|
||||
printf("Use NAV switch on HAT to navigate robot, <CENTER> for > 1 sec to exit.\n"); |
||||
udp_send(server, port, "test"); |
||||
for(;;) { |
||||
buttons = 0; |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_LEFT_BCM)==0) { |
||||
buttons |= BTN_LEFT_MASK; |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_RIGHT_BCM)==0) { |
||||
buttons |= BTN_RIGHT_MASK; |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_UP_BCM)==0) { |
||||
buttons |= BTN_UP_MASK; |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_DOWN_BCM)==0) { |
||||
buttons |= BTN_DOWN_MASK; |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_CENTER_BCM)==0) { |
||||
buttons |= BTN_CENTER_MASK; |
||||
} |
||||
if ((buttons&ALL_BUTTONS_MASK)==(BTN_LEFT_MASK|BTN_UP_MASK)) { |
||||
printf("left-up\n"); |
||||
udp_send(server, port, "nav_left-up"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==BTN_LEFT_MASK) { |
||||
printf("left\n"); |
||||
udp_send(server, port, "nav_left"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==(BTN_RIGHT_MASK|BTN_UP_MASK)) { |
||||
printf("right-up\n"); |
||||
udp_send(server, port, "nav_right-up"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==BTN_RIGHT_MASK) { |
||||
printf("right\n"); |
||||
udp_send(server, port, "nav_right"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==BTN_UP_MASK) { |
||||
printf("up\n"); |
||||
udp_send(server, port, "nav_up"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==(BTN_LEFT_MASK|BTN_DOWN_MASK)) { |
||||
printf("left-down\n"); |
||||
udp_send(server, port, "nav_down"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==(BTN_RIGHT_MASK|BTN_DOWN_MASK)) { |
||||
printf("left-down\n"); |
||||
udp_send(server, port, "nav_down"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==BTN_DOWN_MASK) { |
||||
printf("down\n"); |
||||
udp_send(server, port, "nav_down"); |
||||
} else if ((buttons&ALL_BUTTONS_MASK)==BTN_CENTER_MASK) { |
||||
printf("center\n"); |
||||
udp_send(server, port, "nav_center"); |
||||
|
||||
int timeMs = 0; |
||||
while(Gpio_ReadPin(Gpio_Pins_BTN_CENTER_BCM)==0 && timeMs <= 1000) { |
||||
usleep(100*1000); |
||||
timeMs += 100; |
||||
} |
||||
if (Gpio_ReadPin(Gpio_Pins_BTN_CENTER_BCM)==0) { /* still pressed? */ |
||||
printf("exit program\n"); |
||||
udp_send(server, port, "exit"); |
||||
break; /* break for loop and return */ |
||||
} |
||||
} |
||||
usleep(100*1000); |
||||
} /* for */ |
||||
return 0; |
||||
} |
||||
|
||||
int ROBONAV_Init(void) { |
||||
return 0; /* ok */ |
||||
} |
||||
|
||||
int ROBONAV_Deinit(void) { |
||||
return 0; /* ok */ |
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
/*
|
||||
* robotNav.h |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#ifndef SRC_ROBOTNAV_H_ |
||||
#define SRC_ROBOTNAV_H_ |
||||
|
||||
int ROBONAV_navigate(const char *server, int port); |
||||
|
||||
int ROBONAV_test(void); |
||||
|
||||
int ROBONAV_Deinit(void); |
||||
|
||||
int ROBONAV_Init(void); |
||||
|
||||
#endif /* SRC_ROBOTNAV_H_ */ |
||||
@ -0,0 +1,148 @@ |
||||
/*
|
||||
* udp.c |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
* |
||||
* UDP example code by Paul Krzyzanowski, see |
||||
* https://www.cs.rutgers.edu/~pxk/417/notes/sockets/udp.html
|
||||
*/ |
||||
|
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <netdb.h> |
||||
#include <sys/socket.h> |
||||
#include <arpa/inet.h> |
||||
#include <unistd.h> |
||||
#include "hostname.h" |
||||
#include "platform.h" |
||||
|
||||
#define BUFSIZE (64) |
||||
|
||||
int udp_receive(int port) { |
||||
struct sockaddr_in myaddr; /* our address */ |
||||
struct sockaddr_in remaddr; /* remote address */ |
||||
socklen_t addrlen = sizeof(remaddr); /* length of addresses */ |
||||
int recvlen; /* # bytes received */ |
||||
int fd; /* our socket */ |
||||
int msgcnt = 0; /* count # of messages we received */ |
||||
static unsigned char buf[BUFSIZE]; /* receive buffer */ |
||||
|
||||
/* create a UDP socket */ |
||||
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { |
||||
perror("cannot create socket\n"); |
||||
return 0; |
||||
} |
||||
|
||||
/* bind the socket to any valid IP address and a specific port */ |
||||
memset((char *)&myaddr, 0, sizeof(myaddr)); |
||||
myaddr.sin_family = AF_INET; |
||||
myaddr.sin_addr.s_addr = htonl(INADDR_ANY); |
||||
myaddr.sin_port = htons(port); |
||||
|
||||
if (bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) { |
||||
perror("bind failed"); |
||||
return 0; |
||||
} |
||||
|
||||
/* now loop, receiving data and printing what we received */ |
||||
for (;;) { |
||||
printf("waiting on port %d\n", port); |
||||
recvlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&remaddr, &addrlen); |
||||
if (recvlen > 0) { |
||||
buf[recvlen] = 0; |
||||
printf("received message: \"%s\" (%d bytes)\n", buf, recvlen); |
||||
} else { |
||||
printf("uh oh - something went wrong!\n"); |
||||
} |
||||
sprintf((char*)buf, "ack %d", msgcnt++); |
||||
printf("sending response \"%s\"\n", buf); |
||||
if (sendto(fd, buf, strlen((char*)buf), 0, (struct sockaddr *)&remaddr, addrlen) < 0) { |
||||
perror("sendto"); |
||||
} |
||||
} |
||||
/* never exits */ |
||||
} |
||||
|
||||
|
||||
/* send a UDP datagram. Parameters are strings, e.g.
|
||||
* host IP, e.g. "10.180.254.51" |
||||
* port number, e.g. "1234" |
||||
* msg message string to send, e.g. "hello" |
||||
* \return negative value in case of failure |
||||
*/ |
||||
int udp_send(const char *host, int port, const char *msg) { |
||||
struct sockaddr_in myaddr, remaddr; |
||||
int fd, i; |
||||
socklen_t slen=sizeof(remaddr); |
||||
static char buf[BUFSIZE]; /* message buffer */ |
||||
int recvlen; /* # bytes in acknowledgement message */ |
||||
int portNumber; |
||||
char ipBuf[64]; /* in case 'host' is a hostname and not an IP address, this will hold the IP address */ |
||||
|
||||
/* hostname to IP transformation */ |
||||
if (*host >='0' && *host<='9') { /* IP address */ |
||||
printf("IP: %s\n", host); |
||||
} else { |
||||
if (hostname_to_ip(host, ipBuf, sizeof(ipBuf))==0) { |
||||
printf("hostname '%s' -> IP: '%s'\n", host, ipBuf); |
||||
host = ipBuf; /* point to transformed IP address */ |
||||
} else { |
||||
perror("hostname_to_ip() failed"); |
||||
return -1; |
||||
} |
||||
} |
||||
|
||||
/* create a socket */ |
||||
if ((fd=socket(AF_INET, SOCK_DGRAM, 0))==-1) { |
||||
printf("socket created\n"); |
||||
} |
||||
|
||||
/* bind it to all local addresses and pick any port number */ |
||||
memset((char *)&myaddr, 0, sizeof(myaddr)); |
||||
myaddr.sin_family = AF_INET; |
||||
myaddr.sin_addr.s_addr = htonl(INADDR_ANY); |
||||
myaddr.sin_port = htons(0); |
||||
|
||||
if (bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) { |
||||
perror("bind failed"); |
||||
return -1; |
||||
} |
||||
|
||||
/* now define remaddr, the address to whom we want to send messages */ |
||||
/* For convenience, the host address is expressed as a numeric IP address */ |
||||
/* that we will convert to a binary format via inet_aton */ |
||||
memset((char *) &remaddr, 0, sizeof(remaddr)); |
||||
remaddr.sin_family = AF_INET; |
||||
remaddr.sin_port = htons(port); |
||||
if (inet_aton(host, &remaddr.sin_addr)==0) { |
||||
fprintf(stderr, "inet_aton() failed\n"); |
||||
exit(1); |
||||
} |
||||
|
||||
/* set socket timeout */ |
||||
struct timeval tv; |
||||
tv.tv_sec = 5; |
||||
tv.tv_usec = 0; |
||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,&tv,sizeof(tv)) < 0) { |
||||
perror("Error"); |
||||
} |
||||
/* now let's send the messages */ |
||||
printf("Sending datagram '%s' to '%s' on port %d\n", msg, host, port); |
||||
if (sendto(fd, msg, strlen(msg), 0, (struct sockaddr *)&remaddr, slen)==-1) { |
||||
perror("sendto"); |
||||
exit(1); |
||||
} |
||||
|
||||
/* now receive an acknowledgment from the server */ |
||||
recvlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&remaddr, &slen); |
||||
if (recvlen >= 0) { |
||||
buf[recvlen] = '\0'; /* expect a printable string - terminate it */ |
||||
printf("response: \n%s\n", buf); |
||||
} else { /* timeout */ |
||||
printf("socket receive timeout\n"); |
||||
} |
||||
close(fd); |
||||
return 0; /* ok */ |
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
/*
|
||||
* udp.h |
||||
* |
||||
* Author: Erich Styger |
||||
* License: PDX-License-Identifier: BSD-3-Clause |
||||
*/ |
||||
|
||||
#ifndef SRC_UDP_H_ |
||||
#define SRC_UDP_H_ |
||||
|
||||
void udp_receive(void); |
||||
|
||||
int udp_send(const char *server, int port, const char *msg); |
||||
|
||||
#endif /* SRC_UDP_H_ */ |
||||
Loading…
Reference in new issue