Short version:
Need help with C programming to select channel/s for ADC in STM32F3(Dicovery board). Please find the code that I have written as of now at the end of the question. It would be great if anyone can refer to some resources for bare metal programming.
Long Version:
I am learning programming ARM controllers; what I have is a ST32F303 Discovery board and I believe the method I am trying is called bare metal programing. I have experience in writing plain C code for AVR microcontrollers but that was quite straight forward and almost all the registers were same throughout the series. But when it came to ARM I was totally surprised to see the number of things(files) that we have to get right for even compiling the code properly. By the way I am having a Linux system so I put together the gnu arm toolchain with Eclipse.
After some research and numerous Youtube videos I finalized on two materials
STM32Snippets
NewbieHack - This chaps Youtube series which is for
Of course I am referring the reference manual of the controller in hand too. But the problem is that both of the above resources are not straight forward ones for stm32F3, But I am using the same workflow for writing for F3. Right now I am stuck with ADC as I cant figure out how to select a channel for conversion and some advice and guidance would be helpful
Here is the code I have written so far
/*
* ADC in ARM
*
* Author : Easwaran
*
*/
#include "stm32f30x_conf.h"
int main (void)
{
//ADC calibration
ADC1->CR &= ~ADC_CR_ADEN; //Disables ADC; 0 in ADCEN to disable ADC.
// ADC1->CR
ADC1->CR |= ADC_CR_ADCAL; //put a 1 in calibration register | CR is the control register for ADC1
while ((ADC1->CR & ADC_CR_ADCAL) != 0)
{
//waiting till the calibration is over; ie the bit turns 0
}
//select a clock source
RCC->AHBENR |= RCC_AHBENR_ADC12EN; // enables both ADC1 & 2
RCC->CR |= RCC_CR_HSEON;
while((RCC->CR & RCC_CR_HSERDY) != 1)
{
//to make sure the clock has started
}
ADC1->CR = ADC12_CCR_CKMODE; // this is weird
// enable ADC
ADC1-> CR = ADC_CR_ADEN;
while((ADC1->ISR & ADC_ISR_ADRD) == 0)
{
//waiting to get ready
}
//sampling time
ADC1->SMPR1 |= ADC_SMPR1_SMP0_0 | ADC_SMPR1_SMP0_1 | ADC_SMPR1_SMP0_2;
//set the channel - here internal temp register
while(1)
{
//start conversion
}
}
Related
I'm trying to play a sound, on a single speaker (mono), from a .wav file in SD card using a STM32H7 controller and freertos environment.
I currently managed to generate sound but it is very dirty and jerky.
I'd like to show the parsed header content of my wav file but my reputation score is below 10.
Most important data are :
format : PCM
1 Channel
Sample rate : 44100
Bit per sample : 16
I initialize the SAI2 block A this way :
void MX_SAI2_Init(void)
{
/* USER CODE BEGIN SAI2_Init 0 */
/* USER CODE END SAI2_Init 0 */
/* USER CODE BEGIN SAI2_Init 1 */
/* USER CODE END SAI2_Init 1 */
hsai_BlockA2.Instance = SAI2_Block_A;
hsai_BlockA2.Init.AudioMode = SAI_MODEMASTER_TX;
hsai_BlockA2.Init.Synchro = SAI_ASYNCHRONOUS;
hsai_BlockA2.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
hsai_BlockA2.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
hsai_BlockA2.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;
hsai_BlockA2.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_44K;
hsai_BlockA2.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
hsai_BlockA2.Init.MonoStereoMode = SAI_MONOMODE;
hsai_BlockA2.Init.CompandingMode = SAI_NOCOMPANDING;
hsai_BlockA2.Init.TriState = SAI_OUTPUT_NOTRELEASED;
if (HAL_SAI_InitProtocol(&hsai_BlockA2, SAI_I2S_STANDARD, SAI_PROTOCOL_DATASIZE_16BIT, 2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SAI2_Init 2 */
/* USER CODE END SAI2_Init 2 */
}
I think I set the clock frequency correctly, as I measure a frame synch clock of 43Khz (closest I can get to 44,1Khz)
The file indicate it's using PCM protocol. My init function indicate SAI_I2S_STANDARD but it's only because I was curious of the result with this parameter value. I have bad result in both cases.
And here is the part where I read the file + send data to the SAI DMA
//Before infinite loop I extract the overall file size in bytes.
// Infinite Loop
for(;;)
{
if(drv_sdcard_getDmaTransferComplete()==true)
{
// BufferRead[0]=0xAA;
// BufferRead[1]=0xAA;
//
// ret = HAL_SAI_Transmit_DMA(&hsai_BlockA2, (uint8_t*)BufferRead, 2);
// drv_sdcard_resetDmaTransferComplete();
if((firstBytesDiscarded == true)&& (remainingBytes>0))
{
//read the next BufferRead size audio samples
if(remainingBytes < sizeof(BufferAudio))
{
remainingBytes -= drv_sdcard_readDataNoRewind(file_audio1_index, BufferAudio, remainingBytes);
}
else
{
remainingBytes -= drv_sdcard_readDataNoRewind(file_audio1_index, BufferAudio, sizeof(BufferAudio));
}
//send them by the SAI through DMA
ret = HAL_SAI_Transmit_DMA(&hsai_BlockA2, (uint8_t*)BufferAudio, sizeof(BufferAudio));
//reset transmit flag for forbidding next transmit
drv_sdcard_resetDmaTransferComplete();
}
else
{
//discard header size first bytes
//I removed this part here because it works properly on my side
firstBytesDiscarded = true;
}
}
I have one track of sound quality improvment : it is to filter speaker input. Yesterday I tried cutting # 20Khz and 44khz but it cut too much the signal... So I want to try different cutting frequencies until I find the sound is of good quality. It is a simple RC filter.
But to fix the jerky part, I dont know what to do. To give you an idea on how the sound comes out, I would describe it like this :
we can hear a bit of melody
then scratchy sound [krrrrrrr]
then short silence
and this looping until the end of the file.
Buffer Audio size is 16*1024 bytes.
Thank you for your help
Problems
No double-buffering. You are reading data from the SD-card into the same buffer that you are playing from. So you'll get some samples from the previous read, and some samples from the new read.
Not checking when the DMA is complete. HAL_SAI_Transmit_DMA() returns immediately, and you cannot call it again until the previous DMA has completed.
Not checking return values of HAL functions. You assign ret = HAL_SAI_Transmit_DMAbut then never check what ret is. You should check if there is an error and take appropriate action.
You seem to be driving things from how fast the SD-card can DMA the data. It needs to be based on how fast the SAI is consuming it, otherwise you will have glitches.
Possible solution
The STM32's DMA controller can be configured to run in circular-buffer mode. In this mode, it will DMA all the data given to it, and then start again from the beginning.
It also provides interrupts for when the DMA is half complete, and when it is fully complete.
These two things together can provide a smooth data transfer with no gaps and glitches, if used with the SAI DMA. You'd read data into the entire buffer to start with, and kick off the DMA. When you get the half-complete interrupt, read half a buffer's worth of data into the first half of the buffer. When you get a fully complete interrupt, read half a buffer's worth of data into the second half of the buffer.
This is psuedo-code-ish, but hopefully shows what I mean:
const size_t buff_len = 16u * 1024u;
uint16_t buff[buff_len];
void start_playback(void)
{
read_from_file(buff, buff_len);
if HAL_SAI_Transmit_DMA(&hsai_BlockA2, buff, buff_len) != HAL_OK)
{
// Handle error
}
}
void sai_dma_tx_half_complete_interrupt(void)
{
read_from_file(buff, buff_len / 2u);
}
void sai_dma_tx_full_complete_interrupt(void)
{
read_from_file(buff + buff_len / 2u, buff_len / 2u);
}
You'd need to detect when you have consumed the entire file, and then stop the DMA (with something like HAL_SAI_DMAStop()).
You might want to read this similar question where I gave a similar answer. They were recording to SD-card rather than playing back, but the same principles apply. They also supplied their actual code for the solution they employed.
I'm facing weird behavior with FreeRTOS code.
Especially when using vTaskDelayUntil() and vTaskDelay()
I'm trying to read an input pin from my PIR sensor.
On the scope I see that the PIR is holding 3.3v high for at least 1 second.
The code below only reads my PIR input when I comment out the ' vTaskDelayUntil' line. As soon as I activate that line, PINC register is always 0.
Also when I'm sure there is 3.3v on my input pin.
static void TaskStatemachine(void *pvParameters)
{
(void) pvParameters;
TickType_t xLastWakeTime;
const TickType_t xFrequency = 100;
xLastWakeTime = xTaskGetTickCount();
for(;;)
{
printf("PINC.1 = %d\n", (PINC & (1<<1) ));
vTaskDelayUntil( &xLastWakeTime, ( xFrequency / portTICK_PERIOD_MS ) );
}
}
What is happening here?
I changed xFrequency to different values, but without any luck.
As an experiment, simplify the output thus:
putchar( (PINC & (1<<1)) == 0 ? '0' : '1' ) ;
You will then get a continuous stream of 1 or 0.
If that works with or without the delay, then it seems likely that that the task has too small a stack to support printf(). Try increasing the stack and putting the printf() back in.
I have a small TFT with touch control connected to a Raspberry Pi. The touchscreen works well within X windows.
I would like to be able to use the touchscreen outside of X windows.
Something simple, like two buttons on the screen.
I have experience with C and writing to the framebuffer using SDL. Or directly to memory.
I have no idea how to detect the input of the touchscreen and I am hoping some one could point me in the right direction.
I see the touchscreen as /dev/input/event0
It seems that you are just seeing a regular event device. What have you done so far? You might try for example Using the Input Subsystem article on Linux Journal.
What you should try at first should probably be:
/* how many bytes were read */
size_t rb;
/* the events (up to 64 at once) */
struct input_event ev[64];
rb=read(fd,ev,sizeof(struct input_event)*64);
if (rb < (int) sizeof(struct input_event)) {
perror("evtest: short read");
exit (1);
}
for (yalv = 0;
yalv < (int) (rb / sizeof(struct input_event));
yalv++)
{
//if (EV_KEY == ev[yalv].type)
printf("%ld.%06ld ",
ev[yalv].time.tv_sec,
ev[yalv].time.tv_usec,
printf("type %d code %d value %d\n",
ev[yalv].type,
ev[yalv].code, ev[yalv].value);
}
Then you should pay attention, what event types are being emitted, and then work with them further.
I am working on robot which has to control using wireless serial communication. The robot is running on a microcontroller (by burning a .hex file). I want to control it using my Linux (Ubuntu) PC. I am new to serial port programming. I am able to send the data, but I am not able to read data.
A few piece of code which is running over at the microcontroller:
Function to send data:
void TxData(unsigned char tx_data)
{
SBUF = tx_data; // Transmit data that is passed to this function
while(TI == 0) // Wait while data is being transmitted
;
}
I am sending data through an array of characters data_array[i]:
for (i=4; i<=6; i++)
{
TxData(data_array[i]);
RI = 0; // Clear receive interrupt. Must be cleared by the user.
TI = 0; // Clear transmit interrupt. Must be cleared by the user.
}
Now the piece of code from the C program running on Linux...
while (flag == 0) {
int res = read(fd, buf, 255);
buf[res] = 0; /* Set end of string, so we can printf */
printf(":%s:%d\n", buf, res);
if (buf[0] == '\0')
flag = 1;
}
It prints out value of res = 0.
Actually I want to read data character-by-character to perform calculations and take further decision. Is there another way of doing this?
Note: Is there good study material (code) for serial port programming on Linux?
How can I read from the Linux serial port...
This is a good guide: Serial Programming Guide for POSIX Operating Systems
The read call may return with no data and errno set to EAGAIN. You need to check the return value and loop around to read again if you're expecting data to arrive.
First, take a look at /proc/tty/driver/serial to see that everything is set up correctly (i.e., you see the signals you should see). Then, have a look at the manual page for termios(3), you may be interested in the VMIN and VTIME explanation.
I'm writing a system (X-Platform Windows/Linux) that talks to a custom device using an FTDI USB chip. I use their D2XX driver for device open/close/read/write. So far, so good.
I need to know when the device is disconnected so the program can respond gracefully. At present, under Windows the application receives a sudden unexpected close. Under Linux, when the device is disconnected, there is a sgementation fault.
I have found informaiton under Windows about listening for the WM_DEVICECHANGE message. However, I have not found how to detect this event under Windows. There is information for the device driver level interacting with the kernel. However, I can't figure out how to do this at an application level. The FTDI driver does not offer any such service.
The system is written using the Qt framework with C++. The device driver is FTDI's D2XX driver.
Can anyone point me in the right direction?
Thanks so much in advance!
Judy
You'll probably want to use HAL (freedesktop.org's Hardware Abstraction Layer).
In the future you will probably want to use DeviceKit. It is a project fix the many problems with HAL. It hasn't been adopted by all major distros yet though (I think just Fedora), so you probably don't want to use it right now.
Edit: As Jeach said, you can use udev also. I wouldn't suggest this, as it is much lower level, and harder to program, but if latency is very important, this might be the best option.
Although what I'm about to tell you won't directly answer your question, it may give you a hint as to your next move.
I use udev rules configured in '/etc/udev/rules.d/' which run various scripts. When a USB device gets connected/disconnected I run a script which sends a HUP signal to my binary. Since my requirements can handle a bit of lag it works perfectly fine for me.
But my point is that maybe there is a udev library you can link to and register events programmatically (instead of scripts).
Hope it helps... good luck!
I recently had a project which involved reading via an FTDI chip. I also tried using libftdi but found out that it is much simpler to use /dev/ttyUSB* for reading and writing. This way, you can use QFile('/dev/ttyUSB*') to write and read. You can also check if the device actually exists and it won't segfault. Of course, this is not a very 'Platform independent' way. To get a platform independent method, you can use a Serial library for Qt.
You obviously have to write different implementations for the different operating systems unless you want to create a thread to continuously run:
FT_ListDevices(&numDevs, nullptr, FT_LIST_NUMBER_ONLY);
and enumerate the devices if numDevs changed compared to previous checks.
If you are like me and don't really like to do that sort of continuous polling on your USB devices then you will have to target your specific operating system.
Here's a link to some sample code from FTDI:
http://www.ftdichip.com/Support/SoftwareExamples/CodeExamples/VC.htm
Example 7 shows how to detect the USB insertion and removal on windows:
http://www.ftdichip.com/Support/Documents/AppNotes/AN_152_Detecting_USB_%20Device_Insertion_and_Removal.pdf
On Linux I can personally recommend using udev.
This code is for enumerating the devices:
#include <sys/types.h>
#include <dirent.h>
#include <cstdlib>
#include <libudev.h>
#include <fcntl.h>
struct udev *udev = udev_new();
if (!udev) {
cout << "Can't create udev" <<endl;
}
struct udev_enumerate *enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "usb");
udev_enumerate_scan_devices(enumerate);
struct udev_list_entry *dev_list_entry, *devices = udev_enumerate_get_list_entry(enumerate);
struct udev_device *dev;
udev_list_entry_foreach(dev_list_entry, devices) {
const char *path;
path = udev_list_entry_get_name(dev_list_entry);
dev = udev_device_new_from_syspath(udev, path);
if( udev_device_get_devnode(dev) != nullptr ){
string vendor = (std::string) udev_device_get_sysattr_value(dev, "idVendor");
string product = (std::string) udev_device_get_sysattr_value(dev, "idProduct");
string description = (std::string)udev_device_get_sysattr_value(dev, "product");
cout << vendor << product << description << endl;
}
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
This code I put in a separate thread that waits to receive an insertion or a removal event
struct udev_device *dev;
struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");
udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", NULL);
udev_monitor_enable_receiving(mon);
int fd = udev_monitor_get_fd(mon);
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1){
debugError("Can't get flags for fd");
}
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
while( _running ){
cout << "waiting for udev" << endl;
dev = udev_monitor_receive_device(mon);
if (dev && udev_device_get_devnode(dev) != nullptr ) {
string action = (std::string)udev_device_get_action(dev);
if( action == "add" ){
cout << "do something with your device... " << endl;
} else {
string path = (std::string)udev_device_get_devnode(dev);
for( auto device : _DevicesList ){
if( device.getPath() == path ){
_DevicesList.erase(iter);
cout << "Erased Device from list" << endl;
break;
}
}
}
udev_device_unref(dev);
}
}
udev_monitor_unref(mon);
some of the functions and variables are obviously not defined when you copy/paste this code.
I keep a list of detected devices to check the path and other information like the location ID of the inserted device. I need the location ID later to FT_OpenEx via FT_OPEN_BY_LOCATION. To get the location id I read the contents of the following files:
string getFileContent(string file ){
string content = "";
ifstream readfile( file );
if( readfile.is_open() ){
getline(readfile, content );
readfile.close();
}
return content;
}
string usbdirectory = "/sys/bus/usb/devices";
string dev1content = getFileContent(usbdirectory+"/usb"+udev_device_get_sysattr_value(dev, "busnum" )+"/dev");
int dev1num = std::atoi(dev1content.substr(dev1content.find_first_of(":")+1).c_str());
string dev2content = (std::string)udev_device_get_sysattr_value(dev, "dev");
int dev2num = std::atoi(dev2content.substr(dev2content.find_first_of(":")+1).c_str());
int locationid = dev1num+dev2num+257;
I can't guarantee that the locationid is correct but it seemed to work for me until now.
Don't forget that you have two problems here :
Detecting device insertion / removal
Properly terminating your application.
The first problem has been adressed by Zifre.
But the second problem remains : your Linux app should not be segfaulting when the device is removed, and I think the two problems are unrelated : if the device is removed in the middle of a write or read system call, then those system call will return with an error before you get any notification, and this should not segfault your app.