STM8 的 C 语言编程 (1)-- 基本程序与启动代码分析 现在几乎所有的单片机都能用 C 语言编程了, 采用 C 语言编程确实能带来很多好处, 至少可读性比汇编语言强多了 在 STM8 的开发环境中, 可以通过新建一个工程, 自动地建立起一个 C 语言的框架, 生成后开发环境会自动生成 2 个

Similar documents
C语言的应用.PDF

bingdian001.com

目录

<4D F736F F D20B5DAC8FDCBC4D5C2D7F7D2B5B4F0B0B82E646F63>

untitled

Microsoft Word - MAN2011A_CH_RTT.doc

1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File

6 C51 ANSI C Turbo C C51 Turbo C C51 C51 C51 C51 C51 C51 C51 C51 C C C51 C51 ANSI C MCS-51 C51 ANSI C C C51 bit Byte bit sbit

图 内部结构图 8251 的外部引脚如图 所示, 共 28 个引脚, 每个引脚信号的输入输出方式如图中的箭 头方向所示

Microsoft Word - MSP430 Launchpad 指导书.docx

,Microchip Technology PIC LCD, PIC16F913/914/ 916/917/946 PIC18F6390/6490/8390/8490 PIC16F65J90/85J90 Microchip LCD LCD, Microchip 的优势 LCD PIC, LCD LC

51 C 51 isp 10 C PCB C C C C KEIL

学习MSP430单片机推荐参考书

chap07.key

CC213

+00DE _01EN.book

F515_CS_Book.book

1-1 SH79F6431 A. 2( ) 9~15V ( 12V) U2 U3 3.3V SH79F B. 1(VCC/GND) SH79F6431 C. VDDIO SH79F6431 P4 P5 P0.6 P0.7 VDDIO VDDIO=5V D. 2 V 1.0

2 12

EK-STM32F

C PICC C++ C++ C C #include<pic.h> C static volatile unsigned char 0x01; static volatile unsigned char 0x02; static volatile unsigned cha

JLX

Microsoft Word - mcu-an z-10.doc

MSP430X1XX 系列微控制器具有以下特征 结构框图 超低功耗结构体系 A 额定工作电流在 1MHz V 工作电压 C11X P11X 和 E11X 为 V 从备用模式唤醒为 6 S 丰富的中断能力减少了查询的需要灵活强大的处理能力源操作数有七种寻址模

<4D F736F F D203034CAB5D1E9CBC D20B5C4494F20BDD3BFDACAB5D1E92E646F63>

第5章:汇编语言程序设计

HD ( ) 18 HD ( ) 18 PC 19 PC 19 PC 20 Leica MC170 HD Leica MC190 HD 22 Leica MC170 HD Leica MC190 HD Leica MC170 HD

STM8单片机入门

Microsoft Word - Twin-CANÀý³Ì½éÉÜ.doc

untitled

2

2005.book

极客良品 -CC3200xx wifi 学习板 -PWM 实验 CC32xx-PWM 实验 片内定时器功能介绍 ( 使用的 mcu 外设和上一个节内容是一样的 ) 该 CC320 包含 4 个 32 位用户可编程通用定时 (GPTA0~3 或有文档标注为 TIMERA0~3),GPT 可以用于对具有

一 登录 crm Mobile 系统 : 输入 ShijiCare 用户名和密码, 登录系统, 如图所示 : 第 2 页共 32 页

CHCN_8-14_K.indd

PowerPoint 演示文稿

FY.DOC


Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc

新・明解C言語入門編『索引』

目 录

STM8L IAP 应用程序中编程指导

<4D F736F F D20CAB5D1E BACDBBE3B1E0D3EFD1D4B5C4BBECBACFB1E0B3CCCAB5D1E92E646F63>

ARM Cortex-M3 (STM32F) STMicroelectronics ( ST) STM32F103 Core: ARM 32-bit Cortex -M3 CPU 72 MHz, 90 DMIPS with 1.25 DMIPS/MHz Single-cycle multiplica

25.( 0 在 進 行 水 溫 與 溶 解 量 的 實 驗 時, 每 一 匙 糖 都 要 刮 平 的 主 要 目 的 為 何? 1 避 免 一 次 溶 解 太 多 糖 2 可 以 增 加 溶 解 糖 的 次 數 3 控 制 加 入 的 每 一 匙 糖 都 一 樣 多 4 可 以 減 少 溶 解 量

课外创新研学项目 构想、设计与实现

TD

第 1 章 MSP430 快速入门 因为最近转入 MPS430 的技术支持工作, 所以现在开始学习 430 的开发 由于之前用过 51, 也用过 TI 的 ARM CORTEX-M3, 但是就是没有用过 TI 的 430, 所以将 我学习 430 的过程写出来, 给像我一样之前没有 430 开发经验

2 Keil µ vision 2.1 1) Keil µ vision2 V2.34 µ vision3 2) Sino_Keil.exe Keil c:\keil\ 3) JET51 USB PC C:\Keil\ USB PC 4) S-L

DVK530/531扩展板

HT46R47 f SYS =4MHz 3.3~5.5V f SYS =8MHz 4.5~5.5V 13 位双向输入 / 输出口 1 个与输入 / 输出共用引脚的外部中断输入 8 位带溢出中断的可编程定时 / 计数器 具有 7 级预分频器 石英晶体或 RC 振荡器 位的程序存储器 P

C 1

ATMEL AT90S8515 AVR CPU AVR AVR AVR ATMEL RISC 32 8 r0 r X Y Z R0 R1 R2 R13 R14 R15 R16 R17 R26 R27 R28 R29 R30 R31 0x00 0x

STM8系列单片机入门

从MCS51向AVR的快速转换.PDF

X713_CS_Book.book

F²MC-8L/16LX/FR FAMILY

ICD ICD ICD ICD ICD

SMART 7P 0 HR7P OTPMCU A/D I/O OTP ROM RAM HR7P HR7P HR7PPMB MSOP0 7+input HR7PPSC HR7PPSD SOP SOP6 +input input HR7PERB SSOP0 7

CM ZT1

STC15W4K60S4系列增强型PWM波形发生器应用

Microsoft Word - 部分习题参考答案.doc

Microsoft Word - ISSFA-0134_A_AP_User-definedDownload_SC_.doc

<4D F736F F D20B9F9B0EABBCDBBAFAB48DEB3B4C1A5BDB3F8A7692E646F63>

MCCB EMI EMI

Microsoft Word - MAN2023A_CH_APPONE.doc

untitled

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.07.doc

<4D F736F F D20B5E7D7D3D0C5CFA2C0E0D7A8D2B5C5E0D1F8B7BDB0B8D0DEB6C1D6B8C4CF2E646F63>

STEP-MXO2 V2硬件手册

<4D F736F F D20B5A5C6ACBBFAD4ADC0EDD3EBD3A6D3C3BCB B3CCD0F2C9E8BCC65FB5DA33B0E65F2DD6D5B8E52D4E65772DBFB1CEF3B1ED2DB3F6B0E6C9E72E646F6378>

Microsoft Word - T12_T13_AD_PECÀý³Ì½éÉÜ.doc

第一次段考 二年級社會領域試題 郭玉華 (A)(B) (C)(D)

华恒家庭网关方案

nooog

Microsoft Word - mcu-an z-10.doc

Microsoft Word - LMB402CBC-AppNote-V0.1.doc

电子技术基础 ( 第 版 ) 3. 图解单相桥式整流电路 ( 图 4-1-3) 电路名称电路原理图波形图 整流电路的工作原理 1. 单相半波整流电路 u 1 u u sin t a t 1 u 0 A B VD I A VD R B

