Summary: The Audio Conference Bridge enables a voice call with multiple (n >2) attendants. The algorithm monitors the voice signals from all attendants, and creates the signals to be transmitted to the attendants. In this module the implementation of such a bridge is described, The implementation is based on the integration of user-specific driver with the Simulink environement building blocks.
The Audio Conference Bridge enables a voice call with multiple (n >2) attendants, as described in Figure 1. The algorithm monitors the voice signals from all attendants, and creates the signals to be transmitted to the attendants.
This application cannot be implemented using solely the DSK6713, as it has only 2 analog ports. Therefore two daughterboards, the TLV320AIC20/20KEVM and the DSP/CODEC development platform, are used. In this example we will integrate an algorithm based on Simulink blocks, with a legacy driver.
This module demonstrates the use of the device driver building blocks together with legacy code integration to implement a custom device driver on the DSK6713. This feature allows the use of the Simulink® environment with user-specific drivers.
A wide range of algorithms exist, in the current chapter a simple algorithm based on finding the maximum level among 3 attendants is implemented
![]() |
The DSK6713 supports stereo analog inputs and outputs. The Audio Conference Bridge application demands more analog ports. In this example we will use TLV320AIC24EVM1(). This board contains two TLV320AIC24 () stereo CODECs, thus supporting 4 audio channels. The system is shown in Figure 2.
The TLV320AIC24 implements the smart time division multiplexed serial port (SMARTDM™). The SMARTDM port is a synchronous 4-wire serial port in TDM format for glue-free interface to TI DSPs. The TLV320AIC24 can be gluelessly cascaded to any SMARTDM-based device to form a multichannel CODEC, and up to eight TLV320AIC2x CODECs can be cascaded2.
The SMARTDM port is connected to the Multichannel Buffered Serial Port (McBSP) in the DSP as shown in Figure 3.
The TLV320AIC24 supports various types of analog interfaces, and can operate with sampling rates to 104 KSPS, with 16 bit samples. The block diagram is shown in Figure 4. The TLV320AIC24 features are summarized in Figure 5.
![]() |
![]() |
![]() |
![]() |
The driver operates in a double-buffering scheme as shown in Figure 6. There are two buffers in each direction (Receive/Transmit). For each frame to processes occur simultaneously:
|
The driver uses two EDMA channels. The first will read from a fixed location in the memory, which is the received data register (DRR) of the MCBSP port, to a buffer in the memory that will hold those samples. The second will write from a buffer in the memory to a fixed location that is the transmit register (DXR) of the MCBSP port.
Samples are send/received to/from the CODEC in an interleaved mode. The EDMA receive channel sorts the samples and place the samples of each channel placed in consecutive addresses in the data memory. A symmetric process occurs in the opposite direction. Figure 7 describes the process of receiving samples and Figure 8 describes the process of transmitting samples. Once a buffer is received/transmitted a Callback (please refer to section ) function is called, activating the signal processing procedures.
![]() |
![]() |
The driver consists of 4 files:
Those files should be integrated with a user file, explained in the next section.
The driver interface is implemented in the user defined file. This file consists of the main program and the callback processing function.
The driver allocates a handle to the chain of CODECs, initializes it and afterwards activates the components.
This file:
The user is required to create a callback function were the processing is implemented. This function receives pointers to two buffers as arguments; one buffer contains the latest information read from the CODECs, while the second will contain the data to be written to the CODECs. The callback function template is shown:
void process(Int16 *r_data, Int16 *w_data
{
/* Processing functions (user-defined) */
}
In this section we will describe the way the driver is used in the CCS environment.
An example of this process is provided in the next section.
The example creates two audio paths as follows:
Path # 1: J14 ⇒ CODEC#0/INP1 ⇒ SMARTDM/Channel 0 ⇒ McBSP1/Channel 0 ⇒ DSP ⇒ McBSP1/Channel 2 ⇒ SMARTDM/Channel 2 ⇒ CODEC#2/OUTP1 ⇒ J4
Path # 2: J13 ⇒ CODEC#2/INP3 ⇒ SMARTDM/Channel 2 ⇒ McBSP1/Channel 2 ⇒ DSP ⇒ McBSP1/Channel 0 ⇒ SMARTDM/Channel 0 ⇒ CODEC#0/OUTP2 ⇒ J5
The example is illustrated in Figure 9. A user defined function “test.c” was created for this application.
![]() |
The first part of the program defines the buffers for reading and writing samples.
#define NUM_CODECS 4 // The number of CODECs connected
#define DATANUM 128 // The number of samples in each channel
#define DATAMODE 0 // AIC24 operates in data mode
#if 〔DATAMODE == 1〕
#define BUFS_PER_CHANNEL 1
#else
#define BUFS_PER_CHANNEL 2
#endif
Int16 r_data1[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for read
Int16 w_data1[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for read
Int16 r_data2[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for write
Int16 w_data2[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for write
The callback function follows the template introduced in the previous section, and calls the subroutine “copyData”.
void copyData〔Int16 *inbuf, Int16 *outbuf, Int16 length〕
{
Int16 i = 0;
for 〔i = 0; i < length; i++rbbrk; {
outbuf[i] = inbuf[i];
}
}
// The callback function that is called when the EDMA buffers are full
// The function copies the data from channel 0 to channel 2
void process(Int16 *r_data, Int16 *w_data)
{
if (hAIC24.DataMode)
{
int i;
for (i=0; i<DATANUM; i++)
r_data[0*DATANUM+i] ⩓= 0xfffe;
}
copyData(r_data+0*DATANUM, w_data+2*DATANUM, DATANUM);
}
The main program:
After this step, the program will enter in an endless loop. Samples will be processed each time an EDMA interrupt occurs.
int main()
{
// setting up the AIC24 handle
AIC24_InitDefaults(⩓hAIC24, NUM_CODECS, DATANUM, r_data1, r_data2, w_data1, w_data2, process);
// determining data mode (continuous or programming)
hAIC24.DataMode = DATAMODE;
// example for setting devices input and outputs
// if defaults are ok then this is not necessary
hAIC24.Regs[0].creg6.reg6a.control_bit.mici = 0;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp1 = 1;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp2 = 0;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp3 = 0;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp4 = 0;
hAIC24.Regs[0].creg6.reg6b.control_bit.outp1 = 0;
hAIC24.Regs[0].creg6.reg6b.control_bit.outp2 = 1;
hAIC24.Regs[0].creg6.reg6b.control_bit.outp3 = 0;
hAIC24.Regs[1].creg6.reg6a.control_bit.mici = 0;
hAIC24.Regs[1].creg6.reg6a.control_bit.inp1 = 0;
hAIC24.Regs[1].creg6.reg6a.control_bit.inp2 = 0;
hAIC24.Regs[1].creg6.reg6a.control_bit.inp3 = 0;
hAIC24.Regs[1].creg6.reg6a.control_bit.inp4 = 0;
hAIC24.Regs[1].creg6.reg6b.control_bit.outp1 = 0;
hAIC24.Regs[1].creg6.reg6b.control_bit.outp2 = 0;
hAIC24.Regs[1].creg6.reg6b.control_bit.outp3 = 0;
hAIC24.Regs[2].creg6.reg6a.control_bit.mici = 0;
hAIC24.Regs[2].creg6.reg6a.control_bit.inp1 = 0;
hAIC24.Regs[2].creg6.reg6a.control_bit.inp2 = 0;
hAIC24.Regs[2].creg6.reg6a.control_bit.inp3 = 1;
hAIC24.Regs[2].creg6.reg6a.control_bit.inp4 = 0;
hAIC24.Regs[2].creg6.reg6b.control_bit.outp1 = 1;
hAIC24.Regs[2].creg6.reg6b.control_bit.outp2 = 0;
hAIC24.Regs[2].creg6.reg6b.control_bit.outp3 = 0;
hAIC24.Regs[3].creg6.reg6a.control_bit.mici = 0;
hAIC24.Regs[3].creg6.reg6a.control_bit.inp1 = 0;
hAIC24.Regs[3].creg6.reg6a.control_bit.inp2 = 0;
hAIC24.Regs[3].creg6.reg6a.control_bit.inp3 = 0;
hAIC24.Regs[3].creg6.reg6a.control_bit.inp4 = 0;
hAIC24.Regs[3].creg6.reg6b.control_bit.outp1 = 0;
hAIC24.Regs[3].creg6.reg6b.control_bit.outp2 = 0;
hAIC24.Regs[3].creg6.reg6b.control_bit.outp3 = 0;
// Starting the AIC24
AIC24_Start(⩓hAIC24);
Connect an audio source to J14, a speaker / headphone to J4 and check Path #1.
Connect an audio source to J13, a speaker / headphone to J5 and check Path #2.
The main idea is to create a Simulink environment that reads samples form 4 channels, process them and send them to 4 output channels. The example model is shown in Figure 14.
![]() |
The Simulink driver block is based on the driver described in the previous chapters. Both drivers read and write samples using a double-buffering mechanism, but they differ in the way they activate the processing algorithm.
The algorithm code, in the Simulink environment, runs as a separate free-running task. At the start of the function it checks the semaphore to see if data is ready to read from the buffer – if not then it stays in a wait state until the semaphore is posted by the callback routine (Please refer to section ). Once this has been posted, it reads the data elements from the addresses supplied by the appropriate pointers. Once the algorithm has then processed the data, it writes the data elements from the addresses supplied by the appropriate pointers. After process is concluded, it will wait for the next frame of samples. This process then repeats until halted.
The callback function, in the Simulink environment, needs to set the appropriate pointers each time an EDMA interrupt occur, and post a semaphore for the “Algorithm” task afterwards.
The driver consists of 4 files:
The first three files are the same files used for the CCS driver. The ai24link.c file is similar to the user defined file (test.c for example) described in ,
This file contains the following functions and definitions:
Int16 r_data1[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for read
Int16 w_data1[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for read
Int16 r_data2[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for write
Int16 w_data2[DATANUM*NUM_CODECS*BUFS_PER_CHANNEL]; // data buffer for write
Those pointers are used by the Algorithm block in the Simulink model.
Int16 *InSig1;
Int16 *InSig2;
Int16 *InSig3;
Int16 *InSig4;
Int16 *OutSig1;
Int16 *OutSig2;
Int16 *OutSig3;
Int16 *OutSig4;
As explained the semaphore will be used to wait for the data to arrive.
AIC24_Handle hAIC24;
SEM_Obj AIC24_ready_sem;
It calls the AIC24_Init function and also initializes the semaphore.
void AIC24LINK_init()
{
// setting up the AIC24 handle
AIC24_InitDefaults(⩓hAIC24, NUM_CODECS, DATANUM, r_data1, r_data2, w_data1, w_data2, AIC24LINK_process);
SEM_new(⩓AIC24_ready_sem,0);
}
The callback function AIC24LINK_process simply sets the channel buffer pointers (the pointers the model uses) to the correct places and posts on the semaphore.
void AIC24LINK_process(Int16 *r_data, Int16 *w_data)
{
InSig1 = r_data;
InSig2 = r_data + DATANUM;
InSig3 = r_data + DATANUM*2;
InSig4 = r_data + DATANUM*3;
OutSig1 = w_data;
OutSig2 = w_data + DATANUM;
OutSig3 = w_data + DATANUM*2;
OutSig4 = w_data + DATANUM*3;
SEM_post(⩓AIC24_ready_sem);
}
void AIC24LINK_wait()
{
SEM_pend(⩓AIC24_ready_sem, SYS_FOREVER);
}
This function pends on the semaphore.
This function calls AIC24_start.
The Simulink block will be created from the AIC23 driver used in the “Custom Device Driver/Legacy Code Integration” Simulink demo.
![]() |
![]() |
![]() |
The next steps will guide you through the “Algorithm” block configuration
![]() |
The model is now ready to generate real-time code for processing of 4 audio channels. AN example is shown in the next chapter.
This chapter illustrates the use of the Simulink driver introduced in the previous chapter, to implement a conference bridge.
Figure 15 shows the configuration used for this application. Three headsets8 are used, enabling a 3-port conference. The DSP monitors the 3 input signals, and routes the “loudest” input signal to the three outputs. The algorithm is described in the next section.
![]() |
![]() |
The algorithm processes 64 samples (each channel). It calculates the absolute power of each channel (sum of absolute values). Three counters are used, each one corresponding to a distinct channel.
For each frame the counter correspondent to the loudest channel is incremented. When a channel counter equals a threshold9, the correspondent channel is selected.
For Each 64 samples frame the following algorithm is implemented:
This section will describe the process of generating a real-time application using Simulink blocks for the algorithm itself and the driver introduced in the previous chapter. The Simulink model is shown in Figure 2110.
![]() |
The block diagram shown in Figure 20 is translated to the model shown in the following figures.
![]() |
![]() |
![]() |
![]() |
The real-time model can be found in the file C67_Simulink_conference.rar.
MATLAB and Simulink are registered trademarks of The MathWorks, Inc. See www.mathworks.com/trademarks for a list of additional trademarks. Other product or brand names may be trademarks or registered trademarks of their respective holders.
The following table is based on the one the Codec’s EVM Datasheet.
It matches the board connectors with the inputs and outputs.
(Notice, this table includes corrections and elaborations in compare to the one in the Datasheet).
![]() |
The Inputs and Outputs of a certain channel are set by its 6a and 6b registers.
Those registers can be set, like before, between the initialization of the Handle and its activation.
If, for example, the input samples of channel 0 in the CODEC should be received from INP311. The configuration should be as follows:
int main()
{
// setting up the AIC24 handle
AIC24_InitDefaults(⩓hAIC24, 4, 64, r_data1, r_data2, w_data1, w_data2, process);
hAIC24.Regs[0].creg6.reg6a.control_bit.mici = 0;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp1 = 0;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp2 = 0;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp3 = 1;
hAIC24.Regs[0].creg6.reg6a.control_bit.inp4 = 0;
// Starting the AIC24
AIC24_Start(⩓hAIC24);
}
Each entry corresponds to a bit that determines whether it should be connected or not to the channel input12. The names of the fields correlate to the table.
A similar procedure applies for the output channel:
hAIC24.Regs[0].creg6.reg6b.control_bit.outp1 = 0;
hAIC24.Regs[0].creg6.reg6b.control_bit.outp2 = 1;
hAIC24.Regs[0].creg6.reg6b.control_bit.outp3 = 0;
In the case above, output of channel 0 is connected to OUTP2 (i.e. J1).
A single channel can be connected to a number of outputs. It is not recommended to connect two CODEC channels to a single output.
The default channel configuration is:
Even Channels (i.e. 0,2,…) Input: MICI
Even Channels (i.e. 0,2,…) Output: OUTP1
Odd Channels (i.e. 1,3,…) Input: INP1
Odd Channels (i.e. 1,3,…) Output: OUTP2
| Jumper | State |
| W1: | Closed |
| W2: | 2-3 |
| W3: | 1-2 |
| W4: | 1-2 |
| W5: | Closed |
| P1.13-P1.14: | Closed |
| P1.9-P1.10: | Closed |
| P1.11-P1.12: | Closed |
| Jumper | State |
| W1: | 1-2 |
| W2: | 2-3 |
| Register | Value | Remarks |
| opt | EDMA_OPT_PRI_LOW | Low Priority (Allows other DMAs first) |
| EDMA_OPT_ESIZE_16BIT | 16 bits elements | |
| EDMA_OPT_2DS_NO | Source not using 2D option | |
| EDMA_OPT_SUM_NONE | Source Address is fixed (The MCBSP rcv register) | |
| EDMA_OPT_2DD_NO | Destination not using 2D option | |
| EDMA_OPT_DUM_IDX | Destination address is in double index (sort) mode | |
| EDMA_OPT_TCINT_YES | Cause interrupt | |
| EDMA_OPT_TCC_OF(0) | Transfer Complete Code (given to the interrupt, set later) | |
| EDMA_OPT_LINK_YES | Use linking to another EDMA record | |
| EDMA_OPT_FS_NO | Don't use frame sync | |
| src | MCBSP_getRcvAddr(hAIC->hMcbsp) | The address of the McBSP recv register |
| cnt | EDMA_CNT_FRMCNT_OF(ChannelBufferSize – 1) | The number of frames is the size of the buffer |
| EDMA_CNT_ELECNT_OF(TotalNumChannels) | Each frame has NumChannel elements | |
| dst | EDMA_DST_OF(ReadAddr) | The address of the buffer to write to |
| idx | EDMA_IDX_FRMIDX_OF(-((ChannelBufferSize * (TotalNumChannels-1)) * 2)+2 | Negative Frame Index to move us back to the next position in the first channel after each frame |
| EDMA_IDX_ELEIDX_OF((ChannelBufferSize * 2) | Positive Element Index to move us to the next position in channel after every elemnt | |
| rld | EDMA_RLD_ELERLD_OF(TotalNumChannels) | Should be the same as Element Count |
| EDMA_RLD_LINK_OF(LinkedRecord) | The number of the next record to load (since we use double buffering the next record will point back to this one) |
| Register | Value | Remarks |
| opt | EDMA_OPT_PRI_LOW | Low Priority (Allows other DMAs first) |
| EDMA_OPT_ESIZE_16BIT | 16 bits elements | |
| EDMA_OPT_2DS_NO | Source not using 2D option | |
| EDMA_OPT_SUM_IDX | Source address is in double index (sort) | |
| EDMA_OPT_2DD_NO | Destination not using 2D option | |
| EDMA_OPT_DUM_NONE | Destination Address is fixed (The MCBSP xmt register) | |
| EDMA_OPT_TCINT_YES | Cause interrupt | |
| EDMA_OPT_TCC_OF(0) | Transfer Complete Code (given to the interrupt, set later) | |
| EDMA_OPT_LINK_YES | Use linking to another EDMA record | |
| EDMA_OPT_FS_NO | Don't use frame sync | |
| src | EDMA_SRC_OF(WriteAddr) | The address of the buffer read from |
| cnt | EDMA_CNT_FRMCNT_OF(ChannelBufferSize – 1) | Each frame has NumChannel elements |
| EDMA_CNT_ELECNT_OF(TotalNumChannels) | Each frame has NumChannel elements | |
| dst | MCBSP_getXmtAddr(hAIC->hMcbsp) | The address of the McBSP transmit register |
| idx | EDMA_IDX_FRMIDX_OF(-((ChannelBufferSize * (TotalNumChannels-1)) * 2)+2) | Negative Frame Index to move us back to the next position in the first channel after each frame |
| EDMA_IDX_ELEIDX_OF((ChannelBufferSize * 2)) | Positive Element Index to move us to the next position in channel after every elemnt | |
| rld | EDMA_RLD_ELERLD_OF(TotalNumChannels) | Should be the same as Element Count |
| EDMA_RLD_LINK_OF(LinkedRecord) | The number of the next record to load (since we use double buffering the next record will point back to this one) |
| Register | Value | Remarks |
| Serial Port Control Register (SPCR) | MCBSP_SPCR_FREE_YES | Used for emulation |
| MCBSP_SPCR_SOFT_YES | Used for emulation | |
| MCBSP_SPCR_FRST_YES | Frame sync generator is in reset (generated by master CODEC) | |
| MCBSP_SPCR_GRST_YES | Sample rate generator is in reset (generated by master CODEC) | |
| MCBSP_SPCR_XINTM_FRM | The transmit interrupt is driven by new frame sync | |
| MCBSP_SPCR_XSYNCERR_NO | No synchronization error detected | |
| MCBSP_SPCR_XRST_YES | The serial port transmitter is disabled and in reset state | |
| MCBSP_SPCR_DLB_OFF | Digital Loopback mode off | |
| MCBSP_SPCR_RJUST_RZF | Receive data right-justified and zero-fill MSBSs in DRR | |
| MCBSP_SPCR_CLKSTP_DISABLE | The Clock stop mode is disabled | |
| MCBSP_SPCR_DXENA_OFF | Disable extra delay of DX | |
| MCBSP_SPCR_RINTM_FRM | Receive interrupt on new frame synch | |
| MCBSP_SPCR_RSYNCERR_NO | No synchronization error detected | |
| MCBSP_SPCR_RRST_YES | The serial port receiver is disabled and in reset state | |
| Receive Control Register (RCR) | MCBSP_RCR_RPHASE_SINGLE | receive one phase only |
| MCBSP_RCR_RFRLEN2_OF(0) | Don't Care. Phase 2 is not used | |
| MCBSP_RCR_RWDLEN2_16BIT | Don't Care. Phase 2 is not used | |
| MCBSP_RCR_RCOMPAND_MSB | No companding, data transfer starts with MSB first | |
| MCBSP_RCR_RFIG_NO | sync error doesn't cause transfer restart | |
| MCBSP_RCR_RDATDLY_1BIT | 1 bit data delay | |
| MCBSP_RCR_RFRLEN1_OF(NumChannels-1) | Each frame contains an element for each channel | |
| MCBSP_RCR_RWDLEN1_16BIT | Each sample is 16 bit | |
| MCBSP_RCR_RWDREVRS_DISABLE | Don't Care. Only 16 bit elements are used | |
| Transmit Control Register (XCR) | MCBSP_XCR_XPHASE_SINGLE | Transmit one phase only |
| MCBSP_XCR_XFRLEN2_OF(0) | Don't Care. Phase 2 is not used | |
| MCBSP_XCR_XWDLEN2_16BIT | Don't Care. Phase 2 is not used | |
| MCBSP_XCR_XCOMPAND_MSB | No companding, data transfer starts with MSB first | |
| MCBSP_XCR_XFIG_NO | sync error doesn't cause transfer restart | |
| MCBSP_XCR_XDATDLY_1BIT | 1 bit data delay | |
| MCBSP_XCR_XFRLEN1_OF(NumChannels-1) | Each frame contains an element for each channel | |
| MCBSP_XCR_XWDLEN1_16BIT | Each sample is 16bit | |
| MCBSP_XCR_XWDREVRS_DISABLE | Does not matter, we use 16 bit elements | |
| Sample Rate Generator Register (SRGR) | 0 | Not used, clock and frame sync are generated by the master CODEC (The source code contains the flags but they are 0) |
| Pin Control Register (PCR) | MCBSP_PCR_XIOEN_SP | All pins are dedicated for MCBSP (Not GPIO) |
| MCBSP_PCR_RIOEN_SP | All pins are dedicated for MCBSP (Not GPIO) | |
| MCBSP_PCR_FSXM_EXTERNAL | Transmit frame sync is generated by the master CODEC | |
| MCBSP_PCR_FSRM_EXTERNAL | Receive frame sync is generated by the master CODEC | |
| MCBSP_PCR_CLKXM_INPUT | Transmit clock sync is generated by the master CODEC | |
| MCBSP_PCR_CLKRM_INPUT | Receive clock sync is generated by the master CODEC | |
| MCBSP_PCR_CLKSSTAT_0 | Does not matter (used in GPIO) | |
| MCBSP_PCR_DXSTAT_0 | Does not matter (used in GPIO) | |
| MCBSP_PCR_FSXP_ACTIVEHIGH | Transmit frame sync is active high for AIC24 | |
| MCBSP_PCR_FSRP_ACTIVEHIGH | Receive frame sync is active high for AIC24 | |
| MCBSP_PCR_CLKXP_RISING | Transmit data driven on rising edge | |
| MCBSP_PCR_CLKRP_FALLING | Receive data sampled on falling edge | |
| MCR | 0 | Unused |
| RCER | 0 | Unused |
| XCER | 0 | Unused |