cb243c36a8
* Change MIT license texts to SPDX-License-Identifier SPDX-License-Identifier is much easier to understand and grep than license text so use that instead. * Change GPL exception license texts to SPDX-License-Identifier SPDX-License-Identifier is much easier to understand and grep than license text so use that instead. * Change misc license texts to SPDX-License-Identifier There are some external code in repo which are not licenses as most of the stuff in this repo. We still want every file to have SPDX identifier to easily grep licenses. * Add currently used license files Even though Bacnet-Stack is using SPDX identifiers we still need to give those license files with source. For this reason add all license files to license/ folder. SPDX has also files which would make same thing but this is style which example Linux kernel is using and it is quite clear so I choose that one for now. I choosed not yet bring CC-PDDC as that is not right license for those files. --------- Co-authored-by: Kari Argillander <kari.argillander@fidelix.com>
342 lines
11 KiB
C
342 lines
11 KiB
C
/**************************************************************************
|
|
*
|
|
* Copyright (C) 2009 Steve Karg <skarg@users.sourceforge.net>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
*********************************************************************/
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include "hardware.h"
|
|
#include "bacnet/basic/sys/fifo.h"
|
|
#include "bacnet/basic/sys/mstimer.h"
|
|
#include "led.h"
|
|
#include "nvdata.h"
|
|
/* me */
|
|
#include "rs485.h"
|
|
|
|
/* baud rate */
|
|
static uint32_t Baud_Rate = 9600;
|
|
|
|
/* The minimum time after the end of the stop bit of the final octet of a */
|
|
/* received frame before a node may enable its EIA-485 driver: 40 bit times. */
|
|
/* At 9600 baud, 40 bit times would be about 4.166 milliseconds */
|
|
/* At 19200 baud, 40 bit times would be about 2.083 milliseconds */
|
|
/* At 38400 baud, 40 bit times would be about 1.041 milliseconds */
|
|
/* At 57600 baud, 40 bit times would be about 0.694 milliseconds */
|
|
/* At 76800 baud, 40 bit times would be about 0.520 milliseconds */
|
|
/* At 115200 baud, 40 bit times would be about 0.347 milliseconds */
|
|
/* 40 bits is 4 octets including a start and stop bit with each octet */
|
|
#define Tturnaround (40UL)
|
|
/* turnaround_time_milliseconds = (Tturnaround*1000UL)/Baud_Rate; */
|
|
|
|
/* buffer for storing received bytes - size must be power of two */
|
|
static uint8_t Receive_Buffer_Data[256];
|
|
static FIFO_BUFFER Receive_Buffer;
|
|
|
|
static struct mstimer Silence_Timer;
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Determines the amount of silence time elapsed
|
|
* RETURN: true if the amount of silence time has elapsed
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
bool rs485_silence_time_elapsed(uint16_t milliseconds)
|
|
{
|
|
return (mstimer_elapsed(&Silence_Timer) > milliseconds);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Resets the silence timer
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
void rs485_silence_time_reset(void)
|
|
{
|
|
mstimer_set(&Silence_Timer, 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Configures the RTS output
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
static void rs485_rts_init(void)
|
|
{
|
|
/* configure the port pin as an output */
|
|
BIT_SET(DDRD, DDD4);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: enable the transmit-enable line on the RS-485 transceiver
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
void rs485_rts_enable(bool enable)
|
|
{
|
|
if (enable) {
|
|
BIT_SET(PORTD, PD4);
|
|
} else {
|
|
BIT_CLEAR(PORTD, PD4);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: enable the UART receiver and interrupt
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
static void rs485_receiver_enable(void)
|
|
{
|
|
UCSR0B = _BV(TXEN0) | _BV(RXEN0) | _BV(RXCIE0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: delay for 40 bit times
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
void rs485_turnaround_delay(void)
|
|
{
|
|
uint8_t nbytes = 4;
|
|
|
|
/* delay after reception before trasmitting - per MS/TP spec */
|
|
/* Transmit 4 dummy bytes with RS485 driver off.
|
|
This equals the 40 bit times (1 start, 8 data, 1 stop). */
|
|
rs485_rts_enable(false);
|
|
while (nbytes) {
|
|
/* Send the data byte */
|
|
UDR0 = 0xff;
|
|
while (!BIT_CHECK(UCSR0A, UDRE0)) {
|
|
/* do nothing - wait until Tx buffer is empty */
|
|
}
|
|
nbytes--;
|
|
}
|
|
while (!BIT_CHECK(UCSR0A, TXC0)) {
|
|
/* do nothing - wait until the entire frame in the
|
|
Transmit Shift Register has been shifted out */
|
|
}
|
|
/* Clear the Transmit Complete flag by writing a one to it. */
|
|
BIT_SET(UCSR0A, TXC0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Interrupt service routine for UART Receiver
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
ISR(USART0_RX_vect)
|
|
{
|
|
uint8_t data_byte;
|
|
|
|
if (BIT_CHECK(UCSR0A, RXC0)) {
|
|
/* data is available */
|
|
data_byte = UDR0;
|
|
#ifdef MSTP_MONITOR
|
|
UDR1 = data_byte;
|
|
#endif
|
|
(void)FIFO_Put(&Receive_Buffer, data_byte);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Checks for data on the receive UART, and handles errors
|
|
* RETURN: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
bool rs485_byte_available(uint8_t *data_register)
|
|
{
|
|
bool data_available = false; /* return value */
|
|
|
|
if (!FIFO_Empty(&Receive_Buffer)) {
|
|
led_on_interval(LED_4, 1);
|
|
if (data_register) {
|
|
*data_register = FIFO_Get(&Receive_Buffer);
|
|
}
|
|
data_available = true;
|
|
}
|
|
|
|
return data_available;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: returns an error indication if errors are enabled
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
bool rs485_receive_error(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Transmits a frame using the UART
|
|
* RETURN: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
void rs485_bytes_send(uint8_t *buffer, /* data to send */
|
|
uint16_t nbytes)
|
|
{ /* number of bytes of data */
|
|
led_on(LED_5);
|
|
while (!BIT_CHECK(UCSR0A, UDRE0)) {
|
|
/* do nothing - wait until Tx buffer is empty */
|
|
}
|
|
while (nbytes) {
|
|
/* Send the data byte */
|
|
UDR0 = *buffer;
|
|
#ifdef MSTP_MONITOR
|
|
UDR1 = *buffer;
|
|
#endif
|
|
while (!BIT_CHECK(UCSR0A, UDRE0)) {
|
|
/* do nothing - wait until Tx buffer is empty */
|
|
}
|
|
buffer++;
|
|
nbytes--;
|
|
}
|
|
/* was the frame sent? */
|
|
while (!BIT_CHECK(UCSR0A, TXC0)) {
|
|
/* do nothing - wait until the entire frame in the
|
|
Transmit Shift Register has been shifted out */
|
|
}
|
|
/* Clear the Transmit Complete flag by writing a one to it. */
|
|
BIT_SET(UCSR0A, TXC0);
|
|
rs485_silence_time_reset();
|
|
led_off_delay(LED_5, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Returns the baud rate that we are currently running at
|
|
* RETURN: baud rate in bps
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
uint32_t rs485_baud_rate(void)
|
|
{
|
|
return Baud_Rate;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: configure the UART baud rate
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
static void rs485_baud_rate_configure(void)
|
|
{
|
|
/* 2x speed mode */
|
|
BIT_SET(UCSR0A, U2X0);
|
|
/* configure baud rate */
|
|
UBRR0 = (F_CPU / (8UL * Baud_Rate)) - 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: set the UART baud rate to a standard value
|
|
* RETURN: true if the baud rate is valid
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
bool rs485_baud_rate_set(uint32_t baud)
|
|
{
|
|
bool valid = true;
|
|
uint8_t baud_k = 0;
|
|
|
|
switch (baud) {
|
|
case 9600:
|
|
case 19200:
|
|
case 38400:
|
|
case 57600:
|
|
case 76800:
|
|
case 115200:
|
|
Baud_Rate = baud;
|
|
rs485_baud_rate_configure();
|
|
/* store the baud rate */
|
|
baud_k = baud / 1000;
|
|
eeprom_bytes_write(NV_EEPROM_BAUD_K, &baud_k, 1);
|
|
break;
|
|
default:
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: initialize the hardware UART
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
static void rs485_usart_init(void)
|
|
{
|
|
/* enable the internal pullup on RXD0 */
|
|
BIT_CLEAR(DDRD, DDD0);
|
|
BIT_SET(PORTD, PD0);
|
|
/* enable Transmit and Receive */
|
|
UCSR0B = _BV(TXEN0) | _BV(RXEN0);
|
|
/* Set USART Control and Status Register n C */
|
|
/* Asynchronous USART 8-bit data, No parity, 1 stop */
|
|
/* Set USART Mode Select: UMSELn1 UMSELn0 = 00 for Asynchronous USART */
|
|
/* Set Parity Mode: UPMn1 UPMn0 = 00 for Parity Disabled */
|
|
/* Set Stop Bit Select: USBSn = 0 for 1 stop bit */
|
|
/* Set Character Size: UCSZn2 UCSZn1 UCSZn0 = 011 for 8-bit */
|
|
/* Clock Polarity: UCPOLn = 0 when asynchronous mode is used. */
|
|
UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
|
|
power_usart0_enable();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: read any non-volatile data
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
static void rs485_init_nvdata(void)
|
|
{
|
|
uint8_t baud_k = 9; /* from EEPROM value */
|
|
|
|
eeprom_bytes_read(NV_EEPROM_BAUD_K, &baud_k, 1);
|
|
switch (baud_k) {
|
|
case 9:
|
|
Baud_Rate = 9600;
|
|
break;
|
|
case 19:
|
|
Baud_Rate = 19200;
|
|
break;
|
|
case 38:
|
|
Baud_Rate = 38400;
|
|
break;
|
|
case 57:
|
|
Baud_Rate = 57600;
|
|
break;
|
|
case 76:
|
|
Baud_Rate = 76800;
|
|
break;
|
|
case 115:
|
|
Baud_Rate = 115200;
|
|
break;
|
|
default:
|
|
/* not configured yet */
|
|
Baud_Rate = 38400;
|
|
baud_k = 38400 / 1000;
|
|
eeprom_bytes_write(NV_EEPROM_BAUD_K, &baud_k, 1);
|
|
break;
|
|
}
|
|
rs485_baud_rate_configure();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: initialize the module
|
|
* RETURN: nothing
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
void rs485_init(void)
|
|
{
|
|
FIFO_Init(&Receive_Buffer, &Receive_Buffer_Data[0],
|
|
(unsigned)sizeof(Receive_Buffer_Data));
|
|
rs485_silence_time_reset();
|
|
rs485_rts_init();
|
|
rs485_usart_init();
|
|
rs485_init_nvdata();
|
|
rs485_receiver_enable();
|
|
rs485_rts_enable(false);
|
|
}
|