Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
See what's new at GitLab
4
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Switch to GitLab Next
Sign in / Register
Toggle navigation
C
crowbx
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Iterations
Merge Requests
0
Merge Requests
0
Requirements
Requirements
List
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Test Cases
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issue
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacob Vosmaer
crowbx
Commits
6a355548
Commit
6a355548
authored
May 02, 2020
by
Jacob Vosmaer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove forward declarations
parent
70a997ec
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
298 additions
and
340 deletions
+298
-340
crowbx.c
crowbx.c
+298
-340
No files found.
crowbx.c
View file @
6a355548
...
...
@@ -11,185 +11,235 @@
#include "vibrato_rate.h"
#include "voice.h"
uint16_t
clamp_add
(
uint16_t
x
,
int16_t
delta
);
uint16_t
clamp_add
(
uint16_t
x
,
int16_t
delta
)
{
if
(
delta
<
0
)
{
uint16_t
sub
=
-
delta
;
if
(
x
<
sub
)
{
return
0
;
}
return
x
-
sub
;
}
void
dac_init
(
void
);
uint16_t
dac_pitch_delta
(
uint8_t
semitones
);
void
dac_send
(
uint8_t
command
,
uint16_t
data
);
uint16_t
dac_note_pitch
(
uint8_t
voice
,
uint8_t
note
);
uint8_t
dac_command_set
(
uint8_t
voice
);
uint16_t
add
=
delta
;
if
(
UINT16_MAX
-
x
<
add
)
{
return
UINT16_MAX
;
}
return
x
+
add
;
}
void
gate_init
(
void
);
void
gate_on
(
uint8_t
voice
);
void
gate_on_legato
(
uint8_t
voice
);
void
gate_off
(
uint8_t
voice
);
void
gate_update
(
uint8_t
delta
);
uint16_t
dac_pitch_delta
(
uint8_t
semitones
)
{
if
(
semitones
>=
NUM_SEMITONES
)
{
semitones
=
NUM_SEMITONES
-
1
;
}
enum
{
NUM_NOTES
=
128
};
return
dac_semitone_offset
(
semitones
);
}
int16_t
vibrato_detune
(
void
);
void
vibrato_update
(
uint8_t
delta
);
void
vibrato_set_rate
(
uint8_t
val
);
void
vibrato_set_depth
(
uint8_t
val
);
uint8_t
spi_io
(
uint8_t
data
)
{
SPDR
=
data
;
loop_until_bit_is_set
(
SPSR
,
SPIF
);
return
SPDR
;
}
void
pitch_set_note
(
uint8_t
voice
,
uint8_t
note
,
int16_t
detune
);
void
pitch_set_bend
(
uint16_t
bend
);
uint16_t
pitch_delta
(
uint8_t
semitones
);
void
pitch_update_clock
(
uint8_t
delta
);
void
pitch_env_set_decay
(
uint8_t
decay
);
void
pitch_env_set_amount
(
uint8_t
amount
);
void
pitch_env_trigger
(
uint8_t
voice
);
#define DAC_SYNC PORTB1
#define MOSI PORTB3
#define MISO PORTB4
#define SCK PORTB5
#define SS PORTB2
void
poly_init
(
void
);
void
poly_note_on
(
uint8_t
note
);
void
poly_note_off
(
uint8_t
note
);
uint8_t
dac_command_set
(
uint8_t
v
)
{
struct
dac
*
d
=
dac
(
v
);
if
(
!
d
)
{
return
0
;
};
void
poly2_init
(
void
);
void
poly2_note_on
(
uint8_t
note
);
void
poly2_note_off
(
uint8_t
note
);
return
0
b00110000
|
d
->
channel
;
}
void
mono_init
(
void
);
void
mono_note_on
(
uint8_t
note
);
void
mono_note_off
(
uint8_t
note
);
void
mono_control_change
(
uint8_t
ctl
,
uint8_t
val
);
void
dac_send
(
uint8_t
command
,
uint16_t
data
)
{
PORTB
&=
~
_BV
(
DAC_SYNC
);
spi_io
(
command
);
spi_io
(
data
>>
8
);
spi_io
(
data
&
0xff
);
PORTB
|=
_BV
(
DAC_SYNC
);
}
struct
{
void
(
*
init
)(
void
);
void
(
*
note_on
)(
uint8_t
n
);
void
(
*
note_off
)(
uint8_t
n
);
void
(
*
control_change
)(
uint8_t
ctl
,
uint8_t
val
);
}
programs
[]
=
{
{
poly_init
,
poly_note_on
,
poly_note_off
,
0
},
{
poly2_init
,
poly2_note_on
,
poly2_note_off
,
0
},
{
mono_init
,
mono_note_on
,
mono_note_off
,
mono_control_change
},
};
void
spi_init
(
void
)
{
// slave select, sck, mosi all as output
DDRB
|=
_BV
(
SS
)
|
_BV
(
MOSI
)
|
_BV
(
SCK
);
// Make sure current_program is not 0, so program_change(0) below is not a
// no-op.
uint8_t
current_program
=
ARRAY_SIZE
(
programs
);
PORTB
|=
_BV
(
MISO
);
// MISO pull-up to save power
void
io_init
(
void
)
{
// More outputs to save power; PORTB6/7 is XTAL
DDRB
=
_BV
(
PORTB0
)
|
_BV
(
PORTB1
)
|
_BV
(
PORTB2
)
|
_BV
(
PORTB3
)
|
_BV
(
PORTB4
)
|
_BV
(
PORTB5
);
// spi enable
SPCR
|=
_BV
(
SPE
)
|
_BV
(
MSTR
)
|
_BV
(
CPOL
);
SPSR
|=
_BV
(
SPI2X
);
}
// More outputs to save power; PORTC6 is reset, "PORTC7" does not exist
// (?)
DDRC
=
_BV
(
PORTC0
)
|
_BV
(
PORTC1
)
|
_BV
(
PORTC2
)
|
_BV
(
PORTC3
)
|
_BV
(
PORTC4
)
|
_BV
(
PORTC5
);
void
dac_init
(
void
)
{
DDRB
|=
_BV
(
DAC_SYNC
);
// PORTD0 is UART RX. Set all other PORTD pins output, to save power.
DDRD
=
_BV
(
PORTD1
)
|
_BV
(
PORTD2
)
|
_BV
(
PORTD3
)
|
_BV
(
PORTD4
)
|
_BV
(
PORTD5
)
|
_BV
(
PORTD6
)
|
_BV
(
PORTD7
);
spi_init
();
// Run Timer0 (8-bit timer) at F_CPU/1024 -- faster incurs roll arounds.
// Use 16 bit timer?
TCCR0B
|=
_BV
(
CS00
)
|
_BV
(
CS02
);
// dac sync high for power saving
PORTB
|=
_BV
(
DAC_SYNC
);
// Assuming f_cpu is 16 MHz, this gives us 31250 baud
UBRR0H
=
0
;
UBRR0L
=
31
;
_delay_ms
(
1
);
// let dac boot up
}
// Enable UART0 RX and RX interrupt
UCSR0B
=
_BV
(
RXEN0
)
|
_BV
(
RXCIE0
);
uint16_t
dac_note_pitch
(
uint8_t
v
,
uint8_t
note
)
{
enum
{
MIDI_LOWEST
=
24
};
struct
dac
*
d
=
dac
(
v
);
if
(
!
d
)
{
return
0
;
};
// Enable pull-up resistor on RX pin
PORTD
|=
_BV
(
PORTD0
);
// Don't go lower than "C0"
if
(
note
<
MIDI_LOWEST
)
{
note
=
MIDI_LOWEST
;
}
if
(
note
>
127
)
{
note
=
127
;
}
// Globally enable interrupts
sei
();
uint8_t
octave
=
(
note
-
MIDI_LOWEST
)
/
NUM_SEMITONES
;
if
(
octave
>
7
)
{
return
0
;
}
return
clamp_add
(
d
->
offset
+
octave
*
d
->
scale
,
dac_semitone_offset
((
note
-
MIDI_LOWEST
)
%
NUM_SEMITONES
));
}
void
program_change
(
uint8_t
pgm
)
{
if
((
pgm
>=
ARRAY_SIZE
(
programs
))
||
(
pgm
==
current_program
))
{
static
uint8_t
gate_counters
[
NUM_VOICES
];
void
gate_on_legato
(
uint8_t
v
)
{
struct
gate
*
g
=
gate
(
v
);
if
(
!
g
)
{
return
;
}
current_program
=
pgm
;
gate_init
();
programs
[
current_program
].
init
();
*
(
g
->
port
)
|=
g
->
mask
;
}
void
do_clock_update
(
void
)
{
static
uint8_t
prev_clock
=
0
;
void
gate_off
(
uint8_t
v
)
{
struct
gate
*
g
=
gate
(
v
);
if
(
!
g
)
{
return
;
}
uint8_t
clock
=
TCNT
0
;
uint8_t
delta
=
clock
-
prev_clock
;
prev_clock
=
clock
;
gate_counters
[
v
]
=
0
;
*
(
g
->
port
)
&=
~
(
g
->
mask
)
;
}
if
(
delta
==
0
)
{
void
gate_on
(
uint8_t
v
)
{
if
(
v
>=
NUM_VOICES
)
{
return
;
}
vibrato_update
(
delta
);
gate_update
(
delta
);
pitch_update_clock
(
delta
);
gate_off
(
v
);
// RETRIG_DELAY should be as low as possible to reduce MIDI latency, but
// high enough to allow the hardware envelope generators in the CrowBX
// to reset. I know 0x3f to be too low.
enum
{
RETRIG_DELAY
=
0x5f
};
gate_counters
[
v
]
=
RETRIG_DELAY
;
}
void
handle_cc
(
uint8_t
cc
,
uint8_t
val
)
{
if
((
cc
>
127
)
||
(
val
>
127
))
{
return
;
void
gate_update
(
uint8_t
delta
)
{
for_each_voice
(
v
)
{
if
(
gate_counters
[
v
]
==
0
)
{
continue
;
}
if
(
delta
<
gate_counters
[
v
])
{
gate_counters
[
v
]
-=
delta
;
continue
;
}
gate_counters
[
v
]
=
0
;
gate_on_legato
(
v
);
pitch_env_trigger
(
v
);
}
}
switch
(
cc
)
{
case
CC_PITCH_ENV_DECAY
:
pitch_env_set_decay
(
val
);
break
;
case
CC_PITCH_ENV_AMOUNT
:
pitch_env_set_amount
(
val
);
break
;
case
CC_MOD_WHEEL
:
vibrato_set_depth
(
val
);
break
;
case
CC_VIB_RATE
:
vibrato_set_rate
(
val
);
break
;
void
gate_init
(
void
)
{
for_each_voice
(
v
)
{
gate_off
(
v
);
}
}
enum
{
NUM_NOTES
=
128
};
struct
{
uint32_t
phase
;
int16_t
amount
;
uint8_t
rate
;
uint8_t
depth
;
}
vibrato
;
void
vibrato_set_rate
(
uint8_t
val
)
{
vibrato
.
rate
=
val
;
}
void
vibrato_set_depth
(
uint8_t
val
)
{
vibrato
.
depth
=
val
;
}
int16_t
vibrato_detune
(
void
)
{
return
vibrato
.
amount
;
}
void
vibrato_update
(
uint8_t
delta
)
{
enum
{
VIB_MID
=
INT16_MAX
>>
1
};
vibrato
.
phase
+=
delta
*
pgm_read_float
(
&
(
vibrato_rate_table
[
vibrato
.
rate
]));
uint16_t
phase16
=
vibrato
.
phase
>>
16
;
if
(
phase16
<
INT16_MAX
)
{
vibrato
.
amount
=
phase16
-
VIB_MID
;
}
else
{
vibrato
.
amount
=
phase16
-
INT16_MAX
;
vibrato
.
amount
=
INT16_MAX
-
vibrato
.
amount
;
vibrato
.
amount
-=
VIB_MID
;
}
if
(
programs
[
current_program
].
control_change
)
{
programs
[
current_program
].
control_change
(
cc
,
val
);
float
pitch_factor
=
dac_pitch_delta
(
7
)
/
((
float
)
INT16_MAX
);
vibrato
.
amount
*=
pitch_factor
;
float
depth_factor
=
vibrato
.
depth
/
127
.
0
;
vibrato
.
amount
*=
depth_factor
;
if
(
!
vibrato
.
depth
)
{
vibrato
.
amount
=
0
;
}
}
int
main
(
void
)
{
io_init
();
dac_init
();
midi_init
();
program_change
(
0
);
struct
{
int16_t
bend_data
;
uint16_t
base_pitch
[
NUM_VOICES
];
}
pitch
;
for
(;;)
{
do_clock_update
();
void
pitch_set_note
(
uint8_t
voice
,
uint8_t
note
,
int16_t
detune
)
{
if
((
voice
>=
NUM_VOICES
)
||
(
note
>
127
))
{
return
;
}
uint8_t
status
,
data1
,
data2
;
if
(
!
midi_read
(
&
status
,
&
data1
,
&
data2
))
{
continue
;
}
pitch
.
base_pitch
[
voice
]
=
clamp_add
(
dac_note_pitch
(
voice
,
note
),
detune
);
}
switch
(
status
)
{
case
MIDI_NOTE_ON
:
if
(
data2
)
{
programs
[
current_program
].
note_on
(
data1
);
}
else
{
programs
[
current_program
].
note_off
(
data1
);
}
break
;
case
MIDI_NOTE_OFF
:
programs
[
current_program
].
note_off
(
data1
);
break
;
case
MIDI_PROGRAM_CHANGE
:
program_change
(
data1
);
break
;
case
MIDI_CONTROL_CHANGE
:
handle_cc
(
data1
,
data2
);
break
;
case
MIDI_PITCH_BEND
:
pitch_set_bend
((
data2
<<
7
)
|
data1
);
break
;
}
}
void
pitch_set_bend
(
uint16_t
bend
)
{
enum
{
BEND_CENTER
=
8192
,
BEND_MAX
=
16383
};
return
0
;
float
bend_factor
=
((
int16_t
)(
bend
&
BEND_MAX
)
-
BEND_CENTER
)
/
((
float
)
BEND_MAX
);
pitch
.
bend_data
=
dac_pitch_delta
(
4
)
*
bend_factor
;
}
void
pitch_update_clock
(
uint8_t
delta
)
{
pitch_env_update_clock
(
delta
);
for_each_voice
(
v
)
{
uint16_t
data
=
clamp_add
(
pitch
.
base_pitch
[
v
],
vibrato_detune
());
data
=
clamp_add
(
data
,
pitch_env_delta
(
v
)
*
(
float
)
dac_pitch_delta
(
5
));
data
=
clamp_add
(
data
,
pitch
.
bend_data
);
dac_send
(
dac_command_set
(
v
),
data
);
}
}
struct
{
...
...
@@ -310,73 +360,16 @@ void poly2_note_off(uint8_t note) {
return
;
}
l_delete
(
&
poly2
.
note_queue
,
note
);
for_each_voice
(
v
)
{
if
(
poly2
.
current_note
[
v
]
==
note
)
{
poly2
.
current_note
[
v
]
=
poly2
.
note_queue
.
sup
;
gate_off
(
v
);
l_pushl
(
&
poly2
.
available_voices
,
v
);
}
}
poly2_assign_available_voices
();
}
static
uint8_t
gate_counters
[
NUM_VOICES
];
void
gate_init
(
void
)
{
for_each_voice
(
v
)
{
gate_off
(
v
);
}
}
void
gate_on_legato
(
uint8_t
v
)
{
struct
gate
*
g
=
gate
(
v
);
if
(
!
g
)
{
return
;
}
*
(
g
->
port
)
|=
g
->
mask
;
}
void
gate_off
(
uint8_t
v
)
{
struct
gate
*
g
=
gate
(
v
);
if
(
!
g
)
{
return
;
}
gate_counters
[
v
]
=
0
;
*
(
g
->
port
)
&=
~
(
g
->
mask
);
}
void
gate_on
(
uint8_t
v
)
{
if
(
v
>=
NUM_VOICES
)
{
return
;
}
gate_off
(
v
);
// RETRIG_DELAY should be as low as possible to reduce MIDI latency, but
// high enough to allow the hardware envelope generators in the CrowBX
// to reset. I know 0x3f to be too low.
enum
{
RETRIG_DELAY
=
0x5f
};
gate_counters
[
v
]
=
RETRIG_DELAY
;
}
void
gate_update
(
uint8_t
delta
)
{
for_each_voice
(
v
)
{
if
(
gate_counters
[
v
]
==
0
)
{
continue
;
}
if
(
delta
<
gate_counters
[
v
])
{
gate_counters
[
v
]
-=
delta
;
continue
;
l_delete
(
&
poly2
.
note_queue
,
note
);
for_each_voice
(
v
)
{
if
(
poly2
.
current_note
[
v
]
==
note
)
{
poly2
.
current_note
[
v
]
=
poly2
.
note_queue
.
sup
;
gate_off
(
v
);
l_pushl
(
&
poly2
.
available_voices
,
v
);
}
gate_counters
[
v
]
=
0
;
gate_on_legato
(
v
);
pitch_env_trigger
(
v
);
}
poly2_assign_available_voices
();
}
struct
{
...
...
@@ -461,177 +454,142 @@ void mono_control_change(uint8_t ctl, uint8_t val) {
}
struct
{
uint32_t
phase
;
int16_t
amount
;
uint8_t
rate
;
uint8_t
depth
;
}
vibrato
;
void
(
*
init
)(
void
);
void
(
*
note_on
)(
uint8_t
n
);
void
(
*
note_off
)(
uint8_t
n
);
void
(
*
control_change
)(
uint8_t
ctl
,
uint8_t
val
);
}
programs
[]
=
{
{
poly_init
,
poly_note_on
,
poly_note_off
,
0
},
{
poly2_init
,
poly2_note_on
,
poly2_note_off
,
0
},
{
mono_init
,
mono_note_on
,
mono_note_off
,
mono_control_change
},
};
void
vibrato_set_rate
(
uint8_t
val
)
{
vibrato
.
rate
=
val
;
}
void
vibrato_set_depth
(
uint8_t
val
)
{
vibrato
.
depth
=
val
;
}
int16_t
vibrato_detune
(
void
)
{
return
vibrato
.
amount
;
}
// Make sure current_program is not 0, so program_change(0) below is not a
// no-op.
uint8_t
current_program
=
ARRAY_SIZE
(
programs
);
void
vibrato_update
(
uint8_t
delta
)
{
enum
{
VIB_MID
=
INT16_MAX
>>
1
};
void
io_init
(
void
)
{
// More outputs to save power; PORTB6/7 is XTAL
DDRB
=
_BV
(
PORTB0
)
|
_BV
(
PORTB1
)
|
_BV
(
PORTB2
)
|
_BV
(
PORTB3
)
|
_BV
(
PORTB4
)
|
_BV
(
PORTB5
);
vibrato
.
phase
+=
delta
*
pgm_read_float
(
&
(
vibrato_rate_table
[
vibrato
.
rate
]));
// More outputs to save power; PORTC6 is reset, "PORTC7" does not exist
// (?)
DDRC
=
_BV
(
PORTC0
)
|
_BV
(
PORTC1
)
|
_BV
(
PORTC2
)
|
_BV
(
PORTC3
)
|
_BV
(
PORTC4
)
|
_BV
(
PORTC5
);
uint16_t
phase16
=
vibrato
.
phase
>>
16
;
if
(
phase16
<
INT16_MAX
)
{
vibrato
.
amount
=
phase16
-
VIB_MID
;
}
else
{
vibrato
.
amount
=
phase16
-
INT16_MAX
;
vibrato
.
amount
=
INT16_MAX
-
vibrato
.
amount
;
vibrato
.
amount
-=
VIB_MID
;
}
// PORTD0 is UART RX. Set all other PORTD pins output, to save power.
DDRD
=
_BV
(
PORTD1
)
|
_BV
(
PORTD2
)
|
_BV
(
PORTD3
)
|
_BV
(
PORTD4
)
|
_BV
(
PORTD5
)
|
_BV
(
PORTD6
)
|
_BV
(
PORTD7
);
float
pitch_factor
=
dac_pitch_delta
(
7
)
/
((
float
)
INT16_MAX
);
vibrato
.
amount
*=
pitch_factor
;
// Run Timer0 (8-bit timer) at F_CPU/1024 -- faster incurs roll arounds.
// Use 16 bit timer?
TCCR0B
|=
_BV
(
CS00
)
|
_BV
(
CS02
);
float
depth_factor
=
vibrato
.
depth
/
127
.
0
;
vibrato
.
amount
*=
depth_factor
;
// Assuming f_cpu is 16 MHz, this gives us 31250 baud
UBRR0H
=
0
;
UBRR0L
=
31
;
if
(
!
vibrato
.
depth
)
{
vibrato
.
amount
=
0
;
}
}
// Enable UART0 RX and RX interrupt
UCSR0B
=
_BV
(
RXEN0
)
|
_BV
(
RXCIE0
);
enum
{
BEND_CENTER
=
8192
,
BEND_MAX
=
16383
,
};
// Enable pull-up resistor on RX pin
PORTD
|=
_BV
(
PORTD0
);
struct
{
int16_t
bend_data
;
uint16_t
base_pitch
[
NUM_VOICES
];
}
pitch
;
// Globally enable interrupts
sei
();
}
void
p
itch_set_note
(
uint8_t
voice
,
uint8_t
note
,
int16_t
detune
)
{
if
((
voice
>=
NUM_VOICES
)
||
(
note
>
127
))
{
void
p
rogram_change
(
uint8_t
pgm
)
{
if
((
pgm
>=
ARRAY_SIZE
(
programs
))
||
(
pgm
==
current_program
))
{
return
;
}
pitch
.
base_pitch
[
voice
]
=
clamp_add
(
dac_note_pitch
(
voice
,
note
),
detune
);
}
current_program
=
pgm
;
void
pitch_set_bend
(
uint16_t
bend
)
{
float
bend_factor
=
((
int16_t
)(
bend
&
BEND_MAX
)
-
BEND_CENTER
)
/
((
float
)
BEND_MAX
);
pitch
.
bend_data
=
dac_pitch_delta
(
4
)
*
bend_factor
;
gate_init
();
programs
[
current_program
].
init
();
}
void
pitch_update_clock
(
uint8_t
delta
)
{
pitch_env_update_clock
(
delta
)
;
void
do_clock_update
(
void
)
{
static
uint8_t
prev_clock
=
0
;
for_each_voice
(
v
)
{
uint16_t
data
=
clamp_add
(
pitch
.
base_pitch
[
v
],
vibrato_detune
());
data
=
clamp_add
(
data
,
pitch_env_delta
(
v
)
*
(
float
)
dac_pitch_delta
(
5
));
data
=
clamp_add
(
data
,
pitch
.
bend_data
);
dac_send
(
dac_command_set
(
v
),
data
);
}
}
uint8_t
clock
=
TCNT0
;
uint8_t
delta
=
clock
-
prev_clock
;
prev_clock
=
clock
;
uint16_t
dac_pitch_delta
(
uint8_t
semitones
)
{
if
(
semitones
>=
NUM_SEMITONES
)
{
semitones
=
NUM_SEMITONES
-
1
;
if
(
delta
==
0
)
{
return
;
}
return
dac_semitone_offset
(
semitones
);
}
uint8_t
spi_io
(
uint8_t
data
)
{
SPDR
=
data
;
loop_until_bit_is_set
(
SPSR
,
SPIF
);
return
SPDR
;
}
#define DAC_SYNC PORTB1
#define MOSI PORTB3
#define MISO PORTB4
#define SCK PORTB5
#define SS PORTB2
uint8_t
dac_command_set
(
uint8_t
v
)
{
struct
dac
*
d
=
dac
(
v
);
if
(
!
d
)
{
return
0
;
};
return
0
b00110000
|
d
->
channel
;
}
void
dac_send
(
uint8_t
command
,
uint16_t
data
)
{
PORTB
&=
~
_BV
(
DAC_SYNC
);
spi_io
(
command
);
spi_io
(
data
>>
8
);
spi_io
(
data
&
0xff
);
PORTB
|=
_BV
(
DAC_SYNC
);
}
void
spi_init
(
void
)
{
// slave select, sck, mosi all as output
DDRB
|=
_BV
(
SS
)
|
_BV
(
MOSI
)
|
_BV
(
SCK
);