前言

参考文章\资源

武汉芯源半导体

最新固件库下载地址

CW32L031C8数据手册下载

CW32F030C8数据手册下载

CW Programmer CW32系列芯片的烧录工具配套软件

芯源CW32 MCU官方技术支持论坛

吐槽一下,CW32是真的难搞,网上资料也不多,最好去看固件库例程跟着搞吧

开箱

这个板子是参加圆梦杯申请的,饭盒派CW32_48F/L大学计划开发板

焊接前

一块 CW32030C8T6 最小系统板,然后还给了三片 CW32L031C8(超低功耗MCU产品) 样片

焊接后

注意:电容是有极性的,灰色区域是负极(引脚长为正短为负),对应板子上有+号的是正极,蜂鸣器也是引脚长为正短为负,也可以看丝印有+号,对应板子也上有+号,晶振不分正负,但是焊接要小心不能连锡了

配置开发环境

  • 去官网下载最新的固件库,解压即可,然后找到pack包(在压缩包里),直接双击安装即可

  • 新建工程

正常新建即可,选择芯片那就选对应的型号,然后这里需要勾选 CORE

  • 然后在工程文件夹里新建3个文件夹,app---存放外设USER---存放主函数代码还有公用代码LIB---存放CW库.c.hOTHER---存放启动文件,中断服务程序文件等,把刚刚下载解压的固件库里的对应文件复制过去

core_cm0plus.h 在路径:C:\Users\44478\AppData\Local\Arm\Packs\ARM\CMSIS\5.8.0\CMSIS\Core\Include下可以找到

工程文件夹 存放
USER main.c,main.h(主函数)
public.c,public.h(公用函数)
system.c,system.h(系统函数)
system_init.c,system_init.h(系统初始函数)
callback.c,callback.h(中断函数)
task.c,task.h
LIB inc文件夹
src文件夹
APP 外设代码
OTHER startup_cw32f030.s
core_cm0plus.h
  • Wch-link接线

需要注意要给TFT屏幕接5V,或者加多条线给最小系统板USB供电,否则屏幕可能很暗

如果没有link也可以进行ISP下载就是通过串口1下载hex文件,去下载 CW32_Pragrammer 软件进行下载

要进入ISP烧录模式需要将BOOT引脚上拉后再通电

  • 然后一般头文件包含这3个,后面用到库再包含对应库头文件
cpp
#include "base_types.h"
#include "cw32f030.h"
#include "system_cw32f030.h"
  • 搞好好直接vscode进行写代码烧写即可
  • 最终的固定代码
system_init.h
cpp
#ifndef __SYSTEM_INIT_H
#define __SYSTEM_INIT_H
#include "main.h"

typedef struct
{
    void (*Hardware_Init)(void);    // 硬件初始化
}System_Init_t;


extern System_Init_t System_Init;

#endif
system_init.c
cpp
/***************************************************************************
 * File: system_init.c
 * Author: Luckys.
 * Date: 2023/06/11
 * description: 系统初始化
****************************************************************************/

#include "main.h"

/*====================================variable definition declaration area BEGIN===================================*/

uint8_t Init_Cnt = 10;  // 初始化超时等待计数

/*====================================variable definition declaration area   END===================================*/

/*====================================static function declaration area BEGIN====================================*/

static void Hardware_Init(void);    // 硬件初始化
static void RCC_Config(void);   // 时钟配置
/*====================================static function declaration area   END====================================*/



System_Init_t System_Init = 
{
    Hardware_Init,
};

/*
* @function: Hardware_Init
* @param: None
* @retval: None
* @brief: 硬件初始化
*/
static void Hardware_Init(void)
{
    Public.System_MS_Delay(1000);   // DHT11上电后至少需要延时1s等待稳定
    RCC_Config();   // 时钟配置
    InitTick(64000000); // SYSTICK初始化
    Btim.BTIM1_Init(1999, BTIM_PRS_DIV32);  // 基本定时器1初始化(计算:64000000 / 2000 / 32 = 1000Hz --- 1 / 1000Hz = 0.001s)    
    Led.Led_Init(); // LED初始化
    Key_1.Key_1_Init(); // 按键初始化
    Key_2.Key_2_Init(); // 按键初始化
    Buzzer.Buzzer_Init();   // 蜂鸣器初始化
#ifdef USE_TFT 
    ST7735.ST7735_Init();   // ST7735 LCD屏幕初始化
#elif defined(USE_096_OLED)
    I2C_Soft.I2C_Init();   // I2C初始化
    OLED096.OLED096_Init(); // 0.96寸OLED初始化
#elif defined(USE_091_OLED)
    // 0.91寸OLED代码待添加
#endif
#ifdef USE_ADC_Single_One 
    ADC_1.ADC1_Single_Channel_One_Init();   // ADC单通道单次采集初始化    
#elif defined(USE_ADC_Serial_Scan) 
    ADC_1.ADC1_Serial_Scan_Init();  // ADC序列扫描初始化
#endif     
    USART1.USART1_Init();   // 串口1初始化
    myRTC.myRTC_Init(2023, RTC_Month_June, 16, RTC_Weekday_Friday, 18, 50, 20); // RTC初始化  
    myRTC.myRTC_Alarm_A_Init(); // 闹钟初始化
    while (!DHT11.DHT11_Init() && (Init_Cnt--)) // DHT11初始化
    {
        Public.System_MS_Delay(100);
    }
    DHT11.DHT11_Read_Data(&DHT11.DHT11_Temperture,&DHT11.DHT11_Humidity); // 获取一次温湿度
    Gtim.Gtim1_PWM_Output_Init(399, GTIM_PRESCALER_DIV64);    // PWM输出初始化 64000000 / 64 / 400 = 2500Hz (即2.5ms) 
#ifdef USE_PWM_IC 
    Gtim.Gtim2_PWM_IC_Init();   // 输入捕获初始化
#endif
#ifdef USE_GTIM3_TOGG
    Gtim.Gtim3_PWM_Toggle_Init(399, GTIM_PRESCALER_DIV64);   // 互补初始化 64000000 / 1024 / 12500 = 5hz (即200ms)  
#endif    
    // Atim.Atim_Base_Init(1999, ATIM_Prescaler_DIV32);    // 高级定时器1初始化(计算:64000000 / 2000 / 32 = 1000Hz --- 1 / 1000Hz = 0.001s) 
#ifdef USE_PWM_IC_ATIM   
    Atim.Atim_PWM_Input_Init();
#endif
#ifdef USE_ATIM_PWM       
    Atim.Atim_PWM_Output_OC_Init();
#endif    
    printf("初始化完成\r\n");
}

/*
* @function: RCC_Config
* @param: None
* @retval: None
* @brief: 时钟配置
*/
static void RCC_Config(void)
{
    /* 0. HSI使能并校准 */
    RCC_HSI_Enable(RCC_HSIOSC_DIV6);

    /* 1. 设置HCLK和PCLK的分频系数 */
    RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
    RCC_PCLKPRS_Config(RCC_PCLK_DIV1);

    /* 2. 使能PLL,通过PLL倍频到64MHz */
    RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8); // HSI 默认输出频率8MHz
    // RCC_PLL_OUT();  //PC13脚输出PLL时钟

    ///< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle
    ///< 当使用的时钟源HCLK大于48MHz:设置FLASH 读等待周期为3 cycle
    __RCC_FLASH_CLK_ENABLE();
    FLASH_SetLatency(FLASH_Latency_3);

    /* 3. 时钟切换到PLL */
    RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
    RCC_SystemCoreClockUpdate(64000000);
}
system.h
cpp
#ifndef __SYSTEM_H
#define __SYSTEM_H
#include "main.h"

typedef struct
{
    void (*System_Run)(void);  // 系统运行
    void (*Error_Handler)(void);    // 系统错误处理
    void (*Task_Marks_Handler)(void);   // 任务标记
}System_t;


extern System_t System;
#endif
system.c
cpp
/***************************************************************************
 * File: system.c
 * Author: Luckys.
 * Date: 2023/06/11
 * description: 系统函数
****************************************************************************/

#include "main.h"


/*====================================static function declaration area BEGIN====================================*/
static void System_Run(void);  // 系统运行
static void Error_Handler(void);    // 系统错误处理
static void Task_Marks_Handler(void);   // 任务标记函数
static void Task_Pro_Handler(void); // 任务处理函数

/*====================================static function declaration area   END====================================*/

System_t System = 
{
    System_Run,
    Error_Handler,
    Task_Marks_Handler,
};

/*
* @function: System_Run
* @param: None
* @retval: None
* @brief: 系统运行
*/
static void System_Run(void)
{
    Task_Pro_Handler();
}

/*
* @function: Error_Handler
* @param: None
* @retval: None
* @brief: 系统错误处理
*/
static void Error_Handler(void)
{
    Buzzer.Buzzer_ON();
}

/*
* @function: Task_Marks_Handler
* @param: None
* @retval: None
* @brief: 任务标记函数
*/
static void Task_Marks_Handler(void)
{
    uint8_t i;

    for (i = 0; i < ucTasks_Max; i++)
    {
        if (Task[i].Task_Cnt)   // 判断计数是否为0
        {
            Task[i].Task_Cnt--; // 递减
            if (0 == Task[i].Task_Cnt)  // 计数到0
            {
                Task[i].Task_Cnt = Task[i].Task_Timer;  // 重装载计数
                Task[i].Run_Status = TRUE;  // 任务执行状态标志置1
            }
        }
    }
}

/*
* @function: Task_Pro_Handler
* @param: None
* @retval: None
* @brief: 任务处理函数
*/
static void Task_Pro_Handler(void)
{
    uint8_t i;

    for (i = 0; i < ucTasks_Max; i++)
    {
        if (Task[i].Run_Status) // 判断执行状态:TRUE--执行 FALSE--不执行
        {
            Task[i].Run_Status = FALSE;
            Task[i].Task_Hook();    // 执行函数
        }
    }
}
public.h
cpp
#ifndef __PUBLIC_H
#define __PUBLIC_H
#include "main.h"

// Debug Port(Default serial port 1)
#define UART_DEBUG  CW_UART1

/***********************全局宏预编译 BEGIN***********************/

// 选择屏幕(只能3选1)
// #define USE_TFT 
#define USE_096_OLED
// #define USE_091_OLED

// ADC选择模式(单通道单次/序列扫描)
// #define USE_ADC_Single_One
#define USE_ADC_Serial_Scan

// RTC中断选择打开
// #define USE_RTC_Interrupt

// 输入捕获有问题,暂时屏蔽
// #define USE_PWM_IC
// #define USE_PWM_IC_ATIM

// GTIM3互补输出
// #define USE_GTIM3_TOGG

// ATIM输出比较
// #define USE_ATIM_PWM

/***********************全局宏预编译   END***********************/


// 取消 FALSE 和 TRUE 宏定义(否则下面枚举报错!)
#undef FALSE
#undef TRUE

// BIT
typedef enum
{
    BIT0 = (uint8_t)(0x01 << 0),    // 0x01 -- 0000 0001
    BIT1 = (uint8_t)(0x01 << 1),    // 0x02 -- 0000 0010
    BIT2 = (uint8_t)(0x01 << 2),    // 0x04 -- 0000 0100    
    BIT3 = (uint8_t)(0x01 << 3),    // 0x08 -- 0000 1000
    BIT4 = (uint8_t)(0x01 << 4),    // 0x10 -- 0001 0000
    BIT5 = (uint8_t)(0x01 << 5),    // 0x20 -- 0010 0000
    BIT6 = (uint8_t)(0x01 << 6),    // 0x40 -- 0100 0000
    BIT7 = (uint8_t)(0x01 << 7),    // 0x80 -- 1000 0000
}BIT_t;

// TRUE/FALSE
typedef enum
{
    FALSE = 0U,
    TRUE = !FALSE 
}FLagStatus_t;

// 超时时间
typedef enum
{
    UART_TX_TimerOut = (uint8_t)100,    // 串口发送单字节等待最大时间(ms)
}TIMER_OUT_t;

typedef struct
{
    void (*System_10US_Delay)(uint32_t);    // 系统延时10*xus
    void (*System_MS_Delay)(uint32_t);  // 系统ms延时
    void (*Memory_Clear)(uint8_t*, uint16_t);   // 内存清除
}Public_t;

extern Public_t Public;

#endif
public.c
cpp
/***************************************************************************
 * File: USER
 * Author: Luckys.
 * Date: 2023/06/11
 * description: 公用部分代码
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void Memory_Clear(uint8_t*, uint16_t);    // 内存清除
static void System_MS_Delay(uint32_t);   // 系统ms延时
static void System_10US_Delay(uint32_t); // 系统延时10*xus

/*====================================static function declaration area   END====================================*/
Public_t Public = 
{
    System_10US_Delay,
    System_MS_Delay,
    Memory_Clear,
};

/*
* @function: Memory_Clear
* @param: puc_Buffer -> 要清除的内存首地址 LEN -> 内存长度(注意字串符用strlen() 数组用sizeof() !!!)
* @retval: None
* @brief: 内存清除
*/
static void Memory_Clear(uint8_t* puc_Buffer, uint16_t LEN)
{
    uint16_t i = 0;

    for (i = 0; i < LEN; i++)
    {
        *(puc_Buffer + i) = (uint8_t)0;
    }
}

/*
* @function: fputc
* @param: ch -> 要写入的字符的 ASCII 码值,应当是一个整型数 f -> 指向要写入的文件(或流)的指针
* @retval: None
* @brief: 重定向printf
*/
int fputc(int ch, FILE *f)
{
    USART_SendData_8bit(UART_DEBUG, (uint8_t)ch);   // 发送一个数据(8bit)
    // 等待发送完成,1:完成,0:还没完成
    while (USART_GetFlagStatus(UART_DEBUG, USART_FLAG_TXE) == RESET);

    return ch;
}

/*
* @function: System_MS_Delay
* @param: ms -> 需要延时的时间(ms)
* @retval: None
* @brief: 系统ms延时
*/
static void System_MS_Delay(uint32_t ms)
{
    delay1ms(ms);   // CW库的延时函数
}

/*
* @function: System_10US_Delay
* @param: us -> 需要延时的时间(us*10)
* @retval: None
* @brief: 系统10*xus延时
*/
static void System_10US_Delay(uint32_t us)
{
   delay10us(us);   // CW库的延时函数
}


callback.h
cpp
#ifndef __CALLBACK_H
#define __CALLBACK_H
#include "main.h"

// 调试用
#define CALLBACK_Debug 0

#endif
callback.c
cpp
/***************************************************************************
 * File: USER
 * Author: Luckys.
 * Date: 2023/06/11
 * description: 中断函数
****************************************************************************/
#include "main.h"

/*
* @function: UART1_IRQHandler
* @param: None
* @retval: None
* @brief: 串口1中断服务函数
*/
void UART1_IRQHandler(void)
{
    if (USART_GetITStatus(CW_UART1, USART_IT_RC) != RESET)
    {
        USART1.puc_Usart1_Rec_Buffer[USART1.ucUsart1_Rx_Cnt] = USART_ReceiveData_8bit(CW_UART1);
        if (USART1.puc_Usart1_Rec_Buffer[USART1.ucUsart1_Rx_Cnt] == 0x0A)
        {
            if (USART1.puc_Usart1_Rec_Buffer[USART1.ucUsart1_Rx_Cnt - 1] == 0x0D)   // 判断先后接收到 0x0D 0x0A 则当做一帧
            {
#if CALLBACK_Debug                
                // for (uint16_t i = 0; i < USART1.ucUsart1_Rx_Cnt - 1; i++)   // Cnt - 1去除0x0D
                // {
                //     *(USART1.puc_Usart1_Send_Buffer + i) = *(USART1.puc_Usart1_Rec_Buffer + i);
                // }
                // // 发送数据
                // USART1.USART1_Send_Array(USART1.puc_Usart1_Send_Buffer, USART1.ucUsart1_Rx_Cnt - 1);    // Cnt - 1去除0x0D
                // printf("\r\n");
#endif                
                // ModBus协议解析
                Modbus.Protocol_Analysis(&USART1);
            }
            else
            {
                USART1.ucUsart1_Rx_Cnt++;
            }
        }
        else
        {
            USART1.ucUsart1_Rx_Cnt++;
        }
        USART_ClearITPendingBit(CW_UART1, USART_IT_RC);
    }
}

/*
* @function: BTIM1_IRQHandler
* @param: None
* @retval: None
* @brief: BTIM1中断服务函数
*/
void BTIM1_IRQHandler(void)
{
    static uint16_t count_100ms = 0;
    if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))			//检查BTIM的状态寄存器的状态位是否置位
    {
        BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);	//清除中断标志位
        count_100ms++;

        System.Task_Marks_Handler();
        if(100 == count_100ms)
        {
            count_100ms = 0;
            Key_1.vusKey_1_Timer_Count++;
        }
    }
}

/*
* @function: GPIOA_IRQHandler
* @param: None
* @retval: None
* @brief: GPIOB外部中断服务函数
*/
void GPIOB_IRQHandler(void)
{
    if (CW_GPIOB->ISR_f.PIN2)   // 判断哪个引脚触发
    {
        GPIOB_INTFLAG_CLR(bv2); // 清除标志位
        Led.Led_Flip(LED2);
    }
}

/*
* @function: ADC_IRQHandler
* @param: None
* @retval: None
* @brief: ADC中断服务函数
*/
void ADC_IRQHandler(void)
{
    ADC_1.gFlagIrq = CW_ADC->ISR;   // 获取中断标志寄存器
    CW_ADC->ICR = 0x00; // 中断标志清除寄存器
}

/*
* @function: RTC_IRQHandler
* @param: None
* @retval: None
* @brief: RTC中断服务函数
*/
void RTC_IRQHandler(void)
{
    if (RTC_GetITState(RTC_IT_ALARMA))  // 闹钟中断触发
    {
        RTC_ClearITPendingBit(RTC_IT_ALARMA);
        Buzzer.Buzzer_ON();
    }
#ifdef USE_RTC_Interrupt    
    if (RTC_GetITState(RTC_IT_INTERVAL))    // RTC秒中断触发
    {
        RTC_ClearITPendingBit(RTC_IT_INTERVAL);
    }
#endif    
}

/*
* @function: GTIM1_IRQHandler
* @param: None
* @retval: None
* @brief: 定时器1中断函数
*/
void GTIM1_IRQHandler(void)
{
    // static uint16_t Timer_Cnt;

    GTIM_ClearITPendingBit(CW_GTIM1, GTIM_IT_OV);
    // 需要再打开
    // Timer_Cnt++;
    // if (Timer_Cnt >= 800)   // 2.5x400 = 2s
    // {
    //     Timer_Cnt = 0;
    //     Gtim.Gtim1_CH_Set_Pulse[0] += 40; // 占空比+10%
    //     if (Gtim.Gtim1_CH_Set_Pulse[0] > 360)
    //     {
    //         Gtim.Gtim1_CH_Set_Pulse[0] = 40;
    //     }
    //     // GTIM_SetCompare1(CW_GTIM1, Gtim.Gtim1_CH_Set_Pulse[3]);
    //     CW_GTIM1->CCR1 = Gtim.Gtim1_CH_Set_Pulse[3];  // 设置占空比跟上面函数作用一样
    // }
}

/*
* @function: ATIM_IRQHandler
* @param: None
* @retval: None
* @brief: 高级定时器中断服务函数
*/
void ATIM_IRQHandler(void)
{
    if (ATIM_GetITStatus(ATIM_IT_OVF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_OVF);
    }

    if (ATIM_GetITStatus(ATIM_IT_C1BF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_C1BF);
    }

    if (ATIM_GetITStatus(ATIM_IT_C1AF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_C1AF);
    }
}

#ifdef USE_PWM_IC
/*
* @function: GTIM2_IRQHandler
* @param: None
* @retval: None
* @brief: GTIM2中断服务函数
*/
void GTIM2_IRQHandler(void)
{
    static uint8_t Status = 0;   // 标志位,用于表示当前处于PWM信号的哪一阶段
    static uint32_t cnt = 0;    // 计数
    if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_OV)) // 判断是否为GTIM1计数器溢出中断
    {
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_OV); // 清除计数器溢出中断标志位
        if (Status == 1)                              // 如果当前处于PWM信号的第二阶段
        {
            cnt++;
        }
    }
    if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC1)) // 判断是否为GTIM1捕获比较匹配中断
    {
        if (Status == 0) // 如果当前处于PWM信号的第一阶段
        {
            Gtim.Gtim2_IC_Fre = CW_GTIM2->CCR1; // 获取捕获比较器1的值,即为PWM信号的周期
            Status = 1;                                     // 切换至PWM信号的第二阶段
        }
        else if (Status == 1) // 如果当前处于PWM信号的第二阶段
        {
            Gtim.Gtim2_IC_Fre = CW_GTIM2->CCR1 + cnt * 65536 - Gtim.Gtim2_IC_Fre; // 计算PWM信号的周期
            Status = 0;                                                                       // 切换至PWM信号的第一阶段
            cnt = 0;                                                                          // 计数器清零
        }
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC1); // 清除捕获比较器1匹配中断标志位
    }

    if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC2)) // 判断是否为GTIM1捕获比较匹配中断
    {
        if (Status == 1) // 如果当前处于PWM信号的第二阶段
        {
            Gtim.Gtim2_IC_Duty = CW_GTIM2->CCR2 + cnt * 65536 - Gtim.Gtim2_IC_Fre; // 计算PWM信号的占空比
        }
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC2); // 清除捕获比较器2匹配中断标志位
    }
}
#endif
task.h
cpp
#ifndef __TASK_H
#define __TASK_H
#include "main.h"

typedef struct
{
    uint8_t Run_Status; // 任务状态:TRUE/FALSE
    uint16_t Task_Cnt;  // 任务定时计数器(ms)
    uint16_t Task_Timer;    // 重载计数器(任务分配的时间ms)
    void (*Task_Hook)(void);    // 任务函数
}Task_t;

extern Task_t Task[];
extern uint8_t ucTasks_Max;

#endif
task.c
cpp
/***************************************************************************
 * File: task.c
 * Author: Luckys.
 * Date: 2023/06/13
 * description: 任务调度
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void TasksHandle_10MS(void); // 任务
static void TasksHandle_20MS(void); // 任务
static void TasksHandle_100MS(void); // 任务
static void TasksHandle_250MS(void); // 任务
static void TasksHandle_1S(void); // 任务
static void TasksHandle_1p5S(void); // 任务


/*====================================static function declaration area   END====================================*/

Task_t Task[] = 
{
    {FALSE, 10, 10, TasksHandle_10MS},  // task Period: 10ms
    {FALSE, 100, 100, TasksHandle_100MS},  // task Period: 20ms
    {FALSE, 20, 20, TasksHandle_20MS},  // task Period: 20ms
    {FALSE, 250, 250, TasksHandle_250MS},  // task Period: 250ms  
    {FALSE, 1000, 1000, TasksHandle_1S},  // task Period: 1s  
    {FALSE, 1500, 1500, TasksHandle_1p5S},  // task Period: 1.5s  
};

/*====================================variable definition declaration area BEGIN===================================*/

// 最大任务数量
uint8_t ucTasks_Max = sizeof(Task) / sizeof(Task[0]);

/*====================================variable definition declaration area   END===================================*/

/*
* @function: TasksHandle_10MS
* @param: None
* @retval: None
* @brief: 任务
*/
static void TasksHandle_10MS(void)
{
    Key_1.Key_1_Scan();
    Key_1.Key_1_Handler();
}

/*
* @function: TasksHandle_20MS
* @param: None
* @retval: None
* @brief: 任务
*/
static void TasksHandle_20MS(void)
{

#ifdef USE_096_OLED     
    switch(Menu.Now_Page_Status)
    {
        case PAGE_TempHum:Menu.Menu_Page1();break;
        case PAGE_RtcTimeDate:Menu.Menu_Page2();break;
        case PAGE_ADC:Menu.Menu_Page3();break;
        case PAGE_PWM:Menu.Menu_Page4();break;
        default:Menu.Now_Page_Status = PAGE_TempHum;break;
    }
#endif    
}

/*
* @function: TasksHandle_100MS
* @param: None
* @retval: None
* @brief: 任务
*/
static void TasksHandle_100MS(void)
{
#ifdef USE_ADC_Single_One
    ADC_1.ADC1_Single_Channel_One_Convert();    
#elif defined(USE_ADC_Serial_Scan)
    ADC_1.ADC1_Serial_Scan_Convert();
#endif
    if (Menu.Now_Page_Status == PAGE_ADC)
    {
        // OLED刷新
        // printf("A---%.1f\r\n",ADC_1.ADC_Single_Result);
        sprintf((char*)Page3.OLED096_Display_Buff[0],"B0:%.2f A4:%.2f",ADC_1.ADC_Serial_Result_Arr[0], ADC_1.ADC_Serial_Result_Arr[1]);
        OLED096.padString((char*)Page3.OLED096_Display_Buff[0],16);
        sprintf((char*)Page3.OLED096_Display_Buff[1],"A5:%.2f A6:%.2f",ADC_1.ADC_Serial_Result_Arr[2], ADC_1.ADC_Serial_Result_Arr[3]);
        OLED096.padString((char*)Page3.OLED096_Display_Buff[1],16);
    }
}

/*
* @function: TasksHandle_250MS
* @param: None
* @retval: None
* @brief: 任务
*/
static void TasksHandle_250MS(void)
{
    Led.Led_Flip(LED1);
    if (Menu.Now_Page_Status == PAGE_PWM)
    {
        Gtim.Gtim1_Calculate(); // 计算
    }  
}
/*
* @function: TasksHandle_1S
* @param: None
* @retval: None
* @brief: 任务
*/
static void TasksHandle_1S(void)
{
    Led.Led_Flip(LED2);
    if (Menu.Now_Page_Status == PAGE_RtcTimeDate)
    {    
        myRTC.myRTC_Refresh();
    }
}

/*
* @function: TasksHandle_1p5S
* @param: None
* @retval: None
* @brief: 任务
*/
static void TasksHandle_1p5S(void)
{
    if (Menu.Now_Page_Status == PAGE_TempHum)
    {    
        DHT11.DHT11_Read_Data(&DHT11.DHT11_Temperture,&DHT11.DHT11_Humidity); // 获取一次温湿度
    }
}
main.h
cpp
#ifndef __MAIN_H
#define __MAIN_H
#include "base_types.h"
#include "cw32f030.h"
#include "system_cw32f030.h"

#include "cw32f030_adc.h"	
#include "cw32f030_atim.h"	
#include "cw32f030_awt.h"	
#include "cw32f030_btim.h"	
#include "cw32f030_crc.h"	
#include "cw32f030_debug.h"	
#include "cw32f030_digitalsign.h"	
#include "cw32f030_dma.h"	
#include "cw32f030_flash.h"	
#include "cw32f030_gpio.h"	
#include "cw32f030_gtim.h"	
#include "cw32f030_i2c.h"
#include "cw32f030_iwdt.h"	
#include "cw32f030_lvd.h"	
#include "cw32f030_pwr.h"	
#include "cw32f030_ram.h"	
#include "cw32f030_rcc.h"
#include "cw32f030_rtc.h"	
#include "cw32f030_spi.h"	
#include "cw32f030_systick.h"	
#include "cw32f030_uart.h"	
#include "cw32f030_wwdt.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include "task.h"
#include "system_init.h"
#include "system.h"
#include "callback.h"
#include "public.h"
#include "led.h"
#include "key_1.h"
#include "key_2.h"
#include "buzzer.h"
#include "usart1.h"
#include "crc_16.h"
#include "modbus.h"
#include "btim.h"
#include "gtim.h"
#include "atim.h"
#include "tft_st7735.h"
#include "i2c.h"
#include "oled_096.h"
#include "adc1.h"
#include "rtc.h"
#include "menu.h"
#include "dht11.h"

#endif
main.c
cpp
/***************************************************************************
 * File: main.c
 * Author: Luckys.
 * Date: 2023/06/11
 * description: CW32030C8T6大学板
****************************************************************************/
#include "main.h"

int main(void)
{
  System_Init.Hardware_Init();
  
  while (1)
  {
    System.System_Run();
  }
}

板子资源

  • 资源外设

48PIN MCU: CW32F030C8T6位微控制器, 64M 主频 ,LQFP48封装

4针SWD下载仿真接口;

一个DC口,开发板可 12V 电源接口;

5V、GND、3.3V电源,通过 2.54mm 的单排针孔位引出;

所有GPIO通过 2.54mm(100mil) 间距双排针孔位引出;

一个系统复位按键

电源LED指示灯;

3个用户按键;

3个LED灯

一个1.77TFT显示屏;

一个有源蜂鸣器;

一路电位器;

蓝牙接口;

WIFI接口;

矩阵键盘接口;

电子秤接口;

MPU6050接口;

DHT11温湿度接口;

数据手册阅读

  • 内核:ARM® Cortex®-M0+,最高主频 64MHz
  • 工作电压:1.65V 至 5.5V
  • 最大 64K 字节 FLASH,最大 8K 字节 RAM,128 字节 OTP 存储器
  • 一组高级控制PWM 定时器,四组 16 位通用定时器,三组 16 位基本定时器,窗口看门狗定时器,独立看门狗定时器
  • 三路低功耗 UART
  • 两路 SPI 接口 12Mbit/s
  • 两路 I2C 接口 1Mbit/s
  • 12 位ADC 一个
  • 80 位唯一ID
  • 框图

  • 时钟树

HSIOSC 时钟频率固定为48MHz,频率精度低于HSE 时钟

系统上电复位完成后默认选择HSI 作为SysClk 的时钟源,时钟频率默认值是8MHz

所以我们可以HSIOSC进行6分频变成 8MHz,然后通过PLL进行8倍频变成 64MHz,这样系统时钟就设置为最大了,然后SYSCLK进行1分频,HCLK频率等于系统时钟64MHz

cpp
static void RCC_Config(void)
{
    /* 0. HSI使能并校准 */
    RCC_HSI_Enable(RCC_HSIOSC_DIV6);

    /* 1. 设置HCLK和PCLK的分频系数 */
    RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
    RCC_PCLKPRS_Config(RCC_PCLK_DIV1);

    /* 2. 使能PLL,通过PLL倍频到64MHz */
    RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8); // HSI 默认输出频率8MHz
    // RCC_PLL_OUT();  //PC13脚输出PLL时钟

    ///< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle
    ///< 当使用的时钟源HCLK大于48MHz:设置FLASH 读等待周期为3 cycle
    __RCC_FLASH_CLK_ENABLE();
    FLASH_SetLatency(FLASH_Latency_3);

    /* 3. 时钟切换到PLL */
    RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
    RCC_SystemCoreClockUpdate(64000000);
}

手册问题

cw32f030_gpio.h

库函数跟STM32F103固件库一模一样的用法,那些函数都是同样的

但是多了一些简化操作(以PA引脚为例,其他的一样操作),x用数字代替!

cpp
// 设置PAx引脚为高电平
PA0x_SETHIGH();

// 设置PAx引脚为低电平
PA0x_SETLOW();

// 翻转PAx引脚状态
PA0x_TOG();

// 复用PA14为I2C
PA14_AFx_GPIO();
PA14_AFx_I2C1SCL();

// 设置为推挽输出或者开漏
PA0x_PUSHPULL_ENABLE();
PA0x_OPENDRAIN_ENABLE();

// 设置为输入输出
PA0x_DIR_OUTPUT();
PA0x_DIR_INPUT();

// 设置为模拟输入
PA0x_ANALOG_ENABLE();
// 设置为数字
PA0x_DIGTAL_ENABLE();
  • 复用的话查手册GPIO那
  • 端口复位状态

上电或复位后, SWCLK(PA14)SWDIO (PA13) 默认为数字上拉, BOOT (PF3) 默认为数字功能。其他端口默认为模拟高阻输入,上拉或下拉均默认不打开。

  • 使能时钟的函数可以是这样
cpp
RCC_APBPeriphClk_Enable2(RCC_AHB_PERIPH_GPIOA,ENABLE);	// 跟STM32一样

// 另一种是直接点(CW特有的)
__RCC_GPIOA_CLK_ENABLE();
  • 中断通道号可以在 cw32f030.h 里找到,初始化可能用得到(如果形参类型是 IRQn_Type IRQn 就是通道号)

SYStick

cw32f030_systick.c

滴答定时器,默认1ms,需要用户启动

cpp
InitTick(64000000);	// 参数是HCLK时钟频率

常用函数:

cpp
// ms延时
SysTickDelay(uint32_t Delay)
// 获取当前计数值
uint32_t GetTick(void)    

中断服务函数

cpp
void SysTick_Handler(void)
{
    /* USER CODE BEGIN SysTick_IRQn */
    uwTick += uwTickFreq;
    /* USER CODE END SysTick_IRQn */
}

滴答定时器准确度的话应该不错的用示波器测量过,延时100ms翻转一次电平,测量的引脚频率是差不多5Hz也就是周期200ms

system_cw32f030.c 里一些系统函数,比如 delay1ms,delay10us

LED

  • 硬件连接

  • 程序编写
system_init.c
cpp
/*
* @function: Hardware_Init
* @param: None
* @retval: None
* @brief: 硬件初始化
*/
static void Hardware_Init(void)
{
    RCC_Config();   // 时钟配置
    InitTick(64000000); // SYSTICK初始化
    Led.Led_Init(); // LED初始化
}
led.h
cpp
#ifndef __LED_H
#define __LED_H
#include "main.h"

// 管脚 LED1--PA7 LED2--PA8 LED--PC13
#define Led1_Pin    GPIO_PIN_7
#define Led2_Pin    GPIO_PIN_8
#define Led3_Pin    GPIO_PIN_13

// 定义枚举类型
typedef enum
{
    LED1 = (uint8_t)0x01,
    LED2 = (uint8_t)0x02,
    LED3 = (uint8_t)0x03,
} Led_Num_t;

typedef struct
{
    void (*Led_Init)(void); // LED初始化
    void (*Led_ON)(Led_Num_t);   // 打开
    void (*Led_OFF)(Led_Num_t);  // 关闭
    void (*Led_Flip)(Led_Num_t); // 翻转
} Led_t;

extern Led_t Led;

#endif
led.c
cpp
/***************************************************************************
 * File: led.c
 * Author: Luckys.
 * Date: 2023/06/11
 * description: LED
****************************************************************************/

#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void Led_Init(void);      // LED初始化
static void Led_ON(Led_Num_t);   // 打开
static void Led_OFF(Led_Num_t);  // 关闭
static void Led_Flip(Led_Num_t); // 翻转

/*====================================static function declaration area   END====================================*/

Led_t Led = 
{
    Led_Init,
    Led_ON,
    Led_OFF,
    Led_Flip,
};

/*
* @function: Led_Init
* @param: None
* @retval: None
* @brief: LED初始化
*/
static void Led_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // 使能时钟
    __RCC_GPIOC_CLK_ENABLE();
    __RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStructure.IT = GPIO_IT_NONE; // 管脚中断模式--无
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;  // 模式
    GPIO_InitStructure.Pins = Led1_Pin | Led2_Pin;  
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = Led3_Pin;
    GPIO_Init(CW_GPIOC, &GPIO_InitStructure);

    GPIO_WritePin(CW_GPIOA, Led1_Pin | Led2_Pin, GPIO_Pin_SET); // 默认灭
    GPIO_WritePin(CW_GPIOC, Led3_Pin, GPIO_Pin_SET);
}

/*
* @function: Led_ON
* @param: LEDx -> 1,2,3
* @retval: None
* @brief: LED打开
*/
static void Led_ON(Led_Num_t LEDx)
{
    switch(LEDx)
    {
        case LED1:
        {
            GPIO_WritePin(CW_GPIOA, Led1_Pin, GPIO_Pin_RESET);
            break;
        }
        case LED2:
        {
            GPIO_WritePin(CW_GPIOA, Led2_Pin, GPIO_Pin_RESET);
            break;
        }
        case LED3:
        {
            GPIO_WritePin(CW_GPIOC, Led3_Pin, GPIO_Pin_RESET);
            break;
        }
        default:
        {
            GPIO_WritePin(CW_GPIOA, Led1_Pin, GPIO_Pin_RESET);
            break;
        }
    }
}

/*
* @function: Led_OFF
* @param: LEDx -> 1,2,3
* @retval: None
* @brief: LED关闭
*/
static void Led_OFF(Led_Num_t LEDx)
{
    switch(LEDx)
    {
        case LED1:
        {
            GPIO_WritePin(CW_GPIOA, Led1_Pin, GPIO_Pin_SET);
            break;
        }
        case LED2:
        {
            GPIO_WritePin(CW_GPIOA, Led2_Pin, GPIO_Pin_SET);
            break;
        }
        case LED3:
        {
            GPIO_WritePin(CW_GPIOC, Led3_Pin, GPIO_Pin_SET);
            break;
        }
        default:
        {
            GPIO_WritePin(CW_GPIOA, Led1_Pin, GPIO_Pin_SET);
            break;
        }
    }    
}

/*
* @function: Led_Flip
* @param: LEDx -> 1,2,3
* @retval: None
* @brief: LED初始化
*/
static void Led_Flip(Led_Num_t LEDx)
{
    switch(LEDx)
    {
        case LED1:
        {
            GPIO_TogglePin(CW_GPIOA, Led1_Pin);
            break;
        }
        case LED2:
        {
            GPIO_TogglePin(CW_GPIOA, Led2_Pin);
            break;
        }
        case LED3:
        {
            GPIO_TogglePin(CW_GPIOC, Led3_Pin);
            break;
        }
        default:
        {
            GPIO_TogglePin(CW_GPIOA, Led1_Pin);
            break;
        }
    }    
}

KEY

底板按键

  • 硬件连接

  • 程序编写
key1.h
cpp
#ifndef __KEY_1_H
#define __KEY_1_H
#include "main.h"

// 管脚 K1--PB13 K2--PB14 K3--PB15
#define Key1_Pin    GPIO_PIN_13
#define Key2_Pin    GPIO_PIN_14
#define Key3_Pin    GPIO_PIN_15

// 读取按键电平
#define READ_KEY1    GPIO_ReadPin(CW_GPIOB,GPIO_PIN_13)
#define READ_KEY2    GPIO_ReadPin(CW_GPIOB,GPIO_PIN_14)
#define READ_KEY3    GPIO_ReadPin(CW_GPIOB,GPIO_PIN_15)

typedef enum
{
    KEY_NULL  = (uint8_t)0x00,  // 无按键按下键值
    KEY1_DOWN = (uint8_t)0x01,  // 按键1按下键值  
    KEY2_DOWN = (uint8_t)0x02,  // 按键2按下键值
    KEY3_DOWN = (uint8_t)0x03,  // 按键3按下键值
}Key_1_Status_t;

typedef struct
{
    uint16_t volatile vusKey_1_Timer_Count; // 长按计数
    uint8_t volatile vucKey_1_Flag_Arr[6];  // 按键标志位(短长按)
    void (*Key_1_Init)(void);   // 按键初始化
    void (*Key_1_Scan)(void);   // 按键三行消抖---按键扫描
    void (*Key_1_Handler)(void);    // 按键处理
}Key_1_t;

extern Key_1_t Key_1;


#endif
key1.c
cpp
/***************************************************************************
 * File: key_1.c
 * Author: Luckys.
 * Date: 2023/06/11
 * description: 底板独立按键(低电平有效)
****************************************************************************/

#include "main.h"

/*====================================variable definition declaration area BEGIN===================================*/
// 按键键值、抬起一瞬间、按下一瞬间
static uint8_t ucKey_1_Value,ucKey_1_Up,ucKey_1_Down;

/*====================================variable definition declaration area   END===================================*/

/*====================================static function declaration area BEGIN====================================*/

static void Key_1_Init(void);    // 按键初始化
static uint8_t Key_1_Return_Value(void); // 返回键值
static void Key_1_Scan(void);   // 按键三行消抖---按键扫描
static void Key_1_Handler(void);    // 按键处理
/*====================================static function declaration area   END====================================*/

Key_1_t Key_1 = 
{
    0,
    {FALSE},
    Key_1_Init,
    Key_1_Scan,
    Key_1_Handler,
};

/*
* @function: Key_1_Init
* @param: None
* @retval: None
* @brief: 按键初始化
*/
static void Key_1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // 使能时钟
    __RCC_GPIOB_CLK_ENABLE();

    GPIO_InitStructure.IT = GPIO_IT_NONE;          // 管脚中断模式--无
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP; // 模式--上拉输入
    GPIO_InitStructure.Pins = Key1_Pin | Key2_Pin | Key3_Pin;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
}

/*
* @function: Key_1_Return_Value
* @param: None
* @retval: None
* @brief: 返回键值
*/
static uint8_t Key_1_Return_Value(void)
{
    if ((!READ_KEY1) || (!READ_KEY2) || (!READ_KEY3))
    {
        if (!READ_KEY1)
        {
            return KEY1_DOWN;
        }
        else if (!READ_KEY2)
        {
            return KEY2_DOWN;
        }
        else if (!READ_KEY3)
        {
            return KEY3_DOWN;
        }                
    }

    return KEY_NULL;
}

/*
* @function: Key_1_Scan
* @param: None
* @retval: None
* @brief: 按键三行消抖---按键扫描
*/
static void Key_1_Scan(void)
{
    static uint8_t uckey_1_old;

    ucKey_1_Value = Key_1_Return_Value();                      // 读取按键的键值
    ucKey_1_Up = ~ucKey_1_Value & (uckey_1_old ^ ucKey_1_Value); // 按键的上升沿检测 只在按键抬起的瞬间有效 其他时刻都为零无效
    ucKey_1_Down = ucKey_1_Value & (uckey_1_old ^ ucKey_1_Value); // 按键的下降沿检测 只在按键按下的瞬间有效 其他时刻都为零无效
    uckey_1_old = ucKey_1_Value;                          // 记录上一次按键按下后的键值

    if (ucKey_1_Down) // 当有按键按下时
    {
        Key_1.vusKey_1_Timer_Count = 0; // 将计时器清零 从零开始计时 此处使用了基础定时器用于计时
    }

    if (Key_1.vusKey_1_Timer_Count < 10) // 如果计时时间小于1s 短按
    {
        switch (ucKey_1_Up) // 判断按键是否抬起 选择键值执行短按的相应程序
        {
            case KEY1_DOWN:Key_1.vucKey_1_Flag_Arr[0] = TRUE;break;
            case KEY2_DOWN:Key_1.vucKey_1_Flag_Arr[1] = TRUE;break;
            case KEY3_DOWN:Key_1.vucKey_1_Flag_Arr[2] = TRUE;break;
            default:break;
        }
    }
    else // 长按 计时时间超过1s
    {
        switch (ucKey_1_Value) // 判断按键是否抬起 选择键值执行短按的相应程序
        {
            case KEY1_DOWN:Key_1.vucKey_1_Flag_Arr[3] = TRUE;break;
            case KEY2_DOWN:Key_1.vucKey_1_Flag_Arr[4] = TRUE;break;
            case KEY3_DOWN:Key_1.vucKey_1_Flag_Arr[5] = TRUE;break;
            default:break;
        }
    }
}

/*
* @function: Key_1_Handler
* @param: None
* @retval: None
* @brief: 按键处理
*/
static void Key_1_Handler(void)
{
    if (Key_1.vucKey_1_Flag_Arr[0]) // K1短按
    {
        if (Buzzer.Buzzer_Status == Buzzer_Status_OFF)
        {
            Buzzer.Buzzer_ON();
        }
        else
        {
            Buzzer.Buzzer_OFF();
        }
        Key_1.vucKey_1_Flag_Arr[0] = FALSE;
    }
    else if (Key_1.vucKey_1_Flag_Arr[1])    // K2短按
    {
        // 切换页面
        Menu.Now_Page_Status = (Menu.Now_Page_Status % PAGE_MAX) + 1;
        OLED096.OLED096_Clear();
        Key_1.vucKey_1_Flag_Arr[1] = FALSE;
    }
    else if (Key_1.vucKey_1_Flag_Arr[2])    //K3短按
    {
        Key_1.vucKey_1_Flag_Arr[2] = FALSE;
    }
    else if (Key_1.vucKey_1_Flag_Arr[3])    // K1长按
    {
        Gtim.Gtim1_CH_Set_Pulse[0] += 20;   // 占空比加5%
        Gtim.Gtim1_CH_Set_Pulse[1] += 20;   // 占空比加5%

        if (Gtim.Gtim1_CH_Set_Pulse[0] > 380)
        {
            Gtim.Gtim1_CH_Set_Pulse[0] = 20;    // 占空比恢复到5%
        }
        if (Gtim.Gtim1_CH_Set_Pulse[1] > 380)
        {
            Gtim.Gtim1_CH_Set_Pulse[1] = 40;    // 占空比恢复到10%
        }
        // 更新占空比
        CW_GTIM1->CCR1 = Gtim.Gtim1_CH_Set_Pulse[0];
        CW_GTIM1->CCR2 = Gtim.Gtim1_CH_Set_Pulse[1];
        Key_1.vucKey_1_Flag_Arr[3] = FALSE;
    }
    else if (Key_1.vucKey_1_Flag_Arr[4])    // K2长按
    {
        Key_1.vucKey_1_Flag_Arr[4] = FALSE;
    }    
}

最小系统板按键+外部中断

  • 硬件连接

  • 程序编写
key_2.h
cpp
#ifndef __KEY_2_H
#define __KEY_2_H
#include "main.h"

// 管脚 SW1--PB2
#define SW1_Pin    GPIO_PIN_2

// 读取按键电平
#define READ_SW1    GPIO_ReadPin(CW_GPIOB,GPIO_PIN_2)


typedef struct
{
    void (*Key_2_Init)(void);   // 按键初始化
}Key_2_t;

extern Key_2_t Key_2;


#endif
key_2.c
cpp
/***************************************************************************
 * File: key_2.c
 * Author: Luckys.
 * Date: 2023/06/13
 * description: 最小系统板的按键---外部中断方式(按键高电平有效)
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void Key_2_Init(void);   // 按键初始化

/*====================================static function declaration area   END====================================*/


Key_2_t Key_2 = 
{
    Key_2_Init,
};

/*
* @function: Key_2_Init
* @param: None
* @retval: None
* @brief: 按键初始化
*/
static void Key_2_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // 使能时钟
    __RCC_GPIOB_CLK_ENABLE();

    GPIO_InitStructure.IT = GPIO_IT_RISING;          // 管脚中断模式--上升沿触发
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLDOWN; // 模式--下拉输入
    GPIO_InitStructure.Pins = SW1_Pin;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure); 

    GPIOB_INTFLAG_CLR(bv2);  // 清除PB2中断标志
    NVIC_EnableIRQ(GPIOB_IRQn); // 使能NVIC 
}

编写中断

蜂鸣器

  • 硬件连接

  • 程序编写
buzzer.h
cpp
#ifndef __BUZZER_H
#define __BUZZER_H
#include "main.h"

// 管家定义 buzzer--PB3
#define Buzzer_Pin GPIO_PIN_3

typedef enum
{
    Buzzer_Status_ON = (uint8_t)0x01,   // 蜂鸣器打开中态
    Buzzer_Status_OFF = (uint8_t)0x00,  // 蜂鸣器关闭态
}Buzzer_Status_t;

typedef struct
{
    Buzzer_Status_t Buzzer_Status;    // 蜂鸣器状态
    void (*Buzzer_Init)(void);  // 蜂鸣器初始化
    void (*Buzzer_ON)(void);  // 蜂鸣器打开
    void (*Buzzer_OFF)(void);  // 蜂鸣器关闭
}Buzzer_t;

extern Buzzer_t Buzzer;

#endif
buzzer.c
cpp
/***************************************************************************
 * File: buzzer.c
 * Author: Luckys.
 * Date: 2023/06/12
 * description: 蜂鸣器
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void Buzzer_Init(void);  // 蜂鸣器初始化
static void Buzzer_ON(void);    // 蜂鸣器打开
static void Buzzer_OFF(void);   // 蜂鸣器关闭

/*====================================static function declaration area   END====================================*/
Buzzer_t Buzzer = 
{
    Buzzer_Status_OFF,
    Buzzer_Init,
    Buzzer_ON,
    Buzzer_OFF,
};

/*
* @function: Buzzer_Init
* @param: None
* @retval: None
* @brief: 蜂鸣器初始化
*/
static void Buzzer_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // 使能时钟
    __RCC_GPIOB_CLK_ENABLE();

    GPIO_InitStructure.IT = GPIO_IT_NONE;          // 管脚中断模式--无
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 模式--推挽输出
    GPIO_InitStructure.Pins = Buzzer_Pin;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);

    GPIO_WritePin(CW_GPIOB,Buzzer_Pin,GPIO_Pin_RESET); // 默认蜂鸣器关闭
}

/*
* @function: Buzzer_ON
* @param: None
* @retval: None
* @brief: 蜂鸣器打开
*/
static void Buzzer_ON(void)
{
    GPIO_WritePin(CW_GPIOB,Buzzer_Pin,GPIO_Pin_SET);
    Buzzer.Buzzer_Status = Buzzer_Status_ON;
}

/*
* @function: Buzzer_OFF
* @param: None
* @retval: None
* @brief: 蜂鸣器关闭
*/
static void Buzzer_OFF(void)
{
    GPIO_WritePin(CW_GPIOB,Buzzer_Pin,GPIO_Pin_RESET);
    Buzzer.Buzzer_Status = Buzzer_Status_OFF;
}

串口

有三个串口分别是 USART1,USART2,USART3

这里我们使用连接link下载器的那个串口来进行实验—USART1_TX --- PB8USART1_RX --- PB9,因为PA13,PA14被SW占用了,试了那个没效果

接线方式:

单片机PB8 ---- Wch-link RXD

单片机PB9 ---- Wch-link TXD

然后这个单片机它没有空闲中断,所以只能普通中断进行接收

  • 程序编写

普通发送+中断接收+模拟Modbus协议

串口发送的话需要注意发送一个字节需要判断等待发送完成再发,不然可能每次只发送成功2个字节后面的就丢了

试了添加了超时机制,发送后等待一段时间等待发送完成标志位置1(UART_TX_TimerOut是枚举值是100)

cpp
uint32_t TimerOut = GetTick() + UART_TX_TimerOut;   // 获取当前计数值+串口超时时间(ms)

while ((USART_GetFlagStatus(UART_DEBUG, USART_FLAG_TXE) == RESET) && (TimerOut--))
{
    if (0 == TimerOut)
    {
        System.Error_Handler();	// 进入错误处理
        return;
    }
}
usart1.h
cpp
#ifndef __USART1_H
#define __USART1_H
#include "main.h"

// 串口发送长度,接收长度
#define USART1_Send_LENGTH 20
#define USART1_Rec_LENGTH  100

// 串口1引脚 PB9(RX) PB8(TX)
#define Usart1_Tx_Pin GPIO_PIN_8
#define Usart1_Rx_Pin GPIO_PIN_9

typedef struct
{
    uint8_t ucUsart1_Rx_Cnt;   // 接收长度计数
    uint8_t* puc_Usart1_Send_Buffer;    // 发送缓存指针
    uint8_t* puc_Usart1_Rec_Buffer; // 接收缓存指针
    void (*USART1_Init)(void);   // 串口1初始化
    void (*USART1_Send_Array)(uint8_t*, uint16_t);  // 发送数组
    void (*USART1_Send_String)(uint8_t*);   // 发送字符串
}USART1_t;

extern USART1_t USART1;


#endif
usart1.c
cpp
/***************************************************************************
 * File: usart1.c
 * Author: Luckys.
 * Date: 2023/06/12
 * description: USART1
 -----------------------------------
USART1:TX--PB8 RX--PB9
接线:
    单片机PB8 ---- Wch-link RXD
    单片机PB9 ---- Wch-link TXD
 -----------------------------------
****************************************************************************/
#include "main.h"

/*====================================variable definition declaration area BEGIN===================================*/

static uint8_t Usart1_Send_Bufferp[USART1_Send_LENGTH] = {0x00}; // 发送数组
static uint8_t Usaer1_Rec_Buffer[USART1_Rec_LENGTH] = {0x00};    // 接收数据

/*====================================variable definition declaration area   END===================================*/

/*====================================static function declaration area BEGIN====================================*/

static void USART1_Init(void);  // 串口1初始化
static void USART1_Send_Array(uint8_t*, uint16_t);  // 发送数组
static void USART1_Send_String(uint8_t*);   // 发送字符串

/*====================================static function declaration area   END====================================*/

USART1_t USART1 = 
{
    0,
    Usart1_Send_Bufferp,
    Usaer1_Rec_Buffer,
    USART1_Init,
    USART1_Send_Array,
    USART1_Send_String,
};

/*
* @function: USART1_Init
* @param: None
* @retval: None
* @brief: 串口1初始化
*/
static void USART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;	//定义结构体
    USART_InitTypeDef USART_InitStructure;	//定义结构体

    __RCC_GPIOB_CLK_ENABLE();   // 使能引脚时钟
    __RCC_UART1_CLK_ENABLE();   // 使能串口时钟
    // 打开复用
    PB08_AFx_UART1TXD();
    PB09_AFx_UART1RXD();
    /*端口初始化*/
    GPIO_InitStructure.IT = GPIO_IT_NONE; // 管脚中断模式--无
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;  // 模式--推挽输出
    GPIO_InitStructure.Pins = Usart1_Tx_Pin;  
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure); 

    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;  // 模式--浮空输入
    GPIO_InitStructure.Pins = Usart1_Rx_Pin;   
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);  

    /*串口初始化*/
    CW_UART1->CR1_f.SYNC = 0;   // 0-异步全双工通信模式; 1-同步半双工通信模式
    CW_UART1->ICR = 0x00;   // 清除所有串口中断标志
    USART_InitStructure.USART_BaudRate = 9600;  // 波特率
    USART_InitStructure.USART_Over = USART_Over_16;	// 采样方式---16倍采样
    USART_InitStructure.USART_StopBits = USART_StopBits_1;	//停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;	//无奇偶校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//禁止硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式  
    // 下面3条是CW特有的    
    USART_InitStructure.USART_UclkFreq = 64000000;  // 传输时钟UCLK频率 Hz
    USART_InitStructure.USART_StartBit = USART_StartBit_FE; // 起始位判定方式---下降沿(低功耗则选择低电平)
    USART_InitStructure.USART_Source = USART_Source_PCLK;   // 传输时钟源UCLK---PCLK(看时钟树)
    USART_Init(CW_UART1,&USART_InitStructure);  // 串口初始化
    
    /*NVIC初始化(不需要定义结构体)*/
    NVIC_SetPriority(UART1_IRQn,0); // 优先级,无优先级分组
    NVIC_EnableIRQ(UART1_IRQn); // 使能中断---中断通道号

    USART_ITConfig(CW_UART1,USART_IT_RC,ENABLE);    // 接收完成中断使能
}

/*
* @function: USART1_Send_Array
* @param: p_Arr -> 待发送数组 LEN -> 数组长度(使用sizeof计算)
* @retval: None
* @brief: 发送数组
*/
static void USART1_Send_Array(uint8_t* p_Arr, uint16_t LEN)
{
    uint16_t i;

    for (i = 0; i < LEN; i++)
    {
        USART_SendData_8bit(CW_UART1,*(p_Arr + i));
        // 等待发送完成,1:完成,0:还没完成
        while (USART_GetFlagStatus(UART_DEBUG, USART_FLAG_TXE) == RESET);
    }
    while(USART_GetFlagStatus(CW_UART1, USART_FLAG_TXBUSY) == SET); // 等待串口空闲
}

/*
* @function: USART1_Send_String
* @param: p_Str -> 待发送字符串
* @retval: None
* @brief: 发送字符串
*/
static void USART1_Send_String(uint8_t* p_Str)
{
    while (*p_Str)
    {
        USART_SendData_8bit(CW_UART1,*p_Str);
        // 等待发送完成,1:完成,0:还没完成
        while (USART_GetFlagStatus(UART_DEBUG, USART_FLAG_TXE) == RESET);
        p_Str++;
    }
    while(USART_GetFlagStatus(CW_UART1, USART_FLAG_TXBUSY) == SET); // 等待串口空闲
}

编写中断函数

modbus.h
cpp
#ifndef __MODBUS_H
#define __MODBUS_H
#include "main.h"

// 定义---读写寄存器功能号,命令长度
#define FunctionCode_Read_Register 		(uint8_t)0x03
#define FunctionCode_Write_Register 	(uint8_t)0x06
#define Modbus_Order_LENGTH           (uint8_t)8

typedef struct
{
    uint16_t Addr; // 地址

    void (*Protocol_Analysis)(USART1_t *); // 协议分析
} Modbus_t;

extern Modbus_t Modbus;

#endif
modbus.c
cpp
/***************************************************************************
 * File: modbus.c
 * Author: Luckys.
 * Date: 2023/06/12
 * description: ModBus协议
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void Protocol_Analysis(USART1_t*);  //协议分析
static void Modbus_Read_Register(USART1_t*);   //读寄存器
static void Modbus_Wrtie_Register(USART1_t*);  //写寄存器

/*====================================static function declaration area   END====================================*/
Modbus_t  Modbus = 
{
	1,
	
	Protocol_Analysis
};

/*
* @function: Protocol_Analysis
* @param: UART -> 串口1结构体指针
* @retval: None
* @brief: 描述
*/
static void Protocol_Analysis(USART1_t *UART)
{
    USART1_t *const COM = UART;
    uint8_t i = 0, Index = 0;

    // 过滤干扰数据,首字节为modbus地址,共8字节
    for (i = 0; i < USART1_Rec_LENGTH; i++)
    {
        // 检测键值起始数据Modbus.Addr
        if (Index == 0)
        {
            if (*(COM->puc_Usart1_Rec_Buffer + i) != Modbus.Addr)
                // 跳过下面的代码i++进行下一次循环
                continue;
        }

        *(COM->puc_Usart1_Rec_Buffer + Index) = *(COM->puc_Usart1_Rec_Buffer + i);

        // 已读取8个字节
        if (Index == Modbus_Order_LENGTH)
        {
            break;
        }

        Index++;
    }

    // 计算CRC-16
    CRC_16.CRC_Value = CRC_16.CRC_Check(COM->puc_Usart1_Rec_Buffer, 6); // 计算CRC值
    CRC_16.CRC_H = (uint8_t)(CRC_16.CRC_Value >> 8);
    CRC_16.CRC_L = (uint8_t)CRC_16.CRC_Value;

    // 校验CRC-16(为了兼容不同市面上的协议,高字节在前或者低字节在前)
    if (((*(COM->puc_Usart1_Rec_Buffer + 6) == CRC_16.CRC_L) && (*(COM->puc_Usart1_Rec_Buffer + 7) == CRC_16.CRC_H)) ||
        ((*(COM->puc_Usart1_Rec_Buffer + 6) == CRC_16.CRC_H) && (*(COM->puc_Usart1_Rec_Buffer + 7) == CRC_16.CRC_L)))
    {
        // 校验地址
        if ((*(COM->puc_Usart1_Rec_Buffer + 0)) == Modbus.Addr)
        {
            // 处理数据
            if ((*(COM->puc_Usart1_Rec_Buffer + 1)) == FunctionCode_Read_Register)
            {
                Modbus_Read_Register(COM);
            }
            else if ((*(COM->puc_Usart1_Rec_Buffer + 1)) == FunctionCode_Write_Register)
            {
                Modbus_Wrtie_Register(COM);
            }
        }
    }
    // 清缓存
    Public.Memory_Clear(COM->puc_Usart1_Rec_Buffer, USART1_Rec_LENGTH);
    USART1.ucUsart1_Rx_Cnt = 0;
}

/*
* @function: Modbus_Read_Register
* @param: UART -> 串口1结构体指针
* @retval: None
* @brief: 读寄存器
*/
static void Modbus_Read_Register(USART1_t *UART)
{
    USART1_t *const  COM = UART;

    //校验地址
    if((*(COM->puc_Usart1_Rec_Buffer + 2) == 0x9C) && (*(COM->puc_Usart1_Rec_Buffer + 3) == 0x41))
    {
        ////回应数据
        //地址码
        *(COM->puc_Usart1_Send_Buffer + 0)  = Modbus.Addr;
        //功能码
        *(COM->puc_Usart1_Send_Buffer + 1)  = FunctionCode_Read_Register;
        //数据长度
        *(COM->puc_Usart1_Send_Buffer + 2)  = 2;
        //蜂鸣器状态
        *(COM->puc_Usart1_Send_Buffer + 3)  = 0;
        *(COM->puc_Usart1_Send_Buffer + 4) = Buzzer.Buzzer_Status;

        //插入CRC
        CRC_16.CRC_Value = CRC_16.CRC_Check(COM->puc_Usart1_Send_Buffer, 5); //计算CRC值,因为CRC前有5个字节
        CRC_16.CRC_H     = (uint8_t)(CRC_16.CRC_Value >> 8);
        CRC_16.CRC_L     = (uint8_t)CRC_16.CRC_Value;
		// 低位在前高位在后
        *(COM->puc_Usart1_Send_Buffer + 5) = CRC_16.CRC_L;
        *(COM->puc_Usart1_Send_Buffer + 6) = CRC_16.CRC_H;

        //发送数据
        USART1.USART1_Send_Array(COM->puc_Usart1_Send_Buffer, 7);
        printf("\r\n"); // VOFA+上位机需要的!
    }
}

/*
* @function: Modbus_Wrtie_Register
* @param: UART -> 串口1结构体指针
* @retval: None
* @brief: 描述
*/
static void Modbus_Wrtie_Register(USART1_t *UART)
{
    USART1_t *const COM = UART;
    uint8_t i;

    ////回应数据
    // 准备数据
    for (i = 0; i < 8; i++)
    {
        *(COM->puc_Usart1_Send_Buffer + i) = *(COM->puc_Usart1_Rec_Buffer + i);
    }
    // 发送数据
    USART1.USART1_Send_Array(COM->puc_Usart1_Send_Buffer, 8);
    printf("\r\n"); // VOFA+上位机需要的!

    // 校验地址 -> 蜂鸣器
    if ((*(COM->puc_Usart1_Rec_Buffer + 2) == 0x9C) && (*(COM->puc_Usart1_Rec_Buffer + 3) == 0x44)) // 40004
    {
        // 控制蜂鸣器
        if (*(COM->puc_Usart1_Rec_Buffer + 5) == 0x01)
        {
            Buzzer.Buzzer_ON();
        }
        else
        {
            Buzzer.Buzzer_OFF();
        }
    }
}

CRC_16.h和CRC_16.c 跟之前的modbus一样复制即可这里就不展示了

发跟收都要加 0x0D 0x0A(\r\n)

  1. 上位机通过发送 01 03 9C 41 00 01 4E FA 去向单片机读取蜂鸣器状态,单片机回复 01 03 02 00 00 44 B8 或者 01 03 02 00 01 84 79 表示响和不响
  2. 上位机发送 01 06 9C 44 00 01 4F 26 控制单片机蜂鸣器响,发送 01 06 9C 44 00 00 8F E7 控制单片机蜂鸣器不响

定时器

数据手册介绍

只是使用定时器计数功能,不需要占用引脚资源,而当使用定时器通道时,则需要使用相应的引脚来输出或捕获信号(所以使用通道需要看手册的复用功能考虑用哪个)

重装载值最大是65535,分频系数它有给选项不能自定义(取值范围2的n次幂)

基本定时器

定时器模式(常用)

btim.h
cpp
#ifndef __BTIM_H
#define __BTIM_H
#include "main.h"

typedef struct
{
    void (*BTIM1_Init)(uint16_t, uint16_t);   // BTIM1初始化
}Btim_t;

extern Btim_t Btim;


#endif
btim.c
cpp
/***************************************************************************
 * File: btim.c
 * Author: Luckys.
 * Date: 2023/06/13
 * description: 基本定时器
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void BTIM1_Init(uint16_t, uint16_t); // BTIM1初始化

/*====================================static function declaration area   END====================================*/
Btim_t Btim = 
{
    BTIM1_Init,
};

/*
* @function: BTIM1_Init
* @param: arr -> 重装载值 psc -> 时钟分频系数(例如BTIM_PRS_DIV1)
* @retval: None
* @brief: BTIM1初始化
*/
static void BTIM1_Init(uint16_t arr, uint16_t psc)
{
    BTIM_TimeBaseInitTypeDef BTM_TimerBaseInitStructure;

    __RCC_BTIM_CLK_ENABLE();    // 打开定时器时钟

    __disable_irq();    // 关闭中断
    NVIC_EnableIRQ(BTIM1_IRQn); // 使能BTIM1中断
    __enable_irq(); // 打开中断

    BTM_TimerBaseInitStructure.BTIM_Mode = BTIM_Mode_TIMER; // 模式---定时器模式
    BTM_TimerBaseInitStructure.BTIM_OPMode = BTIM_OPMode_Repetitive;    // 连续模式
    BTM_TimerBaseInitStructure.BTIM_Period = arr;   // 重装载值
    BTM_TimerBaseInitStructure.BTIM_Prescaler = psc;    // 时钟预分频系数

    BTIM_TimeBaseInit(CW_BTIM1, &BTM_TimerBaseInitStructure);			//配置BTIM1定时器
	BTIM_ITConfig(CW_BTIM1, BTIM_IT_OV, ENABLE);					//使能BTIM1中断
	BTIM_Cmd(CW_BTIM1, ENABLE);										//使能定时器
}

通用定时器

  • 手册

PWM普通输出

电平极性在头文件有定义

cpp
#define GTIM_OC_OUTPUT_PWM_HIGH                 (14UL)
#define GTIM_OC_OUTPUT_PWM_LOW                  (15UL)

需要注意的是PWM_HIGH输出的有效电平是低电平,PWM_LOW输出的有效电平是高电平,用的时候注意即可

gtim.h
cpp
#ifndef __GHTIM_H
#define __GHTIM_H
#include "main.h"

typedef struct
{
    uint16_t Gtim1_CH_Fre; // GTIM频率
    float Gtim1_CH_Duty[2];  // GTIM1 通道占空比 
    uint16_t Gtim1_CH_Set_Pulse[2];   // 设置占空比(2个通道的)
    void (*Gtim1_PWM_Output)(uint16_t, uint16_t);   // 通用定时器1PWM输出初始化
    void (*Gtim1_Calculate)(void);  // 计算频率占空比
}Gtim_t;



extern Gtim_t Gtim;

#endif
gtim.c
cpp
/***************************************************************************
 * File: gtim.c
 * Author: Luckys.
 * Date: 2023/06/13
 * description: 通用定时器
 -----------------------------------
用到:
    PWM普通输出:
                GTIM1: PB4 --- CH1  PB5 --- CH2 
 -----------------------------------
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void Gtim1_PWM_Output(uint16_t arr, uint16_t psc);   // 通用定时器1PWM输出初始化
static void Gtim1_Calculate(void); // 计算频率占空比

/*====================================static function declaration area   END====================================*/
Gtim_t Gtim = 
{
    0,
    {0.0, 0.0},
    {200, 200},
    Gtim1_PWM_Output,
    Gtim1_Calculate,
};


/*
* @function: Gtim1_PWM_Output
* @param: arr -> 重装载值 psc -> 时钟分频系数(例如 GTIM_PRESCALER_DIV64)
* @retval: None
* @brief: 通用定时器1PWM输出初始化
*/
static void Gtim1_PWM_Output(uint16_t arr, uint16_t psc)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GTIM_InitTypeDef GTIM_InitStructure;

    // 使能时钟
    __RCC_GTIM1_CLK_ENABLE();
    __RCC_GPIOB_CLK_ENABLE();

    PB04_AFx_GTIM1CH1();    // PB4复用为GTIM1-CH1
    PB05_AFx_GTIM1CH2();    // PB5复用为GTIM1-CH2

    GPIO_InitStructure.IT = GPIO_IT_NONE; // 管脚中断模式--无
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;  // 模式--推挽输出
    GPIO_InitStructure.Pins = GPIO_PIN_4 | GPIO_PIN_5;  
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure); 

    /*定时器配置*/
    GTIM_InitStructure.Mode = GTIM_MODE_TIME; // 定时器模式 --- 定时器模式
    GTIM_InitStructure.OneShotMode = GTIM_COUNT_CONTINUE;  // 单次/连续计数模式 --- 连续
    GTIM_InitStructure.Prescaler = psc;    // 预分频系数 --- psc
    GTIM_InitStructure.ReloadValue = arr;  // 重载值 --- arr
    GTIM_InitStructure.ToggleOutState = DISABLE;   // 翻转输出使能选择 --- 不使能
    GTIM_TimeBaseInit(CW_GTIM1, &GTIM_InitStructure);   // 初始化
    /*通道1*/
    GTIM_OCInit(CW_GTIM1, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_LOW);  // 比较输出功能初始化 --- 定时器 通道 有效电平极性-高电平
    /*通道2*/
    GTIM_OCInit(CW_GTIM1, GTIM_CHANNEL2, GTIM_OC_OUTPUT_PWM_HIGH);  // 比较输出功能初始化 --- 定时器 通道 有效电平极性-高电平
    /*设置初始占空比*/
    CW_GTIM1->CCR1 = Gtim.Gtim1_CH_Set_Pulse[0];   // 设置通道1初始占空比 --- 初始化默认50%占空比  
    CW_GTIM1->CCR2 = Gtim.Gtim1_CH_Set_Pulse[1];   // 设置通道2初始占空比 --- 初始化默认50%占空比   

    GTIM_ITConfig(CW_GTIM1, GTIM_IT_OV, ENABLE);    // 中断打开
    GTIM_Cmd(CW_GTIM1, ENABLE); // 使能定时器
    // 使能中断
    __disable_irq();
    NVIC_EnableIRQ(GTIM1_IRQn);
    __enable_irq();    
}

输入捕获

通道1的输入捕获中断获取计数值VALUE1,通道2的输入捕获中断获取计数值VALUE2,通道1的第2次输入捕获中断获取计数值VALUE3。则信号脉宽=VALUE2-VALUE1,信号周期=VALUE3-VALUE1。注意如果待测量信号的脉宽和周期较长,在计算时需要考虑定时器的溢出问题

这部分有BUG,不能检测到待解决 —2023/6/18

callback.c
cpp
/*
* @function: GTIM2_IRQHandler
* @param: None
* @retval: None
* @brief: GTIM2中断服务函数
*/
void GTIM2_IRQHandler(void)
{
    static uint8_t Status = 0;   // 标志位,用于表示当前处于PWM信号的哪一阶段
    static uint32_t cnt = 0;    // 计数
    if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_OV)) // 判断是否为GTIM1计数器溢出中断
    {
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_OV); // 清除计数器溢出中断标志位
        if (Status == 1)                              // 如果当前处于PWM信号的第二阶段
        {
            cnt++;
        }
    }
    if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC1)) // 判断是否为GTIM1捕获比较匹配中断
    {
        if (Status == 0) // 如果当前处于PWM信号的第一阶段
        {
            Gtim.Gtim2_IC_Fre = CW_GTIM2->CCR1; // 获取捕获比较器1的值,即为PWM信号的周期
            Status = 1;                                     // 切换至PWM信号的第二阶段
        }
        else if (Status == 1) // 如果当前处于PWM信号的第二阶段
        {
            Gtim.Gtim2_IC_Fre = CW_GTIM2->CCR1 + cnt * 65536 - Gtim.Gtim2_IC_Fre; // 计算PWM信号的周期
            Status = 0;                                                                       // 切换至PWM信号的第一阶段
            cnt = 0;                                                                          // 计数器清零
        }
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC1); // 清除捕获比较器1匹配中断标志位
    }

    if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC2)) // 判断是否为GTIM1捕获比较匹配中断
    {
        if (Status == 1) // 如果当前处于PWM信号的第二阶段
        {
            Gtim.Gtim2_IC_Duty = CW_GTIM2->CCR2 + cnt * 65536 - Gtim.Gtim2_IC_Fre; // 计算PWM信号的占空比
            Gtim.Gtim2_IC_Flag = TRUE;
        }
        GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC2); // 清除捕获比较器2匹配中断标志位
    }
}
gtim.c
cpp
/*
* @function: Gtim2_PWM_IC_Init
* @param: None
* @retval: None
* @brief: GTIM2输入捕获初始化
*/
static void Gtim2_PWM_IC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GTIM_InitTypeDef GTIM_InitStrucure;
    GTIM_ICInitTypeDef GTIM_ICInitStructure;   

    __RCC_GTIM2_CLK_ENABLE();   // 
    __RCC_GPIOA_CLK_ENABLE(); 
    /*GPIO*/
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
    GPIO_InitStructure.Pins = GPIO_PIN_0 | GPIO_PIN_1;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
    PA00_AFx_GTIM2CH1();    // AF
    PA01_AFx_GTIM2CH2();    

    __disable_irq();
    NVIC_EnableIRQ(GTIM2_IRQn);
    __enable_irq(); 
    
    GTIM_InitStrucure.Mode = GTIM_MODE_TIME;    
    GTIM_InitStrucure.OneShotMode = GTIM_COUNT_CONTINUE;    
    GTIM_InitStrucure.Prescaler = GTIM_PRESCALER_DIV1;  
    GTIM_InitStrucure.ReloadValue = 0xFFFF; // ARR
    GTIM_InitStrucure.ToggleOutState = DISABLE;
    GTIM_TimeBaseInit(CW_GTIM2, &GTIM_InitStrucure);

    GTIM_ICInitStructure.CHx = GTIM_CHANNEL1;   // CH1
    GTIM_ICInitStructure.ICFilter = GTIM_CHx_FILTER_NONE;   // 过滤器 -- 无
    GTIM_ICInitStructure.ICInvert = GTIM_CHx_INVERT_ON; // 翻转 --- 无
    GTIM_ICInitStructure.ICPolarity = GTIM_ICPolarity_Rising;   // 上升沿
    GTIM_ICInit(CW_GTIM2, &GTIM_ICInitStructure);

    GTIM_ICInitStructure.CHx = GTIM_CHANNEL2;   // CH2
    GTIM_ICInitStructure.ICPolarity = GTIM_ICPolarity_Falling;   // 下降沿
    GTIM_ICInit(CW_GTIM2, &GTIM_ICInitStructure);

    GTIM_ITConfig(CW_GTIM2, GTIM_IT_CC1 | GTIM_IT_CC2 | GTIM_IT_OV, ENABLE);
    GTIM_Cmd(CW_GTIM2, ENABLE);   
}

PWM互补输出

各占一半频率,比如设置2500Hz,则两个通道各占一半1.25Hz

gtim.c
cpp
/*
* @function: Gtim3_PWM_Toggle_Init
* @param: arr -> 重装载值 psc ->  预分频(比如 GTIM_PRESCALER_DIV1024)
* @retval: None
* @brief: GTIM3互补输出初始化
*/
static void Gtim3_PWM_Toggle_Init(uint16_t arr, uint16_t psc)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GTIM_InitTypeDef GTIM_InitStrucure; 

    __RCC_GTIM3_CLK_ENABLE();   // 
    __RCC_GPIOC_CLK_ENABLE();

    PC14_AFx_GTIM3TOGN();    // AF
    PC15_AFx_GTIM3TOGP(); 
    /*GPIO*/
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Pins = GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOC, &GPIO_InitStructure);
    
    GTIM_InitStrucure.Mode = GTIM_MODE_TIME;    
    GTIM_InitStrucure.OneShotMode = GTIM_COUNT_CONTINUE;    
    GTIM_InitStrucure.Prescaler = psc;  
    GTIM_InitStrucure.ReloadValue = arr; // ARR
    GTIM_InitStrucure.ToggleOutState = ENABLE;
    GTIM_TimeBaseInit(CW_GTIM3, &GTIM_InitStrucure); 
    GTIM_Cmd(CW_GTIM3, ENABLE);
}

高级定时器

输入捕获

输入捕获也是有问题

atim.c
cpp
/*
* @function: Atim_PWM_Input_Init
* @param: None
* @retval: None
* @brief: 输入捕获初始化
*/
static void Atim_PWM_Input_Init(void)
{
    ATIM_InitTypeDef ATIM_InitStructure;
    ATIM_ICInitTypeDef ATIM_ICInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    __RCC_GPIOB_CLK_ENABLE();   
    __RCC_ATIM_CLK_ENABLE();
    /*gpio init*/
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
    GPIO_InitStructure.Pins = GPIO_PIN_2;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    PB02_AFx_ATIMCH1A();    // PB2 --- CH1A
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
    /*open nvic*/ 
    __disable_irq();
    NVIC_EnableIRQ(ATIM_IRQn);
    __enable_irq();

    ATIM_InitStructure.BufferState = DISABLE;
    ATIM_InitStructure.ClockSelect = ATIM_CLOCK_PCLK;
    ATIM_InitStructure.CounterAlignedMode = ATIM_COUNT_MODE_EDGE_ALIGN;
    ATIM_InitStructure.CounterDirection = ATIM_COUNTING_UP;
    ATIM_InitStructure.CounterOPMode = ATIM_OP_MODE_REPETITIVE;
    ATIM_InitStructure.OverFlowMask = DISABLE;
    ATIM_InitStructure.Prescaler = ATIM_Prescaler_DIV64;
    ATIM_InitStructure.ReloadValue = 0xFFFF;
    ATIM_InitStructure.RepetitionCounter = 0;
    ATIM_InitStructure.UnderFlowMask = DISABLE;  
    ATIM_Init(&ATIM_InitStructure);

    ATIM_ICInitStructure.ICFilter = ATIM_ICFILTER_NONE; // 输入滤波配置
    ATIM_ICInitStructure.ICPolarity = ATIM_ICPOLARITY_BOTHEDGE; // 输入捕获极性:上升、下降、双沿
    ATIM_IC1AInit(&ATIM_ICInitStructure);   // 输入捕获通道1A设置

    ATIM_ITConfig(ATIM_CR_IT_OVE, ENABLE);  // ATIM中断设置
    ATIM_CH1Config(ATIM_CHxA_CIE, ENABLE);  // 设置通道1的功能
    ATIM_Cmd(ENABLE);             
}
callback.c
cpp
uint32_t PWMPeriod = 0;
uint32_t PWMWidth = 0;
uint8_t ProcessState = 0;
/*
* @function: ATIM_IRQHandler
* @param: None
* @retval: None
* @brief: 高级定时器中断服务函数
*/
void ATIM_IRQHandler(void)
{
    static uint8_t stage = 0;
    static uint32_t cnt = 0;
    if (ATIM_GetITStatus(ATIM_IT_OVF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_OVF);
        if (stage)
        {
            cnt++;
        }
    }

    if (ATIM_GetITStatus(ATIM_IT_C1AF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_C1AF);
        if (stage == 0)
        {
            PWMPeriod = ATIM_GetCapture1A();
            cnt = 0;
            stage++;
        }
        else if (stage == 1)
        {
            PWMWidth = ATIM_GetCapture1A() + cnt * 0x10000UL - PWMPeriod;
            stage++;
        }
        else if (stage == 2)
        {
            PWMPeriod = ATIM_GetCapture1A() + cnt * 0x10000UL - PWMPeriod;
            stage = 0;
            ProcessState = 1;
        }
    }
}

输出比较

atim.c
cpp
/*
* @function: Atim_PWM_Output_OC_Init
* @param: None
* @retval: None
* @brief: 输出比较初始化
*/
static void Atim_PWM_Output_OC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ATIM_InitTypeDef ATIM_InitStructure;
    ATIM_OCInitTypeDef ATIM_OCInitStructure;

    __RCC_ATIM_CLK_ENABLE();
    __RCC_GPIOB_CLK_ENABLE();
    /*gpio init*/
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Pins = GPIO_PIN_2 | GPIO_PIN_13;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    PB02_AFx_ATIMCH1A();
    PB13_AFx_ATIMCH1B();
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);   
    __disable_irq();
    NVIC_EnableIRQ(ATIM_IRQn);
    __enable_irq();    

    ATIM_InitStructure.BufferState = DISABLE;
    ATIM_InitStructure.ClockSelect = ATIM_CLOCK_PCLK;
    ATIM_InitStructure.CounterAlignedMode = ATIM_COUNT_MODE_EDGE_ALIGN;
    ATIM_InitStructure.CounterDirection = ATIM_COUNTING_UP;
    ATIM_InitStructure.CounterOPMode = ATIM_OP_MODE_REPETITIVE;
    ATIM_InitStructure.OverFlowMask = DISABLE;
    ATIM_InitStructure.Prescaler = ATIM_Prescaler_DIV1;
    ATIM_InitStructure.ReloadValue = 6400;   // 10KHz            
    ATIM_InitStructure.RepetitionCounter = 0;
    ATIM_InitStructure.UnderFlowMask = DISABLE;
    ATIM_Init(&ATIM_InitStructure);  

    ATIM_OCInitStructure.BufferState = DISABLE;     // 比较缓存使能状态
    ATIM_OCInitStructure.OCDMAState = DISABLE;  // 比较匹配触发DMA使能状态
    ATIM_OCInitStructure.OCInterruptSelect = ATIM_OC_IT_UP_COUNTER; // 比较匹配触发中断使能状态
    ATIM_OCInitStructure.OCInterruptState = ENABLE; // 比较匹配触发中断使能状态
    ATIM_OCInitStructure.OCMode = ATIM_OCMODE_PWM1; //  比较模式配置
    ATIM_OCInitStructure.OCPolarity = ATIM_OCPOLARITY_NONINVERT;    // 端口极性选择:正向、反向---不翻转电平
    ATIM_OC1AInit(&ATIM_OCInitStructure);   // CH1A比较输出设置
    ATIM_OC1BInit(&ATIM_OCInitStructure);   // CH1B比较输出设置  

    ATIM_ITConfig(ATIM_CR_IT_OVE, ENABLE);  // ATIM中断设置
    ATIM_CH1Config(ATIM_CHxB_CIE | ATIM_CHxA_CIE, ENABLE);  // 设置通道1的功能
    ATIM_SetCompare1A(3200); // 50%占空比
    ATIM_SetCompare1B(2560);    // 40%占空比
    // ATIM_PWMOutputConfig(OCREFA_TYPE_SINGLE, OUTPUT_TYPE_COMP, 1);  // 设置PWM输出的参数
    ATIM_Cmd(ENABLE);   // ATIM 启动   
    ATIM_CtrlPWMOutputs(ENABLE);    // 使能PWM输出
}
callback.c
cpp
/*
* @function: ATIM_IRQHandler
* @param: None
* @retval: None
* @brief: 高级定时器中断服务函数
*/
void ATIM_IRQHandler(void)
{
    if (ATIM_GetITStatus(ATIM_IT_OVF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_OVF);
    }

    if (ATIM_GetITStatus(ATIM_IT_C1BF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_C1BF);
    }

    if (ATIM_GetITStatus(ATIM_IT_C1AF))
    {
        ATIM_ClearITPendingBit(ATIM_IT_C1AF);
    }
}

TFT-ST7735

见【屏幕学习篇】

OLED

见【协议学习篇】

ADC

  • 手册

一个12 位精度ADC

16 路输入转换通道
–13 路外部引脚输入
–内置温度传感器
–内置 BGR 1.2V 基准
–1/3 VDDA 电源电压

4 路参考电压源(Vref)
–VDDA 电源电压
–ExRef(PB0)引脚电压
–内置 1.5V 参考电压
–内置 2.5V 参考电压

只有一个ADC所以不能同时进行单通道或者序列

  • 硬件连接

  • 程序编写

单通道单次采集 + 序列扫描采集(多通道)

adc1.h
cpp
#ifndef __ADC1_H
#define __ADC1_H
#include "main.h"


typedef struct
{
    volatile uint8_t gFlagIrq;  // ADC完成采集中断标志位
    volatile float ADC_Single_Result;    // ADC单次采集单通道转换结果
    volatile float ADC_Serial_Result_Arr[4];  // ADC序列扫描转换结果数组
    void (*ADC1_Single_Channel_One_Init)(void);    // ADC单通道单次采集初始化
    void (*ADC1_Serial_Scan_Init)(void);    // ADC序列扫描采集初始化
    void (*ADC1_Single_Channel_One_Convert)(void); // ADC单通道单次采集转换
    void (*ADC1_Serial_Scan_Convert)(void); // ADC序列扫描采集转换
}ADC_1_t;

extern ADC_1_t ADC_1;

#endif
adc1.c
cpp
/***************************************************************************
 * File: adc1.c
 * Author: Luckys.
 * Date: 2023/06/15
 * description: ADC
 -----------------------------------
注意:板子的电位器,对应ADC通道引脚是PB0,然后还添加了另外3个通道分别是PA4 PA5 PA6 
注意:需要在【public.h】进行ADC选择模式(单通道单次/序列扫描)
 -----------------------------------
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/
static void ADC1_Single_Channel_One_Init(void);    // ADC单通道单次采集初始化
static void ADC1_Serial_Scan_Init(void);    // ADC序列扫描采集初始化
static void ADC1_Single_Channel_One_Convert(void); // ADC单通道单次采集转换
static void ADC1_Serial_Scan_Convert(void);    // ADC序列扫描采集转换

/*====================================static function declaration area   END====================================*/
ADC_1_t ADC_1 = 
{
    0,
    0,
    {0.0},
    ADC1_Single_Channel_One_Init,
    ADC1_Serial_Scan_Init,
    ADC1_Single_Channel_One_Convert,
    ADC1_Serial_Scan_Convert,
};

/*
* @function: ADC1_Single_Channel_One_Init
* @param: None
* @retval: None
* @brief: ADC单通道单次采集初始化
*/
static void ADC1_Single_Channel_One_Init(void)
{
    ADC_SingleChTypeDef ADC_SingleChInitStructure;

    // 打开时钟
    __RCC_GPIOB_CLK_ENABLE();   
    __RCC_ADC_CLK_ENABLE();
    
    // 引脚设为模拟输入
    PB00_ANALOG_ENABLE();   // PB0 --- ADC_CH8

    /*ADC配置*/
    ADC_SingleChInitStructure.ADC_Chmux = ADC_ExInputCH8;   // 输入通道 -- 8
    ADC_SingleChInitStructure.ADC_DiscardEn = ADC_DiscardNull;  // 单通道ADC转换结果保存策略配置
    ADC_SingleChInitStructure.ADC_WdtStruct.ADC_WdtAll = ADC_WdtDisable;    // ADC模拟看门狗使能 -- 不使能
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_AccEn = ADC_AccDisable;   // 转换结果累加是否使能 -- 否
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_Align = ADC_AlignRight; // 转换结果对齐方式 -- 右对齐(采集多少就是多少)
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div4;    // 时钟选择(ADC工作时钟ADCCLK,由系统时钟PCLK 经预分频器分频得到) -- 4分频
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_DMAEn = ADC_DmaDisable; // ADC转换完成是/否触发DMA使能 -- 不使能
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_InBufEn = ADC_BufEnable;   // ADC输入增益使能 -- 开启
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_OpMode = ADC_SingleChOneMode;    // 操作模式 -- 单通道单次转换模式
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_SampleTime = ADC_SampTime10Clk;    // ADC采样时间 -- 10个ADCCLK 个数
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_TsEn = ADC_TsDisable;  // 内置温度传感器是/否使能 -- 否
    ADC_SingleChInitStructure.ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;   // ADC参考电压 -- 使用芯片供电电压

    ADC_SingleChOneModeCfg(&ADC_SingleChInitStructure); // ADC单通道单次转换模式配置
    ADC_Enable();   // ADC使能
    ADC_SoftwareStartConvCmd(ENABLE);   // ADC转换软件启动
}

/*
* @function: ADC1_Serial_Scan_Init
* @param: None
* @retval: None
* @brief: ADC序列扫描采集初始化
*/
static void ADC1_Serial_Scan_Init(void)
{
    ADC_SerialChTypeDef ADC_SerialChInitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    
    // 打开时钟
    __RCC_GPIOA_CLK_ENABLE();
    __RCC_GPIOB_CLK_ENABLE();   
    __RCC_ADC_CLK_ENABLE();
    
    // 引脚设为模拟输入
    PA04_ANALOG_ENABLE();   // PA4 --- ADC_CH4
    PA05_ANALOG_ENABLE();   // PA5 --- ADC_CH5
    PA06_ANALOG_ENABLE();   // PA6 --- ADC_CH6
    PB00_ANALOG_ENABLE();   // PB0 --- ADC_CH8

    ADC_StructInit(&ADC_InitStructure); // 默认值初始化
    ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div16;   // ADC CLK: 64000000/16 = 4000KHz 不能超过24M
    ADC_SerialChInitStructure.ADC_SqrEns = ADC_SqrEns03;    // 采集4个通道 
    ADC_SerialChInitStructure.ADC_Sqr0Chmux = ADC_SqrCh8;
    ADC_SerialChInitStructure.ADC_Sqr1Chmux = ADC_SqrCh4;
    ADC_SerialChInitStructure.ADC_Sqr2Chmux = ADC_SqrCh5;
    ADC_SerialChInitStructure.ADC_Sqr3Chmux = ADC_SqrCh6;
    ADC_SerialChInitStructure.ADC_InitStruct = ADC_InitStructure;

    ADC_SerialChScanModeCfg(&ADC_SerialChInitStructure);    // 序列扫描转换初始化
    ADC_ITConfig(ADC_IT_EOS, ENABLE);   // 中断使能
    ADC_EnableIrq(ADC_INT_PRIORITY);    // 优先级
    ADC_ClearITPendingAll();    // 清除所有ADC中断标志
    ADC_Enable();   // ADC使能
    ADC_SoftwareStartConvCmd(ENABLE);   // ADC转换软件启动
}

/*
* @function: ADC1_Single_Channel_One_Convert
* @param: None
* @retval: None
* @brief: ADC单通道单次采集转换
*/
static void ADC1_Single_Channel_One_Convert(void)
{
    uint16_t adc_temp;

    ADC_SoftwareStartConvCmd(ENABLE);

    while(ADC_GetITStatus(ADC_IT_EOC))
    {
        ADC_ClearITPendingBit(ADC_IT_EOC);  // 清除标志位
        adc_temp = ADC_GetConversionValue();    // 获取单次转换的值
        ADC_1.ADC_Single_Result = (float)adc_temp*(3.3f/4096u);  // 计算电压结果
    }
}

/*
* @function: ADC1_Serial_Scan_Convert
* @param: None
* @retval: None
* @brief: ADC序列扫描采集转换
*/
static void ADC1_Serial_Scan_Convert(void)
{
    uint16_t adc_temp[4];

    while (!(ADC_1.gFlagIrq & ADC_ISR_EOS_Msk));    // &上掩码0x2
    ADC_1.gFlagIrq = 0u;    // 置0
    ADC_GetSqr0Result(adc_temp);                                        // 获取序列0通道转换的值
    ADC_GetSqr1Result(&adc_temp[1]);                                    // 获取序列1通道转换的值
    ADC_GetSqr2Result(&adc_temp[2]);                                    // 获取序列2通道转换的值
    ADC_GetSqr3Result(&adc_temp[3]);                                    // 获取序列3通道转换的值
    ADC_SoftwareStartConvCmd(ENABLE);                                   // ADC转换软件启动
    ADC_1.ADC_Serial_Result_Arr[0] = (float)adc_temp[0] * (3.3f / 4096u); // 计算电压结果--PB0
    ADC_1.ADC_Serial_Result_Arr[1] = (float)adc_temp[1] * (3.3f / 4096u); // 计算电压结果--PA4
    ADC_1.ADC_Serial_Result_Arr[2] = (float)adc_temp[2] * (3.3f / 4096u); // 计算电压结果--PA5
    ADC_1.ADC_Serial_Result_Arr[3] = (float)adc_temp[3] * (3.3f / 4096u); // 计算电压结果--PA6
}
callback.c
cpp
/*
* @function: ADC_IRQHandler
* @param: None
* @retval: None
* @brief: ADC中断服务函数
*/
void ADC_IRQHandler(void)
{
    ADC_1.gFlagIrq = CW_ADC->ISR;   // 获取中断标志寄存器
    CW_ADC->ICR = 0x00; // 中断标志清除寄存器
}

这次采集4个通道的值,注意复用功能

RTC

  • 手册

初始化一次后,后面的初始化是改变了RTC的,复位的话它底层也会判断,所以想改RTC时间的话则需要使用函数 RTC_SetTimeRTC_SetDate 进行更改(注意需要这个结构体的全部成员都初始化一遍比如24小时制那些否则默认是12小时制的)

一开始我还去改底层那个,其实不用改这样默认就挺好的,需要改时间的话就用函数

设置日期和时间时需要设置为BCD码格式,即每4个二进制表示一个十进制数,比如十进制16对应BCD码就是0x16(相当于十进制前面加0x而已)

闹钟功能:

下面是屏蔽标志,当你选择 RTC_AlarmMask_All 后,相当于秒中断了

cpp
// 如果想屏蔽所有,只剩下秒可以这样,这样就每分钟的第10s就触发了
RTC_AlarmMask_All & (~RTC_AlarmMask_Seconds);
// 如果想加上分钟限制可以这样
RTC_AlarmMask_All & (~(RTC_AlarmMask_Seconds|RTC_AlarmMask_Minutes));
// 其他以此类推

  • 程序编写
rtc.h
cpp
#ifndef __RTC_H
#define __RTC_H
#include "main.h"

typedef enum
{
    YEAR = (uint8_t)0,
    MON = (uint8_t)1,
    DAY = (uint8_t)2,
    HOUR = (uint8_t)3,
    MIN = (uint8_t)4,
    SEC = (uint8_t)5,    
}myRTC_Buff_Index_t;

typedef struct
{
    uint16_t usRtc_Buff[6]; // 存储年月日时分秒
    void (*myRTC_Init)(uint16_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);   // RTC初始化
    void (*myRTC_Refresh)(void);    // RTC刷新
    void (*myRTC_Set_Time)(uint8_t, uint8_t, uint8_t);  // 设置时间
    void (*myRTC_Set_Date)(uint16_t, uint8_t, uint8_t,uint8_t);    // 设置日期
    void (*myRTC_Alarm_A_Init)(void);   // 闹钟A初始化
}myRTC_t;


extern myRTC_t myRTC;


#endif
rtc.c
cpp
/***************************************************************************
 * File: rtc.c
 * Author: Luckys.
 * Date: 2023/06/15
 * description: RTC
****************************************************************************/
#include "main.h"

/*====================================variable definition declaration area BEGIN===================================*/

