I²C Communication from Scratch in C on Raspberry Pi in Linux - c

I want to create a small library for communication with I²C devices, especially the MPU6050 accelerometer / gyroscope module from scratch in C. Technically, I could use a library like wiringPi, but.. where is the fun with that. Right now I'm trying to write a function which scans all possible 7 bit addresses (0 - 127) and reads the acknowledge bit to find all 'active' addresses of connected I²C devices. To do that, I wrote a small library which can control the GPIO pins by writing to the coresponding GPIO files in /sys/class/gpio. It can for example setup a pin, set its direction (input or output), set its digital output and read a digital input from a pin. I tested all of these functions and they work great and fast.
For communication with the MPU6050 I²C slave I connected the power and ground pins of the MPU board to the Raspberry's GPIO power and ground pins and the SDA and SCL lines to GPIO pins 14 and 15. I read a lot about I²C recently and I do understand how it works. The problem is, that my code to scan through all addresses does not work. Here's my code:
#include <stdio.h>
#include <unistd.h>
#include "string.h"
#include "gpio.h"
#define SCL 14
#define SDA 15
#define FREQ 100000
#define high 1
#define low 0
char binret[8];
void scanI2C();
void charToBin(unsigned char);
void delay();
int main () {
setupPin(SCL, OUTPUT);
setupPin(SDA, OUTPUT);
scanI2C();
}
void scanI2C() {
int addr = 0;
for (int i = 90; i < 128; i++) {
charToBin(i);
// Start Condition
setPin(SDA, high);
setPin(SCL, high);
delay();
setPin(SDA, low);
for (int j = 0; j < 9; j++) {
delay();
setPin(SCL, low);
delay();
setPin(SCL, high);
if (j < 7) {
setPin(SDA, binret[j+1]);
}
// WRITE Bit
if (j == 7) {
setPin(SDA, high);
}
// Listen for ACK bit
if (j == 8) {
setPinDirection(SDA, INPUT);
printf("Checking Address %d (%d%d%d%d%d%d%d%d)", i, binret[0], binret[1], binret[2], binret[3], binret[4], binret[5], binret[6], binret[7]);
if (readPin(SDA) <= 0.5) {
printf(" Bingo!");
}
setPinDirection(SDA, OUTPUT);
printf("\n");
}
}
// Stop Condition
setPin(SCL, low);
delay();
setPin(SDA, high);
delay();
setPin(SCL, high);
delay();
delay();
delay();
delay();
delay();
}
}
void charToBin(unsigned char a) {
memset(binret, 0, sizeof(binret));
int val = 128;
for (int i = 0; i < 8; i++) {
int bit = a & val;
if (bit != 0) {
binret[i] = 1;
}
val /= 2;
}
}
void delay() {
usleep(1000000.0/FREQ);
}
Since I do actually know the address of the MPU6050 I²C slave to be 0x69 or 105 in decimal, I would expect the output to be something like this:
...
Checking Address 103 (01100111)
Checking Address 104 (01101000)
Checking Address 105 (01101001) Bingo!
Checking Address 106 (01101010)
Checking Address 107 (01101011)
Checking Address 108 (01101100)
Checking Address 109 (01101101)
Checking Address 110 (01101110)
...
But no, I don't get a Bingo! at address 105, which means that my I²C communication is definitely not working. Advice on how to address this issue is very much appreciated! Thank you and have a nice day :)

Typically it is the LSB of the 8-bit "address" that is the R/W bit. If you have a device with address 105 I would expect it to respond when your 8-bit address is 210 or 211. Have you tried the entire address space?
Does readPin() return a float? If not, why are you comparing to a float?
It looks like you are changing SDA while SCL is high and you are sending the address bits. I don't think that is allowed.
Do you have a pullup resistor on SDA?
Have you verified signal levels and timing with an oscilloscope?

Related

Controller received data whilst busy

