10.02.2015

Bluetooth HC-05

Za niewielkie pieniądze można kupić moduł bluetooth HC-05. Dzięki niemu STM32F4Discovery otrzyma możliwość komunikacji ze światem zewnętrznym poprzez port szeregowy i to bezprzewodowo. Dobrze jest zakupić moduł przylutowany do adaptera PCB, dzięki niemu łatwo go podłączymy. Ja posiadam moduł HC-05 taki jak poniżej.
HC-05 STM32F4DiscoveryHC-05 STM32F4Discovery


STM32F4Discovery posiada następujące porty COM:

COM1: (rx, tx, cts, rts)=(PA10, PA9 , PA11     , PA12)
COM2: (rx, tx, cts, rts)=(PA3 , PA2 , PD3      , PA1)
COM3: (rx, tx, cts, rts)=(PD9 , PD8 , PD11     , PD12)
COM4: (rx, tx, cts, rts)=(PC11, PC10, GPIO_NONE, GPIO_NONE)
COM5: (rx, tx, cts, rts)=(PD2 , PC12, GPIO_NONE, GPIO_NONE)
COM6: (rx, tx, cts, rts)=(PC7 , PC6 , GPIO_NONE, GPIO_NONE)

Moduł możemy podłączyć do dowolnego portu COM, ale nie sprawdzałem wszystkich. Ja podłączyłem do portu COM2. Minimum co musimy podpiąć, oprócz zasilania, to piny RXD i TXD. Łączymy je na krzyż do wyjść TX i RX. Schemat podłączenia wygląda tak:

HC-05 GND -> STM32F4Discovery GND
HC-05 VCC -> STM32F4Discovery VCC (+5V)
HC-05 RXD -> STM32F4Discovery PA2
HC-05 TXD -> STM32F4Discovery PA3

Dodatkowo można podłączyć wejście WAKEUP (przeważnie podpisane KEY) i wyjście STATE. Ustawienie stanu wysokiego na WAKEUP (KEY) powoduje przejście modułu w tryb komend AT. Umożliwia to konfigurację i sterowanie modułu. Na przykład zmianę nazwy sieciowej, ustawienia innych parametrów transmisji, wykrycie innych modułów itp. Natomiast na wyjściu STATE jest ustawiany stan wysoki w przypadku sparowania modułu. Ja tych dwóch pinów nie podłączałem. Aha. Domyślnie HC-05 działa jako slave z następującymi parametrami: prędkość 9600, brak parzystości, 8 bitów danych, 1 bit stopu, hasło parowania 1234. No oczywiście do poprawnego komunikowania się z modułem niezbędne jest sparowanie z innym urządzeniem bluetooth. Ja do testu sparowałem moduł z PeCetem.

Ok. Prosty test. Wysyłamy bez przerwy tekst kontrolny i wyświetlamy informację jeśli coś odbierzemy. Pozwoli to zorientować się czy moduł działa.

public class Program
{
    public static void Main()
    {
        using (var serial = new SerialPort("COM2"))
        {
            serial.Open();
            serial.DataReceived += (s, e) =>
            {
                Debug.Print("Data received");
                while (serial.BytesToRead > 0)
                    serial.ReadByte();
            };
                

            byte[] buffer = Encoding.UTF8.GetBytes("Ping from STM32F4Discovery\r\n");
            for (;;)
            {
                serial.Write(buffer, 0, buffer.Length);
                Thread.Sleep(3000);
            }
        }
    }
}
Teraz wystarczy tylko odpalić jakiś program terminalowy (np. realterm) i zobaczyć co się będzie działo. Można też zbudować taki prosty program do wysyłania i odbierania danych i odpalić na komputerze (u mnie sparowany port to COM18).

