I see that once slave address is written in the interrupt handler, I get an ACK Failure right away, but when I do the same thing with polling approach, I do get an ACK and goes on to read off the values from the device.
With interrupts, I see ISR gets triggered only once (in which it writes slave address) and never gets called again. My i2c configurations are fine, but there's something with interrupts that I seem to be missing out on.
Polling approach
HAL_Status I2C_HAL_TX_IT () {
GenerateStartCondition();
// validate the completion of start condition
while (!GetFlagStatus(hi2c->Instance, I2C_SR1_SB) && HAL_Timeout(5));
// write slave address along with write bit
I2C_WriteSlaveAddress(hi2c, WRITE);
// wait for address to be sent
while (!GetFlagStatus(hi2c->Instance, I2C_SR1_ADDR) && HAL_Timeout(5));
// clear address flag
I2C_ClearADDRFlag(hi2c->Instance);
// write data to DR ...
}
With interrupts:
void HAL_StartI2CInterrupts() {
GenerateStartCondition();
// setting control bits
hi2c->Instance->CR2 |= I2C_CR2_ITBUFEN;
hi2c->Instance->CR2 |= I2C_CR2_ITEVTEN;
hi2c->Instance->CR2 |= I2C_CR2_ITERREN;
}
void I2C1_EV_IRQHandler ()
{
uint8_t event_interrupt = (hi2c->Instance->CR2 & I2C_CR2_ITEVTEN) >> I2C_CR2_ITEVTEN_Pos;
uint8_t buffer_interrupt = (hi2c->Instance->CR2 & I2C_CR2_ITBUFEN) >> I2C_CR2_ITBUFEN_Pos;
uint8_t var;
if (event_interrupt)
{
// validate the completion of START condition
var = (hi2c->Instance->SR1 & I2C_SR1_SB) >> I2C_SR1_SB_Pos;
if (var)
{
if (hi2c->I2C_State == I2C_TX_BUSY)
{
I2C_WriteSlaveAddress(hi2c, WRITE);
}
}
// check ADDR bit ...
}
}
I'm working with the NXP LPC1788 microcontroller at the moment, and I'm trying to configure it to communicate with a Windows 7 PC using USB. I have limited experience with USB (I started learning the protocol at the start of this week), but I've worked with the LPC1788 for quite some time and have experience with other communication protocols (CAN, I2C, SSP).
I want to configure my microcontroller to act as a device and for the PC to act as the host. I'm suspecting that I will need to configure the microcontroller to communicate using full-speed interrupt transfers. Additionally, I will likely need to create my own vendor-specific USB driver for the PC later - I have not done this yet, and my descriptors are not properly configured.
My specific problem is that when I run my program and initialise/enable the microcontroller's USB device, I receive only two USB interrupts. The device interrupt status (DEVINTST) values in each case are:
0x19 - (FRAME, DEVSTAT, and CCEMPTY interrupts)
0x1 - (FRAME interrupt)
In the case of receiving the DEVSTAT interrupt, I read the following value from the serial interface engine using a GET DEVICE STATUS command:
0x19 - (CON (connected), SUS_CH (suspend state change), and RST (bus reset))
Using USBlyzer, I obtain only the following four packets: http://i.imgur.com/WRk7RBv.png.
I'm expecting that even without properly configured descriptors or a matching driver on the PC end, I should still be receiving more than this. I would have expected to be receiving a Get Descriptor request in Endpoint 0.
My main function only initialises my USB device and loops indefinitely.
The USB initialisation code is given below:
void USBInit()
{
// Turn on power and clock
CLKPWR_ConfigPPWR(CLKPWR_PCONP_PCUSB, ENABLE);
// PLL0 clock is 96 MHz, usbclk should be 48 MHz.
LPC_SC->USBCLKSEL = 0x102;
// Configure USB pins.
PINSEL_ConfigPin(0, 29, 1); // USB_D+1
PINSEL_ConfigPin(0, 30, 1); // USB_D-1
PINSEL_ConfigPin(1, 18, 1); // USB_UP_LED1
PINSEL_ConfigPin(2, 9, 1); // USB_CONNECT1
PINSEL_ConfigPin(1, 30, 2); // USB_VBUS
//PINSEL_ConfigPin(1, 19, 2); // USB_PPWR1
PINSEL_SetPinMode(1, 30, PINSEL_BASICMODE_PLAINOUT);
// Set DEV_CLK_EN and AHB_CLK_EN.
LPC_USB->USBClkCtrl |= 0x12;
// Wait until change is reflected in clock status register.
while((LPC_USB->USBClkSt & 0x12) != 0x12);
// Select USB port 1.
LPC_USB->USBClkCtrl |= 0x8;
while((LPC_USB->USBClkSt & 0x8) == 0);
LPC_USB->StCtrl &= ~0x3;
LPC_USB->USBClkCtrl &= ~0x8;
// Reset the USB.
USBReset();
// Configure interrupt mode.
writeSIECommandData(CMD_SET_MODE, 0);
// Enable NVIC USB interrupts.
NVIC_EnableIRQ(USB_IRQn);
// Set device address to 0x0 and enable device & connection.
USBSetAddress(0);
USBSetConnection(TRUE);
//printf("USB initialised\n");
//printf("EpIntEn: 0x%x\n", LPC_USB->EpIntEn);
// No errors here (SIE Error code: 0x0).
USBPrintErrCode();
// Packet sequence violation here (SIE Error code: 0x11).
USBPrintErrCode();
}
The USB reset function:
void USBReset()
{
LPC_USB->EpInd = 0;
LPC_USB->MaxPSize = USB_MAX_PACKET_SIZE;
LPC_USB->EpInd = 1;
LPC_USB->MaxPSize = USB_MAX_PACKET_SIZE;
while ((LPC_USB->DevIntSt & EP_RLZED_INT) == 0);
LPC_USB->EpIntClr = 0xFFFFFFFF;
LPC_USB->EpIntEn = 0xFFFFFFFF;
LPC_USB->DevIntClr = 0xFFFFFFFF;
LPC_USB->DevIntEn = DEV_STAT_INT | EP_SLOW_INT | EP_FAST_INT;
}
The USB set address function:
void USBSetAddress(uint32_t addr)
{
writeSIECommandData(CMD_SET_ADDR, DAT_WR_BYTE(DEV_EN | addr));
writeSIECommandData(CMD_SET_ADDR, DAT_WR_BYTE(DEV_EN | addr));
}
The USB set connection function:
void USBSetConnection(uint32_t connect)
{
writeSIECommandData(CMD_SET_DEV_STAT, DAT_WR_BYTE(connect ? DEV_CON : 0));
}
The USB interrupt service routine:
void USB_IRQHandler(void)
{
uint32_t data;
uint32_t interruptData = LPC_USB->DevIntSt;
printf("InterruptData: 0x%x\n", interruptData);
// Handle device status interrupt (reset, connection change, suspend/resume).
if(interruptData & DEV_STAT_INT)
{
LPC_USB->DevIntClr = DEV_STAT_INT;
writeSIECommand(CMD_GET_DEV_STAT);
data = readSIECommandData(DAT_GET_DEV_STAT);
printf("Data: 0x%x\n", data);
// Device reset.
if(data & DEV_RST)
{
USBReset();
USBResetCore();
printf("USB Reset\n");
}
// Connection change.
if(data & DEV_CON_CH)
{
printf("Connection change\n");
/* Pass */
}
// Suspend/resume.
if(data & DEV_SUS_CH)
{
if(data & DEV_SUS)
{
printf("USB Suspend\n");
USBSuspend();
}
else
{
printf("USB Resume\n");
USBResume();
}
}
return;
}
// Handle endpoint interrupt.
if(interruptData & EP_SLOW_INT)
{
printf("Endpoint interrupt\n");
}
if(interruptData & EP_FAST_INT)
{
printf("Endpoint interrupt\n");
}
}
The terminal output:
InterruptData: 0x19
Data: 0x19
USB Reset
USB Resume
InterruptData: 0x1
Edit: Using the SIE's GET ERROR CODE command, I found that I end up with an "unexpected packet" error by the end of the USB initialisation function. However, if I read the test register, I get back 0xA50F as expected, meaning that my communication with the SIE is working and my USB/AHB clocks are presumably configured correctly and running.
I managed to get an example USB project working in IAR Embedded Workbench. Based on that, I made two changes that mostly fixed my program:
I changed the configuration of my project from "semihosted" to "SWO" which made my print outputs a lot faster.
I removed the following line. It apparently misconfigured the USB clock rate.
LPC_SC->USBCLKSEL = 0x102;
I'm using PIC32MX575F256L microcontroller. This is a simple program to turn on and turn of LED's connected to lower 8 bits of port A using a simple switch as external interrupt:
#include<p32xxxx.h>
#include<plib.h>
void __ISR(3,ipl4) EXTInterrupthandller(void) // interrupt handler
{
PORTAINV = 0x0ff; //toggle lower 8 bits of port A (LEDs)
IFS0CLR = 0x00000800; //clear the interrupt flag
}
main()
{
DDPCONbits.JTAGEN = 0;
TRISA= 0; // set port A as output port
TRISESET = 0x0200; // set external interrupt pin as input pin
PORTA = 0; // initially LEDs are off
IEC0CLR = 0x00000800; // disable INT3
IPC0SET = 0x10000000; // set priority and sub priority of interrupt
INTCONCLR = 0x00000004; // clear the bit for falling edge trigger
IFS0CLR = 0x00000800; // clear the interrupt flag
INTEnableSystemSingleVectoredInt(); // configure system to handle single vector interrupts
IEC0SET = 0x00000800; // enable INT3
while(1); // main loop
}
I'm developing a C application using avr-libc on an AVR ATmega328P microcontroller. Since I don't have an ICE debugger for it, I followed these instructions and this tutorial for making the stdio.h functions such as printf able to use the hardware UART as stdout.
That works, and I can see the output on a PC terminal connected to my target board, but the strange thing is: When I have only one printf on main, but before the main loop something is causing the processor to reset, while if I have a printf only inside the main loop or before the main loop AND inside the loop it works fine. Something like this:
#include <stdio.h>
/* stream definitions for UART input/output */
FILE uart_output = FDEV_SETUP_STREAM(uart_drv_send_byte, NULL, _FDEV_SETUP_WRITE);
FILE uart_input = FDEV_SETUP_STREAM(NULL, uart_drv_read_byte, _FDEV_SETUP_READ);
int main() {
/* Definition of stdout and stdin */
stdout = &uart_output;
stdin = &uart_input;
/* Configures Timer1 for generating a compare interrupt each 1ms (1kHz) */
timer_init()
/* UART initialization */
uart_drv_start(UBRRH_VALUE, UBRRL_VALUE, USE_2X, &PORTB, 2);
/* Sets the sleep mode to idle */
set_sleep_mode(SLEEP_MODE_IDLE);
printf("START ");
/* main loop */
while(1) {
printf("LOOP ");
/* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
sleep_mode();
}
}
The code above produces the following output:
START LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
which is expected. If we comment the printf("START ") line it produces this:
LOOP LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
which is also fine. The problem is, if I don't have any printf inside the while loop, it goes like this:
START START START START START START ... START
That clearly shows the processor is being restarted, since the expected output would be just one START and nothing else while the infinite loop goes on being awaken only on the 1 kHz timer interrupts. Why is this happening? I should stress there's no watchdog timer configured (if there was, the cases where only LOOP is printed would be interrupted by a new START also).
Monitoring execution using GPIO pins
To try to get some insight into the situation, I turned GPIO pins ON and OFF around the problematic print("START ") and sleep_mode in the main loop:
int main() {
/* Irrelevant parts suppressed... */
GPIO1_ON;
printf("START ");
GPIO1_OFF;
/* Main loop */
while(1) {
/* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
GPIO2_ON;
sleep_mode();
GPIO2_OFF;
}
}
It turned out that GPIO1 stays ON for 132 µs (printf("START ") call time) and then OFF for 6.6 ms - roughly the time to transmit the six characters at 9600 bit/s - and GPIO2 toggles 12 times (six times two interrupts: the UART-ready-to-transmit interrupt and the UART-empty-data-register interrupt), showing sleep active for another 1.4 ms before GPIO1 goes ON again indicating a new printf("START ") - hence after reset. I'll probably have to check out the UART code, but I'm pretty sure the non-interrupt UART version also shows the same problem, and that doesn't explain either why having a printf inside the main loop works OK, without a reset happening (I would expect the reset would happen in any case should the UART code be faulty).
(SOLVED!): For completeness, The UART init and TX code is below**
This was my first attempt in writing an interrupt driven UART driver for the AVR, but one that could be used either on a RS-232 or a RS-485, which requires activating a TX_ENABLE pin while transmitting data. It turned out that, since I had to make the code useable either on ATmega328P or ATmega644, the interrupt vectors have different names, so I used a #define TX_VECTOR to assume the right name according to the processor used. In the process of making and testing the driver the choosing of "TX_VECTOR" for the UDRE data empty interrupt ended up masking the fact I hadn't defined the USART0_TX_vect yet (this was work in progress, I might not even need both anyway...)
Right now I just defined an empty interrupt service routine (ISR) for USART0_TX_vect and the thing doesn't reset anymore, showing #PeterGibson nailed it right on. Thanks a lot!
// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
#define RX_VECTOR USART_RX_vect
#define TX_VECTOR USART_UDRE_vect
// Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
#define RX_VECTOR USART0_RX_vect
#define TX_VECTOR USART0_UDRE_vect
#endif
ISR(TX_VECTOR)
{
uint8_t byte;
if (!ringbuffer_read_byte(&txrb, &byte)) {
/* If RS-485 is enabled, sets TX_ENABLE high */
if (TX_ENABLE_PORT)
*TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
UDR0 = byte;
}
else {
/* No more chars to be read from ringbuffer, disables empty
* data register interrupt */
UCSR0B &= ~_BV(UDRIE0);
}
/* If RS-485 mode is on and the interrupt was called with TXC0 set it
* means transmission is over. TX_ENABLED should be cleared. */
if ((TX_ENABLE_PORT) && (UCSR0A & _BV(TXC0) & _BV(UDR0))) {
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
UCSR0B &= ~_BV(UDRIE0);
}
}
void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
volatile uint8_t* rs485_tx_enable_io_port,
uint8_t rs485_tx_enable_io_pin)
{
/* Initializes TX and RX ring buffers */
ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);
/* Disables UART */
UCSR0B = 0x00;
/* Initializes baud rate */
UBRR0H = ubrrh;
UBRR0L = ubrrl;
if (use2x)
UCSR0A |= _BV(U2X0);
else
UCSR0A &= ~_BV(U2X0);
/* Configures async 8N1 operation */
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
/* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
* configures the pin as output and enables the TX data register empty
* interrupt so it gets disabled in the end of transmission */
if (rs485_tx_enable_io_port) {
TX_ENABLE_PORT = rs485_tx_enable_io_port;
TX_ENABLE_PIN = rs485_tx_enable_io_pin;
/* Configures the RS-485 driver as an output (on the datasheet the data
* direction register is always on the byte preceding the I/O port addr) */
*(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);
/* Clears TX_ENABLE pin (active high) */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
/* Enables end of transmission interrupt */
UCSR0B = _BV(TXCIE0);
}
/* Enables receptor, transmitter and RX complete interrupts */
UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
}
FIXED UART CODE (NOW WORKING 100%!)
In order to help anyone interested or developing a similar interrupt driven UART driver for the AVR ATmega, here it goes the code with the problems above fixed and tested. Thanks to everyone who helped me spot the problem with the missing ISR!
// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
#define RX_BYTE_AVAILABLE USART_RX_vect
#define TX_FRAME_ENDED USART_TX_vect
#define TX_DATA_REGISTER_EMPTY USART_UDRE_vect
// Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
#define RX_BYTE_AVAILABLE USART0_RX_vect
#define TX_FRAME_ENDED USART0_TX_vect
#define TX_DATA_REGISTER_EMPTY USART0_UDRE_vect
#endif
/* I/O port containing the pin to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t* TX_ENABLE_PORT = NULL;
/** Pin from the I/O port to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t TX_ENABLE_PIN = 0;
ISR(RX_BYTE_AVAILABLE)
{
// Read the status and RX registers.
uint8_t status = UCSR0A;
// Framing error - treat as EOF.
if (status & _BV(FE0)) {
/* TODO: increment statistics */
}
// Overrun or parity error.
if (status & (_BV(DOR0) | _BV(UPE0))) {
/* TODO: increment statistics */
}
ringbuffer_write_byte(&rxrb, UDR0);
}
ISR(TX_FRAME_ENDED)
{
/* The end of frame interrupt will be enabled only when in RS-485 mode, so
* there is no need to test, just turn off the TX_ENABLE pin */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
}
ISR(TX_DATA_REGISTER_EMPTY)
{
uint8_t byte;
if (!ringbuffer_read_byte(&txrb, &byte)) {
/* If RS-485 is enabled, sets TX_ENABLE high */
if (TX_ENABLE_PORT)
*TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
UDR0 = byte;
}
else {
/* No more chars to be read from ringbuffer, disables empty
* data register interrupt */
UCSR0B &= ~_BV(UDRIE0);
}
}
void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
volatile uint8_t* rs485_tx_enable_io_port,
uint8_t rs485_tx_enable_io_pin)
{
/* Initializes TX and RX ring buffers */
ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);
cli();
/* Disables UART */
UCSR0B = 0x00;
/* Initializes baud rate */
UBRR0H = ubrrh;
UBRR0L = ubrrl;
if (use2x)
UCSR0A |= _BV(U2X0);
else
UCSR0A &= ~_BV(U2X0);
/* Configures async 8N1 operation */
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
/* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
* configures the pin as output and enables the TX data register empty
* interrupt so it gets disabled in the end of transmission */
if (rs485_tx_enable_io_port) {
TX_ENABLE_PORT = rs485_tx_enable_io_port;
TX_ENABLE_PIN = rs485_tx_enable_io_pin;
/* Configures the RS-485 driver as an output (on the datasheet the data
* direction register is always on the byte preceding the I/O port addr) */
*(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);
/* Clears TX_ENABLE pin (active high) */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
/* Enables end of transmission interrupt */
UCSR0B = _BV(TXCIE0);
}
/* Enables receptor, transmitter and RX complete interrupts */
UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
sei();
}
void uart_drv_send_byte(uint8_t byte, FILE *stream)
{
if (byte == '\n') {
uart_drv_send_byte('\r', stream);
}
uint8_t sreg = SREG;
cli();
/* Write byte to the ring buffer, blocking while it is full */
while(ringbuffer_write_byte(&txrb, byte)) {
/* Enable interrupts to allow emptying a full buffer */
SREG = sreg;
_NOP();
sreg = SREG;
cli();
}
/* Enables empty data register interrupt */
UCSR0B |= _BV(UDRIE0);
SREG = sreg;
}
uint8_t uart_drv_read_byte(FILE *stream)
{
uint8_t byte;
uint8_t sreg = SREG;
cli();
ringbuffer_read_byte(&rxrb, &byte);
SREG = sreg;
return byte;
}
You've possibly enabled the UDRE (Uart Data Register Empty) interrupt and not set a vector for it, so when the interrupt triggers the processor resets (according to the defaults). When printf is called continuously in the main loop, this interrupt is never triggered.
From the docs
Catch-all interrupt vector
If an unexpected interrupt occurs (interrupt is enabled and no handler
is installed, which usually indicates a bug), then the default action
is to reset the device by jumping to the reset vector. You can
override this by supplying a function named BADISR_vect which should
be defined with ISR() as such. (The name BADISR_vect is actually an
alias for __vector_default. The latter must be used inside assembly
code in case is not included.)
I ran in the same situation right now, but since I don't have a high reputation on stackoverflow, I can not vote.
here is a snippet of my initialization procedure that caused this problem to me:
void USART_Init()
{
cli();
/* Set baud rate */
UBRR0H = (uint8_t)(BAUD_PRESCALE>>8);
UBRR0L = (uint8_t)BAUD_PRESCALE;
/* Enable receiver and transmitter */
UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
/* Set frame format: 8data, 1stop bit 8N1 => 86uS for a byte*/
UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
/*enable Rx and Tx Interrupts*/
UCSR0B |= (1 << RXCIE0) | (1 << TXCIE0); //<- this was the problem
/*initialize the RingBuffer*/
RingBuffer_Init(&RxBuffer);
sei();
}
The problem was that I initially used interrupt based transmission, but later on I have changed the design and went for 10ms polling for Tx sequence, and forgotten to change this line as well in the init procedure.
Thanks very much for pointing this out Peter Gibson.
I have a problem , please help me.
for about a project homework ı need read from virtual serial port with microC and send this info to AT89C52 microconttoller..
This is my source code:
int uart_rd;
void main() {
P1=0X00;
UART1_Init(9600);
delay_ms(100);
while(1)
{
if(UART1_Data_Ready()){
uart_rd=UART1_read();
if(uart_rd=='1')
{P1=0X01; delay_ms(1500); P1=0X00; }
if(uart_rd=='2')
{P1=0X02; delay_ms(1500); P1=0X00; }
}
}
}
BUT I cant get info from the port. Where is the mistake.Please help me...
You are defining your UART receive variable (uart_rd) as an int, which is a 2 byte variable. I would expect UART1_read() to return a single byte (char).
I am not familiar with your particular setup or debugging/troubleshooting options, but you might try writing some code to assist in debugging your issue. The following example may be useful. It does assume that LEDs are connected to both port 1 and port 2, so some adjustment may be necessary.
char uart_rd;
void main()
{
UART1_Init(9600); // Initialize UART at 9600 bps
delay_ms(100); // Wait for UART to stabilize
while(1)
{
if(UART1_Data_Ready())
{
P2 = 0xFF; // Turn ON PORT2 LEDs upon data ready
uart_rd = UART1_read(); // Receive data
P1 = uart_rd; // Display data on port 1 LEDs
UART1_write(uart_rd); // Transmit same data back
delay_ms(1500); // Brief delay
P1 = 0x00; // Turn OFF port 1 LEDs
P2 = 0x00; // Turn OFF port 2 LEDs
}
}
}