I have two (different) boards, each based on the ATmega32U4. Both are programmed bare-metal, in C with avr-gcc. Board 1 has USB functionality, using the LUFA library (I started with the USBtoSerial demo project, and adapted it to implement my own commands).
Board 2 generates a PWM signal to control fans speed; that part works (I've tested it hardcoding a timed sequence of values, and I confirm that the fans spin at expected speeds)
Now, I want Board 1 to occasionally transmit an 8-bit value to Board 2, to set the duty cycle of the PWM (and thus, to set the fans speed). Transmission is a single byte, receiver does not need to send any response.
Transmitter is essentially the code from page 235 of the datasheet (just slightly refactored):
// I2C functions:
void I2C_init()
{
const unsigned int baudrate = (F_CPU / (2 * I2C_CLOCK));
TWSR = 0;
TWBR = ((uint8_t) (baudrate - 16));
}
uint8_t I2C_start()
{
TWCR = ((0x1 << TWSTA) | (0x1 << TWINT) | (0x1 << TWEN)); // Start condition
while (!(TWCR & (1 << TWINT)))
{}
return (TWSR & 0xF8);
}
uint8_t I2C_stop()
{
TWCR = (1 << TWSTO) | (1 << TWINT) | (1 << TWEN); // Stop condition
while (TWCR & (0x1 << TWSTO))
{}
return (TWSR & 0xF8);
}
uint8_t I2C_write (uint8_t data)
{
TWDR = data; // Transmit data
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (0x1 << TWINT))) // Wait for the transmission to complete
{}
return (TWSR & 0xF8);
}
...
Upon receiving USB command fans_speed <param>\n, I execute:
const uint8_t status_start = I2C_start();
const uint8_t status_write_addr = I2C_write (FANS_BOARD_ADDRESS << 1);
const uint8_t status_write_data = I2C_write (((uint8_t)(atoi(param))));
const uint8_t status_stop = I2C_stop();
sprintf (response, "%02X %02X %02X %02X\r\n",
status_start, status_write_addr, status_write_data, status_stop);
// response is then transmitted through USB
On the USB host, I run minicom -D /dev/ttyACM0
to interact with board B1, and see the following:
fans_speed 7 [[ this is the command that I'm typing and gets transmitted ]]
08 18 28 F8
fans_speed 2
The first line is what I transmit (I enabled local echo on minicom); second line (USB response from Board 1, showing the status codes) seems ok. Not sure about that last F8 code, which corresponds to constant TW_NO_INFO in <util/twi.h>, but I do see the 7 received by Board 2 (see below for description)
I then transmit a second time, and this shows the problem I'm having: after the first transmission is correctly received, the master (Board 1) hangs; subsequent transmissions are not received, and Board 1 actually stops responding to USB commands).
This is the receiver code, roughly based on the sample code in https://www.electronicwings.com/avr-atmega/atmega1632-i2c (a significantly simplified version, since my use-case is simpler):
int main()
{
... (declarations and initializations)
init_pwm();
// Init I2C receiver
TWAR = I2C_ADDR << 1;
TWCR = (0x1 << TWEN) | (0x1 << TWEA) | (0x1 << TWINT); // Enable TWI and ACK on the slave
while (true)
{
// Wait for address
while (!(TWCR & (0x1 << TWINT)))
{}
uint8_t status = TW_STATUS;
if (status == TW_SR_SLA_ACK || status == TW_SR_ARB_LOST_SLA_ACK)
{
TWCR = (0x1 << TWINT) | (0x1 << TWEN) | (0x1 << TWEA);
while (!(TWCR & (0x1 << TWINT)))
{}
const uint8_t speed = TWDR;
status = TW_STATUS;
TWCR = (0x1 << TWINT) | (0x1 << TWEN) | (0x1 << TWEA);
// set_pwm_duty_cycle (speed);
if (status == TW_SR_DATA_ACK)
{
flash_LED (speed); // Flashes 'speed' times
}
else
{
flash_LED (1);
_delay_ms(500);
}
}
}
The LED never flashes a single time (good; it means the else is never taken). I do see the LED flashing seven times.
However: why does it hang after the first transmission? Is the bug in the receiver, or the transmitter (or both) ??