请教:STM8的定时中断处理端口问题

zsda2007   2010-8-25 17:14 楼主
最近STM8遇到比较郁闷的问题,现在有针对方案,但是还是想破头了想不明白。
型号是STM8S105K6:
1、定时中断TIM2溢出时间为1ms,主要用于数码管的显示(直接端口驱动,电流没有超过额定限制),用到端口PC2、PC3、PC4、PC5;
2、输出端口PC1,在控制函数中用到,控制函数在主循环体中。

问题是这样的:
只要我在中断外操作PC1,数码管显示就会偶尔出现毛刺(即位选或段码端口有误操作);将PC1放入中断中,现象消除,已经排除硬件问题和干扰问题。测试了PB端口,同样有这个问题,现在比较头疼,ST的工程师有给出一个信息是,ST采用流水式工艺,端口的输出会有1~2个指令的延时,所以我就猜测是不是这种延时被定时中断打乱,PC寄存器被重新定义时发生了错误?

大家有没有遇到过这种问题啊?

回复评论 (19)

                                 把输出端口PC1和中断中输出端口PC2、PC3、PC4、PC5的操作语句写出来看看。
点赞  2010-8-25 17:32
我的开发环境是IAR,为防止意外,我写了两个版本,
一个是包含iostm8.h,涉及的代码如下:
端口的宏定义:
#define IO_F  PC_ODR_ODR6 //O
#define IO_C  PC_ODR_ODR5 //O
#define IO_G  PC_ODR_ODR4 //O
#define IO_B  PC_ODR_ODR3 //O

#define OUT   PC_ODR_ODR1 //O

端口段码显示驱动:
void IO_Dis(uint8 buf)
{
uint8 dat;
dat=(buf&0xff);
IO_A=!((bit)(dat&0x01));
IO_B=!((bit)(dat&0x02));
IO_C=!((bit)(dat&0x04));
IO_D=!((bit)(dat&0x08));
IO_E=!((bit)(dat&0x10));
IO_F=!((bit)(dat&0x20));
IO_G=!((bit)(dat&0x40));
IO_P=!((bit)(dat&0x80));
}
上面的bit是宏定义了bool;
PC1的操作就是将OUT=1或OUT=0;

第二个是使用了库头文件
端口操作定义
#define PORTC_SET(pin,var)    GPIOC->ODR = (GPIOC->ODR&(~pin))|(var?pin:0)

#define B_A  0x20
#define B_D  0x10
#define B_E  0x08
……

显示驱动为:
void IO_Dis(uint8 buf)
{
uint8 dat;
dat=~(buf&0xff);

PORTB_SET(B_A,(dat&0x01));
PORTC_SET(B_B,(dat&0x02));
PORTC_SET(B_C,(dat&0x04));
PORTB_SET(B_D,(dat&0x08));
PORTB_SET(B_E,(dat&0x10));
PORTC_SET(B_F,(dat&0x20));
PORTC_SET(B_G,(dat&0x40));
}

显示驱动函数void IO_Dis(uint8 buf)是在定时中断中执行的,1ms溢出中断,OUT的操作在主函数中。


点赞  2010-8-25 22:19
                                 请问PC_ODR_ODR6、PC_ODR_ODR5、PC_ODR_ODR5等是怎么定义的?对应怎样的操作?
点赞  2010-8-25 22:38
这个是在IAR自带的iostm8.h中已经定义了的,定义代码如下:
typedef struct
{
  unsigned char ODR0        : 1;
  unsigned char ODR1        : 1;
  unsigned char ODR2        : 1;
  unsigned char ODR3        : 1;
  unsigned char ODR4        : 1;
  unsigned char ODR5        : 1;
  unsigned char ODR6        : 1;
  unsigned char ODR7        : 1;
} __BITS_PC_ODR;

__IO_REG8_BIT(PC_ODR,      0x500A, __READ_WRITE, __BITS_PC_ODR);

#define PC_ODR_ODR0              PC_ODR_bit.ODR0
#define PC_ODR_ODR1              PC_ODR_bit.ODR1
#define PC_ODR_ODR2              PC_ODR_bit.ODR2
#define PC_ODR_ODR3              PC_ODR_bit.ODR3
#define PC_ODR_ODR4              PC_ODR_bit.ODR4
#define PC_ODR_ODR5              PC_ODR_bit.ODR5
#define PC_ODR_ODR6              PC_ODR_bit.ODR6
#define PC_ODR_ODR7              PC_ODR_bit.ODR7

4# 版主
点赞  2010-8-25 22:47


我昨天仿真下看了汇编的操作,循环外的对PC1端口操作的汇编指令

像这种没有对整个端口进行“=”赋值操作的语句,会不会有可能在ADD指令之后,OR指令之前被中断打断,地址0X00又被中断中的程序调用,导致了混乱?

我现在暂时操作,是在处理端口之前将定时中断关闭,在处理完成之后将中断开启,这是无奈的办法。大家有没有好的建议?
点赞  2010-8-26 11:21
实际上,按照3楼和5楼给出的定义,你对I/O口的操作都不是原子操作,即不是独立的赋值操作,而是读-修改-写操作;如果在读-修改-写操作的执行中途被中断,并插入另一个读-修改-写操作,显然被中断的操作结果将会出错。

STM8S的参考手册上,对这种需要原子操作的情况,建议直接使用BSET和BRST指令操作ODR寄存器,这样就可以避免“读-修改-写操作”,从而避免操作被中断的可能。
点赞  2010-8-26 12:28
谢谢版主的提醒,我按照你的做法已经实现了,这也提醒我在其他端口的处理上也采用这种方法,对了,汇编指令的设置位和清除位,PDF上面的指令有一个写错了
正确的是:
asm("BSET 0x500A,#1");//对端口PC1置1
asm("BRES 0x500A,#1");//对端口PC1置0

点赞  2010-8-26 13:15
谢谢版主的提醒,我按照你的做法已经实现了,这也提醒我在其他端口的处理上也采用这种方法,对了,汇编指令的设置位和清除位,PDF上面的指令有一个写错了
正确的是:
asm("BSET 0x500A,#1");//对端口PC1置1
asm("BRE ...
哪个PDF?第几页?第几行?
点赞  2010-8-26 13:34
中文手册的85页,和英文手册的105页,都用BRST指令来端口输出清零,但是在英文版的文件(PM0040)上没有看到BRST指令,
位操作指令都是BRES,在PM0040文件的第70页,有相关说明。
9# 版主
点赞  2010-8-26 13:43
中文手册的85页,和英文手册的105页,都用BRST指令来端口输出清零,但是在英文版的文件(PM0040)上没有看到BRST指令,
位操作指令都是BRES,在PM0040文件的第70页,有相关说明。
9# 版主  ...
谢谢。

不记得哪个文件编号是PM0040,只见过PM0044;即使是PM0044,也不记得谁把它翻译成中文,请上载这个文档到这里让我看看。想问问这个PM0040的名称是什么?
点赞  2010-8-26 14:15
这个文件是英文版的
汇编指令.pdf (3.92 MB)
点赞  2010-8-26 15:23
12楼这个是PM0044(STM8 CPU programming manual),而不是PM0040。

不过在RM0016(STM8S参考手册)中,在GPIO寄存器章节,确实把BRES指令写成了BRST,估计是笔误。
点赞  2010-8-26 15:37


不好意思我也笔误了。
今天的问题我后来又深入的想了一下,
你对I/O口的操作都不是原子操作,即不是独立的赋值操作,而是读-修改-写操作;如果在读-修改-写操作的执行中途被中断,并插入另一个读-修改-写操作,显然被中断的操作结果将会出错
这里我感觉有一个问题,就是为什么中断会把另一个读-修改-写操作赋给原来被中断的程序处?在上面的操作中0X00地址的数据是原程序的修改地址,那是不是意味着中断中也同样处理了0X00地址?
这个和51的寄存器R0、R1……概念类似,如果端口操作会被中断打算,那会不会如果我的一个变量是存放在0X00地址的,中断处理过后变量的值就变了?中断地址方面的资料,版主能否推荐一下,我现在还没有什么概念。

13# 版主
点赞  2010-8-26 16:21
这个很简单,比如你要对PC6置'1'又不想改变其它位,你会先读出GPIOC->ODR,再'或'上0x40,再写回结果;这个过程至少需要3条指令:
1.  LD A, 0x500A   ; 读出PC_ODR
2.   OR A, #0x40
3.   LD 0x500A, A   ; 写回PC_ODR

如果在执行完上述第1条或第2条指令后,执行第3条指令前发生中断,同时在中断中有对PC5的操作,比如中断前PC5=0,在中断中把PC5改为'1',当中断结束后,上述第3条指令又会把PC5改回'0',这不是就错了吗?!
点赞  2010-8-26 16:59
恩,理解了,看的挺直观的。其实就是最后那么一下,把之前保护现场的堆栈里面的值又全部取出来,对PC端口进行了原始的“正确”操作,反不知把中断处理PC的“功劳”给抹杀了。明白了,非常感谢!
15# 版主
点赞  2010-8-26 17:11
如果用BSET和BRES指令,更改PC6只需一条指令,更改操作是不可被中断的,所以不会有冲突出现!

    BSET  0x500A, #6
点赞  2010-8-26 17:16
                                 布尔变量的操作都不会被中断掉
点赞  2010-8-30 21:26
                                 标准的C语言中没有布尔变量的类型,因此布尔变量的操作是否会被中断掉,要看具体编译器的实现。
点赞  2010-8-30 21:55
编译器不保证一条C语言指令做为一个原子操作。
比如用8bit的MCU实现u32+u32的操作,中途有可能被打断,所以需要OS的设计者实现临界区或信号量等方式来辅助此类操作。
点赞  2010-8-30 22:10
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复