feat(boot): 添加SPI OTA功能并更新配置

- 添加SpiOta.h和SpiOta.cpp文件以实现SPI OTA功能
- 在项目配置中添加SPI_I2S外设组件支持
- 设置外部晶振值为8MHz (HXTAL_VALUE=8000000U)
- 调整ROM0大小从512KB减小到2KB以适应引导程序需求
- 移除评估板相关文件和看门狗驱动文件以优化存储空间
This commit is contained in:
cbb
2026-05-25 01:33:00 +08:00
parent 77fc3f44b0
commit 747fc56886
65 changed files with 4408 additions and 30057 deletions
+6 -1
View File
@@ -21,6 +21,8 @@ project:
files:
- file: ./device/Can.h
- file: ./device/Can.cpp
- file: ./device/SpiOta.h
- file: ./device/SpiOta.cpp
output:
type:
@@ -46,12 +48,14 @@ project:
- component: Device:Startup
- component: Device:GD32E50x_StdPeripherals:GPIO
- component: Device:GD32E50x_StdPeripherals:EXTI
- component: Device:GD32E50x_StdPeripherals:SPI_I2S
misc:
- for-compiler: AC6
C-CPP:
- -DGD32E508
- -DGD32E50X
- -DHXTAL_VALUE=8000000U
- -gdwarf-5
- -D__MICROLIB
- -fshort-enums
@@ -64,8 +68,9 @@ project:
ASM:
- -DGD32E508
- -DGD32E50X
- -DHXTAL_VALUE=8000000U
- -gdwarf-5
- -D__MICROLIB
Link:
- --library_type=microlib
- --entry=Reset_Handler
- --entry=Reset_Handler
@@ -1,239 +0,0 @@
/*!
\file gd32e503z_eval.c
\brief firmware functions to manage leds, keys, COM ports
\version 2025-07-09, V1.6.1, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e503z_eval.h"
/* private variables */
static uint32_t GPIO_PORT[LEDn] = {LED1_GPIO_PORT, LED2_GPIO_PORT,
LED3_GPIO_PORT, LED4_GPIO_PORT};
static uint32_t GPIO_PIN[LEDn] = {LED1_PIN, LED2_PIN, LED3_PIN, LED4_PIN};
static rcu_periph_enum COM_CLK[COMn] = {EVAL_COM0_CLK};
static uint32_t COM_TX_PIN[COMn] = {EVAL_COM0_TX_PIN};
static uint32_t COM_RX_PIN[COMn] = {EVAL_COM0_RX_PIN};
static uint32_t COM_GPIO_PORT[COMn] = {EVAL_COM0_GPIO_PORT};
static rcu_periph_enum COM_GPIO_CLK[COMn] = {EVAL_COM0_GPIO_CLK};
static rcu_periph_enum GPIO_CLK[LEDn] = {LED1_GPIO_CLK, LED2_GPIO_CLK,
LED3_GPIO_CLK, LED4_GPIO_CLK};
static uint32_t KEY_PORT[KEYn] = {KEY_A_GPIO_PORT,
KEY_B_GPIO_PORT,
KEY_C_GPIO_PORT,
KEY_D_GPIO_PORT,
KEY_CET_GPIO_PORT};
static uint32_t KEY_PIN[KEYn] = {KEY_A_PIN,
KEY_B_PIN,
KEY_C_PIN,
KEY_D_PIN,
KEY_CET_PIN};
static rcu_periph_enum KEY_CLK[KEYn] = {KEY_A_GPIO_CLK,
KEY_B_GPIO_CLK,
KEY_C_GPIO_CLK,
KEY_D_GPIO_CLK,
KEY_CET_GPIO_CLK};
static exti_line_enum KEY_EXTI_LINE[KEYn] = {KEY_A_EXTI_LINE,
KEY_B_EXTI_LINE,
KEY_C_EXTI_LINE,
KEY_D_EXTI_LINE,
KEY_CET_EXTI_LINE};
static uint8_t KEY_PORT_SOURCE[KEYn] = {KEY_A_EXTI_PORT_SOURCE,
KEY_B_EXTI_PORT_SOURCE,
KEY_C_EXTI_PORT_SOURCE,
KEY_D_EXTI_PORT_SOURCE,
KEY_CET_EXTI_PORT_SOURCE};
static uint8_t KEY_PIN_SOURCE[KEYn] = {KEY_A_EXTI_PIN_SOURCE,
KEY_B_EXTI_PIN_SOURCE,
KEY_C_EXTI_PIN_SOURCE,
KEY_D_EXTI_PIN_SOURCE,
KEY_CET_EXTI_PIN_SOURCE};
static const IRQn_Type KEY_IRQn[KEYn] = {KEY_A_EXTI_IRQn,
KEY_B_EXTI_IRQn,
KEY_C_EXTI_IRQn,
KEY_D_EXTI_IRQn,
KEY_CET_EXTI_IRQn};
/*!
\brief configure led GPIO
\param[in] lednum: specify the led to be configured
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_init (led_typedef_enum lednum)
{
/* enable the led clock */
rcu_periph_clock_enable(GPIO_CLK[lednum]);
/* configure led GPIO port */
gpio_init(GPIO_PORT[lednum], GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN[lednum]);
GPIO_BC(GPIO_PORT[lednum]) = GPIO_PIN[lednum];
}
/*!
\brief turn on selected led
\param[in] lednum: specify the led to be turned on
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_on(led_typedef_enum lednum)
{
GPIO_BOP(GPIO_PORT[lednum]) = GPIO_PIN[lednum];
}
/*!
\brief turn off selected led
\param[in] lednum: specify the led to be turned off
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_off(led_typedef_enum lednum)
{
GPIO_BC(GPIO_PORT[lednum]) = GPIO_PIN[lednum];
}
/*!
\brief toggle selected led
\param[in] lednum: specify the led to be toggled
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_toggle(led_typedef_enum lednum)
{
gpio_bit_write(GPIO_PORT[lednum], GPIO_PIN[lednum],
(bit_status)(1-gpio_input_bit_get(GPIO_PORT[lednum], GPIO_PIN[lednum])));
}
/*!
\brief configure key
\param[in] key_num: specify the key to be configured
\arg KEY_A: wakeup key
\arg KEY_B: tamper key
\arg KEY_C: user key
\arg KEY_D: extension key
\arg KEY_CET: extension key
\param[in] key_mode: specify button mode
\arg KEY_MODE_GPIO: key will be used as simple IO
\arg KEY_MODE_EXTI: key will be connected to EXTI line with interrupt
\param[out] none
\retval none
*/
void gd_eval_key_init(key_typedef_enum key_num, keymode_typedef_enum key_mode)
{
/* enable the key clock */
rcu_periph_clock_enable(KEY_CLK[key_num]);
rcu_periph_clock_enable(RCU_AF);
/* configure button pin as input */
gpio_init(KEY_PORT[key_num], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, KEY_PIN[key_num]);
if(key_mode == KEY_MODE_EXTI) {
/* enable and set key EXTI interrupt to the lowest priority */
nvic_irq_enable(KEY_IRQn[key_num], 2U, 0U);
/* connect key EXTI line to key GPIO pin */
gpio_exti_source_select(KEY_PORT_SOURCE[key_num], KEY_PIN_SOURCE[key_num]);
/* configure key EXTI line */
exti_init(KEY_EXTI_LINE[key_num], EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_flag_clear(KEY_EXTI_LINE[key_num]);
}
}
/*!
\brief return the selected key state
\param[in] key: specify the key to be checked
\arg KEY_A: wakeup key
\arg KEY_B: tamper key
\arg KEY_C: user key
\arg KEY_D: extension key
\arg KEY_CET: extension key
\param[out] none
\retval the key's GPIO pin value
*/
uint8_t gd_eval_key_state_get(key_typedef_enum key)
{
return gpio_input_bit_get(KEY_PORT[key], KEY_PIN[key]);
}
/*!
\brief configure COM port
\param[in] com: COM on the board
\arg EVAL_COM0: COM0 on the board
\param[out] none
\retval none
*/
void gd_eval_com_init(uint32_t com)
{
uint32_t com_id = 0U;
if(EVAL_COM0 == com){
com_id = 0U;
}else{
while(1);
}
/* enable GPIO clock */
rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);
/* enable USART clock */
rcu_periph_clock_enable(COM_CLK[com_id]);
/* connect port to USARTx_Tx */
gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, COM_TX_PIN[com_id]);
/* connect port to USARTx_Rx */
gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, COM_RX_PIN[com_id]);
/* USART configure */
usart_deinit(com);
usart_baudrate_set(com, 115200U);
usart_receive_config(com, USART_RECEIVE_ENABLE);
usart_transmit_config(com, USART_TRANSMIT_ENABLE);
usart_enable(com);
}
@@ -1,239 +0,0 @@
/*!
\file gd32e503z_eval.c
\brief firmware functions to manage leds, keys, COM ports
\version 2025-07-09, V1.6.1, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e503z_eval.h"
/* private variables */
static uint32_t GPIO_PORT[LEDn] = {LED1_GPIO_PORT, LED2_GPIO_PORT,
LED3_GPIO_PORT, LED4_GPIO_PORT};
static uint32_t GPIO_PIN[LEDn] = {LED1_PIN, LED2_PIN, LED3_PIN, LED4_PIN};
static rcu_periph_enum COM_CLK[COMn] = {EVAL_COM0_CLK};
static uint32_t COM_TX_PIN[COMn] = {EVAL_COM0_TX_PIN};
static uint32_t COM_RX_PIN[COMn] = {EVAL_COM0_RX_PIN};
static uint32_t COM_GPIO_PORT[COMn] = {EVAL_COM0_GPIO_PORT};
static rcu_periph_enum COM_GPIO_CLK[COMn] = {EVAL_COM0_GPIO_CLK};
static rcu_periph_enum GPIO_CLK[LEDn] = {LED1_GPIO_CLK, LED2_GPIO_CLK,
LED3_GPIO_CLK, LED4_GPIO_CLK};
static uint32_t KEY_PORT[KEYn] = {KEY_A_GPIO_PORT,
KEY_B_GPIO_PORT,
KEY_C_GPIO_PORT,
KEY_D_GPIO_PORT,
KEY_CET_GPIO_PORT};
static uint32_t KEY_PIN[KEYn] = {KEY_A_PIN,
KEY_B_PIN,
KEY_C_PIN,
KEY_D_PIN,
KEY_CET_PIN};
static rcu_periph_enum KEY_CLK[KEYn] = {KEY_A_GPIO_CLK,
KEY_B_GPIO_CLK,
KEY_C_GPIO_CLK,
KEY_D_GPIO_CLK,
KEY_CET_GPIO_CLK};
static exti_line_enum KEY_EXTI_LINE[KEYn] = {KEY_A_EXTI_LINE,
KEY_B_EXTI_LINE,
KEY_C_EXTI_LINE,
KEY_D_EXTI_LINE,
KEY_CET_EXTI_LINE};
static uint8_t KEY_PORT_SOURCE[KEYn] = {KEY_A_EXTI_PORT_SOURCE,
KEY_B_EXTI_PORT_SOURCE,
KEY_C_EXTI_PORT_SOURCE,
KEY_D_EXTI_PORT_SOURCE,
KEY_CET_EXTI_PORT_SOURCE};
static uint8_t KEY_PIN_SOURCE[KEYn] = {KEY_A_EXTI_PIN_SOURCE,
KEY_B_EXTI_PIN_SOURCE,
KEY_C_EXTI_PIN_SOURCE,
KEY_D_EXTI_PIN_SOURCE,
KEY_CET_EXTI_PIN_SOURCE};
static const IRQn_Type KEY_IRQn[KEYn] = {KEY_A_EXTI_IRQn,
KEY_B_EXTI_IRQn,
KEY_C_EXTI_IRQn,
KEY_D_EXTI_IRQn,
KEY_CET_EXTI_IRQn};
/*!
\brief configure led GPIO
\param[in] lednum: specify the led to be configured
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_init (led_typedef_enum lednum)
{
/* enable the led clock */
rcu_periph_clock_enable(GPIO_CLK[lednum]);
/* configure led GPIO port */
gpio_init(GPIO_PORT[lednum], GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN[lednum]);
GPIO_BC(GPIO_PORT[lednum]) = GPIO_PIN[lednum];
}
/*!
\brief turn on selected led
\param[in] lednum: specify the led to be turned on
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_on(led_typedef_enum lednum)
{
GPIO_BOP(GPIO_PORT[lednum]) = GPIO_PIN[lednum];
}
/*!
\brief turn off selected led
\param[in] lednum: specify the led to be turned off
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_off(led_typedef_enum lednum)
{
GPIO_BC(GPIO_PORT[lednum]) = GPIO_PIN[lednum];
}
/*!
\brief toggle selected led
\param[in] lednum: specify the led to be toggled
\arg LED1
\arg LED2
\arg LED3
\arg LED4
\param[out] none
\retval none
*/
void gd_eval_led_toggle(led_typedef_enum lednum)
{
gpio_bit_write(GPIO_PORT[lednum], GPIO_PIN[lednum],
(bit_status)(1-gpio_input_bit_get(GPIO_PORT[lednum], GPIO_PIN[lednum])));
}
/*!
\brief configure key
\param[in] key_num: specify the key to be configured
\arg KEY_A: wakeup key
\arg KEY_B: tamper key
\arg KEY_C: user key
\arg KEY_D: extension key
\arg KEY_CET: extension key
\param[in] key_mode: specify button mode
\arg KEY_MODE_GPIO: key will be used as simple IO
\arg KEY_MODE_EXTI: key will be connected to EXTI line with interrupt
\param[out] none
\retval none
*/
void gd_eval_key_init(key_typedef_enum key_num, keymode_typedef_enum key_mode)
{
/* enable the key clock */
rcu_periph_clock_enable(KEY_CLK[key_num]);
rcu_periph_clock_enable(RCU_AF);
/* configure button pin as input */
gpio_init(KEY_PORT[key_num], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, KEY_PIN[key_num]);
if(key_mode == KEY_MODE_EXTI) {
/* enable and set key EXTI interrupt to the lowest priority */
nvic_irq_enable(KEY_IRQn[key_num], 2U, 0U);
/* connect key EXTI line to key GPIO pin */
gpio_exti_source_select(KEY_PORT_SOURCE[key_num], KEY_PIN_SOURCE[key_num]);
/* configure key EXTI line */
exti_init(KEY_EXTI_LINE[key_num], EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_flag_clear(KEY_EXTI_LINE[key_num]);
}
}
/*!
\brief return the selected key state
\param[in] key: specify the key to be checked
\arg KEY_A: wakeup key
\arg KEY_B: tamper key
\arg KEY_C: user key
\arg KEY_D: extension key
\arg KEY_CET: extension key
\param[out] none
\retval the key's GPIO pin value
*/
uint8_t gd_eval_key_state_get(key_typedef_enum key)
{
return gpio_input_bit_get(KEY_PORT[key], KEY_PIN[key]);
}
/*!
\brief configure COM port
\param[in] com: COM on the board
\arg EVAL_COM0: COM0 on the board
\param[out] none
\retval none
*/
void gd_eval_com_init(uint32_t com)
{
uint32_t com_id = 0U;
if(EVAL_COM0 == com){
com_id = 0U;
}else{
while(1);
}
/* enable GPIO clock */
rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);
/* enable USART clock */
rcu_periph_clock_enable(COM_CLK[com_id]);
/* connect port to USARTx_Tx */
gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, COM_TX_PIN[com_id]);
/* connect port to USARTx_Rx */
gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, COM_RX_PIN[com_id]);
/* USART configure */
usart_deinit(com);
usart_baudrate_set(com, 115200U);
usart_receive_config(com, USART_RECEIVE_ENABLE);
usart_transmit_config(com, USART_TRANSMIT_ENABLE);
usart_enable(com);
}
@@ -1,227 +0,0 @@
/*!
\file gd32e50x_fwdgt.c
\brief FWDGT driver
\version 2026-02-09, V1.7.0, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e50x_fwdgt.h"
/*!
\brief enable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_enable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
}
/*!
\brief disable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_disable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_DISABLE;
}
/*!
\brief start the free watchdog timer counter
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_enable(void)
{
FWDGT_CTL = FWDGT_KEY_ENABLE;
}
/*!
\brief configure the free watchdog timer counter prescaler value
\param[in] prescaler_value: specify prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_prescaler_value_config(uint16_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_PSC */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief configure the free watchdog timer counter reload value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_reload_value_config(uint16_t reload_value)
{
uint32_t timeout = FWDGT_RLD_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief reload the counter of FWDGT
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_counter_reload(void)
{
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
/*!
\brief configure counter reload value, and prescaler divider value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[in] prescaler_value: FWDGT prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_config(uint16_t reload_value, uint8_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* start the free watchdog timer counter */
FWDGT_CTL = FWDGT_KEY_ENABLE;
/* enable write access to FWDGT_PSC,and FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT PSC */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
if(SUCCESS == status) {
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
timeout = FWDGT_RLD_TIMEOUT;
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
}
if(SUCCESS == status) {
/* reload the counter */
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
return status;
}
/*!
\brief get flag state of FWDGT
\param[in] flag: flag to get
only one parameter can be selected which is shown as below:
\arg FWDGT_FLAG_PUD: a write operation to FWDGT_PSC register is on going
\arg FWDGT_FLAG_RUD: a write operation to FWDGT_RLD register is on going
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus fwdgt_flag_get(uint16_t flag)
{
FlagStatus flag_status = RESET;
if(FWDGT_STAT & flag) {
flag_status = SET;
}
return flag_status;
}
@@ -1,227 +0,0 @@
/*!
\file gd32e50x_fwdgt.c
\brief FWDGT driver
\version 2026-02-09, V1.7.0, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e50x_fwdgt.h"
/*!
\brief enable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_enable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
}
/*!
\brief disable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_disable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_DISABLE;
}
/*!
\brief start the free watchdog timer counter
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_enable(void)
{
FWDGT_CTL = FWDGT_KEY_ENABLE;
}
/*!
\brief configure the free watchdog timer counter prescaler value
\param[in] prescaler_value: specify prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_prescaler_value_config(uint16_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_PSC */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief configure the free watchdog timer counter reload value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_reload_value_config(uint16_t reload_value)
{
uint32_t timeout = FWDGT_RLD_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief reload the counter of FWDGT
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_counter_reload(void)
{
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
/*!
\brief configure counter reload value, and prescaler divider value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[in] prescaler_value: FWDGT prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_config(uint16_t reload_value, uint8_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* start the free watchdog timer counter */
FWDGT_CTL = FWDGT_KEY_ENABLE;
/* enable write access to FWDGT_PSC,and FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT PSC */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
if(SUCCESS == status) {
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
timeout = FWDGT_RLD_TIMEOUT;
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
}
if(SUCCESS == status) {
/* reload the counter */
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
return status;
}
/*!
\brief get flag state of FWDGT
\param[in] flag: flag to get
only one parameter can be selected which is shown as below:
\arg FWDGT_FLAG_PUD: a write operation to FWDGT_PSC register is on going
\arg FWDGT_FLAG_RUD: a write operation to FWDGT_RLD register is on going
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus fwdgt_flag_get(uint16_t flag)
{
FlagStatus flag_status = RESET;
if(FWDGT_STAT & flag) {
flag_status = SET;
}
return flag_status;
}
@@ -0,0 +1,895 @@
/*!
\file gd32e50x_spi.c
\brief SPI driver
\version 2026-02-09, V1.7.0, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e50x_spi.h"
/* SPI/I2S parameter initialization mask */
#define SPI_INIT_MASK ((uint32_t)0x00003040U) /*!< SPI parameter initialization mask */
#define I2S_INIT_MASK ((uint32_t)0x0000F047U) /*!< I2S parameter initialization mask */
#define I2S_FULL_DUPLEX_MASK ((uint32_t)0x00000480U) /*!< I2S full duples mode configure parameter initialization mask */
/* default value */
#define SPI_I2SPSC_DEFAULT_VALUE ((uint32_t)0x00000002U) /*!< default value of SPI_I2SPSC register */
/* I2S clock source selection, multiplication and division mask */
#define I2S1_CLOCK_SEL ((uint32_t)0x00020000U) /*!< I2S1 clock source selection */
#define I2S2_CLOCK_SEL ((uint32_t)0x00040000U) /*!< I2S2 clock source selection */
#define I2S_CLOCK_MUL_MASK ((uint32_t)0x0000F000U) /*!< I2S clock multiplication mask */
#define I2S_CLOCK_DIV_MASK ((uint32_t)0x000000F0U) /*!< I2S clock division mask */
/*!
\brief reset SPI and I2S
\param[in] spi_periph: SPIx(x=0,1,2),include I2S1_ADD and I2S2_ADD
\param[out] none
\retval none
*/
void spi_i2s_deinit(uint32_t spi_periph)
{
switch(spi_periph){
case SPI0:
/* reset SPI0 */
rcu_periph_reset_enable(RCU_SPI0RST);
rcu_periph_reset_disable(RCU_SPI0RST);
break;
case SPI1:
/* reset SPI1 , I2S1 and I2S1_ADD */
rcu_periph_reset_enable(RCU_SPI1RST);
rcu_periph_reset_disable(RCU_SPI1RST);
break;
case SPI2:
/* reset SPI2 , I2S2 and I2S2_ADD */
rcu_periph_reset_enable(RCU_SPI2RST);
rcu_periph_reset_disable(RCU_SPI2RST);
break;
default :
break;
}
}
/*!
\brief initialize the parameters of SPI struct with default values
\param[in] none
\param[out] spi_parameter_struct: the initialized struct spi_parameter_struct pointer
\retval none
*/
void spi_struct_para_init(spi_parameter_struct *spi_struct)
{
/* configure the structure with default value */
spi_struct->device_mode = SPI_SLAVE;
spi_struct->trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_struct->frame_size = SPI_FRAMESIZE_8BIT;
spi_struct->nss = SPI_NSS_HARD;
spi_struct->clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_struct->prescale = SPI_PSC_2;
spi_struct->endian = SPI_ENDIAN_MSB;
}
/*!
\brief initialize SPI parameter
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] spi_struct: SPI parameter initialization stuct members of the structure
and the member values are shown as below:
device_mode: SPI_MASTER, SPI_SLAVE
trans_mode: SPI_TRANSMODE_FULLDUPLEX, SPI_TRANSMODE_RECEIVEONLY,
SPI_TRANSMODE_BDRECEIVE, SPI_TRANSMODE_BDTRANSMIT
frame_size: SPI_FRAMESIZE_16BIT, SPI_FRAMESIZE_8BIT
nss: SPI_NSS_SOFT, SPI_NSS_HARD
endian: SPI_ENDIAN_MSB, SPI_ENDIAN_LSB
clock_polarity_phase: SPI_CK_PL_LOW_PH_1EDGE, SPI_CK_PL_HIGH_PH_1EDGE
SPI_CK_PL_LOW_PH_2EDGE, SPI_CK_PL_HIGH_PH_2EDGE
prescale: SPI_PSC_n (n=2,4,8,16,32,64,128,256)
\param[out] none
\retval none
*/
void spi_init(uint32_t spi_periph, spi_parameter_struct* spi_struct)
{
uint32_t reg = 0U;
reg = SPI_CTL0(spi_periph);
reg &= SPI_INIT_MASK;
/* select SPI as master or slave */
reg |= spi_struct->device_mode;
/* select SPI transfer mode */
reg |= spi_struct->trans_mode;
/* select SPI frame size */
reg |= spi_struct->frame_size;
/* select SPI NSS use hardware or software */
reg |= spi_struct->nss;
/* select SPI LSB or MSB */
reg |= spi_struct->endian;
/* select SPI polarity and phase */
reg |= spi_struct->clock_polarity_phase;
/* select SPI prescale to adjust transmit speed */
reg |= spi_struct->prescale;
/* write to SPI_CTL0 register */
SPI_CTL0(spi_periph) = (uint32_t)reg;
SPI_I2SCTL(spi_periph) &= (uint32_t)(~SPI_I2SCTL_I2SSEL);
}
/*!
\brief enable SPI
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_enable(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_SPIEN;
}
/*!
\brief disable SPI
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_disable(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_SPIEN);
}
/*!
\brief initialize I2S parameter
\param[in] spi_periph: SPIx(x=1,2)
\param[in] i2s_mode: I2S operation mode
only one parameter can be selected which is shown as below:
\arg I2S_MODE_SLAVETX: I2S slave transmit mode
\arg I2S_MODE_SLAVERX: I2S slave receive mode
\arg I2S_MODE_MASTERTX: I2S master transmit mode
\arg I2S_MODE_MASTERRX: I2S master receive mode
\param[in] i2s_standard: I2S standard
only one parameter can be selected which is shown as below:
\arg I2S_STD_PHILIPS: I2S philips standard
\arg I2S_STD_MSB: I2S MSB standard
\arg I2S_STD_LSB: I2S LSB standard
\arg I2S_STD_PCMSHORT: I2S PCM short standard
\arg I2S_STD_PCMLONG: I2S PCM long standard
\param[in] i2s_ckpl: I2S idle state clock polarity
only one parameter can be selected which is shown as below:
\arg I2S_CKPL_LOW: I2S clock polarity low level
\arg I2S_CKPL_HIGH: I2S clock polarity high level
\param[out] none
\retval none
*/
void i2s_init(uint32_t spi_periph, uint32_t i2s_mode, uint32_t i2s_standard, uint32_t i2s_ckpl)
{
uint32_t reg= 0U;
reg = SPI_I2SCTL(spi_periph);
reg &= I2S_INIT_MASK;
/* enable I2S mode */
reg |= (uint32_t)SPI_I2SCTL_I2SSEL;
/* select I2S mode */
reg |= (uint32_t)i2s_mode;
/* select I2S standard */
reg |= (uint32_t)i2s_standard;
/* select I2S polarity */
reg |= (uint32_t)i2s_ckpl;
/* write to SPI_I2SCTL register */
SPI_I2SCTL(spi_periph) = (uint32_t)reg;
}
/*!
\brief configure I2S prescaler
\param[in] spi_periph: SPIx(x=1,2)
\param[in] i2s_audiosample: I2S audio sample rate
only one parameter can be selected which is shown as below:
\arg I2S_AUDIOSAMPLE_8K: audio sample rate is 8KHz
\arg I2S_AUDIOSAMPLE_11K: audio sample rate is 11KHz
\arg I2S_AUDIOSAMPLE_16K: audio sample rate is 16KHz
\arg I2S_AUDIOSAMPLE_22K: audio sample rate is 22KHz
\arg I2S_AUDIOSAMPLE_32K: audio sample rate is 32KHz
\arg I2S_AUDIOSAMPLE_44K: audio sample rate is 44KHz
\arg I2S_AUDIOSAMPLE_48K: audio sample rate is 48KHz
\arg I2S_AUDIOSAMPLE_96K: audio sample rate is 96KHz
\arg I2S_AUDIOSAMPLE_192K: audio sample rate is 192KHz
\param[in] i2s_frameformat: I2S data length and channel length
only one parameter can be selected which is shown as below:
\arg I2S_FRAMEFORMAT_DT16B_CH16B: I2S data length is 16 bit and channel length is 16 bit
\arg I2S_FRAMEFORMAT_DT16B_CH32B: I2S data length is 16 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT24B_CH32B: I2S data length is 24 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT32B_CH32B: I2S data length is 32 bit and channel length is 32 bit
\param[in] i2s_mckout: I2S master clock output
only one parameter can be selected which is shown as below:
\arg I2S_MCKOUT_ENABLE: I2S master clock output enable
\arg I2S_MCKOUT_DISABLE: I2S master clock output disable
\param[out] none
\retval none
*/
void i2s_psc_config(uint32_t spi_periph, uint32_t i2s_audiosample, uint32_t i2s_frameformat, uint32_t i2s_mckout)
{
uint32_t i2sdiv = 2U, i2sof = 0U;
uint32_t clks = 0U;
uint32_t i2sclock = 0U;
#ifdef GD32E50X_CL
uint32_t pll2mf_4 = 0U;
#endif /* GD32E50X_CL */
/* deinit SPI_I2SPSC register */
SPI_I2SPSC(spi_periph) = SPI_I2SPSC_DEFAULT_VALUE;
#ifdef GD32E50X_CL
/* get the I2S clock source */
if(((uint32_t)spi_periph) == SPI1){
/* I2S1 clock source selection */
clks = I2S1_CLOCK_SEL;
}else{
/* I2S2 clock source selection */
clks = I2S2_CLOCK_SEL;
}
if(0U != (RCU_CFG1 & clks)){
/* get RCU PLL2 clock multiplication factor */
clks = (uint32_t)((RCU_CFG1 & I2S_CLOCK_MUL_MASK) >> 12U);
pll2mf_4 = RCU_CFG1 & RCU_CFG1_PLL2MF_4;
if( 0U == pll2mf_4){
if((clks > 5U) && (clks < 15U)){
/* multiplier is between 8 and 16 */
clks += 2U;
}else{
if(15U == clks){
/* multiplier is 20 */
clks = 20U;
}
}
}else{
if(clks < 15U){
/* multiplier is between 18 and 32 */
clks += 18U;
}else{
if(15U == clks){
/* multiplier is 40 */
clks = 40U;
}
}
}
/* get the PREDV1 value */
i2sclock = (uint32_t)(((RCU_CFG1 & I2S_CLOCK_DIV_MASK) >> 4U) + 1U);
/* calculate i2sclock based on PLL2 and PREDV1 */
i2sclock = (uint32_t)((HXTAL_VALUE / i2sclock) * clks * 2U);
}else{
/* get system clock */
i2sclock = rcu_clock_freq_get(CK_SYS);
}
#else
/* get system clock */
i2sclock = rcu_clock_freq_get(CK_SYS);
#endif /* GD32E50X_CL */
/* config the prescaler depending on the mclk output state, the frame format and audio sample rate */
if(I2S_MCKOUT_ENABLE == i2s_mckout){
clks = (uint32_t)(((i2sclock / 256U) * 10U) / i2s_audiosample);
}else{
if(I2S_FRAMEFORMAT_DT16B_CH16B == i2s_frameformat){
clks = (uint32_t)(((i2sclock / 32U) *10U ) / i2s_audiosample);
}else{
clks = (uint32_t)(((i2sclock / 64U) *10U ) / i2s_audiosample);
}
}
/* remove the floating point */
clks = (clks + 5U) / 10U;
i2sof = (clks & 0x00000001U);
i2sdiv = ((clks - i2sof) / 2U);
i2sof = (i2sof << 8U);
/* set the default values */
if((i2sdiv < 2U) || (i2sdiv > 255U)){
i2sdiv = 2U;
i2sof = 0U;
}
/* configure SPI_I2SPSC */
SPI_I2SPSC(spi_periph) = (uint32_t)(i2sdiv | i2sof | i2s_mckout);
/* clear SPI_I2SCTL_DTLEN and SPI_I2SCTL_CHLEN bits */
SPI_I2SCTL(spi_periph) &= (uint32_t)(~(SPI_I2SCTL_DTLEN | SPI_I2SCTL_CHLEN));
/* configure data frame format */
SPI_I2SCTL(spi_periph) |= (uint32_t)i2s_frameformat;
}
/*!
\brief enable I2S
\param[in] spi_periph: SPIx(x=1,2)
\param[out] none
\retval none
*/
void i2s_enable(uint32_t spi_periph)
{
SPI_I2SCTL(spi_periph) |= (uint32_t)SPI_I2SCTL_I2SEN;
}
/*!
\brief disable I2S
\param[in] spi_periph: SPIx(x=1,2)
\param[out] none
\retval none
*/
void i2s_disable(uint32_t spi_periph)
{
SPI_I2SCTL(spi_periph) &= (uint32_t)(~SPI_I2SCTL_I2SEN);
}
/*!
\brief enable SPI NSS output
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_output_enable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_NSSDRV;
}
/*!
\brief disable SPI NSS output
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_output_disable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_NSSDRV);
}
/*!
\brief SPI NSS pin high level in software mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_internal_high(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_SWNSS;
}
/*!
\brief SPI NSS pin low level in software mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_internal_low(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_SWNSS);
}
/*!
\brief enable SPI DMA send or receive
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] dma: SPI DMA mode
only one parameter can be selected which is shown as below:
\arg SPI_DMA_TRANSMIT: SPI transmit data use DMA
\arg SPI_DMA_RECEIVE: SPI receive data use DMA
\param[out] none
\retval none
*/
void spi_dma_enable(uint32_t spi_periph, uint8_t dma)
{
if(SPI_DMA_TRANSMIT == dma){
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_DMATEN;
}else{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_DMAREN;
}
}
/*!
\brief disable SPI DMA send or receive
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] dma: SPI DMA mode
only one parameter can be selected which is shown as below:
\arg SPI_DMA_TRANSMIT: SPI transmit data use DMA
\arg SPI_DMA_RECEIVE: SPI receive data use DMA
\param[out] none
\retval none
*/
void spi_dma_disable(uint32_t spi_periph, uint8_t dma)
{
if(SPI_DMA_TRANSMIT == dma){
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_DMATEN);
}else{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_DMAREN);
}
}
/*!
\brief configure SPI/I2S data frame format
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] frame_format: SPI frame size
only one parameter can be selected which is shown as below:
\arg SPI_FRAMESIZE_16BIT: SPI frame size is 16 bits
\arg SPI_FRAMESIZE_8BIT: SPI frame size is 8 bits
\param[out] none
\retval none
*/
void spi_i2s_data_frame_format_config(uint32_t spi_periph, uint16_t frame_format)
{
/* clear SPI_CTL0_FF16 bit */
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_FF16);
/* confige SPI_CTL0_FF16 bit */
SPI_CTL0(spi_periph) |= (uint32_t)frame_format;
}
/*!
\brief SPI transmit data
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] data: 16-bit data
\param[out] none
\retval none
*/
void spi_i2s_data_transmit(uint32_t spi_periph, uint16_t data)
{
SPI_DATA(spi_periph) = (uint32_t)data;
}
/*!
\brief SPI receive data
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval 16-bit data
*/
uint16_t spi_i2s_data_receive(uint32_t spi_periph)
{
return ((uint16_t)SPI_DATA(spi_periph));
}
/*!
\brief configure SPI bidirectional transfer direction
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] transfer_direction: SPI transfer direction
only one parameter can be selected which is shown as below:
\arg SPI_BIDIRECTIONAL_TRANSMIT: SPI work in transmit-only mode
\arg SPI_BIDIRECTIONAL_RECEIVE: SPI work in receive-only mode
\retval none
*/
void spi_bidirectional_transfer_config(uint32_t spi_periph, uint32_t transfer_direction)
{
if(SPI_BIDIRECTIONAL_TRANSMIT == transfer_direction){
/* set the transmit only mode */
SPI_CTL0(spi_periph) |= (uint32_t)SPI_BIDIRECTIONAL_TRANSMIT;
}else{
/* set the receive only mode */
SPI_CTL0(spi_periph) &= SPI_BIDIRECTIONAL_RECEIVE;
}
}
/*!
\brief clear SPI/I2S format error flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] flag: SPI/I2S frame format error flag
\arg SPI_FLAG_FERR: only for SPI work in TI mode
\arg I2S_FLAG_FERR: for I2S
\param[out] none
\retval none
*/
void spi_i2s_format_error_clear(uint32_t spi_periph, uint32_t flag)
{
SPI_STAT(spi_periph) = (uint32_t)(~flag);
}
/*!
\brief set SPI CRC polynomial
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] crc_poly: CRC polynomial value
\param[out] none
\retval none
*/
void spi_crc_polynomial_set(uint32_t spi_periph,uint16_t crc_poly)
{
/* enable SPI CRC */
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_CRCEN;
/* set SPI CRC polynomial */
SPI_CRCPOLY(spi_periph) = (uint32_t)crc_poly;
}
/*!
\brief get SPI CRC polynomial
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval 16-bit CRC polynomial
*/
uint16_t spi_crc_polynomial_get(uint32_t spi_periph)
{
return ((uint16_t)SPI_CRCPOLY(spi_periph));
}
/*!
\brief turn on CRC function
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_on(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_CRCEN;
}
/*!
\brief turn off CRC function
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_off(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_CRCEN);
}
/*!
\brief SPI next data is CRC value
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_next(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_CRCNT;
}
/*!
\brief get SPI CRC send value or receive value
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] crc: SPI crc value
only one parameter can be selected which is shown as below:
\arg SPI_CRC_TX: get transmit crc value
\arg SPI_CRC_RX: get receive crc value
\param[out] none
\retval 16-bit CRC value
*/
uint16_t spi_crc_get(uint32_t spi_periph,uint8_t crc)
{
if(SPI_CRC_TX == crc){
return ((uint16_t)(SPI_TCRC(spi_periph)));
}else{
return ((uint16_t)(SPI_RCRC(spi_periph)));
}
}
/*!
\brief clear SPI CRC error flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_error_clear(uint32_t spi_periph)
{
SPI_STAT(spi_periph) &= (uint32_t)(~SPI_FLAG_CRCERR);
}
/*!
\brief enable SPI TI mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_ti_mode_enable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_TMOD;
}
/*!
\brief disable SPI TI mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_ti_mode_disable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_TMOD);
}
/*!
\brief enable SPI NSS pulse mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nssp_mode_enable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_NSSP;
}
/*!
\brief disable SPI NSS pulse mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nssp_mode_disable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_NSSP);
}
/*!
\brief configure i2s full duplex mode
\param[in] i2s_add_periph: I2Sx_ADD(x=1,2)
\param[in] i2s_mode:
\arg I2S_MODE_SLAVETX : I2S slave transmit mode
\arg I2S_MODE_SLAVERX : I2S slave receive mode
\arg I2S_MODE_MASTERTX : I2S master transmit mode
\arg I2S_MODE_MASTERRX : I2S master receive mode
\param[in] i2s_standard:
\arg I2S_STD_PHILIPS : I2S philips standard
\arg I2S_STD_MSB : I2S MSB standard
\arg I2S_STD_LSB : I2S LSB standard
\arg I2S_STD_PCMSHORT : I2S PCM short standard
\arg I2S_STD_PCMLONG : I2S PCM long standard
\param[in] i2s_ckpl:
\arg I2S_CKPL_LOW : I2S clock polarity low level
\arg I2S_CKPL_HIGH : I2S clock polarity high level
\param[in] i2s_frameformat:
\arg I2S_FRAMEFORMAT_DT16B_CH16B: I2S data length is 16 bit and channel length is 16 bit
\arg I2S_FRAMEFORMAT_DT16B_CH32B: I2S data length is 16 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT24B_CH32B: I2S data length is 24 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT32B_CH32B: I2S data length is 32 bit and channel length is 32 bit
\param[out] none
\retval none
*/
void i2s_full_duplex_mode_config(uint32_t i2s_add_periph, uint32_t i2s_mode, uint32_t i2s_standard,
uint32_t i2s_ckpl, uint32_t i2s_frameformat)
{
uint32_t reg = 0U, tmp = 0U;
reg = I2S_ADD_I2SCTL(i2s_add_periph);
reg &= I2S_FULL_DUPLEX_MASK;
/* get the mode of the extra I2S module I2Sx_ADD */
if((I2S_MODE_MASTERTX == i2s_mode) || (I2S_MODE_SLAVETX == i2s_mode)){
tmp = I2S_MODE_SLAVERX;
}else{
tmp = I2S_MODE_SLAVETX;
}
/* enable I2S mode */
reg |= (uint32_t)SPI_I2SCTL_I2SSEL;
/* select I2S mode */
reg |= (uint32_t)tmp;
/* select I2S standard */
reg |= (uint32_t)i2s_standard;
/* select I2S polarity */
reg |= (uint32_t)i2s_ckpl;
/* configure data frame format */
reg |= (uint32_t)i2s_frameformat;
/* write to SPI_I2SCTL register */
I2S_ADD_I2SCTL(i2s_add_periph) = (uint32_t)reg;
}
/*!
\brief enable SPI quad wire mode
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) |= (uint32_t)SPI_QCTL_QMOD;
}
/*!
\brief disable SPI quad wire mode
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_disable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) &= (uint32_t)(~SPI_QCTL_QMOD);
}
/*!
\brief enable SPI quad wire mode write
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_write_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) &= (uint32_t)(~SPI_QCTL_QRD);
}
/*!
\brief enable SPI quad wire mode read
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_read_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) |= (uint32_t)SPI_QCTL_QRD;
}
/*!
\brief enable SPI quad wire mode SPI_IO2 and SPI_IO3 pin output
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_io23_output_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) |= (uint32_t)SPI_QCTL_IO23_DRV;
}
/*!
\brief disable SPI quad wire mode SPI_IO2 and SPI_IO3 pin output
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_io23_output_disable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) &= (uint32_t)(~SPI_QCTL_IO23_DRV);
}
/*!
\brief enable SPI and I2S interrupt
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] interrupt: SPI/I2S interrupt
only one parameter can be selected which is shown as below:
\arg SPI_I2S_INT_TBE: transmit buffer empty interrupt
\arg SPI_I2S_INT_RBNE: receive buffer not empty interrupt
\arg SPI_I2S_INT_ERR: CRC error,configuration error,reception overrun error,
transmission underrun error and format error interrupt
\param[out] none
\retval none
*/
void spi_i2s_interrupt_enable(uint32_t spi_periph, uint8_t interrupt)
{
SPI_CTL1(spi_periph) |= (uint32_t)interrupt;
}
/*!
\brief disable SPI and I2S interrupt
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] interrupt: SPI/I2S interrupt
only one parameter can be selected which is shown as below:
\arg SPI_I2S_INT_TBE: transmit buffer empty interrupt
\arg SPI_I2S_INT_RBNE: receive buffer not empty interrupt
\arg SPI_I2S_INT_ERR: CRC error,configuration error,reception overrun error,
transmission underrun error and format error interrupt
\param[out] none
\retval none
*/
void spi_i2s_interrupt_disable(uint32_t spi_periph, uint8_t interrupt)
{
SPI_CTL1(spi_periph) &= ~(uint32_t)interrupt;
}
/*!
\brief get SPI and I2S interrupt flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] interrupt: SPI/I2S interrupt flag status
only one parameter can be selected which is shown as below:
\arg SPI_I2S_INT_FLAG_TBE: transmit buffer empty interrupt flag
\arg SPI_I2S_INT_FLAG_RBNE: receive buffer not empty interrupt flag
\arg SPI_I2S_INT_FLAG_RXORERR: overrun interrupt flag
\arg SPI_INT_FLAG_CONFERR: config error interrupt flag
\arg SPI_INT_FLAG_CRCERR: CRC error interrupt flag
\arg I2S_INT_FLAG_TXURERR: underrun error interrupt flag
\arg SPI_I2S_INT_FLAG_FERR: format error interrupt flag
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus spi_i2s_interrupt_flag_get(uint32_t spi_periph, uint8_t interrupt)
{
uint32_t reg1 = SPI_STAT(spi_periph);
uint32_t reg2 = SPI_CTL1(spi_periph);
switch(interrupt){
/* SPI/I2S transmit buffer empty interrupt */
case SPI_I2S_INT_FLAG_TBE:
reg1 = reg1 & SPI_STAT_TBE;
reg2 = reg2 & SPI_CTL1_TBEIE;
break;
/* SPI/I2S receive buffer not empty interrupt */
case SPI_I2S_INT_FLAG_RBNE:
reg1 = reg1 & SPI_STAT_RBNE;
reg2 = reg2 & SPI_CTL1_RBNEIE;
break;
/* SPI/I2S overrun interrupt */
case SPI_I2S_INT_FLAG_RXORERR:
reg1 = reg1 & SPI_STAT_RXORERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* SPI config error interrupt */
case SPI_INT_FLAG_CONFERR:
reg1 = reg1 & SPI_STAT_CONFERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* SPI CRC error interrupt */
case SPI_INT_FLAG_CRCERR:
reg1 = reg1 & SPI_STAT_CRCERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* I2S underrun error interrupt */
case I2S_INT_FLAG_TXURERR:
reg1 = reg1 & SPI_STAT_TXURERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* SPI/I2S format error interrupt */
case SPI_I2S_INT_FLAG_FERR:
reg1 = reg1 & SPI_STAT_FERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
default :
break;
}
/*get SPI/I2S interrupt flag status */
if(reg1 && reg2){
return SET;
}else{
return RESET;
}
}
/*!
\brief get SPI and I2S flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] flag: SPI/I2S flag status
only one parameter can be selected which is shown as below:
\arg SPI_FLAG_TBE: transmit buffer empty flag
\arg SPI_FLAG_RBNE: receive buffer not empty flag
\arg SPI_FLAG_TRANS: transmit on-going flag
\arg SPI_FLAG_RXORERR: receive overrun error flag
\arg SPI_FLAG_CONFERR: mode config error flag
\arg SPI_FLAG_CRCERR: CRC error flag
\arg SPI_FLAG_FERR: format error flag
\arg I2S_FLAG_TBE: transmit buffer empty flag
\arg I2S_FLAG_RBNE: receive buffer not empty flag
\arg I2S_FLAG_TRANS: transmit on-going flag
\arg I2S_FLAG_RXORERR: overrun error flag
\arg I2S_FLAG_TXURERR: underrun error flag
\arg I2S_FLAG_CH: channel side flag
\arg I2S_FLAG_FERR: format error flag
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus spi_i2s_flag_get(uint32_t spi_periph, uint32_t flag)
{
if(SPI_STAT(spi_periph) & flag){
return SET;
}else{
return RESET;
}
}
@@ -0,0 +1,895 @@
/*!
\file gd32e50x_spi.c
\brief SPI driver
\version 2026-02-09, V1.7.0, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e50x_spi.h"
/* SPI/I2S parameter initialization mask */
#define SPI_INIT_MASK ((uint32_t)0x00003040U) /*!< SPI parameter initialization mask */
#define I2S_INIT_MASK ((uint32_t)0x0000F047U) /*!< I2S parameter initialization mask */
#define I2S_FULL_DUPLEX_MASK ((uint32_t)0x00000480U) /*!< I2S full duples mode configure parameter initialization mask */
/* default value */
#define SPI_I2SPSC_DEFAULT_VALUE ((uint32_t)0x00000002U) /*!< default value of SPI_I2SPSC register */
/* I2S clock source selection, multiplication and division mask */
#define I2S1_CLOCK_SEL ((uint32_t)0x00020000U) /*!< I2S1 clock source selection */
#define I2S2_CLOCK_SEL ((uint32_t)0x00040000U) /*!< I2S2 clock source selection */
#define I2S_CLOCK_MUL_MASK ((uint32_t)0x0000F000U) /*!< I2S clock multiplication mask */
#define I2S_CLOCK_DIV_MASK ((uint32_t)0x000000F0U) /*!< I2S clock division mask */
/*!
\brief reset SPI and I2S
\param[in] spi_periph: SPIx(x=0,1,2),include I2S1_ADD and I2S2_ADD
\param[out] none
\retval none
*/
void spi_i2s_deinit(uint32_t spi_periph)
{
switch(spi_periph){
case SPI0:
/* reset SPI0 */
rcu_periph_reset_enable(RCU_SPI0RST);
rcu_periph_reset_disable(RCU_SPI0RST);
break;
case SPI1:
/* reset SPI1 , I2S1 and I2S1_ADD */
rcu_periph_reset_enable(RCU_SPI1RST);
rcu_periph_reset_disable(RCU_SPI1RST);
break;
case SPI2:
/* reset SPI2 , I2S2 and I2S2_ADD */
rcu_periph_reset_enable(RCU_SPI2RST);
rcu_periph_reset_disable(RCU_SPI2RST);
break;
default :
break;
}
}
/*!
\brief initialize the parameters of SPI struct with default values
\param[in] none
\param[out] spi_parameter_struct: the initialized struct spi_parameter_struct pointer
\retval none
*/
void spi_struct_para_init(spi_parameter_struct *spi_struct)
{
/* configure the structure with default value */
spi_struct->device_mode = SPI_SLAVE;
spi_struct->trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_struct->frame_size = SPI_FRAMESIZE_8BIT;
spi_struct->nss = SPI_NSS_HARD;
spi_struct->clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_struct->prescale = SPI_PSC_2;
spi_struct->endian = SPI_ENDIAN_MSB;
}
/*!
\brief initialize SPI parameter
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] spi_struct: SPI parameter initialization stuct members of the structure
and the member values are shown as below:
device_mode: SPI_MASTER, SPI_SLAVE
trans_mode: SPI_TRANSMODE_FULLDUPLEX, SPI_TRANSMODE_RECEIVEONLY,
SPI_TRANSMODE_BDRECEIVE, SPI_TRANSMODE_BDTRANSMIT
frame_size: SPI_FRAMESIZE_16BIT, SPI_FRAMESIZE_8BIT
nss: SPI_NSS_SOFT, SPI_NSS_HARD
endian: SPI_ENDIAN_MSB, SPI_ENDIAN_LSB
clock_polarity_phase: SPI_CK_PL_LOW_PH_1EDGE, SPI_CK_PL_HIGH_PH_1EDGE
SPI_CK_PL_LOW_PH_2EDGE, SPI_CK_PL_HIGH_PH_2EDGE
prescale: SPI_PSC_n (n=2,4,8,16,32,64,128,256)
\param[out] none
\retval none
*/
void spi_init(uint32_t spi_periph, spi_parameter_struct* spi_struct)
{
uint32_t reg = 0U;
reg = SPI_CTL0(spi_periph);
reg &= SPI_INIT_MASK;
/* select SPI as master or slave */
reg |= spi_struct->device_mode;
/* select SPI transfer mode */
reg |= spi_struct->trans_mode;
/* select SPI frame size */
reg |= spi_struct->frame_size;
/* select SPI NSS use hardware or software */
reg |= spi_struct->nss;
/* select SPI LSB or MSB */
reg |= spi_struct->endian;
/* select SPI polarity and phase */
reg |= spi_struct->clock_polarity_phase;
/* select SPI prescale to adjust transmit speed */
reg |= spi_struct->prescale;
/* write to SPI_CTL0 register */
SPI_CTL0(spi_periph) = (uint32_t)reg;
SPI_I2SCTL(spi_periph) &= (uint32_t)(~SPI_I2SCTL_I2SSEL);
}
/*!
\brief enable SPI
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_enable(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_SPIEN;
}
/*!
\brief disable SPI
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_disable(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_SPIEN);
}
/*!
\brief initialize I2S parameter
\param[in] spi_periph: SPIx(x=1,2)
\param[in] i2s_mode: I2S operation mode
only one parameter can be selected which is shown as below:
\arg I2S_MODE_SLAVETX: I2S slave transmit mode
\arg I2S_MODE_SLAVERX: I2S slave receive mode
\arg I2S_MODE_MASTERTX: I2S master transmit mode
\arg I2S_MODE_MASTERRX: I2S master receive mode
\param[in] i2s_standard: I2S standard
only one parameter can be selected which is shown as below:
\arg I2S_STD_PHILIPS: I2S philips standard
\arg I2S_STD_MSB: I2S MSB standard
\arg I2S_STD_LSB: I2S LSB standard
\arg I2S_STD_PCMSHORT: I2S PCM short standard
\arg I2S_STD_PCMLONG: I2S PCM long standard
\param[in] i2s_ckpl: I2S idle state clock polarity
only one parameter can be selected which is shown as below:
\arg I2S_CKPL_LOW: I2S clock polarity low level
\arg I2S_CKPL_HIGH: I2S clock polarity high level
\param[out] none
\retval none
*/
void i2s_init(uint32_t spi_periph, uint32_t i2s_mode, uint32_t i2s_standard, uint32_t i2s_ckpl)
{
uint32_t reg= 0U;
reg = SPI_I2SCTL(spi_periph);
reg &= I2S_INIT_MASK;
/* enable I2S mode */
reg |= (uint32_t)SPI_I2SCTL_I2SSEL;
/* select I2S mode */
reg |= (uint32_t)i2s_mode;
/* select I2S standard */
reg |= (uint32_t)i2s_standard;
/* select I2S polarity */
reg |= (uint32_t)i2s_ckpl;
/* write to SPI_I2SCTL register */
SPI_I2SCTL(spi_periph) = (uint32_t)reg;
}
/*!
\brief configure I2S prescaler
\param[in] spi_periph: SPIx(x=1,2)
\param[in] i2s_audiosample: I2S audio sample rate
only one parameter can be selected which is shown as below:
\arg I2S_AUDIOSAMPLE_8K: audio sample rate is 8KHz
\arg I2S_AUDIOSAMPLE_11K: audio sample rate is 11KHz
\arg I2S_AUDIOSAMPLE_16K: audio sample rate is 16KHz
\arg I2S_AUDIOSAMPLE_22K: audio sample rate is 22KHz
\arg I2S_AUDIOSAMPLE_32K: audio sample rate is 32KHz
\arg I2S_AUDIOSAMPLE_44K: audio sample rate is 44KHz
\arg I2S_AUDIOSAMPLE_48K: audio sample rate is 48KHz
\arg I2S_AUDIOSAMPLE_96K: audio sample rate is 96KHz
\arg I2S_AUDIOSAMPLE_192K: audio sample rate is 192KHz
\param[in] i2s_frameformat: I2S data length and channel length
only one parameter can be selected which is shown as below:
\arg I2S_FRAMEFORMAT_DT16B_CH16B: I2S data length is 16 bit and channel length is 16 bit
\arg I2S_FRAMEFORMAT_DT16B_CH32B: I2S data length is 16 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT24B_CH32B: I2S data length is 24 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT32B_CH32B: I2S data length is 32 bit and channel length is 32 bit
\param[in] i2s_mckout: I2S master clock output
only one parameter can be selected which is shown as below:
\arg I2S_MCKOUT_ENABLE: I2S master clock output enable
\arg I2S_MCKOUT_DISABLE: I2S master clock output disable
\param[out] none
\retval none
*/
void i2s_psc_config(uint32_t spi_periph, uint32_t i2s_audiosample, uint32_t i2s_frameformat, uint32_t i2s_mckout)
{
uint32_t i2sdiv = 2U, i2sof = 0U;
uint32_t clks = 0U;
uint32_t i2sclock = 0U;
#ifdef GD32E50X_CL
uint32_t pll2mf_4 = 0U;
#endif /* GD32E50X_CL */
/* deinit SPI_I2SPSC register */
SPI_I2SPSC(spi_periph) = SPI_I2SPSC_DEFAULT_VALUE;
#ifdef GD32E50X_CL
/* get the I2S clock source */
if(((uint32_t)spi_periph) == SPI1){
/* I2S1 clock source selection */
clks = I2S1_CLOCK_SEL;
}else{
/* I2S2 clock source selection */
clks = I2S2_CLOCK_SEL;
}
if(0U != (RCU_CFG1 & clks)){
/* get RCU PLL2 clock multiplication factor */
clks = (uint32_t)((RCU_CFG1 & I2S_CLOCK_MUL_MASK) >> 12U);
pll2mf_4 = RCU_CFG1 & RCU_CFG1_PLL2MF_4;
if( 0U == pll2mf_4){
if((clks > 5U) && (clks < 15U)){
/* multiplier is between 8 and 16 */
clks += 2U;
}else{
if(15U == clks){
/* multiplier is 20 */
clks = 20U;
}
}
}else{
if(clks < 15U){
/* multiplier is between 18 and 32 */
clks += 18U;
}else{
if(15U == clks){
/* multiplier is 40 */
clks = 40U;
}
}
}
/* get the PREDV1 value */
i2sclock = (uint32_t)(((RCU_CFG1 & I2S_CLOCK_DIV_MASK) >> 4U) + 1U);
/* calculate i2sclock based on PLL2 and PREDV1 */
i2sclock = (uint32_t)((HXTAL_VALUE / i2sclock) * clks * 2U);
}else{
/* get system clock */
i2sclock = rcu_clock_freq_get(CK_SYS);
}
#else
/* get system clock */
i2sclock = rcu_clock_freq_get(CK_SYS);
#endif /* GD32E50X_CL */
/* config the prescaler depending on the mclk output state, the frame format and audio sample rate */
if(I2S_MCKOUT_ENABLE == i2s_mckout){
clks = (uint32_t)(((i2sclock / 256U) * 10U) / i2s_audiosample);
}else{
if(I2S_FRAMEFORMAT_DT16B_CH16B == i2s_frameformat){
clks = (uint32_t)(((i2sclock / 32U) *10U ) / i2s_audiosample);
}else{
clks = (uint32_t)(((i2sclock / 64U) *10U ) / i2s_audiosample);
}
}
/* remove the floating point */
clks = (clks + 5U) / 10U;
i2sof = (clks & 0x00000001U);
i2sdiv = ((clks - i2sof) / 2U);
i2sof = (i2sof << 8U);
/* set the default values */
if((i2sdiv < 2U) || (i2sdiv > 255U)){
i2sdiv = 2U;
i2sof = 0U;
}
/* configure SPI_I2SPSC */
SPI_I2SPSC(spi_periph) = (uint32_t)(i2sdiv | i2sof | i2s_mckout);
/* clear SPI_I2SCTL_DTLEN and SPI_I2SCTL_CHLEN bits */
SPI_I2SCTL(spi_periph) &= (uint32_t)(~(SPI_I2SCTL_DTLEN | SPI_I2SCTL_CHLEN));
/* configure data frame format */
SPI_I2SCTL(spi_periph) |= (uint32_t)i2s_frameformat;
}
/*!
\brief enable I2S
\param[in] spi_periph: SPIx(x=1,2)
\param[out] none
\retval none
*/
void i2s_enable(uint32_t spi_periph)
{
SPI_I2SCTL(spi_periph) |= (uint32_t)SPI_I2SCTL_I2SEN;
}
/*!
\brief disable I2S
\param[in] spi_periph: SPIx(x=1,2)
\param[out] none
\retval none
*/
void i2s_disable(uint32_t spi_periph)
{
SPI_I2SCTL(spi_periph) &= (uint32_t)(~SPI_I2SCTL_I2SEN);
}
/*!
\brief enable SPI NSS output
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_output_enable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_NSSDRV;
}
/*!
\brief disable SPI NSS output
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_output_disable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_NSSDRV);
}
/*!
\brief SPI NSS pin high level in software mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_internal_high(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_SWNSS;
}
/*!
\brief SPI NSS pin low level in software mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nss_internal_low(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_SWNSS);
}
/*!
\brief enable SPI DMA send or receive
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] dma: SPI DMA mode
only one parameter can be selected which is shown as below:
\arg SPI_DMA_TRANSMIT: SPI transmit data use DMA
\arg SPI_DMA_RECEIVE: SPI receive data use DMA
\param[out] none
\retval none
*/
void spi_dma_enable(uint32_t spi_periph, uint8_t dma)
{
if(SPI_DMA_TRANSMIT == dma){
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_DMATEN;
}else{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_DMAREN;
}
}
/*!
\brief disable SPI DMA send or receive
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] dma: SPI DMA mode
only one parameter can be selected which is shown as below:
\arg SPI_DMA_TRANSMIT: SPI transmit data use DMA
\arg SPI_DMA_RECEIVE: SPI receive data use DMA
\param[out] none
\retval none
*/
void spi_dma_disable(uint32_t spi_periph, uint8_t dma)
{
if(SPI_DMA_TRANSMIT == dma){
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_DMATEN);
}else{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_DMAREN);
}
}
/*!
\brief configure SPI/I2S data frame format
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] frame_format: SPI frame size
only one parameter can be selected which is shown as below:
\arg SPI_FRAMESIZE_16BIT: SPI frame size is 16 bits
\arg SPI_FRAMESIZE_8BIT: SPI frame size is 8 bits
\param[out] none
\retval none
*/
void spi_i2s_data_frame_format_config(uint32_t spi_periph, uint16_t frame_format)
{
/* clear SPI_CTL0_FF16 bit */
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_FF16);
/* confige SPI_CTL0_FF16 bit */
SPI_CTL0(spi_periph) |= (uint32_t)frame_format;
}
/*!
\brief SPI transmit data
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] data: 16-bit data
\param[out] none
\retval none
*/
void spi_i2s_data_transmit(uint32_t spi_periph, uint16_t data)
{
SPI_DATA(spi_periph) = (uint32_t)data;
}
/*!
\brief SPI receive data
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval 16-bit data
*/
uint16_t spi_i2s_data_receive(uint32_t spi_periph)
{
return ((uint16_t)SPI_DATA(spi_periph));
}
/*!
\brief configure SPI bidirectional transfer direction
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] transfer_direction: SPI transfer direction
only one parameter can be selected which is shown as below:
\arg SPI_BIDIRECTIONAL_TRANSMIT: SPI work in transmit-only mode
\arg SPI_BIDIRECTIONAL_RECEIVE: SPI work in receive-only mode
\retval none
*/
void spi_bidirectional_transfer_config(uint32_t spi_periph, uint32_t transfer_direction)
{
if(SPI_BIDIRECTIONAL_TRANSMIT == transfer_direction){
/* set the transmit only mode */
SPI_CTL0(spi_periph) |= (uint32_t)SPI_BIDIRECTIONAL_TRANSMIT;
}else{
/* set the receive only mode */
SPI_CTL0(spi_periph) &= SPI_BIDIRECTIONAL_RECEIVE;
}
}
/*!
\brief clear SPI/I2S format error flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] flag: SPI/I2S frame format error flag
\arg SPI_FLAG_FERR: only for SPI work in TI mode
\arg I2S_FLAG_FERR: for I2S
\param[out] none
\retval none
*/
void spi_i2s_format_error_clear(uint32_t spi_periph, uint32_t flag)
{
SPI_STAT(spi_periph) = (uint32_t)(~flag);
}
/*!
\brief set SPI CRC polynomial
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] crc_poly: CRC polynomial value
\param[out] none
\retval none
*/
void spi_crc_polynomial_set(uint32_t spi_periph,uint16_t crc_poly)
{
/* enable SPI CRC */
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_CRCEN;
/* set SPI CRC polynomial */
SPI_CRCPOLY(spi_periph) = (uint32_t)crc_poly;
}
/*!
\brief get SPI CRC polynomial
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval 16-bit CRC polynomial
*/
uint16_t spi_crc_polynomial_get(uint32_t spi_periph)
{
return ((uint16_t)SPI_CRCPOLY(spi_periph));
}
/*!
\brief turn on CRC function
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_on(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_CRCEN;
}
/*!
\brief turn off CRC function
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_off(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_CRCEN);
}
/*!
\brief SPI next data is CRC value
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_next(uint32_t spi_periph)
{
SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_CRCNT;
}
/*!
\brief get SPI CRC send value or receive value
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] crc: SPI crc value
only one parameter can be selected which is shown as below:
\arg SPI_CRC_TX: get transmit crc value
\arg SPI_CRC_RX: get receive crc value
\param[out] none
\retval 16-bit CRC value
*/
uint16_t spi_crc_get(uint32_t spi_periph,uint8_t crc)
{
if(SPI_CRC_TX == crc){
return ((uint16_t)(SPI_TCRC(spi_periph)));
}else{
return ((uint16_t)(SPI_RCRC(spi_periph)));
}
}
/*!
\brief clear SPI CRC error flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_crc_error_clear(uint32_t spi_periph)
{
SPI_STAT(spi_periph) &= (uint32_t)(~SPI_FLAG_CRCERR);
}
/*!
\brief enable SPI TI mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_ti_mode_enable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_TMOD;
}
/*!
\brief disable SPI TI mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_ti_mode_disable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_TMOD);
}
/*!
\brief enable SPI NSS pulse mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nssp_mode_enable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_NSSP;
}
/*!
\brief disable SPI NSS pulse mode
\param[in] spi_periph: SPIx(x=0,1,2)
\param[out] none
\retval none
*/
void spi_nssp_mode_disable(uint32_t spi_periph)
{
SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_NSSP);
}
/*!
\brief configure i2s full duplex mode
\param[in] i2s_add_periph: I2Sx_ADD(x=1,2)
\param[in] i2s_mode:
\arg I2S_MODE_SLAVETX : I2S slave transmit mode
\arg I2S_MODE_SLAVERX : I2S slave receive mode
\arg I2S_MODE_MASTERTX : I2S master transmit mode
\arg I2S_MODE_MASTERRX : I2S master receive mode
\param[in] i2s_standard:
\arg I2S_STD_PHILIPS : I2S philips standard
\arg I2S_STD_MSB : I2S MSB standard
\arg I2S_STD_LSB : I2S LSB standard
\arg I2S_STD_PCMSHORT : I2S PCM short standard
\arg I2S_STD_PCMLONG : I2S PCM long standard
\param[in] i2s_ckpl:
\arg I2S_CKPL_LOW : I2S clock polarity low level
\arg I2S_CKPL_HIGH : I2S clock polarity high level
\param[in] i2s_frameformat:
\arg I2S_FRAMEFORMAT_DT16B_CH16B: I2S data length is 16 bit and channel length is 16 bit
\arg I2S_FRAMEFORMAT_DT16B_CH32B: I2S data length is 16 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT24B_CH32B: I2S data length is 24 bit and channel length is 32 bit
\arg I2S_FRAMEFORMAT_DT32B_CH32B: I2S data length is 32 bit and channel length is 32 bit
\param[out] none
\retval none
*/
void i2s_full_duplex_mode_config(uint32_t i2s_add_periph, uint32_t i2s_mode, uint32_t i2s_standard,
uint32_t i2s_ckpl, uint32_t i2s_frameformat)
{
uint32_t reg = 0U, tmp = 0U;
reg = I2S_ADD_I2SCTL(i2s_add_periph);
reg &= I2S_FULL_DUPLEX_MASK;
/* get the mode of the extra I2S module I2Sx_ADD */
if((I2S_MODE_MASTERTX == i2s_mode) || (I2S_MODE_SLAVETX == i2s_mode)){
tmp = I2S_MODE_SLAVERX;
}else{
tmp = I2S_MODE_SLAVETX;
}
/* enable I2S mode */
reg |= (uint32_t)SPI_I2SCTL_I2SSEL;
/* select I2S mode */
reg |= (uint32_t)tmp;
/* select I2S standard */
reg |= (uint32_t)i2s_standard;
/* select I2S polarity */
reg |= (uint32_t)i2s_ckpl;
/* configure data frame format */
reg |= (uint32_t)i2s_frameformat;
/* write to SPI_I2SCTL register */
I2S_ADD_I2SCTL(i2s_add_periph) = (uint32_t)reg;
}
/*!
\brief enable SPI quad wire mode
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) |= (uint32_t)SPI_QCTL_QMOD;
}
/*!
\brief disable SPI quad wire mode
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_disable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) &= (uint32_t)(~SPI_QCTL_QMOD);
}
/*!
\brief enable SPI quad wire mode write
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_write_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) &= (uint32_t)(~SPI_QCTL_QRD);
}
/*!
\brief enable SPI quad wire mode read
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_read_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) |= (uint32_t)SPI_QCTL_QRD;
}
/*!
\brief enable SPI quad wire mode SPI_IO2 and SPI_IO3 pin output
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_io23_output_enable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) |= (uint32_t)SPI_QCTL_IO23_DRV;
}
/*!
\brief disable SPI quad wire mode SPI_IO2 and SPI_IO3 pin output
\param[in] spi_periph: SPIx(only x=0)
\param[out] none
\retval none
*/
void spi_quad_io23_output_disable(uint32_t spi_periph)
{
SPI_QCTL(spi_periph) &= (uint32_t)(~SPI_QCTL_IO23_DRV);
}
/*!
\brief enable SPI and I2S interrupt
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] interrupt: SPI/I2S interrupt
only one parameter can be selected which is shown as below:
\arg SPI_I2S_INT_TBE: transmit buffer empty interrupt
\arg SPI_I2S_INT_RBNE: receive buffer not empty interrupt
\arg SPI_I2S_INT_ERR: CRC error,configuration error,reception overrun error,
transmission underrun error and format error interrupt
\param[out] none
\retval none
*/
void spi_i2s_interrupt_enable(uint32_t spi_periph, uint8_t interrupt)
{
SPI_CTL1(spi_periph) |= (uint32_t)interrupt;
}
/*!
\brief disable SPI and I2S interrupt
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] interrupt: SPI/I2S interrupt
only one parameter can be selected which is shown as below:
\arg SPI_I2S_INT_TBE: transmit buffer empty interrupt
\arg SPI_I2S_INT_RBNE: receive buffer not empty interrupt
\arg SPI_I2S_INT_ERR: CRC error,configuration error,reception overrun error,
transmission underrun error and format error interrupt
\param[out] none
\retval none
*/
void spi_i2s_interrupt_disable(uint32_t spi_periph, uint8_t interrupt)
{
SPI_CTL1(spi_periph) &= ~(uint32_t)interrupt;
}
/*!
\brief get SPI and I2S interrupt flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] interrupt: SPI/I2S interrupt flag status
only one parameter can be selected which is shown as below:
\arg SPI_I2S_INT_FLAG_TBE: transmit buffer empty interrupt flag
\arg SPI_I2S_INT_FLAG_RBNE: receive buffer not empty interrupt flag
\arg SPI_I2S_INT_FLAG_RXORERR: overrun interrupt flag
\arg SPI_INT_FLAG_CONFERR: config error interrupt flag
\arg SPI_INT_FLAG_CRCERR: CRC error interrupt flag
\arg I2S_INT_FLAG_TXURERR: underrun error interrupt flag
\arg SPI_I2S_INT_FLAG_FERR: format error interrupt flag
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus spi_i2s_interrupt_flag_get(uint32_t spi_periph, uint8_t interrupt)
{
uint32_t reg1 = SPI_STAT(spi_periph);
uint32_t reg2 = SPI_CTL1(spi_periph);
switch(interrupt){
/* SPI/I2S transmit buffer empty interrupt */
case SPI_I2S_INT_FLAG_TBE:
reg1 = reg1 & SPI_STAT_TBE;
reg2 = reg2 & SPI_CTL1_TBEIE;
break;
/* SPI/I2S receive buffer not empty interrupt */
case SPI_I2S_INT_FLAG_RBNE:
reg1 = reg1 & SPI_STAT_RBNE;
reg2 = reg2 & SPI_CTL1_RBNEIE;
break;
/* SPI/I2S overrun interrupt */
case SPI_I2S_INT_FLAG_RXORERR:
reg1 = reg1 & SPI_STAT_RXORERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* SPI config error interrupt */
case SPI_INT_FLAG_CONFERR:
reg1 = reg1 & SPI_STAT_CONFERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* SPI CRC error interrupt */
case SPI_INT_FLAG_CRCERR:
reg1 = reg1 & SPI_STAT_CRCERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* I2S underrun error interrupt */
case I2S_INT_FLAG_TXURERR:
reg1 = reg1 & SPI_STAT_TXURERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
/* SPI/I2S format error interrupt */
case SPI_I2S_INT_FLAG_FERR:
reg1 = reg1 & SPI_STAT_FERR;
reg2 = reg2 & SPI_CTL1_ERRIE;
break;
default :
break;
}
/*get SPI/I2S interrupt flag status */
if(reg1 && reg2){
return SET;
}else{
return RESET;
}
}
/*!
\brief get SPI and I2S flag status
\param[in] spi_periph: SPIx(x=0,1,2)
\param[in] flag: SPI/I2S flag status
only one parameter can be selected which is shown as below:
\arg SPI_FLAG_TBE: transmit buffer empty flag
\arg SPI_FLAG_RBNE: receive buffer not empty flag
\arg SPI_FLAG_TRANS: transmit on-going flag
\arg SPI_FLAG_RXORERR: receive overrun error flag
\arg SPI_FLAG_CONFERR: mode config error flag
\arg SPI_FLAG_CRCERR: CRC error flag
\arg SPI_FLAG_FERR: format error flag
\arg I2S_FLAG_TBE: transmit buffer empty flag
\arg I2S_FLAG_RBNE: receive buffer not empty flag
\arg I2S_FLAG_TRANS: transmit on-going flag
\arg I2S_FLAG_RXORERR: overrun error flag
\arg I2S_FLAG_TXURERR: underrun error flag
\arg I2S_FLAG_CH: channel side flag
\arg I2S_FLAG_FERR: format error flag
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus spi_i2s_flag_get(uint32_t spi_periph, uint32_t flag)
{
if(SPI_STAT(spi_periph) & flag){
return SET;
}else{
return RESET;
}
}
@@ -17,7 +17,7 @@
#define __ROM0_BASE 0x08000000
// <o> Region size [bytes] <0x0-0xFFFFFFFF:8>
// <i> Defines size of memory region. Default: 0x00080000
#define __ROM0_SIZE 0x00080000
#define __ROM0_SIZE 0x00012000
// </h>
// <h> __ROM1 (unused)
@@ -33,6 +33,7 @@
#define VECT_TAB_OFFSET (uint32_t)0x00 /* vector table base offset */
/* select a system clock by uncommenting the following line */
/* use IRC8M */
//#define __SYSTEM_CLOCK_IRC8M (uint32_t)(__IRC8M)
@@ -144,6 +145,12 @@ static void system_clock_180m_hxtal(void);
static void system_clock_config(void);
/* software delay to prevent the impact of Vcore fluctuations.
It is strongly recommended to include it to avoid issues caused by self-removal. */
/*!
\brief software delay helper used during clock switching
\param[in] time: delay scale factor
\param[out] none
\retval none
*/
static void _soft_delay_(uint32_t time)
{
__IO uint32_t i;
@@ -997,7 +1004,23 @@ static void system_clock_180m_hxtal(void)
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= RCU_PLL_MUL45;
#elif (defined(GD32E50X_CL) || defined(GD32E508))
#elif defined(GD32E508)
/* GD32E508 + 8MHz HXTAL:
CK_PREDIV0 = (CK_HXTAL)/2 *8 /8 = 4 MHz
CK_PLL = CK_PREDIV0 * 45 = 180 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_PLL_MUL45);
RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PREDV0SEL | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV1 | RCU_CFG1_PREDV0);
RCU_CFG1 |= (RCU_PLLPRESRC_HXTAL | RCU_PREDV0SRC_CKPLL1 | RCU_PLL1_MUL8 | RCU_PREDV1_DIV2 | RCU_PREDV0_DIV8);
/* enable PLL1 */
RCU_CTL |= RCU_CTL_PLL1EN;
/* wait till PLL1 is ready */
while((RCU_CTL & RCU_CTL_PLL1STB) == 0U){
}
#elif defined(GD32E50X_CL)
/* CK_PLL = (CK_PREDIV0) * 45 = 180 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_PLL_MUL45);
+229 -46
View File
@@ -8,42 +8,68 @@
#include "gd32e50x_misc.h"
#include <string.h>
/**
* @file Can.cpp
* @brief Bootloader CAN-FD 通信适配层实现。
*
* 负责 CAN0 初始化、OTA 帧收发、软件发送队列与中断处理。
*/
CAN_transmiter CAN0_transmiter;
extern OTAHandler ota;
#define CAN_TX_QUEUE_SIZE 8
typedef struct {
u32 id;
u8 length;
u8 data[64];
u8 valid;
} can_tx_queue_item_t;
static can_tx_queue_item_t tx_queue[CAN_TX_QUEUE_SIZE];
static volatile u8 tx_queue_head = 0;
static volatile u8 tx_queue_tail = 0;
static volatile u8 tx_queue_count = 0;
static can_trasnmit_message_struct queue_transmit_message;
CAN_transmiter::tx_queue_item_t CAN_transmiter::s_txQueue[CAN_transmiter::kTxQueueSize] = {};
volatile u8 CAN_transmiter::s_txQueueHead = 0U;
volatile u8 CAN_transmiter::s_txQueueTail = 0U;
volatile u8 CAN_transmiter::s_txQueueCount = 0U;
can_trasnmit_message_struct CAN_transmiter::s_queueTransmitMessage = {};
/**
* @brief 初始化 CAN0 相关 GPIO。
*
* @return 0 表示初始化结束。
*/
u8 can_gpio_init(void) {
/** 打开 CAN0、GPIOB 和 AF 时钟。 */
rcu_periph_clock_enable(RCU_CAN0);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
/**
* CAN0 使用 PB8/PB9 的部分重映射:
* - PB8 : RX,上拉输入
* - PB9 : TX,复用推挽输出
*/
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, ENABLE);
/**
* PB13 当前被拉低,通常可作为 CAN 收发器使能/待机控制脚。
* 这里代码没有额外命名,先按“上电后默认拉低”保留原始行为。
*/
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
gpio_bit_reset(GPIOB, GPIO_PIN_13);
return 0;
}
/**
* @brief CAN DMA 初始化占位函数。
*
* @return 无。
*/
void can_dma_init(void) {
/* Reserved: current receive path uses direct FIFO read to avoid DMA race. */
/**
* 预留接口:当前接收路径直接从 FIFO 取数,
* 这样实现更简单,也避免 DMA 与中断抢数据的时序问题。
*/
}
/**
* @brief 配置 CAN-FD 控制器、滤波器和中断。
*
* @return 无。
*/
void canfd_config(void) {
can_parameter_struct can_parameter;
can_fdframe_struct can_fd_parameter;
@@ -54,6 +80,10 @@ void canfd_config(void) {
can_dma_init();
can_deinit(CAN0);
/**
* 配置经典 CAN 仲裁段时序。
* 这里 prescaler=6、BS1=7TQ、BS2=2TQ,配合具体时钟得到目标波特率。
*/
can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
can_parameter.time_triggered = DISABLE;
can_parameter.auto_bus_off_recovery = DISABLE;
@@ -68,6 +98,10 @@ void canfd_config(void) {
can_parameter.prescaler = 6;
can_init(CAN0, &can_parameter);
/**
* 配置 CAN-FD 数据段时序。
* fd_brs 打开后,数据段可以比仲裁段更快。
*/
can_struct_para_init(CAN_FD_FRAME_STRUCT, &can_fd_parameter);
can_fd_parameter.fd_frame = ENABLE;
can_fd_parameter.excp_event_detect = ENABLE;
@@ -84,6 +118,10 @@ void canfd_config(void) {
can_fd_parameter.data_prescaler = 6;
can_fd_init(CAN0, &can_fd_parameter);
/**
* 过滤器全部放行。
* 后续真正是否处理某个 ID,在接收中断里再按 OTA ID 白名单判断。
*/
can_struct_para_init(CAN_FILTER_STRUCT, &can_filter);
can_filter.filter_mode = CAN_FILTERMODE_MASK;
can_filter.filter_bits = CAN_FILTERBITS_32BIT;
@@ -96,67 +134,136 @@ void canfd_config(void) {
can_filter.filter_number = 0;
can_filter_init(CAN0, &can_filter);
/** 打开接收和发送相关中断。 */
nvic_irq_enable(CAN0_RX0_IRQn, 1U, 0U);
nvic_irq_enable(CAN0_TX_IRQn, 1U, 1U);
can_interrupt_enable(CAN0, CAN_INTEN_RFNEIE0);
}
/**
* @brief 初始化 CAN 发送器对象。
*
* @return 0 表示初始化完成。
*/
u8 CAN_transmiter::init(void) {
canfd_config();
/** 初始化软件侧收发对象和状态。 */
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &transmit_message);
can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &receive_message);
can0_receive_count = 0;
can0_send_flag = RESET;
memset(transmit_data, 0, sizeof(transmit_data));
memset(s_txQueue, 0, sizeof(s_txQueue));
s_txQueueHead = 0U;
s_txQueueTail = 0U;
s_txQueueCount = 0U;
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &s_queueTransmitMessage);
return 0;
}
/**
* @brief 轮询处理已接收的 CAN OTA 帧。
*
* @return 无。
*/
void CAN_transmiter::communication_check(void) {
/** 发送完成标记目前只做一次性消费。 */
if (SET == can0_send_flag) {
can0_send_flag = RESET;
}
/**
* 这里采用“中断收帧 + 主循环消费”的简单模型:
* 中断只保存最近一帧并累加计数,真正 OTA 处理放在主循环里完成。
*/
if (can0_receive_count > 0) {
can0_receive_count--;
ota.handleOTAMessage(receive_message.rx_efid, receive_message.rx_data,
receive_message.rx_dlen);
/** 走 CAN 收到的帧时,显式把 OTA 回包通道切回 CAN。 */
ota_bind_can_transport();
if (receive_message.rx_efid == CANFD_OTA_CANCEL_ID) {
ota.handleLinkMessage(OTA_LINK_MSG_CANCEL, receive_message.rx_data,
receive_message.rx_dlen);
} else if (receive_message.rx_efid == CANFD_OTA_DATA_ID) {
ota.handleLinkMessage(OTA_LINK_MSG_DATA, receive_message.rx_data,
receive_message.rx_dlen);
} else if (receive_message.rx_efid == CANFD_OTA_REQ_ID) {
ota.handleLinkMessage(OTA_LINK_MSG_REQ, receive_message.rx_data,
receive_message.rx_dlen);
}
}
}
static u8 CAN_TX_QueuePush(u32 id, u8 length, u8 *data) {
if (tx_queue_count >= CAN_TX_QUEUE_SIZE) {
/**
* @brief 向软件发送队列压入一帧待发 CAN-FD 消息。
*
* @param id 扩展帧 ID。
* @param length 有效载荷长度。
* @param data 指向待发送数据的指针。
* @return 1 表示入队成功,0 表示队列已满。
*/
u8 CAN_transmiter::queuePush(u32 id, u8 length, u8 *data) {
/** 队列满时直接失败,交给上层决定是否重试。 */
if (s_txQueueCount >= kTxQueueSize) {
return 0;
}
tx_queue[tx_queue_tail].id = id;
tx_queue[tx_queue_tail].length = length;
memset(tx_queue[tx_queue_tail].data, 0, sizeof(tx_queue[tx_queue_tail].data));
s_txQueue[s_txQueueTail].id = id;
s_txQueue[s_txQueueTail].length = length;
memset(s_txQueue[s_txQueueTail].data, 0, sizeof(s_txQueue[s_txQueueTail].data));
for (u8 i = 0; i < length && i < 64U; ++i) {
tx_queue[tx_queue_tail].data[i] = data[i];
s_txQueue[s_txQueueTail].data[i] = data[i];
}
tx_queue[tx_queue_tail].valid = 1U;
tx_queue_tail = (tx_queue_tail + 1U) % CAN_TX_QUEUE_SIZE;
tx_queue_count++;
s_txQueue[s_txQueueTail].valid = 1U;
s_txQueueTail = (s_txQueueTail + 1U) % kTxQueueSize;
s_txQueueCount++;
return 1;
}
static u8 CAN_TX_QueuePop(can_tx_queue_item_t *item) {
if (tx_queue_count == 0U) {
/**
* @brief 从软件发送队列弹出一帧待发 CAN-FD 消息。
*
* @param item 指向输出队列项的指针。
* @return 1 表示出队成功,0 表示队列为空。
*/
u8 CAN_transmiter::queuePop(tx_queue_item_t *item) {
if (s_txQueueCount == 0U) {
return 0;
}
*item = tx_queue[tx_queue_head];
tx_queue[tx_queue_head].valid = 0U;
tx_queue_head = (tx_queue_head + 1U) % CAN_TX_QUEUE_SIZE;
tx_queue_count--;
*item = s_txQueue[s_txQueueHead];
s_txQueue[s_txQueueHead].valid = 0U;
s_txQueueHead = (s_txQueueHead + 1U) % kTxQueueSize;
s_txQueueCount--;
return 1;
}
/**
* @brief 异步发送一帧 CAN-FD 数据。
*
* @param id 扩展帧 ID。
* @param length 原始数据长度。
* @param data 指向待发送数据缓冲区的指针。
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 CAN_transmiter::CANFD_AsyncSend(u32 id, u8 length, u8 *data) {
u8 valid_length = length;
/** 只允许在 CAN-FD 模式下发送。 */
if (!(CAN_CTL(CAN0) & CAN_CTL_DFZ)) {
return 0;
}
if (data == NULL) {
return 0;
}
/**
* CAN-FD 的 DLC 不是任意长度都原样支持。
* 这里把长度归一化到标准支持值:8/12/16/20/24/32/48/64。
*/
if (length > 8U) {
if (length < 13U) valid_length = 12U;
else if (length < 17U) valid_length = 16U;
@@ -168,6 +275,7 @@ u8 CAN_transmiter::CANFD_AsyncSend(u32 id, u8 length, u8 *data) {
else return 0;
}
/** 组装发送帧。 */
transmit_message.tx_ft = CAN_FT_DATA;
transmit_message.tx_ff = CAN_FF_EXTENDED;
transmit_message.tx_efid = id;
@@ -176,20 +284,37 @@ u8 CAN_transmiter::CANFD_AsyncSend(u32 id, u8 length, u8 *data) {
transmit_message.fd_brs = ENABLE;
transmit_message.fd_esi = DISABLE;
/** 不足 DLC 的部分补 0。 */
for (u8 i = 0; i < valid_length; ++i) {
transmit_message.tx_data[i] = (i < length) ? data[i] : 0U;
}
/**
* 尝试直接发送。
* 如果没有空邮箱,则压入软件队列,等 TX 中断再补发。
*/
transmit_mailbox = can_message_transmit(CAN0, &transmit_message);
if (transmit_mailbox == CAN_NOMAILBOX) {
return CAN_TX_QueuePush(id, valid_length, data);
return queuePush(id, valid_length, data);
}
/** 有排队需求时,通过邮箱空中断继续推进发送。 */
can_interrupt_enable(CAN0, CAN_INTEN_TMEIE);
return 1;
}
/**
* @brief 发送带 4 字节业务数据的 OTA ACK。
*
* @param packet_num 分包序号,当前实现未使用。
* @param data 指向业务数据缓冲区的指针。
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 CAN_transmiter::sendDATA_ACK(u32 packet_num, u8 *data) {
/**
* 兼容旧协议格式:
* 前两个字节固定为 0xBA 0xC0,后四字节带上包号/状态信息。
*/
(void)packet_num;
memset(transmit_data, 0, sizeof(transmit_data));
transmit_data[0] = 0xBA;
@@ -200,11 +325,32 @@ u8 CAN_transmiter::sendDATA_ACK(u32 packet_num, u8 *data) {
return CANFD_AsyncSend(CANFD_OTA_ACK_ID, 6, transmit_data);
}
/**
* @brief 发送备份恢复提示帧。
*
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 CAN_transmiter::sendBackup() {
/**
* 发送一个空 ACK 帧,给外部上位机一个“正在走备份恢复”的提示。
* 当前具体语义依赖对端实现。
*/
return CANFD_AsyncSend(CANFD_OTA_ACK_ID, 0, transmit_data);
}
/**
* @brief 发送简化 OTA ACK。
*
* @param packet_num 分包序号,当前实现未使用。
* @param if_success 是否成功。
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 CAN_transmiter::sendDATA_ACK(u32 packet_num, bool if_success) {
/**
* 简化 ACK 版本:
* if_success 为真时写入 0x0001;
* 为假则发送全 0,保留给对端区分处理。
*/
(void)packet_num;
memset(transmit_data, 0, 4);
if (if_success) {
@@ -214,11 +360,22 @@ u8 CAN_transmiter::sendDATA_ACK(u32 packet_num, bool if_success) {
return CANFD_AsyncSend(CANFD_OTA_ACK_ID, 4, transmit_data);
}
void CAN0_RX0_IRQHandler(void) {
/**
* @brief CAN RX0 中断实际处理逻辑。
*
* @return 无。
*/
void CAN_transmiter::onRx0Interrupt() {
/** FIFO0 中存在新帧。 */
if (can_interrupt_flag_get(CAN0, CAN_INT_FLAG_RFL0) != RESET) {
can_interrupt_flag_clear(CAN0, CAN_INT_FLAG_RFL0);
can_receive_message_struct temp_message;
can_message_receive(CAN0, CAN_FIFO0, &temp_message);
/**
* 仅保留 OTA 相关帧,避免 Bootloader 被无关 CAN 报文干扰。
*/
if (temp_message.rx_efid == CANFD_OTA_DATA_ID ||
temp_message.rx_efid == CANFD_OTA_REQ_ID ||
temp_message.rx_efid == CANFD_OTA_ACK_ID ||
@@ -231,6 +388,7 @@ void CAN0_RX0_IRQHandler(void) {
}
}
/** FIFO 满/溢出标志清理。 */
if (can_interrupt_flag_get(CAN0, CAN_INT_FLAG_RFF0) != RESET) {
can_interrupt_flag_clear(CAN0, CAN_INT_FLAG_RFF0);
}
@@ -239,28 +397,53 @@ void CAN0_RX0_IRQHandler(void) {
}
}
void CAN0_TX_IRQHandler(void) {
/**
* @brief CAN TX 中断实际处理逻辑。
*
* @return 无。
*/
void CAN_transmiter::onTxInterrupt() {
/** 只要任意邮箱空出来,就尝试从软件队列补一帧出去。 */
if (can_flag_get(CAN0, CAN_FLAG_TME0) != RESET ||
can_flag_get(CAN0, CAN_FLAG_TME1) != RESET ||
can_flag_get(CAN0, CAN_FLAG_TME2) != RESET) {
can_tx_queue_item_t next_msg;
if (CAN_TX_QueuePop(&next_msg)) {
queue_transmit_message.tx_ft = CAN_FT_DATA;
queue_transmit_message.tx_ff = CAN_FF_EXTENDED;
queue_transmit_message.tx_efid = next_msg.id;
queue_transmit_message.tx_dlen = next_msg.length;
queue_transmit_message.fd_flag = CAN_FDF_FDFRAME;
queue_transmit_message.fd_brs = ENABLE;
queue_transmit_message.fd_esi = DISABLE;
tx_queue_item_t next_msg;
if (queuePop(&next_msg)) {
s_queueTransmitMessage.tx_ft = CAN_FT_DATA;
s_queueTransmitMessage.tx_ff = CAN_FF_EXTENDED;
s_queueTransmitMessage.tx_efid = next_msg.id;
s_queueTransmitMessage.tx_dlen = next_msg.length;
s_queueTransmitMessage.fd_flag = CAN_FDF_FDFRAME;
s_queueTransmitMessage.fd_brs = ENABLE;
s_queueTransmitMessage.fd_esi = DISABLE;
for (u8 i = 0; i < next_msg.length; ++i) {
queue_transmit_message.tx_data[i] = next_msg.data[i];
s_queueTransmitMessage.tx_data[i] = next_msg.data[i];
}
can_message_transmit(CAN0, &queue_transmit_message);
can_message_transmit(CAN0, &s_queueTransmitMessage);
} else {
/** 队列已经空了,就关掉 TX 中断,避免空转。 */
can_interrupt_disable(CAN0, CAN_INTEN_TMEIE);
}
/** 清三个邮箱空标志。 */
can_flag_clear(CAN0, CAN_FLAG_TME0);
can_flag_clear(CAN0, CAN_FLAG_TME1);
can_flag_clear(CAN0, CAN_FLAG_TME2);
}
}
/**
* @brief CAN0 RX FIFO0 interrupt service routine.
* @return none
*/
extern "C" void CAN0_RX0_IRQHandler(void) {
CAN_transmiter::onRx0Interrupt();
}
/**
* @brief CAN0 transmit interrupt service routine.
* @return none
*/
extern "C" void CAN0_TX_IRQHandler(void) {
CAN_transmiter::onTxInterrupt();
}
+116 -7
View File
@@ -1,8 +1,15 @@
#ifndef _CAN_H
#define _CAN_H
/**
* @file Can.h
* @brief Bootloader CAN-FD 通信适配层头文件。
*
* 定义 CAN 初始化、收发队列与 OTA 回包接口。
*/
#include <stdint.h>
#include "sys/config.h"
#include "config.h"
#ifdef __cplusplus
extern "C" {
@@ -11,30 +18,132 @@ extern "C" {
#include "gd32e50x_can.h"
#include <stdio.h>
/**
* @brief 初始化 CAN0 相关 GPIO 与引脚复用。
*
* @return 0 表示初始化结束。
*/
u8 can_gpio_init(void);
/**
* @brief 完成 CAN-FD 控制器、滤波器与中断配置。
*
* @return 无。
*/
void canfd_config(void);
/**
* @brief 初始化 CAN DMA 通道。
*
* @note 当前实现未启用 DMA,该函数作为保留占位接口。
*
* @return 无。
*/
void can_dma_init(void);
/**
* @brief CAN0 FIFO0 接收中断服务函数。
*
* @return 无。
*/
void CAN0_RX0_IRQHandler(void);
/**
* @brief CAN0 发送邮箱空中断服务函数。
*
* @return 无。
*/
void CAN0_TX_IRQHandler(void);
/**
* @brief Bootloader 的 CAN-FD 通信适配层。
*
* 负责初始化 CAN0 外设、收发 OTA 相关帧,并在发送邮箱繁忙时
* 通过软件队列缓存待发消息。
*/
class CAN_transmiter {
private:
can_trasnmit_message_struct transmit_message;
u8 transmit_mailbox = CAN_NOMAILBOX;
u8 transmit_data[64];
static constexpr u8 kTxQueueSize = 8U;
typedef struct {
u32 id; /**< 扩展帧 ID */
u8 length; /**< 有效载荷长度(CAN-FD DLC 归一化后的字节数) */
u8 data[64]; /**< 最多支持 64 字节 CAN-FD 载荷 */
u8 valid; /**< 当前槽位是否有效 */
} tx_queue_item_t;
static tx_queue_item_t s_txQueue[kTxQueueSize]; /**< 软件发送环形队列。 */
static volatile u8 s_txQueueHead; /**< 软件发送队列头索引。 */
static volatile u8 s_txQueueTail; /**< 软件发送队列尾索引。 */
static volatile u8 s_txQueueCount; /**< 软件发送队列当前元素数。 */
static can_trasnmit_message_struct s_queueTransmitMessage; /**< 发送中断复用帧对象。 */
can_trasnmit_message_struct transmit_message; /**< 当前正在组装/发送的 CAN-FD 帧。 */
u8 transmit_mailbox = CAN_NOMAILBOX; /**< can_message_transmit() 返回的邮箱号。 */
u8 transmit_data[64]; /**< 通用发送缓冲区,最大支持 64 字节 CAN-FD 载荷。 */
/** @brief 向软件发送队列压入一帧待发消息。 */
static u8 queuePush(u32 id, u8 length, u8 *data);
/** @brief 从软件发送队列弹出一帧待发消息。 */
static u8 queuePop(tx_queue_item_t *item);
public:
/** @brief 初始化 CAN 控制器与软件状态。
*
* @return 0 表示初始化完成。
*/
u8 init(void);
can_receive_message_struct receive_message;
volatile u8 can0_receive_count;
volatile FlagStatus can0_send_flag;
can_receive_message_struct receive_message; /**< 最近收到的一帧 OTA 相关消息。 */
volatile u8 can0_receive_count; /**< 等待主循环处理的接收帧计数。 */
volatile FlagStatus can0_send_flag; /**< 发送完成标记。 */
/**
* @brief 轮询处理最近收到的 CAN OTA 帧。
*
* 该函数会根据接收帧 ID 把消息转交给 OTA 状态机处理。
*
* @return 无。
*/
void communication_check();
/** @brief 异步发送一帧 CAN-FD 数据。
*
* 当硬件发送邮箱不可用时,函数会尝试把该帧压入软件队列。
*
* @param ID 扩展帧 ID。
* @param length 原始数据长度。
* @param data 指向待发送数据缓冲区的指针。
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 CANFD_AsyncSend(u32 ID, u8 length, u8 *data);
/** @brief 发送简化 OTA ACK 帧。
*
* @param packet_num 分包序号,当前实现未实际使用。
* @param if_success 是否成功,true 表示成功。
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 sendDATA_ACK(u32 packet_num, bool if_success);
/** @brief 发送带 4 字节有效载荷的 OTA ACK 帧。
*
* @param packet_num 分包序号,当前实现未实际使用。
* @param data 指向 4 字节业务数据的缓冲区指针。
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 sendDATA_ACK(u32 packet_num, u8 *data);
/** @brief 发送「备份恢复」提示帧。
*
* @return 1 表示发送成功或成功入队,0 表示失败。
*/
u8 sendBackup();
/** @brief CAN RX0 中断实际处理逻辑。 */
static void onRx0Interrupt();
/** @brief CAN TX 中断实际处理逻辑。 */
static void onTxInterrupt();
};
#ifdef __cplusplus
+395
View File
@@ -0,0 +1,395 @@
#include "SpiOta.h"
#include "core_cm33.h"
#include "gd32e50x_gpio.h"
#include "gd32e50x_exti.h"
#include "gd32e50x_rcu.h"
#include "gd32e50x_misc.h"
#include "gd32e50x_spi.h"
#include <stdio.h>
#include <string.h>
/**
* @file SpiOta.cpp
* @brief Bootloader SPI OTA 从机模块实现(PendSV 延迟响应版)。
*
* CS 释放后,EXTI ISR 只做轻量校验(SOF + LEN + CRC),
* 校验通过则触发 PendSV(最低优先级 ISR),由 PendSV ISR 调用
* handleLinkMessage 完成帧处理和响应准备。
*/
OTAHandler *SpiOta::s_otaHandler = nullptr;
volatile bool SpiOta::s_csActive = false;
volatile u16 SpiOta::s_rxStreamLen = 0U;
volatile u16 SpiOta::s_txStreamPos = 0U;
u8 SpiOta::s_rxStream[SpiOta::kRxBufSize] = {};
u8 SpiOta::s_txBuf[SpiOta::kTxBufSize] = {};
volatile u16 SpiOta::s_txLen = 0U;
volatile bool SpiOta::s_txPending = false;
volatile bool SpiOta::s_isrLogPending = false;
u8 SpiOta::s_isrLogBuf[kIsrLogBufSize] = {};
u16 SpiOta::s_isrLogLen = 0U;
volatile bool SpiOta::s_pendSVPending = false;
/* ============================================================
* CRC8 / 帧构建 / 帧解析
* ============================================================ */
u8 SpiOta::crc8(const u8 *data, u16 len) {
u8 crc = 0x00U;
for (u16 i = 0; i < len; ++i) { crc ^= data[i]; }
return crc;
}
bool SpiOta::buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
u8 *out, u16 *out_len) {
if (!out || !out_len) { return false; }
u16 total_len = (u16)(kFrameOverhead + payload_len);
if (total_len > kTxBufSize) { return false; }
out[0] = kSof;
out[1] = (u8)type;
out[2] = (u8)(payload_len & 0xFFU);
out[3] = (u8)((payload_len >> 8) & 0xFFU);
if (payload && payload_len > 0U) { memcpy(&out[4], payload, payload_len); }
out[4 + payload_len] = crc8(out, (u16)(4U + payload_len));
*out_len = total_len;
return true;
}
bool SpiOta::tryParseFrame(const u8 *frame, u16 frame_len,
spi_ota_frame_type_t *type,
const u8 **payload,
u16 *payload_len) {
if (!frame || frame_len < kFrameOverhead) { return false; }
if (frame[0] != kSof) { return false; }
u16 len = (u16)(frame[2] | ((u16)frame[3] << 8));
u16 expected = (u16)(kFrameOverhead + len);
if (expected != frame_len) { return false; }
if (crc8(frame, (u16)(4U + len)) != frame[4U + len]) { return false; }
if (type) { *type = (spi_ota_frame_type_t)frame[1]; }
if (payload) { *payload = &frame[4]; }
if (payload_len){ *payload_len = len; }
return true;
}
ota_link_msg_type_t SpiOta::mapMsgType(spi_ota_frame_type_t type) {
switch (type) {
case SPI_OTA_CMD_REQ: return OTA_LINK_MSG_REQ;
case SPI_OTA_CMD_DATA: return OTA_LINK_MSG_DATA;
default: return OTA_LINK_MSG_CANCEL;
}
}
/* ============================================================
* 回包发送适配器(ISR 安全)
* ============================================================ */
void SpiOta::sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
spi_ota_frame_type_t rsp_type = SPI_OTA_RSP_ACK;
if (type == OTA_LINK_TX_WAIT) { rsp_type = SPI_OTA_RSP_WAIT; }
u8 frame_buf[kTxBufSize] = {};
u16 built_len = 0U;
if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) {
queueTxFrame(frame_buf, built_len);
}
}
uint32_t SpiOta::enterCritical() {
uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
void SpiOta::exitCritical(uint32_t primask) {
__set_PRIMASK(primask);
}
void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
if (!frame || len == 0U || len > kTxBufSize) { return; }
bool enable_tbe = false;
uint32_t primask = enterCritical();
memcpy(s_txBuf, frame, len);
s_txLen = len;
s_txPending = true;
s_txStreamPos = 0U;
enable_tbe = s_csActive;
exitCritical(primask);
if (enable_tbe) { spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_TBE); }
}
/* ============================================================
* EXTI ISR:轻量校验 + 触发 PendSV
* ============================================================ */
void SpiOta::isrValidateAndPend() {
u16 rx_len = s_rxStreamLen;
if (rx_len < kFrameOverhead) { return; }
if (s_rxStream[0] != kSof) { return; }
u16 payload_len = (u16)(s_rxStream[2] | ((u16)s_rxStream[3] << 8));
u16 expected = (u16)(kFrameOverhead + payload_len);
if (expected > rx_len) { return; }
if (crc8(s_rxStream, (u16)(4U + payload_len)) != s_rxStream[4U + payload_len]) {
u8 err = 0xEEU;
u8 err_frame[kTxBufSize] = {};
u16 built = 0U;
if (buildFrame(SPI_OTA_RSP_ERROR, &err, 1U, err_frame, &built)) {
queueTxFrame(err_frame, built);
}
return;
}
/* ★ 校验通过,触发 PendSV ★ */
s_pendSVPending = true;
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
isrLogFrame("[EXTI-OK]", s_rxStream, expected);
}
/* ============================================================
* PendSV ISR:在低优先级 ISR 中完成帧处理
* ============================================================ */
void SpiOta::onPendSvProcess() {
if (!s_pendSVPending) { return; }
s_pendSVPending = false;
u16 frame_len = s_rxStreamLen;
spi_ota_frame_type_t frame_type;
const u8 *payload = nullptr;
u16 payload_len = 0U;
if (!tryParseFrame(s_rxStream, frame_len, &frame_type, &payload, &payload_len)) {
return;
}
if (s_otaHandler == nullptr) { return; }
s_otaHandler->setTransportSender(sender);
ota_link_msg_type_t msg_type = mapMsgType(frame_type);
s_otaHandler->handleLinkMessage(msg_type, const_cast<u8 *>(payload), (u8)payload_len);
isrLogFrame("[PendSV-DONE]", s_txBuf, s_txLen);
}
/* ============================================================
* ISR 内延迟日志
* ============================================================ */
void SpiOta::isrLogFrame(const char *tag, const u8 *data, u16 len) {
if (s_isrLogPending) { return; }
u16 pos = 0U;
if (tag) {
while (*tag && pos < kIsrLogBufSize - 1U) { s_isrLogBuf[pos++] = (u8)*tag++; }
s_isrLogBuf[pos++] = ' ';
}
const u16 preview = (len < 32U) ? len : 32U;
for (u16 i = 0U; i < preview && pos + 2U < kIsrLogBufSize; ++i) {
static const char hx[] = "0123456789ABCDEF";
s_isrLogBuf[pos++] = (u8)hx[data[i] >> 4];
s_isrLogBuf[pos++] = (u8)hx[data[i] & 0x0FU];
}
s_isrLogBuf[pos] = 0U;
s_isrLogLen = pos;
s_isrLogPending = true;
}
/* ============================================================
* 硬件初始化
* ============================================================ */
void SpiOta::configureGpio() {
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
gpio_pin_remap_config(GPIO_SPI0_REMAP, ENABLE);
gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7);
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
}
void SpiOta::configureSpi0() {
rcu_periph_clock_enable(RCU_SPI0);
spi_i2s_deinit(SPI0);
spi_parameter_struct spi_init_struct;
spi_struct_para_init(&spi_init_struct);
spi_init_struct.device_mode = SPI_SLAVE;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.prescale = SPI_PSC_8;
spi_init(SPI0, &spi_init_struct);
spi_nss_internal_low(SPI0);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
nvic_irq_enable(SPI0_IRQn, 1U, 0U);
spi_enable(SPI0);
}
void SpiOta::configureExti() {
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_15);
exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
exti_interrupt_enable(EXTI_15);
exti_interrupt_flag_clear(EXTI_15);
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
/* PendSV 优先级设最低(不阻塞 SPI/EXTI),通过 SCB->ICSR 触发,无需 NVIC_EnableIRQ */
NVIC_SetPriority(PendSV_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
}
void SpiOta::init() {
s_csActive = false;
s_rxStreamLen = 0U;
s_txStreamPos = 0U;
s_txLen = 0U;
s_txPending = false;
s_isrLogPending = false;
s_pendSVPending = false;
memset(s_rxStream, 0, sizeof(s_rxStream));
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
configureGpio();
configureSpi0();
configureExti();
}
/* ============================================================
* CS 边沿处理
* ============================================================ */
void SpiOta::onCsAssert() {
if (s_csActive) { return; }
s_csActive = true;
s_rxStreamLen = 0U;
s_txStreamPos = 0U;
if (s_txPending && s_txLen > 0U) {
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_TBE);
}
}
void SpiOta::onCsRelease() {
if (!s_csActive) { return; }
s_csActive = false;
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
if (s_rxStreamLen > 0U && s_rxStreamLen <= kRxBufSize) {
isrValidateAndPend();
}
}
/* ============================================================
* SPI0 中断处理
* ============================================================ */
void SpiOta::onSpiInterrupt() {
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); }
u8 byte = (u8)(spi_i2s_data_receive(SPI0) & 0xFFU);
if (s_csActive && s_rxStreamLen < kRxBufSize) { s_rxStream[s_rxStreamLen++] = byte; }
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_TBE) != RESET) {
if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); }
u8 tx_byte = 0xFFU;
if (s_csActive && s_txPending && s_txStreamPos < s_txLen) {
tx_byte = s_txBuf[s_txStreamPos++];
if (s_txStreamPos >= s_txLen) {
s_txPending = false;
s_txLen = 0U;
s_txStreamPos = 0U;
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
}
} else {
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
}
spi_i2s_data_transmit(SPI0, tx_byte);
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
(void)spi_i2s_data_receive(SPI0);
s_rxStreamLen = 0U;
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
}
}
void SpiOta::onCsExtiInterrupt() {
if (exti_interrupt_flag_get(EXTI_15) != RESET) {
exti_interrupt_flag_clear(EXTI_15);
if (gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); }
else { onCsRelease(); }
}
}
/* ============================================================
* 主循环接口
* ============================================================ */
void SpiOta::poll() {
if (s_isrLogPending) {
printf("[SPI-BOOT] %s\r\n", s_isrLogBuf);
s_isrLogPending = false;
}
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
}
void SpiOta::setSender(OTAHandler *handler) { s_otaHandler = handler; }
void SpiOta::feedRxBytes(const u8 *data, u16 len) {
if (!data || len == 0U || !s_csActive) { return; }
if (len > kRxBufSize) { data += (len - kRxBufSize); len = kRxBufSize; }
memcpy(s_rxStream, data, len);
s_rxStreamLen = len;
}
bool SpiOta::hasTxFrame() { return s_txPending; }
u16 SpiOta::copyTxFrame(u8 *out, u16 max_len) {
if (!out || max_len == 0U) { return 0U; }
uint32_t primask = enterCritical();
if (!s_txPending || s_txLen == 0U) { exitCritical(primask); return 0U; }
u16 copy_len = (s_txLen < max_len) ? s_txLen : max_len;
memcpy(out, s_txBuf, copy_len);
s_txPending = false;
s_txLen = 0U;
s_txStreamPos = 0U;
exitCritical(primask);
return copy_len;
}
/* ============================================================
* C 风格兼容包装
* ============================================================ */
void spi_ota_init(void) { SpiOta::init(); }
void spi_ota_poll(void) { SpiOta::poll(); }
void spi_ota_set_sender(OTAHandler *handler) { SpiOta::setSender(handler); }
void spi_ota_on_cs_assert(void) { SpiOta::onCsAssert(); }
void spi_ota_on_cs_release(void) { SpiOta::onCsRelease(); }
void spi_ota_gpio_init(void) { SpiOta::configureGpio(); }
void spi_ota_spi0_init(void) { SpiOta::configureSpi0(); }
void spi_ota_exti_init(void) { SpiOta::configureExti(); }
void spi_ota_irq_handler(void) { SpiOta::onSpiInterrupt(); }
void spi_ota_cs_exti_handler(void) { SpiOta::onCsExtiInterrupt(); }
void spi_ota_feed_rx_bytes(const u8 *data, u16 len) { SpiOta::feedRxBytes(data, len); }
bool spi_ota_has_tx_frame(void) { return SpiOta::hasTxFrame(); }
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len) { return SpiOta::copyTxFrame(out, max_len); }
/**
* @brief SPI0 中断向量入口。
*/
extern "C" void SPI0_IRQHandler(void) {
SpiOta::onSpiInterrupt();
}
/**
* @brief PendSV 中断向量入口。
* @note 调用 SpiOta::onPendSvProcess() 在低优先级 ISR 中完成帧处理。
*/
extern "C" void PendSV_Handler(void) {
SpiOta::onPendSvProcess();
}
+108
View File
@@ -0,0 +1,108 @@
#ifndef __SPI_OTA_H__
#define __SPI_OTA_H__
#include "OTA.h"
/**
* @file SpiOta.h
* @brief SPI OTA 从机协议头文件(PendSV 延迟响应版)。
*
* CS 释放后,EXTI ISR 只做轻量校验(SOF + LEN + CRC),
* 校验通过则触发 PendSV(最低优先级 ISR),由 PendSV 调用
* handleLinkMessage 完成帧处理和响应准备。
*/
typedef enum {
SPI_OTA_CMD_REQ = 0x01,
SPI_OTA_CMD_DATA = 0x02,
SPI_OTA_CMD_CANCEL = 0x03,
SPI_OTA_RSP_ACK = 0x80,
SPI_OTA_RSP_WAIT = 0x81,
SPI_OTA_RSP_ERROR = 0x82
} spi_ota_frame_type_t;
class SpiOta {
public:
static constexpr u8 kSof = 0xA5U;
static constexpr u16 kRxBufSize = 256U;
static constexpr u16 kTxBufSize = 128U;
static constexpr u16 kFrameOverhead = 5U;
static constexpr u16 kIsrLogBufSize = 128U;
static void init();
static void poll();
static void setSender(OTAHandler *handler);
static void onCsAssert();
static void onCsRelease();
static void configureGpio();
static void configureSpi0();
static void configureExti();
static void onSpiInterrupt();
static void onCsExtiInterrupt();
static void feedRxBytes(const u8 *data, u16 len);
static bool hasTxFrame();
static u16 copyTxFrame(u8 *out, u16 max_len);
/** @brief EXTI ISR 内轻量校验,通过则触发 PendSV。 */
static void isrValidateAndPend();
/** @brief PendSV ISR 内执行帧处理和响应准备。 */
static void onPendSvProcess();
/** @brief ISR 内轻量日志写入延迟打印缓冲。 */
static void isrLogFrame(const char *tag, const u8 *data, u16 len);
/* ---- PendSV 需要访问的成员放 public ---- */
static OTAHandler *s_otaHandler;
static volatile bool s_csActive;
static volatile u16 s_rxStreamLen;
static volatile u16 s_txStreamPos;
static u8 s_rxStream[kRxBufSize];
static u8 s_txBuf[kTxBufSize];
static volatile u16 s_txLen;
static volatile bool s_txPending;
static volatile bool s_isrLogPending;
static u8 s_isrLogBuf[kIsrLogBufSize];
static u16 s_isrLogLen;
static volatile bool s_pendSVPending;
private:
static u8 crc8(const u8 *data, u16 len);
static bool buildFrame(spi_ota_frame_type_t type, const u8 *payload, u16 payload_len,
u8 *out, u16 *out_len);
static bool tryParseFrame(const u8 *frame, u16 frame_len,
spi_ota_frame_type_t *type,
const u8 **payload,
u16 *payload_len);
static ota_link_msg_type_t mapMsgType(spi_ota_frame_type_t type);
static void sender(ota_link_tx_type_t type, u8 len, const u8 *data);
static void queueTxFrame(const u8 *frame, u16 len);
static uint32_t enterCritical();
static void exitCritical(uint32_t primask);
};
#ifdef __cplusplus
extern "C" {
#endif
/**
* 引脚约定:CS=PA15, SCK=PB3, MISO=PB4, MOSI=PB5
*/
void spi_ota_init(void);
void spi_ota_poll(void);
void spi_ota_set_sender(OTAHandler *handler);
void spi_ota_on_cs_assert(void);
void spi_ota_on_cs_release(void);
void spi_ota_gpio_init(void);
void spi_ota_spi0_init(void);
void spi_ota_exti_init(void);
void spi_ota_irq_handler(void);
void spi_ota_cs_exti_handler(void);
void spi_ota_feed_rx_bytes(const u8 *data, u16 len);
bool spi_ota_has_tx_frame(void);
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len);
#ifdef __cplusplus
}
#endif
#endif
+160 -19
View File
@@ -1,5 +1,6 @@
#include "main.h"
#include "device/Can.h"
#include "device/SpiOta.h"
#include "sys/config.h"
#include "gd32e50x.h"
#include "sys/OTA.h"
@@ -9,67 +10,207 @@
extern CAN_transmiter CAN0_transmiter;
extern OTAHandler ota;
/** @brief Bootloader 全局运行实例。 */
BootloaderSystem sys;
/**
* @brief 初始化 Bootloader 运行所需的硬件外设。
*
* 初始化顺序依次为 SysTick、串口、CAN、OTA 核心、SPI OTA
* 和睡眠状态检查。
*
* @return 无。
*/
void BootloaderSystem::initHardware() {
systick_config();
USART0_init();
/**
* 初始化顺序尽量从基础时基到通信外设:
* 1. SysTick:为延时和超时逻辑提供基础时钟
* 2. USART0:调试打印输出
* 3. CAN:OTA 主通信链路之一
* 4. OTA 核心状态机
* 5. SPI OTA:备用/新增的 OTA 通道
* 6. 睡眠状态检查
*/
SysTickTimer::init();
BootDebugUart::init();
printf("\r\n[BOOT] power on\r\n");
printf("[BOOT] BOOT=0x%08lX size=0x%08lX APPBACK=0x%08lX APPLATEST=0x%08lX APP_MAX=0x%08lX\r\n",
(unsigned long)BOOTLOADER_START_ADDR,
(unsigned long)BOOTLOADER_SIZE,
(unsigned long)APPBACK_START_ADDR,
(unsigned long)APPLATEST_START_ADDR,
(unsigned long)APP_MAX_SIZE);
printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock);
CAN0_transmiter.init();
check_sleep();
ota.init();
SpiOta::init();
/** 让 SPI OTA 模块在收到完整帧后,转交给 ota 实例处理。 */
SpiOta::setSender(&ota);
/** 检查是否需要进入深度睡眠模式。 */
{
const int sleep_state = BootSleepManager::check();
printf("[BOOT] sleep_check=%d\r\n", sleep_state);
}
}
void BootloaderSystem::run() {
volatile uint32_t *flash_ptr = (volatile uint32_t *)n_APP_LATEST_EN_ADDR;
uint32_t app_enable = *flash_ptr;
flash_ptr = (volatile uint32_t *)n_APP_LATEST_CHECK_ADDR;
uint32_t app_check = *flash_ptr;
/** @brief 心跳打印间隔(毫秒)。 */
static constexpr uint32_t kHeartbeatPrintIntervalMs = 2000U;
/**
* @brief 主循环辅助函数:轮询 CAN + SPI OTA 后台任务。
*
* @note SPI OTA 帧解析和响应准备已在 ISR 内完成,
* poll() 只负责消费 ISR 延迟日志和推进 OTA 后台任务(延迟擦除/跳转)。
*/
static void pollAllLinks(void) {
CAN0_transmiter.communication_check();
SpiOta::poll();
}
/**
* @brief 定时心跳打印:每隔 kHeartbeatPrintIntervalMs 输出一条状态摘要。
*
* @param[in,out] heartbeat_counter 上次打印后的累计毫秒数。
* @param[in] loop_delay_ms 每轮主循环的延时毫秒数。
* @param[in] path_label 当前停留路径描述,如 "wait_ota_window"。
*/
static void heartbeatPrint(uint32_t &heartbeat_counter, uint32_t loop_delay_ms, const char *path_label) {
heartbeat_counter += loop_delay_ms;
if (heartbeat_counter >= kHeartbeatPrintIntervalMs) {
heartbeat_counter = 0U;
printf("[BOOT] \xe2\x99\xa5 %s ota_active=%d spi_tx_pending=%d\r\n",
path_label,
ota.isActive() ? 1 : 0,
SpiOta::hasTxFrame() ? 1 : 0);
}
}
/**
* @brief 执行 Bootloader 主流程。
*
* 根据当前应用区和备份应用区的状态标志,选择执行应用跳转、
* OTA 等待或备份恢复逻辑。
*
* @return 无。
*/
void BootloaderSystem::run() {
volatile uint32_t *flash_ptr;
uint32_t app_enable, app_check;
flash_ptr = (volatile uint32_t *)n_APP_LATEST_EN_ADDR;
app_enable = *flash_ptr;
flash_ptr = (volatile uint32_t *)n_APP_LATEST_CHECK_ADDR;
app_check = *flash_ptr;
printf("[BOOT] app_enable=0x%08lX app_check=0x%08lX\r\n",
(unsigned long)app_enable,
(unsigned long)app_check);
/**
* 情况 1:应用已校验完成,且允许启动。
* 直接跳转到主应用,不在 Bootloader 多停留。
*/
if (app_check == OTA_AVAL && app_enable == OTA_AVAL) {
printf("[BOOT] path=jump_latest_now\r\n");
ota.jumpToApplication();
} else if (app_check == OTA_AVAL && app_enable == OTA_NOT_AVAL) {
}
/**
* 情况 2:应用是有效的,但当前被标记为"暂不启动"。
* 这通常表示系统希望给 OTA 留一个短暂窗口,若窗口内没有升级请求,再启动应用。
*/
else if (app_check == OTA_AVAL && app_enable == OTA_NOT_AVAL) {
uint32_t delay_count = 0;
uint32_t heartbeat_counter = 0U;
int ota_requested = 0;
printf("[BOOT] path=wait_ota_window timeout_ms=%lu\r\n", (unsigned long)APP_DELAY_BOOT_TIME);
while (delay_count < APP_DELAY_BOOT_TIME) {
CAN0_transmiter.communication_check();
ota.process();
pollAllLinks();
heartbeatPrint(heartbeat_counter, 10U, "wait_ota_window");
if (ota.isActive()) {
ota_requested = 1;
printf("[BOOT] ota_requested at %lu ms\r\n", (unsigned long)delay_count);
break;
}
delay_1ms(10);
delay_count += 10;
}
if (ota_requested) {
/**
* 已经进入 OTA 会话:
* 后续不再尝试跳应用,持续停留在 Bootloader 内处理升级。
*/
printf("[BOOT] path=stay_in_ota\r\n");
uint32_t hb_ota = 0U;
while (1) {
CAN0_transmiter.communication_check();
ota.process();
pollAllLinks();
heartbeatPrint(hb_ota, 10U, "stay_in_ota");
delay_1ms(10);
}
} else {
/** 等待窗口内没有任何 OTA 请求,正常启动当前应用。 */
printf("[BOOT] ota_window_timeout_then_jump\r\n");
ota.jumpToApplication();
}
} else {
}
/**
* 情况 3:当前主应用区不可用。
* 尝试检查备份区是否可恢复;如果可恢复,则复制到主应用区后启动。
*/
else {
uint32_t back_aval, back_check;
flash_ptr = (volatile uint32_t *)n_APP_BACKUP_AVAL_ADDR;
uint32_t back_aval = *flash_ptr;
back_aval = *flash_ptr;
flash_ptr = (volatile uint32_t *)n_APP_BACKUP_CHECK_ADDR;
uint32_t back_check = *flash_ptr;
back_check = *flash_ptr;
printf("[BOOT] back_aval=0x%08lX back_check=0x%08lX\r\n",
(unsigned long)back_aval,
(unsigned long)back_check);
if (back_check == OTA_AVAL && back_aval == OTA_AVAL) {
/**
* 备份区可用:
* 1. 先发送一帧提示,表示即将走备份恢复路径;
* 2. 把备份应用搬运到主应用区;
* 3. 跳转运行。
*/
printf("[BOOT] path=restore_backup_then_jump\r\n");
CAN0_transmiter.sendBackup();
move_app_data();
ota.jumpToApplication();
} else {
/**
* 当前区和备份区都不可用:
* 只能长期停留在 Bootloader,等待外部重新下发固件。
*/
printf("[BOOT] path=stay_boot_wait_image\r\n");
uint32_t hb_wait = 0U;
while (1) {
CAN0_transmiter.communication_check();
ota.process();
pollAllLinks();
heartbeatPrint(hb_wait, 10U, "stay_boot_wait_image");
delay_1ms(10);
}
}
}
}
/**
* @brief Bootloader 程序入口。
*
* @return 理论上不会返回;若异常返回则维持空转。
*/
int main(void) {
sys.initHardware();
sys.run();
/** 理论上 run() 不会返回,这里仅做保险。 */
while (1) {
}
}
}
+26
View File
@@ -3,9 +3,35 @@
#include <stdint.h>
/**
* @brief Bootloader 顶层调度对象。
*
* 负责初始化系统硬件,并根据 Flash 中保存的应用状态决定
* 是直接跳转主应用、等待 OTA 请求,还是执行备份恢复流程。
*/
class BootloaderSystem {
public:
/**
* @brief 初始化 Bootloader 运行所需的硬件外设。
*
* 初始化内容包括 SysTick、串口、CAN、OTA 状态机、SPI OTA
* 以及睡眠状态检查。
*
* @return 无。
*/
void initHardware();
/**
* @brief 执行 Bootloader 主流程。
*
* 该函数会读取应用状态标志,并根据状态决定后续行为:
* - 直接跳转主应用;
* - 在限定时间内等待 OTA 请求;
* - 从备份应用区恢复后跳转;
* - 永久停留在 Bootloader 等待升级。
*
* @return 无。
*/
void run();
};
+473 -35
View File
@@ -16,7 +16,76 @@
extern CAN_transmiter CAN0_transmiter;
OTAHandler ota;
/**
* OTA.cpp 模块说明
* ----------------
* 这里实现 Bootloader 的核心 OTA 逻辑:
* 1. 握手与会话密钥派生
* 2. 固件大小确认
* 3. 擦除应用区
* 4. 固件包校验、解密、写 Flash
* 5. 超时处理、错误上报、应用跳转
*
* 设计上,OTA 状态机与底层传输层解耦:
* - CAN 与 SPI 只负责收发原始消息
* - 进入本文件后统一走 handleLinkMessage()
*/
/**
* @brief CAN 链路回包发送适配器。
*
* @param type OTA 回包类型。
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
static void ota_can_sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
/**
* 将 OTA 统一回包类型映射到具体 CAN ID。
* 当前只定义了 ACK 与 WAIT 两类返回帧。
*/
u32 can_id = 0U;
if (type == OTA_LINK_TX_ACK) {
can_id = CANFD_OTA_ACK_ID;
} else if (type == OTA_LINK_TX_WAIT) {
can_id = CANFD_OTA_WAIT_ID;
} else {
return;
}
CAN0_transmiter.CANFD_AsyncSend(can_id, len, const_cast<u8 *>(data));
}
/**
* @brief SPI 链路回包发送占位适配器。
*
* @param type OTA 回包类型。
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
static void ota_spi_sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
(void)type;
(void)len;
(void)data;
/**
* 这里仍保留占位实现。
* 当前 SPI OTA 的真正回包发送,是由 device/SpiOta.cpp 内的 spi_ota_sender() 接管的。
*/
}
/**
* @brief 向指定 Flash 地址写入一个 32 位字。
*
* @param address 目标地址。
* @param data 目标数据。
* @return FMC 编程状态码。
*/
static inline fmc_state_enum write_ADDRESS(u32 address, u32 data) {
/**
* 对单个配置字进行安全写入。
* 主要用于 OTA 状态页 / 睡眠配置页中的标志位更新。
*/
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(address, data);
@@ -24,7 +93,20 @@ static inline fmc_state_enum write_ADDRESS(u32 address, u32 data) {
return status;
}
/**
* @brief 更新 OTA 配置页中的指定状态字。
*
* @param addr 目标配置字地址。
* @param data 目标写入值。
* @return FLASH_EEPROM_OK 表示成功,FLASH_EEPROM_ERROR 表示失败。
*/
u8 OTA_SET(u32 addr, u32 data) {
/**
* OTA 配置页更新策略:
* - 如果目标值本来就是 data,直接返回成功
* - 如果只是把某个字从擦除态写成 OTA_AVAL(0),可以直接写
* - 其它情况(尤其涉及从 0 改回 1)则必须整页擦除后重写全部 6 个配置字
*/
fmc_state_enum fmc_status;
u32 temp_val[OTA_CONFIG_NUM];
volatile u32 *flash_ptr = (volatile u32 *)addr;
@@ -34,6 +116,9 @@ u8 OTA_SET(u32 addr, u32 data) {
return FLASH_EEPROM_OK;
}
/**
* Flash 只能把 bit 从 1 写成 0,所以写 OTA_AVAL(0x00000000) 时可以尝试直接编程。
*/
if (data == OTA_AVAL) {
fmc_status = write_ADDRESS(addr, data);
if (fmc_status == FMC_READY) {
@@ -41,6 +126,7 @@ u8 OTA_SET(u32 addr, u32 data) {
}
}
/** 读取当前配置页中的全部状态,供擦除后重写。 */
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
temp_val[0] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR;
@@ -54,6 +140,7 @@ u8 OTA_SET(u32 addr, u32 data) {
flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR;
temp_val[5] = *flash_ptr;
/** 整页擦除后重建配置页。 */
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_status = fmc_page_erase(OTA_CONFIG_PAGE_ADDR);
@@ -84,41 +171,128 @@ u8 OTA_SET(u32 addr, u32 data) {
return FLASH_EEPROM_OK;
}
/**
* @brief 从 4 字节小端数组中提取 32 位整数。
*
* @param array 指向至少 4 字节数组的指针。
* @return 提取出的 32 位无符号整数。
*/
static inline uint32_t extract_u32_from_4bytes(const uint8_t *array) {
/**
* 协议里数值字段按小端排列存放:
* byte0 -> 最低字节,byte3 -> 最高字节
*/
return ((uint32_t)array[3] << 24) | ((uint32_t)array[2] << 16) |
((uint32_t)array[1] << 8) | (uint32_t)array[0];
}
void OTAHandler::init() {
/**
* @brief 将 32 位整数按小端写入 4 字节数组。
*/
static inline void write_u32_to_4bytes(uint8_t *array, uint32_t value) {
array[0] = (uint8_t)(value & 0xFFU);
array[1] = (uint8_t)((value >> 8) & 0xFFU);
array[2] = (uint8_t)((value >> 16) & 0xFFU);
array[3] = (uint8_t)((value >> 24) & 0xFFU);
}
/**
* @brief 发送带 0xBA 0xC0 头的结构化 ACK。
*/
static inline void send_structured_ack(uint32_t seq_or_flag) {
u8 send_data[6] = {0xBA, 0xC0, 0x00, 0x00, 0x00, 0x00};
write_u32_to_4bytes(&send_data[2], seq_or_flag);
send_ack_frame(6, send_data);
}
/**
* @brief 初始化 OTA 状态机。
*
* @return 无。
*/
void OTAHandler::init() {
/** 默认回包出口先绑定到 CAN。 */
transport_send = ota_can_sender;
}
/**
* @brief 切换 OTA 回包出口到 CAN。
*
* @return 无。
*/
void ota_bind_can_transport(void) {
ota.setTransportSender(ota_can_sender);
}
/**
* @brief 切换 OTA 回包出口到 SPI。
*
* @return 无。
*/
void ota_bind_spi_transport(void) {
ota.setTransportSender(ota_spi_sender);
}
/**
* @brief 推进 OTA 状态机后台逻辑。
*
* @return 无。
*/
void OTAHandler::process() {
/**
* 把真正耗时的 Flash 擦除动作推迟到主循环做,
* 避免在接收消息的处理路径里执行过重操作。
*/
if (pending_flash_erase) {
pending_flash_erase = false;
eraseApplicationArea();
ota_state = OTA_STATE_RECEIVING_FIRMWARE;
}
if (pending_app_jump) {
pending_app_jump = false;
delay_1ms(20);
jumpToApplication();
return;
}
if (ota_state == OTA_STATE_IDLE) {
/**
* 空闲态持续清零超时计数,并定期发 WAIT 帧,
* 相当于告诉上位机“Bootloader 在线,尚未进入升级事务”。
*/
ota_time = 0;
static u8 zero[1] = {0};
CAN0_transmiter.CANFD_AsyncSend(CANFD_OTA_WAIT_ID, 0, zero);
send_wait_frame(0, zero);
delay_1ms(10);
return;
} else {
/**
* 非空闲态时,按固定节奏推进超时计数。
* 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。
*/
delay_1us(100);
ota_time++;
}
if (ota_time > OTA_TIMEOUT) {
/**
* OTA 会话超时:
* - 复位状态机到空闲态
* - 上报超时错误
* - 如果当前主应用仍有效,则尝试回到主应用运行
*/
ota_time = 0;
ota_state = OTA_STATE_IDLE;
ota_error = OTA_ERROR_TIMEOUT;
volatile u32 *flash_ptr = (volatile u32 *)n_APP_LATEST_CHECK_ADDR;
u32 if_checked = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
u32 if_aval = *flash_ptr;
send_error_frame(OTA_ERROR_TIMEOUT);
send_error_frame(0xFFFFFFFFU, OTA_ERROR_TIMEOUT);
if (if_checked == OTA_AVAL && if_aval == OTA_AVAL) {
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
jumpToApplication();
@@ -126,29 +300,79 @@ void OTAHandler::process() {
}
}
/**
* @brief 通过当前链路发送 ACK。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void OTAHandler::sendAck(u8 len, const u8 *data) {
if (transport_send != nullptr) {
transport_send(OTA_LINK_TX_ACK, len, data);
}
}
/**
* @brief 通过当前链路发送 WAIT。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void OTAHandler::sendWait(u8 len, const u8 *data) {
if (transport_send != nullptr) {
transport_send(OTA_LINK_TX_WAIT, len, data);
}
}
/**
* @brief 校验主应用向量表是否合法。
*
* @retval true 主应用向量表合法。
* @retval false 主应用向量表非法。
*/
bool validateApplication() {
/**
* 通过应用向量表做一个最基本的合法性检查:
* 1. 初始 MSP 必须落在 SRAM 范围内
* 2. Reset_Handler 必须落在主应用区范围内
*/
u32 *app_vector = (u32 *)APPLATEST_START_ADDR;
u32 stack_ptr = app_vector[0];
u32 reset_handler = app_vector[1];
bool stack_valid = (stack_ptr >= 0x20000000U && stack_ptr < 0x20020000U);
bool reset_valid = (reset_handler >= APPLATEST_START_ADDR &&
reset_handler < (APPLATEST_START_ADDR + APP_MAX_SIZE));
return stack_valid && reset_valid;
}
/**
* @brief 跳转执行主应用。
*
* @return 无。
*/
void OTAHandler::jumpToApplication() {
if (!validateApplication()) {
/** 向量表不合法时,拒绝跳转,避免飞到未知地址。 */
printf("[BOOT] jump rejected: invalid app vector @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR);
return;
}
printf("[BOOT] jumping to app @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR);
/** 把当前主应用重新标记为“有效且允许启动”。 */
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
/** 关闭并清空所有 NVIC 中断。 */
for (int i = 0; i < 8; ++i) {
NVIC->ICER[i] = 0xFFFFFFFFU;
NVIC->ICPR[i] = 0xFFFFFFFFU;
}
/** 关闭 Bootloader 运行过程中启用过的外设。 */
can_deinit(CAN0);
rcu_system_clock_source_config(RCU_CKSYSSRC_IRC8M);
@@ -160,18 +384,32 @@ void OTAHandler::jumpToApplication() {
rcu_periph_clock_disable(RCU_TIMER0);
rcu_periph_clock_disable(RCU_DMA0);
/** 把中断向量表切到主应用起始地址。 */
SCB->VTOR = APPLATEST_START_ADDR;
/** 读取主应用向量表中的 MSP 和 Reset_Handler。 */
uint32_t msp_value = *(__IO uint32_t *)APPLATEST_START_ADDR;
uint32_t reset_handler = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U);
typedef void (*AppEntryFunc)(void);
AppEntryFunc app_entry = (AppEntryFunc)reset_handler;
/** 切栈后直接跳转执行主应用。 */
__set_MSP(msp_value);
rcu_all_reset_flag_clear();
app_entry();
}
/**
* @brief 擦除当前主应用区。
*
* @return 无。
*/
void OTAHandler::eraseApplicationArea() {
/**
* 开始真正接收新固件前,先把当前主应用状态全部标记为无效,
* 防止中途掉电后误跳到一个被擦了一半的应用。
*/
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_NOT_AVAL);
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL);
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_NOT_AVAL);
@@ -184,7 +422,7 @@ void OTAHandler::eraseApplicationArea() {
fmc_state_enum status = fmc_page_erase(addr);
if (status != FMC_READY) {
ota_error = OTA_ERROR_FLASH_ERROR;
send_error_frame(OTA_ERROR_FLASH_ERROR);
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
break;
}
}
@@ -192,7 +430,20 @@ void OTAHandler::eraseApplicationArea() {
__enable_irq();
}
/**
* @brief 向主应用区写入一段固件数据。
*
* @param offset 相对于主应用起始地址的偏移量。
* @param data 指向待写入数据的指针。
* @param size 数据长度。
* @return 无。
*/
void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
/**
* 将一个分包写入主应用区:
* - 先按 4 字节对齐批量写
* - 剩余不足 4 字节时,用 0xFF 补齐一个 word 再写入
*/
fmc_unlock();
u32 write_addr = APPLATEST_START_ADDR + offset;
u32 i = 0;
@@ -207,14 +458,35 @@ void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
fmc_lock();
}
void OTAHandler::handleOTAMessage(u32 id, u8 *data, u8 len) {
if (id == CANFD_OTA_CANCEL_ID) {
/**
* @brief 处理统一抽象后的 OTA 链路消息。
*
* @param type 链路消息类型。
* @param data 指向消息数据缓冲区的指针。
* @param len 消息数据长度。
* @return 无。
*/
void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
/**
* CANCEL:无条件终止当前 OTA 会话,回到空闲态。
*/
if (type == OTA_LINK_MSG_CANCEL) {
ota_state = OTA_STATE_IDLE;
ota_time = 0;
ota_error = OTA_ERROR_NONE;
return;
} else if (id == CANFD_OTA_DATA_ID) {
}
/**
* DATA:根据当前状态解释为“固件分包”或“固件大小信息”等。
*/
else if (type == OTA_LINK_MSG_DATA) {
ota_time = 0;
/**
* 正常固件数据包:
* - 固定 64 字节
* - 含会话号、包号、48 字节密文、结束标志、CRC32
*/
if (len == 64U && ota_state == OTA_STATE_RECEIVING_FIRMWARE) {
u32 receive_session_num = extract_u32_from_4bytes(data);
(void)receive_session_num;
@@ -222,15 +494,20 @@ void OTAHandler::handleOTAMessage(u32 id, u8 *data, u8 len) {
u32 receive_IFEND = extract_u32_from_4bytes(data + 56);
const u8 *receive_encrypted_data = data + 8;
const u8 *receive_dataCRC32 = data + 60;
u8 decrypted_data[OTA_PACKET_SIZE];
u8 calculated_dataCRC32[4];
/** 使用当前会话密钥解密 48 字节数据区。 */
aes128_set_key(&aes_ctx, current_K_session);
aes128_ecb_decrypt_48bytes(&aes_ctx, receive_encrypted_data, decrypted_data);
/** 校验分包 CRC32(密钥参与 CRC,防止简单重放/篡改)。 */
calculate_crc32_with_key(current_K_session, decrypted_data, calculated_dataCRC32);
if (memcmp(receive_dataCRC32, calculated_dataCRC32, SIZE_U32) != 0) {
ota_error = OTA_ERROR_INVALID_CRC;
send_error_frame(OTA_ERROR_INVALID_CRC);
receive_packet_num -= 1U;
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_CRC);
return;
} else {
u32 offset = receive_packet_num * OTA_PACKET_SIZE;
if (offset + OTA_PACKET_SIZE <= APP_MAX_SIZE) {
@@ -238,21 +515,31 @@ void OTAHandler::handleOTAMessage(u32 id, u8 *data, u8 len) {
} else {
ota_error = OTA_ERROR_INVALID_SIZE;
ota_state = OTA_STATE_IDLE;
send_error_frame(OTA_ERROR_INVALID_SIZE);
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_SIZE);
}
}
/**
* IFEND == 0xFFFFFFFF 表示最后一包:
* 先反馈“全部写入完成,等待 FINISH_OTA”,
* 真正跳转放到收到 0x0A AC 后再执行。
*/
if (receive_IFEND == 0xFFFFFFFFU) {
u8 pack_number[4] = {0xFF, 0xFF, 0xFF, 0xFF};
CAN0_transmiter.sendDATA_ACK(CANFD_OTA_ACK_ID, pack_number);
ota_state = OTA_STATE_FINISHED;
jumpToApplication();
send_structured_ack(0xFFFFFFFFU);
return;
}
/** 正常情况下 ACK 下一个希望收到的包号。 */
u32 request_packet = receive_packet_num + 1U;
u8 pack_number[4];
memcpy(pack_number, &request_packet, 4);
CAN0_transmiter.sendDATA_ACK(CANFD_OTA_ACK_ID, pack_number);
} else if (len == 4U && ota_state == OTA_STATE_CHECKING_FIRMWARE) {
send_structured_ack(request_packet);
}
/**
* 固件大小阶段:
* 在握手成功后,由上位机先发 4 字节固件大小,
* Bootloader 校验是否超过 APP_MAX_SIZE,并准备擦除 Flash。
*/
else if (len == 4U && ota_state == OTA_STATE_CHECKING_FIRMWARE) {
u32 new_size = *(u32 *)data;
if (new_size <= APP_MAX_SIZE) {
expected_size = new_size;
@@ -260,83 +547,205 @@ void OTAHandler::handleOTAMessage(u32 id, u8 *data, u8 len) {
} else {
ota_error = OTA_ERROR_INVALID_SIZE;
ota_state = OTA_STATE_IDLE;
send_error_frame(OTA_ERROR_INVALID_SIZE);
send_error_frame(0U, OTA_ERROR_INVALID_SIZE);
return;
}
/** 把擦除动作延后到 process() 中执行。 */
pending_flash_erase = true;
u8 send_data[6] = {0xBA, 0xC0, 0x00, 0x00, 0x00, 0x00};
CAN0_transmiter.CANFD_AsyncSend(CANFD_OTA_ACK_ID, 6, send_data);
} else if (len == 2U && ota_state == OTA_STATE_FINISHED) {
} else {
/** 固件大小合法,返回严格 seq 语义 ACK:阶段起始包号 0。 */
send_structured_ack(0U);
}
/** 结束态下要求显式收到 0x0A AC,确认可以切换到 APP。 */
else if (len == 2U && ota_state == OTA_STATE_FINISHED) {
if (data[0] == 0x0AU && data[1] == 0xACU) {
if (!validateApplication()) {
ota_error = OTA_ERROR_FLASH_ERROR;
ota_state = OTA_STATE_IDLE;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
return;
}
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
u8 finish_ack[2] = {0x0A, 0xAC};
send_ack_frame(2, finish_ack);
pending_app_jump = true;
return;
}
ota_error = OTA_ERROR_INVALID_PACKET;
ota_state = OTA_STATE_IDLE;
send_error_frame(OTA_ERROR_INVALID_PACKET);
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET);
return;
}
} else if (id == CANFD_OTA_REQ_ID) {
/** 其它长度 / 状态组合都视作非法包。 */
else {
ota_error = OTA_ERROR_INVALID_PACKET;
ota_state = OTA_STATE_IDLE;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET);
}
}
/**
* REQ:用于握手阶段。
*/
else if (type == OTA_LINK_MSG_REQ) {
ota_time = 0;
/**
* 第一步:上位机发固定魔术字 0x00 0xAC,申请开始握手。
*/
if (data[0] == 0x00U && data[1] == 0xACU && len == 2U && ota_state == OTA_STATE_IDLE) {
u8 send_data[2] = {0x00, 0xAC};
CAN0_transmiter.CANFD_AsyncSend(CANFD_OTA_ACK_ID, 2, send_data);
send_ack_frame(2, send_data);
ota_state = OTA_STATE_WAITING_RANDOM;
} else if (len == 16U && ota_state == OTA_STATE_WAITING_RANDOM) {
}
/**
* 第二步:上位机发送 16 字节随机数 nonce_server。
* Bootloader:
* - 读取设备 UID
* - 用主密钥加密 nonce 派生会话密钥 K_session
* - 回传 UID + nonce 片段 + 加密后的 K_session
*/
else if (len == 16U && ota_state == OTA_STATE_WAITING_RANDOM) {
for (u8 i = 0; i < 16U; ++i) {
nonce_server[i] = data[i];
}
getDeviceSerialNumber(device_uid);
aes128_set_key(&aes_ctx, K_master);
u8 K_session[16];
aes128_encrypt_block(&aes_ctx, nonce_server, K_session);
u8 encrypted_Ksession[16];
u8 temp_block[AES_BLOCK_SIZE];
aes128_encrypt_block(&aes_ctx, K_session, temp_block);
memcpy(encrypted_Ksession, temp_block, 16);
u8 handshake_response[36];
memcpy(handshake_response, device_uid, 12);
memcpy(handshake_response + 12, nonce_server + 8, 8);
memcpy(handshake_response + 20, encrypted_Ksession, 16);
CAN0_transmiter.CANFD_AsyncSend(CANFD_OTA_ACK_ID, 36, handshake_response);
send_ack_frame(36, handshake_response);
memcpy(current_K_session, K_session, 16);
ota_state = OTA_STATE_WAITING_CONFIRM;
} else if (len == 16U && ota_state == OTA_STATE_WAITING_CONFIRM) {
}
/**
* 第三步:上位机使用会话密钥加密 (nonce_server XOR 0x55),回传 16 字节确认块。
* Bootloader 解密后比对,成功则握手完成。
*/
else if (len == 16U && ota_state == OTA_STATE_WAITING_CONFIRM) {
u8 plaintext[16];
u8 expected[16];
u8 encrypted_confirm[16];
memcpy(encrypted_confirm, data, 16);
aes128_set_key(&aes_ctx, current_K_session);
aes128_decrypt_block(&aes_ctx, encrypted_confirm, plaintext);
for (int i = 0; i < 16; ++i) {
expected[i] = nonce_server[i] ^ 0x55U;
}
if (memcmp(plaintext, expected, 16) != 0) {
ota_error = OTA_ERROR_INVALID_KEY;
ota_state = OTA_STATE_IDLE;
send_error_frame(OTA_ERROR_INVALID_KEY);
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_KEY);
return;
}
u8 send_data[2] = {0xBA, 0xC0};
CAN0_transmiter.CANFD_AsyncSend(CANFD_OTA_ACK_ID, 2, send_data);
send_ack_frame(2, send_data);
ota_state = OTA_STATE_CHECKING_FIRMWARE;
} else {
}
/** 其它情况一律判定为握手非法。 */
else {
ota_error = OTA_ERROR_INVALID_HANDSHAKE;
ota_state = OTA_STATE_IDLE;
send_error_frame(OTA_ERROR_INVALID_HANDSHAKE);
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_HANDSHAKE);
return;
}
}
}
void send_error_frame(u8 error) {
u8 error_frame[1] = {error};
CAN0_transmiter.CANFD_AsyncSend(CANFD_OTA_ACK_ID, 1, error_frame);
/**
* @brief 兼容旧接口:按 CAN ID 分发 OTA 消息。
*
* @param id CAN 扩展帧 ID。
* @param data 指向消息数据缓冲区的指针。
* @param len 消息数据长度。
* @return 无。
*/
void OTAHandler::handleOTAMessage(u32 id, u8 *data, u8 len) {
/** 兼容旧的“按 CAN ID 分发”调用方式。 */
if (id == CANFD_OTA_CANCEL_ID) {
handleLinkMessage(OTA_LINK_MSG_CANCEL, data, len);
} else if (id == CANFD_OTA_DATA_ID) {
handleLinkMessage(OTA_LINK_MSG_DATA, data, len);
} else if (id == CANFD_OTA_REQ_ID) {
handleLinkMessage(OTA_LINK_MSG_REQ, data, len);
}
}
/**
* @brief 发送 OTA ACK 帧。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void send_ack_frame(u8 len, const u8 *data) {
ota.sendAck(len, data);
}
/**
* @brief 发送 OTA WAIT 帧。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void send_wait_frame(u8 len, const u8 *data) {
ota.sendWait(len, data);
}
/**
* @brief 发送 OTA 错误帧。
*
* @param error 错误码。
* @return 无。
*/
void send_error_frame(u32 packet_num, u8 error) {
/**
* 结构化错误帧:
* [0..1] = 0xBA 0xC0
* [2..5] = 相关包号(小端)
* [6] = 错误码
*/
u8 error_frame[7] = {0xBA, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, error};
write_u32_to_4bytes(&error_frame[2], packet_num);
send_ack_frame(7, error_frame);
}
/**
* @brief 读取芯片唯一序列号。
*
* @param buffer 指向至少 12 字节输出缓冲区的指针。
* @return 无。
*/
void getDeviceSerialNumber(u8 *buffer) {
/** 从 GD32 固定 UID 地址读取 96bit 设备序列号。 */
volatile uint32_t *lsb = (volatile uint32_t *)DEVICE_SERIAL_ADDR_LSB;
volatile uint32_t *mid = (volatile uint32_t *)DEVICE_SERIAL_ADDR_MID;
volatile uint32_t *msb = (volatile uint32_t *)DEVICE_SERIAL_ADDR_MSB;
uint32_t serial_lsb = *lsb;
uint32_t serial_mid = *mid;
uint32_t serial_msb = *msb;
for (int i = 0; i < 4; ++i) {
buffer[i] = (u8)(serial_lsb >> (i * 8));
buffer[i + 4] = (u8)(serial_mid >> (i * 8));
@@ -344,7 +753,19 @@ void getDeviceSerialNumber(u8 *buffer) {
}
}
/**
* @brief 将备份应用区整份固件搬运到当前主应用区。
*
* @return FMC 状态码;CRC 校验失败时返回 0xFF。
*/
fmc_state_enum move_app_data() {
/**
* 把备份应用区整体复制到当前主应用区:
* 1. 先擦当前主应用区
* 2. 计算备份区 CRC
* 3. 逐 word 拷贝非 0xFFFFFFFF 的数据
* 4. 再计算目标区 CRC 比较,确认搬运成功
*/
fmc_state_enum fmc_status = FMC_READY;
__disable_irq();
fmc_unlock();
@@ -389,20 +810,37 @@ fmc_state_enum move_app_data() {
return fmc_status;
}
/**
* @brief 对会话密钥和数据区执行 CRC32 计算。
*
* @param key 指向 16 字节会话密钥的指针。
* @param data 指向 48 字节数据区的指针。
* @param output 指向 4 字节输出缓冲区的指针。
* @return 无。
*/
void calculate_crc32_with_key(const u8 *key, const u8 *data, u8 *output) {
/**
* 计算方式:
* 1. 先把 16 字节会话密钥喂入 CRC32
* 2. 再把 48 字节有效数据喂入 CRC32
* 3. 最终按小端输出 4 字节结果
*/
uint32_t crc = 0xFFFFFFFFU;
for (int i = 0; i < 16; ++i) {
crc ^= key[i];
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U)));
}
}
for (u32 i = 0; i < OTA_PACKET_SIZE; ++i) {
crc ^= data[i];
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U)));
}
}
crc ^= 0xFFFFFFFFU;
output[0] = (u8)(crc & 0xFFU);
output[1] = (u8)((crc >> 8) & 0xFFU);
+218 -15
View File
@@ -7,29 +7,153 @@
#include "gd32e50x.h"
#include <stdint.h>
/**
* @brief OTA 统一链路消息类型。
*
* 无论底层来自 CAN 还是 SPI,进入 OTA 状态机前都先映射成这三类。
*/
typedef enum {
OTA_LINK_MSG_REQ = 0,
OTA_LINK_MSG_DATA,
OTA_LINK_MSG_CANCEL
} ota_link_msg_type_t;
/**
* @brief OTA 状态机向下层链路发送时使用的消息类型。
*/
typedef enum {
OTA_LINK_TX_ACK = 0,
OTA_LINK_TX_WAIT
} ota_link_tx_type_t;
/**
* @brief OTA 传输层发送函数类型。
*
* @param type 回包类型,ACK 或 WAIT。
* @param len 有效载荷长度,单位为字节。
* @param data 指向有效载荷缓冲区的指针。
*/
typedef void (*ota_send_fn_t)(ota_link_tx_type_t type, u8 len, const u8 *data);
/**
* @brief Bootloader OTA 升级核心状态机。
*
* 负责握手协商、固件大小检查、擦除应用区、接收与写入固件分包、
* 错误处理以及升级完成后的应用跳转。
*/
class OTAHandler {
public:
u32 ota_stateota_time;
u32 received_size;
u32 expected_size;
u32 ota_stateota_time; /**< 历史遗留字段,当前实现未实际使用。 */
u32 received_size; /**< 已接收总字节数。 */
u32 expected_size; /**< 预期固件总字节数。 */
/**
* @brief 初始化 OTA 状态机与默认发送通道。
*
* @return 无。
*/
void init();
/**
* @brief 推进 OTA 状态机的后台处理逻辑。
*
* 主要处理延迟擦除、空闲等待与超时判断。
*
* @return 无。
*/
void process();
/**
* @brief 判断当前是否处于 OTA 活跃状态。
*
* @retval true 状态机当前不处于空闲态。
* @retval false 状态机当前处于空闲态。
*/
bool isActive() const { return ota_state != OTA_STATE_IDLE; }
/**
* @brief 设置当前底层回包发送函数。
*
* @param sender 底层发送函数指针。
* @return 无。
*/
void setTransportSender(ota_send_fn_t sender) { transport_send = sender; }
/**
* @brief 通过当前绑定的传输层发送 ACK。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void sendAck(u8 len, const u8 *data);
/**
* @brief 通过当前绑定的传输层发送 WAIT。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void sendWait(u8 len, const u8 *data);
/**
* @brief 处理统一抽象后的 OTA 链路消息。
*
* @param type 链路消息类型。
* @param data 指向消息数据缓冲区的指针。
* @param len 消息数据长度。
* @return 无。
*/
void handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len);
/**
* @brief 兼容旧接口:按 CAN ID 处理 OTA 消息。
*
* @param id CAN 扩展帧 ID。
* @param data 指向消息数据缓冲区的指针。
* @param len 消息数据长度。
* @return 无。
*/
void handleOTAMessage(u32 id, u8 *data, u8 len);
/**
* @brief 关闭 Bootloader 运行环境并跳转执行主应用。
*
* @return 无。
*/
void jumpToApplication();
private:
u32 ota_time;
u32 ota_time; /**< OTA 会话计时器。 */
/**
* @brief 擦除当前主应用区。
*
* @return 无。
*/
void eraseApplicationArea();
/**
* @brief 向当前主应用区写入一段固件分片。
*
* @param offset 相对于主应用起始地址的偏移量。
* @param data 指向待写入数据的指针。
* @param size 本次写入的数据长度。
* @return 无。
*/
void writeApplicationData(u32 offset, u8 *data, u32 size);
bool pending_flash_erase = false;
u32 pending_flash_offset = 0;
u8 pending_flash_data[64];
u8 pending_flash_size = 0;
bool pending_flash_erase = false; /**< 是否存在待处理的延迟擦除请求。 */
bool pending_app_jump = false; /**< 是否存在待处理的应用跳转请求。 */
u32 pending_flash_offset = 0; /**< 预留:批量写入方案使用的偏移字段。 */
u8 pending_flash_data[64]; /**< 预留:批量写入方案使用的数据缓冲。 */
u8 pending_flash_size = 0; /**< 预留:批量写入方案使用的数据长度。 */
ota_send_fn_t transport_send = nullptr; /**< 当前底层回包发送函数。 */
/**
* @brief OTA 流程状态定义。
*/
typedef enum {
OTA_STATE_IDLE = 0,
OTA_STATE_WAITING_RANDOM,
@@ -39,6 +163,9 @@ class OTAHandler {
OTA_STATE_FINISHED
} ota_state_t;
/**
* @brief OTA 错误类型定义。
*/
typedef enum {
OTA_ERROR_NONE = 0,
OTA_ERROR_TIMEOUT,
@@ -50,19 +177,95 @@ class OTAHandler {
OTA_ERROR_INVALID_SIZE
} ota_error_t;
u8 device_uid[12];
u8 current_K_session[16];
u8 nonce_server[16];
aes128_context aes_ctx;
ota_state_t ota_state = OTA_STATE_IDLE;
ota_error_t ota_error = OTA_ERROR_NONE;
u8 device_uid[12]; /**< 芯片 96bit UID。 */
u8 current_K_session[16]; /**< 当前会话密钥。 */
u8 nonce_server[16]; /**< 上位机发来的随机数。 */
aes128_context aes_ctx; /**< AES 上下文。 */
ota_state_t ota_state = OTA_STATE_IDLE; /**< 当前 OTA 状态。 */
ota_error_t ota_error = OTA_ERROR_NONE; /**< 当前 OTA 错误码。 */
};
/**
* @brief 读取芯片 UID。
*
* @param buffer 指向至少 12 字节输出缓冲区的指针。
* @return 无。
*/
void getDeviceSerialNumber(u8 *buffer);
/**
* @brief 使用会话密钥和数据区计算 CRC32。
*
* @param key 指向 16 字节会话密钥的指针。
* @param data 指向 48 字节数据区的指针。
* @param output 指向 4 字节输出缓冲区的指针。
* @return 无。
*/
void calculate_crc32_with_key(const u8 *key, const u8 *data, u8 *output);
void send_error_frame(u8 error);
/**
* @brief 将 OTA 回包出口切换到 CAN。
*
* @return 无。
*/
void ota_bind_can_transport(void);
/**
* @brief 将 OTA 回包出口切换到 SPI。
*
* @return 无。
*/
void ota_bind_spi_transport(void);
/**
* @brief 发送结构化 OTA 错误帧。
*
* @param packet_num 相关包号;非分包场景可传 0xFFFFFFFF。
* @param error 错误码。
* @return 无。
*/
void send_error_frame(u32 packet_num, u8 error);
/**
* @brief 发送 OTA ACK 帧。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void send_ack_frame(u8 len, const u8 *data);
/**
* @brief 发送 OTA WAIT 帧。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void send_wait_frame(u8 len, const u8 *data);
/**
* @brief 校验主应用向量表是否有效。
*
* @retval true 应用向量表看起来合法。
* @retval false 应用向量表不合法。
*/
bool validateApplication();
/**
* @brief 从备份应用区搬运整份固件到当前主应用区。
*
* @return FMC 返回的状态码;CRC 不一致时返回 0xFF。
*/
fmc_state_enum move_app_data();
/**
* @brief 更新 OTA 配置页中的某个标志字。
*
* @param addr 目标配置字地址。
* @param data 目标写入值。
* @return FLASH_EEPROM_OK 表示成功,FLASH_EEPROM_ERROR 表示失败。
*/
u8 OTA_SET(u32 addr, u32 data);
#endif
+154 -69
View File
@@ -1,6 +1,6 @@
#include "aes128.h"
// S盒 (Substitution Box)
/** S盒 (Substitution Box) */
static const u8 sbox[256] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
@@ -25,7 +25,7 @@ static const u8 sbox[256] = {
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
0xb0, 0x54, 0xbb, 0x16};
// 逆S盒 (InvSBox)
/** 逆S盒 (InvSBox) */
const u8 inv_sbox[256] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e,
0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
@@ -50,50 +50,69 @@ const u8 inv_sbox[256] = {
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63,
0x55, 0x21, 0x0c, 0x7d};
// 轮常量
/** 轮常量 */
static const u8 rcon[10] = {0x01, 0x02, 0x04, 0x08, 0x10,
0x20, 0x40, 0x80, 0x1b, 0x36};
// 伽罗瓦域乘法 (xtime) - 确保正确
/** 伽罗瓦域乘法 (xtime) - 确保正确 */
static u8 xtime(u8 x) { return (x << 1) ^ (((x >> 7) & 1) * 0x1b); }
// 关键修复:AES状态矩阵是列优先排列!
// 内存布局应该是:s00, s10, s20, s30, s01, s11, s21, s31, ...
// 而不是行优先
/** 关键修复:AES状态矩阵是列优先排列! */
/** 内存布局应该是:s00, s10, s20, s30, s01, s11, s21, s31, ... */
/** 而不是行优先 */
// 轮密钥加 - 无需修改
/** 轮密钥加 - 无需修改 */
/**
* @brief 对状态矩阵执行轮密钥加。
*
* @param state 指向 16 字节状态矩阵的指针。
* @param round_key 指向 16 字节轮密钥的指针。
* @return 无。
*/
void add_round_key(u8 *state, const u8 *round_key) {
for (u8 i = 0; i < 16; i++) {
state[i] ^= round_key[i];
}
}
// 字节替换 - 无需修改
/** 字节替换 - 无需修改 */
/**
* @brief 对状态矩阵执行字节替换。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void sub_bytes(u8 *state) {
for (u8 i = 0; i < 16; i++) {
state[i] = sbox[state[i]];
}
}
// 行移位 - 修复为列优先排列
/** 行移位 - 修复为列优先排列 */
/**
* @brief 对状态矩阵执行行移位。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void shift_rows(u8 *state) {
u8 temp;
// AES状态矩阵(列优先):
// 列0: state[0], state[1], state[2], state[3] -> 行0,1,2,3
// 列1: state[4], state[5], state[6], state[7] -> 行0,1,2,3
// 列2: state[8], state[9], state[10], state[11] -> 行0,1,2,3
// 列3: state[12], state[13], state[14], state[15] -> 行0,1,2,3
/** AES状态矩阵(列优先): */
/** 列0: state[0], state[1], state[2], state[3] -> 行0,1,2,3 */
/** 列1: state[4], state[5], state[6], state[7] -> 行0,1,2,3 */
/** 列2: state[8], state[9], state[10], state[11] -> 行0,1,2,3 */
/** 列3: state[12], state[13], state[14], state[15] -> 行0,1,2,3 */
// 第1行(索引0,4,8,12)不移位
// 第2行(索引1,5,9,13)左移1位
/** 第1行(索引0,4,8,12)不移位 */
/** 第2行(索引1,5,9,13)左移1位 */
temp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = temp;
// 第3行(索引2,6,10,14)左移2位
/** 第3行(索引2,6,10,14)左移2位 */
temp = state[2];
state[2] = state[10];
state[10] = temp;
@@ -101,7 +120,7 @@ void shift_rows(u8 *state) {
state[6] = state[14];
state[14] = temp;
// 第4行(索引3,7,11,15)左移3位
/** 第4行(索引3,7,11,15)左移3位 */
temp = state[3];
state[3] = state[15];
state[15] = state[11];
@@ -109,25 +128,31 @@ void shift_rows(u8 *state) {
state[7] = temp;
}
// 列混合 - 修复为正确的实现
/** 列混合 - 修复为正确的实现 */
/**
* @brief 对状态矩阵执行列混合。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void mix_columns(u8 *state) {
u8 a[4], b[4];
// 处理每一列
/** 处理每一列 */
for (u8 col = 0; col < 4; col++) {
// 获取当前列的4个字节
/** 获取当前列的4个字节 */
a[0] = state[col * 4 + 0];
a[1] = state[col * 4 + 1];
a[2] = state[col * 4 + 2];
a[3] = state[col * 4 + 3];
// 计算混合后的值
/** 计算混合后的值 */
b[0] = xtime(a[0]) ^ xtime(a[1]) ^ a[1] ^ a[2] ^ a[3];
b[1] = a[0] ^ xtime(a[1]) ^ xtime(a[2]) ^ a[2] ^ a[3];
b[2] = a[0] ^ a[1] ^ xtime(a[2]) ^ xtime(a[3]) ^ a[3];
b[3] = xtime(a[0]) ^ a[0] ^ a[1] ^ a[2] ^ xtime(a[3]);
// 存回结果
/** 存回结果 */
state[col * 4 + 0] = b[0];
state[col * 4 + 1] = b[1];
state[col * 4 + 2] = b[2];
@@ -135,7 +160,7 @@ void mix_columns(u8 *state) {
}
}
// 伽罗瓦域乘法 (用于逆列混合)
/** 伽罗瓦域乘法 (用于逆列混合) */
static u8 gf_mul(u8 a, u8 b) {
u8 p = 0;
u8 high_bit;
@@ -147,32 +172,44 @@ static u8 gf_mul(u8 a, u8 b) {
high_bit = a & 0x80;
a <<= 1;
if (high_bit) {
a ^= 0x1b; // x^8 + x^4 + x^3 + x + 1
a ^= 0x1b; /**< x^8 + x^4 + x^3 + x + 1 */
}
b >>= 1;
}
return p;
}
// 逆字节替换
/** 逆字节替换 */
/**
* @brief 对状态矩阵执行逆字节替换。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void inv_sub_bytes(u8 *state) {
for (u8 i = 0; i < 16; i++) {
state[i] = inv_sbox[state[i]];
}
}
// 逆行移位 - 与shift_rows对应
/** 逆行移位 - 与shift_rows对应 */
/**
* @brief 对状态矩阵执行逆行移位。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void inv_shift_rows(u8 *state) {
u8 temp;
// 第2行右移1位
/** 第2行右移1位 */
temp = state[13];
state[13] = state[9];
state[9] = state[5];
state[5] = state[1];
state[1] = temp;
// 第3行右移2位
/** 第3行右移2位 */
temp = state[2];
state[2] = state[10];
state[10] = temp;
@@ -180,7 +217,7 @@ void inv_shift_rows(u8 *state) {
state[6] = state[14];
state[14] = temp;
// 第4行右移3位
/** 第4行右移3位 */
temp = state[7];
state[7] = state[11];
state[11] = state[15];
@@ -188,19 +225,25 @@ void inv_shift_rows(u8 *state) {
state[3] = temp;
}
// 逆列混合 - 正确实现
/** 逆列混合 - 正确实现 */
/**
* @brief 对状态矩阵执行逆列混合。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void inv_mix_columns(u8 *state) {
u8 a[4], b[4];
// 处理每一列
/** 处理每一列 */
for (u8 col = 0; col < 4; col++) {
// 获取当前列的4个字节
/** 获取当前列的4个字节 */
a[0] = state[col * 4 + 0];
a[1] = state[col * 4 + 1];
a[2] = state[col * 4 + 2];
a[3] = state[col * 4 + 3];
// 计算逆混合后的值
/** 计算逆混合后的值 */
b[0] = gf_mul(0x0e, a[0]) ^ gf_mul(0x0b, a[1]) ^ gf_mul(0x0d, a[2]) ^
gf_mul(0x09, a[3]);
b[1] = gf_mul(0x09, a[0]) ^ gf_mul(0x0e, a[1]) ^ gf_mul(0x0b, a[2]) ^
@@ -210,7 +253,7 @@ void inv_mix_columns(u8 *state) {
b[3] = gf_mul(0x0b, a[0]) ^ gf_mul(0x0d, a[1]) ^ gf_mul(0x09, a[2]) ^
gf_mul(0x0e, a[3]);
// 存回结果
/** 存回结果 */
state[col * 4 + 0] = b[0];
state[col * 4 + 1] = b[1];
state[col * 4 + 2] = b[2];
@@ -218,45 +261,45 @@ void inv_mix_columns(u8 *state) {
}
}
// 密钥扩展 - 修复为标准实现
/** 密钥扩展 - 修复为标准实现 */
static void key_expansion(aes128_context *ctx) {
u8 temp[4];
u8 i, rcon_iter = 0;
// 复制原始密钥到第一轮密钥
/** 复制原始密钥到第一轮密钥 */
memcpy(ctx->round_keys[0], ctx->key, 16);
// 生成10轮密钥
/** 生成10轮密钥 */
for (i = 1; i <= 10; i++) {
// 复制前一轮最后一列
/** 复制前一轮最后一列 */
temp[0] = ctx->round_keys[i - 1][12];
temp[1] = ctx->round_keys[i - 1][13];
temp[2] = ctx->round_keys[i - 1][14];
temp[3] = ctx->round_keys[i - 1][15];
// 字循环 (RotWord)
/** 字循环 (RotWord) */
u8 t = temp[0];
temp[0] = temp[1];
temp[1] = temp[2];
temp[2] = temp[3];
temp[3] = t;
// 字节替换 (SubWord)
/** 字节替换 (SubWord) */
temp[0] = sbox[temp[0]];
temp[1] = sbox[temp[1]];
temp[2] = sbox[temp[2]];
temp[3] = sbox[temp[3]];
// 轮常量异或
/** 轮常量异或 */
temp[0] ^= rcon[i - 1];
// 生成新的第一列
/** 生成新的第一列 */
ctx->round_keys[i][0] = ctx->round_keys[i - 1][0] ^ temp[0];
ctx->round_keys[i][1] = ctx->round_keys[i - 1][1] ^ temp[1];
ctx->round_keys[i][2] = ctx->round_keys[i - 1][2] ^ temp[2];
ctx->round_keys[i][3] = ctx->round_keys[i - 1][3] ^ temp[3];
// 生成其他列
/** 生成其他列 */
for (u8 j = 1; j < 4; j++) {
ctx->round_keys[i][j * 4 + 0] =
ctx->round_keys[i][(j - 1) * 4 + 0] ^
@@ -274,21 +317,34 @@ static void key_expansion(aes128_context *ctx) {
}
}
// 设置密钥
/**
* @brief 设置 AES-128 密钥并执行轮密钥扩展。
*
* @param ctx 指向 AES 上下文的指针。
* @param key 指向 16 字节密钥的指针。
* @return 无。
*/
void aes128_set_key(aes128_context *ctx, const u8 *key) {
memcpy(ctx->key, key, AES128_KEY_LEN);
key_expansion(ctx);
}
// 加密单个块
/**
* @brief 加密单个 16 字节分组。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向输入分组的指针。
* @param output 指向输出缓冲区的指针。
* @return 无。
*/
void aes128_encrypt_block(aes128_context *ctx, const u8 *input, u8 *output) {
// 复制输入到状态
/** 复制输入到状态 */
memcpy(ctx->state, input, AES_BLOCK_SIZE);
// 初始轮密钥加
/** 初始轮密钥加 */
add_round_key(ctx->state, ctx->round_keys[0]);
// 9轮标准轮
/** 9轮标准轮 */
for (u8 round = 1; round <= 9; round++) {
sub_bytes(ctx->state);
shift_rows(ctx->state);
@@ -296,33 +352,47 @@ void aes128_encrypt_block(aes128_context *ctx, const u8 *input, u8 *output) {
add_round_key(ctx->state, ctx->round_keys[round]);
}
// 最终轮 (没有mix_columns)
/** 最终轮 (没有mix_columns) */
sub_bytes(ctx->state);
shift_rows(ctx->state);
add_round_key(ctx->state, ctx->round_keys[10]);
// 复制结果到输出
/** 复制结果到输出 */
memcpy(output, ctx->state, AES_BLOCK_SIZE);
}
// ECB模式加密48字节数据
/**
* @brief 以 ECB 模式加密 48 字节数据。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向 48 字节输入数据的指针。
* @param output 指向 48 字节输出缓冲区的指针。
* @return 无。
*/
void aes128_ecb_encrypt_48bytes(aes128_context *ctx, const u8 *input,
u8 *output) {
// 分3块加密 (48 = 16 * 3)
/** 分3块加密 (48 = 16 * 3) */
aes128_encrypt_block(ctx, input, output);
aes128_encrypt_block(ctx, input + 16, output + 16);
aes128_encrypt_block(ctx, input + 32, output + 32);
}
// 解密单个块
/**
* @brief 解密单个 16 字节分组。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向输入分组的指针。
* @param output 指向输出缓冲区的指针。
* @return 无。
*/
void aes128_decrypt_block(aes128_context *ctx, const u8 *input, u8 *output) {
// 复制输入到状态
/** 复制输入到状态 */
memcpy(ctx->state, input, AES_BLOCK_SIZE);
// 初始轮密钥加(使用最后一轮密钥)
/** 初始轮密钥加(使用最后一轮密钥) */
add_round_key(ctx->state, ctx->round_keys[10]);
// 9轮标准解密轮
/** 9轮标准解密轮 */
for (u8 round = 9; round >= 1; round--) {
inv_shift_rows(ctx->state);
inv_sub_bytes(ctx->state);
@@ -330,41 +400,56 @@ void aes128_decrypt_block(aes128_context *ctx, const u8 *input, u8 *output) {
inv_mix_columns(ctx->state);
}
// 最终轮(没有inv_mix_columns)
/** 最终轮(没有inv_mix_columns) */
inv_shift_rows(ctx->state);
inv_sub_bytes(ctx->state);
add_round_key(ctx->state, ctx->round_keys[0]);
// 复制结果到输出
/** 复制结果到输出 */
memcpy(output, ctx->state, AES_BLOCK_SIZE);
}
// ECB模式解密48字节数据
/**
* @brief 以 ECB 模式解密 48 字节数据。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向 48 字节输入数据的指针。
* @param output 指向 48 字节输出缓冲区的指针。
* @return 无。
*/
void aes128_ecb_decrypt_48bytes(aes128_context *ctx, const u8 *input,
u8 *output) {
// 分3块解密 (48 = 16 * 3)
/** 分3块解密 (48 = 16 * 3) */
aes128_decrypt_block(ctx, input, output);
aes128_decrypt_block(ctx, input + 16, output + 16);
aes128_decrypt_block(ctx, input + 32, output + 32);
}
// 验证函数:加密后解密,对比原始数据
/**
* @brief 执行 AES 加密-解密自检。
*
* @param ctx 指向 AES 上下文的指针。
* @param original_data 指向原始数据缓冲区的指针。
* @param length 参与比对的数据长度。
* @retval 1 验证成功。
* @retval 0 验证失败。
*/
u8 aes128_verify_encryption(aes128_context *ctx, const u8 *original_data,
size_t length) {
size_t length) {
u8 temp_ciphertext[48];
u8 temp_plaintext[48];
u8 result = 1; // 1表示验证成功,0表示失败
u8 result = 1; /**< 1表示验证成功,0表示失败 */
// 1. 加密原始数据
/** 1. 加密原始数据 */
aes128_ecb_encrypt_48bytes(ctx, original_data, temp_ciphertext);
// 2. 解密密文
/** 2. 解密密文 */
aes128_ecb_decrypt_48bytes(ctx, temp_ciphertext, temp_plaintext);
// 3. 对比原始数据和解密后的数据
/** 3. 对比原始数据和解密后的数据 */
for (size_t i = 0; i < length; i++) {
if (original_data[i] != temp_plaintext[i]) {
result = 0; // 验证失败
result = 0; /**< 验证失败 */
break;
}
}
+113 -10
View File
@@ -3,39 +3,142 @@
#include "cstdint"
#include <string.h>
// AES-128 密钥长度 (16 bytes)
/** @brief AES-128 密钥长度,单位为字节。 */
#define AES128_KEY_LEN 16
// AES 块大小 (16 bytes)
/** @brief AES 单个分组长度,单位为字节。 */
#define AES_BLOCK_SIZE 16
// AES 状态结构
/**
* @brief AES-128 上下文。
*/
typedef struct {
u8 key[AES128_KEY_LEN]; // 128位/16字节密钥
u8 round_keys[11][16]; // 10轮 + 初始轮密钥
u8 state[AES_BLOCK_SIZE]; // 状态矩阵
u8 key[AES128_KEY_LEN]; /**< 原始 128bit 密钥。 */
u8 round_keys[11][16]; /**< 扩展后的 11 组轮密钥。 */
u8 state[AES_BLOCK_SIZE]; /**< 当前状态矩阵。 */
} aes128_context;
// 内部函数声明(需要在头文件中声明,因为解密函数会使用)
/**
* @brief 对状态矩阵执行轮密钥加操作。
*
* @param state 指向 16 字节状态矩阵的指针。
* @param round_key 指向 16 字节轮密钥的指针。
* @return 无。
*/
void add_round_key(u8 *state, const u8 *round_key);
/**
* @brief 对状态矩阵执行 SubBytes 变换。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void sub_bytes(u8 *state);
/**
* @brief 对状态矩阵执行 ShiftRows 变换。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void shift_rows(u8 *state);
/**
* @brief 对状态矩阵执行 MixColumns 变换。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void mix_columns(u8 *state);
/**
* @brief 对状态矩阵执行逆字节替换。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void inv_sub_bytes(u8 *state);
/**
* @brief 对状态矩阵执行逆行移位。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void inv_shift_rows(u8 *state);
/**
* @brief 对状态矩阵执行逆列混合。
*
* @param state 指向 16 字节状态矩阵的指针。
* @return 无。
*/
void inv_mix_columns(u8 *state);
// 公共函数声明
/**
* @brief 设置 AES-128 密钥并完成轮密钥扩展。
*
* @param ctx 指向 AES 上下文的指针。
* @param key 指向 16 字节密钥的指针。
* @return 无。
*/
void aes128_set_key(aes128_context *ctx, const u8 *key);
/**
* @brief 加密单个 16 字节分组。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向 16 字节输入分组的指针。
* @param output 指向 16 字节输出缓冲区的指针。
* @return 无。
*/
void aes128_encrypt_block(aes128_context *ctx, const u8 *input, u8 *output);
/**
* @brief 以 ECB 模式加密 48 字节数据。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向 48 字节输入数据的指针。
* @param output 指向 48 字节输出缓冲区的指针。
* @return 无。
*/
void aes128_ecb_encrypt_48bytes(aes128_context *ctx, const u8 *input,
u8 *output);
/**
* @brief 解密单个 16 字节分组。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向 16 字节输入分组的指针。
* @param output 指向 16 字节输出缓冲区的指针。
* @return 无。
*/
void aes128_decrypt_block(aes128_context *ctx, const u8 *input, u8 *output);
/**
* @brief 以 ECB 模式解密 48 字节数据。
*
* @param ctx 指向 AES 上下文的指针。
* @param input 指向 48 字节输入数据的指针。
* @param output 指向 48 字节输出缓冲区的指针。
* @return 无。
*/
void aes128_ecb_decrypt_48bytes(aes128_context *ctx, const u8 *input,
u8 *output);
/**
* @brief 执行 AES 加密-解密自检。
*
* @param ctx 指向 AES 上下文的指针。
* @param original_data 指向原始数据缓冲区的指针。
* @param length 参与比对的数据长度。
* @retval 1 验证成功。
* @retval 0 验证失败。
*/
u8 aes128_verify_encryption(aes128_context *ctx, const u8 *original_data,
size_t length);
// 逆S盒 (用于解密)
/** @brief 逆 S 盒查找表。 */
extern const u8 inv_sbox[256];
#endif /* __AES_H */
#endif /* __AES_H */
+76 -37
View File
@@ -3,68 +3,107 @@
#include <stdint.h>
/** 项目内部统一使用的简写类型。 */
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
/** 调试开关:需要时可按模块打开。 */
#define EEP_DEBUG 0
#define DEBUG_OTA 0
#define CAN_DEBUG 0
#define SLEEP_DEBUG 0
/* Flash layout for GD32E508VE 512KB boot project */
static const u32 BOOTLOADER_START_ADDR = 0x08000000U;
static const u32 BOOTLOADER_SIZE = 0x00012000U; /* 72KB */
static const u32 APPBACK_START_ADDR = 0x08012000U;
static const u32 APPLATEST_START_ADDR = 0x08046000U;
static const u32 APP_MAX_SIZE = 0x00018000U; /* 96KB */
static const u32 FMC_PAGE_SIZE = 0x00000800U; /* 2KB, GD32E50x */
static const u32 OTA_CONFIG_PAGE_ADDR = 0x0807E000U;
static const u32 SLEEP_CONFIG_START = 0x0807E800U;
static const u32 SLEEP_CONFIG_END = 0x0807F000U;
/**
* Flash 布局说明(GD32E508VE,512KB)
* -----------------------------------
* 0x08000000 ~ 0x08011FFF : Bootloader 区(72KB)
* 0x08012000 ~ 0x08047FFF : 备份应用区(216KB)
* 0x08048000 ~ 0x0807DFFF : 当前主应用区(216KB)
* 0x0807E000 ~ 0x0807E7FF : OTA 状态配置页
* 0x0807E800 ~ 0x0807EFFF : 睡眠配置页
*/
inline constexpr u32 BOOTLOADER_START_ADDR = 0x08000000U;
inline constexpr u32 BOOTLOADER_SIZE = 0x00012000U; /**< 72KB */
inline constexpr u32 APPBACK_START_ADDR = 0x08012000U; /**< 备份应用起始地址 */
inline constexpr u32 APPLATEST_START_ADDR = 0x08048000U; /**< 当前有效应用起始地址 */
inline constexpr u32 APP_MAX_SIZE = 0x00036000U; /**< 216KB */
inline constexpr u32 FMC_PAGE_SIZE = 0x00000800U; /**< 2KB,GD32E50x 单页大小 */
inline constexpr u32 OTA_CONFIG_PAGE_ADDR = 0x0807E000U; /**< OTA 元数据页 */
inline constexpr u32 SLEEP_CONFIG_START = 0x0807E800U; /**< 睡眠配置页起始 */
inline constexpr u32 SLEEP_CONFIG_END = 0x0807F000U; /**< 睡眠配置页结束(开区间) */
static const u32 FLASH_EMPTY = 0xFFFFFFFFU;
static const u32 SLEEP_ENABLE = 0x11111111U;
static const u32 SLEEP_DISABLE = 0x00000000U;
/** Flash 默认擦除值,以及睡眠开关标志值。 */
inline constexpr u32 FLASH_EMPTY = 0xFFFFFFFFU;
inline constexpr u32 SLEEP_ENABLE = 0x11111111U;
inline constexpr u32 SLEEP_DISABLE = 0x00000000U;
static const u32 OTA_CONFIG_NUM = 6U;
static const u32 n_APP_LATEST_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x00U;
static const u32 n_APP_BACKUP_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x04U;
static const u32 n_APP_LATEST_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x08U;
static const u32 n_APP_BACKUP_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x0CU;
static const u32 n_APP_LATEST_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x10U;
static const u32 n_APP_BACKUP_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x14U;
/**
* OTA 配置页一共使用 6 个 word,分别保存:
* latest_aval / backup_aval / latest_en / backup_en / latest_check / backup_check
*/
inline constexpr u32 OTA_CONFIG_NUM = 6U;
inline constexpr u32 n_APP_LATEST_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x00U;
inline constexpr u32 n_APP_BACKUP_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x04U;
inline constexpr u32 n_APP_LATEST_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x08U;
inline constexpr u32 n_APP_BACKUP_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x0CU;
inline constexpr u32 n_APP_LATEST_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x10U;
inline constexpr u32 n_APP_BACKUP_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x14U;
static const u32 OTA_NOT_AVAL = 0xFFFFFFFFU;
static const u32 OTA_AVAL = 0x00000000U;
static const u32 FLASH_EEPROM_OK = 0U;
static const u32 FLASH_EEPROM_ERROR = 1U;
/**
* 本项目把 0x00000000 视为“有效/可用”,
* 把 0xFFFFFFFF 视为“无效/未写入”。
* 这样设计是因为 Flash 擦除态天然就是 0xFFFFFFFF。
*/
inline constexpr u32 OTA_NOT_AVAL = 0xFFFFFFFFU;
inline constexpr u32 OTA_AVAL = 0x00000000U;
inline constexpr u32 FLASH_EEPROM_OK = 0U;
inline constexpr u32 FLASH_EEPROM_ERROR = 1U;
static const u32 CANFD_OTA_REQ_ID = 0x00000100U;
static const u32 CANFD_OTA_DATA_ID = 0x00000101U;
static const u32 CANFD_OTA_ACK_ID = 0x00000102U;
static const u32 CANFD_OTA_WAIT_ID = 0x00000103U;
static const u32 CANFD_OTA_CANCEL_ID = 0x00000000U;
/** CAN-FD OTA 链路使用的扩展帧 ID。 */
inline constexpr u32 CANFD_OTA_REQ_ID = 0x00000100U;
inline constexpr u32 CANFD_OTA_DATA_ID = 0x00000101U;
inline constexpr u32 CANFD_OTA_ACK_ID = 0x00000102U;
inline constexpr u32 CANFD_OTA_WAIT_ID = 0x00000103U;
inline constexpr u32 CANFD_OTA_CANCEL_ID = 0x00000000U;
/* GD32E50x UID base address */
static const u32 DEVICE_SERIAL_ADDR_LSB = 0x1FFFF7ACU;
static const u32 DEVICE_SERIAL_ADDR_MID = 0x1FFFF7B0U;
static const u32 DEVICE_SERIAL_ADDR_MSB = 0x1FFFF7B4U;
/** GD32E50x 芯片 UID 三段地址。 */
inline constexpr u32 DEVICE_SERIAL_ADDR_LSB = 0x1FFFF7E8U;
inline constexpr u32 DEVICE_SERIAL_ADDR_MID = 0x1FFFF7ECU;
inline constexpr u32 DEVICE_SERIAL_ADDR_MSB = 0x1FFFF7F0U;
static const u32 OTA_PACKET_SIZE = 48U;
/**
* OTA 数据包设计:
* - 每帧总长 64 字节
* - 其中真实加密固件数据为 48 字节
* - 其余用于会话号、包序号、结束标志和 CRC32
*/
inline constexpr u32 OTA_PACKET_SIZE = 48U;
/** 便于按字节/按字访问同一个 32bit 数值。 */
typedef union {
u32 u;
u8 b[4];
} u32_union_t;
static const uint8_t K_master[16] = {
/**
* 主密钥:用于 OTA 握手阶段派生会话密钥。
* 当前是写死在固件中的固定密钥。
*/
inline constexpr uint8_t K_master[16] = {
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10
};
static const u32 OTA_TIMEOUT = 10000U;
static const u32 APP_DELAY_BOOT_TIME = 10000U;
/**
* 超时与启动等待参数:
* - OTA_TIMEOUT:OTA 会话超时时间计数上限
* - APP_DELAY_BOOT_TIME:发现应用存在但被禁用时,Bootloader 等待 OTA 请求的窗口
*/
inline constexpr u32 OTA_TIMEOUT = 10000U;
inline constexpr u32 APP_DELAY_BOOT_TIME = 10000U;
/** 常用尺寸常量。 */
static const u8 SIZE_U32 = sizeof(uint32_t);
static const u8 SIZE_FLOAT = sizeof(float);
+211 -14
View File
@@ -1,6 +1,7 @@
#include "initial.h"
#include "OTA.h"
#include "config.h"
#include "device/SpiOta.h"
#include "core_cm33.h"
#include "gd32e50x.h"
#include "gd32e50x_exti.h"
@@ -11,13 +12,22 @@
#include "gd32e50x_usart.h"
#include "systick.h"
u8 USART0_init(void) {
/**
* @brief 初始化 USART0 调试串口。
*
* @return 1 表示初始化完成。
*/
u8 BootDebugUart::init()
{
/** 打开 GPIOA 与 USART0 时钟。 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_USART0);
/** PA9 = TX,PA10 = RX。 */
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
/** 配置串口为 115200 8N1(其余参数使用默认值)。 */
usart_deinit(USART0);
usart_baudrate_set(USART0, 115200U);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
@@ -26,7 +36,16 @@ u8 USART0_init(void) {
return 1;
}
int fputc(int ch, FILE *f) throw() {
/**
* @brief printf 重定向输出函数。
*
* @param ch 待输出字符。
* @param f 目标文件流,当前实现未使用。
* @return 输出字符值。
*/
int BootDebugUart::putChar(int ch, FILE *f)
{
/** printf 最终走到这里,通过 USART0 单字节输出。 */
(void)f;
usart_data_transmit(USART0, (uint8_t)ch);
while (RESET == usart_flag_get(USART0, USART_FLAG_TBE)) {
@@ -34,25 +53,113 @@ int fputc(int ch, FILE *f) throw() {
return ch;
}
u16 get_reset_cause(void) {
/**
* @brief Redirect standard output character to the boot debug UART.
* @param ch Output character.
* @param f Stream pointer from libc.
* @return Forwarded character value.
*/
int fputc(int ch, FILE *f) throw() { return BootDebugUart::putChar(ch, f); }
/**
* @brief Initialize USART0 through the boot debug UART helper.
* @return 1 when initialization succeeds.
*/
u8 USART0_init(void) { return BootDebugUart::init(); }
/**
* @brief 获取复位原因。
*
* @return 复位原因编码。
*/
u16 BootPlatform::getResetCause(void)
{
/** 预留:如需区分上电/看门狗/软复位等原因,可在此补齐。 */
return 0;
}
void unused_pin_init(void) {
/**
* @brief Get reset cause through the platform helper.
* @return Reset-cause code.
*/
u16 get_reset_cause(void) { return BootPlatform::getResetCause(); }
/**
* @brief 初始化未使用引脚。
*
* @return 无。
*/
void BootPlatform::initUnusedPins(void)
{
/**
* 预留:如果需要降低悬空功耗,可把未使用 GPIO 统一初始化。
* 当前只保证时钟可用,未进一步配置具体引脚。
*/
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOC);
}
void print_system_clock_info(void) {
/**
* @brief Initialize unused pins through the platform helper.
* @return none
*/
void unused_pin_init(void) { BootPlatform::initUnusedPins(); }
/**
* @brief 打印系统时钟信息。
*
* @return 无。
*/
void BootPlatform::printSystemClockInfo(void)
{
/** 预留:可在此打印系统时钟源与频率。 */
}
void scanAppFlashContent(int print_content, u32 page_count) {
/**
* @brief Print system clock info through the platform helper.
* @return none
*/
void print_system_clock_info(void) { BootPlatform::printSystemClockInfo(); }
/**
* @brief 扫描主应用区 Flash 内容。
*
* @param print_content 是否打印内容。
* @param page_count 需要扫描的页数。
* @return 无。
*/
void BootPlatform::scanAppFlashContent(int print_content, u32 page_count)
{
/** 预留:调试用,后续可打印主应用区内容。 */
(void)print_content;
(void)page_count;
}
static fmc_state_enum write_ADDRESS(u32 address, u32 data) {
/**
* @brief Scan application flash content through the platform helper.
* @param print_content Whether to print flash payload.
* @param page_count Number of pages to scan.
* @return none
*/
void scanAppFlashContent(int print_content, u32 page_count) { BootPlatform::scanAppFlashContent(print_content, page_count); }
/**
* @brief 向指定 Flash 地址写入一个 32 位字。
*
* @param address 目标地址。
* @param data 目标数据。
* @return FMC 编程状态码。
*/
fmc_state_enum BootSleepManager::writeAddress(u32 address, u32 data)
{
/**
* 对单个 Flash word 做安全写入:
* - 解锁 FMC
* - 清状态位
* - 编程
* - 重新上锁
*/
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(address, data);
@@ -60,11 +167,24 @@ static fmc_state_enum write_ADDRESS(u32 address, u32 data) {
return status;
}
void enter_deep_sleep(void) {
/**
* @brief 进入深度睡眠模式。
*
* @return 无。
*/
void BootSleepManager::enterDeepSleep(void)
{
/**
* 进入深睡前先关掉暂时不需要的外设时钟,降低待机功耗。
*/
rcu_periph_clock_disable(RCU_TIMER0);
rcu_periph_clock_disable(RCU_TIMER1);
rcu_periph_clock_disable(RCU_ADC0);
/**
* 使用 PB8 作为唤醒外部事件输入。
* 这里配置为浮空输入,并挂接 EXTI8 上升沿触发。
*/
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
@@ -74,22 +194,47 @@ void enter_deep_sleep(void) {
exti_interrupt_enable(EXTI_8);
nvic_irq_enable(EXTI5_9_IRQn, 0U, 0U);
exti_interrupt_flag_clear(EXTI_8);
__enable_irq();
delay_1ms(10U);
/** 进入深度睡眠,等待外部中断唤醒。 */
pmu_to_deepsleepmode(PMU_LDO_LOWPOWER, PMU_LOWDRIVER_DISABLE, WFI_CMD);
}
void EXTI5_9_IRQHandler(void) {
/**
* @brief 处理 EXTI5_9 中断实际逻辑。
*
* @return 无。
*/
void BootSleepManager::onExtiInterrupt(void)
{
/**
* EXTI5_9 同时复用了两个来源:
* 1. PB8:深睡唤醒信号
* 2. PD7:SPI OTA 片选信号
*/
if (exti_interrupt_flag_get(EXTI_8) != RESET) {
exti_interrupt_flag_clear(EXTI_8);
exti_interrupt_disable(EXTI_8);
/** 唤醒后重新允许启动主应用。 */
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
/**
* 睡眠配置区按日志式写法追加状态:
* 从后往前找最后一个有效条目,再把 SLEEP_ENABLE 改写为 SLEEP_DISABLE。
*/
for (u32 address = SLEEP_CONFIG_END - SIZE_U32; address >= SLEEP_CONFIG_START; address -= SIZE_U32) {
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 sleep_check = *flash_ptr;
if (sleep_check == FLASH_EMPTY && address > SLEEP_CONFIG_START) {
continue;
} else if (sleep_check == SLEEP_ENABLE && address > SLEEP_CONFIG_START) {
/**
* 若最后一个可写位置已经用满,则整页擦除后从头开始写。
*/
if (address == SLEEP_CONFIG_END - SIZE_U32) {
fmc_unlock();
if (fmc_page_erase(SLEEP_CONFIG_START) != FMC_READY) {
@@ -98,34 +243,86 @@ void EXTI5_9_IRQHandler(void) {
}
address = SLEEP_CONFIG_START - SIZE_U32;
}
(void)write_ADDRESS(address + SIZE_U32, SLEEP_DISABLE);
(void)writeAddress(address + SIZE_U32, SLEEP_DISABLE);
break;
} else if (sleep_check == SLEEP_DISABLE) {
/** 已经记录为禁用睡眠,无需重复写。 */
break;
} else {
(void)write_ADDRESS(address, SLEEP_DISABLE);
/** 配置区为空或数据异常时,直接当前位置补写禁用标记。 */
(void)writeAddress(address, SLEEP_DISABLE);
break;
}
}
/** 通过软复位重新进入正常启动流程。 */
system_soft_reset();
}
/** 继续分发给 SPI OTA 的 CS 边沿处理逻辑。 */
spi_ota_cs_exti_handler();
}
int check_sleep(void) {
/**
* @brief 检查睡眠配置并决定是否进入深睡。
*
* @return 0 表示已有有效配置;1 表示写默认配置失败;2 表示已写入默认禁用配置。
*/
int BootSleepManager::check(void)
{
/**
* 启动时检查睡眠配置页中最新的有效状态:
* - SLEEP_ENABLE -> 直接进深睡
* - SLEEP_DISABLE -> 正常启动
* - 全空 -> 默认写入禁用状态
*/
for (u32 address = SLEEP_CONFIG_END - SIZE_U32; address >= SLEEP_CONFIG_START; address -= SIZE_U32) {
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 sleep_check = *flash_ptr;
if (sleep_check == FLASH_EMPTY) {
continue;
} else if (sleep_check == SLEEP_ENABLE) {
enter_deep_sleep();
enterDeepSleep();
return 0;
} else if (sleep_check == SLEEP_DISABLE) {
return 0;
}
}
if (write_ADDRESS(SLEEP_CONFIG_START, SLEEP_DISABLE) != FMC_READY) {
/** 首次运行或配置页为空:写入默认值 SLEEP_DISABLE。 */
if (writeAddress(SLEEP_CONFIG_START, SLEEP_DISABLE) != FMC_READY) {
return 1;
}
return 2;
}
/**
* @brief Enter deep sleep through the sleep manager.
* @return none
*/
void enter_deep_sleep(void) { BootSleepManager::enterDeepSleep(); }
/**
* @brief Check sleep configuration through the sleep manager.
* @return Status code from BootSleepManager::check().
*/
int check_sleep(void) { return BootSleepManager::check(); }
/**
* @brief External interrupt handler for EXTI line group 5 to 9.
* @return none
*/
extern "C" void EXTI5_9_IRQHandler(void)
{
BootSleepManager::onExtiInterrupt();
}
/**
* @brief External interrupt handler for EXTI line group 10 to 15.
* @return none
*/
extern "C" void EXTI10_15_IRQHandler(void)
{
BootSleepManager::onExtiInterrupt();
}
+113
View File
@@ -7,16 +7,129 @@ extern "C" {
#include <stdio.h>
#include <stdint.h>
#include "gd32e50x_fmc.h"
#include "sys/config.h"
/**
* @brief Bootloader 调试串口模块。
*
* 负责 USART0 初始化与 printf 输出重定向。
*/
class BootDebugUart {
public:
/** @brief 初始化 USART0 调试串口。 */
static u8 init();
/** @brief 输出单个字符。 */
static int putChar(int ch, FILE *f);
};
/**
* @brief Bootloader 睡眠与唤醒管理模块。
*
* 负责深度睡眠进入、睡眠配置检查以及 EXTI5_9 唤醒路径处理。
*/
class BootSleepManager {
public:
/** @brief 进入深度睡眠模式。 */
static void enterDeepSleep();
/** @brief 检查睡眠配置并决定是否进入深睡。 */
static int check();
/** @brief 处理 EXTI5_9 中断实际逻辑。 */
static void onExtiInterrupt();
private:
/** @brief 向指定 Flash 地址写入一个 32 位字。 */
static fmc_state_enum writeAddress(u32 address, u32 data);
};
/**
* @brief Bootloader 平台辅助模块。
*
* 负责复位原因读取、未使用引脚初始化、系统时钟信息打印与应用区扫描等平台杂项。
*/
class BootPlatform {
public:
static u16 getResetCause();
static void initUnusedPins();
static void printSystemClockInfo();
static void scanAppFlashContent(int print_content, u32 page_count);
};
/**
* @brief printf 重定向输出函数。
*
* @param ch 待输出字符。
* @param f 目标文件流,当前实现未使用。
* @return 输出的字符值。
*/
int fputc(int ch, FILE *f) throw();
/**
* @brief 初始化 USART0 调试串口。
*
* @return 1 表示初始化完成。
*/
u8 USART0_init(void);
/**
* @brief 获取复位原因。
*
* @note 当前实现为占位实现。
*
* @return 复位原因编码。
*/
u16 get_reset_cause(void);
/**
* @brief 初始化未使用引脚。
*
* @return 无。
*/
void unused_pin_init(void);
/**
* @brief 打印系统时钟信息。
*
* @note 当前实现为占位实现。
*
* @return 无。
*/
void print_system_clock_info(void);
/**
* @brief 进入深度睡眠模式。
*
* @return 无。
*/
void enter_deep_sleep(void);
/**
* @brief 检查睡眠配置并决定是否进入深睡。
*
* @return 0 表示已有有效配置;1 表示写默认配置失败;2 表示已写入默认禁用配置。
*/
int check_sleep(void);
/**
* @brief EXTI5_9 中断服务函数。
*
* @return 无。
*/
void EXTI5_9_IRQHandler(void);
/**
* @brief EXTI10_15 中断服务函数。
* @return 无。
*/
void EXTI10_15_IRQHandler(void);
/**
* @brief 扫描主应用区 Flash 内容。
*
* @param print_content 是否打印内容。
* @param page_count 需要扫描的页数。
* @return 无。
*/
void scanAppFlashContent(int print_content, u32 page_count);
#ifdef __cplusplus
+131 -14
View File
@@ -1,47 +1,164 @@
#include "systick.h"
#include "gd32e50x.h"
static volatile uint32_t delay_countdown;
/**
* SysTick 驱动模块
* -----------------
* 提供 Bootloader 中最基础的延时与软复位能力:
* - SysTickTimer::init(): 配置 1ms 系统节拍
* - SysTickTimer::delayUs()/delayMs()/delaySeconds(): 简单阻塞延时
* - SysTickTimer::softReset(): 触发 MCU 软复位
*/
void systick_config(void) {
volatile uint32_t SysTickTimer::s_delayCountdown = 0U;
/**
* @brief 配置 SysTick 为 1ms 周期中断。
*
* @return 无。
*/
void SysTickTimer::init()
{
/** 将 SysTick 配置为 1ms 一次中断。 */
if (SysTick_Config(SystemCoreClock / 1000U)) {
/** 配置失败通常意味着时钟参数异常,直接停机等待。 */
while (1) {
}
}
/** 设为最高优先级,保证延时基准尽量稳定。 */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
void delay_1us(uint32_t count) {
/**
* @brief 执行微秒级粗略阻塞延时。
*
* @param count 延时微秒数。
* @return 无。
*/
void SysTickTimer::delayUs(uint32_t count)
{
/**
* 这里使用空指令忙等实现粗略微秒延时。
* 由于循环/指令开销存在,精度不会像硬件定时器那样严格。
*/
uint32_t cycles = (SystemCoreClock / 4000000UL) * count;
for (uint32_t i = 0; i < cycles; ++i) {
__NOP();
}
}
void delay_1ms(uint32_t count) {
delay_countdown = count;
while (delay_countdown != 0U) {
/**
* @brief 执行毫秒级阻塞延时。
*
* @param count 延时毫秒数。
* @return 无。
*/
void SysTickTimer::delayMs(uint32_t count)
{
/** 设置倒计时,由 SysTick_Handler 每毫秒递减。 */
s_delayCountdown = count;
while (s_delayCountdown != 0U) {
}
}
void delay_1s(uint32_t count) {
/**
* @brief 执行秒级阻塞延时。
*
* @param count 延时秒数。
* @return 无。
*/
void SysTickTimer::delaySeconds(uint32_t count)
{
/** 基于 1ms 延时拼出秒级延时。 */
while (count--) {
delay_1ms(1000U);
delayMs(1000U);
}
}
void delay_decrement(void) {
if (delay_countdown != 0U) {
delay_countdown--;
/**
* @brief 递减毫秒倒计时。
*
* @return 无。
*/
void SysTickTimer::decrement()
{
/** 供 SysTick 中断调用,防止无符号下溢。 */
if (s_delayCountdown != 0U) {
s_delayCountdown--;
}
}
void SysTick_Handler(void) {
delay_decrement();
/**
* @brief SysTick 中断实际处理逻辑。
*
* @return 无。
*/
void SysTickTimer::onInterrupt()
{
/** 每次滴答中断只做极简工作,降低中断负担。 */
decrement();
}
void system_soft_reset(void) {
/**
* @brief 触发 MCU 软件复位。
*
* @return 无。
*/
void SysTickTimer::softReset()
{
/** 通过 CMSIS 接口触发系统软复位。 */
NVIC_SystemReset();
/** 理论上不会执行到这里,保留死循环作为保险。 */
while (1) {
}
}
/**
* @brief Configure SysTick via the timer helper.
* @return none
*/
void systick_config(void) { SysTickTimer::init(); }
/**
* @brief Delay for microseconds via the timer helper.
* @param count Delay time in microseconds.
* @return none
*/
void delay_1us(uint32_t count) { SysTickTimer::delayUs(count); }
/**
* @brief Delay for milliseconds via the timer helper.
* @param count Delay time in milliseconds.
* @return none
*/
void delay_1ms(uint32_t count) { SysTickTimer::delayMs(count); }
/**
* @brief Delay for seconds via the timer helper.
* @param count Delay time in seconds.
* @return none
*/
void delay_1s(uint32_t count) { SysTickTimer::delaySeconds(count); }
/**
* @brief Decrement the global millisecond delay counter.
* @return none
*/
void delay_decrement(void) { SysTickTimer::decrement(); }
/**
* @brief Trigger a software reset via the timer helper.
* @return none
*/
void system_soft_reset(void) { SysTickTimer::softReset(); }
/**
* @brief SysTick interrupt service routine.
* @return none
*/
extern "C" void SysTick_Handler(void)
{
SysTickTimer::onInterrupt();
}
+64
View File
@@ -7,11 +7,75 @@ extern "C" {
#include <stdint.h>
/**
* @brief Bootloader SysTick 时基模块。
*
* 负责提供毫秒节拍、阻塞延时与软复位能力。
*/
class SysTickTimer {
public:
/** @brief 初始化 1ms SysTick 节拍。 */
static void init();
/** @brief 毫秒阻塞延时。 */
static void delayMs(uint32_t count);
/** @brief 微秒级粗略阻塞延时。 */
static void delayUs(uint32_t count);
/** @brief 秒级阻塞延时。 */
static void delaySeconds(uint32_t count);
/** @brief 递减毫秒倒计时。 */
static void decrement();
/** @brief SysTick 中断实际处理逻辑。 */
static void onInterrupt();
/** @brief 触发软件复位。 */
static void softReset();
private:
static volatile uint32_t s_delayCountdown;
};
/**
* @brief 配置 SysTick 为 1ms 周期中断。
*
* @return 无。
*/
void systick_config(void);
/**
* @brief 执行毫秒级阻塞延时。
*
* @param count 延时毫秒数。
* @return 无。
*/
void delay_1ms(uint32_t count);
/**
* @brief 执行微秒级粗略阻塞延时。
*
* @param count 延时微秒数。
* @return 无。
*/
void delay_1us(uint32_t count);
/**
* @brief 执行秒级阻塞延时。
*
* @param count 延时秒数。
* @return 无。
*/
void delay_1s(uint32_t count);
/**
* @brief 递减 SysTick 维护的毫秒倒计时。
*
* @return 无。
*/
void delay_decrement(void);
/**
* @brief 触发 MCU 软件复位。
*
* @return 无。
*/
void system_soft_reset(void);
#ifdef __cplusplus