嵌入式技术基础与实践-学习笔记

目录

第一章

1.列表罗列嵌入式系统常用术语

2.运行示例程序

第二章(1)

1.理解并学习main.s汇编源文件

2.修改main.s源文件

第二章(2)

1.学习main.s源程序

2.编写示例程序:datax、datay、dataz三个数中,找出最大值并显示。

第三章

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

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

第四章

1.学习CH04示例程序,包括gpio.c和4个工程中的main.c

2.给出gpio_set(LIGHT_RED,LIGHT_OFF);语句中LIGHT_RED和LIGHT_OFF的值是多少?贴出每一步的查找截图

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

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

第六章(1)

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

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

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

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

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

第六章(2/实验二)

1.运行并理解..\04\-Software\CH06文件夹中的几个程序。

2.实现UART_2串口的接收程序,当收到字符时:①在电脑的输出窗口显示下一个字符,如收到A显示B;②亮灯:收到字符G亮绿灯;收到R亮红灯;收到B亮蓝灯;收到其他字符不亮灯。

2.1 用构件调用方式实现。

2.2 UART部分用直接地址方式实现(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)。


第一章

1.列表罗列嵌入式系统常用术语

与硬件相关的术语
中文名英文缩写英文全称
封装PKGPackage
印制电路板PCBPrinted Circuit Board
动态可读写随机存储器DRAMDynamic Random Access Memory
静态可读写随机存储器SRAMStatic Random Access Memory
只读存储器ROMRead Only Memory
闪速存储器FMFlash Memory
模拟量与开关量AS, DSAnalog Signal, Digital Signal

常见封装形式可分为两大类:
通孔封装:单列直插(SIP)、双列直插(DIP)、Z字形直插式封装(ZIP)......
贴片封装:小外形封装(SOP)、紧缩小外形封装(SSOP)、四方扁平封装(QFP)、塑料薄方封装(LQFP)、塑料扁平组件式封装(PFP)、插针网格阵列封装(PGA)、球栅阵列封装(BGA)......

与通信相关的术语
中文名英文缩写英文全称
并行通信PCParallel Communication
串行通信SCSerial Communication
串行外设接口SPISerial Peripheral Interface
集成电路互联总线I2CInter-Integrated Circuit
通用串行总线USBUniversal Serial Bus
控制器局域网CANController Area Network
边界扫描测试协议JTAGJoint Test Action Group
串行线调试技术SWDSerial Wire Debug

与功能模块相关的术语
中文名英文缩写英文全称
通用输入输出GPIOGeneral Purpose I/O
模数转换ADCAnalog to Digital Convert
数模转换DACDigital to Analog Convert
脉冲宽度调制器PWMPulse Width Modulator
看门狗WDTWatch Dog
液晶显示LCDLiquid Crystal Dispaly
发光二极管LEDLight Emitting Diode
键盘KBDKeyboard

2.运行示例程序

步骤1:硬件接线

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

步骤3:编译工程

步骤4:连接GEC。这一步刚开始检测到GEC在COM串口但是握手失败,发现是安装了老版本的环境但是下的工程文件是新版的,最后卸载了旧版环境安装了对应最新版的就成功连接上了,浪费我半天,最烦的是晚上某大学的金葫芦社区打不开,那手册又是全英文的几百页看不懂,气煞我也

步骤5:下载机器码

步骤6:观察运行结果

步骤7:通过串口观察运行情况

①把波特率设为115200并打开对应串口

②验证串口收发。

打开另一个串口,默认波特率,发送数据,启动!

(发的太快有时会显示乱码,原因是小日子排放的核污水污染了数据bushi)

第二章(1)

1.理解并学习main.s汇编源文件

写出94~101行语句的c语言描述

源代码(94-101):

//(2)======主循环部分(开头)=====================================
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语言描述:

