From 894c50ff4e14c1d801b2263b20c738af2fb4ba2e Mon Sep 17 00:00:00 2001 From: Niels Moseley Date: Thu, 8 Feb 2018 23:47:09 +0100 Subject: [PATCH] Added debounced PS/2 keyboard interface and A1 top-level selection between keyboard and UART RX --- boards/terasic_de0/apple-one.qsf | 15 +- rtl/ps2keyboard/debounce.v | 72 ++++ rtl/ps2keyboard/ps2keyboard.v | 546 +++++++++++++++---------------- tools/vgaromgen/main.c | 24 +- 4 files changed, 352 insertions(+), 305 deletions(-) create mode 100644 rtl/ps2keyboard/debounce.v diff --git a/boards/terasic_de0/apple-one.qsf b/boards/terasic_de0/apple-one.qsf index 5c97e03..0b1a0f4 100644 --- a/boards/terasic_de0/apple-one.qsf +++ b/boards/terasic_de0/apple-one.qsf @@ -360,6 +360,14 @@ set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to UART_CTS set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to UART_RXD set_global_assignment -name ENABLE_SIGNALTAP ON set_global_assignment -name USE_SIGNALTAP_FILE output_files/stp1.stp +set_global_assignment -name USE_CONFIGURATION_DEVICE OFF +set_global_assignment -name CRC_ERROR_OPEN_DRAIN OFF +set_global_assignment -name CYCLONEII_RESERVE_NCEO_AFTER_CONFIGURATION "USE AS REGULAR IO" +set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -rise +set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -fall +set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -rise +set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -fall +set_global_assignment -name VERILOG_FILE ../../rtl/ps2keyboard/debounce.v set_global_assignment -name VERILOG_FILE ../../rtl/vga/vram.v set_global_assignment -name VERILOG_FILE ../../rtl/vga/vga.v set_global_assignment -name VERILOG_FILE ../../rtl/vga/font_rom.v @@ -378,11 +386,4 @@ set_global_assignment -name VERILOG_FILE ../../rtl/uart/uart.v set_global_assignment -name VERILOG_FILE ../../rtl/uart/async_tx_rx.v set_global_assignment -name VERILOG_FILE ../../rtl/rom_wozmon.v set_global_assignment -name VERILOG_FILE ../../rtl/ram.v -set_global_assignment -name USE_CONFIGURATION_DEVICE OFF -set_global_assignment -name CRC_ERROR_OPEN_DRAIN OFF -set_global_assignment -name CYCLONEII_RESERVE_NCEO_AFTER_CONFIGURATION "USE AS REGULAR IO" -set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -rise -set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -fall -set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -rise -set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -fall set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top \ No newline at end of file diff --git a/rtl/ps2keyboard/debounce.v b/rtl/ps2keyboard/debounce.v new file mode 100644 index 0000000..b988b4f --- /dev/null +++ b/rtl/ps2keyboard/debounce.v @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +// Description: PS/2 keyboard debounce logic to be used for the +// clock line +// +// Author.....: Niels A. Moseley +// Date.......: 8-2-2018 +// + +module debounce( + input clk25, // 25MHz clock + input rst, // active high reset + + input sig_in, // input signal + output reg sig_out // debounced output signal +); + +wire clk_enb; // enable triggering at clk25 divided by 64 +reg [5:0] clk_div; // clock divider counter + +reg sig_ff1; // first input signal synchronizer +reg sig_ff2; // second input signal synchronizer + +assign clk_enb = (clk_div == 6'd0); + +// clock divider +always @(posedge clk25 or posedge rst) +begin + if (rst) + clk_div <= 6'd0; + else + clk_div <= clk_div + 6'd1; +end + +// debounce timer +always @(posedge clk25 or posedge rst) +begin + if (rst) + begin + sig_out <= 1'b0; + sig_ff1 <= 1'b0; + sig_ff2 <= 1'b0; + end + else if (clk_enb) + begin + // this runs ar approximately 391k Hz + // giving a debounce time of around 2.5us + sig_ff1 <= sig_in; + sig_ff2 <= sig_ff1; + if ((sig_ff1 ^ sig_ff2) == 1'd0) + begin + sig_out <= sig_ff2; + end + end +end + +endmodule diff --git a/rtl/ps2keyboard/ps2keyboard.v b/rtl/ps2keyboard/ps2keyboard.v index 855528e..eba9589 100644 --- a/rtl/ps2keyboard/ps2keyboard.v +++ b/rtl/ps2keyboard/ps2keyboard.v @@ -35,21 +35,16 @@ module ps2keyboard ( output reg [7:0] dout // 8-bit output bus. ); - // ************************************************************ - // signals in the slow PS/2 clock domain - // ************************************************************ reg [3:0] rxcnt; // count how many bits have been shift into rxshiftbuf reg [10:0] rxshiftbuf; // 11 bit shift receive register - reg rx_flag = 0; // this flag changes state (0->1 or 1->0) when + reg rx_flag = 0; // this flag is 1 when // valid data is available in rxshiftbuf - // ************************************************************ - // signals in the high-speed clock (clk25) domain - // ************************************************************ - reg [7:0] rx; // receive buffer - reg rxflag_ff; // flip-flop state for clk domain xing - reg rx_rdy; // data ready to be read + reg [7:0] rx; // scancode receive buffer + wire ps2_clkdb; // debounced PS/2 clock signal + reg prev_ps2_clkdb; // previous clock state (in clk25 domain) + // keyboard translation signals reg [7:0] ascii; // ASCII code of received character reg ascii_rdy; // new ASCII character received @@ -58,53 +53,55 @@ module ps2keyboard ( reg [2:0] next_state; reg [15:0] debounce_timer; -// -// PS/2 data from a device changes when the clock -// is low, so we latch when the clock transitions -// to a high state -// -always @(negedge key_clk or posedge rst) -begin - if (rst == 1'b1) + debounce ps2clk_debounce + ( + .clk25(clk25), + .rst(rst), + .sig_in(key_clk), + .sig_out(ps2_clkdb) + ); + + always @(posedge clk25 or posedge rst) begin - // reset the serial buffer - rxshiftbuf <= 11'b0; - rxcnt <= 0; - rx_flag <= 0; - end - else - begin - // shift in LSB first from keyboard - rxshiftbuf <= {key_din, rxshiftbuf[10:1]}; - rxcnt <= rxcnt + 4'b1; - if (rxcnt == 4'd10) + if (rst) begin - // 10 bits have been shifted in - // we should have a complete - // scan code here, including - // start, parity and stop bits. - rxcnt <= 0; - rx_flag <= !rx_flag; // change state to signal new data + prev_ps2_clkdb <= 1'b0; + rx_flag <= 1'b0; + end + else + begin + rx_flag <= 1'b0; // reset the new data flag register + + // check for negative edge of PS/2 clock + // and sample the state of the PS/2 data line + if ((prev_ps2_clkdb == 1'b1) && (ps2_clkdb == 1'b0)) + begin + rxshiftbuf <= {key_din, rxshiftbuf[10:1]}; + rxcnt <= rxcnt + 4'b1; + if (rxcnt == 4'd10) + begin + // 10 bits have been shifted in + // we should have a complete + // scan code here, including + // start, parity and stop bits. + rxcnt <= 0; + + // signal new data is present + // note: this signal will only remain high for one + // clock cycle! + // + // TODO: check parity here? + rx_flag <= 1'b1; + end + end + + // update previous clock state + prev_ps2_clkdb <= ps2_clkdb; + end end -end - -// -// clock domain crossing from slow PS/2 clock to -// high-speed clock domain: -// -// --------------| | -// | _______ | XOR |----> rx_valid_stb -// flag ---| D Q |----| | -// | | -// clk ----|> | -// |_______| -// -// when flag toggles state, tx_valid_stb will become -// '1' for exactly one (high-speed) clock cycle. -// - + // // IBM Keyboard code page translation // state machine for US keyboard layout @@ -112,248 +109,225 @@ end // http://www.computer-engineering.org/ps2keyboard/scancodes2.html // -localparam S_KEYNORMAL = 3'b000; -localparam S_KEYF0 = 3'b001; // regular key release state -localparam S_KEYE0 = 3'b010; // extended key state -localparam S_KEYE0F0 = 3'b011; // extended release state + localparam S_KEYNORMAL = 3'b000; + localparam S_KEYF0 = 3'b001; // regular key release state + localparam S_KEYE0 = 3'b010; // extended key state + localparam S_KEYE0F0 = 3'b011; // extended release state -always @(posedge clk25 or posedge rst) -begin - if (rst) + always @(posedge clk25 or posedge rst) begin - rxflag_ff <= 0; - rx <= 0; - rx_rdy <= 0; - ascii_rdy <= 0; - shift <= 0; - cur_state <= S_KEYNORMAL; - end - else - begin - // check for new RX data from the keyboard - rxflag_ff <= rx_flag; - - if ((rxflag_ff ^ rx_flag) == 1'b1) + if (rst) begin - // we detected a change in the rx_flag - // so we have valid data in the rxshiftbuf - - // bits 8 .. 1 contain the actual keyboard - // scan code. The other bits are start/parity - // and stop bits. - // - // TODO: do a parity check! .. - rx <= rxshiftbuf[8:1]; - rx_rdy <= 1; - end - - // handle I/O from CPU - if (cs == 1'b1) - begin - if (address == 1'b0) - begin - // RX buffer address - dout <= {1'b1, ascii[6:0]}; - ascii_rdy <= 1'b0; - end - else - begin - // RX status register - dout <= {ascii_rdy, 7'b0}; - end - end - - // handle the debounce timer - if (debounce_timer != 16'd0) - begin - debounce_timer <= debounce_timer - 16'd1; - end - - // keyboard translation state machine - if (rx_rdy == 1'b1) - begin - rx_rdy <= 1'b0; - case(cur_state) - S_KEYNORMAL: - begin - if (rx == 8'hF0) - next_state = S_KEYF0; - else if (rx == 8'hE0) - next_state = S_KEYE0; - else - begin - // check the debounce timer, if this is - // not zero, a new key arrived too quickly - // and we simply discard it. For better - // debouncing, we should check if the key - // is actually the same as the previous received/ - // key, but let's try this first to see if it works - // ok... - - if (debounce_timer == 16'd0) - begin - ascii_rdy <= 1'b1; // new key has arrived! - debounce_timer <= 16'hFFFF; // reset the debounce timer - end - - // check for a SHIFT key - if ((rx == 8'h59) || (rx == 8'h12)) - begin - shift <= 1'b1; - ascii_rdy <= 1'b0; // shift is not a key! - end - else begin - if (!shift) - case(rx) - 8'h1C: ascii <= "A"; - 8'h32: ascii <= "B"; - 8'h21: ascii <= "C"; - 8'h23: ascii <= "D"; - 8'h24: ascii <= "E"; - 8'h2B: ascii <= "F"; - 8'h34: ascii <= "G"; - 8'h33: ascii <= "H"; - 8'h43: ascii <= "I"; - 8'h3B: ascii <= "J"; - 8'h42: ascii <= "K"; - 8'h4B: ascii <= "L"; - 8'h3A: ascii <= "M"; - 8'h31: ascii <= "N"; - 8'h44: ascii <= "O"; - 8'h4D: ascii <= "P"; - 8'h15: ascii <= "Q"; - 8'h2D: ascii <= "R"; - 8'h1B: ascii <= "S"; - 8'h2C: ascii <= "T"; - 8'h3C: ascii <= "U"; - 8'h2A: ascii <= "V"; - 8'h1D: ascii <= "W"; - 8'h22: ascii <= "X"; - 8'h35: ascii <= "Y"; - 8'h1A: ascii <= "Z"; - - 8'h45: ascii <= "0"; - 8'h16: ascii <= "1"; - 8'h1E: ascii <= "2"; - 8'h26: ascii <= "3"; - 8'h25: ascii <= "4"; - 8'h2E: ascii <= "5"; - 8'h36: ascii <= "6"; - 8'h3D: ascii <= "7"; - 8'h3E: ascii <= "8"; - 8'h46: ascii <= "9"; - - 8'h4E: ascii <= "-"; - 8'h55: ascii <= "="; - 8'h5D: ascii <= "\\ "; // extra spaced needed by Quartus - 8'h66: ascii <= 8'd8; // backspace - 8'h29: ascii <= " "; - - 8'h5A: ascii <= 8'd13; // enter - 8'h54: ascii <= "["; - 8'h5B: ascii <= "]"; - 8'h4C: ascii <= ";"; - 8'h52: ascii <= "'"; - 8'h41: ascii <= ","; - 8'h49: ascii <= "."; - 8'h4A: ascii <= "/"; - 8'h59: shift <= 1'b1; // right shfit - 8'h12: shift <= 1'b1; // left shift - default: ascii <= "."; - endcase - else - // Here, we're in a shifted state - case(rx) - 8'h1C: ascii <= "A"; - 8'h32: ascii <= "B"; - 8'h21: ascii <= "C"; - 8'h23: ascii <= "D"; - 8'h24: ascii <= "E"; - 8'h2B: ascii <= "F"; - 8'h34: ascii <= "G"; - 8'h33: ascii <= "H"; - 8'h43: ascii <= "I"; - 8'h3B: ascii <= "J"; - 8'h42: ascii <= "K"; - 8'h4B: ascii <= "L"; - 8'h3A: ascii <= "M"; - 8'h31: ascii <= "N"; - 8'h44: ascii <= "O"; - 8'h4D: ascii <= "P"; - 8'h15: ascii <= "Q"; - 8'h2D: ascii <= "R"; - 8'h1B: ascii <= "S"; - 8'h2C: ascii <= "T"; - 8'h3C: ascii <= "U"; - 8'h2A: ascii <= "V"; - 8'h1D: ascii <= "W"; - 8'h22: ascii <= "X"; - 8'h35: ascii <= "Y"; - 8'h1A: ascii <= "Z"; - - 8'h45: ascii <= ")"; - 8'h16: ascii <= "!"; - 8'h1E: ascii <= "@"; - 8'h26: ascii <= "#"; - 8'h25: ascii <= "$"; - 8'h2E: ascii <= "%"; - 8'h36: ascii <= "^"; - 8'h3D: ascii <= "&"; - 8'h3E: ascii <= "*"; - 8'h46: ascii <= "("; - - 8'h4E: ascii <= "_"; - 8'h55: ascii <= "+"; - 8'h5D: ascii <= "|"; - 8'h66: ascii <= 8'd8; // backspace - 8'h29: ascii <= " "; - - 8'h5A: ascii <= 8'd13; // enter - 8'h54: ascii <= "{"; - 8'h5B: ascii <= "}"; - 8'h4C: ascii <= ":"; - 8'h52: ascii <= "\""; - 8'h41: ascii <= "<"; - 8'h49: ascii <= ">"; - 8'h4A: ascii <= "?"; - default: ascii <= "."; - endcase - end - end - end - S_KEYF0: - // when we end up here, a 0xF0 byte was received - // which usually means a key release event - begin - if ((rx == 8'h59) || (rx == 8'h12)) - shift <= 1'b0; - next_state = S_KEYNORMAL; - end - S_KEYE0: - begin - if (rx == 8'hF0) - next_state = S_KEYE0F0; - else - next_state = S_KEYNORMAL; - end - S_KEYE0F0: - begin - next_state = S_KEYNORMAL; - end - default: - begin - next_state = S_KEYNORMAL; - end - endcase; + rx <= 0; + ascii_rdy <= 0; + shift <= 0; + cur_state <= S_KEYNORMAL; end else begin - next_state = cur_state; - end + // handle I/O from CPU + if (cs == 1'b1) + begin + if (address == 1'b0) + begin + // RX buffer address + dout <= {1'b1, ascii[6:0]}; + ascii_rdy <= 1'b0; + end + else + begin + // RX status register + dout <= {ascii_rdy, 7'b0}; + end + end + + // keyboard translation state machine + if (rx_flag == 1'b1) + begin + // latch data from the serial buffer into + // the rx scancode buffer. + rx <= rxshiftbuf[8:1]; + case(cur_state) + S_KEYNORMAL: + begin + if (rx == 8'hF0) + next_state = S_KEYF0; + else if (rx == 8'hE0) + next_state = S_KEYE0; + else + begin + // check the debounce timer, if this is + // not zero, a new key arrived too quickly + // and we simply discard it. For better + // debouncing, we should check if the key + // is actually the same as the previous received/ + // key, but let's try this first to see if it works + // ok... + + //if (debounce_timer == 16'd0) + //begin + ascii_rdy <= 1'b1; // new key has arrived! + //debounce_timer <= 16'hFFFF; // reset the debounce timer + //end + + // check for a SHIFT key + if ((rx == 8'h59) || (rx == 8'h12)) + begin + shift <= 1'b1; + ascii_rdy <= 1'b0; // shift is not a key! + end + else begin + if (!shift) + case(rx) + 8'h1C: ascii <= "A"; + 8'h32: ascii <= "B"; + 8'h21: ascii <= "C"; + 8'h23: ascii <= "D"; + 8'h24: ascii <= "E"; + 8'h2B: ascii <= "F"; + 8'h34: ascii <= "G"; + 8'h33: ascii <= "H"; + 8'h43: ascii <= "I"; + 8'h3B: ascii <= "J"; + 8'h42: ascii <= "K"; + 8'h4B: ascii <= "L"; + 8'h3A: ascii <= "M"; + 8'h31: ascii <= "N"; + 8'h44: ascii <= "O"; + 8'h4D: ascii <= "P"; + 8'h15: ascii <= "Q"; + 8'h2D: ascii <= "R"; + 8'h1B: ascii <= "S"; + 8'h2C: ascii <= "T"; + 8'h3C: ascii <= "U"; + 8'h2A: ascii <= "V"; + 8'h1D: ascii <= "W"; + 8'h22: ascii <= "X"; + 8'h35: ascii <= "Y"; + 8'h1A: ascii <= "Z"; - cur_state <= next_state; + 8'h45: ascii <= "0"; + 8'h16: ascii <= "1"; + 8'h1E: ascii <= "2"; + 8'h26: ascii <= "3"; + 8'h25: ascii <= "4"; + 8'h2E: ascii <= "5"; + 8'h36: ascii <= "6"; + 8'h3D: ascii <= "7"; + 8'h3E: ascii <= "8"; + 8'h46: ascii <= "9"; + + 8'h4E: ascii <= "-"; + 8'h55: ascii <= "="; + 8'h5D: ascii <= 8'h34; // backslash + 8'h66: ascii <= 8'd8; // backspace + 8'h29: ascii <= " "; + + 8'h5A: ascii <= 8'd13; // enter + 8'h54: ascii <= "["; + 8'h5B: ascii <= "]"; + 8'h4C: ascii <= ";"; + 8'h52: ascii <= "'"; + 8'h41: ascii <= ","; + 8'h49: ascii <= "."; + 8'h4A: ascii <= "/"; + 8'h59: shift <= 1'b1; // right shfit + 8'h12: shift <= 1'b1; // left shift + default: ascii <= "."; + endcase + else + // Here, we're in a shifted state + case(rx) + 8'h1C: ascii <= "A"; + 8'h32: ascii <= "B"; + 8'h21: ascii <= "C"; + 8'h23: ascii <= "D"; + 8'h24: ascii <= "E"; + 8'h2B: ascii <= "F"; + 8'h34: ascii <= "G"; + 8'h33: ascii <= "H"; + 8'h43: ascii <= "I"; + 8'h3B: ascii <= "J"; + 8'h42: ascii <= "K"; + 8'h4B: ascii <= "L"; + 8'h3A: ascii <= "M"; + 8'h31: ascii <= "N"; + 8'h44: ascii <= "O"; + 8'h4D: ascii <= "P"; + 8'h15: ascii <= "Q"; + 8'h2D: ascii <= "R"; + 8'h1B: ascii <= "S"; + 8'h2C: ascii <= "T"; + 8'h3C: ascii <= "U"; + 8'h2A: ascii <= "V"; + 8'h1D: ascii <= "W"; + 8'h22: ascii <= "X"; + 8'h35: ascii <= "Y"; + 8'h1A: ascii <= "Z"; + + 8'h45: ascii <= ")"; + 8'h16: ascii <= "!"; + 8'h1E: ascii <= "@"; + 8'h26: ascii <= "#"; + 8'h25: ascii <= "$"; + 8'h2E: ascii <= "%"; + 8'h36: ascii <= "^"; + 8'h3D: ascii <= "&"; + 8'h3E: ascii <= "*"; + 8'h46: ascii <= "("; + + 8'h4E: ascii <= "_"; + 8'h55: ascii <= "+"; + 8'h5D: ascii <= "|"; + 8'h66: ascii <= 8'd8; // backspace + 8'h29: ascii <= " "; + + 8'h5A: ascii <= 8'd13; // enter + 8'h54: ascii <= "{"; + 8'h5B: ascii <= "}"; + 8'h4C: ascii <= ":"; + 8'h52: ascii <= "\""; + 8'h41: ascii <= "<"; + 8'h49: ascii <= ">"; + 8'h4A: ascii <= "?"; + default: ascii <= "."; + endcase + end + end + end + S_KEYF0: + // when we end up here, a 0xF0 byte was received + // which usually means a key release event + begin + if ((rx == 8'h59) || (rx == 8'h12)) + shift <= 1'b0; + next_state = S_KEYNORMAL; + end + S_KEYE0: + begin + if (rx == 8'hF0) + next_state = S_KEYE0F0; + else + next_state = S_KEYNORMAL; + end + S_KEYE0F0: + begin + next_state = S_KEYNORMAL; + end + default: + begin + next_state = S_KEYNORMAL; + end + endcase; + end + else + begin + next_state = cur_state; // deliberate blocking assingment! + end + + cur_state <= next_state; + end end -end endmodule diff --git a/tools/vgaromgen/main.c b/tools/vgaromgen/main.c index 935b874..a580260 100644 --- a/tools/vgaromgen/main.c +++ b/tools/vgaromgen/main.c @@ -1,7 +1,7 @@ -/* Converts vga_font.bin into vga_font.hex - +/* Converts vga_font.bin into vga_font.hex + Author: Niels A. Moseley - + */ #include @@ -12,22 +12,22 @@ const char hextbl[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', int main(int argc, char *argv[]) { printf("VGAROMGEN v1.0\n"); - + FILE *fin = fopen("../../roms/vga_font.bin","rt"); if (fin == NULL) { - printf("Error: cannot open vga_font.bin!\n"); + printf("Error: cannot open vga_font.bin!\n"); return 1; } - + FILE *fout = fopen("../../roms/vga_font_bitreversed.hex","wt"); if (fout == NULL) { - printf("Error: cannot open vga_font_bitreversed.hex for writing!\n"); + printf("Error: cannot open vga_font_bitreversed.hex for writing!\n"); fclose(fin); - return 1; + return 1; } - + uint8_t count = 0; uint8_t byte = 0; uint32_t bytecount = 0; @@ -39,7 +39,7 @@ int main(int argc, char *argv[]) byte >>= 1; if (c == '1') byte |= 0x80; - + count++; if (count == 8) { @@ -52,9 +52,9 @@ int main(int argc, char *argv[]) } } } - + printf("Done: converted %d bytes\n", bytecount); - + fclose(fout); fclose(fin); return 0;