Author Topic: ADC with Altera Cyclone FPGA - internal and external (I2C) implementation  (Read 7422 times)

0 Members and 1 Guest are viewing this topic.

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
How would you implement the simplest and most ingenious ADC in Altera Cyclone II - IV, without using an external ADC?
It can be 8 - 10 bits, doesn't have to be super linear, just to read the approximate position of a linear potentiometer.
I will post my idea so nobody accuses me of asking others to think for me. It is not very ingenious though but, for me as a noob, is easier than reading an I2C ADC.

Edit: To add more details, a counter in the FPGA that gets latched when the output of the comparator goes high and some logic that discharges the cap and resets the counter.
« Last Edit: May 13, 2020, 12:24:30 am by Miti »
Fear does not stop death, it stops life.
 

Online T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21686
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: ADC in Altera Cyclone FPGA
« Reply #1 on: May 09, 2020, 11:04:58 pm »
I understand LVDS inputs can be reasonable comparators; YMMV.  (Varies by family; some FPGAs have shite receivers with just about zero common mode range.)  In combination with a CMOS output, you have a S-D ADC possible.  Preferably with the CMOS output buffered to VREF, else you're relying on VCCIO and pin output resistance for that.  Or in combination with a DAC (of whatever applicable type, parallel or serial), you have a SAR ADC.  That should be fine for, eh, maybe 8 bits give or take calibration.

Ramp, sure, can be done that way; beware of linearity though.  With care, that should be usable up to 10-12 bits, with INL being the worst offender and DNL being pretty smooth.  Slow -- low sample rate due to comparing it with a digital counter.  (S-D is slow too, but can dither pretty fast without much effort.)

As for your pot, are you sure you can't use an encoder instead? :P

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #2 on: May 10, 2020, 03:42:27 am »
but, for me as a noob, is easier than reading an I2C ADC.
Nope, it identically complicated as reading a fixed address I2C ADC.  You just need a little time to think and practice some Verilog coding.

Only if the chosen I2C ADC needs internal registers to be set in advance will using the I2C be any more complicated.

3 wire ADCs using SPI bus and I2S are even easier to interface with since you don't need to worry about a bidirectional IO pins.  Running multiple of these samplers in parallel is extra easy as each sampler uses 1 additional data input pin while the 2 control outputs are shared between the ADCs.  And in your Verilog code, you just add an additional serial capture register byte for each additional ADC.

(HINT: we are here to help and strengthening your Verilog skills for IO bus controls will have a bigger payout in the long run...)
« Last Edit: May 10, 2020, 05:17:31 am by BrianHG »
 

Offline Wiljan

  • Regular Contributor
  • *
  • Posts: 225
  • Country: dk
Re: ADC in Altera Cyclone FPGA
« Reply #3 on: May 10, 2020, 05:25:02 am »
Why not use the Max10 from Intel? it does have ADC build-in and are cheap. It also have build-in flash and only need 1 voldtage supply
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #4 on: May 10, 2020, 07:29:33 am »
Why not use the Max10 from Intel? it does have ADC build-in and are cheap. It also have build-in flash and only need 1 voldtage supply
Dollar for Dollar, the Max10 has a lower density than the Cyclone's.  However, for smaller designs,  or where budget isn't a concern, it can be very useful.
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #5 on: May 10, 2020, 07:49:40 pm »
Nope, it identically complicated as reading a fixed address I2C ADC.  You just need a little time to think and practice some Verilog coding.
...
Only if the chosen I2C ADC needs internal registers to be set in advance will using the I2C be any more complicated.
...
(HINT: we are here to help and strengthening your Verilog skills for IO bus controls will have a bigger payout in the long run...)

I really want to add I2C to my project and I thought that's for a later date and higher level.
But with that very generous offer to help me improve my Verilog coding skills, you convinced me. Thank you!

I will go with an I2C ADC, for example MCP3021 plus MCP6021 input buffer.
I have a Terasic DE1 Altera Cyclone II development board and for the beginning I will be using an EEPROM with known content. If I can get to reading the content of any address in that EEPROM and display the address and the content on the 4 x 7 segment displays, I can read from the ADC.
I've attached a sample code for that board and an I2C library that I found the most appropriate and readable, in my opinion, for this application. I will try to understand and add comments to the code.
For the beginning, I will strip the code to the DE1_Default top level only and I will try adding the I2C module "i2c_master.v" and "read_eeprom.v" as higher level that uses the i2c_master.v to read from an address and display on the LED 7 segment.
Fear does not stop death, it stops life.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #6 on: May 11, 2020, 01:30:17 am »
Ok Miti,
You have a very complete development board.
The example I2C source you provided is written by someone else.  Since you do not want to debug and reverse understand their code, we will not be using it.

You will be writing an ultra simple I2C interface in Verilog specifically for the MCP3021 so you may actually gain programming skills instead of plug in and tie together third party modules skills.

Now before we begin, I need to know a few things:
1. Which Quartus are you using?
2. Have you ever written anything in Verilog in Quartus?
3. Created a 'Sheet Symbol' for such code?
4. Placed that generated symbol on a top level block diagram schematic?
5. Wired that module to IO pins on your developement board?
6. Have you ever simulated your own code before?

« Last Edit: May 11, 2020, 03:13:58 am by BrianHG »
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: ADC in Altera Cyclone FPGA
« Reply #7 on: May 11, 2020, 12:22:07 pm »
Not saying that this is the right way , but lease complex way for such a simple device as the MCP3021, and with limited HDL skills you could consider stripping things down, making the problem less of a coding issue and more of a data issue.

Just use long shift registers to:
- control the SCLK
- control the tristate on the SDAT line
- collect the data bits from from the ADC
- control when the data from the serial input shift register are transferred to the serial output (and possibly strobe a "data enable" line).

If you then have the correct length shift registers, with the correct values in, the code becomes mundane without any tricky logic traps. All you need is the right length shift registers with the correct patterns in it (and to adjust your constants to match).

Not saying that these shift registers below are the correct length, or the correct values are there for anything in particular, but it generates something resembling an I2C waveform and shows the idea.

