STM32 and Bluetooth or remote control of PC do it yourself
Instead of introducing
Good afternoon. Today I will try to talk about my attempt to build a PC remote control system within the same room.
Immediately note for those who say bike. Yes it is a bicycle. And it was interesting for me to build it. For several reasons. One of which is the desire to do it yourself and not buy.
Prehistory
One day my head was visited by the idea of remote control of my PC, namely, in addition to simple commands to a media player, also receiving feedback in the form of the status of a particular application and the system as a whole. The closest analogue of this device is the now fashionable electronic watch for smartphones. And since there was nothing close by that which was similar in functionality, I decided to educate myself and not only assemble this system myself.
One of the variants used the data transmission method customary for remote controls for modern household appliances - IR or IR. However, due to the fact that it is not very convenient, and the problem with long distances. In fact, everything is simpler than you might think, the module that I got out of one and the "corpses" of the PDA was designed to work as a transceiver, that is, there was not even a signal inverter. So when I connected it directly to the USB <-> UART converter on the debugging console, I saw a continuous stream of random characters. ')
Therefore, I decided to use my usual BT transceiver. There is also an abscess in the bins of the ESD-200 module left over from one of the tests (do not take it, it is really inconvenient and a bit dull, as well as an expensive module, plus packages start to disappear at long distances). So that it was not boring to pick up the screen from Siemens M55. There is also a block of six buttons to which you can assign arbitrary commands. I chose the STM32F4DISCOVERY debug board as the brain, and the debugger is on board, and you don’t need to solder anything.
General scheme
Attention: what is described here is present mainly in theory. In practice, some points are simplified as this is a layout and simply because it was faster to do so. On the part of the PC, on which, incidentally, Linux (I have this is Gentoo Linux, you can have any other distribution), the program is running - the server. She polls the list of available devices and after finding the necessary connection with it. On the device side there is a trigger that controls the status of the connection. When a connection is detected, it calls the primary poll module (a kind of ping). Which, if successful, brings the device into interactive mode. This mode is characterized by the fact that the transfer initiates any of the two components of the system. Such a principle of system operation was chosen for two reasons - low response time to any actions, absence of additional timers (except for the TIMEOUT timer from the daemon on the PC) and, of course, a relatively simple exchange protocol. Although the idea is needed TIMEOUT from the device because There are sometimes problems with the BT module.
Implementation
Now let's see what we got in the above painted scheme:
A demon that spins on the PC side. Although there is a demon there, in the current implementation it is a script for a couple of hundred lines with minimal error handling. One of the most interesting components. Entirely written in Python. One of the features of this module is that it uses the DBUS messaging system to manage user programs. By the way, the implementation of this protocol for the Python language is in any modern distribution kit. Further I will write in detail how this code works.
A protocol for fully asynchronous multi-threaded message exchange, implemented on top of a serial port with emulation of multiple connections using a message addressing system. It was coined after reading the description of network protocols and USB standards, and I also wrote it because I was lazy to smoke MODBUS, or rather sad to look at its implementation.
A device with firmware that, using the Bluetooth module, transmits commands via the serial port when the user presses the buttons of the device. Feedback is also possible in the form of displaying information on the device display. But unfortunately, this has been done purely for example. Passing fixed commands and nothing else.
Demon
After searching for the device, we connect to it and wait for the receipt of commands. Some of the requests will be answered, while others will simply be performed a certain action.
Arranged in the prototype is quite simple:
#!/usr/bin/python2 # -*- coding: utf-8 -*- import serial import dbus import subprocess import time import os # BT , , . , . # ser = serial.Serial('/dev/rfcomm0', 115200, timeout=1) # DBUS, Amarok. bus = dbus.SessionBus() am = bus.get_object('org.kde.amarok', '/Player') # , . commands = { 'p': [am.PlayPause, []], '>': [am.Next, []], '<': [am.Prev, []], 'm': [am.Mute, []], '+': [am.VolumeUp, [1,]], '-': [am.VolumeDown, [1,]] } print 'Connected' # . try: while 1: try: # , , . line = ser.read(1) # except serial.serialutil.SerialException: # , , . ser.close() time.sleep(0.5) while 1: try: # ser = serial.Serial('/dev/rfcomm0', 115200, timeout=1) break # , .. , 2 . except serial.serialutil.SerialException: time.sleep(2) # ( , , ) , if len(line) == 1: # , if line[0] == 'C': print 'Command' # 2 - line += ser.read(2) # 2 - .. 3 . if len(line) == 3: print "0x%02x 0x%02x 0x%02x" % (ord(line[0]), ord(line[1]), ord(line[2])) # ping, , .. "" . if ord(line[1]) == 0x00 and ord(line[2]) == 0x00: print 'Device ping' ser.write('A') ser.write(chr(0x00)) ser.write(chr(0x02)) ser.write(chr(ord('O'))) ser.write(chr(ord('K'))) print 'Ansver to device' # , . if ord(line[1]) == 0x02: # . mlen = ord(line[2]) message = ser.read(mlen) # if message in commands: current = commands[message] current[0](*current[1]) # - . except KeyboardInterrupt: ser.close() del am print 'Exiting' # BT . # cleaning cmd = "sudo rfcomm unbind all" runner(cmd)
Protocol
The protocol is quite simple, it consists of a three-byte header and a message with a maximum length of 255 bytes, as well as it may not have a message - only the header. The header indicates the type of message, the recipient's address and the size of the message. A more detailed description of the protocol is set forth in this document . Of course, in practice, not everything that has been described has been implemented, but at least it works. Although there are still problems with receiving messages and other errors in the exchange.
Device
That's actually the kind of device that I did, as well as the module from the PC, this device was made as a prototype, given the bitter experience of failures when building the first version of the system: The device is controlled by firmware written in C, the build uses its own project structure which you can see by following the link to the project below.
/* main work function */voidwork(void){ unsignedshort i, j; unsignedchar mailbox_num = 0; volatile ProtoIOMBox * mbox; /* , - . */// . /* check status */ check_status(); // "" , . . /* send ping */ mbox->outbox->header = 'C'; /* Command */ mbox->outbox->size = 0x00; /* 0 for ping request */ mbox->outbox_s = PROTO_IO_MBOX_READY; /* Box ready */ mbox->inbox->size = 64; /* buffer len for control */ mbox->inbox_s = PROTO_IO_MBOX_READY; /* Box ready */ /* wait connection estabilished */ while (status == 0); /* send ping message */ proto_send_msg(mailbox_num); /* wait to send message */ while (mbox->outbox_s <= PROTO_IO_MBOX_SEND); if (mbox->outbox_s == PROTO_IO_MBOX_COMPLETE) LCD_String("Con", 36, 6, 1, WHITE, GLASSY); else LCD_String("Un", 36, 6, 1, RED, GLASSY); /* get ping message */ /* FIXME wtf? this not work or work parity */ //proto_get_msg(mailbox_num); /* wait to get message */ while (mbox->inbox_s <= PROTO_IO_MBOX_SEND); if (mbox->inbox_s == PROTO_IO_MBOX_COMPLETE) { LCD_String("OK", 36 + 3 * 7, 6, 1, GREEN, GLASSY); for (i = 0; i < mbox->inbox->size; i++) LCD_Char(mbox->inbox->message[i], 70 + i * 6, 6, 1, WHITE, GLASSY); } else LCD_String("ERR", 36 + 3 * 7, 6, 1, RED, GLASSY); // . , . /* infinity loop */ while (1) { if (button_state.state[B_LGHT] == B_CLICK) { sender('+'); button_state.state[B_LGHT] = B_RELEASE; } /* */ } }
I also want to mention separately the module for sending and receiving messages - in the project this is the proto.o module - the source code proto.c and the header file proto.h. I will not give the code as it is big. But in general, I will tell you how it works. The module is designed entirely to work from interrupts, however, data transfer is not correctly implemented now, therefore it requires prior initialization call. Receiving and sending a message is carried out using 2 state machines, which change their state as the bytes are sent. Implemented message validation and error handling.
I also attach a video of the system as a whole:
Afterword
Since at the moment I received a new board ( BeagleBone Black ) and are working on software development for this board, I decided once the project was frozen, I put it in open access: All source codes are available on my GitHub account . I am also happy, if possible, to answer any questions you may have regarding the project.