原理图
STC8系列
- STC8A: 字母“A”代表 ADC,是 STC 12 位 ADC 的起航产品
- STC8F: 无ADC、PWM 和PCA 功能,现 STC8F 的改版芯片与原始的 STC8F 管脚完全兼容,但对STC8F内部设计进行了优化和更新,用户需要修改程序,所以命名为 STC8C
- STC8C: 字母“C”代表改版,是 STC8F 的改版芯片
- STC8G: 字母“G”最初是芯片生产时打错字了,后来将错就错,定义 G 系列为“GOOD”系列,STC8G 系列简单易学
- STC8H: 字母“H”取自“高”的英文单词“High”的首字母,“高”表示“16 位高级 PWM”
目前主流的位STC8H,其他都属于过去系列
STC32系列
- STC32G: 32位8051单片机,2022年推出。
为新产品,32位,更高效。由于推出时间不久,通常对于应用到产品中会保留观望态度。但是,很快就会普及。对于开发而言,和STC8系列很多是类似的,会STC8,上手STC32是很简单的。
STC8H8K64U
STC8H8K64U功能框图
内核:
●超高速 8051 内核 (1T),比传统 8051 约快 12 倍以上
●指令代码完全兼容传统 8051
●22 个中断源,4 级中断优先级
●支持在线仿真
工作电压:
●1.9V~5.5V
工作温度:
●-40C~85C (芯片为-40C~125C制程,超温度范围应用请参考电气特性章节说明)
Flash存储器:
●最大 64K 字节 FLASH 程序存储器 (ROM) ,用于存储用户代码
●支持用户配置 EEPROM 大小,512 字节单页擦除,擦写次数可达 10 万次以上
●支持在系统编程方式 (ISP) 更新用户应用程序,无需专用编程器
●支持单芯片仿真,无需专用仿真器,理论断点个数无限制
SRAM:
●128 字节内部直接访问 RAM (DATA,C 语言程序中使用 data 关键字进行声明)
●128 字节内部间接访问 RAM (IDATA,C 语言程序中使用 idata 关键字进行声明)
●8192 字节内部扩展 RAM(内部 XDATA,C 语言程序中使用 xdata 关键字进行声明)
●1280 字节 USB 数据 RAM
时钟控制:
●内部高精度IRC (4MHZ45MH,ISP 编程时选择或手动输入,还可以用户软件分频到较低的频率工作如 100KHz)
○误差士0.3% (常温下 25C )中
○-1.35%~+1.30%温漂(全温度范围,-40C~85C)
○-0.76%~+0.98%温漂(温度范围,-20C~65°C)
●内部 32KHz 低速 IRC (误差较大)
●外部晶振(4MHz~45MHZ) 和外部时钟
用户可自由选择上面的 3 种时钟源
复位:
●硬件复位
○上电复位,实测电压值为 1.69V~1.82V。 (在芯片未使能低压复位功能时有效)9上电复位电压由一个上限电压和一个下限电压组成的电压范围,当工作电压从 5V/3.3V 向下掉到上电复位的下限门槛电压时,芯片处于复位状态,当电压从 OV 上升到上电复位的上限门电压时芯片解除复位状态。
○复位脚复位,出厂时 P5.4 默认为 IO 口,ISP 下载时可将 P5.4管脚设置为复位脚(注意: 当设置 P5.4安管脚为复位脚时,复位电平为低电平)
○看门狗溢出复位
○低压检测复位,提供 4 级低压检测电压: 1.9V、2.3V、2.8V、3.7V。每级低乐检测电压都是由一个上限电乐和一个下限电乐组成的电压范用,当工作电压从 5V/3.3V 向下掉到低压检测的下限门槛电压时,低压检测生效,当电压从 0V 上升到低压检测的上限门槛电压时,低压检测生效。
●软件复位
○软件方式写复位触发寄存器
中断:
●提供 22 个中断源: INTO (支持上升沿和下降沿中断)、INT1 (支持上升沿和下降沿中断)、INT2(只支持下降沿中断)、INT3(只支持下降沿中断) 、INT4(只支持下降沿中断) 、定时器 0、定时器 1、定时器2、定时器3、定时器4、串口1、串口2、串口3、串口4、ADC 模数转换、LVD 低压检测、SPI、T2C比较器、PWMA、PWMB、USB
●提供 4 级中断优先级
●时钟停振模式下可以唤醒的中断:INTO(P3.2)、INT1(P3.3)、INT2P3.)、INT3(P3.7)、INT4(P3.0)、TO(P3.4)、T1(P3.5)、T2(P1.2)、T3(P0.4)、 T4(P0.6)、RXD(P3.0/P3.6/P1.6/P4.3)、RXD2(P1.0/P4.6)、RXD3(P0.0/P5.0)、RXD4(P0.2/P5.2)、I2C SDA(P1.4/P2.4/P3.3)以及比较器中断、低压检测中断、掉电唤醒定时器唤醒。
数字外设:
●5个16 位定时器: 定时器0、定时器 1、定时器 2、定时器 3、定时器 4,其中定时器 0的模式3 具有NMI(不可屏蔽中断) 功能,定时器 0 和定时器 1 的模式 0为 16 位自动重载模式4 人高速串口:串口1、串口2、串门3、串口4,波特率时钟源最快可为FOSC/48路/2 组高级 PWM,可实现带死区的控制信号,并支持外部异常检测功能,另外还支持 16 位定时器、8个外部中断、8 路外部捕获测量脉宽等功能
●SPI: 支持主机模式和从机模式以及主机/从机自动切换I2C:支持主机模式和从机模式
●MDU16: 硬件 16 位乘除法器 (支持 32 位除以 16 位、16 位除以 16 位、16 位乘 16位、数据移位以及数据规格化等运算)
●USB: USB2.0/USB1.1 兼容全速 USB,6 个双向端点,支持 4 种端点传输模式(控制传输、中断传输、批量传输和同步传输) ,每个端点拥有 64 字节的缓冲区
●I/0 口中断:所有的 I/0 均支持中断,每组 I/0 中断有独立的中断入口地址,所有的I/0 中断可支持 4种中断模式:高电平中断、低电平中断、上升沿中断、下降沿中断。提供 4 级中断优先级并支持掉电唤醒功能。(注: A 版芯片无此功能)
●DMA :支持 Memory-To-Memory 、 SPI 、 UART1TX/UARTIRX 、 UART2TX/UART2RXUART3TX/UART3RX、UART4TX/UART4RX、ADC (自动计算多次 ADC 结果的平均值)
模拟外设:
●超高速 ADC,支持 12 位高精度 15 通道(通道0通道 14)的模数转换,速度最快能达到 800K (每秒进行 80 万次 ADC 转换)
●ADC 的通道 15 用于测试内部 1.19V 参考信号源(芯片在出厂时,内部参考信号源已调整为 1.19V)
●比较器,一组比较器(A 版芯片: 比较器的正端可选择 CMP+和所有的 ADC 输入端口,比较器的负端可选择 CMP和内部 1.19V 的参考源: B 版芯片:比较器的正端可选择 CMP+、CMP+ 2、CMP+ 3 和所有的ADC 输入端口,比较器的负端可选择 CMP-端口和内部 1.19V 的参考源。所以比较器可当作多路比较器进行分时复用)
●DAC: 8 路高级 PWM 定时器可当8路DAC 使用
GPIO:
●最多可达 60 个 GPIO: P0.0P07、P1.0 P1,7 (无P1.2) 、P2.0 P2,7、P3.0 P3,7、P4.0 P47、P5.0P5.4.
●P6.0~P6.7、P7.0~P7.7所有的 GPIO 均支持如下 4 种模式: 准双向口模式、强推挽输出模式、开漏输出模式、高阻输入模式
●除 P3.0 和 P3.1 外,其余所有 IO 口上电后的状态均为高阻输入状态,用户在使用 IO口时必须先设置IO口模式。另外每个 IO口均可独立使能内部 4K 上拉电阻
实现点灯
代码实现
在 main.c中编写代码,实现main函数
#include "STC8H.H"
int main() {
P5M0 = 0x00;
P5M1 = 0x00;
//实现点灯
P53 = 1;
while(1) {}
}
#include "STC8H.H"
int main() {
P5M0 = 0x00;
P5M1 = 0x00;
//实现灭灯
P53 = 0;
while(1) {}
}
#include "STC8H.H"
引入头文件P5M0 = 0x00;P5M1 = 0x00;
配置引脚模式-
P53=1;
配置IO引脚的电平,1是拉高平,默认是低电平
IO口的工作模式
工作模式 |
PnM1 |
PnM0 |
说明 |
准双向口 |
0 |
0 |
弱上拉,即可输入也可输出 |
推挽输出 |
0 |
1 |
强上拉输出。可达20mA,要加限流电阻 |
高阻输入 |
1 |
0 |
电流既不能流入也不能流出,但可用于测电平 |
开漏输出 |
1 |
1 |
内部上拉电阻断开。开漏模式既可读外部状态也可对外输出(高电平或低电平)。如要正确读外部状态或需要对外输出高电平,需外加上拉电阻,否则读不到外部状态,也对外输不出高电平。 |
设置P53这个引脚的工作模式
P5M1 &= ~0x08, P5M0 &= ~0x08; //准双向口
P5M1 &= ~0x08, P5M0 |= 0x08; //推挽输出
P5M1 |= 0x08, P5M0 &= ~0x08; //高阻输入
P5M1 |= 0x08, P5M0 |= 0x08; //开漏输出
- P5表示的是5号端口
- 0x08表示的是3号引脚,对应二进制
0000 1000
引脚编号和值的对应关系
编号 |
值 |
0 |
0x01 |
1 |
0x02 |
2 |
0x04 |
3 |
0x08 |
4 |
0x10 |
5 |
0x20 |
6 |
0x40 |
7 |
0x80 |
如果想配置任意端口的任意引脚的工作模式,可以通过上面的方式类比出来。
软延时操作
#include "STC8H.H"
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 57;
j = 27;
k = 112;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
int main() {
P5M1 &= ~0x08, P5M0 &= ~0x08; //准双向口
//P5M1 &= ~0x08, P5M0 |= 0x08; //推挽输出
//P5M1 |= 0x08, P5M0 &= ~0x08; //高阻输入
//P5M1 |= 0x08, P5M0 |= 0x08; //开漏输出
while(1) {
P53 = 1;//开
Delay1000ms();
P53 = 0;//关
Delay1000ms();
}
}
举例:在没有头文件的情况下进行点灯
sfr P5M1 = 0xC9;
sfr P5M0 = 0xCA;
sfr P5 = 0xC8;
sbit P53 = P5^3;
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 57;
j = 27;
k = 112;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
int main() {
//P5M1 &= ~0x08, P5M0 &= ~0x08; //准双向口
P5M1 &= ~0x08, P5M0 |= 0x08; //推挽输出
//P5M1 |= 0x08, P5M0 &= ~0x08; //高阻输入
//P5M1 |= 0x08, P5M0 |= 0x08; //开漏输出
while(1) {
P53 = 1;//开
Delay1000ms();
P53 = 0;//关
Delay1000ms();
}
}
sfr和sbit和关键字:
- 在 C51 中,sfr 和 sbit 是两个关键字,它们可以用来定义特殊功能寄存器 (SFR) 和位 (Bit)。
- SFR (Special Function Register) 即特殊功能寄存器,是单片机内部特定功能模块所对应的寄存器。例如,端口寄存器、定时器/计数器寄存器、串行口寄存器等。这些寄存器通过 sfr 关键字来定义。
- Bit 是指特定寄存器中的某一位。在 C51 中,可以用 sbit 关键字来定义一个 Bit。
- sfr 和 sbit 可以在程序中用来对单片机的特定功能寄存器和位进行访问和控制,使用起来比直接操作寄存器更加方便和直观。
使用库函数点灯
下载STC8H的库函数:📎STC8G-STC8H-LIB-DEMO-CODE_2023.07.17_优化版.zip
把库函数加入到项目中,在main.c进行LED的开关控制
#include "Config.h"
#include "GPIO.h"
void Delay500ms() //@11.0592MHz
{
unsigned char data i, j, k;
i = 29;
j = 14;
k = 54;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void GPIO_config(){
GPIO_InitTypeDef gpioInit;
gpioInit.Mode = GPIO_OUT_PP;// 推挽
gpioInit.Pin = GPIO_Pin_3; // P53
GPIO_Inilize(GPIO_P5, &gpioInit);
}
void main(){
// 配置IO模式
// P53 配置准双向 (使用宏函数)
// P5_MODE_IO_PU(GPIO_Pin_3);
// P53 配置推挽输出模式(使用初始化函数)
GPIO_config();
while(1){
P53 = 1;
Delay500ms();
P53 = 0;
Delay500ms();
}
}
什么是库函数?
库函数是一组已经封装好的程序,提供给开发者调用使用。这些函数通常是由语言的开发者或第三方库编写的,实现了一些通用的功能,如IO、PWM、串口、Timer等,可以让开发者无需重复编写这些功能,而是直接调用库函数即可。这样可以提高开发效率、减少重复代码的编写、降低程序出错的可能性,并且可以让代码更加易于维护和扩展。许多编程语言都有自带的库函数,同时也可以通过引入第三方库来扩展其功能。
面向库函数和面向寄存器开发
- 简化编程难度:使用库函数可以使编程更加简单,减少编程错误的可能性。
- 提高可读性:库函数名字通常比寄存器名称更加直观,更容易理解。
- 节省时间:使用库函数可以节省编程时间,因为库函数已经被编写和测试过,可以直接调用使用,而无需重新编写和测试代码。
- 更加可移植:使用库函数可以增加代码的可移植性,因为库函数已经被开发和测试过,可以在不同的硬件平台上使用,而无需进行大量的修改。
- 更加安全:使用库函数可以减少编程错误,例如溢出、死循环等问题,从而使程序更加安全可靠。
当然,在某些情况下,使用寄存器操作可能更加高效,例如在对时间要求比较高的嵌入式系统中,需要最大程度地减少代码运行时间。因此,要根据实际情况来选择使用库函数还是直接寄存器操作。
使用delay模块延时
//引用头文件Delay.h
#include "Delay.h"
//进行延时操作
delay_ms(250); // 延时250毫秒, 这里只支持1~255ms
串口调试UART
串口是一种在数据通讯中广泛使用的通讯接口,通常我们叫做UART
(通用异步收发传输器Universal Asynchronous Receiver/Transmitter),其具有数据传输速度稳定、可靠性高、适用范围广等优点。在嵌入式系统中,串口常用于与外部设备进行通讯,如传感器、液晶显示屏、WiFi模块、蓝牙模块等。
串口通信中的 TXD(Transmit Data)和 RXD(Receive Data)是串口通信中的两个重要信号。
TXD是指串口发送端的数据信号,而RXD是指串口接收端的数据信号。在串口通信中,发送端把要发送的数据发送到TXD引脚上,接收端则通过RXD引脚来接收这些数据。
TXD和RXD信号的实现方式取决于使用的芯片或模块。一般来说,它们都是通过芯片或模块的串口功能来实现的,这需要将相应的引脚连接到芯片或模块的串口引脚上。
在发送数据时,需要将要发送的数据通过串口的发送缓冲区发送到TXD引脚上,接收端通过RXD引脚接收这些数据并放入接收缓冲区中。在接收端收到完整的数据后,可以通过相应的处理进行数据的解析和处理。
需要注意的是,TXD和RXD的电平标准也需要一致,一般常见的有TTL电平和RS232电平,如果不一致则需要进行电平转换。同时,在编写程序时也需要注意串口波特率、数据位、停止位等参数的设置,以保证通信的稳定和可靠。
以下是STC8H的芯片引脚介绍图
其中有4组Uart通讯口:
串口 |
RXD |
TXD |
UART1 |
P3.0 |
P3.1 |
P3.6 |
P3.7 |
|
P1.6 |
P1.7 |
|
P4.3 |
P4.4 |
|
UART2 |
P1.0 |
P1.1 |
P4.6 |
P4.7 |
|
UART3 |
P0.0 |
P0.1 |
P5.0 |
P5.1 |
|
UART4 |
P0.2 |
P0.3 |
P5.2 |
P5.3 |
串口TTL通讯协议
串口TTL(Transistor-Transistor Logic)是一种串口通信协议,使用TTL电平来进行串口数据传输。它主要用于嵌入式系统、传感器、模块等设备之间的数据通信。
串口TTL主要包括两个信号线:TX(Transmit,发送)和RX(Receive,接收)。TX线是串口TTL的输出线路,用于将数据从串口设备发送出去;RX线是串口TTL的输入线路,用于接收数据到串口设备。
串口TTL使用的是异步串行通信协议,其数据传输的原理是将数据分成一定的数据帧,在数据帧的首尾各加上一个起始位和停止位,用于确定每个数据帧的开始和结束位置。此外,串口TTL通信协议还规定了数据位的长度和奇偶校验位。
串口TTL通常有不同的波特率(Baud Rate)可供选择,波特率是指每秒钟传输的数据位数,通常表示为 bps(bits per second),比如 9600 bps、115200 bps 等等。波特率的设置必须要保证发送和接收设备的波特率一致,否则会导致通信失败。
需要注意的是,串口TTL使用的是TTL电平,其电压范围是0~5V,不同的设备的串口TTL信号的电平有时会有所不同,因此在连接不同设备时需要注意电平的兼容性。
串口转USB
串口转 USB 是一种将串口信号转换为 USB 信号的设备。它通常被用于连接没有 USB 接口的设备(如单片机、传感器等)与计算机之间的通讯,使这些设备可以通过 USB 接口与计算机进行通信。
在使用串口转 USB 设备时,需要将其插入计算机的 USB 接口,并将串口连接到需要通信的设备上。在计算机中打开串口终端程序,设置串口参数(如波特率、数据位、停止位等),即可开始进行数据传输。在通信过程中,串口转 USB 设备将串口信号转换为 USB 信号,并将其发送到计算机上,或者将从计算机上接收到的 USB 信号转换为串口信号并发送到外部设备上。
串口转 USB 设备通常由一个 USB 转串口芯片和一个串口接口组成。常见的 USB 转串口芯片有 FTDI 和 CH340 等,它们提供了一组标准的串口接口,可以方便地连接到各种外部设备上。
总之,串口转 USB 设备是一种非常实用的工具,它可以帮助我们连接各种没有 USB 接口的设备,方便数据的传输和通讯。
STC8H核心板串口调试
- 原理图
D+
D-
对应的usb口,和pc主机连接P3.1
P3.0
对应的芯片- 采用CH340将串口和USB之间进行转换
所以,我们在PC机上为什么会装串口驱动,主要是用来解析CH340转换后的消息,这样PC机就能认识了
2. 使用库函数编写串口通讯逻辑
需求:通过串口调试工具,发送消息给开发板,开发板原封不动的将消息传回。
开发步骤:
- 新建项目
- 导入库函数
- 编写逻辑
串口调试实现
1.代码编写(发送)
#include "Config.h"
#include "GPIO.h"
#include "UART.h"
#include "Delay.h"
#include "NVIC.h"
#include "Switch.h"
/************* 功能说明 **************
双串口全双工中断方式收发通讯程序。
通过PC向MCU发送数据, MCU收到后通过串口把收到的数据原样返回, 默认波特率:115200,N,8,1.
通过开启 UART.h 头文件里面的 UART1~UART4 定义,启动不同通道的串口通信。
******************************************/
/******************* IO配置函数 *******************/
void GPIO_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1; //指定要初始化的IO, GPIO_Pin_0 ~ GPIO_Pin_7
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P3,&GPIO_InitStructure); //初始化
}
/*************** 串口初始化函数 *****************/
void UART_config(void)
{
COMx_InitDefine COMx_InitStructure; //结构定义
COMx_InitStructure.UART_Mode = UART_8bit_BRTx; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
COMx_InitStructure.UART_BRT_Use = BRT_Timer1; //选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
COMx_InitStructure.UART_BaudRate = 115200ul; //波特率, 一般 110 ~ 115200
COMx_InitStructure.UART_RxEnable = ENABLE; //接收允许, ENABLE或DISABLE
COMx_InitStructure.BaudRateDouble = DISABLE; //波特率加倍, ENABLE或DISABLE
UART_Configuration(UART1, &COMx_InitStructure); //初始化串口1 UART1,UART2,UART3,UART4
NVIC_UART1_Init(ENABLE,Priority_1); //中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
UART1_SW(UART1_SW_P30_P31); //UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}
/**********************************************/
void main(void)
{
// EAXSFR(); /* 扩展寄存器访问使能 */
GPIO_config();
UART_config();
EA = 1;
TX1_write2buff(0x23); // #
printf("STC8H8K64U UART1 Test Programme!\r\n"); //UART1发送一个字符串
PrintString1("STC8H8K64U UART1 Test Programme!\r\n"); //UART1发送一个字符串
while (1)
{
TX1_write2buff(0x2F); // /
delay_ms(250);
delay_ms(250);
delay_ms(250);
delay_ms(250);
}
}
2.代码编写(接收并回写)
#include "Config.h"
#include "GPIO.h"
#include "UART.h"
#include "Delay.h"
#include "NVIC.h"
#include "Switch.h"
void GPIO_config(void) {
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1; //指定要初始化的IO, P30, P31
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}
void UART_config(void) {
COMx_InitDefine COMx_InitStructure; //结构定义
COMx_InitStructure.UART_Mode = UART_8bit_BRTx; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
COMx_InitStructure.UART_BRT_Use = BRT_Timer1; //选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
COMx_InitStructure.UART_BaudRate = 115200ul; //波特率, 一般 110 ~ 115200
COMx_InitStructure.UART_RxEnable = ENABLE; //接收允许, ENABLE或DISABLE
COMx_InitStructure.BaudRateDouble = DISABLE; //波特率加倍, ENABLE或DISABLE
UART_Configuration(UART1, &COMx_InitStructure); //初始化串口1 UART1,UART2,UART3,UART4
NVIC_UART1_Init(ENABLE,Priority_1); //中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
UART1_SW(UART1_SW_P30_P31); //UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}
void on_uart1_recv() {
u8 i;
// RX_Cnt收到的数据个数(字节u8 - unsigned char)
// 将收到的数据, 按字节逐个循环
for(i=0; i<COM1.RX_Cnt; i++) {
u8 dat = RX1_Buffer[i]; // 1 1 1 1 0 0 0 0 -> 0xF0
TX1_write2buff(dat); //收到的数据原样返回
}
}
/**
开启串口调试,接收数据,把收到的数据原样返回
**/
void main() {
// 初始化IO
GPIO_config();
// 初始化UART
UART_config();
// 开启中断(全局)必须要写!
EA = 1;
// 写一个字节
TX1_write2buff(0x23);
// 通过PrintString1输出字符串
PrintString1("STC8H8K64U UART1 Test Programme!\r\n"); //UART1发送一个字符串
// 通过printf输出字符串
printf("STC8H8K64U UART1 Test Programme!\r\n"); //UART1发送一个字符串
while(1) {
// 超时计数
// 一旦收到了一个字节数据,RX_TimeOut会初始化一个值(例如:5)
if((COM1.RX_TimeOut > 0) && (--COM1.RX_TimeOut == 0))
{
if(COM1.RX_Cnt > 0)
{
// 收到数据了,on_uart1_recv();
on_uart1_recv();
}
// 处理完数据,将数据个数清零
COM1.RX_Cnt = 0;
}
// 注意这里delay代码的位置,属于while
delay_ms(10);
}
}
功能配置
- 配置IO的工作模式:如果不配置工作模式,会导致串口不工作。(UART1的当前代码中的引脚
P3.0
和P3.1
默认是准双向口,可以不配置,但是不要存在侥幸心理,导致其他的串口使用中没有配置准双向口) - 配置UART的串口工作模式
UART_Mode
:
-
UART_ShiftRight
同步移位输出:按位传输,效率低,通常不用。UART_8bit_BRTx
8位数据,可变波特率:常用。发送和接收的数据为8位。UART_9bit
9位数据,固定波特率,即无法在运行时动态更改波特率。UART_9bit_BRTx
9位数据,可变波特率:发送和接收的数据为9位。最后一位为奇偶校验位。
- 配置UART的波特率
RaudRate
:根据实际情况来定,波特率越高,传输越快,但是出现丢帧的概率越高。通常115200
就够用。单位是bit/s
- 配置UART的波特率发生器
BRT_Use
:系统提供了4个发生器,通常一一对应。
-
BRT_Timer1
BRT_Timer2
BRT_Timer3
BRT_Timer4
- 配置UART是否接收
RxEnable
:可以获取RXD接收的数据。 - 配置UART波特率加倍
BaudRateDouble
:默认不加倍,配置加倍会导致波特率是设定的双倍,过高会导致丢帧。 - 配置UART中断
Interrupt
和优先级Priority
:UART的数据收发是通过中断实现的,如果不配置,则无法对外发送数据,TXD和RXD不工作。 - 配置UART的端口
P_SW
:串口通道可以通过几组引脚来实现,但是需要指明是哪一组。
中断开启
由于uart中的发送是通过中断实现的,需要开启,但是STC8还提供了一个总的开关,如果总开关不打开,一样不起作用。
//开启中断
EA = 1;
UART的接收与发送
- 关于接收
接收是通过接收缓冲区进行接收。
在死循环中,间隔一定时间到缓冲区中去取数据,有数据,说明就是接收了
2.关于发送
发送是通过发送缓冲区进行发送。
发送提供了单个字节发送的API: TX1_write2buff(byte)
发送提供了字符串发送的API: PrintString1(str)
配置printf
修改了UART.h
的printf
输出串口配置为UART1
,才能通过printf
打印日志
打开UART.h头文件,保留用到的UART宏
#define UART1 1 //使用哪些串口就开对应的定义,不用的串口可屏蔽掉定义,节省资源
//#define UART2 2
//#define UART3 3
//#define UART4 4
指定printf
函数使用UART1
串口
#define PRINTF_SELECT UART1 //选择 printf 函数所使用的串口,参数 UART1~UART4
串口通信过程
中断系统INT
中断的概念
中断系统是为使 CPU 具有对外界紧急事件的实时处理能力而设置的。
当中央处理机 CPU 正在处理某件事的时候外界发生了紧急事件请求,要求 CPU 暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示 CPU 中断的请求源称为中断源。微型机的中断系统一般允许多人中断源,当几个中新源同时向 CPU 请求中断,要求为它服务的时候,这就存在 CPU 优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU 总是先响应优先级别最高的中断请求
当 CPU 正在处理一个中断源请求的时候(执行相应的中断服务程序),发生了另外一个优先级比它还高的中断源请求。如果 CPU 能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中新系统,没有中断嵌套功能的中断系统称为单级中断系统。
用户可以用关总中断允许位(EA/IE.7)或相应中断的允许位屏蔽相应的中断请求,也可以用打开相应的中断允许位来使 CPU 响应相应的中断申请,每一个中断源可以用软件独立地控制为开中断或关中断状态,部分中断的优先级别均可用软件设置。高优先级的中断请求可以打断低优先级的中断,反之,低优先级的中断请求不可以打断高优先级的中断。当两个相同优先级的中断同时产生时,将由查询次序来决定系统先响应哪个中断。
中断源
能请示CPU中断的请求源为中断源。STC8H中的中断源如下图
中断函数
通过 interrupt
关键字定义中断函数。示例如下
void UART1_int (void) interrupt 0
{
}
UART1_int
是中断函数的名称,可以随意取,按照自己的需求定interrupt
是中断函数的标记,说明当前函数是中断函数0
是中断次序,这个就需要根据自己业务,查询用户手册来定。
中断函数,可以理解为回调函数,就是这个函数定义出来了,在什么时机调用,不是我们做的,是系统自己调用的。而我们关心的是,某个事件触发了这个函数调用,我们可以在这个函数中写自己的逻辑。
验证Uart的中断函数
接收时亮灯,发送时灭灯
sfr P5M1 = 0xC9;
sfr P5M0 = 0xCA;
sfr P5 = 0xC8;
sbit P53 = P5^3;
sfr T2L = 0xd7;
sfr T2H = 0xd6;
sfr AUXR = 0x8e;
sfr IE = 0xA8;
sbit EA = IE^7;
sbit ES = IE^4;
sfr SCON = 0x98;
sfr SBUF = 0x99;
sbit RI = SCON^0;
sbit TI = SCON^1;
void uart_hello(void) interrupt 4 {
if(RI) {
// 如果接收寄存器RI触发了中断,打开灯
RI = 0;
P53 = 1;//开
}
if(TI) {
// 如果发送寄存器TI触发了中断,关掉灯
TI = 0;
P53 = 0;//关
}
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 57;
j = 27;
k = 112;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
int main() {
P5M1 &= ~0x08, P5M0 |= 0x08; //推挽输出
SCON = 0x50;
T2L = 0xe8; //65536-11059200/115200/4=0FFE8H
T2H = 0xff;
AUXR = 0x15;//启动定时器
EA = 1;
ES = 1;
P53 = 0;
while(1) {
// 休眠1000ms
Delay1000ms();
// 发送一个数据0x11
SBUF = 0x11;
// 将TI位寄存器置为1 (这里可以不设置, 只要给SBUF赋值了, TI也会自动变为1)
TI = 1;
}
}
完成的内容有:
- 配置Uart初始化,包括定时发生器
- 查询几个寄存器地址:SBUF,IE
系统时钟
时间与周期
系统时钟
系统时钟是指计算机中用于控制各个设备协调工作的定时器。它是计算机的主频,是CPU和外设工作的基础,通常表示为以赫兹为单位的频率,如1MHz,10MHz等等。
系统时钟的时钟信号,通常以晶振的形式提供。STC8H单片机支持外部晶振和内部晶振两种时钟源,可以通过相应的配置来选择使用哪种时钟源。
时钟周期
时钟周期是系统时钟一个完整的周期所需的时间。它的倒数就是时钟频率,即每秒钟发生的时钟周期数。例如,STC8H的时钟频率为24MHz
,那么每个时钟周期的时间就是1/24MHz=41.67ns
。
机器周期
也叫做指令周期。指令周期是一条指令的执行时间。
早期的STC8H单片机的机器周期为12个时钟周期。现在的STC8H可以有两种配置,一个是1T,一个是12T。
- 12T也就是早期的配置,假设当系统时钟为24MHz时,每个机器周期的时间就是
12 * 41.67ns = 500ns
。 - 1T是芯片架构升级后的,每个机器周期的时间为
1 * 41.67ns = 41.67ns.
。
NOP指令
NOP指令是一种汇编指令,表示“no operation”(不执行任何操作)。它不会改变寄存器的值,也不会修改存储器中的数据。在程序中插入NOP指令可以用于延时或调整代码的执行顺序。
在大多数处理器中,NOP指令会被翻译成一个或多个机器指令来实现其“不执行任何操作”的效果。在STC8H单片机中,NOP指令被翻译成一条长度为1个字节的指令,不做任何操作。
NOP指令在某些情况下也被用于填充一些未使用的空间,使程序的大小达到特定的大小或对齐要求。在编写汇编代码时,程序员可以在代码中插入NOP指令来占用空间,使得代码和数据能够对齐在内存中的特定地址上,以提高程序的执行效率。
我们可以理解为让程序执行时,睡1个NOP指令周期的时长。
库函数系统时钟配置
在config.h
中,配置系统时钟频率。
//#define MAIN_Fosc 22118400L //定义主时钟
//#define MAIN_Fosc 12000000L //定义主时钟
//#define MAIN_Fosc 11059200L //定义主时钟
//#define MAIN_Fosc 5529600L //定义主时钟
#define MAIN_Fosc 24000000L //定义主时钟
测试不同时钟的执行周期
#include "config.h"
#include "GPIO.h"
#include "delay.h"
void GPIO_config(void) {
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
GPIO_InitStructure.Pin = GPIO_Pin_3; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P5, &GPIO_InitStructure);//初始化
}
int main() {
GPIO_config();
while(1) {
P53 = 1;
NOP1();
P53 = 0;
//NOP1();
}
}
总结:
- 主频越高,执行速度越快。
- 主频越高,干扰越强,越容易出现问题
extern关键字
extern理解
extern是C语言中的一个关键字,用于说明一个全局变量或函数的定义不在本文件中,而在其他文件中,告诉编译器该变量或函数已经在别的文件中定义过了。
在C语言中,如果要在一个源文件中使用另一个源文件中定义的全局变量或函数,需要使用extern关键字声明一下该变量或函数,这样编译器才能知道该变量或函数已经在其他文件中定义过了。
extern变量
以下是extern关键字的用法和示例:
在一个源文件中定义全局变量,然后在另一个源文件中使用该变量:
int global_var = 10;
#include <stdio.h>
extern int global_var; // 声明全局变量
int main() {
printf("%d\n", global_var); // 使用全局变量
return 0;
}
extern函数
再一个源文件中定义函数,然后在另一个源文件中使用该函数:
int add(int a, int b) {
return a + b;
}
extern int add(int a, int b); // 声明函数
int main() {
int result = add(1, 2); // 使用函数
printf("%d\n", result);
return 0;
}
头文件中定义
需要注意的是,如果在一个源文件中定义了一个全局变量或函数,并且该变量或函数要在多个源文件中使用,那么需要将该变量或函数的定义放在一个头文件中,并在其他源文件中包含该头文件。
例如,将上面的global_var
和add
函数的定义放在一个头文件中:
extern int global_var;
extern int add(int a, int b);
然后在其他源文件中包含该头文件即可使用该变量和函数:
// myimpl.c
#include "myheader.h"
int global_var = 10;
int add(int a, int b) {
return a + b;
}
// test.c
#include <stdio.h>
#include "myheader.h"
int main() {
printf("%d\n", global_var);
int result = add(1, 2);
printf("%d\n", result);
return 0;
}
定时器Timer
定时器
定时器是一种计时装置,通常由一个晶体振荡器提供时钟信号,可以计时一定的时间后执行相应的操作。在单片机中,定时器一般是由计数器和时钟源组成的,可以用来产生一定时间间隔的中断信号,或者用于测量输入信号的周期和占空比等。定时器通常具有多种工作模式和计数方式,可以灵活地应用于各种场合。
STC8H内置了5个16位定时器:T0,T1,T2,T3,T4.
Timer案例
使用定时器,控制板载LED高低电平输出。
#include "Config.h"
#include "Timer.h"
#include "GPIO.h"
#include "NVIC.h"
void GPIO_config(void) {
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
GPIO_InitStructure.Pin = GPIO_Pin_3; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P5, &GPIO_InitStructure); //初始化
}
//int arr[];
//int counter = 3;
void TIMER_config(void) {
TIM_InitTypeDef TIM_InitStructure; //结构定义
//定时器0做16位自动重装, 中断频率为100000HZ,中断函数从P6.7取反输出50KHZ方波信号.
TIM_InitStructure.TIM_Mode = TIM_16BitAutoReload; //指定工作模式, TIM_16BitAutoReload,TIM_16Bit,TIM_8BitAutoReload,TIM_16BitAutoReloadNoMask
TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_1T; //指定时钟源, TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext
TIM_InitStructure.TIM_ClkOut = DISABLE; //是否输出高速脉冲, ENABLE或DISABLE
TIM_InitStructure.TIM_Value = 65536UL - (MAIN_Fosc / 1000UL); // 初值,指定Timer频率 1000hz (每秒执行1000次,每次1ms(周期))
// 不要小于367hz (2.7ms周期)
// 不要大于1 000 000hz 一百万 (1us周期)
TIM_InitStructure.TIM_Run = ENABLE; //是否初始化后启动定时器, ENABLE或DISABLE
Timer_Inilize(Timer0,&TIM_InitStructure); //初始化Timer0 Timer0,Timer1,Timer2,Timer3,Timer4
NVIC_Timer0_Init(ENABLE,Priority_0); //中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
}
void main(){
GPIO_config();
TIMER_config();
// 开启全局中断
EA = 1;
P53 = 0; // 熄灯
while(1);
}
void Timer0_ISR_Handler (void) interrupt TMR0_VECTOR //进中断时已经清除标志
{
// TODO: 在此处添加用户代码
P53 = ~P53;
}
定时器配置理解
工作模式
工作模式指的是计数方式,timer的计数是在主频计数的基础上,来进行数数的。timer有16位的计数器,通过计数器来计数来确定定时器运行的时长,在关键位置触发定时中断。
●16位自动重装载模式:可以被设置成定时或者计数两种模式,每当定时器溢出时就会触发中断或者输出信号。
●16位不可重装载模式:计数值达到设定值后,定时器就会停止计数,需要重新初始化才能继续计数。
●8位自动重装载模式:8位计数器溢出时触发中断或输出信号。
●不可屏蔽中断的16位自动重装载模式:16位计数器溢出时触发中断或输出信号,并且可以通过软件或硬件方式清除定时器计数器的值。
通常使用16位自动重装载模式.
中断配置
中断配置是为了打开中断开关的,从而可以触发中断回调的,如果不配置,将无法触发中断回调。
时钟源
可配置的是重要有两个:
●1T: 跟随主频。
●12T: 进行12分频。
是否输出高速脉冲
TIM_ClkOut,可以配置DISABLE或者ENABLE
如果配置ENABLE,则P3.5端口会同步输出时钟脉冲
时钟周期设置
时钟周期指的是1秒钟执行多少次timer中断。
TIM_InitStructure.TIM_Value = 65536UL - (MAIN_Fosc / 10000UL);
以上配置中,TIM_InitStructure.TIM_Value
最终会转化位寄存器配置。
其中,10000UL
表示的就是时钟周期,意思就是这个timer回调1秒钟要调用 10000
次。
注意时钟周期的取值范围,通过以上数学公式,(MAIN_Fosc / Timer频率)
不能大于65536UL
;理论上时钟周期可以无限大,经过测试,最大值为500000UL
,也就是2us
调用1次。但是,我们要考虑到,如果timer设置到这么高的频率,你在回调中执行的代码时长就不能超过这个值。如果是24M主频,1个时钟周期为 1/24MHz=41.67ns,但是一个指令通常由多个时钟周期组成,一段代码又由多个指令组成,这么一算,可做的操作就很少了。因此我们不要设置得那么大。
LED灯
原理图
控制分析
S8550 PNP 特性
B: base, 基极。(理解:基于/根据 这个条件做什么事情)
E: emitter, 发射极。(理解:发射端)
C: collector, 集电极。(理解:收集电的区域,用电的器件在这个区域)
PNP型三极管,E极为输入端,C极为输出端,B极为控制端
B极 为高电平时,E极到C极的电路截止,无法导通。
B极 为低电平时,E极到C极的电路打开,正常导通。
开关控制
通过引脚 LED_SW
来控制 B极是否为高低电平来控制是否导通
LED控制
通过LED的负极控制灯是否亮。如果负极为低则亮,负极为高则不亮。
功能设计
点亮LED
点亮灯泡1
几种GPIO模式
- 准双向口,也称为弱上拉模式,可做输入和输出操作,电流小,通常作为信号功能使用
- 推挽输出,也称为强上拉模式,作为输出操作,电流持续,作为功率输出
- 开漏输出,可做输入和输出操作,需要外部提供上拉电阻
- 高阻输入,电流无法输入,但是可以外部输入电平会拉高或拉低其位寄存器,用于数模转换
三极管特点
三极管是电流控制的器件,如果需要三极管导通或是关闭,需要持续给B极输入电流。(相对于mos管而言,三极管功耗较大,mos管耗电要少很多)
示例代码
#include "config.h"
#include "GPIO.h"
#include "delay.h"
void GPIO_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
GPIO_InitStructure.Pin = GPIO_Pin_7; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P2,&GPIO_InitStructure); //初始化
GPIO_InitStructure.Pin = GPIO_Pin_5; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P4,&GPIO_InitStructure); //初始化
}
int main() {
//GPIO 初始化
GPIO_config();
// led 开关打开
P45 = 0;
while(1) {
P27 = 1;
delay_ms(250);
delay_ms(250);
delay_ms(250);
delay_ms(250);
P27 = 0;
delay_ms(250);
delay_ms(250);
delay_ms(250);
delay_ms(250);
}
return 0;
}
走马灯
实现灯的顺序点亮
#include "config.h"
#include "GPIO.h"
#include "delay.h"
#define LED1 P27
#define LED2 P26
#define LED3 P15
#define LED4 P14
#define LED5 P23
#define LED6 P22
#define LED7 P21
#define LED8 P20
#define LED_SW P45
void GPIO_config(void) {
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
GPIO_InitStructure.Pin = GPIO_Pin_5; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);//初始化
GPIO_InitStructure.Pin = GPIO_Pin_4 | GPIO_Pin_5; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P1, &GPIO_InitStructure);//初始化
GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_6 | GPIO_Pin_7; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);//初始化
}
int main() {
int i;
GPIO_config();
EA = 1;
LED_SW = 0;
while(1) {
for(i = 0;i < 8;i++) {
LED1 = i == 0 ? 0 : 1;
LED2 = i == 1 ? 0 : 1;
LED3 = i == 2 ? 0 : 1;
LED4 = i == 3 ? 0 : 1;
LED5 = i == 4 ? 0 : 1;
LED6 = i == 5 ? 0 : 1;
LED7 = i == 6 ? 0 : 1;
LED8 = i == 7 ? 0 : 1;
delay_ms(100);
}
for(i = 7;i >= 0;i--) {
LED1 = i == 0 ? 0 : 1;
LED2 = i == 1 ? 0 : 1;
LED3 = i == 2 ? 0 : 1;
LED4 = i == 3 ? 0 : 1;
LED5 = i == 4 ? 0 : 1;
LED6 = i == 5 ? 0 : 1;
LED7 = i == 6 ? 0 : 1;
LED8 = i == 7 ? 0 : 1;
delay_ms(100);
}
}
}
LED呼吸灯(PWM)
PWM基础概念
PWM全称是脉宽调制(Pulse Width Modulation),是一种通过改变信号的脉冲宽度来控制电路输出的技术。PWM技术在工业自动化、电机控制、LED调光等领域广泛应用。
PWM是一种将数字信号转换为模拟信号的技术,它通过改变信号的占空比来控制输出的电平。在STC8H中,PWM输出的频率和占空比可以由程序控制,因此可以用来控制各种电机、灯光和其他设备的亮度、速度等参数。
STC8H芯片
STC8H 系列的单片机内部集成了8 通道 16 位高级PWM 定时器,分成两周期可不同的 PWM,分别命名为 PWMA 和PWMB ,可分别单独设置。
第一组 PWMA 可配置成4 组互补/对称/死区控制的PWM 或捕捉外部信号。
第二组 PWMB 可配置成4 路PWM 输出或捕捉外部信号。
两组 PWM 的时钟频率可分别独立设置。
PWM与引脚对应关系如下图:
PWM |
PWM通道 |
对应引脚 |
|
PWMxP |
PWMxN |
||
PWMA |
PWM1P & PWM1N |
P1.0 |
P1.1 |
P2.0 |
P2.1 |
||
PWM2P & PWM2N |
P5.4 |
P1.3 |
|
P2.2 |
P2.3 |
||
PWM3P & PWM3N |
P1.4 |
P1.5 |
|
P2.4 |
P2.5 |
||
PWM4P & PWM4N |
P1.6 |
P1.7 |
|
P2.6 |
P2.7 |
||
P3.4 |
P3.3 |
||
PWMB |
PWM5 |
P0.0 |
|
P1.7 |
|||
P2.0 |
|||
PWM6 |
P0.1 |
||
P2.1 |
|||
P5.4 |
|||
PWM7 |
P0.2 |
||
P2.2 |
|||
P3.3 |
|||
PWM8 |
P0.3 |
||
P2.3 |
|||
P3.4 |
PWMA应用
控制引脚P2.7实现LED灯1的呼吸效果。
1.拷贝所需库文件(其他必备库请自行准备)
STC8H_PWM.c
STC8H_PWM.h
NVIC.c
NVIC.h
Switch.h
2.导入头文件,初始化宏及全局变量
#include "Config.h"
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "Switch.h"
#include "STC8H_PWM.h"
#define LED_SW P45
#define LED1 P27
#define LED2 P26
#define LED3 P15
#define FREQ 1000
#define PERIOD ((MAIN_Fosc / FREQ) - 1) // 周期
PWMx_Duty dutyA;
3.配置GPIO
void GPIO_config(void) {
GPIO_InitTypeDef GPIO_InitStructure; //结构定义
// LED_SW
GPIO_InitStructure.Pin = GPIO_Pin_5; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);//初始化
// P2
GPIO_InitStructure.Pin = GPIO_Pin_6 | GPIO_Pin_7; //指定要初始化的IO,
GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);//初始化
}
4.配置PWM
void PWM_config(void)
{
PWMx_InitDefine PWMx_InitStructure;
// 配置PWM4
PWMx_InitStructure.PWM_Mode = CCMRn_PWM_MODE2; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
PWMx_InitStructure.PWM_Duty = 0; //PWM占空比时间, 0~Period
PWMx_InitStructure.PWM_EnoSelect = ENO4P | ENO4N; //输出通道选择, ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
PWM_Configuration(PWM4, &PWMx_InitStructure);
// 配置PWMA
PWMx_InitStructure.PWM_Period = PERIOD; //周期时间, 0~65535
PWMx_InitStructure.PWM_DeadTime = 0; //死区发生器设置, 0~255
PWMx_InitStructure.PWM_MainOutEnable= ENABLE; //主输出使能, ENABLE,DISABLE
PWMx_InitStructure.PWM_CEN_Enable = ENABLE; //使能计数器, ENABLE,DISABLE
PWM_Configuration(PWMA, &PWMx_InitStructure); //初始化PWM通用寄存器, PWMA,PWMB
// 切换PWM4选择PWM4_SW_P26_P27
PWM4_SW(PWM4_SW_P26_P27); //PWM4_SW_P16_P17,PWM4_SW_P26_P27,PWM4_SW_P66_P67,PWM4_SW_P34_P33
// 初始化PWMA的中断
NVIC_PWM_Init(PWMA,DISABLE,Priority_0);
}
5.编写Main函数
void main() {
char direction = 1;
u8 duty_percent = 0;// 0 -> 100
EAXSFR(); /* 扩展寄存器访问使能, 必写! */
GPIO_config();
PWM_config();
EA = 1;
// 总开关
LED_SW = 0;
LED1 = 0; // P2.7 PWM4
LED2 = 0;
LED3 = 0;
// 循环之前,设置一次pwm(可选)
dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
UpdatePwm(PWM4, &dutyA);
// 0 -> 100
while(1) {
duty_percent += direction;
// 让duty_percent一直在0-100来回往返
if(duty_percent >= 100) {
d