2016年2月14日星期日

STM32 Nucleo-F746ZG开发板入门指南

全新的Nucleo-F746终于到手上了,这是ST的Nucleo-144产品线的第一个开发套件,而且可以说,如果考虑到一个正品的Arduino DUE价格超过40美元且其MCU只是Cortex-M3内核,Nucleo-F746的街机的价格(大约23美元)大概是一个创客在市场上可以找到的最好的开发套件。

nucleo-144-vs-nucleo-64.jpg

和经典的Nucleo-64相比,该开发板更让人印象深刻:更宽并提供了很多更标准的外设。现在用户LED有三个(红、蓝和绿),并且电源指示灯变成了绿色。最大的变化时,该开发板配备了LAN接口、磁力计和SMSC物理层收发器PHY。这意味着,我们可以使用运行在216MHz的功能强大的Cortex-M7内核开始开发物联网应用。和ST的以一个低价的F7开发板STM32F746-Discovery相比,它并没有提供LCD显示屏。但是,Nucleo-144通过Zio接口引出了大多数MCU的信号I/O,而Discovery-F7仅有少量的信号布线到Arduino式的连接器。如果你想要使用Nucleo-144开发一个定制产品,那么它是最好的选择。

在这篇文章中,我将一步步展示开始使用这个奇妙般的硬件。我们将使用LwIP栈在Nucleo上创建一个简单的Web服务器。该Web应用允许我们通过使用bootstrapjQuery和Nucleo板上的LED及用户按键进行交互。以下视频展示了HTTP服务器时如何工作的。



第1步:创建新的Eclipse工程

第一步是创建一个新的Eclipse工程。最近Liviu Ionescu已经更新了工程模板,增加了对STM32F7微控制器的支持。然而,在这里我们可以使用另一种方法:首先建立一个基本的ARM C/C++工程,然后我们将CubeMX生成的工程导入到里面,它简化了很多MCU的配置过程。此外,它允许我们快速导入LwIP栈,该栈是用来开发STM32微控制器的TCP/IP应用程序。
转到File->New->C Project,然后选择Hello World ARM Cortex-M C/C++ Project项。给工程起个你喜欢的名字。点击Next
Schermata-2016-01-21-alle-12.47.23.png
在下一步中从 Processor core项中选择Cortex-M7。在Flash size字段输入1024,在RAM size字段输入320,如下图所示。
Schermata-2016-01-21-alle-12.50.05.png
点击Next。在接下来的步骤中,除了在Vendor CMSIS字段中写入stm32f7xx,其他的都保持不变。
Schermata-2016-01-21-alle-12.53.43.png
全部采用标准选项,完成工程向导。

现在,在我们导入CubeMx工程到该Eclipse工程之前,最好修改ldscripts/mem.ld文件。默认情况下,GNU ARM插件分配0x00000000作为FLASH存储的首地址,而在所有的STM32微控制器中,内部的FLASH被映射到0x08000000。因此,从Eclipse IDE中打开ldscripts/mem.ld文件,并且在MEMORY部分的第一行按照如下所示编写:
...
MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 320K
...
OK。下一个重要的步骤是在导入CubeMX之前,关闭Eclipse工程。

第2步:创建新的CubeMX工程
现在,我们需要在CubeMX创建一个新的工程。首先,确保使用CubeMX的最新版本,在写本篇教程时最新版本是4.12。这是支持Nucleo-144产品线的第一个版本,所以至少有这个版本是很重要的。
启动CubeMX,并创建新的工程。在New Project向导中,选择Board Selector选项卡,并且在列表中选择Nucleo-F746ZG。确保勾选“Initialize all IP with their default mode”。点击OK,等待工程生成。
OK。现在我们需要启用两个中间件栈:FreeRTOS和LwIP。在IP Tree面板(CubeMX左侧的树视图),启用FREERTOS和LWIP模块,如下图所示。
Schermata-2016-01-22-alle-08.28.53.png
现在转到CubeMX的Configuration部分,并点击LWIP按钮。在DHCP部分禁止DHCP模块,然后分配一个静态IP地址,如下图所示。
Schermata-2016-01-22-alle-08.30.51.png
IP地址必须与你的网络配置相匹配。点击OK按钮。OK,我们已经准备好生成工程代码。转到菜单 Project->Generate Code。选择你喜欢的项目名称(我命名为nucleo-f7),但要确保在Toolchain/IDE选择的是SW4STM32,如下图所示。
Schermata-2016-01-22-alle-08.34.15.png
点击OK按钮。


第3步:在Eclipse中导入CubeMX项目

现在我们可以将使用CubeMX生成的工程导入到创建的Eclipse工程里面。为了做到这一点,我们将要使用我制作的一个工具,名字叫CubeMXImporter,可以在我的github账户获得。该工具基于Python 2.7lxml库。可以在这里找到所有的安装指令。CubeMXImporter安装完成后,打开终端命令行,并输入以下指令:
  1. $ python cubemximporter.py <path-to-eclipse-project-folder> <path-to-cubemx-project>
复制代码
例如,如果你将Eclipse工程起名为tm32-nucleo144-f7,且CubeMX工程起名为nucleo-f7,那么该指令需要通过以下方式调用:
  1. $ python cubemximporter.py <path-to-eclipse-workspace>/stm32-nucleo144-f7 <path-to-cubemx-output-directory>/nucleo-f7
复制代码
CubeMXImporter将会自动导入工程框架、CubeF7以及中间件库(LwIP和FreeRTOS)到Eclipse工程里面。

