嵌入式技术基础与实践

第一章作业 

1、列表罗列嵌入式系统常用术语(中文名、英文缩写、英文全称)

与硬件相关的术语

中文名

英文缩写

英文全称

封装

Package

印刷电路板

PCB

Printed Circuit Board

模拟量

Analog Quantity

开关量

Switching Quantity

单列直插

SIP

Single-in-line Package

双列直插

DIP

Dual-in-line Package

Z字形直插式封装

ZIP

Zigzag-in-line Package

小外形封装

SOP

Small Outline Package

紧缩小外形封装

SSOP

Shrink Small Outline Package

四方扁平封装

QFP

Quad-Flat Package

塑料薄方封装

LQFP

Plastic-Low-profile Quad-Flat Package

塑料扁平组件式封装

PFP

Plastic Flat Package

插针网格阵列封装

PGA

Ceramic Pin Grid Array Package

球栅阵列封装

BGA

Ball Grid Array Package

动态可读写随机存储器

DRAM

Dynamic Random Access Memory

静态可读写随机存储器

SRAM

Static Random Access Memory

只读存储器

ROM

Read Only Memory

与通信相关的术语

中文名

英文缩写

英文全称

并行通信

Parallel Communication

串行通信

Serial Communication

串行外行接口

SPI

Serial Peripheral Interface

集成电路互联总线

I2C

Integrated Circuit Internet Bus

通用串行总线

USB

Universal Serial Bus

控制器局域网

CAN

Controller Area Network

边界扫描测试协议

JTAG

Joint Test Action Group

串行线调试技术

SWD

Serial Wire Debug

与功能模块相关的术语

中文名

英文缩写

英文全称

通用输入/输出

GPIO

General Purpose I/0

模数转换

ADC

Analog to Digital Convert

数模转换

DAC

Digital to Analog Convert

脉冲宽度调制器

PWM

Pulse Width Modulator,

看门狗

Watch Dog

液晶显示

LCD

Liquid Crystal Display

发光二极管

LED

Light Emitting Diode

键盘

Keyboard

2、运行示例程序

打开环境,导入工程

成功编译工程

成功连接GEC并下载机器码,完成自动更新,同时指示灯运作正常

观察程序运行过程,并验证串口收发

 第二章作业

打开04-Software/ch02/CH02-1-20220118工程目录,
编译下载运行,理解并学习main.s汇编源文件。

修改main.s源文件,增加以下内容:
· 1、在第一行显示"广州大学"字样。
· 2、编写一个1+2+…+10的程序,将求和结果存入名为
"sumresult"的内存单元中,并将求和结果、sumresult
的地址用printf显示出来。
注:建议在源程序中"main_loop:"语句之前的地方增加,结束时用"bl."停住。
printf的格式详见05 UserBoard/printf.h文件。

1. 写出main.s中94~101行语句的C语言描述。

汇编代码

C语言代码

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

1、在第一行显示"广州大学"字样。

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

定义需要使用的数据输出格式,使用的变量及输出的字符串

通过ldr命令将初始的datax的值以及sumresult的值存入到r2和r3中,编写loop循环,通过add命令使得r2的值增加,并依次将和存入r3中,通过比较r2和11的值判断循环是否结束,然后通过str命令将求和结果存回sumresult所指的地址,最后通过printf函数显示

第三章作业

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

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

本书使用的芯片型号为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),片内RAM一般用来存储全局变量、静态变量、临时变量等。大部分编程将其连续在一起使用,即地址范围是0x2000_0000~0x2000_FFFF,共64KB。

第四章作业

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

方法一:点击右键查看定义直接查找

方法二:通过工具栏中“编辑”->“查找和替换”->”文件查找/替换“可打开查找窗口进行查找

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

将D19,D18设置为01,即设定9号针脚为输出模式,即蓝灯所连接的针脚
将D17,D16设置为01,即设定8号针脚为输出模式,即绿灯所连接的针脚
将D15,D14设置为01,即设定7号针脚为输出模式,即红灯所连接的针脚

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

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

     蓝灯和绿灯执行相同操作

运行结果

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

引脚定义

初始化红绿蓝灯,并设置初始状态为灯亮

使用gpio_set函数设置灯的状态

运行结果

第六章作业

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

(1)设置引脚复用功能为串口。通过GPIO模块的端口模式寄存器(GPIOA_MODER)设定为引脚为复用功能模式;通过GPIO复用功能低位寄存器(GPIOA_AFRL)设定为UARTx_TX和UARTx_RX。

(2)设置波特率。通过UART波特率寄存器(UART_BRR)设定使用什么速度收发字节。

