Cannot make software serial send more than one character

ChibiOS public support forum for topics related to the Atmel AVR family of micro-controllers.

Moderators: utzig, tfAteba

carldong
Posts: 62
Joined: Wed Oct 15, 2014 6:08 pm
Has thanked: 1 time
Been thanked: 1 time

Re: Cannot make software serial send more than one character

Postby carldong » Fri Mar 31, 2017 1:56 am

utzig wrote:Ok, got it to work!

You won't believe this but the problem is the ISR vector. Your where defining it like this:

Code: Select all

#define AVR_SDS_RX_VECT PCINT0_vect

But it actually is this:

Code: Select all

#define AVR_SDS_RX_VECT INT0_vect

So the MCU was resetting because there was not INT0 handler!

Also in the block where you write EICRA, etc, please change your #if from:

Code: Select all

#if defined AVR_SDS_USE_INT0

to:

Code: Select all

#if AVR_SDS_USE_INT0


Cheers,
Fabio Utzig


Hmm, I made the change, and now it at least look like it is not resetting. However, I always read 255... Did you use the same main function? I am not sure whether I am doing something wrong there.

carldong
Posts: 62
Joined: Wed Oct 15, 2014 6:08 pm
Has thanked: 1 time
Been thanked: 1 time

Re: Cannot make software serial send more than one character

Postby carldong » Fri Mar 31, 2017 2:34 am

Toggling an LED shows that for some reason the system reads an input around every second, even when I am not typing... Now I am afraid that I also need to debug my USB-TTL cable...

Well, I used a 3.3V cable... And switching the board to 3.3V removed this particular issue. However, I still get many spurious characters, returning -1, or MSG_TIMEOUT every second once in a while, despite that I specified a timeout of TIME_INFINITE. Not sure what happened.

carldong
Posts: 62
Joined: Wed Oct 15, 2014 6:08 pm
Has thanked: 1 time
Been thanked: 1 time

Re: Cannot make software serial send more than one character

Postby carldong » Fri Mar 31, 2017 2:36 am

Also, pressing space gives -4, Q_FULL, while anything else gives Q_TIMEOUT, and pressing enter gives nothing... Am I really wrong about how the serial works here?

carldong
Posts: 62
Joined: Wed Oct 15, 2014 6:08 pm
Has thanked: 1 time
Been thanked: 1 time

Re: Cannot make software serial send more than one character

Postby carldong » Fri Mar 31, 2017 2:52 am

AH HAH! I made a mistake in the state transitions, doubling the effective BAUD rate! Now, the full code should be working. I tested single characters and sdRead(). Despite that sometimes it misses characters, probably due to a filled queue buffer, the code should fully work!

