в модуле времени добавила функцию получения тиков. в папке variants добавила функции для работы с прерываниями. добавлена рыба библиотеки softserial

This commit is contained in:
klassents committed 2024-09-13 15:35:01 +07:00
1 parent 63217147e5
commit 136fbc78d3
8 files changed
+683 -15

No files matched your search

@@ -0,0 +1,486 @@
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
The latest version of this library can always be found at
http://arduiniana.org.
*/
// When set, _DEBUG co-opts pins 11 and 13 for debugging with an
// oscilloscope or logic analyzer. Beware: it also slightly modifies
// the bit times, so don't rely on it too much at high baud rates
#define _DEBUG 0
#define _DEBUG_PIN1 11
#define _DEBUG_PIN2 13
#define MAX_SOFT_SERIAL_SPEED 115200
#define MIN_SOFT_SERIAL_SPEED 300
//
// Includes
//
// #include <avr/pgmspace.h>
#include <Arduino.h>
#include <SoftwareSerial.h>
#include "mik32_hal_irq.h"
#include "mik32_hal_gpio.h"
//
// Statics
//
SoftwareSerial *SoftwareSerial::active_object = 0;
uint8_t SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF];
volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0;
volatile uint8_t SoftwareSerial::_receive_buffer_head = 0;
//
// Debugging
//
// This function generates a brief pulse
// for debugging or measuring on an oscilloscope.
// #if _DEBUG
// inline void DebugPulse(uint8_t pin, uint8_t count)
// {
// volatile uint8_t *pport = portOutputRegister(digitalPinToPort(pin));
// uint8_t val = *pport;
// while (count--)
// {
// *pport = val | digitalPinToBitMask(pin);
// *pport = val;
// }
// }
// #else
// inline void DebugPulse(uint8_t, uint8_t) {}
// #endif
//
// Private methods
//
/* static */
inline void SoftwareSerial::tunedDelay(uint16_t delayTicks)
{
uint64_t startTicks = SysTick_GetTicks();
while ((SysTick_GetTicks() - startTicks) < delayTicks)
;
}
// This function sets the current object as the "listening"
// one and returns true if it replaces another
bool SoftwareSerial::listen()
{
if (!_rx_delay_stopbit)
return false;
if (active_object != this)
{
if (active_object)
active_object->stopListening();
_buffer_overflow = false;
_receive_buffer_head = _receive_buffer_tail = 0;
active_object = this;
setRxIntMsk(true);
return true;
}
return false;
}
// Stop listening. Returns true if we were actually listening.
bool SoftwareSerial::stopListening()
{
if (active_object == this)
{
setRxIntMsk(false);
active_object = NULL;
return true;
}
return false;
}
//
// The receive routine called by the interrupt handler
//
void SoftwareSerial::recv()
{
// #if GCC_VERSION < 40302
// // Work-around for avr-gcc 4.3.0 OSX version bug
// // Preserve the registers that the compiler misses
// // (courtesy of Arduino forum user *etracer*)
// asm volatile(
// "push r18 \n\t"
// "push r19 \n\t"
// "push r20 \n\t"
// "push r21 \n\t"
// "push r22 \n\t"
// "push r23 \n\t"
// "push r26 \n\t"
// "push r27 \n\t"
// ::);
// #endif
// uint8_t data = 0;
// // If RX line is high, then we don't see any start bit
// // so interrupt is probably not for us
// if (_inverse_logic ? rx_pin_read() : !rx_pin_read())
// {
// // Disable further interrupts during reception, this prevents
// // triggering another interrupt directly after we return, which can
// // cause problems at higher baudrates.
// setRxIntMsk(false);
// // Wait approximately 1/2 of a bit width to "center" the sample
// tunedDelay(_rx_delay_centering);
// DebugPulse(_DEBUG_PIN2, 1);
// // Read each of the 8 bits
// for (uint8_t i=8; i > 0; --i)
// {
// tunedDelay(_rx_delay_intrabit);
// data >>= 1;
// DebugPulse(_DEBUG_PIN2, 1);
// if (rx_pin_read())
// data |= 0x80;
// }
// if (_inverse_logic)
// data = ~data;
// // if buffer full, set the overflow flag and return
// uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF;
// if (next != _receive_buffer_head)
// {
// // save new data in buffer: tail points to where byte goes
// _receive_buffer[_receive_buffer_tail] = data; // save new byte
// _receive_buffer_tail = next;
// }
// else
// {
// DebugPulse(_DEBUG_PIN1, 1);
// _buffer_overflow = true;
// }
// // skip the stop bit
// tunedDelay(_rx_delay_stopbit);
// DebugPulse(_DEBUG_PIN1, 1);
// // Re-enable interrupts when we're sure to be inside the stop bit
// setRxIntMsk(true);
// }
// #if GCC_VERSION < 40302
// // Work-around for avr-gcc 4.3.0 OSX version bug
// // Restore the registers that the compiler misses
// asm volatile(
// "pop r27 \n\t"
// "pop r26 \n\t"
// "pop r23 \n\t"
// "pop r22 \n\t"
// "pop r21 \n\t"
// "pop r20 \n\t"
// "pop r19 \n\t"
// "pop r18 \n\t"
// ::);
// #endif
}
uint8_t SoftwareSerial::rx_pin_read()
{
return (uint8_t)HAL_GPIO_ReadPin((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receivePin);
}
//
// Interrupt handling
//
/* static */
inline void SoftwareSerial::handle_interrupt() // todo унести в обработчик прерываний
{
if (active_object)
{
active_object->recv();
}
}
// #if defined(PCINT0_vect)
// ISR(PCINT0_vect)
// {
// SoftwareSerial::handle_interrupt();
// }
// #endif
//
// Constructor
//
SoftwareSerial::SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic /* = false */) :
_rx_delay_centering(0),
_rx_delay_intrabit(0),
_rx_delay_stopbit(0),
_tx_delay(0),
_buffer_overflow(false),
_inverse_logic(inverse_logic)
{
setTX(transmitPin);
setRX(receivePin);
}
//
// Destructor
//
SoftwareSerial::~SoftwareSerial()
{
end();
}
void SoftwareSerial::setTX(uint8_t tx)
{
// First write, then set output. If we do this the other way around,
// the pin would be output low for a short while before switching to
// output high. Now, it is input with pullup for a short while, which
// is fine. With inverse logic, either order is fine.
digitalWrite(tx, _inverse_logic ? LOW : HIGH); // todo проверить, что правда выставляется до конфигурации
pinMode(tx, OUTPUT);
_transmitBitMask = (uint16_t)digitalPinToBitMask(tx); // save tx bit mask
_transmitPortRegister = (uint32_t)digitalPinToPort(tx); // save tx port adress
}
void SoftwareSerial::setRX(uint8_t rx)
{
if (!_inverse_logic)
pinMode(rx, INPUT_PULLUP); // pullup for normal logic
else
pinMode(rx, INPUT);
_receivePin = rx; // save rx digital pin number
_receiveBitMask = (uint16_t)digitalPinToBitMask(rx); // save rx bit mask
_receivePortRegister = (uint32_t)digitalPinToPort(rx); // save rx port adress
}
uint16_t SoftwareSerial::subtract_cap(uint16_t num, uint16_t sub)
{
if (num > sub)
return num - sub;
else
return 1;
}
//
// Public methods
//
void SoftwareSerial::begin(long speed)
{
_rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0;
// limit speed
if (speed > MAX_SOFT_SERIAL_SPEED) speed = MAX_SOFT_SERIAL_SPEED;
if (speed < MIN_SOFT_SERIAL_SPEED) speed = MIN_SOFT_SERIAL_SPEED;
// Precalculate the various delays in number of ticks
uint32_t bit_delay = F_CPU / speed;
// 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit,
// 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits,
// 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit
// These are all close enough to just use 15 cycles, since the inter-bit
// timings are the most critical (deviations stack 8 times)
_tx_delay = subtract_cap(bit_delay, 15);
// Only setup rx when we have a valid INT for this pin
if (digitalPinToInterrupt(_receivePin) != NOT_AN_INTERRUPT)
{
#if GCC_VERSION > 40800
// Timings counted from gcc 4.8.2 output. This works up to 115200 on
// 16Mhz and 57600 on 8Mhz.
//
// When the start bit occurs, there are 3 or 4 cycles before the
// interrupt flag is set, 4 cycles before the PC is set to the right
// interrupt vector address and the old PC is pushed on the stack,
// and then 75 cycles of instructions (including the RJMP in the
// ISR vector table) until the first delay. After the delay, there
// are 17 more cycles until the pin value is read (excluding the
// delay in the loop).
// We want to have a total delay of 1.5 bit time. Inside the loop,
// we already wait for 1 bit time - 23 cycles, so here we wait for
// 0.5 bit time - (71 + 18 - 22) cycles.
_rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 75 + 17 - 23));
// There are 23 cycles in each loop iteration (excluding the delay)
_rx_delay_intrabit = subtract_cap(bit_delay, 23);
// There are 37 cycles from the last bit read to the start of
// stopbit delay and 11 cycles from the delay until the interrupt
// mask is enabled again (which _must_ happen during the stopbit).
// This delay aims at 3/4 of a bit time, meaning the end of the
// delay will be at 1/4th of the stopbit. This allows some extra
// time for ISR cleanup, which makes 115200 baud at 16Mhz work more
// reliably
_rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (37 + 11));
#else // Timings counted from gcc 4.3.2 output
// Note that this code is a _lot_ slower, mostly due to bad register
// allocation choices of gcc. This works up to 57600 on 16Mhz and
// 38400 on 8Mhz.
_rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 97 + 29 - 11));
_rx_delay_intrabit = subtract_cap(bit_delay, 11);
_rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (44 + 17));
#endif
// Enable the INT for the entire port here, but never disable it
// (others might also need it, so we disable the interrupt by using
// the per-pin PCMSK register).
if (_inverse_logic)
_int_mode = GPIO_INT_MODE_RISING; // start bit: low to high
else
_int_mode = GPIO_INT_MODE_FALLING; // start bit: high to low
// Save gpio_mux and gpio_line for on/off interrupts
_int_maskMux = digitalPinToGpioIntMux(_receivePin); // gpio mux value
_int_maskLine = digitalPinToGpioIntLine(_receivePin); // gpio irq line value
HAL_EPIC_MaskEdgeSet(HAL_EPIC_GPIO_IRQ_MASK); // turn on the mask in EPIC
// *digitalPinToPCICR((int8_t)_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin));
// _pcint_maskreg = digitalPinToPCMSK(_receivePin);
// _pcint_maskvalue = _BV(digitalPinToPCMSKbit(_receivePin));
tunedDelay(_tx_delay); // if we were low this establishes the end
}
// #if _DEBUG
// pinMode(_DEBUG_PIN1, OUTPUT);
// pinMode(_DEBUG_PIN2, OUTPUT);
// #endif
listen();
}
void SoftwareSerial::setRxIntMsk(bool enable)
{
if (enable)
HAL_GPIO_InitInterruptLine((HAL_GPIO_Line_Config)_int_maskMux, (HAL_GPIO_InterruptMode)_int_mode);
// *_pcint_maskreg |= _pcint_maskvalue;
else
HAL_GPIO_DeInitInterruptLine((HAL_GPIO_Line)_int_maskLine);
// *_pcint_maskreg &= ~_pcint_maskvalue;
}
void SoftwareSerial::end()
{
stopListening();
}
// Read data from buffer
int SoftwareSerial::read()
{
if (!isListening())
return -1;
// Empty buffer?
if (_receive_buffer_head == _receive_buffer_tail)
return -1;
// Read from "head"
uint8_t data = _receive_buffer[_receive_buffer_head]; // grab next byte
_receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF;
return data;
}
int SoftwareSerial::available()
{
if (!isListening())
return 0;
return ((unsigned int)(_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head)) % _SS_MAX_RX_BUFF;
}
size_t SoftwareSerial::write(uint8_t byte)
{
if (_tx_delay == 0)
{
setWriteError();
return 0;
}
// By declaring these as local variables, the compiler will put them
// in registers _before_ disabling interrupts and entering the
// critical timing sections below, which makes it a lot easier to
// verify the cycle timings
uint8_t bit_mask = _transmitBitMask; // битовая маска в порту
bool inv = _inverse_logic;
uint16_t delay = _tx_delay;
if (inv)
byte = ~byte;
noInterrupts(); // turn off interrupts for a clean txmit
// Write the start bit
if (inv)
// todo проверить, можно ли так выставлять через адрес порта
HAL_GPIO_WritePin((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)bit_mask, GPIO_PIN_HIGH);
// *reg |= reg_mask; // бит tx в 1
else
HAL_GPIO_WritePin((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)bit_mask, GPIO_PIN_LOW);
// *reg &= inv_mask; // бит tx в 0
tunedDelay(delay);
// Write each of the 8 bits
for (uint8_t i = 8; i > 0; --i)
{
if (byte & 1) // choose bit
// *reg |= reg_mask; // send 1
HAL_GPIO_WritePin((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)bit_mask, GPIO_PIN_HIGH);
else
// *reg &= inv_mask; // send 0
HAL_GPIO_WritePin((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)bit_mask, GPIO_PIN_LOW);
tunedDelay(delay);
byte >>= 1;
}
// restore pin to natural state
if (inv)
HAL_GPIO_WritePin((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)bit_mask, GPIO_PIN_LOW);
// *reg &= inv_mask; // бит tx в 0
else
HAL_GPIO_WritePin((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)bit_mask, GPIO_PIN_HIGH);
// *reg |= reg_mask; // бит tx в 1
interrupts(); // enable interrupts
tunedDelay(_tx_delay);
return 1;
}
void SoftwareSerial::flush()
{
// There is no tx buffering, simply return
}
int SoftwareSerial::peek()
{
if (!isListening())
return -1;
// Empty buffer?
if (_receive_buffer_head == _receive_buffer_tail)
return -1;
// Read from "head"
return _receive_buffer[_receive_buffer_head];
}
@@ -0,0 +1,110 @@
/*
SoftwareSerial.h
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
The latest version of this library can always be found at
http://arduiniana.org.
*/
#ifndef SoftwareSerial_h
#define SoftwareSerial_h
#include <inttypes.h>
#include <Stream.h>
/******************************************************************************
* Definitions
******************************************************************************/
#ifndef _SS_MAX_RX_BUFF
#define _SS_MAX_RX_BUFF 64 // RX buffer size
#endif
#ifndef GCC_VERSION
#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#endif
class SoftwareSerial : public Stream
{
private:
// per object data
uint8_t _receivePin;
uint16_t _receiveBitMask;
uint32_t _receivePortRegister;
uint16_t _transmitBitMask;
uint32_t _transmitPortRegister;
uint8_t _int_maskMux;
uint8_t _int_maskLine;
uint8_t _int_mode;
// Expressed as 4-cycle delays (must never be 0!)
uint16_t _rx_delay_centering;
uint16_t _rx_delay_intrabit;
uint16_t _rx_delay_stopbit;
uint16_t _tx_delay;
uint16_t _buffer_overflow:1;
uint16_t _inverse_logic:1;
// static data
static uint8_t _receive_buffer[_SS_MAX_RX_BUFF];
static volatile uint8_t _receive_buffer_tail;
static volatile uint8_t _receive_buffer_head;
static SoftwareSerial *active_object;
// private methods
inline void recv() __attribute__((__always_inline__));
uint8_t rx_pin_read();
void setTX(uint8_t transmitPin);
void setRX(uint8_t receivePin);
inline void setRxIntMsk(bool enable) __attribute__((__always_inline__));
// Return num - sub, or 1 if the result would be < 1
static uint16_t subtract_cap(uint16_t num, uint16_t sub);
// private static method for timing
static inline void tunedDelay(uint16_t delayTicks);
public:
// public methods
SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic = false);
~SoftwareSerial();
void begin(long speed);
bool listen();
void end();
bool isListening() { return this == active_object; }
bool stopListening();
bool overflow()
{
bool ret = _buffer_overflow;
if (ret)
_buffer_overflow = false;
return ret;
}
int peek();
virtual size_t write(uint8_t byte);
virtual int read();
virtual int available();
virtual void flush();
operator bool() { return true; }
using Print::write;
// public only for easy access by interrupt handlers
static inline void handle_interrupt() __attribute__((__always_inline__));
};
#endif