Code: [Select]
`timescale 1ns / 1ps
module dummy_i2c(
    input clk,

    output i2c_sclk,
    inout  i2c_dat,  // Externally pulled up

    output [3:0] leds
    );

   // Shift registers
   reg [79:0] data_in_sr;
   reg [79:0] tristate_sr;
   reg [79:0] sclk_sr;
   reg [79:0] data_ready_sr;

   // For driving the output buffer
   wire pin_value;
   wire pin_tristate;

   // For dividing the clock
   reg [3:0]  counter;

   // Holds the last read data value
   reg [3:0]  led_reg;

   // Attach the input/output pins to the internal registers
   assign i2c_sclk     = sclk_sr[39];
   assign pin_tristate = tristate_sr[39];
   assign leds         = led_reg;

initial begin
   // Patterns for waggling the pins to generate the I2C transaction
                    //  77777777776666666666555555555544444444443333333333222222222211111111110000000000
                    //  98765432109876543210987654321098765432109876543210987654321098765432109876543210
   tristate_sr   <= 80'b10011110000111100001111000000000000111111111111111111111111111111111111111100111;
   sclk_sr       <= 80'b11001100110011001100110011001100110011001100110011001100110011001100110011001111;
   data_ready_sr <= 80'b00000000000000000000000000000000000000000000000000000000000000000000000000000001;
   counter       <=  4'b0000;
end


always @ (posedge clk) begin
   // Divide the system CLK by 5, making the serial clk 1/20th the system clock.
if(counter == 4'b0101) begin
  // If we should have valid data in the shift register then save it.
  if(data_ready_sr[39] == 1'b1) begin
     led_reg = { data_in_sr[14], data_in_sr[10], data_in_sr[6], data_in_sr[2] };
  end
  // Move all the shift registers along on bit.
  tristate_sr    <= { tristate_sr[78:0],   tristate_sr[79] };
  sclk_sr        <= { sclk_sr[78:0],       sclk_sr[79] };
  data_ready_sr  <= { data_ready_sr[78:0], data_ready_sr[79] };
  // Capture incoming data in the input shift register
  data_in_sr     <= { data_in_sr[78:0],    pin_value };
  counter        <= 4'b0;
end else begin
   counter <= counter + 1;
end
end

  // This is for an Xilinx part, but the equivilent will exist for Altera/Intel
IOBUF #(
      .DRIVE(12), // Specify the output drive strength
      .IOSTANDARD("DEFAULT"), // Specify the I/O standard
      .SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst (
      .O(pin_value),     // Buffer output
      .IO(i2c_dat),      // Buffer inout port (connect directly to top-level port)
      .I(0),  // Buffer input
      .T(pin_tristate)   // 3-state enable input, high=input, low=output
   );
endmodule

« Last Edit: May 11, 2020, 12:24:02 pm by hamster_nz »
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 
The following users thanked this post: Miti

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #8 on: May 11, 2020, 12:44:42 pm »
Ok Miti,
You have a very complete development board.
The example I2C source you provided is written by someone else.  Since you do not want to debug and reverse understand their code, we will not be using it.

You will be writing an ultra simple I2C interface in Verilog specifically for the MCP3021 so you may actually gain programming skills instead of plug in and tie together third party modules skills.

Now before we begin, I need to know a few things:
1. Which Quartus are you using? 13.0SP1, this is the latest that support Cyclone II.
2. Have you ever written anything in Verilog in Quartus? Basic things like examples from web, small counters and dividers, LED drivers.
3. Created a 'Sheet Symbol' for such code? Yes, as I said above, basic.
4. Placed that generated symbol on a top level block diagram schematic? Yes.
5. Wired that module to IO pins on your developement board? Yes.
6. Have you ever simulated your own code before? I tried to and it works if I don't use mega functions, altcounter, altpll, etc.

See here for simulation issues:
https://www.eevblog.com/forum/fpga/which-cyclone-pll-implementation-is-better/
Fear does not stop death, it stops life.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #9 on: May 11, 2020, 10:01:20 pm »
Ok, lets start.
     We will begin doing this 'Extra Dumb' style, so smart experienced ones out there please be patient as we will convert all the 'IF's to case statements and improve sequencing lateron.  The goal is first to make Miti write this first interface himself.

Step 1:

Make the Verilog module (MCP3021_reader) inst with inputs and outputs, IE
INPUTS -> clk, reset, conv, SCL_in, SDA_in
Outputs regs -> SCL_oe, SCL_out, SDA_oe, SDA_out, adc_val (10bit) , adc_rdy (strobes high when ready)

(I listed the SCL and SDA as ins and outs, + bidir pin tristate controls out for both since we may allow full I2C bus release.)

Since we need to run the I2C at a lower frequency, we want 2 adjustable parameter for the module:
#1- CLK_IN_HZ
#2- I2C_SCL_HZ

Start you module, use as an example setup my RS232 transceiver posted here:
https://www.eevblog.com/forum/fpga/verilog-rs232-uart-and-rs232-debugger-source-code-and-educational-tutorial/msg2801394/#msg2801394
(We will be programming with similar, yet even simpler coding techniques to make this work)

For now,
1. Make a synchronous reset defaults for the output pins.
2. Make a period counter (call it I2C_period[23:0] ) with enough bits to operate as slow as 2 cycles per second if you use you dev board's 25Mhz clock.
3. Make the 'adc_rdy' output reg cycle/invert it's value once every period count.
4. Make a symbol for the 'MCP3021_reader', and wire it into your dev-board's Cyclone so that the reset is button actuated and the adc_rdy will cycle an led.
5. Test and post the .sv file here and we will add the sequencer to I2C communicate with the MCP3021.

When I say test, 2 steps,
A) set the 2 parameters so the led will blink as a speed you can visually see
B) simulate so that you can see the 'adc_rdy' cycle, BUT change the parameters so that this cycling happens around every 4-16 clocks, not once every 25million clock cycles.

make your code similar to :
Code: [Select]
always @ (posedge clk) begin

if (reset) begin

end else begin

end

end // posedge clk
endmodule...

« Last Edit: May 11, 2020, 10:24:49 pm by BrianHG »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14472
  • Country: fr
Re: ADC in Altera Cyclone FPGA
« Reply #10 on: May 12, 2020, 12:07:04 am »
I would personally probably implement that as a sigma-delta ADC. I already have a generic sigma-delta modulator in VHDL with which I have implemented DACs. Implementing an ADC on top of that would be relatively simple. All I'd need would be a couple external parts: typically a comparator and a couple passives.
 
The following users thanked this post: Miti

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #11 on: May 12, 2020, 01:31:32 am »
Not saying that this is the right way , but lease complex way for such a simple device as the MCP3021, and with limited HDL skills you could consider stripping things down, making the problem less of a coding issue and more of a data issue.

Just use long shift registers to:
- control the SCLK
- control the tristate on the SDAT line
- collect the data bits from from the ADC
- control when the data from the serial input shift register are transferred to the serial output (and possibly strobe a "data enable" line).

If you then have the correct length shift registers, with the correct values in, the code becomes mundane without any tricky logic traps. All you need is the right length shift registers with the correct patterns in it (and to adjust your constants to match).

Not saying that these shift registers below are the correct length, or the correct values are there for anything in particular, but it generates something resembling an I2C waveform and shows the idea.

Code: [Select]
`timescale 1ns / 1ps
module dummy_i2c(
    input clk,

    output i2c_sclk,
    inout  i2c_dat,  // Externally pulled up

    output [3:0] leds
    );

   // Shift registers
   reg [79:0] data_in_sr;
   reg [79:0] tristate_sr;
   reg [79:0] sclk_sr;
   reg [79:0] data_ready_sr;

   // For driving the output buffer
   wire pin_value;
   wire pin_tristate;

   // For dividing the clock
   reg [3:0]  counter;

   // Holds the last read data value
   reg [3:0]  led_reg;

   // Attach the input/output pins to the internal registers
   assign i2c_sclk     = sclk_sr[39];
   assign pin_tristate = tristate_sr[39];
   assign leds         = led_reg;

initial begin
   // Patterns for waggling the pins to generate the I2C transaction
                    //  77777777776666666666555555555544444444443333333333222222222211111111110000000000
                    //  98765432109876543210987654321098765432109876543210987654321098765432109876543210
   tristate_sr   <= 80'b10011110000111100001111000000000000111111111111111111111111111111111111111100111;
   sclk_sr       <= 80'b11001100110011001100110011001100110011001100110011001100110011001100110011001111;
   data_ready_sr <= 80'b00000000000000000000000000000000000000000000000000000000000000000000000000000001;
   counter       <=  4'b0000;
end


always @ (posedge clk) begin
   // Divide the system CLK by 5, making the serial clk 1/20th the system clock.
