ARM programming at Pascal

Once, suddenly, completely unexpectedly and without declaring war, an idea came up. And for this it was required to write and program the STM32 crystal.

But what are the problems? stm32vldiscovery was lying on a shelf and was waiting in the wings, I know programming and often write “on order”. I am friends with iron well.

First of all, the question “what to write on?” There are many programming environments, but the language is only “C”. No options. I do not consider assembler in principle. You can blink an LED, but something more complicated requires enormous labor.

But I do not know C! At all. All my life I wrote only in Pascal / Delphi. Learn the language? Have you tried to learn a language when you are over 40 years old? When work, family and a minimum of free time. When the mind is not as sharp as in youth. And to start all this for the sake of one project is no more sense than to study for the law and buy a car for the trip to the bakery in the next house.

The solution was found “mikroPascal PRO for ARM” from MikroElektronika. To be honest, I already worked with “mikroPascal PRO for PIC” at the peak of the popularity of PIC chips. Impressions were not very good. The compiler “with oddities”, the shell also did not differ in stability and a friendly interface.

It was all the more interesting to see what changed over the years and in which direction.

And so what we have on hand:
  • Board stm32f4discovery;
  • mikroPascal PRO for ARM with a license key (taken from a friend. Then will have to be returned). Without a key - a limit of 2 KB on the size of the code;
  • An engineer who was taught exclusively at Pascal at the university.

Task: master microcontroller programming without a single line of C code.

So let's get started ...

Creating a project is easy. File -> New -> New Project.

Indicate the type of microcontroller. He will ask what standard libraries we use (by default - all). We leave. "Extra" libraries during compilation will be ejected automatically.

Do not forget to set the timing. If in doubt, use one of the standard “Scheme”. This is a set of settings for timing parameters.

First, let's play with the LEDs. Let's just ask them to burn. I remind you that the LEDs are on the ports D12, D13, D14 and D15.
The code
program MyProject1;
begin
  // initialize the exit port
  GPIO_Digital_Output (@GPIOD_BASE, _GPIO_PINMASK_12 or _GPIO_PINMASK_13 or _GPIO_PINMASK_14 or _GPIO_PINMASK_15);
  // light
  SetBit (GPIOD_ODR, 12);
  SetBit (GPIOD_ODR, 13);
  SetBit (GPIOD_ODR, 14);
  SetBit (GPIOD_ODR, 15);
  while true do nop;
end.


Stop! Now a huge number of people with experience working with these microcontrollers will have a question: where is the inclusion of port clocking ?!

Very simple: when you initialize a port for input or output, clocking is turned on automatically. Do not believe it - go to View-> Statistics-> Functions Tree (my favorite!).



If there is any doubt about what the compiler does “automatically” - go and see. It’s certainly nice to look at the lit LED. But boring. Let's ask him to blink
The code
program MyProject1;
begin
  // set pins for exit and entry
  GPIO_Digital_Output (@GPIOD_BASE, _GPIO_PINMASK_12 or _GPIO_PINMASK_13 or _GPIO_PINMASK_14 or _GPIO_PINMASK_15);
  while true do begin
      if TestBit (GPIOD_ODR, 12) then ClearBit (GPIOD_ODR, 12) else SetBit (GPIOD_ODR, 12);
      Delay_1sec;
  end;
end.


There is a button on the board. Let’s launch it. When the button is pressed, the LEDs light up. And when released, they go out (though unexpectedly! - a joke). The button is on A0. For reading we use the Button function. It can suppress the bounce of contacts and takes into account the logic of the button (NC or NO). In principle, you can easily read the status of a port bit “directly”, but it is more readable.

