GD32的GD库开发
所有的Cortex-M处理器都有相同的SysTick定时器,因为CMSIS-Core头文件中定义了一个名为SysTick的结构体。
这个定时器可以用作延时函数,不管是STM32的芯片还是GD32,AT32的芯片,delay函数都可以这么写,只要它是cortex-M3/M4的芯片。
以下代码基于GD32F303,主频120M。
延迟函数
示例代码
代码移植于江科大。
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
//这里假设主频为72M,如果主频不是72M,自行修改即可
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
GPIO
参数配置
和STM32的GPIO类似。
库函数调用
时钟使能:rcu_periph_clock_enable()
GPIO参数初始化:gpio_init(uint32_t gpio_periph, uint32_t mode, uint32_t speed, uint32_t pin)
以初始化PA8,PD2为例
rcu_periph_clock_enable(RCU_GPIOA); //GPIOA时钟使能
rcu_periph_clock_enable(RCU_GPIOD); //GPIOD时钟使能
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8); //设置PA8推挽输出
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2); //设置PD2推挽输出
写引脚电平
gpio_bit_write(GPIOA, GPIO_PIN_8, SET):
读引脚电平
gpio_input_bit_get(GPIOA,GPIO_PIN_13)
翻转引脚电平
//翻转IO口状态
void gpio_togglepin(uint32_t gpio_periph, uint32_t pin)
{
uint32_t octl;
octl = GPIO_OCTL(gpio_periph);
GPIO_BOP(gpio_periph) = ((octl & pin) << 16u) | (~octl & pin);
}
ADC
参数配置
ADC初始化
//初始化ADC
void Adc_Init(void)
{
rcu_periph_clock_enable(RCU_GPIOC); //使能GPIOC时钟
gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_4); //AD采集引脚(PC4)模式设置,模拟输入
rcu_periph_clock_enable(RCU_ADC0); //使能ADC0时钟
adc_deinit(ADC0); //复位ADC0
//ADC时钟来自APB2,频率为120Mhz
//使用6分频,得到APB2/6 = 20Mhz的ADC时钟
rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6); //配置ADC时钟
adc_mode_config(ADC_MODE_FREE); //ADC独立工作模式
adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE); //非扫描模式
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE); //禁止连续模式
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); //数据右对齐
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE); //常规序列使能外部触发
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE); //常规序列使用软件触发
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1); //规则序列长度,1个转换在规则序列中,也就是只转换规则序列1
adc_enable(ADC0); //使能ADC0
adc_calibration_enable(ADC0); //使能ADC0校准复位
}
函数调用
获取ADC值
//获得ADC转换后的结果
//ch:通道值 0~17
//返回值:转换结果
uint16_t Get_Adc(uint8_t ch)
{
uint16_t adc_value = 0;
adc_regular_channel_config(ADC0, 0, ch, ADC_SAMPLETIME_239POINT5);//配置ADC规则通道组,选择采样时间为239.5周期,提高采样时间可以提高精确度
adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); //软件触发使能,常规序列转换开始
while(SET != adc_flag_get(ADC0,ADC_FLAG_EOC)); //等待转换结束
adc_value = adc_regular_data_read(ADC0); //读ADC规则组数据寄存器
return adc_value; //返回最近一次ADC0的转换结果
}
获取多组ADC平均值
//获取通道ch的转换值,取times次,然后平均
//ch:通道编号
//times:获取次数
//返回值:通道ch的times次转换结果平均值
uint16_t Get_Adc_Average(uint8_t ch,uint8_t times)
{
uint32_t temp_val=0;
uint8_t t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch); //获取times次数据
delay_ms(5);
}
return temp_val/times; //返回平均值
}
自行转换ADC值为电压
adc_val = Get_Adc_Average(ADC_CH14,20);
adc_volt = (float)adc_val/4096.0f * 3.3f;
串口
参数配置
uart.c。
代码分为三部分。
第一部分为os以及printf函数的相关定义。
第二部分为串口的初始化
第三部分为串口的中断处理函数
#include "usart.h"
/* 如果使用os,则包括下面的头文件即可. */
#if SYS_SUPPORT_OS
#include "includes.h" /* os 使用 */
#endif
/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while(RESET == usart_flag_get(USART0, USART_FLAG_TC)); /* 等待上一个字符发送完成 */
usart_data_transmit(USART0, (uint8_t)ch); /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
/******************************************************************************************/
#if USART_EN_RX /*如果使能了接收*/
/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t USART_RX_BUF[USART_REC_LEN];
/* 接收状态
* bit15, 接收完成标志
* bit14, 接收到0x0d
* bit13~0, 接收到的有效字节数目
*/
uint16_t USART_RX_STA = 0;
//串口0初始化函数
//bound: 波特率, 根据自己需要设置波特率值
void usart_init(uint32_t bound)
{
//使能GPIO时钟和复用时钟
rcu_periph_clock_enable(RCU_GPIOA); //使能GPIOA时钟
rcu_periph_clock_enable(RCU_AF); //使能复用时钟
rcu_periph_clock_enable(RCU_USART0); //使能串口时钟
//配置TX的GPIO
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
//配置RX的GPIO
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
//配置USART的参数
usart_deinit(USART0); //RCU配置恢复默认值
usart_baudrate_set(USART0, bound); //设置波特率
usart_stop_bit_set(USART0, USART_STB_1BIT); //一个停止位
usart_word_length_set(USART0, USART_WL_8BIT); //字长为8位数据格式
usart_parity_config(USART0, USART_PM_NONE); //无奇偶校验位
usart_receive_config(USART0, USART_RECEIVE_ENABLE); //使能接收
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); //使能发送
usart_interrupt_enable(USART0, USART_INT_RBNE); //使能接收缓冲区非空中断
//配置NVIC,并设置中断优先级
nvic_irq_enable(USART0_IRQn, 3, 3); //抢占优先级3,子优先级3
//使能串口
usart_enable(USART0);
}
void USART0_IRQHandler(void)
{
uint8_t Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾(回车/换行) )
{
Res =usart_data_receive(USART0); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
uart.h
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "sys.h"
//
//串口0初始化
//
//如果想串口中断接收,请不要注释以下宏定义
#define USART_REC_LEN 200 /* 定义最大接收字节数 200 */
#define USART_EN_RX 1 /* 使能(1)/禁止(0)串口0接收 */
extern uint8_t USART_RX_BUF[USART_REC_LEN]; /*接收缓冲,最大USART_REC_LEN个字节.末字节为换行符*/
extern uint16_t USART_RX_STA; /*接收状态标记*/
void usart_init(uint32_t bound); /* 串口初始化函数 */
#endif
库函数调用
串口使用的代码参考
if(USART_RX_STA&0x8000) //接收完了一次数据
{
len=USART_RX_STA&0x3fff; //得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
for(t=0;t<len;t++)
{
usart_data_transmit(USART0, USART_RX_BUF[t]); //发送接收到的数据
while(RESET == usart_flag_get(USART0, USART_FLAG_TC));//等待发送结束
}
printf("\r\n\r\n"); //插入换行
USART_RX_STA=0;
}
else
{
times++;
if(times%5000==0)
{
printf("\r\nWKS MiniGD32开发板 串口实验\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0) LED0_TOGGLE();//闪烁LED,提示系统正在运行.
delay_ms(10);
}
PWM输出
PWM频率计算公式
公式应用:
如果我们要输出1000hz,占空比为50%的方波信号。
假如系统频率为120MHz = 120*10^6Hz。可以这样设置
PSC = 1200 - 1,PSC + 1 = 1200。ARR = 100 - 1。ARR + 1 = 100。
f = fsystem / [ (PSC+1)*(ARR+1) ] = 1000Hz
参数配置
定时器初始化,这里设置上电后默认输出1000Hz,占空比为50%。
/**
* @brief 初始化TIMER0为PWM模式
*
* 该函数用于初始化TIMER0定时器为PWM模式,设置相关的时钟、GPIO和定时器参数,
* 以生成PWM信号。PWM信号的频率和占空比可以通过参数arr和psc进行调整。
*
* @param arr 自动重装载值,决定了PWM信号的周期
* @param psc 预分频值,决定了定时器的时钟频率
*/
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
void TIM0_PWM_Init(uint16_t arr,uint16_t psc)
{
//定义初始化结构体变量
timer_oc_parameter_struct timer_ocinitpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_GPIOA); //使能GPIOA时钟
rcu_periph_clock_enable(RCU_TIMER0); //使能TIMER0时钟
rcu_periph_clock_enable(RCU_AF); //使能AF时钟
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8); //设置PA8复用推挽输出
timer_deinit(TIMER0); //复位TIMER0
timer_initpara.prescaler = psc; //设置预分频值
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //设置对齐模式
timer_initpara.counterdirection = TIMER_COUNTER_UP; //设置向上计数模式
timer_initpara.period = arr; //设置自动重装载值。/* ARR-1即为周期 */
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //设置时钟分频因子
timer_initpara.repetitioncounter = 0; //设置重复计数值
timer_init(TIMER0, &timer_initpara); //根据参数初始化定时器
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE; //使能通道输出
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH; //设置通道输出极性为高
timer_channel_output_config(TIMER0, TIMER_CH_0, &timer_ocinitpara); //定时器通道输出配置
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, arr/2); //设置占空比,这里默认设置比较值为自动重装载值的一半,即占空比为50%
timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0); //设置通道比较模式为PWM模式0
timer_primary_output_config (TIMER0, ENABLE); //TIMER0所有的通道输出使能
timer_enable(TIMER0); //使能定时器TIMER0
}
//设置TIMER0通道0的占空比
//compare:比较值 CCR
void TIM_SetTIM0Compare1(uint32_t compare)
{
/* compare的取值范围:0-ARR。占空比 = CCR/(ARR+1) */
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, compare);
}
库函数调用
初始化,并填入ARR和PSC参数。
TIM0_PWM_Init(100-1,1200-1); //默认设置1000HZ,占空比为50%
PWM捕获
参数配置
库函数调用
定时器
参数配置
定时器初始化配置
//通用定时器2中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//APB1时钟为60MHz,TIMER2时钟选择为APB1的2倍,因此,TIMER2时钟为120MHz
void TIM2_Int_Init(uint16_t arr,uint16_t psc)
{
timer_parameter_struct timer_initpara; //timer_initpara用于存放定时器的参数
//使能RCU相关时钟
rcu_periph_clock_enable(RCU_TIMER2); //使能TIMER2的时钟
//复位TIMER2
timer_deinit(TIMER2); //复位TIMER2
timer_struct_para_init(&timer_initpara); //初始化timer_initpara为默认值
//配置TIMER2
timer_initpara.prescaler = psc; //设置预分频值
timer_initpara.counterdirection = TIMER_COUNTER_UP; //设置向上计数模式
timer_initpara.period = arr; //设置自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //设置时钟分频因子
timer_init(TIMER2, &timer_initpara); //根据参数初始化定时器
//使能定时器及其中断
timer_interrupt_enable(TIMER2, TIMER_INT_UP); //使能定时器的更新中断
nvic_irq_enable(TIMER2_IRQn, 1, 3); //配置NVIC设置优先级,抢占优先级1,响应优先级3
timer_enable(TIMER2); //使能定时器TIMER2
}
函数调用
//定时器2中断服务程序
void TIMER2_IRQHandler(void)
{
if(timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_UP) == SET) //判断定时器更新中断是否发生
{
/*--功能代码--*/
LED1_TOGGLE();
//LED1翻转
/*-----------*/
timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP); //清除定时器更新中断标志
}
}
IIC
底层驱动
.c文件
//初始化IIC
void IIC_Init(void)
{
rcu_periph_clock_enable(RCU_GPIO_IIC); //GPIOC时钟使能
//SCL引脚模式设置,推挽输出
gpio_init(GPIO_SCL, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_SCL);
//SDA引脚模式设置,开漏输出,这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平
gpio_init(GPIO_SDA, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_SDA);
IIC_Stop(); //停止总线上所有设备
}
//IIC延时函数,用于控制IIC读写速度
static void IIC_delay(void)
{
delay_us(2); //2us的延时
}
//产生IIC起始信号
void IIC_Start(void)
{
IIC_SDA(1);
IIC_SCL(1);
IIC_delay();
IIC_SDA(0); //START信号: 当SCL为高时, SDA从高变成低, 表示起始信号
IIC_delay();
IIC_SCL(0); //钳住I2C总线,准备发送或接收数据
IIC_delay();
}
//产生IIC停止信号
void IIC_Stop(void)
{
IIC_SDA(0); //STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号
IIC_delay();
IIC_SCL(1);
IIC_delay();
IIC_SDA(1); //发送I2C总线结束信号
IIC_delay();
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
uint8_t waittime = 0;
uint8_t rack = 0;
IIC_SDA(1); //主机释放SDA线(此时外部器件可以拉低SDA线)
IIC_delay();
IIC_SCL(1); //SCL=1, 此时从机可以返回ACK
IIC_delay();
while(IIC_READ_SDA) //等待应答
{
waittime++;
if(waittime>250)
{
IIC_Stop();
rack = 1;
break; //没有收到应答信号
}
}
IIC_SCL(0); //SCL=0, 结束ACK检查
IIC_delay();
return rack;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SDA(0); //SCL 0 -> 1 时 SDA = 0,表示应答
IIC_delay();
IIC_SCL(1); //产生一个时钟
IIC_delay();
IIC_SCL(0);
IIC_delay();
IIC_SDA(1); //主机释放SDA线
IIC_delay();
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SDA(1); //SCL 0 -> 1 时 SDA = 1,表示不应答
IIC_delay();
IIC_SCL(1); //产生一个时钟
IIC_delay();
IIC_SCL(0);
IIC_delay();
}
//IIC发送一个字节
//data: 要发送的数据
void IIC_Send_Byte(uint8_t data)
{
uint8_t t;
for(t=0;t<8;t++)
{
IIC_SDA((data & 0x80) >> 7); //高位先发送
IIC_delay();
IIC_SCL(1);
IIC_delay();
IIC_SCL(0);
data <<= 1; //左移1位,用于下一位发送
}
IIC_SDA(1); //发送完成, 主机释放SDA线
}
//IIC读取一个字节
//ack:ack=1时,发送ACK,ack=0,发送nACK
//返回值:接收到的数据
uint8_t IIC_Read_Byte(uint8_t ack)
{
uint8_t i,receive=0;
for(i=0;i<8;i++ ) //接收1个字节数据
{
receive <<= 1; //高位先输出,所以先收到的数据位要左移
IIC_SCL(1);
IIC_delay();
if (IIC_READ_SDA)
{
receive++;
}
IIC_SCL(0);
IIC_delay();
}
if (!ack)
IIC_NAck(); //发送nACK
else
IIC_Ack(); //发送ACK
return receive; //返回读到的数据
}
.h
#ifndef _MYIIC_H
#define _MYIIC_H
#include "sys.h"
/* 移植时只需修改引脚相关宏定义 */
#define RCU_GPIO_IIC RCU_GPIOC
#define GPIO_SCL GPIOC
#define GPIO_PIN_SCL GPIO_PIN_12
#define GPIO_SDA GPIOC
#define GPIO_PIN_SDA GPIO_PIN_11
/*----------------------------*/
//IO操作
#define IIC_SCL(x) gpio_bit_write(GPIO_SCL, GPIO_PIN_SCL,(bit_status)(x)) //SCL
#define IIC_SDA(x) gpio_bit_write(GPIO_SDA, GPIO_PIN_SDA, (bit_status)(x)) //SDA
#define IIC_READ_SDA gpio_input_bit_get(GPIO_SDA,GPIO_PIN_SDA) //读取SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
uint8_t IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Send_Byte(uint8_t data); //IIC发送一个字节
uint8_t IIC_Read_Byte(uint8_t ack); //IIC读取一个字节
#endif