Stm32f3-discovery USB <-> PC communication

Intro

In the previous post we have managed to get a serial communication with an host PC through USB.

Now we will add a big loop with a state machine in order to handle simple commands from the host (i.e. start/stop sampling) and to send back the requested data. In the next post this programming approach will be substituted with an Real Time OS.

The data will be displayed on the host on a custom program realized with the Qt framework.

Microcontroller

State machine

This example comes from Stack Overflow and shows a table driven approach. The implementation will be based on this pattern.

typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t;
typedef struct instance_data instance_data_t;
typedef state_t state_func_t( instance_data_t *data );

state_t do_state_initial( instance_data_t *data );
state_t do_state_foo( instance_data_t *data );
state_t do_state_bar( instance_data_t *data );

state_func_t* const state_table[ NUM_STATES ] = {
    do_state_initial, do_state_foo, do_state_bar
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    return state_table[ cur_state ]( data );
};

int main( void ) {
    state_t cur_state = STATE_INITIAL;
    instance_data_t data;

    while ( 1 ) {
        cur_state = run_state( cur_state, &data );

        // do other program logic, run other state machines, etc
    }
}

Add transaction states (if any).

typedef void transition_func_t( instance_data_t *data );

void do_initial_to_foo( instance_data_t *data );
void do_foo_to_bar( instance_data_t *data );
void do_bar_to_initial( instance_data_t *data );
void do_bar_to_foo( instance_data_t *data );
void do_bar_to_bar( instance_data_t *data );

transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = {
    { NULL,              do_initial_to_foo, NULL },
    { NULL,              NULL,              do_foo_to_bar },
    { do_bar_to_initial, do_bar_to_foo,     do_bar_to_bar }
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    state_t new_state = state_table[ cur_state ]( data );
    transition_func_t *transition =
               transition_table[ cur_state ][ new_state ];

    if ( transition ) {
        transition( data );
    }

    return new_state;
};

Implementation

The idea is very simple and minimal; we wait for a start command – we send the sensor data through USB at regular intervals until we get a stop command.

Since we have a stream of data (like TCP/IP) we need to implement a protocol in order to send and receiving meaningful data. For the shake of simplicity we will implement the minimum required, avoiding checksum, data compression, or whatever. The commands will also be encoded in text. This is really bad from the communication point of view and for the coding point of view too. However the main goal is to set up a communication channel using this beast called USB, and having human readable data using a traffic analyzer has no price.

This is a simple start, that can be easily enhanced and expanded.

Data structures:

typedef struct
{
	/* The ID of this real-time data item */
	unsigned char ucID;

	/* The size of this real-time data item in bytes */
	unsigned char ucSize;

	/* A pointer to the value of this real-time data item */
	unsigned char *pucValue;
}
tUIRealTimeData;

#define DATA_ACCELERATION_X        0x01
#define DATA_ACCELERATION_Y        0x02
#define DATA_ACCELERATION_Z        0x03

typedef struct
{
	long lAccelerationX;
	long lAccelerationY;
	long lAccelerationZ;
}
tBoardStatus;

tBoardStatus sBoardStatus;

const tUIRealTimeData g_sRealTimeData[] =
{

		{
				DATA_ACCELERATION_X,
				4,
				(unsigned char *)&sBoardStatus.lAccelerationX
		},

		{
				DATA_ACCELERATION_Y,
				4,
				(unsigned char *)&sBoardStatus.lAccelerationY
		},

		{
				DATA_ACCELERATION_Z,
				4,
				(unsigned char *)&sBoardStatus.lAccelerationZ
		},
};

We define a simple state machine, it starts in IDLE mode and can switch back and forth to SAMPLING mode:

typedef enum { STATE_IDLE, STATE_SAMPLING, NUM_STATES } state_t;

typedef struct instance_data {int pollo;} instance_data_t;
typedef state_t state_func_t( instance_data_t *data );

state_t do_state_idle( instance_data_t *data );
state_t do_state_sampling( instance_data_t *data );

