Верилог

Использование ПЛИС для подсчета витков при намотке катушек

coil winder rig
Аппарат для намотки катушек

В былинные времена радиолюбители замыкали намоточным устройством клавишу на калькуляторе. В наши дни калькулятор найти сложно, зато под рукой есть много чего другого, поинтереснее. Например, бывает отладочная плата с FPGA Cyclone II.

Для подсчета витков понадобится датчик оборотов. Самый простой датчик — геркон. Чтобы он замыкался на каждый оборот вала, понадобится хороший магнитик. Очень хорошо работают маленькие магнитики, которые обеспечивают парковку головок в винчестерах. Еще нужна собственно электроотвертка, какой-нибудь кусок пластика и много изоленты. Еще неплохо иметь проводок, например тот, который до сих пор всегда прилагается к DVD-приводам для передачи аналогового звука в звуковую карту.

Герконы чрезвычайно хрупки. Поэтому хоть это и соблазнительно — просто засунуть концы в разъемчик, лучше все же зачистить контакты с одной стороны и припаять провода к ножкам. Затем надо примотать эту конструкцию к пластику, пластик примотать к неподвижной части отвертки так, чтобы геркон пришелся напротив вала. А на вал надо нацепить магнитик, или может быть два магнитика — это надо подобрать.

Результирующая схема
Осталось прицепить конструкцию к DE1 и описать схему счетчика. Проводок от DVD удобен тем, что его можно насадить в любое место разъемов GPIO. Я выбрал такое место, чтобы один провод был землей, а второй — GPIO_0[9]. Чтобы схема работала без дополнительных деталей, в Quartus-е надо включить слабую подтяжку для этой ножки. Для этого надо пойти в Assignment Editor, выбрать в нем категорию Logic Options и добавить для вывода «GPIO_0[9]» опцию «Weak Pull-Up Resistor: On». В .qsf-файле это отразится следующей строчкой:

set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to GPIO_0[9]

Теперь нормальное состояние счетного входа — «1», а с каждым оборотом он будет замыкаться на землю. Осталось это явление зафиксировать и подсчитать.

Считать импульсы на Verilog-е проще простого. Можно просто сделать событие на каждый положительный перепад входного сигнала и увеличивать счетчик на единицу. Но асинхронные схемы нестабильны, плохо масштабируются и вообще не рекомендуются в ПЛИС, поэтому лучше тактировать схему от общего тактового генератора, а входной сигнал сэмплировать. Для начала опишем такой счетчик:


// локальное переназначение внешних сигналов, для удобства
wire clk = clk50mhz;                                // тактовый сигнал
wire reset = ~KEY[0];                               // сброс, активный 1
wire reed = GPIO_0[9];                              // сигнал с геркона

reg [15:0] turnscount;                              // счетчик витков
reg        sensorsampled;                           // предыдущее значение входного сигнала

always @(posedge clk) begin
    if (reset)
        turnscount <= 0;
    else begin
        // записали в регистр текущее значение
        sensorsampled <= reed; 
        if (~sensorsampled && reed) begin
            // текущее значение 1, сохраненное 0 -- это фронт
            turnscount <= turnscount + 1'b1;
        end
    end
end

Эта схема сэмплирует входной сигнал и по его переднему фронту увеличивает счетчик на 1. Такой счетчик уже можно запустить и проверить. Он будет работать, но немного странно. Причина странности — в дребезге контактов, который случается даже в вакуумных переключателях. На частоте 50 МГц схема успевает зарегистрировать по нескольку десятков замыканий на один оборот вала, хотя, казалось бы. Чтобы обойти эту проблему, будем квантовать входной сигнал немного реже. Например, если регистрировать входной сигнал в 262144 раза реже, частота квантования будет 190 Гц.


reg [17:0] slooow;                                  // делительный регистр
wire       slow_enable = slooow == 0;               // медленный разрешающий сигнал

// этот процесс считает тактовые импульсы для
// получения медленного разрешающего сигнала slow_enable
always @(posedge clk) begin: _slow
    slooow <= slooow + 1'b1;
end
Теперь у нас есть разрешающий сигнал, который становится единицей ровно на один такт, один раз в каждые 262144 тика. Осталось завести его на входы Enable регистров sensorsampled и turnscount:
wire plus_one = ~sensorsampled && reed;             // признак переднего фронта

always @(posedge clk) begin
    if (reset) 
        turnscount <= 0;
    else 
        if (slow_enable) begin
            if (plus_one) turnscount <= turnscount + 1'b1;
        end
end

Вывести текущее значение счетчика можно с помощью стандартного примера из комплекта DE1:

SEG7_LUT_4 seg7display(HEX0, HEX1, HEX2, HEX3, turnscount);

Таким счетчиком витков уже вполне можно пользоваться и можно было бы остановиться на достигнутом. Но, нормальные люди обычно считают в десятичной системе, придется сделать десятичный счетчик. Сначала сделаем одну декаду с переносом:

// синхронный декадный счетчик со сбросом
module decade_counter(input clk,
              input ena,
              input reset,
              output reg[3:0] value,
              output c);

assign c = value == 9;

always @(posedge clk) 
    if (reset) 
        value <= 4'b0;
    else if (ena) 
        value <= value == 9 ? 0 : (value + 1'b1);
    
endmodule

А затем объединим четыре декады в один счетчик в главном модуле:

wire [15:0] bcd_turns;                              // шина счетчика оборотов 
                                                    // в двоично-десятичном формате
wire        cy0,cy1,cy2;                            // флаги поразрядного переноса

// инстанциируем один десятичный разряд счетчика, инкремент по plus_one & slow_enable, 
// выход подключен к разрядам 3:0 шины bcd_turns, перенос в сигнале cy0
decade_counter ctr0(.clk(clk), .ena(plus_one & slow_enable), .reset(reset),
                    .value(bcd_turns[3:0]), .c(cy0));
                    
// аналогично следующий десятичный разряд, с учетом переноса из предыдущего разряда
decade_counter ctr1(.clk(clk), .ena(plus_one & slow_enable & cy0), .reset(reset),
                    .value(bcd_turns[7:4]), .c(cy1));
// ...                    
decade_counter ctr2(.clk(clk), .ena(plus_one & slow_enable & cy0 & cy1), .reset(reset),
                    .value(bcd_turns[11:8]), .c(cy2));
// ...                    
decade_counter ctr3(.clk(clk), .ena(plus_one & slow_enable & cy0 & cy1 & cy2), .reset(reset),
                    .value(bcd_turns[15:12]));

К дисплейному дешифратору надо теперь подвести проводки шины bcd_turns:

SEG7_LUT_4 seg7display(HEX0, HEX1, HEX2, HEX3, bcd_turns);

…и никогда больше не нагружать голову пересчетом числа витков.

Скачать готовый проект можно тут.



-*-*-
Viacheslav Slavinsky2008 svofski on gmail
Updated: Wed Mar 18 17:41:23 MSK 2009