PIC16F F MPLAB 08 16F LED 15 LED

Microsoft Word - IRFWX-A124_A_SM59D03G2_SM59D04G2_PCA_ APN_SC_.doc

Microsoft Word - 实用案例.doc

Ioncube Php Encoder 8 3 Crack 4. llamaba octobre traslado General Search colony

Ps22Pdf

JTAG ICE PC JTAG ICE JTAG ISP... 5 IDE AVR STUDIO JTAGICE JTAGICE... 12

Andes Technology PPT Temp

DPJJX1.DOC

<4D F736F F D20C7B6C8EBCABDCFB5CDB3C9E8BCC6CAA6B0B8C0FDB5BCD1A75FD1F9D5C22E646F63>

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

《C语言程序设计》教材习题参考答案

Microsoft Word - ~ doc

untitled

Microsoft Word - 正文.doc

Microsoft Word - AN3259C

Electrical and Optical Clock Data Recovery Solutions - Data Sheet

<4D F736F F D C4EAC6D5CDA8B8DFB5C8D1A7D0A3D5D0C9FAC8ABB9FACDB3D2BBBFBCCAD4CEC4BFC6D7DBBACDCAD4BEEDBCB0B4F0B0B82DD6D8C7ECBEED2E646F63>

2013 C 1 # include <stdio.h> 2 int main ( void ) 3 { 4 int cases, a, b, i; 5 scanf ("%d", & cases ); 6 for (i = 0;i < cases ;i ++) 7 { 8 scanf ("%d %d


2/14 Buffer I12, /* x=2, buffer = I 1 2 */ Buffer I243, /* x=34, buffer = I 2 43 */ x=56, buffer = I243 Buffer I243I265 code_int(int x, char *buffer)

Transcription:

STM8 C 语言编程与模块使用范例 目录 STM8 的 C 语言编程 (1)-- 基本程序与启动代码分析 STM8 的 C 语言编程 (2)-- 变量空间的分配 STM8 的 C 语言编程 (3) GPIO 输出 STM8 的 C 语言编程 (4) GPIO 输出和输入 STM8 的 C 语言编程 (5)--8 位定时器应用之一 STM8 的 C 语言编程 (6)--8 位定时器应用之二 STM8 的 C 语言编程 (7)--16 位定时器的中断应用 STM8 的 C 语言编程 (8)-- UART 应用 STM8 与汇编语言 (9)--EEPROM 应用 STM8 的 C 语言编程 (10)-- 修改 CPU 的时钟 STM8 的 C 语言编程 (11)-- 切换时钟源 STM8 的 C 语言编程 (12)-- AD 转换 STM8 的 C 语言编程 (13)-- 蜂鸣器 STM8 的 C 语言编程 (14)-- PWM

STM8 的 C 语言编程 (1)-- 基本程序与启动代码分析 现在几乎所有的单片机都能用 C 语言编程了, 采用 C 语言编程确实能带来很多好处, 至少可读性比汇编语言强多了 在 STM8 的开发环境中, 可以通过新建一个工程, 自动地建立起一个 C 语言的框架, 生成后开发环境会自动生成 2 个 C 语言的程序, 一个是 main.c, 另一个是 stm8_interrupt_vector.c main.c 中就是一个空的 main() 函数, 如下所示 : main() while (1); 而在 stm8_interrupt_vector.c 中, 就是声明了对应该芯片的中断向量, 如下所示 : /* BASIC INTERRUPT VECTOR TABLE FOR STM8 devices * Copyright (c) 2007 STMicroelectronics */ typedef void @far (*interrupt_handler_t)(void); struct interrupt_vector unsigned char interrupt_instruction; interrupt_handler_t interrupt_handler; ; @far @interrupt void NonHandledInterrupt (void) /* in order to detect unexpected events during development, it is recommended to set a breakpoint on the following instruction */ return; extern void _stext(); /* startup routine */ struct interrupt_vector const _vectab[] = 0x82, (interrupt_handler_t)_stext, /* reset */ 0x82, NonHandledInterrupt, /* trap */ 0x82, NonHandledInterrupt, /* irq0 */

0x82, NonHandledInterrupt, /* irq1 */ 0x82, NonHandledInterrupt, /* irq2 */ 0x82, NonHandledInterrupt, /* irq3 */ 0x82, NonHandledInterrupt, /* irq4 */ 0x82, NonHandledInterrupt, /* irq5 */ 0x82, NonHandledInterrupt, /* irq6 */ 0x82, NonHandledInterrupt, /* irq7 */ 0x82, NonHandledInterrupt, /* irq8 */ 0x82, NonHandledInterrupt, /* irq9 */ 0x82, NonHandledInterrupt, /* irq10 */ 0x82, NonHandledInterrupt, /* irq11 */ 0x82, NonHandledInterrupt, /* irq12 */ 0x82, NonHandledInterrupt, /* irq13 */ 0x82, NonHandledInterrupt, /* irq14 */ 0x82, NonHandledInterrupt, /* irq15 */ 0x82, NonHandledInterrupt, /* irq16 */ 0x82, NonHandledInterrupt, /* irq17 */ 0x82, NonHandledInterrupt, /* irq18 */ 0x82, NonHandledInterrupt, /* irq19 */ 0x82, NonHandledInterrupt, /* irq20 */ 0x82, NonHandledInterrupt, /* irq21 */ 0x82, NonHandledInterrupt, /* irq22 */ 0x82, NonHandledInterrupt, /* irq23 */ 0x82, NonHandledInterrupt, /* irq24 */ 0x82, NonHandledInterrupt, /* irq25 */ 0x82, NonHandledInterrupt, /* irq26 */ 0x82, NonHandledInterrupt, /* irq27 */ 0x82, NonHandledInterrupt, /* irq28 */ 0x82, NonHandledInterrupt, /* irq29 */ ; 在 stm8_interrupt_vector.c 中, 除了定义了中断向量表外, 还定义了空的中断服务程序, 用于那些不用的中断 当然在自动建立时, 所有的中断服务都是空的, 因此, 除了第 1 个复位的向量外, 其它都指向那个空的中断服务函数 生成框架后, 就可以用 Build 菜单下的 Rebuild All 对项目进行编译和连接, 生成所需的目标文件, 然后就可以加载到 STM8 的芯片中, 这里由于 main() 函数是一个空函数, 因此没有任何实际的功能 不过我们可以把这个框架对应的汇编代码反出来, 看看 C 语言生成的代码, 这样可以更深入地了解 C 语言编程的特点 生成的代码包括 4 个部分, 如图 1 图 2 图 3 图 4 所示

图 1 图 2

图 3 图 4 图 1 显示的是从内存地址 8000H 开始的中断向量表, 中断向量表中的第 1 行 82008083H 为复位后 单片机运行的第 1 跳指令的地址 从表中可以看出, 单片机复位后, 将从 8083H 开始运行 其它行的 中断向量都指向同一个位置的中断服务程序 80D0H 图 2 显示的是 3 个字节, 前 2 个字节 8083H 为复位后的第 1 条指令的地址, 第 3 个字节是一个常量 0, 后面的启动代码要用到 图 3 显示的是启动代码, 启动代码中除了初始化堆栈指针外, 就是初始化 RAM 单元 由于目前是一个空的框架, 因此在初始化完堆栈指针 ( 设置成 0FFFH) 后, 由于 8082H 单元的内容为 0, 因此程序就跳到了 80B1H, 此处是一个循环, 将 RAM 单元从 0 到 5 初始化成 0 然后由于寄存器 X 设置成 01 00H, 就直接通过 CALL main 进入 C 的 main() 函数 图 4 显示的是 main() 函数和中断服务函数,main() 函数对应的代码就是一个无限的循环, 而中断服务 函数就一条指令, 即中断返回指令

