首页 技术 正文
技术 2022年11月8日
0 收藏 379 点赞 2,105 浏览 9937 个字

我们需要平台

如果说,SharePoint 的价值之一在于提供了几乎开箱即用的 innovation 环境,那么,智能设备的开发平台也一样。不必每次都从头开始,所以需要固定的工作室和开发平台作为创新的起点,这样就会每次比从零开始“高一点点”。

当然,这里不是没有纠结的。平台毕竟不是最终的产品,平台太弱固然难以支撑创新,但平台太强则臃肿和僵化同样也会限制创新:面对成百上千的类型、接口的时候,即使做一个小玩意儿也要学上一年半载,任何人都会畏惧的。有那个时间,不如自己写一个出来了。所以成功的开发平台如 jQuey 和 jQuery UI 是拆开的,Bootstrap 是可以按模块自定义下载的。

与 SharePoint 在企业协作领域的一家独大不同,智能控制设备的平台可谓五花八门,这就有点儿像 python 世界的 web framework。看似很繁荣,但是缺少标准,哪个都难以让人下决心深入钻研(貌似现在有往 django 靠拢的趋势,这是件好事)。所以,在试过了网上各种开发板以后,Jony 下了决心要搭建自己的开发平台,满足自己的实际需要。

在这里,我想起来《西游记》里面的故事:孙悟空学艺归来回到花果山以后,第一件事情就是去找一件称手的兵器,然后几番周折,最终得到了如意金箍棒。认真对待技术的人,搭建自己的开发平台,好像也有点儿这个意思。从这个角度说,《西游记》真的不是一本简单的魔幻现实主义小说。

智能控制设备的开发平台,有 2 个大的技术决策:1,用什么 MCU(微控制器);2,用什么操作系统。

MCU 为什么是 STM32?

主流的 MCU 阵营有这么几个:

  • ST 意法半导体
    便宜、高性能!有固件库可以方便开发,资料多。
    STM32F10x 系列,样片在 10 元左右一个,20K 内存、72MHz 主频、各种外设,已经可以做很多事情了。更吸引人的是,他们家的控制器的固件库有通用性,熟悉了一个产品线的开发之后,比较容易能够切换到其它的产品线。
    说到通用性,所有基于相同核心的 CPU 其实都在某种程度上相通。如手机 ARM 内核。甚至在我看来,只要你是基于时钟(数字脉冲)的、可以通过寄存器读写数据与指令的累加器,都是相通的,当然,实际掌握起来难度也是有的。
  • LPC 恩智普
    43xx 系列是双核的控制器!性能那是没话说,高端的 LPC 4370 ADC 采样率是 8千万次/秒,相比之下同是 ARM cortex-m4 内核的 ST F407 系列只有 7百万次/秒。问题就是太 TMD 贵了,性价比不如 ST。
  • Freescale 飞思卡尔
    不熟,掠过。
  • AVR
    开发烧录程序的时候容易锁住芯片,变态!而且贵,真的贵。但是,大名鼎鼎的 Arduino 就是用的他们家的 MCU,奇了怪了。不过,如果你是从 51 系列转过来的话,会有惊艳的感觉。 
  • 51 系列
    上个世纪的恐龙,慢、耗电。只不过架构简单,所以还在被大量的当作入门学习用途。

ARM 9, 11 系列(包括树莓派)已经不是简单的控制器了(那就是小电脑),太过庞大,作为微处理器还行,仅仅拿来做控制器则太过复杂、而且耗电。

而且都用到那个级别了,我为什么不弄个微型 PC 呐?!直接上 .net 也好拯救我几个脑细胞。

补充:虽然 LPC 43xx 那么贵,那么复杂,那么没有性价比,但不知道为什么,我还是经常会想起它。

操作系统为什么是 rt-thread?

复杂的控制器平台上面当然要有操作系统啦!

20k 字节的内存那显然是跑不起 windows 的(DOS 都不行),且架构也不同。但是,微控制器用的开源操作系统已经有很多了,够用了。

