-
Notifications
You must be signed in to change notification settings - Fork 6
/
firmware.c
211 lines (184 loc) · 5.72 KB
/
firmware.c
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
#include <avr/interrupt.h>
#include "ym2149.h"
#define BUF_SIZE 1536
// Possible RX states
#define RX_PROGRESS 1
#define RX_COMPLETE 2
#define RX_WAITING 3
#define RX_CHUNKLO 4
#define RX_WAITLO 5
// Possible statuses sent on the UART
#define RX_ACK 0
#define RX_BAD 255
// Possible Samples states
#define SMP_TSHI 0
#define SMP_TSLO 1
#define SMP_WAIT 3
#define SMP_ADDR 4
#define SMP_VAL 5
#define SMP_ERR 6
// Macro to get a byte from a circular buffer
#define M_CIRC_BUF_GET_BYTE *buf.first; if(++buf.first==buf.end){buf.first=buf.start;}
#define M_BLK_CB_GET_BYTE(VAR) while(buf.last==buf.first); VAR=M_CIRC_BUF_GET_BYTE
#define M_UART_PUT_BYTE(DATA) while(!(UCSR0A & (1<<UDRE0))); UDR0=DATA
// We need to instruct the compiler that these data may be changed
// during an interrupt.
struct circular_buffer {
volatile char* start;
volatile char* end;
volatile char* volatile first;
volatile char* volatile last;
};
static char buf_data[BUF_SIZE];
static struct circular_buffer buf;
static volatile unsigned char rx_state; // RX State register
static unsigned char smp_state; // Sample state register
static volatile unsigned int free_cnt; // Free space in buffer
// Sound level is an exponential function of a channel's volume
// Formula is V = exp(ln(2)/2 * (x-15)) were x is the volume in the YM2149
// Exact formula to compute the array:
// [round(256*math.exp((math.log(2)/2)*(x-15))) - 1 for x in range(16)]
static unsigned char sound_level[] = {0, 1, 2, 3, 5, 7, 10, 15, 22,
31, 44, 63, 90, 127, 180, 255};
// Define interrupt code when data is received from the UART
ISR(USART_RX_vect) {
static unsigned int count;
static unsigned int chunk_size;
switch(rx_state) {
case RX_PROGRESS:
*buf.last = UDR0;
if (++buf.last == buf.end) buf.last = buf.start;
if (++count == chunk_size) rx_state = RX_COMPLETE;
break;
case RX_WAITING:
chunk_size = UDR0; // consuming data
rx_state = RX_WAITLO;
break;
case RX_WAITLO:
chunk_size = (chunk_size<<8) + UDR0;
if (chunk_size > free_cnt) {
UDR0 = RX_BAD;
rx_state = RX_COMPLETE;
break;
}
UDR0 = RX_ACK;
if (chunk_size == 0) {
rx_state = RX_COMPLETE;
break;
}
count = 0;
rx_state = RX_PROGRESS;
}
}
/***** Initialization *****/
void init_uart(void) {
// Set max speed i.e 1Mbits
UBRR0H = 0;
UBRR0L = 0;
// Setting bi-directionnal UART communication
// Enable USART RX complete interrupt
UCSR0B |= 1<<TXEN0 | 1<<RXEN0 | 1<<RXCIE0;
// Set frame format = 8-N-1 - Only supported format by ch341 linux driver
UCSR0C = 0x03<<UCSZ00;
}
void init_samples_timer(void) {
// Let OC1A in normal port operation (disconnected) / Normal counting mode
// Prescale CLK I/O from 16MHz to 2MHz
TCCR1B |= 1<<CS11;
}
void init_pwm(void) {
// Set OC0B in fast pwm mode
TCCR0A |= 0x02<<COM0B0; // Clear OC0B on Compare Match, set OC0B at BOTTOM, (non-inverting mode)
TCCR0A |= 0x03<<WGM00; // Fast PWM
TCCR0B |= 0x01<<CS00; // clkI/O /(No prescaling)
// Set OC2A & OC2B in fast pwm mode
TCCR2A |= 0x02<<COM2A0; // Clear OC2A on Compare Match, set OC2A at BOTTOM, (non-inverting mode)
TCCR2A |= 0x02<<COM2B0; // Clear OC2B on Compare Match, set OC2B at BOTTOM, (non-inverting mode)
TCCR2A |= 0x03<<WGM20; // Fast PWM
TCCR2B |= 0x01<<CS20; // clkI/O /(No prescaling)
// Toggle Pins to output
DDRD |= 1<<DDD5; // Set OC0B
DDRB |= 1<<DDB3; // Set OC2A
DDRD |= 1<<DDD3; // Set OC2B
}
void init(void) {
buf.end = buf_data + BUF_SIZE;
buf.start = buf_data;
buf.first = buf_data;
buf.last = buf_data;
// init_led();
init_uart();
init_samples_timer();
init_pwm();
ym_set_bus_ctl();
// Enable interrupts
sei();
}
/***** Main *****/
int main() {
unsigned char addr=0;
unsigned char val;
init();
smp_state = SMP_TSHI;
rx_state = RX_COMPLETE;
for(;;) {
// Sample state machine
switch(smp_state) {
case SMP_TSHI:
// Set OCR1A to the next timestamp
if (buf.last == buf.first) break;
OCR1AH = M_CIRC_BUF_GET_BYTE;
smp_state = SMP_TSLO;
/* FALLTHRU */
case SMP_TSLO:
if (buf.last == buf.first) break;
OCR1AL = M_CIRC_BUF_GET_BYTE;
TIFR1 |= 1<<OCF1A; // Clear the flag
smp_state = SMP_WAIT;
/* FALLTHRU */
case SMP_WAIT:
if (!(TIFR1 & 1<<OCF1A)) break;
smp_state = SMP_ADDR;
/* FALLTHRU */
case SMP_ADDR:
if (buf.last == buf.first) break;
addr = M_CIRC_BUF_GET_BYTE;
if (addr == 0xff) { smp_state = SMP_TSHI; break; } // End of sample
if (addr & 0xf0) { smp_state = SMP_ERR; break; } // Address is bogus
smp_state = SMP_VAL; // Address is valid
/* FALLTHRU */
case SMP_VAL:
if (buf.last == buf.first) break;
val = M_CIRC_BUF_GET_BYTE;
ym_send_data(addr, val);
// Have LED aligned with 4 LSB @ addresses 0x8 0x9 & 0xa
switch (addr) {
case 0x8: OCR0B = sound_level[val&0x0f]; break;
case 0x9: OCR2A = sound_level[val&0x0f]; break;
case 0xa: OCR2B = sound_level[val&0x0f];
}
smp_state = SMP_ADDR;
break;
default: /* SMP_ERR */
if (buf.last == buf.first) break;
addr = M_CIRC_BUF_GET_BYTE;
if (addr != 0xff) break; // Consuming data until next sample
smp_state = SMP_TSHI;
}
// RX State machine when RX_COMPLETE
switch (rx_state) {
case RX_COMPLETE:
if (!(UCSR0A & 1<<UDRE0)) break;
if (buf.last < buf.first) free_cnt = buf.first - buf.last - 1;
else free_cnt = BUF_SIZE-1 - (buf.last - buf.first);
if (free_cnt == 0) break;
UDR0 = free_cnt>>8 & 0xff;
rx_state = RX_CHUNKLO;
/* FALLTHRU */
case RX_CHUNKLO:
if (!(UCSR0A & 1<<UDRE0)) break;
UDR0 = free_cnt & 0xff;
rx_state = RX_WAITING;
}
}
}