I am trying to use interrupts in code vision for proteus. INT0 for increasing the counter value and INT1 for decreasing it. for that purpose I declared two funcions interrupt [EXT_INT0] void ext_int0_isr(void) and interrupt [EXT_INT1] void ext_int1_isr(void) but when I run the code it doesn't work and I get the infinite warning "Controller received data whilst busy" in proteus. I'd really appreciate if you could help me.
#include <mega8535.h>
#include <alcd.h>
#include <stdio.h>
#include <delay.h>
int i = 0 ;
int j = 0 ;
char number[16];
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
#asm("cli")// Global disable interrupts
i++;
//go to products counting
if(i < 10) goto ENTERED;
//go to products packaging process
if(i >= 11)
{
lcd_clear();
lcd_gotoxy(2,0);
lcd_putsf("Products Are");
for (j=0; j < 10; j++)
{
lcd_gotoxy(1,1);
lcd_putsf("Being Packaged.");
delay_ms(500);
lcd_gotoxy(1,1);
lcd_putsf("Being ");
delay_ms(500);
}
}
i=1;
ENTERED:
if(i < 11)
{
sprintf(number,"Number Of People=%d \n (one Entered)",i);
}
}
// External Interrupt 1 service routine
interrupt [EXT_INT1] void ext_int1_isr(void)
{
#asm("cli")// Global disable interrupts
i--;
//go to products counting
if(i < 11) goto WENTOUT;
//go to products packaging process
if(i >= 11)
{
lcd_clear();
lcd_gotoxy(2,0);
lcd_putsf("Products Are");
for (j=0; j < 10; j++)
{
lcd_gotoxy(1,1);
lcd_putsf("Being Packaged.");
delay_ms(500);
lcd_gotoxy(1,1);
lcd_putsf("Being ");
delay_ms(500);
}
}
i=1;
WENTOUT:
if(i < 11)
{
sprintf(number,"Number Of People=%d \n (one went out)",i);
}
}
void main(void)
{
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Falling Edge
GICR|=(1<<INT1) | (1<<INT0) | (0<<INT2);
MCUCR=(1<<ISC11) | (0<<ISC10) | (1<<ISC01) | (0<<ISC00);
MCUCSR=(0<<ISC2);
GIFR=(1<<INTF1) | (1<<INTF0) | (0<<INTF2);
// Alphanumeric LCD initialization
// Connections are specified in the
// Project|Configure|C Compiler|Libraries|Alphanumeric LCD menu:
// RS - PORTA Bit 0
// RD - PORTA Bit 1
// EN - PORTA Bit 2
// D4 - PORTA Bit 4
// D5 - PORTA Bit 5
// D6 - PORTA Bit 6
// D7 - PORTA Bit 7
// Characters/line: 16
lcd_init(16);
sprintf(number,"Number Of People=%d",i);
while (1)
{
lcd_gotoxy(4,0);
lcd_puts(number);
#asm("sei")// Global enable interrupts
delay_ms(50);
lcd_clear();
}
}
The problem was that the string's length that I was trying to put in the LCD was more than LCD's capacity and after I shortened the string the problem was solved.

attiny85 bit bang uart with arduino