备选的有这么几个:

  • FreeRTOS
    太臃肿。已经到了 8.0 了,估计我是没有机会看完它的源码了(这种智能控制系统,看不完源码谁敢用啊?!),简单试用过以后,pass。
  • rt-thread
    中国人自己开发的,稳定版本是 1.2.1,有希望看完源码。精简、靠谱,自带一个叫做 finsh 的片上调试工具,非常实用。各种信号量、互斥锁、邮箱、事件等线程协同功能都有。Jony 去张江见过开发团队,比较放心。缺点是:文件和代码的组织方式和我的习惯有点儿“出入”,后面谈到配置工程的时候会说到。
    需要注意的是,rt-thread 2.0 版本的设计思想和 1.2 的完全不同,将会把 linux 纳入进来,是的,不是在 linux 里面嵌入 rt-thread,而是把 linux 嵌入到 rt-thread 里面!想法是强大的,但对我的应用场景来说,有点儿太大了,所以呢,我还是用 1.2.1。
  • RTX
    要用 Keil 开发才有,而且功能实在太少,就是简单的轮询调度任务,pass。不过,配合 Keil 调试起来真的是很方便,参考我另一篇文章:搭建使用 RTX51-Tiny 的 C51 Keil 项目环境
  • ucOS II
    看了网友的分析,比 rt-thread 占资源,且功能还少,pass。

相比“裸机”跑代码来说,在微控制器上面跑操作系统,设计和实现的难度瞬间上去了,但它能显著降低应用的难度,这个扎实的地基虽然很难挖,但能支撑百层的高楼。凡是绕不过去的,还是早点儿开始接触、掌握好。

获取 rt-thread

万能的 Github:https://github.com/RT-Thread/rt-thread

rt-thread 的官网:http://www.rt-thread.org。文档呢,官网是有的,不过,真的是只能作为参考,很明显是开发人员的事后开发笔记整理的。目前还是只能通过看代码来理解详细的使用方式,从文档和论坛的只言片语里面,是难以还原真相的。不过,就如上面所说,rt-thread 的好处就是它的版本还比较小,即便缺乏文档,也是可以看源码看下去的。谢天谢地。

获取 STM32 固件库

如何从意法半导体的官网 http://www.st.com 上找到资料并下载下来,是个技术活儿。该官网的搜索功能是个奇葩,比百度的搜索结果还难以让人理解。

基本上,只能先输入想找的芯片的型号,然后从结果里面再找下载。

STM32 的固件库封装了很多针对芯片的操作以及寄存器设置,如果不用固件库,你要让芯片的某个引脚输出高电平,要这样写:GPIOD->BSRR = 0x00000005; 用了固件库,你就可以这样写:GPIO_SetBits(GPIOD, GPIO_Pin_0|GPIO_Pin_2); (提示:2^0 + 2^2 = 5)。虽然可以对应起来,但实在没有必要折磨自己,还是用固件库吧。

配置工程目录

曾经有高人说过“给我看他的数据结构,我就知道他的代码是怎么写的。”我说“给我看他的目录结构,我就知道他的方案是怎么设计的。”(说到数据结构这件事,我不得不说,json 在这方面做出了巨大贡献,如同 c 语言的 struct,只不过是跨语言平台的。哪怕是做 SharePoint 项目,我也会先考虑定义好适当的 json 对象。)

当我说到“工程”的时候,其实是指 workspace、solution,而非“项目”。“项目”叫做 project。一个工程可以包含多个项目。“分而治之”是人类处理复杂事物的基本策略。

问:哪些会变化,哪些不会?

答:所有固件库、开源组件都被认为不会因为我自己的应用需要而变化,只有自己应用写的会。前者只需要随着发行方升级即可,Github 上面拉下来也好、从官网下载文件解压覆盖也好,都可以。

工程目录布局规则如下:

  • ST MCU 固件库目录 STLib
    • MCU 系列子目录,比如 STM32F10x、STM32F4xx
      • CMSIS
      • 外设驱动子目录,如 STM32F10x_StdPeriph_Driver
    • 截图示意
  • rt-thread 目录
    • 里面原样保持从 Github 拉下来的目录结构即可,如图所示
  • 工程根目录
    • 工程文件。IAR 的工程文件扩展名是 .eww,如同 Visual Studio 的工程文件扩展名是 .sln 一样。
    • 文档目录。
    • 项目目录。有多少项目,就有多少个项目目录。
      • 项目文件。IAR 的项目文件扩展名是 .ewp,如同 Visual Studio 的 C# 项目文件扩展名是 .csproj 一样。
      • main.c 用 IAR 创建新的项目时,main.c 默认会放在项目目录下面。我觉得放在那里挺好,没有必要改变。
      • App 目录。里面放和应用有关的程序文件。
        • 按照上面的路径配置,假设在 App 目录里面有一个 app.c 的程序文件,则该文件到 ST 固件库的相对路径是 “../../STLib”,同样,到 rt-thread 的相对路径是“../../rt-thread”
      • Board 目录。里面放和电路板设置有关的程序文件,比如,串口的管脚定义。这个目录里面文件的意义,是把固件库 & rt-thread 与 应用有关的程序文件隔离开来。个人认为,这一层的作用是很重要的,要好好规划。
      • Driver 目录。里面放置 rt-thread 提供的各种片上外设的驱动程序,需要从 rt-thread 的 bsp 子目录里面对应的芯片驱动中拷贝过来。之所以需要拷贝而非简单的引用,是因为这一层的驱动程序可能需要根据应用的需要做定制。
      • Fireware 目录。里面放置 ST 固件库所需的文件,比如 stm32f10x_conf.h。
      • RT-Thread 目录。里面放置 rt-thread 所需的配置文件,比如 rtconfig.h。
      • 项目目录截图示意(项目名称叫 LCD)

配置工程

ST 微控制器 MCU 的开发 IDE,有 3 大阵营

  • 官方的 ST Studio
  • IAR
  • MDK(Keil)

不知道为什么,我选了 IAR。这中间当然有很多故事,因为我的确 3 种都尝试过,不过,年纪大了,已经记不得这些经历了。不幸的是,rt-thread 虽然有针对 IAR 的移植,但是开发团队用的是 MDK(Keil),于是在配置工程的时候多了一些波折。

下面是配置的步骤和对应的选项。

打开配置选项窗口
选择合适的 Device
勾上“Using CMSIS”
输出所有的汇编链接信息
设置编译器的头文件搜索路径以及与定义的宏

头文件搜索路径如下:

$PROJ_DIR$\..\..\STLib\STM32F10x\CMSIS\CM3\DeviceSupport\ST\STM32F10x
$PROJ_DIR$\..\..\STLib\STM32F10x\STM32F10x_StdPeriph_Driver\inc
$PROJ_DIR$\App
$PROJ_DIR$\Board
$PROJ_DIR$\Driver
$PROJ_DIR$\Firmware
$PROJ_DIR$\RT-Thread
$PROJ_DIR$\..\..\RT-Thread\components\init
$PROJ_DIR$\..\..\RT-Thread\components\drivers\include\drivers
$PROJ_DIR$\..\..\RT-Thread\libcpu\arm\cortex-m3
$PROJ_DIR$\..\..\RT-Thread\libcpu\arm\common
$PROJ_DIR$\..\..\RT-Thread\components\finsh
$PROJ_DIR$\..\..\RT-Thread\include
$PROJ_DIR$\..\..\RT-Thread\components\drivers\include

预定义的宏如下:

STM32F10X_MD
USE_STDPERIPH_DRIVER
HSE_VALUE=((uint32_t)8000000)
SYSCLK_FREQ_24MHz=24000000

其中:
STM32F10X_MD 表示采用的 MCU 系列和密度,可以在芯片手册里面查到。
HSE_VALUE 是 MCU 外部晶振的频率,按照你的实际需要修改。
SYSCLK_FREQ_24MHZ 是总线频率,也是按照实际需要修改。

指定默认的链接器配置文件

需要使用 rt-thread 的 bsp 目录下对应的 MCU 的配置文件。否则烧录进去的程序在芯片中的布局会不正确,导致 finsh 组件无法正确运行:finsh 移植遇到了问题,始终输出”Null node”

指定调试工具

我用的 ST-LINK,所以选择 ST-LINK。

USE Flash Loader
设置 ST-LINK 搭建基于 STM32 和 rt-thread 的开发平台
好了以后可以点 OK 关闭选项设置对话框,然后开始项目文件设置。
引用 ST 固件库

firmware 文件组下面的文件全部来自 STLib 文件夹,引用即可。但是,注意 system_stm32f10x.c 文件,这里面包含 MCU 初始化时钟的设置。如果你没有如上面步骤一样在编译器的预定义宏里面定义时钟,那么,可以修改这个文件来实现。

引用 rt-thread 文件(不同的组件放在不同的子文件组里面) 搭建基于 STM32 和 rt-thread 的开发平台
搭建基于 STM32 和 rt-thread 的开发平台 
搭建基于 STM32 和 rt-thread 的开发平台

注意,这里面的 rtconfig.h 是拷贝到项目目录下面的 RT-Thread 子目录里面的那个,因为需要结合应用的需要进行修改。

拷贝 rt-thread bsp 目录里面的驱动程序到 Driver 目录下

开发示例

按照上面的方式建立好工程和项目以后,已经可以开始一个简单的小程序做测试了,让板上的 LED 每一秒闪烁一次并通过串口来显示 finsh。

