mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
509 lines
14 KiB
C
509 lines
14 KiB
C
/*
|
|
* Copyright (C) 2017 Inria
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser General
|
|
* Public License v2.1. See the file LICENSE in the top level directory for more
|
|
* details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_ata8520e
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Device driver for Microchip ATA8520E transceiver
|
|
*
|
|
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
|
|
#include "ztimer.h"
|
|
#include "fmt.h"
|
|
|
|
#include "periph/spi.h"
|
|
#include "periph/gpio.h"
|
|
|
|
#include "board.h"
|
|
#include "ata8520e_internals.h"
|
|
#include "ata8520e.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
/* Warning: to correctly display the debug messages in callbacks,
|
|
add CFLAGS+=-DTHREAD_STACKSIZE_IDLE=THREAD_STACKSIZE_DEFAULT to the build
|
|
command.
|
|
*/
|
|
#include "debug.h"
|
|
|
|
#define SPIDEV (dev->params.spi)
|
|
#define CSPIN (dev->params.cs_pin)
|
|
#define INTPIN (dev->params.int_pin)
|
|
#define RESETPIN (dev->params.reset_pin)
|
|
#define POWERPIN (dev->params.power_pin)
|
|
#define SPI_CS_DELAY_MS (1)
|
|
#define TX_TIMEOUT (8U) /* 8 s */
|
|
#define TX_RX_TIMEOUT (50U) /* 50 s */
|
|
|
|
static void _print_atmel_status(uint8_t status)
|
|
{
|
|
DEBUG("[ata8520e] Atmel status: %d\n", status);
|
|
if (status & ATA8520E_ATMEL_PA_MASK) {
|
|
DEBUG("[ata8520e] Atmel: PA ON\n");
|
|
}
|
|
else {
|
|
DEBUG("[ata8520e] Atmel: PA OFF\n");
|
|
}
|
|
if ((status >> 1) & ATA8520E_ATMEL_SYSTEM_READY_MASK) {
|
|
DEBUG("[ata8520e] Atmel: System ready to operate\n");
|
|
return;
|
|
}
|
|
if ((status >> 1) & ATA8520E_ATMEL_FRAME_SENT_MASK) {
|
|
DEBUG("[ata8520e] Atmel: Frame sent\n");
|
|
return;
|
|
}
|
|
switch ((status >> 1) & 0x0F) {
|
|
case ATA8520E_ATMEL_OK:
|
|
DEBUG("[ata8520e] Atmel: System is ready\n");
|
|
break;
|
|
case ATA8520E_ATMEL_COMMAND_ERROR:
|
|
DEBUG("[ata8520e] Atmel: Command error / not supported\n");
|
|
break;
|
|
case ATA8520E_ATMEL_GENERIC_ERROR:
|
|
DEBUG("[ata8520e] Atmel: Generic error\n");
|
|
break;
|
|
case ATA8520E_ATMEL_FREQUENCY_ERROR:
|
|
DEBUG("[ata8520e] Atmel: Frequency error\n");
|
|
break;
|
|
case ATA8520E_ATMEL_USAGE_ERROR:
|
|
DEBUG("[ata8520e] Atmel: Usage error\n");
|
|
break;
|
|
case ATA8520E_ATMEL_OPENING_ERROR:
|
|
DEBUG("[ata8520e] Atmel: Opening error\n");
|
|
break;
|
|
case ATA8520E_ATMEL_CLOSING_ERROR:
|
|
DEBUG("[ata8520e] Atmel: Closing error\n");
|
|
break;
|
|
case ATA8520E_ATMEL_SEND_ERROR:
|
|
DEBUG("[ata8520e] Atmel: Send error\n");
|
|
break;
|
|
default:
|
|
DEBUG("[ata8520e] Atmel: Unknown status code '%02X'\n",
|
|
(status >> 1) & 0x0F);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void _print_sigfox_status(uint8_t status)
|
|
{
|
|
DEBUG("[ata8520e] Sigfox status: %d\n", status);
|
|
switch (status) {
|
|
case ATA8520E_SIGFOX_NO_ERROR:
|
|
DEBUG("[ata8520e] Sigfox: OK\n");
|
|
break;
|
|
case ATA8520E_SIGFOX_TX_LEN_TOO_LONG:
|
|
DEBUG("[ata8520e] Sigfox: TX data length exceeds 12 bytes\n");
|
|
break;
|
|
case ATA8520E_SIGFOX_RX_TIMEOUT:
|
|
DEBUG("[ata8520e] Sigfox: Timeout for downlink message\n");
|
|
break;
|
|
case ATA8520E_SIGFOX_RX_BIT_TIMEOUT:
|
|
DEBUG("[ata8520e] Sigfox: Timeout for bit downlink\n");
|
|
break;
|
|
case ATA8520E_SIGFOX2_INIT_ERROR:
|
|
DEBUG("[ata8520e] Sigfox2: Initialization error\n");
|
|
break;
|
|
case ATA8520E_SIGFOX2_TX_ERROR:
|
|
DEBUG("[ata8520e] Sigfox2: Error during send\n");
|
|
break;
|
|
case ATA8520E_SIGFOX2_RF_ERROR:
|
|
DEBUG("[ata8520e] Sigfox2: Error in RF frequency\n");
|
|
break;
|
|
case ATA8520E_SIGFOX2_DF_WAIT_ERROR:
|
|
DEBUG("[ata8520e] Sigfox2: Error during wait for data frame\n");
|
|
break;
|
|
default:
|
|
DEBUG("[ata8520e] Sigfox: Internal error '%02X'\n", status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void _irq_handler(void *arg)
|
|
{
|
|
(void) arg;
|
|
ata8520e_t * dev = (ata8520e_t *)arg;
|
|
DEBUG("[ata8520e] Event received !\n");
|
|
dev->event_received = 1;
|
|
switch (dev->internal_state) {
|
|
case ATA8520E_STATE_IDLE:
|
|
DEBUG("[ata8520e] IDLE state\n");
|
|
break;
|
|
case ATA8520E_STATE_INIT:
|
|
DEBUG("[ata8520e] INIT state\n");
|
|
break;
|
|
case ATA8520E_STATE_TX:
|
|
DEBUG("[ata8520e] TX state\n");
|
|
break;
|
|
case ATA8520E_STATE_RX:
|
|
DEBUG("[ata8520e] RX state\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void _getbus(const ata8520e_t *dev)
|
|
{
|
|
spi_acquire(SPIDEV, CSPIN, SPI_MODE_0, dev->params.spi_clk);
|
|
}
|
|
|
|
static void _spi_transfer_byte(const ata8520e_t *dev, bool cont, uint8_t out)
|
|
{
|
|
/* Manually triggering CS because of a required delay, see datasheet,
|
|
section 2.1.1, page 10 */
|
|
gpio_clear((gpio_t)CSPIN);
|
|
ztimer_sleep(ZTIMER_MSEC, SPI_CS_DELAY_MS);
|
|
spi_transfer_byte(SPIDEV, SPI_CS_UNDEF, cont, out);
|
|
ztimer_sleep(ZTIMER_MSEC, SPI_CS_DELAY_MS);
|
|
gpio_set((gpio_t)CSPIN);
|
|
}
|
|
|
|
static void _spi_transfer_bytes(const ata8520e_t *dev, bool cont,
|
|
const void *out, void *in, size_t len)
|
|
{
|
|
/* Manually triggering CS because of a required delay, see datasheet,
|
|
section 2.1.1, page 10 */
|
|
gpio_clear((gpio_t)CSPIN);
|
|
ztimer_sleep(ZTIMER_MSEC, SPI_CS_DELAY_MS);
|
|
spi_transfer_bytes(SPIDEV, SPI_CS_UNDEF, cont, out, in, len);
|
|
ztimer_sleep(ZTIMER_MSEC, SPI_CS_DELAY_MS);
|
|
gpio_set((gpio_t)CSPIN);
|
|
}
|
|
|
|
static void _send_command(const ata8520e_t *dev, uint8_t command)
|
|
{
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, false, command);
|
|
spi_release(SPIDEV);
|
|
}
|
|
|
|
static void _status(const ata8520e_t *dev)
|
|
{
|
|
/* clear the event line and check the device status,
|
|
see datasheet, section 2.1.2.10, page 12 */
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_GET_STATUS);
|
|
_spi_transfer_byte(dev, true, 0);
|
|
_spi_transfer_byte(dev, true, 0); /* SSM unused */
|
|
uint8_t atmel = spi_transfer_byte(SPIDEV, CSPIN, true, 0);
|
|
uint8_t sigfox = spi_transfer_byte(SPIDEV, CSPIN, true, 0);
|
|
uint8_t sigfox2 = spi_transfer_byte(SPIDEV, CSPIN, false, 0);
|
|
spi_release(SPIDEV);
|
|
|
|
if (IS_ACTIVE(ENABLE_DEBUG)) {
|
|
_print_atmel_status(atmel);
|
|
_print_sigfox_status(sigfox);
|
|
_print_sigfox_status(sigfox2);
|
|
}
|
|
else {
|
|
(void)atmel;
|
|
(void)sigfox;
|
|
(void)sigfox2;
|
|
}
|
|
}
|
|
|
|
static void _reset(const ata8520e_t *dev)
|
|
{
|
|
gpio_set(RESETPIN);
|
|
ztimer_sleep(ZTIMER_MSEC, 10);
|
|
gpio_clear(RESETPIN);
|
|
ztimer_sleep(ZTIMER_MSEC, 10);
|
|
gpio_set(RESETPIN);
|
|
}
|
|
|
|
static void _poweron(const ata8520e_t *dev)
|
|
{
|
|
/* power up procedure, see datasheet, section 2.2.1, page 24 */
|
|
gpio_set(POWERPIN);
|
|
_reset(dev);
|
|
}
|
|
|
|
static void _poweroff(const ata8520e_t *dev)
|
|
{
|
|
/* power down procedure, see datasheet, section 2.2.2, page 24 */
|
|
_status(dev);
|
|
gpio_clear(POWERPIN);
|
|
_send_command(dev, ATA8520E_OFF_MODE);
|
|
}
|
|
|
|
int ata8520e_init(ata8520e_t *dev, const ata8520e_params_t *params)
|
|
{
|
|
/* write config params to device descriptor */
|
|
dev->params = *params;
|
|
|
|
/* Initialize pins*/
|
|
if (gpio_init_int(INTPIN, GPIO_IN_PD,
|
|
GPIO_FALLING, _irq_handler, dev) < 0 ) {
|
|
DEBUG("[ata8520e] ERROR: Interrupt pin not initialized\n");
|
|
return -ATA8520E_ERR_GPIO_INT;
|
|
}
|
|
if (gpio_init(POWERPIN, GPIO_OUT) < 0) {
|
|
DEBUG("[ata8520e] ERROR: Power pin not initialized\n");
|
|
return -ATA8520E_ERR_GPIO_POWER;
|
|
}
|
|
if (gpio_init(RESETPIN, GPIO_OUT) < 0) {
|
|
DEBUG("[ata8520e] ERROR: Reset pin not initialized\n");
|
|
return -ATA8520E_ERR_GPIO_RESET;
|
|
}
|
|
|
|
dev->internal_state = ATA8520E_STATE_INIT;
|
|
_poweron(dev);
|
|
|
|
/* Initialize SPI bus*/
|
|
if (spi_init_cs(dev->params.spi, CSPIN) < 0) {
|
|
DEBUG("[ata8520e] ERROR: SPI not initialized\n");
|
|
return -ATA8520E_ERR_SPI;
|
|
}
|
|
|
|
ztimer_sleep(ZTIMER_MSEC, 100);
|
|
|
|
if (IS_ACTIVE(ENABLE_DEBUG)) {
|
|
char sigfox_id[SIGFOX_ID_LENGTH + 1];
|
|
ata8520e_read_id(dev, sigfox_id);
|
|
|
|
char sigfox_pac[SIGFOX_PAC_LENGTH + 1];
|
|
ata8520e_read_pac(dev, sigfox_pac);
|
|
|
|
uint8_t atmel_version[2];
|
|
ata8520e_read_atmel_version(dev, atmel_version);
|
|
|
|
char sigfox_version[11];
|
|
ata8520e_read_sigfox_version(dev, sigfox_version);
|
|
|
|
DEBUG("[ata8520e] Atmel version: %d.%d\n",
|
|
atmel_version[0], atmel_version[1]);
|
|
DEBUG("[ata8520e] Sigfox version: %s\n", sigfox_version);
|
|
DEBUG("[ata8520e] Sigfox ID: %s\n", sigfox_id);
|
|
DEBUG("[ata8520e] Sigfox PAC: %s\n", sigfox_pac);
|
|
}
|
|
|
|
/* clear event line */
|
|
_status(dev);
|
|
|
|
dev->internal_state = ATA8520E_STATE_IDLE;
|
|
|
|
_poweroff(dev);
|
|
|
|
return ATA8520E_OK;
|
|
}
|
|
|
|
void ata8520e_system_reset(const ata8520e_t *dev)
|
|
{
|
|
_send_command(dev, ATA8520E_SYSTEM_RESET);
|
|
}
|
|
|
|
void ata8520e_read_atmel_version(const ata8520e_t *dev, uint8_t *version)
|
|
{
|
|
_poweron(dev);
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_ATMEL_VERSION);
|
|
_spi_transfer_byte(dev, true, 0);
|
|
_spi_transfer_bytes(dev, false, 0, version, 2);
|
|
spi_release(SPIDEV);
|
|
_poweroff(dev);
|
|
}
|
|
|
|
void ata8520e_read_sigfox_version(const ata8520e_t *dev, char *version)
|
|
{
|
|
_poweron(dev);
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_SIGFOX_VERSION);
|
|
_spi_transfer_byte(dev, true, 0);
|
|
_spi_transfer_bytes(dev, false, 0, version, 11);
|
|
spi_release(SPIDEV);
|
|
_poweroff(dev);
|
|
}
|
|
|
|
void ata8520e_read_pac(const ata8520e_t *dev, char *pac)
|
|
{
|
|
_poweron(dev);
|
|
uint8_t pac_bytes[SIGFOX_PAC_LENGTH];
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_GET_PAC);
|
|
_spi_transfer_byte(dev, true, 0);
|
|
_spi_transfer_bytes(dev, false, NULL, pac_bytes, SIGFOX_PAC_LENGTH / 2);
|
|
spi_release(SPIDEV);
|
|
fmt_bytes_hex(pac, pac_bytes, SIGFOX_PAC_LENGTH >> 1);
|
|
pac[SIGFOX_PAC_LENGTH] = '\0';
|
|
_poweroff(dev);
|
|
}
|
|
|
|
void ata8520e_read_id(const ata8520e_t *dev, char *id)
|
|
{
|
|
_poweron(dev);
|
|
uint8_t id_bytes[SIGFOX_ID_LENGTH];
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_GET_ID);
|
|
_spi_transfer_byte(dev, true, 0);
|
|
_spi_transfer_bytes(dev, false, NULL, id_bytes, SIGFOX_ID_LENGTH);
|
|
spi_release(SPIDEV);
|
|
/* create id string (4 hexadecimal values in reversed order) */
|
|
fmt_bytes_hex_reverse(id, id_bytes, SIGFOX_ID_LENGTH >> 1);
|
|
id[SIGFOX_ID_LENGTH] = '\0';
|
|
_poweroff(dev);
|
|
}
|
|
|
|
static void isr_event_timeout(void *arg)
|
|
{
|
|
ata8520e_t *dev = (ata8520e_t *)arg;
|
|
mutex_unlock(&(dev->event_lock));
|
|
}
|
|
|
|
static bool _wait_event(ata8520e_t *dev, uint8_t timeout)
|
|
{
|
|
dev->event_received = 0;
|
|
ztimer_now_t start_time = ztimer_now(ZTIMER_MSEC);
|
|
ztimer_t event_timer;
|
|
event_timer.callback = isr_event_timeout;
|
|
event_timer.arg = dev;
|
|
ztimer_set(ZTIMER_MSEC, &event_timer, (uint32_t)timeout);
|
|
|
|
/* waiting for the event */
|
|
while ((!dev->event_received) &&
|
|
((int32_t)(start_time + timeout - ztimer_now(ZTIMER_MSEC)) > 0)) {
|
|
mutex_lock(&(dev->event_lock));
|
|
}
|
|
ztimer_remove(ZTIMER_MSEC, &event_timer);
|
|
|
|
if (dev->event_received == 0) {
|
|
DEBUG("[ata8520e] event timeout\n");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void _prepare_send_frame(ata8520e_t *dev, uint8_t *msg, uint8_t msg_len)
|
|
{
|
|
_poweron(dev);
|
|
ztimer_sleep(ZTIMER_MSEC, 5);
|
|
_status(dev);
|
|
|
|
/* Verify message length */
|
|
if (msg_len > SIGFOX_MAX_TX_LENGTH) {
|
|
DEBUG("[ata8520e] Message exceeds the maximum %d characters length "
|
|
"allowed. It will be truncated.\n", SIGFOX_MAX_TX_LENGTH);
|
|
msg_len = SIGFOX_MAX_TX_LENGTH;
|
|
}
|
|
|
|
dev->internal_state = ATA8520E_STATE_TX;
|
|
|
|
/* Write message in TX buffer */
|
|
DEBUG("[ata8520e] Writing send frame to RX buffer\n");
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_WRITE_TX_BUFFER);
|
|
_spi_transfer_byte(dev, true, msg_len);
|
|
_spi_transfer_bytes(dev, false, msg, NULL, msg_len);
|
|
spi_release(SPIDEV);
|
|
}
|
|
|
|
static int _wait_send(ata8520e_t *dev, uint8_t timeout)
|
|
{
|
|
DEBUG("[ata8520e] waiting for TX to complete\n");
|
|
/* Wait 8s maximum for the message to be sent */
|
|
int ret = ATA8520E_OK;
|
|
if (_wait_event(dev, timeout)) {
|
|
DEBUG("[ata8520e] failed to send message\n");
|
|
ret = -ATA8520E_ERR_EVENT_TIMEOUT;
|
|
}
|
|
else {
|
|
DEBUG("[ata8520e] message sent\n");
|
|
}
|
|
|
|
/* Clear event line */
|
|
_status(dev);
|
|
|
|
/* back to idle state */
|
|
dev->internal_state = ATA8520E_STATE_IDLE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ata8520e_send_frame(ata8520e_t *dev, uint8_t *msg, uint8_t msg_len)
|
|
{
|
|
DEBUG("[ata8520e] Sending frame '%s', length: %d\n", (char*)msg, msg_len);
|
|
_prepare_send_frame(dev, msg, msg_len);
|
|
|
|
/* Trigger TX */
|
|
_send_command(dev, ATA8520E_SEND_FRAME);
|
|
|
|
int ret = _wait_send(dev, TX_TIMEOUT);
|
|
|
|
/* switch off transceiver before returning */
|
|
_poweroff(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ata8520e_send_receive_frame(ata8520e_t *dev, uint8_t *msg, uint8_t msg_len,
|
|
uint8_t *rx_payload)
|
|
{
|
|
DEBUG("[ata8520e] Sending frame '%s', length: %d\n", (char*)msg, msg_len);
|
|
_prepare_send_frame(dev, msg, msg_len);
|
|
|
|
/* Trigger TX */
|
|
_send_command(dev, ATA8520E_SEND_RECEIVE_FRAME);
|
|
|
|
int ret = _wait_send(dev, TX_RX_TIMEOUT);
|
|
if (ret == -ATA8520E_ERR_EVENT_TIMEOUT) {
|
|
return ret;
|
|
}
|
|
|
|
/* Read RX message */
|
|
dev->internal_state = ATA8520E_STATE_RX;
|
|
DEBUG("[ata8520e] Reading RX buffer\n");
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_READ_RX_BUFFER);
|
|
_spi_transfer_byte(dev, true, 0);
|
|
_spi_transfer_bytes(dev, false, NULL, rx_payload, SIGFOX_RX_LENGTH);
|
|
spi_release(SPIDEV);
|
|
|
|
/* back to idle state */
|
|
dev->internal_state = ATA8520E_STATE_IDLE;
|
|
|
|
/* switch off transceiver before returning */
|
|
_poweroff(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ata8520e_send_bit(ata8520e_t *dev, bool bit)
|
|
{
|
|
DEBUG("[ata8520e] Sending bit '%d'\n", bit);
|
|
_poweron(dev);
|
|
ztimer_sleep(ZTIMER_MSEC, 5);
|
|
_status(dev);
|
|
|
|
dev->internal_state = ATA8520E_STATE_TX;
|
|
|
|
/* Sends the bit */
|
|
_getbus(dev);
|
|
_spi_transfer_byte(dev, true, ATA8520E_SEND_BIT);
|
|
if (bit) {
|
|
_spi_transfer_byte(dev, false, 0x01);
|
|
}
|
|
else {
|
|
_spi_transfer_byte(dev, false, 0x00);
|
|
}
|
|
spi_release(SPIDEV);
|
|
|
|
int ret = _wait_send(dev, TX_TIMEOUT);
|
|
|
|
/* switch off transceiver before returning */
|
|
_poweroff(dev);
|
|
|
|
return ret;
|
|
}
|