void main_loop() {
    // (2)======主循环部分(开头)=====================================
    // 主循环开始
    while(1) {
        // (2.1)主循环次数变量mMainLoopCount+1
        mMainLoopCount += 1;
        // (2.2)未达到主循环次数设定值,继续循环
        if (mMainLoopCount >= MainLoopNUM) 
        {
            break; // 如果mMainLoopCount大于或等于MainLoopNUM,则退出循环
        }

2.修改main.s源文件

修改main.s源文件,增加以下内容:
1、在第一行显示“广州大学”字样·

hello_information:           //字符串标号
    .ascii "-------------------------------------------------------\n"
    .ascii "广州大学,庆庆庆庆庆庆庆庆庆庆庆庆庆庆庆庆庆庆\n"
    .ascii "------------------------------------------------------\n\0"

 2、编写一个1+2+..+10的程序,将求和结果存入名为“sumresult”的内存单元中,并将求和结果用printf显示出来。(注:建议在源程序中“main loop:”语句之前的地方增加结束时用“bl.“停住。printf的格式详见05 UserBoard/printf.h文件)

//修改处
sumresult:
	.word 0

mov r2, #0                      	// 将寄存器r2初始化为0,用来累计求和
    mov r0, #10                     // 将寄存器r0初始化为10,用于循环计数
p1:
    add r2, r2, r0                  // 累加r0到r2,即累计求和
    sub r0, r0, #1                  // 将r0减1
    cmp r0, #0                      // 比较r0与0
    bne p1                          // 如果不等于0,继续循环
    ldr r1, =sumresult              // 将sumresult的地址加载到r1
    str r2, [r1]                    // 将计算结果存储到sumresult指向的内存空间
    ldr r0, =data_format            // 加载十进制格式字符串的地址到r0
    ldr r1, =sumresult              // 加载sumresult的地址到r1
    ldr r1, [r1]                    // 从内存地址加载实际的计算结果到r1
    bl printf                       // 调用printf函数,r0是格式字符串的地址,r1是值
	
	
	bl .   //在此打桩(.表示当前地址),理解发光二极管为何亮起来了?

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

//(2)======主循环部分(开头)=====================================

结果

第二章(2)

1.学习main.s源程序

打开04-Software/ch02/CH02-1-20220118工程目录07 AppPrg/main.s源程序。
源程序(作业2):

//=====================================================================
//文件名称: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 "------------------------------------------------------\n\0"
data_format:
    .ascii "%d\n\0"                 //printf使用的数据格式控制符

data_format1:
    .ascii "%08x:%02x\n\0"                 //printf使用的数据格式控制符,其中8表示输出位数,
                                         //0表示将输出的前面补上0,直到占满指定列宽为止
 
light_show1:
	.ascii "LIGHT_BLUE:ON--\n\0"    //灯亮状态提示   
light_show2:
	.ascii "LIGHT_BLUE:OFF--\n\0"   //灯暗状态提示
light_show3:
	.ascii "闪烁次数mLightCount=\0"  //闪烁次数提示
//(0.1.2)定义变量
.align 4               //.word格式四字节对齐
mMainLoopCount:		   //定义主循环次数变量
	.word 0
mFlag:				   //定义灯的状态标志,1为亮,0为暗
	.byte 'A'	
.align 4
mLightCount:
    .word 0



//修改处(作业2)
sumresult:
	.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  
//显示hello_information定义的字符串
	ldr r0,=hello_information   //待显示字符串首地址
	bl  printf		            //调用printf显示字符串
	
	
	
	
//修改处(作业2)
mov r2, #0                      // 将寄存器r2初始化为0,用来累计求和
    mov r0, #10                     // 将寄存器r0初始化为10,用于循环计数
p1:
    add r2, r2, r0                  // 累加r0到r2,即累计求和
    sub r0, r0, #1                  // 将r0减1
    cmp r0, #0                      // 比较r0与0
    bne p1                          // 如果不等于0,继续循环
    ldr r1, =sumresult              // 将sumresult的地址加载到r1
    str r2, [r1]                    // 将计算结果存储到sumresult指向的内存空间
    ldr r0, =data_format            // 加载十进制格式字符串的地址到r0
    ldr r1, =sumresult              // 加载sumresult的地址到r1
    ldr r1, [r1]                    // 从内存地址加载实际的计算结果到r1
    bl printf                       // 调用printf函数,r0是格式字符串的地址,r1是值
	
	
	bl .   //在此打桩(.表示当前地址),理解发光二极管为何亮起来了?



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

//(2)======主循环部分(开头)=====================================
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     //未达到,继续循环
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理

//测试代码部分[理解机器码存储]
Label:	
	MOV  R0,#0xDE               //立即数范围为0x00~0xFF
	
	ldr r0,=data_format1        //输出格式送r0 
	ldr r1,=Label               //r1中是Label地址
	ldrb r2,[r1]                //r2中是Label地址中的数据
	bl  printf	
	
	ldr r0,=data_format1         //输出格式送r0 
	ldr r1,=Label+1              //r1中是Label+1地址
	ldrb r2,[r1]                 //r2中是Label+1地址中的数据
	bl  printf	
	
	ldr r0,=data_format1         //输出格式送r0 
	ldr r1,=Label+2              //r1中是Label+2地址
	ldrb r2,[r1]                 //r2中是Label+2地址中的数据
	bl  printf	
	
	ldr r0,=data_format1         //输出格式送r0 
	ldr r1,=Label+3              //r1中是Label+3地址
	ldrb r2,[r1]                 //r2中是Label+3地址中的数据
	bl  printf	

//(2.3.1)清除循环次数变量
		ldr r2,=mMainLoopCount     //r2←mMainLoopCount的地址
		mov r1,#0
		str r1,[r2]	
//(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志	
		//判断灯的状态标志
		ldr r2,=mFlag		   
		ldr r6,[r2]
		cmp r6,#'L'			
		bne main_light_off	   //mFlag不等于'L'转
		//mFlag等于'L'情况
		ldr r3,=mLightCount	   //灯的闪烁次数mLightCount+1
		ldr r1,[r3]
		add r1,#1				
		str r1,[r3]
		ldr r0,=light_show3   //显示“灯的闪烁次数mLightCount=”
		bl  printf				
		ldr r0,=data_format    //显示灯的闪烁次数值
		ldr r2,=mLightCount
		ldr r1,[r2]
		bl  printf	
		ldr r2,=mFlag           //灯的状态标志改为'A'
		mov r7,#'A'
		str r7,[r2]             
		ldr r0,=LIGHT_BLUE      //亮灯
		ldr r1,=LIGHT_ON
		bl  gpio_set          
		ldr r0, =light_show1    //显示灯亮提示
		bl  printf	
		//mFlag等于'L'情况处理完毕,转
		b main_exit  
//(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
main_light_off:
        ldr r2,=mFlag		   //灯的状态标志改为'L'        
		mov r7,#'L'
		str r7,[r2]   
        ldr r0,=LIGHT_BLUE      //暗灯
		ldr r1,=LIGHT_OFF
		bl  gpio_set  
        ldr r0, =light_show2    //显示灯暗提示
		bl  printf	
main_exit:
	b main_loop                 //继续循环
//(2)======主循环部分(结尾)=====================================
.end     //整个程序结束标志(结尾)

2.编写示例程序:datax、datay、dataz三个数中,找出最大值并显示。

(将该语句段放在main.s中“bl .”前位置。)

定义变量:

//(0.1.2)定义变量
.align 4               //.word格式四字节对齐
mMainLoopCount:		   //定义主循环次数变量
	.word 0
mFlag:				   //定义灯的状态标志,1为亮,0为暗
	.byte 'A'	
.align 4
mLightCount:
    .word 0


//修改处(作业2)
sumresult:
	.word 0

	
//修改处(作业3)
datax:
    .word 114514              // 定义变量datax并初始化为homo
datay:
    .word 0x8765432d1     // 定义变量datay并初始化为16进制数8765432d1
dataz:
    .word 7777777           // 定义变量dataz并初始化为7777777
datamax:
    .space 4              // 为变量datamax分配4字节的空间,用于存放三个数中的最大值
data_format5:
    .ascii "最大数字为:%d\n\0" // 定义字符串,用于printf格式化输出最大值

比大小:

//修改处(作业3)
    ldr r1,=datax          // 将x的地址加载到r1寄存器
    ldr r2,[r1]            // 将x的值加载到r2寄存器
    ldr r1,=datay          // 将y的地址加载到r1寄存器
    ldr r3,[r1]            // 将y的值加载到r3寄存器
    //r2==x,r3==y
    cmp r2,r3              // 比较r2和r3的值
    bge tag1               // 如果r2>=r3,则跳转到tag1
    mov r2,r3              // 如果r2<r3,将r3的值移动到r2,即r2现在是r2和r3中的较大者
    //让较大值始终在r2
tag1:
    ldr r1,=dataz          // 将z的地址加载到r1寄存器
    ldr r3,[r1]            // 将z的值加载到r3寄存器
    //r3==z把较小的值覆盖
    cmp r2,r3              // 再次比较,这次是r2(当前最大值)与z的值
    bge tag2               // 如果r2>=r3,跳转到tag2
    mov r2,r3              // 如果r2<r3,更新r2为r3的值,此时r2为三个数中的最大值
    //让较大值始终在r2
tag2:
    ldr r1,=datamax        // 将max的地址加载到r1寄存器
    str r2,[r1]            // 将最大值r2存储在max指定的地址
    //得到三者最大值在datamax中

    ldr r0,=data_format5   // 加载data_format5的地址到r0寄存器,r0用作printf的格式字符串
    ldr r2,=datamax        // 加载max的地址到r2寄存器
    ldr r1,[r2]            // 加载max的值到r1寄存器,r1用作printf的输出值
    bl printf              // 调用printf函数打印最大值
    b .                    // 无限循环到当前地址,常用于嵌入式系统调试,防止程序继续执行未定义行为



bl .   //在此打桩(.表示当前地址),理解发光二极管为何亮起来了?

连接板子,编译并更新串口后的结果:

(十进制结果为1985229521,跟datay:0x8765432d1不相等,啊?)

    在32位系统中,一个整数(通常是int或unsigned int类型)可以表示的最大无符号值是0xFFFFFFFF,等于十进制的4294967295。而datay值实际上是一个40位的数,超过了32位可以表示的范围。当这个值被存储到一个32位的变量中时,它会被截断,只保留最低的32位。0x8765432d1的二进制表示为1000 0111 0110 0101 0100 0011 0010 1101 0001。当它被截断为32位时,最高的8位(1000)会被丢弃,这恰好是十六进制的0x765432d1,对应的十进制值为1985229521。

第三章

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

        认识一个MCU,从了解型号含义开始,一般来说,主要包括芯片家族、产品类型、具体特性、引脚数目、Flash大小、封装类型及温度范围等

        STM32L系列芯片的命名格式为     STM32   X   AAA   Y   B   T   C

STM32系列芯片命令字段说明
字段说明取值
STM32芯片家族表示32位MCU
X产品类型F:基础型
L:超低功耗型
W:无线系统芯片
AAA具体特性
(取决于产品系列)
0xx:入门级MCU
1xx:主流MCU
2xx:高性能MCU
4xx:高性能微控制器,具有DSP和FPU指令
7xx:配备ARM-Cortex-M7内核的超高性能MCU
Y引脚数目T表示36;C表示48;R表示64;V表示100;Z表示144;B表示208;N表示216
BFlash大小8:64KB
C:256KB
E:512KB
I:2048KB
T封装类型T:LQFP封装
H:BGA封装
I:UFBGA封装

C
 
温度范围6/A: -40℃ ~ +85℃
7/B:-40℃ ~ +105℃
3/C:-40℃ ~ +125℃
D:   -40℃ ~ +150℃

        本书所使用的MCU型号为STM32L431RCT6。对照命名格式,可以从型号获得以下信息:属于32位的MCU,超低功耗型,高性能微控制器,引脚数为64,Flash大小为256KB,封装形式为64引脚LQFP封装,工作温度范围为-40℃ ~ +85℃

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

在苏州大学嵌入式社区下载的文件中找到数据手册


RAM信息:64kb的嵌入式静态RAM

Flash信息:256KB

地址范围:代码区的地址范围是0.5G,Flash的地址范围是0x0800 0000到0x0804 0000

第四章

1.学习CH04示例程序,包括gpio.c和4个工程中的main.c

(1)GPIO-ASM-STM32L431-20231129的main.s
功能:使用汇编调用GPIO实现蓝色小灯的闪烁。

(2)GPIO_BLUELIGHT-20230328的main.c
功能:c语言实现红灯常亮。

(3)GPIO-Output-Component_STM32L431_20200928的main.c
功能:利用api实现蓝灯的循环亮灭

(4)GPIO-Output-DirectAddress_STM32L431_20200928的main.c
功能:用直接操作寄存器地址的方式来控制蓝色小灯的亮灭

2.给出gpio_set(LIGHT_RED,LIGHT_OFF);语句中LIGHT_RED和LIGHT_OFF的值是多少?贴出每一步的查找截图

找到user.h文件
看到LIGHT_RED由PTB_NUM|7定义
LIGHT_OFF的值为 1

在gpio.h文件中找到了PTB_NUM定义为1<<8,综上可知LIGHT_RED的值是263

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

文件(4)/07_AppPrg/main.c

#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)给全局变量赋初值
    
    //(1.5)用户外设模块初始化
    // 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位复位寄存器
	//(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;  //置位/复位寄存器地址
    gpio_brr=gpio_ptr+10;  //GPIO位复位寄存器
    //(1.5.3)GPIO初始化
    //(1.5.3.1)使能相应GPIOB的时钟
    *RCC_AHB2|=(1<<1);       //GPIOB的B口时钟使能
    //(1.5.3.1)定义B口9脚为输出引脚(令D19、D18=01)方法如下:
    
    //这里使得B9变为了01,输出模式,亮蓝灯
    *gpio_mode &= ~(3<<18);  //0b11111111111100111111111111111111; 
    *gpio_mode |=(1<<18);    //0b00000000000001000000000000000000;

    //这里使得B8变为了01输出模式,绿灯亮
    *gpio_mode &= ~(3<<16);  //0b11111111111111001111111111111111; 
    *gpio_mode |=(1<<16);    //0b00000000000000010000000000000000;

    //这里使得B7变为了01输出模式,红灯亮
    *gpio_mode &= ~(3<<14);  //0b11111111111111110011111111111111; 
    *gpio_mode |=(1<<14);    //0b00000000000000000100000000000000;

    //(思考:为什么这样赋值?答案见本文件末尾注①)
    
    //(1.6)使能模块中断

    //(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
    
    printf("庆大学 XJD777\r\n");
    
    //for(;;) {  }     //在此打桩,理解蓝色发光二极管为何亮起来了?
    
    //(1)======启动部分(结尾)==========================================
    
    //(2)======主循环部分(开头)=========================================
for(;;)    
	{

	//(2.1)主循环次数+1,并判断是否小于特定常数
	mMainLoopCount++;                         //+1
	if (mMainLoopCount<=6556677)  continue;   //如果小于特定常数,继续循环
	 //(2.2)主循环次数超过特定常数,灯状态进行切换(这样灯会闪烁)
	mMainLoopCount=0;      //清主循环次数
//红灯
	//切换灯状态
	*gpio_brr|=(1<<7);     //设置灯“亮”
	printf("红灯亮\r\n");  //通过调试串口输出灯的状态

	for(int i=0;i<10000000;i++){}
	*gpio_bsrr|=(1<<7);     //设置灯“暗”
	printf("红灯灭\r\n");   //通过调试串口输出灯的状态

	for(int i=0;i<10000000;i++){}
 //绿灯
	 *gpio_brr|=(1<<8);     //设置灯“亮”
	printf("绿灯亮\r\n");  //通过调试串口输出灯的状态
	for(int i=0;i<10000000;i++){}
	*gpio_bsrr|=(1<<8);     //设置灯“暗”
	printf("绿灯灭\r\n");   //通过调试串口输出灯的状态
	for(int i=0;i<10000000;i++){}
//蓝灯
	 *gpio_brr|=(1<<9);     //设置灯“亮”
	printf("蓝灯亮\r\n");  //通过调试串口输出灯的状态
	for(int i=0;i<10000000;i++){}
	*gpio_bsrr|=(1<<9);     //设置灯“暗”
	printf("蓝灯灭\r\n");   //通过调试串口输出灯的状态
	for(int i=0;i<10000000;i++){}
	}
                  
    //(2)======主循环部分(结尾)========================================
}

结果 

三灯闪烁

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

文件(3)/07_AppPrg/main.c

#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  groupFlag;
//(1.2)【不变】关总中断
	DISABLE_INTERRUPTS;

//(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=1;    //主循环次数变量
	//mFlag='A';           //灯的状态标志
	mLightCount=0;       //闪烁循环次数
	
	groupFlag = 0;		 //8种组合闪烁的标志,0~7分别对应代表暗,红,绿,黄,蓝,紫,青,白
//(1.4)给全局变量赋初值
   
//(1.5)用户外设模块初始化
	//gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
	
	//初始化三盏灯,但初始状态为黑
	gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);	//红灯
	gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);	//绿灯
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);	//蓝灯

   
//(1.6)使能模块中断
    
   
//(1.7)【不变】开总中断
	ENABLE_INTERRUPTS;
	
    
    printf("-------------------------------\n");
    printf("庆XJD777 调用构件八灯闪烁\n");
    printf("-------------------------------\n");

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

//(2)======主循环部分(开头)========================================
	for(;;)
	{
	
//(2.1)主循环次数变量+1
        mMainLoopCount++;
//(2.2)未达到主循环次数设定值,继续循环
		if (mMainLoopCount<=12888999) continue;
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
//(2.3.1)清除循环次数变量
		mMainLoopCount=0; 
	

	
		if(groupFlag == 0)	//暗
		{
			mLightCount++;
			gpio_set(LIGHT_RED,LIGHT_OFF);
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_OFF);
			printf("当前循环次数:%d\n",mLightCount);
			printf("暗灯\n");
			groupFlag = 1;
			continue;
		}

		if(groupFlag == 1)	//红
		{
			gpio_set(LIGHT_RED,LIGHT_ON);
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_OFF);
			printf("红灯\n");
			groupFlag = 2;
			continue;
		}

		if(groupFlag == 2)	//绿
		{
			gpio_set(LIGHT_RED,LIGHT_OFF);
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			gpio_set(LIGHT_BLUE,LIGHT_OFF);
			printf("绿灯\n");
			groupFlag = 3;
			continue;
		}

		if(groupFlag == 3)	//黄=红+绿
		{
			gpio_set(LIGHT_RED,LIGHT_ON);
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			gpio_set(LIGHT_BLUE,LIGHT_OFF);
			printf("黄灯\n");
			groupFlag = 4;
			continue;
		}

		if(groupFlag == 4)	//蓝
		{
			gpio_set(LIGHT_RED,LIGHT_OFF);
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_ON);
			printf("蓝灯\n");
			groupFlag = 5;
			continue;
		}

		if(groupFlag == 5)	//紫=红+蓝
		{
			gpio_set(LIGHT_RED,LIGHT_ON);
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_ON);
			printf("紫灯\n");
			groupFlag = 6;
			continue;
		}

		if(groupFlag == 6)	//青=蓝+绿
		{
			gpio_set(LIGHT_RED,LIGHT_OFF);
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			gpio_set(LIGHT_BLUE,LIGHT_ON);
			printf("青灯\n");
			groupFlag = 7;
			continue;
		}

		if(groupFlag == 7)	//白=红+蓝+绿
		{
			gpio_set(LIGHT_RED,LIGHT_ON);
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			gpio_set(LIGHT_BLUE,LIGHT_ON);
			printf("白灯\n");
			groupFlag = 0;
			continue;
		}
	
	}

结果

八灯闪烁

第六章(1)

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

时钟使能寄存器地址:
RCC_APB1: UART的时钟使能寄存器地址,用于使能UART时钟。
RCC_AHB2: GPIO的A口时钟使能寄存器地址,用于使能GPIOA的时钟。
端口基地址
gpio_ptr: GPIOA端口的基地址,用于配置GPIO端口的相关设置。
uart_ptr: UART2端口的基地址,用于配置UART相关寄存器。
引脚模式寄存器地址和复用功能寄存器地址
gpio_mode: GPIO引脚模式寄存器地址,用于配置引脚模式。
gpio_afrl: GPIO复用功能低位寄存器,用于配置引脚的复用功能。
UART相关寄存器地址
uart_cr1: UART控制寄存器1基地址,用于配置UART的工作模式和使能相关功能。
uart_brr: UART波特率寄存器地址,用于配置波特率。
uart_isr: UART中断和状态寄存器基地址,用于配置中断相关设置。
uart_cr2: UART控制寄存器2基地址,用于配置UART的工作模式。
uart_cr3: UART控制寄存器3基地址,用于配置UART的工作模式。
波特率设置参数
根据波特率计算得到的USARTDIV值,写入到UART波特率寄存器(uart_brr)中。
其他设置
清除相应的标志位,如中断状态寄存器中的标志位。
禁用相应的功能,如关闭UART功能、关闭发送和接收功能。
使能UART功能
启用UART发送和接收功能。
最后开启UART功能使能位,使UART开始正常工作。

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

