打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
手把手教你打造最简STM32F0 USB开发板
userphoto

2023.07.17 黑龙江

关注
想学STM32,不知道从哪开始的有木有?想学ARM单片机,嫌买开发板、调试器费钱的有木有?买了STM32开发板没有资料不会玩,放在那里吃灰的有木有?买了开发板,照着例子跑通了几个程序,依然一头雾水的有木有? 
我是个非常抠门的人,搞DIY也省得很——一切从简。(太复杂了的搞不定,软件硬件都是如此) 所以正在玩的STM32也简化到底了,有兴趣的看看吧。
这是刚完成的STM32F072 USB开发板,使用48脚LQFP的STM32F072C8T6,也可以使用其它封装兼容的带USB型号,甚至是M3系的STM32F103C8T6这种。上半年从论坛买了块STM32F091 Nucleo, 但是不带USB,所以为了学习USB自己做一块咯。下面是电路图,除了一片1117 3.3V LDO,外围器件少到极致了吧,晶振不用的话是可以不装的。板子可以直接通过 USB mini口供电。外围引出的插针有一路 SPI, 一路 I2S, 一路 UART, 一路 I2C, 一路 8-bit GPIO, 一路 UART/I2C共用,以及几个零星的GPIO。这些已方便开发简单的USB设备了。
 
PCB layout 示意图
 
 
 
‍‍好,STM32F072 10块钱以内就可以搞定,整个开发板成本很低了吧。如果你有ST-Link, 或者是带有ST-Link的STM32 Discovery/Nucleo开发板,用SWD调试线连上就可以下载程序了。如果没有ST-Link, 还可以从串口下载程序,只需要把BOOT0跳线接上即可,因为STM32内带了Bootloader.  如果连串口线都没有?呵呵,要是像F072这样带USB的,还可以从USB直接下载,别的硬件也省了,怎么样,够简吧?
OK,来写第一个测试程序:定时控制LED闪烁。
  1. #include "stm32f0xx.h"

  2. int main(void)

  3. {

  4.     RCC->AHBENR |= RCC_AHBENR_GPIOAEN;  // enable GPIO port A & B clock

  5.     GPIOA->MODER = GPIO_MODER_MODER8_0; // PA8 as general output (LED)

  6.     RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;     // enable basic timer 6

  7.     TIM6->PSC = 9999;       // prescaler

  8.     TIM6->ARR = 399;        // auto reload value

  9.     TIM6->CR1 = TIM_CR1_URS|TIM_CR1_CEN;    // start counter

  10.     while(1)

  11.     {

  12.         static char a=0;

  13.         if(TIM6->SR & TIM_SR_UIF)   // check if overflow

  14.         {

  15.             TIM6->SR &= ~TIM_SR_UIF;    // clear flag

  16.             if(a==0)

  17.             {

  18.                 GPIOA->BSRR = (1<<8);

  19.                 a=1;

  20.             }

  21.             else

  22.             {

  23.                 GPIOA->BRR = (1<<8);

  24.                 a=0;

  25.             }

  26.         }

  27.     }

  28. }

