В проекте rpi-hat-io я уже подключал шаговый моторчик 28byj-48 к плате MA3128 и заставлял вал моторчика вращаться. Сейчас я хочу сделать проект посложнее. Во-первых, я хочу подключить сразу 4 моторчика и управлять вращением каждого из них независимо. Во-вторых, мне хотелось бы избавиться в программе управления от программных задержек. Программные задержки вещь ненадежная. На микрокомпьютере Raspberry работает обычная десктопная операционная система, которая в принципе не может обеспечить высокую точность программных задержек.
Мне хотелось бы в ПЛИС платы MA3128 реализовать какую-то более сложную логику, чтобы можно было бы подать команду "выполнить шаг на двигателе N". В ПЛИС платы всего 128 логических элементов, как же быть? Попробуем сделать..
Я предлагаю следующее аппаратно-программное решение. В ПЛИС платы MA3128 реализую простой последовательный приемник. Он будет принимать поток байтов с заданной скоростью, например стандартной 115200 бит в секунду. Каждый принятый байт будет командой, которая исполняется, например выполняет один шаг или полушаг на указанном шаговом двигателе. В коде команды может указываться номер двигателя, которому предназначена команда. Команда может снимать или наоборот разрешать напряжения на обмотках или команда может вообще не делать ничего. Вот здесь формат байта, который я придумал к реализации в моей ПЛИС EPM3128ATC100 платы MA3128:
Хочу особо обратить внимание на команды, которые не "делают ничего". К примеру, просто команда 0x00 не делает ничего, так как не содержит бита указывающего, что нужно сделать шаг или выключить обмотки или еще что-то. Такие команды так же важны в моем проекте, так как с их помощью я хочу задавать точно временные интервалы между шагами двигателя. В традиционных операционных системах типа Linux или Windows драйвер последовательного порта имеет внутренние передающие и принимающие буфферы. Я могу записать в последовательный порт хоть 1000 байт, но сразу же они не будут переданы. Они помещаются в передающий буфер, откуда по прерываниям от аппаратного контроллера последовательного порта байт за байтом они будут отправляться на передачу с запрограммированной скоростью. В современных аппаратных контроллерах есть даже внутреннее ФИФО, которое так же буферирует передачу. Это позволяет программному обеспечению передавать в линию Tx регулярно четко с заданной скоростью старт бит, восемь бит данных, стоп бит. И так каждый байт непрерывно и последовательно. Я собираюсь регулировать скорость вращения вала шагового двигателя как раз вставкой в поток пустых команд: команда "шаг", затем к примеру 100 пустых байт, опять команда "шаг", затем опять 100 пустых команд и так далее. Количество вставленных пустых байт будет регулировать скорость вращения вала шагового мотора. Этот метод будет давать четкую временную последовательность импульсов на выводах шаговых двигателей. К тому же, этот способ практически не нагружает процессор.
Еще один плюс этого метода: для управления из Raspberry четырьмя шаговыми двигателями, подключенными к плате MA3128 я буду использовать только линии последовательного порта разъема GPIO: PIN8 UART Tx и PIN10 UART Rx. Оставшиеся пины остаются свободными для других приложений. То есть можно подключать несколько разных плат стэком на распбери и возможно они не будут мешать друг другу.
Модуль верхнего уровня для ПЛИС написан на Verilog HDL:
module max(
input wire CLK,
input wire CLK2,
output wire [7:0]LED,
input wire [1:0]KEY,
output wire [3:0]MA,
output wire [3:0]MB,
output wire [3:0]MC,
output wire [3:0]MD,
output wire [9:0]IOA,
output wire [9:0]IOB,
//Raspberry GPIO pins
//inout wire [27:0]GPIO
input wire SERIAL_RX,
output wire SERIAL_TX
);
assign IOA = 10'd00;
assign IOB = 10'd00;
assign SERIAL_TX = 1'b1;
wire [7:0]rbyte;
wire [3:0]num_bits;
wire rbyte_ready;
serial serial_inst(
.clk(CLK),
.rx( SERIAL_RX ),
.rx_byte(rbyte),
.rbyte_ready(rbyte_ready),
.onum_bits(num_bits)
);
assign LED = {MB,MA};
motor #(.IDX(0)) motor_inst0(
.clk(CLK),
.control(rbyte),
.wr(rbyte_ready),
.f0( MA[0] ),
.f1( MA[1] ),
.f2( MA[2] ),
.f3( MA[3] )
);
motor #(.IDX(1)) motor_inst1(
.clk(CLK),
.control(rbyte),
.wr(rbyte_ready),
.f0( MB[0] ),
.f1( MB[1] ),
.f2( MB[2] ),
.f3( MB[3] )
);
motor #(.IDX(2)) motor_inst2(
.clk(CLK),
.control(rbyte),
.wr(rbyte_ready),
.f0( MC[0] ),
.f1( MC[1] ),
.f2( MC[2] ),
.f3( MC[3] )
);
motor #(.IDX(3)) motor_inst3(
.clk(CLK),
.control(rbyte),
.wr(rbyte_ready),
.f0( MD[0] ),
.f1( MD[1] ),
.f2( MD[2] ),
.f3( MD[3] )
);
endmodule
Здесь видно установленный экземпляр модуля приемника последовательного порта и 4 экземпляра модуля управления шаговыми двигателями. Принятый байт отправляется сразу всем модулям моторов, но каждый из них сам решает, нужно ли что-то делать по этой команде или нет. Да и сам модуль мотора не сложный:
module motor(
input wire clk,
input wire [7:0]control,
input wire wr,
output reg f0,
output reg f1,
output reg f2,
output reg f3
);
parameter IDX = 0;
wire ctrl_step; assign ctrl_step = control[0];
wire ctrl_dir; assign ctrl_dir = control[1];
wire ctrl_halfstep; assign ctrl_halfstep = control[2];
wire ctrl_sleep; assign ctrl_sleep = control[3];
wire ctrl_reset; assign ctrl_reset = control[4];
wire [1:0]ctrl_addr; assign ctrl_addr = control[6:5];
reg [2:0]cnt8 = 3'd0;
reg sleep=1'b0;
reg halfstep=1'b0;
always @( posedge clk )
begin
if( wr && ctrl_addr==IDX )
begin
sleep <= ctrl_sleep;
halfstep <= ctrl_halfstep;
if( ctrl_reset )
cnt8 <= 3'd0;
else
if( ctrl_step)
cnt8 <= ctrl_dir ? cnt8+1'b1 : cnt8-1'b1;
end
end
always @(posedge clk)
if( sleep )
begin
f3 <= 1'b0;
f2 <= 1'b0;
f1 <= 1'b0;
f0 <= 1'b0;
end
else
if(halfstep)
begin
f0 <= (cnt8==0 || cnt8==6 || cnt8==7 );
f1 <= (cnt8==0 || cnt8==1 || cnt8==2 );
f2 <= (cnt8==2 || cnt8==3 || cnt8==4 );
f3 <= (cnt8==4 || cnt8==5 || cnt8==6 );
end
else
begin
f0 <= (cnt8[1:0]==0 || cnt8[1:0]==3);
f1 <= (cnt8[1:0]==0 || cnt8[1:0]==1);
f2 <= (cnt8[1:0]==1 || cnt8[1:0]==2);
f3 <= (cnt8[1:0]==2 || cnt8[1:0]==3);
end
endmodule
Весь проект можно взять на github https://github.com/marsohod4you/MA3128 в директории rpi-serial4steppers В этом проекте так же вы найдете программу на питоне, которая управляет шаговыми двигателями. Программа использует tkinter, чтобы показать пользователю такое окно управления:
Четыре слайдера для четырех моторов. Когда слайдер поднимается вверх соответствующий мотор начинает вращать вал. Чем выше значение слайдера, тем быстрее вращение. Опуская слайдер ниже нуля меняю направление вращения.
Работу проекта демонстрирует это видео:
Присмотритесь, движение слайдеров в программе запускает вращение валов двигателей. Можно управлять скоростью и направлением вращения.
Здесь, кроме 3х моторчиков 28byj-48, которые с редукторами внутри, я, для разнообразия подключил еще моторчик типа NEMA17. Только для его управления я использовал другой силовой модуль L298N.
Таким образом, мы смогли подключить четыре шаговых двигателя к микрокомпьютеру Raspberry и можем ими управлять. Этот проект может дальше стать основой для каких-то новых устройств, машинок, роботов.
Подробнее...