/********************************** (C) COPYRIGHT ***************************** * File Name : CH390_Interface.c * Author : WCH (Modified for STM32 HAL) * Version : V1.1 * Date : 2024/08/20 * Description : CH390 interface for STM32 HAL Library (SPI mode) ****************************************************************************** * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. * Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics. * * Modified for STM32F103 HAL Library with FreeRTOS support. ******************************************************************************/ #include "stm32f1xx_hal.h" #include "main.h" #include "CH390.h" #include "CH390_Interface.h" #include "SEGGER_RTT.h" /* FreeRTOS includes */ #ifdef USE_FREERTOS #include "FreeRTOS.h" #include "task.h" #endif /* * This file defines CH390 operation interface using STM32 HAL Library. * Only SPI mode is implemented for this project. * * Hardware connections: * - PA4: SPI1_NSS (directly controlled as GPIO for CS) * - PA5: SPI1_SCK * - PA6: SPI1_MISO * - PA7: SPI1_MOSI * - PB0: CH390 INT (EXTI0) * - PB1: CH390 RST */ #ifdef CH390_INTERFACE_SPI /* GPIO Pin Definitions - matching CubeMX configuration */ #define CH390_CS_PORT GPIOA #define CH390_CS_PIN GPIO_PIN_4 #define CH390_RST_PORT GPIOB #define CH390_RST_PIN GPIO_PIN_1 #define CH390_INT_PORT GPIOB #define CH390_INT_PIN GPIO_PIN_0 #define CH390_SCK_PORT GPIOA #define CH390_SCK_PIN GPIO_PIN_5 #define CH390_MISO_PORT GPIOA #define CH390_MISO_PIN GPIO_PIN_6 #define CH390_MOSI_PORT GPIOA #define CH390_MOSI_PIN GPIO_PIN_7 /* External SPI handle from spi.c */ extern SPI_HandleTypeDef hspi1; /* Timeout for SPI operations (ms) */ #define SPI_TIMEOUT 100 /*---------------------------------------------------------------------------- * Low-level GPIO operations *---------------------------------------------------------------------------*/ /** * @brief Set CS pin state * @param state 0=low (select), 1=high (deselect) */ static inline void ch390_cs(uint8_t state) { HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN, state ? GPIO_PIN_SET : GPIO_PIN_RESET); ch390_delay_us(2); } /** * @brief Set RST pin state * @param state 0=low (reset), 1=high (normal) */ static inline void ch390_rst(uint8_t state) { HAL_GPIO_WritePin(CH390_RST_PORT, CH390_RST_PIN, state ? GPIO_PIN_SET : GPIO_PIN_RESET); } /*---------------------------------------------------------------------------- * SPI Communication *---------------------------------------------------------------------------*/ /** * @brief Exchange one byte over SPI * @param byte Byte to send * @return Received byte */ static uint8_t ch390_spi_exchange_byte(uint8_t byte) { uint8_t rx_data = 0; if (HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, SPI_TIMEOUT) != HAL_OK) { return 0; } return rx_data; } static void ch390_spi_apply_mode(uint32_t polarity, uint32_t phase) { hspi1.Init.CLKPolarity = polarity; hspi1.Init.CLKPhase = phase; hspi1.Init.NSS = SPI_NSS_SOFT; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } } /** * @brief Read a dummy byte (send 0x00) * @return Received byte */ #define ch390_spi_dummy_read() ch390_spi_exchange_byte(0x00) /*---------------------------------------------------------------------------- * Public Interface Functions *---------------------------------------------------------------------------*/ /** * @brief Initialize CH390 GPIO pins * @note CS and RST pins are configured here. SPI pins are handled by CubeMX. * INT pin (PB0) is configured as EXTI in CubeMX. */ void ch390_gpio_init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* Enable GPIO clocks */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /* Configure CS pin (PA4) as GPIO output - we control it manually */ /* Note: CubeMX may configure PA4 as SPI1_NSS, we need to reconfigure it */ HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN, GPIO_PIN_SET); /* Deselect */ GPIO_InitStruct.Pin = CH390_CS_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(CH390_CS_PORT, &GPIO_InitStruct); /* Configure RST pin (PB1) as output */ HAL_GPIO_WritePin(CH390_RST_PORT, CH390_RST_PIN, GPIO_PIN_SET); /* Not reset */ GPIO_InitStruct.Pin = CH390_RST_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(CH390_RST_PORT, &GPIO_InitStruct); /* INT pin (PB0) is configured as EXTI input by CubeMX */ } /** * @brief Initialize CH390 interrupt (EXTI0 on PB0) * @note EXTI and NVIC are configured in CubeMX. This function can enable/disable. */ void ch390_interrupt_init(void) { /* EXTI0 is configured in CubeMX for PB0 */ /* NVIC priority should be >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY */ /* for FreeRTOS compatibility */ HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); } /** * @brief Initialize SPI for CH390 * @note SPI1 is already initialized by CubeMX. This function reconfigures * for CH390 requirements (Mode 3: CPOL=High, CPHA=2Edge). */ void ch390_spi_init(void) { /* SPI1 is initialized by MX_SPI1_Init() in main.c */ /* We need to ensure correct SPI mode for CH390: */ /* - CPOL = High (idle clock is high) */ /* - CPHA = 2Edge (data captured on second edge) */ ch390_spi_apply_mode(SPI_POLARITY_HIGH, SPI_PHASE_2EDGE); } /** * @brief Get CH390 interrupt pin state * @return Non-zero if INT pin is high (active low interrupt) */ uint16_t ch390_get_int_pin(void) { return HAL_GPIO_ReadPin(CH390_INT_PORT, CH390_INT_PIN); } /** * @brief Delay in microseconds * @param time Delay time in microseconds * @note Uses DWT cycle counter for accurate timing if available, * otherwise falls back to simple loop delay. */ void ch390_delay_us(uint32_t time) { #ifdef USE_FREERTOS /* For FreeRTOS, if delay is long enough, use vTaskDelay */ if (time >= 1000) { /* Convert to milliseconds and use FreeRTOS delay if in task context */ if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { vTaskDelay(pdMS_TO_TICKS(time / 1000)); time = time % 1000; } } #endif /* Short delay using DWT or simple loop */ if (time > 0) { /* Simple delay loop - approximately calibrated for 72MHz */ /* Each iteration is roughly 1/9 us at 72MHz */ volatile uint32_t count = time * 9; while (count--) { __NOP(); } } } /** * @brief Hardware reset CH390 by pulling RST pin low */ void ch390_hardware_reset(void) { ch390_rst(0); /* Assert reset (low) */ ch390_delay_us(1000); /* Hold reset for 1ms to satisfy datasheet minimum */ ch390_rst(1); /* Release reset (high) */ ch390_delay_us(50000); /* Wait 50ms for CH390 to initialize reliably */ } /*---------------------------------------------------------------------------- * CH390 Register/Memory Access Functions (SPI Mode) *---------------------------------------------------------------------------*/ /** * @brief Read a CH390 register * @param reg Register address * @return Register value */ uint8_t ch390_read_reg(uint8_t reg) { uint8_t value; ch390_cs(0); /* CS low - select */ ch390_spi_exchange_byte(reg | OPC_REG_R); /* Send read command */ value = ch390_spi_dummy_read(); /* Read register value */ ch390_cs(1); /* CS high - deselect */ return value; } /** * @brief Write a CH390 register * @param reg Register address * @param value Value to write */ void ch390_write_reg(uint8_t reg, uint8_t value) { uint8_t frame[2]; frame[0] = reg | OPC_REG_W; frame[1] = value; ch390_cs(0); /* CS low - select */ ch390_spi_exchange_byte(frame[0]); /* Send write command */ ch390_spi_exchange_byte(frame[1]); /* Send write data */ ch390_cs(1); /* CS high - deselect */ } /** * @brief Read data from CH390 RX SRAM * @param data Buffer to store received data * @param length Number of bytes to read */ void ch390_read_mem(uint8_t *data, int length) { int i; ch390_cs(0); /* CS low - select */ ch390_spi_exchange_byte(OPC_MEM_READ); /* Send memory read command */ /* Read data bytes */ for (i = 0; i < length; i++) { data[i] = ch390_spi_dummy_read(); } ch390_cs(1); /* CS high - deselect */ } /** * @brief Read data from CH390 RX SRAM using DMA (for larger transfers) * @param data Buffer to store received data * @param length Number of bytes to read * @note Falls back to polling mode for small transfers */ void ch390_read_mem_dma(uint8_t *data, int length) { /* For small transfers, use polling mode */ if (length < 64) { ch390_read_mem(data, length); return; } /* For larger transfers, could use DMA - currently using polling */ /* TODO: Implement DMA transfer if needed for performance */ ch390_read_mem(data, length); } /** * @brief Write data to CH390 TX SRAM * @param data Data buffer to send * @param length Number of bytes to write */ void ch390_write_mem(uint8_t *data, int length) { int i; ch390_cs(0); /* CS low - select */ ch390_spi_exchange_byte(OPC_MEM_WRITE); /* Send memory write command */ /* Write data bytes */ for (i = 0; i < length; i++) { ch390_spi_exchange_byte(data[i]); } ch390_cs(1); /* CS high - deselect */ } /** * @brief Write data to CH390 TX SRAM using DMA (for larger transfers) * @param data Data buffer to send * @param length Number of bytes to write * @note Falls back to polling mode for small transfers */ void ch390_write_mem_dma(uint8_t *data, int length) { /* For small transfers, use polling mode */ if (length < 64) { ch390_write_mem(data, length); return; } /* For larger transfers, could use DMA - currently using polling */ /* TODO: Implement DMA transfer if needed for performance */ ch390_write_mem(data, length); } #else /* CH390_INTERFACE_SPI */ /* * Non-SPI modes (8-bit/16-bit parallel) are not implemented for this project. * This project uses SPI interface only. */ #error "This project only supports CH390 SPI interface. Please define CH390_INTERFACE_SPI in CH390.h" #endif /* CH390_INTERFACE_SPI */