diff --git a/src/drivers/osd/atxxxx/atxxxx.cpp b/src/drivers/osd/atxxxx/atxxxx.cpp
index d6c7e0296b9c4949171799c2eee33c009c59ecf4..2d5c7a34c742cdda87a553d1049e38ab748849f2 100644
--- a/src/drivers/osd/atxxxx/atxxxx.cpp
+++ b/src/drivers/osd/atxxxx/atxxxx.cpp
@@ -34,44 +34,62 @@
 /**
  * @file atxxxx.cpp
  * @author Daniele Pettenuzzo
+ * @author Beat Küng <beat-kueng@gmx.net>
  *
  * Driver for the ATXXXX chip on the omnibus fcu connected via SPI.
  */
 
 #include "atxxxx.h"
 
+struct work_s OSDatxxxx::_work = {};
+
 
 OSDatxxxx::OSDatxxxx(int bus) :
 	SPI("OSD", OSD_DEVICE_PATH, bus, OSD_SPIDEV, SPIDEV_MODE0, OSD_SPI_BUS_SPEED),
-	_measure_ticks(0),
-	_sample_perf(perf_alloc(PC_ELAPSED, "osd_read")),
-	_comms_errors(perf_alloc(PC_COUNT, "osd_com_err")),
-	_battery_sub(-1),
-	_local_position_sub(-1),
-	_vehicle_status_sub(-1),
-	_battery_voltage_filtered_v(0),
-	_battery_discharge_mah(0),
-	_battery_valid(false),
-	_local_position_z(0),
-	_local_position_valid(false),
-	_arming_state(1),
-	_arming_timestamp(0)
+	ModuleParams(nullptr)
 {
-	_p_tx_mode = param_find("OSD_ATXXXX_CFG");
-	param_get(_p_tx_mode, &_tx_mode);
-
-	// work_cancel in the dtor will explode if we don't do this...
-	memset(&_work, 0, sizeof(_work));
+	_battery_sub = orb_subscribe(ORB_ID(battery_status));
+	_local_position_sub = orb_subscribe(ORB_ID(vehicle_local_position));
+	_vehicle_status_sub = orb_subscribe(ORB_ID(vehicle_status));
 }
 
 OSDatxxxx::~OSDatxxxx()
 {
-	/* make sure we are truly inactive */
-	stop();
+	orb_unsubscribe(_battery_sub);
+	orb_unsubscribe(_local_position_sub);
+	orb_unsubscribe(_vehicle_status_sub);
+}
+
+int OSDatxxxx::task_spawn(int argc, char *argv[])
+{
+	int ch;
+	int myoptind = 1;
+	const char *myoptarg = nullptr;
+	int spi_bus = OSD_BUS;
+
+	while ((ch = px4_getopt(argc, argv, "b:", &myoptind, &myoptarg)) != EOF) {
+		switch (ch) {
+		case 'b':
+			spi_bus = (uint8_t)atoi(myoptarg);
+			break;
+		}
+	}
+
+	int ret = work_queue(LPWORK, &_work, (worker_t)&OSDatxxxx::initialize_trampoline, (void *)(long)spi_bus, 0);
 
-	// free perf counters
-	perf_free(_sample_perf);
-	perf_free(_comms_errors);
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = wait_until_running();
+
+	if (ret < 0) {
+		return ret;
+	}
+
+	_task_id = task_id_is_work_queue;
+
+	return 0;
 }
 
 
