Pages

Thursday, June 5, 2014

I2C, Nucleo STM32F401RE and FreeRTOS

My Nucleo board arrived today, I have been eagerly awaiting its arrival since I purchased it last week. The board comes with a powerful ARM Cortex M4 processor with max speed of 84MHz. This should be enough for limited DSP functionality (or better get an ARM Cortex M4F instead). This morning I've decoupled my previous breadboard (MCP23017 with leds) setup to test the I2C bus on this new board. My aim was just to familiarize myself with the board, use the tools that come along with it and develop a simple application to write to the I2C bus.

The nucleo board also comes with an arduino compatible header which is great since I also happen to have a lot of arduino sheilds. Do note that the nucleo board uses 3.3 volt logic and it may not be compatible with other arduino shields. To give an overview of this experiment, here's my running setup:


So it's a very simple setup, we output an 8 bit counter value to the leds and loop.

To accomplish this experiment, we need the following tools:

- Download STM32CubeMX from STMicroelectronics here. You will find the link to the download at the end of the page.  We will use the CubeMX software to generate an initialization code for our nucleo F401RE board. It will also generate a template project for your compiler tool chain. There is also an online ide at mbed, but I currently do not use it since I work offline most of the time.

- I currently own an Embedded Workbench for ARM tool chain, technically the CubeMX software can generate targets for other tool chain. EWARM is the one I currently have for now.

- Install the needed Nucleo drivers at this link. And while you are on that page, download also the firmware update to fix some important issues with the board.

- The MCP23017 (I2C io extender) setup and schematics are discussed in my previous blog, maybe you should examine it first.

Once everything is downloaded and set-up, it's time to have some fun with the board. 

1) Let us try to setup our board initialization with CubeMX to generate our project files. Open CubeMX software and click on "New Project".

2) Once done, you will be prompted to select the board setup similar to the image shown below. Select the board selector tab and choose the Nucleo F401RE board.




3) After the board has been selected, the default configuration scree appears. This allows us to choose the pin assignments for the board. 



4) On the left side notice that there are currently no I2C buses that are enabled, choose I2C1 (expand it), and select "I2C" instead of disabled. FreeRTOS is not necessary, but since I'd be doing experiments with it in the future, we go ahead and enable it now (expand and check the enabled check box).

5) The default I2C lines on the Nucleo board are assigned to PB6/PB7. I need to re-assign these so that it would be easier to attach it to the headers. Looking at the board specs, the alternate configuration for it is PB8/PB9 which is mapped to the arduino CN5 header. To do this, Ctrl-Click on the original PB6/PB7 and notice that the alternate ports (highlighted) are shown on the CubeMX gui. To re-assign, simply right click on the alternate port and click "I2C1_CLK" or "I2C1_SDA" for PB8 and PB9. Your setup should be exactly like the image above (where ports are already re-assigned).

6) Next we need to setup our clock, select the "Clock Configuration" tab. The default configuration for CubeMX assumes that our board will be powered by an external oscillator. But this is not the case, there's no crystal assigned to C33 and C34 on the nucleo board, so our clock source will entirely be from the internal clock oscillator. To configure it, configure your setup similar to the setup below:


Note that in the above setup, I choose the internal oscillator (as source to the PLL) to provide the 84Mhz clock to our system.

7) Before we generate the project, we need to review the current configuration. Go ahead and click the configuration tab. Notice the settings that were enabled.

8) Another thing worth noting is the Nested Vector interrupt configuration (NVIC), click on the NVIC button while on the configuration screen and you should be taken to the screen shown below:


This screen allows us to configure interrupts and their priority. Right now leave this configuration as it is (only system tick timer is enabled).

9) We can now go ahead and generate the project files. Do this by saving the project first (diskette icon), the pop-up dialog will prompt you for the location where you want the project to be stored. 

10) Finally generate the project files via the (gear icon) generate button. The pop-up dialog will prompt you for the location where the project will be stored and the type of project files that will be generated. In my case, this would be EWARM (Embedded Workbench). 

11) Open the project in embedded workbench, we'll first try to examine the settings that are generated by CubeMX and make necessary adjustments.




12) In the project window, click on the generated stm32f4xx_hal_msp.c file. This file contains callbacks used by the HAL to initialize/setup user defined ports. Navigate into the HAL_I2C_MspInit() function:

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(hi2c->Instance==I2C1)
  {
    /* Peripheral clock enable */
    __I2C1_CLK_ENABLE();
  
    /**I2C1 GPIO Configuration    
    PB8     ------> I2C1_SCL
    PB9     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  }

In the above code, change the GPIO_InitStruct.Pull to GPIO_PULLUP, we need a pullup resistor to drive both lines of the I2C bus. We also need to change the speed to GPIO_SPEED_HIGH.

13) In the main.c file, we need to create a user thread that would count and output the count value to our MCP23017 chip at address 0x21. First create a function prototype at the beginning of the main.c file:

static void StartThread(void const * argument);
static void CounterThread(void const * argumnet); /* For our led counter */

We then define the function, insert the line of code below right after the StartThread function:

static void CounterThread(void const * argument) {

  /* USER CODE BEGIN 5 */
  uint8_t count = 0;
  char data[2];
  
  /* We need to send data to the direction 
   * register first to setup our led counter as output
   */
  data[0] = 0x00;
  data[1] = 0x00;
  HAL_I2C_Master_Transmit(&hi2c1, (uint16_t) (0x21 << 1), (uint8_t *) data, 2, 1000);
 
  /* Infinite loop */
  data[0] = 0x14;
  for(;;)
  {
    data[1] = count;
    HAL_I2C_Master_Transmit(&hi2c1, (uint16_t) (0x21 << 1), (uint8_t*) data, 2, 1000);
    count ++;
    osDelay(100);
  }
  /* USER CODE END 5 */ 
}

The function HAL_I2C_Master_Transmit() function sends the data to the I2C bus, it takes the handle to the i2c bus (hi2c1), the device address - shifted one bit to the left since we use 7 bit addressing mode, the data to write and the size of the data to write. The last parameter is the timeout value in ms (in this case, I use 1000).

14) We then register our thread in FreeRTOS, in the main() function, add the following lines of code before the scheduler is started (otherwise our thread will never be executed).

  /* Code generated for FreeRTOS */
  /* Create Start thread */
  osThreadDef(USER_Thread, StartThread, osPriorityNormal, 0, 2 * configMINIMAL_STACK_SIZE);
  osThreadCreate (osThread(USER_Thread), NULL);

  /* Create Count led thread */
  osThreadDef(USER_Thread1, CounterThread, osPriorityNormal, 0, 2 * configMINIMAL_STACK_SIZE);
  osThreadCreate (osThread(USER_Thread1), NULL);
  
  /* Start scheduler */
  osKernelStart(NULL, NULL);


osThreadDef() defines our thread taking into its arguments the name of our thread function (CounterThread), the thread priority and the name of the thread handle (USER_Thread1).

15) Compile the project and check for errors. You can right click on Embedded Workbench's project and click "Build All".

16) Before we could transfer or debug our design on the Nucleo board, we need to configure our debugging options. Right click on the project and select "Options" :


Make sure on the ST-Link category, SWD is selected (unless you have the expensive JTAG cable) and set the CPU clock frequency.

17) You should now be able to debug and download the file to the Nucleo board. 





No comments: