嵌入式技术基础与实践

第一章

1.1嵌入式系统常用术语

与功能模块相关的术语

封装

package

单列直插

Single-in-line Package

SIP

双列直插

Dual-in-line Package

DIP

Z字形直插式封装

Zigzag-in-line Package

ZIP

小外形封装

Small Outline Package

SOP

紧缩小外形封装

Shrink Small Outline Package

SSOP

四方扁平封装

Quad-Flat Package

QFP

塑料薄方封装

Plastic-Low-profile Quad-Flat Package

LQFP

塑料扁平组件式封装

Plastic Flat Package

PFP

插针网格阵列封装

Ceramic Pin Grid Array Package

PGA

球栅阵列封装

Ball Grid Array Package

BGA

印制电路板

Printed Circuit Board

PCB

动态可读写随机存储器

Dynamic Random Access Memory

DRAM

静态可读写随机存储器

Static Random Access Memory

SRAM

只读存储器

Read Only Memory

ROM

电可擦除

Electrically Erasable Programmable Read-Only Memory

EPROM

印刷电路板

printed circuit board

PCB

模拟量

Analog quantity

开关量

Switching quantity

与通信相关的术语

并行通信

Parallel Communication

串行通信

serial communication

串行外设接口

Serial Peripheral Interface

SPI

集成电路互联网总线

Integrated Circuit Internet Bus

ICIB

通用串行总线

Universal Serial Bus

USB

控制器局域网

Controller Area Network

CAN

边界扫描测试协议

Joint Test Action Group

JTAG

串行线调试

Serial Wire Debug

SWD

与功能模块相关的术语

通用输入输出

General Purpose I/0

GPIO

模数转换

Analog to Digital Convert

ADC

数模转换

Digital to Analog Convert

DAC

脉冲宽度调制器

Pulse Width Modulator

PWM

看门狗

Watch Dog

液晶显示

Liguid Crystal Display

LCD

发光二极管

Light Emitting Diode

LED

键盘

keyboard

1.2编译、下载与运行第一个嵌入式程序

(参照课本第4页1.1.3)

步骤1:硬件连线

步骤2:打开环境,导入工程

步骤3:编译工程

步骤4:连接GEC

步骤5:下载机器码

步骤6:观察运行结果

与1.1.1预设一致

步骤7:通过串口观察运行结果

此接口显示三色灯的状态、MCU温度、环境温度

此接口显示字符串的收发

第二章

本项目基于04-Software/ch02/CH02-1-20220118工程文件,删除了本例不需要的部分代码,使其阅读起来更简洁。(不建议随意删除代码,容易误删导致运行异常)

本例要求:

修改main.s源文件,增加以下内容


1、在第一行显示“广州大学”字样

定义需要输出的字符串,标号即为字符串首地址,\0为字符串结束标志

主函数main:下添加以下代码

编译运行后的结果如下,代码运行到 bl . 处停止,进行后续代码时需要将其注释

2、编写一个1+2+..+10的程序,将求和结果存入名为“sumresult”的内存单元中,并将求和结果用printf显示出来。

2.1数据定义

2.1.1在main.s文件中添加以下变量

定义需要使用的数据输出格式

定义需要输出的字符串

定义需要使用的变量

2.1.2在include.inc文件中添加常量

2.2编写主函数main:

在主函数下添加循环,循环内为本例主要计算代码,基本思路为:定义变量temp储存增量的数值,使其每次循环自加一;定义结果sumresualt存储累加的数值,每次循环将其与增量相加。定义常量,当循环次数达到10时,退出循环。

2.3编译、下载、运行

2.4源码

main.s
//=====================================================================
//文件名称:main.s
//功能概要:汇编编程调用GPIO构件控制小灯闪烁(利用printf输出提示信息)
//版权所有:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20180810-20191018
//=====================================================================
.include "include.inc"    //头文件中主要定义了程序中需要使用到的一些常量
//(0)数据段与代码段的定义
//(0.1)定义数据存储data段开始,实际数据存储在RAM中
.section .data
//(0.1.1)定义需要输出的字符串,标号即为字符串首地址,\0为字符串结束标志
hello_information:           //字符串标号
    .ascii "-------------------------------------------------------\n"
    .ascii "金葫芦提示:                                             \n"
    .ascii "1、本程序用来示范如何输出指令所在flash单元的地址以及指令  \n"
    .ascii "所对应的机器码。                                        \n"
    .ascii "2、Debug文件夹中的.lst文件可搜索汇编指令找到对应的机器码。\n"
    .ascii "3、printf函数可根据输出格式输出参数的值。                \n"
    .ascii "------------------------------------------------------\n\0"
    
data_format:
    .ascii "%d\n\0"                 //printf使用的数据格式控制符

data_format1:
    .ascii "%08x:%02x\n\0"                 //printf使用的数据格式控制符,其中8表示输出位数,
                                         //0表示将输出的前面补上0,直到占满指定列宽为止
                                         
show1:
	.ascii "----------------------\n"
	.ascii "广州大学\n"                //输出广州大学字样   
	.ascii "----------------------\n\0"
	
show2:
	.ascii "----------------------\n"
	.ascii "1+2+3+...+9+10=\0"    		//输出计算结果
	
show3:
	.ascii "增量:\0"	//输出增量
	
show4:
	.ascii "累计数值:\0"  //输出累计数值

//(0.1.2)定义变量

.align 4   
sumresualt:  //定义结果变量
	.word 0		//.word格式四字节对齐
	
.align 4
temp:		//定义增量变量
	.word 0

//(0.2)定义代码存储text段开始,实际代码存储在Flash中
.section   .text
.syntax unified        //指示下方指令为ARM和thumb通用格式
.thumb                 //Thumb指令集
.type main function    //声明main为函数类型                     
.global main           //将main定义成全局函数,便于芯片初始化之后调用
.align 2               //指令和数据采用2字节对齐,兼容Thumb指令集

//--------------------------------------------------------------------                        
//main.c使用的内部函数声明处
//--------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
main:
//(1)======启动部分(开头)主循环前的初始化工作======================
//(1.1)声明main函数使用的局部变量
//(1.2)【不变】关总中断
	cpsid i   
//(1.3)给主函数使用的局部变量赋初值
//(1.4)给全局变量赋初值
//(1.5)用户外设模块初始化
//  初始化蓝灯, r0、r1、r2是gpio_init的入口参数
	ldr r0,=LIGHT_BLUE     //r0指明端口和引脚(用=,因常量>=256,需用ldr)
	mov r1,#GPIO_OUTPUT    //r1指明引脚方向为输出
	mov r2,#LIGHT_OFF       //r2指明引脚的初始状态为亮
	bl  gpio_init          //调用gpio初始化函数
//  初始化串口UART_User1
	mov r0,#UART_User       //串口号
	ldr r1,=UART_BAUD       //波特率
	bl uart_init            //调用uart初始化函数
//(1.6)使能模块中断
    mov r0,#UART_User       //串口号
    bl  uart_enable_re_int  //调用uart中断使能函数
//(1.7)【不变】开总中断
	cpsie  i              
	
//显示show1定义的字符串
	ldr r0,=show1  //待显示字符串首地址,即输出“广州大学”字样
	bl printf	//调用printf显示字符串
	
//	bl .   //在此打桩(.表示当前地址)
	
//(1)======启动部分(结尾)=======================================

//(2)======主循环部分(开头)=====================================
main_loop:                      //主循环标签(开头)
		//计算算式
		ldr r2,=temp  //读取增量地址
		ldr r1,[r2]  //读取增量数值
		add r1,#1  //数值加一
		str r1,[r2]  //数值送回地址
		
		ldr r0,=show3  //显示增量
		bl printf	//打印 "增量:"
		ldr r0,=data_format  //设置数据输出格式 "%d\n\0",即将对应格式地址传入r0
		ldr r2,=temp	//读取增量地址
		ldr r1,[r2]		//读取增量数值,并将数值传入r1
		bl printf	//依据r0中的数据格式,将r1数值传入对应位置
		
		ldr r2,=temp  //读取增量地址
		ldr r1,[r2]  //读取增量数值
		ldr r4,=sumresualt  //读取结果地址
		ldr r3,[r4]		//读取结果数值
		adc r3,r1		// 结果<-结果+增量
		str r3,[r4]		//数值送回地址
		
		ldr r0,=show4  //打印 "累积数值:"
		bl printf
		ldr r0,=data_format  //设置数据输出格式"%d\n\0"
		ldr r2,=sumresualt  //读取结果地址
		ldr r1,[r2]		//读取当前结果数值
		bl printf	//输出结果
		
		ldr r2,=temp  //读取增量地址
		ldr r1,[r2]  //读取增量数值
		ldr r3,=MaxNUM  //读取增量最大值常量
		cmp r1,r3		//对比数值 r1-r3
		blO  main_loop  //未达到设定值,跳转到循环头部
		
//达到主循环次数设定值,执行下列语句

//===========主循环部分(结尾)==========================

		ldr r0,=show2  //输出 "1+2+3+...+9+10=\0"
		bl printf
		ldr r0,=data_format		//输出结果
		ldr r2,=sumresualt
		ldr r1,[r2]
		bl printf
		

.end     //整个程序结束标志(结尾)
include.inc
//=======================================================================================
//文件名称:include.inc
//功能概要:汇编程序总头文件
//版权所有:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20180810-20191022
//=======================================================================================
.include "user.inc"

//宏定义常数
.equ MaxNUM,10  //累加最大值为10(常量)

3.写出汇编语句的C语言描述

  汇编源码

main_loop:                      //主循环标签(开头)
//(2.1)主循环次数变量mMainLoopCount+1
		ldr r2,=mMainLoopCount     //r2←mMainLoopCount的地址
		ldr r1, [r2]
		add r1,#1
		str r1,[r2]	
//(2.2)未达到主循环次数设定值,继续循环
        ldr r2,=MainLoopNUM
		cmp r1,r2
		blO  main_loop     //未达到,继续循环

C语言描述

int mMainLoopCount = 0;
int MainLoopNUM = 10;
while(mMainLoopCount < MainLoopNUM)
{
	mMainLoopCount++;
}

4.补充( 汇编语言下printf的使用 )

查询本项目文件05_UserBoard/printf.h

定义变量与数据格式

数据格式为"测试用例 %d %d \n\0",变量data1赋值1,变量data2赋值2

编写printf输出示例,输出流程如下:

将数据格式的地址传入r0,即=data_format0,即 "测试用例 %d %d \n\0"

将若干所需变量依次传入r1~r7,本例有两个格式字符%d,故需要将数据传入r1,r2

(变量需要先取址,再取值;常量直接取址即可获得设定的数值)

运行结果如下

第三章

1.对照命名格式,给出所用MCU芯片型号标识所获得的信息。

本书使用的芯片型号为STM32L431RCT6,即属于32位的MCU,超低功耗型,高性能微控制器,引脚数为64,Flash大小为256KB,封装形式为64引脚LQFP封装,工作范围为-40℃~+85℃。

2.给出所用MCU芯片的RAM及Flash大小、地址范围。

STM32L4片内Flash大小为256KB,其地址范围是0x0800_0000~0x0803_FFFF,扇区大小为2KB,扇区共128个。

STM32L4片内RAM为SRAM,大小为64KB,分为SRAM1和SRAM2,其地址范围分别是0x2000_0000~0x2000_BFFF(48KB)和0x2000_C000~0x2000_FFFF(16KB),大部分编程将其连续在一起使用。

第四章

