A brief tutorial on DSP interrupt settings[Copy link]
I. Brief Introduction This article introduces a simple method for setting interrupts in the TMS320C6000 series. Through the example of timer interrupt, MCBSP serial port receiving interrupt and external interrupt, this article introduces how to configure each interrupt register, write the interrupt vector table and the interrupt service function. Finally, a brief example program is provided for download and use. This example has been tested on the TI official experiment board of DSK6416. Since the timer and serial port working modes are relatively complicated, the parts not related to interrupts are not introduced. 2. What common tasks need to be done to implement DSP interrupts Set which non-maskable interrupts are allowed Set the interrupt source of each allowed non-maskable interrupt Set to enable the total interrupt Design the interrupt vector table Mount the interrupt vector table to the instruction memory through the cmd file Provide an interrupt processing function If the first address of the interrupt vector table is not mounted at address 0, then you need to set the interrupt vector table address register For different interrupt sources, you need to do your own work. For example, if it is an external interrupt, you need to set the pin polarity, that is, to generate an interrupt from high -> low or vice versa. In order to take care of readers with less knowledge, the following will start from a new project and guide everyone to create an interrupt sample program. If you are familiar with creating projects, you can skip this step. 3. Create a new project 1. Click Project->New, set Project Name to intexample, Project Type to Executable, and Target to the device you need. Here, since I am using the DSK6416 evaluation board, I choose TMS320C64XX. 2. Add the standard library rts6400.lib to automatically generate functions such as c_int00. Right-click the current project, select "Add Files to Project", select the path where the library is located, which is usually installed with CCS. You can refer to the path address of this CCS3.1 version: CCStudio_v3.1\C6000\cgtools\lib\rts6400.lib If you are using other device types, please select other device libraries in the lib folder. Add source files, select File->New->Source File, save as main.c to the project path. Write the main function in this file. void main(void) { while(1); } Finally, add this file to the project through steps 2. 3.Add the register alias definition header file. In this example, after defining aliases for the registers that need to be used, a global.h file is formed. The contents will be introduced step by step later. Here you can create an empty file and include it in main.c. #include "global.h" At this point, a new DSP project framework is completed. 4. Add cmd link file In order to implement memory configuration during linking, we need to provide a cmd file. For convenience, you can copy a copy from the official sample program and modify it. In the installation directory D:\CCStudio_v3.1\tutorial\device type\hello1 example, you will find a hello1.cmd. Copy it to the directory of this project, rename it to link.cmd, and finally add it to the project. Since this file does not declare stack and heap, a warning will be generated. If there is a lot of dynamic data, it is easy to overflow. Therefore, we'd better provide the size of stack and heap in this file. The values can be adjusted according to actual conditions. After modification, the content of this file is similar to: -stack 0x1000 -heap 0x1000 MEMORY { ISRAM : origin = 0x0, len = 0x1000000 } SECTIONS { .vectors > ISRAM .text > ISRAM .bss > ISRAM .cinit > ISRAM .const > ISRAM .far > ISRAM .stack > ISRAM .cio > ISRAM .sysmem > ISRAM } At this point, the project has been established and you can compile it again to see if it is normal. -------------------------- intexample.pjt - Debug --------------------------- [main.c] "D:\CCStudio_v3.1\C6000\cgtools\bin\cl6x" -g -fr"D:/intexample/Debug" -d"_DEBUG" -mv6400 -@"Debug.lkf" "main.c" [Linking...] "D:\CCStudio_v3.1\C6000\cgtools\bin\cl6x" -@"Debug.lkf" Build Complete, 0 Errors, 0 Warnings, 0 Remarks. 4. Timer Interrupt Design First, we implement a timer interrupt because it is not affected by external factors and is easy to test. In global.h file, add the control register and interrupt register alias definitions. In addition, in order to use Timer 1, its alias should also be defined: /*Define Control Register*/ extern cregister volatile unsigned int AMR; /* Address Mode Register */ extern cregister volatile unsigned int CSR; /* Control Status Register */ extern cregister volatile unsigned int IFR; /* Interrupt Flag Register */ extern cregister volatile unsigned int ISR; /* Interrupt Set Register */ extern cregister volatile unsigned int ICR; /* Interrupt Clear Register */ extern cregister volatile unsigned int IER; /* Interrupt Enable Register */ extern cregister volatile unsigned int ISTP; /* Interrupt Service Tbl Ptr */ extern cregister volatile unsigned int IRP; /* Interrupt Return Pointer */ extern cregister volatile unsigned int NRP; /* Non-maskable Int Return Ptr*/ extern cregister volatile unsigned int IN; /* General Purpose Input Reg */ extern cregister volatile unsigned int OUT; /* General Purpose Output Reg */ /* Define interrupt selection register */ #define MUXH 0x019C0000 #define MUXL 0x019C0004#define EXTPOL 0x019C0008/size#define CTL1 0x01980000 //Timer1 control register#define PRD1 0x01980004 //Timer1 period register#define CNT1 0x01980008 //Timer1 counter register#define PRD2 0x01980004 //Timer1 period register#define CNT1 0x01980008 //Timer1 counter register#define CNT2 0x01980008 //Timer1 counter register#define CNT3 0x01980004 //Timer3 counter register#define CNT4 0x01980008 //Timer3 counter register#define CNT5 0x01980006 //Timer5 counter register#define CNT6 0x01980007 //Timer6 counter register#define CNT7 0x01980008 //Timer7 counter register#define CNT8 0x01980009 //Timer8 counter register#define CNT9 { *( volatile unsigned int* )CTL1= 0x00000201; //Counter function setting *( volatile unsigned int* )PRD1= 0x1000; //Counter period value *( volatile unsigned int* )CTL1|= 0x000000C0; //Counter clear, start } And call it in the main function. Then we set the interrupt register parameters. DSP supports 1 RESET interrupt, 1 NMI (non-maskable interrupt), 12 maskable interrupts (INT4-15), they have priority order, INT4 is the highest and INT15 is the lowest. Each interrupt number can be set to any interrupt source. Here we select interrupt INT10, that is, turn on interrupt 10, and set its interrupt source to timer 1, that is, fill in the interrupt source selection code in the specified bit in MUXH or MUXL: The interrupt source selection code is defined as follows: (This content can be obtained by searching INTSEL in the help) INTSEL (Interrupt Selection Number Deion) 00000b DSPINT Host port host to DSP interrupt 00001b TINT0 Timer 0 interrupt 00010b TINT1 Timer 1 interrupt 00011b SD_INT EMIF SDRAM timer interrupt 00100b EXT_INT4 External interrupt 4 00101b EXT_INT5 External interrupt 5 00110b EXT_INT6 External interrupt 6 00111b EXT_INT7 External interrupt 7 01000b EDMA_INT EDMA channel (0-15) interrupt 01001-01011b Reserved 01100b XINT0 McBSP0 transmit interrupt 01101b RINT0 McBSP0 receive interrupt 01110b XINT1 McBSP1 transmitinterrupt 01111b RINT1 McBSP1 receive interrupt 10000-11111b Reserved The interrupt selection code of timer 1 is 00010. The register definitions of MUXH and MUXL are as follows: (can also be obtained through help) MUXH Bit interrupt source 30-26 INTSEL15 25-21 INTSEL14 20-16 INTSEL13 14-10 INTSEL12 9-5 INTSEL11 4-0 INTSEL10 31, 15 bits are reserved, fill with 0 MUXL Bit interrupt source 30-26 INTSEL9 25-21 INTSEL8 20-16 INTSEL7 14-10 INTSEL6 9-5 INTSEL5 4-0 INTSEL4 31, 15 bits are reserved, fill with 0 Therefore, we set the 4th to 0th bits of MUXH to the interrupt selection code 00010 of timer 1, and the remaining bits can be set arbitrarily (you can fill in 1 here). After converting to hexadecimal, the settings are as follows: *( volatile unsigned int* )MUXH=0x7fff7fe2; MUXL can be left unset. Turn on interrupt to IE10, enable global interrupt: IER |= 0x00000402; // IE10=1 CSR |= 0x00000001; // Global interrupt enable The above completes the configuration of interrupt parameters, the interrupt is enabled and can be entered. The following is the interrupt processing process. It is mainly divided into designing the interrupt vector table and the interrupt processing function. We can copy a prototype of the vector table from the example of DSP CCS. For example, CCStudio_v3.1\tutorial\dsk6416\hello1\vectors.asm Copy it to the directory of this project and add it to the project. The interrupt vector table contains 16 interrupt processing units, and each unit must be limited to 8 instructions. If there are less than 8 statements, you can use nop to fill them (but nop 4 counts as 1 statement). If there are too many service programs, you can create a special interrupt service program. At this time, this table only plays a jump role, so that the CPU can correctly address and find the correct interrupt service entry. First, analyze this file. The file begins with a macro defined to handle unused interrupts. unused .macro id .global unused:id: unused:id: b unused:id: ; nested branches to block interrupts nop 4 b unused:id: nop nop nop nop nop Its approach is to put the program into an infinite loop. I think this approach may not be the best, so I suggest using a direct return method. Return to the interrupted address, for maskable interrupts it is birp, so replace this macro part with, and make sure to have 8 entries. unused .macro id .global unused:id: unused:id: b irp nop nop nop nop nop nop nop nop .endm In this way, even if we accidentally enable this interrupt, we will return smoothly. Of course, if we are sure that it is not enabled, then its content is meaningless. The body of the code uses a series of unused n to insert this macro, which takes up space. Since the return of NMI is different from that of maskable interrupt, it is located below RESET in the vector table, that is, unused 1. We delete it and replace it with NMI: b nrp nop nop nop nop nop nop nop nop In order to implement the processing of timer 1 interrupt, we delete unused 10 and replace it with our own interrupt jump routine as follows: INT10: stw b0,*--b15 mvkl _xint0_isr,b0 mvkh _xint0_isr,b0 b b0 ldw *b15++,b0 nop 3 nop nop In addition, the AND statement is required: .ref _c_int00 ; C entry point Similarly, add the reference of the handler .ref _xint0_isr ; timer 1 interrupt handler Since the location of the interrupt vector table needs to be specified and should be aligned to 400H, the segment name has been defined in this file: .sect ".vectors" Therefore, we need to mount this .vector code segment to a dedicated specified memory area. Modify the link.cmd link file, add the INT area, and start at address 0. Its size is 400H, and modify the original ISRAM starting point. And point the .vector in SECTIONS to the memory area defined by yourself. MEMORY { INT : origin = 0x00000000, len = 0x0000400 ISRAM : origin = 0x00000400, len = 0x1000000 } SECTIONS { .vectors > INT … } The interrupt vector table is set and installed. Finally, design the interrupt service function and add it in main.c: interrupt void xint0_isr(void) { } Note that the interrupt keyword must be marked to generate the interrupt return statement birp. At the same time, the entry and exit parameters of this function should be void. If you need to update the variable, you can use the global variable method. In addition, the C language function name differs from the assembly language by an "_", please pay attention to add it when designing the interrupt vector table. After the above steps, the entire timer interrupt production process is completed. At this time, you can add a breakpoint on interrupt void xint0_isr(void), and it should stop here after running. If the entry fails, you can first set a breakpoint on the sentence INT10:stw b0,*--b15 of vector.asm. If it does not enter here, it proves that the interrupt did not come in. You can check whether there is a problem with the parameter setting. 5. External interrupt design DSP6000 series provides four interrupt input pins INT4-7, so external interrupts can be realized through the input level changes of these four pins. For the polarity of the level change, it is divided into high to low and low to high. Therefore, DSP uses register EXTPOL to set it. Only the lower 4 bits of EXTPOL are valid, representing INT4-7 respectively. For each bit, there are: 0: Low -> high generates interrupt 1: High -> low generates interrupt Therefore, setting it can complete the polarity change. Below, taking setting the external port INT7 interrupt and mounting it to interrupt No. 12 as an example, the implementation process is briefly described: Set interrupt No. 12 to external interrupt 7, that is, MUXH (4:0) = 00111. At this time, MUXH is set to: *( volatile unsigned int* )MUXH=0x7fff7ce2; //0111 1100 1110 0010 Turn on the 12th bit of IER. IER |= 0x00001402; // IE10=1 IE12=1 Replace unused 12 of vectors.asm with: INT12: stw b0,*--b15 mvkl _extint7_isr,b0 mvkh _extint7_isr,b0 b b0 ldw *b15++,b0 nop 3 nop nop and add reference .ref _extint7_isr Add the service function in main.c: interrupt void extint7_isr(void) { } In hardware, a low->high signal on the INT7/GPIO7 pin can trigger an interrupt. If you change this polarity, you can set the fourth bit of EXTPOL to 1: *( volatile unsigned int* )EXTPOL|= 0x00000008; At this time, a high->low signal can generate an interrupt. It should be noted that if you have initialized the GPIO, you must ensure that the corresponding bit of the interrupt pin of GPEN is 1. If all are enabled: *(volatile unsigned int* )GPEN = 0x000000F0; VI. Design of MCBSP serial port receiving interrupt In actual application, it is often necessary to receive serial port data through interrupt. Here, it is assumed that MCBSP0 receiving interrupt is added to No. 11. First, add the MCBSP0 alias to global.h file. Set the MCBSP0 parameters and enable it. Its initialization function is: void MCBSP0_Init(void) { *( volatile unsigned int* )McBSP0_SPCR = 0x00000000; *( volatile unsigned int* )McBSP0_SRGR = 0x200000FF; *( volatile unsigned int* )McBSP0_PCR = 0x00000800; *( volatile unsigned int* )McBSP0_XCR = 0x000100A0; *( volatile unsigned int* )McBSP0_RCR = 0x000100A0; *( volatile unsigned int* )McBSP0_MCR = 0x00000000; *( volatile unsigned int* )McBSP0_SPCR |= 0x00C10001; } and called in the main function. Enable interrupt 11: IER |= 0x00001C02; // IE10=1 IE11=1 IE12=1 And set MUXH (9:5) = 01101. Combining the above three interrupts, MUXH is: *( volatile unsigned int* )MUXH=0x7fff1da2; // 0001 1101 1010 0010 Of course, if only the current interrupt is considered, MUXH can be set to: *( volatile unsigned int* )MUXH=0x7fff7dbf; // 0111 1101 1011 1111 Make an interrupt service routine and take out the data: interrupt void rint0_isr(void) { int DotRev; DotRev=*( volatile unsigned int *)McBSP0_DRR; } Modify vectors.asm and replace unused 11 is: INT11: stw b0,*--b15 mvkl _rint0_isr,b0 mvkh _rint0_isr,b0 b b0 ldw *b15++,b0 nop 3 nop nop Add reference: .ref _rint0_isr ; mcbsp 0 receive interrupt handler At this time, all tasks are completed. You can observe the received value by setting breakpoints. In addition, it is necessary to note that the data must be taken out in the service program, otherwise it will stop receiving new data. 7. Other topics 1. Set the starting position of the interrupt vector table The above discussion is about placing the interrupt vector table at address 0. If you need to place it at any address (aligned with 400H), then you need to provide the starting address of the vector table. For example, our terminal vector position: INT is set to: MEMORY { INT: origin = 0x00000400, len = 0x0000400 ISRAM: origin = 0x00000800, len = 0x1000000 } So when initializing the interrupt, we should set: ISTP=0x00000400; 2. Check the current interrupt bitmap You can check the corresponding bits (15:0) of the interrupt flag register IFR to see if an interrupt has arrived. 3. Clear/set the original interrupt If you need to clear the original interrupt, you can do so by setting the corresponding bit in the ICR register. If you want to manually trigger an interrupt, you can set the corresponding bit in the ISR register, which will update the IFR bitmap. For example, in the timer interrupt service routine, we manually trigger the external INT7 interrupt No. 12 by setting the 12th bit of the ISR. interrupt void xint0_isr(void) { ISR=0x00001000; } Then the CPU will execute extint7_isr(void) to handle this interrupt. For another example, in the external interrupt in the above example, sometimes there will be an interruption as soon as the computer is turned on without sending a signal. How to overcome this problem? It can be overcome through ICR. Setting ICR can clear the maskable interrupt. The corresponding bit is valid. For example, when setting the interrupt initialization, all the original interrupts are cleared. Then you can add the statement: ICR =0xffffffff; 4. Interrupt settings under DSP/BIOS Under DSP/BIOS management, we do not need to set the interrupt vector table and interrupt initialization by ourselves, everything can be done through the graphical settings of BIOS. Add a DSP/BIOS Select File->New, select DSK6416 in this test, and the reader can select according to their actual needs. Save as Configuration1.cdb. Add it to the project. As required in the above example, select HWI interrupts 10, 11, and 12, right-click and select Properties and fill in the following parameters respectively: HWI_INT10 interrupt source=Timer_1 =_xint0_isr (note the underscore!) HWI_INT11 interrupt source=MCSP_0_Receive =_rint0_isr HWI_INT11 interrupt source=External_Pin_7 =_extint7_isr The same method can be used to enable interrupts in the main function. IER |= 0x00001C02; // IE10=1 IE11=1 IE12=1 CSR |= 0x00000001; // Global interrupt enable The configuration is now complete. 5. How to check if the interrupt cannot come in? First check whether the corresponding bit of IER is set to open and the lowest bit of CSR is set. Secondly, check whether the address of the interrupt vector table is set correctly. If it is confirmed to be correct. Set a breakpoint at the position where the vector table interrupt should enter. Run to see if the breakpoint is executed. If so, then check whether the interrupt service routine is executed. If the interrupt only comes in once and cannot be entered again, you can check whether the interrupt vector table can return to the original program. If it cannot return, check whether it is 8 statements. In addition, you can check whether the birp statement is executed by tracing. If you can return to the original program normally, such as serial port reception, check whether there is no value that causes blocking. If so, you need to retrieve the original value to have a new interrupt. 6. Where can I find the description of several interrupt registers? You can find it by searching for keywords in Help->Contents. You can also refer to the official documentation.