@@ -79,16 +97,18 @@ int
 OSDatxxxx::init()
 {
 	/* do SPI init (and probe) first */
-	if (SPI::init() != PX4_OK) {
-		goto fail;
+	int ret = SPI::init();
+
+	if (ret != PX4_OK) {
+		return ret;
 	}
 
-	if (reset() != PX4_OK) {
-		goto fail;
+	if ((ret = reset()) != PX4_OK) {
+		return ret;
 	}
 
-	if (init_osd() != PX4_OK) {
-		goto fail;
+	if ((ret = init_osd()) != PX4_OK) {
+		return ret;
 	}
 
 	for (int i = 0; i < OSD_CHARS_PER_ROW; i++) {
@@ -97,15 +117,41 @@ OSDatxxxx::init()
 		}
 	}
 
-	_battery_sub = orb_subscribe(ORB_ID(battery_status));
-	// _local_position_sub = orb_subscribe(ORB_ID(vehicle_local_position));
-	_vehicle_status_sub = orb_subscribe(ORB_ID(vehicle_status));
+	return ret;
+}
 
-	return PX4_OK;
+int OSDatxxxx::start()
+{
+	if (is_running()) {
+		return 0;
+	}
 
-fail:
-	return PX4_ERROR;
+	init();
 
+	// Kick off the cycling. We can call it directly because we're already in the work queue context.
+	cycle();
+
+	return 0;
+}
+
+void OSDatxxxx::initialize_trampoline(void *arg)
+{
+	OSDatxxxx *osd = new OSDatxxxx((long)arg);
+
+	if (!osd) {
+		PX4_ERR("alloc failed");
+		return;
+	}
+
+	osd->start();
+	_object.store(osd);
+}
+
+void OSDatxxxx::cycle_trampoline(void *arg)
+{
+	OSDatxxxx *obj = reinterpret_cast<OSDatxxxx *>(arg);
+
+	obj->cycle();
 }
 
 
@@ -131,7 +177,7 @@ OSDatxxxx::init_osd()
 	int ret = PX4_OK;
 	uint8_t data = OSD_ZERO_BYTE;
 
-	if (_tx_mode == 2) {
+	if (_param_atxxxx_cfg.get() == 2) {
 		data |= OSD_PAL_TX_MODE;
 	}
 
@@ -148,20 +194,6 @@ OSDatxxxx::init_osd()
 
 }
 
-
-int
-OSDatxxxx::ioctl(device::file_t *filp, int cmd, unsigned long arg)
-{
-	return -1;
-}
-
-ssize_t
-OSDatxxxx::read(device::file_t *filp, char *buffer, size_t buflen)
-{
-	return -1;
-}
-
-
 int
 OSDatxxxx::readRegister(unsigned reg, uint8_t *data, unsigned count)
 {
@@ -173,7 +205,6 @@ OSDatxxxx::readRegister(unsigned reg, uint8_t *data, unsigned count)
 	ret = transfer(&cmd[0], &cmd[0], count + 1);
 
 	if (OK != ret) {
-		perf_count(_comms_errors);
 		DEVICE_LOG("spi::transfer returned %d", ret);
 		return ret;
 	}
@@ -197,7 +228,6 @@ OSDatxxxx::writeRegister(unsigned reg, uint8_t data)
 	ret = transfer(&cmd[0], nullptr, 2);
 
 	if (OK != ret) {
-		perf_count(_comms_errors);
 		DEVICE_LOG("spi::transfer returned %d", ret);
 		return ret;
 	}
@@ -440,20 +470,6 @@ OSDatxxxx::update_screen()
 
 }
 
-
-void
-OSDatxxxx::start()
-{
-	/* schedule a cycle to start things */
-	work_queue(LPWORK, &_work, (worker_t)&OSDatxxxx::cycle_trampoline, this, USEC2TICK(OSD_US));
-}
-
-void
-OSDatxxxx::stop()
-{
-	work_cancel(LPWORK, &_work);
-}
-
 int
 OSDatxxxx::reset()
 {
@@ -463,17 +479,14 @@ OSDatxxxx::reset()
 	return ret;
 }
 
-void
-OSDatxxxx::cycle_trampoline(void *arg)
-{
-	OSDatxxxx *dev = (OSDatxxxx *)arg;
-
-	dev->cycle();
-}
-
 void
 OSDatxxxx::cycle()
 {
+	if (should_exit()) {
+		exit_and_cleanup();
+		return;
+	}
+
 	update_topics();
 
 	if (_battery_valid || _local_position_valid || _arming_state > 1) {
@@ -489,179 +502,32 @@ OSDatxxxx::cycle()
 
 }
 
-void
-OSDatxxxx::print_info()
+int OSDatxxxx::print_usage(const char *reason)
 {
-	perf_print_counter(_sample_perf);
-	perf_print_counter(_comms_errors);
-	printf("poll interval:  %u ticks\n", _measure_ticks);
-	printf("battery_status: %.3f\n", (double)_battery_voltage_filtered_v);
-	printf("arming_state: %d\n", _arming_state);
-	printf("arming_timestamp: %5.1f\n", (double)_arming_timestamp);
-
-}
-
-
-/**
- * Local functions in support of the shell command.
- */
-namespace osd
-{
-
-OSDatxxxx	*g_dev;
-
-int	start(int spi_bus);
-int	stop();
-int	info();
-void usage();
-
-
-/**
- * Start the driver.
- */
-int
-start(int spi_bus)
-{
-	int fd;
-
-	if (g_dev != nullptr) {
-		PX4_ERR("already started");
-		goto fail;
-	}
-
-	/* create the driver */
-	g_dev = new OSDatxxxx(spi_bus);
-
-	if (g_dev == nullptr) {
-		goto fail;
+	if (reason) {
+		printf("%s\n\n", reason);
 	}
 
-	if (OK != g_dev->init()) {
-		goto fail;
-	}
+	PRINT_MODULE_DESCRIPTION(
+		R"DESCR_STR(
+### Description
+OSD driver for the ATXXXX chip that is mounted on the OmnibusF4SD board for example.
+)DESCR_STR");
 
-	fd = open(OSD_DEVICE_PATH, O_RDONLY);
-
-	if (fd < 0) {
-		goto fail;
-	}
+	PRINT_MODULE_USAGE_NAME("atxxxx", "driver");
+	PRINT_MODULE_USAGE_COMMAND_DESCR("start", "Start the driver");
+	PRINT_MODULE_USAGE_PARAM_INT('b', -1, 0, 100, "SPI bus (default: use board-specific bus)", true);
+	PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
 
-	g_dev->start();
-
-	return PX4_OK;
-
-fail:
-
-	if (g_dev != nullptr) {
-		delete g_dev;
-		g_dev = nullptr;
-	}
-
-	PX4_ERR("driver start failed");
-	return PX4_ERROR;
+	return 0;
 }
 
-/**
- * Stop the driver
- */
-int
-stop()
+int OSDatxxxx::custom_command(int argc, char *argv[])
 {
-	if (g_dev != nullptr) {
-		delete g_dev;
-		g_dev = nullptr;
-
-	} else {
-		PX4_ERR("driver not running");
-		return PX4_ERROR;
-	}
-
-	return PX4_OK;
+	return print_usage("unrecognized command");
 }
 
-/**
- * Print a little info about the driver.
- */
-int
-info()
+int atxxxx_main(int argc, char *argv[])
 {
-	if (g_dev == nullptr) {
-		PX4_ERR("driver not running");
-		return PX4_ERROR;
-	}
-
-	printf("state @ %p\n", g_dev);
-	g_dev->print_info();
-
-	return PX4_OK;
-}
-
-/**
- * Print a little info about how to start/stop/use the driver
- */
-void usage()
-{
-	PX4_INFO("usage: atxxxx {start|stop|status'}");
-	PX4_INFO("    [-b SPI_BUS]");
-}
-
-} // namespace osd
-
-
-int
-atxxxx_main(int argc, char *argv[])
-{
-	if (argc < 2) {
-		osd::usage();
-		return PX4_ERROR;
-	}
-
-	// don't exit from getopt loop to leave getopt global variables in consistent state,
-	// set error flag instead
-	bool err_flag = false;
-	int ch;
-	int myoptind = 1;
-	const char *myoptarg = nullptr;
-	int spi_bus = OSD_BUS;
-
-	while ((ch = px4_getopt(argc, argv, "b:", &myoptind, &myoptarg)) != EOF) {
-		switch (ch) {
-		case 'b':
-			spi_bus = (uint8_t)atoi(myoptarg);
-			break;
-
-		default:
-			err_flag = true;
-			break;
-		}
-	}
-
-	if (err_flag) {
-		osd::usage();
-		return PX4_ERROR;
-	}
-
-	/*
-	 * Start/load the driver.
-	 */
-	if (!strcmp(argv[myoptind], "start")) {
-		return osd::start(spi_bus);
-	}
-
-	/*
-	 * Stop the driver
-	 */
-	if (!strcmp(argv[myoptind], "stop")) {
-		return osd::stop();
-	}
-
-	/*
-	 * Print driver information.
-	 */
-	if (!strcmp(argv[myoptind], "info") || !strcmp(argv[myoptind], "status")) {
-		return osd::info();
-	}
-
-	osd::usage();
-	return PX4_ERROR;
+	return OSDatxxxx::main(argc, argv);
 }
diff --git a/src/drivers/osd/atxxxx/atxxxx.h b/src/drivers/osd/atxxxx/atxxxx.h
index 90c9e2c49cef42e55d89d4e1fdff6e22d26ad701..ca15b85f6120c2b5848a299a9be58d106e23fed2 100644
--- a/src/drivers/osd/atxxxx/atxxxx.h
+++ b/src/drivers/osd/atxxxx/atxxxx.h
@@ -45,9 +45,10 @@
 #include <string.h>
 #include <stdio.h>
 
-#include <perf/perf_counter.h>
 #include <parameters/param.h>
 #include <px4_getopt.h>
+#include <px4_module.h>
+#include <px4_module_params.h>
 #include <px4_workqueue.h>
 
 #include <board_config.h>
@@ -92,75 +93,45 @@
 # error This requires CONFIG_SCHED_WORKQUEUE.
 #endif
 
-class OSDatxxxx : public device::SPI
+
+extern "C" __EXPORT int atxxxx_main(int argc, char *argv[]);
+
+
+class OSDatxxxx : public device::SPI, public ModuleBase<OSDatxxxx>, public ModuleParams
 {
 public:
 	OSDatxxxx(int bus = OSD_BUS);
 
-	virtual ~OSDatxxxx();
+	~OSDatxxxx();
 
 	virtual int init();
 
-	virtual ssize_t read(device::file_t *filp, char *buffer, size_t buflen);
-
-	virtual int ioctl(device::file_t *filp, int cmd, unsigned long arg);
+	/**
+	 * @see ModuleBase::custom_command
+	 */
+	static int custom_command(int argc, char *argv[]);
 
 	/**
-	* Diagnostics - print some basic information about the driver.
-	*/
-	void print_info();
+	 * @see ModuleBase::print_usage
+	 */
+	static int print_usage(const char *reason = nullptr);
 
 	/**
-	* Initialise the automatic measurement state machine and start it.
-	*
-	* @note This function is called at open and error time.  It might make sense
-	*       to make it more aggressive about resetting the bus in case of errors.
-	*/
-	void start();
+	 * @see ModuleBase::task_spawn
+	 */
+	static int task_spawn(int argc, char *argv[]);
 
 protected:
 	virtual int probe();
 
 private:
-	work_s _work;
-
-	int _measure_ticks;
-
-	perf_counter_t _sample_perf;
-	perf_counter_t _comms_errors;
-
-	param_t _p_tx_mode;
-	int32_t _tx_mode;
-
-	int _battery_sub;
-	int _local_position_sub;
-	int _vehicle_status_sub;
-
-	bool _on;
-
-	// battery
-	float _battery_voltage_filtered_v;
-	float _battery_discharge_mah;
-	bool _battery_valid;
-
-	// altitude
-	float _local_position_z;
-	bool _local_position_valid;
+	static void cycle_trampoline(void *arg);
 
-	// flight time
-	uint8_t _arming_state;
-	uint64_t _arming_timestamp;
+	void cycle();
 
-	/**
-	* Stop the automatic measurement state machine.
-	*/
-	void stop();
+	static void initialize_trampoline(void *arg);
 
-	/**
-	* Perform a poll cycle; collect from the previous measurement
-	* and start a new one.
-	*/
-	void cycle();
+	int start();
 
 	int reset();
 
@@ -183,17 +154,26 @@ private:
 	int update_topics();
 	int	update_screen();
 
-	/**
-	* Static trampoline from the workq context; because we don't have a
-	* generic workq wrapper yet.
-	*
-	* @param arg		Instance pointer for the driver that is polling.
-	*/
-	static void cycle_trampoline(void *arg);
+	static work_s _work;
 
+	int _battery_sub{-1};
+	int _local_position_sub{-1};
+	int _vehicle_status_sub{-1};
 
+	// battery
+	float _battery_voltage_filtered_v{0.f};
+	float _battery_discharge_mah{0.f};
+	bool _battery_valid{false};
+
+	// altitude
+	float _local_position_z{0.f};
+	bool _local_position_valid{false};
+
+	// flight time
+	uint8_t _arming_state{0};
+	uint64_t _arming_timestamp{0};
+
+	DEFINE_PARAMETERS(
+		(ParamInt<px4::params::OSD_ATXXXX_CFG>) _param_atxxxx_cfg
+	)
 };
-/*
- * Driver 'main' command.
- */
-extern "C" __EXPORT int atxxxx_main(int argc, char *argv[]);