├── src ├── macro.i ├── led.S ├── init.S ├── ledproc.c ├── main.S ├── Makefile ├── timer.S ├── process.S └── serial_io.S └── README.md /src/macro.i: -------------------------------------------------------------------------------- 1 | ; macro to push multiple registers in a sequence to the stack 2 | .macro pushm reg0 reg1 3 | push \reg0 4 | .if \reg1-\reg0 5 | pushm "(\reg0+1)",\reg1 6 | .endif 7 | .endm 8 | 9 | ; macro to pop multiple registers in a sequence off the stack 10 | .macro popm reg0 reg1 11 | pop \reg1 12 | .if \reg1-\reg0 13 | popm \reg0,"(\reg1-1)" 14 | .endif 15 | .endm 16 | 17 | -------------------------------------------------------------------------------- /src/led.S: -------------------------------------------------------------------------------- 1 | /*! \file led.S 2 | * This file contains functions to handle the led. 3 | * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at 4 | */ 5 | 6 | .file "led.S" 7 | 8 | #include 9 | 10 | 11 | .section .text 12 | 13 | 14 | ; Function initializes the led port on the Arduino board. 15 | .global init_ledport 16 | init_ledport: 17 | ldi r18,0x20 ; set bit 5 of port b to output 18 | out _SFR_IO_ADDR(DDRB),r18 19 | ret 20 | 21 | 22 | ; Function to toggle led. 23 | .global toggle_led 24 | toggle_led: 25 | in r18,_SFR_IO_ADDR(PORTB) ; read port b 26 | ldi r19,0x20 ; toggle bit 5 27 | eor r18,r19 28 | out _SFR_IO_ADDR(PORTB),r18 ; output value to port b again 29 | ret 30 | 31 | -------------------------------------------------------------------------------- /src/init.S: -------------------------------------------------------------------------------- 1 | /*! \file init.S 2 | * System initialization. 3 | * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at 4 | */ 5 | 6 | .file "init.S" 7 | 8 | #include 9 | 10 | 11 | .section .vectors 12 | 13 | ; reset vector 14 | .org 0 15 | rjmp __ctors_start 16 | 17 | ; timer 0 overflow vector 18 | .org 0x40 19 | rjmp t0_handler 20 | 21 | ; serial input buffer vector 22 | .org 0x48 23 | rjmp serial_rx_handler 24 | 25 | .org 0x4c 26 | rjmp serial_tx_handler 27 | 28 | 29 | ; "ConstrucTORS" 30 | ;__ctors_start: 31 | .section .ctors 32 | ldi r16,0 ; clear system status register 33 | out _SFR_IO_ADDR(SREG),r16 34 | ldi r16,lo8(RAMEND) ; init stack 35 | out _SFR_IO_ADDR(SPL),r16 36 | ldi r16,hi8(RAMEND) 37 | out _SFR_IO_ADDR(SPH),r16 38 | 39 | rcall init_procs ; call OS initialization routines 40 | rcall init_timer 41 | 42 | ldi r16,lo8(main) ; start first process (main) 43 | ldi r17,hi8(main) 44 | lsr r17 45 | ror r16 46 | push r16 47 | push r17 48 | 49 | reti 50 | 51 | -------------------------------------------------------------------------------- /src/ledproc.c: -------------------------------------------------------------------------------- 1 | /*! \file ledproc.c 2 | * This file contains the function led_proc() which continuously toggles the 3 | * led on the Arduino board. The function is started as separate process by 4 | * main(). 5 | * The function wait() waits a given number of time slices. 6 | * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at 7 | */ 8 | 9 | int toggle_led(void); // defined in led.S 10 | void init_ledport(void); // defined in led.S 11 | unsigned long get_uptime(void); // defined in timer.S 12 | void sys_sleep(void); // defined in process.S 13 | 14 | 15 | /* Waits the defined number t of time slices until it returns. 16 | * @param t Number of time slices to wait. 17 | */ 18 | void wait(unsigned int t) 19 | { 20 | unsigned long end; 21 | 22 | end = get_uptime() + t; 23 | while (get_uptime() < end) 24 | sys_sleep(); 25 | } 26 | 27 | 28 | /* This function continuously toggles the led. The function does never return. 29 | */ 30 | void led_proc(void) 31 | { 32 | init_ledport(); 33 | for (;;) 34 | { 35 | toggle_led(); 36 | wait(5); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main.S: -------------------------------------------------------------------------------- 1 | /*! \file main.S 2 | * This file contains the main() routine which is the first process started by 3 | * the kernel. 4 | * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at 5 | */ 6 | 7 | .file "main.S" 8 | 9 | #include 10 | 11 | #define SEND_PROMPT 12 | 13 | .section .text 14 | 15 | ; Main function. It is finally called in the init routing of the kernel (in 16 | ; init.S). It starts additional processes and then in a loop reads from 17 | ; the serial port and outputs the string "\nOK\r\n" to the serial port. 18 | .global main 19 | main: 20 | rcall init_serial ; initialize serial port (UART) 21 | 22 | ldi YL,lo8(led_proc) ; Start led process (defined in ledproc.c) which 23 | ldi YH,hi8(led_proc) ; toggles the led. 24 | rcall start_proc 25 | 26 | ; ldi YL,lo8(sendp) ; Start another process which send a single period 27 | ; ldi YH,hi8(sendp) ; to the terminal (function defined below). 28 | ; rcall start_proc 29 | 30 | .Lmainloop: 31 | #ifdef SEND_PROMPT 32 | ldi YL,lo8(.Lstr_arduino) 33 | ldi YH,hi8(.Lstr_arduino) 34 | ldi r16,.Lstr_arduino_len 35 | rcall sys_pwrite 36 | #endif 37 | 38 | rcall sys_read_flush 39 | ldi r26,lo8(buf) ; Read line from terminal into the buffer buf. 40 | ldi r27,hi8(buf) 41 | rcall sys_read 42 | 43 | ldi YL,lo8(.Lstr_ok) ; send "ok" string 44 | ldi YH,hi8(.Lstr_ok) 45 | ldi r16,.Lstr_ok_len 46 | rcall sys_pwrite 47 | 48 | rjmp .Lmainloop ; Repeat endlessly. 49 | 50 | 51 | ; Function to send a '.'. 52 | sendp: 53 | ldi r16,'.' ; send '.' 54 | rcall sys_send 55 | ldi r24,27 ; wait(27) 56 | ldi r25,0 57 | rcall wait 58 | rjmp sendp ; endless loop 59 | 60 | .Lstr_ok: 61 | .ascii "\rOK\r\n" 62 | .set .Lstr_ok_len, . - .Lstr_ok 63 | .Lstr_arduino: 64 | .ascii "Arduino> " 65 | .set .Lstr_arduino_len, . - .Lstr_arduino 66 | 67 | .balign 2 68 | 69 | 70 | .section .data 71 | 72 | ; Data buffer used for serial input. 73 | buf: 74 | .space 256 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to build and upload assembler sources for Arduino. 2 | # 3 | # @author Bernhard R. Fischer, 4096R/8E24F29D 4 | # 5 | # @license This is free software, do what ever you like with it. 6 | # 7 | # @usage Put this file into the directory with your assembler sources. The name 8 | # of the directory will be the name of the final (TARGET) file. All assembler 9 | # sources shall have the extension .S, thus being preprocessed by cpp. 10 | # Assembler include (header) files should not have the extensions .s or .S but 11 | # .inc or .h. Have a look at the macros MCU, USBDEV, and BAUD which probably 12 | # have to be changed according to your Arduino board. Finally run `make`. It 13 | # will assemble everything into an elf binary (TARGET.elf) and an Intel hex 14 | # file (TARGET.hex). This can be uploaded by calling `make upload`. To show the 15 | # final disassembly call `make dump`. 16 | # 17 | # @notes To compile and upload using this Makefile you need the packages 18 | # 'gcc-avr' and 'avrdude' (the upload utility). 19 | # 20 | # To connect to the serial port directly (if you programmed the USART0) you 21 | # need a terminal program, e.g. minicom. Run minicom with the following 22 | # statement and options: `minicom -D /dev/ttyACM0 -o -b 9600 -w` 23 | # 24 | TARGET = $(notdir $(CURDIR)) 25 | SOURCES = $(wildcard *.S) $(wildcard *.c) $(wildcard *.i) 26 | OBJECTS = $(patsubst %.S,%.o,$(wildcard *.S)) $(patsubst %.c,%.o,$(wildcard *.c)) 27 | MCU = atmega328p 28 | #MCU = atmega16 29 | ## settings for Uno 30 | USBDEV = /dev/ttyACM0 31 | BAUD = 115200 32 | # settings for Duemilanove 33 | #USBDEV = /dev/ttyUSB0 34 | #BAUD = 57600 35 | 36 | AS = avr-as 37 | CC = avr-gcc 38 | LD = avr-ld 39 | OBJDUMP = avr-objdump 40 | CPP = avr-cpp 41 | COMPRESSOR = xz 42 | 43 | CFLAGS = -g -Wall -mmcu=$(MCU) 44 | CPPLAGS = -g -mmcu=$(MCU) 45 | ASFLAGS = -g -mmcu=$(MCU) 46 | LDFLAGS = -Tdata=0x800100 -nostdlib 47 | #LDFLAGS = -Tbss=0x800100 -Tdata=0x800300 48 | AVRDUDE = avrdude 49 | AVRDUDE_CONF = /etc/avrdude.conf 50 | 51 | all: $(TARGET).hex 52 | 53 | $(TARGET).elf: $(OBJECTS) 54 | $(CC) $(LDFLAGS) $(OBJECTS) -o $@ 55 | 56 | $(TARGET).hex: $(TARGET).elf 57 | avr-objcopy -O ihex -R .eeprom $(TARGET).elf $(TARGET).hex 58 | 59 | upload: $(TARGET).hex 60 | $(AVRDUDE) -C $(AVRDUDE_CONF) -p $(MCU) -c arduino -P $(USBDEV) -b $(BAUD) -D -U flash:w:$(TARGET).hex:i 61 | 62 | dump: $(TARGET).elf 63 | $(OBJDUMP) -d $(TARGET).elf 64 | 65 | clean: 66 | rm -f $(TARGET).elf $(TARGET).hex $(OBJECTS) *~ 67 | 68 | $(TARGET).tar: 69 | if test -d $(TARGET) ; then rm -rf $(TARGET) ; fi 70 | mkdir $(TARGET) 71 | cp $(SOURCES) $(MAKEFILE_LIST) $(TARGET) 72 | tar cf $(TARGET).tar $(TARGET) 73 | 74 | dist: $(TARGET).tar 75 | $(COMPRESSOR) $(TARGET).tar 76 | 77 | minicom: 78 | minicom -D $(USBDEV) -o -b 9600 -w 79 | 80 | .PHONY: clean upload dump dist 81 | 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniOS 2 | 3 | Arduino MiniOS is a tiny multi-tasking operating system for Arduino. It was 4 | developed during a course focusing on assembler programming and operating 5 | system internals. 6 | 7 | This operating system currently serves demonstrational purpose and is not 8 | thought to be a mature OS for productional use. Although it can and probably 9 | will be enhanced and further developed. 10 | 11 | **You may also have a look at [AVRShell](https://github.com/rahra/avrshell) which 12 | is a far more mature version of this.** 13 | 14 | ## Description 15 | 16 | The core components of MiniOS is a dispatcher (```context_switch()```) and a 17 | dumb scheduler (```get_next_proc()```). Both are implemented in the file ```process.S```. The time slices for context switching are defined by the T0 18 | timer interrupt, every ~16ms on a 16 MHz Arduino board. The code is found in ```timer.S```. This file additionally contains an uptime counter which is used 19 | in the function ```wait()``` in ```ledproc.c``` which sleeps a specific number 20 | of time slices. ```Process.S``` also contains the function to start new 21 | processes (```start_proc()```). After system initialization a single task 22 | (```main()```) is started by the OS (see below). All other processes have to be 23 | started subsequently by ```main()``` or its sub-processes by calling ```start_proc()```. 24 | 25 | The file ```serial_io.S``` contains the code for complete interrupt driven 26 | sending and receiving of data on the serial line. In this case it is used for a 27 | tiny command line interface (which actually does not know any commands at the 28 | current stage of development ;) ). 29 | 30 | The system is initialized in ```init.S```. This is initializing the interrupts, 31 | the timer and all registers and starts the first initial task which is ```main()``` located in the file ```main.S```. ```Main()``` can then start further child processes. 32 | 33 | Currently two processes are running: ```main()``` which implements the command 34 | line and serial communication and ```led_proc()``` which toggles the LED on 35 | the Arduino board. The latter is implemented in C to show the interference 36 | between C and Assembler on AVR by following the [calling conventions](http://www.atmel.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_reg_usage.html) accordingly. 37 | 38 | 39 | ## Author 40 | 41 | Arduino MiniOS is developed and maintained by Bernhard R. Fischer, 42 | 4096R/8E24F29D . You may also [follow me on Twitter](https://twitter.com/_Rahra_) or read my [tech and society blog](https://www.cypherpunk.at/). 43 | You may also have a look at [my collection of Arduino materials](https://www.cypherpunk.at/download/Arduino/). 44 | 45 | Feel free to contact me :) 46 | 47 | 48 | ## License 49 | 50 | This code is completely free. Do what every you like with it and buy me a beer. 51 | -------------------------------------------------------------------------------- /src/timer.S: -------------------------------------------------------------------------------- 1 | /*! \file timer.S 2 | * This file contains all timer related functions. This is an uptime counter 3 | * and the context switching for the multi-tasking. 4 | * 5 | * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at 6 | */ 7 | 8 | .file "timer.S" 9 | 10 | .include "macro.i" 11 | 12 | #include 13 | 14 | .section .text 15 | 16 | 17 | /*! Initialize the timer 0 to simple overflow mode with a clock divider of 18 | * 1024. Thus, the timer interrupt is triggered all 16.384 ms on an 16 MHz 19 | * Arduino board (1 / 16000000 * 1024 * 256 = 0.016384 s). This is the chosen 20 | * time slice for this multi-tasking operating system. 21 | */ 22 | .global init_timer 23 | init_timer: 24 | clr r16 ; set timer normal mode 25 | out _SFR_IO_ADDR(TCCR0A),r16 26 | ldi r16,0x05 ; set clock divider 1024 27 | out _SFR_IO_ADDR(TCCR0B),r16 28 | clr r16 ; counter register auf 0 (=256) 29 | out _SFR_IO_ADDR(TCNT0),r16 30 | 31 | ldi r16,1 ; timer interrupt enable 32 | sts TIMSK0,r16 33 | 34 | ldi XL,lo8(uptime) ; init uptime to 0 35 | ldi XH,hi8(uptime) 36 | 37 | clr r16 38 | st X+,r16 39 | st X+,r16 40 | st X+,r16 41 | st X+,r16 42 | 43 | ret 44 | 45 | 46 | /*! This is the interrupt handler for the timer 0 interrupt. It invokes the 47 | * context switch and it increases the uptime counter. 48 | */ 49 | .global t0_handler 50 | t0_handler: 51 | ; save full context to (current) stack 52 | pushm 0,31 53 | in r16,_SFR_IO_ADDR(SREG) 54 | push r16 55 | 56 | ; call uptime counter 57 | rcall t0_count 58 | 59 | ; copy SP to Y 60 | in YL,_SFR_IO_ADDR(SPL) 61 | in YH,_SFR_IO_ADDR(SPH) 62 | 63 | ; do context switch 64 | rcall context_switch 65 | 66 | ; copy new stack address in Y to SP 67 | out _SFR_IO_ADDR(SPL),YL 68 | out _SFR_IO_ADDR(SPH),YH 69 | 70 | ; restore full context 71 | pop r16 72 | out _SFR_IO_ADDR(SREG),r16 73 | popm 0,31 74 | reti 75 | 76 | 77 | /*! This function increases to system uptime by 1. 78 | */ 79 | t0_count: 80 | ; load address of uptime variable to X 81 | ldi XL,lo8(uptime) 82 | ldi XH,hi8(uptime) 83 | 84 | ; get current uptime from memory 85 | ld r28,X+ 86 | ld r29,X+ 87 | ld r30,X+ 88 | ld r31,X+ 89 | 90 | ; increase by 1 91 | clr r16 92 | adiw r28,1 93 | adc r30,r16 94 | adc r31,r16 95 | 96 | ; write uptime back to memory 97 | st -X,r31 98 | st -X,r30 99 | st -X,r29 100 | st -X,r28 101 | 102 | ret 103 | 104 | 105 | /*! This function returns the current uptime. 106 | * @prototype long get_uptime(void) 107 | * @return 32 bit uptime in r22-r25. 108 | */ 109 | .global get_uptime 110 | get_uptime: 111 | ldi XL,lo8(uptime) 112 | ldi XH,hi8(uptime) 113 | 114 | cli 115 | ld r22,X+ 116 | ld r23,X+ 117 | ld r24,X+ 118 | ld r25,X+ 119 | sei 120 | 121 | ret 122 | 123 | .section .data 124 | ; 32 bit uptime counter 125 | uptime: 126 | .space 4 127 | 128 | -------------------------------------------------------------------------------- /src/process.S: -------------------------------------------------------------------------------- 1 | /*! \file process.S 2 | * This file contains the routines for the process management. 3 | * 4 | * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at 5 | */ 6 | 7 | .file "process.S" 8 | 9 | #include 10 | 11 | ; maximum number of processes 12 | #define MAX_PROCS 5 13 | ; number of bytes used per process in the process list 14 | #define PROC_LIST_ENTRY 2 15 | ; process stack size 16 | #define STACK_SIZE 64 17 | 18 | .section .text 19 | 20 | ; Initialize default values at kernel startup. 21 | .global init_procs 22 | init_procs: 23 | clr r16 ; Set the current PID to 0. 24 | sts current_proc,r16 25 | ldi r16,1 ; Set the number of processes to 1. 26 | sts num_proc,r16 27 | ret 28 | 29 | 30 | ; Function saves stack pointer and returns new stack pointer 31 | ; @param stack pointer to save in Y 32 | ; @return new stack pointer returned in Y 33 | 34 | .global context_switch 35 | context_switch: 36 | lds r16,current_proc 37 | rcall proc_list_address 38 | 39 | ; save current SP (Y) to proc_list 40 | st Z+,YL 41 | st Z,YH 42 | 43 | ; determine next process to schedule 44 | rcall get_next_proc 45 | sts current_proc,r16 46 | 47 | ; calculate process list address of new process 48 | rcall proc_list_address 49 | ; ...and store its stack address to Y 50 | ld YL,Z+ 51 | ld YH,Z 52 | 53 | ret 54 | 55 | 56 | ; Calculate address of proc_list entry 57 | ; @param r16 number of process 58 | ; @return Z contains address of proc_list entry 59 | proc_list_address: 60 | push r17 61 | 62 | ; calculate process list offset: 63 | ; multiply process number with size per entry 64 | ldi r17,PROC_LIST_ENTRY 65 | mul r17,r16 66 | 67 | ; get start address of process list 68 | ldi ZL,lo8(proc_list) 69 | ldi ZH,hi8(proc_list) 70 | ; and add offset 71 | add ZL,r0 72 | adc ZH,r1 73 | 74 | pop r17 75 | ret 76 | 77 | 78 | ; get number of next process 79 | ; This actually is the (most simple round robin) scheduler ;) 80 | ; @param r16 current process 81 | ; @return r16 number of next process 82 | get_next_proc: 83 | ; increase current PID 84 | inc r16 85 | push r17 86 | ; and start at the first process if the last one is reached 87 | lds r17,num_proc 88 | cp r16,r17 89 | brne .Lgnpexit 90 | clr r16 91 | .Lgnpexit: 92 | pop r17 93 | ret 94 | 95 | 96 | ; Start a new process 97 | ; @param Y Start address of new process 98 | .global start_proc 99 | start_proc: 100 | push r16 101 | push XL 102 | push XH 103 | push YL 104 | push YH 105 | push ZL 106 | push ZH 107 | 108 | movw r26,r28 109 | 110 | ; disable all interrupts 111 | cli 112 | 113 | ; get new PID and calculate stack (top) address 114 | lds r16,num_proc 115 | rcall stack_address 116 | 117 | lsr XH ; divide process entry address by 2 118 | ror XL ; (because AVR prog addresses are word not byte addresses) 119 | 120 | st Y,XL ; save entry point to the stack of the new process 121 | st -Y,XH 122 | 123 | sbiw YL,32 ; subtract 32 from Y (stack) which is the register space 124 | ; of the context switcher 125 | 126 | clr r16 ; store 0 to Y (stack) which is the SREG 127 | st -Y,r16 128 | 129 | sbiw YL,1 130 | 131 | ; store final stack address to the process list 132 | lds r16,num_proc 133 | rcall proc_list_address 134 | 135 | st Z+,YL ; store new stack pointer to proc_list 136 | st Z,YH 137 | 138 | ; increase total number of processes 139 | inc r16 140 | sts num_proc,r16 141 | 142 | ; enable interrupts again 143 | sei 144 | 145 | pop ZH 146 | pop ZL 147 | pop YH 148 | pop YL 149 | pop XH 150 | pop XL 151 | pop r16 152 | 153 | ret 154 | 155 | 156 | 157 | ; Calculate address of stack in memory 158 | ; @param r16 Number of process (PID) 159 | ; @return Y address of stack 160 | stack_address: 161 | mov r0,r16 162 | ldi r16,STACK_SIZE 163 | mul r16,r0 164 | 165 | ldi YL,lo8(RAMEND) 166 | ldi YH,hi8(RAMEND) 167 | sub YL,r0 168 | sbc YH,r1 169 | 170 | ret 171 | 172 | 173 | .global sys_sleep 174 | sys_sleep: 175 | sleep 176 | ret 177 | 178 | 179 | .section .data 180 | ; currently active process 181 | current_proc: 182 | .space 1 183 | ; total number of "running" processes 184 | num_proc: 185 | .space 1 186 | ; process list 187 | proc_list: 188 | .space MAX_PROCS * PROC_LIST_ENTRY 189 | 190 | -------------------------------------------------------------------------------- /src/serial_io.S: -------------------------------------------------------------------------------- 1 | /*! \file serial_io.S 2 | * This file contains the code for the serial communication. 3 | * It is an interrupt driven sender and receiver. 4 | * 5 | * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at 6 | */ 7 | 8 | .file "serial_io.S" 9 | 10 | #include 11 | 12 | ; baud rate for 16MHz Arduino 13 | ; 207 = 9600, 103 = 19200, 16 = 115200 14 | #define BAUDCOUNT 207 15 | 16 | 17 | .section .text 18 | .balign 2 19 | 20 | .global init_serial 21 | init_serial: 22 | push r16 23 | 24 | ldi r16,hi8(BAUDCOUNT) 25 | sts UBRR0H,r16 26 | ldi r16,lo8(BAUDCOUNT) 27 | sts UBRR0L,r16 28 | ldi r16,0x02 ; mode U2X (double baud clock) 29 | sts UCSR0A,r16 30 | ldi r16,0x18 | _BV(RXCIE0) ; RXCIE, RXEN, TXEN 31 | sts UCSR0B,r16 32 | ldi r16,0x06 ; 8N1 33 | sts UCSR0C,r16 34 | 35 | rcall sys_read_flush 36 | clr r16 37 | sts kbuf_output_end,r16 38 | sts kbuf_output_pos,r16 39 | 40 | pop r16 41 | ret 42 | 43 | 44 | /*! Serial receiver interrupt handler. */ 45 | .global serial_rx_handler 46 | serial_rx_handler: 47 | push r16 48 | in r16,_SFR_IO_ADDR(SREG) 49 | 50 | push r16 51 | push r17 52 | push r18 53 | push r28 54 | push r29 55 | 56 | lds r16,UDR0 ; read data from input register 57 | cpi r16,8 ; backspace? 58 | breq .Lhandlebs 59 | cpi r16,'\r' 60 | brne .Lsersave 61 | ldi r16,'\n' 62 | sts kbuf_input_ready,r16 63 | 64 | .Lsersave: 65 | rcall sys_send ; send character back 66 | ldi YL,lo8(kbuf_input) ; buffer address to Y 67 | ldi YH,hi8(kbuf_input) 68 | lds r17,kbuf_input_pos ; get byte counter from RAM 69 | clr r18 ; Y += r17 (16 bit) 70 | add r28,r17 71 | adc r29,r18 72 | st Y,r16 ; copy character to buffer 73 | inc r17 ; increase character counter 74 | sts kbuf_input_pos,r17 ; write counter back to RAM 75 | 76 | .Lrx_int_exit: 77 | pop r29 78 | pop r28 79 | pop r18 80 | pop r17 81 | pop r16 82 | 83 | out _SFR_IO_ADDR(SREG),r16 84 | pop r16 85 | reti 86 | 87 | .Lhandlebs: 88 | lds r17,kbuf_input_pos 89 | tst r17 90 | breq .Lrx_int_exit 91 | 92 | ldi r28,lo8(dummy) 93 | ldi r29,hi8(dummy) 94 | ldi r16,3 95 | rcall sys_pwrite 96 | 97 | dec r17 98 | sts kbuf_input_pos,r17 99 | rjmp .Lrx_int_exit 100 | 101 | 102 | #ifdef WITH_SYSCALL 103 | .global send 104 | send: 105 | push r24 106 | ldi r24,SYS_SEND 107 | rcall syscall 108 | pop r24 109 | ret 110 | #endif 111 | 112 | 113 | #if 0 114 | /*! Send a byte directly (not interrupt driven) to the serial port. 115 | * @parameter r16 Character to send. 116 | */ 117 | .global send_direct 118 | send_direct: 119 | push r17 120 | ; wait if transmitter is busy 121 | .Lsendb: 122 | lds r17,UCSR0A 123 | sbrs r17,5 124 | rjmp .Lsendb 125 | ; write byte to transmit buffer 126 | sts UDR0,r16 127 | pop r17 128 | ret 129 | #endif 130 | 131 | 132 | ; send a single byte to serial buffer 133 | ; @param r16 byte to send 134 | .global sys_send 135 | sys_send: 136 | push r28 137 | push r29 138 | push r16 139 | in r28,_SFR_IO_ADDR(SPL) 140 | in r29,_SFR_IO_ADDR(SPH) 141 | ldi r16,1 142 | adiw r28,1 143 | rcall sys_write 144 | pop r16 145 | pop r29 146 | pop r28 147 | ret 148 | 149 | /* push r17 150 | .Lsendwait: 151 | lds r17,UCSR0A 152 | sbrs r17,5 153 | rjmp .Lsendwait 154 | sts UDR0,r16 155 | pop r17 156 | ret*/ 157 | 158 | 159 | #ifdef WITH_SYSCALL 160 | .global read 161 | read: 162 | push r24 163 | ldi r24,SYS_READ 164 | rcall syscall 165 | pop r24 166 | ret 167 | #endif 168 | 169 | 170 | ; read a line from input buffer 171 | ; FIXME: there's no paramter for buffer length 172 | ; @param Y start address of destination buffer buffer should be 256 bytes long) 173 | ; @return r24 number of bytes copied 174 | .global sys_read 175 | sys_read: 176 | push r16 177 | push r17 178 | push r30 179 | push r31 180 | 181 | .Lrdtest: 182 | lds r16,UCSR0B 183 | andi r16,~_BV(RXCIE0) ; disable receiver interrupt 184 | sts UCSR0B,r16 185 | 186 | ;.Lrdtest: 187 | lds r16,kbuf_input_ready 188 | tst r16 189 | breq .Lrdwait 190 | 191 | ; Daten vorhanden 192 | ;cli ; workaround 193 | ldi r30,lo8(kbuf_input) 194 | ldi r31,hi8(kbuf_input) 195 | lds r16,kbuf_input_pos ; loop counter 196 | mov r24,r16 197 | .Lrdloop: 198 | ld r17,Z+ ; copy bytes 199 | st Y+,r17 200 | dec r16 201 | brne .Lrdloop 202 | 203 | sts kbuf_input_pos,r16 204 | sts kbuf_input_ready,r16 205 | ;sei ; workaround 206 | 207 | lds r16,UCSR0B ; enable receiver interrupt 208 | ori r16,_BV(RXCIE0) 209 | sts UCSR0B,r16 210 | 211 | pop r31 212 | pop r30 213 | pop r17 214 | pop r16 215 | ret 216 | 217 | .Lrdwait: ; no data available 218 | lds r16,UCSR0B ; enable receiver interrupt 219 | ori r16,_BV(RXCIE0) 220 | sts UCSR0B,r16 221 | ;sleep ; FIXME: we should pass control to scheduler here 222 | rjmp .Lrdtest 223 | 224 | 225 | .global sys_read_flush 226 | sys_read_flush: 227 | push r16 228 | clr r16 229 | sts kbuf_input_ready,r16 ; set ready variable to 0 230 | sts kbuf_input_pos,r16 231 | pop r16 232 | ret 233 | 234 | 235 | .global serial_tx_handler 236 | serial_tx_handler: 237 | push r0 238 | in r0,_SFR_IO_ADDR(SREG) 239 | push r16 240 | push r17 241 | 242 | lds r16,kbuf_output_end 243 | lds r17,kbuf_output_pos 244 | sub r16,r17 245 | brne .Lstx_send_byte 246 | lds r17,UCSR0B ; switch off interrupt 247 | andi r17,~_BV(UDRIE0) 248 | sts UCSR0B,r17 249 | rjmp .Lstx_exit 250 | 251 | .Lstx_send_byte: 252 | push r28 253 | push r29 254 | ldi r28,lo8(kbuf_output) ; load send buffer address to Y 255 | ldi r29,hi8(kbuf_output) 256 | add r28,r17 ; add send_pos to Y 257 | clr r16 258 | adc r29,r16 259 | ld r16,Y ; load byte from send buffer 260 | sts UDR0,r16 ; write to serial register 261 | inc r17 262 | sts kbuf_output_pos,r17 ; store new send_pos to RAM 263 | pop r29 264 | pop r28 265 | 266 | .Lstx_exit: 267 | pop r17 268 | pop r16 269 | out _SFR_IO_ADDR(SREG),r0 270 | pop r0 271 | reti 272 | 273 | 274 | #ifdef WITH_SYSCALL 275 | .global write 276 | write: 277 | rcall sys_lock 278 | push r24 279 | ldi r24,SYS_WRITE 280 | rcall syscall 281 | pop r24 282 | rcall sys_unlock 283 | ret 284 | #endif 285 | 286 | 287 | .global sys_write 288 | ; write string to serial port output buffer 289 | ; @param src Source SRAM address in Y (modified) 290 | ; @param cnt Number of bytes in r16 (modified) 291 | sys_write: 292 | tst r16 293 | brne .Lwrite 294 | ret 295 | 296 | .Lwrite: 297 | push r16 298 | push r17 299 | push r18 300 | push r30 301 | push r31 302 | 303 | .Lwrite_test_buf: 304 | lds r17,UCSR0B ; disable USART Data Register Empty Interrupt 305 | andi r17,~_BV(UDRIE0) 306 | sts UCSR0B,r17 307 | 308 | lds r17,kbuf_output_end 309 | lds r18,kbuf_output_pos 310 | sub r18,r17 ; calculate number of free bytes in buffer 311 | breq .Lwrite_start ; if 0 -> assume 256 312 | cp r16,r18 ; test if there's enough free. There must be at least 1 byte more free than in r16 313 | brsh .Lwrite_block ; block if r18 <= r16 314 | 315 | .Lwrite_start: 316 | ldi r30,lo8(kbuf_output) ; load send buffer address to Z 317 | ldi r31,hi8(kbuf_output) 318 | add r30,r17 ; increase Z by send_buf_end 319 | ldi r18,0 320 | adc r31,r18 321 | 322 | .Lwrite_copy: 323 | ld r18,Y+ ; load byte from source buffer 324 | st Z+,r18 ; store byte to destination buffer (send buffer) 325 | 326 | inc r17 ; increase send_buf_end 327 | brne .Lwrite_dec 328 | rcall .Lwrite_ld_baddr ; buffer wraps 329 | .Lwrite_dec: 330 | dec r16 331 | brne .Lwrite_copy 332 | 333 | sts kbuf_output_end,r17 ; store new end address of new buffer 334 | 335 | lds r17,UCSR0B ; enable USART Data Register Empty Interrupt 336 | ori r17,_BV(UDRIE0) 337 | sts UCSR0B,r17 338 | 339 | pop r31 340 | pop r30 341 | pop r18 342 | pop r17 343 | pop r16 344 | ret 345 | 346 | .Lwrite_ld_baddr: 347 | ldi r30,lo8(kbuf_output) ; load send buffer address to Z 348 | ldi r31,hi8(kbuf_output) 349 | ret 350 | 351 | .Lwrite_block: 352 | lds r17,UCSR0B ; enable interrupt 353 | ori r17,_BV(UDRIE0) 354 | sts UCSR0B,r17 355 | ;sleep 356 | rjmp .Lwrite_test_buf 357 | 358 | 359 | #ifdef WITH_SYSCALL 360 | .global pwrite 361 | pwrite: 362 | push r24 363 | ldi r24,SYS_PWRITE 364 | rcall syscall 365 | pop r24 366 | ret 367 | #endif 368 | 369 | 370 | .global sys_pwrite 371 | ; write string to serial port output buffer 372 | ; @param src Source PROGMEM address in Y (modified) 373 | ; @param cnt Number of bytes in r16 (modified) 374 | sys_pwrite: 375 | tst r16 ; safety check r16 != 0 376 | brne .Lpwrite 377 | ret 378 | 379 | .Lpwrite: 380 | push ZL 381 | push ZH 382 | push r17 383 | 384 | movw ZL,YL 385 | mov r17,r16 ; copy one byte after the other to r16 386 | .Lpwloop: 387 | lpm r16,Z+ 388 | rcall sys_send ; and call sys_send() 389 | dec r17 390 | brne .Lpwloop 391 | 392 | pop r17 393 | pop ZH 394 | pop ZL 395 | ret 396 | 397 | 398 | dummy: 399 | .byte 8,' ',8 400 | 401 | .section .data 402 | ; data buffer for incoming bytes 403 | kbuf_input: 404 | .space 256 405 | ; number of bytes in the input buffer 406 | kbuf_input_pos: 407 | .space 1 408 | ; variable != 0 if '\n' was read, otherwise 0 409 | kbuf_input_ready: 410 | .space 1 411 | ; output buffer 412 | kbuf_output: 413 | .space 256 414 | kbuf_output_end: ; points to the 1st free byte behind the data to send 415 | .space 1 416 | kbuf_output_pos: ; points to the 1st byte of data to send 417 | .space 1 418 | 419 | --------------------------------------------------------------------------------