Not many configurable options are present, though. Now SDS takes TIM2 and INT0 exclusively, and requires a hand calculation on TIM2 configuration. None of these are hard requirement -- INT1, PCINTx, and TIM0 can all be used. Timers might even be shared too, but that will be another wall of code (which I don't plan on writing). Since I am only using this on a single project, I will leave the code as is for now. I am not using Pastebin because I fear that it might get lost in time. Make patches if you wish to include it into mainstream. I didn't comment a lot, though.

serial_lld.h

Code: Select all

/*
    ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/**
 * @file    AVR/serial_lld.h
 * @brief   AVR low level serial driver header.
 *
 * @addtogroup SERIAL
 * @{
 */

#ifndef _SERIAL_LLD_H_
#define _SERIAL_LLD_H_

#if HAL_USE_SERIAL || defined(__DOXYGEN__)

/*===========================================================================*/
/* Driver constants.                                                         */
/*===========================================================================*/

/*===========================================================================*/
/* Driver pre-compile time settings.                                         */
/*===========================================================================*/

/**
 * @brief   USART0 driver enable switch.
 * @details If set to @p TRUE the support for USART0 is included.
 * @note    The default is @p FALSE.
 */
#if !defined(AVR_SERIAL_USE_USART0) || defined(__DOXYGEN__)
  #define AVR_SERIAL_USE_USART0              TRUE
#endif

/**
 * @brief   USART1 driver enable switch.
 * @details If set to @p TRUE the support for USART1 is included.
 * @note    The default is @p TRUE.
 */
#if !defined(AVR_SERIAL_USE_USART1) || defined(__DOXYGEN__)
#define AVR_SERIAL_USE_USART1              TRUE
#endif

/**
 * @brief   Software Serial driver enable switch.
 * @details If set to @p TRUE the support for Software Serial is included.
 * @note    The default is @p FALSE.
 */
#if !defined(AVR_SERIAL_USE_USARTS) || defined(__DOXYGEN__)
  #define AVR_SERIAL_USE_USARTS              FALSE
  #if !defined(AVR_SDS_USE_INT0) || defined(__DOXYGEN__)
    #define AVR_SDS_USE_INT0 TRUE
  #endif
#endif

/*===========================================================================*/
/* Derived constants and error checks.                                       */
/*===========================================================================*/

/*===========================================================================*/
/* Driver data structures and types.                                         */
/*===========================================================================*/

/**
 * @brief   AVR Serial Driver configuration structure.
 * @details An instance of this structure must be passed to @p sdStart()
 *          in order to configure and start a serial driver operations.
 */
typedef struct {
  /**
   * @brief Initialization value for the BRR register.
   */
  uint16_t                  sc_brr;
  /**
   * @brief Number of bits per character (USART_CHAR_SIZE_5 to USART_CHAR_SIZE_9).
   */
  uint8_t                   sc_bits_per_char;
  /**
   * @brief Top value of OCR2A for soft serial.
   */
  uint8_t                   sc_ocr2a;
  /**
   * @brief Bits of TC2 Control Register which selects divider for soft serial.
   */
  uint8_t                   sc_tccr2b_div;
} SerialConfig;


/**
 * @brief   @p SerialDriver specific data.
 */
#define _serial_driver_data                                                 \
  _base_asynchronous_channel_data                                           \
  /* Driver state.*/                                                        \
  sdstate_t                 state;                                          \
  /* Input queue.*/                                                         \
  input_queue_t             iqueue;                                         \
  /* Output queue.*/                                                        \
  output_queue_t            oqueue;                                         \
  /* Input circular buffer.*/                                               \
  uint8_t                   ib[SERIAL_BUFFERS_SIZE];                        \
  /* Output circular buffer.*/                                              \
  uint8_t                   ob[SERIAL_BUFFERS_SIZE];                        \
  /* End of the mandatory fields.*/

/*===========================================================================*/
/* Driver macros.                                                            */
/*===========================================================================*/

/**
 * @brief   Macro for baud rate computation.
 * @note    Make sure the final baud rate is within tolerance.
 */
#define UBRR(b)     (((F_CPU / b) >> 4) - 1)

/**
 * @brief   Macro for baud rate computation when U2Xn == 1.
 * @note    Make sure the final baud rate is within tolerance.
 */
#define UBRR2x(b)    (((F_CPU / b) >> 3) - 1)

/**
* @brief   Macro for baud rate computation.
* @note    Make sure the final baud rate is within tolerance.
* @note    This version uses floating point math for greater accuracy.
*/
#define UBRR_F(b)   ((((double) F_CPU / (double) b) / 16.0) - 0.5)

/**
* @brief   Macro for baud rate computation when U2Xn == 1.
* @note    Make sure the final baud rate is within tolerance.
* @note    This version uses floating point math for greater accuracy.
*/
#define UBRR2x_F(b)  ((((double) F_CPU / (double) b) / 8.0) - 0.5)

#define USART_CHAR_SIZE_5      0
#define USART_CHAR_SIZE_6      1
#define USART_CHAR_SIZE_7      2
#define USART_CHAR_SIZE_8      3
#define USART_CHAR_SIZE_9      4

/*===========================================================================*/
/* External declarations.                                                    */
/*===========================================================================*/

#if AVR_SERIAL_USE_USART0 && !defined(__DOXYGEN__)
extern SerialDriver SD1;
#endif
#if AVR_SERIAL_USE_USART1 && !defined(__DOXYGEN__)
extern SerialDriver SD2;
#endif
#if AVR_SERIAL_USE_USARTS && !defined(__DOXYGEN__)
extern SerialDriver SDS;
#endif

#ifdef __cplusplus
extern "C" {
#endif
  void sd_lld_init(void);
  void sd_lld_start(SerialDriver *sdp, const SerialConfig *config);
  void sd_lld_stop(SerialDriver *sdp);
#ifdef __cplusplus
}
#endif

#endif /* HAL_USE_SERIAL */

#endif /* _SERIAL_LLD_H_ */

/** @} */


serial_lld.c:

Code: Select all

/*
    ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/**
 * @file    AVR/serial_lld.c
 * @brief   AVR low level serial driver code.
 *
 * @addtogroup SERIAL
 * @{
 */

#include "hal.h"

#if HAL_USE_SERIAL || defined(__DOXYGEN__)

/*===========================================================================*/
/* Driver exported variables.                                                */
/*===========================================================================*/

/**
 * @brief   USART0 serial driver identifier.
 * @note    The name does not follow the convention used in the other ports
 *          (COMn) because a name conflict with the AVR headers.
 */
#if AVR_SERIAL_USE_USART0 || defined(__DOXYGEN__)
SerialDriver SD1;

/* USARTs are not consistently named across the AVR range */
#ifdef USART0_RX_vect
#define AVR_SD1_RX_VECT USART0_RX_vect
#define AVR_SD1_TX_VECT USART0_UDRE_vect
#elif defined(USART_RX_vect)
#define AVR_SD1_RX_VECT USART_RX_vect
#define AVR_SD1_TX_VECT USART_UDRE_vect
#else
#error "Cannot find USART to use for SD1"
#endif
#endif /* AVR_SERIAL_USE_USART0 */

/**
 * @brief   USART1 serial driver identifier.
 * @note    The name does not follow the convention used in the other ports
 *          (COMn) because a name conflict with the AVR headers.
 */
#if AVR_SERIAL_USE_USART1 || defined(__DOXYGEN__)
SerialDriver SD2;

/* Check if USART1 exists for this MCU */
#ifdef USART1_RX_vect
#define AVR_SD2_RX_VECT USART1_RX_vect
#define AVR_SD2_TX_VECT USART1_UDRE_vect
#else
#error "Cannot find USART to use for SD2"
#endif
#endif /* AVR_SERIAL_USE_USART1 */

/**
 * @brief   Software serial driver identifier
 */
#if AVR_SERIAL_USE_USARTS || defined(__DOXYGEN__)
SerialDriver SDS;
#if !AVR_GPT_USE_TIM2
#error "Software serial requires AVR_GPT_USE_TIM2"
#endif
/* Uses INT0*/
#if AVR_SDS_USE_INT0
#define AVR_SDS_RX_PORT IOPORT4
#define AVR_SDS_RX_PIN 2
#define AVR_SDS_RX_VECT INT0_vect
#define AVR_SDS_RX_TCCR2B_CLK_MASK 0b00000111
#endif
/* By default, uses PB1 as TX.*/
#if !defined(AVR_SDS_TX_PORT)
#define AVR_SDS_TX_PORT IOPORT4
#endif
#if !defined(AVR_SDS_TX_PIN)
#define AVR_SDS_TX_PIN 3
#endif
#endif

/*===========================================================================*/
/* Driver local variables and types.                                         */
/*===========================================================================*/

/**
 * @brief SDS state machine
 */
typedef enum {
  SDS_RX_IDLE,
  SDS_RX_WAIT,
  SDS_RX_SAMPLE
} sds_rx_state_t;

typedef enum {
  SDS_TX_IDLE,
  SDS_TX_TRANSMIT,
  SDS_TX_WAIT
} sds_tx_state_t;

/**
 * @brief   Driver default configuration.
 */
static const SerialConfig default_config = {
    UBRR(SERIAL_DEFAULT_BITRATE),
    USART_CHAR_SIZE_8,
    96,
    (1 << CS21)};

/**
 * @brief SDS Timer clock control value
 */
static uint8_t sds_rx_tccr2b_div;

/**
 * @brief SDS UART bits per character
 */
static uint8_t sds_bits_per_char;

static volatile sds_rx_state_t sds_rx_state = SDS_RX_IDLE;

/*===========================================================================*/
/* Driver local functions.                                                   */
/*===========================================================================*/

static void set_error(uint8_t sra, SerialDriver *sdp) {
  eventflags_t sts = 0;
  uint8_t dor = 0;
  uint8_t upe = 0;
  uint8_t fe = 0;

#if AVR_SERIAL_USE_USART0
  if (&SD1 == sdp) {
    dor = (1 << DOR0);
    upe = (1 << UPE0);
    fe = (1 << FE0);
  }
#endif

#if AVR_SERIAL_USE_USART1
  if (&SD2 == sdp) {
    dor = (1 << DOR1);
    upe = (1 << UPE1);
    fe = (1 << FE1);
  }
#endif

  if (sra & dor)
    sts |= SD_OVERRUN_ERROR;
  if (sra & upe)
    sts |= SD_PARITY_ERROR;
  if (sra & fe)
    sts |= SD_FRAMING_ERROR;
  osalSysLockFromISR();
  chnAddFlagsI(sdp, sts);
  osalSysUnlockFromISR();
}

#if AVR_SERIAL_USE_USART0 || defined(__DOXYGEN__)
static void notify1(io_queue_t *qp) {

  (void)qp;
  UCSR0B |= (1 << UDRIE0);
}

/**
 * @brief   USART0 initialization.
 *
 * @param[in] config    the architecture-dependent serial driver configuration
 */
static void usart0_init(const SerialConfig *config) {

  UBRR0L = config->sc_brr;
  UBRR0H = config->sc_brr >> 8;
  UCSR0A = 0;
  UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
  switch (config->sc_bits_per_char) {
  case USART_CHAR_SIZE_5:
    UCSR0C = 0;
    break;
  case USART_CHAR_SIZE_6:
    UCSR0C = (1 << UCSZ00);
    break;
  case USART_CHAR_SIZE_7:
    UCSR0C = (1 << UCSZ01);
    break;
  case USART_CHAR_SIZE_9:
    UCSR0B |= (1 << UCSZ02);
    UCSR0C = (1 << UCSZ00) | (1 << UCSZ01);
    break;
  case USART_CHAR_SIZE_8:
  default:
    UCSR0C = (1 << UCSZ00) | (1 << UCSZ01);
  }
}

/**
 * @brief   USART0 de-initialization.
 */
static void usart0_deinit(void) {

  UCSR0A = 0;
  UCSR0B = 0;
  UCSR0C = 0;
}
#endif

#if AVR_SERIAL_USE_USART1 || defined(__DOXYGEN__)
static void notify2(io_queue_t *qp) {

  (void)qp;
  UCSR1B |= (1 << UDRIE1);
}

/**
 * @brief   USART1 initialization.
 *
 * @param[in] config    the architecture-dependent serial driver configuration
 */
static void usart1_init(const SerialConfig *config) {

  UBRR1L = config->sc_brr;
  UBRR1H = config->sc_brr >> 8;
  UCSR1A = 0;
  UCSR1B = (1 << RXEN1) | (1 << TXEN1) | (1 << RXCIE1);
  switch (config->sc_bits_per_char) {
  case USART_CHAR_SIZE_5:
    UCSR1C = 0;
    break;
  case USART_CHAR_SIZE_6:
    UCSR1C = (1 << UCSZ10);
    break;
  case USART_CHAR_SIZE_7:
    UCSR1C = (1 << UCSZ11);
    break;
  case USART_CHAR_SIZE_9:
    UCSR1B |= (1 << UCSZ12);
    UCSR1C = (1 << UCSZ10) | (1 << UCSZ11);
    break;
  case USART_CHAR_SIZE_8:
  default:
    UCSR1C = (1 << UCSZ10) | (1 << UCSZ11);
  }
}

/**
 * @brief   USART1 de-initialization.
 */
static void usart1_deinit(void) {

  UCSR1A = 0;
  UCSR1B = 0;
  UCSR1C = 0;
}
#endif

#if AVR_SERIAL_USE_USARTS || defined(__DOXYGEN__)

// /**
//  * @brief Generates a single half period. Used in receiving
//  */
// void usartS_start_timer_half(void) {
//   /* Resets counter to half length.*/
//   TCNT2 = OCR2A / 2;
//   /* Start timer.*/
//   TCCR2B &= ~AVR_SDS_RX_TCCR2B_CLK_MASK; /* Clear CLK section.*/
//   TCCR2B |= sds_rx_tccr2b_div;           /* Set CLK setting.*/
// }

void usartS_start_timer(void) {
  /* Reset counter.*/
  TCNT2 = 0;
  /* Start timer.*/
  TCCR2B &= ~AVR_SDS_RX_TCCR2B_CLK_MASK; /* Clear CLK section.*/
  TCCR2B |= sds_rx_tccr2b_div;           /* Set CLK setting.*/
}

void usartS_stop_timer(void) {
  TCCR2B &= ~AVR_SDS_RX_TCCR2B_CLK_MASK;
}

void usartS_reset_timer(void) {
  usartS_stop_timer();
  usartS_start_timer();
}

/**
 * @brief   USARTS initialization.
 *
 * @param[in] config    the architecture-dependent serial driver configuration
 */
static void usartS_init(const SerialConfig *config) {
  /* Sets appropriate I/O mode.*/
  palSetPadMode(AVR_SDS_RX_PORT, AVR_SDS_RX_PIN, PAL_MODE_INPUT);
  palSetPadMode(AVR_SDS_TX_PORT, AVR_SDS_TX_PIN, PAL_MODE_OUTPUT_PUSHPULL);
#if AVR_SDS_USE_INT0
  /* Falling edge of INT0 triggers interrupt.*/
  EICRA |= (1 << ISC01);
  EICRA &= ~(1 << ISC00);
  EIMSK |= 1 << 0;
#endif
  /* Timer 2 CTC mode.*/
  TCCR2A |= 1 << WGM21;
  TCCR2A &= ~((1 << WGM22) | (1 << WGM20));
  /* Save the timer clock input.*/
  sds_rx_tccr2b_div = config->sc_tccr2b_div;
  /* Default to be 8 bit.*/
  switch (config->sc_bits_per_char) {
  default:
    sds_bits_per_char = 8;
  }

  /* Timer 2 Top.*/
  OCR2A = config->sc_ocr2a;
  /* Timer 2 output compare A interrupt.*/
  TIMSK2 |= 1 << OCIE2A;
  usartS_start_timer();
}

/**
* @brief   USART0 de-initialization.
*/
static void usartS_deinit(void) {
  usartS_stop_timer();
  TIMSK2 &= ~(1 << OCIE2A);
  EIMSK &= ~(1 << 0);
}

#endif

/*===========================================================================*/
/* Driver interrupt handlers.                                                */
/*===========================================================================*/

#if AVR_SERIAL_USE_USART0 || defined(__DOXYGEN__)
/**
 * @brief   USART0 RX interrupt handler.
 *
 * @isr
 */
OSAL_IRQ_HANDLER(AVR_SD1_RX_VECT) {
  uint8_t sra;

  OSAL_IRQ_PROLOGUE();

  sra = UCSR0A;
  if (sra & ((1 << DOR0) | (1 << UPE0) | (1 << FE0)))
    set_error(sra, &SD1);
  osalSysLockFromISR();
  sdIncomingDataI(&SD1, UDR0);
  osalSysUnlockFromISR();

  OSAL_IRQ_EPILOGUE();
}

/**
 * @brief   USART0 TX interrupt handler.
 *
 * @isr
 */
OSAL_IRQ_HANDLER(AVR_SD1_TX_VECT) {
  msg_t b;

  OSAL_IRQ_PROLOGUE();
  osalSysLockFromISR();
  b = sdRequestDataI(&SD1);
  osalSysUnlockFromISR();
  if (b < Q_OK)
    UCSR0B &= ~(1 << UDRIE0);
  else
    UDR0 = b;

  OSAL_IRQ_EPILOGUE();
}
#endif /* AVR_SERIAL_USE_USART0 */

#if AVR_SERIAL_USE_USART1 || defined(__DOXYGEN__)
/**
 * @brief   USART1 RX interrupt handler.
 *
 * @isr
 */
OSAL_IRQ_HANDLER(AVR_SD2_RX_VECT) {
  uint8_t sra;

  OSAL_IRQ_PROLOGUE();

  sra = UCSR1A;
  if (sra & ((1 << DOR1) | (1 << UPE1) | (1 << FE1)))
    set_error(sra, &SD2);
  osalSysLockFromISR();
  sdIncomingDataI(&SD2, UDR1);
  osalSysUnlockFromISR();

  OSAL_IRQ_EPILOGUE();
}

/**
 * @brief   USART1 TX interrupt handler.
 *
 * @isr
 */
OSAL_IRQ_HANDLER(AVR_SD2_TX_VECT) {
  msg_t b;

  OSAL_IRQ_PROLOGUE();

  osalSysLockFromISR();
  b = sdRequestDataI(&SD2);
  osalSysUnlockFromISR();
  if (b < Q_OK)
    UCSR1B &= ~(1 << UDRIE1);
  else
    UDR1 = b;

  OSAL_IRQ_EPILOGUE();
}
#endif /* AVR_SERIAL_USE_USART1 */

#if AVR_SERIAL_USE_USARTS || defined(__DOXYGEN__)

/**
 * @brief PCINT interrupt handler
 *
 * @details This handler changes state by sensing the START bit. Otherwise do nothing
 *
 * @isr
 */
OSAL_IRQ_HANDLER(AVR_SDS_RX_VECT) {
  OSAL_IRQ_PROLOGUE();
  chSysDisable();
  switch (sds_rx_state) {
  case SDS_RX_IDLE:
    sds_rx_state = SDS_RX_WAIT;
    break;
  default:
    break;
  }
  chSysEnable();
  OSAL_IRQ_EPILOGUE();
}

/**
 * @brief TIMER2 Comparator A interrupt
 *
 * @details VERY IMPORTANT: Timer is triggered twice per data bit, so 4800 BAUD
 * serial requires 9600Hz clock.
 *
 * @isr
 */
OSAL_IRQ_HANDLER(TIMER2_COMPA_vect) {
  /* Data byte.*/

  OSAL_IRQ_PROLOGUE();
  {
    static int8_t rx_i;
    static uint8_t rx_byte;
    /* RX state machine.*/
    switch (sds_rx_state) {
    case SDS_RX_IDLE:
      rx_i = -1;
      rx_byte = 0;
      /* Do Nothing.*/
      break;
    case SDS_RX_WAIT: /* Waits a clock before sampling*/
      // byte = 0;
      sds_rx_state = SDS_RX_SAMPLE;
      break;
    case SDS_RX_SAMPLE:
      if (rx_i < 0) {
        /* Do nothing, START.*/
      } else if (rx_i < sds_bits_per_char) {
        rx_byte |= palReadPad(AVR_SDS_RX_PORT, AVR_SDS_RX_PIN) << rx_i;
      } else {
        /* If last bit is STOP, then assume info is correct. Otherwise, treat as garbage*/
        if (palReadPad(AVR_SDS_RX_PORT, AVR_SDS_RX_PIN)) {
          osalSysLockFromISR();
          sdIncomingDataI(&SDS, rx_byte);
          osalSysUnlockFromISR();
        }
        rx_byte = 0;
      }
      if (rx_i < sds_bits_per_char) {
        sds_rx_state = SDS_RX_WAIT;
      } else {
        sds_rx_state = SDS_RX_IDLE;
      }
      ++rx_i;
      break;
    }
  }
  /* TX state machine.*/
  {
    static sds_tx_state_t tx_state = SDS_TX_IDLE;
    static int8_t tx_byte;
    static int8_t tx_i;
    switch (tx_state) {
    case SDS_TX_IDLE:
      tx_i = -1;
      osalSysLockFromISR();
      tx_byte = sdRequestDataI(&SDS);
      osalSysUnlockFromISR();
      if (tx_byte >= Q_OK) {
        tx_state = SDS_TX_TRANSMIT;
      }
      break;
    case SDS_TX_TRANSMIT: {
      uint8_t bit;
      /* START.*/
      if (tx_i == -1) {
        bit = 0;
      }
      /* STOP.*/
      else if (tx_i == sds_bits_per_char) {
        bit = 1;
      }
      /* Data.*/
      else {
        bit = (tx_byte & (1 << tx_i)) != 0;
      }
      palWritePad(AVR_SDS_TX_PORT, AVR_SDS_TX_PIN, bit);
      tx_state = SDS_TX_WAIT;
      break;
    }
    case SDS_TX_WAIT:
      if (tx_i == sds_bits_per_char) {
        tx_state = SDS_TX_IDLE;
      } else {
        tx_state = SDS_TX_TRANSMIT;
      }
      ++tx_i;
      break;
    }
  }
  OSAL_IRQ_EPILOGUE();
}

#endif

/*===========================================================================*/
/* Driver exported functions.                                                */
/*===========================================================================*/

/**
 * @brief   Low level serial driver initialization.
 *
 * @notapi
 */
void sd_lld_init(void) {

#if AVR_SERIAL_USE_USART0
  sdObjectInit(&SD1, NULL, notify1);
#endif
#if AVR_SERIAL_USE_USART1
  sdObjectInit(&SD2, NULL, notify2);
#endif
#if AVR_SERIAL_USE_USARTS
  sdObjectInit(&SDS, NULL, NULL);
#endif
}

/**
 * @brief   Low level serial driver configuration and (re)start.
 *
 * @param[in] sdp       pointer to a @p SerialDriver object
 * @param[in] config    the architecture-dependent serial driver configuration.
 *                      If this parameter is set to @p NULL then a default
 *                      configuration is used.
 *
 * @notapi
 */
void sd_lld_start(SerialDriver *sdp, const SerialConfig *config) {

  if (config == NULL)
    config = &default_config;

#if AVR_SERIAL_USE_USART0
  if (&SD1 == sdp) {
    usart0_init(config);
    return;
  }
#endif
#if AVR_SERIAL_USE_USART1
  if (&SD2 == sdp) {
    usart1_init(config);
    return;
  }
#endif
#if AVR_SERIAL_USE_USARTS
  if (&SDS == sdp) {
    usartS_init(config);
    return;
  }
#endif
}

/**
 * @brief   Low level serial driver stop.
 * @details De-initializes the USART, stops the associated clock, resets the
 *          interrupt vector.
 *
 * @param[in] sdp       pointer to a @p SerialDriver object
 *
 * @notapi
 */
void sd_lld_stop(SerialDriver *sdp) {

#if AVR_SERIAL_USE_USART0
  if (&SD1 == sdp)
    usart0_deinit();
#endif
#if AVR_SERIAL_USE_USART1
  if (&SD2 == sdp)
    usart1_deinit();
#endif
#if AVR_SERIAL_USE_USARTS
  if (&SDS == sdp) {
    usartS_deinit();
  }
#endif
}

#endif /* HAL_USE_SERIAL */

/** @} */


And a small bonus here. I modified `i2c_lld_start()` to allow for a full range of clock speed. Take it if you want to merge it in main stream.

i2c_lld.c:

Code: Select all

/*
    ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/**
 * @file    AVR/i2c_lld.c
 * @brief   AVR I2C subsystem low level driver source.
 *
 * @addtogroup I2C
 * @{
 */

#include "hal.h"

#if HAL_USE_I2C || defined(__DOXYGEN__)

/*===========================================================================*/
/* Driver local definitions.                                                 */
/*===========================================================================*/

/*===========================================================================*/
/* Driver exported variables.                                                */
/*===========================================================================*/

/** @brief I2C driver identifier.*/
#if AVR_I2C_USE_I2C1 || defined(__DOXYGEN__)
I2CDriver I2CD1;
#endif

/*===========================================================================*/
/* Driver local variables and types.                                         */
/*===========================================================================*/

/*===========================================================================*/
/* Driver local functions.                                                   */
/*===========================================================================*/

/*===========================================================================*/
/* Driver interrupt handlers.                                                */
/*===========================================================================*/

#if AVR_I2C_USE_I2C1 || defined(__DOXYGEN__)
/**
 * @brief   I2C event interrupt handler.
 *
 * @notapi
 */
OSAL_IRQ_HANDLER(TWI_vect) {
  OSAL_IRQ_PROLOGUE();

  I2CDriver *i2cp = &I2CD1;

  switch (TWSR & 0xF8) {
  case TWI_START:
  case TWI_REPEAT_START:
    TWDR = (i2cp->addr << 1);
    if ((i2cp->txbuf == NULL) || (i2cp->txbytes == 0) || (i2cp->txidx == i2cp->txbytes)) {
      TWDR |= 0x01;
    }
    TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE));
    break;
  case TWI_MASTER_TX_ADDR_ACK:
  case TWI_MASTER_TX_DATA_ACK:
    if (i2cp->txidx < i2cp->txbytes) {
      TWDR = i2cp->txbuf[i2cp->txidx++];
      TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE));
    } else {
      if (i2cp->rxbuf && i2cp->rxbytes) {
        TWCR = ((1 << TWSTA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE));
      } else {
        TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN));
        _i2c_wakeup_isr(i2cp);
      }
    }
    break;
  case TWI_MASTER_RX_ADDR_ACK:
    if (i2cp->rxidx == (i2cp->rxbytes - 1)) {
      TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE));
    } else {
      TWCR = ((1 << TWEA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE));
    }
    break;
  case TWI_MASTER_RX_DATA_ACK:
    i2cp->rxbuf[i2cp->rxidx++] = TWDR;
    if (i2cp->rxidx == (i2cp->rxbytes - 1)) {
      TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE));
    } else {
      TWCR = ((1 << TWEA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE));
    }
    break;
  case TWI_MASTER_RX_DATA_NACK:
    i2cp->rxbuf[i2cp->rxidx] = TWDR;
    TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN));
    _i2c_wakeup_isr(i2cp);
  case TWI_MASTER_TX_ADDR_NACK:
  case TWI_MASTER_TX_DATA_NACK:
  case TWI_MASTER_RX_ADDR_NACK:
    i2cp->errors |= I2C_ACK_FAILURE;
    break;
  case TWI_ARBITRATION_LOST:
    i2cp->errors |= I2C_ARBITRATION_LOST;
    break;
  case TWI_BUS_ERROR:
    i2cp->errors |= I2C_BUS_ERROR;
    break;
  default:
    /* FIXME: only gets here if there are other MASTERs in the bus */
    TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN));
    _i2c_wakeup_error_isr(i2cp);
  }

  if (i2cp->errors != I2C_NO_ERROR) {
    TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN));
    _i2c_wakeup_error_isr(i2cp);
  }

  OSAL_IRQ_EPILOGUE();
}
#endif /* AVR_I2C_USE_I2C1 */

