// bit bang'ing Software-UART TX example
//
// Adapted / Ported from
//
// a tiny software UART TX for the AVR ATtiny
// https://marcelmg.github.io/software_uart/
program fp_attiny85_test;
{$MODE OBJFPC}
{$MACRO ON}
{$INLINE ON}
{$LONGSTRINGS OFF}
{$WRITEABLECONST OFF}
// Timer interrupt service routine name.
{$DEFINE TIMER_INT := 'TIMER0_COMPA_ISR'}
// Change these to use another pin.
{$DEFINE TX_PORT := PORTB }
{$DEFINE TX_PIN := 0 }
{$DEFINE TX_DDR := DDRB }
{$DEFINE TX_DDR_PIN := 0 }
uses
intrinsics;
// Read directly from flash and not from SRAM.
// HAS_LPMX also means that movw is available.
{$if defined(CPUAVR_HAS_LPMX)}
const
HelloWorldStr: string[14] = 'Hello World ! '; section '.progmem';
FPCStr: string[22] = 'Free Pascal Rocks !!! '; section '.progmem';
function read_progmem_byte(constref v: byte): byte; assembler; nostackframe;
asm
MOVW ZL, r24
LPM r24, Z
end;
function read_progmem_str(constref s: shortstring): ShortString;
var
len, i: byte;
begin
len := read_progmem_byte(byte(s[0]));
setlength(Result, len);
for i := 1 to len do
Result[i] := char(read_progmem_byte(byte(s[i])));
end;
{$ENDIF}
procedure AtomicWrite(var Value: word; new_value: word);
var
b: byte;
begin
b := avr_save;
Value := new_value;
avr_restore(b);
end;
function AtomicRead(var Value: word): word;
var
b: byte;
begin
b := avr_save;
Result := Value;
avr_restore(b);
end;
var
// Used as a TX shift register.
tx_shift_reg: word;
function UART_tx(constref ACharacter: char): boolean;
var
local_tx_shift_reg: word;
begin
Result := True;
local_tx_shift_reg := AtomicRead(tx_shift_reg);
// Check if sending the previous character is not yet finished.
// Transmission is finished when tx_shift_reg = 0.
if local_tx_shift_reg <> 0 then
Result := False
else
begin
// Fill the TX shift register with the character to be sent and
// the start & stop bits (start bit (1<<0) is already 0).
local_tx_shift_reg := (word(ACharacter) shl 1) or (1 shl 9);
AtomicWrite(tx_shift_reg, local_tx_shift_reg);
// Start Timer0 with a prescaler of 8.
TCCR0B := (1 shl CS0 + 1);
end;
end;
procedure UART_tx_str(constref AString: ShortString);
var
iLen, x: byte;
begin
iLen := Length(AString);
x := 1;
while (iLen >= x) do
// Send the next character after the
// the previous character transmission is finished.
if UART_tx(AString[x]) then
Inc(x);
end;
procedure UART_init;
begin
// Set TX pin as output.
TX_DDR := TX_DDR or (1 shl TX_DDR_PIN);
TX_PORT := TX_PORT or (1 shl TX_PIN);
// Set Timer0 to CTC mode.
TCCR0A := (1 shl WGM0 + 1);
// Enable output compare 0 A interrupt.
TIMSK := TIMSK or (1 shl OCF0A);
// Set compare value to 103 to achieve a 9600 baud rate (i.e. 104µs),
// together with the 8MHz/8=1MHz Timer0 clock.
{NOTE: since the internal 8MHz oscillator is not very accurate, this value can be tuned
to achieve the desired baud rate, so if it doesn't work with the nominal value (103), try
increasing or decreasing the value by 1 or 2 }
OCR0A := 103;
end;
procedure TIMER0_COMPA_ISR; public Name TIMER_INT; Interrupt;
begin
// Output LSB of the TX shift register at the TX pin.
if (tx_shift_reg and 1) = 1 then
TX_PORT := TX_PORT or (1 shl TX_PIN)
else
TX_PORT := TX_PORT and not (1 shl TX_PIN);
// Shift the TX shift register one bit to the right.
tx_shift_reg := (tx_shift_reg shr 1);
// If the stop bit has been sent, the shift register will be 0
// and the transmission is completed, so we can stop & reset Timer0.
if tx_shift_reg = 0 then
begin
TCCR0B := 0;
TCNT0 := 0;
end;
end;
// Generated by delay loop calculator
// at http://www.bretmulvey.com/avrdelay.html
// Delay 4 000 000 cycles
// 500ms at 8.0 MHz
procedure _delay_loop; assembler; nostackframe;
label
L1;
asm
LDI r18, 21
LDI r19, 75
LDI r20, 191
L1:
DEC r20
BRNE L1
DEC r19
BRNE L1
DEC r18
BRNE L1
NOP
end;
begin
UART_init;
// Enable Global Interrupts.
avr_sei;
repeat
// Read directly from flash and not from SRAM.
// HAS_LPMX also means that movw is available.
{$if defined(CPUAVR_HAS_LPMX)}
UART_tx_str(read_progmem_str(HelloWorldStr));
UART_tx_str(read_progmem_str(FPCStr));
{$elseif}
UART_tx_str('Hello World ! ');
UART_tx_str('Free Pascal Rocks !!! ');
{$endif}
_delay_loop;
until False;
end.