title: HC32F448基础功能使用
date: 2024.07.04
小华HC32F448基本使用方法及例程
话不多说,本人的模板例程在这,开源自行下载:
链接:https://pan.baidu.com/s/1JSSa2bRqiioi4Yr8vWp28g?pwd=r113
提取码:r113
小华单片机是一款国产的32位微处理器,
从他官网上看,这款单片机采用ARMCortex-M4内核,Vcc为1.8 ~ 3.6V,200MHz主频,内置256KB Flash,68KB SRAM,内部集成PLL,3个12-bit 2.5MSPS SAR ADC,212-bit DAC,6U(S)ART、3SPI、2I2C、2*CAN等外设,有QFN32/QFN48/LQFP48/LQFP64/LQFP80五种封装。
横向对比他的HC32F460可以看到:
其他产品的优点这里不在赘述,详情参考官网小华半导体有限公司 (xhsc.com.cn)
具体新建项目工程不在这里讲,可以参考这篇文章[HC32F4A0PITB创建工程模板_hc32f4a0pitb软件创建-CSDN博客](https://blog.csdn.net/honmy18/article/details/139503472?ops_request_misc=%7B%22request%5Fid%22%3A%22172008141316800213047389%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=172008141316800213047389&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-139503472-null-null.142v100pc_search_result_base3&utm_term=HC32F4 新建&spm=1018.2226.3001.4187)
本文章只对我自己的项目模板做讲解
1、GPIO输入输出
源文件如下:
#include "GPIO.h"
/**
* @brief LED Init
* @param None
* @retval None
*/
void My_GPIO_Init(void)
{
stc_gpio_init_t stcGpioInit;
//IO
(void)GPIO_StructInit(&stcGpioInit);
stcGpioInit.u16PinState = PIN_STAT_SET;
stcGpioInit.u16PinDir = PIN_DIR_OUT;
(void)GPIO_Init(LED_G_PORT, LED_G_PIN, &stcGpioInit);
//Key
(void)GPIO_StructInit(&stcGpioInit);
stcGpioInit.u16PinDir = PIN_DIR_IN;
stcGpioInit.u16PullUp=PIN_PU_ON;
(void)GPIO_Init(USER_PORT, USER_PIN, &stcGpioInit);
}
头文件如下:
#ifndef __GPIO_H_
#define __GPIO_H_
#include "hc32_ll.h"
#include "hc32_ll_gpio.h"
#define LED_G_PORT (GPIO_PORT_B)
#define LED_G_PIN (GPIO_PIN_05)
#define USER_PORT (GPIO_PORT_D)
#define USER_PIN (GPIO_PIN_02)
/* LED toggle definition */
#define LED_G_TOGGLE() (GPIO_TogglePins(LED_G_PORT, LED_G_PIN))
#define DLY_MS (20000UL)
void My_GPIO_Init(void);
#endif
以上是配置GPIO的相关操作,包括了LED的闪灯以及按键的输入。详细的代码可以参考官方给的参考代码,虽然读起来很枯燥,但是这是开发一款新板子的必要步骤,或者就等着别人做好现成的拿去学。需要注意的是,在主函数中,因为使用了LL库,需要在初始化代码加上这段把初始化代码括起来:
LL_PERIPH_WE(EXAMPLE_PERIPH_WE|LL_PERIPH_SEL);
//初始化代码
/* Peripheral registers write protected */
LL_PERIPH_WP(EXAMPLE_PERIPH_WP|LL_PERIPH_SEL);
具体定义:
/* unlock/lock peripheral */
#define EXAMPLE_PERIPH_WE (LL_PERIPH_GPIO | LL_PERIPH_EFM | LL_PERIPH_FCG | \
LL_PERIPH_PWC_CLK_RMU | LL_PERIPH_SRAM)
#define EXAMPLE_PERIPH_WP (LL_PERIPH_EFM | LL_PERIPH_FCG | LL_PERIPH_SRAM)
这部分的话可以去参考官方的文档,当然,如果你出现了类似于这种的报错的话:
..\source\main.c(165): error: #20: identifier "stc_tmr0_init_t" is undefined
stc_tmr0_init_t stcTmr0Init;
..\source\main.c(173): warning: #223-D: function "TMR0_StructInit" declared implicitly
(void)TMR0_StructInit(&stcTmr0Init);
..\source\main.c(174): error: #20: identifier "TMR0_CLK_SRC_XTAL32" is undefined
stcTmr0Init.u32ClockSrc = TMR0_CLK_SRC_XTAL32;
..\source\main.c(175): error: #20: identifier "TMR0_CLK_DIV2" is undefined
stcTmr0Init.u32ClockDiv = TMR0_CLK_DIV2;
..\source\main.c(176): error: #20: identifier "TMR0_FUNC_CMP" is undefined
stcTmr0Init.u32Func = TMR0_FUNC_CMP;
..\source\main.c(178): warning: #223-D: function "TMR0_Init" declared implicitly
(void)TMR0_Init(TMR0_UNIT, TMR0_CH, &stcTmr0Init);
..\source\main.c(178): error: #20: identifier "TMR0_CH_B" is undefined
(void)TMR0_Init(TMR0_UNIT, TMR0_CH, &stcTmr0Init);
..\source\main.c(181): warning: #223-D: function "TMR0_HWStopCondCmd" declared implicitly
TMR0_HWStopCondCmd(TMR0_UNIT, TMR0_CH, ENABLE);
..\source\main.c(184): warning: #223-D: function "TMR0_IntCmd" declared implicitly
TMR0_IntCmd(TMR0_UNIT, TMR0_CH_INT, ENABLE);
..\source\main.c(184): error: #20: identifier "TMR0_INT_CMP_B" is undefined
TMR0_IntCmd(TMR0_UNIT, TMR0_CH_INT, ENABLE);
大概率是在 hc32f4xx_conf.h 中没有开启所对应的功能导致的:
把这个OFF改成ON,再结合一下官方的代码就可以正常编译运行了。
2、定时器相关
定时器作为一款单片机的心脏,发挥着很重要的作用,对于这款单片机来说,具体代码如下:
/* TMR0 unit and channel definition */
#define TMR0_UNIT (CM_TMR0_1)
#define TMR0_CLK (FCG2_PERIPH_TMR0_1)
#define TMR0_CH (TMR0_CH_B)
#define TMR0_TRIG_CH (AOS_TMR0)
#define TMR0_CH_INT (TMR0_INT_CMP_B)
#define TMR0_CH_FLAG (TMR0_FLAG_CMP_B)
#define TMR0_INT_SRC (INT_SRC_TMR0_1_CMP_B)
#define TMR0_IRQn (INT006_IRQn)
/* Period = 1 / (Clock freq / div) * (Compare value + 1) = 500ms */
#define TMR0_CMP_VALUE (XTAL32_VALUE / 16U / 2U - 1U)
其中,这句话:
是使用外部晶振的时候需要,内部时钟的话直接在初始化里面分频就可以得到自己想要的时间周期。(本开发板的外部晶振时钟频率为8MHz)
具体代码如下:
/**
* @brief Configure TMR0.
* @note In asynchronous clock, If you want to write a TMR0 register, you need to wait for
* at least 3 asynchronous clock cycles after the last write operation!
* @param None
* @retval None
*/
void TMR0_Config(void)
{
stc_tmr0_init_t stcTmr0Init;
stc_irq_signin_config_t stcIrqSignConfig;
/* Enable timer0 and AOS clock */
FCG_Fcg2PeriphClockCmd(TMR0_CLK, ENABLE);
FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_AOS, ENABLE);
/* TIMER0 configuration */
(void)TMR0_StructInit(&stcTmr0Init);
stcTmr0Init.u32ClockSrc = TMR0_CLK_SRC_XTAL32;
stcTmr0Init.u32ClockDiv = TMR0_CLK_DIV2;
stcTmr0Init.u32Func = TMR0_FUNC_CMP;
stcTmr0Init.u16CompareValue = (uint16_t)TMR0_CMP_VALUE;
(void)TMR0_Init(TMR0_UNIT, TMR0_CH, &stcTmr0Init);
/* Asynchronous clock source, writing to TMR0 register requires waiting for three asynchronous clocks. */
DDL_DelayMS(1000U);
TMR0_HWStopCondCmd(TMR0_UNIT, TMR0_CH, ENABLE);
/* Asynchronous clock source, writing to TMR0 register requires waiting for three asynchronous clocks. */
DDL_DelayMS(1000U);
TMR0_IntCmd(TMR0_UNIT, TMR0_CH_INT, ENABLE);
// /* Asynchronous clock source, writing to TMR0 register requires waiting for three asynchronous clocks. */
DDL_DelayMS(1000U);
AOS_SetTriggerEventSrc(TMR0_TRIG_CH, BSP_KEY_KEY5_EVT);
/* Interrupt configuration */
stcIrqSignConfig.enIntSrc = TMR0_INT_SRC;
stcIrqSignConfig.enIRQn = TMR0_IRQn;
stcIrqSignConfig.pfnCallback = &TMR0_CompareIrqCallback;
(void)INTC_IrqSignIn(&stcIrqSignConfig);
NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);
}
/**
* @brief Configure XTAL32.
* @param None
* @retval None
*/
void XTAL32_Config(void)
{
stc_clock_xtal32_init_t stcXtal32Init;
(void)CLK_Xtal32StructInit(&stcXtal32Init);
/* Configure Xtal32 */
stcXtal32Init.u8State = CLK_XTAL32_ON;
stcXtal32Init.u8Drv = CLK_XTAL32_DRV_MID;
stcXtal32Init.u8Filter = CLK_XTAL32_FILTER_OFF;
GPIO_AnalogCmd(BSP_XTAL32_PORT, BSP_XTAL32_IN_PIN | BSP_XTAL32_OUT_PIN, ENABLE);
(void)CLK_Xtal32Init(&stcXtal32Init);
}
这块代码是为了使用外部晶振写的初始化代码,使用内部晶振的话不用考虑。其中,初始化的代码当中,这部分很重要:
第一个参数是你使用的始终源,最后一个参数是比较值,也就是算周期要用的值,上面define过。
需要特别注意的是:当你使用外部晶振时,在异步时钟下,必须保证所选的异步时钟开启;否则TMR0将不会计数,配置也不一定准确。异步时钟下,对TMR0寄存器操作需要3个异步时钟才能写入。因此,代码这部分的延时是充分考虑到这个因素:
我只能说,这种初始化延时的不确定因素真的会在很关键的时候要你的命。原因可能是内核,反正我见过好多国产的单片机都有这个通病,很离谱。
3、U(S)ART相关
相关define如下:
/* USART RX/TX pin definition */
#define USART_RX_PORT (GPIO_PORT_C) /* PC11: USART2_RX */
#define USART_RX_PIN (GPIO_PIN_11)
#define USART_RX_GPIO_FUNC (GPIO_FUNC_37)
#define USART_TX_PORT (GPIO_PORT_C) /* PC10: USART2_TX */
#define USART_TX_PIN (GPIO_PIN_10)
#define USART_TX_GPIO_FUNC (GPIO_FUNC_36)
/* USART unit definition */
#define USART_UNIT (CM_USART2)
#define USART_FCG_ENABLE() (FCG_Fcg3PeriphClockCmd(FCG3_PERIPH_USART2, ENABLE))
/* USART interrupt definition */
#define USART_RX_ERR_IRQn (INT000_IRQn)
#define USART_RX_ERR_INT_SRC (INT_SRC_USART2_EI)
主函数初始化时要加上这句:
BSP_CLK_Init();//务必保留
之后就是正常的初始化程序:
/* Configure USART RX/TX pin. */
GPIO_SetFunc(USART_RX_PORT, USART_RX_PIN, USART_RX_GPIO_FUNC);
GPIO_SetFunc(USART_TX_PORT, USART_TX_PIN, USART_TX_GPIO_FUNC);
/* Enable peripheral clock */
USART_FCG_ENABLE();
/* Initialize UART. */
(void)USART_UART_StructInit(&stcUartInit);
stcUartInit.u32ClockDiv = USART_CLK_DIV64;
stcUartInit.u32Baudrate = 115200UL;
stcUartInit.u32OverSampleBit = USART_OVER_SAMPLE_8BIT;
if (LL_OK != USART_UART_Init(USART_UNIT, &stcUartInit, NULL)) {
for (;;) {
}
}
/* Register error IRQ handler && configure NVIC. */
stcIrqSigninConfig.enIRQn = USART_RX_ERR_IRQn;
stcIrqSigninConfig.enIntSrc = USART_RX_ERR_INT_SRC;
stcIrqSigninConfig.pfnCallback = &USART_RxError_IrqCallback;
(void)INTC_IrqSignIn(&stcIrqSigninConfig);
NVIC_ClearPendingIRQ(stcIrqSigninConfig.enIRQn);
NVIC_SetPriority(stcIrqSigninConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
NVIC_EnableIRQ(stcIrqSigninConfig.enIRQn);
/* Peripheral registers write protected */
LL_PERIPH_WP(EXAMPLE_PERIPH_WP|LL_PERIPH_SEL);
/* Enable RX/TX function */
USART_FuncCmd(USART_UNIT, (USART_RX | USART_INT_RX | USART_TX), ENABLE);
我在定时器里面做了收发的相关操作:
#if (MASTER_DEVICE_ENABLE == DDL_ON)
USART_WriteData(USART_UNIT, 0x0A);
#else
if (SET == USART_GetStatus(USART_UNIT, USART_FLAG_RX_FULL)) {
flag = USART_ReadData(USART_UNIT);
while (RESET == USART_GetStatus(USART_UNIT, USART_FLAG_TX_EMPTY));
if(flag==0x0A)LED_G_TOGGLE();
else GPIO_ResetPins(LED_G_PORT, LED_G_PIN);
USART_WriteData(USART_UNIT, flag);
}
#endif
主要设置了主从机的相关代码,只需要改预编译的define就行。内容就是一个主机发特定字符,另一个从机接受后判断是否为这个字符后进行GPIO反转电平的操作实现LED闪灯。
这部分代码是设置波特率等相关寄存器的操作。
总的来说,如果你有单片机开发的基础的话,不用担心会有多难,只不过是时间问题。其他功能的话还有待开发,等用到的时候再写吧。