通过分析, 可以看出用 C 语言编程时, 比汇编语言编程时, 就是多出了一段启动代码 STM8 的 C 语言编程 (2)-- 变量空间的分配 采用 C 这样的高级语言, 其实可以不用关心变量在存储器空间中是如何具体分配的 但如果了解如何分配, 对编程还是有好处的, 尤其是在调试时 例如下面的程序定义了全局变量数组 buffer 和一个局部变量 i, 在 RAM 中如何分配的呢? /* MAIN.C file * * Copyright (c) 2002-2005 STMicroelectronics */ unsigned char buffer[10]; // 定义全局变量 main() unsigned char i; // 定义局部变量 for(i=0;i<10;i++) buffer[i] = 0x55; 我们可以通过 DEBUG 中的反汇编窗口, 看到如下的对应代码 : 从这段代码中可以看到, 全局变量 buffer 被分配到空间从地址 0000H 到 0009H 而局部变量 i 则在堆栈空间中分配, 通过 PUSH A 指令, 将堆栈指针减 1, 腾出一个字节的空间, 而 SP+1 指向的空间就是分配给局部变量使用的空间 由此可以得出初步的结论, 对于全局变量, 内存分配是从低地址 0000H 开始向上分配的 而局部变量则是在堆栈空间中分配

另外从上一篇文章中, 可以知道堆栈指针初始化时为 0FFFH 而根据 PUSH 指令的定义, 当压栈后堆栈指针减 1 因此堆栈是从上往下使用的 因此根据内存分配和堆栈使用规则, 我们在程序设计时, 不能定义过多的变量, 免得没有空间给堆栈使用 换句话说, 当定义变量时, 一定要考虑到堆栈空间, 尤其是那些复杂的系统, 程序调用层数多, 这样就会占用大量的堆栈空间 总之, 在单片机的程序设计时, 由于 RAM 空间非常有限, 要充分考虑到全局变量 局部变量 程序调用层数和中断服务调用对空间的占用 STM8 的 C 语言编程 (3) GPIO 输出 与前些日子写的用汇编语言进行的实验一样, 从今天开始, 要在 ST 的三合一开发板上, 用 C 语言编写程序, 进行一系列的实验 首先当然从最简单的 LED 指示灯闪烁的实验开始 开发板上的 LED1 接在 STM8 的 PD3 上, 因此要将 PD3 设置成输出模式, 为了提高高电平时的输出电流, 要将其设置成推挽输出方式 这主要通过设置对应的 DDR/CR1/CR2 寄存器实现 利用 ST 的开发工具, 先生成一个 C 语言程序的框架, 然后修改其中的 main.c, 修改后的代码如下 编译通过后, 下载到开发板, 运行程序, 可以看到 LED1 在闪烁, 且闪烁的频率为 5HZ /* MAIN.C file * Copyright (c) 2002-2005 STMicroelectronics */ #include "STM8S207C_S.h" // 函数功能 : 延时函数 // 输入参数 :ms -- 要延时的毫秒数, 这里假设 CPU 的主频为 2MHZ // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void DelayMS(unsigned int ms) unsigned char i; while(ms!= 0) for(i=0;i<250;i++) for(i=0;i<75;i++)

ms--; // 函数功能 : 主函数 // 初始化 GPIO 端口 PD3, 驱动 PD3 为高电平和低电平 // 输入参数 :ms -- 要延时的毫秒数, 这里假设 CPU 的主频为 2MHZ // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 main() PD_DDR = 0x08; PD_CR1 = 0x08; // 将 PD3 设置成推挽输出 PD_CR2 = 0x00; while(1) PD_ODR = PD_ODR 0x08; // 将 PD3 的输出设置成 1 DelayMS(100); // 延时 100MS PD_ODR = PD_ODR & 0xF7; // 将 PD3 的输出设置成 0 DelayMS(100); // 延时 100MS 需要注意的是, 当生成完框架后, 为了能方便使用 STM8 的寄存器名字, 必须包括 STM8S207C_S. h, 最好将该文件拷贝到 C:\Program Files\STMicroelectronics\st_toolset\include 目录下, 拷贝到工程目录下 或者将该路径填写到该工程的 Settings 中的 C Compiler 选项 Preprocessor 的 Additional i nclude 中, 这样编译时才会找到该文件 STM8 的 C 语言编程 (4) GPIO 输出和输入 今天要进行的实验, 是利用 GPIO 进行输入和输出 在 ST 的三合一开发板上, 按键接在 GPIO 的 PD 7 上,LED 接在 GPIO 的 PD3 上, 因此我们要将 GPIO 的 PD7 初始化成输入,PD3 初始化成输出 关于 GPIO 的引脚设置, 主要是要初始化方向寄存器 DDR, 控制寄存器 1(CR1) 和控制寄存器 2(C R2), 寄存器的每一位对应 GPIO 的每一个引脚 具体的设置功能定义如下 : DDR CR1 CR2 引脚设置 0 0 0 悬浮输入

0 0 1 上拉输入 0 1 0 中断悬浮输入 0 1 1 中断上拉输入 1 0 0 开漏输出 1 1 0 推挽输出 1 X 1 输出 ( 最快速度为 10MHZ) 另外, 输出引脚对应的寄存器为 ODR, 输入引脚对应的寄存器为 IDR 下面的程序是检测按键的状态, 当按键按下时, 点亮 LED, 当按键抬起时, 熄灭 LED 同样也是利用 ST 的开发工具, 先生成一个 C 语言程序的框架, 然后修改其中的 main.c, 修改后的代 码如下 编译通过后, 下载到开发板, 运行程序, 按下按键,LED 就点亮, 抬起按键,LED 就熄灭了 另外, 要注意, 将 STM8S207C_S.h 拷贝到当前项目的目录下 // 程序描述 : 检测开发板上的按键, 若按下, 则点亮 LED, 若抬起, 则熄灭 LED // 按键接在 MCU 的 GPIO 的 PD7 上 // LED 接在 MCU 的 GPIO 的 PD3 上 #include "STM8S207C_S.h" main() PD_DDR = 0x08; PD_CR1 = 0x08; // 将 PD3 设置成推挽输出 PD_CR2 = 0x00; while(1) // 进入无限循环 if((pd_idr & 0x80) == 0x80) // 读入 PD7 的引脚信号 PD_ODR = PD_ODR & 0xF7; // 如果 PD7 为 1, 则将 PD3 的输出设置成 0, 熄灭 LED else PD_ODR = PD_ODR 0x08; // 否则, 将 PD3 的输出设置成 1, 点亮 LED

