diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..3eaf150 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +CC=gcc +CFLAGS=-c -Wall + +all: wh1080_rf + +wh1080_rf: wh1080_rf.o bcm2835.o bmp085.o + $(CC) -lm wh1080_rf.o bcm2835.o bmp085.o -o wh1080_rf + +wh1080_rf.o: wh1080_rf.c + $(CC) $(CFLAGS) wh1080_rf.c + +bcm2835.o: bcm2835.c + $(CC) $(CFLAGS) bcm2835.c + +bmp085.o: bmp085.c + $(CC) $(CFLAGS) bmp085.c + +clean: + rm -f wh1080_rf.o bcm2835.o bmp085.o wh1080_rf \ No newline at end of file diff --git a/bcm2835.c b/bcm2835.c new file mode 100755 index 0000000..aeebab6 --- /dev/null +++ b/bcm2835.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include + +#include "bcm2835.h" + +struct bcm2835_peripheral gpio = {GPIO_BASE, 0}; +struct bcm2835_peripheral bsc0 = {BSC0_BASE, 0}; +struct bcm2835_peripheral timer_arm = {TIMER_ARM_BASE, 0}; + +// Exposes the physical address defined in the passed structure using mmap on /dev/mem +int map_peripheral(struct bcm2835_peripheral *p) +{ + if(p->init_count > 0) { + p->init_count++; + return 0; + } + + // Open /dev/mem + if ((p->mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { + printf("Failed to open /dev/mem, try checking permissions.\n"); + return -1; + } + + p->map = mmap( + NULL, + BLOCK_SIZE, + PROT_READ|PROT_WRITE, + MAP_SHARED, + p->mem_fd, // File descriptor to physical memory virtual file '/dev/mem' + p->addr_p // Address in physical map that we want this memory block to expose + ); + + if (p->map == MAP_FAILED) { + perror("mmap"); + return -1; + } + + p->addr = (volatile unsigned int *)p->map; + p->init_count++; + + return 0; +} + + +void unmap_peripheral(struct bcm2835_peripheral *p) { + + p->init_count--; + if(p->init_count == 0) { + munmap(p->map, BLOCK_SIZE); + close(p->mem_fd); + } +} + +// Function to wait for the I2C transaction to complete +void wait_i2c_done() { + + while((!((BSC0_S) & BSC_S_DONE))) { + usleep(100); + } +} + +// Function to write data to an I2C device via the FIFO. This doesn't refill the FIFO, so writes are limited to 16 bytes +// including the register address. len specifies the number of bytes in the buffer. +void i2c_write(char dev_addr, char reg_addr, char *buf, unsigned short len) { + + int idx; + + BSC0_A = dev_addr; + BSC0_DLEN = len + 1; // one byte for the register address, plus the buffer length + + BSC0_FIFO = reg_addr; // start register address + for(idx=0; idx < len; idx++) + BSC0_FIFO = buf[idx]; + + BSC0_S = CLEAR_STATUS; // Reset status bits (see #define) + BSC0_C = START_WRITE; // Start Write (see #define) + + wait_i2c_done(); + +} + +// Function to read a number of bytes into a buffer from the FIFO of the I2C controller +void i2c_read(char dev_addr, char reg_addr, char *buf, unsigned short len) { + + i2c_write(dev_addr, reg_addr, NULL, 0); + + unsigned short bufidx = 0; + + memset(buf, 0, len); // clear the buffer + + BSC0_DLEN = len; + BSC0_S = CLEAR_STATUS; // Reset status bits (see #define) + BSC0_C = START_READ; // Start Read after clearing FIFO (see #define) + + do { + // Wait for some data to appear in the FIFO + while((BSC0_S & BSC_S_TA) && !(BSC0_S & BSC_S_RXD)); + + // Consume the FIFO + while((BSC0_S & BSC_S_RXD) && (bufidx < len)) { + buf[bufidx++] = BSC0_FIFO; + } + } while((!(BSC0_S & BSC_S_DONE))); +} + +void dump_bsc_status() { + + unsigned int s = BSC0_S; + + printf("BSC0_S: ERR=%d RXF=%d TXE=%d RXD=%d TXD=%d RXR=%d TXW=%d DONE=%d TA=%d\n", + (s & BSC_S_ERR) != 0, + (s & BSC_S_RXF) != 0, + (s & BSC_S_TXE) != 0, + (s & BSC_S_RXD) != 0, + (s & BSC_S_TXD) != 0, + (s & BSC_S_RXR) != 0, + (s & BSC_S_TXW) != 0, + (s & BSC_S_DONE) != 0, + (s & BSC_S_TA) != 0 ); +} diff --git a/bcm2835.h b/bcm2835.h new file mode 100755 index 0000000..3de837f --- /dev/null +++ b/bcm2835.h @@ -0,0 +1,96 @@ +#define PAGESIZE 4096 +#define BLOCK_SIZE 4096 + +#define IOBASE 0x20000000 + +#define GPIO_BASE (IOBASE + 0x200000) +#define BSC0_BASE (IOBASE + 0x205000) +#define TIMER_ARM_BASE (IOBASE + 0x00B000) + + +/* + * Defines for ARM Timer peripheral + */ +#define TIMER_ARM_LOAD *(timer_arm.addr + 0x100) +#define TIMER_ARM_VALUE *(timer_arm.addr + 0x101) +#define TIMER_ARM_CONTROL *(timer_arm.addr + 0x102) +#define TIMER_ARM_IRQ_CLEAR *(timer_arm.addr + 0x103) +#define TIMER_ARM_IRQ_RAW *(timer_arm.addr + 0x104) +#define TIMER_ARM_IRQ_MASK *(timer_arm.addr + 0x105) +#define TIMER_ARM_RELOAD *(timer_arm.addr + 0x106) +#define TIMER_ARM_PREDIVIDE *(timer_arm.addr + 0x107) +#define TIMER_ARM_COUNT *(timer_arm.addr + 0x108) + +// Zero-shifts are here for code readability, where a zero-bit holds significant +// meaning other than just a negated state, or where the negated state is notable. + +#define TIMER_ARM_C_16BIT (0 << 1) +#define TIMER_ARM_C_23BIT (1 << 1) +#define TIMER_ARM_C_PS1 (0 << 2) +#define TIMER_ARM_C_PS16 (1 << 2) +#define TIMER_ARM_C_PS256 (2 << 2) +#define TIMER_ARM_C_PS1_1 (3 << 2) +#define TIMER_ARM_C_INTEN (1 << 5) +#define TIMER_ARM_C_DISABLE (0 << 7) +#define TIMER_ARM_C_ENABLE (1 << 7) +#define TIMER_ARM_C_DBGHALT (1 << 8) +#define TIMER_ARM_C_FREE_EN (1 << 9) +#define TIMER_ARM_C_FPS(n) ((n & 0xff) << 16) +#define TIMER_ARM_C_FPS_MASK (0xff << 16) + + +/* + * Defines for I2C peripheral (aka BSC, or Broadcom Serial Controller) + */ +#define BSC0_C *(bsc0.addr + 0x00) +#define BSC0_S *(bsc0.addr + 0x01) +#define BSC0_DLEN *(bsc0.addr + 0x02) +#define BSC0_A *(bsc0.addr + 0x03) +#define BSC0_FIFO *(bsc0.addr + 0x04) + +#define BSC_C_I2CEN (1 << 15) +#define BSC_C_INTR (1 << 10) +#define BSC_C_INTT (1 << 9) +#define BSC_C_INTD (1 << 8) +#define BSC_C_ST (1 << 7) +#define BSC_C_CLEAR (1 << 4) +#define BSC_C_READ 1 + +#define START_READ BSC_C_I2CEN|BSC_C_ST|BSC_C_CLEAR|BSC_C_READ +#define START_WRITE BSC_C_I2CEN|BSC_C_ST + +#define BSC_S_CLKT (1 << 9) +#define BSC_S_ERR (1 << 8) +#define BSC_S_RXF (1 << 7) +#define BSC_S_TXE (1 << 6) +#define BSC_S_RXD (1 << 5) +#define BSC_S_TXD (1 << 4) +#define BSC_S_RXR (1 << 3) +#define BSC_S_TXW (1 << 2) +#define BSC_S_DONE (1 << 1) +#define BSC_S_TA 1 + +#define CLEAR_STATUS BSC_S_CLKT|BSC_S_ERR|BSC_S_DONE + + + +struct bcm2835_peripheral { + unsigned long addr_p; // Physical address + unsigned long init_count; + int mem_fd; // File Descriptor for /dev/mem + void *map; // The mmap() + volatile unsigned int *addr; +}; + +extern struct bcm2835_peripheral gpio; +extern struct bcm2835_peripheral bsc0; +extern struct bcm2835_peripheral timer_arm; + +extern void wait_i2c_done(); +extern void i2c_read(char dev_addr, char reg_addr, char *buf, unsigned short len); +extern void i2c_write(char dev_addr, char reg_addr, char *buf, unsigned short len); +extern void dump_bsc_status(); + +extern int map_peripheral(struct bcm2835_peripheral *p); +extern void unmap_peripheral(struct bcm2835_peripheral *p); + diff --git a/bmp085.c b/bmp085.c new file mode 100755 index 0000000..5941db5 --- /dev/null +++ b/bmp085.c @@ -0,0 +1,145 @@ +/* + * pcf8563_i2c_rtc.c - example of accessing a PCF8563 via the BSC0 (I2C) peripheral on a BCM2835 (Raspberry Pi) + * + * Copyright 2012 Kevin Sangeelee. + * Released as GPLv2, see + * + * This is intended as an example of using Raspberry Pi hardware registers to drive an RTC chip. Use at your own risk or + * not at all. As far as possible, I've omitted anything that doesn't relate to the RTC or the Raspi registers. There are more + * conventional ways of doing this using kernel drivers, though these are harder to follow. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "bcm2835.h" + + +//////////////// +// main() +//////////////// + +char eeprom[22] = { 0, 0, }; +short ac1, ac2, ac3, b1, b2, mb, mc, md; +unsigned short ac4, ac5, ac6; + +float temperature_deg_c; +float pressure_hpa; + +int read_bmp085(float altitude) { + + if(map_peripheral(&gpio) == -1) { + printf("Failed to map the physical GPIO registers into the virtual memory space.\n"); + return -1; + } + if(map_peripheral(&bsc0) == -1) { + printf("Failed to map the physical BSC0 (I2C) registers into the virtual memory space.\n"); + return -1; + } + + /* BSC0 is on GPIO 0 & 1 */ + *gpio.addr &= ~0x3f; // Mask out bits 0-5 of FSEL0 (i.e. force to zero) + *gpio.addr |= 0x24; // Set bits 0-5 of FSEL0 to binary '100100' + + // Read eeprom data if the array is empty + // I2C Device Address 0x77 (hardwired into the chip, 0xEE & 0xEF) + if((unsigned short)*eeprom == 0) { + + // Device 0x77, register 0xaa, read into buf, 22 bytes + i2c_read(0x77, 0xaa, eeprom, 22); + + ac1 = (short)eeprom[0] << 8 | eeprom[1]; + ac2 = (short)eeprom[2] << 8 | eeprom[3]; + ac3 = (short)eeprom[4] << 8 | eeprom[5]; + ac4 = (unsigned short)eeprom[6] << 8 | eeprom[7]; + ac5 = (unsigned short)eeprom[8] << 8 | eeprom[9]; + ac6 = (unsigned short)eeprom[10] << 8 | eeprom[11]; + b1 = (short)eeprom[12] << 8 | eeprom[13]; + b2 = (short)eeprom[14] << 8 | eeprom[15]; + mb = (short)eeprom[16] << 8 | eeprom[17]; + mc = (short)eeprom[18] << 8 | eeprom[19]; + md = (short)eeprom[20] << 8 | eeprom[21]; + + // Test values + //ac1 = 408, ac2 = -72, ac3 = -14383, ac4 = 32741, ac5 = 32757, ac6 = 23153; + //b1 = 6190, b2 = 4, mb = -32768, mc = -8711, md = 2868; + // Also include 'ut = 27898' , 'up = 23843', and 'oss = 0' + //printf("%d %d %d %d %d %d\n", ac1, ac2, ac3, ac4, ac5, ac6); + //printf("%d %d %d %d %d\n", b1, b2, mb, mc, md); + } + + + char ut_buf[2]; + char up_buf[2]; + + char cmd_ut = 0x2e; + char cmd_up[] = {0x34, 0x74, 0xb4, 0xf4}; + int oss_delay[] = {4500, 7500, 13500, 25500}; + int oss = 1; // This is an index into the above array + + /* + * Get Uncompensated Temperature from BMP085 + */ + i2c_write(0x77, 0xf4, &cmd_ut, 1); + usleep(4500); // just wait the maximum possible time for conversion + i2c_read(0x77, 0xf6, ut_buf, 2); + + long ut = (long)ut_buf[0] << 8 | ut_buf[1]; + + // Temperature compensation algorithm (derived from datasheet) + long x1 = ((ut - ac6) * ac5) >> 15; + long x2 = (mc * (1 << 11)) / (x1 + md); + long b5 = x1 + x2; + long t = (b5 + 8) >> 4; + + temperature_deg_c = (float)t / 10; + printf("Temperature: %0.1fC\n", temperature_deg_c); + + int idx; + float p0 = 0; + + for(idx=0; idx < 2; idx++) { + /* + * Get Uncompensated Pressure from BMP085, based on the OverSampling Setting + * of (0, 1, 2, or 3). This determines accuracy, conversion delay, and power consumption. + */ + i2c_write(0x77, 0xf4, &cmd_up[oss], 1); + usleep(oss_delay[oss]); // wait according to the chosen oss mode + i2c_read(0x77, 0xf6, up_buf, 3); + + long up = (((long)up_buf[0] << 16) | ((long)up_buf[1] << 8) | up_buf[2]) >> (8 - oss); + + // Pressure compensation algorithm (derived from datasheet) + long b6 = b5 - 4000; + x1 = (b2 * (b6 * b6 >> 12)) >> 11; + x2 = ac2 * b6 >> 11; + long x3 = x1 + x2; + long b3 = (((ac1 * 4 + x3) << oss) + 2) >> 2; + x1 = ac3 * b6 >> 13; + x2 = (b1 * (b6 * b6 >> 12)) >> 16; + x3 = ((x1 + x2) + 2) >> 2; + unsigned long b4 = ac4 * (unsigned long)(x3 + 32768) >>15; + unsigned long b7 = ((unsigned long)up - b3) * (50000 >> oss); + long p = b7 < 0x80000000 ? (b7 * 2) / b4 : (b7 / b4) * 2; + x1 = (p >> 8) * (p >> 8); + x1 = (x1 * 3038) >> 16; + x2 = (-7357 * p) >> 16; + p = p + (x1 + x2 + 3791) / 16; + + p0 += (float)p / powf(1.0f - (altitude / 44330), 5.255f); + + usleep(100000); + } + p0 /= 2; + pressure_hpa = p0 / 100; + printf("Pressure p0 (sea level): %0.1f hPa\n", pressure_hpa); + + unmap_peripheral(&gpio); + unmap_peripheral(&bsc0); + + return 0; +} diff --git a/rfm01.h b/rfm01.h new file mode 100755 index 0000000..e376100 --- /dev/null +++ b/rfm01.h @@ -0,0 +1,280 @@ +// Choose either RFM01 or RFM12B +#define RFM01 +//#define RFM12B + +#define CMD_STATUS 0x0000 +#define CMD_FREQ 0xa000 + +#ifdef RFM01 + + #define CMD_CONFIG 0x8000 + #define CMD_RCON 0xc000 + #define CMD_WAKEUP 0xe000 + #define CMD_LOWDUTY 0xcc00 + #define CMD_LOWBATT 0xc200 + #define CMD_AFC 0xc600 + #define CMD_DFILTER 0xc420 + #define CMD_DRATE 0xc800 + #define CMD_FIFO 0xce00 + #define CMD_RSTMODE 0xda00 + + #define CMD_RESET 0xff00 +#endif + +#ifdef RFM12B + + #define CMD_CONFIG 0x8000 + #define CMD_PM 0x8200 + #define CMD_DRATE 0xc600 + #define CMD_RCON 0x9000 + #define CMD_DFILTER 0xc228 + #define CMD_FIFO 0xca00 + #define CMD_SYNCRON 0xce00 + #define CMD_FIFO_RD 0xb000 + #define CMD_AFC 0xc400 + #define CMD_TCON 0x9800 + #define CMD_PLL 0xcc12 + #define CMD_TX_WR 0xb800 + #define CMD_WAKEUP 0xe000 + #define CMD_LOWDUTY 0xc800 + #define CMD_LOWBATT 0xc000 + + #define CMD_RESET 0xfe00 +#endif + +#ifdef RFM01 + //CONFIG + + #define BAND_433 (1 << 11) + #define LOWBATT_EN (1 << 10) + #define WAKEUP_EN (1 << 9) + + #define NO_CLOCK 1 + + #define LOAD_CAP_8C5 (0 << 4) + #define LOAD_CAP_9C0 (1 << 4) + #define LOAD_CAP_9C5 (2 << 4) + #define LOAD_CAP_10C0 (3 << 4) + #define LOAD_CAP_10C5 (4 << 4) + #define LOAD_CAP_11C0 (5 << 4) + #define LOAD_CAP_11C5 (6 << 4) + #define LOAD_CAP_12C0 (7 << 4) + #define LOAD_CAP_12C5 (8 << 4) + #define LOAD_CAP_13C0 (9 << 4) + #define LOAD_CAP_13C5 (10 << 4) + #define LOAD_CAP_14C0 (11 << 4) + #define LOAD_CAP_14C5 (12 << 4) + #define LOAD_CAP_15C0 (13 << 4) + #define LOAD_CAP_15C5 (14 << 4) + #define LOAD_CAP_16C0 (15 << 4) + + #define BW_67 (6 << 1) + #define BW_134 (5 << 1) + #define BW_200 (4 << 1) + #define BW_270 (3 << 1) + #define BW_340 (2 << 1) + #define BW_400 (1 << 1) + #define BW_X1 (0 << 1) + #define BW_X2 (7 << 1) + + // RCON values + #define VDI_DRSSI (0 << 6) + #define VDI_DQD (1 << 6) + #define VDI_CR_LOCK (2 << 6) + #define VDI_DRSSIDQD (3 << 6) + + #define LNA_20 (3 << 4) + #define LNA_14 (1 << 4) + #define LNA_6 (2 << 4) + #define LNA_0 (0 << 4) + + #define LNA_XX (3 << 4) + + #define RSSI_103 (0 << 1) + #define RSSI_97 (1 << 1) + #define RSSI_91 (2 << 1) + #define RSSI_85 (3 << 1) + #define RSSI_79 (4 << 1) + #define RSSI_73 (5 << 1) + #define RSSI_X1 (6 << 1) + #define RSSI_X2 (7 << 1) + + #define RX_EN 1 + + // DFILTER values + #define CR_AUTO (1 << 7) + #define CR_LOCK_FAST (1 << 6) + #define CR_LOCK_SLOW (0 << 6) + #define FILTER_OOK (0 << 3) + #define FILTER_DIGITAL (1 << 3) + #define FILTER_X (2 << 3) + #define FILTER_ANALOG (3 << 3) + + #define DQD_0 0 + #define DQD_1 1 + #define DQD_2 2 + #define DQD_3 3 + #define DQD_4 4 + #define DQD_5 5 + #define DQD_6 6 + #define DQD_7 7 + + // AFC values + #define AFC_ON (1 << 0) + #define AFC_OFF (0 << 0) + #define AFC_OUT_ON (1 << 1) + #define AFC_OUT_OFF (0 << 1) + #define AFC_FINE (1 << 2) + #define AFC_STROBE (1 << 3) + + #define AFC_RL_3 (3 << 4) + #define AFC_RL_7 (2 << 4) + #define AFC_RL_15 (1 << 4) + #define AFC_RL_NONE (0 << 4) + + #define AFC_MANUAL (0 << 6) + #define AFC_POWERUP (1 << 6) + #define AFC_VDI (2 << 6) + #define AFC_ALWAYS (3 << 6) +#endif + +#ifdef RFM12B + #define P16 (1 << 10) + + #define VDI_FAST (0 << 8) // CR_LOCK && DRSSI && DQD + #define VDI_MEDIUM (1 << 8) // CR_LOCK && (DRSSI | DQD) + #define VDI_SLOW (2 << 8) // DQD + #define VDI_ON (3 << 8) // Always high + + #define LNA_LOW (3 << 3) + #define LNA_MEDIUM (2 << 3) + #define LNA_HIGH (1 << 3) + #define LNA_MAX (0 << 3) + + #define RSSI_103 0 + #define RSSI_97 1 + #define RSSI_91 2 + #define RSSI_85 3 + #define RSSI_79 4 + #define RSSI_73 5 + #define RSSI_X1 6 + #define RSSI_X2 7 + + #define BW_67 (6 << 5) + #define BW_134 (5 << 5) + #define BW_200 (4 << 5) + #define BW_270 (3 << 5) + #define BW_340 (2 << 5) + #define BW_400 (1 << 5) + #define BW_X1 (0 << 5) + #define BW_X2 (7 << 5) + + // AFC values + #define AFC_ON (1 << 0) + #define AFC_OFF (0 << 0) + #define AFC_OUT_ON (1 << 1) + #define AFC_OUT_OFF (0 << 1) + #define AFC_FINE (1 << 2) + #define AFC_STROBE (1 << 3) + + #define AFC_RL_3 (3 << 4) + #define AFC_RL_7 (2 << 4) + #define AFC_RL_15 (1 << 4) + #define AFC_RL_NONE (0 << 4) + + #define AFC_MANUAL (0 << 6) + #define AFC_POWERUP (1 << 6) + #define AFC_VDI (2 << 6) + #define AFC_ALWAYS (3 << 6) +#endif + + + + +#ifdef RFM01 + #define STATUS_FFIT (1 << 15) + #define STATUS_FFOV (1 << 14) + #define STATUS_WKUP (1 << 13) + #define STATUS_LBD (1 << 12) + #define STATUS_FFEM (1 << 11) + #define STATUS_RSSI (1 << 10) + #define STATUS_DRSSI (1 << 10) + #define STATUS_DQD (1 << 9) + #define STATUS_CRL (1 << 8) + #define STATUS_ATGL (1 << 7) + #define STATUS_ASAME (1 << 6) + #define STATUS_OFFS6 (1 << 5) + #define STATUS_OFFS4 (1 << 4) + #define STATUS_OFFS3 (1 << 3) + #define STATUS_OFFS2 (1 << 2) + #define STATUS_OFFS1 (1 << 1) + #define STATUS_OFFS0 (1 << 0) + #define STATUS_OFFS 0x3f + #define STATUS_OFFSIGN 0x20 +#endif + +#ifdef RFM12B + #define STATUS_RGIT (1 << 15) + #define STATUS_FFIT (1 << 15) + #define STATUS_POR (1 << 14) + #define STATUS_RGUR (1 << 13) + #define STATUS_FFOV (1 << 13) + #define STATUS_WKUP (1 << 12) + #define STATUS_EXT (1 << 11) + #define STATUS_LBD (1 << 10) + #define STATUS_FFEM (1 << 9) + #define STATUS_RSSI (1 << 8) + #define STATUS_DRSSI (1 << 8) + #define STATUS_DQD (1 << 7) + #define STATUS_CRL (1 << 6) + #define STATUS_ATGL (1 << 5) + #define STATUS_OFFS6 (1 << 4) + #define STATUS_OFFS3 (1 << 3) + #define STATUS_OFFS2 (1 << 2) + #define STATUS_OFFS1 (1 << 1) + #define STATUS_OFFS0 (1 << 0) + #define STATUS_OFFS 0x1f + #define STATUS_OFFSIGN 0x10 +#endif + +/* + * RSSIth = RSSIsetth + Glna, where RSSIsetth is dBm and Glna is dB + * and is expressed as dB referenced to max G. This means that zero is the + * highest amplification, and the negative dB scales down the RSSIsetth + * threshold. + */ + +#define L0R73 {"LNA_0,RSSI_73",LNA_0,RSSI_73} +#define L0R79 {"LNA_0,RSSI_79",LNA_0,RSSI_79} +#define L0R85 {"LNA_0,RSSI_85",LNA_0,RSSI_85} +#define L0R91 {"LNA_0,RSSI_91",LNA_0,RSSI_91} +#define L0R97 {"LNA_0,RSSI_97",LNA_0,RSSI_97} +#define L0R103 {"LNA_0,RSSI_103",LNA_0,RSSI_103} +#define L6R73 {"LNA_6,RSSI_73",LNA_6,RSSI_73} +#define L6R79 {"LNA_6,RSSI_79",LNA_6,RSSI_79} +#define L6R85 {"LNA_6,RSSI_85",LNA_6,RSSI_85} +#define L6R91 {"LNA_6,RSSI_91",LNA_6,RSSI_91} +#define L6R97 {"LNA_6,RSSI_97",LNA_6,RSSI_97} +#define L6R103 {"LNA_6,RSSI_103",LNA_6,RSSI_103} +#define L14R73 {"LNA_14,RSSI_73",LNA_14,RSSI_73} +#define L14R79 {"LNA_14,RSSI_79",LNA_14,RSSI_79} +#define L14R85 {"LNA_14,RSSI_85",LNA_14,RSSI_85} +#define L14R91 {"LNA_14,RSSI_91",LNA_14,RSSI_91} +#define L14R97 {"LNA_14,RSSI_97",LNA_14,RSSI_97} +#define L14R103 {"LNA_14,RSSI_103",LNA_14,RSSI_103} +#define L20R73 {"LNA_20,RSSI_73",LNA_20,RSSI_73} +#define L20R79 {"LNA_20,RSSI_79",LNA_20,RSSI_79} +#define L20R85 {"LNA_20,RSSI_85",LNA_20,RSSI_85} +#define L20R91 {"LNA_20,RSSI_91",LNA_20,RSSI_91} +#define L20R97 {"LNA_20,RSSI_97",LNA_20,RSSI_97} +#define L20R103 {"LNA_20,RSSI_103",LNA_20,RSSI_103} + +struct RSSI { + char *name; + uint16_t g_lna; + uint16_t rssi_setth; + uint16_t cmd_config; + uint16_t cmd_rcon; + uint16_t flags; // bit 0: active, bit 1: not suitable + float duty[6]; +}; diff --git a/wh1080_rf.c b/wh1080_rf.c new file mode 100755 index 0000000..d07c2d4 --- /dev/null +++ b/wh1080_rf.c @@ -0,0 +1,545 @@ +/* +* Maplin N96GY (Fine Offset WH1080/WH1081) RF receiver using a +* Raspberry Pi and an RFM01 or RFM12b transceiver module. I switched +* to an RFM01 module after frying the RFM12b; turns out it works *far* +* better anyway, so it was something of a blessing in disguise. +* +* The code here is really just experimental, and is not intended to be used +* beyond a learning excercise. It conveys the basics of what's required +* to get the Raspberry Pi receiving sensor data, but that's about it! +* +* I can't be sure it still works with an RFM12b, but it shouldn't be far off +* the mark if not - a bit of debugging may be required, but I no longer +* have a working module to test. +* +* This program configures an RFM01 to receive RF transmissions from the +* weather station's sensors, and reads them directly from the receiver's +* demodulator via the DATA pin, in to a GPIO pin on the Raspberry Pi. The +* pulse widths are used to derive the data-packet that was transmitted. +* +* The process switches to SCHED_RR for realtime latency while it waits +* for a packet. It returns to SCHED_OTHER when a packet is received. +* This ensures that bit transitions aren't missed, and also allows very +* heavy loads to run on the Pi while maintaining reliable reads. Optionally, +* the command 'sysctl kernel.sched_wakeup_granularity_ns=100000' may +* further improve latency, though it seems to work with Raspbian defaults +* regardless. +* +* Includes Luc Small's version of CRC8 from the OneWire Arduino library +* adapted for Fine Offset's calculations that also happen to work for this +* weather station. The SPI code was derived from the driver example at +* kernel.org. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation as version 2 of the License. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wh1080_rf.h" +#include "bcm2835.h" +#include "rfm01.h" + +uint16_t bw_scale[6] = {BW_67, BW_134, BW_200, BW_270, BW_340, BW_400}; + +struct RSSI rssi_scale[24] = { + L0R73,L0R79,L0R85,L0R91,L0R97,L0R103, + L6R73,L6R79,L6R85,L6R91,L6R97,L6R103, + L14R73,L14R79,L14R85,L14R91,L14R97,L14R103, + L20R73,L20R79,L20R85,L20R91,L20R97,L20R103, +}; + +uint8_t _crc8( uint8_t *addr, uint8_t len); + +static void pabort(const char *s) +{ + perror(s); + abort(); +} + +static const char *device = "/dev/spidev0.0"; +static uint8_t mode=0; +static uint8_t bits = 8; +static uint32_t speed = 1000000; +static uint16_t delay=0; + + +static uint16_t send_command16(int fd, uint16_t cmd) +{ + uint8_t tx[2]; + uint8_t *buf = (uint8_t *)&cmd; + tx[0] = buf[1]; + tx[1] = buf[0]; + + //printf("SPI %02x%02x\n", buf[1], buf[0]); + + uint8_t rx[2] = {0, 0}; + struct spi_ioc_transfer tr = { + .tx_buf = (unsigned long)tx, + .rx_buf = (unsigned long)rx, + .len = 2, + .delay_usecs = delay, + .speed_hz = speed, + .bits_per_word = bits, + }; + + if(ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 1) + pabort("can't send spi message"); + + return (((uint16_t)rx[0]) << 8) + rx[1]; +} + +int g_low_threshold = 1000; + +uint16_t cmd_reset = CMD_RESET; +uint16_t cmd_status = CMD_STATUS; + +// Expected bit rate: 95 = 1959, 99 = 1700, 9c = 1500, a1 = 1268, aa = 1000, b8 - 756, d5 = 500 +uint16_t cmd_drate = CMD_DRATE|0xaa; // drate is c8xx rather than c6xx +uint16_t cmd_freq = CMD_FREQ|0x620; // 433.92 MHz + +#ifdef RFM01 + uint16_t cmd_afc = CMD_AFC|AFC_ON|AFC_OUT_ON|AFC_MANUAL|AFC_FINE|AFC_RL_7; + uint16_t cmd_dcycle = CMD_LOWDUTY|0x00; + uint16_t cmd_fifo = CMD_FIFO|0x00; + + uint16_t cmd_config = CMD_CONFIG|BAND_433|LOAD_CAP_12C0|BW_67; + uint16_t cmd_rcon = (CMD_RCON|RX_EN|VDI_DRSSI|LNA_0|RSSI_91); + uint16_t cmd_dfilter = (CMD_DFILTER|CR_LOCK_FAST|FILTER_OOK); +#endif + +#ifdef RFM12B + uint16_t cmd_config = 0x8017; + uint16_t cmd_power = 0x8281; // RFM01 doesn't support this + uint16_t cmd_sync = 0xce55; + uint16_t cmd_afc = 0xc407; // or C400 for no AFC. C6xx on RFM01 + uint16_t cmd_dcycle = 0xc800; + uint16_t cmd_pll = 0xcc1f; + uint16_t cmd_fifo = 0xca8a; // CExx rather than CAxx on RFM01 + + uint16_t cmd_dfilter = 0xc260; + uint16_t cmd_rcon = (CMD_RCON|P16|VDI_MEDIUM|LNA_MEDIUM|RSSI_97|BW_340); +#endif + +void strobe_afc(int fd) { + + send_command16(fd, cmd_afc|AFC_STROBE); // Strobe high + send_command16(fd, cmd_afc & (~AFC_ON)); // Strobe low, disable AFC processing + uint16_t status = send_command16(fd, cmd_status); + // get offs bits and extend two's complement to a byte + int8_t offset = (status & STATUS_OFFS) | (status & STATUS_OFFSIGN ? 0xe0 : 0); + + float freq_offs = (float)offset; + #ifdef RFM12B + freq_offs *= 2.5; + #endif + send_command16(fd, cmd_afc); // Strobe low, re-enable AFC + + printf("Frequency deviation %0.1fKHz (%d)\n", freq_offs, (int)offset); + + send_command16(fd, cmd_rcon); +} + +/* + * Sample the DRSSI flag at 'interval' microsecond intervals over a period of 'duration' ms, + *and return the average. +*/ + +float sample_rssi(int fd, int duration, int interval) { + + unsigned int start_time, now; + unsigned int loop_count = 0, rssi_total = 0; + + start_time = TIMER_ARM_COUNT; + + do { + uint16_t status = send_command16(fd, cmd_status); + int rssi = (status & STATUS_RSSI) ? 1 : 0; + loop_count++; + rssi_total+=rssi; + now = TIMER_ARM_COUNT; + usleep(interval); // microseconds + } while(now - start_time < (duration * 1000)); // duration as microseconds + + float duty = ((float)rssi_total/loop_count) * 100; + + return duty; +} + +extern int read_bmp085(float altitude); + +int main(int argc, char *argv[]) +{ + //unsigned char bytes2[] = {0xa1,0x82,0x0a,0x59,0x03,0x06,0x00,0x4e,0x06,0xc8}; + //calculate_values(bytes2); + //return -1; + + uint8_t packet_sig = 0xfa; + + if(map_peripheral(&gpio) == -1 || map_peripheral(&timer_arm) == -1) { + printf("Failed to map the GPIO or TIMER registers into the virtual memory space.\n"); + return -1; + } + + // 0xF90200; // run at 1MHz + TIMER_ARM_CONTROL = TIMER_ARM_C_DISABLE|TIMER_ARM_C_FREE_EN + |TIMER_ARM_C_16BIT|TIMER_ARM_C_PS1 + |TIMER_ARM_C_FPS(0xf9); + + // Init GPIO21 (on pin 13) as input (DATA), GPIO22 (pin 15) as output (nRES) + //*(gpio.addr + 2) = (*(gpio.addr + 2) & 0xfffffe07)|(0x001 << 6); + + // Init GPIO27 + *(gpio.addr + 2) = (*(gpio.addr + 2) & 0xff1fffff); + + #ifdef RFM01 + printf("Initialising RFM01\n"); + #endif + #ifdef RFM12B + printf("Initialising RFM12b\n"); + #endif + + int fd; + + fd = open(device, O_RDWR); + if (fd < 0) + pabort("can't open device"); + + // SPI mode + if(ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) + pabort("Can't set SPI mode"); + + // Bits per word (driver only supports 8 -bits I think, but RFM12B handles this ok) + if(ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) == -1) + pabort("Can't set bits per word"); + + // SPI clock speed (Hz) + if(ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1) + pabort("Can't set SPI clock speed"); + + printf("SPI: mode %d, %d-bit, %d KHz\n", mode, bits, speed/1000); + + // LED on + *(gpio.addr + (0x1c >> 2)) = 1 << 22; + + // Reset the module? Maybe use software reset if needed. + //send_command(fd, cmd_fifo); // in case reset sensitivity is low + //send_command(fd, cmd_reset); + usleep(100000); + + // LED off + *(gpio.addr + (0x28 >> 2)) = 1 << 22; + + send_command16(fd, cmd_status); + send_command16(fd, cmd_config); + send_command16(fd, cmd_freq); + send_command16(fd, cmd_drate); + send_command16(fd, cmd_rcon); + send_command16(fd, cmd_dfilter); + send_command16(fd, cmd_fifo); + send_command16(fd, cmd_afc); + send_command16(fd, cmd_dcycle); + #ifdef RFM12B + send_command16(fd, cmd_power); + send_command16(fd, cmd_sync); + send_command16(fd, cmd_pll); + #endif + + printf("Ctrl+C to exit\n"); + usleep(5000); // Allow crystal oscillator to start + + + int idx1, idx2; + for(idx1=0; idx1 < 24; idx1++) { + + uint16_t cmd_rcon_mod = (cmd_rcon & ~(RSSI_X2|LNA_XX)) | (rssi_scale[idx1].rssi_setth |rssi_scale[idx1].g_lna); + + printf("%15s idx %-2d ", rssi_scale[idx1].name, idx1); + + for(idx2=0; idx2 < 6; idx2++) { + + uint16_t cmd_config_mod = (cmd_config & ~BW_X2) | bw_scale[idx2]; + + send_command16(fd, cmd_config_mod); + send_command16(fd, cmd_rcon_mod); + usleep(1000); + + rssi_scale[idx1].duty[idx2] = sample_rssi(fd, 25, 100); + + if(cmd_rcon_mod == cmd_rcon && cmd_config_mod == cmd_config) + printf("%6.2f< ", rssi_scale[idx1].duty[idx2]); + else + printf("%6.2f ", rssi_scale[idx1].duty[idx2]); + fflush(stdout); + } + printf("\n"); + } + send_command16(fd, cmd_config); + send_command16(fd, cmd_rcon); + usleep(1000); + + + // Show the average RSSI to indicate noise at startup. If args dictate + // then repeat forever. Note that an unshielded Ethernet cable will + // radiate noise, so include a delay to allow console output to be flushed. + do { + float duty = sample_rssi(fd, 250, 100); + printf("RSSI Duty %0.2f\r", duty); + fflush(stdout); + usleep(250000); + } while(argc > 1); + + printf("\n"); + + uint16_t status; + + uint8_t rssi = 0, oldrssi = 0; + unsigned int shorts = 0; + unsigned int rssitime, oldrssitime, now; + int count = 0, timeout = 1; + time_t last_valid = time(0); + int crc_passed; + + unsigned int rssitime_buf[500]; + unsigned char bytes[10]; + + oldrssitime = TIMER_ARM_COUNT; + + // Switch to realtime scheduler + scheduler_realtime(); + do { + + // Read the GPIO pin for clocked DATA value + status = ((*(gpio.addr + 13)) >> 27) & 1; + rssi = status; + rssitime = TIMER_ARM_COUNT; + // Check if the pin transitioned + if(rssi != oldrssi) { + // If falling edge (1 -> 0), then store bit pulse duration + if(rssi == 0) { + rssitime_buf[count] = rssitime - oldrssitime; + if(++count == 500) + count = 499; + } + oldrssi = rssi; + oldrssitime = rssitime; + timeout = 0; + } + // Check time since last transition. If timeout, then dump packet. + int packet_offset = 0; + now = TIMER_ARM_COUNT; + if(!timeout && (now - oldrssitime) > 5000) { // && count > 0 + + uint8_t sig_matched = 0; + + if(count > 60) { // then maybe something at least interesting + // Look for device_id + int idx; + uint8_t bit, sig_in = 0; + for(idx=0; idx < count; idx++) { + bit = rssitime_buf[idx] < g_low_threshold ? 1 : 0; + sig_in = (sig_in << 1) | bit; + + if((sig_matched = (sig_in == packet_sig))) { + packet_offset = idx - 3; + break; + } + } + printf("\rData bits = %d (offset %d) (%d short) %s\n", + count, packet_offset, shorts, sig_matched ? "Packet signature found" : "No packet signature found"); + if(count == 88 && sig_matched) { // then probably a data packet + + // LED on + *(gpio.addr + (0x1c >> 2)) = 1 << 22; + + strobe_afc(fd); // lock frequency to good signal + + int b; + uint8_t byte; + for(idx=0; idx < 10; idx++) { + byte = 0; + for(b=0; b < 8; b++) { + // Short pulses 1, long pulses 0 + uint8_t bit = rssitime_buf[packet_offset + (idx * 8 + b)] < g_low_threshold ? 1 : 0; + byte = (byte << 1) + bit; + } + bytes[idx] = byte; + printf("%02x ", byte); + } + crc_passed = bytes[9] == _crc8(bytes, 9); + printf("crc %s (gap %ds)\n", crc_passed ? "ok" : "fail", (int)(time(0) - last_valid)); + last_valid = time(0); + fflush(stdout); + } + } else { + if(shorts++ % 10 == 0) { + printf("."); + fflush(stdout); + } + } + timeout = 1; + + // If we get enough bits, then dump stats to indicate pulse lengths coming from the device. + if(count > 40) { + // These are slightly confusing - lo used to mean low side of threshold, but printf below reports them as binary + // 0 and 1. So the meanings are opposite - to be fixed. + unsigned int idx, min_lo=999999, min_hi=999999, max_lo = 0, max_hi = 0; + unsigned int val; + for(idx = 0; idx < count; idx++) { + // printf("RSSI 1 -> 0 %3d: %4dus ( %s )\n", idx, rssitime_buf[idx], + // rssitime_buf[idx] >= LOW_THRESHOLD ? "Hi" : "Lo"); + val = rssitime_buf[idx]; + + // Short pulses are binary '1', long pulses are binary '0' + if(val < g_low_threshold) { + if(val < min_lo) + min_lo = val; + if(val > max_lo) + max_lo = val; + } else { + if(val < min_hi) + min_hi = val; + if(val > max_hi) + max_hi = val; + } + } + + printf("Pulse stats: Hi: %u - %u Lo: %u - %u (%d point)\n", min_lo, max_lo, min_hi, max_hi, count); + + // Recalculate the pulse threshold if we got a perfect read. + if(count == 88 && crc_passed) { + //g_low_threshold = ( ((max_lo + min_lo) / 2) + ((max_hi + min_hi) / 2)) / 2; + g_low_threshold =(max_lo + min_hi) / 2; + printf("Threshold now %d\n", g_low_threshold); + + // Note the time of the last reading... + unsigned int wait_start = TIMER_ARM_COUNT, elapsed; + + // at this point, we can do other stuff that requires the RT scheduler + + #ifdef USE_BMP085 + read_bmp085(ALTITUDE_M); // read pressure, calculate for the given altitude + #endif + + calculate_values(bytes); + + // Wait for remainder of 47 seconds in standard scheduler until we can expect the next read + scheduler_standard(); + + do { + elapsed = (TIMER_ARM_COUNT - wait_start) / 1000000; + printf("Wait %us \r", 47 - elapsed); + fflush(stdout); + usleep(250000); + } while(elapsed < 47); + printf("Listening for transmission\n"); + scheduler_realtime(); + } + } + count = 0; + + + // LED off + *(gpio.addr + (0x28 >> 2)) = 1 << 22; + } + usleep(5); // No point running with nanosecond loops when pulses are in the hundreds of microseconds... + + } while(1); // Ctrl+C to exit for now... + + + // Currenty unreachable + close(fd); + + unmap_peripheral(&gpio); + unmap_peripheral(&timer_arm); + + return 0; +} + +char *direction_name[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}; + +void calculate_values(unsigned char *buf) { + + unsigned short device_id = ((unsigned short)buf[0] << 4) | (buf[1] >> 4); + unsigned short temperature_raw = (((unsigned short)buf[1] & 0x0f) << 8) | buf[2]; + float temperature = ((float)temperature_raw - 400) / 10; + int humidity = buf[3]; + + unsigned short wind_avg_raw = (unsigned short)buf[4]; + float wind_avg_ms = roundf((float)wind_avg_raw * 34.0f) / 100; + float wind_avg_mph = wind_avg_ms * 2.23693629f; + + unsigned short wind_gust_raw = (unsigned short)buf[5]; + float wind_gust_ms = roundf((float)wind_gust_raw * 34.0f) / 100; + float wind_gust_mph = wind_gust_ms * 2.23693629f; + + unsigned short rain_raw = (((unsigned short)buf[6] & 0x0f) << 8) | buf[7]; + + float rain = (float)rain_raw * 0.3f; + + int direction = buf[8] & 0x0f; + + char *direction_str = direction_name[direction]; + + printf("Station Id: %04X\n", device_id); + printf("Temperature: %0.1fC, Humidity: %d%%\n", temperature, humidity); + printf("Wind speed: %0.2f m/s, Gust Speed %0.2f m/s, %s\n", wind_avg_ms, wind_gust_ms, direction_str); + printf("Wind speed: %0.1f mph, Gust Speed %0.1f mph, %s\n", wind_avg_mph, wind_gust_mph, direction_str); + printf("Total rain: %0.1f mm\n", rain); +} + +/* +* Function taken from Luc Small (http://lucsmall.com), itself +* derived from the OneWire Arduino library. Modifications to +* the polynomial according to Fine Offset's CRC8 calulations. +*/ +uint8_t _crc8( uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + // Indicated changes are from reference CRC-8 function in OneWire library + while (len--) { + uint8_t inbyte = *addr++; + uint8_t i; + for (i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x80; // changed from & 0x01 + crc <<= 1; // changed from right shift + if (mix) crc ^= 0x31;// changed from 0x8C; + inbyte <<= 1; // changed from right shift + } + } + return crc; +} + +void scheduler_realtime() { + + struct sched_param p; + + p.__sched_priority = sched_get_priority_max(SCHED_RR); + + if( sched_setscheduler( 0, SCHED_RR, &p ) == -1 ) { + perror("Failed to switch to realtime scheduler."); + } +} + +void scheduler_standard() { + + struct sched_param p; + + p.__sched_priority = 0; + + if( sched_setscheduler( 0, SCHED_OTHER, &p ) == -1 ) { + perror("Failed to switch to normal scheduler."); + } +} diff --git a/wh1080_rf.h b/wh1080_rf.h new file mode 100755 index 0000000..17e4980 --- /dev/null +++ b/wh1080_rf.h @@ -0,0 +1,8 @@ + +//#define USE_BMP085 +//#define ALTITUDE_M 210.0f +#define ALTITUDE_M 10.0f + +void scheduler_realtime(); +void scheduler_standard(); +void calculate_values(unsigned char *buf);