Skip to content

Commit 8ff2bea

Browse files
committed
machine/stm32u585: implement ADC
Signed-off-by: deadprogram <ron@hybridgroup.com>
1 parent 1e25992 commit 8ff2bea

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

src/machine/board_arduino_uno_q.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ const (
1919
A4 = PC1
2020
A5 = PC0
2121

22+
// ADC pin aliases
23+
ADC0 = A0
24+
ADC1 = A1
25+
ADC2 = A2
26+
ADC3 = A3
27+
ADC4 = A4
28+
ADC5 = A5
29+
2230
D0 = PB7
2331
D1 = PB6
2432
D2 = PB3
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//go:build stm32u5
2+
3+
package machine
4+
5+
import (
6+
"device/stm32"
7+
"unsafe"
8+
)
9+
10+
// InitADC initializes the registers needed for ADC1.
11+
func InitADC() {
12+
// Enable ADC bus clock.
13+
enableAltFuncClock(unsafe.Pointer(stm32.ADC1))
14+
15+
// Declare VDDA analog supply valid. Without ASV the ADC LDO cannot start.
16+
stm32.PWR.SVMCR.SetBits(stm32.PWR_SVMCR_ASV)
17+
18+
// Set ADC12_Common CCR: synchronous clock HCLK/1.
19+
// ADC12_Common is mapped as ADC_Type; CCR at offset 0x08 = .CR field.
20+
stm32.ADC12_Common.CR.Set(1 << 16) // CKMODE=01
21+
22+
// Exit deep power-down mode.
23+
stm32.ADC1.CR.ClearBits(stm32.ADC_CR_DEEPPWD)
24+
25+
// Enable internal voltage regulator and wait for LDO ready.
26+
stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADVREGEN)
27+
for !stm32.ADC1.ISR.HasBits(stm32.ADC_ISR_LDORDY) {
28+
}
29+
30+
// Calibrate ADC (single-ended).
31+
stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADCAL)
32+
for stm32.ADC1.CR.HasBits(stm32.ADC_CR_ADCAL) {
33+
}
34+
35+
// 12-bit resolution, overwrite DR on overrun.
36+
stm32.ADC1.CFGR1.Set(stm32.ADC_CFGR1_RES_TwelveBit<<stm32.ADC_CFGR1_RES_Pos |
37+
stm32.ADC_CFGR1_OVRMOD)
38+
39+
// Pre-select all channels (PCSEL must be written while ADEN=0).
40+
stm32.ADC1.PCSEL.Set(0xFFFFF)
41+
42+
// Clear ADRDY flag, enable ADC, wait for ready.
43+
stm32.ADC1.ISR.Set(stm32.ADC_ISR_ADRDY)
44+
stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADEN)
45+
for !stm32.ADC1.ISR.HasBits(stm32.ADC_ISR_ADRDY) {
46+
}
47+
}
48+
49+
// Configure configures an ADC pin to be able to read analog data.
50+
func (a ADC) Configure(config ADCConfig) {
51+
a.Pin.Configure(PinConfig{Mode: PinInputAnalog})
52+
53+
ch := a.getChannel()
54+
55+
// Set sampling time for this channel.
56+
// Channels 0-9 use SMPR1, channels 10-19 use SMPR2.
57+
// Each channel uses 3 bits. Default to 36.5 cycles for reliable reads.
58+
const smpVal = 0x4 // 36.5 ADC clock cycles
59+
if ch <= 9 {
60+
pos := uint8(ch) * 3
61+
stm32.ADC1.SMPR1.ReplaceBits(smpVal, 0x7, pos)
62+
} else {
63+
pos := uint8(ch-10) * 3
64+
stm32.ADC1.SMPR2.ReplaceBits(smpVal, 0x7, pos)
65+
}
66+
}
67+
68+
// Get returns the current value of an ADC pin in the range 0..0xffff.
69+
func (a ADC) Get() uint16 {
70+
ch := uint32(a.getChannel())
71+
72+
// PCSEL already set for all channels in InitADC (must be written while ADEN=0).
73+
74+
// Set up regular sequence: 1 conversion, channel in SQ1
75+
// Note: ADC_SQR1_SQ1_Pos (0xC) in the device header is wrong for the 14-bit ADC1/ADC2.
76+
// RM0456 shows SQ1 at bits [10:6] = position 6.
77+
stm32.ADC1.SQR1.Set(ch << 6) // L=0 means 1 conversion
78+
79+
// Start conversion
80+
stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADSTART)
81+
82+
// Wait for end of conversion
83+
for stm32.ADC1.ISR.Get()&stm32.ADC_ISR_EOC == 0 {
84+
}
85+
86+
// Read 12-bit result and scale to 16-bit
87+
result := uint16(stm32.ADC1.DR.Get()) << 4
88+
89+
return result
90+
}
91+
92+
// getChannel returns the ADC1 channel number for a given GPIO pin.
93+
// STM32U585 ADC1 channel mapping (from datasheet Table 24):
94+
//
95+
// PC0=CH1, PC1=CH2, PC2=CH3, PC3=CH4
96+
// PA0=CH5, PA1=CH6, PA2=CH7, PA3=CH8
97+
// PA4=CH9, PA5=CH10, PA6=CH11, PA7=CH12
98+
// PB0=CH13, PB1=CH14
99+
func (a ADC) getChannel() uint8 {
100+
switch a.Pin {
101+
case PC0:
102+
return 1
103+
case PC1:
104+
return 2
105+
case PC2:
106+
return 3
107+
case PC3:
108+
return 4
109+
case PA0:
110+
return 5
111+
case PA1:
112+
return 6
113+
case PA2:
114+
return 7
115+
case PA3:
116+
return 8
117+
case PA4:
118+
return 9
119+
case PA5:
120+
return 10
121+
case PA6:
122+
return 11
123+
case PA7:
124+
return 12
125+
case PB0:
126+
return 13
127+
case PB1:
128+
return 14
129+
}
130+
return 0
131+
}

