设计协约
在我们的上个章节中,我们写了一个接口,其没有强制执行设计协约。让我们再看下我们假想的GPIO配置寄存器:
| 名字 | 位数(s) | 值 | 含义 | 注释 |
|---|---|---|---|---|
| 使能 | 0 | 0 | 关闭 | 关闭GPIO |
| 1 | 使能 | 使能GPIO | ||
| 方向 | 1 | 0 | 输入 | 方向设置成输入 |
| 1 | 输出 | 方向设置成输出 | ||
| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻抗 |
| 01 | 下拉 | 下拉输入管脚 | ||
| 10 | 上拉 | 上拉输入管脚 | ||
| 11 | n/a | 无效模式。不要设置 | ||
| 输出模式 | 4 | 0 | 拉低 | 拉低输出管脚 |
| 1 | 拉高 | 拉高输出管脚 | ||
| 输入状态 | 5 | x | in-val | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 |
如果我们在使用底层硬件之前检查状态,在运行时强制遵守我们的设计协约,我们写的代码可能像这一样:
1. /// GPIO接口
2. struct GpioConfig {
3. /// 由svd2rust生成的GPIO配制结构体
4. periph: GPIO_CONFIG,
5. }
7. impl GpioConfig {
8. pub fn set_enable(&mut self, is_enabled: bool) {
9. self.periph.modify(|_r, w| {
10. w.enable().set_bit(is_enabled)
11. });
12. }
14. pub fn set_direction(&mut self, is_output: bool) -> Result<(), ()> {
15. if self.periph.read().enable().bit_is_clear() {
16. // 必须被使能配置方向
17. return Err(());
18. }
20. self.periph.modify(|r, w| {
21. w.direction().set_bit(is_output)
22. });
24. Ok(())
25. }
27. pub fn set_input_mode(&mut self, variant: InputMode) -> Result<(), ()> {
28. if self.periph.read().enable().bit_is_clear() {
29. // 必须被使能配置输入模式
30. return Err(());
31. }
33. if self.periph.read().direction().bit_is_set() {
34. // 方向必须被设置成输入
35. return Err(());
36. }
38. self.periph.modify(|_r, w| {
39. w.input_mode().variant(variant)
40. });
42. Ok(())
43. }
45. pub fn set_output_status(&mut self, is_high: bool) -> Result<(), ()> {
46. if self.periph.read().enable().bit_is_clear() {
47. // 设置输出状态必须被使能
48. return Err(());
49. }
51. if self.periph.read().direction().bit_is_clear() {
52. // 方向必须是输出
53. return Err(());
54. }
56. self.periph.modify(|_r, w| {
57. w.output_mode.set_bit(is_high)
58. });
60. Ok(())
61. }
63. pub fn get_input_status(&self) -> Result<bool, ()> {
64. if self.periph.read().enable().bit_is_clear() {
65. // 获取状态必须被使能
66. return Err(());
67. }
69. if self.periph.read().direction().bit_is_set() {
70. // 方向必须是输入
71. return Err(());
72. }
74. Ok(self.periph.read().input_status().bit_is_set())
75. }
76. }
因为我们不需要强制遵守硬件上的限制,所以我们最后做了很多运行时检查,它浪费了我们很多时间和资源,对于开发者来说,这个代码用起来就没那么愉快了。
类型状态(Type states)
但是,如果我们使用Rust的类型系统去强制状态转换的规则会怎样?看下这个例子:
1. /// GPIO接口
2. struct GpioConfig<ENABLED, DIRECTION, MODE> {
3. /// 由svd2rust产生的GPIO配置结构体
4. periph: GPIO_CONFIG,
5. enabled: ENABLED,
6. direction: DIRECTION,
7. mode: MODE,
8. }
10. // GpioConfig中MODE的类型状态
11. struct Disabled;
12. struct Enabled;
13. struct Output;
14. struct Input;
15. struct PulledLow;
16. struct PulledHigh;
17. struct HighZ;
18. struct DontCare;
20. /// 这些函数可能被用于所有的GPIO管脚
21. impl<EN, DIR, IN_MODE> GpioConfig<EN, DIR, IN_MODE> {
22. pub fn into_disabled(self) -> GpioConfig<Disabled, DontCare, DontCare> {
23. self.periph.modify(|_r, w| w.enable.disabled());
24. GpioConfig {
25. periph: self.periph,
26. enabled: Disabled,
27. direction: DontCare,
28. mode: DontCare,
29. }
30. }
32. pub fn into_enabled_input(self) -> GpioConfig<Enabled, Input, HighZ> {
33. self.periph.modify(|_r, w| {
34. w.enable.enabled()
35. .direction.input()
36. .input_mode.high_z()
37. });
38. GpioConfig {
39. periph: self.periph,
40. enabled: Enabled,
41. direction: Input,
42. mode: HighZ,
43. }
44. }
46. pub fn into_enabled_output(self) -> GpioConfig<Enabled, Output, DontCare> {
47. self.periph.modify(|_r, w| {
48. w.enable.enabled()
49. .direction.output()
50. .input_mode.set_high()
51. });
52. GpioConfig {
53. periph: self.periph,
54. enabled: Enabled,
55. direction: Output,
56. mode: DontCare,
57. }
58. }
59. }
61. /// 这个函数可能被用于一个输出管脚
62. impl GpioConfig<Enabled, Output, DontCare> {
63. pub fn set_bit(&mut self, set_high: bool) {
64. self.periph.modify(|_r, w| w.output_mode.set_bit(set_high));
65. }
66. }
68. /// 这些方法可能被用于使能一个输入GPIO
69. impl<IN_MODE> GpioConfig<Enabled, Input, IN_MODE> {
70. pub fn bit_is_set(&self) -> bool {
71. self.periph.read().input_status.bit_is_set()
72. }
74. pub fn into_input_high_z(self) -> GpioConfig<Enabled, Input, HighZ> {
75. self.periph.modify(|_r, w| w.input_mode().high_z());
76. GpioConfig {
77. periph: self.periph,
78. enabled: Enabled,
79. direction: Input,
80. mode: HighZ,
81. }
82. }
84. pub fn into_input_pull_down(self) -> GpioConfig<Enabled, Input, PulledLow> {
85. self.periph.modify(|_r, w| w.input_mode().pull_low());
86. GpioConfig {
87. periph: self.periph,
88. enabled: Enabled,
89. direction: Input,
90. mode: PulledLow,
91. }
92. }
94. pub fn into_input_pull_up(self) -> GpioConfig<Enabled, Input, PulledHigh> {
95. self.periph.modify(|_r, w| w.input_mode().pull_high());
96. GpioConfig {
97. periph: self.periph,
98. enabled: Enabled,
99. direction: Input,
100. mode: PulledHigh,
101. }
102. }
103. }
现在让我们看下代码如何用这个API:
1. /*
2. * 案例 1: Unconfigured to High-Z input
3. */
4. let pin: GpioConfig<Disabled, _, _> = get_gpio();
6. // 不能这么做,pin没有被使能
7. // pin.into_input_pull_down();
9. // 现在从unconfigured to a high-z input打开管脚
10. let input_pin = pin.into_enabled_input();
12. // 从管脚读取
13. let pin_state = input_pin.bit_is_set();
15. // 不能这么做,输入管脚没有这个接口
16. // input_pin.set_bit(true);
18. /*
19. * 案例 2: High-Z 输入到下拉输入
20. */
21. let pulled_low = input_pin.into_input_pull_down();
22. let pin_state = pulled_low.bit_is_set();
24. /*
25. * 案例 3: 下拉输入到输出, 拉高
26. */
27. let output_pin = pulled_low.into_enabled_output();
28. output_pin.set_bit(true);
30. // 不能这么做,输出管脚没有这个接口
31. // output_pin.into_input_pull_down();
这绝对是存储管脚状态的便捷方法,但是为什么这么做?为什么这比把状态当成一个enum存在我们的GpioConfig结构体中更好?
编译时功能安全(Functional Safety)
因为我们在编译时完全强制我们遵守我们的设计约定,这造成了没有运行时开销。当你有一个在输入模式的管脚时,是不可能去设置一个输出模式的。你必须先把它设置成一个输出管脚,然后再设置输出模式。因为在执行一个函数前会检查现在的状态,因此没有运行时消耗。
也因为这些状态被类型系统强制遵守,因此没有为这个接口的使用者留太多的犯错空间。如果它们尝试执行一个非法的状态转换,代码将不会编译!
