feat(boot): 添加SPI OTA功能并更新配置
- 添加SpiOta.h和SpiOta.cpp文件以实现SPI OTA功能 - 在项目配置中添加SPI_I2S外设组件支持 - 设置外部晶振值为8MHz (HXTAL_VALUE=8000000U) - 调整ROM0大小从512KB减小到2KB以适应引导程序需求 - 移除评估板相关文件和看门狗驱动文件以优化存储空间
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user