diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/drivers/ao_rn4678.c | 583 | ||||
| -rw-r--r-- | src/drivers/ao_rn4678.h | 98 | 
2 files changed, 681 insertions, 0 deletions
| diff --git a/src/drivers/ao_rn4678.c b/src/drivers/ao_rn4678.c new file mode 100644 index 00000000..f5b50459 --- /dev/null +++ b/src/drivers/ao_rn4678.c @@ -0,0 +1,583 @@ +/* + * Copyright © 2017 Keith Packard <keithp@keithp.com> + * + * 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, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + */ + +#include <ao.h> +#include <ao_rn4678.h> +#include <ao_exti.h> +#include <stdarg.h> + +static uint8_t	ao_rn_connected; + +#define AO_RN_DEBUG 0 + +#if AO_RN_DEBUG +static void ao_rn_dbg(char *format, ...) { +	va_list a; +	uint32_t	irq = ao_arch_irqsave(); +	ao_arch_release_interrupts(); +	va_start(a, format); +	vprintf(format, a); +	va_end(a); +	flush(); +	ao_arch_irqrestore(irq); +} + +static char	ao_rn_dir; + +static void +ao_rn_log_char(char c, char dir) +{ +	if (dir != ao_rn_dir) { +		putchar(dir); +		ao_rn_dir = dir; +	} +	switch (c) { +	case '\r': +		putchar('\\'); putchar('r'); +		break; +	case '\n': +		putchar('\\'); putchar('n'); +		break; +	default: +		putchar(c); +	} +	flush(); +} + +static void +ao_rn_log_out_char(char c) +{ +	ao_rn_log_char(c, '}'); +} + +static void +ao_rn_log_in_char(char c) +{ +	ao_rn_log_char(c, '{'); +} + +static inline void +ao_rn_putchar(char c) +{ +	ao_rn_log_out_char(c); +	ao_serial_rn_putchar(c); +} + +static inline int +_ao_rn_pollchar(void) +{ +	int	c = _ao_serial_rn_pollchar(); + +	if (c != AO_READ_AGAIN) { +		ao_arch_release_interrupts(); +		ao_rn_log_in_char((char) c); +		ao_arch_block_interrupts(); +	} +	return c; +} +#else +#define ao_rn_dbg(fmt, ...) +#define ao_rn_putchar(c)	ao_serial_rn_putchar(c) +#define _ao_rn_pollchar()	_ao_serial_rn_pollchar() +#endif + +/* For stdio, this skips all status messages *sigh* */ + +#define STATUS_CHAR	'%' + +static const char *status_strings[] = { +	"RFCOMM_CLOSE", +	"RFCOMM_OPEN", +	"CONNECT", +	"DISCONN", +	"BONDED", +}; + +#define NUM_STATUS_STRING	(sizeof status_strings/sizeof status_strings[0]) + +int +_ao_wrap_rn_pollchar(void) +{ +	static char	buffer[64]; +	static int	buf_cnt, buf_ptr; +	static int	draining; +	int		c = AO_READ_AGAIN; +	unsigned	i; +	int		done = 0; + +	while (!done && !draining) { +		c = _ao_serial_rn_pollchar(); + +		if (c == AO_READ_AGAIN) +			return AO_READ_AGAIN; + +		if (buf_cnt) { +			/* buffering chars */ + +			if (c == STATUS_CHAR) { +				/* End of status string, drop it and carry on */ +				buffer[buf_cnt] = '\0'; +				ao_rn_dbg("discard %s\n", buffer); +				buf_cnt = 0; +			} else if (buf_cnt == sizeof(buffer)) { +				/* If we filled the buffer, just give up */ +				draining = 1; +			} else { +				buffer[buf_cnt++] = c; +				for (i = 0; i < NUM_STATUS_STRING; i++) { +					int cmp = strlen(status_strings[i]); +					if (cmp >= buf_cnt) +						cmp = buf_cnt-1; +					if (memcmp(buffer+1, status_strings[i], cmp) == 0) +						break; +				} +				if (i == NUM_STATUS_STRING) +					draining = 1; +			} +		} else if (c == STATUS_CHAR) { +			buffer[0] = c; +			buf_cnt = 1; +			buf_ptr = 0; +		} else +			done = 1; +	} +	if (draining) { +		c = buffer[buf_ptr++] & 0xff; +		if (buf_ptr == buf_cnt) { +			buf_ptr = buf_cnt = 0; +			draining = 0; +		} +	} +#if AO_RN_DEBUG +	ao_arch_release_interrupts(); +	ao_usb_putchar(c); ao_usb_flush(); +	ao_arch_block_interrupts(); +#endif +	return c; +} + +#if AO_RN_DEBUG +static void +ao_wrap_rn_putchar(char c) +{ +	ao_usb_putchar(c); ao_usb_flush(); +	ao_serial_rn_putchar(c); +} +#else +#define ao_wrap_rn_putchar ao_serial_rn_putchar +#endif + +static void +ao_rn_puts(char *s) +{ +	char c; + +	while ((c = *s++)) +		ao_rn_putchar(c); +} + +static void +ao_rn_drain(void) +{ +	int	timeout = 0; + +	ao_rn_dbg("drain...\n"); +	ao_serial_rn_drain(); +	while (!timeout) { +		ao_arch_block_interrupts(); +		while (_ao_rn_pollchar() == AO_READ_AGAIN) { +			if (_ao_serial_rn_sleep_for(AO_MS_TO_TICKS(10))) { +				timeout = 1; +				break; +			} +		} +		ao_arch_release_interrupts(); +	} +	ao_rn_dbg("drain done\n"); +} + +static void +ao_rn_send_cmd(char *cmd, char *param) +{ +	ao_rn_dbg("send_cmd %s%s\n", cmd, param ? param : ""); +	ao_rn_drain(); +	ao_rn_puts(cmd); +	if (param) +		ao_rn_puts(param); +	ao_rn_putchar('\r'); +} + +static int +ao_rn_wait_char(AO_TICK_TYPE giveup_time) +{ +	int c; + +	ao_arch_block_interrupts(); +	while ((c = _ao_rn_pollchar()) == AO_READ_AGAIN) { +		AO_TICK_SIGNED	delay = (AO_TICK_SIGNED) (giveup_time - ao_time()); +		if (delay < 0) { +			ao_arch_release_interrupts(); +			return AO_READ_AGAIN; +		} +		_ao_serial_rn_sleep_for(delay); +	} +	ao_arch_release_interrupts(); +	return c; +} + +static int +ao_rn_wait_for(int timeout, char *match) +{ +	char		reply[AO_RN_MAX_REPLY_LEN + 1]; +	int		match_len = strlen(match); +	AO_TICK_TYPE	giveup_time = ao_time() + timeout; +	int		c; + +	ao_rn_dbg("wait for %d, \"%s\"\n", timeout, match); +	memset(reply, ' ', sizeof(reply)); +	while (memcmp(reply, match, match_len) != 0) { +		c = ao_rn_wait_char(giveup_time); +		if (c == AO_READ_AGAIN) { +			ao_rn_dbg("\twait for timeout\n"); +			return AO_RN_TIMEOUT; +		} +		reply[match_len] = (char) c; +		memmove(reply, reply+1, match_len); +		reply[match_len] = '\0'; +		ao_rn_dbg("\tmatch now \"%s\"\n", reply); +	} +	ao_rn_dbg("\twait for ok\n"); +	return AO_RN_OK; +} + +static int +ao_rn_wait_line(AO_TICK_TYPE giveup_time, char *line, int len) +{ +	char *l = line; + +	ao_rn_dbg("wait line\n"); +	for (;;) { +		int c = ao_rn_wait_char(giveup_time); + +		/* timeout */ +		if (c == AO_READ_AGAIN) { +			ao_rn_dbg("\twait line timeout\n"); +			return AO_RN_TIMEOUT; +		} + +		/* done */ +		if (c == '\r') { +			*l = '\0'; +			ao_rn_dbg("\twait line \"%s\"\n", line); +			return AO_RN_OK; +		} + +		if (c == '\n') +			continue; + +		/* buffer overrun */ +		if (len <= 1) +			return AO_RN_ERROR; + +		*l++ = (char) c; +		len--; +	} +} + +static int +ao_rn_wait_status(void) +{ +	char		message[AO_RN_MAX_REPLY_LEN]; +	AO_TICK_TYPE	giveup_time = ao_time() + AO_RN_CMD_TIMEOUT; +	int		status; + +	ao_rn_dbg("wait status\n"); +	status = ao_rn_wait_line(giveup_time, message, sizeof (message)); +	if (status == AO_RN_OK) +		if (strncmp(message, "AOK", 3) != 0) +			status = AO_RN_ERROR; +	return status; +} + +static int +ao_rn_set_name(void) +{ +	char	sn[8]; +	char	*s = sn + 8; +	char	c; +	int	n; + +	ao_rn_dbg("set name...\n"); +	ao_rn_send_cmd(AO_RN_SET_NAME_CMD, "TeleBT-"); +	*--s = '\0'; +	*--s = '\r'; +	n = ao_serial_number; +	do { +		*--s = '0' + n % 10; +	} while (n /= 10); +	while ((c = *s++)) +		ao_rn_putchar(c); +	return ao_rn_wait_status(); +} + +static int +ao_rn_get_name(char *name, int len) +{ +	ao_rn_dbg("get name...\n"); +	ao_rn_send_cmd(AO_RN_GET_NAME_CMD, NULL); +	return ao_rn_wait_line(ao_time() + AO_RN_CMD_TIMEOUT, name, len); +} + +static void +ao_rn_check_link(void) +{ +	ao_rn_connected = 1 - ao_gpio_get(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN, foo); +} + +static void +ao_rn_isr(void) +{ +	ao_rn_check_link(); +	ao_wakeup(&ao_rn_connected); +} + +static void +ao_bt_panic(int where) +{ +	int i; +	for (;;) { +		for (i = 0; i < 50; i++) { +			ao_led_toggle(AO_BT_LED); +			ao_delay(AO_MS_TO_TICKS(10)); +		} +		ao_led_off(AO_BT_LED); +		ao_delay(AO_MS_TO_TICKS(500)); +		for (i = 0; i < where; i++) { +			ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(200)); +			ao_delay(AO_MS_TO_TICKS(200)); +		} +	} +} + +static uint8_t	ao_rn_stdio; + +/* + * Set the stdio echo for the bluetooth link + */ +void +ao_rn_echo(uint8_t echo) +{ +	ao_stdios[ao_rn_stdio].echo = echo; +} + +static void +ao_rn(void) +{ +	int	status = AO_RN_ERROR; +	char	name[17]; +	int	i; + +	ao_rn_dbg("ao_rn top\n"); + +	/* Select CMD mode after the device gets out of reset */ +	ao_gpio_set(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_CMD); + +	for (i = 0; i < 3; i++) { +		ao_rn_dbg("reset device\n"); + +		ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0); +		ao_delay(AO_MS_TO_TICKS(100)); + +		/* Reboot the RN4678 and wait for it to start talking */ +		ao_rn_drain(); +		ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 1); +		status = ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, AO_RN_REBOOT_MSG); +		if (status != AO_RN_OK) { +			ao_rn_dbg("reboot failed\n"); +			continue; +		} + +		/* After it reboots, it can take a moment before it responds +		 * to commands +		 */ +		(void) ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, "CMD> "); + +		/* Check to see if the name is already set and assume +		 * that the device is ready to go +		 */ +		status = ao_rn_get_name(name, sizeof (name)); +		if (status != AO_RN_OK) { +			ao_rn_dbg("get name failed\n"); +			continue; +		} + +		if (strncmp(name, "TeleBT", 6) == 0) { +			ao_rn_dbg("name is set\n"); +			status = AO_RN_OK; +			break; +		} + +		/* Make the command pin control command/data mode */ +		ao_rn_send_cmd(AO_RN_SET_COMMAND_PIN, NULL); +		if (ao_rn_wait_status() != AO_RN_OK) { +			ao_rn_dbg("set command pin failed\n"); +			continue; +		} + +		/* Select 'fast' mode to ignore command sequence (more or less) */ +		ao_rn_send_cmd(AO_RN_SET_FAST_MODE, NULL); +		if (ao_rn_wait_status() != AO_RN_OK) { +			ao_rn_dbg("set fast mode failed\n"); +			continue; +		} + +		/* Finally, set the name. Doing this last makes it possible to check +		 * if the whole sequence has been done +		 */ +		if (ao_rn_set_name() != AO_RN_OK) { +			ao_rn_dbg("set name failed\n"); +			continue; +		} + +		/* After we've configured the device, go back around and reboot it +		 * as that's how we get the new configuration to take effect +		 */ +	} +	ao_rn_dbg("ao_rn status %d\n", status); + +	if (status != AO_RN_OK) +		ao_bt_panic(4); + +	ao_gpio_set(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_DATA); + +	/* Wait for the hardware to finish sending messages, then clear the queue */ +	ao_delay(AO_MS_TO_TICKS(200)); +	ao_rn_drain(); + +	ao_exti_enable(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN); + +#if 1 +	ao_rn_stdio = ao_add_stdio(_ao_wrap_rn_pollchar, +				   ao_wrap_rn_putchar, +				   NULL); + +	ao_rn_echo(0); + +	ao_rn_check_link(); + +	ao_rn_dbg("RN running\n"); + +	/* +	 * Now just hang around and flash the blue LED when we've got +	 * a connection +	 */ +	for (;;) { +		ao_arch_block_interrupts(); +		while (!ao_rn_connected) +			ao_sleep(&ao_rn_connected); +		ao_arch_release_interrupts(); +		while (ao_rn_connected) { +			ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(20)); +			ao_delay(AO_SEC_TO_TICKS(3)); +		} +	} +#else + +	/* +	 * Separate debug code when things aren't working. Just dump +	 * inbound bluetooth characters to stdout +	 */ +	for (;;) { +		int	c; + +		while (rn_cmd_running) +			ao_delay(AO_MS_TO_TICKS(1000)); + +		ao_arch_block_interrupts(); +		while ((c = _ao_wrap_rn_pollchar()) == AO_READ_AGAIN) +			ao_sleep(&ao_serial_rn_rx_fifo); +		ao_arch_release_interrupts(); +		putchar(c); flush(); +	} +#endif +} + +static struct ao_task ao_rn_task; + +static void +ao_rn_factory(void) +{ +	int	i; +	int	v = 0; + +	/* +	 * Factory reset. Flip pin P3_1 5 times within the first five +	 * seconds of power-on +	 */ + +	/* Select our target output pin */ +	ao_enable_output(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, foo, v); + +	/* Turn off the BT device using the SW_BTN pin */ +	printf("Power down BT\n"); flush(); +	ao_gpio_set(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 0); +	ao_delay(AO_MS_TO_TICKS(1000)); + +	/* And turn it back on */ +	printf("Power up BT\n"); flush(); +	ao_gpio_set(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 1); + +	/* Right after power on, poke P3_1 five times to force a +	 * factory reset +	 */ +	for (i = 0; i < 10; i++) { +		v = 1-v; +		ao_delay(AO_MS_TO_TICKS(100)); +		ao_gpio_set(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, foo, v); +		ao_led_toggle(AO_BT_LED); +	} + +	/* And let P3_1 float again */ +	ao_enable_input(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, AO_EXTI_MODE_PULL_NONE); +} + +static const struct ao_cmds rn_cmds[] = { +	{ ao_rn_factory, "F\0Factory reset rn4678" }, +	{ 0 }, +}; + +void +ao_rn4678_init(void) +{ +	(void) ao_rn_set_name; + +	ao_serial_rn_set_speed(AO_SERIAL_SPEED_115200); + +	/* Reset line */ +	ao_enable_output(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0); + +	/* SW_BTN */ +	ao_enable_output(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 1); + +	/* P3_7 command/data selector */ +	ao_enable_output(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_CMD); + +	ao_enable_input(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN, AO_EXTI_MODE_PULL_NONE); +	ao_exti_setup(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN, +		      AO_EXTI_MODE_FALLING|AO_EXTI_MODE_RISING|AO_EXTI_PRIORITY_LOW, +		      ao_rn_isr); + +	ao_cmd_register(rn_cmds); +	ao_add_task(&ao_rn_task, ao_rn, "bluetooth"); +} diff --git a/src/drivers/ao_rn4678.h b/src/drivers/ao_rn4678.h new file mode 100644 index 00000000..d6fea23a --- /dev/null +++ b/src/drivers/ao_rn4678.h @@ -0,0 +1,98 @@ +/* + * Copyright © 2017 Keith Packard <keithp@keithp.com> + * + * 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, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + */ + +#ifndef _AO_RN4678_H_ +#define _AO_RN4678_H_ + +/* From the rn4678 pictail board + +    1	SW_BTN	0-off/1-on	 2	P2_0	1-app/0-test (boot time) +    3	P2_4	1-app/0-WF	 4	EAN	0-app/1-WF (reboots) +    5				 6	RTS	0 +    7				 8	P3_2	1 (NC) +    9	TXD			10	P3_3	1 (NC) +   11	RXD			12	P3_4	1 (NC) +   13				14 +   15				16	P3_7	1 (NC) +   17	P0_5	1 (NC)		18	RST_N	1 run/0 reset +   19	WAKE_UP	1 run/0 btn	20 +   21	P0_4	0 (NC)		22 +   23	P1_5	1 (NC)		24	P3_1	1 (NC) +   25	CTS	0		26	3.3V +   27				28	GND + + +   Interesting pins: + +   Not connected to microcontroller: + +   SW_BTN	0-off		1-on +   P2_4		0 write-flash	1-app +   WAKE_UP	0-stop		1-run +   P2_0		0 test		1-run +   EAN		1-WF (reboots)	0-run +   RST_N	0 reset		1 run + +   Connected to microcontroller: + +   CTS		mostly 0 +   RTS		mostly 1 + +   TXD +   RXD + +   Other connections -- LDO33_O to VDD_IO + +*/ + +#define AO_RN_REBOOT_MSG	"%REBOOT%" + +#define AO_RN_CMD_TIMEOUT	AO_MS_TO_TICKS(200) + +#define AO_RN_REBOOT_TIMEOUT	AO_MS_TO_TICKS(2000) + +#define AO_RN_MAX_REPLY_LEN	10 + +#define AO_RN_SET_NAME_CMD		"SN," +#define AO_RN_GET_NAME_CMD		"GN" + +#define AO_RN_SET_STATUS_STRING	"SO," +#define AO_RN_STATUS_STRING_DISABLE	" " + +#define AO_RN_REBOOT_CMD		"R,1" + +#define AO_RN_TIMEOUT	-1 +#define AO_RN_ERROR	0 +#define AO_RN_OK	1 + +#define AO_RN_SET_COMMAND_PIN	"SX,07,0B" + +#define AO_RN_SET_AUTH_JUST_WORKS	"SA,2" +#define AO_RN_SET_FAST_MODE		"SQ,9000" + +/* This pin is configured to control cmd/data mode */ +#define AO_RN_CMD_PORT	AO_RN_P3_7_PORT +#define AO_RN_CMD_PIN	AO_RN_P3_7_PIN + +#define AO_RN_CMD_CMD	0 +#define AO_RN_CMD_DATA	1 + +/* This pin indicates BT connection status */ +#define AO_RN_CONNECTED_PORT	AO_RN_P1_5_PORT +#define AO_RN_CONNECTED_PIN	AO_RN_P1_5_PIN + +void +ao_rn4678_init(void); + +#endif /* _AO_RN_H_ */ | 