I have tried to implement TX only uart for ATTiny85 and receive the bits using arduino micro. (similiar question did not help since it is quite different to my situation)
My intend is to be able to print via attiny85 -> arduino -> console so I can debug the attiny85, since I don't have oscilloscope available.
attiny85 fuses are: "efuse:w:0xff:m -U hfuse:w:0xdf:m -U lfuse:w:0xf1:m" aka. 16MHz F_CPU
arduino also seems to have 16MHz F_CPU
Similiar to the mentioned question attiny85 sends bits via timer0 ISR one bit at time:
#ifndef F_CPU
#define F_CPU 16000000UL // 16 MHz
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define TX_PIN PB0
volatile uint16_t tx_shift_reg = 0;
ISR(TIMER0_COMPA_vect) {
uint16_t local_tx_shift_reg = tx_shift_reg;
if( local_tx_shift_reg & 0x01 ) {
PORTB |= _BV(TX_PIN);
} else {
PORTB &= ~_BV(TX_PIN);
}
local_tx_shift_reg >>= 1;
if(!local_tx_shift_reg) {
// Stop timer0.
GTCCR |= (1<<TSM) | (1<<PSR0);
}
tx_shift_reg = local_tx_shift_reg;
}
void UART_tx(char byte) {
uint16_t local_tx_shift_reg = tx_shift_reg;
local_tx_shift_reg = (0b1<<9) | ((uint16_t)byte<<1);
tx_shift_reg = local_tx_shift_reg;
TCNT0 = 0;
TCCR0B |= (1<<CS02)|(1<<CS00); // 1024 prescaler
GTCCR &= ~(1<<TSM);
}
void UART_tx_char(char c) {
UART_tx( c );
// wait until transmission is finished
while(tx_shift_reg);
}
void UART_init() {
cli()
// set TX pins as output
DDRB |= (1<<TX_PIN);
PORTB |= (1<<TX_PIN);
// set timer0 to CTC mode, keep it halted.
TCCR0A |= (1<<WGM01);
TCCR0B = 0;
// enable interrupt
TIMSK |= (1<<OCIE0A);
OCR0A = 128;
OCR0B = 128;
TCNT0 = 0;
GTCCR |= (1<<TSM);
sei();
}
void main(void)
{
UART_init();
while(1) {
for(char c = 1; c < 128; ++c) {
UART_tx_char(c);
_delay_ms(100);
}
}
}
Then arduino receives the bits:
/*
* ATtiny85 bit-bang uart monitor for ATmega32u4
*/
#define LED_PIN 13
#define RX_PIN 7
// rx_state == 0 // timer1 not running
// rx_state == 1 // receive in progress
// rx_state == 2 // data ready in rx_data_reg
volatile int rx_state = 0;
volatile int rx_bit_nro = 0;
volatile uint16_t rx_shift_reg = 0;
volatile uint16_t rx_data_reg = 0;
void rx_start_interupt() {
if(rx_state == 0) {
digitalWrite(LED_PIN, HIGH);
while(digitalRead(RX_PIN));
// Start timer1
rx_state++;
TCNT1 = 0;
TCCR1B = (1 << WGM12) | (1 <<CS12) | (1 << CS10);
}
}
ISR(TIMER1_COMPA_vect) {
uint16_t bit = digitalRead(RX_PIN) > 0;
rx_shift_reg >>= 1;
rx_shift_reg |= (bit << 7);
++rx_bit_nro;
if(rx_bit_nro == 9) {
// Stop timer.
TCCR1B = 0;
TCNT1 = 0;
rx_data_reg = rx_shift_reg;
rx_shift_reg = 0;
rx_bit_nro = 0;
rx_state++;
digitalWrite(LED_PIN, LOW);
}
}
void setup() {
noInterrupts();
// Program timer1 for UART bit bang receive.
TCCR1A = 0; // set entire TCCR1A register to 0 (stops it)
TCCR1B = 0; // same for TCCR1B
TCNT1 = 0; // initialize counter value to 0
OCR1A = 128;
TIMSK1 |= (1 << OCIE1A);
interrupts();
pinMode(LED_PIN, OUTPUT);
pinMode(RX_PIN, INPUT);
// Attach RX start interupt.
pinMode(digitalPinToInterrupt(RX_PIN), INPUT);
attachInterrupt(digitalPinToInterrupt(RX_PIN), rx_start_interupt, FALLING);
Serial.begin(9600);
while(!Serial);
Serial.print("F_CPU:");
Serial.println(F_CPU,DEC);
Serial.println("Waiting for data from attiny85...");
}
void loop() {
if(rx_state == 2) {
uint16_t local_rx_data = rx_data_reg;
rx_state = 0;
Serial.println(local_rx_data,HEX);
}
}
I have tried pretty much everything to make it work, but received bytes come back garbace.
What am I missing?
Note: I'm using timer0 on attiny85 and timer1 on arduino.
Solved: switching ISR to TIMER1_COMPAB_vect and OCR1B = 64 actually works! Yay!
I recently ran into this issue of serial comms coming out as garbage on the other end. In my case, the ATtiny85 was sending bits to my laptop using an USB to TTL UART converter which had worked beautifully in other situations, but, I was just getting garbage in the Arduino IDE serial monitor.
I found a forum post which mentioned the possibility of calibrating OSCCAL.
I get a little bit fancier in my related blog post, but I tested the theory that I should calibrate OSCCAL using this code:
#include <SoftwareSerial.h>
SoftwareSerial comm(-1, 0);
static const int anchor = 128;
void
print_osccal(int v) {
comm.println(F("********************************"));
comm.print(F("OSCCAL = "));
comm.println(v);
comm.println(F("********************************"));
}
void
setup() {
delay(5000);
comm.begin(300);
OSCCAL = anchor;
print_osccal(anchor);
delay(5000);
}
void
loop() {
int x;
for (int i = 1; i < 128; ++i) {
x = anchor + i;
OSCCAL = x;
print_osccal(x);
delay(1000);
x = anchor - i;
OSCCAL = x;
print_osccal(x);
delay(1000);
}
}
It was a revelation seeing garbage all of a sudden transform to nicely formatted messages in the serial monitor (and, of course, back to garbage as the search is an infinite loop).
Now, the ATtiny85's internal oscillator can only support 1 MHz and 8 MHz. As I understand it, OSCCAL exists because this internal oscillator is 1) not very accurate and 2) is very sensitive the temperature.
If the ATtiny85 is set to run at 16MHz, it needs a reliable, external oscillator, and no amount of fiddling with OSCCAL might help. But, it did in my case allow me to discover the value(s) which made SoftwareSerial tick at 8 MHz and a range of baud rates from 300 to 38400.
This allowed me to get the right value for the bit banging UART to work with the 1MHz clock which is not supported by SoftwareSerial.

