Merge branch 'master' of github.com:r0ket/r0ket
This commit is contained in:
commit
5f17ea9bcd
|
@ -8,7 +8,7 @@ OBJS = main.o
|
||||||
VPATH +=
|
VPATH +=
|
||||||
OBJS +=
|
OBJS +=
|
||||||
OBJS += basic/basic.o basic/reinvoke_isp.o basic/delayms.o basic/voltage.o
|
OBJS += basic/basic.o basic/reinvoke_isp.o basic/delayms.o basic/voltage.o
|
||||||
OBJS += basic/keyin.o basic/uuid.o
|
OBJS += basic/keyin.o basic/uuid.o basic/crc.o
|
||||||
LIBS += core/libcore.a lcd/liblcd.a applications/libapp.a filesystem/libfat.a usb/libusb.a funk/libfunk.a
|
LIBS += core/libcore.a lcd/liblcd.a applications/libapp.a filesystem/libfat.a usb/libusb.a funk/libfunk.a
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -104,5 +104,5 @@ $(OUTFILE).elf: $(OBJS) $(SYS_OBJS) $(LIBS) $(LPCFIX) $(LD_TEMP)
|
||||||
-@echo ""
|
-@echo ""
|
||||||
$(LPCFIX) -c $@
|
$(LPCFIX) -c $@
|
||||||
|
|
||||||
.PHONY: $(LD_TEMP) lcd/liblcd.a applications/libapp.a filesystem/libfat.a usb/libusb.a
|
.PHONY: $(LD_TEMP) lcd/liblcd.a applications/libapp.a filesystem/libfat.a usb/libusb.a funk/libfunk.a
|
||||||
|
|
||||||
|
|
|
@ -23,65 +23,78 @@ void f_status(void){
|
||||||
int dy=8;
|
int dy=8;
|
||||||
uint8_t buf[4];
|
uint8_t buf[4];
|
||||||
|
|
||||||
// Enable SPI correctly
|
|
||||||
sspInit(0, sspClockPolarity_Low, sspClockPhase_RisingEdge);
|
|
||||||
|
|
||||||
// Enable CS & CE pins
|
|
||||||
gpioSetDir(RB_SPI_NRF_CS, gpioDirection_Output);
|
|
||||||
gpioSetPullup(&RB_SPI_NRF_CS_IO, gpioPullupMode_Inactive);
|
|
||||||
gpioSetDir(3,2, gpioDirection_Output);
|
|
||||||
gpioSetPullup(&IOCON_PIO3_2, gpioPullupMode_Inactive);
|
|
||||||
CS_HIGH();
|
|
||||||
|
|
||||||
gpioSetDir(RB_NRF_CE, gpioDirection_Output);
|
|
||||||
gpioSetPullup(&RB_NRF_CE_IO, gpioPullupMode_Inactive);
|
|
||||||
gpioSetValue(RB_NRF_CE, 0);
|
|
||||||
|
|
||||||
delayms(10);
|
|
||||||
|
|
||||||
buf[0]=C_W_REGISTER | R_CONFIG;
|
|
||||||
buf[1]=R_CONFIG_PRIM_RX| R_CONFIG_PWR_UP| R_CONFIG_CRCO;
|
|
||||||
// buf[0]=C_R_REGISTER | 5; buf[1]=10;
|
|
||||||
// buf[0]=C_W_REGISTER | R_EN_AA; buf[1]=0x21;
|
|
||||||
buf[2]=0;
|
|
||||||
buf[3]=0;
|
|
||||||
|
|
||||||
dx=DoString(0,dy,"Snd:"); DoIntX(dx,dy,*(int*)buf);dy+=8;
|
|
||||||
|
|
||||||
CS_LOW();
|
|
||||||
delayms(10);
|
|
||||||
sspSendReceive(0, buf, 2);
|
|
||||||
//sspReceive(0, buf, 2);
|
|
||||||
CS_HIGH();
|
|
||||||
delayms(10);
|
|
||||||
|
|
||||||
dx=DoString(0,dy,"Rcv:"); DoIntX(dx,dy,*(int*)buf);dy+=8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
CS_LOW();
|
|
||||||
status=nrf_cmd_status(C_NOP);
|
|
||||||
CS_HIGH();
|
|
||||||
dx=DoString(0,dy,"St:"); DoIntX(dx,dy,status); dy+=8;
|
|
||||||
*/
|
|
||||||
|
|
||||||
buf[0]=C_R_REGISTER | R_CONFIG;
|
buf[0]=C_R_REGISTER | R_CONFIG;
|
||||||
// buf[0]=C_R_REGISTER | R_EN_AA;
|
|
||||||
buf[1]=0;
|
buf[1]=0;
|
||||||
buf[2]=0;
|
buf[2]=0;
|
||||||
buf[3]=0;
|
buf[3]=0;
|
||||||
dx=DoString(0,dy,"S2:"); DoIntX(dx,dy,*(int*)buf);dy+=8;
|
dx=DoString(0,dy,"S:"); DoIntX(dx,dy,*(int*)buf);dy+=8;
|
||||||
|
nrf_cmd_rw_long(buf,2);
|
||||||
|
dx=DoString(0,dy,"R:"); DoIntX(dx,dy,*(int*)buf);dy+=8;
|
||||||
|
|
||||||
CS_LOW();
|
int status=nrf_cmd_status(C_NOP);
|
||||||
delayms(10);
|
dx=DoString(0,dy,"St:"); DoIntX(dx,dy,status);dy+=8;
|
||||||
sspSendReceive(0, buf, 2);
|
|
||||||
//sspReceive(0, buf, 2);
|
|
||||||
CS_HIGH();
|
|
||||||
delayms(10);
|
|
||||||
|
|
||||||
dx=DoString(0,dy,"R2:"); DoIntX(dx,dy,*(int*)buf);dy+=8;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void f_recv(void){
|
void f_recv(void){
|
||||||
|
int dx=0;
|
||||||
|
int dy=8;
|
||||||
|
uint8_t buf[32];
|
||||||
|
int len;
|
||||||
|
|
||||||
|
len=nrf_rcv_pkt_time(500,sizeof(buf),buf);
|
||||||
|
|
||||||
|
if(len==0){
|
||||||
|
dx=DoString(0,dy,"No pkt"); dy+=8;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if(len<0){
|
||||||
|
dx=DoString(0,dy,"Pkt too lg"); dy+=8;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
dx=DoString(0,dy,"Size:"); DoInt(dx,dy,len); dy+=8;
|
||||||
|
dx=DoString(0,dy,"1:"); DoIntX(dx,dy,*(int*)(buf));dy+=8;
|
||||||
|
dx=DoString(0,dy,"2:"); DoIntX(dx,dy,*(int*)(buf+4));dy+=8;
|
||||||
|
dx=DoString(0,dy,"3:"); DoIntX(dx,dy,*(int*)(buf+8));dy+=8;
|
||||||
|
dx=DoString(0,dy,"4:"); DoIntX(dx,dy,*(int*)(buf+12));dy+=8;
|
||||||
|
|
||||||
|
len=crc16(buf,14);
|
||||||
|
dx=DoString(0,dy,"c:"); DoIntX(dx,dy,len);dy+=8;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
void f_send(void){
|
||||||
|
static char ctr=1;
|
||||||
|
int dx=0;
|
||||||
|
int dy=8;
|
||||||
|
uint8_t buf[32];
|
||||||
|
int status;
|
||||||
|
int crc;
|
||||||
|
|
||||||
|
buf[0]=0x05; // ID
|
||||||
|
buf[1]=0xEC; // ID
|
||||||
|
buf[2]=0xff;
|
||||||
|
buf[3]=0xff; // Send intensity
|
||||||
|
|
||||||
|
buf[4]=0x00; // ctr
|
||||||
|
buf[5]=0x00; // ctr
|
||||||
|
buf[6]=0x00; // ctr
|
||||||
|
buf[7]=ctr++; // ctr
|
||||||
|
|
||||||
|
buf[8]=0xff;
|
||||||
|
buf[9]=0xff;
|
||||||
|
buf[10]=0xff;
|
||||||
|
buf[11]=0xff;
|
||||||
|
buf[12]=0xff;
|
||||||
|
buf[13]=0xff;
|
||||||
|
|
||||||
|
crc=crc16(buf,14);
|
||||||
|
buf[14]=crc & 0xff; // CRC
|
||||||
|
buf[15]=(crc >>8) & 0xff; // CRC
|
||||||
|
|
||||||
|
status=nrf_snd_pkt_crc(16,buf);
|
||||||
|
|
||||||
|
dx=DoString(0,dy,"St:"); DoInt(dx,dy,status); dy+=8;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void gotoISP(void) {
|
void gotoISP(void) {
|
||||||
|
@ -108,12 +121,14 @@ const struct MENU_DEF menu_ISP = {"Invoke ISP", &gotoISP};
|
||||||
const struct MENU_DEF menu_init = {"F Init", &f_init};
|
const struct MENU_DEF menu_init = {"F Init", &f_init};
|
||||||
const struct MENU_DEF menu_status = {"F Status", &f_status};
|
const struct MENU_DEF menu_status = {"F Status", &f_status};
|
||||||
const struct MENU_DEF menu_rcv = {"F Recv", &f_recv};
|
const struct MENU_DEF menu_rcv = {"F Recv", &f_recv};
|
||||||
|
const struct MENU_DEF menu_snd = {"F Send", &f_send};
|
||||||
const struct MENU_DEF menu_nop = {"---", NULL};
|
const struct MENU_DEF menu_nop = {"---", NULL};
|
||||||
|
|
||||||
static menuentry menu[] = {
|
static menuentry menu[] = {
|
||||||
&menu_status,
|
|
||||||
&menu_init,
|
&menu_init,
|
||||||
|
&menu_status,
|
||||||
&menu_rcv,
|
&menu_rcv,
|
||||||
|
&menu_snd,
|
||||||
&menu_nop,
|
&menu_nop,
|
||||||
&menu_ISP,
|
&menu_ISP,
|
||||||
NULL,
|
NULL,
|
||||||
|
@ -236,3 +251,4 @@ void tick_funk(void){
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -152,4 +152,8 @@ uint16_t GetUUID16(void);
|
||||||
// for core/iap/iap.c (no official definition)
|
// for core/iap/iap.c (no official definition)
|
||||||
void iap_entry(uint32_t param_tab[], uint32_t result_tab[]);
|
void iap_entry(uint32_t param_tab[], uint32_t result_tab[]);
|
||||||
|
|
||||||
|
// crc.c
|
||||||
|
uint16_t crc16(char * buf, int len);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
#include "basic.h"
|
||||||
|
|
||||||
|
// Calculate the CRC for transmitted and received data using
|
||||||
|
// the CCITT 16bit algorithm (X^16 + X^12 + X^5 + 1).
|
||||||
|
|
||||||
|
uint16_t crc16(char * buf, int len){
|
||||||
|
unsigned int crc=0xffff;
|
||||||
|
|
||||||
|
for(int i=0;i<len;i++){
|
||||||
|
crc = (unsigned char)(crc >> 8) | (crc << 8);
|
||||||
|
crc ^= buf[i];
|
||||||
|
crc ^= (unsigned char)(crc & 0xff) >> 4;
|
||||||
|
crc ^= (crc << 8) << 4;
|
||||||
|
crc ^= ((crc & 0xff) << 4) << 1;
|
||||||
|
};
|
||||||
|
return crc;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Note:
|
||||||
|
It is best not to alter this code. For example, (crc<<8)<<4 does
|
||||||
|
not generate the same code as crc<<12. Although the result of the
|
||||||
|
computation is the same, the latter generates much more code and
|
||||||
|
executes slower.
|
||||||
|
*/
|
|
@ -32,5 +32,5 @@ $(LIBFILE): $(OBJS)
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(LIBFILE)
|
rm -f $(OBJS) $(LIBFILE)
|
||||||
|
|
||||||
nrf24l01p.o: nrf24l01p.h
|
nrf24l01p.o: nrf24l01p.c nrf24l01p.h
|
||||||
|
|
||||||
|
|
|
@ -7,71 +7,63 @@
|
||||||
#define MAC_BEACON "BEACO"
|
#define MAC_BEACON "BEACO"
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------*/
|
||||||
/* Transmit a byte via SPI (Platform dependent) */
|
/* Transmit a byte via SPI */
|
||||||
/*-----------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------*/
|
||||||
void xmit_spi(uint8_t dat) {
|
inline void xmit_spi(uint8_t dat) {
|
||||||
sspSend(0, (uint8_t*) &dat, 1);
|
sspSend(0, (uint8_t*) &dat, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------*/
|
|
||||||
/* Receive a byte from MMC via SPI (Platform dependent) */
|
|
||||||
/*-----------------------------------------------------------------------*/
|
|
||||||
uint8_t rcvr_spi (void) {
|
|
||||||
uint8_t data = 0;
|
|
||||||
|
|
||||||
sspReceive(0, &data, 1);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define rcvr_spi_m(dst) \
|
|
||||||
do { \
|
|
||||||
sspReceive(0, (uint8_t*)(dst), 1); \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define CS_LOW() gpioSetValue(RB_SPI_NRF_CS, 0)
|
#define CS_LOW() gpioSetValue(RB_SPI_NRF_CS, 0)
|
||||||
#define CS_HIGH() gpioSetValue(RB_SPI_NRF_CS, 1)
|
#define CS_HIGH() gpioSetValue(RB_SPI_NRF_CS, 1)
|
||||||
#define CE_LOW() gpioSetValue(RB_NRF_CE, 0)
|
#define CE_LOW() gpioSetValue(RB_NRF_CE, 0)
|
||||||
#define CE_HIGH() gpioSetValue(RB_NRF_CE, 1)
|
#define CE_HIGH() gpioSetValue(RB_NRF_CE, 1)
|
||||||
|
|
||||||
void nrf_cmd(uint8_t cmd){
|
void nrf_cmd(uint8_t cmd){
|
||||||
|
CS_LOW();
|
||||||
xmit_spi(cmd);
|
xmit_spi(cmd);
|
||||||
|
CS_HIGH();
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t nrf_cmd_status(uint8_t cmd){
|
uint8_t nrf_cmd_status(uint8_t cmd){
|
||||||
|
CS_LOW();
|
||||||
sspSendReceive(0, &cmd, 1);
|
sspSendReceive(0, &cmd, 1);
|
||||||
return cmd;
|
return cmd;
|
||||||
|
CS_HIGH();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void nrf_cmd_rw_long(uint8_t* data, int len){
|
||||||
|
CS_LOW();
|
||||||
|
sspSendReceive(0,data,len);
|
||||||
|
CS_HIGH();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
void nrf_write_reg(const uint8_t reg, const uint8_t val){
|
void nrf_write_reg(const uint8_t reg, const uint8_t val){
|
||||||
|
CS_LOW();
|
||||||
xmit_spi(C_W_REGISTER | reg);
|
xmit_spi(C_W_REGISTER | reg);
|
||||||
xmit_spi(val);
|
xmit_spi(val);
|
||||||
|
CS_HIGH();
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
void nrf_read_long(const uint8_t cmd, int len, uint8_t* data){
|
||||||
uint8_t nrf_read_reg(const uint8_t reg, uint8_t val){
|
CS_LOW();
|
||||||
xmit_spi(C_R_REGISTER | reg);
|
xmit_spi(cmd);
|
||||||
// do i need to read the status byte here?
|
for(int i=0;i<len;i++)
|
||||||
xmit_spi(val);
|
|
||||||
return rcvr_spi();
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
void nrf_write_reg_long(const uint8_t reg, int len, char* data){
|
|
||||||
xmit_spi(C_W_REGISTER | reg);
|
|
||||||
for(int i=0;i<len;i++){
|
|
||||||
xmit_spi(data[i]);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//XXX: Why is len a pointer?
|
|
||||||
void nrf_cmd_read_long(const uint8_t cmd, int *len, char* data){
|
|
||||||
data[0] = 0xFF;
|
|
||||||
for(int i=1;i<*len;i++)
|
|
||||||
data[i] = 0x00;
|
data[i] = 0x00;
|
||||||
sspSendReceive(0,data,*len);
|
sspSendReceive(0,data,len);
|
||||||
|
CS_HIGH();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void nrf_write_long(const uint8_t cmd, int len, uint8_t* data){
|
||||||
|
CS_LOW();
|
||||||
|
xmit_spi(cmd);
|
||||||
|
sspSend(0,data,len);
|
||||||
|
CS_HIGH();
|
||||||
|
};
|
||||||
|
|
||||||
|
#define nrf_write_reg_long(reg, len, data) \
|
||||||
|
nrf_write_long(C_W_REGISTER|reg, len, data)
|
||||||
|
|
||||||
void nrf_init() {
|
void nrf_init() {
|
||||||
// Enable SPI correctly
|
// Enable SPI correctly
|
||||||
sspInit(0, sspClockPolarity_Low, sspClockPhase_RisingEdge);
|
sspInit(0, sspClockPolarity_Low, sspClockPhase_RisingEdge);
|
||||||
|
@ -101,26 +93,24 @@ void nrf_init() {
|
||||||
);
|
);
|
||||||
|
|
||||||
nrf_write_reg(R_RX_PW_P0,16);
|
nrf_write_reg(R_RX_PW_P0,16);
|
||||||
nrf_write_reg_long(R_RX_ADDR_P0,5,"\x1\x2\x3\x2\1");
|
nrf_write_reg_long(R_RX_ADDR_P0,5,(uint8_t*)"\x1\x2\x3\x2\1");
|
||||||
|
|
||||||
// nrf_write_reg(R_RX_PW_P1,16);
|
// nrf_write_reg(R_RX_PW_P1,16);
|
||||||
// nrf_write_reg_long(R_RX_ADDR_P1,5,"R0KET");
|
// nrf_write_reg_long(R_RX_ADDR_P1,5,"R0KET");
|
||||||
|
|
||||||
// OpenBeacon transmit address
|
// OpenBeacon transmit address
|
||||||
nrf_write_reg_long(R_TX_ADDR,5,MAC_BEACON);
|
nrf_write_reg_long(R_TX_ADDR,5,(uint8_t*)MAC_BEACON);
|
||||||
|
|
||||||
// Set speed / strength
|
// Set speed / strength
|
||||||
nrf_write_reg(R_RF_SETUP,DEFAULT_SPEED|R_RF_SETUP_RF_PWR_3);
|
nrf_write_reg(R_RF_SETUP,DEFAULT_SPEED|R_RF_SETUP_RF_PWR_3);
|
||||||
|
|
||||||
// XXX: or write R_CONFIG last?
|
// XXX: or write R_CONFIG last?
|
||||||
CS_HIGH();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int nrf_rcv_pkt_time(int maxtime, int maxsize, char * pkt){
|
int nrf_rcv_pkt_time(int maxtime, int maxsize, uint8_t * pkt){
|
||||||
char buf;
|
char buf;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
CS_LOW();
|
|
||||||
nrf_write_reg(R_CONFIG,
|
nrf_write_reg(R_CONFIG,
|
||||||
R_CONFIG_PRIM_RX| // Receive mode
|
R_CONFIG_PRIM_RX| // Receive mode
|
||||||
R_CONFIG_PWR_UP| // Power on
|
R_CONFIG_PWR_UP| // Power on
|
||||||
|
@ -130,7 +120,7 @@ int nrf_rcv_pkt_time(int maxtime, int maxsize, char * pkt){
|
||||||
delayms(maxtime); // XXX: check interrupt?
|
delayms(maxtime); // XXX: check interrupt?
|
||||||
CE_LOW();
|
CE_LOW();
|
||||||
len=1;
|
len=1;
|
||||||
nrf_cmd_read_long(C_R_RX_PL_WID,&len,&buf);
|
nrf_read_long(C_R_RX_PL_WID,len,&buf);
|
||||||
len=buf;
|
len=buf;
|
||||||
if(len>32 || len==0){
|
if(len>32 || len==0){
|
||||||
return 0; // no packet
|
return 0; // no packet
|
||||||
|
@ -138,7 +128,23 @@ int nrf_rcv_pkt_time(int maxtime, int maxsize, char * pkt){
|
||||||
if(len>maxsize){
|
if(len>maxsize){
|
||||||
return -1; // packet too large
|
return -1; // packet too large
|
||||||
};
|
};
|
||||||
nrf_cmd_read_long(C_R_RX_PAYLOAD,&len,pkt);
|
nrf_read_long(C_R_RX_PAYLOAD,len,pkt);
|
||||||
CS_HIGH();
|
CS_HIGH();
|
||||||
return len;
|
return len;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
char nrf_snd_pkt_crc(int size, uint8_t * pkt){
|
||||||
|
|
||||||
|
nrf_write_reg(R_CONFIG,
|
||||||
|
R_CONFIG_PWR_UP| // Power on
|
||||||
|
R_CONFIG_CRCO // 2-byte CRC
|
||||||
|
);
|
||||||
|
|
||||||
|
nrf_write_long(C_W_TX_PAYLOAD,size,pkt);
|
||||||
|
|
||||||
|
CE_HIGH();
|
||||||
|
delayms(10); // Send it. (only needs >10ys, i think)
|
||||||
|
CE_LOW();
|
||||||
|
|
||||||
|
return nrf_cmd_status(C_NOP);
|
||||||
|
};
|
||||||
|
|
|
@ -93,13 +93,15 @@
|
||||||
#define R_RF_SETUP_DR_250K 0x20
|
#define R_RF_SETUP_DR_250K 0x20
|
||||||
|
|
||||||
/* exported functions */
|
/* exported functions */
|
||||||
int nrf_rcv_pkt_time(int maxtime, int maxsize, char * pkt);
|
int nrf_rcv_pkt_time(int maxtime, int maxsize, uint8_t * pkt);
|
||||||
void nrf_init() ;
|
void nrf_init() ;
|
||||||
void nrf_cmd_read_long(const uint8_t cmd, int *len, char* data);
|
|
||||||
void nrf_write_reg_long(const uint8_t reg, int len, char* data);
|
|
||||||
void nrf_write_reg(const uint8_t reg, const uint8_t val);
|
|
||||||
uint8_t nrf_cmd_status(uint8_t cmd);
|
|
||||||
void nrf_cmd(uint8_t cmd);
|
void nrf_cmd(uint8_t cmd);
|
||||||
|
uint8_t nrf_cmd_status(uint8_t cmd);
|
||||||
|
void nrf_cmd_rw_long(uint8_t* data, int len);
|
||||||
|
void nrf_read_long(const uint8_t reg, int len, uint8_t* data);
|
||||||
|
void nrf_write_reg(const uint8_t reg, const uint8_t val);
|
||||||
|
void nrf_write_reg_long(const uint8_t reg, int len, uint8_t* data);
|
||||||
|
|
||||||
/* END */
|
/* END */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue