完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
` 本帖最后由 patele 于 2016-3-17 23:07 编辑 Stm8s开发需要准备的材料: 电脑,开发板,stlink 1, 下载安装软件 1.1本教程开发和烧录软件使用stvd + sdvp,请自行下载安装sttoolset***.exe, 安装后有如下2个软件工具(ST Visual Develop用于编译和debug, ST Visual Programmer用于烧写) , 如果你使用的是其他开发工具,如iar,本教程的代码可作参考。 1.2 stm8编译器 stm8 32K cosmic 下载安装(记住安装路径, 我的是C:Program Files (x86)COSMICCXSTM8_32K),Cosmic 公司发布的一个免费版的Cosmic STM8编译器 ,也可直接用于stm8的软件开发,但是一般作为插件提供给stvd。 1.3 本教程debug烧录器使用stlink, 请自行安装 stlink 驱动,本人电脑win7 64bit 版,stlink 驱动是插上stlink后自动安装的,安装成功后在设备管理器中可以看到如下图中标出的一项。 2,创建stm8工程 本人的stm8的具体型号是stm8s103f3p6, 所以后面都默认建立stm8s103f3p6 的工程。 2.1 启动stvd 2.2 file --> New Workspace --> Creat workspace and project --> 确定 2.3 在弹出的对话框中填写workspace名和workspace 存放路径 ---> ok 2.4 在弹出的对话框中填写 project名和路径,然后选择编译工具链,本教程使用 stm8 cosmic,然后填写工具链的路径,即上述1.2提示记住的安装路径,本人填写 C:Program Files (x86)COSMICCXSTM8_32K ---> ok 2.5 在弹出的对话框中的 filter 中输入自己的芯片型号,选择对应芯片,点击 Seclet, 本人使用 stm8s103f3p6 --> ok 2.6 工程已经基本创建完成,如下图(红线标注的分别是workspace 和 project) 双击 source file 展开可以看到在该目录下有2个重要文件main.c 和 stm8_interrupt_vector.c Main.c 中的函数 main() 是应用程序入口, stm8_interrupt_vector.c 中主要是中断向量表 2.7 背景:stvd自动生成的main.c 和 stm8_interrupt_vector.c 更适合一些人所说的寄存器开发,寄存器开发相对库函数开发具有代码更紧凑占用flash少,cpu效率高等优势,而库函数开发的优势则是减轻开发工作,缩短开发周期,而且用库函数开发好程序之后,是可以很方便的修改成寄存器版本的,对于目前的市场来说,缩短开发周期意味着产品更快的推向市场,既可以缩减成本,也可以抢占市场,所以使用库函数开发是最佳选择。 2.7.1 为了方便使用库函数,本人将从ST官方获取的 main.c 和 stm8_interrupt_vector.c 替换掉本工程源码中的对应文件,直接用新的 mian.c 和 stm8_interrupt_vector.c 覆盖原来的即可。 2.7.2 将获取得到 stm8s_conf.h、 stm8s_it.c 和 stm8s_it.h 拷贝到工程源码中。 2.7.3 将获取到的库STM8S_StdPeriph_Driver添加到工程源码中。 进入 STM8S_StdPeriph_Driver 可以看到2个重要文件夹 inc 和src, 其中 inc 中都是***.h头文件,除文件stm8s.h之外的文件都是包含库函数的声明,如 stm8s_adc1.h 是用于 adc1 的所有库函数声明,而stm8s则定义了一些重要的编译信息以及将寄存器地址映射成符号等; Src中则是库函数的实现,每一个xxx.c 文件在inc中都有一个对应名的xxx.h 文件。 2.7.4 在stvd工程新建库函数目录 STM8S_StdPeriph_Driver,用于添加库文件到工程中 在 stvd 的工程中选中工程,然后右键 --> New Folder 在弹出的对话框中输入 STM8S_StdPeriph_Driver -- > ok 2.7.5 编译工程 直接按快捷键 F7 或者点击工具栏图标
(中间红圈的) 或者点击菜单栏 Build --> Build. 即可编译工程。 提示错误: #error opstm8 main.c:24 can’t open stm8s.h 提示很明确:无法打开stm8s.h, 文件stm8s.h 就是前面介绍的库中的inc 中的文件,显然是因为没有添加到stvd工程中。 解决办法:选中 include files --> 右键 --> add files to folder , 找到stm8s.h,添加到工程中, 再编译,出现很多如下类型错误: #error ****************** "Please select first the target STM8S/A device used in your application (in stm8s.h file)" 提示需要在stm8s.h中先选中芯片型号 解决办法:打开stm8s.h, 找到 (因为本人使用的是stm8s103f3p6) /* #define STM8S103*/ /*!< STM8S Low density devices */ 改成 #define STM8S103 /*!< STM8S Low density devices */ 再编译,出现很多如下类型错误: #error clnk Debugcreatproject.lkf:1 symbol *********************** 原因是在 stm8_interrupt_vector.c 定义的中断向量表中引用了很多函数指针,但是编译器找不到该函数。将上述拷贝的 stm8s_it.c 添加到 Source files 中,将 stm8s_it.h 和 stm8s_conf.h 添加到 include files 中。 再编译,通过。 0 error(s), 0 warning(s) 2.7.6 保存 File --> Save Workspace ` |
|
相关推荐
|
|
顶顶
顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 顶 |
|
|
|
|
|
帖子过去一年了,大家以为我弃帖了吧。我又回来挖坟了。我会坚持把当初准备写的内容写完并分享出来的。今天更新2篇,这个周末抽空写的。
|
|
|
|
|
|
第六讲:io口之中断输入 实验目的: 理解中断过程,并学会io中断编程。 功能需求: 通过io口中断方式实现按键按下点亮led,松开熄灭led。 分析原理图,研究实现方案: 如上图:按键的io口对应PB4和PB5,led对应的io口对应PB5。 那么这里采用PB4作为测试的中断io口,对于PB4口来说,S1按下时刻,PB4的状态由上拉到3.3V变为接地,即高电平变成低电平的一个下降沿;而S1松开的状态正好相反。 对于led的控制口PB5来讲,输出高电平则led两端都是高,led不亮;输出低电平则led阳极高电平,阴极低电平,led点亮。 按照以上方案,实现代码如下: 1,main.c 文件 void main(void) { GPIO_DeInit(GPIOB); // 将PB port设置为复位后的缺省状态 GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_IN_PU_IT); // 将PB4设置为上拉输入,外部中断模式 GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_SLOW); // 将PB5设置为推挽输出,初始高电平 EXTI_DeInit(); // 将外部中断设置为复位后的缺省状态 EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOB, EXTI_SENSITIVITY_FALL_ONLY); // 将PB port设置为对下降沿中断敏感 enableInterrupts(); // 使能中断 /* Infinite loop */ while (1) { // if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_4) == RESET) // GPIO_WriteHigh(GPIOB, GPIO_PIN_5); // else // GPIO_WriteLow(GPIOB, GPIO_PIN_5); } } 2,stm8s_it.c 文件 INTERRUPT_HANDLER(EXTI_PORTB_IRQHandler, 4) // PB中断服务函数 { /* In order to detect unexpected events during development, it is recommended to set a breakpoint on the following instruction. */ #if 0 if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_4) == RESET) // 若PB4为低电平 GPIO_WriteHigh(GPIOB, GPIO_PIN_5); // PB5输出高电平,led熄灭 else GPIO_WriteLow(GPIOB, GPIO_PIN_5); #else GPIO_WriteReverse(GPIOB, GPIO_PIN_5); // PB5输出相反电平,若为高则输出低,为低则输出高 #endif } 编译烧录测试,操作按键就可以发现led的状态变化。 程序的执行流程是初始化PB45后使能中断,然后进入while(1)死循环,防止程序执行完毕退出,这时操作按键,满足了PB4的中断条件就会进入中断服务函数执行(控制PB5的输出状态,led的状态就会变化),中断服务函数执行完毕cpu会回到原中断点继续(while死循环)。 小记: 在编写此工程时发现一个编译器的bug:强制转换问题. 按照类型BitStatus的定义: typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus, BitStatus, BitAction; 那么函数 GPIO_ReadInputPin 的返回值只有 RESET / SET 两种结果,一般这么理解是没问题的,但是如果你使用的与我一样的编译器来编译stm8的工程,稍有不慎可能出现意想不到的现象。 现象重现: 把上述工程中的main.c 中的 enableInterrupts(); // 使能中断 注释掉,这样就不会执行 stm8s_it.c 中的中断服务函数了;然后将 while(1)中的四行注释打开。如下: while (1) { if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_4) == SET) GPIO_WriteHigh(GPIOB, GPIO_PIN_5); else GPIO_WriteLow(GPIOB, GPIO_PIN_5); } 按照此循环所要达到的目的:PB4如果是高电平则熄灭led,如果是低电平则点亮led。 那么编译之后烧录到硬件上测试:发现led一直都是点亮状态,无论是否按下按键。 如果将 if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_4) == SET) 修改成 if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_4) == RESET) 再编译测试,发现功能又是正常的。 为什么会这样呢? 再做一个测试,将main.c 修改如下: void main(void) { volatile u8 val; GPIO_DeInit(GPIOB); // 将PB port设置为复位后的缺省状态 GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_IN_PU_IT); // 将PB4设置为上拉输入,外部中断模式 GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_SLOW); // 将PB5设置为推挽输出,初始高电平 EXTI_DeInit(); // 将外部中断设置为复位后的缺省状态 EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOB, EXTI_SENSITIVITY_FALL_ONLY); // 将PB port设置为对下降沿中断敏感 //enableInterrupts(); // 使能中断 val = RESET; val = SET; /* Infinite loop */ while (1) { #if 0 if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_4) == SET) GPIO_WriteHigh(GPIOB, GPIO_PIN_5); else GPIO_WriteLow(GPIOB, GPIO_PIN_5); #else val = GPIO_ReadInputPin(GPIOB, GPIO_PIN_4); val = RESET; // 加这句只是为了添加断点使用 #endif } } 定义一个变量volatile u8 val; 分别用 RESET, SET, 和PB4的状态来给val 赋值。 编译并使用debug调试,将val 加入watch窗口,将断点添加到行 val = SET;执行,程序停在断点处,查看val 的值为0x0,即 RESET 的值为0,符合定义; 然后将断点添加到 val = GPIO_ReadInputPin(GPIOB, GPIO_PIN_4);执行,查看val 的值为0x1,即 SET 的值为1,符合定义; 然后将断点添加到val = RESET; // 加这句只是为了添加断点使用 执行(不按下按键,PB4为上拉输入,GPIO_ReadInputPin读到高电平,应为SET),查看val 的值为0x10, 再查看 GPIO_ReadInputPin 的代码: BitStatus GPIO_ReadInputPin(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef GPIO_Pin) { return ((BitStatus)(GPIOx->IDR & (uint8_t)GPIO_Pin)); } 此函数本该将结果强制转换成 BitStatus 类型再返回,然而返回的值 0x10 既不是 SET(0x1) 也不是 RESET (0x0). 显然这是编译器强制转换出现了bug。 工程代码请到 http://pan.baidu.com/s/1eRuuaEu 下载 |
|
|
|
|
|
第七讲:片内外设之串口 实验目的: 1,学会串口数据发送 2,学会串口数据接收 3,将串口作为调试串口使用 功能需求: 1,通过串口发送一串数据; 2,通过串口接收一串数据; 3,使用c库函数,将串口设为调试串口用于输出调试信息。 分析原理图,研究实现方案: 如上图: 串口TX/RX 对应的脚为PD5和PD6,需要将核心板上的串口对应连接到电脑的串口上,笔者使用的笔记本,没有串口,所以使用了一个u***转串口模块,某宝最便宜的3元左右。需要注意的有2点:1,mcu_TX 接 pc_RX, mcu_RX 接 pc_TX, 即发送端接对方接收端; 2,mcu 与 pc的串口要共地,即GND需要连在一起。 代码实现: 1,通过串口发送一串数据 1.1,发送一个字符’a’, 详情请看代码和注释 void main(void) { // 将uart1 设置成复位后的缺省状态 UART1_DeInit(); // 设置uart1的参数: 115200波特率, 8位宽度, 1位停止位, // 无校验,无流控,TX mode // 115200,8,N, 1 这是串口常用参数,但是一般单片机更常见的波特率是9600 UART1_Init(115200, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_NO, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TX_ENABLE/* UART1_MODE_TXRX_ENABLE*/); // 使能 uart1 UART1_Cmd(ENABLE); // 等待串口发送寄存器为空, // 方法就是不停读取发送寄存器的状态, // 若是RESET 状态就继续读取;若是其他状态就执行下一步 // 注意后面的分号,形同 // while(condition) // { // ; // } while(UART1_GetFlagStatus(UART1_FLAG_TXE) == RESET); // 发送字符 'a' UART1_SendData8('a'); /* Infinite loop */ while (1) { } } 如下图串口调试助手,在连接好硬件按照对应的串口参数配置好串口之后(红框所示),执行程序,在接收显示框中显示接收到字符’a’(箭头所示)。 1.2,修改一下代码,封装函数并按照易读的风格定义函数名,并写好函数注释,使代码更有层次和更容易阅读: /** * 函数功能: 初始化串口uart1,BaudRate, 8, N, 1, TX mode * 输入参数: BaudRate,串口波特率 * 函数返回值: void */ void uart1_init(uint32_t BaudRate) { // 将uart1 设置成复位后的缺省状态 UART1_DeInit(); // 设置uart1的参数: 波特率BaudRate, 8位宽度, 1位停止位, // 无校验,无流控,TX mode UART1_Init(BaudRate, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_NO, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TX_ENABLE/* UART1_MODE_TXRX_ENABLE*/); // 使能 uart1 UART1_Cmd(ENABLE); } /** * 函数功能: 使用串口uart1发送8位数据 * 输入参数: c, 8位有效数据 * 函数返回值: 成功发送的数据 */ uint8_t uart1_sendChar(uint8_t c) { // 等待串口发送寄存器为空 while(UART1_GetFlagStatus(UART1_FLAG_TXE) == RESET); // 发送数据c UART1_SendData8(c); return c; } /* Private defines -----------------------------------------------------------*/ /* Private function prototypes -----------------------------------------------*/ /* Private functions ---------------------------------------------------------*/ void main(void) { // 将串口 uart1 波特率设置为9600 uart1_init(9600); // 发送字符'a' uart1_sendChar('a'); // 发送字符'a' uart1_sendChar(0x61); /* Infinite loop */ while (1) { } } 测试结果: 这里使用 uart1_sendChar(0x61); 来发送字符 ‘a’, 是为了让读者理解ascii表,对于机器,识别的就是0x61,而这不利于人类识别,因此规定了统一的ascii表,然后我们只需要在代码中写上容易识别的’a’,编译器就会帮我们编译成0x61 1.3 发送字符串”abcde” 读者看到这个需求,再回顾1.2,是不是马上想到这样实现即可: uart1_sendChar('a'); uart1_sendChar('b'); uart1_sendChar('c'); uart1_sendChar('d'); uart1_sendChar('e'); 从需求上看,这么做确实达到了。但是这么做有很大的缺陷:1,不利于阅读;2,代码冗余,遇到大工程这么写很可能导致mcu的rom空间不足;3,不利于修改维护。 为了解决上述问题怎么办? 答案是封装一个函数,实现的功能就是传入任意字符串,都通过串口将该字符串发送出去。 具体怎么实现? 需要了解2个基础知识:1,指针;2,字符串的存储方式。 指针就不讲解了,不理解可以查阅相关资料,并仔细研读理解下面的函数 uint8_t * uart1_sendString(uint8_t *pStr)。字符串的存储方式就是字符串数据存储在介质中一块连续的空间,并且以字符’ ’(值为0x0)结束。 因此实现方式就是判断字符是否是’ ’(结束),如果不是就发送,然后取出下一个字符循环处理;如果是就结束退出。 #include "stm8s.h" #define NULL ((void*)0) /** * 函数功能: 初始化串口uart1,BaudRate, 8, N, 1, TX mode * 输入参数: BaudRate,串口波特率 * 函数返回值: void */ void uart1_init(uint32_t BaudRate) { // 将uart1 设置成复位后的缺省状态 UART1_DeInit(); // 设置uart1的参数: 波特率BaudRate, 8位宽度, 1位停止位, // 无校验,无流控,TX mode UART1_Init(BaudRate, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_NO, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TX_ENABLE/* UART1_MODE_TXRX_ENABLE*/); // 使能 uart1 UART1_Cmd(ENABLE); } /** * 函数功能: 使用串口uart1发送8位数据 * 输入参数: c, 8位有效数据 * 函数返回值: 成功发送的数据 */ uint8_t uart1_sendChar(uint8_t c) { // 等待串口发送寄存器为空 while(UART1_GetFlagStatus(UART1_FLAG_TXE) == RESET); // 发送数据c UART1_SendData8(c); return c; } /** * 函数功能: 使用串口uart1发送传入的任意字符串 * 输入参数: pStr, 字符串数据首地址 * 函数返回值: 成功发送的数据 */ uint8_t * uart1_sendString(uint8_t *pStr) { uint8_t *p = pStr; // 判断字符串是否为空 if(pStr == NULL) return NULL; // 取出的字符是否是 ' |