The code
program MyProject1;
begin
  // set pins for exit and entry
  GPIO_Digital_Output (@GPIOD_BASE, _GPIO_PINMASK_12 or _GPIO_PINMASK_13 or _GPIO_PINMASK_14 or _GPIO_PINMASK_15);
  GPIO_Digital_Input (@GPIOA_BASE, _GPIO_PINMASK_0);
  while true do
    if Button (GPIOA_IDR, 0, 10, 1) then begin
      // light
      SetBit (GPIOD_ODR, 12);
      SetBit (GPIOD_ODR, 13);
      SetBit (GPIOD_ODR, 14);
      SetBit (GPIOD_ODR, 15);
    end else begin
      // extinguish
      ClearBit (GPIOD_ODR, 12);
      ClearBit (GPIOD_ODR, 13);
      ClearBit (GPIOD_ODR, 14);
      ClearBit (GPIOD_ODR, 15);
    end;
end.


Direct button polling in a loop? It is not always possible to constantly check the button. Often a crystal is busy with more important things than polling buttons. And we can just “miss” the moment of pressing. It will be more correct when the button is pressed to generate an interrupt, where we process the event.
The code
program MyProject1;
procedure INT_EXTI0 (); iv IVT_INT_EXTI0; ics ICS_AUTO;
begin
  EXTI_PR: = $ FFFF; // clear flag
  if TestBit (GPIOD_ODR, 12) then ClearBit (GPIOD_ODR, 12) else SetBit (GPIOD_ODR, 12);
end;
begin
  // configure the conclusions
  GPIO_Digital_Output (@GPIOD_BASE, _GPIO_PINMASK_12);
  GPIO_Digital_Input (@GPIOA_BASE, _GPIO_PINMASK_0);
  EXTI_IMR: =% 1; Allow interruption from the desired input
  EXTI_FTSR: = $ FFFF; Arrival interrupt 0
  NVIC_IntEnable (IVT_INT_EXTI0); // Enable External interrupt
  EnableInterrupts ();
  while true do;
end.



What do we have in the crystal? Timer? Let's play with the PWM mode. What is the conclusion? The 4th timer comes to the conclusion that it is connected to the LED. So what are we waiting for?
The code
program MyProject1;
var
  ratio, tmp: word;
begin
  ratio: = PWM_TIM4_Init (25000);
  tmp: = 0;
  PWM_TIM4_Set_Duty (0, _PWM_NON_INVERTED, _PWM_CHANNEL1);
  PWM_TIM4_Start (_PWM_CHANNEL1, @ _GPIO_MODULE_TIM4_CH1_PD12);
  while true do begin
    PWM_TIM4_Set_Duty (ratio-tmp, _PWM_INVERTED, _PWM_CHANNEL1);
    Inc (tmp);
    if tmp> ratio then tmp: = 0;
    Delay_1ms;
  end;
end.



The timer can be used not only for PWM. Hands just itch to use a timer to perform periodic actions. Let's for example blink a timer. The timer will pull the interrupt, and the interrupt itself will control the LED.

To do this, you need to long and hard to consider the coefficients for writing to the timer registers. And if not, use the free Timer Calculator program from the manufacturer’s website.

We drive in the initial data and get the finished timer code. Something like this:



The code
program ETXI;
procedure Timer2_interrupt (); iv IVT_INT_TIM2;
begin
  TIM2_SR.UIF: = 0;
  if TestBit (GPIOD_ODR, 12) then ClearBit (GPIOD_ODR, 12) else SetBit (GPIOD_ODR, 12);
end;
procedure InitTimer2 ();
begin
  RCC_APB1ENR.TIM2EN: = 1;
  TIM2_CR1.CEN: = 0;
  TIM2_PSC: = 2239;
  TIM2_ARR: = 62499;
  NVIC_IntEnable (IVT_INT_TIM2);
  TIM2_DIER.UIE: = 1;
  TIM2_CR1.CEN: = 1;
end;
begin
  // set the pins to exit
  GPIO_Digital_Output (@GPIOD_BASE, _GPIO_PINMASK_12); // Enable digital output on PORTD
  // // Timer2 Prescaler: 2239; Preload = 62499; Actual Interrupt Time = 1
  InitTimer2 ();
  while (TRUE) do nop; // Infinite loop
end.


UART? Easy too. We connect the UART-USB adapter. For example, let's display the processor temperature every second.
The code
program MyProject1;
var 
  uart_tx: string [20];
  tmp: integer;
  tmp1: real;
begin
  UART2_Init (9600); // Configure UART at 9600 bps
  ADC1_Init ();
  Delay_ms (100); // We expect stabilization of UART
  TSVREFE_bit: = 1;
  UART2_Write_Text ('Hello!');
  UART2_Write (13);
  UART2_Write (10);
  while (TRUE) do
    begin
      tmp: = ADC1_Read (16); // Read the temperature data
      tmp1: = (3300 * tmp) / 4095; // Recalculate mV. Based on: 3.3 V = 4096
      tmp1: = ((tmp1-760) /2.5) +25; // Count in degrees. V25 = 0.76VS = 2.5mV / C
      FloatToStr (tmp1, uart_tx);
      uart_tx: = 'T:' + uart_tx + 'C';
      UART2_Write_Text (uart_tx);
      UART2_Write (13); UART2_Write (10);
      Delay_ms (1000);
    end;
end.


We launch ... Case temperature -27C. This is despite the fact that in room 23C. In principle, nothing strange. The manufacturer himself does not recommend using this sensor to measure absolute temperatures, but use it only to measure temperature increase / decrease. The output voltage of the temperature sensor is shifted from chip to chip, and the shift can reach 45 degrees.

Stop! We forgot to initialize the GPIO? And here is another “proprietary” feature: when initializing a module working “on pins”, pins are initialized automatically. Doubts? Go to View-> Statistics-> Functions Tree (my favorite!).



As you can see, the conclusions are automatically transferred to the alternative mode and clocking is turned on for them.

And finally - get to USB. The easiest. Let's make a HID device that simply returns the received message.
The code
program HID_Read_Write_Polling;
var cnt, kk: byte;
var readbuff: array [64] of byte;
var writebuff: array [64] of byte;
begin
  HID_Enable (@ readbuff, @ writebuff);
  while TRUE do
    begin
      USB_Polling_Proc (); // Call this routine periodically
      kk: = HID_Read ();
      if (kk <> 0) then
      begin
        for cnt: = 0 to 63 do
          writebuff [cnt]: = readbuff [cnt];
        HID_Write (@ writebuff, 64);
      end;
    end;
end.


Or a similar action using Interrupt:

The code
program HID_Read_Write;
var cnt: byte;
var readbuff: array [64] of byte;
var writebuff: array [64] of byte;
procedure USB1Interrupt (); iv IVT_INT_OTG_FS;
begin
   USB_Interrupt_Proc ();
end;
begin
  HID_Enable (@ readbuff, @ writebuff);
  while TRUE do
    begin
      while (HID_Read () = 0) do
        ;
      for cnt: = 0 to 63 do
        writebuff [cnt]: = readbuff [cnt];
      while (HID_Write (@ writebuff, 64) = 0) do
        ;
    end;
end.


For this program to work properly (and any program that uses the standard USB library), you need to generate the USBdsc.mpas file. It contains the USB parameters for this device. I must say right away, from the “Tools” menu there is a utility “HID Terminel”. This utility allows you to generate the correct file, sufficient to run examples and simple applications. And if something more complicated - look at the description of the USB bus. And edit the file. Fortunately, he is generously provided with comments.



Using this utility, we check the operation of the example:



Of course, there will now be a bunch of grunts about the “inefficiency” of the code. But look - a rather “heavy” example with USB HID takes up 1.7% of flash and 1.2% of operative memory.



And a little digression.

All this “magical” machinery only works if the clock system is properly configured. If the absolutely correct code starts to “fool”, the external interfaces begin to “disappear” and the USB interface connection generates the message “Device ID Request Error” - open the project settings and check the clock again.



All this shows that knowledge of C is not a prerequisite for the effective programming of the microcontroller. And the presence of standard libraries significantly reduces the entry threshold for untrained people. In this case, the code is quite compact and readable.

A detailed description of the programming environment - will be written in a separate article.

PS: Actually, I know C (C ++). I read and understand the code freely. But writing programs in this language is “uncomfortable” for me. Brains automatically issue output in a pascal-like code.

Also popular now: