Как стать автором
Обновить

Отладочная плата ПЛИС — Франкенштейн. Звуки и музыка

Время на прочтение 8 мин
Количество просмотров 8.5K

Сегодня у нас самая предновогодняя серия про ПЛИС и отладочную плату Френки. Предыдущие серии 1, 2.


Мы уже передавали тоновые сигналы по радио с помощью нашей платы Франкенштейн. Теперь попробуем воспроизводить звуки и музыку.


Для этого подключим к ПЛИС обычный динамик. К Френки подключен генератор на 25.175 МГц. Если поделить эту частоту до диапазона слышимых частот и подать на вывод ПЛИС, то мы можем услышать звук. Меня частоту мы можем получить разные звуки.


Тестировать качество звучания будет самый лучший слухач в доме — Маша. Диапазон частот в 60 КГц — это вам не шутки! )))



Схема подключения


Для воспроизведения звука, я использую динамик из конструктора "Знаток" (другого с ходу не нашел). Динамик подключаю к выводу ПЛИС через резистор с сопротивлением 470 Ом.



Простейший звук


В ПЛИС легко помещаются двоичные счетчики. Поэтому с такой задачей справится даже Френки. Например, 16 битный счетчик будет считать от 0 до 65535. Имея наши 25.175 МГц и 16 битный счетчик мы можем получить звук частотой 25175000 / 65536 = 384,14 Гц


module epm7064_test(clk, audio);
input wire clk;
output wire audio;

// двоичный счетчик на 16 бит
reg [15:0] counter;
always @(posedge clk) counter <= counter+1;

// выведем старший бит счетчика на динамик
assign audio = counter[15];

endmodule


Нота «ЛЯ» первой октавы (440 Гц)


Мы получили случайную частоту в звуковом диапазоне. Теперь можно попробовать получить частоту, равную 440 Гц. Это частота ноты "ЛЯ" первой октавы.


Для этого, вместо деления тактовой частоты на 65536, мы должны поделить значение частоты тактового генератора на 57216 (27175000 Гц / 440 Гц).


module epm7064_test(clk, audio);
input wire clk;
output wire audio;

// двоичный счетчик на 16 бит
reg [15:0] counter; initial counter <= 16'd0;

always @(posedge clk) begin
    if(counter==57216) begin
        counter <= 0; 
    end else begin
        counter <= counter + 1'b1;
    end
end

assign audio = counter[15];

endmodule


Но в этой реализации есть проблема. Хотя частота и равна 440 Гц, но скважность сигнала не равна 50%. Низкий уровень выводится при значениях счетчика от 0 до 32767, а высокий от 32767 до 56817. Получается, что динамик находится в единице только 42% времени. Самый простой способ получить скважность 50% — это добавить еще одно деление на два. То есть, сначала на 28608, а затем еще на 2.


module epm7064_test(clk, audio);
input wire clk;
output audio;

// двоичный счетчик на 15 бит
reg [14:0] counter; initial counter <= 15'd0;

always @(posedge clk) begin
    if(counter==28608) begin
        counter <= 0; 
    end else begin
        counter <= counter + 1'b1;
    end
end

reg audio;
always @(posedge clk) if(counter==28608) audio <= ~audio;

endmodule

Конечно, на слух это почти не отличить.



Чтобы сделать более гибкий код, можно добавить параметр clkdivider. Изменение направление счета в счетчике на обратное — это всего лишь вопрос предпочтений. Разницы в используемых ресурсах ПЛИС никакой не будет.


module epm7064_test(clk, audio);
input wire clk;
output audio;

parameter clkdivider = 25175000/440/2;

reg [14:0] counter; initial counter <= 15'd0;

always @(posedge clk) begin
    if(counter==0) begin
        counter <= clkdivider - 1'b1; 
    end else begin
        counter <= counter - 1'b1;
    end
end

reg audio;
always @(posedge clk) if(counter==0) audio <= ~audio;

endmodule

Сирена скорой помощи


Теперь давайте попробуем чередовать два разных тона. Для этого используем
счетчик (назовем его tone) на 24 бита, для получения частоты порядка 1.5 Гц.
Этот сигнал будем использовать для переключения между двумя частотами.


module epm7064_test(clk, audio);
input wire clk;
output audio;

parameter clkdivider = 25175000/440/2;

reg [14:0] counter; initial counter <= 15'd0;

reg [23:0] tone; initial tone <= 24'd0;
always @(posedge clk) tone <= tone+1;

always @(posedge clk) begin
    if(counter==0) begin
        counter <= (tone[23] ? clkdivider-1 : clkdivider/2-1); 
    end else begin
        counter <= counter - 1'b1;
    end
end

reg audio;
always @(posedge clk) if(counter==0) audio <= ~audio;

endmodule


А теперь попробуем сделать полицейскую сирену. Для начала поменяем счетчик tone так, чтобы в нем было только 23 бита, чтобы увеличить частоту вдвое (старший бит будет меняться с частотой примерно 3 Гц).


А теперь нужно придумать трюк, чтобы получить "пилу". Возьмем биты с 15 по 21 из счетчика тона. Это будет выглядеть так: tone[21:15]. Эти 7 бит дадут нам значение от 0 до 127, которые будут нарастать с определенной скоростью. Как только значение достигнет 127, оно сбросится в 0 и начнет нарастать снова. Для того, чтобы получить уменьшающееся значение, мы можем инвертировать эти биты. Это будет выглядеть так: (~tone[21:15]). Этим мы получим значения, которые идут вниз от 127 до 0, а потом все повторяется.


Для того, чтобы получить пилу, которая плавно нарастает, а потом плавно спадает,
возьем бит tone[22].


wire [6:0] ramp = (tone[22] ? tone[21:15] : ~tone[21:15]);

// это все равно, что
// "if tone[22]=1 then ramp=tone[21:15] else ramp=~tone[21:15]"

Но такая пила еще не готова к применению в нашем проекте.


Значение пилы меняется от 7'b0000000 до 7'b1111111. Для того, чтобы получить значения из которых можно было бы уже получить звук, мы добавим к слову значения пилы еще 2 бита '01' слева и шесть бит '000000' справа.


wire [14:0] clkdivider = {2'b01, ramp, 6'b000000};

Таким образом, мы получим постоянно меняющееся значение clkdivider в диапазоне от 15'b010000000000000 до 15'b011111111000000, или в шестнадцатиричной системе от 15'h2000 до 15'h3FC0, или в десятичной 8192 to 16320. При частоте генератора 25.175 МГц мы получим звук сирены от 771 Гц до 1537 Гц.


module epm7064_test(clk, audio);
input wire clk;
output audio;

reg [14:0] counter; initial counter <= 15'd0;

reg [23:0] tone; initial tone <= 24'd0;
always @(posedge clk) tone <= tone+1;

wire [6:0] ramp = (tone[22] ? tone[21:15] : ~tone[21:15]);

wire [14:0] clkdivider = {2'b01, ramp, 6'b000000};

always @(posedge clk) begin
    if(counter==0) begin
        counter <= clkdivider;
    end else begin
        counter <= counter - 1'b1;
    end
end

reg audio;
always @(posedge clk) if(counter==0) audio <= ~audio;

endmodule


И мы получили быструю сирену. Но настоящая сирена меняет свою скорость с быстрой на медленную. Мы можем взять биты tone[21:15] для быстрой пилы, и биты tone[24:18] для пилы с меньшей частотой.


wire [6:0] fastsweep = (tone[22] ? tone[21:15] : ~tone[21:15]);
wire [6:0] slowsweep = (tone[25] ? tone[24:18] : ~tone[24:18]);
wire [14:0] clkdivider = {2'b01, (tone[27] ? slowsweep : fastsweep), 6'b000000};

Для этого потребуется увеличить разрядность счетчика tone.


module epm7064_test(clk, audio);
input wire clk;
output audio;

reg [14:0] counter; initial counter <= 15'd0;

reg [27:0] tone; initial tone <= 28'd0;
always @(posedge clk) tone <= tone+1;

wire [6:0] ramp = (tone[22] ? tone[21:15] : ~tone[21:15]);

wire [6:0] fastsweep = (tone[22] ? tone[21:15] : ~tone[21:15]);
wire [6:0] slowsweep = (tone[25] ? tone[24:18] : ~tone[24:18]);
wire [14:0] clkdivider = {2'b01, (tone[27] ? slowsweep : fastsweep), 6'b000000};

always @(posedge clk) begin
    if(counter==0) begin
        counter <= clkdivider;
    end else begin
        counter <= counter - 1'b1;
    end
end

reg audio;
always @(posedge clk) if(counter==0) audio <= ~audio;

endmodule


Играем мелодию


А теперь, в честь наступающего нового года, попробуем сыграть песенку про ёлочку. Поищем ноты и видео, чтобы понять, как играется эта песенка.


Для того, чтобы играть последовательность нот, нам нужно создать секвенсор, который будет выдавать номер нужной ноты, в нужный момент времени. Это значение ноты мы преобразуем в константу для делителя частоты, чтобы получить соответствующую ноту.


Судя по видео, нам потребуется всего одна октава + 1. В принципе, все ноты нам не нужы.
А нужны только 7 нот, которые используются в песне. Поэтому, для хранения номера ноты достаточно 3 бит. Правильнее, конечно, будет сказать не номер ноты, а номер тона или частоты в мелодии.


Итак, у нас в мелодии есть ноты: до, ре, ми, фа, соль, ля и до следующей октавы. Мы их соответственно и пронумеруем. Последовательность получается такая:


соль           5
ми ми         3 3

соль           5
ми ми         3 3

соль фа ми ре до    5 4 3 2 1

ля до(+1) ля
соль ми ми
соль фа ми ре до

Нарисую временную диаграмму.




С помощью секретного табличного процессора, рассчитываем частоты нот и значения делителей, немного их округлив.




Теперь, когда все расчеты окончены, опишем логику.


Делитель частоты по номеру ноты:


wire [15:0] clkdivider = 
                         (note_num == 0) ? 16'd0 :
                         (note_num == 1) ? 16'b1011101111110000 :
                         (note_num == 2) ? 16'b1010011101110000 :
                         (note_num == 3) ? 16'b1001010100110000 :
                         (note_num == 4) ? 16'b1000110011010000 :
                         (note_num == 5) ? 16'b0111110101110000 :
                         (note_num == 6) ? 16'b0110111111000000 :
                         (note_num == 7) ? 16'b0101111000000000 : 16'd0;

Судя по картинке, секвенсор у нас получается на 64 шага. Для этого увеличиваем разрядность счетчика tone.


reg [27:0] tone; initial tone <= 28'd0;
always @(posedge clk) tone <= tone + 1'b1;

wire [5:0] step_num = tone[26:(26-5)];  // 6 бит это от 0 до 63

Преобразование из номера номера шага секвенсора в номер ноты:


wire [2:0] note_num = 
                      (step_num == 0) ? 5 :    // 1
                      (step_num == 1) ? 5 :    // 2
                      (step_num == 2) ? 0 :    // 3
                      (step_num == 3) ? 0 :    // 4
                      (step_num == 4) ? 3 :    // 5
                      (step_num == 5) ? 0 :    // 6
                      (step_num == 6) ? 3 :    // 7
                      (step_num == 7) ? 0 :    // 8
                      (step_num == 8) ? 5 :    // 9
                      (step_num == 9) ? 5 :    // 10
                      (step_num ==10) ? 0 :    // 11
                      (step_num ==11) ? 0 :    // 12
                      (step_num ==12) ? 3 :    // 13
                      (step_num ==13) ? 0 :    // 14
                      (step_num ==14) ? 3 :    // 15
                      (step_num ==15) ? 0 :    // 16
... и т. д.

полностью
module epm7064_test(clk, audio);
input wire clk;
output audio;

reg [15:0] counter; initial counter <= 16'd0;

reg [27:0] tone; initial tone <= 28'd0;
always @(posedge clk) tone <= tone + 1'b1;

wire [5:0] step_num = tone[26:(26-5)];

wire [2:0] note_num = 
                      (step_num == 0) ? 5 :    // 1
                      (step_num == 1) ? 5 :    // 2
                      (step_num == 2) ? 0 :    // 3
                      (step_num == 3) ? 0 :    // 4
                      (step_num == 4) ? 3 :    // 5
                      (step_num == 5) ? 0 :    // 6
                      (step_num == 6) ? 3 :    // 7
                      (step_num == 7) ? 0 :    // 8
                      (step_num == 8) ? 5 :    // 9
                      (step_num == 9) ? 5 :    // 10
                      (step_num ==10) ? 0 :    // 11
                      (step_num ==11) ? 0 :    // 12
                      (step_num ==12) ? 3 :    // 13
                      (step_num ==13) ? 0 :    // 14
                      (step_num ==14) ? 3 :    // 15
                      (step_num ==15) ? 0 :    // 16

                      (step_num ==16) ? 5 :    // 17
                      (step_num ==17) ? 0 :    // 18
                      (step_num ==18) ? 4 :    // 19
                      (step_num ==19) ? 0 :    // 20
                      (step_num ==20) ? 3 :    // 21
                      (step_num ==21) ? 0 :    // 22
                      (step_num ==22) ? 2 :    // 23
                      (step_num ==23) ? 0 :    // 24
                      (step_num ==24) ? 1 :    // 25
                      (step_num ==25) ? 1 :    // 26
                      (step_num ==26) ? 1 :    // 27
                      (step_num ==27) ? 1 :    // 28
                      (step_num ==28) ? 0 :    // 29
                      (step_num ==29) ? 0 :    // 30
                      (step_num ==30) ? 0 :    // 31
                      (step_num ==31) ? 0 :    // 32

                      (step_num ==32) ? 6 :    // 33
                      (step_num ==33) ? 6 :    // 34
                      (step_num ==34) ? 0 :    // 35
                      (step_num ==35) ? 0 :    // 36
                      (step_num ==36) ? 7 :    // 37
                      (step_num ==37) ? 0 :    // 38
                      (step_num ==38) ? 6 :    // 39
                      (step_num ==39) ? 0 :    // 40
                      (step_num ==40) ? 5 :    // 41
                      (step_num ==41) ? 5 :    // 42
                      (step_num ==42) ? 0 :    // 43
                      (step_num ==43) ? 0 :    // 44
                      (step_num ==44) ? 3 :    // 45
                      (step_num ==45) ? 0 :    // 46
                      (step_num ==46) ? 3 :    // 47
                      (step_num ==47) ? 0 :    // 48

                      (step_num ==48) ? 5 :    // 49
                      (step_num ==49) ? 0 :    // 50
                      (step_num ==50) ? 4 :    // 51
                      (step_num ==51) ? 0 :    // 52
                      (step_num ==52) ? 3 :    // 53
                      (step_num ==53) ? 0 :    // 54
                      (step_num ==54) ? 2 :    // 55
                      (step_num ==55) ? 0 :    // 56
                      (step_num ==56) ? 1 :    // 57
                      (step_num ==57) ? 1 :    // 58
                      (step_num ==58) ? 1 :    // 59
                      (step_num ==59) ? 1 :    // 60
                      (step_num ==60) ? 0 :    // 61
                      (step_num ==61) ? 0 :    // 62
                      (step_num ==62) ? 0 :    // 63
                      (step_num ==63) ? 0 :    // 64

                       0;

wire [15:0] clkdivider = 
                         (note_num == 0) ? 16'd0 :
                         (note_num == 1) ? 16'b1011101111110000 :
                         (note_num == 2) ? 16'b1010011101110000 :
                         (note_num == 3) ? 16'b1001010100110000 :
                         (note_num == 4) ? 16'b1000110011010000 :
                         (note_num == 5) ? 16'b0111110101110000 :
                         (note_num == 6) ? 16'b0110111111000000 :
                         (note_num == 7) ? 16'b0101111000000000 : 16'd0;

always @(posedge clk) begin
    if(counter==0) begin
        counter <= clkdivider;
    end else begin
        counter <= counter - 1'b1;
    end
end

reg audio;
always @(posedge clk) if(counter==0) audio <= ~audio;

endmodule

Начнем контрольные прослушивания



Мне кажется, Маша одобряет ;) ^_^


Выводы


Как выяснилось, ПЛИС на 64 ячейки способен выступать в роли генератора звукового сигнала для сирены сигнализации. Проект с мелодией про маленькую ёлочку, занял 61 ячейку из 64-х. А это значит, что есть еще ресурсы, их можно использовать и создать, например, музыкальный дверной звонок (привет 90е!).


Исходные коды: https://github.com/UA3MQJ/epm7064_audio


При написании статьи, использовались идеи и исходные коды статьи с сайта fpga2funMusic Box.

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+15
Комментарии 19
Комментарии Комментарии 19

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн