潘多拉板子个人笔记(一)——点亮一盏LED灯

发表在 内核学习营2019-5-8 01:18 [复制链接] 4 328

本帖最后由 家定不举棋 于 2019-5-8 01:19 编辑 + e, o% e8 |6 a6 l0 i( M& f

, j5 {: G! [$ ^  Z2 d+ C5 r1 t& o买了好久的潘多拉板子,一直积灰,有点浪费,收拾了一下学习学习,然后将心得记在这里,也想各位指点。9 v; ^9 Z+ I* h

点亮一颗LED

) |# r& ]7 D, H4 {

主函数流程

' @& |* {8 V# g$ ^+ s" S

首先将 PE7 设置为输出模式,然后进入循环模式:向引脚写入低电平信号并打印 led on 和 count 的值,等待500ms,向引脚写入高电平信号并打印 led off,等待500ms,将 count 加一。

. |& P3 Y, S$ L4 b

所使用的RT-Thread资源

+ z( y8 c0 x4 \+ K: t! J

整个程序使用到了 RT-Thread 中的 PIN设备、内核线程,在程序中共调用了三个头文件,其作用如下:

& G, l4 l! ^, ?8 Y- W 9 V6 q: f, T$ H# W$ n4 O4 D# J. Z( t/ U* z) n* v$ x  J# ^$ U: y  R6 @* a$ r# Q5 W& M4 b+ K% S: F) q# G, V2 q, x: L, Z2 S. m* }3 t% n5 a6 A! ~# S5 E+ }) y+ Z. J9 W, `6 b  Z+ t, |1 \/ |7 ^! a* M6 q; ?5 V5 L2 G, E3 g3 L+ Y1 C5 Q) W* z# y1 @% w; C2 n  X" E$ T7 e" H8 D5 b( S: I! m& s: \/ Y) i5 X- ]0 R2 D5 m) y# ^2 B# M! S" h% S: u" ~% N5 I, S3 o, t+ h; v3 R9 [5 ]" D( F9 q, K# D2 F9 A: x# X1 ?8 V: q5 q  d7 g4 d- a9 ?0 ~! o& N7 ?8 }8 X/ W/ N5 B$ X. o# j6 u% b/ z9 ]; L1 v, e7 H& R* A+ v) @) d3 ]* \7 i' ?- b% ~: t
头文件 作用
rtthread.h rtthread内核头文件,内核 API 的申明
rtdevice.h rtthread设备驱动框架的整合
board.h 针对stm32l4xx的头文件
4 Y; v" P6 s' V/ d: C7 }

整个程序使用到了如下 API:

" a* J/ q8 F; X1 m! s! L8 H# q7 N! W7 y7 D+ p  ]$ a5 s) b  d0 W; a# h& a' J4 d: f; E* }' n$ ]: b7 N1 ]$ j$ M) B9 ]1 A( m& w8 q) ?- D6 ?2 L9 w% h3 ~+ v8 R& K6 f* t9 h: I8 u7 w; L0 T5 T# }6 O8 c4 w7 \$ c; P4 t* X$ C- e. ]5 ]* ~' e8 L5 ]  P' }( G) l% m# h1 Y( t$ t4 E6 p7 s5 {+ S1 v0 I/ N* V6 m( G- x1 ~$ J4 |7 `. V1 a% K# @1 }2 N7 s9 i6 A2 |8 p, @3 F1 E: i- u$ J# o; c. S) T9 w0 n! p4 P$ C4 c; K5 A+ x' `# Z6 q4 ^$ _! D+ e6 r1 L% R. w" A& `' i4 H" z9 x5 f! b. U1 U1 F4 b) T4 f6 q, ?: A! o) I$ ~3 m& T  n+ u( p0 ]) Z, g) D* Q/ P( U+ m) A% c; }0 u4 W! m2 G! w8 v* r) @3 u6 ?2 ^+ h  z; n4 J3 x) ~: I. r2 v$ c1 ]% ^9 M( A5 V1 `! g% L! I, I, _; Z3 J- F: {% b; D6 ]' x6 E" V" p& y$ U8 a  C0 w- {6 ?, T4 m7 ^/ X- {; L+ _
名称 定义文件 作用
rt_pin_mode pin.c 定义引脚的工作模式
rt_pin_write pin.c 向引脚写信号
rt_kprintf kseervicce.c 向串口打印数据
rt_thread_mdelay thread.c 延时函数,并将线程挂起
% s4 H- G5 r7 O& U4 a- C

rt-thread提供了完善的设备框架,其真正的运行原理还需要对其进行分析。

0 A+ s3 M2 U. U- M+ k

rt_pin_mode

4 Z5 q5 A9 A4 s

线程执行的第一步便是对引脚的工作模式定义,其中 rt_pin_mode 的源码如下:

  C( u7 z2 a8 u6 y6 `" `+ ~
    void rt_pin_mode(rt_base_t pin, rt_base_t mode)
) `/ q4 |6 g& H) V    {1 e( m. a# T6 y8 V% a4 F" a5 d
        RT_ASSERT(_hw_pin.ops != RT_NULL);
) l* A. m( ^& U8 |; ]5 Y        _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode);9 |/ p1 i, g- A( a" l9 D
    }
1 O: t5 |7 \) T

代码简单,但是内部原理不简单。其主要原理便是对 _hw_pin.ops 进行断言判断是否为空,若不为空便对其对象进行设置引脚和模式 。那么有以下问题:

1 @9 ^+ R# t, o5 i! M$ e7 m: t% Y  Y' w% U
    & y& B# b1 m' M! Y  B1 V7 u
  1. _hw_pin 是什么;
  2. ( t/ T) M7 ^# i5 S
  3. 为什么需要判断 _hw_pin.ops 里的值是否为空,他里面有什么;
  4. 7 `. v5 V8 `# s0 `! L" Y
  5. _hw_pin.ops 中的 pin_mode 具体实质操作是什么;
  6. / I3 P8 [1 A- `/ W: _  ^
2 ]! I: j' d- M% |1 y0 y

为了解决第一个问题,查看了 _hw_pin 的定义,即为 static struct rt_device_pin _hw_pin;,那么可以知道 _hw_pin 为结构体 rt_device_pin,这里可以回答第一个问题,其中  rt_device_pin 结构体如下:

* T6 _! E0 E5 }" p4 @6 }3 g: Y
    struct rt_device_pin
1 Y. Z, s( U6 L4 B3 p    {" Y3 v3 f7 b, C
        struct rt_device parent;0 b  D9 \; B/ Y% l' ~: i
        const struct rt_pin_ops *ops;6 o0 c& n9 B+ s( U7 H$ C
    };
' V3 v. u7 P+ ~* r/ w" d

其内部便是设备的内核对象和对引脚的操作接口,但是对于 _hw_pin 里具体内容还是一概不知,但是可以确定如果  _hw_pin.ops 内部为空则程序无法执行,这也可以回答第二个问题的前一半问题。

- |! K/ q+ X# k$ F0 U

通过查找可以知道在 pin.c 文件中提供了 rt_device_pin_register 来对_hw_pin 进行初始化。在这里可以看到它的内核类型为 RT_Device_Class_Miscellaneous (该值在 rt_device_class_type 中定义),设置了常用设备接口中的读、写和控制为 _pin_read_pin_write_pin_control。抱着好奇的心思去看看这三个真正的操作是怎么实现的:

' _; V+ p! K- ]' D& r
    " i2 `7 Q1 Z4 D8 Y. B8 |; M
  • 1 F3 T) T3 P! C  f

    _pin_read:先把 pin 进行断言判断不为 RT_NULL,确定 status 不为空和 sizestatus 的大小相同,执行 pin->ops->pin_read,返回 size大小。

      C$ ]0 B$ L( f( l. Y6 U
  • ; a; S3 b1 [0 D5 |
  • , d. w5 R# N. U* F7 g

    _pin_write:先判断 pin 进行断言判断不为 RT_NULL,确定 status 不为空和 sizestatus 的大小相同,执行 pin->ops->pin_write,返回 size 大小。

    4 M+ p# o; t+ k: c5 E
  • - E! F4 Q5 d: F4 o5 K( ~) q
  • _pin_control:先判断 pin 进行断言判断不为 RT_NULL,确定 mode 不为 RT_NULL,执行 pin->ops->pin_mode,返回0。
  • 5 }+ @( S2 o4 P7 _+ e5 {" T
& q& E5 o* n) Q* l. d

由上述可知还需知道 ops 中的操作,但是 ops 为输入形参,那么产生新的问题:

% [2 ^0 c  r9 t. s1 Y1 m$ c
    ' p5 e- q; g$ d, r9 J; B
  1. rt_device_pin_register 在程序中实质在哪里执行;
  2. 5 Z: C; f0 h. y7 h6 s
  3. rt_device_pin_register 在执行的时候赋值进去的参数是什么;
  4. 1 R! E) [! P, G! D( I
2 `, \; n" D6 \4 S" T" D& M

带着上述问题重新查看程序,可以看到 drv_gpio.c 文件中的如下代码:

4 k' h# w( z, O/ W; A. U# V! F
    int rt_hw_pin_init(void)
8 @9 D0 Y' w, A( E8 R& w/ x/ _6 q! r    {' }5 _; x( y2 t! i
        return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);4 z+ U  E) c4 J! w6 _7 [7 q
    }
" G- G: b7 H) w- n2 i" E8 E

然后可以知道在 board.c 文件中的 rt_hw_board_init()中调用了 rt_hw_pin_init() 函数,且必须定义 BSP_USING_GPIO,该定义在 rtconfig.h 文件中(rtthread的裁剪工作本质也是在该文件中对定义进行修改, menuconfig 工具中的配置也是对该文件的一种操作,其中这也是C语言中的一种小技巧,在附录中进行详细说明),而 rt_hw_board_init() 在 rtthread 启动函数 rtthread_startup 中便得到了执行。

8 t. A% i- p3 R7 Y% E

而在这之中可以看到定义在 drv_gpio.c 文件中的 _stm32_pin_ops 的如下:

! `8 b) @- u) B/ }* [! {
    const static struct rt_pin_ops _stm32_pin_ops =
1 i% H% P# \% z+ h9 \0 F    {
6 `/ t+ U* A% K: _6 q; `4 o1 k) B$ o& I        stm32_pin_mode,* j3 X9 p3 Q6 Z- r
        stm32_pin_write,
& Y1 `3 v2 i+ w( B# j& L  c        stm32_pin_read,- P& w! C/ F& X$ h
        stm32_pin_attach_irq,
5 r6 F0 I. L4 O        stm32_pin_dettach_irq,3 A+ e4 }7 h$ ^. h$ B) D
        stm32_pin_irq_enable,
% F% }6 P7 W1 z* h    };
# z$ x( ~/ \: n, q- I' I1 I& S

上述API也是 _hw_pin.ops 的本质。

0 y' |/ K1 X$ l  v

附录

% W4 @( I* E/ ^+ b2 [% B

对于代码的注释最常用的为 ”//“  和 ”/... /“,在 RTThread 中使用了如下注释方法:

+ P$ I8 l  U  u
    #ifdef define_array
5 x( S- ]6 Q  p% G. r9 m2 c$ Q6 c/ @    func()0 J/ b! y; f! `8 |# }$ b
    #endif
* m! ?) R% I) ~, Y/ U% O

如果 define_array 变量在之前定义了那么就执行 func() 程序,如果未定义则不执行,即相当于:

$ y2 v$ B$ g) g  u
    #ifdef 0: C, x+ ]% |8 O. |
    func(). Y/ w; T  z; H$ @% d1 j' S
    #endif
! [' Q& D! A/ q

而上述方法也是一种很好的注释方法,因为 ”//“ 的注释会显得凌乱,而 ”/... /“ 不支持嵌套。

8 b4 l, W; Z; W. c, Y
使用道具 举报 显示全部楼层 回复
最新评论 | 正序浏览
显示全部楼层 |楼层直达:
发表于 2019-6-11 10:32:02 | 显示全部楼层
主函数流程建议用程序流程图去表达,这样会不会让人更加容易理解些?
使用道具 举报 回复
发表于 2019-6-15 10:19:02 | 显示全部楼层
这里"#ifdef 0"是怎么回事?
使用道具 举报 回复
发表于 2019-6-15 10:20:04 | 显示全部楼层
  1. error: macro names must be identifiers$ |! f, i$ H9 ]( m
  2. #ifdef 0
复制代码
使用道具 举报 回复
发表于 2019-6-15 10:22:35 | 显示全部楼层
附录上的不叫注释吧,应该叫条件编译
使用道具 举报 回复
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|RT-Thread开发者社区  

© 2013-2017 上海睿赛德电子科技有限公司 沪ICP备13014002号-1

Powered by RT-Thread

快速回复 返回顶部 返回列表