diff --git a/src/Emulator/Peripherals/Peripherals.csproj b/src/Emulator/Peripherals/Peripherals.csproj
index 9ea8c7df2..9f46f01de 100644
--- a/src/Emulator/Peripherals/Peripherals.csproj
+++ b/src/Emulator/Peripherals/Peripherals.csproj
@@ -371,7 +371,10 @@
+
+
+
diff --git a/src/Emulator/Peripherals/Peripherals/Analog/STM32C0_ADC.cs b/src/Emulator/Peripherals/Peripherals/Analog/STM32C0_ADC.cs
new file mode 100644
index 000000000..af05fc42c
--- /dev/null
+++ b/src/Emulator/Peripherals/Peripherals/Analog/STM32C0_ADC.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2010-2023 Antmicro
+// Copyright (c) 2023 OS Systems
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Peripherals.DMA;
+
+namespace Antmicro.Renode.Peripherals.Analog
+{
+ public class STM32C0_ADC : STM32_ADC_Common
+ {
+ public STM32C0_ADC(IMachine machine, double referenceVoltage, uint externalEventFrequency, int dmaChannel = 0, IDMA dmaPeripheral = null)
+ : base(
+ machine,
+ referenceVoltage,
+ externalEventFrequency,
+ dmaChannel,
+ dmaPeripheral,
+ // Base class configuration
+ watchdogCount: 3,
+ hasCalibration: true,
+ hasHighCalAddress: false,
+ channelCount: 23,
+ hasPrescaler: false,
+ hasVbatPin: false,
+ hasChannelSequence: true,
+ hasPowerRegister: false
+ )
+ {}
+ }
+}
diff --git a/src/Emulator/Peripherals/Peripherals/Analog/STM32F0_ADC.cs b/src/Emulator/Peripherals/Peripherals/Analog/STM32F0_ADC.cs
index 99e0a10fd..0fc79336d 100644
--- a/src/Emulator/Peripherals/Peripherals/Analog/STM32F0_ADC.cs
+++ b/src/Emulator/Peripherals/Peripherals/Analog/STM32F0_ADC.cs
@@ -22,6 +22,7 @@ public STM32F0_ADC(IMachine machine, double referenceVoltage, uint externalEvent
// Base class configuration
watchdogCount: 1,
hasCalibration: false,
+ hasHighCalAddress: false,
channelCount: 19,
hasPrescaler: false,
hasVbatPin: true,
diff --git a/src/Emulator/Peripherals/Peripherals/Analog/STM32G0_ADC.cs b/src/Emulator/Peripherals/Peripherals/Analog/STM32G0_ADC.cs
new file mode 100644
index 000000000..27adc2a89
--- /dev/null
+++ b/src/Emulator/Peripherals/Peripherals/Analog/STM32G0_ADC.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2010-2023 Antmicro
+// Copyright (c) 2023 OS Systems
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Peripherals.DMA;
+
+namespace Antmicro.Renode.Peripherals.Analog
+{
+ public class STM32G0_ADC : STM32_ADC_Common
+ {
+ public STM32G0_ADC(IMachine machine, double referenceVoltage, uint externalEventFrequency, int dmaChannel = 0, IDMA dmaPeripheral = null)
+ : base(
+ machine,
+ referenceVoltage,
+ externalEventFrequency,
+ dmaChannel,
+ dmaPeripheral,
+ // Base class configuration
+ watchdogCount: 3,
+ hasCalibration: true,
+ hasHighCalAddress: false,
+ channelCount: 19,
+ hasPrescaler: true,
+ hasVbatPin: true,
+ hasChannelSequence: true,
+ hasPowerRegister: false
+ )
+ {}
+ }
+}
diff --git a/src/Emulator/Peripherals/Peripherals/Analog/STM32L0_ADC.cs b/src/Emulator/Peripherals/Peripherals/Analog/STM32L0_ADC.cs
new file mode 100644
index 000000000..a537fb87f
--- /dev/null
+++ b/src/Emulator/Peripherals/Peripherals/Analog/STM32L0_ADC.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2010-2023 Antmicro
+// Copyright (c) 2023 OS Systems
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Peripherals.DMA;
+
+namespace Antmicro.Renode.Peripherals.Analog
+{
+ public class STM32L0_ADC : STM32_ADC_Common
+ {
+ public STM32L0_ADC(IMachine machine, double referenceVoltage, uint externalEventFrequency, int dmaChannel = 0, IDMA dmaPeripheral = null)
+ : base(
+ machine,
+ referenceVoltage,
+ externalEventFrequency,
+ dmaChannel,
+ dmaPeripheral,
+ // Base class configuration
+ watchdogCount: 1,
+ hasCalibration: true,
+ hasHighCalAddress: false,
+ channelCount: 18,
+ hasPrescaler: true,
+ hasVbatPin: false,
+ hasChannelSequence: false,
+ hasPowerRegister: true
+ )
+ {}
+ }
+}
diff --git a/src/Emulator/Peripherals/Peripherals/Analog/STM32WBA_ADC.cs b/src/Emulator/Peripherals/Peripherals/Analog/STM32WBA_ADC.cs
index 66b6f41e8..63656bdeb 100644
--- a/src/Emulator/Peripherals/Peripherals/Analog/STM32WBA_ADC.cs
+++ b/src/Emulator/Peripherals/Peripherals/Analog/STM32WBA_ADC.cs
@@ -22,6 +22,7 @@ public STM32WBA_ADC(IMachine machine, double referenceVoltage, uint externalEven
// Base class configuration
watchdogCount: 3,
hasCalibration: true,
+ hasHighCalAddress: true,
channelCount: 14,
hasPrescaler: true,
hasVbatPin: false,
diff --git a/src/Emulator/Peripherals/Peripherals/Analog/STM32_ADC_Common.cs b/src/Emulator/Peripherals/Peripherals/Analog/STM32_ADC_Common.cs
index 184e6d4ff..70c02956b 100644
--- a/src/Emulator/Peripherals/Peripherals/Analog/STM32_ADC_Common.cs
+++ b/src/Emulator/Peripherals/Peripherals/Analog/STM32_ADC_Common.cs
@@ -1,5 +1,6 @@
//
// Copyright (c) 2010-2023 Antmicro
+// Copyright (c) 2023 OS Systems
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
@@ -26,6 +27,7 @@ namespace Antmicro.Renode.Peripherals.Analog
// *hasCalibration ----- Specifies whether the calibration factor and voltage regulator are available to the software.
// ADCs without this feature will still have the ADCAL flag available to trigger the calibration procedure,
// but not the CALFACT register nor the ADVREGEN field.
+ // hasHighCalAddress -- Specifies that ADC_CALFACT is at low address or high address.
// channelCount ------- Specifies the amount of available channels.
// Includes both internal sources (like the temperature sensor) as well as external.
// *hasPrescaler ------- Specifies whether the ADC contains a prescaler for the external clock input.
@@ -46,10 +48,10 @@ namespace Antmicro.Renode.Peripherals.Analog
public abstract class STM32_ADC_Common : IKnownSize, IProvidesRegisterCollection, IDoubleWordPeripheral
{
public STM32_ADC_Common(IMachine machine, double referenceVoltage, uint externalEventFrequency, int dmaChannel = 0, IDMA dmaPeripheral = null,
- int? watchdogCount = null, bool? hasCalibration = null, int? channelCount = null, bool? hasPrescaler = null,
+ int? watchdogCount = null, bool? hasCalibration = null, bool? hasHighCalAddress = null, int? channelCount = null, bool? hasPrescaler = null,
bool? hasVbatPin = null, bool? hasChannelSequence = null, bool? hasPowerRegister = null)
{
- if(!watchdogCount.HasValue || !hasCalibration.HasValue || !channelCount.HasValue || !hasPrescaler.HasValue ||
+ if(!watchdogCount.HasValue || !hasCalibration.HasValue || !hasHighCalAddress.HasValue || !channelCount.HasValue || !hasPrescaler.HasValue ||
!hasVbatPin.HasValue || !hasChannelSequence.HasValue || !hasPowerRegister.HasValue)
{
throw new ConstructionException("Missing configuration options");
@@ -73,12 +75,13 @@ public STM32_ADC_Common(IMachine machine, double referenceVoltage, uint external
this.machine = machine;
bool calibration = hasCalibration.Value;
+ bool calibrationHighAddress = hasHighCalAddress.Value;
bool prescaler = hasPrescaler.Value;
bool vbatPin = hasVbatPin.Value;
- bool channelSequence = hasChannelSequence.Value;
bool powerRegister = hasPowerRegister.Value;
ChannelCount = channelCount.Value;
WatchdogCount = watchdogCount.Value;
+ this.hasChannelSequence = hasChannelSequence.Value;
if(WatchdogCount < 1 || WatchdogCount > 3)
{
@@ -97,7 +100,7 @@ public STM32_ADC_Common(IMachine machine, double referenceVoltage, uint external
analogWatchdog3SelectedChannels = new IFlagRegisterField[ChannelCount];
}
- registers = new DoubleWordRegisterCollection(this, BuildRegistersMap(calibration, prescaler, vbatPin, channelSequence, powerRegister));
+ registers = new DoubleWordRegisterCollection(this, BuildRegistersMap(calibration, calibrationHighAddress, prescaler, vbatPin, powerRegister));
IRQ = new GPIO();
this.dmaChannel = dmaChannel;
@@ -182,18 +185,33 @@ private void ValidateChannel(int channel)
}
}
+ private void UpdateChannelConfig()
+ {
+ if(this.hasChannelSequence)
+ {
+ endOfChannelConfigFlag.Value = true;
+ UpdateInterrupts();
+ }
+ }
+
private void UpdateInterrupts()
{
var adcReady = adcReadyFlag.Value && adcReadyInterruptEnable.Value;
var analogWatchdog = analogWatchdogsInterruptEnable.Zip(analogWatchdogFlags, (enable, flag) =>
{
- return enable.Value && flag.Value;
+ return enable.Value && flag.Value;
}).Any(flag => flag);
var endOfSampling = endOfSamplingFlag.Value && endOfSamplingInterruptEnable.Value;
var endOfConversion = endOfConversionFlag.Value && endOfConversionInterruptEnable.Value;
var endOfSequence = endOfSequenceFlag.Value && endOfSequenceInterruptEnable.Value;
+ var endOfCalibration = endOfCalibrationFlag.Value && endOfCalibrationInterruptEnable.Value;
+ var endOfChannelConfig = false;
+ if(this.hasChannelSequence)
+ {
+ endOfChannelConfig = endOfChannelConfigFlag.Value && endOfChannelConfigInterruptEnable.Value;
+ }
- IRQ.Set(adcReady || analogWatchdog || endOfSampling || endOfConversion || endOfSequence);
+ IRQ.Set(adcReady || analogWatchdog || endOfSampling || endOfConversion || endOfSequence || endOfCalibration || endOfChannelConfig);
}
private void StartSampling()
@@ -331,7 +349,7 @@ private uint MilivoltsToSample(double sampleInMilivolts)
default:
throw new Exception("This should never have happend!");
}
-
+
uint referencedValue = (uint)Math.Round((sampleInMilivolts / (referenceVoltage * 1000)) * ((1 << resolutionInBits) - 1));
if(align.Value == Align.Left)
{
@@ -340,7 +358,7 @@ private uint MilivoltsToSample(double sampleInMilivolts)
return referencedValue;
}
- private Dictionary BuildRegistersMap(bool hasCalibration, bool hasPrescaler, bool hasVbatPin, bool hasChannelSequence, bool hasPowerRegister)
+ private Dictionary BuildRegistersMap(bool hasCalibration, bool hasHighCalAddress, bool hasPrescaler, bool hasVbatPin, bool hasPowerRegister)
{
var isrRegister = new DoubleWordRegister(this)
.WithFlag(0, out adcReadyFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: "ADRDY")
@@ -354,12 +372,12 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
machine.LocalTimeSource.ExecuteInNearestSyncedState((___) => SampleNextChannel());
}
}, name: "EOC")
- .WithFlag(3, out endOfSequenceFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: "EOSEQ")
+ .WithFlag(3, out endOfSequenceFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: "EOS")
.WithFlag(4, out adcOverrunFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: "OVR")
.WithReservedBits(5, 2)
.WithFlags(7, WatchdogCount, out analogWatchdogFlags, FieldMode.Read | FieldMode.WriteOneToClear, name: "AWD")
.WithReservedBits(7 + WatchdogCount, 4 - WatchdogCount)
- .WithReservedBits(13, 19);
+ .WithReservedBits(14, 18);
var interruptEnableRegister = new DoubleWordRegister(this)
.WithFlag(0, out adcReadyInterruptEnable, name: "ADRDYIE")
@@ -370,16 +388,16 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
.WithReservedBits(5, 2)
.WithFlags(7, WatchdogCount, out analogWatchdogsInterruptEnable, name: "AWDIE")
.WithReservedBits(7 + WatchdogCount, 4 - WatchdogCount)
- .WithReservedBits(13, 19)
+ .WithReservedBits(14, 18)
.WithWriteCallback((_, __) => UpdateInterrupts());
if(hasCalibration)
{
isrRegister
- .WithTaggedFlag("EOCAL", 11)
+ .WithFlag(11, out endOfCalibrationFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: "EOCAL")
.WithTaggedFlag("LDORDY", 12);
interruptEnableRegister
- .WithTaggedFlag("EOCALIE", 11)
+ .WithFlag(11, out endOfCalibrationInterruptEnable, name: "EOCALIE")
.WithTaggedFlag("LDORDYIE", 12);
}
else
@@ -390,20 +408,38 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
.WithReservedBits(11, 2);
}
+ if(this.hasChannelSequence)
+ {
+ isrRegister
+ .WithFlag(13, out endOfChannelConfigFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: "CCRDY");
+ interruptEnableRegister
+ .WithFlag(13, out endOfChannelConfigInterruptEnable, name: "CCRDYIE");
+ }
+ else
+ {
+ isrRegister
+ .WithReservedBits(13, 1);
+ interruptEnableRegister
+ .WithReservedBits(13, 1);
+ }
+
var configurationRegister1 = new DoubleWordRegister(this)
.WithFlag(0, out dmaEnabled, name: "DMAEN")
.WithTaggedFlag("DMACFG", 1)
// When fully configurable channel sequencer is available, the SCANDIR and RES fields are swapped
- .WithEnumField(hasChannelSequence ? 4 : 2, 1, out scanDirection, name: "SCANDIR")
- .WithEnumField(hasChannelSequence ? 2 : 3, 2, out resolution, name: "RES")
+ .WithEnumField(this.hasChannelSequence ? 4 : 2, 1, out scanDirection, writeCallback: (_, __) =>
+ {
+ UpdateChannelConfig();
+ }, name: "SCANDIR")
+ .WithEnumField(this.hasChannelSequence ? 2 : 3, 2, out resolution, name: "RES")
.WithEnumField(5, 1, out align, name: "ALIGN")
- .WithTag("EXTSEL", 6, 2)
+ .WithTag("EXTSEL", 6, 3)
.WithReservedBits(9, 1)
- .WithValueField(10, 2, writeCallback: (_, val) =>
- {
+ .WithValueField(10, 2, writeCallback: (_, val) =>
+ {
// On hardware it is possible to configure on which edge should the trigger fire
// This Peripheral mocks external trigger using `externalEventFrequency`, so we only distinguish between manual/external trigger
- externalTrigger = (val > 0);
+ externalTrigger = (val > 0);
}, name: "EXTEN")
.WithTaggedFlag("OVRMOD", 12)
.WithTaggedFlag("CONT", 13)
@@ -427,10 +463,13 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
.WithReservedBits(15, 1);
}
- if(hasChannelSequence)
+ if(this.hasChannelSequence)
{
configurationRegister1
- .WithTaggedFlag("CHSELRMOD", 21);
+ .WithFlag(21, valueProviderCallback: _ => false, writeCallback: (_, __) =>
+ {
+ UpdateChannelConfig();
+ }, name: "CHSELRMOD");
}
else
{
@@ -439,14 +478,20 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
}
var configurationRegister2 = new DoubleWordRegister(this)
- .WithReservedBits(0, 30)
+ .WithFlag(0, name: "OVSE")
+ .WithReservedBits(1, 1)
+ .WithFlags(2, 3, name: "OVSR")
+ .WithFlags(5, 4, name: "OVSS")
+ .WithTag("TOVS", 9, 1)
+ .WithReservedBits(10, 20)
.WithTag("CKMODE", 30, 2);
var commonConfigurationRegister = new DoubleWordRegister(this)
.WithReservedBits(0, 18)
.WithTaggedFlag("VREFEN", 22)
.WithTaggedFlag("TSEN", 23)
- .WithReservedBits(25, 7);
+ .WithTaggedFlag("LFMEN", 25)
+ .WithReservedBits(26, 6);
if(hasPrescaler)
{
@@ -481,7 +526,7 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
{
enabled = true;
adcReadyFlag.Value = true;
- UpdateInterrupts();
+ UpdateInterrupts();
}
}, name: "ADEN")
// Reading one from below field would mean that command is in progress. This is never the case in this model
@@ -511,8 +556,13 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
sequenceInProgress = false;
}
}, name: "ADSTP")
- .WithReservedBits(5, 25)
- .WithTaggedFlag("ADCAL", 31)
+ .WithReservedBits(5, 22)
+ .WithTag("ADVREGEN", 28, 1)
+ .WithReservedBits(29, 2)
+ .WithFlag(31, valueProviderCallback: _ => false, writeCallback: (_, __) =>
+ {
+ UpdateInterrupts();
+ }, name: "ADCAL")
},
{(long)Registers.Configuration1, configurationRegister1},
{(long)Registers.Configuration2, configurationRegister2},
@@ -525,6 +575,7 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
valueProviderCallback: (id, __) => channelSelected[id],
writeCallback: (id, _, val) => { this.Log(LogLevel.Debug, "Channel {0} enable set as {1}", id, val); channelSelected[id] = val; })
.WithReservedBits(ChannelCount, 32 - ChannelCount)
+ .WithWriteCallback((_, __) => UpdateChannelConfig())
},
{(long)Registers.DataRegister, new DoubleWordRegister(this)
.WithValueField(0, 16, out data, FieldMode.Read, readCallback: (_, __) =>
@@ -575,7 +626,8 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
if(hasCalibration)
{
- registers.Add((long)Registers.CalibrationFactor, new DoubleWordRegister(this)
+ long regAddr = hasHighCalAddress ? (long)Registers.CalibrationFactorHigh : (long)Registers.CalibrationFactorLow;
+ registers.Add(regAddr, new DoubleWordRegister(this)
.WithValueField(0, 7, name: "CALFACT")
.WithReservedBits(7, 25));
}
@@ -587,7 +639,7 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
.WithTaggedFlag("DPD", 1) // Deep-power-down mode
.WithReservedBits(2, 30));
}
-
+
return registers;
}
@@ -596,12 +648,13 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
private bool externalTrigger;
private bool sequenceInProgress;
private bool awaitingConversion;
+ private bool hasChannelSequence;
private bool[] channelSelected;
-
+
private IEnumRegisterField align;
private IEnumRegisterField resolution;
private IEnumRegisterField scanDirection;
-
+
private IFlagRegisterField dmaEnabled;
private IFlagRegisterField analogWatchdogEnable;
private IFlagRegisterField startFlag;
@@ -613,14 +666,18 @@ private Dictionary BuildRegistersMap(bool hasCalibrati
private IFlagRegisterField endOfConversionFlag;
private IFlagRegisterField endOfSamplingFlag;
private IFlagRegisterField endOfSequenceFlag;
+ private IFlagRegisterField endOfCalibrationFlag;
+ private IFlagRegisterField endOfChannelConfigFlag;
private IFlagRegisterField adcOverrunInterruptEnable;
private IFlagRegisterField adcReadyInterruptEnable;
private IFlagRegisterField[] analogWatchdogsInterruptEnable;
private IFlagRegisterField endOfConversionInterruptEnable;
private IFlagRegisterField endOfSamplingInterruptEnable;
private IFlagRegisterField endOfSequenceInterruptEnable;
+ private IFlagRegisterField endOfCalibrationInterruptEnable;
+ private IFlagRegisterField endOfChannelConfigInterruptEnable;
private IFlagRegisterField analogWatchdogSingleChannel;
-
+
private IValueRegisterField data;
// Watchdog 1 either watches all channels or a single channel
private IValueRegisterField analogWatchdogChannel;
@@ -683,7 +740,8 @@ private enum Registers
Watchdog2Configuration = 0xA0, // ADC_AWD2CR
Watchdog3Configuration = 0xA4, // ADC_AWD3CR
// Gap intended
- CalibrationFactor = 0xC4, // ADC_CALFACT
+ CalibrationFactorLow = 0xB4, // ADC_CALFACT (C0/L0/G0)
+ CalibrationFactorHigh = 0xC4, // ADC_CALFACT (WBA)
// Gap intended
CommonConfiguration = 0x308, // ADC_CCR
}