diff --git a/doc/PIC18F26K83 Peripheral Pin Select Implementation Guide.pdf b/doc/PIC18F26K83 Peripheral Pin Select Implementation Guide.pdf new file mode 100644 index 0000000..45c02cd Binary files /dev/null and b/doc/PIC18F26K83 Peripheral Pin Select Implementation Guide.pdf differ diff --git a/doc/gpio_driver.rst b/doc/gpio_driver.rst index 50d84d7..4ab2307 100644 --- a/doc/gpio_driver.rst +++ b/doc/gpio_driver.rst @@ -1,46 +1,304 @@ -GPIO Driver (Not Implemented) -***************************** +GPIO Driver +*********** -Macros -====== +The GPIO driver provides basic digital input/output functionality for the PIC18F26K83. This driver focuses on high-level GPIO operations, while the underlying pin configuration (direction, digital/analog mode, PPS mapping) is handled by the dedicated pin configuration module. -Output enable -------------- -.. c:macro:: gpio_enable_output(port, pin) +**Key Features:** - Initialize a GPIO pin to be used as digital output. +* Simple digital I/O macros for performance-critical operations +* Support for all available GPIO pins (PORTA, PORTB, PORTC) +* Macro-based implementation for zero runtime overhead +* Separation of concerns: pin mode configuration handled separately - :param port: MCU port, a letter e.g. A, B - :param pin: Pin number +**Current Status:** Interface defined, implementation pending -Input enable ------------- -.. c:macro:: gpio_enable_input(port, pin) +Relationship with Pin Configuration +==================================== + +The GPIO driver operates at a higher level than the pin configuration module: + +* **Pin Configuration Module**: Handles pin direction (TRIS), digital/analog mode (ANSEL), and PPS routing +* **GPIO Driver**: Provides convenient macros for reading/writing pin values after configuration + +**Setup Sequence:** + +1. Configure pin using pin configuration module functions +2. Use GPIO macros for fast I/O operations - Initialize a GPIO pin to be used as digital input. +.. code-block:: c - :param port: MCU port, a letter e.g. A, B - :param pin: Pin number + // Step 1: Configure pin for digital output (done once) + pin_config_t led_pin = {.port = 1, .pin = 5}; // RB5 + configure_pin_digital(led_pin, 0); // 0 = output + + // Step 2: Use GPIO macros for fast operations (done repeatedly) + gpio_set_output(B, 5, 1); // Turn on LED + gpio_set_output(B, 5, 0); // Turn off LED + +GPIO Macros +=========== + +Output Control +-------------- +.. c:macro:: gpio_enable_output(port, pin) + + Initialize a GPIO pin for digital output operation. + + :param port: MCU port letter (A, B, or C) + :param pin: Pin number (0-7) + + **Example:** + + .. code-block:: c + + gpio_enable_output(B, 5); // Configure RB5 as output -Set output value ----------------- .. c:macro:: gpio_set_output(port, pin, value) - Set a GPIO digital output pin to output a value. + Set a GPIO digital output pin to the specified value. + + :param port: MCU port letter (A, B, or C) + :param pin: Pin number (0-7) + :param value: Output value (0 = low, 1 = high) + + **Example:** + + .. code-block:: c + + gpio_set_output(C, 2, 1); // Set RC2 high + gpio_set_output(C, 2, 0); // Set RC2 low - :param port: MCU port, a letter e.g. A, B - :param pin: Pin number - :param value: Value to be set +Input Control +------------- +.. c:macro:: gpio_enable_input(port, pin) + + Initialize a GPIO pin for digital input operation. + + :param port: MCU port letter (A, B, or C) + :param pin: Pin number (0-7) + + **Example:** + + .. code-block:: c + + gpio_enable_input(A, 3); // Configure RA3 as input -Read input value ----------------- .. c:macro:: gpio_get_input(port, pin) - Read digital input from a pin. + Read the current state of a digital input pin. + + :param port: MCU port letter (A, B, or C) + :param pin: Pin number (0-7) + :returns: Pin state (0 = low, 1 = high) + + **Example:** + + .. code-block:: c + + if (gpio_get_input(A, 0)) { + // RA0 is high + } + +Usage Examples +============== + +Basic GPIO Operations +--------------------- + +.. code-block:: c + + #include "gpio.h" + + int main(void) { + // Initialize MCU + mcu_init(); + + // Configure LED output + gpio_enable_output(B, 5); // RB5 as LED output + + // Configure button input + gpio_enable_input(A, 0); // RA0 as button input + + while(1) { + // Read button state and control LED + if (gpio_get_input(A, 0)) { + gpio_set_output(B, 5, 1); // Button pressed, LED on + } else { + gpio_set_output(B, 5, 0); // Button not pressed, LED off + } + } + } + +Integration with Pin Configuration Module +------------------------------------------ + +For more complex scenarios requiring PPS routing or special pin configurations: + +.. code-block:: c + + #include "pin_config.h" + #include "gpio.h" + + int main(void) { + mcu_init(); + + // Complex example: Configure pins for various functions + + // 1. Regular GPIO (no PPS needed) + gpio_enable_output(C, 0); // Simple LED + gpio_enable_input(A, 1); // Simple button + + // 2. Pins that need PPS configuration first + // Configure PWM output pin, then use as GPIO if needed + pwm_pin_config_t pwm_config = { + .output = {.port = 1, .pin = 6} // RB6 + }; + pps_configure_pwm(1, pwm_config); + // Now RB6 is configured for PWM, but could be used as GPIO too + + // 3. External interrupt pins + ext_int_pin_config_t int_config = { + .input = {.port = 1, .pin = 0} // RB0 + }; + pps_configure_external_interrupt(0, int_config); + // RB0 is now configured for INT0, hardware handles the input + + while(1) { + // Simple GPIO operations + gpio_set_output(C, 0, 1); + delay_ms(500); + gpio_set_output(C, 0, 0); + delay_ms(500); + } + } + +Blinking LED Example +--------------------- + +.. code-block:: c + + #include "gpio.h" + #include "millis.h" + + #define LED_PORT C + #define LED_PIN 2 + #define BLINK_INTERVAL_MS 1000 + + int main(void) { + mcu_init(); + timer0_init(); // For millis() function + + // Configure LED pin + gpio_enable_output(LED_PORT, LED_PIN); + + uint32_t last_toggle = 0; + uint8_t led_state = 0; + + while(1) { + uint32_t current_time = millis(); + + if (current_time - last_toggle >= BLINK_INTERVAL_MS) { + led_state = !led_state; + gpio_set_output(LED_PORT, LED_PIN, led_state); + last_toggle = current_time; + } + } + } + +Design Rationale +================ + +Why Macros? +----------- + +The GPIO driver uses macros instead of functions for performance reasons: + +**Performance Benefits:** +* **Zero Runtime Overhead**: Macros expand to direct register operations +* **Compile-time Optimization**: Port and pin selections resolved at compile time +* **Optimal Code Generation**: Single instruction operations where possible + +**Example Expansion:** + +.. code-block:: c + + // This macro call: + gpio_set_output(B, 3, 1); + + // Expands to something like: + PORTBbits.RB3 = 1; + + // Which generates a single assembly instruction + +**Alternative Approaches:** + +Function-based GPIO would require runtime overhead: + +.. code-block:: c + + // Hypothetical function call: + gpio_set_pin(GPIO_PORTB, 3, 1); + + // Would require: + // - Function call overhead + // - Parameter passing + // - Runtime port/pin selection logic + // - Multiple assembly instructions + +Macro Parameter Format +----------------------- + +The macro parameters use port letters (A, B, C) rather than numbers for clarity: + +* **More Readable**: ``gpio_set_output(B, 5, 1)`` vs ``gpio_set_output(1, 5, 1)`` +* **Hardware Correlation**: Matches datasheet naming (RB5, RC2, etc.) +* **Compile-time Validation**: Invalid port letters cause immediate compile errors + +Architecture Integration +========================= + +Layer Responsibilities +----------------------- + +.. code-block:: text + + Application Layer + | + +-----v-----+ + | GPIO | <- High-level I/O operations + | Driver | + +-----------+ + | + +-----v-----+ + | Pin | <- Pin configuration and PPS routing + | Config | + +-----------+ + | + +-----v-----+ + | Hardware | <- Register-level operations + | Registers | + +-----------+ + +**Clear Separation:** +* **GPIO Driver**: Fast I/O operations for application logic +* **Pin Config**: One-time setup of pin modes and routing +* **Hardware Layer**: Direct register manipulation + +Implementation Status +====================== + +**Current Status:** Interface defined, implementation pending + +**Planned Implementation:** +The macros will be implemented to generate optimal assembly code for each supported port and pin combination. - :param port: MCU port, a letter e.g. A, B - :param pin: Pin number +**Future Enhancements:** +* Port-wide operations (read/write entire ports) +* Atomic bit manipulation functions +* Pin change interrupt integration +* Pull-up/pull-down configuration support -Why Macros -========== -We want to pass which pin we are operating on without performance penalty. If we use a macro, it can just take for example ``A,3`` as a parameter and use it to generate C register write to the register. +**Integration Notes:** +* Designed to work seamlessly with the pin configuration module +* Compatible with all PPS-routed peripherals +* Suitable for both simple GPIO and complex mixed-mode applications diff --git a/doc/i2c_driver.rst b/doc/i2c_driver.rst index e15bcf5..813a6ef 100644 --- a/doc/i2c_driver.rst +++ b/doc/i2c_driver.rst @@ -1,86 +1,267 @@ -I2C Master Driver (Not Working) -******************************* +I2C Master Driver +****************** -The driver only handles when the device is used as an I2C master, because it's unlikely the MCU is going to used as an I2C slave on the rocket. Functions for reading 8 and 16 bit registers are provided. +The I2C driver provides master-only communication functionality for the PIC18F26K83. The driver focuses purely on I2C protocol implementation, while pin configuration is handled separately by the dedicated pin configuration module. -Assumptions in this revision -============================ -- I2C Address(exclude R/W bits) are 7 bits -- Only one I2C controller could be used -- Register address are 8 bit wide. +**Key Features:** + +* Master-only operation (slave mode not supported) +* Support for I2C1 and I2C2 modules +* 8-bit and 16-bit register access functions +* 7-bit addressing support +* Separation of concerns: pin configuration handled separately + +**Current Status:** Under development + +Prerequisites +============= + +Before using the I2C driver, you must configure the I2C pins using the pin configuration module: + +.. code-block:: c + + // Configure I2C1 pins first + i2c_pin_config_t i2c_config = { + .scl = {.port = 2, .pin = 3}, // RC3 for SCL + .sda = {.port = 2, .pin = 4} // RC4 for SDA + }; + w_status_t status = pps_configure_i2c(1, i2c_config); + if (status != W_SUCCESS) { + // Handle configuration error + return -1; + } + + // Then initialize I2C module + i2c_init(freq_divider); + +For details on I2C pin configuration, see the :doc:`pin_config` documentation. + +Design Assumptions +================== + +The current I2C driver implementation is based on these assumptions: + +* **Master Mode Only**: The MCU operates as I2C master since slave operation is unlikely in rocket applications +* **7-bit Addressing**: I2C device addresses are 7 bits (excluding R/W bit) +* **Single Controller**: Only one I2C controller is used at a time (though hardware supports I2C1 and I2C2) +* **8-bit Register Addresses**: Device register addresses are limited to 8 bits I2C Controller Functions ======================== +Initialization +-------------- .. c:function:: void i2c_init(uint8_t freq_div) - Initialize I2C module and set up pins + Initialize I2C module for master mode operation. + + :param freq_div: Frequency divider for I2C clock. I2C Frequency = 100 kHz / freq_div + :type freq_div: uint8_t + + .. note:: + Pin configuration must be completed using :c:func:`pps_configure_i2c` before calling this function. - :param uint8_t freq_div: frequency divider, I2C Frequency = 100 kHz / freq_div +Data Transfer Functions +----------------------- .. c:function:: bool i2c_write_data(uint8_t i2c_addr, const uint8_t *data, uint8_t len) - Send data through I2C, this is usually used for sending register address and write data. + Send data through I2C. Typically used for sending register addresses and write data. - :param uint8_t i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the RW bit - :param const uint8_t* data: data to be transmitted - :param uint8_t len: length of data to be transmitted in bytes, the I2C address byte is not included - :return: success or not - :retval true: success - :retval false: failed + :param i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the R/W bit + :type i2c_addr: uint8_t + :param data: Data to be transmitted + :type data: const uint8_t* + :param len: Length of data to be transmitted in bytes (I2C address byte not included) + :type len: uint8_t + :returns: Transfer status + :retval true: Success + :retval false: Failed + :rtype: bool .. c:function:: bool i2c_read_data(uint8_t i2c_addr, uint8_t reg_addr, uint8_t *data, uint8_t len) - Receive data through I2C, this is usually used for receiving read data. + Receive data through I2C. Typically used for reading register data. + + :param i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the R/W bit + :type i2c_addr: uint8_t + :param reg_addr: Device register address (8 bits max) + :type reg_addr: uint8_t + :param data: Buffer for received data + :type data: uint8_t* + :param len: Length of data to be received in bytes + :type len: uint8_t + :returns: Transfer status + :retval true: Success + :retval false: Failed + :rtype: bool + +Register Access Functions +------------------------- - :param uint8_t i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the RW bit - :param uint8_t reg_addr: device register map address, max 8 bits wide - :param uint8_t* data: buffer for received data - :param uint8_t len: length of data to be received in bytes - :return: success or not - :retval true: success - :retval false: failed +8-bit Register Functions +~~~~~~~~~~~~~~~~~~~~~~~~ .. c:function:: bool i2c_read_reg8(uint8_t i2c_addr, uint8_t reg_addr, uint8_t* value) - Read a byte-wide device register + Read a byte-wide device register. - :param uint8_t i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the RW bit - :param uint8_t reg_addr: device register map address, max 8 bits wide - :param uint8_t* value: pointer to register value buffer - :return: success or not - :retval true: success - :retval false: failed + :param i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the R/W bit + :type i2c_addr: uint8_t + :param reg_addr: Device register address (8 bits max) + :type reg_addr: uint8_t + :param value: Pointer to register value buffer + :type value: uint8_t* + :returns: Operation status + :retval true: Success + :retval false: Failed + :rtype: bool -.. c:function:: bool i2c_read_reg16(uint8_t i2c_addr, uint8_t reg_addr, uint16_t* value) +.. c:function:: bool i2c_write_reg8(uint8_t i2c_addr, uint8_t reg_addr, uint8_t value) - Read a 2-byte device register + Write a byte-wide device register. - :param uint8_t i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the RW bit - :param uint8_t reg_addr: device register map address, max 8 bits wide - :param uint16_t* value: pointer to register value buffer - :return: success or not - :retval true: success - :retval false: failed + :param i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the R/W bit + :type i2c_addr: uint8_t + :param reg_addr: Device register address (8 bits max) + :type reg_addr: uint8_t + :param value: Value to be written to the register + :type value: uint8_t + :returns: Operation status + :retval true: Success + :retval false: Failed + :rtype: bool -.. c:function:: bool i2c_write_reg8(uint8_t i2c_addr, uint8_t reg_addr, uint8_t value) +16-bit Register Functions +~~~~~~~~~~~~~~~~~~~~~~~~~ - Write a byte-wide device register +.. c:function:: bool i2c_read_reg16(uint8_t i2c_addr, uint8_t reg_addr, uint16_t* value) - :param uint8_t i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the RW bit - :param uint8_t reg_addr: device register map address, max 8 bits wide - :param uint8_t value: value to be written to the register - :return: success or not - :retval true: success - :retval false: failed + Read a 2-byte device register. + + :param i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the R/W bit + :type i2c_addr: uint8_t + :param reg_addr: Device register address (8 bits max) + :type reg_addr: uint8_t + :param value: Pointer to register value buffer + :type value: uint16_t* + :returns: Operation status + :retval true: Success + :retval false: Failed + :rtype: bool .. c:function:: bool i2c_write_reg16(uint8_t i2c_addr, uint8_t reg_addr, uint16_t value) - Write a 2-byte device register + Write a 2-byte device register. + + :param i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the R/W bit + :type i2c_addr: uint8_t + :param reg_addr: Device register address (8 bits max) + :type reg_addr: uint8_t + :param value: Value to be written to the register + :type value: uint16_t + :returns: Operation status + :retval true: Success + :retval false: Failed + :rtype: bool + +Usage Example +============= + +Complete I2C Setup and Usage +---------------------------- + +.. code-block:: c + + #include "pin_config.h" + #include "i2c.h" + + int main(void) { + // Initialize MCU + mcu_init(); + + // Configure I2C1 pins + i2c_pin_config_t i2c_config = { + .scl = {.port = 2, .pin = 3}, // RC3 + .sda = {.port = 2, .pin = 4} // RC4 + }; + + w_status_t status = pps_configure_i2c(1, i2c_config); + if (status != W_SUCCESS) { + // Handle pin configuration error + return -1; + } + + // Initialize I2C at 400kHz (100kHz / 0.25) + i2c_init(1); // freq_div = 1 for ~100kHz, adjust as needed + + // Example: Read from a sensor + uint8_t sensor_addr = 0x48; // 7-bit address (will be shifted for R/W) + uint8_t reg_addr = 0x00; // Temperature register + uint16_t temperature; + + if (i2c_read_reg16(sensor_addr, reg_addr, &temperature)) { + // Successfully read temperature + // Process temperature data + } else { + // Handle I2C communication error + } + + while(1) { + // Main application loop + } + } + +Device Communication Pattern +----------------------------- + +.. code-block:: c + + // Example: Configure a device register + uint8_t device_addr = 0x1D; // Accelerometer address + uint8_t config_reg = 0x20; // Control register + uint8_t config_val = 0x47; // Enable XYZ axes, 50Hz + + if (!i2c_write_reg8(device_addr, config_reg, config_val)) { + // Handle write error + } + + // Read acceleration data + uint8_t accel_data[6]; + if (i2c_read_data(device_addr, 0x28, accel_data, 6)) { + // Process XYZ acceleration data + int16_t accel_x = (accel_data[1] << 8) | accel_data[0]; + int16_t accel_y = (accel_data[3] << 8) | accel_data[2]; + int16_t accel_z = (accel_data[5] << 8) | accel_data[4]; + } + +Architecture Notes +================== + +**Separation of Concerns:** +* Pin configuration is handled by the pin configuration module +* I2C driver focuses solely on protocol implementation +* Clean interfaces between modules enable independent testing and development + +**Multi-Module Support:** +While the current implementation assumes single controller usage, the hardware supports both I2C1 and I2C2. Future enhancements could extend the API to support multiple simultaneous I2C controllers. + +**Error Handling:** +Functions return boolean status codes. Consider implementing more detailed error reporting in future revisions for better debugging and fault diagnosis. + +Implementation Status +====================== + +**Current Status:** Under development + +**Limitations:** +* Single I2C controller support (hardware supports I2C1 and I2C2) +* 8-bit register address limitation +* Boolean-only error reporting - :param uint8_t i2c_addr: I2C peripheral address, where addr[7:1] is the 7-bit address, and addr[0] is the RW bit - :param uint8_t reg_addr: device register map address, max 8 bits wide - :param uint16_t value: value to be written to the register - :return: success or not - :retval true: success - :retval false: failed +**Future Enhancements:** +* Multi-controller support +* Extended register address support (16-bit) +* Detailed error codes +* Interrupt-driven operation +* DMA support for bulk transfers diff --git a/doc/index.rst b/doc/index.rst index 7c9eddd..81ef506 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -8,6 +8,7 @@ The standard embedded firmware library is a common library for Waterloo Rocketry :caption: Contents pic18_init.rst + pin_config.rst i2c_driver.rst gpio_driver.rst spi_driver.rst diff --git a/doc/pin_config.rst b/doc/pin_config.rst new file mode 100644 index 0000000..6a7f8b7 --- /dev/null +++ b/doc/pin_config.rst @@ -0,0 +1,637 @@ +Peripheral Pin Select (PPS) Configuration +****************************************** + +Overview +======== + +The Peripheral Pin Select (PPS) module provides centralized pin configuration for all digital +peripherals on the PIC18F26K83. This module handles pin configuration for I2C, SPI, UART, PWM, +Timer, and External Interrupt peripherals, following modular programming principles with clear +separation of concerns. + +**Key Features:** + +* Centralized pin configuration for all digital peripherals +* Support for multiple peripheral instances (I2C1/2, UART1-5, SPI1/2, CCP1-5, Timer0-6) +* Automatic PPS register lock/unlock management with interrupt safety +* Robust error handling with comprehensive cleanup patterns +* BARR-C coding standard compliance (no function-like macros) +* Modular design promoting separation of concerns + +The pin configuration module is responsible for: + +* Pin direction (input/output) configuration +* Digital mode selection +* Open-drain configuration for I2C +* PPS input and output mappings +* Multiple module support (I2C1/2, UART1-5, etc.) +* Interrupt-safe PPS register management + +Other peripheral modules (PWM, I2C, SPI, UART) focus purely on their functionality and rely on this +module for pin configuration. + +.. note:: + This module handles only digital peripherals. + + +Data Structures +=============== + +Pin Configuration Structure +--------------------------- +.. c:type:: pin_config_t + + Structure that holds basic pin configuration details. + + .. c:member:: uint8_t port + + Port number (0=PORTA, 1=PORTB, 2=PORTC). + + .. c:member:: uint8_t pin + + Pin number (0-7). + +I2C Pin Configuration Structure +------------------------------- +.. c:type:: i2c_pin_config_t + + Structure that holds I2C pin configuration details. + + .. c:member:: pin_config_t scl + + SCL (Serial Clock) pin configuration structure. + + .. c:member:: pin_config_t sda + + SDA (Serial Data) pin configuration structure. + +SPI Pin Configuration Structure +------------------------------- +.. c:type:: spi_pin_config_t + + Structure that holds SPI pin configuration details. + + .. c:member:: pin_config_t sck + + SCK (Serial Clock) pin configuration structure. + + .. c:member:: pin_config_t sdi + + SDI (Serial Data Input) pin configuration structure. + + .. c:member:: pin_config_t sdo + + SDO (Serial Data Output) pin configuration structure. + + .. c:member:: pin_config_t ss + + SS (Slave Select) pin configuration structure (optional). + +UART Pin Configuration Structure +-------------------------------- +.. c:type:: uart_pin_config_t + + Structure that holds UART pin configuration details. + + .. c:member:: pin_config_t tx + + TX (Transmit) pin configuration structure. + + .. c:member:: pin_config_t rx + + RX (Receive) pin configuration structure. + +PWM Pin Configuration Structure +------------------------------- +.. c:type:: pwm_pin_config_t + + Structure that holds PWM pin configuration details. + + .. c:member:: pin_config_t output + + PWM output pin configuration structure. + +External Interrupt Pin Configuration Structure +---------------------------------------------- +.. c:type:: ext_int_pin_config_t + + Structure that holds external interrupt pin configuration details. + + .. c:member:: pin_config_t input + + External interrupt input pin configuration structure. + +Functions +========= + +Dynamic I2C Configuration +------------------------- +.. c:function:: w_status_t pps_configure_i2c(uint8_t i2c_module, i2c_pin_config_t pin_config) + + :param i2c_module: I2C module number (1 or 2) + :param pin_config: I2C pin configuration structure + :returns: Status code indicating success or failure + :retval: ``W_SUCCESS`` if configuration was successful + :retval: ``W_INVALID_PARAM`` if module number or pin parameters are invalid + + Configure I2C pins dynamically for the specified module. Sets up both input and output + mappings since I2C is bidirectional, configures pins for digital mode and open-drain + operation as required for I2C communication. + + **Features:** + + * Configures both SCL and SDA pins for open-drain operation + * Sets up bidirectional PPS mappings for I2C communication + * Validates module number (1-2) and pin parameters + * Atomic operation with automatic PPS lock management + +Dynamic SPI Configuration +------------------------- +.. c:function:: w_status_t pps_configure_spi(uint8_t spi_module, spi_pin_config_t pin_config, bool use_ss) + + :param spi_module: SPI module number (1 or 2) + :param pin_config: SPI pin configuration structure + :param use_ss: Whether to configure slave select pin + :returns: Status code indicating success or failure + :retval: ``W_SUCCESS`` if configuration was successful + :retval: ``W_INVALID_PARAM`` if module number or pin parameters are invalid + + Configure SPI pins dynamically for master mode operation. Sets up appropriate pin + directions (SCK and SDO as outputs, SDI as input) and PPS mappings. Optionally + configures slave select pin. + + **Features:** + + * Configures pins for SPI master mode operation + * Optional slave select (SS) pin configuration + * Validates module number (1-2) and pin parameters + * Proper input/output pin direction setup + +Dynamic UART Configuration +-------------------------- +.. c:function:: w_status_t pps_configure_uart(uint8_t uart_module, uart_pin_config_t pin_config) + + :param uart_module: UART module number (1-5) + :param pin_config: UART pin configuration structure + :returns: Status code indicating success or failure + :retval: ``W_SUCCESS`` if configuration was successful + :retval: ``W_INVALID_PARAM`` if module number or pin parameters are invalid + + Configure UART pins dynamically for the specified module. Sets up TX as output and RX + as input with appropriate PPS mappings. + + **Features:** + + * Supports UART modules 1-5 + * Configures TX pin as output, RX pin as input + * Sets up proper PPS input and output mappings + * Validates module number and pin parameters + +Dynamic PWM Configuration +------------------------- +.. c:function:: w_status_t pps_configure_pwm(uint8_t ccp_module, pwm_pin_config_t pin_config) + + :param ccp_module: CCP module number (1-5) + :param pin_config: PWM pin configuration structure + :returns: Status code indicating success or failure + :retval: ``W_SUCCESS`` if configuration was successful + :retval: ``W_INVALID_PARAM`` if module number or pin parameters are invalid + + Configure PWM/CCP pins dynamically for the specified module. Sets up the output pin + direction and PPS mapping. This must be called before initializing the PWM module. + + **Features:** + + * Supports CCP modules 1-5 for PWM output + * Configures output pin direction and PPS mapping + * Must be called before PWM module initialization + * Validates module number and pin parameters + +Dynamic External Interrupt Configuration +---------------------------------------- +.. c:function:: w_status_t pps_configure_external_interrupt(uint8_t int_number, ext_int_pin_config_t pin_config) + + :param int_number: External interrupt number (0-2 for INT0, INT1, INT2) + :param pin_config: External interrupt pin configuration structure + :returns: Status code indicating success or failure + :retval: ``W_SUCCESS`` if configuration was successful + :retval: ``W_INVALID_PARAM`` if interrupt number or pin parameters are invalid + + Configure external interrupt pins dynamically for the specified interrupt. Sets up the + pin as a digital input and maps it to the interrupt input via PPS. + + **Features:** + + * Supports external interrupts INT0, INT1, and INT2 + * Configures pin as digital input + * Maps pin to interrupt input via PPS + * Validates interrupt number and pin parameters + +Dynamic Timer Clock Configuration +--------------------------------- +.. c:function:: w_status_t pps_configure_timer_clk(uint8_t timer, pin_config_t pin_config) + + :param timer: Timer number to configure (0-6) + :param pin_config: Pin configuration for the external clock input + :returns: Status code indicating success or failure + :retval: ``W_SUCCESS`` if configuration was successful + :retval: ``W_INVALID_PARAM`` if timer number or pin parameters are invalid + + Configure external clock input for the specified timer. Sets up the pin as a digital + input and maps it to the timer's external clock input via PPS. + + **Features:** + + * Supports Timer0 through Timer6 + * Configures pin as digital input for external clock + * Maps pin to timer clock input via PPS + * Validates timer number and pin parameters + +Dynamic Timer Gate Configuration +-------------------------------- +.. c:function:: w_status_t pps_configure_timer_gate(uint8_t timer, pin_config_t pin_config) + + :param timer: Timer number to configure (0-6) + :param pin_config: Pin configuration for the timer gate input + :returns: Status code indicating success or failure + :retval: ``W_SUCCESS`` if configuration was successful + :retval: ``W_INVALID_PARAM`` if timer number or pin parameters are invalid + + Configure timer gate input for the specified timer. Sets up the pin as a digital + input and maps it to the timer's gate input via PPS. + + **Features:** + + * Supports Timer0 through Timer6 + * Configures pin as digital input for gate control + * Maps pin to timer gate input via PPS + * Validates timer number and pin parameters + +Security and Safety Features +============================ + +PPS Lock Management +------------------- + +The module implements robust PPS register lock management to ensure system security: + +* **Automatic Lock/Unlock**: All configuration functions automatically handle PPS register unlocking and locking +* **Interrupt Safety**: PPS operations preserve the current interrupt state during critical sequences +* **Error Recovery**: If any configuration step fails, PPS registers are always locked before returning +* **Cleanup Pattern**: All functions use a consistent cleanup pattern with ``goto`` statements to ensure proper resource management + +Lock/Unlock Sequence +-------------------- + +The PPS lock/unlock mechanism follows these steps: + +1. **Save Interrupt State**: Current GIE (Global Interrupt Enable) state is preserved +2. **Disable Interrupts**: Interrupts are temporarily disabled during the critical sequence +3. **Unlock Sequence**: Write 0x55, then 0xAA to PPSLOCK register, then clear PPSLOCKED bit +4. **Configuration**: Perform the required pin and PPS configuration +5. **Lock Sequence**: Write 0x55, then 0xAA to PPSLOCK register, then set PPSLOCKED bit +6. **Restore Interrupts**: Original GIE state is restored + +**Security Benefits:** + +* Prevents accidental PPS register modification +* Ensures atomic configuration operations +* Maintains interrupt context integrity +* Provides predictable behavior in all execution contexts + +Usage Examples +============== + +Basic Usage with Multiple Modules +---------------------------------- + +Example showing configuration of multiple peripheral modules: + +.. code-block:: c + + int main(void) { + // Initialize the microcontroller (clocks, etc.) + mcu_init(); + + // Configure I2C1 on RC3/RC4 + i2c_pin_config_t i2c1_config = { + .scl = {.port = 2, .pin = 3}, // RC3 + .sda = {.port = 2, .pin = 4} // RC4 + }; + w_status_t status = pps_configure_i2c(1, i2c1_config); + if (status != W_SUCCESS) { + // Handle error + return -1; + } + + // Configure I2C2 on different pins: RA1/RA2 + i2c_pin_config_t i2c2_config = { + .scl = {.port = 0, .pin = 1}, // RA1 + .sda = {.port = 0, .pin = 2} // RA2 + }; + status = pps_configure_i2c(2, i2c2_config); + if (status != W_SUCCESS) { + // Handle error + return -1; + } + + // Configure PWM on CCP1 using RB5 + pwm_pin_config_t pwm_config = { + .output = {.port = 1, .pin = 5} // RB5 + }; + status = pps_configure_pwm(1, pwm_config); + if (status != W_SUCCESS) { + // Handle error + return -1; + } + + // Configure UART1 on RC6/RC7 + uart_pin_config_t uart_config = { + .tx = {.port = 2, .pin = 6}, // RC6 + .rx = {.port = 2, .pin = 7} // RC7 + }; + status = pps_configure_uart(1, uart_config); + if (status != W_SUCCESS) { + // Handle error + return -1; + } + + // Configure external interrupt INT0 on RB0 + ext_int_pin_config_t int_config = { + .input = {.port = 1, .pin = 0} // RB0 + }; + status = pps_configure_external_interrupt(0, int_config); + if (status != W_SUCCESS) { + // Handle error + return -1; + } + + // Now initialize peripherals (pin configuration is done) + i2c_init(frequency_divider); + pwm_init(1, pwm_period); // No pin config needed, already done + uart_init(baud_rate); + + while(1) { + // Main application loop + } + } + +Advanced SPI Configuration with Slave Select +-------------------------------------------- + +Example showing SPI configuration with optional slave select: + +.. code-block:: c + + // Configure SPI1 with slave select for external device control + spi_pin_config_t spi_config = { + .sck = {.port = 2, .pin = 0}, // RC0 - Serial Clock + .sdi = {.port = 2, .pin = 1}, // RC1 - Serial Data Input + .sdo = {.port = 2, .pin = 2}, // RC2 - Serial Data Output + .ss = {.port = 2, .pin = 5} // RC5 - Slave Select + }; + + // Configure with slave select enabled + w_status_t status = pps_configure_spi(1, spi_config, true); + if (status != W_SUCCESS) { + // Handle configuration error + return -1; + } + + // Initialize SPI peripheral after pin configuration + spi_init(); + +Modular Design Example +---------------------- + +Example showing separation of concerns: + +.. code-block:: c + + // In pin_setup.c - Centralized pin configuration + w_status_t setup_all_pins(void) { + w_status_t status; + + // Configure all I2C pins + i2c_pin_config_t i2c_config = { + .scl = {.port = 2, .pin = 3}, + .sda = {.port = 2, .pin = 4} + }; + status = pps_configure_i2c(1, i2c_config); + if (status != W_SUCCESS) return status; + + // Configure all PWM pins + pwm_pin_config_t pwm1_config = {.output = {.port = 1, .pin = 5}}; + status = pps_configure_pwm(1, pwm1_config); + if (status != W_SUCCESS) return status; + + pwm_pin_config_t pwm2_config = {.output = {.port = 1, .pin = 6}}; + status = pps_configure_pwm(2, pwm2_config); + if (status != W_SUCCESS) return status; + + // Configure SPI with slave select + spi_pin_config_t spi_config = { + .sck = {.port = 2, .pin = 0}, + .sdi = {.port = 2, .pin = 1}, + .sdo = {.port = 2, .pin = 2}, + .ss = {.port = 2, .pin = 5} + }; + status = pps_configure_spi(1, spi_config, true); + if (status != W_SUCCESS) return status; + + // Configure timer external clock + pin_config_t timer_clk = {.port = 1, .pin = 3}; + status = pps_configure_timer_clk(1, timer_clk); + if (status != W_SUCCESS) return status; + + return W_SUCCESS; + } + + // In main.c - Clean peripheral initialization + int main(void) { + mcu_init(); + + // Configure ALL pins first + if (setup_all_pins() != W_SUCCESS) { + // Handle error + return -1; + } + + // Then initialize peripherals (no pin concerns) + i2c_init(400000); // 400kHz I2C + pwm_init(1, 1000); // PWM1 with 1000 period + pwm_init(2, 2000); // PWM2 with 2000 period + spi_init(); // SPI master mode + + while(1) { + // Application logic + } + } + +Timer Configuration Example +--------------------------- + +Example showing timer external clock and gate configuration: + +.. code-block:: c + + // Configure Timer1 external clock on RB3 + pin_config_t timer1_clk = {.port = 1, .pin = 3}; // RB3 + w_status_t status = pps_configure_timer_clk(1, timer1_clk); + if (status != W_SUCCESS) { + // Handle error + return -1; + } + + // Configure Timer1 gate control on RB4 + pin_config_t timer1_gate = {.port = 1, .pin = 4}; // RB4 + status = pps_configure_timer_gate(1, timer1_gate); + if (status != W_SUCCESS) { + // Handle error + return -1; + } + + // Now initialize timer with external clock and gate + timer1_init_external_clock(); + +Implementation Details +====================== + +The PPS configuration module uses helper functions following BARR-C coding standards: + +Module Support +-------------- + +* **I2C**: Supports I2C1 and I2C2 modules with automatic open-drain configuration +* **SPI**: Supports SPI1 and SPI2 modules with optional slave select for master mode +* **UART**: Supports UART1 through UART5 modules with proper TX/RX configuration +* **PWM/CCP**: Supports CCP1 through CCP5 modules for PWM output +* **External Interrupts**: Supports INT0, INT1, and INT2 with proper input configuration +* **Timer**: Supports Timer0 through Timer6 external clock and gate inputs + +Helper Functions +---------------- + +All register access is done through proper functions (no function-like macros per BARR-C): + +**Register Access Functions:** +* ``get_tris_register()`` - Get TRIS register for port direction control +* ``get_ansel_register()`` - Get ANSEL register for analog/digital mode selection +* ``get_odcon_register()`` - Get ODCON register for open-drain configuration +* ``get_pps_output_register()`` - Get PPS output register for port/pin mapping + +**Configuration Functions:** +* ``configure_pin_digital()`` - Configure pin for digital I/O with direction setting +* ``configure_pin_open_drain()`` - Configure pin for open-drain mode (I2C) +* ``configure_pps_output()`` - Configure PPS output mapping for peripherals +* ``validate_pin_config()`` - Validate pin configuration parameters + +**PPS Management Functions:** +* ``pps_unlock()`` - Safely unlock PPS registers with interrupt state preservation +* ``pps_lock()`` - Safely lock PPS registers with interrupt state restoration + +PPS Codes +--------- + +The module defines peripheral codes for PPS configuration: + +**I2C Output Codes:** +* ``PPS_I2C1_SCL_OUTPUT``: 0b100001 - I2C1 Serial Clock output +* ``PPS_I2C1_SDA_OUTPUT``: 0b100010 - I2C1 Serial Data output +* ``PPS_I2C2_SCL_OUTPUT``: 0b100011 - I2C2 Serial Clock output +* ``PPS_I2C2_SDA_OUTPUT``: 0b100100 - I2C2 Serial Data output + +**SPI Output Codes:** +* ``PPS_SPI1_SCK_OUTPUT``: 0b011110 - SPI1 Serial Clock output +* ``PPS_SPI1_SDO_OUTPUT``: 0b011111 - SPI1 Serial Data Output +* ``PPS_SPI2_SCK_OUTPUT``: 0b100000 - SPI2 Serial Clock output +* ``PPS_SPI2_SDO_OUTPUT``: 0b100001 - SPI2 Serial Data Output + +**UART Output Codes:** +* ``PPS_UART1_TX_OUTPUT``: 0b010011 - UART1 Transmit output +* ``PPS_UART2_TX_OUTPUT``: 0b010100 - UART2 Transmit output +* ``PPS_UART3_TX_OUTPUT``: 0b010101 - UART3 Transmit output +* ``PPS_UART4_TX_OUTPUT``: 0b010110 - UART4 Transmit output +* ``PPS_UART5_TX_OUTPUT``: 0b010111 - UART5 Transmit output + +**PWM/CCP Output Codes:** +* ``PPS_CCP1_OUTPUT``: 0b001100 - CCP1/PWM1 output +* ``PPS_CCP2_OUTPUT``: 0b001101 - CCP2/PWM2 output +* ``PPS_CCP3_OUTPUT``: 0b001110 - CCP3/PWM3 output +* ``PPS_CCP4_OUTPUT``: 0b001111 - CCP4/PWM4 output +* ``PPS_CCP5_OUTPUT``: 0b010000 - CCP5/PWM5 output + +Error Handling and Recovery +--------------------------- + +The implementation provides comprehensive error handling: + +**Parameter Validation:** +* Module number range checking (e.g., I2C: 1-2, UART: 1-5, CCP: 1-5) +* Port number validation (0-2 for PORTA-PORTC) +* Pin number validation (0-7 for each port) + +**Error Recovery:** +* Cleanup pattern ensures PPS registers are always locked +* All functions use ``goto cleanup`` for error paths after PPS unlock +* Original interrupt state is always restored +* Failed operations leave system in consistent state + +**Error Codes:** +* ``W_SUCCESS`` - Operation completed successfully +* ``W_INVALID_PARAM`` - Invalid module number, port, or pin parameter + +Architecture and Design Principles +================================== + +Separation of Concerns +---------------------- + +The pin configuration module follows strict separation of concerns: + +* **Pin Configuration Layer**: Handles all pin-related setup (direction, mode, PPS mapping) +* **Peripheral Layers**: Focus solely on peripheral functionality and register configuration +* **Application Layer**: Uses both layers independently with clear interfaces + +This design provides several benefits: + +* **Modularity**: Each module has a single, well-defined responsibility +* **Testability**: Pin configuration can be tested independently of peripheral logic +* **Flexibility**: Pin mappings can be changed without modifying peripheral code +* **Maintainability**: Clear boundaries make code easier to understand and modify + + +Thread Safety and Interrupt Handling +------------------------------------ + +The module provides robust interrupt safety: + +* **Interrupt State Preservation**: Current GIE state is saved and restored +* **Atomic Operations**: PPS configuration is atomic with respect to interrupts +* **Critical Section Management**: Minimal time spent with interrupts disabled +* **Predictable Behavior**: Safe to call from interrupt context or initialization code + +Best Practices +============== + +Configuration Order +------------------- + +Follow this recommended order for system initialization: + +1. **MCU Initialization**: Set up clocks, power management +2. **Pin Configuration**: Configure all peripheral pins using this module +3. **Peripheral Initialization**: Initialize peripheral modules +4. **Application Logic**: Start main application functionality + +Notes +===== + +* **Separation of Concerns**: Pin configuration is handled entirely by this module +* **Module Independence**: PWM, I2C, SPI, UART modules focus only on their functionality +* **Multiple Module Support**: Supports multiple instances of peripherals where available +* **Parameter Validation**: All functions validate module numbers and pin parameters +* **Error Handling**: Returns appropriate error codes for invalid configurations +* **Security**: Robust PPS lock management prevents accidental register modification +* **Interrupt Safety**: Preserves interrupt state during critical PPS sequences +* **Resource Management**: Automatic cleanup ensures system consistency \ No newline at end of file diff --git a/doc/pwm_driver.rst b/doc/pwm_driver.rst index d033ff6..33eccf2 100644 --- a/doc/pwm_driver.rst +++ b/doc/pwm_driver.rst @@ -1,119 +1,63 @@ PIC18 PWM Driver **************** -The driver provides PWM output functionality for the PIC18F26K83 using the CCP (Capture/Compare/PWM) modules. It supports up to four PWM channels, each with configurable pin mapping. Timer 2 is utilized by this driver to manage PWM periods. +The driver provides PWM output functionality for the PIC18F26K83 using the CCP (Capture/Compare/PWM) modules. It supports up to five PWM channels (CCP1-CCP5), each with configurable pin mapping through the pin configuration module. Timer 2 is utilized by this driver to manage PWM periods. -Integration -=========== +Features +======== -Pin Configuration ------------------ -.. c:macro:: CONCAT(a, b, c) - - Concatenates three tokens to dynamically form register names. - - :param a: First part of the token. - :param b: Second part of the token. - :param c: Third part of the token. - -.. c:macro:: CCPR_L(module) - - Accesses the CCPR Low register for the specified module. - - :param module: CCP module number (1-4). - -.. c:macro:: CCPR_H(module) - - Accesses the CCPR High register for the specified module. - - :param module: CCP module number (1-4). - -.. c:macro:: CCP_CON(module) - - Accesses the CCPxCON control register for the specified module. - - :param module: CCP module number (1-4). - -.. c:macro:: GET_TRIS_REG(port) - - Retrieves the TRIS register for the specified port. - - :param port: Port letter (A, B, C). - -.. c:macro:: GET_PPS_REG(port, pin) - - Retrieves the PPS register address for the specified port and pin. +* Support for CCP modules 1-5 for PWM output generation +* Configurable PWM period and duty cycle +* Timer 2 integration for period management +* Separation of concerns: pin configuration handled by dedicated pin configuration module +* BARR-C compliant implementation with proper error handling - :param port: Port letter (A, B, C). - :param pin: Pin number (0-7). - -.. c:macro:: SET_TRIS_OUTPUT(port, pin) - - Sets the specified pin as output by modifying the TRIS register. - - :param port: Port letter (A, B, C). - :param pin: Pin number (0-7). - -.. c:macro:: ASSIGN_PPS(port, pin, ccp_module) - - Assigns the CCP module to the specified PPS register to map the peripheral to the desired pin. - - :param port: Port letter (A, B, C). - :param pin: Pin number (0-7). - :param ccp_module: CCP module number (1-4). - -CCP Mode Configuration ----------------------- -.. c:macro:: CONFIGURE_CCP_MODE(ccp_module, ccp_con) - - Configure the CCP module for PWM mode. - - :param ccp_module: CCP module number (1-4) - :param ccp_con: CCPxCON register to configure - -Output Pin Configuration ------------------------- -.. c:macro:: SET_PWM_OUTPUT_PIN(ccp_module, output_pin) - - Set the TRIS register for the output pin. - - :param ccp_module: CCP module number (1-4) - :param output_pin: Output pin number - -Duty Cycle Configuration ------------------------- -.. c:macro:: WRITE_DUTY_CYCLE(ccp_module, duty_cycle) - - Write the 10-bit duty cycle value to the appropriate CCPRxH:CCPRxL register pair. +Prerequisites +============= - :param ccp_module: CCP module number (1-4) - :param duty_cycle: 10-bit duty cycle value (0-1023) +Before using the PWM driver, you must configure the output pins using the pin configuration module: + +.. code-block:: c + + // Configure PWM output pin first + pwm_pin_config_t pwm_config = { + .output = {.port = 1, .pin = 5} // RB5 for PWM output + }; + w_status_t status = pps_configure_pwm(1, pwm_config); + if (status != W_SUCCESS) { + // Handle configuration error + } + + // Then initialize PWM module + status = pwm_init(1, 1000); // CCP1, period = 1000 + if (status != W_SUCCESS) { + // Handle initialization error + } PWM Controller Functions ======================== -PWM Pin Configuration Structure -------------------------------- -.. c:type:: pwm_pin_config_t +PWM Pin Configuration +----------------------- - Structure that holds the configuration details for a PWM pin. +The PWM pin configuration uses the :c:type:`pwm_pin_config_t` structure defined in the pin configuration module. This structure contains a single output pin configuration that specifies where the PWM signal should be routed. - :param port: Port letter (A, B, C). - :param pin: Pin number (0-7). - :param pps_reg: PPS register value for this pin. +For details on the structure definition, see the :doc:`pin_config` documentation. Initialization -------------- -.. c:function:: w_status_t pwm_init(uint8_t ccp_module, pwm_pin_config_t pin_config, uint16_t pwm_period) +.. c:function:: w_status_t pwm_init(uint8_t ccp_module, uint16_t pwm_period) - Initializes PWM for the specified CCP module with the given pin configuration and PWM period. + Initializes PWM for the specified CCP module with the given PWM period. - :param ccp_module: CCP module number (1-4). - :param pin_config: PWM pin configuration structure containing port, pin, and PPS register values. + :param ccp_module: CCP module number (1-5). :param pwm_period: PWM period value. :return: W_SUCCESS on successful initialization, otherwise an error code. This function configures Timer 2, sets the PWM period, and enables the PWM output for the specified CCP module. + + .. note:: + Pin configuration must be done separately using :c:func:`pps_configure_pwm` from the pin configuration module before calling this function. PWM Operation ============= @@ -122,7 +66,7 @@ PWM Operation Updates the duty cycle of the specified CCP module to the new value. - :param ccp_module: CCP module number (1-4). + :param ccp_module: CCP module number (1-5). :param duty_cycle: New duty cycle value (0-1023). :return: W_SUCCESS if successful, W_INVALID_PARAM if parameters are out of range. @@ -130,24 +74,18 @@ PWM Operation Timer Configuration ------------------- -.. c:macro:: CONFIGURE_TIMER2(pwm_period) - - Configures Timer 2 to manage PWM periods. The prescaler and postscaler are set to 1:1. - :param pwm_period: PWM period value to load into the PR2 register. +The PWM driver automatically configures Timer 2 to manage PWM periods. The prescaler and postscaler are set to 1:1 for optimal resolution. The PWM period is set during initialization via the ``pwm_init()`` function. Helper Functions ================ PPS Configuration ----------------- -.. c:function:: static w_status_t configure_pps(uint8_t ccp_module, pwm_pin_config_t pin_config) - Configures Peripheral Pin Select (PPS) for the specified pin and CCP module. This function is essential for routing the PWM signal to the correct output pin. +PPS (Peripheral Pin Select) configuration for PWM is handled by the pin configuration module. Use :c:func:`pps_configure_pwm` to configure the output pin before initializing the PWM module. - :param ccp_module: CCP module number (1-4). - :param pin_config: Structure containing port, pin, and PPS register values. - :return: W_SUCCESS if successful, W_INVALID_PARAM if the module number is out of range. +See the :doc:`pin_config` documentation for details on PWM pin configuration. Error Handling ============== diff --git a/doc/spi_driver.rst b/doc/spi_driver.rst index 62f31a0..a893073 100644 --- a/doc/spi_driver.rst +++ b/doc/spi_driver.rst @@ -1,10 +1,56 @@ -SPI Driver (Not Implemented) -***************************** +SPI Driver +********** -Features -======== -- SPI register read/write function -- Chip select signal toggle by GPIO driver +The SPI driver provides master mode communication functionality for the PIC18F26K83. The driver focuses on SPI protocol implementation, while pin configuration is handled separately by the dedicated pin configuration module. + +**Key Features:** + +* Master mode operation (slave mode not currently supported) +* Support for SPI1 and SPI2 modules +* 8-bit register read/write functions for peripheral devices +* Chip select signal management through GPIO integration +* Separation of concerns: pin configuration handled separately + +**Current Status:** Interface defined, implementation pending + +Prerequisites +============= + +Before using the SPI driver, you must configure the SPI pins using the pin configuration module: + +.. code-block:: c + + // Configure SPI1 pins first + spi_pin_config_t spi_config = { + .sck = {.port = 2, .pin = 0}, // RC0 for Serial Clock + .sdi = {.port = 2, .pin = 1}, // RC1 for Serial Data Input + .sdo = {.port = 2, .pin = 2}, // RC2 for Serial Data Output + .ss = {.port = 2, .pin = 5} // RC5 for Slave Select (optional) + }; + + // Configure with slave select enabled + w_status_t status = pps_configure_spi(1, spi_config, true); + if (status != W_SUCCESS) { + // Handle configuration error + return -1; + } + + // Then initialize SPI module + spi_init(freq_divider); + +For details on SPI pin configuration, see the :doc:`pin_config` documentation. + +Design Features +=============== + +**Master Mode Operation:** +The driver is designed for master mode operation where the MCU controls the clock and initiates all communications. + +**Chip Select Management:** +Chip select (CS) signals are managed through GPIO driver integration, allowing flexible control of multiple SPI devices. + +**Register-based Communication:** +Optimized functions for common peripheral register operations (read/write 8-bit registers). SPI Controller Functions ======================== @@ -13,44 +59,313 @@ Initialization -------------- .. c:function:: void spi_init(uint8_t freq) - Initialize SPI module and set up pins. + Initialize SPI module for master mode operation. + + :param freq: Frequency divider for SPI clock generation + :type freq: uint8_t + + .. note:: + Pin configuration must be completed using :c:func:`pps_configure_spi` before calling this function. + + .. note:: + Exact frequency calculation is TBD and will be documented when implementation is complete. - :param uint8_t freq: frequency divider, exact meaning TBD +Low-Level Transfer Functions +----------------------------- -Transmit --------- .. c:function:: void spi_tx(uint8_t data) - Transmit SPI + Transmit a single byte via SPI. - :param uint8_t data: 8 bit data to write + :param data: 8-bit data to transmit + :type data: uint8_t + + This function initiates an SPI transmission. For full-duplex operation, combine with :c:func:`spi_rx`. -Receive -------- .. c:function:: uint8_t spi_rx(void) - Receive SPI + Receive a single byte from SPI. - :return: Received byte + :returns: Received data byte :rtype: uint8_t + + This function reads the SPI receive buffer. In master mode, a transmission must be initiated first to generate the clock for reception. + +High-Level Register Functions +----------------------------- + +These functions are designed for easy peripheral device register access: -Write 8-bit Register --------------------- .. c:function:: void spi_write8(uint8_t reg_addr, uint8_t value) - Write to a 8-bit register of a SPI peripheral. - Note that toggle CS line have to be done before and after calling this function. + Write to an 8-bit register of an SPI peripheral device. - :param uint8_t reg_addr: 8 bit register address - :param uint8_t value: 8 bit data to be written + :param reg_addr: 8-bit register address + :type reg_addr: uint8_t + :param value: 8-bit data to be written + :type value: uint8_t + + .. important:: + Chip select (CS) line must be toggled manually before and after calling this function. -Read 8-bit Register -------------------- .. c:function:: uint8_t spi_read8(uint8_t reg_addr) - Read from a 8-bit register of a SPI peripheral. - Note that toggle CS line have to be done before and after calling this function. + Read from an 8-bit register of an SPI peripheral device. - :param uint8_t reg_addr: 8 bit register address - :return: data read from the register + :param reg_addr: 8-bit register address to read from + :type reg_addr: uint8_t + :returns: Data read from the register :rtype: uint8_t + + .. important:: + Chip select (CS) line must be toggled manually before and after calling this function. + +Usage Examples +============== + +Basic SPI Setup and Communication +--------------------------------- + +.. code-block:: c + + #include "pin_config.h" + #include "spi.h" + #include "gpio.h" + + int main(void) { + // Initialize MCU + mcu_init(); + + // Configure SPI1 pins + spi_pin_config_t spi_config = { + .sck = {.port = 2, .pin = 0}, // RC0 + .sdi = {.port = 2, .pin = 1}, // RC1 + .sdo = {.port = 2, .pin = 2}, // RC2 + .ss = {.port = 2, .pin = 5} // RC5 + }; + + w_status_t status = pps_configure_spi(1, spi_config, true); + if (status != W_SUCCESS) { + // Handle pin configuration error + return -1; + } + + // Configure CS pin as GPIO output (for manual control) + gpio_enable_output(C, 5); // RC5 as CS + gpio_set_output(C, 5, 1); // CS idle high + + // Initialize SPI module + spi_init(4); // Example frequency divider + + while(1) { + // Example: Read device ID from SPI device + gpio_set_output(C, 5, 0); // Assert CS + uint8_t device_id = spi_read8(0x00); // Read ID register + gpio_set_output(C, 5, 1); // Deassert CS + + // Process device_id... + + delay_ms(1000); + } + } + +Multiple SPI Device Management +------------------------------- + +.. code-block:: c + + #include "pin_config.h" + #include "spi.h" + #include "gpio.h" + + // Define chip select pins for different devices + #define FLASH_CS_PORT C + #define FLASH_CS_PIN 5 + #define SENSOR_CS_PORT C + #define SENSOR_CS_PIN 6 + + // Helper functions for chip select management + void flash_cs_assert(void) { + gpio_set_output(FLASH_CS_PORT, FLASH_CS_PIN, 0); + } + + void flash_cs_deassert(void) { + gpio_set_output(FLASH_CS_PORT, FLASH_CS_PIN, 1); + } + + void sensor_cs_assert(void) { + gpio_set_output(SENSOR_CS_PORT, SENSOR_CS_PIN, 0); + } + + void sensor_cs_deassert(void) { + gpio_set_output(SENSOR_CS_PORT, SENSOR_CS_PIN, 1); + } + + int main(void) { + mcu_init(); + + // Configure SPI pins (shared by both devices) + spi_pin_config_t spi_config = { + .sck = {.port = 2, .pin = 0}, // RC0 + .sdi = {.port = 2, .pin = 1}, // RC1 + .sdo = {.port = 2, .pin = 2}, // RC2 + // Note: .ss not used for manual CS control + }; + pps_configure_spi(1, spi_config, false); // No automatic SS + + // Configure individual CS pins + gpio_enable_output(FLASH_CS_PORT, FLASH_CS_PIN); + gpio_enable_output(SENSOR_CS_PORT, SENSOR_CS_PIN); + flash_cs_deassert(); // CS idle high + sensor_cs_deassert(); // CS idle high + + spi_init(4); + + while(1) { + // Communicate with flash memory + flash_cs_assert(); + spi_write8(0x06, 0x00); // Write enable command + flash_cs_deassert(); + + delay_ms(10); + + // Communicate with sensor + sensor_cs_assert(); + uint8_t temp_data = spi_read8(0x00); // Read temperature + sensor_cs_deassert(); + + // Process data... + delay_ms(1000); + } + } + +Advanced Register Operations +----------------------------- + +.. code-block:: c + + // Example: Configure an accelerometer via SPI + void accelerometer_init(void) { + // Assert CS + gpio_set_output(ACCEL_CS_PORT, ACCEL_CS_PIN, 0); + + // Configure control register 1: 50Hz, XYZ enable + spi_write8(0x20, 0x47); + + // Configure control register 4: +/-2g scale + spi_write8(0x23, 0x00); + + // Deassert CS + gpio_set_output(ACCEL_CS_PORT, ACCEL_CS_PIN, 1); + } + + void read_acceleration(int16_t *x, int16_t *y, int16_t *z) { + uint8_t accel_data[6]; + + gpio_set_output(ACCEL_CS_PORT, ACCEL_CS_PIN, 0); + + // Read 6 bytes starting from X_L register + for (uint8_t i = 0; i < 6; i++) { + accel_data[i] = spi_read8(0x28 + i); + } + + gpio_set_output(ACCEL_CS_PORT, ACCEL_CS_PIN, 1); + + // Combine low and high bytes + *x = (int16_t)((accel_data[1] << 8) | accel_data[0]); + *y = (int16_t)((accel_data[3] << 8) | accel_data[2]); + *z = (int16_t)((accel_data[5] << 8) | accel_data[4]); + } + +Architecture Integration +========================= + +SPI Bus Sharing +--------------- + +The SPI driver is designed to support multiple devices on the same SPI bus: + +.. code-block:: text + + MCU (Master) + | + +----- SCK ----+---- Device 1 (SCK) + | | + +----- SDI ----+---- Device 1 (SDO) + | | + +----- SDO ----+---- Device 1 (SDI) + | | + +----- CS1 ---------- Device 1 (CS) + | | + +----- CS2 ----+----- Device 2 (CS) + | + +----- Device 2 (SCK) + | + +----- Device 2 (SDI) + | + +----- Device 2 (SDO) + +**Key Points:** +* SCK, SDI, SDO are shared among all devices +* Each device has its own CS signal +* Only one device should be active (CS asserted) at a time + +Layer Responsibilities +----------------------- + +.. code-block:: text + + Application Layer + | + +-----v-----+ + | SPI | <- High-level register operations + | Driver | Low-level transfer functions + +-----------+ + | + +-----v-----+ + | GPIO | <- Chip select management + | Driver | + +-----------+ + | + +-----v-----+ + | Pin | <- SPI pin configuration and PPS routing + | Config | + +-----------+ + | + +-----v-----+ + | Hardware | <- SPI peripheral registers + | SPI | + +-----------+ + +**Integration Benefits:** +* **Modular Design**: Each layer has specific responsibilities +* **Reusability**: GPIO and pin config layers used by other peripherals +* **Testability**: Each layer can be tested independently +* **Flexibility**: Easy to add new SPI devices or change pin assignments + +Implementation Status +====================== + +**Current Status:** Interface defined, implementation pending + +**Planned Features:** +* Full-duplex and half-duplex communication modes +* Configurable clock polarity and phase (CPOL/CPHA) +* Multiple frequency options +* Interrupt-driven operation for improved performance + +**Design Considerations:** +* **Clock Configuration**: SPI clock will be derived from system clock with configurable dividers +* **Buffer Management**: Transmit and receive operations will be optimized for common use cases +* **Error Handling**: Status reporting for communication errors and timeouts + +**Integration with Other Modules:** +* **GPIO Driver**: Used for chip select signal management +* **Pin Configuration**: Handles SPI pin routing and electrical configuration +* **Timer Module**: May be used for timeout functionality + +**Future Enhancements:** +* DMA support for high-speed transfers +* Multi-master mode support +* Hardware-controlled chip select for single-device scenarios +* Automatic protocol handling for common device types (EEPROM, ADC, etc.) diff --git a/include/pin_config.h b/include/pin_config.h new file mode 100644 index 0000000..2054e9b --- /dev/null +++ b/include/pin_config.h @@ -0,0 +1,135 @@ +#ifndef ROCKETLIB_PIN_CONFIG_H +#define ROCKETLIB_PIN_CONFIG_H + +#include "common.h" +#include +#include + +// Structure to hold pin configuration details +typedef struct { + uint8_t port; // Port number (0=A, 1=B, 2=C) + uint8_t pin; // Pin number (0-7) +} pin_config_t; + +// Structure for I2C pin configuration +typedef struct { + pin_config_t scl; // SCL pin configuration + pin_config_t sda; // SDA pin configuration +} i2c_pin_config_t; + +// Structure for SPI pin configuration +typedef struct { + pin_config_t sck; // Clock pin configuration + pin_config_t sdi; // Data input pin configuration + pin_config_t sdo; // Data output pin configuration + pin_config_t ss; // Slave select pin configuration (optional) +} spi_pin_config_t; + +// Structure for UART pin configuration +typedef struct { + pin_config_t tx; // Transmit pin configuration + pin_config_t rx; // Receive pin configuration +} uart_pin_config_t; + +// Structure for PWM pin configuration +typedef struct { + pin_config_t output; // PWM output pin configuration +} pwm_pin_config_t; + +// Structure for external interrupt pin configuration +typedef struct { + pin_config_t input; // External interrupt input pin configuration +} ext_int_pin_config_t; + +// PPS input and output codes for different peripherals +// I2C PPS codes +#define PPS_I2C1_SCL_OUTPUT 0b100001 +#define PPS_I2C1_SDA_OUTPUT 0b100010 +#define PPS_I2C2_SCL_OUTPUT 0b100011 +#define PPS_I2C2_SDA_OUTPUT 0b100100 + +// SPI PPS codes +#define PPS_SPI1_SCK_OUTPUT 0b011110 +#define PPS_SPI1_SDO_OUTPUT 0b011111 +#define PPS_SPI2_SCK_OUTPUT 0b100000 +#define PPS_SPI2_SDO_OUTPUT 0b100001 + +// UART PPS codes +#define PPS_UART1_TX_OUTPUT 0b010011 +#define PPS_UART2_TX_OUTPUT 0b010100 +#define PPS_UART3_TX_OUTPUT 0b010101 +#define PPS_UART4_TX_OUTPUT 0b010110 +#define PPS_UART5_TX_OUTPUT 0b010111 + +// PWM/CCP PPS codes +#define PPS_CCP1_OUTPUT 0b001100 +#define PPS_CCP2_OUTPUT 0b001101 +#define PPS_CCP3_OUTPUT 0b001110 +#define PPS_CCP4_OUTPUT 0b001111 +#define PPS_CCP5_OUTPUT 0b010000 + +/** + * @brief Configure I2C pins dynamically + * + * @param i2c_module I2C module number (1-2) + * @param pin_config I2C pin configuration structure + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pps_configure_i2c(uint8_t i2c_module, i2c_pin_config_t pin_config); + +/** + * @brief Configure SPI pins dynamically for master mode + * + * @param spi_module SPI module number (1-2) + * @param pin_config SPI pin configuration structure + * @param use_ss Whether to configure slave select pin + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pps_configure_spi(uint8_t spi_module, spi_pin_config_t pin_config, bool use_ss); + +/** + * @brief Configure UART pins dynamically + * + * @param uart_module UART module number (1-5) + * @param pin_config UART pin configuration structure + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pps_configure_uart(uint8_t uart_module, uart_pin_config_t pin_config); + +/** + * @brief Configure PWM/CCP pins dynamically + * + * @param ccp_module CCP module number (1-5) + * @param pin_config PWM pin configuration structure + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pps_configure_pwm(uint8_t ccp_module, pwm_pin_config_t pin_config); + +/** + * @brief Configure external interrupt pins dynamically + * + * @param int_number External interrupt number (0-2 for INT0, INT1, INT2) + * @param pin_config External interrupt pin configuration structure + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pps_configure_external_interrupt(uint8_t int_number, ext_int_pin_config_t pin_config); + +/** + * @brief Configure timer external clock input + * + * @param timer Timer number (0, 1, 2, 3, 4, 5, 6) + * @param pin_config Pin configuration for the external clock input + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pps_configure_timer_clk(uint8_t timer, pin_config_t pin_config); + +/** + * @brief Configure timer gate input + * + * @param timer Timer number (0, 1, 2, 3, 4, 5, 6) + * @param pin_config Pin configuration for the timer gate input + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pps_configure_timer_gate(uint8_t timer, pin_config_t pin_config); + +#endif /* ROCKETLIB_PIN_CONFIG_H */ \ No newline at end of file diff --git a/include/pwm.h b/include/pwm.h index 85ec508..535cc34 100644 --- a/include/pwm.h +++ b/include/pwm.h @@ -4,54 +4,25 @@ #include "common.h" #include -// Structure to hold the configuration details for a PWM pin -typedef struct { - uint8_t port; // Port letter (A, B, C) - uint8_t pin; // Pin number (0-7) - uint8_t pps_reg; // PPS register value for this pin -} pwm_pin_config_t; - -// Macro to concatenate tokens for register naming -// This macro concatenates three tokens together, used for constructing register names dynamically -#define CONCAT(a, b, c) a##b##c - -// Macro to get the CCPR Low register based on module number -// This macro forms the register name for the low byte of the Compare/Capture/PWM register -#define CCPR_L(module) CONCAT(CCPR, module, L) - -// Macro to get the CCPR High register based on module number -// This macro forms the register name for the high byte of the Compare/Capture/PWM register -#define CCPR_H(module) CONCAT(CCPR, module, H) - -// Macro to get the CCPxCON register based on module number -// This macro forms the register name for the control register of the specified CCP module -#define CCP_CON(module) CONCAT(CCP, module, CON) - -// Macro to get the TRIS register based on port letter -// TRIS registers control the direction of pins (input or output) -// This macro dynamically constructs the TRIS register name for a given port -#define GET_TRIS_REG(port) CONCAT(TRIS, port, A) - -// Macro to get the PPS register address based on port and pin -// PPS (Peripheral Pin Select) allows mapping of peripherals to different pins -// This macro forms the PPS register name for a given port and pin -#define GET_PPS_REG(port, pin) CONCAT(R, port, pin##PPS) - -// Macro to set the TRIS register for a specific pin -// This macro configures the specified pin as an output by clearing the corresponding bit in the -// TRIS register -#define SET_TRIS_OUTPUT(port, pin) (GET_TRIS_REG(port) &= ~(1 << (pin))) - -// Macro to assign the CCP module to the PPS register -// This macro assigns the CCP module to a specific pin by writing to the PPS register -#define ASSIGN_PPS(port, pin, ccp_module) (*GET_PPS_REG(port, pin) = (ccp_module)) - -// Function prototypes -w_status_t pwm_init( - uint8_t ccp_module, pwm_pin_config_t pin_config, uint16_t pwm_period -); // Initializes the PWM for a specific CCP module -w_status_t pwm_update_duty_cycle( - uint8_t ccp_module, uint16_t duty_cycle -); // Updates the duty cycle of the specified CCP module +/** + * @brief Initialize PWM for a specific CCP module + * + * This function only handles PWM initialization. Pin configuration must be done + * separately using the pin_config module before calling this function. + * + * @param ccp_module CCP module number (1-5) + * @param pwm_period PWM period value + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pwm_init(uint8_t ccp_module, uint16_t pwm_period); + +/** + * @brief Update the duty cycle of a specific CCP module + * + * @param ccp_module CCP module number (1-5) + * @param duty_cycle 10-bit duty cycle value (0-1023) + * @return w_status_t W_SUCCESS if successful, W_INVALID_PARAM if parameters are invalid + */ +w_status_t pwm_update_duty_cycle(uint8_t ccp_module, uint16_t duty_cycle); #endif /* ROCKETLIB_PWM_H */ diff --git a/pic18f26k83/pin_config.c b/pic18f26k83/pin_config.c new file mode 100644 index 0000000..650d813 --- /dev/null +++ b/pic18f26k83/pin_config.c @@ -0,0 +1,791 @@ +#include "pin_config.h" +#include "common.h" +#include + +/** + * @brief Unlocks the PPS registers for configuration + */ +static w_status_t pps_unlock(void) { + uint8_t gie_state = INTCON0bits.GIE; // Save current GIE state + INTCON0bits.GIE = 0; // Disable global interrupts during critical sequence + PPSLOCK = 0x55; // First unlock sequence value + PPSLOCK = 0xAA; // Second unlock sequence value + PPSLOCKbits.PPSLOCKED = 0; // Clear PPSLOCKED bit to enable PPS changes + INTCON0bits.GIE = gie_state; // Restore original GIE state + return W_SUCCESS; +} + +/** + * @brief Locks the PPS registers to prevent accidental changes + */ +static w_status_t pps_lock(void) { + uint8_t gie_state = INTCON0bits.GIE; // Save current GIE state + INTCON0bits.GIE = 0; // Disable global interrupts during critical sequence + PPSLOCK = 0x55; // First lock sequence value + PPSLOCK = 0xAA; // Second lock sequence value + PPSLOCKbits.PPSLOCKED = 1; // Set PPSLOCKED bit to lock PPS registers + INTCON0bits.GIE = gie_state; // Restore original GIE state + return W_SUCCESS; +} + +/** + * @brief Get TRIS register address for the specified port + */ +static volatile uint8_t *get_tris_register(uint8_t port) { + switch (port) { + case 0U: + return &TRISA; + case 1U: + return &TRISB; + case 2U: + return &TRISC; + default: + return NULL; + } +} + +/** + * @brief Get ANSEL register address for the specified port + */ +static volatile uint8_t *get_ansel_register(uint8_t port) { + switch (port) { + case 0U: + return &ANSELA; + case 1U: + return &ANSELB; + case 2U: + return &ANSELC; + default: + return NULL; + } +} + +/** + * @brief Get ODCON register address for the specified port + */ +static volatile uint8_t *get_odcon_register(uint8_t port) { + switch (port) { + case 0U: + return &ODCONA; + case 1U: + return &ODCONB; + case 2U: + return &ODCONC; + default: + return NULL; + } +} + +/** + * @brief Get PPS output register address for the specified port and pin + */ +static volatile uint8_t *get_pps_output_register(uint8_t port, uint8_t pin) { + if (port == 0U) { // PORTA + switch (pin) { + case 0U: + return &RA0PPS; + case 1U: + return &RA1PPS; + case 2U: + return &RA2PPS; + case 3U: + return &RA3PPS; + case 4U: + return &RA4PPS; + case 5U: + return &RA5PPS; + case 6U: + return &RA6PPS; + case 7U: + return &RA7PPS; + default: + return NULL; + } + } else if (port == 1U) { // PORTB + switch (pin) { + case 0U: + return &RB0PPS; + case 1U: + return &RB1PPS; + case 2U: + return &RB2PPS; + case 3U: + return &RB3PPS; + case 4U: + return &RB4PPS; + case 5U: + return &RB5PPS; + case 6U: + return &RB6PPS; + case 7U: + return &RB7PPS; + default: + return NULL; + } + } else if (port == 2U) { // PORTC + switch (pin) { + case 0U: + return &RC0PPS; + case 1U: + return &RC1PPS; + case 2U: + return &RC2PPS; + case 3U: + return &RC3PPS; + case 4U: + return &RC4PPS; + case 5U: + return &RC5PPS; + case 6U: + return &RC6PPS; + case 7U: + return &RC7PPS; + default: + return NULL; + } + } + return NULL; +} + +/** + * @brief Validate pin configuration parameters + */ +static w_status_t validate_pin_config(pin_config_t pin_config) { + if (pin_config.port > 2U || pin_config.pin > 7U) { + return W_INVALID_PARAM; + } + return W_SUCCESS; +} + +/** + * @brief Configure a pin for digital I/O + */ +static w_status_t configure_pin_digital(pin_config_t pin_config, uint8_t direction) { + w_status_t status = validate_pin_config(pin_config); + if (status != W_SUCCESS) { + return status; + } + + volatile uint8_t *ansel_reg = get_ansel_register(pin_config.port); + volatile uint8_t *tris_reg = get_tris_register(pin_config.port); + + if (ansel_reg == NULL || tris_reg == NULL) { + return W_INVALID_PARAM; + } + + // Set pin to digital mode + *ansel_reg &= ~(1U << pin_config.pin); + + // Set direction (0=output, 1=input) + if (direction != 0U) { + *tris_reg |= (1U << pin_config.pin); + } else { + *tris_reg &= ~(1U << pin_config.pin); + } + + return W_SUCCESS; +} + +/** + * @brief Set pin to open-drain mode + */ +static w_status_t configure_pin_open_drain(pin_config_t pin_config) { + w_status_t status = validate_pin_config(pin_config); + if (status != W_SUCCESS) { + return status; + } + + volatile uint8_t *odcon_reg = get_odcon_register(pin_config.port); + if (odcon_reg == NULL) { + return W_INVALID_PARAM; + } + + *odcon_reg |= (1U << pin_config.pin); + return W_SUCCESS; +} + +/** + * @brief Get port-pin code for PPS input mapping + */ +static uint8_t get_port_pin_code(pin_config_t pin_config) { + return (uint8_t)((pin_config.port << 3) | pin_config.pin); +} + +/** + * @brief Configure PPS output mapping + */ +static w_status_t configure_pps_output(pin_config_t pin_config, uint8_t peripheral_code) { + w_status_t status = validate_pin_config(pin_config); + if (status != W_SUCCESS) { + return status; + } + + volatile uint8_t *pps_reg = get_pps_output_register(pin_config.port, pin_config.pin); + if (pps_reg == NULL) { + return W_INVALID_PARAM; + } + + *pps_reg = peripheral_code; + return W_SUCCESS; +} + +/** + * @brief Get I2C PPS codes for the specified module + */ +static w_status_t get_i2c_pps_codes(uint8_t i2c_module, uint8_t *scl_code, uint8_t *sda_code) { + switch (i2c_module) { + case 1U: + *scl_code = PPS_I2C1_SCL_OUTPUT; + *sda_code = PPS_I2C1_SDA_OUTPUT; + return W_SUCCESS; + case 2U: + *scl_code = PPS_I2C2_SCL_OUTPUT; + *sda_code = PPS_I2C2_SDA_OUTPUT; + return W_SUCCESS; + default: + return W_INVALID_PARAM; + } +} + +/** + * @brief Configure I2C PPS input registers + */ +static w_status_t configure_i2c_input_pps(uint8_t i2c_module, i2c_pin_config_t pin_config) { + uint8_t scl_port_pin_code = get_port_pin_code(pin_config.scl); + uint8_t sda_port_pin_code = get_port_pin_code(pin_config.sda); + + switch (i2c_module) { + case 1U: + I2C1SCLPPS = scl_port_pin_code; + I2C1SDAPPS = sda_port_pin_code; + return W_SUCCESS; + case 2U: + I2C2SCLPPS = scl_port_pin_code; + I2C2SDAPPS = sda_port_pin_code; + return W_SUCCESS; + default: + return W_INVALID_PARAM; + } +} + +/** + * @brief Get SPI PPS codes for the specified module + */ +static w_status_t get_spi_pps_codes(uint8_t spi_module, uint8_t *sck_code, uint8_t *sdo_code) { + switch (spi_module) { + case 1U: + *sck_code = PPS_SPI1_SCK_OUTPUT; + *sdo_code = PPS_SPI1_SDO_OUTPUT; + return W_SUCCESS; + case 2U: + *sck_code = PPS_SPI2_SCK_OUTPUT; + *sdo_code = PPS_SPI2_SDO_OUTPUT; + return W_SUCCESS; + default: + return W_INVALID_PARAM; + } +} + +/** + * @brief Configure SPI PPS input registers + */ +static w_status_t configure_spi_input_pps(uint8_t spi_module, spi_pin_config_t pin_config) { + uint8_t sck_port_pin_code = get_port_pin_code(pin_config.sck); + uint8_t sdi_port_pin_code = get_port_pin_code(pin_config.sdi); + + switch (spi_module) { + case 1U: + SPI1SCKPPS = sck_port_pin_code; + SPI1SDIPPS = sdi_port_pin_code; + return W_SUCCESS; + case 2U: + SPI2SCKPPS = sck_port_pin_code; + SPI2SDIPPS = sdi_port_pin_code; + return W_SUCCESS; + default: + return W_INVALID_PARAM; + } +} + +/** + * @brief Configure UART PPS input registers + */ +static w_status_t configure_uart_input_pps(uint8_t uart_module, pin_config_t rx_pin) { + uint8_t rx_port_pin_code = get_port_pin_code(rx_pin); + + switch (uart_module) { + case 1U: + U1RXPPS = rx_port_pin_code; + return W_SUCCESS; + case 2U: + U2RXPPS = rx_port_pin_code; + return W_SUCCESS; + case 3U: + U3RXPPS = rx_port_pin_code; + return W_SUCCESS; + case 4U: + U4RXPPS = rx_port_pin_code; + return W_SUCCESS; + case 5U: + U5RXPPS = rx_port_pin_code; + return W_SUCCESS; + default: + return W_INVALID_PARAM; + } +} + +/** + * @brief Get UART TX PPS code for the specified module + */ +static w_status_t get_uart_tx_pps_code(uint8_t uart_module, uint8_t *tx_code) { + switch (uart_module) { + case 1U: + *tx_code = PPS_UART1_TX_OUTPUT; + return W_SUCCESS; + case 2U: + *tx_code = PPS_UART2_TX_OUTPUT; + return W_SUCCESS; + case 3U: + *tx_code = PPS_UART3_TX_OUTPUT; + return W_SUCCESS; + case 4U: + *tx_code = PPS_UART4_TX_OUTPUT; + return W_SUCCESS; + case 5U: + *tx_code = PPS_UART5_TX_OUTPUT; + return W_SUCCESS; + default: + return W_INVALID_PARAM; + } +} + +/** + * @brief Get CCP PPS code for the specified module + */ +static w_status_t get_ccp_pps_code(uint8_t ccp_module, uint8_t *pps_code) { + switch (ccp_module) { + case 1U: + *pps_code = PPS_CCP1_OUTPUT; + return W_SUCCESS; + case 2U: + *pps_code = PPS_CCP2_OUTPUT; + return W_SUCCESS; + case 3U: + *pps_code = PPS_CCP3_OUTPUT; + return W_SUCCESS; + case 4U: + *pps_code = PPS_CCP4_OUTPUT; + return W_SUCCESS; + case 5U: + *pps_code = PPS_CCP5_OUTPUT; + return W_SUCCESS; + default: + return W_INVALID_PARAM; + } +} + +w_status_t pps_configure_i2c(uint8_t i2c_module, i2c_pin_config_t pin_config) { + w_status_t status; + w_status_t final_status = W_SUCCESS; + uint8_t scl_pps_code, sda_pps_code; + + // Validate module number + if (i2c_module < 1U || i2c_module > 2U) { + return W_INVALID_PARAM; + } + + // Get PPS codes for this I2C module + status = get_i2c_pps_codes(i2c_module, &scl_pps_code, &sda_pps_code); + if (status != W_SUCCESS) { + return status; + } + + // Unlock PPS registers + status = pps_unlock(); + if (status != W_SUCCESS) { + return status; + } + + // Configure SCL pin + status = configure_pin_digital(pin_config.scl, 1U); // Input initially + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + status = configure_pin_open_drain(pin_config.scl); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure SDA pin + status = configure_pin_digital(pin_config.sda, 1U); // Input initially + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + status = configure_pin_open_drain(pin_config.sda); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS input mappings + status = configure_i2c_input_pps(i2c_module, pin_config); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS output mappings + status = configure_pps_output(pin_config.scl, scl_pps_code); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + status = configure_pps_output(pin_config.sda, sda_pps_code); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + +cleanup: + // Always lock PPS registers before returning + pps_lock(); + return final_status; +} + +w_status_t pps_configure_spi(uint8_t spi_module, spi_pin_config_t pin_config, bool use_ss) { + w_status_t status; + w_status_t final_status = W_SUCCESS; + uint8_t sck_pps_code, sdo_pps_code; + + // Validate module number + if (spi_module < 1U || spi_module > 2U) { + return W_INVALID_PARAM; + } + + // Get PPS codes for this SPI module + status = get_spi_pps_codes(spi_module, &sck_pps_code, &sdo_pps_code); + if (status != W_SUCCESS) { + return status; + } + + // Unlock PPS registers + status = pps_unlock(); + if (status != W_SUCCESS) { + return status; + } + + // Configure SCK pin (output for master mode) + status = configure_pin_digital(pin_config.sck, 0U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure SDI pin (input for master mode) + status = configure_pin_digital(pin_config.sdi, 1U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure SDO pin (output for master mode) + status = configure_pin_digital(pin_config.sdo, 0U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure SS pin if requested (output for master mode) + if (use_ss) { + status = configure_pin_digital(pin_config.ss, 0U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + } + + // Configure PPS input mappings + status = configure_spi_input_pps(spi_module, pin_config); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS output mappings + status = configure_pps_output(pin_config.sck, sck_pps_code); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + status = configure_pps_output(pin_config.sdo, sdo_pps_code); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + +cleanup: + // Always lock PPS registers before returning + pps_lock(); + return final_status; +} + +w_status_t pps_configure_uart(uint8_t uart_module, uart_pin_config_t pin_config) { + w_status_t status; + w_status_t final_status = W_SUCCESS; + uint8_t tx_pps_code; + + // Validate module number + if (uart_module < 1U || uart_module > 5U) { + return W_INVALID_PARAM; + } + + // Get TX PPS code for this UART module + status = get_uart_tx_pps_code(uart_module, &tx_pps_code); + if (status != W_SUCCESS) { + return status; + } + + // Unlock PPS registers + status = pps_unlock(); + if (status != W_SUCCESS) { + return status; + } + + // Configure TX pin (output) + status = configure_pin_digital(pin_config.tx, 0U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure RX pin (input) + status = configure_pin_digital(pin_config.rx, 1U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS input mapping + status = configure_uart_input_pps(uart_module, pin_config.rx); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS output mapping + status = configure_pps_output(pin_config.tx, tx_pps_code); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + +cleanup: + // Always lock PPS registers before returning + pps_lock(); + return final_status; +} + +w_status_t pps_configure_pwm(uint8_t ccp_module, pwm_pin_config_t pin_config) { + w_status_t status; + w_status_t final_status = W_SUCCESS; + uint8_t pps_code; + + // Validate module number + if (ccp_module < 1U || ccp_module > 5U) { + return W_INVALID_PARAM; + } + + // Get PPS code for this CCP module + status = get_ccp_pps_code(ccp_module, &pps_code); + if (status != W_SUCCESS) { + return status; + } + + // Unlock PPS registers + status = pps_unlock(); + if (status != W_SUCCESS) { + return status; + } + + // Configure output pin (PWM output) + status = configure_pin_digital(pin_config.output, 0U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS output mapping + status = configure_pps_output(pin_config.output, pps_code); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + +cleanup: + // Always lock PPS registers before returning + pps_lock(); + return final_status; +} + +w_status_t pps_configure_external_interrupt(uint8_t int_number, ext_int_pin_config_t pin_config) { + w_status_t status; + w_status_t final_status = W_SUCCESS; + + // Validate interrupt number (INT0, INT1, INT2) + if (int_number > 2U) { + return W_INVALID_PARAM; + } + + // Unlock PPS registers + status = pps_unlock(); + if (status != W_SUCCESS) { + return status; + } + + // Configure pin as digital input + status = configure_pin_digital(pin_config.input, 1U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS input mapping based on interrupt number + uint8_t port_pin_code = get_port_pin_code(pin_config.input); + switch (int_number) { + case 0U: + INT0PPS = port_pin_code; + break; + case 1U: + INT1PPS = port_pin_code; + break; + case 2U: + INT2PPS = port_pin_code; + break; + default: + final_status = W_INVALID_PARAM; + goto cleanup; + } + +cleanup: + // Always lock PPS registers before returning + pps_lock(); + return final_status; +} + +w_status_t pps_configure_timer_clk(uint8_t timer, pin_config_t pin_config) { + w_status_t status; + w_status_t final_status = W_SUCCESS; + + // Validate timer number (expanded to support more timers) + if (timer > 6U) { + return W_INVALID_PARAM; + } + + // Unlock PPS registers + status = pps_unlock(); + if (status != W_SUCCESS) { + return status; + } + + // Configure pin as digital input + status = configure_pin_digital(pin_config, 1U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS input mapping based on timer + uint8_t port_pin_code = get_port_pin_code(pin_config); + switch (timer) { + case 0U: + T0CKIPPS = port_pin_code; + break; + case 1U: + T1CKIPPS = port_pin_code; + break; + case 2U: + T2CKIPPS = port_pin_code; + break; + case 3U: + T3CKIPPS = port_pin_code; + break; + case 4U: + T4CKIPPS = port_pin_code; + break; + case 5U: + T5CKIPPS = port_pin_code; + break; + case 6U: + T6CKIPPS = port_pin_code; + break; + default: + final_status = W_INVALID_PARAM; + goto cleanup; + } + +cleanup: + // Always lock PPS registers before returning + pps_lock(); + return final_status; +} + +w_status_t pps_configure_timer_gate(uint8_t timer, pin_config_t pin_config) { + w_status_t status; + w_status_t final_status = W_SUCCESS; + + // Validate timer number + if (timer > 6U) { + return W_INVALID_PARAM; + } + + // Unlock PPS registers + status = pps_unlock(); + if (status != W_SUCCESS) { + return status; + } + + // Configure pin as digital input + status = configure_pin_digital(pin_config, 1U); + if (status != W_SUCCESS) { + final_status = status; + goto cleanup; + } + + // Configure PPS input mapping for timer gate + uint8_t port_pin_code = get_port_pin_code(pin_config); + switch (timer) { + case 0U: + T0GPPS = port_pin_code; + break; + case 1U: + T1GPPS = port_pin_code; + break; + case 2U: + T2GPPS = port_pin_code; + break; + case 3U: + T3GPPS = port_pin_code; + break; + case 4U: + T4GPPS = port_pin_code; + break; + case 5U: + T5GPPS = port_pin_code; + break; + case 6U: + T6GPPS = port_pin_code; + break; + default: + final_status = W_INVALID_PARAM; + goto cleanup; + } + +cleanup: + // Always lock PPS registers before returning + pps_lock(); + return final_status; +} + diff --git a/pic18f26k83/pwm.c b/pic18f26k83/pwm.c index 6b2794f..6e0d8c0 100644 --- a/pic18f26k83/pwm.c +++ b/pic18f26k83/pwm.c @@ -1,66 +1,116 @@ #include "pwm.h" #include -// Helper function to configure PPS registers using macros -static w_status_t configure_pps(uint8_t ccp_module, pwm_pin_config_t pin_config) { - volatile uint8_t *pps_reg; - - // Ensure the CCP module number is within valid range (1-4) - if (ccp_module < 1 || ccp_module > 4) { - return W_INVALID_PARAM; // Return error if the module number is out of range +/** + * @brief Get CCPR Low register address for the specified CCP module + */ +static volatile uint8_t *get_ccpr_low_register(uint8_t ccp_module) { + switch (ccp_module) { + case 1U: + return &CCPR1L; + case 2U: + return &CCPR2L; + case 3U: + return &CCPR3L; + case 4U: + return &CCPR4L; + case 5U: + return &CCPR5L; + default: + return NULL; } +} - // Set the pin as output to drive PWM signal - // This macro modifies the TRIS register to set the specified pin as an output - SET_TRIS_OUTPUT(pin_config.port, pin_config.pin); - - // Assign the CCP module to the corresponding PPS register - // This macro sets up the peripheral pin select to link the CCP module to the desired pin - ASSIGN_PPS(pin_config.port, pin_config.pin, ccp_module); +/** + * @brief Get CCPR High register address for the specified CCP module + */ +static volatile uint8_t *get_ccpr_high_register(uint8_t ccp_module) { + switch (ccp_module) { + case 1U: + return &CCPR1H; + case 2U: + return &CCPR2H; + case 3U: + return &CCPR3H; + case 4U: + return &CCPR4H; + case 5U: + return &CCPR5H; + default: + return NULL; + } +} - return W_SUCCESS; // Return success status after configuring PPS +/** + * @brief Get CCPxCON register address for the specified CCP module + */ +static volatile uint8_t *get_ccp_con_register(uint8_t ccp_module) { + switch (ccp_module) { + case 1U: + return &CCP1CON; + case 2U: + return &CCP2CON; + case 3U: + return &CCP3CON; + case 4U: + return &CCP4CON; + case 5U: + return &CCP5CON; + default: + return NULL; + } } -// Initialize PWM for a specific CCP module -w_status_t pwm_init(uint8_t ccp_module, pwm_pin_config_t pin_config, uint16_t pwm_period) { - // Configure PPS registers to map CCP module to the selected pin - w_status_t status = configure_pps(ccp_module, pin_config); - if (status != W_SUCCESS) { - return status; // Return error status if PPS configuration fails +w_status_t pwm_init(uint8_t ccp_module, uint16_t pwm_period) { + // Validate CCP module + if (ccp_module < 1U || ccp_module > 5U) { + return W_INVALID_PARAM; } - // Obtain the address of the CCPxCON register using macro - volatile uint8_t *ccp_con = &CCP_CON(ccp_module); - *ccp_con = 0x8C; // Enable CCP module in PWM mode (PWM mode selection) + // Get the CCPxCON register for this module + volatile uint8_t *ccp_con = get_ccp_con_register(ccp_module); + if (ccp_con == NULL) { + return W_INVALID_PARAM; + } + + // Enable CCP module in PWM mode + *ccp_con = 0x8CU; // PWM mode selection // Set PWM period using Timer2 - PR2 = pwm_period & 0xFF; // Load lower 8 bits of PWM period into PR2 register - TMR2 = 0; // Reset Timer2 count to 0 - T2CONbits.T2CKPS = 0; // Set Timer2 prescaler to 1:1 (no prescaling) - T2CONbits.TOUTPS = 0; // Set Timer2 postscaler to 1:1 (no postscaling) - T2CONbits.TMR2ON = 1; // Start Timer2 to begin PWM operation + PR2 = (uint8_t)(pwm_period & 0xFFU); // Load lower 8 bits of PWM period into PR2 register + TMR2 = 0U; // Reset Timer2 count to 0 + T2CONbits.T2CKPS = 0U; // Set Timer2 prescaler to 1:1 (no prescaling) + T2CONbits.TOUTPS = 0U; // Set Timer2 postscaler to 1:1 (no postscaling) + T2CONbits.TMR2ON = 1U; // Start Timer2 to begin PWM operation // Wait for Timer2 to reach the period value before starting PWM - while (!PIR1bits.TMR2IF) {} // Wait until Timer2 overflow flag is set - PIR1bits.TMR2IF = 0; // Clear Timer2 interrupt flag to continue + while (PIR1bits.TMR2IF == 0U) { + // Wait until Timer2 overflow flag is set + } + PIR1bits.TMR2IF = 0U; // Clear Timer2 interrupt flag to continue - return W_SUCCESS; // Return success status after PWM initialization + return W_SUCCESS; } -// Update the duty cycle of a specific CCP module w_status_t pwm_update_duty_cycle(uint8_t ccp_module, uint16_t duty_cycle) { // Validate CCP module and duty cycle range - if (ccp_module < 1 || ccp_module > 4 || duty_cycle > 1023) { - return W_INVALID_PARAM; // Return error if module number or duty cycle is out of range + if (ccp_module < 1U || ccp_module > 5U || duty_cycle > 1023U) { + return W_INVALID_PARAM; + } + + // Get register addresses + volatile uint8_t *ccpr_low = get_ccpr_low_register(ccp_module); + volatile uint8_t *ccpr_high = get_ccpr_high_register(ccp_module); + + if (ccpr_low == NULL || ccpr_high == NULL) { + return W_INVALID_PARAM; } // Update the lower 8 bits of the duty cycle - // This sets the low byte of the duty cycle for the PWM signal - CCPR_L(ccp_module) = duty_cycle & 0xFF; + *ccpr_low = (uint8_t)(duty_cycle & 0xFFU); // Update the upper 2 bits of the duty cycle for 10-bit resolution - // This sets the high bits of the duty cycle to achieve 10-bit PWM precision - CCPR_H(ccp_module) = (duty_cycle >> 8) & 0x03; + *ccpr_high = (uint8_t)((duty_cycle >> 8) & 0x03U); - return W_SUCCESS; // Return success status after updating duty cycle + return W_SUCCESS; }