当前位置:网站首页>嵌入式学习硬件篇------初识ARM
嵌入式学习硬件篇------初识ARM
2022-08-02 14:12:00 【矮矮哒贞子】
ARM体系结构的演变与发展
ARM公司简介
ARM(Advanced RISC Machine Limited)于1991年成立于英国剑桥,最早由Acorn、Apple和VLSI合资成立,主要出售芯片设计技术的授权。
ARM 公司不生产芯片,只是出售芯片设计方案。
ARM技术特征
体积小、低功耗、低成本、高性能。 |
---|
支持32位ARM指令集和16位Thumb指令集 |
寄存器数量多,指令的执行速度快 |
寻址方式简单高效 |
大部分数据操作都在寄存器中完成 |
指令长度固定 |
ARM体系架构的发展历程
v1架构 | 地址空间采用26位寻址方式,寻址空间是64MB |
---|---|
v2架构 | 相比于v1架构,增加了乘法指令集。并且支持协处理器指令 |
v3架构 | ARM处理器的体系架构实现了32位地址空间,完善了前面版本的指令结构 |
v4 | 增加了半字符指令的读取和写入操作,增加了处理器系统模式 |
v5 | v架构的ARM处理器提升了ARM和Thumb两种指令的交互工作能力,通知有了DSP指令 |
v6 | 2001年发布的v6架构,在该版本中增加了媒体指令 |
v7 | 基于V6架构采用Thumb-2技术,他是在ARM的Thumb代码压缩技术基础上发展起来的,并保持了对现存ARM解决方案的完整的代码兼容性。 |
v8 | 在32位ARM架构上进行开发,v8包含两个执行状态:AArch64和AArch32。 |
Cortex-A9内核工作模式
Cortex-A9基于ARMv7-A架构,共有8种工作模式。
用户模式(user) | 非特权模式,大多数的任务执行在此工作模式下 |
---|---|
FIQ:快速中断模式 | 当一个高优先级(fast)中断产生时,会进入这种模式 |
IRQ:外部中断模式 | 当一个低优先级(normal)中断产生时,会进入这种模式 |
Supervisor:特权模式【权限最大】 | 当复位按键按下或者软中断指令执行时,将会进入SVC模式 |
Abort:数据访问指令终止 | 当存取异常时,将会进入这种模式 |
Undef:未定义指令终止模式 | 此时使用User模式相同寄存器集的特权模式 |
Monitor:监控模式【Crotex-A系列专有模式】 | 为了安全二扩展出来的用于执行安全监控代码的模式,也是一种特权模式 |
嵌入式系统:
嵌入式系统 = 嵌入式硬件 + 嵌入式软件
嵌入式软件分为:
裸机 | APP |
---|---|
非裸机 | APP + OS操作系统 |
嵌入式硬件分为:
控制器 | CPU |
---|---|
输入设备 | 鼠标、键盘 |
输出设备 | 显示器、音响 |
存储器 | 内存、外存 |
运算器 | ALU |
总线 | APB |
存储器又被分为:
辅助存储器 | 硬盘、U盘、光盘 【CPU不能直接访问】 |
---|---|
主存储器 | 内存SDRAM 【CPU可以直接访问】 |
高速缓存器 | Cache 【CPU可直接访问】 |
寄存器 | 用来存放CPU在运算过程中的数据和结果值 【CPU可直接访问】 |
名称 | 功能 |
---|---|
控制器 | 整个嵌入式设备的指挥部 |
运算器(ALU) | 信息进行处理和运算的额部件 |
寄存器 | 运算器的数据被寄存在这里 |
CPU | 控制器 + 运算器 + 寄存器组 |
SOC(System on Chip) | 片上系统,指一块芯片上面集成了CPU和外围组件 |
总线(Bus) | 连接嵌入式设备的各个模块,传输数据 |
flash | 存储数据 |
SOC:
总线分为三类:
- 控制总线
- 数据总线
- 地址总线
flash包括:
Nor Flash | 既能存储数据,又能运行程序 |
---|---|
Nand Flash | 只能存储数据 |
甚么是处理器的内核?
内核等同与架构,而处理器的架构是由控制时序电路和ARM指令集/Thumb指令集组成的。
ARM指令集:32bit。当ARM内核处于ARM态时,使用ARM指令集
Thumb指令集:16bit。当ARM内核处于Thumb态时,使用Thumb指令集。
ARM启动后默认为ARM态,默认也是执行ARM指令集,只有当用户手动切换到Thumb态时,才会执行Thumb指令集。
处于ARM态的内核有8中工作模式
二进制 : M[4:0] | 工作模式 | 介绍 |
---|---|---|
10000 | User:用户模式 | 非特权模式,大部分任务执行在这种工作模式下 |
10001 | FIQ:快速中断模式 | 当一个高优先级(FAST)中断产生时,会进入这种模式。 |
10010 | IRQ:外部中断模式 | 当一个低优先级(Normal)中断产生时,会进入这种模式。 |
10011 | Supervisor:特权模式(权限最大) | 当复位键按下或者软中断指令执行时,会进入SVC模式 |
10111 | Abort:数据访问终止模式 | 当存取异常时会进入这种模式 |
11011 | Undef:未定义指令中断模式 | 当执行未定义的指令时会进入这种模式 |
11111 | System:系统模式 | 使用User模式相同 |
寄存器介绍
寄存器(Register):用在指令执行过程中,存放运算数据和结果的一种容器
一共有40个寄存器,其中有5类特殊寄存器是用来做辅助计算使用的。
我只介绍重要的寄存器:
r13(SP):存放SP栈指针,常用作堆栈指针
r14(LR):在程序执行中断或者函数调用时,保存当前执行的指令的下一条指令的地址。
这里做一下说明:
指令流水线:
为了增加处理器的指令流速度,ARM7系列使用了3级流水线。程序执行时有一个PC(Program Counter,PC指针),它用来存放当前欲执行指令的地址。
三级流水线的执行分为三个步骤:
取指令—>译码—>编译
流水线执行就是在第一条指令进行编译的时候,同时第二条指令在进行译码,同时第三条指令正在从存储器种被读取。
得益于指令流水线,能够在同一时间执行多一条指令,所以会提高程序的执行效率。
r15(PC):程序的取值位置
程序的串行与并行:
正如汤老师所说:Obviously!并行比串行节省时间!
中间先省略
通信方式
并行通信 | 串行通信 |
---|---|
并行速度快,但是占用IO口 | 串行比并行速度慢,但节省IO口 |
串口在通信过程中,一次只能传输一个比特的数据,其单位是bps(波特率)。
信道分类:
单工通信 | 只能由一方向另一方通信 而没有反方向的交互,eg:广播 |
---|---|
半双工通信 | 双方都可以通信,但是同一时刻仅能有一方发送,另一方只能接收 eg:对讲机 |
全双工通信 | 通信双方能同时收发数据 eg:电话 |
数据单位
- 码元
- 波特率(Baud)
波特率是马原传输速率的单位。1波特<==>每秒传输一个码元。 - 信息的传输速率:比特/秒(bit/s)
- 比特率与码元的传输速率"波特"在数量上有一定的关系。
- 若1个码元只携带1bit的信息量,此时bit/s = Baud
- 若一个码元携带n bit的信息量,则M Baud的码元传输速率所对应的信息传输速率为:M * n bit/s。
串行通信
1.同步通信方式
同步通信方式( Synchronous )所用的数据格式没有起始位、停止位,一次传送的字符个数可变。在传送前,先按照一定的格式将各种信息装配成一个包,该包包括供接收方识别用的同步字符一个或两个,其后紧跟着要传送的n个字符,再后就是校验字符。
2.异步通信方式
异步方式(Asynchronous) :也称“起止同步式”。
查看原理图,找到串口
开发板型号:华清远见FS4412_DEV_V5,基于三星ExynosF4412的ARM芯片,主频为1.4~1.6GHz。
首先对照原理图,找到UART口
这里使用的是板子上的COM2,对应原理图就是CON7
顺着串口的接线反向查找,会发现从ARM芯片引出的串口并不是直接接到9针的RS-232接口上面,而是先后经过两个小芯片:74ALVC164245DGG、SP3232EEA
为什么要这样接线?
答:因为ARM的标准工作电压是1.8V左右,而RS-232串口通信需要的电压在-15V~+15V这个区间:
1:在电路中用3-15V的正电压表示1
0:用-3~-15V的电压表示0
所以通常用+12V表示高电平’1’,用-12V表示低电平’0’
为了顺利实现通信,需要进行两次的电压转换,先将1.8V的电压通过74ALVC164245DGG芯片升高至3.3V左右,然后再使用SP3232EEA芯片将3.3V的电压继续抬高。实现0和1的转换。
配置寄存器
通过逆向查找最终我们找到了串口在芯片上的哪一个寄存器上。现在就需要打开ARM的芯片手册,查看相关寄存器的位介绍了。
下图为ARM芯片关于当前UART的引脚标识:
不难看出,我们所使用的UART串口需要在GPA1寄存器中进行配置。接下来就去查看GPX1寄存器。
如何查看芯片手册
以Exynos 4412的手册为例:
打开芯片手册,将手册的导航窗口打开,浏览并找到【General Purpose Input/Output (GPIO) Control】,直译过来就是:通用I/O控制,也就是通用寄存器。
选择这个通用I/O,你会看到里面又有分类:
我们可以看到开头是寄存器的概述,里面注明了寄存器名称、起始地址、简述以及复位后的初始值。但是这不是我们要找的,我们要找的是某一个寄存器的全部比特位的详细介绍,所以继续从列表中找,分别点开Part1、2、3、4部分,寻找我们的GPA1寄存器。
很快啊!一下就找到了,就在第一部分的开头,可以看到有GPA1CON和GPA1DAT,下面还有一堆,发生甚么事了?这么多分类的嘛?我们目前实现UART通信只需要配置GPA1CON控制寄存器。
寄存器都是有32个bit位,但不一定都全部使用,有的寄存器只使用8位,有的使用16位,但是每一个寄存器都有32位。如下图所示,一个寄存器共引出8个引脚,也就是每4个bit位控制一个引脚的功能。
所以理论上来说,每个引脚最大可以通过4个bit位提供2^4=16种功能选项,16进制也就是0-F。
GPA1寄存器就仅仅使用了24个bit位,共输出6个引脚:GPA1CON[0]–GPA1CON[5]
我们使用的UART是0引脚和1引脚,座椅只需要配置GPAICON[0]和GPA1CON[1]即可。
配置将[3:0]–>(意思就是0bit至3bit位)设置为0x2
[7:4]比特位也设置为0x2如何配置
我们要找到该寄存器的首地址,然后通过加上该位的偏移地址,就能得出寄存器的某一位在内存中的地址。最后将该地址经过与或非取反等一系列逻辑运算就能够将我们需要的位给设置好
//uart 寄存器
#define GPA1CON (* (volatile unsigned int *)0x11400020)
//bit7-bit4 === 2, GPA1_1 配置为uart 2 的输出
//bit3-bit0 === 2, GPA1_0 配置为uart 2 的输入
宏定义吧!我能理解;
无符号整型变量把!我也能理解;
但是你前面加的 volatile是个甚么意思?
解释:用volatile修饰的变量,是为了防止变量被编译器优化
CPU第一次读取数据时,是通过APB总线从内存中拿到数据,然后放在寄存器中,这样能够方便下次对数据的调用。所以当下一次CPU再次需要使用该数据时,它不会再去从内存中取数据,而是直接从寄存器中获取。
但是,寄存器一共就那么几个,当CPU处理很多数据时,最开始存入寄存器的该变量的值有可能会被重写,所以CPU再次使用时再从寄存其中调用该数据时,其实就已经发生了调用出错。
我们再=在声明的变量前面加上volatile关键字后,CPU每次使用该变量时都会去内存中调用,而不会去寄存器中获取。这就是防止被优化的意思。
- 配置ULCON2 帧格式控制寄存器
一帧8个bit位 | [1:0] ----->0x11 |
---|---|
无校验位 | bit5 == 0 |
1个停止位 | bit2 == 0 |
#define ULCON2 (* (volatile unsigned int *)0x13820000)
//帧格式控制
//bit5 ==0 无校验
//bit2 == 0 , 1个停止位
//bit1--bit0 === 11 , 8个数据位
- 配置UCON2 ,UART的控制寄存器
设置阻塞的发送和接收:
[1:0]---->0x01
[3:2]---->0x01
#define UCON2 (* (volatile unsigned int *)0x13820004)
//uart2 的控制寄存器
//bit3--bit2 ==== 01 发送是阻塞模式
//bit1--bit0 ==== 01 接收是阻塞模式
- UTRSTAT2寄存器,UART2的状态寄存器
当发送结束后bit1会自动置为1,当接收成功后bit0会置为1。
我们可以通过这两个位来判断发送和接收。 - UTXH2,发送buffer寄存器
- URXH2,接收buffer寄存器
#define UTXH2 (* (volatile unsigned int *)0x13820020)
//发送buffer 寄存器 , 写入该寄存器的值 被 uart2 发送出去
#define URXH2 (* (volatile unsigned int *)0x13820024)
//接收buffer 寄存器 , uart2 收到的数据保存在这里
- UBRDIV2,存储分频后的整数部分,控制波特率
apb 总线的时钟是100m, uart 需要的工作时钟是11520016
分频数 = 100m / (11520016) ======== 54.25 -1 ===== 53.25
#define UBRDIV2 (* (volatile unsigned int *)0x13820028)
//控制波特率的 , 分频数 的 整数 部分 ==================53
- UFRACVAL2,控制波特率,存储分频数的小数部分。
#define UFRACVAL2 (* (volatile unsigned int *)0x1382002C)
//控制波特率的 , 分频数 的 小数部分*16
代码实现(C和汇编混编)
通过虚拟机里面安装用于编译源码的交叉编译工具链 ,从而实现用C语言编写ARM的汇编语言。
将交叉编译工具链压缩文件拷贝至虚拟机的家目录,也就是cd后的那个目录。
拷贝gcc-4.6.4.tar.xz 到 用户家目录下,并解压tar -xvf gcc-4.6.4.tar.xz
进入解压后的gcc-4.6.4/bin 输入pwd 查看当前路径(如/home/你的虚拟机名/gcc-4.6.4/bin)
输入 export PATH=$PATH:/home/farsight/gcc-4.6.4/bin
想永远有效,需追加该行到当前用户启动脚本(~/.bashrc)的末尾,方法:
$ vim ~/.bashrc
在最后一行下面添加
export PATH=$PATH:/home/farsight/gcc-4.6.4/bin
:wq 保存退出
$ source ~/.bashrc
切换到别的目录, 输入arm-n ,按tab键能补全为arm-none-linux-gnueabi- 说明OK
配置完环境后开始编写代码:
#define GPX1CON (*(volatile unsigned int *)0x11000C20) //LED2 [3:0]
#define GPX1DAT (*(volatile unsigned int *)0x11000C24)
#define GPX2CON (*(volatile unsigned int *)0x11000C40) //LED3 [31:28]
#define GPX2DAT (*(volatile unsigned int *)0x11000C44)
#define GPF3CON (*(volatile unsigned int *)0x114001E0) //LED4[19:16]
#define GPF3DAT (*(volatile unsigned int *)0x114001E4) //LED5[23:20]
//uart 寄存器
#define GPA1CON (* (volatile unsigned int *)0x11400020)
//bit7-bit4 === 2, GPA1_1 配置为uart 2 的输出
//bit3-bit0 === 2, GPA1_0 配置为uart 2 的输入
#define ULCON2 (* (volatile unsigned int *)0x13820000)
//帧格式控制
//bit5 ==0 无校验
//bit2 == 0 , 1个停止位
//bit1--bit0 === 11 , 8个数据位
#define UCON2 (* (volatile unsigned int *)0x13820004)
//uart2 的控制寄存器
//bit3--bit2 ==== 01 发送是阻塞模式
//bit1--bit0 ==== 01 接收是阻塞模式
#define UTRSTAT2 (* (volatile unsigned int *)0x13820010)
//uart2 状态寄存器
//bit0 === 1? 1 , uart2 收到了数据
//bit1 === 1? 1 , 之前的数据发送完毕, 可以发新的数据
#define UTXH2 (* (volatile unsigned int *)0x13820020)
//发送buffer 寄存器 , 写入该寄存器的值 被 uart2 发送出去
#define URXH2 (* (volatile unsigned int *)0x13820024)
//接收buffer 寄存器 , uart2 收到的数据保存在这里
#define UBRDIV2 (* (volatile unsigned int *)0x13820028)
//控制波特率的 , 分频数 的 整数 部分 ==================53
#define UFRACVAL2 (* (volatile unsigned int *)0x1382002C)
//控制波特率的 , 分频数 的 小数部分*16 ============ 0.25*16 =======4
//apb 总线的时钟是100m, uart 需要的工作时钟是115200*16
//分频数 = 100m / (115200*16) ======== 54.25 -1 ===== 53.25
/*ADC寄存器配置*/
#define ADCCON (* (volatile unsigned int *)0x126C0000) //ADC3
/*bit16 = 1 ---> 12位分辨率 *转换完成后bit15=1 bit14 = 1---->ADC的工作时钟为APB总线时钟:100M bit[13:6] = 19,ADC的最大工作频率为5M,因为他会自动加1,所以设置为19(100/(19+1) = 5) bit2 = 0 ---->Adc工作在normal模式 bit1 = 0----->0=禁用读取启动操作1=启用按读启动操作 bit0 = 1----->开始ADC转换,0:转换结束 * */
#define ADCDAT (* (volatile unsigned int *)0x126C000C)
#define ADCMUX (* (volatile unsigned int *)0x126C001C)
/*宏定义蜂名气的IO口*/
#define GPD0CON (* (volatile unsigned int *)0x114000A0) //GPD0_0-->0X2:TOUT_0 Timer_0的输出
/*配置定时器0 PWM*/
//一级分频
#define TCFG0 (* (volatile unsigned int *)0x139D0000)
//二级分频
#define TCFG1 (* (volatile unsigned int *)0x139D0004)
//TCON定时器控制器
#define TCON (* (volatile unsigned int *)0x139D0008)
//TCNTB0 计数寄存器
#define TCNTB0 (* (volatile unsigned int *)0x139D000C)
//TCMPB0 比较寄存器
#define TCMPB0 (* (volatile unsigned int *)0x139D0010)
void pwmInit(void)
{
GPD0CON = GPD0CON & ~(0XF << 0); //清空[3:0]
GPD0CON = GPD0CON | (0X2 << 0); //设置GPD0_0引脚为Timer0的输出口
TCFG0 = TCFG0 & ~(0XFF << 0); //将TCFG0一级分频寄存器的[7:0]置为0
TCFG0 = TCFG0 | (199 << 0); //一级分频/200
TCFG1 = TCFG1 & ~(0XF << 0); //由于使用的是定时器0,所以二级分频选择TCFG1的0引脚
TCFG1 = TCFG1 | (2 << 0); //4分频 1000hz
TCON = TCON | (0X1 << 3); //TCON的bit3置为1:自动重载模式
TCON = TCON & ~(0X1 << 2); //TCON的bit2置为0,输出电平不反转
TCNTB0 = 125; //100M / 200 / 4 = 125000 ---> TCNTB0 = (1/f) / (1/F)
TCMPB0 = 100; //站空比0.8 PWM频率 2分频后的频率 ==>t/T
}
void uartInit(void) //uart初始化
{
GPA1CON = GPA1CON & ~(0xf << 0); //将GPA1CON寄存器的[3:0]位设置为0
GPA1CON = GPA1CON | (0x2 << 0); //将GPA1CON寄存器的[3:0]设置为2,UART_2_RXD
GPA1CON = GPA1CON & ~(0XF << 4); //将GPA1CON寄存器的[7:4]位设置为0
GPA1CON = GPA1CON | (0x2 << 4); //将GPA1CON寄存器的[7:4]位置为2,UART_2_TXD
ULCON2 = ULCON2 & ~(0x1 << 5); //将ULCON2的bit5置为0:无校验
ULCON2 = ULCON2 & ~(0x1 << 2); //将ULCON2的bit2置为0:1个停止位
ULCON2 = ULCON2 | (0x3 << 0); //bit1,2 = 1,1; bit3,4,5 = 0,0,0
UCON2 = UCON2 & ~(0x3 << 0); //将UCON2寄存器的bit[1:0]置为0
UCON2 = UCON2 | (0x1 << 0); //将UCON2寄存器的bit[1:0]设置为1:阻塞模式
UCON2 = UCON2 & ~(0x3 << 2); //同理,[3:2]位置0
UCON2 = UCON2 | (0x1 << 2); //[3:2]位设为1:阻塞模式
UBRDIV2 = 53; //波特率除数的整数部分
UFRACVAL2 = 4; //小数部分
}
void ADCInit()
{
ADCCON = ADCCON | (1 << 16); //设置ADC为12位分辨率2^12次方
ADCCON = ADCCON | (1 << 14); //设置ADC的工作时钟为APB总线时钟:100M
ADCCON = ADCCON & ~(0XFF << 6); //将bit[13:6]清零
ADCCON = ADCCON | (0X13 << 6); //19
ADCCON = ADCCON & ~(1 << 2); //bit2=0
ADCCON = ADCCON & ~(1 << 1); //bit1=0
ADCMUX = 0X3; //使用ADC3通道
}
unsigned int ADCread()
{
unsigned int i;
ADCCON = ADCCON | (1 << 0); //bit0=1 开始转换
while(! (ADCCON & (0X1 << 15))); //循环判断bit15是否=1,1:ADC开始
// 0:ADC关闭
i = ADCDAT & 0XFFF; //将ADC的值赋值给变量i
return i;
}
void uartSend(char chr);
void DispADC(unsigned int adc)
{
int tmp;
uartSend(adc / 1000 + '0');
tmp = adc % 1000;
uartSend(tmp / 100 + '0');
tmp = tmp % 100;
uartSend(tmp / 10 + '0');
uartSend(tmp % 10 + '0');
uartSend(' ');
}
void uartSend(char chr)
{
//UTRSTAT2 bit1
//UTRSTAT2
while(! (UTRSTAT2 & (1 << 1 ))) ; //阻塞的发送
UTXH2 = chr;
//UTXH2 = chr;
}
char uartGet(void)
{
char tmp;
while(! (UTRSTAT2 & (1 << 0))) ; //阻塞发送
//UTRSTAT2 bit0
tmp = URXH2;
return tmp;
}
void myDelay()
{
int i;
for(i=0 ;i<200000 ;i++);
}
void led2()
{
GPX1CON = GPX1CON & ~(15<<0); // 清空GPD0控制寄存器的低四位
GPX1CON = GPX1CON | (1<<0); //将GPD0控制寄存器的最低位置为1,设置为Output模式
GPX1DAT = GPX1DAT | (1<<0);
//延时几秒
myDelay();
GPX1DAT = GPX1DAT & ~(1<<0);
myDelay();
}
void led3()
{
GPX2CON = GPX2CON & ~(15<<28);
GPX2CON = GPX2CON | (1<<28);
GPX2DAT = GPX2DAT | (1<<7);
myDelay();
GPX2DAT = GPX2DAT & ~(1<<7);
myDelay();
}
void led4()
{
GPF3CON = GPF3CON & ~(0xff<<16);
GPF3CON = GPF3CON | (0x11<<16);
GPF3DAT = GPF3DAT | (1<<4);
myDelay();
GPF3DAT = GPF3DAT & ~(1<<4);
myDelay();
}
void led5()
{
GPF3CON = GPF3CON & ~(0xff<<16);
GPF3CON = GPF3CON | (0x11<<16);
GPF3DAT = GPF3DAT | (1<<5);
myDelay();
GPF3DAT = GPF3DAT & ~(1<<5);
myDelay();
}
void BZ_ON()
{
TCON = TCON | (0X1 << 1); //更新计数器的值
TCON = TCON & ~(0X1 << 1); //关闭更新
TCON = TCON | (0X1 << 0); //打开计数器
}
void BZ_OFF()
{
//TCON = TCON | (0X1 << 1); //更新计数器的值
//TCON = TCON & ~(0X1 << 1); //关闭更新
TCON = TCON & ~(0X1 << 0); //打开计数器
//TCON = 0; //关闭计数器
}
int main()
{
char i;
unsigned int j;
uartInit(); //初始化uart
ADCInit(); //初始化ADC
pwmInit();
while(1)
{
i = uartGet();
j = ADCread();
DispADC(j);
if(i == 'a')
{
led2(); //led2闪烁
DispADC(j);
BZ_ON();
if(j>0 && j <500)
{
led3();
led4();
}
uartSend(i + 1);
}
else if(i == 'b')
{
led3();
BZ_OFF();
if(j>=500 && j<2000)
{
led4();
led5();
}
uartSend(i + 1);
}
if(i == 'c')
{
if(j>=2000 && j<4500)
{
led2();
led3();
led4();
led5();
}
//uartSend((char)j);
}
else
{
continue;
}
}
return 0;
}
代码实现的功能不仅仅是UART通信,还有ADC转换、LED闪烁。
链接文件、启动文件、Makefile我会打包上传,需要的请自取。
完整代码
先到这里。我困了。
边栏推荐
猜你喜欢
Detailed introduction to the hierarchical method of binary tree creation
3. User upload avatar
MATLAB drawing command fimplicit detailed introduction to drawing implicit function graphics
cmake configure libtorch error Failed to compute shorthash for libnvrtc.so
MATLAB绘图函数plot详解
In-depth understanding of Golang's Map
第二十六章:二维数组
倍增和稀疏表
Detailed explanation of MATLAB drawing function fplot
Redis common interview questions
随机推荐
Detailed explanation of MATLAB drawing function fplot
专硕与学硕
KiCad Common Shortcuts
MMD->Unity一站式解决方案
仿真结果的格式&定制
Based on the least squares linear regression equation coefficient estimation
Manifest merger failed : Attribute [email protected] value=
[System Design and Implementation] Flink-based distracted driving prediction and data analysis system
shader入门精要2
shader 和 ray marching
Redis常见面试题
戴森球计划这个游戏牛逼
剑指offer:合并两个排序的链表
pygame image rotate continuously
Introduction to MATLAB drawing functions ezplot explanation
lua编程
Project: combing the database table
TCP的三次握手和四次挥手
Article pygame drag the implementation of the method
深入理解Mysql索引底层数据结构与算法