/*===========================================================================*/
/* Driver exported functions.                                                */
/*===========================================================================*/

/**
 * @brief   Low level I2C driver initialization.
 *
 * @notapi
 */
void i2c_lld_init(void) {
  i2cObjectInit(&I2CD1);
  I2CD1.thread = NULL;
}

/**
 * @brief   Configures and activates the I2C peripheral.
 *
 * @param[in] i2cp      pointer to the @p I2CDriver object
 *
 * @notapi
 */
void i2c_lld_start(I2CDriver *i2cp) {
  uint32_t clock_speed = 100000;
  // uint32_t clock_speed = 1000;
  /* TODO: Test TWI without external pull-ups (use internal) */


  if (i2cp->config != NULL)
    clock_speed = i2cp->config->clock_speed;

  uint32_t bdiv = ((F_CPU / clock_speed) - 16 ) / 2;
  if (bdiv < 256) {
    /* Configure prescaler to 1 */
    TWSR &= 0xF8;
    TWBR = bdiv;
  }
  else if (bdiv < 1024) {
    /* Prescale 4.*/
    TWSR &= 0xF9;
    TWSR |= 0x01;
    TWBR = bdiv / 4;
  }
  else if (bdiv < 4096) {
    TWSR &= 0xFA;
    TWSR |= 0x02;
    TWBR = bdiv / 16;
  }
  else {
    TWSR &= 0xFB;
    TWSR |= 0x03;
    TWBR = bdiv / 64;
  }
  /* Configure baudrate */
  // TWBR = ((F_CPU / clock_speed) - 16) / 2;
}