1.给出 gpio set(LIGHT RED,LIGHT OFF);语句中LIGHT RED和LIGHT OFF的值是多少?贴出每一步的查找截图。

利用右键,查看定义可以简单快捷地查询

通过工具栏中“编辑”->“查找和替换”->”文件查找/替换“可打开查找窗口,也可以进行查询

需要注意的是,本项目中,低电平对应灯亮,高电平对应灯暗

2.用直接地址编程方式,实现红绿蓝三灯轮流闪烁

查询STM32L431xx数据手册以及STM32L4xx参考手册可获得以下信息

GPIO的B的基地址

为0x48000400

RCC复位和时钟控制基地址

为0x40021000

时钟使能寄存器地址则在RCC基地址上加0x4C,即0x4002104C,将D1置1,可使GPIO的B口时钟使能,即*RCC_AHB2|=(1<<1);

GPIO的B口引脚模式寄存器地址

在B口基地址上加0x00,即0x48000400

将D19,D18设置为01,即设定9号针脚为输出模式,即蓝灯所连接的针脚

*gpio_mode &= ~(3<<18);
*gpio_mode |=(1<<18);

将D17,D16设置为01,即设定8号针脚为输出模式,即绿灯所连接的针脚

*gpio_mode &= ~(3<<16);
*gpio_mode |=(1<<16);

将D15,D14设置为01,即设定7号针脚为输出模式,即红灯所连接的针脚

*gpio_mode &= ~(3<<14);
*gpio_mode |=(1<<14);

GPIO的B口置位/复位寄存器地址

在GPIO的B口基地址上加0x18,可用以下移位语句

gpio_bsrr=gpio_ptr+6;  //置位/复位寄存器地址,+6字节,即+0x18

将D9,D8,D7设置为1,即可设置蓝灯,绿灯,红灯对应的引脚为高电平,即设置灯为“暗”

(本程序规定高电平为熄灭灯泡)

*gpio_bsrr|=(1<<9);     //设置蓝灯“暗”
*gpio_bsrr|=(1<<8);     //设置绿灯“暗”
*gpio_bsrr|=(1<<7);     //设置红灯“暗”

GPIO的B口位复位寄存器

在GPIO的B口基地址上加0x28,可用以下移位语句

gpio_brr=gpio_ptr+10;  //GPIO位复位寄存器,+10字节,即+0x28

将D9,D8,D7设置为1,即可设置蓝灯,绿灯,红灯对应的引脚为低电平,即设置灯为“亮”

(本程序规定低电平为点亮灯泡)

*gpio_brr|=(1<<9);     //设置蓝灯“亮”
*gpio_brr|=(1<<8);     //设置绿灯“亮”
*gpio_brr|=(1<<7);     //设置红灯“亮”

源代码

//====================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:2017.08, 2020.05
//功能描述:见本工程的<01_Doc>文件夹下Readme.txt文件
//====================================================================

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程见书稿)
int main(void)
{
    //(1)======启动部分(开头)==========================================
    //(1.1)声明main函数使用的局部变量
    uint32_t mMainLoopCount;  //主循环使用的记录主循环次数变量
    uint8_t  mFlag;           //主循环使用的临时变量
    uint32_t  mTag;            //轮流亮灯标志 0红 1绿 2蓝
    
    //(1.2)【不变】关总中断
    DISABLE_INTERRUPTS;

    
    //(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount = 0;     //主循环使用的记录主循环次数变量
    mFlag='A';              //主循环使用的临时变量:灯状态标志
    mTag=0;					//标志变量
    
    //(1.4)给全局变量赋初值
    
    //(1.5)用户外设模块初始化
    // B口7脚(红灯,低电平点亮)
    // B口8脚(绿灯,低电平点亮)
    // B口9脚(蓝灯,低电平点亮)
    //(1.5.1)声明变量
    volatile uint32_t* RCC_AHB2;    //GPIO的B口时钟使能寄存器地址
    volatile uint32_t* gpio_ptr;    //GPIO的B口基地址
    volatile uint32_t* gpio_mode;   //引脚模式寄存器地址=口基地址
	volatile uint32_t* gpio_bsrr;   //置位/复位寄存器地址
	volatile uint32_t* gpio_brr;    //GPIO位复位寄存器
	                                 //一个寄存器为4字节
	//(1.5.2)变量赋值
    RCC_AHB2=(uint32_t*)0x4002104C;   //GPIO的B口时钟使能寄存器地址
	gpio_ptr=(uint32_t*)0x48000400;   //GPIO的B口基地址
	gpio_mode=gpio_ptr;    //引脚模式寄存器地址=口基地址
    gpio_bsrr=gpio_ptr+6;  //置位/复位寄存器地址,+6字节,即+0x18
    gpio_brr=gpio_ptr+10;  //GPIO位复位寄存器,+10字节,即+0x28
    //(1.5.3)GPIO初始化
    //(1.5.3.1)使能相应GPIOB的时钟
    *RCC_AHB2|=(1<<1);       //GPIOB的B口时钟使能
    //(1.5.3.1)定义B口7、8、9脚为输出引脚(令D19、D18、D17、D16、D15、D14=010101)方法如下:
    *gpio_mode &= ~(3<<18);  //0b11111111111100111111111111111111; 
    *gpio_mode &= ~(3<<16);  //0b11111111111100001111111111111111;
    *gpio_mode &= ~(3<<14);  //0b11111111111100000011111111111111;
    *gpio_mode |=(1<<18);    //0b00000000000001000000000000000000;  //初始红灯为亮,即低电平
    *gpio_mode |=(1<<16);    //0b00000000000001010000000000000000;  //初始绿灯为暗,即高电平
    *gpio_mode |=(1<<14);    //0b00000000000001010100000000000000;  //初始蓝灯为亮,即低电平
    
    *gpio_bsrr|=(1<<7);  //设置红灯初始值为暗
    *gpio_bsrr|=(1<<8);  //设置绿灯初始值为暗
    *gpio_bsrr|=(1<<9);  //设置蓝灯初始值为暗
    
    //(思考:为什么这样赋值?答案见本文件末尾注①)
    
    
    
    //(1.6)使能模块中断

    //(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
    
    printf("-----------------------------------------------------\r\n"); 
    printf("金葫芦提示:直接地址方式进行GPIO输出\r\n"); 
    printf("    这个编程有点难以看懂,使用构件编程就简单多了,\r\n");
    printf("    但是构件制作要经过这一关,因此,我们把构件制作与\r\n");
    printf("    基于构件的编程分成不同过程。学习嵌入式系统,\r\n");
    printf("    以理解GPIO、UART、定时器、Flash、ADC、...\r\n");
    printf("    知识要素为出发点,学会正确运用构件进行应用编程,\r\n");
    printf("    理解和掌握2~3个简单构件的制作方法即可。\r\n");
    printf("----------------------------------------------------\r\n"); 
    
//    for(;;) {  }     //在此打桩,理解蓝色发光二极管为何亮起来了?
    
    //(1)======启动部分(结尾)==========================================
    
    //(2)======主循环部分(开头)=========================================
    for(;;)     //for(;;)(开头)
    {
        //(2.1)主循环次数+1,并判断是否小于特定常数
        mMainLoopCount++;                         //+1
        if (mMainLoopCount<=6556677)  continue;   //如果小于特定常数,继续循环
        //(2.2)主循环次数超过特定常数,灯状态进行切换(这样灯会闪烁)
        mMainLoopCount=0;      //清主循环次数
        
        //切换灯状态
		switch(mTag)
        {
        	case 0:
        	case 1:
        		if (mFlag=='A')   //若灯状态标志为'A'
        		{
        			*gpio_brr|=(1<<7);     //设置红灯“亮”
            		printf("红灯:亮\r\n");  //通过调试串口输出灯的状态
            		mFlag='L';             //改变状态标志
        		}
        		else
        		{
        			*gpio_bsrr|=(1<<7);     //设置红灯“暗”
            		printf("红灯:暗\r\n");  //通过调试串口输出灯的状态
            		mFlag='A';             //改变状态标志
            	}
        		break;
        	case 2:
        	case 3:
        		if (mFlag=='A')   //若灯状态标志为'A'
        		{
        			*gpio_brr|=(1<<8);     //设置绿灯“亮”
           	 		printf("绿灯:亮\r\n");  //通过调试串口输出灯的状态
           	 		mFlag='L';             //改变状态标志
           	 	}
           	 	else
           	 	{
           	 		*gpio_bsrr|=(1<<8);     //设置绿灯“暗”
           	 		printf("绿灯:暗\r\n");  //通过调试串口输出灯的状态
           	 		mFlag='A';             //改变状态标志
           	 	}
        		break;
        	case 4:
        	case 5:
        		if (mFlag=='A')   //若灯状态标志为'A'
        		{
        			*gpio_brr|=(1<<9);     //设置蓝灯“亮”
            		printf("蓝灯:亮\r\n");  //通过调试串口输出灯的状态
            		mFlag='L';             //改变状态标志
            	}
            	else
            	{
            		*gpio_bsrr|=(1<<9);     //设置蓝灯“暗”
            		printf("蓝灯:暗\r\n");  //通过调试串口输出灯的状态
            		mFlag='A';             //改变状态标志
            	}
        		break;
        	default:
        		break;
        }	
        
        mTag = mTag + 1;
        mTag = mTag % 6;
    }   //for(;;)结尾
    //(2)======主循环部分(结尾)========================================
}

/*
注① 这样做的目的在于更改了D19、D18两位的值,而不改变其他位的值,不这样的话,
     可能把不需要改变的位也改变了!
*/
//======以下为主函数调用的子函数存放处=====================================

//======以下为主函数调用的子函数===========================================


//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */

运行结果

3.用调用构件的方式,实现红绿蓝的八种组合轮流闪烁

引脚定义

初始化引脚

设置红灯对应引脚为输出,且初始状态为灯亮

gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);     //初始化红灯

设置绿灯对应引脚为输出,且初始状态为灯亮

gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_ON);   //初始化绿灯

设置蓝灯对应引脚为输出,且初始状态为灯亮

gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯

设定引脚状态

可以直接设置灯亮或灯灭,如下代码,将红灯点亮,将绿灯熄灭

gpio_set(LIGHT_RED,LIGHT_ON);  //点亮红灯
gpio_set(LIGHT_GREEN,LIGHT_OFF);  //熄灭绿灯

源代码

//======================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20191108-20200419
//功能描述:见本工程的..\01_Doc\Readme.txt
//移植规则:【固定】
//======================================================================
#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
	uint32_t mMainLoopCount;  //主循环次数变量
	uint8_t  mFlag;           //灯的状态标志
//	uint32_t mLightCount;     //灯的状态切换次数
	uint8_t  mTag;            //八种组合灯的标志

//(1.2)【不变】关总中断
	DISABLE_INTERRUPTS;

//(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=0;    //主循环次数变量
	mFlag='A';           //灯的状态标志
//	mLightCount=0;       //灯的闪烁次数
	mTag=0;              //初始化标志

//(1.4)给全局变量赋初值
   
//(1.5)用户外设模块初始化
	gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);     //初始化红灯
	gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_ON);   //初始化绿灯
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
   
//(1.6)使能模块中断
   
//(1.7)【不变】开总中断
	ENABLE_INTERRUPTS;
	
	printf("------------------------------------------------------\n");   
    printf("金葫芦提示:构件法输出控制小灯亮暗   \n");
    printf("    第一次用构件方法点亮的蓝色发光二极管,\n");
    printf("    这是进行应用编程的第一步,可以在此基础上,\n");
    printf("   “照葫芦画瓢”地继续学习实践。\n");
    printf("    例如:改为绿灯;调整闪烁频率等。\n");
    printf("------------------------------------------------------\n"); 
    
    //asm ("bl .");
    
