从零开始的CW32学习制作生活是不是有些太突然了

项目来源

协会和武汉芯源公司合作开展的对CW32进行的讲座和宣传活动,作为乙方会针对CW32系列进行教学,并最终完成一系列开源项目

最终成果

完成若干CW32的小项目,包括但不限于流水灯、温湿度传感器、OLED屏幕等外设的教学,以及单片机原理:GPIO配置、定时器、中断、I2C、SPI等等原理和使用方法。并且针对4月份的船模比赛,完成一套完成的船模控制器系统。

目前计划

逐步在bbs进行简单外设的教学,在4月前完成对协会的船模控制器的讲座和硬件制作教学

这次一定不咕咕咕 :smiling_face_with_three_hearts:

1 个赞

1.环境的安装

编译环境的搭建

  • keil的安装

  • CW32的package导入
    先把芯源官网贴出来芯源官网,在官网的产品中心找到MCU,然后打开Cortex-M0系列,并选择CW32F030C8,这里给一个传送门,在技术文档里面的固件库里面,就可以下载到CW32F030的库了

  • 给一个一步到位链接

  • 双击固件包“IdeSupport\MDK”目录下的 WHXY.CW32F030_DFP.pack 文件, 在弹出
    的界面下点击“Next”进行安装 Pack

  • 打开刚才安装好的Keil MDK-ARM 微控制器集成开发环境

  • 在左上角的file->open打开一个例程看看

  • 选择工具栏的“Option”按钮,以显示“Option”对话框,选择“Device”标签,在器件列表中选择“WHXY”目录下的相应器件(以 CW32F030C8 为例),并点击“OK”确定

  • 然后在debug框里找到Use,将下载器型号设置为CMSIS-DAP

  • 打开旁边的setting,将左边的Adapter改成你的下载器,右边的的SW Device显示当前有没有连上设备,我这里接好了下载器和芯片,所以有显示设备。没有连上的话会显示failure


    image

  • 在左上角工具栏,第一个选项是编译,第二是bulid工程,第三个红圈是下载,一般我们通过编译确定程序是否有错,没有问题后点击build构建程序,然后通过下载按钮下载到程序中
    image

  • 那环境安装教程就结束啦

  • 然后是官方教程链接

keil的安装中的百度云文件我已经上传在协会cloud中:https://cloud.scumaker.org/s/SeqJCbnjYtQMdPp

good

2.点亮一个LED灯

一直流传着这样一段话:过年了,家里的大学生一天不务正业,就知道用他的什么板各种点灯,也不知道他在干什么,反正电费哗哗地掉

运行逻辑

  • 这几天三石从杂物堆里又翻出了很多杂乱的零件,然后他发现居然有一块核心板,在这个开发板盛行的时代,找到一个核心板是多么的不容易,他把核心板的排针焊好,然后在keil装好了库文件,准备试一下点亮一个LED灯。
  • 可是过了一个多小时,他还是没能成功点亮,为什么呢?
  • 这时候他脑海中的“系统”反馈到:这用的是标准库,不是HAL库!

什么是标准库和HAL库呢?
如果你有一个开发板,那肯定第一步要配置好环境,然后找到一个标准的模板(template)进行开发,通常我们都是找到一个能用的模板,然后各种修改。所以一个空的模板,含有对应芯片型号的库函数,这样的模板工程,我们叫他标准库;后来st公司(意法半导体)自己做了一个软件,叫做stm32cubemx,使用者可以在软件里选择自己的芯片型号,并且在软件里面进行初始化,这就是HAL库

  • “总的来说,就是HAL库方便移植和初始化,标准库什么都要自己写”,系统如是回复三石。
  • 但是这个cw32只有标准库,这可难倒三石了。上一次看标准库还是两年前的事。
  • 就当是复习单片机原理了,三石这么想着
  • 然后他开始配置时钟树,什么是时钟树呢?系统回答道

单片机里面的时间就是通过时钟(RCC)产生的,时钟的产生流程有点像一个树状图,所以叫做“时钟树”,简而言之,就是你通过代码配置频率,最后完成时间的配置。因为频率是通过晶振产生的(振动频率),所以需要用代码将晶振的频率改为你想要的频率。

  • 三石又想起当时看不懂时钟树的痛苦了,但是现在他知道,只用看一个地方就好
  • 1.核心板接的外部晶振是8M,所以首先传入芯片,此时频率是HSE
  • 2.使能HSI,才能进行PLL
  • 3.通过PLL进行分频(分频可以理解为乘多少倍,如8倍,1/2倍)
  • 4.选择从PLL来源的频率
  • 5.打开HCLK,把时钟频率给系统
  • 然后三石写了一个配置时钟的程序
void RCC_Config()
{
	//使能HSI
  RCC_HSI_Enable(RCC_HSIOSC_DIV6);
	
	//设置HCLK和PCLK
	RCC_HCLKPRS_Config(RCC_HCLK_DIV8);
	RCC_PCLKPRS_Config(RCC_PCLK_DIV8);
	
	//使能PLL分频到64Mhz
	RCC_PLL_Enable(RCC_PLLSOURCE_HSI,8000000,8);
	
	//使用PLL时钟
	RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
	RCC_SystemCoreClockUpdate(64000000);
}
  • 这样就好了吧,三石想着,现在单片机的时钟就没问题了,然后需要做的就是让LED引脚知道它是LED
  • 系统又提示三石,LED也需要初始化

单片机上的GPIO(俗称引脚),需要初始化后才能使用,就是相当于你要告诉它,它的任务是什么,它才会照做,具体的步骤就是在引脚的结构体中进行初始化就行

void GPIO_Config()
{
	//初始化结构体
	GPIO_InitTypeDef GPIO_InitStruct;
	//__RCC_GPIOA_CLK_ENABLE();
	//打开GPIO_C的时钟
	__RCC_GPIOC_CLK_ENABLE();
	//中断配置为无
  GPIO_InitStruct.IT = GPIO_IT_NONE;
	//模式设置为开漏输出: 只能输出低电平,外部不接上拉电阻时,所以要想输出高电平必须要外接上拉电阻
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	//此时设置为LED的引脚
  GPIO_InitStruct.Pins = LED_GPIO_PINS;
	//速率设置为高速
  GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
	//赋值完毕后,运行初始化函数
  GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
	//初始给LED赋值为0
  GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PINS,GPIO_Pin_RESET);
}
  • OK,这下LED也初始化完成了,初始化有很多不同的参数,等到需要的时候,再去问问系统吧。然后三石开始写主函数
int32_t main(void)
{
  RCC_Config();//时钟初始化
	GPIO_Config();//GPIO初始化

    while (1)
    {
		//翻转LED
		GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PINS);
		//延时
        Delay(0XFFFF);
    }
}
  • 以上就是LED的运行逻辑

之后演示图片和源程序也会相应给出

3.串口通信

原理介绍

:robot:我是一个单片机,我要怎么才能让其他人知道我说了什么呢?

:pig:偶不知道哦

:monkey_face:你会发出声音或者比手势吗?这样别人就能知道你想干什么了嗷

:robot:这些东西对于我来说太复杂了,我希望有更简单的方式

:rooster:oh我的朋友,为什么不试试神奇的串口发送呢?

:robot:这是什么东西,好像很厉害

:rooster:好的,请容许我介绍伟大的串口通信,这是一种设备之间互相通信的方式,我们可以将它分为硬件层面和协议层面:

  • 物理层
    串口的物理层常用一种叫RS-232的标准,两个信号的 DB9 接口,通过串口信号线连接在一起,信号线是使用的 RS-232 标准传输数据信号。但是 RS-232 标准信号不能直接被控制器识别使用,所以我们需要使用一个电平转换芯片转换成 TTL 标准信号,才能实现通信。

    这就是为什么你只能通过下载器才能实现电脑和单片机之间的通信,你的下载器就相当于一个TTL功能的设备。
    image