STM8 的 C 语言编程 (5)--8 位定时器应用之一 在 STM8 单片机中, 有多种定时器资源, 既有 8 位的定时器, 也有普通的 16 位定时器, 还有高级的定时器 今天的实验是用最简单的 8 位定时器 TIM4 来进行延时, 然后驱动 LED 闪烁 为了简单起见, 这里是通过程序查询定时器是否产生更新事件, 来判断定时器的延时是否结束 同样还是利用 ST 的开发工具, 生成一个 C 程序的框架, 然后修改其中的 main.c, 修改后的代码如下 编译通过后, 下载到开发板, 运行程序, 可以看到 LED 在闪烁, 或者用示波器可以在 LED 引脚上看到方波 在这里要特别提醒的是, 从 ST 给的手册上看, 这个定时器中的计数器是一个加 1 计数器, 但本人在实验过程中感觉不太对, 经过反复的实验, 我认为应该是一个减 1 计数器 ( 也许是我拿的手册不对, 或许是理解上有误 ) 例如, 当给定时器中的自动装载寄存器装入 255 时, 产生的方波频率最小, 就象下面代码中计算的那样, 产生的方波频率为 30HZ 左右 若初始化时给自动装载寄存器装入 1, 则产生的方波频率最大, 大约为 3.9K 左右 也就是说实际的分频数为 ARR 寄存器的值 +1 // 程序描述 : 通过初始化定时器 4, 进行延时, 驱动 LED 闪烁 // LED 接在 MCU 的 GPIO 的 PD3 上 #include "STM8S207C_S.h" main() // 首先初始化 GPIO PD_DDR = 0x08; PD_CR1 = 0x08; // 将 PD3 设置成推挽输出 PD_CR2 = 0x00; // 然后初始化定时器 4 TIM4_IER = 0x00; TIM4_EGR = 0x01; TIM4_PSCR = 0x07; TIM4_ARR = 255; TIM4_CNTR = 255; TIM4_CR1 = 0x01; // 禁止中断 // 允许产生更新事件 // 计数器时钟 = 主时钟 /128=2MHZ/128 // 相当于计数器周期为 64uS // 设定重装载时的寄存器值,255 是最大值 // 设定计数器的初值 // 定时周期 =(ARR+1)*64=16320uS // b0 = 1, 允许计数器工作 // b1 = 0, 允许更新 // 设置控制器, 启动定时器 while(1) // 进入无限循环

while((tim4_sr1 & 0x81) == 0x00); // 等待更新标志 TIM4_SR1 = 0x00; // 清除更新标志 PD_ODR = PD_ODR ^ 0x08; // LED 驱动信号取反 // LED 闪烁频率 =2MHZ/128/255/2=30.63 STM8 的 C 语言编程 (6)--8 位定时器应用之二 今天进行的实验依然是用定时器 4, 只不过改成了用中断方式来实现, 由定时器 4 的中断服务程序来驱动 LED 的闪烁 实现中断方式的关键点有几个, 第一个关键点就是要打开定时器 4 的中断允许位, 在定时器 4 的 IER 寄存器中有定义 第二个关键点, 就是打开 CPU 的全局中断允许位, 在汇编语言中, 就是执行 RIM 指令, 在 C 语言中, 用下列语句实现 : _asm("rim"); 第 3 个关键点就是中断服务程序的框架或写法, 中断服务程序的写法如下 : @far @interrupt void TIM4_UPD_OVF_IRQHandler (void) // 下面是中断服务程序的实体 第 4 个关键点就是要设置中断向量, 即将中断服务程序的入口填写到中断向量表中, 如下所示, 将 IR Q23 对应的中断服务程序的入口填写成 TIM4_UPD_OVF_IRQHandler struct interrupt_vector const _vectab[] = 0x82, (interrupt_handler_t)_stext, /* reset */ 0x82, NonHandledInterrupt, /* trap */ 0x82, NonHandledInterrupt, /* irq0 */ 0x82, NonHandledInterrupt, /* irq1 */ 0x82, NonHandledInterrupt, /* irq2 */ 0x82, NonHandledInterrupt, /* irq3 */ 0x82, NonHandledInterrupt, /* irq4 */ 0x82, NonHandledInterrupt, /* irq5 */ 0x82, NonHandledInterrupt, /* irq6 */ 0x82, NonHandledInterrupt, /* irq7 */ 0x82, NonHandledInterrupt, /* irq8 */ 0x82, NonHandledInterrupt, /* irq9 */ 0x82, NonHandledInterrupt, /* irq10 */ 0x82, NonHandledInterrupt, /* irq11 */

0x82, NonHandledInterrupt, /* irq12 */ 0x82, NonHandledInterrupt, /* irq13 */ 0x82, NonHandledInterrupt, /* irq14 */ 0x82, NonHandledInterrupt, /* irq15 */ 0x82, NonHandledInterrupt, /* irq16 */ 0x82, NonHandledInterrupt, /* irq17 */ 0x82, NonHandledInterrupt, /* irq18 */ 0x82, NonHandledInterrupt, /* irq19 */ 0x82, NonHandledInterrupt, /* irq20 */ 0x82, NonHandledInterrupt, /* irq21 */ 0x82, NonHandledInterrupt, /* irq22 */ 0x82, TIM4_UPD_OVF_IRQHandler,/* irq23 */ 0x82, NonHandledInterrupt, /* irq24 */ 0x82, NonHandledInterrupt, /* irq25 */ 0x82, NonHandledInterrupt, /* irq26 */ 0x82, NonHandledInterrupt, /* irq27 */ 0x82, NonHandledInterrupt, /* irq28 */ 0x82, NonHandledInterrupt, /* irq29 */ ; 解决了以上 4 个关键点, 我们就能很轻松地用 C 语言实现中断服务了 同样还是利用 ST 的开发工具, 生成一个 C 程序的框架, 然后修改其中的 main.c, 修改后的代码如下 另外还要修改 stm8_interrupt_vector.c 编译通过后, 下载到开发板, 运行程序, 可以看到 LED 在闪烁, 或者用示波器可以在 LED 引脚上看到方波 修改后的 main.c 如下 : // 程序描述 : 通过初始化定时器 4, 以中断方式驱动 LED 闪烁 // LED 接在 MCU 的 GPIO 的 PD3 上 #include "STM8S207C_S.h" main() // 首先初始化 GPIO PD_DDR = 0x08; PD_CR1 = 0x08; // 将 PD3 设置成推挽输出 PD_CR2 = 0x00; // 然后初始化定时器 4 TIM4_IER = 0x00; // 禁止中断

TIM4_EGR = 0x01; TIM4_PSCR = 0x07; TIM4_ARR = 255; TIM4_CNTR = 255; TIM4_CR1 = 0x01; TIM4_IER = 0x01; _asm("rim"); // 允许产生更新事件 // 计数器时钟 = 主时钟 /128=2MHZ/128 // 相当于计数器周期为 64uS // 设定重装载时的寄存器值,255 是最大值 // 设定计数器的初值 // 定时周期 =(ARR+1)*64=16320uS // b0 = 1, 允许计数器工作 // b1 = 0, 允许更新 // 设置控制器, 启动定时器 // 允许更新中断 // 允许 CPU 全局中断 while(1) // 进入无限循环 // 函数功能 : 定时器 4 的更新中断服务程序 // 输入参数 : 无 // 输出参数 : 无 // 返回值 : 无 @far @interrupt void TIM4_UPD_OVF_IRQHandler (void) TIM4_SR1 = 0x00; // 清除更新标志 PD_ODR = PD_ODR ^ 0x08; // LED 驱动信号取反 //LED 闪烁频率 =2MHZ/128/255/2=30.63 修改后的 stm8_interrupt_vector.c 如下 : /* BASIC INTERRUPT VECTOR TABLE FOR STM8 devices * Copyright (c) 2007 STMicroelectronics */ typedef void @far (*interrupt_handler_t)(void); struct interrupt_vector unsigned char interrupt_instruction; interrupt_handler_t interrupt_handler; ;

