The bare metal framework is very extensible. In this tutorial, we’re going to explore adding new audio components (e.g. a codec, a MEMS microphone, etc.) to our system. Most audio components have both an I2S interface for audio and either an SPI or I2C interface for control.
This tutorial will cover how to configure a new audio component, with SigmaStudio, and then use this configuration in the bare metal framework to initialize it.
If you are using an audio component from Analog Devices, SigmaStudio can very likely be used to generate a set of configuration parameters with which the bare metal framework can initialize the component.
First, if you don’t already have SigmaStudio installed, get the latest version from http://www.analog.com/sigmastudio.
It's important to download and install SigmaStudio and not SigmaStudio for SHARC.
In this example, we will show how to configure and connect the ADI SSM3582 (a Class D amplifier) to our system over I2C.
Open up SigmaStudo. It will default to a blank project. If not, create a new project with File→New Project. We’re going to save our project within the framework so we can easily access the files that we export. Save your project into the following path:
C:\Analog Devices\SAM_BareMetal_SDK-Rel1.0.0\framework\drivers\bm_adau_driver\configurations\ss_schematics
Let's call our project SSM3582_basic.dspproj
. You will see other projects in that directory, they correspond to preexisting drivers in the framework (more on this later).
Look for the component we’re interested in within the table on the left hand side and drag an instance of that component into our hardware configuration tab. Once we’ve dragged it in, click on the grey text within the box (IC initially), and give the component a more descriptive name. Our exported files will use this stub so it will make it easier to keep track of things.
Click on the configuration tab for this component at the bottom of the Hardware Configuration pane. Click on the Chip/SAI/DAC Control tab. You will see a graphical interface with a number of options. Note that there are several tabs across the top, each with more options.
In our case, the default options are what is needed. So, without changing these settings, carry on reading through Step 2 to export the configuration files.
In the Action menu, select Link Compile Download.
It will appear that nothing has happened but SigmaStudio has indeed compiled the configuration options for this project.
After this, select Export System Files (immediately below Link Compile Download).
This will bring up a file dialog to create a folder for your project; name it SSM3582-basic. Within this folder, create another one called Exported Init Files. Press Save to export the files into this folder.
If you open this folder, you will see a number of files generated by SigmaStudio. We are concerned with two in particular, that contain the blocks of I2C initialization data for the bm_adau_driver
driver to initialize the new part.
Now that we have generated the initialization files, let's connect these to the bm_adau_driver
driver.
A set of intermediate files provide a C struct
to access information held in the two .dat
files generated at Step 2. This enables us to pass a pointer to this struct
rather than managing links to the configuration files when we initialize our new driver.
Import the bare metal framework projects in CCES (instructions here) and navigate to ${Core_0_Project}\src\drivers\bm_adau_driver\configurations
. You will find a number of .c
files in this directory, each corresponding to a particular device. Notice that there is already a file for the ssm3582_configuration.c
. Open it.
Copy the “2ch_i2s_slave” configuration variables (shown below) and paste them again into the same file.
uint8_t ssm3582_2ch_i2s_slave_TxBuffer[] = { #include "drivers/bm_adau_driver/configurations/ss_schematics/Class_D_Fin/Exported_init_files/TxBuffer_SSM3582_1.dat" }; uint16_t ssm3582_2ch_i2s_slave_NumBytes[] = { #include "drivers/bm_adau_driver/configurations/ss_schematics/Class_D_Fin/Exported_init_files/NumBytes_SSM3582_1.dat" }; BM_ADAU_DEVICE_INIT_DATA ssm3582_2ch_i2s_slave = {.data_tx_buffer = ssm3582_2ch_i2s_slave_TxBuffer, .data_num_bytes = ssm3582_2ch_i2s_slave_NumBytes, .total_lines = sizeof(ssm3582_2ch_i2s_slave_NumBytes) / sizeof(uint16_t), .ignore_first_byte_of_init_file = true};
Give new unique names to the copied configuration variables, and change the file paths to point to the .dat
files generated at Step 2.
The code snippet below shows an example of the copied configuration code, with new variable names:
// My basic SSM3582 configuration uint8_t SSM3582_basic_slave_TxBuffer[] = { #include "drivers/bm_adau_driver/configurations/ss_schematics/SSM3582-basic/Exported_init_files/TxBuffer_SSM3582.dat" }; uint16_t SSM3582_basic_NumBytes[] = { #include "drivers/bm_adau_driver/configurations/ss_schematics/SSM3582-basic/Exported_init_files/NumBytes_SSM3582.dat" }; BM_ADAU_DEVICE_INIT_DATA SSM3582_basic_slave = {.data_tx_buffer = SSM3582_basic_slave_TxBuffer, .data_num_bytes = SSM3582_basic_NumBytes, .total_lines = sizeof(SSM3582_basic_NumBytes) / sizeof(uint16_t), .ignore_first_byte_of_init_file = true};
We can now reference the C struct called SSM3582_basic_slave
when we need to reference this new configuration.
Next, let's create an external reference to this struct. Open bm_adau_device.h
, located one directory up from the file we edited at Step 3. The code snippet shows the syntax to declare the reference; copy the statement below the external reference to aSSM3582_2ch_i2s_slave
into your 'bm_adau_device.h' source:
// SSM3582 (2 channel class D chip) extern BM_ADAU_DEVICE_init_Data SSM3582_2ch_i2s_slave; extern BM_ADAU_DEVICE_init_Data SSM3582_basic_slave; // Our new SSM3582 configuration
From this point, the init files data, generated at step 2, can be read from the C structure in any source file within the framework. All we need to do to initialize the new device is to create an instance of the bm_adau_device
driver and initialize it like so:
// Create an instance of an bm_adau_device driver structure and initialize component #define SSM35822_I2C_ADDR (0x10) BM_ADAU_DEVICE ssm3582_basic; BM_ADAU_RESULT res; if (res = adau_initialize( &ssm3582_basic, // Address of our ADAU driver instance TWI0, // Which TWI/I2C port we're connected to SSM35822_I2C_ADDR, // The I2C address of the SSM5822 &SSM3582_basic_slave, // Address of our init file struct we created above SSM3582_ADDR_BYTES) // byte configuration of init files != ADAU_SIMPLE_SUCCESS) { // Handle error here }
adau_initialize()
will then initialize the SSM3582 over I2C using the init files exported from SigmaStudio.
In the bare metal framework, the audio component initialization is done within the audio framework file. If you’re using the standard bare metal framework, the init function can be found in the ARM project, within ${Core_0_Project}\src\audio_frameworks\audio_framework_8ch_sam_and_audioproj_fin_arm.c
. And if you’re using the Linux version of the framework where Linux is running on the ARM, then SHARC core 1 is responsible for audio component initialization. In this case, the init functionality can be found in core 1 (SHARC) within this file: src/audio_frameworks/audio_framework_8ch_sam_and_audioproj_fin_core1.c
.
A number of other bm_adau_driver
struct declarations can be found at the top of these files. The adau_initialize
code can be found within the audioframework_initialize()
function.
The bm_adau_driver
also includes functions to read/write to control registers of the device. For example, the followng code snippet shows how to change the Class D amp gain to 0dB:
adau_write_ctrl_reg( &ssm3582_basic, 0x7, 0x40 ); // Set left channel to 0dB adau_write_ctrl_reg( &ssm3582_basic, 0x8, 0x40 ); // Set right channel to 0dB
And we can use the same approach to read control registers:
uint16_t leftGain = adau_read_ctrl_reg( &ssm3582_basic, 0x7 ); // Read left channel gain
If the audio component has a SigmaDSP core, this same driver can also be used to read and write parameter memory for the SigmaDSP. Use the adau_read_parameter_ram()
and adau_write_parameter_ram()
functions to access parameter RAM.