ADC Example - PalouseRobosub/SUBLIBinal GitHub Wiki
ADC Example
This page contains information describing the provided ADC example. Code examples can be found within the examples
folder in the repository.
/********************************************************
* File Name: adc_ex.c
*
* Description:
* Main file
*
*
*********************************************************/
/*************************************************************************
System Includes
************************************************************************/
#include "sublibinal.h"
#include "sublibinal_config.h"
//forward declarations
void timer_callback(void);
void adc_callback(ADC_Node node);
/*************************************************************************
Main Function
************************************************************************/
int main(void) {
//buffers
uint8 uart_tx_buffer[128];
uint8 adc_work_queue[10*sizeof(ADC_Data)], adc_results_queue[10*sizeof(ADC_Data)];
//structures for configuring peripherals
UART_Config uart_config = {0};
Timer_Config timer_config = {0};
ADC_Config adc_config = {0};
Packetizer_Config packetizer_config = {0};
//disable interrupts
disable_Interrupts();
//setup peripherals
timer_config.frequency = 10; //Set the timer to operate at 10Hz
timer_config.pbclk = PB_CLK;
timer_config.which_timer = Timer_1; //Use timer 1
timer_config.callback = &timer_callback; //Hand a callback function for the ISR functionality
timer_config.enabled = 1; //Enable the timer
initialize_Timer(timer_config); //Initialize the timer module
uart_config.which_uart = UART_CH_1; //Specify UART channel 1 for us
uart_config.pb_clk = PB_CLK; //Tell the module the speed of our clock
uart_config.speed = 115200; //Specify the buadrate to be 115.2k
uart_config.tx_buffer_ptr = uart_tx_buffer; //Hand a data pointer to the transmission buffer
uart_config.tx_buffer_size = sizeof(uart_tx_buffer); //Tell the UART the size of the transmission buffer
uart_config.tx_en = 1; //Enable transmission
uart_config.tx_pin = Pin_RPB15;
initialize_UART(uart_config); //Initialize the UART module
packetizer_config.callback = NULL; //Specify that there is no callback function for the packetizer
packetizer_config.control_byte = '!'; //Specify that the ! symbol is our control byte for the packet
packetizer_config.uart_config = uart_config; //Hand the packetizer our UART configuration that the packetizer will use
packetizer_config.which_channel = PACKET_UART_CH_1; //Specify that this will use UART Channel 1 of rhte packetizer
initialize_packetizer(packetizer_config); //Initialize the packetizer moudule
adc_config.channels = (1 << ADC_CH_0) | (1 << ADC_CH_1); //Specify that we are using ADC channels 0 and 1
adc_config.work_buffer_ptr = adc_work_queue; //Hand a pointer for the work data
adc_config.work_buffer_size = sizeof(adc_work_queue); //Tell the ADC the size of the work data
adc_config.result_buffer_ptr = adc_results_queue; //Hand a data pointer for the results data
adc_config.result_buffer_size = sizeof(adc_results_queue); //Tell the ADC the size of the results buffer
initialize_ADC(adc_config); //Initialize the ADC module
//Global interrupt enable. Do this last!
enable_Interrupts();
while (1) {
bg_process_ADC(); //This process must be in the embedded loop for processing of completed data
}
return 0;
}
void timer_callback(void)
{
//Conduct a read of an ADC node
ADC_Node node = {0};
node.device_id = 0x01; //Specify an arbitrary ID for this device. We will use this for identification purposes later
node.channel = ADC_CH_0; //Specify which channel this device is on
node.callback = &adc_callback; //Specify a callback for when the data is completed
read_ADC(node); //Read the ADC node
node.device_id = 0x02; //Specify another arbitrary ID for this other device
node.channel = ADC_CH_1; //Specify that this will be read from channel 1
node.callback = &adc_callback; //Hand a callback function for when the data is completed
read_ADC(node); //Conduct a read of this different ADC node
}
//This function is called during the bg_process_ADC() function
//The purpose of this function is to do something meaningful with the data
void adc_callback(ADC_Node node)
{
uint8 send_data[3];
uint8 data_LB, data_HB;
//We will break apart the data in the node into a series of bytes
data_LB = node.data & 0xFF;
data_HB = node.data >> 8;
//We will populate an array with data for transmission
send_data[0] = node.device_id;
send_data[1] = data_LB;
send_data[2] = data_HB;
//Send a packet of information up the Packetizer
send_packet(PACKET_UART_CH_1, send_data, sizeof(send_data));
}
This example contains a number of crucial portions of code that are extremely important for ADC Configuration. In this example, a UART with packetizer implementation is initialized for transmission of converted ADC data, and a timer firing at a frequency of 1KHz is driving the ADC reads.
The first important section of code is the initialization of the ADC:
adc_config.channels = (1 << ADC_CH_0) | (1 << ADC_CH_1); //Specify that we are using ADC channels 0 and 1
adc_config.work_buffer_ptr = adc_work_queue; //Hand a pointer for the work data
adc_config.work_buffer_size = sizeof(adc_work_queue); //Tell the ADC the size of the work data
adc_config.result_buffer_ptr = adc_results_queue; //Hand a data pointer for the results data
adc_config.result_buffer_size = sizeof(adc_results_queue); //Tell the ADC the size of the results buffer
initialize_ADC(adc_config); //Initialize the ADC module
In this code section, we specify that we are using AN0 and AN1 pins and we provide work and result buffers to the ADC. We then call the ADC initialization to set up the ADC for conversion.
The next important section of code is located towards the end of the main program:
while (1) {
bg_process_ADC(); //This process must be in the embedded loop for processing of completed data
}
This is where the background process should be placed. This process must continually run so that converted data from the AC can be properly handled.
Another crucial portion of ADC implementation occurs within the timer callback function, where we create a node and place it in the work buffer to perform a read of the ADC:
void timer_callback(void)
{
//Conduct a read of an ADC node
ADC_Node node = {0};
node.device_id = 0x01; //Specify an arbitrary ID for this device. We will use this for identification purposes later
node.channel = ADC_CH_0; //Specify which channel this device is on
node.callback = &adc_callback; //Specify a callback for when the data is completed
read_ADC(node); //Read the ADC node
node.device_id = 0x02; //Specify another arbitrary ID for this other device
node.channel = ADC_CH_1; //Specify that this will be read from channel 1
node.callback = &adc_callback; //Hand a callback function for when the data is completed
read_ADC(node); //Conduct a read of this different ADC node
}
In this function, we are specifying that the first read will have an ID=1, will be read from channel 0, and will invoke the adc_callback
function when completed. We then place this node in the work buffer by calling the read_ADC(node);
line. This function also exemplifies that we may freely modify the node after placing it in the buffer without causing the ADC to malfunction. We then create another read with an ID=2, using channel 1, that also utilizes the adc_callback
function. We then enqueue this node for a read as well and return to normal program execution.
The final crucial portion of the ADC example occurs within the adc_callback
function.
void adc_callback(ADC_Node node)
{
uint8 send_data[3];
uint8 data_LB, data_HB;
//We will break apart the data in the node into a series of bytes
data_LB = node.data & 0xFF;
data_HB = node.data >> 8;
//We will populate an array with data for transmission
send_data[0] = node.device_id;
send_data[1] = data_LB;
send_data[2] = data_HB;
//Send a packet of information up the Packetizer
send_packet(PACKET_UART_CH_1, send_data, sizeof(send_data));
}
The first important observation is to see that this function returns nothing, but accepts a single ADC_Node
parameter. This parameter is used for providing the results and ID to the callback function. Within this function, we must break apart the results of the ADC into two separate bytes for transmission on the UART. We then construct a packet of information out of the device ID and the results and transmit this data up the UART. This shows that data can then be differentiated based on ID and the use of the ADC_Node
function parameter. If desired, a switch statement can be implemented to call different programs based upon the node ID. The callback function is invoked from the background process, so it is not contained within an interrupt and may be interrupted.