//    for(;;) {  }     //在此打桩,理解蓝色发光二极管为何亮起来了?
    
        
//(1)======启动部分(结尾)==========================================

//(2)======主循环部分(开头)========================================
	for(;;)   //for(;;)(开头)
	{
//(2.1)主循环次数变量+1
        mMainLoopCount++;
//(2.2)未达到主循环次数设定值,继续循环
		if (mMainLoopCount<=12888999)  continue;
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
//(2.3.1)清除循环次数变量
		mMainLoopCount=0; 
		
		if (mFlag=='A')                    //判断灯的状态标志
		{
			mFlag='L'; 
			switch(mTag)
			{
				case 0:
					gpio_set(LIGHT_RED,LIGHT_ON);
					gpio_set(LIGHT_GREEN,LIGHT_OFF);
					gpio_set(LIGHT_BLUE,LIGHT_OFF);
					printf("红灯 亮\n");
					break;
				case 1:
					gpio_set(LIGHT_RED,LIGHT_OFF);
					gpio_set(LIGHT_GREEN,LIGHT_ON);
					gpio_set(LIGHT_BLUE,LIGHT_OFF);
					printf("绿灯 亮\n");
					break;
				case 2:
					gpio_set(LIGHT_RED,LIGHT_OFF);
					gpio_set(LIGHT_GREEN,LIGHT_OFF);
					gpio_set(LIGHT_BLUE,LIGHT_ON);
					printf("蓝灯 亮\n");
					break;
				case 3:
					gpio_set(LIGHT_RED,LIGHT_ON);
					gpio_set(LIGHT_GREEN,LIGHT_ON);
					gpio_set(LIGHT_BLUE,LIGHT_OFF);
					printf("红灯 绿灯 亮\n");
					break;
				case 4:
					gpio_set(LIGHT_RED,LIGHT_ON);
					gpio_set(LIGHT_GREEN,LIGHT_OFF);
					gpio_set(LIGHT_BLUE,LIGHT_ON);
					printf("红灯 蓝灯 亮\n");
					break;
				case 5:
					gpio_set(LIGHT_RED,LIGHT_OFF);
					gpio_set(LIGHT_GREEN,LIGHT_ON);
					gpio_set(LIGHT_BLUE,LIGHT_ON);
					printf("绿灯 蓝灯 亮\n");
					break;
				case 6:
					gpio_set(LIGHT_RED,LIGHT_ON);
					gpio_set(LIGHT_GREEN,LIGHT_ON);
					gpio_set(LIGHT_BLUE,LIGHT_ON);
					printf("红灯 绿灯 蓝灯 亮\n");
					break;
				default:
					break;
			}
		}
		else
		{
			gpio_set(LIGHT_RED,LIGHT_OFF);
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_OFF);
			printf("灭灯\n");
			
			mFlag = 'A';
			
			mTag = mTag+1;
			mTag = mTag%7;
		}
		
	}  //for(;;)结尾
//(2)======主循环部分(结尾)========================================
}   //main函数(结尾)


//======以下为主函数调用的子函数===========================================


//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */

运行结果

第六章

1、编写UART_2串口发送程序时,初始化需要设置哪些参数?

将GPIO引脚设置为复用功能UARTx_TX和UARTx_RX,同时通过传入波特率确定收发速度

2、假设速度为115200,系统时钟为72MHz,波特率寄存器BRR中的值应该是多少?

DIV_Fraction=16*0.0625=1=OXO1;//小数部分

DIV_Mantissa=39=0X27;//整数部分

USARTDIV=72000000/(115200*16)=39.0625

USARTI->BRR的值为0X0271

3、中断向量表在哪个文件中?表中有多少项?给出部分截图。

stm32L431xx.h

4、以下是中断源使能函数,假设中断源为TIM6,将函数实例化(写出各项具体数值)。

5、假设将UART_2和TIM6交换其在中断向量表中的位置和IRQ号,UART_2可以正常中断吗?

不能,每一个中断在向量表中都有一个唯一的中断号(IRQ号),这个IRQ号用于在向量表中定位对应的中断服务函数,交换IRQ号会导致当某个中断发生时,CPU无法根据中断向量表找到对应的中断服务函数并执行。

实现UART 2串口的接收程序

当收到字符时

①在电脑的输出窗口显示下一个字符,如收到A显示B,

②亮灯:收到字符G,亮绿灯;收到字符R,亮红灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。

>实现方式!

1、UART部分用直接地址方式实现

(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)。

//=====================================================================
//文件名称:isr.c(中断处理程序源文件)
//框架提供:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20170801-20191020
//功能描述:提供中断处理程序编程框架
//移植规则:【固定】
//=====================================================================

void USART2_IRQHandler(void)
{
	uint8_t ch;
	uint8_t flag;
	uint32_t t;
	
	volatile uint32_t* uart_isr=0x4000441CUL;         // UART2中断和状态寄存器基地址
	volatile uint32_t* uart_rdr=0x40004424UL;         // UART2接收数据寄存器基地址
	volatile uint32_t* uart_tdr=0x40004428UL;         // UART2发数据寄存器基地址
	
	DISABLE_INTERRUPTS;   //关总中断
	//接收一个字节的数据
	//ch=uart_re1(UART_User,&flag);  //调用接收一个字节的函数,清接收中断位
	
	for (t = 0; t < 0xFBBB; t++)//查询指定次数
	{
		//判断接收缓冲区是否满
		if (*uart_isr & (0x1UL<<5UL))
		{
			ch = *uart_rdr;    //获取数据,清接收中断位
			flag = 1;  //接收成功
			break;
		}
	}
	if(t >= 0xFBBB)
	{
		ch = 0xFF;
		flag = 0;    //未收到数据
	}
	
	
	if(flag)                       //有数据
	{
		gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);
		gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
		gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
		
		switch(ch)
		{	
			case 'R':
			gpio_set(LIGHT_RED,LIGHT_ON);
			break;
			case 'G':
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			break;
			case 'B':
			gpio_set(LIGHT_BLUE,LIGHT_ON);
			break;
			default:
			break;
		}
		
		//uart_send1(UART_User,ch+1);  //回发接收到的字节  
		
		for (t = 0; t < 0xFBBB; t++)//查询指定次数
		{
			//发送缓冲区为空则发送数据
			if ( *uart_isr & (0x1UL<<7UL) )
			{
				*uart_tdr = ch+1;
				break;
			}
		}
	}
	ENABLE_INTERRUPTS;    //开总中断
	
 }

2、用构件调用方式实现

//=====================================================================
//文件名称:isr.c(中断处理程序源文件)
//框架提供:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20170801-20191020
//功能描述:提供中断处理程序编程框架
//移植规则:【固定】
//=====================================================================

#include "includes.h"

//======================================================================
//程序名称:UART_User_Handler
//触发条件:UART_User串口收到一个字节触发
//======================================================================

void UART_User_Handler(void)
{
	//【1】声明局部变量
	uint8_t ch;
	uint8_t flag;
	uint8_t f;
    //【2】关总中断
	DISABLE_INTERRUPTS; 
	//【3】读取接到的一个字节
	ch=uart_re1(UART_User,&flag);
	//【4】根据flag判断是否真正收到一个字节的数据
	if(flag)
	{
		gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);
		gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
		gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
	
		switch(ch)
		{
			case 'R':
			gpio_set(LIGHT_RED,LIGHT_ON);
			break;
			case 'G':
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			break;
			case 'B':
			gpio_set(LIGHT_BLUE,LIGHT_ON);
			break;
			default:
			break;
		}
		uart_send1(UART_User,ch+1);
	}
	//【5】开总中断
	ENABLE_INTERRUPTS;   
 }

运行结果

第七章

1、利用SysTick定时器编写倒计时程序,如初始设置为2分30秒,每秒在屏幕上输出一次时间,倒计时为0后,红灯亮,停止屏幕输出,并关闭SysTick定时器的中断。

//=====================================================================
//文件名称:isr.c(中断处理程序源文件)
//框架提供:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20170801-20191020
//功能描述:提供中断处理程序编程框架
//=====================================================================
#include "includes.h"
//声明使用到的内部函数
//isr.c使用的内部函数声明处
void SecAdd1(uint8_t *p);

//=====================================================================
//函数名称:SYSTICK_USER_Handler(SysTick定时器中断处理程序)
//参数说明:无
//函数返回:无
//功能概要:(1)每10ms中断触发本程序一次;(2)达到一秒时,调用秒+1
//           程序,计算“时、分、秒”
//特别提示:(1)使用全局变量字节型数组gTime[3],分别存储“时、分、秒”
//          (2)注意其中静态变量的使用
//=====================================================================
void SysTick_Handler()
{
	//printf("***\n");
	static uint8_t SysTickCount = 0;
	SysTickCount++;    //Tick单元+1
	wdog_feed();      //看门狗“喂狗”
	if (SysTickCount >= 100)
	{
		SysTickCount = 0;
		SecDel1(gTime);
		printf("current time : %d 时 %d 分 %d 秒 \n",gTime[0],gTime[1],gTime[2]);
		
		if(gTime[0] <= 0 && gTime[1] <= 0 && gTime[2] <= 0)
		{
			gpio_set(LIGHT_RED,LIGHT_ON);
			gpio_set(LIGHT_BLUE,LIGHT_OFF);
			
			SysTick->CTRL=0;//禁止SysTick
			SysTick->VAL=0; //清除计数器
		}
	}
}

//===========================================================================
//函数名称:SecAdd1
//函数返回:无
//参数说明:*p:为指向一个时分秒数组p[3]
//功能概要:秒单元+1,并处理时分单元(00:00:00-23:59:59)
//===========================================================================
void SecAdd1(uint8_t *p)
{
	*(p+2)+=1;         //秒+1
	if(*(p+2)>=60)     //秒溢出
	{
		*(p+2)=0;       //清秒
		*(p+1)+=1;      //分+1
		if(*(p+1)>=60)  //分溢出
		{
			*(p+1)=0;    //清分
			*p+=1;       //时+1
			if(*p>=24)   //时溢出
			{
				*p=0;      //清时
			}
		}
	}
}

void SecDel1(uint8_t *p)
{
	

	if(*(p+2)>=1)
		*(p+2)-=1;         //秒-1
		
	if(*(p+2)<=0)     //秒为0
	{
		if(*(p+1)>=1)
		{
			*(p+1)-=1;      //分-1
			*(p+2)=60;       //借位
		}
			
		if(*(p+1)<=0)  //分为0
		{
			if(p[0]>=1)
			{
				*(p+1)=60;    //借位
				*p-=1;       //时-1
			}
			
			if(p[0]<=0)   //时为0
			{
				p[0]=0;      //归零
			}
		}
	}
}

/*
 知识要素:
 1.本文件中的中断处理函数调用的均是相关设备封装好的具体构件,在更换芯片
 时,只需保证设备的构件接口一致,即可保证本文件的相关中断处理函数不做任何
 更改,从而达到芯片无关性的要求。
 */

2、利用RTC显示日期(年月日、时分秒),每秒更新。并设置某个时间的闹钟。闹钟时间到时,屏幕上显示有你的姓名的文字,并点亮绿灯。

//======================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20191108-20200419
//功能描述:见本工程的..\01_Doc\Readme.txt
//移植规则:【固定】
//======================================================================
#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
	uint32_t mMainLoopCount;  //主循环次数变量
	uint8_t  mFlag;           //灯的状态标志
	

