#include #include #include #include #include "pt6524.h" #include "spi.h" // PT6524 Command/Address // Datasheet specifies 41H, sent with DORD=1 (LSB-first) // Hardware automatically reverses 0x41 to 0x82 on the wire #define PT6524_ADDRESS 0x41 // Control bits #define CTRL_BU (1 << 0) // Normal/Power Saving Mode (0=Normal, 1=Power Saving) #define CTRL_SC (1 << 1) // Segment ON/OFF (0=ON, 1=OFF) #define CTRL_DR (1 << 2) // Bias Drive (0=1/3 bias, 1=1/2 bias) #define CTRL_P3 (1 << 3) // Port select bit 3 #define CTRL_P2 (1 << 4) // Port select bit 2 #define CTRL_P1 (1 << 5) // Port select bit 1 #define CTRL_P0 (1 << 6) // Port select bit 0 #define CTRL_CU (1 << 7) // Current drain control (0=Normal, 1=Low) // Display framebuffer - stores segment states // Each bit represents one segment/COM intersection // Organized as: D1, D2, D3, D4, D5, D6, ... D160 // Where D1=SG1:COM1, D2=SG1:COM2, D3=SG1:COM3, D4=SG1:COM4, // D5=SG2:COM1, D6=SG2:COM2, etc. static uint8_t framebuffer[(PT6524_TOTAL_SEGMENTS * PT6524_TOTAL_COMS + 7) / 8]; // Initialize PT6524 LCD driver void pt6524_init(void) { // Set DISP_ON low BEFORE configuring as output DISP_ON_PORT &= ~DISP_ON; DISP_ON_DDR |= DISP_ON; // Initialize SPI spi_init(); // Keep display off during initialization DISP_ON_LOW(); pt6524_clear_all(); // Enable display DISP_ON_HIGH(); } // Set a specific segment on or off // address: Linear address (0 to PT6524_MAX_ADDRESS-1) // Address 0 = SG1:COM1, 1 = SG1:COM2, 2 = SG1:COM3, 3 = SG1:COM4, // Address 4 = SG2:COM1, 5 = SG2:COM2, etc. // state: 0 = off, non-zero = on void pt6524_set_segment(uint8_t address, uint8_t state) { // Validate address against actual framebuffer size if (address >= PT6524_MAX_ADDRESS) return; // Calculate bit position in framebuffer // Linear address directly maps to bit index in framebuffer uint8_t byte_pos = address / 8; uint8_t bit_pos = address % 8; // Set or clear the bit if (state) { framebuffer[byte_pos] |= (1 << bit_pos); } else { framebuffer[byte_pos] &= ~(1 << bit_pos); } } // Clear all segments void pt6524_clear_all(void) { memset(framebuffer, 0, sizeof(framebuffer)); } // Get direct access to framebuffer for efficient bulk updates uint8_t* pt6524_get_framebuffer(void) { return framebuffer; } // Write a single byte to framebuffer void pt6524_write_byte(uint8_t byte_index, uint8_t value) { if (byte_index < sizeof(framebuffer)) framebuffer[byte_index] = value; } // Write a block of bytes to framebuffer void pt6524_write_block(uint8_t byte_index, const uint8_t* data, uint8_t length) { if (byte_index + length <= sizeof(framebuffer)) memcpy(&framebuffer[byte_index], data, length); } #define DATA_BITS_PER_BLOCK 52 static inline void pt6524_send_block(uint8_t dir_bits, uint16_t start_addr) { const uint8_t control = 0x00; // DR=0 for 1/3 bias _delay_us(.16); spi_write(PT6524_ADDRESS); CE_HIGH(); uint8_t data_payload[7] = {0}; // 52 bits = 6.5 bytes // Determine how many bits to send in this block uint16_t bits_to_send = PT6524_MAX_ADDRESS - start_addr; if (bits_to_send > DATA_BITS_PER_BLOCK) { bits_to_send = DATA_BITS_PER_BLOCK; } // Prepare data payload from framebuffer uint16_t start_byte_idx = start_addr / 8; uint8_t start_bit_offset = start_addr % 8; uint16_t current_byte_idx = start_byte_idx; uint16_t bits_prepared = 0; for (uint8_t i = 0; i < sizeof(data_payload); i++) { if (bits_prepared >= bits_to_send) { break; } uint8_t data_byte = 0; if (start_bit_offset == 0) { if (current_byte_idx < sizeof(framebuffer)) { data_byte = framebuffer[current_byte_idx]; } } else { if (current_byte_idx < sizeof(framebuffer)) { data_byte = (framebuffer[current_byte_idx] >> start_bit_offset); } if (current_byte_idx + 1 < sizeof(framebuffer)) { data_byte |= (framebuffer[current_byte_idx + 1] << (8 - start_bit_offset)); } } uint16_t bits_left_to_prepare = bits_to_send - bits_prepared; if (bits_left_to_prepare < 8) { data_byte &= (1 << bits_left_to_prepare) - 1; } data_payload[i] = data_byte; bits_prepared += 8; current_byte_idx++; } // Write data payload bytes 1-6 for (uint8_t i = 0; i < 6; i++) { spi_write(data_payload[i]); } uint8_t byte7 = data_payload[6]; uint8_t byte8 = 0; // Prepare bytes 7 and 8 which contain last data nibble and control bits switch (dir_bits) { case 0: // dir=00 byte7 |= ((control & CTRL_CU) ? 0x40 : 0) | ((control & CTRL_P0) ? 0x80 : 0); byte8 = ((control & CTRL_P1) ? 0x01 : 0) | ((control & CTRL_P2) ? 0x02 : 0) | ((control & CTRL_P3) ? 0x04 : 0) | ((control & CTRL_DR) ? 0x08 : 0) | ((control & CTRL_SC) ? 0x10 : 0) | ((control & CTRL_BU) ? 0x20 : 0); break; case 1: // dir=01 byte8 = 0x40; break; case 2: // dir=10 byte8 = 0x80; break; case 3: // dir=11 byte8 = 0xC0; break; } spi_write(byte7); spi_write(byte8); CE_LOW(); } // Update the display by sending framebuffer data via SPI // This function calls the pt6524_send_block helper for each required transaction. // With compiler optimizations, this will be expanded into a series of efficient, // hardcoded SPI writes specific to the chosen PT6524_MAX_ADDRESS. void pt6524_update_display(void) { #if PT6524_MAX_ADDRESS > (DATA_BITS_PER_BLOCK * 0) pt6524_send_block(0, (DATA_BITS_PER_BLOCK * 0)); #endif #if PT6524_MAX_ADDRESS > (DATA_BITS_PER_BLOCK * 1) pt6524_send_block(1, (DATA_BITS_PER_BLOCK * 1)); #endif #if PT6524_MAX_ADDRESS > (DATA_BITS_PER_BLOCK * 2) pt6524_send_block(2, (DATA_BITS_PER_BLOCK * 2)); #endif #if PT6524_MAX_ADDRESS > (DATA_BITS_PER_BLOCK * 3) pt6524_send_block(3, (DATA_BITS_PER_BLOCK * 3)); #endif }