SmallWhite · 2022年11月27日 · 广东

【GD32F427开发板试用】位带操作实现多线程下的跑马灯

一、位带操作

作用:对某一位或者几个连续的位进行操作

前言

我们在使用GD32等单片机时使用到的固件库编程,很经常会遇到位带操作,固件库对外设寄存器的每个关键bit都做了定义,例如宏定义中的:
image.png
写1:用1左移n位后和具体位进行|=的运算得到
写0:用1左移n位后取反得到第n位的0值然后和具体位进行&=的运算得到

/* GPIO_OCTL */
#define GPIO_OCTL_OCTL0            BIT(0)               
#define GPIO_OCTL_OCTL1            BIT(1)               

例如上述代码中的端口输出控制寄存器(GPIOx_OCTL),和待会还要用到的端口输入状态寄存器(GPIOx_ISTA),想要实现对GPIO的某一位(某一pin)操作的话,需要知道这两个寄存器的地址
1.端口输入状态寄存器(GPIOx_ISTAT, x=A..I)
地址偏移:0x10
复位值:0x0000 XXXX
2.端口输出控制寄存器(GPIOx_OCTL, x=A..I)
地址偏移:0x14
复位值:0x0000 0000

所以说每个端口x的输入状态以及输出控制寄存器的地址=端口x基地址+寄存器的偏移地址:

#define output_offset 0x14
#define input_offset  0x10

//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA+output_offset) //0X40020000U+0x0U+output_offset
#define GPIOB_ODR_Addr    (GPIOB+output_offset) //0X40020000U+0x00000400U+output_offset
#define GPIOC_ODR_Addr    (GPIOC+output_offset) //0X40020000U+0x00000800U+output_offset
#define GPIOD_ODR_Addr    (GPIOD+output_offset) 
#define GPIOE_ODR_Addr    (GPIOE+output_offset) 
   
#define GPIOA_IDR_Addr    (GPIOA+input_offset) 
#define GPIOB_IDR_Addr    (GPIOB+input_offset)  
#define GPIOC_IDR_Addr    (GPIOC+input_offset)
#define GPIOD_IDR_Addr    (GPIOD+input_offset) 
#define GPIOE_IDR_Addr    (GPIOE+input_offset)

二、实现位带操作的前提条件

1.支持位带操作的两个内存区的范围是:
0x2000_0000‐0x200F_FFFF(SRAM区中的最低1MB)
0x4000_0000‐0x400F_FFFF(片上外设区中的最低1MB)
而GPIOA~GPIOE的地址刚好在以上范围内(0x4002 0000~0x4002 1000)

2.查阅GD32的数据手册说明:
3.png

3.对于片上外设位带区的某个bit,我们假设记它所在字节地址为A,位序号为n,则该bit在别名区的地址为:
Addr=0x42000000+(A-0x40000000)32+4n

4.回到代码中实现:
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
整体宏定义代码如下:

//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

#define output_offset 0x14
#define input_offset  0x10

//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA+output_offset) //0X40020000U+0x0U+output_offset
#define GPIOB_ODR_Addr    (GPIOB+output_offset) //0X40020000U+0x00000400U+output_offset
#define GPIOC_ODR_Addr    (GPIOC+output_offset) //0X40020000U+0x00000800U+output_offset
#define GPIOD_ODR_Addr    (GPIOD+output_offset) 
#define GPIOE_ODR_Addr    (GPIOE+output_offset) 
   
#define GPIOA_IDR_Addr    (GPIOA+input_offset) 
#define GPIOB_IDR_Addr    (GPIOB+input_offset)  
#define GPIOC_IDR_Addr    (GPIOC+input_offset)
#define GPIOD_IDR_Addr    (GPIOD+input_offset) 
#define GPIOE_IDR_Addr    (GPIOE+input_offset)

//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入
void Led_Flash_Task(void *p_arg)
{
    OS_ERR err;
    while(1)
    {
        running_led_out=!running_led_out;
        OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1s
    }
}

5.现在跑个UCOS多线程下的跑马灯看看:
4.jpg

可以看到正常运行串口发送线程和跑马灯线程,UCOS部分的移植也是看网上的,具体步骤还没时间去完全理清思路,现在就是能跑的状态,目前也是发的第一篇文章,如有错误请多多指教!

推荐阅读
关注数
10707
内容数
187
中国高性能通用微控制器领域的领跑者兆易创新GD系列芯片技术专栏。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息