Linux 4.5 GPIO Interrupt Through Devicetree on Xilinx Zynq Platform - c

I am using a custom development board with a Zynq XC72010 used to run a Linux 4.5 kernel. I am developing a device driver for a chip we are testing in house and I am having a lot of issues trying to bind a GPIO line to a software IRQ. So far I have tried a few methods and exhausted any google search I can think of. The relevant parts of my devicetree configuration:
/ {
compatible = "xlnx,zynq-7000";
amba {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
ranges;
intc: interrupt-controller#f8f01000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xF8F01000 0x1000>,
<0xF8F00100 0x100>;
};
i2c0: i2c#e0004000 {
compatible = "cdns,i2c-r1p10";
status = "disabled";
clocks = <&clkc 38>;
interrupt-parent = <&intc>;
interrupts = <0 25 4>;
reg = <0xe0004000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
// I WANT INTERRUPT TO TRIGGER
// ON THIS DEVICE (axi_gpio_0, pin 2)
device: device#48 {
compatible = "device,name";
reg = <0x48>;
reset-gpios = <&axi_gpio_0 1 0>;
interrupt-parent = <&axi_gpio_0>;
interrupt-gpios = <&axi_gpio_0 2 0>;
};
};
};
amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges ;
axi_gpio_0: gpio#41200000 {
#gpio-cells = <2>;
compatible = "xlnx,xps-gpio-1.00.a";
gpio-controller;
interrupt-parent = <&intc>;
interrupts = <0 31 4>;
reg = <0x41200000 0x10000>;
xlnx,all-inputs = <0x0>;
xlnx,all-inputs-2 = <0x0>;
xlnx,all-outputs = <0x0>;
xlnx,all-outputs-2 = <0x0>;
xlnx,dout-default = <0x00000000>;
xlnx,dout-default-2 = <0x00000000>;
xlnx,gpio-width = <0x10>;
xlnx,gpio2-width = <0x20>;
xlnx,interrupt-present = <0x1>;
xlnx,is-dual = <0x0>;
xlnx,tri-default = <0xFFFFFFFF>;
xlnx,tri-default-2 = <0xFFFFFFFF>;
};
};
I am trying to assign an interrupt to pin 2 of 'axi_gpio_0' inside of 'device'.
Browsing google yielded 3 common methods for binding the interrupt in driver code:
/* Method 1 */
device->interrupt_gpio = devm_gpiod_get_optional(&i2c_client->dev,
"interrupt", GPIOD_IN);
if(IS_ERR(device->interrupt_gpio))
return PTR_ERR(device->interrupt_gpio);
printk("device: Interrupt GPIO = %d\n",desc_to_gpio(device->interrupt_gpio));
irq = gpiod_to_irq(device->interrupt_gpio);
printk("device: IRQ = %d\n",irq);
ret = devm_request_threaded_irq(&i2c_client->dev, irq,
NULL, device_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
"device", device);
if (ret != 0)
dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);
/* Method 2 */
device->interrupt_gpio = devm_gpiod_get_optional(&i2c_client->dev,
"interrupt", GPIOD_ASIS);
if (IS_ERR(device->interrupt_gpio))
return PTR_ERR(device->interrupt_gpio);
if (device->interrupt_gpio) {
dev_info(&i2c_client->dev, "Found interrupt GPIO: %d\n",desc_to_gpio(device->interrupt_gpio));
dev_info(&i2c_client->dev, "IRQ Number: %d\n",gpiod_to_irq(device->interrupt_gpio));
gpio_request(desc_to_gpio(device->interrupt_gpio), "DEVICE_INT"); // Request a GPIO pin from the driver
gpio_direction_input(desc_to_gpio(device->interrupt_gpio)); // Set GPIO as input
gpio_set_debounce(desc_to_gpio(device->interrupt_gpio), 50); // Set a 50ms debounce, adjust to your needs
gpio_export(desc_to_gpio(device->interrupt_gpio), false); // The GPIO will appear in /sys/class/gpio
ret = request_irq(gpiod_to_irq(device->interrupt_gpio), // requested interrupt
(irq_handler_t) irqHandler, // pointer to handler function
IRQF_TRIGGER_RISING, // interrupt mode flag
"DEVICE_IRQ_HANDLER", // used in /proc/interrupts
NULL); // the *dev_id shared interrupt lines, NULL is okay
if (ret != 0) {
dev_err(&i2c_client->dev,
"Failed to request IRQ: %d\n", ret);
}
}
else {
dev_err(&i2c_client->dev, "Failed to get interrupt GPIO pin\n");
}
/* Method 3 */
dev_info(&i2c_client->dev, "IRQ requested: %d\n", i2c_client->irq);
ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq,
NULL, device_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"device", device);
if (ret != 0)
dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);
I tried combinations of all of these methods and various devicetree configurations, but none of them achieved the functionality I need.
Method 1 results in this output:
device: Interrupt GPIO = 892
device: IRQ = -6
device 0-0048: Failed to request IRQ: -22
Method 2 results in this output:
device 0-0048: Found interrupt GPIO: 892
device 0-0048: IRQ Number: -6
device 0-0048: Failed to request IRQ: -22
So, trying to use the descriptor GPIO and the old GPIO api's are both unsuccessful in binding an interrupt.
To try method 3, I tweaked the devicetree:
device: device#48 {
compatible = "device,name";
reg = <0x48>;
interrupt-parent = <&axi_gpio_0>; // or <&intc>?
interrupts = <0 2 0x02>; // trying to grab pin 2
};
Method 3 results in this output:
genirq: Setting trigger mode 2 for irq 168 failed (gic_set_type+0x0/0x48)
device 0-0048: IRQ requested: 168
genirq: Setting trigger mode 8 for irq 168 failed (gic_set_type+0x0/0x48)
device 0-0048: Failed to request IRQ: -22
It seems the problem is assigning a software interrupt to a specific GPIO in Linux. I don't see what I'm missing here. Any advice is appreciated.
EDIT 1:
I found out that Linux doesn't like low-level interrupts for whatever reason. Changing method 3 to:
device: device#48 {
compatible = "device,name";
reg = <0x48>;
interrupt-parent = <&axi_gpio_0>;
interrupts = <0 2 0x04>;
};
And driver code to:
dev_info(&i2c_client->dev, "IRQ requested: %d\n", i2c_client->irq);
ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq,
NULL, device_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
"device", device);
if (ret != 0)
dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);
allows me to successfully request an IRQ. However, my signal is active low, so this doesn't really help me much. Also, I'm not sure that this method is referencing axi_gpio_0 pin 2 as the interrupt signal. I can use both intc and axi_gpio_0 as interrupt-parent and it maps to the same IRQ number (I see this from cat /proc/interrupts). So, ignoring the polarity of the signal, how do I make sure that my registered interrupt is triggered based on the toggling of axi_gpio_0 pin 2?
EDIT 2:
I traced the issue with requesting an active-low interrupt to the driver for the interrupt controller: kernel/drivers/irqchip/irq-gic.c. This section of code is what causes the problem:
static int gic_set_type(struct irq_data *d, unsigned int type)
{
void __iomem *base = gic_dist_base(d);
unsigned int gicirq = gic_irq(d);
/* Interrupt configuration for SGIs can't be changed */
if (gicirq < 16)
return -EINVAL;
/* SPIs have restrictions on the supported types */
if (gicirq >= 32 && type != IRQ_TYPE_LEVEL_HIGH &&
type != IRQ_TYPE_EDGE_RISING)
return -EINVAL;
return gic_configure_irq(gicirq, type, base, NULL);
}
Hacking the kernel is not at all what I want to do, but commenting out:
/* SPIs have restrictions on the supported types */
/*if (gicirq >= 32 && type != IRQ_TYPE_LEVEL_HIGH &&
type != IRQ_TYPE_EDGE_RISING)
return -EINVAL;*/
allows me to request an active-low interrupt. For testing purposes, this should work temporarily.
UPDATE:
I have successfully created an IRQ on a GPIO pin. My problem was with the GPIO controller I was using. The controller was a Xilinx IP block inside of the Zynq Programmable Logic block and this controller is unable to trigger interrupts on GPIO pins (for reasons unknown to me). I soldered the interrupt pin on the board I was working on to a GPIO pin on a different, more generic controller and now Linux is playing nicely with me.
To summarize, a GPIO controller that matches compatible = "xlnx,xps-gpio-1.00.a"; is unable to bind to software interrupts in Linux. If you have this problem, USE A DIFFERENT GPIO CONTROLLER.
Thanks everyone for the help.