好了,现在我们可以在Eclipse中重新打开该工程。打开后,在Project Explorer视图的工程名上右击并选择Refresh项。
通过使用建议的CubeMXImporter工具,现在我们需要手动做一些事情。首先,我们需要为Cortex-M7设定正确的FPU配置。转到Project Settings->C/C++ Build->Settings->Target Processor。在Float ABI字段选择"FP instructions (hard)",并且在FPU Type字段选择“fpv5-sp-d16”,如下图所示:
Schermata-2016-01-22-alle-08.47.47.png
点击OK。现在我们需要选择正确的FreeRTOS算法。在Project Explorer视图中,转到Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang文件夹。选择文件heap_1.cheap_2.cheap_3.cheap_5.c。右击并选择Resource Configurations->Exclude from build...。选择所有的工程配置,然后点击OK。现在我们可以编译该应用程序并且使用OpenOCD下载到NUCLEO板。
如果一切都正确的话,就可以ping到Nucleo开发板。


第4步:创建一个简单的HTTP服务器

现在,我们准备使用LwIP建立一个简单的HTTP服务器。我们需要做的第一件重要的事情是修改stm32f7_xx_it.c里面的SysTick_Handler()函数里面的内容。一个已知的Bug会引起HAL_IncTick() 调用被省略。所以,我们需要按照以下方式进行修改:
  1. void SysTick_Handler(void) {
  2.   HAL_IncTick();
  3.   osSystickHandler();
  4. }
复制代码
main() 的代码非常简单,我们首先初始化硬件,然后使用StartDefaultTask()函数创建一个新的线程。
  1. int main(void) {
  2.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  3.   HAL_Init();

  4.   /* Configure the system clock */
  5.   SystemClock_Config();

  6.   /* Initialize all configured peripherals */
  7.   MX_ADC1_Init();
  8.   MX_GPIO_Init();
  9.   MX_USART3_UART_Init();
  10.   MX_USB_OTG_FS_PCD_Init();

  11.   /* Create the thread(s) */
  12.   /* definition and creation of defaultTask */
  13.   osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  14.   defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

  15.   /* Start scheduler */
  16.   osKernelStart();
  17.   
  18.   /* We should never get here as control is now taken by the scheduler */
  19.   while (1);
  20. }
复制代码
StartDefaultTask()函数只是初始化LwIP栈以及启动HTTP服务器。
  1. void StartDefaultTask(void const * argument) {
  2.   /* init code for LWIP */
  3.   MX_LWIP_Init();

  4.   http_server_netconn_init();
  5.   /* Infinite loop */
  6.   for(;;)  {
  7.     osThreadTerminate(NULL);
  8.   }
  9. }
复制代码
有关HTTP服务器是如何工作的介绍并不在本片教程的范围:它需要专门的文章介绍。所有的底层工作有http_server_serve()函数完成,该函数仅仅扫描一些已知字符串的HTTP请求。剩余的工作由jQuery执行。
  1. void http_server_serve(struct netconn *conn) {
  2.   struct netbuf *inbuf;
  3.   err_t recv_err;
  4.   char* buf;
  5.   u16_t buflen;

  6.   /* Read the data from the port, blocking if nothing yet there. 
  7.    We assume the request (the part we care about) is in one netbuf */
  8.   recv_err = netconn_recv(conn, &inbuf);
  9.   
  10.   if (recv_err == ERR_OK)
  11.   {
  12.     if (netconn_err(conn) == ERR_OK) 
  13.     {
  14.       netbuf_data(inbuf, (void**)&buf, &buflen);
  15.     
  16.       /* Is this an HTTP GET command? (only check the first 5 chars, since
  17.       there are other formats for GET, and we're keeping it very simple )*/
  18.       if ((buflen >=5) && (strncmp(buf, "GET /", 5) == 0))
  19.       {
  20.               if (strncmp((char const *)buf,"GET /index.html",15)==0) {
  21.                       netconn_write(conn, (const unsigned char*)index_html, index_html_len, NETCONN_NOCOPY);
  22.               }
  23.               if (strncmp((char const *)buf,"GET /led1", 9) == 0) {
  24.                       HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
  25.               }
  26.               if (strncmp((char const *)buf,"GET /led2", 9) == 0) {
  27.                       HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
  28.               }
  29.               if (strncmp((char const *)buf,"GET /led3", 9) == 0) {
  30.                       HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
  31.               }
  32.               if (strncmp((char const *)buf,"GET /btn1", 9) == 0) {
  33.                       if(HAL_GPIO_ReadPin(User_Blue_Button_GPIO_Port, User_Blue_Button_Pin) == GPIO_PIN_SET)
  34.                               netconn_write(conn, (const unsigned char*)"ON", 2, NETCONN_NOCOPY);
  35.                       else
  36.                               netconn_write(conn, (const unsigned char*)"OFF", 3, NETCONN_NOCOPY);
  37.               }
  38.               if (strncmp((char const *)buf,"GET /adc", 8) == 0) {
  39.                       sprintf(buf, "%2.1f °C", getMCUTemperature());
  40.                       netconn_write(conn, (const unsigned char*)buf, strlen(buf), NETCONN_NOCOPY);
  41.               }
  42.       }
  43.     }
  44.   }
  45.   /* Close the connection (server closes in HTTP) */
  46.   netconn_close(conn);
  47.   
  48.   /* Delete the buffer (netconn_recv gives us ownership,
  49.    so we have to make sure to deallocate the buffer) */
  50.   netbuf_delete(inbuf);
  51. }
复制代码
webpages/index.html包含HTML代码。该代码使用UNIX指令xxd转换成C字符串:
  1. xxd -i index.html > index.h
复制代码
并且嵌入到最终的固件(STM32F7有足够的空间来保存整个的HTML文件)。该网页可以通过 http://<nucleo-ip>/index.html访问。

没有评论:

发表评论