Archived
1
0
This repo is archived. You can view files and clone it, but cannot push or open issues or pull requests.
controller/Scan/ISSILed/i2c.c
Jacob Alexander 1e47c7abc2 Initial LED Animiation Support
- Added port swapping to KType
- Added initial KType default keymappings
- Initial PixelMap animiation framework
- Swapped in new i2c library for ISSILed
2016-01-03 10:21:10 -08:00

314 lines
7.9 KiB
C

/*
* Copyright ( C ) 2014 Jan Rychter
* Modifications ( C ) 2015 Jacob Alexander
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files ( the "Software" ), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// ----- Includes ----
// Compiler Includes
#include <Lib/ScanLib.h>
// Project Includes
#include <print.h>
// Local Includes
#include "i2c.h"
// ----- Variables -----
volatile I2C_Channel i2c_channels[1];
// ----- Functions -----
inline void i2c_setup( )
{
// Enable I2C internal clock
SIM_SCGC4 |= SIM_SCGC4_I2C0; // Bus 0
// External pull-up resistor
PORTB_PCR0 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
PORTB_PCR1 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
// SCL Frequency Divider
// 1.8 MBaud ( likely higher than spec )
// 0x82 -> 36 MHz / (4 * 3) = 2.25 MBaud
// 0x80 => mul(4)
// 0x05 => ICL(5)
I2C0_F = 0x84;
I2C0_FLT = 4;
I2C0_C1 = I2C_C1_IICEN;
I2C0_C2 = I2C_C2_HDRS; // High drive select
// Enable I2C Interrupt
NVIC_ENABLE_IRQ( IRQ_I2C0 );
}
uint8_t i2c_busy()
{
volatile I2C_Channel *channel = &( i2c_channels[0] );
if ( channel->status == I2C_BUSY )
{
return 1;
}
return 0;
}
// These are here for readability and correspond to bit 0 of the address byte.
#define I2C_WRITING 0
#define I2C_READING 1
int32_t i2c_send_sequence(
uint16_t *sequence,
uint32_t sequence_length,
uint8_t *received_data,
void ( *callback_fn )( void* ),
void *user_data
) {
volatile I2C_Channel *channel = &( i2c_channels[0] );
int32_t result = 0;
uint8_t status;
if ( channel->status == I2C_BUSY )
{
return -1;
}
// Debug
/*
for ( uint8_t c = 0; c < sequence_length; c++ )
{
printHex( sequence[c] );
print(" ");
}
print(NL);
*/
channel->sequence = sequence;
channel->sequence_end = sequence + sequence_length;
channel->received_data = received_data;
channel->status = I2C_BUSY;
channel->txrx = I2C_WRITING;
channel->callback_fn = callback_fn;
channel->user_data = user_data;
// reads_ahead does not need to be initialized
// Acknowledge the interrupt request, just in case
I2C0_S |= I2C_S_IICIF;
I2C0_C1 = ( I2C_C1_IICEN | I2C_C1_IICIE );
// Generate a start condition and prepare for transmitting.
I2C0_C1 |= ( I2C_C1_MST | I2C_C1_TX );
status = I2C0_S;
if ( status & I2C_S_ARBL )
{
warn_print("Arbitration lost");
result = -1;
goto i2c_send_sequence_cleanup;
}
// Write the first (address) byte.
I2C0_D = *channel->sequence++;
// Everything is OK.
return result;
i2c_send_sequence_cleanup:
I2C0_C1 &= ~( I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX );
channel->status = I2C_ERROR;
return result;
}
void i2c0_isr()
{
volatile I2C_Channel* channel = &i2c_channels[0];
uint8_t element;
uint8_t status;
status = I2C0_S;
// Acknowledge the interrupt request
I2C0_S |= I2C_S_IICIF;
// Arbitration problem
if ( status & I2C_S_ARBL )
{
warn_print("Arbitration error");
I2C0_S |= I2C_S_ARBL;
goto i2c_isr_error;
}
if ( channel->txrx == I2C_READING )
{
switch( channel->reads_ahead )
{
// All the reads in the sequence have been processed ( but note that the final data register read still needs to
// be done below! Now, the next thing is either a restart or the end of a sequence. In any case, we need to
// switch to TX mode, either to generate a repeated start condition, or to avoid triggering another I2C read
// when reading the contents of the data register.
case 0:
I2C0_C1 |= I2C_C1_TX;
// Perform the final data register read now that it's safe to do so.
*channel->received_data++ = I2C0_D;
// Do we have a repeated start?
if ( ( channel->sequence < channel->sequence_end ) && ( *channel->sequence == I2C_RESTART ) )
{
// Generate a repeated start condition.
I2C0_C1 |= I2C_C1_RSTA;
// A restart is processed immediately, so we need to get a new element from our sequence. This is safe, because
// a sequence cannot end with a RESTART: there has to be something after it. Note that the only thing that can
// come after a restart is an address write.
channel->txrx = I2C_WRITING;
channel->sequence++;
element = *channel->sequence;
I2C0_D = element;
}
else
{
goto i2c_isr_stop;
}
break;
case 1:
// do not ACK the final read
I2C0_C1 |= I2C_C1_TXAK;
*channel->received_data++ = I2C0_D;
break;
default:
*channel->received_data++ = I2C0_D;
break;
}
channel->reads_ahead--;
}
// channel->txrx == I2C_WRITING
else
{
// First, check if we are at the end of a sequence.
if ( channel->sequence == channel->sequence_end )
goto i2c_isr_stop;
// We received a NACK. Generate a STOP condition and abort.
if ( status & I2C_S_RXAK )
{
warn_print("NACK Received");
goto i2c_isr_error;
}
// check next thing in our sequence
element = *channel->sequence;
// Do we have a restart? If so, generate repeated start and make sure TX is on.
if ( element == I2C_RESTART )
{
I2C0_C1 |= I2C_C1_RSTA | I2C_C1_TX;
// A restart is processed immediately, so we need to get a new element from our sequence.
// This is safe, because a sequence cannot end with a RESTART: there has to be something after it.
channel->sequence++;
element = *channel->sequence;
// Note that the only thing that can come after a restart is a write.
I2C0_D = element;
}
else
{
if ( element == I2C_READ ) {
channel->txrx = I2C_READING;
// How many reads do we have ahead of us ( not including this one )?
// For reads we need to know the segment length to correctly plan NACK transmissions.
// We already know about one read
channel->reads_ahead = 1;
while (
( ( channel->sequence + channel->reads_ahead ) < channel->sequence_end ) &&
( *( channel->sequence + channel->reads_ahead ) == I2C_READ )
) {
channel->reads_ahead++;
}
// Switch to RX mode.
I2C0_C1 &= ~I2C_C1_TX;
// do not ACK the final read
if ( channel->reads_ahead == 1 )
{
I2C0_C1 |= I2C_C1_TXAK;
}
// ACK all but the final read
else
{
I2C0_C1 &= ~( I2C_C1_TXAK );
}
// Dummy read comes first, note that this is not valid data!
// This only triggers a read, actual data will come in the next interrupt call and overwrite this.
// This is why we do not increment the received_data pointer.
*channel->received_data = I2C0_D;
channel->reads_ahead--;
}
// Not a restart, not a read, must be a write.
else
{
I2C0_D = element;
}
}
}
channel->sequence++;
return;
i2c_isr_stop:
// Generate STOP ( set MST=0 ), switch to RX mode, and disable further interrupts.
I2C0_C1 &= ~( I2C_C1_MST | I2C_C1_IICIE | I2C_C1_TXAK );
channel->status = I2C_AVAILABLE;
// Call the user-supplied callback function upon successful completion (if it exists).
if ( channel->callback_fn )
{
// Delay 10 microseconds before starting linked function
delayMicroseconds(10);
( *channel->callback_fn )( channel->user_data );
}
return;
i2c_isr_error:
// Generate STOP and disable further interrupts.
I2C0_C1 &= ~( I2C_C1_MST | I2C_C1_IICIE );
channel->status = I2C_ERROR;
return;
}