stm32_hal2/dac.rs
1//! Support for the digital to Analog converter (DAC) peripheral.
2
3use core::ops::Deref;
4
5#[cfg(not(any(feature = "f", feature = "l5", feature = "g4")))]
6use cortex_m::delay::Delay;
7
8use crate::{
9 error::Result,
10 pac::{self, RCC},
11 util::RccPeriph,
12};
13
14cfg_if! {
15 if #[cfg(any(feature = "f3", feature = "g4", feature = "h7b3"))] {
16 use pac::dac1 as p;
17 } else {
18 use pac::dac as p;
19 }
20}
21
22#[cfg(any(feature = "f3", feature = "l4"))]
23use crate::dma::DmaInput;
24#[cfg(not(any(feature = "f4", feature = "l552")))]
25use crate::dma::{self, ChannelCfg, DmaChannel};
26#[cfg(feature = "g0")]
27use crate::pac::DMA as DMA1;
28#[cfg(not(feature = "g0"))]
29use crate::pac::DMA1;
30
31#[derive(Clone, Copy)]
32#[repr(u8)]
33/// Sets the MCR register, Mode1 and Mode2 fields.
34pub enum DacMode {
35 /// DAC channel is connected to external pin with Buffer enabled
36 NormExternalOnlyBufEn = 0b000,
37 /// DAC channel is connected to external pin and to on chip peripherals with buffer
38 /// enabled
39 NormExternalAndPeriphBufEn = 0b001,
40 /// DAC channel is connected to external pin with buffer disabled
41 NormExternalOnlyBufDis = 0b010,
42 /// DAC channel is connected to on chip peripherals with Buffer disabled
43 NormExternalAndPeriphBufDis = 0b011,
44 /// DAC channel is connected to external pin with Buffer enabled. (Sample and Hold)
45 ShNormExternalOnlyBufEn = 0b100,
46 /// DAC channel is connected to external pin and to on chip peripherals with buffer. (Sample and Hold)
47 /// enabled
48 ShExternalAndPeriphBufEn = 0b101,
49 /// DAC channel is connected to external pin with buffer disabled. (Sample and Hold)
50 ShNormExternalOnlyBufDis = 0b110,
51 /// DAC channel is connected to on chip peripherals with Buffer disabled. (Sample and Hold)
52 ShNormExternalAndPeriphBufDis = 0b111,
53}
54
55use cfg_if::cfg_if;
56
57#[derive(Clone, Copy)]
58#[repr(u8)]
59/// Select the channel to output to. Most MCUs only use 2 channels.
60pub enum DacChannel {
61 C1 = 1,
62 #[cfg(not(any(feature = "wl", feature = "f410")))]
63 C2 = 2,
64}
65
66#[derive(Clone, Copy)]
67/// Three options are available to set DAC precision. Sets the DHR8R1 etc register contents.
68pub enum DacBits {
69 /// Eight bit precision, right-aligned.
70 EightR,
71 /// 12-bit precision, left-aligned.
72 TwelveL,
73 /// 12-bit precision, right-aligned.
74 TwelveR,
75}
76
77#[derive(Clone, Copy)]
78#[repr(u8)]
79#[cfg(not(any(feature = "h7", feature = "g4")))]
80/// Select a trigger, used by some features. Sets CR, TSEL1 and TSEL2 fields, for Channel 1
81/// and Channel 2 triggers respectively. See L44 RM, Table 75. DAC trigger selection.
82pub enum Trigger {
83 /// Timer 6
84 Tim6 = 0b000,
85 /// Timers 3 or 8
86 Tim3_8 = 0b001,
87 /// Timer 7
88 Tim7 = 0b010,
89 /// Timer 15
90 Tim5 = 0b011,
91 /// Timer 2
92 Tim2 = 0b100,
93 /// Timer 4
94 Tim4 = 0b101,
95 /// Eg, for interrupts
96 Exti9 = 0b110,
97 /// A software trigger
98 Swtrig = 0b111,
99}
100
101#[derive(Clone, Copy)]
102#[repr(u8)]
103#[cfg(feature = "g4")]
104/// Trigger selection on G4, used by TSELx and Sawtooth generation ST[INC|RST]TRIGSELx
105/// Section 11.3.4 Table 66 - DAC trigger interconnect table
106pub enum Trigger {
107 Software = 0,
108 /// DAC[1,2,4] use TIM8, DAC3 uses TIM1
109 Tim8_1 = 1,
110 Tim7 = 2,
111 Tim15 = 3,
112 Tim2 = 4,
113 Tim4 = 5,
114 /// Reset uses EXTI9, increment uses EXTI10
115 ExtI9_10 = 6,
116 Tim6 = 7,
117 Tim3 = 8,
118
119 /// HRTIM Reset and Increment triggers
120 /// hrtim_[reset|step]_trigX
121 HrtimTrigger1 = 9,
122 HrtimTrigger2 = 10,
123 HrtimTrigger3 = 11,
124 HrtimTrigger4 = 12,
125 HrtimTrigger5 = 13,
126 HrtimTrigger6 = 14,
127
128 /// Update/Reset Triggers from HRTIM
129 /// DAC1 - hrtim_trg1
130 /// DAC2 - hrtim_trg2
131 /// DAC3 - hrtim_trg3
132 /// DAC4 - hrtim_trg1
133 HrtimDacTrigger1_2_3 = 15,
134}
135
136#[derive(Clone, Copy)]
137#[repr(u8)]
138#[cfg(feature = "g4")]
139/// Select a waveform generation. Sets CR, WAVE1 and WAVE2 for Channel 1
140/// and Channel 2 repectively. See G4 RM, section 22.7.1 noise/triangle wave generation enable
141pub enum WaveGeneration {
142 /// No wave generation
143 Disabled = 0b00,
144 /// Noise wave generation mode
145 Noise = 0b01,
146 /// Triange wave generation mode
147 Triangle = 0b10,
148 /// Sawtooth wave generation mode
149 Sawtooth = 0b11,
150}
151
152#[derive(Clone, Copy)]
153#[repr(u8)]
154#[cfg(any(feature = "g4"))]
155pub enum SawtoothDirection {
156 Falling = 0b0,
157 Rising = 0b1,
158}
159
160#[derive(Clone, Copy)]
161#[cfg(any(feature = "g4"))]
162/// Sawtooth Generation configuration for the generate_sawtooth() method
163pub struct SawtoothConfig {
164 /// Specify the increment trigger source from the Trigger enum
165 /// when increment is triggered, the DAC will increment
166 /// incremenbt or decrement (depending on the configured direction)
167 /// the output by the increment value specified in the config.
168 pub increment_trigger: Trigger,
169 /// Specify the reset trigger source from the Trigger enum
170 /// when reset is triggered, the DAC will reset to the initial value
171 /// specified in the config.
172 pub reset_trigger: Trigger,
173 /// Initial value that is set on the DAC output each time a reset
174 /// event is triggered.
175 pub initial: u16,
176 /// Increment value that is added/subtracted from the DAC output
177 /// each time an increment event is triggered.
178 pub increment: u16,
179 /// The direction of the sawtooth wave to control if the wave
180 /// is incremented or decremented on each increment trigger event.
181 pub direction: SawtoothDirection,
182}
183
184#[derive(Clone, Copy)]
185#[repr(u8)]
186#[cfg(feature = "h7")]
187/// Select a trigger, used by some features. Sets CR, TSEL1 and TSEL2 fields, for Channel 1
188/// and Channel 2 triggers respectively. See H743 RM, Table 225. DAC interconnection.
189pub enum Trigger {
190 /// A software trigger
191 Swtrig = 0,
192 /// Timer 1
193 Tim1 = 1,
194 /// Timer 2
195 Tim2 = 2,
196 /// Timer 3
197 Tim4 = 3,
198 /// Timer 4
199 Tim5 = 4,
200 /// Timer 5
201 Tim6 = 5,
202 /// Timer 6
203 Tim7 = 6,
204 /// Timer 7
205 Tim8 = 7,
206 /// Timer 8
207 Tim15 = 8,
208 /// High resolution timer trigger 1
209 Hrtim1Trig1 = 9,
210 /// High resolution timer trigger 2
211 Hrtim1Trig2 = 10,
212 /// Low power timer 1
213 Lptim1 = 11,
214 /// Low power timer 2
215 Lptim2 = 12,
216 /// Eg, for interrupts
217 Exti9 = 13,
218}
219
220#[derive(Clone, Copy)]
221#[repr(u8)]
222#[cfg(feature = "g4")]
223/// High frequency interface mode selection.
224/// Used in MCR
225/// See G4 RM, section 22.7.16
226pub enum HighFrequencyMode {
227 // High frequency interface mode disabled
228 Disabled = 0b00,
229 // High frequency interface mode compatible to AHB > 80MHz
230 Medium = 0b01,
231 // High frequency interface mode compatible to AHB > 160MHz
232 High = 0b10,
233}
234
235#[derive(Clone)]
236pub struct DacConfig {
237 /// Mode: Ie buffer enabled or not, and connected to internal, external, or both. Defaults
238 /// to external only, buffer enabled.
239 pub mode: DacMode,
240 /// Output bit depth and alignment. Defaults to 12 bits, right-aligned.
241 pub bits: DacBits,
242
243 #[cfg(feature = "g4")]
244 pub hfsel: HighFrequencyMode,
245}
246
247impl Default for DacConfig {
248 fn default() -> Self {
249 Self {
250 mode: DacMode::NormExternalOnlyBufEn,
251 bits: DacBits::TwelveR,
252 #[cfg(feature = "g4")]
253 hfsel: HighFrequencyMode::High,
254 }
255 }
256}
257
258/// Represents a Digital to Analog Converter (DAC) peripheral.
259pub struct Dac<R> {
260 pub regs: R,
261 pub cfg: DacConfig,
262 vref: f32,
263}
264
265// todo: Calculate the VDDA vref, as you do with onboard ADCs!
266
267impl<R> Dac<R>
268where
269 R: Deref<Target = p::RegisterBlock> + RccPeriph,
270{
271 /// Initialize a DAC peripheral, including enabling and resetting
272 /// its RCC peripheral clock. `vref` is in volts.
273 pub fn new(regs: R, cfg: DacConfig, vref: f32) -> Self {
274 let rcc = unsafe { &(*RCC::ptr()) };
275 R::en_reset(rcc);
276
277 // See H743 RM, Table 227 for info on the buffer.
278 cfg_if! {
279 if #[cfg(any(
280 // feature = "l5",
281 feature = "g4",
282 ))] {
283 regs.mcr().modify(|_, w| unsafe {
284 w.mode1().bits(cfg.mode as u8);
285 w.mode2().bits(cfg.mode as u8)
286 });
287 } else if #[cfg(any(feature = "h7", feature = "l4", feature = "l5", feature = "wl"))] {
288 regs.mcr().modify(|_, w| unsafe {
289 #[cfg(not(feature = "wl"))]
290 w.mode2().bits(cfg.mode as u8);
291 return w.mode1().bits(cfg.mode as u8);
292
293 });
294 }
295 // Note: mode not present on on F3 and F4.
296 }
297
298 Self { regs, cfg, vref }
299 }
300
301 /// Calibrate the DAC output buffer by performing a "User
302 /// trimming" operation. It is useful when the VDDA/VREF+
303 /// voltage or temperature differ from the factory trimming
304 /// conditions.
305 ///
306 /// The calibration is only valid when the DAC channel is
307 /// operating with the buffer enabled. If applied in other
308 /// modes it has no effect.
309 ///
310 /// After the calibration operation, the DAC channel is
311 /// disabled.
312 #[cfg(not(any(feature = "f3", feature = "f4", feature = "l5", feature = "g4")))]
313 pub fn calibrate_buffer(
314 // This function taken from STM32H7xx-hal.
315 &mut self,
316 channel: DacChannel,
317 delay: &mut Delay,
318 ) {
319 self.disable(channel);
320
321 let mut trim = 0;
322
323 loop {
324 self.regs
325 .ccr()
326 .modify(|_, w| unsafe { w.otrim(channel as u8 - 1).bits(trim) });
327
328 delay.delay_us(64);
329
330 let cal_flag = self
331 .regs
332 .sr()
333 .read()
334 .cal_flag(channel as u8 - 1)
335 .bit_is_set();
336
337 if cal_flag {
338 break;
339 }
340 trim += 1;
341 }
342 }
343
344 /// Enable the DAC, for a specific channel.
345 pub fn enable(&mut self, channel: DacChannel) {
346 let cr = &self.regs.cr();
347
348 cr.modify(|_, w| w.en(channel as u8 - 1).bit(true));
349 }
350
351 /// Disable the DAC, for a specific channel.
352 pub fn disable(&mut self, channel: DacChannel) {
353 let cr = &self.regs.cr();
354
355 // Doesn't use the newer API.
356 match channel {
357 DacChannel::C1 => cr.modify(|_, w| w.en1().clear_bit()),
358 #[cfg(not(any(feature = "wl", feature = "f410")))]
359 DacChannel::C2 => cr.modify(|_, w| w.en2().clear_bit()),
360 };
361 }
362
363 /// Set the DAC output word.
364 pub fn write(&mut self, channel: DacChannel, val: u16) {
365 // RM: DAC conversion
366 // The DORx cannot be written directly and any data transfer to the DAC channelx must
367 // be performed by loading the DHRx register (write operation to DHR8Rx,
368 // DHR12Lx, DHR12Rx, DHR8RD, DHR12RD or DHR12LD).
369 // Data stored in the DHRx register are automatically transferred to the DORx
370 // register after one APB1 clock cycle, if no hardware trigger is selected (TENx bit in CR
371 // register is reset). However, when a hardware trigger is selected (TENx bit in CR
372 // register is set) and a trigger occurs, the transfer is performed three APB1 clock cycles after
373 // the trigger signal.
374 // When DORx is loaded with the DHRx contents, the analog output voltage
375 // becomes available after a time tSETTLING that depends on the power supply voltage and the
376 // analog output load.
377
378 // todo: Should we ensure the word doesn't overflow the set `bits` value?
379
380 // todo: Use the field accessors (Assuming not too diff among variants?). These
381 // todo let you write u16 directly instead of casting as u32.
382 let val = val as u32;
383
384 match self.cfg.bits {
385 DacBits::EightR => self
386 .regs
387 .dhr8r(channel as usize - 1)
388 .modify(|_, w| unsafe { w.bits(val) }),
389 DacBits::TwelveL => self
390 .regs
391 .dhr12l(channel as usize - 1)
392 .modify(|_, w| unsafe { w.bits(val) }),
393 DacBits::TwelveR => self
394 .regs
395 .dhr12r(channel as usize - 1)
396 .modify(|_, w| unsafe { w.bits(val) }),
397 };
398 }
399
400 /// Send values to the DAC using DMA. Each trigger (Eg using a timer; the basic timers Tim6
401 /// and Tim7 are designed for DAC triggering) sends one word from the buffer to the DAC's
402 /// output.
403 /// Note that the `dma_channel` argument is unused on F3 and L4, since it is hard-coded,
404 /// and can't be configured using the DMAMUX peripheral. (`dma::mux()` fn).
405 #[cfg(not(any(feature = "f4", feature = "l552")))]
406 pub unsafe fn write_dma(
407 &mut self,
408 buf: &[u16],
409 channel: DacChannel,
410 dma_channel: DmaChannel,
411 channel_cfg: ChannelCfg,
412 dma_periph: dma::DmaPeriph,
413 // dma: &mut Dma<D>,
414 ) -> Result<()> {
415 // where
416 // D: Deref<Target = dma_p::RegisterBlock>,
417 // {
418 let (ptr, len) = (buf.as_ptr(), buf.len());
419
420 #[cfg(any(feature = "f3", feature = "l4"))]
421 let dma_channel = match channel {
422 DacChannel::C1 => DmaInput::Dac1Ch1.dma1_channel(),
423 DacChannel::C2 => DmaInput::Dac1Ch2.dma1_channel(),
424 };
425
426 #[cfg(feature = "l4")]
427 match dma_periph {
428 dma::DmaPeriph::Dma1 => {
429 let mut regs = unsafe { &(*DMA1::ptr()) };
430 match channel {
431 DacChannel::C1 => dma::channel_select(&mut regs, DmaInput::Dac1Ch1),
432 DacChannel::C2 => dma::channel_select(&mut regs, DmaInput::Dac1Ch2),
433 }
434 }
435 dma::DmaPeriph::Dma2 => {
436 let mut regs = unsafe { &(*pac::DMA2::ptr()) };
437 match channel {
438 DacChannel::C1 => dma::channel_select(&mut regs, DmaInput::Dac1Ch1),
439 DacChannel::C2 => dma::channel_select(&mut regs, DmaInput::Dac1Ch2),
440 };
441 }
442 }
443
444 // H743 RM, section 26.4.8: DMA requests
445 // Each DAC channel has a DMA capability. Two DMA channels are used to service DAC
446 // channel DMA requests.
447
448 // When an external trigger (but not a software trigger) occurs while the DMAENx bit is set, the
449 // value of the DHRx register is transferred into the DORx register when the
450 // transfer is complete, and a DMA request is generated.
451 #[cfg(feature = "g4")]
452 match channel {
453 DacChannel::C1 => self.regs.cr().modify(|_, w| w.dmaen1().bit(true)),
454 DacChannel::C2 => self.regs.cr().modify(|_, w| w.dmaen2().bit(true)),
455 };
456
457 #[cfg(not(feature = "g4"))]
458 match channel {
459 DacChannel::C1 => self.regs.cr().modify(|_, w| w.dmaen1().bit(true)),
460 #[cfg(not(any(feature = "wl", feature = "f410")))]
461 DacChannel::C2 => self.regs.cr().modify(|_, w| w.dmaen2().bit(true)),
462 };
463
464 // In dual mode, if both DMAENx bits are set, two DMA requests are generated. If only one
465 // DMA request is needed, only the corresponding DMAENx bit must be set. In this way, the
466 // application can manage both DAC channels in dual mode by using one DMA request and a
467 // unique DMA channel.
468
469 // As DHRx to DORx data transfer occurred before the DMA request, the very first
470 // data has to be written to the DHRx before the first trigger event occurs.
471
472 // DMA underrun
473 // The DAC DMA request is not queued so that if a second external trigger arrives before the
474 // acknowledgment for the first external trigger is received (first request), then no new request
475 // is issued and the DMA channelx underrun flag DMAUDRx in the SR register is set,
476 // reporting the error condition. The DAC channelx continues to convert old data.
477
478 // The software must clear the DMAUDRx flag by writing 1, clear the DMAEN bit of the used
479 // DMA stream and re-initialize both DMA and DAC channelx to restart the transfer correctly.
480 // The software must modify the DAC trigger conversion frequency or lighten the DMA
481 // workload to avoid a new DMA underrun. Finally, the DAC conversion could be resumed by
482 // enabling both DMA data transfer and conversion trigger.
483
484 // For each DAC channelx, an interrupt is also generated if its corresponding DMAUDRIEx bit
485 // in the CR register is enabled.
486
487 let periph_addr = match &self.cfg.bits {
488 DacBits::EightR => &self.regs.dhr8r(channel as usize - 1) as *const _ as u32,
489 DacBits::TwelveL => &self.regs.dhr12l(channel as usize - 1) as *const _ as u32,
490 DacBits::TwelveR => &self.regs.dhr12r(channel as usize - 1) as *const _ as u32,
491 };
492
493 let len = len as u32;
494
495 match dma_periph {
496 dma::DmaPeriph::Dma1 => {
497 let mut regs = unsafe { &(*DMA1::ptr()) };
498 dma::cfg_channel(
499 &mut regs,
500 dma_channel,
501 periph_addr,
502 ptr as u32,
503 len,
504 dma::Direction::ReadFromMem,
505 dma::DataSize::S16,
506 dma::DataSize::S16,
507 channel_cfg,
508 )
509 }
510 #[cfg(dma2)]
511 dma::DmaPeriph::Dma2 => {
512 let mut regs = unsafe { &(*pac::DMA2::ptr()) };
513 dma::cfg_channel(
514 &mut regs,
515 dma_channel,
516 periph_addr,
517 ptr as u32,
518 len,
519 dma::Direction::ReadFromMem,
520 dma::DataSize::S16,
521 dma::DataSize::S16,
522 channel_cfg,
523 )
524 }
525 }
526 }
527
528 /// Set the DAC output voltage.
529 pub fn write_voltage(&mut self, channel: DacChannel, volts: f32) {
530 let max_word = match self.cfg.bits {
531 DacBits::EightR => 255.,
532 DacBits::TwelveL => 4_095.,
533 DacBits::TwelveR => 4_095.,
534 };
535
536 let val = ((volts / self.vref) * max_word) as u16;
537 self.write(channel, val);
538 }
539
540 // todo: Trouble finding right `tsel` fields for l5 and WL. RM shows same as others. PAC bug?
541 // todo Or is the PAC breaking the bits field into multiple bits?
542 #[cfg(not(any(feature = "l5", feature = "wl")))]
543 /// Select and activate a trigger. See f303 Reference manual, section 16.5.4.
544 /// Each time a DAC interface detects a rising edge on the selected trigger source (refer to the
545 /// table below), the last data stored into the DHRx register are transferred into the
546 /// DORx register. The DORx register is updated three pclk cycles after the
547 /// trigger occurs.
548 pub fn set_trigger(&mut self, channel: DacChannel, trigger: Trigger) {
549 let cr = &self.regs.cr();
550
551 // Note: tsel1 didn't update with the new approach, so repetition here.
552 match channel {
553 DacChannel::C1 => cr.modify(|_, w| unsafe {
554 w.ten(channel as u8 - 1).bit(true);
555 w.tsel1().bits(trigger as u8)
556 }),
557 #[cfg(not(feature = "f410"))]
558 DacChannel::C2 => cr.modify(|_, w| unsafe {
559 w.ten(channel as u8 - 1).bit(true);
560 w.tsel2().bits(trigger as u8)
561 }),
562 };
563 }
564
565 #[cfg(not(any(feature = "l5", feature = "wl")))] // See note on `set_trigger`.
566 /// Independent trigger with single LFSR generation
567 /// See f303 Reference Manual section 16.5.2
568 pub fn trigger_lfsr(&mut self, channel: DacChannel, trigger: Trigger, data: u16) {
569 let cr = &self.regs.cr();
570
571 // todo: This may not be correct.
572 cr.modify(|_, w| unsafe {
573 w.mamp(channel as u8 - 1).bits(0b01);
574 w.wave(channel as u8 - 1).bits(0b01)
575 });
576
577 self.set_trigger(channel, trigger);
578 self.write(channel, data);
579 }
580
581 #[cfg(not(any(feature = "l5", feature = "wl")))] // See note on `set_trigger`.
582 /// Independent trigger with single triangle generation
583 /// See f303 Reference Manual section 16.5.2
584 pub fn trigger_triangle(&mut self, channel: DacChannel, trigger: Trigger, data: u16) {
585 let cr = &self.regs.cr();
586
587 cr.modify(|_, w| unsafe {
588 w.wave(channel as u8 - 1).bits(0b10);
589 w.mamp(channel as u8 - 1).bits(0b10)
590 });
591
592 self.set_trigger(channel, trigger);
593 self.write(channel, data);
594 }
595
596 // todo: Put back. PAC 0.16 API change. (July 2025)
597 // // #[cfg(any(feature = "g4"))]
598 // pub fn generate_sawtooth(&self, channel: DacChannel, config: SawtoothConfig) {
599 // let cr = &self.regs.cr();
600 //
601 // self.regs.str1().modify(|_, w| {
602 // w.strstdata(channel as u8).variant(config.initial);
603 // w.stincdata(channel as u8).variant(config.increment);
604 // w.stdir(channel as u8).variant(config.direction as u8 == 1)
605 // });
606 //
607 // self.regs.stmodr().modify(|_, w| {
608 // w.stinctrigsel(channel as u8).variant(config.increment_trigger as u8);
609 // w.strsttrigsel(channel as u8).variant(config.reset_trigger as u8)
610 // });
611 //
612 // cr.modify(|_, w| w.wave(channel as u8).variant(WaveGeneration::Sawtooth as u8));
613 // }
614
615 /// Enable the DMA Underrun interrupt - the only interrupt available.
616 pub fn enable_interrupt(&mut self, channel: DacChannel) {
617 let cr = &self.regs.cr();
618
619 cr.modify(|_, w| w.dmaudrie(channel as u8 - 1).bit(true));
620 }
621
622 #[cfg(not(feature = "g4"))] // todo: PAC ommission? SR missing on G4? In RM. (may not affect all G4 variants)
623 /// Clear the DMA Underrun interrupt - the only interrupt available.
624 pub fn clear_interrupt(&mut self, channel: DacChannel) {
625 self.regs.sr().write(|w| match channel {
626 DacChannel::C1 => w.dmaudr1().bit(true),
627 #[cfg(not(any(feature = "wl", feature = "f410")))]
628 DacChannel::C2 => w.dmaudr2().bit(true),
629 });
630 }
631}