USART BRR寄存器(中的USARTDIV值用来计算串口的通信速率。假设USART CR1中第15位“过采样”模式为:

过采样因子为8:USARTDIV = 2*72MHz / 115200 = 1250
过采样因子为16:USARTDIV = 72MHz / 115200 = 625

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

中断向量表在\03_MCU\startup\startup_stm32|431rctx.s中。表中有98项。前16项为内核中断,后面的为非内核中断

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

__STATIC INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
    if ((int32 t)(IRQn) >= 0)
    {
    NVIC -> ISER [(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32 t)IRQn) & 0x1FUL));
    }
}

实例化:
找到TIM6的中断号为54

54/32=1    54%32=22, 将ISER[1]的第22位设置为1

//函数内部实现将IRQ号值右移5位,54>>5=1,索引值为1
ISER[(((uint32_t)IRQn) >> 5UL)]	
//54 & 0x1F=22,第22位
(uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL))

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

可以正常中断,因为中断的处理逻辑还依赖于硬件中断控制器(NVIC)的配置以及中断服务例程(ISR)的正确实现。


 

第六章(2/实验二)

1.运行并理解..\04\-Software\CH06文件夹中的几个程序。

UART-STM32L431-ADDR-20210103:
通过直接操作寄存器实现GPIO和UART的初始化和配置。通过UART发送数据,并在循环中不断计数和打印发送次数。