//(1.2)【不变】关总中断
	DISABLE_INTERRUPTS;

//(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=0;    //主循环次数变量
	mFlag='A';           //灯的状态标志
	
    
//(1.4)给全局变量赋初值
    g_RTC_Flag=0;
//(1.5)用户外设模块初始化
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);	//初始化蓝灯
	gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);	//初始化绿灯
    uart_init(UART_User,115200);
    RTC_Init();         //RTC初始化
	RTC_Set_Time(0,0,0);         //设置时间为0:0:0
    RTC_Set_Date(0,0,0,0);  //设置日期
//(1.6)使能模块中断
    RTC_PeriodWKUP_Enable_Int();                               //使能唤醒中断
    uart_enable_re_int(UART_User);
//(1.7)【不变】开总中断
	ENABLE_INTERRUPTS;
    RTC_Set_PeriodWakeUp(1);                            //配置WAKE UP中断,每秒中断一次
    
    
    printf("------------------------------------------------------\n"); 
    printf("金葫芦提示:                                           \n");
    printf(" (1)蓝灯闪烁\n");
    printf(" (2)设置日历基准时间为00/00/00 00:00:00 星期0\n");
   	printf(" (3)设置每秒唤醒中断,在中断输出MCU的相对时间\n");
   	printf(" (4)可通过User串口和RTC-测试程序C#2019改变基准时间\n");
    printf("------------------------------------------------------\n");
    
    
//(1)======启动部分(结尾)==========================================

//(2)======主循环部分(开头)========================================
	for(;;)   //for(;;)(开头)
	{
	    
//(2.1)主循环次数变量+1
        mMainLoopCount++;
//(2.2)未达到主循环次数设定值,继续循环
		if (mMainLoopCount<=12888999)  continue;
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
//(2.3.1)清除循环次数变量
		mMainLoopCount=0; 
		
		if(g_RTC_Flag==1) //根据串口接收的数据设置基准时间
		{
			g_RTC_Flag=0;
			gcRTC_Date_Time.Year=(uint8_t)((gcRTCBuf[1]-'0')*10+(gcRTCBuf[2]-'0'));
            gcRTC_Date_Time.Month=(uint8_t)((gcRTCBuf[4]-'0')*10+(gcRTCBuf[5]-'0'));
            gcRTC_Date_Time.Date=(uint8_t)((gcRTCBuf[7]-'0')*10+(gcRTCBuf[8]-'0'));
            gcRTC_Date_Time.Hours=(uint8_t)((gcRTCBuf[10]-'0')*10+(gcRTCBuf[11]-'0'));
            gcRTC_Date_Time.Minutes=(uint8_t)((gcRTCBuf[13]-'0')*10+(gcRTCBuf[14]-'0'));
            gcRTC_Date_Time.Seconds=(uint8_t)((gcRTCBuf[16]-'0')*10+(gcRTCBuf[17]-'0'));
            gcRTC_Date_Time.Weekday=(uint8_t)((gcRTCBuf[23]-'0'));   
            RTC_Set_Time(gcRTC_Date_Time.Hours,gcRTC_Date_Time.Minutes,gcRTC_Date_Time.Seconds);         //设置时间
            RTC_Set_Date(gcRTC_Date_Time.Year,gcRTC_Date_Time.Month,gcRTC_Date_Time.Date,gcRTC_Date_Time.Weekday);  //设置日期
		}
		
		if(g_RTC_Flag==2) //根据串口接收的数据设置闹钟
		{
			g_RTC_Flag=0;
			gcRTC_Date_clock.Year=(uint8_t)((gcRTCBuf[1]-'0')*10+(gcRTCBuf[2]-'0'));
            gcRTC_Date_clock.Month=(uint8_t)((gcRTCBuf[4]-'0')*10+(gcRTCBuf[5]-'0'));
            gcRTC_Date_clock.Date=(uint8_t)((gcRTCBuf[7]-'0')*10+(gcRTCBuf[8]-'0'));
            gcRTC_Date_clock.Hours=(uint8_t)((gcRTCBuf[10]-'0')*10+(gcRTCBuf[11]-'0'));
            gcRTC_Date_clock.Minutes=(uint8_t)((gcRTCBuf[13]-'0')*10+(gcRTCBuf[14]-'0'));
            gcRTC_Date_clock.Seconds=(uint8_t)((gcRTCBuf[16]-'0')*10+(gcRTCBuf[17]-'0'));
            gcRTC_Date_clock.Weekday=(uint8_t)((gcRTCBuf[23]-'0'));   
            
			char *p=NumToStr("已设置闹钟:%02d/%02d/%02d %02d:%02d:%02d 星期%d\n",gcRTC_Date_clock.Year,gcRTC_Date_clock.Month,gcRTC_Date_clock.Date,gcRTC_Date_clock.Hours,gcRTC_Date_clock.Minutes,gcRTC_Date_clock.Seconds,gcRTC_Date_clock.Weekday);
		 	uart_send_string(UART_User,p);
			printf("已设置闹钟:%02d/%02d/%02d %02d:%02d:%02d 星期%d\n",gcRTC_Date_clock.Year,gcRTC_Date_clock.Month,gcRTC_Date_clock.Date,gcRTC_Date_clock.Hours,gcRTC_Date_clock.Minutes,gcRTC_Date_clock.Seconds,gcRTC_Date_clock.Weekday);
		}
		
//(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志
		if (mFlag=='L')                    //判断灯的状态标志
		{	
			mFlag='A';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_ON);  //灯“亮”	
		}
//(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
		else
		{
			mFlag='L';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_OFF); //灯“暗”
		}
	}  //for(;;)结尾
//(2)======主循环部分(结尾)========================================
}   //main函数(结尾)


//======以下为主函数调用的子函数===========================================


//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */
//=====================================================================
//文件名称:isr.c(中断处理程序源文件)
//框架提供:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20170801-20191020
//功能描述:提供中断处理程序编程框架
//移植规则:【固定】
//=====================================================================
#include "includes.h"
void User_SysFun(uint8_t ch);
uint8_t CreateFrame(uint8_t Data,uint8_t * buffer);		//组帧函数声明

//======================================================================
//程序名称:UART_User_Handler
//触发条件:UART_User串口收到一个字节触发
//备    注:进入本程序后,可使用uart_get_re_int函数可再进行中断标志判断
//          (1-有UART接收中断,0-没有UART接收中断)
//======================================================================
void UART_User_Handler(void)
{
	 //(1)变量声明
    uint8_t flag,ch;
    DISABLE_INTERRUPTS;      //关总中断
    //(2)未触发串口接收中断,退出
    if(!uart_get_re_int(UART_User)) goto UART_User_Handler_EXIT;
    //(3)收到一个字节,读出该字节数据
    ch = uart_re1(UART_User,&flag);        //调用接收一个字节的函数
    if(!flag) goto UART_User_Handler_EXIT; //实际未收到数据,退出
  //(4)以下代码根据是否使用模板提供的User串口通信帧结构,及是否利用User串口
    //     进行带有设备序列号的进行程序更新而选择
    //(4.1)【自行组帧使用(开始)】
    if(CreateFrame(ch,gcRTCBuf))
	{   
		if(gcRTCBuf[0]=='?')
        	g_RTC_Flag=1;
        
        if(gcRTCBuf[0]=='C')
        	g_RTC_Flag=2;
        	
	}
      
       //    【自行组帧使用(结束)】
    //(4.2)【使用模板提供的User串口通信帧结构(开始)】
    /*
       User_SysFun(ch);          //利用User串口进行程序更新
       if (gcRecvLen == 0) goto UART_User_Handler_EXIT; 
       //至此,不仅收到完整帧,且序号比较也一致,可以根据命令字节gcRecvBuf[16]进行编程
       switch(gcRecvBuf[16])  //帧标识
       {
           case 1:  //0之外的数据,自身命令
           break;
           default:
           break;
        }
        gcRecvLen = 0;   //帧已经使用完毕,下次若收到一个字节,可以继续组帧
        //【使用模板提供的User串口通信帧结构(结束)】
     */
    //(5)【公共退出区】
UART_User_Handler_EXIT:
    ENABLE_INTERRUPTS;//开总中断
}

//内部函数
void User_SysFun(uint8_t ch)
{
    //(1)收到的一个字节参与组帧
    if(gcRecvLen == 0)  gcRecvLen =useremuart_frame(ch,(uint8_t*)gcRecvBuf);
    //(2)字节进入组帧后,判断gcRecvLen=0?若为0,表示组帧尚未完成,
    //     下次收到一个字节,再继续组帧
    if(gcRecvLen == 0) goto User_SysFun_Exit;
    //(3)至此,gcRecvLen≠0,表示组帧完成,gcRecvLen为帧的长度,校验序列号后(与
    //     根据Flash中倒数一扇区开始的16字节进行比较)
    //     gcRecvBuf[16]进行跳转
    if(strncmp((char *)(gcRecvBuf),(char *)((MCU_SECTOR_NUM-1)*MCU_SECTORSIZE+
       MCU_FLASH_ADDR_START),16) != 0)
    {
        gcRecvLen = 0;         //恢复接收状态
        goto User_SysFun_Exit;
    }
    //(4)至此,不仅收到完整帧,且序号比较也一致, 根据命令字节gcRecvBuf[16]进行跳转
    //若为User串口程序更新命令,则进行程序更新
    switch(gcRecvBuf[16])  //帧标识
    {
        case 0:
            SYSTEM_FUNCTION((uint8_t *)(gcRecvBuf+17));
            gcRecvLen = 0;         //恢复接收状态
        break;
        default:
        break;
    }
User_SysFun_Exit:
    return;
}

//======================================================================
//程序名称:RTC_WKUP_IRQHandler
//函数参数:无
//中断类型:RTC闹钟唤醒中断处理函数
//======================================================================
 void RTC_WKUP_IRQHandler(void)
 {
 	 uint8_t hour,min,sec;
 	 uint8_t  year,month,date,week;
 	 char *p;
	 if(RTC_PeriodWKUP_Get_Int())         //唤醒中断的标志
	 {
	 	 RTC_PeriodWKUP_Clear();           //清除唤醒中断标志
	 	 RTC_Get_Date(&year,&month,&date,&week); //获取RTC记录的日期
		 RTC_Get_Time(&hour,&min,&sec);    //获取RTC记录的时间
		 
		 p=NumToStr("%02d/%02d/%02d %02d:%02d:%02d 星期%d\n",year,month,date,hour,min,sec,week);
		 uart_send_string(UART_User,p);
		 printf("%02d/%02d/%02d %02d:%02d:%02d 星期%d\n",year,month,date,hour,min,sec,week); 
		 
		 if(hour == gcRTC_Date_clock.Hours 
		 &&  min == gcRTC_Date_clock.Minutes
		 &&  sec == gcRTC_Date_clock.Seconds
		 &&  year == gcRTC_Date_clock.Year
		 &&  month == gcRTC_Date_clock.Month
		 &&  date == gcRTC_Date_clock.Date
		 &&  week == gcRTC_Date_clock.Weekday)
		 {
		 	p=NumToStr("闹钟响起:赵子校 点亮绿灯 \n");
		 	uart_send_string(UART_User,p);
		 	printf("闹钟响起:赵子校 点亮绿灯 \n");
		 	
		 	gpio_set(LIGHT_GREEN,LIGHT_ON);
		 }
	 }
 }
