From a5690b5bf4e1fba0538fd4bd28d0f372e1c2a330 Mon Sep 17 00:00:00 2001 From: muzkr Date: Sun, 26 Oct 2025 01:46:11 +0800 Subject: [PATCH] SPI flash driver (write) & settings saving --- App/CMakeLists.txt | 5 +- App/app/fm.c | 7 +- App/app/uart.c | 5 +- App/board.c | 12 +- App/driver/backlight.c | 3 + App/driver/bk4819.c | 3 +- App/driver/dac.c | 50 ++++++++ App/driver/dac.h | 21 +++ App/driver/py25q16.c | 253 ++++++++++++++++++++++++++++++++++-- App/driver/py25q16.h | 10 +- App/driver/st7565.c | 55 ++++---- App/settings.c | 284 ++++++++++++++++++++++++++--------------- CMakeLists.txt | 4 +- CMakePresets.json | 1 + 14 files changed, 562 insertions(+), 151 deletions(-) create mode 100644 App/driver/dac.c create mode 100644 App/driver/dac.h diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index 4c47ca55..c24707d3 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -12,7 +12,6 @@ target_sources(App INTERFACE driver/gpio.c driver/i2c.c driver/keyboard.c - # driver/spi.c driver/st7565.c driver/system.c driver/systick.c @@ -106,7 +105,9 @@ enable_feature(ENABLE_AIRCOPY ui/aircopy.c ) enable_feature(ENABLE_NOAA) -# enable_feature(ENABLE_VOICE) +enable_feature(ENABLE_VOICE + driver/dac.c +) enable_feature(ENABLE_VOX) enable_feature(ENABLE_ALARM) enable_feature(ENABLE_TX1750) diff --git a/App/app/fm.c b/App/app/fm.c index 2d669370..d6da154d 100644 --- a/App/app/fm.c +++ b/App/app/fm.c @@ -119,12 +119,7 @@ void FM_TurnOff(void) void FM_EraseChannels(void) { - uint8_t Template[8]; - memset(Template, 0xFF, sizeof(Template)); - - for (unsigned i = 0; i < 5; i++) - EEPROM_WriteBuffer(0x0E40 + (i * 8), Template); - + PY25Q16_SectorErase(0x003000); memset(gFM_Channels, 0xFF, sizeof(gFM_Channels)); } diff --git a/App/app/uart.c b/App/app/uart.c index 28b8c87a..4d1fa8a5 100644 --- a/App/app/uart.c +++ b/App/app/uart.c @@ -328,7 +328,10 @@ static void CMD_051D(const uint8_t *pBuffer) bReloadEeprom = true; if ((Offset < 0x0E98 || Offset >= 0x0EA0) || !bIsInLockScreen || pCmd->bAllowPassword) - EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U]); + { + // EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U]); + // TODO: To be implemented + } } if (bReloadEeprom) diff --git a/App/board.c b/App/board.c index 45ff32c2..2a771716 100644 --- a/App/board.c +++ b/App/board.c @@ -25,6 +25,7 @@ #include "py32f071_ll_gpio.h" #include "py32f071_ll_rcc.h" #include "py32f071_ll_adc.h" +#include "driver/dac.h" #include "driver/backlight.h" #ifdef ENABLE_FMRADIO #include "driver/bk1080.h" @@ -110,15 +111,21 @@ void BOARD_GPIO_Init(void) InitStruct.Pin = LL_GPIO_PIN_9 | LL_GPIO_PIN_8 | LL_GPIO_PIN_2; LL_GPIO_Init(GPIOB, &InitStruct); + // TODO: conditional compile per ENABLE_FLASHLIGHT // Flashlight: PC13 InitStruct.Pin = LL_GPIO_PIN_13; LL_GPIO_Init(GPIOC, &InitStruct); +#ifdef ENABLE_FMRADIO // BK1080 SCK: PF5 // BK1080 SDA: PF6 + InitStruct.Pin = LL_GPIO_PIN_6 | LL_GPIO_PIN_5; + LL_GPIO_Init(GPIOF, &InitStruct); +#endif + // Backlight: PF8 // BK4819 CS: PF9 - InitStruct.Pin = LL_GPIO_PIN_9 | LL_GPIO_PIN_8 | LL_GPIO_PIN_6 | LL_GPIO_PIN_5; + InitStruct.Pin = LL_GPIO_PIN_9 | LL_GPIO_PIN_8 ; LL_GPIO_Init(GPIOF, &InitStruct); #ifndef ENABLE_SWD @@ -172,6 +179,9 @@ void BOARD_Init(void) BOARD_GPIO_Init(); BACKLIGHT_InitHardware(); BOARD_ADC_Init(); +#ifdef ENABLE_VOICE + DAC_Init(); +#endif PY25Q16_Init(); ST7565_Init(); #ifdef ENABLE_FMRADIO diff --git a/App/driver/backlight.c b/App/driver/backlight.c index 11d3a75a..79e6ccfa 100644 --- a/App/driver/backlight.c +++ b/App/driver/backlight.c @@ -81,6 +81,9 @@ static void BACKLIGHT_Sound(void) void BACKLIGHT_TurnOn(void) { + // TODO: This is temporary. Will delete + GPIO_TurnOnBacklight(); + #ifdef ENABLE_FEAT_F4HWN_SLEEP gSleepModeCountdown_500ms = gSetting_set_off * 120; #endif diff --git a/App/driver/bk4819.c b/App/driver/bk4819.c index 35ddd399..89bf3df2 100644 --- a/App/driver/bk4819.c +++ b/App/driver/bk4819.c @@ -1,4 +1,5 @@ -/* Copyright 2023 Dual Tachyon +/* Copyright 2025 muzkr https://github.com/muzkr + * Copyright 2023 Dual Tachyon * https://github.com/DualTachyon * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/App/driver/dac.c b/App/driver/dac.c new file mode 100644 index 00000000..8671cda2 --- /dev/null +++ b/App/driver/dac.c @@ -0,0 +1,50 @@ +/* Copyright 2025 muzkr https://github.com/muzkr + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "driver/dac.h" +#include "driver/systick.h" +#include "py32f071_ll_bus.h" +#include "py32f071_ll_gpio.h" +#include "py32f071_ll_dac.h" +#include "py32f071_ll_tim.h" + +#define TIMx TIM6 +#define DAC_CHANNEL LL_DAC_CHANNEL_1 + +static inline void TIM_Init() +{ + LL_TIM_SetPrescaler(TIMx, 3); + LL_TIM_SetAutoReload(TIMx, 1499); + LL_TIM_SetTriggerOutput(TIMx, LL_TIM_TRGO_UPDATE); +} + +void DAC_Init() +{ + // Channel 1: PA4 + LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); + LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_4, LL_GPIO_MODE_ANALOG); + + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_DAC1); + LL_DAC_SetTriggerSource(DAC1, DAC_CHANNEL, LL_DAC_TRIG_EXT_TIM6_TRGO); + LL_DAC_SetOutputBuffer(DAC1, DAC_CHANNEL, LL_DAC_OUTPUT_BUFFER_ENABLE); + LL_DAC_EnableDMAReq(DAC1, DAC_CHANNEL); + LL_DAC_Enable(DAC1, DAC_CHANNEL); + + SYSTICK_DelayUs(15); + + TIM_Init(); + + LL_DAC_EnableTrigger(DAC1, DAC_CHANNEL); +} diff --git a/App/driver/dac.h b/App/driver/dac.h new file mode 100644 index 00000000..b2438479 --- /dev/null +++ b/App/driver/dac.h @@ -0,0 +1,21 @@ +/* Copyright 2025 muzkr https://github.com/muzkr + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRIVER_DAC_H +#define DRIVER_DAC_H + +void DAC_Init(); + +#endif // DRIVER_DAC_H diff --git a/App/driver/py25q16.c b/App/driver/py25q16.c index a9037c6a..42540138 100644 --- a/App/driver/py25q16.c +++ b/App/driver/py25q16.c @@ -1,5 +1,5 @@ -/* Copyright 2023 Dual Tachyon - * https://github.com/DualTachyon +/* Copyright 2025 muzkr + * https://github.com/muzkr * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ #include "py32f071_ll_spi.h" #include "py32f071_ll_dma.h" #include "driver/system.h" +#include "driver/systick.h" #define SPIx SPI2 #define CHANNEL_RD LL_DMA_CHANNEL_4 @@ -31,7 +32,12 @@ #define CS_PIN GPIO_MAKE_PIN(GPIOA, LL_GPIO_PIN_3) -static uint8_t TX_Buf[256]; +#define SECTOR_SIZE 0x1000 +#define PAGE_SIZE 0x100 + +static uint32_t SectorCacheAddr = 0x1000000; +static uint8_t SectorCache[SECTOR_SIZE]; +static uint8_t BlackHole[1]; static volatile bool TC_Flag; static inline void CS_Assert() @@ -126,7 +132,56 @@ static void SPI_ReadBuf(uint8_t *Buf, uint32_t Size) LL_DMA_SetPeriphAddress(DMA1, CHANNEL_RD, LL_SPI_DMA_GetRegAddr(SPIx)); LL_DMA_SetDataLength(DMA1, CHANNEL_RD, Size); - LL_DMA_SetMemoryAddress(DMA1, CHANNEL_WR, (uint32_t)TX_Buf); + LL_DMA_SetMemoryAddress(DMA1, CHANNEL_WR, (uint32_t)BlackHole); + LL_DMA_SetPeriphAddress(DMA1, CHANNEL_WR, LL_SPI_DMA_GetRegAddr(SPIx)); + LL_DMA_SetDataLength(DMA1, CHANNEL_WR, Size); + + TC_Flag = false; + LL_DMA_EnableIT_TC(DMA1, CHANNEL_RD); + LL_DMA_EnableChannel(DMA1, CHANNEL_RD); + LL_DMA_EnableChannel(DMA1, CHANNEL_WR); + + LL_SPI_EnableDMAReq_RX(SPIx); + LL_SPI_Enable(SPIx); + LL_SPI_EnableDMAReq_TX(SPIx); + + while (!TC_Flag) + ; +} + +static void SPI_WriteBuf(const uint8_t *Buf, uint32_t Size) +{ + LL_SPI_Disable(SPIx); + LL_DMA_DisableChannel(DMA1, CHANNEL_RD); + LL_DMA_DisableChannel(DMA1, CHANNEL_WR); + + LL_DMA_ClearFlag_GI4(DMA1); + + LL_DMA_ConfigTransfer(DMA1, CHANNEL_RD, // + LL_DMA_DIRECTION_PERIPH_TO_MEMORY // + | LL_DMA_MODE_NORMAL // + | LL_DMA_PERIPH_NOINCREMENT // + | LL_DMA_MEMORY_NOINCREMENT // + | LL_DMA_PDATAALIGN_BYTE // + | LL_DMA_MDATAALIGN_BYTE // + | LL_DMA_PRIORITY_LOW // + ); + + LL_DMA_ConfigTransfer(DMA1, CHANNEL_WR, // + LL_DMA_DIRECTION_MEMORY_TO_PERIPH // + | LL_DMA_MODE_NORMAL // + | LL_DMA_PERIPH_NOINCREMENT // + | LL_DMA_MEMORY_INCREMENT // + | LL_DMA_PDATAALIGN_BYTE // + | LL_DMA_MDATAALIGN_BYTE // + | LL_DMA_PRIORITY_LOW // + ); + + LL_DMA_SetMemoryAddress(DMA1, CHANNEL_RD, (uint32_t)BlackHole); + LL_DMA_SetPeriphAddress(DMA1, CHANNEL_RD, LL_SPI_DMA_GetRegAddr(SPIx)); + LL_DMA_SetDataLength(DMA1, CHANNEL_RD, Size); + + LL_DMA_SetMemoryAddress(DMA1, CHANNEL_WR, (uint32_t)Buf); LL_DMA_SetPeriphAddress(DMA1, CHANNEL_WR, LL_SPI_DMA_GetRegAddr(SPIx)); LL_DMA_SetDataLength(DMA1, CHANNEL_WR, Size); @@ -153,6 +208,14 @@ static uint8_t SPI_WriteByte(uint8_t Value) return LL_SPI_ReceiveData8(SPIx); } +static void WriteAddr(uint32_t Addr); +static uint8_t ReadStatusReg(uint32_t Which); +static void WaitWIP(); +static void WriteEnable(); +static void SectorErase(uint32_t Addr); +static void SectorProgram(uint32_t Addr, const uint8_t *Buf, uint32_t Size); +static void PageProgram(uint32_t Addr, const uint8_t *Buf, uint32_t Size); + void PY25Q16_Init() { CS_Release(); @@ -164,9 +227,7 @@ void PY25Q16_ReadBuffer(uint32_t Address, void *pBuffer, uint32_t Size) CS_Assert(); SPI_WriteByte(0x03); // Fast read - SPI_WriteByte(0xff & (Address >> 16)); - SPI_WriteByte(0xff & (Address >> 8)); - SPI_WriteByte(0xff & Address); + WriteAddr(Address); if (Size >= 16) { @@ -183,8 +244,184 @@ void PY25Q16_ReadBuffer(uint32_t Address, void *pBuffer, uint32_t Size) CS_Release(); } -void PY25Q16_WriteBuffer(uint16_t Address, const void *pBuffer) +void PY25Q16_WriteBuffer(uint32_t Address, const void *pBuffer, uint32_t Size, bool Append) { + while (Size) + { + uint32_t SecIndex = Address / SECTOR_SIZE; + uint32_t SecOffset = Address % SECTOR_SIZE; + uint32_t SecSize = SECTOR_SIZE - SecOffset; + if (Size < SecSize) + { + SecSize = Size; + } + + uint32_t SecAddr = SecIndex * SECTOR_SIZE; + if (SecAddr != SectorCacheAddr) + { + PY25Q16_ReadBuffer(SecAddr, SectorCache, SECTOR_SIZE); + SectorCacheAddr = SecAddr; + } + + if (0 != strncmp(pBuffer, (char *)SectorCache + SecOffset, SecSize)) + { + bool Erase = false; + for (uint32_t i = 0; i < SecSize; i++) + { + if (0xff != SectorCache[SecOffset + i]) + { + Erase = true; + break; + } + } + + memcpy(SectorCache + SecOffset, pBuffer, SecSize); + + if (Erase) + { + SectorErase(SecAddr); + if (Append) + { + SectorProgram(SecAddr, SectorCache, SecOffset + SecSize); + memset(SectorCache + SecOffset + SecSize, 0xff, SECTOR_SIZE - SecOffset - SecSize); + } + else + { + SectorProgram(SecAddr, SectorCache, SECTOR_SIZE); + } + } + else + { + SectorProgram(Address, pBuffer, SecSize); + } + } + + Address += SecSize; + pBuffer += SecSize; + Size -= SecSize; + } // while +} + +void PY25Q16_SectorErase(uint32_t Address) +{ + Address -= (Address % SECTOR_SIZE); + SectorErase(Address); + if (SectorCacheAddr == Address) + { + memset(SectorCache, 0xff, SECTOR_SIZE); + } +} + +static inline void WriteAddr(uint32_t Addr) +{ + SPI_WriteByte(0xff & (Addr >> 16)); + SPI_WriteByte(0xff & (Addr >> 8)); + SPI_WriteByte(0xff & Addr); +} + +static uint8_t ReadStatusReg(uint32_t Which) +{ + uint8_t Cmd; + switch (Which) + { + case 0: + Cmd = 0x5; + break; + case 1: + Cmd = 0x35; + break; + case 2: + Cmd = 0x15; + break; + default: + return 0; + } + + CS_Assert(); + SPI_WriteByte(Cmd); + uint8_t Value = SPI_WriteByte(0xff); + CS_Release(); + + return Value; +} + +static void WaitWIP() +{ + for (int i = 0; i < 1000000; i++) + { + uint8_t Status = ReadStatusReg(0); + if (1 & Status) // WIP + { + SYSTICK_DelayUs(10); + continue; + } + break; + } +} + +static void WriteEnable() +{ + CS_Assert(); + SPI_WriteByte(0x6); + CS_Release(); +} + +static void SectorErase(uint32_t Addr) +{ + WriteEnable(); + WaitWIP(); + + CS_Assert(); + SPI_WriteByte(0x20); + WriteAddr(Addr); + CS_Release(); + + WaitWIP(); +} + +static void SectorProgram(uint32_t Addr, const uint8_t *Buf, uint32_t Size) +{ + while (Size) + { + uint32_t Size1 = PAGE_SIZE - (Addr % PAGE_SIZE); + if (Size < Size1) + { + Size1 = Size; + } + + PageProgram(Addr, Buf, Size1); + + Addr += Size1; + Buf += Size1; + Size -= Size1; + } +} + +static void PageProgram(uint32_t Addr, const uint8_t *Buf, uint32_t Size) +{ + WriteEnable(); + WaitWIP(); + + CS_Assert(); + + SPI_WriteByte(0x2); + WriteAddr(Addr); + + if (Size >= 16) + { + SPI_WriteBuf(Buf, Size); + } + else + { + for (uint32_t i = 0; i < Size; i++) + { + SPI_WriteByte(Buf[i]); + } + } + + CS_Release(); + + WaitWIP(); } void DMA1_Channel4_5_6_7_IRQHandler() diff --git a/App/driver/py25q16.h b/App/driver/py25q16.h index b35e7a5e..fae064e0 100644 --- a/App/driver/py25q16.h +++ b/App/driver/py25q16.h @@ -1,5 +1,5 @@ -/* Copyright 2023 Dual Tachyon - * https://github.com/DualTachyon +/* Copyright 2025 muzkr + * https://github.com/muzkr * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,11 @@ #define DRIVER_PY25Q16_H #include +#include void PY25Q16_Init(); void PY25Q16_ReadBuffer(uint32_t Address, void *pBuffer, uint32_t Size); -void PY25Q16_WriteBuffer(uint16_t Address, const void *pBuffer); - -#define EEPROM_WriteBuffer PY25Q16_WriteBuffer +void PY25Q16_WriteBuffer(uint32_t Address, const void *pBuffer, uint32_t Size, bool Append); +void PY25Q16_SectorErase(uint32_t Address); #endif diff --git a/App/driver/st7565.c b/App/driver/st7565.c index 22fca16c..ea348c51 100644 --- a/App/driver/st7565.c +++ b/App/driver/st7565.c @@ -74,22 +74,22 @@ static void SPI_Init() LL_SPI_Enable(SPIx); } -static inline void AssertCS() +static inline void CS_Assert() { GPIO_ResetOutputPin(PIN_CS); } -static inline void ReleaseCS() +static inline void CS_Release() { GPIO_SetOutputPin(PIN_CS); } -static inline void SetA0() +static inline void A0_Set() { GPIO_SetOutputPin(PIN_A0); } -static inline void ResetA0() +static inline void A0_Reset() { GPIO_ResetOutputPin(PIN_A0); } @@ -110,7 +110,7 @@ static uint8_t SPI_WriteByte(uint8_t Value) static void DrawLine(uint8_t column, uint8_t line, const uint8_t * lineBuffer, unsigned size_defVal) { ST7565_SelectColumnAndLine(column + 4, line); - SetA0(); + A0_Set(); for (unsigned i = 0; i < size_defVal; i++) { SPI_WriteByte(lineBuffer ? lineBuffer[i] : size_defVal); } @@ -118,9 +118,9 @@ static void DrawLine(uint8_t column, uint8_t line, const uint8_t * lineBuffer, u void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const uint8_t *pBitmap, const unsigned int Size) { - AssertCS(); + CS_Assert(); DrawLine(Column, Line, pBitmap, Size); - ReleaseCS(); + CS_Release(); } @@ -134,7 +134,7 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u static void ST7565_BlitScreen(uint8_t line) { - AssertCS(); + CS_Assert(); ST7565_WriteByte(0x40); if(line == 0) @@ -152,7 +152,7 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u } } - ReleaseCS(); + CS_Release(); } void ST7565_BlitFullScreen(void) @@ -172,38 +172,38 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u #else void ST7565_BlitFullScreen(void) { - AssertCS(); + CS_Assert(); ST7565_WriteByte(0x40); for (unsigned line = 0; line < FRAME_LINES; line++) { DrawLine(0, line+1, gFrameBuffer[line], LCD_WIDTH); } - ReleaseCS(); + CS_Release(); } void ST7565_BlitLine(unsigned line) { - AssertCS(); + CS_Assert(); ST7565_WriteByte(0x40); // start line ? DrawLine(0, line+1, gFrameBuffer[line], LCD_WIDTH); - ReleaseCS(); + CS_Release(); } void ST7565_BlitStatusLine(void) { // the top small text line on the display - AssertCS(); + CS_Assert(); ST7565_WriteByte(0x40); // start line ? DrawLine(0, 0, gStatusLine, LCD_WIDTH); - ReleaseCS(); + CS_Release(); } #endif void ST7565_FillScreen(uint8_t value) { - AssertCS(); + CS_Assert(); for (unsigned i = 0; i < 8; i++) { DrawLine(0, i, NULL, value); } - ReleaseCS(); + CS_Release(); } // Software reset @@ -289,7 +289,7 @@ uint8_t cmds[] = { #if defined(ENABLE_FEAT_F4HWN_CTR) || defined(ENABLE_FEAT_F4HWN_INV) void ST7565_ContrastAndInv(void) { - AssertCS(); + CS_Assert(); ST7565_WriteByte(ST7565_CMD_SOFTWARE_RESET); // software reset for(uint8_t i = 0; i < 8; i++) @@ -327,7 +327,7 @@ void ST7565_Init(void) { SPI_Init(); ST7565_HardwareReset(); - AssertCS(); + CS_Assert(); ST7565_WriteByte(ST7565_CMD_SOFTWARE_RESET); // software reset SYSTEM_DelayMs(120); @@ -353,7 +353,7 @@ void ST7565_Init(void) ST7565_WriteByte(ST7565_CMD_SET_START_LINE | 0); // line 0 ST7565_WriteByte(ST7565_CMD_DISPLAY_ON_OFF | 1); // D=1 - ReleaseCS(); + CS_Release(); ST7565_FillScreen(0x00); } @@ -361,17 +361,17 @@ void ST7565_Init(void) #ifdef ENABLE_FEAT_F4HWN_SLEEP void ST7565_ShutDown(void) { - AssertCS(); + CS_Assert(); ST7565_WriteByte(ST7565_CMD_POWER_CIRCUIT | 0b000); // VB=0 VR=1 VF=1 ST7565_WriteByte(ST7565_CMD_SET_START_LINE | 0); // line 0 ST7565_WriteByte(ST7565_CMD_DISPLAY_ON_OFF | 0); // D=1 - ReleaseCS(); + CS_Release(); } #endif void ST7565_FixInterfGlitch(void) { - AssertCS(); + CS_Assert(); for(uint8_t i = 0; i < ARRAY_SIZE(cmds); i++) #ifdef ENABLE_FEAT_F4HWN ST7565_Cmd(i); @@ -379,7 +379,7 @@ void ST7565_FixInterfGlitch(void) ST7565_WriteByte(cmds[i]); #endif - ReleaseCS(); + CS_Release(); } void ST7565_HardwareReset(void) @@ -390,14 +390,17 @@ void ST7565_HardwareReset(void) void ST7565_SelectColumnAndLine(uint8_t Column, uint8_t Line) { - ResetA0(); + A0_Reset(); SPI_WriteByte(Line + 176); SPI_WriteByte(((Column >> 4) & 0x0F) | 0x10); SPI_WriteByte((Column >> 0) & 0x0F); } +/** + * Write a command (rather than pixel data) + */ void ST7565_WriteByte(uint8_t Value) { - ResetA0(); + A0_Reset(); SPI_WriteByte(Value); } diff --git a/App/settings.c b/App/settings.c index cfae4868..bc0d0ca5 100644 --- a/App/settings.c +++ b/App/settings.c @@ -1,4 +1,5 @@ -/* Copyright 2023 Dual Tachyon +/* Copyright 2025 muzkr https://github.com/muzkr + * Copyright 2023 Dual Tachyon * https://github.com/DualTachyon * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -502,33 +503,54 @@ void SETTINGS_FetchChannelName(char *s, const int channel) void SETTINGS_FactoryReset(bool bIsAll) { - uint16_t i; - uint8_t Template[8]; - - memset(Template, 0xFF, sizeof(Template)); - - //for (i = 0x0C80; i < 0x1E00; i += 8) - for (i = 0x0000; i < 0x1E00; i += 8) + // 0000 - 0c80 + PY25Q16_SectorErase(0); + // 0c80 - 0d60 + PY25Q16_SectorErase(0x001000); + // 0d60 - 0e30 + if (bIsAll) { - if ( - !(i >= 0x0EE0 && i < 0x0F18) && // ANI ID + DTMF codes - !(i >= 0x0F30 && i < 0x0F50) && // AES KEY + F LOCK + Scramble Enable - !(i >= 0x1C00 && i < 0x1E00) && // DTMF contacts - !(i >= 0x0EB0 && i < 0x0ED0) && // Welcome strings - !(i >= 0x0EA0 && i < 0x0EA8) && // Voice Prompt - (bIsAll || - ( - !(i >= 0x0D60 && i < 0x0E28) && // MR Channel Attributes - !(i >= 0x0F18 && i < 0x0F30) && // Scan List - !(i >= 0x0F50 && i < 0x1C00) && // MR Channel Names - !(i >= 0x0E40 && i < 0x0E70) && // FM Channels - !(i >= 0x0E88 && i < 0x0E90) // FM settings - )) - ) - { - EEPROM_WriteBuffer(i, Template); - } + PY25Q16_SectorErase(0x002000); } + // 0e40 - 0e68 + if (bIsAll) + { + PY25Q16_SectorErase(0x003000); + } + // 0e70 - 0e80 + PY25Q16_SectorErase(0x004000); + // 0e80 - 0e88 + PY25Q16_SectorErase(0x005000); + // 0e88 - 0e90 + if (bIsAll) + { + PY25Q16_SectorErase(0x006000); + } + // 0e90 - 0ee0 + do + { + uint8_t Buf[0x50]; + memset(Buf, 0xff, 0x50); + // 0EA0 - 0EA8 : keep + PY25Q16_ReadBuffer(0x007000 + 0x10, Buf + 0x10, 8); + // 0EB0 - 0ED0 : keep + PY25Q16_ReadBuffer(0x007000 + 0x20, Buf + 0x20, 0x20); + PY25Q16_WriteBuffer(0x007000, Buf, 0x50, true); + } while (0); + // 0ee0 - 0f18 : keep + // 0f18 - 0f20 + if (bIsAll) + { + PY25Q16_SectorErase(0x009000); + } + // 0f30 - 0f40 : keep + // 0f40 - 0f48 : keep + // 0f50 - 1bd0 + if (bIsAll) + { + PY25Q16_SectorErase(0x00e000); + } + // 1c00 - 1d00 : keep if (bIsAll) { @@ -547,7 +569,7 @@ void SETTINGS_FactoryReset(bool bIsAll) #endif #ifdef ENABLE_FEAT_F4HWN - EEPROM_WriteBuffer(0x1FF0, Template); + PY25Q16_SectorErase(0x00c000); #endif } } @@ -571,11 +593,12 @@ void SETTINGS_SaveFM(void) fmCfg.selFreq = gEeprom.FM_SelectedFrequency; fmCfg.isMrMode = gEeprom.FM_IsMrMode; fmCfg.band = gEeprom.FM_Band; - //fmCfg.space = gEeprom.FM_Space; - EEPROM_WriteBuffer(0x0E88, fmCfg.__raw); + // fmCfg.space = gEeprom.FM_Space; + // 0E88 + PY25Q16_WriteBuffer(0x006000, fmCfg.__raw, 8, true); - for (unsigned i = 0; i < 5; i++) - EEPROM_WriteBuffer(0x0E40 + (i * 8), &gFM_Channels[i * 4]); + // 0E40 + PY25Q16_WriteBuffer(0x003000, gFM_Channels, sizeof(gFM_Channels), true); } #endif @@ -599,18 +622,23 @@ void SETTINGS_SaveVfoIndices(void) State[7] = gEeprom.NoaaChannel[1]; #endif - EEPROM_WriteBuffer(0x0E80, State); + // 0x0E80 + PY25Q16_WriteBuffer(0x005000, State, 8, true); } void SETTINGS_SaveSettings(void) { - uint8_t State[8]; + uint8_t *State; uint8_t tmp = 0; + uint8_t SecBuf[0x50]; - #ifdef ENABLE_PWRON_PASSWORD - uint32_t Password[2]; - #endif + // ---------------------- + // 0e70 - 0e80 + memset(SecBuf, 0xff, 0x10); + + // 0x0E70 + State = SecBuf; State[0] = gEeprom.CHAN_1_CALL; State[1] = gEeprom.SQUELCH_LEVEL; State[2] = gEeprom.TX_TIMEOUT_TIMER; @@ -634,8 +662,9 @@ void SETTINGS_SaveSettings(void) State[6] = 0; #endif State[7] = gEeprom.MIC_SENSITIVITY; - EEPROM_WriteBuffer(0x0E70, State); + // 0x0E78 + State = SecBuf + 0x8; State[0] = (gEeprom.BACKLIGHT_MIN << 4) + gEeprom.BACKLIGHT_MAX; State[1] = gEeprom.CHANNEL_DISPLAY_MODE; State[2] = gEeprom.CROSS_BAND_RX_TX; @@ -671,8 +700,17 @@ void SETTINGS_SaveSettings(void) #else State[7] = gEeprom.VFO_OPEN; #endif - EEPROM_WriteBuffer(0x0E78, State); + PY25Q16_WriteBuffer(0x004000, SecBuf, 0x10, true); + + // ------------------------- + // 0e90 - 0ee0 + + // memset(SecBuf, 0xff, 0x50); + PY25Q16_ReadBuffer(0x007000, SecBuf, 0x50); + + // 0x0E90 + State = SecBuf; State[0] = gEeprom.BEEP_CONTROL; State[0] |= gEeprom.KEY_M_LONG_PRESS_ACTION << 1; State[1] = gEeprom.KEY_1_SHORT_PRESS_ACTION; @@ -682,15 +720,15 @@ void SETTINGS_SaveSettings(void) State[5] = gEeprom.SCAN_RESUME_MODE; State[6] = gEeprom.AUTO_KEYPAD_LOCK; State[7] = gEeprom.POWER_ON_DISPLAY_MODE; - EEPROM_WriteBuffer(0x0E90, State); + // 0x0E98 #ifdef ENABLE_PWRON_PASSWORD - memset(Password, 0xFF, sizeof(Password)); - Password[0] = gEeprom.POWER_ON_PASSWORD; - EEPROM_WriteBuffer(0x0E98, Password); + State = SecBuf + 0x8; + State[0] = gEeprom.POWER_ON_PASSWORD; #endif - memset(State, 0xFF, sizeof(State)); + // 0x0EA0 + State = SecBuf + 0x10; #ifdef ENABLE_VOICE State[0] = gEeprom.VOICE_PROMPT; #endif @@ -698,9 +736,9 @@ void SETTINGS_SaveSettings(void) State[1] = gEeprom.S0_LEVEL; State[2] = gEeprom.S9_LEVEL; #endif - EEPROM_WriteBuffer(0x0EA0, State); - + // 0x0EA8 + State = SecBuf + 0x18; #if defined(ENABLE_ALARM) || defined(ENABLE_TX1750) State[0] = gEeprom.ALARM_MODE; #else @@ -710,8 +748,9 @@ void SETTINGS_SaveSettings(void) State[2] = gEeprom.REPEATER_TAIL_TONE_ELIMINATION; State[3] = gEeprom.TX_VFO; State[4] = gEeprom.BATTERY_TYPE; - EEPROM_WriteBuffer(0x0EA8, State); + // 0x0ED0 + State = SecBuf + 0x40; State[0] = gEeprom.DTMF_SIDE_TONE; #ifdef ENABLE_DTMF_CALLING State[1] = gEeprom.DTMF_SEPARATE_CODE; @@ -722,16 +761,24 @@ void SETTINGS_SaveSettings(void) State[5] = gEeprom.DTMF_PRELOAD_TIME / 10U; State[6] = gEeprom.DTMF_FIRST_CODE_PERSIST_TIME / 10U; State[7] = gEeprom.DTMF_HASH_CODE_PERSIST_TIME / 10U; - EEPROM_WriteBuffer(0x0ED0, State); - memset(State, 0xFF, sizeof(State)); + // 0x0ED8 + State = SecBuf + 0x48; State[0] = gEeprom.DTMF_CODE_PERSIST_TIME / 10U; State[1] = gEeprom.DTMF_CODE_INTERVAL_TIME / 10U; #ifdef ENABLE_DTMF_CALLING State[2] = gEeprom.PERMIT_REMOTE_KILL; #endif - EEPROM_WriteBuffer(0x0ED8, State); + PY25Q16_WriteBuffer(0x007000, SecBuf, 0x50, true); + + // ------------------------- + // 0f18 - 0f20 + + memset(SecBuf, 0xff, 0x8); + + // 0x0F18 + State = SecBuf; State[0] = gEeprom.SCAN_LIST_DEFAULT; tmp = 0; @@ -750,9 +797,16 @@ void SETTINGS_SaveSettings(void) State[5] = gEeprom.SCANLIST_PRIORITY_CH2[1]; State[6] = gEeprom.SCANLIST_PRIORITY_CH1[2]; State[7] = gEeprom.SCANLIST_PRIORITY_CH2[2]; - EEPROM_WriteBuffer(0x0F18, State); - memset(State, 0xFF, sizeof(State)); + PY25Q16_WriteBuffer(0x009000, SecBuf, 8, true); + + // --------------------- + // 0f40 - 0f48 + + memset(SecBuf, 0xff, 8); + + // 0x0F40 + State = SecBuf; State[0] = gSetting_F_LOCK; #ifndef ENABLE_FEAT_F4HWN State[1] = gSetting_350TX; @@ -784,12 +838,15 @@ void SETTINGS_SaveSettings(void) #endif State[7] = (State[7] & ~(3u << 6)) | ((gSetting_backlight_on_tx_rx & 3u) << 6); - EEPROM_WriteBuffer(0x0F40, State); + PY25Q16_WriteBuffer(0x00b000, SecBuf, 8, true); + + // ------------------ #ifdef ENABLE_FEAT_F4HWN // 0x1FF0 + State = SecBuf; // TODO: TBD - PY25Q16_ReadBuffer(0x00c000, State, sizeof(State)); + PY25Q16_ReadBuffer(0x00c000, State, 8); //memset(State, 0xFF, sizeof(State)); @@ -830,7 +887,7 @@ void SETTINGS_SaveSettings(void) gEeprom.KEY_LOCK_PTT = gSetting_set_lck; - EEPROM_WriteBuffer(0x1FF0, State); + PY25Q16_WriteBuffer(0x00c000, SecBuf, 8, true); #endif #ifdef ENABLE_FEAT_F4HWN_VOL @@ -845,45 +902,53 @@ void SETTINGS_SaveChannel(uint8_t Channel, uint8_t VFO, const VFO_Info_t *pVFO, return; #endif - uint16_t OffsetVFO = Channel * 16; + // 0 + uint16_t OffsetVFO = 0 + Channel * 16; if (IS_FREQ_CHANNEL(Channel)) { // it's a VFO, not a channel - OffsetVFO = (VFO == 0) ? 0x0C80 : 0x0C90; + // 0x0C80 + OffsetVFO = (VFO == 0) ? 0x001000 : 0x001010; OffsetVFO += (Channel - FREQ_CHANNEL_FIRST) * 32; } if (Mode >= 2 || IS_FREQ_CHANNEL(Channel)) { // copy VFO to a channel - union { + typedef union { uint8_t _8[8]; uint32_t _32[2]; - } State; + } State_t; + + State_t *State; - State._32[0] = pVFO->freq_config_RX.Frequency; - State._32[1] = pVFO->TX_OFFSET_FREQUENCY; - EEPROM_WriteBuffer(OffsetVFO + 0, State._32); + uint8_t Buf[0x10]; - State._8[0] = pVFO->freq_config_RX.Code; - State._8[1] = pVFO->freq_config_TX.Code; - State._8[2] = (pVFO->freq_config_TX.CodeType << 4) | pVFO->freq_config_RX.CodeType; - State._8[3] = (pVFO->Modulation << 4) | pVFO->TX_OFFSET_FREQUENCY_DIRECTION; - State._8[4] = 0 + State = (State_t *)Buf; + State -> _32[0] = pVFO->freq_config_RX.Frequency; + State -> _32[1] = pVFO->TX_OFFSET_FREQUENCY; + + State = (State_t *)(Buf + 0x8); + State -> _8[0] = pVFO->freq_config_RX.Code; + State -> _8[1] = pVFO->freq_config_TX.Code; + State -> _8[2] = (pVFO->freq_config_TX.CodeType << 4) | pVFO->freq_config_RX.CodeType; + State -> _8[3] = (pVFO->Modulation << 4) | pVFO->TX_OFFSET_FREQUENCY_DIRECTION; + State -> _8[4] = 0 | (pVFO->TX_LOCK << 6) | (pVFO->BUSY_CHANNEL_LOCK << 5) | (pVFO->OUTPUT_POWER << 2) | (pVFO->CHANNEL_BANDWIDTH << 1) | (pVFO->FrequencyReverse << 0); - State._8[5] = ((pVFO->DTMF_PTT_ID_TX_MODE & 7u) << 1) + State -> _8[5] = ((pVFO->DTMF_PTT_ID_TX_MODE & 7u) << 1) #ifdef ENABLE_DTMF_CALLING | ((pVFO->DTMF_DECODING_ENABLE & 1u) << 0) #endif ; - State._8[6] = pVFO->STEP_SETTING; + State -> _8[6] = pVFO->STEP_SETTING; #ifdef ENABLE_FEAT_F4HWN - State._8[7] = 0; + State -> _8[7] = 0; #else - State._8[7] = pVFO->SCRAMBLING_TYPE; + State -> _8[7] = pVFO->SCRAMBLING_TYPE; #endif - EEPROM_WriteBuffer(OffsetVFO + 8, State._8); + + PY25Q16_WriteBuffer(OffsetVFO, Buf, 0x10, false); SETTINGS_UpdateChannel(Channel, pVFO, true, true, true); @@ -903,13 +968,14 @@ void SETTINGS_SaveChannel(uint8_t Channel, uint8_t VFO, const VFO_Info_t *pVFO, void SETTINGS_SaveBatteryCalibration(const uint16_t * batteryCalibration) { - uint16_t buf[4]; - EEPROM_WriteBuffer(0x1F40, batteryCalibration); + uint16_t buf[8]; + memcpy(buf, batteryCalibration, 8); // 0x1F48 - PY25Q16_ReadBuffer(0x010000 + 0x148, buf, sizeof(buf)); + PY25Q16_ReadBuffer(0x010000 + 0x148, buf + 4, 8); buf[0] = batteryCalibration[4]; buf[1] = batteryCalibration[5]; - EEPROM_WriteBuffer(0x1F48, buf); + // 0x1F40 + PY25Q16_WriteBuffer(0x010000, buf, 0x10, false); } void SETTINGS_SaveChannelName(uint8_t channel, const char * name) @@ -917,8 +983,8 @@ void SETTINGS_SaveChannelName(uint8_t channel, const char * name) uint16_t offset = channel * 16; uint8_t buf[16] = {0}; memcpy(buf, name, MIN(strlen(name), 10u)); - EEPROM_WriteBuffer(0x0F50 + offset, buf); - EEPROM_WriteBuffer(0x0F58 + offset, buf + 8); + // 0x0F50 + PY25Q16_WriteBuffer(0x00e000 + offset, buf, 0x10, false); } void SETTINGS_UpdateChannel(uint8_t channel, const VFO_Info_t *pVFO, bool keep, bool check, bool save) @@ -927,7 +993,7 @@ void SETTINGS_UpdateChannel(uint8_t channel, const VFO_Info_t *pVFO, bool keep, if (!IS_NOAA_CHANNEL(channel)) #endif { - uint8_t state[8]; + ChannelAttributes_t state; ChannelAttributes_t att = { .band = 0x7, .compander = 0, @@ -937,8 +1003,7 @@ void SETTINGS_UpdateChannel(uint8_t channel, const VFO_Info_t *pVFO, bool keep, }; // default attributes // 0x0D60 - uint16_t offset = 0x002000 + (channel & ~7u); - PY25Q16_ReadBuffer(offset, state, sizeof(state)); + PY25Q16_ReadBuffer(0x002000 + channel, &state, 1); if (keep) { att.band = pVFO->Band; @@ -946,20 +1011,22 @@ void SETTINGS_UpdateChannel(uint8_t channel, const VFO_Info_t *pVFO, bool keep, att.scanlist2 = pVFO->SCANLIST2_PARTICIPATION; att.scanlist3 = pVFO->SCANLIST3_PARTICIPATION; att.compander = pVFO->Compander; - if (check && state[channel & 7u] == att.__val) + if (check && state.__val == att.__val) return; // no change in the attributes } - state[channel & 7u] = att.__val; + state.__val = att.__val; -#ifdef ENABLE_FEAT_F4HWN +#ifndef ENABLE_FEAT_F4HWN + save = true; +#endif if(save) { - EEPROM_WriteBuffer(offset, state); + uint8_t buf[224]; + PY25Q16_ReadBuffer(0x002000, buf, sizeof(buf)); + buf[channel] = state.__val; + PY25Q16_WriteBuffer(0x002000, buf, sizeof(buf), true); } -#else - EEPROM_WriteBuffer(offset, state); -#endif gMR_ChannelAttributes[channel] = att; @@ -1031,18 +1098,18 @@ State[1] = 0 | (1 << 6) #endif ; - EEPROM_WriteBuffer(0x1FF0, State); + PY25Q16_WriteBuffer(0x00c000, State, sizeof(State), true); } #ifdef ENABLE_FEAT_F4HWN_RESUME_STATE void SETTINGS_WriteCurrentState(void) { - uint8_t State[8]; + uint8_t State[0x10]; // 0x0E78 - PY25Q16_ReadBuffer(0x004000 + 0x8, State, sizeof(State)); - //State[3] = (gEeprom.CURRENT_STATE << 4) | (gEeprom.BATTERY_SAVE & 0x0F); - State[7] = (gEeprom.VFO_OPEN & 0x01) | ((gEeprom.CURRENT_STATE & 0x07) << 1) | ((gEeprom.SCAN_LIST_DEFAULT & 0x07) << 4); - EEPROM_WriteBuffer(0x0E78, State); + PY25Q16_ReadBuffer(0x004000, State, sizeof(State)); + //State[11] = (gEeprom.CURRENT_STATE << 4) | (gEeprom.BATTERY_SAVE & 0x0F); + State[15] = (gEeprom.VFO_OPEN & 0x01) | ((gEeprom.CURRENT_STATE & 0x07) << 1) | ((gEeprom.SCAN_LIST_DEFAULT & 0x07) << 4); + PY25Q16_WriteBuffer(0x004000, State, sizeof(State), true); } #endif @@ -1053,20 +1120,37 @@ State[1] = 0 // 0x1F88 PY25Q16_ReadBuffer(0x010000 + 0x188, State, sizeof(State)); State[6] = gEeprom.VOLUME_GAIN; - EEPROM_WriteBuffer(0x1F88, State); + PY25Q16_WriteBuffer(0x010000 + 0x188, State, sizeof(State), false); } #endif #ifdef ENABLE_FEAT_F4HWN void SETTINGS_ResetTxLock(void) { - uint8_t State[8]; - for(uint8_t channel = 0; channel < 200; channel++) + // TODO: This is expensive operation! + +#define SETTINGS_ResetTxLock_BATCH 10 + + uint8_t Buf[0xc80 / SETTINGS_ResetTxLock_BATCH]; + const uint32_t BatchSize = 0xc80 / SETTINGS_ResetTxLock_BATCH; + const uint32_t BatchChCnt = BatchSize / 0x10; + + for (uint32_t i = 0; i < SETTINGS_ResetTxLock_BATCH; i++) { - uint16_t OffsetVFO = channel * 16; - PY25Q16_ReadBuffer(OffsetVFO + 8, State, sizeof(State)); - State[4] |= (1 << 6); - EEPROM_WriteBuffer(OffsetVFO + 8, State); + uint32_t Offset = i * BatchSize; + PY25Q16_ReadBuffer(0 + Offset, Buf, sizeof(Buf)); + + uint8_t *State; + for (uint8_t channel = 0; channel < BatchChCnt; channel++) + { + uint16_t OffsetVFO = channel * 16; + State = Buf + OffsetVFO; + State[4] |= (1 << 6); + } + + PY25Q16_WriteBuffer(0 + Offset, Buf, sizeof(Buf), false); } + +#undef SETTINGS_ResetTxLock_BATCH } #endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ccc5647..81e336e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,12 +50,14 @@ else() endif() endif() -if(${TARGET}) +if(TARGET) set(EXE_NAME ${TARGET}) else() set(EXE_NAME "firmware") endif() +message("EXE_NAME = " ${EXE_NAME}) + add_executable(${EXE_NAME}) add_subdirectory(Drivers) diff --git a/CMakePresets.json b/CMakePresets.json index 0055df94..e3ee6736 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -75,6 +75,7 @@ "name": "Custom", "inherits": "Broadcast", "cacheVariables": { + "ENABLE_VOICE": false, "ENABLE_SWD": true, "ENABLE_AIRCOPY": false }