/**
 * @brief   Deactivates the I2C peripheral.
 *
 * @param[in] i2cp      pointer to the @p I2CDriver object
 *
 * @notapi
 */
void i2c_lld_stop(I2CDriver *i2cp) {

  if (i2cp->state != I2C_STOP) {
    /* Disable TWI subsystem and stop all operations */
    TWCR &= ~(1 << TWEN);
  }
}

/**
 * @brief   Receives data via the I2C bus as master.
 *
 * @param[in] i2cp      pointer to the @p I2CDriver object
 * @param[in] addr      slave device address
 * @param[out] rxbuf    pointer to the receive buffer
 * @param[in] rxbytes   number of bytes to be received
 * @param[in] timeout   the number of ticks before the operation timeouts,
 *                      the following special values are allowed:
 *                      - @a TIME_INFINITE no timeout.
 *                      .
 * @return              The operation status.
 * @retval MSG_OK       if the function succeeded.
 * @retval MSG_RESET    if one or more I2C errors occurred, the errors can
 *                      be retrieved using @p i2cGetErrors().
 * @retval MSG_TIMEOUT  if a timeout occurred before operation end. <b>After a
 *                      timeout the driver must be stopped and restarted
 *                      because the bus is in an uncertain state</b>.
 *
 * @notapi
 */
msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr,
                                     uint8_t *rxbuf, size_t rxbytes,
                                     systime_t timeout) {
  i2cp->errors = I2C_NO_ERROR;
  i2cp->addr = addr;
  i2cp->txbuf = NULL;
  i2cp->txbytes = 0;
  i2cp->txidx = 0;
  i2cp->rxbuf = rxbuf;
  i2cp->rxbytes = rxbytes;
  i2cp->rxidx = 0;

  /* Send START */
  TWCR = ((1 << TWSTA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE));

  return osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE);
}

/**
 * @brief   Transmits data via the I2C bus as master.
 *
 * @param[in] i2cp      pointer to the @p I2CDriver object
 * @param[in] addr      slave device address
 * @param[in] txbuf     pointer to the transmit buffer
 * @param[in] txbytes   number of bytes to be transmitted
 * @param[out] rxbuf    pointer to the receive buffer
 * @param[in] rxbytes   number of bytes to be received
 * @param[in] timeout   the number of ticks before the operation timeouts,
 *                      the following special values are allowed:
 *                      - @a TIME_INFINITE no timeout.
 *                      .
 * @return              The operation status.
 * @retval MSG_OK       if the function succeeded.
 * @retval MSG_RESET    if one or more I2C errors occurred, the errors can
 *                      be retrieved using @p i2cGetErrors().
 * @retval MSG_TIMEOUT  if a timeout occurred before operation end. <b>After a
 *                      timeout the driver must be stopped and restarted
 *                      because the bus is in an uncertain state</b>.
 *
 * @notapi
 */
msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr,
                                      const uint8_t *txbuf, size_t txbytes,
                                      uint8_t *rxbuf, size_t rxbytes,
                                      systime_t timeout) {
                                          chDbgAssert(I2CD1.errors == I2C_NO_ERROR, "i2c");
  i2cp->errors = I2C_NO_ERROR;
  i2cp->addr = addr;
  i2cp->txbuf = txbuf;
  i2cp->txbytes = txbytes;
  i2cp->txidx = 0;
  i2cp->rxbuf = rxbuf;
  i2cp->rxbytes = rxbytes;
  i2cp->rxidx = 0;

  TWCR = ((1 << TWSTA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE));

  return osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE);
}

#endif /* HAL_USE_I2C */

/** @} */


carldong
Posts: 62
Joined: Wed Oct 15, 2014 6:08 pm
Has thanked: 1 time
Been thanked: 1 time

Re: Cannot make software serial send more than one character

Postby carldong » Fri Mar 31, 2017 3:00 am

Oh, right -- It still misses a few characters occasionally. But for my application that is fine. I may put some more time to fix it, though.

Just a note: `sdRead()` has inconsistent results. However, `sdGet()` has no problem at all. Since I haven't figured out how to use `sdRead()` yet, I assume that it is my wrong usage causing the problem.