//======================================================================
//程序名称:RTC_Alarm_IRQHandler
//中断类型:RTC闹钟中断处理函数
//======================================================================
void RTC_Alarm_IRQHandler(void)
{

	if(RTC_Alarm_Get_Int(A))            //闹钟A的中断标志位
	{
		RTC_Alarm_Clear(A);       //清闹钟A的中断标志位
		printf("This is ALARM_A!!!\n");
	}
	if(RTC_Alarm_Get_Int(B))            //闹钟A的中断标志位
	{
		RTC_Alarm_Clear(B);       //清闹钟A的中断标志位
		printf("This is ALARM_B!!!\n");
	}
	
}

//内部调用函数
//===========================================================================
//函数名称:CreateFrame
//功能概要:组建数据帧,将待组帧数据加入到数据帧中
//参数说明:Data:待组帧数据
//       buffer:数据帧变量
//函数返回:组帧状态    0-组帧未成功,1-组帧成功
//===========================================================================
uint8_t CreateFrame(uint8_t Data,uint8_t * buffer)
{
    static uint8_t frameLen=0;    //帧的计数器
    uint8_t frameFlag;            //组帧状态

    frameFlag=0;            //组帧状态初始化
    //根据静态变量frameLen组帧
    switch(frameLen)
    {
        case 0:    //第一个数据
        {
            if (Data=='?' || Data=='C')    //收到数据是帧头FrameHead
            {
                buffer[0]=Data;
                frameLen++;
                frameFlag=0;        //组帧开始
            }
            break;
        }
        default:    //其他情况
        {
        	
            //如果接收到的不是帧尾
            if(frameLen>=1 && Data!='!')
            {
                buffer[frameLen]=Data;
                frameLen++;
                break;
            }

            //若是末尾数据则组帧成功
            if(Data=='!')
            {
                buffer[frameLen]=Data;
            	frameFlag=1;    //组帧成功
                frameLen=0;     //计数清0,准备重新组帧
                break;
            }
        }
    }
    return frameFlag;                 //返回组帧状态
}


/*
 知识要素:
 1.本文件中的中断处理函数调用的均是相关设备封装好的具体构件,在更换芯片
 时,只需保证设备的构件接口一致,即可保证本文件的相关中断处理函数不做任何
 更改,从而达到芯片无关性的要求。
 */

修改当前时间

设置闹钟

闹钟响起

3、利用PWM脉宽调制,交替显示红灯的5个短闪和5个长闪。

//====================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:2017.08, 2020.05
//功能描述:见本工程的<01_Doc>文件夹下Readme.txt文件
//====================================================================

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件
void Delay_ms(uint16_t u16ms);
//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程见书稿)
int main(void)
{
    //(1)======启动部分(开头)==========================================
    //(1.1)声明main函数使用的局部变量
    uint8_t  mFlag;           //灯的状态标志
    uint8_t Flag;             //希望采集的电平高低标志
    double  m_duty;          //占空比
    uint32_t m_i;           //控制在未知周期内不同占空比的波形只打印有限次
    uint8_t m_K;           //确保每次能正确打印输出PWM波形
    
    //(1.2)【不变】关总中断
    DISABLE_INTERRUPTS;
    
    //(1.3)给主函数使用的局部变量赋初值
    Flag=1;
    mFlag=0;     //灯的状态标志
    //(1.4)给全局变量赋初值
    
    //(1.5)用户外设模块初始化
    gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);    //初始化蓝灯
    pwm_init(PWM_USER,1500,2000,10.0,PWM_CENTER,PWM_MINUS);   //PWM输出初始化
    
    //(1.6)使能模块中断
    
    //(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
    
    printf("------------------------------------------------------\n"); 
    printf("金葫芦提示:                                           \n");
    printf(" (1)蓝灯以不同亮暗程度交替闪烁\n");
    printf(" (2)串口输出PWM的高低电平\n");
    printf(" (3)可通过PWM-测试程序-C#2019观察波形变化\n");
    printf("------------------------------------------------------\n");
    //for(;;) {  }     //在此打桩,理解蓝色发光二极管为何亮起来了?
    
    //(1)======启动部分(结尾)==========================================
    
    //(2)======主循环部分(开头)=========================================
    m_K=0;
    m_duty=10.0;
    for(;;)     //for(;;)(开头)
    {       
        for (m_i=0;m_i<20;m_i++)            
        {
        	if(m_i<10)
        		m_duty=10.0;
        	else
        		m_duty=50.0;
        		
        	pwm_update(PWM_USER,m_duty);         //调节占空比
        		
            	m_K=0;                        //保证每次输出打印完整的PWM波,再进入下一个循环                 
            	do 
            	{
             	    mFlag=gpio_get(PWM_USER);
                	if ((mFlag==1)&&(Flag==1))
                	{
                    	printf("高电平:1\n");
                    	Flag=0;
                    	m_K++;
                    	gpio_reverse(LIGHT_RED);//小灯反转
                	}
                	else if ((mFlag==0)&&(Flag==0))
                	{	
                    	printf("低电平:0\n");
                    	Flag=1;
                    	m_K++;
                    	gpio_reverse(LIGHT_RED);
                	}
            	}
            	while (m_K<1);
            
        }
    }  //for(;;)结尾
    //(2)======主循环部分(结尾)========================================
}

//======以下为主函数调用的子函数存放处=====================================
//======================================================================
//函数名称:Delay_ms
//函数返回:无
//参数说明:无
//功能概要:延时 - 毫秒级
//======================================================================
void Delay_ms(uint16_t u16ms)
{
    uint32_t u32ctr;
    for(u32ctr = 0; u32ctr < 8000*u16ms; u32ctr++)
    {
        __ASM("NOP");
    }
}
//====================================================================
/*
知识要素:
(1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
实现对硬件的干预。
(2)本文件中标有【不变】的地方为系统保留,此类代码与具体项目无关,不宜删除。
(3)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
义全局变量,在其他文件中包含"includes.h"头文件时,
编译时会自动增加extern
*/

4、GEC39定义为输出引脚,GEC10定义为输入引脚,用杜邦线将两个引脚相连,验证捕捉实验程序Incapture-Outcmp-20211110,观察输出的时间间隔。

时间间隔逐渐缩短,蓝灯闪烁频率变快,直到闪烁5次后,恢复为初始状态,以此循环下去

第八章

在ADC模块中,显示当前温度和芯片内部温度,感受温度变化(分别用冷、热触碰)。

//======================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20191108-20200419
//功能描述:见本工程的..\01_Doc\Readme.txt
//移植规则:【固定】
//======================================================================
#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处
void Delay_ms(uint16_t u16ms);
float Regression_Ext_Temp(uint16_t tmpAD);      //环境温度AD值转为实际温度
float Regression_MCU_Temp(uint16_t mcu_temp_AD); //MCU温度AD值转为实际温度

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
    //(1)======启动部分(开头)==========================================
    //(1.1)声明main函数使用的局部变量
    uint32_t mMainLoopCount;  //主循环次数变量
    uint8_t  mFlag;           //灯的状态标志
    uint32_t mCount;			//延时的次数
    uint32_t mLightCount;     //灯的状态切换次数
    uint16_t num_AD1;	
    uint16_t num_AD2;
    uint16_t num_AD3;
    
    float temp_AD1;
    float temp_AD2;
    float temp_AD3;
    
    //(1.2)【不变】关总中断
    DISABLE_INTERRUPTS;
    
    //(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=0;    //主循环次数变量
    mFlag='A'; 
    mLightCount=0;       //灯的闪烁次数
    mCount=0;//记次数
    
    //(1.4)给全局变量赋初值
    
    //(1.5)用户外设模块初始化
    gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
    adc_init(ADC_CHANNEL_1,AD_DIFF);			    //初始化ADC通道1,
    adc_init(ADC_CHANNEL_15,AD_DIFF);			    //初始化ADC通道15:外部温度
    adc_init(ADC_CHANNEL_TEMPSENSOR,AD_SINGLE);	//初始化ADC通道17:内部温度
    
    emuart_init(UART_User,115200);
    //(1.6)使能模块中断
    uart_enable_re_int(UART_User);
    
    //(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
    
    printf("------------------------------------------------------\n"); 
    printf("金葫芦提示:                                           \n"); 
    printf("(1)目的:ADC单端输入与差分输入测试                    \n"); 
    printf("(2)单端:内部温度传感器,通道号17,无需引脚对应        \n");
    printf("     差分:GEC引脚47、46(通道1、2)                  \n");
    printf("           GEC引脚12、11(通道15、16                  \n");
    printf("(3)测试方法:单端:手摸芯片表面,A/D值增大,不要摸    \n");
    printf("                    到引脚,静电可能损坏芯片           \n");
    printf("              差分:将引脚47接地、46接3.3V,观察通道1情况\n");
    printf("                    将引脚46接地、47接3.3V,观察通道1情况\n");
    printf("             类似方法,观察通道15                      \n");
    printf("------------------------------------------------------\n"); 
     
    //(1)======启动部分(结尾)==========================================
    
    //(2)======主循环部分(开头)========================================
    for(;;)   //for(;;)(开头)
    {
        //(2.1)主循环次数变量+1
        mMainLoopCount++;
        //(2.2)未达到主循环次数设定值,继续循环
        //延时1秒
        if (mMainLoopCount<=3000000)  continue;
        //(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
        //(2.3.1)清除循环次数变量
        mMainLoopCount=0;
        //(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志
		if (mFlag=='L')                    //判断灯的状态标志
		{
			mLightCount++;  
			mFlag='A';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_ON);  //灯“亮”
			Delay_ms(1000);
		}
        //(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
		else
		{
			mFlag='L';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_OFF); //灯“暗”
			Delay_ms(1000);
		}
        num_AD1 = adc_ave(ADC_CHANNEL_1,8);
        num_AD2 = adc_ave(ADC_CHANNEL_15,8);
        num_AD3 = adc_ave(ADC_CHANNEL_TEMPSENSOR,8);
        
        temp_AD2 = Regression_Ext_Temp(num_AD2);
        temp_AD3 = Regression_MCU_Temp(num_AD3);
        
        printf("通道1(GEC47、46)的A/D值: %d\r\n",num_AD1);
        printf("通道15(GEC12、11)的A/D值:%d\r\n",num_AD2);
        printf("内部温度传感器的A/D值:%d   \r\n",num_AD3);
        printf("外部温度: %f \r\n",temp_AD2);
        printf("内部温度: %f \r\n\n",temp_AD3);
        mCount++;      
    }  //for(;;)结尾
    //(2)======主循环部分(结尾)========================================
}   //main函数(结尾)


//======以下为主函数调用的子函数===========================================
//======================================================================
//函数名称:Delay_ms
//函数返回:无
//参数说明:无
//功能概要:延时 - 毫秒级
//======================================================================
void Delay_ms(uint16_t u16ms)
{
    uint32_t u32ctr;
    for(u32ctr = 0; u32ctr < 8000*u16ms; u32ctr++)
    {
        __ASM("NOP");
    }
}

//======================================================================
//功能概要:连续判断三次GPIO的输入引脚,大部分为0,则认为有触摸
//参数说明:GPIO引脚
//函数返回:1:有触摸,0:无触摸
//原理概要:当GPIO引脚被定义为无上下拉输入功能时,容易收到外界干扰,本程序
//         把这个特性转为有用的功能,由于人体相当于一个大电阻,手触摸这个
//         引脚会使得引脚状态发生随机性改变,利用这种变化可以被视为有触摸,
//         实现了无触摸功能引脚的触摸功能
//======================================================================



