《嵌入式系统开发》系列专栏主要以LPC1100系列微控制器为硬件平台,详细介绍Cortex—-M0微控制器的原理与开发技术,基于keil仿真软件平台设计最小应用系统板和具有在板仿真器的口袋开发板以及相关例程。
本文已收录于嵌入式系统开发系列专栏:嵌入式系统开发 欢迎订阅,持续更新。
【嵌入式系统开发】Keil 实现十次作业详细代码目录
作业1 利用delay()函数实现led闪烁
作业2:利用系统节拍定时器实现LED灯闪烁
作业3 按键输入控制LED灯闪烁频率
作业4 设置16位定时器1工作在PWM模式
作业5 UART数据传输
作业6:LM75BD温度检测
作业7FLASH存储器操作
作业8 ADC模数转换
作业9 电源管理与功率控制
作业10:RTC时间设置与读取
作业1 利用delay()函数实现led闪烁
实现两秒亮灭
#include "LPC11xx.h" // 引入LPC11xx芯片的头文件,该文件中定义了LPC11xx的寄存
void LED_Init(void) {
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 6); // 使能GPIO的时钟,将GPIO所在的AHB总线时钟位设置为1
LPC_GPIO1->DIR |= (1UL << 9); // 配置GPIO1.9为输出引脚
}
void LED_On(void) {
LPC_GPIO1->DATA &= ~(1UL << 9); // 设置GPIO1.9输出低电平,点亮LED
}
void LED_Off(void) {
LPC_GPIO1->DATA |= (1UL << 9); // 设置GPIO1.9输出高电平,熄灭LED
}
void Delay_1s(void){
int i=SystemCoreClock/5; // 计算循环次数,延时1秒
while(--i); // 进行延时
}
int main (void) {
LED_Init(); // 初始化LED
while (1) {
LED_On(); // 点亮LED
Delay_1s(); // 延时1秒
LED_Off(); // 熄灭LED
Delay_1s(); // 延时1秒
}
return 0;
}
作业2:利用系统节拍定时器实现LED灯闪烁
通过系统节拍定时器,中断服务子程序,完成小灯不停的闪烁(亮灭),闪烁间隔3秒,并为每一行代码写汉语注释
#include "LPC11xx.h" /* 引入头文件"LPC11xx.h" */
extern volatile unsigned char clock_1s; /*外部变量声明 */
/*系统节拍定时器中断处理函数*/
void SysTick_Handler (void) {
static unsigned long ticks; /* 静态无符号长整型变量声明*/
if (ticks++ >= 149) { /*定时10ms,循环150次 */
ticks = 0; /*初始化赋值变量 ticks */
clock_1s = 1; /*设置标志 */
}
} /*中断设置,按照节拍定时器所给的时间进行中断,我们给定10ms执行一次,实现LED闪烁间隔3s */
/* LED初始化设置 */
void LED_Init(void) {
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 6); /* GPIO 时钟使能 */
LPC_GPIO1->DIR |= (1UL << 9); /*将 GPIO1_9 作为输出口*/
}
/*状态翻转函数,使用一次后,灯的状态翻转一次 */
void LED_Toggle(void) {
LPC_GPIO1->DATA ^= (1UL <<9); /* LED 闪烁切换 */
}
/*主函数*/
int main (void) { /* 主程序开始 */
SysTick_Config(SystemCoreClock/100); /* 定时器10ms定时一次 */
LED_Init(); /* LED 初始化 */
while (1) { /* 无限循环,以实现小灯的不停闪烁 */
if(clock_1s){ /* clock_1s = 1时,中断执行结束 */
clock_1s = 0; /*标志*/
LED_Toggle(); /* 执行LED状态翻转函数 */
}
}
}
作业3 按键输入控制LED灯闪烁频率
在Keil MDK 4.74上编写一段程序,系统上电,系统工作频率为48MHz,RGB红色灯亮,利用系统节拍定时器SysTick实现定时1s,控制LPC1114微控制器的GPIO引脚PIO1_9状态反转(LED灯BLINKY闪烁),设置按键PIO0_1,PIO1_4,PIO1_8,为GPIO输入,设置中断方式判断按键状态。
按键功能:1. PIO1_4按键按下增加定时时间,最大2s,PIO1_8按键按下减少定时时间,最小100ms,步进间隔100ms,适当考虑按键防抖功能。2. 按键PIO0_1按下,降低系统工作频率为24MHz,RGB绿色灯亮,观察一下LED灯BLINKY闪烁的频率有无变化?分析原因;再次按下,系统工作频率恢复到48MHz,RGB红色灯亮,观察LED灯BLINKY闪烁的频率。
#include "LPC11xx.h" /* LPC11xx definitions */
/* Followling definitions are associated with function */
#define CLOCK_SETUP 1
#define SYSOSCCTRL_Val 0x00000000 // Reset: 0x000
#define WDTOSCCTRL_Val 0x00000000 // Reset: 0x000
#define SYSPLLCTRL_Val 0x00000023 // Reset: 0x000
#define SYSPLLCTRL24_Val 0x00000041 // M=2,P=4
#define SYSPLLCLKSEL_Val 0x00000001 // Reset: 0x000
#define MAINCLKSEL_Val 0x00000003 // Reset: 0x000
#define SYSAHBCLKDIV_Val 0x00000001 // Reset: 0x001
volatile unsigned char clock_1s; /* Blink symble when 1 to blink */
uint32_t d=10;
uint8_tb=1; /* Frequency symble when 1 is 48MHz*/
uint32_ts=999; /* The interrupt cycles */
/*----------------------------------------------------------------------------
The Subfunction
*----------------------------------------------------------------------------*/
void LED_Init(void) {
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 6); /*enable clock for GPIO*/
LPC_GPIO1->DIR |= (1UL << 9); /*configure GPIO1_9 as output*/
LPC_GPIO2->DIR |= (1UL << 9);/*configure GPIO2_9 as output*/
LPC_GPIO2->DIR |= (1UL << 8);/*configure GPIO2_8 as output*/
LPC_GPIO2->DATA &= ~(1UL <<9);/*turn on red1_9*/
LPC_GPIO2->DATA |= (1UL << 8);/*turn off green1_8*/
}
void Key_Init(void){
LPC_GPIO0->DIR &=~(1UL<<1); /*configure GPIO0_1 as input*/
LPC_GPIO1->DIR &=~(1UL<<4); /*configure GPIO1_4 as intput*/
LPC_GPIO1->DIR &=~(1UL<<8); /*configure GPIO1_8 as intput*/
LPC_IOCON->PIO0_1 &= ~0x07;
LPC_IOCON->PIO0_1 |= 0x00; /*configure PIO0_1 as GPIO*/
LPC_IOCON->PIO1_4 &= ~0x07;
LPC_IOCON->PIO1_4 |= 0x00;/*configure PIO1_4 as GPIO*/
LPC_IOCON->PIO1_8 &= ~0x07;
LPC_IOCON->PIO1_8 |= 0x00;/*configure PIO1_8 as GPIO*/
LPC_GPIO0->IEV &=~(1UL<<1); /*falling edge trigger*/
LPC_GPIO0->IE |=(1UL<<1); /*Open GPIO0_1 interrupt function*/
LPC_GPIO1->IEV &=~(1UL<<4); /*falling edge trigger*/
LPC_GPIO1->IE |=(1UL<<4); /*Open GPIO1_4 interrupt function*/
LPC_GPIO1->IEV &=~(1UL<<8); /*falling edge trigger*/
LPC_GPIO1->IE |=(1UL<<8); /*Open GPIO1_8 interrupt function*/
NVIC_EnableIRQ(EINT0_IRQn); /*Use CMSIS P0 inner function*/
NVIC_EnableIRQ(EINT1_IRQn); /*Use CMSIS P1 inner function*/
}
void LED_Toggle(void) {
LPC_GPIO1->DATA ^= (1UL <<9); /* Toggle the BLINKY LED */
}
void SystemInit24 (void) { /* Change the system frequency to 24MHz */
volatile uint32_t i;
#if (CLOCK_SETUP) /* Clock Setup */
#if ((SYSPLLCLKSEL_Val & 0x03) == 1)
LPC_SYSCON->PDRUNCFG &= ~(1 << 5); /* Power-up System Osc */
LPC_SYSCON->SYSOSCCTRL = SYSOSCCTRL_Val;
for (i = 0; i < 200; i++) __NOP();
#endif
LPC_SYSCON->SYSPLLCLKSEL = SYSPLLCLKSEL_Val; /* Select PLL Input */
LPC_SYSCON->SYSPLLCLKUEN= 0x01; /* Update Clock Source */
LPC_SYSCON->SYSPLLCLKUEN = 0x00; /* Toggle Update Register */
LPC_SYSCON->SYSPLLCLKUEN = 0x01;
while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01)); /* Wait Until Updated */
#if ((MAINCLKSEL_Val & 0x03) == 3) /* Main Clock is PLL Out */
LPC_SYSCON->SYSPLLCTRL = SYSPLLCTRL24_Val; /* SYSPLLCTRL24_Val 0x00000041 0100 0001 M=2,10 P=4*/
LPC_SYSCON->PDRUNCFG &= ~(1 << 7); /* Power-up SYSPLL */
while (!(LPC_SYSCON->SYSPLLSTAT & 0x01)); /* Wait Until PLL Locked */
#endif
#if (((MAINCLKSEL_Val & 0x03) == 2) )
LPC_SYSCON->WDTOSCCTRL = WDTOSCCTRL_Val;
LPC_SYSCON->PDRUNCFG &= ~(1 << 6); /* Power-up WDT Clock */
for (i = 0; i < 200; i++) __NOP();
#endif
LPC_SYSCON->MAINCLKSEL = MAINCLKSEL_Val; /* Select PLL Clock Output */
LPC_SYSCON->MAINCLKUEN = 0x01; /* Update MCLK Clock Source */
LPC_SYSCON->MAINCLKUEN = 0x00; /* Toggle Update Register */
LPC_SYSCON->MAINCLKUEN = 0x01;
while (!(LPC_SYSCON->MAINCLKUEN & 0x01)); /* Wait Until Updated */
LPC_SYSCON->SYSAHBCLKDIV = SYSAHBCLKDIV_Val;
#endif
}
void SysTick_Handler(void){
static unsigned long ticks;
if(ticks++ >=s){ /*Set Clocks to 1 every (s+1)ms*/
ticks = 0;
clock_1s = 1
}
void CutShiver(uint32_t t) /* Eliminate the key jitter */
{while(t--) ;
}
void PIOINT0_IRQHandler(void){ /* The interrupt tiggering event of P0 */
if((LPC_GPIO0->DATA&(1<<1))==0) /* Determine whether GPIO0_1 is pressed */
{
CutShiver(0xff); /* Eliminate the key jitter */
if((LPC_GPIO0->DATA&(1<<1))==0) /* Prevent misjudgment */
{
while((LPC_GPIO0->DATA&(1<<1))==0); /*waiting for release key*/
if(b==1) /* Determine the current system frequency */
{LPC_GPIO2->DATA |= (1UL <<9); /*turn off red*/
LPC_GPIO2->DATA &= ~ (1UL <<8); /*turn on green*/
SystemInit24(); /* Change the system frequency to 24MHz */
SystemCoreClockUpdate(); /* Update the core clock */
b=0; /* Change the frequency symble */
LPC_GPIO0->IC |=(1<<1); /*Clear the interrupt*/
}
lse
{
LPC_GPIO2->DATA|= (1UL <<8); /*turn off green*/
LPC_GPIO2->DATA &= ~(1UL <<9); /*turn on red*/
SystemInit(); /* Change the system frequency to 48MHz */
SystemCoreClockUpdate(); /* Update the core clock */
b=1; /* Change the frequency symble */
LPC_GPIO0->IC |=(1<<1); /*Clear the interrupt*/
}
}
}
}
void PIOINT1_IRQHandler(void){ /* The interrupt tiggering event of P1 */
if((LPC_GPIO1->DATA&(1<<4))!=(1<<4)) /* Determine whether GPIO1_4 is pressed */
{ CutShiver(0xff); /* Eliminate the key jitter */
if((LPC_GPIO1->DATA&(1<<4))!=(1<<4)) /* Prevent misjudgment */
{while((LPC_GPIO1->DATA&(1<<4))==0); /*waiting for release key*/
if(d<20)
d=d+1;
s=d*100-1; /* Add 100ms to the delay */
LPC_GPIO1->IC |= (1<<4); /*Clear the interrupt*/
}
}
else if((LPC_GPIO1->DATA&(1<<8))!=(1<<8)) /* Determine whether GPIO1_8 is pressed */
{CutShiver(0xff);/* Eliminate the key jitter */
if((LPC_GPIO1->DATA&(1<<8))!=(1<<8)) /* Prevent misjudgment */
{while((LPC_GPIO1->DATA&(1<<8))==0); /*waiting for release key*/
if(d>1)
d=d-1;
s=d*100-1; /* Reduce 100ms to the delay */
LPC_GPIO1->IC |= (1<<8); /*Clear the interrupt*/
}
}
}
/*----------------------------------------------------------------------------
MAIN function
*----------------------------------------------------------------------------*/
int main (void) { /* Main Program */
LED_Init(); /*LED gpio init*/
Key_Init();/*KEY gpio init*/
SystemInit(); /*Init system frequence to the 48MHz*/
SysTick_Config(SystemCoreClock/1000); /*Generate an interruption every 1ms*/
while(1){
if(clock_1s)
{clock_1s=0;/* Reload the blink symble */
LED_Toggle(); /* Toggle the BLINKY LED */
}
}
}
作业4 设置16位定时器1工作在PWM模式
(1)利用16位定时器1实现定时1s,控制LPC1114微控制器的GPIO引脚PIO1_9上的LED灯状态反转(可以用中断方式也可以用匹配输出功能);
(2)利用另外一个定时器定时增大或者减小占空比,实现PIO1_9上的LED灯渐亮渐灭的呼吸灯效果,改变呼吸的频率观察效果。
//第一题
#include "lpc11xx.h" // 包含LPC11xx的头文件
void LED_init(void) // LED初始化函数
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 6); // 使能GPIO的时钟
LPC_GPIO1->DIR |= (1UL << 9); // GPIO1.9设置为输出模式
LPC_GPIO1->DATA &= ~(1UL << 9); // GPIO1.9输出低电平
}
void LED_Toggle(void) // LED闪烁函数
{
LPC_GPIO1->DATA ^= (1UL << 9); // 取反GPIO1.9的输出电平
}
void TMR16B1_COUNT_Init(void) // 16位定时器初始化函数
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 8); // 使能定时器的时钟
LPC_TMR16B1->IR = 0x1F; // 定时器中断标志寄存器清零
LPC_TMR16B1->PR = 999; // 定时器分频系数为1000
LPC_TMR16B1->MCR = 3; // 匹配后TC复位并中断
LPC_TMR16B1->MR0 = SystemCoreClock / 1000; // 定时器的匹配寄存器设为1ms
LPC_TMR16B1->TCR = 0x01; // 使能定时器
NVIC_EnableIRQ(TIMER_16_1_IRQn); // 使能定时器中断
}
void TIMER16_1_IRQHandler(void) // 定时器中断处理函数
{
if ((LPC_TMR16B1->IR |= 0x01) == 1) // 判断是否为定时器匹配中断
LED_Toggle(); // LED闪烁
}
int main() // 主函数
{
LED_init(); // 调用LED初始化函数
TMR16B1_COUNT_Init(); // 调用定时器初始化函数
while(1); // 一直循环
return 0; // 返回0
}
//第二题
#include "lpc11xx.h"
int flag=1; //flag 加减标志
float x=0; //占空比
void LED_init(void) //LED initial
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 6); /* enable clock for GPIO */
/* configure GPIO1.9 as output */
LPC_GPIO1->DIR |= (1UL << 9);
//PC_GPIO1->DATA &= ~(1UL << 9);
}
void TMR16B1_PWM_Init(void) /*16位定时器1初始化*/
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 8);//16位定时器1使能
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 16);//开启时钟
LPC_IOCON->PIO1_9 |= 0x01; //P1_9复用为mat0
LPC_TMR16B1->PR = 0; //分频1
LPC_TMR16B1->PWMC= 0x01; //MAT0为PWM模式
LPC_TMR16B1->MCR = 0x02 <<9; //匹配后TC复位且不中断
LPC_TMR16B1->MR3 = SystemCoreClock/1000; //MR3 1ms通道3计时
LPC_TMR16B1->MR0 = LPC_TMR16B1->MR3;//输出
LPC_TMR16B1->TCR = 0x01; //使能定时器1
}
void TMR32B0_Init(void){ /*32位定时器0初始化*/
LPC_SYSCON->SYSAHBCLKCTRL|=(1UL<<9); //使能
LPC_TMR32B0->IR=0x1F; //中断清0
LPC_TMR32B0->PR=0; //分频1
LPC_TMR32B0->MCR=3; //匹配后复位TC并中断
LPC_TMR32B0->MR0=SystemCoreClock/10; //中断时间0.1s
LPC_TMR32B0->TCR=0x01; //使能定时器
NVIC_EnableIRQ(TIMER_32_0_IRQn); //设置中断并使能
}
void TIMER32_0_IRQHandler(void){ //中断子程序
if((LPC_TMR32B0->IR|=0x01)==1){ //判断MR0是否中断并清零中断
if(x<=0.13) //周期改变占空比x<0.13则不断增加占空比,到达0.92后不断减少
flag=1;
else if(x>=0.92)
flag=0;
if(flag==1) //经过不断实验调试发现此数值能得到较好的呼吸灯结果
x=x+0.05;
else if(flag==0)
x=x-0.05;
}
}
int main()
{
LED_init();
TMR32B0_Init();
TMR16B1_PWM_Init();
while(1)
{
LPC_TMR16B1->MR0 = x*LPC_TMR16B1->MR3; //不同占空比输出
}
}
作业5 UART数据传输
(1)初始化LPC1114微控制器UART串行口,设置波特率为115200,数据位:8,停止位:1;校验位:无;中断使能;
(2)利用中断方式等待接收数据,利用PC串口调试助手向LPC1114发送一个16进制数据0xAA,LPC1114接收到数据后,发送一个十六进制数据0x55,串口调试助手应该能够正确显示接收到的数据。
(3)尝试使用FIFO连续发送和接收一组数据(不超过16字节)。
第一题程序
#include "LPC11xx.h" /* LPC11xx definitions */
int i=0;
uint8_t test=0x55;//发送数据
char wro[5]="error";//若输入其他则返回错误
uint8_t data[8];//接收缓冲区
void UART_init(void)
{
LPC_SYSCON->SYSAHBCLKCTRL|=((1UL<<6)|(1UL<<16));//使能IOCON时钟:SYSAHBCLKCTRL寄存器的16位为IO配置块时钟使能位
LPC_IOCON->PIO1_6=(1UL<<0);//IOCON->PIO1_6寄存器的2:0位为001则将P1.6设置为RXD
LPC_IOCON->PIO1_7=(1UL<<0); //IOCON->PIO1_7寄存器的2:0位为001则将P1.7设置为TXD
LPC_SYSCON->SYSAHBCLKCTRL|=(1UL<<12);//使能UART时钟
LPC_SYSCON->UARTCLKDIV=(4UL<<0);//时钟分频值为4:,则时钟为12MHZ 波特率=【12000000/(16*4)】*【8/(8+5)】=11538
LPC_UART->LCR=0x83;//UART线控制寄存器,0x83对应10000011 故8位传输,1个停止位,无奇偶校验,使能对除数锁存器的访问
LPC_UART->DLL=4;//写除数锁存器低位值
LPC_UART->FDR=0x85;//1000 0101 8,5 FR=0.615
LPC_UART->DLM=0;//写除数锁存器高位值,如果该寄存器前四位>0且DLM=0,则DLL寄存器的值必须大于等于3
LPC_UART->LCR=0x03;//禁止对除数锁存器访问,即00000011,DLAB=0
LPC_UART->IER=0x01;//设置UART中断使能寄存器
LPC_UART->FCR=0x81;//使能FIFO 收满1字节中断
NVIC_EnableIRQ(UART_IRQn);//使能UART中断
}
void UART_SendBytes(void) //发送数据
{
int a;
if(data[0]==0xAA){ //如果是AA则发送55
LPC_UART->THR=test; //测试数据可更改
}
else{
for(a=4;a>0;a--){//如果不是AA则发送error
LPC_UART->THR=wro[a];
}
while((LPC_UART->LSR&0x40)==0);//等待发送完毕
}
void UART_IRQHandler(void)
{
if(i==8) i=0;
switch(LPC_UART->IIR&0x0f)
{
case 0x04: //RDA
for(;i<8;i++){
data[1]=LPC_UART->RBR;//读取数据保存到data中
}
UART_SendBytes();
break;
case 0x0c: //CTI
while((LPC_UART->LSR&0x01)==1){
data[i++]=LPC_UART->RBR;//读取数据
}
UART_SendBytes();
break;
default:
break;
}
}
/*----------------------------------------------------------------------------
MAIN function
*----------------------------------------------------------------------------*/
int main (void) { /* Main Program */
UART_init();
while(1);
}
第二题程序
#include"lpc11xx.h"
int i=0;
int sen=0;
int rec=0;
uint16_t sendbuf[8];
uint16_t recvbuf[8];
void UART_init(void)
{
LPC_SYSCON->SYSAHBCLKCTRL|=((1UL<<6)|(1UL<<16));//使能IOCON时钟:SYSAHBCLKCTRL寄存器的16位为IO配置块时钟使能位
LPC_IOCON->PIO1_6=(1UL<<0);//IOCON->PIO1_6寄存器的2:0位为001则将P1.6设置为RXD
LPC_IOCON->PIO1_7=(1UL<<0); //IOCON->PIO1_7寄存器的2:0位为001则将P1.7设置为TXD
LPC_SYSCON->SYSAHBCLKCTRL|=(1UL<<12);//使能UART时钟
LPC_SYSCON->UARTCLKDIV=(4UL<<0);//时钟分频值为4:,则时钟为12MHZ 波特率=【12000000/(16*4)】*【8/(8+5)】=11538
LPC_UART->LCR=0x83;//UART线控制寄存器,0x83对应10000011 故8位传输,1个停止位,无奇偶校验,使能对除数锁存器的访问
LPC_UART->DLL=4;//写除数锁存器低位值
LPC_UART->FDR=0x85;//1000 0101 8,5 FR=0.615
LPC_UART->DLM=0;//写除数锁存器高位值,如果该寄存器前四位>0且DLM=0,则DLL寄存器的值必须大于等于3
LPC_UART->LCR=0x03;//禁止对除数锁存器访问,即00000011,DLAB=0
LPC_UART->IER=0x01;//设置UART中断使能寄存器
LPC_UART->FCR=0x81;//使能FIFO 收满1字节中断
NVIC_EnableIRQ(UART_IRQn);//使能UART中断
}
void UART_SendBytes(void)//发送数据
{
for(rec=0;rec<8;rec++)
{
sendbuf[rec]=recvbuf[rec]; //把接受数据存到发送数据数组
}
for(sen=0;sen<8;sen++){ //依次发送
LPC_UART->THR=sendbuf[sen];
while((LPC_UART->LSR&0x40)==0); //等待发送完毕
}
}
void UART_IRQHandler(void)
{switch(LPC_UART->IIR&0x0f)
{
case 0x04: //发生RDA中断
recvbuf[i++]=LPC_UART->RBR; // 从RBR读取数据
if(i==8) {
UART_SendBytes();
i=0;
}
break;
case 0x0c: //发生字符超时中断
while((LPC_UART->LSR&0x01)==1){
recvbuf[i++]=LPC_UART->RBR; //从RBR读取数据
}
if(i==8) {
UART_SendBytes(); //收集满8个字符则发送
i=0;
}
break;
default:
break;
}
}
/*----------------------------------------------------------------------------
MAIN function
*----------------------------------------------------------------------------*/
int main (void) { /* Main Program */
UART_init();
while(1);
}
作业6:LM75BD温度检测
(1)初始化LPC1114微控制器UART串行口,定时器和I2C总线接口;
(2)利用通用定时器实现定时1s中断,在定时器中断服务子程序中读取LM75BD(I2C总线)当前温度值,并通过串行口发送到PC,在PC上利用串口调试助手接收数据,并观察温度的变化。
#include "lpc11xx.h"
/* I2C 控制寄存器 */
#define I2CONSET_AA (1<<2) // 是否产生应答信号允许位,即是否设置为从机模式
#define I2CONSET_SI (1<<3) // I2C中断标志位
#define I2CONSET_STO (1<<4) // 停止标志位
#define I2CONSET_STA (1<<5) // 开始标志位
#define I2CONSET_I2EN (1<<6) // I2C接口允许位
/* I2C “清控制寄存器位”寄存器 */
#define I2CONCLR_AAC (1<<2) // 清应答信号允许位
#define I2CONCLR_SIC (1<<3) // 清I2C中断标志位
#define I2CONCLR_STAC (1<<5) // 清开始标志位
#define I2CONCLR_I2ENC (1<<6) // 清I2C接口允许位
void TMR32B0_Init(void){ /*32位定时器0初始化*/
LPC_SYSCON->SYSAHBCLKCTRL|=(1UL<<9); //使能
LPC_TMR32B0->IR=0x1F; //中断清0
LPC_TMR32B0->PR=9; //分频9 9+1=10,1s
LPC_TMR32B0->MCR=3; //匹配后复位TC并中断
LPC_TMR32B0->MR0=SystemCoreClock/10; //中断时间0.1s
LPC_TMR32B0->TCR=0x01; //使能定时器
NVIC_EnableIRQ(TIMER_32_0_IRQn); //设置中断并使能
}
void UART_init(uint32_t baudrate)
{
uint32_t DL_value,Clear=Clear; // (用这种方式定义变量解决编译器的Warning)
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); // 使能IOCON时钟
LPC_IOCON->PIO1_6 &= ~0x07;
LPC_IOCON->PIO1_6 |= 0x01; //把P1.6脚设置为RXD
LPC_IOCON->PIO1_7 &= ~0x07;
LPC_IOCON->PIO1_7 |= 0x01; //把P1.7脚设置为TXD
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); // 禁能IOCON时钟
LPC_SYSCON->UARTCLKDIV = 0x1; //时钟分频值为1
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<12); //允许UART时钟
LPC_UART->LCR = 0x83; //8位传输,1个停止位,无几偶校验,允许访问除数锁存器
DL_value = SystemCoreClock/16/baudrate ; //计算该波特率要求的除数锁存寄存器值
LPC_UART->DLM = DL_value / 256; //写除数锁存器高位值
LPC_UART->DLL = DL_value % 256; //写除数锁存器低位值
LPC_UART->LCR = 0x03; //DLAB置0
LPC_UART->FCR = 0x07; //允许FIFO,清空RxFIFO 和 TxFIFO
Clear = LPC_UART->LSR; //读UART状态寄存器将清空残留状态
}
/************************************************/
/* 函数功能:串口接收字节数据 */
/************************************************/
uint8_t UART_recive(void)
{
while(!(LPC_UART->LSR & (1<<0))); //等待接收到数据
return(LPC_UART->RBR); //读出数据
}
/************************************************/
/* 函数功能:串口发送字节数据 */
/************************************************/
void UART_send_byte(uint8_t byte)
{
while ( !(LPC_UART->LSR & (1<<5)) );//等待发送完
LPC_UART->THR = byte;
}
/************************************************/
/* 函数功能:串口发送数组数据 */
/************************************************/
void UART_send(uint8_t *Buffer, uint32_t Length)
{
while(Length != 0)
{
while ( !(LPC_UART->LSR & (1<<5)) );//等待发送完
LPC_UART->THR = *Buffer;
Buffer++;
Length--;
}
}
void I2C_Init(uint8_t Mode)
{
LPC_SYSCON->PRESETCTRL |= (1<<1); //使I2C上电 I2C模块(在启动I2C模块之前,必须向该位写1)
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<5); //使能I2C时钟
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); // 使能IOCON时钟
LPC_IOCON->PIO0_4 &= ~0x3F;
LPC_IOCON->PIO0_4 |= 0x01; //把P0.4脚配置为 I2C SCL
LPC_IOCON->PIO0_5 &= ~0x3F;
LPC_IOCON->PIO0_5 |= 0x01; //把P0.5脚配置为 I2C SDA
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); // 禁能IOCON时钟
if(Mode == 1) // 快速I2C通信 (大约400KHz传输速率)
{
LPC_I2C->SCLH = 60;
LPC_I2C->SCLL = 65;
}
if(Mode == 2) //快速+I2C通信 (大约1M传输速率)
{
LPC_I2C->SCLH = 25;
LPC_I2C->SCLL = 25;
}
else // 低速I2C通信 (大约100KHz传输速率)
{
LPC_I2C->SCLH = 250;
LPC_I2C->SCLL = 250;
}
LPC_I2C->CONCLR = 0xFF; // 清所有标志(CONCLR的置1操作的)
LPC_I2C->CONSET |= I2CONSET_I2EN; // 使能I2C接口
}
/***************************************************
/* 函数功能:发送开始信号 */
/***************************************************
void I2C_Start(void)
{
LPC_I2C->CONSET |= I2CONSET_STA; // 使能发送开始信号
while(!(LPC_I2C->CONSET & I2CONSET_SI)); // 等待开始信号发送完成
LPC_I2C->CONCLR = I2CONCLR_STAC | I2CONCLR_SIC; // 清除开始START位和SI位
}
/***************************************************/
/* 函数功能:发送停止信号 */
/***************************************************/
void I2C_Stop(void)
{
LPC_I2C->CONCLR = I2CONCLR_SIC; // 清SI标志位
LPC_I2C->CONSET |= I2CONSET_STO; // 发送停止信号
while( LPC_I2C->CONSET & I2CONSET_STO ); // 等待停止信号发送完成
}
/****************************************************/
/* 函数功能:I2C发送一字节数据 */
/* 入口参数:dat : 要发送的字节 */
/***************************************************/
void I2C_Send_Byte(uint8_t dat)
{
uint16_t TimeOut;
LPC_I2C->DAT = dat; // 把字节写入DAT寄存器
LPC_I2C->CONCLR = I2CONSET_SI; // 清除SI标志(1<<3)
TimeOut=20000;//或延时10000
while((!(LPC_I2C->CONSET & I2CONSET_SI))&&(TimeOut--)); // 等待数据发送完成
}
/***************************************************/
/* 函数功能:I2C接收一字节数据 */
/* 入口参数:rebyte : 要接收的字节 */
/***************************************************/
uint8_t I2C_Recieve_Byte(void)
{
uint8_t rebyte;
uint16_t TimeOut;
LPC_I2C->CONCLR = I2CONCLR_AAC | I2CONCLR_SIC; // 清AA和SI标志
TimeOut=20000;
while((!(LPC_I2C->CONSET & I2CONSET_SI))&&(TimeOut--)); // 等待接收数据完成
rebyte = (uint8_t)LPC_I2C->DAT; // 把接收到的数据给了rebyte
return rebyte;
}
/****************************************************/
/* 函数功能:I2C发送命令数据 */
/* 入口参数:Ctrl : 命令+地址字节 */
/* 出口参数:0:成功 */
/* 1: 失败 */
/***************************************************/
uint8_t I2C_Send_Ctrl(uint8_t CtrlAndAddr)
{
uint8_t res;
uint16_t TimeOut;
if(CtrlAndAddr & 0x01) // 如果是读命令
res = 0x40; // 40H代表开始信号和读命令已经传输完毕,并且已经接收到ACK
else // 如果是写命令
res = 0x18; // 18H代表开始信号和写命令已经传输完毕,并且已经接收到ACK
// 发送开始信号
LPC_I2C->CONCLR = 0xFF; // 清所有标志位
LPC_I2C->CONSET |= I2CONSET_I2EN | I2CONSET_STA; // 使能发送开始信号
TimeOut=20000;
while((!(LPC_I2C->CONSET & I2CONSET_SI))&&(TimeOut--)); // 等待开始信号发送完成
// 发送命令+地址字节
LPC_I2C->DAT = CtrlAndAddr; // 把要发送的字节给了DAT寄存器
LPC_I2C->CONCLR = I2CONCLR_STAC | I2CONCLR_SIC; // 清除开始START位和SI位
TimeOut=20000;
while((!(LPC_I2C->CONSET & I2CONSET_SI))&&(TimeOut--)); // 等待数据发送完成
if(LPC_I2C->STAT != res) // 观察STAT寄存器响应的状态,判断是否正确执行读或写命令
{
I2C_Stop(); // 没有完成任务,发送停止信号,结束I2C通信
return 1; // 返回1,表明失败!
}
return 0; // 如果正确执行返回0
}
/**********************************************/
/* 温度传感器LM75BD的数据读取函数 */
/**********************************************/
uint16_t Temputerature_Test(void)
{
uint16_t Temputerature_8,Temputerature_16; //温度值,1次接收8位
float Temputerature; //存储获得的温度值
//IIC启动---默认配置温度模式
I2C_Start();
//发送(0x91)1001 0001:1001,硬件布线; 0001,从机地址--000 读模式--1
I2C_Send_Byte(0x91);
Temputerature_8 = I2C_Recieve_Byte();//读LM75BD温度寄存器的高八位数据
Temputerature_16 = (Temputerature_8 <<8)+(I2C_Recieve_Byte());
// IIC停止
I2C_Stop();
//取温度数字量
Temputerature_16 = Temputerature_16 >> 5;//1LSB=0.125℃---低五位为无效数据(移出)
/* Temputerature_16:温度寄存器的数据D0--D10:其中D10为温度的正负数据*/
//负温度
if(Temputerature_16&0x0400)
Temputerature = -(~(Temputerature_16&0x03FF)+1)*0.125;//负温度的数据的转换(二进制的补码+1)
//正温度
else
Temputerature = 0.125*(float)Temputerature_16;
//返回温度值 1LSB=0.01℃
return ((uint16_t)(Temputerature*100));//返回的温度数据扩大了100倍便于读出小数部分;
}
uint8_t rec_buf; //串口接收数据
uint16_t temp; //获的温度传感器LM75BD的数据
uint8_t buf[6]={0+'0',1+'0',0+'0'}; //初始值--测试串口发送数据
/**********************************************/
/* 软件延时函数 */
/**********************************************/
void delay(void)
{
uint16_t i,j;
for(i=0;i<500;i++)
for(j=0;j<1000;j++);
}
/**********************************************/
/* 获得温度传感器LM75BD的数据 */
/**********************************************/
void Get_temprature(void)
{
temp=Temputerature_Test();
buf[0]=temp/10000+'0';
if((temp/10000)==0) buf[0]=' '; // 数据读取习惯--去除前面的0
buf[1]=temp/1000%10+'0';
buf[2]=temp/100%10+'0';
buf[3]='.';
buf[4]=temp/10%10+'0'; // 小数部分
buf[5]=temp%10+'0';
}
/**********************************************/
/* 中断服务子程序函数 */
/**********************************************/
void TIMER32_0_IRQHandler(void){ //中断服务子程序
if((LPC_TMR32B0->IR|=0x01)==1){ //判断MR0是否中断并清零中断
Get_temprature(); // 获得温度数据
UART_send((uint8_t*)"Current Temperature is:",23); // 串口发送字符串数组
UART_send(buf,6);
UART_send_byte('C'); // 单位:摄氏度
UART_send((uint8_t*)"\r\n",2);
}
}
/**********************************************/
/* 主函数 */
/**********************************************/
int main()
{
SystemInit(); // 系统时钟初始化
I2C_Init(1); // IIC初始化--模式:快速+I2C通信
UART_init(9600); // 把串口波特率配置为9600
delay(); // 初始化延时----等待相关器件配置完成
//串口测试正常
UART_send((uint8_t*)"UART is ok!\r\n", 13); // 串口发送字符串数组
UART_send((uint8_t*)"UART Test Data:", 15);
UART_send(buf,3);
UART_send((uint8_t*)"\r\n",2);
Temputerature_Test(); //进行一次读取(过滤第一个数据)--第一次读取的数据温度不准确
TMR32B0_Init(); //中断子程序
while(1)
{
}
}
作业7FLASH存储器操作
(1)初始化LPC1114微控制器UART串行口,定时器、I2C总线接口、SSP串行端口为SPI帧格式;
(2)系统启动后,利用通用定时器实现定时1s中断,在定时器中断服务子程序中读取LM75BD(I2C总线)当前温度值,并通过SPI接口将读到的温度值存储到FLASH存储器XT25F02中,在PC上利用串口调试助手向LPC1114的UART接口发送读取命令,从FLASH存储器中读出温度数据,并通过UART接口发送到PC,利用串口调试助手接收温度数据(保留3位小数)。
#include "lpc11xx.h"
#define NCS LPC_GPIO2->DATA|=(1<<0) // F_CS = 1;高
#define ECS LPC_GPIO2->DATA&=~(1<<0) // F_CS = 0;低
#define GBK_EN
//W25Q16读写
#define FLASH_ID 0XEF14
//指令表
#define W25Q_WriteEnable 0x06
#define W25Q_WriteDisable 0x04
#define W25Q_ReadStatusReg 0x05
#define W25Q_WriteStatusReg 0x01
#define W25Q_ReadData 0x03
#define W25Q_FastReadData 0x0B
#define W25Q_FastReadDual 0x3B
#define W25Q_PageProgram 0x02
#define W25Q_BlockErase 0xD8
#define W25Q_SectorErase 0x20
#define W25Q_ChipErase 0xC7
#define W25Q_PowerDown 0xB9
#define W25Q_ReleasePowerDown 0xAB
#define W25Q_DeviceID 0xAB
#define W25Q_ManufactDeviceID 0x90
#define W25Q_JedecDeviceID 0x9F
void W25Q16_Wait_Busy(void); //等待空闲
uint16_t wendu;
uint8_t buff[7];
uint8_t Wdata[7];
uint8_t Rdata[7];
uint32_t flashdizhi;
uint8_t output=0;
int j;
/************************ssp*************************
/* 函数功能:SPI1通信 */
/* 说明: 发送一个字节,接收一个字节 */
/*****************************************/
uint8_t SPI1_communication(uint8_t TxData)
{
while(((LPC_SSP1->SR)&(1<<4))==(1<<4));//忙时等待,SR状态寄存器bit4 BSY:忙时为1
LPC_SSP1->DR = TxData; //把要发送的数写入TxFIFO
while(((LPC_SSP1->SR)&(1<<2))!=(1<<2));//等待接收完,SR状态寄存器bit2 RNE:接收FIFO非空为1
return(LPC_SSP1->DR); //返回收到的数据
}
/*****************************************/
/* 函数功能:SPI0通信 */
/* 说明: 发送一个字节,接收一个字节 */
/*****************************************/
uint8_t SPI0_communication(uint8_t TxData)
{
while(((LPC_SSP0->SR)&(1<<4))==(1<<4));//忙时等待,SR状态寄存器bit4 BSY:忙时为1
LPC_SSP0->DR = TxData; //把要发送的数写入TxFIFO
while(((LPC_SSP0->SR)&(1<<2))!=(1<<2));//等待接收完,SR状态寄存器bit2 RNE:接收FIFO非空为1
return(LPC_SSP0->DR); //返回收到的数据
}
/*****************************************/
/* 函数功能:SPI1初始化 */
/* 说明: 没有用SSEL1 */
/*****************************************/
void SPI1_Init(void)
{
uint8_t i,Clear=Clear;//Clear=Clear:用这种语句形式解决编译产生的Waring:never used!
LPC_SYSCON->PRESETCTRL |= (0x1<<2); //禁止LPC_SSP1复位
LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<18);//允许LPC_SSP1时钟 bit18
LPC_SYSCON->SSP1CLKDIV = 0x06; //6分频:48/6=8Mhz
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); // 使能IOCON时钟(bit16)
LPC_IOCON->PIO2_1 &= ~0x07;
LPC_IOCON->PIO2_1 |= 0x02; //把PIO2_1选择为LPC_SSP CLK
LPC_IOCON->PIO2_2 &= ~0x07;
LPC_IOCON->PIO2_2 |= 0x02; //把PIO2_2选择为LPC_SSP MISO
LPC_IOCON->PIO2_3 &= ~0x07;
LPC_IOCON->PIO2_3 |= 0x02; //把PIO2_3选择为LPC_SSP MOSI
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); // 禁能IOCON时钟(bit16)
// 8位数据传输,SPI模式, CPOL = 1, CPHA = 1,空闲时CLK为1,SCR = 0
LPC_SSP1->CR0 = 0x01C7; //1 1100 0111
// 预分频值(注意:这里必须为偶数 2~254)
LPC_SSP1->CPSR = 0x04;
LPC_SSP1->CR1 &= ~(1<<0);//LBM=0:正常模式
LPC_SSP1->CR1 &= ~(1<<2);//MS=0:主机模式
LPC_SSP1->CR1 |= (1<<1);//SSE=1:使能SPI1
//清空RxFIFO,LPC1114收发均有8帧FIFO,每帧可放置4~16位数据
for ( i = 0; i < 8; i++ )
{
Clear = LPC_SSP1->DR;//读数据寄存器DR将清空RxFIFO
}
}
/*****************************************/
/* 函数功能:SPI0初始化 */
/* 说明: 没有用SSEL0 */
/*****************************************/
void SPI0_Init(void)
{
uint8_t i,Clear=Clear;//Clear=Clear:用这种语句形式解决编译产生的Waring:never used!
LPC_SYSCON->PRESETCTRL |= (0x1<<0); //禁止LPC_SSP0复位
LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<11);//允许LPC_SSP0时钟 bit11
LPC_SYSCON->SSP0CLKDIV = 0x01; //分频系数为1,使SPI0速率最大:48Mhz
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); // 使能IOCON时钟(bit16)
LPC_IOCON->SCK_LOC = 0x02; //把SCK0复用到PIO0_6引脚
LPC_IOCON->PIO0_6 &= ~0x07;
LPC_IOCON->PIO0_6 |= 0x02; //把PIO0_6设置为SSP CLK
LPC_IOCON->PIO0_8 &= ~0x07;
LPC_IOCON->PIO0_8 |= 0x01; //把PIO0_8设置为SSP MISO
LPC_IOCON->PIO0_9 &= ~0x07;
LPC_IOCON->PIO0_9 |= 0x01; //把PIO0_9设置为SSP MOSI
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); // 禁能IOCON时钟(bit16)
// 8位数据传输,SPI模式, CPOL = 0, CPHA = 0,空闲时CLK为0,第一个上升沿采集数据,SCR = 0
LPC_SSP0->CR0 = 0x0107;
// 预分频值(注意:必须为偶数 2~254)
LPC_SSP0->CPSR = 0x02;
LPC_SSP0->CR1 &= ~(1<<0);//LBM=0:正常模式
LPC_SSP0->CR1 &= ~(1<<2);//MS=0:主机模式
LPC_SSP0->CR1 |= (1<<1);//SSE=1:使能SPI0
//清空RxFIFO,LPC1114收发均有8帧FIFO,每帧可放置4~16位数据
for ( i = 0; i < 8; i++ )
{
Clear = LPC_SSP0->DR;//读数据寄存器DR将清空RxFIFO
}
}
/************************************************/
/* 函数功能:初始化UART口 */
/************************************************/
void UART_Init(uint32_t baudrate)
{
uint32_t DL_value,Clear=Clear; // (用这种方式定义变量解决编译器的Warning)
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); // 使能IOCON时钟
LPC_IOCON->PIO1_6 &= ~0x07;
LPC_IOCON->PIO1_6 |= 0x01; //把P1.6脚设置为RXD
LPC_IOCON->PIO1_7 &= ~0x07;
LPC_IOCON->PIO1_7 |= 0x01; //把P1.7脚设置为TXD
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); // 禁能IOCON时钟
LPC_SYSCON->UARTCLKDIV = 0x1; //时钟分频值为1
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<12);//允许UART时钟
LPC_UART->LCR = 0x83; //8位传输,1个停止位,无几偶校验,允许访问除数锁存器
DL_value = SystemCoreClock/16/baudrate ; //计算该波特率要求的除数锁存寄存器值
LPC_UART->DLM = DL_value / 256; //写除数锁存器高位值
LPC_UART->DLL = DL_value % 256; //写除数锁存器低位值
LPC_UART->LCR = 0x03; //DLAB置0
LPC_UART->FCR = 0x07; //允许FIFO,清空RxFIFO 和 TxFIFO
Clear = LPC_UART->LSR; //读UART状态寄存器将清空残留状态
}
/************************************************/
/* 函数功能:串口接收字节数据 */
/************************************************/
uint8_t UART_receive(void)
{
while(!(LPC_UART->LSR & (1<<0)));//等待接收到数据
return(LPC_UART->RBR); //读出数据
}
/************************************************/
/* 函数功能:串口发送字节数据 */
/************************************************/
void UART_send_byte(uint8_t byte)
{
LPC_UART->THR = byte;
while ((LPC_UART->LSR & (1<<5))!=(1<<5));//等待发送完
}
/************************************************/
/* 函数功能:串口发送数组数据 */
/************************************************/
void UART_send(uint8_t *Buffer, uint32_t Length)
{
while(Length != 0)
{
while ( !(LPC_UART->LSR & (1<<5)) );//等待发送完
LPC_UART->THR = *Buffer;
Buffer++;
Length--;
}
}
/*********************************************W25Q16***********************************************/
void W25Q16_Init(void)
{
//初始化W25Q16的控制IO口
LPC_GPIO2->DIR |= (1<<0); // P2.0为w25q16的选择引脚所连接的IO
LPC_GPIO2->DATA |= (1<<0); //
SPI1_Init(); //初始化SPI1
}
//读W25Q16状态寄存器 */
uint8_t W25Q16_ReadSR(void)
{
uint8_t byte=0;
ECS; //选择
SPI1_communication(W25Q_ReadStatusReg); //发送读取状态寄存器命令
byte=SPI1_communication(0Xff); //读取一个字节
NCS; //取消片选
return byte; //返回8位W25Q16的状态值
}//写入使能
void W25Q16_Write_Enable(void)
{
ECS; //使能器件
SPI1_communication(W25Q_WriteEnable); //发送写使能
NCS; //取消片选
}
/***************************************************函数功能:读取W25Q16数据 */
Buffer:读出数据后放到此数组
Addr:开始读取的地址(24bit)
ByteNum:要读取的字节数(最大65535)
****************************************************
void W25Q16_Read(uint8_t* Buffer,uint32_t Addr,uint16_t ByteNum)
{
uint16_t i;
ECS; //使能器件
SPI1_communication(W25Q_ReadData); //发送读取命令
SPI1_communication((uint8_t)((Addr)>>16)); //发送24bit地址
SPI1_communication((uint8_t)((Addr)>>8));
SPI1_communication((uint8_t)Addr);
for(i=0;i>16)); //发送24bit地址
SPI1_communication((uint8_t)((Addr)>>8));
SPI1_communication((uint8_t)Addr);
for(i=0;iPRESETCTRL |= (1<<1); //复位I2C外设 De-asserted I2C 模块(在启动 I2C 模块之前,必须向该位写 1)
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<5); //使能I2C时钟
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); //使能I/O 配置模块的时钟
LPC_IOCON->PIO0_4 &= ~0x3F; //先清零
LPC_IOCON->PIO0_4 |= 0x01; //选择 I2C 功能 SCL ( 开漏引脚 )
LPC_IOCON->PIO0_5 &= ~0x3F;
LPC_IOCON->PIO0_5 |= 0x01; //选择 I2C 功能 SDA ( 开漏引脚 )
LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<16); // 低功耗考虑
if(Mode == 1) // 快速模式400KHZ
{
LPC_I2C->SCLH = 60;
LPC_I2C->SCLL = 65;
}
if(Mode == 2) //I2C快速模式PLUS1MHZ
{
LPC_I2C->SCLH = 25;
LPC_I2C->SCLL = 25;
}
else // 标准模式100KHZ
{
LPC_I2C->SCLH = 250;
LPC_I2C->SCLL = 250;
}
LPC_I2C->CONCLR = 0xFF; //清除所有I2C0CONSET寄存器的位
LPC_I2C->CONSET |= (1<<6); // I2EN=1 使能I2C接口
}
//I2C启动子程序(主控发送开始信号)
void I2C_Start(void)
{
LPC_I2C->CONSET |= (1<<5); // STA=1 I2C进入主模式并发送一个起始条件
while(!(LPC_I2C->CONSET & (1<<3))); // 当I2C状态改变时,I2C中断标志位3即SI置位
LPC_I2C->CONCLR = (1<<5) | (1<<3); // 清零I2C中断位和START标志位
}
//I2C停止子程序
void I2C_Stop(void)
{
LPC_I2C->CONCLR = (1<<3); //清除 SI标志位
LPC_I2C->CONSET |= (1<<4); //发出停止条件,设置停止标志位
while( LPC_I2C->CONSET & (1<<4) ); //等待停止标志位被自动清除
}
//发送一个字节数据
void I2C_Send_Byte(uint8_t dat)
{
uint16_t TimeOut;
LPC_I2C->DAT=dat; //包含要发送的数据
LPC_I2C->CONCLR = (1<<3); // 清除中断
TimeOut=20000; //???10000
while((!(LPC_I2C->CONSET & (1<<3)))&&(TimeOut--));
}
//接收一个字节数据
uint8_t I2C_Recieve_Byte(void)
{
uint8_t Rdata;
uint16_t TimeOut;
LPC_I2C->CONSET = (1<<2); //AA位置1,在出现特定情况时返回应答信号
LPC_I2C->CONCLR = (1<<3); //中断位清零
TimeOut=20000;
while((!(LPC_I2C->CONSET & (1<<3)))&&(TimeOut--));
Rdata = (uint8_t)LPC_I2C->DAT; //收到的数据
return Rdata;
}
/*****************************************TEM******/
void delay(void)
{
uint16_t i,j;
for(i=0;i<1000;i++)
for(j=0;j<1000;j++);
}
uint16_t cewen(void)
{
uint8_t front8bit,after8bit;
uint16_t wendu16bit;
float T;
I2C_Start();
I2C_Send_Byte(0x91);
front8bit = I2C_Recieve_Byte();
after8bit = I2C_Recieve_Byte();
wendu16bit = (front8bit <<8)+(after8bit);
I2C_Stop();
wendu16bit = wendu16bit >> 5;
if(wendu16bit&0x0400) //10000000000温度采用二进制补码表示
T = -(~(wendu16bit&0x03FF)+1)*0.125;
else
T = 0.125*(float)wendu16bit;
return ((uint16_t)(T*1000));
}
void Get_wendu(void)
{
wendu=cewen();
buff[0]=wendu/100000+'0';
if((wendu/100000)==0) buff[0]=' ';
buff[1]=wendu/10000%10+'0';
buff[2]=wendu/1000%10+'0';
buff[3]='.';
buff[4]=wendu/100%10+'0'; // 小数点部分
buff[5]=wendu/10%10+'0';
buff[6]=wendu%10+'0';
}
void TMR32B0_Init(void){ /*32位定时器0初始化*/
LPC_SYSCON->SYSAHBCLKCTRL|=(1UL<<9); //使能
LPC_TMR32B0->IR=0x1F; //中断清0
LPC_TMR32B0->PR=9; //分频9 9+1=10,1s
LPC_TMR32B0->MCR=3; //匹配后复位TC并中断
LPC_TMR32B0->MR0=SystemCoreClock/10; //中断时间0.1s
LPC_TMR32B0->TCR=0x01; //使能定时器
NVIC_EnableIRQ(TIMER_32_0_IRQn); //设置中断并使能
}
/* 中断服务子程序函数 */
/**********************************************/
void TIMER32_0_IRQHandler(void){ //中断服务子程序
if(!output&&LPC_UART->RBR=='s'){ //判断输入s开始发送
output=1;
}
if((LPC_TMR32B0->IR|=0x01)==1){ //判断MR0是否中断并清零中断
Get_wendu();
if(output){
W25Q16_Write_Page(buff,flashdizhi,7) ; //温度一位一位写入flash
W25Q16_Read(Rdata, flashdizhi, 7); //读出存到Rdata
delay();
UART_send(Rdata, 7); //发送数据
UART_send("\r\n",2);
flashdizhi+=6;
}
}
}
int main()
{
UART_Init(9600);
W25Q16_Init();
SystemInit();
I2C_Init(1);
flashdizhi = 0x000; // 从W25Q16第0个扇区第一个字节开始写入数据
W25Q16_Erase_Chip() ; //擦除
TMR32B0_Init(); //定时器初始化
while(1)
{
}
}
作业8 ADC模数转换
(1)初始化LPC1114微控制器UART串行口,AD转换器分别初始化为软件控制模式(CR中的BURST位为0,立即启动、硬件触发[1])和硬件扫描模式(CR中的BURST位为1),开启AD中断,设置AD0为模拟输入引脚;
(2)启动AD转换,在AD中断服务子程序中读取AD转换的值(AD0通道),并通过UART接口发送到PC,利用串口调试助手接收数据。
#include "LPC11xx.h" /* LPC11xx definitions */
/*-----------------------------------------------------------------------------
The Subfunction
*----------------------------------------------------------------------------*/
uint32_t regVal;
uint32_t ADC0_Value;
void UART_init(void) /* Init the UART function */
{
LPC_SYSCON->SYSAHBCLKCTRL|=((1UL<<6)|(1UL<<16)); //使能IOCON时钟
LPC_IOCON->PIO1_6=(1UL<<0); /* Definit P1.6 as RxD */
LPC_IOCON->PIO1_7=(1UL<<0);/* Definit P1.7 as TxD */
LPC_SYSCON->SYSAHBCLKCTRL|=(1UL<<12);//´打开串口时钟,配置完串口也不要关闭,因为串口工作需要时钟
LPC_SYSCON->UARTCLKDIV=(4UL<<0); /* UART clock = CCLK/4 */
LPC_UART->LCR=0x83; // 配置U0LCR寄存器,8位传输,1个停止位,无奇偶校验,允许访问除数锁存器
LPC_UART->DLL=4; /* 115200 Baud Rate @ 12.0MHz PCLK */
LPC_UART->FDR=0x85; /* FR=1.627,DIVADVAL=5,MULVAL=8 */
LPC_UART->DLM=0; /* High divisor latch=0 */
LPC_UART->LCR=0x03; //DLAB置0
LPC_UART->IER=0x01; //启用 UART 中断功能
LPC_UART->FCR=0x81; //将接收缓冲区长度设置为 8B
NVIC_EnableIRQ(UART_IRQn); //定义中断功能
}
void UART_SendBytes(uint32_t data16send) //通过搜索 U0LSR 发送数据
{
LPC_UART->THR=data16send; //将发送数据放入 THR
while((LPC_UART->LSR&0x40)==0); }
void TIM16B0_MAT_Init(uint16_t a){
LPC_SYSCON->SYSAHBCLKCTRL|=(1<<7); // 使能定时器0时钟
LPC_IOCON->PIO0_8=0x02; /配置PIO0_8为定时器16B0的MAT0功能 LPC_TMR16B0->TCR=0x02; 设置定时器16B0为复位状态 LPC_TMR16B0->PR=SystemCoreClock/1000-1; // 设置定时器分频,以1毫秒为基准 LPC_TMR16B0->MR0=a; // 设置定时器MAT0的值 LPC_TMR16B0->IR=0x01; // 清除中断标志 LPC_TMR16B0->MCR=0x02; // 当计数器与MAT0匹配时,产生中断
LPC_TMR16B0->EMR=0x31; // 定时器16B0的MAT0输出翻转模式
LPC_TMR16B0->TCR=0x01; // 启动计时器
}
void ADC_Init(void)
{
// 使能 ADC 和 IOCON 外设的时钟
LPC_SYSCON->SYSAHBCLKCTRL |= ((1UL<<6) | (1UL<<16));
// 上电 ADC 模块
LPC_SYSCON->PDRUNCFG &= ~(1UL<<4);
// 使能 ADC 外设的时钟
LPC_SYSCON->SYSAHBCLKCTRL |= (1UL<<13);
// 配置 P0.11 引脚为 ADC 输入
LPC_IOCON->R_PIO0_11 |= 0x82; // 选择 ADC 功能并禁用上拉电阻
LPC_IOCON->R_PIO0_11 |= 0x02; // 将引脚设置为 AD0
LPC_IOCON->R_PIO0_11 &= ~(1UL<<7); // 配置引脚为模拟输入模式
// 配置 ADC 控制寄存器
LPC_ADC->CR = (1UL<<0) | (23UL<<8) | (1UL<<25) | (1UL<<26) | (1UL<<27
// 使能 ADC 转换完成中断
LPC_ADC->INTEN = (1UL<<0);
// 使能 ADC 转换完成中断的 NVIC 中断向量
NVIC_EnableIRQ(ADC_IRQn);
// 初始化定时器 16B0,用于控制 ADC 转换频率
TIM16B0_MAT_Init(500);
}
void ADC_IRQHandler(void)
{
// 读取 ADC 状态寄存器
regVal = LPC_ADC->STAT;
// 读取 ADC 数据寄存器,获取 ADC 转换结果
regVal = LPC_ADC->DR[0];
// 提取 ADC 转换结果的有效数据,并保存到 ADC0_Value 变量中
ADC0_Value = (regVal >> 6) & 0x3ff;
// 将 ADC 转换结果转换为电压值(单位:伏特),并发送到串口
regVal = (ADC0_Value * 3.3) / 1024;
UART_SendBytes(regVal >> 8); // 发送高字节
UART_SendBytes(regVal); // 发送低字节
}
void delay(void)//一个自动生成的延时函数
{
uint16_t i,j;
for(j=0;j<5000;j++)
for(i=0;i<500;i++);
}
int main (void) { /* Main Program */
UART_init(); /* Init the UART function */
ADC_Init();
delay();
while(1){
}
}
作业9 电源管理与功率控制
(1)立即睡眠:初始化LPC1114,SysTick定时100ms,在异常服务子程序中设置一个计数器,每计10个数就设置PIO1_9引脚状态反转,在主程序中调用__WFI( )内部函数,进入睡眠模式,观察系统节拍定时器中断是否可以唤醒睡眠模式?并就现象作解释说明。
(2)退出时睡眠:初始化LPC1114,SysTick定时100ms,在异常服务子程序中设置一个计数器,每计10个数就设置PIO1_9引脚状态反转,设置系统控制寄存器SCR的SLEEPONEXIT位为1(退出时睡眠),观察系统运行状态,并作解释说明。
(3)深度睡眠:设置系统控制寄存器SCR的SLEEPDEEP位为1,重复(2)的操作,观察系统运行状态,并作解释说明。
(4)深度掉电:用文字说明如何进入深度掉电模式,如何从深度掉电模式唤醒。
1.立即睡眠
程序:
#include
void Led_init(){
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); // 使能时钟
LPC_GPIO1->DIR |= (1<<9);//给高电平
}
void LED_Toggle(void) {
LPC_GPIO1->DATA ^= (1UL <<9); /* 翻转LED */
}
void SysTick_Handler(void){
static unsigned long ticks=0;//进入中断的次数
if(ticks==10){//计时10个数
LED_Toggle();//将接口取反
ticks=0;
}
ticks++;
}
int main(){
/
SystemInit();// 主时钟设置成48Mhz
Led_init();
SysTick_Config(SystemCoreClock/10); //计时器计时100ms
__WFI();
while(1){
}
}
现象:LED正常闪烁
利用定时器中断可以唤醒睡眠模式,当SLEEPONEXIT为0时,程序调用WFI命令,同时系统会立马进入睡眠模式。
在睡眠模式中,因为systick为外设,有自己的时钟所以仍然可以运行,在这里如果有中断出现,则处理中断函数。在中断函数结束后,程序这个时候将进入线程模式中,开始执行WFI后面的代码。
系统节拍定时器能唤醒睡眠模式
2.退出时睡眠
#include
void Led_init(){
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); // 使能时钟
LPC_GPIO1->DIR |= (1<<9);//给高电平
}
void LED_Toggle(void) {
LPC_GPIO1->DATA ^= (1UL <<9); /* Toggle the BLINKY LED */
}
void SysTick_Handler(void){
static unsigned long ticks=0;//进入中断的次数
if(ticks==10){//计时10个数
LED_Toggle();//将接口取反
ticks=0;
}
ticks++;
}
int main(){
SystemInit();// 主时钟设置成48Mhz
Led_init();
SysTick_Config(SystemCoreClock/10); //计时器计时100ms
SCB->SCR |=(1<<1); //退出时睡眠
//SCB->SCR |=SCB_SCR_SLEEPONEXIT_Msk;
__WFI();
while(1){
}
}
现象:小灯正常闪烁
进入睡眠,外设仍然继续运行,利用系统节拍定时器中断唤醒睡眠模式,当SLEEPONEXIT为1时,程序调用WFI命令,系统会进入睡眠模式。
在睡眠模式中,如果有中断出现,则进入中断处理函数。 在中断函数结束后,由于SLEEPONEXIT=1 ,返回线程模式后进入睡眠模式程序这个时候将再次进入睡眠模式中。
3.深度睡眠
#include
void Led_init(){
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); // 使能时钟
LPC_GPIO1->DIR |= (1<<9);//给高电平
}
void LED_Toggle(void) {
LPC_GPIO1->DATA ^= (1UL <<9); /* Toggle the BLINKY LED */
}
void SysTick_Handler(void){
static unsigned long ticks=0;//进入中断的次数
if(ticks==10){//计时10个数
LED_Toggle();//将接口取反
ticks=0;
}
ticks++;
}
int main(){
SystemInit();// 主时钟设置成48Mhz
Led_init();
SysTick_Config(SystemCoreClock/100); //计时器计时100ms
SCB->SCR |=(1<<2); //设置深度睡眠
SCB->SCR |=(1<<1); //退出时 深度睡眠
//SCB->SCR |=SCB_SCR_SLEEPONEXIT_Msk;
__WFI();
while(1){
}
}
现象:LED灯不亮
系统 SLEEPDEEP=1 系统在调用WFI后将进入深度睡眠模式。进入深度睡眠模式后,中断程序无法唤醒CPU,故LED不亮。
作业10:RTC时间设置与读取
在Keil MDK 4.74上按照CMSIS标准编写一段程序:
(1)初始化LPC1114微控制器UART串行口,I2C总线接口,并且初始化DS1307;
(2)读出DS1307的实时日期和时间,通过串行口发送到PC,在PC上利用串口调试助手接收数据,并通过串口调试助手发送日期和时间的设置值,观察RTC是否能够正常运行。
//ds1307.c
#include
void I2c_Start();
void I2C_Stop();
void I2C_Send_Byte();
uint8_t I2C_Recieve_Byte();
void Ds1307_WriteByte(uint8_t WriteAddr,uint8_t WriteData);
void DS1307_Write(uint8_t *data);
/*函数功能:DS1307 初始化,默认初始化为全 0*/
void DS1307Init(){
uint8_t time[7]={0x30,0x27,0x21,0x02,0x07,0x02,0x23};//设置时间 2023 年 2 月 7 日星期二 21
时 27 分 30 秒
DS1307_Write(time);
}
void DS1307_Read(uint8_t *data){
Ds1307_WriteByte(0x3f,0x01);//定位 ds1307 内部指针到 0x3f(RAM 尾部)处
I2c_Start();//start
I2C_Send_Byte(0xd1);//读
LPC_I2C->CONSET =(1<<2);//AA=1
data[0]=I2C_Recieve_Byte();
data[1]=I2C_Recieve_Byte();
data[2]=I2C_Recieve_Byte();
data[3]=I2C_Recieve_Byte();
data[4]=I2C_Recieve_Byte();
data[5]=I2C_Recieve_Byte();
LPC_I2C->CONCLR =(1<<2);//AA=0
data[6]=I2C_Recieve_Byte();
I2C_Stop();//STOP
}
/* 函数功能:DS1307 写*/
void DS1307_Write(uint8_t *data){
I2c_Start();//start
I2C_Send_Byte(0xd0);//读
LPC_I2C->CONSET =(1<<2);//AA=1
I2C_Send_Byte(0x00);//从 0x00 开始写入
I2C_Send_Byte(data[0]);
I2C_Send_Byte(data[1]);
I2C_Send_Byte(data[2]);
I2C_Send_Byte(data[3]);
I2C_Send_Byte(data[4]);
I2C_Send_Byte(data[5]);
LPC_I2C->CONCLR =(1<<2);//AA=0
I2C_Send_Byte(data[6]);
I2C_Stop();//STOP
}
/*函数功能:DS1307 写一个字节*/
void Ds1307_WriteByte(uint8_t WriteAddr,uint8_t WriteData)
{
//I2C_Start();
I2c_Start();//start
I2C_Send_Byte(0xd0); // Device Addr + Write (operation)
I2C_Send_Byte(WriteAddr);
LPC_I2C->CONCLR =(1<<2);//AA=0 接受完下一个字节后返回非应答信号
I2C_Send_Byte(WriteData);
I2C_Stop();
}
/* 函数功能:DS1307 读一个字节*/
uint8_t Ds1307_ReadByte()
{
uint8_t RevData;
I2c_Start();//start
I2C_Send_Byte(0xD1); // Device Addr + Write (operation)
LPC_I2C->CONCLR =(1<<2);//AA=0
RevData = I2C_Recieve_Byte();
I2C_Stop();
return RevData;
}
//I2C.c
#include
/*函数功能:I2C 初始化*/
void I2CInit(){
LPC_SYSCON->PRESETCTRL |= (1<<1); //复位取消
LPC_SYSCON->SYSAHBCLKCTRL |=(1<<5);//使能 I2C
LPC_SYSCON->SYSAHBCLKCTRL |=(1<<16);//使能 IO 配置块
//选择快速模式
LPC_IOCON->PIO0_4 &=~(0X3F); //选择快速模式
LPC_IOCON->PIO0_4 |=0X01;//选择 SCL
LPC_IOCON->PIO0_5 &=~(0X3F); //选择快速模式
LPC_IOCON->PIO0_5 |=0X01;//选择 SDA
//设置 SCL 频率为 400kHZ
LPC_I2C->SCLH=40;
LPC_I2C->SCLL=80;
//使能 I2C 同时将其他控制位清 0
LPC_I2C->CONCLR=0XFF;
LPC_I2C->CONSET |=(1<<6);
}
/*函数功能:发送开始信号*/
void I2c_Start(){
LPC_I2C->CONSET =(1<<5);// 发送开始信号
while(!(LPC_I2C->CONSET&(1<<3))){//等待开始信号发送完成 SI 置位
}
LPC_I2C->CONCLR =(1<<5|1<<3); //清零 START 和 SI
}
/*函数功能:发送停止信号*/
void I2C_Stop(){
LPC_I2C->CONCLR =(1<<3);
LPC_I2C->CONSET =(1<<4);// 发送停止信号
while((LPC_I2C->CONSET&(1<<4))){//等待停止信号发送完成 SI 置位
}
}
/*函数功能:发送一个字节*/
void I2C_Send_Byte(uint8_t data){
LPC_I2C->DAT=data;
LPC_I2C->CONCLR =(1<<3); //开始发送数据 清 SI
while(!(LPC_I2C->CONSET&(1<<3))){//等待数据发送完成 SI 置位
}
}
/*函数功能:接受一个字节*/
uint8_t I2C_Recieve_Byte(){
LPC_I2C->CONCLR =(1<<3);//开始接受数据 清 SI
while(!(LPC_I2C->CONSET&(1<<3))){//等待接受数据完成 SI 置位
}
return (uint8_t)LPC_I2C->DAT;
}
//Tmr16b0.c
#include
void UART_Send(uint8_t str[],int lenght);
void UART_Send_Bit();
void Delay_1s();
void DS1307_Read(uint8_t *data);
void Ds1307_WriteByte(uint8_t WriteAddr,uint8_t WriteData);
uint8_t Ds1307_ReadByte();
uint8_t buf[100]; //存放温度
void TMR16B0_Init(){//定时器初始化 定时 1s
LPC_SYSCON->SYSAHBCLKCTRL |=(1<<7);//使能 16B0
LPC_TMR16B0->MCR =3; //匹配 MR0 时复位 TC 且中断
LPC_TMR16B0->PR=799; //预分频值 799
//LPC_TMR16B0->PR=1599; //预分频值 1599 定时 2 秒
LPC_TMR16B0->MR0=SystemCoreClock/800; // 设置周期为 1 秒
LPC_TMR16B0->TCR=0X01; //启动定时
NVIC_EnableIRQ(TIMER_16_0_IRQn); //启动中断
}
void TIMER16_0_IRQHandler(){//中断
uint8_t data[7];
DS1307_Read(data);
UART_Send_Bit(0xff); //方便区分
UART_Send(data,7);
UART_Send_Bit(0xff);
LPC_TMR16B0->IR |=0X01; //清中断
}
//UART.c
#include
uint32_t Rcv_Buf[100]; //存放数据
int buf_i=0;//数据长度
void DS1307_Write(uint8_t *data);
void Delay_1s(void);
void UARTInit(){
//配置引脚
LPC_SYSCON->SYSAHBCLKCTRL |=(1<<16);//使能 IO
LPC_SYSCON->SYSAHBCLKCTRL |=(1<<6) ;//使能 GPIO
LPC_IOCON->PIO1_6 |= 0x01; //设置成 RXD 引脚
LPC_IOCON->PIO1_7 |= 0x01; //设置成 TXD 引脚
LPC_UART->LCR=3; //数据 8 停止 1 无校验
//设置波特率 115384 近似 115200
LPC_SYSCON->SYSAHBCLKCTRL |=(1<<12);//使能 UART
LPC_SYSCON->UARTCLKDIV=4; //设置分频值 4 获得 UART 时钟为 12Mhz
LPC_UART->LCR=0X83; //DLAB=1
LPC_UART->DLL=4;
LPC_UART->DLM=0;
LPC_UART->LCR=0x03; //DLAB=0
LPC_UART->FDR=0X85; //MIV=8 DIV=5
LPC_UART->FCR =0X81; //使能 FIFO 深度设置为 8
LPC_UART->IER |=1<<0; //使能接受中断
NVIC_EnableIRQ(UART_IRQn); //启动中断
}
/*发送字符串*/
void UART_Send(uint8_t str[],int lenght){
int i;
for(i=0;iTHR= str[i];
while((LPC_UART->LSR&0X40)==0);//等待数据发送完成
}
}
/*发送 一个字节*/
void UART_Send_Bit(uint8_t data){
LPC_UART->THR= data;
while((LPC_UART->LSR&0X40)==0);//等待数据发送完成
}
void UART_IRQHandler(){
int i;
uint8_t data[16]={0};
uint8_t adc;
for(i=0;i<16;i++){
data[i]=0x11;
}
switch(LPC_UART->IIR&0X0F){
case 0x04: //RDA
for(i=0;i<8;i++){
Rcv_Buf[buf_i++]=LPC_UART->RBR; //接受数据
}
break;
case 0x0c: //CTI
//每次都只发送 7 组数据,一定会进入 CTI
i=0;
while((LPC_UART->LSR&0X01)==1){
Rcv_Buf[buf_i]=LPC_UART->RBR;//接受数据
data[i]= Rcv_Buf[buf_i];
i++;
buf_i++;
}
DS1307_Write(data);
break;
}
}
//main.c
#include
void UARTInit();
void Delay_1s(void);
void UART_Send_Bit(uint8_t data);
void UART_Send(uint8_t str[],int lenght);
void TMR16B0_Init();
void I2CInit();
void DS1307Init();
void Delay_1s();
void DS1307_Read(uint8_t *data);
void Led_init(){
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); // 使能时钟
LPC_GPIO1->DIR |= (1<<9);
LPC_GPIO1->DATA &= ~(1<<9); //拉低
}
void Delay_1s(){
int i=SystemCoreClock/5;//1s
while(--i);
}
int main(){
SystemInit();// 主时钟设置成 48Mhz
UARTInit();
I2CInit();
Delay_1s();
DS1307Init();
TMR16B0_Init();
while(1){
//UART_Send("11");
}
《嵌入式系统开发》系列专栏主要以LPC1100系列微控制器为硬件平台,详细介绍Cortex—-M0微控制器的原理与开发技术,基于keil仿真软件平台设计最小应用系统板和具有在板仿真器的口袋开发板以及相关例程。
本文已收录于嵌入式系统开发系列专栏:嵌入式系统开发 欢迎订阅,持续更新。