首先,在 Board 文件组下面建立 board.h、board.c 来定义自己的硬件环境的配置。

board.h 里面的宏定义 STM32_SRAM_SIZE 很重要,需要根据你所用的 MCU 的实际内存大小来设置。

   1: /* board.h */

   2: #ifndef __BOARD_H__

   3: #define __BOARD_H__

   4:  

   5: #define STM32_EXT_SRAM          0

   6: //    <o>Begin Address of External SRAM

   7: //        <i>Default: 0x68000000

   8: #define STM32_EXT_SRAM_BEGIN    0x68000000 /* the begining address of external SRAM */

   9: //    <o>End Address of External SRAM

  10: //        <i>Default: 0x68080000

  11: #define STM32_EXT_SRAM_END      0x68080000 /* the end address of external SRAM */

  12: // </e>

  13:  

  14: // <o> Internal SRAM memory size[Kbytes] <8-64>

  15: //    <i>Default: 64

  16: #define STM32_SRAM_SIZE         20

  17: #define STM32_SRAM_END          (0x20000000 + STM32_SRAM_SIZE * 1024)

  18:  

  19: void board_initiate();

  20:  

  21: /* LED */

  22: #define USING_LED1

  23: #define led1_periph_clock       RCC_APB2Periph_GPIOC

  24: #define led1_port               GPIOC

  25: #define led1_pin                (GPIO_Pin_13)

  26:  

  27: #endif

   1: /* board.c */

   2: #include <rthw.h>

   3: #include <rtthread.h>

   4:  

   5: #include <stm32f10x.h>

   6: #include "board.h"

   7: #include "usart.h"

   8:  

   9: void board_initiate(){

  10:     SystemInit();

  11:     SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );

  12:  

  13:     rt_hw_usart_init();

  14:     rt_console_set_device(RT_CONSOLE_DEVICE_NAME);

  15: }

  16:  

  17: void SysTick_Handler(void)

  18: {

  19:     rt_interrupt_enter();

  20:     rt_tick_increase();

  21:     rt_interrupt_leave();

  22: }

然后,在 Driver 文件组里面建立 led.h、led.c 的驱动。

   1: /* led.h */

   2: #ifndef __LED_H__

   3: #define __LED_H__

   4:  

   5: #include <stdint.h>

   6:  

   7: void led_initiate(void);

   8: void led_on(uint8_t led);

   9: void led_off(uint8_t led);

  10:  

  11: #endif

   1: /* led.c */

   2: #include <stm32f10x.h>

   3: #include "led.h"

   4: #include <board.h>

   5:  

   6: void led_initiate(void)

   7: {

   8:     GPIO_InitTypeDef GPIO_InitStructure;

   9:     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;

  10:     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

  11:  

  12: #ifdef USING_LED1

  13:     RCC_APB2PeriphClockCmd(led1_periph_clock,ENABLE);

  14:     GPIO_InitStructure.GPIO_Pin   = led1_pin;

  15:     GPIO_Init(led1_port, &GPIO_InitStructure);

  16: #endif

  17: }

  18:  

  19: void led_on(uint8_t n)

  20: {

  21: #ifdef USING_LED1

  22:     if(n!=0)return;

  23:     GPIO_SetBits(led1_port, led1_pin);

  24: #endif

  25: }

  26: void led_off(uint8_t n)

  27: {

  28: #ifdef USING_LED1

  29:     if(n!=0)return;

  30:     GPIO_ResetBits(led1_port, led1_pin);

  31: #endif

  32: }

然后,在 App 文件组里面建立 app.h、app.c 文件,里面的内容和具体应用相关。

   1: /* app.h */

   2: #ifndef __APP_H__

   3: #define __APP_H__

   4:  

   5: int app_initiate(void);

   6:  

   7: #endif /* __APP_H__ */

   1: /* app.c */

   2: #include <rthw.h>

   3: #include <rtthread.h>

   4:  

   5: #include "app.h"

   6: #include "led.h"

   7:  

   8: #ifdef  RT_USING_COMPONENTS_INIT

   9: #include <components.h>

  10: #endif  /* RT_USING_COMPONENTS_INIT */

  11:  

  12: static rt_uint8_t led_stack[256];

  13: static struct rt_thread led_thread;

  14: static void led_thread_entry(void* parameter)

  15: {

  16:     led_initiate();

  17:     while (1)

  18:     {

  19:         led_on(0);

  20:         rt_thread_delay( RT_TICK_PER_SECOND/2 );

  21:         led_off(0);

  22:         rt_thread_delay( RT_TICK_PER_SECOND/2 );

  23:  

  24:     }

  25: }

  26:  

  27: int app_initiate(void)

  28: {

  29:     rt_err_t result;

  30:     /* init led thread */

  31:     result = rt_thread_init(&led_thread,

  32:             "led",

  33:             led_thread_entry,

  34:             RT_NULL,

  35:             (rt_uint8_t*)&led_stack[0],

  36:             sizeof(led_stack),

  37:             20,

  38:             5);

  39:     if (result == RT_EOK)

  40:     {

  41:         rt_thread_startup(&led_thread);

  42:     }

  43:  

  44: #ifdef RT_USING_COMPONENTS_INIT

  45:     /* initialization RT-Thread Components */

  46:     rt_components_init();

  47: #endif

  48:  

  49:     return 0;

  50: }

其中 led 线程的堆栈长度是 256 字节,这是实践出来的。一开始,可以给一个尽量大的堆栈尺寸,然后,运行时通过 finsh 的 list_thread() 命令输出其实际使用的最大的堆栈尺寸,最后回来修改。

接下来打开 rtconfig.h 确保里面的 #define RT_USING_FINSH 没有被注释掉。

最后,在 main.c 里面加入启动代码。

   1: /* main.c */

   2: #include <rthw.h>

   3: #include <rtthread.h>

   4: #ifdef __CC_ARM

   5: extern int Image$$RW_IRAM1$$ZI$$Limit;

   6: #elif __ICCARM__

   7: #pragma section="HEAP"

   8: #else

   9: extern int __bss_end;

  10: #endif

  11:  

  12: #include <stdint.h>

  13: #include "board.h"

  14: #include "app.h"

  15:  

  16: int main()

  17: {

  18:     board_initiate();

  19: #ifdef RT_USING_HEAP

  20: #if STM32_EXT_SRAM

  21:     rt_system_heap_init((void*)STM32_EXT_SRAM_BEGIN, (void*)STM32_EXT_SRAM_END);

  22: #else

  23: #ifdef __CC_ARM

  24:     rt_system_heap_init((void*)&Image$$RW_IRAM1$$ZI$$Limit, (void*)STM32_SRAM_END);

  25: #elif __ICCARM__

  26:     rt_system_heap_init(__segment_end("HEAP"), (void*)STM32_SRAM_END);

  27: #else

  28:     /* init memory system */

  29:     rt_system_heap_init((void*)&__bss_end, (void*)STM32_SRAM_END);

  30: #endif

  31: #endif  /* STM32_EXT_SRAM */

  32: #endif /* RT_USING_HEAP */

  33:     /* init scheduler system */

  34:     rt_system_scheduler_init();

  35:     /* initialize timer */

  36:     rt_system_timer_init();

  37:     /* init timer thread */

  38:     rt_system_timer_thread_init();

  39:     /* init application */

  40:     app_initiate();

  41:     /* init idle thread */

  42:     rt_thread_idle_init();

  43:     /* start scheduler */

  44:     rt_system_scheduler_start();

  45:     /* never reach here */

  46:     return 0;

  47: }

  48:  

  49: void assert_failed(uint8_t* file, uint32_t line)

  50: {

  51:     while (1) ;

  52: }

电路设计

本着省钱、够用的原则,我选择了 STM32F103C8T6,不到 10 元一片,管脚对于我的测试应用足够。

为了确保 MCU 芯片可以满足需要,建议先用 ST 自家出的 MicroXplorer 来规划一下芯片上面外设和 IO 的分配,做到心中有数。

围绕着芯片的选型,就可以开始电路设计了。

下面是 MCU 的核心电路:

核心电路的 P1 是 SWD 的编程接口,其管脚的排列是与 ST 的 STM32F4 Discovery 开发板兼容的,这样就可以直接通过 STM32F4 Discovery 开发板对自己的实验电路进行调试了。

运行效果

然后,就可以编译并下载到芯片中运行了。

下面贴一个实际的效果(里面不仅仅有 LED 了)。

跑一下 finsh,可以看到很多有价值的系统运行数据,如内存的使用情况,各个线程的堆栈使用情况等等。

这样,开发平台搭建完成,接下来就看自己的想象力了。

P.S. 这段时间看似有点儿不务正业了,后面会把 SharePoint 的时间补回来的。

■ 本文结束

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,965
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,486
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,331
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,114
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,747
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,781