//============================================================================
//函数名称:Regression_Ext_Temp
//功能概要:将读到的环境温度AD值转换为实际温度
//参数说明:tmpAD:通过adc_read函数得到的AD值
//函数返回:实际温度值
//============================================================================
float Regression_Ext_Temp(uint16_t tmpAD)
{
    float Vtemp,Rtemp,temp;
    if(tmpAD<=72)
    {
       return -274;
    }
    Vtemp = (tmpAD*3300.0)/4096;
    Rtemp = Vtemp/(3300.0 - Vtemp)*10000.0;
    temp = (1/(log(Rtemp/10000.0)/3950.0 + (1/(273.15 + 25)))) - 273.15 + 0.5; 
    return temp; 
}


//============================================================================
//函数名称:Regression_MCU_Temp
//功能概要:将读到的mcu温度AD值转换为实际温度
//参数说明:mcu_temp_AD:通过adc_read函数得到的AD值
//函数返回:实际温度值
//============================================================================
float Regression_MCU_Temp(uint16_t mcu_temp_AD)
{
	float mcu_temp_result;
	mcu_temp_result=(float)(55+(100*((float)(mcu_temp_AD) - AD_CAL1))/(AD_CAL2 - AD_CAL1));
	return mcu_temp_result;
}

//========================================================================
/*
知识要素:
(1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
实现对硬件的干预。
(2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
义全局变量,在其他文件中包含"includes.h"头文件时,
编译时会自动增加extern
*/





用实验验证,对于有数据的某扇区,如果没有擦除(Flash erase),可否写人新数据?

注:扇区号为学号 后2位,数据文本中要有姓名。

//======================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20191108-20200419
//功能描述:见本工程的..\01_Doc\Readme.txt
//移植规则:【固定】
//======================================================================
#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
	uint32_t mMainLoopCount;  //主循环次数变量
	uint8_t  mFlag;           //灯的状态标志
	uint32_t mLightCount;     //灯的状态切换次数
	uint8_t mK1[32];	  //按照逻辑读方式从指定flash区域中读取的数据
	uint8_t mK2[32];      //按照物理读方式从指定flash区域中读取的数据
    
    uint8_t flash_test[32]={'A','B','C','D','E','F','G',' ','t',
                            'o',' ','S','o','o','c','h','o','w',' ',
                            'U','n','i','v','e','r','s','i','t','y','!'};
	uint8_t result;    //判断扇区是否为空标识
//(1.2)【不变】关总中断
	DISABLE_INTERRUPTS;

//(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=0;    //主循环次数变量
	mFlag='A';           //灯的状态标志
	mLightCount=0;       //灯的闪烁次数

//(1.4)给全局变量赋初值
   
//(1.5)用户外设模块初始化
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯

//(1.6)使能模块中断
   
   
//(1.7)【不变】开总中断
	ENABLE_INTERRUPTS;


    printf("------------------------------------------------------\n"); 
    printf("金葫芦提示:                                           \n"); 
    printf("(1)目的:flash扇区读写数据测试                       \n"); 
    printf("(2)测试过程:两种读写数据方式                        \n");
    printf("     第一种:使用flash_write向50扇区写入一串字符串     \n");
    printf("     再用flash_read_logic将字符串读出,并用printf打印  \n");
    printf("     第二种:使用flash_write_physical向32扇区写入一串字符串\n");
    printf("     再用flash_read_physical将字符串读出,并用printf打印  \n");
    printf("------------------------------------------------------\n");
       
//(1)======启动部分(结尾)==========================================
    
    //擦除第50扇区
	flash_erase(50);   
	
	result = flash_isempty(50,MCU_SECTORSIZE); // 判断第50扇区是否为空
	printf("第50扇区是否为空,1表示空,0表示不空:%d\n",result);
	
    //向50扇区第0偏移地址开始写32个字节数据
    flash_write(50,0,32,(uint8_t *) "This is GZHU 赵子校!");
	flash_read_logic(mK1,50,0,32); //从50扇区读取32个字节到mK1中
	printf("逻辑读方式读取50扇区的32字节的内容:  %s\n",mK1);
	
	result = flash_isempty(50,MCU_SECTORSIZE); // 判断第50扇区是否为空
	printf("第50扇区是否为空,1表示空,0表示不空:%d\n",result);
	
	//向50扇区第0偏移地址开始写32个字节数据
    flash_write(50,0,32,(uint8_t *) "Welcome to Soochow University!");
	flash_read_logic(mK1,50,0,32); //从50扇区读取32个字节到mK1中
	printf("逻辑读方式读取50扇区的32字节的内容:  %s\n",mK1);
	
	
	
//(2)======主循环部分(开头)========================================
	for(;;)   //for(;;)(开头)
	{
//(2.1)主循环次数变量+1
        mMainLoopCount++;
//(2.2)未达到主循环次数设定值,继续循环
		if (mMainLoopCount<=12888999)  continue;
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
//(2.3.1)清除循环次数变量
		mMainLoopCount=0; 
//(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志
		if (mFlag=='L')                    //判断灯的状态标志
		{
			mLightCount++;  
			//printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='A';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_ON);  //灯“亮”
			//printf(" LIGHT_BLUE:ON--\n");   //串口输出灯的状态
		}
//(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
		else
		{
			mFlag='L';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_OFF); //灯“暗”
			//printf(" LIGHT_BLUE:OFF--\n");  //串口输出灯的状态
		}
	}  //for(;;)结尾
//(2)======主循环部分(结尾)========================================
}   //main函数(结尾)




//======以下为主函数调用的子函数===========================================


//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */

在未擦除扇区数据时不可写入数据,flash_write函数内,会自动在写入数据时,擦除旧的数据,故测试时要将其注释掉。

Flash编程原理只能将1写为0,擦除扇区就是将所有字节变为0xFF

第十章

2个或以上同学相互连接,利用CAN通信,向对方发送带有本人姓名的信息。连线方式:按基本原理性电路(不带收发器芯片)连接

开发板A所装载的程序

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
	//(1)======启动部分(开头)==========================================
	//(1.1)声明main函数使用的局部变量
	vuint32_t mMainLoopCount;  //主循环次数变量
	uint8_t  mFlag;           //灯的状态标志
	uint32_t mLightCount;     //灯的状态切换次数
	uint32_t localMsgID;
	uint32_t txMsgID;
	uint32_t BitRate;


	//(1.2)【不变】关总中断
	DISABLE_INTERRUPTS;

	//(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=0;    //主循环次数变量
	mFlag='A';           //灯的状态标志
	mLightCount=0;       //灯的闪烁次数
	localMsgID = 0x0AU;
	txMsgID = 0x0BU;
	BitRate = 36;

	//(1.4)给全局变量赋初值

	//(1.5)用户外设模块初始化
	gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
    emuart_init(UART_User,115200);
    uart_init(UART_3,115200);
    //【***CAN模块初始化***】
    can_init(CAN_1,localMsgID,BitRate);

    //(1.6)使能模块中断
    uart_enable_re_int(UART_User);
    uart_enable_re_int(UART_3);
    //【***使能CAN模块中断***】
    can_enable_recv_int(CAN_1);
    //(1.7)【不变】开总中断
	ENABLE_INTERRUPTS;

	//(1)======启动部分(结尾)==========================================

	//(2)======主循环部分(开头)========================================
	for(;;)   //for(;;)(开头)
	{
		//(2.1)主循环次数变量+1
        mMainLoopCount++;
        //(2.2)未达到主循环次数设定值,继续循环
		if (mMainLoopCount<=12889000)  continue;
		//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
		//(2.3.1)清除循环次数变量
		mMainLoopCount=0;
		//(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志
		if (mFlag=='L')                    //判断灯的状态标志
		{
			mLightCount++;
			//printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='A';                       //灯的状态标志
			gpio_set(LIGHT_RED,LIGHT_ON);  //灯“亮”
			//printf(" LIGHT_RED:ON--\n");   //串口输出灯的状态
			//【***CAN模块发送一帧数据***】
			if(can_send(CAN_1, txMsgID, 8, (uint8_t*)"I am zzx") != 0) printf("failed\r\n");
		}
		//(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
		else
		{
			mFlag='L';                       //灯的状态标志
			gpio_set(LIGHT_RED,LIGHT_OFF); //灯“暗”
			//printf(" LIGHT_RED:OFF--\n");  //串口输出灯的状态
		}
	}  //for(;;)结尾
	//(2)======主循环部分(结尾)========================================
}   //main函数(结尾)


//======以下为主函数调用的子函数===========================================
//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */

开发板B所装载的程序

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
	//(1)======启动部分(开头)==========================================
	//(1.1)声明main函数使用的局部变量
	vuint32_t mMainLoopCount;  //主循环次数变量
	uint8_t  mFlag;           //灯的状态标志
	uint32_t mLightCount;     //灯的状态切换次数
	uint32_t localMsgID;
	uint32_t txMsgID;
	uint32_t BitRate;


	//(1.2)【不变】关总中断
	DISABLE_INTERRUPTS;

	//(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=0;    //主循环次数变量
	mFlag='A';           //灯的状态标志
	mLightCount=0;       //灯的闪烁次数
	localMsgID = 0x0BU;
	txMsgID = 0x0AU;
	BitRate = 36;

	//(1.4)给全局变量赋初值

	//(1.5)用户外设模块初始化
	gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
    emuart_init(UART_User,115200);
    uart_init(UART_3,115200);
    //【***CAN模块初始化***】
    can_init(CAN_1,localMsgID,BitRate);

    //(1.6)使能模块中断
    uart_enable_re_int(UART_User);
    uart_enable_re_int(UART_3);
    //【***使能CAN模块中断***】
    can_enable_recv_int(CAN_1);
    //(1.7)【不变】开总中断
	ENABLE_INTERRUPTS;

	//(1)======启动部分(结尾)==========================================

	//(2)======主循环部分(开头)========================================
	for(;;)   //for(;;)(开头)
	{
		//(2.1)主循环次数变量+1
        mMainLoopCount++;
        //(2.2)未达到主循环次数设定值,继续循环
		if (mMainLoopCount<=12889000)  continue;
		//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
		//(2.3.1)清除循环次数变量
		mMainLoopCount=0;
		//(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志
		if (mFlag=='L')                    //判断灯的状态标志
		{
			mLightCount++;
			//printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='A';                       //灯的状态标志
			gpio_set(LIGHT_RED,LIGHT_ON);  //灯“亮”
			printf("章郑杰\r\n");   //串口输出灯的状态
			//【***CAN模块发送一帧数据***】
			if(can_send(CAN_1, txMsgID, 8, (uint8_t*)"祝佳文\r\n") != 0) printf("failed\r\n");
		}
		//(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
		else
		{
			mFlag='L';                       //灯的状态标志
			gpio_set(LIGHT_RED,LIGHT_OFF); //灯“暗”
			//printf(" LIGHT_RED:OFF--\n");  //串口输出灯的状态
		}
	}  //for(;;)结尾
	//(2)======主循环部分(结尾)========================================
}   //main函数(结尾)


//======以下为主函数调用的子函数===========================================
//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */

对于can的驱动函数文件加注释。在can(加注释).c中标了“//2024.6”的语句加以理解并写出注释。

//======================================================================
//文件名称:can.c
//功能概要:uart底层驱动构件源文件
//版权所有:苏州大学嵌入式系统与物联网研究所(sumcu.suda.edu.cn)
//更新记录:2021-02-03 V1.0  JJL
//======================================================================
#include "can.h"

CAN_TypeDef *CAN_ARR[] = {(CAN_TypeDef*)CAN1_BASE};
IRQn_Type table_irq_can[2] = {CAN1_RX0_IRQn, CAN1_RX1_IRQn};