Using the device tree node of method 3, you should be able to retrieve the IRQ using irq_of_parse_and_map(i2c_client->dev.of_node, 0).
The retrieved IRQ can then be requested as you have done it with devm_request_threaded_irq().

Related

Device or resource busy while configuring gpio as a char device

I am trying to configure a GPIO pin as output but it is failing with the reason of device or resource busy. The pin is configured as output, open-drain and belongs to the group of gpiochip24 at offset 3. On reading the information from line info IOCTL I can see that the flag is 3 (OPEN_DRAIN) and is assigned a consumer. But when I try to configure the pin as output with a default value using the GPIO_GET_LINEHANDLE_IOCTL, the IOCTL fails.
int gpioFd = open("/dev/gpiochip24", 0);
if (gpioFd < 0) {
printf("ERROR: GPIO chip fail\n");
return -1;
}
struct gpiohandle_request req;
req.flags |= GPIOHANDLE_REQUEST_OUTPUT;
req.lineoffsets[0] = 3;
req.lines = 1;
req.default_values[0] = 0;
strcpy(req.consumer_label, "P_EN");
int lhfd = ioctl(gpioFd, GPIO_GET_LINEHANDLE_IOCTL, &req);
if(lhfd < 0)
{
int err = errno;
printf("Error No: %d\n", err);
printf("ERROR: Gpio Line handle\n");
return -1;
}
Output:
Error No: 16
ERROR: Gpio Line handle
I looked inside /sys/kernel/debug/gpio to understand who is using the resource but I get the following response:
gpiochip24: GPIOs 99-106, parent: platform/139b0000.pinctrl, gpg2
How do I resolve this error of device busy?
Because req is local, it has garbage when allocated, and it doesn't look like the code is initializing it properly, and - in particular - the flags value probably has junk in it.
req.flags |= GPIOHANDLE_REQUEST_OUTPUT; // no
req.flags = GPIOHANDLE_REQUEST_OUTPUT; // yes
would work around this particular issue, but be sure all the other fields in this structure are not relying on junk.