@far @interrupt void NonHandledInterrupt (void) /* in order to detect unexpected events during development, it is recommended to set a breakpoint on the following instruction */ return; extern void _stext(); /* startup routine */ extern @far @interrupt void TIM4_UPD_OVF_IRQHandler (void); struct interrupt_vector const _vectab[] = 0x82, (interrupt_handler_t)_stext, /* reset */ 0x82, NonHandledInterrupt, /* trap */ 0x82, NonHandledInterrupt, /* irq0 */ 0x82, NonHandledInterrupt, /* irq1 */ 0x82, NonHandledInterrupt, /* irq2 */ 0x82, NonHandledInterrupt, /* irq3 */ 0x82, NonHandledInterrupt, /* irq4 */ 0x82, NonHandledInterrupt, /* irq5 */ 0x82, NonHandledInterrupt, /* irq6 */ 0x82, NonHandledInterrupt, /* irq7 */ 0x82, NonHandledInterrupt, /* irq8 */ 0x82, NonHandledInterrupt, /* irq9 */ 0x82, NonHandledInterrupt, /* irq10 */ 0x82, NonHandledInterrupt, /* irq11 */ 0x82, NonHandledInterrupt, /* irq12 */ 0x82, NonHandledInterrupt, /* irq13 */ 0x82, NonHandledInterrupt, /* irq14 */ 0x82, NonHandledInterrupt, /* irq15 */ 0x82, NonHandledInterrupt, /* irq16 */ 0x82, NonHandledInterrupt, /* irq17 */ 0x82, NonHandledInterrupt, /* irq18 */ 0x82, NonHandledInterrupt, /* irq19 */ 0x82, NonHandledInterrupt, /* irq20 */ 0x82, NonHandledInterrupt, /* irq21 */ 0x82, NonHandledInterrupt, /* irq22 */

; 0x82, TIM4_UPD_OVF_IRQHandler,/* irq23 */ 0x82, NonHandledInterrupt, /* irq24 */ 0x82, NonHandledInterrupt, /* irq25 */ 0x82, NonHandledInterrupt, /* irq26 */ 0x82, NonHandledInterrupt, /* irq27 */ 0x82, NonHandledInterrupt, /* irq28 */ 0x82, NonHandledInterrupt, /* irq29 */ STM8 的 C 语言编程 (7)--16 位定时器的中断应用 在 STM8 中, 除了有 8 位的定时器外, 还有 16 位的定时器 今天进行的实验就是针对 16 位定时器 2 来进行的 除了计数单元为 16 位的, 其它设置与前面 8 位的定时器基本一样 下面的程序也是采样中断方式, 由定时器 2 的中断服务程序来驱动 LED 的闪烁 具体的程序代码如下, 其它注意点见上一篇, 另外要注意别忘了修改相应的中断向量 // 程序描述 : 通过初始化定时器 2, 以中断方式驱动 LED 闪烁 // LED 接在 MCU 的 GPIO 的 PD3 上 #include "STM8S207C_S.h" main() // 首先初始化 GPIO PD_DDR = 0x08; PD_CR1 = 0x08; // 将 PD3 设置成推挽输出 PD_CR2 = 0x00; // 然后初始化定时器 4 TIM2_IER = 0x00; TIM2_EGR = 0x01; TIM2_PSCR = 0x01; TIM2_ARRH = 0xEA; TIM2_ARRL = 0x60; // 禁止中断 // 允许产生更新事件 // 计数器时钟 = 主时钟 /128=2MHZ/2 // 相当于计数器周期为 1uS // 设定重装载时的寄存器值 // 注意必须保证先写入高 8 位, 再写入低 8 位 // 设定重装载时的寄存器的高 8 位 TIM2_CNTRH = 0xEA; // 设定计数器的初值

TIM2_CNTRL = 0x60; TIM2_CR1 = 0x01; TIM2_IER = 0x01; _asm("rim"); // 定时周期 =1*60000=60000uS=60ms // b0 = 1, 允许计数器工作 // b1 = 0, 允许更新 // 设置控制器, 启动定时器 // 允许更新中断 // 允许 CPU 全局中断 while(1) // 进入无限循环 // 函数功能 : 定时器 4 的更新中断服务程序 // 输入参数 : 无 // 输出参数 : 无 // 返回值 : 无 @far @interrupt void TIM2_UPD_IRQHandler (void) TIM2_SR1 = 0x00; // 清除更新标志 PD_ODR = PD_ODR ^ 0x08; // LED 驱动信号取反 //LED 闪烁频率 =2MHZ/2/60000/2=8.3 STM8 的 C 语言编程 (8)-- UART 应用 串口通讯也是单片机应用中经常要用到, 今天的实验就是利用 STM8 的 UART 资源, 来进行串口通讯的实验 实验程序的功能是以中断方式接收串口数据, 然后将接收到的数据以查询方式发送到串口 程序代码如下, 首先要对 STM8 的 UART 进行初始化, 初始化时要注意的是波特率寄存器的设置, 当求出一个波特率的分频系数 ( 一个 16 位的数 ) 后, 要将高 4 位和低 4 位写到 BRR2 中, 而将中间的 8 位写到 BR R1 中, 并且必须是先写 BRR2, 再写 BRR1 同样也是利用 ST 的开发工具, 生成一个 C 语言的框架, 然后修改其中的 main.c, 同时由于需要用到中断服务, 因此还要修改 stm8_interrupt_vector.c 修改后, 编译连接, 然后下载到开发板上, 再做一根与 PC 机相连的线, 把开发板的串口与 PC 机的串口连接起来, 注意,2 3 脚要交叉 在 PC 机上运行超级终端, 设置波特率为 9600, 然后每按下一个按键, 屏幕上就显示对应的字符 修改后的 main.c 和 stm8_interrupt_vector.c 如下 : // 程序描述 : 初始化 UART, 以中断方式接收字符, 以查询方式发送

// UART 通讯参数 :9600bps,8 位数据,1 位停止位, 无校验 #include "STM8S207C_S.h" // 函数功能 : 初始化 UART // 输入参数 : 无 // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void UART3_Init(void) LINUART_CR2 = 0; // 禁止 UART 发送和接收 LINUART_CR1 = 0; // b5 = 0, 允许 UART // b2 = 0, 禁止校验 LINUART_CR3 = 0; // b5,b4 = 00,1 个停止位 // 设置波特率, 必须注意以下几点 : // (1) 必须先写 BRR2 // (2) BRR1 存放的是分频系数的第 11 位到第 4 位, // (3) BRR2 存放的是分频系数的第 15 位到第 12 位, 和第 3 位到第 0 位 // 例如对于波特率位 9600 时, 分频系数 =2000000/9600=208 // 对应的十六进制数为 00D0,BBR1=0D,BBR2=00 LINUART_BRR2 = 0; LINUART_BRR1 = 0x0d; // 实际的波特率分频系数为 00D0(208) // 对应的波特率为 2000000/208=9600 LINUART_CR2 = 0x2C; // b3 = 1, 允许发送 // b2 = 1, 允许接收 // b5 = 1, 允许产生接收中断 // 函数功能 : 从 UART3 发送一个字符 // 输入参数 :ch -- 要发送的字符 // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void UART3_SendChar(unsigned char ch)

while((linuart_sr & 0x80) == 0x00); // 若发送寄存器不空, 则等待 LINUART_DR = ch; // 将要发送的字符送到数据寄存器 main() // 首先初始化 UART3 UART3_Init(); _asm("rim"); // 允许 CPU 全局中断 while(1) // 进入无限循环 // 函数功能 :UART3 的接收中断服务程序 // 输入参数 : 无 // 输出参数 : 无 // 返回值 : 无 @far @interrupt void UART3_Recv_IRQHandler (void) unsigned char ch; ch = LINUART_DR; UART3_SendChar(ch); // 读入接收到的字符 // 将字符发送出去 /* BASIC INTERRUPT VECTOR TABLE FOR STM8 devices * Copyright (c) 2007 STMicroelectronics */ typedef void @far (*interrupt_handler_t)(void); struct interrupt_vector unsigned char interrupt_instruction; interrupt_handler_t interrupt_handler;

; @far @interrupt void NonHandledInterrupt (void) /* in order to detect unexpected events during development, it is recommended to set a breakpoint on the following instruction */ return; extern void _stext(); /* startup routine */ extern @far @interrupt void UART3_Recv_IRQHandler(); struct interrupt_vector const _vectab[] = 0x82, (interrupt_handler_t)_stext, /* reset */ 0x82, NonHandledInterrupt, /* trap */ 0x82, NonHandledInterrupt, /* irq0 */ 0x82, NonHandledInterrupt, /* irq1 */ 0x82, NonHandledInterrupt, /* irq2 */ 0x82, NonHandledInterrupt, /* irq3 */ 0x82, NonHandledInterrupt, /* irq4 */ 0x82, NonHandledInterrupt, /* irq5 */ 0x82, NonHandledInterrupt, /* irq6 */ 0x82, NonHandledInterrupt, /* irq7 */ 0x82, NonHandledInterrupt, /* irq8 */ 0x82, NonHandledInterrupt, /* irq9 */ 0x82, NonHandledInterrupt, /* irq10 */ 0x82, NonHandledInterrupt, /* irq11 */ 0x82, NonHandledInterrupt, /* irq12 */ 0x82, NonHandledInterrupt, /* irq13 */ 0x82, NonHandledInterrupt, /* irq14 */ 0x82, NonHandledInterrupt, /* irq15 */ 0x82, NonHandledInterrupt, /* irq16 */ 0x82, NonHandledInterrupt, /* irq17 */ 0x82, NonHandledInterrupt, /* irq18 */ 0x82, NonHandledInterrupt, /* irq19 */ 0x82, NonHandledInterrupt, /* irq20 */ 0x82, UART3_Recv_IRQHandler, /* irq21 */

; 0x82, NonHandledInterrupt, /* irq22 */ 0x82, NonHandledInterrupt, /* irq23 */ 0x82, NonHandledInterrupt, /* irq24 */ 0x82, NonHandledInterrupt, /* irq25 */ 0x82, NonHandledInterrupt, /* irq26 */ 0x82, NonHandledInterrupt, /* irq27 */ 0x82, NonHandledInterrupt, /* irq28 */ 0x82, NonHandledInterrupt, /* irq29 */ STM8 与汇编语言 (9)--EEPROM 应用 EEPROM 是单片机应用系统中经常会用到的存储器, 它主要用来保存一些掉电后需要保持不变的数据 在以前的单片机系统中, 通常都是在单片机外面再扩充一个 EEPROM 芯片, 这种方法除了增加成本外, 也降低了可靠性 现在, 很多单片机的公司都推出了集成有小容量 EEPROM 的单片机, 这样就方便了使用, 降低了成本, 提高了可靠性 STM8 单片机芯片内部也集成有 EEPROM, 容量从 640 字节到 2K 字节 最为特色的是, 在 STM8 单片机中, 对 EEPROM 的访问就象常规的 RAM 一样, 非常方便 EEPROM 的地址空间与内存是统一编址的, 地址从 004000H 开始, 大小根据不同的芯片型号而定 下面的实验程序, 就是先给 EEPROM 中的第一个单元 004000H 写入 55H, 然后再读到全局变量 ch 中 同样还是利用 ST 的开发工具, 生成一个 C 语言程序的框架, 然后修改其中的 main.c, 修改后的代码如下 // 程序描述 : 对芯片内部的 EEPROM 存储单元进行实验 #include "STM8S207C_S.h" unsigned char ch; main() unsigned char *p; p = (unsigned char *)0x4000; // 指针 p 指向芯片内部的 EEPROM 第一个单元 // 对数据 EEPROM 进行解锁 do FLASH_DUKR = 0xae; // 写入第一个密钥 FLASH_DUKR = 0x56; // 写入第二个密钥 while((flash_iapsr & 0x08) == 0); // 若解锁未成功, 则重新再来

*p = 0xaa; // 写入第一个字节 while((flash_iapsr & 0x04) == 0); // 等待写操作成功 ch = *p; // 将写入的内容读到变量 ch 中 while(1) ; 这里要注意的是,2 个密钥的顺序, 与 STM8 的用户手册上是相反的, 如果按照手册上的顺序, 就会停留在 do while 循环中 具体原因, 也不是很清楚, 也可能是我拿到的手册 ( 中文和英文的都一样 ) 太旧了, 或者是理解有误 另外, 上面的实验程序中,ch 不能为局部变量, 否则的话, 在调试环境中跟踪 ch 变量时, 显示的结果就不对, 通过反汇编, 我觉得是编译有问题, 当定义成局部变量时,ch = *p 的汇编代码如下 : main.c:23 ch = *p; // 将写入的内容读到变量 ch 中 0x80f0 <main+34> 0x7B01 LD A,(0x01,SP) LD A,(0x01,SP) 0x80f2 <main+36> 0x97 LD XL,A LD XL,A 0x80f3 <main+37> 0x1E02 LDW X,(0x02,SP) LDW X,(0x02,SP) 0x80f5 <main+39> 0xF6 LD A,(X) LD A,(X) 0x80f6 <main+40> 0x97 LD XL,A LD XL,A 如果将 ch 定义成全局变量, 则汇编代码为 : main.c:22 ch = *p; // 将写入的内容读到变量 ch 中 0x80ef <main+33> 0x1E01 LDW X,(0x01,SP) LDW X,(0x01,SP) 0x80f1 <main+35> 0xF6 LD A,(X) LD A,(X) 0x80f2 <main+36> 0xB700 LD 0x00,A LD 0x00,A 这一段代码的分析仅供参考, 本人使用的开发环境为 STVD4.1.0, 编译器版本号为 :COSMIC 的 Cx STM84.2.4 STM8 的 C 语言编程 (10)-- 修改 CPU 的时钟 在有些单片机的应用系统中, 并不需要 CPU 运行在多高的频率 在低频率下运行, 芯片的功耗会大大下降 STM8 单片机在运行过程中, 可以随时修改 CPU 运行时钟频率, 非常方便 实现这一功能, 主要涉及到时钟分频寄存器 (CLK_CKDIVR) 时钟分频寄存器是一个 8 位的寄存器, 高 3 位保留, 位 4 和位 3 用于定义高速内部时钟的预分频, 而位 2 到位 0 则用于 CPU 时钟的分频 这 5 位的详细定义如下 :

位 4 位 3 高速内部时钟的分频系数 0 0 1 0 1 2 1 0 4 1 1 8 位 2 位 1 位 0 CPU 时钟的分频系数 0 0 0 1 0 0 1 2 0 1 0 4 0 1 1 8 1 0 0 16 1 0 1 32 1 1 0 64 1 1 1 128 假设我们使用内部的高速 RC 振荡器, 其频率为 16MHZ, 当位 4 为 0, 位 3 为 1 时, 则内部高速时钟的分频系数为 2, 因此输出的主时钟为 8MHZ 当位 2 为 0, 位 1 为 1, 位 0 为 0 时,CPU 时钟的分频系数为 4, 即 CPU 时钟 = 主时钟 /4=2MHZ 下面的实验程序首先将 CPU 的运行时钟设置在 8MHZ, 然后快速闪烁 LED 指示灯 接着, 通过修改主时钟的分频系数和 CPU 时钟的分频系数, 将 CPU 时钟频率设置在 500KHZ, 然后再慢速闪烁 LED 指示灯 通过观察 LED 指示灯的闪烁频率, 可以看到, 同样的循环代码, 由于 CPU 时钟频率的改变, 闪烁频率和时间长短都发生了变化 同样还是利用 ST 的开发工具, 生成一个 C 语言程序的框架, 然后修改其中的 main.c, 修改后的代码如下 修改后的代码编译连接后, 就可以下载到开发板上, 运行后会看到 LED 的闪烁频率有明显的变化 // 程序描述 : 通过修改 CPU 时钟的分频系数, 来改变 CPU 的运行速度 #include "STM8S207C_S.h" // 函数功能 : 延时函数 // 输入参数 :ms -- 要延时的毫秒数, 这里假设 CPU 的主频为 2MHZ // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void DelayMS(unsigned int ms) unsigned char i;

while(ms!= 0) for(i=0;i<250;i++) for(i=0;i<75;i++) ms--; main() int i; PD_DDR = 0x08; PD_CR1 = 0x08; PD_CR2 = 0x00; // 将 PD3 设置成推挽输出 CLK_SWR = 0xE1; // 选择芯片内部的 16MHZ 的 RC 振荡器为主时钟 for(;;) // 进入无限循环 // 下面设置 CPU 时钟分频器, 使得 CPU 时钟 = 主时钟 // 通过发光二极管, 可以看出, 程序运行的速度确实明显提高了 CLK_CKDIVR = 0x08; // 主时钟 = 16MHZ / 2 // CPU 时钟 = 主时钟 = 8MHZ for(i=0;i<10;i++) PD_ODR = 0x08; DelayMS(100); PD_ODR = 0x00; DelayMS(100); // 下面设置 CPU 时钟分频器, 使得 CPU 时钟 = 主时钟 /4 // 通过发光二极管, 可以看出, 程序运行的速度确实明显下降了 CLK_CKDIVR = 0x1A; // 主时钟 = 16MHZ / 8 // CPU 时钟 = 主时钟 / 4 = 500KHZ for(i=0;i<10;i++)

PD_ODR = 0x08; DelayMS(100); PD_ODR = 0x00; DelayMS(100); STM8 的 C 语言编程 (11)-- 切换时钟源 STM8 单片机的时钟源非常丰富, 芯片内部既有 16MHZ 的高速 RC 振荡器, 也有 128KHZ 的低速 RC 振荡器, 外部还可以接一个高速的晶体振荡器 在系统运行过程中, 可以根据需要, 自由地切换 单片机复位后, 首先采用的是内部的高速 RC 振荡器, 且分频系数为 8, 因此 CPU 的上电运行的时钟频率为 2MHZ 切换时钟源, 主要涉及到的寄存器有 : 主时钟切换寄存器 CLK_SWR 和切换控制寄存器 CLK_SWCR 主时钟切换寄存器的复位值为 0xe1, 表示切换到内部的高速 RC 振荡器上 当往该寄存器写入 0xb4 时, 表示切换到外部的高速晶体振荡器上 在实际切换过程中, 应该先将切换控制寄存器中的 SWEN( 第 1 位 ) 设置成 1, 然后设置 CLK_SWC R 的值, 最后要判断切换控制寄存器中的 SWIF 标志是否切换成功 下面的实验程序首先将主时钟源切换到外部的晶体振荡器上, 振荡频率为 8MHZ, 然后, 然后快速闪烁 LED 指示灯 接着, 将主时钟源又切换到内部的振荡器上, 振荡频率为 2MHZ, 然后再慢速闪烁 LED 指示灯 通过观察 LED 指示灯的闪烁频率, 可以看到, 同样的循环代码, 由于主时钟源的改变的改变, 闪烁频率和时间长短都发生了变化 同样还是利用 ST 的开发工具, 生成一个 C 语言程序的框架, 然后修改其中的 main.c, 修改后的代码如下 // 程序描述 : 通过切换 CPU 的主时钟源, 来改变 CPU 的运行速度 #include "STM8S207C_S.h" // 函数功能 : 延时函数 // 输入参数 :ms -- 要延时的毫秒数, 这里假设 CPU 的主频为 2MHZ // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void DelayMS(unsigned int ms) unsigned char i; while(ms!= 0)

for(i=0;i<250;i++) for(i=0;i<75;i++) ms--; main() int i; // 将 PD3 设置成推挽输出, 以便推动 LED PD_DDR = 0x08; PD_CR1 = 0x08; PD_CR2 = 0x00; // 启动外部高速晶体振荡器 CLK_ECKR = 0x01; // 允许外部高速振荡器工作 while((clk_eckr & 0x02) == 0x00); // 等待外部高速振荡器准备好 // 注意, 复位后 CPU 的时钟源来自内部的 RC 振荡器 for(;;) // 进入无限循环 // 下面将 CPU 的时钟源切换到外部的高速晶体振荡器上, 在开发板上的频率为 8MHZ // 通过发光二极管, 可以看出, 程序运行的速度确实明显提高了 CLK_SWCR = CLK_SWCR 0x02; // SWEN <- 1 CLK_SWR = 0xB4; // 选择芯片外部的高速振荡器为主时钟 while((clk_swcr & 0x08) == 0); // 等待切换成功 CLK_SWCR = CLK_SWCR & 0xFD; // 清除切换标志 for(i=0;i<10;i++) PD_ODR = 0x08; DelayMS(100); // LED 高速闪烁 10 次

PD_ODR = 0x00; DelayMS(100); // 下面将 CPU 的时钟源切换到内部的 RC 振荡器上, 由于 CLK_CKDIVR 的复位值为 0x18 // 所以 16MHZ 的 RC 振荡器要经过 8 分频后才作为主时钟, 因此频率为 2MHZ // 通过发光二极管, 可以看出, 程序运行的速度确实明显下降了 CLK_SWCR = CLK_SWCR 0x02; // SWEN <- 1 CLK_SWR = 0xE1; // 选择 HSI 为主时钟源 while((clk_swcr & 0x08) == 0); // 等待切换成功 CLK_SWCR = CLK_SWCR & 0xFD; // 清除切换标志 for(i=0;i<10;i++) PD_ODR = 0x08; DelayMS(100); PD_ODR = 0x00; DelayMS(100); // LED 低速闪烁 10 次 STM8 的 C 语言编程 (12)-- AD 转换 在许多的单片机应用系统中, 都需要 A/D 转换器, 将模拟量转换成数字量 在 STM8 单片机中, 提供的是 10 位的 A/D, 通道数随芯片不同而不同, 少的有 4 个通道, 多的则有 16 个通道 下面的实验程序首先对 A/D 输入进行采样, 然后将采样结果的高 8 位 ( 丢弃最低的 2 位 ), 作为延时参数去调用延时子程序, 然后再去驱动 LED 控制信号 因此不同的采样值, 决定了 LED 的闪烁频率 当旋转 ST 三合一开发板上的电位器时, 可以看到 LED 的闪烁频率发生变化 同样还是利用 ST 的开发工具, 生成一个 C 语言程序的框架, 然后修改其中的 main.c, 修改后的代码如下 // 程序描述 : 通过 AD 模块, 采样电位器的电压, 改变 LED 的闪烁频率 #include "STM8S207C_S.h"

// 函数功能 : 延时函数 // 输入参数 :ms -- 要延时的毫秒数, 这里假设 CPU 的主频为 2MHZ // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void DelayMS(unsigned int ms) unsigned char i; while(ms!= 0) for(i=0;i<250;i++) for(i=0;i<75;i++) ms--; main() int i; // 将 PD3 设置成推挽输出, 以便推动 LED PD_DDR = 0x08; PD_CR1 = 0x08; PD_CR2 = 0x00; // 初始化 A/D 模块 ADC_CR2 = 0x00; // A/D 结果数据左对齐 ADC_CR1 = 0x00; // ADC 时钟 = 主时钟 /2=1MHZ // ADC 转换模式 = 单次 // 禁止 ADC 转换 ADC_CSR = 0x03; // 选择通道 3 ADC_TDRL = 0x20; for(;;) // 进入无限循环