这里左边是你的下载器,右边是你的单片机,注意连接时,RXD和TXD要交错着接线,毕竟A发送的消息,B来接收,也就对应着RXD对应TXD,很合理吧。

  • 协议层:
    传输的信号大概呈现出下图的样子:起始位,数据位,校验位,停止位


    还有一些具体的概念:
    1.波特率:异步通信之中没有时钟信号的,所以需要在两个设备之间约定好波特率(及每个码元的长度),可以理解为传输速率,但是两个设备要设置相同的波特率,否则会出现乱码。
    2.通讯的起始和停止信号:串口通讯的一个数据包从起始信号开始,到停止信号结束,起始信号由一个逻辑 0 表示,停止信号可以自定义,但也需要两个设备约定好
    3.有效数据:起始位之后就是有效数据(主体数据),一般 5 , 6 , 7 , 8 位长。
    4.数据校验:由于数据通信容易受到外部干扰而导致数据偏差,可以通过加入校验位来解决,一般来说,有奇偶校验, 01 校验,或者无校验。

  • 然后是具体的操作流程,这里就不过多介绍了

:pig:厉不厉害你鸡哥 :call_me_hand:

:robot:谢谢你,现在我正在尝试生成代码

  • 宏定义
//UARTx
#define  DEBUG_USARTx                   CW_UART1//选择串口1
#define  DEBUG_USART_CLK                RCC_APB2_PERIPH_UART1//配置串口时钟
#define  DEBUG_USART_APBClkENx          RCC_APBPeriphClk_Enable2//使能时钟
#define  DEBUG_USART_BaudRate           115200//设置波特率为115200
#define  DEBUG_USART_UclkFreq           8000000//串口传输评率

//UARTx GPIO
#define  DEBUG_USART_GPIO_CLK           RCC_AHB_PERIPH_GPIOA//设置GPIOA时钟
#define  DEBUG_USART_TX_GPIO_PORT       CW_GPIOA//设置TX管脚
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_PIN_8
#define  DEBUG_USART_RX_GPIO_PORT       CW_GPIOA//设置RX管脚
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_PIN_9

//GPIO AF
#define  DEBUG_USART_AFTX               PA08_AFx_UART1TXD()//串口1的TXD
#define  DEBUG_USART_AFRX               PA09_AFx_UART1RXD()//串口1的RXD
  • 初始化时钟配置
void RCC_Configuration(void)
{
    //SYSCLK = HSI = 8MHz = HCLK = PCLK
    RCC_HSI_Enable(RCC_HSIOSC_DIV6);

    //外设时钟使能
    RCC_AHBPeriphClk_Enable(DEBUG_USART_GPIO_CLK, ENABLE);
    DEBUG_USART_APBClkENx(DEBUG_USART_CLK, ENABLE);
}
  • 初始化GPIO
void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    //UART TX RX 复用
    DEBUG_USART_AFTX;
    DEBUG_USART_AFRX;

    GPIO_InitStructure.Pins = DEBUG_USART_TX_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = DEBUG_USART_RX_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
}
  • 串口的配置
void UART_Configuration(void)
{
    USART_InitTypeDef USART_InitStructure;

    USART_InitStructure.USART_BaudRate = DEBUG_USART_BaudRate;
    USART_InitStructure.USART_Over = USART_Over_16;
    USART_InitStructure.USART_Source = USART_Source_PCLK;//配置时钟源
    USART_InitStructure.USART_UclkFreq = DEBUG_USART_UclkFreq;
    USART_InitStructure.USART_StartBit = USART_StartBit_FE;
    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;//串口模式选择
    USART_Init(DEBUG_USARTx, &USART_InitStructure);//串口初始化
}
  • 主函数的配置
int32_t main(void)
{
    //配置RCC
    RCC_Configuration();

    //配置GPIO
    GPIO_Configuration();

    //配置UART
    UART_Configuration();

    printf("\r\nCW32F030 UART Printf Example\r\n");

    while(1)
    {
			printf("hello\n");
    }
}

在这种情况下我就可以一直说“hello”啦!

:rooster:等一等我的朋友,你忘记了一件关键的事情,那就是串口中断!

PUTCHAR_PROTOTYPE
{
    USART_SendData_8bit(DEBUG_USARTx, (uint8_t)ch);

    while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);

    return ch;
}

串口对应着一个中断,你需要配置它的功能,在这里就是反复发送一个字符串,然后把中断的标志位给清理掉,如果不清理的话,你就只能说一次“hello”了,所以一定要清理标志位哦。

:robot:程序下载中


:robot:hello,hello,hello,hello…
:pig: : :call_me_hand:
:monkey_face: : :call_me_hand:
:robot:hello,hello,hello,hello…

1 个赞