UART-STM32L431-ISR-20210109:使用函数封装方式初始化硬件,并通过计数器控制LED灯的状态。
开发板蓝灯闪烁。

UART-STM32L431-Sent-20210103:结合了LED状态控制和UART通信,通过计数器和状态反转实现LED灯的控制,并增加了串口数据发送和长浮点数输出。
每次蓝灯亮和暗都会发送一次数据。

2.实现UART_2串口的接收程序,当收到字符时:
①在电脑的输出窗口显示下一个字符,如收到A显示B;
②亮灯:收到字符G亮绿灯;收到R亮红灯;收到B亮蓝灯;收到其他字符不亮灯。

2.1 用构件调用方式实现。

isr.c部分代码

void USART2_IRQHandler(void)
{
	uint8_t ch;
	uint8_t flag;
	
	DISABLE_INTERRUPTS;   //关总中断

	//接收一个字节的数据
	ch = uart_re1(UART_User,&flag);  //调用接收一个字节的函数,清接收中断位
	if(flag)	//有数据
	{
		if((ch == 'R')||(ch == 'r'))			//打开红灯(开灯函数为金葫芦编写)
		{
		    gpio_set(LIGHT_GREEN,LIGHT_OFF);	//关闭绿灯
		    gpio_set(LIGHT_BLUE,LIGHT_OFF);	//关闭蓝灯
		    gpio_set(LIGHT_RED,LIGHT_ON);		//打开红灯
		}
		else if((ch == 'G')||(ch == 'g'))		//打开绿灯
		{
		    gpio_set(LIGHT_BLUE,LIGHT_OFF);	//关闭蓝灯
		    gpio_set(LIGHT_RED,LIGHT_OFF);		//关闭红灯
		    gpio_set(LIGHT_GREEN,LIGHT_ON);	//打开绿灯
		}
		else if((ch == 'B')||(ch == 'b'))		//打开蓝灯
		{
		    gpio_set(LIGHT_RED,LIGHT_OFF);		//关闭红灯
		    gpio_set(LIGHT_GREEN,LIGHT_OFF);	//关闭绿灯
		    gpio_set(LIGHT_BLUE,LIGHT_ON);		//打开蓝灯
		}
		uart_send1(UART_User,ch + 1);  //回发接收到的字节
	}

	ENABLE_INTERRUPTS;	//开总中断
}