Now we have a full duplex software serial port, great!

Marco
Posts: 124
Joined: Tue Apr 16, 2013 8:22 pm
Has thanked: 4 times
Been thanked: 11 times

Re: Cannot make software serial send more than one character

Postby Marco » Fri Mar 31, 2017 2:46 pm

Nice work!
Maybe this driver can be used to make a hardware independent software serial driver later on?
I think it could be a complex driver on top of the EXT, GPT and PAL drivers.

carldong
Posts: 62
Joined: Wed Oct 15, 2014 6:08 pm
Has thanked: 1 time
Been thanked: 1 time

Re: Cannot make software serial send more than one character

Postby carldong » Fri Mar 31, 2017 6:00 pm

Marco wrote:Nice work!
Maybe this driver can be used to make a hardware independent software serial driver later on?
I think it could be a complex driver on top of the EXT, GPT and PAL drivers.


I would say, maybe share timer with sysclock. Unless it is doing things like 115k Baud, the overhead should be small enough. Also, there are possibility to add arbitrary number using PCIINT interrupts instead of INT0, while sharing timers.

utzig
Posts: 353
Joined: Sat Jan 07, 2012 6:22 pm
Location: Brazil
Has thanked: 1 time
Been thanked: 15 times
Contact:

Re: Cannot make software serial send more than one character

Postby utzig » Sat Apr 01, 2017 4:29 pm

Marco wrote:Maybe this driver can be used to make a hardware independent software serial driver later on?
I think it could be a complex driver on top of the EXT, GPT and PAL drivers.

I'm with you on this. I'll try to clean it up and merge it initially as an AVR driver, but next we can step-by-step change the low-level direct access of registers, etc to use the other driver interfaces up to the point where no more AVR dependencies exist which would enable it to be moved somewhere else in the tree.

Cheers,
Fabio Utzig


Return to “AVR Support”

Who is online

Users browsing this forum: No registered users and 3 guests