uint8_t can_send_once(uint8_t canNo, uint32_t DestID, uint16_t len ,uint8_t* buff);
uint8_t CAN_HWInit(uint8_t CANChannel);
uint8_t CAN_SWInit_Entry(uint8_t canNo);
void CAN_SWInit_CTLMode(uint8_t canNo);
void CAN_SWInit_BT(uint8_t canNo, uint32_t CANMode, uint32_t Prescaler);
uint8_t CAN_SWInit_Quit(uint8_t canNo);
uint8_t CANFilterConfig(uint8_t canNo, uint32_t canID, uint32_t FilterBank, uint32_t Can_Rx_FifoNo, uint8_t IsActivate, uint32_t FilterMode, uint32_t FilterScale);

//=====================================================================
//函数名称:can_init
//函数返回:无
//参数说明:canNo:模块号,本芯片只有CAN_1
//		    canID:自身CAN节点的唯一标识,例如按照CANopen协议给出
//          BitRate:位速率
//功能概要:初始化CAN模块
//=====================================================================
void can_init(uint8_t canNo, uint32_t canID, uint32_t BitRate)
{
	//声明Init函数使用的局部变量
	uint32_t CANMode;
	uint32_t CANFilterBank;
	uint32_t CANFiltermode;
	uint32_t CAN_Filterscale;

	//给Init函数使用的局部变量赋初值
	CANMode = CAN_MODE_NORMAL;                //(0x00000000U)	   //正常模式
	CANFilterBank = CANFilterBank0;
	CANFiltermode = CAN_FILTERMODE_IDMASK;
	CAN_Filterscale = CAN_FILTERSCALE_32BIT;

	//(1)CAN总线硬件初始化
	CAN_HWInit(CAN_CHANNEL);
	//(2)CAN总线进入软件初始化模式
	CAN_SWInit_Entry(canNo);
	//(3)CAN总线模式设置
	CAN_SWInit_CTLMode(canNo);
	//(4)CAN总线位时序配置
	CAN_SWInit_BT(canNo,CANMode,BitRate);
	//(5)CAN总线过滤器初始化
    CANFilterConfig(canNo, canID, CANFilterBank, CAN_RX_FIFO0, 1, CANFiltermode, CAN_Filterscale);
    //(6)CAN总线退出软件初始化模式,进入正常模式
    CAN_SWInit_Quit(canNo);
}

//=====================================================================
//函数名称:can_send
//函数返回:0=正常,1=错误
//参数说明:canNo:模块号,本芯片只有CAN_1
//          DestID:目标CAN节点的唯一标识,例如按照CANopen协议给出
//          len:待发送数据的字节数
//          buff:待发送数据发送缓冲区首地址
//功能概要:CAN模块发送数据
//=====================================================================
uint8_t can_send(uint8_t canNo, uint32_t DestID, uint16_t len ,uint8_t* buff)
{
	if(DestID > 0x1FFFFFFFU) return 1;
	uint8_t send_length;
	for(int i = len; i > 0; i = i-8)
	{
		send_length = (i>8)?8:i;
		if(can_send_once(canNo,DestID,send_length,buff+len-i) == 1)   //依据DestID向目标CAN节点发送缓冲区内长度为len的数据,每次发送8字节
		{
			return 1;
		}
	}
	return 0;
}

//=====================================================================
//函数名称:can_recv
//函数返回:接收到的字节数
//参数说明:canNo:模块号,本芯片只有CAN_1
//          buff:接收到的数据存放的内存区首地址
//功能概要:在CAN模块接收中断中调用本函数接收已经到达的数据
//=====================================================================
uint8_t can_recv(uint8_t canNo, uint8_t *buff)
{
	uint8_t len;
	uint32_t RxFifo = CAN_RX_FIFO0;
	//(1)判断哪个邮箱收到了报文信息
	if(RxFifo == CAN_RX_FIFO0)
	{
		if ((CAN_ARR[canNo-1]->RF0R & CAN_RF0R_FMP0) == 0U)   //FIFO0寄存器,检查否有数据
		{
			return 1;
		}
	}
	else
	{
		if ((CAN_ARR[canNo-1]->RF1R & CAN_RF1R_FMP1) == 0U)
		{
			return 1;
		}
	}
	//(2)获取数据长度
    len = (CAN_RDT0R_DLC & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDTR) >> CAN_RDT0R_DLC_Pos;  //从RDTR获取寄存器内数据长度
    //(3)获取数据帧中的数据
    buff[0] = (uint8_t)((CAN_RDL0R_DATA0 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDLR) >> CAN_RDL0R_DATA0_Pos);
    buff[1] = (uint8_t)((CAN_RDL0R_DATA1 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDLR) >> CAN_RDL0R_DATA1_Pos);
    buff[2] = (uint8_t)((CAN_RDL0R_DATA2 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDLR) >> CAN_RDL0R_DATA2_Pos);
    buff[3] = (uint8_t)((CAN_RDL0R_DATA3 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDLR) >> CAN_RDL0R_DATA3_Pos);
    buff[4] = (uint8_t)((CAN_RDH0R_DATA4 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDHR) >> CAN_RDH0R_DATA4_Pos);
    buff[5] = (uint8_t)((CAN_RDH0R_DATA5 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDHR) >> CAN_RDH0R_DATA5_Pos);
    buff[6] = (uint8_t)((CAN_RDH0R_DATA6 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDHR) >> CAN_RDH0R_DATA6_Pos);
    buff[7] = (uint8_t)((CAN_RDH0R_DATA7 & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDHR) >> CAN_RDH0R_DATA7_Pos);
    //(4)清除标志位,等待接收下一帧数据
    if (RxFifo == CAN_RX_FIFO0)
    {
      SET_BIT(CAN_ARR[canNo-1]->RF0R, CAN_RF0R_RFOM0);  //RF0R |= CAN_RF0R_RFOM0,将RF0R清除
    }
    else
    {
      SET_BIT(CAN_ARR[canNo-1]->RF1R, CAN_RF1R_RFOM1);
    }
	return len;
}

//=====================================================================
//函数名称:CAN_enable_re_int
//函数返回:无
//参数说明:canNo:模块基地址号,Can_Rx_FifoNo:中断使用的邮箱号
//功能概要:CAN接收中断开启
//=====================================================================
void can_enable_recv_int(uint8_t canNo)
{
	uint8_t Can_Rx_FifoNo;
	Can_Rx_FifoNo = CAN_RX_FIFO0;
	if(Can_Rx_FifoNo == CAN_RX_FIFO0)
		SET_BIT(CAN_ARR[canNo-1]->IER, CAN_IER_FMPIE0);    //interrupt enable register 启用寄存器允许中断
	else
		SET_BIT(CAN_ARR[canNo-1]->IER,CAN_IER_FMPIE1);
	NVIC_EnableIRQ(table_irq_can[Can_Rx_FifoNo]);     //开中断控制器IRQ中断
}

//=====================================================================
//函数名称:can_disable_recv_int
//函数返回:无
//参数说明:canNo:模块号,本芯片只有CAN_1
//功能概要:关闭CAN接收中断
//=====================================================================
void can_disable_recv_int  (uint8_t canNo)
{
	uint8_t Can_Rx_FifoNo;
	Can_Rx_FifoNo = CAN_RX_FIFO0;
	if(Can_Rx_FifoNo == CAN_RX_FIFO0)
		CLEAR_BIT(CAN_ARR[canNo-1]->IER, CAN_IER_FMPIE0);
	else
		CLEAR_BIT(CAN_ARR[canNo-1]->IER,CAN_IER_FMPIE1);
	NVIC_DisableIRQ(table_irq_can[Can_Rx_FifoNo]);
}

//=====================================================================
//函数名称:can_send_once
//函数返回:0=正常,1=错误
//参数说明:canNo:模块号,本芯片只有CAN_1
//          DestID:目标CAN节点的唯一标识,例如按照CANopen协议给出
//          len:待发送数据的字节数
//          buff:待发送数据发送缓冲区首地址
//功能概要:CAN模块发送一次数据
//=====================================================================
uint8_t can_send_once(uint8_t canNo, uint32_t DestID, uint16_t len ,uint8_t* buff)
{
	//(1)定义Can发送函数所需要用到的变量
	uint32_t transmit_mailbox;
	uint32_t register_tsr;
	uint32_t rtr;
	rtr = CAN_RTR_DATA;
	register_tsr = READ_REG(CAN_ARR[canNo-1]->TSR);

	//(2)判断3个邮箱中是否有空闲邮箱,若有,选取其中一个进行发送,选取顺序为1,2,3
    if (((register_tsr & CAN_TSR_TME0) != 0U) ||    
        ((register_tsr & CAN_TSR_TME1) != 0U) ||
        ((register_tsr & CAN_TSR_TME2) != 0U))
    {
    	transmit_mailbox = (register_tsr & CAN_TSR_CODE) >> CAN_TSR_CODE_Pos;     //获取空邮箱个数
    	if(transmit_mailbox > 2U)
    	{
    		return 1;
    	}

    	//(2.1)判断并设置发送帧为标准帧还是扩展帧
    	if(DestID <= 0x7FFU)    //发送标准帧
    	{
    		CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_STID_Pos)|CAN_ID_STD|rtr);  //寄存器内设置为标准帧,数据帧
    	}
    	else
    	{
    		CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_EXID_Pos)|CAN_ID_EXT|rtr);  //寄存器内设置为远程帧,数据帧
    	}
    	//(2.2)设置发送帧的数据长度
    	CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TDTR = len;
        //SET_BIT(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TDTR, CAN_TDT0R_TGT);
        //(2.3)设置发送帧的数据
        WRITE_REG(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TDHR,   //逐位写入数据,寄存器的高位
                  ((uint32_t)buff[7] << CAN_TDH0R_DATA7_Pos) |
                  ((uint32_t)buff[6] << CAN_TDH0R_DATA6_Pos) |
                  ((uint32_t)buff[5] << CAN_TDH0R_DATA5_Pos) |
                  ((uint32_t)buff[4] << CAN_TDH0R_DATA4_Pos));
        WRITE_REG(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TDLR,
                  ((uint32_t)buff[3] << CAN_TDL0R_DATA3_Pos) |
                  ((uint32_t)buff[2] << CAN_TDL0R_DATA2_Pos) |
                  ((uint32_t)buff[1] << CAN_TDL0R_DATA1_Pos) |
                  ((uint32_t)buff[0] << CAN_TDL0R_DATA0_Pos));
        //(2.4)发送Can数据报
        SET_BIT(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR, CAN_TI0R_TXRQ);   //将所选邮箱内的TIR置位,请求发送数据
        return 0;
    }
    else
    {
    	return 1;
    }
}

