Xử lý tín hiệu điều khiển và cấu hình phần cứng
Một khi giao diện thu thập tín hiệu từ bộ điều khiển hoàn tất, bước tiếp theo là chuyển đổi dữ liệu số thành hành động vật lý cho động cơ và khớp robot. Cả hai thành phần này đều vận hành dựa trên nguyên lý điều chế độ rộng xung (PWM), tuy nhiên cách thiết lập tham số sẽ khác nhau tùy thuộc vào đặc tính cơ học của từng thiết bị.
Động cơ DC thường yêu cầu dòng điện lớn hơn khả năng cung cấp trực tiếp của các chân GPIO vi điều khiển. Do đó, cần sử dụng module driver làm trung gian. Một module tiêu chuẩn có khả năng điều khiển hai bánh xe, trong đó mỗi bánh cần hai chân xác định chiều quay và một kênh PWM điều chỉnh tốc độ. Thay vì đi dây rời rạc, giải pháp này áp dụng chuẩn giao tiếp Gadgeteer với đầu cắm 10 chân (bao gồm nguồn 3.3V, 5V, mass và các kênh dữ liệu), cho phép nhúng trực tiếp vào bo mạch chủ mà không cần breadboard phức tạp.
Với servo robot, quy trình đơn giản hơn về mặt dây dẫn. Mỗi khớp servo chỉ cần ba dây: nguồn 5V, mass và tín hiệu PWM. Ba khớp servo sẽ chiếm giữ ba kênh PWM độc lập. Lưu ý quan trọng là nguồn cấp cho cụm servo nên được tách biệt hoặc ổn định hóa để tránh hiện tượng sụt áp khi động cơ hoạt động đồng thời. Vi điều khiển vẫn đảm nhận vai trò sinh sóng PWM chính xác từ các chân I/O đã được mapping.
Lớp PWM trong .NET Micro Framework
Thư viện lõi cung cấp lớp PWM kế thừa IDisposable, hỗ trợ hai hàm khởi tạo phục vụ hai nhóm thiết bị khác nhau:
public class PWM : IDisposable
{
// Dạng 1: Điều khiển theo tần số và tỷ lệ duty cycle
public PWM(Cpu.PWMChannel channel, double frequencyHz, double dutyCycle, bool invert);
// Dạng 2: Điều khiển theo chu kỳ và độ rộng xung tuyệt đối
public PWM(Cpu.PWMChannel channel, uint period, uint duration, PWM.ScaleFactor scale, bool invert);
public uint Duration { get; set; }
public double DutyCycle { get; set; }
public double Frequency { get; set; }
public uint Period { get; set; }
public void Start();
public void Stop();
}
Hàm khởi tạo thứ nhất phù hợp cho động cơ DC. Tham số channel trỏ đến kênh PWM cứng của SoC. frequencyHz thường nằm trong khoảng 1000Hz đến 25000Hz (giá trị khuyến nghị khoảng 10000Hz để cân bằng giữa tiếng ồn và mô-men xoắn). dutyCycle nhận giá trị thập phân từ 0.0 (dừng hoàn toàn) đến 1.0 (tốc độ tối đa). Tham số invert giữ mặc định false trừ khi mạch ngoại vi yêu cầu đảo pha.
Hàm khởi tạo thứ hai tối ưu cho servo. period và duration chia sẻ cùng đơn vị thời gian do scale quy định (ví dụ: Microseconds). Chu kỳ chuẩn của servo công nghiệp là 20000 microgiây. Độ rộng xung duration quyết định góc quay, thường dao động trong khoảng 1000 đến 2000 microgiây. Giá trị này cần được tinh chỉnh dựa trên datasheet cụ thể của từng nhãn servo.
Tối ưu hóa mã nguồn điều khiển động cơ
Dưới đây là cách structuring lại logic khởi tạo và xử lý sự kiện di chuyển, sử dụng các hằng số rõ ràng và hàm phụ trợ để giảm thiểu lặp code:
private static readonly Cpu.Pin[] DirectionPins =
{
Mainboard.Gadgeteer.Socket2.Pin4, Mainboard.Gadgeteer.Socket2.Pin5,
Mainboard.Gadgeteer.Socket2.Pin6, Mainboard.Gadgeteer.Socket2.Pin7,
Mainboard.Gadgeteer.Socket1.Pin4, Mainboard.Gadgeteer.Socket1.Pin5,
Mainboard.Gadgeteer.Socket1.Pin6, Mainboard.Gadgeteer.Socket1.Pin7
};
private static OutputPort[] _dirOutputs = new OutputPort[DirectionPins.Length];
private static readonly Cpu.PWMChannel[] MotorChans =
{
Mainboard.PWM.Channel13, Mainboard.PWM.Channel14,
Mainboard.PWM.Channel2, Mainboard.PWM.Channel3
};
private static PWM[] _motors = new PWM[MotorChans.Length];
private const int FreqBase = 10000;
private const int CenterPoint = 128;
private const int TurnThreshold = 30;
Khởi tạo cổng xuất và kích hoạt sóng xung:
for (int i = 0; i < _dirOutputs.Length; i++)
{
_dirOutputs[i] = new OutputPort(DirectionPins[i], false);
}
for (int i = 0; i < _motors.Length; i++)
{
_motors[i] = new PWM(MotorChans[i], FreqBase, 0.0, false);
_motors[i].Start();
}
Logic xử lý joystick đọc tọa độ gốc, tính toán độ lệch và ánh xạ vào tỷ lệ tốc độ từng bánh. Nếu trục Y nhỏ hơn điểm neo, xe tiến; ngược lại xe lùi. Khi độ lệch trục X vượt ngưỡng, hệ thống sẽ giảm tốc độ bánh tương ứng để tạo quỹ đạo cong.
private void OnJoyStickMove(object sender, ControllerInputEventArgs e)
{
bool travelMode = e.LowerLeftTriggerState >= 128;
if (!travelMode) return;
int offX = Math.Abs(e.StickX - CenterPoint);
int offY = Math.Abs(e.StickY - CenterPoint);
double speedRatio = offY / (double)CenterPoint;
bool forward = e.StickY < CenterPoint;
ApplyDirectionPattern(forward);
for (int i = 0; i < _motors.Length; i++)
{
double targetDuty = speedRatio;
if (offX > TurnThreshold)
{
double subtract = offX / (double)CenterPoint;
// Giảm tốc bánh trái khi rẽ phải, giảm bánh phải khi rẽ trái
targetDuty -= (i == 2 || i == 3) ?
(e.StickX < CenterPoint ? 0 : subtract) :
(e.StickX < CenterPoint ? subtract : 0);
}
_motors[i].DutyCycle = Math.Max(0.0, Math.Min(1.0, targetDuty));
}
}
private void ApplyDirectionPattern(bool goForward)
{
// Mẫu logic HIGH/LOW cho H-Bridge
int[] pattern = goForward ?
new int[] { 1, 0, 1, 0, 1, 0, 1, 0 } :
new int[] { 0, 1, 0, 1, 0, 1, 0, 1 };
for (int i = 0; i < pattern.Length; i++)
{
_dirOutputs[i].Write(pattern[i] == 1);
}
}
Cấu trúc điều khiển khớp servo
Việc điều chỉnh góc servo được thực hiện trực tiếp bằng thay đổi thuộc tính Duration. Chu kỳ giữ cố định, trong khi độ rộng xung được dịch chuyển quanh giá trị trung hòa (neutral) dựa trên tín hiệu núm xoay hoặc lệnh cơ học.
private static readonly Cpu.PwmChannel[] ServoChans =
{
Mainboard.PWM.Channel8, Mainboard.PWM.Channel9,
Mainboard.PWM.Channel6, Mainboard.PWM.Channel0, Mainboard.PWM.Channel4
};
private static readonly uint PeriodUs = 20000;
private static readonly uint NeutralPulseUs = 1500;
private static PWM[] _servos = new PWM[ServoChans.Length];
private static int gripperDelta = 0;
for (int i = 0; i < ServoChans.Length; i++)
{
_servos[i] = new PWM(ServoChans[i], PeriodUs, NeutralPulseUs, PWM.ScaleFactor.Microseconds, false);
_servos[i].Start();
}
private void ProcessServoCommands(object sender, ControllerInputEventArgs e)
{
// Dịch chuyển khớp xoay ngang và dọc
if (e.LeftStickActive)
{
int px = (int)(NeutralPulseUs - (e.StickX - CenterPoint) * 5);
int py = (int)(NeutralPulseUs - (e.StickY - CenterPoint) * 5);
_servos[0].Duration = (uint)Range(px, 1000, 2000);
_servos[1].Duration = (uint)Range(py, 1000, 2000);
}
// Tăng/giảm độ mở kẹp cơ khí
if (e.RightLowerBtnHit)
{
gripperDelta = Math.Min(gripperDelta + 10, 255);
}
else if (e.RightUpperBtnHit)
{
gripperDelta = Math.Max(gripperDelta - 10, 0);
}
// Áp dụng xung cho khớp kẹp
int gripperVal = (int)(NeutralPulseUs + (gripperDelta - CenterPoint) * 5);
_servos[2].Duration = (uint)Range(gripperVal, 1000, 2000);
}
private static int Range(int val, int min, int max) => Math.Max(min, Math.Min(max, val));
Việc biên dịch và chạy thử nghiệm các khối code này được thực hiện trực tiếp trên môi trường IDE tích hợp. Công cụ theo dõi biến runtime cho phép quan sát giá trị xung phát ra tại từng chân GPIO ngay lập tức, giúp điều chỉnh nhanh chóng các tham số giới hạn (min/max) cho phù hợp với đặc tính cơ khí thực tế của động cơ và servo đang sử dụng.