Why it is required to get clock information of system bus in linux Network driver?

I am going through network driver source and find this in probe function
priv->busclk = devm_clk_get(&pdev->dev, "ahb2_gmac");
if (IS_ERR(priv->busclk)) {
ret = PTR_ERR(priv->busclk);
dev_err(&pdev->dev, "Cannot get AHB clock err=%d\n", ret);
return ret;
}
ret = clk_prepare_enable(priv->busclk);
if (ret != 0) {
dev_err(&pdev->dev, "Cannot prepare_enable busclk\n");
return ret;
}
cr = clk_get_rate(priv->miiclk);
dev_info(&pdev->dev, "Current MII clkrate %lu\n", cr);
ret = clk_set_rate(priv->miiclk, cr / 4);
In first statement devm_clk_get(&pdev->dev, "ahb2_gmac"),
we are getting Bus(AHB2) clock and here clk_get_rate(priv->miiclk), we are getting the mii interterface clock
What purpore it serves (getting the bus and mii clock), how it helps in proper emac operations?
It is getting the device clock settings specified in the DTB and enabling and setting the device clock rate to the same. Without enabling the clocks, the peripheral will not function. For details of the final clock rate setting, you might need to have a look at the data sheet

How can I improve the rate at which the CAN IRQ is called for the LPC1788 microcontroller?

