说明
- 在开发过程中,涉及到不同优先级操作同一个变量的时候,在低优先级操作时往往需要屏蔽高优先级的代码,否则可能有一些我们不愿看到的结果
- WCH 作为国内为数不多RSIC-V的相关MCU产品被大规模商用的供应商,其riscv MCU(ch32v0系列除外)基本都加入了原子指令扩展,利用好这些扩展指令将大大方便我们的开发
适用芯片
- 所有支持A扩展的riscv芯片
一个简单的问题示例
下面我们在中断中和主循环中同时操作一个变量,这里我们使用的是GPIOB寄存器,在不屏蔽的情况下,看产生什么问题
在ch582芯片上,
->中断里面每10us翻转一次PB15,使用例程的GPIOB_InverseBits,原型为(R32_PB_OUT ^= pin)
->主循环里面不断翻转PB14,使用例程GPIOB_InverseBits
void main_loop() {
while(1) {
GPIOB_InverseBits(GPIO_Pin_14);
}
}
__INTERRUPT
__HIGH_CODE
void TMR0_IRQHandler(void) {
GPIOB_InverseBits(GPIO_Pin_15);
TMR0_ClearITFlag(TMR0_3_IT_CYC_END);
}
我们发现中断里面的控制的gpio 并没有总是按照我们想象的那样按10us去翻转,经常我们翻转过去,立刻后变了回来
传统的解决方法
->主循环翻转前关掉对应的定时器中断,或者全局中断
void main_loop() {
while(1) {
PFIC_DisableIRQ(TMR0_IRQn)
GPIOB_InverseBits(GPIO_Pin_14);
PFIC_EnableIRQ(TMR0_IRQn)
}
}
__INTERRUPT
__HIGH_CODE
void TMR0_IRQHandler(void) {
GPIOB_InverseBits(GPIO_Pin_15);
TMR0_ClearITFlag(TMR0_3_IT_CYC_END);
}
这里我们在低优先级的主循环里面操作这个寄存器时候,我们先屏蔽了会同时修改这个寄存器的中断,保证在我们读出来再回写的时候不会被打断
使用riscv原子操作指令解决
->中断里面每10us翻转一次PB15,使用例程的 amoxor.w
->主循环里面不断翻转PB14,使用原子指令 amoxor.w
static inline uint32_t __AMOXOR_W(volatile int32_t *addr,uint32_t value) {
uint32_t result;
__asm volatile ("amoxor.w %0, %2, %1" : \
"=r"(result), "+A"(*addr) : "r"(value) : "memory");
return *addr;
}
void main_loop() {
while(1) {
__AMOXOR_W(&R32_PB_OUT,GPIO_Pin_14);
}
}
__INTERRUPT
__HIGH_CODE
void TMR0_IRQHandler(void) {
__AMOXOR_W(&R32_PB_OUT,GPIO_Pin_15);
TMR0_ClearITFlag(TMR0_3_IT_CYC_END);
}
这里我们使用的原子指令,这个结果可以看出与中断屏蔽再修改的结果是一样的
后记
上面我们只是演示了riscv atomic扩展指令中的amoxor.w
指令,用来进行异或运算,实际上原子指令还有很多,大家可以直接翻看riscv的官方手册对应章节
附件
riscv 原子指令常用:与,或,异或
/**
* \brief Atomic And with 32bit value
* \details Atomically AND 32bit value with value in memory using amoand.d.
* \param [in] addr Address pointer to data, address need to be 4byte aligned
* \param [in] value value to be ANDed
* \return return memory value & and value
*/
static inline int32_t __AMOAND_W(volatile int32_t *addr, int32_t value) {
register int32_t result;
__asm volatile ("amoand.w %0, %2, %1" : \
"=r"(result), "+A"(*addr) : "r"(value) : "memory");
return *addr;
}
/**
* \brief Atomic OR with 32bit value
* \details Atomically OR 32bit value with value in memory using amoor.d.
* \param [in] addr Address pointer to data, address need to be 4byte aligned
* \param [in] value value to be ORed
* \return return memory value | and value
*/
static inline int32_t __AMOOR_W(volatile int32_t *addr, int32_t value) {
register int32_t result;
__asm volatile ("amoor.w %0, %2, %1" : \
"=r"(result), "+A"(*addr) : "r"(value) : "memory");
return *addr;
}
/**
* \brief Atomic XOR with 32bit value
* \details Atomically XOR 32bit value with value in memory using amoxor.d.
* \param [in] addr Address pointer to data, address need to be 4byte aligned
* \param [in] value value to be XORed
* \return return memory value ^ and value
*/
static inline uint32_t __AMOXOR_W(volatile int32_t *addr,uint32_t value) {
uint32_t result;
__asm volatile ("amoxor.w %0, %2, %1" : \
"=r"(result), "+A"(*addr) : "r"(value) : "memory");
return *addr;
}