forked from dougg3/mac-rom-simm-programmer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mcp23s17.c
213 lines (174 loc) · 6.06 KB
/
mcp23s17.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
212
213
/*
* mcp23s17.c
*
* Created on: Nov 25, 2011
* Author: Doug
*
* Copyright (C) 2011-2012 Doug Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "mcp23s17.h"
#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
static bool MCP23S17_Inited = false;
// Pin definitions on PORTB
#define SPI_CS (1 << 0)
#define SPI_SCK (1 << 1)
#define SPI_MOSI (1 << 2)
#define SPI_MISO (1 << 3)
#define MCP23S17_RESET (1 << 7)
#define ASSERT_CS() PORTB &= ~SPI_CS
#define DEASSERT_CS() PORTB |= SPI_CS;
// A few register defines
#define MCP23S17_CONTROL_WRITE(address) (0x40 | (address << 1))
#define MCP23S17_CONTROL_READ(address) (0x40 | (address << 1) | 1)
#define MCP23S17_IODIRA 0x00
#define MCP23S17_IODIRB 0x01
#define MCP23S17_IPOLA 0x02
#define MCP23S17_IPOLB 0x03
#define MCP23S17_GPINTENA 0x04
#define MCP23S17_GPINTENB 0x05
#define MCP23S17_DEFVALA 0x06
#define MCP23S17_DEFVALB 0x07
#define MCP23S17_INTCONA 0x08
#define MCP23S17_INTCONB 0x09
#define MCP23S17_IOCON 0x0A
#define MCP23S17_IOCON_AGAIN 0x0B
#define MCP23S17_GPPUA 0x0C
#define MCP23S17_GPPUB 0x0D
#define MCP23S17_INTFA 0x0E
#define MCP23S17_INTFB 0x0F
#define MCP23S17_INTCAPA 0x10
#define MCP23S17_INTCAPB 0x11
#define MCP23S17_GPIOA 0x12
#define MCP23S17_GPIOB 0x13
#define MCP23S17_OLATA 0x14
#define MCP23S17_OLATB 0x15
// Private functions
void MCP23S17_WriteBothRegs(uint8_t addrA, uint16_t value);
uint16_t MCP23S17_ReadBothRegs(uint8_t addrA);
uint8_t MCP23S17_ByteRW(uint8_t b);
void MCP23S17_Init()
{
// If it has already been initialized, no need to do it again.
if (MCP23S17_Inited)
{
return;
}
// Initialize the SPI pins
// Set MOSI, SCLK, and CS as outputs, MISO as input
// (Also, set the MCP23S17 reset line as an output)
DDRB |= SPI_CS | SPI_SCK | SPI_MOSI | MCP23S17_RESET;
DDRB &= ~SPI_MISO;
// Initialize the SPI peripheral
// We can run it at 8 MHz (divider of 2 from 16 MHz system clock -- maximum speed of MCP23S17 10 MHz)
#if ((F_CPU / 2) > 10000000)
#error This code assumes that the CPU clock divided by 2 is less than or equal to the MCP23S17's maximum speed of 10 MHz, and in this case, it's not.
#endif
SPCR = (0 << SPIE) | // No SPI interrupts
(1 << SPE) | // Enable SPI
(0 << DORD) | // MSB first
(1 << MSTR) | // Master mode
(0 << CPOL) | // SPI mode 0,0
(0 << CPHA) |
(0 << SPR0); // SCK frequency = F_CPU/2 (because of SPI2X being set below
SPSR = (1 << SPI2X); // Double the SPI clock rate -- allows /2 instead of /4
// Leave CS deasserted
DEASSERT_CS();
// Pull the MCP23S17 out of reset (it's pulled down to GND on the board with a 100k pulldown
// so it won't activate during AVR ISP programming)
PORTB |= MCP23S17_RESET;
_delay_ms(50);
// All done!
MCP23S17_Inited = true;
}
void MCP23S17_SetDDR(uint16_t ddr)
{
// The MCP23S17's DDR is backwards from the AVR's.
// I like dealing with it so it behaves like the AVR's,
// so I invert any DDR values in this driver.
// In other words, when you set or get the DDR through
// this driver, the 1s and 0s are backwards from what
// the MCP23S17's datasheet says, but they are
// consistent with the AVR. I value the consistency more.
MCP23S17_WriteBothRegs(MCP23S17_IODIRA, ~ddr);
}
void MCP23S17_SetPins(uint16_t data)
{
MCP23S17_WriteBothRegs(MCP23S17_GPIOA, data);
}
uint16_t MCP23S17_ReadPins(void)
{
return MCP23S17_ReadBothRegs(MCP23S17_GPIOA);
}
void MCP23S17_SetPullups(uint16_t pullups)
{
MCP23S17_WriteBothRegs(MCP23S17_GPPUA, pullups);
}
// Determines the output values of output pins without reading any input pins
uint16_t MCP23S17_GetOutputs(void)
{
return MCP23S17_ReadBothRegs(MCP23S17_OLATA);
}
uint16_t MCP23S17_GetDDR(void)
{
// As I mentioned above, DDR bits are inverted from
// what the MCP23S17's datasheet says, but
// consistent with what the AVR's datasheet says
return ~MCP23S17_ReadBothRegs(MCP23S17_IODIRA);
}
uint16_t MCP23S17_GetPullups(void)
{
return MCP23S17_ReadBothRegs(MCP23S17_GPPUA);
}
uint8_t MCP23S17_ByteRW(uint8_t b)
{
SPDR = b;
while ((SPSR & (1 << SPIF)) == 0);
return SPDR;
}
void MCP23S17_WriteBothRegs(uint8_t addrA, uint16_t value)
{
// addrA should contain the address of the "A" register.
// the chip should also be in "same bank" mode.
ASSERT_CS();
// Start off the communication by telling the MCP23S17 that we are writing to a register
MCP23S17_ByteRW(MCP23S17_CONTROL_WRITE(0));
// Tell it the first register we're writing to (the "A" register)
MCP23S17_ByteRW(addrA);
// Write the first byte of the register
MCP23S17_ByteRW((uint8_t)((value >> 8) & 0xFF));
// It should auto-increment to the "B" register, now write that
MCP23S17_ByteRW((uint8_t)(value & 0xFF));
DEASSERT_CS();
}
uint16_t MCP23S17_ReadBothRegs(uint8_t addrA)
{
uint16_t returnVal;
ASSERT_CS();
// Start off the communication by telling the MCP23S17 that we are reading from a register
MCP23S17_ByteRW(MCP23S17_CONTROL_READ(0));
// Tell it which register we're reading from (the "A" register)
MCP23S17_ByteRW(addrA);
// Read the first byte of the register
returnVal = (((uint16_t)MCP23S17_ByteRW(0)) << 8);
// It should auto-increment to the "B" register, now read that
returnVal |= MCP23S17_ByteRW(0);
DEASSERT_CS();
return returnVal;
}