AVRly - AVR Development Resources
mcp48x2_dac.c
Go to the documentation of this file.
1/******************************************************************************
2 @copyright Copyright © 2022 by Jason Duffy.
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21******************************************************************************/
22
23/**
24 * @file mcp48x2_dac.c
25 * @ingroup mcp48x2
26 * @author Jason Duffy
27 * @date 4th March 2022
28 * @brief Driver for the MCP4812 10 bit DAC (digital to analog converter) chip.
29 * @bug No known bugs.
30 * @details This file provides the basic low-level functionality for the
31 * MCP4812 10 bit DAC from Microchip. This driver currently only allows for a
32 * single DAC to be used, but this might be improved on at a later date. This
33 * driver was written using the datasheet for the Microchip MCP4812 chip, which
34 * can be found at the link below.
35 * @see https://ww1.microchip.com/downloads/en/DeviceDoc/20002249B.pdf
36 */
37
38#include <util/delay.h>
39#include <avr/interrupt.h>
40
41#include "mcp48x2_dac.h"
42#include "atmega_spi.h"
43#include "pin_defines.h"
44
45// Bit positions for configuration.
46#define CHANNEL_BIT 15U
47#define GAIN_BIT 13U
48#define SHUTDOWN_BIT 12U
49
50// Offset values for setting raw level value.
51#define TWELVE_BIT_LEVEL_OFFSET 0U
52#define TEN_BIT_LEVEL_OFFSET 2U
53#define EIGHT_BIT_LEVEL_OFFSET 4U
54
55// Offset values for setting millivolts.
56#define TWELVE_BIT_MV_OFFSET 0U
57#define TEN_BIT_MV_OFFSET 1U
58#define EIGHT_BIT_MV_OFFSET 3U
59
60/**
61 * File scope copy of dac_config_t pointer to store address of config object.
62 */
63static dac_config_t *p_config_global;
64
65/**
66 * File scope variable to store the bitshift value to account for resolution
67 * differences between chip models.
68 */
69static uint8_t mv_resolution_shift = 0;
70
71/**
72 * File scope variable to store the bitshift value to account for resolution
73 * differences between chip models.
74 */
75static uint8_t level_resolution_shift = 0;
76
77// Forward declarations of private helper functions.
78void chip_select(void);
79void chip_deselect(void);
80
81
82/*
83 * Initialisation routine (run once at startup).
84 * This function is to be called before using any other DAC functions.
85 * Instantiate the dac_config_t object first then pass it's address into and
86 * call init_lcd() before using any other lcd functions.
87 * @param p_config is a pointer to the dac_config_t object.
88 */
89void init_dac(dac_config_t *p_config)
90{
91 // Copy config object pointer to a variable with file scope.
92 p_config_global = p_config;
93
94 // Delay to allow power ramp up in device.
95 _delay_ms(500);
96
97 // Initialise SPI comms.
98 init_spi(lsb_first,
99 controller,
100 rising_edge,
101 sample_leading_edge,
102 cpu_clk_div_16,
103 single_speed);
104
105 DAC_CTRL_DDR |= (1 << DAC_CS); // Set chip select as output.
106 DAC_CTRL_PORT |= (1 << DAC_CS); // Set CS line high (not selected).
107 DAC_CTRL_DDR |= (1 << LDAC); // Set LDAC line (latch) as output.
108 DAC_CTRL_PORT |= (1 << LDAC); // Set LDAC high.
109
110 // Save the value to bitshift millivolts value by.
111 if (p_config_global->model == mcp4802)
112 {
113 mv_resolution_shift = EIGHT_BIT_MV_OFFSET;
114 level_resolution_shift = EIGHT_BIT_LEVEL_OFFSET;
115 }
116 else if (p_config_global->model == mcp4812)
117 {
118 mv_resolution_shift = TEN_BIT_MV_OFFSET;
119 level_resolution_shift = TEN_BIT_LEVEL_OFFSET;
120 }
121 else
122 {
123 mv_resolution_shift = TWELVE_BIT_MV_OFFSET;
124 level_resolution_shift = TWELVE_BIT_LEVEL_OFFSET;
125 }
126
127 // Establish latching settings
128 if (!p_config_global->sync_manually)
129 {
130 DAC_CTRL_PORT &= ~(1 << LDAC);
131 }
132
133 // Send new values to DAC
135}
136
137
138/*
139 * Sends a new millivolts value to be output on DAC (Along with config
140 * settings). For 8 or 10 bit models only.
141 * @param channel_a: true = Channel A, false = Channel B.
142 * @param millivolts: mV value to be output by DAC. Ensure that this value
143 * doesn't exceed the maxixum for the DAC model and gain setting.
144 */
145void dac_set_voltage(bool channel_a, uint16_t millivolts)
146{
147 // Manipulate mv value to suit register size of chip and gain setting.
148 if (channel_a)
149 {
150 if (p_config_global->channel_a.gain_low)
151 {
152 p_config_global->channel_a.level =
153 (millivolts >> mv_resolution_shift);
154 }
155 else
156 {
157 p_config_global->channel_a.level =
158 (millivolts >> (mv_resolution_shift + 1));
159 }
160 }
161 else
162 {
163 if (p_config_global->channel_b.gain_low)
164 {
165 p_config_global->channel_b.level =
166 (millivolts >> mv_resolution_shift);
167 }
168 else
169 {
170 p_config_global->channel_b.level =
171 (millivolts >> (mv_resolution_shift + 1));
172 }
173 }
174
175 // Send new values to DAC
177}
178
179
180/*
181 * Sends a new millivolts value to be output on DAC (Along with config
182 * settings). For 12 bit models only.
183 * @param channel_a: true = Channel A, false = Channel B.
184 * @param millivolts: mV value to be output by DAC. Ensure that this value
185 * doesn't exceed the maxixum for the DAC model and gain setting.
186 * @param fractional: true = millivolts value has 0.5mV added to it. Only to be
187 * used when gain_low is true.
188 */
189void dac_set_voltage_12_bit(bool channel_a,
190 uint16_t millivolts,
191 bool fractional)
192{
193 if (channel_a)
194 {
195 if(p_config_global->channel_a.gain_low)
196 {
197 p_config_global->channel_a.level =
198 (millivolts << (TWELVE_BIT_MV_OFFSET + 1));
199 // Add the 0.5mV value if required.
200 if (fractional == true)
201 {
202 p_config_global->channel_a.level += 1;
203 }
204 }
205 else
206 {
207 p_config_global->channel_a.level =
208 (millivolts << TWELVE_BIT_MV_OFFSET);
209 }
210 }
211 else
212 {
213 if(p_config_global->channel_b.gain_low)
214 {
215 p_config_global->channel_b.level =
216 (millivolts << (TWELVE_BIT_MV_OFFSET + 1));
217 // Add the 0.5mV value if required.
218 if (fractional == true)
219 {
220 p_config_global->channel_b.level += 1;
221 }
222 }
223 else
224 {
225 p_config_global->channel_b.level =
226 (millivolts << TWELVE_BIT_MV_OFFSET);
227 }
228 }
229
230 // Send new values to DAC
232}
233
234
235/*
236 * Applies new config settings. This function takes updated config settings for
237 * both channels of DAC, then re-sends data so that the new settings take
238 * effect.
239 */
241{
242 uint16_t channel_a_data = 0;
243 uint16_t channel_b_data = 0;
244
245 // Set up config bits in 16bit word to be sent.
246 channel_a_data &= ~(1 << CHANNEL_BIT); // Clear channel bit (Channel A).
247 channel_a_data |= (p_config_global->channel_a.gain_low << GAIN_BIT);
248 channel_a_data |= (p_config_global->channel_a.active << SHUTDOWN_BIT);
249
250 channel_b_data |= (1 << CHANNEL_BIT); // Set channel bit (Channel B).
251 channel_b_data |= (p_config_global->channel_b.gain_low << GAIN_BIT);
252 channel_b_data |= (p_config_global->channel_b.active << SHUTDOWN_BIT);
253
254 // Shift level into correct place for chip and OR it into our 16bit word.
255 channel_a_data |=
256 (p_config_global->channel_a.level << level_resolution_shift);
257
258 channel_b_data |=
259 (p_config_global->channel_b.level << level_resolution_shift);
260
261
262 // Send data for channel A to DAC over SPI.
263 chip_select();
264 spi_trade_word(channel_a_data);
266
267 // Send data for channel B to DAC over SPI.
268 chip_select();
269 spi_trade_word(channel_b_data);
271
272 // If sync_manually is set to false, pulse LDAC to update values.
273 if (!p_config_global->sync_manually)
274 {
275 pulse_latch();
276 }
277}
278
279
280/*
281 * Pulses LDAC pin pow for 1 microsecond. If sync_manually = true, call this
282 * function to latch the new voltage values into the output registers. If
283 * sync_manually = false, this function is called automatically by
284 * dac_reconfigure() or dac_set_voltage().
285 */
286void pulse_latch(void)
287{
288 DAC_CTRL_PORT &= ~(1 << LDAC);
289 _delay_us(1);
290 DAC_CTRL_PORT |= (1 << LDAC);
291}
292
293// ------------------------------------------------------------------------- //
294// ------------------------ Private Helper Functions ----------------------- //
295// ------------------------------------------------------------------------- //
296
297
298/**
299 * Private helper function - pulls CS line (Chip Select) low in order to begin
300 * SPI communication with DAC.
301 */
302void chip_select(void)
303{
304 DAC_CTRL_PORT &= ~(1 << DAC_CS);
305}
306
307/**
308 * Private helper function - pulls CS line (Chip Select) high to signal the end
309 * of SPI communication with DAC.
310 */
312{
313 DAC_CTRL_PORT |= (1 << DAC_CS);
314}
315
316
317/*** end of file ***/
void init_spi(spi_transfer_mode_t transfer_mode, spi_control_mode_t control_mode, spi_polarity_mode_t polarity_mode, spi_phase_mode_t phase_mode, spi_clk_rate_t clk_rate, spi_dbl_clk_mode_t dbl_clock)
Initialisation routine to set up SPI comms.
Definition: atmega_spi.c:53
uint16_t spi_trade_word(uint16_t data)
Sends out a 16bit word of data over spi (in two bytes) and returns the byte it receives.
Definition: atmega_spi.c:98
Driver for SPI communication between the ATmega328P and other SPI compatible devices.
void chip_select(void)
Private helper function - pulls CS line (Chip Select) low in order to begin SPI communication with DA...
Definition: mcp48x2_dac.c:302
void dac_set_voltage(bool channel_a, uint16_t millivolts)
Sends a new millivolts value to be output on DAC (Along with config settings).
Definition: mcp48x2_dac.c:145
void dac_set_voltage_12_bit(bool channel_a, uint16_t millivolts, bool fractional)
Sends a new millivolts value to be output on DAC (Along with config settings).
Definition: mcp48x2_dac.c:189
void init_dac(dac_config_t *p_config)
Initialisation routine (run once at startup).
Definition: mcp48x2_dac.c:89
void dac_reconfigure(void)
Applies new config settings.
Definition: mcp48x2_dac.c:240
void chip_deselect(void)
Private helper function - pulls CS line (Chip Select) high to signal the end of SPI communication wit...
Definition: mcp48x2_dac.c:311
void pulse_latch(void)
Pulses LDAC pin low for a brief time (1uS).
Definition: mcp48x2_dac.c:286
Driver for the MCP4812 10 bit DAC (digital to analog converter) chip.
Definitions for pin mapping (for CCS81 gas sensor)
Config struct for DAC.
Definition: mcp48x2_dac.h:76