Close

Weekend Update

A project log for MagiLog: Open Automotive Datalogging

Developing a full featured datalogger on the cheap

nigelNigel 05/20/2018 at 04:110 Comments

I was able to finish the analog module HDL as well as the digital simulator PCB. I’ll expand more on the latter tomorrow, but I’ll go over the ins and outs of the code used in the analog module in this update. The point of this module is to interface my chosen ADC on one end, and produce data and ram write requests on the other. This module in the end will be a few levels down from the top as we need to combine the data from disparate modules into a single address space that we can access from the XMega.

The analog module is comprised of 4 main parts, which I will cover individually: The top-level module, the process controller, the data shift register, and the accumulator. With these 4 components I can interface with my ADC which produces 12bit measurements and combine multiple measurements into a new 14bit measurement for each channel. These measurements are then ready to be saved in some variety of RAM to be accessed by the XMega.

The top-level module is quite simple, it contains the modules needed for each ADC and maps the appropriate I/O for the data to be recorded. One thing to note is we get around the need for a separate bit shifting step by forcing the two MSB to 0 and then discarding the two LSB to get the 14 bits of data we care about.

module ADC_Master(
    input wire clk, // ADC clock in, 8MHz nominal
    input wire trigger, //Update cycle trigger signal
    input wire MISO, // Master In, Slave Out
    output wire MOSI, // Master Out, Slave In
    output wire sclk, //Clock output to ADC
    output wire CS, //ADC Chip select line
    output wire [2:0] adr, //Address to write data to
    output wire [15:0] data, // Data to be written
    output wire w_en // Write enable for data
    );
    
    wire shift_en, done; // Shift register status comms
    wire add, rst; //Accumulator control lines
    wire complete;
    wire [15:0] acc_in;
    wire [15:0] acc_out;
    wire [2:0] chan;
    
    assign data = {2'b00, acc_out[15:2]}; //Bitshift 16 bit sum to get 14 bit data
    assign w_en = complete;
    
    spi_shift shift ( //SPI Shift register module
        .sclk(sclk),
        .MOSI(MOSI),
        .MISO(MISO),
        .CS(CS),
        .clk(clk),
        .done(done),
        .shift_en(shift_en),
        .chan(chan),
        .data(acc_in)
    );
    
    spi_ctrl ctrl ( //SPI controller
        .shift_en(shift_en),
        .adr(chan),
        .done(done),
        .add(add),
        .rst(rst),
        .complete(complete),
        .write_adr(adr),
        .clk(clk),
        .trigger(trigger)
    );
    
    ADC_Accum Accum1 ( //Accumulator for oversampling
      .b(acc_in), // input [15 : 0] b
      .clk(add), // input clk
      .sclr(rst), // input sclr
      .q(acc_out) // output [15 : 0] q
    );
    
    
endmodule


The module that does the actual interfacing with the ADC is spi_shift. Overall, it’s pretty simple. It shifts in the data written by the ADC on the positive clock edges and creates the address to shift out on the right bits on the negative clock. This allows me to cycle through all 8 channels of each ADC depending on what is requested by the controller.

module spi_shift(
    input wire [2:0] chan, //Channel to shift out
    input wire shift_en, clk,
    input wire MISO,
    output reg [15:0] data,
    output wire sclk,
    output reg MOSI,
    output wire CS, 
    output reg done //Done notification line
    );
    
    reg [3:0] count;
    
    assign sclk = shift_en & clk; //Only output clock when we need to
    assign CS = ~shift_en; //Invert shift enable to create chip select for ADC
    
    always @(negedge clk) begin 
        if (count == 4'b0010) //Output for next ADC Chan on neg
            MOSI <= chan[2];
        else if (count == 4'b0011)
            MOSI <= chan[1];
        else if (count == 4'b0100) 
            MOSI <= chan[0];
        else 
            MOSI <= 0;
        if (count == 4'b1111) //If last bit received, signal done
            done <= 1'b1;
        else 
            done <= 1'b0;
    end
    
    always @(posedge clk) begin
        if (shift_en == 1'b1) begin // Shift in data on pos if enabled
            count <= count + 1'b1;
            data <= {data[14:0], MISO};
        end else begin //Otherwise wait
            count <= 4'b0;
            data <= data;
        end    
    end
    
endmodule


The accumulator is a prebuilt IP that comes free with ISE webpack. Using the graphical IP configurator I set the correct parameters for my use.

The bulk of the action happens in the spi_ctrl module. This is the module that controls the data flow, telling the other modules what to do at the right time. This module is responsible for toggling the data transfer, accumulator reset and the writing of a final value at the correct address. I did this through a state machine with 5 states, with an additional state available if needed. Here’s a code snippet of the “Run” state which is where most of the data movement happens to give you an idea of the code structure.

Run : begin //Main running state
                shift_en <= 1'b1; //Turn on shift register
                complete <= 1'b0;
                flag2 <= flag2;
                if (count == 4'b1111) begin
                    if (flag2 == 1'b1) begin //Use flags to run send only once
                        nstate <= Send;
                        flag2 <= 1'b0;
                    end else begin
                        nstate <= nstate;
                        flag2 <= flag2;
                    end
                    write_adr <= write_adr;
                    adr <= adr;
                end else if (count == 4'b0000) begin
                    write_adr <= curradd;
                    adr <= adr;
                    nstate <= nstate;
                    flag <= 1'b0;
                    flag2 <= 1'b1;
                end else if (count == 4'b1000) begin
                    if (flag == 0) begin //Use flags to increment only once
                        nadd <= curradd + 1'b1;
                        flag <= 1'b1;
                    end else begin
                        nadd <= nadd;
                        flag <= flag;  
                    end
                    adr <= adr;
                    write_adr <= write_adr; 
                end else if (count == 4'b1110) begin //Update address for shifter
                    adr <= curradd;
                    write_adr <= write_adr;
                    nstate <= nstate;  
                end else begin
                    write_adr <= write_adr;
                    adr <= adr;
                    nstate <= nstate;
                    flag <= flag;
                end
            end


All that’s left to be back on track is the digital module HDL. I’ve done most of the planning and it’s much simpler than the analog code. So (fingers crossed) it won’t take me too long to write. Until tomorrow, peace!

Discussions