mirror of
https://codeberg.org/scip/attinycore-makefile-tests.git
synced 2025-12-16 19:00:57 +01:00
194 lines
5.2 KiB
C
194 lines
5.2 KiB
C
/* optimized half-duplex high-speed AVR serial uart
|
|
* Ralph Doncaster 2020 open source MIT license
|
|
*
|
|
* picoUART is accurate to the cycle (+- 0.5 cycle error)
|
|
* 0.64% error at 115.2k/8M and 0.4% error at 115.2k/9.6M
|
|
*
|
|
* define PU_BAUD_RATE before #include to change default baud rate
|
|
*
|
|
* capable of single-pin operation (PU_TX = PU_RX) as follows:
|
|
* connect MCU pin to host RX line, and a 1.5k-4.7k resistor between
|
|
* host RX and TX line. Note this also gives local echo.
|
|
*
|
|
* 20200123 version 0.5
|
|
* 20200123 version 0.6 - improve inline asm
|
|
* 20200201 version 0.7 - use push/pull during tx
|
|
* 20200203 version 0.8 - add prints_P, prefix globals with PU_
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <avr/io.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#ifndef PU_BAUD_RATE
|
|
#define PU_BAUD_RATE 115200L // default baud rate
|
|
#endif
|
|
|
|
#ifndef PU_TX
|
|
#define PU_TX D,1
|
|
#define PU_RX D,0
|
|
#endif
|
|
|
|
// use static inline functions for type safety
|
|
extern inline float PUBIT_CYCLES() {return F_CPU/(PU_BAUD_RATE*1.0);}
|
|
|
|
// delay based on cycle count of asm code + 0.5 for rounding
|
|
extern inline int PUTXWAIT() {return PUBIT_CYCLES() - 7 + 0.5;}
|
|
extern inline int PURXWAIT() {return PUBIT_CYCLES() - 5 + 0.5;}
|
|
|
|
// correct for PURXWAIT skew in PURXSTART calculation
|
|
// skew is half of 7 delay intervals between 8 bits
|
|
extern inline float PUSKEW() {
|
|
return (PUBIT_CYCLES() - (int)(PUBIT_CYCLES() + 0.5)) * 3.5;
|
|
}
|
|
// Time from falling edge of start bit to sample 1st bit is 1.5 *
|
|
// bit-time. Subtract 2 cycles for sbic, 1 for lsr, and PURXWAIT.
|
|
// Subtract 1.5 cycles because start bit detection is accurate to
|
|
// +-1.5 cycles. Add 0.5 cycles for int rounding, and add skew.
|
|
extern inline int PURXSTART() {
|
|
return (PUBIT_CYCLES()*1.5 -3 -PURXWAIT() -1 +PUSKEW());
|
|
}
|
|
|
|
// min rx/tx turn-around time in resistor-only 1-wire mode
|
|
inline void pu_rxtx_wait()
|
|
{
|
|
__builtin_avr_delay_cycles(PUBIT_CYCLES()*1.5);
|
|
}
|
|
|
|
// I/O register macros
|
|
#define BIT(r,b) (b)
|
|
#define PORT(r,b) (PORT ## r)
|
|
#define DDR(r,b) (DDR ## r)
|
|
#define PIN(r,b) (PIN ## r)
|
|
#define bit(io) BIT(io)
|
|
#define port(io) PORT(io)
|
|
#define ddr(io) DDR(io)
|
|
#define pin(io) PIN(io)
|
|
|
|
// use up registers so only r25:r24 are free for the compiler
|
|
#define alloc_regs()\
|
|
register int dummy1 asm("r20");\
|
|
asm volatile ("" : "=r" (dummy1));\
|
|
register int dummy2 asm("r26");\
|
|
asm volatile ("" : "=r" (dummy2));\
|
|
register int dummy3 asm("r30");\
|
|
asm volatile ("" : "=r" (dummy3));
|
|
|
|
#define touch_regs()\
|
|
asm volatile ("" :: "r" (dummy1));\
|
|
asm volatile ("" :: "r" (dummy2));\
|
|
asm volatile ("" :: "r" (dummy3));
|
|
|
|
__attribute((naked))
|
|
void _pu_tx()
|
|
{
|
|
alloc_regs();
|
|
register char c asm("r18");
|
|
register char sr asm("r19");
|
|
asm volatile (
|
|
"cbi %[tx_port], %[tx_bit]\n" // disable pullup
|
|
"cli\n"
|
|
"sbi %[tx_port]-1, %[tx_bit]\n" // start bit
|
|
"in r0, %[tx_port]\n" // save DDR in r0
|
|
"ldi %[sr], 3\n" // stop bit & idle state
|
|
"Ltxbit:\n"
|
|
: [c] "+r" (c),
|
|
[sr] "+r" (sr)
|
|
: [tx_port] "I" (_SFR_IO_ADDR(port(PU_TX))),
|
|
[tx_bit] "I" (bit(PU_TX))
|
|
);
|
|
__builtin_avr_delay_cycles(PUTXWAIT());
|
|
// 7 cycle loop
|
|
asm volatile (
|
|
"bst %[c], 0\n" // store lsb in T
|
|
"bld r0, %[tx_bit]\n"
|
|
"lsr %[sr]\n" // 2-byte shift register
|
|
"ror %[c]\n" // shift for next bit
|
|
"out %[tx_port], r0\n"
|
|
"brne Ltxbit\n"
|
|
"cbi %[tx_port]-1, %[tx_bit]\n" // set to input mode
|
|
"reti\n" // return & enable interrupts
|
|
: [c] "+r" (c),
|
|
[sr] "+r" (sr)
|
|
: [tx_port] "I" (_SFR_IO_ADDR(port(PU_TX))),
|
|
[tx_bit] "I" (bit(PU_TX))
|
|
);
|
|
touch_regs();
|
|
}
|
|
|
|
inline void pu_tx(char c)
|
|
{
|
|
register char ch asm("r18") = c;
|
|
asm volatile ("%~call %x1" : "+r"(ch) : "i"(_pu_tx) : "r19", "r24", "r25");
|
|
}
|
|
|
|
inline void prints_P(const char* s)
|
|
{
|
|
register char c asm("r18");
|
|
asm volatile (
|
|
"1:\n"
|
|
"lpm %[c], %a0+\n" // read next char
|
|
"tst %[c]\n"
|
|
"breq 1f\n"
|
|
"%~call %x2\n"
|
|
"rjmp 1b\n"
|
|
"1:\n"
|
|
: "+e" (s), [c] "+r" (c)
|
|
: "i" (_pu_tx)
|
|
: "r19", "r24", "r25"
|
|
);
|
|
}
|
|
|
|
__attribute((naked))
|
|
void _pu_rx()
|
|
{
|
|
alloc_regs();
|
|
register char c asm("r18");
|
|
register char dummy4 asm("r19");
|
|
asm volatile (
|
|
// wait for idle state (high)
|
|
"1: sbis %[rx_pin], %[rx_bit]\n"
|
|
"rjmp 1b\n"
|
|
"ldi %[c], 0x80\n" // bit shift counter
|
|
"cli\n"
|
|
// wait for start bit (low)
|
|
"1: sbic %[rx_pin], %[rx_bit]\n"
|
|
"rjmp 1b\n"
|
|
: [c] "=d" (c)
|
|
: [rx_pin] "I" (_SFR_IO_ADDR(pin(PU_RX))),
|
|
[rx_bit] "I" (bit(PU_RX))
|
|
);
|
|
__builtin_avr_delay_cycles(PURXSTART());
|
|
asm volatile ("Lrxbit:");
|
|
__builtin_avr_delay_cycles(PURXWAIT());
|
|
// 5 cycle loop
|
|
asm volatile (
|
|
"lsr %[c]\n"
|
|
"sbic %[rx_pin], %[rx_bit]\n"
|
|
"ori %[c], 0x80\n"
|
|
"brcc Lrxbit\n"
|
|
"reti\n"
|
|
: [c] "+d" (c)
|
|
: [rx_pin] "I" (_SFR_IO_ADDR(pin(PU_RX))),
|
|
[rx_bit] "I" (bit(PU_RX)),
|
|
"r" (dummy4)
|
|
);
|
|
touch_regs();
|
|
}
|
|
|
|
inline char pu_rx()
|
|
{
|
|
register char c asm("r18");
|
|
asm ("%~call %x1" : "=r"(c) : "i"(_pu_rx) : "r24", "r25");
|
|
return c;
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|