Skip to content

Commit 84f6434

Browse files
committed
Rework ADC implementation
This also supports all devices now, instead of only f303 variants. The channel information of the channels where generated automatically, with help of the stm32cubemx "database" (xml files), and `xq` a xml wrapper program for `jq`. As a rework of codegen is needed to support the ADC channel generation the easier route was taken, including checking in the scripts used for that. - Support most important configurations. - Better OneShot implementation support. - Interrupt support.
1 parent 24149c6 commit 84f6434

File tree

14 files changed

+3233
-399
lines changed

14 files changed

+3233
-399
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
6060
and ADC2, which the old API did not resemble.
6161
- The API provides no clock configuration for ADC peripherals yet, but
6262
correctly acts upon changes done through the `pac`. ([#281])
63+
- Major rework of the ADC implementation: ([#281])
64+
- Add `CommonAdc` support.
65+
- Add internal sensor peripheral support like `TemeperaturSensor` and similar.
66+
- Lift restriction of the ADC implementation for `stm32f303`, though
67+
`stm32f373` is still not supported.
68+
- Enable manual configuration of the Adc peripheral of most important features
69+
- `ConversionMode`
70+
- `Sequence` length
71+
- `OverrunMode`
72+
- etc.
73+
- Leverage type states to:
74+
- Allow a `Disabled` ADC, which can be manually calibrated.
75+
- Allow a `OneShot` ADC implementation through `into_oneshot` with an
76+
optimal configuration for the `OneShot` embedded-hal trait.
77+
- Support every possible ADC channel.
78+
- Add interrupt support.
6379

6480
## [v0.8.2] - 2021-12-14
6581

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ bare-metal = "1.0.0"
5656

5757
[dev-dependencies]
5858
cortex-m-semihosting = "0.3.7"
59+
defmt = "0.3.0"
5960
defmt-rtt = "0.3.0"
6061
defmt-test = "0.3.0"
6162
panic-probe = "0.3.0"

codegen/tools/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
gen.py
2+
__pycache__

codegen/tools/NOTES.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# ADC Channels
2+
3+
The ADC channel approach did not use the current codegen code, as
4+
it was more convenient to query the xml database with a commandline tool,
5+
to get an idea how it was structured.
6+
7+
This code **should** be integrated into the codegen rust source in the future.
8+
But to make this happen codegen has to be refactored, as it is heavily
9+
tailored to the `IP` resolution right now.
10+
11+
What really should happen though, is to integrate all those features into
12+
[**`cube-parse`**](https://github.com/stm32-rs/cube-parse/)
13+
and only have a shim of processing code, which tailors the output to our
14+
macros.

codegen/tools/adc_channel.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
This script generates the ADC channel! macro with arguents for all pins
5+
of all chip subfamilies.
6+
7+
The strategy is, to get the maximum possible support for the most
8+
finest currently possible chip subfamily distinction.
9+
10+
That means, that some impls for some chips are more than the actual
11+
chip is actually capable of.
12+
"""
13+
14+
import re
15+
16+
import subprocess
17+
18+
# Hacky way to create a dict variable with a bash script
19+
# which is then saved to a file which then can be directly imported.
20+
channel = subprocess.check_output(['bash', 'adc_channel.sh'])
21+
with open("gen.py", "wb") as f:
22+
f.write(channel)
23+
24+
import gen
25+
26+
CHIP_SELECT = {
27+
"stm32f301x6": r".*STM32F301.(6|8|\(6-8\)).*\.xml",
28+
"stm32f302x6": r".*STM32F302.(6|8|\(6-8\)).*\.xml",
29+
"stm32f303x6": r".*STM32F303.(6|8|\(6-8\)).*\.xml",
30+
"stm32f302xb": r".*STM32F302.(B|C|\(B-C\)|\(8-B-C\)).*\.xml",
31+
"stm32f303xb": r".*STM32F303.(B|C|\(B-C\)|\(8-B-C\)).*\.xml",
32+
"stm32f302xd": r".*STM32F302.(D|E|\(D-E\)).*\.xml",
33+
"stm32f303xd": r".*STM32F303.(D|E).*\.xml",
34+
"stm32f328": r".*STM32F328.*\.xml",
35+
"stm32f358": r".*STM32F358.*\.xml",
36+
"stm32f398": r".*STM32F398.*\.xml",
37+
"stm32f373": r".*STM32F37(3|8).*\.xml",
38+
"stm32f334": r".*STM32F334.*\.xml",
39+
}
40+
41+
42+
def main():
43+
chip_post = {}
44+
45+
# Split ADC into adc and channel
46+
for chip_name, pin_list in gen.CHANNELS.items():
47+
pins = []
48+
for pin in pin_list:
49+
adc_list = []
50+
for i in [1, 2]:
51+
try:
52+
(adc, channel) = pin[i].split("_")
53+
except IndexError:
54+
continue
55+
adc_list.append((adc, int(channel)))
56+
pins.append((pin[0], adc_list))
57+
chip_post[chip_name] = pins
58+
59+
chip_results = {}
60+
61+
# Group into chip categories and select chip with max entries
62+
# Choosing the maximum list is a compromise right now:
63+
# 1. The stm32cubemx database is much more fine-grained about
64+
# the types of chips than our current feature is
65+
# 2. Only allowing the minimal subset of all chip-subfamilies
66+
# is can be pretty annoying and would severly restrict the
67+
# channel implementation for the stm32f303xc and st32f303xd
68+
# for example.
69+
# 3. This goes a little against the current impl strategy,
70+
# that is, allowing more than a chip might be possible to use.
71+
# But the alternative of finer feature selection is a pretty
72+
# big change and gives no real benefit, other than annoying
73+
# the user.
74+
for chip, regex in CHIP_SELECT.items():
75+
grouping = []
76+
for chip_name, _ in chip_post.items():
77+
if re.search(regex, chip_name):
78+
grouping.append(chip_post[chip_name])
79+
grouping = list(max(grouping))
80+
chip_results[chip] = grouping
81+
82+
# Print the resulintg dictionary in a format, compatible with the
83+
# `channel!` macro.
84+
for type, result in chip_results.items():
85+
type_variant_map = {
86+
'x6': 'x8',
87+
'xb': 'xc',
88+
'xd': 'xe',
89+
}
90+
for key, value in type_variant_map.items():
91+
if type[-2:] == key:
92+
type_variant = type[:-2] + value
93+
print(f'}} else if #[cfg(any(feature = "{type}", feature = "{type_variant}"))] {{')
94+
break
95+
else:
96+
print(f'}} else if #[cfg(feature = "{type}")] {{')
97+
print(" channel!([")
98+
for elem in result:
99+
line = f" ({', '.join([str(e) for e in elem])})"
100+
line = str(elem).replace("'", "")
101+
print(" " + line + ",")
102+
print(" ]);")
103+
104+
105+
if __name__ == "__main__":
106+
main()

codegen/tools/adc_channel.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
# Using xq to query the stm32cubemx database for adc channels
3+
# xq beeing a wrapper of jq converting xml to a json representation internally.
4+
#
5+
# This is the source of the CHANNELS dict in adc_channel.py
6+
# TODO(Sh3Rm4n): This should probably be rewritten in the codegen part.
7+
# Though therefor the codegen should be refactored.
8+
9+
set -euo pipefail
10+
11+
echo "CHANNELS = {"
12+
for xml in /opt/stm32cubemx/db/mcu/STM32F3*.xml; do
13+
echo '"'$xml'": ['
14+
xq '.Mcu.Pin[]
15+
| select(.Signal[]?."@Name"? | startswith("ADC"))
16+
| [."@Name", (.Signal[]? | select(."@Name" | startswith("ADC")))."@Name"]
17+
| @csv' --raw-output < $xml \
18+
| rg -v EXTI \
19+
| rg -v OSC32 \
20+
| rg -v 'jq:' \
21+
| sed 's/IN//g' \
22+
| sort -u \
23+
| awk '{print "("$0"),"}'
24+
echo '],'
25+
done
26+
echo "}"

examples/adc.rs

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,51 @@
33

44
//! Example usage for ADC on STM32F303
55
6-
use panic_semihosting as _;
6+
use defmt_rtt as _;
7+
use panic_probe as _;
8+
9+
use defmt;
710

811
use cortex_m::asm;
912
use cortex_m_rt::entry;
10-
use cortex_m_semihosting::hprintln;
1113

14+
use embedded_hal::adc::OneShot;
1215
use stm32f3xx_hal::{adc, pac, prelude::*};
1316

1417
#[entry]
15-
/// Main Thread
1618
fn main() -> ! {
1719
// Get peripherals, clocks and freeze them
18-
let mut dp = pac::Peripherals::take().unwrap();
20+
let dp = pac::Peripherals::take().unwrap();
1921
let mut rcc = dp.RCC.constrain();
2022
let clocks = rcc.cfgr.freeze(&mut dp.FLASH.constrain().acr);
2123

24+
let mut adc_common = adc::CommonAdc::new(dp.ADC1_2, &clocks, &mut rcc.ahb);
25+
26+
let mut tuple = (dp.ADC1, dp.ADC2);
27+
let mut ts = adc::TemperatureSensor::new(&mut adc_common, &mut tuple);
28+
2229
// set up adc1
2330
let mut adc = adc::Adc::new(
24-
dp.ADC1, // The ADC we are going to control
31+
tuple.0, // The ADC we are going to control
32+
adc::config::Config::default(),
2533
// The following is only needed to make sure the clock signal for the ADC is set up
2634
// correctly.
27-
clocks,
28-
&mut rcc.ahb,
29-
);
35+
&clocks,
36+
&adc_common,
37+
)
38+
// Convert the ADC into `OneShot` mode.
39+
//
40+
// This is not necessary, as the normal ADC does also implement the `OneShot` trait, which can
41+
// call `read`. But this conversion implies less overhead for the trait implementation. The
42+
// consequence is, that in this mode nothing can be configured manually.
43+
.into_oneshot();
44+
45+
// You can also create an ADC, which is initially disabled and uncalibrated and calibrate
46+
// it later:
47+
let mut adc2 = adc::Adc::new_disabled(tuple.1);
48+
adc2.calibrate(&clocks, &adc_common);
49+
adc2.set_config(adc::config::Config::default());
50+
let _ = adc2.into_enabled();
3051

3152
// Set up pin PA0 as analog pin.
3253
// This pin is connected to the user button on the stm32f3discovery board.
@@ -39,7 +60,7 @@ fn main() -> ! {
3960
// Also know that integer division and the ADC hardware unit always round down.
4061
// To make up for those errors, see this forum entry:
4162
// [https://forum.allaboutcircuits.com/threads/why-adc-1024-is-correct-and-adc-1023-is-just-plain-wrong.80018/]
42-
hprintln!("
63+
defmt::info!("
4364
The ADC has a 12 bit resolution, i.e. if your reference Value is 3V:
4465
approx. ADC value | approx. volt value
4566
==================+===================
@@ -49,11 +70,15 @@ fn main() -> ! {
4970
5071
If you are using a STM32F3Discovery, PA0 is connected to the User Button.
5172
Pressing it should connect the user Button to to HIGH and the value should change from 0 to 4095.
52-
").expect("Error using hprintln.");
73+
");
5374

5475
loop {
55-
let adc_data: u16 = adc.read(&mut analog_pin).expect("Error reading adc1.");
56-
hprintln!("PA0 reads {}", adc_data).ok();
76+
let adc_data: u16 = adc.read(&mut analog_pin).unwrap();
77+
defmt::trace!("PA0 reads {}", adc_data);
78+
let adc_data: u16 = adc.read(&mut ts).unwrap();
79+
defmt::trace!("TemperatureSensor reads {}", adc_data);
5780
asm::delay(2_000_000);
5881
}
5982
}
83+
84+
// TODO: Add adc example, which uses Continuous or Discontinous and interrupts.

0 commit comments

Comments
 (0)