显示下一个字符:

输入RGB亮灯:


2.2 UART部分用直接地址方式实现(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)。

isr.c部分代码

//USART2中断服务函数
void USART2_IRQHandler(void)
{
	volatile uint32_t* usart2 = (volatile uint32_t*)0x40004400UL;		//usart2寄存器基地址
	volatile uint32_t* usart2_cr1 = usart2;		//usart2控制寄存器1基地址
	volatile uint32_t* usart2_isr = usart2 + 7;	//usart2状态寄存器基地址
	volatile uint32_t* usart2_rdr = usart2 + 9;	//usart2接收数据寄存器基地址
	volatile uint32_t* usart2_tdr = usart2 + 10;	//usart2发送数据寄存器基地址
	volatile uint32_t* nvic_icer = (volatile uint32_t*)0xE000E180UL;	//nvic中断清除使能寄存器基地址
	volatile uint32_t* nvic_icpr = (volatile uint32_t*)0xE000E280UL;	//nvic中断清除挂起寄存器
	uint8_t data;
	
	DISABLE_INTERRUPTS;   //关总中断
	if((*usart2_cr1)&(0x1UL<<5U))	//判断是否使能接收缓冲区非空中断
	{
		for (uint32_t i = 0; i < 0xFFFF; ++i)//查询指定次数
		{
			if((*usart2_isr)&(0x1UL<<5U))	//判断读取数据寄存器是否非空
			{
				data = *usart2_rdr;			//读取接收寄存器
				if((data == 'R')||(data == 'r'))		//打开红灯(开灯函数为金葫芦编写)
				{
				    gpio_set(LIGHT_GREEN,LIGHT_OFF);	//关闭绿灯
				    gpio_set(LIGHT_BLUE,LIGHT_OFF);	//关闭蓝灯
				    gpio_set(LIGHT_RED,LIGHT_ON);		//打开红灯
				}
				else if((data == 'G')||(data == 'g'))	//打开绿灯
				{
				    gpio_set(LIGHT_BLUE,LIGHT_OFF);	//关闭蓝灯
				    gpio_set(LIGHT_RED,LIGHT_OFF);		//关闭红灯
				    gpio_set(LIGHT_GREEN,LIGHT_ON);	//打开绿灯
				}
				else if((data == 'B')||(data == 'b'))	//打开蓝灯
				{
				    gpio_set(LIGHT_RED,LIGHT_OFF);		//关闭红灯
				    gpio_set(LIGHT_GREEN,LIGHT_OFF);	//关闭绿灯
				    gpio_set(LIGHT_BLUE,LIGHT_ON);		//打开蓝灯
				}

				for (uint32_t j = 0; j < 0xFFFF; ++j)//查询指定次数
				{
					if((*usart2_isr)&(0x1UL<<7U))	//判断发送数据寄存器是否为空
					{
						*usart2_tdr = data + 1;		//回发接收到的内容(内容加一)
						break;
					}
				}//end for
				break;
			}
		}//end for
	}
	ENABLE_INTERRUPTS;	//开总中断
}

结果同上。

第七章

8.1

8.2

第十章(实验四)

10.1对于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;                //2024.6
	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)   //2024.6
		{
			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)   //2024.6
		{
			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;  //2024.6
    //(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);  //2024.6
    }
    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);    //2024.6
	else
		SET_BIT(CAN_ARR[canNo-1]->IER,CAN_IER_FMPIE1);
	NVIC_EnableIRQ(table_irq_can[Can_Rx_FifoNo]);     //2024.6
}

//=====================================================================
//函数名称: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;     //2024.6
    	if(transmit_mailbox > 2U)
    	{
    		return 1;
    	}

    	//(2.1)判断并设置发送帧为标准帧还是扩展帧
    	if(DestID <= 0x7FFU)    //2024.6
    	{
    		CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_STID_Pos)|CAN_ID_STD|rtr);  //2024.6
    	}
    	else
    	{
    		CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_EXID_Pos)|CAN_ID_EXT|rtr);  //2024.6
    	}
    	//(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,   //2024.6
                  ((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);   //2024.6
        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;   //2024.6
		RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
		GPIOA->MODER &= ~(GPIO_MODER_MODE11|GPIO_MODER_MODE12);
		GPIOA->MODER |= (GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1);  //2024.6
		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);   //2024.6
	}
	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);    //2024.6
	i = 0;
	while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_SLAK) != 0U)   //2024.6
	{
		if(i++ > 0x30000)
		{
			return 1;
		}
	}

	SET_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_INRQ);    //2024.6
	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);   //2024.6
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_ABOM);   //2024.6
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_AWUM);   //2024.6
	SET_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_NART);     //2024.6
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_RFLM);    //2024.6
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_TXFP);    //2024.6
}

//函数名称: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);    //2024.6
}

//=====================================================================
//函数名称: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);    //2024.6
	i = 0;
    while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_INAK) != 0U)    //2024.6
    {
      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;    //2024.6

	FilterIdHigh = (CanID >> 16) & 0xFFFF;    //2024.6
	FilterIdLow = (CanID & 0xFFFF);    //2024.6
	FilterMaskIdHigh = 0xFFE0;    //2024.6
	FilterMaskIdLow = 0x0000;    //2024.6
	filternbrbitpos = (uint32_t)1 << (FilterBank & 0x1FU);    //2024.6

	//设置过滤器初始化模式 (FINIT=1),在此模式下可以进行过滤器初始化
	SET_BIT(CAN_ARR[canNo-1]->FMR, CAN_FMR_FINIT);    //2024.6
	CLEAR_BIT(CAN_ARR[canNo-1]->FA1R, filternbrbitpos);    //2024.6
	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)    //2024.6
	{
	  SET_BIT(CAN_ARR[canNo-1]->FS1R, filternbrbitpos);    //2024.6
	  CAN_ARR[canNo-1]->sFilterRegister[FilterBank].FR1 =    //2024.6
		((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)    //2024.6 
	{
	  CLEAR_BIT(CAN_ARR[canNo-1]->FM1R, filternbrbitpos);    //2024.6
	}
	else
	{
	  SET_BIT(CAN_ARR[canNo-1]->FM1R, filternbrbitpos);
	}
	if (Can_Rx_FifoNo == CAN_FILTER_FIFO0)     //2024.6
	{
	  CLEAR_BIT(CAN_ARR[canNo-1]->FFA1R, filternbrbitpos);   //2024.6
	}
	else
	{
	  SET_BIT(CAN_ARR[canNo-1]->FFA1R, filternbrbitpos);
	}
	if (IsActivate == 1)    //2024.6
	{
	  SET_BIT(CAN_ARR[canNo-1]->FA1R, filternbrbitpos);   //2024.6
	}
	//退出过滤器初始化模式 (FINIT=0)
	CLEAR_BIT(CAN_ARR[canNo-1]->FMR, CAN_FMR_FINIT);    //2024.6

	return 0;
}

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

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值