This is a common challenge faced when developing or integration embedded systems.
Hyphothesis:
All (All is a strong quantifier for sure) embedded systems need to be able to receive data from a host/master system to change their behaviour during their lifecycle.
Assumptions:
- The Embedded system (ES) in question receives some sort of calibration/configuration values during the production process.
- The ES changes its behaviour during normal operation based on external input (Fieldbus, WiFi-Connection, Key-Pads...)
- The ES can receive data to end it'soperations (Think remote detonation of ordnance or a "End Comms" command for spacecraft)
Typical approaches:
- Use a common method of interfacing with a standard way of interfacing (Plug a ethernet cable into your PC and fire up a SSH terminal to connect with a industrial controller e.g.)
- Us a common method of interfacing with a purpose build way of interfacing (Plug a ethernet cable into your PC and write a protocoll to communicate with your gadget)
- Us a purpose build method of interfacing with a standard way of interfacing (Build a ethernet to parallel to USB to I2C converter and fire up a SSH terminal to connect with your gadget)
- Us a purpose build method of interfacing with a purpose build way of interfacing (Choose your own physical means of communication as well as the protocoll)
For small scale hobby projects, the (2) approach is the most suiteable.
Here is why:
Most "DIY-Grade" embedded boards (Arduino, Curiosity Boards, and so on... ) come with a USB (common way) interface useable by the uC firmware. But, the complexity and overhead associated with meeting standard protocols (USB-Device or other...) are commonly not woth the benefits.
This is, except:
- You are forced to meet certain standards (Comercial products, desired inter-operability, .... )
- You are forced to meet reliabilty standards (Device must not fail in operation, device is situated in remote locations without physical access, ...)
- You find a ready-to-use software library online and your application allows the overhead .....
And here is how:
To implement a Host-Device interface, you first have to make some decisions:
- Which physical interface to use (Choose what is available, e.g. USB or UART/USB)
- Choose what protocol constraints you will have to meet (Does it need to be realtime, what bandwidth, error protection, error correction, multi-slave, multi-master, topology and so on)
- What are your software requirements on the system side? (Upload calibration values once, exchange information - status or commands - during operation, and so on...)
- What type of interface to use on the host-side (GUI interface with buttons, command line, script, remote commands, web-based, whatever)
For your application:
I'd recommend to following approach:
- Use a UART/USB converter board and plug it into your PC/Laptop. It will act as a Serial-Port.
- I would go with a fixed-frame, fixed-command, single-master/slave, memory-map based protocoll (More on that later)
- The protocoll should allow to write/read data to the RAM of the gadget during runtime
- I would go with a simple GUI allowing your father to easily use the gadget ( Single button for every command and a big "are you sure?" frame if he clicks anything he should not)
Let's get rolling with the protocol:
We set out to design a fixed-frame, fixed-command, single-master/slave, memory-map protcoll. Your can google for ModBus-RTU for example.
The basic operation mode is: Your PC sends a sequence of bytes to the gadget, the gadget reads them and make decisions based on the contents and then replys "OK" or "N-OK", or "OK, and the data you requested is 83" to the PC.
Fixed-Frame:
The frame (The sequence of bytes) is the same for every command (Contents can change of course). E.g: 2Bytes
So, your PC will send two bytes for every request and will then wait for the gadget to respond two bytes.
Fixed-Command:
The frame contains a command (Stored in the first byte). The meaning of a given command (E.G command '1':'Write to Memory') does not change depending on the state of your gadget. So, commands stay the same during runtime.
Single-Master/Slave:
This means, that in your protocol only a single master (The one who sends commands) and a single Slave (The one who responds "OK" or "N-OK") exists at any given time.
Memory-Map:
This means, that there is a dedicated memory area in your gadgets uC where the data received/to be send by/to the master is stored. Your main application "reads" this memory and performs actions based on it's contents.
Let's get rolling with the firmware:
Here is some "psuedo/C code" on how to implement the protocol.
The PC sends two bytes per command with the following structure:
(4Bits Command)(4Bits Address)(8Bit Data)
and the gadget respondes with:
(8Bits status Flag)(8Bit Data)
volatile uint8_t dataArray[16]; //Here the data to be send/received is stored
volatile uint8_t uartBuffer[2]; //Here the raw UART transmissions are stored
#define CMD_ERROR 0U
#define CMD_SUCCESS 1U
void Uart_Transreceive(){
//Wait for new data to arrive on the UART
//And then store it into the Buffer
for( uint8_t idx = 0; idx < 2; idx++ ){
while (UART_WaitForNewDataByte()){/*NOP*/}
uartBuffer[idx] = UART_GetCurrentDataByte();
}
//Frame fully received so interprete it
//This puts the data to send in the buffer
Uart_HandleFrameRx();
//Now send the data
for( uint8_t idx = 0; idx < 2; idx++ ){
while (!UART_CanSendNewByte()){/*NOP*/}
UART_SetCurrentDataByte(uartBuffer[idx]);
}
}
void Uart_HandleFrameRx(){
//Check to first byte in the buffer (The command/Address)
//The command is stored in the upper 4-Bits of the byte
//and the address is stored in the lower 4-bits
//This gives us 16 commands and 16 data address to write/read to/from
uint8_t command = ( uartBuffer[0] & 0xF0 ) >> 4; //Get upper for bits and align them to the rigth
uint8_t address = uartBuffer[0] & 0x0F;
switch(command){
case 0 : { dataArray[address] = uartBuffer[1]; break;} //Write to memory what was received
case 1 : { uartBuffer[1] = dataArray[address] ; break;} //Read from memory what is to be send
/* Implement other commands 0...15 to your liking */
/* But always use the default case to catch errors */
default : { uartBuffer[0] = CMD_ERROR; uartBuffer[1] = 0; return;} //The return here is important!
}
//Now signal success to the Host pc
//This is only exxecuted if the default case is not executed
//So a valid command was received
uartBuffer[0] = CMD_SUCCESS;
}
void Motor_RunProgram(){
//Lets use the data received
Motor1_SetSpeed(dataArray[1]); //Speed stored at adr=1
Motor1_SetParamXY(dataArray[XY]); //You get the idea....
//Now run the motor and wait until it has finished
//This must be blocking, so must return only
//after the motor has stopped running its sequence
//And now reset the "active" bit
dataArray[0] = 0;
}
int main(void){
/* Setup and so on */
UART_SetupInterface_8N1_9600Baud();
while (true){
//Now lets run the actual "protocol"
//This will block until two bytes are received and processed
//When a special "Begin Motor programm" was received
//Your motor programm is run and then returns here
//NOTE: During the motor programm, no new commands can be processed
Uart_Transreceive();
if ( dataArray[0] == 1 )
Motor_RunProgram();
}
}
Now let's get rolling with the host software:
Here is some "psuedo/C#" code to use for your GUI application.
This can ofcourse be enhanced!
class Programm{
static SerialPort sp;
void Main(){
/* Do your Setup etc... */
//See your windows "Device-Manager" on which COM-Port to use
//Configure as 8N1 9600 as commonly used
sp = new SerialPort("COM1",9600,8,1);
}
static byte SendCommand(byte command, byte address, byte data){
//Build the first byte (4Bits Command)(4Bits Address)(8Bit Data)
//and populate the buffer with it
byte[] buffer = new byte[2];
buffer[0] = ( command & 0x0F << 4 ) | ( address & 0x0F );
buffer[1] = data;
//Now send the data and wait a short time for the
//response ... can be enhanced (Timeouts and so on)
sp.Send(buffer,2);
Thread.Sleep(1000);
sp.Read(buffer,2);
//Now check if there was an error
//The first byte should always be "success"
if ( buffer[0] != 1 ) {/* Error happend */ }
//and returnt the data received from the gadget
//if a read command was issued
return buffer[1];
}
void Button_RunMotor1WithGivenParameters(){
//Lets say your programm consists of four parameters
//So send these four parameters to the gagdet
//And the start the programm
//See the firmware
//Sets speed to value 15 (Write command to adr = 1
SendCommand(0,1,15);
//Write generic param (Write command to adr = XY with value ABC
SendCommand(0,XY,ABC);
//read back some param
//Reads back the data stored in the address 7 in the gadget
byte param = SendCommand(1,7,0);
//And now execute the motor programm
//Writes '1' to the dataAraay[0] check in the main Loop
SendCommand(0,0,1);
}
}
And: Tadaaaaaaa .... of you go!*
You now have an idea of the uC and the PC side as well as some knowledge on how to implement such a protocol.
There are many protocols used commonly for these tasks. One is ModBus-RTU