I'm developing an application for the NXP LPC1788 microcontroller using the IAR Embedded Workbench IDE and one of the requirements is to receive CAN messages off of the CAN1 port as fast as possible. My issue is that there seems to be a delay of ~270 microseconds between the start of one invocation of the CAN IRQ and the start of the next even though it only seems to take the microcontroller ~30 microseconds to handle the interrupt.
In addition to this interrupt, I also have regular timer interrupts scheduled every 50 microseconds which process received CAN and USB messages. However, this interrupt routine takes ~3 microseconds if there are no CAN or USB messages to process and only around ~80 microseconds if there are.
Because of this, I'm wondering why the CAN IRQ is not able to fire faster than every 270 microseconds and what I can do to fix that.
USB interrupts have negligible impact on performance since USB messages arrive much more infrequently than CAN messages, and I've determined that this problem occurs even when this interrupt is never fired.
Currently, my application seems to be able to handle CAN messages with an average inter-arrival time of ~300 us (tested for 3 messages generated at 1 ms intervals, with ~500,000 messages processed and a 0% drop rate).
This code initialises the CAN:
void CANHandlerInit()
{
// Configure CAN pins.
PINSEL_ConfigPin(0, 0, 1); // RD1.
PINSEL_ConfigPin(0, 1, 1); // TD1.
PINSEL_ConfigPin(0, 4, 2); // RD2.
PINSEL_ConfigPin(0, 5, 2); // TD2.
CAN_Init(CAN_1, CAN_BAUD_RATE);
CAN_Init(CAN_2, CAN_BAUD_RATE);
//printf("CAN Handler initialised\n");
}
This is used to run the CAN:
void CANHandlerRun()
{
// Enter reset mode.
LPC_CAN1->MOD |= 0x1;
LPC_CAN2->MOD |= 0x1;
#if CAN_SOURCE_PORT == CAN_PORT_1
SFF_GPR_Table[0].controller1 = SFF_GPR_Table[0].controller2 = CAN1_CTRL;
SFF_GPR_Table[0].disable1 = SFF_GPR_Table[0].disable2 = MSG_ENABLE;
SFF_GPR_Table[0].lowerID = 0x0;
SFF_GPR_Table[0].upperID = 0x7FF;
#else
SFF_GPR_Table[0].controller1 = SFF_GPR_Table[0].controller2 = CAN2_CTRL;
SFF_GPR_Table[0].disable1 = SFF_GPR_Table[0].disable2 = MSG_ENABLE;
SFF_GPR_Table[0].lowerID = 0x0;
SFF_GPR_Table[0].upperID = 0x7FF;
#endif
AFTable.FullCAN_Sec = NULL;
AFTable.FC_NumEntry = 0;
AFTable.SFF_Sec = NULL;
AFTable.SFF_NumEntry = 0;
AFTable.SFF_GPR_Sec = &SFF_GPR_Table[0];
AFTable.SFF_GPR_NumEntry = 1;
AFTable.EFF_Sec = NULL;
AFTable.EFF_NumEntry = 0;
AFTable.EFF_GPR_Sec = NULL;
AFTable.EFF_GPR_NumEntry = 0;
if(CAN_SetupAFLUT(&AFTable) != CAN_OK) printf("AFLUT error\n");
LPC_CANAF->AFMR = 0;
// Re-enter normal operational mode.
LPC_CAN1->MOD &= ~0x1;
LPC_CAN2->MOD &= ~0x1;
// Enable interrupts on transmitting and receiving messages.
#if CAN_SOURCE_PORT == CAN_PORT_1
LPC_CAN1->IER |= 0x1; /* RIE */
LPC_CAN1->IER |= (1 << 8); /* IDIE */
#else
LPC_CAN2->IER |= 0x1;
LPC_CAN2->IER |= (1 << 8);
#endif
NVIC_EnableIRQ(CAN_IRQn);
}
This is the CAN IRQ code:
void CAN_IRQHandler(void)
{
ITM_EVENT32_WITH_PC(1, 0xAAAAAAAA);
#if CAN_SOURCE_PORT == CAN_PORT_1
if(LPC_CAN1->SR & 0x1 /* RBS */)
{
CAN_ReceiveMsg(CAN_1, &recMessage);
COMMS_NotifyCANMessageReceived();
CAN_SetCommand(CAN_1, CAN_CMR_RRB);
}
#else
if(LPC_CAN2->SR & 0x1)
{
CAN_ReceiveMsg(CAN_2, &recMessage);
COMMS_NotifyCANMessageReceived();
CAN_SetCommand(CAN_2, CAN_CMR_RRB);
}
#endif
ITM_EVENT32_WITH_PC(1, 0xBBBBBBBB);
}
The COMMS_NotifyCANMessageReceived code:
void COMMS_NotifyCANMessageReceived()
{
COMMS_BUFFER_T commsBuffer;
#if MAX_MSG_QUEUE_LENGTH > 0
uint32_t length;
LIST_AcquireLock(canMessageList);
length = LIST_GetLength(canMessageList);
LIST_ReleaseLock(canMessageList);
if(length >= MAX_MSG_QUEUE_LENGTH)
{
ITM_EVENT32_WITH_PC(2, 0x43214321);
return;
}
#endif
commsBuffer.flags = COMMS_MODE_CAN;
COMMS_GetData(&commsBuffer);
LIST_AcquireLock(canMessageList);
LIST_AddItem(canMessageList, &commsBuffer);
LIST_ReleaseLock(canMessageList);
}
This is my timer interrupt code which is triggered every 50 microseconds:
void TIMER0_IRQHandler(void)
{
uint32_t canMessageCount, usbMessageCount;
if(TIM_GetIntStatus(LPC_TIM0, TIM_MR0_INT) == SET)
{
//ITM_EVENT32_WITH_PC(4, 0);
LIST_AcquireLock(canMessageList);
canMessageCount = LIST_GetLength(canMessageList);
LIST_ReleaseLock(canMessageList);
LIST_AcquireLock(usbMessageList);
usbMessageCount = LIST_GetLength(usbMessageList);
LIST_ReleaseLock(usbMessageList);
if(canMessageList != NULL && canMessageCount > 0)
{
LIST_AcquireLock(canMessageList);
if(!LIST_PopAtIndex(canMessageList, 0, &outCommsBuffer))
{
LIST_ReleaseLock(canMessageList);
goto TIMER_IRQ_END;
}
LIST_ReleaseLock(canMessageList);
ITM_EVENT32_WITH_PC(4, 0x88888888);
interpretMessage(outCommsBuffer);
ITM_EVENT32_WITH_PC(4, 0x99999999);
}
else if(usbMessageList != NULL && usbMessageCount > 0)
{
LIST_AcquireLock(usbMessageList);
if(!LIST_PopAtIndex(usbMessageList, 0, &outCommsBuffer))
{
LIST_ReleaseLock(usbMessageList);
goto TIMER_IRQ_END;
}
LIST_ReleaseLock(usbMessageList);
ITM_EVENT32_WITH_PC(4, 0xCCCCCCCC);
interpretMessage(outCommsBuffer);
ITM_EVENT32_WITH_PC(4, 0xDDDDDDDD);
}
//ITM_EVENT32_WITH_PC(4, 1);
}
TIMER_IRQ_END:
TIM_ClearIntPending(LPC_TIM0, TIM_MR0_INT);
}
Below is an excerpt of a typical event log while running the application with an average inter-arrival time between messages of 200 us:
4s 718164.69 us 0x00005E82 0xAAAAAAAA
4s 718175.27 us 0x00005EC4 0xBBBBBBBB
4s 718197.10 us 0x000056C4 0x88888888
4s 718216.50 us 0x00005700 0x99999999
4s 718438.69 us 0x00005E82 0xAAAAAAAA
4s 718449.40 us 0x00005EC4 0xBBBBBBBB
4s 718456.42 us 0x000056C4 0x88888888
4s 718476.56 us 0x00005700 0x99999999
4s 718707.04 us 0x00005E82 0xAAAAAAAA
4s 718717.54 us 0x00005EC4 0xBBBBBBBB
4s 718747.15 us 0x000056C4 0x88888888
4s 718768.00 us 0x000056C4 0x99999999
Where:
0xAAAAAAAA indicates the start of the CAN IRQ.
0xBBBBBBBB indicates the end of the CAN IRQ.
0x88888888 indicates that a CAN message is about to be processed by interpretMessage within the timer IRQ.
0x99999999 indicates that we've returned from interpretMessage.
You can see that, despite the fact that messages are arriving every 200 us, the CAN IRQ is only servicing them every ~270 us. This causes the receive queue to build up until eventually messages start getting dropped.
Any help would be appreciated.
EDIT 1
I should perhaps also mention that I have my CAN peripherals programmed to operate at a rate of 500 kBaud. In addition, my application involves echoing received CAN messages, so any messages arriving on port 1 are re-transmitted on port 2.
EDIT 2
The CAN and TIMER_0 interrupt routines have equal priority:
NVIC_SetPriority(USB_IRQn, 1);
NVIC_SetPriority(CAN_IRQn, 1);
NVIC_SetPriority(TIMER0_IRQn, 1);
EDIT 3
I can confirm that the problem remains and there is little/no change to the ~270 us interval between interrupts even when I disable timer interrupts and have the CAN IRQ do nothing but copy the received CAN message to RAM and release the receive buffer.
I'm an idiot. At 500 kBaud, it will take 271 us for a CAN message with an 11-bit standard identifier and max stuff bits to be transmitted. There's no apparent problem in my code, the bottleneck is simply how fast CAN messages can be transmitted on the bus at that point.