(3)开启UART功能。通过UART控制寄存器(UART_CR1,UART_CR2和UART_CR3)开启UART功能,启动串口发送与接收功能。


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

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

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

中断向量表存放在 startup_stm32|431rctx.s中


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

找到TIM6中断号为54

 当IRQn=54,是大于0的,进入if条件语句,

ISER[(((uint32_t)IRQn) >> 5UL)]是将54右移5位,实际上是整除32,54>>5 == 1,所以NVIC->ISER[1],

((uint32_t)IRQn) & 0x1FUL)是保留54的低5位,十进制是22,1UL<<22是将1左移22位,

然后把这个结果赋值给NVIC->ISER[1]。所以最后是NVIC->ISER[1]的第22位设为1,从而使得中断号为54的中断被使能。


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

不能,每一个中断在向量表中都有一个唯一的中断号(IRQ号),这个IRQ号用于在向量表中定位对应的中断服务函数,将UART_2和TIM6在中断向量表中的位置和IRQ号进行交换,那么在代码中相应的中断处理程序可能会被错误地调用,因为中断向量表中的位置和IRQ号是与特定的外设和中断服务函数相关联的。

实现UART_2串口的接收程序,

当收到字符时:

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

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

1、用构件调用方式实现;

main.c

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件
 
int main(void)
{
	//关总中断
	DISABLE_INTERRUPTS;
 
	//用户外设模块初始化
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
    gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);
    gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
 
	uart_init(UART_User,115200);     //初始化串口模块   
  
	//打开使能模块中断
	uart_enable_re_int(UART_User);  //使能UART_USER模块接收中断功能
	
	//开总中断
	ENABLE_INTERRUPTS;
	
	//提示进入串口工具进行操作
	printf("=====================================\n");
	printf("---------------广州大学---------------\n");
	printf("网络211 32106200041 zzj\n");
	printf("=====================================\n");
}

isr.c

//=====================================================================
//文件名称: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;   
 }

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

main.c

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


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

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
    //(1)======启动部分(开头)==========================================
    //(1.1)声明main函数使用的局部变量
    uint8_t  mTest;
    uint32_t mCount;
    
    //uart寄存器相关地址
    volatile uint32_t* RCC_AHB2;     //GPIO的A口时钟使能寄存器地址
    volatile uint32_t* RCC_APB1;     //UART的2口时钟使能寄存器地址
    volatile uint32_t* gpio_ptr;       //GPIO的A口基地址
    volatile uint32_t* uart_ptr;       //uart2端口的基地址
    volatile uint32_t* gpio_mode;    //引脚模式寄存器地址=口基地址
    volatile uint32_t* gpio_afrl;      //GPIO复用功能低位寄存器
    volatile uint32_t* uart_brr;      //UART波特率寄存器地址
    volatile uint32_t* uart_isr;      // UART中断和状态寄存器基地址
    volatile uint32_t* uart_cr1;      //UART控制寄存器1基地址 
    volatile uint32_t* uart_cr2;      // UART控制寄存器2基地址
    volatile uint32_t* uart_cr3;      // UART控制寄存器3基地址
    volatile uint32_t* uart_tdr;      // UART发送数据寄存器
    uint16_t usartdiv;   //BRR寄存器应赋的值
    
    //变量赋值
    
    RCC_APB1=0x40021058UL;   //UART时钟使能寄存器地址
    RCC_AHB2=0x4002104CUL;   //GPIO的A口时钟使能寄存器地址
    gpio_ptr=0x48000000UL;   //GPIOA端口的基地址
    uart_ptr=0x40004400UL;  //UART2端口的基地址
    gpio_mode=0x48000000UL;              //引脚模式寄存器地址=口基地址
    gpio_afrl=0x48000020UL;           // GPIO复用功能低位寄存器
    uart_cr1=0x40004400UL;              //UART控制寄存器1基地址 
    uart_brr=0x4000440CUL;          // UART波特率寄存器地址
    uart_isr=0x4000441CUL;         // UART中断和状态寄存器基地址
    uart_tdr=0x40004428UL;         //UART发送数据寄存器
    uart_cr2=0x40004404UL;      // UART控制寄存器2基地址
    uart_cr3=0x40004408UL;      //UART控制寄存器3基地址
    
    //(1.2)【不变】关总中断
    DISABLE_INTERRUPTS;
    
    //(1.3)给主函数使用的局部变量赋初值
    mCount=0;
    //(1.4)给全局变量赋初值
    
    //(1.5)用户外设模块初始化
    gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
    //uart_init(UART_User,115200);
    
    //使能GPIOA和UART2的时钟
    *RCC_APB1|=(0x1UL<<17U);       //UART2时钟使能 
    *RCC_AHB2 |=(0x1UL<<0U);       //GPIOA时钟使能
    
    //将GPIO端口设置为复用功能
    //首先将D7、D6、D5、D4清零
    *gpio_mode &= ~((0x3UL<<4U)|(0x3UL<<6U)); 
    //然后将D7、D6、D5、D4设为1010,设置PTA2、PTA3为复用功能串行功能。
    *gpio_mode |=((0x2UL<<4U)|(0x2UL<<6U));
    
    //选择引脚的端口复用功能
    //首先将D15~D8清零
    *gpio_afrl &= ~((0xFUL<<8U)|(0xFUL<<12U));
    //然后将D15~D8设置为01110111,分别将PTA3、PTA2引脚设置为USART2_RX、USART2_TX 
    *gpio_afrl=(((0x1UL<<8U)|(0x2UL<<8U)|(0x4UL<<8U))|((0x1UL<<12U)
    |(0x2UL<<12U)|(0x4UL<<12U)));         
    
    //暂时禁用UART功能,控制寄存器1的第0位对应的是UE—USART使能位。
    //此位清零后,USART预分频器和输出将立即停止,并丢弃所有当前操作。
    *uart_cr1 &= ~(0x1UL);
    
    //暂时关闭串口发送与接收功能,控制寄存器1的发送器使能位(D3)、接收器使能位(D2)
    *uart_cr1 &= ~((0x1UL<<3U)|(0x1UL<<2U));
    
    //配置波特率
    if(*uart_cr1&(0x1UL<<15) == (0x1UL<<15))             
    usartdiv = (uint16_t)((SystemCoreClock/115200)*2);
    else
    usartdiv = (uint16_t)((SystemCoreClock/115200));
    *uart_brr = usartdiv;
    
    //初始化控制寄存器和中断状态寄存器、清标志位
    //关中断
    *uart_isr = 0x0UL;    
    //将控制寄存器2的两个使能位清零。D14—LIN模式使能位、D11—时钟使能位 
    *uart_cr2 &= ~((0x1UL<<14U)|(0x1UL<<11U));
    //将控制寄存器3的三个使能位清零。D5 (SCEN) —smartcard模式使能位、
    //D3 (HDSEL) —半双工选择位、D1 (IREN) —IrDA 模式使能位
    *uart_cr3 &= ~((0x1UL<<5U) | (0x1UL<<3U) |(0x1UL<<1U));
    
    //启动串口发送与接收功能
    *uart_cr1 |= ((0x1UL<<3U)|(0x1UL<<2U)); 
    
    //开启UART功能
    *uart_cr1 |= (0x1UL<<0U); 

    
    //(1.6)使能模块中断
    uart_enable_re_int(UART_User);  //使能UART_User模块接收中断功能
    //(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
    
    printf("=====================================\n");
	printf("---------------广州大学---------------\n");
	printf("网络211 32106200041 zzj\n");
	printf("=====================================\n");
	}

isr.c

#include "includes.h"
#define GLOBLE_VAR
 
volatile uint32_t* uart_isr = (volatile uint32_t*)0x4000441CUL;	// UART中断和状态寄存器基地址
volatile uint32_t* uart_rdr = (volatile uint32_t*)0x40004424UL;	// UART接受数据寄存器
volatile uint32_t* uart_tdr = (volatile uint32_t*)0x40004428UL;	// UART发送数据寄存器
 
void User_SysFun(uint8_t ch);
//======================================================================
//程序名称:UART_User_Handler
//触发条件:UART_User串口收到一个字节触发
//备    注:进入本程序后,可使用uart_get_re_int函数可再进行中断标志判断
//          (1-有UART接收中断,0-没有UART接收中断)
//======================================================================
void USART2_IRQHandler(void)
{
	uint8_t ch;
	uint8_t flag;
	uint32_t t;
	
	DISABLE_INTERRUPTS;   //关总中断
	//接收一个字节的数据
	//ch=uart_re1(UART_User,&flag);  //调用接收一个字节的函数,清接收中断位
	for(t = 0; t < 0xFBBB; t++)//一直查询缓冲区是否有数据
	{	
		//先判断isr状态位,再获取数据
		if((*uart_isr)&(1<<5U))//第五位为1
		{
			ch = *uart_rdr;//从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);
			
	}
	ENABLE_INTERRUPTS;    //开总中断
	
 }
 
运行结果:

第七章作业

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

设置倒计时时间,并初始化红灯

运行结果:

倒计时2分30秒

倒计时结束,红灯亮

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

设置闹钟

收到数据帧头为?设置时间,收到数据帧头为Z设置闹钟

闹钟唤醒中断

运行结果:

设置时间:

设置闹钟:

闹钟唤醒:

绿灯亮:

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

前10次为短闪,占空比为10,后10次为长闪,占空比为60

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

时间间隔逐渐缩短,蓝灯的闪烁频率逐渐变快。

第八章作业

1、用实验验证,对于有数据的某扇区,如果没有擦除(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 *) "I am zzj from 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 *) "章郑杰 from 广州大学!");
	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.c中的函数内部的擦除函数注释掉

再次运行发现第二次没有写入

所以对于有数据的某扇区,如果没有擦除(Flash_erase),是不可以写入新数据的。

2、在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_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通道:内部温度
    
    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
*/





运行结果:

用手触摸热敏电阻后,当前温度上升

用手触摸芯片后,芯片内部温度上升

第十章作业

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 设置CAN模式为正常模式
	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 依据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)   //2024.6 检查CAN接收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;  //2024.6 从CAN接收FIFO中获取当前消息的数据长度
    //(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 启用CAN接收FIFO 0的满中断
    }
    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);    
	else
		SET_BIT(CAN_ARR[canNo-1]->IER,CAN_IER_FMPIE1);
	NVIC_EnableIRQ(table_irq_can[Can_Rx_FifoNo]);     //2024.6 使用NVIC(嵌套向量中断控制器)来启用与CAN接收FIFO相关的中断。
}

//=====================================================================
//函数名称: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 计算并获取一个可用于发送的CAN邮箱编号
    	if(transmit_mailbox > 2U)
    	{
    		return 1;
    	}

    	//(2.1)判断并设置发送帧为标准帧还是扩展帧
    	if(DestID <= 0x7FFU)    //2024.6 检查目标CAN节点的标识符是否适合使用标准帧格式(11位ID)
    	{
    		CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_STID_Pos)|CAN_ID_STD|rtr);  //2024.6 设置传输邮箱的TIR寄存器,配置消息的ID、帧类型(标准帧)和远程传输请求(RTR)。
    	}
    	else
    	{
    		CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_EXID_Pos)|CAN_ID_EXT|rtr);  //2024.6 设置传输邮箱的TIR寄存器,配置消息的ID、帧类型(标准帧)和远程传输请求(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,   //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 将所选邮箱内的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;   //2024.6 使能CAN1模块的时钟
		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 //设置引脚为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);    //2024.6 清除了CAN_MCR寄存器中的CAN_MCR_SLEEP位,以退出CAN模块的睡眠模式。
	i = 0;
	while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_SLAK) != 0U)   //2024.6 如果等待时间过长(超过0x30000次循环),则返回错误
	{
		if(i++ > 0x30000)
		{
			return 1;
		}
	}

	SET_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_INRQ);    //2024.6 进入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);   //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 禁用接收FIFO锁定功能
	CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_TXFP);    //2024.6 清除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);    //2024.6 配置了CAN位时序寄存器(BTR),设置了预分频器、同步跳位宽度(SJW)、时间段(TS1和TS2)和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);    //2024.6 清除了CAN_MCR寄存器中的CAN_MCR_INRQ位,以退出CAN模块的初始化模式。
	i = 0;
    while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_INAK) != 0U)    //2024.6 检查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;    //2024.6 换为标准ID格式

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

	//设置过滤器初始化模式 (FINIT=1),在此模式下可以进行过滤器初始化
	SET_BIT(CAN_ARR[canNo-1]->FMR, CAN_FMR_FINIT);    //2024.6 设置了CAN_FMR寄存器中的CAN_FMR_FINIT位,以进入过滤器初始化模式。
	CLEAR_BIT(CAN_ARR[canNo-1]->FA1R, filternbrbitpos);    //2024.6 清除了CAN_FA1R寄存器中的相应位,以选择过滤器编号。
	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 过滤器大小为32位的情况
	{
	  SET_BIT(CAN_ARR[canNo-1]->FS1R, filternbrbitpos);    //2024.6 设置了CAN_FS1R寄存器中的相应位,以选择过滤器的规模。
	  CAN_ARR[canNo-1]->sFilterRegister[FilterBank].FR1 =    //2024.6 设置了过滤器寄存器的FR1字段,用于配置过滤器的ID或掩码。
		((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 根据接收FIFO选择配置过滤器关联的FIFO
	{
	  CLEAR_BIT(CAN_ARR[canNo-1]->FFA1R, filternbrbitpos);   //2024.6 过滤器关联到FIFO0
	}
	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;
}

2、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*)"章郑杰\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
 */

开发板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(" LIGHT_RED:ON--\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
 */
运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值