上面这个程序所做的事情,先是初始化GPIO, 设置PA8为输出口(板子上连了一个LED),然后是设置定时器Timer 6, 这是一个自动重装的计数器,我把它调到0.5秒中溢出一次。在下面的循环里面,就是检测溢出标志,然后切换LED的亮和灭状态。学过C语言的,都应该看得懂;至于RCC, GPIOA, TIM6 这几个结构指针的定义,都在#include的头文件里面,这是和硬件相关的,具体请查阅"RM0091 STM32F0x1/STM32F0x2/STM32F0x8 Reference Manual"编程手册。
如何编译上面这个 C 程序,且听下回分解。这里暂且假定编译成功了,得到一个 HEX 文件,也就是要烧写的二进制代码。
如果你是使用KEIL, IAR等集成开发环境,那么用自带的烧写工具就可以进行写入了。如果是像我cruelfox这样追求精简,仅使用GCC命令行工具的,就需要再找下载程序用的软件了。
如果是使用ST-Link,可以使用ST自己的STVP (Visual Programmer),这个东东在ST网站上可以下载到,不过是包含在九十兆左右的一个大包"ST Toolset"里面。(下载URL http://www.st.com/web/catalog/tools/FM147/CL1794/SC961/SS1533/PF210568 )这个软件的界面是这个样子的:
 
主菜单上面 Erase, Program, Verify, Read 功能很明了了,File-->Open可以加载HEX文件。第一次运行STVP的时候,要选择ST-LINK调试器,和 SWD接口。
如果没有ST-Link, 使用串口下载的话,需要"Flash Loader Demostrator"软件,这个也可以从ST网站直接下载(URL http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/demo_and_example/stsw-mcu005.zip)。下载前要把BOOT0跳线接上,使STM32进入Bootloader模式,USART1连接到PC的串口(我用的是FT232RL USB转串口),把MCU加电。运行软件,界面是这样的:
 
选择串口,然后点"Next",如果成功连上了,则界面变成下面这样
 
点"Next"继续
 
这时已显示出识别出的STM32型号,点"Next"到下一步进行具体的操作。
 

OK, 下载HEX,擦除,上载(读Flash内容) 功能都一看就明白了吧。
第三种下载方式,从USB,需要ST的"DFUSe Demo"软件,也是从ST网站下载的(URL http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/demo_and_example/stsw-stm32080.zip)。也需要把BOOT0跳线接上,还必须连接USB口,然后PC提示找到了新硬件。安装好驱动以后,再启动软件,界面如下:
 

不过现在不能把HEX文件直接写入,而需要先生成dfu文件,使用一起安装得到的"DFU File Manager"程序,从HEX生成dfu.
 

至于 VID, PID 我还是保留和原来的一致,不然得重新安装驱动(为什么要使用DFU文件我还没理解清楚)。得到dfu文件就可以用上面的软件烧写了。
怎么样,我的开发板够精简吧?
STM32F0xx 系列是ARM Cortex-M0架构,地址空间32位,也就是4G Bytes的访问范围。数据和代码使用同一编址,下图是地址空间的布局:
 


实际上单片机用到的资源很少,地址空间大部分都没有内容。我使用的STM32F072C8T6带有64kB的Flash ROM, 16kB的SRAM,起始分别是 0x08000000 和 0x20000000. (由于有硬件映射功能,在0x00000000也就是最低地址,还可以访问ROM或者RAM的内容). 单片机片上外设的寄存器,则分布在更高的地址空间。读写这些寄存器,在CPU看来和读写内存(RAM)操作是一样的。
所以,C语言访问设备寄存器,和访问内存中的一个变量一样。只要知道寄存器的地址,通过一个指针访问就可以实现读写。上一贴子我的程序中引用了 RCC, GPIOA, TIM6 这三个(结构)指针,它们的值(也就是地址)以及类型(代表访问的内容)定义在 stm32f0xx.h 这个头文件中。因为设备寄存器太多了哇,如果每一个都定义一个指针就太烦琐了,所以把按功能划分定义成组,每组用一个C语言的结构类型表示,写起来也更清晰。而寄存器里面的位描述也可以定义成一些宏,在读程序的时候就知道是什么意思了。如果有兴趣,可以把 stm32f0xx.h 文件和STM32F0的手册对照着阅读。
好,假设已经熟悉寄存器操作了,知道怎么配置寄存器实现想要的功能,那么就可以写C程序让STM32工作了。现在需要一个工具来将C程序翻译成机器代码——编译器,或者是叫做工具链(Tool chain)。Keil MDK-ARM 或者 IAR-EWARM 开发环境都带有各自的编译器,不过我更偏向于用开源的GCC-ARM. 在launchpad.net上可以下载到编译好的arm-gcc工具链zip包,将它解压缩,加到PATH里面就可以直接用了,很方便(很精简吧)。
OK,现在来编译上面那个mini.c文件,命令行:
arm-none-eabi-gcc -c -Os -mcpu=cortex-m0 -mthumb mini.c
gcc的参数 -c 是表示仅编译,-Os 是优化代码大小,-mcpu=cortex-m0 -mthumb 是指定指令集的,因为ARM有不同的版本。对了,include的头文件还没弄到呢。要编译通过需要把 stm32f0xx.h 这个文件找来。我的建议是下载ST提供的 "STM32F0x2 USB FS Device Library" 程序库(URL http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware/stsw-stm32092.zip),把里面需要的头文件等等扒出来。在 stm32f0xx.h 中还包含了另外几个头文件,一并弄出来放到工程目录下。
如果编译成功,将得到 mini.o 目标文件。可以用 arm-none-eabi-objdump -S mini.o 反汇编看看翻译成什么代码了。
  1. E:\arm\test072\mini>arm-none-eabi-objdump -S mini.o

  2. mini.o:     file format elf32-littlearm

  3. Disassembly of section .text.startup:

  4. 00000000 <main>:

  5.    0:   4b16            ldr     r3, [pc, #88]   ; (5c <main+0x5c>)

  6.    2:   2280            movs    r2, #128        ; 0x80

  7.    4:   6959            ldr     r1, [r3, #20]

  8.    6:   0292            lsls    r2, r2, #10

  9.    8:   430a            orrs    r2, r1

  10.    a:   b510            push    {r4, lr}

  11.    c:   2180            movs    r1, #128        ; 0x80

  12.    e:   615a            str     r2, [r3, #20]

  13.   24:   851a            strh    r2, [r3, #40]   ; 0x28

  14.   26:   2290            movs    r2, #144        ; 0x90

  15.   28:   32ff            adds    r2, #255        ; 0xff

  16.   2a:   62da            str     r2, [r3, #44]   ; 0x2c

  17. 完整代码请点击阅读原文

  18.   50:   6184            str     r4, [r0, #24]

  19.   52:   1c0a            adds    r2, r1, #0

  20.   54:   e7ee            b.n     34 <main+0x34>

  21.   56:   8504            strh    r4, [r0, #40]   ; 0x28

  22.   58:   2200            movs    r2, #0

  23.   5a:   e7eb            b.n     34 <main+0x34>

  24.   5c:   40021000        .word   0x40021000

  25.   60:   40001000        .word   0x40001000

  26.   64:   0000270f        .word   0x0000270f

  27.   68:   00000000        .word   0x00000000


如上,其实里面就一个main函数。但是 main 的入口地址还没有确定,而且它还使用了一个static型的内存变量,地址也还没有确定。可以用 arm-none-eabi-nm mini.o 来查看模块里面的全局符号表:
  1. E:\arm\test072\mini>arm-none-eabi-nm mini.o

  2. 00000000 b a.4686

  3. 00000000 T main

那么,怎么让程序放到ROM中合适的地址,并运行呢?如果熟悉C语言编程就知道还有一步——链接,才能确定符号的地址。但是,到目前为止我们还没有告诉GCC地址的布局,也就是RAM从哪里开始,代码放在哪里。因为ARM的器件很多,这并不是统一的,所以需要提供一些信息给链接程序。具体地,需要一个Linker Script, 可以从软件包中找到 STM32F072C8_FLASH.ld (或者用近似的来修改得到)
  1. /*

  2. *****************************************************************************

  3. **  File        : stm32_flash.ld

  4. *****************************************************************************

  5. */

  6. /* Entry Point */

  7. ENTRY(Reset_Handler)

  8. /* Highest address of the user mode stack */

  9. _estack = 0x20003FFF;    /* end of RAM */

  10. 完整代码请点击阅读原文

  11.   /DISCARD/ :

  12.   {

  13.     libc.a ( * )

  14.     libm.a ( * )

  15.     libgcc.a ( * )

  16.   }

  17.   .ARM.attributes 0 : { *(.ARM.attributes) }

  18. }


OK,下面就是链接了,使用命令 arm-none-eabi-ld mini.o -Le:\arm-2014q3\arm-none-eabi\lib\armv6-m -Le:\arm-2014q3\lib\gcc\arm-none-eabi\4.8.4\armv6-m -T STM32F072C8_FLASH.ld -o mini.elf
这里面 -L 参数是添加标准库文件的搜索路径,虽然暂时并没有用到C标准库里面的东西,但是Linker Script里面引用了标准库文件。-o 指定输出的目标文件。这么就快要得到最终的机器码了,不过好象还缺少了什么……
arm-none-eabi-ld: warning: cannot find entry symbol Reset_Handler; defaulting to  08000000
linker给了一个警告:找不到入口地址 Reset_Handler 的值,设成了默认 0x08000000. 下面再用objdump -S反汇编看一下
  1. E:\arm\test072\mini>arm-none-eabi-objdump -S mini.elf

  2. mini.elf:     file format elf32-littlearm

  3. Disassembly of section .text:

  4. 08000000 <main>:

  5. 8000000:       4b16            ldr     r3, [pc, #88]   ; (800005c <main+0x5c>)

  6. 8000002:       2280            movs    r2, #128        ; 0x80

  7. 8000004:       6959            ldr     r1, [r3, #20]

  8. 8000006:       0292            lsls    r2, r2, #10

  9. 8000008:       430a            orrs    r2, r1

  10. 800000a:       b510            push    {r4, lr}

  11. 完整代码请点击阅读原文

  12. 8000050:       6184            str     r4, [r0, #24]

  13. 8000052:       1c0a            adds    r2, r1, #0

  14. 8000054:       e7ee            b.n     8000034 <main+0x34>

  15. 8000056:       8504            strh    r4, [r0, #40]   ; 0x28

  16. 8000058:       2200            movs    r2, #0

  17. 800005a:       e7eb            b.n     8000034 <main+0x34>

  18. 800005c:       40021000        .word   0x40021000

  19. 8000060:       40001000        .word   0x40001000

  20. 8000064:       0000270f        .word   0x0000270f

  21. 8000068:       20000000        .word   0x20000000


现在 main() 被放到ROM最开始去了,这好象是对的?如果了解ARM Cortex-M0下就知道这样错了,因为最开始应该是中断向量表。我们还没有编写Linker Script中的 .isr_vectors 段的内容。而且,一上来初始化堆栈指针等工作都没有做就直接运行 main() 了也不合适吧?还缺少了初始化代码。
在软件包中搜刮一个 startup_stm32f072.s 汇编文件
  1. /**

  2.   ******************************************************************************

  3.   * @file      startup_stm32f072.s

  4.   * @author    MCD Application Team

  5.   ******************************************************************************

  6.   */

  7.   .syntax unified

  8.   .cpu cortex-m0

  9.   .fpu softvfp

  10.   .thumb

  11. .global g_pfnVectors

  12. .global Default_Handler

  13. 完整代码请点击阅读原文

  14.   .weak USART2_IRQHandler

  15.   .thumb_set USART2_IRQHandler,Default_Handler

  16.   .weak USART3_4_IRQHandler

  17.   .thumb_set USART3_4_IRQHandler,Default_Handler

  18.   .weak CEC_CAN_IRQHandler

  19.   .thumb_set CEC_CAN_IRQHandler,Default_Handler

  20.   .weak USB_IRQHandler

  21.   .thumb_set USB_IRQHandler,Default_Handler

  22. /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


原来是这样,中断向量表在这里进行了描述,还有设置堆栈,初始化全局变量的代码,然后跳转到 main 执行。好了,这样就该差不多了。这个汇编程序是GNU AS的语法,可以用 arm-none-eabi-gcc 来直接汇编
arm-none-eabi-gcc -c startup_stm32f072.s
链接两个目标模块
arm-none-eabi-ld mini.o startup_stm32f072.o -Le:\arm-2014q3\arm-none-eabi\lib\armv6-m -Le:\arm-2014q3\lib\gcc\arm-none-eabi\4.8.4\armv6-m -T STM32F072C8_FLASH.ld -o mini.elf
最后转换出一个 HEX 文件
arm-none-eabi-objcopy -Oihex mini.elf mini.hex
可以进行烧写了。
我这个是最简化的例子,使用最简化的软件工具,不过已经包含了基本的C语言框架。
第一个USB工程例子:USB存储盘。因为手边没有SPI或I2C接口的Flash, 就权且用单片机的RAM虚拟一下吧。这个"U盘"容量标成160kB, 实际上有效的存储只有10kB,用一点扇区映射的技巧骗过操作系统,当然存放文件只能几kB了。

ST官方提供了一个 STM32F0 的USB固件库,(
URL http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware/stsw-stm32092.zip),包括了硬件库、核心库和类库的C头文件和源文件。我cruelfox琢磨了一整天,还是没搞清楚怎么调用这些函数,只好照着个Example改吧。

于是就用MassStorage类的例子来下手了,这个目录里面的主文件 app.c 非常简单
  1. /**

  2.   * @file    app.c

  3.   */ 

  4. /* Includes ------------------------------------------------------------------*/ 

  5. #include  "usbd_msc_core.h"

  6. #include  "usbd_usr.h"

  7. USB_CORE_HANDLE  USB_Device_dev ;

  8. int main(void)

  9. {

  10.   USBD_Init(&USB_Device_dev,

  11.             &USR_desc, 

  12.             &USBD_MSC_cb, 

  13.             &USR_cb);

  14.   while (1)

  15.   {

  16.   }


  17. #ifdef USE_FULL_ASSERT

  18. void assert_failed(uint8_t* file, uint32_t line)

  19. {

  20.   while (1)

  21.   {}

  22. }

  23. #endif

一切USB相关的东西都在 USBD_init() 这个函数里面了。追查源代码,可以发现这个函数调用其实是向库函数提供了一堆回调函数的接口,也就是说写一些子程序,让驱动程序在需要的时候调用。这样开发USB的我们就不用去管中断什么的了,要做的就是实现各种USB的请求——其实程序库里面已经把大部分的请求应答都实现了,而具体的类库(比如我这里用的MassStorage类)又处理了剩余的大部分,所以只剩下功能性的需要自己写。
在 usbd_desc.c 这个程序里面可以看到描述符是怎么创建和返回的
  1. /**

  2.   ******************************************************************************

  3.   * @file    usbd_desc.c

  4.   * @author  MCD Application Team

  5.   * @version V1.0.0

  6.   * @date    31-January-2014

  7.   * @brief   This file provides the USBD descriptors and string formating method.

  8.   ******************************************************************************

  9.   * @attention

  10.   *

  11.   * <h2><center>© COPYRIGHT 2014 STMicroelectronics</center></h2>

  12.   *

  13.   * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");

  14.   * You may not use this file except in compliance with the License.

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
VSCode自制的IDE编译多个源文件
GCC编译优化指南
[原创]GCC编译器选项及优化提示
编译器的编译选项解析
程序员情感ORG: 信息“gdb(gcc 的debug 工具
深入理解计算系统」从Hello World开始
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服