qzc0927 发表于 2024-11-15 17:32

CW32L010学习笔记

<div style="text-align:center">
    CW32学习开发笔记
</div>


# 硬件原理图:

## 主芯片

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114103849101.png)

引脚封装图:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114104051352.png)

​      CW32L010 是基于 eFlash 的单芯片低功耗微控制器,集成了主频高达 48MHz 的 ARM® Cortex®-M0+ 内核、
高速嵌入式存储器(多至 64K 字节 FLASH 和多至 4K 字节 SRAM)以及一系列全面的增强型外设和 I/O 口。
所有型号都提供全套的通信接口(二路 UART、一路 SPI 和一路 I2C)、12 位高速 ADC、四组通用和基本定时器、
一组低功耗定时器以及一组高级控制 PWM 定时器。

## 供电电源

​          使用type-c直接供电即可,不需要再接其他电源转换芯片,CW32L010 可以在 -40℃到 85℃的温度范围内工作,供电电压宽达 1.62V ~ 5.5V。支持 Sleep 和 DeepSleep两种低功耗工作模式。

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114104435067.png)

## 复位电路

!(C:/Users/syz/AppData/Roaming/Typora/typora-user-images/image-20241114104524841.png)

## 滤波电路

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114104852343.png)

## 内部稳压

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114105052457.png)

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114105024829.png)

## 调试下载

默认使用SWD接口下载程序,原理图如下:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114105146199.png)



## 板载指示灯

用于查看系统运行状态,原理图如下:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114110648242.png)

# 软件功能

## 工程创建

具体如何创建工程就不所说明了,官方例程都有说明,我主要说下的我的目录结构设计:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114105830013.png)

## 串口通讯

​         内部集成 2 个通用异步收发器 (UART),支持异步全双工、同步半双工和单线半双工模式,支持硬件数据流控
和多机通信,还支持 LIN(局域互连网络);可编程数据帧结构,可以通过小数波特率发生器提供宽范围的
波特率选择。内置定时器模块,支持等待超时检测、接收空闲检测、自动波特率检测和通用定时功能。
UART 控制器工作在双时钟域下,允许在深度休眠模式下进行数据的接收,接收完成中断可以唤醒 MCU 回到
运行模式。注意:仅 UART1 支持 LIN 和定时器功能;UART2 可通过片内外设互联与 BTIM/GTIM/ATIM 的从模式协同工
作实现超时定时器相关功能。我们接着实现串口通讯功能;

- 1.串口功能硬件引脚

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114135526600.png)

使用串口2来是实现通讯,再看引脚的复用功能。

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114135659092.png)

- 2.代码实现

```C
#include"bsp_uart2.h"
#include "cw32l010_gpio.h"
#include "cw32l010_uart.h"
#include"stdio.h"
#include "cw32l010_sysctrl.h"

//UARTx
#defineDEBUG_UARTx                   CW_UART2
#defineDEBUG_UART_CLK                SYSCTRL_APB1_PERIPH_UART2
#defineDEBUG_UART_APBClkENx          SYSCTRL_APBPeriphClk_Enable1
#defineDEBUG_UART_BaudRate         115200
#defineDEBUG_UART_UclkFreq         HSIOSC_VALUE   //串口全速运行

//UARTx GPIO
#defineDEBUG_UART_GPIO_CLK         (SYSCTRL_AHB_PERIPH_GPIOB)
#defineDEBUG_UART_TX_GPIO_PORT       CW_GPIOB
#defineDEBUG_UART_TX_GPIO_PIN      GPIO_PIN_5
#defineDEBUG_UART_RX_GPIO_PORT       CW_GPIOB
#defineDEBUG_UART_RX_GPIO_PIN      GPIO_PIN_6

//GPIO AF
#defineDEBUG_UART_AFTX               PB05_AFx_UART2TXD()
#defineDEBUG_UART_AFRX               PB06_AFx_UART2RXD()


static void UART_Configuration(void)
{
//    //外设时钟使能,放在外设里面自己进行使能
   
    DEBUG_UART_APBClkENx(DEBUG_UART_CLK, ENABLE);
       
       
        UART_InitTypeDef UART_InitStructure = {0};

    UART_InitStructure.UART_BaudRate = DEBUG_UART_BaudRate;
    UART_InitStructure.UART_Over = UART_Over_16;
    UART_InitStructure.UART_Source = UART_Source_PCLK;
    UART_InitStructure.UART_UclkFreq = DEBUG_UART_UclkFreq;
    UART_InitStructure.UART_StartBit = UART_StartBit_FE;
    UART_InitStructure.UART_StopBits = UART_StopBits_1;
    UART_InitStructure.UART_Parity = UART_Parity_No ;
    UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
    UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(DEBUG_UARTx, &UART_InitStructure);

}


/**
* @brief 配置GPIO
*
*/
static void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
       
        //外设时钟使能,放在外设里面自己进行使能
        SYSCTRL_AHBPeriphClk_Enable(DEBUG_UART_GPIO_CLK, ENABLE);
   
    GPIO_WritePin(DEBUG_UART_TX_GPIO_PORT, DEBUG_UART_TX_GPIO_PIN,GPIO_Pin_SET);    // 设置TXD的默认电平为高,空闲

    GPIO_InitStructure.Pins = DEBUG_UART_TX_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(DEBUG_UART_TX_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = DEBUG_UART_RX_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_Init(DEBUG_UART_RX_GPIO_PORT, &GPIO_InitStructure);

   //UART TX RX 复用
    DEBUG_UART_AFTX;
    DEBUG_UART_AFRX;
}



void UART2_Configuration(void)
{
        UART_Configuration();
        GPIO_Configuration();
}


#ifdef __GNUC__
    /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
    set to 'Yes') calls __io_putchar() */
    #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
    #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */


/**
* @brief Retargets the C library printf function to the UART.
*
*/
PUTCHAR_PROTOTYPE
{
    UART_SendData_8bit(DEBUG_UARTx, (uint8_t)ch);

    while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);

    return ch;
}

size_t __write(int handle, const unsigned char * buffer, size_t size)
{
    size_t nChars = 0;

    if (buffer == 0)
    {
      /*
         * This means that we should flush internal buffers.Since we
         * don't we just return.(Remember, "handle" == -1 means that all
         * handles should be flushed.)
         */
      return 0;
    }


    for (/* Empty */; size != 0; --size)
    {
      UART_SendData_8bit(DEBUG_UARTx, *buffer++);
      while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
      ++nChars;
    }

    return nChars;
}

/******************************************************************************
* EOF (not truncated)
******************************************************************************/
#ifdefUSE_FULL_ASSERT
/**
* @briefReports the name of the source file and the source line number
*         where the assert_param error has occurred.
* @paramfile: pointer to the source file name
* @paramline: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number,
       tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

```

- 3.编写打印测试函数

```C
static void Printf_Function(void)
{
    DEBUG_LOG("\r\n");
    DEBUG_LOG("Compile time:");
    DEBUG_LOG(__DATE__);
    DEBUG_LOG("");
    DEBUG_LOG(__TIME__);
    DEBUG_LOG("\r\n+-------------------+\r\n");
    DEBUG_LOG("%s,%s,%d,%s\r\n", __FUNCTION__,__FILE__,__LINE__,__DATE__);
    DEBUG_LOG("\r\n+-------------------+\r\n");
}
```

- 4.查看串口终端信息

使用MobaXterm终端工具查看:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114140550582.png)

- 5.注意点

为了让代码支持GNU扩展,keil设置需要注意:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114140950288.png)

同时,串口打印的时候,添加头文件`"stdio.h"`;

## GPIO口输入输出

根据板载资源,使用板载的LED来测试。前面硬件说明的时候提到,使用的引脚为PB00;就直接上代码了。

- 1.编写驱动代码

```C
#include"drv_led.h"




// 初始化 LED 引脚
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __SYSCTRL_GPIOB_CLK_ENABLE();

    GPIO_InitStruct.IT = GPIO_IT_NONE;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pins = LED_GPIO_PINS;

    GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
       
}

// 控制 LED 开关
void LED_Control(GPIO_PinState state)
{       
        GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PINS,state);
}

// 切换 LED 状态
void LED_Toggle(void) {
    GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PINS);
}

// 读取 LED 状态
int LED_Read(void) {
    return GPIO_ReadPin(LED_GPIO_PORT, LED_GPIO_PINS) == GPIO_Pin_SET ? 1 : 0;
}

// 定义并初始化 LED 操作结构体实例
LED_Ops_t myLED = {
    .init = LED_Init,
    .control = LED_Control,
    .toggle = LED_Toggle,
    .read = LED_Read
};

```

- 2.编写测试程序

```C
int32_t main(void)
{
       
        bsp_init();
        driver_init();

    while(1)
    {
                SysTickDelay(1000);
                myLED.toggle();
    }
}
```

调试下载之后,可直接观察板载LED灯是否在循环闪烁。

## 调试等级

- 1.直接上代码,调试等级头文件;

```C
#ifndef __LOG_H
#define __LOG_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#define GLOB_LOG_EVEL         LOG_DEBUG

typedef enum {
    FALSE,
    TRUE
    } status;
// 定义日志级别
typedef enum {
    LOG_DEBUG,
    LOG_INFO,
    LOG_WARNING,
    LOG_ERROR
    } LogLevel;



//extern LogMsg lmsg;
// 颜色
#define Blue    "\033[34m" // Blue
#define Green   "\033[32m" // Green
#define Yellow"\033[33m" // Yellow
#define Red   "\033[31m" // Red
#define Reset   "\033[0m"// Reset color


// 记录日志的宏定义

#define LOG_MESSAGE(format, ...) printf(":%s(),Line:%05d: " format "\r\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)

void LOG_MSG(LogLevel level, const char *message);
#endif

```

- 2.功能函数实现文件:

```C
#include "log.h"

// 日志输出函数
void LOG_MSG(LogLevel level, const char *message) {
    switch (level) {
      case LOG_DEBUG:
            printf(Blue "DEBUG: %s" Reset "\r\n", message);
            break;
      case LOG_INFO:
            printf(Green "INFO: %s" Reset "\r\n", message);
            break;
      case LOG_WARNING:
            printf(Yellow "WARNING: %s" Reset "\r\n", message);
            break;
      case LOG_ERROR:
            printf(Red "ERROR: %s" Reset "\r\n", message);
            break;
      default:
            printf("UNKNOWN: %s\n", message);
            break;
    }
}
```

- 3.编写测试函数

```C
/*宏定义错误码信息*/
static void Error_Code_Info(void)
{
        DEBUG_LOG("%d", SYSTEM_OK);
        DEBUG_LOG("%d", SYSTEM_ERR_E_1);
        DEBUG_LOG("%d", SYSTEM_ERR_E_2);
        DEBUG_LOG("%d", SYSTEM_ERR_MQTT_INFO_ERROR);


        LOG_MSG(LOG_DEBUG, "This is a debug message");
        LOG_MSG(LOG_INFO, "This is an info message");
        LOG_MSG(LOG_WARNING, "This is a warning message");
        LOG_MSG(LOG_ERROR, "This is an error message");
}
```

- 4.终端输出

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241114145804066.png)

## 串口中断

CW32单片机的串口有好几种工作方式,异步全双工,同步半双工,单线半双工,由于没有DMA通道,为了避免频繁的进入中断,采用串口接收中断,串口查询发送方式实现收发;

配置简单队列消息,实现方式如下:

- 1、定义队列结构

```C
#define myQ2_SIZE           512
#define RxBuffer2_SIZE   myQ2_SIZE

typedef volatile struct
{
    uint8_t   m_getIdx;
    uint8_t   m_putIdx;
    uint8_t   m_entry[ myQ2_SIZE ];
} myQ2;

externmyQ2   volatile RxBuffer2;
externmyQ2   volatile TxBuffer2;

voidUART2_Buffer_Init(void);
```

- 2、初始化队列结构

```C
myQ2   volatile RxBuffer2;
myQ2   volatile TxBuffer2;


voidUART2_Buffer_Init(void)
{
    CBUF_Init(RxBuffer2);
    CBUF_Init(TxBuffer2);
}

```

- 3、使能串口接收中断

```C
void NVIC_Configuration(void)
{
    //优先级,无优先级分组
    NVIC_SetPriority(DEBUG_UART_IRQ, 0);
    //UARTx中断使能
    NVIC_EnableIRQ(DEBUG_UART_IRQ);
       
       
        //使能UARTx RC中断
    UART_ITConfig(DEBUG_UARTx, UART_IT_RC, ENABLE);
        UART_ClearITPendingBit(CW_UART2, UART_IT_RC);
       
}
```

- 4、编写测试函数,实现串口功能收发

```C
int32_t main(void)
{
        bsp_init();
        driver_init();
    while(1)
    {               
                uint16_tdataLen=0;
                dataLen = CBUF_Len(RxBuffer2);
                if(dataLen!=0)
                {
                        //拷贝数据
                        memcpy((char*)TxBuffer2.m_entry,(char*)RxBuffer2.m_entry,dataLen);
                        //查询发送数据
                        UART_SendBuf_Polling(CW_UART2,TxBuffer2.m_entry,dataLen);
                       
                        USART2_Clear();
                }
                SysTickDelay(1000);
                myLED.toggle();
    }
}
```

- 5、查看串口终端收发

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241115144847070.png)

从截图可以看出,当前收发数据一致;

## 控制台Shell

下面介绍下开源项目是 letter-shell,一个功能强大的嵌入式shell,letter shell 3.x是一个C语言编写的,可以嵌入在程序中的嵌入式shell,通俗一点说就是一个串口命令行,可以通过命令行调用、运行程序中的函数。目前 letter-shell 3.0版本支持的功能有:

- 命令自动补全
- 快捷键功能定义
- 命令权限管理
- 用户管理
- 变量支持

> 项目地址:https://github.com/NevermindZZT/letter-shell

移植过程:

- 1.复制源码到工程中:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241115165344228.png)

- 2.在自定义接口`shell_port.c`中实现自己的串口读写函数

```C
#include "shell.h"
#include "main.h"
#include "bsp_uart2.h"
#include "shell_port.h"

Shell shell;
char shellBuffer;


/**
* @brief 用户shell写
*
* @param data 数据
* @param len 数据长度
*
* @return short 实际写入的数据长度
*/
short userShellWrite(char *data, unsigned short len)
{
        UART_SendBuf_Polling(CW_UART2,(uint8_t *)data, len);
    return len;
}


/**
* @brief 用户shell读
*
* @param data 数据
* @param len 数据长度
*
* @return short 实际读取到
*/
short userShellRead(char *data, unsigned short len)
{
        return UART2_GetString((uint8_t *)data, len);
}

/**
* @brief 用户shell上锁
*
* @param shell shell
*
* @return int 0
*/
int userShellLock(Shell *shell)
{
        return 0;
}

/**
* @brief 用户shell解锁
*
* @param shell shell
*
* @return int 0
*/
int userShellUnlock(Shell *shell)
{
        return 0;
}

/**
* @brief 用户shell初始化
*
*/
void userShellInit(void)
{

                //注册自己实现的写函数
    shell.write = userShellWrite;
//        shell.read = userShellRead;       
        //调用shell初始化函数
    shellInit(&shell, shellBuffer, 512);

}
```

- 3.在终端函数中定义

对于裸机环境,在主循环中调用`shellTask`,或者在接收到数据时,调用`shellHandler`,我这里在中断中调用

```C
void UART2_IRQHandler(void)
{
    /* USER CODE BEGIN */
    uint8_t TxRxBuffer;
    if (UART_GetITStatus(CW_UART2, UART_IT_RC) != RESET)
    {
                /*使用简易队列进行接收数据*/
                TxRxBuffer = UART_ReceiveData_8bit(CW_UART2);       
                shellHandler(&shell,TxRxBuffer);
      CBUF_Push(RxBuffer2, TxRxBuffer);
      UART_ClearITPendingBit(CW_UART2, UART_IT_RC);
    }
    /* USER CODE END */
}
```

- 4.调用初始化shell

```C
userShellInit();
```

- 5.串口终端实现结果

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241115170003058.png)

查看当前系统时钟:

!(https://boreyun.oss-cn-shanghai.aliyuncs.com/image-20241115170036353.png)

其他实现方式,参考官方文档说明。

lemonboard 发表于 2024-11-15 23:49

<p>QFN20 是不是太小巧可爱了啊</p>

Jacktang 发表于 2024-11-16 09:18

<p>用type-c直接供电比较方便了</p>

qzc0927 发表于 2024-11-19 11:13

Jacktang 发表于 2024-11-16 09:18
用type-c直接供电比较方便了

<p>需要注意,这个板子的电源兼容常见的3.3V和5V,有些模块在3.3V供电的要注意</p>
页: [1]
查看完整版本: CW32L010学习笔记