src/machine/machine_stm32u5.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ func enableAltFuncClock(bus unsafe.Pointer) {
272272
stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SPI2EN)
273273
case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable
274274
stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_SPI3EN)
275+
case unsafe.Pointer(stm32.ADC1): // ADC1/ADC2 clock enable
276+
stm32.RCC.AHB2ENR1.SetBits(stm32.RCC_AHB2ENR1_ADC12EN)
277+
case unsafe.Pointer(stm32.ADC4): // ADC4 clock enable
278+
stm32.RCC.AHB3ENR.SetBits(stm32.RCC_AHB3ENR_ADC4EN)
275279
case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable
276280
stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_WWDGEN)
277281
case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable

src/runtime/runtime_stm32u5.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,12 @@ func initCLK() {
3030
// Enable PWR peripheral clock (required on STM32U5 before accessing PWR registers).
3131
stm32.RCC.AHB3ENR.SetBits(stm32.RCC_AHB3ENR_PWREN)
3232
_ = stm32.RCC.AHB3ENR.Get() // read-back for clock stabilization
33+
34+
// Switch from VOS Range 4 to Range 3. Range 4 doesn't support ADC (RM0456 §6.3.6).
35+
// Range 3 supports up to 50 MHz HCLK and enables ADC. No flash wait-state change needed at 4 MHz.
36+
// The EPOD booster must be enabled before changing VOS (RM0456 §10.5.4).
37+
stm32.PWR.VOSR.SetBits(stm32.PWR_VOSR_BOOSTEN)
38+
stm32.PWR.VOSR.ReplaceBits(stm32.PWR_VOSR_VOS_Range3<<stm32.PWR_VOSR_VOS_Pos, stm32.PWR_VOSR_VOS_Msk, 0)
39+
for !stm32.PWR.VOSR.HasBits(stm32.PWR_VOSR_VOSRDY) {
40+
}
3341
}

0 commit comments

Comments
 (0)