-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
269 lines (268 loc) · 15.2 KB
/
index.html
File metadata and controls
269 lines (268 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
<!DOCTYPE html>
<html>
<head>
<title> MCP4725 D/A Converter </title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<h1>Fast Function to Write Samples in the MCP4725</h1>
<div class="content1">
<h2> Motivation </h2>
<ul>
<li> Simple microcontrollers don't have a D/A converter as an internal peripheral, then the use of an external D/A converter with I2C protocol is very interesting, since the hardware scheme is easy to implement.</li>
<li> The MCP4725 is a low-power, high accuracy, single channel, 12-bit buffered voltage output Digital-to-Analog Converter.
<li> Considering the Arduino Platform, there are several available libraries to communicate with the MCP4725, as instance,"Adafruit_MCP4725.h" and "MCP4725.h", or even a routine written with functions from the "Wire.h" library. </li>
<li> As the I2C is a serial communication protocol (slower than parallel transmission), the data transfer time is the one of bottlenecks when defining the sample rate for the signal generation in the MCP4725.</li>
<li> In this page we will show the write access time of the MCP4725, considering the mentioned libraries, and propose a faster solution.</li>
</ul>
</div>
<div>
<h2> The Plant </h2>
<p> The scheme of connection of the Arduino UNO to the MCP4725 is shown in Fig. 1. </p>
<figure>
<img src="Arduino_MCP4725.png" style="width:50%">
<figcaption>Fig.1 - Arduino UNO and MCP4725 connection scheme</figcaption>
</figure>
</div>
<div class="content2">
<h2> Distinct Library Functions and The Write Access Time </h2> <!--This is a comment. Comments are not displayed in the browser-->
<ul>
<li> The best way to define the sample rate to send samples to MCP4725 is to use a timer hardware interrupt, as instance, the Timer1 Overflow Interrupt. </li>
<li> Considering a constant sample vector, it is possible to store the samples in the program memory, where there are more space, or keep them in RAM memory, if the application uses a small amount of data. </li>
<li> In some applications the samples are calculated in real time, but this implies a significant computational burden. As the Atmega328P is a simple fixed point microcontroller, only simple calculations can be executed for a given sample rate. In the presented example, the samples are calculated in real-time, implying a limited sample rate (2kHz). </li>
</ul>
<!--<table style="width:80%"> -->
<table>
<tr>
<th><h3> Library or Function </h3></th>
<th><h3> ISR Code Example </h3> <p style="color:Tomato;">(Only the write code snippet in the MCP4725!) </p> </th>
<th><h3> Time Duration Pulse </h3></th>
</tr>
<tr>
<th><a href="https://github.com/adafruit/Adafruit_MCP4725">Adafruit_MCP4725 Library</a></th>
<th>
<div class="code_div">
<p><code>
ISR(TIMER1_OVF_vect)<br>
{<br>
  :<br>
  sei();//enable TWI interrupt recognition inside Timer 1 ISR<br>
  PINB |=0x01; //pulse for time measurement<br>
  dac.setVoltage(sample, false); //send sample<br>
  PINB |=0x01; //toggle PB0<br>
  :<br>
}
</code> </p>
</div>
</th>
<th>
<figure>
<img src="Adafruit_MCP4725_Write_function.png" style="width:50%">
<figcaption>Fig. 2. The square pulse generated by the version 1 code during the sample writing into MCP4725</figcaption>
</figure>
</th>
</tr>
<tr>
<th><a href="https://github.com/RobTillaart/MCP4725"> MCP4725 Library</a></th>
<th>
<div class="code_div">
<p><code>
ISR(TIMER1_OVF_vect)<br>
{<br>
  :<br>
  sei();//enable TWI interrupt recognition inside Timer 1 ISR<br>
  PINB |=0x01; //pulse for time measurement<br>
  MCP.setValue(sample); //send sample<br>
  PINB |=0x01; //toggle PB0<br>
  :<br>
}
</code> </p>
</div>
</th>
<th>
<figure>
<img src="MCP4725_Write_function.png" style="width:50%">
<figcaption>Fig. 3. The square pulse generated by the version 2 code during the sample writing into MCP4725</figcaption>
</figure>
</th>
</tr>
<tr>
<th><a href="https://github.com/arduino/ArduinoCore-avr/tree/master/libraries/Wire"> Wire Library</a></th>
<th>
<div class="code_div">
<p><code>
ISR(TIMER1_OVF_vect)<br>
{<br>
  :<br>
  sei();//enable TWI interrupt recognition inside Timer 1 ISR<br>
  PINB |=0x01; //pulse for time measurement<br>
  DA_Wire_Write(sample); //send sample<br>
  PINB |=0x01; //toggle PB0<br>
  :<br>
}<br>
void DA_Wire_Write(int sample)<br>
{ <br>
  Wire.beginTransmission(MCP4725_address);<br>
  Wire.write((sample & 0x0FFF) >> 8); //send MSB:command+upper nibble<br>
  Wire.write(sample); //send LSB<br>
  Wire.endTransmission();<br>
}
</code> </p>
</div>
</th>
<th>
<figure>
<img src="Wire_Write_function.png" style="width:50%">
<figcaption>Fig. 4. The square pulse generated by the version 3 code during the sample writing into MCP4725</figcaption>
</figure>
</th>
</tr>
<tr>
<th> Direct Register Access Model </th>
<th>
<div class="code_div">
<p><code>
ISR(TIMER1_OVF_vect)<br>
{<br>
  :<br>
  PINB |=0x01; //pulse for time measurement<br>
  error = DA_Fast_Write(sample); //send sample<br>
  PINB |=0x01; //toggle PB0<br>
  :<br>
}<br>
int DA_Fast_Write(int sample) //direct register Access <br>
{ <br>
  sample &= 0x0FFF; //saturation in case the argument > 4095 <br>
  uint8_t LSB = (uint8_t)sample; //get LSB of data <br>
  uint8_t MSB = (uint8_t)(sample>>8); //get MSB of data <br>
  //D7-D4=0000 => Power Down Normal Mode and Fast Mode command <br>
<br>
  TWCR |= 0xA0 ; //set TWI START Condition Bit <br>
  while (!(TWCR & 0x80)); // wait until the start condition has been sent <br>
  if ((TWSR & 0xF8) != 0x08) return 1; //mask the prescaler bits => status <br>
  TWCR &= 0x5F ; //clear TWSTA <br>
<br>
  TWDR = MCP4725_Address<<1 //SLA+W = Slave address+write bit command (0) <br>
  TWCR |= 0x80; // start transmission of address <br>
  while (!(TWCR & 0x80)); // wait until SLA+W has been transmitted <br>
  if ((TWSR & 0xF8) != 0x18) return 2; //mask the prescaler bits => status <br>
<br>
  TWDR = MSB; //load MSB data do TWDR <br>
  TWCR |=0x80; // start transmission of MSB data <br>
  while (!(TWCR & 0x80)); // wait until MSB has been transmitted <br>
  if ((TWSR & 0xF8) != 0x28) return 3; //mask the prescaler bits => status <br>
<br>
  TWDR = LSB; //load LSB data do TWDR <br>
  TWCR |= 0x80; //start transmission of LSB data <br>
  while (!(TWCR & 0x80)); // wait until LSB has been transmitted <br>
  if ((TWSR & 0xF8) != 0x28) return 4; //mask the prescaler bits => status <br>
  TWCR |=0x90; // set TWI STOP Condition Bit <br>
  return 0; // return zero if writing process was succeeded <br>
}
</code> </p>
</div>
</th>
<th>
<figure>
<img src="Alternative_Write_function.png" style="width:50%">
<figcaption>Fig. 5. The square pulse generated by the version 4 code during the sample writing into MCP4725</figcaption>
</figure>
</th>
</tr>
</table>
<h3> Notes </h3>
<ul>
<li> The square pulses present in the oscilloscope figures are relative to the signal on pin PB0 (Arduino UNO), and were obtained considering a sampling rate of 2kHz and an I2C clock of 400kHz. The pulse widths are proportional to the duration of the process of writing a sample to the MCP4725. See the instructions to set and clear (toggle) the PB0 bit in the code snippet. </li>
<li> The libraries "Adafruit_MCP4725.h", "MCP4725.h" and "Wire.h" use the hardware interrupt of TWI, then if a MCP4725 write function is included in an ISR (Timer 1 Overflow ISR, as instance), it is necessary to enable the global interrupt flag inside of the ISR, otherwise the code won't work. The Atmega328P disables the global interrupt flag when an interrupt is acknowledged, as default. </li>
<li> The proposed function presents the lowest write cycle time among the presented options. In this case, it is not necessary to enable the global interrupt flag within the ISR.</li>
<li> The default clock speed for the TWI is 100kHz in the libraries "MCP4725.h" and "Wire.h", so change it to 400kHz, if it is the case.</li>
<li> The proposed function uses the "Fast Mode Write Command" of the MCP4725 (see MCP4725 Datasheet). Some modifications are required to perform other write or read commands.</li>
<li> Links to the complete codes are provided below. </li>
</ul>
</div>
<div class="content3">
<h2> Waveform Generation - Sampling Rate of 2kHz</h2>
<ul>
<li> Assuming a 50Hz sine waveform, any of the writing options on the MCP4725 can be used for a 2kHz sampling rate. The sampling rate can be higher if the sinusoid samples are previously calculated and inserted into a vector in memory. In the presented example, the samples are calculated by the ISR.</li>
<li> All codes generate the same sine waveform shown in Fig. 6. </li>
<li> The square pulses shown in Figs. 7 to 10 represent the time duration of the ISR (calculations plus MCP4725 writing). The sample calculation takes around 170 microseconds to be executed (CPU clock at 16MHz).</li>
</ul>
<figure>
<img src="Sine_wave_50Hz_Sample_Rate_2kHz.png" style="width:50%">
<figcaption>Fig.6. Sine waveform generated by the code in the MCP4725 output </figcaption>
</figure>
<table>
<tr>
<th><h4> Result using the Adrafruit_MCP4725 Library </h4>
<figure>
<img src="ISR_pulse_Adafruit_MCP4725.png" style="width:60%">
<figcaption>Fig.7. Square pulse used to measure the time duration of the ISR - Version 1 Code </figcaption>
</figure>
</th>
<th><h4> Result using the MCP4725 Library </h4>
<figure>
<img src="ISR_pulse_MCP4725.png" style="width:60%">
<figcaption>Fig.8. Square pulse used to measure the time duration of the ISR - Version 2 Code </figcaption>
</figure>
</th>
</tr>
<tr>
<th><h4> Result using the Wire Library </h4>
<figure>
<img src="ISR_pulse_Wire_lib.png" style="width:60%">
<figcaption>Fig.9. Square pulse used to measure the time duration of the ISR - Version 3 Code </figcaption>
</figure>
</th>
<th><h4> Result using TWI Direct Register Access </h4>
<figure>
<img src="ISR_pulse_TWI_Direct_Access_Register.png" style="width:60%">
<figcaption>Fig.10. Square pulse used to measure the time duration of the ISR - Version 4 Code </figcaption>
</figure>
</th>
</tr>
</table>
</div>
<div class="content4">
<h2> Waveform Generation - Sampling Rate of 4kHz</h2>
<table>
<tr>
<th><h4> Sine Waveform of 50Hz </h4>
<figure>
<img src="Sine_wave_50Hz_Sample_Rate_4kHz.png" style="width:60%">
<figcaption> Fig.11. Sine waveform generated by the code in the MCP4725 output </figcaption>
</figure>
</th>
<th><h4> Result using TWI Direct Register Access </h4>
<figure>
<img src="ISR_pulse_TWI_Direct_Access_Register_4kHz.png" style="width:60%">
<figcaption>Fig.12. Square pulse used to measure the time duration of the ISR - Version 4 Code </figcaption>
</figure>
</th>
</tr>
</table>
<ul>
<li> Considering the version 4 of the code, the sampling rate was doubled, generating the result of Fig. 11, where we observed a better definition of the signal. It is not possible to run code from versions 1 to 3 at this sample rate.</li>
<li> As we can see in Fig. 12, the maximum speed for real-time execution has been reached, so it is not possible to increase the sample rate in this strategy. To do this, one option would be to use previously calculated samples, avoiding the calculation within the ISR. </li>
<li> For generating more complex signals in real-time calculation, or using higher sampling rates, a faster platform should be considered.</li>
<li> If you change the sampling rate, don't forget to update the time integral step of the angular frequency.</li>
</ul>
</div>
<div class="content5">
<h2> Complete Code Links </h2>
<ul>
<li><a href="https://github.com/Ernane-AAC/MCP4725_Fast_Write_Access/tree/main/MCP4725_Sinewave_ver1">Version 1: Sine Wave Geration on MCP4725 using Adafruit_MCP4725 Library</a></li>
<li><a href="https://github.com/Ernane-AAC/MCP4725_Fast_Write_Access/tree/main/MCP4725_Sinewave_ver2">Version 2: Sine Wave Geration on MCP4725 using MCP4725 Library</a></li>
<li><a href="https://github.com/Ernane-AAC/MCP4725_Fast_Write_Access/tree/main/MCP4725_Sinewave_ver3">Version 3: Sine Wave Geration on MCP4725 using Wire Library</a></li>
<li><a href="https://github.com/Ernane-AAC/MCP4725_Fast_Write_Access/tree/main/MCP4725_Sinewave_ver4">Version 4: Sine Wave Geration on MCP4725 using TWI Direct Access Register Library</a></li>
</ul>
</div>
<div>
<h2> Conclusion </h2>
<ul>
<li>Considering the direct register access in an Arduino UNO application, it can be a way to exploit its capability, avoiding the changing for a more complex and expensive platform.</li>
<li>The Atmega328P datasheet is quite precise and give you all information you need to do that. </li>
<li>The lack of information is the main problem to develop advanced application in the modern microcontrollers. For some of them, the datasheet is short, confusing and you are hostage to the existing libraries.</li>
</ul>
</div>
</body>
</html>