mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			174 Commits
		
	
	
		
			FluxEngine
			...
			FluxEngine
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 035dd1fad1 | ||
|  | d2df79a665 | ||
|  | 103e0a13bb | ||
|  | d1b5eec84a | ||
|  | 6b1e6b31ed | ||
|  | f6f6db913e | ||
|  | ec0a6416fd | ||
|  | 1787402be9 | ||
|  | 5f6d99f138 | ||
|  | d1e2b0d1f8 | ||
|  | c2c51bbe33 | ||
|  | bb806e3853 | ||
|  | a11d0e75c8 | ||
|  | 5406ff0ea3 | ||
|  | c88317b44a | ||
|  | 6898062d66 | ||
|  | 6e1f264e6a | ||
|  | 082be14232 | ||
|  | 231aa44d03 | ||
|  | cdb12f35d4 | ||
|  | e831ee8b44 | ||
|  | 40e9a6082f | ||
|  | 53cec292d0 | ||
|  | 3f85309ee5 | ||
|  | 70944f8521 | ||
|  | 2ab00c42ff | ||
|  | a572742caa | ||
|  | 400e5f8580 | ||
|  | 74f0fd89b6 | ||
|  | 09f9bea7a2 | ||
|  | 8bffb38117 | ||
|  | eb5d545c35 | ||
|  | a79a545730 | ||
|  | 3863dab944 | ||
|  | e53b7ecd8b | ||
|  | 7d88673ed5 | ||
|  | 41f2da71e4 | ||
|  | cb4ee0fd74 | ||
|  | 088381a5a6 | ||
|  | 629af2a697 | ||
|  | 884edfd497 | ||
|  | 83dd9e462e | ||
|  | 70a6dfd98a | ||
|  | 7f5d96382b | ||
|  | fd4d1c4bb7 | ||
|  | 7eaf3de572 | ||
|  | 4b608de3fb | ||
|  | b47e6e852b | ||
|  | a8a8ce4a36 | ||
|  | c61376d5a1 | ||
|  | d3a5bb08d3 | ||
|  | f1506d0dbd | ||
|  | 15e6d4959e | ||
|  | 41216fd1cd | ||
|  | b8786866db | ||
|  | 82bd1bead4 | ||
|  | 6e2bdcad79 | ||
|  | ef3c9f3d03 | ||
|  | 5427f24df2 | ||
|  | b374340303 | ||
|  | c78ed2c6ad | ||
|  | 3b02bc8cf1 | ||
|  | c7e48a7e76 | ||
|  | 77d125c03d | ||
|  | 8aa52aeefd | ||
|  | 0bab038454 | ||
|  | 6c3b49f4d0 | ||
|  | 03dd689f17 | ||
|  | c375c948c0 | ||
|  | cbcf457ce3 | ||
|  | 4855f825e2 | ||
|  | 85bc1637f2 | ||
|  | 73398b83a9 | ||
|  | 2727e66d40 | ||
|  | 8b6be5a501 | ||
|  | 4fee29307c | ||
|  | 35f8249c67 | ||
|  | d1467a14b8 | ||
|  | 3e6b9eb74d | ||
|  | ce2e8fb4b5 | ||
|  | 7eaa75c05d | ||
|  | e86de4483a | ||
|  | 203a74713f | ||
|  | 59ed2a6793 | ||
|  | a03283ce64 | ||
|  | 984cdaeb03 | ||
|  | a1ed4a9171 | ||
|  | 93caf8e549 | ||
|  | 3841942153 | ||
|  | 5706877b67 | ||
|  | d60900262b | ||
|  | 54ea34400b | ||
|  | db2ab8841a | ||
|  | adba93ae0a | ||
|  | 98587d04a7 | ||
|  | 0051b64648 | ||
|  | 603009ba15 | ||
|  | adb9809692 | ||
|  | 06eb10d2a0 | ||
|  | 2244299bd9 | ||
|  | 6ca06ecafb | ||
|  | 9a5958f80b | ||
|  | 2b53ac057c | ||
|  | 5deba8af41 | ||
|  | 3c54a663b8 | ||
|  | 1fd65452c4 | ||
|  | 30646ccb07 | ||
|  | 5be7249a30 | ||
|  | 067af18103 | ||
|  | 8dbd2a72a7 | ||
|  | c29e131a3b | ||
|  | a9e30c1e49 | ||
|  | 972c8c6b61 | ||
|  | 2007ff7546 | ||
|  | 64694580cd | ||
|  | deaab94494 | ||
|  | 1509e1f89d | ||
|  | 29e1ddc2ff | ||
|  | 1fe6434563 | ||
|  | 0367b7e77d | ||
|  | e6da85bf64 | ||
|  | cd19fcdadd | ||
|  | 1954f02cfb | ||
|  | 39b23200b0 | ||
|  | 0644d6d965 | ||
|  | a075694d8e | ||
|  | b1ea5a9a35 | ||
|  | 00087cbb6b | ||
|  | 1b48ea20c4 | ||
|  | 3d0f019fc4 | ||
|  | a08bfc183f | ||
|  | c5aef9b051 | ||
|  | fc2655ecd6 | ||
|  | a737c723d3 | ||
|  | 37aa8b62b0 | ||
|  | a401173f6d | ||
|  | ce76dc4279 | ||
|  | 1025bd857b | ||
|  | 025802b2d0 | ||
|  | adbcb2cd31 | ||
|  | c47a563790 | ||
|  | 04c09d1a5b | ||
|  | 323da8272a | ||
|  | 38700c79fc | ||
|  | d504d1890a | ||
|  | d53e757cfb | ||
|  | 4983239458 | ||
|  | 376985828a | ||
|  | dce0a26820 | ||
|  | 14e0a67e7d | ||
|  | 1656947764 | ||
|  | 647862cdbd | ||
|  | 4a8d83838c | ||
|  | 8acf8e181d | ||
|  | 2df9920209 | ||
|  | 1a6c6b5420 | ||
|  | edc56d44d6 | ||
|  | ef4eff0195 | ||
|  | df8d45bf66 | ||
|  | 89a27619ff | ||
|  | 387a86969a | ||
|  | acb5059d17 | ||
|  | a4002d2617 | ||
|  | a63a90bbd0 | ||
|  | d25f96dd24 | ||
|  | e8febe6508 | ||
|  | ad3a930c6a | ||
|  | be41c1de76 | ||
|  | d528978667 | ||
|  | 827fcf69d2 | ||
|  | 711ff545e0 | ||
|  | 5befa31050 | ||
|  | 8e5c2d0ebb | ||
|  | f95fceeb3d | 
| @@ -1,6 +1,7 @@ | ||||
| version: '{branch}.{build}' | ||||
| clone_depth: 1 | ||||
| skip_tags: true | ||||
| image: Visual Studio 2019 | ||||
|  | ||||
| environment: | ||||
|   MSYSTEM: MINGW32 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ jobs: | ||||
|       with: | ||||
|         fetch-depth: 1 | ||||
|     - name: apt | ||||
|       run: sudo apt install libusb-1.0-0-dev libsqlite3-dev ninja-build | ||||
|       run: sudo apt update && sudo apt install libusb-1.0-0-dev libsqlite3-dev ninja-build | ||||
|     - name: make | ||||
|       run: make | ||||
|  | ||||
|   | ||||
| @@ -1,246 +1,246 @@ | ||||
| :4000000000800020110000007910000079100000064A08B5136843F020031360044B1A6803F53F5302331A6001F044F8E8460040FA46004010B5054C237833B9044B13B1D7 | ||||
| :400040000448AFF300800123237010BD6081FF1F0000000028360000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF00000000EB | ||||
| :400080006481FF1F28360000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F00F030133136011685368994202BF024B01221A72704700BF8081FF1F9E | ||||
| :4000C0000F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F00F030133536051681368994202BF024B01221A72704700BF8081FF1F0F000080024B012200205A722B | ||||
| :4001000002F086B88081FF1F10B5C4B2204601F077F90128FAD110BD08B572B60F4B0F49DA680132DA601A690132C82A08BF00221A615A6918690132A72A08BF00224A617F | ||||
| :400140005B69002B0CBF02230023002814BF184643F0010002F054FD62B608BD8081FF1F10B504460D4B8278997E91421CBF00225A7702689A610279094B1A718378002BB3 | ||||
| :4001800014BF0220012002F0FFFCE07802F0F6FC2079BDE8104002F02DBD00BF8081FF1F9881FF1F70B5C4B220460E4601F030F9314605460246204601F090F9204601F0D1 | ||||
| :4001C0001FF90128FAD0284670BD000038B50B4CA57F5DB904F11800FFF7C2FF012001F0ADFF4FF47A7002F04FF96577E36823620123A377BDE8384002F084B98081FF1F9D | ||||
| :4002000038B50446C5B2284601F0A4FF062002F051F944F00200C0B201F09CFF062002F049F9284601F096FFBDE83840062002F02BB910B5642401F087FF20B10120BDE8B6 | ||||
| :400240001040FFF7DDBF0120FFF7DAFF013CF2D1F4E7000038B5044D0024285D013402F0D9F8102CF9D138BDA481FF1F08B502F0F3FA002002F0FCFA02F00EFB02F018FB10 | ||||
| :4002800080B208BD10B50446012001F07BFF642002F0FAF8FFF7EAFF2080002001F072FF642002F0F1F8FFF7E1FF608010BD08B502F0FEFB002002F007FC02F019FC02F0AB | ||||
| :4002C00023FC80B208BD10B50446FFF7B2FF322002F0DAF8FFF7EBFF20800020FFF790FF322002F0D1F8FFF7E2FF608010BD0FB400B593B014AB53F8042B402102A80193D3 | ||||
| :4003000002F07AFD02A801F08AFF01F094FF13B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF7EFFE62782146BDE81040042001F080B83A36000007B50023B1 | ||||
| :40034000ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB0000F8B51D4C0646FFF733FF637F03B156B91A48FFF7BEFFFFF75EFF012000236077636302F048 | ||||
| :4003800099F83246616B1548FFF7B1FF114D0027636B9E4216D001F0D7FE00B16F63636B9E4205DD0020FFF72BFF6B6B013305E005DA0120FFF724FF6B6B013B6B6302F059 | ||||
| :4003C000A1F8E5E7322002F05FF8BDE8F8400448FFF78DBF8081FF1F473600004E3600006B3600002DE9F04F9BB062B602F0F4F8B749042002F018F9B64801F0B1FEB64878 | ||||
| :4004000002F0E4FBB54801F0E5FE02F0C5FA02F097F9002002F0B8FB01F000FF0221002000F0A4FFAE4D0321084602F04BF82E462C46DFF8D4B202F065F8AB7F73B12A6A46 | ||||
| :40044000EB689B1A41F28832934207D9002001F075FE002002F098FB0023AB7700F0BEFF18B9A048FFF743FF04E000F0BDFF0028F7D109E000F0B2FF0028FBD09A48FFF7F1 | ||||
| :4004800036FF032001F016F8032000F0B9FF0128D1D19648FFF764FE95490320FFF782FE94F838109348FFF722FF94F83830023B122B00F2F583DFE813F01300F3031C0015 | ||||
| :4004C000F3032200F3034400F3036800F303A001F3033503F3035403F3035A03F303650303238DF828308DF829300B238DF82A3044E394F83A00FFF731FF7F4B3BE3FFF7AA | ||||
| :4005000065FE00236372E068627A02F0FF0132B9EB681B1AB3F57A7FF6DD0B4608E03BB100227272F168627A12B9EB685B1AFAE707228DF8282004228DF82920ADF82A30A6 | ||||
| :400540001CE30220FFF7E0FD4FF000090DF1280A4FF480780027C8EB0903DA1907F80A200137402FF9D10220FFF7CEFD3A465146022000F061FFB8F10108EBD109F1010983 | ||||
| :40058000B9F1400FE4D15D4BC7E294F83A0001F0F9FD606BFFF7E2FE02F09AFB584BDFF86C811A78002742F004021A701A7842F001021A701A7802F0FE021A701A7802F0A2 | ||||
| :4005C000FE021A7002F088FB0220FFF79DFD41F6FF734FF480420121022002F0DBFA84F8780001F0FDFE08F807000137102FF8D1DFF81CA100270AF15D081FFA88F901377D | ||||
| :40060000102F14BF3A4600221AF8010F2244062392F82420402101F017FF4A4646F242419AF8000001F022FF09F14009102F1FFA89F9E4D196F83B3033B100237372637A9C | ||||
| :40064000002BFCD000237372142200210AA802F09FFB40234FF0FF320D9300232360626023722368274F234493F8241094F878000C9701F06DFE94F8780001F02BFE01210C | ||||
| :4006800094F8780001F0FEFD04972368002BFCD000277760D6F80CA0237A7BB901F032FFA98F04E0EB68CAEB03038B4206D2626823689A422ED12B7A002BF3D094F808801C | ||||
| :4006C000042194F878005FFA88F801F04DFE54E019010000F900000091000000C50000008081FF1F793600008C3600009881FF1FB881FF1F963600002C3600002E3600006F | ||||
| :40070000926400400086FF1F9C640040A481FF1FA381FF1F4FF0000962680AA808EB82124A440A92C9F140020B9200F0DBFA0B9A0137C2F1400209EB02030D9A5FFA83F900 | ||||
| :400740005AB90220FFF7E0FC4022BD49022000F073FE049B0C9340230D93B9F13F0FDBD96268B84B01321340002BBEBF03F1FF3363F00F03013363608EE794F8780001F0B3 | ||||
| :4007800003FE0028F9D10AA800F0E0FA0220FFF7BBFC0D9B402B07D002204022A84900F04BFE0220FFF7B0FC0D9B022033F040021DBFC3F1400292B2A149114600F03CFEEF | ||||
| :4007C0000220FFF7A1FCFFF745FDB8F1000F06D09D48FFF78CFD0220FFF7B0FD06E09B4B09A81B88ADF82430FFF796FD627A3946237A9748FFF77BFD55E29648FFF777FDEF | ||||
| :40080000E76B17F03F0701D003204AE2012001F0BFFC95F83A0001F0B5FC02F059FA9BF80030DFF8348203F0FB038BF800309BF8003043F001038BF800309BF8003003F027 | ||||
| :40084000FE038BF800309BF8003003F0FE038BF8003002F041FA686BFFF780FD01214FF4804341F6FF72084601F098FC85F8780001F0B6FD08F807000137102FF8D1DFF88D | ||||
| :40088000DC91002709F15D031FFA83F807930137102F14BF3A46002219F8010F2244052392F82420402101F0CFFD414646F24C4299F8000001F0DAFD08F14008102F1FFA56 | ||||
| :4008C00088F8E4D10027B946BA46F36B4FF0FF389B09142239460AA837600493C6F80480377202F055FA402301200D9300F0E2FDCDF81880059701F005FE2268514B01322B | ||||
| :400900001340002BBCBF03F1FF3363F00F036168B8BF01338B4200F0A380BAF1000F07D0237A002B40F0B0806B7A002B40F0AC800B9B002B34D1B9F1000F0BD07F22404926 | ||||
| :400940005A540133402BFAD10A910B9328E0BAF1000F06D1012000F053FD01288046F6D107E0237A002B40F08F806B7A002BF1D08AE03349FFF716FC8146314B0B90404665 | ||||
| :400980000A9300F097FDB9F13F0F07F1010706DD049BDB1BD3F1000949EB030900E0C1460B9BDBB12368079A0AA802EB83120D9BC3F1400313440C9300F0D7F90D9B6BB9FF | ||||
| :4009C0002A68204B01321340002BBEBF03F1FF3363F00F030133236040230D93636801333ED12B680F2B3BD14FF00008C5F8048001F002FC85F808806B6895F878002B44DD | ||||
| :400A000093F8241001F0A4FC95F8780001F062FC012195F8780001F035FC85F80980637A002BFCD04FF00008012086F8098001F0EFFB404601F0ACFBCDF8188013E000BF78 | ||||
| :400A40000086FF1F0F000080A536000030360000BF360000D2360000A481FF1FA381FF1FBAF1000F05D0237A73B96B7A63B94FF0010A6368069A93423FF43DAF059B0133AA | ||||
| :400A800005936B68069336E701F0BCFB012001F07FFB002001F0BCFB042194F8780001F063FC94F8780001F06FFC0028F9D196F8780001F0FDFB737A327A02930123039278 | ||||
| :400AC0000193CDF80090059B3A4604997A48FFF70EFCB9F1000F16D1049BBB420ADD012000F08EFC01288046F6D17449FFF75AFB3F2803DC012000F017FD04E0404600F04D | ||||
| :400B0000D9FC0137E8E7FFF7A5FB6D48FFF7EFFB237A0BB10220C4E06A4B1B8809A8ADF824302CE094F83A0001F02CFB606BFFF715FC6548FFF7DBFB00236372637A002B58 | ||||
| :400B4000FCD0012001F064FB00237372637A002BFCD0002001F05CFB5C48FFF7C8FB5C4B09E000206077FFF7F9FB5A4B03E05A48FFF7F6FA594B1B88ADF828300AA8FFF72A | ||||
| :400B8000CBFB90E0A37F3BB1002001F0D7FA002001F0FAFF0023AB7701F07CFF002001F01FFF2A2701F04AFE002001F0EDFD3A4600210AA802F0ECF815238DF828308DF8DD | ||||
| :400BC000297001F09FFC002001F0B8FA002001F0DBFFC82001F058FC0DEB0700FFF752FB0DF13E00FFF76FFB01F08CFC012001F0CBFF322001F048FC0DF12E00FFF742FBDC | ||||
| :400C00000DF14200FFF75FFB012001F097FA4FF4967001F039FC01F075FC0DF13600FFF731FB0DF14A00FFF74EFB002001F086FA4FF4967001F028FC01F064FC022001F080 | ||||
| :400C4000A3FF322001F020FC0DF13200FFF71AFB0DF14600FFF737FB012001F06FFA4FF4967001F011FC01F04DFC0DF13A00FFF709FB0DF14E00FFF726FB002001F05EFAD2 | ||||
| :400C80004FF4967001F000FC01F03CFC002001F07BFF0023637701F07FFE01F051FD6DE70120FFF74BFB032000F004FC0C48FFF71EFBFFF7C0BB00BFDC3600000086FF1F93 | ||||
| :400CC0000C370000323600001B370000293700003436000036360000B881FF1F383600003637000010B54268002A2ED0C368002B2BD00368048A591C01601B78013A13F007 | ||||
| :400D0000800F817C42601DBF03F0010242EA84030231083114BF43F0020343EA0423817414BF03820382837C072BDCD9028A083B42FA03F38268511C81601370C368013B5A | ||||
| :400D4000C360837C083B8374CDE710BD07B5827C42B102AA002102F8011D026001224260FFF7C0FF03B05DF804FB30B543686BB3C2685AB3827C072A0CD8046890F91050D4 | ||||
| :400D8000611C01602178013B41EA05210832018243608274827C018AA2F108042141CBB2090608D5C3F3801363F07F03023A03F08103827402E08474002BD7D08268511C1C | ||||
| :400DC00081601370C368013BC360CFE730BD00002DE9F04172B6854B61221A70A3F5F06301221A801924824A9C7092E803008033062283F8002283E80300522203F5807397 | ||||
| :400E00001A707C4B7C4A1B787C4EDBB2137040F618027B4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450501F047FF013C05F003052ED0032DF0D1714B92 | ||||
| :400E40004FF480721A8007221A706F4A002548211570917002221D705D7103F8032C0422DA716A4A6A4C13786A4E43F00103137012F8013C062743F0030302F8013C237823 | ||||
| :400E8000012243F080032370584B1A70624A137843F02003137000E0FEE707FB056300219A881868013501F073FF072DF5D15B485B4E002550F8041F05F1105303F1520287 | ||||
| :400EC00021F0FF075333C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D14E4B00221A604E4B4E4F1A684E4BDFF87C811A604D4A137891 | ||||
| :400F000043F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370444A454B07CA03C31A80444A2833106843F8250C127903F8212C414A6C | ||||
| :400F400007CA03C31A80404AE83B07CA03C31A803E4A083307CA03C31A803D4A3D4BA2F5616203CBC2F8100EC2F8141E1378042043F008031370384B02F5AA521B783D7845 | ||||
| :400F8000DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302D4B48221A702D4A402313702C49937013729372082382F81F322022F7 | ||||
| :400FC0000A7048710A72284A0A20137001F072FA264B88F8006044223D70254D1A7094E80F0007C52B80BDE8F08100BF00480040840F00480F010049A14600402542004005 | ||||
| :40100000224200400440004006400040A2430040A04300403B370000E8460040FCFFFF47A0000048007600408C0F0048F84600400876004003500140440F0048C05100405C | ||||
| :40104000500F0048580F0048640F0048700F0048325100407C0F0048CF0100491D51004001590040235B0040585B004076580040B0430040F946004008B501F05DFE0368BC | ||||
| :401080000C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F04CFE0C2303604FF0FF33184608BDCC80FF1F4087FF1F80B51148114B0025C0B1A3F1FB | ||||
| :4010C000100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F028FEFFF776F9FEE700BF010000004B | ||||
| :401100000C390000124A134B10B51A60124A134C1368134843F4007313600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084AD8 | ||||
| :401140001A60FFF745FEBDE8104001F085B800BF0004FA050CED00E014ED00E0000000000080FF1F79100000BC760040C080FF1F08ED00E0F8B501F0ABFD444A01271378CA | ||||
| :40118000022643F001031370137C414C43F001031374404B02F5E3521F700B3203F8946C1378054603F07F031370002001F082F92378394A03F0F90323701378384603F0FF | ||||
| :4011C000DF03137023783B43237001F073F9282001F070F9314B30461A7802F07F021A701A7802F0BF021A7023783343237001F061F923782A4A43F004032370002313701E | ||||
| :401200002846537001F068FD0721172001F09AF92449172001F088F90721182001F092F92149182001F080F90721152001F08AF91E49152001F078F90721052001F082F9BE | ||||
| :401240001B49052001F070F90721062001F07AF91849062001F068F90721084601F072F91549072001F060F90721082001F06AF91249082001F058F907210C2001F062F956 | ||||
| :40128000BDE8F8400E490C2001F04EB9A5430040944300409D60004012600040F851004084600040CF19000009180000CD190000011900002D1900005D1900009519000088 | ||||
| :4012C000D319000008B51D4B1D4A1870002313701C4A13701C4A13701C4A13701C4A13701C4A13701C4A13701C4B4FF400021A604FF080721A604FF400121A6020221A6076 | ||||
| :4013000040221A6080221A604FF480721A60144B19B91A7802F0FE0202E01A7842F001021A70104B03221A70802203F8202C012001F0C6FC0C4B04221A7008BD7C86FF1F6B | ||||
| :401340008286FF1F8086FF1F8186FF1F7D86FF1F6C86FF1F7F86FF1FF486FF1F00E100E009600040286000401260004070B5074C054623780E461BB9FFF7FCFE0123237001 | ||||
| :4013800031462846BDE87040FFF79CBF4086FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4A13700A4A13700A4B03221A70802203F8202C70478286FF1FF0 | ||||
| :4013C0008086FF1F8186FF1F7D86FF1F6C86FF1F7F86FF1FF486FF1F28600040014B1878704700BF8186FF1F044B1A7802F0FF001AB118780022C0B21A7070478086FF1F53 | ||||
| :40140000024A0C2303FB0020407870478886FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA0020023880B2704700207047FC5F0040431E072BF0B531D846 | ||||
| :40144000194B0C2505FB0035EC88184FA4B2C4F50074A2424FF00C0404FB003488BFEA88E67884BFC2F5007292B2104C46EA12260501EE552C44D6B2667041B90C2202FB17 | ||||
| :401480000030002343704379DBB2A370F0BD0E46074F2F44751AADB2AA42EFD916F8015B3D72F7E7F0BD00BF8886FF1FFC5F004070600040431E072B0AD8064A0C2303FB32 | ||||
| :4014C000002300225A705A79034BD2B200011A54704700BF8886FF1FFE5F004038B505461446D1B1431E072B19D8FFF791FFA04203D22846FFF78CFF04460A4609482B01AF | ||||
| :401500001844531A9BB29C4203D9037A02F8013BF7E72846FFF7CEFF02E00C4600E00024204638BD70600040431E072B9FBF024B000108221A547047FE5F004030B51A4A02 | ||||
| :401540001A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E85450680133013050601088013880B21080ECE718460B780E4C082BA1 | ||||
| :401580000E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BFF886FF1FF486FF1F006000407086FF1F6D86FF1F8286FF1F5E | ||||
| :4015C0007E86FF1FF586FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A0120137070478286FF1F7E86FF1F6D86FF1FF486FF1FF586FF1F30B5164B36 | ||||
| :4016000016491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B229705168DBB20131516011880130013989B21180ECE7D8 | ||||
| :40164000094A1370094A137883F080031370084B0B221A7030BD00BF29600040F886FF1F006000407086FF1FF586FF1F7E86FF1F6D86FF1F064A06231370064A01201370B2 | ||||
| :40168000054B80221A70054B00221A70704700BF8286FF1F6D86FF1F7E86FF1FF586FF1F054B9A683AB19A68044910709A680988518000229A6070477086FF1FF886FF1F57 | ||||
| :4016C00008B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B06D043B900F0D4FB04E001F0F3FA01E000F008FD8B | ||||
| :4017000010B9034B03221A7008BD00BF286000406D86FF1F0060004008B5084A084B0120197813880B449BB21380064B00221A70FFF7B6FF044B03221A7008BDF886FF1F01 | ||||
| :40174000F486FF1F8286FF1F6D86FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFBB | ||||
| :401780008286FF1F6D86FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BF8286FF1F6D86FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B4F | ||||
| :4017C000002201201A70FFF76BFF054B03221A7008BD00BF6C86FF1F086000408286FF1F6D86FF1F074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF706 | ||||
| :40180000D3BF70478286FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF7F | ||||
| :4018400023781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BDB4 | ||||
| :40188000286000406D86FF1F7E86FF1FF586FF1F29600040054A00231380054A916819B191680B7092685380704700BFF886FF1F7086FF1F0E4808B503889BB213B9FFF7A0 | ||||
| :4018C00083FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FE012008BD7086FF1F8286FF1F7E86FF1F0060004084 | ||||
| :40190000084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B6000408886FF1F094B02221A700F3B93F82230074B1A7E02F0030242 | ||||
| :40194000012A1EBFDA7E82F08002DA7601225A76704700BF0B6000408886FF1F0B4B04221A700F3B93F83230094B93F8242002F00302012A1EBF93F8272082F0800283F8E3 | ||||
| :401980002720012283F82520704700BF0B6000408886FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012A1EBF93F8332082F0800283F83320012283F83120A2 | ||||
| :4019C000704700BF0B6000408886FF1F7047FFF7DFBC00F039BC000070B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B782B70FCB1222323702D | ||||
| :401A000001AD03232846637000F016FE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F10400EAD108B070BDB1 | ||||
| :401A4000653700002DE9F0411A4D01222E460C2080274FF0080E184B00FB025C14012344187016499CF805802144B8F1000F07D09CF8044024064CBF887081F802E000E0F1 | ||||
| :401A80008F7000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D6D1054BFF221A70BDE8F08100BF8886FF1F70600040FC5F00402D | ||||
| :401AC0000A600040064B074A1B7802EBC30253681A7C824286BF03EBC0035869002070477C86FF1FC83700002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFFF | ||||
| :401B0000A8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED19A7893F8039002F07F0206FB02FA05EB38 | ||||
| :401B40000A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E0C9 | ||||
| :401B800009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F8DC | ||||
| :401BC00006A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083394E7BDE8F88F00BF8186FF1F8886FF1F13 | ||||
| :401C00007D86FF1FFC5F0040706000406E86FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BD8186FF1F00212DE9F84F0B464E4E0C2707FB01F44F | ||||
| :401C400001313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8A2 | ||||
| :401C8000F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D093F802A00AF07F06AE4229D10E89B3F866 | ||||
| :401CC00004B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F0030607DA012E0CBF07260D264E7181F801808A | ||||
| :401D000006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF7F8 | ||||
| :401D400067FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF76BBEBDE8F88F00BF8886FF1F6E86FF1FF686FF1F59 | ||||
| :401D80008186FF1F7F86FF1F8486FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D170517841F002015170127912F0800F074A36 | ||||
| :401DC0001A4414BF8D2389239370FFF753BC0020704700BF006000408886FF1FFC5F004030B4194B1A7902F07F02531E072B27D8164B0C2404FB02339978154D01F0FE0139 | ||||
| :401E000099700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E0012B0CBF082309238B7030BCFFF71ABCBE | ||||
| :401E4000002030BC704700BF006000408886FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D2012909D8074A0848535CDBB24354A37818 | ||||
| :401E80000120DBB2535410BD002010BD8186FF1F006000406E86FF1FF686FF1F38B58A4A8A4C13780021DBB221801806517840F18D800A2900F20581DFE811F05D00030138 | ||||
| :401EC00003010301030103010B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B686360122310E0CB78022B12D18878FFF7E5FD002800F0E180C2 | ||||
| :401F0000436863606368DA7863689B7843EA02232380BDE83840FFF7CDBCCB78032B26D16D4B00228878D5B2854209D3664A91786A4AEE2908BF1346634A917881B106E0C9 | ||||
| :401F4000187801320028F1D018780344EAE764499278097C914203D16248FFF73DFD614B1A78002A00F0AD801A78228018E0BDE8384000F0B5BE13F0030313D0022B40F0D6 | ||||
| :401F8000A0802380504B0C211B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280514A11784F4AC9B2117053706260ACE7012323804D4BEFE7E4 | ||||
| :401FC0000123238013794C4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB25AE0937803F0FF0153B9404B1A7891425FD01970404B012024 | ||||
| :402000001870FFF715FE58E0481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB2D2B20A70364A3249D25CCB5C9A4240D0314B01221A70FFF749 | ||||
| :4020400053FD3AE003F00303012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0FD0315E003F00303012B13D008D3022BFE | ||||
| :402080001FD1114B9B78E3B9BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A795AB998781B791749DBB2CA5C22EA0002CA54BDE83840FFF7DC | ||||
| :4020C000D9BA002038BD00BF006000407086FF1F7C86FF1FC83700002C380000B43700009F3800001487FF1F8886FF1F4186FF1F7F86FF1F8186FF1F6E86FF1F6C86FF1F4C | ||||
| :402100008086FF1F7D86FF1FF686FF1F8386FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A80FFF7C4BB00207047006000407086FF1F8C370000014B1870D5 | ||||
| :40214000704700BF76650040014B1878704700BF6B650040014B1870704700BF78650040064A0123136002F688321268E0211064034A1170A2F540721360704780E100E0FB | ||||
| :4021800000E400E0014B1870704700BF7E640040014B1870704700BF7864004073B515461E460B4C01230022019200920A4601461846237000F0F4F832462946207800F00D | ||||
| :4021C000AFF80221207800F099F8207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A607047A2 | ||||
| :4022000000E100E0014B04221A60704780E100E0014B1870704700BF77640040704738B505460078012428B100F0F2FC285D0134E4B2F8E738BD08B50D2000F0E9FCBDE8C9 | ||||
| :4022400008400A2000F0E4BC024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE080FF1FF87B00401A | ||||
| :402280000078004017280FD8084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C2547C | ||||
| :4022C000C25C02F0DF02C25400207047012070471070004017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD807 | ||||
| :40230000064A0001835C490003F0F10301F00E011943815400207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F583 | ||||
| :40234000EC4081809ABFC280002001207047000017289FBF034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120C2 | ||||
| :40238000704700BF1470004017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF80820D4 | ||||
| :4023C00003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F075FC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78D4 | ||||
| :40240000137000E0FF2400F067FC204610BD00BFE080FF1F030610B5044611D400F058FC084AE300117803F1804303F5F04319705378147001335370BDE8104000F04CBCB6 | ||||
| :4024400010BD00BFE080FF1F30B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC280BD | ||||
| :40248000002001207047000038B50446084DB4F5004F05D9286800F013FCA4F50044F6E7034B58686043BDE8384000F009BC00BFE880FF1F024B1B7A584300F001BC00BFC8 | ||||
| :4024C000E880FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BFE0 | ||||
| :4025000083430040014B01221A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BFBA | ||||
| :4025400008ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72A2 | ||||
| :40258000DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD4486FF1F0526000010E000E0E880FF1F14E000E018E000E0024A136843F002031360704710E000E08E | ||||
| :4025C00008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF4486FF1F024B1868C0F30040704700BFF1 | ||||
| :4026000010E000E038B5FFF7F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF4886FF1F024B03EB80035868596070474486FF1F134B144A1B78DBB2C6 | ||||
| :402640000360127843EA0223114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA32 | ||||
| :4026800002634360704700BF0301004904010049EC460040020100490101004900010049050100490601004910B500F011FB204A044613780A2043F002031370137C43F068 | ||||
| :4026C0000203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8EC | ||||
| :40270000012CCE2203F8062CA3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D4BAAB4300400E5900402F5B004080E200E0B8 | ||||
| :4027400008B500F0C5FA0F4A137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F039 | ||||
| :40278000ABBA00BF08590040044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF00A | ||||
| :4027C000000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470A59004076370000FE86FF1F0087FF1F0487FF1F9A | ||||
| :4028000008B5102000F0A6F907210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BDF928000009590040FC86FF1F10B5054C93 | ||||
| :4028400023781BB9FFF7DCFF01232370BDE81040FFF72ABF6086FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D00C | ||||
| :40288000002404E02046FFF715FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF0487FF1FA25B00400E4A13881BB223B111880A2309B2594365 | ||||
| :4028C00001E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B27047FE86FF1F0087FF1FFC86FF1F7047000010B500F07F | ||||
| :40290000E7F9214A044613780A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A708F | ||||
| :4029400013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE8104091 | ||||
| :402980001A6000F0A9B900BFAB43004006590040275B004080E200E008B500F099F90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F829 | ||||
| :4029C0000A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F07FB900BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF21 | ||||
| :402A00008223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B73 | ||||
| :402A40001A80704702590040803700000A87FF1F1087FF1F0887FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF64 | ||||
| :402A8000034B00221A8008BD512B0000015900400C87FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BF6186FF1F044B1A7802F0FB021A701A7842F001027B | ||||
| :402AC0001A7070470059004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF9C | ||||
| :402B00000887FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F05F | ||||
| :402B400000B270470A87FF1F1087FF1F0C87FF1F70470000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040014B187044 | ||||
| :402B8000704700BF73640040014B1870704700BF7664004073B515461E460B4C0023019300930A46014618462370FFF7F9FB324629462078FFF7B4FB02212078FFF79EFBBC | ||||
| :402BC000207802B070BD00BFF880FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF7B650040B5 | ||||
| :402C0000014B1870704700BF7465004000000000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF814388346AE | ||||
| :402C40000121C9430C460125002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D9002001300138D5 | ||||
| :402C8000013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF480FF1F00C2010000000000C4 | ||||
| :402CC0000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A0D | ||||
| :402D000000E0137900207047006000408886FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF0E | ||||
| :402D40005C81FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E700F0BAFD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BDEA | ||||
| :402D8000E4380000E4380000E4380000EC38000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBFC5 | ||||
| :402DC000234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A460E | ||||
| :402E0000014603480068FFF7CBFF03B05DF804FB5C81FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57F | ||||
| :402E40007B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA63 | ||||
| :402E80000C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E44B8 | ||||
| :402EC0002660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF832 | ||||
| :402F00002930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78C5 | ||||
| :402F4000002B00F0A08000234FF0FF3204930793059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF72 | ||||
| :402F800020228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B14 | ||||
| :402FC00003D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A78013093 | ||||
| :40300000303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F991 | ||||
| :4030400088B1194B33B9039B073323F007030833039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099BEA | ||||
| :403080005344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BFB3380000B9380000BD38000000000000152E00002DE9F04791461F460A698B680646934286 | ||||
| :4030C000B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046F4 | ||||
| :40310000C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF3053 | ||||
| :40314000BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4582 | ||||
| :4031800009D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8F6 | ||||
| :4031C000002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A4A | ||||
| :403200003AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D224F | ||||
| :403240005B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F8452018 | ||||
| :403280002268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DB3 | ||||
| :4032C000A56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42A3 | ||||
| :40330000DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E019600023236135 | ||||
| :40334000754616E01A68111D1960156800216268284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF7CF | ||||
| :4033800097FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D4293 | ||||
| :4033C000F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F08365370000C438000010B5C9B202449042034605D01C7801308C42F8D1184610BD06 | ||||
| :40340000002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD33 | ||||
| :40344000994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A0188342B0 | ||||
| :4034800001BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C23ED | ||||
| :4034C0002B6009E021686018824201BF106852680918216062605C602846BDE8384000F098B838BD6886FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA942AC | ||||
| :4035000002D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7CF | ||||
| :40354000174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BDFA | ||||
| :40358000211A304600F034F80130EBD10C233360304600F03EF8002070BD00BF6886FF1F6486FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF284650 | ||||
| :4035C000F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF744FDB9 | ||||
| :40360000431C02D1236803B12B6038BD3887FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F02110213027265706C792028 | ||||
| :4036400030782530327800686F6D696E6700626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E6973686564207365656B0057616974696E679A | ||||
| :4036800020666F72205553422E2E2E0055534220726561647900636F6D6D616E642030782530327800756E64657272756E206166746572202564207061636B65747300638E | ||||
| :4036C0006F756E743D256420693D256420643D256400636D645F777269746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D2564206F | ||||
| :40370000756E64657272756E3D25640077726974652066696E69736865640073746172742065726173696E670073746F702065726173696E670069646C6500005100401076 | ||||
| :403740000040510040300000000140001000140140000800400140000A004C0140000200500140200030313233343536373839414243444546000001000000040000001018 | ||||
| :4037800000010000000400000010000028000000000104000100000000000000000157494E5553420000303030303100000000000000000012034D0053004600540031007C | ||||
| :4037C000300030000100000001000000D0370000010000009F380000000000000000000001000000E83700000100000071380000040000000A380000000000000000000078 | ||||
| :403800000000000008380000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E0067004E | ||||
| :4038400069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FF0B | ||||
| :4038800000000107050102400000070582024000000705030340000A0705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C0065666724 | ||||
| :4038C000454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000005110000F8B500BFF8BC08BC9E4670473500000010390000A9 | ||||
| :40390000C880FF1F98000000E005000000000000000000004087FF1FFF000000675000400C000000070000007F800000030000000000007D00FA0000400000000090D0039A | ||||
| :40394000FF0000000000000000000000000000000000000000000000000000000000000000000000B13800000000000000000000000000000000000000000000000000005F | ||||
| :40398000000000000000000000000000000000000000000000000000000000000000000000000000FC80FF1F0000000000000000000000000000000000000000000000006D | ||||
| :4039C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C7 | ||||
| :403A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086 | ||||
| :403A40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046 | ||||
| :403A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 | ||||
| :403AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C6 | ||||
| :403B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000085 | ||||
| :403B40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045 | ||||
| :403B80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005 | ||||
| :403BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C5 | ||||
| :403C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084 | ||||
| :403C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044 | ||||
| :403C80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004 | ||||
| :4000000000800020110000003110000031100000064A08B5136843F020031360044B1A6803F53F5302331A6001F020F8E8460040FA46004010B5054C237833B9044B13B18B | ||||
| :400040000448AFF300800123237010BD6881FF1F00000000B8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000051 | ||||
| :400080006C81FF1FB8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1FCC | ||||
| :4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F0000800E4BDA681AB901215F | ||||
| :4001000019745A7410E05A7C1A741A7C0AB1002207E059699A69D8688A1A82428CBF002201225A745A699A611B7C13B1002002F043B970478881FF1F10B5C4B2204601F054 | ||||
| :4001400057F90128FAD110BD08B572B60F4B0F495A6901325A61DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002B0CBF02230023002814BF184660 | ||||
| :4001800043F0010002F080FE62B608BD8881FF1F38B50446C5B2284602F0B0F8062002F0CDFA44F00200C0B202F0A8F8062002F0C5FA284602F0A2F8BDE83840062002F0C2 | ||||
| :4001C000A7BA10B5642402F093F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F003F9012805D0204601F01CFA2846FFF79FFF204601F000F9AD | ||||
| :40020000314605460246204601F0BCF9204601F0EFF80028FAD1284670BD000038B5044D0024285D013402F039FA402CF9D138BDAC81FF1F08B502F053FC002002F05CFCBD | ||||
| :4002400002F06EFC02F078FC80B208BD10B50446012002F06BF8642002F05AFAFFF7EAFF2080002002F062F8642002F051FAFFF7E1FF608010BD08B502F05EFD002002F02B | ||||
| :4002800067FD02F079FD02F083FD80B208BD10B50446FFF796FF322002F03AFAFFF7EBFF20800120FFF774FF322002F031FAFFF7E2FF608010BD0FB400B593B014AB53F835 | ||||
| :4002C000042B402102A8019302F0DEFE02A802F07AF802F084F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF62782146BDE81040042001F0D0B8B5 | ||||
| :40030000CC38000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0D8FF002002F06BFD002384F864304E | ||||
| :4003400010BD00BF8881FF1F38B5104D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F865302279094B1A71A378002B14BF0220012002F04AFDE07802F02E | ||||
| :4003800041FD2079BDE8384002F078BD8881FF1FED81FF1F38B50D4C94F8645065B904F16500FFF7D1FF012001F09CFF4FF47A7002F0AEF984F86A506369E366012384F84D | ||||
| :4003C0006430BDE8384002F0E1B900BF8881FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF767FFFFF7EBFE0120002384F86A00236702F0A1F92A46216F184872 | ||||
| :40040000FFF759FF144E0027236F9D4216D001F06FFF00B13767236F9D4205DD0120FFF7B7FE336F013305E005DA0020FFF7B0FE336F013B336702F0A9F9E5E7322002F047 | ||||
| :4004400067F92A2DCCBF0020012002F023FDBDE8F8400448FFF72FBF8881FF1FD9380000E0380000FD3800002DE9F04F99B062B602F0F6F99D49042002F01AFA9C4801F0D9 | ||||
| :4004800043FF9C4802F0E6FC9B4801F077FF02F0C7FB02F099FA002002F0BAFC01F092FF0221002000F05AFF944C012001F0D2F8002384F86730FFF76DFFFFF782FE84F8A3 | ||||
| :4004C0007400FFF72FFF012384F86730FFF762FFFFF777FE84F87500FFF724FF884B94F87400884994F875202546002A14BF0A461A46002808BF19468348FFF7DCFE032177 | ||||
| :40050000084602F023F9264602F040F994F8643043B1EA6E6B699B1A41F28832934201D9FFF700FF00F052FF18B97848FFF7C3FE04E000F051FF0028F7D10BE000F046FF85 | ||||
| :4005400010B902F023F9F9E77148FFF7B4FE032001F06CF8032000F04BFF0128D4D16D48FFF7F2FE6C490320FFF738FE94F876106A48FFF7A0FE94F87630023B142B00F229 | ||||
| :40058000D483DFE813F01500D2031E00D2032400D2034F00D2037500D203D700D203BF01D2030603D2032A03D2033103D2034B0303238DF820308DF821300F238DF8223084 | ||||
| :4005C00028E394F87800FFF703FF554B1FE3FFF7E1FE002323746069227C02F0FF0132B96B691B1AB3F57A7FF6DD0B4611E083B10022174696F878107069277494F810E007 | ||||
| :40060000BEF1000F02D16B691B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F7E20220FFF787FD4FF000080DF1200A02F0ABF84FF480790027C9EB080303 | ||||
| :40064000DA1907F80A200137402FF9D10220FFF773FD3A465146022000F022FFB9F10109EBD108F10108B8F1400FE2D12D4B38E04FF0010A4FF000080DF1200B02F086F844 | ||||
| :400680004FF0000959460120FFF7A8FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B464A461E48FFF702FE4FF0000A0137402FEBD109F101091B | ||||
| :4006C000B9F5807FDED108F10108B8F1400FD5D151461648FFF7EFFDBAF1000F00F01A81134B1B8807A8ADF81C3094E249010000F900000091000000C50000008881FF1F0B | ||||
| :400700000F3900000B390000123900002A3900003D390000ED81FF1FFE81FF1F47390000BC380000BE3800005639000072390000C0380000206FFFF74BFE94F8780001F081 | ||||
| :40074000F5FD94F8780001F0D9FD02F009FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F0F7FB0220FFF7DCFCF9 | ||||
| :40078000012141F6FF734FF48042084602F046FB84F8B60001F068FF08F807000137402FF8D1DFF8B0A200270AF195091FFA89F80137402F14BF3A4600221AF8010F2244C0 | ||||
| :4007C000062392F82420402101F082FF424646F24E419AF8000001F08DFF08F14008402F1FFA88F8E4D196F8793053B196F87C30F36000233374237C002BFCD000233374E1 | ||||
| :40080000F36000234FF0FF32236062602372236894F8B600234493F8241001F0DDFE94F8B60001F09BFE012194F8B60001F06EFE2368002BFCD0002398467360D6F814A0D4 | ||||
| :40084000012701F0A3FF6369B4F87A20CAEB030393420DD367B1042195F8B60001F0C8FE94F8B60001F0D4FE0028F9D107463072237AFBB96A682B689A4202D1002FE0D1D9 | ||||
| :4008800018E00220FFF758FC6968402209EB8111022000F005FE6A68674B01321340002BBEBF03F1FF3363F03F03013308F101086360C6E70220277AFFF73EFC0022114687 | ||||
| :4008C000022000F0EDFD0220FFF736FCFFB2FFF7A5FC002001F012FD37B15848FFF7EBFC0220FFF70FFD06E0554B08A81B88ADF82030FFF7F5FC227C4146237A5148FFF7BB | ||||
| :40090000DAFC15E25048FFF7D6FCD4F87A7017F03F0701D0032009E2286FFFF759FD95F8780001F003FD95F8780001F0E7FC012001F002FD02F014FB444BDFF814811A7857 | ||||
| :4009400042F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F003FB01214FF4804341F6FF72084601F0E9FC85F8B60001F077FE08F8070001378C | ||||
| :40098000402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F82420402101F090FE414646F24B5299F8000001F09BFE08F1FF | ||||
| :4009C0004008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF0010A4FF0000A2168114A01310A40002ABCBF02F1FF3262F03B | ||||
| :400A00003F026068B8BF013282426FD02BB1227A002A7AD12A7C002A77D12068049A059302EB8010BAF1000F16D040223F2102F0F7FA1CE09E6400403F0000807C390000C9 | ||||
| :400A4000C238000096390000A93900009B650040AC81FF1FAB81FF1F014601370120FFF7BDFBC7EB0903D3F1000A4AEB030A2168B34A01310A40002ABEBF02F1FF3262F0BB | ||||
| :400A80003F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F080FC85F808806B6895F8B6002B4493F8241001F092FD95F8B60001F050FD012195F8BF | ||||
| :400AC000B60001F023FD95F87E30EB6085F81080237C002BFCD04FF00008012086F8108001F06AFC404601F027FC00E023B1237A5BB92B7C4BB90123626842453FF477AFC1 | ||||
| :400B00000BF1010BD5F8048071E701F04FFC012001F012FC002001F04FFC042194F8B60001F066FD94F8B60001F072FD80460028F8D196F8B60001F0FFFC337C327A029318 | ||||
| :400B4000012303920193CDF800A05B463A4649467C48FFF7B0FBC6F80C80BAF1000F0BD0FFF75CFB002001F0C9FB237A63B17648FFF7A1FB0220D9E0B945F1D07349012035 | ||||
| :400B8000FFF72CFB0137F7E77148FFF794FB714B3DE094F8780001F0C9FB206FFFF718FC6D48FFF788FB94F87930E36000232374237C002BFCD0012001F0FEFB00233374CE | ||||
| :400BC000237C002BFCD0002001F0F6FB00236348F360FFF770FB624B19E0002084F86A00FFF7F6FB5F4B12E094F8743023B195F875200AB985F8782094F875201AB113B9F6 | ||||
| :400C0000012385F878305848FFF79EFB574B1B88ADF8203008A8FFF763FB89E0FFF782FB02F07CF8002002F01FF82A2701F04AFF002001F0EDFE3A46002108A802F0F0F9E0 | ||||
| :400C400017238DF820308DF8217001F09FFD002001F048FB002002F0DBF8C82001F058FD0DF12200FFF7F2FA0DF13600FFF70FFB01F08CFD012002F0CBF8322001F048FD4D | ||||
| :400C80000DF12600FFF7E2FA0DF13A00FFF7FFFA012001F027FB4FF4967001F039FD01F075FD0DF12E00FFF7D1FA0DF14200FFF7EEFA002001F016FB4FF4967001F028FD84 | ||||
| :400CC00001F064FD022002F0A3F8322001F020FD0DEB0700FFF7BAFA0DF13E00FFF7D7FA012001F0FFFA4FF4967001F011FD01F04DFD0DF13200FFF7A9FA0DF14600FFF756 | ||||
| :400D0000C6FA002001F0EEFA4FF4967001F000FD01F03CFD002002F07BF8002384F86A3001F07EFF01F050FE74E70120FFF7EAFA032000F07BFC0E48FFF7BDFAFFF7E4BBB6 | ||||
| :400D40003F000080B3390000E33900004092FF1FED390000C4380000F5390000033A0000C6380000C8380000FE81FF1FCA380000103A00002DE9F04172B6884B61221A70F9 | ||||
| :400D8000A3F5F06301221A801924854A9C7092E803008033062283F8002283E80300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F25122A6 | ||||
| :400DC00023F8022C33784FF4F07003F0010343EA450502F0BDF8013C05F003052ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F893 | ||||
| :400E0000032C0422DA716D4A6D4C13786D4E43F00103137012F8013C062743F0030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FBAD | ||||
| :400E4000056300219A881868013502F0E9F8072DF5D15E485E4E002550F8041F05F1105303F1480221F0FF074933C9B20B4452005B0002329A4206D012F802EC12F801CC0C | ||||
| :400E80000EF807C0F5E7B0420D44E5D1514A002313609360136193614F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F002031374F7 | ||||
| :400EC0002378A2F5863243F040032370413A137843F010031370464A464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C33B | ||||
| :400F00001A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C329F | ||||
| :400F40001B091170F6B2537045F003033B7046F0030388F800302F4B48221A702E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F0C7 | ||||
| :400F8000DDFB284B88F8006044223D70264D1A7094E80F0007C52B80BDE8F081004800405C0900480F010049A146004025420040224200400440004006400040A2430040AF | ||||
| :400FC000A0430040153A0000E8460040FCFFFF478C0000480076004064090048F8460040207600406809004828760040035001401C090048C051004028090048300900485A | ||||
| :401000003C090048480900483251004054090048CF0100491D51004001590040235B0040585B004076580040B0430040F946004008B501F0C9FF03680C2B00D1FEE7FEE79D | ||||
| :40104000084908B50B68084A1844821A802A01DC086005E001F0B8FF0C2303604FF0FF33184608BDCC80FF1F9093FF1F80B51148114B0025C0B1A3F1100192C92246043933 | ||||
| :40108000161BB74204D051F8046F42F8046BF7E7114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F094FFFFF7DCF9FEE700BF01000000E43B0000124A134BF0 | ||||
| :4010C00010B51A60124A134C1368134843F4007313600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8CA | ||||
| :40110000104001F0EDB900BF0004FA050CED00E014ED00E0000000000080FF1F31100000BC760040C080FF1F08ED00E0F8B501F017FF4B4A01271378022643F001031370EA | ||||
| :40114000137C484C43F001031374474B02F5E3521F700B3203F8946C1378054603F07F031370002001F0EAFA2378404A03F0F90323701378384603F0DF03137023783B4325 | ||||
| :40118000237001F0DBFA282001F0D8FA384B30461A7802F07F021A701A7802F0BF021A7023783343237001F0C9FA2378314A43F0040323700023137053702F4AFF2199544A | ||||
| :4011C0000133092BFBD1284601F0CEFE0721172001F0FCFA2949172001F0EAFA0721182001F0F4FA2649182001F0E2FA0721152001F0ECFA2349152001F0DAFA0721052032 | ||||
| :4012000001F0E4FA2049052001F0D2FA0721062001F0DCFA1D49062001F0CAFA0721084601F0D4FA1A49072001F0C2FA0721082001F0CCFA1749082001F0BAFA0021162047 | ||||
| :4012400001F0C4FA1449162001F0B2FA07210C2001F0BCFABDE8F84010490C2001F0A8BAA5430040944300409D60004012600040F851004084600040B592FF1FFB1A00008A | ||||
| :4012800035190000F91A00002D1A0000591A0000891A0000C11A0000011B0000751B0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A1370D9 | ||||
| :4012C0001F4A13701F4A13701F4B4FF400021A604FF080721A604FF400121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7851 | ||||
| :4013000002F0FE0202E01A7842F001021A70114B03221A70802203F8202C012001F018FE0D4B04221A7010BDD092FF1FD692FF1FD492FF1FD592FF1FD192FF1FC092FF1F97 | ||||
| :40134000D392FF1F4893FF1F00E100E09E6000409C600040286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF8092FF1F7A | ||||
| :401380000A4A002313700A4A13700A4A13700A4A13700A4A13700A4A13700A4A13700A4B03221A70802203F8202C7047D692FF1FD492FF1FD592FF1FD192FF1FC092FF1F05 | ||||
| :4013C000D392FF1F4893FF1F28600040014B1878704700BFD592FF1F044B1A7802F0FF001AB118780022C0B21A707047D492FF1F024A0C2303FB002040787047DC92FF1FB8 | ||||
| :40140000431E072B0CD8074A064B00010344805C5B7800F00F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044670 | ||||
| :4014400019BF8AB24FF480438BB24FF48042032D18D8DFE805F002070C110021084601F01BF80DE00021084600F0FAFF08E00021084600F0D9FF03E00021084600F0B8FF3E | ||||
| :40148000054B1855EDB2072D03D801F0EDF8034B185538BDDC92FF1FAC92FF1FB592FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB274 | ||||
| :4014C000C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FBBF | ||||
| :40150000047200215170B9F1000F28D03DB31B4F385D01F011F811232946FE2218F8040001F0D6F806F5C04278321FFA89F118F8040001F0DFF8124D18F80410385D01F004 | ||||
| :401540004BF80121385D00F0E1FF735D43F002037355735D03F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFDC92FF1FFC5F0040B592FF1FAC92FF1FD4 | ||||
| :40158000706000402DE9F047044615468846002940D0431E072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF893 | ||||
| :4015C000040000F0B9FF1223FE222946305D01F07FF807F5C0411FFA88F27831305D01F089F8DFF84490315D1AF8040000F0F4FF01211AF8040000F089FF17F8093043F0F1 | ||||
| :40160000020307F8093017F8093003F0FD0307F8093002E00D4600E000252846BDE8F087B592FF1FAC92FF1F70600040431E072B0AD8064A0C2303FB002300225A705A7991 | ||||
| :40164000034BD2B200011A54704700BFDC92FF1FFE5F0040431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488E7 | ||||
| :40168000D8B2A4B27CB1082B0CD050680078C0B2E85450680133013050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F23237022 | ||||
| :4016C000022301E0022323701370094B1870087030BD00BF4C93FF1F4893FF1F00600040C492FF1FC192FF1FD692FF1FD292FF1F4993FF1F074B02221A70074B80221A70AE | ||||
| :40170000064B0F221A70064A00231370054A012013707047D692FF1FD292FF1FC192FF1F4893FF1F4993FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80CC | ||||
| :40174000124C134A0020118889B279B173B15568215C013BC9B229705168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A70DF | ||||
| :4017800030BD00BF296000404C93FF1F00600040C492FF1F4993FF1FD292FF1FC192FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFD692FF1F52 | ||||
| :4017C000C192FF1FD292FF1F4993FF1F054B9A683AB19A68044910709A680988518000229A607047C492FF1F4C93FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A1E | ||||
| :40180000137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B06D043B900F012FC04E001F0A5FB01E000F046FD10B9034B03221A7008BD00BF28600040C192FF1FC0 | ||||
| :401840000060004008B5084A084B0120197813880B449BB21380064B00221A70FFF7B6FF044B03221A7008BD4C93FF1F4893FF1FD692FF1FC192FF1F08B50C4B1B78DBB25E | ||||
| :40188000042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFD692FF1FC192FF1F08B5054B002201201A70FFF7B6 | ||||
| :4018C00085FF034B03221A7008BD00BFD692FF1FC192FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFA5 | ||||
| :40190000C092FF1F08600040D692FF1FC192FF1F074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047D692FF1F38B51D4C2378DBB2DD0634D51B | ||||
| :4019400018060AD503F00F03012B2ED1FFF74EFF174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13705F | ||||
| :401980001278934211D10A4A0849154613782078DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BD28600040C192FF1FD292FF1F4993FF1F29600040AD | ||||
| :4019C000054A00231380054A916819B191680B7092685380704700BF4C93FF1FC492FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FFAC | ||||
| :401A0000094AD1799379028843EA012392B2934238BF0380FFF728FE012008BDC492FF1FD692FF1FD292FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A07 | ||||
| :401A40001EBFDA7B82F08002DA7301225A7370470B600040DC92FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BFFE | ||||
| :401A80000B600040DC92FF1F0B4B04221A700F3B93F83230094B93F8242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040DC92FF1F78 | ||||
| :401AC0000B4B08221A700F3B93F84230094B93F8302002F00302012A1EBF93F8332082F0800283F83320012283F83120704700BF0B600040DC92FF1F7047FFF741BC000081 | ||||
| :401B0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4C35 | ||||
| :401B4000EDB204F80E50840706D507FB036425792D0658BF84F801C090700133DBB24908D7E7F0BD9F600040DC92FF1F70600040FE5F004000F0ACBC70B50446184B88B021 | ||||
| :401B800003AA03F11006154618685968083303C5B3422A46F7D11B782B70FCB12223237001AD03232846637000F08AFE002220461146AB5C08AC04EB131414F8144C03F053 | ||||
| :401BC0000F03847008AC234413F8143C0132082AC1700371417100F10400EAD108B070BD3F3A00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02580B | ||||
| :401C00001401234418705F70164998F805902144B9F1000F07D098F8044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71A2 | ||||
| :401C4000CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE8F08300BFDC92FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EB6F | ||||
| :401C8000C003586900207047D092FF1FA03A00002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C260027FE | ||||
| :401CC0004FF0010E834268D01A78A24263D11CF80420597891425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0A6 | ||||
| :401D0000B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEAA3 | ||||
| :401D400002191A4906FB0282494481F802E0B2F808A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F8FE | ||||
| :401D80000790B2F806905FFA89F981F80490D288C2F307224A71083394E7BDE8F88F00BFD592FF1FDC92FF1FD192FF1FFC5F004070600040C292FF1F08B5064B187801384A | ||||
| :401DC000C0B2FFF753FF20B143681B7900EBC300406908BDD592FF1F00212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD37013819372537031 | ||||
| :401E00005371EFD118B1464B1D70464B1D70464B1A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010273 | ||||
| :401E400002F0FE021A70027AD20007FB0541C36803EB02094B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B34576 | ||||
| :401E80001AD181F804A0DE780E7093F902A0DE78BAF1000F06F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092D89 | ||||
| :401EC000C3D1C1680A328B1C0A440C200833934209D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D38012890131134463 | ||||
| :401F00009BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE8F88F00BFDC92FF1FC292FF1F4A93FF1FD592FF1FD392FF1FD892FF1F114B1B7903F07F035A1E072A3C | ||||
| :401F400019D80F490C2202FB031291781B0141F0010191700021D170517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF0060004065 | ||||
| :401F8000DC92FF1FFC5F004030B4194B1A7902F07F02531E072B27D8164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F013 | ||||
| :401FC000FD025A701A795B78120605D5012B01D18C7006E00D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040DC92FF1FFC5F004010B50D4BA5 | ||||
| :402000000D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDD592FF1F00600040C292FF1FB6 | ||||
| :402040004A93FF1F38B58A4A8A4C13780021DBB221801806517840F18D800A2900F20581DFE811F05D00030103010301030103010B0003017E0003018200D3787C49012B9C | ||||
| :4020800009D17D4B1A787D4B03EBC2035B685B686360122310E0CB78022B12D18878FFF7E5FD002800F0E180436863606368DA7863689B7843EA02232380BDE83840FFF7EE | ||||
| :4020C0008FBCCB78032B26D16D4B00228878D5B2854209D3664A91786A4AEE2908BF1346634A917881B106E0187801320028F1D018780344EAE764499278097C914203D180 | ||||
| :402100006248FFF739FD614B1A78002A00F0AD801A78228018E0BDE8384000F029BF13F0030313D0022B40F0A0802380504B0C211B7903F07F02564B01FB02339A78554BC3 | ||||
| :40214000D2B21A7000225A706360B6E702222280514A11784F4AC9B2117053706260ACE7012323804D4BEFE70123238013794C4A1344E9E701390A2977D8DFE801F0377608 | ||||
| :402180004F76067676760A7620009378454ADBB25AE0937803F0FF0153B9404B1A7891425FD01970404B01201870FFF715FE58E0481EC0B2FFF75AFD0028EED155E0FFF793 | ||||
| :4021C0001DFF002851D02A4A384913791279DBB2D2B20A70364A3249D25CCB5C9A4240D0314B01221A70FFF753FD3AE003F00303012B2BD009D3022B37D11D4B9B78002B47 | ||||
| :4022000033D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0FD0315E003F00303012B13D008D3022B1FD1114B9B78E3B9BDE83840FFF77EBE0D4B9B78012B14D1B7 | ||||
| :40224000154A137843F0020313700AE0084B1A795AB998781B791749DBB2CA5C22EA0002CA54BDE83840FFF79BBA002038BD00BF00600040C492FF1FD092FF1FA03A000049 | ||||
| :40228000043B00008C3A0000773B00006893FF1FDC92FF1F8192FF1FD392FF1FD592FF1FC292FF1FC092FF1FD492FF1FD192FF1F4A93FF1FD792FF1F074B1A78120609D546 | ||||
| :4022C0005B78012B06D1054B054A5A6012781A80FFF786BB0020704700600040C492FF1F643A0000014B1870704700BF7A640040014B1878704700BF6B650040014B18702D | ||||
| :40230000704700BF79640040064A0123136002F688321268E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF7A650040014B1870704700BF89 | ||||
| :402340007865004073B515461E460B4C05230022019200920A4601461846237000F064F932462946207800F01FF90221207800F009F9207802B070BDD080FF1F064A04232E | ||||
| :40238000136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BF30 | ||||
| :4023C0007E640040704738B505460078012428B100F066FD285D0134E4B2F8E738BD08B50D2000F05DFDBDE808400A2000F058BDF7B516461F460B4C002303250193009359 | ||||
| :402400000A4601462846257000F00EF93A463146207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257030 | ||||
| :4024400000F0F2F83A463146207800F0ADF82946207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0D6F83A463146DE | ||||
| :40248000207800F091F80221207800F07BF8207803B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0BBF832462946207800F076F8022120782A | ||||
| :4024C00000F060F8207802B070BD00BFE380FF1F024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFCC | ||||
| :40250000E480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001EA | ||||
| :40254000C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004017280DD8074900010B4603441A7942F004021A71435C43F001034354002070470120704740 | ||||
| :402580001070004017280BD8064A0001835C490003F0F10301F00E011943815400207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBF85 | ||||
| :4025C000C00000F1804000F5EC4081809ABFC280002001207047000017289FBF034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBFDC | ||||
| :402600001143195400200120704700BF1470004017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC4422 | ||||
| :40264000D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F079FC0A4A5378182B0AD91478013B5370E30003F1FB | ||||
| :40268000804303F5F0431B78137000E0FF2400F06BFC204610BD00BFE480FF1F030610B5044611D400F05CFC084AE300117803F1804303F5F0431970537814700133537094 | ||||
| :4026C000BDE8104000F050BC10BD00BFE480FF1F30B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5D2 | ||||
| :40270000F04081805ABFC280002001207047000038B50446084DB4F5004F05D9286800F017FCA4F50044F6E7034B58686043BDE8384000F00DBC00BFEC80FF1F024B1B7AB4 | ||||
| :40274000584300F005BC00BFEC80FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F060 | ||||
| :4027800010021A70704700BF83430040014B01221A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230009 | ||||
| :4027C00042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040334 | ||||
| :4028000013600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD8492FF1F8D28000010E000E0EC80FF1F14E000E018E000E0024A136843F0020334 | ||||
| :402840001360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF8492FF1F024B186891 | ||||
| :40288000C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF8892FF1F024B03EB80035868596070478492FF1F1F | ||||
| :4028C000134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA024382 | ||||
| :402900000A4A4360127843EA02634360704700BF0301004904010049EC460040020100490101004900010049050100490601004910B500F015FB204A044613780A2043F07D | ||||
| :4029400002031370137C43F00203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F837 | ||||
| :40298000012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D8BAAB4300400E59004026 | ||||
| :4029C0002F5B004080E200E008B500F0C9FA0F4A137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE0388 | ||||
| :402A00009371BDE8084000F0AFBA00BF08590040044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B2042812 | ||||
| :402A4000137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470A590040503A00005293FF1FB7 | ||||
| :402A80005493FF1F5893FF1F08B5102000F0A6F907210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BD812B0000095900402E | ||||
| :402AC0005093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABFA092FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0F1 | ||||
| :402B0000010403D10028F9D0002404E02046FFF715FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF5893FF1FA25B00400E4A13881BB223B1D5 | ||||
| :402B400011880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475293FF1F5493FF1F5093FF1F29 | ||||
| :402B80007047000010B500F0EBF9214A044613780A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F0030343 | ||||
| :402BC0001370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074BEE | ||||
| :402C000008222046BDE810401A6000F0ADB900BFAB43004006590040275B004080E200E008B500F09DF90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C9E | ||||
| :402C400003F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F083B900BF00590040044A137803F03F0343EA8010C0B21070704700BF0059004016 | ||||
| :402C8000082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBDD | ||||
| :402CC000F1F305490B60054B1A807047025900405A3A00005E93FF1F6493FF1F5C93FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F035 | ||||
| :402D000006031370FFF7BCFF034B00221A8008BDD92D0000015900406093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BFA192FF1F044B1A7802F0FB02D6 | ||||
| :402D40001A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B88108822 | ||||
| :402D8000181A00B2704700BF5C93FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF86 | ||||
| :402DC0005B42134493FBF1F000B270475E93FF1F6493FF1F6093FF1F70470000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF9A | ||||
| :402E000006410040014B1870704700BF78640040014B1870704700BF7965004073B515461E460B4C04230022019200920A46014618462370FFF7F8FB324629462078FFF7AB | ||||
| :402E4000B3FB02212078FFF79DFB207802B070BDFC80FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B18708C | ||||
| :402E8000704700BF7C640040014B1870704700BF7B640040014B1870704700BF7F640040FEB5494652465B460EB40746244909688A46244A12682448022100F071F803009B | ||||
| :402EC00020480068C018204900F06AF8143883460121C9430C460125002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B705846AA | ||||
| :402F000000F026F80136072EF2D9002001300138013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF3008039 | ||||
| :402F4000D480FF1FF880FF1F00C20100000000000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D181 | ||||
| :402F800013790C2103F07F02044B01FB02339B7A00E013790020704700600040DC92FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB0B | ||||
| :402FC00011217047704700BF014B1868704700BF6081FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E700F0DEFD084B094C1E46E41AA41000251E | ||||
| :40300000A54204D056F8253098470135F8E770BDBC3B0000BC3B0000BC3B0000C43B000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B6026 | ||||
| :403040004FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B0022F6 | ||||
| :403080001A701BB030BD000007B5009313460A46014603480068FFF7CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD025685F | ||||
| :4030C000096902236F1A656905EB450595FBF3F57B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A462B | ||||
| :4031000000F098FB064670B92169504600F0E8FA0C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF4646494632462068B9 | ||||
| :4031400000F0B3FAA36800209B1BA36023681E442660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F8D4 | ||||
| :403180000030CDE040236B610023099320238DF82930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF7EA | ||||
| :4031C00071FF013000F0A780099B4B4409933B78002B00F0A08000234FF0FF3204930793059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46E7 | ||||
| :40320000801A06FA00F018430490EFE7D90644BF20228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F002006C | ||||
| :40324000079204900BE027463B780134303B092B03D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E010 | ||||
| :40328000002319460593781C0A2407463A780130303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B14023CBEB000003FA00F0049B01371843049053 | ||||
| :4032C000397806221B487E1C8DF8281000F0D4F988B1194B33B9039B073323F007030833039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A937 | ||||
| :40330000404600F093F8B0F1FF3F824603D0099B5344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BF8B3B0000913B0000953B000000000000A5300000D1 | ||||
| :403340002DE9F04791461F460A698B6806469342B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BFD9F800300233C9F80030256815F00605F2 | ||||
| :4033800010D104F1190A07E00123524639463046C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F84E | ||||
| :4033C00045102244023382F8431003E04FF0FF30BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500252C | ||||
| :403400009342C4BF9B1AED184FF000091A344D4509D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9BE3 | ||||
| :4034400001F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B880A0 | ||||
| :403480000A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F104011960E2 | ||||
| :4034C00002D0B2F9003000E01368002B3CDA2D225B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A40 | ||||
| :4035000014BF0A2208221BE078225A4984F845202268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F0200222601BB9226822F020022260102266 | ||||
| :40354000002084F8430001E049490A226568002DA56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D131 | ||||
| :40358000082A0BD12368DA0708D5236962689A42DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F1040087 | ||||
| :4035C0001860136801D0198000E0196000232361754616E01A68111D1960156800216268284600F049F808B1401B6060636804E004F1420584F8422001232361002384F8A9 | ||||
| :403600004330CDF800803B4603AA21463046FFF797FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046F8 | ||||
| :40364000C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F0833F3A00009C3B000010B5C9B202449042CF | ||||
| :40368000034605D01C7801308C42F8D1184610BD002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21A41 | ||||
| :4036C000D34204D013F8014D01F8014DF8E710BD994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B912 | ||||
| :403700006360146030E0A3420DD92268A018834201BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D1F2 | ||||
| :4037400010685268014419605A600DE002D90C232B6009E021686018824201BF106852680918216062605C602846BDE8384000F098B838BDA892FF1F70B5CD1C25F003051A | ||||
| :4037800008350C2D38BF0C25002D064601DBA94202D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B6896 | ||||
| :4037C0001360636018BF0C4615E00C464968E9E7174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B005E | ||||
| :40380000231D20F00700C31A0ED05A42E25070BD211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA892FF1FA492FF1FF8B5074615460E4621B91146D1 | ||||
| :40384000BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD40 | ||||
| :4038800038B5064C0023054608462360FDF7D8FB431C02D1236803B12B6038BD8C93FF1F7047704751F8040C0028BEBF091851F8043CC018043870470000000005020902A2 | ||||
| :4038C0000B020D020F021102130215027265706C792030782530327800686F6D696E6700626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E08 | ||||
| :403900006973686564207365656B00796573006E6F00647269766520303A20257320647269766520313A2025730057616974696E6720666F72205553422E2E2E00555342F3 | ||||
| :4039400020726561647900636F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F74202564007061737365643D256400756E646544 | ||||
| :403980007272756E206166746572202564207061636B65747300636F756E743D256420693D256420643D256400636D645F777269746500703D25642063723D256420637729 | ||||
| :4039C0003D256420663D256420773D256420696E6465783D256420756E64657272756E3D256400756E64657272756E21007375636365737300737461727420657261736912 | ||||
| :403A00006E670073746F702065726173696E670069646C650000510040100040510040300000000140001000140140000800400140000A004C0140000200500140200030F9 | ||||
| :403A400031323334353637383941424344454600000100000004000000100001000000040000001028000000000104000100000000000000000157494E5553420000303043 | ||||
| :403A800030303100000000000000000012034D005300460054003100300030000100000001000000A83A000001000000773B0000000000000000000001000000C03A000003 | ||||
| :403AC00001000000493B000004000000E23A0000000000000000000000000000E03A0000FF00000001024000FF00000082024000FF00000003034000FF00000084034000F7 | ||||
| :403B0000FF00020304030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F006C006F0003 | ||||
| :403B400067006900650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000A0705840340000A12010002FF0001080921 | ||||
| :403B800012006E0100020180014300232D302B2000686C4C00656667454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000094 | ||||
| :403BC000BD100000F8B500BFF8BC08BC9E46704735000000E83B0000C880FF1FA00000002812000000000000000000009093FF1FFF000000675000400C0000000700000097 | ||||
| :403C0000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003FF000000000000000000000000000000000000000000000000000000000000000000000031 | ||||
| :403C4000893B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080 | ||||
| :403C80000081FF1F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065 | ||||
| :403CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C4 | ||||
| :403D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083 | ||||
| :403D40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043 | ||||
| @@ -4098,69 +4098,69 @@ | ||||
| :40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041 | ||||
| :40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 | ||||
| :0200000480007A | ||||
| :400000000145004007520040015B00400264004001010140490201404103014059040140540501404C0601404707014047080140470901404C0A0140500B0140590C0140B7 | ||||
| :40004000520D0140460E0140380F01404E1401405F1501405516014058170140491801404C1901404B1A0140581B01400D400140094101400C4201400A43014003440140AF | ||||
| :4000800004450140064601400E4701400B480140104901401D4C01400A4D014008500140045101407E020841090210801801600361157C4027212D0A3501E60103400410EF | ||||
| :4000C0000620070E09300A1C0B020C100D3E0E4011011461150816061710186219301A01200B210822602320266027302A802B012C7C2D042F3032FC337E360337013980D2 | ||||
| :400100003E403F404201470E481049FF4AFF4BFF4F83580459045A045B045C995D095F01823F843F8A028C019208963F9A3F9C3F9D01A210A604AA20AC3FAD02B502B63F2A | ||||
| :40014000B701BE40BF50D804D904DB04DF010085020A04100504060108A90D040E040F2010201120134215401680171418021C101D801E20200222102680272028022D08FD | ||||
| :400180002E802F4832403410350437013D423E083F20458046084720550858405A205C405E2A6501660867066A8080A08404870288408A028C20A508B108C0EFC26FC4FF5D | ||||
| :4001C000CAF8CCE8CEF0D010D6FCD8F0E607EE20020703D3040805130610072009010B080DF20E280F04111212401380151217401804190A1A301B051C021E381F12203F7E | ||||
| :40020000251327E028382A012C342E08307F33F0350737083B203F405608580459045B045C995D905F018002821883018404860887018B018E188F0191019207950198180E | ||||
| :400240009B019C049E109F01A01FA301A418A501A601AB01AE20AF01B03FB101B301BF05C036C9FFCAFFCBFFD004D601D804D904DA04DB04DC09DD09DF01E080E240E44082 | ||||
| :40028000E5640102032204020504060209410B080E840F02120B130215081742184019021E801F0121442298250127502B202C102D202E052F22310432923698370238028F | ||||
| :4002C00039483A103D843EA247084A024B804F1059806002680269406B026D046E216F0675287622861094449548960497C698819B809C1A9D0D9E819F15A0E4A110A501E4 | ||||
| :40030000A605A708A801AB80AC81AE88B001B401B510C0BDC2DBC4BBCAF2CCFFCEFFD040D220D608D808E202EA0B02010301040107010A010B010D040E010F0312011302F6 | ||||
| :400340001503160117041A011B011E011F012201230125012601270428012B012E012F01330734013E103F04580459045B045F0180038140821C844C860387078B408C8032 | ||||
| :400380008D1F9104931095189620970199189A059E4F9F20A003A102A210A318A504A708A80FAA40AB18AD80AE0AB03FB280B3C0B440B53FBA02BE04D804D904DB04DC9907 | ||||
| :4003C000DF010142032004020528060209400B280E65124A13021404158016A019131A601F10201023042504280229242B402D282E812F62320A33603404369A3810390293 | ||||
| :400400003A403B043D843E2E3F8059405B8069406E806F0D7A027B018580918C92019486975C988199209A229BD29C089D859F09A0E6A110A298A302A540A607A729A8400D | ||||
| :40044000C0FDC2F7C4FBCAFFCCFFCEFFE208EE0C0156020103210403070108030B020C030D061003110713081602170119061A031C031F042106220423782540260827069F | ||||
| :4004800029112A032B262C032D7F320F3403357F3E103F10580459045B045C095F0180018103830C843F870F89088A028E048F01910F923F95049620983F99089D089E3F1D | ||||
| :4004C000A108A208A508A610AA3FAB02AC3FAF08B23FB70FBE04BF40D804D904DF01008801020244044005150AB10B040E450F111105120813801401153017861B081C2078 | ||||
| :400500001D041E081F042302270429102A452D102E022F223218334234013510368838403A203B093C503D043E016D806E406F028A028B0490509145929095209645975408 | ||||
| :40054000980299139AA59B409C809D049E02A044A288A411A54FA604A72AB404C0FFC2FFC4FFCAFFCCFFCEFFE208EE0C0105050F0601071009050A040C010D080F021105AF | ||||
| :40058000120115051602180119101C081D052001230524012505292C2A012B022C082D132F2030073408373F3A223F40580459045C095F018001820C8303853D860187C0FF | ||||
| :4005C000880689808C068D809003918092089407958099309A069C069D809E01A10CA201A340A5A9A602A801A980AA06AC06AD56B1FFB40FBE10BF01D608D804D904DB0439 | ||||
| :40060000DD90DF010008030A044205100608072009800A200B840D080E451108120A130214201521160219111A881C042180260229122A042B022E092F4632013364350557 | ||||
| :400640003680372239403A213B043C403D023F145F40600861106680868088028904905A914592329308940496C99714982099029A259B649C88A044A288A411A54EA60479 | ||||
| :40068000A720AB20AD80AE20AF01B412B610B701C0F7C2FFC4FFCAFFCCFFCEFFD610D810E8C0EE1002490502060108400A300C480E20100115021602184919011E04204807 | ||||
| :4006C00021052210244928012D022E013207330134383504364037023A083BC03F04400145C047E0482249FF4AFF4BFF50045609580459045A045B045C995D995F0162C025 | ||||
| :40070000825D8501861D881D8C1D9207941D98019A309C1D9E40A01DA45DA801AA08AC0CAE22B040B203B301B43CBE15BF04C043C702C810C9FFCAFFCBFFCD20CEF0D11014 | ||||
| :40074000D804D904DA04DD09DF01E108E240E340E480E640E7400062032004800511072008090A810B080E420F2410101188128013101450151016011A201B121C101D9077 | ||||
| :400780001F8421042402269827202C442E202F81361438083C044104430245804C405004550159015A015CB05D1065406D456E106F547444750276A57709828083408504AB | ||||
| :4007C000860189408A808E04904094109505965997149C0F9D829E859F38A201A410A50FA680A720C0FFC2FFC4FFCA90CC60CE42D013D610D810E280E63001030208040194 | ||||
| :40080000070808080B020D030E0F100F1110140815031A021B031C031D031E0C2008230424082703280429032C082F01300F310F35103E013F10580459045B045C905F0131 | ||||
| :400840008001812E8341850F861087108A3F8B088D0E8E028F70903F930295219620974E983F9B019E3F9F04A10EA208A604A70EAA3FAB01AC3FAD7FB57FB63FBE40BF103B | ||||
| :40088000D804D904DF010040010202040320041005800680071008040AC90C100D420E190F0411051201132015011601172819011E022040210122102404281929012C40C4 | ||||
| :4008C0002E502F203002310832403320340236843720380439823B103C603D083E0168028101821088408A04C0FFC2FFC4FFCAFFCCFFCEFFE40400AA0101024405080644A9 | ||||
| :4009000009010CF10D0112221401170218CC1A221B011C881F01221123042B012D01300E3108330734F0360F3B083E013F015608580859045B045C995D905F0180028210E0 | ||||
| :4009400083078540860A89188B018D028F18907A9104920493109401968098199A629B209C829E059F40A040A21BA318A41BA518A620A904AB08AD1FB13FB280B478B540A1 | ||||
| :40098000B607B920BA80BE04BF10D804D904DC99DF01000A01800504074009880A400B040C020D080E090F20110114401620188019801B501D041E091F2420202101231042 | ||||
| :4009C000250126012708280829012B022C022F64302431403642371038043A413B503D863E2058805A1060066180680469106A4079808080830884118501880489108B44A1 | ||||
| :400A00008C0A8D408E40910592109380948095809748984099419A219B509C089D049E40A022A140A242A302A410A601A760AA01B508C03DC2FFC438CAFDCCBECEFFD60C07 | ||||
| :400A4000D80CDE08E008E280E402E649EA06EE02001F0101034006200720090B0A180C180F0B1080124013021418160119421A071B051C021E1822C02440250126802702E3 | ||||
| :400A800028042A082B082C042D022E102F1431403280343F350736403738388839803B203E443F01580459045B045C995F01803F8501861087868A3F8B288C01916892048B | ||||
| :400AC000963F983F99869B019D039E089F80A108A202A43FA984AA20AD50AE3FB03FB118B307B580B760B908BE01BF14D608D804D908DB04DC90DD90DF0100A802400440A7 | ||||
| :400B000006880710088909200C400D040E8810A0112013801514174519011C021D081E082101220123192402254026A22740280229102A022C022D102E802F24304032818E | ||||
| :400B400033203622381039083D883E223F01410442205A8060026F0179808044812082108322850188018E408F40901091089380948695029758988199509A089B949C0AE3 | ||||
| :400B80009D049F08A0E6A114A240A540A607A720B610C0FFC2FFC4FFCA7DCCADCEF6D608D808DE08E28DE820EE0B03220401050709800A020C0C0E020F111107140A160473 | ||||
| :400BC00017071B081C102107223025442601280829552B222D662E042F11310F320F330F34103580362037703B0A3E103F10580459045B045C995F01810182068402850160 | ||||
| :400C0000880289018C028F0190039101930294029501970299019E039F01A002A101A402A701A801A901AA04AC02AD01B003B204B503BE05BF10D804D904DF010088010262 | ||||
| :400C40000215031804240580070108040A510C080E0211451208168A170819081B011C201E061F302220252F26042782280629082A012C082D802E082F80310833923624D6 | ||||
| :400C8000370239613A043D103E023F846D9081048410870889208C049050914592509308952096059714980299139A849B409C809F80A044A288A302A401A50FA720B34087 | ||||
| :400CC000C0EFC2ADC4FFCAFFCCEFCEFFE211E440E860EE0B0002010104020501080209010C020F01100215011602180219011C081D011E0120022301240326042704281059 | ||||
| :400D000029012C1C2E032F02321F3507370739A03E04580459045C905F018006810282018360850186088738880489328C078D3290079102931095339602970498079932E7 | ||||
| :400D40009D329E10A007A133A30CA601A732A805A932AA02AD1FAF40B30FB570B61FBF14D804D904DB04DC09DF01005002A0040205400608071008020A010B280E600F0644 | ||||
| :400D800010021109120815101620170219081D401E011F80200A2110221023042718290A2B0A2FA63004321033403402351136A439443A113C503D013F0448035F406C099A | ||||
| :400DC0006D106F018511875088048A128B098C108EA88FC090509144924093289420952096019714980499139A919B429E0A9F01A104A28CA32BA409A7C0AB02AD40B480B4 | ||||
| :400E0000C0FFC2FFC47FCAFFCCFECEFFD610E090E260E440E6B1EC10EE021280170430203304368438083B403C808004C430CCF0CE70E68032043380364037029740A420E0 | ||||
| :400E4000A604B040CCF0530456109308960897409F02A420A644A780AF04D460EE1097409E109F02A640A780A820AA04AE04EA80EE3014808301C4045D0893029880D60148 | ||||
| :400E80001B0493029880B108C608E80108080B080D800F4086019302970898809B40AB04AF04B740C20FE8082580890297409E10A580AB02AE40AF80B180C820EED053045D | ||||
| :400EC00056207240750282408B048E209902AE10AF40D460DC80DE20E220EA40EE40070408020E010F201F10538056205B40670283048A208F109302960198809B40A38043 | ||||
| :400F0000A980AC08AF40B780C001C20DC601D407D601E208E401E8027210808092109880AF10B402B610DC01EA08EC08010109010B010D010F0111011B011D0100FF01AB85 | ||||
| :400F400002021005BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B84700470000010000800000008000800000000000000303005F | ||||
| :400F800003000000270018012700180100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000AD | ||||
| :400000000145004009520040015B0040016500400101014003030140010501400307014052080140500901404F0A0140480B0140490C0140470D0140400E0140360F01409E | ||||
| :4000400002150140031701405C18014057190140471A0140531B01400B400140124101400B4201400D43014002440140074501400B4601400C470140104801401149014069 | ||||
| :40008000154C01400D4D014005500140045101407E02080209411080110218041902600C61157C402721290AE204E601EA20EE02E202E608EA10EE04000801080404050568 | ||||
| :4000C0000708080809080C400D080E1011011201130E143A16401708180819081C021E042305240825042701280629082A782C202D012E402F02307F310F327F330F380AE4 | ||||
| :400100003B0A580459045B045C995F018203830187018A108C018D018E248F08907C9282930E961097019A809D019E1C9F04A101A210A302A510A610AA10AB01AC01AE48FD | ||||
| :40014000B01FB10FB4F0B510BA20BB02BF10D608D80BD90BDB04DC99DD90DF01012202010488051007800A860B100C020DA00E2011011208134414021504162017201804BE | ||||
| :4001800019021A041B011CA01E202008220126012790282029202A012C022E282F4032493514360139013B443C803D2A5980614069906F0278417B0288049141928093D450 | ||||
| :4001C00094249602970A982299249A019B409C019D019EC0A188A208A402A504A709B404B502B604C0FBC2FFC4EFCAF7CCEBCEFBD608D808DE01E608EA10EE0400FC0108A1 | ||||
| :4002000002020310071408200AD00BE40CE10D100E080F8212031314161017141D1F1E101F4020802110223C2321261028402A302B142CE12E04301F310F33F034E03B08A2 | ||||
| :40024000580B590B5B045C995F0180368209852A86128740882D89098A128B148E098F209080924093C09440968098079C249D089F13A03FA180A320A840A908ADEAB0076D | ||||
| :40028000B1E0B280B301B438B518B640B706BE44D808D904DB04DC99DF0100820120040A07050A420B100EA80F01110112091304171219201A401D8A1E081F4020602101B0 | ||||
| :4002C000231025102652275029202A012C022F483249364239033B543D283E806102620169406C0C6F0178017F0187058C028D04900292809345946495029642971298206E | ||||
| :4003000099049B069C019D219E449F01A041A208A418A701B040B204B7C0C0FBC2FDC45FCAB5CC9BCE7FDE11E40800020104040806140A010C1C101012081301140215050D | ||||
| :400340001601170220032B022D062E022F0130043107320336183A083F015608580459045B045C095D905F018128829083118401853F86448A9C8C208D3F8EC090C191FFA2 | ||||
| :40038000922895149690972199019A039B0E9D409E90A0FCA202A902AA90AB01ADC0AE90B140B330B41FB50FB6E0B780BA80D608D80BD90BDB04DC99DD90DF0101A604240E | ||||
| :4003C0000501070208400A240B400EA0120113121401164019801A041B401D841EA02080229C2344260129602C082F0230023294370539063A053B403F01580259025A0213 | ||||
| :400400005B405F806140670168036A806B026C016F06780182019440950296E4974698209C429D209E049F02A412A580A741B240B420B540C0FFC23FC49BCA3CCCCFCE8BAA | ||||
| :40044000D618D818DE01EA20EE0203900401060407900A100B9C0D010E100F4812101520161017C01A031B031DFC1E1C1F02201C2202239025C127242A102B902C012E08A9 | ||||
| :400480002F9033E0341F351F3B08420147E0482049FF4AFF4BFF4F83580859085A045B045C995D095F018310871C8B108D018F0893109F03A310A501A704AB10AD1CAF0254 | ||||
| :4004C000B71FD908DB04DC90DF0100400120032008420A281110134219101A0820042120220826802701284229202A042B202C422D202F20312032483402368039123B4544 | ||||
| :400500003C203D103F454180520159105A445B0160046208638269406E8078807C807F018201C007C20FC40BCAFFCC9ECEFFD008D60FD80FDE18EA01EE08E020E623EE0BF2 | ||||
| :40054000022005880612083209880C080E010F011049118812321432151017A0182119461A1C1BB81D9A1E091F20206321422204230424322588282029882A022C322D04B6 | ||||
| :400580003020313F324033C1341F37C1398A3A203E055608580459045B045C995D905F018401860289018D028E039002920199089B049D049F08A004B002B102B204B304D8 | ||||
| :4005C000B401B501B708B822B988BE15BF55C043C520C802C9FFCAFFCBFFCD20CEF0D110D804D904DA04DB04DD09DF01E108E240E340E480E640E740000801400320051496 | ||||
| :40060000060107400A800D800EA8108016441799181019021B101C401E081F0420012111220424A02505264227A02D202F52324033203504360137A039903C243E4040504B | ||||
| :40064000488049204A0459405A205D045E085F4064016580670268046A806C036E406F01831085118B4491D0938094A89504970A984099449B509C019D119EC89F20A0500A | ||||
| :40068000A188A211A402A535A708A980C0F5C2F8C4F8CAF0CCFCCE7CD003D61CD810E022E620EA04EE0B010129022C013102350136013E403F115608580459045B045D9048 | ||||
| :4006C0005F0180478208830885218626874E880189018A388B708D4E8E40914E92029426974E98269B029D809E11A010A201A321A520A604A701A811AA26AB04AC26AD0FF1 | ||||
| :40070000AF10B040B380B43FB57FB63FBAA0BB20BF04C003C50EC70CC811C9FFCAFFCBFFD004D601D804D904DA04DB04DC99DD09DF01E2C0040205940DA20E201002150580 | ||||
| :40074000162017A01B401D111E011F102040221024042530260827082B042E642F40344035043602371038013C803D283E0145084F04570858905C406001630868026C042B | ||||
| :400780006D016E046F02768981028410860888808D028E049002910492409380940C96209724984099869A049BB09D119E899F08A040A18CA212A530A74AA881AE04B74049 | ||||
| :4007C000C0F0C2F0C4F1CAF4CCF0CEF1D040D61CD80CE008E404E602E8041B011F083180330836843B408340C630CCF0CE10E22032043380364037023B043F808180A0042B | ||||
| :40080000A340A580A604AE80AF41B004CCF0CE60E680EE40531057208510960897049F02A004A644A780D460E240860491209608972498809F02A004A640A7A0AA04B48062 | ||||
| :40084000E610EE201680C40458405E019A80A404AC04D401D6011B04844096019C40A404A710B680B710C608EA08EE0808080B080E020F4087048A209601A404A710AB043D | ||||
| :40088000C20FE00425808004871089808B04912097249880A004A720AB02AE40AF80C820E6C0EED0511054045880700477809008912098809B80A004AB80AF20D4E0DC8000 | ||||
| :4008C000DE20EA80EE1005200A400C100F201C0852225620610186029641A220A404A710AA41AC08AF40C001C20DC601D407D80270018001852086018810960198109920A0 | ||||
| :40090000AA20B501DC01E204EC0201010D010F0111011D0100FF01AB02021105BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B88D | ||||
| :4009400047004700000100008000000282008200000000000007070007000000270018012700180100040000000500000000000000000000000000000000000000000000C4 | ||||
| :400980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037 | ||||
| :4009C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F7 | ||||
| :400A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B6 | ||||
| :400A40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076 | ||||
| :400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036 | ||||
| :400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6 | ||||
| :400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5 | ||||
| :400B40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075 | ||||
| :400B80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035 | ||||
| :400BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F5 | ||||
| :400C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B4 | ||||
| :400C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074 | ||||
| :400C80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034 | ||||
| :400CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F4 | ||||
| :400D000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B3 | ||||
| :400D40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073 | ||||
| :400D80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033 | ||||
| :400DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F3 | ||||
| :400E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B2 | ||||
| :400E40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000072 | ||||
| :400E80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032 | ||||
| :400EC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F2 | ||||
| :400F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B1 | ||||
| :400F40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071 | ||||
| :400F80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031 | ||||
| :400FC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F1 | ||||
| :4010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0 | ||||
| :401040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070 | ||||
| @@ -4615,12 +4615,12 @@ | ||||
| :0200000490105A | ||||
| :04000000BC90ACAF55 | ||||
| :0200000490303A | ||||
| :0200000001A954 | ||||
| :02000000CEC56B | ||||
| :0200000490402A | ||||
| :4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0 | ||||
| :400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080 | ||||
| :400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040 | ||||
| :4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 | ||||
| :0200000490501A | ||||
| :0C00000000012E16106900002E301212B4 | ||||
| :0C00000000012E16106900002E2FDF2ECC | ||||
| :00000001FF | ||||
| @@ -20,33 +20,34 @@ module FIFOout ( | ||||
| wire [7:0] po; | ||||
| assign d = po; | ||||
|  | ||||
| localparam STATE_WAIT = 1'b0; | ||||
| localparam STATE_READ = 1'b1; | ||||
| localparam STATE_WAITFORREQ = 0; | ||||
| localparam STATE_READFROMFIFO = 1; | ||||
| localparam STATE_WAITFORNREQ = 2; | ||||
|  | ||||
| reg state; | ||||
| reg oldreq; | ||||
| reg [1:0] state; | ||||
| wire readfromfifo; | ||||
|  | ||||
| assign ack = (state != STATE_READ); | ||||
| assign ack = (state == STATE_WAITFORNREQ); | ||||
| assign readfromfifo = (state == STATE_READFROMFIFO); | ||||
|  | ||||
| always @(posedge clk) | ||||
| begin | ||||
|     case (state) | ||||
|         STATE_WAIT: | ||||
|         /* opcode is not valid; req is low; wait for req to go high. */ | ||||
|         STATE_WAITFORREQ: | ||||
|         begin | ||||
|             if (!empty) | ||||
|             begin | ||||
|                 if (req && !oldreq) | ||||
|                 begin | ||||
|                     state <= STATE_READ; | ||||
|                 end | ||||
|                 oldreq <= req; | ||||
|             end | ||||
|             if (!empty && req) | ||||
|                 state <= STATE_READFROMFIFO; | ||||
|         end | ||||
|          | ||||
|         STATE_READ: | ||||
|         begin | ||||
|             state <= STATE_WAIT; | ||||
|         end | ||||
|         /* Fetch a single value from the FIFO. */ | ||||
|         STATE_READFROMFIFO: | ||||
|             state <= STATE_WAITFORNREQ; | ||||
|              | ||||
|         /* opcode is valid; ack is high. Wait for req to go low. */ | ||||
|         STATE_WAITFORNREQ: | ||||
|             if (!req) | ||||
|                 state <= STATE_WAITFORREQ; | ||||
|     endcase | ||||
| end | ||||
|              | ||||
| @@ -55,11 +56,11 @@ cy_psoc3_dp #(.cy_dpconfig( | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM0: STATE_WAITFORREQ*/ | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM0: idle */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC___F0, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM1: STATE_LOAD*/ | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM1: read from fifo */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| @@ -102,7 +103,7 @@ cy_psoc3_dp #(.cy_dpconfig( | ||||
| )) dp( | ||||
|         /*  input                   */  .reset(1'b0), | ||||
|         /*  input                   */  .clk(clk), | ||||
|         /*  input   [02:00]         */  .cs_addr({2'b0, state}), | ||||
|         /*  input   [02:00]         */  .cs_addr({2'b0, readfromfifo}), | ||||
|         /*  input                   */  .route_si(1'b0), | ||||
|         /*  input                   */  .route_ci(1'b0), | ||||
|         /*  input                   */  .f0_load(1'b0), | ||||
|   | ||||
| @@ -814,7 +814,7 @@ | ||||
|   </Group> | ||||
|   <Group key="Component"> | ||||
|     <Group key="v1"> | ||||
|       <Data key="cy_boot" value="cy_boot_v5_80" /> | ||||
|       <Data key="cy_boot" value="cy_boot_v5_81" /> | ||||
|       <Data key="Em_EEPROM_Dynamic" value="Em_EEPROM_Dynamic_v2_20" /> | ||||
|       <Data key="LIN_Dynamic" value="LIN_Dynamic_v5_0" /> | ||||
|     </Group> | ||||
| @@ -848,15 +848,18 @@ | ||||
|       <Data key="efd5f185-0c32-4824-ba72-3ceb5356f5a7" value="Clock_1" /> | ||||
|     </Group> | ||||
|     <Group key="Pin"> | ||||
|       <Data key="3e1862bb-be82-47b0-9549-7ebfe76b6f7b" value="Pin_2" /> | ||||
|       <Data key="4a398466-709f-4228-9500-96178658e13e" value="RDATA" /> | ||||
|       <Data key="5a3407c1-b434-4438-a7b4-b9dfd2280495" value="MOTEA" /> | ||||
|       <Data key="8d318d8b-cf7b-4b6b-b02c-ab1c5c49d0ba" value="SW1" /> | ||||
|       <Data key="8fc20a4f-e4d1-44b3-a5d4-546e8628d61e" value="LED" /> | ||||
|       <Data key="12e00eac-69b5-4717-85c8-25ef6b224d4c" value="DEBUG_PINS" /> | ||||
|       <Data key="41e2d8ed-5494-4d8c-8ff7-f4f789cece51" value="REDWC" /> | ||||
|       <Data key="264be2d3-9481-494b-8d9c-c1905a45e9cc" value="FDD" /> | ||||
|       <Data key="472f8fdb-f772-44fb-8897-cc690694237b" value="WDATA" /> | ||||
|       <Data key="736cb12b-c863-43d4-a8f0-42f06023f8b5" value="SIDE1" /> | ||||
|       <Data key="4249c923-fcff-453b-8629-bec6fddd00c1" value="STEP" /> | ||||
|       <Data key="27315b0e-6a8c-4b7f-be77-73ab434fa803" value="Pin_1" /> | ||||
|       <Data key="1425177d-0d0e-4468-8bcc-e638e5509a9b" value="UartRx" /> | ||||
|       <Data key="a5d987c6-e45b-45b9-ad93-656fab06d720" value="TRK00" /> | ||||
|       <Data key="a93ef5b3-00f4-42c0-8fad-0e275a7e2537" value="MOTEB" /> | ||||
| @@ -868,6 +871,7 @@ | ||||
|       <Data key="c5367cde-21d5-4866-9a32-d16abfea0c61" value="WPT" /> | ||||
|       <Data key="d19368c5-6855-41bb-a9ff-808938abef00" value="INDEX" /> | ||||
|       <Data key="e9f14b5a-b2bf-49b8-98f3-d7b5a43ace8d" value="DRVSB" /> | ||||
|       <Data key="e16b5ef8-00d3-40a4-bc1c-194983c8eb3d" value="LOW_CURRENT" /> | ||||
|       <Data key="e851a3b9-efb8-48be-bbb8-b303b216c393" value="INDEX300" /> | ||||
|       <Data key="e51063a9-4fad-40c7-a06b-7cc4b137dc18" value="DSKCHG" /> | ||||
|       <Data key="ea7ee228-8b3f-426c-8bb8-cd7a81937769" value="DIR" /> | ||||
| @@ -3962,6 +3966,11 @@ | ||||
|     </Group> | ||||
|   </Group> | ||||
|   <Group key="Pin2"> | ||||
|     <Group key="3e1862bb-be82-47b0-9549-7ebfe76b6f7b"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="0,6" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="4a398466-709f-4228-9500-96178658e13e"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="1,5" /> | ||||
| @@ -3977,6 +3986,11 @@ | ||||
|         <Data key="Port Format" value="2,2" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="8fc20a4f-e4d1-44b3-a5d4-546e8628d61e"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="2,1" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="12e00eac-69b5-4717-85c8-25ef6b224d4c"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="2,2" /> | ||||
| @@ -4058,6 +4072,11 @@ | ||||
|         <Data key="Port Format" value="1,0" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="27315b0e-6a8c-4b7f-be77-73ab434fa803"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="0,7" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="1425177d-0d0e-4468-8bcc-e638e5509a9b"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="12,6" /> | ||||
| @@ -4143,6 +4162,11 @@ | ||||
|         <Data key="Port Format" value="12,3" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="e16b5ef8-00d3-40a4-bc1c-194983c8eb3d"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="3,2" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="e851a3b9-efb8-48be-bbb8-b303b216c393"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="3,0" /> | ||||
|   | ||||
| @@ -39,20 +39,6 @@ | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="crunch.c" persistent="..\lib\common\crunch.c"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="crunch.h" persistent="..\lib\common\crunch.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| @@ -1533,20 +1519,20 @@ | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep4" persistent=""> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep4_dma.c" persistent="Generated_Source\PSoC5\USBFS_ep4_dma.c"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep4_dma.h" persistent="Generated_Source\PSoC5\USBFS_ep4_dma.h"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| @@ -1559,20 +1545,20 @@ | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep3" persistent=""> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep3_dma.c" persistent="Generated_Source\PSoC5\USBFS_ep3_dma.c"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep3_dma.h" persistent="Generated_Source\PSoC5\USBFS_ep3_dma.h"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| @@ -1585,20 +1571,20 @@ | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep2" persistent=""> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep2_dma.c" persistent="Generated_Source\PSoC5\USBFS_ep2_dma.c"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep2_dma.h" persistent="Generated_Source\PSoC5\USBFS_ep2_dma.h"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| @@ -1611,20 +1597,20 @@ | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep1" persistent=""> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep1_dma.c" persistent="Generated_Source\PSoC5\USBFS_ep1_dma.c"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="USBFS_ep1_dma.h" persistent="Generated_Source\PSoC5\USBFS_ep1_dma.h"> | ||||
| <Hidden v="True" /> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| @@ -2762,6 +2748,171 @@ | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED" persistent=""> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED_aliases.h" persistent="Generated_Source\PSoC5\LED_aliases.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED.c" persistent="Generated_Source\PSoC5\LED.c"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED.h" persistent="Generated_Source\PSoC5\LED.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2" persistent=""> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2_aliases.h" persistent="Generated_Source\PSoC5\Pin_2_aliases.h"> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2.c" persistent="Generated_Source\PSoC5\Pin_2.c"> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_2.h" persistent="Generated_Source\PSoC5\Pin_2.h"> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1" persistent=""> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1_aliases.h" persistent="Generated_Source\PSoC5\Pin_1_aliases.h"> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1.c" persistent="Generated_Source\PSoC5\Pin_1.c"> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="Pin_1.h" persistent="Generated_Source\PSoC5\Pin_1.h"> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43_REG" persistent=""> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43_REG.h" persistent="Generated_Source\PSoC5\TK43_REG.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43_REG.c" persistent="Generated_Source\PSoC5\TK43_REG.c"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43_REG_PM.c" persistent="Generated_Source\PSoC5\TK43_REG_PM.c"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43" persistent=""> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43_aliases.h" persistent="Generated_Source\PSoC5\TK43_aliases.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43.c" persistent="Generated_Source\PSoC5\TK43.c"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="TK43.h" persistent="Generated_Source\PSoC5\TK43.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| @@ -3347,6 +3498,6 @@ | ||||
| </ignored_deps> | ||||
| </CyGuid_495451fe-d201-4d01-b22d-5d3f5609ac37> | ||||
| <boot_component v="" /> | ||||
| <current_generation v="114" /> | ||||
| <current_generation v="150" /> | ||||
| </CyGuid_fec8f9e8-2365-4bdb-96d3-a4380222e01b> | ||||
| </CyXmlSerializer> | ||||
| @@ -1,7 +1,6 @@ | ||||
|  | ||||
| //`#start header` -- edit after this line, do not edit this line | ||||
| `include "cypress.v" | ||||
| `include "../SuperCounter/SuperCounter.v" | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| // Generated on 12/11/2019 at 21:18 | ||||
| @@ -9,7 +8,7 @@ | ||||
| module Sampler ( | ||||
| 	output [2:0] debug_state, | ||||
| 	output reg [7:0] opcode, | ||||
| 	output  req, | ||||
| 	output  reg       req, | ||||
| 	input   clock, | ||||
| 	input   index, | ||||
| 	input   rdata, | ||||
| @@ -19,113 +18,63 @@ module Sampler ( | ||||
|  | ||||
| //`#start body` -- edit after this line, do not edit this line | ||||
|  | ||||
| localparam STATE_RESET = 0; | ||||
| localparam STATE_WAITING = 1; | ||||
| localparam STATE_INTERVAL = 2; | ||||
| localparam STATE_DISPATCH = 3; | ||||
| localparam STATE_OPCODE = 4; | ||||
| localparam STATE_COUNTING = 5; | ||||
| // NOTE: Reset pulse is used in both clock domains, and must be long enough | ||||
| // to be detected in both. | ||||
|  | ||||
| reg [2:0] state; | ||||
| wire [6:0] counter; | ||||
| reg [5:0] counter; | ||||
|  | ||||
| wire countnow; | ||||
| assign countnow = (state == STATE_COUNTING); | ||||
| reg index_q; | ||||
| reg rdata_q; | ||||
|  | ||||
| wire counterreset; | ||||
| assign counterreset = (state == STATE_INTERVAL) || (state == STATE_OPCODE); | ||||
| reg index_edge; | ||||
| reg rdata_edge; | ||||
|  | ||||
| SuperCounter #(.Delta(1), .ResetValue(0)) Counter | ||||
| ( | ||||
|     /* input */ .clk(clock), | ||||
|     /* input */ .reset(counterreset), | ||||
|     /* input */ .count(countnow), | ||||
|     /* output */ .d(counter) | ||||
| ); | ||||
| reg req_toggle; | ||||
|  | ||||
| reg oldsampleclock; | ||||
| wire sampleclocked; | ||||
| assign sampleclocked = !oldsampleclock && sampleclock; | ||||
|  | ||||
| reg oldindex; | ||||
| wire indexed; | ||||
| assign indexed = !oldindex && index; | ||||
|  | ||||
| wire rdataed; | ||||
| reg oldrdata; | ||||
| assign rdataed = !oldrdata && rdata; | ||||
|  | ||||
| assign req = (state == STATE_INTERVAL) || (state == STATE_OPCODE); | ||||
|  | ||||
| always @(posedge clock) | ||||
| always @(posedge sampleclock) | ||||
| begin | ||||
|     if (reset) | ||||
|     begin | ||||
|         state <= STATE_RESET; | ||||
|         opcode <= 0; | ||||
|         oldsampleclock <= 0; | ||||
|         oldindex <= 0; | ||||
|         oldrdata <= 0; | ||||
|         index_edge <= 0; | ||||
|         rdata_edge <= 0; | ||||
|         index_q <= 0; | ||||
|         rdata_q <= 0; | ||||
|         counter <= 0; | ||||
|         req_toggle <= 0; | ||||
|     end | ||||
|     else | ||||
|         case (state) | ||||
|             STATE_RESET: | ||||
|                 state <= STATE_WAITING; | ||||
|              | ||||
|             STATE_WAITING: | ||||
|             begin | ||||
|                 if (rdataed || indexed) | ||||
|                 begin | ||||
|                     opcode <= {0, counter}; | ||||
|                     state <= STATE_INTERVAL; | ||||
|                 end | ||||
|                 else if (sampleclocked) | ||||
|                 begin | ||||
|                     oldsampleclock <= 1; | ||||
|                     if (counter == 7'h7f) | ||||
|                     begin | ||||
|                         opcode <= {0, counter}; | ||||
|                         state <= STATE_OPCODE; | ||||
|                     end | ||||
|                     else | ||||
|                         state <= STATE_COUNTING; | ||||
|                 end | ||||
|                  | ||||
|                 if (oldrdata && !rdata) | ||||
|                     oldrdata <= 0; | ||||
|                 if (oldindex && !index) | ||||
|                     oldindex <= 0; | ||||
|                 if (oldsampleclock && !sampleclock) | ||||
|                     oldsampleclock <= 0; | ||||
|             end | ||||
|              | ||||
|             STATE_INTERVAL: /* interval byte sent here; counter reset */ | ||||
|                 state <= STATE_DISPATCH; | ||||
|                  | ||||
|             STATE_DISPATCH: /* relax after interval byte, dispatch for opcode */ | ||||
|             begin | ||||
|                 if (rdataed) | ||||
|                 begin | ||||
|                     oldrdata <= 1; | ||||
|                     opcode <= 8'h80; | ||||
|                     state <= STATE_OPCODE; | ||||
|                 end | ||||
|                 else if (indexed) | ||||
|                 begin | ||||
|                     oldindex <= 1; | ||||
|                     opcode <= 8'h81; | ||||
|                     state <= STATE_OPCODE; | ||||
|                 end | ||||
|                 else | ||||
|                     state <= STATE_WAITING; | ||||
|             end | ||||
|              | ||||
|             STATE_OPCODE: /* opcode byte sent here */ | ||||
|                 state <= STATE_WAITING; | ||||
|                              | ||||
|             STATE_COUNTING: /* counter changes here */ | ||||
|                 state <= STATE_WAITING; | ||||
|         endcase | ||||
|     begin | ||||
|         /* Both index and rdata are active high -- positive-going edges | ||||
|          * indicate the start of an index pulse and read pulse, respectively. | ||||
|          */ | ||||
|           | ||||
|         index_edge <= index && !index_q; | ||||
|         index_q <= index; | ||||
|          | ||||
|         rdata_edge <= rdata && !rdata_q; | ||||
|         rdata_q <= rdata; | ||||
|          | ||||
|         if (rdata_edge || index_edge || (counter == 6'h3f)) begin | ||||
|             opcode <= { rdata_edge, index_edge, counter }; | ||||
|             req_toggle <= ~req_toggle; | ||||
|             counter <= 1;   /* remember to count this tick */ | ||||
|         end else begin | ||||
|             counter <= counter + 1; | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| reg req_toggle_q; | ||||
|  | ||||
| always @(posedge clock) | ||||
| begin | ||||
|     if (reset) begin | ||||
|         req_toggle_q <= 0; | ||||
|         req <= 0; | ||||
|     end else begin | ||||
|         req_toggle_q <= req_toggle; | ||||
|         req <= (req_toggle != req_toggle_q); | ||||
|     end | ||||
| end | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
|   | ||||
| @@ -18,21 +18,16 @@ module Sequencer ( | ||||
|  | ||||
| //`#start body` -- edit after this line, do not edit this line | ||||
|  | ||||
| localparam STATE_IDLE = 0; | ||||
| localparam STATE_LOAD = 1; | ||||
| localparam STATE_WAITING = 2; | ||||
| localparam STATE_PULSING = 3; | ||||
| localparam STATE_INDEXING = 4; | ||||
| localparam STATE_LOAD = 0; | ||||
| localparam STATE_WRITING = 1; | ||||
|  | ||||
| localparam OPCODE_PULSE = 8'h80; | ||||
| localparam OPCODE_INDEX = 8'h81; | ||||
| reg state; | ||||
| reg [5:0] countdown; | ||||
| reg pulsepending; | ||||
|  | ||||
| reg [2:0] state; | ||||
| reg [6:0] countdown; | ||||
|  | ||||
| assign req = (state == STATE_LOAD); | ||||
| assign wdata = (state == STATE_PULSING); | ||||
| assign debug_state = state; | ||||
| assign req = (!reset && (state == STATE_LOAD)); | ||||
| assign wdata = (!reset && (state == STATE_WRITING) && (countdown == 0) && pulsepending); | ||||
| assign debug_state = 0; | ||||
|  | ||||
| reg olddataclock; | ||||
| wire dataclocked; | ||||
| @@ -40,9 +35,7 @@ always @(posedge clock) olddataclock <= dataclock; | ||||
| assign dataclocked = !olddataclock && dataclock; | ||||
|  | ||||
| reg oldsampleclock; | ||||
| wire sampleclocked; | ||||
| always @(posedge clock) oldsampleclock <= sampleclock; | ||||
| assign sampleclocked = !oldsampleclock && sampleclock; | ||||
| reg sampleclocked; | ||||
|  | ||||
| reg oldindex; | ||||
| wire indexed; | ||||
| @@ -53,48 +46,44 @@ always @(posedge clock) | ||||
| begin | ||||
|     if (reset) | ||||
|     begin | ||||
|         state <= STATE_IDLE; | ||||
|         state <= STATE_LOAD; | ||||
|         countdown <= 0; | ||||
|         pulsepending <= 0; | ||||
|         oldsampleclock <= 0; | ||||
|     end | ||||
|     else | ||||
|     begin | ||||
|         if (!oldsampleclock && sampleclock) | ||||
|             sampleclocked <= 1; | ||||
|         oldsampleclock <= sampleclock; | ||||
|              | ||||
|         case (state) | ||||
|             STATE_IDLE: | ||||
|                 state <= STATE_LOAD; | ||||
|              | ||||
|             STATE_LOAD: | ||||
|             begin | ||||
|                 /* A posedge on dataclocked indicates that another opcode has | ||||
|                  * arrived. */ | ||||
|                 if (dataclocked) | ||||
|                     case (opcode) | ||||
|                         OPCODE_PULSE: | ||||
|                             state <= STATE_PULSING; | ||||
|                          | ||||
|                         OPCODE_INDEX: | ||||
|                             state <= STATE_INDEXING; | ||||
|                          | ||||
|                         default: | ||||
|                         begin | ||||
|                             countdown <= opcode[6:0]; | ||||
|                             state <= STATE_WAITING; | ||||
|                         end | ||||
|                     endcase | ||||
|                 begin | ||||
|                     pulsepending <= opcode[7]; | ||||
|                     countdown <= opcode[5:0]; | ||||
|                      | ||||
|                     state <= STATE_WRITING; | ||||
|                 end | ||||
|             end | ||||
|              | ||||
|             STATE_WAITING: | ||||
|             STATE_WRITING: | ||||
|             begin | ||||
|                 if (sampleclocked) | ||||
|                 begin | ||||
|                     if (countdown == 0) | ||||
|                         state <= STATE_LOAD; | ||||
|                     else | ||||
|                         countdown <= countdown - 1; | ||||
|                     sampleclocked <= 0; | ||||
|                 end | ||||
|              | ||||
|             STATE_PULSING: | ||||
|                 state <= STATE_LOAD; | ||||
|              | ||||
|             STATE_INDEXING: | ||||
|                 if (indexed) | ||||
|                     state <= STATE_LOAD; | ||||
|                 else | ||||
|                     state <= STATE_INDEXING; | ||||
|             end | ||||
|         endcase | ||||
|     end | ||||
| end | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -5,7 +5,6 @@ | ||||
| #include <setjmp.h> | ||||
| #include "project.h" | ||||
| #include "../protocol.h" | ||||
| #include "../lib/common/crunch.h" | ||||
|  | ||||
| #define MOTOR_ON_TIME 5000 /* milliseconds */ | ||||
| #define STEP_INTERVAL_TIME 6 /* ms */ | ||||
| @@ -14,11 +13,18 @@ | ||||
| #define DISKSTATUS_WPT    1 | ||||
| #define DISKSTATUS_DSKCHG 2 | ||||
|  | ||||
| #define STEP_TOWARDS0 1 | ||||
| #define STEP_AWAYFROM0 0 | ||||
| #define STEP_TOWARDS0 0 | ||||
| #define STEP_AWAYFROM0 1 | ||||
|  | ||||
| static bool drive0_present; | ||||
| static bool drive1_present; | ||||
|  | ||||
| static volatile uint32_t clock = 0; /* ms */ | ||||
| static volatile bool index_irq = false; | ||||
| /* Duration in ms. 0 causes every pulse to be an index pulse. Durations since | ||||
|  * last pulse greater than this value imply sector pulse. Otherwise is an index | ||||
|  * pulse. */ | ||||
| static volatile uint32_t hardsec_index_threshold = 0; | ||||
|  | ||||
| static bool motor_on = false; | ||||
| static uint32_t motor_on_time = 0; | ||||
| @@ -26,7 +32,7 @@ static bool homed = false; | ||||
| static int current_track = 0; | ||||
| static struct set_drive_frame current_drive_flags; | ||||
|  | ||||
| #define BUFFER_COUNT 16 | ||||
| #define BUFFER_COUNT 64 /* the maximum */ | ||||
| #define BUFFER_SIZE 64 | ||||
| static uint8_t td[BUFFER_COUNT]; | ||||
| static uint8_t dma_buffer[BUFFER_COUNT][BUFFER_SIZE] __attribute__((aligned())); | ||||
| @@ -41,6 +47,8 @@ static volatile bool dma_underrun = false; | ||||
| #define DECLARE_REPLY_FRAME(STRUCT, TYPE) \ | ||||
|     STRUCT r = {.f = { .type = TYPE, .size = sizeof(STRUCT) }} | ||||
|  | ||||
| static void stop_motor(void); | ||||
|  | ||||
| static void system_timer_cb(void) | ||||
| { | ||||
|     CyGlobalIntDisable; | ||||
| @@ -65,12 +73,34 @@ static void system_timer_cb(void) | ||||
|  | ||||
| CY_ISR(index_irq_cb) | ||||
| { | ||||
|     index_irq = true; | ||||
|     /* Hard sectored media has sector pulses at the beginning of every sector | ||||
|      * and the index pulse is an extra pulse in the middle of the last sector. | ||||
|      * When the extra pulse is seen, the next sector pulse is also the start of | ||||
|      * the track. */ | ||||
|     static bool hardsec_index_irq_primed = false; | ||||
|     static uint32_t hardsec_last_pulse_time = 0; | ||||
|  | ||||
|     if (!hardsec_index_threshold) | ||||
|     { | ||||
|         index_irq = true; | ||||
|         hardsec_index_irq_primed = false; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         index_irq = hardsec_index_irq_primed; | ||||
|         if (index_irq) | ||||
|             hardsec_index_irq_primed = false; | ||||
|         else | ||||
|             hardsec_index_irq_primed = | ||||
|                 clock - hardsec_last_pulse_time <= hardsec_index_threshold; | ||||
|         hardsec_last_pulse_time = clock; | ||||
|     } | ||||
|      | ||||
|     /* Stop writing the instant the index pulse comes along; it may take a few | ||||
|      * moments for the main code to notice the pulse, and we don't want to overwrite | ||||
|      * the beginning of the track. */ | ||||
|     ERASE_REG_Write(0); | ||||
|     if (index_irq) | ||||
|         ERASE_REG_Write(0); | ||||
| } | ||||
|  | ||||
| CY_ISR(capture_dma_finished_irq_cb) | ||||
| @@ -103,7 +133,10 @@ static void print(const char* msg, ...) | ||||
| static void set_drive_flags(struct set_drive_frame* flags) | ||||
| { | ||||
|     if (current_drive_flags.drive != flags->drive) | ||||
|     { | ||||
|         stop_motor(); | ||||
|         homed = false; | ||||
|     } | ||||
|      | ||||
|     current_drive_flags = *flags; | ||||
|     DRIVESELECT_REG_Write(flags->drive ? 2 : 1); /* select drive 1 or 0 */ | ||||
| @@ -142,6 +175,12 @@ static void wait_until_writeable(int ep) | ||||
|         ; | ||||
| } | ||||
|  | ||||
| static void wait_until_readable(int ep) | ||||
| { | ||||
|     while (USBFS_GetEPState(ep) != USBFS_OUT_BUFFER_FULL) | ||||
|         ; | ||||
| } | ||||
|  | ||||
| static void send_reply(struct any_frame* f) | ||||
| { | ||||
|     print("reply 0x%02x", f->f.type); | ||||
| @@ -159,9 +198,15 @@ static void send_error(int code) | ||||
| /* buffer must be big enough for a frame */ | ||||
| static int usb_read(int ep, uint8_t buffer[FRAME_SIZE]) | ||||
| { | ||||
|     if (USBFS_GetEPState(ep) != USBFS_OUT_BUFFER_FULL) | ||||
|     { | ||||
|         USBFS_EnableOutEP(ep); | ||||
|         wait_until_readable(ep); | ||||
|     } | ||||
|  | ||||
|     int length = USBFS_GetEPCount(ep); | ||||
|     USBFS_ReadOutEP(ep, buffer, length); | ||||
|     while (USBFS_GetEPState(ep) == USBFS_OUT_BUFFER_FULL) | ||||
|     while (USBFS_GetEPState(ep) != USBFS_OUT_BUFFER_EMPTY) | ||||
|         ; | ||||
|     return length; | ||||
| } | ||||
| @@ -183,19 +228,19 @@ static void step(int dir) | ||||
|     CyDelay(STEP_INTERVAL_TIME); | ||||
| } | ||||
|  | ||||
| static void home(void) | ||||
| /* returns true if it looks like a drive is attached */ | ||||
| static bool home(void) | ||||
| { | ||||
|     for (int i=0; i<100; i++) | ||||
|     { | ||||
|         /* Don't keep stepping forever, because if a drive's | ||||
|          * not connected bad things happen. */ | ||||
|         if (TRACK0_REG_Read()) | ||||
|             break; | ||||
|             return true; | ||||
|         step(STEP_TOWARDS0); | ||||
|     } | ||||
|      | ||||
|     /* Step to -1, which should be a nop, to reset the disk on disk change. */ | ||||
|     step(STEP_TOWARDS0); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| static void seek_to(int track) | ||||
| @@ -230,6 +275,8 @@ static void seek_to(int track) | ||||
|         CyWdtClear(); | ||||
|     } | ||||
|     CyDelay(STEP_SETTLING_TIME); | ||||
|      | ||||
|     TK43_REG_Write(track < 43); /* high if 0..42, low if 43 or up */ | ||||
|     print("finished seek"); | ||||
| } | ||||
|  | ||||
| @@ -248,7 +295,7 @@ static void cmd_recalibrate(void) | ||||
|     send_reply(&r);     | ||||
| } | ||||
|      | ||||
| static void cmd_measure_speed(struct any_frame* f) | ||||
| static void cmd_measure_speed(struct measurespeed_frame* f) | ||||
| { | ||||
|     start_motor(); | ||||
|      | ||||
| @@ -267,10 +314,14 @@ static void cmd_measure_speed(struct any_frame* f) | ||||
|  | ||||
|     if (elapsed != 0) | ||||
|     { | ||||
|         index_irq = false; | ||||
|         int target_pulse_count = f->hard_sector_count + 1; | ||||
|         start_clock = clock; | ||||
|         while (!index_irq) | ||||
|             elapsed = clock - start_clock; | ||||
|         for (int x=0; x<target_pulse_count; x++) | ||||
|         { | ||||
|             index_irq = false; | ||||
|             while (!index_irq) | ||||
|                 elapsed = clock - start_clock; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     DECLARE_REPLY_FRAME(struct speed_frame, F_FRAME_MEASURE_SPEED_REPLY); | ||||
| @@ -278,12 +329,14 @@ static void cmd_measure_speed(struct any_frame* f) | ||||
|     send_reply((struct any_frame*) &r);     | ||||
| } | ||||
|  | ||||
| static void cmd_bulk_test(struct any_frame* f) | ||||
| static void cmd_bulk_write_test(struct any_frame* f) | ||||
| { | ||||
|     uint8_t buffer[64]; | ||||
|      | ||||
|     wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); | ||||
|     for (int x=0; x<64; x++) | ||||
|     { | ||||
|         CyWdtClear(); | ||||
|         for (int y=0; y<256; y++) | ||||
|         { | ||||
|             for (unsigned z=0; z<sizeof(buffer); z++) | ||||
| @@ -292,11 +345,44 @@ static void cmd_bulk_test(struct any_frame* f) | ||||
|             wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); | ||||
|             USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, buffer, sizeof(buffer)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_BULK_TEST_REPLY); | ||||
|     DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_BULK_WRITE_TEST_REPLY); | ||||
|     send_reply(&r); | ||||
| } | ||||
|  | ||||
| static void cmd_bulk_read_test(struct any_frame* f) | ||||
| { | ||||
|     uint8_t buffer[64]; | ||||
|      | ||||
|     bool passed = true; | ||||
|     for (int x=0; x<64; x++) | ||||
|     { | ||||
|         CyWdtClear(); | ||||
|         for (int y=0; y<256; y++) | ||||
|         { | ||||
|             usb_read(FLUXENGINE_DATA_OUT_EP_NUM, buffer); | ||||
|             for (unsigned z=0; z<sizeof(buffer); z++) | ||||
|             { | ||||
|                 if (buffer[z] != (uint8)(x+y+z)) | ||||
|                 { | ||||
|                     print("fail %d+%d+%d == %d, not %d", x, y, z, buffer[z], (uint8)(x+y+z)); | ||||
|                     passed = false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     print("passed=%d", passed); | ||||
|     if (passed) | ||||
|     { | ||||
|         DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_BULK_READ_TEST_REPLY); | ||||
|         send_reply(&r); | ||||
|     } | ||||
|     else | ||||
|         send_error(F_ERROR_INVALID_VALUE); | ||||
| } | ||||
|  | ||||
| static void deinit_dma(void) | ||||
| { | ||||
|     for (int i=0; i<BUFFER_COUNT; i++) | ||||
| @@ -306,7 +392,7 @@ static void deinit_dma(void) | ||||
| static void init_capture_dma(void) | ||||
| { | ||||
|     dma_channel = SAMPLER_DMA_DmaInitialize( | ||||
|         2 /* bytes */, | ||||
|         1 /* bytes */, | ||||
|         true /* request per burst */,  | ||||
|         HI16(CYDEV_PERIPH_BASE), | ||||
|         HI16(CYDEV_SRAM_BASE)); | ||||
| @@ -327,14 +413,15 @@ static void init_capture_dma(void) | ||||
|  | ||||
| static void cmd_read(struct read_frame* f) | ||||
| { | ||||
|     SIDE_REG_Write(f->side); | ||||
|     seek_to(current_track); | ||||
|     SIDE_REG_Write(f->side); | ||||
|     STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */ | ||||
|      | ||||
|     /* Do slow setup *before* we go into the real-time bit. */ | ||||
|      | ||||
|     { | ||||
|         uint8_t i = CyEnterCriticalSection(); | ||||
|         SAMPLER_FIFO_SET_LEVEL_MID; | ||||
|         SAMPLER_FIFO_SET_LEVEL_NORMAL; | ||||
|         SAMPLER_FIFO_CLEAR; | ||||
|         SAMPLER_FIFO_SINGLE_BUFFER_UNSET; | ||||
|         CyExitCriticalSection(i); | ||||
| @@ -347,16 +434,14 @@ static void cmd_read(struct read_frame* f) | ||||
|          | ||||
|     if (f->synced) | ||||
|     { | ||||
|         hardsec_index_threshold = f->hardsec_threshold_ms; | ||||
|         index_irq = false; | ||||
|         while (!index_irq) | ||||
|             ; | ||||
|         index_irq = false; | ||||
|         hardsec_index_threshold = 0; | ||||
|     } | ||||
|      | ||||
|     crunch_state_t cs = {}; | ||||
|     cs.outputptr = usb_buffer; | ||||
|     cs.outputlen = BUFFER_SIZE; | ||||
|      | ||||
|     dma_writing_to_td = 0; | ||||
|     dma_reading_from_td = -1; | ||||
|     dma_underrun = false; | ||||
| @@ -371,75 +456,64 @@ static void cmd_read(struct read_frame* f) | ||||
|     while (dma_writing_to_td == 0) | ||||
|         ; | ||||
|     dma_reading_from_td = 0; | ||||
|     bool dma_running = true; | ||||
|      | ||||
|     /* Start transferring. */ | ||||
|  | ||||
|     uint32_t start_time = clock; | ||||
|     while (!dma_underrun) | ||||
|     for (;;) | ||||
|     { | ||||
|         CyWdtClear(); | ||||
|  | ||||
|         /* Wait for the next block to be read. */ | ||||
|         while (dma_reading_from_td == dma_writing_to_td) | ||||
|         /* If the sample session is over, stop reading but continue processing until | ||||
|          * the DMA chain is empty. */ | ||||
|          | ||||
|         if ((clock - start_time) >= f->milliseconds) | ||||
|         { | ||||
|             /* On an underrun, give up immediately. */ | ||||
|             if (dma_underrun) | ||||
|                 goto abort; | ||||
|              | ||||
|             /* Also finish if the sample session is over. */ | ||||
|             if ((clock - start_time) >= f->milliseconds) | ||||
|                 goto abort; | ||||
|         } | ||||
|  | ||||
|         uint8_t dma_buffer_usage = 0; | ||||
|         while (dma_buffer_usage < BUFFER_SIZE) | ||||
|         { | ||||
|             cs.inputptr = dma_buffer[dma_reading_from_td] + dma_buffer_usage; | ||||
|             cs.inputlen = BUFFER_SIZE - dma_buffer_usage; | ||||
|             crunch(&cs); | ||||
|             dma_buffer_usage += BUFFER_SIZE - cs.inputlen; | ||||
|             count++; | ||||
|              | ||||
|             /* If there is no available space in the output buffer, flush the buffer via | ||||
|              * USB and go again. */ | ||||
|             if (cs.outputlen == 0) | ||||
|             if (dma_running) | ||||
|             { | ||||
|                 wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); | ||||
|                 USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, usb_buffer, BUFFER_SIZE); | ||||
|                  | ||||
|                 cs.outputptr = usb_buffer; | ||||
|                 cs.outputlen = BUFFER_SIZE; | ||||
|                 CyDmaChSetRequest(dma_channel, CY_DMA_CPU_TERM_CHAIN); | ||||
|                 while (CyDmaChGetRequest(dma_channel)) | ||||
|                     ; | ||||
|                 dma_running = false; | ||||
|                 dma_underrun = false; | ||||
|             } | ||||
|         } | ||||
|         dma_reading_from_td = NEXT_BUFFER(dma_reading_from_td); | ||||
|          | ||||
|         /* If there's an underrun event, stop immediately. */ | ||||
|          | ||||
|         if (dma_underrun) | ||||
|             goto abort; | ||||
|          | ||||
|         /* If there are no more blocks to be read, check to see if we've finished. */ | ||||
|          | ||||
|         if (dma_reading_from_td == dma_writing_to_td) | ||||
|         { | ||||
|             /* Also if we've run out of blocks to send. */ | ||||
|              | ||||
|             if (!dma_running) | ||||
|                 goto abort; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             /* Otherwise, there's a block waiting, so attempt to send it. */ | ||||
|              | ||||
|             wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); | ||||
|             USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, dma_buffer[dma_reading_from_td], BUFFER_SIZE); | ||||
|             count++; | ||||
|             dma_reading_from_td = NEXT_BUFFER(dma_reading_from_td); | ||||
|         } | ||||
|     } | ||||
| abort:; | ||||
|     bool saved_dma_underrun = dma_underrun; | ||||
|     CyDmaChSetRequest(dma_channel, CY_DMA_CPU_TERM_CHAIN); | ||||
|     while (CyDmaChGetRequest(dma_channel)) | ||||
|         ; | ||||
|  | ||||
|     donecrunch(&cs); | ||||
|     /* Terminate the transfer (all transfers are an exact number of fragments). */ | ||||
|     wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); | ||||
|     /* If there's a complete packet waiting, send it. */ | ||||
|     if (cs.outputlen != BUFFER_SIZE) | ||||
|     { | ||||
|         USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, usb_buffer, BUFFER_SIZE); | ||||
|         wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); | ||||
|     } | ||||
|     if ((cs.outputlen != 0) && (cs.outputlen != BUFFER_SIZE)) | ||||
|     { | ||||
|         /* If there's a partial packet waiting, send it; this will also terminate the transfer. */ | ||||
|         USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, usb_buffer, BUFFER_SIZE-cs.outputlen); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         /* Otherwise just terminate the transfer. */ | ||||
|         USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, NULL, 0); | ||||
|     } | ||||
|     USBFS_LoadInEP(FLUXENGINE_DATA_IN_EP_NUM, NULL, 0); | ||||
|     wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM); | ||||
|     deinit_dma(); | ||||
|  | ||||
|     STEP_REG_Write(0); | ||||
|     if (saved_dma_underrun) | ||||
|     { | ||||
|         print("underrun after %d packets"); | ||||
| @@ -485,36 +559,33 @@ static void cmd_write(struct write_frame* f) | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     SEQUENCER_CONTROL_Write(1); /* put the sequencer into reset */ | ||||
|  | ||||
|     seek_to(current_track);     | ||||
|     SIDE_REG_Write(f->side); | ||||
|     STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */ | ||||
|      | ||||
|     SEQUENCER_CONTROL_Write(1); /* put the sequencer into reset */ | ||||
|     { | ||||
|         uint8_t i = CyEnterCriticalSection();         | ||||
|         REPLAY_FIFO_SET_LEVEL_NORMAL; | ||||
|         REPLAY_FIFO_SET_LEVEL_MID; | ||||
|         REPLAY_FIFO_CLEAR; | ||||
|         REPLAY_FIFO_SINGLE_BUFFER_UNSET; | ||||
|         CyExitCriticalSection(i); | ||||
|     } | ||||
|     seek_to(current_track);     | ||||
|  | ||||
|     init_replay_dma(); | ||||
|     bool writing = false; /* to the disk */ | ||||
|     bool finished = false; | ||||
|     int packets = f->bytes_to_write / FRAME_SIZE; | ||||
|     bool finished = (packets == 0); | ||||
|     int count_written = 0; | ||||
|     int count_read = 0; | ||||
|     dma_writing_to_td = 0; | ||||
|     dma_reading_from_td = -1; | ||||
|     dma_underrun = false; | ||||
|  | ||||
|     crunch_state_t cs = {}; | ||||
|     cs.outputlen = BUFFER_SIZE; | ||||
|     USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM); | ||||
|      | ||||
|     int old_reading_from_td = -1; | ||||
|     for (;;) | ||||
|     { | ||||
|         CyWdtClear(); | ||||
|         //CyWdtClear(); | ||||
|  | ||||
|         /* Read data from USB into the buffers. */ | ||||
|          | ||||
| @@ -523,54 +594,25 @@ static void cmd_write(struct write_frame* f) | ||||
|             if (writing && (dma_underrun || index_irq)) | ||||
|                 goto abort; | ||||
|              | ||||
|             /* Read crunched data, if necessary. */ | ||||
|              | ||||
|             if (cs.inputlen == 0) | ||||
|             uint8_t* buffer = dma_buffer[dma_writing_to_td]; | ||||
|             if (finished) | ||||
|             { | ||||
|                 if (finished) | ||||
|                 { | ||||
|                     /* There's no more data to read, so fake some. */ | ||||
|                      | ||||
|                     for (int i=0; i<BUFFER_SIZE; i++) | ||||
|                         usb_buffer[i+0] = 0x7f; | ||||
|                     cs.inputptr = usb_buffer; | ||||
|                     cs.inputlen = BUFFER_SIZE; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     while (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) != USBFS_OUT_BUFFER_FULL) | ||||
|                     { | ||||
|                         if (writing && (dma_underrun || index_irq)) | ||||
|                             goto abort; | ||||
|                     } | ||||
|  | ||||
|                     int length = usb_read(FLUXENGINE_DATA_OUT_EP_NUM, usb_buffer); | ||||
|                     cs.inputptr = usb_buffer; | ||||
|                     cs.inputlen = length; | ||||
|                     USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM); | ||||
|  | ||||
|                     count_read++; | ||||
|                     if ((length < FRAME_SIZE) || (count_read == packets)) | ||||
|                         finished = true; | ||||
|                 } | ||||
|                 /* There's no more data to read, so fake some. */ | ||||
|                  | ||||
|                 memset(buffer, 0x3f, BUFFER_SIZE); | ||||
|             } | ||||
|              | ||||
|             /* If there *is* data waiting in the buffer, uncrunch it. */ | ||||
|              | ||||
|             if (cs.inputlen != 0) | ||||
|             else | ||||
|             { | ||||
|                 cs.outputptr = dma_buffer[dma_writing_to_td] + BUFFER_SIZE - cs.outputlen; | ||||
|                 uncrunch(&cs); | ||||
|                 if (cs.outputlen == 0) | ||||
|                 { | ||||
|                     /* Completed a DMA buffer; queue it for writing. */ | ||||
|                      | ||||
|                     dma_writing_to_td = NEXT_BUFFER(dma_writing_to_td); | ||||
|                     cs.outputlen = BUFFER_SIZE; | ||||
|                 } | ||||
|                 (void) usb_read(FLUXENGINE_DATA_OUT_EP_NUM, buffer); | ||||
|                 count_read++; | ||||
|                  | ||||
|                 if (count_read == packets) | ||||
|                     finished = true; | ||||
|             } | ||||
|             dma_writing_to_td = NEXT_BUFFER(dma_writing_to_td); | ||||
|              | ||||
|             /* Once all the buffers are full, start writing. */ | ||||
|              | ||||
|             /* If we have a full buffer, start writing. */ | ||||
|             if ((dma_reading_from_td == -1) && (dma_writing_to_td == BUFFER_COUNT-1)) | ||||
|             { | ||||
|                 dma_reading_from_td = old_reading_from_td = 0; | ||||
| @@ -585,7 +627,8 @@ static void cmd_write(struct write_frame* f) | ||||
|  | ||||
|                 /* Wait for the index marker. While this happens, the DMA engine | ||||
|                  * will prime the FIFO. */ | ||||
|                  | ||||
|  | ||||
|                 hardsec_index_threshold = f->hardsec_threshold_ms; | ||||
|                 index_irq = false; | ||||
|                 while (!index_irq) | ||||
|                     ; | ||||
| @@ -596,7 +639,7 @@ static void cmd_write(struct write_frame* f) | ||||
|                 SEQUENCER_CONTROL_Write(0); /* start writing! */ | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         if (writing && (dma_underrun || index_irq)) | ||||
|             goto abort; | ||||
|  | ||||
| @@ -620,31 +663,29 @@ abort: | ||||
|     } | ||||
|      | ||||
|     print("p=%d cr=%d cw=%d f=%d w=%d index=%d underrun=%d", packets, count_read, count_written, finished, writing, index_irq, dma_underrun); | ||||
|     hardsec_index_threshold = 0; | ||||
|     if (!finished) | ||||
|     { | ||||
|         while (count_read < packets) | ||||
|         /* There's still some data to read, so just read and blackhole it --- | ||||
|          * easier than trying to terminate the connection. */ | ||||
|         while (count_read != packets) | ||||
|         { | ||||
|             if (USBFS_GetEPState(FLUXENGINE_DATA_OUT_EP_NUM) == USBFS_OUT_BUFFER_FULL) | ||||
|             { | ||||
|                 int length = usb_read(FLUXENGINE_DATA_OUT_EP_NUM, usb_buffer); | ||||
|                 if (length < FRAME_SIZE) | ||||
|                     break; | ||||
|                 USBFS_EnableOutEP(FLUXENGINE_DATA_OUT_EP_NUM); | ||||
|                 count_read++; | ||||
|             } | ||||
|             (void) usb_read(FLUXENGINE_DATA_OUT_EP_NUM, usb_buffer); | ||||
|             count_read++; | ||||
|         } | ||||
|         USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM); | ||||
|     } | ||||
|      | ||||
|     deinit_dma(); | ||||
|     print("write finished"); | ||||
|      | ||||
|     STEP_REG_Write(0); | ||||
|     if (dma_underrun) | ||||
|     { | ||||
|         print("underrun!"); | ||||
|         send_error(F_ERROR_UNDERRUN); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     print("success"); | ||||
|     DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_WRITE_REPLY); | ||||
|     send_reply((struct any_frame*) &r); | ||||
| } | ||||
| @@ -656,6 +697,7 @@ static void cmd_erase(struct erase_frame* f) | ||||
|     /* Disk is now spinning. */ | ||||
|      | ||||
|     print("start erasing"); | ||||
|     hardsec_index_threshold = f->hardsec_threshold_ms; | ||||
|     index_irq = false; | ||||
|     while (!index_irq) | ||||
|         ; | ||||
| @@ -664,6 +706,7 @@ static void cmd_erase(struct erase_frame* f) | ||||
|     while (!index_irq) | ||||
|         ; | ||||
|     ERASE_REG_Write(0); | ||||
|     hardsec_index_threshold = 0; | ||||
|     print("stop erasing"); | ||||
|  | ||||
|     DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_ERASE_REPLY); | ||||
| @@ -672,6 +715,10 @@ static void cmd_erase(struct erase_frame* f) | ||||
|  | ||||
| static void cmd_set_drive(struct set_drive_frame* f) | ||||
| { | ||||
|     if (drive0_present && !drive1_present) | ||||
|         f->drive = 0; | ||||
|     if (drive1_present && !drive0_present) | ||||
|         f->drive = 1; | ||||
|     set_drive_flags(f); | ||||
|      | ||||
|     DECLARE_REPLY_FRAME(struct any_frame, F_FRAME_SET_DRIVE_REPLY); | ||||
| @@ -785,11 +832,15 @@ static void handle_command(void) | ||||
|             break; | ||||
|          | ||||
|         case F_FRAME_MEASURE_SPEED_CMD: | ||||
|             cmd_measure_speed(f); | ||||
|             cmd_measure_speed((struct measurespeed_frame*) f); | ||||
|             break; | ||||
|              | ||||
|         case F_FRAME_BULK_TEST_CMD: | ||||
|             cmd_bulk_test(f); | ||||
|         case F_FRAME_BULK_WRITE_TEST_CMD: | ||||
|             cmd_bulk_write_test(f); | ||||
|             break; | ||||
|              | ||||
|         case F_FRAME_BULK_READ_TEST_CMD: | ||||
|             cmd_bulk_read_test(f); | ||||
|             break; | ||||
|              | ||||
|         case F_FRAME_READ_CMD: | ||||
| @@ -821,6 +872,21 @@ static void handle_command(void) | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void detect_drives(void) | ||||
| { | ||||
|     current_drive_flags.drive = 0; | ||||
|     start_motor(); | ||||
|     drive0_present = home(); | ||||
|     stop_motor(); | ||||
|      | ||||
|     current_drive_flags.drive = 1; | ||||
|     start_motor(); | ||||
|     drive1_present = home(); | ||||
|     stop_motor(); | ||||
|      | ||||
|     print("drive 0: %s drive 1: %s", drive0_present ? "yes" : "no", drive1_present ? "yes" : "no"); | ||||
| } | ||||
|  | ||||
| int main(void) | ||||
| { | ||||
|     CyGlobalIntEnable; | ||||
| @@ -834,11 +900,11 @@ int main(void) | ||||
|     DRIVESELECT_REG_Write(0); | ||||
|     UART_Start(); | ||||
|     USBFS_Start(0, USBFS_DWR_VDDD_OPERATION); | ||||
|     USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM); | ||||
|      | ||||
|     detect_drives(); | ||||
|     CyWdtStart(CYWDT_1024_TICKS, CYWDT_LPMODE_DISABLED); | ||||
|      | ||||
|     /* UART_PutString("GO\r"); */ | ||||
|  | ||||
|     for (;;) | ||||
|     { | ||||
|         CyWdtClear(); | ||||
| @@ -854,7 +920,7 @@ int main(void) | ||||
|         { | ||||
|             print("Waiting for USB..."); | ||||
|             while (!USBFS_GetConfiguration()) | ||||
|                 ; | ||||
|                 CyWdtClear(); | ||||
|             print("USB ready"); | ||||
|             USBFS_EnableOutEP(FLUXENGINE_CMD_OUT_EP_NUM); | ||||
|         } | ||||
|   | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -39,7 +39,7 @@ CFLAGS += -Ilib -Idep/fmt -Iarch | ||||
| export OBJDIR = .obj | ||||
|  | ||||
| all: .obj/build.ninja | ||||
| 	@ninja -f .obj/build.ninja -v | ||||
| 	@ninja -f .obj/build.ninja | ||||
|  | ||||
| clean: | ||||
| 	@echo CLEAN | ||||
|   | ||||
							
								
								
									
										48
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								README.md
									
									
									
									
									
								
							| @@ -24,17 +24,18 @@ Don't believe me? Watch the demo reel! | ||||
| <iframe width="373" height="210" src="https://www.youtube.com/embed/m_s1iw8eW7o" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> | ||||
| </div> | ||||
|  | ||||
| **Important note.** On 2019-02-09 I did a hardware redesign and moved the pins on | ||||
| the board. Sorry for the inconvenience, but it means you don't have to modify | ||||
| the board any more to make it work. If you built the hardware prior to then, | ||||
| you'll need to adjust it. | ||||
| **New!** The FluxEngine client software now works with | ||||
| [GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you | ||||
| can't find a PSoC5 development kit, or don't want to use the Cypress Windows | ||||
| tools for programming it, you can use one of these instead. Very nearly all | ||||
| FluxEngine features are available with the GreaseWeazle and it works out-of-the | ||||
| box. See the [dedicated GreaseWeazle documentation page](doc/greaseweazle.md) | ||||
| for more information. | ||||
|  | ||||
| **Another important note.** On 2019-07-03 I've revamped the build process and | ||||
| the (command line) user interface. It should be much nicer now, not least in | ||||
| that there's a single client binary with all the functionality in it. The | ||||
| interface is a little different, but not much. The build process is now | ||||
| better (simpler). See [the building](doc/building.md) and | ||||
| [using](doc/using.md) pages for more details. | ||||
| **Important note.** On 2020-04-02 I changed the bytecode format (and firmware). | ||||
| Flux files will need to be upgraded with `fluxengine upgradefluxfile`. The new | ||||
| format should be more reliable and use way, way less bandwidth. Sorry for the | ||||
| inconvenience. | ||||
|  | ||||
| Where? | ||||
| ------ | ||||
| @@ -59,6 +60,11 @@ following friendly articles: | ||||
|   - [Using a FluxEngine](doc/using.md) ∾ what to do with your new hardware ∾ | ||||
|     flux files and image files ∾ knowing what you're doing | ||||
|  | ||||
|   - [Using GreaseWeazle hardware with the FluxEngine client | ||||
| 	software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to | ||||
| 	go for help | ||||
|  | ||||
|  | ||||
|   - [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact science ∾ | ||||
|     the sector map ∾ clock detection and the histogram | ||||
|  | ||||
| @@ -79,20 +85,24 @@ people who've had it work). | ||||
|  | ||||
| | Format                                   | Read? | Write? | Notes | | ||||
| |:-----------------------------------------|:-----:|:------:|-------| | ||||
| | IBM PC compatible                        |  🦄   |        | and compatibles (like the Atari ST) | | ||||
| | [Acorn ADFS](doc/disk-acornadfs.md)      |  🦄   |        | single- and double- sided           | | ||||
| | [Acorn DFS](doc/disk-acorndfs.md)        |  🦄   |        |                                     | | ||||
| | [Ampro Little Board](doc/disk-ampro.md)  |  🦖   |        |                                     | | ||||
| | [IBM PC compatible](doc/disk-ibm.md)     |  🦄   |   🦄   | and compatibles (like the Atari ST) | | ||||
| | [Acorn ADFS](doc/disk-acornadfs.md)      |  🦄   |   🦖*  | single- and double- sided           | | ||||
| | [Acorn DFS](doc/disk-acorndfs.md)        |  🦄   |   🦖*  |                                     | | ||||
| | [Ampro Little Board](doc/disk-ampro.md)  |  🦖   |   🦖*   |                                     | | ||||
| | [Apple II DOS 3.3](doc/disk-apple2.md)   |  🦄   |        | doesn't do logical sector remapping | | ||||
| | [Amiga](doc/disk-amiga.md)               |  🦄   |        |                                     | | ||||
| | [Commodore 64 1541](doc/disk-c64.md)     |  🦖   |        | and probably the other GCR formats  | | ||||
| | [Brother 120kB](doc/disk-brother.md)     |  🦄   |        |                                     | | ||||
| | [Brother 240kB](doc/disk-brother.md)     |  🦄   |   🦄   |                                     | | ||||
| | [Brother FB-100](doc/disk-fb100.md)      |  🦖   |        | Tandy Model 100, Husky Hunter, knitting machines | | ||||
| | [Macintosh 800kB](doc/disk-macintosh.md) |  🦖   |        | and probably the 400kB too          | | ||||
| | [TRS-80](doc/disk-trs80.md)              |  🦖   |        | a minor variation of the IBM scheme | | ||||
| | [Macintosh 800kB](doc/disk-macintosh.md) |  🦄   |   🦄   | and probably the 400kB too          | | ||||
| | [TRS-80](doc/disk-trs80.md)              |  🦖   |   🦖*  | a minor variation of the IBM scheme | | ||||
| {: .datatable } | ||||
|  | ||||
| `*`: these formats are variations of the generic IBM format, and since the | ||||
| IBM writer is completely generic, it should be configurable for these | ||||
| formats... theoretically. I don't have the hardware to try it. | ||||
|  | ||||
| ### Even older disk formats | ||||
|  | ||||
| These formats are for particularly old, weird architectures, even by the | ||||
| @@ -106,8 +116,10 @@ at least, check the CRC so what data's there is probably good. | ||||
| | [AES Superplus / No Problem](doc/disk-aeslanier.md) |  🦖   | | hard sectors! | | ||||
| | [Durango F85](doc/disk-durangof85.md)    |  🦖   |        | 5.25" | | ||||
| | [DVK MX](doc/disk-mx.md)                 |  🦖   |        | Soviet PDP-11 clone | | ||||
| | [Victor 9000](doc/disk-victor9k.md)      |  🦖   |        | 8-inch        | | ||||
| | [Zilog MCZ](doc/disk-zilogmcz.md)        |  🦖   |        | 8-inch _and_ hard sectors | | ||||
| | [Micropolis](doc/disk-micropolis.md)     |  🦄   |        | Micropolis 100tpi drives | | ||||
| | [TI DS990 FD1000](doc/disk-tids990.md)   |  🦄   |  🦄    | 8" | | ||||
| | [Victor 9000](doc/disk-victor9k.md)      |  🦖   |        | 8" | | ||||
| | [Zilog MCZ](doc/disk-zilogmcz.md)        |  🦖   |        | 8" _and_ hard sectors | | ||||
| {: .datatable } | ||||
|  | ||||
| ### Notes | ||||
|   | ||||
| @@ -20,6 +20,8 @@ public: | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|  | ||||
| 	std::set<unsigned> requiredSectors(Track& track) const; | ||||
| }; | ||||
|  | ||||
| class AmigaEncoder : public AbstractEncoder | ||||
|   | ||||
| @@ -32,6 +32,8 @@ AbstractDecoder::RecordType AmigaDecoder::advanceToNextRecord() | ||||
| void AmigaDecoder::decodeSectorRecord() | ||||
| { | ||||
|     const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16); | ||||
| 	if (rawbits.size() < (AMIGA_RECORD_SIZE*16)) | ||||
| 		return; | ||||
|     const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2); | ||||
|     const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE); | ||||
|  | ||||
| @@ -56,3 +58,10 @@ void AmigaDecoder::decodeSectorRecord() | ||||
|     _sector->data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo); | ||||
|     _sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|  | ||||
| std::set<unsigned> AmigaDecoder::requiredSectors(Track& track) const | ||||
| { | ||||
| 	static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; | ||||
| 	return sectors; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,8 @@ static DoubleFlag postIndexGapMs( | ||||
| 	"Post-index gap before first sector header (milliseconds).", | ||||
| 	0.5); | ||||
|  | ||||
| static bool lastBit; | ||||
|  | ||||
| static int charToInt(char c) | ||||
| { | ||||
| 	if (isdigit(c)) | ||||
| @@ -51,7 +53,7 @@ static void write_interleaved_bytes(std::vector<bool>& bits, unsigned& cursor, c | ||||
| { | ||||
| 	assert(!(bytes.size() & 3)); | ||||
| 	Bytes interleaved = amigaInterleave(bytes); | ||||
| 	encodeMfm(bits, cursor, interleaved); | ||||
| 	encodeMfm(bits, cursor, interleaved, lastBit); | ||||
| } | ||||
|  | ||||
| static void write_interleaved_bytes(std::vector<bool>& bits, unsigned& cursor, uint32_t data) | ||||
| @@ -108,6 +110,7 @@ std::unique_ptr<Fluxmap> AmigaEncoder::encode( | ||||
| 	unsigned cursor = 0; | ||||
|  | ||||
|     fillBitmapTo(bits, cursor, postIndexGapMs * 1000 / clockRateUs, { true, false }); | ||||
| 	lastBit = false; | ||||
|  | ||||
| 	for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++) | ||||
| 	{ | ||||
|   | ||||
| @@ -9,7 +9,8 @@ | ||||
| #define BROTHER_DATA_RECORD_CHECKSUM     3 | ||||
| #define BROTHER_DATA_RECORD_ENCODED_SIZE 415 | ||||
|  | ||||
| #define BROTHER_TRACKS_PER_DISK          78 | ||||
| #define BROTHER_TRACKS_PER_240KB_DISK    78 | ||||
| #define BROTHER_TRACKS_PER_120KB_DISK    39 | ||||
| #define BROTHER_SECTORS_PER_TRACK        12 | ||||
|  | ||||
| class Sector; | ||||
| @@ -28,8 +29,16 @@ public: | ||||
| class BrotherEncoder : public AbstractEncoder | ||||
| { | ||||
| public: | ||||
| 	BrotherEncoder(int format, int bias): | ||||
| 		_format(format), | ||||
| 		_bias(bias) | ||||
| 	{} | ||||
|  | ||||
| 	virtual ~BrotherEncoder() {} | ||||
|  | ||||
| private: | ||||
| 	int _format; | ||||
| 	int _bias; | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors); | ||||
| }; | ||||
|   | ||||
| @@ -129,9 +129,25 @@ static int charToInt(char c) | ||||
| std::unique_ptr<Fluxmap> BrotherEncoder::encode( | ||||
| 	int physicalTrack, int physicalSide, const SectorSet& allSectors) | ||||
| { | ||||
| 	if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_DISK) | ||||
|         || (physicalSide != 0)) | ||||
| 	int logicalTrack; | ||||
| 	if (physicalSide != 0) | ||||
| 		return std::unique_ptr<Fluxmap>(); | ||||
| 	physicalTrack -= _bias; | ||||
| 	switch (_format) | ||||
| 	{ | ||||
| 		case 120: | ||||
| 			if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2)) | ||||
| 					|| (physicalTrack & 1)) | ||||
| 				return std::unique_ptr<Fluxmap>(); | ||||
| 			logicalTrack = physicalTrack/2; | ||||
| 			break; | ||||
|  | ||||
| 		case 240: | ||||
| 			if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK)) | ||||
| 				return std::unique_ptr<Fluxmap>(); | ||||
| 			logicalTrack = physicalTrack; | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	int bitsPerRevolution = 200000.0 / clockRateUs; | ||||
| 	const std::string& skew = sectorSkew.get(); | ||||
| @@ -146,10 +162,10 @@ std::unique_ptr<Fluxmap> BrotherEncoder::encode( | ||||
| 		double dataMs = headerMs + postHeaderSpacingMs; | ||||
| 		unsigned dataCursor = dataMs*1e3 / clockRateUs; | ||||
|  | ||||
| 		const auto& sectorData = allSectors.get(physicalTrack, 0, sectorId); | ||||
| 		const auto& sectorData = allSectors.get(logicalTrack, 0, sectorId); | ||||
|  | ||||
| 		fillBitmapTo(bits, cursor, headerCursor, { true, false }); | ||||
| 		write_sector_header(bits, cursor, physicalTrack, sectorId); | ||||
| 		write_sector_header(bits, cursor, logicalTrack, sectorId); | ||||
| 		fillBitmapTo(bits, cursor, dataCursor, { true, false }); | ||||
| 		write_sector_data(bits, cursor, sectorData->data); | ||||
| 	} | ||||
|   | ||||
| @@ -57,11 +57,11 @@ const FluxPattern FM_TRS80DAM1_PATTERN(16, 0xf56b); | ||||
|  | ||||
| /*  | ||||
|  * TRS80DAM2 record: | ||||
|  * flux:   XXXX-X-X-XX-XXX- = 0xf56c | ||||
|  * flux:   XXXX-X-X-XX-XXX- = 0xf56e | ||||
|  * clock:  X X - - - X X X  = 0xc7 | ||||
|  * data:    X X X X X - X - = 0xfa | ||||
|  */ | ||||
| const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56c); | ||||
| const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56e); | ||||
|  | ||||
| /* MFM record separator: | ||||
|  * 0xA1 is: | ||||
|   | ||||
							
								
								
									
										236
									
								
								arch/ibm/encoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								arch/ibm/encoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| #include "globals.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "ibm.h" | ||||
| #include "crc.h" | ||||
| #include "sectorset.h" | ||||
| #include "writer.h" | ||||
| #include "fmt/format.h" | ||||
| #include <ctype.h> | ||||
|  | ||||
| /* IAM record separator: | ||||
|  * 0xC2 is: | ||||
|  * data:    1  1  0  0  0  0  1  0  = 0xc2 | ||||
|  * mfm:     01 01 00 10 10 10 01 00 = 0x5254 | ||||
|  * special: 01 01 00 10 00 10 01 00 = 0x5224 | ||||
|  */ | ||||
| #define MFM_IAM_SEPARATOR 0x5224 | ||||
|  | ||||
| /* FM IAM record: | ||||
|  * flux:   XXXX-XXX-XXXX-X- = 0xf77a | ||||
|  * clock:  X X - X - X X X  = 0xd7 | ||||
|  * data:    X X X X X X - - = 0xfc | ||||
|  */ | ||||
| #define FM_IAM_RECORD 0xf77a | ||||
|  | ||||
| /* MFM IAM record: | ||||
|  * data:   1  1  1  1  1  1  0  0  = 0xfc | ||||
|  * flux:   01 01 01 01 01 01 00 10 = 0x5552 | ||||
|  */ | ||||
| #define MFM_IAM_RECORD 0x5552 | ||||
|  | ||||
| /* MFM record separator: | ||||
|  * 0xA1 is: | ||||
|  * data:    1  0  1  0  0  0  0  1  = 0xa1 | ||||
|  * mfm:     01 00 01 00 10 10 10 01 = 0x44a9 | ||||
|  * special: 01 00 01 00 10 00 10 01 = 0x4489 | ||||
|  *                       ^^^^^ | ||||
|  * When shifted out of phase, the special 0xa1 byte becomes an illegal | ||||
|  * encoding (you can't do 10 00). So this can't be spoofed by user data. | ||||
|  *  | ||||
|  * shifted: 10 00 10 01 00 01 00 1 | ||||
|  *  | ||||
|  * It's repeated three times. | ||||
|  */ | ||||
| #define MFM_RECORD_SEPARATOR 0x4489 | ||||
| #define MFM_RECORD_SEPARATOR_BYTE 0xa1 | ||||
|  | ||||
| /* MFM IDAM byte: | ||||
|  * data:    1  1  1  1  1  1  1  0  = 0xfe | ||||
|  * mfm:     01 01 01 01 01 01 01 00 = 0x5554 | ||||
|  */ | ||||
|  | ||||
| /* MFM DAM byte: | ||||
|  * data:    1  1  1  1  1  0  1  1  = 0xfb | ||||
|  * mfm:     01 01 01 01 01 00 01 01 = 0x5545 | ||||
|  */ | ||||
|  | ||||
| static int charToInt(char c) | ||||
| { | ||||
| 	if (isdigit(c)) | ||||
| 		return c - '0'; | ||||
| 	return 10 + tolower(c) - 'a'; | ||||
| } | ||||
|  | ||||
| void IbmEncoder::writeRawBits(uint32_t data, int width) | ||||
| { | ||||
| 	_cursor += width; | ||||
| 	_lastBit = data & 1; | ||||
| 	for (int i=0; i<width; i++) | ||||
| 	{ | ||||
| 		unsigned pos = _cursor - i - 1; | ||||
| 		if (pos < _bits.size()) | ||||
| 			_bits[pos] = data & 1; | ||||
| 		data >>= 1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void IbmEncoder::writeBytes(const Bytes& bytes) | ||||
| { | ||||
| 	if (_parameters.useFm) | ||||
| 		encodeFm(_bits, _cursor, bytes); | ||||
| 	else | ||||
| 		encodeMfm(_bits, _cursor, bytes, _lastBit); | ||||
| } | ||||
|  | ||||
| void IbmEncoder::writeBytes(int count, uint8_t byte) | ||||
| { | ||||
| 	Bytes bytes = { byte }; | ||||
| 	for (int i=0; i<count; i++) | ||||
| 		writeBytes(bytes); | ||||
| } | ||||
|  | ||||
| static uint8_t decodeUint16(uint16_t raw) | ||||
| { | ||||
| 	Bytes b; | ||||
| 	ByteWriter bw(b); | ||||
| 	bw.write_be16(raw); | ||||
| 	return decodeFmMfm(b.toBits())[0]; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Fluxmap> IbmEncoder::encode( | ||||
| 	int physicalTrack, int physicalSide, const SectorSet& allSectors) | ||||
| { | ||||
| 	double clockRateUs = 1e3 / _parameters.clockRateKhz; | ||||
| 	if (!_parameters.useFm) | ||||
| 		clockRateUs /= 2.0; | ||||
| 	int bitsPerRevolution = (_parameters.trackLengthMs * 1000.0) / clockRateUs; | ||||
| 	_bits.resize(bitsPerRevolution); | ||||
| 	_cursor = 0; | ||||
|  | ||||
| 	uint8_t idamUnencoded = decodeUint16(_parameters.idamByte); | ||||
| 	uint8_t damUnencoded = decodeUint16(_parameters.damByte); | ||||
|  | ||||
| 	uint8_t sectorSize = 0; | ||||
| 	{ | ||||
| 		int s = _parameters.sectorSize >> 7; | ||||
| 		while (s > 1) | ||||
| 		{ | ||||
| 			s >>= 1; | ||||
| 			sectorSize += 1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	uint8_t gapFill = _parameters.useFm ? 0x00 : 0x4e; | ||||
|  | ||||
| 	writeBytes(_parameters.gap0, gapFill); | ||||
| 	if (_parameters.emitIam) | ||||
| 	{ | ||||
| 		writeBytes(_parameters.useFm ? 6 : 12, 0x00); | ||||
| 		if (!_parameters.useFm) | ||||
| 		{ | ||||
| 			for (int i=0; i<3; i++) | ||||
| 				writeRawBits(MFM_IAM_SEPARATOR, 16); | ||||
| 		} | ||||
| 		writeRawBits(_parameters.useFm ? FM_IAM_RECORD : MFM_IAM_RECORD, 16); | ||||
| 		writeBytes(_parameters.gap1, gapFill); | ||||
| 	} | ||||
|  | ||||
| 	bool first = true; | ||||
| 	for (char sectorChar : _parameters.sectorSkew) | ||||
| 	{ | ||||
| 		int sectorId = charToInt(sectorChar); | ||||
| 		if (!first) | ||||
| 			writeBytes(_parameters.gap3, gapFill); | ||||
| 		first = false; | ||||
|  | ||||
| 		const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId); | ||||
| 		if (!sectorData) | ||||
| 			Error() << fmt::format("format tried to find sector {} which wasn't in the input file", sectorId); | ||||
|  | ||||
| 		/* Writing the sector and data records are fantastically annoying. | ||||
| 		 * The CRC is calculated from the *very start* of the record, and | ||||
| 		 * include the malformed marker bytes. Our encoder doesn't know | ||||
| 		 * about this, of course, with the result that we have to construct | ||||
| 		 * the unencoded header, calculate the checksum, and then use the | ||||
| 		 * same logic to emit the bytes which require special encoding | ||||
| 		 * before encoding the rest of the header normally. */ | ||||
|  | ||||
| 		{ | ||||
| 			Bytes header; | ||||
| 			ByteWriter bw(header); | ||||
|  | ||||
| 			writeBytes(_parameters.useFm ? 6 : 12, 0x00); | ||||
| 			if (!_parameters.useFm) | ||||
| 			{ | ||||
| 				for (int i=0; i<3; i++) | ||||
| 					bw.write_8(MFM_RECORD_SEPARATOR_BYTE); | ||||
| 			} | ||||
| 			bw.write_8(idamUnencoded); | ||||
| 			bw.write_8(sectorData->logicalTrack); | ||||
| 			bw.write_8(sectorData->logicalSide); | ||||
| 			bw.write_8(sectorData->logicalSector + _parameters.startSectorId); | ||||
| 			bw.write_8(sectorSize); | ||||
| 			uint16_t crc = crc16(CCITT_POLY, header); | ||||
| 			bw.write_be16(crc); | ||||
|  | ||||
| 			int conventionalHeaderStart = 0; | ||||
| 			if (!_parameters.useFm) | ||||
| 			{ | ||||
| 				for (int i=0; i<3; i++) | ||||
| 					writeRawBits(MFM_RECORD_SEPARATOR, 16); | ||||
| 				conventionalHeaderStart += 3; | ||||
|  | ||||
| 			} | ||||
| 			writeRawBits(_parameters.idamByte, 16); | ||||
| 			conventionalHeaderStart += 1; | ||||
|  | ||||
| 			writeBytes(header.slice(conventionalHeaderStart)); | ||||
| 		} | ||||
|  | ||||
| 		writeBytes(_parameters.gap2, gapFill); | ||||
|  | ||||
| 		{ | ||||
| 			Bytes data; | ||||
| 			ByteWriter bw(data); | ||||
|  | ||||
| 			writeBytes(_parameters.useFm ? 6 : 12, 0x00); | ||||
| 			if (!_parameters.useFm) | ||||
| 			{ | ||||
| 				for (int i=0; i<3; i++) | ||||
| 					bw.write_8(MFM_RECORD_SEPARATOR_BYTE); | ||||
| 			} | ||||
| 			bw.write_8(damUnencoded); | ||||
|  | ||||
| 			Bytes truncatedData = sectorData->data.slice(0, _parameters.sectorSize); | ||||
| 			bw += truncatedData; | ||||
| 			hexdump(std::cout, data.slice(0, 64)); | ||||
| 			uint16_t crc = crc16(CCITT_POLY, data); | ||||
| 			bw.write_be16(crc); | ||||
|  | ||||
| 			int conventionalHeaderStart = 0; | ||||
| 			if (!_parameters.useFm) | ||||
| 			{ | ||||
| 				for (int i=0; i<3; i++) | ||||
| 					writeRawBits(MFM_RECORD_SEPARATOR, 16); | ||||
| 				conventionalHeaderStart += 3; | ||||
|  | ||||
| 			} | ||||
| 			writeRawBits(_parameters.damByte, 16); | ||||
| 			conventionalHeaderStart += 1; | ||||
|  | ||||
| 			writeBytes(data.slice(conventionalHeaderStart)); | ||||
| 		} | ||||
|     } | ||||
|  | ||||
| 	if (_cursor >= _bits.size()) | ||||
| 		Error() << "track data overrun"; | ||||
| 	while (_cursor < _bits.size()) | ||||
| 		writeBytes(1, gapFill); | ||||
|  | ||||
| 	std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| 	fluxmap->appendBits(_bits, clockRateUs*1e3); | ||||
| 	return fluxmap; | ||||
| } | ||||
|  | ||||
| @@ -2,6 +2,7 @@ | ||||
| #define IBM_H | ||||
|  | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| /* IBM format (i.e. ordinary PC floppies). */ | ||||
|  | ||||
| @@ -31,68 +32,68 @@ struct IbmIdam | ||||
| class IbmDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     IbmDecoder(unsigned sectorBase, bool ignoreSideByte=false): | ||||
|     IbmDecoder(unsigned sectorBase, bool ignoreSideByte=false, | ||||
| 			const std::set<unsigned> requiredSectors=std::set<unsigned>()): | ||||
|         _sectorBase(sectorBase), | ||||
|         _ignoreSideByte(ignoreSideByte) | ||||
|         _ignoreSideByte(ignoreSideByte), | ||||
| 		_requiredSectors(requiredSectors) | ||||
|     {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
|  | ||||
| 	std::set<unsigned> requiredSectors(Track& track) const | ||||
| 	{ return _requiredSectors; } | ||||
|  | ||||
| private: | ||||
|     unsigned _sectorBase; | ||||
|     bool _ignoreSideByte; | ||||
| 	std::set<unsigned> _requiredSectors; | ||||
|     unsigned _currentSectorSize; | ||||
|     unsigned _currentHeaderLength; | ||||
| }; | ||||
|  | ||||
| #if 0 | ||||
| class AbstractIbmDecoder : public AbstractSoftSectorDecoder | ||||
| struct IbmParameters | ||||
| { | ||||
| 	int trackLengthMs; | ||||
| 	int sectorSize; | ||||
| 	bool emitIam; | ||||
| 	int startSectorId; | ||||
| 	int clockRateKhz; | ||||
| 	bool useFm; | ||||
| 	uint16_t idamByte; | ||||
| 	uint16_t damByte; | ||||
| 	int gap0; | ||||
| 	int gap1; | ||||
| 	int gap2; | ||||
| 	int gap3; | ||||
| 	std::string sectorSkew; | ||||
| }; | ||||
|  | ||||
| class IbmEncoder : public AbstractEncoder | ||||
| { | ||||
| public: | ||||
|     AbstractIbmDecoder(unsigned sectorIdBase): | ||||
|         _sectorIdBase(sectorIdBase) | ||||
|     {} | ||||
|     virtual ~AbstractIbmDecoder() {} | ||||
| 	IbmEncoder(const IbmParameters& parameters): | ||||
| 		_parameters(parameters) | ||||
| 	{} | ||||
|  | ||||
|     SectorVector decodeToSectors(const RawRecordVector& rawRecords, unsigned physicalTrack, unsigned physicalSide); | ||||
| 	virtual ~IbmEncoder() {} | ||||
|  | ||||
| protected: | ||||
|     virtual int skipHeaderBytes() const = 0; | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors); | ||||
|  | ||||
| private: | ||||
|     unsigned _sectorIdBase; | ||||
| 	void writeRawBits(uint32_t data, int width); | ||||
| 	void writeBytes(const Bytes& bytes); | ||||
| 	void writeBytes(int count, uint8_t value); | ||||
| 	void writeSync(); | ||||
| 	 | ||||
| private: | ||||
| 	IbmParameters _parameters; | ||||
| 	std::vector<bool> _bits; | ||||
| 	unsigned _cursor; | ||||
| 	bool _lastBit; | ||||
| }; | ||||
|  | ||||
| class IbmFmDecoder : public AbstractIbmDecoder | ||||
| { | ||||
| public: | ||||
|     IbmFmDecoder(unsigned sectorIdBase): | ||||
|         AbstractIbmDecoder(sectorIdBase) | ||||
|     {} | ||||
|  | ||||
|     int recordMatcher(uint64_t fifo) const; | ||||
|  | ||||
| protected: | ||||
|     int skipHeaderBytes() const | ||||
|     { return 0; } | ||||
| }; | ||||
|  | ||||
| class IbmMfmDecoder : public AbstractIbmDecoder | ||||
| { | ||||
| public: | ||||
|     IbmMfmDecoder(unsigned sectorIdBase): | ||||
|         AbstractIbmDecoder(sectorIdBase) | ||||
|     {} | ||||
|  | ||||
|     nanoseconds_t guessClock(Fluxmap& fluxmap) const; | ||||
|     int recordMatcher(uint64_t fifo) const; | ||||
|  | ||||
| protected: | ||||
|     int skipHeaderBytes() const | ||||
|     { return 3; } | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -118,10 +118,10 @@ static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status) | ||||
| uint8_t decode_side(uint8_t side) | ||||
| { | ||||
|     /* Mac disks, being weird, use the side byte to encode both the side (in | ||||
|      * bit 5) and also whether we're above track 0x3f (in bit 6). | ||||
|      * bit 5) and also whether we're above track 0x3f (in bit 0). | ||||
|      */ | ||||
|  | ||||
|     return !!(side & 0x40); | ||||
|     return !!(side & 0x20); | ||||
| } | ||||
|  | ||||
| AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord() | ||||
| @@ -184,3 +184,25 @@ void MacintoshDecoder::decodeDataRecord() | ||||
|     _sector->data.clear(); | ||||
|     _sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12)); | ||||
| } | ||||
|  | ||||
| std::set<unsigned> MacintoshDecoder::requiredSectors(Track& track) const | ||||
| { | ||||
| 	int count; | ||||
| 	if (track.physicalTrack < 16) | ||||
| 		count = 12; | ||||
| 	else if (track.physicalTrack < 32) | ||||
| 		count = 11; | ||||
| 	else if (track.physicalTrack < 48) | ||||
| 		count = 10; | ||||
| 	else if (track.physicalTrack < 64) | ||||
| 		count = 9; | ||||
| 	else | ||||
| 		count = 8; | ||||
|  | ||||
| 	std::set<unsigned> sectors; | ||||
| 	while (count--) | ||||
| 		sectors.insert(count); | ||||
| 	return sectors; | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										242
									
								
								arch/macintosh/encoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								arch/macintosh/encoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | ||||
| #include "globals.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "macintosh.h" | ||||
| #include "crc.h" | ||||
| #include "sectorset.h" | ||||
| #include "writer.h" | ||||
| #include "fmt/format.h" | ||||
| #include <ctype.h> | ||||
|  | ||||
| FlagGroup macintoshEncoderFlags; | ||||
|  | ||||
| static DoubleFlag postIndexGapUs( | ||||
| 	{ "--post-index-gap-us" }, | ||||
| 	"Post-index gap before first sector header (microseconds).", | ||||
| 	0); | ||||
|  | ||||
| static DoubleFlag clockCompensation( | ||||
| 	{ "--clock-compensation-factor" }, | ||||
| 	"Scale the output clock by this much.", | ||||
| 	1.0); | ||||
|  | ||||
| static bool lastBit; | ||||
|  | ||||
| static double clockRateUsForTrack(unsigned track) | ||||
| { | ||||
| 	if (track < 16) | ||||
| 		return 2.623; | ||||
| 	if (track < 32) | ||||
| 		return 2.861; | ||||
| 	if (track < 48) | ||||
| 		return 3.148; | ||||
| 	if (track < 64) | ||||
| 		return 3.497; | ||||
| 	return 3.934; | ||||
| } | ||||
|  | ||||
| static unsigned sectorsForTrack(unsigned track) | ||||
| { | ||||
| 	if (track < 16) | ||||
| 		return 12; | ||||
| 	if (track < 32) | ||||
| 		return 11; | ||||
| 	if (track < 48) | ||||
| 		return 10; | ||||
| 	if (track < 64) | ||||
| 		return 9; | ||||
| 	return 8; | ||||
| } | ||||
|  | ||||
| static int encode_data_gcr(uint8_t gcr) | ||||
| { | ||||
|     switch (gcr) | ||||
|     { | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case data: return gcr; | ||||
| 		#include "data_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| }; | ||||
|  | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan Woods | ||||
|  * and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp | ||||
|  */ | ||||
| static Bytes encode_crazy_data(const Bytes& input) | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     ByteReader br(input); | ||||
|  | ||||
| 	uint8_t w1, w2, w3, w4; | ||||
|  | ||||
|     static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3; | ||||
|  | ||||
|     uint8_t b1[LOOKUP_LEN + 1]; | ||||
|     uint8_t b2[LOOKUP_LEN + 1]; | ||||
|     uint8_t b3[LOOKUP_LEN + 1]; | ||||
|  | ||||
| 	uint32_t c1 = 0; | ||||
| 	uint32_t c2 = 0; | ||||
| 	uint32_t c3 = 0; | ||||
| 	for (int j=0;; j++) | ||||
| 	{ | ||||
| 		c1 = (c1 & 0xff) << 1; | ||||
| 		if (c1 & 0x0100) | ||||
| 			c1++; | ||||
|  | ||||
| 		uint8_t val = br.read_8(); | ||||
| 		c3 += val; | ||||
| 		if (c1 & 0x0100) | ||||
| 		{ | ||||
| 			c3++; | ||||
| 			c1 &= 0xff; | ||||
| 		} | ||||
| 		b1[j] = (val ^ c1) & 0xff; | ||||
|  | ||||
| 		val = br.read_8(); | ||||
| 		c2 += val; | ||||
| 		if (c3 > 0xff) | ||||
| 		{ | ||||
| 			c2++; | ||||
| 			c3 &= 0xff; | ||||
| 		} | ||||
| 		b2[j] = (val ^ c3) & 0xff; | ||||
|  | ||||
| 		if (br.pos == 524) | ||||
| 			break; | ||||
|  | ||||
| 		val = br.read_8(); | ||||
| 		c1 += val; | ||||
| 		if (c2 > 0xff) | ||||
| 		{ | ||||
| 			c1++; | ||||
| 			c2 &= 0xff; | ||||
| 		} | ||||
| 		b3[j] = (val ^ c2) & 0xff; | ||||
| 	} | ||||
| 	uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2); | ||||
| 	b3[LOOKUP_LEN] = 0; | ||||
|  | ||||
| 	for (int i = 0; i <= LOOKUP_LEN; i++) | ||||
| 	{ | ||||
| 		w1 = b1[i] & 0x3f; | ||||
| 		w2 = b2[i] & 0x3f; | ||||
| 		w3 = b3[i] & 0x3f; | ||||
| 		w4 =  ((b1[i] & 0xc0) >> 2); | ||||
| 		w4 |= ((b2[i] & 0xc0) >> 4); | ||||
| 		w4 |= ((b3[i] & 0xc0) >> 6); | ||||
|  | ||||
| 		bw.write_8(w4); | ||||
| 		bw.write_8(w1); | ||||
| 		bw.write_8(w2); | ||||
|  | ||||
| 		if (i != LOOKUP_LEN) | ||||
| 			bw.write_8(w3); | ||||
| 	} | ||||
|  | ||||
| 	bw.write_8(c4 & 0x3f); | ||||
| 	bw.write_8(c3 & 0x3f); | ||||
| 	bw.write_8(c2 & 0x3f); | ||||
| 	bw.write_8(c1 & 0x3f); | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src) | ||||
| { | ||||
| 	for (bool bit : src) | ||||
| 	{ | ||||
| 		if (cursor < bits.size()) | ||||
| 			bits[cursor++] = bit; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width) | ||||
| { | ||||
| 	cursor += width; | ||||
| 	for (int i=0; i<width; i++) | ||||
| 	{ | ||||
| 		unsigned pos = cursor - i - 1; | ||||
| 		if (pos < bits.size()) | ||||
| 			bits[pos] = data & 1; | ||||
| 		data >>= 1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static uint8_t encode_side(uint8_t track, uint8_t side) | ||||
| { | ||||
|     /* Mac disks, being weird, use the side byte to encode both the side (in | ||||
|      * bit 5) and also whether we're above track 0x3f (in bit 0). | ||||
|      */ | ||||
|  | ||||
| 	return (side ? 0x20 : 0x00) | ((track>0x3f) ? 0x01 : 0x00); | ||||
| } | ||||
|  | ||||
| static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector) | ||||
| { | ||||
| 	if ((sector->data.size() != 512) && (sector->data.size() != 524)) | ||||
| 		Error() << "unsupported sector size --- you must pick 512 or 524"; | ||||
|  | ||||
| 	write_bits(bits, cursor, 0xff, 1*8); /* pad byte */ | ||||
| 	for (int i=0; i<7; i++) | ||||
| 		write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */ | ||||
| 	write_bits(bits, cursor, MAC_SECTOR_RECORD, 3*8); | ||||
|  | ||||
|     uint8_t encodedTrack = sector->physicalTrack & 0x3f; | ||||
| 	uint8_t encodedSector = sector->logicalSector; | ||||
| 	uint8_t encodedSide = encode_side(sector->physicalTrack, sector->logicalSide); | ||||
| 	uint8_t formatByte = MAC_FORMAT_BYTE; | ||||
| 	uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; | ||||
|  | ||||
| 	write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1*8); | ||||
| 	write_bits(bits, cursor, encode_data_gcr(encodedSector), 1*8); | ||||
| 	write_bits(bits, cursor, encode_data_gcr(encodedSide), 1*8); | ||||
| 	write_bits(bits, cursor, encode_data_gcr(formatByte), 1*8); | ||||
| 	write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1*8); | ||||
|  | ||||
| 	write_bits(bits, cursor, 0xdeaaff, 3*8); | ||||
| 	write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */ | ||||
| 	write_bits(bits, cursor, MAC_DATA_RECORD, 3*8); | ||||
| 	write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1*8); | ||||
|  | ||||
| 	Bytes wireData; | ||||
| 	wireData.writer().append(sector->data.slice(512, 12)).append(sector->data.slice(0, 512)); | ||||
| 	for (uint8_t b : encode_crazy_data(wireData)) | ||||
| 		write_bits(bits, cursor, encode_data_gcr(b), 1*8); | ||||
|  | ||||
| 	write_bits(bits, cursor, 0xdeaaff, 3*8); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Fluxmap> MacintoshEncoder::encode( | ||||
| 	int physicalTrack, int physicalSide, const SectorSet& allSectors) | ||||
| { | ||||
| 	if ((physicalTrack < 0) || (physicalTrack >= MAC_TRACKS_PER_DISK)) | ||||
| 		return std::unique_ptr<Fluxmap>(); | ||||
|  | ||||
| 	double clockRateUs = clockRateUsForTrack(physicalTrack) * clockCompensation; | ||||
| 	int bitsPerRevolution = 200000.0 / clockRateUs; | ||||
| 	std::vector<bool> bits(bitsPerRevolution); | ||||
| 	unsigned cursor = 0; | ||||
|  | ||||
|     fillBitmapTo(bits, cursor, postIndexGapUs / clockRateUs, { true, false }); | ||||
| 	lastBit = false; | ||||
|  | ||||
| 	unsigned numSectors = sectorsForTrack(physicalTrack); | ||||
| 	for (int sectorId=0; sectorId<numSectors; sectorId++) | ||||
| 	{ | ||||
| 		const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId); | ||||
| 		write_sector(bits, cursor, sectorData); | ||||
|     } | ||||
|  | ||||
| 	if (cursor >= bits.size()) | ||||
| 		Error() << fmt::format("track data overrun by {} bits", cursor - bits.size()); | ||||
| 	fillBitmapTo(bits, cursor, bits.size(), { true, false }); | ||||
|  | ||||
| 	std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| 	fluxmap->appendBits(bits, clockRateUs*1e3); | ||||
| 	return fluxmap; | ||||
| } | ||||
|  | ||||
| @@ -1,11 +1,17 @@ | ||||
| #ifndef MACINTOSH_H | ||||
| #define MACINTOSH_H | ||||
|  | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| #define MAC_SECTOR_RECORD   0xd5aa96 /* 1101 0101 1010 1010 1001 0110 */ | ||||
| #define MAC_DATA_RECORD     0xd5aaad /* 1101 0101 1010 1010 1010 1101 */ | ||||
|  | ||||
| #define MAC_SECTOR_LENGTH   524 /* yes, really */ | ||||
| #define MAC_ENCODED_SECTOR_LENGTH 703 | ||||
| #define MAC_FORMAT_BYTE     0x22 | ||||
|  | ||||
| #define MAC_TRACKS_PER_DISK 80 | ||||
|  | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
| @@ -18,7 +24,21 @@ public: | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
|  | ||||
| 	std::set<unsigned> requiredSectors(Track& track) const; | ||||
| }; | ||||
|  | ||||
| class MacintoshEncoder : public AbstractEncoder | ||||
| { | ||||
| public: | ||||
| 	virtual ~MacintoshEncoder() {} | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors); | ||||
| }; | ||||
|  | ||||
| extern FlagGroup macintoshEncoderFlags; | ||||
|  | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
							
								
								
									
										61
									
								
								arch/micropolis/decoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								arch/micropolis/decoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "micropolis.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| /* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. */ | ||||
| static const FluxPattern SECTOR_SYNC_PATTERN(32, 0xaaaa5555); | ||||
|  | ||||
| AbstractDecoder::RecordType MicropolisDecoder::advanceToNextRecord() | ||||
| { | ||||
| 	_fmr->seekToIndexMark(); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_SYNC_PATTERN) { | ||||
| 		readRawBits(16); | ||||
| 		return SECTOR_RECORD; | ||||
| 	} | ||||
| 	return UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| /* Adds all bytes, with carry. */ | ||||
| static uint8_t checksum(const Bytes& bytes) { | ||||
| 	ByteReader br(bytes); | ||||
| 	uint16_t sum = 0; | ||||
| 	while (!br.eof()) { | ||||
| 		if (sum > 0xFF) { | ||||
| 			sum -= 0x100 - 1; | ||||
| 		} | ||||
| 		sum += br.read_8(); | ||||
| 	} | ||||
| 	/* The last carry is ignored */ | ||||
| 	return sum & 0xFF; | ||||
| } | ||||
|  | ||||
| void MicropolisDecoder::decodeSectorRecord() | ||||
| { | ||||
| 	auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16); | ||||
| 	auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE); | ||||
| 	ByteReader br(bytes); | ||||
|  | ||||
| 	br.read_8();  /* sync */ | ||||
| 	_sector->logicalTrack = br.read_8(); | ||||
| 	_sector->logicalSide = _sector->physicalSide; | ||||
| 	_sector->logicalSector = br.read_8(); | ||||
| 	if (_sector->logicalSector > 15) | ||||
| 		return; | ||||
| 	if (_sector->logicalTrack > 77) | ||||
| 		return; | ||||
|  | ||||
| 	br.read(10);  /* OS data or padding */ | ||||
| 	_sector->data = br.read(256); | ||||
| 	uint8_t wantChecksum = br.read_8(); | ||||
| 	uint8_t gotChecksum = checksum(bytes.slice(1, 2+266)); | ||||
| 	br.read(5);  /* 4 byte ECC and ECC-present flag */ | ||||
|  | ||||
| 	_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
							
								
								
									
										18
									
								
								arch/micropolis/micropolis.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								arch/micropolis/micropolis.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #ifndef ZILOGMCZ_H | ||||
| #define ZILOGMCZ_H | ||||
|  | ||||
| #define MICROPOLIS_ENCODED_SECTOR_SIZE (1+2+266+6) | ||||
|  | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class MicropolisDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
| 	virtual ~MicropolisDecoder() {} | ||||
|  | ||||
| 	RecordType advanceToNextRecord(); | ||||
| 	void decodeSectorRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
| @@ -37,7 +37,7 @@ AbstractDecoder::RecordType MxDecoder::advanceToNextRecord() | ||||
|         const FluxMatcher* matcher = nullptr; | ||||
|         _sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher); | ||||
|         readRawBits(32); /* skip the ID mark */ | ||||
|         _logicalTrack = decodeFmMfm(readRawBits(32)).reader().read_be16(); | ||||
|         _logicalTrack = decodeFmMfm(readRawBits(32)).slice(0, 32).reader().read_be16(); | ||||
|     } | ||||
|     else if (_currentSector == 10) | ||||
|     { | ||||
|   | ||||
							
								
								
									
										87
									
								
								arch/tids990/decoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								arch/tids990/decoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "tids990/tids990.h" | ||||
| #include "crc.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "sector.h" | ||||
| #include "record.h" | ||||
| #include "track.h" | ||||
| #include <string.h> | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| /* The Texas Instruments DS990 uses MFM with a scheme similar to a simplified | ||||
|  * version of the IBM record scheme (it's actually easier to parse than IBM). | ||||
|  * There are 26 sectors per track, each holding a rather weird 288 bytes. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * Sector record: | ||||
|  * data:    0  1  0  1  0  1  0  1 .0  0  0  0  1  0  1  0  = 0x550a | ||||
|  * mfm:     00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 00 = 0x11112a44 | ||||
|  * special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 00 = 0x11112244 | ||||
|  *                                        ^^ | ||||
|  * When shifted out of phase, the special 0xa1 byte becomes an illegal | ||||
|  * encoding (you can't do 10 00). So this can't be spoofed by user data. | ||||
|  */ | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244); | ||||
|  | ||||
| /* | ||||
|  * Data record: | ||||
|  * data:    0  1  0  1  0  1  0  1 .0  0  0  0  1  0  1  1  = 0x550c | ||||
|  * mfm:     00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 01 = 0x11112a45 | ||||
|  * special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 01 = 0x11112245 | ||||
|  *                                        ^^ | ||||
|  * When shifted out of phase, the special 0xa1 byte becomes an illegal | ||||
|  * encoding (you can't do 10 00). So this can't be spoofed by user data. | ||||
|  */ | ||||
| const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245); | ||||
|  | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN }); | ||||
|  | ||||
| AbstractDecoder::RecordType TiDs990Decoder::advanceToNextRecord() | ||||
| { | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_RECORD_PATTERN) | ||||
| 		return RecordType::SECTOR_RECORD; | ||||
| 	if (matcher == &DATA_RECORD_PATTERN) | ||||
| 		return RecordType::DATA_RECORD; | ||||
| 	return RecordType::UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void TiDs990Decoder::decodeSectorRecord() | ||||
| { | ||||
|     auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16); | ||||
|     auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE); | ||||
|  | ||||
|     ByteReader br(bytes); | ||||
| 	uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3)); | ||||
|  | ||||
| 	br.seek(2); | ||||
| 	_sector->logicalSide = br.read_8() >> 3; | ||||
| 	_sector->logicalTrack = br.read_8(); | ||||
| 	br.read_8(); /* number of sectors per track */ | ||||
| 	_sector->logicalSector = br.read_8(); | ||||
| 	br.read_be16(); /* sector size */ | ||||
|     uint16_t wantChecksum = br.read_be16(); | ||||
|  | ||||
|     if (wantChecksum == gotChecksum) | ||||
|         _sector->status = Sector::DATA_MISSING; /* correct but unintuitive */ | ||||
| } | ||||
|  | ||||
| void TiDs990Decoder::decodeDataRecord() | ||||
| { | ||||
| 	auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16); | ||||
| 	auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE); | ||||
|  | ||||
| 	ByteReader br(bytes); | ||||
| 	uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3)); | ||||
|  | ||||
| 	br.seek(2); | ||||
| 	_sector->data = br.read(TIDS990_PAYLOAD_SIZE); | ||||
| 	uint16_t wantChecksum = br.read_be16(); | ||||
|     _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|  | ||||
							
								
								
									
										176
									
								
								arch/tids990/encoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								arch/tids990/encoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | ||||
| #include "globals.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "tids990.h" | ||||
| #include "crc.h" | ||||
| #include "sectorset.h" | ||||
| #include "writer.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| FlagGroup tids990EncoderFlags; | ||||
|  | ||||
| static IntFlag trackLengthMs( | ||||
| 	{ "--tids990-track-length-ms" }, | ||||
| 	"Length of a track in milliseconds.", | ||||
| 	166); | ||||
|  | ||||
| static IntFlag sectorCount( | ||||
| 	{ "--tids990-sector-count" }, | ||||
| 	"Number of sectors per track.", | ||||
| 	26); | ||||
|  | ||||
| static IntFlag clockRateKhz( | ||||
| 	{ "--tids990-clock-rate-khz" }, | ||||
| 	"Clock rate of data to write.", | ||||
| 	500); | ||||
|  | ||||
| static HexIntFlag am1Byte( | ||||
| 	{ "--tids990-am1-byte" }, | ||||
| 	"16-bit RAW bit pattern to use for the AM1 ID byte", | ||||
| 	0x2244); | ||||
|  | ||||
| static HexIntFlag am2Byte( | ||||
| 	{ "--tids990-am2-byte" }, | ||||
| 	"16-bit RAW bit pattern to use for the AM2 ID byte", | ||||
| 	0x2245); | ||||
|  | ||||
| static IntFlag gap1( | ||||
| 	{ "--tids990-gap1-bytes" }, | ||||
| 	"Size of gap 1 (the post-index gap).", | ||||
| 	80); | ||||
|  | ||||
| static IntFlag gap2( | ||||
| 	{ "--tids990-gap2-bytes" }, | ||||
| 	"Size of gap 2 (the post-ID gap).", | ||||
| 	21); | ||||
|  | ||||
| static IntFlag gap3( | ||||
| 	{ "--tids990-gap3-bytes" }, | ||||
| 	"Size of gap 3 (the post-data or format gap).", | ||||
| 	51); | ||||
|  | ||||
| static StringFlag sectorSkew( | ||||
| 	{ "--tids990-sector-skew" }, | ||||
| 	"Order to emit sectors.", | ||||
| 	"1mhc72nid83oje94pkfa50lgb6"); | ||||
|  | ||||
| static int charToInt(char c) | ||||
| { | ||||
| 	if (isdigit(c)) | ||||
| 		return c - '0'; | ||||
| 	return 10 + tolower(c) - 'a'; | ||||
| } | ||||
|  | ||||
| void TiDs990Encoder::writeRawBits(uint32_t data, int width) | ||||
| { | ||||
| 	_cursor += width; | ||||
| 	_lastBit = data & 1; | ||||
| 	for (int i=0; i<width; i++) | ||||
| 	{ | ||||
| 		unsigned pos = _cursor - i - 1; | ||||
| 		if (pos < _bits.size()) | ||||
| 			_bits[pos] = data & 1; | ||||
| 		data >>= 1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TiDs990Encoder::writeBytes(const Bytes& bytes) | ||||
| { | ||||
| 	encodeMfm(_bits, _cursor, bytes, _lastBit); | ||||
| } | ||||
|  | ||||
| void TiDs990Encoder::writeBytes(int count, uint8_t byte) | ||||
| { | ||||
| 	Bytes bytes = { byte }; | ||||
| 	for (int i=0; i<count; i++) | ||||
| 		writeBytes(bytes); | ||||
| } | ||||
|  | ||||
| static uint8_t decodeUint16(uint16_t raw) | ||||
| { | ||||
| 	Bytes b; | ||||
| 	ByteWriter bw(b); | ||||
| 	bw.write_be16(raw); | ||||
| 	return decodeFmMfm(b.toBits())[0]; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Fluxmap> TiDs990Encoder::encode( | ||||
| 	int physicalTrack, int physicalSide, const SectorSet& allSectors) | ||||
| { | ||||
| 	double clockRateUs = 1e3 / clockRateKhz / 2.0; | ||||
| 	int bitsPerRevolution = (trackLengthMs * 1000.0) / clockRateUs; | ||||
| 	_bits.resize(bitsPerRevolution); | ||||
| 	_cursor = 0; | ||||
|  | ||||
| 	uint8_t am1Unencoded = decodeUint16(am1Byte); | ||||
| 	uint8_t am2Unencoded = decodeUint16(am2Byte); | ||||
|  | ||||
| 	writeBytes(gap1, 0x55); | ||||
|  | ||||
| 	bool first = true; | ||||
| 	for (char sectorChar : sectorSkew.get()) | ||||
| 	{ | ||||
| 		int sectorId = charToInt(sectorChar); | ||||
| 		if (!first) | ||||
| 			writeBytes(gap3, 0x55); | ||||
| 		first = false; | ||||
|  | ||||
| 		const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId); | ||||
| 		if (!sectorData) | ||||
| 			Error() << fmt::format("format tried to find sector {} which wasn't in the input file", sectorId); | ||||
|  | ||||
| 		/* Writing the sector and data records are fantastically annoying. | ||||
| 		 * The CRC is calculated from the *very start* of the record, and | ||||
| 		 * include the malformed marker bytes. Our encoder doesn't know | ||||
| 		 * about this, of course, with the result that we have to construct | ||||
| 		 * the unencoded header, calculate the checksum, and then use the | ||||
| 		 * same logic to emit the bytes which require special encoding | ||||
| 		 * before encoding the rest of the header normally. */ | ||||
|  | ||||
| 		{ | ||||
| 			Bytes header; | ||||
| 			ByteWriter bw(header); | ||||
|  | ||||
| 			writeBytes(12, 0x55); | ||||
| 			bw.write_8(am1Unencoded); | ||||
| 			bw.write_8(sectorData->logicalSide << 3); | ||||
| 			bw.write_8(sectorData->logicalTrack); | ||||
| 			bw.write_8(sectorCount); | ||||
| 			bw.write_8(sectorData->logicalSector); | ||||
| 			bw.write_be16(sectorData->data.size()); | ||||
| 			uint16_t crc = crc16(CCITT_POLY, header); | ||||
| 			bw.write_be16(crc); | ||||
|  | ||||
| 			writeRawBits(am1Byte, 16); | ||||
| 			writeBytes(header.slice(1)); | ||||
| 		} | ||||
|  | ||||
| 		writeBytes(gap2, 0x55); | ||||
|  | ||||
| 		{ | ||||
| 			Bytes data; | ||||
| 			ByteWriter bw(data); | ||||
|  | ||||
| 			writeBytes(12, 0x55); | ||||
| 			bw.write_8(am2Unencoded); | ||||
|  | ||||
| 			bw += sectorData->data; | ||||
| 			uint16_t crc = crc16(CCITT_POLY, data); | ||||
| 			bw.write_be16(crc); | ||||
|  | ||||
| 			writeRawBits(am2Byte, 16); | ||||
| 			writeBytes(data.slice(1)); | ||||
| 		} | ||||
|     } | ||||
|  | ||||
| 	if (_cursor >= _bits.size()) | ||||
| 		Error() << "track data overrun"; | ||||
| 	while (_cursor < _bits.size()) | ||||
| 		writeBytes(1, 0x55); | ||||
| 	 | ||||
| 	std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| 	fluxmap->appendBits(_bits, clockRateUs*1e3); | ||||
| 	return fluxmap; | ||||
| } | ||||
|  | ||||
							
								
								
									
										47
									
								
								arch/tids990/tids990.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								arch/tids990/tids990.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #ifndef TIDS990_H | ||||
| #define TIDS990_H | ||||
|  | ||||
| #define TIDS990_PAYLOAD_SIZE       288 /* bytes */ | ||||
| #define TIDS990_SECTOR_RECORD_SIZE 10 /* bytes */ | ||||
| #define TIDS990_DATA_RECORD_SIZE   (TIDS990_PAYLOAD_SIZE + 4) /* bytes */ | ||||
|  | ||||
| class Sector; | ||||
| class SectorSet; | ||||
| class Fluxmap; | ||||
| class Track; | ||||
|  | ||||
| class TiDs990Decoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~TiDs990Decoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
| 	void decodeDataRecord(); | ||||
| }; | ||||
|  | ||||
| class TiDs990Encoder : public AbstractEncoder | ||||
| { | ||||
| public: | ||||
| 	virtual ~TiDs990Encoder() {} | ||||
|  | ||||
| private: | ||||
| 	void writeRawBits(uint32_t data, int width); | ||||
| 	void writeBytes(const Bytes& bytes); | ||||
| 	void writeBytes(int count, uint8_t value); | ||||
| 	void writeSync(); | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors); | ||||
|  | ||||
| private: | ||||
| 	std::vector<bool> _bits; | ||||
| 	unsigned _cursor; | ||||
| 	bool _lastBit; | ||||
| }; | ||||
|  | ||||
| extern FlagGroup tids990EncoderFlags; | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
							
								
								
									
										190
									
								
								doc/building.md
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								doc/building.md
									
									
									
									
									
								
							| @@ -25,6 +25,8 @@ This is the physical stuff you'll need. | ||||
|     connector](https://eu.mouser.com/ProductDetail/Amphenol-FCI/86130342114345E1LF?qs=%2Fha2pyFadug%252BpMTyxmFhglPPVKuWXYuFpPNgq%252BsrzhDnXxo8B28k7UCGc7F%2FXjsi) | ||||
|     (or one of the other myriad compatible connectors; there's a billion). | ||||
|  | ||||
|   - A floppy drive cable, preferably one with two connectors and a twist. | ||||
|  | ||||
|   - A suitable power supply. 3.5" floppy drives use 5V at about an amp | ||||
|     (usually less) --- sadly, too much to power from USB. 5.25" floppy drives | ||||
|     also require 12V. An old but decent quality PC power supply is ideal, as | ||||
| @@ -48,7 +50,7 @@ All you need to do is attach your chosen connector to the board. You'll need | ||||
| to make sure that pin 2 on the cable is connected to pin 2.7 on the board, | ||||
| and pin 34 to pin 1.7 on the board (and of course all the ones in between). | ||||
| Apart from grounding the board (see below), this is literally all there is to | ||||
| it. | ||||
| it. The actual pinout is described in detail below. | ||||
|  | ||||
| The pads are small, but soldering them isn't too bad with a needle-nosed | ||||
| soldering iron tip. | ||||
| @@ -173,6 +175,7 @@ pattern. Press and hold the little button near the light for five seconds | ||||
| until the light stays solidly on. Now you should be able to acquire | ||||
| the port and proceed normally. | ||||
|  | ||||
|  | ||||
| ## Building the client | ||||
|  | ||||
| The client software is where the intelligence, such as it is, is. It's pretty | ||||
| @@ -181,12 +184,12 @@ well, although on Windows it'll need MSYS2 and mingw32. You'll need to | ||||
| install some support packages. | ||||
|  | ||||
|   - For Linux (this is Ubuntu, but this should apply to Debian too): | ||||
|   `ninja-build`, `libusb-1.0-0-dev`, `libsqlite3-dev`. | ||||
| 	`ninja-build`, `libusb-1.0-0-dev`, `libsqlite3-dev`. | ||||
|   - For OSX with Homebrew: `ninja`, `libusb`, `pkg-config`, `sqlite`. | ||||
|   - For Windows with MSYS2: `make`, `ninja`, `mingw-w64-i686-libusb`, | ||||
|   `mingw-w64-i686-sqlite3`, `mingw-w64-i686-zlib`, `mingw-w64-i686-gcc`. | ||||
| 	`mingw-w64-i686-sqlite3`, `mingw-w64-i686-zlib`, `mingw-w64-i686-gcc`. | ||||
|  | ||||
| These lists are not necessarily exhaustive --- plaese [get in | ||||
| These lists are not necessarily exhaustive --- please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new) if I've missed | ||||
| anything. | ||||
|  | ||||
| @@ -197,11 +200,184 @@ dependencies and you should be able to put it anywhere. | ||||
| If it doesn't build, please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|  | ||||
|  | ||||
| ## Connecting it up | ||||
|  | ||||
| You should now have a working board, so it's time to test it. | ||||
|  | ||||
|   1. Plug the motherboard end of your floppy disk cable into the FluxEngine. | ||||
|       | ||||
|      The **red stripe goes on the right**. The **lower set of | ||||
|      holes connect to the board**. See the pinout below. | ||||
|  | ||||
|      If you're using header pins, the upper row of holes in the connector | ||||
|      should overhang the edge of the board. If you're using a floppy drive | ||||
|      motherboard connector, you're golden, of course (unless you have one of | ||||
|      those annoying unkeyed cables, or have accidentally soldered the | ||||
|      connector on in the wrong place --- don't laugh, I've done it.) | ||||
|  | ||||
|   2. Plug the drive end of your floppy disk cable into the drive (or drives). | ||||
|  | ||||
|      Floppy disk cables typically have [two pairs of floppy disk drive | ||||
|      connectors with a twist between | ||||
|      them](http://www.nullmodem.com/Floppy.htm). (Each pair has one connector | ||||
|      for a 3.5" drive and a different one for a 5.25" drive.) (Some cables | ||||
|      are cheap and just have the 3.5" connectors. Some are _very_ cheap and | ||||
|      have a single 3.5" connector, after the twist.) | ||||
|       | ||||
|      If you have **two** drives, plug them into both connectors. FluxEngine, | ||||
|      sadly, non-standard disk numbering (there are reasons). Drive 0 is the | ||||
|      one nearest the motherboard; that is, before the twist. Drive 1 is the | ||||
|      one at the end of the cable; that is, after the twist. Drive 0 is the | ||||
|      default. You can tell the client to select drive 1 by using `-s :d=1`. | ||||
|  | ||||
|      If you have **one** drive, you may plug it into _either_ connector. | ||||
|      FluxEngine will autodetect it and treat it as drive 0. However, you'll | ||||
|      get the most reliable electrical signal if you plug it in at the end of | ||||
|      the cable. | ||||
|  | ||||
|      **A note on termination:** some 5.25" drives require jumper configuration | ||||
|      to tell them whether they're at the end of the cable or in the middle of | ||||
|      the cable. 3.5" drives don't, and my 5.25" drives don't, so I can't | ||||
|      advise there. Consult your drive datasheet for details. | ||||
|  | ||||
|   3. **Important.** Make sure that no disk you care about is in the drive. | ||||
| 	 (Because if your wiring is wrong and a disk is inserted, you'll corrupt | ||||
| 	 it.) | ||||
|  | ||||
|   4. Connect the floppy drive to power. Nothing should happen. If you've | ||||
| 	 connected something in backwards, you'll see the drive light up, the motor | ||||
| 	 start, and if you didn't take the disk out, one track has just been wiped. | ||||
| 	 If this happens, check your wiring. | ||||
|  | ||||
|   5. Strip off the little piece of protective plastic on the USB socket on the | ||||
| 	 board --- the little socket at the end, not the big programmer plug. | ||||
|  | ||||
|   6. Connect the FluxEngine to your PC via USB. | ||||
|  | ||||
|   7. Insert a scratch disk and do `fluxengine rpm` from the shell. The motor | ||||
|      should work and it'll tell you that the disk is spinning at about 300 | ||||
|      rpm for a 3.5" disk, or 360 rpm for a 5.25" disk. If it doesn't, please | ||||
|      [get in touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|  | ||||
|   8. Do `fluxengine test bandwidth` from the shell. It'll measure your USB | ||||
| 	 bandwidth. Ideally you should be getting above 800kB/s in both directions. | ||||
| 	 FluxEngine needs about 300kB/s for a DD disk and about 600kB/s for a HD | ||||
| 	 disk, so if you're getting less than this, try a different USB port. | ||||
|  | ||||
|   9. Insert a standard PC formatted floppy disk into the drive (probably a good | ||||
|      idea to remove the old disk first). Then do `fluxengine read ibm`. It | ||||
|      should read the disk, emitting copious diagnostics, and spit out an | ||||
|      `ibm.img` file containing the decoded disk image (either 1440kB or 720kB | ||||
|      depending). | ||||
|  | ||||
|  10. Profit! | ||||
|  | ||||
| ## Technical details | ||||
|  | ||||
| The board pinout and the way it's connected to the floppy bus is described | ||||
| below. | ||||
|  | ||||
| ```ditaa | ||||
| :-E -s 0.75 | ||||
|                  +-----+ | ||||
|                  ||||||| | ||||
|             +----+-----+----+ | ||||
|             +cAAA           + | ||||
|             +  Debug board  + | ||||
|             +----+-----+----+ | ||||
|             + GND|cDDD | VDD+   | ||||
|             +----+     +----+ | ||||
| INDEX300 ---+ 3.0|     | GND+--------------------------+ | ||||
|             +----+     +----+                 +--+--+  | | ||||
| INDEX360 ---+ 3.1|     | 1.7+------ DISKCHG --+34+33+--+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|     TK43 ---+ 3.2|     | 1.6+------- SIDE1 ---+32+31+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 3.3|     | 1.5+------- RDATA ---+30+29+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 3.4|     | 1.4+-------- WPT ----+28+27+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 3.5|     | 1.3+------- TRK00 ---+26+25+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 3.6|     | 1.2+------- WGATE ---+24+23+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 3.7|     | 1.1+------- WDATA ---+22+21+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             +15.0|     | 1.0+------- STEP ----+20+19+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             +15.1|     |12.0+--- DIR/SIDE1 ---+18+17+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             +15.2|     |12.1+------- MOTEB ---+16+15+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             +15.3|     |12.2+------- DRVSA ---+14+13+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             +15.4|     |12.3+------- DRVSB ---+12+11+ | ||||
|             +----+     +----+                 +--+--+ | ||||
|             +15.5|     |12.4+------- MOTEA ---+10+9 + | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 0.0|     |12.5+------- INDEX ---+8 +7 + | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 0.1|     |12.6+-------- n/c ----+6 +5 + | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 0.2|     |12.7+- TX --- n/c ----+4 +3 + | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 0.3|     | 2.7+------- REDWC ---+2 +1 + | ||||
|             +----+     +----+                 +--+--+ | ||||
|             + 0.4|     | 2.6+   | ||||
|             +----+     +----+                FDD socket | ||||
|             + 0.5|     | 2.5+   | ||||
|             +----+     +----+ | ||||
|             + 0.6|     | 2.4+    TX: debug UART from board | ||||
|             +----+     +----+ | ||||
|             + 0.7|     | 2.3+ | ||||
|             +----+     +----+ | ||||
|             + RST|     | 2.2+   | ||||
|             +----+     +----+ | ||||
|             + GND|     | 2.1+   | ||||
|             +----+ USB +----+ | ||||
|             + VDD+-----+ 2.0+   | ||||
|             +----+-----+----+ | ||||
|                PSoC5 board | ||||
| ``` | ||||
|  | ||||
| Notes: | ||||
|  | ||||
|   - `DIR/SIDE1` is the step direction pin. During reads or writes, `SIDE1` is | ||||
|   also multiplexed onto it, because some drives expect this. This is harmless | ||||
|   on other drives because the `DIR` pin is ignored during reads or writes. | ||||
|  | ||||
|   - `TX` is the debug UART port. It's on pin 12.7 because the board routes it | ||||
|   to the USB serial port on the programmer, so you can get debug information | ||||
|   from the FluxEngine by just plugging the programming end into a USB port | ||||
|   and using a serial terminal at 115200 baud. If you solder a floppy drive | ||||
|   connector on, then it'll end up connected to pin 4 of the floppy drive bus, | ||||
|   which is usually not connected. It's possible that some floppy drives do, | ||||
|   in fact, use this pin. You may wish to remove pin 4 from the floppy drive | ||||
|   socket before attaching it to the FluxEngine to make sure that this pin is | ||||
|   not connected; however, so far I have not found any drives for which this | ||||
|   is necessary. If you do find one, _please_ [get in | ||||
|   touch](https://github.com/davidgiven/fluxengine/issues/new) so I can | ||||
|   document it. | ||||
|  | ||||
|   - The `GND` pin only really needs to be connected to one of the floppy bus | ||||
|   ground pins; pin 33 is the closest. For extra safety, you can bridge all | ||||
|   the odd numbered pins together and ground them all if you like. | ||||
|  | ||||
|   - `INDEX300` and `INDEX360` are optional output pins which generate fake | ||||
|   timing pulses for 300 and 360 RPM drives. These are useful for certain | ||||
|   rather exotic things. See the section on flippy disks [in the FAQ](faq.md) | ||||
|   for more details; you can normally ignore these. | ||||
|  | ||||
|   - `TK43` is an optional output pin which goes low when the drive is seeking | ||||
|   to track 43 or above. This is useful when using 8" floppy drives, which | ||||
|   require reduced write current when writing to these tracks. | ||||
|  | ||||
| ## Next steps | ||||
|  | ||||
| The board's now assembled and programmed. Plug it into your drive, strip the | ||||
| plastic off the little USB connector and plug that into your computer, and | ||||
| you're ready to start using it. | ||||
| You should now be ready to go. You'll want to read [the client | ||||
| documentation](using.md) for information about how to actually do interesting | ||||
| things. | ||||
|  | ||||
| I _do_ make updates to the firmware whenever necessary, so you may need to | ||||
| reprogram it at intervals; you may want to take this into account if you | ||||
|   | ||||
| @@ -14,10 +14,12 @@ Apparently about 20% of Brother word processors have alignment issues which | ||||
| means that the disks can't be read by FluxEngine (because the tracks on the | ||||
| disk don't line up with the position of the head in a PC drive). The word | ||||
| processors themselves solved this by microstepping until they found where the | ||||
| real track is, but normal PC drives aren't capable of doing this. | ||||
| Particularly with the 120kB disks, you might want to fiddle with the start | ||||
| track (e.g. `:t=0-79x2`) to get a clean read. Keep an eye on the bad sector | ||||
| map that's dumped at the end of a read. | ||||
| real track is, but normal PC drives aren't capable of doing this.  Particularly | ||||
| with the 120kB disks, you might want to fiddle with the start track (e.g. | ||||
| `:t=0-79x2`) to get a clean read. Keep an eye on the bad sector map that's | ||||
| dumped at the end of a read. My word processor likes to put logical track 0 on | ||||
| physical track 3, which means that logical track 77 is on physical track 80; | ||||
| luckily my PC drive can access track 80. | ||||
|  | ||||
| Using FluxEngine to *write* disks isn't a problem, so the | ||||
| simplest solution is to use FluxEngine to create a new disk, with the tracks | ||||
| @@ -30,7 +32,7 @@ If you find one of these misaligned disks then *please* [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new); I want to | ||||
| investigate. | ||||
|  | ||||
| Reading discs | ||||
| Reading disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
| @@ -41,7 +43,7 @@ fluxengine read brother | ||||
|  | ||||
| You should end up with a `brother.img` which is 239616 bytes long. | ||||
|  | ||||
| Writing discs | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
| @@ -53,6 +55,27 @@ fluxengine write brother | ||||
| ...and it'll write a `brother.img` file which is 239616 bytes long to the | ||||
| disk. (Use `-i` to specify a different input filename.) | ||||
|  | ||||
| Dealing with misaligned disks | ||||
| ----------------------------- | ||||
|  | ||||
| While FluxEngine can't read misaligned disks directly, Brother word processors | ||||
| _can_. If you have access to a compatible word processor, there's a fairly | ||||
| simple workaround to allow you to extract the data: | ||||
|  | ||||
|   1. Format a disk using FluxEngine (by simply writing a blank filesystem image | ||||
| 	 to a disk). This will have the correct alignment to work on a PC drive. | ||||
|  | ||||
|   2. Use a word processor to copy the misaligned disk to the newly formatted | ||||
| 	 disk. The machine will happily adjust itself to both sets of alignments. | ||||
|  | ||||
|   3. Use FluxEngine to read the data off the correctly aligned disk. | ||||
|  | ||||
| I realise this is rather unsatisfactory, as the Brother hardware is becoming | ||||
| rarer and they cope rather badly with damaged disks, but this is a limitation | ||||
| of the hardware of normal PC drives. (It _is_ possible to deliberately misalign | ||||
| a drive to make it match up with a bad disk, but this is for experts only --- I | ||||
| wouldn't dare.) | ||||
|  | ||||
| Low level format | ||||
| ---------------- | ||||
|  | ||||
| @@ -60,14 +83,6 @@ The drive is a single-sided 3.5" drive spinning at not 300 rpm (I don't know | ||||
| the precise speed yet but FluxEngine doesn't care). The 240kB disks have 78 | ||||
| tracks and the 120kB disks have 39. | ||||
|  | ||||
| The Brother drive alignment is kinda variable; when you put the disk in the | ||||
| drive it seeks all the way to physical track 0 and then starts searching for | ||||
| something which looks like data. My machine likes to put logical track 0 on | ||||
| physical track 3. FluxEngine puts logical track 0 on physical track 0 for | ||||
| simplicity, which works fine (at least on my machine). If this doesn't work | ||||
| for you, [get in touch](https://github.com/davidgiven/fluxengine/issues/new); | ||||
| there are potential workarounds. | ||||
|  | ||||
| Each track has 12 256-byte sectors. The drive ignores the index hole so they're | ||||
| lined up all anyhow. As FluxEngine can only read from index to index, it | ||||
| actually reads two complete revolutions and reassembles the sectors from that. | ||||
| @@ -138,7 +153,8 @@ mcopy -i brother.img ::brother.doc linux.doc | ||||
| ``` | ||||
|  | ||||
| The word processor checks the media byte, unfortunately, so you'll need to | ||||
| change it back to 0x58 before writing an image to disk. | ||||
| change it back to 0x58 before writing an image to disk. Just run | ||||
| `brother240tool` on the image again and it will flip it back. | ||||
|  | ||||
| The file format is not WP-1, and currently remains completely unknown, | ||||
| although it's probably related. If anyone knows anything about this, please | ||||
|   | ||||
							
								
								
									
										152
									
								
								doc/disk-ibm.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								doc/disk-ibm.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| Disk: Generic IBM | ||||
| ================= | ||||
|  | ||||
| IBM scheme disks are _the_ most common disk format, ever. They're used by a | ||||
| huge variety of different systems, and they come in a huge variety of different | ||||
| forms, but they're all fundamentally the same: either FM or MFM, either single | ||||
| or double sided, with distinct sector header and data records and no sector | ||||
| metadata. Systems which use IBM scheme disks include but are not limited to: | ||||
|  | ||||
|   - IBM PCs (naturally) | ||||
|   - Atari ST | ||||
|   - late era Apple machines | ||||
|   - Acorn machines | ||||
|   - the TRS-80 | ||||
|   - late era Commodore machines (the 1571 and so on) | ||||
|   - most CP/M machines | ||||
|   - etc | ||||
|  | ||||
| FluxEngine supports reading these. However, some variants are more peculiar | ||||
| than others, and as a result there are specific decoders which set the defaults | ||||
| correctly for certain formats (for example: on PC disks the sector numbers | ||||
| start from 1, but on [Acorn](disk-acorndfs.md) disks they start from 0). The | ||||
| IBM decoder described here is the generic one, and is suited for 'conventional' | ||||
| PC disks. While you can read all the variant formats with it if you use the | ||||
| right set of arguments, it's easier to use the specific decoder. | ||||
|  | ||||
| The generic decoder is mostly self-configuring, and will detect the format of | ||||
| your disk for you. | ||||
|  | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
|     fluxengine read ibm | ||||
|  | ||||
| ...and you'll end up with an `ibm.img` file. This should work on most PC disks | ||||
| (including FM 360kB disks, 3.5" 1440kB disks, 5.25" 1200kB disks, etc.) The size | ||||
| of the disk image will vary depending on the format. | ||||
|  | ||||
| Configuration options you'll want include: | ||||
|  | ||||
|   - `--ibm-sector-id-base=N`: specifies the ID of the first sector; this defaults | ||||
| 	to 1. Some formats (like the Acorn ones) start at 0. This can't be | ||||
| 	autodetected because FluxEngine can't distinguish between a disk which | ||||
| 	starts at sector 1 and a disk which starts at sector 0 but all the sector | ||||
| 	0s are missing. | ||||
|  | ||||
|   - `--ibm-ignore-side-byte=true|false`: each sector header describes the location of the | ||||
| 	sector: sector ID, track and side. Some formats use the wrong side ID, so | ||||
| 	the sectors on side 1 are labelled as belonging to side 0. This causes | ||||
| 	FluxEngine to see duplicate sectors (as it can't distinguish between the | ||||
| 	two sides). This option tells FluxEngine to ignore the side byte completely | ||||
| 	and use the physical side instead. | ||||
|  | ||||
|   - `--ibm-required-sectors=range`: if you know how many sectors to expect per | ||||
| 	track, you can improve reads by telling FluxEngine what to expect here. If | ||||
| 	a track is read and a sector on this list is _not_ present, then FluxEngine | ||||
| 	assumes the read failed and will retry. This avoids the situation where | ||||
| 	FluxEngine can't tell the difference between a sector missing because it's | ||||
| 	bad or a sector missing because it was never written in the first place. If | ||||
| 	sectors are seen outside the range here, it will still be read. You can use | ||||
| 	the same syntax as for track specifiers: e.g. `0-9`, `0,1,2,3`, etc. | ||||
|  | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| FluxEngine can also write IBM scheme disks. Unfortunately the format is | ||||
| incredibly flexible and you need to specify every single parameter, which | ||||
| makes things slightly awkward. | ||||
|  | ||||
| The syntax is: | ||||
|  | ||||
|     fluxengine write ibm -i input.img <options> | ||||
|  | ||||
| The format of `input.img` will vary depending on the kind of disk you're | ||||
| writing, which is configured by the options. There are some presets, which | ||||
| you will almost certainly want to use if possible: | ||||
|  | ||||
|   - `--ibm-preset-720`: a standard 720kB DS DD 3.5" disk, with 80 cylinders, | ||||
|   2 sides, and 9 sectors per track. | ||||
|   - `--ibm-preset-1440`: a standard 1440kB DS HD 3.5" disk, with 80 | ||||
|   cylinders, 2 sides, and 18 sectors per track. | ||||
|  | ||||
| These options simply preset the following, lower-level options. Note that | ||||
| options are processed left to right, so it's possible to use a preset and | ||||
| then change some settings. To see the values for a preset, simply append | ||||
| `--help`. | ||||
|  | ||||
|   - `--ibm-track-length-ms=N`: one disk rotation, in milliseconds. This is used | ||||
|   to determine whether all the data will fit on a track or not. `fluxengine | ||||
|   rpm` will tell you this; it'll be 200 for a normal 3.5" drive and 166 for a | ||||
|   normal 5.25" drive. | ||||
|   - `--ibm-sector-size=N`: the size of a sector, in bytes. Must be a power of | ||||
|   two. | ||||
|   - `--ibm-emit-iam=true|false`: whether to emit the IAM record at the top of | ||||
|   the track. The standard format requires it, but it's ignored by absolutely | ||||
|   everyone and you can fit a bit more data on the disk without it. | ||||
|   - `--ibm-start-sector-id=N`: the sector ID of the first sector. Normally 1, | ||||
|   except for non-standard formats like Acorn's, which use 0. | ||||
|   - `--ibm-use-fm=true|false`: uses FM rather than MFM. | ||||
|   - `--ibm-idam-byte=N`: the sixteen-bit raw bit pattern used for the IDAM ID | ||||
|   byte. Big-endian, clock bit first. | ||||
|   - `--ibm-dam-byte-N`: the sixteen-bit raw bit pattern used for the DAM ID | ||||
|   byte. Big-endian, clock bit first. | ||||
|   - `--ibm-gap0-bytes=N`: the size of gap 0 in bytes (between the start of | ||||
|   the track and the IAM record). | ||||
|   - `--ibm-gap1-bytes=N`: the size of gap 1 in bytes (between the IAM record | ||||
|   and the first sector record). | ||||
|   - `--ibm-gap2-bytes=N`: the size of gap 2 in bytes (between each sector | ||||
|   record and the data record). | ||||
|   - `--ibm-gap3-bytes=N`: the size of gap 3 in bytes (between the data record | ||||
|   and the next sector record). | ||||
|   - `--ibm-sector-skew=0123...`: a string representing the order in which to | ||||
|   write sectors: each character represents on sector, with `0` being the | ||||
|   first (always, regardless of `--ibm-start-sector-id` above). Sectors 10 and | ||||
|   above are represented as latters from `A` up. | ||||
|  | ||||
| Mixed-format disks | ||||
| ------------------ | ||||
|  | ||||
| Some disks, usually those belonging to early CP/M machines, have more than one | ||||
| format on the disk at once. Typically, the first few tracks will be low-density | ||||
| FM encoded and will be read by the machine's ROM; those tracks contain new | ||||
| floppy drive handling code capable of coping with MFM data, and so the rest of | ||||
| the disk will use that, allowing them to store more data. | ||||
|  | ||||
| FluxEngine copes with these fine, but the disk images are a bit weird. If track | ||||
| 0 is FM and contains five sectors, but track 1 is MFM with nine sectors (MFM is | ||||
| more efficient and the sectors are physically smaller, allowing you to get more | ||||
| on), then the resulting image will have nine sectors per track... but track 0 | ||||
| will only contain data in the first five. | ||||
|  | ||||
| This is typically what you want as it makes locating the sectors in the image | ||||
| easier, but emulators will typically require a different format. Please [get | ||||
| in touch](https://github.com/davidgiven/fluxengine/issues/new) if you have | ||||
| specific requirements (nothing's come up yet). Alternatively, you can tell | ||||
| FluxEngine to write a [`.ldbs` | ||||
| file](http://www.seasip.info/Unix/LibDsk/ldbs.html) and then use | ||||
| [libdsk](http://www.seasip.info/Unix/LibDsk/) to convert it to something | ||||
| useful. | ||||
|  | ||||
| One easy option when reading these is to simply read the two sections of the | ||||
| disk into two different image files. | ||||
|  | ||||
| FluxEngine can write these too, but in two different passes with different | ||||
| options. It's possible to assemble a flux file by judicious use of `-D | ||||
| something.flux --merge`, which can then be written in a single pass with | ||||
| `fluxengine writeflux`, but it's usually not worth the bother: just write the | ||||
| boot tracks, then write the data tracks, possibly with a script for automation. | ||||
| @@ -45,22 +45,34 @@ Reading discs | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read mac | ||||
| fluxengine read mac -o mac.diskcopy | ||||
| ``` | ||||
|  | ||||
| You should end up with an `mac.img` which is 1001888 bytes long (for a normal | ||||
| DD disk). If you want the single-sided variety, use `-s :s=0`. | ||||
| You should end up with a `mac.diskcopy` file which is compatible with DiskCopy | ||||
| 4.2, which most Mac emulators support. | ||||
|  | ||||
| **Big warning!** The image may not work in an emulator. Mac disk images are | ||||
| complicated due to the way the tracks are different sizes and the odd sector | ||||
| size. FluxEngine chooses to store them in a simple 524 x 12 x 2 x 80 layout, | ||||
| with holes where missing sectors should be. This was easiest. If anyone can | ||||
| suggest a better way, please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
| **Big warning!** Mac disk images are complicated due to the way the tracks are | ||||
| different sizes and the odd sector size. If you use a normal `.img` file, then | ||||
| FluxEngine will store them in a simple 524 x 12 x 2 x 80 layout, with holes | ||||
| where missing sectors should be; this was easiest, but is unlikely to work with | ||||
| most Mac emulators and other software. In these files, the 12 bytes of | ||||
| metadata _follow_ the 512 bytes of user payload in the sector image. If you | ||||
| don't want it, specify a geometry in the output file with a 512-byte sectore | ||||
| size like `-o mac.img:c=80:h=1:s=12:b=512`. | ||||
|  | ||||
| The 12 bytes of metadata _follow_ the 512 bytes of user payload in the sector | ||||
| image. If you don't want it, specify a geometry in the output file with a | ||||
| 512-byte sectore size like `-o mac.img:c=80:h=1:s=12:b=512`. | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine write mac -i mac.diskcopy | ||||
| ``` | ||||
|  | ||||
| It'll read the DiskCopy 4.2 file and write it out. | ||||
|  | ||||
| The same warning as above applies --- you can use normal `.img` files but it's | ||||
| problematic. Use DiskCopy 4.2 files instead. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| @@ -74,3 +86,7 @@ Useful references | ||||
|  | ||||
|   - [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an | ||||
|     epicly detailed writeup of the Apple II disk format (which is closely related) | ||||
|  | ||||
|   - [The DiskCopy 4.2 | ||||
| 	format](https://www.discferret.com/wiki/Apple_DiskCopy_4.2), described on | ||||
| 	the DiskFerret website. | ||||
|   | ||||
							
								
								
									
										55
									
								
								doc/disk-micropolis.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								doc/disk-micropolis.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| Disk: Micropolis | ||||
| ================ | ||||
|  | ||||
| Micropolis MetaFloppy disks use MFM and hard sectors. They were 100 TPI and | ||||
| stored 315k per side. Each of the 16 sectors contains 266 bytes of "user data," | ||||
| allowing 10 bytes of metadata for use by the operating system. Micropolis DOS | ||||
| (MDOS) used the metadata bytes, but CP/M did not. | ||||
|  | ||||
| Some later systems were Micropolis-compatible and so were also 100 TPI, like | ||||
| the Vector Graphic Dual-Mode Disk Controller which was paired with a Tandon | ||||
| drive. | ||||
|  | ||||
| **Important note:** You _cannot_ read these disks with a normal PC drive, as | ||||
| these drives are 96tpi.The track spacing is determined by the physical geometry | ||||
| of the drive and can't be changed in software. You'll need to get hold of a | ||||
| 100tpi Micropolis drive. Luckily these seem to use the same connector and | ||||
| pinout as a 96tpi PC 5.25" drive. In use they should be identical. | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read micropolis | ||||
| ``` | ||||
|  | ||||
| You should end up with a `micropolis.img` which is 630784 bytes long (for a | ||||
| normal DD disk). The image is written in CHS order, but HCS is generally used | ||||
| by CP/M tools so the image needs to be post-processed. For only half-full disks | ||||
| or single-sided disks, you can use `-s :s=0` to read only one side of the disk | ||||
| which works around the problem. | ||||
|  | ||||
| The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK, | ||||
| and SETSEC, but no function to select the head/side. Double-sided floppies | ||||
| could be represented as having either twice the number of sectors, for CHS, or | ||||
| twice the number of tracks, HCS; the second side's tracks logically followed | ||||
| the first side (e.g., tracks 77-153). Micropolis disks tended to be the latter. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   - [Micropolis 1040/1050 S-100 Floppy Disk Subsystems User's Manual][micropolis1040/1050]. | ||||
|     Section 6, pages 261-266. Documents pre-ECC sector format. Note that the | ||||
|     entire record, starting at the sync byte, is controlled by software | ||||
|  | ||||
|   - [Vector Graphic Dual-Mode Disk Controller Board Engineering Documentation][vectordualmode]. | ||||
|     Section 1.6.2. Documents ECC sector format | ||||
|  | ||||
|   - [AltairZ80 Simulator Usage Manual][altairz80]. Section 10.6. Documents ECC | ||||
|     sector format and VGI file format | ||||
|  | ||||
| [micropolis1040/1050]: http://www.bitsavers.org/pdf/micropolis/metafloppy/1084-01_1040_1050_Users_Manual_Apr79.pdf | ||||
| [vectordualmode]: http://bitsavers.org/pdf/vectorGraphic/hardware/7200-1200-02-1_Dual-Mode_Disk_Controller_Board_Engineering_Documentation_Feb81.pdf | ||||
| [altairz80]: http://www.bitsavers.org/simh.trailing-edge.com_201206/pdf/altairz80_doc.pdf | ||||
							
								
								
									
										46
									
								
								doc/disk-tids990.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								doc/disk-tids990.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| Disk: TI DS990 FD1000 | ||||
| ===================== | ||||
|  | ||||
| The Texas Instruments DS990 was a multiuser modular computing system from 1998, | ||||
| based around the TMS-9900 processor (as used by the TI-99). It had an 8" floppy | ||||
| drive module, the FD1000, which was a 77-track, 288-byte sector FM/MFM system | ||||
| with 26 sectors per track. The encoding scheme was very similar to a simplified | ||||
| version of the IBM scheme, but of course not compatible. A double-sided disk | ||||
| would store a very satisfactory 1126kB of data; here's one at <a | ||||
| href="https://www.old-computers.com/museum/computer.asp?st=1&c=1025">old-computers.com</a>: | ||||
|  | ||||
| <div style="text-align: center"> | ||||
| <a href="https://www.old-computers.com/museum/computer.asp?st=1&c=1025"> | ||||
| <img src="tids990.jpg" style="max-width: 60%" alt="A DS990 at old-computers.com"></a> | ||||
| </div> | ||||
|  | ||||
| FluxEngine will read and write these (but only the DSDD MFM variant). | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read tids990 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `tids990.img` which is 1153152 bytes long. | ||||
|  | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine write tids990 -i tids990.img | ||||
| ``` | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   - [The FD1000 Depot Maintenance | ||||
| 	Manual](http://www.bitsavers.org/pdf/ti/990/disk/2261885-9701_FD1000depotVo1_Jan81.pdf) | ||||
|  | ||||
|  | ||||
| @@ -32,3 +32,11 @@ If you've got a 40-track disk, use `-s :t=0-79x2`. | ||||
|  | ||||
| If you've got a single density disk, use `--read-fm=true`. (Double density is | ||||
| the default.) | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   - [The JV3 file format](https://www.tim-mann.org/trs80/dskspec.html): | ||||
| 	documents the most popular emulator disk image. | ||||
|  | ||||
|   | ||||
							
								
								
									
										71
									
								
								doc/greaseweazle.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								doc/greaseweazle.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| Using the FluxEngine client software with GreaseWeazle hardware | ||||
| =============================================================== | ||||
|  | ||||
| The FluxEngine isn't the only project which does this; another one is the | ||||
| [GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki), a Blue Pill based | ||||
| completely open source solution. This requires more work to set up (or you can | ||||
| buy a prebuilt GreaseWeazle board), but provides completely open source | ||||
| hardware which doesn't require the use of the Cypress Windows-based tools that | ||||
| the FluxEngine does. Luckily, the FluxEngine software supports it | ||||
| out-of-the-box --- just plug it in and nearly everything should work. | ||||
|  | ||||
| I am aware that having _software_ called FluxEngine and _hardware_ called | ||||
| FluxEngine makes things complicated when you're not using the FluxEngine client | ||||
| software with a FluxEngine board, but I'm afraid it's too late to change that | ||||
| now. Sorry. | ||||
|  | ||||
| If you're using Windows | ||||
| ----------------------- | ||||
|  | ||||
| In order to access the GreaseWeazle from Windows, you need to install a WinUSB | ||||
| driver for it. You can do this with the [Zadig](https://zadig.akeo.ie/) | ||||
| program. Download it, plug in the GreaseWeazle, and run it; select Options, | ||||
| List All Devices, and then open the big dropdown box and select the | ||||
| GreaseWeazle. You should see something like this. | ||||
|  | ||||
| <div style="text-align: center"> | ||||
| <img src="zadig.png" style="width:80%" alt="Zadig screenshot"></a> | ||||
| </div> | ||||
|  | ||||
| Ensure that the Driver boxes say `usbser` and `WinUSB`. Then press 'Replace | ||||
| Driver'. Once done, the GreaseWeazle will be visible to the FluxEngine client. | ||||
|  | ||||
| **Important note!** Unfortunately, now, the original GreaseWeazle client won't | ||||
| work --- you can't use both drivers at once. I'm working on this. To switch | ||||
| back to the original driver, for using the GreaseWeazle client software | ||||
| instead, open up Zadig again, go through the same process, but make sure the left Driver box says `WinUSB` and the right one says `USB Serial (CDC)`. Now, when you press 'Replace Driver' the original driver will be restored. | ||||
|  | ||||
| What works | ||||
| ---------- | ||||
|  | ||||
| Supported features with the GreaseWeazle include: | ||||
|  | ||||
|   - simple reading and writing of disks, seeking etc | ||||
|   - erasing disks | ||||
|   - determining disk rotation speed | ||||
|  | ||||
| What doesn't work | ||||
| ----------------- | ||||
|  | ||||
| (I'm still working on this. If you have an urgent need for anything, please | ||||
| [file an issue](https://github.com/davidgiven/fluxengine/issues/new) and I'll | ||||
| see what I can do.) | ||||
|  | ||||
|   - voltage measurement | ||||
|   - hard sectored disks (you can still read these, but you can't use | ||||
| 	`--hard-sector-count`). | ||||
|  | ||||
| Who to contact | ||||
| -------------- | ||||
|  | ||||
| I want to make it clear that the FluxEngine code is _not_ supported by the | ||||
| GreaseWeazle team. If you have any problems, please [contact | ||||
| me](https://github.com/davidgiven/fluxengine/issues/new) and not them. | ||||
|  | ||||
| In addition, the GreaseWeazle release cycle is not synchronised to the | ||||
| FluxEngine release cycle, so it's possible you'll have a version of the | ||||
| GreaseWeazle firmware which is not supported by FluxEngine. Hopefully, it'll | ||||
| detect this and complain. Again, [file an | ||||
| issue](https://github.com/davidgiven/fluxengine/issues/new) and I'll look into | ||||
| it. | ||||
|  | ||||
| @@ -22,19 +22,17 @@ FluxEngine, where a different datapath state machine thingy (the PSoC5LP has | ||||
| 24, all independently programmable) to interpret the bytecodes, generate a | ||||
| stream of pulses to the disk. | ||||
|  | ||||
| The bytecode format represents an interval between pulses as a byte, a pulse | ||||
| as a byte, and the index hole as a byte. Timer overflows are handled by | ||||
| sending multiple intervals in a row. However, the USB transport applies a | ||||
| simple compression system to this in order to get the USB bandwidth down to | ||||
| something manageable. | ||||
| The bytecode format is very simple with a six-bit interval since the previous | ||||
| event in the lower six bits and the top two bits are set of a pulse or an index | ||||
| hole (or both, or neither). | ||||
|  | ||||
| An HD floppy has a nominal pulse frequency of 500kHz, and we use a sample | ||||
| clock of 12MHz (every 83ns). This means that our 500kHz pulses will have an | ||||
| average interval of 24. This gives us more than enough resolution. At this | ||||
| speed, in the 200ms that a 3.5" disk takes to rotate, we will see about | ||||
| 100,000 pulses. Each one is encoded as two bytes, one for the interval and | ||||
| one to generate the pulse; so that revolution generates 200kB of data. | ||||
| (Extremely approximately. The actual figure is less.) | ||||
| 100,000 pulses. Each one is encoded as a single byte; so that revolution | ||||
| generates 100kB of data.  (Extremely approximately. The actual figure varies | ||||
| depending on what data is stored on the disk.) | ||||
|  | ||||
| (The clock needs to be absolutely rock solid or we get jitter which makes the | ||||
| data difficult to analyse, so 12 was chosen to be derivable from the | ||||
| @@ -59,53 +57,36 @@ Some useful and/or interesting numbers: | ||||
|  | ||||
| ## Why don't I use an Arduino / STM32 / ESP32 / Raspberry Pi / etc? | ||||
|  | ||||
| I've got a _lot_ of questions on this, and multiple Github issues of people | ||||
| -I've got a _lot_ of questions on this, and multiple Github issues of people | ||||
| debating it. It's complicated, but it's essentially a tradeoff between speed | ||||
| and complexity. | ||||
| and complexity.- | ||||
|  | ||||
| FluxEngine's read process involves generating a lot of data using a fairly | ||||
| brute force sampling approach --- about 150kB per disk revolution, and | ||||
| sometimes it needs to record multiple revolutions. Most microcontrollers | ||||
| don't have enough RAM to buffer this, so instead I have to stream it over USB | ||||
| back to the host PC in real time. The disk won't wait, so I need to stream data faster | ||||
| than the disk is producing it: the total is about 800kB/s. | ||||
| **Update as of 2020-01-08:** | ||||
|  | ||||
| Handling USB is pretty CPU-hungry, so my candidate microntroller has to be | ||||
| able to cope with the ruinously strict real-time requirements of the | ||||
| sampler's 12MHz clock as well as keeping up with 13,000 USB interrupts a | ||||
| second (one for each 64-byte frame) in order to transfer the data. | ||||
| Right. Well. | ||||
|  | ||||
| The Atmels and STM32s I found were perfectly capable of doing the real-time | ||||
| sampling, using hand-tool assembly, but I very much doubt whether they could | ||||
| do the USB streaming as well (although I want to move away from the Cypress | ||||
| onto something less proprietary and easier to source, so I'd like to be | ||||
| proven wrong here). | ||||
| This section used to have a long explanation as to why these other platforms | ||||
| were unsuitable --- essentially, they're generally missing out on either the | ||||
| realtimeness to sample the data correctly (Raspberry Pi) or enough CPU to | ||||
| stream the data over USB while also sampling it (Arduino). | ||||
|  | ||||
| The Raspberry Pi easily has enough processing power and memory, but it's also | ||||
| got terrible GPIO pin read performance --- [about | ||||
| 1kHz](https://raspberrypi.stackexchange.com/questions/9646/how-fast-is-gpiodma-multi-i2s-input/10197#10197). | ||||
| That's a long way from the 12MHz I need. | ||||
| This is correct, but it turns out that the STM32 has some built-in features | ||||
| which support the FluxEngine's use case almost exactly: you can configure the | ||||
| DMA engine to sample the interval between pulses and write them directly into | ||||
| memory, and you can configure the PWM engine the read samples from memory and | ||||
| use them to time pulses to the output. There's a bit less functionality, so you | ||||
| can't do things like measure the signal voltages, and they're less convenient | ||||
| as you need an adapter cable or board, but this will allow you to replicate the | ||||
| FluxEngine hardware on a $2 Blue Pill. | ||||
|  | ||||
| The PSoC5LP part I'm using has enough CPU to handle the USB side of things, | ||||
| and it _also_ has a whole set of FPGA-like soft programmable features, | ||||
| including 24 mini-ALU systems that are ideally suited to exactly this kind of | ||||
| sampling. I can read the disk and generate the byte stream describing the | ||||
| flux pattern entirely in 'hardware', without involving the main CPU at all. | ||||
| This is then DMAed directly into a set of ring buffers read for the USB | ||||
| system to pick up and relay back to the PC. It's incredibly simple and works | ||||
| well. (The same applies to writing flux back onto the disk.) | ||||
| I am _not_ planning on replacing the PSoC5 with a Blue Pill, because someone | ||||
| already has: [the GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki) is | ||||
| a completely open source firmware package which will read and write Supercard | ||||
| Pro files via a standard Blue Pill or via a prebuilt board. It's supported by | ||||
| the FluxEngine client software, and you should, mostly, be able to use | ||||
| GreaseWeazle hardware interchangeably with FluxEngine hardware. See the | ||||
| [dedicated page](greaseweazle.md) for more information. | ||||
|  | ||||
| The development board I'm using, the | ||||
| [CY8CKIT-059](https://www.cypress.com/documentation/development-kitsboards/cy8ckit-059-psoc-5lp-prototyping-kit-onboard-programmer-and), | ||||
| also has another big advantage: it's the right shape. It's got 17 holes in a | ||||
| row connected to GPIO pins, and it's a native 5V part, which means I can just | ||||
| connect a floppy drive connector directly to the board without needing to | ||||
| build any hardware. No adapter board, no level shifting, no special cable, | ||||
| nothing. This makes the FluxEngine hardware incredibly easy to assemble, | ||||
| which therefore means cheap. | ||||
|  | ||||
| Speaking of which, the CY8CKIT-059 is $10. (Before shipping, which is | ||||
| admittedly expensive.) | ||||
|  | ||||
| ### Some useful links | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								doc/tids990.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/tids990.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 23 KiB | 
							
								
								
									
										248
									
								
								doc/using.md
									
									
									
									
									
								
							
							
						
						
									
										248
									
								
								doc/using.md
									
									
									
									
									
								
							| @@ -1,78 +1,8 @@ | ||||
| Using a FluxEngine | ||||
| ================== | ||||
|  | ||||
| So you've [built the hardware](building.md)! What now? | ||||
|  | ||||
| ## Connecting it up | ||||
|  | ||||
| In order to do anything useful, you have to plug it in to a floppy disk drive (or two). | ||||
|  | ||||
|   1. Plug the motherboard end of your floppy disk cable into the FluxEngine. | ||||
|       | ||||
|      The **red stripe goes on the right**. The **lower set of | ||||
|      holes connect to the board**. (Pin 2 of the connector needs to connect | ||||
|      to pin 2.7 on the board.) | ||||
|  | ||||
|      If you're using header pins, the upper row of holes in the connector | ||||
|      should overhang the edge of the board. If you're using a floppy drive | ||||
|      motherboard connector, you're golden, of course (unless you have one of | ||||
|      those annoying unkeyed cables, or have accidentally soldered the | ||||
|      connector on in the wrong place --- don't laugh, I've done it.) | ||||
|  | ||||
|   2. Plug the drive end of your floppy disk cable into the drive (or drives). | ||||
|  | ||||
|      Floppy disk cables typically have [two pairs of floppy disk drive | ||||
|      connectors with a twist between | ||||
|      them](http://www.nullmodem.com/Floppy.htm). (Each pair has one connector | ||||
|      for a 3.5" drive and a different one for a 5.25" drive.) (Some cables | ||||
|      are cheap and just have the 3.5" connectors. Some are _very_ cheap and | ||||
|      have a single 3.5" connector, after the twist.) | ||||
|       | ||||
|      FluxEngine uses, sadly, non-standard disk numbering (there are reasons). | ||||
|      Drive 0 is the one nearest the motherboard; that is, before the twist. | ||||
|      Drive 1 is the one at the end of the cable; that is, after the twist. | ||||
|      Drive 0 is the default. If you only have one drive, remember to plug the | ||||
|      drive into the connector _before_ the twist. (Or use `-s :d=1` to select | ||||
|      drive 1 when working with disks.) | ||||
|  | ||||
|   3. **Important.** Make sure that no disk you care about is in the drive. | ||||
|      (Because if your wiring is wrong and a disk is inserted, you'll corrupt it.) | ||||
|  | ||||
|   4. Connect the floppy drive to power. Nothing should happen. If you've | ||||
|      connected something in backwards, you'll see the drive light up, the | ||||
|      motor start, and if you didn't take the disk out, one track has just | ||||
|      been wiped. If this happens, check your wiring. | ||||
|  | ||||
|   5. Connect the FluxEngine to your PC via USB --- using the little socket on | ||||
|      the board, not the big programmer plug. | ||||
|  | ||||
|   6. Insert a scratch disk and do `fluxengine rpm` from the shell. The motor | ||||
|      should work and it'll tell you that the disk is spinning at about 300 | ||||
|      rpm for a 3.5" disk, or 360 rpm for a 5.25" disk. If it doesn't, please | ||||
|      [get in touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|  | ||||
|   7. Do `fluxengine test bulktransport` from the shell. It'll measure your USB | ||||
|      bandwidth. Ideally you should be getting above 900kB/s. FluxEngine needs | ||||
|      about 850kB/s, so if you're getting less than this, try a different USB | ||||
|      port. | ||||
|  | ||||
|   8. Insert a standard PC formatted floppy disk into the drive (probably a good | ||||
|      idea to remove the old disk first). Then do `fluxengine read ibm`. It | ||||
|      should read the disk, emitting copious diagnostics, and spit out an | ||||
|      `ibm.img` file containing the decoded disk image (either 1440kB or 720kB | ||||
|      depending). | ||||
|  | ||||
|   9. Profit! | ||||
|  | ||||
| ## Bonus hardware features | ||||
|  | ||||
| For advanced users, the board has a few extra signals which are useful for special purposes. | ||||
|  | ||||
|   - Pin 3[0] produces short pulses every 200ms. This is useful for spoofing | ||||
|     index signals to 300 RPM drives; for example, to read flippy disks. | ||||
|  | ||||
|   - Pin 3[1] is the same, but produces the pulses every 166ms; this works with | ||||
|     360 RPM drives. | ||||
| So you've [built the hardware](building.md), programmed and tested it! What | ||||
| now? | ||||
|  | ||||
| ## The programs | ||||
|  | ||||
| @@ -81,10 +11,48 @@ moving too quickly for the documentation to keep up. It does respond to | ||||
| `--help` or `help` depending on context. There are some common properties, | ||||
| described below. | ||||
|  | ||||
| ### Core concepts | ||||
|  | ||||
| FluxEngine fundamentally takes file system images and puts them on disk; or | ||||
| reads the disk and produces a file system image. | ||||
|  | ||||
| A file system image typically has the extension `.img`. It contains a | ||||
| sector-by-sector record of the _decoded_ data on the disk. For example, on a | ||||
| disk with 512 byte sectors, one sector will occupy 512 bytes. These are | ||||
| typically what you want in everyday life. | ||||
|  | ||||
| FluxEngine can also record the raw magnetic data on the disk into a file, which | ||||
| we call a _flux file_. This contains all the low-level data which the drive | ||||
| produced as the disk rotated. These are continuous streams of samples from the | ||||
| disk and are completely useless in day-to-day life. FluxEngine uses its own | ||||
| format for this, `.flux`, although it's capable of limited interchange with | ||||
| Kryoflux, Supercard Pro and Catweasel files. A flux file will typically contain | ||||
| from 80 to 150 kilobytes of data per track. | ||||
|  | ||||
| In general, FluxEngine can use either a real disk or a flux file | ||||
| interchangeably: you can specify either at (very nearly) any time. A very | ||||
| common workflow is to read a disk to a flux file, and then reread from the flux | ||||
| file while changing the decoder options, to save disk wear. It's also much faster. | ||||
|  | ||||
| ### Connecting it up | ||||
|  | ||||
| To use, simply plug your FluxEngine into your computer and run the client. If a | ||||
| single device is plugged in, it will be automatically detected and used. | ||||
|  | ||||
| If _more_ than one device is plugged in, you need to specify which one to use | ||||
| with the `--device` parameter, which takes the device serial number as a | ||||
| parameter.  You can find out the serial numbers by running the command without | ||||
| the `--device` parameter, and if more than one device is attached they will be | ||||
| listed. The serial number is also shown whenever a connection is made. | ||||
|  | ||||
| You _can_ work with more than one FluxEngine at the same time, using different | ||||
| invocations of the client; but be careful of USB bandwidth. If the devices are | ||||
| connected via the same hub, the bandwidth will be shared. | ||||
|  | ||||
| ### Source and destination specifiers | ||||
|  | ||||
| When reading from or writing to _a disk_ (or a file pretending to be a disk), | ||||
| use the `--source` (`-s`) and `--dest` (`-d`) options to tell FluxEngine | ||||
| When reading from or writing _flux_ (either from or to a real disk, or a flux | ||||
| file), use the `--source` (`-s`) and `--dest` (`-d`) options to tell FluxEngine | ||||
| which bits of the disk you want to access. These use a common syntax: | ||||
|  | ||||
| ``` | ||||
| @@ -94,7 +62,7 @@ fluxengine read ibm -s fakedisk.flux:t=0-79:s=0 | ||||
|   - To access a real disk, leave out the filename (so `:t=0-79:s=0`). | ||||
|  | ||||
|   - To access only some tracks, use the `t=` modifier. To access only some | ||||
|     sides, use the `s=` modifier. To change drives, use `d=`. | ||||
|     sides, use the `s=` modifier. | ||||
|  | ||||
|   - Inside a modifier, you can use a comma separated list of ranges. So | ||||
|     `:t=0-3` and `:t=0,1,2,3` are equivalent. | ||||
| @@ -120,18 +88,15 @@ If you _don't_ specify a modifier, you'll get the default, which should be | ||||
| sensible for the command you're using. | ||||
|  | ||||
| **Important note:** FluxEngine _always_ uses zero-based units (even if the | ||||
| *disk format says otherwise). | ||||
| disk format says otherwise). | ||||
|  | ||||
| ### Input and output specifiers | ||||
|  | ||||
| These use a very similar syntax to the source and destination specifiers | ||||
| (because they're based on the same microformat library!) but are used for | ||||
| input and output _images_: i.e. nicely lined up arrays of sectors which you | ||||
| can actually do something with. | ||||
|  | ||||
| Use `--input` (`-i`) or `--output` (`-o`) as appropriate to tell FluxEngine | ||||
| where you want to read from or write to. The actual format is autodetected | ||||
| based on the extension: | ||||
| When reading or writing _file system images_, use the `--input` (`-i`) and | ||||
| `--output` (`-o`) options to specify the file and file format. These use a very | ||||
| similar syntax to the source and destination specifiers (because they're based | ||||
| on the same microformat library!) but with different specifiers. Also, the | ||||
| exact format varies according to the extension: | ||||
|  | ||||
|   - `.img` or `.adf`: raw sector images in CHS order. Append | ||||
|     `:c=80:h=2:s=9:b=512` to set the geometry; that specifies 80 cylinders, 2 | ||||
| @@ -167,6 +132,14 @@ based on the extension: | ||||
|     FluxEngine's D64 support is currently limited to write only. It will work | ||||
|     with up to 40 logical tracks. | ||||
|  | ||||
|   - `.diskcopy`: a Macintosh DiskCopy 4.2 file. This is a special-purpose | ||||
| 	format due to the weird layout of Mac GCR disks, but it can also support | ||||
| 	720kB and 1440kB IBM disks (although there's no real benefit). | ||||
|  | ||||
|   - `.jv3`: a disk image format mainly used by the TRS-80. These images can be | ||||
| 	read, but not yet written. You only get the data; the density and DAM bits | ||||
| 	are ignored. | ||||
|  | ||||
| ### High density disks | ||||
|  | ||||
| High density disks use a different magnetic medium to low and double density | ||||
| @@ -191,17 +164,23 @@ here.](http://www.retrotechnology.com/herbs_stuff/guzis.html) | ||||
| These flags apply to many operations and are useful for modifying the overall | ||||
| behaviour. | ||||
|  | ||||
|   - `--revolutions=X`: when reading, spin the disk X times. Many formats | ||||
|   require `--revolutions=2` (which should happen automatically); or you can | ||||
|   increase the number to sample more data. | ||||
|   - `--revolutions=X`: when reading, spin the disk X times. X can be a floating | ||||
| 	point number. The default is usually 1.25. Some formats default to 1. | ||||
| 	Increasing the number will sample more data, and can be useful on dubious | ||||
| 	disks to try and get a better read. | ||||
|  | ||||
|   - `--sync-with-index=true|false`: wait for an index pulse before starting to | ||||
| 	read the disk. (Ignored for write operations.) By default FluxEngine | ||||
| 	doesn't, as it makes reads faster, but when diagnosing disk problems it's | ||||
| 	helpful to have all your data start at the same place each time. | ||||
|  | ||||
|   - `--index-source=X`, `--write-index-source=X`: set the source of index | ||||
|   pulses when reading or writing respectively. This is for use with drives | ||||
|   which don't produce index pulse data. Use 0 to get index pulses from the | ||||
|   drive, 1 to fake 300RPM pulses, or 2 to fake 360RPM pulses. Note this has | ||||
|   no effect on the _drive_, so it doesn't help with flippy disks, but is | ||||
|   useful for using very old drives with FluxEngine itself. If you use this | ||||
|   option, then any index marks in the sampled flux are, of course, garbage. | ||||
| 	pulses when reading or writing respectively. This is for use with drives | ||||
| 	which don't produce index pulse data. Use 0 to get index pulses from the | ||||
| 	drive, 1 to fake 300RPM pulses, or 2 to fake 360RPM pulses. Note this has | ||||
| 	no effect on the _drive_, so it doesn't help with flippy disks, but is | ||||
| 	useful for using very old drives with FluxEngine itself. If you use this | ||||
| 	option, then any index marks in the sampled flux are, of course, garbage. | ||||
|  | ||||
| ### The commands | ||||
|  | ||||
| @@ -211,60 +190,57 @@ installed anywhere and after building you'll find them in the `.obj` | ||||
| directory. | ||||
|  | ||||
|   - `fluxengine erase`: wipes (all or part of) a disk --- erases it without | ||||
|   writing a pulsetrain. | ||||
| 	writing a pulsetrain. | ||||
|  | ||||
|   - `fluxengine inspect`: dumps the raw pulsetrain / bitstream to stdout. | ||||
|   Mainly useful for debugging. | ||||
| 	Mainly useful for debugging. | ||||
|  | ||||
|   - `fluxengine read *`: reads various formats of disk. See the per-format | ||||
|   documentation linked from the table above. These all take an optional | ||||
|   `--write-flux` option which will cause the raw flux to be written to the | ||||
|   specified file. There are various `--dump` options for showing raw data | ||||
|   during the decode process. | ||||
| 	documentation linked from the table [in the index page](../README.md). | ||||
| 	These all take an optional `--write-flux` option which will cause the raw | ||||
| 	flux to be written to the specified file as well as the normal decode. | ||||
| 	There are various `--dump` options for showing raw data during the decode | ||||
| 	process, and `--write-csv` will write a copious CSV report of the state of | ||||
| 	every sector in the file in machine-readable format. | ||||
|  | ||||
|   - `fluxengine write *`: writes various formats of disk. Again, see the | ||||
|   per-format documentation above. | ||||
| 	per-format documentation [in the index page](../README.md). | ||||
|  | ||||
|   - `fluxengine writeflux`: writes raw flux files. This is much less useful | ||||
|   than you might think: you can't write flux files read from a disk to | ||||
|   another disk. (See the [FAQ](faq.md) for more information.) It's mainly | ||||
|   useful for flux files synthesised by the other `fluxengine write` commands. | ||||
| 	than you might think: you can't reliably write flux files read from a disk | ||||
| 	to another disk. (See the [FAQ](faq.md) for more information.) It's mainly | ||||
| 	useful for flux files synthesised by the other `fluxengine write` commands. | ||||
|  | ||||
|   - `fluxengine writetestpattern`: writes regular pulses (at a configurable | ||||
|   interval) to the disk. Useful for testing drive jitter, erasing disks in a | ||||
|   more secure fashion, or simply debugging. Goes well with `fluxengine | ||||
|   inspect`. | ||||
| 	interval) to the disk. Useful for testing drive jitter, erasing disks in a | ||||
| 	more secure fashion, or simply debugging. Goes well with `fluxengine | ||||
| 	inspect`. | ||||
|  | ||||
|   - `fluxengine rpm`: measures the RPM of the drive (requires a disk in the | ||||
|   drive). Mainly useful for testing. | ||||
| 	drive). Mainly useful for testing. | ||||
|  | ||||
|   - `fluxengine seek`: moves the head. Mainly useful for finding out whether | ||||
|   your drive can seek to track 82. (Mine can't.) | ||||
| 	your drive can seek to track 82. (Mine can't.) | ||||
|  | ||||
|   - `fluxengine test bulktransport`: measures your USB throughput. You need | ||||
|   about 600kB/s for FluxEngine to work. You don't need a disk in the drive | ||||
|   for this one. | ||||
|   - `fluxengine test bandwidth`: measures your USB throughput.  You don't need | ||||
| 	a disk in the drive for this one. | ||||
|  | ||||
|   - `fluxengine test voltages`: measures your FDD bus signal voltages, which | ||||
|   is useful for testing for termination issues. | ||||
|   - `fluxengine test voltages`: measures your FDD bus signal voltages, which is | ||||
| 	useful for testing for termination issues. | ||||
|  | ||||
|   - `fluxengine upgradefluxfile`: occasionally I need to upgrade the flux | ||||
|   file format in a non-backwards-compatible way; this tool will upgrade flux | ||||
|   files to the new format. | ||||
|   - `fluxengine upgradefluxfile`: occasionally I need to upgrade the flux file | ||||
| 	format in a non-backwards-compatible way; this tool will upgrade flux files | ||||
| 	to the new format. | ||||
|  | ||||
|   - `fluxengine convert`: converts flux files from various formats to various | ||||
|   other formats. You can use this to convert Catweasel flux files to | ||||
|   FluxEngine's native format, FluxEngine flux files to various other formats | ||||
|   useful for debugging (including VCD which can be loaded into | ||||
|   [sigrok](http://sigrok.org)), and bidirectional conversion to and from | ||||
|   Supercard Pro `.scp` format. | ||||
|   - `fluxengine convert`: converts files from various formats to various other | ||||
| 	formats. The main use of this is probably `fluxengine convert image`, which | ||||
| 	will convert a disk image from one format to another. | ||||
|  | ||||
|   **Important SCP note:** import (`fluxengine convert scptoflux`) should be | ||||
|   fairly robust, but export (`fluxengine convert fluxtoscp`) should only be | ||||
|   done with great caution as FluxEngine files contain features which can't be | ||||
|   represented very well in `.scp` format and they're probably pretty dubious. | ||||
|   As ever, please [get in | ||||
|   touch](https://github.com/davidgiven/fluxengine/issues/new) with any reports. | ||||
| 	There are also subcommands for converting Catweasel flux files to | ||||
| 	FluxEngine's native format, FluxEngine flux files to various other formats | ||||
| 	useful for debugging (including VCD which can be loaded into | ||||
| 	[sigrok](http://sigrok.org)), and bidirectional conversion to and from | ||||
| 	Supercard Pro `.scp` format. | ||||
|  | ||||
| Commands which normally take `--source` or `--dest` get a sensible default if | ||||
| left unspecified. `fluxengine read ibm` on its own will read drive 0 and | ||||
| @@ -290,8 +266,10 @@ disk). For a 5.25" disk, use `--visualiser-period=166`. | ||||
| Supplied with FluxEngine, but not part of FluxEngine, are some little tools I | ||||
| wrote to do useful things. These are built alongside FluxEngine. | ||||
|  | ||||
|   - `brother120tool`: extracts files from a 120kB Brother filesystem image. | ||||
|  | ||||
|   - `brother120tool`, `brother240tool`: does things to Brother word processor | ||||
| 	disks. These are [documented on the Brother disk format | ||||
| 	page](disk-brother.md). | ||||
|    | ||||
| ## The recommended workflow | ||||
|  | ||||
| So you've just received, say, a huge pile of old Brother word processor disks | ||||
| @@ -300,13 +278,13 @@ containing valuable historical data, and you want to read them. | ||||
| Typically I do this: | ||||
|  | ||||
| ``` | ||||
| $ fluxengine read brother -s :d=0 -o brother.img --write-flux=brother.flux --write-svg=brother.svg | ||||
| $ fluxengine read brother -s :d=0 -o brother.img --write-flux=brother.flux --overwrite --write-svg=brother.svg | ||||
| ``` | ||||
|  | ||||
| This will read the disk in drive 0 and write out a filesystem image. It'll | ||||
| also copy the flux to brother.flux and write out an SVG visualisation. If I | ||||
| then need to tweak the settings, I can rerun the decode without having to | ||||
| physically touch the disk like this: | ||||
| This will read the disk in drive 0 and write out a filesystem image. It'll also | ||||
| copy the flux to `brother.flux` (replacing any old one) and write out an SVG | ||||
| visualisation. If I then need to tweak the settings, I can rerun the decode | ||||
| without having to physically touch the disk like this: | ||||
|  | ||||
| ``` | ||||
| $ fluxengine read brother -s brother.flux -o brother.img --write-svg=brother.svg | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								doc/zadig.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/zadig.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										88
									
								
								lib/bytes.cc
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								lib/bytes.cc
									
									
									
									
									
								
							| @@ -1,7 +1,6 @@ | ||||
| #include "globals.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include "common/crunch.h" | ||||
| #include <fstream> | ||||
| #include <zlib.h> | ||||
|  | ||||
| @@ -42,6 +41,12 @@ Bytes::Bytes(const uint8_t* ptr, size_t len): | ||||
|     _high(len) | ||||
| {} | ||||
|  | ||||
| Bytes::Bytes(const std::string& s): | ||||
| 	_data(createVector((const uint8_t*)&s[0], s.size())), | ||||
| 	_low(0), | ||||
| 	_high(s.size()) | ||||
| {} | ||||
|  | ||||
| Bytes::Bytes(std::initializer_list<uint8_t> data): | ||||
|     _data(createVector(data)), | ||||
|     _low(0), | ||||
| @@ -147,6 +152,28 @@ Bytes Bytes::slice(unsigned start, unsigned len) const | ||||
|     } | ||||
| } | ||||
|  | ||||
| Bytes Bytes::slice(unsigned start) const | ||||
| { | ||||
| 	int len = 0; | ||||
| 	if (start < size()) | ||||
| 		len = size() - start; | ||||
| 	return slice(start, len); | ||||
| } | ||||
|  | ||||
| std::vector<bool> Bytes::toBits() const | ||||
| { | ||||
| 	std::vector<bool> bits; | ||||
| 	for (uint8_t byte : *this) | ||||
| 	{ | ||||
| 		for (int i=0; i<8; i++) | ||||
| 		{ | ||||
| 			bits.push_back(byte & 0x80); | ||||
| 			byte <<= 1; | ||||
| 		} | ||||
| 	} | ||||
| 	return bits; | ||||
| } | ||||
|  | ||||
| uint8_t toByte( | ||||
|     std::vector<bool>::const_iterator start, | ||||
|     std::vector<bool>::const_iterator end) | ||||
| @@ -227,60 +254,6 @@ Bytes Bytes::decompress() const | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes Bytes::crunch() const | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     Bytes outputBuffer(1024*1024); | ||||
|  | ||||
|     crunch_state_t cs = {}; | ||||
|     cs.inputptr = begin(); | ||||
|     cs.inputlen = size(); | ||||
|  | ||||
|     do | ||||
|     { | ||||
|         cs.outputptr = outputBuffer.begin(); | ||||
|         cs.outputlen = outputBuffer.size(); | ||||
|  | ||||
|         ::crunch(&cs); | ||||
|         bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen); | ||||
|     } | ||||
|     while (cs.inputlen != 0); | ||||
|     cs.outputptr = outputBuffer.begin(); | ||||
|     cs.outputlen = outputBuffer.size(); | ||||
|     donecrunch(&cs); | ||||
|     bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen); | ||||
|  | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes Bytes::uncrunch() const | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     Bytes outputBuffer(1024*1024); | ||||
|  | ||||
|     crunch_state_t cs = {}; | ||||
|     cs.inputptr = begin(); | ||||
|     cs.inputlen = size(); | ||||
|  | ||||
|     do | ||||
|     { | ||||
|         cs.outputptr = outputBuffer.begin(); | ||||
|         cs.outputlen = outputBuffer.size(); | ||||
|  | ||||
|         ::uncrunch(&cs); | ||||
|         bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen); | ||||
|     } | ||||
|     while (cs.inputlen != 0); | ||||
|     cs.outputptr = outputBuffer.begin(); | ||||
|     cs.outputlen = outputBuffer.size(); | ||||
|     doneuncrunch(&cs); | ||||
|     bw += outputBuffer.slice(0, outputBuffer.size() - cs.outputlen); | ||||
|  | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| void Bytes::writeToFile(const std::string& filename) const | ||||
| { | ||||
|     std::ofstream f(filename, std::ios::out | std::ios::binary); | ||||
| @@ -291,6 +264,11 @@ void Bytes::writeToFile(const std::string& filename) const | ||||
|     f.close(); | ||||
| } | ||||
|  | ||||
| void Bytes::writeTo(std::ostream& stream) const | ||||
| { | ||||
| 	stream.write((const char*) cbegin(), size()); | ||||
| } | ||||
|  | ||||
| ByteReader Bytes::reader() const | ||||
| { | ||||
|     return ByteReader(*this); | ||||
|   | ||||
							
								
								
									
										20
									
								
								lib/bytes.h
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								lib/bytes.h
									
									
									
									
									
								
							| @@ -1,6 +1,8 @@ | ||||
| #ifndef BYTES_H | ||||
| #define BYTES_H | ||||
|  | ||||
| #include <string.h> | ||||
|  | ||||
| class ByteReader; | ||||
| class ByteWriter; | ||||
|  | ||||
| @@ -10,6 +12,7 @@ public: | ||||
|     Bytes(); | ||||
|     Bytes(unsigned size); | ||||
|     Bytes(const uint8_t* ptr, size_t len); | ||||
|     Bytes(const std::string& data); | ||||
|     Bytes(std::initializer_list<uint8_t> data); | ||||
|     Bytes(std::shared_ptr<std::vector<uint8_t>> data); | ||||
|     Bytes(std::shared_ptr<std::vector<uint8_t>> data, unsigned start, unsigned end); | ||||
| @@ -38,6 +41,8 @@ public: | ||||
|     uint8_t* begin()              { checkWritable(); return &(*_data)[_low]; } | ||||
|     uint8_t* end()                { checkWritable(); return &(*_data)[_high]; } | ||||
|  | ||||
| 	operator const std::string () const { return std::string(cbegin(), cend()); } | ||||
|  | ||||
|     void boundsCheck(unsigned pos) const; | ||||
|     void checkWritable(); | ||||
|     void adjustBounds(unsigned pos); | ||||
| @@ -47,16 +52,17 @@ public: | ||||
|     { resize(0); return *this; } | ||||
|  | ||||
|     Bytes slice(unsigned start, unsigned len) const; | ||||
|     Bytes slice(unsigned start) const; | ||||
|     Bytes swab() const; | ||||
|     Bytes compress() const; | ||||
|     Bytes decompress() const; | ||||
|     Bytes crunch() const; | ||||
|     Bytes uncrunch() const; | ||||
| 	std::vector<bool> toBits() const; | ||||
|  | ||||
|     ByteReader reader() const; | ||||
|     ByteWriter writer(); | ||||
|  | ||||
|     void writeToFile(const std::string& filename) const; | ||||
| 	void writeTo(std::ostream& stream) const; | ||||
|  | ||||
| private: | ||||
|     std::shared_ptr<std::vector<uint8_t>> _data; | ||||
| @@ -270,6 +276,16 @@ public: | ||||
|  | ||||
|     ByteWriter& operator += (std::istream& stream); | ||||
|  | ||||
| 	ByteWriter& append(const char* data) | ||||
| 	{ | ||||
| 		return *this += Bytes((const uint8_t*)data, strlen(data)); | ||||
| 	} | ||||
|  | ||||
| 	ByteWriter& append(const std::string& data) | ||||
| 	{ | ||||
| 		return *this += Bytes(data); | ||||
| 	} | ||||
|  | ||||
|     ByteWriter& append(const Bytes data) | ||||
|     { | ||||
|         return *this += data; | ||||
|   | ||||
| @@ -1,86 +0,0 @@ | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include "crunch.h" | ||||
|  | ||||
| void crunch(crunch_state_t* state) | ||||
| { | ||||
|     while (state->inputlen && state->outputlen) | ||||
|     { | ||||
|         uint8_t data = *state->inputptr++; | ||||
|         state->inputlen--; | ||||
|  | ||||
|         if (data & 0x80) | ||||
|         { | ||||
|             state->fifo = (state->fifo << 2) | 2 | (data & 1); | ||||
|             state->fifolen += 2; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             state->fifo = (state->fifo << 8) | data; | ||||
|             state->fifolen += 8; | ||||
|         } | ||||
|  | ||||
|         if (state->fifolen >= 8) | ||||
|         { | ||||
|             data = state->fifo >> (state->fifolen - 8); | ||||
|             *state->outputptr++ = data; | ||||
|             state->outputlen--; | ||||
|             state->fifolen -= 8; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void donecrunch(crunch_state_t* state) | ||||
| { | ||||
|     if (state->fifolen > 0) | ||||
|     { | ||||
|         uint8_t b = 0; | ||||
|         state->inputptr = &b; | ||||
|         state->inputlen = 1; | ||||
|         crunch(state); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void uncrunch(crunch_state_t* state) | ||||
| { | ||||
|     while (state->inputlen && state->outputlen) | ||||
|     { | ||||
|         if (state->fifolen < 8) | ||||
|         { | ||||
|             if (state->inputlen) | ||||
|             { | ||||
|                 state->fifo = (state->fifo << 8) | *state->inputptr++; | ||||
|                 state->inputlen--; | ||||
|                 state->fifolen += 8; | ||||
|             } | ||||
|             else | ||||
|                 state->fifo <<= 8; | ||||
|         } | ||||
|  | ||||
|         uint8_t data = state->fifo >> (state->fifolen - 8); | ||||
|         if (data & 0x80) | ||||
|         { | ||||
|             data = ((data >> 6) & 0x01) | 0x80; | ||||
|             state->fifolen -= 2; | ||||
|         } | ||||
|         else | ||||
|             state->fifolen -= 8; | ||||
|  | ||||
|         if (data) | ||||
|         { | ||||
|             *state->outputptr++ = data; | ||||
|             state->outputlen--; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void doneuncrunch(crunch_state_t* state) | ||||
| { | ||||
|     if (state->fifolen > 0) | ||||
|     { | ||||
|         uint8_t b = 0; | ||||
|         state->inputptr = &b; | ||||
|         state->inputlen = 1; | ||||
|         uncrunch(state); | ||||
|     } | ||||
| } | ||||
| @@ -1,45 +0,0 @@ | ||||
| #ifndef CRUNCH_H | ||||
| #define CRUNCH_H | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
| /* To save bandwidth, we compress the byte stream from the sampler when | ||||
|  * sending it over USB. The encoding used is: | ||||
|  *  | ||||
|  * 0nnn.nnnn: value 0x00..0x7f | ||||
|  * 1n       : value 0x80|n | ||||
|  * | ||||
|  * The end of the buffer is terminated with zeroes, which are ignored | ||||
|  * (not written to the output). | ||||
|  *  | ||||
|  * This saves ~40%, which gets us in under the bandwidth cap. | ||||
|  */ | ||||
|      | ||||
| typedef struct crunch_state_t | ||||
| { | ||||
|     const uint8_t* inputptr; | ||||
|     uint32_t inputlen; | ||||
|     uint8_t* outputptr; | ||||
|     uint32_t outputlen; | ||||
|     uint16_t fifo; | ||||
|     uint8_t fifolen; | ||||
| } | ||||
| crunch_state_t; | ||||
|  | ||||
| /* Crunches as much as possible and then stops. */ | ||||
| extern void crunch(crunch_state_t* state); | ||||
| extern void donecrunch(crunch_state_t* state); | ||||
|  | ||||
| /* Uncrunches as much as possible and then stops. */ | ||||
| extern void uncrunch(crunch_state_t* state); | ||||
| extern void doneuncrunch(crunch_state_t* state); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
| @@ -15,34 +15,31 @@ std::vector<std::string> DataSpec::split( | ||||
| { | ||||
|     std::vector<std::string> ret; | ||||
|  | ||||
|     size_t start = 0; | ||||
|     size_t end = 0; | ||||
|     size_t len = 0; | ||||
|     do | ||||
|     { | ||||
|         end = s.find(delimiter,start);  | ||||
|         len = end - start; | ||||
|         std::string token = s.substr(start, len); | ||||
|         ret.emplace_back( token ); | ||||
|         start += len + delimiter.length(); | ||||
|     } | ||||
|     while (end != std::string::npos); | ||||
| 	if (!s.empty()) | ||||
| 	{ | ||||
| 		size_t start = 0; | ||||
| 		size_t end = 0; | ||||
| 		size_t len = 0; | ||||
| 		do | ||||
| 		{ | ||||
| 			end = s.find(delimiter,start);  | ||||
| 			len = end - start; | ||||
| 			std::string token = s.substr(start, len); | ||||
| 			ret.emplace_back( token ); | ||||
| 			start += len + delimiter.length(); | ||||
| 		} | ||||
| 		while (end != std::string::npos); | ||||
| 	} | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| DataSpec::Modifier DataSpec::parseMod(const std::string& spec) | ||||
| std::set<unsigned> DataSpec::parseRange(const std::string& data) | ||||
| { | ||||
|     static const std::regex MOD_REGEX("([a-z]*)=([-x+0-9,]*)"); | ||||
|     static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?"); | ||||
| 	static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?"); | ||||
|  | ||||
|     std::smatch match; | ||||
|     if (!std::regex_match(spec, match, MOD_REGEX)) | ||||
|         Error() << "invalid data modifier syntax '" << spec << "'"; | ||||
|      | ||||
|     Modifier m; | ||||
|     m.name = match[1]; | ||||
|     m.source = spec; | ||||
|     for (auto& data : split(match[2], ",")) | ||||
| 	std::set<unsigned> result; | ||||
|     for (auto& data : split(data, ",")) | ||||
|     { | ||||
|         int start = 0; | ||||
|         int count = 1; | ||||
| @@ -64,9 +61,24 @@ DataSpec::Modifier DataSpec::parseMod(const std::string& spec) | ||||
|             Error() << "mod '" << data << "' specifies an illegal quantity"; | ||||
|  | ||||
|         for (int i = start; i < (start+count); i += step) | ||||
|             m.data.insert(i); | ||||
|             result.insert(i); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| DataSpec::Modifier DataSpec::parseMod(const std::string& spec) | ||||
| { | ||||
| 	static const std::regex MOD_REGEX("([a-z]*)=([-x+0-9,]*)"); | ||||
|  | ||||
|     std::smatch match; | ||||
|     if (!std::regex_match(spec, match, MOD_REGEX)) | ||||
|         Error() << "invalid data modifier syntax '" << spec << "'"; | ||||
|      | ||||
|     Modifier m; | ||||
|     m.name = match[1]; | ||||
|     m.source = spec; | ||||
| 	m.data = parseRange(match[2]); | ||||
|     return m; | ||||
| } | ||||
|  | ||||
| @@ -74,7 +86,7 @@ void DataSpec::set(const std::string& spec) | ||||
| { | ||||
|     std::vector<std::string> words = split(spec, ":"); | ||||
|     if (words.size() == 0) | ||||
|         Error() << "empty data specification (you have to specify *something*)"; | ||||
| 		return; | ||||
|  | ||||
|     filename = words[0]; | ||||
|     if (words.size() > 1) | ||||
|   | ||||
| @@ -34,6 +34,8 @@ public: | ||||
| public: | ||||
|     static std::vector<std::string> split( | ||||
|         const std::string& s, const std::string& delimiter); | ||||
| 	static std::set<unsigned> parseRange(const std::string& spec); | ||||
|  | ||||
|     static Modifier parseMod(const std::string& spec); | ||||
|  | ||||
| public: | ||||
| @@ -117,4 +119,34 @@ private: | ||||
|     DataSpec _value; | ||||
| }; | ||||
|  | ||||
| class RangeFlag : public Flag | ||||
| { | ||||
| public: | ||||
|     RangeFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             const std::string& defaultValue): | ||||
|         Flag(names, helptext), | ||||
| 		_stringValue(defaultValue), | ||||
|         _value(DataSpec::parseRange(defaultValue)) | ||||
|     {} | ||||
|  | ||||
|     const std::set<unsigned>& get() const | ||||
|     { checkInitialised(); return _value; } | ||||
|  | ||||
|     operator const std::set<unsigned>& () const | ||||
|     { return get(); } | ||||
|  | ||||
|     bool hasArgument() const { return true; } | ||||
|     const std::string defaultValueAsString() const { return _stringValue; } | ||||
|  | ||||
|     void set(const std::string& value) | ||||
| 	{ | ||||
| 		_stringValue = value; | ||||
| 		_value = DataSpec::parseRange(value); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	std::string _stringValue; | ||||
|     std::set<unsigned> _value; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -35,7 +35,7 @@ void AbstractDecoder::decodeToSectors(Track& track) | ||||
|             return; | ||||
|         if ((r == UNKNOWN_RECORD) || (r == DATA_RECORD)) | ||||
|         { | ||||
|             fmr.readNextMatchingOpcode(F_OP_PULSE); | ||||
|             fmr.findEvent(F_BIT_PULSE); | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
| @@ -56,7 +56,7 @@ void AbstractDecoder::decodeToSectors(Track& track) | ||||
| 				r = advanceToNextRecord(); | ||||
| 				if (r != UNKNOWN_RECORD) | ||||
| 					break; | ||||
| 				if (fmr.readNextMatchingOpcode(F_OP_PULSE) == 0) | ||||
| 				if (fmr.findEvent(F_BIT_PULSE) == 0) | ||||
|                     break; | ||||
| 			} | ||||
|             recordStart = fmr.tell(); | ||||
| @@ -88,3 +88,10 @@ void AbstractDecoder::pushRecord(const Fluxmap::Position& start, const Fluxmap:: | ||||
|     _track->rawrecords.push_back(record); | ||||
|     _fmr->seek(here); | ||||
| } | ||||
|  | ||||
| std::set<unsigned> AbstractDecoder::requiredSectors(Track& track) const | ||||
| { | ||||
| 	static std::set<unsigned> empty; | ||||
| 	return empty; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,8 @@ extern void setDecoderManualClockRate(double clockrate_us); | ||||
|  | ||||
| extern Bytes decodeFmMfm(std::vector<bool>::const_iterator start, | ||||
|     std::vector<bool>::const_iterator end); | ||||
| extern void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input); | ||||
| extern void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input, bool& lastBit); | ||||
| extern void encodeFm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input); | ||||
|  | ||||
| static inline Bytes decodeFmMfm(const std::vector<bool> bits) | ||||
| { return decodeFmMfm(bits.begin(), bits.end()); } | ||||
| @@ -51,6 +52,11 @@ public: | ||||
|     void seek(const Fluxmap::Position& pos) | ||||
|     { return _fmr->seek(pos); }  | ||||
|  | ||||
| 	/* Returns a set of sectors required to exist on this track. If the reader | ||||
| 	 * sees any missing, it will consider this to be an error and will retry | ||||
| 	 * the read. */ | ||||
| 	virtual std::set<unsigned> requiredSectors(Track& track) const; | ||||
|  | ||||
| protected: | ||||
|     virtual void beginTrack() {}; | ||||
|     virtual RecordType advanceToNextRecord() = 0; | ||||
|   | ||||
| @@ -30,16 +30,15 @@ static DoubleFlag minimumClockUs( | ||||
|     "Refuse to detect clocks shorter than this, to avoid false positives.", | ||||
|     0.75); | ||||
|  | ||||
| int FluxmapReader::readOpcode(unsigned& ticks) | ||||
| uint8_t FluxmapReader::getNextEvent(unsigned& ticks) | ||||
| { | ||||
|     ticks = 0; | ||||
|  | ||||
|     while (!eof()) | ||||
|     { | ||||
|         uint8_t b = _bytes[_pos.bytes++]; | ||||
|         if (b < 0x80) | ||||
|             ticks += b; | ||||
|         else | ||||
| 		ticks += b & 0x3f; | ||||
| 		if (b & (F_BIT_PULSE|F_BIT_INDEX)) | ||||
|         { | ||||
|             _pos.ticks += ticks; | ||||
|             return b; | ||||
| @@ -47,21 +46,21 @@ int FluxmapReader::readOpcode(unsigned& ticks) | ||||
|     } | ||||
|  | ||||
|     _pos.ticks += ticks; | ||||
|     return -1; | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| unsigned FluxmapReader::readNextMatchingOpcode(uint8_t opcode) | ||||
| unsigned FluxmapReader::findEvent(uint8_t target) | ||||
| { | ||||
|     unsigned ticks = 0; | ||||
|  | ||||
|     for (;;) | ||||
|     { | ||||
|         unsigned thisTicks; | ||||
|         int op = readOpcode(thisTicks); | ||||
|         uint8_t bits = getNextEvent(thisTicks); | ||||
|         ticks += thisTicks; | ||||
|         if (op == -1) | ||||
|         if (eof()) | ||||
|             return 0; | ||||
|         if (op == opcode) | ||||
|         if (bits & target) | ||||
|             return ticks; | ||||
|     } | ||||
| } | ||||
| @@ -73,7 +72,7 @@ unsigned FluxmapReader::readInterval(nanoseconds_t clock) | ||||
|  | ||||
|     while (ticks < thresholdTicks) | ||||
|     { | ||||
|         unsigned thisTicks = readNextMatchingOpcode(F_OP_PULSE); | ||||
|         unsigned thisTicks = findEvent(F_BIT_PULSE); | ||||
|         if (!thisTicks) | ||||
|             break; | ||||
|         ticks += thisTicks; | ||||
| @@ -196,7 +195,7 @@ void FluxmapReader::seek(nanoseconds_t ns) | ||||
|     while (!eof() && (_pos.ticks < ticks)) | ||||
|     { | ||||
|         unsigned t; | ||||
|         readOpcode(t); | ||||
|         getNextEvent(t); | ||||
|     } | ||||
|     _pos.zeroes = 0; | ||||
| } | ||||
| @@ -237,7 +236,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu | ||||
|             positions[i] = positions[i+1]; | ||||
|             candidates[i] = candidates[i+1]; | ||||
|         } | ||||
|         candidates[intervalCount] = readNextMatchingOpcode(F_OP_PULSE); | ||||
|         candidates[intervalCount] = findEvent(F_BIT_PULSE); | ||||
|         positions[intervalCount] = tell(); | ||||
|  | ||||
|     } | ||||
| @@ -248,7 +247,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu | ||||
|  | ||||
| void FluxmapReader::seekToIndexMark() | ||||
| { | ||||
|     readNextMatchingOpcode(F_OP_INDEX); | ||||
|     findEvent(F_BIT_INDEX); | ||||
|     _pos.zeroes = 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -96,8 +96,8 @@ public: | ||||
|         _pos = pos; | ||||
|     } | ||||
|  | ||||
|     int readOpcode(unsigned& ticks); | ||||
|     unsigned readNextMatchingOpcode(uint8_t opcode); | ||||
|     uint8_t getNextEvent(unsigned& ticks); | ||||
|     unsigned findEvent(uint8_t bits); | ||||
|     unsigned readInterval(nanoseconds_t clock); /* with debounce support */ | ||||
|  | ||||
|     /* Important! You can only reliably seek to 1 bits. */ | ||||
|   | ||||
| @@ -52,9 +52,32 @@ Bytes decodeFmMfm( | ||||
|     return bytes; | ||||
| } | ||||
|  | ||||
| void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input) | ||||
| void encodeFm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input) | ||||
| { | ||||
|     bool lastBit = false; | ||||
| 	if (bits.size() == 0) | ||||
| 		return; | ||||
|     unsigned len = bits.size()-1; | ||||
|  | ||||
|     for (uint8_t b : input) | ||||
|     { | ||||
|         for (int i=0; i<8; i++) | ||||
|         { | ||||
|             bool bit = b & 0x80; | ||||
|             b <<= 1; | ||||
|  | ||||
|             if (cursor >= len) | ||||
|                 return; | ||||
|              | ||||
|             bits[cursor++] = true; | ||||
|             bits[cursor++] = bit; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input, bool& lastBit) | ||||
| { | ||||
| 	if (bits.size() == 0) | ||||
| 		return; | ||||
|     unsigned len = bits.size()-1; | ||||
|  | ||||
|     for (uint8_t b : input) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| static FlagGroup* currentFlagGroup; | ||||
| static std::vector<Flag*> all_flags; | ||||
| @@ -172,6 +173,11 @@ void BoolFlag::set(const std::string& value) | ||||
| 		Error() << "can't parse '" << value << "'; try 'true' or 'false'"; | ||||
| } | ||||
|  | ||||
| const std::string HexIntFlag::defaultValueAsString() const | ||||
| { | ||||
| 	return fmt::format("0x{:x}", _defaultValue); | ||||
| } | ||||
|  | ||||
| static void doHelp() | ||||
| { | ||||
|     std::cout << "FluxEngine options:" << std::endl; | ||||
|   | ||||
							
								
								
									
										13
									
								
								lib/flags.h
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								lib/flags.h
									
									
									
									
									
								
							| @@ -135,6 +135,17 @@ public: | ||||
|     void set(const std::string& value) { _value = std::stoi(value); } | ||||
| }; | ||||
|  | ||||
| class HexIntFlag : public IntFlag | ||||
| { | ||||
| public: | ||||
|     HexIntFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             int defaultValue = 0): | ||||
|         IntFlag(names, helptext, defaultValue) | ||||
|     {} | ||||
|  | ||||
|     const std::string defaultValueAsString() const; | ||||
| }; | ||||
|  | ||||
| class DoubleFlag : public ValueFlag<double> | ||||
| { | ||||
| public: | ||||
| @@ -147,7 +158,7 @@ public: | ||||
|     void set(const std::string& value) { _value = std::stod(value); } | ||||
| }; | ||||
|  | ||||
| class BoolFlag : public ValueFlag<double> | ||||
| class BoolFlag : public ValueFlag<bool> | ||||
| { | ||||
| public: | ||||
|     BoolFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|   | ||||
| @@ -4,6 +4,9 @@ | ||||
|  | ||||
| Fluxmap& Fluxmap::appendBytes(const Bytes& bytes) | ||||
| { | ||||
| 	if (bytes.size() == 0) | ||||
| 		return *this; | ||||
|  | ||||
|     return appendBytes(&bytes[0], bytes.size()); | ||||
| } | ||||
|  | ||||
| @@ -15,8 +18,7 @@ Fluxmap& Fluxmap::appendBytes(const uint8_t* ptr, size_t len) | ||||
|     while (len--) | ||||
|     { | ||||
|         uint8_t byte = *ptr++; | ||||
|         if (byte < 0x80) | ||||
|             _ticks += byte; | ||||
| 		_ticks += byte & 0x3f; | ||||
|         bw.write_8(byte); | ||||
|     } | ||||
|  | ||||
| @@ -24,12 +26,19 @@ Fluxmap& Fluxmap::appendBytes(const uint8_t* ptr, size_t len) | ||||
|     return *this; | ||||
| } | ||||
|  | ||||
| uint8_t& Fluxmap::findLastByte() | ||||
| { | ||||
| 	if (_bytes.empty()) | ||||
| 		appendByte(0x00); | ||||
| 	return *(_bytes.end() - 1); | ||||
| } | ||||
|  | ||||
| Fluxmap& Fluxmap::appendInterval(uint32_t ticks) | ||||
| { | ||||
|     while (ticks >= 0x7f) | ||||
|     while (ticks >= 0x3f) | ||||
|     { | ||||
|         appendByte(0x7f); | ||||
|         ticks -= 0x7f; | ||||
|         appendByte(0x3f); | ||||
|         ticks -= 0x3f; | ||||
|     } | ||||
|     appendByte((uint8_t)ticks); | ||||
|     return *this; | ||||
| @@ -37,13 +46,13 @@ Fluxmap& Fluxmap::appendInterval(uint32_t ticks) | ||||
|  | ||||
| Fluxmap& Fluxmap::appendPulse() | ||||
| { | ||||
|     appendByte(0x80); | ||||
| 	findLastByte() |= 0x80; | ||||
|     return *this; | ||||
| } | ||||
|  | ||||
| Fluxmap& Fluxmap::appendIndex() | ||||
| { | ||||
|     appendByte(0x81); | ||||
| 	findLastByte() |= 0x40; | ||||
|     return *this; | ||||
| } | ||||
|  | ||||
| @@ -54,25 +63,26 @@ void Fluxmap::precompensate(int threshold_ticks, int amount_ticks) | ||||
|     for (unsigned i=0; i<_bytes.size(); i++) | ||||
|     { | ||||
|         uint8_t& prev = (i == 0) ? junk : _bytes[i-1]; | ||||
|         uint8_t curr = _bytes[i]; | ||||
| 		uint8_t prevticks = prev & 0x3f; | ||||
|         uint8_t currticks = _bytes[i] & 0x3f; | ||||
|  | ||||
|         if (curr < (3*threshold_ticks)) | ||||
|         if (currticks < (3*threshold_ticks)) | ||||
|         { | ||||
|             if ((prev <= threshold_ticks) && (curr > threshold_ticks)) | ||||
|             if ((prevticks <= threshold_ticks) && (currticks > threshold_ticks)) | ||||
|             { | ||||
|                 /* 01001; move the previous bit backwards. */ | ||||
|                 if (prev >= (1+amount_ticks)) | ||||
|                 if (prevticks >= (1+amount_ticks)) | ||||
|                     prev -= amount_ticks; | ||||
|                 if (curr <= (0x7f-amount_ticks)) | ||||
|                     curr += amount_ticks; | ||||
|                 if (currticks <= (0x7f-amount_ticks)) | ||||
|                     currticks += amount_ticks; | ||||
|             } | ||||
|             else if ((prev > threshold_ticks) && (curr <= threshold_ticks)) | ||||
|             else if ((prevticks > threshold_ticks) && (currticks <= threshold_ticks)) | ||||
|             { | ||||
|                 /* 00101; move the current bit forwards. */ | ||||
|                 if (prev <= (0x7f-amount_ticks)) | ||||
|                 if (prevticks <= (0x7f-amount_ticks)) | ||||
|                     prev += amount_ticks; | ||||
|                 if (curr >= (1+amount_ticks)) | ||||
|                     curr -= amount_ticks; | ||||
|                 if (currticks >= (1+amount_ticks)) | ||||
|                     currticks -= amount_ticks; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -48,6 +48,9 @@ public: | ||||
|  | ||||
| 	void precompensate(int threshold_ticks, int amount_ticks); | ||||
|  | ||||
| private: | ||||
| 	uint8_t& findLastByte(); | ||||
|  | ||||
| private: | ||||
|     nanoseconds_t _duration = 0; | ||||
|     int _ticks = 0; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "flags.h" | ||||
|  | ||||
| extern FlagGroup hardwareFluxSinkFlags; | ||||
| extern FlagGroup sqliteFluxSinkFlags; | ||||
|  | ||||
| class Fluxmap; | ||||
| class FluxSpec; | ||||
| @@ -13,11 +14,9 @@ class FluxSink | ||||
| public: | ||||
|     virtual ~FluxSink() {} | ||||
|  | ||||
| private: | ||||
|     static std::unique_ptr<FluxSink> createSqliteFluxSink(const std::string& filename); | ||||
|     static std::unique_ptr<FluxSink> createHardwareFluxSink(unsigned drive); | ||||
|  | ||||
| public: | ||||
|     static std::unique_ptr<FluxSink> create(const FluxSpec& spec); | ||||
|  | ||||
| public: | ||||
| @@ -25,6 +24,7 @@ public: | ||||
| }; | ||||
|  | ||||
| extern void setHardwareFluxSinkDensity(bool high_density); | ||||
| extern void setHardwareFluxSinkHardSectorCount(int sectorCount); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fluxmap.h" | ||||
| #include "usb.h" | ||||
| #include "usb/usb.h" | ||||
| #include "fluxsink/fluxsink.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| FlagGroup hardwareFluxSinkFlags; | ||||
| FlagGroup hardwareFluxSinkFlags = { | ||||
| 	&usbFlags, | ||||
| }; | ||||
|  | ||||
| static bool high_density = false; | ||||
|  | ||||
| @@ -13,17 +16,37 @@ static IntFlag indexMode( | ||||
|     "index pulse source (0=drive, 1=300 RPM fake source, 2=360 RPM fake source", | ||||
|     0); | ||||
|  | ||||
| static IntFlag hardSectorCount( | ||||
|     { "--write-hard-sector-count" }, | ||||
|     "number of hard sectors on the disk (0=soft sectors)", | ||||
|     0); | ||||
|  | ||||
| void setHardwareFluxSinkDensity(bool high_density) | ||||
| { | ||||
| 	::high_density = high_density; | ||||
| } | ||||
|  | ||||
| void setHardwareFluxSinkHardSectorCount(int sectorCount) | ||||
| { | ||||
| 	::hardSectorCount.setDefaultValue(sectorCount); | ||||
| } | ||||
|  | ||||
| class HardwareFluxSink : public FluxSink | ||||
| { | ||||
| public: | ||||
|     HardwareFluxSink(unsigned drive): | ||||
|         _drive(drive) | ||||
|     { | ||||
| 		if (hardSectorCount != 0) | ||||
| 		{ | ||||
| 			usbSetDrive(_drive, high_density, indexMode); | ||||
| 			std::cerr << "Measuring rotational speed... " << std::flush; | ||||
| 			nanoseconds_t oneRevolution = usbGetRotationalPeriod(hardSectorCount); | ||||
| 			_hardSectorThreshold = oneRevolution * 3 / (4 * hardSectorCount); | ||||
| 			std::cerr << fmt::format("{}ms\n", oneRevolution / 1e6); | ||||
| 		} | ||||
| 		else | ||||
| 			_hardSectorThreshold = 0; | ||||
|     } | ||||
|  | ||||
|     ~HardwareFluxSink() | ||||
| @@ -36,12 +59,12 @@ public: | ||||
|         usbSetDrive(_drive, high_density, indexMode); | ||||
|         usbSeek(track); | ||||
|  | ||||
|         Bytes crunched = fluxmap.rawBytes().crunch(); | ||||
|         return usbWrite(side, crunched); | ||||
|         return usbWrite(side, fluxmap.rawBytes(), _hardSectorThreshold); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     unsigned _drive; | ||||
|     nanoseconds_t _hardSectorThreshold; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink(unsigned drive) | ||||
|   | ||||
| @@ -2,14 +2,37 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "sql.h" | ||||
| #include "fluxsink/fluxsink.h" | ||||
| #include "flags.h" | ||||
| #include "fmt/format.h" | ||||
| #include <unistd.h> | ||||
|  | ||||
| FlagGroup sqliteFluxSinkFlags; | ||||
|  | ||||
| static SettableFlag mergeFlag( | ||||
| 	{ "--merge" }, | ||||
| 	"merge new data into existing flux file"); | ||||
|  | ||||
| static SettableFlag overwriteFlag( | ||||
| 	{ "--overwrite" }, | ||||
| 	"overwrite existing flux file"); | ||||
|  | ||||
| class SqliteFluxSink : public FluxSink | ||||
| { | ||||
| public: | ||||
|     SqliteFluxSink(const std::string& filename) | ||||
|     { | ||||
| 		if (mergeFlag && overwriteFlag) | ||||
| 			Error() << "you can't specify --merge and --overwrite"; | ||||
|  | ||||
| 		if (!mergeFlag) | ||||
| 		{ | ||||
| 			if (!overwriteFlag && (access(filename.c_str(), F_OK) == 0)) | ||||
| 				Error() << "cowardly refusing to overwrite flux file without --merge or --overwrite specified"; | ||||
| 			if ((access(filename.c_str(), F_OK) == 0) && (remove(filename.c_str()) != 0)) | ||||
| 				Error() << fmt::format("failed to overwrite flux file"); | ||||
| 		} | ||||
| 		_outdb = sqlOpen(filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); | ||||
|  | ||||
| 		int oldVersion = sqlReadIntProperty(_outdb, "version"); | ||||
| 		if ((oldVersion != 0) && (oldVersion != FLUX_VERSION_CURRENT)) | ||||
|             Error() << fmt::format("that flux file is version {}, but this client is for version {}", | ||||
|   | ||||
| @@ -30,6 +30,7 @@ public: | ||||
| extern void setHardwareFluxSourceRevolutions(double revolutions); | ||||
| extern void setHardwareFluxSourceDensity(bool high_density); | ||||
| extern void setHardwareFluxSourceSynced(bool synced); | ||||
| extern void setHardwareFluxSourceHardSectorCount(int sectorCount); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fluxmap.h" | ||||
| #include "usb.h" | ||||
| #include "usb/usb.h" | ||||
| #include "fluxsource/fluxsource.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| FlagGroup hardwareFluxSourceFlags; | ||||
| FlagGroup hardwareFluxSourceFlags = { | ||||
| 	&usbFlags | ||||
| }; | ||||
|  | ||||
| static DoubleFlag revolutions( | ||||
|     { "--revolutions" }, | ||||
| @@ -22,6 +24,11 @@ static IntFlag indexMode( | ||||
|     "index pulse source (0=drive, 1=300 RPM fake source, 2=360 RPM fake source", | ||||
|     0); | ||||
|  | ||||
| static IntFlag hardSectorCount( | ||||
|     { "--hard-sector-count" }, | ||||
|     "number of hard sectors on the disk (0=soft sectors)", | ||||
|     0); | ||||
|  | ||||
| static bool high_density = false; | ||||
|  | ||||
| void setHardwareFluxSourceDensity(bool high_density) | ||||
| @@ -37,7 +44,11 @@ public: | ||||
|     { | ||||
|         usbSetDrive(_drive, high_density, indexMode); | ||||
|         std::cerr << "Measuring rotational speed... " << std::flush; | ||||
|         _oneRevolution = usbGetRotationalPeriod(); | ||||
|         _oneRevolution = usbGetRotationalPeriod(hardSectorCount); | ||||
| 	if (hardSectorCount != 0) | ||||
| 		_hardSectorThreshold = _oneRevolution * 3 / (4 * hardSectorCount); | ||||
| 	else | ||||
| 		_hardSectorThreshold = 0; | ||||
|         std::cerr << fmt::format("{}ms\n", _oneRevolution / 1e6); | ||||
|     } | ||||
|  | ||||
| @@ -50,9 +61,10 @@ public: | ||||
|     { | ||||
|         usbSetDrive(_drive, high_density, indexMode); | ||||
|         usbSeek(track); | ||||
|         Bytes crunched = usbRead(side, synced, revolutions * _oneRevolution); | ||||
|         Bytes data = usbRead( | ||||
| 			side, synced, revolutions * _oneRevolution, _hardSectorThreshold); | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBytes(crunched.uncrunch()); | ||||
|         fluxmap->appendBytes(data); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| @@ -70,6 +82,7 @@ private: | ||||
|     unsigned _drive; | ||||
|     unsigned _revolutions; | ||||
|     nanoseconds_t _oneRevolution; | ||||
|     nanoseconds_t _hardSectorThreshold; | ||||
| }; | ||||
|  | ||||
| void setHardwareFluxSourceRevolutions(double revolutions) | ||||
| @@ -82,6 +95,11 @@ void setHardwareFluxSourceSynced(bool synced) | ||||
|     ::synced.setDefaultValue(synced); | ||||
| } | ||||
|  | ||||
| void setHardwareFluxSourceHardSectorCount(int sectorCount) | ||||
| { | ||||
|     ::hardSectorCount.setDefaultValue(sectorCount); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<FluxSource> FluxSource::createHardwareFluxSource(unsigned drive) | ||||
| { | ||||
|     return std::unique_ptr<FluxSource>(new HardwareFluxSource(drive)); | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| #include <set> | ||||
| #include <cassert> | ||||
|  | ||||
| typedef int nanoseconds_t; | ||||
| typedef double nanoseconds_t; | ||||
| class Bytes; | ||||
|  | ||||
| extern double getCurrentTime(); | ||||
|   | ||||
							
								
								
									
										25
									
								
								lib/image.cc
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								lib/image.cc
									
									
									
									
									
								
							| @@ -1,25 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| SectorSet readSectorsFromFile(const ImageSpec& spec) | ||||
| { | ||||
| 	return ImageReader::create(spec)->readImage(); | ||||
| } | ||||
|  | ||||
| void writeSectorsToFile(const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
| 	std::unique_ptr<ImageWriter> writer(ImageWriter::create(sectors, spec)); | ||||
| 	writer->adjustGeometry(); | ||||
| 	writer->printMap(); | ||||
| 	writer->writeImage(); | ||||
| } | ||||
							
								
								
									
										14
									
								
								lib/image.h
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								lib/image.h
									
									
									
									
									
								
							| @@ -1,14 +0,0 @@ | ||||
| #ifndef IMAGE_H | ||||
| #define IMAGE_H | ||||
|  | ||||
| class SectorSet; | ||||
| class ImageSpec; | ||||
|  | ||||
| extern SectorSet readSectorsFromFile( | ||||
| 	const ImageSpec& filename); | ||||
|  | ||||
| extern void writeSectorsToFile( | ||||
| 	const SectorSet& sectors, | ||||
| 	const ImageSpec& filename); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										128
									
								
								lib/imagereader/diskcopyimagereader.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								lib/imagereader/diskcopyimagereader.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| class DiskCopyImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	DiskCopyImageReader(const ImageSpec& spec): | ||||
| 		ImageReader(spec) | ||||
| 	{} | ||||
|  | ||||
| 	SectorSet readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| 		Bytes data; | ||||
| 		data.writer() += inputFile; | ||||
| 		ByteReader br(data); | ||||
|  | ||||
| 		br.seek(1); | ||||
| 		std::string label = br.read(data[0]); | ||||
|  | ||||
| 		br.seek(0x40); | ||||
| 		uint32_t dataSize = br.read_be32(); | ||||
|  | ||||
| 		br.seek(0x50); | ||||
| 		uint8_t encoding = br.read_8(); | ||||
| 		uint8_t formatByte = br.read_8(); | ||||
|  | ||||
| 		unsigned numCylinders = 80; | ||||
| 		unsigned numHeads = 2; | ||||
| 		unsigned numSectors = 0; | ||||
| 		bool mfm = false; | ||||
|  | ||||
| 		switch (encoding) | ||||
| 		{ | ||||
| 			case 0: /* GCR CLV 400kB */ | ||||
| 				numHeads = 1; | ||||
| 				break; | ||||
|  | ||||
| 			case 1: /* GCR CLV 800kB */ | ||||
| 				break; | ||||
|  | ||||
| 			case 2: /* MFM CAV 720kB */ | ||||
| 				numSectors = 9; | ||||
| 				mfm = true; | ||||
| 				break; | ||||
|  | ||||
| 			case 3: /* MFM CAV 1440kB */ | ||||
| 				numSectors = 18; | ||||
| 				mfm = true; | ||||
| 				break; | ||||
|  | ||||
| 			default: | ||||
| 				Error() << fmt::format("don't understant DiskCopy disks of type {}", encoding); | ||||
| 		} | ||||
|  | ||||
| 		std::cout << "reading DiskCopy 4.2 image\n" | ||||
| 		          << fmt::format("{} cylinders, {} heads; {}; {}\n", | ||||
| 				  		numCylinders, numHeads, | ||||
| 						mfm ? "MFM" : "GCR", | ||||
| 						label); | ||||
|  | ||||
| 		auto sectorsPerTrack = [&](int track) -> int | ||||
| 		{ | ||||
| 			if (mfm) | ||||
| 				return numSectors; | ||||
|  | ||||
| 			if (track < 16) | ||||
| 				return 12; | ||||
| 			if (track < 32) | ||||
| 				return 11; | ||||
| 			if (track < 48) | ||||
| 				return 10; | ||||
| 			if (track < 64) | ||||
| 				return 9; | ||||
| 			return 8; | ||||
| 		}; | ||||
|  | ||||
| 		uint32_t dataPtr = 0x54; | ||||
| 		uint32_t tagPtr = dataPtr + dataSize; | ||||
|  | ||||
|         SectorSet sectors; | ||||
|         for (int track = 0; track < numCylinders; track++) | ||||
|         { | ||||
| 			int numSectors = sectorsPerTrack(track); | ||||
|             for (int head = 0; head < numHeads; head++) | ||||
|             { | ||||
|                 for (int sectorId = 0; sectorId < numSectors; sectorId++) | ||||
|                 { | ||||
| 					br.seek(dataPtr); | ||||
| 					Bytes payload = br.read(512); | ||||
| 					dataPtr += 512; | ||||
|  | ||||
| 					br.seek(tagPtr); | ||||
| 					Bytes tag = br.read(12); | ||||
| 					tagPtr += 12; | ||||
|  | ||||
|                     std::unique_ptr<Sector>& sector = sectors.get(track, head, sectorId); | ||||
|                     sector.reset(new Sector); | ||||
|                     sector->status = Sector::OK; | ||||
|                     sector->logicalTrack = sector->physicalTrack = track; | ||||
|                     sector->logicalSide = sector->physicalSide = head; | ||||
|                     sector->logicalSector = sectorId; | ||||
|                     sector->data.writer().append(payload).append(tag); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sectors; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createDiskCopyImageReader( | ||||
| 	const ImageSpec& spec) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new DiskCopyImageReader(spec)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,17 +1,22 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <ctype.h> | ||||
|  | ||||
| std::map<std::string, ImageReader::Constructor> ImageReader::formats = | ||||
| { | ||||
| 	{".adf", ImageReader::createImgImageReader}, | ||||
| 	{".d81", ImageReader::createImgImageReader}, | ||||
| 	{".diskcopy", ImageReader::createDiskCopyImageReader}, | ||||
| 	{".img", ImageReader::createImgImageReader}, | ||||
| 	{".ima", ImageReader::createImgImageReader}, | ||||
| 	{".jv1", ImageReader::createImgImageReader}, | ||||
| 	{".jv3", ImageReader::createJv3ImageReader}, | ||||
| }; | ||||
|  | ||||
| static bool ends_with(const std::string& value, const std::string& ending) | ||||
| @@ -43,7 +48,7 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageSpec& spec) | ||||
| void ImageReader::verifyImageSpec(const ImageSpec& spec) | ||||
| { | ||||
|     if (!findConstructor(spec)) | ||||
|         Error() << "unrecognised image filename extension"; | ||||
|         Error() << "unrecognised input image filename extension"; | ||||
| } | ||||
|  | ||||
| ImageReader::ImageReader(const ImageSpec& spec): | ||||
|   | ||||
| @@ -23,7 +23,9 @@ private: | ||||
|  | ||||
| 	static std::map<std::string, Constructor> formats; | ||||
|  | ||||
|     static std::unique_ptr<ImageReader> createDiskCopyImageReader(const ImageSpec& spec); | ||||
|     static std::unique_ptr<ImageReader> createImgImageReader(const ImageSpec& spec); | ||||
|     static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageSpec& spec); | ||||
|  | ||||
| 	static Constructor findConstructor(const ImageSpec& spec); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
|   | ||||
							
								
								
									
										141
									
								
								lib/imagereader/jv3imagereader.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								lib/imagereader/jv3imagereader.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| /* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, which may appear | ||||
|  * in any order, followed by the same again for more sectors. To find the second data block | ||||
|  * you need to know the size of the first data block, which requires parsing it. | ||||
|  * | ||||
|  * https://www.tim-mann.org/trs80/dskspec.html | ||||
|  * | ||||
|  * typedef struct { | ||||
|  *   SectorHeader headers1[2901]; | ||||
|  *   unsigned char writeprot; | ||||
|  *   unsigned char data1[]; | ||||
|  *   SectorHeader headers2[2901]; | ||||
|  *   unsigned char padding; | ||||
|  *   unsigned char data2[]; | ||||
|  * } JV3; | ||||
|  * | ||||
|  * typedef struct { | ||||
|  *   unsigned char track; | ||||
|  *   unsigned char sector; | ||||
|  *   unsigned char flags; | ||||
|  * } SectorHeader; | ||||
|  */ | ||||
|  | ||||
| struct SectorHeader | ||||
| { | ||||
| 	uint8_t track; | ||||
| 	uint8_t sector; | ||||
| 	uint8_t flags; | ||||
| }; | ||||
|  | ||||
| #define JV3_DENSITY     0x80  /* 1=dden, 0=sden */ | ||||
| #define JV3_DAM         0x60  /* data address mark code; see below */ | ||||
| #define JV3_SIDE        0x10  /* 0=side 0, 1=side 1 */ | ||||
| #define JV3_ERROR       0x08  /* 0=ok, 1=CRC error */ | ||||
| #define JV3_NONIBM      0x04  /* 0=normal, 1=short */ | ||||
| #define JV3_SIZE        0x03  /* in used sectors: 0=256,1=128,2=1024,3=512 | ||||
|                                  in free sectors: 0=512,1=1024,2=128,3=256 */ | ||||
|  | ||||
| #define JV3_FREE        0xFF  /* in track and sector fields of free sectors */ | ||||
| #define JV3_FREEF       0xFC  /* in flags field, or'd with size code */ | ||||
|  | ||||
| static unsigned getSectorSize(uint8_t flags) | ||||
| { | ||||
| 	if ((flags & JV3_FREEF) == JV3_FREEF) | ||||
| 	{ | ||||
| 		switch (flags & JV3_SIZE) | ||||
| 		{ | ||||
| 			case 0: return 512; | ||||
| 			case 1: return 1024; | ||||
| 			case 2: return 128; | ||||
| 			case 3: return 256; | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		switch (flags & JV3_SIZE) | ||||
| 		{ | ||||
| 			case 0: return 256; | ||||
| 			case 1: return 128; | ||||
| 			case 2: return 1024; | ||||
| 			case 3: return 512; | ||||
| 		} | ||||
| 	} | ||||
| 	Error() << "not reachable"; | ||||
| } | ||||
|  | ||||
| class Jv3ImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	Jv3ImageReader(const ImageSpec& spec): | ||||
| 		ImageReader(spec) | ||||
| 	{} | ||||
|  | ||||
| 	SectorSet readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| 		inputFile.seekg( 0, std::ios::end); | ||||
| 		unsigned inputFileSize = inputFile.tellg(); | ||||
| 		unsigned headerPtr = 0; | ||||
| 		SectorSet sectors; | ||||
| 		for (;;) | ||||
| 		{ | ||||
| 			unsigned dataPtr = headerPtr + 2901*3 + 1; | ||||
| 			if (dataPtr >= inputFileSize) | ||||
| 				break; | ||||
|  | ||||
| 			for (unsigned i=0; i<2901; i++) | ||||
| 			{ | ||||
| 				SectorHeader header = {0, 0, 0xff}; | ||||
| 				inputFile.seekg(headerPtr); | ||||
| 				inputFile.read((char*) &header, 3); | ||||
| 				unsigned sectorSize = getSectorSize(header.flags); | ||||
| 				if ((header.flags & JV3_FREEF) != JV3_FREEF) | ||||
| 				{ | ||||
| 					Bytes data(sectorSize); | ||||
| 					inputFile.seekg(dataPtr); | ||||
| 					inputFile.read((char*) data.begin(), sectorSize); | ||||
|  | ||||
| 					unsigned head = !!(header.flags & JV3_SIDE); | ||||
|                     std::unique_ptr<Sector>& sector = sectors.get(header.track, head, header.sector); | ||||
|                     sector.reset(new Sector); | ||||
|                     sector->status = Sector::OK; | ||||
|                     sector->logicalTrack = sector->physicalTrack = header.track; | ||||
|                     sector->logicalSide = sector->physicalSide = head; | ||||
|                     sector->logicalSector = header.sector; | ||||
|                     sector->data = data; | ||||
| 				} | ||||
|  | ||||
| 				headerPtr += 3; | ||||
| 				dataPtr += sectorSize; | ||||
| 			} | ||||
|  | ||||
| 			/* dataPtr is now pointing at the beginning of the next chunk. */ | ||||
|  | ||||
| 			headerPtr = dataPtr; | ||||
| 		} | ||||
|  | ||||
|         return sectors; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createJv3ImageReader( | ||||
| 	const ImageSpec& spec) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new Jv3ImageReader(spec)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
|   | ||||
							
								
								
									
										168
									
								
								lib/imagewriter/diskcopyimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								lib/imagewriter/diskcopyimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include "ldbs.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| static const char LABEL[] = "FluxEngine image"; | ||||
|  | ||||
| static void write_and_update_checksum(ByteWriter& bw, uint32_t& checksum, const Bytes& data) | ||||
| { | ||||
| 	ByteReader br(data); | ||||
| 	while (!br.eof()) | ||||
| 	{ | ||||
| 		uint32_t i = br.read_be16(); | ||||
| 		checksum += i; | ||||
| 		checksum = (checksum >> 1) | (checksum << 31); | ||||
| 		bw.write_be16(i); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| class DiskCopyImageWriter : public ImageWriter | ||||
| { | ||||
| public: | ||||
| 	DiskCopyImageWriter(const SectorSet& sectors, const ImageSpec& spec): | ||||
| 		ImageWriter(sectors, spec) | ||||
| 	{} | ||||
|  | ||||
| 	void writeImage() | ||||
| 	{ | ||||
| 		bool mfm = false; | ||||
|  | ||||
| 		if (spec.bytes == 524) | ||||
| 		{ | ||||
| 			/* GCR disk */ | ||||
| 		} | ||||
| 		else if (spec.bytes == 512) | ||||
| 		{ | ||||
| 			/* MFM disk */ | ||||
| 			mfm = true; | ||||
| 		} | ||||
| 		else | ||||
| 			Error() << "this image is not compatible with the DiskCopy 4.2 format"; | ||||
|  | ||||
| 		std::cout << "writing DiskCopy 4.2 image\n" | ||||
| 		          << fmt::format("{} tracks, {} heads, {} sectors, {} bytes per sector; {}\n", | ||||
| 				  		spec.cylinders, spec.heads, spec.sectors, spec.bytes, | ||||
| 						mfm ? "MFM" : "GCR"); | ||||
|  | ||||
| 		auto sectors_per_track = [&](int track) -> int | ||||
| 		{ | ||||
| 			if (mfm) | ||||
| 				return spec.sectors; | ||||
|  | ||||
| 			if (track < 16) | ||||
| 				return 12; | ||||
| 			if (track < 32) | ||||
| 				return 11; | ||||
| 			if (track < 48) | ||||
| 				return 10; | ||||
| 			if (track < 64) | ||||
| 				return 9; | ||||
| 			return 8; | ||||
| 		}; | ||||
|  | ||||
| 		Bytes image; | ||||
| 		ByteWriter bw(image); | ||||
|  | ||||
| 		/* Write the actual sectr data. */ | ||||
|  | ||||
| 		uint32_t dataChecksum = 0; | ||||
| 		uint32_t tagChecksum = 0; | ||||
| 		uint32_t offset = 0x54; | ||||
| 		uint32_t sectorDataStart = offset; | ||||
| 		for (int track = 0; track < spec.cylinders; track++) | ||||
| 		{ | ||||
| 			for (int head = 0; head < spec.heads; head++) | ||||
| 			{ | ||||
| 				int sectorCount = sectors_per_track(track); | ||||
| 				for (int sectorId = 0; sectorId < sectorCount; sectorId++) | ||||
| 				{ | ||||
| 					const auto& sector = sectors.get(track, head, sectorId); | ||||
| 					if (sector) | ||||
| 					{ | ||||
| 						bw.seek(offset); | ||||
| 						write_and_update_checksum(bw, dataChecksum, sector->data.slice(0, 512)); | ||||
| 					} | ||||
| 					offset += 512; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		uint32_t sectorDataEnd = offset; | ||||
| 		if (!mfm) | ||||
| 		{ | ||||
| 			for (int track = 0; track < spec.cylinders; track++) | ||||
| 			{ | ||||
| 				for (int head = 0; head < spec.heads; head++) | ||||
| 				{ | ||||
| 					int sectorCount = sectors_per_track(track); | ||||
| 					for (int sectorId = 0; sectorId < sectorCount; sectorId++) | ||||
| 					{ | ||||
| 						const auto& sector = sectors.get(track, head, sectorId); | ||||
| 						if (sector) | ||||
| 						{ | ||||
| 							bw.seek(offset); | ||||
| 							write_and_update_checksum(bw, tagChecksum, sector->data.slice(512, 12)); | ||||
| 						} | ||||
| 						offset += 12; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		uint32_t tagDataEnd = offset; | ||||
|  | ||||
| 		/* Write the header. */ | ||||
|  | ||||
| 		uint8_t encoding; | ||||
| 		uint8_t format; | ||||
| 		if (mfm) | ||||
| 		{ | ||||
| 			format = 0x22; | ||||
| 			if (spec.sectors == 18) | ||||
| 				encoding = 3; | ||||
| 			else | ||||
| 				encoding = 2; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (spec.heads == 2) | ||||
| 			{ | ||||
| 				encoding = 1; | ||||
| 				format = 0x22; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				encoding = 0; | ||||
| 				format = 0x02; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		bw.seek(0); | ||||
| 		bw.write_8(sizeof(LABEL)); | ||||
| 		bw.append(LABEL); | ||||
| 		bw.seek(0x40); | ||||
| 		bw.write_be32(sectorDataEnd - sectorDataStart); /* data size */ | ||||
| 		bw.write_be32(tagDataEnd - sectorDataEnd); /* tag size */ | ||||
| 		bw.write_be32(dataChecksum); /* data checksum */ | ||||
| 		bw.write_be32(tagChecksum); /* tag checksum */ | ||||
| 		bw.write_8(encoding); /* encoding */ | ||||
| 		bw.write_8(format); /* format byte */ | ||||
| 		bw.write_be16(0x0100); /* magic number */ | ||||
|  | ||||
| 		image.writeToFile(spec.filename); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageWriter> ImageWriter::createDiskCopyImageWriter( | ||||
| 	const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
|     return std::unique_ptr<ImageWriter>(new DiskCopyImageWriter(sectors, spec)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,17 +1,19 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| std::map<std::string, ImageWriter::Constructor> ImageWriter::formats = | ||||
| { | ||||
| 	{".adf", ImageWriter::createImgImageWriter}, | ||||
| 	{".d64", ImageWriter::createD64ImageWriter}, | ||||
| 	{".d81", ImageWriter::createImgImageWriter}, | ||||
| 	{".diskcopy", ImageWriter::createDiskCopyImageWriter}, | ||||
| 	{".img", ImageWriter::createImgImageWriter}, | ||||
| 	{".ldbs", ImageWriter::createLDBSImageWriter}, | ||||
| }; | ||||
| @@ -45,7 +47,7 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const SectorSet& sectors, const | ||||
| void ImageWriter::verifyImageSpec(const ImageSpec& spec) | ||||
| { | ||||
| 	if (!findConstructor(spec)) | ||||
| 		Error() << "unrecognised image filename extension"; | ||||
| 		Error() << "unrecognised output image filename extension"; | ||||
| } | ||||
|  | ||||
| ImageWriter::ImageWriter(const SectorSet& sectors, const ImageSpec& spec): | ||||
| @@ -63,6 +65,56 @@ void ImageWriter::adjustGeometry() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ImageWriter::writeCsv(const std::string& filename) | ||||
| { | ||||
| 	std::ofstream f(filename, std::ios::out); | ||||
| 	if (!f.is_open()) | ||||
| 		Error() << "cannot open CSV report file"; | ||||
|  | ||||
| 	f << "\"Physical track\"," | ||||
| 		"\"Physical side\"," | ||||
| 		"\"Logical track\"," | ||||
| 		"\"Logical side\"," | ||||
| 		"\"Logical sector\"," | ||||
| 		"\"Clock (ns)\"," | ||||
| 		"\"Header start (ns)\"," | ||||
| 		"\"Header end (ns)\"," | ||||
| 		"\"Data start (ns)\"," | ||||
| 		"\"Data end (ns)\"," | ||||
| 		"\"Raw data address (bytes)\"," | ||||
| 		"\"User payload length (bytes)\"," | ||||
| 		"\"Status\"" | ||||
| 		"\n"; | ||||
|  | ||||
| 	for (int track = 0; track < spec.cylinders; track++) | ||||
| 	{ | ||||
| 		for (int head = 0; head < spec.heads; head++) | ||||
| 		{ | ||||
| 			for (int sectorId = 0; sectorId < spec.sectors; sectorId++) | ||||
| 			{ | ||||
| 				f << fmt::format("{},{},", track, head); | ||||
| 				const auto& sector = sectors.get(track, head, sectorId); | ||||
| 				if (!sector) | ||||
| 					f << fmt::format(",,{},,,,,,,,MISSING\n", sectorId); | ||||
| 				else | ||||
| 					f << fmt::format("{},{},{},{},{},{},{},{},{},{},{}\n", | ||||
| 						sector->logicalTrack, | ||||
| 						sector->logicalSide, | ||||
| 						sector->logicalSector, | ||||
| 						sector->clock, | ||||
| 						sector->headerStartTime, | ||||
| 						sector->headerEndTime, | ||||
| 						sector->dataStartTime, | ||||
| 						sector->dataEndTime, | ||||
| 						sector->position.bytes, | ||||
| 						sector->data.size(), | ||||
| 						Sector::statusToString(sector->status) | ||||
| 					); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ImageWriter::printMap() | ||||
| { | ||||
| 	int badSectors = 0; | ||||
|   | ||||
| @@ -29,12 +29,15 @@ private: | ||||
| 		const SectorSet& sectors, const ImageSpec& spec); | ||||
|     static std::unique_ptr<ImageWriter> createD64ImageWriter( | ||||
| 		const SectorSet& sectors, const ImageSpec& spec); | ||||
|     static std::unique_ptr<ImageWriter> createDiskCopyImageWriter( | ||||
| 		const SectorSet& sectors, const ImageSpec& spec); | ||||
|  | ||||
| 	static Constructor findConstructor(const ImageSpec& spec); | ||||
|  | ||||
| public: | ||||
| 	virtual void adjustGeometry(); | ||||
| 	void printMap(); | ||||
| 	void writeCsv(const std::string& filename); | ||||
| 	virtual void writeImage() = 0; | ||||
|  | ||||
| protected: | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| @@ -47,7 +46,7 @@ public: | ||||
| 					if (sector) | ||||
| 					{ | ||||
| 						outputFile.seekp(sector->logicalTrack*trackSize + sector->logicalSide*headSize + sector->logicalSector*numBytes, std::ios::beg); | ||||
| 						outputFile.write((const char*) sector->data.cbegin(), sector->data.size()); | ||||
| 						sector->data.slice(0, numBytes).writeTo(outputFile); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "usb.h" | ||||
| #include "usb/usb.h" | ||||
| #include "fluxsource/fluxsource.h" | ||||
| #include "fluxsink/fluxsink.h" | ||||
| #include "reader.h" | ||||
| #include "fluxmap.h" | ||||
| #include "sql.h" | ||||
| @@ -11,14 +12,21 @@ | ||||
| #include "sectorset.h" | ||||
| #include "visualiser.h" | ||||
| #include "record.h" | ||||
| #include "image.h" | ||||
| #include "bytes.h" | ||||
| #include "decoders/rawbits.h" | ||||
| #include "track.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| FlagGroup readerFlags { &hardwareFluxSourceFlags, &fluxmapReaderFlags, &visualiserFlags }; | ||||
| FlagGroup readerFlags | ||||
| { | ||||
| 	&hardwareFluxSourceFlags, | ||||
| 	&sqliteFluxSinkFlags, | ||||
| 	&fluxmapReaderFlags, | ||||
| 	&visualiserFlags | ||||
| }; | ||||
|  | ||||
| static DataSpecFlag source( | ||||
|     { "--source", "-s" }, | ||||
| @@ -61,7 +69,12 @@ static SettableFlag highDensityFlag( | ||||
| 	{ "--high-density", "--hd" }, | ||||
| 	"set the drive to high density mode"); | ||||
|  | ||||
| static sqlite3* outdb; | ||||
| static StringFlag csvFile( | ||||
| 	{ "--write-csv" }, | ||||
| 	"write a CSV report of the disk state", | ||||
| 	""); | ||||
|  | ||||
| static std::unique_ptr<FluxSink> outputFluxSink; | ||||
|  | ||||
| void setReaderDefaultSource(const std::string& source) | ||||
| { | ||||
| @@ -78,16 +91,31 @@ void setReaderRevolutions(int revolutions) | ||||
| 	setHardwareFluxSourceRevolutions(revolutions); | ||||
| } | ||||
|  | ||||
| void setReaderHardSectorCount(int sectorCount) | ||||
| { | ||||
|     setHardwareFluxSourceHardSectorCount(sectorCount); | ||||
| } | ||||
|  | ||||
| static void writeSectorsToFile(const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
| 	std::unique_ptr<ImageWriter> writer(ImageWriter::create(sectors, spec)); | ||||
| 	writer->adjustGeometry(); | ||||
| 	writer->printMap(); | ||||
| 	if (!csvFile.get().empty()) | ||||
| 		writer->writeCsv(csvFile.get()); | ||||
| 	writer->writeImage(); | ||||
| } | ||||
|  | ||||
| void Track::readFluxmap() | ||||
| { | ||||
| 	std::cout << fmt::format("{0:>3}.{1}: ", physicalTrack, physicalSide) << std::flush; | ||||
| 	fluxmap = fluxsource->readFlux(physicalTrack, physicalSide); | ||||
| 	std::cout << fmt::format( | ||||
| 		"{0} ms in {1} bytes\n", | ||||
|             int(fluxmap->duration()/1e6), | ||||
|             fluxmap->duration()/1e6, | ||||
|             fluxmap->bytes()); | ||||
| 	if (outdb) | ||||
| 		sqlWriteFlux(outdb, physicalTrack, physicalSide, *fluxmap); | ||||
| 	if (outputFluxSink) | ||||
| 		outputFluxSink->writeFlux(physicalTrack, physicalSide, *fluxmap); | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<Track>> readTracks() | ||||
| @@ -100,17 +128,8 @@ std::vector<std::unique_ptr<Track>> readTracks() | ||||
|  | ||||
| 	if (!destination.get().empty()) | ||||
| 	{ | ||||
| 		outdb = sqlOpen(destination, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); | ||||
| 		std::cout << "Writing a copy of the flux to " << destination.get() << std::endl; | ||||
| 		sqlPrepareFlux(outdb); | ||||
| 		sqlStmt(outdb, "BEGIN;"); | ||||
|         sqlWriteIntProperty(outdb, "version", FLUX_VERSION_CURRENT); | ||||
| 		atexit([]() | ||||
| 			{ | ||||
| 				sqlStmt(outdb, "COMMIT;"); | ||||
| 				sqlClose(outdb); | ||||
| 			} | ||||
| 		); | ||||
| 		outputFluxSink = FluxSink::createSqliteFluxSink(destination.get()); | ||||
| 	} | ||||
|  | ||||
| 	std::shared_ptr<FluxSource> fluxSource = FluxSource::create(spec); | ||||
| @@ -178,8 +197,6 @@ void readDiskCommand(AbstractDecoder& decoder) | ||||
| 			decoder.decodeToSectors(*track); | ||||
|  | ||||
| 			std::cout << "       "; | ||||
| 			if (!track->sectors.empty()) | ||||
| 			{ | ||||
| 				std::cout << fmt::format("{} records, {} sectors; ", | ||||
| 					track->rawrecords.size(), | ||||
| 					track->sectors.size()); | ||||
| @@ -195,9 +212,12 @@ void readDiskCommand(AbstractDecoder& decoder) | ||||
| 				} | ||||
|  | ||||
| 				bool hasBadSectors = false; | ||||
| 				std::set<unsigned> requiredSectors = decoder.requiredSectors(*track); | ||||
| 				for (const auto& i : readSectors) | ||||
| 				{ | ||||
| 					const auto& sector = i.second; | ||||
| 					requiredSectors.erase(sector->logicalSector); | ||||
|  | ||||
| 					if (sector->status != Sector::OK) | ||||
| 					{ | ||||
| 						std::cout << std::endl | ||||
| @@ -206,6 +226,12 @@ void readDiskCommand(AbstractDecoder& decoder) | ||||
| 						hasBadSectors = true; | ||||
| 					} | ||||
| 				} | ||||
| 				for (unsigned logicalSector : requiredSectors) | ||||
| 				{ | ||||
| 					std::cout << "\n" | ||||
| 					          << "       Required sector " << logicalSector << " missing; "; | ||||
| 					hasBadSectors = true; | ||||
| 				} | ||||
|  | ||||
| 				if (hasBadSectors) | ||||
| 					failures = false; | ||||
| @@ -215,7 +241,6 @@ void readDiskCommand(AbstractDecoder& decoder) | ||||
|  | ||||
| 				if (!hasBadSectors) | ||||
| 					break; | ||||
| 			} | ||||
|  | ||||
| 			if (!track->fluxsource->retryable()) | ||||
| 				break; | ||||
| @@ -241,13 +266,13 @@ void readDiskCommand(AbstractDecoder& decoder) | ||||
|         if (dumpSectors) | ||||
|         { | ||||
|             std::cout << "\nDecoded sectors follow:\n\n"; | ||||
|             for (auto& i : readSectors) | ||||
|             for (auto& sector : track->sectors) | ||||
|             { | ||||
|                 auto& sector = i.second; | ||||
| 				std::cout << fmt::format("{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock\n", | ||||
|                             sector->logicalTrack, sector->logicalSide, sector->logicalSector, | ||||
|                             sector->position.ns() / 1000.0, sector->clock / 1000.0); | ||||
| 				hexdump(std::cout, sector->data); | ||||
| 				std::cout << fmt::format("{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: status {}\n", | ||||
|                             sector.logicalTrack, sector.logicalSide, sector.logicalSector, | ||||
|                             sector.position.ns() / 1000.0, sector.clock / 1000.0, | ||||
| 							sector.status); | ||||
| 				hexdump(std::cout, sector.data); | ||||
| 				std::cout << std::endl; | ||||
|             } | ||||
|         } | ||||
| @@ -276,7 +301,7 @@ void readDiskCommand(AbstractDecoder& decoder) | ||||
|  | ||||
| 	if (!visualise.get().empty()) | ||||
| 		visualiseSectorsToFile(allSectors, visualise.get()); | ||||
|  | ||||
| 	 | ||||
|     writeSectorsToFile(allSectors, outputSpec); | ||||
| 	if (failures) | ||||
| 		std::cerr << "Warning: some sectors could not be decoded." << std::endl; | ||||
|   | ||||
| @@ -13,6 +13,7 @@ extern FlagGroup readerFlags; | ||||
| extern void setReaderDefaultSource(const std::string& source); | ||||
| extern void setReaderDefaultOutput(const std::string& output); | ||||
| extern void setReaderRevolutions(int revolutions); | ||||
| extern void setReaderHardSectorCount(int sectorCount); | ||||
|  | ||||
| extern std::vector<std::unique_ptr<Track>> readTracks(); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
|  | ||||
|   | ||||
| @@ -10,8 +10,9 @@ enum | ||||
|     FLUX_VERSION_0, /* without properties table */ | ||||
|     FLUX_VERSION_1, | ||||
|     FLUX_VERSION_2, /* new bytecode with index marks */ | ||||
| 	FLUX_VERSION_3, /* simplified bytecode with six-bit timer */ | ||||
|  | ||||
|     FLUX_VERSION_CURRENT = 2, | ||||
|     FLUX_VERSION_CURRENT = FLUX_VERSION_3, | ||||
| }; | ||||
|  | ||||
| extern void sqlCheck(sqlite3* db, int i); | ||||
|   | ||||
							
								
								
									
										306
									
								
								lib/usb.cc
									
									
									
									
									
								
							
							
						
						
									
										306
									
								
								lib/usb.cc
									
									
									
									
									
								
							| @@ -1,306 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "usb.h" | ||||
| #include "protocol.h" | ||||
| #include "fluxmap.h" | ||||
| #include "bytes.h" | ||||
| #include "common/crunch.h" | ||||
| #include <libusb.h> | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| #define TIMEOUT 5000 | ||||
|  | ||||
| static libusb_device_handle* device; | ||||
|  | ||||
| static uint8_t buffer[FRAME_SIZE]; | ||||
|  | ||||
| static std::string usberror(int i) | ||||
| { | ||||
|     return libusb_strerror((libusb_error) i); | ||||
| } | ||||
|  | ||||
| static void usb_init() | ||||
| { | ||||
|     if (device) | ||||
|         return; | ||||
|  | ||||
|     int i = libusb_init(NULL); | ||||
|     if (i < 0) | ||||
|         Error() << "could not start libusb: " << usberror(i); | ||||
|  | ||||
|     device = libusb_open_device_with_vid_pid(NULL, FLUXENGINE_VID, FLUXENGINE_PID); | ||||
|     if (!device) | ||||
| 		Error() << "cannot find the FluxEngine (is it plugged in?)"; | ||||
|      | ||||
|     int cfg = -1; | ||||
|     libusb_get_configuration(device, &cfg); | ||||
|     if (cfg != 1) | ||||
|     { | ||||
|         i = libusb_set_configuration(device, 1); | ||||
|         if (i < 0) | ||||
|             Error() << "the FluxEngine would not accept configuration: " << usberror(i); | ||||
|     } | ||||
|  | ||||
|     i = libusb_claim_interface(device, 0); | ||||
|     if (i < 0) | ||||
|         Error() << "could not claim interface: " << usberror(i); | ||||
|  | ||||
|     int version = usbGetVersion(); | ||||
|     if (version != FLUXENGINE_VERSION) | ||||
|         Error() << "your FluxEngine firmware is at version " << version | ||||
|                 << " but the client is for version " << FLUXENGINE_VERSION | ||||
|                 << "; please upgrade"; | ||||
| } | ||||
|  | ||||
| static int usb_cmd_send(void* ptr, int len) | ||||
| { | ||||
|     //std::cerr << "send:\n"; | ||||
|     //hexdump(std::cerr, Bytes((const uint8_t*)ptr, len)); | ||||
|     int i = libusb_interrupt_transfer(device, FLUXENGINE_CMD_OUT_EP, | ||||
|         (uint8_t*) ptr, len, &len, TIMEOUT); | ||||
|     if (i < 0) | ||||
|         Error() << "failed to send command: " << usberror(i); | ||||
|     return len; | ||||
| } | ||||
|  | ||||
| void usb_cmd_recv(void* ptr, int len) | ||||
| { | ||||
|     int i = libusb_interrupt_transfer(device, FLUXENGINE_CMD_IN_EP, | ||||
|        (uint8_t*)  ptr, len, &len, TIMEOUT); | ||||
|     if (i < 0) | ||||
|         Error() << "failed to receive command reply: " << usberror(i); | ||||
|     //std::cerr << "recv:\n"; | ||||
|     //hexdump(std::cerr, Bytes((const uint8_t*)ptr, len)); | ||||
| } | ||||
|  | ||||
| static void bad_reply(void) | ||||
| { | ||||
|     struct error_frame* f = (struct error_frame*) buffer; | ||||
|     if (f->f.type != F_FRAME_ERROR) | ||||
|         Error() << fmt::format("bad USB reply 0x{:2x}", f->f.type); | ||||
|     switch (f->error) | ||||
|     { | ||||
|         case F_ERROR_BAD_COMMAND: | ||||
|             Error() << "device did not understand command"; | ||||
|  | ||||
|         case F_ERROR_UNDERRUN: | ||||
|             Error() << "USB underrun (not enough bandwidth)"; | ||||
|              | ||||
|         default: | ||||
|             Error() << fmt::format("unknown device error {}", f->error); | ||||
|     } | ||||
| } | ||||
|  | ||||
| template <typename T> | ||||
| static T* await_reply(int desired) | ||||
| { | ||||
|     for (;;) | ||||
|     { | ||||
|         usb_cmd_recv(buffer, sizeof(buffer)); | ||||
|         struct any_frame* r = (struct any_frame*) buffer; | ||||
|         if (r->f.type == F_FRAME_DEBUG) | ||||
|         { | ||||
|             std::cout << "dev: " << ((struct debug_frame*)r)->payload << std::endl; | ||||
|             continue; | ||||
|         } | ||||
|         if (r->f.type != desired) | ||||
|             bad_reply(); | ||||
|         return (T*) r; | ||||
|     } | ||||
| } | ||||
|  | ||||
| int usbGetVersion(void) | ||||
| { | ||||
|     usb_init(); | ||||
|  | ||||
|     struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|     auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY); | ||||
|     return r->version; | ||||
| } | ||||
|  | ||||
| void usbSeek(int track) | ||||
| { | ||||
|     usb_init(); | ||||
|  | ||||
|     struct seek_frame f = { | ||||
|         { .type = F_FRAME_SEEK_CMD, .size = sizeof(f) }, | ||||
|         .track = (uint8_t) track | ||||
|     }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|     await_reply<struct any_frame>(F_FRAME_SEEK_REPLY); | ||||
| } | ||||
|  | ||||
| void usbRecalibrate() | ||||
| { | ||||
|     usb_init(); | ||||
|  | ||||
|     struct any_frame f = { | ||||
|         { .type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f) }, | ||||
|     }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|     await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY); | ||||
| } | ||||
|  | ||||
| nanoseconds_t usbGetRotationalPeriod(void) | ||||
| { | ||||
|     usb_init(); | ||||
|  | ||||
|     struct any_frame f = { .f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)} }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
|     auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY); | ||||
|     return r->period_ms * 1000000; | ||||
| } | ||||
|  | ||||
| static int large_bulk_transfer(int ep, Bytes& bytes) | ||||
| { | ||||
|     int len; | ||||
|     int i = libusb_bulk_transfer(device, ep, bytes.begin(), bytes.size(), &len, TIMEOUT); | ||||
|     if (i < 0) | ||||
|         Error() << "data transfer failed: " << usberror(i); | ||||
|     return len; | ||||
| } | ||||
|  | ||||
| void usbTestBulkTransport() | ||||
| { | ||||
|     usb_init(); | ||||
|  | ||||
|     struct any_frame f = { .f = {.type = F_FRAME_BULK_TEST_CMD, .size = sizeof(f)} }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
|     /* These must match the device. */ | ||||
|     const int XSIZE = 64; | ||||
|     const int YSIZE = 256; | ||||
|     const int ZSIZE = 64; | ||||
|  | ||||
|     Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE); | ||||
|     double start_time = getCurrentTime(); | ||||
|     large_bulk_transfer(FLUXENGINE_DATA_IN_EP, bulk_buffer); | ||||
|     double elapsed_time = getCurrentTime() - start_time; | ||||
|  | ||||
|     std::cout << "Transferred " | ||||
|               << bulk_buffer.size() | ||||
|               << " bytes in " | ||||
|               << int(elapsed_time * 1000.0) | ||||
|               << " (" | ||||
|               << int((bulk_buffer.size() / 1024.0) / elapsed_time) | ||||
|               << " kB/s)" | ||||
|               << std::endl; | ||||
|  | ||||
|     for (int x=0; x<XSIZE; x++) | ||||
|         for (int y=0; y<YSIZE; y++) | ||||
|             for (int z=0; z<ZSIZE; z++) | ||||
|             { | ||||
|                 int offset = x*XSIZE*YSIZE + y*ZSIZE + z; | ||||
|                 if (bulk_buffer[offset] != uint8_t(x+y+z)) | ||||
|                     Error() << "data transfer corrupted at 0x" | ||||
|                             << std::hex << offset << std::dec | ||||
|                             << " " | ||||
|                             << x << '.' << y << '.' << z << '.'; | ||||
|             } | ||||
|  | ||||
|     await_reply<struct any_frame>(F_FRAME_BULK_TEST_REPLY); | ||||
| } | ||||
|  | ||||
| Bytes usbRead(int side, bool synced, nanoseconds_t readTime) | ||||
| { | ||||
|     struct read_frame f = { | ||||
|         .f = { .type = F_FRAME_READ_CMD, .size = sizeof(f) }, | ||||
|         .side = (uint8_t) side, | ||||
|         .synced = (uint8_t) synced | ||||
|     }; | ||||
|     uint16_t milliseconds = readTime / 1e6; | ||||
|     ((uint8_t*)&f.milliseconds)[0] = milliseconds; | ||||
|     ((uint8_t*)&f.milliseconds)[1] = milliseconds >> 8; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
|     auto fluxmap = std::unique_ptr<Fluxmap>(new Fluxmap); | ||||
|  | ||||
|     Bytes buffer(1024*1024); | ||||
|     int len = large_bulk_transfer(FLUXENGINE_DATA_IN_EP, buffer); | ||||
|     buffer.resize(len); | ||||
|  | ||||
|     await_reply<struct any_frame>(F_FRAME_READ_REPLY); | ||||
|     return buffer; | ||||
| } | ||||
|  | ||||
| void usbWrite(int side, const Bytes& bytes) | ||||
| { | ||||
|     unsigned safelen = bytes.size() & ~(FRAME_SIZE-1); | ||||
|     Bytes safeBytes = bytes.slice(0, safelen); | ||||
|  | ||||
|     struct write_frame f = { | ||||
|         .f = { .type = F_FRAME_WRITE_CMD, .size = sizeof(f) }, | ||||
|         .side = (uint8_t) side, | ||||
|     }; | ||||
|     ((uint8_t*)&f.bytes_to_write)[0] = safelen; | ||||
|     ((uint8_t*)&f.bytes_to_write)[1] = safelen >> 8; | ||||
|     ((uint8_t*)&f.bytes_to_write)[2] = safelen >> 16; | ||||
|     ((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24; | ||||
|  | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
|     large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, safeBytes); | ||||
|      | ||||
|     await_reply<struct any_frame>(F_FRAME_WRITE_REPLY); | ||||
| } | ||||
|  | ||||
| void usbErase(int side) | ||||
| { | ||||
|     struct erase_frame f = { | ||||
|         .f = { .type = F_FRAME_ERASE_CMD, .size = sizeof(f) }, | ||||
|         .side = (uint8_t) side, | ||||
|     }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
|     await_reply<struct any_frame>(F_FRAME_ERASE_REPLY); | ||||
| } | ||||
|  | ||||
| void usbSetDrive(int drive, bool high_density, int index_mode) | ||||
| { | ||||
|     usb_init(); | ||||
|  | ||||
|     struct set_drive_frame f = { | ||||
|         { .type = F_FRAME_SET_DRIVE_CMD, .size = sizeof(f) }, | ||||
|         .drive = (uint8_t) drive, | ||||
|         .high_density = high_density, | ||||
|         .index_mode = (uint8_t) index_mode | ||||
|     }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|     await_reply<struct any_frame>(F_FRAME_SET_DRIVE_REPLY); | ||||
| } | ||||
|  | ||||
| /* Hacky: the board always operates in little-endian mode. */ | ||||
| static uint16_t read_short_from_usb(uint16_t usb) | ||||
| { | ||||
|     uint8_t* p = (uint8_t*)&usb; | ||||
|     return p[0] | (p[1] << 8); | ||||
| } | ||||
|  | ||||
| static void convert_voltages_from_usb(const struct voltages& vin, struct voltages& vout) | ||||
| { | ||||
|     vout.logic0_mv = read_short_from_usb(vin.logic0_mv); | ||||
|     vout.logic1_mv = read_short_from_usb(vin.logic1_mv); | ||||
| } | ||||
|  | ||||
| void usbMeasureVoltages(struct voltages_frame* voltages) | ||||
| { | ||||
|     usb_init(); | ||||
|  | ||||
|     struct any_frame f = { | ||||
|         { .type = F_FRAME_MEASURE_VOLTAGES_CMD, .size = sizeof(f) }, | ||||
|     }; | ||||
|     usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
|     struct voltages_frame* r = await_reply<struct voltages_frame>(F_FRAME_MEASURE_VOLTAGES_REPLY); | ||||
|     convert_voltages_from_usb(r->input_both_off, voltages->input_both_off); | ||||
|     convert_voltages_from_usb(r->input_drive_0_selected, voltages->input_drive_0_selected); | ||||
|     convert_voltages_from_usb(r->input_drive_1_selected, voltages->input_drive_1_selected); | ||||
|     convert_voltages_from_usb(r->input_drive_0_running, voltages->input_drive_0_running); | ||||
|     convert_voltages_from_usb(r->input_drive_1_running, voltages->input_drive_1_running); | ||||
|     convert_voltages_from_usb(r->output_both_off, voltages->output_both_off); | ||||
|     convert_voltages_from_usb(r->output_drive_0_selected, voltages->output_drive_0_selected); | ||||
|     convert_voltages_from_usb(r->output_drive_1_selected, voltages->output_drive_1_selected); | ||||
|     convert_voltages_from_usb(r->output_drive_0_running, voltages->output_drive_0_running); | ||||
|     convert_voltages_from_usb(r->output_drive_1_running, voltages->output_drive_1_running); | ||||
| } | ||||
							
								
								
									
										18
									
								
								lib/usb.h
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								lib/usb.h
									
									
									
									
									
								
							| @@ -1,18 +0,0 @@ | ||||
| #ifndef USB_H | ||||
| #define USB_H | ||||
|  | ||||
| class Fluxmap; | ||||
| class Bytes; | ||||
|  | ||||
| extern int usbGetVersion(); | ||||
| extern void usbRecalibrate(); | ||||
| extern void usbSeek(int track); | ||||
| extern nanoseconds_t usbGetRotationalPeriod(); | ||||
| extern void usbTestBulkTransport(); | ||||
| extern Bytes usbRead(int side, bool synced, nanoseconds_t readTime); | ||||
| extern void usbWrite(int side, const Bytes& bytes); | ||||
| extern void usbErase(int side); | ||||
| extern void usbSetDrive(int drive, bool high_density, int index_mode); | ||||
| extern void usbMeasureVoltages(struct voltages_frame* voltages); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										333
									
								
								lib/usb/fluxengineusb.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								lib/usb/fluxengineusb.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,333 @@ | ||||
| #include "globals.h" | ||||
| #include "usb.h" | ||||
| #include "protocol.h" | ||||
| #include "fluxmap.h" | ||||
| #include "bytes.h" | ||||
| #include <libusb.h> | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| #define TIMEOUT 5000 | ||||
|  | ||||
| /* Hacky: the board always operates in little-endian mode. */ | ||||
| static uint16_t read_short_from_usb(uint16_t usb) | ||||
| { | ||||
| 	uint8_t* p = (uint8_t*)&usb; | ||||
| 	return p[0] | (p[1] << 8); | ||||
| } | ||||
|  | ||||
| class FluxEngineUsb : public USB | ||||
| { | ||||
| private: | ||||
| 	uint8_t _buffer[FRAME_SIZE]; | ||||
|  | ||||
| 	int usb_cmd_send(void* ptr, int len) | ||||
| 	{ | ||||
| 		//std::cerr << "send:\n"; | ||||
| 		//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len)); | ||||
| 		int i = libusb_interrupt_transfer(_device, FLUXENGINE_CMD_OUT_EP, | ||||
| 			(uint8_t*) ptr, len, &len, TIMEOUT); | ||||
| 		if (i < 0) | ||||
| 			Error() << "failed to send command: " << usberror(i); | ||||
| 		return len; | ||||
| 	} | ||||
|  | ||||
| 	void usb_cmd_recv(void* ptr, int len) | ||||
| 	{ | ||||
| 		int i = libusb_interrupt_transfer(_device, FLUXENGINE_CMD_IN_EP, | ||||
| 		   (uint8_t*)  ptr, len, &len, TIMEOUT); | ||||
| 		if (i < 0) | ||||
| 			Error() << "failed to receive command reply: " << usberror(i); | ||||
| 		//std::cerr << "recv:\n"; | ||||
| 		//hexdump(std::cerr, Bytes((const uint8_t*)ptr, len)); | ||||
| 	} | ||||
|  | ||||
| 	int large_bulk_transfer(int ep, Bytes& bytes) | ||||
| 	{ | ||||
| 		if (bytes.size() == 0) | ||||
| 			return 0; | ||||
|  | ||||
| 		int len; | ||||
| 		int i = libusb_bulk_transfer(_device, ep, bytes.begin(), bytes.size(), &len, TIMEOUT); | ||||
| 		if (i < 0) | ||||
| 			Error() << fmt::format("data transfer failed at {} bytes: {}", len, usberror(i)); | ||||
| 		return len; | ||||
| 	} | ||||
|  | ||||
| public: | ||||
| 	FluxEngineUsb(libusb_device_handle* device) | ||||
| 	{ | ||||
| 		_device = device; | ||||
|  | ||||
| 		int i; | ||||
| 		int cfg = -1; | ||||
| 		libusb_get_configuration(_device, &cfg); | ||||
| 		if (cfg != 1) | ||||
| 		{ | ||||
| 			i = libusb_set_configuration(_device, 1); | ||||
| 			if (i < 0) | ||||
| 				Error() << "the FluxEngine would not accept configuration: " << usberror(i); | ||||
| 		} | ||||
|  | ||||
| 		i = libusb_claim_interface(_device, 0); | ||||
| 		if (i < 0) | ||||
| 			Error() << "could not claim interface: " << usberror(i); | ||||
|  | ||||
| 		int version = getVersion(); | ||||
| 		if (version != FLUXENGINE_VERSION) | ||||
| 			Error() << "your FluxEngine firmware is at version " << version | ||||
| 					<< " but the client is for version " << FLUXENGINE_VERSION | ||||
| 					<< "; please upgrade"; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	void bad_reply(void) | ||||
| 	{ | ||||
| 		struct error_frame* f = (struct error_frame*) _buffer; | ||||
| 		if (f->f.type != F_FRAME_ERROR) | ||||
| 			Error() << fmt::format("bad USB reply 0x{:2x}", f->f.type); | ||||
| 		switch (f->error) | ||||
| 		{ | ||||
| 			case F_ERROR_BAD_COMMAND: | ||||
| 				Error() << "device did not understand command"; | ||||
|  | ||||
| 			case F_ERROR_UNDERRUN: | ||||
| 				Error() << "USB underrun (not enough bandwidth)"; | ||||
| 				 | ||||
| 			default: | ||||
| 				Error() << fmt::format("unknown device error {}", f->error); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	template <typename T> | ||||
| 	T* await_reply(int desired) | ||||
| 	{ | ||||
| 		for (;;) | ||||
| 		{ | ||||
| 			usb_cmd_recv(_buffer, sizeof(_buffer)); | ||||
| 			struct any_frame* r = (struct any_frame*) _buffer; | ||||
| 			if (r->f.type == F_FRAME_DEBUG) | ||||
| 			{ | ||||
| 				std::cout << "dev: " << ((struct debug_frame*)r)->payload << std::endl; | ||||
| 				continue; | ||||
| 			} | ||||
| 			if (r->f.type != desired) | ||||
| 				bad_reply(); | ||||
| 			return (T*) r; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| public: | ||||
| 	int getVersion() | ||||
| 	{ | ||||
| 		struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} }; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
| 		auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY); | ||||
| 		return r->version; | ||||
| 	} | ||||
|  | ||||
| 	void seek(int track) | ||||
| 	{ | ||||
| 		struct seek_frame f = { | ||||
| 			{ .type = F_FRAME_SEEK_CMD, .size = sizeof(f) }, | ||||
| 			.track = (uint8_t) track | ||||
| 		}; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
| 		await_reply<struct any_frame>(F_FRAME_SEEK_REPLY); | ||||
| 	} | ||||
|  | ||||
| 	void recalibrate() | ||||
| 	{ | ||||
| 		struct any_frame f = { | ||||
| 			{ .type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f) }, | ||||
| 		}; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
| 		await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY); | ||||
| 	} | ||||
|  | ||||
| 	nanoseconds_t getRotationalPeriod(int hardSectorCount) | ||||
| 	{ | ||||
| 		struct measurespeed_frame f = { | ||||
| 			.f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)}, | ||||
| 			.hard_sector_count = (uint8_t) hardSectorCount, | ||||
| 		}; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
| 		auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY); | ||||
| 		return r->period_ms * 1000000; | ||||
| 	} | ||||
|  | ||||
| 	void testBulkWrite() | ||||
| 	{ | ||||
| 		struct any_frame f = { .f = {.type = F_FRAME_BULK_WRITE_TEST_CMD, .size = sizeof(f)} }; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
| 		/* These must match the device. */ | ||||
| 		const int XSIZE = 64; | ||||
| 		const int YSIZE = 256; | ||||
| 		const int ZSIZE = 64; | ||||
|  | ||||
| 		Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE); | ||||
| 		double start_time = getCurrentTime(); | ||||
| 		large_bulk_transfer(FLUXENGINE_DATA_IN_EP, bulk_buffer); | ||||
| 		double elapsed_time = getCurrentTime() - start_time; | ||||
|  | ||||
| 		std::cout << "Transferred " | ||||
| 				  << bulk_buffer.size() | ||||
| 				  << " bytes from FluxEngine -> PC in " | ||||
| 				  << int(elapsed_time * 1000.0) | ||||
| 				  << " ms (" | ||||
| 				  << int((bulk_buffer.size() / 1024.0) / elapsed_time) | ||||
| 				  << " kB/s)" | ||||
| 				  << std::endl; | ||||
|  | ||||
| 		for (int x=0; x<XSIZE; x++) | ||||
| 			for (int y=0; y<YSIZE; y++) | ||||
| 				for (int z=0; z<ZSIZE; z++) | ||||
| 				{ | ||||
| 					int offset = x*XSIZE*YSIZE + y*ZSIZE + z; | ||||
| 					if (bulk_buffer[offset] != uint8_t(x+y+z)) | ||||
| 						Error() << "data transfer corrupted at 0x" | ||||
| 								<< std::hex << offset << std::dec | ||||
| 								<< " " | ||||
| 								<< x << '.' << y << '.' << z << '.'; | ||||
| 				} | ||||
|  | ||||
| 		await_reply<struct any_frame>(F_FRAME_BULK_WRITE_TEST_REPLY); | ||||
| 	} | ||||
|  | ||||
| 	void testBulkRead() | ||||
| 	{ | ||||
| 		struct any_frame f = { .f = {.type = F_FRAME_BULK_READ_TEST_CMD, .size = sizeof(f)} }; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
| 		/* These must match the device. */ | ||||
| 		const int XSIZE = 64; | ||||
| 		const int YSIZE = 256; | ||||
| 		const int ZSIZE = 64; | ||||
|  | ||||
| 		Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE); | ||||
| 		for (int x=0; x<XSIZE; x++) | ||||
| 			for (int y=0; y<YSIZE; y++) | ||||
| 				for (int z=0; z<ZSIZE; z++) | ||||
| 				{ | ||||
| 					int offset = x*XSIZE*YSIZE + y*ZSIZE + z; | ||||
| 					bulk_buffer[offset] = uint8_t(x+y+z); | ||||
| 				} | ||||
|  | ||||
| 		double start_time = getCurrentTime(); | ||||
| 		large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, bulk_buffer); | ||||
| 		double elapsed_time = getCurrentTime() - start_time; | ||||
|  | ||||
| 		std::cout << "Transferred " | ||||
| 				  << bulk_buffer.size() | ||||
| 				  << " bytes from PC -> FluxEngine in " | ||||
| 				  << int(elapsed_time * 1000.0) | ||||
| 				  << " ms (" | ||||
| 				  << int((bulk_buffer.size() / 1024.0) / elapsed_time) | ||||
| 				  << " kB/s)" | ||||
| 				  << std::endl; | ||||
|  | ||||
| 		await_reply<struct any_frame>(F_FRAME_BULK_READ_TEST_REPLY); | ||||
| 	} | ||||
|  | ||||
| 	Bytes read(int side, bool synced, nanoseconds_t readTime, | ||||
| 	           nanoseconds_t hardSectorThreshold) | ||||
| 	{ | ||||
| 		struct read_frame f = { | ||||
| 			.f = { .type = F_FRAME_READ_CMD, .size = sizeof(f) }, | ||||
| 			.side = (uint8_t) side, | ||||
| 			.synced = (uint8_t) synced, | ||||
| 		}; | ||||
| 		f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */ | ||||
| 		uint16_t milliseconds = readTime / 1e6; | ||||
| 		((uint8_t*)&f.milliseconds)[0] = milliseconds; | ||||
| 		((uint8_t*)&f.milliseconds)[1] = milliseconds >> 8; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
| 		auto fluxmap = std::unique_ptr<Fluxmap>(new Fluxmap); | ||||
|  | ||||
| 		Bytes buffer(1024*1024); | ||||
| 		int len = large_bulk_transfer(FLUXENGINE_DATA_IN_EP, buffer); | ||||
| 		buffer.resize(len); | ||||
|  | ||||
| 		await_reply<struct any_frame>(F_FRAME_READ_REPLY); | ||||
| 		return buffer; | ||||
| 	} | ||||
|  | ||||
| 	void write(int side, const Bytes& bytes, nanoseconds_t hardSectorThreshold) | ||||
| 	{ | ||||
| 		unsigned safelen = bytes.size() & ~(FRAME_SIZE-1); | ||||
| 		Bytes safeBytes = bytes.slice(0, safelen); | ||||
|  | ||||
| 		struct write_frame f = { | ||||
| 			.f = { .type = F_FRAME_WRITE_CMD, .size = sizeof(f) }, | ||||
| 			.side = (uint8_t) side, | ||||
| 		}; | ||||
| 		f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */ | ||||
| 		((uint8_t*)&f.bytes_to_write)[0] = safelen; | ||||
| 		((uint8_t*)&f.bytes_to_write)[1] = safelen >> 8; | ||||
| 		((uint8_t*)&f.bytes_to_write)[2] = safelen >> 16; | ||||
| 		((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24; | ||||
|  | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
| 		large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, safeBytes); | ||||
| 		 | ||||
| 		await_reply<struct any_frame>(F_FRAME_WRITE_REPLY); | ||||
| 	} | ||||
|  | ||||
| 	void erase(int side, nanoseconds_t hardSectorThreshold) | ||||
| 	{ | ||||
| 		struct erase_frame f = { | ||||
| 			.f = { .type = F_FRAME_ERASE_CMD, .size = sizeof(f) }, | ||||
| 			.side = (uint8_t) side, | ||||
| 		}; | ||||
| 		f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */ | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
| 		await_reply<struct any_frame>(F_FRAME_ERASE_REPLY); | ||||
| 	} | ||||
|  | ||||
| 	void setDrive(int drive, bool high_density, int index_mode) | ||||
| 	{ | ||||
| 		struct set_drive_frame f = { | ||||
| 			{ .type = F_FRAME_SET_DRIVE_CMD, .size = sizeof(f) }, | ||||
| 			.drive = (uint8_t) drive, | ||||
| 			.high_density = high_density, | ||||
| 			.index_mode = (uint8_t) index_mode | ||||
| 		}; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
| 		await_reply<struct any_frame>(F_FRAME_SET_DRIVE_REPLY); | ||||
| 	} | ||||
|  | ||||
| 	void measureVoltages(struct voltages_frame* voltages) | ||||
| 	{ | ||||
| 		struct any_frame f = { | ||||
| 			{ .type = F_FRAME_MEASURE_VOLTAGES_CMD, .size = sizeof(f) }, | ||||
| 		}; | ||||
| 		usb_cmd_send(&f, f.f.size); | ||||
|  | ||||
| 		auto convert_voltages_from_usb = [&](const struct voltages& vin, struct voltages& vout) | ||||
| 		{ | ||||
| 			vout.logic0_mv = read_short_from_usb(vin.logic0_mv); | ||||
| 			vout.logic1_mv = read_short_from_usb(vin.logic1_mv); | ||||
| 		}; | ||||
|  | ||||
| 		struct voltages_frame* r = await_reply<struct voltages_frame>(F_FRAME_MEASURE_VOLTAGES_REPLY); | ||||
| 		convert_voltages_from_usb(r->input_both_off, voltages->input_both_off); | ||||
| 		convert_voltages_from_usb(r->input_drive_0_selected, voltages->input_drive_0_selected); | ||||
| 		convert_voltages_from_usb(r->input_drive_1_selected, voltages->input_drive_1_selected); | ||||
| 		convert_voltages_from_usb(r->input_drive_0_running, voltages->input_drive_0_running); | ||||
| 		convert_voltages_from_usb(r->input_drive_1_running, voltages->input_drive_1_running); | ||||
| 		convert_voltages_from_usb(r->output_both_off, voltages->output_both_off); | ||||
| 		convert_voltages_from_usb(r->output_drive_0_selected, voltages->output_drive_0_selected); | ||||
| 		convert_voltages_from_usb(r->output_drive_1_selected, voltages->output_drive_1_selected); | ||||
| 		convert_voltages_from_usb(r->output_drive_0_running, voltages->output_drive_0_running); | ||||
| 		convert_voltages_from_usb(r->output_drive_1_running, voltages->output_drive_1_running); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| USB* createFluxengineUsb(libusb_device_handle* device) | ||||
| { | ||||
| 	return new FluxEngineUsb(device); | ||||
| } | ||||
|  | ||||
							
								
								
									
										156
									
								
								lib/usb/greaseweazle.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/usb/greaseweazle.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| #include "globals.h" | ||||
| #include "usb.h" | ||||
| #include "protocol.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include "greaseweazle.h" | ||||
|  | ||||
| Bytes fluxEngineToGreaseWeazle(const Bytes& fldata, nanoseconds_t clock) | ||||
| { | ||||
| 	Bytes gwdata; | ||||
| 	ByteWriter bw(gwdata); | ||||
| 	ByteReader br(fldata); | ||||
| 	uint32_t ticks_fl = 0; | ||||
| 	uint32_t ticks_gw = 0; | ||||
|  | ||||
| 	auto write_28 = [&](uint32_t val) { | ||||
| 		bw.write_8(1 | (val<<1) & 0xff); | ||||
| 		bw.write_8(1 | (val>>6) & 0xff); | ||||
| 		bw.write_8(1 | (val>>13) & 0xff); | ||||
| 		bw.write_8(1 | (val>>20) & 0xff); | ||||
| 	}; | ||||
|  | ||||
| 	while (!br.eof()) | ||||
| 	{ | ||||
| 		uint8_t b = br.read_8(); | ||||
| 		ticks_fl += b & 0x3f; | ||||
| 		if (b & F_BIT_PULSE) | ||||
| 		{ | ||||
| 			uint32_t newticks_gw = ticks_fl * NS_PER_TICK / clock; | ||||
| 			uint32_t delta = newticks_gw - ticks_gw; | ||||
| 			if (delta < 250) | ||||
| 				bw.write_8(delta); | ||||
| 			else | ||||
| 			{ | ||||
| 				int high = (delta-250) / 255; | ||||
| 				if (high < 5) | ||||
| 				{ | ||||
| 					bw.write_8(250 + high); | ||||
| 					bw.write_8(1 + (delta-250) % 255); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					bw.write_8(255); | ||||
| 					bw.write_8(FLUXOP_SPACE); | ||||
| 					write_28(delta - 249); | ||||
| 					bw.write_8(249); | ||||
| 				} | ||||
| 			} | ||||
| 			ticks_gw = newticks_gw; | ||||
| 		} | ||||
| 	} | ||||
| 	bw.write_8(0); /* end of stream */ | ||||
| 	return gwdata; | ||||
| } | ||||
|  | ||||
| Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock) | ||||
| { | ||||
| 	Bytes fldata; | ||||
| 	ByteReader br(gwdata); | ||||
| 	ByteWriter bw(fldata); | ||||
|  | ||||
|     auto read_28 = [&]() { | ||||
|         return ((br.read_8() & 0xfe) >> 1) | ||||
|             | ((br.read_8() & 0xfe) << 6) | ||||
|             | ((br.read_8() & 0xfe) << 13) | ||||
|             | ((br.read_8() & 0xfe) << 20); | ||||
|     }; | ||||
|  | ||||
| 	uint32_t ticks_gw = 0; | ||||
| 	uint32_t lastevent_fl = 0; | ||||
| 	uint32_t index_gw = ~0; | ||||
|  | ||||
| 	while (!br.eof()) | ||||
| 	{ | ||||
| 		uint8_t b = br.read_8(); | ||||
| 		if (!b) | ||||
| 			break; | ||||
|  | ||||
| 		uint8_t event = 0; | ||||
| 		if (b == 255) | ||||
| 		{ | ||||
| 			switch (br.read_8()) | ||||
| 			{ | ||||
| 				case FLUXOP_INDEX: | ||||
| 					index_gw = ticks_gw + read_28(); | ||||
| 					break; | ||||
|  | ||||
| 				case FLUXOP_SPACE: | ||||
| 					ticks_gw += read_28(); | ||||
| 					break; | ||||
|  | ||||
| 				default: | ||||
| 					Error() << "bad opcode in GreaseWeazle stream"; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (b < 250) | ||||
| 				ticks_gw += b; | ||||
| 			else | ||||
| 			{ | ||||
| 				int delta = 250 + (b-250)*255 + br.read_8() - 1; | ||||
| 				ticks_gw += delta; | ||||
| 			} | ||||
| 			event = F_BIT_PULSE; | ||||
| 		} | ||||
|  | ||||
| 		if (event) | ||||
| 		{ | ||||
| 			uint32_t index_fl = (index_gw * clock) / NS_PER_TICK; | ||||
| 			uint32_t ticks_fl = (ticks_gw * clock) / NS_PER_TICK; | ||||
| 			if (index_gw != ~0) | ||||
| 			{ | ||||
| 				if (index_fl < ticks_fl) | ||||
| 				{ | ||||
| 					uint32_t delta_fl = index_fl - lastevent_fl; | ||||
| 					while (delta_fl > 0x3f) | ||||
| 					{ | ||||
| 						bw.write_8(0x3f); | ||||
| 						delta_fl -= 0x3f; | ||||
| 					} | ||||
| 					bw.write_8(delta_fl | F_BIT_INDEX); | ||||
| 					lastevent_fl = index_fl; | ||||
| 					index_gw = ~0; | ||||
| 				} | ||||
| 				else if (index_fl == ticks_fl) | ||||
| 					event |= F_BIT_INDEX; | ||||
| 			} | ||||
|  | ||||
| 			uint32_t delta_fl = ticks_fl - lastevent_fl; | ||||
| 			while (delta_fl > 0x3f) | ||||
| 			{ | ||||
| 				bw.write_8(0x3f); | ||||
| 				delta_fl -= 0x3f; | ||||
| 			} | ||||
| 			bw.write_8(delta_fl | event); | ||||
| 			lastevent_fl = ticks_fl; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return fldata; | ||||
| } | ||||
|  | ||||
| /* Left-truncates at the first index mark, so the resulting data as aligned at | ||||
|  * the index. */ | ||||
| Bytes stripPartialRotation(const Bytes& fldata) | ||||
| { | ||||
| 	for (unsigned i=0; i<fldata.size(); i++) | ||||
| 	{ | ||||
| 		uint8_t b = fldata[i]; | ||||
| 		if (b & F_BIT_INDEX) | ||||
| 			return fldata.slice(i); | ||||
| 	} | ||||
| 	return fldata; | ||||
| } | ||||
|  | ||||
							
								
								
									
										203
									
								
								lib/usb/greaseweazle.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								lib/usb/greaseweazle.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | ||||
| #ifndef GREASEWEAZLE_H | ||||
| #define GREASEWEAZLE_H | ||||
|  | ||||
| #define GREASEWEAZLE_VID 0x1209 | ||||
| #define GREASEWEAZLE_PID 0x4d69 | ||||
|  | ||||
| #define EP_OUT 0x02 | ||||
| #define EP_IN 0x83 | ||||
|  | ||||
| #define GREASEWEAZLE_VERSION 22 | ||||
|  | ||||
| extern Bytes fluxEngineToGreaseWeazle(const Bytes& fldata, nanoseconds_t clock); | ||||
| extern Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock); | ||||
| extern Bytes stripPartialRotation(const Bytes& fldata); | ||||
|  | ||||
| /* Copied from https://github.com/keirf/Greaseweazle/blob/master/inc/cdc_acm_protocol.h. | ||||
|  * | ||||
|  * WANING: these headers were originally defined with 'packed', which is a gccism so it's | ||||
|  * been dummied out. Don't use them expecting wire protocol structures. */ | ||||
|  | ||||
| #define packed /* */ | ||||
|  | ||||
| /* | ||||
|  * GREASEWEAZLE COMMAND SET | ||||
|  */ | ||||
|  | ||||
| /* CMD_GET_INFO, length=3, idx. Returns 32 bytes after ACK. */ | ||||
| #define CMD_GET_INFO        0 | ||||
| /* [BOOTLOADER] CMD_UPDATE, length=6, <update_len>.  | ||||
|  * Host follows with <update_len> bytes. | ||||
|  * Bootloader finally returns a status byte, 0 on success. */ | ||||
| /* [MAIN FIRMWARE] CMD_UPDATE, length=10, <update_len>, 0xdeafbee3. | ||||
|  * Host follows with <update_len> bytes. | ||||
|  * Main firmware finally returns a status byte, 0 on success. */ | ||||
| #define CMD_UPDATE          1 | ||||
| /* CMD_SEEK, length=3, cyl#. Seek to cyl# on selected drive. */ | ||||
| #define CMD_SEEK            2 | ||||
| /* CMD_HEAD, length=3, head# (0=bottom) */ | ||||
| #define CMD_HEAD            3 | ||||
| /* CMD_SET_PARAMS, length=3+nr, idx, <nr bytes> */ | ||||
| #define CMD_SET_PARAMS      4 | ||||
| /* CMD_GET_PARAMS, length=4, idx, nr_bytes. Returns nr_bytes after ACK. */ | ||||
| #define CMD_GET_PARAMS      5 | ||||
| /* CMD_MOTOR, length=4, drive#, on/off. Turn on/off a drive motor. */ | ||||
| #define CMD_MOTOR           6 | ||||
| /* CMD_READ_FLUX, length=2-8. Argument is gw_read_flux. | ||||
|  * Returns flux readings until EOStream. */ | ||||
| #define CMD_READ_FLUX       7 | ||||
| /* CMD_WRITE_FLUX, length=2-4. Argument is gw_write_flux. | ||||
|  * Host follows with flux readings until EOStream. */ | ||||
| #define CMD_WRITE_FLUX      8 | ||||
| /* CMD_GET_FLUX_STATUS, length=2. Last read/write status returned in ACK. */ | ||||
| #define CMD_GET_FLUX_STATUS 9 | ||||
| /* CMD_SWITCH_FW_MODE, length=3, <mode> */ | ||||
| #define CMD_SWITCH_FW_MODE 11 | ||||
| /* CMD_SELECT, length=3, drive#. Select drive# as current unit. */ | ||||
| #define CMD_SELECT         12 | ||||
| /* CMD_DESELECT, length=2. Deselect current unit (if any). */ | ||||
| #define CMD_DESELECT       13 | ||||
| /* CMD_SET_BUS_TYPE, length=3, bus_type. Set the bus type. */ | ||||
| #define CMD_SET_BUS_TYPE   14 | ||||
| /* CMD_SET_PIN, length=4, pin#, level. */ | ||||
| #define CMD_SET_PIN        15 | ||||
| /* CMD_RESET, length=2. Reset all state to initial (power on) values. */ | ||||
| #define CMD_RESET          16 | ||||
| /* CMD_ERASE_FLUX, length=6. Argument is gw_erase_flux. */ | ||||
| #define CMD_ERASE_FLUX     17 | ||||
| /* CMD_SOURCE_BYTES, length=6. Argument is gw_sink_source_bytes. */ | ||||
| #define CMD_SOURCE_BYTES   18 | ||||
| /* CMD_SINK_BYTES, length=6. Argument is gw_sink_source_bytes. */ | ||||
| #define CMD_SINK_BYTES     19 | ||||
| #define CMD_MAX            19 | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * CMD_SET_BUS CODES | ||||
|  */ | ||||
| #define BUS_NONE            0 | ||||
| #define BUS_IBMPC           1 | ||||
| #define BUS_SHUGART         2 | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * ACK RETURN CODES | ||||
|  */ | ||||
| #define ACK_OKAY            0 | ||||
| #define ACK_BAD_COMMAND     1 | ||||
| #define ACK_NO_INDEX        2 | ||||
| #define ACK_NO_TRK0         3 | ||||
| #define ACK_FLUX_OVERFLOW   4 | ||||
| #define ACK_FLUX_UNDERFLOW  5 | ||||
| #define ACK_WRPROT          6 | ||||
| #define ACK_NO_UNIT         7 | ||||
| #define ACK_NO_BUS          8 | ||||
| #define ACK_BAD_UNIT        9 | ||||
| #define ACK_BAD_PIN        10 | ||||
| #define ACK_BAD_CYLINDER   11 | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * CONTROL-CHANNEL COMMAND SET: | ||||
|  * We abuse SET_LINE_CODING requests over endpoint 0, stashing a command | ||||
|  * in the baud-rate field. | ||||
|  */ | ||||
| #define BAUD_NORMAL        9600 | ||||
| #define BAUD_CLEAR_COMMS  10000 | ||||
|  | ||||
| /* | ||||
|  * Flux stream opcodes. Preceded by 0xFF byte. | ||||
|  *  | ||||
|  * Argument types: | ||||
|  *  N28: 28-bit non-negative integer N, encoded as 4 bytes b0,b1,b2,b3: | ||||
|  *   b0 = (uint8_t)(1 | (N <<  1)) | ||||
|  *   b1 = (uint8_t)(1 | (N >>  6)) | ||||
|  *   b2 = (uint8_t)(1 | (N >> 13)) | ||||
|  *   b3 = (uint8_t)(1 | (N >> 20)) | ||||
|  */ | ||||
| /* FLUXOP_INDEX [CMD_READ_FLUX] | ||||
|  *  Args: | ||||
|  *   +4 [N28]: ticks to index, relative to sample cursor. | ||||
|  *  Signals an index pulse in the read stream. Sample cursor is unaffected. */ | ||||
| #define FLUXOP_INDEX      1 | ||||
| /* FLUXOP_SPACE [CMD_READ_FLUX, CMD_WRITE_FLUX] | ||||
|  *  Args: | ||||
|  *   +4 [N28]: ticks to increment the sample cursor. | ||||
|  *  Increments the sample cursor with no intervening flux transitions. */ | ||||
| #define FLUXOP_SPACE      2 | ||||
| /* FLUXOP_ASTABLE [CMD_WRITE_FLUX] | ||||
|  *  Args: | ||||
|  *   +4 [N28]: astable period. | ||||
|  *  Generate regular flux transitions at specified astable period.  | ||||
|  *  Duration is specified by immediately preceding FLUXOP_SPACE opcode(s). */ | ||||
| #define FLUXOP_ASTABLE    3 | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * COMMAND PACKETS | ||||
|  */ | ||||
|  | ||||
| /* CMD_GET_INFO, index 0 */ | ||||
| #define GETINFO_FIRMWARE 0 | ||||
| struct packed gw_info { | ||||
|     uint8_t fw_major; | ||||
|     uint8_t fw_minor; | ||||
|     uint8_t is_main_firmware; /* == 0 -> update bootloader */ | ||||
|     uint8_t max_cmd; | ||||
|     uint32_t sample_freq; | ||||
|     uint8_t hw_model, hw_submodel; | ||||
|     uint8_t usb_speed; | ||||
| }; | ||||
| extern struct gw_info gw_info; | ||||
|  | ||||
| /* CMD_GET_INFO, index 1 */ | ||||
| #define GETINFO_BW_STATS 1 | ||||
| struct packed gw_bw_stats { | ||||
|     struct packed { | ||||
|         uint32_t bytes; | ||||
|         uint32_t usecs; | ||||
|     } min_bw, max_bw; | ||||
| }; | ||||
|  | ||||
| /* CMD_READ_FLUX */ | ||||
| struct packed gw_read_flux { | ||||
|     /* Maximum ticks to read for (or 0, for no limit). */ | ||||
|     uint32_t ticks; | ||||
|     /* Maximum index pulses to read (or 0, for no limit). */ | ||||
|     uint16_t max_index; | ||||
| }; | ||||
|  | ||||
| /* CMD_WRITE_FLUX */ | ||||
| struct packed gw_write_flux { | ||||
|     /* If non-zero, start the write at the index pulse. */ | ||||
|     uint8_t cue_at_index; | ||||
|     /* If non-zero, terminate the write at the next index pulse. */ | ||||
|     uint8_t terminate_at_index; | ||||
| }; | ||||
|  | ||||
| /* CMD_ERASE_FLUX */ | ||||
| struct packed gw_erase_flux { | ||||
|     uint32_t ticks; | ||||
| }; | ||||
|  | ||||
| /* CMD_SINK_SOURCE_BYTES */ | ||||
| struct packed gw_sink_source_bytes { | ||||
|     uint32_t nr_bytes; | ||||
| }; | ||||
|  | ||||
| /* CMD_{GET,SET}_PARAMS, index 0 */ | ||||
| #define PARAMS_DELAYS 0 | ||||
| struct packed gw_delay { | ||||
|     uint16_t select_delay; /* usec */ | ||||
|     uint16_t step_delay;   /* usec */ | ||||
|     uint16_t seek_settle;  /* msec */ | ||||
|     uint16_t motor_delay;  /* msec */ | ||||
|     uint16_t auto_off;     /* msec */ | ||||
| }; | ||||
|  | ||||
| /* CMD_SWITCH_FW_MODE */ | ||||
| #define FW_MODE_BOOTLOADER 0 | ||||
| #define FW_MODE_NORMAL     1 | ||||
|  | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										390
									
								
								lib/usb/greaseweazleusb.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								lib/usb/greaseweazleusb.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,390 @@ | ||||
| #include "globals.h" | ||||
| #include "usb.h" | ||||
| #include "protocol.h" | ||||
| #include "fluxmap.h" | ||||
| #include "bytes.h" | ||||
| #include <libusb.h> | ||||
| #include "fmt/format.h" | ||||
| #include "greaseweazle.h" | ||||
|  | ||||
| #define TIMEOUT 5000 | ||||
|  | ||||
| static const char* gw_error(int e) | ||||
| { | ||||
|     switch (e) | ||||
|     { | ||||
|         case ACK_OKAY:           return "OK"; | ||||
|         case ACK_BAD_COMMAND:    return "Bad command"; | ||||
|         case ACK_NO_INDEX:       return "No index"; | ||||
|         case ACK_NO_TRK0:        return "No track 0"; | ||||
|         case ACK_FLUX_OVERFLOW:  return "Overflow"; | ||||
|         case ACK_FLUX_UNDERFLOW: return "Underflow"; | ||||
|         case ACK_WRPROT:         return "Write protected"; | ||||
|         case ACK_NO_UNIT:        return "No unit"; | ||||
|         case ACK_NO_BUS:         return "No bus"; | ||||
|         case ACK_BAD_UNIT:       return "Invalid unit"; | ||||
|         case ACK_BAD_PIN:        return "Invalid pin"; | ||||
|         case ACK_BAD_CYLINDER:   return "Invalid cylinder"; | ||||
|         default:                 return "Unknown error"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| class GreaseWeazleUsb : public USB | ||||
| { | ||||
| private: | ||||
|     uint8_t _readbuffer[4096]; | ||||
|     int _readbuffer_ptr = 0; | ||||
|     int _readbuffer_fill = 0; | ||||
|  | ||||
|     void read_bytes(uint8_t* buffer, int len) | ||||
|     { | ||||
|         while (len > 0) | ||||
|         { | ||||
|             if (_readbuffer_ptr < _readbuffer_fill) | ||||
|             { | ||||
|                 int buffered = std::min(len, _readbuffer_fill - _readbuffer_ptr); | ||||
|                 memcpy(buffer, _readbuffer + _readbuffer_ptr, buffered); | ||||
|                 _readbuffer_ptr += buffered; | ||||
|                 buffer += buffered; | ||||
|                 len -= buffered; | ||||
|             } | ||||
|  | ||||
|             if (len == 0) | ||||
|                 break; | ||||
|  | ||||
|             int actual; | ||||
|             int rc = libusb_bulk_transfer(_device, EP_IN, | ||||
|                 _readbuffer, sizeof(_readbuffer), | ||||
|                 &actual, TIMEOUT); | ||||
|             if (rc < 0) | ||||
|                 Error() << "failed to receive command reply: " << usberror(rc); | ||||
|  | ||||
|             _readbuffer_fill = actual; | ||||
|             _readbuffer_ptr = 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void read_bytes(Bytes& bytes) | ||||
|     { | ||||
|         read_bytes(bytes.begin(), bytes.size()); | ||||
|     } | ||||
|  | ||||
|     Bytes read_bytes(unsigned len) | ||||
|     { | ||||
|         Bytes b(len); | ||||
|         read_bytes(b); | ||||
|         return b; | ||||
|     } | ||||
|  | ||||
|     uint8_t read_byte() | ||||
|     { | ||||
|         uint8_t b; | ||||
|         read_bytes(&b, 1); | ||||
|         return b; | ||||
|     } | ||||
|  | ||||
|     uint32_t read_28() | ||||
|     { | ||||
|         return ((read_byte() & 0xfe) >> 1) | ||||
|             | ((read_byte() & 0xfe) << 6) | ||||
|             | ((read_byte() & 0xfe) << 13) | ||||
|             | ((read_byte() & 0xfe) << 20); | ||||
|     } | ||||
|  | ||||
|     void write_bytes(const uint8_t* buffer, int len) | ||||
|     { | ||||
|         while (len > 0) | ||||
|         { | ||||
|             int actual; | ||||
|             int rc = libusb_bulk_transfer(_device, EP_OUT, (uint8_t*)buffer, len, &actual, 0); | ||||
|             if (rc < 0) | ||||
|                 Error() << "failed to send command: " << usberror(rc); | ||||
|  | ||||
|             buffer += actual; | ||||
|             len -= actual; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void write_bytes(const Bytes& bytes) | ||||
|     { | ||||
|         write_bytes(bytes.cbegin(), bytes.size()); | ||||
|     } | ||||
|  | ||||
|     void do_command(const Bytes& command) | ||||
|     { | ||||
|         write_bytes(command); | ||||
|  | ||||
|         uint8_t buffer[2]; | ||||
|         read_bytes(buffer, sizeof(buffer)); | ||||
|  | ||||
|         if (buffer[0] != command[0]) | ||||
|             Error() << fmt::format("command returned garbage (0x{:x} != 0x{:x} with status 0x{:x})", | ||||
|                 buffer[0], command[0], buffer[1]); | ||||
|         if (buffer[1]) | ||||
|             Error() << fmt::format("GreaseWeazle error: {}", gw_error(buffer[1])); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     GreaseWeazleUsb(libusb_device_handle* device) | ||||
|     { | ||||
|         _device = device; | ||||
|  | ||||
|         /* Configure the device. */ | ||||
|  | ||||
|         int i; | ||||
|         int cfg = -1; | ||||
|         libusb_get_configuration(_device, &cfg); | ||||
|         if (cfg != 1) | ||||
|         { | ||||
|             i = libusb_set_configuration(_device, 1); | ||||
|             if (i < 0) | ||||
|                 Error() << "the GreaseWeazle would not accept configuration: " << usberror(i); | ||||
|         } | ||||
|  | ||||
|         /* Detach the existing kernel serial port driver, if there is one, and claim it ourselves. */ | ||||
|  | ||||
|         for (int i = 0; i < 2; i++) | ||||
|         { | ||||
|             if (libusb_kernel_driver_active(_device, i)) | ||||
|                 libusb_detach_kernel_driver(_device, i); | ||||
|             int rc = libusb_claim_interface(_device, i); | ||||
|             if (rc < 0) | ||||
|                 Error() << "unable to claim interface: " << libusb_error_name(rc); | ||||
|         } | ||||
|  | ||||
|         int version = getVersion(); | ||||
|         if (version != GREASEWEAZLE_VERSION) | ||||
|             Error() << "your GreaseWeazle firmware is at version " << version | ||||
|                     << " but the client is for version " << GREASEWEAZLE_VERSION | ||||
|                     << "; please upgrade"; | ||||
|  | ||||
|         /* Configure the hardware. */ | ||||
|  | ||||
|         do_command({ CMD_SET_BUS_TYPE, 3, BUS_IBMPC }); | ||||
|     } | ||||
|  | ||||
|     int getVersion() | ||||
|     { | ||||
|         do_command({ CMD_GET_INFO, 3, GETINFO_FIRMWARE }); | ||||
|  | ||||
|         Bytes response = read_bytes(32); | ||||
|         ByteReader br(response); | ||||
|  | ||||
|         br.seek(4); | ||||
|         nanoseconds_t freq = br.read_le32(); | ||||
|         _clock = 1000000000 / freq; | ||||
|  | ||||
|         br.seek(0); | ||||
|         return br.read_be16(); | ||||
|     } | ||||
|  | ||||
|     void recalibrate() | ||||
|     { | ||||
|         seek(0); | ||||
|     } | ||||
|      | ||||
|     void seek(int track) | ||||
|     { | ||||
|         do_command({ CMD_SEEK, 3, (uint8_t)track }); | ||||
|     } | ||||
|      | ||||
|     nanoseconds_t getRotationalPeriod(int hardSectorCount) | ||||
|     { | ||||
|         if (hardSectorCount != 0) | ||||
|             Error() << "hard sectors are currently unsupported on the GreaseWeazel"; | ||||
|  | ||||
|         /* The GreaseWeazle doesn't have a command to fetch the period directly, | ||||
|          * so we have to do a flux read. */ | ||||
|  | ||||
|         do_command({ CMD_READ_FLUX, 2 }); | ||||
|  | ||||
|         uint32_t ticks_gw = 0; | ||||
|         uint32_t firstindex = ~0; | ||||
|         uint32_t secondindex = ~0; | ||||
|         for (;;) | ||||
|         { | ||||
|             uint8_t b = read_byte(); | ||||
|             if (!b) | ||||
|                 break; | ||||
|  | ||||
|             if (b == 255) | ||||
|             { | ||||
|                 switch (read_byte()) | ||||
|                 { | ||||
|                     case FLUXOP_INDEX: | ||||
|                     { | ||||
|                         uint32_t index = read_28() + ticks_gw; | ||||
|                         if (firstindex == ~0) | ||||
|                             firstindex = index; | ||||
|                         else if (secondindex == ~0) | ||||
|                             secondindex = index; | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     case FLUXOP_SPACE: | ||||
|                         read_bytes(4); | ||||
|                         break; | ||||
|  | ||||
|                     default: | ||||
|                         Error() << "bad opcode in GreaseWeazle stream"; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (b < 250) | ||||
|                     ticks_gw += b; | ||||
|                 else | ||||
|                 { | ||||
|                     int delta = 250 + (b-250)*255 + read_byte() - 1; | ||||
|                     ticks_gw += delta; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (secondindex == ~0) | ||||
|             Error() << "unable to determine disk rotational period (is a disk in the drive?)"; | ||||
|         do_command({ CMD_GET_FLUX_STATUS, 2 }); | ||||
|  | ||||
|         _revolutions = (nanoseconds_t)(secondindex - firstindex) * _clock; | ||||
|         return _revolutions; | ||||
|     } | ||||
|      | ||||
|     void testBulkWrite() | ||||
|     { | ||||
|         const int LEN = 10*1024*1024; | ||||
|         Bytes cmd(6); | ||||
|         ByteWriter bw(cmd); | ||||
|         bw.write_8(CMD_SINK_BYTES); | ||||
|         bw.write_8(cmd.size()); | ||||
|         bw.write_le32(LEN); | ||||
|         do_command(cmd); | ||||
|  | ||||
|         Bytes junk(LEN); | ||||
| 		double start_time = getCurrentTime(); | ||||
|         write_bytes(LEN); | ||||
|         read_bytes(1); | ||||
| 		double elapsed_time = getCurrentTime() - start_time; | ||||
|  | ||||
| 		std::cout << "Transferred " | ||||
| 				  << LEN | ||||
| 				  << " bytes from PC -> GreaseWeazle in " | ||||
| 				  << int(elapsed_time * 1000.0) | ||||
| 				  << " ms (" | ||||
| 				  << int((LEN / 1024.0) / elapsed_time) | ||||
| 				  << " kB/s)" | ||||
| 				  << std::endl; | ||||
|     } | ||||
|      | ||||
|     void testBulkRead() | ||||
|     { | ||||
|         const int LEN = 10*1024*1024; | ||||
|         Bytes cmd(6); | ||||
|         ByteWriter bw(cmd); | ||||
|         bw.write_8(CMD_SOURCE_BYTES); | ||||
|         bw.write_8(cmd.size()); | ||||
|         bw.write_le32(LEN); | ||||
|         do_command(cmd); | ||||
|  | ||||
| 		double start_time = getCurrentTime(); | ||||
|         read_bytes(LEN); | ||||
| 		double elapsed_time = getCurrentTime() - start_time; | ||||
|  | ||||
| 		std::cout << "Transferred " | ||||
| 				  << LEN | ||||
| 				  << " bytes from GreaseWeazle -> PC in " | ||||
| 				  << int(elapsed_time * 1000.0) | ||||
| 				  << " ms (" | ||||
| 				  << int((LEN / 1024.0) / elapsed_time) | ||||
| 				  << " kB/s)" | ||||
| 				  << std::endl; | ||||
|     } | ||||
|  | ||||
|     Bytes read(int side, bool synced, nanoseconds_t readTime, nanoseconds_t hardSectorThreshold) | ||||
|     { | ||||
|         if (hardSectorThreshold != 0) | ||||
|             Error() << "hard sectors are currently unsupported on the GreaseWeazel"; | ||||
|  | ||||
|         int revolutions = (readTime+_revolutions-1) / _revolutions; | ||||
|  | ||||
|         do_command({ CMD_HEAD, 3, (uint8_t)side }); | ||||
|  | ||||
|         { | ||||
|             Bytes cmd(4); | ||||
|             cmd.writer() | ||||
|                 .write_8(CMD_READ_FLUX) | ||||
|                 .write_8(cmd.size()) | ||||
|                 .write_le32(revolutions + (synced ? 1 : 0)); | ||||
|             do_command(cmd); | ||||
|         } | ||||
|  | ||||
| 		Bytes buffer; | ||||
|         ByteWriter bw(buffer); | ||||
|         for (;;) | ||||
|         { | ||||
|             uint8_t b = read_byte(); | ||||
|             if (!b) | ||||
|                 break; | ||||
|             bw.write_8(b); | ||||
|         } | ||||
|  | ||||
|         do_command({ CMD_GET_FLUX_STATUS, 2 }); | ||||
|  | ||||
|         Bytes fldata = greaseWeazleToFluxEngine(buffer, _clock); | ||||
|         if (synced) | ||||
|             fldata = stripPartialRotation(fldata); | ||||
|         return fldata; | ||||
|     } | ||||
|      | ||||
|     void write(int side, const Bytes& fldata, nanoseconds_t hardSectorThreshold) | ||||
|     { | ||||
|         if (hardSectorThreshold != 0) | ||||
|             Error() << "hard sectors are currently unsupported on the GreaseWeazel"; | ||||
|  | ||||
|         do_command({ CMD_HEAD, 3, (uint8_t)side }); | ||||
|         do_command({ CMD_WRITE_FLUX, 3, 1 }); | ||||
|         write_bytes(fluxEngineToGreaseWeazle(fldata, _clock)); | ||||
|         read_byte(); /* synchronise */ | ||||
|  | ||||
|         do_command({ CMD_GET_FLUX_STATUS, 2 }); | ||||
|     } | ||||
|      | ||||
|     void erase(int side, nanoseconds_t hardSectorThreshold) | ||||
|     { | ||||
|         if (hardSectorThreshold != 0) | ||||
|             Error() << "hard sectors are currently unsupported on the GreaseWeazel"; | ||||
|  | ||||
|         do_command({ CMD_HEAD, 3, (uint8_t)side }); | ||||
|  | ||||
|         Bytes cmd(6); | ||||
|         ByteWriter bw(cmd); | ||||
|         bw.write_8(CMD_ERASE_FLUX); | ||||
|         bw.write_8(cmd.size()); | ||||
|         bw.write_le32(200e6 / _clock); | ||||
|         do_command(cmd); | ||||
|         read_byte(); /* synchronise */ | ||||
|  | ||||
|         do_command({ CMD_GET_FLUX_STATUS, 2 }); | ||||
|     } | ||||
|      | ||||
|     void setDrive(int drive, bool high_density, int index_mode) | ||||
|     { | ||||
|         do_command({ CMD_SELECT, 3, (uint8_t)drive }); | ||||
|         do_command({ CMD_MOTOR, 4, (uint8_t)drive, 1 }); | ||||
|         do_command({ CMD_SET_PIN, 4, 2, (uint8_t)(high_density ? 0 : 1) }); | ||||
|     } | ||||
|  | ||||
|     void measureVoltages(struct voltages_frame* voltages) | ||||
|     { Error() << "unsupported operation on the GreaseWeazle"; } | ||||
|  | ||||
| private: | ||||
|     nanoseconds_t _clock; | ||||
|     nanoseconds_t _revolutions; | ||||
| }; | ||||
|  | ||||
| USB* createGreaseWeazleUsb(libusb_device_handle* device) | ||||
| { | ||||
|     return new GreaseWeazleUsb(device); | ||||
| } | ||||
|  | ||||
| // vim: sw=4 ts=4 et | ||||
|  | ||||
							
								
								
									
										170
									
								
								lib/usb/usb.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								lib/usb/usb.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "usb.h" | ||||
| #include "protocol.h" | ||||
| #include "fluxmap.h" | ||||
| #include "bytes.h" | ||||
| #include <libusb.h> | ||||
| #include "fmt/format.h" | ||||
| #include "greaseweazle.h" | ||||
|  | ||||
| FlagGroup usbFlags; | ||||
|  | ||||
| static StringFlag device( | ||||
|     { "--device" }, | ||||
|     "serial number of hardware device to use", | ||||
| 	""); | ||||
|  | ||||
| static USB* usb = NULL; | ||||
|  | ||||
| enum | ||||
| { | ||||
| 	DEV_FLUXENGINE, | ||||
| 	DEV_GREASEWEAZLE, | ||||
| }; | ||||
|  | ||||
| struct CandidateDevice | ||||
| { | ||||
| 	libusb_device* device; | ||||
| 	libusb_device_descriptor desc; | ||||
| 	int type; | ||||
| 	std::string serial; | ||||
| }; | ||||
|  | ||||
| USB::~USB() | ||||
| {} | ||||
|  | ||||
| std::string USB::usberror(int i) | ||||
| { | ||||
|     return libusb_strerror((libusb_error) i); | ||||
| } | ||||
|  | ||||
| static const char* device_type(int i) | ||||
| { | ||||
| 	switch (i) | ||||
| 	{ | ||||
| 		case DEV_FLUXENGINE: return "FluxEngine"; | ||||
| 		case DEV_GREASEWEAZLE: return "GreaseWeazle"; | ||||
| 		default: assert(false); | ||||
| 	} | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| static const std::string get_serial_number(libusb_device* device, libusb_device_descriptor* desc) | ||||
| { | ||||
| 	std::string serial; | ||||
|  | ||||
| 	libusb_device_handle* handle; | ||||
| 	if (libusb_open(device, &handle) == 0) | ||||
| 	{ | ||||
| 		unsigned char buffer[64]; | ||||
| 		libusb_get_string_descriptor_ascii(handle, desc->iSerialNumber, buffer, sizeof(buffer)); | ||||
| 		serial = (const char*) buffer; | ||||
| 		libusb_close(handle); | ||||
| 	} | ||||
|  | ||||
| 	return serial; | ||||
| } | ||||
|  | ||||
| static std::map<std::string, std::unique_ptr<CandidateDevice>> get_candidates(libusb_device** devices, int numdevices) | ||||
| { | ||||
| 	std::map<std::string, std::unique_ptr<CandidateDevice>> candidates; | ||||
| 	for (int i=0; i<numdevices; i++) | ||||
| 	{ | ||||
| 		std::unique_ptr<CandidateDevice> candidate(new CandidateDevice()); | ||||
| 		candidate->device = devices[i]; | ||||
| 		(void) libusb_get_device_descriptor(devices[i], &candidate->desc); | ||||
|  | ||||
| 		uint32_t id = (candidate->desc.idVendor << 16) | candidate->desc.idProduct; | ||||
| 		switch (id) | ||||
| 		{ | ||||
| 			case (FLUXENGINE_VID<<16) | FLUXENGINE_PID: | ||||
| 			{ | ||||
| 				candidate->type = DEV_FLUXENGINE; | ||||
| 				candidate->serial = get_serial_number(candidate->device, &candidate->desc); | ||||
| 				candidates[candidate->serial] = std::move(candidate); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			case (GREASEWEAZLE_VID<<16) | GREASEWEAZLE_PID: | ||||
| 			{ | ||||
| 				candidate->type = DEV_GREASEWEAZLE; | ||||
| 				candidate->serial = get_serial_number(candidate->device, &candidate->desc); | ||||
| 				candidates[candidate->serial] = std::move(candidate); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return candidates; | ||||
| } | ||||
|  | ||||
| static void open_device(CandidateDevice& candidate) | ||||
| { | ||||
| 	libusb_device_handle* handle; | ||||
| 	int i = libusb_open(candidate.device, &handle); | ||||
| 	if (i < 0) | ||||
| 		Error() << "cannot open USB device: " << libusb_strerror((libusb_error) i); | ||||
| 	 | ||||
| 	std::cout << "Using " << device_type(candidate.type) << " with serial number " << candidate.serial << '\n'; | ||||
| 	switch (candidate.type) | ||||
| 	{ | ||||
| 		case DEV_FLUXENGINE: | ||||
| 			usb = createFluxengineUsb(handle); | ||||
| 			break; | ||||
|  | ||||
| 		case DEV_GREASEWEAZLE: | ||||
| 			usb = createGreaseWeazleUsb(handle); | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static CandidateDevice& select_candidate(const std::map<std::string, std::unique_ptr<CandidateDevice>>& devices) | ||||
| { | ||||
| 	if (devices.size() == 0) | ||||
| 		Error() << "no USB devices found (is one plugged in? Do you have permission to access USB devices?)"; | ||||
|  | ||||
| 	if (device.get() == "") | ||||
| 	{ | ||||
| 		if (devices.size() == 1) | ||||
| 			return *devices.begin()->second; | ||||
|  | ||||
| 		std::cout << "More than one USB device detected. Use --device to specify which one to use:\n"; | ||||
| 		for (auto& i : devices) | ||||
| 			std::cout << "  " << device_type(i.second->type) << ": " << i.first << '\n'; | ||||
| 		Error() << "specify USB device"; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		const auto& i = devices.find(device); | ||||
| 		if (i != devices.end()) | ||||
| 			return *i->second; | ||||
|  | ||||
| 		Error() << "device with serial number '" << device.get() << "' not found"; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| USB& getUsb() | ||||
| { | ||||
| 	if (!usb) | ||||
| 	{ | ||||
| 		int i = libusb_init(NULL); | ||||
| 		if (i < 0) | ||||
| 			Error() << "could not start libusb: " << libusb_strerror((libusb_error) i); | ||||
|  | ||||
| 		libusb_device** devices; | ||||
| 		int numdevices = libusb_get_device_list(NULL, &devices); | ||||
| 		if (numdevices < 0) | ||||
| 			Error() << "could not enumerate USB bus: " << libusb_strerror((libusb_error) numdevices); | ||||
|  | ||||
| 		auto candidates = get_candidates(devices, numdevices); | ||||
| 		auto candidate = select_candidate(candidates); | ||||
| 		open_device(candidate); | ||||
|  | ||||
| 		libusb_free_device_list(devices, true); | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	return *usb; | ||||
| } | ||||
|  | ||||
							
								
								
									
										67
									
								
								lib/usb/usb.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								lib/usb/usb.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| #ifndef USB_H | ||||
| #define USB_H | ||||
|  | ||||
| #include "bytes.h" | ||||
| #include "flags.h" | ||||
|  | ||||
| class Fluxmap; | ||||
| class libusb_device_handle; | ||||
|  | ||||
| class USB | ||||
| { | ||||
| public: | ||||
| 	virtual ~USB(); | ||||
|  | ||||
| 	virtual int getVersion() = 0; | ||||
| 	virtual void recalibrate() = 0; | ||||
| 	virtual void seek(int track) = 0; | ||||
| 	virtual nanoseconds_t getRotationalPeriod(int hardSectorCount) = 0; | ||||
| 	virtual void testBulkWrite() = 0; | ||||
| 	virtual void testBulkRead() = 0; | ||||
| 	virtual Bytes read(int side, bool synced, nanoseconds_t readTime, | ||||
| 	                   nanoseconds_t hardSectorThreshold) = 0; | ||||
| 	virtual void write(int side, const Bytes& bytes, | ||||
| 	                   nanoseconds_t hardSectorThreshold) = 0; | ||||
| 	virtual void erase(int side, nanoseconds_t hardSectorThreshold) = 0; | ||||
| 	virtual void setDrive(int drive, bool high_density, int index_mode) = 0; | ||||
| 	virtual void measureVoltages(struct voltages_frame* voltages) = 0; | ||||
|  | ||||
| protected: | ||||
| 	std::string usberror(int i); | ||||
|  | ||||
| 	libusb_device_handle* _device; | ||||
| }; | ||||
|  | ||||
| extern FlagGroup usbFlags; | ||||
| extern USB& getUsb(); | ||||
|  | ||||
| extern USB* createFluxengineUsb(libusb_device_handle* device); | ||||
| extern USB* createGreaseWeazleUsb(libusb_device_handle* device); | ||||
|  | ||||
| static inline int usbGetVersion()     { return getUsb().getVersion(); } | ||||
| static inline void usbRecalibrate()   { getUsb().recalibrate(); } | ||||
| static inline void usbSeek(int track) { getUsb().seek(track); } | ||||
| static inline void usbTestBulkWrite() { getUsb().testBulkWrite(); } | ||||
| static inline void usbTestBulkRead()  { getUsb().testBulkRead(); } | ||||
|  | ||||
| static inline void usbErase(int side, nanoseconds_t hardSectorThreshold) | ||||
| { getUsb().erase(side, hardSectorThreshold); } | ||||
|  | ||||
| static inline nanoseconds_t usbGetRotationalPeriod(int hardSectorCount) | ||||
| { return getUsb().getRotationalPeriod(hardSectorCount); } | ||||
|  | ||||
| static inline Bytes usbRead(int side, bool synced, nanoseconds_t readTime, | ||||
|                             nanoseconds_t hardSectorThreshold) | ||||
| { return getUsb().read(side, synced, readTime, hardSectorThreshold); } | ||||
|  | ||||
| static inline void usbWrite(int side, const Bytes& bytes, | ||||
|                             nanoseconds_t hardSectorThreshold) | ||||
| { getUsb().write(side, bytes, hardSectorThreshold); } | ||||
|  | ||||
| static inline void usbSetDrive(int drive, bool high_density, int index_mode) | ||||
| { getUsb().setDrive(drive, high_density, index_mode); } | ||||
|  | ||||
| static inline void usbMeasureVoltages(struct voltages_frame* voltages) | ||||
| { getUsb().measureVoltages(voltages); } | ||||
|  | ||||
| #endif | ||||
| @@ -1,6 +1,5 @@ | ||||
| #define _USE_MATH_DEFINES | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "visualiser.h" | ||||
| @@ -48,8 +47,8 @@ void visualiseSectorsToFile(const SectorSet& sectors, const std::string& filenam | ||||
|  | ||||
|             auto drawArc = [&](const std::unique_ptr<Sector>& sector, nanoseconds_t start, nanoseconds_t end, const std::string& colour) | ||||
|             { | ||||
|                 start %= period*1000000; | ||||
|                 end %= period*1000000; | ||||
|                 start = fmod(start, period*1000000.0); | ||||
|                 end = fmod(end, period*1000000.0); | ||||
|                 if (end < start) | ||||
|                     end += period*1000000; | ||||
|                  | ||||
|   | ||||
| @@ -4,18 +4,18 @@ | ||||
| #include "writer.h" | ||||
| #include "sql.h" | ||||
| #include "protocol.h" | ||||
| #include "usb.h" | ||||
| #include "usb/usb.h" | ||||
| #include "dataspec.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "fluxsource/fluxsource.h" | ||||
| #include "fluxsink/fluxsink.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "fmt/format.h" | ||||
| #include "record.h" | ||||
| #include "image.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
|  | ||||
| FlagGroup writerFlags { &hardwareFluxSourceFlags, &hardwareFluxSinkFlags }; | ||||
| FlagGroup writerFlags { &hardwareFluxSourceFlags, &sqliteFluxSinkFlags, &hardwareFluxSinkFlags }; | ||||
|  | ||||
| static DataSpecFlag dest( | ||||
|     { "--dest", "-d" }, | ||||
| @@ -43,6 +43,16 @@ void setWriterDefaultInput(const std::string& input) | ||||
|     ::input.set(input); | ||||
| } | ||||
|  | ||||
| void setWriterHardSectorCount(int sectorCount) | ||||
| { | ||||
| 	setHardwareFluxSinkHardSectorCount(sectorCount); | ||||
| } | ||||
|  | ||||
| static SectorSet readSectorsFromFile(const ImageSpec& spec) | ||||
| { | ||||
| 	return ImageReader::create(spec)->readImage(); | ||||
| } | ||||
|  | ||||
| void writeTracks( | ||||
| 	const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer) | ||||
| { | ||||
| @@ -53,19 +63,7 @@ void writeTracks( | ||||
| 	setHardwareFluxSourceDensity(highDensityFlag); | ||||
| 	setHardwareFluxSinkDensity(highDensityFlag); | ||||
|  | ||||
| 	if (!spec.filename.empty()) | ||||
| 	{ | ||||
| 		outdb = sqlOpen(spec.filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); | ||||
| 		sqlPrepareFlux(outdb); | ||||
| 		sqlStmt(outdb, "BEGIN;"); | ||||
|         sqlWriteIntProperty(outdb, "version", FLUX_VERSION_CURRENT); | ||||
| 		atexit([]() | ||||
| 			{ | ||||
| 				sqlStmt(outdb, "COMMIT;"); | ||||
| 				sqlClose(outdb); | ||||
| 			} | ||||
| 		); | ||||
| 	} | ||||
| 	std::shared_ptr<FluxSink> fluxSink = FluxSink::create(spec); | ||||
|  | ||||
|     for (const auto& location : spec.locations) | ||||
|     { | ||||
| @@ -73,29 +71,16 @@ void writeTracks( | ||||
|         std::unique_ptr<Fluxmap> fluxmap = producer(location.track, location.side); | ||||
|         if (!fluxmap) | ||||
|         { | ||||
|             if (!outdb) | ||||
|             { | ||||
|                 std::cout << "erasing\n"; | ||||
|                 usbSeek(location.track); | ||||
|                 usbErase(location.side); | ||||
|             } | ||||
|             else | ||||
|                 std::cout << "skipping\n"; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2); | ||||
|             if (outdb) | ||||
|                 sqlWriteFlux(outdb, location.track, location.side, *fluxmap); | ||||
|             else | ||||
|             { | ||||
|                 Bytes crunched = fluxmap->rawBytes().crunch(); | ||||
|                 usbSeek(location.track); | ||||
|                 usbWrite(location.side, crunched); | ||||
|             } | ||||
|             std::cout << fmt::format( | ||||
|                 "{0} ms in {1} bytes", int(fluxmap->duration()/1e6), fluxmap->bytes()) << std::endl; | ||||
|             /* Create an empty fluxmap for writing. */ | ||||
|             fluxmap.reset(new Fluxmap()); | ||||
|         } | ||||
|  | ||||
|         /* Precompensation actually seems to make things worse, so let's leave | ||||
|             * it disabled for now. */ | ||||
|         //fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2); | ||||
|         fluxSink->writeFlux(location.track, location.side, *fluxmap); | ||||
|         std::cout << fmt::format( | ||||
|             "{0} ms in {1} bytes", int(fluxmap->duration()/1e6), fluxmap->bytes()) << std::endl; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ class Geometry; | ||||
|  | ||||
| extern void setWriterDefaultDest(const std::string& dest); | ||||
| extern void setWriterDefaultInput(const std::string& input); | ||||
| extern void setWriterHardSectorCount(int sectorCount); | ||||
|  | ||||
| extern void writeTracks(const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer); | ||||
|  | ||||
|   | ||||
							
								
								
									
										28
									
								
								mkninja.sh
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								mkninja.sh
									
									
									
									
									
								
							| @@ -151,9 +151,12 @@ buildlibrary libfmt.a \ | ||||
|     dep/fmt/posix.cc \ | ||||
|  | ||||
| buildlibrary libbackend.a \ | ||||
| 	lib/imagereader/diskcopyimagereader.cc \ | ||||
| 	lib/imagereader/imagereader.cc \ | ||||
| 	lib/imagereader/imgimagereader.cc \ | ||||
| 	lib/imagereader/jv3imagereader.cc \ | ||||
| 	lib/imagewriter/d64imagewriter.cc \ | ||||
| 	lib/imagewriter/diskcopyimagewriter.cc \ | ||||
| 	lib/imagewriter/imagewriter.cc \ | ||||
| 	lib/imagewriter/imgimagewriter.cc \ | ||||
| 	lib/imagewriter/ldbsimagewriter.cc \ | ||||
| @@ -168,12 +171,16 @@ buildlibrary libbackend.a \ | ||||
|     arch/f85/decoder.cc \ | ||||
|     arch/fb100/decoder.cc \ | ||||
|     arch/ibm/decoder.cc \ | ||||
|     arch/ibm/encoder.cc \ | ||||
|     arch/macintosh/decoder.cc \ | ||||
|     arch/macintosh/encoder.cc \ | ||||
|     arch/micropolis/decoder.cc \ | ||||
|     arch/mx/decoder.cc \ | ||||
| 	arch/tids990/decoder.cc \ | ||||
| 	arch/tids990/encoder.cc \ | ||||
|     arch/victor9k/decoder.cc \ | ||||
|     arch/zilogmcz/decoder.cc \ | ||||
|     lib/bytes.cc \ | ||||
|     lib/common/crunch.c \ | ||||
|     lib/crc.cc \ | ||||
|     lib/dataspec.cc \ | ||||
|     lib/decoders/decoders.cc \ | ||||
| @@ -190,24 +197,28 @@ buildlibrary libbackend.a \ | ||||
|     lib/fluxsource/kryoflux.cc \ | ||||
|     lib/fluxsource/sqlitefluxsource.cc \ | ||||
|     lib/fluxsource/streamfluxsource.cc \ | ||||
|     lib/usb/usb.cc \ | ||||
|     lib/usb/fluxengineusb.cc \ | ||||
|     lib/usb/greaseweazle.cc \ | ||||
|     lib/usb/greaseweazleusb.cc \ | ||||
|     lib/globals.cc \ | ||||
|     lib/hexdump.cc \ | ||||
|     lib/image.cc \ | ||||
|     lib/ldbs.cc \ | ||||
|     lib/reader.cc \ | ||||
|     lib/sector.cc \ | ||||
|     lib/sectorset.cc \ | ||||
|     lib/sql.cc \ | ||||
|     lib/usb.cc \ | ||||
|     lib/visualiser.cc \ | ||||
|     lib/writer.cc \ | ||||
|  | ||||
| buildlibrary libfrontend.a \ | ||||
| 	src/fe-analysedriveresponse.cc \ | ||||
|     src/fe-cwftoflux.cc \ | ||||
|     src/fe-erase.cc \ | ||||
|     src/fe-fluxtoau.cc \ | ||||
|     src/fe-fluxtoscp.cc \ | ||||
|     src/fe-fluxtovcd.cc \ | ||||
|     src/fe-image.cc \ | ||||
|     src/fe-inspect.cc \ | ||||
|     src/fe-readadfs.cc \ | ||||
|     src/fe-readaeslanier.cc \ | ||||
| @@ -221,17 +232,22 @@ buildlibrary libfrontend.a \ | ||||
|     src/fe-readfb100.cc \ | ||||
|     src/fe-readibm.cc \ | ||||
|     src/fe-readmac.cc \ | ||||
|     src/fe-readmicropolis.cc \ | ||||
|     src/fe-readmx.cc \ | ||||
| 	src/fe-readtids990.cc \ | ||||
|     src/fe-readvictor9k.cc \ | ||||
|     src/fe-readzilogmcz.cc \ | ||||
|     src/fe-rpm.cc \ | ||||
|     src/fe-scptoflux.cc \ | ||||
|     src/fe-seek.cc \ | ||||
|     src/fe-testbulktransport.cc \ | ||||
|     src/fe-testbandwidth.cc \ | ||||
|     src/fe-testvoltages.cc \ | ||||
|     src/fe-upgradefluxfile.cc \ | ||||
|     src/fe-writeamiga.cc \ | ||||
|     src/fe-writebrother.cc \ | ||||
|     src/fe-writeibm.cc \ | ||||
|     src/fe-writemac.cc \ | ||||
|     src/fe-writetids990.cc \ | ||||
|     src/fe-writeflux.cc \ | ||||
|     src/fe-writetestpattern.cc \ | ||||
|     src/fluxengine.cc \ | ||||
| @@ -258,14 +274,14 @@ buildsimpleprogram brother240tool \ | ||||
|     libemu.a \ | ||||
|     libfmt.a \ | ||||
|  | ||||
| runtest amiga-test          tests/amiga.cc | ||||
| runtest bitaccumulator-test tests/bitaccumulator.cc | ||||
| runtest bytes-test          tests/bytes.cc | ||||
| runtest compression-test    tests/compression.cc | ||||
| runtest crunch-test         tests/crunch.cc | ||||
| runtest dataspec-test       tests/dataspec.cc | ||||
| runtest flags-test          tests/flags.cc | ||||
| runtest fluxpattern-test    tests/fluxpattern.cc | ||||
| runtest fmmfm-test          tests/fmmfm.cc | ||||
| runtest greaseweazle-test   tests/greaseweazle.cc | ||||
| runtest kryoflux-test       tests/kryoflux.cc | ||||
| runtest ldbs-test           tests/ldbs.cc | ||||
| runtest amiga-test          tests/amiga.cc | ||||
|   | ||||
							
								
								
									
										23
									
								
								protocol.h
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								protocol.h
									
									
									
									
									
								
							| @@ -3,7 +3,7 @@ | ||||
|  | ||||
| enum  | ||||
| { | ||||
|     FLUXENGINE_VERSION = 11, | ||||
|     FLUXENGINE_VERSION = 15, | ||||
|  | ||||
|     FLUXENGINE_VID = 0x1209, | ||||
|     FLUXENGINE_PID = 0x6e00, | ||||
| @@ -48,10 +48,12 @@ enum | ||||
|     F_FRAME_GET_VERSION_REPLY,    /* version_frame */ | ||||
|     F_FRAME_SEEK_CMD,             /* seek_frame */ | ||||
|     F_FRAME_SEEK_REPLY,           /* any_frame */ | ||||
|     F_FRAME_MEASURE_SPEED_CMD,    /* any_frame */ | ||||
|     F_FRAME_MEASURE_SPEED_CMD,    /* measurespeed_frame */ | ||||
|     F_FRAME_MEASURE_SPEED_REPLY,  /* speed_frame */ | ||||
|     F_FRAME_BULK_TEST_CMD,        /* any_frame */ | ||||
|     F_FRAME_BULK_TEST_REPLY,      /* any_frame */ | ||||
|     F_FRAME_BULK_WRITE_TEST_CMD,   /* any_frame */ | ||||
|     F_FRAME_BULK_WRITE_TEST_REPLY, /* any_frame */ | ||||
|     F_FRAME_BULK_READ_TEST_CMD,   /* any_frame */ | ||||
|     F_FRAME_BULK_READ_TEST_REPLY, /* any_frame */ | ||||
|     F_FRAME_READ_CMD,             /* read_frame */ | ||||
|     F_FRAME_READ_REPLY,           /* any_frame */ | ||||
|     F_FRAME_WRITE_CMD,            /* write_frame */ | ||||
| @@ -84,8 +86,8 @@ enum | ||||
|  | ||||
| enum | ||||
| { | ||||
|     F_OP_PULSE = 0x80, | ||||
|     F_OP_INDEX = 0x81 | ||||
|     F_BIT_PULSE = 0x80, | ||||
|     F_BIT_INDEX = 0x40 | ||||
| }; | ||||
|  | ||||
| struct frame_header | ||||
| @@ -123,6 +125,12 @@ struct seek_frame | ||||
|     uint8_t track; | ||||
| }; | ||||
|  | ||||
| struct measurespeed_frame  | ||||
| { | ||||
|     struct frame_header f; | ||||
| 	uint8_t hard_sector_count; | ||||
| }; | ||||
|  | ||||
| struct speed_frame | ||||
| { | ||||
|     struct frame_header f; | ||||
| @@ -135,6 +143,7 @@ struct read_frame | ||||
|     uint8_t side; | ||||
|     uint8_t synced; | ||||
|     uint16_t milliseconds; | ||||
|     uint8_t hardsec_threshold_ms; | ||||
| }; | ||||
|  | ||||
| struct write_frame | ||||
| @@ -142,12 +151,14 @@ struct write_frame | ||||
|     struct frame_header f; | ||||
|     uint8_t side; | ||||
|     uint32_t bytes_to_write; | ||||
|     uint8_t hardsec_threshold_ms; | ||||
| }; | ||||
|  | ||||
| struct erase_frame | ||||
| { | ||||
|     struct frame_header f; | ||||
|     uint8_t side; | ||||
|     uint8_t hardsec_threshold_ms; | ||||
| }; | ||||
|  | ||||
| struct set_drive_frame | ||||
|   | ||||
							
								
								
									
										35
									
								
								scripts/analysedriveresponse.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								scripts/analysedriveresponse.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import numpy | ||||
| import matplotlib.pyplot as plt | ||||
| import matplotlib.animation as animation | ||||
|  | ||||
| TICK_FREQUENCY = 12e6 | ||||
| TICKS_PER_US = TICK_FREQUENCY / 1e6 | ||||
| print(TICKS_PER_US) | ||||
|  | ||||
| # Load data. | ||||
| data = numpy.loadtxt(open("driveresponse.csv", "rb"), delimiter=",", skiprows=1) | ||||
|  | ||||
| labels = data[:, 0] | ||||
| frequencies = data[:, 1:] | ||||
|  | ||||
| # Scale the frequencies. | ||||
| def scaled(row): | ||||
|     m = row.mean() | ||||
|     if m != 0: | ||||
|         return row / m | ||||
|     else: | ||||
|         return row | ||||
|  | ||||
| scaledfreq = numpy.array([scaled(row) for row in frequencies]) | ||||
|  | ||||
| # Create new Figure with black background | ||||
| fig = plt.figure(figsize=(8, 8), facecolor='#aaa') | ||||
|  | ||||
| plt.imshow(scaledfreq, extent=[0, 512/TICKS_PER_US, labels[0], labels[-1]], cmap='jet', | ||||
|            vmin=0, vmax=1, origin='lower', aspect='auto') | ||||
| plt.colorbar() | ||||
| plt.ylabel("Interval period (us)") | ||||
| plt.xlabel("Response (us)") | ||||
| plt.show() | ||||
|  | ||||
| plt.show() | ||||
							
								
								
									
										135
									
								
								src/fe-analysedriveresponse.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/fe-analysedriveresponse.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "usb/usb.h" | ||||
| #include "dataspec.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "writer.h" | ||||
| #include "protocol.h" | ||||
| #include "fmt/format.h" | ||||
| #include <fstream> | ||||
|  | ||||
| static FlagGroup flags = { | ||||
| 	&usbFlags, | ||||
| }; | ||||
|  | ||||
| static DataSpecFlag dest( | ||||
|     { "--dest", "-d" }, | ||||
|     "destination to analyse", | ||||
|     ":d=0:t=0:s=0"); | ||||
|  | ||||
| static DoubleFlag minInterval( | ||||
| 	{ "--min-interval-us" }, | ||||
| 	"Minimum pulse interval", | ||||
| 	2.0); | ||||
|  | ||||
| static DoubleFlag maxInterval( | ||||
| 	{ "--max-interval-us" }, | ||||
| 	"Maximum pulse interval", | ||||
| 	10.0); | ||||
|  | ||||
| static DoubleFlag intervalStep( | ||||
| 	{ "--interval-step-us" }, | ||||
| 	"Interval step, approximately", | ||||
| 	0.2); | ||||
|  | ||||
| static StringFlag writeCsv( | ||||
| 	{ "--write-csv" }, | ||||
| 	"Write detailed CSV data", | ||||
| 	"driveresponse.csv"); | ||||
|  | ||||
| int mainAnalyseDriveResponse(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
|     FluxSpec spec(dest); | ||||
| 	if (spec.locations.size() != 1) | ||||
| 		Error() << "the destination dataspec must contain exactly one track (two sides count as two tracks)"; | ||||
|  | ||||
|     usbSetDrive(spec.drive, false, F_INDEX_REAL); | ||||
| 	usbSeek(spec.locations[0].track); | ||||
|  | ||||
| 	std::cout << "Measuring rotational speed...\n"; | ||||
|     nanoseconds_t period = usbGetRotationalPeriod(0); | ||||
| 	if (period == 0) | ||||
| 		Error() << "Unable to measure rotational speed (try fluxengine rpm)."; | ||||
|  | ||||
| 	std::ofstream csv; | ||||
| 	if (writeCsv.get() != "") | ||||
| 		csv.open(writeCsv); | ||||
|  | ||||
| 	for (double interval = minInterval; interval<maxInterval; interval += intervalStep) | ||||
| 	{ | ||||
| 		unsigned ticks = (unsigned) (interval * TICKS_PER_US); | ||||
| 		std::cout << fmt::format("Interval {:.2f}: ", ticks * US_PER_TICK); | ||||
| 		std::cout << std::flush; | ||||
|  | ||||
| 		std::vector<int> frequencies(512); | ||||
|  | ||||
| 		if (interval >= 2.0) | ||||
| 		{ | ||||
| 			/* Write the test pattern. */ | ||||
|  | ||||
| 			Fluxmap outFluxmap; | ||||
| 			while (outFluxmap.duration() < period) | ||||
| 			{ | ||||
| 				outFluxmap.appendInterval(ticks); | ||||
| 				outFluxmap.appendPulse(); | ||||
| 			} | ||||
| 			usbWrite(spec.locations[0].side, outFluxmap.rawBytes(), 0); | ||||
|  | ||||
| 			/* Read the test pattern in again. */ | ||||
|  | ||||
| 			Fluxmap inFluxmap; | ||||
| 			inFluxmap.appendBytes(usbRead(spec.locations[0].side, true, period, 0)); | ||||
|  | ||||
| 			/* Compute histogram. */ | ||||
|  | ||||
| 			FluxmapReader fmr(inFluxmap); | ||||
| 			fmr.seek((double)period*0.1); /* skip first 10% and last 10% as contains junk */ | ||||
| 			fmr.findEvent(F_BIT_PULSE); | ||||
| 			while (fmr.tell().ns() < ((double)period*0.9)) | ||||
| 			{ | ||||
| 				unsigned ticks = fmr.findEvent(F_BIT_PULSE); | ||||
| 				if (ticks < frequencies.size()) | ||||
| 					frequencies[ticks]++; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/* Compute standard deviation. */ | ||||
|  | ||||
| 		int sum = 0; | ||||
| 		int prod = 0; | ||||
| 		for (int i=0; i<frequencies.size(); i++) | ||||
| 		{ | ||||
| 			sum += frequencies[i]; | ||||
| 			prod += i * frequencies[i]; | ||||
| 		} | ||||
| 		if (sum == 0) | ||||
| 			std::cout << "failed\n"; | ||||
| 		else | ||||
| 		{ | ||||
| 			double mean = prod / sum; | ||||
| 			double sqsum = 0; | ||||
| 			for (int i=0; i<frequencies.size(); i++) | ||||
| 			{ | ||||
| 				double dx = (double)i - mean; | ||||
| 				sqsum += (double)frequencies[i] * dx * dx; | ||||
| 			} | ||||
| 			double stdv = sqrt(sqsum / sum); | ||||
| 			std::cout << fmt::format("{:.4f} {:.4f}\n", stdv, mean/TICKS_PER_US); | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		if (writeCsv.get() != "") | ||||
| 		{ | ||||
| 			csv << interval; | ||||
| 			for (int i : frequencies) | ||||
| 				csv << "," << i; | ||||
| 			csv << '\n'; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @@ -81,14 +81,14 @@ int mainConvertFluxToAu(int argc, const char* argv[]) | ||||
|         while (!fmr.eof()) | ||||
|         { | ||||
|             unsigned ticks; | ||||
|             int op = fmr.readOpcode(ticks); | ||||
|             if (op == -1) | ||||
|             uint8_t bits = fmr.getNextEvent(ticks); | ||||
|             if (fmr.eof()) | ||||
|                 break; | ||||
|             timestamp += ticks; | ||||
|  | ||||
|             if (op == F_OP_PULSE) | ||||
|             if (bits & F_BIT_PULSE) | ||||
|                 data[timestamp*channels + 0] = 0x7f; | ||||
|             if (withIndex && (op == F_OP_INDEX)) | ||||
|             if (withIndex && (bits & F_BIT_INDEX)) | ||||
|                 data[timestamp*channels + 1] = 0x7f; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,11 @@ static SettableFlag fortyTrackMode( | ||||
|     "set 48 tpi mode; only every other physical track is emitted" | ||||
| ); | ||||
|  | ||||
| static SettableFlag indexedMode( | ||||
| 	{ "--indexed", "-i" }, | ||||
| 	"align data to track boundaries" | ||||
| ); | ||||
|  | ||||
| static SettableFlag singleSided( | ||||
|     { "--single-sided", "-s" }, | ||||
|     "only emit side 0" | ||||
| @@ -45,14 +50,18 @@ static void write_le32(uint8_t dest[4], uint32_t v) | ||||
|     dest[3] = v >> 24; | ||||
| } | ||||
|  | ||||
| static void appendChecksum(uint32_t& checksum, const Bytes& bytes) | ||||
| { | ||||
| 	ByteReader br(bytes); | ||||
| 	while (!br.eof()) | ||||
| 		checksum += br.read_8(); | ||||
| } | ||||
|  | ||||
| static int strackno(int track, int side) | ||||
| { | ||||
|     if (fortyTrackMode) | ||||
|         track /= 2; | ||||
|     if (singleSided) | ||||
|         return track; | ||||
|     else | ||||
|         return (track << 1) | side; | ||||
| 	return (track << 1) | side; | ||||
| } | ||||
|  | ||||
| int mainConvertFluxToScp(int argc, const char* argv[]) | ||||
| @@ -90,7 +99,8 @@ int mainConvertFluxToScp(int argc, const char* argv[]) | ||||
|     fileheader.revolutions = 5; | ||||
|     fileheader.start_track = 0; | ||||
|     fileheader.end_track = maxStrack; | ||||
|     fileheader.flags = SCP_FLAG_INDEXED | (fortyTrackMode ? 0 : SCP_FLAG_96TPI); | ||||
|     fileheader.flags = (indexedMode ? SCP_FLAG_INDEXED : 0) | ||||
| 			| (fortyTrackMode ? 0 : SCP_FLAG_96TPI); | ||||
|     fileheader.cell_width = 0; | ||||
|     fileheader.heads = singleSided ? 1 : 0; | ||||
|  | ||||
| @@ -104,9 +114,15 @@ int mainConvertFluxToScp(int argc, const char* argv[]) | ||||
|         for (int side = 0; side <= maxside; side++) | ||||
|         { | ||||
|             int strack = strackno(track, side); | ||||
|             std::cout << fmt::format("FE track {}.{}, SCP track {}: ", track, side, strack) << std::flush; | ||||
|             std::cout << fmt::format("{}.{}: ", track, side) << std::flush; | ||||
|  | ||||
|             auto fluxmap = sqlReadFlux(inputDb, track, side); | ||||
| 			if (fluxmap->bytes() == 0) | ||||
| 			{ | ||||
| 				std::cout << "missing\n"; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
|             ScpTrack trackheader = {0}; | ||||
|             trackheader.track_id[0] = 'T'; | ||||
|             trackheader.track_id[1] = 'R'; | ||||
| @@ -117,6 +133,9 @@ int mainConvertFluxToScp(int argc, const char* argv[]) | ||||
|             Bytes fluxdata; | ||||
|             ByteWriter fluxdataWriter(fluxdata); | ||||
|  | ||||
| 			if (indexedMode) | ||||
| 				fmr.findEvent(F_BIT_INDEX); | ||||
|  | ||||
|             int revolution = 0; | ||||
|             unsigned revTicks = 0; | ||||
|             unsigned totalTicks = 0; | ||||
| @@ -125,50 +144,41 @@ int mainConvertFluxToScp(int argc, const char* argv[]) | ||||
|             while (revolution < 5) | ||||
|             { | ||||
|                 unsigned ticks; | ||||
|                 int opcode = fmr.readOpcode(ticks); | ||||
|                 if (ticks) | ||||
|                 { | ||||
|                     ticksSinceLastPulse += ticks; | ||||
|                     totalTicks += ticks; | ||||
|                     revTicks += ticks; | ||||
|                 } | ||||
|                 uint8_t bits = fmr.getNextEvent(ticks); | ||||
| 				ticksSinceLastPulse += ticks; | ||||
| 				totalTicks += ticks; | ||||
| 				revTicks += ticks; | ||||
|  | ||||
|                 switch (opcode) | ||||
|                 { | ||||
|                     case -1: /* end of flux, treat like an index marker */ | ||||
|                     case F_OP_INDEX: | ||||
|                     { | ||||
|                         auto* revheader = &trackheader.revolution[revolution]; | ||||
|                         write_le32(revheader->offset, startOffset + sizeof(ScpTrack)); | ||||
|                         write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2); | ||||
|                         write_le32(revheader->index, revTicks * NS_PER_TICK / 25); | ||||
|                         revolution++; | ||||
|                         revheader++; | ||||
|                         revTicks = 0; | ||||
|                         startOffset = fluxdataWriter.pos; | ||||
|                         break; | ||||
|                     } | ||||
| 				if (fmr.eof() || (bits & F_BIT_INDEX)) | ||||
| 				{ | ||||
| 					auto* revheader = &trackheader.revolution[revolution]; | ||||
| 					write_le32(revheader->offset, startOffset + sizeof(ScpTrack)); | ||||
| 					write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2); | ||||
| 					write_le32(revheader->index, revTicks * NS_PER_TICK / 25); | ||||
| 					revolution++; | ||||
| 					revheader++; | ||||
| 					revTicks = 0; | ||||
| 					startOffset = fluxdataWriter.pos; | ||||
| 				} | ||||
|  | ||||
|                     case F_OP_PULSE: | ||||
|                     { | ||||
|                         unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25; | ||||
|                         while (t >= 0x10000) | ||||
|                         { | ||||
|                             fluxdataWriter.write_be16(0); | ||||
|                             t -= 0x10000; | ||||
|                         } | ||||
|                         fluxdataWriter.write_be16(t); | ||||
|                         ticksSinceLastPulse = 0; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 				if (bits & F_BIT_PULSE) | ||||
| 				{ | ||||
| 					unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25; | ||||
| 					while (t >= 0x10000) | ||||
| 					{ | ||||
| 						fluxdataWriter.write_be16(0); | ||||
| 						t -= 0x10000; | ||||
| 					} | ||||
| 					fluxdataWriter.write_be16(t); | ||||
| 					ticksSinceLastPulse = 0; | ||||
| 				} | ||||
|             } | ||||
|  | ||||
|             write_le32(fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader)); | ||||
|             trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader)); | ||||
|             trackdataWriter += fluxdata; | ||||
|  | ||||
|             std::cout << fmt::format("{} ms in {} bytes\n", | ||||
|             std::cout << fmt::format("{:.3f} ms in {} bytes\n", | ||||
|                 totalTicks * MS_PER_TICK, | ||||
|                 fluxdata.size()); | ||||
|         } | ||||
| @@ -176,6 +186,13 @@ int mainConvertFluxToScp(int argc, const char* argv[]) | ||||
|  | ||||
|     sqlClose(inputDb); | ||||
|      | ||||
| 	uint32_t checksum = 0; | ||||
| 	appendChecksum(checksum, | ||||
| 		Bytes((const uint8_t*) &fileheader, sizeof(fileheader)) | ||||
| 			.slice(0x10)); | ||||
| 	appendChecksum(checksum, trackdata); | ||||
| 	write_le32(fileheader.checksum, checksum); | ||||
|  | ||||
|     std::cout << "Writing output file...\n"; | ||||
|     std::ofstream of(filenames[1], std::ios::out | std::ios::binary); | ||||
|     if (!of.is_open()) | ||||
|   | ||||
| @@ -59,8 +59,8 @@ int mainConvertFluxToVcd(int argc, const char* argv[]) | ||||
|     while (!fmr.eof()) | ||||
|     { | ||||
|         unsigned ticks; | ||||
|         int op = fmr.readOpcode(ticks); | ||||
|         if (op == -1) | ||||
|         uint8_t bits = fmr.getNextEvent(ticks); | ||||
|         if (fmr.eof()) | ||||
|             break; | ||||
|  | ||||
|         unsigned newtimestamp = timestamp + ticks; | ||||
| @@ -71,9 +71,9 @@ int mainConvertFluxToVcd(int argc, const char* argv[]) | ||||
|             of << fmt::format("#{} ", (uint64_t)(timestamp * NS_PER_TICK)); | ||||
|         } | ||||
|  | ||||
|         if (op == F_OP_PULSE) | ||||
|         if (bits & F_BIT_PULSE) | ||||
|             of << "1p "; | ||||
|         if (op == F_OP_INDEX) | ||||
|         if (bits & F_BIT_INDEX) | ||||
|             of << "1i "; | ||||
|  | ||||
|         lasttimestamp = timestamp; | ||||
|   | ||||
							
								
								
									
										38
									
								
								src/fe-image.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/fe-image.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include <fstream> | ||||
|  | ||||
| static FlagGroup flags { }; | ||||
|  | ||||
| static void syntax() | ||||
| { | ||||
|     std::cout << "Syntax: fluxengine convert image <srcspec> <destspec>\n"; | ||||
|     exit(0); | ||||
| } | ||||
|  | ||||
| int mainConvertImage(int argc, const char* argv[]) | ||||
| { | ||||
|     auto filenames = flags.parseFlagsWithFilenames(argc, argv); | ||||
|     if (filenames.size() != 2) | ||||
|         syntax(); | ||||
|  | ||||
| 	DataSpec ids(filenames[0]); | ||||
| 	ImageSpec iis(ids); | ||||
| 	SectorSet sectors = ImageReader::create(iis)->readImage(); | ||||
|  | ||||
| 	DataSpec ods(filenames[1]); | ||||
| 	ImageSpec ois(ods); | ||||
| 	auto writer = ImageWriter::create(sectors, ois); | ||||
| 	writer->adjustGeometry(); | ||||
| 	writer->printMap(); | ||||
| 	writer->writeImage(); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @@ -4,7 +4,6 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "image.h" | ||||
| #include "protocol.h" | ||||
| #include "decoders/rawbits.h" | ||||
| #include "record.h" | ||||
| @@ -73,7 +72,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap) | ||||
|  | ||||
|     while (!fr.eof()) | ||||
|     { | ||||
|         unsigned interval = fr.readNextMatchingOpcode(F_OP_PULSE); | ||||
|         unsigned interval = fr.findEvent(F_BIT_PULSE); | ||||
|         if (interval > 0xff) | ||||
|             continue; | ||||
|         buckets[interval]++; | ||||
| @@ -156,7 +155,11 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap) | ||||
| 				s += BLOCK_ELEMENTS[8]; | ||||
| 			s += BLOCK_ELEMENTS[bar & 7]; | ||||
|  | ||||
| 			std::cout << fmt::format("{:.2f} {:6} {}", (double)i * US_PER_TICK, value, s); | ||||
| 			std::cout << fmt::format("{: 3} {:.2f} {:6} {}", | ||||
| 					i, | ||||
| 					(double)i * US_PER_TICK, | ||||
| 					value, | ||||
| 					s); | ||||
| 			std::cout << std::endl; | ||||
| 		} | ||||
| 	} | ||||
| @@ -186,6 +189,12 @@ int mainInspect(int argc, const char* argv[]) | ||||
| 	auto& track = *tracks.begin(); | ||||
| 	track->readFluxmap(); | ||||
|  | ||||
| 	std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n", | ||||
| 			track->fluxmap->bytes(), | ||||
| 			track->fluxmap->duration() / 1e6); | ||||
| 	std::cout << fmt::format("Required USB bandwidth: {}kB/s\n", | ||||
| 			track->fluxmap->bytes()/1024.0 / (track->fluxmap->duration() / 1e9)); | ||||
|  | ||||
| 	nanoseconds_t clockPeriod = guessClock(*track->fluxmap); | ||||
| 	std::cout << fmt::format("{:.2f} us clock detected.", (double)clockPeriod/1000.0) << std::flush; | ||||
|  | ||||
| @@ -209,7 +218,7 @@ int mainInspect(int argc, const char* argv[]) | ||||
| 		nanoseconds_t lasttransition = 0; | ||||
| 		while (!fmr.eof()) | ||||
| 		{ | ||||
| 			ticks += fmr.readNextMatchingOpcode(F_OP_PULSE); | ||||
| 			ticks += fmr.findEvent(F_BIT_PULSE); | ||||
|  | ||||
| 			nanoseconds_t transition = ticks*NS_PER_TICK; | ||||
| 			nanoseconds_t next; | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| #include "reader.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "image.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "record.h" | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user