class Program
{
    static void Main()
    {
        using (var serial = new SerialPort("COM18"))
        {
            serial.Open();
            serial.DataReceived += (s, e) => Console.WriteLine(serial.ReadLine());

            byte[] buffer = Encoding.ASCII.GetBytes("Ping from PC\r\n");
            while (!Console.KeyAvailable)
            {
                serial.Write(buffer, 0, buffer.Length);
                Thread.Sleep(3000);
            }
        }
    }
}
Dobra. Bardziej zaawansowany program. Zdalne włączanie i wyłączanie kolorowych diod na płytce. Wysłanie znaków r, g, b, o spowoduje odpowiednio zapalanie i gaszenie skojarzonych diod. Najpierw kot w .NETMF.

Definiujemy komendy i w tablicy kojarzymy je z odpowiednimi diodami. Dodatkowo w głównej pętli programu wysyłamy tekst testowy (co w poprzednim programie) do klienta:

public enum Command
{
    Unknown,
    Red,
    Green,
    Blue,
    Orange
}

private static readonly Hashtable Led = new Hashtable();

public static void Main()
{
    Led.Add(Command.Red, new OutputPort(Stm32F4Discovery.LedPins.Red, false));
    Led.Add(Command.Green, new OutputPort(Stm32F4Discovery.LedPins.Green, false));
    Led.Add(Command.Blue, new OutputPort(Stm32F4Discovery.LedPins.Blue, false));
    Led.Add(Command.Orange, new OutputPort(Stm32F4Discovery.LedPins.Orange, false));

    using (var serial = new SerialPort("COM2"))
    {
        serial.Open();
        serial.DataReceived += DataReceived;

        byte[] buffer = Encoding.UTF8.GetBytes("Ping from STM32F4Discovery\r\n");
        for (; ; )
        {
            serial.Write(buffer, 0, buffer.Length);
            Thread.Sleep(10000);
        }
    }
}

Najważniejsza część programu odbywa się w procedurze DataReceived. Czytamy po kolei wszystkie bajty z bufora portu, konwertujemy na komendy, i zmieniamy stan odpowiedniej diody. Dodatkowo wysyłamy poprzez port aktualny stan diody:

private static void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    var port = (SerialPort) sender;
    while (port.BytesToRead > 0)
    {
        int b = port.ReadByte();
        if (b == -1)
            continue;

        Command command = ToCommand(b);
        if (command == Command.Unknown)
        {
            Debug.Print(b.ToString("X2"));
            continue;
        }

        var led = (OutputPort) Led[command];
        bool newValue = !led.Read();
        led.Write(newValue);

        string response = (char)b + "=" + (newValue ? "on" : "off") + "\r\n";
        byte[] buffer = Encoding.UTF8.GetBytes(response);
        port.Write(buffer, 0, buffer.Length);
    }
}

private static Command ToCommand(int value)
{
    if (value == 'r')
        return Command.Red;

    if (value == 'g')
        return Command.Green;

    if (value == 'b')
        return Command.Blue;

    if (value == 'o')
        return Command.Orange;

    return Command.Unknown;
}
Teraz kot programu sterującego na PC. Program wyświetla odebrane dane w oknie konsoli, a tekst wpisany i zatwierdzony enterem wysyła:

class Program
{
    static void Main()
    {
        using (var port = new SerialPort("COM18"))
        {
            port.DataReceived += DataReceived;
            port.Open();
                
            for(;;)
            {
                string send = Console.ReadLine();
                if(send.Length == 1 && send[0] == 'q')
                    return;

                port.Write(send);
            }
        }
    }

    static void DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        var port = (SerialPort) sender;
        while (port.BytesToRead > 0)
        {
            int b = port.ReadByte();
            if (b == -1) 
                continue;

            bool printable = (b >= ' ' && b <= '~')
                             || b == 0x0d
                             || b == 0x0a;
            if (printable)
                Console.Write(Convert.ToChar(b));
            else
                Console.Write(b.ToString("X2"));
        }
    }
}

Kot programu i klienta: DemoBTHC05, SerialController