前言
介绍一下单片机STM8的学习。首先开发环境搭建,可以在IAR下开发,也可以使用官方STVD开发。IAR收费,STVD界面太丑,使用不方便
IAR For STM8下载链接:https://download.csdn.net/download/weixin_44567668/87365232
STVD软件包:https://download.csdn.net/download/weixin_44567668/87446511
STM8标准库:https://download.csdn.net/download/weixin_44567668/88633544
本文以IAR新建工程,参考代码见附件资源
一、新建工程
1.1 文件创建
新建一个Template文件夹,然后在其中创建三个子文件夹Driver、EWSTM、APP
1.2 工程创建
打开IAR,选择Project->Create New Project后对话框点击OK
文件路径选择Template下的EWSTM里,这边命名Test
点击Save All,保存工程文件.eww,以后就可以使用这个文件打开工程
1.3 添加工程
首先右击->Add->Add Group,添加分组,这边取名APP
新建一个文件
复制以下代码
#include "iostm8s207rb.h"
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
void Clk_conf(void)
{
CLK_CKDIVR&= (uint8_t)(~0x18);/*使能内部时钟*/
CLK_CKDIVR|= (uint8_t)0x00;/*设置时钟为内部16M高速时钟*/
}
int main(void)
{
Clk_conf();
// LED_conf();
while(1)
{
}
}
保存文件名为main.c,路径为Template–APP
此时对APP分组右击就可以把main.c添加进来
1.4 工程选项设置
工作空间->右击->Option打开工程选项
General Options->Target->Device,找到相应芯片
General Options->Library Configuration->Library选择Full
C/C++ Compiler里面添加头文件,这里没有就先不添加了
点击OK结束,回到工程点击快捷键F7即可编译工程
补充:其他IAR相关设置,参考以下链接:
https://blog.csdn.net/weixin_44567668/article/details/122479777
二、GPIO
2.1 GPIO简介
GPIO(General Purpose Input/Opuput,通用输入/输出口)用于单片机和外部设备进行数据传输,当 GPIO设置为输出时,单片机可以输出任意数据到 GPIO;当 GPIO 设置为输入时,单片机可以读取别的设备传输至 GPIO 上的数据。有些 GPIO 除了做为普通输入输出使用之外,还具有第二功能。比如有些 I/O 还具有模拟输入、外部中断、片上外设等第二功能。但是某一个时刻只能完成一个功能。
浮动输入与上拉输入是 GPIO 作为输入时的两种方式,浮动输入是 GPIO 的管脚的电平状态是不确定的,容易受到外部电平的干扰;上拉输入时,管脚的电平则只有高电平或低电平两种确定的状态。一般I/O 做外部中断输入时要设置为上拉输入,做 A/D 转换时要设置为浮动输入。
推挽输出、开漏输出则为 GPIO 输出的两种方式。推挽输出方式能够独立的输出高电平和低电平,适合驱动数字器件;开漏输出可以稳定的输出低电平,能承受大的灌电流,但是开漏输出无法输出高电平,需要外接上拉电阻才可以输出高电平,输出的高电平则有外接的上拉电平决定,这种输出方式适合做电流型驱动。
2.2 GPIO寄存器
2.2.1 端口 X 输出数据寄存器 Px_ODR
在端口为输出模式时,写入此寄存器的数值会通过芯片内部锁存器加到对应的管脚上;读此寄存器,则返回之前所存的值。
在端口为输入模式时,写入此寄存器的数值也会被锁存至寄存器,但不会影响管脚上原有的电平状态。
2.2.2 端口X输入寄存器Px_IDR
不管端口处于输入模式还是输出模式,都可以通过读该寄存器获取管脚的电平;返回 0 代表对应管脚为逻辑低电平,返回 1 代表对应管脚为逻辑高电平。此寄存器只读。
2.2.3 端口 X 数据方向寄存器 Px_DDR
这些位可通过软件置1或置0,用来在输入或输出模式下选择不同的功能。
在输入模式时(DDR=0):
0:浮空输入
1:带上拉电阻输入
在输出模式时(DDR=1):
0:模拟开漏输出(不是真正的开漏输出)
1: 推挽输出, 由CR2相应的位做输出摆率控制
2.2.4 端口 X 控制寄存器 1 Px_CR1
在输入模式时(DDR=0):
0: 禁止外部中断
1: 使能外部中断
在输出模式时(DDR=1):
0:输出速度最大为2MHZ.
1:输出速度最大为10MHZ
2.2.5 端口 X 控制寄存器 2 Px_CR2
2.3 使用GPIO 的一些小提醒
①复位之后,所有的 GPIO 均为悬浮输入模式;
②没有使用的管脚,必须连接到一个固定的电平;
③低功耗模式 Halt 下,PA1、PA2 如果被用来连接外部谐振器,为达到最低功耗,PA1、PA2 需要设置为上拉输入;
④管脚复用功能的输入功能使用之前需要设置对应管脚为输入模式;
⑤管脚的外部中断对应管脚需要设置为输入模式;
⑥ADC 输入管脚推需要设置为不带中断浮空输入,推荐禁止施密特触发器;
⑦当管脚设置为 SPI 时,为达到最大性能,对应管脚必须设置为带上拉、快速摆率;
⑧复位之后,所有 GPIO 相关寄存器全部被清零;
⑨GPIO 配置一览表如表所示:
2.4 示例代码(寄存器操作)
//GPIO 进行初始化
void InitLED(void)
{
PC_DDR|=0x08;//设置 PC3 为输出模式
PC_CR1|=0x08;//设置 PC3 为推挽输出
PC_CR2|=0x00;//设置 PC3 为 10MHz 快速输出
PE_DDR|=0x01;//设置 PE0 为输出模式
PE_CR1|=0x01;//设置 PE0 为推挽输出
PE_CR2|=0x00;//设置 PE0 为 10MHz 快速输出
PD_DDR|=0x08;//设置 PD3 为输出模式
PD_CR1|=0x08;//设置 PD3 为推挽输出
PD_CR2|=0x00;//设置 PD3 为 10MHz 快速输出
}
//执行
void main( void )
{
InitLED(); //初始化 LED 端口
while(1)
{
PC_ODR&=0xF7;//PC_ODR 第 3 位清零,输出低电平
PE_ODR&=0xFE; //PE_ODR 第 0 位清零,输出低电平
PD_ODR&=0xF7; //PD_ODR 第 3 位清零,输出低电平
Delay(5000); //延时
PC_ODR|=0x08;//PC_ODR 第 3 位置位,输出高电平
PE_ODR|=0x01; //PE_ODR 第 0 位置位,输出高电平
PD_ODR|=0x08; //PD_ODR 第 3 位置位,输出高电平
Delay(5000); //延时
}
}
三、中断控制器 ITC
3.1 中断控制器简介
STM8S 单片机中断控制器功能非常强大,其特点可以总结如下:
■硬件中断管理:
—所有 I/O 口都具有外部中断能力,每个端口都具有独立的中断向量和独立的中断标志;
—外设中断;
■软件中断管理 TRAP;
■具有灵活的优先级和中断等级管理,支持可嵌套的或同级中断管理:
—多达 4 个软件可编程的嵌套等级;
—最多有 32 个中断向量,中断向量的入口地址由硬件固定;
—2 个不可屏蔽的中断:RESET、TRAP;
—1 个不可屏蔽的最高优先级的硬件中断 TLI(PD7);
3.2 中断向量与中断指令
STM8S 单片机的中断源有 2 类:不可屏蔽中断、可屏蔽中断。
不可屏蔽中断是无法屏蔽的中断,只要有中断请求,处理器必须要处理。不可屏蔽中断有复位中断、TRAP、TLI。
复位中断在软件中断、硬件中断中的优先级最高,上电、掉电、看门狗溢出、软件复位、SWIM 复 位、非法操作码、EMS 干扰以及正常的复位信号都可引起复位中断。此中断可以使处理器退出停机模式。
TRAP(不可屏蔽的软件中断)是执行 TRAP 指令时产生的软件中断。此中断不能使处理器退出停机模式。
TLI 中断在所有外部中断中级别最高且不可屏蔽。
可屏蔽中断是可以通过相关寄存器关掉的中断,当中断被关掉之后,即使有中断请求信号,处理器也不会响应,故称为可屏蔽中断。可屏蔽中断包括所有的外部中断及片上外设中断。
STM8S 的外部中断向量一共有 5 个:
■PA 口:PA[6:2] 共 5 个管脚
■PB 口:PB[7:0] 共 8 个管脚
■PC 口:PC[7:0] 共 8 个管脚
■PD 口:PD[6:0] 共 7 个管脚
■PE 口:PE[7:0] 共 8 个管脚
软件中断优先级:
STM8 中断指令:
3.3 中断寄存器
3.3.1 CPU CC 寄存器
3.3.2 软件优先级寄存器 ITC_SPRx
ITC_SPRx 寄存器一共由 8 个寄存器组成,每两位控制一个中断的软件优先级,通过软件对其进行读写。值与软件优先级的对应关系,请参考前面的相关描述。对此组寄存器进行设置时,只能将中断的软件优先级设置为 1~3,禁止设置为 0,即禁止写入值 10B。此组寄存器的值会在中断请求发生时,自动复制到 CPU CC 寄存器的 I[1:0]位。
ITC_SPRx 的寄存器位与中断源的对应关系:
3.3.3 外部中断控制寄存器 1
3.3.4 外部中断控制寄存器2
3.4 示例代码
//中断初始化
void InitEXTI(void)
{
EXTI_CR1=(2<<0);//PA 下降沿触发
PA_DDR&=0x8f;//PA[6:4]为输入模式
PA_CR1|=0x70;//PA[6:4]上拉
PA_CR2|=0x70;//PA[6:4]使能外部中断
}
//中断服务函数
#pragma vector=0x05 //定义中断服务函数入口地址
__interrupt void EXTI0_IRQHandler(void) //STVD不用__interrupt,而用@far @interrupt
{
asm("sim");//关中断;asm()为在 C 语言程序中插入汇编语句
if(PA_IDR_IDR4==0)
PC_ODR_ODR3^=1;//PC_ODR 第 3 位翻转
if(PA_IDR_IDR5==0)
PE_ODR_ODR0^=1; //PE_ODR 第 0 位翻转
if(PA_IDR_IDR6==0)
PD_ODR_ODR3^=1;//PD_ODR 第 3 位翻转
asm("rim"); //开中断
}
//主函数
void main( void ) {
InitLED(); //初始化 LED
InitEXTI();//初始化外部中断
asm("rim"); //开中断
while(1) //等待中断
{
}
}
四、定时器
4.1 STM8S定时器简介
随着单片机技术的发展,定时器的功能越发强大,从最原始的定时、计数,到高级的 PWM 输出、输入捕获,越来越多的功能成为现代单片机定时器的标配。为了满足不同的应用需求,STM8S 提供了三种
不同类型的定时器:
■ 高级控制型:TIM1,16 位;
■ 通用型:TIM2/TIM3/TIM5,16 位;
■ 基本型:TIM4/TIM6,8 位;
三种类型的定时器的功能对比如表:
4.2 8位基本型定时器TIM4
STM8S 的定时器 TIM4 由一个带可编程预分频器的 8 位可自动重载的向上计数器组成。TIM4 的功能图如图所示:
由图 6.1 可以看出,TIM4 的时钟源为系统主时钟 f MASTER ,因为 f MASTER 来源于 HSE、HSI、LSI,所以也相当于 TIM4 的时钟源可以为 HSE、HSI、LSI。f MASTER 直接连接到 CK_PSC 时钟,然后经过预分频器分频,3 位可编程预分频器可以提供 1、2、4、8、16、32、64、128 的分频,生成 CK_CNT 时钟,以驱动向上计数器进行计数。计数时钟的频率为:
向上计数时,计数器从 0 开始计数,当计数值(TIM4_CNTR 寄存器的值)增加到用户预先设置在自动重装载寄存器 TIM4_ARR 寄存器中的值时,计数器重新从 0 开始计数并产生一个计数器溢出事件。此时,如果定时器 TIM4_CR1 寄存器中的 UDIS 位为 0,则会产生一个定时器更新事件 UEV。UEV 不一定必须定时器溢出时才会产生,如果 TIM4 计数器没有溢出,我们可以通过在程序中置位 TIM4_EGR 寄存器的UG 位来产生一个更新事件 UEV。使用软件置位 TIM4_CR1 寄存器中的 UDIS 位,可以禁止 UEV 事件的产生,可以避免在更新预装载寄存器时更新影子寄存器。
什么是影子寄存器呢?通俗点讲就是某个寄存器在物理上对应 2 个寄存器,一个物理寄存器呈现在程序员面前,可以由程序员来读写它的值,我们称其为预装载寄存器(preload register);另一个寄存器则对程序员屏蔽,程序员无法直接对其操作,但实际中是它在起真正的作用,我们称其为影子寄存器(shadow register)。
在计数器计数的过程中,需要时刻将计数值与预设值进行比较,以判断是否有溢出,实际与计数值比较的不是 TIM4_ARR 寄存器,而是其影子寄存器。设计预装载寄存器与影子寄存器的优点是可以保证多个通道时可以准确的同步,因为这种架构下所有通道可以在更新事件发生时在同一时间更新影子寄存器为所对应的预装载寄存器,而如果只有预装载寄存器,则只能通过程序一条一条的顺序更新,无法做到准确同步。
更新事件发生后 STM8S 的 CPU 完成以下的动作:
■ TIM4_ARR 的值被重新更新到其影子寄存器;
■ 预分频寄存器 TIM4_PSCR 的值被重新更新到预分频寄存器缓冲器;
■ 硬件更新状态寄存器 TIM4_SR1 中的 UIF 位(更新中断标志位);
如果想使用 TIM4 的溢出中断,还需要使能 TIM4 的溢出中断功能,具体为置位 TIM4_IER 寄存器的
UIE 位。
4.3 TIM4的相关寄存器
4.3.1 控制寄存器 1 (TIMx_CR1)
4.3.2 中断使能寄存器 TIM4_IER
3.3.3 状态寄存器 1 TIM4_SR1
4.3.4 事件产生寄存器 TIM4_EGR
4.3.5 计数器 (TIMx_CNTR)
4.3.6 预分频寄存器 TIM4_PSCR
4.3.7 自动重装寄存器 TIM4_ARR
4.4 示例代码
//定时器初始化
void InitTIM4(void)
{
TIM4_PSCR=0x07;//分频值,2M/2^7=15.625K
TIM4_IER=0x01;//更新中断使能
TIM4_CNTR=255;//计数器初值,255*(1/15.625K)=0.01632S
TIM4_ARR=255;//自动重装的值
}
//定时器中断函数
#pragma vector=TIM4_OVR_UIF_vector //定义中断服务函数入口地址
__interrupt void TIM4_OVR_UIF__IRQHandler(void)
{
i++;
TIM4_SR=0x00; //清除中断标志
if(i==61)
{ //翻转 LED 接口的输出状态
PC_ODR_ODR3^=1;
PE_ODR_ODR0^=1;
PD_ODR_ODR3^=1;
i=0;
}
}
//主函数
void main( void )
{
InitLED();
InitTIM4();
asm("rim");
TIM4_CR1|=0x01;
while(1)
{
}
}
五、独立看门狗(IWDG)
STM8S 单片机带有 2 种看门狗:独立看门狗、窗口看门狗。这里主要介绍独立看门狗
5.1 独立看门狗框图
STM8S 的独立看门狗用于解决因为硬件或软件的故障而发生的错误。它是由芯片内部 128K 的 LSI驱动,即使系统主时钟失效它也可以工作。
STM8S 芯片内部的 128K LSI 时钟经过 2 分频之后到达预分频器,预分频器的值由预分频寄存器 IWDG_PR 来设置。分频后的时钟驱动 8 位向下计数器计数。8 位向下计数器的计数初值为重装载寄存器 IWDG_RLR 的值,程序运行中为了避免计数器的计数值达到 0,需要周期性的“喂狗”,喂狗的值即为 IWDG_RLR 中的值。喂狗操作时通过给键寄存器 IWDG_KR 中写入 0xAA 实现的,写入此值后,重装载寄存器 IWDG_RLR 中的值就写入计数器,从而完成喂狗。IWDG_PR、IWDG_RLR 是带写保护的寄存器,要修改这 2 个寄存器的值需要先将 0x55 写入键寄存器IWDG_KR 中,修改完成之后需要写 0xAA 到 IWDG_KR 以恢复上述两个寄存器的写保护。写 0xCC 到 IWDG_KR 寄存器会启用独立看门狗。
5.2 相关寄存器
5.2.1 键寄存器(IWDG_KR)
BIT[7:0]为键值
0xCC:KEY_ENABLE,写入此值,IWDG 启动;
0xAA:KEY_REFRESH,写入此值,刷新 IWDG;
0x55:KEY_ACCESS,写入此值 IWDG_PR、IWDG_RLR 写保护失效,可以修改这两个寄存器的值。
5.2.2 预分频寄存器(IWDG_PR)
5.2.3 重装载寄存器(IWDG_RLR)
BIT[7:0],独立看门狗计数器重装载值。向 IWDG_KR 寄存器写入 0xAA,此寄存器的值会写入计数器,从而实现喂狗。喂狗之后计数器从这个值开始向下计数,在计数值变为 0 之前需要再次喂狗,否则到达 0 之后独立看门狗会产生看门狗复位信号。喂狗的超时时间由此寄存器的值及预分频系数决定。
独立看门狗喂狗时间参考表:
5.3 示例代码
//看门狗初始化
void InitIWDG(void)
{
IWDG_KR=0x55;//允许访问 IWDG_PR、IWDG_RLR
IWDG_PR=0x06;//256 分频
IWDG_RLR=250;//250/128000/256
IWDG_KR=0xAA;//重装载
IWDG_KR=0xCC;//使能 IWDG
}
//判断复位的触发源
void IWDG_RstStatus(void)
{
if(RST_SR&0x02)//如果复位由独立看门狗触发
PD_ODR&=0xF7;//PD_ODR 第 3 位清零,输出低电平
RST_SR=0x02;//清除标记
}
//主函数
void main( void ) {
InitEXTI();
IWDG_RstStatus();
InitIWDG();
asm("rim"); //开中断
while(1)
{
PC_ODR=(0<<3);//PC_ODR 第 3 位清零,输出低电平
IWDG_KR=0xAA;//重装载
}
}
六、串口UART
UART很常见的功能不多解释
6.1 串口相关寄存器
6.1.1 状态寄存器 UART_SR
6.1.2 数据寄存器 UART_DR
数据寄存器包含了 UART 需要发送或者接收的数据,值取决于对该寄存器进行的操作是读取还是写入。它实际由 2 个寄存器组成:TDR,发送数据用,提供了内部总线和输出移位寄存器间的并行接口;RDR,接收数据用,提供了输入移位寄存器和内部总线间的并行接口。
6.1.3 波特率寄存器
- 波特率寄存器1(UART_BRR1)
- 波特率寄存器2(UART_BRR2)
STM8S 的 UART 接口波特率的设置是通过对 UART_BRR1 和 UART_BRR2 这两个寄存器操作完成的。UART_BRR1 和 UART_BRR2 组成 16 位的波特率寄存器,程序对 UART_BRR2 的写应该先于UART_BRR1,因为对 UART_BRR1 的写操作会更新波特率寄存器。波特率的计算公式为:
6.1.4 控制寄存器
- 控制寄存器(UART_CR1)
- 控制寄存器 2(UART_CR2)
- 控制寄存器 3(UART_CR3)
- 控制寄存器 4(UART_CR4)
- 控制寄存器 5(UART_CR5)
- 控制寄存器 6(UART_CR6)
5.1.5 保护时间寄存器(UART_GTR)
BIT[7:0],保护时间值。规定了以波特率时钟为单位的保护时间的值,智能卡模式下需要此功能,保护时间过后发送完成标志才被置位。UART3上不存在这些位
6.1.6 分频寄存器(UART_PSCR)
BIT[7:0],预分频器的值。
红外低功耗模式下,代表红外低功耗波特率。
PSC=00000000,保留;
PSC=00000001,对源时钟1分频; PSC=00000010,对源时钟 2 分频; PSC=00000011,对源时钟3分频;
智能卡模式下,代表预分频值,BIT[4:0]有效。
PSC=00000,保留;
PSC=00001,对源时钟 2 分频;
PSC=00010,对源时钟 4 分频;
6.2 示例代码
//串口初始化
void InitUART2(void)
{
UART2_CR1=0x00;//1 个起始位,8 个数据位,无校验位
UART2_CR2=0x2c;//接收中断使能,使能发送、接收
UART2_CR3=0x00;//1 个停止位
//设置波特率 9600
UART2_BRR2=0x00;
UART2_BRR1=0x0d;
}
//串口收发函数
void UART2_SendData8(unsigned char Data)//发送数据函数
{
while((UART2_SR&0x80)==0);//等待发送数据寄存器为空
UART2_DR = Data;//向数据寄存器写数据,发送
}
unsigned char UART2_ReceiveData8(void)//接收数据函数
{
while((UART2_SR&0x20)==0);//等待接收数据寄存器不为空
return ((unsigned char)UART2_DR);//读数据寄存器,接收
}
unsigned char HexTable[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};//十六进制代码表
void UART2_SendHex(unsigned char dat)//发送十六进制函数
{
UART2_SendData8('0');
UART2_SendData8('x');
UART2_SendData8(HexTable[dat>>4]);
UART2_SendData8(HexTable[dat&0x0f]);
UART2_SendData8(' ');
}
void UART2_SendStr(unsigned char *dat)//发送字符串函数
{
while(*dat!='\0')
{
UART2_SendData8(*dat);
dat++;
}
}
//串口中断函数
#pragma vector=UART2_R_RXNE_vector //定义中断服务函数入口地址
__interrupt void UART2_R_RXNE__IRQHandler(void)
{
UART2_SendData8(UART2_ReceiveData8());
}
//主函数
void main( void ) {
InitUART2();
UART2_SendStr("STM8S UART 实验!\r\n");
asm("rim");
while(1)
{}
}
七、串行外设接口 SPI
串行外设接口 (Serial Peripheral Interface,SPI)是一种单主多从的全双工同步串行通信协议。通过 SPI接口 MCU 可以与外设以串行方式进行通信。SPI 接口一般由 4 条通信线路:
SCK,同步时钟信号线。由 SPI 总线上的主设备产生。
MOSI,主输出、从输入信号线。数据从主设备输出进入从设备。
MISO,主输入、从输出信号线。数据从从设备输出进入主设备。
NSS,片选信号线。当总线上有多个从设备时,用此引脚来选择特定的从设备。从设备的 NSS 可以由主设备的 I/O 来驱动。
7.1 SPI 接口相关寄存器
7.1.1 控制寄存器
- 控制寄存器 1(SPI_CR1)
- 控制寄存器 2(SPI_CR2)
7.1.2 SPI 中断控制寄存器(SPI_ICR)
STM8S 的 SPI 接口有 6 个中断源:
TXE 表明发送缓冲器为空,数据发送完毕;RXNE 表明接收缓冲器中接收到了有效的数据。
实际应用中,为了降低功耗,会使系统进入低功耗模式。等待模式(Wait)下,SPI 通信不受影响;停机模式(Halt)下,如果 SPI 处于只接收模式,则会导致数据丢失;如果 SPI 处于全双工和只发送模式下,SPI 作为从设备,只要在进入停机模式之前 NSS 为低电平或 SSI 为 0,则可以从低功耗模式下唤醒而响应通信。
主模式错误、溢出错误、CRC 校验错误都会产生错误中断。主模式错误发生于硬件管理模式下主设备 NSS 引脚被拉低、软件模式下 SSI 被清零。溢出错误发生于主设备已经发出数据字节,而从设备还没有清楚上一次传输的数据字节。CRC 校验错误则发生于接收到的 CRC 与 SPI_RXCRCR 的值不匹配时。
7.1.3 SPI状态寄存器(SPI_SR)
7.1.4 SPI数据寄存器(SPI_DR)
SPI 数据寄存器对应 2 个缓冲区,一个用于发送数据,一个用于接收数据,写寄存器将会使数据放到发送缓冲区,读寄存器将会返回接收缓冲区的数据。
7.1.5 CRC相关寄存器
-
CRC多项式寄存器(SPI_CRCPR)
该寄存器包含计算 CRC 时使用的多项式。 -
接收CRC寄存器(SPI_RXCRCR)
此寄存器存放根据接收到的数据及 SPI_CRCPR 的值计算出的 CRC 校验值。 -
发送CRC寄存器(SPI_RXCRCR)
此寄存器存放根据要发送的数据及 SPI_CRCPR 中的值计算出的 CRC 校验值。
7.2 示例代码
//SPI初始化
void InitSPI(void)
{
SPI_CR1=0x1c; //MSB/16 分频/主设备/空闲低电平/第一个时钟边沿采样
SPI_CR2=0x03; //双向单线/软件 NSS/主模式
SPI_ICR=0; //禁止中断
SPI_CR1|=0x40; //使能 SPI
}
//SPI发送接受函数
unsigned char SPI_FLASH_SendByte(unsigned char byte)
{
while ((SPI_SR&0x02)==0);//等待发送完毕
SPI_SendData(byte);
while ((SPI_SR&0x01)==0);//等待接收完毕
return SPI_ReceiveData();
}
void SPI_SendData(unsigned char byte)
{
SPI_DR = byte;
}
unsigned char SPI_ReceiveData(void)
{
return((unsigned char)SPI_DR);
}
八、存储器
8.1 STM8S 片内存储器的特性
STM8S 单片机片内集成 FLASH 程序存储器和数据 EEPROM,总体特性如下:
■STM8S 片内 EEPROM 分为两个存储器阵列:
—最多 128K 字节的 FLASH 程序存储器,用于存储用户程序,不同的芯片型号存储容量不同;
—包含选项字节 OPTION BYTES在内,最多 2K 字节的数据 EEPROM,不同型号的芯片存储容量不同。
■具有多种编程模式:
字节编程和自动快速字节编程(没有擦除操作)、字编程、块编程和快速块编程(没有擦除操作)、编程/擦除操作结束时和发生非法编程操作时产生中断;
■具有读同时写(RWW)功能。不是所有 STM8S 芯片都具有;
RWW 功能允许用户在执行程序和读程序存储器时对数据 EEPROM 进行写操作。但不可以在写得时候进行读操作。
■具有在应用编程(IAP)和在线编程(ICP)能力。
—IAP 使 STM8S 单片机使用支持的接口(UART、SPI、IIC…)来下载数据,允许在应用程序中对FLASH程序存储器的内容进行重新编排。要使用 IAP 功能需要先通过 ICP 对 FLSH 程序存储器预先编程。
—ICP用于更新整个存储器的内容。它使用 SWIM 接口将程序下载到芯片中。
■具有多种保护特性:
存储器读保护(ROP)、基于存储器存取安全系统(MASS 密钥)的程序存储器写保护、基于存储器存取安全系统(MASS
密钥)的数据存储器写保护、可编程的用户启动代码区域(UBC)写保护。
■在待机模式 Wait 和活跃停机模式 Active Halt 下,存储器可设置为运行状态或掉电状态。
8.2 存储器保护
系统复位之后,主程序和 EEPROM 数据区都被自动保护以防止无意的写操作。如果想修改上述区域的内容,则需要先解锁。解锁机制由存储器存取安全系统 MASS 来管理。
芯片复位之后,可以通过向 FLASH_DUKR 寄存器连续写入两个被称作 MASS 密钥的值来解除程序存储器的写保护。这两个被写入的值与以下两个硬件密钥值进行对比:
● 第一个硬件密钥:0b01010110(0x56)
● 第二个硬件密钥:0b10101110(0xAE)
一般通过如下步骤来解除数据区域的写保护:
1.向 FLASH+DUKR 寄存器写入第一个 8 位密钥。系统复位后,当这个寄存器被首次写入值时,数据总线上的值没有被直接锁存到这个寄存器,而是和第一个硬件密钥值 0x56 比较;
2.如果密钥输入错误,应用程序可以尝试重新输入这两个 MASS 密钥来对数据区解锁;
3.如果第一个硬件密钥正确,当这个寄存器被第二次写入值时,数据总线上的值没有被直接锁存到这个寄存器,而是和第二个硬件密钥值 0xAE 比较。
4.如果密钥输入错误,EEPROM 数据区域在下一次系统复位之前将一直保持写保护状态。系统复位之前,向该寄存器进行的任何写操作都被忽略;
5.如果第二个硬件密钥正确,数据区域的写保护被解除,同时 FLASH_IARSR 寄存器的 DUL 位被置位。
对 FLASH 程序存储区的写操作则遵循以下步骤:
1.向 FLASH_PUKR 寄存器写入第一个 8 位密钥。系统复位后,当这个寄存器被首次写入值时,数据总线上的值没有被直接锁存到这个寄存器,而是和第一个硬件密钥值 0x56 比较;
2.如果密钥输入错误,FLASH_PUKR 寄存器在系统复位之前将被一直锁住。系统复位之前,向该寄存器进行的任何写操作都被忽略;
3.如果第一个硬件密钥正确,当这个寄存器被第二次写入值时,数据总线上的值没有被直接锁存到这个寄存器,而是和第二个硬件密钥值 0xAE 比较。
4.如果密钥输入错误,FLASH_PUKR 寄存器在系统复位之前将被一直锁住。系统复位之前,向该寄存器进行的任何写操作都被忽略;
5.如果第二个硬件密钥正确,数据区域的写保护被解除,同时 FLASH_IARSR 寄存器的 PUL 位被置位
8.3 FLAS相关寄存器
8.3.1 FLASH控制寄存器
- FLASH控制寄存器1(FLASH_CR1)
- FLASH控制寄存器 2(FLASH_CR2)
8.3.2 FLASH互补控制寄存器 2(FLASH_NCR2)
8.3.3 FLASH保护寄存器(FLASH_FPR)
BIT[7:6],保留位。
WPB[5:0],用户启动代码保护位,用于指示用户启动代码的大小,其值在启动时从 UBC选项字节加载。修改用户启动代码区的大小要通过修改选项字节来实现
8.3.4 FLASH保护寄存器(FLASH_NFPR)
BIT[7:6],保留位。
NWPB[5:0],用户启动代码保护位,用于指示用户启动代码的大小,其值为 NUBC 选项字节的对应字节。
8.3.5 FLASH程序存储器解保护寄存器(FLASH_PUKR)
PUK[7:0],程序存储器解锁密钥。
返回值:0x00
8.3.6 DATA EEPROM解保护寄存器(FLASH_DUKR)
DUK[7:0],数据 EEPROM 解锁密钥。
8.3.7 FLASH状态寄存器(FLASH_IAPSR)
8.4 示例代码
void eeprom_write(uchar len,uchar address) //写eeprom 长度及起始地址
{
uchar i,cou=0; //中间变量
_asm("sim"); //关全局中断
do
{
FLASH_DUKR=0xAE; // 写入第一个密钥
FLASH_DUKR=0x56; // 写入第二个密钥
}while(!(FLASH_IAPSR&0x08));
FLASH_CR2=0x40; //字编程
FLASH_NCR2=0xBF;
for(cou=0;cou<10;cou++)
_asm("nop");
p=(uchar *)(0x4000+address);
for(i=address;i<(address+len);i++) //字编程,每次写4个字节
{
*p=eeprom[i];
p++;
}
while(!(FLASH_IAPSR&0x04));
do //重新写保护
{
FLASH_IAPSR&=0xF7;
}while(FLASH_IAPSR&0x08);
_asm("rim"); //开全局中断
}
void eeprom_read(uchar len) //读eeprom
{
uchar i=0;
p=(uchar *)0x4000; //读之前复位指针
for(i=0;i<len;i++) //读出数据
eeprom[i]=*(p++);
}