state_func_t* const state_table[ NUM_STATES ] = {
		do_state_idle, do_state_sampling
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
	return state_table[ cur_state ]( data );
};

The main function simply runs the state machine and pharse incoming data:

	state_t cur_state = STATE_IDLE;
	instance_data_t data;

	/* SysTick end of count event each 10ms */
	RCC_GetClocksFreq(&RCC_Clocks);
	SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);

	initLED();

	STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_EXTI);

	/* Configure the USB */
	initUSB();

	/* Reset UserButton_Pressed variable */
	UserButtonPressed = 0x00;

	STM_EVAL_LEDOn(LED6);

	/* Demo Compass */
	demoCompassConfig();

	while ( 1 ) {
		cur_state = run_state( cur_state, &data );

		cur_state = parseIncomigData(cur_state);
	}

The IDLE state simply light a led for debug purpose, while the SAMPLING state sample and send the data.

state_t do_state_idle( instance_data_t *data )
{
	if (bDeviceState == CONFIGURED)
	{
		STM_EVAL_LEDOn(LED7);
		STM_EVAL_LEDOff(LED8);

	}
	return STATE_IDLE;
}

state_t do_state_sampling( instance_data_t *data )
{
	int i;

	STM_EVAL_LEDOff(LED7);
	STM_EVAL_LEDOn(LED8);

	if (UserButtonPressed) {
		STM_EVAL_LEDOn(LED3);
	} else {
		STM_EVAL_LEDOff(LED3);
	}

	/* Read Acc data */
	demoCompassReadAcc(AccBuffer);

	sBoardStatus.lAccelerationX = AccBuffer[0];
	sBoardStatus.lAccelerationY = AccBuffer[1];
	sBoardStatus.lAccelerationZ = AccBuffer[2];

	for(i = 0; i < 3; i++)
		AccBuffer[i] /= 100.0f;

	UISerialSendRealTimeData();

	/* delay 1s */
	Delay(100);

	return STATE_SAMPLING;
}

The parsing for now is pretty crude. It waits for an end of line and looks for “start” or “stop” sequence in order to move the state machine.

state_t parseIncomigData(state_t currentState)
{
	unsigned char startCommand[] = "start";
	unsigned char stopCommand[] = "stop";
	bool endOfLine = 0;

	if (bDeviceState == CONFIGURED)
	{
		STM_EVAL_LEDOn(LED9);

		CDC_Receive_DATA();
		/*Check to see if we have data yet */
		if (Receive_length  != 0)
		{
			if (Receive_length >= 64)
				Receive_length = 63;

			int i;
			for (i = 0; i < Receive_length; i++) {
				localBuffer[i + counter] = Receive_Buffer[i];

				if ((unsigned char)Receive_Buffer[i] == '\r')
					endOfLine = 1;
				if ((unsigned char)Receive_Buffer[i] == '\n')
					endOfLine = 1;
			}

			counter += Receive_length;

			/* parse incoming data */
			if (endOfLine) {

				counter = 0;
				endOfLine = 0;

				if (memcmp(startCommand, localBuffer, 5) == 0) {
					return STATE_SAMPLING;
				}
				if (memcmp(stopCommand, localBuffer, 4) == 0) {
					return STATE_IDLE;
				}
			}

			Receive_length = 0;
		}
	}

	return currentState;
}

Stream preparation:

void UISerialSendRealTimeData(void)
{
	unsigned long ulPos, ulItem, ulCount;
	unsigned char pucValue[4];

	/* Loop through the available real-time data items */
	for(ulItem = 0, ulPos = 2; ulItem < g_ulNumRealTimeData; ulItem++) {

		/* local copy */
		*((unsigned long *)pucValue) =
				*((unsigned long *)g_sRealTimeData[ulItem].pucValue);

		/* Copy the value of this real-time data item, byte by byte, to the packet */
		for(ulCount = 0; ulCount < g_sRealTimeData[ulItem].ucSize; ulCount++) {
			g_pucSerialData[ulPos++] = pucValue[ulCount];
		}
	}

	/* Put the header and length on the real-time data packet */
	g_pucSerialData[0] = TAG_DATA;
	g_pucSerialData[1] = ulPos + 1;

	CDC_Send_DATA ((unsigned char*)g_pucSerialData, g_pucSerialData[1]);
}

Host

Qt 5.1 has introduced the QSerialPort class, that simplifies the access to the serial port and allows portability between OSs.

QT += serialport

The graphs are created using QCustomPlot.

acc_plot

acc_plot2

43 thoughts on “Stm32f3-discovery USB <-> PC communication

  1. Hi,

    I looks like a cool example that I would love to play around with. Do you have a link for both the USB-client and PC-host projects?

    Kind regards,
    Michael

    1. Hi, this code is a patchwork that I’ve made just to implement the basic functionality. The next step was to implement it with FreeRTOS (described here).

      I’m sorry but this is just a demo/toy project, until I make a complete project and create good quality software I don’t want to publish it… For sure if you have a specific question I’ll be glad to help.

      Anyway, for the firmware part I’ve made available this simple project (echo client, the download link is at the end of the post). If you can set up the USB channel you’re almost done. The sensor code is already available from ST demo project. And for the QT part you can easily find examples for QTSerial communication and for simple plots.

      Kind regards,

      pezzino

      1. Hi again and tanks for your quick answer.

        To be more specifik i have just started working with the STM32F303VCT6 Discovery board and have managed to get the ST demo running in Coocox IDE (free and opensource). I can flash and debug directly and mod the very long and very unprette main.c-file made by ST. A natural next step was to make some sort of PC-interface and the learning curve for this new architecture (have never used ARM MCUs before NOR Qt – atleast in C++) suddenly seemed VERY steep πŸ™

        I understand your professional pride and why you would not want to destribute “ugly” code πŸ™‚ But from my point-of-view several things are a bit unclear (sorry). Which headers have you included, which drivers are you using, and why are e.g. your buffers not declared in the ex. – and how big have you made them? Like fx localBuffer? Please do not perceive this as a direct critique of your blog-post, but more like overall-“concrete”-questions πŸ™‚

        Maybe it is just me… but I was not able to adapt the standard template and make it work – at least not yet…

        Perhaps a more sequential main.c-file explanation would help…

        Thanks in advance…

        Kind regards,
        Michael

        1. Hi, everyone likes compliments, but only critiques will make you think and possibly learn something, so you’re welcome.

          Everyone, after having seen a couple of led blinking, want to have an external communication channel. Traditionally it is an UART, and you can find easy examples on UART for this board too. I wanted to use the USB like you, and I can tell you that it is a real complex device, you have to learn a lot on the USB protocol. Basically the main problem of a USB device is the U! This means that you have to define/declare that you want it as a virtual serial port (VCOM). ST doesn’t provide such example for this particular board. I’ve managed to adapt an example from another board, I can assure you that this is the first step and that it isn’t obvious at all.

          Take a look at this previous post. As you guess it has only excerpts of code πŸ™‚ (I would like to post just the biggest problems/points that I’ve faced, not “ready to run” demo, unless it is a complete and “finished” project). The good news is that I’ve been asked to share the code, and here it is!

          It is for Eclipse, but it is a complete working project.

          Try to have this code working, after that it is really easy to expand it and add sensors data.

          btw, the buffers are 64 bytes long this is due to the length of the USB endpoint.

          Good luck

          pezzino

          1. Okay, I’ll try to set up an Eclipse STM32 environment. However a very low practical challange has come to my mind. Will the virtual com port only be visable when being used? It seems to be initialized in the ST-demo, but Windows does not show af VCP in the manager. And the ST-drivers should be installed?!

          2. The default demo example doesn’t implement the USB HID device. You need to update the firmware with this demo STM32F3-Discovery_FW_V1.1.0/Project/Peripheral_Examples/USB_Example. Anyway this is an HID example, and it says that the device is a joystick/mouse. For VCOM/CDC you need different code as it is a different device served by different driver. For windows I don’t know, but I think that you need something…

  2. I have set up the Eclipse environment and get some errors..

    The
    stm32f30x.c and stm32f30x.h should be replaced with: stm32f30x_it.c and stm32f30x_it.h in hw_config.c and *.h or?

    and the usb_type.h included in hw_config.h seems to missing – correct!?

    kind regards,
    Michael

    1. usb_type.h is under STM32_USB-FS-Device_Driver\inc, but the environment cannot find it in the folder structure apparently…. do you have an idea?

      1. Yes, you have two solution, the “easiest” one is to use the full path in the #include directive.
        The second one is to right click on the project folder -> properties -> C/C++ general -> paths and symbols and add the includes selecting the GNU C language (you will need /stm32f3-usb-vcom-echo/Libraries/STM32_USB-FS-Device_Driver/inc, /stm32f3-usb-vcom-echo/Libraries/CMSIS/Include, /stm32f3-usb-vcom-echo/Libraries/CMSIS/Device/ST/STM32F30x/Include, /stm32f3-usb-vcom-echo/Libraries/STM32F30x_StdPeriph_Driver/inc, and anything that is missing)

        1. The first isn’t pretty and would be REALLY anoying to move to other computers πŸ™‚

          I know the second one and have already done it – but it seems that it has no effect? I have tried to add af file system and workspace.. both in and outside workspace path.. it reeeaaly starts to annoy me this board… thank you for your time and your patience…

          1. don’t give up, it is always frustrating at the beginning..

            anyway I’ve checked the files in the project, there is a .cproject file with all the set up, you also need to provide some defines. You can open this file and check it (e.g. ), but I think that if you import the project in Eclipse from the zip file this is automatic

  3. Good morning…

    Nothing like a good nights sleep to clear your mind. The Eclipse project is now working, but I got some new “exiting errors”, which might be easy to solve?!

    1: cannot open linker script file xxx\xxx\…\ STM32_FLASH.ld

    2 and 3 are (I guess) associated make errors, because the FLASH description was not found. Is it just the same problem all over again, or does this one have a twist?

        1. oh, yes, this is a full path based on my machine.

          You can change it under: project properties -> c/c++ build -> settings -> tool settings -> linker -> general

          there you can change the script file location to your correct path

          1. OK, thanks… Succes!

            But the last two errors did not disappear along with this…
            Description Resource Path Location Type
            make: *** [src/hw_config.o] Error 1 C/C++ Problem
            make: *** [stm32f3-usb-vcom-echo.elf] Error 1 C/C++ Problem

            Any ideas?

    1. Try clean the project and rebuild.

      Don’t trust the problems tab, check just the console tab, and if you see:

      Invoking: ARM Sourcery Linux GNU Print Size
      arm-none-eabi-size –format=berkeley “stm32f3-usb-vcom-echo.elf”
      text data bss dec hex filename
      15724 332 1868 17924 4604 stm32f3-usb-vcom-echo.elf
      Finished building: stm32f3-usb-vcom-echo.siz

      you’re done, if not look for warning/error there

      1. I cannot clean the project. But you are right it says:

        make all
        ‘Invoking: ARM Sourcery Linux GNU Print Size’
        arm-none-eabi-size –format=berkeley “stm32f3-usb-vcom-echo.elf”
        text data bss dec hex filename
        14544 1404 1868 17816 4598 stm32f3-usb-vcom-echo.elf
        ‘Finished building: stm32f3-usb-vcom-echo.siz’
        ‘ ‘

        16:06:44 Build Finished (took 387ms)

        When I try to make a Debug session to flash and Debug from Eclipse it does seem to be able to connect to the device. I am working on a simpler board (the STM32F0 discovery) parallel to this at it worked quite nice with Flash Debug. So how is your GDB set up? And is there anything else that I am missing!

        PS
        I owe you a cake or something after this πŸ˜€

    1. you need to start openocd with the following args:

      -f /usr/local/share/openocd/scripts/board/stm32f3discovery.cfg -c init -c”reset init” -c “flash write_image erase ${project_loc}/Debug/${project_name}.elf” -c reset -c shutdown

      check this post

  4. Some bonus and very concrete questions πŸ™‚

    How is the following declared?:

    g_ulNumRealTimeData
    g_pucSerialData
    TAG_DATA

    I assume that the buffers are as follows:
    extern __IO uint8_t Receive_Buffer[64];
    extern __IO uint8_t AccBuffer[64];
    extern __IO uint8_t localBuffer[64];

    1. TAG_DATA is a byte used in the protocol to discriminate commands/data/.. you can use whatever you want, like:

      typedef enum
      {
      TAG_COMMAND = 0xff,
      TAG_STATUS = 0xfe,
      TAG_DATA = 0xfd
      } Headers;

      It depends on the protocol that you want to use between the micro and the PC, UART (like TCP/IP) just send strams of data, you need a way to detect when a new packet is coming, what type of data it contains, its length…

      g_pucSerialData is symply an array of 64 byte used to prepare and send the message to the PC

      g_ulNumRealTimeData is a simple number of elements in a struct. in that struct I store the values of the sensors, so to fill the payload of the message that I want to send to the PC I need to know the number of parameters in the struct.
      It is a bit complex procedure that has advantages when you want to add or modify the data that you want to send. In the simplest case here you could have a global variable with the value of the acceleration updated somewhere else and you just need to copy this value in the array of data that you want to send.

      1. Okay…

        So like: unsigned long g_pucSerialData[64];

        I can build the project. But something is obviously wrong. LED8 is on, which is set before the while loop. So i guess my state machine is running… The state are declared before the main function and what more could be wrong (I guess a lot)

  5. I tried to go back to your uploaded project, but neither LED6 nor LED4 goes on at any time..

    If it compiles it works πŸ˜€ Well atleast not in mine case

  6. Thanks for your share about your project. I’m a beginner on stm32 (and programmation in general). I have the stm32f3 discovery and i’m trying to communicate with a pc (with Qt like you) with vpc (or usb). I already communicate with the pc with FTDI and usart functions on stm but for the for the moment i’don’t have any success with vpc. It’a possible that you post your complete code (present on this page) for the stm32f3 dicovery ? It can help me to have a better comprehension …

  7. Yes thank’s. i already dowload it (not yet tested for the moment !)but I’m talking about the code on this page to send acceleration data to the pc by the usb …

  8. Hi,

    I have implemented an CDC class on STM32F3 discovery board with the USB-FS library and it works fine.
    I’m processing data all the time in a while loop that has a frequency of about 50 Khz.After processing i send my data by USB. USB connection (VCP 115kbps) was ok and not disturb my processing.
    So i wanted to verify if i can send my data with no lost. For that purpose, i took a variable that i increment each time and then sent by USB.

    In the PC side, i store my received data into a file. My problem is that some data are jumped, i tried two ways to resolve this issue:
    1) I tried to reduce the loop frequency by adding some delay and when i get ~10 Khz, i can receive all my data but it want my loop frequency around 30Khz minimum when sending.
    2) I tried to increase the baud rate but with no success. Normally the USB 2.0 supposed to support the high frequency.

    Do you have please any explanation to this?
    Thanks in advance;

    1. Hi,

      if you send a byte it makes ~10bits (start, stop, parity,..) times 10KHz it makes 100Kbps, so it would make sense.

      Of course you can reach higher rates. Honestly right now I don’t know who is limiting you, but I would suggest to take into account the PC side as well..

      1. Thank you very much ppezzino for your replay. In fact, in my loop i send a float, so 4 byte. If we suppose that the frequency of my loop is about 40 Khz, i would need a speed communication of 40Khz *40 bits= 1600 Kbps.
        Even i tried to increase the baud rate in my board to reach 2000000 but in the PC side there is no effect. May be because the speed of the serial port in PC is limited?
        You said that to send a byte it makes ~10bits (start, stop, parity,..) but in the LINE_CODING linecoding =
        {
        115200, /* baud rate*/
        0x00, /* stop bits-1*/
        0x00, /* parity – none*/
        0x08 /* no. of bits 8*/
        };
        I don’t understand how the USB bloc is configured with this parameter ? there is no register related to do that.

  9. Hi ppezzino,
    You said :”Of course you can reach higher rates. Honestly right now I don’t know who is limiting you, but I would suggest to take into account the PC side as well “.
    What do you mean by to take into account the PC side? there is something special to do to increase the baud rate ?

    Thanks in advance,

    1. Hi,

      I don’t know if these links can help you:

      https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=%2Fpublic%2FSTe2ecommunities%2Fmcu%2FLists%2Fcortex_mx_stm32%2FUSB%20to%20transmit%20real-time%20data%20in%20the%20least%20time%20possible&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=2367

      http://forum.chibios.org/phpbb/viewtopic.php?f=3&t=625

      For the PC side I meant that you said that you’re losing packets, it seems strange to me, you probably should see some error bit set on the micro in this case, or whatever.
      On the PC side you have the physical hardware, a driver and a software that interface with that driver, running in a complex environment. It is a lot of stacks that you assume to be perfect, it isn’t always the case.. If you could connect the stm32 to a device that you know capable of receiving your desired transfer rate, or your PC to a device that you are sure that can generate that transfer rate, you will be sure where to concentrate.

      This link is in German, it talks about VirtualComPort-Driver by ST:

      http://mikrocontroller.bplaced.net/wordpress/?page_id=1263

      I’m not saying that this is the problem, just to take it into account.

      By the way, you should always use 64 bytes packets size to maximize data transfer. Another solution, since you talked about regular interval, could be to look at the isochronous mode.

      good luck, and let me know if you make some progress!

  10. Hi everyone,
    I tried to use a USBVCP Loopback example for STM32F3Discovery, but I have a one problem. Usb is intializating when I replug the cable, but when I use a STM32F4 when usb core was initialized usb was configured. How can I do a usb init with software replug the cable in STM32F3Discovery?

    Best regards
    slqa

  11. Hi,

    I am an electrical-electronics engineer student in Karadeniz Techical University. I am working on a project. This project about communicate between two boards. And this boards are stm32f303vc and stm32f429ZI. We can see directions on stm32f3 discovery with accelerometer. When we are turning the board on the x axis, led3 and led10 is blinking. Actually this is demo program(the second one when you use the button one time). I want to transfer this information into stm32f4 discovery and monitor it. But i dont know where to start so i am asking for your help. If you give me some hints it will bi usefull for me.

    Good days

    Kind Regards,

    1. Hi,

      the easiest way to talk between two boards is to use UART, you can easily find demo program about UART for the exact board you’re using. Start playing with very simple programs, forget the accelerometer. Just configure the UART on both board and check that you can send some dummy data, then build things on it, like adding a timer on one side that send a value and increment it. Make sure that you can both produce some data on one board and “consume” it on the other one.

      When you are confident about your communication channel start the integration with the demo software, or better, if you have time, add only the relevant parts of the demo program to your code.

      Good luck!

  12. Hi,

    Thank you for your advice. I talked to my teacher and he said don’t think high level. Send the data to usb port from F3 and read the data from that usb port with F4. Connect them to usb ports separately not each other. And he said you need may be an extra program to do this. Do you have an idea how to do with this way ? Why should i need an extra program and how should i do this. Thank you for your information

    Good Day, Kind Regards

  13. Hi ,

    Thanks for the great work.
    I have a problem With my STM32F407VG project.I am supposed to develop a program that sends Accelerometer data over VCP.
    I have the VCP part working a part and the accelerometer working also a part.My problem is to gather the two programs together.
    if any one has an idea please help.
    Thanks

Leave a Reply to ppezzino Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.