From 0bff1ffcacd5d0971361cf28803a9b1e9d5378ed Mon Sep 17 00:00:00 2001 From: David Lenfesty Date: Thu, 5 Dec 2019 17:24:03 -0700 Subject: [PATCH] Importing code. I swear there are more commits :) Imported code from larger repo for code. Has some stuff I didn't want here. --- 312_final.code-workspace | 24 + hw/DAC.sch | 16 + hw/DAC.sch-bak | 17 + hw/amp.sch | 172 ++ hw/amp.sch-bak | 173 ++ hw/final.lib | 32 + hw/final_project-cache.lib | 342 +++ hw/final_project-rescue.dcm | 3 + hw/final_project-rescue.lib | 83 + hw/final_project.kicad_pcb | 1 + hw/final_project.pro | 33 + hw/final_project.sch | 788 +++++++ hw/final_project.sch-bak | 787 +++++++ hw/sym-lib-table | 4 + lcd_disp/.vscode/c_cpp_properties.json | 19 + lcd_disp/.vscode/settings.json | 3 + lcd_disp/.vscode/tasks.json | 19 + lcd_disp/Makefile | 52 + lcd_disp/comms.h | 1 + lcd_disp/comms.o | Bin 0 -> 7200 bytes lcd_disp/fifo.c | 52 + lcd_disp/fifo.h | 36 + lcd_disp/fifo.o | Bin 0 -> 6792 bytes lcd_disp/lcd.c | 592 ++++++ lcd_disp/lcd.h | 369 ++++ lcd_disp/lcd.o | Bin 0 -> 13368 bytes lcd_disp/main.c | 172 ++ lcd_disp/main.h | 23 + lcd_disp/main.o | Bin 0 -> 12944 bytes lcd_disp/periph.c | 69 + lcd_disp/periph.h | 45 + lcd_disp/periph.o | Bin 0 -> 8172 bytes lcd_disp/sd-reader.hex | 91 + lcd_disp/sd-reader.map | 491 +++++ lcd_disp/sd-reader.out | Bin 0 -> 22688 bytes sd_reader/.vscode/c_cpp_properties.json | 18 + sd_reader/.vscode/tasks.json | 19 + sd_reader/ChangeLog | 124 ++ sd_reader/Doxyfile | 1525 ++++++++++++++ sd_reader/FAQ | 124 ++ sd_reader/Makefile | 52 + sd_reader/byteordering.c | 110 + sd_reader/byteordering.h | 188 ++ sd_reader/byteordering.o | Bin 0 -> 5880 bytes sd_reader/comms.h | 1 + sd_reader/doc/pic01.jpg | Bin 0 -> 35891 bytes sd_reader/doc/pic02.jpg | Bin 0 -> 31797 bytes sd_reader/fat.c | 2551 +++++++++++++++++++++++ sd_reader/fat.h | 131 ++ sd_reader/fat.o | Bin 0 -> 42444 bytes sd_reader/fat_config.h | 128 ++ sd_reader/fifo.c | 52 + sd_reader/fifo.h | 36 + sd_reader/fifo.o | Bin 0 -> 6852 bytes sd_reader/main.c | 223 ++ sd_reader/main.h | 28 + sd_reader/main.o | Bin 0 -> 16240 bytes sd_reader/partition.c | 155 ++ sd_reader/partition.h | 212 ++ sd_reader/partition.o | Bin 0 -> 8220 bytes sd_reader/partition_config.h | 44 + sd_reader/periph.c | 91 + sd_reader/periph.h | 13 + sd_reader/periph.o | Bin 0 -> 10808 bytes sd_reader/sd-reader.hex | 920 ++++++++ sd_reader/sd-reader.map | 899 ++++++++ sd_reader/sd-reader.out | Bin 0 -> 60664 bytes sd_reader/sd-reader_config.h | 53 + sd_reader/sd_raw.c | 998 +++++++++ sd_reader/sd_raw.h | 148 ++ sd_reader/sd_raw.o | Bin 0 -> 23960 bytes sd_reader/sd_raw_config.h | 139 ++ sd_reader/wav.h | 43 + 73 files changed, 13534 insertions(+) create mode 100644 312_final.code-workspace create mode 100644 hw/DAC.sch create mode 100644 hw/DAC.sch-bak create mode 100644 hw/amp.sch create mode 100644 hw/amp.sch-bak create mode 100644 hw/final.lib create mode 100644 hw/final_project-cache.lib create mode 100644 hw/final_project-rescue.dcm create mode 100644 hw/final_project-rescue.lib create mode 100644 hw/final_project.kicad_pcb create mode 100644 hw/final_project.pro create mode 100644 hw/final_project.sch create mode 100644 hw/final_project.sch-bak create mode 100644 hw/sym-lib-table create mode 100644 lcd_disp/.vscode/c_cpp_properties.json create mode 100644 lcd_disp/.vscode/settings.json create mode 100644 lcd_disp/.vscode/tasks.json create mode 100644 lcd_disp/Makefile create mode 120000 lcd_disp/comms.h create mode 100644 lcd_disp/comms.o create mode 100644 lcd_disp/fifo.c create mode 100644 lcd_disp/fifo.h create mode 100644 lcd_disp/fifo.o create mode 100644 lcd_disp/lcd.c create mode 100644 lcd_disp/lcd.h create mode 100644 lcd_disp/lcd.o create mode 100644 lcd_disp/main.c create mode 100644 lcd_disp/main.h create mode 100644 lcd_disp/main.o create mode 100644 lcd_disp/periph.c create mode 100644 lcd_disp/periph.h create mode 100644 lcd_disp/periph.o create mode 100644 lcd_disp/sd-reader.hex create mode 100644 lcd_disp/sd-reader.map create mode 100755 lcd_disp/sd-reader.out create mode 100644 sd_reader/.vscode/c_cpp_properties.json create mode 100644 sd_reader/.vscode/tasks.json create mode 100644 sd_reader/ChangeLog create mode 100644 sd_reader/Doxyfile create mode 100644 sd_reader/FAQ create mode 100644 sd_reader/Makefile create mode 100644 sd_reader/byteordering.c create mode 100644 sd_reader/byteordering.h create mode 100644 sd_reader/byteordering.o create mode 120000 sd_reader/comms.h create mode 100644 sd_reader/doc/pic01.jpg create mode 100644 sd_reader/doc/pic02.jpg create mode 100644 sd_reader/fat.c create mode 100644 sd_reader/fat.h create mode 100644 sd_reader/fat.o create mode 100644 sd_reader/fat_config.h create mode 100644 sd_reader/fifo.c create mode 100644 sd_reader/fifo.h create mode 100644 sd_reader/fifo.o create mode 100644 sd_reader/main.c create mode 100644 sd_reader/main.h create mode 100644 sd_reader/main.o create mode 100644 sd_reader/partition.c create mode 100644 sd_reader/partition.h create mode 100644 sd_reader/partition.o create mode 100644 sd_reader/partition_config.h create mode 100644 sd_reader/periph.c create mode 100644 sd_reader/periph.h create mode 100644 sd_reader/periph.o create mode 100644 sd_reader/sd-reader.hex create mode 100644 sd_reader/sd-reader.map create mode 100755 sd_reader/sd-reader.out create mode 100644 sd_reader/sd-reader_config.h create mode 100644 sd_reader/sd_raw.c create mode 100644 sd_reader/sd_raw.h create mode 100644 sd_reader/sd_raw.o create mode 100644 sd_reader/sd_raw_config.h create mode 100644 sd_reader/wav.h diff --git a/312_final.code-workspace b/312_final.code-workspace new file mode 100644 index 0000000..186d677 --- /dev/null +++ b/312_final.code-workspace @@ -0,0 +1,24 @@ +{ + "folders": [ + { + "path": "sd_reader" + }, + { + "path": "lcd_disp" + }, + { + "path": "testing" + } + ], + "settings": { + "files.associations": { + "io.h": "c", + "interrupt.h": "c", + "periph.h": "c", + "comms.h": "c", + "string.h": "c", + "main.h": "c", + "sleep.h": "c" + } + } +} \ No newline at end of file diff --git a/hw/DAC.sch b/hw/DAC.sch new file mode 100644 index 0000000..8cd5fdf --- /dev/null +++ b/hw/DAC.sch @@ -0,0 +1,16 @@ +EESchema Schematic File Version 4 +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 2 3 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +$EndSCHEMATC diff --git a/hw/DAC.sch-bak b/hw/DAC.sch-bak new file mode 100644 index 0000000..8be6da2 --- /dev/null +++ b/hw/DAC.sch-bak @@ -0,0 +1,17 @@ +EESchema Schematic File Version 4 +LIBS:final_project-cache +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 2 3 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +$EndSCHEMATC diff --git a/hw/amp.sch b/hw/amp.sch new file mode 100644 index 0000000..83c5fcd --- /dev/null +++ b/hw/amp.sch @@ -0,0 +1,172 @@ +EESchema Schematic File Version 4 +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 3 3 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +Text HLabel 2350 1500 0 50 Input ~ 0 +DAC_out +$Comp +L Amplifier_Operational:TL072 U? +U 1 1 5DDEF8DF +P 4500 2400 +F 0 "U?" H 4500 2767 50 0000 C CNN +F 1 "TL072" H 4500 2676 50 0000 C CNN +F 2 "" H 4500 2400 50 0001 C CNN +F 3 "http://www.ti.com/lit/ds/symlink/tl071.pdf" H 4500 2400 50 0001 C CNN + 1 4500 2400 + 1 0 0 -1 +$EndComp +$Comp +L Amplifier_Operational:TL072 U? +U 2 1 5DDF08AD +P 6350 2650 +F 0 "U?" H 6350 3017 50 0000 C CNN +F 1 "TL072" H 6350 2926 50 0000 C CNN +F 2 "" H 6350 2650 50 0001 C CNN +F 3 "http://www.ti.com/lit/ds/symlink/tl071.pdf" H 6350 2650 50 0001 C CNN + 2 6350 2650 + 1 0 0 -1 +$EndComp +Wire Wire Line + 2350 1500 3050 1500 +Wire Wire Line + 3050 1500 3050 2300 +Wire Wire Line + 4150 2500 4200 2500 +Text Label 5600 2400 0 50 ~ 0 +DIVIDER_OUT +$Comp +L Device:R_POT RV? +U 1 1 5DDFB96D +P 5100 2550 +F 0 "RV?" H 5031 2596 50 0000 R CNN +F 1 "R_POT" H 5031 2505 50 0000 R CNN +F 2 "" H 5100 2550 50 0001 C CNN +F 3 "~" H 5100 2550 50 0001 C CNN + 1 5100 2550 + 1 0 0 -1 +$EndComp +Wire Wire Line + 4800 2400 5100 2400 +Wire Wire Line + 5100 2700 5100 2900 +Wire Wire Line + 5100 2900 5050 2900 +$Comp +L power:GND #PWR? +U 1 1 5DDFD958 +P 5050 2900 +F 0 "#PWR?" H 5050 2650 50 0001 C CNN +F 1 "GND" H 5055 2727 50 0000 C CNN +F 2 "" H 5050 2900 50 0001 C CNN +F 3 "" H 5050 2900 50 0001 C CNN + 1 5050 2900 + 1 0 0 -1 +$EndComp +Wire Wire Line + 5250 2550 6050 2550 +Wire Wire Line + 4150 2500 4150 2650 +Wire Wire Line + 4150 2650 4800 2650 +Wire Wire Line + 4800 2650 4800 2400 +Connection ~ 4800 2400 +Wire Wire Line + 6050 2750 6050 2950 +Wire Wire Line + 6050 2950 6650 2950 +Wire Wire Line + 6650 2950 6650 2650 +Wire Wire Line + 6650 2650 6850 2650 +Connection ~ 6650 2650 +$Comp +L Device:R R? +U 1 1 5DE007D1 +P 6850 2800 +F 0 "R?" H 6920 2846 50 0000 L CNN +F 1 "R" H 6920 2755 50 0000 L CNN +F 2 "" V 6780 2800 50 0001 C CNN +F 3 "~" H 6850 2800 50 0001 C CNN + 1 6850 2800 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE00F57 +P 6850 3100 +F 0 "R?" H 6920 3146 50 0000 L CNN +F 1 "R" H 6920 3055 50 0000 L CNN +F 2 "" V 6780 3100 50 0001 C CNN +F 3 "~" H 6850 3100 50 0001 C CNN + 1 6850 3100 + 1 0 0 -1 +$EndComp +Wire Wire Line + 6850 2950 7250 2950 +Connection ~ 6850 2950 +Wire Wire Line + 6850 3250 6850 3350 +$Comp +L power:GND #PWR? +U 1 1 5DE01A1C +P 6850 3350 +F 0 "#PWR?" H 6850 3100 50 0001 C CNN +F 1 "GND" H 6855 3177 50 0000 C CNN +F 2 "" H 6850 3350 50 0001 C CNN +F 3 "" H 6850 3350 50 0001 C CNN + 1 6850 3350 + 1 0 0 -1 +$EndComp +$Comp +L Device:C C? +U 1 1 5DDF1BEF +P 3200 2300 +F 0 "C?" V 2948 2300 50 0000 C CNN +F 1 "C" V 3039 2300 50 0000 C CNN +F 2 "" H 3238 2150 50 0001 C CNN +F 3 "~" H 3200 2300 50 0001 C CNN + 1 3200 2300 + 0 1 1 0 +$EndComp +$Comp +L Device:R 200k +U 1 1 5DDF226F +P 3700 2450 +F 0 "200k" H 3770 2496 50 0000 L CNN +F 1 "R" H 3770 2405 50 0000 L CNN +F 2 "" V 3630 2450 50 0001 C CNN +F 3 "~" H 3700 2450 50 0001 C CNN + 1 3700 2450 + 1 0 0 -1 +$EndComp +Wire Wire Line + 3350 2300 3700 2300 +Wire Wire Line + 3700 2300 4200 2300 +Connection ~ 3700 2300 +Wire Wire Line + 3700 2600 3700 2750 +$Comp +L power:GND #PWR? +U 1 1 5DDF33D2 +P 3700 2750 +F 0 "#PWR?" H 3700 2500 50 0001 C CNN +F 1 "GND" H 3705 2577 50 0000 C CNN +F 2 "" H 3700 2750 50 0001 C CNN +F 3 "" H 3700 2750 50 0001 C CNN + 1 3700 2750 + 1 0 0 -1 +$EndComp +$EndSCHEMATC diff --git a/hw/amp.sch-bak b/hw/amp.sch-bak new file mode 100644 index 0000000..4fb5383 --- /dev/null +++ b/hw/amp.sch-bak @@ -0,0 +1,173 @@ +EESchema Schematic File Version 4 +LIBS:final_project-cache +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 3 3 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +Text HLabel 2350 1500 0 50 Input ~ 0 +DAC_out +$Comp +L Amplifier_Operational:TL072 U? +U 1 1 5DDEF8DF +P 4500 2400 +F 0 "U?" H 4500 2767 50 0000 C CNN +F 1 "TL072" H 4500 2676 50 0000 C CNN +F 2 "" H 4500 2400 50 0001 C CNN +F 3 "http://www.ti.com/lit/ds/symlink/tl071.pdf" H 4500 2400 50 0001 C CNN + 1 4500 2400 + 1 0 0 -1 +$EndComp +$Comp +L Amplifier_Operational:TL072 U? +U 2 1 5DDF08AD +P 6350 2650 +F 0 "U?" H 6350 3017 50 0000 C CNN +F 1 "TL072" H 6350 2926 50 0000 C CNN +F 2 "" H 6350 2650 50 0001 C CNN +F 3 "http://www.ti.com/lit/ds/symlink/tl071.pdf" H 6350 2650 50 0001 C CNN + 2 6350 2650 + 1 0 0 -1 +$EndComp +Wire Wire Line + 2350 1500 3050 1500 +Wire Wire Line + 3050 1500 3050 2300 +Wire Wire Line + 4150 2500 4200 2500 +Text Label 5600 2400 0 50 ~ 0 +DIVIDER_OUT +$Comp +L Device:R_POT RV? +U 1 1 5DDFB96D +P 5100 2550 +F 0 "RV?" H 5031 2596 50 0000 R CNN +F 1 "R_POT" H 5031 2505 50 0000 R CNN +F 2 "" H 5100 2550 50 0001 C CNN +F 3 "~" H 5100 2550 50 0001 C CNN + 1 5100 2550 + 1 0 0 -1 +$EndComp +Wire Wire Line + 4800 2400 5100 2400 +Wire Wire Line + 5100 2700 5100 2900 +Wire Wire Line + 5100 2900 5050 2900 +$Comp +L power:GND #PWR? +U 1 1 5DDFD958 +P 5050 2900 +F 0 "#PWR?" H 5050 2650 50 0001 C CNN +F 1 "GND" H 5055 2727 50 0000 C CNN +F 2 "" H 5050 2900 50 0001 C CNN +F 3 "" H 5050 2900 50 0001 C CNN + 1 5050 2900 + 1 0 0 -1 +$EndComp +Wire Wire Line + 5250 2550 6050 2550 +Wire Wire Line + 4150 2500 4150 2650 +Wire Wire Line + 4150 2650 4800 2650 +Wire Wire Line + 4800 2650 4800 2400 +Connection ~ 4800 2400 +Wire Wire Line + 6050 2750 6050 2950 +Wire Wire Line + 6050 2950 6650 2950 +Wire Wire Line + 6650 2950 6650 2650 +Wire Wire Line + 6650 2650 6850 2650 +Connection ~ 6650 2650 +$Comp +L Device:R R? +U 1 1 5DE007D1 +P 6850 2800 +F 0 "R?" H 6920 2846 50 0000 L CNN +F 1 "R" H 6920 2755 50 0000 L CNN +F 2 "" V 6780 2800 50 0001 C CNN +F 3 "~" H 6850 2800 50 0001 C CNN + 1 6850 2800 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE00F57 +P 6850 3100 +F 0 "R?" H 6920 3146 50 0000 L CNN +F 1 "R" H 6920 3055 50 0000 L CNN +F 2 "" V 6780 3100 50 0001 C CNN +F 3 "~" H 6850 3100 50 0001 C CNN + 1 6850 3100 + 1 0 0 -1 +$EndComp +Wire Wire Line + 6850 2950 7250 2950 +Connection ~ 6850 2950 +Wire Wire Line + 6850 3250 6850 3350 +$Comp +L power:GND #PWR? +U 1 1 5DE01A1C +P 6850 3350 +F 0 "#PWR?" H 6850 3100 50 0001 C CNN +F 1 "GND" H 6855 3177 50 0000 C CNN +F 2 "" H 6850 3350 50 0001 C CNN +F 3 "" H 6850 3350 50 0001 C CNN + 1 6850 3350 + 1 0 0 -1 +$EndComp +$Comp +L Device:C C? +U 1 1 5DDF1BEF +P 3200 2300 +F 0 "C?" V 2948 2300 50 0000 C CNN +F 1 "C" V 3039 2300 50 0000 C CNN +F 2 "" H 3238 2150 50 0001 C CNN +F 3 "~" H 3200 2300 50 0001 C CNN + 1 3200 2300 + 0 1 1 0 +$EndComp +$Comp +L Device:R 200k +U 1 1 5DDF226F +P 3700 2450 +F 0 "200k" H 3770 2496 50 0000 L CNN +F 1 "R" H 3770 2405 50 0000 L CNN +F 2 "" V 3630 2450 50 0001 C CNN +F 3 "~" H 3700 2450 50 0001 C CNN + 1 3700 2450 + 1 0 0 -1 +$EndComp +Wire Wire Line + 3350 2300 3700 2300 +Wire Wire Line + 3700 2300 4200 2300 +Connection ~ 3700 2300 +Wire Wire Line + 3700 2600 3700 2750 +$Comp +L power:GND #PWR? +U 1 1 5DDF33D2 +P 3700 2750 +F 0 "#PWR?" H 3700 2500 50 0001 C CNN +F 1 "GND" H 3705 2577 50 0000 C CNN +F 2 "" H 3700 2750 50 0001 C CNN +F 3 "" H 3700 2750 50 0001 C CNN + 1 3700 2750 + 1 0 0 -1 +$EndComp +$EndSCHEMATC diff --git a/hw/final.lib b/hw/final.lib new file mode 100644 index 0000000..bbfb010 --- /dev/null +++ b/hw/final.lib @@ -0,0 +1,32 @@ +EESchema-LIBRARY Version 2.4 +#encoding utf-8 +# +# LCD +# +DEF LCD U 0 40 Y Y 1 F N +F0 "U" 250 -1300 50 H V C CNN +F1 "LCD" 250 200 50 H V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +DRAW +S -150 250 350 -1350 0 1 0 f +X GND 1 -250 200 100 R 50 50 1 1 W +X D3 10 -250 -700 100 R 50 50 1 1 B +X D4 11 -250 -800 100 R 50 50 1 1 B +X D5 12 -250 -900 100 R 50 50 1 1 B +X D6 13 -250 -1000 100 R 50 50 1 1 B +X D7 14 -250 -1100 100 R 50 50 1 1 B +X NC 15 -250 -1200 100 R 50 50 1 1 N +X NC 16 -250 -1300 100 R 50 50 1 1 N +X VDD 2 -250 100 100 R 50 50 1 1 W +X Vo 3 -250 0 100 R 50 50 1 1 I +X RS 4 -250 -100 100 R 50 50 1 1 I +X RW 5 -250 -200 100 R 50 50 1 1 I +X EN 6 -250 -300 100 R 50 50 1 1 I +X D0 7 -250 -400 100 R 50 50 1 1 B +X D1 8 -250 -500 100 R 50 50 1 1 B +X D2 9 -250 -600 100 R 50 50 1 1 B +ENDDRAW +ENDDEF +# +#End Library diff --git a/hw/final_project-cache.lib b/hw/final_project-cache.lib new file mode 100644 index 0000000..876be8c --- /dev/null +++ b/hw/final_project-cache.lib @@ -0,0 +1,342 @@ +EESchema-LIBRARY Version 2.4 +#encoding utf-8 +# +# Amplifier_Operational_TL072 +# +DEF Amplifier_Operational_TL072 U 0 5 Y Y 3 L N +F0 "U" 0 200 50 H V L CNN +F1 "Amplifier_Operational_TL072" 0 -200 50 H V L CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +ALIAS LM358 AD8620 LMC6062 LMC6082 TL062 TL072 TL082 NE5532 SA5532 RC4558 RC4560 RC4580 LMV358 TS912 TSV912IDT TSV912IST TLC272 TLC277 MCP602 OPA1678 OPA2134 OPA2340 OPA2376xxD OPA2376xxDGK MC33078 MC33178 LM4562 OP249 OP275 ADA4075-2 MCP6002-xP MCP6002-xSN MCP6002-xMS LM7332 OPA2333xxD OPA2333xxDGK LMC6482 LT1492 LTC6081xMS8 LM6172 MCP6L92 NJM2043 NJM2114 NJM4556A NJM4558 NJM4559 NJM4560 NJM4580 NJM5532 ADA4807-2ARM OPA2691 LT6234 OPA2356xxD OPA2356xxDGK OPA1612AxD MC33172 OPA1602 TLV2372 LT6237 OPA2277 +$FPLIST + SOIC*3.9x4.9mm*P1.27mm* + DIP*W7.62mm* + TO*99* + OnSemi*Micro8* + TSSOP*3x3mm*P0.65mm* + TSSOP*4.4x3mm*P0.65mm* + MSOP*3x3mm*P0.65mm* + SSOP*3.9x4.9mm*P0.635mm* + LFCSP*2x2mm*P0.5mm* + *SIP* + SOIC*5.3x6.2mm*P1.27mm* +$ENDFPLIST +DRAW +P 4 1 1 10 -200 200 200 0 -200 -200 -200 200 f +P 4 2 1 10 -200 200 200 0 -200 -200 -200 200 f +X ~ 1 300 0 100 L 50 50 1 1 O +X - 2 -300 -100 100 R 50 50 1 1 I +X + 3 -300 100 100 R 50 50 1 1 I +X + 5 -300 100 100 R 50 50 2 1 I +X - 6 -300 -100 100 R 50 50 2 1 I +X ~ 7 300 0 100 L 50 50 2 1 O +X V- 4 -100 -300 150 U 50 50 3 1 W +X V+ 8 -100 300 150 D 50 50 3 1 W +ENDDRAW +ENDDEF +# +# Connector_SD_Card +# +DEF Connector_SD_Card J 0 40 Y Y 1 F N +F0 "J" -650 550 50 H V C CNN +F1 "Connector_SD_Card" 600 -550 50 H V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +$FPLIST + SD* +$ENDFPLIST +DRAW +S -350 -375 -250 -425 0 1 0 F +S -350 -275 -250 -325 0 1 0 F +S -350 -175 -250 -225 0 1 0 F +S -350 -75 -250 -125 0 1 0 F +S -350 25 -250 -25 0 1 0 F +S -350 125 -250 75 0 1 0 F +S -350 225 -250 175 0 1 0 F +S -350 325 -250 275 0 1 0 F +S -300 425 -200 375 0 1 0 F +P 6 0 1 0 -400 350 -300 450 800 450 800 -450 -400 -450 -400 350 f +P 6 0 1 0 650 450 650 500 -800 500 -800 -500 650 -500 650 -450 N +X CD/DAT3 1 -900 300 100 R 50 50 1 1 I +X CARD_DETECT 10 900 200 100 L 50 50 1 1 I +X WRITE_PROTECT 11 900 100 100 L 50 50 1 1 I +X SHELL1 12 900 -100 100 L 50 50 1 1 I +X SHELL2 13 900 -200 100 L 50 50 1 1 I +X CMD 2 -900 200 100 R 50 50 1 1 I +X VSS 3 -900 100 100 R 50 50 1 1 W +X VDD 4 -900 0 100 R 50 50 1 1 W +X CLK 5 -900 -100 100 R 50 50 1 1 I +X VSS 6 -900 -200 100 R 50 50 1 1 W +X DAT0 7 -900 -300 100 R 50 50 1 1 I +X DAT1 8 -900 -400 100 R 50 50 1 1 I +X DAT2 9 -900 400 100 R 50 50 1 1 I +ENDDRAW +ENDDEF +# +# Device_C +# +DEF Device_C C 0 10 N Y 1 F N +F0 "C" 25 100 50 H V L CNN +F1 "Device_C" 25 -100 50 H V L CNN +F2 "" 38 -150 50 H I C CNN +F3 "" 0 0 50 H I C CNN +$FPLIST + C_* +$ENDFPLIST +DRAW +P 2 0 1 20 -80 -30 80 -30 N +P 2 0 1 20 -80 30 80 30 N +X ~ 1 0 150 110 D 50 50 1 1 P +X ~ 2 0 -150 110 U 50 50 1 1 P +ENDDRAW +ENDDEF +# +# Device_Crystal +# +DEF Device_Crystal Y 0 40 N N 1 F N +F0 "Y" 0 150 50 H V C CNN +F1 "Device_Crystal" 0 -150 50 H V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +$FPLIST + Crystal* +$ENDFPLIST +DRAW +S -45 100 45 -100 0 1 12 N +P 2 0 1 0 -100 0 -75 0 N +P 2 0 1 20 -75 -50 -75 50 N +P 2 0 1 20 75 -50 75 50 N +P 2 0 1 0 100 0 75 0 N +X 1 1 -150 0 50 R 50 50 1 1 P +X 2 2 150 0 50 L 50 50 1 1 P +ENDDRAW +ENDDEF +# +# Device_R +# +DEF Device_R R 0 0 N Y 1 F N +F0 "R" 80 0 50 V V C CNN +F1 "Device_R" 0 0 50 V V C CNN +F2 "" -70 0 50 V I C CNN +F3 "" 0 0 50 H I C CNN +$FPLIST + R_* +$ENDFPLIST +DRAW +S -40 -100 40 100 0 1 10 N +X ~ 1 0 150 50 D 50 50 1 1 P +X ~ 2 0 -150 50 U 50 50 1 1 P +ENDDRAW +ENDDEF +# +# Device_R_POT +# +DEF Device_R_POT RV 0 40 Y N 1 F N +F0 "RV" -175 0 50 V V C CNN +F1 "Device_R_POT" -100 0 50 V V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +$FPLIST + Potentiometer* +$ENDFPLIST +DRAW +S 40 100 -40 -100 0 1 10 N +P 2 0 1 0 100 0 60 0 N +P 4 0 1 0 45 0 90 20 90 -20 45 0 F +X 1 1 0 150 50 D 50 50 1 1 P +X 2 2 150 0 50 L 50 50 1 1 P +X 3 3 0 -150 50 U 50 50 1 1 P +ENDDRAW +ENDDEF +# +# Regulator_Linear_AP2112K-3.3 +# +DEF Regulator_Linear_AP2112K-3.3 U 0 10 Y Y 1 F N +F0 "U" -200 225 50 H V L CNN +F1 "Regulator_Linear_AP2112K-3.3" 0 225 50 H V L CNN +F2 "Package_TO_SOT_SMD:SOT-23-5" 0 325 50 H I C CNN +F3 "" 0 100 50 H I C CNN +ALIAS AP2204K-1.8 AP2204K-2.5 AP2204K-2.8 AP2204K-3.0 AP2204K-3.3 AP2204K-5.0 AP2127K-1.0 AP2127K-1.2 AP2127K-1.5 AP2127K-1.8 AP2127K-2.5 AP2127K-2.8 AP2127K-3.0 AP2127K-3.3 AP2127K-4.2 AP2127K-4.75 AP2112K-1.2 AP2112K-1.8 AP2112K-2.5 AP2112K-2.6 AP2112K-3.3 +$FPLIST + SOT?23?5* +$ENDFPLIST +DRAW +S -200 175 200 -200 0 1 10 f +X VIN 1 -300 100 100 R 50 50 1 1 W +X GND 2 0 -300 100 U 50 50 1 1 W +X EN 3 -300 0 100 R 50 50 1 1 I +X NC 4 300 0 100 L 50 50 1 1 N N +X VOUT 5 300 100 100 L 50 50 1 1 w +ENDDRAW +ENDDEF +# +# Switch_SW_Push +# +DEF Switch_SW_Push SW 0 40 N N 1 F N +F0 "SW" 50 100 50 H V L CNN +F1 "Switch_SW_Push" 0 -60 50 H V C CNN +F2 "" 0 200 50 H I C CNN +F3 "" 0 200 50 H I C CNN +DRAW +C -80 0 20 0 1 0 N +C 80 0 20 0 1 0 N +P 2 0 1 0 0 50 0 120 N +P 2 0 1 0 100 50 -100 50 N +X 1 1 -200 0 100 R 50 50 0 1 P +X 2 2 200 0 100 L 50 50 0 1 P +ENDDRAW +ENDDEF +# +# final_LCD +# +DEF final_LCD U 0 40 Y Y 1 F N +F0 "U" 250 -1300 50 H V C CNN +F1 "final_LCD" 250 200 50 H V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +DRAW +S -150 250 350 -1350 0 1 0 f +X GND 1 -250 200 100 R 50 50 1 1 W +X D3 10 -250 -700 100 R 50 50 1 1 B +X D4 11 -250 -800 100 R 50 50 1 1 B +X D5 12 -250 -900 100 R 50 50 1 1 B +X D6 13 -250 -1000 100 R 50 50 1 1 B +X D7 14 -250 -1100 100 R 50 50 1 1 B +X NC 15 -250 -1200 100 R 50 50 1 1 N +X NC 16 -250 -1300 100 R 50 50 1 1 N +X VDD 2 -250 100 100 R 50 50 1 1 W +X Vo 3 -250 0 100 R 50 50 1 1 I +X RS 4 -250 -100 100 R 50 50 1 1 I +X RW 5 -250 -200 100 R 50 50 1 1 I +X EN 6 -250 -300 100 R 50 50 1 1 I +X D0 7 -250 -400 100 R 50 50 1 1 B +X D1 8 -250 -500 100 R 50 50 1 1 B +X D2 9 -250 -600 100 R 50 50 1 1 B +ENDDRAW +ENDDEF +# +# final_project-rescue_ATmega328-PU-MCU_Microchip_ATmega +# +DEF final_project-rescue_ATmega328-PU-MCU_Microchip_ATmega U 0 20 Y Y 1 F N +F0 "U" -500 1450 50 H V L BNN +F1 "final_project-rescue_ATmega328-PU-MCU_Microchip_ATmega" 100 -1450 50 H V L TNN +F2 "Package_DIP:DIP-28_W7.62mm" 0 0 50 H I C CIN +F3 "" 0 0 50 H I C CNN +$FPLIST + DIP*W7.62mm* +$ENDFPLIST +DRAW +S -500 -1400 500 1400 0 1 10 f +X ~RESET~/PC6 1 600 -300 100 L 50 50 1 1 T +X XTAL2/PB7 10 600 500 100 L 50 50 1 1 T +X PD5 11 600 -1000 100 L 50 50 1 1 T +X PD6 12 600 -1100 100 L 50 50 1 1 T +X PD7 13 600 -1200 100 L 50 50 1 1 T +X PB0 14 600 1200 100 L 50 50 1 1 T +X PB1 15 600 1100 100 L 50 50 1 1 T +X PB2 16 600 1000 100 L 50 50 1 1 T +X PB3 17 600 900 100 L 50 50 1 1 T +X PB4 18 600 800 100 L 50 50 1 1 T +X PB5 19 600 700 100 L 50 50 1 1 T +X PD0 2 600 -500 100 L 50 50 1 1 T +X AVCC 20 100 1500 100 D 50 50 1 1 W +X AREF 21 -600 1200 100 R 50 50 1 1 P +X GND 22 0 -1500 100 U 50 50 1 1 P N +X PC0 23 600 300 100 L 50 50 1 1 T +X PC1 24 600 200 100 L 50 50 1 1 T +X PC2 25 600 100 100 L 50 50 1 1 T +X PC3 26 600 0 100 L 50 50 1 1 T +X PC4 27 600 -100 100 L 50 50 1 1 T +X PC5 28 600 -200 100 L 50 50 1 1 T +X PD1 3 600 -600 100 L 50 50 1 1 T +X PD2 4 600 -700 100 L 50 50 1 1 T +X PD3 5 600 -800 100 L 50 50 1 1 T +X PD4 6 600 -900 100 L 50 50 1 1 T +X VCC 7 0 1500 100 D 50 50 1 1 W +X GND 8 0 -1500 100 U 50 50 1 1 W +X XTAL1/PB6 9 600 600 100 L 50 50 1 1 T +ENDDRAW +ENDDEF +# +# final_project-rescue_ATtiny2313-20MU-MCU_Microchip_ATtiny +# +DEF final_project-rescue_ATtiny2313-20MU-MCU_Microchip_ATtiny U 0 20 Y Y 1 F N +F0 "U" -500 1050 50 H V L BNN +F1 "final_project-rescue_ATtiny2313-20MU-MCU_Microchip_ATtiny" 100 -1050 50 H V L TNN +F2 "Package_DFN_QFN:MLF-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm" 0 0 50 H I C CIN +F3 "" 0 0 50 H I C CNN +$FPLIST + MLF*1EP*4x4mm*P0.5mm* +$ENDFPLIST +DRAW +S -500 -1000 500 1000 0 1 10 f +X PD1 1 600 -200 100 L 50 50 1 1 T +X PB0 10 600 800 100 L 50 50 1 1 T +X PB1 11 600 700 100 L 50 50 1 1 T +X PB2 12 600 600 100 L 50 50 1 1 T +X PB3 13 600 500 100 L 50 50 1 1 T +X PB4 14 600 400 100 L 50 50 1 1 T +X PB5 15 600 300 100 L 50 50 1 1 T +X PB6 16 600 200 100 L 50 50 1 1 T +X PB7 17 600 100 100 L 50 50 1 1 T +X VCC 18 0 1100 100 D 50 50 1 1 W +X PA2/~RESET 19 -600 800 100 R 50 50 1 1 T +X PA1/XTAL2 2 -600 400 100 R 50 50 1 1 T +X PD0 20 600 -100 100 L 50 50 1 1 T +X GND 21 0 -1100 100 U 50 50 1 1 P N +X PA0/XTAL1 3 -600 600 100 R 50 50 1 1 T +X PD2 4 600 -300 100 L 50 50 1 1 T +X PD3 5 600 -400 100 L 50 50 1 1 T +X PD4 6 600 -500 100 L 50 50 1 1 T +X PD5 7 600 -600 100 L 50 50 1 1 T +X GND 8 0 -1100 100 U 50 50 1 1 W +X PD6 9 600 -700 100 L 50 50 1 1 T +ENDDRAW +ENDDEF +# +# power_+3V3 +# +DEF power_+3V3 #PWR 0 0 Y Y 1 F P +F0 "#PWR" 0 -150 50 H I C CNN +F1 "power_+3V3" 0 140 50 H V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +ALIAS +3.3V +DRAW +P 2 0 1 0 -30 50 0 100 N +P 2 0 1 0 0 0 0 100 N +P 2 0 1 0 0 100 30 50 N +X +3V3 1 0 0 0 U 50 50 1 1 W N +ENDDRAW +ENDDEF +# +# power_+5V +# +DEF power_+5V #PWR 0 0 Y Y 1 F P +F0 "#PWR" 0 -150 50 H I C CNN +F1 "power_+5V" 0 140 50 H V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +DRAW +P 2 0 1 0 -30 50 0 100 N +P 2 0 1 0 0 0 0 100 N +P 2 0 1 0 0 100 30 50 N +X +5V 1 0 0 0 U 50 50 1 1 W N +ENDDRAW +ENDDEF +# +# power_GND +# +DEF power_GND #PWR 0 0 Y Y 1 F P +F0 "#PWR" 0 -250 50 H I C CNN +F1 "power_GND" 0 -150 50 H V C CNN +F2 "" 0 0 50 H I C CNN +F3 "" 0 0 50 H I C CNN +DRAW +P 6 0 1 0 0 0 0 -50 50 -50 0 -100 -50 -50 0 -50 N +X GND 1 0 0 0 D 50 50 1 1 W N +ENDDRAW +ENDDEF +# +#End Library diff --git a/hw/final_project-rescue.dcm b/hw/final_project-rescue.dcm new file mode 100644 index 0000000..5f3ed79 --- /dev/null +++ b/hw/final_project-rescue.dcm @@ -0,0 +1,3 @@ +EESchema-DOCLIB Version 2.0 +# +#End Doc Library diff --git a/hw/final_project-rescue.lib b/hw/final_project-rescue.lib new file mode 100644 index 0000000..f7fbf5a --- /dev/null +++ b/hw/final_project-rescue.lib @@ -0,0 +1,83 @@ +EESchema-LIBRARY Version 2.4 +#encoding utf-8 +# +# ATmega328-PU-MCU_Microchip_ATmega +# +DEF ATmega328-PU-MCU_Microchip_ATmega U 0 20 Y Y 1 F N +F0 "U" -500 1450 50 H V L BNN +F1 "ATmega328-PU-MCU_Microchip_ATmega" 100 -1450 50 H V L TNN +F2 "Package_DIP:DIP-28_W7.62mm" 0 0 50 H I C CIN +F3 "" 0 0 50 H I C CNN +$FPLIST + DIP*W7.62mm* +$ENDFPLIST +DRAW +S -500 -1400 500 1400 0 1 10 f +X ~RESET~/PC6 1 600 -300 100 L 50 50 1 1 T +X XTAL2/PB7 10 600 500 100 L 50 50 1 1 T +X PD5 11 600 -1000 100 L 50 50 1 1 T +X PD6 12 600 -1100 100 L 50 50 1 1 T +X PD7 13 600 -1200 100 L 50 50 1 1 T +X PB0 14 600 1200 100 L 50 50 1 1 T +X PB1 15 600 1100 100 L 50 50 1 1 T +X PB2 16 600 1000 100 L 50 50 1 1 T +X PB3 17 600 900 100 L 50 50 1 1 T +X PB4 18 600 800 100 L 50 50 1 1 T +X PB5 19 600 700 100 L 50 50 1 1 T +X PD0 2 600 -500 100 L 50 50 1 1 T +X AVCC 20 100 1500 100 D 50 50 1 1 W +X AREF 21 -600 1200 100 R 50 50 1 1 P +X GND 22 0 -1500 100 U 50 50 1 1 P N +X PC0 23 600 300 100 L 50 50 1 1 T +X PC1 24 600 200 100 L 50 50 1 1 T +X PC2 25 600 100 100 L 50 50 1 1 T +X PC3 26 600 0 100 L 50 50 1 1 T +X PC4 27 600 -100 100 L 50 50 1 1 T +X PC5 28 600 -200 100 L 50 50 1 1 T +X PD1 3 600 -600 100 L 50 50 1 1 T +X PD2 4 600 -700 100 L 50 50 1 1 T +X PD3 5 600 -800 100 L 50 50 1 1 T +X PD4 6 600 -900 100 L 50 50 1 1 T +X VCC 7 0 1500 100 D 50 50 1 1 W +X GND 8 0 -1500 100 U 50 50 1 1 W +X XTAL1/PB6 9 600 600 100 L 50 50 1 1 T +ENDDRAW +ENDDEF +# +# ATtiny2313-20MU-MCU_Microchip_ATtiny +# +DEF ATtiny2313-20MU-MCU_Microchip_ATtiny U 0 20 Y Y 1 F N +F0 "U" -500 1050 50 H V L BNN +F1 "ATtiny2313-20MU-MCU_Microchip_ATtiny" 100 -1050 50 H V L TNN +F2 "Package_DFN_QFN:MLF-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm" 0 0 50 H I C CIN +F3 "" 0 0 50 H I C CNN +$FPLIST + MLF*1EP*4x4mm*P0.5mm* +$ENDFPLIST +DRAW +S -500 -1000 500 1000 0 1 10 f +X PD1 1 600 -200 100 L 50 50 1 1 T +X PB0 10 600 800 100 L 50 50 1 1 T +X PB1 11 600 700 100 L 50 50 1 1 T +X PB2 12 600 600 100 L 50 50 1 1 T +X PB3 13 600 500 100 L 50 50 1 1 T +X PB4 14 600 400 100 L 50 50 1 1 T +X PB5 15 600 300 100 L 50 50 1 1 T +X PB6 16 600 200 100 L 50 50 1 1 T +X PB7 17 600 100 100 L 50 50 1 1 T +X VCC 18 0 1100 100 D 50 50 1 1 W +X PA2/~RESET 19 -600 800 100 R 50 50 1 1 T +X PA1/XTAL2 2 -600 400 100 R 50 50 1 1 T +X PD0 20 600 -100 100 L 50 50 1 1 T +X GND 21 0 -1100 100 U 50 50 1 1 P N +X PA0/XTAL1 3 -600 600 100 R 50 50 1 1 T +X PD2 4 600 -300 100 L 50 50 1 1 T +X PD3 5 600 -400 100 L 50 50 1 1 T +X PD4 6 600 -500 100 L 50 50 1 1 T +X PD5 7 600 -600 100 L 50 50 1 1 T +X GND 8 0 -1100 100 U 50 50 1 1 W +X PD6 9 600 -700 100 L 50 50 1 1 T +ENDDRAW +ENDDEF +# +#End Library diff --git a/hw/final_project.kicad_pcb b/hw/final_project.kicad_pcb new file mode 100644 index 0000000..02c8ecb --- /dev/null +++ b/hw/final_project.kicad_pcb @@ -0,0 +1 @@ +(kicad_pcb (version 4) (host kicad "dummy file") ) diff --git a/hw/final_project.pro b/hw/final_project.pro new file mode 100644 index 0000000..152769c --- /dev/null +++ b/hw/final_project.pro @@ -0,0 +1,33 @@ +update=22/05/2015 07:44:53 +version=1 +last_client=kicad +[general] +version=1 +RootSch= +BoardNm= +[pcbnew] +version=1 +LastNetListRead= +UseCmpFile=1 +PadDrill=0.600000000000 +PadDrillOvalY=0.600000000000 +PadSizeH=1.500000000000 +PadSizeV=1.500000000000 +PcbTextSizeV=1.500000000000 +PcbTextSizeH=1.500000000000 +PcbTextThickness=0.300000000000 +ModuleTextSizeV=1.000000000000 +ModuleTextSizeH=1.000000000000 +ModuleTextSizeThickness=0.150000000000 +SolderMaskClearance=0.000000000000 +SolderMaskMinWidth=0.000000000000 +DrawSegmentWidth=0.200000000000 +BoardOutlineThickness=0.100000000000 +ModuleOutlineThickness=0.150000000000 +[cvpcb] +version=1 +NetIExt=net +[eeschema] +version=1 +LibDir= +[eeschema/libraries] diff --git a/hw/final_project.sch b/hw/final_project.sch new file mode 100644 index 0000000..a0cad2e --- /dev/null +++ b/hw/final_project.sch @@ -0,0 +1,788 @@ +EESchema Schematic File Version 4 +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 1 3 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +$Comp +L final_project-rescue:ATmega328-PU-MCU_Microchip_ATmega U? +U 1 1 5DDD9932 +P 3450 2950 +F 0 "U?" H 2806 2996 50 0000 R CNN +F 1 "ATmega328-PU" H 2806 2905 50 0000 R CNN +F 2 "Package_DIP:DIP-28_W7.62mm" H 3450 2950 50 0001 C CIN +F 3 "http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega328_P%20AVR%20MCU%20with%20picoPower%20Technology%20Data%20Sheet%2040001984A.pdf" H 3450 2950 50 0001 C CNN + 1 3450 2950 + 1 0 0 -1 +$EndComp +Wire Wire Line + 4050 2250 4250 2250 +Wire Wire Line + 4050 2150 4250 2150 +Wire Wire Line + 4050 2050 4250 2050 +Wire Wire Line + 4050 1950 4250 1950 +Text Label 4250 2250 0 50 ~ 0 +SCK +Text Label 4250 2150 0 50 ~ 0 +MOSI +Text Label 4250 2050 0 50 ~ 0 +MISO +Text Label 4250 1950 0 50 ~ 0 +CS +Wire Wire Line + 4050 3450 4250 3450 +Wire Wire Line + 4050 3550 4250 3550 +Text Label 4250 3450 0 50 ~ 0 +RXD +Text Label 4250 3550 0 50 ~ 0 +TXD +$Comp +L Device:Crystal Y? +U 1 1 5DDDDF12 +P 4650 2450 +F 0 "Y?" V 4604 2581 50 0000 L CNN +F 1 "16MHz" V 4695 2581 50 0000 L CNN +F 2 "" H 4650 2450 50 0001 C CNN +F 3 "~" H 4650 2450 50 0001 C CNN + 1 4650 2450 + 0 1 1 0 +$EndComp +Wire Wire Line + 4650 2350 4650 2300 +Wire Wire Line + 4050 2450 4300 2450 +Wire Wire Line + 4650 2300 5250 2300 +Connection ~ 4650 2300 +Wire Wire Line + 4650 2600 5150 2600 +$Comp +L Device:C C? +U 1 1 5DDDEABA +P 5150 2750 +F 0 "C?" H 5265 2796 50 0000 L CNN +F 1 "18p" H 5265 2705 50 0000 L CNN +F 2 "" H 5188 2600 50 0001 C CNN +F 3 "~" H 5150 2750 50 0001 C CNN + 1 5150 2750 + 1 0 0 -1 +$EndComp +$Comp +L Device:C C? +U 1 1 5DDDEEAD +P 5250 2450 +F 0 "C?" H 5365 2496 50 0000 L CNN +F 1 "18p" H 5365 2405 50 0000 L CNN +F 2 "" H 5288 2300 50 0001 C CNN +F 3 "~" H 5250 2450 50 0001 C CNN + 1 5250 2450 + 1 0 0 -1 +$EndComp +Wire Wire Line + 5250 2600 5250 2900 +Wire Wire Line + 5250 2900 5150 2900 +Wire Wire Line + 5150 2900 5150 3000 +Connection ~ 5150 2900 +$Comp +L power:GND #PWR? +U 1 1 5DDDF660 +P 5150 3000 +F 0 "#PWR?" H 5150 2750 50 0001 C CNN +F 1 "GND" H 5155 2827 50 0000 C CNN +F 2 "" H 5150 3000 50 0001 C CNN +F 3 "" H 5150 3000 50 0001 C CNN + 1 5150 3000 + 1 0 0 -1 +$EndComp +Wire Wire Line + 4050 1750 4500 1750 +Wire Wire Line + 4050 1850 4500 1850 +Wire Wire Line + 4050 2650 4150 2650 +Wire Wire Line + 4050 2950 4150 2950 +Wire Wire Line + 4050 3050 4150 3050 +Wire Wire Line + 4050 3150 4150 3150 +Wire Wire Line + 4050 3650 4600 3650 +Wire Wire Line + 4050 3750 4600 3750 +Wire Wire Line + 4050 3850 4600 3850 +Wire Wire Line + 4050 3950 4600 3950 +Wire Wire Line + 4050 4050 4600 4050 +Wire Wire Line + 4050 4150 4600 4150 +Text Label 4600 3650 0 50 ~ 0 +DAC6 +Text Label 4600 3750 0 50 ~ 0 +DAC7 +Text Label 4600 3850 0 50 ~ 0 +DAC8 +Text Label 4600 3950 0 50 ~ 0 +DAC9 +Text Label 4600 4050 0 50 ~ 0 +DAC10 +Text Label 4600 4150 0 50 ~ 0 +DAC11 +Text Label 4500 1750 0 50 ~ 0 +DAC12 +Text Label 4500 1850 0 50 ~ 0 +DAC13 +Wire Wire Line + 3450 1450 3450 1300 +Wire Wire Line + 3450 4450 3450 4600 +$Comp +L power:+5V #PWR? +U 1 1 5DDEFED6 +P 3450 1300 +F 0 "#PWR?" H 3450 1150 50 0001 C CNN +F 1 "+5V" H 3465 1473 50 0000 C CNN +F 2 "" H 3450 1300 50 0001 C CNN +F 3 "" H 3450 1300 50 0001 C CNN + 1 3450 1300 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DDF0E43 +P 3450 4600 +F 0 "#PWR?" H 3450 4350 50 0001 C CNN +F 1 "GND" H 3455 4427 50 0000 C CNN +F 2 "" H 3450 4600 50 0001 C CNN +F 3 "" H 3450 4600 50 0001 C CNN + 1 3450 4600 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DDF122E +P 6650 3800 +F 0 "#PWR?" H 6650 3550 50 0001 C CNN +F 1 "GND" H 6655 3627 50 0000 C CNN +F 2 "" H 6650 3800 50 0001 C CNN +F 3 "" H 6650 3800 50 0001 C CNN + 1 6650 3800 + 1 0 0 -1 +$EndComp +NoConn ~ 4050 3250 +NoConn ~ 2850 1750 +NoConn ~ 3550 1450 +$Sheet +S 1800 5200 1550 1500 +U 5DDEEC27 +F0 "DAC" 50 +F1 "DAC.sch" 50 +F2 "DAC_OUT" I R 3350 5300 50 +F3 "DAC0" I L 1800 5300 50 +F4 "DAC1" I L 1800 5400 50 +F5 "DAC2" I L 1800 5500 50 +F6 "DAC3" I L 1800 5600 50 +F7 "DAC4" I L 1800 5700 50 +F8 "DAC5" I L 1800 5800 50 +F9 "DAC6" I L 1800 5900 50 +F10 "DAC7" I L 1800 6000 50 +F11 "DAC8" I L 1800 6100 50 +F12 "DAC9" I L 1800 6200 50 +F13 "DAC10" I L 1800 6300 50 +F14 "DAC11" I L 1800 6400 50 +F15 "DAC12" I L 1800 6500 50 +F16 "DAC13" I L 1800 6600 50 +$EndSheet +$Sheet +S 5450 5300 1450 900 +U 5DDEF065 +F0 "amp" 50 +F1 "amp.sch" 50 +F2 "DAC_out" I L 5450 5400 50 +$EndSheet +$Comp +L final:LCD U? +U 1 1 5DE2158A +P 9600 1500 +F 0 "U?" H 9850 200 50 0000 L CNN +F 1 "LCD" H 9800 1700 50 0000 L CNN +F 2 "" H 9600 1500 50 0001 C CNN +F 3 "" H 9600 1500 50 0001 C CNN + 1 9600 1500 + 1 0 0 -1 +$EndComp +Text Label 9350 2300 2 50 ~ 0 +LCD_D4 +Text Label 9350 2400 2 50 ~ 0 +LCD_D5 +Text Label 9350 2500 2 50 ~ 0 +LCD_D6 +Text Label 9350 2600 2 50 ~ 0 +LCD_D7 +Text Label 9350 1600 2 50 ~ 0 +LCD_RS +Text Label 9350 1700 2 50 ~ 0 +LCD_RW +$Comp +L power:GND #PWR? +U 1 1 5DE24EEB +P 8750 1500 +F 0 "#PWR?" H 8750 1250 50 0001 C CNN +F 1 "GND" H 8755 1327 50 0000 C CNN +F 2 "" H 8750 1500 50 0001 C CNN +F 3 "" H 8750 1500 50 0001 C CNN + 1 8750 1500 + 1 0 0 -1 +$EndComp +Wire Wire Line + 9350 1400 9200 1400 +Wire Wire Line + 9200 1400 9200 1000 +$Comp +L power:+5V #PWR? +U 1 1 5DE25CF2 +P 9200 1000 +F 0 "#PWR?" H 9200 850 50 0001 C CNN +F 1 "+5V" H 9215 1173 50 0000 C CNN +F 2 "" H 9200 1000 50 0001 C CNN +F 3 "" H 9200 1000 50 0001 C CNN + 1 9200 1000 + 1 0 0 -1 +$EndComp +Text Label 9350 1500 2 50 ~ 0 +LCD_CONTRAST +Text Label 9350 1800 2 50 ~ 0 +LCD_EN +NoConn ~ 9350 1900 +NoConn ~ 9350 2000 +NoConn ~ 9350 2100 +NoConn ~ 9350 2200 +$Comp +L power:GND #PWR? +U 1 1 5DE2FCEC +P 10350 1700 +F 0 "#PWR?" H 10350 1450 50 0001 C CNN +F 1 "GND" H 10355 1527 50 0000 C CNN +F 2 "" H 10350 1700 50 0001 C CNN +F 3 "" H 10350 1700 50 0001 C CNN + 1 10350 1700 + 1 0 0 -1 +$EndComp +$Comp +L power:+5V #PWR? +U 1 1 5DE2FA18 +P 10350 1000 +F 0 "#PWR?" H 10350 850 50 0001 C CNN +F 1 "+5V" H 10365 1173 50 0000 C CNN +F 2 "" H 10350 1000 50 0001 C CNN +F 3 "" H 10350 1000 50 0001 C CNN + 1 10350 1000 + 1 0 0 -1 +$EndComp +Text Label 10550 1350 0 50 ~ 0 +LCD_CONTRAST +Wire Wire Line + 10350 1350 10350 1400 +Connection ~ 10350 1350 +Wire Wire Line + 10350 1350 10550 1350 +Wire Wire Line + 10350 1300 10350 1350 +$Comp +L Device:R R? +U 1 1 5DE2D84F +P 10350 1550 +F 0 "R?" H 10420 1596 50 0000 L CNN +F 1 "R" H 10420 1505 50 0000 L CNN +F 2 "" V 10280 1550 50 0001 C CNN +F 3 "~" H 10350 1550 50 0001 C CNN + 1 10350 1550 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE2D413 +P 10350 1150 +F 0 "R?" H 10420 1196 50 0000 L CNN +F 1 "R" H 10420 1105 50 0000 L CNN +F 2 "" V 10280 1150 50 0001 C CNN +F 3 "~" H 10350 1150 50 0001 C CNN + 1 10350 1150 + 1 0 0 -1 +$EndComp +Wire Wire Line + 8750 1300 8750 1500 +Wire Wire Line + 9350 1300 8750 1300 +Wire Notes Line + 8650 750 11150 750 +Wire Notes Line + 11150 750 11150 2900 +Wire Notes Line + 11150 2900 8650 2900 +Wire Notes Line + 8650 2900 8650 750 +Text Notes 11000 2900 0 50 ~ 0 +LCD\n +$Comp +L Connector:SD_Card J? +U 1 1 5DE47B4E +P 9850 4650 +F 0 "J?" H 9850 5315 50 0000 C CNN +F 1 "SD_Card" H 9850 5224 50 0000 C CNN +F 2 "" H 9850 4650 50 0001 C CNN +F 3 "http://portal.fciconnect.com/Comergent//fci/drawing/10067847.pdf" H 9850 4650 50 0001 C CNN + 1 9850 4650 + 1 0 0 -1 +$EndComp +NoConn ~ 10750 4450 +NoConn ~ 10750 4550 +NoConn ~ 10750 4750 +NoConn ~ 10750 4850 +NoConn ~ 8900 5050 +NoConn ~ 8950 4250 +Wire Wire Line + 8950 4550 8800 4550 +Wire Wire Line + 8800 4550 8800 4850 +$Comp +L power:GND #PWR? +U 1 1 5DE4FECC +P 8800 5300 +F 0 "#PWR?" H 8800 5050 50 0001 C CNN +F 1 "GND" H 8805 5127 50 0000 C CNN +F 2 "" H 8800 5300 50 0001 C CNN +F 3 "" H 8800 5300 50 0001 C CNN + 1 8800 5300 + 1 0 0 -1 +$EndComp +Wire Wire Line + 8950 4850 8800 4850 +Connection ~ 8800 4850 +Wire Wire Line + 8800 4850 8800 5300 +Wire Wire Line + 8950 4650 8750 4650 +Wire Wire Line + 8750 4650 8750 4000 +Wire Wire Line + 8950 4950 8650 4950 +Text Label 8650 4950 2 50 ~ 0 +MISO +Wire Wire Line + 8950 4750 8650 4750 +Text Label 8650 4750 2 50 ~ 0 +CLK +Wire Wire Line + 8950 4450 8650 4450 +Text Label 8650 4450 2 50 ~ 0 +CMD +Wire Wire Line + 8950 4350 8650 4350 +Text Label 8650 4350 2 50 ~ 0 +CD +$Comp +L power:+3V3 #PWR? +U 1 1 5DE57C4F +P 8750 4000 +F 0 "#PWR?" H 8750 3850 50 0001 C CNN +F 1 "+3V3" H 8765 4173 50 0000 C CNN +F 2 "" H 8750 4000 50 0001 C CNN +F 3 "" H 8750 4000 50 0001 C CNN + 1 8750 4000 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE5821B +P 7400 5500 +F 0 "R?" H 7470 5546 50 0000 L CNN +F 1 "R" H 7470 5455 50 0000 L CNN +F 2 "" V 7330 5500 50 0001 C CNN +F 3 "~" H 7400 5500 50 0001 C CNN + 1 7400 5500 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE589C1 +P 7400 5800 +F 0 "R?" H 7470 5846 50 0000 L CNN +F 1 "R" H 7470 5755 50 0000 L CNN +F 2 "" V 7330 5800 50 0001 C CNN +F 3 "~" H 7400 5800 50 0001 C CNN + 1 7400 5800 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE58C5B +P 7900 5500 +F 0 "R?" H 7970 5546 50 0000 L CNN +F 1 "R" H 7970 5455 50 0000 L CNN +F 2 "" V 7830 5500 50 0001 C CNN +F 3 "~" H 7900 5500 50 0001 C CNN + 1 7900 5500 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE58FA3 +P 7900 5800 +F 0 "R?" H 7970 5846 50 0000 L CNN +F 1 "R" H 7970 5755 50 0000 L CNN +F 2 "" V 7830 5800 50 0001 C CNN +F 3 "~" H 7900 5800 50 0001 C CNN + 1 7900 5800 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE5926A +P 8300 5500 +F 0 "R?" H 8370 5546 50 0000 L CNN +F 1 "R" H 8370 5455 50 0000 L CNN +F 2 "" V 8230 5500 50 0001 C CNN +F 3 "~" H 8300 5500 50 0001 C CNN + 1 8300 5500 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE59509 +P 8300 5800 +F 0 "R?" H 8370 5846 50 0000 L CNN +F 1 "R" H 8370 5755 50 0000 L CNN +F 2 "" V 8230 5800 50 0001 C CNN +F 3 "~" H 8300 5800 50 0001 C CNN + 1 8300 5800 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7400 5350 7400 5250 +Wire Wire Line + 7400 5250 7300 5250 +Text Label 7300 5250 2 50 ~ 0 +MOSI +Wire Wire Line + 7900 5350 7900 5250 +Wire Wire Line + 7900 5250 7750 5250 +Text Label 7750 5250 2 50 ~ 0 +SCK +Wire Wire Line + 8300 5350 8300 5250 +Wire Wire Line + 8300 5250 8200 5250 +Text Label 8200 5250 2 50 ~ 0 +CS +Wire Wire Line + 7400 5650 7500 5650 +Connection ~ 7400 5650 +Wire Wire Line + 7900 5650 8000 5650 +Connection ~ 7900 5650 +Wire Wire Line + 8300 5650 8400 5650 +Connection ~ 8300 5650 +$Comp +L power:GND #PWR? +U 1 1 5DE60F8B +P 8300 5950 +F 0 "#PWR?" H 8300 5700 50 0001 C CNN +F 1 "GND" H 8305 5777 50 0000 C CNN +F 2 "" H 8300 5950 50 0001 C CNN +F 3 "" H 8300 5950 50 0001 C CNN + 1 8300 5950 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE6121F +P 7900 5950 +F 0 "#PWR?" H 7900 5700 50 0001 C CNN +F 1 "GND" H 7905 5777 50 0000 C CNN +F 2 "" H 7900 5950 50 0001 C CNN +F 3 "" H 7900 5950 50 0001 C CNN + 1 7900 5950 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE61424 +P 7400 5950 +F 0 "#PWR?" H 7400 5700 50 0001 C CNN +F 1 "GND" H 7405 5777 50 0000 C CNN +F 2 "" H 7400 5950 50 0001 C CNN +F 3 "" H 7400 5950 50 0001 C CNN + 1 7400 5950 + 1 0 0 -1 +$EndComp +Text Label 8000 5650 0 50 ~ 0 +CLK +Text Label 7500 5650 0 50 ~ 0 +CMD +Text Label 8400 5650 0 50 ~ 0 +CD +Text Notes 7650 6350 0 50 ~ 0 +Level Shifting\n +Wire Wire Line + 5150 5300 5150 5400 +Wire Wire Line + 5150 5400 5450 5400 +Wire Wire Line + 3350 5300 5150 5300 +Wire Wire Line + 4050 2750 4150 2750 +Connection ~ 4650 2600 +Wire Wire Line + 4050 2350 4650 2350 +Text Label 4150 3150 0 50 ~ 0 +DAC5 +Text Label 4150 3050 0 50 ~ 0 +DAC4 +Text Label 4150 2950 0 50 ~ 0 +DAC3 +Text Label 4200 2850 0 50 ~ 0 +DAC2 +Text Label 4150 2750 0 50 ~ 0 +DAC1 +Text Label 4150 2650 0 50 ~ 0 +DAC0 +Wire Wire Line + 4050 2850 4200 2850 +Wire Wire Line + 4300 2600 4650 2600 +Wire Wire Line + 4300 2450 4300 2600 +Text Label 1800 5800 2 50 ~ 0 +DAC5 +Text Label 1800 5700 2 50 ~ 0 +DAC4 +Text Label 1800 5600 2 50 ~ 0 +DAC3 +Text Label 1800 5500 2 50 ~ 0 +DAC2 +Text Label 1800 5400 2 50 ~ 0 +DAC1 +Text Label 1800 5300 2 50 ~ 0 +DAC0 +Text Label 1800 5900 2 50 ~ 0 +DAC6 +Text Label 1800 6000 2 50 ~ 0 +DAC7 +Text Label 1800 6100 2 50 ~ 0 +DAC8 +Text Label 1800 6200 2 50 ~ 0 +DAC9 +Text Label 1800 6300 2 50 ~ 0 +DAC10 +Text Label 1800 6400 2 50 ~ 0 +DAC11 +Text Label 1800 6500 2 50 ~ 0 +DAC12 +Text Label 1800 6600 2 50 ~ 0 +DAC13 +$Comp +L Switch:SW_Push SW? +U 1 1 5DE938F9 +P 950 1400 +F 0 "SW?" V 904 1548 50 0000 L CNN +F 1 "SW_Push" V 995 1548 50 0000 L CNN +F 2 "" H 950 1600 50 0001 C CNN +F 3 "~" H 950 1600 50 0001 C CNN + 1 950 1400 + 0 1 1 0 +$EndComp +$Comp +L Switch:SW_Push SW? +U 1 1 5DE95103 +P 950 2150 +F 0 "SW?" V 904 2298 50 0000 L CNN +F 1 "SW_Push" V 995 2298 50 0000 L CNN +F 2 "" H 950 2350 50 0001 C CNN +F 3 "~" H 950 2350 50 0001 C CNN + 1 950 2150 + 0 1 1 0 +$EndComp +$Comp +L Switch:SW_Push SW? +U 1 1 5DE95459 +P 950 2850 +F 0 "SW?" V 904 2998 50 0000 L CNN +F 1 "SW_Push" V 995 2998 50 0000 L CNN +F 2 "" H 950 3050 50 0001 C CNN +F 3 "~" H 950 3050 50 0001 C CNN + 1 950 2850 + 0 1 1 0 +$EndComp +Text Label 950 1200 2 50 ~ 0 +BUT0 +Text Label 950 1950 2 50 ~ 0 +BUT1 +Text Label 950 2650 2 50 ~ 0 +BUT2 +$Comp +L power:GND #PWR? +U 1 1 5DE97876 +P 950 1600 +F 0 "#PWR?" H 950 1350 50 0001 C CNN +F 1 "GND" H 955 1427 50 0000 C CNN +F 2 "" H 950 1600 50 0001 C CNN +F 3 "" H 950 1600 50 0001 C CNN + 1 950 1600 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE97EED +P 950 2350 +F 0 "#PWR?" H 950 2100 50 0001 C CNN +F 1 "GND" H 955 2177 50 0000 C CNN +F 2 "" H 950 2350 50 0001 C CNN +F 3 "" H 950 2350 50 0001 C CNN + 1 950 2350 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE98112 +P 950 3050 +F 0 "#PWR?" H 950 2800 50 0001 C CNN +F 1 "GND" H 955 2877 50 0000 C CNN +F 2 "" H 950 3050 50 0001 C CNN +F 3 "" H 950 3050 50 0001 C CNN + 1 950 3050 + 1 0 0 -1 +$EndComp +$Comp +L Regulator_Linear:AP2112K-3.3 U? +U 1 1 5DE99466 +P 7600 4300 +F 0 "U?" H 7600 4642 50 0000 C CNN +F 1 "AP2112K-3.3" H 7600 4551 50 0000 C CNN +F 2 "Package_TO_SOT_SMD:SOT-23-5" H 7600 4625 50 0001 C CNN +F 3 "https://www.diodes.com/assets/Datasheets/AP2112.pdf" H 7600 4400 50 0001 C CNN + 1 7600 4300 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7300 4200 7200 4200 +Wire Wire Line + 7200 4200 7200 4100 +$Comp +L power:+5V #PWR? +U 1 1 5DEA3406 +P 7200 4100 +F 0 "#PWR?" H 7200 3950 50 0001 C CNN +F 1 "+5V" H 7215 4273 50 0000 C CNN +F 2 "" H 7200 4100 50 0001 C CNN +F 3 "" H 7200 4100 50 0001 C CNN + 1 7200 4100 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7300 4300 7200 4300 +Wire Wire Line + 7200 4300 7200 4200 +Connection ~ 7200 4200 +$Comp +L power:GND #PWR? +U 1 1 5DEA545A +P 7600 4600 +F 0 "#PWR?" H 7600 4350 50 0001 C CNN +F 1 "GND" H 7605 4427 50 0000 C CNN +F 2 "" H 7600 4600 50 0001 C CNN +F 3 "" H 7600 4600 50 0001 C CNN + 1 7600 4600 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7900 4200 8050 4200 +Wire Wire Line + 8050 4200 8050 4100 +$Comp +L power:+3V3 #PWR? +U 1 1 5DEA72CB +P 8050 4100 +F 0 "#PWR?" H 8050 3950 50 0001 C CNN +F 1 "+3V3" H 8065 4273 50 0000 C CNN +F 2 "" H 8050 4100 50 0001 C CNN +F 3 "" H 8050 4100 50 0001 C CNN + 1 8050 4100 + 1 0 0 -1 +$EndComp +NoConn ~ 6050 1950 +NoConn ~ 6050 2150 +NoConn ~ 6050 1750 +Text Label 7250 1750 0 50 ~ 0 +LCD_RW +Text Label 7250 3250 0 50 ~ 0 +LCD_RS +Text Label 7250 2350 0 50 ~ 0 +LCD_D6 +Text Label 7250 2450 0 50 ~ 0 +LCD_D7 +Text Label 7250 2250 0 50 ~ 0 +LCD_D5 +Text Label 7250 2150 0 50 ~ 0 +LCD_D4 +$Comp +L power:+5V #PWR? +U 1 1 5DDF0261 +P 6650 1150 +F 0 "#PWR?" H 6650 1000 50 0001 C CNN +F 1 "+5V" H 6665 1323 50 0000 C CNN +F 2 "" H 6650 1150 50 0001 C CNN +F 3 "" H 6650 1150 50 0001 C CNN + 1 6650 1150 + 1 0 0 -1 +$EndComp +Wire Wire Line + 6650 3650 6650 3800 +Wire Wire Line + 6650 1450 6650 1150 +Text Label 7450 2750 0 50 ~ 0 +RXD +Text Label 7450 2650 0 50 ~ 0 +TXD +Wire Wire Line + 7250 2750 7450 2750 +Wire Wire Line + 7250 2650 7450 2650 +$Comp +L final_project-rescue:ATtiny2313-20MU-MCU_Microchip_ATtiny U? +U 1 1 5DDDA525 +P 6650 2550 +F 0 "U?" H 6650 3831 50 0000 C CNN +F 1 "ATtiny2313-20MU" H 6650 3740 50 0000 C CNN +F 2 "Package_DFN_QFN:MLF-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm" H 6650 2550 50 0001 C CIN +F 3 "http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2543-AVR-ATtiny2313_Datasheet.pdf" H 6650 2550 50 0001 C CNN + 1 6650 2550 + 1 0 0 -1 +$EndComp +Text Label 7250 1950 0 50 ~ 0 +BUT_RED +Text Label 6050 2150 2 50 ~ 0 +BUT_RIGHT +Text Label 6050 1950 2 50 ~ 0 +BUT_LEFT +Text Label 7250 1850 0 50 ~ 0 +E +$EndSCHEMATC diff --git a/hw/final_project.sch-bak b/hw/final_project.sch-bak new file mode 100644 index 0000000..c325ca8 --- /dev/null +++ b/hw/final_project.sch-bak @@ -0,0 +1,787 @@ +EESchema Schematic File Version 4 +LIBS:final_project-cache +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 1 3 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +$Comp +L MCU_Microchip_ATmega:ATmega328-PU U? +U 1 1 5DDD9932 +P 3450 2950 +F 0 "U?" H 2806 2996 50 0000 R CNN +F 1 "ATmega328-PU" H 2806 2905 50 0000 R CNN +F 2 "Package_DIP:DIP-28_W7.62mm" H 3450 2950 50 0001 C CIN +F 3 "http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega328_P%20AVR%20MCU%20with%20picoPower%20Technology%20Data%20Sheet%2040001984A.pdf" H 3450 2950 50 0001 C CNN + 1 3450 2950 + 1 0 0 -1 +$EndComp +$Comp +L MCU_Microchip_ATtiny:ATtiny2313-20MU U? +U 1 1 5DDDA525 +P 6650 2550 +F 0 "U?" H 6650 3831 50 0000 C CNN +F 1 "ATtiny2313-20MU" H 6650 3740 50 0000 C CNN +F 2 "Package_DFN_QFN:MLF-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm" H 6650 2550 50 0001 C CIN +F 3 "http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2543-AVR-ATtiny2313_Datasheet.pdf" H 6650 2550 50 0001 C CNN + 1 6650 2550 + 1 0 0 -1 +$EndComp +Wire Wire Line + 4050 2250 4250 2250 +Wire Wire Line + 4050 2150 4250 2150 +Wire Wire Line + 4050 2050 4250 2050 +Wire Wire Line + 4050 1950 4250 1950 +Text Label 4250 2250 0 50 ~ 0 +SCK +Text Label 4250 2150 0 50 ~ 0 +MOSI +Text Label 4250 2050 0 50 ~ 0 +MISO +Text Label 4250 1950 0 50 ~ 0 +CS +Wire Wire Line + 4050 3450 4250 3450 +Wire Wire Line + 4050 3550 4250 3550 +Text Label 4250 3450 0 50 ~ 0 +RXD +Text Label 4250 3550 0 50 ~ 0 +TXD +$Comp +L Device:Crystal Y? +U 1 1 5DDDDF12 +P 4650 2450 +F 0 "Y?" V 4604 2581 50 0000 L CNN +F 1 "16MHz" V 4695 2581 50 0000 L CNN +F 2 "" H 4650 2450 50 0001 C CNN +F 3 "~" H 4650 2450 50 0001 C CNN + 1 4650 2450 + 0 1 1 0 +$EndComp +Wire Wire Line + 4650 2350 4650 2300 +Wire Wire Line + 4050 2450 4300 2450 +Wire Wire Line + 4650 2300 5250 2300 +Connection ~ 4650 2300 +Wire Wire Line + 4650 2600 5150 2600 +$Comp +L Device:C C? +U 1 1 5DDDEABA +P 5150 2750 +F 0 "C?" H 5265 2796 50 0000 L CNN +F 1 "18p" H 5265 2705 50 0000 L CNN +F 2 "" H 5188 2600 50 0001 C CNN +F 3 "~" H 5150 2750 50 0001 C CNN + 1 5150 2750 + 1 0 0 -1 +$EndComp +$Comp +L Device:C C? +U 1 1 5DDDEEAD +P 5250 2450 +F 0 "C?" H 5365 2496 50 0000 L CNN +F 1 "18p" H 5365 2405 50 0000 L CNN +F 2 "" H 5288 2300 50 0001 C CNN +F 3 "~" H 5250 2450 50 0001 C CNN + 1 5250 2450 + 1 0 0 -1 +$EndComp +Wire Wire Line + 5250 2600 5250 2900 +Wire Wire Line + 5250 2900 5150 2900 +Wire Wire Line + 5150 2900 5150 3000 +Connection ~ 5150 2900 +$Comp +L power:GND #PWR? +U 1 1 5DDDF660 +P 5150 3000 +F 0 "#PWR?" H 5150 2750 50 0001 C CNN +F 1 "GND" H 5155 2827 50 0000 C CNN +F 2 "" H 5150 3000 50 0001 C CNN +F 3 "" H 5150 3000 50 0001 C CNN + 1 5150 3000 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7250 2650 7450 2650 +Wire Wire Line + 7250 2750 7450 2750 +Text Label 7450 2650 0 50 ~ 0 +TXD +Text Label 7450 2750 0 50 ~ 0 +RXD +Wire Wire Line + 4050 1750 4500 1750 +Wire Wire Line + 4050 1850 4500 1850 +Wire Wire Line + 4050 2650 4150 2650 +Wire Wire Line + 4050 2950 4150 2950 +Wire Wire Line + 4050 3050 4150 3050 +Wire Wire Line + 4050 3150 4150 3150 +Wire Wire Line + 4050 3650 4600 3650 +Wire Wire Line + 4050 3750 4600 3750 +Wire Wire Line + 4050 3850 4600 3850 +Wire Wire Line + 4050 3950 4600 3950 +Wire Wire Line + 4050 4050 4600 4050 +Wire Wire Line + 4050 4150 4600 4150 +Text Label 4600 3650 0 50 ~ 0 +DAC6 +Text Label 4600 3750 0 50 ~ 0 +DAC7 +Text Label 4600 3850 0 50 ~ 0 +DAC8 +Text Label 4600 3950 0 50 ~ 0 +DAC9 +Text Label 4600 4050 0 50 ~ 0 +DAC10 +Text Label 4600 4150 0 50 ~ 0 +DAC11 +Text Label 4500 1750 0 50 ~ 0 +DAC12 +Text Label 4500 1850 0 50 ~ 0 +DAC13 +Wire Wire Line + 3450 1450 3450 1300 +Wire Wire Line + 3450 4450 3450 4600 +Wire Wire Line + 6650 1450 6650 1150 +Wire Wire Line + 6650 3650 6650 3800 +$Comp +L power:+5V #PWR? +U 1 1 5DDEFED6 +P 3450 1300 +F 0 "#PWR?" H 3450 1150 50 0001 C CNN +F 1 "+5V" H 3465 1473 50 0000 C CNN +F 2 "" H 3450 1300 50 0001 C CNN +F 3 "" H 3450 1300 50 0001 C CNN + 1 3450 1300 + 1 0 0 -1 +$EndComp +$Comp +L power:+5V #PWR? +U 1 1 5DDF0261 +P 6650 1150 +F 0 "#PWR?" H 6650 1000 50 0001 C CNN +F 1 "+5V" H 6665 1323 50 0000 C CNN +F 2 "" H 6650 1150 50 0001 C CNN +F 3 "" H 6650 1150 50 0001 C CNN + 1 6650 1150 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DDF0E43 +P 3450 4600 +F 0 "#PWR?" H 3450 4350 50 0001 C CNN +F 1 "GND" H 3455 4427 50 0000 C CNN +F 2 "" H 3450 4600 50 0001 C CNN +F 3 "" H 3450 4600 50 0001 C CNN + 1 3450 4600 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DDF122E +P 6650 3800 +F 0 "#PWR?" H 6650 3550 50 0001 C CNN +F 1 "GND" H 6655 3627 50 0000 C CNN +F 2 "" H 6650 3800 50 0001 C CNN +F 3 "" H 6650 3800 50 0001 C CNN + 1 6650 3800 + 1 0 0 -1 +$EndComp +Text Label 7250 1750 0 50 ~ 0 +LCD_D4 +Text Label 7250 1850 0 50 ~ 0 +LCD_D5 +Text Label 7250 2050 0 50 ~ 0 +LCD_D7 +Text Label 7250 1950 0 50 ~ 0 +LCD_D6 +Text Label 7250 2150 0 50 ~ 0 +LCD_RS +Text Label 7250 2250 0 50 ~ 0 +LCD_RW +Text Label 7250 3050 0 50 ~ 0 +BUT1 +Text Label 7250 2950 0 50 ~ 0 +BUT2 +Text Label 7250 3150 0 50 ~ 0 +BUT0 +NoConn ~ 6050 1750 +NoConn ~ 6050 2150 +NoConn ~ 6050 1950 +NoConn ~ 4050 3250 +NoConn ~ 2850 1750 +NoConn ~ 3550 1450 +$Sheet +S 1800 5200 1550 1500 +U 5DDEEC27 +F0 "DAC" 50 +F1 "DAC.sch" 50 +F2 "DAC_OUT" I R 3350 5300 50 +F3 "DAC0" I L 1800 5300 50 +F4 "DAC1" I L 1800 5400 50 +F5 "DAC2" I L 1800 5500 50 +F6 "DAC3" I L 1800 5600 50 +F7 "DAC4" I L 1800 5700 50 +F8 "DAC5" I L 1800 5800 50 +F9 "DAC6" I L 1800 5900 50 +F10 "DAC7" I L 1800 6000 50 +F11 "DAC8" I L 1800 6100 50 +F12 "DAC9" I L 1800 6200 50 +F13 "DAC10" I L 1800 6300 50 +F14 "DAC11" I L 1800 6400 50 +F15 "DAC12" I L 1800 6500 50 +F16 "DAC13" I L 1800 6600 50 +$EndSheet +$Sheet +S 5450 5300 1450 900 +U 5DDEF065 +F0 "amp" 50 +F1 "amp.sch" 50 +F2 "DAC_out" I L 5450 5400 50 +$EndSheet +$Comp +L final:LCD U? +U 1 1 5DE2158A +P 9600 1500 +F 0 "U?" H 9850 200 50 0000 L CNN +F 1 "LCD" H 9800 1700 50 0000 L CNN +F 2 "" H 9600 1500 50 0001 C CNN +F 3 "" H 9600 1500 50 0001 C CNN + 1 9600 1500 + 1 0 0 -1 +$EndComp +Text Label 9350 2300 2 50 ~ 0 +LCD_D4 +Text Label 9350 2400 2 50 ~ 0 +LCD_D5 +Text Label 9350 2500 2 50 ~ 0 +LCD_D6 +Text Label 9350 2600 2 50 ~ 0 +LCD_D7 +Text Label 9350 1600 2 50 ~ 0 +LCD_RS +Text Label 9350 1700 2 50 ~ 0 +LCD_RW +$Comp +L power:GND #PWR? +U 1 1 5DE24EEB +P 8750 1500 +F 0 "#PWR?" H 8750 1250 50 0001 C CNN +F 1 "GND" H 8755 1327 50 0000 C CNN +F 2 "" H 8750 1500 50 0001 C CNN +F 3 "" H 8750 1500 50 0001 C CNN + 1 8750 1500 + 1 0 0 -1 +$EndComp +Wire Wire Line + 9350 1400 9200 1400 +Wire Wire Line + 9200 1400 9200 1000 +$Comp +L power:+5V #PWR? +U 1 1 5DE25CF2 +P 9200 1000 +F 0 "#PWR?" H 9200 850 50 0001 C CNN +F 1 "+5V" H 9215 1173 50 0000 C CNN +F 2 "" H 9200 1000 50 0001 C CNN +F 3 "" H 9200 1000 50 0001 C CNN + 1 9200 1000 + 1 0 0 -1 +$EndComp +Text Label 9350 1500 2 50 ~ 0 +LCD_CONTRAST +Text Label 9350 1800 2 50 ~ 0 +LCD_EN +NoConn ~ 9350 1900 +NoConn ~ 9350 2000 +NoConn ~ 9350 2100 +NoConn ~ 9350 2200 +$Comp +L power:GND #PWR? +U 1 1 5DE2FCEC +P 10350 1700 +F 0 "#PWR?" H 10350 1450 50 0001 C CNN +F 1 "GND" H 10355 1527 50 0000 C CNN +F 2 "" H 10350 1700 50 0001 C CNN +F 3 "" H 10350 1700 50 0001 C CNN + 1 10350 1700 + 1 0 0 -1 +$EndComp +$Comp +L power:+5V #PWR? +U 1 1 5DE2FA18 +P 10350 1000 +F 0 "#PWR?" H 10350 850 50 0001 C CNN +F 1 "+5V" H 10365 1173 50 0000 C CNN +F 2 "" H 10350 1000 50 0001 C CNN +F 3 "" H 10350 1000 50 0001 C CNN + 1 10350 1000 + 1 0 0 -1 +$EndComp +Text Label 10550 1350 0 50 ~ 0 +LCD_CONTRAST +Wire Wire Line + 10350 1350 10350 1400 +Connection ~ 10350 1350 +Wire Wire Line + 10350 1350 10550 1350 +Wire Wire Line + 10350 1300 10350 1350 +$Comp +L Device:R R? +U 1 1 5DE2D84F +P 10350 1550 +F 0 "R?" H 10420 1596 50 0000 L CNN +F 1 "R" H 10420 1505 50 0000 L CNN +F 2 "" V 10280 1550 50 0001 C CNN +F 3 "~" H 10350 1550 50 0001 C CNN + 1 10350 1550 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE2D413 +P 10350 1150 +F 0 "R?" H 10420 1196 50 0000 L CNN +F 1 "R" H 10420 1105 50 0000 L CNN +F 2 "" V 10280 1150 50 0001 C CNN +F 3 "~" H 10350 1150 50 0001 C CNN + 1 10350 1150 + 1 0 0 -1 +$EndComp +Wire Wire Line + 8750 1300 8750 1500 +Wire Wire Line + 9350 1300 8750 1300 +Wire Notes Line + 8650 750 11150 750 +Wire Notes Line + 11150 750 11150 2900 +Wire Notes Line + 11150 2900 8650 2900 +Wire Notes Line + 8650 2900 8650 750 +Text Notes 11000 2900 0 50 ~ 0 +LCD\n +$Comp +L Connector:SD_Card J? +U 1 1 5DE47B4E +P 9850 4650 +F 0 "J?" H 9850 5315 50 0000 C CNN +F 1 "SD_Card" H 9850 5224 50 0000 C CNN +F 2 "" H 9850 4650 50 0001 C CNN +F 3 "http://portal.fciconnect.com/Comergent//fci/drawing/10067847.pdf" H 9850 4650 50 0001 C CNN + 1 9850 4650 + 1 0 0 -1 +$EndComp +NoConn ~ 10750 4450 +NoConn ~ 10750 4550 +NoConn ~ 10750 4750 +NoConn ~ 10750 4850 +NoConn ~ 8900 5050 +NoConn ~ 8950 4250 +Wire Wire Line + 8950 4550 8800 4550 +Wire Wire Line + 8800 4550 8800 4850 +$Comp +L power:GND #PWR? +U 1 1 5DE4FECC +P 8800 5300 +F 0 "#PWR?" H 8800 5050 50 0001 C CNN +F 1 "GND" H 8805 5127 50 0000 C CNN +F 2 "" H 8800 5300 50 0001 C CNN +F 3 "" H 8800 5300 50 0001 C CNN + 1 8800 5300 + 1 0 0 -1 +$EndComp +Wire Wire Line + 8950 4850 8800 4850 +Connection ~ 8800 4850 +Wire Wire Line + 8800 4850 8800 5300 +Wire Wire Line + 8950 4650 8750 4650 +Wire Wire Line + 8750 4650 8750 4000 +Wire Wire Line + 8950 4950 8650 4950 +Text Label 8650 4950 2 50 ~ 0 +MISO +Wire Wire Line + 8950 4750 8650 4750 +Text Label 8650 4750 2 50 ~ 0 +CLK +Wire Wire Line + 8950 4450 8650 4450 +Text Label 8650 4450 2 50 ~ 0 +CMD +Wire Wire Line + 8950 4350 8650 4350 +Text Label 8650 4350 2 50 ~ 0 +CD +$Comp +L power:+3V3 #PWR? +U 1 1 5DE57C4F +P 8750 4000 +F 0 "#PWR?" H 8750 3850 50 0001 C CNN +F 1 "+3V3" H 8765 4173 50 0000 C CNN +F 2 "" H 8750 4000 50 0001 C CNN +F 3 "" H 8750 4000 50 0001 C CNN + 1 8750 4000 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE5821B +P 7400 5500 +F 0 "R?" H 7470 5546 50 0000 L CNN +F 1 "R" H 7470 5455 50 0000 L CNN +F 2 "" V 7330 5500 50 0001 C CNN +F 3 "~" H 7400 5500 50 0001 C CNN + 1 7400 5500 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE589C1 +P 7400 5800 +F 0 "R?" H 7470 5846 50 0000 L CNN +F 1 "R" H 7470 5755 50 0000 L CNN +F 2 "" V 7330 5800 50 0001 C CNN +F 3 "~" H 7400 5800 50 0001 C CNN + 1 7400 5800 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE58C5B +P 7900 5500 +F 0 "R?" H 7970 5546 50 0000 L CNN +F 1 "R" H 7970 5455 50 0000 L CNN +F 2 "" V 7830 5500 50 0001 C CNN +F 3 "~" H 7900 5500 50 0001 C CNN + 1 7900 5500 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE58FA3 +P 7900 5800 +F 0 "R?" H 7970 5846 50 0000 L CNN +F 1 "R" H 7970 5755 50 0000 L CNN +F 2 "" V 7830 5800 50 0001 C CNN +F 3 "~" H 7900 5800 50 0001 C CNN + 1 7900 5800 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE5926A +P 8300 5500 +F 0 "R?" H 8370 5546 50 0000 L CNN +F 1 "R" H 8370 5455 50 0000 L CNN +F 2 "" V 8230 5500 50 0001 C CNN +F 3 "~" H 8300 5500 50 0001 C CNN + 1 8300 5500 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R? +U 1 1 5DE59509 +P 8300 5800 +F 0 "R?" H 8370 5846 50 0000 L CNN +F 1 "R" H 8370 5755 50 0000 L CNN +F 2 "" V 8230 5800 50 0001 C CNN +F 3 "~" H 8300 5800 50 0001 C CNN + 1 8300 5800 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7400 5350 7400 5250 +Wire Wire Line + 7400 5250 7300 5250 +Text Label 7300 5250 2 50 ~ 0 +MOSI +Wire Wire Line + 7900 5350 7900 5250 +Wire Wire Line + 7900 5250 7750 5250 +Text Label 7750 5250 2 50 ~ 0 +SCK +Wire Wire Line + 8300 5350 8300 5250 +Wire Wire Line + 8300 5250 8200 5250 +Text Label 8200 5250 2 50 ~ 0 +CS +Wire Wire Line + 7400 5650 7500 5650 +Connection ~ 7400 5650 +Wire Wire Line + 7900 5650 8000 5650 +Connection ~ 7900 5650 +Wire Wire Line + 8300 5650 8400 5650 +Connection ~ 8300 5650 +$Comp +L power:GND #PWR? +U 1 1 5DE60F8B +P 8300 5950 +F 0 "#PWR?" H 8300 5700 50 0001 C CNN +F 1 "GND" H 8305 5777 50 0000 C CNN +F 2 "" H 8300 5950 50 0001 C CNN +F 3 "" H 8300 5950 50 0001 C CNN + 1 8300 5950 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE6121F +P 7900 5950 +F 0 "#PWR?" H 7900 5700 50 0001 C CNN +F 1 "GND" H 7905 5777 50 0000 C CNN +F 2 "" H 7900 5950 50 0001 C CNN +F 3 "" H 7900 5950 50 0001 C CNN + 1 7900 5950 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE61424 +P 7400 5950 +F 0 "#PWR?" H 7400 5700 50 0001 C CNN +F 1 "GND" H 7405 5777 50 0000 C CNN +F 2 "" H 7400 5950 50 0001 C CNN +F 3 "" H 7400 5950 50 0001 C CNN + 1 7400 5950 + 1 0 0 -1 +$EndComp +Text Label 8000 5650 0 50 ~ 0 +CLK +Text Label 7500 5650 0 50 ~ 0 +CMD +Text Label 8400 5650 0 50 ~ 0 +CD +Text Notes 7650 4900 0 50 ~ 0 +Level Shifting\n +Wire Wire Line + 5150 5300 5150 5400 +Wire Wire Line + 5150 5400 5450 5400 +Wire Wire Line + 3350 5300 5150 5300 +Wire Wire Line + 4050 2750 4150 2750 +Connection ~ 4650 2600 +Wire Wire Line + 4050 2350 4650 2350 +Text Label 4150 3150 0 50 ~ 0 +DAC5 +Text Label 4150 3050 0 50 ~ 0 +DAC4 +Text Label 4150 2950 0 50 ~ 0 +DAC3 +Text Label 4200 2850 0 50 ~ 0 +DAC2 +Text Label 4150 2750 0 50 ~ 0 +DAC1 +Text Label 4150 2650 0 50 ~ 0 +DAC0 +Wire Wire Line + 4050 2850 4200 2850 +Wire Wire Line + 4300 2600 4650 2600 +Wire Wire Line + 4300 2450 4300 2600 +Text Label 1800 5800 2 50 ~ 0 +DAC5 +Text Label 1800 5700 2 50 ~ 0 +DAC4 +Text Label 1800 5600 2 50 ~ 0 +DAC3 +Text Label 1800 5500 2 50 ~ 0 +DAC2 +Text Label 1800 5400 2 50 ~ 0 +DAC1 +Text Label 1800 5300 2 50 ~ 0 +DAC0 +Text Label 1800 5900 2 50 ~ 0 +DAC6 +Text Label 1800 6000 2 50 ~ 0 +DAC7 +Text Label 1800 6100 2 50 ~ 0 +DAC8 +Text Label 1800 6200 2 50 ~ 0 +DAC9 +Text Label 1800 6300 2 50 ~ 0 +DAC10 +Text Label 1800 6400 2 50 ~ 0 +DAC11 +Text Label 1800 6500 2 50 ~ 0 +DAC12 +Text Label 1800 6600 2 50 ~ 0 +DAC13 +$Comp +L Switch:SW_Push SW? +U 1 1 5DE938F9 +P 950 1400 +F 0 "SW?" V 904 1548 50 0000 L CNN +F 1 "SW_Push" V 995 1548 50 0000 L CNN +F 2 "" H 950 1600 50 0001 C CNN +F 3 "~" H 950 1600 50 0001 C CNN + 1 950 1400 + 0 1 1 0 +$EndComp +$Comp +L Switch:SW_Push SW? +U 1 1 5DE95103 +P 950 2150 +F 0 "SW?" V 904 2298 50 0000 L CNN +F 1 "SW_Push" V 995 2298 50 0000 L CNN +F 2 "" H 950 2350 50 0001 C CNN +F 3 "~" H 950 2350 50 0001 C CNN + 1 950 2150 + 0 1 1 0 +$EndComp +$Comp +L Switch:SW_Push SW? +U 1 1 5DE95459 +P 950 2850 +F 0 "SW?" V 904 2998 50 0000 L CNN +F 1 "SW_Push" V 995 2998 50 0000 L CNN +F 2 "" H 950 3050 50 0001 C CNN +F 3 "~" H 950 3050 50 0001 C CNN + 1 950 2850 + 0 1 1 0 +$EndComp +Text Label 950 1200 2 50 ~ 0 +BUT0 +Text Label 950 1950 2 50 ~ 0 +BUT1 +Text Label 950 2650 2 50 ~ 0 +BUT2 +$Comp +L power:GND #PWR? +U 1 1 5DE97876 +P 950 1600 +F 0 "#PWR?" H 950 1350 50 0001 C CNN +F 1 "GND" H 955 1427 50 0000 C CNN +F 2 "" H 950 1600 50 0001 C CNN +F 3 "" H 950 1600 50 0001 C CNN + 1 950 1600 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE97EED +P 950 2350 +F 0 "#PWR?" H 950 2100 50 0001 C CNN +F 1 "GND" H 955 2177 50 0000 C CNN +F 2 "" H 950 2350 50 0001 C CNN +F 3 "" H 950 2350 50 0001 C CNN + 1 950 2350 + 1 0 0 -1 +$EndComp +$Comp +L power:GND #PWR? +U 1 1 5DE98112 +P 950 3050 +F 0 "#PWR?" H 950 2800 50 0001 C CNN +F 1 "GND" H 955 2877 50 0000 C CNN +F 2 "" H 950 3050 50 0001 C CNN +F 3 "" H 950 3050 50 0001 C CNN + 1 950 3050 + 1 0 0 -1 +$EndComp +$Comp +L Regulator_Linear:AP2112K-3.3 U? +U 1 1 5DE99466 +P 7600 4300 +F 0 "U?" H 7600 4642 50 0000 C CNN +F 1 "AP2112K-3.3" H 7600 4551 50 0000 C CNN +F 2 "Package_TO_SOT_SMD:SOT-23-5" H 7600 4625 50 0001 C CNN +F 3 "https://www.diodes.com/assets/Datasheets/AP2112.pdf" H 7600 4400 50 0001 C CNN + 1 7600 4300 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7300 4200 7200 4200 +Wire Wire Line + 7200 4200 7200 4100 +$Comp +L power:+5V #PWR? +U 1 1 5DEA3406 +P 7200 4100 +F 0 "#PWR?" H 7200 3950 50 0001 C CNN +F 1 "+5V" H 7215 4273 50 0000 C CNN +F 2 "" H 7200 4100 50 0001 C CNN +F 3 "" H 7200 4100 50 0001 C CNN + 1 7200 4100 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7300 4300 7200 4300 +Wire Wire Line + 7200 4300 7200 4200 +Connection ~ 7200 4200 +$Comp +L power:GND #PWR? +U 1 1 5DEA545A +P 7600 4600 +F 0 "#PWR?" H 7600 4350 50 0001 C CNN +F 1 "GND" H 7605 4427 50 0000 C CNN +F 2 "" H 7600 4600 50 0001 C CNN +F 3 "" H 7600 4600 50 0001 C CNN + 1 7600 4600 + 1 0 0 -1 +$EndComp +Wire Wire Line + 7900 4200 8050 4200 +Wire Wire Line + 8050 4200 8050 4100 +$Comp +L power:+3V3 #PWR? +U 1 1 5DEA72CB +P 8050 4100 +F 0 "#PWR?" H 8050 3950 50 0001 C CNN +F 1 "+3V3" H 8065 4273 50 0000 C CNN +F 2 "" H 8050 4100 50 0001 C CNN +F 3 "" H 8050 4100 50 0001 C CNN + 1 8050 4100 + 1 0 0 -1 +$EndComp +$EndSCHEMATC diff --git a/hw/sym-lib-table b/hw/sym-lib-table new file mode 100644 index 0000000..a6b4024 --- /dev/null +++ b/hw/sym-lib-table @@ -0,0 +1,4 @@ +(sym_lib_table + (lib (name final)(type Legacy)(uri ${KIPRJMOD}/final.lib)(options "")(descr "")) + (lib (name final_project-rescue)(type Legacy)(uri ${KIPRJMOD}/final_project-rescue.lib)(options "")(descr "")) +) diff --git a/lcd_disp/.vscode/c_cpp_properties.json b/lcd_disp/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..4ad14cb --- /dev/null +++ b/lcd_disp/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "Linux", + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/lib/ccache/avr-gcc", + "cStandard": "c99", + "cppStandard": "c++17", + "compilerArgs": [ + "-Wall", "-pedantic", "-mmcu=attiy2313a" + ], + "defines": [ + "F_CPU=16000000", + "__" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/lcd_disp/.vscode/settings.json b/lcd_disp/.vscode/settings.json new file mode 100644 index 0000000..02b62d2 --- /dev/null +++ b/lcd_disp/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.intelliSenseEngine": "Tag Parser" +} \ No newline at end of file diff --git a/lcd_disp/.vscode/tasks.json b/lcd_disp/.vscode/tasks.json new file mode 100644 index 0000000..794c3b1 --- /dev/null +++ b/lcd_disp/.vscode/tasks.json @@ -0,0 +1,19 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "make", + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$gcc" + ] + } + ] +} \ No newline at end of file diff --git a/lcd_disp/Makefile b/lcd_disp/Makefile new file mode 100644 index 0000000..ef2f92a --- /dev/null +++ b/lcd_disp/Makefile @@ -0,0 +1,52 @@ + +NAME := sd-reader +HEX := $(NAME).hex +OUT := $(NAME).out +MAP := $(NAME).map +SOURCES := $(wildcard *.c) +HEADERS := $(wildcard *.h) +OBJECTS := $(patsubst %.c,%.o,$(SOURCES)) + +MCU := attiny2313a +MCU_AVRDUDE := t2313 +MCU_FREQ := 8000000UL + +CC := avr-gcc +OBJCOPY := avr-objcopy +SIZE := avr-size -A +DOXYGEN := doxygen + +CFLAGS := -Werror -Wall -pedantic -mmcu=$(MCU) -std=c99 -g -Os -DF_CPU=$(MCU_FREQ) + +all: $(HEX) + +clean: + rm -f $(HEX) $(OUT) $(MAP) $(OBJECTS) + rm -rf doc/html + +flash: $(HEX) + avrdude -y -c avr910 -p $(MCU_AVRDUDE) -U flash:w:$(HEX) + +$(HEX): $(OUT) + $(OBJCOPY) -R .eeprom -O ihex $< $@ + +$(OUT): $(OBJECTS) + $(CC) $(CFLAGS) -o $@ -Wl,-Map,$(MAP) $^ + @echo + @$(SIZE) $@ + @echo + +%.o: %.c $(HEADERS) + $(CC) $(CFLAGS) -c -o $@ $< + +%.pp: %.c + $(CC) $(CFLAGS) -E -o $@ $< + +%.ppo: %.c + $(CC) $(CFLAGS) -E $< + +doc: $(HEADERS) $(SOURCES) Doxyfile + $(DOXYGEN) Doxyfile + +.PHONY: all clean flash doc + diff --git a/lcd_disp/comms.h b/lcd_disp/comms.h new file mode 120000 index 0000000..ad5c9e6 --- /dev/null +++ b/lcd_disp/comms.h @@ -0,0 +1 @@ +../comms/comms.h \ No newline at end of file diff --git a/lcd_disp/comms.o b/lcd_disp/comms.o new file mode 100644 index 0000000000000000000000000000000000000000..8e2029c9817ce11e8346b7ce31c3bb88b661ae53 GIT binary patch literal 7200 zcma)>e~eUD702(eR2Nx=5}@{1I#Pmcvz^~NyX+KQva^73DU_vJP3`0D%u~ex7?oG{)3w}=Un}XjH9LHo$^r^ih_ytc>bZx#Gm z!F9n;3jV&}-wD1Vc*AmMf4ktC;O7MYT<`_Kz1KNADZ#$ruL=IK;J*s)`+&1EAow1^ zUlROn!Dj^jQ}8VxbT;o2>3+eF2!3Ah&ji0KxEBT5 zR9|LGw=zG%oMwKMc_(v|`2ps`%mH)g#aEf1VEHNLuQ0#L{3P@1%ug}@k@*GY_nBX2 zzVSok&v%#ynZL)pm-$D`Rp!&oN0?t@eu?>)%&#&3p7|~2x0wIMe1UoGN}B5(<^=QK znLo|^59S$WdgBmp=5W(F#k>x0nNmMszKNOo&(nu)hJVdEyIA@c=6j4vSN&yuF25VV zO?CO2`tw5n8-jnx&z`&Hv#+yGsFKB=)l_Rc^mjwPQQ;-lEoK1sypZSBayyb&*V*1i zNKtm&?$-AQ*=8s`3?7BXtr$C^d9~JSoy%CEc~@Xx4_jWXRM_&lYxAozQ=5Rosl7KeL^WEn&yW z^J;_DA%EKK&|uMM6l!yGL9C$Sv$eTWyyzb&7vuR_VPURRZ3HZQs8;`Ce0*%&OD9wD zia!;fE?51EH(#$kSSmE)l|s=gmV^1YGNxt%xv zTBru)nQEzMAI*e@Gh|q+YU3TMgJPCJ)=yi@@W}JvPBJyj{$@!R9d;?(>XP+s2{3** zrIctyi7nC$9)E|KVJTv{}a+9Yfdrnj)K zB5A^0P6e~Idc!`KOz65v^tTmGsMj%3(iB=!%mS-nP1tHnqTEJx~J8RMD za`9Yx&wk|PDnZc+k))uBJrLgQ#Qo8PWh~q8CQATn+T$Q)I?iaLe zq**H@$u=91<4Bq`z~dcC2)|YuCL?x@9n+;uy!&%Joz)amrc8+YbEy_6{BVJICW4jRa@puwSVFTc zFLte-&}mDH)#>o9Ei0{x9ojA_E{C1*=#~-TT&7zN+Na92MZLYS+*&g({LU%6jt=iK zE*o9yMY`!SvQ()^ZI_iUUUW@)#wCd#IZ!JXxu7yg3cj6j(|-%hs`x@sk5|f5asNO) zUal4@3&m2%wb(UAgXy|gEKLVX>EK(qacI8OGLAnx*z#a36HC~+f<_U_OWCob&>8k< zzs=I$DfDMbs$VS#DUwCfbiaZynbo?;2}wg$D@6h*46K??X;s7-LOf4atrVezbdXiE z8Lb+IaX3`9Qq(Y1x3KDbqpo#Pv+!A)bz=I~ggo@YtCak}t&bK+%Vba5#%P(@0jwQP zhC_B$Si6|2eUR4NI-gx*l9ryOMbgs42i+h=AUlv1Gujm=DG=EKl#_eV4N^R^1BenC zot6s>V`3I4hv>@<59#&Mh+Q6ir(sA(k`21CFf2Nn^qBRZzO(R{SzPN_7!R!@jL?dVTcV5 zA$(hdbfxyt2{nEiksah-*jVy^&<#OF;3mWxubw<9UOTNq4 z6rdNB9}Yd*!XELv79H8QIZ55p*zR4sCcUv;d2eiD??__6^|$5o-u6#TjE^LRaB_TN zd~9DRPYL;+iEW=7NlU@Ddnd<7G6VR@LO+_k!d#KVlB9s}+Y++FZ&0e>hu2bOv~PIj>vob* zHt!>mR;G50jg3S%@3{Nk{wQym<`<#8+rtt_ma9uw?Es*s(|H zYr11H<_bdBh$#eHGB&4LQlJcK=FAzc_=iHDddM*}I#XE&UXS}GTt+@1IH7eufjgaP zlMapV(TtnAPw)eR>8!xmq<9eT$DK||BHD+j;6E&P@^=JZ21m6%j-4#`X~wYnsAkIB zMljhYP6*y7c)!rEXePf7YTf{TLi1+uG0n90XEoD#%*&cn;8!(Mtj=lP3I3Ddi(vAN z3p2*ll)Dge_rt4G*3h43YheXm*X`go&~E|aA>aKPvPFEhxb`S^-{%; zHSndNVnx5 + +typedef enum { + FIFO_SUCCESS, + FIFO_FULL, + FIFO_EMPTY +} fifo_err_t; + +#define FIFO_LEN 20 +#define FIFO_TYPE uint8_t + +/** @brief Initialises FIFO + * + * This MUST be called before any other fifo_* functions are called. + * Unless memory is initialised to 0. Then it's fine I guess. + */ +void fifo_init(); + +/** @brief Pops most recent addition off of FIFO buffer. + * + * @retval FIFO_SUCCESS Successfully read data from FIFO + * @retval FIFO_EMPTY Buffer was empty, no data read + */ +fifo_err_t fifo_pop(FIFO_TYPE* dest); + +/** @brief Pushes entry onto FIFO buffer. + * + * @retval FIFO_SUCCESS successfully wrote data to FIFO buffer + * @retval FIFO_FULL buffer, was full, no data written + */ +fifo_err_t fifo_push(FIFO_TYPE src); + +#endif \ No newline at end of file diff --git a/lcd_disp/fifo.o b/lcd_disp/fifo.o new file mode 100644 index 0000000000000000000000000000000000000000..c532f82f59d5251ed3aab48a8ecddb1dd87757ca GIT binary patch literal 6792 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 literal 0 HcmV?d00001 diff --git a/lcd_disp/lcd.c b/lcd_disp/lcd.c new file mode 100644 index 0000000..8d7eb9c --- /dev/null +++ b/lcd_disp/lcd.c @@ -0,0 +1,592 @@ +/**************************************************************************** + Title: HD44780U LCD library + Author: Peter Fleury http://tinyurl.com/peterfleury + File: $Id: lcd.c,v 1.15.2.2 2015/01/17 12:16:05 peter Exp $ + Software: AVR-GCC 3.3 + Target: any AVR device, memory mapped mode only for AT90S4414/8515/Mega + + DESCRIPTION + Basic routines for interfacing a HD44780U-based text lcd display + + Originally based on Volker Oth's lcd library, + changed lcd_init(), added additional constants for lcd_command(), + added 4-bit I/O mode, improved and optimized code. + + Library can be operated in memory mapped mode (LCD_IO_MODE=0) or in + 4-bit IO port mode (LCD_IO_MODE=1). 8-bit IO port mode not supported. + + Memory mapped mode compatible with Kanda STK200, but supports also + generation of R/W signal through A8 address line. + + USAGE + See the C include lcd.h file for a description of each function + +*****************************************************************************/ +#include +#include +#include +#include +#include "lcd.h" + + + +/* +** constants/macros +*/ +#define DDR(x) (*(&x - 1)) /* address of data direction register of port x */ +#if defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__) + /* on ATmega64/128 PINF is on port 0x00 and not 0x60 */ + #define PIN(x) ( &PORTF==&(x) ? _SFR_IO8(0x00) : (*(&x - 2)) ) +#else + #define PIN(x) (*(&x - 2)) /* address of input register of port x */ +#endif + + +#if LCD_IO_MODE +#define lcd_e_delay() _delay_us(LCD_DELAY_ENABLE_PULSE) +#define lcd_e_high() LCD_E_PORT |= _BV(LCD_E_PIN); +#define lcd_e_low() LCD_E_PORT &= ~_BV(LCD_E_PIN); +#define lcd_e_toggle() toggle_e() +#define lcd_rw_high() LCD_RW_PORT |= _BV(LCD_RW_PIN) +#define lcd_rw_low() LCD_RW_PORT &= ~_BV(LCD_RW_PIN) +#define lcd_rs_high() LCD_RS_PORT |= _BV(LCD_RS_PIN) +#define lcd_rs_low() LCD_RS_PORT &= ~_BV(LCD_RS_PIN) +#endif + +#if LCD_IO_MODE +#if LCD_LINES==1 +#define LCD_FUNCTION_DEFAULT LCD_FUNCTION_4BIT_1LINE +#else +#define LCD_FUNCTION_DEFAULT LCD_FUNCTION_4BIT_2LINES +#endif +#else +#if LCD_LINES==1 +#define LCD_FUNCTION_DEFAULT LCD_FUNCTION_8BIT_1LINE +#else +#define LCD_FUNCTION_DEFAULT LCD_FUNCTION_8BIT_2LINES +#endif +#endif + +#if LCD_CONTROLLER_KS0073 +#if LCD_LINES==4 + +#define KS0073_EXTENDED_FUNCTION_REGISTER_ON 0x2C /* |0|010|1100 4-bit mode, extension-bit RE = 1 */ +#define KS0073_EXTENDED_FUNCTION_REGISTER_OFF 0x28 /* |0|010|1000 4-bit mode, extension-bit RE = 0 */ +#define KS0073_4LINES_MODE 0x09 /* |0|000|1001 4 lines mode */ + +#endif +#endif + +/* +** function prototypes +*/ +#if LCD_IO_MODE +static void toggle_e(void); +#endif + +/* +** local functions +*/ + + +/************************************************************************* +delay for a minimum of microseconds +the number of loops is calculated at compile-time from MCU clock frequency +*************************************************************************/ +#define delay(us) _delay_us(us) + + +#if LCD_IO_MODE +/* toggle Enable Pin to initiate write */ +static void toggle_e(void) +{ + lcd_e_high(); + lcd_e_delay(); + lcd_e_low(); +} +#endif + + +/************************************************************************* +Low-level function to write byte to LCD controller +Input: data byte to write to LCD + rs 1: write data + 0: write instruction +Returns: none +*************************************************************************/ +#if LCD_IO_MODE +static void lcd_write(uint8_t data,uint8_t rs) +{ + unsigned char dataBits ; + + + if (rs) { /* write data (RS=1, RW=0) */ + lcd_rs_high(); + } else { /* write instruction (RS=0, RW=0) */ + lcd_rs_low(); + } + lcd_rw_low(); /* RW=0 write mode */ + + if ( ( &LCD_DATA0_PORT == &LCD_DATA1_PORT) && ( &LCD_DATA1_PORT == &LCD_DATA2_PORT ) && ( &LCD_DATA2_PORT == &LCD_DATA3_PORT ) + && (LCD_DATA0_PIN == 0) && (LCD_DATA1_PIN == 1) && (LCD_DATA2_PIN == 2) && (LCD_DATA3_PIN == 3) ) + { + /* configure data pins as output */ + DDR(LCD_DATA0_PORT) |= 0x0F; + + /* output high nibble first */ + dataBits = LCD_DATA0_PORT & 0xF0; + LCD_DATA0_PORT = dataBits |((data>>4)&0x0F); + lcd_e_toggle(); + + /* output low nibble */ + LCD_DATA0_PORT = dataBits | (data&0x0F); + lcd_e_toggle(); + + /* all data pins high (inactive) */ + LCD_DATA0_PORT = dataBits | 0x0F; + } + else + { + /* configure data pins as output */ + DDR(LCD_DATA0_PORT) |= _BV(LCD_DATA0_PIN); + DDR(LCD_DATA1_PORT) |= _BV(LCD_DATA1_PIN); + DDR(LCD_DATA2_PORT) |= _BV(LCD_DATA2_PIN); + DDR(LCD_DATA3_PORT) |= _BV(LCD_DATA3_PIN); + + /* output high nibble first */ + LCD_DATA3_PORT &= ~_BV(LCD_DATA3_PIN); + LCD_DATA2_PORT &= ~_BV(LCD_DATA2_PIN); + LCD_DATA1_PORT &= ~_BV(LCD_DATA1_PIN); + LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN); + if(data & 0x80) LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN); + if(data & 0x40) LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN); + if(data & 0x20) LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN); + if(data & 0x10) LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN); + lcd_e_toggle(); + + /* output low nibble */ + LCD_DATA3_PORT &= ~_BV(LCD_DATA3_PIN); + LCD_DATA2_PORT &= ~_BV(LCD_DATA2_PIN); + LCD_DATA1_PORT &= ~_BV(LCD_DATA1_PIN); + LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN); + if(data & 0x08) LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN); + if(data & 0x04) LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN); + if(data & 0x02) LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN); + if(data & 0x01) LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN); + lcd_e_toggle(); + + /* all data pins high (inactive) */ + LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN); + LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN); + LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN); + LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN); + } +} +#else +#define lcd_write(d,rs) if (rs) *(volatile uint8_t*)(LCD_IO_DATA) = d; else *(volatile uint8_t*)(LCD_IO_FUNCTION) = d; +/* rs==0 -> write instruction to LCD_IO_FUNCTION */ +/* rs==1 -> write data to LCD_IO_DATA */ +#endif + + +/************************************************************************* +Low-level function to read byte from LCD controller +Input: rs 1: read data + 0: read busy flag / address counter +Returns: byte read from LCD controller +*************************************************************************/ +#if LCD_IO_MODE +static uint8_t lcd_read(uint8_t rs) +{ + uint8_t data; + + + if (rs) + lcd_rs_high(); /* RS=1: read data */ + else + lcd_rs_low(); /* RS=0: read busy flag */ + lcd_rw_high(); /* RW=1 read mode */ + + if ( ( &LCD_DATA0_PORT == &LCD_DATA1_PORT) && ( &LCD_DATA1_PORT == &LCD_DATA2_PORT ) && ( &LCD_DATA2_PORT == &LCD_DATA3_PORT ) + && ( LCD_DATA0_PIN == 0 )&& (LCD_DATA1_PIN == 1) && (LCD_DATA2_PIN == 2) && (LCD_DATA3_PIN == 3) ) + { + DDR(LCD_DATA0_PORT) &= 0xF0; /* configure data pins as input */ + + lcd_e_high(); + lcd_e_delay(); + data = PIN(LCD_DATA0_PORT) << 4; /* read high nibble first */ + lcd_e_low(); + + lcd_e_delay(); /* Enable 500ns low */ + + lcd_e_high(); + lcd_e_delay(); + data |= PIN(LCD_DATA0_PORT)&0x0F; /* read low nibble */ + lcd_e_low(); + } + else + { + /* configure data pins as input */ + DDR(LCD_DATA0_PORT) &= ~_BV(LCD_DATA0_PIN); + DDR(LCD_DATA1_PORT) &= ~_BV(LCD_DATA1_PIN); + DDR(LCD_DATA2_PORT) &= ~_BV(LCD_DATA2_PIN); + DDR(LCD_DATA3_PORT) &= ~_BV(LCD_DATA3_PIN); + + /* read high nibble first */ + lcd_e_high(); + lcd_e_delay(); + data = 0; + if ( PIN(LCD_DATA0_PORT) & _BV(LCD_DATA0_PIN) ) data |= 0x10; + if ( PIN(LCD_DATA1_PORT) & _BV(LCD_DATA1_PIN) ) data |= 0x20; + if ( PIN(LCD_DATA2_PORT) & _BV(LCD_DATA2_PIN) ) data |= 0x40; + if ( PIN(LCD_DATA3_PORT) & _BV(LCD_DATA3_PIN) ) data |= 0x80; + lcd_e_low(); + + lcd_e_delay(); /* Enable 500ns low */ + + /* read low nibble */ + lcd_e_high(); + lcd_e_delay(); + if ( PIN(LCD_DATA0_PORT) & _BV(LCD_DATA0_PIN) ) data |= 0x01; + if ( PIN(LCD_DATA1_PORT) & _BV(LCD_DATA1_PIN) ) data |= 0x02; + if ( PIN(LCD_DATA2_PORT) & _BV(LCD_DATA2_PIN) ) data |= 0x04; + if ( PIN(LCD_DATA3_PORT) & _BV(LCD_DATA3_PIN) ) data |= 0x08; + lcd_e_low(); + } + return data; +} +#else +#define lcd_read(rs) (rs) ? *(volatile uint8_t*)(LCD_IO_DATA+LCD_IO_READ) : *(volatile uint8_t*)(LCD_IO_FUNCTION+LCD_IO_READ) +/* rs==0 -> read instruction from LCD_IO_FUNCTION */ +/* rs==1 -> read data from LCD_IO_DATA */ +#endif + + +/************************************************************************* +loops while lcd is busy, returns address counter +*************************************************************************/ +static uint8_t lcd_waitbusy(void) + +{ + register uint8_t c; + + /* wait until busy flag is cleared */ + while ( (c=lcd_read(0)) & (1<= LCD_START_LINE2) && (pos < LCD_START_LINE3) ) + addressCounter = LCD_START_LINE3; + else if ( (pos >= LCD_START_LINE3) && (pos < LCD_START_LINE4) ) + addressCounter = LCD_START_LINE4; + else + addressCounter = LCD_START_LINE1; +#else + if ( pos < LCD_START_LINE3 ) + addressCounter = LCD_START_LINE2; + else if ( (pos >= LCD_START_LINE2) && (pos < LCD_START_LINE4) ) + addressCounter = LCD_START_LINE3; + else if ( (pos >= LCD_START_LINE3) && (pos < LCD_START_LINE2) ) + addressCounter = LCD_START_LINE4; + else + addressCounter = LCD_START_LINE1; +#endif +#endif + lcd_command((1<>4; + LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN); // LCD_FUNCTION_8BIT>>4; + lcd_e_toggle(); + delay(LCD_DELAY_INIT); /* delay, busy flag can't be checked here */ + + /* repeat last command */ + lcd_e_toggle(); + delay(LCD_DELAY_INIT_REP); /* delay, busy flag can't be checked here */ + + /* repeat last command a third time */ + lcd_e_toggle(); + delay(LCD_DELAY_INIT_REP); /* delay, busy flag can't be checked here */ + + /* now configure for 4bit mode */ + LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN); // LCD_FUNCTION_4BIT_1LINE>>4 + lcd_e_toggle(); + delay(LCD_DELAY_INIT_4BIT); /* some displays need this additional delay */ + + /* from now the LCD only accepts 4 bit I/O, we can use lcd_command() */ +#else + /* + * Initialize LCD to 8 bit memory mapped mode + */ + + /* enable external SRAM (memory mapped lcd) and one wait state */ + MCUCR = _BV(SRE) | _BV(SRW); + + /* reset LCD */ + delay(LCD_DELAY_BOOTUP); /* wait 16ms after power-on */ + lcd_write(LCD_FUNCTION_8BIT_1LINE,0); /* function set: 8bit interface */ + delay(LCD_DELAY_INIT); /* wait 5ms */ + lcd_write(LCD_FUNCTION_8BIT_1LINE,0); /* function set: 8bit interface */ + delay(LCD_DELAY_INIT_REP); /* wait 64us */ + lcd_write(LCD_FUNCTION_8BIT_1LINE,0); /* function set: 8bit interface */ + delay(LCD_DELAY_INIT_REP); /* wait 64us */ +#endif + +#if KS0073_4LINES_MODE + /* Display with KS0073 controller requires special commands for enabling 4 line mode */ + lcd_command(KS0073_EXTENDED_FUNCTION_REGISTER_ON); + lcd_command(KS0073_4LINES_MODE); + lcd_command(KS0073_EXTENDED_FUNCTION_REGISTER_OFF); +#else + lcd_command(LCD_FUNCTION_DEFAULT); /* function set: display lines */ +#endif + lcd_command(LCD_DISP_OFF); /* display off */ + lcd_clrscr(); /* display clear */ + lcd_command(LCD_MODE_DEFAULT); /* set entry mode */ + lcd_command(dispAttr); /* display/cursor control */ + +}/* lcd_init */ diff --git a/lcd_disp/lcd.h b/lcd_disp/lcd.h new file mode 100644 index 0000000..0dbc197 --- /dev/null +++ b/lcd_disp/lcd.h @@ -0,0 +1,369 @@ +#ifndef LCD_H +#define LCD_H +/************************************************************************* + Title : C include file for the HD44780U LCD library (lcd.c) + Author: Peter Fleury http://tinyurl.com/peterfleury + File: $Id: lcd.h,v 1.14.2.4 2015/01/20 17:16:07 peter Exp $ + Software: AVR-GCC 4.x + Hardware: any AVR device, memory mapped mode only for AVR with + memory mapped interface (AT90S8515/ATmega8515/ATmega128) +***************************************************************************/ + +/** + @mainpage + Collection of libraries for AVR-GCC + @author Peter Fleury pfleury@gmx.ch http://tinyurl.com/peterfleury + @copyright (C) 2015 Peter Fleury, GNU General Public License Version 3 + + @file + @defgroup pfleury_lcd LCD library + @code #include @endcode + + @brief Basic routines for interfacing a HD44780U-based character LCD display + + LCD character displays can be found in many devices, like espresso machines, laser printers. + The Hitachi HD44780 controller and its compatible controllers like Samsung KS0066U have become an industry standard for these types of displays. + + This library allows easy interfacing with a HD44780 compatible display and can be + operated in memory mapped mode (LCD_IO_MODE defined as 0 in the include file lcd.h.) or in + 4-bit IO port mode (LCD_IO_MODE defined as 1). 8-bit IO port mode is not supported. + + Memory mapped mode is compatible with old Kanda STK200 starter kit, but also supports + generation of R/W signal through A8 address line. + + @see The chapter Interfacing a HD44780 Based LCD to an AVR + on my home page, which shows example circuits how to connect an LCD to an AVR controller. + + @author Peter Fleury pfleury@gmx.ch http://tinyurl.com/peterfleury + + @version 2.0 + + @copyright (C) 2015 Peter Fleury, GNU General Public License Version 3 + +*/ + +#include +#include + +#if (__GNUC__ * 100 + __GNUC_MINOR__) < 405 +#error "This library requires AVR-GCC 4.5 or later, update to newer AVR-GCC compiler !" +#endif + + +/**@{*/ + +/* + * LCD and target specific definitions below can be defined in a separate include file with name lcd_definitions.h instead modifying this file + * by adding -D_LCD_DEFINITIONS_FILE to the CDEFS section in the Makefile + * All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h + */ +#ifdef _LCD_DEFINITIONS_FILE +#include "lcd_definitions.h" +#endif + + +/** + * @name Definition for LCD controller type + * Use 0 for HD44780 controller, change to 1 for displays with KS0073 controller. + */ +#ifndef LCD_CONTROLLER_KS0073 +#define LCD_CONTROLLER_KS0073 0 /**< Use 0 for HD44780 controller, 1 for KS0073 controller */ +#endif + +/** + * @name Definitions for Display Size + * Change these definitions to adapt setting to your display + * + * These definitions can be defined in a separate include file \b lcd_definitions.h instead modifying this file by + * adding -D_LCD_DEFINITIONS_FILE to the CDEFS section in the Makefile. + * All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h + * + */ +#ifndef LCD_LINES +#define LCD_LINES 2 /**< number of visible lines of the display */ +#endif +#ifndef LCD_DISP_LENGTH +#define LCD_DISP_LENGTH 16 /**< visibles characters per line of the display */ +#endif +#ifndef LCD_LINE_LENGTH +#define LCD_LINE_LENGTH 0x40 /**< internal line length of the display */ +#endif +#ifndef LCD_START_LINE1 +#define LCD_START_LINE1 0x00 /**< DDRAM address of first char of line 1 */ +#endif +#ifndef LCD_START_LINE2 +#define LCD_START_LINE2 0x40 /**< DDRAM address of first char of line 2 */ +#endif +#ifndef LCD_START_LINE3 +#define LCD_START_LINE3 0x14 /**< DDRAM address of first char of line 3 */ +#endif +#ifndef LCD_START_LINE4 +#define LCD_START_LINE4 0x54 /**< DDRAM address of first char of line 4 */ +#endif +#ifndef LCD_WRAP_LINES +#define LCD_WRAP_LINES 0 /**< 0: no wrap, 1: wrap at end of visibile line */ +#endif + + +/** + * @name Definitions for 4-bit IO mode + * + * The four LCD data lines and the three control lines RS, RW, E can be on the + * same port or on different ports. + * Change LCD_RS_PORT, LCD_RW_PORT, LCD_E_PORT if you want the control lines on + * different ports. + * + * Normally the four data lines should be mapped to bit 0..3 on one port, but it + * is possible to connect these data lines in different order or even on different + * ports by adapting the LCD_DATAx_PORT and LCD_DATAx_PIN definitions. + * + * Adjust these definitions to your target.\n + * These definitions can be defined in a separate include file \b lcd_definitions.h instead modifying this file by + * adding \b -D_LCD_DEFINITIONS_FILE to the \b CDEFS section in the Makefile. + * All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h + * + */ +#define LCD_IO_MODE 1 /**< 0: memory mapped mode, 1: IO port mode */ + +#if LCD_IO_MODE + +#ifndef LCD_PORT +#define LCD_PORT PORTB /**< port for the LCD lines */ +#endif +#ifndef LCD_DATA0_PORT +#define LCD_DATA0_PORT LCD_PORT /**< port for 4bit data bit 0 */ +#endif +#ifndef LCD_DATA1_PORT +#define LCD_DATA1_PORT LCD_PORT /**< port for 4bit data bit 1 */ +#endif +#ifndef LCD_DATA2_PORT +#define LCD_DATA2_PORT LCD_PORT /**< port for 4bit data bit 2 */ +#endif +#ifndef LCD_DATA3_PORT +#define LCD_DATA3_PORT LCD_PORT /**< port for 4bit data bit 3 */ +#endif +#ifndef LCD_DATA0_PIN +#define LCD_DATA0_PIN 5 /**< pin for 4bit data bit 0 */ +#endif +#ifndef LCD_DATA1_PIN +#define LCD_DATA1_PIN 4 /**< pin for 4bit data bit 1 */ +#endif +#ifndef LCD_DATA2_PIN +#define LCD_DATA2_PIN 3 /**< pin for 4bit data bit 2 */ +#endif +#ifndef LCD_DATA3_PIN +#define LCD_DATA3_PIN 2 /**< pin for 4bit data bit 3 */ +#endif +#ifndef LCD_RS_PORT +#define LCD_RS_PORT PORTD /**< port for RS line */ +#endif +#ifndef LCD_RS_PIN +#define LCD_RS_PIN 0 /**< pin for RS line */ +#endif +#ifndef LCD_RW_PORT +#define LCD_RW_PORT PORTD /**< port for RW line */ +#endif +#ifndef LCD_RW_PIN +#define LCD_RW_PIN 1 /**< pin for RW line */ +#endif +#ifndef LCD_E_PORT +#define LCD_E_PORT PORTD /**< port for Enable line */ +#endif +#ifndef LCD_E_PIN +#define LCD_E_PIN 2 /**< pin for Enable line */ +#endif + +#elif defined(__AVR_AT90S4414__) || defined(__AVR_AT90S8515__) || defined(__AVR_ATmega64__) || \ + defined(__AVR_ATmega8515__)|| defined(__AVR_ATmega103__) || defined(__AVR_ATmega128__) || \ + defined(__AVR_ATmega161__) || defined(__AVR_ATmega162__) +/* + * memory mapped mode is only supported when the device has an external data memory interface + */ +#define LCD_IO_DATA 0xC000 /* A15=E=1, A14=RS=1 */ +#define LCD_IO_FUNCTION 0x8000 /* A15=E=1, A14=RS=0 */ +#define LCD_IO_READ 0x0100 /* A8 =R/W=1 (R/W: 1=Read, 0=Write */ + +#else +#error "external data memory interface not available for this device, use 4-bit IO port mode" + +#endif + + +/** + * @name Definitions of delays + * Used to calculate delay timers. + * Adapt the F_CPU define in the Makefile to the clock frequency in Hz of your target + * + * These delay times can be adjusted, if some displays require different delays.\n + * These definitions can be defined in a separate include file \b lcd_definitions.h instead modifying this file by + * adding \b -D_LCD_DEFINITIONS_FILE to the \b CDEFS section in the Makefile. + * All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h + */ +#ifndef LCD_DELAY_BOOTUP +#define LCD_DELAY_BOOTUP 16000 /**< delay in micro seconds after power-on */ +#endif +#ifndef LCD_DELAY_INIT +#define LCD_DELAY_INIT 5000 /**< delay in micro seconds after initialization command sent */ +#endif +#ifndef LCD_DELAY_INIT_REP +#define LCD_DELAY_INIT_REP 64 /**< delay in micro seconds after initialization command repeated */ +#endif +#ifndef LCD_DELAY_INIT_4BIT +#define LCD_DELAY_INIT_4BIT 64 /**< delay in micro seconds after setting 4-bit mode */ +#endif +#ifndef LCD_DELAY_BUSY_FLAG +#define LCD_DELAY_BUSY_FLAG 4 /**< time in micro seconds the address counter is updated after busy flag is cleared */ +#endif +#ifndef LCD_DELAY_ENABLE_PULSE +#define LCD_DELAY_ENABLE_PULSE 1 /**< enable signal pulse width in micro seconds */ +#endif + + +/** + * @name Definitions for LCD command instructions + * The constants define the various LCD controller instructions which can be passed to the + * function lcd_command(), see HD44780 data sheet for a complete description. + */ + +/* instruction register bit positions, see HD44780U data sheet */ +#define LCD_CLR 0 /* DB0: clear display */ +#define LCD_HOME 1 /* DB1: return to home position */ +#define LCD_ENTRY_MODE 2 /* DB2: set entry mode */ +#define LCD_ENTRY_INC 1 /* DB1: 1=increment, 0=decrement */ +#define LCD_ENTRY_SHIFT 0 /* DB2: 1=display shift on */ +#define LCD_ON 3 /* DB3: turn lcd/cursor on */ +#define LCD_ON_DISPLAY 2 /* DB2: turn display on */ +#define LCD_ON_CURSOR 1 /* DB1: turn cursor on */ +#define LCD_ON_BLINK 0 /* DB0: blinking cursor ? */ +#define LCD_MOVE 4 /* DB4: move cursor/display */ +#define LCD_MOVE_DISP 3 /* DB3: move display (0-> cursor) ? */ +#define LCD_MOVE_RIGHT 2 /* DB2: move right (0-> left) ? */ +#define LCD_FUNCTION 5 /* DB5: function set */ +#define LCD_FUNCTION_8BIT 4 /* DB4: set 8BIT mode (0->4BIT mode) */ +#define LCD_FUNCTION_2LINES 3 /* DB3: two lines (0->one line) */ +#define LCD_FUNCTION_10DOTS 2 /* DB2: 5x10 font (0->5x7 font) */ +#define LCD_CGRAM 6 /* DB6: set CG RAM address */ +#define LCD_DDRAM 7 /* DB7: set DD RAM address */ +#define LCD_BUSY 7 /* DB7: LCD is busy */ + +/* set entry mode: display shift on/off, dec/inc cursor move direction */ +#define LCD_ENTRY_DEC 0x04 /* display shift off, dec cursor move dir */ +#define LCD_ENTRY_DEC_SHIFT 0x05 /* display shift on, dec cursor move dir */ +#define LCD_ENTRY_INC_ 0x06 /* display shift off, inc cursor move dir */ +#define LCD_ENTRY_INC_SHIFT 0x07 /* display shift on, inc cursor move dir */ + +/* display on/off, cursor on/off, blinking char at cursor position */ +#define LCD_DISP_OFF 0x08 /* display off */ +#define LCD_DISP_ON 0x0C /* display on, cursor off */ +#define LCD_DISP_ON_BLINK 0x0D /* display on, cursor off, blink char */ +#define LCD_DISP_ON_CURSOR 0x0E /* display on, cursor on */ +#define LCD_DISP_ON_CURSOR_BLINK 0x0F /* display on, cursor on, blink char */ + +/* move cursor/shift display */ +#define LCD_MOVE_CURSOR_LEFT 0x10 /* move cursor left (decrement) */ +#define LCD_MOVE_CURSOR_RIGHT 0x14 /* move cursor right (increment) */ +#define LCD_MOVE_DISP_LEFT 0x18 /* shift display left */ +#define LCD_MOVE_DISP_RIGHT 0x1C /* shift display right */ + +/* function set: set interface data length and number of display lines */ +#define LCD_FUNCTION_4BIT_1LINE 0x20 /* 4-bit interface, single line, 5x7 dots */ +#define LCD_FUNCTION_4BIT_2LINES 0x28 /* 4-bit interface, dual line, 5x7 dots */ +#define LCD_FUNCTION_8BIT_1LINE 0x30 /* 8-bit interface, single line, 5x7 dots */ +#define LCD_FUNCTION_8BIT_2LINES 0x38 /* 8-bit interface, dual line, 5x7 dots */ + + +#define LCD_MODE_DEFAULT ((1<*F8y2z%2JJex)dC7bEqc4Bv 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 literal 0 HcmV?d00001 diff --git a/lcd_disp/main.c b/lcd_disp/main.c new file mode 100644 index 0000000..b34a70e --- /dev/null +++ b/lcd_disp/main.c @@ -0,0 +1,172 @@ +#include "main.h" + +/* ---- Global Flags ---- */ +int8_t display_song = -1; //! ID of the song currently being displayed, -1 if song list is not initialised +int8_t last_displayed_song = -1; +int8_t selected_song = -1; //! ID of the selected song. -1 if no song is selected. +bool update_display = false; //! flag on whether or not to update display + +struct { + char name[SONG_NAME_LEN + 1]; //! array of all song names, names are allocated on heap + uint8_t num_songs; //! Number of songs loaded into display memory + bool playing; //! Whether the currently selected song is playing +} songs; + + +/** @brief Handles right button press + */ +void handle_left_press() { + if (songs.num_songs == 0) { + lcd_clrscr(); + lcd_puts("No songs to play!"); + } else { + // Choose lower ID song. Wrap around. + display_song--; + if (display_song == 0) { + display_song = songs.num_songs - 1; + } + } +} + +/** @brief Handles left button press + */ +void handle_right_press() { + if (songs.num_songs == 0) { + lcd_clrscr(); + lcd_puts("No songs to play!"); + } else { + // Choose higher ID song. Wrap around. + display_song++; + if (display_song == songs.num_songs) { + display_song = 0; + } + } +} + +/** @brief Handles playpause button press. + * + * Based on selected song and displayed song, will select new song + * or play/pause song. + */ +void handle_playpause_press() { + // If there are no songs displayed, error + if (display_song == -1) { + lcd_clrscr(); + lcd_puts("No songs to play!"); + return; + } + + // If no song selected, select the correct one. + if (selected_song == -1) { + comms_select_file(display_song); + return; + } + + // Song selected, send play/pause + if (songs.playing) { + comms_pause(); + songs.playing = false; + } else { + comms_play(); + songs.playing = true; + } + + return; +} + +int main() { + // Scratch variables + uint8_t incoming_cmd = 0; + + fifo_init(); + gpio_init(); + usart_init(); + + lcd_init(LCD_DISP_ON); + lcd_clrscr(); + lcd_gotoxy(0,0); + + // Main loop + while (1) { + // Handle incoming messages + if (fifo_pop(&incoming_cmd) == FIFO_SUCCESS) { + switch (incoming_cmd) { + // Clear songs, so there are none to display + case (COMMS_CMD_CLR): + songs.num_songs = 0; + display_song = -1; + break; + // change number of songs + case (COMMS_CMD_NUM): + while(fifo_pop((FIFO_TYPE*) &songs.num_songs) != FIFO_SUCCESS); + break; + default: + lcd_clrscr(); + lcd_puts("Incorrect command!"); + break; + } + + } + + // Handle "left" button press + if (BUTTONS_PORT & (1 << BUTTONS_LEFT_PIN)) { + handle_left_press(); + _delay_ms(100); // Delay to debounce + } + + // Handle "right" button press + if (BUTTONS_PORT & (1 << BUTTONS_LEFT_PIN)) { + handle_left_press(); + _delay_ms(100); // Delay to debounce + } + + // Handle play/pause button press + if (BUTTONS_PORT & (1 << BUTTONS_LEFT_PIN)) { + handle_left_press(); + _delay_ms(100); // Delay to debounce + } + + // Update display periodically + if (update_display) { + // check if song has changed + if (last_displayed_song != display_song) { + comms_query_name(display_song); + + char tmp_chr; + + // Wait for command to come in + while (fifo_pop((FIFO_TYPE*) &tmp_chr) != FIFO_SUCCESS); + + // TODO add error handling here + + // Wait for length info to come in + while (fifo_pop((FIFO_TYPE*) &tmp_chr) != FIFO_SUCCESS); + uint8_t len = tmp_chr; + + // Read in string + for (uint8_t i = 0; i < len; i++) { + // Hold until fifo returns successfully + while(fifo_pop((FIFO_TYPE*) &songs.name[i]) != FIFO_SUCCESS); + } + } + lcd_clrscr(); + + if (display_song != -1) { + lcd_puts(songs.name); + lcd_gotoxy(0, 1); + lcd_putc('1' + display_song); + lcd_gotoxy(13,1); + lcd_putc('1' + display_song); + lcd_putc('/'); + lcd_putc('0' + songs.num_songs); + + } else { + lcd_puts("No song!"); + } + + update_display = false; + last_displayed_song = display_song; + } + } + +} \ No newline at end of file diff --git a/lcd_disp/main.h b/lcd_disp/main.h new file mode 100644 index 0000000..a52eb9f --- /dev/null +++ b/lcd_disp/main.h @@ -0,0 +1,23 @@ +#ifndef MAIN_H_ +#define MAIN_H_ + +#include +#include +#include +#include +#include +#include +#include + + +#include "lcd.h" +#include "fifo.h" +#include "comms.h" +#include "periph.h" + +#define SONG_NAME_LEN 16 + +/* ---- Globally available flags ---- */ +extern bool update_display; + +#endif \ No newline at end of file diff --git a/lcd_disp/main.o b/lcd_disp/main.o new file mode 100644 index 0000000000000000000000000000000000000000..748160a8533bce43ce1faaf1c75af1a0a41d4116 GIT binary patch literal 12944 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 literal 0 HcmV?d00001 diff --git a/lcd_disp/periph.c b/lcd_disp/periph.c new file mode 100644 index 0000000..bb18795 --- /dev/null +++ b/lcd_disp/periph.c @@ -0,0 +1,69 @@ +#include "periph.h" + + +/** @brief Handles incoming Serial commands from the main player. + */ +ISR(USART_RX_vect) { + cli(); + // Push incoming data to FIFO + fifo_push(UDR); + + // Flag is cleared by reading data from UDR + sei(); +} + +/** @brief Display update timer + */ +ISR(TIMER0_COMPA_vect) { + cli(); + update_display = true; + + // Flag is cleared by calling the vector + sei(); +} + +void gpio_init() { + // Initialise button pins as inputs + BUTTONS_DDR &= ~(1 << BUTTONS_LEFT_PIN) & + ~(1 << BUTTONS_RIGHT_PIN) & + ~(1 << BUTTONS_PLAYPAUSE_PIN); + // Turn on button pullups + BUTTONS_PORT |= (1 << BUTTONS_LEFT_PIN) | + (1 << BUTTONS_RIGHT_PIN) | + (1 << BUTTONS_PLAYPAUSE_PIN); +} + +void timer_init() { + /** + * Need to verify the math here. + * But with a prescaler of 1024, f_cpu at 8MHz, an OCRA of 195 gives + * 20Hz refresh rate. + */ + + // COM0x can be set to defaults + // To set to CTC, WGM = 0b010 + TCCR0A = (1 << WGM01); + + // CS = 0b101 -> 1024 prescaler + // Everything else should be 0. + // TCCR0B = 0b00000101; <- can't use this because binary stuff is a GCC extension + TCCR0B = 5; + + // Set to ~20Hz + OCR0A = 195; + + // Enable interrupt + TIMSK = (1 << OCIE0A); +} + +void usart_init() { + // initialize USART + UBRRL = UBRR_VALUE & 255; + UBRRH = UBRR_VALUE >> 8; + UCSRB = (1 << TXEN) | (1 << RXEN); // fire-up USART + UCSRC = (1 << UCSZ1) | (1 << UCSZ0); // fire-up USART + + + // Enable RX complete interrupt + UCSRB |= (1 << RXCIE); +} \ No newline at end of file diff --git a/lcd_disp/periph.h b/lcd_disp/periph.h new file mode 100644 index 0000000..e1ac82a --- /dev/null +++ b/lcd_disp/periph.h @@ -0,0 +1,45 @@ +/** + * @mainpage + * Peripherals + * + * @brief Peripheral initialization functions and interrupt handling routines. + * + * + * Contains the ISRs to handle the USART RX interrupt and the display timer interrupt. + */ + + +#ifndef PERIPH_H_ +#define PERIPH_H_ + +#include "main.h" + + +#define USART_BAUDRATE 9600 //! USART baudrate, change this to set it. +#define UBRR_VALUE (((F_CPU/(USART_BAUDRATE*16UL)))-1) + +#define BUTTONS_DDR DDRD +#define BUTTONS_PORT PORTD +#define BUTTONS_LEFT_PIN PORTD2 +#define BUTTONS_RIGHT_PIN PORTD3 +#define BUTTONS_PLAYPAUSE_PIN PORTD4 + + +/** @brief Sets up various GPIO functions + * + * First enables buttons as inputs. + */ +void gpio_init(); + +/** @brief Initialises timer for updating display + * + * Sets up timer0 in + */ +void timer_init(); + +/** @brief Sets up USART for TX and RX with a baud rate of USART_BAUDRATE + */ +void usart_init(); + + +#endif \ No newline at end of file diff --git a/lcd_disp/periph.o b/lcd_disp/periph.o new file mode 100644 index 0000000000000000000000000000000000000000..5ea45819aa090f75fa4c32b4fd3ff00e91da061b GIT binary patch literal 8172 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 UrqX(n?k-Jz@8Krm=m_=r|ACd5EdT%j literal 0 HcmV?d00001 diff --git a/sd_reader/.vscode/c_cpp_properties.json b/sd_reader/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..d9993e3 --- /dev/null +++ b/sd_reader/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Linux", + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/lib/ccache/avr-gcc", + "cStandard": "c99", + "cppStandard": "c++17", + "compilerArgs": [ + "-Wall", "-pedantic", "-mmcu=atmega328" + ], + "defines": [ + "F_CPU=16000000" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/sd_reader/.vscode/tasks.json b/sd_reader/.vscode/tasks.json new file mode 100644 index 0000000..794c3b1 --- /dev/null +++ b/sd_reader/.vscode/tasks.json @@ -0,0 +1,19 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "make", + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$gcc" + ] + } + ] +} \ No newline at end of file diff --git a/sd_reader/ChangeLog b/sd_reader/ChangeLog new file mode 100644 index 0000000..848c192 --- /dev/null +++ b/sd_reader/ChangeLog @@ -0,0 +1,124 @@ + +2012-06-12 sd-reader + * fix capacity readout from csd register depending on format version + * fix gcc strict-aliasing warnings (also somewhat enlarges code size) + +2011-04-23 sd-reader + * fix FAT access for cluster numbers beyond 2^15 (for FAT16) and 2^30 (for FAT32) (thanks to Darwin Engwer for testing) + * correctly return disk-full condition from fat_write_file() on certain conditions + * use byteorder memory access functions for fat_fs_get_free() + * be more specific on the return value of fat_write_file() + +2011-02-05 sd-reader + * implement renaming a file or directory + * rewrite byteorder handling to fix unaligned memory accesses on 32-bit and probably 16-bit architectures + * make fat_create_file() not return failure if the file already exists + * make the "cat" output respect the count of bytes actually read + * document how to use fat_seek_file() for retrieving the file position + +2010-10-10 sd-reader + * Fix equal file names when reading two successive 8.3 directory entries. + * Fix calculation of cluster positions beyond 4GB (32 bit integer overflow). + * Fix endless looping of directory listing (occured with valid entry at end of last cluster). + +2010-01-10 sd-reader + * Make LFN support configurable. + * Ignore LFN directory entries without 8.3 name. + * Ignore LFN directory entries which do not match the 8.3 name's checksum. + * Implement delayed directory entry updates (disabled by default) (thanks to Adam Mayer). + * Speedup search for free cluster. + * Fix memory leak when using the "init" command (thanks to Tibor Vilhan). + * Fix ATmega328P-specific pin mappings. + * Add some of the picoPower MCU variants. + +2009-03-30 sd-reader + * Make 8.3 basename and/or extension lowercase when told by Windows NT and later. + * Add ATmega328 pin configuration. + * Fix MMC/SD/SDHC distinction. + * Fix raw block read/write buffering (thanks to Kurt Sterckx). + * Fix fat size calculation for FAT16 when configured with FAT32. + * Fix compilation for read-only configurations. + * Fix card lock detection. + * Make it easier to link with a C++ application (thanks to Jérôme Despatis). + +2008-11-21 sd-reader + * Support for SDHC cards (disabled by default). + * Support for FAT32 (disabled by default). + +2008-06-08 sd-reader + * New "init" command to allow reinitialization of memory card. + * Fix searching through multi-cluster directories. + * Fix reading directory entries spanning a cluster border (backport from mega-eth). + * Do not abort the whole lfn entry when the file name is too long, just drop single characters (backport from mega-eth). + * Change fat16_get_dir_entry_of_path() to ignore a slash at the end (backport from mega-eth). + * Make listing a directory's content much faster (backport from mega-eth). + * Shrink code size by centralizing cluster offset calculation (backport from mega-eth). + * Some other small fixes and optimizations. + +2007-12-13 sd-reader + * Dual-license the major implementation modules under GPL and LGPL. + +2007-06-03 sd-reader + * Fix reading beyond cached block (by Benjamin Meier). + * Implement support for reading and writing file modification dates/times. + (Thanks to Torsten Seeboth for testing.) + +2007-03-01 sd-reader + * Avoid LFN directory entries for the "." and ".." directory references. + This prevented Windows from deleting directories. + * Handle special case where the 8.3 filename begins with 0xe5. + * Fix return value of fat16_delete_file() when deleting empty files. + * Fix fat16_clear_cluster() which was zeroing only 16 of every 32 bytes. + +2007-01-20 sd-reader + * Fix directory creation. + - Correctly create "." and ".." directory entries (8.3 <-> lfn versions). + - Correctly clear cluster containing the directory entries for new directory. + +2006-11-01 sd-reader + * Implement creation and deletion of directories. + * Clear the directory entries of new directory clusters. + * Prevent linkage against printf(). + * Make the use of malloc()/free() optional. + +2006-09-01 sd-reader + * Fix shortening files. + * Fix free disk space calculation. + +2006-08-24 sd-reader + * Improve sleep handling. + * Display extended card information on boot and + when executing the "disk" shell command. + * Correctly determine FAT type by cluster count. + * Fix cluster allocation beyond card capacity. + +2006-08-16 sd-reader + * Provide FAT16 capacity and usage information. + * Implement the backspace key in the mini shell. + * Enter idle mode when waiting for uart activity. + * Make the Card Select pin MCU dependent as well. + * Add mini shell commands to documentation. + +2006-08-08 sd-reader + * Thanks go to Torsten Seeboth for his ongoing efforts + to test changes, fix regressions and give suggestions. + Many of the changes below were initiated by him. + * Much more reliable card initialization. + * Highly improved performance + - optional write buffering + - better cluster handling + - remove unneeded SPI access when reading from buffered block + - use highest spi frequency after card initialization + * Add superfloppy support. + * Better checks when opening a FAT16 filesystem. + * Provide SPI pin mappings for commonly used ATmegas. + * Fix resizing files, hangs could occur. + * Fix overflow when creating files with names longer than 31 characters. + * Fix numerous other small things. + +2006-03-19 sd-reader + * Fix speed regressions. + +2006-03-16 sd-reader + * Initial release. + diff --git a/sd_reader/Doxyfile b/sd_reader/Doxyfile new file mode 100644 index 0000000..703ef53 --- /dev/null +++ b/sd_reader/Doxyfile @@ -0,0 +1,1525 @@ +# Doxyfile 1.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = sd-reader + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class " \ + "The $name widget " \ + "The $name file " \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 0 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = NO + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = NO + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text " + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.c \ + *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = doc + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) +# there is already a search function so this one should typically +# be disabled. + +SEARCHENGINE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = DOXYGEN=1 + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/sd_reader/FAQ b/sd_reader/FAQ new file mode 100644 index 0000000..e709a6c --- /dev/null +++ b/sd_reader/FAQ @@ -0,0 +1,124 @@ +Frequently Asked Questions for sd-reader +======================================== + +General +------- + +Q: Which cards are supported? +A: All MMC/SD/SDHC/miniSD/microSD/microSDHC should work, although not all variants have been tested. + Some very old (low capacity) cards might be broken as well. Cards with a capacity of 16 MB or + less are usually formatted with FAT12, so these are supported in raw mode only, if at all. + +Q: What data rates can I expect? +A: See the benchmark page on the homepage. + http://www.roland-riegel.de/sd-reader/benchmarks/ + +Q: Are there boards available for purchase? +A: No. + +Hardware +-------- + +Q: Where can I find the schematic? +A: Get it on the homepage. + http://www.roland-riegel.de/sd-reader/sd-reader_circuit_latest.zip + +Q: What if my card socket has no Card-Detect and/or Card-Lock switches? +A: Change sd_raw_config.h such that it looks like + + #define configure_pin_available() /* nothing */ + #define configure_pin_locked() /* nothing */ + + #define get_pin_available() 0 + #define get_pin_locked() 1 + +Q: All attempts to write to the card fail, although reading works. What's the problem? +A: Enable write support within sd_raw_config.h. And probably, your card socket has no Card-lock + detection (see question above). + +Q: The card initialization fails. What can I do? +A: Usually this is some kind of hardware problem. + * Check the physical connections. + * Keep the signal lines to the card as short as possible. + * Do not use diodes to derive the card's supply voltage. Use a 3.3V voltage regulator instead. + * Have a stable, buffered power supply and use capacitors for correct voltage regulator + operation (see the schematics linked above). + * Use extra capacitors of 50uF and 100nF as close to the card as possible. + * When using an integrated level shifter or no level shifting at all (see the next question), + try adding a pullup of 50k from the data-out line of the card to 3.3V. + * Make sure the limiting frequency of the level shifter is not exceeded. Have a look into its + datasheet! + * Check the signals with a scope. + +Q: What alternatives to resistor level shifting exist? +A: If you want to use additional devices with SPI or the resistor solution appears too ugly, there + are two possibilities. Either operate the whole circuit with a single 3.3V supply and no level + shifting at all or use a level shifting IC which interfaces the memory card to the AVR. + Depending on your requirements, adequate devices could include MAX3378E, MAX3392E, MAX3395E, + 74LVC245 and 74HCT4050 (optionally together with 74HCT125). Please check the datasheets for the + required DC/AC characteristics! + +Software +-------- + +Q: What's the software license? +A: GPLv2 or (for most parts) LGPLv2.1. Before using a file, read its license terms included at the + beginning of the file. + +Q: What's the programming language used? +A: It's C, in compliance with the ISO C99 standard. + +Q: What are these .patch-files provided? +A: Those record the source code differences between the old and new release. If you edited your + private sd-reader version, it might be easier to apply the patch files using the "patch" utility + common on Unix-like systems, rather than manually inserting the changes by hand. For Windows + users, the GnuWin32 project provides a port of "patch". + +Q: Where can I learn more about the library interface and how to use it? +A: Look into the HTML documentation provided online or within each source distribution. Also take + the provided main.c as an example application and as a starting point. + +Q: How do I adapt it to an ATmegaXYZ and my circuit in particular? +A: Add your MCU-specific pin configuration to sd_raw_config.h. Some commonly used ones are already + included. + +Q: How do I adapt it to a different MCU clock? +A: Change the MCU_FREQ variable within the Makefile. + +Q: How do I adapt it to some different MCU architecture? +A: Change sd_raw_init(), sd_raw_send_byte(), sd_raw_rec_byte() within sd_raw.c and the pin + definitions within sd_raw_config.h appropriately. + +Q: Can the library be used with Arduino? +A: Yes. I do not have any experience with Arduino myself, but people report that it works with some + minor modifications to the library code needed due to some different compiler settings. Please + search the web for details. + +Q: Can I use software SPI? +A: Yes, but the code is not prepared for it. + +Q: My application crashes somewhere in the lib. Is there some bug hidden? +A: There might be a bug. Much more likely, however, is that you experience memory problems, + especially heap/stack collisions. The crashes can appear everywhere, but typically this is not + the place where the real problem is. Try to minimize the size of structures and other memory + blocks your application uses. Sum up the amount of memory your application allocates with global + and local variables and the memory you allocate dynamically with malloc(). The avr-nm utility + also helps a lot here. When called with the "--print-size --size-sort" parameters, it lists all + symbols and their code/memory size within the given file. See the avr-libc FAQ and the nm manual + for further information. + http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_ramoverlap + http://sourceware.org/binutils/docs/binutils/nm.html + +Q: Opening the FAT filesystem fails. What can I do? +A: Make sure there is a FAT16 or FAT32 filesystem on the card. This usually isn't possible for old + cards with 16 MB or less. For larger ones, format the cards using the OS utilities, like the + Windows Explorer, the Unix/Linux mkdosfs command or special SD card format tools. + http://panasonic.jp/support/global/cs/sd/download/sd_formatter.html + http://www.sdcard.org/consumers/formatter/ + +Q: Writing to the card returns no failure, but when checking the file's content the data is not + there. What happens? +A: For performance reasons, writing to the card is always buffered. Before pulling the card out of + its socket (or issuing a reset of the MCU), make sure sd_raw_sync() gets called such that all + buffered data is written out to permanent card storage. + diff --git a/sd_reader/Makefile b/sd_reader/Makefile new file mode 100644 index 0000000..bcfd475 --- /dev/null +++ b/sd_reader/Makefile @@ -0,0 +1,52 @@ + +NAME := sd-reader +HEX := $(NAME).hex +OUT := $(NAME).out +MAP := $(NAME).map +SOURCES := $(wildcard *.c) +HEADERS := $(wildcard *.h) +OBJECTS := $(patsubst %.c,%.o,$(SOURCES)) + +MCU := atmega328p +MCU_AVRDUDE := m328 +MCU_FREQ := 16000000UL + +CC := avr-gcc +OBJCOPY := avr-objcopy +SIZE := avr-size -A +DOXYGEN := doxygen + +CFLAGS := -Werror -Wall -pedantic -mmcu=$(MCU) -std=c99 -g -O3 -DF_CPU=$(MCU_FREQ) + +all: $(HEX) + +clean: + rm -f $(HEX) $(OUT) $(MAP) $(OBJECTS) + rm -rf doc/html + +flash: $(HEX) + avrdude -y -c avr910 -p $(MCU_AVRDUDE) -U flash:w:$(HEX) + +$(HEX): $(OUT) + $(OBJCOPY) -R .eeprom -O ihex $< $@ + +$(OUT): $(OBJECTS) + $(CC) $(CFLAGS) -o $@ -Wl,-Map,$(MAP) $^ + @echo + @$(SIZE) $@ + @echo + +%.o: %.c $(HEADERS) + $(CC) $(CFLAGS) -c -o $@ $< + +%.pp: %.c + $(CC) $(CFLAGS) -E -o $@ $< + +%.ppo: %.c + $(CC) $(CFLAGS) -E $< + +doc: $(HEADERS) $(SOURCES) Doxyfile + $(DOXYGEN) Doxyfile + +.PHONY: all clean flash doc + diff --git a/sd_reader/byteordering.c b/sd_reader/byteordering.c new file mode 100644 index 0000000..d1c67cd --- /dev/null +++ b/sd_reader/byteordering.c @@ -0,0 +1,110 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "byteordering.h" + +/** + * \addtogroup byteordering + * + * Architecture-dependent handling of byte-ordering. + * + * @{ + */ +/** + * \file + * Byte-order handling implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +#if DOXYGEN || SWAP_NEEDED + +/** + * \internal + * Swaps the bytes of a 16-bit integer. + * + * \param[in] i A 16-bit integer which to swap. + * \returns The swapped 16-bit integer. + */ +uint16_t swap16(uint16_t i) +{ + return SWAP16(i); +} + +/** + * \internal + * Swaps the bytes of a 32-bit integer. + * + * \param[in] i A 32-bit integer which to swap. + * \returns The swapped 32-bit integer. + */ +uint32_t swap32(uint32_t i) +{ + return SWAP32(i); +} + +#endif + +/** + * Reads a 16-bit integer from memory in little-endian byte order. + * + * \param[in] p Pointer from where to read the integer. + * \returns The 16-bit integer read from memory. + */ +uint16_t read16(const uint8_t* p) +{ + return (((uint16_t) p[1]) << 8) | + (((uint16_t) p[0]) << 0); +} + +/** + * Reads a 32-bit integer from memory in little-endian byte order. + * + * \param[in] p Pointer from where to read the integer. + * \returns The 32-bit integer read from memory. + */ +uint32_t read32(const uint8_t* p) +{ + return (((uint32_t) p[3]) << 24) | + (((uint32_t) p[2]) << 16) | + (((uint32_t) p[1]) << 8) | + (((uint32_t) p[0]) << 0); +} + +/** + * Writes a 16-bit integer into memory in little-endian byte order. + * + * \param[in] p Pointer where to write the integer to. + * \param[in] i The 16-bit integer to write. + */ +void write16(uint8_t* p, uint16_t i) +{ + p[1] = (uint8_t) ((i & 0xff00) >> 8); + p[0] = (uint8_t) ((i & 0x00ff) >> 0); +} + +/** + * Writes a 32-bit integer into memory in little-endian byte order. + * + * \param[in] p Pointer where to write the integer to. + * \param[in] i The 32-bit integer to write. + */ +void write32(uint8_t* p, uint32_t i) +{ + p[3] = (uint8_t) ((i & 0xff000000) >> 24); + p[2] = (uint8_t) ((i & 0x00ff0000) >> 16); + p[1] = (uint8_t) ((i & 0x0000ff00) >> 8); + p[0] = (uint8_t) ((i & 0x000000ff) >> 0); +} + +/** + * @} + */ + diff --git a/sd_reader/byteordering.h b/sd_reader/byteordering.h new file mode 100644 index 0000000..12fa571 --- /dev/null +++ b/sd_reader/byteordering.h @@ -0,0 +1,188 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef BYTEORDERING_H +#define BYTEORDERING_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup byteordering + * + * @{ + */ +/** + * \file + * Byte-order handling header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +#define SWAP16(val) ((((uint16_t) (val)) << 8) | \ + (((uint16_t) (val)) >> 8) \ + ) +#define SWAP32(val) (((((uint32_t) (val)) & 0x000000ff) << 24) | \ + ((((uint32_t) (val)) & 0x0000ff00) << 8) | \ + ((((uint32_t) (val)) & 0x00ff0000) >> 8) | \ + ((((uint32_t) (val)) & 0xff000000) >> 24) \ + ) + +#if LITTLE_ENDIAN || __AVR__ +#define SWAP_NEEDED 0 +#elif BIG_ENDIAN +#define SWAP_NEEDED 1 +#else +#error "Endianess undefined! Please define LITTLE_ENDIAN=1 or BIG_ENDIAN=1." +#endif + +/** + * \def HTOL16(val) + * + * Converts a 16-bit integer from host byte order to little-endian byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function htol16() instead. This saves code size. + * + * \param[in] val A 16-bit integer in host byte order. + * \returns The given 16-bit integer converted to little-endian byte order. + */ +/** + * \def HTOL32(val) + * + * Converts a 32-bit integer from host byte order to little-endian byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function htol32() instead. This saves code size. + * + * \param[in] val A 32-bit integer in host byte order. + * \returns The given 32-bit integer converted to little-endian byte order. + */ +/** + * \def LTOH16(val) + * + * Converts a 16-bit integer from little-endian byte order to host byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function ltoh16() instead. This saves code size. + * + * \param[in] val A 16-bit integer in little-endian byte order. + * \returns The given 16-bit integer converted to host byte order. + */ +/** + * \def LTOH32(val) + * + * Converts a 32-bit integer from little-endian byte order to host byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function ltoh32() instead. This saves code size. + * + * \param[in] val A 32-bit integer in little-endian byte order. + * \returns The given 32-bit integer converted to host byte order. + */ + +#if SWAP_NEEDED +#define HTOL16(val) SWAP16(val) +#define HTOL32(val) SWAP32(val) +#define LTOH16(val) SWAP16(val) +#define LTOH32(val) SWAP32(val) +#else +#define HTOL16(val) (val) +#define HTOL32(val) (val) +#define LTOH16(val) (val) +#define LTOH32(val) (val) +#endif + +#if DOXYGEN + +/** + * Converts a 16-bit integer from host byte order to little-endian byte order. + * + * Use this function on variable values instead of the + * macro HTOL16(). This saves code size. + * + * \param[in] h A 16-bit integer in host byte order. + * \returns The given 16-bit integer converted to little-endian byte order. + */ +uint16_t htol16(uint16_t h); + +/** + * Converts a 32-bit integer from host byte order to little-endian byte order. + * + * Use this function on variable values instead of the + * macro HTOL32(). This saves code size. + * + * \param[in] h A 32-bit integer in host byte order. + * \returns The given 32-bit integer converted to little-endian byte order. + */ +uint32_t htol32(uint32_t h); + +/** + * Converts a 16-bit integer from little-endian byte order to host byte order. + * + * Use this function on variable values instead of the + * macro LTOH16(). This saves code size. + * + * \param[in] l A 16-bit integer in little-endian byte order. + * \returns The given 16-bit integer converted to host byte order. + */ +uint16_t ltoh16(uint16_t l); + +/** + * Converts a 32-bit integer from little-endian byte order to host byte order. + * + * Use this function on variable values instead of the + * macro LTOH32(). This saves code size. + * + * \param[in] l A 32-bit integer in little-endian byte order. + * \returns The given 32-bit integer converted to host byte order. + */ +uint32_t ltoh32(uint32_t l); + +#elif SWAP_NEEDED + +#define htol16(h) swap16(h) +#define htol32(h) swap32(h) +#define ltoh16(l) swap16(l) +#define ltoh32(l) swap32(l) + +#else + +#define htol16(h) (h) +#define htol32(h) (h) +#define ltoh16(l) (l) +#define ltoh32(l) (l) + +#endif + +uint16_t read16(const uint8_t* p); +uint32_t read32(const uint8_t* p); +void write16(uint8_t* p, uint16_t i); +void write32(uint8_t* p, uint32_t i); + +/** + * @} + */ + +#if SWAP_NEEDED +uint16_t swap16(uint16_t i); +uint32_t swap32(uint32_t i); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/byteordering.o b/sd_reader/byteordering.o new file mode 100644 index 0000000000000000000000000000000000000000..c28ef63b49e8aef33edbff3fdef48ed87011f0ab GIT binary patch literal 5880 zcma)=TWnNC7{{ly+yoOui73RZNJ1CNUe4KWOP5OtN7nCJugz=JU+YKRH?VoZ#QVj{+vSi*})Qorw;+qY-8=p?)IJ2U_J z=DW;Z&N;cgdq+b;2E&z*1WtYf@~@d)F07=O+9 zCgacoYqOW}0PrbQ-!aDTGQL9k7fJOk@d@JJh))vVA)X*!x)Ar8Bwj--ci2RHn#OyG z&kzq1gpxZxH`V{3r1Y@lE0t@cEn&w~3R)@(ZDdco}?}geVd}OpMnb;G1go zcoOuh#U@g`0^A9_T zenLRl)X@H7#oGR7Wd9(IUL*SlXR&`stwj4zF+MDs)KxwIe{n>;SlK%g-M(#m+KoHWQYIViFAir)=@GB|Tp{O2EBUln$bg-w2$&qm<(zb` zJTy`)74i{L9QHf>=2%NSyvd_3wcd5wlI+@f~W6k!81y@7mXMPuetqB2iwB%qkCmijXDnJR9X_SS!UJ`Y9X257!U1nF0zb~IvQtl7@d`3|RO zJELSgE4SY0O_NHcdb-J9`4wGP@`lY^w<&e?>Mb2MM$SRm_G zRHL-%r_7Pk#8K;=t%mi^$hn)gBGyeK40_?(s?aH__M&b!U0Wn_T)Db62Ve4ZGF@97 z@<8s%<^$zmyD*WhM%{zOl!3aKSkp`sD2vh5_!g*2wXjBQi(+e7n~!=GVa_G$)gZW4 zB9Qgq!LqR?Z2GNJCXO2K61Ez3&I@Vi?&7*kyx}ioGXpy3(<<7hu_r*)1jtj7BuZ?{8DuB z&jkLLaKd7gjY39o)C~*#nNBJ#Bo0TV)Qm!w&{vYWj*>z$U=Y@$RWl0l!e}c=lL;l2 zv9?R88HIxe(hVdX@ja!5g9Sb-vvwGN+F)GzkS-N66&oK0h+&c^fjJD*ZGqUqNjYVr zg4o$s!AXp{dOs6m97A_8NDN&rw3C!zd*1^#fLK{JpAE-DV0ewXy;^FR5o#$@gIL!V3`iCdQK)q#fDSHdq?I(hgLl& zQz9c9%8aW@J5||W<0cXKr#YVN*a7~4t0Nuz@D#%ATI?YyM`Y+Ia~+a_YepuHc}2fK z>*WVc&a^_x*2;w>pp^lE5IeVR>j*XP+Tj+b{NwN4m)@05OX!x+vwbIlKQsb2 zK?B6k7re5%kR=wfgt}s~cOA8V!yEu;A$VzSg77r2WZVoKQu+krPQ@5sx6&gYVm!k5 z1*U(A@j1myVeNa0F-Q393H?F-l<_sjKQjFd#h~zVVMntle ziimiHQU-KVoymeuMBwHY?~}po|86TliRW71vnPPjFWvN+e-s9g;gKP=XH>5{ty=>F zSnL1N#X+OjpbfNl4sxOWfHKk9Z-E}U6JFT-`-PZ*h46Ta#g07Zra|za^FVi literal 0 HcmV?d00001 diff --git a/sd_reader/comms.h b/sd_reader/comms.h new file mode 120000 index 0000000..ad5c9e6 --- /dev/null +++ b/sd_reader/comms.h @@ -0,0 +1 @@ +../comms/comms.h \ No newline at end of file diff --git a/sd_reader/doc/pic01.jpg b/sd_reader/doc/pic01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7eb93831eed29213eb85e2edf72455a5fcd5b981 GIT binary patch literal 35891 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 R4@&hK@(;|1XpVHR|JiObPE7y+ literal 0 HcmV?d00001 diff --git a/sd_reader/doc/pic02.jpg b/sd_reader/doc/pic02.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e490c4d87467b9a932a074135cc77e77c420ca00 GIT binary patch literal 31797 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 b$@i~QdpII64*;)I$HgrJVfTp2ji>+FbZKBJ literal 0 HcmV?d00001 diff --git a/sd_reader/fat.c b/sd_reader/fat.c new file mode 100644 index 0000000..e62f4cf --- /dev/null +++ b/sd_reader/fat.c @@ -0,0 +1,2551 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "byteordering.h" +#include "partition.h" +#include "fat.h" +#include "fat_config.h" +#include "sd-reader_config.h" + +#include + +#if USE_DYNAMIC_MEMORY + #include +#endif + +/** + * \addtogroup fat FAT support + * + * This module implements FAT16/FAT32 read and write access. + * + * The following features are supported: + * - File names up to 31 characters long. + * - Unlimited depth of subdirectories. + * - Short 8.3 and long filenames. + * - Creating and deleting files. + * - Reading and writing from and to files. + * - File resizing. + * - File sizes of up to 4 gigabytes. + * + * @{ + */ +/** + * \file + * FAT implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup fat_config FAT configuration + * Preprocessor defines to configure the FAT implementation. + */ + +/** + * \addtogroup fat_fs FAT access + * Basic functions for handling a FAT filesystem. + */ + +/** + * \addtogroup fat_file FAT file functions + * Functions for managing files. + */ + +/** + * \addtogroup fat_dir FAT directory functions + * Functions for managing directories. + */ + +/** + * @} + */ + +#define FAT16_CLUSTER_FREE 0x0000 +#define FAT16_CLUSTER_RESERVED_MIN 0xfff0 +#define FAT16_CLUSTER_RESERVED_MAX 0xfff6 +#define FAT16_CLUSTER_BAD 0xfff7 +#define FAT16_CLUSTER_LAST_MIN 0xfff8 +#define FAT16_CLUSTER_LAST_MAX 0xffff + +#define FAT32_CLUSTER_FREE 0x00000000 +#define FAT32_CLUSTER_RESERVED_MIN 0x0ffffff0 +#define FAT32_CLUSTER_RESERVED_MAX 0x0ffffff6 +#define FAT32_CLUSTER_BAD 0x0ffffff7 +#define FAT32_CLUSTER_LAST_MIN 0x0ffffff8 +#define FAT32_CLUSTER_LAST_MAX 0x0fffffff + +#define FAT_DIRENTRY_DELETED 0xe5 +#define FAT_DIRENTRY_LFNLAST (1 << 6) +#define FAT_DIRENTRY_LFNSEQMASK ((1 << 6) - 1) + +/* Each entry within the directory table has a size of 32 bytes + * and either contains a 8.3 DOS-style file name or a part of a + * long file name, which may consist of several directory table + * entries at once. + * + * multi-byte integer values are stored little-endian! + * + * 8.3 file name entry: + * ==================== + * offset length description + * 0 8 name (space padded) + * 8 3 extension (space padded) + * 11 1 attributes (FAT_ATTRIB_*) + * + * long file name (lfn) entry ordering for a single file name: + * =========================================================== + * LFN entry n + * ... + * LFN entry 2 + * LFN entry 1 + * 8.3 entry (see above) + * + * lfn entry: + * ========== + * offset length description + * 0 1 ordinal field + * 1 2 unicode character 1 + * 3 3 unicode character 2 + * 5 3 unicode character 3 + * 7 3 unicode character 4 + * 9 3 unicode character 5 + * 11 1 attribute (always 0x0f) + * 12 1 type (reserved, always 0) + * 13 1 checksum + * 14 2 unicode character 6 + * 16 2 unicode character 7 + * 18 2 unicode character 8 + * 20 2 unicode character 9 + * 22 2 unicode character 10 + * 24 2 unicode character 11 + * 26 2 cluster (unused, always 0) + * 28 2 unicode character 12 + * 30 2 unicode character 13 + * + * The ordinal field contains a descending number, from n to 1. + * For the n'th lfn entry the ordinal field is or'ed with 0x40. + * For deleted lfn entries, the ordinal field is set to 0xe5. + */ + +struct fat_header_struct +{ + offset_t size; + + offset_t fat_offset; + uint32_t fat_size; + + uint16_t sector_size; + uint16_t cluster_size; + + offset_t cluster_zero_offset; + + offset_t root_dir_offset; +#if FAT_FAT32_SUPPORT + cluster_t root_dir_cluster; +#endif +}; + +struct fat_fs_struct +{ + struct partition_struct* partition; + struct fat_header_struct header; + cluster_t cluster_free; +}; + +struct fat_file_struct +{ + struct fat_fs_struct* fs; + struct fat_dir_entry_struct dir_entry; + offset_t pos; + cluster_t pos_cluster; +}; + +struct fat_dir_struct +{ + struct fat_fs_struct* fs; + struct fat_dir_entry_struct dir_entry; + cluster_t entry_cluster; + uint16_t entry_offset; +}; + +struct fat_read_dir_callback_arg +{ + struct fat_dir_entry_struct* dir_entry; + uintptr_t bytes_read; +#if FAT_LFN_SUPPORT + uint8_t checksum; +#endif + uint8_t finished; +}; + +struct fat_usage_count_callback_arg +{ + cluster_t cluster_count; + uintptr_t buffer_size; +}; + +#if !USE_DYNAMIC_MEMORY +static struct fat_fs_struct fat_fs_handles[FAT_FS_COUNT]; +static struct fat_file_struct fat_file_handles[FAT_FILE_COUNT]; +static struct fat_dir_struct fat_dir_handles[FAT_DIR_COUNT]; +#endif + +static uint8_t fat_read_header(struct fat_fs_struct* fs); +static cluster_t fat_get_next_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num); +static offset_t fat_cluster_offset(const struct fat_fs_struct* fs, cluster_t cluster_num); +static uint8_t fat_dir_entry_read_callback(uint8_t* buffer, offset_t offset, void* p); +#if FAT_LFN_SUPPORT +static uint8_t fat_calc_83_checksum(const uint8_t* file_name_83); +#endif + +static uint8_t fat_get_fs_free_16_callback(uint8_t* buffer, offset_t offset, void* p); +#if FAT_FAT32_SUPPORT +static uint8_t fat_get_fs_free_32_callback(uint8_t* buffer, offset_t offset, void* p); +#endif + +#if FAT_WRITE_SUPPORT +static cluster_t fat_append_clusters(struct fat_fs_struct* fs, cluster_t cluster_num, cluster_t count); +static uint8_t fat_free_clusters(struct fat_fs_struct* fs, cluster_t cluster_num); +static uint8_t fat_terminate_clusters(struct fat_fs_struct* fs, cluster_t cluster_num); +static uint8_t fat_clear_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num); +static uintptr_t fat_clear_cluster_callback(uint8_t* buffer, offset_t offset, void* p); +static offset_t fat_find_offset_for_dir_entry(struct fat_fs_struct* fs, const struct fat_dir_struct* parent, const struct fat_dir_entry_struct* dir_entry); +static uint8_t fat_write_dir_entry(const struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry); +#if FAT_DATETIME_SUPPORT +static void fat_set_file_modification_date(struct fat_dir_entry_struct* dir_entry, uint16_t year, uint8_t month, uint8_t day); +static void fat_set_file_modification_time(struct fat_dir_entry_struct* dir_entry, uint8_t hour, uint8_t min, uint8_t sec); +#endif +#endif + +/** + * \ingroup fat_fs + * Opens a FAT filesystem. + * + * \param[in] partition Discriptor of partition on which the filesystem resides. + * \returns 0 on error, a FAT filesystem descriptor on success. + * \see fat_close + */ +struct fat_fs_struct* fat_open(struct partition_struct* partition) +{ + if(!partition || +#if FAT_WRITE_SUPPORT + !partition->device_write || + !partition->device_write_interval +#else + 0 +#endif + ) + return 0; + +#if USE_DYNAMIC_MEMORY + struct fat_fs_struct* fs = malloc(sizeof(*fs)); + if(!fs) + return 0; +#else + struct fat_fs_struct* fs = fat_fs_handles; + uint8_t i; + for(i = 0; i < FAT_FS_COUNT; ++i) + { + if(!fs->partition) + break; + + ++fs; + } + if(i >= FAT_FS_COUNT) + return 0; +#endif + + memset(fs, 0, sizeof(*fs)); + + fs->partition = partition; + if(!fat_read_header(fs)) + { +#if USE_DYNAMIC_MEMORY + free(fs); +#else + fs->partition = 0; +#endif + return 0; + } + + return fs; +} + +/** + * \ingroup fat_fs + * Closes a FAT filesystem. + * + * When this function returns, the given filesystem descriptor + * will be invalid. + * + * \param[in] fs The filesystem to close. + * \see fat_open + */ +void fat_close(struct fat_fs_struct* fs) +{ + if(!fs) + return; + +#if USE_DYNAMIC_MEMORY + free(fs); +#else + fs->partition = 0; +#endif +} + +/** + * \ingroup fat_fs + * Reads and parses the header of a FAT filesystem. + * + * \param[in,out] fs The filesystem for which to parse the header. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_read_header(struct fat_fs_struct* fs) +{ + if(!fs) + return 0; + + struct partition_struct* partition = fs->partition; + if(!partition) + return 0; + + /* read fat parameters */ +#if FAT_FAT32_SUPPORT + uint8_t buffer[37]; +#else + uint8_t buffer[25]; +#endif + offset_t partition_offset = (offset_t) partition->offset * 512; + if(!partition->device_read(partition_offset + 0x0b, buffer, sizeof(buffer))) + return 0; + + uint16_t bytes_per_sector = read16(&buffer[0x00]); + uint16_t reserved_sectors = read16(&buffer[0x03]); + uint8_t sectors_per_cluster = buffer[0x02]; + uint8_t fat_copies = buffer[0x05]; + uint16_t max_root_entries = read16(&buffer[0x06]); + uint16_t sector_count_16 = read16(&buffer[0x08]); + uint16_t sectors_per_fat = read16(&buffer[0x0b]); + uint32_t sector_count = read32(&buffer[0x15]); +#if FAT_FAT32_SUPPORT + uint32_t sectors_per_fat32 = read32(&buffer[0x19]); + uint32_t cluster_root_dir = read32(&buffer[0x21]); +#endif + + if(sector_count == 0) + { + if(sector_count_16 == 0) + /* illegal volume size */ + return 0; + else + sector_count = sector_count_16; + } +#if FAT_FAT32_SUPPORT + if(sectors_per_fat != 0) + sectors_per_fat32 = sectors_per_fat; + else if(sectors_per_fat32 == 0) + /* this is neither FAT16 nor FAT32 */ + return 0; +#else + if(sectors_per_fat == 0) + /* this is not a FAT16 */ + return 0; +#endif + + /* determine the type of FAT we have here */ + uint32_t data_sector_count = sector_count + - reserved_sectors +#if FAT_FAT32_SUPPORT + - sectors_per_fat32 * fat_copies +#else + - (uint32_t) sectors_per_fat * fat_copies +#endif + - ((max_root_entries * 32 + bytes_per_sector - 1) / bytes_per_sector); + uint32_t data_cluster_count = data_sector_count / sectors_per_cluster; + if(data_cluster_count < 4085) + /* this is a FAT12, not supported */ + return 0; + else if(data_cluster_count < 65525) + /* this is a FAT16 */ + partition->type = PARTITION_TYPE_FAT16; + else + /* this is a FAT32 */ + partition->type = PARTITION_TYPE_FAT32; + + /* fill header information */ + struct fat_header_struct* header = &fs->header; + memset(header, 0, sizeof(*header)); + + header->size = (offset_t) sector_count * bytes_per_sector; + + header->fat_offset = /* jump to partition */ + partition_offset + + /* jump to fat */ + (offset_t) reserved_sectors * bytes_per_sector; + header->fat_size = (data_cluster_count + 2) * (partition->type == PARTITION_TYPE_FAT16 ? 2 : 4); + + header->sector_size = bytes_per_sector; + header->cluster_size = (uint16_t) bytes_per_sector * sectors_per_cluster; + +#if FAT_FAT32_SUPPORT + if(partition->type == PARTITION_TYPE_FAT16) +#endif + { + header->root_dir_offset = /* jump to fats */ + header->fat_offset + + /* jump to root directory entries */ + (offset_t) fat_copies * sectors_per_fat * bytes_per_sector; + + header->cluster_zero_offset = /* jump to root directory entries */ + header->root_dir_offset + + /* skip root directory entries */ + (offset_t) max_root_entries * 32; + } +#if FAT_FAT32_SUPPORT + else + { + header->cluster_zero_offset = /* jump to fats */ + header->fat_offset + + /* skip fats */ + (offset_t) fat_copies * sectors_per_fat32 * bytes_per_sector; + + header->root_dir_cluster = cluster_root_dir; + } +#endif + + return 1; +} + +/** + * \ingroup fat_fs + * Retrieves the next following cluster of a given cluster. + * + * Using the filesystem file allocation table, this function returns + * the number of the cluster containing the data directly following + * the data within the cluster with the given number. + * + * \param[in] fs The filesystem for which to determine the next cluster. + * \param[in] cluster_num The number of the cluster for which to determine its successor. + * \returns The wanted cluster number, or 0 on error. + */ +cluster_t fat_get_next_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + { + /* read appropriate fat entry */ + uint32_t fat_entry; + if(!fs->partition->device_read(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* determine next cluster from fat */ + cluster_num = ltoh32(fat_entry); + + if(cluster_num == FAT32_CLUSTER_FREE || + cluster_num == FAT32_CLUSTER_BAD || + (cluster_num >= FAT32_CLUSTER_RESERVED_MIN && cluster_num <= FAT32_CLUSTER_RESERVED_MAX) || + (cluster_num >= FAT32_CLUSTER_LAST_MIN && cluster_num <= FAT32_CLUSTER_LAST_MAX)) + return 0; + } + else +#endif + { + /* read appropriate fat entry */ + uint16_t fat_entry; + if(!fs->partition->device_read(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* determine next cluster from fat */ + cluster_num = ltoh16(fat_entry); + + if(cluster_num == FAT16_CLUSTER_FREE || + cluster_num == FAT16_CLUSTER_BAD || + (cluster_num >= FAT16_CLUSTER_RESERVED_MIN && cluster_num <= FAT16_CLUSTER_RESERVED_MAX) || + (cluster_num >= FAT16_CLUSTER_LAST_MIN && cluster_num <= FAT16_CLUSTER_LAST_MAX)) + return 0; + } + + return cluster_num; +} + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Appends a new cluster chain to an existing one. + * + * Set cluster_num to zero to create a completely new one. + * + * \param[in] fs The file system on which to operate. + * \param[in] cluster_num The cluster to which to append the new chain. + * \param[in] count The number of clusters to allocate. + * \returns 0 on failure, the number of the first new cluster on success. + */ +cluster_t fat_append_clusters(struct fat_fs_struct* fs, cluster_t cluster_num, cluster_t count) +{ + if(!fs) + return 0; + + device_read_t device_read = fs->partition->device_read; + device_write_t device_write = fs->partition->device_write; + offset_t fat_offset = fs->header.fat_offset; + cluster_t count_left = count; + cluster_t cluster_current = fs->cluster_free; + cluster_t cluster_next = 0; + cluster_t cluster_count; + uint16_t fat_entry16; +#if FAT_FAT32_SUPPORT + uint32_t fat_entry32; + uint8_t is_fat32 = (fs->partition->type == PARTITION_TYPE_FAT32); + + if(is_fat32) + cluster_count = fs->header.fat_size / sizeof(fat_entry32); + else +#endif + cluster_count = fs->header.fat_size / sizeof(fat_entry16); + + fs->cluster_free = 0; + for(cluster_t cluster_left = cluster_count; cluster_left > 0; --cluster_left, ++cluster_current) + { + if(cluster_current < 2 || cluster_current >= cluster_count) + cluster_current = 2; + +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + if(!device_read(fat_offset + (offset_t) cluster_current * sizeof(fat_entry32), (uint8_t*) &fat_entry32, sizeof(fat_entry32))) + return 0; + } + else +#endif + { + if(!device_read(fat_offset + (offset_t) cluster_current * sizeof(fat_entry16), (uint8_t*) &fat_entry16, sizeof(fat_entry16))) + return 0; + } + +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + /* check if this is a free cluster */ + if(fat_entry32 != HTOL32(FAT32_CLUSTER_FREE)) + continue; + + /* If we don't need this free cluster for the + * current allocation, we keep it in mind for + * the next time. + */ + if(count_left == 0) + { + fs->cluster_free = cluster_current; + break; + } + + /* allocate cluster */ + if(cluster_next == 0) + fat_entry32 = HTOL32(FAT32_CLUSTER_LAST_MAX); + else + fat_entry32 = htol32(cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_current * sizeof(fat_entry32), (uint8_t*) &fat_entry32, sizeof(fat_entry32))) + break; + } + else +#endif + { + /* check if this is a free cluster */ + if(fat_entry16 != HTOL16(FAT16_CLUSTER_FREE)) + continue; + + /* If we don't need this free cluster for the + * current allocation, we keep it in mind for + * the next time. + */ + if(count_left == 0) + { + fs->cluster_free = cluster_current; + break; + } + + /* allocate cluster */ + if(cluster_next == 0) + fat_entry16 = HTOL16(FAT16_CLUSTER_LAST_MAX); + else + fat_entry16 = htol16((uint16_t) cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_current * sizeof(fat_entry16), (uint8_t*) &fat_entry16, sizeof(fat_entry16))) + break; + } + + cluster_next = cluster_current; + --count_left; + } + + do + { + if(count_left > 0) + break; + + /* We allocated a new cluster chain. Now join + * it with the existing one (if any). + */ + if(cluster_num >= 2) + { +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + fat_entry32 = htol32(cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry32), (uint8_t*) &fat_entry32, sizeof(fat_entry32))) + break; + } + else +#endif + { + fat_entry16 = htol16((uint16_t) cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry16), (uint8_t*) &fat_entry16, sizeof(fat_entry16))) + break; + } + } + + return cluster_next; + + } while(0); + + /* No space left on device or writing error. + * Free up all clusters already allocated. + */ + fat_free_clusters(fs, cluster_next); + + return 0; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Frees a cluster chain, or a part thereof. + * + * Marks the specified cluster and all clusters which are sequentially + * referenced by it as free. They may then be used again for future + * file allocations. + * + * \note If this function is used for freeing just a part of a cluster + * chain, the new end of the chain is not correctly terminated + * within the FAT. Use fat_terminate_clusters() instead. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The starting cluster of the chain which to free. + * \returns 0 on failure, 1 on success. + * \see fat_terminate_clusters + */ +uint8_t fat_free_clusters(struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + + offset_t fat_offset = fs->header.fat_offset; +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + { + uint32_t fat_entry; + while(cluster_num) + { + if(!fs->partition->device_read(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* get next cluster of current cluster before freeing current cluster */ + uint32_t cluster_num_next = ltoh32(fat_entry); + + if(cluster_num_next == FAT32_CLUSTER_FREE) + return 1; + if(cluster_num_next == FAT32_CLUSTER_BAD || + (cluster_num_next >= FAT32_CLUSTER_RESERVED_MIN && + cluster_num_next <= FAT32_CLUSTER_RESERVED_MAX + ) + ) + return 0; + if(cluster_num_next >= FAT32_CLUSTER_LAST_MIN && cluster_num_next <= FAT32_CLUSTER_LAST_MAX) + cluster_num_next = 0; + + /* We know we will free the cluster, so remember it as + * free for the next allocation. + */ + if(!fs->cluster_free) + fs->cluster_free = cluster_num; + + /* free cluster */ + fat_entry = HTOL32(FAT32_CLUSTER_FREE); + fs->partition->device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry)); + + /* We continue in any case here, even if freeing the cluster failed. + * The cluster is lost, but maybe we can still free up some later ones. + */ + + cluster_num = cluster_num_next; + } + } + else +#endif + { + uint16_t fat_entry; + while(cluster_num) + { + if(!fs->partition->device_read(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* get next cluster of current cluster before freeing current cluster */ + uint16_t cluster_num_next = ltoh16(fat_entry); + + if(cluster_num_next == FAT16_CLUSTER_FREE) + return 1; + if(cluster_num_next == FAT16_CLUSTER_BAD || + (cluster_num_next >= FAT16_CLUSTER_RESERVED_MIN && + cluster_num_next <= FAT16_CLUSTER_RESERVED_MAX + ) + ) + return 0; + if(cluster_num_next >= FAT16_CLUSTER_LAST_MIN && cluster_num_next <= FAT16_CLUSTER_LAST_MAX) + cluster_num_next = 0; + + /* free cluster */ + fat_entry = HTOL16(FAT16_CLUSTER_FREE); + fs->partition->device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry)); + + /* We continue in any case here, even if freeing the cluster failed. + * The cluster is lost, but maybe we can still free up some later ones. + */ + + cluster_num = cluster_num_next; + } + } + + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Frees a part of a cluster chain and correctly terminates the rest. + * + * Marks the specified cluster as the new end of a cluster chain and + * frees all following clusters. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The new end of the cluster chain. + * \returns 0 on failure, 1 on success. + * \see fat_free_clusters + */ +uint8_t fat_terminate_clusters(struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + + /* fetch next cluster before overwriting the cluster entry */ + cluster_t cluster_num_next = fat_get_next_cluster(fs, cluster_num); + + /* mark cluster as the last one */ +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + { + uint32_t fat_entry = HTOL32(FAT32_CLUSTER_LAST_MAX); + if(!fs->partition->device_write(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + } + else +#endif + { + uint16_t fat_entry = HTOL16(FAT16_CLUSTER_LAST_MAX); + if(!fs->partition->device_write(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + } + + /* free remaining clusters */ + if(cluster_num_next) + return fat_free_clusters(fs, cluster_num_next); + else + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Clears a single cluster. + * + * The complete cluster is filled with zeros. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The cluster to clear. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_clear_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(cluster_num < 2) + return 0; + + offset_t cluster_offset = fat_cluster_offset(fs, cluster_num); + + uint8_t zero[16]; + memset(zero, 0, sizeof(zero)); + return fs->partition->device_write_interval(cluster_offset, + zero, + fs->header.cluster_size, + fat_clear_cluster_callback, + 0 + ); +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Callback function for clearing a cluster. + */ +uintptr_t fat_clear_cluster_callback(uint8_t* buffer, offset_t offset, void* p) +{ + return 16; +} +#endif + +/** + * \ingroup fat_fs + * Calculates the offset of the specified cluster. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The cluster whose offset to calculate. + * \returns The cluster offset. + */ +offset_t fat_cluster_offset(const struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + + return fs->header.cluster_zero_offset + (offset_t) (cluster_num - 2) * fs->header.cluster_size; +} + +/** + * \ingroup fat_file + * Retrieves the directory entry of a path. + * + * The given path may both describe a file or a directory. + * + * \param[in] fs The FAT filesystem on which to search. + * \param[in] path The path of which to read the directory entry. + * \param[out] dir_entry The directory entry to fill. + * \returns 0 on failure, 1 on success. + * \see fat_read_dir + */ +uint8_t fat_get_dir_entry_of_path(struct fat_fs_struct* fs, const char* path, struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !path || path[0] == '\0' || !dir_entry) + return 0; + + if(path[0] == '/') + ++path; + + /* begin with the root directory */ + memset(dir_entry, 0, sizeof(*dir_entry)); + dir_entry->attributes = FAT_ATTRIB_DIR; + + while(1) + { + if(path[0] == '\0') + return 1; + + struct fat_dir_struct* dd = fat_open_dir(fs, dir_entry); + if(!dd) + break; + + /* extract the next hierarchy we will search for */ + const char* sub_path = strchr(path, '/'); + uint8_t length_to_sep; + if(sub_path) + { + length_to_sep = sub_path - path; + ++sub_path; + } + else + { + length_to_sep = strlen(path); + sub_path = path + length_to_sep; + } + + /* read directory entries */ + while(fat_read_dir(dd, dir_entry)) + { + /* check if we have found the next hierarchy */ + if((strlen(dir_entry->long_name) != length_to_sep || + strncmp(path, dir_entry->long_name, length_to_sep) != 0)) + continue; + + fat_close_dir(dd); + dd = 0; + + if(path[length_to_sep] == '\0') + /* we iterated through the whole path and have found the file */ + return 1; + + if(dir_entry->attributes & FAT_ATTRIB_DIR) + { + /* we found a parent directory of the file we are searching for */ + path = sub_path; + break; + } + + /* a parent of the file exists, but not the file itself */ + return 0; + } + + fat_close_dir(dd); + } + + return 0; +} + +/** + * \ingroup fat_file + * Opens a file on a FAT filesystem. + * + * \param[in] fs The filesystem on which the file to open lies. + * \param[in] dir_entry The directory entry of the file to open. + * \returns The file handle, or 0 on failure. + * \see fat_close_file + */ +struct fat_file_struct* fat_open_file(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry || (dir_entry->attributes & FAT_ATTRIB_DIR)) + return 0; + +#if USE_DYNAMIC_MEMORY + struct fat_file_struct* fd = malloc(sizeof(*fd)); + if(!fd) + return 0; +#else + struct fat_file_struct* fd = fat_file_handles; + uint8_t i; + for(i = 0; i < FAT_FILE_COUNT; ++i) + { + if(!fd->fs) + break; + + ++fd; + } + if(i >= FAT_FILE_COUNT) + return 0; +#endif + + memcpy(&fd->dir_entry, dir_entry, sizeof(*dir_entry)); + fd->fs = fs; + fd->pos = 0; + fd->pos_cluster = dir_entry->cluster; + + return fd; +} + +/** + * \ingroup fat_file + * Closes a file. + * + * \param[in] fd The file handle of the file to close. + * \see fat_open_file + */ +void fat_close_file(struct fat_file_struct* fd) +{ + if(fd) + { +#if FAT_DELAY_DIRENTRY_UPDATE + /* write directory entry */ + fat_write_dir_entry(fd->fs, &fd->dir_entry); +#endif + +#if USE_DYNAMIC_MEMORY + free(fd); +#else + fd->fs = 0; +#endif + } +} + +/** + * \ingroup fat_file + * Reads data from a file. + * + * The data requested is read from the current file location. + * + * \param[in] fd The file handle of the file from which to read. + * \param[out] buffer The buffer into which to write. + * \param[in] buffer_len The amount of data to read. + * \returns The number of bytes read, 0 on end of file, or -1 on failure. + * \see fat_write_file + */ +intptr_t fat_read_file(struct fat_file_struct* fd, uint8_t* buffer, uintptr_t buffer_len) +{ + /* check arguments */ + if(!fd || !buffer || buffer_len < 1) + return -1; + + /* determine number of bytes to read */ + if(fd->pos + buffer_len > fd->dir_entry.file_size) + buffer_len = fd->dir_entry.file_size - fd->pos; + if(buffer_len == 0) + return 0; + + uint16_t cluster_size = fd->fs->header.cluster_size; + cluster_t cluster_num = fd->pos_cluster; + uintptr_t buffer_left = buffer_len; + uint16_t first_cluster_offset = (uint16_t) (fd->pos & (cluster_size - 1)); + + /* find cluster in which to start reading */ + if(!cluster_num) + { + cluster_num = fd->dir_entry.cluster; + + if(!cluster_num) + { + if(!fd->pos) + return 0; + else + return -1; + } + + if(fd->pos) + { + uint32_t pos = fd->pos; + while(pos >= cluster_size) + { + pos -= cluster_size; + cluster_num = fat_get_next_cluster(fd->fs, cluster_num); + if(!cluster_num) + return -1; + } + } + } + + /* read data */ + do + { + /* calculate data size to copy from cluster */ + offset_t cluster_offset = fat_cluster_offset(fd->fs, cluster_num) + first_cluster_offset; + uint16_t copy_length = cluster_size - first_cluster_offset; + if(copy_length > buffer_left) + copy_length = buffer_left; + + /* read data */ + if(!fd->fs->partition->device_read(cluster_offset, buffer, copy_length)) + return buffer_len - buffer_left; + + /* calculate new file position */ + buffer += copy_length; + buffer_left -= copy_length; + fd->pos += copy_length; + + if(first_cluster_offset + copy_length >= cluster_size) + { + /* we are on a cluster boundary, so get the next cluster */ + if((cluster_num = fat_get_next_cluster(fd->fs, cluster_num))) + { + first_cluster_offset = 0; + } + else + { + fd->pos_cluster = 0; + return buffer_len - buffer_left; + } + } + + fd->pos_cluster = cluster_num; + + } while(buffer_left > 0); /* check if we are done */ + + return buffer_len; +} + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Writes data to a file. + * + * The data is written to the current file location. + * + * \param[in] fd The file handle of the file to which to write. + * \param[in] buffer The buffer from which to read the data to be written. + * \param[in] buffer_len The amount of data to write. + * \returns The number of bytes written (0 or something less than \c buffer_len on disk full) or -1 on failure. + * \see fat_read_file + */ +intptr_t fat_write_file(struct fat_file_struct* fd, const uint8_t* buffer, uintptr_t buffer_len) +{ + /* check arguments */ + if(!fd || !buffer || buffer_len < 1) + return -1; + if(fd->pos > fd->dir_entry.file_size) + return -1; + + uint16_t cluster_size = fd->fs->header.cluster_size; + cluster_t cluster_num = fd->pos_cluster; + uintptr_t buffer_left = buffer_len; + uint16_t first_cluster_offset = (uint16_t) (fd->pos & (cluster_size - 1)); + + /* find cluster in which to start writing */ + if(!cluster_num) + { + cluster_num = fd->dir_entry.cluster; + + if(!cluster_num) + { + if(!fd->pos) + { + /* empty file */ + fd->dir_entry.cluster = cluster_num = fat_append_clusters(fd->fs, 0, 1); + if(!cluster_num) + return 0; + } + else + { + return -1; + } + } + + if(fd->pos) + { + uint32_t pos = fd->pos; + cluster_t cluster_num_next; + while(pos >= cluster_size) + { + pos -= cluster_size; + cluster_num_next = fat_get_next_cluster(fd->fs, cluster_num); + if(!cluster_num_next) + { + if(pos != 0) + return -1; /* current file position points beyond end of file */ + + /* the file exactly ends on a cluster boundary, and we append to it */ + cluster_num_next = fat_append_clusters(fd->fs, cluster_num, 1); + if(!cluster_num_next) + return 0; + } + + cluster_num = cluster_num_next; + } + } + } + + /* write data */ + do + { + /* calculate data size to write to cluster */ + offset_t cluster_offset = fat_cluster_offset(fd->fs, cluster_num) + first_cluster_offset; + uint16_t write_length = cluster_size - first_cluster_offset; + if(write_length > buffer_left) + write_length = buffer_left; + + /* write data which fits into the current cluster */ + if(!fd->fs->partition->device_write(cluster_offset, buffer, write_length)) + break; + + /* calculate new file position */ + buffer += write_length; + buffer_left -= write_length; + fd->pos += write_length; + + if(first_cluster_offset + write_length >= cluster_size) + { + /* we are on a cluster boundary, so get the next cluster */ + cluster_t cluster_num_next = fat_get_next_cluster(fd->fs, cluster_num); + if(!cluster_num_next && buffer_left > 0) + /* we reached the last cluster, append a new one */ + cluster_num_next = fat_append_clusters(fd->fs, cluster_num, 1); + if(!cluster_num_next) + { + fd->pos_cluster = 0; + break; + } + + cluster_num = cluster_num_next; + first_cluster_offset = 0; + } + + fd->pos_cluster = cluster_num; + + } while(buffer_left > 0); /* check if we are done */ + + /* update directory entry */ + if(fd->pos > fd->dir_entry.file_size) + { +#if !FAT_DELAY_DIRENTRY_UPDATE + uint32_t size_old = fd->dir_entry.file_size; +#endif + + /* update file size */ + fd->dir_entry.file_size = fd->pos; + +#if !FAT_DELAY_DIRENTRY_UPDATE + /* write directory entry */ + if(!fat_write_dir_entry(fd->fs, &fd->dir_entry)) + { + /* We do not return an error here since we actually wrote + * some data to disk. So we calculate the amount of data + * we wrote to disk and which lies within the old file size. + */ + buffer_left = fd->pos - size_old; + fd->pos = size_old; + } +#endif + } + + return buffer_len - buffer_left; +} +#endif + +/** + * \ingroup fat_file + * Repositions the read/write file offset. + * + * Changes the file offset where the next call to fat_read_file() + * or fat_write_file() starts reading/writing. + * + * If the new offset is beyond the end of the file, fat_resize_file() + * is implicitly called, i.e. the file is expanded. + * + * The new offset can be given in different ways determined by + * the \c whence parameter: + * - \b FAT_SEEK_SET: \c *offset is relative to the beginning of the file. + * - \b FAT_SEEK_CUR: \c *offset is relative to the current file position. + * - \b FAT_SEEK_END: \c *offset is relative to the end of the file. + * + * The resulting absolute offset is written to the location the \c offset + * parameter points to. + * + * Calling this function can also be used to retrieve the current file position: + \code + int32_t file_pos = 0; + if(!fat_seek_file(fd, &file_pos, FAT_SEEK_CUR)) + { + // error + } + // file_pos now contains the absolute file position + \endcode + * + * \param[in] fd The file decriptor of the file on which to seek. + * \param[in,out] offset A pointer to the new offset, as affected by the \c whence + * parameter. The function writes the new absolute offset + * to this location before it returns. + * \param[in] whence Affects the way \c offset is interpreted, see above. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_seek_file(struct fat_file_struct* fd, int32_t* offset, uint8_t whence) +{ + if(!fd || !offset) + return 0; + + uint32_t new_pos = fd->pos; + switch(whence) + { + case FAT_SEEK_SET: + new_pos = *offset; + break; + case FAT_SEEK_CUR: + new_pos += *offset; + break; + case FAT_SEEK_END: + new_pos = fd->dir_entry.file_size + *offset; + break; + default: + return 0; + } + + if(new_pos > fd->dir_entry.file_size +#if FAT_WRITE_SUPPORT + && !fat_resize_file(fd, new_pos) +#endif + ) + return 0; + + fd->pos = new_pos; + fd->pos_cluster = 0; + + *offset = (int32_t) new_pos; + return 1; +} + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Resizes a file to have a specific size. + * + * Enlarges or shrinks the file pointed to by the file descriptor to have + * exactly the specified size. + * + * If the file is truncated, all bytes having an equal or larger offset + * than the given size are lost. If the file is expanded, the additional + * bytes are allocated. + * + * \note Please be aware that this function just allocates or deallocates disk + * space, it does not explicitely clear it. To avoid data leakage, this + * must be done manually. + * + * \param[in] fd The file decriptor of the file which to resize. + * \param[in] size The new size of the file. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_resize_file(struct fat_file_struct* fd, uint32_t size) +{ + if(!fd) + return 0; + + cluster_t cluster_num = fd->dir_entry.cluster; + uint16_t cluster_size = fd->fs->header.cluster_size; + uint32_t size_new = size; + + do + { + if(cluster_num == 0 && size_new == 0) + /* the file stays empty */ + break; + + /* seek to the next cluster as long as we need the space */ + while(size_new > cluster_size) + { + /* get next cluster of file */ + cluster_t cluster_num_next = fat_get_next_cluster(fd->fs, cluster_num); + if(cluster_num_next) + { + cluster_num = cluster_num_next; + size_new -= cluster_size; + } + else + { + break; + } + } + + if(size_new > cluster_size || cluster_num == 0) + { + /* Allocate new cluster chain and append + * it to the existing one, if available. + */ + cluster_t cluster_count = (size_new + cluster_size - 1) / cluster_size; + cluster_t cluster_new_chain = fat_append_clusters(fd->fs, cluster_num, cluster_count); + if(!cluster_new_chain) + return 0; + + if(!cluster_num) + { + cluster_num = cluster_new_chain; + fd->dir_entry.cluster = cluster_num; + } + } + + /* write new directory entry */ + fd->dir_entry.file_size = size; + if(size == 0) + fd->dir_entry.cluster = 0; + if(!fat_write_dir_entry(fd->fs, &fd->dir_entry)) + return 0; + + if(size == 0) + { + /* free all clusters of file */ + fat_free_clusters(fd->fs, cluster_num); + } + else if(size_new <= cluster_size) + { + /* free all clusters no longer needed */ + fat_terminate_clusters(fd->fs, cluster_num); + } + + } while(0); + + /* correct file position */ + if(size < fd->pos) + { + fd->pos = size; + fd->pos_cluster = 0; + } + + return 1; +} +#endif + +/** + * \ingroup fat_dir + * Opens a directory. + * + * \param[in] fs The filesystem on which the directory to open resides. + * \param[in] dir_entry The directory entry which stands for the directory to open. + * \returns An opaque directory descriptor on success, 0 on failure. + * \see fat_close_dir + */ +struct fat_dir_struct* fat_open_dir(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry || !(dir_entry->attributes & FAT_ATTRIB_DIR)) + return 0; + +#if USE_DYNAMIC_MEMORY + struct fat_dir_struct* dd = malloc(sizeof(*dd)); + if(!dd) + return 0; +#else + struct fat_dir_struct* dd = fat_dir_handles; + uint8_t i; + for(i = 0; i < FAT_DIR_COUNT; ++i) + { + if(!dd->fs) + break; + + ++dd; + } + if(i >= FAT_DIR_COUNT) + return 0; +#endif + + memcpy(&dd->dir_entry, dir_entry, sizeof(*dir_entry)); + dd->fs = fs; + dd->entry_cluster = dir_entry->cluster; + dd->entry_offset = 0; + + return dd; +} + +/** + * \ingroup fat_dir + * Closes a directory descriptor. + * + * This function destroys a directory descriptor which was + * previously obtained by calling fat_open_dir(). When this + * function returns, the given descriptor will be invalid. + * + * \param[in] dd The directory descriptor to close. + * \see fat_open_dir + */ +void fat_close_dir(struct fat_dir_struct* dd) +{ + if(dd) +#if USE_DYNAMIC_MEMORY + free(dd); +#else + dd->fs = 0; +#endif +} + +/** + * \ingroup fat_dir + * Reads the next directory entry contained within a parent directory. + * + * \param[in] dd The descriptor of the parent directory from which to read the entry. + * \param[out] dir_entry Pointer to a buffer into which to write the directory entry information. + * \returns 0 on failure, 1 on success. + * \see fat_reset_dir + */ +uint8_t fat_read_dir(struct fat_dir_struct* dd, struct fat_dir_entry_struct* dir_entry) +{ + if(!dd || !dir_entry) + return 0; + + /* get current position of directory handle */ + struct fat_fs_struct* fs = dd->fs; + const struct fat_header_struct* header = &fs->header; + uint16_t cluster_size = header->cluster_size; + cluster_t cluster_num = dd->entry_cluster; + uint16_t cluster_offset = dd->entry_offset; + struct fat_read_dir_callback_arg arg; + + if(cluster_offset >= cluster_size) + { + /* The latest call hit the border of the last cluster in + * the chain, but it still returned a directory entry. + * So we now reset the handle and signal the caller the + * end of the listing. + */ + fat_reset_dir(dd); + return 0; + } + + /* reset callback arguments */ + memset(&arg, 0, sizeof(arg)); + memset(dir_entry, 0, sizeof(*dir_entry)); + arg.dir_entry = dir_entry; + + /* check if we read from the root directory */ + if(cluster_num == 0) + { +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + cluster_num = header->root_dir_cluster; + else +#endif + cluster_size = header->cluster_zero_offset - header->root_dir_offset; + } + + /* read entries */ + uint8_t buffer[32]; + while(!arg.finished) + { + /* read directory entries up to the cluster border */ + uint16_t cluster_left = cluster_size - cluster_offset; + offset_t pos = cluster_offset; + if(cluster_num == 0) + pos += header->root_dir_offset; + else + pos += fat_cluster_offset(fs, cluster_num); + + arg.bytes_read = 0; + if(!fs->partition->device_read_interval(pos, + buffer, + sizeof(buffer), + cluster_left, + fat_dir_entry_read_callback, + &arg) + ) + return 0; + + cluster_offset += arg.bytes_read; + + if(cluster_offset >= cluster_size) + { + /* we reached the cluster border and switch to the next cluster */ + + /* get number of next cluster */ + if((cluster_num = fat_get_next_cluster(fs, cluster_num)) != 0) + { + cluster_offset = 0; + continue; + } + + /* we are at the end of the cluster chain */ + if(!arg.finished) + { + /* directory entry not found, reset directory handle */ + fat_reset_dir(dd); + return 0; + } + else + { + /* The current execution of the function has been successful, + * so we can not signal an end of the directory listing to + * the caller, but must wait for the next call. So we keep an + * invalid cluster offset to mark this directory handle's + * traversal as finished. + */ + } + + break; + } + } + + dd->entry_cluster = cluster_num; + dd->entry_offset = cluster_offset; + + return arg.finished; +} + +/** + * \ingroup fat_dir + * Resets a directory handle. + * + * Resets the directory handle such that reading restarts + * with the first directory entry. + * + * \param[in] dd The directory handle to reset. + * \returns 0 on failure, 1 on success. + * \see fat_read_dir + */ +uint8_t fat_reset_dir(struct fat_dir_struct* dd) +{ + if(!dd) + return 0; + + dd->entry_cluster = dd->dir_entry.cluster; + dd->entry_offset = 0; + return 1; +} + +/** + * \ingroup fat_fs + * Callback function for reading a directory entry. + * + * Interprets a raw directory entry and puts the contained + * information into a fat_dir_entry_struct structure. + * + * For a single file there may exist multiple directory + * entries. All except the last one are lfn entries, which + * contain parts of the long filename. The last directory + * entry is a traditional 8.3 style one. It contains all + * other information like size, cluster, date and time. + * + * \param[in] buffer A pointer to 32 bytes of raw data. + * \param[in] offset The absolute offset of the raw data. + * \param[in,out] p An argument structure controlling operation. + * \returns 0 on failure or completion, 1 if reading has + * to be continued + */ +uint8_t fat_dir_entry_read_callback(uint8_t* buffer, offset_t offset, void* p) +{ + struct fat_read_dir_callback_arg* arg = p; + struct fat_dir_entry_struct* dir_entry = arg->dir_entry; + + arg->bytes_read += 32; + + /* skip deleted or empty entries */ + if(buffer[0] == FAT_DIRENTRY_DELETED || !buffer[0]) + { +#if FAT_LFN_SUPPORT + arg->checksum = 0; +#endif + return 1; + } + +#if !FAT_LFN_SUPPORT + /* skip lfn entries */ + if(buffer[11] == 0x0f) + return 1; +#endif + + char* long_name = dir_entry->long_name; +#if FAT_LFN_SUPPORT + if(buffer[11] == 0x0f) + { + /* checksum validation */ + if(arg->checksum == 0 || arg->checksum != buffer[13]) + { + /* reset directory entry */ + memset(dir_entry, 0, sizeof(*dir_entry)); + + arg->checksum = buffer[13]; + dir_entry->entry_offset = offset; + } + + /* lfn supports unicode, but we do not, for now. + * So we assume pure ascii and read only every + * second byte. + */ + uint16_t char_offset = ((buffer[0] & 0x3f) - 1) * 13; + const uint8_t char_mapping[] = { 1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30 }; + for(uint8_t i = 0; i <= 12 && char_offset + i < sizeof(dir_entry->long_name) - 1; ++i) + long_name[char_offset + i] = buffer[char_mapping[i]]; + + return 1; + } + else +#endif + { +#if FAT_LFN_SUPPORT + /* if we do not have a long name or the previous lfn does not match, take the 8.3 name */ + if(long_name[0] == '\0' || arg->checksum != fat_calc_83_checksum(buffer)) +#endif + { + /* reset directory entry */ + memset(dir_entry, 0, sizeof(*dir_entry)); + dir_entry->entry_offset = offset; + + uint8_t i; + for(i = 0; i < 8; ++i) + { + if(buffer[i] == ' ') + break; + long_name[i] = buffer[i]; + + /* Windows NT and later versions do not store lfn entries + * for 8.3 names which have a lowercase basename, extension + * or both when everything else is uppercase. They use two + * extra bits to signal a lowercase basename or extension. + */ + if((buffer[12] & 0x08) && buffer[i] >= 'A' && buffer[i] <= 'Z') + long_name[i] += 'a' - 'A'; + } + if(long_name[0] == 0x05) + long_name[0] = (char) FAT_DIRENTRY_DELETED; + + if(buffer[8] != ' ') + { + long_name[i++] = '.'; + + uint8_t j = 8; + for(; j < 11; ++j) + { + if(buffer[j] == ' ') + break; + long_name[i] = buffer[j]; + + /* See above for the lowercase 8.3 name handling of + * Windows NT and later. + */ + if((buffer[12] & 0x10) && buffer[j] >= 'A' && buffer[j] <= 'Z') + long_name[i] += 'a' - 'A'; + + ++i; + } + } + + long_name[i] = '\0'; + } + + /* extract properties of file and store them within the structure */ + dir_entry->attributes = buffer[11]; + dir_entry->cluster = read16(&buffer[26]); +#if FAT_FAT32_SUPPORT + dir_entry->cluster |= ((cluster_t) read16(&buffer[20])) << 16; +#endif + dir_entry->file_size = read32(&buffer[28]); + +#if FAT_DATETIME_SUPPORT + dir_entry->modification_time = read16(&buffer[22]); + dir_entry->modification_date = read16(&buffer[24]); +#endif + + arg->finished = 1; + return 0; + } +} + +#if DOXYGEN || FAT_LFN_SUPPORT +/** + * \ingroup fat_fs + * Calculates the checksum for 8.3 names used within the + * corresponding lfn directory entries. + * + * \param[in] file_name_83 The 11-byte file name buffer. + * \returns The checksum of the given file name. + */ +uint8_t fat_calc_83_checksum(const uint8_t* file_name_83) +{ + uint8_t checksum = file_name_83[0]; + for(uint8_t i = 1; i < 11; ++i) + checksum = ((checksum >> 1) | (checksum << 7)) + file_name_83[i]; + + return checksum; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Searches for space where to store a directory entry. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] parent The directory in which to search. + * \param[in] dir_entry The directory entry for which to search space. + * \returns 0 on failure, a device offset on success. + */ +offset_t fat_find_offset_for_dir_entry(struct fat_fs_struct* fs, const struct fat_dir_struct* parent, const struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry) + return 0; + + /* search for a place where to write the directory entry to disk */ +#if FAT_LFN_SUPPORT + uint8_t free_dir_entries_needed = (strlen(dir_entry->long_name) + 12) / 13 + 1; + uint8_t free_dir_entries_found = 0; +#endif + cluster_t cluster_num = parent->dir_entry.cluster; + offset_t dir_entry_offset = 0; + offset_t offset = 0; + offset_t offset_to = 0; +#if FAT_FAT32_SUPPORT + uint8_t is_fat32 = (fs->partition->type == PARTITION_TYPE_FAT32); +#endif + + if(cluster_num == 0) + { +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + cluster_num = fs->header.root_dir_cluster; + } + else +#endif + { + /* we read/write from the root directory entry */ + offset = fs->header.root_dir_offset; + offset_to = fs->header.cluster_zero_offset; + dir_entry_offset = offset; + } + } + + while(1) + { + if(offset == offset_to) + { + if(cluster_num == 0) + /* We iterated through the whole root directory and + * could not find enough space for the directory entry. + */ + return 0; + + if(offset) + { + /* We reached a cluster boundary and have to + * switch to the next cluster. + */ + + cluster_t cluster_next = fat_get_next_cluster(fs, cluster_num); + if(!cluster_next) + { + cluster_next = fat_append_clusters(fs, cluster_num, 1); + if(!cluster_next) + return 0; + + /* we appended a new cluster and know it is free */ + dir_entry_offset = fs->header.cluster_zero_offset + + (offset_t) (cluster_next - 2) * fs->header.cluster_size; + + /* clear cluster to avoid garbage directory entries */ + fat_clear_cluster(fs, cluster_next); + + break; + } + cluster_num = cluster_next; + } + + offset = fat_cluster_offset(fs, cluster_num); + offset_to = offset + fs->header.cluster_size; + dir_entry_offset = offset; +#if FAT_LFN_SUPPORT + free_dir_entries_found = 0; +#endif + } + + /* read next lfn or 8.3 entry */ + uint8_t first_char; + if(!fs->partition->device_read(offset, &first_char, sizeof(first_char))) + return 0; + + /* check if we found a free directory entry */ + if(first_char == FAT_DIRENTRY_DELETED || !first_char) + { + /* check if we have the needed number of available entries */ +#if FAT_LFN_SUPPORT + ++free_dir_entries_found; + if(free_dir_entries_found >= free_dir_entries_needed) +#endif + break; + + offset += 32; + } + else + { + offset += 32; + dir_entry_offset = offset; +#if FAT_LFN_SUPPORT + free_dir_entries_found = 0; +#endif + } + } + + return dir_entry_offset; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Writes a directory entry to disk. + * + * \note The file name is not checked for invalid characters. + * + * \note The generation of the short 8.3 file name is quite + * simple. The first eight characters are used for the filename. + * The extension, if any, is made up of the first three characters + * following the last dot within the long filename. If the + * filename (without the extension) is longer than eight characters, + * the lower byte of the cluster number replaces the last two + * characters to avoid name clashes. In any other case, it is your + * responsibility to avoid name clashes. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] dir_entry The directory entry to write. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_write_dir_entry(const struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry) + return 0; + +#if FAT_DATETIME_SUPPORT + { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + + fat_get_datetime(&year, &month, &day, &hour, &min, &sec); + fat_set_file_modification_date(dir_entry, year, month, day); + fat_set_file_modification_time(dir_entry, hour, min, sec); + } +#endif + + device_write_t device_write = fs->partition->device_write; + offset_t offset = dir_entry->entry_offset; + const char* name = dir_entry->long_name; + uint8_t name_len = strlen(name); +#if FAT_LFN_SUPPORT + uint8_t lfn_entry_count = (name_len + 12) / 13; +#endif + uint8_t buffer[32]; + + /* write 8.3 entry */ + + /* generate 8.3 file name */ + memset(&buffer[0], ' ', 11); + char* name_ext = strrchr(name, '.'); + if(name_ext && *++name_ext) + { + uint8_t name_ext_len = strlen(name_ext); + name_len -= name_ext_len + 1; + + if(name_ext_len > 3) +#if FAT_LFN_SUPPORT + name_ext_len = 3; +#else + return 0; +#endif + + memcpy(&buffer[8], name_ext, name_ext_len); + } + + if(name_len <= 8) + { + memcpy(buffer, name, name_len); + +#if FAT_LFN_SUPPORT + /* For now, we create lfn entries for all files, + * except the "." and ".." directory references. + * This is to avoid difficulties with capitalization, + * as 8.3 filenames allow uppercase letters only. + * + * Theoretically it would be possible to leave + * the 8.3 entry alone if the basename and the + * extension have no mixed capitalization. + */ + if(name[0] == '.' && + ((name[1] == '.' && name[2] == '\0') || + name[1] == '\0') + ) + lfn_entry_count = 0; +#endif + } + else + { +#if FAT_LFN_SUPPORT + memcpy(buffer, name, 8); + + /* Minimize 8.3 name clashes by appending + * the lower byte of the cluster number. + */ + uint8_t num = dir_entry->cluster & 0xff; + + buffer[6] = (num < 0xa0) ? ('0' + (num >> 4)) : ('a' + (num >> 4)); + num &= 0x0f; + buffer[7] = (num < 0x0a) ? ('0' + num) : ('a' + num); +#else + return 0; +#endif + } + if(buffer[0] == FAT_DIRENTRY_DELETED) + buffer[0] = 0x05; + + /* fill directory entry buffer */ + memset(&buffer[11], 0, sizeof(buffer) - 11); + buffer[0x0b] = dir_entry->attributes; +#if FAT_DATETIME_SUPPORT + write16(&buffer[0x16], dir_entry->modification_time); + write16(&buffer[0x18], dir_entry->modification_date); +#endif +#if FAT_FAT32_SUPPORT + write16(&buffer[0x14], (uint16_t) (dir_entry->cluster >> 16)); +#endif + write16(&buffer[0x1a], dir_entry->cluster); + write32(&buffer[0x1c], dir_entry->file_size); + + /* write to disk */ +#if FAT_LFN_SUPPORT + if(!device_write(offset + (uint16_t) lfn_entry_count * 32, buffer, sizeof(buffer))) +#else + if(!device_write(offset, buffer, sizeof(buffer))) +#endif + return 0; + +#if FAT_LFN_SUPPORT + /* calculate checksum of 8.3 name */ + uint8_t checksum = fat_calc_83_checksum(buffer); + + /* write lfn entries */ + for(uint8_t lfn_entry = lfn_entry_count; lfn_entry > 0; --lfn_entry) + { + memset(buffer, 0xff, sizeof(buffer)); + + /* set file name */ + const char* long_name_curr = name + (lfn_entry - 1) * 13; + uint8_t i = 1; + while(i < 0x1f) + { + buffer[i++] = *long_name_curr; + buffer[i++] = 0; + + switch(i) + { + case 0x0b: + i = 0x0e; + break; + case 0x1a: + i = 0x1c; + break; + } + + if(!*long_name_curr++) + break; + } + + /* set index of lfn entry */ + buffer[0x00] = lfn_entry; + if(lfn_entry == lfn_entry_count) + buffer[0x00] |= FAT_DIRENTRY_LFNLAST; + + /* mark as lfn entry */ + buffer[0x0b] = 0x0f; + + /* set 8.3 checksum */ + buffer[0x0d] = checksum; + + /* clear reserved bytes */ + buffer[0x0c] = 0; + buffer[0x1a] = 0; + buffer[0x1b] = 0; + + /* write entry */ + device_write(offset, buffer, sizeof(buffer)); + + offset += sizeof(buffer); + } +#endif + + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Creates a file. + * + * Creates a file and obtains the directory entry of the + * new file. If the file to create already exists, the + * directory entry of the existing file will be returned + * within the dir_entry parameter. + * + * \note The file name is not checked for invalid characters. + * + * \note The generation of the short 8.3 file name is quite + * simple. The first eight characters are used for the filename. + * The extension, if any, is made up of the first three characters + * following the last dot within the long filename. If the + * filename (without the extension) is longer than eight characters, + * the lower byte of the cluster number replaces the last two + * characters to avoid name clashes. In any other case, it is your + * responsibility to avoid name clashes. + * + * \param[in] parent The handle of the directory in which to create the file. + * \param[in] file The name of the file to create. + * \param[out] dir_entry The directory entry to fill for the new (or existing) file. + * \returns 0 on failure, 1 on success, 2 if the file already existed. + * \see fat_delete_file + */ +uint8_t fat_create_file(struct fat_dir_struct* parent, const char* file, struct fat_dir_entry_struct* dir_entry) +{ + if(!parent || !file || !file[0] || !dir_entry) + return 0; + + /* check if the file already exists */ + while(1) + { + if(!fat_read_dir(parent, dir_entry)) + break; + + if(strcmp(file, dir_entry->long_name) == 0) + { + fat_reset_dir(parent); + return 2; + } + } + + struct fat_fs_struct* fs = parent->fs; + + /* prepare directory entry with values already known */ + memset(dir_entry, 0, sizeof(*dir_entry)); + strncpy(dir_entry->long_name, file, sizeof(dir_entry->long_name) - 1); + + /* find place where to store directory entry */ + if(!(dir_entry->entry_offset = fat_find_offset_for_dir_entry(fs, parent, dir_entry))) + return 0; + + /* write directory entry to disk */ + if(!fat_write_dir_entry(fs, dir_entry)) + return 0; + + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Deletes a file or directory. + * + * If a directory is deleted without first deleting its + * subdirectories and files, disk space occupied by these + * files will get wasted as there is no chance to release + * it and mark it as free. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] dir_entry The directory entry of the file to delete. + * \returns 0 on failure, 1 on success. + * \see fat_create_file + */ +uint8_t fat_delete_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry) + return 0; + + /* get offset of the file's directory entry */ + offset_t dir_entry_offset = dir_entry->entry_offset; + if(!dir_entry_offset) + return 0; + +#if FAT_LFN_SUPPORT + uint8_t buffer[12]; + while(1) + { + /* read directory entry */ + if(!fs->partition->device_read(dir_entry_offset, buffer, sizeof(buffer))) + return 0; + + /* mark the directory entry as deleted */ + buffer[0] = FAT_DIRENTRY_DELETED; + + /* write back entry */ + if(!fs->partition->device_write(dir_entry_offset, buffer, sizeof(buffer))) + return 0; + + /* check if we deleted the whole entry */ + if(buffer[11] != 0x0f) + break; + + dir_entry_offset += 32; + } +#else + /* mark the directory entry as deleted */ + uint8_t first_char = FAT_DIRENTRY_DELETED; + if(!fs->partition->device_write(dir_entry_offset, &first_char, 1)) + return 0; +#endif + + /* We deleted the directory entry. The next thing to do is + * marking all occupied clusters as free. + */ + return (dir_entry->cluster == 0 || fat_free_clusters(fs, dir_entry->cluster)); +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Moves or renames a file. + * + * Changes a file's name, optionally moving it into another + * directory as well. Before calling this function, the + * target file name must not exist. Moving a file to a + * different filesystem (i.e. \a parent_new doesn't lie on + * \a fs) is not supported. + * + * After successfully renaming (and moving) the file, the + * given directory entry is updated such that it points to + * the file's new location. + * + * \note The notes which apply to fat_create_file() also + * apply to this function. + * + * \param[in] fs The filesystem on which to operate. + * \param[in,out] dir_entry The directory entry of the file to move. + * \param[in] parent_new The handle of the new parent directory of the file. + * \param[in] file_new The file's new name. + * \returns 0 on failure, 1 on success. + * \see fat_create_file, fat_delete_file, fat_move_dir + */ +uint8_t fat_move_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry, struct fat_dir_struct* parent_new, const char* file_new) +{ + if(!fs || !dir_entry || !parent_new || (file_new && !file_new[0])) + return 0; + if(fs != parent_new->fs) + return 0; + + /* use existing file name if none has been specified */ + if(!file_new) + file_new = dir_entry->long_name; + + /* create file with new file name */ + struct fat_dir_entry_struct dir_entry_new; + if(!fat_create_file(parent_new, file_new, &dir_entry_new)) + return 0; + + /* copy members of directory entry which do not change with rename */ + dir_entry_new.attributes = dir_entry->attributes; +#if FAT_DATETIME_SUPPORT + dir_entry_new.modification_time = dir_entry->modification_time; + dir_entry_new.modification_date = dir_entry->modification_date; +#endif + dir_entry_new.cluster = dir_entry->cluster; + dir_entry_new.file_size = dir_entry->file_size; + + /* make the new file name point to the old file's content */ + if(!fat_write_dir_entry(fs, &dir_entry_new)) + { + fat_delete_file(fs, &dir_entry_new); + return 0; + } + + /* delete the old file, but not its clusters, which have already been remapped above */ + dir_entry->cluster = 0; + if(!fat_delete_file(fs, dir_entry)) + return 0; + + *dir_entry = dir_entry_new; + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_dir + * Creates a directory. + * + * Creates a directory and obtains its directory entry. + * If the directory to create already exists, its + * directory entry will be returned within the dir_entry + * parameter. + * + * \note The notes which apply to fat_create_file() also + * apply to this function. + * + * \param[in] parent The handle of the parent directory of the new directory. + * \param[in] dir The name of the directory to create. + * \param[out] dir_entry The directory entry to fill for the new directory. + * \returns 0 on failure, 1 on success. + * \see fat_delete_dir + */ +uint8_t fat_create_dir(struct fat_dir_struct* parent, const char* dir, struct fat_dir_entry_struct* dir_entry) +{ + if(!parent || !dir || !dir[0] || !dir_entry) + return 0; + + /* check if the file or directory already exists */ + while(fat_read_dir(parent, dir_entry)) + { + if(strcmp(dir, dir_entry->long_name) == 0) + { + fat_reset_dir(parent); + return 0; + } + } + + struct fat_fs_struct* fs = parent->fs; + + /* allocate cluster which will hold directory entries */ + cluster_t dir_cluster = fat_append_clusters(fs, 0, 1); + if(!dir_cluster) + return 0; + + /* clear cluster to prevent bogus directory entries */ + fat_clear_cluster(fs, dir_cluster); + + memset(dir_entry, 0, sizeof(*dir_entry)); + dir_entry->attributes = FAT_ATTRIB_DIR; + + /* create "." directory self reference */ + dir_entry->entry_offset = fs->header.cluster_zero_offset + + (offset_t) (dir_cluster - 2) * fs->header.cluster_size; + dir_entry->long_name[0] = '.'; + dir_entry->cluster = dir_cluster; + if(!fat_write_dir_entry(fs, dir_entry)) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + /* create ".." parent directory reference */ + dir_entry->entry_offset += 32; + dir_entry->long_name[1] = '.'; + dir_entry->cluster = parent->dir_entry.cluster; + if(!fat_write_dir_entry(fs, dir_entry)) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + /* fill directory entry */ + strncpy(dir_entry->long_name, dir, sizeof(dir_entry->long_name) - 1); + dir_entry->cluster = dir_cluster; + + /* find place where to store directory entry */ + if(!(dir_entry->entry_offset = fat_find_offset_for_dir_entry(fs, parent, dir_entry))) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + /* write directory to disk */ + if(!fat_write_dir_entry(fs, dir_entry)) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + return 1; +} +#endif + +/** + * \ingroup fat_dir + * Deletes a directory. + * + * This is just a synonym for fat_delete_file(). + * If a directory is deleted without first deleting its + * subdirectories and files, disk space occupied by these + * files will get wasted as there is no chance to release + * it and mark it as free. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] dir_entry The directory entry of the directory to delete. + * \returns 0 on failure, 1 on success. + * \see fat_create_dir + */ +#ifdef DOXYGEN +uint8_t fat_delete_dir(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry); +#endif + +/** + * \ingroup fat_dir + * Moves or renames a directory. + * + * This is just a synonym for fat_move_file(). + * + * \param[in] fs The filesystem on which to operate. + * \param[in,out] dir_entry The directory entry of the directory to move. + * \param[in] parent_new The handle of the new parent directory. + * \param[in] dir_new The directory's new name. + * \returns 0 on failure, 1 on success. + * \see fat_create_dir, fat_delete_dir, fat_move_file + */ +#ifdef DOXYGEN +uint8_t fat_move_dir(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry, struct fat_dir_struct* parent_new, const char* dir_new); +#endif + +#if DOXYGEN || FAT_DATETIME_SUPPORT +/** + * \ingroup fat_file + * Returns the modification date of a file. + * + * \param[in] dir_entry The directory entry of which to return the modification date. + * \param[out] year The year the file was last modified. + * \param[out] month The month the file was last modified. + * \param[out] day The day the file was last modified. + */ +void fat_get_file_modification_date(const struct fat_dir_entry_struct* dir_entry, uint16_t* year, uint8_t* month, uint8_t* day) +{ + if(!dir_entry) + return; + + *year = 1980 + ((dir_entry->modification_date >> 9) & 0x7f); + *month = (dir_entry->modification_date >> 5) & 0x0f; + *day = (dir_entry->modification_date >> 0) & 0x1f; +} +#endif + +#if DOXYGEN || FAT_DATETIME_SUPPORT +/** + * \ingroup fat_file + * Returns the modification time of a file. + * + * \param[in] dir_entry The directory entry of which to return the modification time. + * \param[out] hour The hour the file was last modified. + * \param[out] min The min the file was last modified. + * \param[out] sec The sec the file was last modified. + */ +void fat_get_file_modification_time(const struct fat_dir_entry_struct* dir_entry, uint8_t* hour, uint8_t* min, uint8_t* sec) +{ + if(!dir_entry) + return; + + *hour = (dir_entry->modification_time >> 11) & 0x1f; + *min = (dir_entry->modification_time >> 5) & 0x3f; + *sec = ((dir_entry->modification_time >> 0) & 0x1f) * 2; +} +#endif + +#if DOXYGEN || (FAT_WRITE_SUPPORT && FAT_DATETIME_SUPPORT) +/** + * \ingroup fat_file + * Sets the modification time of a date. + * + * \param[in] dir_entry The directory entry for which to set the modification date. + * \param[in] year The year the file was last modified. + * \param[in] month The month the file was last modified. + * \param[in] day The day the file was last modified. + */ +void fat_set_file_modification_date(struct fat_dir_entry_struct* dir_entry, uint16_t year, uint8_t month, uint8_t day) +{ + if(!dir_entry) + return; + + dir_entry->modification_date = + ((year - 1980) << 9) | + ((uint16_t) month << 5) | + ((uint16_t) day << 0); +} +#endif + +#if DOXYGEN || (FAT_WRITE_SUPPORT && FAT_DATETIME_SUPPORT) +/** + * \ingroup fat_file + * Sets the modification time of a file. + * + * \param[in] dir_entry The directory entry for which to set the modification time. + * \param[in] hour The year the file was last modified. + * \param[in] min The month the file was last modified. + * \param[in] sec The day the file was last modified. + */ +void fat_set_file_modification_time(struct fat_dir_entry_struct* dir_entry, uint8_t hour, uint8_t min, uint8_t sec) +{ + if(!dir_entry) + return; + + dir_entry->modification_time = + ((uint16_t) hour << 11) | + ((uint16_t) min << 5) | + ((uint16_t) sec >> 1) ; +} +#endif + +/** + * \ingroup fat_fs + * Returns the amount of total storage capacity of the filesystem in bytes. + * + * \param[in] fs The filesystem on which to operate. + * \returns 0 on failure, the filesystem size in bytes otherwise. + */ +offset_t fat_get_fs_size(const struct fat_fs_struct* fs) +{ + if(!fs) + return 0; + +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + return (offset_t) (fs->header.fat_size / 4 - 2) * fs->header.cluster_size; + else +#endif + return (offset_t) (fs->header.fat_size / 2 - 2) * fs->header.cluster_size; +} + +/** + * \ingroup fat_fs + * Returns the amount of free storage capacity on the filesystem in bytes. + * + * \note As the FAT filesystem is cluster based, this function does not + * return continuous values but multiples of the cluster size. + * + * \param[in] fs The filesystem on which to operate. + * \returns 0 on failure, the free filesystem space in bytes otherwise. + */ +offset_t fat_get_fs_free(const struct fat_fs_struct* fs) +{ + if(!fs) + return 0; + + uint8_t fat[32]; + struct fat_usage_count_callback_arg count_arg; + count_arg.cluster_count = 0; + count_arg.buffer_size = sizeof(fat); + + offset_t fat_offset = fs->header.fat_offset; + uint32_t fat_size = fs->header.fat_size; + while(fat_size > 0) + { + uintptr_t length = UINTPTR_MAX - 1; + if(fat_size < length) + length = fat_size; + + if(!fs->partition->device_read_interval(fat_offset, + fat, + sizeof(fat), + length, +#if FAT_FAT32_SUPPORT + (fs->partition->type == PARTITION_TYPE_FAT16) ? + fat_get_fs_free_16_callback : + fat_get_fs_free_32_callback, +#else + fat_get_fs_free_16_callback, +#endif + &count_arg + ) + ) + return 0; + + fat_offset += length; + fat_size -= length; + } + + return (offset_t) count_arg.cluster_count * fs->header.cluster_size; +} + +/** + * \ingroup fat_fs + * Callback function used for counting free clusters in a FAT. + */ +uint8_t fat_get_fs_free_16_callback(uint8_t* buffer, offset_t offset, void* p) +{ + struct fat_usage_count_callback_arg* count_arg = (struct fat_usage_count_callback_arg*) p; + uintptr_t buffer_size = count_arg->buffer_size; + + for(uintptr_t i = 0; i < buffer_size; i += 2, buffer += 2) + { + uint16_t cluster = read16(buffer); + if(cluster == HTOL16(FAT16_CLUSTER_FREE)) + ++(count_arg->cluster_count); + } + + return 1; +} + +#if DOXYGEN || FAT_FAT32_SUPPORT +/** + * \ingroup fat_fs + * Callback function used for counting free clusters in a FAT32. + */ +uint8_t fat_get_fs_free_32_callback(uint8_t* buffer, offset_t offset, void* p) +{ + struct fat_usage_count_callback_arg* count_arg = (struct fat_usage_count_callback_arg*) p; + uintptr_t buffer_size = count_arg->buffer_size; + + for(uintptr_t i = 0; i < buffer_size; i += 4, buffer += 4) + { + uint32_t cluster = read32(buffer); + if(cluster == HTOL32(FAT32_CLUSTER_FREE)) + ++(count_arg->cluster_count); + } + + return 1; +} +#endif + diff --git a/sd_reader/fat.h b/sd_reader/fat.h new file mode 100644 index 0000000..b67bb29 --- /dev/null +++ b/sd_reader/fat.h @@ -0,0 +1,131 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef FAT_H +#define FAT_H + +#include +#include "fat_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup fat + * + * @{ + */ +/** + * \file + * FAT header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup fat_file + * @{ + */ + +/** The file is read-only. */ +#define FAT_ATTRIB_READONLY (1 << 0) +/** The file is hidden. */ +#define FAT_ATTRIB_HIDDEN (1 << 1) +/** The file is a system file. */ +#define FAT_ATTRIB_SYSTEM (1 << 2) +/** The file is empty and has the volume label as its name. */ +#define FAT_ATTRIB_VOLUME (1 << 3) +/** The file is a directory. */ +#define FAT_ATTRIB_DIR (1 << 4) +/** The file has to be archived. */ +#define FAT_ATTRIB_ARCHIVE (1 << 5) + +/** The given offset is relative to the beginning of the file. */ +#define FAT_SEEK_SET 0 +/** The given offset is relative to the current read/write position. */ +#define FAT_SEEK_CUR 1 +/** The given offset is relative to the end of the file. */ +#define FAT_SEEK_END 2 + +/** + * @} + */ + +struct partition_struct; +struct fat_fs_struct; +struct fat_file_struct; +struct fat_dir_struct; + +/** + * \ingroup fat_file + * Describes a directory entry. + */ +struct fat_dir_entry_struct +{ + /** The file's name, truncated to 31 characters. */ + char long_name[32]; + /** The file's attributes. Mask of the FAT_ATTRIB_* constants. */ + uint8_t attributes; +#if FAT_DATETIME_SUPPORT + /** Compressed representation of modification time. */ + uint16_t modification_time; + /** Compressed representation of modification date. */ + uint16_t modification_date; +#endif + /** The cluster in which the file's first byte resides. */ + cluster_t cluster; + /** The file's size. */ + uint32_t file_size; + /** The total disk offset of this directory entry. */ + offset_t entry_offset; +}; + +struct fat_fs_struct* fat_open(struct partition_struct* partition); +void fat_close(struct fat_fs_struct* fs); + +struct fat_file_struct* fat_open_file(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry); +void fat_close_file(struct fat_file_struct* fd); +intptr_t fat_read_file(struct fat_file_struct* fd, uint8_t* buffer, uintptr_t buffer_len); +intptr_t fat_write_file(struct fat_file_struct* fd, const uint8_t* buffer, uintptr_t buffer_len); +uint8_t fat_seek_file(struct fat_file_struct* fd, int32_t* offset, uint8_t whence); +uint8_t fat_resize_file(struct fat_file_struct* fd, uint32_t size); + +struct fat_dir_struct* fat_open_dir(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry); +void fat_close_dir(struct fat_dir_struct* dd); +uint8_t fat_read_dir(struct fat_dir_struct* dd, struct fat_dir_entry_struct* dir_entry); +uint8_t fat_reset_dir(struct fat_dir_struct* dd); + +uint8_t fat_create_file(struct fat_dir_struct* parent, const char* file, struct fat_dir_entry_struct* dir_entry); +uint8_t fat_delete_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry); +uint8_t fat_move_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry, struct fat_dir_struct* parent_new, const char* file_new); +uint8_t fat_create_dir(struct fat_dir_struct* parent, const char* dir, struct fat_dir_entry_struct* dir_entry); +#define fat_delete_dir fat_delete_file +#define fat_move_dir fat_move_file + +void fat_get_file_modification_date(const struct fat_dir_entry_struct* dir_entry, uint16_t* year, uint8_t* month, uint8_t* day); +void fat_get_file_modification_time(const struct fat_dir_entry_struct* dir_entry, uint8_t* hour, uint8_t* min, uint8_t* sec); + +uint8_t fat_get_dir_entry_of_path(struct fat_fs_struct* fs, const char* path, struct fat_dir_entry_struct* dir_entry); + +offset_t fat_get_fs_size(const struct fat_fs_struct* fs); +offset_t fat_get_fs_free(const struct fat_fs_struct* fs); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/fat.o b/sd_reader/fat.o new file mode 100644 index 0000000000000000000000000000000000000000..37e765658779828c6ad876b28199ff1f1bbce9a7 GIT binary patch literal 42444 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 + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef FAT_CONFIG_H +#define FAT_CONFIG_H + +#include +#include "sd_raw_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup fat + * + * @{ + */ +/** + * \file + * FAT configuration (license: GPLv2 or LGPLv2.1) + */ + +/** + * \ingroup fat_config + * Controls FAT write support. + * + * Set to 1 to enable FAT write support, set to 0 to disable it. + */ +#define FAT_WRITE_SUPPORT SD_RAW_WRITE_SUPPORT + +/** + * \ingroup fat_config + * Controls FAT long filename (LFN) support. + * + * Set to 1 to enable LFN support, set to 0 to disable it. + */ +#define FAT_LFN_SUPPORT 1 + +/** + * \ingroup fat_config + * Controls FAT date and time support. + * + * Set to 1 to enable FAT date and time stamping support. + */ +#define FAT_DATETIME_SUPPORT 0 + +/** + * \ingroup fat_config + * Controls FAT32 support. + * + * Set to 1 to enable FAT32 support. + */ +#define FAT_FAT32_SUPPORT SD_RAW_SDHC + +/** + * \ingroup fat_config + * Controls updates of directory entries. + * + * Set to 1 to delay directory entry updates until the file is closed. + * This can boost performance significantly, but may cause data loss + * if the file is not properly closed. + */ +#define FAT_DELAY_DIRENTRY_UPDATE 0 + +/** + * \ingroup fat_config + * Determines the function used for retrieving current date and time. + * + * Define this to the function call which shall be used to retrieve + * current date and time. + * + * \note Used only when FAT_DATETIME_SUPPORT is 1. + * + * \param[out] year Pointer to a \c uint16_t which receives the current year. + * \param[out] month Pointer to a \c uint8_t which receives the current month. + * \param[out] day Pointer to a \c uint8_t which receives the current day. + * \param[out] hour Pointer to a \c uint8_t which receives the current hour. + * \param[out] min Pointer to a \c uint8_t which receives the current minute. + * \param[out] sec Pointer to a \c uint8_t which receives the current sec. + */ +#define fat_get_datetime(year, month, day, hour, min, sec) \ + get_datetime(year, month, day, hour, min, sec) +/* forward declaration for the above */ +void get_datetime(uint16_t* year, uint8_t* month, uint8_t* day, uint8_t* hour, uint8_t* min, uint8_t* sec); + +/** + * \ingroup fat_config + * Maximum number of filesystem handles. + */ +#define FAT_FS_COUNT 1 + +/** + * \ingroup fat_config + * Maximum number of file handles. + */ +#define FAT_FILE_COUNT 1 + +/** + * \ingroup fat_config + * Maximum number of directory handles. + */ +#define FAT_DIR_COUNT 2 + +/** + * @} + */ + +#if FAT_FAT32_SUPPORT + typedef uint32_t cluster_t; +#else + typedef uint16_t cluster_t; +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/fifo.c b/sd_reader/fifo.c new file mode 100644 index 0000000..e159ebb --- /dev/null +++ b/sd_reader/fifo.c @@ -0,0 +1,52 @@ +#include "fifo.h" + +static struct { + FIFO_TYPE buffer[FIFO_LEN]; + uint16_t rd_ptr; + uint16_t wr_ptr; + uint16_t entries; +} fifo; + +void fifo_init() { + fifo.rd_ptr = 0; + fifo.wr_ptr = 0; + fifo.entries = 0; +} + +fifo_err_t fifo_pop(FIFO_TYPE* dest) { + // FIFO is empty, nothing to return! + if (fifo.entries == 0) { + return FIFO_EMPTY; + } + + // Entries in FIFO, pop + *dest = fifo.buffer[fifo.rd_ptr]; + fifo.rd_ptr++; + fifo.entries--; + + // Ensure pointers don't overflow! + if (fifo.rd_ptr == FIFO_LEN) { + fifo.rd_ptr = 0; + } + + return FIFO_SUCCESS; +} + +fifo_err_t fifo_push(FIFO_TYPE src) { + // FIFO is full, don't store anything + if (fifo.entries == FIFO_LEN) { + return FIFO_FULL; + } + + // FIFO has space, write + fifo.buffer[fifo.wr_ptr] = src; + fifo.wr_ptr++; + fifo.entries++; + + // Ensure pointers don't overflow! + if (fifo.wr_ptr == FIFO_LEN) { + fifo.wr_ptr = 0; + } + + return FIFO_SUCCESS; +} \ No newline at end of file diff --git a/sd_reader/fifo.h b/sd_reader/fifo.h new file mode 100644 index 0000000..67b502b --- /dev/null +++ b/sd_reader/fifo.h @@ -0,0 +1,36 @@ +#ifndef FIFO_H_ +#define FIFO_H_ + +#include + +typedef enum { + FIFO_SUCCESS, + FIFO_FULL, + FIFO_EMPTY +} fifo_err_t; + +#define FIFO_LEN 20 +#define FIFO_TYPE uint8_t + +/** @brief Initialises FIFO + * + * This MUST be called before any other fifo_* functions are called. + * Unless memory is initialised to 0. Then it's fine I guess. + */ +void fifo_init(); + +/** @brief Pops most recent addition off of FIFO buffer. + * + * @retval FIFO_SUCCESS Successfully read data from FIFO + * @retval FIFO_EMPTY Buffer was empty, no data read + */ +fifo_err_t fifo_pop(FIFO_TYPE* dest); + +/** @brief Pushes entry onto FIFO buffer. + * + * @retval FIFO_SUCCESS successfully wrote data to FIFO buffer + * @retval FIFO_FULL buffer, was full, no data written + */ +fifo_err_t fifo_push(FIFO_TYPE src); + +#endif \ No newline at end of file diff --git a/sd_reader/fifo.o b/sd_reader/fifo.o new file mode 100644 index 0000000000000000000000000000000000000000..ec8005faccb2ad1fcf8b2d7de3c98dabb7d57d65 GIT binary patch literal 6852 zcma)>e{5S<700iecKxve+1jpMHzrr>MzVRf-?QVSb_jl1YmvRr z{vs0j)`=I4na8nd%vqU49mSD-01dv!l=3?2TN^^tcTTNb%wNBNLF&OiHe+Dc*tM=UHLQ3yOcF_-~5WbcOn@iXT_}WyL2IpI7`_#s5*fX=&(xSn-VFGm3wv z_&vqTmxX>D#d{ThQ}LUM|E~DfTS7k%DjrjOQ1Q!(-%) z+>H%n*KSDh=fFo~O`lZ!b;V~{|20;<&3uUYSIo~aUu2$VzRG-<`C}_-Tz`c7n2)kO z%lsVk7&9&=lu70nnGZ34m-*Yw=a}DM{sHrEn9no+i}_9FRjX*+A2WY~`2zD0^DmkA zGXIhJAoHJ@Pc#38`E};^ncrppC-YyKKVa^HJmt zBgJxsHNNpX%owCi^9JmjO=yo>0qVT6=M98X^0*sfNx0;ef6&|`EqN0|i(m34VKM=y zOg7-O`DDOJHLsMK%l*d2O7ZU&y|pdJ;`9WoMfZFulaI{nQ3O5J;|8UyRFVu?xB?1pBc!}-~OB_P8I4baD9Q>n@i>#7cJG=%)Y4Y zp6cB;*hslK@{xkXO$H^GbozsmGjlVI%D$O$Dd?K^9jD2$NX2=Fv`>xF$NE&@84-CN z!g1UIjyFSEoN!2)){uhOZU8op(D_X$95-qM<^cM20R8RJ-ozm{NeWt$JU|{ZX`ayl z96F1JpGzheb*??yK!ZitqFis`SkWMc|NccA(p_Xxag{cZiUF5e<-&V2&Nbxi;Uu zIUX)hsv%vQ;@+Iw;;nsHSFUNBm%!PgLApq@4$>PhG^3pXhPmbdS*3#u6VAbLdq-ex zSr0`W$}s($IaUVq=!`C0jWD`EuEPvhBn&eUwnvc)SA};`YZQxSGZiip2QHyLT*S?t zcV;SF9FD*rQ?<7(hZ;qR!qr%GV<~0Zx&(9el%{Q20!>G3ZL88M>`;e`60TuqJ{DUM z6*vHyt+mZhi9fUH!U0)nw)O_zAsb#W>XmZC3b(jq%o($NXeb-)*}h{> zZHmYU1g!xm$@TE5=Vd6#a+*;0NBvhPy{{cnR}TcEmpeIxi&>O6d- zBje@k3H%LXxP0AyC!>B5_}8V&;6sioT?QXdROv!A56d~dGv5vmpW)hn2c>g*mwO5a z)pY(OYFqeiSzf$6Z+y&q#Pjf)0vPpS*YI|R=JYIH)b=qSEZ1vtLS4iC!jFULQla?209-9h>$pL<1l}x6*FzFaF%xf9yjwASYbBqgsVY7od;s>N z!ruU&5IzMyBm4^ZobXxj4~5Txe=1Dt_^$AI@E?R}FI^P=3HTD2VkW*ujQLYpfp4?c zZsLuvOg9S?sGD`ap0ZRgR}1lG`9RaeONC~^#Oa!hv$Y{z3KI~UIKIHpM;zb%|MfQt z72Vem3YRqgQk=Hfd){3zP}qagkjBM1g~nAlCk?gH{-QlV=k^Z#0azF7F9sv+O|9Fg zgjSb<%`!D#!Lb^_+B>Qp*MVKfcLo>Jf6 + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "main.h" + +#define DEBUG 1 + +const char* DATA_CHUNK_ID = "data"; + +/* ---- Control Flow Variables ---- */ +// Commands +uint8_t cmd_depth = 0; +uint8_t cmd_state; + +// Song playing +// TODO fix this atrocious naming +uint16_t song_buf[2][SONG_BUF_LEN]; //! Buffers to play song from +uint8_t song_position; //! where we are in the buffers +bool song_buf_select; //! which buffer we're using +bool song_selected; //! Flag to say we just selected the song, read data in +int8_t song_selection = -1; //! ID of selected song +uint32_t song_len; +uint32_t song_read_position; + + +typedef struct { + struct fat_file_struct* fd; + char* name; +} song_info_t; + +song_info_t songs[MAX_SONG_NUM]; + +int main() +{ + gpio_init(); + timer_init(); + usart_init(); + + // TODO set SPI to highest freq possible, might just need tweaking + sd_raw_init(); + /* we will just use ordinary idle mode */ + //set_sleep_mode(SLEEP_MODE_IDLE); + + /* open first partition */ + struct partition_struct* partition = partition_open(sd_raw_read, + sd_raw_read_interval, + 0, + 0, + 0 + ); + + if(!partition) + { + /* If the partition did not open, assume the storage device + * is a "superfloppy", i.e. has no MBR. + */ + partition = partition_open(sd_raw_read, + sd_raw_read_interval, + 0, + 0, + -1 + ); + } + + /* open file system */ + struct fat_fs_struct* fs = fat_open(partition); + + /* open root directory */ + struct fat_dir_entry_struct directory; + fat_get_dir_entry_of_path(fs, "/", &directory); + + struct fat_dir_struct* dd = fat_open_dir(fs, &directory); + + // Get all of the song names + struct fat_dir_entry_struct dir_entry; + uint8_t song_id = 0; + while(fat_read_dir(dd, &dir_entry)) { + // too many files! + if (song_id == MAX_SONG_NUM) { + break; + } + + // Is file, so add to list + if (!(dir_entry.attributes & FAT_ATTRIB_DIR)) { + // TODO add checking if its .wav + + // Copy song name into memory + uint8_t len = strlen(dir_entry.long_name); + songs[song_id].name = malloc(len); + strcpy(songs[song_id].name, dir_entry.long_name); + + // Open file and keep info in songs + fat_open_file(fs, (const struct fat_dir_entry_struct*) songs[song_id].fd); + + // Move on to reading next song + song_id++; + } + + } + + // Print out all files/directories in top level + while (1) { + /// do stuff + + // Handle incoming commands + if (cmd_depth == 0) { + if (fifo_pop(&cmd_state) == FIFO_SUCCESS) { + // Handle incoming command + switch(cmd_state) { + case (COMMS_CMD_PAUSE): + // Turn off interrupts for play timer to stop playing + TIFR0 &= ~(1 << OCF0A); + cmd_depth = 0; + break; + case (COMMS_CMD_PLAY): + // Turn on interrupts for play timer to start playing + TIFR0 |= (1 << OCF0A); + cmd_depth = 0; + break; + case (COMMS_CMD_SELECT_FILE): + // stop playing whatever song we have right now + // Wait for next loop to handle file + TIFR0 &= ~(1 << OCF0A); + cmd_depth = 1; + break; + case (COMMS_CMD_QUERY_NAME): + // Wait for next loop to handle file info + cmd_depth = 1; + break; + } + } + + } + + if (cmd_depth == 1) { + uint8_t song_id; + if (fifo_pop(&song_id) == FIFO_SUCCESS) { + switch (cmd_state) { + case (COMMS_CMD_SELECT_FILE): + // select proper song and begin reading in + song_selection = song_id; + song_selected = 1; + break; + case (COMMS_CMD_QUERY_NAME): + // Just going to hope we can bump the UART baudrate up high enough + comms_reply_name(songs[song_id].name); + break; + } + } + } + + // TODO check here if we are running into the end of our song buffer, and read in song data + if (song_position > 128 || song_selected) { + // Select opposite buffer + bool read_song_buf = !song_buf_select; + + // Need to get past the metadata + if (song_selected) { + // Seek beginning of file + fat_seek_file(songs[song_selection].fd, 0, FAT_SEEK_SET); + + // Iterate until we get the data + bool is_data = false; + ck_hdr_t header; + while (!is_data) { + fat_read_file(songs[song_selection].fd, + (uint8_t* ) &header, sizeof(ck_hdr_t)); + + // Ghetto strcmp + for (uint8_t i = 0; i < 4; i++) { + if (DATA_CHUNK_ID[i] == header.ckID[i]) { + is_data = true; + } else { + break; + } + } + + // If this is metadata, skip the chunk + int32_t* offset = NULL; + *offset = header.cksize; + if (!is_data) { + fat_seek_file(songs[song_selection].fd, + offset, FAT_SEEK_CUR); + } + } + + // We are now at the actual data + // divide by two to get uint16 + song_len = header.cksize / 2; + song_read_position = 0; + } + + // hit end of song, no more to read + if (song_read_position == song_len) { + song_read_position = 0; + TIFR0 &= ~(1 << OCF0A); + return 0; + } + + + // Read in file + // 512 bytes to read, 256 samples + fat_read_file(songs[song_selection].fd, (uint8_t*) song_buf[read_song_buf], 512); + song_read_position += 256; + + // If we selected the song, make sure we start playing from the right buffer + if (song_selected) { + song_buf_select = read_song_buf; + } + + // Ensure we don't "re-select" the song + song_selected = false; + } + } + + return 0; +} \ No newline at end of file diff --git a/sd_reader/main.h b/sd_reader/main.h new file mode 100644 index 0000000..9540d36 --- /dev/null +++ b/sd_reader/main.h @@ -0,0 +1,28 @@ +#ifndef MAIN_H_ +#define MAIN_H_ + +#include +#include +#include +#include +#include +#include "fat.h" +#include "fat_config.h" +#include "partition.h" +#include "sd_raw.h" +#include "sd_raw_config.h" +#include "fifo.h" +#include "comms.h" +#include "periph.h" +#include "wav.h" + +#define MAX_SONG_NUM 9 +#define SONG_BUF_LEN 256 // NO MORE THAN 256!!! + + +// Song playing +extern uint16_t song_buf[2][SONG_BUF_LEN]; //! Buffers to play song from +extern uint8_t song_position; //! where we are in the buffers +extern bool song_buf_select; //! which buffer we're using + +#endif \ No newline at end of file diff --git a/sd_reader/main.o b/sd_reader/main.o new file mode 100644 index 0000000000000000000000000000000000000000..8816389d1a75f6d068665e95b0b2ce2fb02ea13f GIT binary patch literal 16240 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 literal 0 HcmV?d00001 diff --git a/sd_reader/partition.c b/sd_reader/partition.c new file mode 100644 index 0000000..6c5d982 --- /dev/null +++ b/sd_reader/partition.c @@ -0,0 +1,155 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "byteordering.h" +#include "partition.h" +#include "partition_config.h" +#include "sd-reader_config.h" + +#include + +#if USE_DYNAMIC_MEMORY + #include +#endif + +/** + * \addtogroup partition Partition table support + * + * Support for reading partition tables and access to partitions. + * + * @{ + */ +/** + * \file + * Partition table implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup partition_config Configuration of partition table support + * Preprocessor defines to configure the partition support. + */ + +#if !USE_DYNAMIC_MEMORY +static struct partition_struct partition_handles[PARTITION_COUNT]; +#endif + +/** + * Opens a partition. + * + * Opens a partition by its index number and returns a partition + * handle which describes the opened partition. + * + * \note This function does not support extended partitions. + * + * \param[in] device_read A function pointer which is used to read from the disk. + * \param[in] device_read_interval A function pointer which is used to read in constant intervals from the disk. + * \param[in] device_write A function pointer which is used to write to the disk. + * \param[in] device_write_interval A function pointer which is used to write a data stream to disk. + * \param[in] index The index of the partition which should be opened, range 0 to 3. + * A negative value is allowed as well. In this case, the partition opened is + * not checked for existance, begins at offset zero, has a length of zero + * and is of an unknown type. Use this in case you want to open the whole device + * as a single partition (e.g. for "super floppy" use). + * \returns 0 on failure, a partition descriptor on success. + * \see partition_close + */ +struct partition_struct* partition_open(device_read_t device_read, device_read_interval_t device_read_interval, device_write_t device_write, device_write_interval_t device_write_interval, int8_t index) +{ + struct partition_struct* new_partition = 0; + uint8_t buffer[0x10]; + + if(!device_read || !device_read_interval || index >= 4) + return 0; + + if(index >= 0) + { + /* read specified partition table index */ + if(!device_read(0x01be + index * 0x10, buffer, sizeof(buffer))) + return 0; + + /* abort on empty partition entry */ + if(buffer[4] == 0x00) + return 0; + } + + /* allocate partition descriptor */ +#if USE_DYNAMIC_MEMORY + new_partition = malloc(sizeof(*new_partition)); + if(!new_partition) + return 0; +#else + new_partition = partition_handles; + uint8_t i; + for(i = 0; i < PARTITION_COUNT; ++i) + { + if(new_partition->type == PARTITION_TYPE_FREE) + break; + + ++new_partition; + } + if(i >= PARTITION_COUNT) + return 0; +#endif + + memset(new_partition, 0, sizeof(*new_partition)); + + /* fill partition descriptor */ + new_partition->device_read = device_read; + new_partition->device_read_interval = device_read_interval; + new_partition->device_write = device_write; + new_partition->device_write_interval = device_write_interval; + + if(index >= 0) + { + new_partition->type = buffer[4]; + new_partition->offset = read32(&buffer[8]); + new_partition->length = read32(&buffer[12]); + } + else + { + new_partition->type = 0xff; + } + + return new_partition; +} + +/** + * Closes a partition. + * + * This function destroys a partition descriptor which was + * previously obtained from a call to partition_open(). + * When this function returns, the given descriptor will be + * invalid. + * + * \param[in] partition The partition descriptor to destroy. + * \returns 0 on failure, 1 on success. + * \see partition_open + */ +uint8_t partition_close(struct partition_struct* partition) +{ + if(!partition) + return 0; + + /* destroy partition descriptor */ +#if USE_DYNAMIC_MEMORY + free(partition); +#else + partition->type = PARTITION_TYPE_FREE; +#endif + + return 1; +} + +/** + * @} + */ + diff --git a/sd_reader/partition.h b/sd_reader/partition.h new file mode 100644 index 0000000..d559c6e --- /dev/null +++ b/sd_reader/partition.h @@ -0,0 +1,212 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef PARTITION_H +#define PARTITION_H + +#include +#include "sd_raw_config.h" +#include "partition_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup partition + * + * @{ + */ +/** + * \file + * Partition table header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * The partition table entry is not used. + */ +#define PARTITION_TYPE_FREE 0x00 +/** + * The partition contains a FAT12 filesystem. + */ +#define PARTITION_TYPE_FAT12 0x01 +/** + * The partition contains a FAT16 filesystem with 32MB maximum. + */ +#define PARTITION_TYPE_FAT16_32MB 0x04 +/** + * The partition is an extended partition with its own partition table. + */ +#define PARTITION_TYPE_EXTENDED 0x05 +/** + * The partition contains a FAT16 filesystem. + */ +#define PARTITION_TYPE_FAT16 0x06 +/** + * The partition contains a FAT32 filesystem. + */ +#define PARTITION_TYPE_FAT32 0x0b +/** + * The partition contains a FAT32 filesystem with LBA. + */ +#define PARTITION_TYPE_FAT32_LBA 0x0c +/** + * The partition contains a FAT16 filesystem with LBA. + */ +#define PARTITION_TYPE_FAT16_LBA 0x0e +/** + * The partition is an extended partition with LBA. + */ +#define PARTITION_TYPE_EXTENDED_LBA 0x0f +/** + * The partition has an unknown type. + */ +#define PARTITION_TYPE_UNKNOWN 0xff + +/** + * A function pointer used to read from the partition. + * + * \param[in] offset The offset on the device where to start reading. + * \param[out] buffer The buffer into which to place the data. + * \param[in] length The count of bytes to read. + */ +typedef uint8_t (*device_read_t)(offset_t offset, uint8_t* buffer, uintptr_t length); +/** + * A function pointer passed to a \c device_read_interval_t. + * + * \param[in] buffer The buffer which contains the data just read. + * \param[in] offset The offset from which the data in \c buffer was read. + * \param[in] p An opaque pointer. + * \see device_read_interval_t + */ +typedef uint8_t (*device_read_callback_t)(uint8_t* buffer, offset_t offset, void* p); +/** + * A function pointer used to continuously read units of \c interval bytes + * and call a callback function. + * + * This function starts reading at the specified offset. Every \c interval bytes, + * it calls the callback function with the associated data buffer. + * + * By returning zero, the callback may stop reading. + * + * \param[in] offset Offset from which to start reading. + * \param[in] buffer Pointer to a buffer which is at least interval bytes in size. + * \param[in] interval Number of bytes to read before calling the callback function. + * \param[in] length Number of bytes to read altogether. + * \param[in] callback The function to call every interval bytes. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see device_read_t + */ +typedef uint8_t (*device_read_interval_t)(offset_t offset, uint8_t* buffer, uintptr_t interval, uintptr_t length, device_read_callback_t callback, void* p); +/** + * A function pointer used to write to the partition. + * + * \param[in] offset The offset on the device where to start writing. + * \param[in] buffer The buffer which to write. + * \param[in] length The count of bytes to write. + */ +typedef uint8_t (*device_write_t)(offset_t offset, const uint8_t* buffer, uintptr_t length); +/** + * A function pointer passed to a \c device_write_interval_t. + * + * \param[in] buffer The buffer which receives the data to write. + * \param[in] offset The offset to which the data in \c buffer will be written. + * \param[in] p An opaque pointer. + * \returns The number of bytes put into \c buffer + * \see device_write_interval_t + */ +typedef uintptr_t (*device_write_callback_t)(uint8_t* buffer, offset_t offset, void* p); +/** + * A function pointer used to continuously write a data stream obtained from + * a callback function. + * + * This function starts writing at the specified offset. To obtain the + * next bytes to write, it calls the callback function. The callback fills the + * provided data buffer and returns the number of bytes it has put into the buffer. + * + * By returning zero, the callback may stop writing. + * + * \param[in] offset Offset where to start writing. + * \param[in] buffer Pointer to a buffer which is used for the callback function. + * \param[in] length Number of bytes to write in total. May be zero for endless writes. + * \param[in] callback The function used to obtain the bytes to write. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see device_write_t + */ +typedef uint8_t (*device_write_interval_t)(offset_t offset, uint8_t* buffer, uintptr_t length, device_write_callback_t callback, void* p); + +/** + * Describes a partition. + */ +struct partition_struct +{ + /** + * The function which reads data from the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_read_t device_read; + /** + * The function which repeatedly reads a constant amount of data from the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_read_interval_t device_read_interval; + /** + * The function which writes data to the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_write_t device_write; + /** + * The function which repeatedly writes data to the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_write_interval_t device_write_interval; + + /** + * The type of the partition. + * + * Compare this value to the PARTITION_TYPE_* constants. + */ + uint8_t type; + /** + * The offset in blocks on the disk where this partition starts. + */ + uint32_t offset; + /** + * The length in blocks of this partition. + */ + uint32_t length; +}; + +struct partition_struct* partition_open(device_read_t device_read, device_read_interval_t device_read_interval, device_write_t device_write, device_write_interval_t device_write_interval, int8_t index); +uint8_t partition_close(struct partition_struct* partition); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/partition.o b/sd_reader/partition.o new file mode 100644 index 0000000000000000000000000000000000000000..1f4a759c00dc211e910567aed814cf75d303f458 GIT binary patch literal 8220 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 wpYD%;*1#F?H+U9`Rb11C~D+yDRo literal 0 HcmV?d00001 diff --git a/sd_reader/partition_config.h b/sd_reader/partition_config.h new file mode 100644 index 0000000..0b49138 --- /dev/null +++ b/sd_reader/partition_config.h @@ -0,0 +1,44 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef PARTITION_CONFIG_H +#define PARTITION_CONFIG_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup partition + * + * @{ + */ +/** + * \file + * Partition configuration (license: GPLv2 or LGPLv2.1) + */ + +/** + * \ingroup partition_config + * Maximum number of partition handles. + */ +#define PARTITION_COUNT 1 + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/periph.c b/sd_reader/periph.c new file mode 100644 index 0000000..e0a2f36 --- /dev/null +++ b/sd_reader/periph.c @@ -0,0 +1,91 @@ +#include "main.h" +#include + +/** @brief intercepts incoming Serial commands + */ +ISR(USART_RX_vect) { + cli(); + // Read byte into FIFO + fifo_push(UDR0); + + // Flag is cleared by reading data from UDR + sei(); +} + +/** @brief Interrupt handler for actually playing music + * + * Will only be called if the TIFR OCF0A flag is high, using that as our play/pause flag + * + * This may be possible to hand-optimise better, but I cut it down to ~40 instructions + * which should be fine at 16MHz + */ +ISR(TIMER0_COMPA_vect) { + cli(); + // Handle music playing + /* DAC0 -> DAC5 are on PC0 -> PC5 + * Nothing relevent is on the rest of PORTC + * + * DAC6 -> DAC11 is PD2->PD7 + * + * DAC12 and DAC13 are PB0 and PB1 + */ + uint16_t in_data = song_buf[song_buf_select][song_position]; + // Split into 8 bit because we are using an 8 bit MCU, so code can be better + uint8_t data_lo = in_data; + uint8_t data_hi = in_data >> 8; + + // DAC0 -> DAC5 + PORTC = (data_lo >> 2) & 0x3F; + // DAC6 -> DAC11 + PORTD &= 0x03; + PORTD |= data_hi << 2; + // DAC12 and DAC13 + PORTB &= 0xFC; // clear bits + PORTB |= data_hi >> 6; + + // Go to next sample + song_position++; + // Can just wrap if it's 8 bit + sei(); +} + +void gpio_init() { + // Initialise all DAC outputs + /* DAC0 -> DAC5 are on PC0 -> PC5 + * Nothing relevent is on the rest of PORTC + * + * DAC6 -> DAC11 is PD2->PD7 + * + * DAC12 and DAC13 are PB0 and PB1 + */ + DDRC |= 0x3F; + DDRD |= 0xFC; + DDRC |= 0x03; +} + +void timer_init() { + // Compare outputs should be default, nothing + // Set to CTC, WGM = 0b010 + TCCR0A = (1 << OCIE0A); + + // Set timer to use clock input w/o prescaling + TCCR0B = 1; + + // 16 MHz / 44kHz = 182 clock cycles + // Pushing it for time, may have to move to 8 bit... + OCR0A = 182; + + // Don't enable the interrupt yet +} + +void usart_init() { + // set baudrate + UBRR0L = UBRR_VALUE & 255; + UBRR0H = UBRR_VALUE >> 8; + + // Set data info, no parity, 1 stop, 8-bit + UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00); + + // Start it up + UCSR0B |= (1 << TXEN0) | (1 << RXEN0); +} \ No newline at end of file diff --git a/sd_reader/periph.h b/sd_reader/periph.h new file mode 100644 index 0000000..30568f9 --- /dev/null +++ b/sd_reader/periph.h @@ -0,0 +1,13 @@ +#ifndef PERIPH_H_ +#define PERIPH_H_ + +#define USART_BAUDRATE 9600 //! USART baudrate, change this to set it. +#define UBRR_VALUE (((F_CPU/(USART_BAUDRATE*16UL)))-1) + +// TODO comments + +void gpio_init(); +void timer_init(); +void usart_init(); + +#endif \ No newline at end of file diff --git a/sd_reader/periph.o b/sd_reader/periph.o new file mode 100644 index 0000000000000000000000000000000000000000..1c97c960778166188e9dc484e0009c9567aebdd6 GIT binary patch literal 10808 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 dPD6<91%6_~{BFP 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 literal 0 HcmV?d00001 diff --git a/sd_reader/sd-reader_config.h b/sd_reader/sd-reader_config.h new file mode 100644 index 0000000..16957f8 --- /dev/null +++ b/sd_reader/sd-reader_config.h @@ -0,0 +1,53 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef SD_READER_CONFIG_H +#define SD_READER_CONFIG_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup config Sd-reader configuration + * + * @{ + */ + +/** + * \file + * Common sd-reader configuration used by all modules (license: GPLv2 or LGPLv2.1) + * + * \note This file contains only configuration items relevant to + * all sd-reader implementation files. For module specific configuration + * options, please see the files fat_config.h, partition_config.h + * and sd_raw_config.h. + */ + +/** + * Controls allocation of memory. + * + * Set to 1 to use malloc()/free() for allocation of structures + * like file and directory handles, set to 0 to use pre-allocated + * fixed-size handle arrays. + */ +#define USE_DYNAMIC_MEMORY 0 + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/sd_raw.c b/sd_reader/sd_raw.c new file mode 100644 index 0000000..5e80ed3 --- /dev/null +++ b/sd_reader/sd_raw.c @@ -0,0 +1,998 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include +#include +#include "sd_raw.h" + +/** + * \addtogroup sd_raw MMC/SD/SDHC card raw access + * + * This module implements read and write access to MMC, SD + * and SDHC cards. It serves as a low-level driver for the + * higher level modules such as partition and file system + * access. + * + * @{ + */ +/** + * \file + * MMC/SD/SDHC raw access implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup sd_raw_config MMC/SD configuration + * Preprocessor defines to configure the MMC/SD support. + */ + +/** + * @} + */ + +/* commands available in SPI mode */ + +/* CMD0: response R1 */ +#define CMD_GO_IDLE_STATE 0x00 +/* CMD1: response R1 */ +#define CMD_SEND_OP_COND 0x01 +/* CMD8: response R7 */ +#define CMD_SEND_IF_COND 0x08 +/* CMD9: response R1 */ +#define CMD_SEND_CSD 0x09 +/* CMD10: response R1 */ +#define CMD_SEND_CID 0x0a +/* CMD12: response R1 */ +#define CMD_STOP_TRANSMISSION 0x0c +/* CMD13: response R2 */ +#define CMD_SEND_STATUS 0x0d +/* CMD16: arg0[31:0]: block length, response R1 */ +#define CMD_SET_BLOCKLEN 0x10 +/* CMD17: arg0[31:0]: data address, response R1 */ +#define CMD_READ_SINGLE_BLOCK 0x11 +/* CMD18: arg0[31:0]: data address, response R1 */ +#define CMD_READ_MULTIPLE_BLOCK 0x12 +/* CMD24: arg0[31:0]: data address, response R1 */ +#define CMD_WRITE_SINGLE_BLOCK 0x18 +/* CMD25: arg0[31:0]: data address, response R1 */ +#define CMD_WRITE_MULTIPLE_BLOCK 0x19 +/* CMD27: response R1 */ +#define CMD_PROGRAM_CSD 0x1b +/* CMD28: arg0[31:0]: data address, response R1b */ +#define CMD_SET_WRITE_PROT 0x1c +/* CMD29: arg0[31:0]: data address, response R1b */ +#define CMD_CLR_WRITE_PROT 0x1d +/* CMD30: arg0[31:0]: write protect data address, response R1 */ +#define CMD_SEND_WRITE_PROT 0x1e +/* CMD32: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_SECTOR_START 0x20 +/* CMD33: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_SECTOR_END 0x21 +/* CMD34: arg0[31:0]: data address, response R1 */ +#define CMD_UNTAG_SECTOR 0x22 +/* CMD35: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_ERASE_GROUP_START 0x23 +/* CMD36: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_ERASE_GROUP_END 0x24 +/* CMD37: arg0[31:0]: data address, response R1 */ +#define CMD_UNTAG_ERASE_GROUP 0x25 +/* CMD38: arg0[31:0]: stuff bits, response R1b */ +#define CMD_ERASE 0x26 +/* ACMD41: arg0[31:0]: OCR contents, response R1 */ +#define CMD_SD_SEND_OP_COND 0x29 +/* CMD42: arg0[31:0]: stuff bits, response R1b */ +#define CMD_LOCK_UNLOCK 0x2a +/* CMD55: arg0[31:0]: stuff bits, response R1 */ +#define CMD_APP 0x37 +/* CMD58: arg0[31:0]: stuff bits, response R3 */ +#define CMD_READ_OCR 0x3a +/* CMD59: arg0[31:1]: stuff bits, arg0[0:0]: crc option, response R1 */ +#define CMD_CRC_ON_OFF 0x3b + +/* command responses */ +/* R1: size 1 byte */ +#define R1_IDLE_STATE 0 +#define R1_ERASE_RESET 1 +#define R1_ILL_COMMAND 2 +#define R1_COM_CRC_ERR 3 +#define R1_ERASE_SEQ_ERR 4 +#define R1_ADDR_ERR 5 +#define R1_PARAM_ERR 6 +/* R1b: equals R1, additional busy bytes */ +/* R2: size 2 bytes */ +#define R2_CARD_LOCKED 0 +#define R2_WP_ERASE_SKIP 1 +#define R2_ERR 2 +#define R2_CARD_ERR 3 +#define R2_CARD_ECC_FAIL 4 +#define R2_WP_VIOLATION 5 +#define R2_INVAL_ERASE 6 +#define R2_OUT_OF_RANGE 7 +#define R2_CSD_OVERWRITE 7 +#define R2_IDLE_STATE (R1_IDLE_STATE + 8) +#define R2_ERASE_RESET (R1_ERASE_RESET + 8) +#define R2_ILL_COMMAND (R1_ILL_COMMAND + 8) +#define R2_COM_CRC_ERR (R1_COM_CRC_ERR + 8) +#define R2_ERASE_SEQ_ERR (R1_ERASE_SEQ_ERR + 8) +#define R2_ADDR_ERR (R1_ADDR_ERR + 8) +#define R2_PARAM_ERR (R1_PARAM_ERR + 8) +/* R3: size 5 bytes */ +#define R3_OCR_MASK (0xffffffffUL) +#define R3_IDLE_STATE (R1_IDLE_STATE + 32) +#define R3_ERASE_RESET (R1_ERASE_RESET + 32) +#define R3_ILL_COMMAND (R1_ILL_COMMAND + 32) +#define R3_COM_CRC_ERR (R1_COM_CRC_ERR + 32) +#define R3_ERASE_SEQ_ERR (R1_ERASE_SEQ_ERR + 32) +#define R3_ADDR_ERR (R1_ADDR_ERR + 32) +#define R3_PARAM_ERR (R1_PARAM_ERR + 32) +/* Data Response: size 1 byte */ +#define DR_STATUS_MASK 0x0e +#define DR_STATUS_ACCEPTED 0x05 +#define DR_STATUS_CRC_ERR 0x0a +#define DR_STATUS_WRITE_ERR 0x0c + +/* status bits for card types */ +#define SD_RAW_SPEC_1 0 +#define SD_RAW_SPEC_2 1 +#define SD_RAW_SPEC_SDHC 2 + +#if !SD_RAW_SAVE_RAM +/* static data buffer for acceleration */ +static uint8_t raw_block[512]; +/* offset where the data within raw_block lies on the card */ +static offset_t raw_block_address; +#if SD_RAW_WRITE_BUFFERING +/* flag to remember if raw_block was written to the card */ +static uint8_t raw_block_written; +#endif +#endif + +/* card type state */ +static uint8_t sd_raw_card_type; + +/* private helper functions */ +static void sd_raw_send_byte(uint8_t b); +static uint8_t sd_raw_rec_byte(); +static uint8_t sd_raw_send_command(uint8_t command, uint32_t arg); + +/** + * \ingroup sd_raw + * Initializes memory card communication. + * + * \returns 0 on failure, 1 on success. + */ +uint8_t sd_raw_init() +{ + /* enable inputs for reading card status */ + configure_pin_available(); + configure_pin_locked(); + + /* enable outputs for MOSI, SCK, SS, input for MISO */ + configure_pin_mosi(); + configure_pin_sck(); + configure_pin_ss(); + configure_pin_miso(); + + unselect_card(); + + /* initialize SPI with lowest frequency; max. 400kHz during identification mode of card */ + SPCR = (0 << SPIE) | /* SPI Interrupt Enable */ + (1 << SPE) | /* SPI Enable */ + (0 << DORD) | /* Data Order: MSB first */ + (1 << MSTR) | /* Master mode */ + (0 << CPOL) | /* Clock Polarity: SCK low when idle */ + (0 << CPHA) | /* Clock Phase: sample on rising SCK edge */ + (1 << SPR1) | /* Clock Frequency: f_OSC / 128 */ + (1 << SPR0); + SPSR &= ~(1 << SPI2X); /* No doubled clock frequency */ + + /* initialization procedure */ + sd_raw_card_type = 0; + + if(!sd_raw_available()) + return 0; + + /* card needs 74 cycles minimum to start up */ + for(uint8_t i = 0; i < 10; ++i) + { + /* wait 8 clock cycles */ + sd_raw_rec_byte(); + } + + /* address card */ + select_card(); + + /* reset card */ + uint8_t response; + for(uint16_t i = 0; ; ++i) + { + response = sd_raw_send_command(CMD_GO_IDLE_STATE, 0); + if(response == (1 << R1_IDLE_STATE)) + break; + + if(i == 0x1ff) + { + unselect_card(); + return 0; + } + } + +#if SD_RAW_SDHC + /* check for version of SD card specification */ + response = sd_raw_send_command(CMD_SEND_IF_COND, 0x100 /* 2.7V - 3.6V */ | 0xaa /* test pattern */); + if((response & (1 << R1_ILL_COMMAND)) == 0) + { + sd_raw_rec_byte(); + sd_raw_rec_byte(); + if((sd_raw_rec_byte() & 0x01) == 0) + return 0; /* card operation voltage range doesn't match */ + if(sd_raw_rec_byte() != 0xaa) + return 0; /* wrong test pattern */ + + /* card conforms to SD 2 card specification */ + sd_raw_card_type |= (1 << SD_RAW_SPEC_2); + } + else +#endif + { + /* determine SD/MMC card type */ + sd_raw_send_command(CMD_APP, 0); + response = sd_raw_send_command(CMD_SD_SEND_OP_COND, 0); + if((response & (1 << R1_ILL_COMMAND)) == 0) + { + /* card conforms to SD 1 card specification */ + sd_raw_card_type |= (1 << SD_RAW_SPEC_1); + } + else + { + /* MMC card */ + } + } + + /* wait for card to get ready */ + for(uint16_t i = 0; ; ++i) + { + if(sd_raw_card_type & ((1 << SD_RAW_SPEC_1) | (1 << SD_RAW_SPEC_2))) + { + uint32_t arg = 0; +#if SD_RAW_SDHC + if(sd_raw_card_type & (1 << SD_RAW_SPEC_2)) + arg = 0x40000000; +#endif + sd_raw_send_command(CMD_APP, 0); + response = sd_raw_send_command(CMD_SD_SEND_OP_COND, arg); + } + else + { + response = sd_raw_send_command(CMD_SEND_OP_COND, 0); + } + + if((response & (1 << R1_IDLE_STATE)) == 0) + break; + + if(i == 0x7fff) + { + unselect_card(); + return 0; + } + } + +#if SD_RAW_SDHC + if(sd_raw_card_type & (1 << SD_RAW_SPEC_2)) + { + if(sd_raw_send_command(CMD_READ_OCR, 0)) + { + unselect_card(); + return 0; + } + + if(sd_raw_rec_byte() & 0x40) + sd_raw_card_type |= (1 << SD_RAW_SPEC_SDHC); + + sd_raw_rec_byte(); + sd_raw_rec_byte(); + sd_raw_rec_byte(); + } +#endif + + /* set block size to 512 bytes */ + if(sd_raw_send_command(CMD_SET_BLOCKLEN, 512)) + { + unselect_card(); + return 0; + } + + /* deaddress card */ + unselect_card(); + + /* switch to highest SPI frequency possible */ + SPCR &= ~((1 << SPR1) | (1 << SPR0)); /* Clock Frequency: f_OSC / 4 */ + SPSR |= (1 << SPI2X); /* Doubled Clock Frequency: f_OSC / 2 */ + +#if !SD_RAW_SAVE_RAM + /* the first block is likely to be accessed first, so precache it here */ + raw_block_address = (offset_t) -1; +#if SD_RAW_WRITE_BUFFERING + raw_block_written = 1; +#endif + if(!sd_raw_read(0, raw_block, sizeof(raw_block))) + return 0; +#endif + + return 1; +} + +/** + * \ingroup sd_raw + * Checks wether a memory card is located in the slot. + * + * \returns 1 if the card is available, 0 if it is not. + */ +uint8_t sd_raw_available() +{ + return get_pin_available() == 0x00; +} + +/** + * \ingroup sd_raw + * Checks wether the memory card is locked for write access. + * + * \returns 1 if the card is locked, 0 if it is not. + */ +uint8_t sd_raw_locked() +{ + return get_pin_locked() == 0x00; +} + +/** + * \ingroup sd_raw + * Sends a raw byte to the memory card. + * + * \param[in] b The byte to sent. + * \see sd_raw_rec_byte + */ +void sd_raw_send_byte(uint8_t b) +{ + SPDR = b; + /* wait for byte to be shifted out */ + while(!(SPSR & (1 << SPIF))); + SPSR &= ~(1 << SPIF); +} + +/** + * \ingroup sd_raw + * Receives a raw byte from the memory card. + * + * \returns The byte which should be read. + * \see sd_raw_send_byte + */ +uint8_t sd_raw_rec_byte() +{ + /* send dummy data for receiving some */ + SPDR = 0xff; + while(!(SPSR & (1 << SPIF))); + SPSR &= ~(1 << SPIF); + + return SPDR; +} + +/** + * \ingroup sd_raw + * Send a command to the memory card which responses with a R1 response (and possibly others). + * + * \param[in] command The command to send. + * \param[in] arg The argument for command. + * \returns The command answer. + */ +uint8_t sd_raw_send_command(uint8_t command, uint32_t arg) +{ + uint8_t response; + + /* wait some clock cycles */ + sd_raw_rec_byte(); + + /* send command via SPI */ + sd_raw_send_byte(0x40 | command); + sd_raw_send_byte((arg >> 24) & 0xff); + sd_raw_send_byte((arg >> 16) & 0xff); + sd_raw_send_byte((arg >> 8) & 0xff); + sd_raw_send_byte((arg >> 0) & 0xff); + switch(command) + { + case CMD_GO_IDLE_STATE: + sd_raw_send_byte(0x95); + break; + case CMD_SEND_IF_COND: + sd_raw_send_byte(0x87); + break; + default: + sd_raw_send_byte(0xff); + break; + } + + /* receive response */ + for(uint8_t i = 0; i < 10; ++i) + { + response = sd_raw_rec_byte(); + if(response != 0xff) + break; + } + + return response; +} + +/** + * \ingroup sd_raw + * Reads raw data from the card. + * + * \param[in] offset The offset from which to read. + * \param[out] buffer The buffer into which to write the data. + * \param[in] length The number of bytes to read. + * \returns 0 on failure, 1 on success. + * \see sd_raw_read_interval, sd_raw_write, sd_raw_write_interval + */ +uint8_t sd_raw_read(offset_t offset, uint8_t* buffer, uintptr_t length) +{ + offset_t block_address; + uint16_t block_offset; + uint16_t read_length; + while(length > 0) + { + /* determine byte count to read at once */ + block_offset = offset & 0x01ff; + block_address = offset - block_offset; + read_length = 512 - block_offset; /* read up to block border */ + if(read_length > length) + read_length = length; + +#if !SD_RAW_SAVE_RAM + /* check if the requested data is cached */ + if(block_address != raw_block_address) +#endif + { +#if SD_RAW_WRITE_BUFFERING + if(!sd_raw_sync()) + return 0; +#endif + + /* address card */ + select_card(); + + /* send single block request */ +#if SD_RAW_SDHC + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, (sd_raw_card_type & (1 << SD_RAW_SPEC_SDHC) ? block_address / 512 : block_address))) +#else + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, block_address)) +#endif + { + unselect_card(); + return 0; + } + + /* wait for data block (start byte 0xfe) */ + while(sd_raw_rec_byte() != 0xfe); + +#if SD_RAW_SAVE_RAM + /* read byte block */ + uint16_t read_to = block_offset + read_length; + for(uint16_t i = 0; i < 512; ++i) + { + uint8_t b = sd_raw_rec_byte(); + if(i >= block_offset && i < read_to) + *buffer++ = b; + } +#else + /* read byte block */ + uint8_t* cache = raw_block; + for(uint16_t i = 0; i < 512; ++i) + *cache++ = sd_raw_rec_byte(); + raw_block_address = block_address; + + memcpy(buffer, raw_block + block_offset, read_length); + buffer += read_length; +#endif + + /* read crc16 */ + sd_raw_rec_byte(); + sd_raw_rec_byte(); + + /* deaddress card */ + unselect_card(); + + /* let card some time to finish */ + sd_raw_rec_byte(); + } +#if !SD_RAW_SAVE_RAM + else + { + /* use cached data */ + memcpy(buffer, raw_block + block_offset, read_length); + buffer += read_length; + } +#endif + + length -= read_length; + offset += read_length; + } + + return 1; +} + +/** + * \ingroup sd_raw + * Continuously reads units of \c interval bytes and calls a callback function. + * + * This function starts reading at the specified offset. Every \c interval bytes, + * it calls the callback function with the associated data buffer. + * + * By returning zero, the callback may stop reading. + * + * \note Within the callback function, you can not start another read or + * write operation. + * \note This function only works if the following conditions are met: + * - (offset - (offset % 512)) % interval == 0 + * - length % interval == 0 + * + * \param[in] offset Offset from which to start reading. + * \param[in] buffer Pointer to a buffer which is at least interval bytes in size. + * \param[in] interval Number of bytes to read before calling the callback function. + * \param[in] length Number of bytes to read altogether. + * \param[in] callback The function to call every interval bytes. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see sd_raw_write_interval, sd_raw_read, sd_raw_write + */ +uint8_t sd_raw_read_interval(offset_t offset, uint8_t* buffer, uintptr_t interval, uintptr_t length, sd_raw_read_interval_handler_t callback, void* p) +{ + if(!buffer || interval == 0 || length < interval || !callback) + return 0; + +#if !SD_RAW_SAVE_RAM + while(length >= interval) + { + /* as reading is now buffered, we directly + * hand over the request to sd_raw_read() + */ + if(!sd_raw_read(offset, buffer, interval)) + return 0; + if(!callback(buffer, offset, p)) + break; + offset += interval; + length -= interval; + } + + return 1; +#else + /* address card */ + select_card(); + + uint16_t block_offset; + uint16_t read_length; + uint8_t* buffer_cur; + uint8_t finished = 0; + do + { + /* determine byte count to read at once */ + block_offset = offset & 0x01ff; + read_length = 512 - block_offset; + + /* send single block request */ +#if SD_RAW_SDHC + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, (sd_raw_card_type & (1 << SD_RAW_SPEC_SDHC) ? offset / 512 : offset - block_offset))) +#else + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, offset - block_offset)) +#endif + { + unselect_card(); + return 0; + } + + /* wait for data block (start byte 0xfe) */ + while(sd_raw_rec_byte() != 0xfe); + + /* read up to the data of interest */ + for(uint16_t i = 0; i < block_offset; ++i) + sd_raw_rec_byte(); + + /* read interval bytes of data and execute the callback */ + do + { + if(read_length < interval || length < interval) + break; + + buffer_cur = buffer; + for(uint16_t i = 0; i < interval; ++i) + *buffer_cur++ = sd_raw_rec_byte(); + + if(!callback(buffer, offset + (512 - read_length), p)) + { + finished = 1; + break; + } + + read_length -= interval; + length -= interval; + + } while(read_length > 0 && length > 0); + + /* read rest of data block */ + while(read_length-- > 0) + sd_raw_rec_byte(); + + /* read crc16 */ + sd_raw_rec_byte(); + sd_raw_rec_byte(); + + if(length < interval) + break; + + offset = offset - block_offset + 512; + + } while(!finished); + + /* deaddress card */ + unselect_card(); + + /* let card some time to finish */ + sd_raw_rec_byte(); + + return 1; +#endif +} + +#if DOXYGEN || SD_RAW_WRITE_SUPPORT +/** + * \ingroup sd_raw + * Writes raw data to the card. + * + * \note If write buffering is enabled, you might have to + * call sd_raw_sync() before disconnecting the card + * to ensure all remaining data has been written. + * + * \param[in] offset The offset where to start writing. + * \param[in] buffer The buffer containing the data to be written. + * \param[in] length The number of bytes to write. + * \returns 0 on failure, 1 on success. + * \see sd_raw_write_interval, sd_raw_read, sd_raw_read_interval + */ +uint8_t sd_raw_write(offset_t offset, const uint8_t* buffer, uintptr_t length) +{ + if(sd_raw_locked()) + return 0; + + offset_t block_address; + uint16_t block_offset; + uint16_t write_length; + while(length > 0) + { + /* determine byte count to write at once */ + block_offset = offset & 0x01ff; + block_address = offset - block_offset; + write_length = 512 - block_offset; /* write up to block border */ + if(write_length > length) + write_length = length; + + /* Merge the data to write with the content of the block. + * Use the cached block if available. + */ + if(block_address != raw_block_address) + { +#if SD_RAW_WRITE_BUFFERING + if(!sd_raw_sync()) + return 0; +#endif + + if(block_offset || write_length < 512) + { + if(!sd_raw_read(block_address, raw_block, sizeof(raw_block))) + return 0; + } + raw_block_address = block_address; + } + + if(buffer != raw_block) + { + memcpy(raw_block + block_offset, buffer, write_length); + +#if SD_RAW_WRITE_BUFFERING + raw_block_written = 0; + + if(length == write_length) + return 1; +#endif + } + + /* address card */ + select_card(); + + /* send single block request */ +#if SD_RAW_SDHC + if(sd_raw_send_command(CMD_WRITE_SINGLE_BLOCK, (sd_raw_card_type & (1 << SD_RAW_SPEC_SDHC) ? block_address / 512 : block_address))) +#else + if(sd_raw_send_command(CMD_WRITE_SINGLE_BLOCK, block_address)) +#endif + { + unselect_card(); + return 0; + } + + /* send start byte */ + sd_raw_send_byte(0xfe); + + /* write byte block */ + uint8_t* cache = raw_block; + for(uint16_t i = 0; i < 512; ++i) + sd_raw_send_byte(*cache++); + + /* write dummy crc16 */ + sd_raw_send_byte(0xff); + sd_raw_send_byte(0xff); + + /* wait while card is busy */ + while(sd_raw_rec_byte() != 0xff); + sd_raw_rec_byte(); + + /* deaddress card */ + unselect_card(); + + buffer += write_length; + offset += write_length; + length -= write_length; + +#if SD_RAW_WRITE_BUFFERING + raw_block_written = 1; +#endif + } + + return 1; +} +#endif + +#if DOXYGEN || SD_RAW_WRITE_SUPPORT +/** + * \ingroup sd_raw + * Writes a continuous data stream obtained from a callback function. + * + * This function starts writing at the specified offset. To obtain the + * next bytes to write, it calls the callback function. The callback fills the + * provided data buffer and returns the number of bytes it has put into the buffer. + * + * By returning zero, the callback may stop writing. + * + * \param[in] offset Offset where to start writing. + * \param[in] buffer Pointer to a buffer which is used for the callback function. + * \param[in] length Number of bytes to write in total. May be zero for endless writes. + * \param[in] callback The function used to obtain the bytes to write. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see sd_raw_read_interval, sd_raw_write, sd_raw_read + */ +uint8_t sd_raw_write_interval(offset_t offset, uint8_t* buffer, uintptr_t length, sd_raw_write_interval_handler_t callback, void* p) +{ +#if SD_RAW_SAVE_RAM + #error "SD_RAW_WRITE_SUPPORT is not supported together with SD_RAW_SAVE_RAM" +#endif + + if(!buffer || !callback) + return 0; + + uint8_t endless = (length == 0); + while(endless || length > 0) + { + uint16_t bytes_to_write = callback(buffer, offset, p); + if(!bytes_to_write) + break; + if(!endless && bytes_to_write > length) + return 0; + + /* as writing is always buffered, we directly + * hand over the request to sd_raw_write() + */ + if(!sd_raw_write(offset, buffer, bytes_to_write)) + return 0; + + offset += bytes_to_write; + length -= bytes_to_write; + } + + return 1; +} +#endif + +#if DOXYGEN || SD_RAW_WRITE_SUPPORT +/** + * \ingroup sd_raw + * Writes the write buffer's content to the card. + * + * \note When write buffering is enabled, you should + * call this function before disconnecting the + * card to ensure all remaining data has been + * written. + * + * \returns 0 on failure, 1 on success. + * \see sd_raw_write + */ +uint8_t sd_raw_sync() +{ +#if SD_RAW_WRITE_BUFFERING + if(raw_block_written) + return 1; + if(!sd_raw_write(raw_block_address, raw_block, sizeof(raw_block))) + return 0; + raw_block_written = 1; +#endif + return 1; +} +#endif + +/** + * \ingroup sd_raw + * Reads informational data from the card. + * + * This function reads and returns the card's registers + * containing manufacturing and status information. + * + * \note: The information retrieved by this function is + * not required in any way to operate on the card, + * but it might be nice to display some of the data + * to the user. + * + * \param[in] info A pointer to the structure into which to save the information. + * \returns 0 on failure, 1 on success. + */ +uint8_t sd_raw_get_info(struct sd_raw_info* info) +{ + if(!info || !sd_raw_available()) + return 0; + + memset(info, 0, sizeof(*info)); + + select_card(); + + /* read cid register */ + if(sd_raw_send_command(CMD_SEND_CID, 0)) + { + unselect_card(); + return 0; + } + while(sd_raw_rec_byte() != 0xfe); + for(uint8_t i = 0; i < 18; ++i) + { + uint8_t b = sd_raw_rec_byte(); + + switch(i) + { + case 0: + info->manufacturer = b; + break; + case 1: + case 2: + info->oem[i - 1] = b; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + info->product[i - 3] = b; + break; + case 8: + info->revision = b; + break; + case 9: + case 10: + case 11: + case 12: + info->serial |= (uint32_t) b << ((12 - i) * 8); + break; + case 13: + info->manufacturing_year = b << 4; + break; + case 14: + info->manufacturing_year |= b >> 4; + info->manufacturing_month = b & 0x0f; + break; + } + } + + /* read csd register */ + uint8_t csd_read_bl_len = 0; + uint8_t csd_c_size_mult = 0; +#if SD_RAW_SDHC + uint16_t csd_c_size = 0; +#else + uint32_t csd_c_size = 0; +#endif + uint8_t csd_structure = 0; + if(sd_raw_send_command(CMD_SEND_CSD, 0)) + { + unselect_card(); + return 0; + } + while(sd_raw_rec_byte() != 0xfe); + for(uint8_t i = 0; i < 18; ++i) + { + uint8_t b = sd_raw_rec_byte(); + + if(i == 0) + { + csd_structure = b >> 6; + } + else if(i == 14) + { + if(b & 0x40) + info->flag_copy = 1; + if(b & 0x20) + info->flag_write_protect = 1; + if(b & 0x10) + info->flag_write_protect_temp = 1; + info->format = (b & 0x0c) >> 2; + } + else + { +#if SD_RAW_SDHC + if(csd_structure == 0x01) + { + switch(i) + { + case 7: + b &= 0x3f; + case 8: + case 9: + csd_c_size <<= 8; + csd_c_size |= b; + break; + } + if(i == 9) + { + ++csd_c_size; + info->capacity = (offset_t) csd_c_size * 512 * 1024; + } + } + else if(csd_structure == 0x00) +#endif + { + switch(i) + { + case 5: + csd_read_bl_len = b & 0x0f; + break; + case 6: + csd_c_size = b & 0x03; + csd_c_size <<= 8; + break; + case 7: + csd_c_size |= b; + csd_c_size <<= 2; + break; + case 8: + csd_c_size |= b >> 6; + ++csd_c_size; + break; + case 9: + csd_c_size_mult = b & 0x03; + csd_c_size_mult <<= 1; + break; + case 10: + csd_c_size_mult |= b >> 7; + + info->capacity = (uint32_t) csd_c_size << (csd_c_size_mult + csd_read_bl_len + 2); + break; + } + } + } + } + + unselect_card(); + + return 1; +} + diff --git a/sd_reader/sd_raw.h b/sd_reader/sd_raw.h new file mode 100644 index 0000000..6aa9750 --- /dev/null +++ b/sd_reader/sd_raw.h @@ -0,0 +1,148 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef SD_RAW_H +#define SD_RAW_H + +#include +#include "sd_raw_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup sd_raw + * + * @{ + */ +/** + * \file + * MMC/SD/SDHC raw access header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * The card's layout is harddisk-like, which means it contains + * a master boot record with a partition table. + */ +#define SD_RAW_FORMAT_HARDDISK 0 +/** + * The card contains a single filesystem and no partition table. + */ +#define SD_RAW_FORMAT_SUPERFLOPPY 1 +/** + * The card's layout follows the Universal File Format. + */ +#define SD_RAW_FORMAT_UNIVERSAL 2 +/** + * The card's layout is unknown. + */ +#define SD_RAW_FORMAT_UNKNOWN 3 + +/** + * This struct is used by sd_raw_get_info() to return + * manufacturing and status information of the card. + */ +struct sd_raw_info +{ + /** + * A manufacturer code globally assigned by the SD card organization. + */ + uint8_t manufacturer; + /** + * A string describing the card's OEM or content, globally assigned by the SD card organization. + */ + uint8_t oem[3]; + /** + * A product name. + */ + uint8_t product[6]; + /** + * The card's revision, coded in packed BCD. + * + * For example, the revision value \c 0x32 means "3.2". + */ + uint8_t revision; + /** + * A serial number assigned by the manufacturer. + */ + uint32_t serial; + /** + * The year of manufacturing. + * + * A value of zero means year 2000. + */ + uint8_t manufacturing_year; + /** + * The month of manufacturing. + */ + uint8_t manufacturing_month; + /** + * The card's total capacity in bytes. + */ + offset_t capacity; + /** + * Defines wether the card's content is original or copied. + * + * A value of \c 0 means original, \c 1 means copied. + */ + uint8_t flag_copy; + /** + * Defines wether the card's content is write-protected. + * + * \note This is an internal flag and does not represent the + * state of the card's mechanical write-protect switch. + */ + uint8_t flag_write_protect; + /** + * Defines wether the card's content is temporarily write-protected. + * + * \note This is an internal flag and does not represent the + * state of the card's mechanical write-protect switch. + */ + uint8_t flag_write_protect_temp; + /** + * The card's data layout. + * + * See the \c SD_RAW_FORMAT_* constants for details. + * + * \note This value is not guaranteed to match reality. + */ + uint8_t format; +}; + +typedef uint8_t (*sd_raw_read_interval_handler_t)(uint8_t* buffer, offset_t offset, void* p); +typedef uintptr_t (*sd_raw_write_interval_handler_t)(uint8_t* buffer, offset_t offset, void* p); + +uint8_t sd_raw_init(); +uint8_t sd_raw_available(); +uint8_t sd_raw_locked(); + +uint8_t sd_raw_read(offset_t offset, uint8_t* buffer, uintptr_t length); +uint8_t sd_raw_read_interval(offset_t offset, uint8_t* buffer, uintptr_t interval, uintptr_t length, sd_raw_read_interval_handler_t callback, void* p); +uint8_t sd_raw_write(offset_t offset, const uint8_t* buffer, uintptr_t length); +uint8_t sd_raw_write_interval(offset_t offset, uint8_t* buffer, uintptr_t length, sd_raw_write_interval_handler_t callback, void* p); +uint8_t sd_raw_sync(); + +uint8_t sd_raw_get_info(struct sd_raw_info* info); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/sd_raw.o b/sd_reader/sd_raw.o new file mode 100644 index 0000000000000000000000000000000000000000..77f3a8059db108cf2aa90b08b6465973d44613b4 GIT binary patch literal 23960 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 literal 0 HcmV?d00001 diff --git a/sd_reader/sd_raw_config.h b/sd_reader/sd_raw_config.h new file mode 100644 index 0000000..3d0a4da --- /dev/null +++ b/sd_reader/sd_raw_config.h @@ -0,0 +1,139 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef SD_RAW_CONFIG_H +#define SD_RAW_CONFIG_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup sd_raw + * + * @{ + */ +/** + * \file + * MMC/SD support configuration (license: GPLv2 or LGPLv2.1) + */ + +/** + * \ingroup sd_raw_config + * Controls MMC/SD write support. + * + * Set to 1 to enable MMC/SD write support, set to 0 to disable it. + */ +#define SD_RAW_WRITE_SUPPORT 0 + +/** + * \ingroup sd_raw_config + * Controls MMC/SD write buffering. + * + * Set to 1 to buffer write accesses, set to 0 to disable it. + * + * \note This option has no effect when SD_RAW_WRITE_SUPPORT is 0. + */ +#define SD_RAW_WRITE_BUFFERING 1 + +/** + * \ingroup sd_raw_config + * Controls MMC/SD access buffering. + * + * Set to 1 to save static RAM, but be aware that you will + * lose performance. + * + * \note When SD_RAW_WRITE_SUPPORT is 1, SD_RAW_SAVE_RAM will + * be reset to 0. + */ +#define SD_RAW_SAVE_RAM 1 + +/** + * \ingroup sd_raw_config + * Controls support for SDHC cards. + * + * Set to 1 to support so-called SDHC memory cards, i.e. SD + * cards with more than 2 gigabytes of memory. + */ +#define SD_RAW_SDHC 1 + +/** + * @} + */ + +/* defines for customisation of sd/mmc port access */ +#if defined(__AVR_ATmega8__) || \ + defined(__AVR_ATmega48__) || \ + defined(__AVR_ATmega48P__) || \ + defined(__AVR_ATmega88__) || \ + defined(__AVR_ATmega88P__) || \ + defined(__AVR_ATmega168__) || \ + defined(__AVR_ATmega168P__) || \ + defined(__AVR_ATmega328P__) + #define configure_pin_mosi() DDRB |= (1 << DDB3) + #define configure_pin_sck() DDRB |= (1 << DDB5) + #define configure_pin_ss() DDRB |= (1 << DDB2) + #define configure_pin_miso() DDRB &= ~(1 << DDB4) + + #define select_card() PORTB &= ~(1 << PORTB2) + #define unselect_card() PORTB |= (1 << PORTB2) +#elif defined(__AVR_ATmega16__) || \ + defined(__AVR_ATmega32__) + #define configure_pin_mosi() DDRB |= (1 << DDB5) + #define configure_pin_sck() DDRB |= (1 << DDB7) + #define configure_pin_ss() DDRB |= (1 << DDB4) + #define configure_pin_miso() DDRB &= ~(1 << DDB6) + + #define select_card() PORTB &= ~(1 << PORTB4) + #define unselect_card() PORTB |= (1 << PORTB4) +#elif defined(__AVR_ATmega64__) || \ + defined(__AVR_ATmega128__) || \ + defined(__AVR_ATmega169__) + #define configure_pin_mosi() DDRB |= (1 << DDB2) + #define configure_pin_sck() DDRB |= (1 << DDB1) + #define configure_pin_ss() DDRB |= (1 << DDB0) + #define configure_pin_miso() DDRB &= ~(1 << DDB3) + + #define select_card() PORTB &= ~(1 << PORTB0) + #define unselect_card() PORTB |= (1 << PORTB0) +#else + #error "no sd/mmc pin mapping available!" +#endif + +#define configure_pin_available() DDRC &= ~(1 << DDC4) +#define configure_pin_locked() DDRC &= ~(1 << DDC5) + +#define get_pin_available() (PINC & (1 << PINC4)) +#define get_pin_locked() (PINC & (1 << PINC5)) + +#if SD_RAW_SDHC + typedef uint64_t offset_t; +#else + typedef uint32_t offset_t; +#endif + +/* configuration checks */ +#if SD_RAW_WRITE_SUPPORT +#undef SD_RAW_SAVE_RAM +#define SD_RAW_SAVE_RAM 0 +#else +#undef SD_RAW_WRITE_BUFFERING +#define SD_RAW_WRITE_BUFFERING 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_reader/wav.h b/sd_reader/wav.h new file mode 100644 index 0000000..add32a6 --- /dev/null +++ b/sd_reader/wav.h @@ -0,0 +1,43 @@ +#include +#include +#include + +/** @brief Struct to contain chunk header information + */ +typedef struct { + char ckID[4]; //! Chunk type + uint32_t cksize; //! Size of chunk in bytes +} ck_hdr_t; + +/** @brief Struct to contain master WAV chunk info + */ +struct { + char WAVEID[4]; //! WAVE ID +} ck_master_t; + +/** @brief Types of extension that a fmt type chunk can have + */ +typedef enum { + CK_FMT_STD = 0U, + CK_FMT_NOEXT, + CK_FMT_EXT, +} ck_fmt_ext_t; + +/** @brief Struct to hold fmt chunk data + */ +struct { + struct { + uint16_t wFormatTag; //! Type of data format. + uint16_t nChannels; //! Number of channels + uint32_t nSamplesPerSec; //! Sample rate + uint32_t nAvgBytesPerSec; //! Data rate + uint16_t nBlockAlign; //! Block size in bytes for data + uint16_t wBitsPerSample; //! Bits per sample + uint16_t cbSize; //! Size of fmt type extension. Only valid if chunk size is 18 or 40. + uint16_t wValidBitsPerSample; //! Number of valid bits per sample. Only valid if chunk size is 40 + uint32_t dwChannelMask; //! Speaker position mask. Only valid if chunk size is 40 + uint8_t SubFormat[16]; //! GUID for data format. Only vaid if chunk size is 40 + } data; + + ck_fmt_ext_t type; +} ck_fmt_t;