使用 C 语言如何控制硬件

1、前言

计算机、单片机或者其他各嵌入式系统中包括各种各样的硬件,各类硬件通过系统中的各级总线进行互联,由 CPU 居中控制和协调,进而完成各类工作。 对硬件的控制包括总线上互联的各类硬件,如内存控制器、Flash 控制器、USB & PCIe 等各类高速总线、SPI、 I2C & UART 等各类低速外设,以及通过 IO 直接相连的 LED、开关器件等。 此外,CPU 本身也是硬件。

LVGL 动态内存使用 CCMRAM

LVGL动态内存使用 CCMRAM

使用STM32F407VET6 开发一个项目,使用了 LVGL,然后关注一下 STM32 的 RAM 信息,官方介绍 STM32F407 的 RAM 可用 RAM 是192K,但这 192 K不是连续的,而是分了三块,其中只有两块是连续的,而因为 STM32F4 没有 MMU 这样的内存管理单元,可以将连续的虚拟内核地址翻译到非连续的物理地址,所以我们使用 STM32F407 的 RAM 时必须要注意地址以及内存空间的大小!

GCC自定义内存区域实现LVGL,FreeRTOS内存,C语言malloc内存区域独立使用。

总体内存配置

  • 总体系统内存: 64MB

LVGL 9 内存配置

  • LVGL 动态内存: 32MB
    • 起始地址: 0x000000
    • 结束地址: 0x02000000
    • LVGL 配置项:
      • #define LV_MEM_CUSTOM 1
      • #define LV_MEM_POOL_DEF_SIZE (32 * 1024 * 1024) (32MB)

FreeRTOS 9 内存配置

  • FreeRTOS 堆内存: 16MB
    • 起始地址: 0x02000000
    • 结束地址: 0x03000000
    • FreeRTOS 配置项:
      • #define configAPPLICATION_ALLOCATED_HEAP 1
      • #define configTOTAL_HEAP_SIZE 0x1000000 (16MB)

C 的 malloc 动态内存配置

  • C 的 malloc 内存: 16MB
    • 起始地址: 0x03000000
    • 结束地址: 0x04000000

LVGL 9 配置

1
#define LV_MEM_POOL_DEF_SIZE (32 * 1024 * 1024)  // 32MB in bytes

FreeRTOS V9设置

GCC LD文件配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
;/**************************************************************************
; *                                                                         *
; * Copyright (c) 2019 Nuvoton Technology. All rights reserved.             *
; *                                                                         *
; **************************************************************************/

ENTRY(__vector)

MEMORY
{
  LVGL_HEAP   (rwx)   : ORIGIN = 0x000000, LENGTH = 0x02000000 /* 32MB for LVGL dynamic memory */
  FREERTOS_HEAP (rwx) : ORIGIN = 0x02000000, LENGTH = 0x01000000 /* 16MB for FreeRTOS memory */
  CMALLOC_HEAP  (rwx) : ORIGIN = 0x03000000, LENGTH = 0x01000000 /* 16MB for C malloc dynamic memory */
}

SECTIONS
{
  .text :
  {
    PROVIDE(__image_start = .);
    PROVIDE(__text_start = .);
  
    PROVIDE(__vectors_start = .);
    *(.vectors);
    . = ALIGN(4);
    PROVIDE(__vectors_end = .);
    *(.init);
    . = ALIGN(4);
    *(.text);
    . = ALIGN(4);
    *(.rodata);
    . = ALIGN(4);
    *(.rodata*);
    . = ALIGN(4);

    etext = .;
    
    PROVIDE(__text_end = .);
  } > LVGL_HEAP

  . = ALIGN(4);
  _etext = . ;
  PROVIDE (etext = .);
   
  .data : AT (_etext)
  {
    PROVIDE(__data_start__ = .);
    _data = . ;
    *(.data)
    . = ALIGN(4);
    PROVIDE(__data_end__ = .);
  } > LVGL_HEAP

  . = ALIGN(4);
  _edata = . ;
  PROVIDE (edata = .);

  sbss = .;
  .bss :
  {
    PROVIDE (__bss_start__ = .);
    *(.bss)
    *(.bss.**)
    *(COMMON)
    . = ALIGN(4);
    PROVIDE (__bss_end__ = .);
  }>LVGL_HEAP
  ebss = .;
  bss_size = ebss - sbss; 

  .heap :
  {
    . = ALIGN(8);
    end = .;
  } > LVGL_HEAP

  PROVIDE_HIDDEN (__exidx_start = .);
  .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) }
  PROVIDE_HIDDEN (__exidx_end = .);
  
  /* FreeRTOS memory section */
  .freertos_heap :
  {
    /* ... Additional sections for FreeRTOS memory go here ... */
  } > FREERTOS_HEAP

  /* C malloc memory section */
  .cmalloc_heap :
  {
    /* ... Additional sections for C malloc memory go here ... */
  } > CMALLOC_HEAP
} 

Rust 过程宏 proc-macro 是个啥

定义一个 procedural macro

新建一个 lib 类型的 crate:

1
cargo new hello-macro --lib

procedural macros 只能在  proc-macro  类型的 crate 内定义,所以需要修改 Cargo.toml:

1
2
[lib]
proc-macro = true

删除  src/lib.rs  里的全部内容,然后定义第一个过程宏( procedural macro ):

1
2
3
4
5
6
use proc_macro::TokenStream;

#[proc_macro]
pub fn hello_proc(input: TokenStream) -> TokenStream {
    input
}

目前它的作用跟下面这个声明宏( declarative macro ) 是等价的:

Rust 过程宏 proc-macro2 和 proc-macro 主要区别

The proc-macro2 crate is a drop-in replacement for  proc_macro  except that it is available outside of macros - which makes it testable. Its types are all convertible to and from the  proc_macro  types and have identical methods.

The usual pattern for writing a nontrivial macro is to use  proc_macro  only for the entry point, and use  proc-macro2  for all of the real work:

1
extern crate proc_macro;use proc_macro2::TokenStream; #[proc_macro]pub fn my_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {    let output = transform_stream(TokenStream::from(input));    proc_macro::TokenStream::from(output)} // A testable function!fn transform_stream(input: TokenStream) -> TokenStream {    // actual work goes here}

It’s common to import items from  proc-macro2  so they can be used unqualified, and just used fully qualified names for  proc_macro , since the only time you will use it is in the entry point. It’s also usual to put the core components in a separate library crate, which does not have a dependency on  proc_macro .