pic32 start bit does not clear -- Basic I2C setup

I am new to embedded programming and am trying to get my first I2C project working. I am using the PIC32MX795F512L. I am pretty much following the microchip datasheet for I2C on PIC32. The problem I'm having is the S bit is never cleared from the I2C1STAT register. It is actually unclear to me whether I have to do this or if it is done automatically at the conclusion of the Start event, but right now I'm trying to manually clear it. However, nothing that I do seems to have an effect. If more information is needed to make it easier to understand what is happening let me know. I am using a PICKIT3 so I can get debugging information as well. I know that the Master interrupt occurs, the S bit gets set, I exit the interrupt code and hang on the while statement checking the I2C1STATbits.S.
Edit: I'm editing this post to have my new code instead of the old code. I am now using a 20MHZ peripheral clock. Just one of the many things I tried today that did not work. Delay is just a 256ms delay. Super long I know, but it was quick.
main()
{
//Setup I2C1CON
I2C1CONbits.SIDL = 0; //Continue to run while in Idle
I2C1CONbits.SCLREL = 1; //Release the clock (Unsure of this)
I2C1CONbits.A10M = 0; //Using a 7 bit slave address
I2C1CONbits.DISSLW = 1; //Slew rate control disabled because running at 100 KHZ
I2C1ADD = 0x1E; //Slave address without read or write bit
I2C1BRG = 0x060; //Set the BRG clock rate - Based on Page 24-19
I2C1CONbits.ON = 1; //Turn on the I2C module
delay();
I2C1CONbits.SEN = 1; //Initiate a start event
while(I2C1CONbits.SEN == 1); //Wait until Start event is done
I2C1TRN = 0x3C; //Load the address into the Transmit register
while(I2C1STATbits.TRSTAT == 1);
while(I2C1STATbits.ACKSTAT == 0); //Wait for a ACK from the device
I2C1TRN = 0x00;
while(I2C1STATbits.TRSTAT == 1);
while(I2C1STATbits.ACKSTAT == 0);
I2C1TRN = 0x70;
while(I2C1STATbits.TRSTAT == 1);
while(I2C1STATbits.ACKSTAT == 0);
while(1);
}
Thanks for any help.
I'm also just beggining on PIC32MZ family, setting up the I2C to talk to various memory chips.
I used your code and modified it so that it would work properly. Since I am using PIC32MZ family, I believe the I2C registers should probably be the same.
I2C configuration:
I2C1CONbits.SIDL = 0; // Stop in Idle Mode bit -> Continue module operation when the device enters Idle mode
I2C1CONbits.A10M = 0; // 10-bit Slave Address Flag bit -> I2CxADD register is a 7-bit slave address
I2C1CONbits.DISSLW = 1; // Slew Rate Control Disable bit -> Slew rate control disabled for Standard Speed mode (100 kHz)
I2C1CONbits.ACKDT = 0; // Acknowledge Data bit -> ~ACK is sent
I2C1BRG = 0x0F3; // Baud Rate Generator set to provide 100KHz for SCL with 50 MHz xtal.
I followed the transmission steps provided in the I2C Datasheet so it would be easy to follow the steps coupled with the pdf and my comments on the code.
I2C Data Transmission:
// 1. Turn on the I2C module by setting the ON bit (I2CxCON<15>) to ‘1’.
I2C1CONbits.ON = 1; // I2C Enable bit -> Enables the I2C module and configures the SDAx and SCLx pins as serial port pins
//------------- WRITE begins here ------------
// 2. Assert a Start condition on SDAx and SCLx.
I2C1CONbits.PEN = 0; // Stop Condition Enable Bit -> Stop Condition Idle
I2C1CONbits.SEN = 1; // Start Condition Enable bit -> Initiate Start condition on SDAx and SCLx pins; cleared by module
while(I2C1CONbits.SEN == 1); // SEN is to be cleared when I2C Start procedure has been completed
// 3. Load the Data on the bus
I2C1TRN = 0b10100000 ; // Write the slave address to the transmit register for I2C WRITE
while(I2C1STATbits.TRSTAT == 1); // MASTER Transmit still in progress
while(I2C1STATbits.ACKSTAT == 1); // Master should receive the ACK from Slave, which will clear the I2C1STAT<ACKSTAT> bit.
I2C1TRN = 0xCE; // Register Address
while(I2C1STATbits.TRSTAT == 1);
while(I2C1STATbits.ACKSTAT == 1);
I2C1TRN = 0xCF; // Register Value
while(I2C1STATbits.TRSTAT == 1);
while(I2C1STATbits.ACKSTAT == 1);
I2C1CONbits.PEN = 1; // Stop Condition Enable Bit -> Initiate Stop condition on SDAx and SCLx pins; cleared by module
//-------------- WRITE ends here -------------
This code works well as I used a couple of LEDs to toggle indicating a successful write procedure.
The while(!(I2C1STATbits.ACKSTAT == 0)); is the right way i guess.I2C1ADD is used to set the slave address of the current PIC mc if you are using your I2C as slave.If any master requests start with that slave address the PIC will respond to that as slave.
Read this pdf where initially they have specified that in PIC the I2C is configured as both master and slave and the master request is checked by the same PIC with with the slave address in the I2C1ADD value.
http://ww1.microchip.com/downloads/en/DeviceDoc/61116F.pdf

How can I communicate over USB using the LPC1788 microcontroller?

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;

Resources