//=====================================================================
//函数名称:CAN_HWInit
//函数返回:0=正常,1=错误
//参数说明:CANChannel:硬件引脚组号,共有3组,分别为PTA11&PTA12(CAN_CHANNEL0),PTB8&PTB9(CAN_CHANNEL1),PTD0&PTD1(2)
//功能概要:CAN模块引脚初始化
//=====================================================================
uint8_t CAN_HWInit(uint8_t CANChannel)
{
	if(CANChannel < 0 || CANChannel > 2)
	{
		return 1;
	}
	if(CANChannel == 0)
	{
		RCC->APB1ENR1 |= RCC_APB1ENR1_CAN1EN;   // 使能CAN1时钟
		RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
		GPIOA->MODER &= ~(GPIO_MODER_MODE11|GPIO_MODER_MODE12);
		GPIOA->MODER |= (GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1);  //设置引脚为复用功能模式
		GPIOA->AFR[1] &= ~(GPIO_AFRH_AFSEL11|GPIO_AFRH_AFSEL12);
		GPIOA->AFR[1] |= (GPIO_AFRH_AFSEL11_0|GPIO_AFRH_AFSEL11_3)|(GPIO_AFRH_AFSEL12_0|GPIO_AFRH_AFSEL12_3);   //设置引脚为CAN复用功能
	}
	else if(CANChannel == 1)
	{
		RCC->APB1ENR1 |= RCC_APB1ENR1_CAN1EN;
		RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN;
		GPIOB->MODER &= ~(GPIO_MODER_MODE8|GPIO_MODER_MODE9);
		GPIOB->MODER |= (GPIO_MODER_MODE8_1|GPIO_MODER_MODE9_1);
		GPIOB->AFR[1] &= ~(GPIO_AFRH_AFSEL8|GPIO_AFRH_AFSEL9);
		GPIOB->AFR[1] |= ((GPIO_AFRH_AFSEL8_0|GPIO_AFRH_AFSEL8_3)|
						  (GPIO_AFRH_AFSEL9_0|GPIO_AFRH_AFSEL9_3));
	}
	else
	{
		RCC->APB1ENR1 |= RCC_APB1ENR1_CAN1EN;
		RCC->AHB2ENR |= RCC_AHB2ENR_GPIODEN;
		GPIOD->MODER &= ~(GPIO_MODER_MODE0|GPIO_MODER_MODE1);
		GPIOD->MODER |= (GPIO_MODER_MODE0_1|GPIO_MODER_MODE1_1);
		GPIOD->AFR[0] &= ~(GPIO_AFRL_AFSEL0|GPIO_AFRL_AFSEL1);
		GPIOD->AFR[0] |= ((GPIO_AFRL_AFSEL0_0 | GPIO_AFRL_AFSEL0_3)|
						  (GPIO_AFRL_AFSEL1_0 | GPIO_AFRL_AFSEL1_3));
	}
	return 0;
}

//=====================================================================
//函数名称:CAN_SWInit_Entry
//函数返回:0=正常,1=错误
//参数说明:canNo:模块基地址号,本芯片只有CAN_1,
//功能概要:进入初始化模式
//=====================================================================
uint8_t CAN_SWInit_Entry(uint8_t canNo)
{
	int i;
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_SLEEP);    //清除CAN模块的SLEEP位,使其从休眠模式唤醒
	i = 0;
	while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_SLAK) != 0U)   //如果等待时间过长(超过0x30000次循环),则返回错误
	{
		if(i++ > 0x30000)
		{
			return 1;
		}
	}

	SET_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_INRQ);    //将使CAN模块进入初始化模式
	i = 0;
	while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_INAK) == 0U)
	{
		if(i++ > 0x30000)
		{
			return 1;
		}
	}
	return 0;
}

//=====================================================================
//函数名称:CAN_SWInit_CTLMode
//函数返回:无
//参数说明:canNo:模块基地址号,本芯片只有CAN_1,
//功能概要:CAN总线模式设置
//=====================================================================
void CAN_SWInit_CTLMode(uint8_t canNo)
{
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_TTCM);   //关闭时间触发通信模式
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_ABOM);   //关闭自动总线关闭功能
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_AWUM);   //关闭自动唤醒功能
	SET_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_NART);     //关闭自动重传功能
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_RFLM);    //关闭接收FIFO锁定功能
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_TXFP);    //清除FIFO优先级模式
}

//函数名称:CAN_SWInit_CTLMode
//函数返回:无
//参数说明:canNo:模块基地址号,本芯片只有CAN_1,
//			CANMode:CAN总线工作模式,分别为正常模式(CAN_MODE_NORMAL)、回环模式(CAN_MODE_LOOPBACK)、
//										    静默模式(CAN_MODE_SILENT)以及回环与静默组合模式(CAN_MODE_SILENT_LOOPBACK)
//功能概要:CAN总线位时序配置
void CAN_SWInit_BT(uint8_t canNo, uint32_t CANMode, uint32_t Prescaler)
{
	CAN_ARR[canNo-1]->BTR |= ((uint32_t)(Prescaler-1)|CAN_SJW_1TQ|CAN_BTR_TS1_1|CAN_BTR_TS1_0|CAN_BTR_TS2_2|CANMode);    
	//选择特定CAN模块的BTR寄存器,设置同步跳转宽度为1个时间量子,控制采样点,设置CAN的特定模式。
}

//=====================================================================
//函数名称:CAN_SWInit_Quit
//函数返回:0=正常,1=错误
//参数说明:canNo:模块基地址号
//功能概要:退出初始化模式,进入正常模式
//=====================================================================
uint8_t CAN_SWInit_Quit(uint8_t canNo)
{
	int i;
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_INRQ);    //清除CAN模块的MCR寄存器中的INRQ位,以退出初始化请求模式
	i = 0;
    while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_INAK) != 0U)    //不断检查MSR寄存器中的INAK位,直到其变为0
    {
      if (i++ > 0x30000)
      {
        return 1;
      }
    }
    return 0;
}

//=====================================================================
//函数名称: CANFilterConfig
//函数返回:0=正常,1=错误
//参数说明: canNo:模块基地址号,
//		    canID:自身CAN节点的唯一标识,例如按照CANopen协议给出
//		    Can_Rx_FifoNo:中断使用的邮箱号,
//			IsActivate:是否激活过滤器
//			CANFilterBank:CAN总线过滤器组选择,共有28个,(CANFilterBank0~CANFilterBank27)
//			CANFiltermode:CAN总线过滤器模式,分别为掩码模式(CAN_FILTERMODE_IDMASK)和列表模式(CAN_FILTERMODE_IDLIST)
//			CAN_Filterscale:CAN总线过滤器位数,分别为32位(CAN_FILTERSCALE_32BIT)和16位(CAN_FILTERSCALE_16BIT)
//功能概要:CAN接收中断开启
//=====================================================================
uint8_t CANFilterConfig(uint8_t canNo, uint32_t CanID, uint32_t FilterBank, uint32_t Can_Rx_FifoNo, uint8_t IsActivate, uint32_t FilterMode, uint32_t FilterScale)
{
	uint32_t FilterIdHigh, FilterIdLow, FilterMaskIdHigh, FilterMaskIdLow, filternbrbitpos;
	if(CanID <= 0x7FFU) CanID = CanID << CAN_TI0R_STID_Pos;    //转换为标准ID格式

	FilterIdHigh = (CanID >> 16) & 0xFFFF;    //将CAN ID拆分为高低两部分
	FilterIdLow = (CanID & 0xFFFF);    //将CAN ID拆分为高低两部分
	FilterMaskIdHigh = 0xFFE0;    //设置高位过滤器掩码
	FilterMaskIdLow = 0x0000;    //设置地位过滤器掩码
	filternbrbitpos = (uint32_t)1 << (FilterBank & 0x1FU);    //激活或禁用过滤器的位位置

	//设置过滤器初始化模式 (FINIT=1),在此模式下可以进行过滤器初始化
	SET_BIT(CAN_ARR[canNo-1]->FMR, CAN_FMR_FINIT);    //允许进行过滤器配置
	CLEAR_BIT(CAN_ARR[canNo-1]->FA1R, filternbrbitpos);    //清除对应过滤器组的激活标志
	if (FilterScale == CAN_FILTERSCALE_16BIT)
	{
	  CLEAR_BIT(CAN_ARR[canNo-1]->FS1R, filternbrbitpos);
	  CAN_ARR[canNo-1]->sFilterRegister[FilterBank].FR1 =
		((0x0000FFFFU & (uint32_t)FilterMaskIdLow) << 16U) |
		(0x0000FFFFU & (uint32_t)FilterIdLow);
	  CAN_ARR[canNo-1]->sFilterRegister[FilterBank].FR2 =
		((0x0000FFFFU & (uint32_t)FilterMaskIdHigh) << 16U) |
		(0x0000FFFFU & (uint32_t)FilterIdHigh);
	}
	if (FilterScale == CAN_FILTERSCALE_32BIT)    //过滤器大小为32位的情况
	{
	  SET_BIT(CAN_ARR[canNo-1]->FS1R, filternbrbitpos);    //激活对应过滤器组的32位模式
	  CAN_ARR[canNo-1]->sFilterRegister[FilterBank].FR1 =    //配置过滤器的高32位和低32位
		((0x0000FFFFU & (uint32_t)FilterIdHigh) << 16U) |
		(0x0000FFFFU & (uint32_t)FilterIdLow);
	  CAN_ARR[canNo-1]->sFilterRegister[FilterBank].FR2 =
		((0x0000FFFFU & (uint32_t)FilterMaskIdHigh) << 16U) |
		(0x0000FFFFU & (uint32_t)FilterMaskIdLow);
	}
	if (FilterMode == CAN_FILTERMODE_IDMASK)    //根据过滤器模式选择掩码模式或列表模式 
	{
	  CLEAR_BIT(CAN_ARR[canNo-1]->FM1R, filternbrbitpos);    // 选择掩码模式
	}
	else
	{
	  SET_BIT(CAN_ARR[canNo-1]->FM1R, filternbrbitpos);
	}
	if (Can_Rx_FifoNo == CAN_FILTER_FIFO0)     //根据接收FIFO选择配置过滤器关联的FIFO
	{
	  CLEAR_BIT(CAN_ARR[canNo-1]->FFA1R, filternbrbitpos);   // 过滤器关联到FIFO0
	}
	else
	{
	  SET_BIT(CAN_ARR[canNo-1]->FFA1R, filternbrbitpos);
	}
	if (IsActivate == 1)    // 如果需要激活过滤器
	{
	  SET_BIT(CAN_ARR[canNo-1]->FA1R, filternbrbitpos);   //激活对应过滤器组
	}
	//退出过滤器初始化模式 (FINIT=0)
	CLEAR_BIT(CAN_ARR[canNo-1]->FMR, CAN_FMR_FINIT);    //2024.6

	return 0;
}

  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于STM32嵌入式系统设计与实践光盘是一种提供STM32嵌入式系统设计学习资源的多媒体产品。该光盘中包含了相关教学视频、示例代码、开发工具以及学习资料等。 首先,该光盘通过教学视频为用户提供了系统设计的基础知识和流程,如STM32微控制器的介绍、嵌入式系统设计的原理和方法等。这些视频可以帮助初学者了解STM32的特性和使用方法,为后续的实践提供指导。 其次,光盘中的示例代码为用户提供了实践的参考和开发的基础。这些示例代码包括了各种常见的嵌入式系统设计场景,例如基本输入输出、外设操作、中断处理等。用户可以通过学习和修改这些示例代码来深入理解STM32的使用和系统设计的实践。 此外,光盘还提供了常用的STM32开发工具,如ST-LINK调试工具和Keil MDK软件等。这些工具能够帮助用户进行程序烧录、调试和性能优化等操作,提高系统设计效率和调试能力。 最后,光盘中的学习资料提供了更深入的学习资源,如STM32技术手册、应用笔记、参考设计等。用户可以通过阅读这些资料来进一步拓宽知识面,应用更高级的技术在系统设计中。 综上所述,基于STM32嵌入式系统设计与实践光盘为用户提供了丰富的学习资源,包括教学视频、示例代码、开发工具和学习资料等。这些资源可以帮助用户系统地学习和实践STM32嵌入式系统设计,提高设计能力和应用水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值