// RTC时间日期存储结构体
RTC_TimeTypeDef nTime;
RTC_DateTypeDef nDate;
// 星期
static uint8_t *WeekdayStr[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
/*====================================variable definition declaration area   END===================================*/

/*====================================static function declaration area BEGIN====================================*/

static void myRTC_Init(uint16_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
static void myRTC_Calculate_Date(uint16_t, uint8_t, uint8_t*);    // RTC日期计算(十进制 to BCD码)
static void myRTC_Calculate_Time(uint8_t, uint8_t, uint8_t, uint8_t*); // RTC时间计算(十进制 to BCD码)
static void myRTC_Refresh(void);    // RTC刷新
static void myRTC_Set_Time(uint8_t, uint8_t, uint8_t);  // 设置RTC时间
static void myRTC_Set_Date(uint16_t, uint8_t, uint8_t,uint8_t);  // 设置RTC日期
static void myRTC_Alarm_A_Init(void);   // 闹钟A初始化

/*====================================static function declaration area   END====================================*/
myRTC_t myRTC = 
{
    {0},
    myRTC_Init,
    myRTC_Refresh,
    myRTC_Set_Time,
    myRTC_Set_Date,
    myRTC_Alarm_A_Init,
};

/*
* @function: myRTC_Init
* @param: 年 月(RTC_Month_June) 日 星期(RTC_Weekday_Friday) 时 分 秒
* @retval: None
* @brief: RTC初始化
*/
static void myRTC_Init(uint16_t year, uint8_t mon, uint8_t day, uint8_t week, uint8_t hour, uint8_t min, uint8_t sec)
{
    RTC_InitTypeDef RTC_InitStructure;

    uint8_t Date_temp[2] = {0};  // 按顺序存储: 【年,日】
    uint8_t Time_temp[3] = {0};  // 按顺序存储: 【时,分,秒】

    __RCC_RTC_CLK_ENABLE();
    RCC_LSE_Enable(RCC_LSE_MODE_OSC, RCC_LSE_AMP_NORMAL, RCC_LSE_DRIVER_NORMAL);    // 选择LSE为RTC时钟
    
    myRTC_Calculate_Date(year,day,Date_temp);    // 计算日期
    myRTC_Calculate_Time(hour,min,sec,Time_temp);   // 计算时间

    // 设置日期,DAY、MONTH、YEAR必须为BCD方式,星期为0~6,代表星期日,星期一至星期六
    RTC_InitStructure.DateStruct.Year = Date_temp[0];   // 【年】
    RTC_InitStructure.DateStruct.Month = mon;    // 【月】
    RTC_InitStructure.DateStruct.Day = Date_temp[1];    // 【日】
    RTC_InitStructure.DateStruct.Week = week; // 【星期】
    // 打印测试
    printf("-----Set Date as 20%x/%x/%x\r\n",RTC_InitStructure.DateStruct.Year, RTC_InitStructure.DateStruct.Month, RTC_InitStructure.DateStruct.Day);
    // 设置时间,HOUR、MINIUTE、SECOND必须为BCD方式,须保证HOUR、AMPM、H24之间的关联正确性
    RTC_InitStructure.TimeStruct.Hour = Time_temp[0];   // 【时】
    RTC_InitStructure.TimeStruct.Minute = Time_temp[1]; // 【分】
    RTC_InitStructure.TimeStruct.Second = Time_temp[2]; // 【秒】
    RTC_InitStructure.TimeStruct.AMPM = 0;
    RTC_InitStructure.TimeStruct.H24 = 1; // 24小时制(1) 12小时制(0)   -- 24小时
    RTC_InitStructure.RTC_ClockSource = RTC_RTCCLK_FROM_LSE;    // 时钟源选择 -- 外部晶体时钟LSE
    // 打印测试
    printf("-----Set Time as %02x:%02x:%02x\r\n",RTC_InitStructure.TimeStruct.Hour,RTC_InitStructure.TimeStruct.Minute,RTC_InitStructure.TimeStruct.Second);
    
    RTC_Init(&RTC_InitStructure);
#ifdef USE_RTC_Interrupt
    RTC_SetInterval(RTC_INTERVAL_EVERY_1S); // RTC秒中断
    RTC_ITConfig(RTC_IT_INTERVAL,ENABLE); // 开启RTC中断
    NVIC_EnableIRQ(RTC_IRQn);    // 开启RTC中断
#endif    
}

/*
* @function: myRTC_Alarm_A_Init
* @param: None
* @retval: None
* @brief: 闹钟A初始化
*/
static void myRTC_Alarm_A_Init(void)
{
    RTC_AlarmTypeDef RTC_AlarmStructure;

    RTC_AlarmStructure.RTC_AlarmMask = RTC_AlarmMask_All & (~(RTC_AlarmMask_Seconds|RTC_AlarmMask_Minutes|RTC_AlarmMask_Hours)); // 时分秒吻合触发
    RTC_AlarmStructure.RTC_AlarmTime.AMPM = 0;
    RTC_AlarmStructure.RTC_AlarmTime.H24 = 1;
    RTC_AlarmStructure.RTC_AlarmTime.Hour = 0x19;	// 设置闹钟晚上7点触发
    RTC_AlarmStructure.RTC_AlarmTime.Minute = 0x00;
    RTC_AlarmStructure.RTC_AlarmTime.Second = 0x00;

    RTC_SetAlarm(RTC_Alarm_A,&RTC_AlarmStructure);  // 设置闹钟
    RTC_AlarmCmd(RTC_Alarm_A,ENABLE);   // 使能闹钟

    RTC_ITConfig(RTC_IT_ALARMA,ENABLE); // 开启闹钟中断
    NVIC_EnableIRQ(RTC_IRQn);   // 开启RTC中断
}

/*
* @function: myRTC_Calculate_Date
* @param: 年 日 存储结果的数组
* @retval: None
* @brief: RTC日期计算(十进制 to BCD码)
*/
static __inline void myRTC_Calculate_Date(uint16_t year, uint8_t day, uint8_t* p_temp)
{
    *(p_temp + 0) = ((year / 1000) << 4) | ((year / 100) % 10) | ((year / 10) % 10) << 4 | (year % 10); // 【年】
    *(p_temp + 1) = ((day / 10) % 10) << 4 | (day % 10);    // 【日】
}

/*
* @function: myRTC_Calculate_Time
* @param: 时 分 秒 存储结果的数组
* @retval: None
* @brief: RTC时间计算(十进制 to BCD码)
*/
static __inline void myRTC_Calculate_Time(uint8_t hour, uint8_t min, uint8_t sec, uint8_t* p_temp)
{
    *(p_temp + 0) = ((hour / 10) % 10) << 4 | (hour % 10);  // 【时】
    *(p_temp + 1) = ((min / 10) % 10) << 4 | (min % 10);    // 【分】
    *(p_temp + 2) = ((sec / 10) % 10) << 4 | (sec % 10);    // 【秒】
}

/*
* @function: myRTC_Refresh
* @param: None
* @retval: None
* @brief: RTC刷新
*/
static void myRTC_Refresh(void)
{
    RTC_GetDate(&nDate); // 获取日期
    RTC_GetTime(&nTime); // 获取时间

    // OLED刷新
    sprintf((char *)Page2.OLED096_Display_Buff[0], "Date:20%02x/%02x/%02x", nDate.Year, nDate.Month, nDate.Day);   // 日期
    OLED096.padString((char *)Page2.OLED096_Display_Buff[0], 16);                                                  // 补全空格
    sprintf((char *)Page2.OLED096_Display_Buff[1], "Time:%02x-%02x-%02x", nTime.Hour, nTime.Minute, nTime.Second); // 时间
    OLED096.padString((char *)Page2.OLED096_Display_Buff[1], 16);                                                  // 补全空格
    sprintf((char *)Page2.OLED096_Display_Buff[2], "Week:%s", WeekdayStr[nDate.Week]);                             // 星期
    OLED096.padString((char *)Page2.OLED096_Display_Buff[2], 16);                                                  // 补全空格
}

/*
* @function: myRTC_Set_Time
* @param: 时分秒
* @retval: None
* @brief: 设置RTC时间
*/
static void myRTC_Set_Time(uint8_t hour, uint8_t min, uint8_t sec)
{
    RTC_TimeTypeDef sTime;
    uint8_t Time_temp[3] = {0};

    myRTC_Calculate_Time(hour,min,sec,Time_temp); // 计算
    sTime.Hour = Time_temp[0];
    sTime.Minute = Time_temp[1];
    sTime.Second = Time_temp[2];
    sTime.AMPM = 0;
    sTime.H24 = 1;
    RTC_SetTime(&sTime);    // 设置时间   
}

/*
* @function: myRTC_Set_Date
* @param: None
* @retval: None
* @brief: 设置RTC日期
*/
static void myRTC_Set_Date(uint16_t year, uint8_t mon, uint8_t day,uint8_t week)
{
    RTC_DateTypeDef sDate;
    uint8_t Date_temp[2] = {0};

    myRTC_Calculate_Date(year,day,Date_temp); // 计算
    sDate.Year = Date_temp[0];
    sDate.Month = mon;
    sDate.Day = Date_temp[1];
    sDate.Week = week;
    RTC_SetDate(&sDate);    // 设置日期
}


callback.c
cpp
/*
* @function: RTC_IRQHandler
* @param: None
* @retval: None
* @brief: RTC中断服务函数
*/
void RTC_IRQHandler(void)
{
    if (RTC_GetITState(RTC_IT_ALARMA))  // 闹钟中断触发
    {
        RTC_ClearITPendingBit(RTC_IT_ALARMA);
        Buzzer.Buzzer_ON();
    }
#ifdef USE_RTC_Interrupt    
    if (RTC_GetITState(RTC_IT_INTERVAL))    // RTC秒中断触发
    {
        RTC_ClearITPendingBit(RTC_IT_INTERVAL);
    }
#endif    
}

DHT11温湿度

  • 硬件连接

  • 介绍可以看【STM32入门篇】

  • 时序

复位与检测DHT11

读取数据0/1

  • 程序编写
dht11.h
cpp
#ifndef __DHT11_H
#define __DHT11_H
#include "main.h"

// 引脚定义 PB1
#define DHT11_Pin GPIO_PIN_1

// 引脚模式设置(输出/输入)
#define DHT11_PIN_OUT PB01_DIR_OUTPUT()
#define DHT11_PIN_INPUT PB01_DIR_INPUT()
// 引脚拉低/拉高
#define DHT11_PIN_RESET PB01_SETLOW()
#define DHT11_PIN_SET PB01_SETHIGH()
// 读取引脚电平
#define DHT11_Read_Pin GPIO_ReadPin(CW_GPIOB,DHT11_Pin)


typedef struct
{
    float DHT11_Temperture; // 温度
    uint8_t DHT11_Humidity; // 湿度
    uint8_t (*DHT11_Init)(void);    // DHT11初始化
    uint8_t (*DHT11_Read_Data)(float *, uint8_t *);   // DNT11获取一次数据
}DHT11_t;


extern DHT11_t DHT11;

#endif
dht11.c
cpp
/***************************************************************************
 * File: dht11.c
 * Author: Luckys.
 * Date: 2023/06/16
 * description: dht11温湿度传感器
 -----------------------------------
接线:
    GND ---- GND
    3.3V ---- 3.3V
    PB1 ---- S
 -----------------------------------
****************************************************************************/
#include "main.h"

/*====================================static function declaration area BEGIN====================================*/

static void DHT11_Rest(void);   // DHT11复位
static uint8_t DHT11_Init(void);   // DHT11初始化
static uint8_t DHT11_Check(void);   // DHT11检测
static uint8_t DHT11_Read_Bit(void);  // DHT11读取一个位
static uint8_t DHT11_Read_Byte(void);   // DHT11读取一个字节
static uint8_t DHT11_Read_Data(float *, uint8_t *); // DHT11读取一次数据

/*====================================static function declaration area   END====================================*/
DHT11_t DHT11 = 
{
    0.0,
    0,
    DHT11_Init,
    DHT11_Read_Data,
};

/*
* @function: DHT11_Rest
* @param: None
* @retval: None
* @brief: DHT11复位
*/
static void DHT11_Rest(void)
{
    DHT11_PIN_OUT;  // 输出
    DHT11_PIN_RESET;    // 拉低
    Public.System_MS_Delay(20);    // 拉低至少18ms
    DHT11_PIN_SET;  // 拉高
    Public.System_10us_Delay(3);   // 拉高至少20~40us
}

/*
* @function: DHT11_Check
* @param: None
* @retval: FALSE -- 未检测到DHT11存在 TRUE -- 存在
* @brief: DHT11检测
*/
static uint8_t DHT11_Check(void)
{
    uint8_t retry = TRUE;
    DHT11_PIN_INPUT;    // 输入
    while (DHT11_Read_Pin && (retry < 10))  // DHT11会拉低40~80us
    {
        retry++;
        Public.System_10us_Delay(1);
    }
    if (retry >= 10)
    {
        return FALSE;
    }
    else
    {
        retry = TRUE;
    }
    while ((!DHT11_Read_Pin) && (retry < 10))   // DHT11拉低后会再拉高40~80us
    {
        retry++;
        Public.System_10us_Delay(1);
    }
    if (retry >= 10)
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

/*
* @function: DHT11_Read_Bit
* @param: None
* @retval: 1/0
* @brief: DHT11读取一个位
*/
static uint8_t DHT11_Read_Bit(void)
{
    uint8_t retry = 0;

    while (DHT11_Read_Pin && (retry < 10))  // 等待变成低电平
    {
        retry++;
        Public.System_10us_Delay(1);
    }
    retry = 0;
    while ((!DHT11_Read_Pin) && (retry < 10))  // 等待变成高电平
    {
        retry++;
        Public.System_10us_Delay(1);
    }
    Public.System_10us_Delay(4);    // 进入高电平后延时40us(取一个中间值)
    if (DHT11_Read_Pin) // 判断高低电平,即数据1或0
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

/*
* @function: DHT11_Read_Byte
* @param: None
* @retval: 读到的数据
* @brief: DHT11读取一个字节
*/
static uint8_t DHT11_Read_Byte(void)
{
    uint8_t i, dat;
    dat = 0;
    for (i = 0; i < 8; i++)
    {
        dat <<= 1;
        dat |= DHT11_Read_Bit();
    }
    return dat;
}

/*
* @function: DHT11_Read_Data
* @param: None
* @retval: TRUE -- 成功 FALSE -- 失败
* @brief: DHT11读取一次数据
*/
static uint8_t DHT11_Read_Data(float *temp,uint8_t *humi)
{
    char buf[5];
    uint8_t i;

    DHT11_Rest();   // 复位

    if (DHT11_Check())  // 检测成功
    {
        for (i = 0; i < 5; i++) // 读取5个数据
        {
            buf[i] = DHT11_Read_Byte();
        }
        if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])  // 湿度整数-湿度小数-温度整数-温度小数
        {
            *humi = buf[0];
            *temp = buf[2] + (float)buf[3] / 10.0f; // 合并为一个完整的小数
            // printf("A:%d B:%d C:%d D:%d\r\n",buf[0],buf[1],buf[2],buf[3]);
            // OLED刷新
            sprintf((char*)Page1.OLED096_Display_Buff[0],"Temp:%.1f",*temp);
            OLED096.padString((char*)Page1.OLED096_Display_Buff[0],8);
            sprintf((char*)Page1.OLED096_Display_Buff[1],"Humidity:%d %%",*humi);
            OLED096.padString((char*)Page1.OLED096_Display_Buff[1],16);            
        }
    }
    else
    {
        return FALSE;
    }
    return TRUE;
}

/*
* @function: DHT11_Init
* @param: None
* @retval: DHT11的回应 TRUE -- 成功 FALSE -- 失败
* @brief: DHT11初始化
*/
static uint8_t DHT11_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    __RCC_GPIOB_CLK_ENABLE();

    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出
    GPIO_InitStructure.Pins = DHT11_Pin;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);

    DHT11_Rest();   // 复位

    return DHT11_Check();   // 等待DHT11回应
}

光敏电阻

直接把引脚接到ADC通道即可,然后正常ADC电压计算输出

DMA

不常用

基本配置

cpp
DMA_InitTypeDef DMA_InitStructure;

__RCC_DMA_CLK_ENABLE();
DMA_StructInit(&DMA_InitStructure); // 结构体初始化

DMA_InitStructure.DMA_DstAddress = (uint32_t) &ADC_1.ADC_Result_Array[1];   // 目标地址
DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Increase;    // 指定目标地址寄存器是否递增 -- 递增
DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK;  // 操作模式 -- BLOCK	每传输完成 1 个数据块后会插入一个传输间隙
DMA_InitStructure.DMA_SrcAddress = (uint32_t) &ADC_1.ADC_Result_Array[0];   // 源地址
DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Fix;    // 指定源地址寄存器是否递增 -- 不递增
DMA_InitStructure.DMA_TransferCnt = 16;   // DMA传输次数
DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT; // 数据位宽 -- 32位
DMA_InitStructure.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE;    // ADC转换完成触发 -- 硬触发
DMA_InitStructure.TrigMode = DMA_HardTrig;  // 触发模式 -- 硬触发

更新记录

我的项目工程github

时间 备注
2023/6/23 提交最终版

常见问题

以下问题大部分来自群大佬总结,我把一些会遇到的记录在这!

如果取模.h报错,有可能是编码问题,汉字的话需要把.h保存为GB2312格式保存

CW32要用AC5编译器,用AC6编译会报错(怎么解决可以看【Keil相关】文章)

  • 编译报找不到assert_failed的错误

多见于自己新建的工程,两个解决方法:

  1. main.c中添加
cpp
#ifdef  USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
  1. base_types.h 注释掉下面这行
cpp
#define USE_FULL_ASSERT

出现这个报错,是因为编译器不识别 inline 这种写法,解决方法是把 inline 替换为 __inline 即可

  • 如果烧录时提示找不到FLM文件

解决方法:

  1. 可以手动复制FLM文件到这个文件夹,安装完pack文件后flm文件可以在这个位置找到:

  1. 把原来的算法文件删掉重新添加,列表里找不到CW32的请确认是否安装了pack