Write rate of DAC MCP4725

I have a buffer which contains 16000 PCM samples of 8Khz 8-bit mono. I am trying to play it using 12 bit MCP4725 DAC. I have tried using micros() to control the write interval for the DAC. Here's my code -
uint8_t soundData[16000] = { 234,206,79,255,249,....,210,222 }; // 8Khz 8-bit mono
void setup() {
Serial.begin(115200);
Wire.begin(D2, D1);
delay(100); // delay 100 ms
Serial.flush();
delay(1000);
}
void value_write(uint16_t temp){
Wire.beginTransmission(0x62);
Wire.write(64);
Wire.write(temp >> 4); // the 8 most significant bits...
Wire.write((temp & 15) << 4); // the 4 least significant bits...
Wire.endTransmission();
}
void loop() {
unsigned long currentMicros = micros();
// 125 uS sampling for 8KHz signal
if(currentMicros - previousMicros > 125) {
Serial.print("Index: ");
Serial.println(indx);
uint16_t temp = map(soundData[indx++], 0, 255, 0, 4095);
value_write(temp);
}
}
The write should logically be completed within 2 seconds but takes way more time. Any help regarding successfully writing the PCM values to the DAC at 8000Hz is greatly appreciated.

is it possible compare a 16-bit value with a 8-bit compare match ISR

I am trying to make a servo controller that have a higher resolution than the ATtiny85 8-bit timer/counter. So far I have managed to get about 2000 positions on my servo (1µs/step) within a time frame of 21'000 µs. I have also managed to move 5 servos sequential and with different speed, but now I want to move them synchronous.
My biggest problem is that I don't get how I should make it happen! I have looked around on other servo codes including servo8bit library and tried to find a way. It seems that most of the examples uses compare match ISR to move the servos "at the same time", my problem is that I have a 16-bit integer that I want to compare.
Is there a way to do some magic so I can use the 8-bit compare match ISR with my 16-bit integer? Or does anyone of you have some other suggestions on how I can move my servos synchronous without using compare match ISR?
I hope my questions make sense!
Since I don't really have any code to show yet (only flawed attempts without compar match ISR that makes no sense) I post the link to my TinyServo code if it helps.
EDIT 1:
Here is the part of the code I mentioned and didn't post the first time:
void servoMove(void)
{
uint16_t nextPulse = hPulse[0];
timerSetup (); //16-bit setup for counter
for (i = 0; i < sizeof(servo)/sizeof(servo[0]); i++)
{
if ( (oTime > nextPulse) && (channel < sizeof(servo)/sizeof(servo[0])) ) //check if HIGH pulse (pos) is done
{
PORTB &= ~(1 << servo[channel]);
if (i+1 < sizeof(hPulse)/sizeof(hPulse[0]))
{
nextPulse += hPulse[i+1];
}
channel++;
}
else
{
channel = 0;
oTime = 0; //resets 16-bit variable
tot_overflow = 0; //resets tot_overflow variable
TIFR |= (1 << TOV1); // clear counter1 overflow-flag
TCNT1 = 0; //resets Timer/Counter1
}
}
for (i = 0; i < sizeof(servo)/sizeof(servo[0]); i++)
{
if ( (oTime > tPulse - nextPulse) && (channel < sizeof(servo)/sizeof(servo[0])) ) //check if LOW pulse (period) is done
{
PORTB |= (1 << servo[channel]);
nextPulse -= hPulse[i];
channel++;
}
}
}
void servoPosSet(volatile uint16_t pos[], uint8_t size)
{
for (i = 0; i < size; i++)
{
hPulse[i] = pos[i];
}
}
int main(void)
{
TCCR1 |= (1 << CS12); //set Timer/Counter1 prescaler to increment every 1 µs (PCK/8)
for (channel = 0; channel < size); channel++)
{
DDRB |= (1 << servo[channel]); //sets PB0-PB4 as output pins
}
channel = 0;
uint16_t pos[] = {2000, 1500, 1900, 1300, 1700};
uint8_t size = 5;
while(1)
{
servoPosSet(pos);
servoMove();
}
}
EDIT 2:
This is an illustration of how I think the code should work:
...but it does not!
If you have nothing else to do during the pulse, you could use a busy
loop instead of interrupts:
#include <avr/io.h>
#include <util/delay_basic.h>
/* Send a pulse of width = 4*count cycles. */
void pulse(uint16_t count, uint8_t channel)
{
uint8_t mask = 1 << channel,
old_port = PORTB,
high = old_port | mask,
low = old_port & ~mask;
PORTB = high;
_delay_loop_2(count);
PORTB = low;
}
This will give you a resolution of 4 clock cycles, or 0.5 µs with a
8 MHz clock.
Sending the pulses to the 5 servos should take at most 10 ms. Since
you repeat the pulse train every 21 ms, this leaves you 11 ms
to compute the next set of positions, which should be plenty. You could
program a timer to wake you up every 21 ms, then your main() may
look like:
int main(void)
{
static uint16_t pos[] = {4000, 3000, 3800, 2600, 3400};
uint8_t i;
/* Wake up every 21 ms. */
setup_timer();
sleep_enable();
for (;;) {
/* Update the servos. */
for (i = 0; i < 5; i++) pulse(pos[i], i);
/* Compute the next set of positions. */
...
/* Wait for timer interrupt. */
sleep_cpu();
}
}

Get value from LDR

I'm using the picdem 18F4550 with microchip v8.63 and the c compiler.
components that i'm use are:
- 3 leds (red, green, blue);
- a LDR (327700 NORPS-12 farnell).
I have connected 3 leds (red, green and blue), on RB4, RB5 and RB6 as output.
I also connect the LDR on RB1.
My question is, when the green of another led lights up, how can I retrieve the value in my code that the LDR measures?
I included my code:
#include "p18cxxx.h"
#pragma config WDT = OFF
void main(void)
{
// turn off all LED latches
LATDbits.LATD0 = 0;
LATDbits.LATD1 = 0;
LATDbits.LATD2 = 0;
// make port d bits which drive LEDs outputs
TRISDbits.TRISD0 = 0;
TRISDbits.TRISD1 = 0;
TRISDbits.TRISD2 = 0;
TRISB = 0;
// RB port output.
PORTB = 0;
PORTB = 0b10001111; // 0b01011010
if(PORTBbits.RB4 == 0) {
LATDbits.LATD0 = 1;
}
if(PORTBbits.RB5 == 0) {
LATDbits.LATD1 = 1;
}
if(PORTBbits.RB6 == 0) {
LATDbits.LATD2 = 1;
}
if(PORTBbits.RB1 == 1) {
}
while(1) {
;
}
}
Assuming you are connecting an LDR to a pin configured as GPIO. The voltage threshold for the GPIO pin applies. The value read on the pin is dependent on the resistance of the LDR swinging wide enough to generate both logic low and logic high.
Most likely you want to connect the LDR to the PIC A/D pin instead of GPIO. Use ADCON to configure the A/D. Set up the A/D for conversion, and wait for the A/D completion interrupt. Then read the ADRESH and ADRESL which yields the analog voltage of the LDR.

Resources