diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 8811012..745ca89 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -26,13 +26,13 @@ jobs: PICO_SDK_PATH: $GITHUB_WORKSPACE/pico-sdk steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true # Check out the Pico SDK - name: Checkout Pico SDK - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: raspberrypi/pico-sdk path: pico-sdk @@ -40,7 +40,7 @@ jobs: # Check out the Pico Extras - name: Checkout Pico Extras - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: raspberrypi/pico-extras path: pico-extras @@ -67,9 +67,9 @@ jobs: cmake --build . --config $BUILD_TYPE -j 2 - name: Upload build result - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: elf_file + name: main.elf path: ${{runner.workspace}}/build/main.elf - name: Upload elf to GH release page diff --git a/README.md b/README.md index 76aefc2..b5afc7a 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,13 @@ 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 + i2cset -y 1 0x2C 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 + i2cget -y 1 0x2C 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 1 after the -y flag specifies the i2c bus number to use, and the 0x2C 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. diff --git a/main.c b/main.c index 12779bd..489bc81 100644 --- a/main.c +++ b/main.c @@ -5,32 +5,26 @@ #include "hardware/pwm.h" #include "hardware/clocks.h" - /* 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. +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 -#define I2C1_PERIPHERAL_ADDR 0x30 -// GPIO pins to use for I2C +#define I2C1_PERIPHERAL_ADDR 0x2C + #define GPIO_SDA0 14 #define GPIO_SCK0 15 @@ -40,23 +34,21 @@ The 4 in the i2cget command specifies the address of the ADC channel to read. #define LED_CHANNEL_C_PIN 18 #define LED_CHANNEL_D_PIN 19 -// define firmware version that can be read over i2c #define VERSION_MAJOR 0 -#define VERSION_MINOR 2 +#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 +// store the GPIO pins of the PWMs 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]; +const int randomArray[16] = {2, -1, 1, 0, -2, 2, -1, 0, 1, -2, 0, 2, -1, 1, 0, -2}; -void set_up_pwm_pin(uint pin) { - // starts at duty_cycle = 0, hz = 325k + +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(); @@ -67,72 +59,75 @@ void set_up_pwm_pin(uint pin) { pwm_set_gpio_level(pin, 0); }; -void i2c1_irq_handler() { +void i2c1_irq_handler() { // Get interrupt status uint32_t status = i2c1->hw->intr_stat; + // Check for NACK signals from the master or TX abort + 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 if (status & I2C_IC_INTR_STAT_R_RX_FULL_BITS) { - - // Read the data (this will clear the interrupt) uint32_t value = i2c1->hw->data_cmd; - - // Check if this is the 1st byte we have received if (value & I2C_IC_DATA_CMD_FIRST_DATA_BYTE_BITS) { - - // If so treat it as the address to use pointer = (uint8_t)(value & I2C_IC_DATA_CMD_DAT_BITS); - + if (pointer > 8) { + pointer = 0xFF; // invalid address + } } else { - if (pointer <= 3){ - // If not 1st byte then store the data in the RAM - // and increment the address to point to next byte + 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]); } } } - // Check to see if the I2C controller is requesting data from the RAM + // Check to see if the I2C controller is requesting data if (status & I2C_IC_INTR_STAT_R_RD_REQ_BITS) { - if (pointer >= 4 && pointer <= 7){ + 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. - uint16_t raw = 0; - int i; - for (i = 0; i < 16; ++i){ - raw = raw + adc_read(); + 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]); + } } - i2c1->hw->data_cmd = (raw & 0xFF); // Send the low-order byte - i2c1->hw->data_cmd = (raw >> 8); // Send the high-order byte + uint16_t average = (uint16_t)(running_sum / 16); + i2c1->hw->data_cmd = (average & 0xFF); + i2c1->hw->data_cmd = (average >> 8); } else if (pointer == 8) { i2c1->hw->data_cmd = VERSION_MINOR; i2c1->hw->data_cmd = VERSION_MAJOR; } else { - i2c1->hw->data_cmd = 0; // return 0 - i2c1->hw->data_cmd = 0; + i2c1->hw->data_cmd = 0xFF; + i2c1->hw->data_cmd = 0xFF; } - // Clear the interrupt + // Clear the read request interrupt i2c1->hw->clr_rd_req; } + + // Clear any other interrupts that might have been set + if (status & I2C_IC_INTR_STAT_R_RX_DONE_BITS) { + i2c1->hw->clr_rx_done; + } } -// Main loop - initializes system and then loops while interrupts get on with processing the data int main() { + stdio_init_all(); - // setup ADC - stdio_init_all(); - - // Initialise ADCs adc_init(); - // Make sure GPIO is high-impedance, no pull-ups etc adc_gpio_init(26); adc_gpio_init(27); adc_gpio_init(28); @@ -141,17 +136,14 @@ int main() { // Select ADC input 0 initially (GPIO26) adc_select_input(0); - // Setup I2C1 as peripheral - i2c_init(i2c1, 100000); + i2c_init(i2c1, 100000); i2c_set_slave_mode(i2c1, true, I2C1_PERIPHERAL_ADDR); - // Setup GPIO pins to use and add pull up resistors 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 PWMs set_up_pwm_pin(LED_CHANNEL_A_PIN); set_up_pwm_pin(LED_CHANNEL_B_PIN); set_up_pwm_pin(LED_CHANNEL_C_PIN); @@ -159,14 +151,11 @@ int main() { // 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 irq_set_exclusive_handler(I2C1_IRQ, i2c1_irq_handler); - // Enable I2C interrupt irq_set_enabled(I2C1_IRQ, true); - // Do nothing in main loop while (true) { tight_loop_contents(); }