Skip to content

Commit

Permalink
try this build
Browse files Browse the repository at this point in the history
  • Loading branch information
CamDavidsonPilon committed Feb 1, 2025
1 parent 81e6e6b commit 6cf711e
Showing 1 changed file with 86 additions and 65 deletions.
151 changes: 86 additions & 65 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,160 +4,181 @@
#include "hardware/adc.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"
#include <stdlib.h> // for rand() and srand()

/* The code implements the following i2c API
1. Write to any of the first 4 addresses (0, 1, 2, 3) to set the 8-bit duty cycle of the corresponding PWM
2. Read from any of the next 4 addresses (4, 5, 6, 7) to get the 16 bit ADC value of the corresponding ADC
2. Read from address 8 to return the version of this code.
Here are some examples of using i2cset and i2cget to interact with this program:
To set the duty cycle of the PWM channel corresponding to address 0 to 75%, you could use the following i2cset command:
> i2cset -y 1 0x30 0 0xC0
To read the 16-bit ADC value of the ADC channel corresponding to address 4, you could use the following i2cget command:
> i2cget -y 1 0x30 w 4
Note that the -y flag is used to automatically answer yes to any prompt from i2cset or i2cget.
The 1 after the -y flag specifies the i2c bus number to use, and the 0x30 specifies the i2c address of the peripheral.
The 0 and w in the i2cset and i2cget commands, respectively, specify the starting register address to be accessed.
The 4 in the i2cget command specifies the address of the ADC channel to read.
*/

// define I2C addresses to be used for this peripheral

// I2C peripheral address for this device
#define I2C1_PERIPHERAL_ADDR 0x2C

// I2C GPIO pins
#define GPIO_SDA0 14
#define GPIO_SCK0 15

// GPIO pins to use for PWM -> LED channels
// PWM GPIO pins (LED channels A, B, C, D)
#define LED_CHANNEL_A_PIN 16
#define LED_CHANNEL_B_PIN 17
#define LED_CHANNEL_C_PIN 18
#define LED_CHANNEL_D_PIN 19

// Code version numbers
#define VERSION_MAJOR 0
#define VERSION_MINOR 3

// first 4 addresses are for the PWM channels for LEDS (A, B, C, D)
// second 4 addresses are for the ADCs (0, 1, 2, 3)
uint8_t pointer = 0;
// store the GPIO pins of the PWMs
// Global pointer for I2C register selection (addresses 0–8)
volatile uint8_t pointer = 0;

// PWM channel configuration:
// addresses 0–3 are used to set the 8-bit duty cycle for each PWM output.
uint8_t pwm_channels[4] = {LED_CHANNEL_A_PIN, LED_CHANNEL_B_PIN, LED_CHANNEL_C_PIN, LED_CHANNEL_D_PIN};
// store the duty cycles of the PWMs
uint8_t pwm_duty_cycles[4];
uint8_t pwm_duty_cycles[4] = {0};

const int randomArray[16] = {2, -1, 1, 0, -2, 2, -1, 0, 1, -2, 0, 2, -1, 1, 0, -2};
// Cached ADC values for channels 0–3 (corresponding to I2C addresses 4–7)
// These values are updated continuously in the background.
volatile uint16_t adc_cache[4] = {0};

// Number of ADC samples used for oversampling.
#define NUM_ADC_SAMPLES 256

void set_up_pwm_pin(uint pin) {
// -----------------------------------------------------------------------------
// Function: set_up_pwm_pin
// Configures a given GPIO for PWM output.
// -----------------------------------------------------------------------------
static inline void set_up_pwm_pin(uint pin) {
gpio_set_function(pin, GPIO_FUNC_PWM);
uint slice_num = pwm_gpio_to_slice_num(pin);
pwm_config config = pwm_get_default_config();
float div = (float)clock_get_hz(clk_sys) / (325000 * 256);
// Configure the PWM clock divider and wrap value.
float div = (float) clock_get_hz(clk_sys) / (325000 * 256);
pwm_config_set_clkdiv(&config, div);
pwm_config_set_wrap(&config, 256);
pwm_init(slice_num, &config, true);
pwm_set_gpio_level(pin, 0);
};
}

// -----------------------------------------------------------------------------
// Function: read_adc_oversampled
// Reads from the ADC channel NUM_ADC_SAMPLES times with a small random delay
// between samples (to decorrelate noise), sums the results, and then shifts
// right by 4 bits. This is an oversampling technique that gains 4 extra bits
// of effective resolution (i.e. 12-bit to 16-bit).
// -----------------------------------------------------------------------------
uint16_t read_adc_oversampled(uint8_t adc_channel) {
adc_select_input(adc_channel);
uint32_t sum = 0;
for (int i = 0; i < NUM_ADC_SAMPLES; i++) {
sum += adc_read();
// Introduce a random delay between 5 and 9 µs.
sleep_us(5 + (rand() % 5));
}
// Shifting right by 4 bits (dividing by 16) leverages the oversampling process.
return (uint16_t)(sum >> 4);
}

// -----------------------------------------------------------------------------
// I2C Interrupt Handler
//
// Implements the following I2C API:
// - Write to addresses 0–3 to set the PWM duty cycle for the corresponding channel.
// - Read from addresses 4–7 to return the cached ADC oversampled value for ADC inputs 0–3.
// - Read from address 8 to return the version of this code.
// -----------------------------------------------------------------------------
void i2c1_irq_handler() {
// Get interrupt status
uint32_t status = i2c1->hw->intr_stat;

// Check for NACK signals from the master or TX abort
// Clear any TX abort condition.
if (status & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) {
// Clear the TX abort interrupt
i2c1->hw->clr_tx_abrt;
}

// Check to see if we have received data from the I2C controller
// Process incoming data (master write)
if (status & I2C_IC_INTR_STAT_R_RX_FULL_BITS) {
uint32_t value = i2c1->hw->data_cmd;
if (value & I2C_IC_DATA_CMD_FIRST_DATA_BYTE_BITS) {
// The first byte received specifies the register pointer.
pointer = (uint8_t)(value & I2C_IC_DATA_CMD_DAT_BITS);
if (pointer > 8) {
pointer = 0xFF; // invalid address
pointer = 0xFF; // Mark as an invalid address.
}
} else {
// A subsequent byte: if pointer is 0–3, update the PWM duty cycle.
if (pointer <= 3) {
pwm_duty_cycles[pointer] = (uint8_t)(value & I2C_IC_DATA_CMD_DAT_BITS);
pwm_set_gpio_level(pwm_channels[pointer], (uint8_t) pwm_duty_cycles[pointer]);
pwm_set_gpio_level(pwm_channels[pointer], pwm_duty_cycles[pointer]);
}
}
}

// Check to see if the I2C controller is requesting data
// Process master read requests.
if (status & I2C_IC_INTR_STAT_R_RD_REQ_BITS) {
if (pointer >= 4 && pointer <= 7) {
uint8_t adc_input = pointer - 4;
adc_select_input(adc_input);

// since the adc_read will return maximum 2**12, and I can
// send up to 2**16 data over two bytes in i2c, I can theoretically
// read up to 2**4 = 16 times here.
uint32_t running_sum = 0;
for (int j = 0; j < 16; ++j) {
for (int i = 0; i < 16; ++i) {
running_sum += adc_read();
sleep_us(5 + randomArray[j]);
}
}
uint16_t average = (uint16_t)(running_sum / 16);
i2c1->hw->data_cmd = (average & 0xFF);
i2c1->hw->data_cmd = (average >> 8);
// For addresses 4–7, return the cached ADC value.
uint8_t adc_index = pointer - 4;
uint16_t cached_value = adc_cache[adc_index];
i2c1->hw->data_cmd = (cached_value & 0xFF); // Send lower byte.
i2c1->hw->data_cmd = ((cached_value >> 8) & 0xFF); // Send upper byte.
} else if (pointer == 8) {
// Return version information.
i2c1->hw->data_cmd = VERSION_MINOR;
i2c1->hw->data_cmd = VERSION_MAJOR;
} else {
// For an invalid pointer, return 0xFF on both bytes.
i2c1->hw->data_cmd = 0xFF;
i2c1->hw->data_cmd = 0xFF;
}

// Clear the read request interrupt
// Clear the read request flag.
i2c1->hw->clr_rd_req;
}

// Clear any other interrupts that might have been set
// Clear any RX done status.
if (status & I2C_IC_INTR_STAT_R_RX_DONE_BITS) {
i2c1->hw->clr_rx_done;
}
}


// -----------------------------------------------------------------------------
// Main function: Initializes peripherals and then enters a background loop to
// update the cached ADC values for channels 0–3.
// -----------------------------------------------------------------------------
int main() {
stdio_init_all();

// Initialize the ADC and its GPIOs (channels 0–3 correspond to GPIO26–29).
adc_init();
adc_gpio_init(26);
adc_gpio_init(27);
adc_gpio_init(28);
adc_gpio_init(29);
adc_select_input(0); // Start with channel 0.

// Select ADC input 0 initially (GPIO26)
adc_select_input(0);
// Seed the random number generator (used for ADC sampling delay jitter).
srand((unsigned) time_us_32());

// Initialize I2C1 in slave mode.
i2c_init(i2c1, 100000);
i2c_set_slave_mode(i2c1, true, I2C1_PERIPHERAL_ADDR);

gpio_set_function(GPIO_SDA0, GPIO_FUNC_I2C);
gpio_set_function(GPIO_SCK0, GPIO_FUNC_I2C);
gpio_pull_up(GPIO_SDA0);
gpio_pull_up(GPIO_SCK0);

// Set up PWM outputs.
set_up_pwm_pin(LED_CHANNEL_A_PIN);
set_up_pwm_pin(LED_CHANNEL_B_PIN);
set_up_pwm_pin(LED_CHANNEL_C_PIN);
set_up_pwm_pin(LED_CHANNEL_D_PIN);

// Enable the I2C interrupts we want to process
i2c1->hw->intr_mask = (I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_INTR_MASK_M_RX_FULL_BITS);
// Set up the interrupt handler to service I2C interrupts
// Enable I2C interrupts for RX (data received) and RD_REQ (master read request).
i2c1->hw->intr_mask = I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_INTR_MASK_M_RX_FULL_BITS;
irq_set_exclusive_handler(I2C1_IRQ, i2c1_irq_handler);
// Enable I2C interrupt
irq_set_enabled(I2C1_IRQ, true);

// Background loop: continuously update the cached ADC values.
while (true) {
tight_loop_contents();
for (uint8_t channel = 0; channel < 4; channel++) {
adc_cache[channel] = read_adc_oversampled(channel);
}
// A short delay to yield; adjust as needed.
sleep_ms(1);
}

return 0;
}
}

0 comments on commit 6cf711e

Please sign in to comment.