ADC_CR1 = 0x01; // CR1 寄存器的最低位置 1, 使能 ADC 转换 for(i=0;i<100;i++); // 延时一段时间, 至少 7uS, 保证 ADC 模块的上电完成 ADC_CR1 = ADC_CR1 0x01; // 再次将 CR1 寄存器的最低位置 1 // 使能 ADC 转换 while((adc_csr & 0x80) == 0); // 等待 ADC 结束 i = ADC_DRH; DelayMS(i); // 读出 ADC 结果的高 8 位 // 延时一段时间 PD_ODR = PD_ODR ^ 0x08; // 将 PD3 反相 STM8 的 C 语言编程 (13)-- 蜂鸣器 蜂鸣器是现在单片机应用系统中很常见的, 常用于实现报警功能 为此 STM8 特别集成了蜂鸣器模块, 应用起来非常方便 在应用蜂鸣器模块时, 首先要打开片内的低速 RC 振荡器 ( 当然也能使用外部的高速时钟 ), 其频率为 128KHZ 然后通过设置蜂鸣器控制寄存器 BEEP_CSR 中的 BEEPDIV[4:0] 来获取 8KHZ 的时钟, 再通过 BEEPSEL 最终产生 1KHZ 或 2KHZ 或 4KHZ 的蜂鸣器时钟, 最后使能该寄存器中的 BEEPEN 位, 产生蜂鸣器的输出 下面的实验程序首先初始化低速振荡器, 然后启动蜂鸣器, 再延时 2.5 秒, 然后关闭蜂鸣器 同样还是利用 ST 的开发工具, 生成一个汇编程序的框架, 然后修改其中的 main.c, 修改后的代码如下 // 程序描述 : 启动单片机中的蜂鸣器模块 #include "STM8S207C_S.h" // 函数功能 : 延时函数 // 输入参数 :ms -- 要延时的毫秒数, 这里假设 CPU 的主频为 2MHZ // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void DelayMS(unsigned int ms) unsigned char i; while(ms!= 0) for(i=0;i<250;i++)

for(i=0;i<75;i++) ms--; main() int i; CLK_ICKR = CLK_ICKR 0x08; while((clk_ickr & 0x10) == 0); // 打开芯片内部的低速振荡器 LSI // 等待振荡器稳定 // 通过设置蜂鸣器控制寄存器, 来打开蜂鸣器的功能 // 蜂鸣器控制寄存器的设置 : // BEEPDIV[1:0] = 00 // BEEPDIV[4:0] = 0e // BEEPEN = 1 // 蜂鸣器的输出频率 = Fls / ( 8 * (BEEPDIV + 2) )= 128K / (8 * 16) = 1K BEEP_CSR = 0x2e; for(i=0;i<10;i++) DelayMS(250); BEEP_CSR = 0x1e; while(1); // 关闭蜂鸣器 STM8 的 C 语言编程 (14)-- PWM 在单片机应用系统中, 也常常会用到 PWM 信号输出, 例如电机转速的控制 现在很多高档的单片机也都集成了 PWM 功能模块, 方便用户的应用

对于 PWM 信号, 主要涉及到两个概念, 一个就是 PWM 信号的周期或频率, 另一个就是 PWM 信号的占空比 例如一个频率为 1KHZ, 占空比为 30%, 有效信号为 1 的 PWM 信号, 在用示波器测量时, 就是高电平的时间为 300uS, 低电平的时间为 700uS 的周期波形 在单片机中实现 PWM 信号的功能模块, 实际上就是带比较器的计数器模块 首先该计数器循环计数, 例如从 0 到 N, 那么这个 N 就决定了 PWM 的周期,PWM 周期 =(N+1)* 计数器时钟的周期 在计数器模块中一定还有一个比较器, 比较器有 2 个输入, 一个就是计数器的当前值, 另一个是可以设置的数, 这个数来自一个比较寄存器 当计数器的值小于比较寄存器的值时, 输出为 1( 可以设置为 0), 当计数器的值大于或等于比较寄存器的值时, 输出为 0( 也可设置为 1, 与前面对应 ) 了解了这个基本原理后, 我们就可以使用 STM8 单片机中的 PWM 模块了 下面的实验程序首先将定时器 2 的通道 2 设置成 PWM 输出方式, 然后通过设置自动装载寄存器 TIM2_CCR2, 决定 PWM 信号的周期 在程序的主循环中, 循环修改占空比, 先是从 0 逐渐递增到 128, 然后再从 128 递减到 0 当把下面的程序在 ST 的三合一板上运行时, 可以看到发光二极管 LD1 逐渐变亮, 然后又逐渐变暗, 就这样循环往复 如果用示波器看, 可以看到驱动 LD1 的信号波形的占空比从 0 变到 50%, 然后又从 50% 变到 0 同样还是利用 ST 的开发工具, 生成一个 C 语言程序的框架, 然后修改其中的 main.c, 修改后的代码如下 // 程序描述 : 用 PWM 输出驱动 LED #include "STM8S207C_S.h" void CLK_Init(void); void TIM_Init(void); // 函数功能 : 延时函数 // 输入参数 :ms -- 要延时的毫秒数, 这里假设 CPU 的主频为 2MHZ // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void DelayMS(unsigned int ms) unsigned char i; while(ms!= 0) for(i=0;i<250;i++) for(i=0;i<75;i++)

ms--; // 函数功能 : 初始化时钟 // 输入参数 : 无 // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void CLK_Init() CLK_CKDIVR = 0x11; // 10: fhsi = fhsi RC output/ 4 // = 16MHZ / 4 =4MHZ // 001: fcpu=fmaster/2. = 2MHZ // 函数功能 : 初始化定时器 2 的通道 2, 用于控制 LED 的亮度 // 输入参数 : 无 // 输出参数 : 无 // 返回值 : 无 // 备注 : 无 void TIM_Init() TIM2_CCMR2 = TIM2_CCMR2 0x70;// Output mode PWM2. // 通道 2 被设置成比较输出方式 // OC2M = 111, 为 PWM 模式 2, // 向上计数时, 若计数器小于比较值, 为无效电平 // 即当计数器在 0 到比较值时, 输出为 1, 否则为 0 TIM2_CCER1 = TIM2_CCER1 0x30;// CC polarity low,enable PWM output */ // CC2P = 1, 低电平为有效电平 // CC2E = 1, 开启输出引脚 // 初始化自动装载寄存器, 决定 PWM 方波的频率,Fpwm=4000000/256=15625HZ TIM2_ARRH = 0; TIM2_ARRL = 0xFF; // 初始化比较寄存器, 决定 PWM 方波的占空比 TIM2_CCR2H = 0;

TIM2_CCR2L = 0; // 初始化时钟分频器为 1, 即计数器的时钟频率为 Fmaster=4MHZ TIM2_PSCR = 0; // 启动计数 TIM2_CR1 = TIM2_CR1 0x01; main() unsigned char i; CLK_Init(); TIM_Init(); // 初始化时钟 // 初始化定时器 while(1) // 进入无限循环 // 下面的循环将占空比逐渐从 0 递增到 50% for(i=0;i<128;i++) TIM2_CCR2H = 0; TIM2_CCR2L = i; DelayMS(5); // 下面的循环将占空比逐渐从 50% 递减到 0 for(i=128;i>0;i--) TIM2_CCR2H = 0; TIM2_CCR2L = i; DelayMS(5);