if(counter == 4'b0101) begin
  // If we should have valid data in the shift register then save it.
  if(data_ready_sr[39] == 1'b1) begin
     led_reg = { data_in_sr[14], data_in_sr[10], data_in_sr[6], data_in_sr[2] };
  end
  // Move all the shift registers along on bit.
  tristate_sr    <= { tristate_sr[78:0],   tristate_sr[79] };
  sclk_sr        <= { sclk_sr[78:0],       sclk_sr[79] };
  data_ready_sr  <= { data_ready_sr[78:0], data_ready_sr[79] };
  // Capture incoming data in the input shift register
  data_in_sr     <= { data_in_sr[78:0],    pin_value };
  counter        <= 4'b0;
end else begin
   counter <= counter + 1;
end
end

  // This is for an Xilinx part, but the equivilent will exist for Altera/Intel
IOBUF #(
      .DRIVE(12), // Specify the output drive strength
      .IOSTANDARD("DEFAULT"), // Specify the I/O standard
      .SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst (
      .O(pin_value),     // Buffer output
      .IO(i2c_dat),      // Buffer inout port (connect directly to top-level port)
      .I(0),  // Buffer input
      .T(pin_tristate)   // 3-state enable input, high=input, low=output
   );
endmodule
My first step if a little more complex above a simple 1 unified serial pattern.  Though, I did say above that interfacing with a MCP3021 would be just as simple, or even simpler than making a sigma-delta ADC converter from scratch.

We begin with a period 100/400/1000/any KHz programmable clk with configurable shift segments, then we will separate the segments into the I2C protocol (Start/Stop/ACK/read/write) into separate sequences which will be driven by a master function sequencer so Miti will be able to address any I2C device, including flash I2C eeprom finally adding clock stretching/write eedata timer.

« Last Edit: May 12, 2020, 02:29:00 am by BrianHG »
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #12 on: May 12, 2020, 11:57:57 am »
I'm not only looking to get the job done and go away, I also want to learn in the process so I think Brian's approach is good.

Thanks Brian, I'll work on it in the next few days, life permitted.
Fear does not stop death, it stops life.
 

Offline AndyC_772

  • Super Contributor
  • ***
  • Posts: 4228
  • Country: gb
  • Professional design engineer
    • Cawte Engineering | Reliable Electronics
Re: ADC in Altera Cyclone FPGA
« Reply #13 on: May 12, 2020, 12:44:12 pm »
The simplest way to achieve an ADC-like input is to connect an R-C network to a single-ended digital output, so you can generate a (relatively) slowly rising voltage. Connect this to one half of a differential input, and the variable voltage from your potentiometer to the other half.

To measure voltage, drive the output low for long enough to discharge the cap fully, then drive it high and start a fast timer. Stop or capture the value in the timer register when the differential input changes state.

It's crude, of course, but you should get something approaching reasonable linearity if you restrict the range of the input to about 0.5x the final capacitor voltage. Best of all, it only takes up 3 pins and has essentially zero component cost.

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14472
  • Country: fr
Re: ADC in Altera Cyclone FPGA
« Reply #14 on: May 12, 2020, 02:12:23 pm »
I'm not only looking to get the job done and go away, I also want to learn in the process so I think Brian's approach is good.

Unless I missed something, he talked about interfacing an external ADC? That's certainly something you'll also learn from, but I thought your initial goal was to *implement* an ADC, and not use an external one?

Sorry if I missed something in what Brian suggested, though.
 

Online langwadt

  • Super Contributor
  • ***
  • Posts: 4427
  • Country: dk
Re: ADC in Altera Cyclone FPGA
« Reply #15 on: May 12, 2020, 03:54:02 pm »
The simplest way to achieve an ADC-like input is to connect an R-C network to a single-ended digital output, so you can generate a (relatively) slowly rising voltage. Connect this to one half of a differential input, and the variable voltage from your potentiometer to the other half.

To measure voltage, drive the output low for long enough to discharge the cap fully, then drive it high and start a fast timer. Stop or capture the value in the timer register when the differential input changes state.

It's crude, of course, but you should get something approaching reasonable linearity if you restrict the range of the input to about 0.5x the final capacitor voltage. Best of all, it only takes up 3 pins and has essentially zero component cost.

if it is only for a potmeter input it can be even simpler, similar to the joystick port on old computers.

potmeter(+resistor to set a minimum) from Vcc to a cap, cap voltage to a tri-stateble input, when it reaches thresh-hold pulse the pin low to discharge cap

 
The following users thanked this post: Siwastaja

Online langwadt

  • Super Contributor
  • ***
  • Posts: 4427
  • Country: dk
Re: ADC in Altera Cyclone FPGA
« Reply #16 on: May 12, 2020, 04:05:40 pm »
I understand LVDS inputs can be reasonable comparators; YMMV.  (Varies by family; some FPGAs have shite receivers with just about zero common mode range.)  In combination with a CMOS output, you have a S-D ADC possible.  Preferably with the CMOS output buffered to VREF, else you're relying on VCCIO and pin output resistance for that.  Or in combination with a DAC (of whatever applicable type, parallel or serial), you have a SAR ADC.  That should be fine for, eh, maybe 8 bits give or take calibration.

Ramp, sure, can be done that way; beware of linearity though.  With care, that should be usable up to 10-12 bits, with INL being the worst offender and DNL being pretty smooth.  Slow -- low sample rate due to comparing it with a digital counter.  (S-D is slow too, but can dither pretty fast without much effort.)

As for your pot, are you sure you can't use an encoder instead? :P

Tim

the comperator for an S-D ADC shouldn't need much common mode, the input is centered +/- hysteresis



in a pinch you might be able to get away without the opamp and just use a cap to ground (and an invert of the output)

 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8172
  • Country: fi
Re: ADC in Altera Cyclone FPGA
« Reply #17 on: May 12, 2020, 04:16:57 pm »
if it is only for a potmeter input it can be even simpler, similar to the joystick port on old computers.

potmeter(+resistor to set a minimum) from Vcc to a cap, cap voltage to a tri-stateble input, when it reaches thresh-hold pulse the pin low to discharge cap

Yes, I have done this on Cyclone II, don't have the VHDL around here now but I think it was a ridiculously easy one-hour job or so. It worked very well. Controlled a "throttle" signal with a potentiometer on a DIY conversion EV inverter.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #18 on: May 12, 2020, 09:33:47 pm »
I'm not only looking to get the job done and go away, I also want to learn in the process so I think Brian's approach is good.

Thanks Brian, I'll work on it in the next few days, life permitted.
I recommend editing your first opening post and change the thread title to "I2C interface for MCP3021 ADC with Altera Cyclone FPGA" so this thread doesn't get filled with 2 groups of posts making all our heads spin.
 
The following users thanked this post: SiliconWizard

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #19 on: May 13, 2020, 12:08:56 am »
I'm not only looking to get the job done and go away, I also want to learn in the process so I think Brian's approach is good.

Unless I missed something, he talked about interfacing an external ADC? That's certainly something you'll also learn from, but I thought your initial goal was to *implement* an ADC, and not use an external one?

Sorry if I missed something in what Brian suggested, though.

Yes, you are absolutely right, I started this thinking that I want something easy to implement inside the FPGA, something that I can take on as a beginner in FPGAs. I did not exclude an external I2C device but I saw it as a next level project, and it is next level on my own.
With some help from the community, however, it seems more doable, and thanks to Brian for offering that help.
I'm still interested in picking the community brains for ideas that I know are abundant and may be handy some time.
For example I googled "joystick port on old computers" and Dave's video #1054 came up.

So yes, I figured that people would be puzzled and I apologise.
Fear does not stop death, it stops life.
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #20 on: May 13, 2020, 12:16:21 am »
The simplest way to achieve an ADC-like input is to connect an R-C network to a single-ended digital output, so you can generate a (relatively) slowly rising voltage. Connect this to one half of a differential input, and the variable voltage from your potentiometer to the other half.

To measure voltage, drive the output low for long enough to discharge the cap fully, then drive it high and start a fast timer. Stop or capture the value in the timer register when the differential input changes state.

It's crude, of course, but you should get something approaching reasonable linearity if you restrict the range of the input to about 0.5x the final capacitor voltage. Best of all, it only takes up 3 pins and has essentially zero component cost.

I read somewhere that in the newer Cyclones, the differential inputs don't like common mode but the older ones had really fast comparators that accepted common mode.
Fear does not stop death, it stops life.
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #21 on: May 13, 2020, 12:21:20 am »
The simplest way to achieve an ADC-like input is to connect an R-C network to a single-ended digital output, so you can generate a (relatively) slowly rising voltage. Connect this to one half of a differential input, and the variable voltage from your potentiometer to the other half.

To measure voltage, drive the output low for long enough to discharge the cap fully, then drive it high and start a fast timer. Stop or capture the value in the timer register when the differential input changes state.

It's crude, of course, but you should get something approaching reasonable linearity if you restrict the range of the input to about 0.5x the final capacitor voltage. Best of all, it only takes up 3 pins and has essentially zero component cost.

if it is only for a potmeter input it can be even simpler, similar to the joystick port on old computers.

potmeter(+resistor to set a minimum) from Vcc to a cap, cap voltage to a tri-stateble input, when it reaches thresh-hold pulse the pin low to discharge cap

The Cyclone IOs don't have Schmitt trigger inputs so I wonder how clean and repeatable that threshold level is.
Fear does not stop death, it stops life.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Re: ADC in Altera Cyclone FPGA
« Reply #22 on: May 13, 2020, 03:17:38 am »
The simplest way to achieve an ADC-like input is to connect an R-C network to a single-ended digital output, so you can generate a (relatively) slowly rising voltage. Connect this to one half of a differential input, and the variable voltage from your potentiometer to the other half.

To measure voltage, drive the output low for long enough to discharge the cap fully, then drive it high and start a fast timer. Stop or capture the value in the timer register when the differential input changes state.

It's crude, of course, but you should get something approaching reasonable linearity if you restrict the range of the input to about 0.5x the final capacitor voltage. Best of all, it only takes up 3 pins and has essentially zero component cost.

if it is only for a potmeter input it can be even simpler, similar to the joystick port on old computers.

potmeter(+resistor to set a minimum) from Vcc to a cap, cap voltage to a tri-stateble input, when it reaches thresh-hold pulse the pin low to discharge cap

The Cyclone IOs don't have Schmitt trigger inputs so I wonder how clean and repeatable that threshold level is.
The threshold moves with the VCCIO voltage and ever so slightly with the die temperature.

Though Schmitt trigger inputs may prevent bounce or oscillation on a slow rising or slow falling input signal, the truth is that timing and edge wise, Schmitt trigger inputs are actually noisier than regular inputs.
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Hey Brian,

Here's the module you requested. I can confirm it blinks the LEDR[0] at 2Hz and it resets and stays in reset when I press KEY[0].
Simulation to follow...  :box:
Fear does not stop death, it stops life.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Ok, perfect, excellent.
It is ok to work on solely the 'MCP3021_reader.v' as ascii text here on the forum using the 'Insert Code' icon...

Here are my first corrections/updates:

Code: [Select]
module MCP3021_Reader (
Clk, // System clock
Rst_N, // General reset active low
SCL_in, // I2C serial cloc input. Is this necessary for single master?
SDA_in, // I2C serial data input
Conv, // Start acquisition

SCL_out, // Serial clock output
SDA_out, // Serial data output
SCL_oe, // Output enable for SCL
SDA_oe, // Output enable for SDA
ADC_rdy, // ADC_rdy output - strobes high when data is available
ADC_val // 10 bit ADC register. ADC data is available in this register when ADC_Rdy
);

input Clk;
input Rst_N;
input SCL_in;
input SDA_in;
input Conv;

output reg SCL_out;
output reg SDA_out;
output reg SCL_oe;
output reg SDA_oe;
output reg ADC_rdy;
output reg [9:0] ADC_val;

reg[23:0] I2C_Period = 24'd0; // I2C clock divider

parameter   CLK_IN_HZ =  16;    //25'd24000000; // System clock frequency
parameter   I2C_SCL_HZ = 4; // I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)
localparam  I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;

always @ (posedge Clk)
begin
if (!Rst_N)
begin
I2C_Period <= I2C_PERIOD_SIZE ;
ADC_rdy    <= 0;

SCL_out    <= 1;  // I2C default state is all HIGH
SCL_oe     <= 0;  // make I2C tristate by default.
SDA_out    <= 1;  // I2C default state is all HIGH
SDA_oe     <= 0;  // make I2C tristate by default.

ADC_rdy    <= 0;
end

else
begin
if ( I2C_Period == 0 )
begin

SCL_out    <= 1;  // I2C default state is all HIGH
SCL_oe     <= 1;  // make I2C tristate by default.
SDA_out    <= 1;  // I2C default state is all HIGH
SDA_oe     <= 1;  // make I2C tristate by default.

ADC_rdy <= ~ADC_rdy;
I2C_Period <= I2C_PERIOD_SIZE;
end

else
begin
I2C_Period <= I2C_Period - 1;
end
end

end // posedge clk

endmodule

Ok, now for the changes I made:
#1:
parameter   CLK_IN_HZ =  16;    //25'd24000000;      // System clock frequency
parameter   I2C_SCL_HZ = 4;            // I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)
localparam  I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;

In the parameter, removing the " 25'd### " in Quartus changes this:



to this:



The 'localparam I2C_PERIOD_SIZE' is preferable as we will use a number of localparams to specify the structure and timing of the I2C protocol and it is preferable that all these controls are grouped right at the top of the source code.  It's good to avoid any main structural mathematics in the middle of the code.
 
As for the reset, I added all the defaults for the IO pins.
As for the loop, I made the test for ' if ( I2C_Period == 0 ) ' so that the loop begins right after the reset without delay.
Then I make ' I2C_Period <= I2C_PERIOD_SIZE ; ', and while waiting for the period, I subtract 1 at a time.

I setup a simple Quartus 9.1 project using Quartus' built in simulator as seen here:

993630-2

I've attached the project (minus the sub folders and report & programming binaries so that the .zip is tiny).  It will open, compile and regenerate the missing files in any Quartus version.  Note that it is a virgin project just for simulation.  It is not connected to your development board.

If you are using a more modern proper simulator like Modelsim, continue to build your own simulation.

Next, we will attack the simple 1 dimensional sequencer almost exactly like hamster_nz 's recommendation.  Then, break that one down into 2 dimensions where we make use of a higher dimension calling 6 sub dimension RX/TX _I2Cbyte, TX_I2C_start/stop/ack/nak sequences.

(The 2 dimension version will then be expandable to do a selection of higher main sequences used for addressing I2C eeproms.  Like write_byte and read_byte providing an r_address & w_address and data_in/out bus, then even further if you really want to get advanced like write_block, read_block & block_size for even faster access.)
« Last Edit: May 18, 2020, 05:29:22 am by BrianHG »
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Next step, the 1 dimensional sequencer.

First, you will need to add:
reg - sequence_counter[].
reg - sequence_busy
localparam sequence_size  (for now, make it 20)

Update the code to reset these & when the reset is released, wait for a Conv.

Once the Conv is received, run the period counter sequence for the sequence_size, meaning you should see only 10 cycles of the ADC_rdy and then it should stop until Conv is pulsed again for at least 1 clock cycle.  Basically, only take in the Conv when 'sequence_busy' is false, then make 'sequence_busy' active and run the period counter and oscillator until the sequence_counter[] reaches it's end, then clear the sequence busy.

(I hope you have a 'MCP3021', around 2 posts from now, it should already work...)
« Last Edit: May 18, 2020, 04:12:37 am by BrianHG »
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Next step, the 1 dimensional sequencer.

First, you will need to add:
reg - sequence_counter[].
reg - sequence_busy
localparam sequence_size  (for now, make it 20)

Update the code to reset these & when the reset is released, wait for a Conv.

Once the Conv is received, run the period counter sequence for the sequence_size, meaning you should see only 10 cycles of the ADC_rdy and then it should stop until Conv is pulsed again for at least 1 clock cycle.  Basically, only take in the Conv when 'sequence_busy' is false, then make 'sequence_busy' active and run the period counter and oscillator until the sequence_counter[] reaches it's end, then clear the sequence busy.

This

Code: [Select]
module MCP3021_Reader (
Clk, // System clock
Rst_N, // General reset active low
SCL_in, // I2C serial cloc input. Is this necessary for single master?
SDA_in, // I2C serial data input
Conv, // Start acquisition

SCL_out, // Serial clock output
SDA_out, // Serial data output
SCL_oe, // Output enable for SCL
SDA_oe, // Output enable for SDA
ADC_rdy, // ADC_rdy output - strobes high when data is available
ADC_val // 10 bit ADC register. ADC data is available in this register when ADC_Rdy
);

input Clk;
input Rst_N;
input SCL_in;
input SDA_in;
input Conv;

output reg SCL_out;
output reg SDA_out;
output reg SCL_oe;
output reg SDA_oe;
output reg ADC_rdy;
output reg [9:0] ADC_val;

reg[23:0] I2C_Period = 24'd0; // I2C clock divider

reg[3:0] Sequence_counter;
reg  Sequence_busy;

parameter   CLK_IN_HZ =  16;    //25'd24000000; // System clock frequency
parameter   I2C_SCL_HZ = 4; // I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)
localparam  I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;
localparam Sequence_size = 10;

always @ (posedge Clk)
begin
if (!Rst_N)
begin
I2C_Period <= I2C_PERIOD_SIZE ;
ADC_rdy    <= 0;

SCL_out    <= 1;  // I2C default state is all HIGH
SCL_oe     <= 0;  // make I2C tristate by default.
SDA_out    <= 1;  // I2C default state is all HIGH
SDA_oe     <= 0;  // make I2C tristate by default.

ADC_rdy    <= 0;
Sequence_busy <= 0;
Sequence_counter <= 0;
end

if (!Conv && !Sequence_busy)
begin
Sequence_busy <= 1;
Sequence_counter <= Sequence_size;
end

else

if(Sequence_busy)
begin
if (I2C_Period == 0)
begin

SCL_out    <= 1;  // I2C default state is all HIGH
SCL_oe     <= 1;  // make I2C tristate by default.
SDA_out    <= 1;  // I2C default state is all HIGH
SDA_oe     <= 1;  // make I2C tristate by default.

ADC_rdy <= ~ADC_rdy;
I2C_Period <= I2C_PERIOD_SIZE;

if (ADC_rdy)
begin
Sequence_counter <= Sequence_counter -1;
end

if (Sequence_counter == 0)
begin
Sequence_busy <= 0;
ADC_rdy <= 0;
end
end

else
begin
I2C_Period <= I2C_Period - 1;
end
end

end // posedge clk

endmodule

(I hope you have a 'MCP3021', around 2 posts from now, it should already work...)

Nope, not yet. I want to simulate using an EEPROM 24LC08 with known content for now.

Datasheet here: http://ww1.microchip.com/downloads/en/devicedoc/21710k.pdf
« Last Edit: May 18, 2020, 10:55:46 pm by Miti »
Fear does not stop death, it stops life.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca

(I hope you have a 'MCP3021', around 2 posts from now, it should already work...)

Nope, not yet. I want to simulate using an EEPROM 24LC08 with known content for now.

Datasheet here: http://ww1.microchip.com/downloads/en/devicedoc/21710k.pdf

Well, we will still start with it since it is far simpler than the eeprom which needs the ee slaves master address, then subaddress, then with a read, another I2C_start & that master address again, in read mode, then read the data...

For the 'MCP3021', you present it's address in read mode, then receive 2 bytes, every time.
Don't worry, we will make progress very fast.

Ok, for your code, the ' (!Conv && !Sequence_busy) '.  I know how with TTL logic, and a lot of input button pullups, you can easily make the mistake of wanting negative logic on the input of your modules.

Remember, this module will eventually be buried in logic and controlled by other logic.  By default, for example, if you use our 'ADC_rdy' output to feed and Altera fifo or ram module's 'wren' input to accumulate a read cache, if we made it negative logic, you would need to invert that signal's 'WIRE' between modules.

Personally, even for reset I prefer positive logic.  Here is how I deal with negative logic inputs:



Now, if you prefer to keep your module's 'negative' logic inputs, let me know know.  We will change the Rst input in the module back to Rst_n, and change the Conv input to a Conv_n so when you wire this module into a larger design, when feeding it's input ports, you know that these controls are negative.

After simulation your design, here is what I get:

994422-1

Step #1 patches: (please update your code so we match)
Code: [Select]
module MCP3021_Reader (
Clk, // System clock
Rst, // General reset active low
SCL_in, // I2C serial cloc input. Is this necessary for single master?
SDA_in, // I2C serial data input
Conv, // Start acquisition

SCL_out, // Serial clock output
SDA_out, // Serial data output
SCL_oe, // Output enable for SCL
SDA_oe, // Output enable for SDA
ADC_rdy, // ADC_rdy output - strobes high when data is available
ADC_val // 10 bit ADC register. ADC data is available in this register when ADC_Rdy
);

input Clk;
input Rst;
input SCL_in;
input SDA_in;
input Conv;

output reg SCL_out;
output reg SDA_out;
output reg SCL_oe;
output reg SDA_oe;
output reg ADC_rdy;
output reg [9:0] ADC_val;

reg[15:0] I2C_Period = 16'd0; // I2C clock divider

reg[3:0] Sequence_counter;
reg  Sequence_busy;

parameter   CLK_IN_HZ =  16;    //25'd24000000; // System clock frequency
parameter   I2C_SCL_HZ = 4; // I2C SCL frequency (= 120 for 100KHz or 30 for 400KHz)
localparam  I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ)-1 ;
localparam Sequence_size = 10;

always @ (posedge Clk)
begin
if (Rst)
begin
//I2C_Period <= I2C_PERIOD_SIZE[15:0] ; // This would add a delay after Conv has been asserted before the I2C transaction begins
I2C_Period <= 1'd0 ;                       // This would begin the I2C transaction immediately
ADC_rdy    <= 1'd0 ;

SCL_out    <= 1'd1;  // I2C default state is all HIGH
SCL_oe     <= 1'd0;  // make I2C tristate by default.
SDA_out    <= 1'd1;  // I2C default state is all HIGH
SDA_oe     <= 1'd0;  // make I2C tristate by default.

ADC_rdy          <= 1'd0;
Sequence_busy    <= 1'd0;
Sequence_counter <= 1'd0;
end

if (Conv && !Sequence_busy)
begin
Sequence_busy    <= 1'd1;
Sequence_counter <= Sequence_size[3:0];
end

else

if(Sequence_busy)
begin
if (I2C_Period == 16'd0)
begin

SCL_out    <= 1'd1;  // I2C default state is all HIGH
SCL_oe     <= 1'd1;  // make I2C tristate by default.
SDA_out    <= 1'd1;  // I2C default state is all HIGH
SDA_oe     <= 1'd1;  // make I2C tristate by default.

ADC_rdy    <= ~ADC_rdy;
I2C_Period <= I2C_PERIOD_SIZE[15:0];

if (ADC_rdy)
begin
Sequence_counter <= Sequence_counter - 1'd1;
end

if (Sequence_counter == 4'd0)
begin
Sequence_busy <= 1'd0;
ADC_rdy       <= 1'd0;
end
end

else
begin
I2C_Period <= I2C_Period - 1'd1;
end
end

end // posedge clk

endmodule

#1, I made the module's control inputs positive logic.
#2, I shrank the I2C period counter to 16 bits.
#3, To get rid of compiler warnings, I added these -> I2C_Period <= I2C_PERIOD_SIZE[15:0];
                                           and everywhere here -> ADC_rdy       <= 1'd0;
                                           as well as                   -> if (Sequence_counter == 4'd0)

All throughout the code.  It is good to avoid pesky compiler warnings all over the place...

Now, you made the inner and outer portions of your loop's behavior backwards.  This is fine since I didn't specify how we would be using the sequencer, though the hint was that I said a sequence count of '20' would cycle the ADC_rdy only 10 times.  In I2C, we want to specify actions at the I2C SCL_out's rising and falling edge, so we need the specify a sequence counter which has events at each time.  In fact, we may want 4x the period events.  IE, perform an action at the rise and another action at the fall of the SCL_out output, as well as another action right in the middle on the SCL_out being low and in the middle of it being high as well.

Next post in around an hour with a solution...

Here is a hint:
For the 2x approach, SCL_out <=  Sequence_counter[0];
For the 4x approach, SCL_out <=  Sequence_counter[1];
Within the main looping sequencer of your code.

When defining the size of the sequences, the Sequence_counter = (sequence size *2 or *4) and the preiod counter will be divided by 2 or 4 so that the SCL_out will have the correct speed.

Let me check the I2C data sheets.  The x2 method should be fine, however, I need to double check how clock stretching works since I've only used it once so long ago.  We might just need to go with the x4 method unless you want the x4 just so you have an additional timing slice you may take advantage of if you ever need it.
« Last Edit: May 19, 2020, 12:40:58 am by BrianHG »
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Ok, next step, the dummy sequencer...

Add this as the new top of your code:
Code: [Select]
parameter   CLK_IN_HZ =  16;    // 25000000; // System clock frequency
parameter   I2C_SCL_HZ = 4; // 100000;          // I2C SCL frequency (100000 for 100KHz or 400000 for 400KHz)
localparam  I2C_PERIOD_SIZE = (CLK_IN_HZ/I2C_SCL_HZ/4)-1 ;
reg[15:0]   I2C_Period = 16'd0; // I2C clock divider

// *** We are calling these patterns I2C_start as 1 single long pattern for now.  During the next step, these patterns
// *** will shrink down to just the I2C_start and we will add Pat_I2C_*****_sda/scl/size for the I2C stop, I2C tx/rx byte.
// *** then during the I2C transaction, we will run a higher level sequencer which will choose and fill the correct values
// *** for the internal I2C pattern sequencer which will seam everything together.

// Sequence function                      STA s7  s6  s5  s4  s3  s2  s1  R/W ACK r7  r6  r5  r4  r3  r2  r1  r0  ACK r7  r6  r5  r4  r3  r2  r1  r0  NAK STO
// Sequence position                      111100000000009999999999888888888877777777776666666666555555555544444444443333333333222222222211111111110000000000
//         ***=SDA&SCL high               321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210
// SCL_out                             ***110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011
localparam   Pat_I2C_start_sda_out = 114'b000111100000000111111110000111111110000000000000000000000000000000000000000000000000000000000000000000000000000001;
localparam   Pat_I2C_start_sda_oe  = 114'b111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000111;
localparam   Pat_I2C_start_scl_oe  = 114'b111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111;
localparam   Pat_I2C_start_size    = 29*4-3; // 29 I2C clocks for MCP3021 running in a single sequence stream
localparam   Pat_I2C_data_rdy1     = 10*4+2; // 11 Clocks from the end
localparam   Pat_I2C_data_rdy2     = 1*4+2; // This one is temporary for MCP3021 running in a single sequence stream
localparam   Pat_I2C_ack1          = 76 ; // This one is temporary for MCP3021 running in a single sequence stream
localparam   Pat_I2C_ack2          = 40 ; // This one is temporary for MCP3021 running in a single sequence stream
localparam   Pat_I2C_phase_rxd     = 2 ;  // The is the sample phase position when capturing data bits from the sda_in
localparam   Pat_I2C_phase_txd     = 2 ;  // The is the shift data out phase position when sending data bits to the sda_out

reg [113:0] seq_sda_out, seq_sda_oe, seq_scl_oe;
reg [7:0]   Sequence_counter;
reg         Sequence_busy;
// localparam Sequence_size = 10;  replace with "Pat_I2C_start_size"

Next, build you sequencer and simulate.  Hint, this should be your converter 'IF':

Code: [Select]
if (Conv && !Sequence_busy)
begin
Sequence_busy    <= 1'd1;
Sequence_counter <= Pat_I2C_start_size[7:0];
seq_sda_out      <= Pat_I2C_start_sda_out[113:0];
seq_sda_oe       <= Pat_I2C_start_sda_oe[113:0];
seq_scl_oe       <= Pat_I2C_start_scl_oe[113:0];
end

Get rid of the cycling of the ADC_rdy.
Lets see your new code and new simulation.
« Last Edit: May 19, 2020, 08:34:36 am by BrianHG »
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Note there was a mistake in my sequence above.  I just edited the post and corrected it.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
@Miti > I will most likely no longer be available to help this June.
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Thanks Brian,

I’m swamped at work so I don’t know when I can finalize this. On top of that, some good weather seems to be in the forecast and I missed it so much that I can’t wait to take my bike out a bit in the weekend(s).
Fear does not stop death, it stops life.
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca

Code: [Select]
// Sequence position                      111100000000009999999999888888888877777777776666666666555555555544444444443333333333222222222211111111110000000000
//         ***=SDA&SCL high               321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210


I'll admit I don't understand this part. Can someone explain what this part mean?
Fear does not stop death, it stops life.
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
What you are looking at is the 'Position number' in a 1 bit serial stream.  Reading vertically, from right side to left, I have:
00,01,02,03,04,05,06,07,08,09,10,11,12,...  It's actually counting from 113 at the left down to 0 on the right.

I just wrote that counter position there for your convenience.  Also, once we pass 99, I just didn't add the 100 vertically, I basically truncated the '1' in the '1xx'

The text above these 2 lines of numbers just denotes my homemade I2C labels of the transaction.

Example :
STA = start bit pattern
s7..s0 = slave address transmition bits 6..0, then the R/W bit.


Maybe it would have read better:
Code: [Select]
// Sequence function                      STA s7  s6  s5  s4  s3  s2  s1  R/W ACK r7  r6  r5  r4  r3  r2  r1  r0  ACK r7  r6  r5  r4  r3  r2  r1  r0  NAK STO
// Sequence position                      11111111111111
// Sequence position                      11110000000000999999999988888888887777777777666666666655555555554444444444333333333322222222221111111111
//         ***=SDA&SCL high               321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210

// SCL_out                             ***110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011

As for the SCL_out, I am just illustrating what level the SCL output pin should be at during that counter position, 0 -through- 113, all 114 internal clock cycles.  It is shown this way to illustrate that there are 4 internal sequence positions for every single low to high cycle of the output SCL pin.

In other words, during sequence position 113 and 112, the SCL pin should be high, during position 111 and 110, the SCL pin should be low, during position 109 and 108, the SCL pin should be once again high.

In other words, the sequence position is just a 7 bit counter counting down from 113 to 0, or up, depending on how you code.
« Last Edit: June 10, 2020, 02:59:14 am by BrianHG »
 
The following users thanked this post: Miti

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
Got it now, thanks!

The key words were “reading vertically “.
Fear does not stop death, it stops life.
 

Offline MitiTopic starter

  • Super Contributor
  • ***
  • Posts: 1324
  • Country: ca
I can't put this together to save my life. I blame it on being very tired, these few weeks were very hard at work. However, since I wanted a Cyclone 4 development board, I ordered this one from Ali:
https://www.aliexpress.com/item/32858413588.html

There's a link to some demo code there and I downloaded it. What I found there is demo code for reading a temperature sensor, read/write I2C and IR remote code.
All the comments are in Chinese but Google does wonders translating Chinese. Last weekend, while waiting for the board,  I looked through the code and in few hours, I was able to make all three modules that I mentioned before, working on my Terasic DE1. The code seems very clean to a noob like me.
I still don't have an I2C ADC, but reading current address from an EEPROM is very similar with reading from the DAC, except for the device address.
I've attached the code, someone may find it useful. There was a small mistake with the ACK in one of the module.
You can download the entire code from here:  fpga.redliquid.pl

Code: [Select]
module I2C_READ(
       clk,
rst_n,
scl,sda,data
              );
 
 input clk;//总线时钟 50MHz 
input rst_n;//异步复位,低电平有效 
 
output scl;//SCL 时钟 
inout  sda;// SDA 数据总线 
output [15:0] data;//温度数据 
 
reg [15:0]data_r; // Temperature (or other data) data register
reg scl; // SCL 总线寄存器 
reg sda_r; // SDA 总线寄存器 
reg sda_link; //SDA bus data direction sign: 
reg [8:0]scl_cnt; //SCL clock generation counter
reg [2:0]cnt; //Used to mark SCL clock counter
reg [25:0]timer_cnt; //定时器,每隔2s 读取一次温度数据 
reg [3:0]data_cnt; //数据串并转换寄存器 
reg [7:0]address_reg;//器件地址寄存器 
reg [8:0]state;//状态寄存器 
////////////////////////////////////////////////////////////////////////////////// 
// Process 1, 2, 3: Generate SCL bus clock
always@(posedge clk or negedge rst_n) 
    begin 
        if(!rst_n) 
            scl_cnt <= 9'd0; 
        else if(scl_cnt == 9'd199) 
            scl_cnt <= 9'd0; 
        else 
            scl_cnt <= scl_cnt + 1'b1; 
    end 
always@(posedge clk or negedge rst_n) 
    begin 
        if(!rst_n) 
            cnt <= 3'd5; 
        else   
            case(scl_cnt) 
                9'd49: cnt <= 3'd1;//高电平中间 
                9'd99: cnt <= 3'd2;//下降沿 
                9'd149:cnt <= 3'd3;//低电平中间 
                9'd199:cnt <= 3'd0;//上升沿 
               default: cnt <= 3'd5; 
            endcase 
    end 
`define SCL_HIG (cnt == 3'd1) 
`define SCL_NEG (cnt == 3'd2) 
`define SCL_LOW (cnt == 3'd3) 
`define SCL_POS (cnt == 3'd0) 
always@(posedge clk or negedge rst_n) 
    begin 
        if(!rst_n) 
            scl <= 1'b0; 
        else if(`SCL_POS) 
            scl <= 1'b1; 
        else if(`SCL_NEG) 
            scl <= 1'b0; 
    end 
////////////////////////////////////////////////////////////////////////////////// 
// Process 4: Timer, read temperature data every 1s
always@(posedge clk or negedge rst_n) 
    begin 
        if(!rst_n) 
            timer_cnt <= 26'd0; 
        else if(timer_cnt == 26'd6249999) 
            timer_cnt <= 26'd0; 
        else   
            timer_cnt <= timer_cnt + 1'b1; 
    end 
////////////////////////////////////////////////////////////////////////////////// 
//状态机定义 
parameter IDLE  = 9'b0_0000_0000, 
             START  = 9'b0_0000_0010, 
             ADDRESS    = 9'b0_0000_0100, 
             ACK1       = 9'b0_0000_1000, 
             READ1  = 9'b0_0001_0000, 
             ACK2       = 9'b0_0010_0000, 
             READ2  = 9'b0_0100_0000, 
             NACK       = 9'b0_1000_0000, 
             STOP       = 9'b1_0000_0000; 
//`define DEVICE_ADDRESS 8'b1001_0001//器件地址,读操作
`define DEVICE_ADDRESS 8'b1010_0001//器件地址,读操作   
////////////////////////////////////////////////////////////////////////////////// 
// Process 5: State machine description
always@(posedge clk or negedge rst_n) 
    begin 
        if(!rst_n) 
            begin 
                data_r  <= 16'd0; 
                sda_r       <= 1'b1; 
                sda_link    <= 1'b1; 
                state       <= IDLE; 
                address_reg <= 15'd0; 
                data_cnt    <= 4'd0; 
            end 
        else   
            case(state) 
                IDLE: 
                    begin 
                        sda_r   <= 1'b1; 
                        sda_link <= 1'b1; 
                        if(timer_cnt == 26'd6249999) 
                            state <= START; 
                        else 
                            state <= IDLE; 
                    end 
                START://产生起始信号 
                    begin 
                        if(`SCL_HIG) 
                            begin 
                                sda_r       <= 1'b0; 
                                sda_link    <= 1'b1; 
                                address_reg <= `DEVICE_ADDRESS; 
                                state           <= ADDRESS; 
                                data_cnt        <= 4'd0; 
                            end 
                        else 
                            state <= START; 
                    end 
                ADDRESS://主机对器件进行寻址 
                    begin 
                        if(`SCL_LOW) 
                            begin 
                                if(data_cnt == 4'd8)//寻址完成,SDA改变方向,器件准备输出应答讯号 
                                    begin 
                                        state   <= ACK1; 
                                        data_cnt <=  4'd0; 
                                        sda_r       <= 1'b1; 
                                        sda_link    <= 1'b0; 
                                    end 
                                else//寻址过程中,SDA对器件作为输入 
                                    begin 
                                        state   <= ADDRESS; 
                                        data_cnt <= data_cnt + 1'b1; 
                                        case(data_cnt)   
                                            4'd0: sda_r <= address_reg[7]; 
                                            4'd1: sda_r <= address_reg[6]; 
                                            4'd2: sda_r <= address_reg[5]; 
                                            4'd3: sda_r <= address_reg[4]; 
                                            4'd4: sda_r <= address_reg[3]; 
                                            4'd5: sda_r <= address_reg[2]; 
                                            4'd6: sda_r <= address_reg[1]; 
                                            4'd7: sda_r <= address_reg[0]; 
                                            default: ; 
                                        endcase 
                                    end 
                            end 
                        else 
                            state <= ADDRESS; 
                    end 
                ACK1://器件输出应答信号 
                    begin 
                        if(!sda && (`SCL_HIG)) 
                            state <= READ1; 
                        else if(`SCL_NEG) 
                            state <= READ1; 
                        else 
                            state <= ACK1; 
                    end 
                READ1://读器件数据,高字节 
                    begin 
                        if((`SCL_LOW) && (data_cnt == 4'd8))// After reading the high byte data, the SDA changes direction, and the host is ready to output the response signal 
                            begin 
                                state   <= ACK2; 
                                data_cnt <= 4'd0; 
                                sda_r       <= 1'b0; 
                                sda_link    <= 1'b1; 
                            end 
                        else if(`SCL_HIG)//读数据过程中,器件作为输出 
                            begin 
                                data_cnt <= data_cnt + 1'b1; 
                                case(data_cnt) 
                                    4'd0: data_r[15] <= sda; 
                                    4'd1: data_r[14] <= sda; 
                                    4'd2: data_r[13] <= sda; 
                                    4'd3: data_r[12] <= sda; 
                                    4'd4: data_r[11] <= sda; 
                                    4'd5: data_r[10] <= sda; 
                                    4'd6: data_r[9]  <= sda; 
                                    4'd7: data_r[8]  <= sda; 
                                    default: ; 
                                endcase 
                            end 
                        else 
                            state <= READ1; 
                    end 
                ACK2:// Host output response signal 
                    begin     
                        if(`SCL_LOW) 
                            sda_r <= 1'b0;
                        else if(`SCL_NEG) 
                            begin 
                                sda_r   <= 1'b1; 
                                sda_link    <= 1'b0; 
                                state       <= READ2; 
                            end 
                        else 
                            state <= ACK2; 
                    end 
                READ2://读低字节数据 
                    begin 
                        if((`SCL_LOW) && (data_cnt == 4'd8)) 
                            begin 
                                state   <= NACK; 
                                data_cnt <= 4'd0; 
                                sda_r       <= 1'b1; 
                                sda_link    <= 1'b1; 
                            end 
                        else if(`SCL_HIG) 
                            begin 
                                data_cnt <= data_cnt + 1'b1; 
                                case(data_cnt) 
                                    4'd0: data_r[7] <= sda; 
                                    4'd1: data_r[6] <= sda; 
                                    4'd2: data_r[5] <= sda; 
                                    4'd3: data_r[4] <= sda; 
                                    4'd4: data_r[3] <= sda; 
                                    4'd5: data_r[2] <= sda; 
                                    4'd6: data_r[1]  <= sda; 
                                    4'd7: data_r[0]  <= sda; 
                                    default: ; 
                                endcase 
                            end 
                        else 
                            state <= READ2; 
                    end 
                NACK://主机非应答 
                    begin 
                        if(`SCL_LOW) 
                            begin 
                                state <= STOP; 
                                sda_r   <= 1'b0; 
                            end 
                        else 
                            state <= NACK; 
                    end 
                STOP: 
                    begin 
                        if(`SCL_HIG) 
                            begin 
                                state <= IDLE; 
                                sda_r <= 1'b1; 
                            end 
                        else 
                            state <= STOP; 
                    end 
                default: state <= IDLE; 
            endcase 
    end 

assign sda   = sda_link ? sda_r: 1'bz; 
assign data  = data_r; 
endmodule


Code: [Select]
module I2C_Comm(
clk,rst_n,
sw1,sw2,
scl,sda,
dis_data,
wr,rd
);

input clk; // 50MHz
input rst_n; //Reset signal, active low
input sw1,sw2; //Buttons 1, 2, (1 press to perform write operation, 2 press to perform read operation)
output scl; // 24C02 clock port
inout sda; // 24C02 data port
output[7:0] dis_data; //  Nixie tube display data
output wr, rd;

//Key detection
reg sw1_r,sw2_r; //Key value latch register, detect key value every 20ms
reg[20:0] cnt_20ms; //20ms count register

reg[23:0] Delay_cnt;
/*
always @ (posedge clk or negedge rst_n)
if(!rst_n) cnt_20ms <= 20'd0;
else cnt_20ms <= cnt_20ms+1'b1; //Keep counting

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
sw1_r <= 1'b1; //The key value register is reset, the key value is 1 when no keyboard is pressed
sw2_r <= 1'b1;
end
else if(cnt_20ms == 21'h1fffff) begin
sw1_r <= sw1; //Button 1 value latch
sw2_r <= sw2; //Button 2 value latch
end
*/
always @ (posedge clk or negedge rst_n)
if(!rst_n) Delay_cnt <= 24'd0;
else if(Delay_cnt || !sw1 || !sw2) Delay_cnt <= Delay_cnt+1'b1; //Keep counting

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
sw1_r <= 1'b1; //The key value register is reset, the key value is 1 when no keyboard is pressed
sw2_r <= 1'b1;
end
else if(Delay_cnt == 24'd1) begin
sw1_r <= sw1; //Button 1 value latch
sw2_r <= sw2; //Button 2 value latch
end
else if(Delay_cnt == 24'd20000) begin
sw1_r <= 1'b1; //Button 1 value latch
sw2_r <= 1'b1; //Button 2 value latch
end
assign wr = sw1_r;
assign rd = sw2_r;
//---------------------------------------------
//Crossover
reg[2:0] cnt; // cnt=0: scl rising edge, cnt=1: middle of scl high level, cnt=2: falling edge of scl, cnt=3: middle of scl low level
reg[8:0] cnt_delay; //500 cycle counts to generate the clock required by iic
reg scl_r; //Clock pulse register

always @ (posedge clk or negedge rst_n)
if(!rst_n) cnt_delay <= 9'd0;
else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //Counting to 10us is the period of scl, which is 100KHz
else cnt_delay <= cnt_delay+1'b1; //Clock count

always @ (posedge clk or negedge rst_n) begin
if(!rst_n) cnt <= 3'd5;
else begin
case (cnt_delay)
9'd124: cnt <= 3'd1; //cnt=1: middle of scl high level, used for data sampling
9'd249: cnt <= 3'd2; //cnt=2:scl Falling edge
9'd374: cnt <= 3'd3; //cnt=3:scl Low level middle, used for data change
9'd499: cnt <= 3'd0; //cnt=0:scl Rising edge

default: cnt <= 3'd5;
endcase
end
end


`define SCL_POS (cnt==3'd0) //cnt=0:scl rising edge
`define SCL_HIG (cnt==3'd1) //cnt=1:scl high level middle, used for data sampling
`define SCL_NEG (cnt==3'd2) //cnt=2:scl falling edge
`define SCL_LOW (cnt==3'd3) //cnt=3:scl low level middle, used for data change

always @ (posedge clk or negedge rst_n)
if(!rst_n) scl_r <= 1'b0;
else if(cnt==3'd0) scl_r <= 1'b1; //scl signal rising edge
   else if(cnt==3'd2) scl_r <= 1'b0; //scl signal falling edge

assign scl = scl_r; //Generate the clock needed for iic
//---------------------------------------------
//Need to write the address and data of 24C02

`define DEVICE_READ 8'b1010_0111 //Address of the addressed device (read operation)
`define DEVICE_WRITE 8'b1010_0110 //Address of the addressed device (write operation)
//`define WRITE_DATA 8'b1010_0101 //Data written to EEPROM
`define WRITE_DATA 8'hA5 //Data written to EEPROM
`define BYTE_ADDR 8'b0000_1011 //Write/read address register of EEPROM
reg[7:0] db_r; //Data registers transferred on IIC
reg[7:0] read_data; //Read the data register of EEPROM

//---------------------------------------------
//Read and write timing
parameter IDLE = 4'd0;
parameter START1 = 4'd1;
parameter ADD1 = 4'd2;
parameter ACK1 = 4'd3;
parameter ADD2 = 4'd4;
parameter ACK2 = 4'd5;
parameter START2 = 4'd6;
parameter ADD3 = 4'd7;
parameter ACK3 = 4'd8;
parameter DATA = 4'd9;
parameter ACK4 = 4'd10;
parameter STOP1 = 4'd11;
parameter STOP2 = 4'd12;

reg[3:0] cstate; //Status register
reg sda_r; //Output data register
reg sda_link; //Output data sda signal inout direction control bit
reg[3:0] num; //


always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cstate <= IDLE;
sda_r <= 1'b1;
sda_link <= 1'b0;
num <= 4'd0;
read_data <= 8'b0000_0000;
end
else  
case (cstate)
IDLE: begin
sda_link <= 1'b1; //Data line sda ​​is output
sda_r <= 1'b1;
if(!sw1_r || !sw2_r) begin //One of the SW1 and SW2 keys was pressed
db_r <= `DEVICE_WRITE; //Send device address (write operation)
cstate <= START1;
end
else cstate <= IDLE; //No key is pressed
end
START1: begin
if(`SCL_HIG) begin //scl is high
sda_link <= 1'b1; //Data line sda ​​is output
sda_r <= 1'b0; //Pull down the data line sda ​​to generate the start bit signal
cstate <= ADD1;
num <= 4'd0; //num count is cleared
end
else cstate <= START1; //Wait for the middle position of scl high level to arrive
end
ADD1: begin
if(`SCL_LOW) begin
if(num == 4'd8) begin
num <= 4'd0; //num count is cleared
sda_r <= 1'b1;
sda_link <= 1'b0; //sda set to high impedance state (input)
cstate <= ACK1;
end
else begin
cstate <= ADD1;
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
// sda_r <= db_r[4'd7-num]; //Send device address, starting from high bit
end
end
// else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //The device address is shifted left by 1 bit
else cstate <= ADD1;
end
ACK1: begin
if(/*!sda*/`SCL_NEG) begin //Note: 24C01/02/04/08/16 device can ignore the acknowledge bit
cstate <= ADD2; //Slave response signal
db_r <= `BYTE_ADDR; // 1 address
end
else cstate <= ACK1; //Wait for slave response
end
ADD2: begin
if(`SCL_LOW) begin
if(num==4'd8) begin
num <= 4'd0; //num count is cleared
sda_r <= 1'b1;
sda_link <= 1'b0; //sda set to high impedance state (input)
cstate <= ACK2;
end
else begin
sda_link <= 1'b1; //sda as output
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
// sda_r <= db_r[4'd7-num]; //Send EEPROM address (start with high bit)
cstate <= ADD2;
end
end
// else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //The device address is shifted left by 1 bit
else cstate <= ADD2;
end
ACK2: begin
if(/*!sda*/`SCL_NEG) begin //Slave response signal
if(!sw1_r) begin
cstate <= DATA; //Write operation
db_r <= `WRITE_DATA; //Written data
end
else if(!sw2_r) begin
db_r <= `DEVICE_READ;//Send device address (read operation), read the specific address need to perform the following steps
cstate <= START2; //Read operation
end
end
else cstate <= ACK2; //Wait for slave response
end
START2: begin //Read operation start bit
if(`SCL_LOW) begin
sda_link <= 1'b1; //sda as output
sda_r <= 1'b1; //Pull up the data line sda
cstate <= START2;
end
else if(`SCL_HIG) begin //scl is high level middle
sda_r <= 1'b0; //Pull down the data line sda ​​to generate the start bit signal
cstate <= ADD3;
end
else cstate <= START2;
end
ADD3: begin //Send operation address
if(`SCL_LOW) begin
if(num==4'd8) begin
num <= 4'd0; //num count is cleared
sda_r <= 1'b1;
sda_link <= 1'b0; //sda set to high impedance state (input)
cstate <= ACK3;
end
else begin
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
// sda_r <= db_r[4'd7-num]; //Send EEPROM address (start with high bit)
cstate <= ADD3;
end
end
// else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //The device address is shifted left by 1 bit
else cstate <= ADD3;
end
ACK3: begin
if(/*!sda*/`SCL_NEG) begin
cstate <= DATA; //Slave response signal

sda_link <= 1'b0;
end
else cstate <= ACK3; //Wait for slave response
end
DATA: begin
if(!sw2_r) begin //Read operation
if(num<=4'd7) begin
cstate <= DATA;
if(`SCL_HIG) begin
num <= num+1'b1;
case (num)
4'd0: read_data[7] <= sda;
4'd1: read_data[6] <= sda; 
4'd2: read_data[5] <= sda;
4'd3: read_data[4] <= sda;
4'd4: read_data[3] <= sda;
4'd5: read_data[2] <= sda;
4'd6: read_data[1] <= sda;
4'd7: read_data[0] <= sda;
default: ;
endcase
// read_data[4'd7-num] <= sda; //Read data (start with high bit)
end
// else if(`SCL_NEG) read_data <= {read_data[6:0],read_data[7]}; //Data cycle right
end
else if((`SCL_LOW) && (num==4'd8)) begin
num <= 4'd0; //num count is cleared
cstate <= ACK4;
end
else cstate <= DATA;
end
else if(!sw1_r) begin //Write operation
sda_link <= 1'b1;
if(num<=4'd7) begin
cstate <= DATA;
if(`SCL_LOW) begin
sda_link <= 1'b1; //Data line sda ​​as output
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
// sda_r <= db_r[4'd7-num]; //Write data (start with high bit)
end
// else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //Write data to the left by 1 bit
end
else if((`SCL_LOW) && (num==4'd8)) begin
num <= 4'd0;
sda_r <= 1'b1;
sda_link <= 1'b0; //sda set to high impedance
cstate <= ACK4;
end
else cstate <= DATA;
end
end
ACK4: begin
if(/*!sda*/`SCL_NEG) begin
// sda_r <= 1'b1;
cstate <= STOP1;
end
else cstate <= ACK4;
end
STOP1: begin
if(`SCL_LOW) begin
sda_link <= 1'b1;
sda_r <= 1'b0;
cstate <= STOP1;
end
else if(`SCL_HIG) begin
sda_r <= 1'b1; //When scl is high, sda generates a rising edge (end signal)
cstate <= STOP2;
end
else cstate <= STOP1;
end
STOP2: begin
if(`SCL_LOW) sda_r <= 1'b1;
//else if(cnt_20ms==20'hffff0) cstate <= IDLE;
else if(Delay_cnt==24'hFFFFFF) cstate <= IDLE;
else cstate <= STOP2;
end
default: cstate <= IDLE;
endcase
end

assign sda = sda_link ? sda_r:1'bz;
assign dis_data = read_data;

endmodule


Code: [Select]
//Display the pressed numeric keys on the digital tube

module IR(clk,rst_n,IR,led_cs,led_db);

  input   clk;
  input   rst_n;
  input   IR;
  output [3:0] led_cs;
  output [7:0] led_db;
 
  reg [3:0] led_cs;
  reg [7:0] led_db;
 
  reg [7:0] led1,led2,led3,led4;
  reg [15:0] irda_data;    // save irda data,than send to 7 segment led
  reg [31:0] get_data;     // use for saving 32 bytes irda data
  reg [5:0]  data_cnt;     // 32 bytes irda data counter
  reg [2:0]  cs,ns;
  reg error_flag;          // 32 bytes data during, the data error flag

  //----------------------------------------------------------------------------
  reg irda_reg0;       //To avoid metastability and avoid driving multiple registers, this one is not used.
  reg irda_reg1;       //This can be used, the status of irda in the following program
  reg irda_reg2;       //In order to determine the edge of irda, hit the register again. The following program represents the previous state of irda
  wire irda_neg_pulse; //Determine the falling edge of irda
  wire irda_pos_pulse; //Determine the rising edge of irda
  wire irda_chang;     //Indeed ╥rda's transition edge
 
  reg[15:0] cnt_scan;//Scanning frequency counter
   
  always @ (posedge clk) //Follow register
    if(!rst_n)
      begin
        irda_reg0 <= 1'b0;
        irda_reg1 <= 1'b0;
        irda_reg2 <= 1'b0;
      end
    else
      begin
        led_cs <= 4'b0000; //The bit selection of the nixie tube is on
        irda_reg0 <= IR;
        irda_reg1 <= irda_reg0;
        irda_reg2 <= irda_reg1;
      end
     
  assign irda_chang = irda_neg_pulse | irda_pos_pulse;  //IR received signal changes, rising or falling
  assign irda_neg_pulse = irda_reg2 & (~irda_reg1);  //IR receive signal irda falling edge
  assign irda_pos_pulse = (~irda_reg2) & irda_reg1;      //IR receive signal irda rising edge


  reg [10:0] counter;  //Frequency division 1750 times
  reg [8:0]  counter2; //Count the points after frequency division
  wire check_9ms;  // check leader 9ms time
  wire check_4ms;  // check leader 4.5ms time
  wire low;        // check  data="0" time
  wire high;       // check  data="1" time
 
  //----------------------------------------------------------------------------
  //1750 counts
  always @ (posedge clk)
    if (!rst_n)
      counter <= 11'd0;
    else if (irda_chang)  //irda level jumps, it starts counting again
      counter <= 11'd0;
    else if (counter == 11'd1750)
      counter <= 11'd0;
    else
      counter <= counter + 1'b1;
 
  //----------------------------------------------------------------------------
  always @ (posedge clk)
    if (!rst_n)
      counter2 <= 9'd0;
    else if (irda_chang)  //irda level jumps, counting starts again
      counter2 <= 9'd0;
    else if (counter == 11'd1750)
      counter2 <= counter2 +1'b1;
 

  assign check_9ms = ((217 < counter2) & (counter2 < 297));
  //257  In order to increase stability, take a certain range
  assign check_4ms = ((88 < counter2) & (counter2 < 168));  //128
  assign low  = ((6 < counter2) & (counter2 < 26));         // 16
  assign high = ((38 < counter2) & (counter2 < 58));        // 48

  //----------------------------------------------------------------------------
  // generate statemachine  state machine
    parameter IDLE       = 3'b000, //Initial state
              LEADER_9   = 3'b001, //9ms
              LEADER_4   = 3'b010, //4ms
              DATA_STATE = 3'b100; //transfer data
 
  always @ (posedge clk)
    if (!rst_n)
      cs <= IDLE;
    else
      cs <= ns; //Status bit
     
  always @ ( * )
    case (cs)
      IDLE:
        if (~irda_reg1)
          ns = LEADER_9;
        else
          ns = IDLE;
   
      LEADER_9:
        if (irda_pos_pulse)   //leader 9ms check
          begin
            if (check_9ms)
              ns = LEADER_4;
            else
              ns = IDLE;
          end
        else  //complete if---else--- ;Prevent generation latch
          ns =LEADER_9;
   
      LEADER_4:
        if (irda_neg_pulse)  // leader 4.5ms check
          begin
            if (check_4ms)
              ns = DATA_STATE;
            else
              ns = IDLE;
          end
        else
          ns = LEADER_4;
   
      DATA_STATE:
        if ((data_cnt == 6'd32) & irda_reg2 & irda_reg1)
          ns = IDLE;
        else if (error_flag)
          ns = IDLE;
        else
          ns = DATA_STATE;
      default:
        ns = IDLE;
    endcase

  //The output in the state machine is described by a sequential circuit
  always @ (posedge clk)
    if (!rst_n)
      begin
        data_cnt <= 6'd0;
        get_data <= 32'd0;
        error_flag <= 1'b0;
      end
 
    else if (cs == IDLE)
      begin
        data_cnt <= 6'd0;
        get_data <= 32'd0;
        error_flag <= 1'b0;
      end
 
    else if (cs == DATA_STATE)
      begin
        if (irda_pos_pulse)  // low 0.56ms check
          begin
            if (!low)  //error
              error_flag <= 1'b1;
          end
        else if (irda_neg_pulse)  //check 0.56ms/1.68ms data 0/1
          begin
            if (low)
              get_data[0] <= 1'b0;
            else if (high)
              get_data[0] <= 1'b1;
            else
              error_flag <= 1'b1;
             
            get_data[31:1] <= get_data[30:0];
            data_cnt <= data_cnt + 1'b1;
          end
      end

  always @ (posedge clk)
    if (!rst_n)
begin
      irda_data <= 16'd0;
led2 <= 8'd0;
end
    else if ((data_cnt ==6'd32) & irda_reg1)
  begin
   led1 <= get_data[7:0];  //Data inverse
   led2 <= get_data[15:8]; //Data code
   led3 <= get_data[23:16];//User code
   led4 <= get_data[31:24];
  end
 
//Display the keys pressed by the remote control on the digital tube
always@(led2)
begin
case(led2)

                     //Display 0 to 9 on the digital tube
        8'b01101000: //Code value of remote control board 0
led_db=8'b0000_0000;  //Show 0

8'b00110000: //Code value of remote control board 1
led_db=8'b0000_0001;  //Show 1

8'b00011000: //遥控板2的码值
led_db=8'b0000_00010;  //Show 2

8'b01111010: //遥控板3的码值
led_db=8'b0000_0011;  //Show 3

8'b00010000: //遥控板4的码值
led_db=8'b0000_0100;  //Show 4

8'b00111000: //遥控板5的码值
led_db=8'b0000_0101;  //Show 5

8'b01011010: //遥控板6的码值
led_db=8'b0000_0110;  //Show 6

8'b01000010: //遥控板7的码值
led_db=8'b0000_0111;  //Show 7

8'b01001010: //遥控板8的码值
led_db=8'b0000_1000;  //Show 8

8'b01010010: //遥控板9的码值
led_db=8'b0000_1001;  //Show 9

8'b10011000: //Code value of remote control board 100+
led_db=8'b0001_0000;  //Show 10

8'b10110000: //Code value of remote control board 200+
led_db=8'b0010_0000;  //Show 20

8'b11100000: //Code value of remote control board Vol-
led_db=8'hDD;  //Show 10

8'b10101000: //Code value of remote control board Vol+
led_db=8'h11;  //Show 10

8'b10010000: //Code value of remote control board EQ
led_db=8'hE0;  //Show E0

  //When the key is not pressed, F is displayed
   default: led_db=8'b1110_1110;

endcase
end

endmodule
Fear does not stop death, it stops life.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf