目录
前言
本文以FT61F14x为例,FT61F14x是辉芒微电子(FMD)的微控制器,是一款8位基于EEPROM的RISC MCU。官方提供CMIDE3.0.9可用于编译调试,本文主要讲解其开发环境及部分外设使用,资源见附件。官网链接:https://www.fremontmicro.com/#/home
一、CMIDE的使用
首先需要新建工程,直接编写并编译可能会出现以下链接警告
点击工程->新建工程,设置工程名以及工程路径,然后选择相应的芯片类型,最后点击OK就可以一键生成工程
对生成工程直接点击编译,弹出Options选项配置,可以按照下图修改,点确定便可编译成功。
注意:提示系统占用不可以删除和修改的地方,不要删除,否则会提示链接报警
二、系统时钟与睡眠
2.1 上电复位 (POR)与系统复位
上电过程,即 VDD 从低于 Power-On-Reset 电压(VPOR)上升至高于 VPOR 的过程。当 CPU 重新上电时,VDD 可能没有完全掉电至 0V。初始化配置由CMIDE 界面设置,不能通过指令修改。初始化配置时序如下:
与 POR 不同,系统复位(system reset) 并不会完全复位。系统复位时,CPU 是否启动初始化配置过程则取决于复位触发类型。若启动初始化配置则空闲等待4ms,然后重新加载初始化配置寄存器值,如果使能 PWRT 将额外延时64ms,随后系统正常启动。在系统复位中:
- 除初始化配置寄存器外,POR 过程中被重置的寄存器在系统复位时也会被同样重置;
- 程序计数器 PC = 0x00, 指令寄存器 = “NOP”, 堆栈指针 = “TOS” (栈顶);
除仿真调试的 OCD(On-Chip Debugger) 模块外,以下 7 种事件可触发系统复位:
- 欠压复位 (BOR / LVR) – 总会启动初始化配置;
- 非法指令复位;
- 看门狗复位 (WDT,CPU 处于非 SLEEP 状态);
- EMC 复位– 总会启动初始化配置;
- 软件复位 (执行指令 “RESET”);
- 堆栈溢出复位 (上溢或下溢);
- 外部 I/O 复位 (/MCLRB) – 总会启动初始化配置 ( ≥ VerB 芯片)。
系统复位的大多数设置均由 IDE 界面配置,而不能通过指令修改。系统复位相关寄存器汇总:
2.2 振荡器和系统时钟
系统时钟(SysClk) 可通过指令选择为内部高速振荡器 HIRC 16MHz,内部低速振荡器 LIRC 32KHz,或外部振荡器。如果选择外部振荡器,那么由初始化配置寄存器 “FOSC”选定3 种外部振荡器之一。系统时钟还可通过指令进一步选择为内部或外部振荡器的分频 。系统时钟用于产生指令时钟(Instruction Clock):
指令时钟 = SysClk / N ; N = 2 for 2T, 4 for 4T
SysClk 系统时钟源设置相关用户寄存器:
通常我们选择内部时钟即SCS=1,不分频即MCKCF=0111,使用OSCCON寄存器如下(SCS为OSCCON的BIT0位设置):
OSCCON = 0B01110001; //WDT 32KHZ IRCF=111=16MHZ
//Bit0=1,系统时钟为内部振荡器
//Bit0=0,时钟源由FOSC<2:0>决定即编译选项时选择
2.3 SLEEP睡眠模式 (POWER-DOWN)
睡眠模式下,指令时钟关闭,指令执行停止,大多数模块掉电以降低功耗。除指令时钟外,其他模块可根据需求在 SLEEP 模式下保持运行:
SLEEP示例:
SLEEP(); //SYSCFG.h里定义#define SLEEP() asm("sleep")
NOP(); // 中断方式唤醒
从睡眠中唤醒有 2 个基本原则:
- 基于时间,即 CPU 在一定的时间后醒来。建议选择 LIRC 作为计时的时钟源,因为 LIRC 比 HIRC的功耗更低。
- 基于事件,即触发 POR,系统复位,仅唤醒而不产生中断,以及产生中断的事件,如 LVD,ADC,EEPROM 写完成,外部管脚中断。
注意:
- 如果在执行 SLEEP 指令之前发生中断(中断标志位置 1 且其中断使能,但全局中断 GIE 禁止),则SLEEP 指令将作为 NOP 执行,不会进入睡眠模式。
- 由于同步延时,将中断标志位清 0 之后,至少要等两条指令才可以执行 SLEEP 指令,否则不会进入睡眠模式。
- 从睡眠中唤醒将清零 WDT。
- 使用中断方式从 SLEEP 中唤醒时,将先执行下一条指令,然后再调用中断服务程序。为了避免重复执行,建议紧跟 SLEEP 的后面写为 NOP 指令。
2.4 低电压检测/比较器 (LVD)
LVD 的工作原理与LVR类似,但是所有控制位和参数选择位均由用户指令设置,而不是初始化配置时。LVD 相关寄存器汇总:
三、I/O端口与中断
在2.2小节已经启动系统时钟,这里首先看一下I/O端口功能:
然后看一下相关寄存器:
3.1 I/O初始化配置
寄存器配置步骤如下:
- TRISx:方向控制寄存器,0-输出 1-输入
- WPUx:弱上拉,1-使能 0-关闭
- WPDx:弱下拉,1-使能 0-关闭
- PSRCx:源电流能力,0-4mA 1-33mA
- PSINKx:灌电流能力,0-最小 1-最大
- PORTx:读:返回 IO 引脚上的电平;写:写入相应的 LATx 寄存器
- ANSELA:0-数字 1-模拟AD
void IO_INIT (void)
{
//配置输入输出
TRISA = 0B01111110; //PA输入输出
TRISB = 0B11110110; //PB7
TRISC = 0B11111101; //PC输入输出 0-输出 1-输入
//配置上下拉
WPUA = 0B00000000; //PA端口上拉控制 1-开上拉 0-关上拉
WPUB = 0B00000000; //PB端口上拉控制 1-开上拉 0-关上拉
WPUC = 0B00000000; //PC端口上拉控制 1-开上拉 0-关上拉
WPDA = 0B00000000; //PA端口上拉控制 1-开下拉 0-关下拉
WPDB = 0B00000000; //PB端口上拉控制 1-开下拉 0-关下拉
WPDC = 0B00000000; //PC端口上拉控制 1-开下拉 0-关下拉
//配置源电流和灌电流
PSRC0 = 0B11111111; //PORTA源电流设置最大
PSRC1 = 0B11111111; //PORTB源电流设置最大
PSRC2 = 0B11111111; //PORTC
PSINK0 = 0B11111111; //PORTA灌电流设置最大 0:最小,1:最大
PSINK1 = 0B11111111; //PORTB灌电流设置最大 0:最小,1:最大
PSINK2 = 0B11111111; //PORTC灌电流设置最大 0:最小,1:最大
//初始化IO状态
PORTA = 0B00000000; //输出低电平
PORTB = 0B00000000;
PORTC = 0B00000000;
ANSELA = 0B00000000; //全为数字管脚
}
注意:ANSELA寄存器只有作ADC使用时才置1
3.2 I/O中断配置
INTCON 寄存器:
外部管脚中断配置步骤:
- EPSx:外部中断管脚选择
- ITYPEx:中断触发类型,00-低电平 01-上升沿 10-下降沿 11-双边沿
- EPIE0:0-禁止外部中断 x 1-允许外部中断
- EPIF0:外部中断x标志位,写1清零,读1触发中断
- PEIE:1-使能外部中断 0-关闭
- GIE:1-使能全局中断 0-关闭
//外部中断初始化
void INTERRUPT_INIT (void)
{
EPS0=0B00000000; //选择PA0管脚中断
//Px0~Px3中断管脚选择
EPS1=0B00000000;
//Px4~Px7中断管脚选择
ITYPE0 = 0B00000011; //Px0上升沿下降沿唤醒(x = A/B)
ITYPE1 = 0B00000000;
EPIE0 = 0B00000001; //允许外部中断Px0
EPIF0 = 0xFF; //写1清中断响应标志位
PEIE = 1; //使能外部中断
GIE = 1; //开总中断
}
//中断处理函数
//用户中断函,不可删除
void user_isr()
{
//外部中断
if(EPIF0 & 0x01)
{
EPIF0 |= 0x01; //写1清中断0响应标志位
PEIE = 0; //关闭外设中断
}
}
四、USART接口
通用同步/异步收发器 USART 可与使用工业标准 NRZ 串行数据格式的外设进行通信,USART 相关寄存器地址:
通常我们使用的是异步串口UART,异步模式的数据通信格式为先发送低位,后发送高位。数据处理流程包括阻塞模式和非阻塞模式:
全双工和半双工的配置流程:
- 设置 UARTEN = 1,使能 USART 模块时钟;
- 设置通信波特率 = Fmaster / (16 * {DLH, DLL}) (参阅 ”DLH”, “DLL”);
- 设置通信数据长度为 7, 8 或 9 位 (参阅 “EXTEN”, “LTH”);
- 设置奇偶校验位 (参阅 “PEN”, “EVEN”);
- 设置停止位长度为 1 或 2 位 (参阅 “URSTOP”);
- 选择全双工 (默认) 或半双工工作模式 (参阅 “HDSEL”);
- 如需要,可使能相应的中断 (参阅” GIE”, ” PEIE”, ”URTE” , ”URRXNE”, “TCEN” 和 ”RXSE” 等);
- 根据需要,设置 TXEN = 1 或 RXEN = 1,使能发送或接收功能;
volatile unchar receivedata[10] =0;
volatile unchar senddata =0;
volatile unchar toSend[11]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa};
unchar i=0;
unchar mmm=0;
//串口初始化
void UART_INIT(void)
{
//PCKEN |=0B00100000; //打开UART时钟
TRISA6=0; //PA6 UART_TX
TRISA7=1; //PA7 UART_RX
WPUA | = 0x80;
URIER =0B00100001; //使能发送接收完成中断
URLCR =0B00000001; //8位数据,停止位1,无奇偶校验
URMCR =0B00011000;
URDLL =104; //9600波特率 = Fosc/16*[URDLH:URDLL]
URDLH =0;
ODCON0 = 0; //Tx开路输出
TCF=1;
INTCON=0B11000000;
//TCF: 发送完成标志
//TXEF:1发送寄存器为空
//RXNEF:1按收寄存器非空
}
void user_isr()
{
if(URRXNE && RXNEF) //接收中断
{
receivedata[mmm++] =URDATAL;
if(mmm>=10)
{
mmm=0;
}
NOP();
}
if(TCEN && TCF) //发送中断
{
TCF=1; //写1清0
if(i<10)
{
URDATAL =toSend[i++];
}
else
{
i=0;
}
NOP();
}
}
五、定时器
共有 4 个定时器,包括看门狗定时器(WDT)在内:
这边以基本定时器TIMER4为例,TIM4 原理框图:
TIM4 为 8 位向上计数器:计数器从 0 开始向上计数,当 T4CNT = T4ARR 时,产生上溢事件,然后重新从 0 开始计数。自动重载寄存器 T4ARR 由预装载寄存器和影子寄存器组成。Timer4 相关用户控制寄存器:
Timer4 中断使能和状态位:
Timer4配置步骤:
- 开启时钟,并进行相应的时钟分频
- 开启自动预装载,并设置TIM4ARR自动重装载寄存器的值
- 使能相应中断
void TIM4_INIT(void)
{
PCKEN |=0B00001000; //TIME4模块时钟使能
//TIM4CR1:BIT7-T4ARPE;BIT5~BIT4-T4CKS[1:0];BIT3-T4OPM;BIT2-T4URS;BIT1-T4UDIS;BIT0-T4CEN
TIM4CR1 =0B00000101;
//BIT7: 自动预装载允许位,0:TIM1_ARR寄存器没有缓冲,它可以被直接写入; 1:TIM1_ARR寄存器由预装载缓冲器缓冲。
//BIT6:保留
//BIT5~BIT4:timer4时钟选择位。
//00:系统时钟/主时钟
//01:内部快时钟HIRC
//10:LP时钟,只有当FOSC选择LP模式时才有意义
//11:XT时钟,只有当FOSC选择XT模式时才有意义
//BIT3:单脉冲模式
// 0:在发生更新事件时,计数器不停止;
// 1:在发生下一次更新事件(清除CEN位)时,计数器停止。
//BIT2:
// 0:如果UDIS允许产生更新事件,则下述任一事件产生一个更新中断:
//寄存器被更新(计数器上溢/下溢)
//软件设置UG位
//时钟/触发控制器产生的更新
// 1:如果UDIS允许产生更新事件,则只有当下列事件发生时才产生更新中断,并UIF置1:
//寄存器被更新(计数器上溢/下溢)
//BIT1:
// 0:一旦下列事件发生,产生更新(UEV)事件:
//计数器溢出/下溢
//产生软件更新事件
//时钟/触发模式控制器产生的硬件复位被缓存的寄存器被装入它们的预装载值。
// 1:不产生更新事件,影子寄存器(ARR、PSC、CCRx)保持它们的值。如果设置了UG位或时钟/触发控制器发出了一个硬件复位,则计数器和预分频器被重新初始化。
// BIT0: 0:禁止计数器;1:使能计数器。
TIM4IER =0B00000001;
//BIT0-T4UIE: 0:禁止更新中断;1:允许更新中断。
TIM4SR =0B00000000;
//BIT0-T4UIF:当产生更新事件时该位由硬件置1。它由软件写1清0
//0:无更新事件产生;
//1:更新事件等待响应。当寄存器被更新时该位由硬件置1:
//若TIM4_CR1寄存器的UDIS=0,当计数器上溢或下溢时;
//若TIM4_CR1寄存器的UDIS=0、URS=0,当设置TIM4_EGR寄存器的UG位软件对计数器
//CNT重新初始化时;
//若TIM4_CR1寄存器的UDIS=0、URS=0,当计数器CNT被触发事件重新初始化时。
TIM4EGR =0B00000000;
//BIT0-T4UG:该位由软件置1,由硬件自动清0。
//0:无动作;
//1:重新初始化计数器,并产生一个更新事件。注意预分频器的计数器也被清0(但是预分频系数不变)。若在中心对称模式下或DIR=0(向上计数)则计数器被清0;若DIR=1(向下计数)则计数器取TIM1_ARR的值。
TIM4CNTR=0; //TIM4 8位计数器
TIM4PSCR=0B00000100;
//BIT2~BIT0-T4PSC[2:0]:预分频器对输入的CK_PSC时钟进行分频。
//计数器的时钟频率fCK_CNT等于fCK_PSC/2(PSC[2:0])。PSC[7:3]由硬件清0。
//PSCR包含了当更新事件产生时装入当前预分频器寄存器的值(包括由于清除TIMx_EGR寄存器的UG位产生的计数器清除事件)。这意味着如要新的预分频值生效,必须产生更新事件或者CEN=0。
TIM4ARR =124;
//ARR包含了将要装载入实际的自动重装载寄存器的值。
//当自动重装载的值为空时,计数器不工作。
INTCON |= 0B11000000; //开总中断和外设中断
}
//用户中断函,不可删除
void user_isr()
{
//定时器4的中断处理
if(T4UIE && T4UIF)
{
T4UIF = 1; //写1清零标志位
Time4_8khz ++;
if (Time4_8khz >= 8)
{
Time4_8khz = 0;
Time_1ms ++;
if (Time_1ms >= 1000)
{
Time_1s++;
Time_1ms=0;
}
if (Time_1s >= 6000) Time_1s=0;
}
}
}
六、ADC
ADC 模块可将模拟输入信号转换成 12-bit 的数字信号,结构图如下所示:
emsp; 模拟输入信号可选择为7个 I/O (ANx) 通道之一或1 个内部通道 (1/4VDD)。ADC 由指令、I/O (PA4 / PB3)或 PWM 触发。在触发和 ADC 采样之间可增加延时或前沿消隐(Leading Edge Blanking, LEB)。当 ADC 转换完成时,将置位相应的中断标志位,并可触发中断和/或从睡眠中唤醒。
ADC 相关用户寄存器地址:
ADC 中断使能和状态位:
配置 ADC 包括以下设置 (更改配置时需设置 ADON = 0 以关闭 A/D 转换或外部触发):
- 设置 ADCEN = 1,打开 ADC 模块时钟;
- 配置端口:
a. 设置 TRISx = 1,禁止引脚输出驱动;
b. 设置 ANSELAx = 1,关闭数字输入、弱上拉和弱下拉功能; - 配置 ADC 模块:
a. 选择 ADC 转换时钟源;
b. 选择 ADC 参考电压;
c. 选择 ADC 触发条件:软件、PA4/PB3-ADC_ETR 或 PWM,有或无 LEB;
d. 选择转换结果格式;
e. 使能阈值比较(可选); - 配置 ADC 中断 (可选):
a. 使能 ADC 转换完成中断;
b. 使能外设总中断;
c. 关闭全局中断(如需执行中断服务程序则使能); - 打开 ADC 模块。然后等待所需稳定时间 TST (~15 µs),当 VADC-REF选择内部参考电压时,则需等待内部参考电压的稳定时间TVRINT 和TST时间的较长者,即max(TVRINT,TST)。
volatile unint adcData;
volatile unint theVoltage;
void ADC_INITIAL(void)
{
PCKEN |=0B00000001; //AD模块时钟使能
ANSELA = 0B00000001; //模拟口设置,AN0为模拟管脚
ADCON1 = 0B11100100; //右对齐,转换时钟Fosc/64,负参考电压GND,正参考电压内部电压(2V)
//BIT7:
//1 = 右对齐。装入转换结果时,ADRESH的高4位被设置为0;
//0 = 左对齐。装入转换结果时,ADRESL的低4位被设置为0。
//BIT6~BIT4:ADC转换时钟选择位
//000 = FOSC/2
//001 = FOSC/8
//010 = FOSC/32
//011 = FRC(由专用RC振荡器提供时钟)
//100 = FOSC/4
//101 = FOSC/16
//110 = FOSC/64
//111 = FRC(由专用RC振荡器提供时钟)
//BIT3~BIT2:ADC负参考电压配置位(使用PB6连接外部参考电压或外部电容)
//00 = Int Vref(内部参考电压)
//01 = GND
//10 = Int Vref + Ext Cap(内部参考电压 + 外部电容)
//11 = Ext Vref(外部参考电压)
//BIT1~BIT0 ADC正参考电压配置位(使用PB5连接外部参考电压或外部电容)
//00 = Int Vref(内部参考电压)
//01 = VDD
//10 = Int Vref + Ext Cap(内部参考电压 + 外部电容)
//11 = Ext Vref(外部参考电压)
ADCON0 = 0B00000000; //选择AD转换通道0,使能ADC
//BIT6~BIT4:ADC模拟通道选择位
//000 = AN0
//001 = AN1
//010 = AN2
//011 = AN3
//100 = AN4
//101 = AN5
//110 = AN6
//111 = 1/4 VDD
//其余保留
//BIT3: 保留位
//BIT2: ADC触发信号类型选择
//该位决定启动ADC的触发条件
//0 = 当软件设定GO/DONE位,启动AD转换
//1 = 需要外部触发信号触发才可启动AD转换,触发事件置位GO/DONE位。
//外部触发信号条件由寄存器ETGSEL<2:0>和ETGTYP<1:0>决定。
//BIT1:
//0 = A/D转换完成/未进行。
//1 = A/D转换正在进行或硬件触发延时正在计数。
//BIT0:使能ADC
//0 = ADC被禁止且不消耗工作电流
//1 = ADC被使能
ADCON2 = 0B01000000; //选择内部正参考电压2V,无外部触发源
//BIT7~BIT6:ADC内部参考电压配置位
//00 = 0.5V
//01 = 2V
//10 = 3V
//11 = float(悬空)
//BIT5~BIT4:外部触发信号类型选择
//当ADEX置1,该位决定响应外部触发的类型
//00 = PWM 或 ADC_ETR脚的下降沿
//01 = PWM 或 ADC_ETR脚的上升沿
//10 = 一个PWM周期的中点
//11 = 一个PWM周期的终点
//BIT3:ADC外部触发延时计数器阈值 第8位
//BIT2~BIT0:外部触发源选择
//当ADEX为1,该位选择外部触发ADC的来源
//选择PWM源时需要配置TIMER为PWM输出模式并使能输出。
//000 = PWM0
//001 = PWM1
//010 = PWM2
//011 = PWM3
//100 = PWM4
//101 = PWM5
//110 = PWM6
//111 = ADC_ETR
ADCON3 = 0B00000000;
//BIT7:ADC比较结果响应故障刹车使能
//0 = 禁止
//1 = ADC触发故障刹车功能使能
//BIT6:ADC比较器输出极性选择位
//0 = 若ADC结果的高八位大于或等于ADCMPH[7:0],ADCMPO为1
//1 = 若ADC结果的高八位小于ADCMPH[7:0],ADCMPO为1
//BIT5:ADC结果比较使能位
//0 = ADC结果比较功能关闭
//1 = ADC结果比较功能打开
//BIT4:ADC比较结果输出位
//该位输出ADCMPOP设定的比较输出结果。每次AD转换结束都会更新输出
//BIT3:前沿消隐周期结束后,ADC触发使能
//1 = 触发ADC转换
//0 = 不触发ADC转换
//BIT2:保留位
//BIT1~BIT0 外部LVD管脚输入选择,只有当LVDM为1时才有效
//00 = ELVD0
//01 = ELVD1
//10 = ELVD2
//11 = ELVD3
ADDLY = 0B00000000; //外部触发廷时,没用到
//ADC外部触发启动延时计数器阈值低位
//该8位寄存器与ADCON2.7组成9位计数器,用于在外部触发启动ADC之前加入一段延迟。延迟计数器结束再开始ADC转换
//外部延迟时间 = (ADDLY+6)/FADC
ADCMPH = 0B00000000; //ADC比较阈值,仅8位,用于ADC结果高8位比较。
ADON=1; //全能ADC
}
/*-------------------------------------------------
* 函数名: GET_ADC_DATA
* 功能: 读取通道ADC值
* 输入: adcChannel 通道序号
* 输出: INT类型AD值(单次采样无滤波)
--------------------------------------------------*/
unint GET_ADC_DATA (unchar adcChannel)
{
ADCON0 &= 0B00001111;
ADCON0 |= adcChannel<<4; //重新加载通道值
DelayUs(40); //廷时等待电压稳定 Tst >10us
GO = 1; //启动ADC
NOP();
NOP();
while(GO); //等待ADC转换完成
return (unint)(ADRESH<<8|ADRESL); //整合12位AD值
}
//电压计算:U=采样量/(2^12bit)*2V
void ADC_Calculate(void)
{
adcData = GET_ADC_DATA(0); //通道0 AD值
theVoltage = (unlong)adcData*2*1000/4096; //电压放大1000倍
}
七、EEPROM
FT61E14x 片内集成的非易失性 DATA EEPROM 存储区和程序存储器 PROM 均可通过指令进行读/写访问,由 ”CFGS” 和 ”EEPGD” 选择所访问的存储区。128 x 8−bit 的 DATA EEPROM 和 4k x 14−bit (128 page x 32 words) 的程序 PROM 相互独立。EEPROM 和 PROM 相关用户控制寄存器:
- 写 DATA EEPROM
1.设置 “GIE = 0”;
2.判断 GIE,如果 “GIE = 1”,则重复步骤 (1);
3.将目标地址写入 EEADRL;
4.将目标数据写入 EEDATL;
5.设置 “CFGS = 0” 和 “EEPGD = 0”,选择访问 DATA EEPROM;
6.设置 “WREN = 1”;
7.向 EECON2 中顺序写入 0x55 和 0xAA;
8.设置 “WR = 1” 以启动写;
9.编程完成 (编程时间请参阅 TWRITE-EEPROM) 后,”WR” 将由硬件自动清 0;
- 读 DATA EEPROM
1.设置 “GIE = 0”;
2.判断 GIE,如果 “GIE = 1”,则重复步骤 (1);
3.将目标地址写入 EEADRL;
4.设置 “CFGS = 0” 和 “EEPGD = 0”,选择访问 DATA EEPROM;
5.设置 “RD = 1” 以启动读;
6.等待 4 个指令周期后,从 EEDATL 中读取目标数据。EEDATL 寄存器将保持此值直至下一次读或写操作。”RD”将由硬件自动清 0;
注意:
- 当编程正在进行中时,对 DATA EEPROM 进行读操作将导致读取结果错误。
- 在启动 DATA EEPROM 的写操作前,需要先解锁,即向 EECON2 中顺序写入 0x55 和 0xAA,且不能被打断,因此解锁前需关闭所有中断。
- GIE 清零后,需等待 2 个 NOP 的中断响应延时,再次判断 GIE 是否被清零。
- WR 置 1 后,需至少等待一个 Sysclk (NOP 或任何其他指令),软件才能读到正确的 WR 值,否则将读回 0 (误认为写结束)。
- WR 置 1 后,清零 WREN 不会影响当前的写周期。
- 当写 DATA EEPROM 结束后,WREN 需由软件清零,此保护机制可防止误写操作。此外,上电延时定时器 PWRT(~64ms)也会阻止对 DATA EEPROM 进行写操作。
- 无论 CPB 为何值,软件总是可以读取 EEPROM。
/*----------------------------------------------------
* 函数名称:EEPROMread
* 功能: 读EEPROM数据
* 输入参数:EEAddr 需读取数据的地址
* 返回参数;ReEEPROMread 对应地址读出的数据
----------------------------------------------------*/
unchar EEPROMread(u8 EEAddr)
{
unchar ReEEPROMread;
EEADRL = EEAddr;
CFGS =0;
EEPGD=0;
RD = 1;
NOP();
NOP();
NOP();
NOP();
ReEEPROMread = EEDATL;
return ReEEPROMread;
}
/*----------------------------------------------------
进行FLASH/EEDATA操作时,解锁FLASH/EEDATA的时序不能被打断。
程序中要将此段用汇编指令处理防止被优化
----------------------------------------------------*/
void Unlock_Flash()
{
#asm
MOVLW 0x03
MOVWF _BSREG
MOVLW 0x55
MOVWF _EECON2 & 0x7F
MOVLW 0xAA
MOVWF _EECON2 & 0x7F
BSF _EECON1& 0x7F,1 //WR=1;
NOP
NOP
#endasm
}
/*----------------------------------------------------
* 函数名称:EEPROMwrite
* 功能: 写数据到EEPROM
* 输入参数:EEAddr 需要写入数据的地址
* Data 需要写入的数据
* 返回参数:无
----------------------------------------------------*/
void EEPROMwrite(u8 EEAddr,u8 Data)
{
while(GIE) //等待GIE为0
{
GIE = 0; //写数据必须关闭中?
NOP();
NOP();
}
EEADRL = EEAddr; //EEPROM的地址
EEDATL = Data; //EEPROM的写数据
CFGS =0; //访问EEPROM存储器
EEPGD=0; //访问EEPROM存储?
EEIF = 0;
WREN=1; //写使能
Unlock_Flash(); //Flash 解锁时序不能修改
NOP();
NOP();
NOP();
NOP();
while(WR); //等待EE写入完成
WREN=0;
GIE = 1;
}